morphata 2.3.0__tar.gz → 2.3.1__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 (149) hide show
  1. morphata-2.3.1/.rstcheck.cfg +43 -0
  2. {morphata-2.3.0 → morphata-2.3.1}/AGENTS.md +7 -0
  3. {morphata-2.3.0 → morphata-2.3.1}/CHANGELOG.md +168 -93
  4. {morphata-2.3.0 → morphata-2.3.1}/PKG-INFO +1 -1
  5. {morphata-2.3.0 → morphata-2.3.1}/docs/api/modules.rst +1 -0
  6. {morphata-2.3.0 → morphata-2.3.1}/docs/api/morphata.storage.rst +19 -0
  7. {morphata-2.3.0 → morphata-2.3.1}/docs/api/morphata.symbolic.rst +2 -0
  8. morphata-2.3.1/docs/api/morphata.utils.rst +61 -0
  9. {morphata-2.3.0 → morphata-2.3.1}/docs/concepts/acceptance.rst +2 -2
  10. {morphata-2.3.0 → morphata-2.3.1}/docs/concepts/hoa.rst +3 -3
  11. {morphata-2.3.0 → morphata-2.3.1}/docs/concepts/operators.rst +1 -1
  12. {morphata-2.3.0 → morphata-2.3.1}/docs/index.rst +3 -2
  13. {morphata-2.3.0 → morphata-2.3.1}/docs/quick-start.rst +2 -2
  14. {morphata-2.3.0 → morphata-2.3.1}/src/morphata/_version.py +2 -2
  15. {morphata-2.3.0 → morphata-2.3.1}/src/morphata/acceptance.py +1 -1
  16. {morphata-2.3.0 → morphata-2.3.1}/src/morphata/logic/_common.py +5 -2
  17. {morphata-2.3.0 → morphata-2.3.1}/src/morphata/operators/_backend.py +1 -1
  18. {morphata-2.3.0 → morphata-2.3.1}/src/morphata/operators/matrix.py +1 -1
  19. {morphata-2.3.0 → morphata-2.3.1}/src/morphata/storage/__init__.py +7 -1
  20. morphata-2.3.1/src/morphata/storage/run.py +204 -0
  21. {morphata-2.3.0 → morphata-2.3.1}/src/morphata/storage/signature.py +99 -26
  22. {morphata-2.3.0 → morphata-2.3.1}/src/morphata/symbolic/automaton.py +81 -9
  23. {morphata-2.3.0 → morphata-2.3.1}/src/morphata/symbolic/bdd.py +133 -67
  24. {morphata-2.3.0 → morphata-2.3.1}/src/morphata/symbolic/distance.py +85 -8
  25. {morphata-2.3.0 → morphata-2.3.1}/src/morphata/symbolic/transforms.py +117 -82
  26. morphata-2.3.1/src/morphata/utils/__init__.py +22 -0
  27. morphata-2.3.1/src/morphata/utils/edit_distance.py +624 -0
  28. morphata-2.3.1/src/morphata/utils/witness.py +139 -0
  29. {morphata-2.3.0 → morphata-2.3.1}/tests/hoa/test_exporter.py +100 -52
  30. morphata-2.3.1/tests/hoa/test_parser.py +118 -0
  31. {morphata-2.3.0 → morphata-2.3.1}/tests/logic/test_ltl.py +22 -20
  32. {morphata-2.3.0 → morphata-2.3.1}/tests/logic/test_sere.py +0 -30
  33. {morphata-2.3.0 → morphata-2.3.1}/tests/operators/test_matrix.py +31 -5
  34. {morphata-2.3.0 → morphata-2.3.1}/tests/operators/test_polynomial.py +9 -10
  35. {morphata-2.3.0 → morphata-2.3.1}/tests/storage/test_serialize.py +9 -4
  36. {morphata-2.3.0 → morphata-2.3.1}/tests/storage/test_signature.py +56 -2
  37. {morphata-2.3.0 → morphata-2.3.1}/tests/symbolic/test_bdd_helpers.py +4 -19
  38. {morphata-2.3.0 → morphata-2.3.1}/tests/symbolic/test_distance.py +140 -72
  39. {morphata-2.3.0 → morphata-2.3.1}/tests/symbolic/test_language_equivalence.py +146 -0
  40. {morphata-2.3.0 → morphata-2.3.1}/tests/symbolic/test_ltlf2dfa_equivalence.py +19 -30
  41. {morphata-2.3.0 → morphata-2.3.1}/tests/symbolic/test_symbolic_automaton.py +1 -9
  42. {morphata-2.3.0 → morphata-2.3.1}/tests/test_alphabet.py +0 -25
  43. morphata-2.3.1/tests/utils/test_edit_distance.py +499 -0
  44. morphata-2.3.0/.agents/plans/2026-10-16-canonical-rfsa.md +0 -456
  45. morphata-2.3.0/.rstcheck.cfg +0 -13
  46. morphata-2.3.0/src/morphata/utils/__init__.py +0 -9
  47. morphata-2.3.0/tests/hoa/test_parser.py +0 -62
  48. {morphata-2.3.0 → morphata-2.3.1}/.basedpyright/baseline.json +0 -0
  49. {morphata-2.3.0 → morphata-2.3.1}/.dockerignore +0 -0
  50. {morphata-2.3.0 → morphata-2.3.1}/.editorconfig +0 -0
  51. {morphata-2.3.0 → morphata-2.3.1}/.envrc +0 -0
  52. {morphata-2.3.0 → morphata-2.3.1}/.envrc.recommended +0 -0
  53. {morphata-2.3.0 → morphata-2.3.1}/.fdignore +0 -0
  54. {morphata-2.3.0 → morphata-2.3.1}/.gitattributes +0 -0
  55. {morphata-2.3.0 → morphata-2.3.1}/.gitignore +0 -0
  56. {morphata-2.3.0 → morphata-2.3.1}/.mailmap +0 -0
  57. {morphata-2.3.0 → morphata-2.3.1}/.python-version +0 -0
  58. {morphata-2.3.0 → morphata-2.3.1}/CLAUDE.md +0 -0
  59. {morphata-2.3.0 → morphata-2.3.1}/CONTRIBUTING.md +0 -0
  60. {morphata-2.3.0 → morphata-2.3.1}/LICENSE +0 -0
  61. {morphata-2.3.0 → morphata-2.3.1}/README.rst +0 -0
  62. {morphata-2.3.0 → morphata-2.3.1}/bench/README.md +0 -0
  63. {morphata-2.3.0 → morphata-2.3.1}/bench/__init__.py +0 -0
  64. {morphata-2.3.0 → morphata-2.3.1}/bench/conftest.py +0 -0
  65. {morphata-2.3.0 → morphata-2.3.1}/bench/test_distance_perf.py +0 -0
  66. {morphata-2.3.0 → morphata-2.3.1}/bench/test_equivalence_perf.py +0 -0
  67. {morphata-2.3.0 → morphata-2.3.1}/bench/test_hoa_exporter_perf.py +0 -0
  68. {morphata-2.3.0 → morphata-2.3.1}/bench/test_hoa_parser_perf.py +0 -0
  69. {morphata-2.3.0 → morphata-2.3.1}/bench/test_matrix_operator_perf.py +0 -0
  70. {morphata-2.3.0 → morphata-2.3.1}/bench/test_sere_nlm_perf.py +0 -0
  71. {morphata-2.3.0 → morphata-2.3.1}/bench/test_transforms_perf.py +0 -0
  72. {morphata-2.3.0 → morphata-2.3.1}/ci/make-release +0 -0
  73. {morphata-2.3.0 → morphata-2.3.1}/docs/Makefile +0 -0
  74. {morphata-2.3.0 → morphata-2.3.1}/docs/_static/navigation.html +0 -0
  75. {morphata-2.3.0 → morphata-2.3.1}/docs/api/morphata.acc_expr.rst +0 -0
  76. {morphata-2.3.0 → morphata-2.3.1}/docs/api/morphata.acceptance.rst +0 -0
  77. {morphata-2.3.0 → morphata-2.3.1}/docs/api/morphata.alphabet.rst +0 -0
  78. {morphata-2.3.0 → morphata-2.3.1}/docs/api/morphata.builder.rst +0 -0
  79. {morphata-2.3.0 → morphata-2.3.1}/docs/api/morphata.hoa.rst +0 -0
  80. {morphata-2.3.0 → morphata-2.3.1}/docs/api/morphata.logic.rst +0 -0
  81. {morphata-2.3.0 → morphata-2.3.1}/docs/api/morphata.operators.rst +0 -0
  82. {morphata-2.3.0 → morphata-2.3.1}/docs/bibliography.rst +0 -0
  83. {morphata-2.3.0 → morphata-2.3.1}/docs/changelog.md +0 -0
  84. {morphata-2.3.0 → morphata-2.3.1}/docs/concepts/index.rst +0 -0
  85. {morphata-2.3.0 → morphata-2.3.1}/docs/concepts/symbolic.rst +0 -0
  86. {morphata-2.3.0 → morphata-2.3.1}/docs/concepts/weights.rst +0 -0
  87. {morphata-2.3.0 → morphata-2.3.1}/docs/conf.py +0 -0
  88. {morphata-2.3.0 → morphata-2.3.1}/docs/refs.bib +0 -0
  89. {morphata-2.3.0 → morphata-2.3.1}/justfile +0 -0
  90. {morphata-2.3.0 → morphata-2.3.1}/mypy.ini +0 -0
  91. {morphata-2.3.0 → morphata-2.3.1}/pixi.lock +0 -0
  92. {morphata-2.3.0 → morphata-2.3.1}/project.utf-8.add +0 -0
  93. {morphata-2.3.0 → morphata-2.3.1}/project.utf-8.add.spl +0 -0
  94. {morphata-2.3.0 → morphata-2.3.1}/pyproject.toml +0 -0
  95. {morphata-2.3.0 → morphata-2.3.1}/pyrightconfig.json +0 -0
  96. {morphata-2.3.0 → morphata-2.3.1}/pytest.toml +0 -0
  97. {morphata-2.3.0 → morphata-2.3.1}/rules/no-derivative-multiarg.yml +0 -0
  98. {morphata-2.3.0 → morphata-2.3.1}/rules/no-typing-as-ty.yml +0 -0
  99. {morphata-2.3.0 → morphata-2.3.1}/sgconfig.yml +0 -0
  100. {morphata-2.3.0 → morphata-2.3.1}/src/morphata/__init__.py +0 -0
  101. {morphata-2.3.0 → morphata-2.3.1}/src/morphata/acc_expr.py +0 -0
  102. {morphata-2.3.0 → morphata-2.3.1}/src/morphata/alphabet.py +0 -0
  103. {morphata-2.3.0 → morphata-2.3.1}/src/morphata/builder.py +0 -0
  104. {morphata-2.3.0 → morphata-2.3.1}/src/morphata/hoa/__init__.py +0 -0
  105. {morphata-2.3.0 → morphata-2.3.1}/src/morphata/hoa/__main__.py +0 -0
  106. {morphata-2.3.0 → morphata-2.3.1}/src/morphata/hoa/hoa.lark +0 -0
  107. {morphata-2.3.0 → morphata-2.3.1}/src/morphata/hoa/parser.py +0 -0
  108. {morphata-2.3.0 → morphata-2.3.1}/src/morphata/logic/__init__.py +0 -0
  109. {morphata-2.3.0 → morphata-2.3.1}/src/morphata/logic/__init__.pyi +0 -0
  110. {morphata-2.3.0 → morphata-2.3.1}/src/morphata/logic/ltl.py +0 -0
  111. {morphata-2.3.0 → morphata-2.3.1}/src/morphata/logic/psl.py +0 -0
  112. {morphata-2.3.0 → morphata-2.3.1}/src/morphata/logic/sere.py +0 -0
  113. {morphata-2.3.0 → morphata-2.3.1}/src/morphata/operators/__init__.py +0 -0
  114. {morphata-2.3.0 → morphata-2.3.1}/src/morphata/operators/jax.py +0 -0
  115. {morphata-2.3.0 → morphata-2.3.1}/src/morphata/operators/polynomial.py +0 -0
  116. {morphata-2.3.0 → morphata-2.3.1}/src/morphata/py.typed +0 -0
  117. {morphata-2.3.0 → morphata-2.3.1}/src/morphata/storage/serialize.py +0 -0
  118. {morphata-2.3.0 → morphata-2.3.1}/src/morphata/symbolic/__init__.py +0 -0
  119. {morphata-2.3.0 → morphata-2.3.1}/src/morphata/utils/expressions.py +0 -0
  120. {morphata-2.3.0 → morphata-2.3.1}/src/morphata/utils/memory.py +0 -0
  121. {morphata-2.3.0 → morphata-2.3.1}/src/morphata/utils/sampling.py +0 -0
  122. {morphata-2.3.0 → morphata-2.3.1}/tests/conftest.py +0 -0
  123. {morphata-2.3.0 → morphata-2.3.1}/tests/hoa/aut1.hoa +0 -0
  124. {morphata-2.3.0 → morphata-2.3.1}/tests/hoa/aut11.hoa +0 -0
  125. {morphata-2.3.0 → morphata-2.3.1}/tests/hoa/aut2.hoa +0 -0
  126. {morphata-2.3.0 → morphata-2.3.1}/tests/hoa/aut3.2.hoa +0 -0
  127. {morphata-2.3.0 → morphata-2.3.1}/tests/hoa/aut3.hoa +0 -0
  128. {morphata-2.3.0 → morphata-2.3.1}/tests/hoa/aut4.hoa +0 -0
  129. {morphata-2.3.0 → morphata-2.3.1}/tests/hoa/aut5.hoa +0 -0
  130. {morphata-2.3.0 → morphata-2.3.1}/tests/hoa/aut6.hoa +0 -0
  131. {morphata-2.3.0 → morphata-2.3.1}/tests/hoa/aut7.hoa +0 -0
  132. {morphata-2.3.0 → morphata-2.3.1}/tests/hoa/aut8.hoa +0 -0
  133. {morphata-2.3.0 → morphata-2.3.1}/tests/hoa/finite_aut.hoa +0 -0
  134. {morphata-2.3.0 → morphata-2.3.1}/tests/hoa/gen_buchi_3.hoa +0 -0
  135. {morphata-2.3.0 → morphata-2.3.1}/tests/hoa/gen_cobuchi_2.hoa +0 -0
  136. {morphata-2.3.0 → morphata-2.3.1}/tests/hoa/rabin_2pair.hoa +0 -0
  137. {morphata-2.3.0 → morphata-2.3.1}/tests/hoa/streett_2pair.hoa +0 -0
  138. {morphata-2.3.0 → morphata-2.3.1}/tests/logic/__init__.py +0 -0
  139. {morphata-2.3.0 → morphata-2.3.1}/tests/logic/test_psl.py +0 -0
  140. {morphata-2.3.0 → morphata-2.3.1}/tests/operators/test_jax.py +0 -0
  141. {morphata-2.3.0 → morphata-2.3.1}/tests/operators/test_reductions.py +0 -0
  142. {morphata-2.3.0 → morphata-2.3.1}/tests/symbolic/test_compose.py +0 -0
  143. {morphata-2.3.0 → morphata-2.3.1}/tests/symbolic/test_from_hoaf.py +0 -0
  144. {morphata-2.3.0 → morphata-2.3.1}/tests/symbolic/test_rfsa.py +0 -0
  145. {morphata-2.3.0 → morphata-2.3.1}/tests/symbolic/test_to_hoa.py +0 -0
  146. {morphata-2.3.0 → morphata-2.3.1}/tests/symbolic/test_transforms.py +0 -0
  147. {morphata-2.3.0 → morphata-2.3.1}/tests/test_acceptance_validation.py +0 -0
  148. {morphata-2.3.0 → morphata-2.3.1}/tests/test_builder.py +0 -0
  149. {morphata-2.3.0 → morphata-2.3.1}/uv.lock +0 -0
