morphata 2.0.1__tar.gz → 2.0.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. {morphata-2.0.1 → morphata-2.0.3}/.claude/settings.local.json +12 -1
  2. {morphata-2.0.1 → morphata-2.0.3}/.envrc +0 -4
  3. {morphata-2.0.1 → morphata-2.0.3}/AGENTS.md +160 -126
  4. {morphata-2.0.1 → morphata-2.0.3}/CHANGELOG.md +115 -30
  5. morphata-2.0.3/PKG-INFO +137 -0
  6. morphata-2.0.3/bench/README.md +28 -0
  7. morphata-2.0.3/bench/conftest.py +53 -0
  8. morphata-2.0.3/bench/test_sere_nlm_perf.py +84 -0
  9. morphata-2.0.3/ci/make-release +131 -0
  10. {morphata-2.0.1 → morphata-2.0.3}/pixi.lock +157 -464
  11. {morphata-2.0.1 → morphata-2.0.3}/pixi.toml +9 -7
  12. {morphata-2.0.1 → morphata-2.0.3}/pyproject.toml +4 -2
  13. morphata-2.0.3/pytest.toml +32 -0
  14. morphata-2.0.3/src/morphata/logic/_common.py +136 -0
  15. morphata-2.0.3/src/morphata/logic/psl.py +372 -0
  16. {morphata-2.0.1 → morphata-2.0.3}/src/morphata/logic/sere.py +148 -35
  17. {morphata-2.0.1 → morphata-2.0.3}/src/morphata/symbolic/__init__.py +3 -0
  18. {morphata-2.0.1 → morphata-2.0.3}/src/morphata/symbolic/bdd.py +2 -1
  19. morphata-2.0.3/src/morphata/symbolic/compose.py +363 -0
  20. {morphata-2.0.1 → morphata-2.0.3}/src/morphata/symbolic/transforms.py +4 -4
  21. {morphata-2.0.1 → morphata-2.0.3}/tests/conftest.py +24 -0
  22. morphata-2.0.3/tests/logic/__init__.py +0 -0
  23. morphata-2.0.3/tests/logic/test_psl.py +179 -0
  24. morphata-2.0.3/tests/logic/test_sere.py +489 -0
  25. morphata-2.0.3/tests/logic/test_sere_properties.py +132 -0
  26. morphata-2.0.3/tests/symbolic/test_compose.py +335 -0
  27. {morphata-2.0.1 → morphata-2.0.3}/uv.lock +176 -89
  28. morphata-2.0.1/PKG-INFO +0 -29
  29. morphata-2.0.1/ci/make-release +0 -99
  30. morphata-2.0.1/pytest.toml +0 -14
  31. morphata-2.0.1/src/morphata/logic/_common.py +0 -72
  32. morphata-2.0.1/src/morphata/logic/psl.py +0 -495
  33. morphata-2.0.1/tests/logic/test_psl.py +0 -230
  34. morphata-2.0.1/tests/logic/test_sere.py +0 -236
  35. {morphata-2.0.1 → morphata-2.0.3}/.basedpyright/baseline.json +0 -0
  36. {morphata-2.0.1 → morphata-2.0.3}/.dockerignore +0 -0
  37. {morphata-2.0.1 → morphata-2.0.3}/.editorconfig +0 -0
  38. {morphata-2.0.1 → morphata-2.0.3}/.envrc.recommended +0 -0
  39. {morphata-2.0.1 → morphata-2.0.3}/.fdignore +0 -0
  40. {morphata-2.0.1 → morphata-2.0.3}/.gitattributes +0 -0
  41. {morphata-2.0.1 → morphata-2.0.3}/.gitignore +0 -0
  42. {morphata-2.0.1 → morphata-2.0.3}/.mailmap +0 -0
  43. {morphata-2.0.1 → morphata-2.0.3}/.python-version +0 -0
  44. {morphata-2.0.1 → morphata-2.0.3}/.rstcheck.cfg +0 -0
  45. {morphata-2.0.1 → morphata-2.0.3}/CLAUDE.md +0 -0
  46. {morphata-2.0.1 → morphata-2.0.3}/CONTRIBUTING.md +0 -0
  47. {morphata-2.0.1 → morphata-2.0.3}/LICENSE +0 -0
  48. {morphata-2.0.1 → morphata-2.0.3}/README.md +0 -0
  49. {morphata-2.0.1/tests/logic → morphata-2.0.3/bench}/__init__.py +0 -0
  50. {morphata-2.0.1 → morphata-2.0.3}/cliff.toml +0 -0
  51. {morphata-2.0.1 → morphata-2.0.3}/docs/_static/navigation.html +0 -0
  52. {morphata-2.0.1 → morphata-2.0.3}/docs/api/modules.rst +0 -0
  53. {morphata-2.0.1 → morphata-2.0.3}/docs/api/morphata.acceptance.rst +0 -0
  54. {morphata-2.0.1 → morphata-2.0.3}/docs/api/morphata.alphabet.rst +0 -0
  55. {morphata-2.0.1 → morphata-2.0.3}/docs/api/morphata.automaton.rst +0 -0
  56. {morphata-2.0.1 → morphata-2.0.3}/docs/api/morphata.builder.rst +0 -0
  57. {morphata-2.0.1 → morphata-2.0.3}/docs/api/morphata.hoa.rst +0 -0
  58. {morphata-2.0.1 → morphata-2.0.3}/docs/api/morphata.logic.ltl.rst +0 -0
  59. {morphata-2.0.1 → morphata-2.0.3}/docs/api/morphata.logic.psl.rst +0 -0
  60. {morphata-2.0.1 → morphata-2.0.3}/docs/api/morphata.logic.sere.rst +0 -0
  61. {morphata-2.0.1 → morphata-2.0.3}/docs/api/morphata.logic.strel.rst +0 -0
  62. {morphata-2.0.1 → morphata-2.0.3}/docs/api/morphata.operators.matrix.rst +0 -0
  63. {morphata-2.0.1 → morphata-2.0.3}/docs/api/morphata.operators.polynomial.rst +0 -0
  64. {morphata-2.0.1 → morphata-2.0.3}/docs/api/morphata.operators.rst +0 -0
  65. {morphata-2.0.1 → morphata-2.0.3}/docs/api/morphata.symbolic.rst +0 -0
  66. {morphata-2.0.1 → morphata-2.0.3}/docs/concepts/acceptance.rst +0 -0
  67. {morphata-2.0.1 → morphata-2.0.3}/docs/concepts/hoa.rst +0 -0
  68. {morphata-2.0.1 → morphata-2.0.3}/docs/concepts/index.rst +0 -0
  69. {morphata-2.0.1 → morphata-2.0.3}/docs/concepts/operators.rst +0 -0
  70. {morphata-2.0.1 → morphata-2.0.3}/docs/concepts/spec.rst +0 -0
  71. {morphata-2.0.1 → morphata-2.0.3}/docs/concepts/weights.rst +0 -0
  72. {morphata-2.0.1 → morphata-2.0.3}/docs/conf.py +0 -0
  73. {morphata-2.0.1 → morphata-2.0.3}/docs/index.rst +0 -0
  74. {morphata-2.0.1 → morphata-2.0.3}/docs/quick-start.rst +0 -0
  75. {morphata-2.0.1 → morphata-2.0.3}/docs/symbolic-transforms.rst +0 -0
  76. {morphata-2.0.1 → morphata-2.0.3}/justfile +0 -0
  77. {morphata-2.0.1 → morphata-2.0.3}/mypy.ini +0 -0
  78. {morphata-2.0.1 → morphata-2.0.3}/project.utf-8.add +0 -0
  79. {morphata-2.0.1 → morphata-2.0.3}/project.utf-8.add.spl +0 -0
  80. {morphata-2.0.1 → morphata-2.0.3}/pyrefly.toml +0 -0
  81. {morphata-2.0.1 → morphata-2.0.3}/pyrightconfig.json +0 -0
  82. {morphata-2.0.1 → morphata-2.0.3}/ruff.toml +0 -0
  83. {morphata-2.0.1 → morphata-2.0.3}/src/morphata/__init__.py +0 -0
  84. {morphata-2.0.1 → morphata-2.0.3}/src/morphata/acceptance.py +0 -0
  85. {morphata-2.0.1 → morphata-2.0.3}/src/morphata/alphabet.py +0 -0
  86. {morphata-2.0.1 → morphata-2.0.3}/src/morphata/automaton.py +0 -0
  87. {morphata-2.0.1 → morphata-2.0.3}/src/morphata/builder.py +0 -0
  88. {morphata-2.0.1 → morphata-2.0.3}/src/morphata/hoa/__init__.py +0 -0
  89. {morphata-2.0.1 → morphata-2.0.3}/src/morphata/hoa/__main__.py +0 -0
  90. {morphata-2.0.1 → morphata-2.0.3}/src/morphata/hoa/acc_expr.py +0 -0
  91. {morphata-2.0.1 → morphata-2.0.3}/src/morphata/hoa/exporter.py +0 -0
  92. {morphata-2.0.1 → morphata-2.0.3}/src/morphata/hoa/hoa.lark +0 -0
  93. {morphata-2.0.1 → morphata-2.0.3}/src/morphata/hoa/parser.py +0 -0
  94. {morphata-2.0.1 → morphata-2.0.3}/src/morphata/logic/__init__.py +0 -0
  95. {morphata-2.0.1 → morphata-2.0.3}/src/morphata/logic/ltl.py +0 -0
  96. {morphata-2.0.1 → morphata-2.0.3}/src/morphata/logic/strel.py +0 -0
  97. {morphata-2.0.1 → morphata-2.0.3}/src/morphata/operators/__init__.py +0 -0
  98. {morphata-2.0.1 → morphata-2.0.3}/src/morphata/operators/_backend.py +0 -0
  99. {morphata-2.0.1 → morphata-2.0.3}/src/morphata/operators/matrix.py +0 -0
  100. {morphata-2.0.1 → morphata-2.0.3}/src/morphata/operators/polynomial.py +0 -0
  101. {morphata-2.0.1 → morphata-2.0.3}/src/morphata/py.typed +0 -0
  102. {morphata-2.0.1 → morphata-2.0.3}/src/morphata/symbolic/automaton.py +0 -0
  103. {morphata-2.0.1 → morphata-2.0.3}/src/morphata/symbolic/equivalence.py +0 -0
  104. {morphata-2.0.1 → morphata-2.0.3}/tests/hoa/aut1.hoa +0 -0
  105. {morphata-2.0.1 → morphata-2.0.3}/tests/hoa/aut11.hoa +0 -0
  106. {morphata-2.0.1 → morphata-2.0.3}/tests/hoa/aut2.hoa +0 -0
  107. {morphata-2.0.1 → morphata-2.0.3}/tests/hoa/aut3.2.hoa +0 -0
  108. {morphata-2.0.1 → morphata-2.0.3}/tests/hoa/aut3.hoa +0 -0
  109. {morphata-2.0.1 → morphata-2.0.3}/tests/hoa/aut4.hoa +0 -0
  110. {morphata-2.0.1 → morphata-2.0.3}/tests/hoa/aut5.hoa +0 -0
  111. {morphata-2.0.1 → morphata-2.0.3}/tests/hoa/aut6.hoa +0 -0
  112. {morphata-2.0.1 → morphata-2.0.3}/tests/hoa/aut7.hoa +0 -0
  113. {morphata-2.0.1 → morphata-2.0.3}/tests/hoa/aut8.hoa +0 -0
  114. {morphata-2.0.1 → morphata-2.0.3}/tests/hoa/finite_aut.hoa +0 -0
  115. {morphata-2.0.1 → morphata-2.0.3}/tests/hoa/gen_buchi_3.hoa +0 -0
  116. {morphata-2.0.1 → morphata-2.0.3}/tests/hoa/gen_cobuchi_2.hoa +0 -0
  117. {morphata-2.0.1 → morphata-2.0.3}/tests/hoa/rabin_2pair.hoa +0 -0
  118. {morphata-2.0.1 → morphata-2.0.3}/tests/hoa/streett_2pair.hoa +0 -0
  119. {morphata-2.0.1 → morphata-2.0.3}/tests/hoa/test_exporter_basic.py +0 -0
  120. {morphata-2.0.1 → morphata-2.0.3}/tests/hoa/test_exporter_roundtrip.py +0 -0
  121. {morphata-2.0.1 → morphata-2.0.3}/tests/hoa/test_parser.py +0 -0
  122. {morphata-2.0.1 → morphata-2.0.3}/tests/logic/test_ltl.py +0 -0
  123. {morphata-2.0.1 → morphata-2.0.3}/tests/logic/test_strel.py +0 -0
  124. {morphata-2.0.1 → morphata-2.0.3}/tests/operators/test_matrix_from_ir.py +0 -0
  125. {morphata-2.0.1 → morphata-2.0.3}/tests/operators/test_matrix_from_ltl.py +0 -0
  126. {morphata-2.0.1 → morphata-2.0.3}/tests/operators/test_polynomial_from_ir.py +0 -0
  127. {morphata-2.0.1 → morphata-2.0.3}/tests/symbolic/test_bdd_helpers.py +0 -0
  128. {morphata-2.0.1 → morphata-2.0.3}/tests/symbolic/test_from_hoaf.py +0 -0
  129. {morphata-2.0.1 → morphata-2.0.3}/tests/symbolic/test_ltlf2dfa_equivalence.py +0 -0
  130. {morphata-2.0.1 → morphata-2.0.3}/tests/symbolic/test_step.py +0 -0
  131. {morphata-2.0.1 → morphata-2.0.3}/tests/symbolic/test_symbolic_automaton.py +0 -0
  132. {morphata-2.0.1 → morphata-2.0.3}/tests/symbolic/test_symbolic_from_ir.py +0 -0
  133. {morphata-2.0.1 → morphata-2.0.3}/tests/symbolic/test_transforms.py +0 -0
  134. {morphata-2.0.1 → morphata-2.0.3}/tests/symbolic/test_transforms_ir.py +0 -0
  135. {morphata-2.0.1 → morphata-2.0.3}/tests/symbolic/test_transforms_properties.py +0 -0
  136. {morphata-2.0.1 → morphata-2.0.3}/tests/test_alphabet.py +0 -0
  137. {morphata-2.0.1 → morphata-2.0.3}/tests/test_automaton.py +0 -0
  138. {morphata-2.0.1 → morphata-2.0.3}/tests/test_builder.py +0 -0
  139. {morphata-2.0.1 → morphata-2.0.3}/tests/test_from_parsed.py +0 -0
  140. {morphata-2.0.1 → morphata-2.0.3}/ty.toml +0 -0
