process-bigraph 0.0.43__py3-none-any.whl
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/__init__.py +53 -0
- process_bigraph/composite.py +1551 -0
- process_bigraph/emitter.py +326 -0
- process_bigraph/experiments/__init__.py +0 -0
- process_bigraph/experiments/minimal_gillespie.py +207 -0
- process_bigraph/process_types.py +473 -0
- process_bigraph/processes/__init__.py +26 -0
- process_bigraph/processes/growth_division.py +167 -0
- process_bigraph/processes/parameter_scan.py +350 -0
- process_bigraph/tests.py +1330 -0
- process_bigraph/units.py +25 -0
- process_bigraph-0.0.43.dist-info/METADATA +65 -0
- process_bigraph-0.0.43.dist-info/RECORD +17 -0
- process_bigraph-0.0.43.dist-info/WHEEL +5 -0
- process_bigraph-0.0.43.dist-info/licenses/AUTHORS.md +6 -0
- process_bigraph-0.0.43.dist-info/licenses/LICENSE +201 -0
- process_bigraph-0.0.43.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
"""
|
|
2
|
+
=============
|
|
3
|
+
Process Types
|
|
4
|
+
=============
|
|
5
|
+
|
|
6
|
+
This module contains the process methods and types for the process bigraph schema.
|
|
7
|
+
Additionally, it defines the `ProcessTypes` class, which extends the `TypeSystem`
|
|
8
|
+
class to include process types, and maintains a registry of process types, protocols,
|
|
9
|
+
and emitters.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import copy
|
|
13
|
+
|
|
14
|
+
from bigraph_schema import Registry, Edge, TypeSystem, deep_merge, visit_method, get_path
|
|
15
|
+
|
|
16
|
+
from process_bigraph.protocols import BASE_PROTOCOLS
|
|
17
|
+
from process_bigraph.composite import Composite
|
|
18
|
+
from process_bigraph.emitter import BASE_EMITTERS
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# ======================
|
|
22
|
+
# Process Type Functions
|
|
23
|
+
# ======================
|
|
24
|
+
|
|
25
|
+
def apply_process(schema, current, update, top_schema, top_state, path, core):
|
|
26
|
+
"""
|
|
27
|
+
Apply an update to a process instance using the core's update mechanism.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
schema (dict): The schema for the current process.
|
|
31
|
+
current (dict): The current process state.
|
|
32
|
+
update (dict): The update to apply.
|
|
33
|
+
top_schema (dict): The top-level (composite) schema.
|
|
34
|
+
top_state (dict): The top-level state.
|
|
35
|
+
path (tuple): Path to the current process in the schema tree.
|
|
36
|
+
core: The type system or composition engine.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
dict: Updated process state.
|
|
40
|
+
"""
|
|
41
|
+
process_schema = schema.copy()
|
|
42
|
+
process_schema.pop('_apply', None)
|
|
43
|
+
|
|
44
|
+
return core.apply_update(
|
|
45
|
+
process_schema,
|
|
46
|
+
current,
|
|
47
|
+
update,
|
|
48
|
+
top_schema=top_schema,
|
|
49
|
+
top_state=top_state,
|
|
50
|
+
path=path
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def check_process(schema, state, core):
|
|
55
|
+
"""
|
|
56
|
+
Check if a given state belongs to a valid process instance.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
schema (dict): The expected schema (unused here).
|
|
60
|
+
state (dict): The process state.
|
|
61
|
+
core: The type system (unused here).
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
bool: True if the state contains a valid Edge instance.
|
|
65
|
+
"""
|
|
66
|
+
return 'instance' in state and isinstance(state['instance'], Edge)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def fold_visit(schema, state, method, values, core):
|
|
70
|
+
"""
|
|
71
|
+
Wrapper for visiting a process state using a specific method.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
schema (dict): The process schema.
|
|
75
|
+
state (dict): The process state.
|
|
76
|
+
method (str): The method name to invoke (e.g., 'apply', 'serialize').
|
|
77
|
+
values (dict): Inputs to the visitor method.
|
|
78
|
+
core: The type system.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Any: Result of the visitor operation.
|
|
82
|
+
"""
|
|
83
|
+
return visit_method(schema, state, method, values, core)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def divide_process(schema, state, values, core):
|
|
87
|
+
"""
|
|
88
|
+
Divide a process into multiple daughter process states.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
schema (dict): The schema for the process.
|
|
92
|
+
state (dict): The current process state.
|
|
93
|
+
values (dict): Division parameters including:
|
|
94
|
+
- 'divisions' (int): Number of daughters
|
|
95
|
+
- 'daughter_configs' (list[dict], optional): Config overrides
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
list[dict]: A list of daughter states.
|
|
99
|
+
"""
|
|
100
|
+
num_daughters = values['divisions']
|
|
101
|
+
daughter_configs = values.get('daughter_configs', [{} for _ in range(num_daughters)])
|
|
102
|
+
|
|
103
|
+
if 'config' not in state:
|
|
104
|
+
return daughter_configs
|
|
105
|
+
|
|
106
|
+
existing_config = state['config']
|
|
107
|
+
divisions = []
|
|
108
|
+
|
|
109
|
+
for i in range(num_daughters):
|
|
110
|
+
daughter_config = deep_merge(copy.deepcopy(existing_config), daughter_configs[i])
|
|
111
|
+
|
|
112
|
+
daughter_state = {
|
|
113
|
+
'address': state['address'],
|
|
114
|
+
'config': daughter_config,
|
|
115
|
+
'inputs': copy.deepcopy(state['inputs']),
|
|
116
|
+
'outputs': copy.deepcopy(state['outputs']),
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if 'interval' in state:
|
|
120
|
+
daughter_state['interval'] = state['interval']
|
|
121
|
+
|
|
122
|
+
divisions.append(daughter_state)
|
|
123
|
+
|
|
124
|
+
return divisions
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def serialize_process(schema, value, core):
|
|
128
|
+
"""
|
|
129
|
+
Serialize a process state to a JSON-safe format.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
schema (dict): The schema (unused here).
|
|
133
|
+
value (dict): The full process state.
|
|
134
|
+
core: The type system to handle sub-serialization.
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
dict: Serialized process data.
|
|
138
|
+
"""
|
|
139
|
+
process = value.copy()
|
|
140
|
+
|
|
141
|
+
process['config'] = core.serialize(
|
|
142
|
+
process['instance'].config_schema,
|
|
143
|
+
process['config']
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
del process['instance'] # Remove the live instance for serialization
|
|
147
|
+
|
|
148
|
+
return process
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def deserialize_process(schema, encoded, core):
|
|
152
|
+
"""
|
|
153
|
+
Deserialize a process from a saved (serialized) state.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
schema (dict): The expected process schema.
|
|
157
|
+
encoded (dict): Serialized process state.
|
|
158
|
+
core: The type system, needed to resolve classes and configs.
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
dict: Fully rehydrated process state with instance attached.
|
|
162
|
+
"""
|
|
163
|
+
encoded = encoded or {}
|
|
164
|
+
schema = schema or {}
|
|
165
|
+
|
|
166
|
+
# Base deserialization
|
|
167
|
+
default = core.default(schema)
|
|
168
|
+
deserialized = deep_merge(default, encoded)
|
|
169
|
+
address = deserialized.get('address')
|
|
170
|
+
|
|
171
|
+
if not address:
|
|
172
|
+
return deserialized
|
|
173
|
+
|
|
174
|
+
# protocol, address = deserialized['address'].split(':', 1)
|
|
175
|
+
|
|
176
|
+
# # Determine the process class to instantiate
|
|
177
|
+
# if instance:
|
|
178
|
+
# instantiate = type(instance)
|
|
179
|
+
# else:
|
|
180
|
+
# process_lookup = core.protocol_registry.access(protocol)
|
|
181
|
+
# if not process_lookup:
|
|
182
|
+
# raise Exception(f'Protocol "{protocol}" not implemented')
|
|
183
|
+
# instantiate = process_lookup(core, address)
|
|
184
|
+
# if not instantiate:
|
|
185
|
+
# raise Exception(f'Process "{address}" not found')
|
|
186
|
+
|
|
187
|
+
instantiate = core.parse_protocol(address)
|
|
188
|
+
|
|
189
|
+
# Deserialize the configuration
|
|
190
|
+
config = core.deserialize(instantiate.config_schema, deserialized.get('config', {}))
|
|
191
|
+
interval = core.deserialize('interval', deserialized.get('interval'))
|
|
192
|
+
|
|
193
|
+
if interval is None:
|
|
194
|
+
interval = core.default(schema.get('interval', 'interval'))
|
|
195
|
+
|
|
196
|
+
instance = deserialized.get('instance')
|
|
197
|
+
if not instance:
|
|
198
|
+
instance = instantiate(config, core=core)
|
|
199
|
+
deserialized['instance'] = instance
|
|
200
|
+
|
|
201
|
+
# Deserialize shared steps if any
|
|
202
|
+
shared = deserialized.get('shared', {})
|
|
203
|
+
deserialized['shared'] = {}
|
|
204
|
+
|
|
205
|
+
for step_id, step_config in shared.items():
|
|
206
|
+
step = deserialize_step('step', step_config, core)
|
|
207
|
+
step['instance'].register_shared(instance)
|
|
208
|
+
deserialized['shared'][step_id] = step
|
|
209
|
+
|
|
210
|
+
# Finalize state
|
|
211
|
+
deserialized['config'] = config
|
|
212
|
+
deserialized['interval'] = interval
|
|
213
|
+
deserialized['_inputs'] = copy.deepcopy(instance.inputs())
|
|
214
|
+
deserialized['_outputs'] = copy.deepcopy(instance.outputs())
|
|
215
|
+
|
|
216
|
+
return deserialized
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def deserialize_step(schema, encoded, core):
|
|
220
|
+
"""
|
|
221
|
+
Deserialize a single process step (sub-process in a composite).
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
schema (str): Schema key, typically 'step'.
|
|
225
|
+
encoded (dict): Serialized step data.
|
|
226
|
+
core: The type system.
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
dict: Deserialized step state with process instance.
|
|
230
|
+
"""
|
|
231
|
+
default = core.default(schema)
|
|
232
|
+
deserialized = deep_merge(default, encoded)
|
|
233
|
+
address = deserialized.get('address')
|
|
234
|
+
|
|
235
|
+
if not deserialized.get('address'):
|
|
236
|
+
return deserialized
|
|
237
|
+
|
|
238
|
+
instantiate = core.parse_protocol(address)
|
|
239
|
+
# protocol, address = deserialized['address'].split(':', 1)
|
|
240
|
+
|
|
241
|
+
# # Get class or factory function
|
|
242
|
+
# if instance:
|
|
243
|
+
# instantiate = type(instance)
|
|
244
|
+
# else:
|
|
245
|
+
# protocol = core.protocol_registry.access(protocol)
|
|
246
|
+
# if not protocol:
|
|
247
|
+
# raise Exception(f'Protocol "{protocol}" not implemented')
|
|
248
|
+
# instantiate = protocol.interface(core, address)
|
|
249
|
+
# if not instantiate:
|
|
250
|
+
# raise Exception(f'Process "{address}" not found')
|
|
251
|
+
|
|
252
|
+
# Deserialize config and create instance if needed
|
|
253
|
+
config = core.deserialize(instantiate.config_schema, deserialized.get('config', {}))
|
|
254
|
+
|
|
255
|
+
instance = deserialized.get('instance')
|
|
256
|
+
if not instance:
|
|
257
|
+
instance = instantiate(config, core=core)
|
|
258
|
+
deserialized['instance'] = instance
|
|
259
|
+
|
|
260
|
+
deserialized['config'] = config
|
|
261
|
+
deserialized['_inputs'] = copy.deepcopy(instance.inputs())
|
|
262
|
+
deserialized['_outputs'] = copy.deepcopy(instance.outputs())
|
|
263
|
+
|
|
264
|
+
return deserialized
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
# ===================
|
|
268
|
+
# Process Type System
|
|
269
|
+
# ===================
|
|
270
|
+
|
|
271
|
+
class ProcessTypes(TypeSystem):
|
|
272
|
+
"""
|
|
273
|
+
Extends the TypeSystem to manage simulation process types,
|
|
274
|
+
including registries for process classes, protocols, and emitters.
|
|
275
|
+
|
|
276
|
+
Responsibilities:
|
|
277
|
+
- Registering new process types, protocols, and emitters
|
|
278
|
+
- Initializing edge states for composed processes
|
|
279
|
+
- Providing a default configuration/state template for processes
|
|
280
|
+
"""
|
|
281
|
+
|
|
282
|
+
def __init__(self):
|
|
283
|
+
super().__init__()
|
|
284
|
+
|
|
285
|
+
# Registries to store user-defined and built-in components
|
|
286
|
+
self.process_registry = Registry()
|
|
287
|
+
self.protocol_registry = Registry()
|
|
288
|
+
|
|
289
|
+
# Initialize the core type system with known types and protocols
|
|
290
|
+
self.update_types(PROCESS_TYPES)
|
|
291
|
+
self.register_protocols(BASE_PROTOCOLS)
|
|
292
|
+
self.register_processes(BASE_EMITTERS)
|
|
293
|
+
|
|
294
|
+
# Explicitly register Composite process type
|
|
295
|
+
self.register_process('composite', Composite)
|
|
296
|
+
|
|
297
|
+
def register_protocols(self, protocols):
|
|
298
|
+
"""
|
|
299
|
+
Register a dictionary of protocol types with the core type system.
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
protocols (dict): Mapping of protocol names to protocol definitions.
|
|
303
|
+
"""
|
|
304
|
+
self.protocol_registry.register_multiple(protocols)
|
|
305
|
+
|
|
306
|
+
def register_process(self, name, process_data):
|
|
307
|
+
"""
|
|
308
|
+
Register a new process type into the process registry.
|
|
309
|
+
|
|
310
|
+
Args:
|
|
311
|
+
name (str): Unique name for the process.
|
|
312
|
+
process_data: Associated class or factory function for the process.
|
|
313
|
+
"""
|
|
314
|
+
self.process_registry.register(name, process_data)
|
|
315
|
+
|
|
316
|
+
def register_processes(self, processes):
|
|
317
|
+
"""
|
|
318
|
+
Register multiple process types.
|
|
319
|
+
|
|
320
|
+
Args:
|
|
321
|
+
processes (dict): Mapping of process names to process data.
|
|
322
|
+
"""
|
|
323
|
+
for process_key, process_data in processes.items():
|
|
324
|
+
self.register_process(process_key, process_data)
|
|
325
|
+
|
|
326
|
+
def initialize_edge_state(self, schema, path, edge):
|
|
327
|
+
"""
|
|
328
|
+
Compute the initial state for a given edge in a composite process.
|
|
329
|
+
|
|
330
|
+
This combines the default input and output state projections
|
|
331
|
+
for a process based on its edge mapping and schema.
|
|
332
|
+
|
|
333
|
+
Args:
|
|
334
|
+
schema (dict): The complete schema of the composite.
|
|
335
|
+
path (tuple): Path to the process within the schema.
|
|
336
|
+
edge (dict): The edge entry with an instance and port mappings.
|
|
337
|
+
|
|
338
|
+
Returns:
|
|
339
|
+
dict: The initial merged state for the edge.
|
|
340
|
+
"""
|
|
341
|
+
# Get initial state from the process instance
|
|
342
|
+
initial_state = edge['instance'].initial_state()
|
|
343
|
+
if not initial_state:
|
|
344
|
+
return initial_state
|
|
345
|
+
|
|
346
|
+
# Extract and clone port mappings from the schema
|
|
347
|
+
input_ports = copy.deepcopy(get_path(schema, path + ('_inputs',)))
|
|
348
|
+
output_ports = copy.deepcopy(get_path(schema, path + ('_outputs',)))
|
|
349
|
+
ports = {
|
|
350
|
+
'_inputs': input_ports,
|
|
351
|
+
'_outputs': output_ports
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
# Project the edge's initial state onto its inputs and outputs
|
|
355
|
+
input_state = self.project_edge(
|
|
356
|
+
ports, edge, path[:-1], initial_state, ports_key='inputs'
|
|
357
|
+
) if input_ports else {}
|
|
358
|
+
|
|
359
|
+
output_state = self.project_edge(
|
|
360
|
+
ports, edge, path[:-1], initial_state, ports_key='outputs'
|
|
361
|
+
) if output_ports else {}
|
|
362
|
+
|
|
363
|
+
return deep_merge(input_state, output_state)
|
|
364
|
+
|
|
365
|
+
def parse_protocol(self, address):
|
|
366
|
+
if isinstance(address, str):
|
|
367
|
+
protocol_name, protocol_data = address.split(':', 1)
|
|
368
|
+
else:
|
|
369
|
+
protocol_name = address['protocol']
|
|
370
|
+
protocol_data = address['data']
|
|
371
|
+
|
|
372
|
+
protocol = self.protocol_registry.access(protocol_name)
|
|
373
|
+
if not protocol:
|
|
374
|
+
raise Exception(f'Protocol "{protocol_name}" not implemented')
|
|
375
|
+
|
|
376
|
+
instantiate = protocol.interface(self, protocol_data)
|
|
377
|
+
if not instantiate:
|
|
378
|
+
raise Exception(f'Process "{address}" not found')
|
|
379
|
+
|
|
380
|
+
return instantiate
|
|
381
|
+
|
|
382
|
+
def default_state(self, process_class, initial_state=None):
|
|
383
|
+
"""
|
|
384
|
+
Construct the default runtime state for a given process class.
|
|
385
|
+
|
|
386
|
+
Args:
|
|
387
|
+
process_class (class): The process class to instantiate.
|
|
388
|
+
initial_state (dict, optional): Overrides for the default state.
|
|
389
|
+
|
|
390
|
+
Returns:
|
|
391
|
+
dict: The fully constructed default process state.
|
|
392
|
+
"""
|
|
393
|
+
# Get default config from the process's schema
|
|
394
|
+
default_config = self.default(process_class.config_schema)
|
|
395
|
+
|
|
396
|
+
# Instantiate process with default config
|
|
397
|
+
instance = process_class(default_config, core=self)
|
|
398
|
+
|
|
399
|
+
# Build standard process state structure
|
|
400
|
+
state = {
|
|
401
|
+
'_type': 'process',
|
|
402
|
+
'address': f'local:!{process_class.__module__}.{process_class.__name__}',
|
|
403
|
+
'config': default_config,
|
|
404
|
+
'inputs': instance.default_inputs(),
|
|
405
|
+
'outputs': instance.default_outputs()
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
# Add default interval if it's a subclass of Process
|
|
409
|
+
if isinstance(process_class, type):
|
|
410
|
+
try:
|
|
411
|
+
from vivarium.core.process import Process # Delayed import to avoid circularity
|
|
412
|
+
if issubclass(process_class, Process):
|
|
413
|
+
state['interval'] = 1.0
|
|
414
|
+
except ImportError:
|
|
415
|
+
pass # Skip interval assignment if Process class is unavailable
|
|
416
|
+
|
|
417
|
+
# Apply any user-provided state overrides
|
|
418
|
+
if initial_state:
|
|
419
|
+
state = deep_merge(state, initial_state)
|
|
420
|
+
|
|
421
|
+
return state
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
# ========================
|
|
425
|
+
# Process Types Dictionary
|
|
426
|
+
# ========================
|
|
427
|
+
|
|
428
|
+
PROCESS_TYPES = {
|
|
429
|
+
'protocol': {
|
|
430
|
+
'_type': 'protocol',
|
|
431
|
+
'_inherit': 'any'},
|
|
432
|
+
|
|
433
|
+
'emitter_mode': 'enum[none,all,stores,bridge,paths,ports]',
|
|
434
|
+
|
|
435
|
+
'interval': {
|
|
436
|
+
'_type': 'interval',
|
|
437
|
+
'_inherit': 'float',
|
|
438
|
+
'_apply': 'set',
|
|
439
|
+
'_default': '1.0'},
|
|
440
|
+
|
|
441
|
+
'step': {
|
|
442
|
+
'_type': 'step',
|
|
443
|
+
'_inherit': 'edge',
|
|
444
|
+
'_apply': apply_process,
|
|
445
|
+
'_serialize': serialize_process,
|
|
446
|
+
'_deserialize': deserialize_step,
|
|
447
|
+
'_check': check_process,
|
|
448
|
+
'_fold': fold_visit,
|
|
449
|
+
'_divide': divide_process,
|
|
450
|
+
'_description': '',
|
|
451
|
+
# TODO: support reference to type parameters from other states
|
|
452
|
+
'address': 'protocol',
|
|
453
|
+
'config': 'quote'},
|
|
454
|
+
|
|
455
|
+
# TODO: slice process to allow for navigating through a port
|
|
456
|
+
'process': {
|
|
457
|
+
'_type': 'process',
|
|
458
|
+
'_inherit': 'edge',
|
|
459
|
+
'_apply': apply_process,
|
|
460
|
+
'_serialize': serialize_process,
|
|
461
|
+
'_deserialize': deserialize_process,
|
|
462
|
+
'_check': check_process,
|
|
463
|
+
'_fold': fold_visit,
|
|
464
|
+
'_divide': divide_process,
|
|
465
|
+
'_description': '',
|
|
466
|
+
# TODO: support reference to type parameters from other states
|
|
467
|
+
'interval': 'interval',
|
|
468
|
+
'address': 'protocol',
|
|
469
|
+
'config': 'quote',
|
|
470
|
+
'shared': 'map[step]'},
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from process_bigraph.processes.parameter_scan import ToySystem, ODE, RunProcess, ParameterScan
|
|
2
|
+
from process_bigraph.processes.growth_division import Grow, Divide
|
|
3
|
+
from process_bigraph.emitter import BASE_EMITTERS
|
|
4
|
+
# from process_bigraph.experiments.minimal_gillespie import GillespieInterval, GillespieEvent
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
TOY_PROCESSES = {
|
|
8
|
+
'ToySystem': ToySystem,
|
|
9
|
+
'ToyODE': ODE,
|
|
10
|
+
'RunProcess': RunProcess,
|
|
11
|
+
'ParameterScan': ParameterScan,
|
|
12
|
+
'grow': Grow,
|
|
13
|
+
'divide': Divide,
|
|
14
|
+
# 'GillespieInterval': GillespieInterval,
|
|
15
|
+
# 'GillespieEvent': GillespieEvent
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def register_processes(core):
|
|
20
|
+
for name, process in TOY_PROCESSES.items():
|
|
21
|
+
core.register_process(name, process)
|
|
22
|
+
|
|
23
|
+
core = core.register_processes(
|
|
24
|
+
BASE_EMITTERS)
|
|
25
|
+
|
|
26
|
+
return core
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
from process_bigraph.composite import Step, Process, deep_merge
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Grow(Process):
|
|
5
|
+
config_schema = {
|
|
6
|
+
'rate': 'float'}
|
|
7
|
+
|
|
8
|
+
def inputs(self):
|
|
9
|
+
return {
|
|
10
|
+
'mass': 'float'}
|
|
11
|
+
|
|
12
|
+
def outputs(self):
|
|
13
|
+
return {
|
|
14
|
+
'mass': 'float'}
|
|
15
|
+
|
|
16
|
+
def update(self, state, interval):
|
|
17
|
+
# this calculates a delta
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
'mass': state['mass'] * self.config['rate'] * interval}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# TODO: build composite and divide within it
|
|
24
|
+
|
|
25
|
+
class Divide(Step):
|
|
26
|
+
# assume the agent_schema has the right divide methods present
|
|
27
|
+
config_schema = {
|
|
28
|
+
'agent_id': 'string',
|
|
29
|
+
'agent_schema': 'schema',
|
|
30
|
+
'threshold': 'float',
|
|
31
|
+
'divisions': {
|
|
32
|
+
'_type': 'integer',
|
|
33
|
+
'_default': 2}}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def __init__(self, config, core=None):
|
|
37
|
+
super().__init__(config, core)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def inputs(self):
|
|
41
|
+
return {
|
|
42
|
+
'trigger': 'float'}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def outputs(self):
|
|
46
|
+
return {
|
|
47
|
+
'environment': {
|
|
48
|
+
'_type': 'map',
|
|
49
|
+
'_value': self.config['agent_schema']}}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# this should be generalized to some function that depends on
|
|
53
|
+
# state from the self.config['agent_schema'] (instead of trigger > threshold)
|
|
54
|
+
def update(self, state):
|
|
55
|
+
if state['trigger'] > self.config['threshold']:
|
|
56
|
+
mother = self.config['agent_id']
|
|
57
|
+
daughters = [(
|
|
58
|
+
f'{mother}_{i}', {
|
|
59
|
+
'state': {
|
|
60
|
+
'divide': {
|
|
61
|
+
'config': {
|
|
62
|
+
'agent_id': f'{mother}_{i}'}}}})
|
|
63
|
+
for i in range(self.config['divisions'])]
|
|
64
|
+
|
|
65
|
+
# import ipdb; ipdb.set_trace()
|
|
66
|
+
|
|
67
|
+
# return divide reaction
|
|
68
|
+
return {
|
|
69
|
+
'environment': {
|
|
70
|
+
'_react': {
|
|
71
|
+
'divide': {
|
|
72
|
+
'mother': mother,
|
|
73
|
+
'daughters': daughters}}}}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def generate_bridge_wires(schema):
|
|
77
|
+
return {
|
|
78
|
+
key: [key]
|
|
79
|
+
for key in schema
|
|
80
|
+
if not key.startswith('_')}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def generate_bridge(schema, state, interval=1.0):
|
|
84
|
+
bridge = {
|
|
85
|
+
port: generate_bridge_wires(schema[port])
|
|
86
|
+
for port in ['inputs', 'outputs']}
|
|
87
|
+
|
|
88
|
+
config = {
|
|
89
|
+
'_type': 'quote',
|
|
90
|
+
'state': state,
|
|
91
|
+
'bridge': bridge}
|
|
92
|
+
|
|
93
|
+
composite = {
|
|
94
|
+
'_type': 'process',
|
|
95
|
+
'address': 'parallel:composite',
|
|
96
|
+
'interval': interval,
|
|
97
|
+
'config': config,
|
|
98
|
+
'inputs': generate_bridge_wires(schema['inputs']),
|
|
99
|
+
'outputs': generate_bridge_wires(schema['outputs'])}
|
|
100
|
+
|
|
101
|
+
return composite
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def grow_divide_agent(config=None, state=None, path=None):
|
|
105
|
+
agent_id = path[-1]
|
|
106
|
+
|
|
107
|
+
config = config or {}
|
|
108
|
+
state = state or {}
|
|
109
|
+
path = path or []
|
|
110
|
+
|
|
111
|
+
agent_schema = config.get(
|
|
112
|
+
'agent_schema',
|
|
113
|
+
{'mass': 'float'})
|
|
114
|
+
|
|
115
|
+
grow_config = {
|
|
116
|
+
'rate': 0.1}
|
|
117
|
+
|
|
118
|
+
grow_config = deep_merge(
|
|
119
|
+
grow_config,
|
|
120
|
+
config.get(
|
|
121
|
+
'grow'))
|
|
122
|
+
|
|
123
|
+
divide_config = {
|
|
124
|
+
'agent_id': agent_id,
|
|
125
|
+
'agent_schema': agent_schema,
|
|
126
|
+
'threshold': 2.0,
|
|
127
|
+
'divisions': 2}
|
|
128
|
+
|
|
129
|
+
divide_config = deep_merge(
|
|
130
|
+
divide_config,
|
|
131
|
+
config.get(
|
|
132
|
+
'divide'))
|
|
133
|
+
|
|
134
|
+
grow_divide_state = {
|
|
135
|
+
'grow': {
|
|
136
|
+
'_type': 'process',
|
|
137
|
+
'address': 'local:grow',
|
|
138
|
+
'config': grow_config,
|
|
139
|
+
'inputs': {
|
|
140
|
+
'mass': ['mass']},
|
|
141
|
+
'outputs': {
|
|
142
|
+
'mass': ['mass']}},
|
|
143
|
+
|
|
144
|
+
'divide': {
|
|
145
|
+
'_type': 'process',
|
|
146
|
+
'address': 'local:divide',
|
|
147
|
+
'config': divide_config,
|
|
148
|
+
'inputs': {
|
|
149
|
+
'trigger': ['mass']},
|
|
150
|
+
'outputs': {
|
|
151
|
+
'environment': ['environment']}}}
|
|
152
|
+
|
|
153
|
+
grow_divide_state = deep_merge(
|
|
154
|
+
grow_divide_state,
|
|
155
|
+
state)
|
|
156
|
+
|
|
157
|
+
composite = generate_bridge({
|
|
158
|
+
'inputs': {'mass': ['mass']},
|
|
159
|
+
'outputs': agent_schema},
|
|
160
|
+
grow_divide_state)
|
|
161
|
+
|
|
162
|
+
composite['config']['bridge']['outputs']['environment'] = ['environment']
|
|
163
|
+
composite['outputs']['environment'] = ['..']
|
|
164
|
+
|
|
165
|
+
return composite
|
|
166
|
+
|
|
167
|
+
|