process-bigraph 0.0.39__tar.gz → 0.0.41__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 (34) hide show
  1. {process-bigraph-0.0.39/process_bigraph.egg-info → process-bigraph-0.0.41}/PKG-INFO +1 -1
  2. {process-bigraph-0.0.39 → process-bigraph-0.0.41}/process_bigraph/__init__.py +6 -4
  3. {process-bigraph-0.0.39 → process-bigraph-0.0.41}/process_bigraph/composite.py +368 -1
  4. {process-bigraph-0.0.39 → process-bigraph-0.0.41}/process_bigraph/process_types.py +26 -0
  5. {process-bigraph-0.0.39 → process-bigraph-0.0.41}/process_bigraph/processes/__init__.py +5 -0
  6. {process-bigraph-0.0.39 → process-bigraph-0.0.41}/process_bigraph/tests.py +51 -4
  7. {process-bigraph-0.0.39 → process-bigraph-0.0.41/process_bigraph.egg-info}/PKG-INFO +1 -1
  8. {process-bigraph-0.0.39 → process-bigraph-0.0.41}/pyproject.toml +1 -1
  9. {process-bigraph-0.0.39 → process-bigraph-0.0.41}/setup.py +1 -1
  10. {process-bigraph-0.0.39 → process-bigraph-0.0.41}/.github/workflows/notebook_to_html.yml +0 -0
  11. {process-bigraph-0.0.39 → process-bigraph-0.0.41}/.github/workflows/pytest.yml +0 -0
  12. {process-bigraph-0.0.39 → process-bigraph-0.0.41}/.gitignore +0 -0
  13. {process-bigraph-0.0.39 → process-bigraph-0.0.41}/AUTHORS.md +0 -0
  14. {process-bigraph-0.0.39 → process-bigraph-0.0.41}/CLA.md +0 -0
  15. {process-bigraph-0.0.39 → process-bigraph-0.0.41}/CODE_OF_CONDUCT.md +0 -0
  16. {process-bigraph-0.0.39 → process-bigraph-0.0.41}/CONTRIBUTING.md +0 -0
  17. {process-bigraph-0.0.39 → process-bigraph-0.0.41}/LICENSE +0 -0
  18. {process-bigraph-0.0.39 → process-bigraph-0.0.41}/README.md +0 -0
  19. {process-bigraph-0.0.39 → process-bigraph-0.0.41}/doc/_static/process-bigraph.png +0 -0
  20. {process-bigraph-0.0.39 → process-bigraph-0.0.41}/notebooks/process-bigraphs.ipynb +0 -0
  21. {process-bigraph-0.0.39 → process-bigraph-0.0.41}/notebooks/visualize_processes.ipynb +0 -0
  22. {process-bigraph-0.0.39 → process-bigraph-0.0.41}/process_bigraph/emitter.py +0 -0
  23. {process-bigraph-0.0.39 → process-bigraph-0.0.41}/process_bigraph/experiments/__init__.py +0 -0
  24. {process-bigraph-0.0.39 → process-bigraph-0.0.41}/process_bigraph/experiments/minimal_gillespie.py +0 -0
  25. {process-bigraph-0.0.39 → process-bigraph-0.0.41}/process_bigraph/processes/growth_division.py +0 -0
  26. {process-bigraph-0.0.39 → process-bigraph-0.0.41}/process_bigraph/processes/parameter_scan.py +0 -0
  27. {process-bigraph-0.0.39 → process-bigraph-0.0.41}/process_bigraph/protocols.py +0 -0
  28. {process-bigraph-0.0.39 → process-bigraph-0.0.41}/process_bigraph.egg-info/SOURCES.txt +0 -0
  29. {process-bigraph-0.0.39 → process-bigraph-0.0.41}/process_bigraph.egg-info/dependency_links.txt +0 -0
  30. {process-bigraph-0.0.39 → process-bigraph-0.0.41}/process_bigraph.egg-info/requires.txt +0 -0
  31. {process-bigraph-0.0.39 → process-bigraph-0.0.41}/process_bigraph.egg-info/top_level.txt +0 -0
  32. {process-bigraph-0.0.39 → process-bigraph-0.0.41}/pytest.ini +0 -0
  33. {process-bigraph-0.0.39 → process-bigraph-0.0.41}/release.sh +0 -0
  34. {process-bigraph-0.0.39 → process-bigraph-0.0.41}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: process-bigraph
