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.
Files changed (37) hide show
  1. mycorrhizal/__init__.py +3 -0
  2. mycorrhizal/common/__init__.py +68 -0
  3. mycorrhizal/common/interface_builder.py +203 -0
  4. mycorrhizal/common/interfaces.py +412 -0
  5. mycorrhizal/common/timebase.py +99 -0
  6. mycorrhizal/common/wrappers.py +532 -0
  7. mycorrhizal/enoki/__init__.py +0 -0
  8. mycorrhizal/enoki/core.py +1545 -0
  9. mycorrhizal/enoki/testing_utils.py +529 -0
  10. mycorrhizal/enoki/util.py +220 -0
  11. mycorrhizal/hypha/__init__.py +0 -0
  12. mycorrhizal/hypha/core/__init__.py +107 -0
  13. mycorrhizal/hypha/core/builder.py +404 -0
  14. mycorrhizal/hypha/core/runtime.py +890 -0
  15. mycorrhizal/hypha/core/specs.py +234 -0
  16. mycorrhizal/hypha/util.py +38 -0
  17. mycorrhizal/rhizomorph/README.md +220 -0
  18. mycorrhizal/rhizomorph/__init__.py +0 -0
  19. mycorrhizal/rhizomorph/core.py +1729 -0
  20. mycorrhizal/rhizomorph/util.py +45 -0
  21. mycorrhizal/spores/__init__.py +124 -0
  22. mycorrhizal/spores/cache.py +208 -0
  23. mycorrhizal/spores/core.py +419 -0
  24. mycorrhizal/spores/dsl/__init__.py +48 -0
  25. mycorrhizal/spores/dsl/enoki.py +514 -0
  26. mycorrhizal/spores/dsl/hypha.py +399 -0
  27. mycorrhizal/spores/dsl/rhizomorph.py +351 -0
  28. mycorrhizal/spores/encoder/__init__.py +11 -0
  29. mycorrhizal/spores/encoder/base.py +42 -0
  30. mycorrhizal/spores/encoder/json.py +159 -0
  31. mycorrhizal/spores/extraction.py +484 -0
  32. mycorrhizal/spores/models.py +288 -0
  33. mycorrhizal/spores/transport/__init__.py +10 -0
  34. mycorrhizal/spores/transport/base.py +46 -0
  35. mycorrhizal-0.1.0.dist-info/METADATA +198 -0
  36. mycorrhizal-0.1.0.dist-info/RECORD +37 -0
  37. 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()