morphata 1.0.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.
@@ -0,0 +1,169 @@
1
+ Metadata-Version: 2.3
2
+ Name: morphata
3
+ Version: 1.0.0
4
+ Summary: Flexible automata representations for regular and ω-regular languages.
5
+ Author: Anand Balakrishnan
6
+ Requires-Dist: typing-extensions
7
+ Requires-Dist: lark
8
+ Requires-Dist: logic-asts>=1.4.1
9
+ Requires-Dist: networkx
10
+ Requires-Dist: types-networkx
11
+ Requires-Dist: attrs>=25.4.0
12
+ Requires-Python: >=3.12
13
+ Description-Content-Type: text/markdown
14
+
15
+ # Morphata
16
+
17
+ Morphata is a Python library for constructing, manipulating, and translating
18
+ automata over regular and omega-regular languages.
19
+ It provides flexible graph-based representations for automata without committing
20
+ to any specific model checking or monitoring algorithm.
21
+
22
+ ## Features
23
+
24
+ - **Graph-based automata implementations**
25
+ - Nondeterministic Finite Automata (NFA) for finite words
26
+ - STREL automata for spatio-temporal specifications
27
+
28
+ - **HOA format parser**
29
+ - Extended HOA v1 format with finite-word acceptance support
30
+ - Standard acceptance conditions:
31
+ Buchi, co-Buchi, Rabin, Streett, Parity, Muller
32
+ - Extension:
33
+ `Final(n)` operator for finite-word automata (not in standard HOA v1)
34
+ - Validation and error reporting
35
+
36
+ - **Acceptance conditions**
37
+ - Expression algebra for omega-regular conditions
38
+ - Classical acceptance types (Buchi, generalized-Buchi, co-Buchi, Rabin,
39
+ Streett, Muller, Parity)
40
+ - Finite-word acceptance for regular languages
41
+
42
+ - **Pure structural interfaces**
43
+ - Base automaton interfaces without weighted semantics
44
+ - NetworkX-based graph representations
45
+ - Clean separation from quantitative monitoring (provided by automatix)
46
+
47
+ ## Architecture
48
+
49
+ Morphata serves as the foundation for the automatix library:
50
+
51
+ - **morphata**:
52
+ Graph-based automata, HOA parsing, acceptance conditions
53
+ - **automatix**:
54
+ Weighted automata over semirings (depends on morphata)
55
+ - **algebraic**:
56
+ Pure semiring algebra (independent)
57
+
58
+ This layered architecture allows morphata to be used independently for
59
+ structural automata operations, or as a foundation for weighted semantics.
60
+
61
+ ## Installation
62
+
63
+ Morphata is part of the automatix workspace.
64
+ Install using uv:
65
+
66
+ ```bash
67
+ uv pip install -e packages/morphata
68
+ ```
69
+
70
+ ## Quick Example
71
+
72
+ ```python
73
+ from morphata.automata import NFA
74
+ from logic_asts import base
75
+
76
+ # Create an NFA
77
+ nfa = NFA[str]()
78
+ nfa.add_location(0, initial=True)
79
+ nfa.add_location(1, final=True)
80
+
81
+ # Add transition with guard
82
+ guard_a = base.Variable("a")
83
+ nfa.add_transition(0, 1, guard_a)
84
+
85
+ # Use the automaton
86
+ accepting, next_state = nfa({"a"}, nfa.initial_state)
87
+ print(f"Accepting: {accepting}, Next state: {next_state}")
88
+ ```
89
+
90
+ ## HOA Parser Example
91
+
92
+ ### Standard HOA Format (Omega-Automata)
93
+
94
+ ```python
95
+ from morphata.hoa.parser import parse
96
+
97
+ hoa_string = """
98
+ HOA: v1
99
+ States: 2
100
+ Start: 0
101
+ acc-name: Buchi
102
+ Acceptance: 1 Inf(0)
103
+ AP: 1 "a"
104
+ --BODY--
105
+ State: 0
106
+ [0] 1
107
+ State: 1 {0}
108
+ [t] 1
109
+ --END--
110
+ """
111
+
112
+ automaton = parse(hoa_string)
113
+ print(f"Acceptance: {automaton.header.acc}")
114
+ ```
115
+
116
+ ### Extended Format: Finite-Word Acceptance
117
+
118
+ Morphata extends the HOA v1 format with a `Final(n)` operator for finite-word
119
+ automata.
120
+ This is **not part of the standard HOA specification** but provides a natural
121
+ way to express finite-word acceptance in the HOA syntax.
122
+
123
+ ```python
124
+ from morphata.hoa.parser import parse
125
+
126
+ finite_hoa = """
127
+ HOA: v1
128
+ States: 2
129
+ Start: 0
130
+ acc-name: Finite
131
+ Acceptance: 1 Final(0)
132
+ AP: 1 "a"
133
+ --BODY--
134
+ State: 0
135
+ [0] 1 {0}
136
+ [!0] 0
137
+ State: 1
138
+ [t] 1
139
+ --END--
140
+ """
141
+
142
+ automaton = parse(finite_hoa)
143
+ # Accepts finite words ending with 'a'
144
+ ```
145
+
146
+ **Note**:
147
+ The `Final(n)` operator semantics differ from `Fin(n)` and `Inf(n)`:
148
+ - `Inf(n)`:
149
+ Accept if acceptance set n is visited **infinitely often** (omega-regular)
150
+ - `Fin(n)`:
151
+ Accept if acceptance set n is visited **finitely often** (omega-regular)
152
+ - `Final(n)`:
153
+ Accept if the run **ends in** a state marked with acceptance set n (regular)
154
+
155
+ ## Development
156
+
157
+ Run tests:
158
+ ```bash
159
+ python -m pytest packages/morphata/tests/ -v
160
+ ```
161
+
162
+ Type checking:
163
+ ```bash
164
+ python -m mypy packages/morphata/src/morphata/ --strict
165
+ ```
166
+
167
+ ## License
168
+
169
+ Part of the automatix project.
@@ -0,0 +1,155 @@
1
+ # Morphata
2
+
3
+ Morphata is a Python library for constructing, manipulating, and translating
4
+ automata over regular and omega-regular languages.
5
+ It provides flexible graph-based representations for automata without committing
6
+ to any specific model checking or monitoring algorithm.
7
+
8
+ ## Features
9
+
10
+ - **Graph-based automata implementations**
11
+ - Nondeterministic Finite Automata (NFA) for finite words
12
+ - STREL automata for spatio-temporal specifications
13
+
14
+ - **HOA format parser**
15
+ - Extended HOA v1 format with finite-word acceptance support
16
+ - Standard acceptance conditions:
17
+ Buchi, co-Buchi, Rabin, Streett, Parity, Muller
18
+ - Extension:
19
+ `Final(n)` operator for finite-word automata (not in standard HOA v1)
20
+ - Validation and error reporting
21
+
22
+ - **Acceptance conditions**
23
+ - Expression algebra for omega-regular conditions
24
+ - Classical acceptance types (Buchi, generalized-Buchi, co-Buchi, Rabin,
25
+ Streett, Muller, Parity)
26
+ - Finite-word acceptance for regular languages
27
+
28
+ - **Pure structural interfaces**
29
+ - Base automaton interfaces without weighted semantics
30
+ - NetworkX-based graph representations
31
+ - Clean separation from quantitative monitoring (provided by automatix)
32
+
33
+ ## Architecture
34
+
35
+ Morphata serves as the foundation for the automatix library:
36
+
37
+ - **morphata**:
38
+ Graph-based automata, HOA parsing, acceptance conditions
39
+ - **automatix**:
40
+ Weighted automata over semirings (depends on morphata)
41
+ - **algebraic**:
42
+ Pure semiring algebra (independent)
43
+
44
+ This layered architecture allows morphata to be used independently for
45
+ structural automata operations, or as a foundation for weighted semantics.
46
+
47
+ ## Installation
48
+
49
+ Morphata is part of the automatix workspace.
50
+ Install using uv:
51
+
52
+ ```bash
53
+ uv pip install -e packages/morphata
54
+ ```
55
+
56
+ ## Quick Example
57
+
58
+ ```python
59
+ from morphata.automata import NFA
60
+ from logic_asts import base
61
+
62
+ # Create an NFA
63
+ nfa = NFA[str]()
64
+ nfa.add_location(0, initial=True)
65
+ nfa.add_location(1, final=True)
66
+
67
+ # Add transition with guard
68
+ guard_a = base.Variable("a")
69
+ nfa.add_transition(0, 1, guard_a)
70
+
71
+ # Use the automaton
72
+ accepting, next_state = nfa({"a"}, nfa.initial_state)
73
+ print(f"Accepting: {accepting}, Next state: {next_state}")
74
+ ```
75
+
76
+ ## HOA Parser Example
77
+
78
+ ### Standard HOA Format (Omega-Automata)
79
+
80
+ ```python
81
+ from morphata.hoa.parser import parse
82
+
83
+ hoa_string = """
84
+ HOA: v1
85
+ States: 2
86
+ Start: 0
87
+ acc-name: Buchi
88
+ Acceptance: 1 Inf(0)
89
+ AP: 1 "a"
90
+ --BODY--
91
+ State: 0
92
+ [0] 1
93
+ State: 1 {0}
94
+ [t] 1
95
+ --END--
96
+ """
97
+
98
+ automaton = parse(hoa_string)
99
+ print(f"Acceptance: {automaton.header.acc}")
100
+ ```
101
+
102
+ ### Extended Format: Finite-Word Acceptance
103
+
104
+ Morphata extends the HOA v1 format with a `Final(n)` operator for finite-word
105
+ automata.
106
+ This is **not part of the standard HOA specification** but provides a natural
107
+ way to express finite-word acceptance in the HOA syntax.
108
+
109
+ ```python
110
+ from morphata.hoa.parser import parse
111
+
112
+ finite_hoa = """
113
+ HOA: v1
114
+ States: 2
115
+ Start: 0
116
+ acc-name: Finite
117
+ Acceptance: 1 Final(0)
118
+ AP: 1 "a"
119
+ --BODY--
120
+ State: 0
121
+ [0] 1 {0}
122
+ [!0] 0
123
+ State: 1
124
+ [t] 1
125
+ --END--
126
+ """
127
+
128
+ automaton = parse(finite_hoa)
129
+ # Accepts finite words ending with 'a'
130
+ ```
131
+
132
+ **Note**:
133
+ The `Final(n)` operator semantics differ from `Fin(n)` and `Inf(n)`:
134
+ - `Inf(n)`:
135
+ Accept if acceptance set n is visited **infinitely often** (omega-regular)
136
+ - `Fin(n)`:
137
+ Accept if acceptance set n is visited **finitely often** (omega-regular)
138
+ - `Final(n)`:
139
+ Accept if the run **ends in** a state marked with acceptance set n (regular)
140
+
141
+ ## Development
142
+
143
+ Run tests:
144
+ ```bash
145
+ python -m pytest packages/morphata/tests/ -v
146
+ ```
147
+
148
+ Type checking:
149
+ ```bash
150
+ python -m mypy packages/morphata/src/morphata/ --strict
151
+ ```
152
+
153
+ ## License
154
+
155
+ Part of the automatix project.
@@ -0,0 +1,19 @@
1
+ [project]
2
+ name = "morphata"
3
+ version = "1.0.0"
4
+ description = "Flexible automata representations for regular and ω-regular languages."
5
+ authors = [{ name = "Anand Balakrishnan" }]
6
+ readme = "README.md"
7
+ requires-python = ">=3.12"
8
+ dependencies = [
9
+ "typing-extensions",
10
+ "lark",
11
+ "logic-asts>=1.4.1",
12
+ "networkx",
13
+ "types-networkx",
14
+ "attrs>=25.4.0",
15
+ ]
16
+
17
+ [build-system]
18
+ requires = ["uv_build>=0.9.22,<0.10.0"]
19
+ build-backend = "uv_build"
@@ -0,0 +1,78 @@
1
+ """Morphata: Flexible automata representations for regular and omega-regular languages.
2
+
3
+ This package provides:
4
+ - Pure structural automaton interfaces (Automaton, Domain, TransitionRelation)
5
+ - Acceptance condition expressions (morphata.acceptance)
6
+ - HOA format parser (morphata.hoa.parser)
7
+ - Example implementations (morphata.examples)
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from collections.abc import Hashable, Mapping
13
+ from collections.abc import Set as AbstractSet
14
+ from dataclasses import dataclass
15
+
16
+ from typing_extensions import override
17
+
18
+ from morphata.spec import AcceptanceCondition as AcceptanceCondition
19
+ from morphata.spec import (
20
+ AlternatingTransitions,
21
+ BoolExpr,
22
+ DeterministicTransitions,
23
+ NonDeterministicTransitions,
24
+ UniversalTransitions,
25
+ )
26
+ from morphata.spec import Automaton as Automaton
27
+ from morphata.spec import Domain as Domain
28
+ from morphata.spec import InitialState as InitialState
29
+ from morphata.spec import TransitionRelation as TransitionRelation
30
+
31
+
32
+ @dataclass
33
+ class DeterministicTransitionRelation[Q: Hashable, S: Hashable](DeterministicTransitions[Q, S]):
34
+ data: Mapping[Q, Mapping[S, Q]]
35
+
36
+ @override
37
+ def __call__(self, state: Q, symbol: S) -> Q:
38
+ return self.data[state][symbol]
39
+
40
+
41
+ @dataclass
42
+ class NonDeterministicTransitionRelation[Q: Hashable, S: Hashable](NonDeterministicTransitions[Q, S]):
43
+ data: Mapping[Q, Mapping[S, AbstractSet[Q]]]
44
+
45
+ @override
46
+ def __call__(self, state: Q, symbol: S) -> AbstractSet[Q]:
47
+ return self.data[state][symbol]
48
+
49
+
50
+ @dataclass
51
+ class UniversalTransitionRelation[Q: Hashable, S: Hashable](UniversalTransitions[Q, S]):
52
+ data: Mapping[Q, Mapping[S, AbstractSet[Q]]]
53
+
54
+ @override
55
+ def __call__(self, state: Q, symbol: S) -> AbstractSet[Q]:
56
+ return self.data[state][symbol]
57
+
58
+
59
+ @dataclass
60
+ class AlternatingTransitionRelation[Q: Hashable, S: Hashable](AlternatingTransitions[Q, S]):
61
+ data: Mapping[Q, Mapping[S, BoolExpr[Q]]]
62
+
63
+ @override
64
+ def __call__(self, state: Q, symbol: S) -> BoolExpr[Q]:
65
+ return self.data[state][symbol]
66
+
67
+
68
+ __all__ = [
69
+ "Domain",
70
+ "InitialState",
71
+ "AcceptanceCondition",
72
+ "TransitionRelation",
73
+ "DeterministicTransitions",
74
+ "NonDeterministicTransitions",
75
+ "UniversalTransitions",
76
+ "AlternatingTransitions",
77
+ "Automaton",
78
+ ]
@@ -0,0 +1,222 @@
1
+ """Concrete classes for acceptance conditions
2
+
3
+ Provides abstract and concrete acceptance condition classes for omega-automata
4
+ and finite-word automata.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import typing as ty
10
+ from collections.abc import Iterable
11
+
12
+ from attrs import frozen
13
+ from typing_extensions import overload
14
+
15
+ from morphata.spec import AcceptanceCondition, State
16
+
17
+
18
+ @overload
19
+ def acc_from_name(name: ty.Literal["Finite"], arg: Iterable[State], /) -> Finite[State]: ...
20
+
21
+
22
+ @overload
23
+ def acc_from_name(name: ty.Literal["Buchi"], arg: Iterable[State], /) -> Buchi[State]: ...
24
+
25
+
26
+ @overload
27
+ def acc_from_name(name: ty.Literal["co-Buchi"], arg: Iterable[State], /) -> CoBuchi[State]: ...
28
+
29
+
30
+ @overload
31
+ def acc_from_name(name: ty.Literal["generalized-Buchi"], /, *args: Iterable[State]) -> GeneralizedBuchi[State]: ...
32
+
33
+
34
+ @overload
35
+ def acc_from_name(name: ty.Literal["generalized-co-Buchi"], /, *args: Iterable[State]) -> GeneralizedCoBuchi[State]: ...
36
+
37
+
38
+ @overload
39
+ def acc_from_name(name: ty.Literal["Muller"], /, *args: Iterable[State]) -> Muller[State]: ...
40
+
41
+
42
+ @overload
43
+ def acc_from_name(name: ty.Literal["Streett"], /, *args: AccPair[State]) -> Streett[State]: ...
44
+
45
+
46
+ @overload
47
+ def acc_from_name(name: ty.Literal["Rabin"], /, *args: AccPair[State]) -> Rabin[State]: ...
48
+
49
+
50
+ def acc_from_name(name: str, /, *args) -> AcceptanceCondition[State]:
51
+ match name:
52
+ case "Finite":
53
+ assert len(args) == 1, "Finite acceptance condition requires 1 accepting set"
54
+ arg = args[0]
55
+ assert isinstance(arg, Iterable)
56
+ return Finite(frozenset(arg))
57
+ case "Buchi":
58
+ assert len(args) == 1, "Buchi acceptance condition requires 1 accepting set"
59
+ arg = args[0]
60
+ assert isinstance(arg, Iterable)
61
+ return Buchi(frozenset(arg))
62
+ case "generalized-Buchi":
63
+ assert len(args) >= 0 and all(isinstance(arg, Iterable) for arg in args), (
64
+ "Generalized Buchi condition needs a list of accepting sets"
65
+ )
66
+ return GeneralizedBuchi(tuple(frozenset(arg) for arg in args))
67
+ case "co-Buchi":
68
+ assert len(args) == 1, "CoBuchi acceptance condition requires 1 non-accepting set"
69
+ arg = args[0]
70
+ assert isinstance(arg, Iterable)
71
+ return CoBuchi(frozenset(arg))
72
+ case "generalized-co-Buchi":
73
+ assert len(args) >= 0 and all(isinstance(arg, Iterable) for arg in args), (
74
+ "Generalized CoBuchi condition needs a list of non-accepting sets"
75
+ )
76
+ return GeneralizedCoBuchi(tuple(frozenset(arg) for arg in args))
77
+ case "Streett":
78
+ assert len(args) >= 0 and all(isinstance(arg, tuple) and len(arg) == 2 for arg in args), (
79
+ "Streett condition needs a list of 2-tuples of rejecting (Fin) and accepting (Inf) sets"
80
+ )
81
+ return Streett(tuple(AccPair(*(frozenset(s) for s in arg)) for arg in args))
82
+ case "Rabin":
83
+ assert len(args) >= 0 and all(isinstance(arg, tuple) and len(arg) == 2 for arg in args), (
84
+ "Rabin condition needs a list of 2-tuples of rejecting (Fin) and accepting (Inf) sets"
85
+ )
86
+ return Rabin(tuple(AccPair(*(frozenset(s) for s in arg)) for arg in args))
87
+
88
+ case _:
89
+ raise ValueError(f"Unknown/unsupported named acceptance condition: {name} {args=}")
90
+
91
+
92
+ # @frozen
93
+ # class GenericCondition(AcceptanceCondition[State]):
94
+ # acceptance_sets: tuple[frozenset[State], ...]
95
+ # expr: AccExpr
96
+
97
+ # @override
98
+ # def __len__(self) -> int:
99
+ # return self.num_sets
100
+
101
+ # @override
102
+ # def to_expr(self) -> AccExpr:
103
+ # return self.expr
104
+
105
+
106
+ @frozen
107
+ class Finite(AcceptanceCondition[State]):
108
+ """Finite-word acceptance condition.
109
+
110
+ For finite automata, acceptance means reaching a final state. This uses
111
+ a single acceptance set to mark final states.
112
+ """
113
+
114
+ accepting: frozenset[State]
115
+
116
+ @classmethod
117
+ def is_omega_regular(cls) -> bool:
118
+ """Finite-word acceptance is not omega-regular."""
119
+ return False
120
+
121
+
122
+ @frozen
123
+ class Buchi(AcceptanceCondition[State]):
124
+ """Büchi condition: a run, r, is accepting iff inf(r) intersects with `accepting`"""
125
+
126
+ accepting: frozenset[State]
127
+
128
+ @classmethod
129
+ def is_omega_regular(cls) -> bool:
130
+ """Büchi acceptance is omega-regular."""
131
+ return True
132
+
133
+
134
+ @frozen
135
+ class GeneralizedBuchi(AcceptanceCondition[State]):
136
+ """Generalized Büchi condition: a run, r, is accepting iff inf(r) intersects with `accepting[i]` for some i"""
137
+
138
+ accepting: tuple[frozenset[State], ...]
139
+
140
+ @classmethod
141
+ def is_omega_regular(cls) -> bool:
142
+ """Generalized Büchi acceptance is omega-regular."""
143
+ return True
144
+
145
+
146
+ @frozen
147
+ class CoBuchi(AcceptanceCondition[State]):
148
+ """co-Büchi condition: a run, r, is accepting iff inf(r) does not intersect with `rejecting`"""
149
+
150
+ rejecting: frozenset[State]
151
+
152
+ @classmethod
153
+ def is_omega_regular(cls) -> bool:
154
+ """co-Büchi acceptance is omega-regular."""
155
+ return True
156
+
157
+
158
+ @frozen
159
+ class GeneralizedCoBuchi(AcceptanceCondition[State]):
160
+ """Generalized co-Büchi condition: a run, r, is accepting iff inf(r) does not intersect with `rejecting[i]` for some i"""
161
+
162
+ rejecting: tuple[frozenset[State], ...]
163
+
164
+ @classmethod
165
+ def is_omega_regular(cls) -> bool:
166
+ """Generalized co-Büchi acceptance is omega-regular."""
167
+ return True
168
+
169
+
170
+ class AccPair(ty.NamedTuple, ty.Generic[State]):
171
+ """Pair of accepting and rejecting state sets for Rabin/Streett conditions."""
172
+
173
+ rejecting: frozenset[State]
174
+ """States that must not appear infinitely often"""
175
+ accepting: frozenset[State]
176
+ """States that must appear infinitely often"""
177
+
178
+
179
+ @frozen
180
+ class Streett(AcceptanceCondition[State]):
181
+ """Streett condition: a run, r, is accpting iff _for all_ `i`, we have that inf(r) does not intersect with `pairs[i].rejecting` and does intersect with `pairs[i].accepting`"""
182
+
183
+ pairs: tuple[AccPair[State], ...]
184
+
185
+ @property
186
+ def index(self) -> int:
187
+ """Number of pairs in this condition."""
188
+ return len(self.pairs)
189
+
190
+ @classmethod
191
+ def is_omega_regular(cls) -> bool:
192
+ """Streett acceptance is omega-regular."""
193
+ return True
194
+
195
+
196
+ @frozen
197
+ class Rabin(AcceptanceCondition[State]):
198
+ """Rabin condition: a run, r, is accpting iff _for some_ `i`, we have that inf(r) does not intersect with `pairs[i].rejecting` and does intersect with `pairs[i].accepting`"""
199
+
200
+ pairs: tuple[AccPair[State], ...]
201
+
202
+ @property
203
+ def index(self) -> int:
204
+ """Number of pairs in this condition."""
205
+ return len(self.pairs)
206
+
207
+ @classmethod
208
+ def is_omega_regular(cls) -> bool:
209
+ """Rabin acceptance is omega-regular."""
210
+ return True
211
+
212
+
213
+ @frozen
214
+ class Muller(AcceptanceCondition[State]):
215
+ """Muller condition: a run, r, is accepting iff for some `i`, we have that inf(r) is exactly `sets[i]`"""
216
+
217
+ sets: tuple[frozenset[State], ...]
218
+
219
+ @classmethod
220
+ def is_omega_regular(cls) -> bool:
221
+ """Muller acceptance is omega-regular."""
222
+ return True
File without changes
File without changes