petri_net_nn.ctl¶
ctl ¶
Computation Tree Logic (CTL) model checking for bounded Petri nets.
CTL extends propositional logic with temporal operators that quantify over the tree of possible futures rooted at a state. Two kinds of operators compose:
- Path quantifiers —
A(for all future paths) andE(there exists a future path). - Temporal operators —
X(next state),F(some state in the future),G(every state in the future),U(until).
Combined, you get pairs like AG, EF, AF, EG, plus
the binary A[φ U ψ] and E[φ U ψ]. The pairs answer
questions like:
AG (request → AF response)— every request is eventually followed by a response on every path. The classic liveness property.AG ¬deadlock— on every path, every reachable state has a successor. Safety from deadlock.EF goal— some path eventually reaches the goal. Reachability.EG running— some path keeps the system running forever. Useful for checking non-trivial control loops.
For a bounded Petri net, CTL model checking is decidable — the reachability graph is finite, and the standard fixed-point algorithms terminate. PETRA's checker:
- Builds the reachability graph from the net (reuses
:func:
petri_net_nn.bisimulation.reachability_graph). - Recursively evaluates the formula bottom-up, computing for each sub-formula the set of states satisfying it.
- For temporal operators, the satisfying set is a fixed point
— least fixed point for
EFandEU(eventually-true semantics), greatest fixed point forEG(always-true semantics). The other operators are derived from these three primitives plus boolean connectives.
The AST is built from six primitive nodes — Atom, Not,
And, Or, EX, EU, EG — plus derived
constructors (AX, AG, AF, EF, AU) that expand
to combinations of the primitives via the standard CTL
equivalences. This keeps the model-checking algorithm small (only
six primitive cases) while giving users the full CTL vocabulary.
Scope: bounded Petri nets only. The reachability graph must be
finite; max_states (default 10,000) guards against unbounded
inputs.
Atomic propositions: predicates over the current marking. The
:func:place_has_token, :func:place_empty, :func:place_count_eq,
:func:place_count_ge, :func:transition_enabled helpers cover the
common cases. For anything else, build an Atom directly with
a custom predicate (marking_dict) -> bool.
Atom
dataclass
¶
Atomic proposition. predicate is evaluated against every
reachable marking; label is a human-readable name carried
only for display and debugging.
And
dataclass
¶
Logical conjunction. Stored as a binary node; chain by
nesting (And(a, And(b, c))) or use the :func:conj
helper to fold over a sequence.
EX
dataclass
¶
EX φ — there exists a successor state in which φ holds.
The next-state existential.
EU
dataclass
¶
E[φ U ψ] — there exists a path along which φ holds at
every step until a step where ψ holds. The existential until.
EG
dataclass
¶
EG φ — there exists a path along which φ holds at
every state forever. The existential globally.
CTLResult
dataclass
¶
Outcome of a CTL model-checking query.
Attributes¶
holds_at
The full set of reachable markings at which the formula
is satisfied.
holds_at_initial
Whether the net's initial marking is in holds_at —
the standard yes/no answer for the property.
counterexample
A specific reachable marking that violates the formula,
or None if the formula holds at every reachable
marking. Useful for AG φ failures: the counterexample
is one of the states where φ fails to hold.
AX ¶
AX φ — for all successors, φ holds. Expands to
¬EX ¬φ: nothing exists in the next-state set that
violates φ.
EF ¶
EF φ — eventually on some path, φ holds. Expands to
E[true U φ]: there is a path along which the trivial
proposition true holds until φ holds (the true condition
is a non-restrictive prefix).
Source code in petri_net_nn/ctl.py
AF ¶
AF φ — eventually on every path, φ holds. Expands
to ¬EG ¬φ: there's no path that keeps φ false forever.
AG ¶
AG φ — always on every path, φ holds. Expands to
¬EF ¬φ: no reachable state violates φ. The classic
safety operator.
AU ¶
A[φ U ψ] — on every path, φ holds at every step
until ψ holds. Expands to the standard duality:
¬E[¬ψ U (¬φ ∧ ¬ψ)] ∧ ¬EG ¬ψ. The left conjunct rules
out any path with a ψ-violation reached without satisfying
¬φ-then-¬ψ first; the right conjunct rules out paths where
ψ never holds.
Source code in petri_net_nn/ctl.py
conj ¶
Fold a sequence of formulae into a right-associated
conjunction. Convenience for users — conj(a, b, c) is
nicer to read than And(a, And(b, c)).
Source code in petri_net_nn/ctl.py
disj ¶
Fold a sequence of formulae into a right-associated
disjunction. Mirror of :func:conj.
Source code in petri_net_nn/ctl.py
implies ¶
Material implication φ → ψ desugared to ¬φ ∨ ψ.
Reads cleaner than the equivalent Or(Not(...), ...) at
the call site, which matters for compound safety / liveness
formulae like AG (request → AF response).
Source code in petri_net_nn/ctl.py
place_has_token ¶
Atom that holds in every marking with at least one token
at place. The most common atomic proposition for workflow
nets — "is there work in this state right now?".
Source code in petri_net_nn/ctl.py
place_empty ¶
Atom that holds in every marking where place has zero
tokens. Dual of :func:place_has_token. Useful for
pre-condition checks — "the lock is free", "the buffer is
empty".
Source code in petri_net_nn/ctl.py
place_count_eq ¶
Atom that holds when place has exactly n tokens.
Useful for batching / quorum predicates — "the buffer holds
exactly six items".
Source code in petri_net_nn/ctl.py
place_count_ge ¶
Atom that holds when place has at least n tokens.
Useful for batching thresholds — "there are enough items to
process a batch".
Source code in petri_net_nn/ctl.py
transition_enabled ¶
Atom that holds when transition is enabled — its
preset has enough tokens and its inhibitor preset is empty
and (if applicable) its guard accepts. Defers to
:meth:PetriNet.is_enabled so the predicate stays consistent
with everything else the token-game does.
Source code in petri_net_nn/ctl.py
check_ctl ¶
Model-check formula against net.
Builds the reachability graph and recursively evaluates the
formula bottom-up. Returns a :class:CTLResult with the full
satisfying set, the initial-marking answer, and a sample
counterexample marking if the formula fails to hold at every
reachable marking.
Source code in petri_net_nn/ctl.py
satisfies ¶
Convenience wrapper: does the net's initial marking
satisfy formula? Equivalent to
check_ctl(net, formula).holds_at_initial but reads better
at call sites where only the boolean answer matters.