process-bigraph 0.0.23__tar.gz → 0.0.25__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 (22) hide show
  1. {process-bigraph-0.0.23/process_bigraph.egg-info → process-bigraph-0.0.25}/PKG-INFO +1 -1
  2. {process-bigraph-0.0.23 → process-bigraph-0.0.25}/process_bigraph/composite.py +294 -330
  3. {process-bigraph-0.0.23 → process-bigraph-0.0.25}/process_bigraph/experiments/minimal_gillespie.py +55 -6
  4. process-bigraph-0.0.25/process_bigraph/processes/__init__.py +21 -0
  5. {process-bigraph-0.0.23/process_bigraph/experiments → process-bigraph-0.0.25/process_bigraph/processes}/growth_division.py +1 -9
  6. {process-bigraph-0.0.23 → process-bigraph-0.0.25}/process_bigraph/processes/parameter_scan.py +7 -12
  7. {process-bigraph-0.0.23 → process-bigraph-0.0.25}/process_bigraph/tests.py +14 -78
  8. {process-bigraph-0.0.23 → process-bigraph-0.0.25/process_bigraph.egg-info}/PKG-INFO +1 -1
  9. {process-bigraph-0.0.23 → process-bigraph-0.0.25}/process_bigraph.egg-info/SOURCES.txt +1 -1
  10. {process-bigraph-0.0.23 → process-bigraph-0.0.25}/process_bigraph.egg-info/requires.txt +1 -2
  11. {process-bigraph-0.0.23 → process-bigraph-0.0.25}/setup.py +3 -3
  12. process-bigraph-0.0.23/process_bigraph/processes/__init__.py +0 -18
  13. {process-bigraph-0.0.23 → process-bigraph-0.0.25}/AUTHORS.md +0 -0
  14. {process-bigraph-0.0.23 → process-bigraph-0.0.25}/LICENSE +0 -0
  15. {process-bigraph-0.0.23 → process-bigraph-0.0.25}/README.md +0 -0
  16. {process-bigraph-0.0.23 → process-bigraph-0.0.25}/process_bigraph/__init__.py +0 -0
  17. {process-bigraph-0.0.23 → process-bigraph-0.0.25}/process_bigraph/experiments/__init__.py +0 -0
  18. {process-bigraph-0.0.23 → process-bigraph-0.0.25}/process_bigraph/process_types.py +0 -0
  19. {process-bigraph-0.0.23 → process-bigraph-0.0.25}/process_bigraph/protocols.py +0 -0
  20. {process-bigraph-0.0.23 → process-bigraph-0.0.25}/process_bigraph.egg-info/dependency_links.txt +0 -0
  21. {process-bigraph-0.0.23 → process-bigraph-0.0.25}/process_bigraph.egg-info/top_level.txt +0 -0
  22. {process-bigraph-0.0.23 → process-bigraph-0.0.25}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: process-bigraph
3
- Version: 0.0.23
3
+ Version: 0.0.25
4
4
  Summary: UNKNOWN
5
5
  Home-page: https://github.com/vivarium-collective/process-bigraph
6
6
  Author: Ryan Spangler, Eran Agmon
@@ -15,6 +15,11 @@ from bigraph_schema import Edge, TypeSystem, get_path, set_path, deep_merge, is_
15
15
  from process_bigraph.protocols import local_lookup, local_lookup_module
16
16
 
17
17
 
18
+
19
+ # =========================
20
+ # Process Utility Functions
21
+ # =========================
22
+
18
23
  def assert_interface(interface: Dict):
19
24
  """Ensure that an interface dict has the required keys"""
20
25
  required_keys = ['inputs', 'outputs']
@@ -22,6 +27,224 @@ def assert_interface(interface: Dict):
22
27
  assert existing_keys == set(required_keys), f"every interface requires an inputs schema and an outputs schema, not {existing_keys}"
23
28
 
24
29
 
