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/runtime.py
ADDED
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from .actions import Action, Receive, Send, Tau
|
|
7
|
+
from .agent import Agent
|
|
8
|
+
from .process import Process
|
|
9
|
+
from .sos import transitions
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True, slots=True)
|
|
13
|
+
class GlobalState:
|
|
14
|
+
"""
|
|
15
|
+
Environment-level state.
|
|
16
|
+
|
|
17
|
+
Agent-local state is stored in Agent.state.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
values: tuple[tuple[str, Any], ...] = ()
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def from_dict(
|
|
24
|
+
cls,
|
|
25
|
+
values: dict[str, Any],
|
|
26
|
+
) -> GlobalState:
|
|
27
|
+
|
|
28
|
+
return cls(
|
|
29
|
+
values=tuple(
|
|
30
|
+
sorted(
|
|
31
|
+
values.items(),
|
|
32
|
+
key=lambda item: item[0],
|
|
33
|
+
)
|
|
34
|
+
)
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
def to_dict(self) -> dict[str, Any]:
|
|
38
|
+
return dict(
|
|
39
|
+
self.values
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
def get(
|
|
43
|
+
self,
|
|
44
|
+
name: str,
|
|
45
|
+
default: Any = None,
|
|
46
|
+
) -> Any:
|
|
47
|
+
|
|
48
|
+
return dict(
|
|
49
|
+
self.values
|
|
50
|
+
).get(
|
|
51
|
+
name,
|
|
52
|
+
default,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
def with_value(
|
|
56
|
+
self,
|
|
57
|
+
name: str,
|
|
58
|
+
value: Any,
|
|
59
|
+
) -> GlobalState:
|
|
60
|
+
|
|
61
|
+
values = self.to_dict()
|
|
62
|
+
|
|
63
|
+
values[name] = value
|
|
64
|
+
|
|
65
|
+
return GlobalState.from_dict(
|
|
66
|
+
values
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass(frozen=True, slots=True)
|
|
71
|
+
class Configuration:
|
|
72
|
+
"""
|
|
73
|
+
Global system configuration.
|
|
74
|
+
|
|
75
|
+
Conceptually:
|
|
76
|
+
|
|
77
|
+
C = (s, A)
|
|
78
|
+
|
|
79
|
+
where:
|
|
80
|
+
|
|
81
|
+
s = global state
|
|
82
|
+
A = tuple of agents
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
state: GlobalState
|
|
86
|
+
agents: tuple[Agent, ...]
|
|
87
|
+
|
|
88
|
+
def __post_init__(self) -> None:
|
|
89
|
+
|
|
90
|
+
names = [
|
|
91
|
+
agent.name
|
|
92
|
+
for agent in self.agents
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
if len(names) != len(set(names)):
|
|
96
|
+
raise ValueError(
|
|
97
|
+
"Configuration contains "
|
|
98
|
+
"duplicate agent names"
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
def agent(
|
|
102
|
+
self,
|
|
103
|
+
name: str,
|
|
104
|
+
) -> Agent:
|
|
105
|
+
|
|
106
|
+
for agent in self.agents:
|
|
107
|
+
|
|
108
|
+
if agent.name == name:
|
|
109
|
+
return agent
|
|
110
|
+
|
|
111
|
+
raise KeyError(
|
|
112
|
+
f"Unknown agent: {name}"
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def processes(
|
|
117
|
+
self,
|
|
118
|
+
) -> tuple[Process, ...]:
|
|
119
|
+
|
|
120
|
+
return tuple(
|
|
121
|
+
agent.process
|
|
122
|
+
for agent in self.agents
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
def with_agent(
|
|
126
|
+
self,
|
|
127
|
+
updated: Agent,
|
|
128
|
+
) -> Configuration:
|
|
129
|
+
|
|
130
|
+
if not any(
|
|
131
|
+
agent.name == updated.name
|
|
132
|
+
for agent in self.agents
|
|
133
|
+
):
|
|
134
|
+
raise KeyError(
|
|
135
|
+
f"Unknown agent: "
|
|
136
|
+
f"{updated.name}"
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
updated_agents = tuple(
|
|
140
|
+
updated
|
|
141
|
+
if agent.name == updated.name
|
|
142
|
+
else agent
|
|
143
|
+
for agent in self.agents
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
return Configuration(
|
|
147
|
+
state=self.state,
|
|
148
|
+
agents=updated_agents,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@dataclass(frozen=True, slots=True)
|
|
153
|
+
class SystemTransition:
|
|
154
|
+
"""
|
|
155
|
+
Global transition:
|
|
156
|
+
|
|
157
|
+
C --action--> C'
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
source: Configuration
|
|
161
|
+
action: Action
|
|
162
|
+
target: Configuration
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _replace_two_agents(
|
|
166
|
+
agents: tuple[Agent, ...],
|
|
167
|
+
first: Agent,
|
|
168
|
+
second: Agent,
|
|
169
|
+
) -> tuple[Agent, ...]:
|
|
170
|
+
|
|
171
|
+
if first.name == second.name:
|
|
172
|
+
raise ValueError(
|
|
173
|
+
"Cannot replace the same agent twice"
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
result: list[Agent] = []
|
|
177
|
+
|
|
178
|
+
found_first = False
|
|
179
|
+
found_second = False
|
|
180
|
+
|
|
181
|
+
for agent in agents:
|
|
182
|
+
|
|
183
|
+
if agent.name == first.name:
|
|
184
|
+
|
|
185
|
+
result.append(first)
|
|
186
|
+
found_first = True
|
|
187
|
+
|
|
188
|
+
elif agent.name == second.name:
|
|
189
|
+
|
|
190
|
+
result.append(second)
|
|
191
|
+
found_second = True
|
|
192
|
+
|
|
193
|
+
else:
|
|
194
|
+
|
|
195
|
+
result.append(agent)
|
|
196
|
+
|
|
197
|
+
if not found_first:
|
|
198
|
+
raise KeyError(
|
|
199
|
+
f"Unknown agent: {first.name}"
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
if not found_second:
|
|
203
|
+
raise KeyError(
|
|
204
|
+
f"Unknown agent: {second.name}"
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
return tuple(result)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def system_transitions(
|
|
211
|
+
configuration: Configuration,
|
|
212
|
+
) -> tuple[SystemTransition, ...]:
|
|
213
|
+
"""
|
|
214
|
+
Generate all system-level transitions.
|
|
215
|
+
|
|
216
|
+
Includes:
|
|
217
|
+
|
|
218
|
+
1. Independent agent actions.
|
|
219
|
+
2. Synchronous communication.
|
|
220
|
+
|
|
221
|
+
Communication:
|
|
222
|
+
|
|
223
|
+
send(c, v) | recv(c, x)
|
|
224
|
+
---------------------
|
|
225
|
+
τ
|
|
226
|
+
|
|
227
|
+
The sender advances its process.
|
|
228
|
+
|
|
229
|
+
The receiver advances its process and stores
|
|
230
|
+
v in local variable x.
|
|
231
|
+
"""
|
|
232
|
+
|
|
233
|
+
result: list[SystemTransition] = []
|
|
234
|
+
|
|
235
|
+
agents = configuration.agents
|
|
236
|
+
|
|
237
|
+
# ========================================================
|
|
238
|
+
# Independent actions
|
|
239
|
+
# ========================================================
|
|
240
|
+
|
|
241
|
+
for index, agent in enumerate(agents):
|
|
242
|
+
|
|
243
|
+
for transition in transitions(
|
|
244
|
+
agent.process
|
|
245
|
+
):
|
|
246
|
+
|
|
247
|
+
updated_agent = (
|
|
248
|
+
agent.with_process(
|
|
249
|
+
transition.target
|
|
250
|
+
)
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
updated_agents = tuple(
|
|
254
|
+
updated_agent
|
|
255
|
+
if i == index
|
|
256
|
+
else other
|
|
257
|
+
for i, other in enumerate(
|
|
258
|
+
agents
|
|
259
|
+
)
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
target = Configuration(
|
|
263
|
+
state=configuration.state,
|
|
264
|
+
agents=updated_agents,
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
result.append(
|
|
268
|
+
SystemTransition(
|
|
269
|
+
source=configuration,
|
|
270
|
+
action=transition.action,
|
|
271
|
+
target=target,
|
|
272
|
+
)
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
# ========================================================
|
|
276
|
+
# Synchronous communication
|
|
277
|
+
# ========================================================
|
|
278
|
+
|
|
279
|
+
for sender_index, sender in enumerate(
|
|
280
|
+
agents
|
|
281
|
+
):
|
|
282
|
+
|
|
283
|
+
sender_transitions = transitions(
|
|
284
|
+
sender.process
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
for sender_transition in (
|
|
288
|
+
sender_transitions
|
|
289
|
+
):
|
|
290
|
+
|
|
291
|
+
if not isinstance(
|
|
292
|
+
sender_transition.action,
|
|
293
|
+
Send,
|
|
294
|
+
):
|
|
295
|
+
continue
|
|
296
|
+
|
|
297
|
+
for receiver_index, receiver in enumerate(
|
|
298
|
+
agents
|
|
299
|
+
):
|
|
300
|
+
|
|
301
|
+
if sender_index == receiver_index:
|
|
302
|
+
continue
|
|
303
|
+
|
|
304
|
+
receiver_transitions = transitions(
|
|
305
|
+
receiver.process
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
for receiver_transition in (
|
|
309
|
+
receiver_transitions
|
|
310
|
+
):
|
|
311
|
+
|
|
312
|
+
if not isinstance(
|
|
313
|
+
receiver_transition.action,
|
|
314
|
+
Receive,
|
|
315
|
+
):
|
|
316
|
+
continue
|
|
317
|
+
|
|
318
|
+
send_action = (
|
|
319
|
+
sender_transition.action
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
receive_action = (
|
|
323
|
+
receiver_transition.action
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
if (
|
|
327
|
+
send_action.channel
|
|
328
|
+
!= receive_action.channel
|
|
329
|
+
):
|
|
330
|
+
continue
|
|
331
|
+
|
|
332
|
+
updated_sender = (
|
|
333
|
+
sender.with_process(
|
|
334
|
+
sender_transition.target
|
|
335
|
+
)
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
updated_receiver = (
|
|
339
|
+
receiver
|
|
340
|
+
.with_process(
|
|
341
|
+
receiver_transition.target
|
|
342
|
+
)
|
|
343
|
+
.with_state(
|
|
344
|
+
receive_action.variable,
|
|
345
|
+
send_action.value,
|
|
346
|
+
)
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
updated_agents = (
|
|
350
|
+
_replace_two_agents(
|
|
351
|
+
agents,
|
|
352
|
+
updated_sender,
|
|
353
|
+
updated_receiver,
|
|
354
|
+
)
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
target = Configuration(
|
|
358
|
+
state=configuration.state,
|
|
359
|
+
agents=updated_agents,
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
result.append(
|
|
363
|
+
SystemTransition(
|
|
364
|
+
source=configuration,
|
|
365
|
+
action=Tau(),
|
|
366
|
+
target=target,
|
|
367
|
+
)
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
return tuple(result)
|
flake/semantics.py
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Iterable
|
|
4
|
+
|
|
5
|
+
from .actions import (
|
|
6
|
+
Receive,
|
|
7
|
+
Send,
|
|
8
|
+
Tau,
|
|
9
|
+
)
|
|
10
|
+
from .agent import Agent
|
|
11
|
+
from .runtime import (
|
|
12
|
+
Configuration,
|
|
13
|
+
SystemTransition,
|
|
14
|
+
)
|
|
15
|
+
from .sos import transitions
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _replace_agents(
|
|
19
|
+
configuration: Configuration,
|
|
20
|
+
updates: Iterable[Agent],
|
|
21
|
+
) -> Configuration:
|
|
22
|
+
"""
|
|
23
|
+
Return a configuration with the supplied agents replaced.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
result = configuration
|
|
27
|
+
|
|
28
|
+
for agent in updates:
|
|
29
|
+
result = result.with_agent(agent)
|
|
30
|
+
|
|
31
|
+
return result
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def synchronous_system_transitions(
|
|
35
|
+
configuration: Configuration,
|
|
36
|
+
) -> tuple[SystemTransition, ...]:
|
|
37
|
+
"""
|
|
38
|
+
Strict synchronous process semantics.
|
|
39
|
+
|
|
40
|
+
Tau transitions are executed independently.
|
|
41
|
+
|
|
42
|
+
Send and Receive transitions never execute independently.
|
|
43
|
+
They must synchronize on the same channel.
|
|
44
|
+
|
|
45
|
+
Therefore:
|
|
46
|
+
|
|
47
|
+
send(c, v) | recv(c, x)
|
|
48
|
+
|
|
49
|
+
becomes one transition where:
|
|
50
|
+
|
|
51
|
+
receiver.x = v
|
|
52
|
+
|
|
53
|
+
while the two processes advance simultaneously.
|
|
54
|
+
|
|
55
|
+
This semantics is useful for modeling protocols where
|
|
56
|
+
communication is synchronous, such as the examples in
|
|
57
|
+
the FlakePL benchmark suite.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
result: list[SystemTransition] = []
|
|
61
|
+
|
|
62
|
+
agents = configuration.agents
|
|
63
|
+
|
|
64
|
+
# ------------------------------------------------------------------
|
|
65
|
+
# Independent internal transitions.
|
|
66
|
+
# ------------------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
for agent in agents:
|
|
69
|
+
for transition in transitions(
|
|
70
|
+
agent.process
|
|
71
|
+
):
|
|
72
|
+
if not isinstance(
|
|
73
|
+
transition.action,
|
|
74
|
+
Tau,
|
|
75
|
+
):
|
|
76
|
+
continue
|
|
77
|
+
|
|
78
|
+
updated_agent = agent.with_process(
|
|
79
|
+
transition.target
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
target = configuration.with_agent(
|
|
83
|
+
updated_agent
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
result.append(
|
|
87
|
+
SystemTransition(
|
|
88
|
+
source=configuration,
|
|
89
|
+
action=transition.action,
|
|
90
|
+
target=target,
|
|
91
|
+
)
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# ------------------------------------------------------------------
|
|
95
|
+
# Synchronous communication.
|
|
96
|
+
#
|
|
97
|
+
# We inspect each pair only once.
|
|
98
|
+
# ------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
for left_index in range(
|
|
101
|
+
len(agents)
|
|
102
|
+
):
|
|
103
|
+
for right_index in range(
|
|
104
|
+
left_index + 1,
|
|
105
|
+
len(agents),
|
|
106
|
+
):
|
|
107
|
+
left_agent = agents[left_index]
|
|
108
|
+
right_agent = agents[right_index]
|
|
109
|
+
|
|
110
|
+
left_transitions = transitions(
|
|
111
|
+
left_agent.process
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
right_transitions = transitions(
|
|
115
|
+
right_agent.process
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
for left_transition in left_transitions:
|
|
119
|
+
left_action = left_transition.action
|
|
120
|
+
|
|
121
|
+
for right_transition in right_transitions:
|
|
122
|
+
right_action = right_transition.action
|
|
123
|
+
|
|
124
|
+
# --------------------------------------------------
|
|
125
|
+
# left sends, right receives
|
|
126
|
+
# --------------------------------------------------
|
|
127
|
+
|
|
128
|
+
if (
|
|
129
|
+
isinstance(
|
|
130
|
+
left_action,
|
|
131
|
+
Send,
|
|
132
|
+
)
|
|
133
|
+
and isinstance(
|
|
134
|
+
right_action,
|
|
135
|
+
Receive,
|
|
136
|
+
)
|
|
137
|
+
and left_action.channel
|
|
138
|
+
== right_action.channel
|
|
139
|
+
):
|
|
140
|
+
updated_left = (
|
|
141
|
+
left_agent.with_process(
|
|
142
|
+
left_transition.target
|
|
143
|
+
)
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
updated_right = (
|
|
147
|
+
right_agent
|
|
148
|
+
.with_process(
|
|
149
|
+
right_transition.target
|
|
150
|
+
)
|
|
151
|
+
.with_state(
|
|
152
|
+
right_action.variable,
|
|
153
|
+
left_action.value,
|
|
154
|
+
)
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
target = _replace_agents(
|
|
158
|
+
configuration,
|
|
159
|
+
(
|
|
160
|
+
updated_left,
|
|
161
|
+
updated_right,
|
|
162
|
+
),
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
result.append(
|
|
166
|
+
SystemTransition(
|
|
167
|
+
source=configuration,
|
|
168
|
+
action=Tau(),
|
|
169
|
+
target=target,
|
|
170
|
+
)
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
# --------------------------------------------------
|
|
174
|
+
# left receives, right sends
|
|
175
|
+
# --------------------------------------------------
|
|
176
|
+
|
|
177
|
+
elif (
|
|
178
|
+
isinstance(
|
|
179
|
+
left_action,
|
|
180
|
+
Receive,
|
|
181
|
+
)
|
|
182
|
+
and isinstance(
|
|
183
|
+
right_action,
|
|
184
|
+
Send,
|
|
185
|
+
)
|
|
186
|
+
and left_action.channel
|
|
187
|
+
== right_action.channel
|
|
188
|
+
):
|
|
189
|
+
updated_left = (
|
|
190
|
+
left_agent
|
|
191
|
+
.with_process(
|
|
192
|
+
left_transition.target
|
|
193
|
+
)
|
|
194
|
+
.with_state(
|
|
195
|
+
left_action.variable,
|
|
196
|
+
right_action.value,
|
|
197
|
+
)
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
updated_right = (
|
|
201
|
+
right_agent.with_process(
|
|
202
|
+
right_transition.target
|
|
203
|
+
)
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
target = _replace_agents(
|
|
207
|
+
configuration,
|
|
208
|
+
(
|
|
209
|
+
updated_left,
|
|
210
|
+
updated_right,
|
|
211
|
+
),
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
result.append(
|
|
215
|
+
SystemTransition(
|
|
216
|
+
source=configuration,
|
|
217
|
+
action=Tau(),
|
|
218
|
+
target=target,
|
|
219
|
+
)
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
return tuple(result)
|
flake/sos.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from .actions import Action, Receive, Send, Tau
|
|
6
|
+
from .process import Choice, Nil, Parallel, Prefix, Process
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True, slots=True)
|
|
10
|
+
class Transition:
|
|
11
|
+
"""One process-level operational transition: P --action--> P'."""
|
|
12
|
+
|
|
13
|
+
action: Action
|
|
14
|
+
target: Process
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def transitions(process: Process) -> tuple[Transition, ...]:
|
|
18
|
+
"""Generate all one-step SOS transitions for a process.
|
|
19
|
+
|
|
20
|
+
The MVP supports prefix, nondeterministic choice, parallel composition,
|
|
21
|
+
and synchronous communication between matching send/receive prefixes.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
if isinstance(process, Nil):
|
|
25
|
+
return ()
|
|
26
|
+
|
|
27
|
+
if isinstance(process, Prefix):
|
|
28
|
+
return (Transition(process.action, process.continuation),)
|
|
29
|
+
|
|
30
|
+
if isinstance(process, Choice):
|
|
31
|
+
result: list[Transition] = []
|
|
32
|
+
for option in process.options:
|
|
33
|
+
result.extend(transitions(option))
|
|
34
|
+
return tuple(result)
|
|
35
|
+
|
|
36
|
+
if isinstance(process, Parallel):
|
|
37
|
+
result: list[Transition] = []
|
|
38
|
+
processes = process.processes
|
|
39
|
+
|
|
40
|
+
# Independent actions.
|
|
41
|
+
for index, current in enumerate(processes):
|
|
42
|
+
for transition in transitions(current):
|
|
43
|
+
updated = list(processes)
|
|
44
|
+
updated[index] = transition.target
|
|
45
|
+
result.append(
|
|
46
|
+
Transition(
|
|
47
|
+
transition.action,
|
|
48
|
+
Parallel(tuple(updated)),
|
|
49
|
+
)
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# Pairwise synchronous communication.
|
|
53
|
+
for left_index in range(len(processes)):
|
|
54
|
+
for right_index in range(left_index + 1, len(processes)):
|
|
55
|
+
left = processes[left_index]
|
|
56
|
+
right = processes[right_index]
|
|
57
|
+
|
|
58
|
+
left_transitions = transitions(left)
|
|
59
|
+
right_transitions = transitions(right)
|
|
60
|
+
|
|
61
|
+
for left_transition in left_transitions:
|
|
62
|
+
left_action = left_transition.action
|
|
63
|
+
for right_transition in right_transitions:
|
|
64
|
+
right_action = right_transition.action
|
|
65
|
+
|
|
66
|
+
if isinstance(left_action, Send) and isinstance(right_action, Receive):
|
|
67
|
+
if left_action.channel != right_action.channel:
|
|
68
|
+
continue
|
|
69
|
+
updated = list(processes)
|
|
70
|
+
updated[left_index] = left_transition.target
|
|
71
|
+
updated[right_index] = right_transition.target
|
|
72
|
+
result.append(
|
|
73
|
+
Transition(
|
|
74
|
+
Tau(),
|
|
75
|
+
Parallel(tuple(updated)),
|
|
76
|
+
)
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
elif isinstance(left_action, Receive) and isinstance(right_action, Send):
|
|
80
|
+
if left_action.channel != right_action.channel:
|
|
81
|
+
continue
|
|
82
|
+
updated = list(processes)
|
|
83
|
+
updated[left_index] = left_transition.target
|
|
84
|
+
updated[right_index] = right_transition.target
|
|
85
|
+
result.append(
|
|
86
|
+
Transition(
|
|
87
|
+
Tau(),
|
|
88
|
+
Parallel(tuple(updated)),
|
|
89
|
+
)
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
return tuple(result)
|
|
93
|
+
|
|
94
|
+
raise TypeError(f"Unsupported process type: {type(process).__name__}")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
__all__ = ["Transition", "transitions"]
|