@@ -15,7 +15,18 @@
15
15
  "Bash(.venv/bin/python -m pytest *)",
16
16
  "Skill(superpowers:writing-plans)",
17
17
  "Skill(superpowers:subagent-driven-development)",
18
- "Bash(grep -v \"AssertionError\\\\|Traceback\\\\|File \\\\|^\\\\s*$\")"
18
+ "Bash(grep -v \"AssertionError\\\\|Traceback\\\\|File \\\\|^\\\\s*$\")",
19
+ "Skill(superpowers:brainstorming)",
20
+ "Bash(jj log *)",
21
+ "Bash(jj st *)",
22
+ "Bash(jj diff *)",
23
+ "Skill(superpowers:requesting-code-review)",
24
+ "Bash(timeout 15 python -c ' *)",
25
+ "Read(//tmp/**)",
26
+ "Bash(echo \"exit: $?\")",
27
+ "Skill(superpowers:executing-plans)",
28
+ "Bash(ruff check *)",
29
+ "Bash(ruff format *)"
19
30
  ]
20
31
  }
21
32
  }
@@ -24,7 +24,3 @@ dotenv_if_exists
24
24
 
25
25
  watch_file pixi.lock
26
26
  eval "$(pixi shell-hook -e "$PIXI_ENV")"
