Generic State Machines

The generic classes TransitionSystem, Acceptor, and Transducer provide the functionality of transition systems and finite-state machines. They rely on enumeration-type generic parameters representing the sets of states and the input and output alphabets.

StateMachines Namespace
Notes on the Generic Parameters
Delegate ValidStateTransitionAction
Delegate InvalidStateTransitionAction
NotAState and NotAnAlphabetElement Attributes
Class TransitionSystem
Public Constructor
Public Methods
ResetState
AddValidStateTransition
AddValidStateTransitionChain
AddInvalidStateTransition
IsTransitionValid
TryTransitionTo
Labyrinth
FindDeadEnds
Public Properties
CurrentState
LongestPaths
MaximumPaths
Notes
Delegate AcceptorTransitionAction
Delegate InvalidAcceptorInputHandler
Class Acceptor
Inherited Public Members
Public Constructor
Public Methods
AddStateTransitionFunctionPart
AddInvalidInput
TransitionSignal
Delegate MooreMachineOutputAction
Delegate MealyMachineOutputAction
Class Transducer
Inherited Public Members
Public Constructor
Public Methods
AddOutputFunctionPart
Signal
Examples
Room Door Example
Non-Enumeration Example
Grid Example
Zoo Example
Car Transducer Example
Compatibility and Build

StateMachines Namespace

Inheritance diagram:

System.ObjectTransitionSystemAcceptorTransducer

Notes on the Generic Parameters

The types of the namespace StateMachines depend on the generic-type parameters STATE, INPUT, and OUTPUT.

Typically, a generic-type parameter could be of any enumeration type with its enumeration members representing states. However, it is not a strict rule. In principle, any type with public static fields can be used for the STATE type. In this case, the public static fields will represent the transition system states. Please see the example illustrating the use of a non-enumeration type STATE.

Delegate ValidStateTransitionAction

An instance of the delegate provides a way to define a side effect of a valid transition between two states, startState, and finishState. For example, in the hardware automation applications, it can operate the hardware.

public delegate void ValidStateTransitionAction<STATE>(STATE startState, STATE finishState);

The delegate instance is used in the TransitionSystem.AddValidStateTransition and TransitionSystem.AddValidStateTransitionChain call.

Delegate InvalidStateTransitionAction

An instance of the delegate provides the optional information on an invalid transition between two states, startState and finishState. Its return string value can be used to explain why an attempted transition between the states is considered invalid.

public delegate string InvalidStateTransitionAction<STATE>(STATE startState, STATE finishState);

The delegate instance is used in the TransitionSystem.AddInvalidStateTransition call.

NotAState and NotAnAlphabetElement Attributes

These attributes can be used to mark some enumeration-type members. It excludes them from the set of states or the input or output alphabets. It can be useful to create members irrelevant to the transition system behavior but used for some calculations. For example, such a member can be a bitwise OR combination of several states.