30
+ def find_instances(state, instance_type='process_bigraph.composite.Process'):
31
+ process_class = local_lookup_module(instance_type)
32
+ found = {}
33
+
34
+ for key, inner in state.items():
35
+ if isinstance(inner, dict):
36
+ if isinstance(inner.get('instance'), process_class):
37
+ found[key] = inner
38
+ elif not is_schema_key(key):
39
+ inner_instances = find_instances(
40
+ inner,
41
+ instance_type=instance_type)
42
+
43
+ if inner_instances:
44
+ found[key] = inner_instances
45
+ return found
46
+
47
+
48
+ def find_instance_paths(state, instance_type='process_bigraph.composite.Process'):
49
+ instances = find_instances(state, instance_type)
50
+ return hierarchy_depth(instances)
51
+
52
+
53
+ def find_step_triggers(path, step):
54
+ prefix = tuple(path[:-1])
55
+ triggers = {}
56
+ wire_paths = find_leaves(
57
+ step['inputs'])
58
+
59
+ for wire in wire_paths:
60
+ trigger_path = tuple(prefix) + tuple(wire)
61
+ if trigger_path not in triggers:
62
+ triggers[trigger_path] = []
63
+ triggers[trigger_path].append(path)
64
+
65
+ return triggers
66
+
67
+
68
+ def explode_path(path):
69
+ explode = ()
70
+ paths = [explode]
71
+
72
+ for node in path:
73
+ explode = explode + (node,)
74
+ paths.append(explode)
75
+
76
+ return paths
77
+
78
+
79
+ def merge_collections(existing, new):
80
+ if existing is None:
81
+ existing = {}
82
+ if new is None:
83
+ new = {}
84
+ for key, value in new.items():
85
+ if key in existing:
86
+ if isinstance(existing[key], dict) and isinstance(new[key], collections.abc.Mapping):
87
+ merge_collections(existing[key], new[key])
88
+ elif isinstance(existing[key], list) and isinstance(new[key], collections.abc.Sequence):
89
+ existing[key].extend(new[key])
90
+ else:
91
+ raise Exception(
92
+ f'cannot merge collections as they do not match:\n{existing}\n{new}')
93
+ else:
94
+ existing[key] = value
95
+
96
+ return existing
97
+
98
+
99
+ def empty_front(time):
100
+ return {
101
+ 'time': time,
102
+ 'update': {}
103
+ }
104
+
105
+
106
+ def find_leaves(tree_structure, path=None):
107
+ leaves = []
108
+ path = ()
109
+
110
+ if tree_structure is None:
111
+ pass
112
+ elif isinstance(tree_structure, list):
113
+ leaves = tree_structure
114
+ elif isinstance(tree_structure, tuple):
115
+ leaves.append(tree_structure)
116
+ else:
117
+ for key, value in tree_structure.items():
118
+ if isinstance(value, dict):
119
+ subleaves = find_leaves(value, path + (key,))
120
+ leaves.extend(subleaves)
121
+ else:
122
+ leaves.append(path + tuple(value))
123
+
124
+ return leaves
125
+
126
+
127
+ def build_step_network(steps):
128
+ ancestors = {
129
+ step_key: {
130
+ 'input_paths': None,
131
+ 'output_paths': None}
132
+ for step_key in steps}
133
+
134
+ nodes = {}
135
+
136
+ for step_key, step in steps.items():
137
+ for other_key, other_step in steps.items():
138
+ if step_key == other_key:
139
+ continue
140
+
141
+ schema = step['instance'].interface()
142
+ other_schema = other_step['instance'].interface()
143
+
144
+ assert_interface(schema)
145
+ assert_interface(other_schema)
146
+
147
+ if ancestors[step_key]['input_paths'] is None:
148
+ ancestors[step_key]['input_paths'] = find_leaves(
149
+ step['inputs'])
150
+ input_paths = ancestors[step_key]['input_paths']
151
+
152
+ if ancestors[step_key]['output_paths'] is None:
153
+ ancestors[step_key]['output_paths'] = find_leaves(
154
+ step.get('outputs', {}))
155
+ output_paths = ancestors[step_key]['output_paths']
156
+
157
+ for input in input_paths:
158
+ path = tuple(input)
159
+ if not path in nodes:
160
+ nodes[path] = {
161
+ 'before': set([]),
162
+ 'after': set([])}
163
+ nodes[path]['after'].add(step_key)
164
+
165
+ for output in output_paths:
166
+ if output in input_paths:
167
+ continue
168
+
169
+ path = tuple(output)
170
+ if not path in nodes:
171
+ nodes[path] = {
172
+ 'before': set([]),
173
+ 'after': set([])}
174
+ nodes[path]['before'].add(step_key)
175
+
176
+ return ancestors, nodes
177
+
178
+
179
+ def build_trigger_state(nodes):
180
+ return {
181
+ key: value['before'].copy()
182
+ for key, value in nodes.items()}
183
+
184
+
185
+ def find_downstream(steps, nodes, upstream):
186
+ downstream = set(upstream)
187
+ visited = set([])
188
+ previous_len = -1
189
+
190
+ while len(downstream) > len(visited) and len(visited) > previous_len:
191
+ previous_len = len(visited)
192
+ down = set([])
193
+ for step_path in downstream:
194
+ if step_path not in visited:
195
+ step_outputs = steps[step_path]['output_paths']
196
+ if step_outputs is None:
197
+ step_outputs = [] # Ensure step_outputs is always an iterable
198
+ for output in step_outputs:
199
+ for dependent in nodes[output]['after']:
200
+ down.add(dependent)
201
+ visited.add(step_path)
202
+ downstream |= down
203
+
204
+ return downstream
205
+
206
+
207
+ def determine_steps(steps, remaining, fulfilled):
208
+ to_run = []
209
+ for step_path in remaining:
210
+ step_inputs = steps[step_path]['input_paths']
211
+ if step_inputs is None:
212
+ step_inputs = []
213
+ all_fulfilled = True
214
+ for input in step_inputs:
215
+ if len(fulfilled[input]) > 0:
216
+ all_fulfilled = False
217
+ if all_fulfilled:
218
+ to_run.append(step_path)
219
+
220
+ for step_path in to_run:
221
+ remaining.remove(step_path)
222
+ step_outputs = steps[step_path]['output_paths']
223
+ if step_outputs is None:
224
+ step_outputs = []
225
+
226
+ for output in step_outputs:
227
+ if output in fulfilled and step_path in fulfilled[output]:
228
+ fulfilled[output].remove(step_path)
229
+
230
+ return to_run, remaining, fulfilled
231
+
232
+
233
+ def interval_time_precision(timestep):
234
+ # get number of decimal places to set global time precision
235
+ timestep_str = str(timestep)
236
+ global_time_precision = 0
237
+ if '.' in timestep_str:
238
+ _, decimals = timestep_str.split('.')
239
+ global_time_precision = len(decimals)
240
+
241
+ return global_time_precision
242
+
243
+
244
+ # ======================
245
+ # Process Type Functions
246
+ # ======================
247
+
25
248
  def apply_process(schema, current, update, core):
