process-bigraph 1.0.4__tar.gz → 1.0.6__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.
Files changed (33) hide show
  1. process_bigraph-1.0.6/PKG-INFO +146 -0
  2. process_bigraph-1.0.6/README.md +126 -0
  3. {process_bigraph-1.0.4 → process_bigraph-1.0.6}/process_bigraph/composite.py +106 -50
  4. {process_bigraph-1.0.4 → process_bigraph-1.0.6}/process_bigraph/processes/growth_division.py +1 -1
  5. process_bigraph-1.0.6/process_bigraph/processes/math_expression.py +525 -0
  6. {process_bigraph-1.0.4 → process_bigraph-1.0.6}/process_bigraph/processes/parameter_scan.py +0 -1
  7. process_bigraph-1.0.6/process_bigraph/types/process.py +147 -0
  8. process_bigraph-1.0.6/process_bigraph.egg-info/PKG-INFO +146 -0
  9. {process_bigraph-1.0.4 → process_bigraph-1.0.6}/process_bigraph.egg-info/SOURCES.txt +1 -0
  10. {process_bigraph-1.0.4 → process_bigraph-1.0.6}/process_bigraph.egg-info/requires.txt +6 -0
  11. {process_bigraph-1.0.4 → process_bigraph-1.0.6}/pyproject.toml +7 -1
  12. process_bigraph-1.0.4/PKG-INFO +0 -63
  13. process_bigraph-1.0.4/README.md +0 -49
  14. process_bigraph-1.0.4/process_bigraph/types/process.py +0 -76
  15. process_bigraph-1.0.4/process_bigraph.egg-info/PKG-INFO +0 -63
  16. {process_bigraph-1.0.4 → process_bigraph-1.0.6}/AUTHORS.md +0 -0
  17. {process_bigraph-1.0.4 → process_bigraph-1.0.6}/LICENSE +0 -0
  18. {process_bigraph-1.0.4 → process_bigraph-1.0.6}/process_bigraph/__init__.py +0 -0
  19. {process_bigraph-1.0.4 → process_bigraph-1.0.6}/process_bigraph/emitter.py +0 -0
  20. {process_bigraph-1.0.4 → process_bigraph-1.0.6}/process_bigraph/experiments/__init__.py +0 -0
  21. {process_bigraph-1.0.4 → process_bigraph-1.0.6}/process_bigraph/experiments/minimal_gillespie.py +0 -0
  22. {process_bigraph-1.0.4 → process_bigraph-1.0.6}/process_bigraph/processes/__init__.py +0 -0
  23. {process_bigraph-1.0.4 → process_bigraph-1.0.6}/process_bigraph/processes/examples.py +0 -0
  24. {process_bigraph-1.0.4 → process_bigraph-1.0.6}/process_bigraph/protocols/__init__.py +0 -0
  25. {process_bigraph-1.0.4 → process_bigraph-1.0.6}/process_bigraph/protocols/parallel.py +0 -0
  26. {process_bigraph-1.0.4 → process_bigraph-1.0.6}/process_bigraph/protocols/rest.py +0 -0
  27. {process_bigraph-1.0.4 → process_bigraph-1.0.6}/process_bigraph/protocols/socket.py +0 -0
  28. {process_bigraph-1.0.4 → process_bigraph-1.0.6}/process_bigraph/run.py +0 -0
  29. {process_bigraph-1.0.4 → process_bigraph-1.0.6}/process_bigraph/types/__init__.py +0 -0
  30. {process_bigraph-1.0.4 → process_bigraph-1.0.6}/process_bigraph/units.py +0 -0
  31. {process_bigraph-1.0.4 → process_bigraph-1.0.6}/process_bigraph.egg-info/dependency_links.txt +0 -0
  32. {process_bigraph-1.0.4 → process_bigraph-1.0.6}/process_bigraph.egg-info/top_level.txt +0 -0
  33. {process_bigraph-1.0.4 → process_bigraph-1.0.6}/setup.cfg +0 -0