[System.AttributeUsage(System.AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
public class NotAStateAttribute : ExcludeAttribute {}
[System.AttributeUsage(System.AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
public class NotAnAlphabetElementAttribute : ExcludeAttribute {}

Formally, these attributes can be used interchangeably and applied to any enumeration-member public field. They are made different only for the clarity of the terms “state” and “alphabets”. The effect of these attributes is the same: the members marked with one of these attributes are skipped when building a set of states of an alphabet.

Example:

enum NotAStateAttribute {
    Locked = 1, Closed = 2, Opened = 4,
    OpenedInside = 8, ClosedInside = 16, LockedInside = 32,
    [NotAState] Inside = OpenedInside | ClosedInside | LockedInside };

An attempt to perform a state transition to a NotAState enumeration value using TryTransitionTo will throw an exception.

Class TransitionSystem

The class TransitionSystem provides a way to create a transition system based on any enumeration type representing a set of states. For the class instance, a state transition graph can be created. The instance can walk between states using permitted transitions, and the optional delegate representing the side effect of a transition can be called. An attempt to perform an invalid transition can provide optional information, explaining why the transition is invalid. The class also implements several graph algorithms, including NP-hard ones, such as finding the longest possible paths.

public class TransitionSystem<STATE> : TransitionSystemBase<STATE>{/* … */}

Public Constructor

Creates an instance of TransitionSystem.

Parameter: STATE initialState = default. Defines the initial state of the transition system. See also ResetState.

Note that the use of a non-default initial state can be critically important when a default value for the STATE type is not a state. It can happen if this default value is excluded using the [NotAState] attribute. Another case of a non-state value is demonstrated by the non-enumeration example.

public TransitionSystem(STATE initialState = default);

Public Methods

ResetState

Unconditionally jumps to the initial state defined by the constructor. No delegate instances are called even if there is a valid transition between the current state and the initial state.

public STATE ResetState();

AddValidStateTransition

Adds an edge to the transition system’s transition graph between the states startState and finishState. If the parameter undirected is true, it makes both transitions valid, from startState to finishState, and from finishState to startState.

Optionally, a delegate instance of the type StateTransitionAction is specified. In this case (when the delegate instance is not null), the delegate’s method will be called on each call to TryTransitionTo between corresponding states.

Note that the transition function rules are not respected by the methods Accessor.TransitionSignal and Transducer.Signal.

public void AddValidStateTransition(STATE startState, STATE finishState, StateTransitionAction<STATE> action, bool undirected = false);

AddValidStateTransitionChain

Adds a transition chain to the transition system’s transition graph. Note that if the parameter undirected is true, it makes all the graph edges between the adjacent pairs of states undirected, that is, the transitions in both directions become valid.

Optionally, a delegate instance of the type StateTransitionAction is specified. In this case (when the delegate instance is not null), the same delegate’s method will be called on each call to TryTransitionTo between corresponding states in the chain.

public int AddValidStateTransitionChain(StateTransitionAction<STATE> action, bool undirected = false, params STATE[] chain)

AddInvalidStateTransition

Defines the information in an invalid state transition between the states startState and finishState. This information is used in the return of the call to IsTransitionValid and TryTransitionTo. See also InvalidStateTransitionAction.

Note that the transition function rules are not respected by the methods Accessor.TransitionSignal and Transducer.Signal.

public void AddInvalidStateTransition(STATE startState, STATE finishState, InvalidStateTransitionAction<STATE> action)

IsTransitionValid

Checks if a transition between the state startState and finishState is valid. In addition to the status isValid, returns validityComment that can explain why the transition is invalid. This information is optional, defined using AddInvalidStateTransition.

public (bool isValid, string validityComment) IsTransitionValid(STATE startState, STATE finishState);

TryTransitionTo

Tries to perform the transition from the current state to the state state. Returns the success status of the transition and the comment on the transition validity validityComment. If the transition is invalid, validityComment can provide some information on the reason. This information is optional, see AddInvalidStateTransition.

If the transition is successful, the current state becomes state.

An attempt to perform the transition to the same state is considered valid. In this case, no delegate methods are called.

public (bool success, string validityComment) TryTransitionTo(STATE state);

Labyrinth

Returns all the permitted paths between the states start and finish. If shortest is true the method still finds all paths, but then it determines the shortest path length and returns only the array of paths of the minimal length.

public STATE[][] Labyrinth(STATE start, STATE finish, bool shortest = false);

FindDeadEnds

Finds all “dead ends”, the states not visited along any paths between the states start and finish. Returns allPaths, all permitted paths between start and finish, and deadEnds.

public (STATE[][] allPaths, STATE[] deadEnds) FindDeadEnds(STATE start, STATE finish);

Another form of FindDeadEnds assumes that the parameter allPaths is the array of paths obtained through the prior call to Labyrinth between the state start and some other state finish. In this case, the allPaths array will contain all paths ending with the state finish passed as the second parameter to Labyrinth.

public STATE[] FindDeadEnds(STATE start, STATE[][] allPaths);

Public Properties

CurrentState

Returns the current state of the transition system. Before the very first transition of the instance, the current state is the one defined by the constructor.

public STATE CurrentState;

LongestPaths

Finds the longest path length in the state graph. When this number is found, it finds all the possible paths with this length and returns all these paths.

public (int numberOfPaths, int longestPathLength, STATE[][] longestPaths) LongestPaths; //NP-hard

MaximumPaths

Finds the maximum number of paths between all STATE-to-STATE pairs. When this number is found, it returns the pairs having this number of paths between them.

public (int maximumNumberOfPaths, (STATE start, STATE finish)[] pairsAtMax) MaximumPaths; //NP-hard

Notes

All paths returned by Labyrinth, FindDeadEnds, and LongestPaths are represented as arrays STATE[] or arrays of paths STATE[][], each path represented as an array of STATE. In the array of states, the starting state of the path is not included, and the final state of the path is included.

In other words, given a permitted path between the current state of a transition system and the other states in the array, we can perform the chain of transitions, sequentially to all the states in the array, and all the transitions will be valid.

Example:

var solution = TransitionSystem.Labyrinth(VisitorState.Entry, VisitorState.Exit);
if (solution.Length > 0)
    foreach (var state in solution[0]) // or any other solution path
        TransitionSystem.TryTransitionTo(state); // always valid

Delegate AcceptorTransitionAction

The delegate AcceptorTransitionAction is used to define an acceptor’s state-transition function using AddStateTransitionFunctionPart.

public delegate STATE AcceptorTransitionAction<STATE, INPUT> (STATE state, INPUT input);

Delegate InvalidAcceptorInputHandler

The delegate InvalidAcceptorInputHandler is used to provide additional information on invalid input using the method AddInvalidInput.

public delegate string InvalidAcceptorInputHandler<STATE, INPUT> (STATE state, INPUT input);

Class Acceptor

The class Acceptor implements the functionality of a finite-state acceptor.

public class Acceptor<STATE, INPUT> : TransitionSystem<STATE> {/* … */}

Inherited Public Members

TransitionSystem.ResetState, TransitionSystem.AddValidStateTransition, TransitionSystem.AddValidStateTransitionChain, TransitionSystem.AddInvalidStateTransition, TransitionSystem.IsTransitionValid, TransitionSystem.TryTransitionToTransitionSystem., TransitionSystem.LabyrinthTransitionSystem., TransitionSystem.FindDeadEnds, TransitionSystem.CurrentState, TransitionSystem.LongestPaths, TransitionSystem.MaximumPaths,

Public Constructor

public Acceptor(STATE initialState = default) : base(initialState = default);

Public Methods

AddStateTransitionFunctionPart

public void AddStateTransitionFunctionPart(
    INPUT input, STATE state,
    AcceptorTransitionAction<STATE, INPUT> handler);

AddInvalidInput

The method AddInvalidInput is used to provide additional information on the state and input pair. It is used by TransitionSignal: when these methods find that a state-transition function is not implemented for a CurrentState and input pair, it looks for the invalid acceptor input information to provide an explanation of why the function is not implemented for this pair. If this additional information is not found, TransitionSignal returns a general issue description.

public void AddInvalidInput(INPUT input, STATE state, InvalidAcceptorInputHandler<STATE, INPUT> handler);

TransitionSignal

The method TransitionSignal looks at the state-transition function using the combination of input and CurrentState. If possible, it performs the transition to the state according to the state-transition function, otherwise, it reports the issues.

public record TransitionSignalResult(STATE State, bool Success, string Comment);

public TransitionSignalResult TransitionSignal(INPUT input)andler<STATE, INPUT> handler);

Delegate MooreMachineOutputAction

The delegate MooreMachineOutputAction is used to define an output function for a transducer using the Moore model, see AddOutputFunctionPart.

public delegate OUTPUT MooreMachineOutputAction<STATE, INPUT, OUTPUT> (STATE state);

Delegate MealyMachineOutputAction

The delegate MealyMachineOutputAction is used to define an output function for a transducer using the Mealy model, see AddOutputFunctionPart.

public delegate OUTPUT MealyMachineOutputAction<STATE, INPUT, OUTPUT> (STATE state, INPUT input);

Class Transducer

The class Transducer implements the functionality of a finite-state transducer.

public class Transducer<STATE, INPUT, OUTPUT> : Acceptor<STATE, INPUT> {/* … */}

Inherited Public Members

TransitionSystem.ResetState, TransitionSystem.AddValidStateTransition, TransitionSystem.AddValidStateTransitionChain, TransitionSystem.AddInvalidStateTransition, TransitionSystem.IsTransitionValid, TransitionSystem.TryTransitionToTransitionSystem., TransitionSystem.LabyrinthTransitionSystem., TransitionSystem.FindDeadEnds, TransitionSystem.CurrentState, TransitionSystem.LongestPaths, TransitionSystem.MaximumPaths,

Accessor.AddStateTransitionFunctionPart, Accessor.AddInvalidInput, Accessor.TransitionSignal

Public Constructor

public Transducer(STATE initialState = default) : base(initialState);

Public Methods

AddOutputFunctionPart

There are two functions under the same name AddOutputFunctionPart: one is used to develop a Moore finite-state machine, and another one — to develop a Mealy finite-state machine.

Moore version:

public void AddOutputFunctionPart(
    INPUT input, STATE state,
    MooreMachineOutputAction<STATE, INPUT, OUTPUT> handler);

Mealy version:

public void AddOutputFunctionPart(
    INPUT input, STATE state,
    MealyMachineOutputAction<STATE, INPUT, OUTPUT> handler);

If an output function of a state machine uses a mixture of Moore and Mealy functions for different function arguments, it is, formally speaking, a Mealy machine.

See also: MooreMachineOutputAction, MealyMachineOutputAction.

Signal

The method Signal looks for both state-transition function and output function using the combination of input and Acceptor.CurrentState. If possible, it performs the transition to the state according to the state-transition function and calculates the Output, otherwise, it reports the issues.

In other words, it calls Acceptor.TransitionSignal, takes its output, then uses the output function, and combines the results.

public record SignalResult(
    TransitionSignalResult TransitionResult,
    OUTPUT Output, bool OutputSuccess = true, string OutputComment = null);

public SignalResult Signal(INPUT input);

Examples

Room Door Example

Basic transition system example with 6 states forming a linear transition graph. Demonstrates valid transitions and attempts to perform an invalid transition.

Source code
Description

Non-Enumeration Example

This example shows the use of a non-enumerable type as a TransitionSystem generic parameter. The type double is used as a source of the state set.

Source code
Description

Grid Example

This example demonstrates that NP-hard problems can be pretty hard even for the 24 states. The states in this example form a 6x4 grid with the permitted undirected transition between all neighboring cells. The example demonstrates the calculation of the longest path between two states and the maximum number of possible paths.

Maximum number of paths between a pair of states is 5493.
Total number of paths: 1603536, longest path length: 23.

Source code
Description

Zoo Example

The Zoo example demonstrates the transition system representing the visitor location at the zoo. This example demonstrates all the TransitionSystem features.

Source code
Description

Car Transducer Example

The Car Transducer Example is a comprehensive example demonstrating Acceptor and Transducer features on a highly simplified model of a car with automatic transmission, keyless entry, only three gearbox positions, and lights.

Source code
Description

Compatibility and Build

The solution is compatible with any .NET version. For the earliest common denominator, I use v. 5; by the moment of writing, I tested the versions from 5 to 9.

To change the target .NET version, edit the file “Directory.Build.props”, the property <TargetFramework>. Also, the code can be used for some .NET Framework versions. However, it will require re-creation of project files, which is not hard to do.

The build does not require Visual Studio, Visual Studio Code, or any other IDE. It can be done using the commands

dotnet build -c Debug
dotnet build -c Release

With Visual Studio or Visual Studio Code, the code can be built in the usual ways. Besides, for Visual Studio Code, a launch configuration “Build All” and a task “Build All” are provided.

This documentation is generated from the extended Markdown documentation using Extensible Markdown for Visual Studio Code.