morphata 2.2.3__tar.gz → 2.3.0__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.
- morphata-2.3.0/.agents/plans/2026-10-16-canonical-rfsa.md +456 -0
- {morphata-2.2.3 → morphata-2.3.0}/.envrc +2 -0
- {morphata-2.2.3 → morphata-2.3.0}/AGENTS.md +12 -0
- {morphata-2.2.3 → morphata-2.3.0}/CHANGELOG.md +87 -0
- {morphata-2.2.3 → morphata-2.3.0}/PKG-INFO +4 -3
- {morphata-2.2.3 → morphata-2.3.0}/bench/README.md +6 -0
- {morphata-2.2.3 → morphata-2.3.0}/bench/test_distance_perf.py +19 -20
- {morphata-2.2.3 → morphata-2.3.0}/bench/test_transforms_perf.py +129 -10
- {morphata-2.2.3 → morphata-2.3.0}/docs/api/morphata.operators.rst +9 -0
- {morphata-2.2.3 → morphata-2.3.0}/docs/api/morphata.symbolic.rst +9 -1
- morphata-2.3.0/docs/bibliography.rst +4 -0
- {morphata-2.2.3 → morphata-2.3.0}/docs/concepts/operators.rst +24 -0
- {morphata-2.2.3 → morphata-2.3.0}/docs/concepts/symbolic.rst +55 -0
- {morphata-2.2.3 → morphata-2.3.0}/docs/conf.py +14 -4
- {morphata-2.2.3 → morphata-2.3.0}/docs/index.rst +1 -0
- morphata-2.3.0/docs/refs.bib +516 -0
- {morphata-2.2.3 → morphata-2.3.0}/pixi.lock +663 -692
- {morphata-2.2.3 → morphata-2.3.0}/pyproject.toml +25 -4
- {morphata-2.2.3 → morphata-2.3.0}/pytest.toml +2 -0
- {morphata-2.2.3 → morphata-2.3.0}/src/morphata/_version.py +2 -2
- {morphata-2.2.3 → morphata-2.3.0}/src/morphata/alphabet.py +128 -17
- {morphata-2.2.3 → morphata-2.3.0}/src/morphata/hoa/__init__.py +2 -0
- {morphata-2.2.3 → morphata-2.3.0}/src/morphata/logic/__init__.py +8 -1
- morphata-2.3.0/src/morphata/operators/jax.py +811 -0
- {morphata-2.2.3 → morphata-2.3.0}/src/morphata/operators/matrix.py +11 -15
- {morphata-2.2.3 → morphata-2.3.0}/src/morphata/operators/polynomial.py +3 -0
- {morphata-2.2.3 → morphata-2.3.0}/src/morphata/storage/__init__.py +2 -0
- morphata-2.3.0/src/morphata/storage/serialize.py +554 -0
- {morphata-2.2.3 → morphata-2.3.0}/src/morphata/symbolic/__init__.py +13 -1
- {morphata-2.2.3 → morphata-2.3.0}/src/morphata/symbolic/automaton.py +297 -3
- {morphata-2.2.3 → morphata-2.3.0}/src/morphata/symbolic/bdd.py +11 -7
- morphata-2.3.0/src/morphata/symbolic/distance.py +952 -0
- {morphata-2.2.3 → morphata-2.3.0}/src/morphata/symbolic/transforms.py +344 -29
- morphata-2.3.0/src/morphata/utils/sampling.py +728 -0
- morphata-2.3.0/tests/operators/test_jax.py +81 -0
- {morphata-2.2.3 → morphata-2.3.0}/tests/operators/test_matrix.py +49 -17
- morphata-2.3.0/tests/operators/test_reductions.py +116 -0
- {morphata-2.2.3 → morphata-2.3.0}/tests/storage/test_serialize.py +146 -5
- {morphata-2.2.3 → morphata-2.3.0}/tests/storage/test_signature.py +28 -0
- {morphata-2.2.3 → morphata-2.3.0}/tests/symbolic/test_bdd_helpers.py +12 -0
- morphata-2.3.0/tests/symbolic/test_distance.py +584 -0
- morphata-2.3.0/tests/symbolic/test_rfsa.py +529 -0
- {morphata-2.2.3 → morphata-2.3.0}/tests/symbolic/test_symbolic_automaton.py +53 -1
- {morphata-2.2.3 → morphata-2.3.0}/tests/symbolic/test_transforms.py +156 -1
- morphata-2.3.0/tests/test_alphabet.py +123 -0
- {morphata-2.2.3 → morphata-2.3.0}/uv.lock +1031 -863
- morphata-2.2.3/src/morphata/storage/serialize.py +0 -260
- morphata-2.2.3/src/morphata/symbolic/distance.py +0 -361
- morphata-2.2.3/tests/symbolic/test_distance.py +0 -150
- morphata-2.2.3/tests/test_alphabet.py +0 -58
- {morphata-2.2.3 → morphata-2.3.0}/.basedpyright/baseline.json +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/.dockerignore +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/.editorconfig +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/.envrc.recommended +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/.fdignore +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/.gitattributes +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/.gitignore +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/.mailmap +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/.python-version +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/.rstcheck.cfg +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/CLAUDE.md +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/CONTRIBUTING.md +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/LICENSE +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/README.rst +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/bench/__init__.py +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/bench/conftest.py +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/bench/test_equivalence_perf.py +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/bench/test_hoa_exporter_perf.py +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/bench/test_hoa_parser_perf.py +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/bench/test_matrix_operator_perf.py +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/bench/test_sere_nlm_perf.py +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/ci/make-release +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/docs/Makefile +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/docs/_static/navigation.html +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/docs/api/modules.rst +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/docs/api/morphata.acc_expr.rst +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/docs/api/morphata.acceptance.rst +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/docs/api/morphata.alphabet.rst +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/docs/api/morphata.builder.rst +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/docs/api/morphata.hoa.rst +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/docs/api/morphata.logic.rst +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/docs/api/morphata.storage.rst +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/docs/changelog.md +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/docs/concepts/acceptance.rst +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/docs/concepts/hoa.rst +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/docs/concepts/index.rst +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/docs/concepts/weights.rst +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/docs/quick-start.rst +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/justfile +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/mypy.ini +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/project.utf-8.add +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/project.utf-8.add.spl +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/pyrightconfig.json +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/rules/no-derivative-multiarg.yml +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/rules/no-typing-as-ty.yml +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/sgconfig.yml +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/src/morphata/__init__.py +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/src/morphata/acc_expr.py +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/src/morphata/acceptance.py +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/src/morphata/builder.py +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/src/morphata/hoa/__main__.py +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/src/morphata/hoa/hoa.lark +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/src/morphata/hoa/parser.py +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/src/morphata/logic/__init__.pyi +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/src/morphata/logic/_common.py +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/src/morphata/logic/ltl.py +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/src/morphata/logic/psl.py +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/src/morphata/logic/sere.py +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/src/morphata/operators/__init__.py +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/src/morphata/operators/_backend.py +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/src/morphata/py.typed +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/src/morphata/storage/signature.py +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/src/morphata/utils/__init__.py +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/src/morphata/utils/expressions.py +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/src/morphata/utils/memory.py +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/tests/conftest.py +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/tests/hoa/aut1.hoa +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/tests/hoa/aut11.hoa +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/tests/hoa/aut2.hoa +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/tests/hoa/aut3.2.hoa +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/tests/hoa/aut3.hoa +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/tests/hoa/aut4.hoa +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/tests/hoa/aut5.hoa +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/tests/hoa/aut6.hoa +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/tests/hoa/aut7.hoa +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/tests/hoa/aut8.hoa +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/tests/hoa/finite_aut.hoa +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/tests/hoa/gen_buchi_3.hoa +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/tests/hoa/gen_cobuchi_2.hoa +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/tests/hoa/rabin_2pair.hoa +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/tests/hoa/streett_2pair.hoa +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/tests/hoa/test_exporter.py +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/tests/hoa/test_parser.py +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/tests/logic/__init__.py +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/tests/logic/test_ltl.py +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/tests/logic/test_psl.py +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/tests/logic/test_sere.py +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/tests/operators/test_polynomial.py +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/tests/symbolic/test_compose.py +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/tests/symbolic/test_from_hoaf.py +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/tests/symbolic/test_language_equivalence.py +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/tests/symbolic/test_ltlf2dfa_equivalence.py +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/tests/symbolic/test_to_hoa.py +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/tests/test_acceptance_validation.py +0 -0
- {morphata-2.2.3 → morphata-2.3.0}/tests/test_builder.py +0 -0
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
# Canonical RFSA Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For implementation:** Execute the dependency chain in order and update each
|
|
4
|
+
> checkbox. All tasks are sequential. Agent delegation, commits, and remote
|
|
5
|
+
> actions are not authorized. Perform a separate whole-change review before
|
|
6
|
+
> handing the local diff back to the author.
|
|
7
|
+
|
|
8
|
+
**Goal:** Construct the canonical finite-word RFSA without constructing a full
|
|
9
|
+
DFA or minimizing a DFA in the production path.
|
|
10
|
+
|
|
11
|
+
**Architecture:** Implement the cover-aware subset construction from Section 5
|
|
12
|
+
of Denis, Lemay, and Terlutte, using an online basis of non-coverable reachable
|
|
13
|
+
state subsets. Apply it twice, with language reversal before each pass. Keep
|
|
14
|
+
the algorithm in the existing symbolic transforms module and expose one
|
|
15
|
+
`to_min_rfsa` operation alongside `to_min_dfa`.
|
|
16
|
+
|
|
17
|
+
**Tech Stack:** Python 3.12+, existing `SymbolicAutomaton`, Pollywog-backed BDD
|
|
18
|
+
guards, `Finite` acceptance, pytest, Hypothesis, Ruff, Zuban, BasedPyright,
|
|
19
|
+
Sphinx. No new dependencies.
|
|
20
|
+
|
|
21
|
+
**Spec:** This plan, based on the author's request for the canonical RFSA
|
|
22
|
+
construction without full determinization and the verified literature below.
|
|
23
|
+
|
|
24
|
+
**Assumptions:**
|
|
25
|
+
|
|
26
|
+
- The public name is `to_min_rfsa`, matching `to_min_dfa`.
|
|
27
|
+
- Finite AFAs are accepted through the existing dealternation performed by
|
|
28
|
+
`reverse_language`; this normalization can itself grow exponentially.
|
|
29
|
+
- Canonical means unique up to state renaming, with prime residual states and
|
|
30
|
+
saturated transitions. It does not promise canonical integer IDs or bytes.
|
|
31
|
+
- `initial`, `predicate_exploration_order`, and `abort_on_max_size` follow the
|
|
32
|
+
existing transform conventions, with the bound accounting specified below.
|
|
33
|
+
- Public RFSA diagnostics, arbitrary NFA minimization, experiment tooling, and
|
|
34
|
+
changes to existing transform behavior are outside this plan.
|
|
35
|
+
|
|
36
|
+
## Global Constraints
|
|
37
|
+
|
|
38
|
+
- "Local changes only."
|
|
39
|
+
- "No scheduled, recurring, background, or looping execution, and no spawning
|
|
40
|
+
agents without my approval or an agreed plan that authorizes their work."
|
|
41
|
+
- "If `.jj` exists in the project root, use `jj`."
|
|
42
|
+
- "Morphata must not depend on Automata Encoders."
|
|
43
|
+
- "Use NumPy/numpydoc-style sections for Python docstrings. Use RST math markup
|
|
44
|
+
inside those sections and in the main docstring body."
|
|
45
|
+
- Use the activated environment directly. Do not install dependencies or use
|
|
46
|
+
environment wrappers. Stop and report a missing tool.
|
|
47
|
+
- This is a planning artifact. Implementation requires the author's subsequent
|
|
48
|
+
instruction; the repository defaults to read-only work.
|
|
49
|
+
- Keep this plan local and ignored. Preserve existing changes, including
|
|
50
|
+
`.envrc.recommended` and `CHANGELOG.md`; do not include them in this work.
|
|
51
|
+
- Do not introduce new production modules, public intermediate constructions,
|
|
52
|
+
compatibility layers, or refactors of `explore_and_transform`.
|
|
53
|
+
|
|
54
|
+
## Review Focus
|
|
55
|
+
|
|
56
|
+
- A subset discovered early becomes coverable later: Task 1 late-cover test
|
|
57
|
+
and basis invariant. Removed states must leave no output references.
|
|
58
|
+
- Skipping expansion must preserve all reachable subset generators: Task 1
|
|
59
|
+
exhaustive two-state oracle and closure proof.
|
|
60
|
+
- Set inclusion is different from BDD implication between state disjunctions:
|
|
61
|
+
Task 1 uses `frozenset[int]` and ordinary subset comparison throughout.
|
|
62
|
+
- Language equality alone does not establish canonicality: Task 2 compares
|
|
63
|
+
residual-labelled states, initial/final sets, and all transition guards
|
|
64
|
+
against a separate small-DFA reference.
|
|
65
|
+
- No hidden full DFA construction or alphabet enumeration: Task 2 sentinel
|
|
66
|
+
tests, compressed-language fixtures, and whole-change review.
|
|
67
|
+
- Partial transitions, constants, empty language, alphabet restrictions, and
|
|
68
|
+
selected initial formulas: Tasks 1 and 2 named boundary tests.
|
|
69
|
+
- Resource accounting and exponential cases: Task 1 admission-bound tests and
|
|
70
|
+
Task 2 public documentation. No polynomial-time or output-sensitive claim.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Verified sources and current repository
|
|
75
|
+
|
|
76
|
+
- Zotero key `denis2002residual`, Item ID
|
|
77
|
+
`http://zotero.org/users/5809761/items/8MQULFLR`; verified PDF:
|
|
78
|
+
`/home/anand/Zotero/storage/7RJJJLZ5/Denis et al. - 2002 - Residual finite state automata.pdf`.
|
|
79
|
+
Definition 4.5 specifies canonical states and saturation. Section 5,
|
|
80
|
+
Lemma 5.1 and Theorem 5.2 establish the two-pass construction. The end of
|
|
81
|
+
Section 5 and Proposition 6.2 establish the relevant exponential limits.
|
|
82
|
+
- `src/morphata/symbolic/transforms.py:582-675`: `reverse_language` removes
|
|
83
|
+
alternation, materializes constant successors, reverses edges, swaps initial
|
|
84
|
+
and accepting states, and prunes unreachable states.
|
|
85
|
+
- `src/morphata/symbolic/transforms.py:108-273`: `explore_and_transform` fixes
|
|
86
|
+
output states and edges during exploration. An online basis can later lose
|
|
87
|
+
states, so use a private worklist in the RFSA construction; leave this driver
|
|
88
|
+
unchanged.
|
|
89
|
+
- `src/morphata/symbolic/bdd.py`: `partition_by_state_residual`,
|
|
90
|
+
`state_vars_in_support`, `state_set_to_bdd`, and `relation_from_rows` provide
|
|
91
|
+
symbolic successor partitions and output construction.
|
|
92
|
+
- `src/morphata/symbolic/automaton.py:746-787`: public transform-method pattern.
|
|
93
|
+
`src/morphata/symbolic/__init__.py` provides explicit free-function exports.
|
|
94
|
+
- `tests/symbolic/test_transforms.py` establishes transform behavior and
|
|
95
|
+
bounded property-test patterns; `tests/conftest.py` provides fresh manager
|
|
96
|
+
fixtures. Create one dedicated RFSA test file to keep this substantial test
|
|
97
|
+
family out of the existing large transforms test file.
|
|
98
|
+
- `CONTRIBUTING.md`, `justfile`, and `pytest.toml` establish verification
|
|
99
|
+
commands. Run direct commands; `pytest -q` includes configured source and
|
|
100
|
+
documentation doctests. Sphinx uses `docs/refs.bib`.
|
|
101
|
+
|
|
102
|
+
## Construction contract and notation
|
|
103
|
+
|
|
104
|
+
For a normalized finite NFA \(A=(Q,I,F,\delta)\), all initial/successor formulas
|
|
105
|
+
are disjunctions of indexed states, possibly false. Interpret the false
|
|
106
|
+
formula as the empty subset; true successors must have been materialized by
|
|
107
|
+
the existing normalization. Let \(\mathcal R\) be its reachable state subsets.
|
|
108
|
+
Define
|
|
109
|
+
|
|
110
|
+
\[
|
|
111
|
+
\operatorname{covered}(S,B)
|
|
112
|
+
\iff S=\bigcup\{T\in B\mid T\subsetneq S\}.
|
|
113
|
+
\]
|
|
114
|
+
|
|
115
|
+
The union of an empty family is empty, so the empty subset is always covered.
|
|
116
|
+
The construction \(C(A)\) has the non-coverable members of \(\mathcal R\) as
|
|
117
|
+
states. With final basis \(B\), emit
|
|
118
|
+
|
|
119
|
+
\[
|
|
120
|
+
I_C=\{S\in B\mid S\subseteq I\},\qquad
|
|
121
|
+
F_C=\{S\in B\mid S\cap F\ne\varnothing\},\qquad
|
|
122
|
+
\delta_C(S,a)=\{T\in B\mid T\subseteq\delta(S,a)\}.
|
|
123
|
+
\]
|
|
124
|
+
|
|
125
|
+
The public result is \(C((C(A^R))^R)\). The first pass produces a reachable
|
|
126
|
+
RFSA for the reversed language; the second pass is canonical under Theorem
|
|
127
|
+
5.2. Do not assert canonicality for the first pass on an arbitrary input NFA.
|
|
128
|
+
|
|
129
|
+
### Online basis and proof obligation
|
|
130
|
+
|
|
131
|
+
This scheduling optimization is an implementation adaptation of the paper's
|
|
132
|
+
construction; the paper's definition alone does not prove the optimization.
|
|
133
|
+
|
|
134
|
+
1. Begin with an empty basis and offer the initial subset. For a candidate
|
|
135
|
+
already in the basis, do nothing. For a covered candidate, do not store or
|
|
136
|
+
expand it. Otherwise admit and queue it.
|
|
137
|
+
2. After an admission, remove any basis member now covered by the other
|
|
138
|
+
members. Skip removed members when popping pending work. A member currently
|
|
139
|
+
being expanded may finish expansion from its immutable subset snapshot.
|
|
140
|
+
3. Expand a retained subset \(S\) by partitioning the BDD relation
|
|
141
|
+
\(\bigvee_{q\in S}\Delta(q)\), restricted to
|
|
142
|
+
`aut.alphabet.domain(manager)`. Offer the successor subset for each
|
|
143
|
+
satisfiable guard. Never enumerate alphabet valuations.
|
|
144
|
+
4. Continue until the worklist is empty. Only then assign output state IDs and
|
|
145
|
+
construct the saturated initial formula and transition rows from the final
|
|
146
|
+
basis. Temporary successor partitions may be recomputed during emission;
|
|
147
|
+
do not retain a complete subset-transition graph or permanent visited table.
|
|
148
|
+
|
|
149
|
+
Required argument: every discarded subset remains a union of current basis
|
|
150
|
+
members. Replacing a basis member with a cover preserves that invariant, and
|
|
151
|
+
NFA transition images distribute over union. At termination the basis is
|
|
152
|
+
transition-closed up to union, so it generates all reachable subsets. Every
|
|
153
|
+
retained subset was itself reached from the source initial subset; combining
|
|
154
|
+
these facts and irredundancy identifies the final basis with the non-coverable
|
|
155
|
+
members of \(\mathcal R\). Strict subset covers also show that a removed
|
|
156
|
+
subset cannot later need admission again. Keep this reasoning near the helper.
|
|
157
|
+
|
|
158
|
+
Use `frozenset[int]` for source subsets and BDDs for guards/relations. Boolean
|
|
159
|
+
implication on independent state variables does not test accepted-language
|
|
160
|
+
inclusion. This construction requires neither language hashing nor language
|
|
161
|
+
inclusion queries.
|
|
162
|
+
|
|
163
|
+
### Bounds and exceptional cases
|
|
164
|
+
|
|
165
|
+
- The private pass counts cumulative basis admissions, including members later
|
|
166
|
+
removed. Raise existing `MacroStateLimitError(count)` immediately after the
|
|
167
|
+
first admission exceeding `abort_on_max_size`. A duplicate or covered
|
|
168
|
+
candidate does not increment the count. Do not retain all candidates to count
|
|
169
|
+
them. This bounds admissions, not BDD memory or wall-clock time.
|
|
170
|
+
- The public operation forwards the bound independently to each existing
|
|
171
|
+
reversal/normalization stage and each private pass. It is not a bound on
|
|
172
|
+
final RFSA size. Existing normalization can exceed a zero bound even for an
|
|
173
|
+
empty language; do not add different special-case semantics to the wrapper.
|
|
174
|
+
- An empty basis emits a zero-state automaton with false initial condition and
|
|
175
|
+
`Finite(frozenset())`. Do not materialize an empty-language RFSA state.
|
|
176
|
+
- Keep a nonempty residual containing the empty word accepting. Include every
|
|
177
|
+
eligible initial state and transition, including non-maximal targets.
|
|
178
|
+
- Preserve manager and alphabet identity. Canonicality holds over the supplied
|
|
179
|
+
alphabet domain. Finite AFAs are normalized using existing code; reject
|
|
180
|
+
omega acceptance with `NotImplementedError` before construction.
|
|
181
|
+
- Assign final IDs contiguously from zero by sorting the integer tuple of each
|
|
182
|
+
source subset lexicographically. This fixes the private helper's subset-to-ID
|
|
183
|
+
correspondence for its tests. Predicate exploration order controls guard exploration and
|
|
184
|
+
must cover predicates in the relation/domain. State numbering is outside the
|
|
185
|
+
language-level canonicality guarantee.
|
|
186
|
+
|
|
187
|
+
## Task 1: Implement the private cover-aware NFA construction
|
|
188
|
+
|
|
189
|
+
**Files:**
|
|
190
|
+
|
|
191
|
+
- Modify: `src/morphata/symbolic/transforms.py`, beside the existing reversal
|
|
192
|
+
and finite-word transforms (`reverse_language` currently at lines 582-675).
|
|
193
|
+
- Create: `tests/symbolic/test_rfsa.py` for RFSA fixtures and correctness tests.
|
|
194
|
+
|
|
195
|
+
**Interfaces:**
|
|
196
|
+
|
|
197
|
+
- Consumes: an already normalized, finite, non-alternating `SymbolicAutomaton`
|
|
198
|
+
whose constant true successors have been materialized.
|
|
199
|
+
- Produces the private function:
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
def _residualize_nfa[AP: Hashable, Symbol](
|
|
203
|
+
aut: SymbolicAutomaton[AP, Symbol],
|
|
204
|
+
*,
|
|
205
|
+
predicate_exploration_order: Sequence[AP] | None = None,
|
|
206
|
+
abort_on_max_size: int | None = None,
|
|
207
|
+
) -> SymbolicAutomaton[AP, Symbol]: ...
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**Parallelization:** Sequential; Task 2 depends on this helper and modifies the
|
|
211
|
+
same implementation and test files.
|
|
212
|
+
|
|
213
|
+
- [x] **Step 1: Write the failing basis and boundary tests.** Add:
|
|
214
|
+
- `test_residualize_matches_exhaustive_two_state_subsets`: enumerate all
|
|
215
|
+
256 two-state/two-symbol transition relations and all four initial subsets
|
|
216
|
+
in a test-only reference; compare the non-coverable reachable subsets with
|
|
217
|
+
those represented by the helper's final initial/final/transition structure.
|
|
218
|
+
Use the specified sorted subset-to-index correspondence and compare every
|
|
219
|
+
row, initial set, and final set. Fix source acceptance to `{1}`. Bound
|
|
220
|
+
manager lifetime per transition relation; exercise both symbols.
|
|
221
|
+
- `test_residualize_removes_late_cover`: initial `{0,1}`, transitions
|
|
222
|
+
`0 --a--> 0`, `1 --b--> 1`, all other transitions empty, both accepting.
|
|
223
|
+
Expect two accepting states, both initial, with separate `a`/`b` loops;
|
|
224
|
+
the initially admitted `{0,1}` must disappear completely.
|
|
225
|
+
- `test_residualize_skips_covered_candidate`: initial `{0}`,
|
|
226
|
+
`0 --a--> {1}`, `0 --b--> {0,1}`, `1 --a--> {1}`, `1 --b--> {0}`.
|
|
227
|
+
Set source acceptance to `{1}` and `predicate_exploration_order=('b','a')`,
|
|
228
|
+
which visits the singleton `a` valuation first under False-before-True
|
|
229
|
+
ordering. With cap 2, expect success, the two singleton basis states, and
|
|
230
|
+
both successor targets on the `b` edge from the initial state.
|
|
231
|
+
- `test_residualize_empty_subset`: zero-state false-initial input produces
|
|
232
|
+
zero states and empty acceptance.
|
|
233
|
+
- `test_residualize_admission_bound`: the late-cover example admits three
|
|
234
|
+
subsets cumulatively; cap 2 raises with `.explored == 3`, cap 3 succeeds
|
|
235
|
+
with two output states. The covered-candidate fixture above succeeds at
|
|
236
|
+
cap 2, including repeated visits to its covered union.
|
|
237
|
+
- `test_residualize_preserves_finite_domain`: include an unused letter `c`
|
|
238
|
+
in `finite_alphabet({'a','b','c'})`; partial transitions on it must reject,
|
|
239
|
+
and guards must respect the full alphabet domain.
|
|
240
|
+
|
|
241
|
+
- [x] **Step 2: Run `pytest tests/symbolic/test_rfsa.py -q`.** Expect failure
|
|
242
|
+
because `_residualize_nfa` is absent. Keep the exhaustive reference isolated
|
|
243
|
+
to tests; it may enumerate subsets and alphabet symbols.
|
|
244
|
+
|
|
245
|
+
- [x] **Step 3: Implement `_residualize_nfa` with the online basis and proof
|
|
246
|
+
above.** Keep set-cover checking local to this helper. Emit all final-basis
|
|
247
|
+
subsets contained in each successor image; an arbitrary irredundant cover
|
|
248
|
+
would omit saturated transitions. Build fresh output caches and acceptance
|
|
249
|
+
containing output state IDs only. Do not adapt `explore_and_transform` or
|
|
250
|
+
add calls to `determinize`, `minimize_dfa`, `to_min_dfa`, language equivalence,
|
|
251
|
+
hashing, or alphabet enumeration.
|
|
252
|
+
|
|
253
|
+
- [x] **Step 4: Run the new tests and nearby transforms tests.**
|
|
254
|
+
`pytest tests/symbolic/test_rfsa.py tests/symbolic/test_transforms.py -q`
|
|
255
|
+
must pass. Verify the set-cover argument term by term using the installed
|
|
256
|
+
`verifying-mathematical-notation` skill; explicitly distinguish source
|
|
257
|
+
subsets, output IDs, and residual languages.
|
|
258
|
+
|
|
259
|
+
- [x] **Step 5: Run focused static checks.**
|
|
260
|
+
`ruff check src/morphata/symbolic/transforms.py tests/symbolic/test_rfsa.py`
|
|
261
|
+
and `ruff format --check src/morphata/symbolic/transforms.py tests/symbolic/test_rfsa.py`
|
|
262
|
+
must pass. Run `zuban check` and `basedpyright`; report any pre-existing
|
|
263
|
+
failures separately without changing unrelated files.
|
|
264
|
+
|
|
265
|
+
- [x] **Step 6: Review the local Task 1 diff.**
|
|
266
|
+
`jj diff -- src/morphata/symbolic/transforms.py tests/symbolic/test_rfsa.py`.
|
|
267
|
+
Leave it uncommitted; inspect all changes before starting Task 2.
|
|
268
|
+
|
|
269
|
+
## Task 2: Expose and verify the canonical two-pass operation
|
|
270
|
+
|
|
271
|
+
**Files:**
|
|
272
|
+
|
|
273
|
+
- Modify: `src/morphata/symbolic/transforms.py` for the free function.
|
|
274
|
+
- Modify: `src/morphata/symbolic/automaton.py:746-787` vicinity for the method.
|
|
275
|
+
- Modify: `src/morphata/symbolic/__init__.py` for the explicit export.
|
|
276
|
+
- Modify: `tests/symbolic/test_rfsa.py` for the public contract and oracle.
|
|
277
|
+
- Modify: `docs/concepts/symbolic.rst`, finite-word transformation pipeline.
|
|
278
|
+
- Modify: `docs/api/morphata.symbolic.rst`, free-function listing.
|
|
279
|
+
- Modify: `docs/refs.bib`, add only verified `denis2002residual` metadata.
|
|
280
|
+
|
|
281
|
+
**Interfaces:**
|
|
282
|
+
|
|
283
|
+
- Consumes: Task 1's `_residualize_nfa` and existing `reverse_language`.
|
|
284
|
+
- Produces the exported free function and corresponding method:
|
|
285
|
+
|
|
286
|
+
```python
|
|
287
|
+
def to_min_rfsa[AP: Hashable, Symbol](
|
|
288
|
+
aut: SymbolicAutomaton[AP, Symbol],
|
|
289
|
+
*,
|
|
290
|
+
initial: Function[AP, int] | None = None,
|
|
291
|
+
predicate_exploration_order: Sequence[AP] | None = None,
|
|
292
|
+
abort_on_max_size: int | None = None,
|
|
293
|
+
) -> SymbolicAutomaton[AP, Symbol]: ...
|
|
294
|
+
|
|
295
|
+
def to_min_rfsa(
|
|
296
|
+
self,
|
|
297
|
+
*,
|
|
298
|
+
initial: Function[AP, int] | None = None,
|
|
299
|
+
predicate_exploration_order: Sequence[AP] | None = None,
|
|
300
|
+
abort_on_max_size: int | None = None,
|
|
301
|
+
) -> SymbolicAutomaton[AP, Symbol]: ...
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
**Parallelization:** Sequential after Task 1. No independent agent tasks.
|
|
305
|
+
|
|
306
|
+
- [x] **Step 1: Add failing public-contract and canonicality tests.**
|
|
307
|
+
- `test_to_min_rfsa_finite_language`: use `finite_alphabet({'a','b','c'})`
|
|
308
|
+
and the language `{aa,ab,ba,cb}`. Expect four RFSA states and six minimal
|
|
309
|
+
DFA states in the test reference. Compare prime residual identities and all
|
|
310
|
+
saturated guards, not just the four-state count.
|
|
311
|
+
- `test_to_min_rfsa_matches_small_dfa_reference`: implement a test-only oracle
|
|
312
|
+
from a minimal DFA: enumerate its rooted residuals, compute strict language
|
|
313
|
+
containment by emptiness of differences, discard residuals covered by the
|
|
314
|
+
union of smaller residuals, and construct all initial/final/edge conditions
|
|
315
|
+
from Definition 4.5. Label languages with exact `CanonicalDfaHasher.canonical_bytes`
|
|
316
|
+
on their minimized rooted DFAs, using the same predicates/domain. Compare
|
|
317
|
+
every output state's label, initial and final label sets, and guard BDD for
|
|
318
|
+
each pair of labels. A hash digest alone is insufficient. Use bounded
|
|
319
|
+
generated NFAs with 1-3 states, two letters, 30 examples, and fresh managers.
|
|
320
|
+
- `test_to_min_rfsa_compressed_suffix_family`: the `n`-th symbol from the end
|
|
321
|
+
is `1`, for fixed `n=4,6` over `finite_alphabet({'0','1'})`. Build the NFA
|
|
322
|
+
with initial state 0 looping on both letters and additionally entering 1
|
|
323
|
+
on `1`; states 1 through `n-1` advance on either letter; only `n` accepts.
|
|
324
|
+
Expect RFSA sizes 5/7 and reference DFA sizes 16/64. Compare acceptance
|
|
325
|
+
with `len(word) >= n and word[-n] == '1'` on all words through length `n+1`.
|
|
326
|
+
- `test_to_min_rfsa_does_not_determinize`: after creating fixtures/oracle,
|
|
327
|
+
patch the free `determinize` and `minimize_dfa` and the corresponding
|
|
328
|
+
`SymbolicAutomaton.determinize`/`to_min_dfa` entrypoints to raise. The public
|
|
329
|
+
construction must still return the expected compressed RFSA. Check word
|
|
330
|
+
acceptance while patches are installed; run any DFA oracle outside them.
|
|
331
|
+
- `test_to_min_rfsa_does_not_enumerate_alphabet`: replace `alphabet.symbols`
|
|
332
|
+
with a raising callable and run the public operation on a powerset alphabet
|
|
333
|
+
with irrelevant predicates. Check same manager/alphabet identity and sample
|
|
334
|
+
word acceptance. Avoid a timing threshold.
|
|
335
|
+
- `test_to_min_rfsa_constants_and_partial_moves`: parameterize empty,
|
|
336
|
+
epsilon-only, universal, and nonempty partial NFAs. Expected state counts
|
|
337
|
+
are 0, 1, 1, and the reference count respectively. Empty/epsilon-only outputs
|
|
338
|
+
have no fabricated dead state; universal output has an accepting self-loop.
|
|
339
|
+
- `test_to_min_rfsa_selected_initial`: choose a disconnected component with
|
|
340
|
+
`initial=manager.q_var(q)`; also test false, true, a disjunction, and a
|
|
341
|
+
conjunction through existing dealternation. Compare against the selected
|
|
342
|
+
language, never the stored original initial condition.
|
|
343
|
+
- `test_to_min_rfsa_normalizes_alternation`: a small conjunction/disjunction
|
|
344
|
+
AFA agrees with the full-DFA oracle and returns disjunctive NFA successors.
|
|
345
|
+
- `test_to_min_rfsa_rejects_omega`: Buchi input raises `NotImplementedError`.
|
|
346
|
+
- `test_to_min_rfsa_bound_and_method`: free function and method agree, a
|
|
347
|
+
sufficient cap succeeds, and insufficient cap raises `MacroStateLimitError`.
|
|
348
|
+
Cover final canonicality under input state renaming and a second RFSA pass
|
|
349
|
+
through the residual-labelled oracle comparison, without requiring equal IDs.
|
|
350
|
+
|
|
351
|
+
- [x] **Step 2: Run the public tests and observe the missing interface.**
|
|
352
|
+
`pytest tests/symbolic/test_rfsa.py -q -k to_min_rfsa` must fail because the
|
|
353
|
+
public free function/method is absent.
|
|
354
|
+
|
|
355
|
+
- [x] **Step 3: Add the public free function, method, and export.**
|
|
356
|
+
Check finite acceptance, then call `reverse_language` with the selected
|
|
357
|
+
initial formula, `_residualize_nfa`, `reverse_language`, `_residualize_nfa`.
|
|
358
|
+
Forward order/bound parameters at each stage. Apply `initial` only at the
|
|
359
|
+
first reversal. Do not add a DFA fallback or silently return a noncanonical
|
|
360
|
+
result when a cap is reached. Keep the method a thin wrapper like existing
|
|
361
|
+
transforms; do not force a deterministic cache flag.
|
|
362
|
+
|
|
363
|
+
- [x] **Step 4: Document the exact contract and verified citation.**
|
|
364
|
+
Use NumPy-style sections/RST math in the new docstrings. Explain prime
|
|
365
|
+
residuals, saturated edges, the online cover invariant, partial NFA output,
|
|
366
|
+
canonicality up to isomorphism, and per-stage bounds. Add a small runnable
|
|
367
|
+
finite-language example to the concepts page and list the free function in
|
|
368
|
+
the API page. Obtain the entry with `zotero_ls citation export denis2002residual`
|
|
369
|
+
and append its verified metadata to `docs/refs.bib`; do not alter other
|
|
370
|
+
entries. Use the RST citation role with key `denis2002residual`. State that the full DFA is
|
|
371
|
+
avoided but basis/intermediate/output size can still be exponential. Do not
|
|
372
|
+
promise canonical serialization or unrestricted minimal-NFA size.
|
|
373
|
+
|
|
374
|
+
- [x] **Step 5: Run focused validation.**
|
|
375
|
+
`pytest tests/symbolic/test_rfsa.py tests/symbolic/test_transforms.py tests/symbolic/test_language_equivalence.py tests/symbolic/test_compose.py -q`
|
|
376
|
+
must pass, including oracle adjacency/saturation checks and both sentinels.
|
|
377
|
+
|
|
378
|
+
- [x] **Step 6: Run final validation once after the change is complete.**
|
|
379
|
+
|
|
380
|
+
```bash
|
|
381
|
+
ruff format --check src/morphata/symbolic/transforms.py src/morphata/symbolic/automaton.py src/morphata/symbolic/__init__.py tests/symbolic/test_rfsa.py
|
|
382
|
+
ruff check src/morphata/symbolic/transforms.py src/morphata/symbolic/automaton.py src/morphata/symbolic/__init__.py tests/symbolic/test_rfsa.py
|
|
383
|
+
zuban check
|
|
384
|
+
basedpyright
|
|
385
|
+
pytest -q
|
|
386
|
+
sphinx-build -b html docs/ docs/_build/html
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
Expected: successful formatting, lint, type checking, complete configured
|
|
390
|
+
tests/doctests, and Sphinx build without new diagnostics. If failures arise,
|
|
391
|
+
identify whether they are introduced or pre-existing; do not broaden scope
|
|
392
|
+
to unrelated fixes. Re-run only affected checks after a repair.
|
|
393
|
+
|
|
394
|
+
- [x] **Step 7: Perform the separate whole-change review.**
|
|
395
|
+
Review the final diff against the formulas, basis proof, all Review Focus
|
|
396
|
+
items, no-determinization/alphabet-enumeration constraints, and existing
|
|
397
|
+
local changes. Record actual validation results. Leave implementation and
|
|
398
|
+
documentation local and uncommitted for author review.
|
|
399
|
+
|
|
400
|
+
## Planning validation and handoff
|
|
401
|
+
|
|
402
|
+
Only this plan was written during planning. A separate finite-set calculation
|
|
403
|
+
matched the online basis to exhaustive reachable-subset enumeration in all
|
|
404
|
+
1,024 two-state/two-symbol transition/initial configurations. The same
|
|
405
|
+
calculation produced 5/7 states for the fixed suffix families with `n=4,6`.
|
|
406
|
+
This checks the proposed basis schedule on bounded examples; it is not a
|
|
407
|
+
production implementation or a substitute for the proof and repository tests.
|
|
408
|
+
|
|
409
|
+
Recommend direct sequential implementation after review: both tasks modify
|
|
410
|
+
the same algorithm/test files and Task 2 relies on Task 1. Parallel agents
|
|
411
|
+
offer no independent work here and are not authorized. Confirm or correct
|
|
412
|
+
the proposed API, bound semantics, and scope before implementation.
|
|
413
|
+
|
|
414
|
+
## Implementation progress
|
|
415
|
+
|
|
416
|
+
Task 1: 310 tests passed; Ruff and Zuban passed. BasedPyright reported only
|
|
417
|
+
the expected temporary unused-private-helper warning before Task 2 integration.
|
|
418
|
+
|
|
419
|
+
Task 2: 380 focused tests passed. Ruff formatting/lint, Zuban, BasedPyright,
|
|
420
|
+
and Sphinx passed with no diagnostics. The separate whole-change review checked
|
|
421
|
+
source-subset/output-ID separation, preservation of covers under replacement,
|
|
422
|
+
transition closure under union, saturation, and the absence of production
|
|
423
|
+
determinization or alphabet enumeration.
|
|
424
|
+
|
|
425
|
+
Property testing found that an output basis can exceed the source state count;
|
|
426
|
+
output BDD variables are now declared before emission, with a fixed three-state
|
|
427
|
+
input/four-state-output regression. Exploratory tight caps in the compression
|
|
428
|
+
fixtures were removed: the plan specifies cumulative admissions, whose count
|
|
429
|
+
depends on exploration order, separately from final state count. The dedicated
|
|
430
|
+
late-cover admission-bound tests retain exact expected counts.
|
|
431
|
+
|
|
432
|
+
Raw `pytest -q` encountered four collection errors in stale generated
|
|
433
|
+
`docs/_build/html/_downloads` examples importing removed `morphata.encoders`
|
|
434
|
+
modules. Full source/test/documentation-doctest validation is being run with
|
|
435
|
+
`pytest -q --ignore=docs/_build`; no test configuration or generated files were
|
|
436
|
+
changed to bypass those stale examples.
|
|
437
|
+
|
|
438
|
+
### Mathematical review
|
|
439
|
+
|
|
440
|
+
- Source of truth: the construction contract in this plan and Definition 4.5,
|
|
441
|
+
Lemma 5.1, and Theorem 5.2 of the verified local paper.
|
|
442
|
+
- Symbol ledger: source subsets are `frozenset[int]`; the basis is `basis`;
|
|
443
|
+
source transition images come from OR-folded source rows; guards are BDDs
|
|
444
|
+
restricted to `alphabet.domain`; output IDs are assigned only after closure.
|
|
445
|
+
- Verified correspondences: strict set covers preserve the generated union
|
|
446
|
+
family; source images distribute over union; accepting subsets intersect
|
|
447
|
+
the source accepting set; initial/target subsets use non-strict containment.
|
|
448
|
+
- Mismatches or ambiguities: none remain in the reviewed implementation. The
|
|
449
|
+
online scheduling adaptation and its exponential limits are documented.
|
|
450
|
+
- Recommended fix: none outstanding. All changes remain local and uncommitted.
|
|
451
|
+
|
|
452
|
+
Final validation: `pytest -q --ignore=docs/_build` completed with 770 passed,
|
|
453
|
+
11 xfailed, and 16 dependency warnings in 418.58 seconds. Sphinx rebuilt
|
|
454
|
+
successfully; Ruff formatting/lint, Zuban, and BasedPyright all passed. The
|
|
455
|
+
raw-command generated-document collection issue is recorded above. Both tasks
|
|
456
|
+
and the whole-change review are complete; no commits or remote actions occurred.
|
|
@@ -29,6 +29,13 @@ The narrow exception: a **surgical, already-decided edit** the author has spelle
|
|
|
29
29
|
Do exactly that and nothing more.
|
|
30
30
|
When unsure whether a change qualifies, treat it as out of scope and ask.
|
|
31
31
|
|
|
32
|
+
## Project Boundaries
|
|
33
|
+
|
|
34
|
+
Morphata owns automata representations, algebraic operations, semiring operators,
|
|
35
|
+
and execution. Automata Encoders owns symbolic input features, trainable
|
|
36
|
+
encoders, training/evaluation tools, and comparison baselines. Morphata must
|
|
37
|
+
not depend on Automata Encoders.
|
|
38
|
+
|
|
32
39
|
## Development Environment
|
|
33
40
|
|
|
34
41
|
The development shell already has the project virtual environment activated,
|
|
@@ -38,3 +45,8 @@ etc. are on `PATH` directly.
|
|
|
38
45
|
Just invoke the tool.
|
|
39
46
|
If a tool is missing, stop and tell the user rather than trying to bootstrap
|
|
40
47
|
or reinstall the environment.
|
|
48
|
+
|
|
49
|
+
## Documentation
|
|
50
|
+
|
|
51
|
+
Use NumPy/numpydoc-style sections for Python docstrings. Use RST math markup
|
|
52
|
+
inside those sections and in the main docstring body.
|
|
@@ -1,5 +1,92 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.3.0 — 2026-10-21
|
|
4
|
+
|
|
5
|
+
This is *another* hefty release,
|
|
6
|
+
and has been the culmination of `morphata` being used to dogfood multiple other
|
|
7
|
+
projects --- I will not reveal the level of success of those other projects though ---
|
|
8
|
+
which has allowed me to harden `morphata` and also add a bunch of relevant features.
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `BooleanAlphabet.as_guard` turns a concrete symbol into its BDD guard.
|
|
13
|
+
|
|
14
|
+
- `to_min_rfsa`, also available as `SymbolicAutomaton.to_min_rfsa`,
|
|
15
|
+
constructs canonical residual finite-state automata using cover-aware subset
|
|
16
|
+
exploration and language reversal, without building the full DFA.
|
|
17
|
+
Follows the same API as the other transformers.
|
|
18
|
+
|
|
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).
|
|
22
|
+
|
|
23
|
+
- `JaxBatchedMatrixOperator` runs batches of integer-encoded words over dense matrix
|
|
24
|
+
operators using JAX's `lax.scan`.
|
|
25
|
+
You can run every word on every operator, or pair each word with one operator.
|
|
26
|
+
Padded words with different lengths are supported too.
|
|
27
|
+
|
|
28
|
+
- A JAX min-max Boolean algebra with a choice of native or smooth surrogate gradients.
|
|
29
|
+
The forward pass still computes exact min and max;
|
|
30
|
+
the surrogate gives the backward pass a smoother signal.
|
|
31
|
+
|
|
32
|
+
- Automaton sampling helpers in `morphata.utils.sampling`,
|
|
33
|
+
covering random DFAs and AFAs, structured language families, temporal specifications,
|
|
34
|
+
and local mutations.
|
|
35
|
+
Useful when you want some variety in the automata you are working with.
|
|
36
|
+
|
|
37
|
+
- `factor_initial=True` on `remove_alternation`, `determinize`,
|
|
38
|
+
and `to_min_dfa` transforms the components of the initial formula separately,
|
|
39
|
+
then combines them smallest-first.
|
|
40
|
+
Another option for constructions where exploring everything together gets large.
|
|
41
|
+
|
|
42
|
+
- `EditDistanceAutomaton` converts a given symbolic automaton to a min-plus weighted
|
|
43
|
+
automaton that computes the edit distance between a word and the given automaton.
|
|
44
|
+
Additionally, we have lifted this word-language edit distance to a quasimetric
|
|
45
|
+
that computes a directed Hausdorff distance to the `EditDistanceAutomaton` from some
|
|
46
|
+
other automaton.
|
|
47
|
+
|
|
48
|
+
- `unsigned_levenshtein_distance` computes the worst-case number of edits needed to
|
|
49
|
+
repair a source word into the target language.
|
|
50
|
+
It returns zero when the source language is contained in the target.
|
|
51
|
+
`signed_levenshtein_distance` reports a containment margin
|
|
52
|
+
when the source language is contained in the target,
|
|
53
|
+
and the same repair cost otherwise.
|
|
54
|
+
|
|
55
|
+
- `language_separation_distance` computes the minimum edit distance between any pair of
|
|
56
|
+
words from two languages.
|
|
57
|
+
It returns infinity if either is empty.
|
|
58
|
+
|
|
59
|
+
### Changed
|
|
60
|
+
|
|
61
|
+
- Persistent automata now retain the original predicate objects.
|
|
62
|
+
`morphata.storage.configure_converter` lets you use your own cattrs converter
|
|
63
|
+
and predicate serialization hooks, with consistent ordering across backends.
|
|
64
|
+
The JSON helpers remain for string predicates.
|
|
65
|
+
|
|
66
|
+
- 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.
|
|
69
|
+
|
|
70
|
+
### Fixed
|
|
71
|
+
|
|
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.
|
|
74
|
+
|
|
75
|
+
- Symbolic transformations reuse alphabet domains within each construction stage.
|
|
76
|
+
Dealternation, determinization,
|
|
77
|
+
and RFSA subset exploration cache projected domains by the combined relation's
|
|
78
|
+
predicate support and apply them before partitioning.
|
|
79
|
+
|
|
80
|
+
- Canonical finite-alphabet traversal now restricts transitions before partitioning,
|
|
81
|
+
so impossible predicate valuations do not affect BFS state numbering.
|
|
82
|
+
|
|
83
|
+
- `compose_automata` now removes pruned states from the finite accepting set too.
|
|
84
|
+
Previously, accepting states could survive in the bookkeeping
|
|
85
|
+
after their transition rows had been removed.
|
|
86
|
+
|
|
87
|
+
- `has_conjunctive_state_terms` previously did not correctly handle successor states
|
|
88
|
+
that were just Boolean constants, i.e., unmaterialized accepting/rejecting sinks.
|
|
89
|
+
|
|
3
90
|
## 2.2.3 — 2026-09-22
|
|
4
91
|
|
|
5
92
|
This is a hefty release, but all really cool things that I am super proud of
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: morphata
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
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
|
|
@@ -11,7 +11,7 @@ License-File: LICENSE
|
|
|
11
11
|
Classifier: Intended Audience :: Science/Research
|
|
12
12
|
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
13
13
|
Requires-Python: >=3.12
|
|
14
|
-
Requires-Dist: algebraic-arrays>=1.3.
|
|
14
|
+
Requires-Dist: algebraic-arrays>=1.3.10
|
|
15
15
|
Requires-Dist: attrs>=25.4.0
|
|
16
16
|
Requires-Dist: bitarray>=3.10.0
|
|
17
17
|
Requires-Dist: cattrs>=26.1.0
|
|
@@ -24,7 +24,8 @@ Requires-Dist: typing-extensions>=4.14
|
|
|
24
24
|
Provides-Extra: dot
|
|
25
25
|
Requires-Dist: pydot>=4.0.1; extra == 'dot'
|
|
26
26
|
Provides-Extra: jax
|
|
27
|
-
Requires-Dist:
|
|
27
|
+
Requires-Dist: flax; extra == 'jax'
|
|
28
|
+
Requires-Dist: jax<0.11.2,>=0.8; extra == 'jax'
|
|
28
29
|
Provides-Extra: torch
|
|
29
30
|
Requires-Dist: torch>=2.0; extra == 'torch'
|
|
30
31
|
Description-Content-Type: text/x-rst
|
|
@@ -20,6 +20,12 @@ To get pytest-benchmark's tabular wall-time output:
|
|
|
20
20
|
JAX-only scale cases are opt-in via
|
|
21
21
|
`MORPHATA_RUN_SCALE_BENCHMARKS=1`; run each scale case in a fresh process under
|
|
22
22
|
a hard memory limit.
|
|
23
|
+
- `test_transforms_perf.py` -- alternation removal and determinization over
|
|
24
|
+
single-root conjoined-eventuality and until-chain AFA families, plus composed
|
|
25
|
+
eventuality AFAs whose initial formulas have multiple state variables. The
|
|
26
|
+
composed cases compare the monolithic and opt-in factored-initial
|
|
27
|
+
constructions. Results are grouped by transform, construction, and formula
|
|
28
|
+
size. Regular cases stop at size 10.
|
|
23
29
|
- `test_sere_nlm_perf.py` -- NLM-intersection state-space blowup.
|
|
24
30
|
Cases: `NLMInter(a0..a_{n-1})` for n in 2..6, `NLMInter(a_i;b_i)`
|
|
25
31
|
for n in 2..4, plus length-matching `Inter` baselines.
|