process-bigraph 1.0.6__tar.gz → 1.0.7__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.0.6/process_bigraph.egg-info → process_bigraph-1.0.7}/PKG-INFO +1 -1
- {process_bigraph-1.0.6 → process_bigraph-1.0.7}/process_bigraph/composite.py +133 -23
- {process_bigraph-1.0.6 → process_bigraph-1.0.7/process_bigraph.egg-info}/PKG-INFO +1 -1
- {process_bigraph-1.0.6 → process_bigraph-1.0.7}/pyproject.toml +1 -1
- {process_bigraph-1.0.6 → process_bigraph-1.0.7}/AUTHORS.md +0 -0
- {process_bigraph-1.0.6 → process_bigraph-1.0.7}/LICENSE +0 -0
- {process_bigraph-1.0.6 → process_bigraph-1.0.7}/README.md +0 -0
- {process_bigraph-1.0.6 → process_bigraph-1.0.7}/process_bigraph/__init__.py +0 -0
- {process_bigraph-1.0.6 → process_bigraph-1.0.7}/process_bigraph/emitter.py +0 -0
- {process_bigraph-1.0.6 → process_bigraph-1.0.7}/process_bigraph/experiments/__init__.py +0 -0
- {process_bigraph-1.0.6 → process_bigraph-1.0.7}/process_bigraph/experiments/minimal_gillespie.py +0 -0
- {process_bigraph-1.0.6 → process_bigraph-1.0.7}/process_bigraph/processes/__init__.py +0 -0
- {process_bigraph-1.0.6 → process_bigraph-1.0.7}/process_bigraph/processes/examples.py +0 -0
- {process_bigraph-1.0.6 → process_bigraph-1.0.7}/process_bigraph/processes/growth_division.py +0 -0
- {process_bigraph-1.0.6 → process_bigraph-1.0.7}/process_bigraph/processes/math_expression.py +0 -0
- {process_bigraph-1.0.6 → process_bigraph-1.0.7}/process_bigraph/processes/parameter_scan.py +0 -0
- {process_bigraph-1.0.6 → process_bigraph-1.0.7}/process_bigraph/protocols/__init__.py +0 -0
- {process_bigraph-1.0.6 → process_bigraph-1.0.7}/process_bigraph/protocols/parallel.py +0 -0
- {process_bigraph-1.0.6 → process_bigraph-1.0.7}/process_bigraph/protocols/rest.py +0 -0
- {process_bigraph-1.0.6 → process_bigraph-1.0.7}/process_bigraph/protocols/socket.py +0 -0
- {process_bigraph-1.0.6 → process_bigraph-1.0.7}/process_bigraph/run.py +0 -0
- {process_bigraph-1.0.6 → process_bigraph-1.0.7}/process_bigraph/types/__init__.py +0 -0
- {process_bigraph-1.0.6 → process_bigraph-1.0.7}/process_bigraph/types/process.py +0 -0
- {process_bigraph-1.0.6 → process_bigraph-1.0.7}/process_bigraph/units.py +0 -0
- {process_bigraph-1.0.6 → process_bigraph-1.0.7}/process_bigraph.egg-info/SOURCES.txt +0 -0
- {process_bigraph-1.0.6 → process_bigraph-1.0.7}/process_bigraph.egg-info/dependency_links.txt +0 -0
- {process_bigraph-1.0.6 → process_bigraph-1.0.7}/process_bigraph.egg-info/requires.txt +0 -0
- {process_bigraph-1.0.6 → process_bigraph-1.0.7}/process_bigraph.egg-info/top_level.txt +0 -0
- {process_bigraph-1.0.6 → process_bigraph-1.0.7}/setup.cfg +0 -0
|
@@ -16,6 +16,8 @@ import os
|
|
|
16
16
|
import copy
|
|
17
17
|
import json
|
|
18
18
|
import math
|
|
19
|
+
import numpy as np
|
|
20
|
+
|
|
19
21
|
from typing import (
|
|
20
22
|
Any, Dict, List, Optional, Set, Tuple, Union,
|
|
21
23
|
Mapping, MutableMapping, Sequence,
|
|
@@ -692,6 +694,9 @@ class Process(Open):
|
|
|
692
694
|
update = self.update(state, interval)
|
|
693
695
|
return SyncUpdate(update)
|
|
694
696
|
|
|
697
|
+
def calculate_timestep(self, interval, state):
|
|
698
|
+
return interval
|
|
699
|
+
|
|
695
700
|
def update(self, state: Dict[str, Any], interval: float) -> Dict[str, Any]:
|
|
696
701
|
"""
|
|
697
702
|
Override this method to implement the process logic.
|
|
@@ -906,7 +911,8 @@ class Composite(Process):
|
|
|
906
911
|
'inputs': 'wires',
|
|
907
912
|
'outputs': 'wires'
|
|
908
913
|
},
|
|
909
|
-
'global_time_precision': 'maybe[float]'
|
|
914
|
+
'global_time_precision': 'maybe[float]',
|
|
915
|
+
'skip_initial_steps': 'maybe[boolean]'
|
|
910
916
|
}
|
|
911
917
|
|
|
912
918
|
|
|
@@ -1011,11 +1017,16 @@ class Composite(Process):
|
|
|
1011
1017
|
# A buffer for updates to be emitted at the composite's output interface.
|
|
1012
1018
|
self.bridge_updates: List[Any] = []
|
|
1013
1019
|
|
|
1020
|
+
# Precompile view/project operations for fast runtime access.
|
|
1021
|
+
self._compiled_links = {}
|
|
1022
|
+
self._build_view_project_cache()
|
|
1023
|
+
|
|
1014
1024
|
# Build the dependency network between steps and determine which steps should run first.
|
|
1015
1025
|
self.build_step_network()
|
|
1016
1026
|
|
|
1017
1027
|
# Run all steps that are ready on the first cycle.
|
|
1018
|
-
self.
|
|
1028
|
+
if not self.config.get('skip_initial_steps', False):
|
|
1029
|
+
self.run_steps(self.to_run)
|
|
1019
1030
|
|
|
1020
1031
|
@classmethod
|
|
1021
1032
|
def load(cls, path: str, core: Optional[Any] = None) -> "Composite":
|
|
@@ -1064,6 +1075,42 @@ class Composite(Process):
|
|
|
1064
1075
|
# do we want to do anything with these?
|
|
1065
1076
|
removed_front = self.front.pop(removed_key)
|
|
1066
1077
|
|
|
1078
|
+
def _build_view_project_cache(self) -> None:
|
|
1079
|
+
"""Precompile view/project operations for each process path.
|
|
1080
|
+
|
|
1081
|
+
Delegates to core.precompile_link() which pre-resolves wire paths
|
|
1082
|
+
and precomputes projection schemas so that runtime view/project
|
|
1083
|
+
calls bypass schema traversal entirely.
|
|
1084
|
+
"""
|
|
1085
|
+
self._compiled_links = {}
|
|
1086
|
+
|
|
1087
|
+
for path in list(self.process_paths) + list(self.step_paths):
|
|
1088
|
+
compiled = self.core.precompile_link(
|
|
1089
|
+
self.schema, self.state, path)
|
|
1090
|
+
if compiled is not None:
|
|
1091
|
+
self._compiled_links[path] = compiled
|
|
1092
|
+
|
|
1093
|
+
def _invalidate_caches(self) -> None:
|
|
1094
|
+
"""Invalidate precompiled link caches, forcing rebuild on next use."""
|
|
1095
|
+
self._compiled_links = {}
|
|
1096
|
+
|
|
1097
|
+
def _cached_view(self, path: Tuple[str, ...]) -> Dict[str, Any]:
|
|
1098
|
+
"""Fast view using precompiled link when available."""
|
|
1099
|
+
compiled = self._compiled_links.get(path)
|
|
1100
|
+
if compiled is not None and compiled.get('view') is not None:
|
|
1101
|
+
return self.core.view_fast(compiled['view'], self.state)
|
|
1102
|
+
return self.core.view(self.schema, self.state, path)
|
|
1103
|
+
|
|
1104
|
+
def _cached_project(self, path: Tuple[str, ...], view: Any,
|
|
1105
|
+
ports_key: str = 'outputs') -> Any:
|
|
1106
|
+
"""Fast project using precompiled link when available."""
|
|
1107
|
+
if ports_key == 'outputs':
|
|
1108
|
+
compiled = self._compiled_links.get(path)
|
|
1109
|
+
if compiled is not None and compiled.get('project') is not None:
|
|
1110
|
+
return self.core.project_ports_fast(compiled['project'], view)
|
|
1111
|
+
return self.core.project(
|
|
1112
|
+
self.schema, self.state, path, view, ports_key)
|
|
1113
|
+
|
|
1067
1114
|
def merge(self, schema: Dict[str, Any], state: Dict[str, Any], path: Optional[List[str]] = None) -> None:
|
|
1068
1115
|
"""
|
|
1069
1116
|
Merge a new schema/state subtree into the Composite.
|
|
@@ -1082,6 +1129,7 @@ class Composite(Process):
|
|
|
1082
1129
|
state)
|
|
1083
1130
|
|
|
1084
1131
|
self.find_instance_paths(self.state)
|
|
1132
|
+
self._build_view_project_cache()
|
|
1085
1133
|
|
|
1086
1134
|
def merge_schema(
|
|
1087
1135
|
self,
|
|
@@ -1173,8 +1221,17 @@ class Composite(Process):
|
|
|
1173
1221
|
|
|
1174
1222
|
os.makedirs(outdir, exist_ok=True)
|
|
1175
1223
|
filepath = os.path.join(outdir, filename)
|
|
1176
|
-
|
|
1177
|
-
|
|
1224
|
+
# outjson = json.dumps(
|
|
1225
|
+
# document,
|
|
1226
|
+
# default=encode_key)
|
|
1227
|
+
|
|
1228
|
+
with open(filepath, 'w') as outfile:
|
|
1229
|
+
json.dump(
|
|
1230
|
+
document,
|
|
1231
|
+
outfile,
|
|
1232
|
+
indent=2,
|
|
1233
|
+
default=encode_key)
|
|
1234
|
+
|
|
1178
1235
|
print(f"Saved composite to {filepath}")
|
|
1179
1236
|
|
|
1180
1237
|
|
|
@@ -1463,11 +1520,10 @@ class Composite(Process):
|
|
|
1463
1520
|
state = future_front['state']
|
|
1464
1521
|
else:
|
|
1465
1522
|
# Otherwise, slice the current state for the process
|
|
1466
|
-
state = self.
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
process_interval = process['interval']
|
|
1523
|
+
state = self._cached_view(path)
|
|
1524
|
+
state_interval = process['interval']
|
|
1525
|
+
process_interval = process['instance'].calculate_timestep(state_interval, state)
|
|
1526
|
+
process['interval'] = process_interval
|
|
1471
1527
|
|
|
1472
1528
|
# Determine the target time for the next update
|
|
1473
1529
|
future = (
|
|
@@ -1537,9 +1593,7 @@ class Composite(Process):
|
|
|
1537
1593
|
if not isinstance(update_results, list):
|
|
1538
1594
|
update_results = [update_results]
|
|
1539
1595
|
|
|
1540
|
-
return [self.
|
|
1541
|
-
schema,
|
|
1542
|
-
state,
|
|
1596
|
+
return [self._cached_project(
|
|
1543
1597
|
process_path,
|
|
1544
1598
|
update_result,
|
|
1545
1599
|
ports_key) for update_result in update_results]
|
|
@@ -1547,6 +1601,24 @@ class Composite(Process):
|
|
|
1547
1601
|
# Return a deferred object that will project the update when requested
|
|
1548
1602
|
return Defer(update, defer_project, (self.schema, self.state, path))
|
|
1549
1603
|
|
|
1604
|
+
@staticmethod
|
|
1605
|
+
def _has_structural_keys(state: Any) -> bool:
|
|
1606
|
+
"""Check if a state dict contains keys that signal structural changes.
|
|
1607
|
+
|
|
1608
|
+
Structural changes (_add, _remove, _type) require re-running
|
|
1609
|
+
realize() and find_instance_paths(). Plain value updates do not.
|
|
1610
|
+
"""
|
|
1611
|
+
if not isinstance(state, dict):
|
|
1612
|
+
return False
|
|
1613
|
+
for key, value in state.items():
|
|
1614
|
+
if key in ('_add', '_remove'):
|
|
1615
|
+
return True
|
|
1616
|
+
if key == '_type':
|
|
1617
|
+
return True
|
|
1618
|
+
if isinstance(value, dict) and Composite._has_structural_keys(value):
|
|
1619
|
+
return True
|
|
1620
|
+
return False
|
|
1621
|
+
|
|
1550
1622
|
def apply_updates(self, updates: List["Defer"]) -> List[Union[str, Tuple[str, ...]]]:
|
|
1551
1623
|
"""
|
|
1552
1624
|
Apply a series of deferred updates and record the resulting bridge outputs.
|
|
@@ -1562,6 +1634,7 @@ class Composite(Process):
|
|
|
1562
1634
|
A list of update paths (used to determine which processes to refresh).
|
|
1563
1635
|
"""
|
|
1564
1636
|
update_paths = []
|
|
1637
|
+
had_structural_changes = False
|
|
1565
1638
|
|
|
1566
1639
|
for defer in updates:
|
|
1567
1640
|
# Resolve deferred computation to get update(s)
|
|
@@ -1572,13 +1645,14 @@ class Composite(Process):
|
|
|
1572
1645
|
series = [series]
|
|
1573
1646
|
|
|
1574
1647
|
for update_schema, update_state in series:
|
|
1575
|
-
# if update and isinstance(update, dict) and 'environment' in update and update['environment'] and isinstance(update['environment'], dict) and '_react' in update['environment']:
|
|
1576
|
-
# import ipdb; ipdb.set_trace()
|
|
1577
|
-
|
|
1578
1648
|
# Extract all hierarchical paths touched by this update
|
|
1579
1649
|
paths = hierarchy_depth(update_state)
|
|
1580
1650
|
update_paths.extend(paths.keys())
|
|
1581
1651
|
|
|
1652
|
+
# Detect structural changes before applying
|
|
1653
|
+
if not had_structural_changes:
|
|
1654
|
+
had_structural_changes = self._has_structural_keys(update_state)
|
|
1655
|
+
|
|
1582
1656
|
# Apply update directly to the internal state,
|
|
1583
1657
|
# using the schema from the link itself
|
|
1584
1658
|
self.state, merges = self.core.apply(
|
|
@@ -1586,20 +1660,22 @@ class Composite(Process):
|
|
|
1586
1660
|
self.state,
|
|
1587
1661
|
update_state)
|
|
1588
1662
|
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1663
|
+
if merges:
|
|
1664
|
+
had_structural_changes = True
|
|
1665
|
+
self.schema = self.core.resolve_merges(
|
|
1666
|
+
self.schema,
|
|
1667
|
+
merges)
|
|
1592
1668
|
|
|
1593
1669
|
# Read updated bridge outputs, if available
|
|
1594
1670
|
bridge_update = self.read_bridge(update_state)
|
|
1595
1671
|
if bridge_update:
|
|
1596
1672
|
self.bridge_updates.append(bridge_update)
|
|
1597
1673
|
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1674
|
+
# Only run expensive realize and instance discovery when structural changes occurred
|
|
1675
|
+
if had_structural_changes:
|
|
1676
|
+
self.schema, self.state = self.core.realize(self.schema, self.state)
|
|
1677
|
+
self.find_instance_paths(self.state)
|
|
1678
|
+
self._build_view_project_cache()
|
|
1603
1679
|
|
|
1604
1680
|
return update_paths
|
|
1605
1681
|
|
|
@@ -1613,12 +1689,33 @@ class Composite(Process):
|
|
|
1613
1689
|
Args:
|
|
1614
1690
|
update_paths: A list of hierarchical paths that were modified.
|
|
1615
1691
|
"""
|
|
1692
|
+
# Quick check: if no update path shares a first element with any process path,
|
|
1693
|
+
# then no overlap is possible and we can skip the expensive scan.
|
|
1694
|
+
if not hasattr(self, '_process_path_roots'):
|
|
1695
|
+
self._process_path_roots = set()
|
|
1696
|
+
process_roots = self._process_path_roots
|
|
1697
|
+
if not process_roots:
|
|
1698
|
+
process_roots = {p[0] for p in self.process_paths if p}
|
|
1699
|
+
self._process_path_roots = process_roots
|
|
1700
|
+
|
|
1701
|
+
# Fast rejection: check if any update touches a process-adjacent path
|
|
1702
|
+
needs_check = False
|
|
1703
|
+
for update_path in update_paths:
|
|
1704
|
+
if update_path and update_path[0] in process_roots:
|
|
1705
|
+
needs_check = True
|
|
1706
|
+
break
|
|
1707
|
+
|
|
1708
|
+
if not needs_check:
|
|
1709
|
+
return
|
|
1710
|
+
|
|
1616
1711
|
for update_path in update_paths:
|
|
1617
1712
|
for process_path in self.process_paths.copy():
|
|
1618
1713
|
# Match if update path completely overlaps the process path prefix
|
|
1619
1714
|
updated = all(update == process for update, process in zip(update_path, process_path))
|
|
1620
1715
|
if updated:
|
|
1621
1716
|
self.find_instance_paths(self.state)
|
|
1717
|
+
self._build_view_project_cache()
|
|
1718
|
+
self._process_path_roots = set() # Reset for rebuild
|
|
1622
1719
|
return # Exit early after one match, as paths are re-evaluated
|
|
1623
1720
|
|
|
1624
1721
|
|
|
@@ -1663,3 +1760,16 @@ class Composite(Process):
|
|
|
1663
1760
|
self.run(interval)
|
|
1664
1761
|
|
|
1665
1762
|
return self.bridge_updates
|
|
1763
|
+
|
|
1764
|
+
|
|
1765
|
+
def encode_key(o):
|
|
1766
|
+
if isinstance(o, np.ndarray):
|
|
1767
|
+
o.tolist()
|
|
1768
|
+
|
|
1769
|
+
elif isinstance(o, dict):
|
|
1770
|
+
return {
|
|
1771
|
+
str(k): encode_key(v)
|
|
1772
|
+
for k, v in o.items()}
|
|
1773
|
+
|
|
1774
|
+
else:
|
|
1775
|
+
return o
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{process_bigraph-1.0.6 → process_bigraph-1.0.7}/process_bigraph/experiments/minimal_gillespie.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{process_bigraph-1.0.6 → process_bigraph-1.0.7}/process_bigraph/processes/growth_division.py
RENAMED
|
File without changes
|
{process_bigraph-1.0.6 → process_bigraph-1.0.7}/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
|
{process_bigraph-1.0.6 → process_bigraph-1.0.7}/process_bigraph.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|