bigraph-schema 0.0.32__tar.gz → 0.0.37__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.

Potentially problematic release.


This version of bigraph-schema might be problematic. Click here for more details.

Files changed (19) hide show
  1. {bigraph-schema-0.0.32/bigraph_schema.egg-info → bigraph-schema-0.0.37}/PKG-INFO +5 -1
  2. bigraph-schema-0.0.37/bigraph_schema/data.py +1 -0
  3. {bigraph-schema-0.0.32 → bigraph-schema-0.0.37}/bigraph_schema/registry.py +119 -4
  4. {bigraph-schema-0.0.32 → bigraph-schema-0.0.37}/bigraph_schema/type_system.py +476 -174
  5. {bigraph-schema-0.0.32 → bigraph-schema-0.0.37/bigraph_schema.egg-info}/PKG-INFO +5 -1
  6. {bigraph-schema-0.0.32 → bigraph-schema-0.0.37}/bigraph_schema.egg-info/SOURCES.txt +1 -0
  7. {bigraph-schema-0.0.32 → bigraph-schema-0.0.37}/setup.py +1 -1
  8. {bigraph-schema-0.0.32 → bigraph-schema-0.0.37}/AUTHORS.md +0 -0
  9. {bigraph-schema-0.0.32 → bigraph-schema-0.0.37}/LICENSE +0 -0
  10. {bigraph-schema-0.0.32 → bigraph-schema-0.0.37}/README.md +0 -0
  11. {bigraph-schema-0.0.32 → bigraph-schema-0.0.37}/bigraph_schema/__init__.py +0 -0
  12. {bigraph-schema-0.0.32 → bigraph-schema-0.0.37}/bigraph_schema/parse.py +0 -0
  13. {bigraph-schema-0.0.32 → bigraph-schema-0.0.37}/bigraph_schema/protocols.py +0 -0
  14. {bigraph-schema-0.0.32 → bigraph-schema-0.0.37}/bigraph_schema/react.py +0 -0
  15. {bigraph-schema-0.0.32 → bigraph-schema-0.0.37}/bigraph_schema/units.py +0 -0
  16. {bigraph-schema-0.0.32 → bigraph-schema-0.0.37}/bigraph_schema.egg-info/dependency_links.txt +0 -0
  17. {bigraph-schema-0.0.32 → bigraph-schema-0.0.37}/bigraph_schema.egg-info/requires.txt +0 -0
  18. {bigraph-schema-0.0.32 → bigraph-schema-0.0.37}/bigraph_schema.egg-info/top_level.txt +0 -0
  19. {bigraph-schema-0.0.32 → bigraph-schema-0.0.37}/setup.cfg +0 -0
@@ -1,10 +1,12 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: bigraph-schema
3
- Version: 0.0.32
3
+ Version: 0.0.37
4
4
  Summary: A serializable type schema for compositional systems biology
5
5
  Home-page: https://github.com/vivarium-collective/bigraph-schema
6
6
  Author: Eran Agmon, Ryan Spangler
7
7
  Author-email: agmon.eran@gmail.com, ryan.spangler@gmail.com
8
+ License: UNKNOWN
9
+ Platform: UNKNOWN
8
10
  Classifier: Development Status :: 3 - Alpha
9
11
  Classifier: Intended Audience :: Developers
10
12
  Classifier: License :: OSI Approved :: MIT License
@@ -52,3 +54,5 @@ This resource will guide you through the core concepts and methods, helping you
52
54
  ## License
53
55
 
54
56
  Bigraph-schema is open-source software released under the [Apache 2 License](https://github.com/vivarium-collective/bigraph-schema/blob/main/LICENSE).
57
+
58
+
@@ -13,9 +13,12 @@ import traceback
13
13
  import numpy as np
14
14
 
15
15
  from pprint import pformat as pf
16
+ from typing import Any, Tuple, Union, Optional, Mapping
17
+ from dataclasses import field, make_dataclass
16
18
 
17
19
  from bigraph_schema.parse import parse_expression
18
20
  from bigraph_schema.protocols import local_lookup_module, function_module
21
+ import bigraph_schema.data
19
22
 
20
23
 
21
24
  NONE_SYMBOL = '!nil'
@@ -525,6 +528,34 @@ def fold_union(schema, state, method, values, core):
525
528
  return result
526
529
 
527
530
 
531
+ def type_parameters_for(schema):
532
+ parameters = []
533
+ for key in schema['_type_parameters']:
534
+ subschema = schema.get(f'_{key}', 'any')
535
+ parameters.append(subschema)
536
+
537
+ return parameters
538
+
539
+
540
+ def dataclass_union(schema, path, core):
541
+ parameters = type_parameters_for(schema)
542
+ subtypes = []
543
+ for parameter in parameters:
544
+ dataclass = core.dataclass(
545
+ parameter,
546
+ path)
547
+
548
+ if isinstance(dataclass, str):
549
+ subtypes.append(dataclass)
550
+ elif isinstance(dataclass, type):
551
+ subtypes.append(dataclass.__name__)
552
+ else:
553
+ subtypes.append(str(dataclass))
554
+
555
+ parameter_block = ', '.join(subtypes)
556
+ return eval(f'Union[{parameter_block}]')
557
+
558
+
528
559
  def divide_any(schema, state, values, core):
529
560
  divisions = values.get('divisions', 2)
530
561
 
@@ -551,6 +582,83 @@ def divide_any(schema, state, values, core):
551
582
  for _ in range(divisions)]
552
583
 
553
584
 