26
249
  """Apply an update to a process."""
27
250
  process_schema = schema.copy()
@@ -245,6 +468,10 @@ BASE_PROTOCOLS = {
245
468
  'local': local_lookup}
246
469
 
247
470
 
471
+ # ===================
472
+ # Process Type System
473
+ # ===================
474
+
248
475
  class ProcessTypes(TypeSystem):
249
476
  """
250
477
  ProcessTypes class extends the TypeSystem class to include process types.
@@ -328,6 +555,10 @@ class ProcessTypes(TypeSystem):
328
555
  return state
329
556
 
330
557
 
558
+ # ===============
559
+ # Process Classes
560
+ # ===============
561
+
331
562
  class SyncUpdate():
332
563
  def __init__(self, update):
333
564
  self.update = update
@@ -344,23 +575,6 @@ class Step(Edge):
344
575
  like a workflow.
345
576
  """
346
577
  # TODO: support trigger every time as well as dependency trigger
347
- config_schema = {}
348
-
349
-
350
- def __init__(self, config=None, core=None):
351
- self.core = core or ProcessTypes()
352
-
353
- if config is None:
354
- config = {}
355
-
356
- self.config = self.core.fill(
357
- self.config_schema,
358
- config)
359
-
360
-
361
- def initial_state(self):
362
- return {}
363
-
364
578
 
365
579
  def invoke(self, state, _=None):
366
580
  update = self.update(state)
@@ -389,34 +603,6 @@ class Process(Edge):
389
603
  config: Override the class defaults. This dictionary may
390
604
  also contain the following special keys (TODO):
391
605
  """
392
- config_schema = {}
393
-
394
- def __init__(self, config=None, core=None):
395
- self.core = core or ProcessTypes()
396
-
397
- if config is None:
398
- config = {}
399
-
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__}')
404
-
405
- # fill in defaults for config
406
- self.config = self.core.fill(
407
- self.config_schema,
408
- config)
409
-
410
- # TODO: validate your config after filling, report if anything
411
- # is off
412
- # print(self.core.validate_state(
413
- # self.config_schema,
414
- # config))
415
-
416
-
417
- def initial_state(self):
418
- return {}
419
-
420
606
 
421
607
  def invoke(self, state, interval):
422
608
  update = self.update(state, interval)
@@ -428,9 +614,33 @@ class Process(Edge):
428
614
  return {}
429
615
 
430
616
 
431
- # TODO: should we include run(interval) here?
432
- # process would have to maintain state
617
+ class ProcessEnsemble(Process):
618
+ def __init__(self, config=None, core=None):
619
+ self.__init__(config, core)
620
+
621
+
622
+ def union_interface(self):
623
+ union_inputs = {}
624
+ union_outputs = {}
625
+
626
+ for self_key in dir(self):
627
+ if self_key.startswith('inputs_'):
628
+ inputs = self.getattr(self_key)()
629
+ union_inputs = self.core.resolve_schemas(
630
+ union_inputs,
631
+ inputs)
632
+
633
+ if self_key.startswith('outputs_'):
634
+ outputs = self.getattr(self_key)()
635
+ union_outputs = self.core.resolve_schemas(
636
+ union_outputs,
637
+ outputs)
638
+
639
+ return {
640
+ 'inputs': union_inputs,
641
+ 'outputs': union_outputs}
433
642
 
643
+
434
644
 
435
645
  class Defer:
436
646
  """Allows for delayed application of a function to an update.
@@ -443,7 +653,7 @@ class Defer:
443
653
  defer: An object with a ``.get_command_result()`` method
444
654
  whose output will be passed to the function. For
445
655
  example, the object could be an
446
- :py:class:`vivarium.core.process.Process` object whose
656
+ :Process` object whose
447
657
  ``.get_command_result()`` method will return the process
448
658
  update.
449
659
  function: The function. For example,
@@ -475,216 +685,6 @@ class Defer:
475
685
  self.args)
476
686
 
477
687
 
478
- def find_instances(state, instance_type='process_bigraph.composite.Process'):
479
- process_class = local_lookup_module(instance_type)
480
- found = {}
481
-
482
- for key, inner in state.items():
483
- if isinstance(inner, dict):
484
- if isinstance(inner.get('instance'), process_class):
485
- found[key] = inner
486
- elif not is_schema_key(key):
487
- inner_instances = find_instances(
488
- inner,
489
- instance_type=instance_type)
490
-
491
- if inner_instances:
492
- found[key] = inner_instances
493
- return found
494
-
495
-
496
- def find_instance_paths(state, instance_type='process_bigraph.composite.Process'):
497
- instances = find_instances(state, instance_type)
498
- return hierarchy_depth(instances)
499
-
500
-
501
- def find_step_triggers(path, step):
502
- prefix = tuple(path[:-1])
503
- triggers = {}
504
- wire_paths = find_leaves(
505
- step['inputs'])
506
-
507
- for wire in wire_paths:
508
- trigger_path = tuple(prefix) + tuple(wire)
509
- if trigger_path not in triggers:
510
- triggers[trigger_path] = []
511
- triggers[trigger_path].append(path)
512
-
513
- return triggers
514
-
515
-
516
- def explode_path(path):
517
- explode = ()
518
- paths = [explode]
519
-
520
- for node in path:
521
- explode = explode + (node,)
522
- paths.append(explode)
523
-
524
- return paths
525
-
526
-
527
- def merge_collections(existing, new):
528
- if existing is None:
529
- existing = {}
530
- if new is None:
531
- new = {}
532
- for key, value in new.items():
533
- if key in existing:
534
- if isinstance(existing[key], dict) and isinstance(new[key], collections.abc.Mapping):
535
- merge_collections(existing[key], new[key])
536
- elif isinstance(existing[key], list) and isinstance(new[key], collections.abc.Sequence):
537
- existing[key].extend(new[key])
538
- else:
539
- raise Exception(
540
- f'cannot merge collections as they do not match:\n{existing}\n{new}')
541
- else:
542
- existing[key] = value
543
-
544
- return existing
545
-
546
-
547
- def empty_front(time):
548
- return {
549
- 'time': time,
550
- 'update': {}
551
- }
552
-
553
-
554
- def find_leaves(tree_structure, path=None):
555
- leaves = []
556
- path = ()
557
-
558
- if tree_structure is None:
559
- pass
560
- elif isinstance(tree_structure, list):
561
- leaves = tree_structure
562
- elif isinstance(tree_structure, tuple):
563
- leaves.append(tree_structure)
564
- else:
565
- for key, value in tree_structure.items():
566
- if isinstance(value, dict):
567
- subleaves = find_leaves(value, path + (key,))
568
- leaves.extend(subleaves)
569
- else:
570
- leaves.append(path + tuple(value))
571
-
572
- return leaves
573
-
574
-
575
- def build_step_network(steps):
576
- ancestors = {
577
- step_key: {
578
- 'input_paths': None,
579
- 'output_paths': None}
580
- for step_key in steps}
581
-
582
- nodes = {}
583
-
584
- for step_key, step in steps.items():
585
- for other_key, other_step in steps.items():
586
- if step_key == other_key:
587
- continue
588
-
589
- schema = step['instance'].interface()
590
- other_schema = other_step['instance'].interface()
591
-
592
- assert_interface(schema)
593
- assert_interface(other_schema)
594
-
595
- if ancestors[step_key]['input_paths'] is None:
596
- ancestors[step_key]['input_paths'] = find_leaves(
597
- step['inputs'])
598
- input_paths = ancestors[step_key]['input_paths']
599
-
600
- if ancestors[step_key]['output_paths'] is None:
601
- ancestors[step_key]['output_paths'] = find_leaves(
602
- step.get('outputs', {}))
603
- output_paths = ancestors[step_key]['output_paths']
604
-
605
- for input in input_paths:
606
- path = tuple(input)
607
- if not path in nodes:
608
- nodes[path] = {
609
- 'before': set([]),
610
- 'after': set([])}
611
- nodes[path]['after'].add(step_key)
612
-
613
- for output in output_paths:
614
- path = tuple(output)
615
- if not path in nodes:
616
- nodes[path] = {
617
- 'before': set([]),
618
- 'after': set([])}
619
- nodes[path]['before'].add(step_key)
620
-
621
- return ancestors, nodes
622
-
623
-
624
-
625
- def build_trigger_state(nodes):
626
- return {
627
- key: value['before'].copy()
628
- for key, value in nodes.items()}
629
-
630
-
631
- def find_downstream(steps, nodes, upstream):
632
- downstream = set(upstream)
633
- visited = set([])
634
- previous_len = -1
635
-
636
- while len(downstream) > len(visited) and len(visited) > previous_len:
637
- previous_len = len(visited)
638
- down = set([])
639
- for step_path in downstream:
640
- if step_path not in visited:
641
- step_outputs = steps[step_path]['output_paths']
642
- if step_outputs is None:
643
- step_outputs = [] # Ensure step_outputs is always an iterable
644
- for output in step_outputs:
645
- for dependent in nodes[output]['after']:
646
- down.add(dependent)
647
- visited.add(step_path)
648
- downstream |= down
649
-
650
- return downstream
651
-
652
-
653
- def determine_steps(steps, remaining, fulfilled):
654
- to_run = []
655
- for step_path in remaining:
656
- step_inputs = steps[step_path]['input_paths']
657
- if step_inputs is None:
658
- step_inputs = []
659
- all_fulfilled = True
660
- for input in step_inputs:
661
- if len(fulfilled[input]) > 0:
662
- all_fulfilled = False
663
- if all_fulfilled:
664
- to_run.append(step_path)
665
-
666
- for step_path in to_run:
667
- remaining.remove(step_path)
668
- step_outputs = steps[step_path]['output_paths']
669
- if step_outputs is None:
670
- step_outputs = []
671
- for output in step_outputs:
672
- fulfilled[output].remove(step_path)
673
-
674
- return to_run, remaining, fulfilled
675
-
676
-
677
- def interval_time_precision(timestep):
678
- # get number of decimal places to set global time precision
679
- timestep_str = str(timestep)
680
- global_time_precision = 0
681
- if '.' in timestep_str:
682
- _, decimals = timestep_str.split('.')
683
- global_time_precision = len(decimals)
684
-
685
- return global_time_precision
686
-
687
-
688
688
  class Composite(Process):
689
689
  """
690
690
  Composite parent class.
@@ -724,8 +724,7 @@ class Composite(Process):
724
724
  return composite
725
725
 
726
726
 
727
- def __init__(self, config=None, core=None):
728
- super().__init__(config, core)
727
+ def initialize(self, config=None):
729
728
 
730
729
  # insert global_time into schema if not present
731
730
  initial_composition = self.config.get('composition', {})
@@ -798,8 +797,19 @@ class Composite(Process):
798
797
  self.add_emitter(
799
798
  emitter_config)
800
799
 
801
- self.step_triggers = {}
800
+ self.front: Dict = {
801
+ path: empty_front(self.state['global_time'])
802
+ for path in self.process_paths}
803
+
804
+ self.bridge_updates = []
805
+
806
+ # build the step network
807
+ self.build_step_network()
808
+
809
+ # self.run_steps(self.to_run)
802
810
 
811
+ def build_step_network(self):
812
+ self.step_triggers = {}
803
813
  for step_path, step in self.step_paths.items():
804
814
  step_triggers = find_step_triggers(
805
815
  step_path, step)
@@ -809,20 +819,18 @@ class Composite(Process):
809
819
 
810
820
  self.steps_run = set([])
811
821
 
812
- self.front: Dict = {
813
- path: empty_front(self.state['global_time'])
814
- for path in self.process_paths}
815
-
816
- self.bridge_updates = []
817
-
818
822
  self.step_dependencies, self.node_dependencies = build_step_network(
819
823
  self.step_paths)
820
824
 
821
- self.reset_step_state(self.step_paths)
822
- self.to_run = self.cycle_step_state()
825
+ self.reset_step_state(
826
+ self.step_paths)
823
827
 
824
- # self.run_steps(self.to_run)
828
+ self.to_run = self.cycle_step_state()
825
829
 
830
+ def serialize_state(self):
831
+ return self.core.serialize(
832
+ self.composition,
833
+ self.state)
826
834
 
827
835
  def save(self,
828
836
  filename='composite.json',
@@ -830,9 +838,9 @@ class Composite(Process):
830
838
  schema=False,
831
839
  state=False):
832
840
 
833
- # TODO: add in dependent packages and version
834
- # maybe packagename.typename?
835
- # TODO: add in dependent types
841
+ # upcoming deprecation warning
842
+ print("Warning: save() is deprecated and will be removed in a future version. "
843
+ "Use use Vivarium for managing simulations instead of Composite.")
836
844
 
837
845
  document = {}
838
846
 
@@ -840,30 +848,15 @@ class Composite(Process):
840
848
  schema = state = True
841
849
 
842
850
  if state:
843
- serialized_state = self.core.serialize(
844
- self.composition,
845
- self.state)
846
-
851
+ serialized_state = self.serialize_state()
847
852
  document['state'] = serialized_state
848
853
 
849
854
  if schema:
850
- # serialized_schema = self.core.serialize(
851
- # 'schema',
852
- # self.composition)
853
-
854
855
  serialized_schema = self.core.representation(
855
856
  self.composition)
856
-
857
857
  document['composition'] = serialized_schema
858
858
 
859
- # TODO: make this true
860
- # copy_composite = Composite({
861
- # 'state': self.state})
862
-
863
- # assert copy_composite == self
864
-
865
859
  # save the dictionary to a JSON file
866
-
867
860
  if not os.path.exists(outdir):
868
861
  os.makedirs(outdir)
869
862
  filename = os.path.join(outdir, filename)
@@ -873,10 +866,10 @@ class Composite(Process):
873
866
  json.dump(document, json_file, indent=4)
874
867
  print(f"Created new file: {filename}")
875
868
 
869
+
876
870
  def reset_step_state(self, step_paths):
877
871
  self.trigger_state = build_trigger_state(
878
872
  self.node_dependencies)
879
-
880
873
  self.steps_remaining = set(step_paths)
881
874
 
882
875
 
@@ -885,7 +878,6 @@ class Composite(Process):
885
878
  self.step_dependencies,
886
879
  self.steps_remaining,
887
880
  self.trigger_state)
888
-
889
881
  return to_run
890
882
 
891
883
 
@@ -913,6 +905,11 @@ class Composite(Process):
913
905
 
914
906
 
915
907
  def read_emitter_config(self, emitter_config):
908
+
909
+ # upcoming deprecation warning
910
+ print("Warning: read_emitter_config() is deprecated and will be removed in a future version. "
911
+ "Use use Vivarium for managing simulations and emitters instead of Composite.")
912
+
916
913
  address = emitter_config.get('address', 'local:ram-emitter')
917
914
  config = emitter_config.get('config', {})
918
915
  mode = emitter_config.get('mode', 'none')
@@ -1221,25 +1218,6 @@ class Composite(Process):
1221
1218
  force_complete = False
1222
1219
 
1223
1220
 
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)
1234
-
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)
1239
-
1240
- # return to_run, trigger_state
1241
-
1242
-
1243
1221
  def run_steps(self, step_paths):
1244
1222
  if len(step_paths) > 0:
1245
1223
  updates = []
@@ -1316,6 +1294,7 @@ class Composite(Process):
1316
1294
 
1317
1295
  return results
1318
1296
 
1297
+
1319
1298
  def update(self, state, interval):
1320
1299
  # do everything
1321
1300
 
@@ -1337,13 +1316,11 @@ class Composite(Process):
1337
1316
  return updates
1338
1317
 
1339
1318
 
1340
-
1341
- """
1342
- Emitters
1343
- --------
1344
- Emitters are steps that observe the state of the system and emit it to an external source.
1345
- This could be to a database, to a file, or to the console.
1346
- """
1319
+ # ========
1320
+ # Emitters
1321
+ # ========
1322
+ # Emitters are steps that observe the state of the system and emit it to an external source.
1323
+ # This could be to a database, to a file, or to the console.
1347
1324
 
1348
1325
  class Emitter(Step):
1349
1326
  """Base emitter class.
@@ -1407,16 +1384,3 @@ class RAMEmitter(Emitter):
1407
1384
  BASE_EMITTERS = {
1408
1385
  'console-emitter': ConsoleEmitter,
1409
1386
  'ram-emitter': RAMEmitter}
1410
-
1411
-
1412
- # def StateEmitter(Emitter):
1413
-
1414
-
1415
- # def test_emitter():
1416
- # composite = Composite({})
1417
-
1418
- # composite.add_emitter(['emitters', 'ram'], 'ram-emitter')
1419
- # composite.emit_port(
1420
- # ['emitters', 'ram'],
1421
- # ['processes', 'translation'],
1422
- # ['outputs', 'protein'])
@@ -11,7 +11,7 @@ general stochastic transcription.
11
11
  import numpy as np
12
12
  import pytest
13
13
 
14
- from process_bigraph.composite import Step, Process, Composite
14
+ from process_bigraph.composite import Step, Process, Composite, ProcessEnsemble
15
15
 
16
16
 
17
17
  class GillespieInterval(Step):
@@ -71,7 +71,7 @@ class GillespieInterval(Step):
71
71
  output = {
72
72
  'interval': interval}
73
73
 
74
- print(f'produced interval: {output}')
74
+ # print(f'produced interval: {output}')
75
75
 
76
76
  return output
77
77
 
@@ -87,9 +87,7 @@ class GillespieEvent(Process):
87
87
  '_default': '1e-1'}}
88
88
 
89
89
 
90
- def __init__(self, config=None, core=None):
91
- super().__init__(config, core)
92
-
90
+ def initialize(self, config=None):
93
91
  self.stoichiometry = np.array([[0, 1], [0, -1]])
94
92
 
95
93
 
@@ -151,8 +149,59 @@ class GillespieEvent(Process):
151
149
  'mRNA': {
152
150
  'A mRNA': d_c}}
153
151
 
154
- print(f'received interval: {interval}')
152
+ # print(f'received interval: {interval}')
155
153
 
156
154
  return update
157
155
 
158
156
 
157
+ class GillespieSimulation(ProcessEnsemble):
158
+ def __init__(self, config=None, core=None):
159
+ super.__init__(config, core)
160
+
161
+
162
+ def inputs_interval(self):
163
+ return {
164
+ 'DNA': 'map[default 1]',
165
+ 'mRNA': {
166
+ 'A mRNA': 'default 1',
167
+ 'B mRNA': 'default 1'}}
168
+
169
+ # {
170
+ # '_type': 'map',
171
+ # '_value': 'float(default:1.0)'},
172
+
173
+ # 'G': {
174
+ # '_type': 'float',
175
+ # '_default': '1.0'}},
176
+
177
+
178
+ def outputs_interval(self):
179
+ return {
180
+ 'interval': 'interval'}
181
+
182
+
183
+ # def interface_interval(self):
184
+
185
+
186
+ def calculate_interval(self, inputs):
187
+ # retrieve the state values
188
+ g = input['DNA']['A gene']
189
+ c = input['mRNA']['A mRNA']
190
+
191
+ array_state = np.array([g, c])
192
+
193
+ # Calculate propensities
194
+ propensities = [
195
+ self.config['ktsc'] * array_state[0],
196
+ self.config['kdeg'] * array_state[1]]
197
+ prop_sum = sum(propensities)
198
+
199
+ # The wait time is distributed exponentially
200
+ interval = np.random.exponential(scale=prop_sum)
201
+
202
+ output = {
203
+ 'interval': interval}
204
+
205
+ print(f'produced interval: {output}')
206
+
207
+ return output
@@ -0,0 +1,21 @@
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.experiments.minimal_gillespie import GillespieInterval, GillespieEvent
4
+
5
+
6
+ TOY_PROCESSES = {
7
+ 'ToySystem': ToySystem,
8
+ 'ToyODE': ODE,
9
+ 'RunProcess': RunProcess,
10
+ 'ParameterScan': ParameterScan,
11
+ 'grow': Grow,
12
+ 'divide': Divide,
13
+ # 'GillespieInterval': GillespieInterval,
14
+ # 'GillespieEvent': GillespieEvent
15
+ }
16
+
17
+
18
+ def register_processes(core):
19
+ for name, process in TOY_PROCESSES.items():
20
+ core.register_process(name, process)
21
+ return core
@@ -1,26 +1,18 @@
1
- import pytest
2
- from process_bigraph.composite import Step, Process, Composite, ProcessTypes, interval_time_precision, deep_merge
1
+ from process_bigraph.composite import Step, Process, deep_merge
3
2
 
4
3
 
5
4
  class Grow(Process):
6
5
  config_schema = {
7
6
  'rate': 'float'}
8
7
 
9
-
10
- def __init__(self, config, core=None):
11
- super().__init__(config, core)
12
-
13
-
14
8
  def inputs(self):
15
9
  return {
16
10
  'mass': 'float'}
17
11
 
18
-
19
12
  def outputs(self):
20
13
  return {
21
14
  'mass': 'float'}
22
15
 
23
-
24
16
  def update(self, state, interval):
25
17
  # this calculates a delta
26
18
 
@@ -2,7 +2,7 @@ import copy
2
2
  import numpy as np
3
3
 
4
4
  from bigraph_schema import get_path, set_path, transform_path
5
- from process_bigraph.composite import Step, Process, Composite, ProcessTypes, interval_time_precision, deep_merge
5
+ from process_bigraph.composite import Step, Process, Composite, interval_time_precision, deep_merge
6
6
 
7
7
 
8
8
  class ToySystem(Process):
@@ -33,9 +33,7 @@ class ToySystem(Process):
33
33
  class ODE(Process):
34
34
  config_schema = 'ode_config'
35
35
 
36
- def __init__(self, config, core):
37
- super().__init__(config, core)
38
-
36
+ def initialize(self, config=None):
39
37
  self.reactions_count = len(self.config['rates'])
40
38
 
41
39
  self.species_count = len(
@@ -70,10 +68,9 @@ class RunProcess(Step):
70
68
  'timestep': 'float',
71
69
  'runtime': 'float'}
72
70
 
73
- def __init__(self, config, core):
74
- super().__init__(config, core)
71
+ def initialize(self, config):
75
72
 
76
- self.process = core.deserialize('process', {
73
+ self.process = self.core.deserialize('process', {
77
74
  '_type': 'process',
78
75
  'address': self.config['process_address'],
79
76
  'config': self.config['process_config'],
@@ -154,7 +151,7 @@ class RunProcess(Step):
154
151
  **self.inputs_config),
155
152
  'outputs': {}}}}
156
153
 
157
- self.composite = Composite(composite_config, core=core)
154
+ self.composite = Composite(composite_config, core=self.core)
158
155
 
159
156
  def inputs(self):
160
157
  return self.process.inputs()
@@ -232,8 +229,7 @@ class ParameterScan(Step):
232
229
  'timestep': 'float',
233
230
  'runtime': 'float'}
234
231
 
235
- def __init__(self, config, core):
236
- super().__init__(config, core)
232
+ def initialize(self, config=None):
237
233
 
238
234
  self.steps_count = int(
239
235
  self.config['runtime'] / self.config['timestep']) + 1
@@ -283,7 +279,7 @@ class ParameterScan(Step):
283
279
  self.scan = Composite({
284
280
  'bridge': bridge,
285
281
  'state': state},
286
- core=core)
282
+ core=self.core)
287
283
 
288
284
  results_schema = {}
289
285
  process = self.first_process()
@@ -339,4 +335,3 @@ class ParameterScan(Step):
339
335
  return {
340
336
  'results': update}
341
337
 
342
-
@@ -1,13 +1,21 @@
1
1
  """
2
2
  Tests for Process Bigraph
3
3
  """
4
-
5
- import random
6
4
  import pytest
5
+ import random
7
6
 
8
7
  from process_bigraph import register_types
9
- from process_bigraph.composite import Process, Step, Composite, merge_collections, ProcessTypes
10
- from process_bigraph.experiments.growth_division import grow_divide_agent
8
+ from process_bigraph.composite import (
9
+ Process, Step, Composite, merge_collections, ProcessTypes
10
+ )
11
+ from process_bigraph.processes.growth_division import grow_divide_agent
12
+ from process_bigraph.processes import TOY_PROCESSES
13
+
14
+
15
+ @pytest.fixture
16
+ def core():
17
+ core = ProcessTypes()
18
+ return register_types(core)
11
19
 
12
20
 
13
21
  class IncreaseProcess(Process):
@@ -329,9 +337,6 @@ class SimpleCompartment(Process):
329
337
  return update
330
338
 
331
339
 
332
- # TODO: create reaction registry, register this under "divide"
333
-
334
-
335
340
  def engulf_reaction(config):
336
341
  return {
337
342
  'redex': {},
@@ -533,15 +538,13 @@ def test_parameter_scan(core):
533
538
  'outputs': {
534
539
  'results': ['results']}}}
535
540
 
536
- # TODO: make a Workflow class that is a Step-composite
537
- # scan = Workflow({
538
541
  scan = Composite({
539
542
  'bridge': {
540
543
  'outputs': {
541
544
  'results': ['results']}},
542
545
  'state': state},
543
546
  core=core)
544
-
547
+
545
548
  # TODO: make a method so we can run it directly, provide some way to get the result out
546
549
  # result = scan.update({})
547
550
  result = scan.update({}, 0.0)
@@ -574,16 +577,12 @@ def test_grow_divide(core):
574
577
  'environment': ['environment']}}},
575
578
  core=core)
576
579
 
577
- import ipdb; ipdb.set_trace()
578
-
579
580
  updates = composite.update({
580
581
  'environment': {
581
582
  '0': {
582
583
  'mass': 1.1}}},
583
584
  100.0)
584
585
 
585
- import ipdb; ipdb.set_trace()
586
-
587
586
  # TODO: mass is not synchronized between inside and outside the composite?
588
587
 
589
588
  assert '0_0_0_0_1' in composite.state['environment']
@@ -592,45 +591,6 @@ def test_grow_divide(core):
592
591
 
593
592
  def test_gillespie_composite(core):
594
593
  composite_schema = {
595
- # This all gets inferred -------------
596
- # ==================================
597
- # 'composition': {
598
- # 'interval': {
599
- # '_type': 'step',
600
- # '_ports': {
601
- # 'inputs': {
602
- # 'DNA': {
603
- # 'G': 'float'},
604
- # 'mRNA': {
605
- # 'C': 'float'}},
606
- # 'outputs': {
607
- # 'interval': 'float'}}},
608
- # 'event': {
609
- # '_type': 'process',
610
- # '_ports': {
611
- # 'DNA': {
612
- # 'G': 'float'},
613
- # 'mRNA': {
614
- # 'C': 'float'}},
615
- # 'interval': 'float'}}},
616
- # 'emitter': {
617
- # '_type': 'step',
618
- # '_ports': {
619
- # 'inputs': {
620
- # 'DNA': {
621
- # 'G': 'float'},
622
- # 'mRNA': {
623
- # 'C': 'float'}}},
624
- # 'DNA': {
625
- # 'G': 'float'},
626
- # 'mRNA': {
627
- # 'C': 'float'}},
628
- # 'schema': {
629
- # 'DNA': {
630
- # 'G': 'float'},
631
- # 'mRNA': {
632
- # 'C': 'float'}},
633
-
634
594
  'bridge': {
635
595
  'inputs': {
636
596
  'DNA': ['DNA'],
@@ -674,31 +634,6 @@ def test_gillespie_composite(core):
674
634
  'mRNA': ['mRNA'],
675
635
  'interval': ['event', 'interval']}}}}
676
636
 
677
- # 'emit': 'any'},
678
- # 'inputs': ()}}}
679
-
680
-
681
- # TODO: provide a way to emit everything:
682
- # 'emitter': emit_all(
683
- # 'console-emitter',
684
- # exclusions={'DNA': {}}),
685
-
686
- # TODO: make us able to wire to the top with '**'
687
- # 'ram': {
688
- # '_type': 'step',
689
- # 'address': 'local:ram-emitter',
690
- # 'config': {
691
- # 'ports': {
692
- # 'inputs': 'tree[any]'}},
693
- # 'wires': {
694
- # 'inputs': '**'}}}}
695
-
696
- # 'DNA': {
697
- # 'G': 13.0},
698
-
699
- # 'mRNA': {
700
- # 'C': '21.0'}}}
701
-
702
637
  gillespie = Composite(
703
638
  composite_schema,
704
639
  core=core)
@@ -751,3 +686,4 @@ if __name__ == '__main__':
751
686
  test_parameter_scan(core)
752
687
 
753
688
  test_stochastic_deterministic_composite(core)
689
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: process-bigraph
3
- Version: 0.0.23
3
+ Version: 0.0.25
4
4
  Summary: UNKNOWN
5
5
  Home-page: https://github.com/vivarium-collective/process-bigraph
6
6
  Author: Ryan Spangler, Eran Agmon
@@ -13,7 +13,7 @@ process_bigraph.egg-info/dependency_links.txt
13
13
  process_bigraph.egg-info/requires.txt
14
14
  process_bigraph.egg-info/top_level.txt
15
15
  process_bigraph/experiments/__init__.py
16
- process_bigraph/experiments/growth_division.py
17
16
  process_bigraph/experiments/minimal_gillespie.py
18
17
  process_bigraph/processes/__init__.py
18
+ process_bigraph/processes/growth_division.py
19
19
  process_bigraph/processes/parameter_scan.py
@@ -1,6 +1,5 @@
1
1
  bigraph-schema
2
2
  numpy
3
- pytest>=6.2.5
4
- pymongo
3
+ pytest
5
4
  orjson
6
5
  matplotlib
@@ -2,7 +2,7 @@ import re
2
2
  from setuptools import setup, find_packages
3
3
 
4
4
 
5
- VERSION = '0.0.23'
5
+ VERSION = '0.0.25'
6
6
 
7
7
 
8
8
  with open("README.md", "r") as readme:
@@ -30,6 +30,7 @@ setup(
30
30
  packages=[
31
31
  'process_bigraph',
32
32
  'process_bigraph.processes',
33
+ 'process_bigraph.experiments'
33
34
  ],
34
35
  classifiers=[
35
36
  "Development Status :: 3 - Alpha",
@@ -49,8 +50,7 @@ setup(
49
50
  install_requires=[
50
51
  "bigraph-schema",
51
52
  "numpy",
52
- "pytest>=6.2.5",
53
- "pymongo",
53
+ "pytest",
54
54
  "orjson",
55
55
  "matplotlib"
56
56
  ]
@@ -1,18 +0,0 @@
1
- from process_bigraph.processes.parameter_scan import ToySystem, ODE, RunProcess, ParameterScan
2
- from process_bigraph.experiments.growth_division import Grow, Divide
3
- from process_bigraph.experiments.minimal_gillespie import GillespieInterval, GillespieEvent
4
-
5
-
6
- def register_processes(core):
7
- core.register_process('ToySystem', ToySystem)
8
- core.register_process('ToyODE', ODE)
9
- core.register_process('RunProcess', RunProcess)
10
- core.register_process('ParameterScan', ParameterScan)
11
-
12
- core.register_process('grow', Grow)
13
- core.register_process('divide', Divide)
14
-
15
- core.register_process('GillespieInterval', GillespieInterval)
16
- core.register_process('GillespieEvent', GillespieEvent)
17
-
18
- return core