morphata 2.0.2__tar.gz → 2.0.4__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 (135) hide show
  1. {morphata-2.0.2 → morphata-2.0.4}/.claude/settings.local.json +4 -1
  2. {morphata-2.0.2 → morphata-2.0.4}/AGENTS.md +160 -126
  3. {morphata-2.0.2 → morphata-2.0.4}/CHANGELOG.md +25 -0
  4. morphata-2.0.4/PKG-INFO +137 -0
  5. {morphata-2.0.2 → morphata-2.0.4}/pyproject.toml +2 -1
  6. {morphata-2.0.2 → morphata-2.0.4}/src/morphata/hoa/exporter.py +105 -0
  7. {morphata-2.0.2 → morphata-2.0.4}/src/morphata/symbolic/__init__.py +3 -0
  8. {morphata-2.0.2 → morphata-2.0.4}/src/morphata/symbolic/automaton.py +70 -0
  9. morphata-2.0.4/src/morphata/symbolic/compose.py +363 -0
  10. {morphata-2.0.2 → morphata-2.0.4}/src/morphata/symbolic/transforms.py +4 -4
  11. {morphata-2.0.2 → morphata-2.0.4}/tests/conftest.py +7 -0
  12. morphata-2.0.4/tests/symbolic/test_compose.py +335 -0
  13. morphata-2.0.4/tests/symbolic/test_to_hoa.py +142 -0
  14. {morphata-2.0.2 → morphata-2.0.4}/uv.lock +82 -20
  15. morphata-2.0.2/PKG-INFO +0 -29
  16. {morphata-2.0.2 → morphata-2.0.4}/.basedpyright/baseline.json +0 -0
  17. {morphata-2.0.2 → morphata-2.0.4}/.dockerignore +0 -0
  18. {morphata-2.0.2 → morphata-2.0.4}/.editorconfig +0 -0
  19. {morphata-2.0.2 → morphata-2.0.4}/.envrc +0 -0
  20. {morphata-2.0.2 → morphata-2.0.4}/.envrc.recommended +0 -0
  21. {morphata-2.0.2 → morphata-2.0.4}/.fdignore +0 -0
  22. {morphata-2.0.2 → morphata-2.0.4}/.gitattributes +0 -0
  23. {morphata-2.0.2 → morphata-2.0.4}/.gitignore +0 -0
  24. {morphata-2.0.2 → morphata-2.0.4}/.mailmap +0 -0
  25. {morphata-2.0.2 → morphata-2.0.4}/.python-version +0 -0
  26. {morphata-2.0.2 → morphata-2.0.4}/.rstcheck.cfg +0 -0
  27. {morphata-2.0.2 → morphata-2.0.4}/CLAUDE.md +0 -0
  28. {morphata-2.0.2 → morphata-2.0.4}/CONTRIBUTING.md +0 -0
  29. {morphata-2.0.2 → morphata-2.0.4}/LICENSE +0 -0
  30. {morphata-2.0.2 → morphata-2.0.4}/README.md +0 -0
  31. {morphata-2.0.2 → morphata-2.0.4}/bench/README.md +0 -0
  32. {morphata-2.0.2 → morphata-2.0.4}/bench/__init__.py +0 -0
  33. {morphata-2.0.2 → morphata-2.0.4}/bench/conftest.py +0 -0
  34. {morphata-2.0.2 → morphata-2.0.4}/bench/test_sere_nlm_perf.py +0 -0
  35. {morphata-2.0.2 → morphata-2.0.4}/ci/make-release +0 -0
  36. {morphata-2.0.2 → morphata-2.0.4}/cliff.toml +0 -0
  37. {morphata-2.0.2 → morphata-2.0.4}/docs/_static/navigation.html +0 -0
  38. {morphata-2.0.2 → morphata-2.0.4}/docs/api/modules.rst +0 -0
  39. {morphata-2.0.2 → morphata-2.0.4}/docs/api/morphata.acceptance.rst +0 -0
  40. {morphata-2.0.2 → morphata-2.0.4}/docs/api/morphata.alphabet.rst +0 -0
  41. {morphata-2.0.2 → morphata-2.0.4}/docs/api/morphata.automaton.rst +0 -0
  42. {morphata-2.0.2 → morphata-2.0.4}/docs/api/morphata.builder.rst +0 -0
  43. {morphata-2.0.2 → morphata-2.0.4}/docs/api/morphata.hoa.rst +0 -0
  44. {morphata-2.0.2 → morphata-2.0.4}/docs/api/morphata.logic.ltl.rst +0 -0
  45. {morphata-2.0.2 → morphata-2.0.4}/docs/api/morphata.logic.psl.rst +0 -0
  46. {morphata-2.0.2 → morphata-2.0.4}/docs/api/morphata.logic.sere.rst +0 -0
  47. {morphata-2.0.2 → morphata-2.0.4}/docs/api/morphata.logic.strel.rst +0 -0
  48. {morphata-2.0.2 → morphata-2.0.4}/docs/api/morphata.operators.matrix.rst +0 -0
  49. {morphata-2.0.2 → morphata-2.0.4}/docs/api/morphata.operators.polynomial.rst +0 -0
  50. {morphata-2.0.2 → morphata-2.0.4}/docs/api/morphata.operators.rst +0 -0
  51. {morphata-2.0.2 → morphata-2.0.4}/docs/api/morphata.symbolic.rst +0 -0
  52. {morphata-2.0.2 → morphata-2.0.4}/docs/concepts/acceptance.rst +0 -0
  53. {morphata-2.0.2 → morphata-2.0.4}/docs/concepts/hoa.rst +0 -0
  54. {morphata-2.0.2 → morphata-2.0.4}/docs/concepts/index.rst +0 -0
  55. {morphata-2.0.2 → morphata-2.0.4}/docs/concepts/operators.rst +0 -0
  56. {morphata-2.0.2 → morphata-2.0.4}/docs/concepts/spec.rst +0 -0
  57. {morphata-2.0.2 → morphata-2.0.4}/docs/concepts/weights.rst +0 -0
  58. {morphata-2.0.2 → morphata-2.0.4}/docs/conf.py +0 -0
  59. {morphata-2.0.2 → morphata-2.0.4}/docs/index.rst +0 -0
  60. {morphata-2.0.2 → morphata-2.0.4}/docs/quick-start.rst +0 -0
  61. {morphata-2.0.2 → morphata-2.0.4}/docs/symbolic-transforms.rst +0 -0
  62. {morphata-2.0.2 → morphata-2.0.4}/justfile +0 -0
  63. {morphata-2.0.2 → morphata-2.0.4}/mypy.ini +0 -0
  64. {morphata-2.0.2 → morphata-2.0.4}/pixi.lock +0 -0
  65. {morphata-2.0.2 → morphata-2.0.4}/pixi.toml +0 -0
  66. {morphata-2.0.2 → morphata-2.0.4}/project.utf-8.add +0 -0
  67. {morphata-2.0.2 → morphata-2.0.4}/project.utf-8.add.spl +0 -0
  68. {morphata-2.0.2 → morphata-2.0.4}/pyrefly.toml +0 -0
  69. {morphata-2.0.2 → morphata-2.0.4}/pyrightconfig.json +0 -0
  70. {morphata-2.0.2 → morphata-2.0.4}/pytest.toml +0 -0
  71. {morphata-2.0.2 → morphata-2.0.4}/ruff.toml +0 -0
  72. {morphata-2.0.2 → morphata-2.0.4}/src/morphata/__init__.py +0 -0
  73. {morphata-2.0.2 → morphata-2.0.4}/src/morphata/acceptance.py +0 -0
  74. {morphata-2.0.2 → morphata-2.0.4}/src/morphata/alphabet.py +0 -0
  75. {morphata-2.0.2 → morphata-2.0.4}/src/morphata/automaton.py +0 -0
  76. {morphata-2.0.2 → morphata-2.0.4}/src/morphata/builder.py +0 -0
  77. {morphata-2.0.2 → morphata-2.0.4}/src/morphata/hoa/__init__.py +0 -0
  78. {morphata-2.0.2 → morphata-2.0.4}/src/morphata/hoa/__main__.py +0 -0
  79. {morphata-2.0.2 → morphata-2.0.4}/src/morphata/hoa/acc_expr.py +0 -0
  80. {morphata-2.0.2 → morphata-2.0.4}/src/morphata/hoa/hoa.lark +0 -0
  81. {morphata-2.0.2 → morphata-2.0.4}/src/morphata/hoa/parser.py +0 -0
  82. {morphata-2.0.2 → morphata-2.0.4}/src/morphata/logic/__init__.py +0 -0
  83. {morphata-2.0.2 → morphata-2.0.4}/src/morphata/logic/_common.py +0 -0
  84. {morphata-2.0.2 → morphata-2.0.4}/src/morphata/logic/ltl.py +0 -0
  85. {morphata-2.0.2 → morphata-2.0.4}/src/morphata/logic/psl.py +0 -0
  86. {morphata-2.0.2 → morphata-2.0.4}/src/morphata/logic/sere.py +0 -0
  87. {morphata-2.0.2 → morphata-2.0.4}/src/morphata/logic/strel.py +0 -0
  88. {morphata-2.0.2 → morphata-2.0.4}/src/morphata/operators/__init__.py +0 -0
  89. {morphata-2.0.2 → morphata-2.0.4}/src/morphata/operators/_backend.py +0 -0
  90. {morphata-2.0.2 → morphata-2.0.4}/src/morphata/operators/matrix.py +0 -0
  91. {morphata-2.0.2 → morphata-2.0.4}/src/morphata/operators/polynomial.py +0 -0
  92. {morphata-2.0.2 → morphata-2.0.4}/src/morphata/py.typed +0 -0
  93. {morphata-2.0.2 → morphata-2.0.4}/src/morphata/symbolic/bdd.py +0 -0
  94. {morphata-2.0.2 → morphata-2.0.4}/src/morphata/symbolic/equivalence.py +0 -0
  95. {morphata-2.0.2 → morphata-2.0.4}/tests/hoa/aut1.hoa +0 -0
  96. {morphata-2.0.2 → morphata-2.0.4}/tests/hoa/aut11.hoa +0 -0
  97. {morphata-2.0.2 → morphata-2.0.4}/tests/hoa/aut2.hoa +0 -0
  98. {morphata-2.0.2 → morphata-2.0.4}/tests/hoa/aut3.2.hoa +0 -0
  99. {morphata-2.0.2 → morphata-2.0.4}/tests/hoa/aut3.hoa +0 -0
  100. {morphata-2.0.2 → morphata-2.0.4}/tests/hoa/aut4.hoa +0 -0
  101. {morphata-2.0.2 → morphata-2.0.4}/tests/hoa/aut5.hoa +0 -0
  102. {morphata-2.0.2 → morphata-2.0.4}/tests/hoa/aut6.hoa +0 -0
  103. {morphata-2.0.2 → morphata-2.0.4}/tests/hoa/aut7.hoa +0 -0
  104. {morphata-2.0.2 → morphata-2.0.4}/tests/hoa/aut8.hoa +0 -0
  105. {morphata-2.0.2 → morphata-2.0.4}/tests/hoa/finite_aut.hoa +0 -0
  106. {morphata-2.0.2 → morphata-2.0.4}/tests/hoa/gen_buchi_3.hoa +0 -0
  107. {morphata-2.0.2 → morphata-2.0.4}/tests/hoa/gen_cobuchi_2.hoa +0 -0
  108. {morphata-2.0.2 → morphata-2.0.4}/tests/hoa/rabin_2pair.hoa +0 -0
  109. {morphata-2.0.2 → morphata-2.0.4}/tests/hoa/streett_2pair.hoa +0 -0
  110. {morphata-2.0.2 → morphata-2.0.4}/tests/hoa/test_exporter_basic.py +0 -0
  111. {morphata-2.0.2 → morphata-2.0.4}/tests/hoa/test_exporter_roundtrip.py +0 -0
  112. {morphata-2.0.2 → morphata-2.0.4}/tests/hoa/test_parser.py +0 -0
  113. {morphata-2.0.2 → morphata-2.0.4}/tests/logic/__init__.py +0 -0
  114. {morphata-2.0.2 → morphata-2.0.4}/tests/logic/test_ltl.py +0 -0
  115. {morphata-2.0.2 → morphata-2.0.4}/tests/logic/test_psl.py +0 -0
  116. {morphata-2.0.2 → morphata-2.0.4}/tests/logic/test_sere.py +0 -0
  117. {morphata-2.0.2 → morphata-2.0.4}/tests/logic/test_sere_properties.py +0 -0
  118. {morphata-2.0.2 → morphata-2.0.4}/tests/logic/test_strel.py +0 -0
  119. {morphata-2.0.2 → morphata-2.0.4}/tests/operators/test_matrix_from_ir.py +0 -0
  120. {morphata-2.0.2 → morphata-2.0.4}/tests/operators/test_matrix_from_ltl.py +0 -0
  121. {morphata-2.0.2 → morphata-2.0.4}/tests/operators/test_polynomial_from_ir.py +0 -0
  122. {morphata-2.0.2 → morphata-2.0.4}/tests/symbolic/test_bdd_helpers.py +0 -0
  123. {morphata-2.0.2 → morphata-2.0.4}/tests/symbolic/test_from_hoaf.py +0 -0
  124. {morphata-2.0.2 → morphata-2.0.4}/tests/symbolic/test_ltlf2dfa_equivalence.py +0 -0
  125. {morphata-2.0.2 → morphata-2.0.4}/tests/symbolic/test_step.py +0 -0
  126. {morphata-2.0.2 → morphata-2.0.4}/tests/symbolic/test_symbolic_automaton.py +0 -0
  127. {morphata-2.0.2 → morphata-2.0.4}/tests/symbolic/test_symbolic_from_ir.py +0 -0
  128. {morphata-2.0.2 → morphata-2.0.4}/tests/symbolic/test_transforms.py +0 -0
  129. {morphata-2.0.2 → morphata-2.0.4}/tests/symbolic/test_transforms_ir.py +0 -0
  130. {morphata-2.0.2 → morphata-2.0.4}/tests/symbolic/test_transforms_properties.py +0 -0
  131. {morphata-2.0.2 → morphata-2.0.4}/tests/test_alphabet.py +0 -0
  132. {morphata-2.0.2 → morphata-2.0.4}/tests/test_automaton.py +0 -0
  133. {morphata-2.0.2 → morphata-2.0.4}/tests/test_builder.py +0 -0
  134. {morphata-2.0.2 → morphata-2.0.4}/tests/test_from_parsed.py +0 -0
  135. {morphata-2.0.2 → morphata-2.0.4}/ty.toml +0 -0
