morphata 2.0.0__tar.gz → 2.0.2__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.0.2/.claude/settings.local.json +29 -0
- {morphata-2.0.0 → morphata-2.0.2}/.envrc +5 -5
- morphata-2.0.2/.gitattributes +5 -0
- {morphata-2.0.0 → morphata-2.0.2}/.gitignore +3 -0
- {morphata-2.0.0 → morphata-2.0.2}/AGENTS.md +39 -0
- {morphata-2.0.0 → morphata-2.0.2}/CHANGELOG.md +113 -1
- {morphata-2.0.0 → morphata-2.0.2}/CONTRIBUTING.md +16 -10
- {morphata-2.0.0 → morphata-2.0.2}/PKG-INFO +2 -2
- morphata-2.0.2/bench/README.md +28 -0
- morphata-2.0.2/bench/conftest.py +53 -0
- morphata-2.0.2/bench/test_sere_nlm_perf.py +84 -0
- morphata-2.0.2/ci/make-release +131 -0
- {morphata-2.0.0 → morphata-2.0.2}/docs/api/modules.rst +2 -0
- morphata-2.0.2/docs/api/morphata.logic.psl.rst +15 -0
- morphata-2.0.2/docs/api/morphata.logic.sere.rst +9 -0
- {morphata-2.0.0 → morphata-2.0.2}/docs/conf.py +1 -1
- {morphata-2.0.0 → morphata-2.0.2}/docs/index.rst +9 -1
- morphata-2.0.2/justfile +70 -0
- morphata-2.0.2/pixi.lock +5616 -0
- morphata-2.0.2/pixi.toml +116 -0
- {morphata-2.0.0 → morphata-2.0.2}/pyproject.toml +4 -2
- morphata-2.0.2/pytest.toml +32 -0
- morphata-2.0.2/src/morphata/logic/_common.py +136 -0
- morphata-2.0.2/src/morphata/logic/ltl.py +499 -0
- morphata-2.0.2/src/morphata/logic/psl.py +372 -0
- morphata-2.0.2/src/morphata/logic/sere.py +516 -0
- {morphata-2.0.0 → morphata-2.0.2}/src/morphata/operators/matrix.py +3 -1
- {morphata-2.0.0 → morphata-2.0.2}/src/morphata/operators/polynomial.py +3 -1
- {morphata-2.0.0 → morphata-2.0.2}/src/morphata/symbolic/automaton.py +1 -1
- {morphata-2.0.0 → morphata-2.0.2}/src/morphata/symbolic/bdd.py +225 -14
- {morphata-2.0.0 → morphata-2.0.2}/src/morphata/symbolic/transforms.py +77 -68
- {morphata-2.0.0/tests/symbolic → morphata-2.0.2/tests}/conftest.py +19 -2
- morphata-2.0.2/tests/logic/__init__.py +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/tests/logic/test_ltl.py +122 -69
- morphata-2.0.2/tests/logic/test_psl.py +179 -0
- morphata-2.0.2/tests/logic/test_sere.py +489 -0
- morphata-2.0.2/tests/logic/test_sere_properties.py +132 -0
- {morphata-2.0.0 → morphata-2.0.2}/tests/operators/test_matrix_from_ltl.py +7 -6
- {morphata-2.0.0 → morphata-2.0.2}/tests/operators/test_polynomial_from_ir.py +5 -4
- morphata-2.0.2/tests/symbolic/test_bdd_helpers.py +277 -0
- {morphata-2.0.0 → morphata-2.0.2}/tests/symbolic/test_from_hoaf.py +2 -2
- {morphata-2.0.0 → morphata-2.0.2}/tests/symbolic/test_ltlf2dfa_equivalence.py +1 -1
- {morphata-2.0.0 → morphata-2.0.2}/tests/symbolic/test_step.py +8 -7
- {morphata-2.0.0 → morphata-2.0.2}/tests/symbolic/test_symbolic_automaton.py +26 -1
- {morphata-2.0.0 → morphata-2.0.2}/tests/symbolic/test_symbolic_from_ir.py +7 -6
- {morphata-2.0.0 → morphata-2.0.2}/tests/symbolic/test_transforms.py +47 -0
- {morphata-2.0.0 → morphata-2.0.2}/tests/symbolic/test_transforms_ir.py +5 -4
- {morphata-2.0.0 → morphata-2.0.2}/tests/symbolic/test_transforms_properties.py +45 -21
- {morphata-2.0.0 → morphata-2.0.2}/uv.lock +140 -91
- morphata-2.0.0/.claude/settings.local.json +0 -17
- morphata-2.0.0/.gitattributes +0 -3
- morphata-2.0.0/ci/make-release +0 -99
- morphata-2.0.0/docs/changelog.md +0 -4
- morphata-2.0.0/justfile +0 -54
- morphata-2.0.0/pytest.toml +0 -14
- morphata-2.0.0/src/morphata/logic/ltl.py +0 -448
- morphata-2.0.0/tests/symbolic/test_bdd_helpers.py +0 -143
- {morphata-2.0.0 → morphata-2.0.2}/.basedpyright/baseline.json +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/.dockerignore +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/.editorconfig +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/.envrc.recommended +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/.fdignore +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/.mailmap +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/.python-version +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/.rstcheck.cfg +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/CLAUDE.md +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/LICENSE +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/README.md +0 -0
- {morphata-2.0.0/tests/logic → morphata-2.0.2/bench}/__init__.py +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/cliff.toml +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/docs/_static/navigation.html +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/docs/api/morphata.acceptance.rst +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/docs/api/morphata.alphabet.rst +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/docs/api/morphata.automaton.rst +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/docs/api/morphata.builder.rst +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/docs/api/morphata.hoa.rst +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/docs/api/morphata.logic.ltl.rst +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/docs/api/morphata.logic.strel.rst +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/docs/api/morphata.operators.matrix.rst +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/docs/api/morphata.operators.polynomial.rst +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/docs/api/morphata.operators.rst +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/docs/api/morphata.symbolic.rst +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/docs/concepts/acceptance.rst +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/docs/concepts/hoa.rst +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/docs/concepts/index.rst +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/docs/concepts/operators.rst +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/docs/concepts/spec.rst +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/docs/concepts/weights.rst +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/docs/quick-start.rst +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/docs/symbolic-transforms.rst +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/mypy.ini +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/project.utf-8.add +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/project.utf-8.add.spl +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/pyrefly.toml +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/pyrightconfig.json +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/ruff.toml +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/src/morphata/__init__.py +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/src/morphata/acceptance.py +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/src/morphata/alphabet.py +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/src/morphata/automaton.py +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/src/morphata/builder.py +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/src/morphata/hoa/__init__.py +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/src/morphata/hoa/__main__.py +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/src/morphata/hoa/acc_expr.py +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/src/morphata/hoa/exporter.py +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/src/morphata/hoa/hoa.lark +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/src/morphata/hoa/parser.py +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/src/morphata/logic/__init__.py +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/src/morphata/logic/strel.py +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/src/morphata/operators/__init__.py +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/src/morphata/operators/_backend.py +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/src/morphata/py.typed +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/src/morphata/symbolic/__init__.py +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/src/morphata/symbolic/equivalence.py +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/tests/hoa/aut1.hoa +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/tests/hoa/aut11.hoa +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/tests/hoa/aut2.hoa +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/tests/hoa/aut3.2.hoa +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/tests/hoa/aut3.hoa +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/tests/hoa/aut4.hoa +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/tests/hoa/aut5.hoa +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/tests/hoa/aut6.hoa +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/tests/hoa/aut7.hoa +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/tests/hoa/aut8.hoa +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/tests/hoa/finite_aut.hoa +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/tests/hoa/gen_buchi_3.hoa +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/tests/hoa/gen_cobuchi_2.hoa +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/tests/hoa/rabin_2pair.hoa +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/tests/hoa/streett_2pair.hoa +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/tests/hoa/test_exporter_basic.py +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/tests/hoa/test_exporter_roundtrip.py +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/tests/hoa/test_parser.py +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/tests/logic/test_strel.py +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/tests/operators/test_matrix_from_ir.py +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/tests/test_alphabet.py +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/tests/test_automaton.py +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/tests/test_builder.py +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/tests/test_from_parsed.py +0 -0
- {morphata-2.0.0 → morphata-2.0.2}/ty.toml +0 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"Bash(mypy *)",
|
|
5
|
+
"Bash(python -m mypy *)",
|
|
6
|
+
"Bash(python3 -m mypy *)",
|
|
7
|
+
"Bash(pytest *)",
|
|
8
|
+
"Bash(python -m pytest *)",
|
|
9
|
+
"Bash(python3 -m pytest *)",
|
|
10
|
+
"Bash(just lint *)",
|
|
11
|
+
"Bash(just test *)",
|
|
12
|
+
"Bash(rg *)",
|
|
13
|
+
"Bash(uv run pytest *)",
|
|
14
|
+
"Bash(uv run mypy *)",
|
|
15
|
+
"Bash(.venv/bin/python -m pytest *)",
|
|
16
|
+
"Skill(superpowers:writing-plans)",
|
|
17
|
+
"Skill(superpowers:subagent-driven-development)",
|
|
18
|
+
"Bash(grep -v \"AssertionError\\\\|Traceback\\\\|File \\\\|^\\\\s*$\")",
|
|
19
|
+
"Skill(superpowers:brainstorming)",
|
|
20
|
+
"Bash(jj log *)",
|
|
21
|
+
"Bash(jj st *)",
|
|
22
|
+
"Bash(jj diff *)",
|
|
23
|
+
"Skill(superpowers:requesting-code-review)",
|
|
24
|
+
"Bash(timeout 15 python -c ' *)",
|
|
25
|
+
"Read(//tmp/**)",
|
|
26
|
+
"Bash(echo \"exit: $?\")"
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -12,15 +12,15 @@ if [[ -z "${DEVJAIL:-}" ]] && nvidia_smi="$(type -p "nvidia-smi")" && [[ -n "$nv
|
|
|
12
12
|
if [[ -n "${cuda_major_version:-}" ]]; then
|
|
13
13
|
info "Detected CUDA version: $cuda_major_version"
|
|
14
14
|
export CUDA_VERSION="$cuda_major_version"
|
|
15
|
+
PIXI_ENV="cu$CUDA_VERSION"
|
|
15
16
|
fi
|
|
16
17
|
else
|
|
17
18
|
info "Did not detect nvidia-smi, using non-GPU environment"
|
|
19
|
+
PIXI_ENV="default"
|
|
18
20
|
fi
|
|
21
|
+
export PIXI_ENV
|
|
19
22
|
|
|
20
23
|
dotenv_if_exists
|
|
21
24
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
fi
|
|
25
|
-
source ./.venv/bin/activate
|
|
26
|
-
watch_file pyproject.toml
|
|
25
|
+
watch_file pixi.lock
|
|
26
|
+
eval "$(pixi shell-hook -e "$PIXI_ENV")"
|
|
@@ -4,6 +4,14 @@
|
|
|
4
4
|
It packages structural automaton representations together with semiring-valued
|
|
5
5
|
*operators* that turn an automaton into a computable interpretation.
|
|
6
6
|
|
|
7
|
+
## Development Environment
|
|
8
|
+
|
|
9
|
+
The development shell already has the project virtual environment activated,
|
|
10
|
+
so `pytest`, `mypy`, `ruff`, `sphinx-build`, etc. are on `PATH` directly.
|
|
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.
|
|
14
|
+
|
|
7
15
|
## Core Architecture
|
|
8
16
|
|
|
9
17
|
### IR layer (`morphata.automaton`, `morphata.alphabet`, `morphata.acceptance`)
|
|
@@ -85,6 +93,21 @@ delta - the transition consumes the labelled graph eagerly, so STREL
|
|
|
85
93
|
automata are not symbolically liftable. Operators: Reach, Escape,
|
|
86
94
|
Somewhere, Everywhere. Boolean-only.
|
|
87
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
|
+
|
|
88
111
|
Hand-written automata are built with ``AutomatonBuilder``; the package
|
|
89
112
|
has no separate ``constructions`` namespace.
|
|
90
113
|
|
|
@@ -211,6 +234,18 @@ is backend-agnostic via `backend=`.
|
|
|
211
234
|
sets of transitions. The HOA parser ingests ``{n}`` markers from
|
|
212
235
|
``State:`` declarations; per-edge ``{n}`` markers are ignored. Convert
|
|
213
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.
|
|
244
|
+
- 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.
|
|
214
249
|
|
|
215
250
|
## Testing
|
|
216
251
|
|
|
@@ -334,6 +369,10 @@ matrix_product = algebraic.matmul(arr1, arr2)
|
|
|
334
369
|
- `src/morphata/builder.py` : ``AutomatonBuilder``.
|
|
335
370
|
- `src/morphata/acceptance.py` : expression-based acceptance conditions.
|
|
336
371
|
- `src/morphata/logic/ltl.py` : LTL/LTLf -> AFA conversion.
|
|
372
|
+
- `src/morphata/logic/sere.py` : SERE -> AFA conversion.
|
|
373
|
+
- `src/morphata/logic/psl.py` : PSL -> AFA conversion.
|
|
374
|
+
- `src/morphata/logic/_common.py` : shared BDD scratch-space setup used
|
|
375
|
+
by all three logic converters.
|
|
337
376
|
- `src/morphata/logic/strel.py` : STREL automaton (``Step`` delta).
|
|
338
377
|
- `src/morphata/hoa/` : HOA v1 parser, exporter, grammar.
|
|
339
378
|
- `src/morphata/operators/matrix.py` : ``MatrixOperator``.
|
|
@@ -1,6 +1,118 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 2.0.
|
|
3
|
+
## 2.0.2 — 2026-05-27
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- The rest of the SERE surface that needed real work, rather than a desugaring rule:
|
|
8
|
+
non-length-matching intersection
|
|
9
|
+
(`NLMInter`, the `&` operator), `Complement` (`~`), and `FirstMatch`.
|
|
10
|
+
NLM intersection is handled by a local rewrite in the expansion polynomial;
|
|
11
|
+
complement dualises the polynomial and gets a small BFS-loop special case
|
|
12
|
+
so the state space stays compact.
|
|
13
|
+
`GotoRepeat`, `EqualRepeat`,
|
|
14
|
+
and bounded `FusionRepeat` desugar through `logic_asts.spec.Expr.expand`.
|
|
15
|
+
Unbounded `FusionRepeat[:*i..]` deliberately raises `NotImplementedError` —
|
|
16
|
+
I don't have a clean expansion for it yet.
|
|
17
|
+
- Benchmarking script for SERE NLM intersection, so I can see when I
|
|
18
|
+
break the perf curve.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- `morphata.logic.psl` dropped a layer of in-tree wrappers
|
|
23
|
+
and leans on `logic_asts` types directly now
|
|
24
|
+
that the upstream package carries the extended SERE surface I needed.
|
|
25
|
+
Less code, fewer places for the two sides to disagree.
|
|
26
|
+
|
|
27
|
+
### Miscellaneous
|
|
28
|
+
|
|
29
|
+
- The README is linked from `pyproject.toml`, so it actually shows up
|
|
30
|
+
on PyPI.
|
|
31
|
+
|
|
32
|
+
## 2.0.1 — 2026-05-24
|
|
33
|
+
|
|
34
|
+
The point release that delivers the SERE
|
|
35
|
+
and PSL frontends originally promised alongside 2.0,
|
|
36
|
+
plus the perf work that made the symbolic pipeline actually finish on formulas of
|
|
37
|
+
interest.
|
|
38
|
+
|
|
39
|
+
### Added
|
|
40
|
+
|
|
41
|
+
- `morphata.logic.sere.sere_to_automaton(formula)` —
|
|
42
|
+
converts a SERE expression to an alternating finite automaton.
|
|
43
|
+
Supports `Concat`, `Fusion`, `Alt`, `Inter` (length-matching), `Repeat`
|
|
44
|
+
(bounded and unbounded),
|
|
45
|
+
and Boolean leaves (`Literal`, `Variable`, `Not`, `And`, `Or`).
|
|
46
|
+
- `morphata.logic.psl.psl_to_automaton(phi, *, finite=False)` —
|
|
47
|
+
converts a PSL expression to an alternating automaton,
|
|
48
|
+
with `Finite` acceptance for PSLf (`finite=True`) and `Büchi` acceptance
|
|
49
|
+
for full PSL (`finite=False`).
|
|
50
|
+
Supports the strong / weak / negated-strong closure operators
|
|
51
|
+
and existential / universal suffix implication.
|
|
52
|
+
Suffix implications follow Spot's overlapping semantics:
|
|
53
|
+
the last symbol matched by the SERE prefix is shared with the first position at
|
|
54
|
+
which the continuation is evaluated.
|
|
55
|
+
|
|
56
|
+
### Changed
|
|
57
|
+
|
|
58
|
+
- `morphata.logic.ltl` factored its per-call BDD scratch-space construction into a
|
|
59
|
+
shared helper `morphata.logic._common.make_logic_context`, reused by the SERE and PSL
|
|
60
|
+
converters.
|
|
61
|
+
Two LTL helpers (`_aut_symbolic_expansion`, `_empty_trace_value`) were renamed to
|
|
62
|
+
`ltl_symbolic_expansion` / `ltl_empty_trace_value` so PSL can delegate to them for
|
|
63
|
+
pure-LTL subformulas.
|
|
64
|
+
- Moved off `uv` and onto Pixi. `uv`'s handling of CUDA packages and
|
|
65
|
+
the way it churns the lockfile weren't working for me; Pixi does.
|
|
66
|
+
|
|
67
|
+
### Fixed
|
|
68
|
+
|
|
69
|
+
- New BDD constructions in `morphata.logic` take an explicit manager parameter instead
|
|
70
|
+
of silently spinning up their own.
|
|
71
|
+
- `bdd_to_boolexpr` in `morphata.symbolic.bdd` was silently folding `~x`
|
|
72
|
+
and `x` together because the CUDD complement-edge flag on the current node wasn't
|
|
73
|
+
being respected on the recursive walk.
|
|
74
|
+
Caught while rewriting `ltl_to_automaton`; fixed there.
|
|
75
|
+
|
|
76
|
+
### Performance
|
|
77
|
+
|
|
78
|
+
- `ltl_to_automaton` no longer iterates `2^|AP|` input symbols per AFA state.
|
|
79
|
+
The closure BFS keeps APs symbolic, builds an expansion polynomial with tagged atoms
|
|
80
|
+
(one tag for guard atoms, one for successor states),
|
|
81
|
+
compiles it to a per-call BDD,
|
|
82
|
+
and partitions AP-space by distinct state cofactors via the new
|
|
83
|
+
`mgr_partition_ap_by_state_residual` helper.
|
|
84
|
+
Per-AFA-state cost drops from `O(2^|AP|)` to `O(distinct successor classes)`,
|
|
85
|
+
and AP variables that don't affect a given cofactor get pruned.
|
|
86
|
+
A depth-15 sequential conjunction `F(a_0) & X(F(a_1) & X(...))`
|
|
87
|
+
that used to take ~330s and OOM at depth 20 now builds in ~0.02s.
|
|
88
|
+
The remaining wall-clock on those formulas lives in `nfa_from_afa`,
|
|
89
|
+
which is intrinsic `2^N` in macro state count.
|
|
90
|
+
- `SymbolicAutomaton.to_min_dfa()` is tractable on formulas with non-trivial `|AP|`
|
|
91
|
+
(`~5+` for monotone conjunctions, `~10+` for sequential), where it used to hang.
|
|
92
|
+
Three pieces: prime-implicant DFS (`mgr_minimal_pos_satisfying_subsets`) replaces the
|
|
93
|
+
old `2^(2^|AP|)` BDD-evaluation pass in `_nfa_macro_step` after dealternation;
|
|
94
|
+
symbolic AP-space partition helpers (`mgr_partition_ap_by_classifiers` and
|
|
95
|
+
`mgr_partition_ap_by_state_residual`) replace explicit `2^|AP|` minterm enumeration in
|
|
96
|
+
`determinize` and `nfa_from_afa`; and `_extract_dfa_target` parses the single
|
|
97
|
+
state-variable name directly instead of linear-scanning every state in the manager,
|
|
98
|
+
removing an `O(num_states^2)` per-refinement-round cost inside `minimize()`.
|
|
99
|
+
|
|
100
|
+
Sadly (but not really sadly),
|
|
101
|
+
these performance fixes make the BDD manager a viral argument in `morphata.logic`,
|
|
102
|
+
but it isn't too bad as it is optional,
|
|
103
|
+
and the `morphata.logic.*_to_automaton` functions will automatically create a
|
|
104
|
+
short-lived manager for these things.
|
|
105
|
+
|
|
106
|
+
### Known Limitations
|
|
107
|
+
|
|
108
|
+
- `psl_to_automaton` does not yet decompose LTL temporal operators
|
|
109
|
+
that wrap PSL closures at the top level (e.g. `Always(StrongClosure(r))`).
|
|
110
|
+
Lift the PSL fragment out of the surrounding LTL temporal operator before calling.
|
|
111
|
+
- The continuation of a `SuffixImpliesExist` / `SuffixImpliesUniv`
|
|
112
|
+
must be a pure-LTL formula; nested PSL closures inside a
|
|
113
|
+
continuation raise `NotImplementedError`.
|
|
114
|
+
|
|
115
|
+
## 2.0.0 — 2026-05-23
|
|
4
116
|
|
|
5
117
|
The major version bump folds the old `automatix` package into `morphata`
|
|
6
118
|
and replaces the constellation automata classes with a single typed intermediate
|
|
@@ -6,24 +6,30 @@ The `justfile` defines a set of build, lint, and test commands, which can direct
|
|
|
6
6
|
run as below:
|
|
7
7
|
|
|
8
8
|
|
|
9
|
+
The examples below assume the project virtual environment is activated (either
|
|
10
|
+
via `direnv` and `.envrc`, or by sourcing `.venv/bin/activate` manually). With
|
|
11
|
+
the venv active the tools are on `PATH` directly; no `uv run` prefix is needed.
|
|
12
|
+
If you'd rather not activate, prefix each command with `uv run --dev --frozen`
|
|
13
|
+
(or use the `just` recipes, which handle this for you).
|
|
14
|
+
|
|
9
15
|
**Testing** :
|
|
10
16
|
```bash
|
|
11
17
|
# Run all tests (uses --lf for last-failed)
|
|
12
18
|
just test
|
|
13
19
|
# OR
|
|
14
|
-
|
|
20
|
+
pytest --lf
|
|
15
21
|
|
|
16
22
|
# Run all tests verbosely without --lf
|
|
17
|
-
|
|
23
|
+
pytest -v
|
|
18
24
|
|
|
19
25
|
# Run a single test file
|
|
20
|
-
|
|
26
|
+
pytest tests/test_alphabet.py -v
|
|
21
27
|
|
|
22
28
|
# Run a single test function
|
|
23
|
-
|
|
29
|
+
pytest tests/test_alphabet.py::TestBooleanAlphabet::test_powerset_alphabet -v
|
|
24
30
|
|
|
25
31
|
# Run with coverage
|
|
26
|
-
|
|
32
|
+
pytest --cov=morphata --cov-report=term
|
|
27
33
|
```
|
|
28
34
|
|
|
29
35
|
**Linting and Formatting** :
|
|
@@ -31,8 +37,8 @@ uv run --dev --frozen pytest --cov=morphata --cov-report=term
|
|
|
31
37
|
# Format and lint (fixes issues automatically)
|
|
32
38
|
just fmt
|
|
33
39
|
# OR
|
|
34
|
-
|
|
35
|
-
|
|
40
|
+
ruff format
|
|
41
|
+
ruff check --output-format concise --fix --exit-non-zero-on-fix
|
|
36
42
|
|
|
37
43
|
# Format + type check
|
|
38
44
|
just lint
|
|
@@ -44,9 +50,9 @@ just lint
|
|
|
44
50
|
just type-check
|
|
45
51
|
|
|
46
52
|
# Run specific type checkers
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
53
|
+
mypy --strict
|
|
54
|
+
ty check --output-format concise
|
|
55
|
+
pyrefly check --output-format min-text
|
|
50
56
|
```
|
|
51
57
|
|
|
52
58
|
**Development Setup** :
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: morphata
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.2
|
|
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,7 +16,7 @@ Requires-Dist: attrs>=25.4.0
|
|
|
16
16
|
Requires-Dist: dd>=0.6.0
|
|
17
17
|
Requires-Dist: jaxtyping>=0.3
|
|
18
18
|
Requires-Dist: lark>=1.3.1
|
|
19
|
-
Requires-Dist: logic-asts>=1.
|
|
19
|
+
Requires-Dist: logic-asts>=1.6.2
|
|
20
20
|
Requires-Dist: networkx
|
|
21
21
|
Requires-Dist: numpy
|
|
22
22
|
Requires-Dist: types-networkx
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# morphata bench
|
|
2
|
+
|
|
3
|
+
Opt-in benchmark suite. Not collected by the default `pytest`
|
|
4
|
+
invocation.
|
|
5
|
+
|
|
6
|
+
## Running
|
|
7
|
+
|
|
8
|
+
pytest bench/ -v
|
|
9
|
+
|
|
10
|
+
To get pytest-benchmark's tabular wall-time output:
|
|
11
|
+
|
|
12
|
+
pytest bench/ -v --benchmark-columns=mean,median,min,max,rounds
|
|
13
|
+
|
|
14
|
+
## Suites
|
|
15
|
+
|
|
16
|
+
- `test_sere_nlm_perf.py` -- NLM-intersection state-space blowup.
|
|
17
|
+
Cases: `NLMInter(a0..a_{n-1})` for n in 2..6, `NLMInter(a_i;b_i)`
|
|
18
|
+
for n in 2..4, plus length-matching `Inter` baselines.
|
|
19
|
+
|
|
20
|
+
## Recording results
|
|
21
|
+
|
|
22
|
+
After running, paste the resulting tables (state count + wall time)
|
|
23
|
+
into the spec doc at:
|
|
24
|
+
`docs/superpowers/specs/2026-05-26-psl-refactor-and-nlm-perf-design.md`
|
|
25
|
+
|
|
26
|
+
The Phase 2 fix (memoization / pre-pass canonicalization / first-class
|
|
27
|
+
`_NLMInter` state) is chosen based on the numbers; the choice is
|
|
28
|
+
recorded as a follow-up plan.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""pytest fixtures for the morphata bench suite.
|
|
2
|
+
|
|
3
|
+
The bench/ tree is opt-in: invoke with `pytest bench/` (or
|
|
4
|
+
`pytest bench/test_sere_nlm_perf.py`). It is not collected by the
|
|
5
|
+
default `pytest` invocation.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import gc
|
|
11
|
+
from collections.abc import Iterator
|
|
12
|
+
|
|
13
|
+
import pytest
|
|
14
|
+
|
|
15
|
+
from morphata.logic.sere import _expansion_call_counter
|
|
16
|
+
from morphata.symbolic.bdd import Manager, make_manager
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@pytest.fixture
|
|
20
|
+
def bench_mgr() -> Iterator[Manager]:
|
|
21
|
+
"""Shared BDD manager scoped to one bench test (= many benchmark rounds).
|
|
22
|
+
|
|
23
|
+
Bench tests pass this manager to ``sere_to_automaton(..., manager=mgr)``
|
|
24
|
+
so the per-call ``make_manager()`` path is skipped. That path leaks
|
|
25
|
+
~12 MB of CUDD-internal state per call, which compounds across the
|
|
26
|
+
~100-1000 rounds pytest-benchmark runs per test and exhausts host
|
|
27
|
+
RAM. Sharing the manager keeps RSS flat.
|
|
28
|
+
"""
|
|
29
|
+
manager = make_manager()
|
|
30
|
+
try:
|
|
31
|
+
yield manager
|
|
32
|
+
finally:
|
|
33
|
+
gc.collect()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@pytest.fixture
|
|
37
|
+
def expansion_calls() -> Iterator[list[int]]:
|
|
38
|
+
"""Activate the expansion-call counter for the duration of one test.
|
|
39
|
+
|
|
40
|
+
Yields a single-element list whose entry is filled in by the test
|
|
41
|
+
with the final call count snapshot (`current_expansion_call_count()`).
|
|
42
|
+
"""
|
|
43
|
+
token = _expansion_call_counter.set(0)
|
|
44
|
+
try:
|
|
45
|
+
yield [0]
|
|
46
|
+
finally:
|
|
47
|
+
_expansion_call_counter.reset(token)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def current_expansion_call_count() -> int:
|
|
51
|
+
"""Read the current expansion-call counter value (0 if inactive)."""
|
|
52
|
+
val = _expansion_call_counter.get()
|
|
53
|
+
return val if val is not None else 0
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Benchmark suite for SERE NLMInter state-space behaviour.
|
|
2
|
+
|
|
3
|
+
Run with: ``pytest bench/test_sere_nlm_perf.py -v``
|
|
4
|
+
|
|
5
|
+
Records state count, expansion-call count, and wall time for
|
|
6
|
+
NLM-intersection of n branches, plus length-matching Inter as a
|
|
7
|
+
baseline.
|
|
8
|
+
|
|
9
|
+
History note: the original spec hypothesised an exponential blowup
|
|
10
|
+
in branch count for ``NLMInter``. Direct measurement (see the spec's
|
|
11
|
+
"Phase 1 Benchmark Results" section) showed no such blowup -- state
|
|
12
|
+
count stays at 3 for arbitrarily many AP-literal branches and grows
|
|
13
|
+
linearly (2n+3) for length-2-concat branches. The bench is retained
|
|
14
|
+
as a regression guard.
|
|
15
|
+
|
|
16
|
+
A separate finding from this investigation: ``make_manager()`` leaks
|
|
17
|
+
~12 MB of CUDD-internal state per call, so the bench passes a single
|
|
18
|
+
``bench_mgr`` fixture into ``sere_to_automaton(..., manager=mgr)``
|
|
19
|
+
to keep RSS flat across the ~100-1000 rounds pytest-benchmark runs
|
|
20
|
+
per test. Without that, repeated calls compound to GiB-scale RSS and
|
|
21
|
+
hang the host.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import logic_asts as logic
|
|
27
|
+
import logic_asts.sere as sere
|
|
28
|
+
import pytest
|
|
29
|
+
from pytest_benchmark.fixture import BenchmarkFixture as Benchmark
|
|
30
|
+
|
|
31
|
+
from morphata.logic.sere import sere_to_automaton
|
|
32
|
+
from morphata.symbolic.bdd import Manager
|
|
33
|
+
|
|
34
|
+
from .conftest import current_expansion_call_count
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _nlm_of_aps(n: int) -> logic.Expr:
|
|
38
|
+
return sere.NLMInter(tuple(sere.Variable(f"a{i}") for i in range(n)))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _lm_of_aps(n: int) -> logic.Expr:
|
|
42
|
+
return sere.Inter(tuple(sere.Variable(f"a{i}") for i in range(n)))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _nlm_of_concats(n: int) -> logic.Expr:
|
|
46
|
+
return sere.NLMInter(tuple(sere.Concat((sere.Variable(f"a{i}"), sere.Variable(f"b{i}"))) for i in range(n)))
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _run_and_measure(formula: logic.Expr, mgr: Manager) -> tuple[int, int]:
|
|
50
|
+
"""Return ``(state_count, expansion_call_count)``."""
|
|
51
|
+
aut = sere_to_automaton(formula, manager=mgr)
|
|
52
|
+
states = aut.states
|
|
53
|
+
n_states = len(states) if states is not None and not callable(states) else 0
|
|
54
|
+
return (n_states, current_expansion_call_count())
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@pytest.mark.timeout(10)
|
|
58
|
+
@pytest.mark.parametrize("n", list(range(2, 10)))
|
|
59
|
+
def test_bench_nlm_of_aps(benchmark: Benchmark, expansion_calls: list[int], bench_mgr: Manager, n: int) -> None:
|
|
60
|
+
formula = _nlm_of_aps(n)
|
|
61
|
+
result = benchmark(_run_and_measure, formula, bench_mgr)
|
|
62
|
+
state_count, call_count = result
|
|
63
|
+
expansion_calls[0] = call_count
|
|
64
|
+
print(f"\nNLMInter(a0..a{n - 1}): states={state_count} expansion_calls={call_count}")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@pytest.mark.timeout(10)
|
|
68
|
+
@pytest.mark.parametrize("n", list(range(2, 10)))
|
|
69
|
+
def test_bench_lm_of_aps_baseline(benchmark: Benchmark, expansion_calls: list[int], bench_mgr: Manager, n: int) -> None:
|
|
70
|
+
formula = _lm_of_aps(n)
|
|
71
|
+
result = benchmark(_run_and_measure, formula, bench_mgr)
|
|
72
|
+
state_count, call_count = result
|
|
73
|
+
expansion_calls[0] = call_count
|
|
74
|
+
print(f"\nInter(a0..a{n - 1}): states={state_count} expansion_calls={call_count}")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@pytest.mark.timeout(10)
|
|
78
|
+
@pytest.mark.parametrize("n", list(range(2, 10)))
|
|
79
|
+
def test_bench_nlm_of_concats(benchmark: Benchmark, expansion_calls: list[int], bench_mgr: Manager, n: int) -> None:
|
|
80
|
+
formula = _nlm_of_concats(n)
|
|
81
|
+
result = benchmark(_run_and_measure, formula, bench_mgr)
|
|
82
|
+
state_count, call_count = result
|
|
83
|
+
expansion_calls[0] = call_count
|
|
84
|
+
print(f"\nNLMInter(a_i;b_i) n={n}: states={state_count} expansion_calls={call_count}")
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Usage ------------------------------------------------------------------
|
|
5
|
+
usage() {
|
|
6
|
+
cat <<EOF
|
|
7
|
+
Usage: $(basename "$0") <bump>
|
|
8
|
+
|
|
9
|
+
bump Version bump kind passed to 'uv version --bump'
|
|
10
|
+
(patch, minor, major, etc.)
|
|
11
|
+
|
|
12
|
+
Examples:
|
|
13
|
+
$(basename "$0") patch
|
|
14
|
+
$(basename "$0") minor
|
|
15
|
+
EOF
|
|
16
|
+
exit 1
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if [[ $# -ne 1 ]]; then
|
|
20
|
+
usage
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
PROJECT_DIR="$(cd "$(dirname "$(realpath "${BASH_SOURCE[0]}")")/.." && pwd)"
|
|
24
|
+
cd "$PROJECT_DIR"
|
|
25
|
+
|
|
26
|
+
BUMP="$1"
|
|
27
|
+
|
|
28
|
+
# Helpers ----------------------------------------------------------------
|
|
29
|
+
is_jj() { [[ -d "$PROJECT_DIR/.jj" ]]; }
|
|
30
|
+
|
|
31
|
+
check_clean() {
|
|
32
|
+
if is_jj; then
|
|
33
|
+
if [[ -n "$(jj diff --summary 2>/dev/null)" ]]; then
|
|
34
|
+
echo "error: working copy is not clean (jj diff shows changes)" >&2
|
|
35
|
+
exit 1
|
|
36
|
+
fi
|
|
37
|
+
else
|
|
38
|
+
if [[ -n "$(git status --porcelain)" ]]; then
|
|
39
|
+
echo "error: working copy is not clean (git status shows changes)" >&2
|
|
40
|
+
exit 1
|
|
41
|
+
fi
|
|
42
|
+
fi
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
# Lint & type-check
|
|
46
|
+
echo "==> Running linters and type checkers..."
|
|
47
|
+
just lint
|
|
48
|
+
|
|
49
|
+
# Ensure working copy is clean
|
|
50
|
+
echo -n "==> Checking working copy is clean... "
|
|
51
|
+
check_clean
|
|
52
|
+
echo " OK"
|
|
53
|
+
|
|
54
|
+
# CHANGELOG.md is hand-authored under an `## Unreleased` heading; this
|
|
55
|
+
# script promotes that heading to the new version and dates it.
|
|
56
|
+
# Make sure that the Unreleased section is correctly there
|
|
57
|
+
|
|
58
|
+
UNRELEASED_COUNT="$(grep -c '^## Unreleased$' CHANGELOG.md || true)"
|
|
59
|
+
if [[ "$UNRELEASED_COUNT" -eq 0 ]]; then
|
|
60
|
+
echo "error: CHANGELOG.md has no '## Unreleased' section to promote" >&2
|
|
61
|
+
exit 1
|
|
62
|
+
elif [[ "$UNRELEASED_COUNT" -gt 1 ]]; then
|
|
63
|
+
echo "error: CHANGELOG.md has ${UNRELEASED_COUNT} '## Unreleased' sections; expected exactly one" >&2
|
|
64
|
+
exit 1
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
# Bump version and check tag
|
|
68
|
+
OLD_VERSION="$(uv version --short)"
|
|
69
|
+
NEW_VERSION="$(uv version --bump "$BUMP" --dry-run --short)"
|
|
70
|
+
TAG="v${NEW_VERSION}"
|
|
71
|
+
RELEASE_DATE="$(date +%Y-%m-%d)"
|
|
72
|
+
|
|
73
|
+
echo "==> Bumping $BUMP version..."
|
|
74
|
+
echo " Current version: $OLD_VERSION"
|
|
75
|
+
echo " New version: $NEW_VERSION"
|
|
76
|
+
echo " Tag: $TAG"
|
|
77
|
+
echo " Date: $RELEASE_DATE"
|
|
78
|
+
|
|
79
|
+
if git rev-parse -q --verify "refs/tags/$TAG" >/dev/null 2>&1; then
|
|
80
|
+
echo "error: tag '$TAG' already exists" >&2
|
|
81
|
+
echo "error: refusing to bump $BUMP version" >&2
|
|
82
|
+
exit 1
|
|
83
|
+
fi
|
|
84
|
+
uv version --bump "$BUMP"
|
|
85
|
+
|
|
86
|
+
# Promote CHANGELOG `Unreleased` to the new version
|
|
87
|
+
echo "==> Promoting CHANGELOG Unreleased -> ${NEW_VERSION} (${RELEASE_DATE})"
|
|
88
|
+
sed -i "s/^## Unreleased$/## ${NEW_VERSION} \xe2\x80\x94 ${RELEASE_DATE}/" CHANGELOG.md
|
|
89
|
+
|
|
90
|
+
# Commit
|
|
91
|
+
COMMIT_MSG="chore(release): ${NEW_VERSION}"
|
|
92
|
+
echo "==> Committing: $COMMIT_MSG"
|
|
93
|
+
|
|
94
|
+
if is_jj; then
|
|
95
|
+
jj commit -m "$COMMIT_MSG"
|
|
96
|
+
else
|
|
97
|
+
git add pyproject.toml CHANGELOG.md
|
|
98
|
+
git commit -m "$COMMIT_MSG"
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
# Build the tag annotation from the just-promoted CHANGELOG section.
|
|
102
|
+
# Title is "release: $(uv version)"; body is the section content (the
|
|
103
|
+
# lines between the new `## <ver> ...` heading and the next `## ` heading).
|
|
104
|
+
TAG_TITLE="release: $(uv version)"
|
|
105
|
+
TAG_BODY="$(awk -v ver="$NEW_VERSION" -v prev="$OLD_VERSION" '
|
|
106
|
+
$0 ~ "^## " ver "( |$)" { capture = 1; next }
|
|
107
|
+
capture && /^## / {
|
|
108
|
+
if ($0 !~ "^## " prev "( |$)") {
|
|
109
|
+
printf "error: expected next changelog heading to be %s, got: %s\n", \
|
|
110
|
+
"## " prev, $0 > "/dev/stderr"
|
|
111
|
+
exit 2
|
|
112
|
+
}
|
|
113
|
+
exit
|
|
114
|
+
}
|
|
115
|
+
capture { print }
|
|
116
|
+
' CHANGELOG.md)"
|
|
117
|
+
|
|
118
|
+
# Tag
|
|
119
|
+
echo "==> Tagging $TAG"
|
|
120
|
+
if is_jj; then
|
|
121
|
+
jj git import 2>/dev/null || true
|
|
122
|
+
fi
|
|
123
|
+
printf '%s\n\n%s\n' "$TAG_TITLE" "$TAG_BODY" \
|
|
124
|
+
| git tag -a "$TAG" --cleanup=verbatim -F -
|
|
125
|
+
if is_jj; then
|
|
126
|
+
jj git import 2>/dev/null || true
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
echo ""
|
|
130
|
+
echo "Done! Created tag: ${TAG}"
|
|
131
|
+
echo "To publish, push the tag: git push origin ${TAG}"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
PSL
|
|
2
|
+
===
|
|
3
|
+
|
|
4
|
+
Property Specification Language to Alternating Automaton conversion.
|
|
5
|
+
|
|
6
|
+
Finite-trace (PSLf) and omega-trace (Büchi) acceptance are both
|
|
7
|
+
supported via the ``finite`` keyword argument to
|
|
8
|
+
:func:`~morphata.logic.psl.psl_to_automaton`. Suffix implications
|
|
9
|
+
follow Spot's overlapping semantics: the last symbol matched by the
|
|
10
|
+
SERE prefix is shared with the first position at which the
|
|
11
|
+
continuation is evaluated.
|
|
12
|
+
|
|
13
|
+
.. automodule:: morphata.logic.psl
|
|
14
|
+
:members:
|
|
15
|
+
:show-inheritance:
|