process-bigraph 0.0.23__tar.gz → 0.0.24__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.24}/PKG-INFO +1 -1
  2. {process-bigraph-0.0.23 → process-bigraph-0.0.24}/process_bigraph/composite.py +282 -321
  3. {process-bigraph-0.0.23 → process-bigraph-0.0.24}/process_bigraph/experiments/minimal_gillespie.py +55 -6
  4. process-bigraph-0.0.24/process_bigraph/processes/__init__.py +21 -0
  5. {process-bigraph-0.0.23/process_bigraph/experiments → process-bigraph-0.0.24/process_bigraph/processes}/growth_division.py +1 -9
  6. {process-bigraph-0.0.23 → process-bigraph-0.0.24}/process_bigraph/processes/parameter_scan.py +7 -12
  7. {process-bigraph-0.0.23 → process-bigraph-0.0.24}/process_bigraph/tests.py +14 -78
  8. {process-bigraph-0.0.23 → process-bigraph-0.0.24/process_bigraph.egg-info}/PKG-INFO +1 -1
  9. {process-bigraph-0.0.23 → process-bigraph-0.0.24}/process_bigraph.egg-info/SOURCES.txt +1 -1
  10. {process-bigraph-0.0.23 → process-bigraph-0.0.24}/process_bigraph.egg-info/requires.txt +1 -2
  11. {process-bigraph-0.0.23 → process-bigraph-0.0.24}/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.24}/AUTHORS.md +0 -0
  14. {process-bigraph-0.0.23 → process-bigraph-0.0.24}/LICENSE +0 -0
  15. {process-bigraph-0.0.23 → process-bigraph-0.0.24}/README.md +0 -0
  16. {process-bigraph-0.0.23 → process-bigraph-0.0.24}/process_bigraph/__init__.py +0 -0
  17. {process-bigraph-0.0.23 → process-bigraph-0.0.24}/process_bigraph/experiments/__init__.py +0 -0
  18. {process-bigraph-0.0.23 → process-bigraph-0.0.24}/process_bigraph/process_types.py +0 -0
  19. {process-bigraph-0.0.23 → process-bigraph-0.0.24}/process_bigraph/protocols.py +0 -0
  20. {process-bigraph-0.0.23 → process-bigraph-0.0.24}/process_bigraph.egg-info/dependency_links.txt +0 -0
  21. {process-bigraph-0.0.23 → process-bigraph-0.0.24}/process_bigraph.egg-info/top_level.txt +0 -0
  22. {process-bigraph-0.0.23 → process-bigraph-0.0.24}/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.24
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)
433
638
 
639
+ return {
640
+ 'inputs': union_inputs,
641
+ 'outputs': union_outputs}
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', {})
@@ -818,11 +817,17 @@ class Composite(Process):
818
817
  self.step_dependencies, self.node_dependencies = build_step_network(
819
818
  self.step_paths)
820
819
 
821
- self.reset_step_state(self.step_paths)
820
+ self.reset_step_state(
821
+ self.step_paths)
822
+
822
823
  self.to_run = self.cycle_step_state()
823
824
 
824
825
  # self.run_steps(self.to_run)
825
826
 
827
+ def serialize_state(self):
828
+ return self.core.serialize(
829
+ self.composition,
830
+ self.state)
826
831
 
827
832
  def save(self,
828
833
  filename='composite.json',
@@ -830,9 +835,9 @@ class Composite(Process):
830
835
  schema=False,
831
836
  state=False):
832
837
 
833
- # TODO: add in dependent packages and version
834
- # maybe packagename.typename?
835
- # TODO: add in dependent types
838
+ # upcoming deprecation warning
839
+ print("Warning: save() is deprecated and will be removed in a future version. "
840
+ "Use use Vivarium for managing simulations instead of Composite.")
836
841
 
837
842
  document = {}
838
843
 
@@ -840,30 +845,15 @@ class Composite(Process):
840
845
  schema = state = True
841
846
 
842
847
  if state:
843
- serialized_state = self.core.serialize(
844
- self.composition,
845
- self.state)
846
-
848
+ serialized_state = self.serialize_state()
847
849
  document['state'] = serialized_state
848
850
 
849
851
  if schema:
850
- # serialized_schema = self.core.serialize(
851
- # 'schema',
852
- # self.composition)
853
-
854
852
  serialized_schema = self.core.representation(
855
853
  self.composition)
856
-
857
854
  document['composition'] = serialized_schema
858
855
 
859
- # TODO: make this true
860
- # copy_composite = Composite({
861
- # 'state': self.state})
862
-
863
- # assert copy_composite == self
864
-
865
856
  # save the dictionary to a JSON file
866
-
867
857
  if not os.path.exists(outdir):
868
858
  os.makedirs(outdir)
869
859
  filename = os.path.join(outdir, filename)
@@ -873,10 +863,10 @@ class Composite(Process):
873
863
  json.dump(document, json_file, indent=4)
874
864
  print(f"Created new file: {filename}")
875
865
 
866
+
876
867
  def reset_step_state(self, step_paths):
877
868
  self.trigger_state = build_trigger_state(
878
869
  self.node_dependencies)
879
-
880
870
  self.steps_remaining = set(step_paths)
881
871
 
882
872
 
@@ -885,7 +875,6 @@ class Composite(Process):
885
875
  self.step_dependencies,
886
876
  self.steps_remaining,
887
877
  self.trigger_state)
888
-
889
878
  return to_run
890
879
 
891
880
 
@@ -913,6 +902,11 @@ class Composite(Process):
913
902
 
914
903
 
915
904
  def read_emitter_config(self, emitter_config):
905
+
906
+ # upcoming deprecation warning
907
+ print("Warning: read_emitter_config() is deprecated and will be removed in a future version. "
908
+ "Use use Vivarium for managing simulations and emitters instead of Composite.")
909
+
916
910
  address = emitter_config.get('address', 'local:ram-emitter')
917
911
  config = emitter_config.get('config', {})
918
912
  mode = emitter_config.get('mode', 'none')
@@ -1221,25 +1215,6 @@ class Composite(Process):
1221
1215
  force_complete = False
1222
1216
 
1223
1217
 
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
1218
  def run_steps(self, step_paths):
1244
1219
  if len(step_paths) > 0:
1245
1220
  updates = []
@@ -1316,6 +1291,7 @@ class Composite(Process):
1316
1291
 
1317
1292
  return results
1318
1293
 
1294
+
1319
1295
  def update(self, state, interval):
1320
1296
  # do everything
1321
1297
 
@@ -1337,13 +1313,11 @@ class Composite(Process):
1337
1313
  return updates
1338
1314
 
1339
1315
 
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
- """
1316
+ # ========
1317
+ # Emitters
1318
+ # ========
1319
+ # Emitters are steps that observe the state of the system and emit it to an external source.
1320
+ # This could be to a database, to a file, or to the console.
1347
1321
 
1348
1322
  class Emitter(Step):
1349
1323
  """Base emitter class.
@@ -1407,16 +1381,3 @@ class RAMEmitter(Emitter):
1407
1381
  BASE_EMITTERS = {
1408
1382
  'console-emitter': ConsoleEmitter,
1409
1383
  '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.24
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.24'
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