process-bigraph 0.0.22__tar.gz → 0.0.23__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 (35) hide show
  1. {process-bigraph-0.0.22/process_bigraph.egg-info → process-bigraph-0.0.23}/PKG-INFO +6 -1
  2. process-bigraph-0.0.23/process_bigraph/__init__.py +48 -0
  3. {process-bigraph-0.0.22 → process-bigraph-0.0.23}/process_bigraph/composite.py +283 -57
  4. {process-bigraph-0.0.22 → process-bigraph-0.0.23}/process_bigraph/experiments/growth_division.py +3 -38
  5. process-bigraph-0.0.23/process_bigraph/experiments/minimal_gillespie.py +158 -0
  6. {process-bigraph-0.0.22 → process-bigraph-0.0.23}/process_bigraph/process_types.py +13 -1
  7. process-bigraph-0.0.23/process_bigraph/processes/__init__.py +18 -0
  8. {process-bigraph-0.0.22 → process-bigraph-0.0.23}/process_bigraph/processes/parameter_scan.py +3 -160
  9. {process-bigraph-0.0.22 → process-bigraph-0.0.23}/process_bigraph/tests.py +307 -12
  10. {process-bigraph-0.0.22 → process-bigraph-0.0.23/process_bigraph.egg-info}/PKG-INFO +6 -1
  11. {process-bigraph-0.0.22 → process-bigraph-0.0.23}/process_bigraph.egg-info/SOURCES.txt +0 -11
  12. {process-bigraph-0.0.22 → process-bigraph-0.0.23}/setup.py +1 -1
  13. process-bigraph-0.0.22/.github/workflows/notebook_to_html.yml +0 -43
  14. process-bigraph-0.0.22/.github/workflows/pytest.yml +0 -35
  15. process-bigraph-0.0.22/.gitignore +0 -11
  16. process-bigraph-0.0.22/CLA.md +0 -113
  17. process-bigraph-0.0.22/CODE_OF_CONDUCT.md +0 -137
  18. process-bigraph-0.0.22/CONTRIBUTING.md +0 -44
  19. process-bigraph-0.0.22/doc/_static/process-bigraph.png +0 -0
  20. process-bigraph-0.0.22/notebooks/process-bigraphs.ipynb +0 -739
  21. process-bigraph-0.0.22/notebooks/visualize_processes.ipynb +0 -237
  22. process-bigraph-0.0.22/process_bigraph/__init__.py +0 -16
  23. process-bigraph-0.0.22/process_bigraph/experiments/minimal_gillespie.py +0 -321
  24. process-bigraph-0.0.22/process_bigraph/processes/__init__.py +0 -0
  25. process-bigraph-0.0.22/pytest.ini +0 -7
  26. process-bigraph-0.0.22/release.sh +0 -43
  27. {process-bigraph-0.0.22 → process-bigraph-0.0.23}/AUTHORS.md +0 -0
  28. {process-bigraph-0.0.22 → process-bigraph-0.0.23}/LICENSE +0 -0
  29. {process-bigraph-0.0.22 → process-bigraph-0.0.23}/README.md +0 -0
  30. {process-bigraph-0.0.22 → process-bigraph-0.0.23}/process_bigraph/experiments/__init__.py +0 -0
  31. {process-bigraph-0.0.22 → process-bigraph-0.0.23}/process_bigraph/protocols.py +0 -0
  32. {process-bigraph-0.0.22 → process-bigraph-0.0.23}/process_bigraph.egg-info/dependency_links.txt +0 -0
  33. {process-bigraph-0.0.22 → process-bigraph-0.0.23}/process_bigraph.egg-info/requires.txt +0 -0
  34. {process-bigraph-0.0.22 → process-bigraph-0.0.23}/process_bigraph.egg-info/top_level.txt +0 -0
  35. {process-bigraph-0.0.22 → process-bigraph-0.0.23}/setup.cfg +0 -0
@@ -1,9 +1,12 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: process-bigraph
3
- Version: 0.0.22
3
+ Version: 0.0.23
4
+ Summary: UNKNOWN
4
5
  Home-page: https://github.com/vivarium-collective/process-bigraph