@@ -0,0 +1,146 @@
1
+ Metadata-Version: 2.4
2
+ Name: process-bigraph
3
+ Version: 1.0.6
4
+ Summary: protocol and execution for compositional systems biology
5
+ Requires-Python: >=3.11
6
+ Description-Content-Type: text/markdown
7
+ License-File: LICENSE
8
+ License-File: AUTHORS.md
9
+ Requires-Dist: bigraph-schema
10
+ Requires-Dist: ipdb>=0.13.13
11
+ Requires-Dist: matplotlib
12
+ Requires-Dist: pint>=0.24.4
13
+ Requires-Dist: scipy>=1.8
14
+ Requires-Dist: pandas
15
+ Requires-Dist: sympy
16
+ Requires-Dist: ipykernel
17
+ Requires-Dist: jupyter
18
+ Requires-Dist: notebook
19
+ Dynamic: license-file
20
+
21
+ # Process-Bigraph
22
+
23
+ [![PyPI](https://img.shields.io/pypi/v/process-bigraph.svg)](https://pypi.org/project/process-bigraph/)
24
+ [![GitHub Pages](https://img.shields.io/badge/GitHub%20Pages-Tutorials-brightgreen)](https://vivarium-collective.github.io/process-bigraph/notebooks/index.html)
25
+
26
+ **Process-Bigraph** is a compositional runtime and protocol for building and executing
27
+ **multiscale biological models from interoperable processes**.
28
+
29
+ It provides a shared architectural layer for:
30
+ - declaring **process interfaces**
31
+ - wiring processes through **typed shared state**
32
+ - orchestrating execution across **heterogeneous timescales**
33
+ - supporting **dynamic structure** (workflows, division, graph rewrites)
34
+
35
+ Process-Bigraph is the execution core of **Vivarium 2.0**, designed to integrate models
36
+ built with different formalisms—including ODEs, FBA, agent-based models, spatial solvers,
37
+ and machine-learning components—into a single coherent simulation.
38
+
39
+ <p align="center">
40
+ <img src="https://github.com/vivarium-collective/process-bigraph/blob/main/doc/_static/composition_framework.png?raw=true"
41
+ width="800"
42
+ alt="Process Bigraph composition framework">
43
+ </p>
44
+
45
+ ---
46
+
47
+ ## 🧩 What is a Process Bigraph?
48
+
49
+ A **process bigraph** combines:
50
+
51
+ - **Typed stores** — hierarchical, schema-validated state defined with
52
+ [**bigraph-schema**](https://github.com/vivarium-collective/bigraph-schema)
53
+ - **Processes** — executable components with explicit input/output ports
54
+ - **Composites** — encapsulated sub-simulations with their own internal structure
55
+ - **Orchestration patterns** — multi-timestepping, directed workflows, and event-driven rewrites
56
+
57
+ Processes do **not** mutate state directly.
58
+ Instead, they emit **typed deltas** that are merged by the runtime.
59
+
60
+ This allows:
61
+ - numerical updates
62
+ - structural rewrites
63
+ - scheduling and orchestration
64
+
65
+ to coexist under a single execution semantics.
66
+
67
+ In this sense, Process-Bigraph is a **composition protocol**, not a domain-specific simulator.
68
+
69
+ ---
70
+
71
+ ## 📄 Paper reference
72
+
73
+ The conceptual framework and formal semantics of process bigraphs are introduced in:
74
+
75
+ > **Agmon, E. & Spangler, R. K.**
76
+ > *Process Bigraphs and the Architecture of Compositional Systems Biology*
77
+ > https://arxiv.org/abs/2512.23754
78
+
79
+ ---
80
+
81
+ ## 🚀 Getting Started
82
+
83
+ ### Installation
84
+
85
+ ```console
86
+ pip install process-bigraph
87
+ ```
88
+
89
+ ## 📘 Tutorials
90
+
91
+ The Process-Bigraph tutorials are executable Jupyter notebooks,
92
+ rendered to HTML and published automatically on GitHub Pages.
93
+
94
+ - 📚 **Tutorial Index (all tutorials)**
95
+ https://vivarium-collective.github.io/process-bigraph/notebooks/index.html
96
+
97
+ ### Learning Path (Featured Tutorials)
98
+
99
+ - **Tutorial 1 — Process-Bigraph Basics**
100
+ *Processes, Steps, ports, Composites, workflows, and emitters*
101
+ https://vivarium-collective.github.io/process-bigraph/notebooks/tutorial_1.html
102
+
103
+ - **Tutorial 2 — Wrapping an ODE Solver (`odeint`)**
104
+ *How to expose an existing scientific API as a Process*
105
+ https://vivarium-collective.github.io/process-bigraph/notebooks/tutorial_2.html
106
+
107
+ - **Tutorial 3 — Declarative Math**
108
+ *Defining mathematical relationships, signal pipelines, and events using `MathExpressionStep`*
109
+ https://vivarium-collective.github.io/process-bigraph/notebooks/tutorial_3.html
110
+
111
+ More tutorials are added continuously and appear automatically in the index.
112
+
113
+ ---
114
+
115
+ ## 🧪 Reference Implementation: spatio-flux
116
+
117
+ Process-Bigraph is exercised end-to-end in **spatio-flux**, a multiscale reference
118
+ model built entirely using the process-bigraph protocol.
119
+
120
+ spatio-flux composes spatial fields, particle dynamics, and metabolic processes
121
+ using typed shared state and declarative orchestration.
122
+
123
+ GitHub: https://github.com/vivarium-collective/spatio-flux
124
+ Live test report: https://vivarium-collective.github.io/spatio-flux/report/index.html
125
+
126
+ ---
127
+
128
+ ## 🔗 Related Resources
129
+
130
+ - **Bigraph Schema Basics**
131
+ https://vivarium-collective.github.io/bigraph-viz/notebooks/basics.html
132
+ *Introduction to the schema language underlying Process-Bigraph*
133
+
134
+ - **Visualization of Bigraph Document** — diagramming and rendering with
135
+ [**bigraph-viz**](https://github.com/vivarium-collective/bigraph-viz)
136
+ https://vivarium-collective.github.io/bigraph-viz/notebooks/format.html
137
+
138
+ - **E. coli Whole-Cell Wiring Diagram**
139
+ https://raw.githubusercontent.com/vivarium-collective/bigraph-viz/main/doc/_static/ecoli.png
140
+
141
+ ---
142
+
143
+ ## 📜 License
144
+
145
+ Process-Bigraph is open-source software released under the
146
+ [Apache 2 License](https://github.com/vivarium-collective/process-bigraph/blob/main/LICENSE).
@@ -0,0 +1,126 @@
1
+ # Process-Bigraph
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/process-bigraph.svg)](https://pypi.org/project/process-bigraph/)
4
+ [![GitHub Pages](https://img.shields.io/badge/GitHub%20Pages-Tutorials-brightgreen)](https://vivarium-collective.github.io/process-bigraph/notebooks/index.html)
5
+
6
+ **Process-Bigraph** is a compositional runtime and protocol for building and executing
7
+ **multiscale biological models from interoperable processes**.
8
+
9
+ It provides a shared architectural layer for:
10
+ - declaring **process interfaces**
11
+ - wiring processes through **typed shared state**
12
+ - orchestrating execution across **heterogeneous timescales**
13
+ - supporting **dynamic structure** (workflows, division, graph rewrites)
14
+
15
+ Process-Bigraph is the execution core of **Vivarium 2.0**, designed to integrate models
16
+ built with different formalisms—including ODEs, FBA, agent-based models, spatial solvers,
17
+ and machine-learning components—into a single coherent simulation.
18
+
19
+ <p align="center">
20
+ <img src="https://github.com/vivarium-collective/process-bigraph/blob/main/doc/_static/composition_framework.png?raw=true"
21
+ width="800"
22
+ alt="Process Bigraph composition framework">
23
+ </p>
24
+
25
+ ---
26
+
27
+ ## 🧩 What is a Process Bigraph?
28
+
29
+ A **process bigraph** combines:
30
+
31
+ - **Typed stores** — hierarchical, schema-validated state defined with
32
+ [**bigraph-schema**](https://github.com/vivarium-collective/bigraph-schema)
33
+ - **Processes** — executable components with explicit input/output ports
34
+ - **Composites** — encapsulated sub-simulations with their own internal structure
35
+ - **Orchestration patterns** — multi-timestepping, directed workflows, and event-driven rewrites
36
+
37
+ Processes do **not** mutate state directly.
38
+ Instead, they emit **typed deltas** that are merged by the runtime.
39
+
40
+ This allows:
41
+ - numerical updates
42
+ - structural rewrites
43
+ - scheduling and orchestration
44
+
45
+ to coexist under a single execution semantics.
46
+
47
+ In this sense, Process-Bigraph is a **composition protocol**, not a domain-specific simulator.
48
+
49
+ ---
50
+
51
+ ## 📄 Paper reference
52
+
53
+ The conceptual framework and formal semantics of process bigraphs are introduced in:
54
+
55
+ > **Agmon, E. & Spangler, R. K.**
56
+ > *Process Bigraphs and the Architecture of Compositional Systems Biology*
57
+ > https://arxiv.org/abs/2512.23754
58
+
59
+ ---
60
+
61
+ ## 🚀 Getting Started
62
+
63
+ ### Installation
64
+
65
+ ```console
66
+ pip install process-bigraph
67
+ ```
68
+
69
+ ## 📘 Tutorials
70
+
71
+ The Process-Bigraph tutorials are executable Jupyter notebooks,
72
+ rendered to HTML and published automatically on GitHub Pages.
73
+
74
+ - 📚 **Tutorial Index (all tutorials)**
75
+ https://vivarium-collective.github.io/process-bigraph/notebooks/index.html
76
+
77
+ ### Learning Path (Featured Tutorials)
78
+
79
+ - **Tutorial 1 — Process-Bigraph Basics**
80
+ *Processes, Steps, ports, Composites, workflows, and emitters*
81
+ https://vivarium-collective.github.io/process-bigraph/notebooks/tutorial_1.html
82
+
83
+ - **Tutorial 2 — Wrapping an ODE Solver (`odeint`)**
84
+ *How to expose an existing scientific API as a Process*
85
+ https://vivarium-collective.github.io/process-bigraph/notebooks/tutorial_2.html
86
+
87
+ - **Tutorial 3 — Declarative Math**
88
+ *Defining mathematical relationships, signal pipelines, and events using `MathExpressionStep`*
89
+ https://vivarium-collective.github.io/process-bigraph/notebooks/tutorial_3.html
90
+
91
+ More tutorials are added continuously and appear automatically in the index.
92
+
93
+ ---
94
+
95
+ ## 🧪 Reference Implementation: spatio-flux
96
+
97
+ Process-Bigraph is exercised end-to-end in **spatio-flux**, a multiscale reference
98
+ model built entirely using the process-bigraph protocol.
99
+
100
+ spatio-flux composes spatial fields, particle dynamics, and metabolic processes
101
+ using typed shared state and declarative orchestration.
102
+
103
+ GitHub: https://github.com/vivarium-collective/spatio-flux
104
+ Live test report: https://vivarium-collective.github.io/spatio-flux/report/index.html
105
+
106
+ ---
107
+
108
+ ## 🔗 Related Resources
109
+
110
+ - **Bigraph Schema Basics**
111
+ https://vivarium-collective.github.io/bigraph-viz/notebooks/basics.html
112
+ *Introduction to the schema language underlying Process-Bigraph*
113
+
114
+ - **Visualization of Bigraph Document** — diagramming and rendering with
115
+ [**bigraph-viz**](https://github.com/vivarium-collective/bigraph-viz)
116
+ https://vivarium-collective.github.io/bigraph-viz/notebooks/format.html
117
+
118
+ - **E. coli Whole-Cell Wiring Diagram**
119
+ https://raw.githubusercontent.com/vivarium-collective/bigraph-viz/main/doc/_static/ecoli.png
120
+
121
+ ---
122
+
123
+ ## 📜 License
124
+
125
+ Process-Bigraph is open-source software released under the
126
+ [Apache 2 License](https://github.com/vivarium-collective/process-bigraph/blob/main/LICENSE).
@@ -72,8 +72,11 @@ def find_instances(
72
72
 
73
73
  for key, inner in state.items():
74
74
  if isinstance(inner, dict):
75
- if isinstance(inner.get('instance'), process_class):
75
+ instance = inner.get('instance')
76
+
77
+ if isinstance(instance, process_class):
76
78
  found[key] = inner
79
+
77
80
  elif not is_schema_key(key):
78
81
  sub_instances = find_instances(inner, instance_type)
79
82
  if sub_instances:
@@ -116,10 +119,11 @@ def find_step_triggers(
116
119
  """
117
120
  prefix = tuple(path[:-1])
118
121
  triggers: Dict[Tuple[str, ...], List[Union[List[str], Tuple[str, ...]]]] = {}
119
- wire_paths = find_leaves(step['inputs'])
122
+ wire_paths = find_leaves(step['inputs'], path=prefix)
120
123
 
121
124
  for wire in wire_paths:
122
- trigger_path = resolve_path(prefix + tuple(wire))
125
+ trigger_path = resolve_path(tuple(wire))
126
+ # trigger_path = resolve_path(prefix + tuple(wire))
123
127
  if isinstance(trigger_path, list):
124
128
  import ipdb; ipdb.set_trace()
125
129
  triggers.setdefault(trigger_path, []).append(path)
@@ -210,7 +214,7 @@ def find_leaves(tree_structure, path=None):
210
214
  list: List of leaf paths as tuples.
211
215
  """
212
216
  leaves = []
213
- path = ()
217
+ path = path or ()
214
218
 
215
219
  if tree_structure is None:
216
220
  pass
@@ -221,7 +225,7 @@ def find_leaves(tree_structure, path=None):
221
225
  else:
222
226
  for key, value in tree_structure.items():
223
227
  if isinstance(value, dict):
224
- subleaves = find_leaves(value, path + (key,))
228
+ subleaves = find_leaves(value, path=path)
225
229
  leaves.extend(subleaves)
226
230
  else:
227
231
  leaves.append(path + tuple(value))
@@ -241,7 +245,7 @@ def build_step_network(steps):
241
245
  - nodes: A mapping from paths to sets of steps that are dependent on them.
242
246
  """
243
247
  ancestors = {
244
- step_key: {'input_paths': None, 'output_paths': None}
248
+ step_key: {'input_paths': None, 'output_paths': None, 'priority': None}
245
249
  for step_key in steps
246
250
  }
247
251
  nodes = {}
@@ -252,11 +256,15 @@ def build_step_network(steps):
252
256
 
253
257
  # Compute input paths once per step
254
258
  if ancestors[step_key]['input_paths'] is None:
255
- ancestors[step_key]['input_paths'] = find_leaves(step['inputs'])
259
+ ancestors[step_key]['input_paths'] = find_leaves(step['inputs'], path=step_key[:-1])
256
260
 
257
261
  # Compute output paths once per step
258
262
  if ancestors[step_key]['output_paths'] is None:
259
- ancestors[step_key]['output_paths'] = find_leaves(step.get('outputs', {}))
263
+ ancestors[step_key]['output_paths'] = find_leaves(step.get('outputs', {}), path=step_key[:-1])
264
+
265
+ # Assign the priority
266
+ if ancestors[step_key]['priority'] is None:
267
+ ancestors[step_key]['priority'] = step.get('priority', 0.0)
260
268
 
261
269
  input_paths = ancestors[step_key]['input_paths'] or []
262
270
  output_paths = ancestors[step_key]['output_paths'] or []
@@ -278,7 +286,7 @@ def build_step_network(steps):
278
286
  return ancestors, nodes
279
287
 
280
288
 
281
- def build_trigger_state(nodes):
289
+ def build_trigger_state(nodes, paths):
282
290
  """
283
291
  Initialize the trigger state from dependency nodes.
284
292
 
@@ -288,8 +296,10 @@ def build_trigger_state(nodes):
288
296
  Returns:
289
297
  A mapping of paths to the set of steps waiting on those paths.
290
298
  """
299
+ path_set = set(paths)
300
+
291
301
  return {
292
- key: value['before'].copy()
302
+ key: set(value['before']).intersection(path_set)
293
303
  for key, value in nodes.items()}
294
304
 
295
305
 
@@ -344,13 +354,51 @@ def determine_steps(steps, remaining, fulfilled):
344
354
  """
345
355
  to_run = []
346
356
 
357
+ if not remaining:
358
+ return to_run, remaining, fulfilled
359
+
347
360
  for step_path in list(remaining):
348
361
  step_inputs = steps[step_path].get('input_paths', []) or []
349
362
  if all(len(fulfilled[input]) == 0 for input in step_inputs):
350
363
  to_run.append(step_path)
351
364
 
365
+ if not to_run:
366
+ # cycles
367
+ visited = set([])
368
+ cycle = set([])
369
+ look = list(remaining)
370
+
371
+ while look:
372
+ step_path = look[0]
373
+ look = look[1:]
374
+
375
+ if step_path in visited:
376
+ if step_path in remaining:
377
+ cycle.add(step_path)
378
+
379
+ else:
380
+ visited.add(step_path)
381
+
382
+ inputs = steps[step_path]['input_paths']
383
+ for input_path in inputs:
384
+ if input_path in fulfilled:
385
+ unfulfilled = fulfilled[input_path]
386
+ look += unfulfilled
387
+
388
+ if not cycle:
389
+ return to_run, remaining, fulfilled
390
+
391
+ order = sorted(
392
+ cycle,
393
+ key=lambda path: steps[path]['priority'])
394
+
395
+ priority = order[-1]
396
+ to_run = [priority]
397
+
352
398
  for step_path in to_run:
353
- remaining.remove(step_path)
399
+ if step_path in remaining:
400
+ remaining.remove(step_path)
401
+
354
402
  step_outputs = steps[step_path].get('output_paths', []) or []
355
403
  for output in step_outputs:
356
404
  exploded_path = explode_path(output)[1:]
@@ -658,64 +706,74 @@ class Process(Open):
658
706
  return {}
659
707
 
660
708
 
661
- def as_step(inputs, outputs, core=None):
709
+ def as_step(inputs, outputs, name=None, aliases=None):
662
710
  """
663
- Decorator to create a Step from a function named update_*.
664
- If core is provided, registers under the name *.
711
+ Decorator: convert an `update_*` pure function into a Step subclass.
712
+
713
+ - Does NOT register into any core.
714
+ - Adds metadata so discover_packages can register nice aliases (e.g. "add").
665
715
  """
666
716
  def decorator(func):
667
- assert func.__name__.startswith('update_'), "Function name must be of the form update_*"
668
- step_name = func.__name__[len('update_'):]
717
+ if not func.__name__.startswith("update_"):
718
+ raise AssertionError("Function name must be of the form update_*")
669
719
 
670
- class FunctionStep(Step):
671
- def inputs(self):
672
- return inputs
720
+ step_name = name or func.__name__[len("update_"):]
721
+ step_aliases = list(aliases or [])
722
+ # default alias: the function-derived name, e.g. update_add -> "add"
723
+ if step_name not in step_aliases:
724
+ step_aliases.insert(0, step_name)
673
725
 
674
- def outputs(self):
675
- return outputs
726
+ class FunctionStep(Step):
727
+ def inputs(self): return inputs
728
+ def outputs(self): return outputs
729
+ def update(self, state): return func(state)
676
730
 
677
- def update(self, state):
678
- return func(state)
731
+ FunctionStep.__name__ = f"{step_name}Step"
679
732
 
680
- FunctionStep.__name__ = step_name + 'Step'
733
+ # IMPORTANT: make this class look like it belongs to the user's module
734
+ FunctionStep.__module__ = func.__module__
681
735
 
682
- if core is not None:
683
- core.register_link(step_name, FunctionStep)
736
+ # Discovery metadata
737
+ FunctionStep.__pb_kind__ = "step"
738
+ FunctionStep.__pb_aliases__ = step_aliases
739
+ FunctionStep.__pb_wrapped__ = func
684
740
 
685
741
  return FunctionStep
686
-
687
742
  return decorator
688
743
 
689
744
 
690
- def as_process(inputs, outputs, core=None):
745
+ def as_process(inputs, outputs, name=None, aliases=None):
691
746
  """
692
- Decorator to create a Process from a function named update_*.
693
- If core is provided, registers under the name *.
747
+ Decorator: convert an `update_*` function into a Process subclass.
748
+
749
+ - Does NOT register into any core.
750
+ - Adds metadata so discover_packages can register nice aliases (e.g. "odeint").
694
751
  """
695
752
  def decorator(func):
696
- assert func.__name__.startswith('update_'), "Function name must be of the form update_*"
697
- process_name = func.__name__[len('update_'):]
698
-
699
- class FunctionProcess(Process):
700
- def __init__(self, config=None, core=None):
701
- super().__init__(config=config, core=core)
753
+ if not func.__name__.startswith("update_"):
754
+ raise AssertionError("Function name must be of the form update_*")
702
755
 
703
- def inputs(self):
704
- return inputs
756
+ process_name = name or func.__name__[len("update_"):]
757
+ process_aliases = list(aliases or [])
758
+ if process_name not in process_aliases:
759
+ process_aliases.insert(0, process_name)
705
760
 
706
- def outputs(self):
707
- return outputs
761
+ class FunctionProcess(Process):
762
+ def inputs(self): return inputs
763
+ def outputs(self): return outputs
764
+ def update(self, state, interval): return func(state, interval)
708
765
 
709
- def update(self, state, interval):
710
- return func(state, interval)
766
+ FunctionProcess.__name__ = f"{process_name}Process"
711
767
 
712
- FunctionProcess.__name__ = process_name + 'Process'
768
+ # IMPORTANT: make this class look like it belongs to the user's module
769
+ FunctionProcess.__module__ = func.__module__
713
770
 
714
- if core is not None:
715
- core.register_link(process_name, FunctionProcess)
771
+ # Discovery metadata
772
+ FunctionProcess.__pb_kind__ = "process"
773
+ FunctionProcess.__pb_aliases__ = process_aliases
774
+ FunctionProcess.__pb_wrapped__ = func
716
775
 
717
776
  return FunctionProcess
718
-
719
777
  return decorator
720
778
 
721
779
 
@@ -1147,9 +1205,6 @@ class Composite(Process):
1147
1205
  """
1148
1206
  state = state or self.state
1149
1207
 
1150
- # if 'environment' in state and '_add' in state['environment']:
1151
- # import ipdb; ipdb.set_trace()
1152
-
1153
1208
  bridge_view = self.core.view_ports(
1154
1209
  self.schema,
1155
1210
  state,
@@ -1229,7 +1284,7 @@ class Composite(Process):
1229
1284
  step_paths: A dictionary of step paths (as keys).
1230
1285
  """
1231
1286
  # Start with a fresh trigger state from the dependency graph
1232
- self.trigger_state = build_trigger_state(self.node_dependencies)
1287
+ self.trigger_state = build_trigger_state(self.node_dependencies, step_paths)
1233
1288
 
1234
1289
  # Track steps still waiting to be executed in this cycle
1235
1290
  self.steps_remaining: Set[Union[str, Tuple[str, ...]]] = set(step_paths)
@@ -1359,6 +1414,7 @@ class Composite(Process):
1359
1414
  paths.append(path)
1360
1415
 
1361
1416
  update_paths = self.apply_updates(updates)
1417
+ update_paths.append(('global_time',)) # updated global time can trigger steps
1362
1418
  self.expire_process_paths(update_paths)
1363
1419
  self.trigger_steps(update_paths)
1364
1420
 
@@ -159,7 +159,7 @@ def grow_divide_agent(config=None, state=None, path=None):
159
159
  'mass': ['mass']}},
160
160
 
161
161
  'divide': {
162
- '_type': 'process',
162
+ '_type': 'step',
163
163
  'address': 'local:Divide',
164
164
  'config': divide_config,
165
165
  'inputs': {