@@ -0,0 +1,43 @@
1
+ [rstcheck]
2
+ ignore_directives =
3
+ automodule,
4
+ autoclass,
5
+ autofunction,
6
+ autotype,
7
+ autodata,
8
+ autoexception,
9
+ bibliography,
10
+ footbibliography
11
+ ignore_roles =
12
+ external:py:mod,
13
+ cite,
14
+ cite:p,
15
+ cite:ps,
16
+ cite:t,
17
+ cite:ts,
18
+ cite:ct,
19
+ cite:cts,
20
+ cite:empty,
21
+ cite:alp,
22
+ cite:alps,
23
+ cite:label,
24
+ cite:labelpar,
25
+ cite:year,
26
+ cite:yearpar,
27
+ cite:author,
28
+ cite:authors,
29
+ cite:authorpar,
30
+ cite:authorpars,
31
+ cite:cauthor,
32
+ cite:cauthors,
33
+ footcite,
34
+ footcite:p,
35
+ footcite:ps,
36
+ footcite:t,
37
+ footcite:ts,
38
+ footcite:ct,
39
+ footcite:cts
40
+ report_level = WARNING
41
+
42
+
43
+ # vim: set ft=dosini
@@ -46,6 +46,13 @@ Just invoke the tool.
46
46
  If a tool is missing, stop and tell the user rather than trying to bootstrap