27
-
28
- if [[ -d "$CONDA_PREFIX" ]]; then
29
- export VIRTUAL_ENV="$CONDA_PREFIX"
30
- fi
@@ -9,73 +9,81 @@ It packages structural automaton representations together with semiring-valued
9
9
  The development shell already has the project virtual environment activated,
10
10
  so `pytest`, `mypy`, `ruff`, `sphinx-build`, etc. are on `PATH` directly.
11
11
  **Do not** prefix commands with `uv run`, `pixi run`, `poetry run`, or similar.
12
- Just invoke the tool. If a tool is missing, stop and tell the user rather than
13
- trying to bootstrap or reinstall the environment.
12
+ Just invoke the tool.
13
+ If a tool is missing, stop and tell the user rather than trying to bootstrap
14
+ or reinstall the environment.
14
15
 
15
16
  ## Core Architecture
16
17
 
17
- ### IR layer (`morphata.automaton`, `morphata.alphabet`, `morphata.acceptance`)
18
+ ### IR Layer (`morphata.automaton`, `morphata.alphabet`, `morphata.acceptance`)
18
19
 
19
20
  **`morphata.automaton`** : the canonical automaton IR.
20
- - `Automaton[State, Symbol, AP]` : the single automaton dataclass. Fields:
21
- `states`, `predicates`, `initial: BoolExpr[State]`, `delta: Delta`,
21
+
22
+ - `Automaton[State, Symbol, AP]` : the single automaton dataclass.
23
+ Fields: `states`, `predicates`, `initial: BoolExpr[State]`, `delta: Delta`,
22
24
  `acceptance: AcceptanceCondition[State]`, plus a derived ``variant``.
23
- - `Outgoing[State, AP]` : canonical predicate-row delta. ``fn(state)``
25
+ - `Outgoing[State, AP]` : canonical predicate-row delta. ``fn(state)``
24
26
  yields ``(guard: BoolExpr[AP], successor: BoolExpr[State])`` pairs.
25
- - `Step[State, Symbol]` : escape-hatch delta for input-dependent state
26
- spaces. ``fn(state, symbol) -> BoolExpr[State]``. Used by STREL.
27
+ - `Step[State, Symbol]` : escape-hatch delta for input-dependent state spaces.
28
+ ``fn(state, symbol) -> BoolExpr[State]``.
29
+ Used by STREL.
27
30
  Not symbolically liftable.
28
31
  - `Delta` : union of ``Outgoing`` and ``Step``.
29
32
  - `DeltaKind` : ``DETERMINISTIC``, ``NONDETERMINISTIC``, ``UNIVERSAL``,
30
33
  ``ALTERNATING``.
31
34
  - `classify_delta(delta, states)` : classifies a delta into a
32
- ``DeltaKind``. ``Step`` always classifies as ``ALTERNATING``.
35
+ ``DeltaKind``. ``Step`` always classifies as ``ALTERNATING``.
33
36
  - `BoolExpr[Var]` : alias for ``logic_asts.base.BaseExpr[Var]``.
34
37
 
