Skip to content

Commit b4c2eac

Browse files
committed
use custom clone of Vivarium class for more control during assembly
1 parent 67b3bc7 commit b4c2eac

File tree

11 files changed

+458
-91
lines changed

11 files changed

+458
-91
lines changed

src/simple_process_bigraph_runtime/environment/__init__.py

Whitespace-only changes.
Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
import json
2+
import os
3+
from pathlib import Path
4+
from typing import Any
5+
6+
from bigraph_schema import is_schema_key, set_path, get_path # type: ignore[import-untyped]
7+
from bigraph_schema.utilities import remove_path # type: ignore[import-untyped]
8+
from process_bigraph import Composite, Process, Step, ProcessTypes # type: ignore[import-untyped]
9+
10+
11+
class ProcessBigraphEnv:
12+
core: ProcessTypes
13+
composite: Composite
14+
emitter_config: dict[str, Any]
15+
emitter_paths: dict[str, tuple[str, ...]]
16+
17+
def __init__(self):
18+
self.core = ProcessTypes()
19+
self.composite = Composite(core=self.core)
20+
self.emitter_config = {}
21+
self.emitter_paths = {}
22+
23+
def add_process(self, name: str, process_id, config=None, inputs=None, outputs=None,
24+
path: tuple[str, ...] | None = None):
25+
edge_type = "process" # TODO -- does it matter if this is a step or a process?
26+
config = config or {}
27+
inputs = inputs or {}
28+
outputs = outputs or {}
29+
path = path or ()
30+
31+
# convert string paths to lists
32+
# TODO -- make this into a separate path-parsing function
33+
for ports in [inputs, outputs]:
34+
for port, port_path in ports.items():
35+
ports[port] = parse_path(port_path)
36+
37+
# make the process spec
38+
state = {
39+
name: {
40+
"_type": edge_type,
41+
"address": f"local:{process_id}", # TODO -- only support local right now?
42+
"config": config,
43+
"inputs": inputs,
44+
"outputs": outputs,
45+
}
46+
}
47+
48+
# nest the process in the composite at the given path
49+
self.composite.merge({}, state, path)
50+
self.reset_emitters()
51+
self.reset_paths()
52+
53+
def add_step(self, name: str, process_id, config=None, inputs=None, outputs=None,
54+
path: tuple[str, ...] | None = None):
55+
edge_type = "step" # TODO -- does it matter if this is a step or a process?
56+
config = config or {}
57+
inputs = inputs or {}
58+
outputs = outputs or {}
59+
path = path or ()
60+
61+
# convert string paths to lists
62+
# TODO -- make this into a separate path-parsing function
63+
for ports in [inputs, outputs]:
64+
for port, port_path in ports.items():
65+
ports[port] = parse_path(port_path)
66+
67+
# make the process spec
68+
state = {
69+
name: {
70+
"_type": edge_type,
71+
"address": f"local:{process_id}", # TODO -- only support local right now?
72+
"config": config,
73+
"inputs": inputs,
74+
"outputs": outputs,
75+
}
76+
}
77+
78+
# nest the process in the composite at the given path
79+
self.composite.merge({}, state, path)
80+
self.reset_emitters()
81+
self.reset_paths()
82+
83+
def reset_emitters(self) -> None:
84+
for path, emitter in self.emitter_paths.items():
85+
remove_path(self.composite.state, path)
86+
self.add_emitter()
87+
# for path, instance in self.composite.step_paths.items():
88+
# if "emitter" in path:
89+
# remove_path(self.composite.state, path)
90+
# self.add_emitter()
91+
92+
def reset_paths(self):
93+
self.composite.find_instance_paths(self.composite.state)
94+
self.composite.build_step_network()
95+
96+
def add_emitter(self):
97+
"""
98+
Add an emitter to the composite.
99+
"""
100+
101+
emitter_state = self._read_emitter_config()
102+
103+
# set the emitter at the path
104+
path = tuple(self.emitter_config.get("path", ('emitter',)))
105+
emitter_state = set_path({}, path, emitter_state)
106+
107+
self.composite.merge({}, emitter_state)
108+
109+
# TODO -- this is a hack to get the emitter to show up in the state
110+
# TODO -- this should be done in the merge function
111+
_, instance = self.core.slice(
112+
self.composite.composition,
113+
self.composite.state,
114+
path)
115+
116+
self.emitter_paths[path] = instance
117+
self.composite.step_paths[path] = instance
118+
119+
# rebuild the step network
120+
self.composite.build_step_network()
121+
122+
def _read_emitter_config(self):
123+
address = self.emitter_config.get("address", "local:ram-emitter")
124+
config = self.emitter_config.get("config", {})
125+
mode = self.emitter_config.get("mode", "all")
126+
127+
if mode == "all":
128+
inputs = {}
129+
for key in self.composite.state.keys():
130+
if is_schema_key(key): # skip schema keys
131+
continue
132+
if self.core.inherits_from(self.composite.composition[key], "edge"): # skip edges
133+
continue
134+
inputs[key] = [self.emitter_config.get("inputs", {}).get(key, key)]
135+
136+
elif mode == "none":
137+
inputs = self.emitter_config.get("emit", {})
138+
139+
elif mode == "bridge":
140+
print("Warning: emitter bridge mode not implemented.")
141+
inputs = {}
142+
143+
elif mode == "ports":
144+
print("Warning: emitter ports mode not implemented.")
145+
inputs = {}
146+
147+
if not "emit" in config:
148+
config["emit"] = {
149+
input: "any"
150+
for input in inputs}
151+
152+
return {
153+
"_type": "step",
154+
"address": address,
155+
"config": config,
156+
"inputs": inputs}
157+
158+
def set_value(self, path: list[str], value: Any) -> None:
159+
# TODO -- make this set the value in the composite using core
160+
set_path(self.composite.state, path=path, value=value)
161+
# self.composite[path] = value
162+
# self.composite.set({}, value, path)
163+
164+
def get_type(self, type_id: str) -> dict[str, Any]:
165+
return render_type(type_name_or_dict=self.core.access(type_id), core=self.core)
166+
167+
def make_document(self) -> dict[str, Any]:
168+
serialized_state = self.composite.serialize_state()
169+
170+
# TODO fix RecursionError
171+
# schema = self.core.representation(self.composite.composition)
172+
schema = self.composite.serialize_schema()
173+
# schema = self.composite.composition
174+
175+
return {
176+
"state": serialized_state,
177+
"composition": schema,
178+
}
179+
180+
def save(self, json_file_path: Path):
181+
document = self.make_document()
182+
with open(json_file_path, "w") as json_file:
183+
json.dump(document, json_file, indent=4)
184+
print(f"Saved file: {json_file_path}")
185+
186+
187+
def run(self, interval):
188+
"""
189+
Run the simulation for a given interval.
190+
"""
191+
if not self.emitter_paths:
192+
self.add_emitter()
193+
194+
self.composite.run(interval)
195+
196+
def step(self):
197+
"""
198+
Run the simulation for a single step.
199+
"""
200+
self.composite.update({}, 0)
201+
202+
203+
204+
def render_type(type_name_or_dict: str | dict[str, Any], core: ProcessTypes) -> dict[str, Any]:
205+
type_dict: dict[str, Any] = core.access(type_name_or_dict)
206+
rendered = {}
207+
for key, value in type_dict.items():
208+
if is_schema_key(key):
209+
if key == '_description':
210+
rendered['description'] = value
211+
elif key == '_default':
212+
rendered['default'] = core.default(type_dict)
213+
elif isinstance(value, dict):
214+
rendered[key] = render_type(value, core)
215+
return rendered
216+
217+
def parse_path(path: list[str] | str | dict[str, Any]) -> list[str] | dict[str, Any]:
218+
if isinstance(path, dict):
219+
return {
220+
key: parse_path(value)
221+
for key, value in path.items()}
222+
elif isinstance(path, str):
223+
if path.startswith('/'):
224+
return path.split('/')[1:]
225+
else:
226+
return [path]
227+
else:
228+
return path
229+
230+
231+
class TypedStep(Step):
232+
def __init__(self, config=None):
233+
super().__init__(config)
234+
self._inputs = self.inputs()
235+
self._outputs = self.outputs()
236+
237+
def inputs(self) -> dict[str, Any]:
238+
return {}
239+
240+
241+
def outputs(self) -> dict[str, Any]:
242+
return {}
243+
244+
def update(self, inputs: dict[str, Any]) -> dict[str, Any]:
245+
raise NotImplementedError("Subclasses should implement this method.")