3
- Version: 0.0.39
3
+ Version: 0.0.41
4
4
  Home-page: https://github.com/vivarium-collective/process-bigraph
5
5
  Author: Ryan Spangler, Eran Agmon
6
6
  Author-email: ryan.spangler@gmail.com, agmon.eran@gmail.com
@@ -1,9 +1,8 @@
1
1
  import pprint
2
2
  from bigraph_schema.registry import deep_merge, default
3
3
  from process_bigraph.processes import register_processes
4
- from process_bigraph.composite import Process, Step, Composite, interval_time_precision
5
- from process_bigraph.process_types import ProcessTypes
6
- from process_bigraph.emitter import Emitter, gather_emitter_results, generate_emitter_state
4
+ from process_bigraph.composite import Process, Step, Composite, interval_time_precision, ProcessTypes
5
+ from process_bigraph.emitter import Emitter, gather_emitter_results, generate_emitter_state, BASE_EMITTERS
7
6
 
8
7
 
9
8
  pretty = pprint.PrettyPrinter(indent=2)
@@ -42,9 +41,12 @@ def register_types(core):
42
41
  'rates': 'map[float]',
43
42
  'species': 'map[float]'})
44
43
 
45
- core = register_processes(
44
+ register_processes(
46
45
  core)
47
46
 
48
47
  return core
49
48
 
50
49
 
50
+ def allocate_core():
51
+ core = ProcessTypes()
52
+ return register_types(core)
@@ -11,7 +11,7 @@ import collections
11
11
  from typing import Dict
12
12
 
13
13
  from bigraph_schema import (
14
- Edge, Registry,
14
+ Edge, Registry, TypeSystem, visit_method,
15
15
  get_path, set_path, resolve_path, hierarchy_depth, deep_merge,
16
16
  is_schema_key, strip_schema_keys)
17
17
 
@@ -22,6 +22,7 @@ from process_bigraph.protocols import local_lookup, local_lookup_module
22
22
  # Process Utility Functions
23
23
  # =========================
24
24
 
25
+
25
26
  def assert_interface(interface: Dict):
26
27
  """Ensure that an interface dict has the required keys"""
27
28
  required_keys = ['inputs', 'outputs']
@@ -951,3 +952,369 @@ class Composite(Process):
951
952
  self.bridge_updates = []
952
953
 
953
954
  return updates
955
+
956
+
957
+ # ======================
958
+ # Process Type Functions
959
+ # ======================
960
+
961
+
962
+ def apply_process(schema, current, update, top_schema, top_state, path, core):
963
+ """Apply an update to a process."""
964
+ process_schema = schema.copy()
965
+ process_schema.pop('_apply')
966
+ return core.apply_update(
967
+ process_schema,
968
+ current,
969
+ update,
970
+ top_schema=top_schema,
971
+ top_state=top_state,
972
+ path=path)
973
+
974
+
975
+ def check_process(schema, state, core):
976
+ """Check if this is a process."""
977
+ return 'instance' in state and isinstance(
978
+ state['instance'],
979
+ Edge)
980
+
981
+
982
+ def fold_visit(schema, state, method, values, core):
983
+ visit = visit_method(
984
+ schema,
985
+ state,
986
+ method,
987
+ values,
988
+ core)
989
+
990
+ return visit
991
+
992
+
993
+ def divide_process(schema, state, values, core):
994
+ # daughter_configs must have a config per daughter
995
+
996
+ daughter_configs = values.get(
997
+ 'daughter_configs',
998
+ [{} for index in range(values['divisions'])])
999
+
1000
+ if 'config' not in state:
1001
+ return daughter_configs
1002
+
1003
+ existing_config = state['config']
1004
+
1005
+ divisions = []
1006
+ for index in range(values['divisions']):
1007
+ daughter_config = copy.deepcopy(
1008
+ existing_config)
1009
+ daughter_config = deep_merge(
1010
+ daughter_config,
1011
+ daughter_configs[index])
1012
+
1013
+ # TODO: provide a way to override inputs and outputs
1014
+ daughter_state = {
1015
+ 'address': state['address'],
1016
+ 'config': daughter_config,
1017
+ 'inputs': copy.deepcopy(state['inputs']),
1018
+ 'outputs': copy.deepcopy(state['outputs'])}
1019
+
1020
+ if 'interval' in state:
1021
+ daughter_state['interval'] = state['interval']
1022
+
1023
+ divisions.append(daughter_state)
1024
+
1025
+ return divisions
1026
+
1027
+
1028
+ def serialize_process(schema, value, core):
1029
+ """Serialize a process to a JSON-safe representation."""
1030
+ # TODO -- need to get back the protocol: address and the config
1031
+ process = value.copy()
1032
+ process['config'] = core.serialize(
1033
+ process['instance'].config_schema,
1034
+ process['config'])
1035
+ del process['instance']
1036
+ return process
1037
+
1038
+
1039
+ def deserialize_process(schema, encoded, core):
1040
+ """Deserialize a process from a serialized state.
1041
+
1042
+ This function is used by the type system to deserialize a process.
1043
+
1044
+ :param encoded: A JSON-safe representation of the process.
1045
+ :param bindings: The bindings to use for deserialization.
1046
+ :param core: The type system to use for deserialization.
1047
+
1048
+ :returns: The deserialized state with an instantiated process.
1049
+ """
1050
+ encoded = encoded or {}
1051
+ schema = schema or {}
1052
+
1053
+ default = core.default(schema)
1054
+ deserialized = deep_merge(default, encoded)
1055
+
1056
+ if not deserialized.get('address'):
1057
+ return deserialized
1058
+
1059
+ protocol, address = deserialized['address'].split(':', 1)
1060
+
1061
+ existing_instance = 'instance' in deserialized and deserialized['instance']
1062
+ if existing_instance:
1063
+ instantiate = type(deserialized['instance'])
1064
+ else:
1065
+ process_lookup = core.protocol_registry.access(protocol)
1066
+ if not process_lookup:
1067
+ raise Exception(f'protocol "{protocol}" not implemented')
1068
+
1069
+ instantiate = process_lookup(core, address)
1070
+ if not instantiate:
1071
+ raise Exception(f'process "{address}" not found')
1072
+
1073
+ config = core.deserialize(
1074
+ instantiate.config_schema,
1075
+ deserialized.get('config', {}))
1076
+
1077
+ interval = core.deserialize(
1078
+ 'interval',
1079
+ deserialized.get('interval'))
1080
+
1081
+ if interval is None:
1082
+ interval = core.default(
1083
+ schema.get(
1084
+ 'interval',
1085
+ 'interval'))
1086
+
1087
+ if existing_instance:
1088
+ process = deserialized['instance']
1089
+ else:
1090
+ process = instantiate(
1091
+ config,
1092
+ core=core)
1093
+
1094
+ deserialized['instance'] = process
1095
+
1096
+ # TODO: this mutating the original value directly into
1097
+ # the return value is weird (?)
1098
+ shared = deserialized.get('shared', {})
1099
+ deserialized['shared'] = {}
1100
+ if shared:
1101
+ for step_id, step_config in shared.items():
1102
+ step = deserialize_step(
1103
+ 'step',
1104
+ step_config,
1105
+ core)
1106
+
1107
+ step['instance'].register_shared(
1108
+ process)
1109
+
1110
+ deserialized['shared'][step_id] = step
1111
+
1112
+ deserialized['config'] = config
1113
+ deserialized['interval'] = interval
1114
+ deserialized['_inputs'] = copy.deepcopy(
1115
+ deserialized['instance'].inputs())
1116
+ deserialized['_outputs'] = copy.deepcopy(
1117
+ deserialized['instance'].outputs())
1118
+
1119
+ return deserialized
1120
+
1121
+
1122
+ def deserialize_step(schema, encoded, core):
1123
+ default = core.default(schema)
1124
+ deserialized = deep_merge(default, encoded)
1125
+
1126
+ if not deserialized['address']:
1127
+ return deserialized
1128
+
1129
+ protocol, address = deserialized['address'].split(':', 1)
1130
+
1131
+ existing_instance = 'instance' in deserialized and deserialized['instance']
1132
+ if existing_instance:
1133
+ instantiate = type(deserialized['instance'])
1134
+ else:
1135
+ process_lookup = core.protocol_registry.access(protocol)
1136
+ if not process_lookup:
1137
+ raise Exception(f'protocol "{protocol}" not implemented')
1138
+
1139
+ instantiate = process_lookup(core, address)
1140
+ if not instantiate:
1141
+ raise Exception(f'process "{address}" not found')
1142
+
1143
+ config = core.deserialize(
1144
+ instantiate.config_schema,
1145
+ deserialized.get('config', {}))
1146
+
1147
+ if not existing_instance:
1148
+ process = instantiate(config, core=core)
1149
+ deserialized['instance'] = process
1150
+
1151
+ deserialized['config'] = config
1152
+ deserialized['_inputs'] = copy.deepcopy(
1153
+ deserialized['instance'].inputs())
1154
+ deserialized['_outputs'] = copy.deepcopy(
1155
+ deserialized['instance'].outputs())
1156
+
1157
+ return deserialized
1158
+
1159
+
1160
+ # ===================
1161
+ # Process Type System
1162
+ # ===================
1163
+
1164
+ class ProcessTypes(TypeSystem):
1165
+ """
1166
+ ProcessTypes class extends the TypeSystem class to include process types.
1167
+ It maintains a registry of process types and provides methods to register
1168
+ new process types, protocols, and emitters.
1169
+ """
1170
+
1171
+ def __init__(self):
1172
+ super().__init__()
1173
+ self.process_registry = Registry()
1174
+ self.protocol_registry = Registry()
1175
+
1176
+ self.update_types(PROCESS_TYPES)
1177
+ self.register_protocols(BASE_PROTOCOLS)
1178
+
1179
+ self.register_process('composite', Composite)
1180
+
1181
+
1182
+ def register_protocols(self, protocols):
1183
+ """Register protocols with the core"""
1184
+ self.protocol_registry.register_multiple(protocols)
1185
+
1186
+
1187
+ def register_process(
1188
+ self,
1189
+ name,
1190
+ process_data
1191
+ ):
1192
+ """
1193
+ Registers a new process type in the process registry.
1194
+
1195
+ Args:
1196
+ name (str): The name of the process type.
1197
+ process_data: The data associated with the process type.
1198
+ """
1199
+ self.process_registry.register(name, process_data)
1200
+
1201
+
1202
+ def register_processes(self, processes):
1203
+ for process_key, process_data in processes.items():
1204
+ self.register_process(
1205
+ process_key,
1206
+ process_data)
1207
+
1208
+
1209
+ def initialize_edge_state(self, schema, path, edge):
1210
+ """
1211
+ Initialize the state for an edge based on the schema and the edge.
1212
+ """
1213
+ initial_state = edge['instance'].initial_state()
1214
+ if not initial_state:
1215
+ return initial_state
1216
+
1217
+ input_ports = copy.deepcopy(get_path(schema, path + ('_inputs',)))
1218
+ output_ports = copy.deepcopy(get_path(schema, path + ('_outputs',)))
1219
+ ports = {
1220
+ '_inputs': input_ports,
1221
+ '_outputs': output_ports}
1222
+
1223
+ input_state = {}
1224
+ if input_ports:
1225
+ input_state = self.project_edge(
1226
+ ports,
1227
+ edge,
1228
+ path[:-1],
1229
+ initial_state,
1230
+ ports_key='inputs')
1231
+
1232
+ output_state = {}
1233
+ if output_ports:
1234
+ output_state = self.project_edge(
1235
+ ports,
1236
+ edge,
1237
+ path[:-1],
1238
+ initial_state,
1239
+ ports_key='outputs')
1240
+
1241
+ state = deep_merge(input_state, output_state)
1242
+
1243
+ return state
1244
+
1245
+
1246
+ def default_state(self, process_class, initial_state=None):
1247
+ default_config = self.default(
1248
+ process_class.config_schema)
1249
+
1250
+ instance = process_class(
1251
+ default_config,
1252
+ core=self)
1253
+
1254
+ state = {
1255
+ '_type': 'process',
1256
+ 'address': f'local:!{process_class.__module__}.{process_class.__name__}',
1257
+ 'config': default_config,
1258
+ 'inputs': instance.default_inputs(),
1259
+ 'outputs': instance.default_outputs()}
1260
+
1261
+ if issubclass(process_class, Process):
1262
+ state['interval'] = 1.0
1263
+
1264
+ if initial_state:
1265
+ state = deep_merge(
1266
+ state,
1267
+ initial_state)
1268
+
1269
+ return state
1270
+
1271
+
1272
+ PROCESS_TYPES = {
1273
+ 'protocol': {
1274
+ '_type': 'protocol',
1275
+ '_inherit': 'string'},
1276
+
1277
+ 'emitter_mode': 'enum[none,all,stores,bridge,paths,ports]',
1278
+
1279
+ 'interval': {
1280
+ '_type': 'interval',
1281
+ '_inherit': 'float',
1282
+ '_apply': 'set',
1283
+ '_default': '1.0'},
1284
+
1285
+ 'step': {
1286
+ '_type': 'step',
1287
+ '_inherit': 'edge',
1288
+ '_apply': apply_process,
1289
+ '_serialize': serialize_process,
1290
+ '_deserialize': deserialize_step,
1291
+ '_check': check_process,
1292
+ '_fold': fold_visit,
1293
+ '_divide': divide_process,
1294
+ '_description': '',
1295
+ # TODO: support reference to type parameters from other states
1296
+ 'address': 'protocol',
1297
+ 'config': 'quote'},
1298
+
1299
+ # TODO: slice process to allow for navigating through a port
1300
+ 'process': {
1301
+ '_type': 'process',
1302
+ '_inherit': 'edge',
1303
+ '_apply': apply_process,
1304
+ '_serialize': serialize_process,
1305
+ '_deserialize': deserialize_process,
1306
+ '_check': check_process,
1307
+ '_fold': fold_visit,
1308
+ '_divide': divide_process,
1309
+ '_description': '',
1310
+ # TODO: support reference to type parameters from other states
1311
+ 'interval': 'interval',
1312
+ 'address': 'protocol',
1313
+ 'config': 'quote',
1314
+ 'shared': 'map[step]'},
1315
+ }
1316
+
1317
+
1318
+ BASE_PROTOCOLS = {
1319
+ 'local': local_lookup}
1320
+
@@ -306,6 +306,32 @@ class ProcessTypes(TypeSystem):
306
306
  return state
307
307
 
308
308
 
309
+ def default_state(self, process_class, initial_state=None):
310
+ default_config = self.default(
311
+ process_class.config_schema)
312
+
313
+ instance = process_class(
314
+ default_config,
315
+ core=self)
316
+
317
+ state = {
318
+ '_type': 'process',
319
+ 'address': f'local:!{process_class.__module__}.{process_class.__name__}',
320
+ 'config': default_config,
321
+ 'inputs': instance.default_inputs(),
322
+ 'outputs': instance.default_outputs()}
323
+
324
+ if issubclass(process_class, Process):
325
+ state['interval'] = 1.0
326
+
327
+ if initial_state:
328
+ state = deep_merge(
329
+ state,
330
+ initial_state)
331
+
332
+ return state
333
+
334
+
309
335
  PROCESS_TYPES = {
310
336
  'protocol': {
311
337
  '_type': 'protocol',
@@ -1,5 +1,6 @@
1
1
  from process_bigraph.processes.parameter_scan import ToySystem, ODE, RunProcess, ParameterScan
2
2
  from process_bigraph.processes.growth_division import Grow, Divide
3
+ from process_bigraph.emitter import BASE_EMITTERS
3
4
  # from process_bigraph.experiments.minimal_gillespie import GillespieInterval, GillespieEvent
4
5
 
5
6
 
@@ -18,4 +19,8 @@ TOY_PROCESSES = {
18
19
  def register_processes(core):
19
20
  for name, process in TOY_PROCESSES.items():
20
21
  core.register_process(name, process)
22
+
23
+ core = core.register_processes(
24
+ BASE_EMITTERS)
25
+
21
26
  return core
@@ -7,10 +7,9 @@ import random
7
7
  from bigraph_schema import default
8
8
  from process_bigraph import register_types
9
9
 
10
- from process_bigraph.composite import Process, Step, Composite, merge_collections, match_star_path
10
+ from process_bigraph.composite import Process, Step, Composite, merge_collections, match_star_path, ProcessTypes
11
11
 
12
- from process_bigraph.processes.growth_division import grow_divide_agent
13
- from process_bigraph.process_types import ProcessTypes
12
+ from process_bigraph.processes.growth_division import grow_divide_agent, Grow, Divide
14
13
  from process_bigraph.emitter import emitter_from_wires, gather_emitter_results
15
14
 
16
15
 
@@ -871,20 +870,68 @@ def test_star_update(core):
871
870
  assert star.state['Compartments']['2']['Shared Environment']['counts']['biomass'] == 2899
872
871
 
873
872
 
873
+ class GlobalProcess(Process):
874
+ config_schema = {}
875
+
876
+
877
+ def initialize(self, config):
878
+ pass
879
+
880
+
881
+
882
+
883
+
884
+ def test_default_process_state(core):
885
+ # provide some initial values
886
+ default_rate = {
887
+ 'config': {
888
+ 'rate': 0.001}}
889
+
890
+ # generate a default state for the Grow process
891
+ default_grow = core.default_state(
892
+ Grow,
893
+ default_rate)
894
+
895
+ # create a composite from the default process state
896
+ composite = Composite({
897
+ 'state': {
898
+ 'grow': default_grow,
899
+ 'mass': 1.0}},
900
+ core=core)
901
+
902
+ # run the composite
903
+ composite.run(10.0)
904
+
905
+ # assert the process ran and the mass increased
906
+ assert composite.state['mass'] > 1.0
907
+
908
+ default_divide = core.default_state(
909
+ Divide)
910
+
911
+ assert 'interval' not in default_divide
912
+
913
+
914
+ def test_update_removal(core):
915
+ return {}
916
+
917
+
874
918
  def test_stochastic_deterministic_composite(core):
875
919
  # TODO make the demo for a hybrid stochastic/deterministic simulator
876
920
  pass
877
921
 
922
+
878
923
  def test_match_star_path(core):
879
924
  assert match_star_path(["first", "list", "test"], ["first", "*", "test"])
880
925
  assert not match_star_path(["first", "list", "tent"], ["first", "*", "test"])
881
926
  assert match_star_path(["first", "list", "test"], ["first", "list", "test"])
882
927
 
928
+
883
929
  if __name__ == '__main__':
884
930
  core = ProcessTypes()
885
931
  core = register_types(core)
886
932
 
887
933
  test_default_config(core)
934
+ test_default_process_state(core)
888
935
  test_merge_collections(core)
889
936
  test_process(core)
890
937
  test_composite(core)
@@ -904,4 +951,4 @@ if __name__ == '__main__':
904
951
  test_merge_schema(core)
905
952
  test_grow_divide(core)
906
953
  test_star_update(core)
907
- test_match_star_path(core)
954
+ test_match_star_path(core)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: process-bigraph
3
- Version: 0.0.39
3
+ Version: 0.0.41
4
4
  Home-page: https://github.com/vivarium-collective/process-bigraph
5
5
  Author: Ryan Spangler, Eran Agmon
6
6
  Author-email: ryan.spangler@gmail.com, agmon.eran@gmail.com
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "process-bigraph"
3
- version = "0.0.39"
3
+ version = "0.0.41"
4
4
  description = ""
5
5
  readme = "README.md"
6
6
  requires-python = "==3.12.9"
@@ -2,7 +2,7 @@ import re
2
2
  from setuptools import setup, find_packages
3
3
 
4
4
 
5
- VERSION = '0.0.39'
5
+ VERSION = '0.0.41'
6
6
 
7
7
 
8
8
  with open("README.md", "r") as readme: