Source code for synkit.CRN.Petrinet.persistence
from __future__ import annotations
from dataclasses import dataclass
from typing import Any, List, Set
from .semiflows import find_p_semiflows, stoichiometric_matrix
from .structure import find_siphons
[docs]
@dataclass(frozen=True)
class PersistenceCheckResult:
"""Structured result of the siphon-based persistence sufficient test."""
persistence_ok: bool
siphons: List[Set[str]]
semiflow_supports: List[Set[str]]
uncovered_siphons: List[Set[str]]
[docs]
def siphon_persistence_condition(
crn: Any,
*,
rtol: float = 1e-12,
max_siphon_size: int | None = None,
) -> bool:
"""
Check the Angeli–De Leenheer–Sontag siphon/P-semiflow sufficient condition.
The condition holds when every minimal siphon contains the support of some
P-semiflow.
"""
return siphon_persistence_details(
crn,
rtol=rtol,
max_siphon_size=max_siphon_size,
).persistence_ok
[docs]
def siphon_persistence_details(
crn: Any,
*,
rtol: float = 1e-12,
max_siphon_size: int | None = None,
support_tol: float = 1e-8,
) -> PersistenceCheckResult:
"""
Return detailed information for the siphon-based persistence test.
"""
siphons = find_siphons(crn, max_size=max_siphon_size, names="id")
if not siphons:
return PersistenceCheckResult(True, [], [], [])
species_order, _, _ = stoichiometric_matrix(crn)
y = find_p_semiflows(crn, rtol=rtol)
if y.size == 0:
return PersistenceCheckResult(False, siphons, [], list(siphons))
supports: List[Set[str]] = []
for j in range(y.shape[1]):
supp = {
species_order[i] for i, val in enumerate(y[:, j]) if abs(val) > support_tol
}
if supp:
supports.append(supp)
uncovered = [set(s) for s in siphons if not any(t.issubset(s) for t in supports)]
return PersistenceCheckResult(
persistence_ok=(len(uncovered) == 0),
siphons=[set(s) for s in siphons],
semiflow_supports=supports,
uncovered_siphons=uncovered,
)