src/simple_process_bigraph_runtime/generation/composite_generator.py

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
from typing import Any, cast
22

33
from process_bigraph_lang.dsl.model import Model, StoreDef, ProcessDef, Store, Type
4-
from vivarium import Vivarium # type: ignore[import-untyped]
4+
from simple_process_bigraph_runtime.environment.process_bigraph_env import ProcessBigraphEnv
55

66

7-
def process_composite(model: Model, assembler: Vivarium):
7+
8+
def process_composite(model: Model, assembler: ProcessBigraphEnv):
89
for composite_def in model.compositeDefs:
910

1011
store_path_to_value_map: dict[str, Any] = {}
@@ -49,13 +50,27 @@ def process_composite(model: Model, assembler: Vivarium):
4950
if not process_def.python_path:
5051
raise ValueError(f"Process definition {process_def.name} has no python path")
5152

52-
assembler.add_process(
53-
name=process.name,
54-
process_id=".".join(process_def.python_path.path),
55-
config=process_config,
56-
inputs=input_bindings,
57-
outputs=output_bindings,
58-
)
53+
process_store_path = ()
54+
if process_def.python_path.path[-1].endswith("Step"):
55+
assembler.add_step(
56+
name=process.name,
57+
process_id=".".join(process_def.python_path.path),
58+
config=process_config,
59+
inputs=input_bindings,
60+
outputs=output_bindings,
61+
path=process_store_path
62+
)
63+
elif process_def.python_path.path[-1].endswith("Process"):
64+
assembler.add_process(
65+
name=process.name,
66+
process_id=".".join(process_def.python_path.path),
67+
config=process_config,
68+
inputs=input_bindings,
69+
outputs=output_bindings,
70+
path=process_store_path
71+
)
72+
else:
73+
raise ValueError(f"Process definition {process_def.name} has an invalid python path, expecting to end with Step or Process")
5974