35
- **`morphata.alphabet`** : input alphabet abstractions. Guards in the IR
36
- stay symbolic; the alphabet bundles the symbol-side metadata with the
37
- function that interprets a guard against a symbol.
38
- - `Alphabet[Symbol, AP, V]` (``Protocol``) : the operator-facing
39
- contract. Exposes ``predicates`` and ``weight(symbol, guard) -> V``.
40
- - `BooleanAlphabet[Symbol, AP]` : carries a labeling
41
- ``L: Symbol -> AbstractSet[AP]``. ``weight`` evaluates the guard in
42
- Boolean semantics against the AP-truth set ``L(symbol)``. Satisfies
43
- ``Alphabet[Symbol, AP, bool]``.
44
- - `WeightedAlphabet[Symbol, AP, V]` : carries an arbitrary
45
- ``weight_fn: (Symbol, BoolExpr[AP]) -> V``. Satisfies
46
- ``Alphabet[Symbol, AP, V]``.
38
+ **`morphata.alphabet`** : input alphabet abstractions.
39
+ Guards in the IR stay symbolic;
40
+ the alphabet bundles the symbol-side metadata with the function
41
+ that interprets a guard against a symbol.
42
+
43
+ - `Alphabet[Symbol, AP, V]` (``Protocol``) : the operator-facing contract.
44
+ Exposes ``predicates`` and ``weight(symbol, guard) -> V``.
45
+ - `BooleanAlphabet[Symbol, AP]` : carries a labeling ``L: Symbol -> AbstractSet[AP]``.
46
+ ``weight`` evaluates the guard in Boolean semantics against the AP-truth set
47
+ ``L(symbol)``.
48
+ Satisfies ``Alphabet[Symbol, AP, bool]``.
49
+ - `WeightedAlphabet[Symbol, AP, V]` : carries an arbitrary ``weight_fn:
50
+ (Symbol, BoolExpr[AP]) -> V``.
51
+ Satisfies ``Alphabet[Symbol, AP, V]``.
47
52
  - Factories: ``powerset_alphabet(aps)`` (Sigma = 2^AP, identity
48
53
  labeling), ``finite_alphabet(aps)`` (Sigma = AP, one-hot labeling),
49
54
  ``weighted_alphabet(weight_fn, *, predicates)``.
50
55
  - ``WeightedAlphabet.from_boolean(boolean, semiring)`` lifts a Boolean
51
56
  alphabet into ``V`` via evaluate-then-lift (the guard is evaluated in
52
57
  ``bool`` and the result is mapped to ``semiring.zero`` / ``one``).
53
- - ``WeightedAlphabet.from_predicates(predicates, semiring)`` builds a
54
- weight function from per-AP predicates over ``V``. Replaces the
55
- former ``morphata.weights`` module.
58
+ - ``WeightedAlphabet.from_predicates
59
+ (predicates, semiring)`` builds a weight function from per-AP predicates over ``V``.
60
+ Replaces the former ``morphata.weights`` module.
56
61
 
57
- The reverse direction (``WeightedAlphabet -> BooleanAlphabet``) requires
58
- extra ordering / threshold structure (cf. STL quantitative semantics)
59
- and is deliberately not provided.
62
+ The reverse direction (``WeightedAlphabet -> BooleanAlphabet``) requires extra ordering
63
+ / threshold structure (cf.
64
+ STL quantitative semantics) and is deliberately not provided.
60
65
 
61
- **`morphata.acceptance`** : expression-based acceptance conditions
62
- used by the HOA parser and the rest of the stack - ``Finite``,
63
- ``Buchi``, ``CoBuchi``, ``GeneralizedBuchi``, ``GeneralizedCoBuchi``,
64
- ``Rabin``, ``Streett``, plus a ``GenericCondition`` fallback for
65
- acceptance formulas that do not match a named condition.
66
+ **`morphata.acceptance`** :
67
+ expression-based acceptance conditions used by the HOA parser
68
+ and the rest of the stack - ``Finite``, ``Buchi``, ``CoBuchi``, ``GeneralizedBuchi``,
69
+ ``GeneralizedCoBuchi``, ``Rabin``, ``Streett``,
70
+ plus a ``GenericCondition`` fallback for acceptance formulas
71
+ that do not match a named condition.
66
72
 
67
73
  **`morphata.hoa`** : Hanoi Omega-Automata v1 parser.
74
+
68
75
  - ``parser.py`` : LALR-based parser; supports the ``Final(n)``
69
76
  extension for finite-word acceptance.
70
77
  - ``acc_expr.py`` : acceptance-formula AST shared with the parser.
71
78
  - ``exporter.py`` : HOA v1 serializer; round-trips parser input via
72
79
  ``automaton_to_hoa``.
73
80
 
74
- ### Construction layer
81
+ ### Construction Layer
82
+
83
+ **`morphata.builder.AutomatonBuilder[State, AP]`** :
84
+ the mutable builder that produces an ``Automaton`` IR on ``build()``.
85
+ Replaces the deleted ``morphata.constructions.nfa.NFA``.
75
86
 
76
- **`morphata.builder.AutomatonBuilder[State, AP]`** : the mutable builder
77
- that produces an ``Automaton`` IR on ``build()``. Replaces the deleted
78
- ``morphata.constructions.nfa.NFA``.
79
87
  - ``add_state(state, *, initial=False, accepting=False)``.
80
88
  - ``add_transition(src, guard, succ)`` where ``succ`` may be a single
81
89
  state, a set of states (lifted to ``Or`` / ``And`` per variant), or a
@@ -88,35 +96,40 @@ LTL/LTLf -> alternating ``Automaton`` with an ``Outgoing`` delta.
88
96
  ``finite=True`` -> ``Finite`` acceptance; ``finite=False`` -> ``Buchi``.
89
97
 
90
98
  **`morphata.logic.strel.strel_to_automata(expr, dist_attr, label_fn, ...)`** :
