process-bigraph 1.4.4__tar.gz → 1.4.5__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.
- {process_bigraph-1.4.4/process_bigraph.egg-info → process_bigraph-1.4.5}/PKG-INFO +3 -1
- {process_bigraph-1.4.4 → process_bigraph-1.4.5}/process_bigraph/composite.py +275 -2
- process_bigraph-1.4.5/process_bigraph/protocols/clusters/__init__.py +17 -0
- process_bigraph-1.4.5/process_bigraph/protocols/clusters/ec2_ssm.py +815 -0
- process_bigraph-1.4.5/process_bigraph/protocols/pool.py +309 -0
- process_bigraph-1.4.5/process_bigraph/protocols/session.py +130 -0
- {process_bigraph-1.4.4 → process_bigraph-1.4.5/process_bigraph.egg-info}/PKG-INFO +3 -1
- {process_bigraph-1.4.4 → process_bigraph-1.4.5}/process_bigraph.egg-info/SOURCES.txt +4 -0
- {process_bigraph-1.4.4 → process_bigraph-1.4.5}/process_bigraph.egg-info/requires.txt +3 -0
- {process_bigraph-1.4.4 → process_bigraph-1.4.5}/pyproject.toml +11 -1
- {process_bigraph-1.4.4 → process_bigraph-1.4.5}/AUTHORS.md +0 -0
- {process_bigraph-1.4.4 → process_bigraph-1.4.5}/LICENSE +0 -0
- {process_bigraph-1.4.4 → process_bigraph-1.4.5}/README.md +0 -0
- {process_bigraph-1.4.4 → process_bigraph-1.4.5}/process_bigraph/__init__.py +0 -0
- {process_bigraph-1.4.4 → process_bigraph-1.4.5}/process_bigraph/bundle.py +0 -0
- {process_bigraph-1.4.4 → process_bigraph-1.4.5}/process_bigraph/emitter.py +0 -0
- {process_bigraph-1.4.4 → process_bigraph-1.4.5}/process_bigraph/experiments/__init__.py +0 -0
- {process_bigraph-1.4.4 → process_bigraph-1.4.5}/process_bigraph/experiments/minimal_gillespie.py +0 -0
- {process_bigraph-1.4.4 → process_bigraph-1.4.5}/process_bigraph/nextflow.py +0 -0
- {process_bigraph-1.4.4 → process_bigraph-1.4.5}/process_bigraph/plumbing.py +0 -0
- {process_bigraph-1.4.4 → process_bigraph-1.4.5}/process_bigraph/processes/__init__.py +0 -0
- {process_bigraph-1.4.4 → process_bigraph-1.4.5}/process_bigraph/processes/dynamic_structure.py +0 -0
- {process_bigraph-1.4.4 → process_bigraph-1.4.5}/process_bigraph/processes/examples.py +0 -0
- {process_bigraph-1.4.4 → process_bigraph-1.4.5}/process_bigraph/processes/growth_division.py +0 -0
- {process_bigraph-1.4.4 → process_bigraph-1.4.5}/process_bigraph/processes/math_expression.py +0 -0
- {process_bigraph-1.4.4 → process_bigraph-1.4.5}/process_bigraph/processes/parameter_scan.py +0 -0
- {process_bigraph-1.4.4 → process_bigraph-1.4.5}/process_bigraph/processes/reaction.py +0 -0
- {process_bigraph-1.4.4 → process_bigraph-1.4.5}/process_bigraph/protocols/__init__.py +0 -0
- {process_bigraph-1.4.4 → process_bigraph-1.4.5}/process_bigraph/protocols/parallel.py +0 -0
- {process_bigraph-1.4.4 → process_bigraph-1.4.5}/process_bigraph/protocols/ray.py +0 -0
- {process_bigraph-1.4.4 → process_bigraph-1.4.5}/process_bigraph/protocols/rest.py +0 -0
- {process_bigraph-1.4.4 → process_bigraph-1.4.5}/process_bigraph/protocols/socket.py +0 -0
- {process_bigraph-1.4.4 → process_bigraph-1.4.5}/process_bigraph/run.py +0 -0
- {process_bigraph-1.4.4 → process_bigraph-1.4.5}/process_bigraph/run_step.py +0 -0
- {process_bigraph-1.4.4 → process_bigraph-1.4.5}/process_bigraph/server/__init__.py +0 -0
- {process_bigraph-1.4.4 → process_bigraph-1.4.5}/process_bigraph/server/rest.py +0 -0
- {process_bigraph-1.4.4 → process_bigraph-1.4.5}/process_bigraph/server/start.py +0 -0
- {process_bigraph-1.4.4 → process_bigraph-1.4.5}/process_bigraph/types/__init__.py +0 -0
- {process_bigraph-1.4.4 → process_bigraph-1.4.5}/process_bigraph/types/process.py +0 -0
- {process_bigraph-1.4.4 → process_bigraph-1.4.5}/process_bigraph/units.py +0 -0
- {process_bigraph-1.4.4 → process_bigraph-1.4.5}/process_bigraph.egg-info/dependency_links.txt +0 -0
- {process_bigraph-1.4.4 → process_bigraph-1.4.5}/process_bigraph.egg-info/top_level.txt +0 -0
- {process_bigraph-1.4.4 → process_bigraph-1.4.5}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: process-bigraph
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.5
|
|
4
4
|
Summary: protocol and execution for compositional systems biology
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -20,6 +20,8 @@ Requires-Dist: fastapi-utils>=0.8.0; extra == "server-rest"
|
|
|
20
20
|
Requires-Dist: uvicorn>=0.35.0; extra == "server-rest"
|
|
21
21
|
Requires-Dist: fire; extra == "server-rest"
|
|
22
22
|
Requires-Dist: typing-inspect>=0.9.0; extra == "server-rest"
|
|
23
|
+
Provides-Extra: ec2-ssm
|
|
24
|
+
Requires-Dist: boto3>=1.28; extra == "ec2-ssm"
|
|
23
25
|
Dynamic: license-file
|
|
24
26
|
|
|
25
27
|
# Process-Bigraph
|
|
@@ -19,6 +19,73 @@ import math
|
|
|
19
19
|
import time as _time
|
|
20
20
|
import numpy as np
|
|
21
21
|
|
|
22
|
+
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
# Optional invocation tracing
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
# When the env var ``PROCESS_BIGRAPH_TRACE_FILE`` is set to a writable path,
|
|
27
|
+
# every invocation of ``Composite.process_update`` (i.e. every Process/Step
|
|
28
|
+
# call routed through the framework) writes one JSONL record describing the
|
|
29
|
+
# call: path, class, global_time, interval, an input summary, and an output
|
|
30
|
+
# summary. Useful for diagnosing per-step divergence between two runs by
|
|
31
|
+
# diffing two trace files. No overhead when the env var is unset.
|
|
32
|
+
_TRACE_PATH = os.environ.get('PROCESS_BIGRAPH_TRACE_FILE')
|
|
33
|
+
_TRACE_FH = open(_TRACE_PATH, 'a', buffering=1) if _TRACE_PATH else None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _summarize_value(value, depth=0):
|
|
37
|
+
"""Lightweight, JSON-safe summary of an update fragment.
|
|
38
|
+
|
|
39
|
+
Tries to surface the values most useful for diffing two traces:
|
|
40
|
+
- Scalars and short strings inlined.
|
|
41
|
+
- Numpy arrays summarized as ``{shape, sum, mean, head}``.
|
|
42
|
+
- Dicts recursed (capped depth).
|
|
43
|
+
- Lists/tuples shown as their first few items.
|
|
44
|
+
"""
|
|
45
|
+
if depth > 3:
|
|
46
|
+
return f'<{type(value).__name__}>'
|
|
47
|
+
if value is None or isinstance(value, (bool, int, float, str)):
|
|
48
|
+
return value
|
|
49
|
+
if isinstance(value, np.ndarray):
|
|
50
|
+
try:
|
|
51
|
+
return {
|
|
52
|
+
'_np': True,
|
|
53
|
+
'shape': list(value.shape),
|
|
54
|
+
'dtype': str(value.dtype),
|
|
55
|
+
'sum': float(value.sum()) if value.dtype.kind in 'fi' else None,
|
|
56
|
+
'head': value.flatten()[:5].tolist() if value.size else [],
|
|
57
|
+
}
|
|
58
|
+
except Exception:
|
|
59
|
+
return f'<ndarray shape={value.shape}>'
|
|
60
|
+
if isinstance(value, dict):
|
|
61
|
+
return {k: _summarize_value(v, depth + 1) for k, v in list(value.items())[:32]}
|
|
62
|
+
if isinstance(value, (list, tuple)):
|
|
63
|
+
return [_summarize_value(v, depth + 1) for v in value[:10]]
|
|
64
|
+
return f'<{type(value).__name__}>'
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _trace_invoke(path, instance, state, interval, update):
|
|
68
|
+
"""Append one JSONL record describing a process/step invocation."""
|
|
69
|
+
if _TRACE_FH is None:
|
|
70
|
+
return
|
|
71
|
+
try:
|
|
72
|
+
gt = state.get('global_time') if isinstance(state, dict) else None
|
|
73
|
+
rec = {
|
|
74
|
+
'path': list(path) if isinstance(path, tuple) else path,
|
|
75
|
+
'cls': type(instance).__name__,
|
|
76
|
+
'gt': gt,
|
|
77
|
+
'interval': interval,
|
|
78
|
+
'input': _summarize_value(state),
|
|
79
|
+
'output': _summarize_value(update),
|
|
80
|
+
}
|
|
81
|
+
_TRACE_FH.write(json.dumps(rec, default=str) + '\n')
|
|
82
|
+
except Exception as exc:
|
|
83
|
+
# Tracing must never break a sim. Log a single line and continue.
|
|
84
|
+
try:
|
|
85
|
+
_TRACE_FH.write(json.dumps({'trace_error': str(exc)}) + '\n')
|
|
86
|
+
except Exception:
|
|
87
|
+
pass
|
|
88
|
+
|
|
22
89
|
from typing import (
|
|
23
90
|
Any, Dict, List, Optional, Set, Tuple, Union,
|
|
24
91
|
Mapping, MutableMapping, Sequence,
|
|
@@ -1073,6 +1140,33 @@ class Process(Open):
|
|
|
1073
1140
|
"""
|
|
1074
1141
|
return {}
|
|
1075
1142
|
|
|
1143
|
+
def reconfigure(self, config: Dict[str, Any]) -> None:
|
|
1144
|
+
"""Re-bind cheap, per-sim configuration without re-running the
|
|
1145
|
+
expensive parts of ``initialize``.
|
|
1146
|
+
|
|
1147
|
+
Default implementation re-runs ``initialize(config)``, which
|
|
1148
|
+
preserves backwards compatibility but pays the full cold-start
|
|
1149
|
+
cost. Subclasses with expensive state (loaded scientific models,
|
|
1150
|
+
JIT caches, GPU contexts, persistent solver bases) should
|
|
1151
|
+
override to update only the cheap fields, leaving the
|
|
1152
|
+
expensive ones in place.
|
|
1153
|
+
|
|
1154
|
+
Used by ``ActorPool`` + ``Session`` (see
|
|
1155
|
+
``doc/distributed_lifecycles.md``) to claim a pool actor for
|
|
1156
|
+
one Composite's sim and rebind its per-sim parameters
|
|
1157
|
+
(e.g. cell_keys for a sharded dFBA actor) without paying the
|
|
1158
|
+
cobra-Model load again. Without this hook, every
|
|
1159
|
+
``with Session(pool, ...)`` would have to spawn fresh actors,
|
|
1160
|
+
defeating the pool's point.
|
|
1161
|
+
|
|
1162
|
+
Args:
|
|
1163
|
+
config: New per-sim configuration. Subclasses decide which
|
|
1164
|
+
keys are cheap to rebind vs. require a full re-init;
|
|
1165
|
+
ones that require full re-init can fall through to
|
|
1166
|
+
``self.initialize(config)``.
|
|
1167
|
+
"""
|
|
1168
|
+
self.initialize(config)
|
|
1169
|
+
|
|
1076
1170
|
|
|
1077
1171
|
def as_step(inputs, outputs, name=None, aliases=None):
|
|
1078
1172
|
"""
|
|
@@ -1473,6 +1567,122 @@ class Composite(Process):
|
|
|
1473
1567
|
if flush is not None:
|
|
1474
1568
|
flush()
|
|
1475
1569
|
|
|
1570
|
+
def _partition_processes_by_runtime(self):
|
|
1571
|
+
"""Inspect every process_path and split into:
|
|
1572
|
+
|
|
1573
|
+
- ``managed_paths``: set of process paths whose ``_protocol_runtime``
|
|
1574
|
+
implements ``tick_lifecycle``. These are skipped in the per-process
|
|
1575
|
+
invoke loop and instead dispatched once-per-runtime via
|
|
1576
|
+
``_run_tick_lifecycle``.
|
|
1577
|
+
- ``runtime_groups``: list of ``(runtime, [(path, process), ...])``,
|
|
1578
|
+
one entry per distinct runtime that opted into tick_lifecycle.
|
|
1579
|
+
|
|
1580
|
+
Runtimes that only implement the older ``flush_pending`` hook (no
|
|
1581
|
+
``tick_lifecycle``) keep going through the per-process loop —
|
|
1582
|
+
their batching of remote dispatch is unchanged.
|
|
1583
|
+
"""
|
|
1584
|
+
managed: set = set()
|
|
1585
|
+
groups: dict = {}
|
|
1586
|
+
for path in self.process_paths:
|
|
1587
|
+
process = get_path(self.state, path)
|
|
1588
|
+
instance = process.get('instance') if isinstance(process, dict) else None
|
|
1589
|
+
if instance is None:
|
|
1590
|
+
continue
|
|
1591
|
+
rt = getattr(instance, '_protocol_runtime', None)
|
|
1592
|
+
if rt is None or not hasattr(rt, 'tick_lifecycle'):
|
|
1593
|
+
continue
|
|
1594
|
+
managed.add(path)
|
|
1595
|
+
entry = groups.setdefault(id(rt), (rt, []))
|
|
1596
|
+
entry[1].append((path, process))
|
|
1597
|
+
return managed, list(groups.values())
|
|
1598
|
+
|
|
1599
|
+
def _run_tick_lifecycle(
|
|
1600
|
+
self,
|
|
1601
|
+
runtime,
|
|
1602
|
+
group,
|
|
1603
|
+
end_time: float,
|
|
1604
|
+
full_step: float,
|
|
1605
|
+
force_complete: bool,
|
|
1606
|
+
) -> float:
|
|
1607
|
+
"""Delegate the full invoke+apply lifecycle for a runtime's managed
|
|
1608
|
+
processes to its ``tick_lifecycle`` method. Returns the updated
|
|
1609
|
+
``full_step`` (smallest interval across all groups + plain procs).
|
|
1610
|
+
|
|
1611
|
+
The runtime returns one combined Defer covering all its processes;
|
|
1612
|
+
we stash that under a ``common_path`` key in ``self.front`` so the
|
|
1613
|
+
apply pass walks the schema once for the entire batch instead of
|
|
1614
|
+
N times (one per process).
|
|
1615
|
+
"""
|
|
1616
|
+
# Build the list of ProcessTickRequest dicts the runtime expects.
|
|
1617
|
+
requests = [
|
|
1618
|
+
{
|
|
1619
|
+
'path': path,
|
|
1620
|
+
'instance': process['instance'],
|
|
1621
|
+
'interval': process.get('interval'),
|
|
1622
|
+
}
|
|
1623
|
+
for path, process in group
|
|
1624
|
+
]
|
|
1625
|
+
# Pass composite (self) so the runtime can use whatever it needs
|
|
1626
|
+
# — state (composite.state), schema (composite.schema), core
|
|
1627
|
+
# (composite.core), and projection helpers (composite._cached_project)
|
|
1628
|
+
# — without us prematurely committing to a narrower contract.
|
|
1629
|
+
result = runtime.tick_lifecycle(
|
|
1630
|
+
processes=requests,
|
|
1631
|
+
composite=self,
|
|
1632
|
+
global_time=self.state['global_time'],
|
|
1633
|
+
end_time=end_time,
|
|
1634
|
+
force_complete=force_complete,
|
|
1635
|
+
)
|
|
1636
|
+
if result is None:
|
|
1637
|
+
# Runtime declined — caller should fall back to per-process
|
|
1638
|
+
# path. We don't currently support partial fallback in the
|
|
1639
|
+
# same tick; runtimes that may decline should not implement
|
|
1640
|
+
# tick_lifecycle.
|
|
1641
|
+
return full_step
|
|
1642
|
+
|
|
1643
|
+
next_time = result['next_time']
|
|
1644
|
+
process_paths = result['process_paths']
|
|
1645
|
+
applied = result.get('applied', False)
|
|
1646
|
+
|
|
1647
|
+
interval = next_time - self.state['global_time']
|
|
1648
|
+
if interval < full_step:
|
|
1649
|
+
full_step = interval
|
|
1650
|
+
|
|
1651
|
+
# Update each process's front entry with the next scheduled time —
|
|
1652
|
+
# other Composite logic (expire_process_paths, scheduling) reads
|
|
1653
|
+
# these per-path. Keep them in sync with the runtime's batch.
|
|
1654
|
+
for p in process_paths:
|
|
1655
|
+
if p not in self.front:
|
|
1656
|
+
self.front[p] = empty_front(self.state['global_time'])
|
|
1657
|
+
self.front[p]['time'] = next_time
|
|
1658
|
+
# Per-process update slot empty: the actual delta is either
|
|
1659
|
+
# carried by the combined defer at common_path (v2 path) or
|
|
1660
|
+
# already applied to ``composite.state`` directly by the
|
|
1661
|
+
# runtime (v3 path with ``applied=True``).
|
|
1662
|
+
self.front[p]['update'] = {}
|
|
1663
|
+
|
|
1664
|
+
if applied:
|
|
1665
|
+
# v3 — runtime mutated state directly. Skip apply_updates entirely
|
|
1666
|
+
# for these processes; the framework only needs to advance time.
|
|
1667
|
+
# Useful when per-cell/per-tick reconcile cost dominates and the
|
|
1668
|
+
# runtime can do a vectorized array op instead of a schema walk.
|
|
1669
|
+
#
|
|
1670
|
+
# ``run()`` decides whether to call ``apply_updates`` based on
|
|
1671
|
+
# whether front[path]['update'] is truthy — by leaving them all
|
|
1672
|
+
# empty here AND not placing a defer at common_path, we ensure
|
|
1673
|
+
# apply_updates is never called for this tick's managed group.
|
|
1674
|
+
return full_step
|
|
1675
|
+
|
|
1676
|
+
# v2 — runtime returned a combined Defer; framework runs apply_updates
|
|
1677
|
+
# on it once for the whole batch.
|
|
1678
|
+
common_path = result['common_path']
|
|
1679
|
+
defer = result['defer']
|
|
1680
|
+
if common_path not in self.front:
|
|
1681
|
+
self.front[common_path] = empty_front(self.state['global_time'])
|
|
1682
|
+
self.front[common_path]['time'] = next_time
|
|
1683
|
+
self.front[common_path]['update'] = defer
|
|
1684
|
+
return full_step
|
|
1685
|
+
|
|
1476
1686
|
def find_instance_paths(self, state: Dict[str, Any]) -> None:
|
|
1477
1687
|
"""
|
|
1478
1688
|
Identify all Step and Process instances in the current state.
|
|
@@ -2399,16 +2609,36 @@ class Composite(Process):
|
|
|
2399
2609
|
while self.state['global_time'] < end_time or force_complete:
|
|
2400
2610
|
full_step = math.inf
|
|
2401
2611
|
|
|
2402
|
-
#
|
|
2612
|
+
# Partition processes: ones whose runtime opts into batched
|
|
2613
|
+
# tick_lifecycle vs the regular per-process path. The
|
|
2614
|
+
# runtime-managed group(s) get one method call per runtime,
|
|
2615
|
+
# which (a) skips N _cached_view + N invoke calls in this
|
|
2616
|
+
# tick's invoke pass, and (b) returns one combined Defer that
|
|
2617
|
+
# apply_updates walks once instead of N times.
|
|
2618
|
+
managed_paths, runtime_groups = self._partition_processes_by_runtime()
|
|
2619
|
+
|
|
2620
|
+
# Run each plain (non-runtime-managed) process and compute the
|
|
2621
|
+
# minimum time step that advances simulation.
|
|
2403
2622
|
if self._parallel_processes and len(self.process_paths) > 1:
|
|
2404
2623
|
full_step = self._run_processes_layer_parallel(
|
|
2405
|
-
end_time, full_step, force_complete
|
|
2624
|
+
end_time, full_step, force_complete,
|
|
2625
|
+
skip_paths=managed_paths)
|
|
2406
2626
|
else:
|
|
2407
2627
|
for path in self.process_paths:
|
|
2628
|
+
if path in managed_paths:
|
|
2629
|
+
continue
|
|
2408
2630
|
process = get_path(self.state, path)
|
|
2409
2631
|
full_step = self.run_process(
|
|
2410
2632
|
path, process, end_time, full_step, force_complete)
|
|
2411
2633
|
|
|
2634
|
+
# Now dispatch each runtime group via its tick_lifecycle hook.
|
|
2635
|
+
# Each call returns one combined Defer (placed at a common-path
|
|
2636
|
+
# slot in self.front) so the downstream apply_updates walks
|
|
2637
|
+
# the schema once for the entire batch.
|
|
2638
|
+
for rt, group in runtime_groups:
|
|
2639
|
+
full_step = self._run_tick_lifecycle(
|
|
2640
|
+
rt, group, end_time, full_step, force_complete)
|
|
2641
|
+
|
|
2412
2642
|
if full_step == math.inf:
|
|
2413
2643
|
# No process ran — jump to the next scheduled process time
|
|
2414
2644
|
next_event = end_time
|
|
@@ -2543,6 +2773,7 @@ class Composite(Process):
|
|
|
2543
2773
|
end_time: float,
|
|
2544
2774
|
full_step: float,
|
|
2545
2775
|
force_complete: bool,
|
|
2776
|
+
skip_paths: set = None,
|
|
2546
2777
|
) -> float:
|
|
2547
2778
|
"""Parallel analog of the per-tick `for path in self.process_paths`
|
|
2548
2779
|
loop. Splits run_process into a schedule pass (sequential, cheap)
|
|
@@ -2560,10 +2791,17 @@ class Composite(Process):
|
|
|
2560
2791
|
|
|
2561
2792
|
Not safe when processes share mutable state or hold global locks.
|
|
2562
2793
|
Like `parallel_steps`, this is opt-in for that reason.
|
|
2794
|
+
|
|
2795
|
+
``skip_paths`` (set of paths) is consulted in the schedule pass
|
|
2796
|
+
and any matching process is skipped — used by ``run()`` to exclude
|
|
2797
|
+
runtime-managed processes (those handled via ``tick_lifecycle``).
|
|
2563
2798
|
"""
|
|
2799
|
+
skip_paths = skip_paths or set()
|
|
2564
2800
|
# ---- schedule pass: decide which processes are due now ---------- #
|
|
2565
2801
|
due = [] # (path, process_dict, state, future_time, process_interval)
|
|
2566
2802
|
for path in self.process_paths:
|
|
2803
|
+
if path in skip_paths:
|
|
2804
|
+
continue
|
|
2567
2805
|
process = get_path(self.state, path)
|
|
2568
2806
|
if path not in self.front:
|
|
2569
2807
|
self.front[path] = empty_front(self.state['global_time'])
|
|
@@ -2662,6 +2900,15 @@ class Composite(Process):
|
|
|
2662
2900
|
t0 = _time.monotonic()
|
|
2663
2901
|
update = process['instance'].invoke(clean_state, interval)
|
|
2664
2902
|
self.process_update_time += _time.monotonic() - t0
|
|
2903
|
+
if _TRACE_FH is not None:
|
|
2904
|
+
# Resolve SyncUpdate / Defer to plain dict for the trace; the
|
|
2905
|
+
# invoke return is opaque (SyncUpdate wraps a dict). We only need
|
|
2906
|
+
# a snapshot for the trace, so pull .get() if available.
|
|
2907
|
+
try:
|
|
2908
|
+
resolved = update.get() if hasattr(update, 'get') else update
|
|
2909
|
+
except Exception:
|
|
2910
|
+
resolved = '<unresolvable>'
|
|
2911
|
+
_trace_invoke(path, process['instance'], clean_state, interval, resolved)
|
|
2665
2912
|
# This nested function projects the update into the global state at the given path
|
|
2666
2913
|
def defer_project(update_results: Any, args: Tuple[Any, Any, Union[str, Tuple[str, ...]]]) -> Any:
|
|
2667
2914
|
schema, state, process_path = args
|
|
@@ -2846,12 +3093,38 @@ class Composite(Process):
|
|
|
2846
3093
|
# step_paths indexes incrementally instead of re-scanning
|
|
2847
3094
|
# the full state via find_instance_paths.
|
|
2848
3095
|
structural_events = [] if had_structural_sentinels else None
|
|
3096
|
+
# DEBUG: track cell_mass before/after apply + global_time
|
|
3097
|
+
_dbg_before = None
|
|
3098
|
+
_dbg_update = None
|
|
3099
|
+
_dbg_gt_before = self.state.get('global_time')
|
|
3100
|
+
_dbg_update_gt = combined_update.get('global_time') if isinstance(combined_update, dict) else None
|
|
3101
|
+
try:
|
|
3102
|
+
_agent_state = self.state.get('agents', {}).get('00') or self.state.get('agents', {}).get('0')
|
|
3103
|
+
if _agent_state:
|
|
3104
|
+
_dbg_before = _agent_state.get('listeners', {}).get('mass', {}).get('cell_mass')
|
|
3105
|
+
_u_agents = combined_update.get('agents', {}) if isinstance(combined_update, dict) else {}
|
|
3106
|
+
_u_agent = _u_agents.get('00') if isinstance(_u_agents, dict) else None
|
|
3107
|
+
if not _u_agent and isinstance(_u_agents, dict):
|
|
3108
|
+
_u_agent = _u_agents.get('0') if _u_agents else None
|
|
3109
|
+
if _u_agent:
|
|
3110
|
+
_dbg_update = _u_agent.get('listeners', {}).get('mass', {}).get('cell_mass')
|
|
3111
|
+
except Exception:
|
|
3112
|
+
pass
|
|
2849
3113
|
self.state, merges = self.core.apply(
|
|
2850
3114
|
apply_schema,
|
|
2851
3115
|
self.state,
|
|
2852
3116
|
combined_update,
|
|
2853
3117
|
update_has_structural=had_structural_sentinels,
|
|
2854
3118
|
events=structural_events)
|
|
3119
|
+
try:
|
|
3120
|
+
_agents_dict = self.state.get('agents', {})
|
|
3121
|
+
_agent_state = _agents_dict.get('00') or _agents_dict.get('0')
|
|
3122
|
+
_dbg_after = _agent_state.get('listeners', {}).get('mass', {}).get('cell_mass') if _agent_state else None
|
|
3123
|
+
_gt_after = self.state.get('global_time')
|
|
3124
|
+
if _dbg_update is not None or (_dbg_before is not None and _dbg_before != _dbg_after):
|
|
3125
|
+
print(f'[APPLY_DEBUG] composite_id={id(self)} state_id={id(self.state)} agents_id={id(_agents_dict)} gt_before={_dbg_gt_before} gt_after={_gt_after} update_gt={_dbg_update_gt} cm: {_dbg_before} +update={_dbg_update} -> {_dbg_after}', flush=True)
|
|
3126
|
+
except Exception:
|
|
3127
|
+
pass
|
|
2855
3128
|
# For structural sentinels, apply mutates the
|
|
2856
3129
|
# access-normalized form of ``apply_schema`` in place
|
|
2857
3130
|
# (e.g. ``_divide`` pops the mother key and inserts
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Cluster context managers for distributed process_bigraph runs.
|
|
2
|
+
|
|
3
|
+
Each module here defines one context-manager-shaped cluster type:
|
|
4
|
+
|
|
5
|
+
with EC2SSMRayCluster(...) as cluster:
|
|
6
|
+
pool = get_or_create_pool(MyActor, {...}, size=72, cluster=cluster)
|
|
7
|
+
...
|
|
8
|
+
|
|
9
|
+
Cluster modules import their cloud SDKs lazily so this package itself
|
|
10
|
+
imports cleanly without any cloud extras installed. Install only what
|
|
11
|
+
you need:
|
|
12
|
+
|
|
13
|
+
pip install process-bigraph[ec2-ssm] # boto3 for EC2SSMRayCluster
|
|
14
|
+
|
|
15
|
+
See ``doc/distributed_lifecycles.md`` for how clusters fit into the
|
|
16
|
+
``cluster ⊃ pool ⊃ session ⊃ tick`` layering.
|
|
17
|
+
"""
|