@@ -23,7 +23,10 @@
23
23
  "Skill(superpowers:requesting-code-review)",
24
24
  "Bash(timeout 15 python -c ' *)",
25
25
  "Read(//tmp/**)",
26
- "Bash(echo \"exit: $?\")"
26
+ "Bash(echo \"exit: $?\")",
27
+ "Skill(superpowers:executing-plans)",
28
+ "Bash(ruff check *)",
29
+ "Bash(ruff format *)"
27
30
  ]
28
31
  }
29
32
  }
@@ -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,5 +1,30 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.0.4 — 2026-05-28
4
+
5
+ ### Added
6
+
7
+ Technically a feature release almost immediately
8
+ after another feature release masquerading as a patch,
9
+ this release adds the ability to translate from a `SymbolicAutomaton` back to an
10
+ `Automaton`, allowing for the `SymbolicAutomaton` to be exported to an HOA file.
11
+
12
+ ## 2.0.3 — 2026-05-28
13
+
14
+ ### Added
15
+
16
+ Added a new `morphata.symbolic.compose` API
17
+ that provides some helper functions to construct `SymbolicAutomaton` from composing two
18
+ minimal DFAs or to construct minimal DFAs from LTLf formula with a lot of overlapping
19
+ conjunctive clauses (essentially uses `compose_min_dfa`).
20
+
21
+ This helps fix some downstream issues where the AFA can have maybe 20 states
22
+ (like in Craftax tech trees),
23
+ but always using the `SymbolicAutomaton.to_min_dfa()` pipeline causes us to hit the
24
+ worst-case exponential blow up.
25
+ But, since these specifications have the structure of being conjunctive clauses of much
26
+ smaller minimal DFAs, having a better composition operation benefits us.
27
+
3
28
  ## 2.0.2 — 2026-05-27
