bigraph-schema 1.0.0__py3-none-any.whl

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.
@@ -0,0 +1,370 @@
1
+ from __future__ import annotations
2
+
3
+ import typing
4
+ import numpy as np
5
+ import numpy.lib.format as nf
6
+ import collections
7
+
8
+ from plum import dispatch
9
+ from dataclasses import dataclass, is_dataclass, field
10
+
11
+
12
+ NONE_SYMBOL = '__nil__'
13
+
14
+
15
+ @dataclass(kw_only=True)
16
+ class Node():
17
+ _default: object = None
18
+
19
+ @dataclass(kw_only=True)
20
+ class Place(Node):
21
+ _subnodes: dict = field(default_factory=dict)
22
+
23
+ @dataclass(kw_only=True)
24
+ class Atom(Node):
25
+ pass
26
+
27
+ @dataclass(kw_only=True)
28
+ class Empty(Atom):
29
+ pass
30
+
31
+ @dataclass(kw_only=True)
32
+ class Union(Node):
33
+ _options: typing.Tuple[Node] = field(default_factory=tuple)
34
+
35
+ @dataclass(kw_only=True)
36
+ class Tuple(Node):
37
+ _values: typing.List[Node] = field(default_factory=list)
38
+
39
+ @dataclass(kw_only=True)
40
+ class Boolean(Atom):
41
+ pass
42
+
43
+ @dataclass(kw_only=True)
44
+ class Or(Boolean):
45
+ _default: bool = False
46
+
47
+ @dataclass(kw_only=True)
48
+ class And(Boolean):
49
+ _default: bool = True
50
+
51
+ @dataclass(kw_only=True)
52
+ class Xor(Boolean):
53
+ _default: bool = False
54
+
55
+ @dataclass(kw_only=True)
56
+ class Number(Atom):
57
+ pass
58
+
59
+ @dataclass(kw_only=True)
60
+ class Integer(Number):
61
+ pass
62
+
63
+ @dataclass(kw_only=True)
64
+ class Float(Number):
65
+ pass
66
+
67
+ @dataclass(kw_only=True)
68
+ class Complex(Float):
69
+ pass
70
+
71
+ @dataclass(kw_only=True)
72
+ class Delta(Float):
73
+ pass
74
+
75
+ @dataclass(kw_only=True)
76
+ class Nonnegative(Float):
77
+ pass
78
+
79
+ @dataclass(kw_only=True)
80
+ class NPRandom(Node):
81
+ state: Tuple() = field(default_factory=tuple)
82
+
83
+ @dataclass(kw_only=True)
84
+ class String(Atom):
85
+ pass
86
+
87
+ @dataclass(kw_only=True)
88
+ class Enum(String):
89
+ _values: typing.Tuple[str] = field(default_factory=tuple)
90
+
91
+ @dataclass(kw_only=True)
92
+ class Wrap(Node):
93
+ _value: Node = field(default_factory=Node)
94
+
95
+ @dataclass(kw_only=True)
96
+ class Maybe(Wrap):
97
+ pass
98
+
99
+ @dataclass(kw_only=True)
100
+ class Overwrite(Wrap):
101
+ pass
102
+
103
+ @dataclass(kw_only=True)
104
+ class List(Node):
105
+ _element: Node = field(default_factory=Node)
106
+
107
+ @dataclass(kw_only=True)
108
+ class Map(Node):
109
+ _key: Node = field(default_factory=String)
110
+ _value: Node = field(default_factory=Node)
111
+
112
+ @dataclass(kw_only=True)
113
+ class Tree(Node):
114
+ _leaf: Node = field(default_factory=Node)
115
+
116
+ @dataclass(kw_only=True)
117
+ class Array(Node):
118
+ _shape: typing.Tuple[int] = field(default_factory=tuple)
119
+ _data: np.dtype = field(default_factory=lambda:np.dtype('float64'))
120
+
121
+ @dataclass(kw_only=True)
122
+ class Path(List):
123
+ _element: Node = field(default_factory=Node)
124
+
125
+ @dataclass(kw_only=True)
126
+ class Wires(Tree):
127
+ _leaf: Node = field(default_factory=Path)
128
+
129
+ @dataclass(kw_only=True)
130
+ class Schema(Tree):
131
+ _leaf: Node = field(default_factory=Node)
132
+
133
+ @dataclass(kw_only=True)
134
+ class Protocol(Node):
135
+ protocol: String = field(default_factory=String)
136
+ data: Node = field(default_factory=Node)
137
+
138
+ @dataclass(kw_only=True)
139
+ class LocalProtocol(Protocol):
140
+ data: String = field(default_factory=String)
141
+
142
+ @dataclass(kw_only=True)
143
+ class Link(Node):
144
+ address: Protocol = field(default_factory=Protocol)
145
+ config: Node = field(default_factory=Node)
146
+ _inputs: dict = field(default_factory=dict)
147
+ _outputs: dict = field(default_factory=dict)
148
+ inputs: Wires = field(default_factory=Wires)
149
+ outputs: Wires = field(default_factory=Wires)
150
+
151
+ # types for jumps in traversals
152
+
153
+ @dataclass(kw_only=True)
154
+ class Jump():
155
+ _value: object
156
+
157
+ @dataclass(kw_only=True)
158
+ class Key(Jump):
159
+ _value: str
160
+
161
+ @dataclass(kw_only=True)
162
+ class Index(Jump):
163
+ _value: int
164
+
165
+ @dataclass(kw_only=True)
166
+ class Slice(Jump):
167
+ _value: slice
168
+
169
+ @dataclass(kw_only=True)
170
+ class Star(Jump):
171
+ _value: str = '*'
172
+
173
+ @dataclass(kw_only=True)
174
+ class Match(Jump):
175
+ _match = str # regex
176
+
177
+ def convert_jump(key):
178
+ if isinstance(key, Jump):
179
+ return key
180
+
181
+ convert_key = None
182
+ if isinstance(key, str):
183
+ if key == '*':
184
+ convert_key = Star(_value=key)
185
+ else:
186
+ convert_key = Key(_value=key)
187
+ elif isinstance(key, int):
188
+ convert_key = Index(_value=key)
189
+
190
+ return convert_key or Jump(_value=key)
191
+
192
+ def resolve_path(path):
193
+ """
194
+ Given a path that includes '..' steps, resolve the path to a canonical form
195
+ """
196
+ resolve = []
197
+
198
+ for step in path:
199
+ if step == '..':
200
+ if len(resolve) == 0:
201
+ raise Exception(f'cannot go above the top in path: "{path}"')
202
+ else:
203
+ resolve = resolve[:-1]
204
+ else:
205
+ resolve.append(step)
206
+
207
+ return tuple(resolve)
208
+
209
+ def deep_merge(dct, merge_dct):
210
+ """
211
+ Deep merge `merge_dct` into `dct`, modifying `dct` in-place.
212
+
213
+ Nested dictionaries are recursively merged.
214
+ """
215
+ if dct is None:
216
+ dct = {}
217
+ if merge_dct is None:
218
+ merge_dct = {}
219
+ if not isinstance(merge_dct, dict):
220
+ return merge_dct
221
+
222
+ for k, v in merge_dct.items():
223
+ if (k in dct and isinstance(dct[k], dict)
224
+ and isinstance(v, collections.abc.Mapping)):
225
+ deep_merge(dct[k], v)
226
+ else:
227
+ dct[k] = v
228
+ return dct
229
+
230
+ def is_empty(value):
231
+ return value is None or value == {} or value == []
232
+
233
+ def convert_path(path):
234
+ resolved = resolve_path(path)
235
+ return [
236
+ convert_jump(key)
237
+ for key in resolved]
238
+
239
+ def blank_context(schema, state, path):
240
+ return {
241
+ 'schema': schema,
242
+ 'state': state,
243
+ 'path': (),
244
+ 'subpath': path}
245
+
246
+ def walk_path(context, to, subpath=None):
247
+ return {
248
+ **context,
249
+ 'path': context['path'] + (to,),
250
+ 'subpath': subpath}
251
+
252
+ def dtype_schema(dtype: np.dtype):
253
+ data = nf.dtype_to_descr(dtype)
254
+ if isinstance(data, str):
255
+ if 'f' in data or 'd' in data:
256
+ return Float()
257
+ elif 'U' in data:
258
+ return String()
259
+ elif 'i' in data or 'b' in data or 'h' in data:
260
+ return Integer()
261
+ elif 'F' in data or 'D' in data:
262
+ return Complex()
263
+ elif isinstance(data, list):
264
+ result = {}
265
+ for group in data:
266
+ key = group[0]
267
+ subschema = np.dtype(group[1])
268
+ if len(group) > 2:
269
+ shape = group[2]
270
+ subschema = Array(_shape=shape, _data=subschema)
271
+ result[key] = subschema
272
+
273
+ return result
274
+ else:
275
+ raise Exception('do not know how to interpret dtype as schema:\n\n{dtype}\n\n')
276
+
277
+
278
+ def make_default(schema, state):
279
+ return {
280
+ '_type': schema,
281
+ '_default': state}
282
+
283
+
284
+ @dispatch
285
+ def schema_dtype(schema: Complex):
286
+ return np.dtype('complex128')
287
+
288
+ @dispatch
289
+ def schema_dtype(schema: Float):
290
+ return np.dtype('float64')
291
+
292
+ @dispatch
293
+ def schema_dtype(schema: Integer):
294
+ return np.dtype('int32')
295
+
296
+ @dispatch
297
+ def schema_dtype(schema: Boolean):
298
+ return np.dtype('bool')
299
+
300
+ @dispatch
301
+ def schema_dtype(schema: String):
302
+ return np.dtype('unicode')
303
+
304
+ @dispatch
305
+ def schema_dtype(schema: str):
306
+ return np.dtype(schema)
307
+
308
+ @dispatch
309
+ def schema_dtype(schema: list):
310
+ return np.dtype(schema)
311
+
312
+ @dispatch
313
+ def schema_dtype(schema: dict):
314
+ result = []
315
+ if not schema:
316
+ return None
317
+
318
+ for key, value in schema.items():
319
+ subschema = schema_dtype(value)
320
+ subresult = (key, subschema)
321
+ if isinstance(subschema, Array):
322
+ subshape = subschema._shape
323
+ subresult = subresult + (subshape,)
324
+
325
+ result.append(subresult)
326
+
327
+ if all([isinstance(key, int) for key in schema.keys()]):
328
+ shape = max(schema.keys())
329
+ return Array(_shape=(shape,), _data=result[0][1])
330
+ else:
331
+ return np.dtype(result)
332
+
333
+ @dispatch
334
+ def schema_dtype(schema):
335
+ raise Exception(f'schema dtype not implemented for:\n\n{schema}\n\n')
336
+
337
+
338
+ BASE_TYPES = {
339
+ 'node': Node,
340
+ 'atom': Atom,
341
+ 'empty': Empty,
342
+ 'union': Union,
343
+ 'tuple': Tuple,
344
+ 'boolean': Boolean,
345
+ 'or': Or,
346
+ 'and': And,
347
+ 'xor': Xor,
348
+ 'number': Number,
349
+ 'integer': Integer,
350
+ 'float': Float,
351
+ 'delta': Delta,
352
+ 'nonnegative': Nonnegative,
353
+ 'random_state': NPRandom,
354
+ 'string': String,
355
+ 'enum': Enum,
356
+ 'wrap': Wrap,
357
+ 'maybe': Maybe,
358
+ 'overwrite': Overwrite,
359
+ 'list': List,
360
+ 'map': Map,
361
+ 'tree': Tree,
362
+ 'array': Array,
363
+ 'path': Path,
364
+ 'wires': Wires,
365
+ 'protocol': Protocol,
366
+ 'local': LocalProtocol,
367
+ 'schema': Schema,
368
+ 'link': Link}
369
+
370
+
@@ -0,0 +1,133 @@
1
+ """
2
+ =====
3
+ Units
4
+ =====
5
+
6
+ Register all of the unit types from the pint unit registry
7
+ """
8
+
9
+ from pint import UnitRegistry
10
+
11
+
12
+ units = UnitRegistry()
13
+
14
+
15
+ def render_coefficient(original_power):
16
+ power = abs(original_power)
17
+ int_part = int(power)
18
+ root_part = power % 1
19
+
20
+ if root_part != 0.0:
21
+ render = str(root_part)[2:]
22
+ render = f'{int_part}_{render}'
23
+ else:
24
+ render = str(int_part)
25
+
26
+ return render
27
+
28
+
29
+ def render_units_type(dimensionality):
30
+ unit_keys = list(dimensionality.keys())
31
+ unit_keys.sort()
32
+
33
+ numerator = []
34
+ denominator = []
35
+
36
+ for unit_key in unit_keys:
37
+ inner_key = unit_key.strip('[]')
38
+ power = dimensionality[unit_key]
39
+ negative = False
40
+
41
+ if power < 0:
42
+ negative = True
43
+ power = -power
44
+
45
+ if power == 1:
46
+ render = inner_key
47
+ else:
48
+ render = f'{inner_key}^{render_coefficient(power)}'
49
+
50
+ if negative:
51
+ denominator.append(render)
52
+ else:
53
+ numerator.append(render)
54
+
55
+ render = '*'.join(numerator)
56
+ if len(denominator) > 0:
57
+ render_denominator = '*'.join(denominator)
58
+ render = f'{render}/{render_denominator}'
59
+
60
+ return render
61
+
62
+
63
+ def parse_coefficient(s):
64
+ if s is None:
65
+ return 1
66
+ elif '_' in s:
67
+ parts = s.split('_')
68
+ if len(parts) > 1:
69
+ base, residue = parts
70
+ return int(base) + (float(residue) / 10.0)
71
+ else:
72
+ return int(parts)
73
+ else:
74
+ return int(s)
75
+
76
+
77
+ def parse_dimensionality(s):
78
+ numerator, denominator = s.split('/')
79
+ numerator_terms = numerator.split('*')
80
+ denominator_terms = denominator.split('*')
81
+
82
+ dimensionality = {}
83
+
84
+ for term in numerator_terms:
85
+ base = term.split('^')
86
+ exponent = None
87
+ if len(base) > 1:
88
+ exponent = base[1]
89
+ dimensionality[f'[{base[0]}]'] = parse_coefficient(exponent)
90
+
91
+ for term in denominator_terms:
92
+ power = term.split('^')
93
+ exponent = None
94
+ if len(power) > 1:
95
+ exponent = power[1]
96
+ dimensionality[f'[{power[0]}]'] = -parse_coefficient(exponent)
97
+
98
+ return dimensionality
99
+
100
+
101
+ def test_units_render():
102
+ dimensionality = units.newton.dimensionality
103
+ render = render_units_type(dimensionality)
104
+ recover = parse_dimensionality(render)
105
+
106
+ print(f'original: {dimensionality}')
107
+ print(f'render: {render}')
108
+ print(f'parsed: {recover}')
109
+
110
+ assert render == 'length*mass/time^2'
111
+ assert recover == dimensionality
112
+
113
+
114
+ def test_roots_cycle():
115
+ dimensionality = {
116
+ '[length]': 1.5,
117
+ '[time]': 3,
118
+ '[mass]': -2.5,
119
+ }
120
+ render = render_units_type(dimensionality)
121
+ recover = parse_dimensionality(render)
122
+
123
+ print(f'original: {dimensionality}')
124
+ print(f'render: {render}')
125
+ print(f'parsed: {recover}')
126
+
127
+ assert render == 'length^1_5*time^3/mass^2_5'
128
+ assert recover == dimensionality
129
+
130
+
131
+ if __name__ == '__main__':
132
+ test_units_render()
133
+ test_roots_cycle()
@@ -0,0 +1,66 @@
1
+ Metadata-Version: 2.4
2
+ Name: bigraph-schema
3
+ Version: 1.0.0
4
+ Summary: A serializable type schema for compositional systems biology
5
+ Author: Eran Agmon, Ryan Spangler
6
+ Requires-Python: >=3.7
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ License-File: AUTHORS.md
10
+ Requires-Dist: fire
11
+ Requires-Dist: numpy
12
+ Requires-Dist: orjson
13
+ Requires-Dist: parsimonious
14
+ Requires-Dist: pint
15
+ Requires-Dist: plum-dispatch
16
+ Requires-Dist: pytest
17
+ Requires-Dist: ipdb
18
+ Requires-Dist: requests>=2.31.0
19
+ Requires-Dist: setuptools
20
+ Requires-Dist: twine>=4.0.2
21
+ Dynamic: license-file
22
+
23
+ # Bigraph-Schema
24
+
25
+ [![PyPI version](https://img.shields.io/pypi/v/bigraph-schema.svg)](https://pypi.org/project/bigraph-schema/)
26
+ [![Tutorial](https://img.shields.io/badge/GitHub%20Pages-Tutorial-brightgreen)](https://vivarium-collective.github.io/bigraph-schema/notebooks/demo.html)
27
+
28
+ `bigraph-schema` provides a serializable type schema for compositional and multiscale modeling.
29
+ It defines a compact, extensible language for describing hierarchical data structures — the foundation of the Vivarium 2.0 simulation framework.
30
+
31
+ The library offers a unified interface for representing, validating, and transforming schemas and data in structured simulations.
32
+ By standardizing schema definitions, it enables interoperability between models, extensibility across frameworks, and reproducibility of computational experiments.
33
+
34
+ ---
35
+
36
+ ## Installation
37
+
38
+ Install directly from PyPI:
39
+
40
+ ```console
41
+ pip install bigraph-schema
42
+ ```
43
+ ---
44
+
45
+ ## Getting Started
46
+
47
+ The following resources provide guided introductions and examples:
48
+
49
+ - [Type System Overview](https://vivarium-collective.github.io/bigraph-schema/notebooks/core.html) – Demonstrates how to use the `Core` API for schema inference, normalization, and serialization.
50
+
51
+ - [Bigraph Schema Basics Tutorial](https://vivarium-collective.github.io/bigraph-viz/notebooks/basics.html) – Explains how to compose and visualize schema graphs using the `bigraph-schema` syntax.
52
+
53
+
54
+ ---
55
+
56
+ ## Related Projects
57
+
58
+ - [Vivarium](https://vivarium-collective.github.io/) – a compositional framework for hybrid biological simulations.
59
+ - [Bigraph-Viz](https://vivarium-collective.github.io/bigraph-viz/) – visualization tools for bigraph-schema models.
60
+ - [Process-Bigraph](https://github.com/vivarium-collective/process-bigraph) – process composition and orchestration built on bigraph-schema.
61
+
62
+ ---
63
+
64
+ ## License
65
+
66
+ `bigraph-schema` is open-source software released under the [Apache 2.0 License](https://github.com/vivarium-collective/bigraph-schema/blob/main/LICENSE).
@@ -0,0 +1,28 @@
1
+ bigraph_schema/__init__.py,sha256=Sbrb4L_RpeuJVuGMbmHVk1pTNE4ucM7qbKxBrdp3nd4,3394
2
+ bigraph_schema/core.py,sha256=l-0jkPiJzxejIjJHTgLYymx2iNqayXXhzImRNN_9Yyk,25453
3
+ bigraph_schema/edge.py,sha256=oEwOQg0-mzZ08JElwT8x2cYmW_IXc9BNO5nG0YWYMyA,3421
4
+ bigraph_schema/parse.py,sha256=EByTwL6d0wrDSSO0mfevHKLtxmBPL9IrtLVP2n3lUaY,6246
5
+ bigraph_schema/protocols.py,sha256=rfJ1-yhtfHwcB3yUdKwQDiamXPpoUWztnTzU2-LB74k,1280
6
+ bigraph_schema/schema.py,sha256=NYAWmWTywcVfdW5V_MW_Lb2C8khs-5EoMeXmsBNAcUg,8178
7
+ bigraph_schema/units.py,sha256=PaeqYpCot3eFfZdOUoGqhZjobONpgjc8zxFNNSKg0e4,3020
8
+ bigraph_schema/methods/__init__.py,sha256=RA7YIIQ8EZeCDXFz6Krtz0SggLAZAZCfvI79UmqAQew,790
9
+ bigraph_schema/methods/apply.py,sha256=-R53tfGuMJMciyrBMy7yxiFNy2FzcUeMunzzRRB152Y,6353
10
+ bigraph_schema/methods/check.py,sha256=tj9hj4N_SIjAxq7XMhuPw1K4BxFitHhWWRgi4lefOTk,4231
11
+ bigraph_schema/methods/default.py,sha256=devfn9BnyFjJmpD82FwFcS1bbBA1aak1xroHs6zqTQo,4277
12
+ bigraph_schema/methods/generalize.py,sha256=iYMkr2qxeg7KdWmMWMigldEgH3k9HPJDhNAqr8xkWwE,7879
13
+ bigraph_schema/methods/handle_parameters.py,sha256=hietX6HuasA10SIwQNRqbXirJtT7Zry3Gn71OTJrz_4,4476
14
+ bigraph_schema/methods/infer.py,sha256=d04C7wjIpsObRtjl1jvu7sJoFl1DlH1Ho4_T7HRiSJw,5322
15
+ bigraph_schema/methods/jump.py,sha256=woc7QqtBy23zHOhWFK2CR1YPikS3d_lTvNWIkMiiHiE,10455
16
+ bigraph_schema/methods/merge.py,sha256=2eiR098Ye0NATNIKqNyEnTM7xr0euTBvJFrNIn_KbRQ,10160
17
+ bigraph_schema/methods/realize.py,sha256=MD63Rotc_KDMQCXdwkJA-TOYMkFjOdP8ArfaPLRMuHk,15206
18
+ bigraph_schema/methods/resolve.py,sha256=jHtDpO6EOOzEPRD2VkbGAExSwq7foAVmlPT81zwwhCY,19655
19
+ bigraph_schema/methods/serialize.py,sha256=cOs7b1_c2wTYQk0Fvn_3TjuXupCV3E3ZGQ5ZWopsPOY,12649
20
+ bigraph_schema/methods/validate.py,sha256=XS0bFTQ3GHryI4hLCmQgnI2zdP5yro1wI8rZ45SO_EI,7382
21
+ bigraph_schema/package/__init__.py,sha256=bx_9iGPudkTuaLQOBxQlw-H8NQ8VZSbONNM-BGYJJTI,62
22
+ bigraph_schema/package/discover.py,sha256=dCQBGcsYvpM5FXyU6EHqD8_w68LcRZk0eGZ3EVXCGQU,3599
23
+ bigraph_schema-1.0.0.dist-info/licenses/AUTHORS.md,sha256=1ndh21uie4wWDB9qYiNofzTjITFC_ZSrP_HqFImvcdo,142
24
+ bigraph_schema-1.0.0.dist-info/licenses/LICENSE,sha256=sF9SV-O54AFkJTMLiJaQw0Ngd4QC7nAkzLV3QBIYnC0,11358
25
+ bigraph_schema-1.0.0.dist-info/METADATA,sha256=vKJdFUgYvBElE47USnAcsWCXz_9N3DADmCqf1uaHlf0,2558
26
+ bigraph_schema-1.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
27
+ bigraph_schema-1.0.0.dist-info/top_level.txt,sha256=zce6hZ5UdrtyAiwHWgN62ErKNHqbuEEL6eadbSvLu6g,15
28
+ bigraph_schema-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,6 @@
1
+ # Authors of Bigraph Schema
2
+
3
+ The maintainers of the Bigraph-Schema project are:
4
+
5
+ * Ryan Spangler (@prismofeverything)
6
+ * Eran Agmon (@eagmon)