585
+ def is_schema_key(schema, key):
586
+ return key.strip('_') not in schema.get('_type_parameters', []) and key.startswith('_')
587
+
588
+
589
+ def resolve_any(schema, update, core):
590
+ outcome = schema.copy()
591
+
592
+ for key, subschema in update.items():
593
+ if not key in outcome or is_schema_key(update, key):
594
+ if subschema:
595
+ outcome[key] = subschema
596
+ else:
597
+ outcome[key] = core.resolve_schemas(
598
+ outcome.get(key),
599
+ update[key])
600
+
601
+ return outcome
602
+
603
+
604
+ def dataclass_any(schema, path, core):
605
+ parts = path
606
+ if not parts:
607
+ parts = ['top']
608
+ dataclass_name = '_'.join(parts)
609
+
610
+ if isinstance(schema, dict):
611
+ type_name = schema.get('_type', 'any')
612
+
613
+ branches = {}
614
+ for key, subschema in schema.items():
615
+ if not key.startswith('_'):
616
+ branch = core.dataclass(
617
+ subschema,
618
+ path + [key])
619
+
620
+ def default(subschema=subschema):
621
+ return core.default(subschema)
622
+
623
+ branches[key] = (
624
+ key,
625
+ branch,
626
+ field(default_factory=default))
627
+
628
+ dataclass = make_dataclass(
629
+ dataclass_name,
630
+ branches.values(),
631
+ namespace={
632
+ '__module__': 'bigraph_schema.data'})
633
+
634
+ setattr(
635
+ bigraph_schema.data,
636
+ dataclass_name,
637
+ dataclass)
638
+
639
+ else:
640
+ schema = core.access(schema)
641
+ dataclass = core.dataclass(schema, path)
642
+
643
+ return dataclass
644
+
645
+
646
+ def dataclass_tuple(schema, path, core):
647
+ parameters = type_parameters_for(schema)
648
+ subtypes = []
649
+
650
+ for index, key in enumerate(schema['type_parameters']):
651
+ subschema = schema.get(key, 'any')
652
+ subtype = core.dataclass(
653
+ subschema,
654
+ path + [index])
655
+
656
+ subtypes.append(subtype)
657
+
658
+ parameter_block = ', '.join(subtypes)
659
+ return eval(f'tuple[{parameter_block}]')
660
+
661
+
554
662
  def divide_tuple(schema, state, values, core):
555
663
  divisions = values.get('divisions', 2)
556
664
 
@@ -740,10 +848,13 @@ def merge_any(schema, current_state, new_state, core):
740
848
  elif isinstance(new_state, dict):
741
849
  if isinstance(current_state, dict):
742
850
  for key, value in new_state.items():
743
- current_state[key] = core.merge(
744
- schema.get(key),
745
- current_state.get(key),
746
- value)
851
+ if key.startswith('_'):
852
+ current_state[key] = value
853
+ else:
854
+ current_state[key] = core.merge(
855
+ schema.get(key),
856
+ current_state.get(key),
857
+ value)
747
858
  return current_state
748
859
  else:
749
860
  return new_state
@@ -954,6 +1065,8 @@ registry_types = {
954
1065
  '_check': check_any,
955
1066
  '_serialize': serialize_any,
956
1067
  '_deserialize': deserialize_any,
1068
+ '_dataclass': dataclass_any,
1069
+ '_resolve': resolve_any,
957
1070
  '_fold': fold_any,
958
1071
  '_merge': merge_any,
959
1072
  '_bind': bind_any,
@@ -967,6 +1080,7 @@ registry_types = {
967
1080
  '_slice': slice_tuple,
968
1081
  '_serialize': serialize_tuple,
969
1082
  '_deserialize': deserialize_tuple,
1083
+ '_dataclass': dataclass_tuple,
970
1084
  '_fold': fold_tuple,
971
1085
  '_divide': divide_tuple,
972
1086
  '_bind': bind_tuple,
@@ -980,6 +1094,7 @@ registry_types = {
980
1094
  '_slice': slice_union,
981
1095
  '_serialize': serialize_union,
982
1096
  '_deserialize': deserialize_union,
1097
+ '_dataclass': dataclass_union,
983
1098
  '_fold': fold_union,
984
1099
  '_description': 'union of a set of possible types'}}
985
1100
 
@@ -9,35 +9,35 @@ import pint
9
9
  import pprint
10
10
  import pytest
11
11
  import random
12
+ import typing
12
13
  import inspect
13
14
  import numbers
14
15
  import numpy as np
15
16
 
16
17
  from pint import Quantity
17
18
  from pprint import pformat as pf
19
+ from typing import Any, Tuple, Union, Optional, Mapping, Callable, NewType, get_origin, get_args
20
+ from dataclasses import asdict
18
21
 
19
22
  from bigraph_schema.units import units, render_units_type
20
23
  from bigraph_schema.react import react_divide_counts
21
24
  from bigraph_schema.registry import (
22
25
  NONE_SYMBOL,
23
26
  Registry, TypeRegistry,
24
- type_schema_keys, non_schema_keys,
27
+ type_schema_keys, non_schema_keys, is_schema_key,
25
28
  apply_tree, visit_method,
26
29
  type_merge, deep_merge,
27
30
  get_path, establish_path, set_path, transform_path, remove_path,
28
31
  remove_omitted
29
32
  )
30
33
 
34
+ import bigraph_schema.data as data
31
35
 
32
36
 
33
37
  TYPE_SCHEMAS = {
34
38
  'float': 'float'}
35
39
 
36
40
 
37
- def is_schema_key(schema, key):
38
- return key.strip('_') not in schema.get('_type_parameters', []) and key.startswith('_')
39
-
40
-
41
41
  def resolve_path(path):
42
42
  resolve = []
43
43
 
@@ -50,7 +50,7 @@ def resolve_path(path):
50
50
  else:
51
51
  resolve.append(step)
52
52
 
53
- return resolve
53
+ return tuple(resolve)
54
54
 
55
55
 
56
56
  def apply_schema(schema, current, update, core):
@@ -428,6 +428,8 @@ class TypeSystem:
428
428
  # TODO: after the reaction, fill in the state with missing values
429
429
  # from the schema
430
430
 
431
+ # TODO: add schema to redex and reactum
432
+
431
433
  if 'redex' in reaction or 'reactum' in reaction or 'calls' in reaction:
432
434
  redex = reaction.get('redex', {})
433
435
  reactum = reaction.get('reactum', {})
@@ -438,8 +440,10 @@ class TypeSystem:
438
440
  make_reaction = self.react_registry.access(
439
441
  reaction_key)
440
442
  react = make_reaction(
441
- reaction.get(
442
- reaction_key, {}))
443
+ schema,
444
+ state,
445
+ reaction.get(reaction_key, {}),
446
+ self)
443
447
 
