morphata 2.0.4__tar.gz → 2.0.6__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.4 → morphata-2.0.6}/CHANGELOG.md +26 -0
- {morphata-2.0.4 → morphata-2.0.6}/PKG-INFO +2 -2
- {morphata-2.0.4 → morphata-2.0.6}/ci/make-release +3 -3
- {morphata-2.0.4 → morphata-2.0.6}/pixi.lock +1 -1
- {morphata-2.0.4 → morphata-2.0.6}/pixi.toml +1 -1
- {morphata-2.0.4 → morphata-2.0.6}/pyproject.toml +2 -2
- {morphata-2.0.4 → morphata-2.0.6}/src/morphata/hoa/__init__.py +2 -1
- {morphata-2.0.4 → morphata-2.0.6}/src/morphata/hoa/exporter.py +282 -9
- {morphata-2.0.4 → morphata-2.0.6}/src/morphata/symbolic/__init__.py +5 -1
- {morphata-2.0.4 → morphata-2.0.6}/src/morphata/symbolic/automaton.py +118 -5
- morphata-2.0.6/src/morphata/symbolic/persistence.py +311 -0
- morphata-2.0.6/tests/hoa/test_exporter_streaming.py +298 -0
- morphata-2.0.6/tests/symbolic/test_alphabet_aware_transitions.py +140 -0
- morphata-2.0.6/tests/symbolic/test_persistence.py +125 -0
- {morphata-2.0.4 → morphata-2.0.6}/tests/symbolic/test_to_hoa.py +21 -0
- {morphata-2.0.4 → morphata-2.0.6}/uv.lock +2 -2
- {morphata-2.0.4 → morphata-2.0.6}/.basedpyright/baseline.json +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/.claude/settings.local.json +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/.dockerignore +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/.editorconfig +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/.envrc +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/.envrc.recommended +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/.fdignore +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/.gitattributes +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/.gitignore +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/.mailmap +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/.python-version +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/.rstcheck.cfg +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/AGENTS.md +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/CLAUDE.md +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/CONTRIBUTING.md +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/LICENSE +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/README.md +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/bench/README.md +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/bench/__init__.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/bench/conftest.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/bench/test_sere_nlm_perf.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/cliff.toml +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/docs/_static/navigation.html +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/docs/api/modules.rst +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/docs/api/morphata.acceptance.rst +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/docs/api/morphata.alphabet.rst +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/docs/api/morphata.automaton.rst +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/docs/api/morphata.builder.rst +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/docs/api/morphata.hoa.rst +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/docs/api/morphata.logic.ltl.rst +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/docs/api/morphata.logic.psl.rst +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/docs/api/morphata.logic.sere.rst +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/docs/api/morphata.logic.strel.rst +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/docs/api/morphata.operators.matrix.rst +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/docs/api/morphata.operators.polynomial.rst +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/docs/api/morphata.operators.rst +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/docs/api/morphata.symbolic.rst +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/docs/concepts/acceptance.rst +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/docs/concepts/hoa.rst +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/docs/concepts/index.rst +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/docs/concepts/operators.rst +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/docs/concepts/spec.rst +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/docs/concepts/weights.rst +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/docs/conf.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/docs/index.rst +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/docs/quick-start.rst +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/docs/symbolic-transforms.rst +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/justfile +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/mypy.ini +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/project.utf-8.add +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/project.utf-8.add.spl +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/pyrefly.toml +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/pyrightconfig.json +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/pytest.toml +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/ruff.toml +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/src/morphata/__init__.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/src/morphata/acceptance.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/src/morphata/alphabet.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/src/morphata/automaton.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/src/morphata/builder.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/src/morphata/hoa/__main__.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/src/morphata/hoa/acc_expr.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/src/morphata/hoa/hoa.lark +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/src/morphata/hoa/parser.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/src/morphata/logic/__init__.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/src/morphata/logic/_common.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/src/morphata/logic/ltl.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/src/morphata/logic/psl.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/src/morphata/logic/sere.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/src/morphata/logic/strel.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/src/morphata/operators/__init__.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/src/morphata/operators/_backend.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/src/morphata/operators/matrix.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/src/morphata/operators/polynomial.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/src/morphata/py.typed +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/src/morphata/symbolic/bdd.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/src/morphata/symbolic/compose.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/src/morphata/symbolic/equivalence.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/src/morphata/symbolic/transforms.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/tests/conftest.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/tests/hoa/aut1.hoa +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/tests/hoa/aut11.hoa +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/tests/hoa/aut2.hoa +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/tests/hoa/aut3.2.hoa +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/tests/hoa/aut3.hoa +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/tests/hoa/aut4.hoa +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/tests/hoa/aut5.hoa +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/tests/hoa/aut6.hoa +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/tests/hoa/aut7.hoa +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/tests/hoa/aut8.hoa +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/tests/hoa/finite_aut.hoa +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/tests/hoa/gen_buchi_3.hoa +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/tests/hoa/gen_cobuchi_2.hoa +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/tests/hoa/rabin_2pair.hoa +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/tests/hoa/streett_2pair.hoa +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/tests/hoa/test_exporter_basic.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/tests/hoa/test_exporter_roundtrip.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/tests/hoa/test_parser.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/tests/logic/__init__.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/tests/logic/test_ltl.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/tests/logic/test_psl.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/tests/logic/test_sere.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/tests/logic/test_sere_properties.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/tests/logic/test_strel.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/tests/operators/test_matrix_from_ir.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/tests/operators/test_matrix_from_ltl.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/tests/operators/test_polynomial_from_ir.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/tests/symbolic/test_bdd_helpers.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/tests/symbolic/test_compose.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/tests/symbolic/test_from_hoaf.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/tests/symbolic/test_ltlf2dfa_equivalence.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/tests/symbolic/test_step.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/tests/symbolic/test_symbolic_automaton.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/tests/symbolic/test_symbolic_from_ir.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/tests/symbolic/test_transforms.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/tests/symbolic/test_transforms_ir.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/tests/symbolic/test_transforms_properties.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/tests/test_alphabet.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/tests/test_automaton.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/tests/test_builder.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/tests/test_from_parsed.py +0 -0
- {morphata-2.0.4 → morphata-2.0.6}/ty.toml +0 -0
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.0.6 — 2026-06-02
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- A major regression where the streaming `to_hoa` stuff changed the label enumeration
|
|
8
|
+
semantics to be _minterms_ instead of _cubes_.
|
|
9
|
+
This is now fixed.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Added a free-floating function that helps enumerate the successor BDDs from a symbolic
|
|
14
|
+
automaton state, given a specific alphabet and set of symbols.
|
|
15
|
+
|
|
16
|
+
## 2.0.5 — 2026-06-01
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
`SymbolicAutomaton` and `Automaton` now support a streaming writer
|
|
21
|
+
for HOA export via `morphata.hoa.symbolic_automaton_to_hoa`
|
|
22
|
+
and `morphata.hoa.automaton_to_hoa`, and is also present in `SymbolicAutomaton.to_hoa`.
|
|
23
|
+
|
|
24
|
+
This is mostly a performance thing:
|
|
25
|
+
providing a buffered writer to the exporter allows one to stream the HOA format into a
|
|
26
|
+
file, without having to manifest the entire string representation of the automaton,
|
|
27
|
+
which can be cumbersome.
|
|
28
|
+
|
|
3
29
|
## 2.0.4 — 2026-05-28
|
|
4
30
|
|
|
5
31
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: morphata
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.6
|
|
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
|
|
@@ -20,7 +20,7 @@ Requires-Dist: logic-asts>=1.6.2
|
|
|
20
20
|
Requires-Dist: networkx
|
|
21
21
|
Requires-Dist: numpy
|
|
22
22
|
Requires-Dist: types-networkx
|
|
23
|
-
Requires-Dist: typing-extensions
|
|
23
|
+
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
|
|
@@ -88,7 +88,7 @@ echo "==> Promoting CHANGELOG Unreleased -> ${NEW_VERSION} (${RELEASE_DATE})"
|
|
|
88
88
|
sed -i "s/^## Unreleased$/## ${NEW_VERSION} \xe2\x80\x94 ${RELEASE_DATE}/" CHANGELOG.md
|
|
89
89
|
|
|
90
90
|
# Commit
|
|
91
|
-
COMMIT_MSG="chore(release): $
|
|
91
|
+
COMMIT_MSG="chore(release): $(uv version)"
|
|
92
92
|
echo "==> Committing: $COMMIT_MSG"
|
|
93
93
|
|
|
94
94
|
if is_jj; then
|
|
@@ -120,8 +120,8 @@ echo "==> Tagging $TAG"
|
|
|
120
120
|
if is_jj; then
|
|
121
121
|
jj git import 2>/dev/null || true
|
|
122
122
|
fi
|
|
123
|
-
printf '%s\n\n%s\n' "$TAG_TITLE" "$TAG_BODY"
|
|
124
|
-
|
|
123
|
+
printf '%s\n\n%s\n' "$TAG_TITLE" "$TAG_BODY" |
|
|
124
|
+
git tag -a "$TAG" --cleanup=verbatim -F -
|
|
125
125
|
if is_jj; then
|
|
126
126
|
jj git import 2>/dev/null || true
|
|
127
127
|
fi
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name = "morphata"
|
|
3
3
|
authors = [{ name = "Anand Balakrishnan", email = "anandbala1597@gmail.com" }]
|
|
4
4
|
description = "Representation theory for automata, made practical."
|
|
5
|
-
version = "2.0.
|
|
5
|
+
version = "2.0.6"
|
|
6
6
|
requires-python = ">=3.12"
|
|
7
7
|
classifiers = [
|
|
8
8
|
"Intended Audience :: Science/Research",
|
|
@@ -21,7 +21,7 @@ dependencies = [
|
|
|
21
21
|
"networkx",
|
|
22
22
|
"numpy",
|
|
23
23
|
"types-networkx",
|
|
24
|
-
"typing-extensions",
|
|
24
|
+
"typing-extensions>=4.14",
|
|
25
25
|
]
|
|
26
26
|
|
|
27
27
|
[project.optional-dependencies]
|
|
@@ -37,7 +37,8 @@ Example extended HOA with finite acceptance::
|
|
|
37
37
|
|
|
38
38
|
import morphata.hoa.parser as parser
|
|
39
39
|
from morphata.hoa.exporter import automaton_to_hoa as automaton_to_hoa
|
|
40
|
+
from morphata.hoa.exporter import symbolic_automaton_to_hoa as symbolic_automaton_to_hoa
|
|
40
41
|
from morphata.hoa.parser import ParsedAutomaton as ParsedAutomaton
|
|
41
42
|
from morphata.hoa.parser import parse as parse
|
|
42
43
|
|
|
43
|
-
__all__ = ["parse", "parser", "ParsedAutomaton", "automaton_to_hoa"]
|
|
44
|
+
__all__ = ["parse", "parser", "ParsedAutomaton", "automaton_to_hoa", "symbolic_automaton_to_hoa"]
|
|
@@ -12,23 +12,53 @@ import io
|
|
|
12
12
|
import typing as ty
|
|
13
13
|
from collections.abc import Iterable, Mapping, Sequence
|
|
14
14
|
from collections.abc import Set as AbstractSet
|
|
15
|
+
from typing import TYPE_CHECKING
|
|
15
16
|
|
|
16
17
|
import logic_asts.base as logic
|
|
18
|
+
from typing_extensions import Writer, overload
|
|
17
19
|
|
|
18
20
|
import morphata.hoa.acc_expr as acc_expr
|
|
19
21
|
|
|
20
|
-
if
|
|
22
|
+
if TYPE_CHECKING:
|
|
21
23
|
from morphata.acceptance import AcceptanceCondition
|
|
22
24
|
from morphata.automaton import Automaton
|
|
25
|
+
from morphata.symbolic.automaton import SymbolicAutomaton
|
|
26
|
+
from morphata.symbolic.bdd import Function, Manager
|
|
23
27
|
|
|
24
28
|
|
|
29
|
+
@overload
|
|
25
30
|
def automaton_to_hoa(
|
|
26
|
-
aut: Automaton[int, ty.Any, ty.Any],
|
|
31
|
+
aut: "Automaton[int, ty.Any, ty.Any]",
|
|
32
|
+
*,
|
|
33
|
+
name: str | None = ...,
|
|
34
|
+
properties: "Sequence[str] | None" = ...,
|
|
35
|
+
out: None = ...,
|
|
36
|
+
) -> str: ...
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@overload
|
|
40
|
+
def automaton_to_hoa(
|
|
41
|
+
aut: "Automaton[int, ty.Any, ty.Any]",
|
|
42
|
+
*,
|
|
43
|
+
name: str | None = ...,
|
|
44
|
+
properties: "Sequence[str] | None" = ...,
|
|
45
|
+
out: "Writer[str]",
|
|
46
|
+
) -> None: ...
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def automaton_to_hoa(
|
|
50
|
+
aut: "Automaton[int, ty.Any, ty.Any]",
|
|
27
51
|
*,
|
|
28
52
|
name: str | None = None,
|
|
29
|
-
properties: Sequence[str] | None = None,
|
|
30
|
-
|
|
31
|
-
|
|
53
|
+
properties: "Sequence[str] | None" = None,
|
|
54
|
+
out: "Writer[str] | None" = None,
|
|
55
|
+
) -> "str | None":
|
|
56
|
+
"""Serialize *aut* to HOA v1.
|
|
57
|
+
|
|
58
|
+
If ``out`` is ``None`` (the default), build the HOA in an internal
|
|
59
|
+
``io.StringIO`` and return the resulting string. If ``out`` is
|
|
60
|
+
provided, stream the HOA directly into it and return ``None``; the
|
|
61
|
+
caller owns flushing / closing.
|
|
32
62
|
|
|
33
63
|
Requirements on *aut*:
|
|
34
64
|
|
|
@@ -50,7 +80,8 @@ def automaton_to_hoa(
|
|
|
50
80
|
ap_to_index = {ap: i for i, ap in enumerate(ap_order)}
|
|
51
81
|
acc_name, acc_expr_text, num_sets, acc_state_sets = _acceptance_to_hoa(aut.acceptance)
|
|
52
82
|
|
|
53
|
-
|
|
83
|
+
owns_buffer = out is None
|
|
84
|
+
buf: Writer[str] = io.StringIO() if out is None else out
|
|
54
85
|
_write_header(
|
|
55
86
|
buf,
|
|
56
87
|
aut=aut,
|
|
@@ -69,7 +100,186 @@ def automaton_to_hoa(
|
|
|
69
100
|
ap_to_index=ap_to_index,
|
|
70
101
|
acc_state_sets=acc_state_sets,
|
|
71
102
|
)
|
|
72
|
-
|
|
103
|
+
if owns_buffer:
|
|
104
|
+
assert isinstance(buf, io.StringIO)
|
|
105
|
+
return buf.getvalue()
|
|
106
|
+
return None
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@overload
|
|
110
|
+
def symbolic_automaton_to_hoa(
|
|
111
|
+
sym: "SymbolicAutomaton[ty.Any]",
|
|
112
|
+
*,
|
|
113
|
+
name: str | None = ...,
|
|
114
|
+
properties: Sequence[str] | None = ...,
|
|
115
|
+
out: None = ...,
|
|
116
|
+
) -> str: ...
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@overload
|
|
120
|
+
def symbolic_automaton_to_hoa(
|
|
121
|
+
sym: "SymbolicAutomaton[ty.Any]",
|
|
122
|
+
*,
|
|
123
|
+
name: str | None = ...,
|
|
124
|
+
properties: Sequence[str] | None = ...,
|
|
125
|
+
out: Writer[str],
|
|
126
|
+
) -> None: ...
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def symbolic_automaton_to_hoa(
|
|
130
|
+
sym: "SymbolicAutomaton[ty.Any]",
|
|
131
|
+
*,
|
|
132
|
+
name: str | None = None,
|
|
133
|
+
properties: Sequence[str] | None = None,
|
|
134
|
+
out: Writer[str] | None = None,
|
|
135
|
+
) -> str | None:
|
|
136
|
+
"""Serialize a :class:`SymbolicAutomaton` to HOA v1 directly from its BDDs.
|
|
137
|
+
|
|
138
|
+
Walks ``sym.transitions`` row-by-row. For each cached
|
|
139
|
+
``(guard_bdd, succ_bdd)`` pair, decodes both BDDs into compact
|
|
140
|
+
``logic_asts.BoolExpr`` trees via Shannon expansion
|
|
141
|
+
(:func:`~morphata.symbolic.bdd.bdd_to_boolexpr`) and emits one HOA
|
|
142
|
+
edge per disjunct of the successor expression, with a single label
|
|
143
|
+
expression per row. Cube structure of the guard is preserved -- variables
|
|
144
|
+
absent from a given path through the BDD are projected out -- so the
|
|
145
|
+
output size is proportional to the BDD shape, not to ``2^|support|``.
|
|
146
|
+
|
|
147
|
+
``out`` semantics match :func:`automaton_to_hoa`: when ``None``, an
|
|
148
|
+
internal ``StringIO`` accumulates the output and is returned;
|
|
149
|
+
otherwise the output streams into ``out`` and the function returns
|
|
150
|
+
``None``.
|
|
151
|
+
|
|
152
|
+
Literal-true / literal-false successor BDDs (``mgr.true`` /
|
|
153
|
+
``mgr.false``) are handled by emitting synthetic sink states with
|
|
154
|
+
self-loops, mirroring :func:`_materialize_sinks` on the IR side. The
|
|
155
|
+
BDD manager is not mutated. As on the IR side, sink materialisation
|
|
156
|
+
is only implemented for :class:`~morphata.acceptance.Finite`
|
|
157
|
+
acceptance; omega-regular conditions with literal successors raise
|
|
158
|
+
``NotImplementedError``.
|
|
159
|
+
|
|
160
|
+
Raises
|
|
161
|
+
------
|
|
162
|
+
ValueError
|
|
163
|
+
If the cached transition table is empty but ``sym.num_states > 0``.
|
|
164
|
+
NotImplementedError
|
|
165
|
+
If literal-true / literal-false successors are present and the
|
|
166
|
+
acceptance condition is not :class:`~morphata.acceptance.Finite`.
|
|
167
|
+
"""
|
|
168
|
+
from morphata.acceptance import Finite
|
|
169
|
+
from morphata.symbolic.bdd import bdd_to_boolexpr
|
|
170
|
+
|
|
171
|
+
if sym.num_states > 0 and not sym.transitions:
|
|
172
|
+
raise ValueError(
|
|
173
|
+
"symbolic_automaton_to_hoa requires a populated transition cache; "
|
|
174
|
+
"reconstruct SymbolicAutomaton with cache_transitions=True."
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
mgr = sym.manager
|
|
178
|
+
|
|
179
|
+
needs_true = sym.initial == mgr.true
|
|
180
|
+
needs_false = sym.initial == mgr.false
|
|
181
|
+
for s in range(sym.num_states):
|
|
182
|
+
for _, succ_bdd in sym.transitions.get(s, ()):
|
|
183
|
+
if succ_bdd == mgr.true:
|
|
184
|
+
needs_true = True
|
|
185
|
+
elif succ_bdd == mgr.false:
|
|
186
|
+
needs_false = True
|
|
187
|
+
|
|
188
|
+
if (needs_true or needs_false) and not isinstance(sym.acceptance, Finite):
|
|
189
|
+
raise NotImplementedError(
|
|
190
|
+
"HOA export: literal-true / literal-false successor BDDs require "
|
|
191
|
+
"sink-state materialisation, which is only implemented for "
|
|
192
|
+
f"Finite acceptance; got {type(sym.acceptance).__name__}."
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
next_idx = sym.num_states
|
|
196
|
+
true_sink: int | None = None
|
|
197
|
+
false_sink: int | None = None
|
|
198
|
+
if needs_true:
|
|
199
|
+
true_sink = next_idx
|
|
200
|
+
next_idx += 1
|
|
201
|
+
if needs_false:
|
|
202
|
+
false_sink = next_idx
|
|
203
|
+
next_idx += 1
|
|
204
|
+
total_states = next_idx
|
|
205
|
+
|
|
206
|
+
ap_order = sorted(sym.ap_registry, key=repr)
|
|
207
|
+
ap_to_index: dict[ty.Any, int] = {ap: i for i, ap in enumerate(ap_order)}
|
|
208
|
+
ap_decoder_map: dict[str, ty.Any] = {var: ap for ap, var in sym.ap_registry.items()}
|
|
209
|
+
|
|
210
|
+
def _ap_decode(name: str) -> ty.Any: # noqa: ANN401
|
|
211
|
+
try:
|
|
212
|
+
return ap_decoder_map[name]
|
|
213
|
+
except KeyError as exc:
|
|
214
|
+
raise ValueError(f"Guard BDD references variable {name!r} not in the AP registry.") from exc
|
|
215
|
+
|
|
216
|
+
acc_name, acc_expr_text, num_sets, acc_state_sets = _acceptance_to_hoa(sym.acceptance)
|
|
217
|
+
if true_sink is not None:
|
|
218
|
+
# Finite acceptance: extend set 0 (the accepting set) with the
|
|
219
|
+
# true-sink. ``_acceptance_to_hoa(Finite)`` always returns
|
|
220
|
+
# ``{0: ...}``.
|
|
221
|
+
augmented_sets: dict[int, AbstractSet[int]] = dict(acc_state_sets)
|
|
222
|
+
augmented_sets[0] = frozenset(augmented_sets.get(0, frozenset())) | {true_sink}
|
|
223
|
+
acc_state_sets = augmented_sets
|
|
224
|
+
states = list(range(sym.num_states))
|
|
225
|
+
|
|
226
|
+
owns_buffer = out is None
|
|
227
|
+
buf: Writer[str] = io.StringIO() if owns_buffer else out # type: ignore[assignment]
|
|
228
|
+
|
|
229
|
+
buf.write("HOA: v1\n")
|
|
230
|
+
if name is not None:
|
|
231
|
+
buf.write(f'name: "{name}"\n')
|
|
232
|
+
buf.write(f"States: {total_states}\n")
|
|
233
|
+
if sym.initial == mgr.true:
|
|
234
|
+
assert true_sink is not None
|
|
235
|
+
buf.write(f"Start: {true_sink}\n")
|
|
236
|
+
elif sym.initial == mgr.false:
|
|
237
|
+
assert false_sink is not None
|
|
238
|
+
buf.write(f"Start: {false_sink}\n")
|
|
239
|
+
else:
|
|
240
|
+
initial_expr = ty.cast("logic.BoolExpr[int]", bdd_to_boolexpr(sym.initial))
|
|
241
|
+
for start_line in _initial_start_lines(initial_expr):
|
|
242
|
+
buf.write(f"Start: {start_line}\n")
|
|
243
|
+
ap_words = " ".join(f'"{ap}"' for ap in ap_order)
|
|
244
|
+
buf.write(f"AP: {len(ap_order)}{(' ' + ap_words) if ap_order else ''}\n")
|
|
245
|
+
if acc_name is not None:
|
|
246
|
+
buf.write(f"acc-name: {acc_name}\n")
|
|
247
|
+
buf.write(f"Acceptance: {num_sets} {acc_expr_text}\n")
|
|
248
|
+
if properties:
|
|
249
|
+
buf.write("properties: " + " ".join(properties) + "\n")
|
|
250
|
+
|
|
251
|
+
buf.write("--BODY--\n")
|
|
252
|
+
for s in states:
|
|
253
|
+
sets_for_s = sorted(idx for idx, members in acc_state_sets.items() if s in members)
|
|
254
|
+
marker = (" {" + " ".join(str(i) for i in sets_for_s) + "}") if sets_for_s else ""
|
|
255
|
+
buf.write(f"State: {s}{marker}\n")
|
|
256
|
+
for guard_bdd, succ_bdd in sym.transitions.get(s, ()):
|
|
257
|
+
if succ_bdd == mgr.true:
|
|
258
|
+
assert true_sink is not None
|
|
259
|
+
dsts = [str(true_sink)]
|
|
260
|
+
elif succ_bdd == mgr.false:
|
|
261
|
+
assert false_sink is not None
|
|
262
|
+
dsts = [str(false_sink)]
|
|
263
|
+
else:
|
|
264
|
+
succ_expr = ty.cast("logic.BoolExpr[int]", bdd_to_boolexpr(succ_bdd))
|
|
265
|
+
dsts = _state_expr_to_edges(succ_expr)
|
|
266
|
+
guard_expr = bdd_to_boolexpr(guard_bdd, var_decoder=_ap_decode)
|
|
267
|
+
label = _label_to_hoa(guard_expr, ap_to_index)
|
|
268
|
+
for dst in dsts:
|
|
269
|
+
buf.write(f"[{label}] {dst}\n")
|
|
270
|
+
for sink in (true_sink, false_sink):
|
|
271
|
+
if sink is None:
|
|
272
|
+
continue
|
|
273
|
+
sets_for_sink = sorted(idx for idx, members in acc_state_sets.items() if sink in members)
|
|
274
|
+
marker = (" {" + " ".join(str(i) for i in sets_for_sink) + "}") if sets_for_sink else ""
|
|
275
|
+
buf.write(f"State: {sink}{marker}\n")
|
|
276
|
+
buf.write(f"[t] {sink}\n")
|
|
277
|
+
buf.write("--END--\n")
|
|
278
|
+
|
|
279
|
+
if owns_buffer:
|
|
280
|
+
assert isinstance(buf, io.StringIO)
|
|
281
|
+
return buf.getvalue()
|
|
282
|
+
return None
|
|
73
283
|
|
|
74
284
|
|
|
75
285
|
# ---------------------------------------------------------------------------
|
|
@@ -181,7 +391,7 @@ def _materialize_sinks(
|
|
|
181
391
|
|
|
182
392
|
|
|
183
393
|
def _write_header(
|
|
184
|
-
buf:
|
|
394
|
+
buf: Writer[str],
|
|
185
395
|
*,
|
|
186
396
|
aut: Automaton[int, ty.Any, ty.Any],
|
|
187
397
|
states: list[int],
|
|
@@ -255,7 +465,7 @@ def _flatten_or(expr: logic.BoolExpr[ty.Any]) -> list[logic.BoolExpr[ty.Any]]:
|
|
|
255
465
|
|
|
256
466
|
|
|
257
467
|
def _write_body(
|
|
258
|
-
buf:
|
|
468
|
+
buf: Writer[str],
|
|
259
469
|
*,
|
|
260
470
|
aut: Automaton[int, ty.Any, ty.Any],
|
|
261
471
|
states: list[int],
|
|
@@ -351,6 +561,69 @@ def _conj_to_dst(expr: logic.BoolExpr[int], *, context: str) -> str:
|
|
|
351
561
|
raise ValueError(f"HOA {context} expression must be a disjunction of conjunctions of state variables; got {expr!r}.")
|
|
352
562
|
|
|
353
563
|
|
|
564
|
+
# ---------------------------------------------------------------------------
|
|
565
|
+
# BDD-direct helpers (symbolic_automaton_to_hoa)
|
|
566
|
+
# ---------------------------------------------------------------------------
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
def _cube_to_hoa_label(
|
|
570
|
+
cube: Mapping[str, bool],
|
|
571
|
+
bdd_var_to_ap_index: Mapping[str, int],
|
|
572
|
+
) -> str:
|
|
573
|
+
"""Render a single BDD cube over AP variables as an HOA label.
|
|
574
|
+
|
|
575
|
+
*cube* is a ``{bdd_var_name: bool}`` dict from ``dd``'s
|
|
576
|
+
``BDD.pick_iter``; variables absent from the BDD's support are
|
|
577
|
+
don't-cares and do not appear in the output.
|
|
578
|
+
*bdd_var_to_ap_index* maps the BDD variable name back to the AP's
|
|
579
|
+
HOA index. Literals in the output are sorted by AP index for
|
|
580
|
+
deterministic output.
|
|
581
|
+
"""
|
|
582
|
+
if not cube:
|
|
583
|
+
return "t"
|
|
584
|
+
literals: list[tuple[int, bool]] = []
|
|
585
|
+
for var_name, polarity in cube.items():
|
|
586
|
+
try:
|
|
587
|
+
idx = bdd_var_to_ap_index[var_name]
|
|
588
|
+
except KeyError as exc:
|
|
589
|
+
raise ValueError(f"BDD cube references variable {var_name!r} not in the AP registry.") from exc
|
|
590
|
+
literals.append((idx, polarity))
|
|
591
|
+
literals.sort(key=lambda iv: iv[0])
|
|
592
|
+
parts = [(str(idx) if polarity else f"!{idx}") for idx, polarity in literals]
|
|
593
|
+
if len(parts) == 1:
|
|
594
|
+
return parts[0]
|
|
595
|
+
return "(" + " & ".join(parts) + ")"
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
def _bdd_state_cubes(node: "Function", *, mgr: "Manager") -> list[str]:
|
|
599
|
+
"""Enumerate satisfying cubes of *node* over state variables as HOA dst strings.
|
|
600
|
+
|
|
601
|
+
*node* is a ``dd.Function`` over the state variables ``q0..q{n-1}``;
|
|
602
|
+
*mgr* is the owning ``dd.BDD`` manager (``pick_iter`` lives on it,
|
|
603
|
+
not on Function objects). We enumerate minterms over the BDD's
|
|
604
|
+
*support* (i.e. no ``care_vars`` argument), so a point-successor
|
|
605
|
+
function naturally yields one cube.
|
|
606
|
+
|
|
607
|
+
For each cube:
|
|
608
|
+
|
|
609
|
+
- Positive state variables -> the cube's HOA destination
|
|
610
|
+
``"d1&d2&..."`` (a single integer when there is one positive).
|
|
611
|
+
- Negative state variables in the cube are dropped from the output
|
|
612
|
+
(HOA `dst` expressions list only the chosen states).
|
|
613
|
+
|
|
614
|
+
A cube with zero positive state variables is treated as a bug and
|
|
615
|
+
raises ``ValueError``; the caller is responsible for rejecting
|
|
616
|
+
literal-false successor BDDs before reaching here.
|
|
617
|
+
"""
|
|
618
|
+
cubes: list[str] = []
|
|
619
|
+
for cube in mgr.pick_iter(node):
|
|
620
|
+
positives = sorted(int(name[1:]) for name, val in cube.items() if val)
|
|
621
|
+
if not positives:
|
|
622
|
+
raise ValueError("Successor BDD cube has no positive state variable; cannot render an HOA destination.")
|
|
623
|
+
cubes.append("&".join(str(i) for i in positives))
|
|
624
|
+
return cubes
|
|
625
|
+
|
|
626
|
+
|
|
354
627
|
# ---------------------------------------------------------------------------
|
|
355
628
|
# Acceptance
|
|
356
629
|
# ---------------------------------------------------------------------------
|
|
@@ -10,7 +10,10 @@ This subpackage provides:
|
|
|
10
10
|
- :mod:`morphata.symbolic.automaton`: :class:`SymbolicAutomaton`, a
|
|
11
11
|
representation that wraps an :class:`~morphata.automaton.Automaton`
|
|
12
12
|
so the initial state set and every cached transition are live BDD
|
|
13
|
-
nodes in a single ``dd.BDD`` manager.
|
|
13
|
+
nodes in a single ``dd.BDD`` manager. Also exposes
|
|
14
|
+
:func:`successors_for_alphabet`, a freestanding enumerator that
|
|
15
|
+
yields ``(symbol, succ_bdd)`` per declared symbol without
|
|
16
|
+
materialising a per-state transition relation.
|
|
14
17
|
- :mod:`morphata.symbolic.transforms`: BDD-level structural transforms
|
|
15
18
|
-- :func:`~morphata.symbolic.transforms.nfa_from_afa` (dealternation),
|
|
16
19
|
:func:`~morphata.symbolic.transforms.determinize` (subset construction
|
|
@@ -21,6 +24,7 @@ This subpackage provides:
|
|
|
21
24
|
"""
|
|
22
25
|
|
|
23
26
|
from morphata.symbolic.automaton import SymbolicAutomaton as SymbolicAutomaton
|
|
27
|
+
from morphata.symbolic.automaton import successors_for_alphabet as successors_for_alphabet
|
|
24
28
|
from morphata.symbolic.bdd import AP_VAR_FORMAT as AP_VAR_FORMAT
|
|
25
29
|
from morphata.symbolic.bdd import STATE_VAR_FORMAT as STATE_VAR_FORMAT
|
|
26
30
|
from morphata.symbolic.bdd import APRegistry as APRegistry
|
|
@@ -32,7 +32,7 @@ see :mod:`morphata.symbolic.transforms`.
|
|
|
32
32
|
from __future__ import annotations
|
|
33
33
|
|
|
34
34
|
import typing
|
|
35
|
-
from collections.abc import Callable, Hashable, Iterable, Mapping, Sequence
|
|
35
|
+
from collections.abc import Callable, Hashable, Iterable, Iterator, Mapping, Sequence
|
|
36
36
|
from collections.abc import Set as AbstractSet
|
|
37
37
|
from dataclasses import dataclass, field
|
|
38
38
|
from typing import TYPE_CHECKING, Any, Generic
|
|
@@ -44,6 +44,10 @@ from morphata.automaton import Automaton, BoolExpr
|
|
|
44
44
|
from morphata.hoa import ParsedAutomaton
|
|
45
45
|
|
|
46
46
|
if TYPE_CHECKING:
|
|
47
|
+
from pathlib import Path
|
|
48
|
+
|
|
49
|
+
from typing_extensions import Writer
|
|
50
|
+
|
|
47
51
|
from morphata.alphabet import BooleanAlphabet
|
|
48
52
|
from morphata.symbolic.bdd import (
|
|
49
53
|
APRegistry,
|
|
@@ -230,11 +234,43 @@ class SymbolicAutomaton(Generic[AP]):
|
|
|
230
234
|
*,
|
|
231
235
|
name: str | None = None,
|
|
232
236
|
properties: Sequence[str] | None = None,
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
237
|
+
out: "Writer[str] | None" = None,
|
|
238
|
+
) -> str | None:
|
|
239
|
+
"""Serialize ``self`` to HOA v1 via :func:`symbolic_automaton_to_hoa`.
|
|
240
|
+
|
|
241
|
+
Walks the cached transition BDDs directly -- no
|
|
242
|
+
``logic_asts.BoolExpr`` intermediates. When ``out`` is given,
|
|
243
|
+
streams output into it and returns ``None``; otherwise returns
|
|
244
|
+
the full HOA string.
|
|
245
|
+
"""
|
|
246
|
+
from morphata.hoa import symbolic_automaton_to_hoa
|
|
247
|
+
|
|
248
|
+
return symbolic_automaton_to_hoa(self, name=name, properties=properties, out=out)
|
|
249
|
+
|
|
250
|
+
def to_bdd_json(self, path: "str | Path") -> None:
|
|
251
|
+
"""Persist ``self`` to a BDD JSON cache via :func:`to_bdd_json`.
|
|
252
|
+
|
|
253
|
+
Writes ``path`` (the BDD nodes, via :mod:`dd`'s multi-root JSON
|
|
254
|
+
dump) and a sibling ``path + ".meta.json"`` (acceptance, AP
|
|
255
|
+
registry, transition root layout). Intended as an internal
|
|
256
|
+
fast-path cache; HOA remains the right format for sharing
|
|
257
|
+
automata with other tools.
|
|
258
|
+
"""
|
|
259
|
+
from morphata.symbolic.persistence import to_bdd_json
|
|
260
|
+
|
|
261
|
+
to_bdd_json(self, path)
|
|
262
|
+
|
|
263
|
+
@staticmethod
|
|
264
|
+
def from_bdd_json(
|
|
265
|
+
path: "str | Path",
|
|
266
|
+
*,
|
|
267
|
+
alphabet: "BooleanAlphabet[Any, str]",
|
|
268
|
+
manager: Manager | None = None,
|
|
269
|
+
) -> "SymbolicAutomaton[str]":
|
|
270
|
+
"""Reload a :class:`SymbolicAutomaton` written by :meth:`to_bdd_json`."""
|
|
271
|
+
from morphata.symbolic.persistence import from_bdd_json
|
|
236
272
|
|
|
237
|
-
return
|
|
273
|
+
return from_bdd_json(path, alphabet=alphabet, manager=manager)
|
|
238
274
|
|
|
239
275
|
def to_min_dfa(self) -> "SymbolicAutomaton[AP]":
|
|
240
276
|
"""Determinize and minimize ``self`` into a minimal DFA.
|
|
@@ -349,6 +385,83 @@ class SymbolicAutomaton(Generic[AP]):
|
|
|
349
385
|
)
|
|
350
386
|
|
|
351
387
|
|
|
388
|
+
# ---------------------------------------------------------------------------
|
|
389
|
+
# Alphabet-aware enumeration
|
|
390
|
+
# ---------------------------------------------------------------------------
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def successors_for_alphabet(
|
|
394
|
+
sym_aut: "SymbolicAutomaton[AP]",
|
|
395
|
+
source: int,
|
|
396
|
+
alphabet: "BooleanAlphabet[Any, AP]",
|
|
397
|
+
symbols: Iterable[Any],
|
|
398
|
+
) -> Iterator[tuple[Any, Function]]:
|
|
399
|
+
"""Yield ``(symbol, succ_bdd)`` for each ``symbol in symbols`` whose
|
|
400
|
+
cofactor of the source's outgoing transitions is not ``mgr.false``.
|
|
401
|
+
|
|
402
|
+
Each yielded ``succ_bdd`` is a BDD over state variables encoding the
|
|
403
|
+
set of successors of ``source`` on ``symbol``. For DFA-shaped
|
|
404
|
+
automata it is a single state variable; for non-deterministic
|
|
405
|
+
shapes it is the OR of active successors.
|
|
406
|
+
|
|
407
|
+
Cost is bounded by ``|symbols| * len(sym_aut.transitions[source])``
|
|
408
|
+
``mgr.let`` calls on per-row guards (each typically very small).
|
|
409
|
+
No per-state transition relation is materialised.
|
|
410
|
+
|
|
411
|
+
Yield order is a stable contract: pairs are yielded in the same
|
|
412
|
+
order ``symbols`` is iterated, with ``mgr.false`` symbols omitted.
|
|
413
|
+
Callers may walk the input alongside the yielded pairs with a two-
|
|
414
|
+
pointer pattern instead of materialising an intermediate dict.
|
|
415
|
+
|
|
416
|
+
Equivalence to ``step``: for any ``symbol``, the yielded
|
|
417
|
+
``succ_bdd`` equals ``sym_aut.step(mgr.var(state_var_name(source)),
|
|
418
|
+
symbol)``; symbols whose ``step`` result is ``mgr.false`` are not
|
|
419
|
+
yielded.
|
|
420
|
+
|
|
421
|
+
Parameters
|
|
422
|
+
----------
|
|
423
|
+
sym_aut
|
|
424
|
+
SymbolicAutomaton constructed with ``cache_transitions=True``.
|
|
425
|
+
source
|
|
426
|
+
Source state index in ``range(sym_aut.num_states)``.
|
|
427
|
+
alphabet
|
|
428
|
+
Boolean labeling alphabet over the same AP space as
|
|
429
|
+
``sym_aut.ap_registry``.
|
|
430
|
+
symbols
|
|
431
|
+
Symbols to enumerate. Iterated exactly once.
|
|
432
|
+
|
|
433
|
+
Raises
|
|
434
|
+
------
|
|
435
|
+
ValueError
|
|
436
|
+
If ``sym_aut`` was constructed with ``cache_transitions=False``
|
|
437
|
+
(no per-row pairs to cofactor), or if ``alphabet.predicates``
|
|
438
|
+
contains an AP not present in ``sym_aut.ap_registry``.
|
|
439
|
+
"""
|
|
440
|
+
if sym_aut.num_states > 0 and not sym_aut.transitions:
|
|
441
|
+
raise ValueError(
|
|
442
|
+
"successors_for_alphabet requires a populated transition cache; "
|
|
443
|
+
"reconstruct SymbolicAutomaton with cache_transitions=True."
|
|
444
|
+
)
|
|
445
|
+
unknown = set(alphabet.predicates) - set(sym_aut.ap_registry)
|
|
446
|
+
if unknown:
|
|
447
|
+
raise ValueError(f"alphabet has unknown predicates not in ap_registry: {sorted(map(repr, unknown))}")
|
|
448
|
+
|
|
449
|
+
mgr = sym_aut.manager
|
|
450
|
+
rows = sym_aut.transitions[source]
|
|
451
|
+
ap_registry = sym_aut.ap_registry
|
|
452
|
+
|
|
453
|
+
for symbol in symbols:
|
|
454
|
+
truths = alphabet.labeling(symbol)
|
|
455
|
+
ap_point: dict[str, Function] = {ap_registry[ap]: (mgr.true if ap in truths else mgr.false) for ap in ap_registry}
|
|
456
|
+
succ_acc: Function = mgr.false
|
|
457
|
+
for guard_bdd, succ_bdd in rows:
|
|
458
|
+
restricted = mgr.let(ap_point, guard_bdd)
|
|
459
|
+
if restricted == mgr.true:
|
|
460
|
+
succ_acc = mgr.apply("or", succ_acc, succ_bdd)
|
|
461
|
+
if succ_acc != mgr.false:
|
|
462
|
+
yield symbol, succ_acc
|
|
463
|
+
|
|
464
|
+
|
|
352
465
|
# ---------------------------------------------------------------------------
|
|
353
466
|
# Private helpers
|
|
354
467
|
# ---------------------------------------------------------------------------
|