91
- STREL over labelled graphs. Returns an ``Automaton`` with a ``Step``
92
- delta - the transition consumes the labelled graph eagerly, so STREL
93
- automata are not symbolically liftable. Operators: Reach, Escape,
94
- Somewhere, Everywhere. Boolean-only.
95
-
96
- **`morphata.logic.sere.sere_to_automaton(expr)`** : SERE -> alternating
97
- finite automaton. Supports ``Concat``, ``Fusion``, ``Repeat`` (bounded
98
- and unbounded), ``Inter``, ``Alt``, and Boolean leaves over AP.
99
- Acceptance is always ``Finite``; the accepting-state set is exactly
100
- the *nullable* states (those that match the empty word).
101
-
102
- **`morphata.logic.psl.psl_to_automaton(phi, *, finite=False)`** : PSL ->
103
- alternating automaton. Wraps pure-LTL subformulas in an internal
104
- ``_LTL`` tag and dispatches to the LTL expansion rule; closure
105
- operators (``{r}!``, ``{r}``, ``!{r}``, ``{r}<>-> f``, ``{r}[]-> f``)
106
- become their own tagged states with SERE-step expansion. Suffix
107
- implications follow Spot's overlapping semantics: the last symbol of
108
- ``r`` and the first position of ``f`` coincide. ``finite=True`` ->
109
- ``Finite`` acceptance (PSLf); ``finite=False`` -> ``Buchi``.
110
-
111
- Hand-written automata are built with ``AutomatonBuilder``; the package
112
- has no separate ``constructions`` namespace.
113
-
114
- ### Operator layer (`morphata.operators`)
115
-
116
- Algebraic morphisms that turn an ``Automaton`` plus an ``Alphabet`` into
117
- a computable interpretation over an algebraic domain.
99
+ STREL over labelled graphs.
100
+ Returns an ``Automaton`` with a ``Step`` delta - the transition consumes the labelled
101
+ graph eagerly, so STREL automata are not symbolically liftable.
102
+ Operators: Reach, Escape, Somewhere, Everywhere.
103
+ Boolean-only.
104
+
105
+ **`morphata.logic.sere.sere_to_automaton(expr)`** :
106
+ SERE -> alternating finite automaton.
107
+ Supports ``Concat``, ``Fusion``, ``Repeat``
108
+ (bounded and unbounded), ``Inter``, ``Alt``, and Boolean leaves over AP.
109
+ Acceptance is always ``Finite``;
110
+ the accepting-state set is exactly the *nullable* states
111
+ (those that match the empty word).
112
+
113
+ **`morphata.logic.psl.psl_to_automaton(phi, *, finite=False)`** :
114
+ PSL -> alternating automaton.
115
+ Wraps pure-LTL subformulas in an internal ``_LTL`` tag
116
+ and dispatches to the LTL expansion rule; closure operators (``{r}!
117
+ ``, ``{r}``, ``!{r}``, ``{r}<>-> f``, ``{r}[]-> f``) become their own tagged states with
118
+ SERE-step expansion.
119
+ Suffix implications follow Spot's overlapping semantics:
120
+ the last symbol of ``r`` and the first position of ``f`` coincide.
121
+ ``finite=True`` -> ``Finite`` acceptance (PSLf); ``finite=False`` -> ``Buchi``.
122
+
123
+ Hand-written automata are built with ``AutomatonBuilder``;
124
+ the package has no separate ``constructions`` namespace.
125
+
126
+ ### Operator Layer (`morphata.operators`)
127
+
128
+ Algebraic morphisms that turn an ``Automaton`` plus an ``Alphabet`` into a computable
129
+ interpretation over an algebraic domain.
118
130
 
119
131
  **`morphata.operators.matrix.MatrixOperator`** :
132
+
120
133
  - Weighted finite-word NFA semantics as semiring matrix multiplication.
121
134
  - Fields: ``initial_weights``, ``final_weights``, ``semiring``, an
122
135
  ``alphabet`` bound at construction, and a frozen ``(src, dst, guard)``
@@ -129,6 +142,7 @@ a computable interpretation over an algebraic domain.
129
142
  - ``cost_transitions(x)`` returns an ``algebraic.AlgebraicArray``.
130
143
 
131
144
  **`morphata.operators.polynomial.PolynomialOperator`** :
145
+
132
146
  - Alternating finite automata as multilinear polynomials over a
133
147
  bounded distributive lattice (typically Boolean).
134
148
  - The alphabet is bound at construction. ``step(current, symbol)``
@@ -139,51 +153,64 @@ a computable interpretation over an algebraic domain.
139
153
  ``PolynomialOperator.from_ltl(formula, algebra, *, alphabet, finite=True, ...)``.
140
154
  - Backed by ``algebraic.polynomials.RankDecomposition``.
141
155
 
142
- **`morphata.operators._backend`** : ``resolve_backend(...)`` and the
143
- ``_StaticAux`` JAX-pytree helper. Inferred from any provided
144
- ``AlgebraicArray`` or specified explicitly via ``backend=``.
156
+ **`morphata.operators._backend`** :
157
+ ``resolve_backend(...)`` and the ``_StaticAux`` JAX-pytree helper.
158
+ Inferred from any provided ``AlgebraicArray`` or specified explicitly via ``backend=``.
145
159
 
146
- ### Symbolic layer (`morphata.symbolic`)
160
+ ### Symbolic Layer (`morphata.symbolic`)
147
161
 
148
- BDD-backed representation of an ``Automaton``. Structural / runtime
149
- layer: no target algebra, no semiring.
162
+ BDD-backed representation of an ``Automaton``.
163
+ Structural / runtime layer: no target algebra, no semiring.
150
164
 
151
- **`morphata.symbolic.bdd`** : thin bridge between ``BoolExpr`` and the
152
- ``dd`` BDD package. Exports ``make_manager(reordering=True)``,
153
- ``declare_state_vars``, ``state_var_name``, ``STATE_VAR_FORMAT``,
154
- ``boolexpr_to_bdd(expr, mgr, num_vars) -> dd.Function``,
155
- ``bdd_to_boolexpr(node) -> BoolExpr[int]``. Nothing else in
156
- ``morphata`` imports ``dd`` directly.
165
+ **`morphata.symbolic.bdd`** :
166
+ thin bridge between ``BoolExpr`` and the ``dd`` BDD package.
167
+ Exports ``make_manager(reordering=True)``, ``declare_state_vars``, ``state_var_name``,
168
+ ``STATE_VAR_FORMAT``, ``boolexpr_to_bdd
169
+ (expr, mgr, num_vars) -> dd.Function``, ``bdd_to_boolexpr(node) -> BoolExpr[int]``.
170
+ Nothing else in ``morphata`` imports ``dd`` directly.
157
171
 
158
172
  **`morphata.symbolic.automaton.SymbolicAutomaton`** :
159
- - Wraps an ``Automaton`` so the initial-state set and every cached
160
- transition are live ``dd.Function`` nodes in a single ``dd.BDD``
161
- manager. Acceptance is stored as given; only ``accepts(word)``
162
- requires ``Finite`` acceptance.
173
+
174
+ - Wraps an ``Automaton`` so the initial-state set
175
+ and every cached transition are live ``dd.Function`` nodes in a single ``dd.BDD``
176
+ manager.
177
+ Acceptance is stored as given; only ``accepts(word)`` requires ``Finite`` acceptance.
163
178
  - Requires ``aut.delta`` to be ``Outgoing``; ``Step`` deltas (STREL)