444
448
  redex = react.get('redex', {})
445
449
  reactum = react.get('reactum', {})
@@ -451,16 +455,24 @@ class TypeSystem:
451
455
  redex,
452
456
  mode=mode)
453
457
 
458
+ # for path in paths:
459
+ # path_schema, path_state = self.slice(
460
+ # schema,
461
+ # state,
462
+ # path)
463
+
454
464
  def merge_state(before):
455
465
  remaining = remove_omitted(
456
466
  redex,
457
467
  reactum,
458
468
  before)
459
469
 
460
- return deep_merge(
470
+ merged = deep_merge(
461
471
  remaining,
462
472
  reactum)
463
473
 
474
+ return merged
475
+
464
476
  for path in paths:
465
477
  state = transform_path(
466
478
  state,
@@ -470,6 +482,36 @@ class TypeSystem:
470
482
  return state
471
483
 
472
484
 
485
+ # TODO: maybe all fields are optional?
486
+ def dataclass(self, schema, path=None):
487
+ path = path or []
488
+
489
+ dataclass_function = self.choose_method(
490
+ schema,
491
+ {},
492
+ 'dataclass')
493
+
494
+ return dataclass_function(
495
+ schema,
496
+ path,
497
+ self)
498
+
499
+
500
+ def resolve(self, schema, update):
501
+ if update is None:
502
+ return schema
503
+ else:
504
+ resolve_function = self.choose_method(
505
+ schema,
506
+ {},
507
+ 'resolve')
508
+
509
+ return resolve_function(
510
+ schema,
511
+ update,
512
+ self)
513
+
514
+
473
515
  def check_state(self, schema, state):
474
516
  schema = self.access(schema)
475
517
 
@@ -524,11 +566,13 @@ class TypeSystem:
524
566
 
525
567
  def apply_update(self, schema, state, update):
526
568
  if isinstance(update, dict) and '_react' in update:
527
- state = self.react(
569
+ new_state = self.react(
528
570
  schema,
529
571
  state,
530
572
  update['_react'])
531
573
 
574
+ state = self.deserialize(schema, new_state)
575
+
532
576
  elif isinstance(update, dict) and '_fold' in update:
533
577
  fold = update['_fold']
534
578
 
@@ -879,13 +923,12 @@ class TypeSystem:
879
923
  def ports_schema(self, schema, instance, edge_path, ports_key='inputs'):
880
924
  found = self.access(schema)
881
925
 
882
- ports_schema = {}
883
- ports = {}
926
+ edge_schema, edge_state = self.slice(
927
+ schema,
928
+ instance,
929
+ edge_path)
884
930
 
885
- edge_schema = get_path(found, edge_path)
886
931
  ports_schema = edge_schema.get(f'_{ports_key}')
887
-
888
- edge_state = get_path(instance, edge_path)
889
932
  ports = edge_state.get(ports_key)
890
933
 
891
934
  return ports_schema, ports
@@ -954,33 +997,38 @@ class TypeSystem:
954
997
  wires = [wires]
955
998
 
956
999
  if isinstance(wires, (list, tuple)):
957
- destination = list(path) + list(wires)
1000
+ destination = resolve_path(list(path) + list(wires))
958
1001
  result = set_path(
959
1002
  result,
960
1003
  destination,
961
1004
  states)
962
1005
 
963
1006
  elif isinstance(wires, dict):
964
- branches = []
965
- for key in wires.keys():
966
- subports, substates = self.slice(ports, states, key)
967
- projection = self.project(
968
- subports,
969
- wires[key],
970
- path,
971
- substates)
1007
+ if isinstance(states, list):
1008
+ result = [
1009
+ self.project(ports, wires, path, state)
1010
+ for state in states]
1011
+ else:
1012
+ branches = []
1013
+ for key in wires.keys():
1014
+ subports, substates = self.slice(ports, states, key)
1015
+ projection = self.project(
1016
+ subports,
1017
+ wires[key],
1018
+ path,
1019
+ substates)
972
1020
 
973
- if projection is not None:
974
- branches.append(projection)
1021
+ if projection is not None:
1022
+ branches.append(projection)
975
1023
 
976
- branches = [
977
- branch
978
- for branch in branches
979
- if branch is not None] # and list(branch)[0][1] is not None]
1024
+ branches = [
1025
+ branch
1026
+ for branch in branches
1027
+ if branch is not None] # and list(branch)[0][1] is not None]
980
1028
 
981
- result = {}
982
- for branch in branches:
983
- deep_merge(result, branch)
1029
+ result = {}
1030
+ for branch in branches:
1031
+ deep_merge(result, branch)
984
1032
  else:
985
1033
  raise Exception(
986
1034
  f'inverting state\n {states}\naccording to ports schema\n {ports}\nbut wires are not recognized\n {wires}')
@@ -991,8 +1039,8 @@ class TypeSystem:
991
1039
  def project_edge(self, schema, instance, edge_path, states, ports_key='outputs'):
992
1040
  """
993
1041
  Given states from the perspective of an edge (through its ports), produce states aligned to the tree
994
- the wires point to.
995
- (inverse of view)
1042
+ the wires point to.
1043
+ (inverse of view)
996
1044
  """
997
1045
 
998
1046
  if schema is None:
@@ -1111,13 +1159,13 @@ class TypeSystem:
1111
1159
  update = self.access(initial_update)
1112
1160
 
1113
1161
  if self.equivalent(current, update):
1114
- return current
1162
+ outcome = current
1115
1163
 
1116
1164
  elif self.inherits_from(current, update):
1117
- return current
1165
+ outcome = current
1118
1166
 
1119
1167
  elif self.inherits_from(update, current):
1120
- return update
1168
+ outcome = update
1121
1169
 
1122
1170
  elif '_type' in current and '_type' in update and current['_type'] == update['_type']:
1123
1171
  outcome = current.copy()
@@ -1141,22 +1189,33 @@ class TypeSystem:
1141
1189
  outcome.get(key),
1142
1190
  update[key])
1143
1191
 
1144
- return outcome
1192
+ elif '_type' in update and '_type' not in current:
1193
+ outcome = self.resolve(update, current)
1145
1194
 
1146
1195
  else:
1147
- outcome = current.copy()
1196
+ outcome = self.resolve(current, update)
1148
1197
 
1149
- for key in update:
1150
- if not key in outcome or is_schema_key(update, key):
1151
- key_update = update[key]
1152
- if key_update:
1153
- outcome[key] = key_update
1154
- else:
1155
- outcome[key] = self.resolve_schemas(
1156
- outcome.get(key),
1157
- update[key])
1198
+ # elif '_type' in current:
1199
+ # outcome = self.resolve(current, update)
1200
+
1201
+ # elif '_type' in update:
1202
+ # outcome = self.resolve(update, current)
1203
+
1204
+ # else:
1205
+ # outcome = self.resolve(current, update)
1206
+ # outcome = current.copy()
1207
+
1208
+ # for key in update:
1209
+ # if not key in outcome or is_schema_key(update, key):
1210
+ # key_update = update[key]
1211
+ # if key_update:
1212
+ # outcome[key] = key_update
1213
+ # else:
1214
+ # outcome[key] = self.resolve_schemas(
1215
+ # outcome.get(key),
1216
+ # update[key])
1158
1217
 
1159
- return outcome
1218
+ return outcome
1160
1219
 
1161
1220
 
1162
1221
  def infer_wires(self, ports, state, wires, top_schema=None, path=None):
@@ -1203,27 +1262,19 @@ class TypeSystem:
1203
1262
  raise Exception(f'no wires at port "{port_key}" in ports {ports} with state {state}')
1204
1263
 
1205
1264
  else:
1206
- peer = get_path(
1207
- top_schema,
1208
- path[:-1])
1209
-
1265
+ compound_path = resolve_path(path[:-1] + tuple(port_wires))
1210
1266
  destination = establish_path(
1211
- peer,
1212
- port_wires[:-1],
1213
- top=top_schema,
1214
- cursor=path[:-1])
1267
+ top_schema,
1268
+ compound_path[:-1])
1215
1269
 
1216
1270
  # TODO: validate the schema/state
1217
- destination_key = port_wires[-1]
1271
+ destination_key = compound_path[-1]
1218
1272
  if destination_key in destination:
1219
1273
  current = destination[destination_key]
1220
1274
  port_schema = self.resolve_schemas(
1221
1275
  current,
1222
1276
  port_schema)
1223
1277
 
1224
- if isinstance(destination, tuple):
1225
- import ipdb; ipdb.set_trace()
1226
-
1227
1278
  destination[destination_key] = self.access(
1228
1279
  port_schema)
1229
1280
 
@@ -1341,21 +1392,16 @@ class TypeSystem:
1341
1392
  pass
1342
1393
 
1343
1394
  else:
1344
- type_schema = TYPE_SCHEMAS.get(str(type(state)), schema)
1345
-
1346
- peer = get_path(schema, path)
1347
- destination = establish_path(
1348
- peer,
1349
- path[:-1],
1350
- top=schema,
1351
- cursor=path[:-1])
1352
-
1353
- path_key = path[-1]
1354
- if path_key in destination:
1355
- # TODO: validate
1356
- pass
1357
- else:
1358
- destination[path_key] = type_schema
1395
+ type_schema = TYPE_SCHEMAS.get(
1396
+ type(state).__name__,
1397
+ 'any')
1398
+
1399
+ schema, top_state = self.set_slice(
1400
+ schema,
1401
+ top_state,
1402
+ path,
1403
+ type_schema,
1404
+ state)
1359
1405
 
1360
1406
  return schema, top_state
1361
1407
 
@@ -1529,6 +1575,14 @@ def concatenate(schema, current, update, core=None):
1529
1575
  return current + update
1530
1576
 
1531
1577
 
1578
+ def dataclass_float(schema, path, core):
1579
+ return float
1580
+
1581
+
1582
+ def dataclass_integer(schema, path, core):
1583
+ return int
1584
+
1585
+
1532
1586
  def divide_float(schema, state, values, core):
1533
1587
  divisions = values.get('divisions', 2)
1534
1588
  portion = float(state) / divisions
@@ -1659,6 +1713,18 @@ def check_list(schema, state, core):
1659
1713
  return False
1660
1714
 
1661
1715
 
1716
+ def dataclass_list(schema, path, core):
1717
+ element_type = core.find_parameter(
1718
+ schema,
1719
+ 'element')
1720
+
1721
+ dataclass = core.dataclass(
1722
+ element_type,
1723
+ path + ['element'])
1724
+
1725
+ return list[dataclass]
1726
+
1727
+
1662
1728
  def slice_list(schema, state, path, core):
1663
1729
  element_type = core.find_parameter(
1664
1730
  schema,
@@ -1724,6 +1790,25 @@ def check_tree(schema, state, core):
1724
1790
  return core.check(leaf_type, state)
1725
1791
 
1726
1792
 
1793
+ def dataclass_tree(schema, path, core):
1794
+ leaf_type = core.find_parameter(
1795
+ schema,
1796
+ 'leaf')
1797
+
1798
+ leaf_dataclass = core.dataclass(
1799
+ leaf_type,
1800
+ path + ['leaf'])
1801
+
1802
+ dataclass_name = '_'.join(path)
1803
+ # block = f"{dataclass_name} = NewType('{dataclass_name}', Union[{leaf_dataclass}, Mapping[str, '{dataclass_name}']])"
1804
+ block = f"NewType('{dataclass_name}', Union[{leaf_dataclass}, Mapping[str, '{dataclass_name}']])"
1805
+
1806
+ dataclass = eval(block)
1807
+ setattr(data, dataclass_name, dataclass)
1808
+
1809
+ return dataclass
1810
+
1811
+
1727
1812
  def slice_tree(schema, state, path, core):
1728
1813
  leaf_type = core.find_parameter(
1729
1814
  schema,
@@ -1817,6 +1902,30 @@ def apply_map(schema, current, update, core=None):
1817
1902
  return result
1818
1903
 
1819
1904
 
1905
+ def resolve_map(schema, update, core):
1906
+ if isinstance(update, dict):
1907
+ value_schema = schema.get('_value', {})
1908
+ for key, subschema in update.items():
1909
+ value_schema = core.resolve_schemas(
1910
+ value_schema,
1911
+ subschema)
1912
+ schema['_value'] = value_schema
1913
+
1914
+ return schema
1915
+
1916
+
1917
+ def dataclass_map(schema, path, core):
1918
+ value_type = core.find_parameter(
1919
+ schema,
1920
+ 'value')
1921
+
1922
+ dataclass = core.dataclass(
1923
+ value_type,
1924
+ path + ['value'])
1925
+
1926
+ return Mapping[str, dataclass]
1927
+
1928
+
1820
1929
  def check_map(schema, state, core=None):
1821
1930
  value_type = core.find_parameter(
1822
1931
  schema,
@@ -1893,6 +2002,18 @@ def apply_maybe(schema, current, update, core):
1893
2002
  update)
1894
2003
 
1895
2004
 
2005
+ def dataclass_maybe(schema, path, core):
2006
+ value_type = core.find_parameter(
2007
+ schema,
2008
+ 'value')
2009
+
2010
+ dataclass = core.dataclass(
2011
+ value_type,
2012
+ path + ['value'])
2013
+
2014
+ return Optional[dataclass]
2015
+
2016
+
1896
2017
  def check_maybe(schema, state, core):
1897
2018
  if state is None:
1898
2019
  return True
@@ -1984,6 +2105,20 @@ def apply_edge(schema, current, update, core):
1984
2105
  return result
1985
2106
 
1986
2107
 
2108
+ def dataclass_edge(schema, path, core):
2109
+ inputs = schema.get('_inputs', {})
2110
+ inputs_dataclass = core.dataclass(
2111
+ inputs,
2112
+ path + ['inputs'])
2113
+
2114
+ outputs = schema.get('_outputs', {})
2115
+ outputs_dataclass = core.dataclass(
2116
+ outputs,
2117
+ path + ['outputs'])
2118
+
2119
+ return Callable[[inputs_dataclass], outputs_dataclass]
2120
+
2121
+
1987
2122
  def check_ports(state, core, key):
1988
2123
  return key in state and core.check(
1989
2124
  'wires',
@@ -2021,6 +2156,10 @@ def check_array(schema, state, core):
2021
2156
 
2022
2157
 
2023
2158
 
2159
+ def dataclass_array(schema, path, core):
2160
+ return np.ndarray
2161
+
2162
+
2024
2163
  def slice_array(schema, state, path, core):
2025
2164
  if len(path) > 0:
2026
2165
  head = path[0]
@@ -2058,16 +2197,16 @@ def serialize_array(schema, value, core):
2058
2197
  if isinstance(value, dict):
2059
2198
  return value
2060
2199
  else:
2061
- data = 'string'
2200
+ array_data = 'string'
2062
2201
  dtype = value.dtype.name
2063
2202
  if dtype.startswith('int'):
2064
- data = 'integer'
2203
+ array_data = 'integer'
2065
2204
  elif dtype.startswith('float'):
2066
- data = 'float'
2205
+ array_data = 'float'
2067
2206
 
2068
2207
  return {
2069
2208
  'bytes': value.tobytes(),
2070
- 'data': data,
2209
+ 'data': array_data,
2071
2210
  'shape': value.shape}
2072
2211
 
2073
2212
 
@@ -2339,6 +2478,14 @@ def register_units(core, units):
2339
2478
 
2340
2479
 
2341
2480
 
2481
+ def dataclass_boolean(schema, path, core):
2482
+ return bool
2483
+
2484
+
2485
+ def dataclass_string(schema, path, core):
2486
+ return str
2487
+
2488
+
2342
2489
  base_type_library = {
2343
2490
  'boolean': {
2344
2491
  '_type': 'boolean',
@@ -2347,7 +2494,7 @@ base_type_library = {
2347
2494
  '_apply': apply_boolean,
2348
2495
  '_serialize': serialize_boolean,
2349
2496
  '_deserialize': deserialize_boolean,
2350
- },
2497
+ '_dataclass': dataclass_boolean},
2351
2498
 
2352
2499
  # abstract number type
2353
2500
  'number': {
@@ -2363,6 +2510,7 @@ base_type_library = {
2363
2510
  # inherit _apply and _serialize from number type
2364
2511
  '_check': check_integer,
2365
2512
  '_deserialize': deserialize_integer,
2513
+ '_dataclass': dataclass_integer,
2366
2514
  '_description': '64-bit integer',
2367
2515
  '_inherit': 'number'},
2368
2516
 
@@ -2372,6 +2520,7 @@ base_type_library = {
2372
2520
  '_check': check_float,
2373
2521
  '_deserialize': deserialize_float,
2374
2522
  '_divide': divide_float,
2523
+ '_dataclass': dataclass_float,
2375
2524
  '_description': '64-bit floating point precision number',
2376
2525
  '_inherit': 'number'},
2377
2526
 
@@ -2382,6 +2531,7 @@ base_type_library = {
2382
2531
  '_apply': replace,
2383
2532
  '_serialize': serialize_string,
2384
2533
  '_deserialize': deserialize_string,
2534
+ '_dataclass': dataclass_string,
2385
2535
  '_description': '64-bit integer'},
2386
2536
 
2387
2537
  'list': {
@@ -2392,12 +2542,12 @@ base_type_library = {
2392
2542
  '_apply': apply_list,
2393
2543
  '_serialize': serialize_list,
2394
2544
  '_deserialize': deserialize_list,
2545
+ '_dataclass': dataclass_list,
2395
2546
  '_fold': fold_list,
2396
2547
  '_divide': divide_list,
2397
2548
  '_type_parameters': ['element'],
2398
2549
  '_description': 'general list type (or sublists)'},
2399
2550
 
2400
- # TODO: tree should behave as if the leaf type is a valid tree
2401
2551
  'tree': {
2402
2552
  '_type': 'tree',
2403
2553
  '_default': {},
@@ -2406,6 +2556,7 @@ base_type_library = {
2406
2556
  '_apply': apply_tree,
2407
2557
  '_serialize': serialize_tree,
2408
2558
  '_deserialize': deserialize_tree,
2559
+ '_dataclass': dataclass_tree,
2409
2560
  '_fold': fold_tree,
2410
2561
  '_divide': divide_tree,
2411
2562
  '_type_parameters': ['leaf'],
@@ -2417,6 +2568,8 @@ base_type_library = {
2417
2568
  '_apply': apply_map,
2418
2569
  '_serialize': serialize_map,
2419
2570
  '_deserialize': deserialize_map,
2571
+ '_resolve': resolve_map,
2572
+ '_dataclass': dataclass_map,
2420
2573
  '_check': check_map,
2421
2574
  '_slice': slice_map,
2422
2575
  '_fold': fold_map,
@@ -2433,6 +2586,7 @@ base_type_library = {
2433
2586
  '_apply': apply_array,
2434
2587
  '_serialize': serialize_array,
2435
2588
  '_deserialize': deserialize_array,
2589
+ '_dataclass': dataclass_array,
2436
2590
  '_type_parameters': [
2437
2591
  'shape',
2438
2592
  'data'],
@@ -2446,6 +2600,7 @@ base_type_library = {
2446
2600
  '_slice': slice_maybe,
2447
2601
  '_serialize': serialize_maybe,
2448
2602
  '_deserialize': deserialize_maybe,
2603
+ '_dataclass': dataclass_maybe,
2449
2604
  '_fold': fold_maybe,
2450
2605
  '_type_parameters': ['value'],
2451
2606
  '_description': 'type to represent values that could be empty'},
@@ -2465,7 +2620,6 @@ base_type_library = {
2465
2620
  '_apply': apply_schema},
2466
2621
 
2467
2622
  'edge': {
2468
- # TODO: do we need to have defaults informed by type parameters?
2469
2623
  '_type': 'edge',
2470
2624
  '_default': {
2471
2625
  'inputs': {},
@@ -2473,6 +2627,7 @@ base_type_library = {
2473
2627
  '_apply': apply_edge,
2474
2628
  '_serialize': serialize_edge,
2475
2629
  '_deserialize': deserialize_edge,
2630
+ '_dataclass': dataclass_edge,
2476
2631
  '_check': check_edge,
2477
2632
  '_type_parameters': ['inputs', 'outputs'],
2478
2633
  '_description': 'hyperedges in the bigraph, with inputs and outputs as type parameters',
@@ -2480,10 +2635,109 @@ base_type_library = {
2480
2635
  'outputs': 'wires'}}
2481
2636
 
2482
2637
 
2638
+ def add_reaction(schema, state, reaction, core):
2639
+ path = reaction.get('path')
2640
+
2641
+ redex = {}
2642
+ establish_path(
2643
+ redex,
2644
+ path)
2645
+
2646
+ reactum = {}
2647
+ node = establish_path(
2648
+ reactum,
2649
+ path)
2650
+
2651
+ deep_merge(
2652
+ node,
2653
+ reaction.get('add', {}))
2654
+
2655
+ return {
2656
+ 'redex': redex,
2657
+ 'reactum': reactum}
2658
+
2659
+
2660
+ def remove_reaction(schema, state, reaction, core):
2661
+ path = reaction.get('path', ())
2662
+ redex = {}
2663
+ node = establish_path(
2664
+ redex,
2665
+ path)
2666
+
2667
+ for remove in reaction.get('remove', []):
2668
+ node[remove] = {}
2669
+
2670
+ reactum = {}
2671
+ establish_path(
2672
+ reactum,
2673
+ path)
2674
+
2675
+ return {
2676
+ 'redex': redex,
2677
+ 'reactum': reactum}
2678
+
2679
+
2680
+ def replace_reaction(schema, state, reaction, core):
2681
+ path = reaction.get('path', ())
2682
+
2683
+ redex = {}
2684
+ node = establish_path(
2685
+ redex,
2686
+ path)
2687
+
2688
+ for before_key, before_state in reaction.get('before', {}).items():
2689
+ node[before_key] = before_state
2690
+
2691
+ reactum = {}
2692
+ node = establish_path(
2693
+ reactum,
2694
+ path)
2695
+
2696
+ for after_key, after_state in reaction.get('after', {}).items():
2697
+ node[after_key] = after_state
2698
+
2699
+ return {
2700
+ 'redex': redex,
2701
+ 'reactum': reactum}
2702
+
2703
+
2704
+ def divide_reaction(schema, state, reaction, core):
2705
+ mother = reaction['mother']
2706
+ daughters = reaction['daughters']
2707
+
2708
+ mother_schema, mother_state = core.slice(
2709
+ schema,
2710
+ state,
2711
+ mother)
2712
+
2713
+ division = core.fold(
2714
+ mother_schema,
2715
+ mother_state,
2716
+ 'divide', {
2717
+ 'divisions': len(daughters),
2718
+ 'daughter_configs': [daughter[1] for daughter in daughters]})
2719
+
2720
+ after = {
2721
+ daughter[0]: daughter_state
2722
+ for daughter, daughter_state in zip(daughters, division)}
2723
+
2724
+ replace = {
2725
+ 'before': {
2726
+ mother: {}},
2727
+ 'after': after}
2728
+
2729
+ return replace_reaction(
2730
+ schema,
2731
+ state,
2732
+ replace,
2733
+ core)
2734
+
2735
+
2483
2736
  def register_base_reactions(core):
2484
- core.register_reaction(
2485
- 'divide_counts',
2486
- react_divide_counts)
2737
+ core.register_reaction('add', add_reaction)
2738
+ core.register_reaction('remove', remove_reaction)
2739
+ core.register_reaction('replace', replace_reaction)
2740
+ core.register_reaction('divide', divide_reaction)
2487
2741
 
2488
2742
 
2489
2743
  def register_cube(core):
@@ -3442,31 +3696,6 @@ def test_add_reaction(compartment_types):
3442
3696
  'counts': {'A': 13},
3443
3697
  'inner': {}}}}}
3444
3698
 
3445
- def add_reaction(config):
3446
- path = config.get('path')
3447
-
3448
- redex = {}
3449
- establish_path(
3450
- redex,
3451
- path)
3452
-
3453
- reactum = {}
3454
- node = establish_path(
3455
- reactum,
3456
- path)
3457
-
3458
- deep_merge(
3459
- node,
3460
- config.get('add', {}))
3461
-
3462
- return {
3463
- 'redex': redex,
3464
- 'reactum': reactum}
3465
-
3466
- compartment_types.react_registry.register(
3467
- 'add',
3468
- add_reaction)
3469
-
3470
3699
  add_config = {
3471
3700
  'path': ['environment', 'inner'],
3472
3701
  'add': {
@@ -3508,30 +3737,6 @@ def test_remove_reaction(compartment_types):
3508
3737
  'counts': {'A': 13},
3509
3738
  'inner': {}}}}}
3510
3739
 
3511
- # TODO: register these for general access
3512
- def remove_reaction(config):
3513
- path = config.get('path', ())
3514
- redex = {}
3515
- node = establish_path(
3516
- redex,
3517
- path)
3518
-
3519
- for remove in config.get('remove', []):
3520
- node[remove] = {}
3521
-
3522
- reactum = {}
3523
- establish_path(
3524
- reactum,
3525
- path)
3526
-
3527
- return {
3528
- 'redex': redex,
3529
- 'reactum': reactum}
3530
-
3531
- compartment_types.react_registry.register(
3532
- 'remove',
3533
- remove_reaction)
3534
-
3535
3740
  remove_config = {
3536
3741
  'path': ['environment', 'inner'],
3537
3742
  'remove': ['0']}
@@ -3566,43 +3771,16 @@ def test_replace_reaction(compartment_types):
3566
3771
  'counts': {'A': 13},
3567
3772
  'inner': {}}}}}
3568
3773
 
3569
- def replace_reaction(config):
3570
- path = config.get('path', ())
3571
-
3572
- redex = {}
3573
- node = establish_path(
3574
- redex,
3575
- path)
3576
-
3577
- for before_key, before_state in config.get('before', {}).items():
3578
- node[before_key] = before_state
3579
-
3580
- reactum = {}
3581
- node = establish_path(
3582
- reactum,
3583
- path)
3584
-
3585
- for after_key, after_state in config.get('after', {}).items():
3586
- node[after_key] = after_state
3587
-
3588
- return {
3589
- 'redex': redex,
3590
- 'reactum': reactum}
3591
-
3592
- compartment_types.react_registry.register(
3593
- 'replace',
3594
- replace_reaction)
3595
-
3596
- replace_config = {
3597
- 'path': ['environment', 'inner'],
3598
- 'before': {'0': {'A': '?1'}},
3599
- 'after': {
3600
- '2': {
3601
- 'counts': {
3602
- 'A': {'function': 'divide', 'arguments': ['?1', 0.5], }}},
3603
- '3': {
3604
- 'counts': {
3605
- 'A': '@1'}}}}
3774
+ # replace_config = {
3775
+ # 'path': ['environment', 'inner'],
3776
+ # 'before': {'0': {'A': '?1'}},
3777
+ # 'after': {
3778
+ # '2': {
3779
+ # 'counts': {
3780
+ # 'A': {'function': 'divide', 'arguments': ['?1', 0.5], }}},
3781
+ # '3': {
3782
+ # 'counts': {
3783
+ # 'A': '@1'}}}}
3606
3784
 
3607
3785
  replace_config = {
3608
3786
  'path': ['environment', 'inner'],
@@ -4380,6 +4558,130 @@ def test_set_slice(core):
4380
4558
  1])[1] == 33
4381
4559
 
4382
4560
 
4561
+ def from_state(dataclass, state):
4562
+ if hasattr(dataclass, '__dataclass_fields__'):
4563
+ fields = dataclass.__dataclass_fields__
4564
+ state = state or {}
4565
+
4566
+ init = {}
4567
+ for key, field in fields.items():
4568
+ substate = from_state(
4569
+ field.type,
4570
+ state.get(key))
4571
+ init[key] = substate
4572
+ instance = dataclass(**init)
4573
+ # elif get_origin(dataclass) in [typing.Union, typing.Mapping]:
4574
+ # instance = state
4575
+ else:
4576
+ instance = state
4577
+ # instance = dataclass(state)
4578
+
4579
+ return instance
4580
+
4581
+
4582
+ def test_dataclass(core):
4583
+ simple_schema = {
4584
+ 'a': 'float',
4585
+ 'b': 'integer',
4586
+ 'c': 'boolean',
4587
+ 'x': 'string'}
4588
+
4589
+ # TODO: accept just a string instead of only a path
4590
+ simple_dataclass = core.dataclass(
4591
+ simple_schema,
4592
+ ['simple'])
4593
+
4594
+ simple_state = {
4595
+ 'a': 88.888,
4596
+ 'b': 11111,
4597
+ 'c': False,
4598
+ 'x': 'not a string'}
4599
+
4600
+ simple_new = simple_dataclass(
4601
+ a=1.11,
4602
+ b=33,
4603
+ c=True,
4604
+ x='what')
4605
+
4606
+ simple_from = from_state(
4607
+ simple_dataclass,
4608
+ simple_state)
4609
+
4610
+ nested_schema = {
4611
+ 'a': {
4612
+ 'a': {
4613
+ 'a': 'float',
4614
+ 'b': 'float'},
4615
+ 'x': 'float'}}
4616
+
4617
+ nested_dataclass = core.dataclass(
4618
+ nested_schema,
4619
+ ['nested'])
4620
+
4621
+ nested_state = {
4622
+ 'a': {
4623
+ 'a': {
4624
+ 'a': 13.4444,
4625
+ 'b': 888.88},
4626
+ 'x': 111.11111}}
4627
+
4628
+ nested_new = data.nested(
4629
+ data.nested_a(
4630
+ data.nested_a_a(
4631
+ a=222.22,
4632
+ b=3.3333),
4633
+ 5555.55))
4634
+
4635
+ nested_from = from_state(
4636
+ nested_dataclass,
4637
+ nested_state)
4638
+
4639
+ complex_schema = {
4640
+ 'a': 'tree[maybe[float]]',
4641
+ 'b': 'float~list[string]',
4642
+ 'c': {
4643
+ 'd': 'map[edge[GGG:float,OOO:float]]',
4644
+ 'e': 'array[(3|4|10),float]'}}
4645
+
4646
+ complex_dataclass = core.dataclass(
4647
+ complex_schema,
4648
+ ['complex'])
4649
+
4650
+ complex_state = {
4651
+ 'a': {
4652
+ 'x': {
4653
+ 'oooo': None,
4654
+ 'y': 1.1,
4655
+ 'z': 33.33},
4656
+ 'w': 44.444},
4657
+ 'b': ['1', '11', '111', '1111'],
4658
+ 'c': {
4659
+ 'd': {
4660
+ 'A': {
4661
+ 'inputs': {
4662
+ 'GGG': ['..', '..', 'a', 'w']},
4663
+ 'outputs': {
4664
+ 'OOO': ['..', '..', 'a', 'x', 'y']}},
4665
+ 'B': {
4666
+ 'inputs': {
4667
+ 'GGG': ['..', '..', 'a', 'x', 'y']},
4668
+ 'outputs': {
4669
+ 'OOO': ['..', '..', 'a', 'w']}}},
4670
+ 'e': np.zeros((3, 4, 10))}}
4671
+
4672
+ complex_from = from_state(
4673
+ complex_dataclass,
4674
+ complex_state)
4675
+
4676
+ complex_dict = asdict(complex_from)
4677
+
4678
+ # assert complex_dict == complex_state ?
4679
+
4680
+ assert complex_from.a['x']['oooo'] is None
4681
+ assert len(complex_from.c.d['A']['inputs']['GGG'])
4682
+ assert isinstance(complex_from.c.e, np.ndarray)
4683
+
4684
+
4383
4685
  if __name__ == '__main__':
4384
4686
  core = TypeSystem()
4385
4687
 
@@ -4422,4 +4724,4 @@ if __name__ == '__main__':
4422
4724
  test_bind(core)
4423
4725
  test_slice(core)
4424
4726
  test_set_slice(core)
4425
-
4727
+ test_dataclass(core)
@@ -1,10 +1,12 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: bigraph-schema
3
- Version: 0.0.32
3
+ Version: 0.0.37
4
4
  Summary: A serializable type schema for compositional systems biology
5
5
  Home-page: https://github.com/vivarium-collective/bigraph-schema
6
6
  Author: Eran Agmon, Ryan Spangler
7
7
  Author-email: agmon.eran@gmail.com, ryan.spangler@gmail.com
8
+ License: UNKNOWN
9
+ Platform: UNKNOWN
8
10
  Classifier: Development Status :: 3 - Alpha
9
11
  Classifier: Intended Audience :: Developers
10
12
  Classifier: License :: OSI Approved :: MIT License
@@ -52,3 +54,5 @@ This resource will guide you through the core concepts and methods, helping you
52
54
  ## License
53
55
 
54
56
  Bigraph-schema is open-source software released under the [Apache 2 License](https://github.com/vivarium-collective/bigraph-schema/blob/main/LICENSE).
57
+
58
+
@@ -3,6 +3,7 @@ LICENSE
3
3
  README.md
4
4
  setup.py
5
5
  bigraph_schema/__init__.py
6
+ bigraph_schema/data.py
6
7
  bigraph_schema/parse.py
7
8
  bigraph_schema/protocols.py
8
9
  bigraph_schema/react.py
@@ -1,7 +1,7 @@
1
1
  from setuptools import setup, find_packages
2
2
 
3
3
 
4
- VERSION = '0.0.32'
4
+ VERSION = '0.0.37'
5
5
 
6
6
 
7
7
  with open("README.md", "r") as readme:
File without changes