flakepl 0.3.1__py3-none-any.whl
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.
- flake/__init__.py +102 -0
- flake/actions.py +41 -0
- flake/agent.py +107 -0
- flake/dynamic.py +449 -0
- flake/epistemic.py +2068 -0
- flake/execution.py +892 -0
- flake/explorer.py +196 -0
- flake/formula.py +173 -0
- flake/observation.py +500 -0
- flake/process.py +342 -0
- flake/propositions.py +46 -0
- flake/py.typed +0 -0
- flake/runtime.py +370 -0
- flake/semantics.py +222 -0
- flake/sos.py +97 -0
- flake/temporal.py +109 -0
- flake/verify.py +1081 -0
- flakepl/__init__.py +8 -0
- flakepl/_api.py +29 -0
- flakepl/agent.py +9 -0
- flakepl/explorer.py +19 -0
- flakepl/process.py +31 -0
- flakepl/py.typed +0 -0
- flakepl/runtime.py +20 -0
- flakepl/semantics.py +7 -0
- flakepl/sos.py +5 -0
- flakepl/verify.py +29 -0
- flakepl-0.3.1.dist-info/METADATA +126 -0
- flakepl-0.3.1.dist-info/RECORD +31 -0
- flakepl-0.3.1.dist-info/WHEEL +5 -0
- flakepl-0.3.1.dist-info/top_level.txt +2 -0
flake/explorer.py
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections import deque
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Callable, Iterable
|
|
6
|
+
|
|
7
|
+
from .runtime import (
|
|
8
|
+
Configuration,
|
|
9
|
+
SystemTransition,
|
|
10
|
+
system_transitions,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
TransitionProvider = Callable[
|
|
15
|
+
[Configuration],
|
|
16
|
+
Iterable[SystemTransition],
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True, slots=True)
|
|
21
|
+
class StateSpace:
|
|
22
|
+
"""
|
|
23
|
+
Finite reachable state space.
|
|
24
|
+
|
|
25
|
+
initials:
|
|
26
|
+
Initial configurations.
|
|
27
|
+
|
|
28
|
+
states:
|
|
29
|
+
All discovered configurations.
|
|
30
|
+
|
|
31
|
+
transitions:
|
|
32
|
+
Reachable transitions between configurations.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
initials: tuple[Configuration, ...]
|
|
36
|
+
states: tuple[Configuration, ...]
|
|
37
|
+
transitions: tuple[SystemTransition, ...]
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def initial(self) -> Configuration:
|
|
41
|
+
"""
|
|
42
|
+
Convenience accessor for a single-initial state space.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
if len(self.initials) != 1:
|
|
46
|
+
raise ValueError(
|
|
47
|
+
"StateSpace.initial requires exactly one "
|
|
48
|
+
"initial configuration"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
return self.initials[0]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _explore_many(
|
|
55
|
+
initials: Iterable[Configuration],
|
|
56
|
+
transition_provider: TransitionProvider,
|
|
57
|
+
max_states: int | None = None,
|
|
58
|
+
) -> StateSpace:
|
|
59
|
+
initial_tuple = tuple(
|
|
60
|
+
initials
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
if not initial_tuple:
|
|
64
|
+
raise ValueError(
|
|
65
|
+
"At least one initial configuration is required"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
if max_states is not None:
|
|
69
|
+
if max_states <= 0:
|
|
70
|
+
raise ValueError(
|
|
71
|
+
"max_states must be positive"
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
visited: set[Configuration] = set(
|
|
75
|
+
initial_tuple
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
queue: deque[Configuration] = deque(
|
|
79
|
+
initial_tuple
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
transitions_result: list[
|
|
83
|
+
SystemTransition
|
|
84
|
+
] = []
|
|
85
|
+
|
|
86
|
+
while queue:
|
|
87
|
+
current = queue.popleft()
|
|
88
|
+
|
|
89
|
+
for transition in transition_provider(
|
|
90
|
+
current
|
|
91
|
+
):
|
|
92
|
+
transitions_result.append(
|
|
93
|
+
transition
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
target = transition.target
|
|
97
|
+
|
|
98
|
+
if target in visited:
|
|
99
|
+
continue
|
|
100
|
+
|
|
101
|
+
if (
|
|
102
|
+
max_states is not None
|
|
103
|
+
and len(visited) >= max_states
|
|
104
|
+
):
|
|
105
|
+
continue
|
|
106
|
+
|
|
107
|
+
visited.add(
|
|
108
|
+
target
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
queue.append(
|
|
112
|
+
target
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
states = tuple(
|
|
116
|
+
visited
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
return StateSpace(
|
|
120
|
+
initials=initial_tuple,
|
|
121
|
+
states=states,
|
|
122
|
+
transitions=tuple(
|
|
123
|
+
transitions_result
|
|
124
|
+
),
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def explore(
|
|
129
|
+
initial: Configuration,
|
|
130
|
+
max_states: int | None = None,
|
|
131
|
+
) -> StateSpace:
|
|
132
|
+
"""
|
|
133
|
+
Explore using the default runtime semantics.
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
return _explore_many(
|
|
137
|
+
(initial,),
|
|
138
|
+
system_transitions,
|
|
139
|
+
max_states=max_states,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def explore_many(
|
|
144
|
+
initials: Iterable[Configuration],
|
|
145
|
+
max_states: int | None = None,
|
|
146
|
+
) -> StateSpace:
|
|
147
|
+
"""
|
|
148
|
+
Explore multiple initial configurations using the
|
|
149
|
+
default runtime semantics.
|
|
150
|
+
"""
|
|
151
|
+
|
|
152
|
+
return _explore_many(
|
|
153
|
+
initials,
|
|
154
|
+
system_transitions,
|
|
155
|
+
max_states=max_states,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def explore_with(
|
|
160
|
+
initial: Configuration,
|
|
161
|
+
transition_provider: TransitionProvider,
|
|
162
|
+
max_states: int | None = None,
|
|
163
|
+
) -> StateSpace:
|
|
164
|
+
"""
|
|
165
|
+
Explore using explicitly supplied operational semantics.
|
|
166
|
+
|
|
167
|
+
Example:
|
|
168
|
+
|
|
169
|
+
space = explore_with(
|
|
170
|
+
initial,
|
|
171
|
+
synchronous_system_transitions,
|
|
172
|
+
)
|
|
173
|
+
"""
|
|
174
|
+
|
|
175
|
+
return _explore_many(
|
|
176
|
+
(initial,),
|
|
177
|
+
transition_provider,
|
|
178
|
+
max_states=max_states,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def explore_many_with(
|
|
183
|
+
initials: Iterable[Configuration],
|
|
184
|
+
transition_provider: TransitionProvider,
|
|
185
|
+
max_states: int | None = None,
|
|
186
|
+
) -> StateSpace:
|
|
187
|
+
"""
|
|
188
|
+
Explore multiple initial configurations using explicitly
|
|
189
|
+
supplied operational semantics.
|
|
190
|
+
"""
|
|
191
|
+
|
|
192
|
+
return _explore_many(
|
|
193
|
+
initials,
|
|
194
|
+
transition_provider,
|
|
195
|
+
max_states=max_states,
|
|
196
|
+
)
|
flake/formula.py
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Formula:
|
|
7
|
+
"""
|
|
8
|
+
Base class for all FlakePL formulas.
|
|
9
|
+
|
|
10
|
+
Formula objects form an immutable abstract syntax tree.
|
|
11
|
+
|
|
12
|
+
Boolean composition:
|
|
13
|
+
|
|
14
|
+
phi & psi
|
|
15
|
+
phi | psi
|
|
16
|
+
~phi
|
|
17
|
+
|
|
18
|
+
Derived operators:
|
|
19
|
+
|
|
20
|
+
phi.implies(psi)
|
|
21
|
+
phi.iff(psi)
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __and__(
|
|
25
|
+
self,
|
|
26
|
+
other: "Formula",
|
|
27
|
+
) -> "Formula":
|
|
28
|
+
return And(
|
|
29
|
+
self,
|
|
30
|
+
other,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
def __or__(
|
|
34
|
+
self,
|
|
35
|
+
other: "Formula",
|
|
36
|
+
) -> "Formula":
|
|
37
|
+
return Or(
|
|
38
|
+
self,
|
|
39
|
+
other,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
def __invert__(self) -> "Formula":
|
|
43
|
+
return Not(
|
|
44
|
+
self
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
def implies(
|
|
48
|
+
self,
|
|
49
|
+
other: "Formula",
|
|
50
|
+
) -> "Formula":
|
|
51
|
+
"""
|
|
52
|
+
Logical implication:
|
|
53
|
+
|
|
54
|
+
φ → ψ
|
|
55
|
+
|
|
56
|
+
represented as:
|
|
57
|
+
|
|
58
|
+
¬φ ∨ ψ
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
return Or(
|
|
62
|
+
Not(self),
|
|
63
|
+
other,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
def iff(
|
|
67
|
+
self,
|
|
68
|
+
other: "Formula",
|
|
69
|
+
) -> "Formula":
|
|
70
|
+
"""
|
|
71
|
+
Logical equivalence:
|
|
72
|
+
|
|
73
|
+
φ ↔ ψ
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
return And(
|
|
77
|
+
self.implies(other),
|
|
78
|
+
other.implies(self),
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def implies(
|
|
83
|
+
left: Formula,
|
|
84
|
+
right: Formula,
|
|
85
|
+
) -> Formula:
|
|
86
|
+
"""
|
|
87
|
+
Functional form of implication.
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
return left.implies(
|
|
91
|
+
right
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def iff(
|
|
96
|
+
left: Formula,
|
|
97
|
+
right: Formula,
|
|
98
|
+
) -> Formula:
|
|
99
|
+
"""
|
|
100
|
+
Functional form of logical equivalence.
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
return left.iff(
|
|
104
|
+
right
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@dataclass(frozen=True, slots=True)
|
|
109
|
+
class Atom(Formula):
|
|
110
|
+
"""
|
|
111
|
+
Atomic proposition.
|
|
112
|
+
|
|
113
|
+
Example:
|
|
114
|
+
|
|
115
|
+
Atom("ready")
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
name: str
|
|
119
|
+
|
|
120
|
+
def __post_init__(self) -> None:
|
|
121
|
+
if not self.name.strip():
|
|
122
|
+
raise ValueError(
|
|
123
|
+
"Atom name must not be empty"
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
def __repr__(self) -> str:
|
|
127
|
+
return self.name
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@dataclass(frozen=True, slots=True)
|
|
131
|
+
class Not(Formula):
|
|
132
|
+
"""
|
|
133
|
+
Boolean negation.
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
operand: Formula
|
|
137
|
+
|
|
138
|
+
def __repr__(self) -> str:
|
|
139
|
+
return (
|
|
140
|
+
f"¬({self.operand!r})"
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@dataclass(frozen=True, slots=True)
|
|
145
|
+
class And(Formula):
|
|
146
|
+
"""
|
|
147
|
+
Boolean conjunction.
|
|
148
|
+
"""
|
|
149
|
+
|
|
150
|
+
left: Formula
|
|
151
|
+
right: Formula
|
|
152
|
+
|
|
153
|
+
def __repr__(self) -> str:
|
|
154
|
+
return (
|
|
155
|
+
f"({self.left!r} ∧ "
|
|
156
|
+
f"{self.right!r})"
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@dataclass(frozen=True, slots=True)
|
|
161
|
+
class Or(Formula):
|
|
162
|
+
"""
|
|
163
|
+
Boolean disjunction.
|
|
164
|
+
"""
|
|
165
|
+
|
|
166
|
+
left: Formula
|
|
167
|
+
right: Formula
|
|
168
|
+
|
|
169
|
+
def __repr__(self) -> str:
|
|
170
|
+
return (
|
|
171
|
+
f"({self.left!r} ∨ "
|
|
172
|
+
f"{self.right!r})"
|
|
173
|
+
)
|