petri_net_nn.bisimulation¶
bisimulation ¶
Strong and weak bisimulation for bounded Petri nets.
Implements §7.3 of the architecture spec — a structural check that
two Petri nets exhibit the same observable behaviour from their
initial markings, where "observable" means the labels on their
transitions. The strong checker (are_bisimilar) matches each
transition exactly; the weak checker (are_weakly_bisimilar)
collapses transitions marked silent=True so refactorings that
add or remove internal steps (logging, no-op gates, internal
handoffs) don't break the equivalence.
Algorithm: reachability-graph + partition refinement. Each net's labelled transition system is computed by BFS over markings from M_0; the two LTSs are combined and refined into bisimulation equivalence classes; the nets are bisimilar iff their initial markings end up in the same class.
For weak bisimulation, the LTS is saturated first: silent transitions are relabelled to a τ sentinel, the τ-edges are closed reflexive-transitively (every state gains a τ self-loop; τ-paths become direct τ-edges), and each visible-action edge is extended with τ-prefix and τ-suffix detours into all τ-reachable neighbours. Strong bisimulation over the saturated LTS gives weak bisimulation over the original.
Scope: bounded Petri nets only. The BFS terminates exactly when
the reachable state space is finite, which is the case for every
1-bounded workflow net produced by parse_bpmn today. A
max_states cap raises clearly if a caller feeds in something
unbounded.
LTS
dataclass
¶
Labelled transition system view of a Petri net's reachable
markings. The initial field is the canonical frozenset
representation of M_0; states contains every reachable marking;
transitions records every (from, label, to) edge in the
reachability graph.
reachability_graph ¶
Compute the reachable LTS of net from its initial marking.
Each LTS state is a frozenset of (place, token-count) pairs;
this canonical form lets bisimulation refinement use markings
as dict keys. max_states guards against accidentally fed
unbounded nets — it raises ValueError if the frontier grows
past the cap.
Silent transitions appear with their declared label, exactly
as visible transitions do; the strong checker treats them as
ordinary edges. Use _reachability_graph_with_tau (the weak
checker calls it) to relabel silent transitions to TAU.
Source code in petri_net_nn/bisimulation.py
bisimulation_equivalence_classes ¶
Return the partition of net's reachable markings into
bisimulation equivalence classes — markings from which the net
exhibits the same labelled future behaviour.
Source code in petri_net_nn/bisimulation.py
are_bisimilar ¶
Strong-bisimilarity check between the initial markings of two Petri nets.
The two nets are bisimilar iff their initial markings fall into
the same equivalence class of the combined LTS — that is, every
labelled successor of one can be matched exactly by a labelled
successor of the other, recursively. "Exactly" includes silent
transitions, which behave like any other labelled edge here; use
:func:are_weakly_bisimilar if you want silent transitions
collapsed.
Source code in petri_net_nn/bisimulation.py
are_weakly_bisimilar ¶
Weak-bisimilarity check between the initial markings of two Petri nets.
Weak bisimulation differs from strong bisimulation in one
important way: transitions marked silent=True are treated as
invisible internal steps that can be inserted or removed without
changing the observable behaviour. A refactoring that adds an
internal logging transition, a no-op routing gate, or any other
structural artefact whose label nobody cares about will be
strongly bisimilar-rejected but weakly bisimilar-accepted —
which is what makes the cost-ranked refactoring story actually
work for real-world redesigns.
Internally the LTS for each net is built with silent transitions
relabelled to the :data:TAU sentinel; both LTSs are then
saturated (see :func:_saturate for the algorithm); strong
bisimulation over the saturated combined LTS is weak bisimulation
over the originals.
Source code in petri_net_nn/bisimulation.py
weak_bisimulation_equivalence_classes ¶
The partition of net's reachable markings into weak
bisimulation equivalence classes.
Same idea as :func:bisimulation_equivalence_classes, but two
markings are in the same class if they exhibit the same
visible-action behaviour up to silent τ-detours. Useful for
asking "how many distinguishable observable states does this
net have?", which collapses transient internal states into
the equivalence class of whatever they τ-reach.