process-bigraph 1.1.7__tar.gz → 1.2.1__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.1.7/process_bigraph.egg-info → process_bigraph-1.2.1}/PKG-INFO +1 -1
- {process_bigraph-1.1.7 → process_bigraph-1.2.1}/process_bigraph/composite.py +192 -118
- {process_bigraph-1.1.7 → process_bigraph-1.2.1/process_bigraph.egg-info}/PKG-INFO +1 -1
- {process_bigraph-1.1.7 → process_bigraph-1.2.1}/pyproject.toml +1 -1
- {process_bigraph-1.1.7 → process_bigraph-1.2.1}/.github/workflows/notebook_to_html.yml +0 -0
- {process_bigraph-1.1.7 → process_bigraph-1.2.1}/.github/workflows/pytest.yml +0 -0
- {process_bigraph-1.1.7 → process_bigraph-1.2.1}/.gitignore +0 -0
- {process_bigraph-1.1.7 → process_bigraph-1.2.1}/AUTHORS.md +0 -0
- {process_bigraph-1.1.7 → process_bigraph-1.2.1}/CLA.md +0 -0
- {process_bigraph-1.1.7 → process_bigraph-1.2.1}/CODE_OF_CONDUCT.md +0 -0
- {process_bigraph-1.1.7 → process_bigraph-1.2.1}/CONTRIBUTING.md +0 -0
- {process_bigraph-1.1.7 → process_bigraph-1.2.1}/LICENSE +0 -0
- {process_bigraph-1.1.7 → process_bigraph-1.2.1}/README.md +0 -0
- {process_bigraph-1.1.7 → process_bigraph-1.2.1}/doc/_static/process-bigraph.png +0 -0
- {process_bigraph-1.1.7 → process_bigraph-1.2.1}/notebooks/process-bigraphs.ipynb +0 -0
- {process_bigraph-1.1.7 → process_bigraph-1.2.1}/notebooks/visualize_processes.ipynb +0 -0
- {process_bigraph-1.1.7 → process_bigraph-1.2.1}/process_bigraph/__init__.py +0 -0
- {process_bigraph-1.1.7 → process_bigraph-1.2.1}/process_bigraph/bundle.py +0 -0
- {process_bigraph-1.1.7 → process_bigraph-1.2.1}/process_bigraph/emitter.py +0 -0
- {process_bigraph-1.1.7 → process_bigraph-1.2.1}/process_bigraph/experiments/__init__.py +0 -0
- {process_bigraph-1.1.7 → process_bigraph-1.2.1}/process_bigraph/experiments/minimal_gillespie.py +0 -0
- {process_bigraph-1.1.7 → process_bigraph-1.2.1}/process_bigraph/nextflow.py +0 -0
- {process_bigraph-1.1.7 → process_bigraph-1.2.1}/process_bigraph/plumbing.py +0 -0
- {process_bigraph-1.1.7 → process_bigraph-1.2.1}/process_bigraph/processes/__init__.py +0 -0
- {process_bigraph-1.1.7 → process_bigraph-1.2.1}/process_bigraph/processes/dynamic_structure.py +0 -0
- {process_bigraph-1.1.7 → process_bigraph-1.2.1}/process_bigraph/processes/examples.py +0 -0
- {process_bigraph-1.1.7 → process_bigraph-1.2.1}/process_bigraph/processes/growth_division.py +0 -0
- {process_bigraph-1.1.7 → process_bigraph-1.2.1}/process_bigraph/processes/math_expression.py +0 -0
- {process_bigraph-1.1.7 → process_bigraph-1.2.1}/process_bigraph/processes/parameter_scan.py +0 -0
- {process_bigraph-1.1.7 → process_bigraph-1.2.1}/process_bigraph/processes/reaction.py +0 -0
- {process_bigraph-1.1.7 → process_bigraph-1.2.1}/process_bigraph/protocols/__init__.py +0 -0
- {process_bigraph-1.1.7 → process_bigraph-1.2.1}/process_bigraph/protocols/parallel.py +0 -0
- {process_bigraph-1.1.7 → process_bigraph-1.2.1}/process_bigraph/protocols/rest.py +0 -0
- {process_bigraph-1.1.7 → process_bigraph-1.2.1}/process_bigraph/protocols/socket.py +0 -0
- {process_bigraph-1.1.7 → process_bigraph-1.2.1}/process_bigraph/run.py +0 -0
- {process_bigraph-1.1.7 → process_bigraph-1.2.1}/process_bigraph/run_step.py +0 -0
- {process_bigraph-1.1.7 → process_bigraph-1.2.1}/process_bigraph/types/__init__.py +0 -0
- {process_bigraph-1.1.7 → process_bigraph-1.2.1}/process_bigraph/types/process.py +0 -0
- {process_bigraph-1.1.7 → process_bigraph-1.2.1}/process_bigraph/units.py +0 -0
- {process_bigraph-1.1.7 → process_bigraph-1.2.1}/process_bigraph.egg-info/SOURCES.txt +0 -0
- {process_bigraph-1.1.7 → process_bigraph-1.2.1}/process_bigraph.egg-info/dependency_links.txt +0 -0
- {process_bigraph-1.1.7 → process_bigraph-1.2.1}/process_bigraph.egg-info/requires.txt +0 -0
- {process_bigraph-1.1.7 → process_bigraph-1.2.1}/process_bigraph.egg-info/top_level.txt +0 -0
- {process_bigraph-1.1.7 → process_bigraph-1.2.1}/pytest.ini +0 -0
- {process_bigraph-1.1.7 → process_bigraph-1.2.1}/release.sh +0 -0
- {process_bigraph-1.1.7 → process_bigraph-1.2.1}/setup.cfg +0 -0
|
@@ -32,6 +32,7 @@ from bigraph_schema import (
|
|
|
32
32
|
is_schema_key, strip_schema_keys)
|
|
33
33
|
|
|
34
34
|
from bigraph_schema.protocols import local_lookup_module
|
|
35
|
+
from bigraph_schema.methods.events import NodeAdded, NodeRemoved, Divided
|
|
35
36
|
|
|
36
37
|
|
|
37
38
|
# =========================
|
|
@@ -1281,6 +1282,14 @@ class Composite(Process):
|
|
|
1281
1282
|
# extensions: yes; pure-Python loops: no).
|
|
1282
1283
|
'parallel_steps': 'boolean{false}',
|
|
1283
1284
|
'parallel_workers': 'maybe[integer]',
|
|
1285
|
+
# When True, ``initialize`` trusts ``state`` as-is and skips the
|
|
1286
|
+
# per-process ``link_state`` + ``combine`` pass that normally
|
|
1287
|
+
# seeds initial values. Set this when loading from a saved
|
|
1288
|
+
# bundle (e.g. daughter_state from a workflow generation):
|
|
1289
|
+
# the saved state already contains everything processes would
|
|
1290
|
+
# have contributed, and re-combining their ``initial_state()``
|
|
1291
|
+
# overwrites correctly-divided values with mother-shaped ones.
|
|
1292
|
+
'skip_process_state': 'boolean{false}',
|
|
1284
1293
|
}
|
|
1285
1294
|
|
|
1286
1295
|
|
|
@@ -1336,37 +1345,43 @@ class Composite(Process):
|
|
|
1336
1345
|
self.edge_paths = {**self.process_paths, **self.step_paths}
|
|
1337
1346
|
|
|
1338
1347
|
# Initialize each process/step's state and accumulate it into a unified state tree.
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1348
|
+
# ``skip_process_state`` short-circuits this when the caller supplied
|
|
1349
|
+
# state that already contains everything processes would have seeded
|
|
1350
|
+
# (e.g. a daughter_state bundle from a previous workflow generation).
|
|
1351
|
+
# Without the skip, ``combine`` overwrites correctly-divided values
|
|
1352
|
+
# with full mother-shaped ones from each process's ``initial_state()``.
|
|
1353
|
+
if not self.config.get('skip_process_state', False):
|
|
1354
|
+
edge_schema = {}
|
|
1355
|
+
edge_state = {}
|
|
1356
|
+
for path, edge in self.edge_paths.items():
|
|
1357
|
+
# Generate the initial state for this specific edge (process or step).
|
|
1358
|
+
initial_schema, initial_state = self.core.link_state(
|
|
1359
|
+
edge,
|
|
1360
|
+
path)
|
|
1361
|
+
|
|
1362
|
+
# Merge the new edge state with the global state tree, checking for conflicts.
|
|
1363
|
+
try:
|
|
1364
|
+
edge_schema, edge_state = self.core.combine(
|
|
1365
|
+
edge_schema, edge_state,
|
|
1366
|
+
initial_schema, initial_state)
|
|
1367
|
+
|
|
1368
|
+
except Exception as e:
|
|
1369
|
+
import sys as _sys
|
|
1370
|
+
_sys.stderr.write(f'[INIT_COMBINE_FAIL] edge={path}\n')
|
|
1371
|
+
_sys.stderr.write(f'[INIT_COMBINE_FAIL] new_schema={initial_schema}\n')
|
|
1372
|
+
_sys.stderr.write(f'[INIT_COMBINE_FAIL] err={e}\n')
|
|
1373
|
+
_sys.stderr.flush()
|
|
1374
|
+
raise Exception(
|
|
1375
|
+
f'initial state from edge does not match initial state from other edges:\n'
|
|
1376
|
+
f'{path}\n{edge}\n{edge_state}\n'
|
|
1377
|
+
f'{e}'
|
|
1378
|
+
)
|
|
1379
|
+
|
|
1380
|
+
# Apply the merged edge_state into the global state and update instance paths.
|
|
1381
|
+
if edge_state:
|
|
1382
|
+
self.schema, self.state = self.core.combine(
|
|
1350
1383
|
edge_schema, edge_state,
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
except Exception as e:
|
|
1354
|
-
import sys as _sys
|
|
1355
|
-
_sys.stderr.write(f'[INIT_COMBINE_FAIL] edge={path}\n')
|
|
1356
|
-
_sys.stderr.write(f'[INIT_COMBINE_FAIL] new_schema={initial_schema}\n')
|
|
1357
|
-
_sys.stderr.write(f'[INIT_COMBINE_FAIL] err={e}\n')
|
|
1358
|
-
_sys.stderr.flush()
|
|
1359
|
-
raise Exception(
|
|
1360
|
-
f'initial state from edge does not match initial state from other edges:\n'
|
|
1361
|
-
f'{path}\n{edge}\n{edge_state}\n'
|
|
1362
|
-
f'{e}'
|
|
1363
|
-
)
|
|
1364
|
-
|
|
1365
|
-
# Apply the merged edge_state into the global state and update instance paths.
|
|
1366
|
-
if edge_state:
|
|
1367
|
-
self.schema, self.state = self.core.combine(
|
|
1368
|
-
edge_schema, edge_state,
|
|
1369
|
-
self.schema, self.state)
|
|
1384
|
+
self.schema, self.state)
|
|
1370
1385
|
|
|
1371
1386
|
# Wire the input/output schema for the Composite from the bridge config.
|
|
1372
1387
|
self.process_schema = {
|
|
@@ -1464,6 +1479,94 @@ class Composite(Process):
|
|
|
1464
1479
|
# do we want to do anything with these?
|
|
1465
1480
|
removed_front = self.front.pop(removed_key)
|
|
1466
1481
|
|
|
1482
|
+
def _apply_structural_events(self, events: List[Any]) -> None:
|
|
1483
|
+
"""Update process/step indexes from apply-emitted structural events.
|
|
1484
|
+
|
|
1485
|
+
Replaces the full-state ``find_instance_paths`` rescan when apply
|
|
1486
|
+
tells us exactly which subtrees changed. For each:
|
|
1487
|
+
- ``NodeAdded``: scan the new subtree only and merge any
|
|
1488
|
+
discovered Process/Step instances into the existing index.
|
|
1489
|
+
- ``NodeRemoved``: drop entries at-or-under the removed path.
|
|
1490
|
+
- ``Divided``: drop the mother's entries, scan each daughter
|
|
1491
|
+
subtree.
|
|
1492
|
+
|
|
1493
|
+
The compiled-apply cache is invalidated unconditionally because
|
|
1494
|
+
``apply(dict)``'s ``_divide`` branch mutates schemas in place.
|
|
1495
|
+
|
|
1496
|
+
Step network rebuild is conditional on step_paths actually
|
|
1497
|
+
changing — value-only structural events that don't add/remove
|
|
1498
|
+
any Step instances skip the rebuild.
|
|
1499
|
+
"""
|
|
1500
|
+
self.core.invalidate_compiled_apply()
|
|
1501
|
+
|
|
1502
|
+
previous_step_paths = (set(self.step_paths.keys())
|
|
1503
|
+
if hasattr(self, 'step_paths') else set())
|
|
1504
|
+
if not hasattr(self, 'step_paths'):
|
|
1505
|
+
self.step_paths = {}
|
|
1506
|
+
|
|
1507
|
+
def _index_subtree(root_path: tuple, subtree_state: Any) -> None:
|
|
1508
|
+
"""Walk subtree, merge Process/Step instances into indexes."""
|
|
1509
|
+
# find_instances takes a dict; if subtree is a dict, walk;
|
|
1510
|
+
# if it's a leaf or wrapped instance, check at this level.
|
|
1511
|
+
if not isinstance(subtree_state, dict):
|
|
1512
|
+
return
|
|
1513
|
+
instance = subtree_state.get('instance')
|
|
1514
|
+
from process_bigraph.composite import Process as _Proc
|
|
1515
|
+
from process_bigraph.composite import Step as _Step
|
|
1516
|
+
if isinstance(instance, _Proc):
|
|
1517
|
+
self.process_paths[root_path] = subtree_state
|
|
1518
|
+
return # Don't recurse into a process's internal state
|
|
1519
|
+
if isinstance(instance, _Step):
|
|
1520
|
+
self.step_paths[root_path] = subtree_state
|
|
1521
|
+
return
|
|
1522
|
+
# Recurse into non-instance dict
|
|
1523
|
+
for key, child in subtree_state.items():
|
|
1524
|
+
if is_schema_key(key):
|
|
1525
|
+
continue
|
|
1526
|
+
_index_subtree(root_path + (key,), child)
|
|
1527
|
+
|
|
1528
|
+
def _drop_under(root_path: tuple) -> None:
|
|
1529
|
+
"""Remove process/step/front entries at-or-under root_path."""
|
|
1530
|
+
for path in list(self.process_paths.keys()):
|
|
1531
|
+
if path == root_path or path[:len(root_path)] == root_path:
|
|
1532
|
+
del self.process_paths[path]
|
|
1533
|
+
for path in list(self.step_paths.keys()):
|
|
1534
|
+
if path == root_path or path[:len(root_path)] == root_path:
|
|
1535
|
+
del self.step_paths[path]
|
|
1536
|
+
for path in list(self.front.keys()):
|
|
1537
|
+
if path == root_path or path[:len(root_path)] == root_path:
|
|
1538
|
+
del self.front[path]
|
|
1539
|
+
|
|
1540
|
+
for event in events:
|
|
1541
|
+
if isinstance(event, NodeAdded):
|
|
1542
|
+
added_path = event.path + (event.key,)
|
|
1543
|
+
# Read fresh subtree from realized state — event.state is
|
|
1544
|
+
# pre-realize (no instances yet).
|
|
1545
|
+
subtree = get_path(self.state, list(added_path))
|
|
1546
|
+
if subtree is not None:
|
|
1547
|
+
_index_subtree(added_path, subtree)
|
|
1548
|
+
elif isinstance(event, NodeRemoved):
|
|
1549
|
+
removed_path = event.path + (event.key,)
|
|
1550
|
+
_drop_under(removed_path)
|
|
1551
|
+
elif isinstance(event, Divided):
|
|
1552
|
+
mother_path = event.path + (event.mother_key,)
|
|
1553
|
+
_drop_under(mother_path)
|
|
1554
|
+
for d_key in event.daughter_keys:
|
|
1555
|
+
d_path = event.path + (d_key,)
|
|
1556
|
+
subtree = get_path(self.state, list(d_path))
|
|
1557
|
+
if subtree is not None:
|
|
1558
|
+
_index_subtree(d_path, subtree)
|
|
1559
|
+
|
|
1560
|
+
# Step network only needs rebuild if step_paths changed
|
|
1561
|
+
current_step_paths = set(self.step_paths.keys())
|
|
1562
|
+
if current_step_paths != previous_step_paths:
|
|
1563
|
+
self.build_step_network()
|
|
1564
|
+
|
|
1565
|
+
# Sync front buffer: drop entries for paths no longer present
|
|
1566
|
+
all_paths = set(self.process_paths.keys()) | current_step_paths
|
|
1567
|
+
for removed_key in set(self.front.keys()).difference(all_paths):
|
|
1568
|
+
self.front.pop(removed_key)
|
|
1569
|
+
|
|
1467
1570
|
def _build_view_project_cache(self) -> None:
|
|
1468
1571
|
"""Precompile view/project operations for each process path.
|
|
1469
1572
|
|
|
@@ -1768,7 +1871,12 @@ class Composite(Process):
|
|
|
1768
1871
|
from process_bigraph.bundle import load_bundle
|
|
1769
1872
|
|
|
1770
1873
|
document = load_bundle(bundle_dir, as_numpy=True)
|
|
1771
|
-
|
|
1874
|
+
# The bundle's state is authoritative — it was saved post-init
|
|
1875
|
+
# so it already contains every contribution the per-process
|
|
1876
|
+
# ``link_state`` pass would re-add. Combining ``initial_state()``
|
|
1877
|
+
# on top here would clobber correctly-divided values (e.g. a
|
|
1878
|
+
# daughter's halved bulk array) with mother-shaped originals.
|
|
1879
|
+
config = {'skip_process_state': True, **document, **kwargs}
|
|
1772
1880
|
return cls(config, core=core)
|
|
1773
1881
|
|
|
1774
1882
|
|
|
@@ -2036,12 +2144,13 @@ class Composite(Process):
|
|
|
2036
2144
|
step = get_path(self.state, step_path)
|
|
2037
2145
|
state = self._cached_view(step_path)
|
|
2038
2146
|
instance = step.get('instance')
|
|
2039
|
-
|
|
2147
|
+
# _view_resolved emits only port-key entries; no schema
|
|
2148
|
+
# keys to strip. perform_update sees the same dict.
|
|
2040
2149
|
if instance is not None and hasattr(instance, 'perform_update'):
|
|
2041
|
-
if not instance.perform_update(
|
|
2150
|
+
if not instance.perform_update(state):
|
|
2042
2151
|
return None
|
|
2043
2152
|
return self.process_update(
|
|
2044
|
-
step_path, step,
|
|
2153
|
+
step_path, step, state, -1.0, 'outputs',
|
|
2045
2154
|
already_clean=True)
|
|
2046
2155
|
# list() forces all futures to resolve before continuing
|
|
2047
2156
|
updates = [u for u in pool.map(_run_one, step_paths) if u is not None]
|
|
@@ -2056,17 +2165,15 @@ class Composite(Process):
|
|
|
2056
2165
|
state = self._cached_view(step_path)
|
|
2057
2166
|
|
|
2058
2167
|
instance = step.get('instance')
|
|
2059
|
-
#
|
|
2060
|
-
# perform_update
|
|
2061
|
-
# (invoke trusts the caller and skips its own gate).
|
|
2062
|
-
clean_state = strip_schema_keys(state)
|
|
2168
|
+
# _view_resolved emits only port-key entries; no schema
|
|
2169
|
+
# keys to strip. perform_update sees the same dict.
|
|
2063
2170
|
if instance is not None and hasattr(instance, 'perform_update'):
|
|
2064
|
-
if not instance.perform_update(
|
|
2171
|
+
if not instance.perform_update(state):
|
|
2065
2172
|
continue
|
|
2066
2173
|
|
|
2067
2174
|
# Steps are always invoked with interval = -1.0
|
|
2068
2175
|
step_update = self.process_update(
|
|
2069
|
-
step_path, step,
|
|
2176
|
+
step_path, step, state, -1.0, 'outputs',
|
|
2070
2177
|
already_clean=True)
|
|
2071
2178
|
|
|
2072
2179
|
updates.append(step_update)
|
|
@@ -2074,16 +2181,6 @@ class Composite(Process):
|
|
|
2074
2181
|
update_paths = self.apply_updates(updates)
|
|
2075
2182
|
self.expire_process_paths(update_paths)
|
|
2076
2183
|
|
|
2077
|
-
# Opt-in halt: caller (e.g. EcoliSim) sets
|
|
2078
|
-
# ``_halt_after_structural`` to stop the cascade after a
|
|
2079
|
-
# divide/add/remove apply so newly-spawned processes don't
|
|
2080
|
-
# run on the tick that birthed them. Default behavior is
|
|
2081
|
-
# to continue cascading (test_dynamic_structure relies on
|
|
2082
|
-
# this for spawn chains).
|
|
2083
|
-
if (getattr(self, '_halt_after_structural', False)
|
|
2084
|
-
and getattr(self, '_last_apply_structural', False)):
|
|
2085
|
-
return
|
|
2086
|
-
|
|
2087
2184
|
to_run = self.cycle_step_state()
|
|
2088
2185
|
|
|
2089
2186
|
if to_run:
|
|
@@ -2155,19 +2252,6 @@ class Composite(Process):
|
|
|
2155
2252
|
update_paths.append(('global_time',)) # updated global time can trigger steps
|
|
2156
2253
|
self.expire_process_paths(update_paths)
|
|
2157
2254
|
self.steps_run = set() # Reset for new timestep
|
|
2158
|
-
# Caller can request an early-return after a structural
|
|
2159
|
-
# apply (e.g. division) by setting
|
|
2160
|
-
# ``self._halt_after_structural = True`` *before* the
|
|
2161
|
-
# cascade fires. EcoliSim uses this to stop after
|
|
2162
|
-
# divide so daughters save with post-divide-pre-tick
|
|
2163
|
-
# values, matching v1 handoff (mother's last tick
|
|
2164
|
-
# didn't run on daughters). Dynamic-structure tests
|
|
2165
|
-
# leave the flag False and get the cascading
|
|
2166
|
-
# spawn/divide trigger_steps behavior as before.
|
|
2167
|
-
if (getattr(self, '_halt_after_structural', False)
|
|
2168
|
-
and getattr(self, '_last_apply_structural', False)):
|
|
2169
|
-
self.framework_time += _time.monotonic() - fw_start
|
|
2170
|
-
return
|
|
2171
2255
|
self.trigger_steps(update_paths)
|
|
2172
2256
|
self.framework_time += _time.monotonic() - fw_start
|
|
2173
2257
|
|
|
@@ -2244,7 +2328,12 @@ class Composite(Process):
|
|
|
2244
2328
|
|
|
2245
2329
|
# Only proceed if the next step occurs within the target range
|
|
2246
2330
|
if future <= end_time:
|
|
2247
|
-
|
|
2331
|
+
# state came from _cached_view (or future_front whose source
|
|
2332
|
+
# is also _cached_view) — _view_resolved only emits port-key
|
|
2333
|
+
# entries, so the dict has no schema keys to strip.
|
|
2334
|
+
update = self.process_update(
|
|
2335
|
+
path, process, state, process_interval,
|
|
2336
|
+
already_clean=True)
|
|
2248
2337
|
|
|
2249
2338
|
# Store the update to apply when simulation reaches `future` time
|
|
2250
2339
|
self.front[path]['time'] = future
|
|
@@ -2352,39 +2441,6 @@ class Composite(Process):
|
|
|
2352
2441
|
return True
|
|
2353
2442
|
return False
|
|
2354
2443
|
|
|
2355
|
-
@staticmethod
|
|
2356
|
-
def _walk_update(state: Any, path: tuple = ()) -> tuple:
|
|
2357
|
-
"""Single-pass walk over an update tree.
|
|
2358
|
-
|
|
2359
|
-
Combines hierarchy_depth and _has_structural_keys into one
|
|
2360
|
-
traversal — both are called on every apply_updates phase 1
|
|
2361
|
-
invocation and were duplicating the same recursive walk.
|
|
2362
|
-
|
|
2363
|
-
Returns (paths_list, has_structural) where paths_list mirrors
|
|
2364
|
-
what hierarchy_depth would have returned (list of leaf path
|
|
2365
|
-
tuples) and has_structural is True if any _add/_remove/_type
|
|
2366
|
-
sentinel was found anywhere in the tree.
|
|
2367
|
-
"""
|
|
2368
|
-
if not isinstance(state, dict):
|
|
2369
|
-
return [path], False
|
|
2370
|
-
paths = []
|
|
2371
|
-
has_structural = False
|
|
2372
|
-
for key, value in state.items():
|
|
2373
|
-
if isinstance(key, str) and key.startswith('_'):
|
|
2374
|
-
# Schema key — note any structural sentinels.
|
|
2375
|
-
# _divide is structural because it replaces a mother
|
|
2376
|
-
# with two daughters whose process instances need to
|
|
2377
|
-
# be re-discovered by find_instance_paths.
|
|
2378
|
-
if key in ('_add', '_remove', '_type', '_divide'):
|
|
2379
|
-
has_structural = True
|
|
2380
|
-
paths.append(path)
|
|
2381
|
-
continue
|
|
2382
|
-
sub_paths, sub_struct = Composite._walk_update(value, path + (key,))
|
|
2383
|
-
paths.extend(sub_paths)
|
|
2384
|
-
if sub_struct:
|
|
2385
|
-
has_structural = True
|
|
2386
|
-
return paths, has_structural
|
|
2387
|
-
|
|
2388
2444
|
def apply_updates(self, updates: List["Defer"]) -> List[Union[str, Tuple[str, ...]]]:
|
|
2389
2445
|
"""
|
|
2390
2446
|
Apply a series of deferred updates and record the resulting bridge outputs.
|
|
@@ -2401,10 +2457,12 @@ class Composite(Process):
|
|
|
2401
2457
|
"""
|
|
2402
2458
|
update_paths = []
|
|
2403
2459
|
had_structural_changes = False
|
|
2460
|
+
had_structural_sentinels = False # _add/_remove/_divide detected
|
|
2404
2461
|
|
|
2405
|
-
# Phase 1: Resolve all deferred updates and collect them
|
|
2462
|
+
# Phase 1: Resolve all deferred updates and collect them.
|
|
2463
|
+
# Path discovery + structural detection happen during reconcile
|
|
2464
|
+
# (Phase 2) via a ReconcileSummary sink — no separate walk pass.
|
|
2406
2465
|
resolved_updates = []
|
|
2407
|
-
had_structural_sentinels = False # _add/_remove/_divide detected
|
|
2408
2466
|
for defer in updates:
|
|
2409
2467
|
series = defer.get()
|
|
2410
2468
|
if series is None:
|
|
@@ -2413,18 +2471,6 @@ class Composite(Process):
|
|
|
2413
2471
|
series = [series]
|
|
2414
2472
|
|
|
2415
2473
|
for update_schema, update_state in series:
|
|
2416
|
-
# Single-pass walk: collects paths AND detects structural
|
|
2417
|
-
# change sentinels in one traversal. (Cache attempts here
|
|
2418
|
-
# have foundered on the fact that the update *value* shape
|
|
2419
|
-
# — not just the schema id — drives the leaf paths, and
|
|
2420
|
-
# dynamic-structure processes emit varying shapes per
|
|
2421
|
-
# tick. Walk-on-every-call costs ~1.3 ms/tick but is
|
|
2422
|
-
# always correct.)
|
|
2423
|
-
walk_paths, walk_struct = self._walk_update(update_state)
|
|
2424
|
-
update_paths.extend(walk_paths)
|
|
2425
|
-
if walk_struct and not had_structural_sentinels:
|
|
2426
|
-
had_structural_sentinels = True
|
|
2427
|
-
|
|
2428
2474
|
# read_bridge fast-paths to None when no bridge outputs
|
|
2429
2475
|
# are configured (vEcoli's case) — no walk happens.
|
|
2430
2476
|
bridge_update = self.read_bridge(update_state)
|
|
@@ -2463,9 +2509,22 @@ class Composite(Process):
|
|
|
2463
2509
|
combined_schema,
|
|
2464
2510
|
)
|
|
2465
2511
|
|
|
2466
|
-
# Reconcile all update states using the combined schema
|
|
2467
|
-
|
|
2468
|
-
|
|
2512
|
+
# Reconcile all update states using the combined schema.
|
|
2513
|
+
# Install a summary sink so reconcile populates leaf paths
|
|
2514
|
+
# and the structural-sentinel flag during its existing walk
|
|
2515
|
+
# — eliminates the redundant per-defer _walk_update pass.
|
|
2516
|
+
from bigraph_schema.methods.events import (
|
|
2517
|
+
ReconcileSummary, install_reconcile_sink, uninstall_reconcile_sink)
|
|
2518
|
+
summary = ReconcileSummary(paths=[])
|
|
2519
|
+
prev_sink = install_reconcile_sink(summary)
|
|
2520
|
+
try:
|
|
2521
|
+
all_states = [state for _, state in resolved_updates]
|
|
2522
|
+
combined_update = self.core.reconcile(combined_schema, all_states)
|
|
2523
|
+
finally:
|
|
2524
|
+
uninstall_reconcile_sink(prev_sink)
|
|
2525
|
+
update_paths = summary.paths
|
|
2526
|
+
if summary.has_structural:
|
|
2527
|
+
had_structural_sentinels = True
|
|
2469
2528
|
|
|
2470
2529
|
if combined_update:
|
|
2471
2530
|
# An update that lands inside an existing process's
|
|
@@ -2491,10 +2550,18 @@ class Composite(Process):
|
|
|
2491
2550
|
self.schema, combined_schema)
|
|
2492
2551
|
else:
|
|
2493
2552
|
apply_schema = combined_schema
|
|
2553
|
+
# Collect structural events (NodeAdded/NodeRemoved/
|
|
2554
|
+
# Divided) emitted by sentinel handlers during apply.
|
|
2555
|
+
# On structural ticks, used to update process_paths/
|
|
2556
|
+
# step_paths indexes incrementally instead of re-scanning
|
|
2557
|
+
# the full state via find_instance_paths.
|
|
2558
|
+
structural_events = [] if had_structural_sentinels else None
|
|
2494
2559
|
self.state, merges = self.core.apply(
|
|
2495
2560
|
apply_schema,
|
|
2496
2561
|
self.state,
|
|
2497
|
-
combined_update
|
|
2562
|
+
combined_update,
|
|
2563
|
+
update_has_structural=had_structural_sentinels,
|
|
2564
|
+
events=structural_events)
|
|
2498
2565
|
# For structural sentinels, apply may have mutated
|
|
2499
2566
|
# apply_schema in place (e.g. _divide pops/inserts
|
|
2500
2567
|
# keys in a dict schema). Propagate back to self.schema
|
|
@@ -2520,11 +2587,18 @@ class Composite(Process):
|
|
|
2520
2587
|
if had_structural_sentinels:
|
|
2521
2588
|
# Real structural change: processes may have been
|
|
2522
2589
|
# added/removed/replaced. Realize new state (to
|
|
2523
|
-
# instantiate added process declarations) then
|
|
2524
|
-
#
|
|
2590
|
+
# instantiate added process declarations) then update
|
|
2591
|
+
# the process/step indexes from the events apply emitted.
|
|
2525
2592
|
self.schema, self.state = self.core.realize(
|
|
2526
2593
|
self.schema, self.state)
|
|
2527
|
-
|
|
2594
|
+
if structural_events:
|
|
2595
|
+
self._apply_structural_events(structural_events)
|
|
2596
|
+
else:
|
|
2597
|
+
# Fallback: had_structural_sentinels was set but no
|
|
2598
|
+
# events emitted (e.g. _update_touches_process_path
|
|
2599
|
+
# detected an in-process update with no _add/_remove/
|
|
2600
|
+
# _divide sentinel). Full rescan is correct here.
|
|
2601
|
+
self.find_instance_paths(self.state)
|
|
2528
2602
|
self._build_view_project_cache()
|
|
2529
2603
|
if hasattr(self, 'expire_layer_walk_cache'):
|
|
2530
2604
|
self.expire_layer_walk_cache()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{process_bigraph-1.1.7 → process_bigraph-1.2.1}/process_bigraph/experiments/minimal_gillespie.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{process_bigraph-1.1.7 → process_bigraph-1.2.1}/process_bigraph/processes/dynamic_structure.py
RENAMED
|
File without changes
|
|
File without changes
|
{process_bigraph-1.1.7 → process_bigraph-1.2.1}/process_bigraph/processes/growth_division.py
RENAMED
|
File without changes
|
{process_bigraph-1.1.7 → process_bigraph-1.2.1}/process_bigraph/processes/math_expression.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{process_bigraph-1.1.7 → process_bigraph-1.2.1}/process_bigraph.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|