164
179
  raise ``TypeError``.
165
- - Constructor: ``SymbolicAutomaton.from_automaton(aut, *, alphabet,
166
- manager=None, reordering=True, cache_transitions=True, name_fn=None)``.
180
+ - Constructor: ``SymbolicAutomaton.from_automaton
181
+ (aut, *, alphabet, manager=None, reordering=True, cache_transitions=True,
182
+ name_fn=None)``.
167
183
  Convenience ``from_ltl`` / ``from_hoaf`` constructors compose with
168
- ``ltl_to_automaton`` and the HOA parser, and likewise require
169
- ``alphabet=``.
170
- - Per-step primitives: ``step(current, symbol)``, ``run(word)``,
171
- ``accepts(word)``. The alphabet is bound at construction and must be a
172
- ``BooleanAlphabet``; the BDD cofactor path generalises to any
173
- user-supplied labeling.
184
+ ``ltl_to_automaton`` and the HOA parser, and likewise require ``alphabet=``.
185
+ - Per-step primitives: ``step(current, symbol)``, ``run(word)``, ``accepts(word)``.
186
+ The alphabet is bound at construction and must be a ``BooleanAlphabet``;
187
+ the BDD cofactor path generalises to any user-supplied labeling.
174
188
  - State variables in the manager are always named ``q0..q{n-1}``;
175
189
  callers who want their own variable names should apply ``mgr.let``
176
190
  with an alias map.
177
191
 
178
- **`morphata.symbolic.transforms`** : BDD-level structural transforms
179
- over ``SymbolicAutomaton`` -- ``nfa_from_afa`` (dealternation),
180
- ``determinize`` (subset construction with an explicit sink), and
181
- ``minimize`` (Hopcroft partition refinement). The three compose to
182
- ``SymbolicAutomaton.to_min_dfa``.
183
-
184
- **`morphata.symbolic.equivalence`** : language-equivalence and
185
- isomorphism checks for DFA-shaped symbolic automata --
186
- ``language_equivalent``, ``canonical_form``, ``isomorphic``.
192
+ **`morphata.symbolic.transforms`** :
193
+ BDD-level structural transforms over ``SymbolicAutomaton`` -- ``nfa_from_afa``
194
+ (dealternation), ``determinize`` (subset construction with an explicit sink), and
195
+ ``minimize`` (Hopcroft partition refinement).
196
+ The three compose to ``SymbolicAutomaton.to_min_dfa``.
197
+
198
+ **`morphata.symbolic.compose`** :
199
+ compositional min-DFA construction on top of ``minimize``.
200
+ ``compose_min_dfa(left, right, op)`` takes the product of two DFA-shaped
201
+ ``SymbolicAutomaton`` instances over a shared manager and minimizes the result; ``op``
202
+ is ``"and"`` or ``"or"``.
203
+ ``min_dfa_of_conjunction(formulas, *, alphabet)`` builds each conjunct's min-DFA over a
204
+ shared manager with a canonical AP ``name_fn`` derived from the alphabet's predicate
205
+ set, then folds them under ``compose_min_dfa(..., op="and")``.
206
+ For top-level LTLf conjunctions where each conjunct has a small min-DFA
207
+ but the monolithic AFA explodes under ``determinize``,
208
+ the layered path can be orders of magnitude faster.
209
+
210
+ **`morphata.symbolic.equivalence`** :
211
+ language-equivalence and isomorphism checks
212
+ for DFA-shaped symbolic automata -- ``language_equivalent``, ``canonical_form``,
213
+ ``isomorphic``.
187
214
 
188
215
  ## Dependencies
189
216
 
@@ -194,18 +221,18 @@ isomorphism checks for DFA-shaped symbolic automata --
194
221
  - Optional: `jax` (for the JAX backend), `torch` (for the PyTorch
195
222
  backend), `pydot` (for `.dot` exports).
196
223
 
197
- `morphata` operators that use `equinox` are JAX-flavoured; `algebraic`
198
- is backend-agnostic via `backend=`.
224
+ `morphata` operators that use `equinox` are JAX-flavoured;
225
+ `algebraic` is backend-agnostic via `backend=`.
199
226
 
200
227
  ## Design Principles
201
228
 
202
- 1. **Single IR shape** : everything is a
203
- ``morphata.automaton.Automaton``. Variants (det / nondet / univ /
204
- alt) are derived from the rows, not encoded in separate classes.
205
- 2. **Alphabet-Carried Characteristic Function** : guards stay symbolic
206
- in the IR. ``Alphabet.weight(symbol, guard)`` lifts a symbol against
207
- a guard. Match the alphabet's ``V`` type to your consumer (``bool``
208
- for symbolic evaluation, semiring elements for weighted evaluation).
229
+ 1. **Single IR shape** : everything is a ``morphata.automaton.Automaton``.
230
+ Variants (det / nondet / univ / alt) are derived from the rows,
231
+ not encoded in separate classes.
232
+ 2. **Alphabet-Carried Characteristic Function** : guards stay symbolic in the IR.
233
+ ``Alphabet.weight(symbol, guard)`` lifts a symbol against a guard.
234
+ Match the alphabet's ``V`` type to your consumer
235
+ (``bool`` for symbolic evaluation, semiring elements for weighted evaluation).
209
236
  3. **Semiring-Agnostic Operators** : operator factories accept the
210
237
  target algebra and the alphabet separately; the alphabet does not
211
238
  know about the algebra.
@@ -221,31 +248,36 @@ is backend-agnostic via `backend=`.
221
248
 
222
249
  ## Usage Notes and Limitations
223
250
 
224
- - STREL automata use a ``Step`` delta (the transition consumes the input
225
- graph eagerly), so they cannot be lifted to ``SymbolicAutomaton`` and
226
- are not consumable by ``MatrixOperator`` / ``PolynomialOperator``.
251
+ - STREL automata use a ``Step`` delta
252
+ (the transition consumes the input graph eagerly),
253
+ so they cannot be lifted to ``SymbolicAutomaton``
254
+ and are not consumable by ``MatrixOperator`` / ``PolynomialOperator``.
227
255
  STREL itself is Boolean-only.