47
47
  or reinstall the environment.
48
48
 
49
+ ## Tests
50
+
51
+ Write tests to verify the common case and edge cases with distinct failure modes.
52
+ Do not add tests solely to increase coverage. Check expected behavior against
53
+ the public contract or an independent reference; avoid tests that merely repeat
54
+ the implementation. Each test should identify a behavior or failure it checks.
55
+
49
56
  ## Documentation
50
57
 
51
58
  Use NumPy/numpydoc-style sections for Python docstrings. Use RST math markup
@@ -1,5 +1,71 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.3.1 — 2026-11-11
4
+
5
+ This isn't a hefty release,
6
+ but does have some nice QoL things as well as some performance updates and bug fixes.
7
+ A large chunk of these was discovered/needed
8
+ while dogfooding my learned automata embeddings project,
9
+ so I guess they are informed by "real-world" scenarios.
10
+
11
+ ### Added
12
+
13
+ - `SymbolicAutomaton.distinguishing_words` finds the shortest words accepted by exactly
14
+ one of two automata.
15
+ You can ask for several witnesses, set a minimum length, and randomize ties.
16
+ Works with alternating automata too, without first determinizing them,
17
+ and the two automata can use different BDD managers.
18
+
19
+ - Persistent automata can now be run directly through `morphata.storage`,
20
+ without reconstructing a symbolic automaton or needing a BDD manager.
21
+ There is a `distinguishing_words` helper for these too.
22
+
23
+ - Edit distances for DFAs given as explicit transition tables,
24
+ through `DFATable` and the `dfa_*` distance functions in `morphata.utils`.
25
+ These cover unsigned and signed directed Levenshtein distances,
26
+ and minimum language separation, without BDD operations.
27
+ Helpers convert deterministic symbolic and persistent automata to tables.
28
+ The unsigned distance also accepts a `cap`,
29
+ useful when you only care how far apart the languages are up to some budget.
30
+
31
+ - `CanonicalDfaHasher.residual_digests_from_table` computes residual digests directly
32
+ from a complete DFA table.
33
+ Gives the same digests as the symbolic path with `minimize=False`,
34
+ without going through BDDs.
35
+
36
+ ### Performance
37
+
38
+ - Symbolic language repair distance now starts with small edit budgets
39
+ and increases them until it finds one that works.
40
+ Small distances no longer require building the automaton
41
+ for the full finite-repair bound before the search even starts.
42
+
43
+ - Transition partitioning does less repeated BDD work.
44
+ Transformations also retain known determinism and alternation flags,
45
+ saving checks whose answers the construction already knows.
46
+
47
+ ### Fixed
48
+
49
+ - Matrix operators built from specifications now materialize constant accepting
50
+ and rejecting sinks.
51
+ Those successors need actual states in the matrix representation too.
52
+
53
+ - BDD caches used during partitioning, determinization,
54
+ and dealternation are now cleared when their scope ends,
55
+ fixing memory leaks from retained BDD references.
56
+
57
+ - Alphabet domains are now applied when compiling specifications,
58
+ exposing transition partitions, and reversing DFA transitions.
59
+ Impossible predicate valuations no longer contribute edges in these paths.
60
+
61
+ - Automata with constant initial formulas and no states now compose correctly.
62
+ Previously, composition assumed there was at least one state to remap.
63
+
64
+ ### Documentation
65
+
66
+ - The automaton sampling helpers now have API documentation, covering random automata,
67
+ structured language families, and mutations.
68
+
3
69
  ## 2.3.0 — 2026-10-21