6075
for store_path_str in store_path_to_value_map:
6176
store_path = store_path_str.split("::")

src/simple_process_bigraph_runtime/generation/process_generator.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
from process_bigraph_lang.dsl.model import ProcessDef
2-
from vivarium import Vivarium # type: ignore[import-untyped]
3-
from vivarium.vivarium import VivariumTypes # type: ignore[import-untyped]
42

3+
from simple_process_bigraph_runtime.environment.process_bigraph_env import ProcessBigraphEnv
54

6-
def register_process_defs(assembler: Vivarium, process_defs: list[ProcessDef]) -> None:
5+
6+
def register_process_defs(assembler: ProcessBigraphEnv, process_defs: list[ProcessDef]) -> None:
77
# note: does not actually dynamically build processes...yet
8-
registry: VivariumTypes = assembler.core
8+
registry = assembler.core
99
for process_def in process_defs:
1010
if not process_def.python_path:
1111
raise ValueError(f"Process definition {process_def.name} has no python path")
Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
from process_bigraph_lang.dsl.model import Type
2-
from vivarium import Vivarium # type: ignore[import-untyped]
32

3+
from simple_process_bigraph_runtime.environment.process_bigraph_env import ProcessBigraphEnv
44

5-
def register_types(assembler: Vivarium, types_to_register: list[Type]):
5+
6+
def register_types(assembler: ProcessBigraphEnv, types_to_register: list[Type]):
67
for type_to_register in types_to_register:
7-
if 0 == assembler.get_type(type_to_register.name).size:
8+
if assembler.get_type(type_to_register.name) == {}:
89
raise ValueError(f"Type {type_to_register.name} is an unknown type")
910
# in the future, we'd attempt to generate a new type

src/simple_process_bigraph_runtime/generation/unit_generator.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from process_bigraph_lang.dsl.model import Unit
2-
from vivarium import Vivarium # type: ignore[import-untyped]
2+
from simple_process_bigraph_runtime.environment.process_bigraph_env import ProcessBigraphEnv
33

4-
def register_units(assembler: Vivarium, units_to_register: list[Unit]) -> None:
4+
5+
def register_units(assembler: ProcessBigraphEnv, units_to_register: list[Unit]) -> None:
56
for unit_to_register in units_to_register:
67
pass
78
# if 0 == assembler.get_type(unit_to_register.name).size:

src/simple_process_bigraph_runtime/main.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import simple_process_bigraph_runtime.registry.spatio_flux_library as spatioflux
1313
import simple_process_bigraph_runtime.registry.toy_library as toy
1414
from simple_process_bigraph_runtime import dsl_adapter
15+
from simple_process_bigraph_runtime.environment.process_bigraph_env import ProcessBigraphEnv
1516
from simple_process_bigraph_runtime.generation.composite_generator import process_composite
1617
from simple_process_bigraph_runtime.generation.process_generator import register_process_defs
1718
from simple_process_bigraph_runtime.generation.type_generator import register_types
@@ -70,9 +71,13 @@ def validate_pb_absolute_path(absolute_path: str) -> str:
7071
return absolute_path
7172

7273
def performConversion(ast_model: Model) -> tuple[Composite, dict, ProcessTypes]:
73-
assembler = Vivarium()
74-
spatioflux.apply_to_vivarium(assembler)
75-
toy.apply_to_vivarium(assembler)
74+
assembler = ProcessBigraphEnv()
75+
76+
# register the libraries
77+
spatioflux.register(assembler)
78+
toy.register(assembler)
79+
80+
# register the types, units, and process definitions from the model
7681
register_types(assembler, ast_model.types)
7782
register_units(assembler, ast_model.units)
7883
register_process_defs(assembler, ast_model.processDefs)

src/simple_process_bigraph_runtime/registry/spatio_flux_library.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
from spatio_flux import processes # type: ignore[import-untyped]
33
from vivarium import Vivarium # type: ignore[import-untyped]
44

5+
from simple_process_bigraph_runtime.environment.process_bigraph_env import ProcessBigraphEnv
6+
57

68
def apply_non_negative(schema, current, update, top_schema, top_state, path, core):
79
new_value = current + update
@@ -48,8 +50,8 @@ def apply_non_negative(schema, current, update, top_schema, top_state, path, cor
4850

4951
PROCESS_DICT = processes.PROCESS_DICT.copy()
5052

51-
def apply_to_vivarium(vivarium: Vivarium) -> None:
53+
def register(assembler: ProcessBigraphEnv) -> None:
5254
for type_name, type_schema in TYPES_DICT.items():
53-
vivarium.core.register(type_name, type_schema)
55+
assembler.core.register(type_name, type_schema)
5456
for process_name, process in PROCESS_DICT.items():
55-
vivarium.core.register_process(process_name, process)
57+
assembler.core.register_process(process_name, process)

0 commit comments

Comments
 (0)