4
29
 
5
30
  ### Added
@@ -0,0 +1,137 @@
1
+ Metadata-Version: 2.4
2
+ Name: morphata
3
+ Version: 2.0.4
4
+ Summary: Representation theory for automata, made practical.
5
+ Project-URL: Documentation, https://docs.anandb.dev/morphata
6
+ Project-URL: Repository, https://git.anandb.dev/morphata.git
7
+ Project-URL: Changelog, https://docs.anandb.dev/morphata/changelog.html
8
+ Author-email: Anand Balakrishnan <anandbala1597@gmail.com>
9
+ License-Expression: BSD-3-Clause
10
+ License-File: LICENSE
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
13
+ Requires-Python: >=3.12
14
+ Requires-Dist: algebraic-arrays>=1.3.0
15
+ Requires-Dist: attrs>=25.4.0
16
+ Requires-Dist: dd>=0.6.0
17
+ Requires-Dist: jaxtyping>=0.3
18
+ Requires-Dist: lark>=1.3.1
19
+ Requires-Dist: logic-asts>=1.6.2
20
+ Requires-Dist: networkx
21
+ Requires-Dist: numpy
22
+ Requires-Dist: types-networkx
23
+ Requires-Dist: typing-extensions
24
+ Provides-Extra: dot
25
+ Requires-Dist: pydot>=4.0.1; extra == 'dot'
26
+ Provides-Extra: jax
27
+ Requires-Dist: jax>=0.8; extra == 'jax'
28
+ Provides-Extra: torch
29
+ Requires-Dist: torch>=2.0; extra == 'torch'
30
+ Description-Content-Type: text/markdown
31
+
32
+ # morphata
33
+
34
+ **morphata** does representation theory for automata, and makes it practical.
35
+
36
+ An automaton -- its states, transitions, and acceptance condition -- is a
37
+ *structural* object. *Operators* turn that structure into a quantitative
38
+ interpretation: a matrix-valued morphism over a semiring, a multilinear
39
+ polynomial over a bounded distributive lattice, or a BDD-canonicalised
40
+ symbolic representation. ``morphata`` packages both halves -- the structures
41
+ and the morphisms that interpret them -- in one library.
42
+
43
+ It builds on [`algebraic`](https://docs.anandb.dev/algebraic) for
44
+ backend-agnostic semiring algebra (NumPy, JAX, PyTorch).
45
+
46
+ ## Quick Start
47
+
48
+ ### Installation
49
+
50
+ ```bash
51
+ pip install morphata
52
+ ```
53
+
54
+ ### Basic Usage
55
+
56
+ ```python
57
+ import algebraic
58
+ import jax.numpy as jnp
59
+ from morphata.builder import AutomatonBuilder
60
+ from morphata.alphabet import weighted_alphabet
61
+ from morphata.operators.matrix import MatrixOperator
62
+ import logic_asts as logic
63
+
64
+ builder: AutomatonBuilder[int, str] = AutomatonBuilder()
65
+ builder.add_state(0, initial=True)
66
+ builder.add_state(1, accepting=True)
67
+ builder.add_transition(0, guard=logic.Variable("a"), succ=1)
68
+ aut = builder.build()
69
+
70
+ def xi(x, guard):
71
+ return float(x[0])
72
+
73
+ alphabet = weighted_alphabet(xi=xi)
74
+ maxplus = algebraic.semirings.tropical_semiring(minplus=False)
75
+ op = MatrixOperator.from_nfa(aut, semiring=maxplus, alphabet=alphabet)
76
+
77
+ x = jnp.array([2.0])
78
+ transitions = op.cost_transitions(x)
79
+ ```
80
+
81
+ For more, see the [documentation](https://docs.anandb.dev/morphata).
82
+
83
+ ## Citation
84
+
85
+ If you use the matrix operator or this package, please cite one of:
86
+
87
+ - For differentiable weighted automata in general:
88
+
89
+ ```bibtex
90
+ @inproceedings{balakrishnan2024differentiable,
91
+ title = {Differentiable {{Weighted Automata}}},
92
+ booktitle = {{{ICML}} 2024 {{Workshop}} on {{Differentiable Almost Everything}}: {{Differentiable Relaxations}}, {{Algorithms}}, {{Operators}}, and {{Simulators}}},
93
+ author = {Balakrishnan, Anand and Deshmukh, Jyotirmoy V.},
94
+ year = 2024,
95
+ month = jun,
96
+ url = {https://openreview.net/forum?id=k2hIQYqHTh},
97
+ copyright = {All rights reserved},
98
+ langid = {english}
99
+ }
100
+ ```
101
+
102
+ - For weighted automata in motion planning:
103
+
104
+ ```bibtex
105
+ @inproceedings{balakrishnan2024motion,
106
+ title = {Motion {{Planning}} for {{Automata-based Objectives}} Using {{Efficient Gradient-based Methods}}},
107
+ booktitle = {2024 {{IEEE}}/{{RSJ International Conference}} on {{Intelligent Robots}} and {{Systems}} ({{IROS}})},
108
+ author = {Balakrishnan, Anand and Atasever, Merve and Deshmukh, Jyotirmoy V.},
109
+ year = 2024,
110
+ month = oct,
111
+ pages = {13734--13740},
112
+ issn = {2153-0866},
113
+ doi = {10.1109/IROS58592.2024.10802177}
114
+ }
115
+ ```
116
+
117
+ - For alternating weighted automata in multi-agent systems:
118
+
119
+ ```bibtex
120
+ @inproceedings{balakrishnan2025monitoring,
121
+ title = {Monitoring {{Spatially Distributed Cyber-Physical Systems}} with {{Alternating Finite Automata}}},
122
+ booktitle = {Proceedings of the 28th {{ACM International Conference}} on {{Hybrid Systems}}: {{Computation}} and {{Control}}},
123
+ author = {Balakrishnan, Anand and Paul, Sheryl and Silvetti, Simone and Nenzi, Laura and Deshmukh, Jyotirmoy V.},
124
+ year = 2025,
125
+ month = may,
126
+ pages = {1--11},
127
+ publisher = {ACM},
128
+ address = {Irvine CA USA},
129
+ doi = {10.1145/3716863.3718033},
130
+ isbn = {979-8-4007-1504-4},
131
+ langid = {english}
132
+ }
133
+ ```
134
+
135
+ ## License
136
+
137
+ See LICENSE file for details.
@@ -2,13 +2,14 @@
2
2
  name = "morphata"
3
3
  authors = [{ name = "Anand Balakrishnan", email = "anandbala1597@gmail.com" }]
4
4
  description = "Representation theory for automata, made practical."
5
- version = "2.0.2"
5
+ version = "2.0.4"
6
6
  requires-python = ">=3.12"
7
7
  classifiers = [
8
8
  "Intended Audience :: Science/Research",
9
9
  "Topic :: Scientific/Engineering :: Mathematics",
10
10
  ]
11
11
  license = "BSD-3-Clause"
12
+ readme = "README.md"
12
13
 
13
14
  dependencies = [
14
15
  "attrs>=25.4.0",