228
- - ``MatrixOperator`` targets finite-word acceptance; omega-regular
229
- acceptance is not implemented for it. Successor expressions must be
230
- single ``Variable(dst)`` per row (no alternation / universal lifting).
256
+ - ``MatrixOperator`` targets finite-word acceptance;
257
+ omega-regular acceptance is not implemented for it.
258
+ Successor expressions must be single ``Variable(dst)`` per row
259
+ (no alternation / universal lifting).
231
260
  - ``PolynomialOperator`` is tested with Boolean algebra; other semirings
232
261
  may require additional validation.
233
- - **State-based acceptance only.** Acceptance sets are sets of states, not
234
- sets of transitions. The HOA parser ingests ``{n}`` markers from
235
- ``State:`` declarations; per-edge ``{n}`` markers are ignored. Convert
236
- transition-based HOA files to state-based form before loading them.
237
- - ``psl_to_automaton`` requires the continuation of any
238
- ``SuffixImpliesExist`` / ``SuffixImpliesUniv`` to be a pure-LTL
239
- formula in v1. Nested PSL closures inside a suffix-implication
240
- continuation raise ``NotImplementedError``. Likewise, LTL temporal
241
- operators wrapping a PSL closure at the top level
242
- (e.g. ``Always(StrongClosure(r))``) are not yet decomposed; lift the
243
- PSL fragment out of the LTL temporal operator before calling.
262
+ - **State-based acceptance only.**
263
+ Acceptance sets are sets of states, not sets of transitions.
264
+ The HOA parser ingests ``{n}`` markers from ``State:`` declarations;
265
+ per-edge ``{n}`` markers are ignored.
266
+ Convert transition-based HOA files to state-based form before loading them.
267
+ - ``psl_to_automaton`` requires the continuation of any ``SuffixImpliesExist`` /
268
+ ``SuffixImpliesUniv`` to be a pure-LTL formula in v1.
269
+ Nested PSL closures inside a suffix-implication continuation raise
270
+ ``NotImplementedError``.
271
+ Likewise, LTL temporal operators wrapping a PSL closure at the top level
272
+ (e.g. ``Always(StrongClosure(r))``)
273
+ are not yet decomposed; lift the PSL fragment out of the LTL temporal operator
274
+ before calling.
244
275
  - The PSL Buchi acceptance classification is the natural per-tag rule
245
- (strong-closure non-accepting, weak-closure accepting, etc.). It has
246
- not been cross-checked against a published reference for every
247
- combination of operators; consult ``docs/superpowers/specs/`` before
248
- relying on omega-language semantics in production.
276
+ (strong-closure non-accepting, weak-closure accepting, etc.).
277
+ It has not been cross-checked against a published reference
278
+ for every combination of operators;
279
+ consult ``docs/superpowers/specs/``
280
+ before relying on omega-language semantics in production.
249
281
 
250
282
  ## Testing
251
283
 
@@ -379,6 +411,8 @@ matrix_product = algebraic.matmul(arr1, arr2)
379
411
  - `src/morphata/operators/polynomial.py` : ``PolynomialOperator``.
380
412
  - `src/morphata/symbolic/automaton.py` : ``SymbolicAutomaton``.
381
413
  - `src/morphata/symbolic/bdd.py` : ``BoolExpr`` / ``dd`` bridge.
414
+ - `src/morphata/symbolic/compose.py` : ``compose_min_dfa`` and
415
+ ``min_dfa_of_conjunction``.
382
416
  - `src/morphata/symbolic/transforms.py` : ``nfa_from_afa`` /
383
417
  ``determinize`` / ``minimize``.
384
418
  - `src/morphata/symbolic/equivalence.py` : language equivalence and
@@ -1,47 +1,132 @@
1
1
  # Changelog
2
2
 
3
- ## Unreleased
3
+ ## 2.0.3 — 2026-05-28
4
4
 
5
5
  ### Added
6
6
 
7
- - `morphata.logic.sere.sere_to_automaton(formula)` — converts a
8
- SERE expression to an alternating finite automaton. Supports
9
- `Concat`, `Fusion`, `Alt`, `Inter` (length-matching), `Repeat`
10
- (bounded and unbounded), and Boolean leaves (`Literal`, `Variable`,
11
- `Not`, `And`, `Or`).
12
- - `morphata.logic.psl.psl_to_automaton(phi, *, finite=False)` —
13
- converts a PSL expression to an alternating automaton, with
14
- `Finite` acceptance for PSLf (`finite=True`) and `Büchi` acceptance
15
- for full PSL (`finite=False`). Supports the strong / weak /
16
- negated-strong closure operators and existential / universal
17
- suffix implication. Suffix implications follow Spot's overlapping
18
- semantics: the last symbol matched by the SERE prefix is shared
19
- with the first position at which the continuation is evaluated.
7
+ Added a new `morphata.symbolic.compose` API
8
+ that provides some helper functions to construct `SymbolicAutomaton` from composing two
9
+ minimal DFAs or to construct minimal DFAs from LTLf formula with a lot of overlapping
10
+ conjunctive clauses (essentially uses `compose_min_dfa`).
11
+
12
+ This helps fix some downstream issues where the AFA can have maybe 20 states
13
+ (like in Craftax tech trees),
14
+ but always using the `SymbolicAutomaton.to_min_dfa()` pipeline causes us to hit the
15
+ worst-case exponential blow up.
16
+ But, since these specifications have the structure of being conjunctive clauses of much
17
+ smaller minimal DFAs, having a better composition operation benefits us.
18
+
19
+ ## 2.0.2 — 2026-05-27
20
+
21
+ ### Added
22
+
23
+ - The rest of the SERE surface that needed real work, rather than a desugaring rule:
24
+ non-length-matching intersection
25
+ (`NLMInter`, the `&` operator), `Complement` (`~`), and `FirstMatch`.
26
+ NLM intersection is handled by a local rewrite in the expansion polynomial;
27
+ complement dualises the polynomial and gets a small BFS-loop special case
28
+ so the state space stays compact.
29
+ `GotoRepeat`, `EqualRepeat`,
30
+ and bounded `FusionRepeat` desugar through `logic_asts.spec.Expr.expand`.
31
+ Unbounded `FusionRepeat[:*i..]` deliberately raises `NotImplementedError` —
32
+ I don't have a clean expansion for it yet.
33
+ - Benchmarking script for SERE NLM intersection, so I can see when I
34
+ break the perf curve.
20
35
 
21
36
  ### Changed
22
37
 