5
6
  Author: Ryan Spangler, Eran Agmon
6
7
  Author-email: ryan.spangler@gmail.com, agmon.eran@gmail.com
8
+ License: UNKNOWN
9
+ Platform: UNKNOWN
7
10
  Classifier: Development Status :: 3 - Alpha
8
11
  Classifier: Intended Audience :: Developers
9
12
  Classifier: License :: OSI Approved :: MIT License
@@ -70,3 +73,5 @@ diagraph of a whole-cell E. coli model.
70
73
  ## License
71
74
 
72
75
  Bigraph-schema is open-source software released under the [Apache 2 License](https://github.com/vivarium-collective/process-bigraph/blob/main/LICENSE).
76
+
77
+
@@ -0,0 +1,48 @@
1
+ import pprint
2
+ from bigraph_schema.registry import deep_merge, default
3
+ from process_bigraph.processes import register_processes
4
+ from process_bigraph.composite import Process, Step, Composite, ProcessTypes, interval_time_precision
5
+
6
+
7
+ pretty = pprint.PrettyPrinter(indent=2)
8
+
9
+
10
+ def pp(x):
11
+ """Print ``x`` in a pretty format."""
12
+ pretty.pprint(x)
13
+
14
+
15
+ def pf(x):
16
+ """Format ``x`` for display."""
17
+ return pretty.pformat(x)
18
+
19
+
20
+ def register_types(core):
21
+ core.register('default 1', {
22
+ '_inherit': 'float',
23
+ '_default': 1.0})
24
+
25
+ core.register('species_dependent_process', {
26
+ '_inherit': ['process'],
27
+ '_inputs': {
28
+ 'species': {
29
+ '_type': 'array',
30
+ '_data': 'float'}},
31
+ '_outputs': {
32
+ 'species': {
33
+ '_type': 'array',
34
+ '_data': 'float'}}})
35
+
36
+ core.register('ode_config', {
37
+ 'stoichiometry': {
38
+ '_type': 'array',
39
+ '_data': 'integer'},
40
+ 'rates': 'map[float]',
41
+ 'species': 'map[float]'})
42
+
43
+ core = register_processes(
44
+ core)
45
+
46
+ return core
47
+
48
+
@@ -10,9 +10,8 @@ import math
10
10
  import collections
11
11
  from typing import Dict
12
12
 
13
- from bigraph_schema import Edge, TypeSystem, get_path, set_path, deep_merge, is_schema_key, strip_schema_keys, Registry, hierarchy_depth
13
+ from bigraph_schema import Edge, TypeSystem, get_path, set_path, deep_merge, is_schema_key, strip_schema_keys, Registry, hierarchy_depth, visit_method
14
14
 
15
- from process_bigraph.process_types import process_types
16
15
  from process_bigraph.protocols import local_lookup, local_lookup_module
17
16
 
18
17
 
@@ -23,22 +22,227 @@ def assert_interface(interface: Dict):
23
22
  assert existing_keys == set(required_keys), f"every interface requires an inputs schema and an outputs schema, not {existing_keys}"
24
23
 
25
24
 
26
- def register_process_types(core: TypeSystem):
27
- """Register process types with the core"""
28
- for process_key, process_type in process_types.items():
29
- core.register(process_key, process_type)
30
- return core
25
+ def apply_process(schema, current, update, core):
26
+ """Apply an update to a process."""
27
+ process_schema = schema.copy()
28
+ process_schema.pop('_apply')
29
+ return core.apply(
30
+ process_schema,
31
+ current,
32
+ update)
31
33
 
32
34
 
33
- def register_protocols(core: TypeSystem):
34
- """Register protocols with the core"""
35
- core.protocol_registry.register('local', local_lookup)
35
+ def check_process(schema, state, core):
36
+ """Check if this is a process."""
37
+ return 'instance' in state and isinstance(
38
+ state['instance'],
39
+ Edge)
36
40
 
37
41
 
38
- def register_emitters(core: TypeSystem):
39
- """Register emitters with the core"""
40
- core.register_process('console-emitter', ConsoleEmitter)
41
- core.register_process('ram-emitter', RAMEmitter)
42
+ def fold_visit(schema, state, method, values, core):
43
+ visit = visit_method(
44
+ schema,
45
+ state,
46
+ method,
47
+ values,
48
+ core)
49
+
50
+ return visit
51
+
52
+
53
+ def divide_process(schema, state, values, core):
54
+ # daughter_configs must have a config per daughter
55
+
56
+ daughter_configs = values.get(
57
+ 'daughter_configs',
58
+ [{} for index in range(values['divisions'])])
59
+
60
+ if 'config' not in state:
61
+ return daughter_configs
62
+
63
+ existing_config = state['config']
64
+
65
+ divisions = []
66
+ for index in range(values['divisions']):
67
+ daughter_config = copy.deepcopy(
68
+ existing_config)
69
+ daughter_config = deep_merge(
70
+ daughter_config,
71
+ daughter_configs[index])
72
+
73
+ # TODO: provide a way to override inputs and outputs
74
+ daughter_state = {
75
+ 'address': state['address'],
76
+ 'config': daughter_config,
77
+ 'inputs': copy.deepcopy(state['inputs']),
78
+ 'outputs': copy.deepcopy(state['outputs'])}
79
+
80
+ if 'interval' in state:
81
+ daughter_state['interval'] = state['interval']
82
+
83
+ divisions.append(daughter_state)
84
+
85
+ return divisions
86
+
87
+
88
+ def serialize_process(schema, value, core):
89
+ """Serialize a process to a JSON-safe representation."""
90
+ # TODO -- need to get back the protocol: address and the config
91
+ process = value.copy()
92
+ process['config'] = core.serialize(
93
+ process['instance'].config_schema,
94
+ process['config'])
95
+ del process['instance']
96
+ return process
97
+
98
+
99
+ def deserialize_process(schema, encoded, core):
100
+ """Deserialize a process from a serialized state.
101
+
102
+ This function is used by the type system to deserialize a process.
103
+
104
+ :param encoded: A JSON-safe representation of the process.
105
+ :param bindings: The bindings to use for deserialization.
106
+ :param core: The type system to use for deserialization.
107
+
108
+ :returns: The deserialized state with an instantiated process.
109
+ """
110
+ encoded = encoded or {}
111
+ schema = schema or {}
112
+
113
+ if not encoded:
114
+ deserialized = core.default(schema)
115
+ else:
116
+ deserialized = encoded.copy()
117
+
118
+ if not deserialized.get('address'):
119
+ return deserialized
120
+
121
+ protocol, address = deserialized['address'].split(':', 1)
122
+
123
+ if 'instance' in deserialized:
124
+ instantiate = type(deserialized['instance'])
125
+ else:
126
+ process_lookup = core.protocol_registry.access(protocol)
127
+ if not process_lookup:
128
+ raise Exception(f'protocol "{protocol}" not implemented')
129
+
130
+ instantiate = process_lookup(core, address)
131
+ if not instantiate:
132
+ raise Exception(f'process "{address}" not found')
133
+
134
+ config = core.deserialize(
135
+ instantiate.config_schema,
136
+ deserialized.get('config', {}))
137
+
138
+ interval = core.deserialize(
139
+ 'interval',
140
+ deserialized.get('interval'))
141
+
142
+ if interval is None:
143
+ interval = core.default(
144
+ schema.get(
145
+ 'interval',
146
+ 'interval'))
147
+
148
+ if not 'instance' in deserialized:
149
+ process = instantiate(
150
+ config,
151
+ core=core)
152
+
153
+ deserialized['instance'] = process
154
+
155
+ deserialized['config'] = config
156
+ deserialized['interval'] = interval
157
+ deserialized['_inputs'] = deserialized['instance'].inputs()
158
+ deserialized['_outputs'] = deserialized['instance'].outputs()
159
+
160
+ return deserialized
161
+
162
+
163
+ def deserialize_step(schema, encoded, core):
164
+ if not encoded:
165
+ deserialized = core.default(schema)
166
+ else:
167
+ deserialized = encoded.copy()
168
+
169
+ if not deserialized['address']:
170
+ return deserialized
171
+
172
+ protocol, address = deserialized['address'].split(':', 1)
173
+
174
+ if 'instance' in deserialized:
175
+ instantiate = type(deserialized['instance'])
176
+ else:
177
+ process_lookup = core.protocol_registry.access(protocol)
178
+ if not process_lookup:
179
+ raise Exception(f'protocol "{protocol}" not implemented')
180
+
181
+ instantiate = process_lookup(core, address)
182
+ if not instantiate:
183
+ raise Exception(f'process "{address}" not found')
184
+
185
+ config = core.deserialize(
186
+ instantiate.config_schema,
187
+ deserialized.get('config', {}))
188
+
189
+ if not 'instance' in deserialized:
190
+ process = instantiate(config, core=core)
191
+ deserialized['instance'] = process
192
+
193
+ deserialized['config'] = config
194
+ deserialized['_inputs'] = deserialized['instance'].inputs()
195
+ deserialized['_outputs'] = deserialized['instance'].outputs()
196
+
197
+ return deserialized
198
+
199
+
200
+ PROCESS_TYPES = {
201
+ 'protocol': {
202
+ '_type': 'protocol',
203
+ '_inherit': 'string'},
204
+
205
+ 'emitter_mode': 'enum[none,all,stores,bridge,paths,ports]',
206
+
207
+ 'interval': {
208
+ '_type': 'interval',
209
+ '_inherit': 'float',
210
+ '_apply': 'set',
211
+ '_default': '1.0'},
212
+
213
+ 'step': {
214
+ '_type': 'step',
215
+ '_inherit': 'edge',
216
+ '_apply': apply_process,
217
+ '_serialize': serialize_process,
218
+ '_deserialize': deserialize_step,
219
+ '_check': check_process,
220
+ '_fold': fold_visit,
221
+ '_divide': divide_process,
222
+ '_description': '',
223
+ # TODO: support reference to type parameters from other states
224
+ 'address': 'protocol',
225
+ 'config': 'quote'},
226
+
227
+ # TODO: slice process to allow for navigating through a port
228
+ 'process': {
229
+ '_type': 'process',
230
+ '_inherit': 'edge',
231
+ '_apply': apply_process,
232
+ '_serialize': serialize_process,
233
+ '_deserialize': deserialize_process,
234
+ '_check': check_process,
235
+ '_fold': fold_visit,
236
+ '_divide': divide_process,
237
+ '_description': '',
238
+ # TODO: support reference to type parameters from other states
239
+ 'interval': 'interval',
240
+ 'address': 'protocol',
241
+ 'config': 'quote'}}
242
+
243
+
244
+ BASE_PROTOCOLS = {
245
+ 'local': local_lookup}
42
246
 
43
247
 
44
248
  class ProcessTypes(TypeSystem):
@@ -53,13 +257,18 @@ class ProcessTypes(TypeSystem):
53
257
  self.process_registry = Registry()
54
258
  self.protocol_registry = Registry()
55
259
 
56
- register_process_types(self)
57
- register_protocols(self)
58
- register_emitters(self)
260
+ self.register_types(PROCESS_TYPES)
261
+ self.register_protocols(BASE_PROTOCOLS)
262
+ self.register_processes(BASE_EMITTERS)
59
263
 
60
264
  self.register_process('composite', Composite)
61
265
 
62
266
 
267
+ def register_protocols(self, protocols):
268
+ """Register protocols with the core"""
269
+ self.protocol_registry.register_multiple(protocols)
270
+
271
+
63
272
  def register_process(
64
273
  self,
65
274
  name,
@@ -75,6 +284,13 @@ class ProcessTypes(TypeSystem):
75
284
  self.process_registry.register(name, process_data)
76
285
 
77
286
 
287
+ def register_processes(self, processes):
288
+ for process_key, process_data in processes.items():
289
+ self.register_process(
290
+ process_key,
291
+ process_data)
292
+
293
+
78
294
  def initialize_edge_state(self, schema, path, edge):
79
295
  """
80
296
  Initialize the state for an edge based on the schema and the edge.
@@ -181,10 +397,10 @@ class Process(Edge):
181
397
  if config is None:
182
398
  config = {}
183
399
 
184
- # check that all keywords in config are in config_schema
185
- for key in config.keys():
186
- if key not in self.config_schema:
187
- raise Exception(f'config key {key} not in config_schema for {self.__class__.__name__}')
400
+ # # check that all keywords in config are in config_schema
401
+ # for key in config.keys():
402
+ # if key not in self.config_schema:
403
+ # raise Exception(f'config key {key} not in config_schema for {self.__class__.__name__}')
188
404
 
189
405
  # fill in defaults for config
190
406
  self.config = self.core.fill(
@@ -339,7 +555,9 @@ def find_leaves(tree_structure, path=None):
339
555
  leaves = []
340
556
  path = ()
341
557
 
342
- if isinstance(tree_structure, list):
558
+ if tree_structure is None:
559
+ pass
560
+ elif isinstance(tree_structure, list):
343
561
  leaves = tree_structure
344
562
  elif isinstance(tree_structure, tuple):
345
563
  leaves.append(tree_structure)
@@ -519,18 +737,18 @@ class Composite(Process):
519
737
  if 'global_time' not in initial_state:
520
738
  initial_state['global_time'] = 0.0
521
739
 
522
- composition, state = self.core.complete(
740
+ self.composition, self.state = self.core.generate(
523
741
  initial_composition,
524
742
  initial_state)
525
743
 
526
- self.composition = copy.deepcopy(
527
- self.core.access(composition))
744
+ # self.composition = copy.deepcopy(
745
+ # self.core.access(composition))
528
746
 
529
747
  # TODO: add flag to self.core.access(copy=True)
530
748
  self.bridge = self.config.get('bridge', {})
531
749
 
532
750
  self.find_instance_paths(
533
- state)
751
+ self.state)
534
752
 
535
753
  # merge the processes and steps into a single "edges" dict
536
754
  self.edge_paths = self.process_paths.copy()
@@ -553,11 +771,13 @@ class Composite(Process):
553
771
  raise Exception(
554
772
  f'initial state from edge does not match initial state from other edges:\n{path}\n{edge}\n{edge_state}')
555
773
 
556
- state = deep_merge(state, edge_state)
774
+ self.state = deep_merge(
775
+ self.state,
776
+ edge_state)
557
777
 
558
- self.state = self.core.deserialize(
559
- self.composition,
560
- state)
778
+ # self.state = self.core.deserialize(
779
+ # self.composition,
780
+ # self.state)
561
781
 
562
782
  # TODO: call validate on this composite, not just check
563
783
  # assert self.core.validate(
@@ -603,6 +823,7 @@ class Composite(Process):
603
823
 
604
824
  # self.run_steps(self.to_run)
605
825
 
826
+
606
827
  def save(self,
607
828
  filename='composite.json',
608
829
  outdir='out',
@@ -729,7 +950,11 @@ class Composite(Process):
729
950
  step_config = self.read_emitter_config(emitter_config)
730
951
  emitter = set_path(
731
952
  {}, path, step_config)
732
- self.merge(emitter)
953
+
954
+ self.merge(
955
+ {},
956
+ emitter)
957
+
733
958
  _, instance = self.core.slice(
734
959
  self.composition,
735
960
  self.state,
@@ -739,19 +964,14 @@ class Composite(Process):
739
964
  self.step_paths[path] = instance
740
965
 
741
966
 
742
- # TODO: merge needs to be schema aware,
743
- # and since the results of the merge may
744
- # entail a schema update, we need to return
745
- # the new schema
746
- def merge(self, initial_state):
747
- self.state = self.core.merge(
967
+ def merge(self, schema, state, path=None):
968
+ path = path or []
969
+ self.composition, self.state = self.core.merge(
748
970
  self.composition,
749
971
  self.state,
750
- initial_state)
751
-
752
- self.composition, self.state = self.core.complete(
753
- self.composition,
754
- self.state)
972
+ path,
973
+ schema,
974
+ state)
755
975
 
756
976
 
757
977
  def process_update(
@@ -1001,23 +1221,23 @@ class Composite(Process):
1001
1221
  force_complete = False
1002
1222
 
1003
1223
 
1004
- def determine_steps(self):
1005
- to_run = []
1006
- for step_key, wires in trigger_state['steps']:
1007
- fulfilled = True
1008
- for input in wires['input_paths']:
1009
- if len(trigger_state['states'][tuple(input)]) > 0:
1010
- fulfilled = False
1011
- break
1012
- if fulfilled:
1013
- to_run.append(step_key)
1224
+ # def determine_steps(self):
1225
+ # to_run = []
1226
+ # for step_key, wires in trigger_state['steps']:
1227
+ # fulfilled = True
1228
+ # for input in wires['input_paths']:
1229
+ # if len(trigger_state['states'][tuple(input)]) > 0:
1230
+ # fulfilled = False
1231
+ # break
1232
+ # if fulfilled:
1233
+ # to_run.append(step_key)
1014
1234
 
1015
- for step_key in to_run:
1016
- wires = trigger_state['steps'][step_key]
1017
- for output in wires['output_paths']:
1018
- trigger_state['states'][tuple(output)].remove(step_key)
1235
+ # for step_key in to_run:
1236
+ # wires = trigger_state['steps'][step_key]
1237
+ # for output in wires['output_paths']:
1238
+ # trigger_state['states'][tuple(output)].remove(step_key)
1019
1239
 
1020
- return to_run, trigger_state
1240
+ # return to_run, trigger_state
1021
1241
 
1022
1242
 
1023
1243
  def run_steps(self, step_paths):
@@ -1106,6 +1326,7 @@ class Composite(Process):
1106
1326
  state)
1107
1327
 
1108
1328
  self.merge(
1329
+ {},
1109
1330
  projection)
1110
1331
 
1111
1332
  self.run(interval)
@@ -1183,6 +1404,11 @@ class RAMEmitter(Emitter):
1183
1404
  return result
1184
1405
 
1185
1406
 
1407
+ BASE_EMITTERS = {
1408
+ 'console-emitter': ConsoleEmitter,
1409
+ 'ram-emitter': RAMEmitter}
1410
+
1411
+
1186
1412
  # def StateEmitter(Emitter):
1187
1413
 
1188
1414
 
@@ -1,5 +1,5 @@
1
1
  import pytest
2
- from process_bigraph import Step, Process, Composite, ProcessTypes, interval_time_precision, deep_merge
2
+ from process_bigraph.composite import Step, Process, Composite, ProcessTypes, interval_time_precision, deep_merge
3
3
 
4
4
 
5
5
  class Grow(Process):
@@ -92,6 +92,7 @@ def generate_bridge(schema, state, interval=1.0):
92
92
  for port in ['inputs', 'outputs']}
93
93
 
94
94
  config = {
95
+ '_type': 'quote',
95
96
  'state': state,
96
97
  'bridge': bridge}
97
98
 
@@ -160,7 +161,7 @@ def grow_divide_agent(config=None, state=None, path=None):
160
161
  state)
161
162
 
162
163
  composite = generate_bridge({
163
- 'inputs': {},
164
+ 'inputs': {'mass': ['mass']},
164
165
  'outputs': agent_schema},
165
166
  grow_divide_state)
166
167
 
@@ -170,39 +171,3 @@ def grow_divide_agent(config=None, state=None, path=None):
170
171
  return composite
171
172
 
172
173
 
173
- def test_grow_divide(core):
174
- initial_mass = 1.0
175
-
176
- grow_divide = grow_divide_agent(
177
- {'grow': {'rate': 0.03}},
178
- {'mass': initial_mass},
179
- ['environment', '0'])
180
-
181
- environment = {
182
- 'environment': {
183
- '0': {
184
- 'mass': initial_mass,
185
- 'grow_divide': grow_divide}}}
186
-
187
- composite = Composite({
188
- 'state': environment},
189
- core=core)
190
-
191
- updates = composite.update({}, 100.0)
192
- assert '0_0_0_0_0' in composite.state['environment']
193
-
194
-
195
- @pytest.fixture
196
- def core():
197
- core = ProcessTypes()
198
- core.register_process('grow', Grow)
199
- core.register_process('divide', Divide)
200
- return core
201
-
202
-
203
- if __name__ == '__main__':
204
- core = ProcessTypes()
205
- core.register_process('grow', Grow)
206
- core.register_process('divide', Divide)
207
-
208
- test_grow_divide(core)