Transition Systems
States, actions, transitions
A transition system is one of the simplest models of "things that change". It
is a triple where:
- is a set of states (the configurations a system can be in)
- is a set of actions (the things you can do)
- assigns to each state-action pair a set of
possible successor states
In words: from state , doing action lands you in some state in .
If for every the system is deterministic — every
action has at most one result. Otherwise it is non-deterministic.
From a transition system we can build the result relation which, given a
state, returns the set of states reachable by a single action (ignoring which
action):
Why care? Transition systems model essentially everything that steps:
Turing machines, -calculus, AI planning problems, network protocols,
game states, agents in an environment. Once you have states and transitions, you
can ask: "can I reach state from ?", "is every reachable state safe?",
"do two paths always converge?". Those questions become formal.
Mathematical structures can be implemented in programming via record data structures (Def 1.2). In C:
struct grule {
char *lhs;
char **rhs;
int rhs_len;
};
struct grammar {
char **nonterminals;
int n_n;
char **terminals;
int n_t;
struct grule *rules;
int n_p;
char *start;
};
The struct grule represents a single production rule: a left-hand side nonterminal lhs and a list rhs of right-hand side symbols. The struct grammar represents a full PSG (Def 1.1): nonterminals , terminals , productions , and start symbol .
Usage example:
struct grule r = { "S", (char*[]){"NP", "VP"}, 2 };
struct grammar G = {
(char*[]){"S", "NP", "VP"}, 3,
(char*[]){"john", "likes", "mary"}, 3,
&r, 1,
"S"
};
C structs give the data of a structure but not its axioms (e.g. 'every production has a nonterminal as its head') — those must be enforced by convention or runtime checks.
Object-oriented languages capture mathematical structures as classes, with inheritance modeling the is-a relationship between structures.
abstract class Magma {
def op(a: Elem, b: Elem): Elem
}
class Semigroup extends Magma {
// Adds associativity axiom:
// forall a b c: op(op(a,b),c) == op(a,op(b,c))
}
class Monoid extends Semigroup {
def identity: Elem
// Adds identity axiom:
// forall a: op(a, identity) == a
}
class Group extends Monoid {
def inverse(a: Elem): Elem
// Adds inverse axiom:
// forall a: op(a, inverse(a)) == identity
}
Inheritance models the theory graph: Group is-a Monoid is-a Semigroup is-a Magma. Each subclass adds one axiom. Methods like op, identity, inverse implement the structure's operations.
However, OOP cannot enforce axioms at compile time (unlike dependent types or proof assistants). So OOP captures the shape of mathematical structures, but the axioms remain a programmer's responsibility.
A phrase-structure grammar can be modelled as a transition system (Def 3.1) where:
- States — all sentential forms.
- Actions — the production rules are the actions.
- Transition model — applying action (production rule) to state produces new state(s) in one step.
For a deterministic sub-grammar, for all . A string is in iff (zero or more steps from the start state).
Why this matters. Once we see defined as reachability in a transition system, all transition-system theory applies: termination, confluence, complexity. We get a unified vocabulary for grammars, automata, ARS, and program semantics.