23
- - `morphata.logic.ltl` factored its per-call BDD scratch-space
24
- construction into a shared helper
25
- `morphata.logic._common.make_logic_context`, reused by the SERE
26
- and PSL converters. Two LTL helpers (`_aut_symbolic_expansion`,
27
- `_empty_trace_value`) were renamed to `ltl_symbolic_expansion` /
28
- `ltl_empty_trace_value` so PSL can delegate to them for pure-LTL
29
- subformulas.
38
+ - `morphata.logic.psl` dropped a layer of in-tree wrappers
39
+ and leans on `logic_asts` types directly now
40
+ that the upstream package carries the extended SERE surface I needed.
41
+ Less code, fewer places for the two sides to disagree.
42
+
43
+ ### Miscellaneous
44
+
45
+ - The README is linked from `pyproject.toml`, so it actually shows up
46
+ on PyPI.
47
+
48
+ ## 2.0.1 — 2026-05-24
49
+
50
+ The point release that delivers the SERE
51
+ and PSL frontends originally promised alongside 2.0,
52
+ plus the perf work that made the symbolic pipeline actually finish on formulas of
53
+ interest.
54
+
55
+ ### Added
56
+
57
+ - `morphata.logic.sere.sere_to_automaton(formula)` —
58
+ converts a SERE expression to an alternating finite automaton.
59
+ Supports `Concat`, `Fusion`, `Alt`, `Inter` (length-matching), `Repeat`
60
+ (bounded and unbounded),
61
+ and Boolean leaves (`Literal`, `Variable`, `Not`, `And`, `Or`).
62
+ - `morphata.logic.psl.psl_to_automaton(phi, *, finite=False)` —
63
+ converts a PSL expression to an alternating automaton,
64
+ with `Finite` acceptance for PSLf (`finite=True`) and `Büchi` acceptance
65
+ for full PSL (`finite=False`).
66
+ Supports the strong / weak / negated-strong closure operators
67
+ and existential / universal suffix implication.
68
+ Suffix implications follow Spot's overlapping semantics:
69
+ the last symbol matched by the SERE prefix is shared with the first position at
70
+ which the continuation is evaluated.
71
+
72
+ ### Changed
30
73
 
31
- ### Known limitations
74
+ - `morphata.logic.ltl` factored its per-call BDD scratch-space construction into a
75
+ shared helper `morphata.logic._common.make_logic_context`, reused by the SERE and PSL
76
+ converters.
77
+ Two LTL helpers (`_aut_symbolic_expansion`, `_empty_trace_value`) were renamed to
78
+ `ltl_symbolic_expansion` / `ltl_empty_trace_value` so PSL can delegate to them for
79
+ pure-LTL subformulas.
80
+ - Moved off `uv` and onto Pixi. `uv`'s handling of CUDA packages and
81
+ the way it churns the lockfile weren't working for me; Pixi does.
82
+
83
+ ### Fixed
84
+
85
+ - New BDD constructions in `morphata.logic` take an explicit manager parameter instead
86
+ of silently spinning up their own.
87
+ - `bdd_to_boolexpr` in `morphata.symbolic.bdd` was silently folding `~x`
88
+ and `x` together because the CUDD complement-edge flag on the current node wasn't
89
+ being respected on the recursive walk.
90
+ Caught while rewriting `ltl_to_automaton`; fixed there.
91
+
92
+ ### Performance
93
+
94
+ - `ltl_to_automaton` no longer iterates `2^|AP|` input symbols per AFA state.
95
+ The closure BFS keeps APs symbolic, builds an expansion polynomial with tagged atoms
96
+ (one tag for guard atoms, one for successor states),
97
+ compiles it to a per-call BDD,
98
+ and partitions AP-space by distinct state cofactors via the new
99
+ `mgr_partition_ap_by_state_residual` helper.
100
+ Per-AFA-state cost drops from `O(2^|AP|)` to `O(distinct successor classes)`,
101
+ and AP variables that don't affect a given cofactor get pruned.
102
+ A depth-15 sequential conjunction `F(a_0) & X(F(a_1) & X(...))`
103
+ that used to take ~330s and OOM at depth 20 now builds in ~0.02s.
104
+ The remaining wall-clock on those formulas lives in `nfa_from_afa`,
105
+ which is intrinsic `2^N` in macro state count.
106
+ - `SymbolicAutomaton.to_min_dfa()` is tractable on formulas with non-trivial `|AP|`
107
+ (`~5+` for monotone conjunctions, `~10+` for sequential), where it used to hang.
108
+ Three pieces: prime-implicant DFS (`mgr_minimal_pos_satisfying_subsets`) replaces the
109
+ old `2^(2^|AP|)` BDD-evaluation pass in `_nfa_macro_step` after dealternation;
110
+ symbolic AP-space partition helpers (`mgr_partition_ap_by_classifiers` and
111
+ `mgr_partition_ap_by_state_residual`) replace explicit `2^|AP|` minterm enumeration in
112
+ `determinize` and `nfa_from_afa`; and `_extract_dfa_target` parses the single
113
+ state-variable name directly instead of linear-scanning every state in the manager,
114
+ removing an `O(num_states^2)` per-refinement-round cost inside `minimize()`.
115
+
116
+ Sadly (but not really sadly),
117
+ these performance fixes make the BDD manager a viral argument in `morphata.logic`,
118
+ but it isn't too bad as it is optional,
119
+ and the `morphata.logic.*_to_automaton` functions will automatically create a
120
+ short-lived manager for these things.
121
+
122
+ ### Known Limitations
32
123
 
33
124
  - `psl_to_automaton` does not yet decompose LTL temporal operators
34
- that wrap PSL closures at the top level
35
- (e.g. `Always(StrongClosure(r))`). Lift the PSL fragment out of
36
- the surrounding LTL temporal operator before calling.
125
+ that wrap PSL closures at the top level (e.g. `Always(StrongClosure(r))`).
126
+ Lift the PSL fragment out of the surrounding LTL temporal operator before calling.
37
127
  - The continuation of a `SuffixImpliesExist` / `SuffixImpliesUniv`
38
128
  must be a pure-LTL formula; nested PSL closures inside a
39
129
  continuation raise `NotImplementedError`.
40
- - The Büchi acceptance classification for PSL has not been
41
- cross-checked against a published reference for every operator
42
- combination; finite-trace (PSLf) acceptance is exercised end-to-end
43
- via `SymbolicAutomaton.accepts` and is high-confidence, but Büchi
44
- is currently only smoke-tested for construction.
45
130
 
46
131
  ## 2.0.0 — 2026-05-23
47
132