4
70
 
5
71
  This is *another* hefty release,
@@ -16,9 +82,9 @@ which has allowed me to harden `morphata` and also add a bunch of relevant featu
16
82
  exploration and language reversal, without building the full DFA.
17
83
  Follows the same API as the other transformers.
18
84
 
19
- - `SymbolicAutomaton` now has helpers for determining if its language is empty, whether
20
- it contains the language of another automaton, and a helper to return relational edges
21
- (for nondeterministic automata only).
85
+ - `SymbolicAutomaton` now has helpers for determining if its language is empty,
86
+ whether it contains the language of another automaton,
87
+ and a helper to return relational edges (for nondeterministic automata only).
22
88
 
23
89
  - `JaxBatchedMatrixOperator` runs batches of integer-encoded words over dense matrix
24
90
  operators using JAX's `lax.scan`.
@@ -64,13 +130,14 @@ which has allowed me to harden `morphata` and also add a bunch of relevant featu
64
130
  The JSON helpers remain for string predicates.
65
131
 
66
132
  - Custom alphabet domain callbacks now need to accept `care_vars`.
67
- `BooleanAlphabet.intersect_with_domain` uses this to constrain guards using only
68
- the predicates those guards actually mention.
133
+ `BooleanAlphabet.intersect_with_domain` uses this to constrain guards using only the
134
+ predicates those guards actually mention.
69
135
 
70
136
  ### Fixed
71
137
 
72
- - DFA minimization now compares guards on the full alphabet domain, so guards
73
- that differ only on impossible predicate valuations do not split equivalent states.
138
+ - DFA minimization now compares guards on the full alphabet domain,
139
+ so guards that differ only on impossible predicate valuations do not split equivalent
140
+ states.
74
141
 
75
142
  - Symbolic transformations reuse alphabet domains within each construction stage.
76
143
  Dealternation, determinization,
@@ -144,8 +211,8 @@ so I will just be making this a patch release
144
211
  which keeps finite-alphabet guards compact without changing their meaning.
145
212
 
146
213
  - Symbolic automaton transformations accept an optional initial state formula,
147
- allowing a transformation to start from a formula other than the automaton's
148
- stored initial condition without modifying the source automaton.
214
+ allowing a transformation to start from a formula other than the automaton's stored
215
+ initial condition without modifying the source automaton.
149
216
 
150
217
  - `prune_unreachable`, which removes the states unreachable from an automaton's initial
