petri_net_nn.soundness¶
soundness ¶
Aalst soundness verification and deadlock localisation.
A net is sound in van der Aalst's sense iff:
-
Option to complete. From every reachable marking, the intended final marking is reachable. Nothing the net does is a one-way trip to a stuck state.
-
Proper completion. When tokens reach the sink, no tokens linger elsewhere. The final marking is the only marking with the expected sink token count.
-
No dead transitions. Every transition is enabled in at least one reachable marking. A transition that can never fire is a structural bug — either the modeller forgot a flow, or the transition itself is dead code.
PETRA's existing :func:petri_net_nn.PetriNet.validate catches
well-formedness (every arc references real nodes, every
transition has inputs and outputs, etc.). That's structural
sanity, not behavioural soundness. This module closes the gap:
behavioural soundness, computed by reachability-graph traversal.
The companion :func:find_deadlocks returns the non-final
markings the net can reach but can't progress from — a more
actionable, location-pinned form of the same information that
the soundness check's option-to-complete failure already
implies.
These checks are deliberately decoupled from training. They analyse the structure of the net, not anything the trained network has learned. Run them before training to catch modeller-side bugs; run them after a refactoring to confirm the new variant is still sound; bake them into CI to enforce soundness on every scenario.
Scope: bounded Petri nets only — the reachability graph must
terminate. max_states (default 10,000) is the standard guard.
SoundnessReport
dataclass
¶
SoundnessReport(final_marking, incomplete_markings=list(), lingering_token_markings=list(), dead_transitions=list())
Outcome of an Aalst soundness check.
The three lists pin which condition failed and where. A net is
sound iff all three are empty — checked via :attr:is_sound.
Attributes¶
final_marking The marking the soundness check used as the intended completion state. Echoed back so the caller can confirm the heuristic picked what they expected. incomplete_markings Reachable markings from which the final marking cannot be reached. Each one is a one-way trip into a dead end — the option-to-complete property fails here. lingering_token_markings Reachable markings that have at least the final marking's token count at the sink but also carry tokens elsewhere. These break proper completion — the sink "fired" but work continues in parallel branches, suggesting an unsynchronised AND-split or a missed join. dead_transitions Transitions that are never enabled in any reachable marking. Either the structure has a missing input arc, the transition's preset is unreachable, or the transition itself is unnecessary.
is_sound
property
¶
True iff none of the three soundness conditions
failed. A sound net is one that, from every reachable
marking, can complete cleanly with no lingering tokens
and no dead transitions.
summary ¶
One-line human-readable digest of the report. Useful for log lines and assertion messages.
Source code in petri_net_nn/soundness.py
check_soundness ¶
Run Aalst soundness on net.
Parameters¶
net
The PetriNet to check. Must be bounded — the reachability
graph has to terminate.
final_marking
The marking the net is meant to reach at completion. When
omitted, the heuristic in :func:_default_final_marking
picks one token at each sink place (no-outgoing-arcs).
Pass an explicit marking when the sink is ambiguous or
when the intended completion state has multiple sink
tokens (rare).
max_states
Reachability-graph size cap; raises ValueError if the
net is unbounded enough to exceed it.
Returns¶
SoundnessReport
The pinned outcome — three lists (incomplete markings,
lingering-token markings, dead transitions) plus a
is_sound flag.
Source code in petri_net_nn/soundness.py
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 | |
find_deadlocks ¶
Find non-final markings the net can reach but can't progress from.
A deadlock is a marking with no enabled transitions that isn't the intended final marking. From a deadlock the net is stuck — no further firings are possible. Each deadlock is a specific token configuration you can hand a developer or process owner: "here's what the system was holding when it froze."
This overlaps with the option-to-complete failure that
:func:check_soundness reports, but the two are not identical.
Soundness asks "can every reachable state reach the final
state?"; deadlock localisation asks "which states have
nowhere to go?". Soundness reports a superset (any state
that can only reach a deadlock fails option-to-complete too),
but the deadlock list is the root cause pinned to specific
markings.
Parameters¶
net
The PetriNet to inspect. Must be bounded.
final_marking
Excluded from the result — the final marking is intended
to have no successors, so it's not a deadlock. Same
default as :func:check_soundness.
max_states
Reachability-graph size cap.
Returns¶
list[Marking] Markings (sorted, for deterministic output) with no enabled transitions and not equal to the final marking. Empty list = no deadlocks.