mycorrhizal 0.1.0__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.
- mycorrhizal/__init__.py +3 -0
- mycorrhizal/common/__init__.py +68 -0
- mycorrhizal/common/interface_builder.py +203 -0
- mycorrhizal/common/interfaces.py +412 -0
- mycorrhizal/common/timebase.py +99 -0
- mycorrhizal/common/wrappers.py +532 -0
- mycorrhizal/enoki/__init__.py +0 -0
- mycorrhizal/enoki/core.py +1545 -0
- mycorrhizal/enoki/testing_utils.py +529 -0
- mycorrhizal/enoki/util.py +220 -0
- mycorrhizal/hypha/__init__.py +0 -0
- mycorrhizal/hypha/core/__init__.py +107 -0
- mycorrhizal/hypha/core/builder.py +404 -0
- mycorrhizal/hypha/core/runtime.py +890 -0
- mycorrhizal/hypha/core/specs.py +234 -0
- mycorrhizal/hypha/util.py +38 -0
- mycorrhizal/rhizomorph/README.md +220 -0
- mycorrhizal/rhizomorph/__init__.py +0 -0
- mycorrhizal/rhizomorph/core.py +1729 -0
- mycorrhizal/rhizomorph/util.py +45 -0
- mycorrhizal/spores/__init__.py +124 -0
- mycorrhizal/spores/cache.py +208 -0
- mycorrhizal/spores/core.py +419 -0
- mycorrhizal/spores/dsl/__init__.py +48 -0
- mycorrhizal/spores/dsl/enoki.py +514 -0
- mycorrhizal/spores/dsl/hypha.py +399 -0
- mycorrhizal/spores/dsl/rhizomorph.py +351 -0
- mycorrhizal/spores/encoder/__init__.py +11 -0
- mycorrhizal/spores/encoder/base.py +42 -0
- mycorrhizal/spores/encoder/json.py +159 -0
- mycorrhizal/spores/extraction.py +484 -0
- mycorrhizal/spores/models.py +288 -0
- mycorrhizal/spores/transport/__init__.py +10 -0
- mycorrhizal/spores/transport/base.py +46 -0
- mycorrhizal-0.1.0.dist-info/METADATA +198 -0
- mycorrhizal-0.1.0.dist-info/RECORD +37 -0
- mycorrhizal-0.1.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Hypha DSL - Builder Layer
|
|
4
|
+
|
|
5
|
+
NetBuilder provides the API for declaratively constructing Petri net specifications.
|
|
6
|
+
Used within @pn.net decorated functions.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import Optional, Callable, List, Dict, Type, Any
|
|
10
|
+
from .specs import (
|
|
11
|
+
NetSpec,
|
|
12
|
+
PlaceSpec,
|
|
13
|
+
TransitionSpec,
|
|
14
|
+
GuardSpec,
|
|
15
|
+
ArcSpec,
|
|
16
|
+
PlaceType,
|
|
17
|
+
PlaceRef,
|
|
18
|
+
TransitionRef,
|
|
19
|
+
SubnetRef,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ArcChain:
|
|
24
|
+
"""Fluent interface for chaining arc definitions"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, builder: "NetBuilder", last_ref: Any):
|
|
27
|
+
self.builder = builder
|
|
28
|
+
self.last_ref = last_ref
|
|
29
|
+
self.last_type = type(last_ref)
|
|
30
|
+
|
|
31
|
+
def arc(
|
|
32
|
+
self, target: Any, name: Optional[str] = None, weight: int = 1
|
|
33
|
+
) -> "ArcChain":
|
|
34
|
+
"""Chain another arc from the last element to target"""
|
|
35
|
+
target_type = type(target)
|
|
36
|
+
|
|
37
|
+
if self.last_type == target_type:
|
|
38
|
+
raise ValueError(
|
|
39
|
+
f"Cannot connect {self.last_type.__name__} to {target_type.__name__} directly. "
|
|
40
|
+
f"Arcs must alternate between places and transitions."
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
arc_spec = ArcSpec(self.last_ref, target, weight, name)
|
|
44
|
+
self.builder.spec.arcs.append(arc_spec)
|
|
45
|
+
|
|
46
|
+
return ArcChain(self.builder, target)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class NetBuilder:
|
|
50
|
+
"""Builder for constructing Petri net specifications"""
|
|
51
|
+
|
|
52
|
+
def __init__(self, name: str, parent: Optional[NetSpec] = None):
|
|
53
|
+
self.spec = NetSpec(name, parent=parent)
|
|
54
|
+
|
|
55
|
+
def place(
|
|
56
|
+
self,
|
|
57
|
+
name: str,
|
|
58
|
+
type: PlaceType = PlaceType.BAG,
|
|
59
|
+
state_factory: Optional[Callable] = None,
|
|
60
|
+
) -> PlaceRef:
|
|
61
|
+
"""Declare a regular place"""
|
|
62
|
+
place_spec = PlaceSpec(name, type, state_factory=state_factory)
|
|
63
|
+
self.spec.places[name] = place_spec
|
|
64
|
+
return PlaceRef(name, self.spec)
|
|
65
|
+
|
|
66
|
+
def io_input_place(self):
|
|
67
|
+
"""Decorator for IOInputPlace with async generator"""
|
|
68
|
+
|
|
69
|
+
def decorator(func: Callable) -> PlaceRef:
|
|
70
|
+
name = func.__name__
|
|
71
|
+
place_spec = PlaceSpec(
|
|
72
|
+
name, PlaceType.QUEUE, handler=func, is_io_input=True
|
|
73
|
+
)
|
|
74
|
+
self.spec.places[name] = place_spec
|
|
75
|
+
return PlaceRef(name, self.spec)
|
|
76
|
+
|
|
77
|
+
return decorator
|
|
78
|
+
|
|
79
|
+
def io_output_place(self):
|
|
80
|
+
"""Decorator for IOOutputPlace with async handler"""
|
|
81
|
+
|
|
82
|
+
def decorator(func: Callable) -> PlaceRef:
|
|
83
|
+
name = func.__name__
|
|
84
|
+
place_spec = PlaceSpec(
|
|
85
|
+
name, PlaceType.QUEUE, handler=func, is_io_output=True
|
|
86
|
+
)
|
|
87
|
+
self.spec.places[name] = place_spec
|
|
88
|
+
return PlaceRef(name, self.spec)
|
|
89
|
+
|
|
90
|
+
return decorator
|
|
91
|
+
|
|
92
|
+
def guard(self, func: Callable) -> GuardSpec:
|
|
93
|
+
"""Create a guard specification from a function"""
|
|
94
|
+
return GuardSpec(func)
|
|
95
|
+
|
|
96
|
+
def transition(
|
|
97
|
+
self,
|
|
98
|
+
guard: Optional[GuardSpec] = None,
|
|
99
|
+
state_factory: Optional[Callable] = None,
|
|
100
|
+
):
|
|
101
|
+
"""Decorator for transition function"""
|
|
102
|
+
|
|
103
|
+
def decorator(func: Callable) -> TransitionRef:
|
|
104
|
+
name = func.__name__
|
|
105
|
+
trans_spec = TransitionSpec(name, func, guard, state_factory)
|
|
106
|
+
self.spec.transitions[name] = trans_spec
|
|
107
|
+
return TransitionRef(name, self.spec)
|
|
108
|
+
|
|
109
|
+
return decorator
|
|
110
|
+
|
|
111
|
+
def arc(
|
|
112
|
+
self, source: Any, target: Any, name: Optional[str] = None, weight: int = 1
|
|
113
|
+
) -> ArcChain:
|
|
114
|
+
"""Create an arc and return chainable ArcChain"""
|
|
115
|
+
source_type = type(source)
|
|
116
|
+
target_type = type(target)
|
|
117
|
+
|
|
118
|
+
if source_type == target_type:
|
|
119
|
+
raise ValueError(
|
|
120
|
+
f"Cannot connect {source_type.__name__} to {target_type.__name__} directly. "
|
|
121
|
+
f"Arcs must alternate between places and transitions."
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
arc_spec = ArcSpec(source, target, weight, name)
|
|
125
|
+
self.spec.arcs.append(arc_spec)
|
|
126
|
+
|
|
127
|
+
return ArcChain(self, target)
|
|
128
|
+
|
|
129
|
+
def subnet(self, net_func: Callable, instance_name: str) -> SubnetRef:
|
|
130
|
+
"""Instantiate a subnet with given instance name"""
|
|
131
|
+
# 1. Validation
|
|
132
|
+
if not hasattr(net_func, "_spec"):
|
|
133
|
+
raise ValueError(
|
|
134
|
+
f"{net_func.__name__} is not a valid net. "
|
|
135
|
+
f"Did you forget to decorate it with @pn.net?"
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
original_spec = net_func._spec
|
|
139
|
+
|
|
140
|
+
# 2. Create subnet spec and copy places/transitions
|
|
141
|
+
subnet_spec = NetSpec(instance_name, parent=self.spec)
|
|
142
|
+
|
|
143
|
+
# Copy places and transitions (PlaceSpec/TransitionSpec are plain
|
|
144
|
+
# dataclasses holding callables/state factories; keeping the same
|
|
145
|
+
# instances is acceptable since they are stateless descriptors).
|
|
146
|
+
subnet_spec.places = dict(original_spec.places)
|
|
147
|
+
subnet_spec.transitions = dict(original_spec.transitions)
|
|
148
|
+
|
|
149
|
+
# 3. Remap arcs to point into the new subnet
|
|
150
|
+
subnet_spec.arcs = self._remap_arcs_to_subnet(original_spec.arcs, subnet_spec)
|
|
151
|
+
|
|
152
|
+
# 4. Ensure nested subnets are copied
|
|
153
|
+
self._ensure_nested_subnets_copied(original_spec, subnet_spec)
|
|
154
|
+
|
|
155
|
+
# 5. Register and return
|
|
156
|
+
self.spec.subnets[instance_name] = subnet_spec
|
|
157
|
+
return SubnetRef(subnet_spec)
|
|
158
|
+
|
|
159
|
+
def _remap_arcs_to_subnet(self, arcs: List[ArcSpec], subnet_spec: NetSpec) -> List[ArcSpec]:
|
|
160
|
+
"""Remap arc references to point into target subnet.
|
|
161
|
+
|
|
162
|
+
For each PlaceRef/TransitionRef in the arcs, creates a new reference
|
|
163
|
+
bound to the target subnet. Handles nested SubnetRefs by recursively
|
|
164
|
+
copying their specs into the target subnet.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
arcs: Original arcs from the template net
|
|
168
|
+
subnet_spec: Target subnet spec to bind references to
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
New list of ArcSpec with remapped references
|
|
172
|
+
"""
|
|
173
|
+
new_arcs = []
|
|
174
|
+
for arc in arcs:
|
|
175
|
+
new_src = self._remap_ref_to_subnet(arc.source, subnet_spec, original_spec=None)
|
|
176
|
+
new_tgt = self._remap_ref_to_subnet(arc.target, subnet_spec, original_spec=None)
|
|
177
|
+
new_arcs.append(ArcSpec(new_src, new_tgt, arc.weight, arc.name))
|
|
178
|
+
return new_arcs
|
|
179
|
+
|
|
180
|
+
def _remap_ref_to_subnet(self, ref: Any, target_subnet: NetSpec, original_spec: Optional[NetSpec] = None) -> Any:
|
|
181
|
+
"""Remap a single reference to point into target subnet.
|
|
182
|
+
|
|
183
|
+
Handles:
|
|
184
|
+
- PlaceRef: Create new PlaceRef bound to target_subnet
|
|
185
|
+
- TransitionRef: Create new TransitionRef bound to target_subnet
|
|
186
|
+
- SubnetRef: Recursively copy nested subnet spec into target_subnet
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
ref: Reference to remap (PlaceRef, TransitionRef, SubnetRef, or other)
|
|
190
|
+
target_subnet: The subnet spec to bind the reference to
|
|
191
|
+
original_spec: The original template spec (for resolving nested subnets)
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
The remapped reference, or original if unknown type
|
|
195
|
+
"""
|
|
196
|
+
from .specs import PlaceRef, TransitionRef, SubnetRef
|
|
197
|
+
|
|
198
|
+
if isinstance(ref, PlaceRef):
|
|
199
|
+
return PlaceRef(ref.local_name, target_subnet)
|
|
200
|
+
if isinstance(ref, TransitionRef):
|
|
201
|
+
return TransitionRef(ref.local_name, target_subnet)
|
|
202
|
+
if isinstance(ref, SubnetRef):
|
|
203
|
+
# Handle nested subnets recursively
|
|
204
|
+
nested_name = ref.spec.name
|
|
205
|
+
if nested_name not in target_subnet.subnets:
|
|
206
|
+
# Copy the nested spec into this subnet (recursive)
|
|
207
|
+
copied_nested = self._copy_spec_with_parent(ref.spec, target_subnet)
|
|
208
|
+
target_subnet.subnets[nested_name] = copied_nested
|
|
209
|
+
return SubnetRef(copied_nested)
|
|
210
|
+
else:
|
|
211
|
+
# Already copied, return reference to existing copy
|
|
212
|
+
return SubnetRef(target_subnet.subnets[nested_name])
|
|
213
|
+
|
|
214
|
+
# Unknown ref type: return as-is
|
|
215
|
+
return ref
|
|
216
|
+
|
|
217
|
+
def _ensure_nested_subnets_copied(self, original_spec: NetSpec, subnet_spec: NetSpec):
|
|
218
|
+
"""Ensure all nested subnets are copied into the target subnet.
|
|
219
|
+
|
|
220
|
+
This handles nested subnets that weren't already processed during
|
|
221
|
+
arc remapping. Each nested subnet is copied with its parent set
|
|
222
|
+
to the target subnet.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
original_spec: The template spec containing nested subnets
|
|
226
|
+
subnet_spec: The target subnet spec to copy nested subnets into
|
|
227
|
+
"""
|
|
228
|
+
for sub_name, sub_spec in original_spec.subnets.items():
|
|
229
|
+
# Only copy if not already handled (e.g., by arc remapping)
|
|
230
|
+
if sub_name not in subnet_spec.subnets:
|
|
231
|
+
subnet_spec.subnets[sub_name] = self._copy_spec_with_parent(
|
|
232
|
+
sub_spec, subnet_spec
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
def _copy_spec_with_parent(self, original: NetSpec, new_parent: NetSpec) -> NetSpec:
|
|
236
|
+
"""Deep copy a spec and set new parent"""
|
|
237
|
+
new_spec = NetSpec(original.name, parent=new_parent)
|
|
238
|
+
new_spec.places = dict(original.places)
|
|
239
|
+
new_spec.transitions = dict(original.transitions)
|
|
240
|
+
new_spec.arcs = list(original.arcs)
|
|
241
|
+
|
|
242
|
+
for sub_name, sub_spec in original.subnets.items():
|
|
243
|
+
new_spec.subnets[sub_name] = self._copy_spec_with_parent(sub_spec, new_spec)
|
|
244
|
+
|
|
245
|
+
return new_spec
|
|
246
|
+
|
|
247
|
+
def forward(self, input_place: Any, output_place: Any, name: Optional[str] = None):
|
|
248
|
+
"""Create a simple pass-through transition"""
|
|
249
|
+
trans_name = (
|
|
250
|
+
name or f"forward_{input_place.local_name}_to_{output_place.local_name}"
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
def make_handler(out_place):
|
|
254
|
+
async def forward_handler(consumed, bb, timebase):
|
|
255
|
+
for token in consumed:
|
|
256
|
+
yield {out_place: token}
|
|
257
|
+
|
|
258
|
+
return forward_handler
|
|
259
|
+
|
|
260
|
+
trans_spec = TransitionSpec(trans_name, make_handler(output_place))
|
|
261
|
+
self.spec.transitions[trans_name] = trans_spec
|
|
262
|
+
trans_ref = TransitionRef(trans_name, self.spec)
|
|
263
|
+
|
|
264
|
+
self.spec.arcs.append(ArcSpec(input_place, trans_ref))
|
|
265
|
+
self.spec.arcs.append(ArcSpec(trans_ref, output_place))
|
|
266
|
+
|
|
267
|
+
def fork(
|
|
268
|
+
self, input_place: Any, output_places: List[Any], name: Optional[str] = None
|
|
269
|
+
):
|
|
270
|
+
"""Create a transition that broadcasts tokens to multiple outputs"""
|
|
271
|
+
trans_name = name or f"fork_{input_place.local_name}"
|
|
272
|
+
|
|
273
|
+
def make_handler(out_places):
|
|
274
|
+
async def fork_handler(consumed, bb, timebase):
|
|
275
|
+
for token in consumed:
|
|
276
|
+
for output_place in out_places:
|
|
277
|
+
yield {output_place: token}
|
|
278
|
+
|
|
279
|
+
return fork_handler
|
|
280
|
+
|
|
281
|
+
trans_spec = TransitionSpec(trans_name, make_handler(output_places))
|
|
282
|
+
self.spec.transitions[trans_name] = trans_spec
|
|
283
|
+
trans_ref = TransitionRef(trans_name, self.spec)
|
|
284
|
+
|
|
285
|
+
self.spec.arcs.append(ArcSpec(input_place, trans_ref))
|
|
286
|
+
for output_place in output_places:
|
|
287
|
+
self.spec.arcs.append(ArcSpec(trans_ref, output_place))
|
|
288
|
+
|
|
289
|
+
def join(
|
|
290
|
+
self, input_places: List[Any], output_place: Any, name: Optional[str] = None
|
|
291
|
+
):
|
|
292
|
+
"""Create a transition that waits for tokens from all inputs"""
|
|
293
|
+
trans_name = name or f"join_to_{output_place.local_name}"
|
|
294
|
+
|
|
295
|
+
def make_handler(out_place):
|
|
296
|
+
async def join_handler(consumed, bb, timebase):
|
|
297
|
+
for token in consumed:
|
|
298
|
+
yield {out_place: token}
|
|
299
|
+
|
|
300
|
+
return join_handler
|
|
301
|
+
|
|
302
|
+
trans_spec = TransitionSpec(trans_name, make_handler(output_place))
|
|
303
|
+
self.spec.transitions[trans_name] = trans_spec
|
|
304
|
+
trans_ref = TransitionRef(trans_name, self.spec)
|
|
305
|
+
|
|
306
|
+
for input_place in input_places:
|
|
307
|
+
self.spec.arcs.append(ArcSpec(input_place, trans_ref))
|
|
308
|
+
self.spec.arcs.append(ArcSpec(trans_ref, output_place))
|
|
309
|
+
|
|
310
|
+
def merge(
|
|
311
|
+
self, input_places: List[Any], output_place: Any, name: Optional[str] = None
|
|
312
|
+
):
|
|
313
|
+
"""Create transitions from each input to output"""
|
|
314
|
+
for i, input_place in enumerate(input_places):
|
|
315
|
+
trans_name = (
|
|
316
|
+
name or f"{input_place.local_name}_to_{output_place.local_name}"
|
|
317
|
+
)
|
|
318
|
+
if len(input_places) > 1 and not name:
|
|
319
|
+
trans_name = (
|
|
320
|
+
f"{input_place.local_name}_{i}_to_{output_place.local_name}"
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
def make_handler(out_place):
|
|
324
|
+
async def merge_handler(consumed, bb, timebase):
|
|
325
|
+
for token in consumed:
|
|
326
|
+
yield {out_place: token}
|
|
327
|
+
|
|
328
|
+
return merge_handler
|
|
329
|
+
|
|
330
|
+
trans_spec = TransitionSpec(trans_name, make_handler(output_place))
|
|
331
|
+
self.spec.transitions[trans_name] = trans_spec
|
|
332
|
+
trans_ref = TransitionRef(trans_name, self.spec)
|
|
333
|
+
|
|
334
|
+
self.spec.arcs.append(ArcSpec(input_place, trans_ref))
|
|
335
|
+
self.spec.arcs.append(ArcSpec(trans_ref, output_place))
|
|
336
|
+
|
|
337
|
+
def round_robin(
|
|
338
|
+
self, input_place: Any, output_places: List[Any], name: Optional[str] = None
|
|
339
|
+
):
|
|
340
|
+
"""Create a transition that distributes tokens round-robin"""
|
|
341
|
+
trans_name = name or f"round_robin_{input_place.local_name}"
|
|
342
|
+
|
|
343
|
+
def make_handler(out_places):
|
|
344
|
+
async def round_robin_handler(consumed, bb, timebase, state):
|
|
345
|
+
for token in consumed:
|
|
346
|
+
output_place = out_places[state["index"]]
|
|
347
|
+
state["index"] = (state["index"] + 1) % len(out_places)
|
|
348
|
+
yield {output_place: token}
|
|
349
|
+
|
|
350
|
+
return round_robin_handler
|
|
351
|
+
|
|
352
|
+
trans_spec = TransitionSpec(
|
|
353
|
+
trans_name, make_handler(output_places), state_factory=lambda: {"index": 0}
|
|
354
|
+
)
|
|
355
|
+
self.spec.transitions[trans_name] = trans_spec
|
|
356
|
+
trans_ref = TransitionRef(trans_name, self.spec)
|
|
357
|
+
|
|
358
|
+
self.spec.arcs.append(ArcSpec(input_place, trans_ref))
|
|
359
|
+
for output_place in output_places:
|
|
360
|
+
self.spec.arcs.append(ArcSpec(trans_ref, output_place))
|
|
361
|
+
|
|
362
|
+
def route(
|
|
363
|
+
self, input_place: Any, type_map: Dict[Type, Any], name: Optional[str] = None
|
|
364
|
+
):
|
|
365
|
+
"""Create a transition that routes by token type"""
|
|
366
|
+
trans_name = name or f"route_{input_place.local_name}"
|
|
367
|
+
|
|
368
|
+
def make_handler(t_map):
|
|
369
|
+
async def route_handler(consumed, bb, timebase):
|
|
370
|
+
for token in consumed:
|
|
371
|
+
token_type = type(token)
|
|
372
|
+
if token_type in t_map:
|
|
373
|
+
yield {t_map[token_type]: token}
|
|
374
|
+
|
|
375
|
+
return route_handler
|
|
376
|
+
|
|
377
|
+
trans_spec = TransitionSpec(trans_name, make_handler(type_map))
|
|
378
|
+
self.spec.transitions[trans_name] = trans_spec
|
|
379
|
+
trans_ref = TransitionRef(trans_name, self.spec)
|
|
380
|
+
|
|
381
|
+
self.spec.arcs.append(ArcSpec(input_place, trans_ref))
|
|
382
|
+
for output_place in type_map.values():
|
|
383
|
+
self.spec.arcs.append(ArcSpec(trans_ref, output_place))
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
class PetriNetDSL:
|
|
387
|
+
"""Module-level API for Petri net definition"""
|
|
388
|
+
|
|
389
|
+
@staticmethod
|
|
390
|
+
def net(func: Callable) -> Callable:
|
|
391
|
+
"""
|
|
392
|
+
Decorator for defining a Petri net.
|
|
393
|
+
The decorated function receives a NetBuilder as its parameter.
|
|
394
|
+
"""
|
|
395
|
+
builder = NetBuilder(func.__name__, parent=None)
|
|
396
|
+
func(builder)
|
|
397
|
+
|
|
398
|
+
func._spec = builder.spec
|
|
399
|
+
func.to_mermaid = lambda: builder.spec.to_mermaid()
|
|
400
|
+
|
|
401
|
+
return func
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
pn = PetriNetDSL()
|