151
218
  states and, with `remove_sink_states=True`, the materialized sink states too (an
@@ -170,9 +237,9 @@ so I will just be making this a patch release
170
237
  since the manager's variable levels are not stable under reordering.
171
238
  The partition is recomputed on each call.
172
239
 
173
- - A `make_sink_states` keyword on `remove_alternation`, `determinize`, and
174
- `minimize_dfa`, controlling whether constant `True` and `False` successors are
175
- materialized as indexed sink states.
240
+ - A `make_sink_states` keyword on `remove_alternation`, `determinize`,
241
+ and `minimize_dfa`, controlling whether constant `True`
242
+ and `False` successors are materialized as indexed sink states.
176
243
 
177
244
  - A `reverse_language` utility that produces an automaton
178
245
  that accepts the reversed language.
@@ -198,12 +265,12 @@ so I will just be making this a patch release
198
265
  - Add a signed version of the Levenshtein distance between two nondeterministic finite
199
266
  automata.
200
267
 
201
- - `SemiringAlphabet` now has two optional fields that allows one to enumerate all
202
- symbols in the alphabet (not just predicates), and test if some symbol is contained in
203
- the alphabet.
268
+ - `SemiringAlphabet` now has two optional fields
269
+ that allows one to enumerate all symbols in the alphabet
270
+ (not just predicates), and test if some symbol is contained in the alphabet.
204
271
 
205
- - In addition, `BooleanAlphabet` now have a mandatory `domain` keyword argument which
206
- constrains the universe of allowed predicate valuations as a BDD.
272
+ - In addition, `BooleanAlphabet` now have a mandatory `domain` keyword argument
273
+ which constrains the universe of allowed predicate valuations as a BDD.
207
274
 
208
275
  - `morphata.storage` has means to store canonical representations (morphata-specific) of
209
276
  `SymbolicAutomaton` to disk.
@@ -226,9 +293,11 @@ so I will just be making this a patch release
226
293
 
227
294
  ### Developer Experience
228
295
 
229
- - The Pixi environments now describe CPU, CUDA 12, and CUDA 13 targets with virtual
230
- packages, so dependency resolution does not have to guess which machine it is
231
- eventually going to meet. `sphinx-autobuild` also joins the development toolbox
296
+ - The Pixi environments now describe CPU, CUDA 12,
297
+ and CUDA 13 targets with virtual packages,
298
+ so dependency resolution does not have to guess
299
+ which machine it is eventually going to meet.
300
+ `sphinx-autobuild` also joins the development toolbox
232
301
  for faster documentation tinkering.
233
302
 
234
303
  ### Fixed
@@ -298,9 +367,9 @@ and increase the surface area of properly typed code.
298
367
  The variadic `compose_automata`, `is_language_equivalent`,
299
368
  and `forward_bisim_reduction` helpers cover composition, equivalence checking,
300
369
  and language-preserving quotienting without leaving the symbolic representation.
301
- - Add a typed `BDDManager`/`Function` API and symbolic utilities for replacing a
302
- manager, reindexing or quotienting states, materializing sinks, traversing reachable
303
- states, and constructing universal acceptors and rejectors.
370
+ - Add a typed `BDDManager`/`Function` API and symbolic utilities
371
+ for replacing a manager, reindexing or quotienting states, materializing sinks,
372
+ traversing reachable states, and constructing universal acceptors and rejectors.
304
373
  - Support arbitrary HOA v1 acceptance expressions through `GenericCondition`,
305
374
  in addition to the concrete finite, Büchi, co-Büchi, generalized Büchi/co-Büchi,
306
375
  Rabin, and Streett conditions.
@@ -324,8 +393,9 @@ and increase the surface area of properly typed code.
324
393
  use a Boolean state expression for an alternating conjunction.
325
394
  The old `variant`/`DeltaKind` construction API is gone.
326
395
  - Alphabets now separate symbol labeling from guard weighting.
327
- `BooleanAlphabet.labeling` returns an `AP -> bool` mapping, while operator weight
328
- functions receive guards in sum-of-products form. `Alphabet`, `WeightedAlphabet`,
396
+ `BooleanAlphabet.labeling` returns an `AP -> bool` mapping,
397
+ while operator weight functions receive guards in sum-of-products form.
398
+ `Alphabet`, `WeightedAlphabet`,
329
399
  and `weighted_alphabet` are replaced by `BooleanAlphabet`, `SemiringAlphabet`,
330
400
  `powerset_alphabet`, and `finite_alphabet`.
331
401
  - `MatrixOperator` and `PolynomialOperator` are constructed with
@@ -341,12 +411,13 @@ and increase the surface area of properly typed code.
341
411
  ### Fixed
342
412
 
343
413
  - Correct symbolic derivatives for negated variables and SERE nodes.
344
- - Fix breadth-first traversal ordering, symbolic language equivalence, and transition
345
- partitioning when multiple guards have the same residual.
346
- - Correct `step()` labeling and the structural checks for deterministic and
347
- forward-progressing automata.
348
- - Make builders, HOA conversion, and both operators handle sparse external state IDs
349
- consistently by using dense indices only internally.
414
+ - Fix breadth-first traversal ordering, symbolic language equivalence,
415
+ and transition partitioning when multiple guards have the same residual.
416
+ - Correct `step()` labeling and the structural checks for deterministic
417
+ and forward-progressing automata.
418
+ - Make builders, HOA conversion,
419
+ and both operators handle sparse external state IDs consistently by using dense
420
+ indices only internally.
350
421
 
351
422
  ### Removed
352
423
 
@@ -355,9 +426,9 @@ and increase the surface area of properly typed code.
355
426
  `symbolic.equivalence`, and `symbolic.compose` modules.
356
427
  Their surviving operations now live on `SymbolicAutomaton` or in `morphata.symbolic`;
357
428
  there is no replacement for STREL or BDD-JSON persistence in this release.
358
- - Remove the specialized composition-map/transition-enumeration APIs and structural
359
- `prune`; use general symbolic composition and `forward_bisim_reduction` where those
360
- semantics fit.
429
+ - Remove the specialized composition-map/transition-enumeration APIs
430
+ and structural `prune`; use general symbolic composition and `forward_bisim_reduction`
431
+ where those semantics fit.
361
432
 
362
433
  ## 2.1.0 — 2026-06-11
363
434
 
@@ -409,9 +480,9 @@ and increase the surface area of properly typed code.
409
480
 
410
481
  ### Fixed
411
482
 
412
- - Using `SymbolicAutomaton.to_bdd_json` and `SymbolicAutomaton.from_bdd_json` did not
413
- preserve the positive normal form of the BDDs because we were using incorrect Shannon
414
- cofactor expansion.
483
+ - Using `SymbolicAutomaton.to_bdd_json`
484
+ and `SymbolicAutomaton.from_bdd_json` did not preserve the positive normal form of the
485
+ BDDs because we were using incorrect Shannon cofactor expansion.
415
486
 
416
487
  ### Performance
417
488
 
@@ -453,10 +524,9 @@ and increase the surface area of properly typed code.
453
524
  but minimization can merge classes such
454
525
  that a representative's per-factor successor lands on a non-representative tuple,
455
526
  so the full surjection has to be there.
456
- - `iter_composed_transitions` enumerates `(state, symbol, successor)`
457
- of a composed DFA without building the product BDD, walking factor
458
- deltas directly and hitting `tuple_to_class` once per
459
- `(state, symbol)`.
527
+ - `iter_composed_transitions` enumerates `(state, symbol, successor)` of a composed DFA
528
+ without building the product BDD, walking factor deltas directly and hitting
529
+ `tuple_to_class` once per `(state, symbol)`.
460
530
 
461
531
  ### Performance
462
532
 
@@ -469,23 +539,22 @@ and increase the surface area of properly typed code.
469
539
  and per-class point dicts across same-support rows.
470
540
  Conjunction-fold DFAs get 1.3-1.6x; `F a & F b & F c & F d` q1 went ~343us -> ~175us.
471
541
  - `isomorphic` routes through a compact support-reduced fingerprint
472
- and skips the broadcast expansion. `|AP|=19` sparse case:
473
- ~333ms -> ~83ms.
542
+ and skips the broadcast expansion.
543
+ `|AP|=19` sparse case: ~333ms -> ~83ms.
474
544
 
475
545
  ### Changed
476
546
 
477
- - `SymbolicAutomaton` carries a derived `ap_inverse: Mapping[str, AP]`
478
- populated in `__post_init__`; `to_automaton` and
479
- `compose._merge_managers` route through it.
480
- - New symbolic benches are pytest-benchmark cases, so timings get
481
- recorded rather than gated on a hard-coded threshold.
547
+ - `SymbolicAutomaton` carries a derived `ap_inverse: Mapping[str, AP]` populated in
548
+ `__post_init__`; `to_automaton` and `compose._merge_managers` route through it.
549
+ - New symbolic benches are pytest-benchmark cases,
550
+ so timings get recorded rather than gated on a hard-coded threshold.
482
551
 
483
552
  ## 2.0.7 — 2026-06-02
484
553
 
485
554
  ### Fixed
486
555
 
487
- - The BDD JSON exporter in `dd.cudd` needed to be worked around because of a bug where
488
- `mgr.true` and `mrg.false` do not survive a round trip serialization.
556
+ - The BDD JSON exporter in `dd.cudd` needed to be worked around because of a bug
557
+ where `mgr.true` and `mrg.false` do not survive a round trip serialization.
489
558
 
490
559
  ## 2.0.6 — 2026-06-02
491
560
 
@@ -497,8 +566,9 @@ and increase the surface area of properly typed code.
497
566
 
498
567
  ### Added
499
568
 
500
- - Added a free-floating function that helps enumerate the successor BDDs from a symbolic
501
- automaton state, given a specific alphabet and set of symbols.
569
+ - Added a free-floating function
570
+ that helps enumerate the successor BDDs from a symbolic automaton state,
571
+ given a specific alphabet and set of symbols.
502
572
 
503
573
  ## 2.0.5 — 2026-06-01
504
574
 
@@ -552,8 +622,8 @@ smaller minimal DFAs, having a better composition operation benefits us.
552
622
  and bounded `FusionRepeat` desugar through `logic_asts.spec.Expr.expand`.
553
623
  Unbounded `FusionRepeat[:*i..]` deliberately raises `NotImplementedError` —
554
624
  I don't have a clean expansion for it yet.
555
- - Benchmarking script for SERE NLM intersection, so I can see when I
556
- break the perf curve.
625
+ - Benchmarking script for SERE NLM intersection,
626
+ so I can see when I break the perf curve.
557
627
 
558
628
  ### Changed
559
629
 
@@ -564,8 +634,7 @@ smaller minimal DFAs, having a better composition operation benefits us.
564
634
 
565
635
  ### Miscellaneous
566
636
 
567
- - The README is linked from `pyproject.toml`, so it actually shows up
568
- on PyPI.
637
+ - The README is linked from `pyproject.toml`, so it actually shows up on PyPI.
569
638
 
570
639
  ## 2.0.1 — 2026-05-24
571
640
 
@@ -599,8 +668,9 @@ interest.
599
668
  Two LTL helpers (`_aut_symbolic_expansion`, `_empty_trace_value`) were renamed to
600
669
  `ltl_symbolic_expansion` / `ltl_empty_trace_value` so PSL can delegate to them for
601
670
  pure-LTL subformulas.
602
- - Moved off `uv` and onto Pixi. `uv`'s handling of CUDA packages and
603
- the way it churns the lockfile weren't working for me; Pixi does.
671
+ - Moved off `uv` and onto Pixi.
672
+ `uv`'s handling of CUDA packages and the way it churns the lockfile weren't working
673
+ for me; Pixi does.
604
674
 
605
675
  ### Fixed
606
676
 
@@ -646,9 +716,8 @@ short-lived manager for these things.
646
716
  - `psl_to_automaton` does not yet decompose LTL temporal operators
647
717
  that wrap PSL closures at the top level (e.g. `Always(StrongClosure(r))`).
648
718
  Lift the PSL fragment out of the surrounding LTL temporal operator before calling.
649
- - The continuation of a `SuffixImpliesExist` / `SuffixImpliesUniv`
650
- must be a pure-LTL formula; nested PSL closures inside a
651
- continuation raise `NotImplementedError`.
719
+ - The continuation of a `SuffixImpliesExist` / `SuffixImpliesUniv` must be a pure-LTL
720
+ formula; nested PSL closures inside a continuation raise `NotImplementedError`.
652
721
 
653
722
  ## 2.0.0 — 2026-05-23
654
723
 
@@ -711,9 +780,10 @@ which will be summarized below.
711
780
  and `WeightedAlphabet.from_predicates(...)` folds per-AP predicates through the
712
781
  semiring.
713
782
  The reverse direction is deliberately omitted —
714
- recovering Boolean semantics from arbitrary `V` needs an ordering and a cut (cf.
715
- STL), and pretending otherwise would silently produce garbage.
716
- - **Ergonomic construction.** `AutomatonBuilder` replaces the old NFA builder.
783
+ recovering Boolean semantics from arbitrary `V` needs an ordering and a cut
784
+ (cf. STL), and pretending otherwise would silently produce garbage.
785
+ - **Ergonomic construction.**
786
+ `AutomatonBuilder` replaces the old NFA builder.
717
787
  Add states, add transitions with a guard and a successor
718
788
  (single state, set of states, or a raw `BoolExpr` for full alternation),
719
789
  and `build()` cleans up by merging rows that share a successor.
@@ -741,9 +811,11 @@ which will be summarized below.
741
811
  The BDD cofactor path works for any `BooleanAlphabet`,
742
812
  so you can feed a `SymbolicAutomaton` over integers, graph nodes, whatever —
743
813
  as long as you can write a labeling.
744
- - **The AFA-to-min-DFA pipeline actually exists now.** `nfa_from_afa` (dealternation),
745
- `determinize` (subset construction with a sink), `minimize` (Hopcroft) —
746
- all share one BDD manager and successively tighten the invariants on successor BDDs.
814
+ - **The AFA-to-min-DFA pipeline actually exists now.**
815
+ `nfa_from_afa` (dealternation), `determinize`
816
+ (subset construction with a sink),
817
+ `minimize` (Hopcroft) — all share one BDD manager
818
+ and successively tighten the invariants on successor BDDs.
747
819
  `SymbolicAutomaton.to_min_dfa` chains the three.
748
820
  Finite-word only for now; omega-regular determinisation is a different beast.
749
821
  - **DFA equivalence checks.**
@@ -754,17 +826,19 @@ which will be summarized below.
754
826
 
755
827
  ### Operators
756
828
 
757
- - **One per-step shape.** `SymbolicAutomaton`, `MatrixOperator`,
829
+ - **One per-step shape.**
830
+ `SymbolicAutomaton`, `MatrixOperator`,
758
831
  and `PolynomialOperator` all expose the same `step(current, symbol)` / `run(word)` /
759
832
  `accepts(word)` shape.
760
833
  Each binds its alphabet at construction
761
834
  (matching how the semiring / algebra were already bound),
762
835
  so the per-call API is monomorphic and `V` is pinned end-to-end.
763
836
  Wanted symmetry between the layers; got it.
764
- - **`PolynomialOperator` evaluates lazily.** `step` evaluates guards against
765
- `alphabet.weight` on the fly, with no per-symbol precomputation — important because
766
- the natural LTL alphabet is `Σ = 2^AP`, and you really don't want to materialise
767
- `2^|AP|` polynomials up front.
837
+ - **`PolynomialOperator` evaluates lazily.**
838
+ `step` evaluates guards against `alphabet.weight` on the fly,
839
+ with no per-symbol precomputation —
840
+ important because the natural LTL alphabet is `Σ = 2^AP`,
841
+ and you really don't want to materialise `2^|AP|` polynomials up front.
768
842
  The AFA's row structure stays static
769
843
  (good for JAX tracing), and the alphabet decides whether to memoise.
770
844
  - **Polynomial performance.**
@@ -774,8 +848,9 @@ which will be summarized below.
774
848
 
775
849
  ### Tooling and Tests
776
850
 
777
- - **Hand-written changelogs.** `git cliff` was producing entries
778
- that needed enough editing that I might as well write them by hand.
851
+ - **Hand-written changelogs.**
852
+ `git cliff` was producing entries that needed enough editing that I might
853
+ as well write them by hand.
779
854
  `ci/make-release` no longer touches `CHANGELOG.md` or tag messages.
780
855
  Run `git cliff` separately if you want a draft to crib from.
781
856
  - **Tests.**
@@ -788,23 +863,23 @@ which will be summarized below.
788
863
 
789
864
  Documentation and tooling cleanup.
790
865
 
791
- - Replaced stray non-ASCII characters in docs (those that survived from
792
- copy-paste rather than authored content).
866
+ - Replaced stray non-ASCII characters in docs
867
+ (those that survived from copy-paste rather than authored content).
793
868
  - Started publishing CHANGELOGs and wired them into the Sphinx docs.
794
869
  - Added a `pytest-timeout` dev dependency so hanging tests fail loudly.
795
- - Added a mypy override for the `dd` BDD library so type-checking is
796
- clean for symbolic code.
870
+ - Added a mypy override for the `dd` BDD library so type-checking is clean
871
+ for symbolic code.
797
872
 
798
873
  ## 1.0.2 — 2026-04-17
799
874
 
800
- - Added `dd` as a dependency along with a small in-tree wrapper, in
801
- preparation for the BDD-backed symbolic layer.
802
- - Fixed a queue ordering bug in `ltl_to_automaton` that produced
803
- incorrect successor expansions for some operator combinations.
804
- - First pass at user-facing documentation (README, LICENSE, package
805
- URLs) and at RST linting for the docs.
806
- - Trimmed CI to a single type checker (`mypy`) and stopped silently
807
- hiding missing-import errors.
875
+ - Added `dd` as a dependency along with a small in-tree wrapper,
876
+ in preparation for the BDD-backed symbolic layer.
877
+ - Fixed a queue ordering bug in `ltl_to_automaton`
878
+ that produced incorrect successor expansions for some operator combinations.
879
+ - First pass at user-facing documentation
880
+ (README, LICENSE, package URLs) and at RST linting for the docs.
881
+ - Trimmed CI to a single type checker (`mypy`)
882
+ and stopped silently hiding missing-import errors.
808
883
  - Ported all operators to the current `algebraic` API.
809
884
  This is source-incompatible with the previous internal `automatix` algebra module.
810
885
 
@@ -813,15 +888,15 @@ Documentation and tooling cleanup.
813
888
  Initial wave of feature work that established the current shape of the library.
814
889
 
815
890
  - Boolean polynomials implemented on top of BDDs.
816
- - STREL: Lark-based parser for STREL expressions and a translation to
817
- alternating finite automata, including the spatial Reach/Escape
818
- operators.
891
+ - STREL: Lark-based parser for STREL expressions
892
+ and a translation to alternating finite automata,
893
+ including the spatial Reach/Escape operators.
819
894
  - LTL/LTLf to alternating Büchi / finite automata.
820
895
  - Algebraic structures moved to the separate `algebraic` package.
821
- - HOA parsing moved out into a dedicated `hoaparser` package (later
822
- re-integrated as `morphata.hoa`).
823
- - JAX and PyTorch made optional dependencies; both backends are picked
824
- up automatically by `algebraic` when installed.
896
+ - HOA parsing moved out into a dedicated `hoaparser` package
897
+ (later re-integrated as `morphata.hoa`).
898
+ - JAX and PyTorch made optional dependencies;
899
+ both backends are picked up automatically by `algebraic` when installed.
825
900
  - Test suite rebuilt against the new APIs.
826
901
 
827
902
  ## Pre-1.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: morphata
3
- Version: 2.3.0
3
+ Version: 2.3.1
4
4
  Summary: Representation theory for automata, made practical.
5
5
  Project-URL: Documentation, https://docs.anandb.dev/morphata
6
6
  Project-URL: Repository, https://git.anandb.dev/morphata.git
@@ -16,3 +16,4 @@
16
16
  morphata.hoa
17
17
  morphata.builder
18
18
  morphata.storage
19
+ morphata.utils
@@ -34,3 +34,22 @@ Persistent automata
34
34
  .. autofunction:: morphata.storage.automaton_to_json
35
35
 
36
36
  .. autofunction:: morphata.storage.automaton_from_json
37
+
38
+ Running persistent automata
39
+ ---------------------------
40
+
41
+ These functions run a :class:`~morphata.storage.PersistentAutomaton` without a BDD
42
+ manager. A run configuration is a positive state formula in minimal form.
43
+
44
+ .. automodule:: morphata.storage.run
45
+ :no-members:
46
+
47
+ .. autofunction:: morphata.storage.minimal_state_sop
48
+
49
+ .. autofunction:: morphata.storage.step
50
+
51
+ .. autofunction:: morphata.storage.is_accepting
52
+
53
+ .. autofunction:: morphata.storage.run_semantics
54
+
55
+ .. autofunction:: morphata.storage.distinguishing_words
@@ -22,6 +22,8 @@
22
22
 
23
23
  .. autofunction:: morphata.symbolic.language_separation_distance
24
24
 
25
+ .. autofunction:: morphata.symbolic.distance.dfa_from_symbolic_automaton
26
+
25
27
  .. autoclass:: morphata.symbolic.MacroStateLimitError
26
28
 
27
29
  .. autofunction:: morphata.symbolic.minimize_dfa
@@ -0,0 +1,61 @@
1
+ ``morphata.utils``
2
+ ==================
3
+
4
+ .. automodule:: morphata.utils
5
+ :no-members:
6
+
7
+ Distinguishing words
8
+ --------------------
9
+
10
+ .. automodule:: morphata.utils.witness
11
+ :no-members:
12
+
13
+ .. autoclass:: morphata.utils.RunSemantics
14
+ :members: initial, step, is_accepting
15
+
16
+ .. autofunction:: morphata.utils.distinguishing_words
17
+
18
+ Edit distances between DFAs
19
+ ---------------------------
20
+
21
+ .. automodule:: morphata.utils.edit_distance
22
+ :no-members:
23
+
24
+ .. autoclass:: morphata.utils.DFATable
25
+ :members: initial, transitions, accepting
26
+
27
+ .. autofunction:: morphata.utils.dfa_unsigned_levenshtein_distance
28
+
29
+ .. autofunction:: morphata.utils.dfa_signed_levenshtein_distance
30
+
31
+ .. autofunction:: morphata.utils.dfa_language_separation_distance
32
+
33
+ .. autofunction:: morphata.utils.dfa_from_persistent
34
+
35
+ Sampling automata
36
+ -----------------
37
+
38
+ .. automodule:: morphata.utils.sampling
39
+ :no-members:
40
+
41
+ .. autofunction:: morphata.utils.sampling.random_dfa
42
+
43
+ .. autofunction:: morphata.utils.sampling.fresh_random_afa
44
+
45
+ .. autofunction:: morphata.utils.sampling.reverse_random_dfa
46
+
47
+ .. autofunction:: morphata.utils.sampling.kth_from_end
48
+
49
+ .. autofunction:: morphata.utils.sampling.modular_counters
50
+
51
+ .. autofunction:: morphata.utils.sampling.eventually_conjunction
52
+
53
+ .. autofunction:: morphata.utils.sampling.safety_conjunction
54
+
55
+ .. autofunction:: morphata.utils.sampling.locally_testable
56
+
57
+ .. autofunction:: morphata.utils.sampling.finite_trace_pattern
58
+
59
+ .. autofunction:: morphata.utils.sampling.mutate
60
+
61
+ .. autofunction:: morphata.utils.sampling.vary
@@ -2,11 +2,11 @@ Acceptance Conditions
2
2
  =====================
3
3
 
4
4
  An :class:`~morphata.acceptance.AcceptanceCondition` decides when a run
5
- of an automaton is considered accepting. morphata supports both
5
+ of an automaton is considered accepting. ``morphata`` supports both
6
6
  **finite-word** and **omega-regular** acceptance.
7
7
 
8
8
  Omega-regular support is structural: conditions can be represented, imported,
9
- exported, and complemented. morphata does not evaluate infinite runs.
9
+ exported, and complemented. ``morphata`` does not evaluate infinite runs.
10
10
  :meth:`~morphata.symbolic.SymbolicAutomaton.accepts`, operators, language
11
11
  equivalence, and current transforms implement finite-word semantics only.
12
12
 
@@ -2,7 +2,7 @@ HOA Format
2
2
  ==========
3
3
 
4
4
  The `Hanoi Omega-Automata (HOA) <https://adl.github.io/hoaf/>`_ format
5
- is a standard text representation for omega-automata. morphata provides
5
+ is a standard text representation for omega-automata. ``morphata`` provides
6
6
  a parser for HOA v1, extended with a ``Final(n)`` operator for
7
7
  finite-word acceptance.
8
8
 
@@ -76,7 +76,7 @@ The ``Final(n)`` Extension
76
76
 
77
77
  Standard HOA defines ``Inf(n)`` (visit set *n* infinitely often) and
78
78
  ``Fin(n)`` (visit set *n* finitely often) for omega-regular acceptance.
79
- morphata adds ``Final(n)`` for finite-word automata:
79
+ ``morphata`` adds ``Final(n)`` for finite-word automata:
80
80
 
81
81
  - ``Final(n)``: accept if the run **ends in** a state marked with
82
82
  acceptance set *n*.
@@ -122,7 +122,7 @@ This automaton accepts exactly the finite words that contain ``a``.
122
122
  State-based acceptance only
123
123
  ---------------------------
124
124
 
125
- morphata reads only state-based acceptance markers (``{n}`` attached
125
+ ``morphata`` reads only state-based acceptance markers (``{n}`` attached
126
126
  to ``State:`` declarations). Per-edge ``{n}`` markers in transition
127
127
  lines are ignored. Convert transition-based HOA files to state-based
128
128
  form before loading them.