bigraph-schema 1.3.4__tar.gz → 1.4.2__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.
- {bigraph_schema-1.3.4/bigraph_schema.egg-info → bigraph_schema-1.4.2}/PKG-INFO +1 -2
- {bigraph_schema-1.3.4 → bigraph_schema-1.4.2}/bigraph_schema/__init__.py +0 -10
- {bigraph_schema-1.3.4 → bigraph_schema-1.4.2}/bigraph_schema/core.py +43 -137
- {bigraph_schema-1.3.4 → bigraph_schema-1.4.2}/bigraph_schema/edge.py +21 -0
- {bigraph_schema-1.3.4 → bigraph_schema-1.4.2}/bigraph_schema/methods/bundle.py +94 -2
- {bigraph_schema-1.3.4 → bigraph_schema-1.4.2}/bigraph_schema/methods/merge.py +0 -11
- {bigraph_schema-1.3.4 → bigraph_schema-1.4.2}/bigraph_schema/methods/resolve.py +9 -3
- {bigraph_schema-1.3.4 → bigraph_schema-1.4.2}/bigraph_schema/parse.py +120 -2
- {bigraph_schema-1.3.4 → bigraph_schema-1.4.2/bigraph_schema.egg-info}/PKG-INFO +1 -2
- {bigraph_schema-1.3.4 → bigraph_schema-1.4.2}/bigraph_schema.egg-info/requires.txt +0 -1
- {bigraph_schema-1.3.4 → bigraph_schema-1.4.2}/pyproject.toml +1 -2
- {bigraph_schema-1.3.4 → bigraph_schema-1.4.2}/AUTHORS.md +0 -0
- {bigraph_schema-1.3.4 → bigraph_schema-1.4.2}/LICENSE +0 -0
- {bigraph_schema-1.3.4 → bigraph_schema-1.4.2}/README.md +0 -0
- {bigraph_schema-1.3.4 → bigraph_schema-1.4.2}/bigraph_schema/assembly.py +0 -0
- {bigraph_schema-1.3.4 → bigraph_schema-1.4.2}/bigraph_schema/calculi.py +0 -0
- {bigraph_schema-1.3.4 → bigraph_schema-1.4.2}/bigraph_schema/json_codec.py +0 -0
- {bigraph_schema-1.3.4 → bigraph_schema-1.4.2}/bigraph_schema/methods/__init__.py +0 -0
- {bigraph_schema-1.3.4 → bigraph_schema-1.4.2}/bigraph_schema/methods/apply.py +0 -0
- {bigraph_schema-1.3.4 → bigraph_schema-1.4.2}/bigraph_schema/methods/check.py +0 -0
- {bigraph_schema-1.3.4 → bigraph_schema-1.4.2}/bigraph_schema/methods/coerce.py +0 -0
- {bigraph_schema-1.3.4 → bigraph_schema-1.4.2}/bigraph_schema/methods/default.py +0 -0
- {bigraph_schema-1.3.4 → bigraph_schema-1.4.2}/bigraph_schema/methods/derive.py +0 -0
- {bigraph_schema-1.3.4 → bigraph_schema-1.4.2}/bigraph_schema/methods/diff.py +0 -0
- {bigraph_schema-1.3.4 → bigraph_schema-1.4.2}/bigraph_schema/methods/divide.py +0 -0
- {bigraph_schema-1.3.4 → bigraph_schema-1.4.2}/bigraph_schema/methods/events.py +0 -0
- {bigraph_schema-1.3.4 → bigraph_schema-1.4.2}/bigraph_schema/methods/generalize.py +0 -0
- {bigraph_schema-1.3.4 → bigraph_schema-1.4.2}/bigraph_schema/methods/handle_parameters.py +0 -0
- {bigraph_schema-1.3.4 → bigraph_schema-1.4.2}/bigraph_schema/methods/infer.py +0 -0
- {bigraph_schema-1.3.4 → bigraph_schema-1.4.2}/bigraph_schema/methods/is_empty.py +0 -0
- {bigraph_schema-1.3.4 → bigraph_schema-1.4.2}/bigraph_schema/methods/jump.py +0 -0
- {bigraph_schema-1.3.4 → bigraph_schema-1.4.2}/bigraph_schema/methods/patch.py +0 -0
- {bigraph_schema-1.3.4 → bigraph_schema-1.4.2}/bigraph_schema/methods/realize.py +0 -0
- {bigraph_schema-1.3.4 → bigraph_schema-1.4.2}/bigraph_schema/methods/reconcile.py +0 -0
- {bigraph_schema-1.3.4 → bigraph_schema-1.4.2}/bigraph_schema/methods/select.py +0 -0
- {bigraph_schema-1.3.4 → bigraph_schema-1.4.2}/bigraph_schema/methods/serialize.py +0 -0
- {bigraph_schema-1.3.4 → bigraph_schema-1.4.2}/bigraph_schema/methods/transform.py +0 -0
- {bigraph_schema-1.3.4 → bigraph_schema-1.4.2}/bigraph_schema/methods/validate.py +0 -0
- {bigraph_schema-1.3.4 → bigraph_schema-1.4.2}/bigraph_schema/methods/walk.py +0 -0
- {bigraph_schema-1.3.4 → bigraph_schema-1.4.2}/bigraph_schema/package/__init__.py +0 -0
- {bigraph_schema-1.3.4 → bigraph_schema-1.4.2}/bigraph_schema/package/discover.py +0 -0
- {bigraph_schema-1.3.4 → bigraph_schema-1.4.2}/bigraph_schema/protocols.py +0 -0
- {bigraph_schema-1.3.4 → bigraph_schema-1.4.2}/bigraph_schema/schema.py +0 -0
- {bigraph_schema-1.3.4 → bigraph_schema-1.4.2}/bigraph_schema/units.py +0 -0
- {bigraph_schema-1.3.4 → bigraph_schema-1.4.2}/bigraph_schema.egg-info/SOURCES.txt +0 -0
- {bigraph_schema-1.3.4 → bigraph_schema-1.4.2}/bigraph_schema.egg-info/dependency_links.txt +0 -0
- {bigraph_schema-1.3.4 → bigraph_schema-1.4.2}/bigraph_schema.egg-info/top_level.txt +0 -0
- {bigraph_schema-1.3.4 → bigraph_schema-1.4.2}/setup.cfg +0 -0
- {bigraph_schema-1.3.4 → bigraph_schema-1.4.2}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bigraph-schema
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.2
|
|
4
4
|
Summary: A serializable type schema for compositional systems biology
|
|
5
5
|
Author: Eran Agmon, Ryan Spangler
|
|
6
6
|
Requires-Python: >=3.9
|
|
@@ -9,7 +9,6 @@ License-File: LICENSE
|
|
|
9
9
|
License-File: AUTHORS.md
|
|
10
10
|
Requires-Dist: fire
|
|
11
11
|
Requires-Dist: numpy
|
|
12
|
-
Requires-Dist: orjson
|
|
13
12
|
Requires-Dist: pandas
|
|
14
13
|
Requires-Dist: parsimonious
|
|
15
14
|
Requires-Dist: pint
|
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
# from bigraph_schema.registry import (
|
|
2
|
-
# deep_merge, validate_merge, default, Registry, hierarchy_depth, is_schema_key, establish_path,
|
|
3
|
-
# strip_schema_keys, type_parameter_key, non_schema_keys, set_path, transform_path)
|
|
4
|
-
|
|
5
|
-
# from bigraph_schema.utilities import get_path, visit_method
|
|
6
|
-
|
|
7
|
-
# from bigraph_schema.type_system import TypeSystem
|
|
8
|
-
# from bigraph_schema.type_functions import FUNCTION_TYPE, METHOD_TYPE, type_schema_keys, resolve_path
|
|
9
|
-
|
|
10
|
-
|
|
11
1
|
from bigraph_schema.edge import Edge
|
|
12
2
|
from bigraph_schema.protocols import local_lookup_module
|
|
13
3
|
|
|
@@ -19,23 +19,12 @@ validation, and data transformation.
|
|
|
19
19
|
expressions into structured schema nodes (`Union`, `Tuple`, `Array`, `Link`, etc.).
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
|
-
import sys
|
|
23
22
|
import copy
|
|
24
|
-
import typing
|
|
25
|
-
import inspect
|
|
26
23
|
import collections
|
|
27
24
|
|
|
28
25
|
from pprint import pformat as pf
|
|
29
26
|
|
|
30
|
-
import
|
|
31
|
-
from numpy import dtype
|
|
32
|
-
import numpy.lib.format as nf
|
|
33
|
-
|
|
34
|
-
import logging
|
|
35
|
-
|
|
36
|
-
from plum import dispatch
|
|
37
|
-
from parsimonious.nodes import NodeVisitor
|
|
38
|
-
from dataclasses import dataclass, is_dataclass, replace
|
|
27
|
+
from dataclasses import dataclass, replace
|
|
39
28
|
import importlib.metadata
|
|
40
29
|
|
|
41
30
|
from bigraph_schema.schema import (
|
|
@@ -79,11 +68,10 @@ from bigraph_schema.schema import (
|
|
|
79
68
|
LocalProtocol
|
|
80
69
|
)
|
|
81
70
|
|
|
82
|
-
from bigraph_schema.parse import visit_expression
|
|
71
|
+
from bigraph_schema.parse import visit_expression, CoreVisitor
|
|
83
72
|
from bigraph_schema.edge import Edge
|
|
84
73
|
from bigraph_schema.methods import (
|
|
85
74
|
reify_schema,
|
|
86
|
-
handle_parameters,
|
|
87
75
|
infer,
|
|
88
76
|
default,
|
|
89
77
|
resolve,
|
|
@@ -126,122 +114,6 @@ def append_link_path(schema, link_path):
|
|
|
126
114
|
return schema
|
|
127
115
|
|
|
128
116
|
|
|
129
|
-
class CoreVisitor(NodeVisitor):
|
|
130
|
-
"""Visitor that converts parsed bigraph expressions into schema node structures.
|
|
131
|
-
|
|
132
|
-
Operates within a `Core` context, mapping grammar constructs
|
|
133
|
-
(unions, merges, type parameters, and defaults) into dataclass-based nodes.
|
|
134
|
-
Handles normalization of nested expressions (e.g. `tuple[int,float]`,
|
|
135
|
-
`link[a:int|b:string]`, `(x:y|z:w)`) into instances of `Union`, `Tuple`,
|
|
136
|
-
or structured dicts.
|
|
137
|
-
"""
|
|
138
|
-
|
|
139
|
-
def __init__(self, operation):
|
|
140
|
-
"""Initialize with the active `Core`."""
|
|
141
|
-
self.operation = operation
|
|
142
|
-
|
|
143
|
-
def visit_expression(self, node, visit):
|
|
144
|
-
"""Top-level entry; returns first child."""
|
|
145
|
-
return visit[0]
|
|
146
|
-
|
|
147
|
-
def visit_union(self, node, visit):
|
|
148
|
-
"""Parse `a~b~c` into a `Union(_options=[a,b,c])`."""
|
|
149
|
-
head = [visit[0]]
|
|
150
|
-
tail = [tree['visit'][1] for tree in visit[1]['visit']]
|
|
151
|
-
return Union(_options=head + tail)
|
|
152
|
-
|
|
153
|
-
def visit_merge(self, node, visit):
|
|
154
|
-
"""Parse `a|b|c`; dicts merge to one mapping, others form a `Tuple`."""
|
|
155
|
-
head = [visit[0]]
|
|
156
|
-
tail = [tree['visit'][1] for tree in visit[1]['visit']]
|
|
157
|
-
nodes = head + tail
|
|
158
|
-
if all(isinstance(tree, dict) for tree in nodes):
|
|
159
|
-
merged = {}
|
|
160
|
-
for tree in nodes:
|
|
161
|
-
merged.update(tree)
|
|
162
|
-
return merged
|
|
163
|
-
else:
|
|
164
|
-
try:
|
|
165
|
-
values = tuple([int(x) for x in nodes])
|
|
166
|
-
|
|
167
|
-
return values
|
|
168
|
-
except Exception as e:
|
|
169
|
-
return Tuple(_values=nodes)
|
|
170
|
-
|
|
171
|
-
def visit_tree(self, node, visit):
|
|
172
|
-
"""Delegate directly to nested element."""
|
|
173
|
-
return visit[0]
|
|
174
|
-
|
|
175
|
-
def visit_bigraph(self, node, visit):
|
|
176
|
-
"""Alias for tree; allows recursion within nested bigraphs."""
|
|
177
|
-
return visit[0]
|
|
178
|
-
|
|
179
|
-
def visit_group(self, node, visit):
|
|
180
|
-
"""Handle grouped subexpression `( ... )`; return tuple or dict."""
|
|
181
|
-
group_value = visit[1]
|
|
182
|
-
return group_value if isinstance(group_value, (list, tuple, dict, Tuple)) else (group_value,)
|
|
183
|
-
|
|
184
|
-
def visit_nest(self, node, visit):
|
|
185
|
-
"""Handle `key:subtype` pairs (used in trees/maps).
|
|
186
|
-
|
|
187
|
-
The left side of the colon is a dict key — always a plain string,
|
|
188
|
-
not a resolved type. We use the raw text from the parse node
|
|
189
|
-
rather than the visited (type-resolved) value, since key names
|
|
190
|
-
like ``process`` may collide with registered type names.
|
|
191
|
-
"""
|
|
192
|
-
key = node.children[0].text
|
|
193
|
-
return {key: visit[2]}
|
|
194
|
-
|
|
195
|
-
def visit_type_name(self, node, visit):
|
|
196
|
-
"""Resolve base type, parameters, and defaults into schema nodes."""
|
|
197
|
-
schema = visit[0]
|
|
198
|
-
|
|
199
|
-
# Parse parameter list
|
|
200
|
-
type_parameters = [
|
|
201
|
-
parameter
|
|
202
|
-
for parameter in visit[1]['visit']]
|
|
203
|
-
|
|
204
|
-
if type_parameters:
|
|
205
|
-
schema = handle_parameters(self.operation, schema, type_parameters[0])
|
|
206
|
-
|
|
207
|
-
# Parse default value `{...}`
|
|
208
|
-
default_visit = visit[2]['visit']
|
|
209
|
-
if default_visit:
|
|
210
|
-
default = default_visit[0]
|
|
211
|
-
if isinstance(schema, Node):
|
|
212
|
-
schema._default = default
|
|
213
|
-
elif isinstance(schema, dict):
|
|
214
|
-
schema['_default'] = default
|
|
215
|
-
|
|
216
|
-
return schema
|
|
217
|
-
|
|
218
|
-
def visit_parameter_list(self, node, visit):
|
|
219
|
-
"""Return ordered list of parameters `[A,B,C]`."""
|
|
220
|
-
first = [visit[1]]
|
|
221
|
-
rest = [inner['visit'][1] for inner in visit[2]['visit']]
|
|
222
|
-
return first + rest
|
|
223
|
-
|
|
224
|
-
def visit_default_block(self, node, visit):
|
|
225
|
-
"""Extract contents of `{...}` blocks."""
|
|
226
|
-
return visit[1]
|
|
227
|
-
|
|
228
|
-
def visit_default(self, node, visit):
|
|
229
|
-
"""Return text inside default braces as string."""
|
|
230
|
-
return node.text
|
|
231
|
-
|
|
232
|
-
def visit_symbol(self, node, visit):
|
|
233
|
-
"""Resolve bare symbol names via the operation registry or parse visitor."""
|
|
234
|
-
return self.operation.access(node.text)
|
|
235
|
-
|
|
236
|
-
def visit_nothing(self, node, visit):
|
|
237
|
-
"""Handle empty productions (e.g., trailing commas)."""
|
|
238
|
-
return None
|
|
239
|
-
|
|
240
|
-
def generic_visit(self, node, visit):
|
|
241
|
-
"""Fallback: return raw parse node and visited children."""
|
|
242
|
-
return {'node': node, 'visit': visit}
|
|
243
|
-
|
|
244
|
-
|
|
245
117
|
class Core:
|
|
246
118
|
"""Bigraph-schema operation: registry, parsing, normalization, and ops.
|
|
247
119
|
|
|
@@ -1619,24 +1491,58 @@ class Core:
|
|
|
1619
1491
|
|
|
1620
1492
|
_cached_base_core = None
|
|
1621
1493
|
|
|
1494
|
+
|
|
1495
|
+
def _isolated_copy(base):
|
|
1496
|
+
"""Return a Core that shares nothing mutable with ``base``.
|
|
1497
|
+
|
|
1498
|
+
``copy.copy`` alone is a *shallow* copy: the new object would share
|
|
1499
|
+
the very same ``registry``, ``link_registry``, ``method_registry`` and
|
|
1500
|
+
cache dicts as ``base``, so registering a type on one leaks into all
|
|
1501
|
+
others. Here we shallow-copy the object (preserving cheaply-derived
|
|
1502
|
+
state like ``packages_distributions``) and then give the copy its own
|
|
1503
|
+
registry containers plus fresh, empty per-instance caches.
|
|
1504
|
+
|
|
1505
|
+
The copy depth is deliberately container-level, not deep: registry
|
|
1506
|
+
*entries* are immutable schema nodes/dicts, so duplicating the dicts
|
|
1507
|
+
that hold them is enough to prevent key-level leakage between cores
|
|
1508
|
+
without the cost of deep-copying every schema.
|
|
1509
|
+
"""
|
|
1510
|
+
import copy
|
|
1511
|
+
clone = copy.copy(base)
|
|
1512
|
+
clone.registry = dict(base.registry)
|
|
1513
|
+
clone.link_registry = dict(base.link_registry)
|
|
1514
|
+
clone.method_registry = dict(base.method_registry)
|
|
1515
|
+
# Fresh, empty per-instance caches so warm entries (and the id-keyed
|
|
1516
|
+
# witnesses they hold) never bleed between independently-allocated cores.
|
|
1517
|
+
clone._access_cache = collections.OrderedDict()
|
|
1518
|
+
clone._access_string_cache = {}
|
|
1519
|
+
clone._link_cache = {}
|
|
1520
|
+
clone._resolve_cache = collections.OrderedDict()
|
|
1521
|
+
clone._promote_cache = collections.OrderedDict()
|
|
1522
|
+
# The parse visitor closes over its owning core; rebind it to the clone.
|
|
1523
|
+
clone.parse_visitor = CoreVisitor(clone)
|
|
1524
|
+
return clone
|
|
1525
|
+
|
|
1526
|
+
|
|
1622
1527
|
def allocate_core(top=None):
|
|
1623
1528
|
"""Allocate a new Core with all discovered packages.
|
|
1624
1529
|
|
|
1625
|
-
The base core (without ``top``) is
|
|
1626
|
-
|
|
1627
|
-
|
|
1530
|
+
The base core (without ``top``) is built once and cached to avoid
|
|
1531
|
+
repeated expensive package discovery. Each call returns an *isolated*
|
|
1532
|
+
copy: it shares no mutable state with any other allocated core, so
|
|
1533
|
+
types/links/methods registered on one core never leak into another,
|
|
1534
|
+
while the base types from discovery are present on every copy. See
|
|
1535
|
+
:func:`_isolated_copy` for the (container-level) copy semantics.
|
|
1628
1536
|
"""
|
|
1629
1537
|
global _cached_base_core
|
|
1630
1538
|
if top is None and _cached_base_core is not None:
|
|
1631
|
-
|
|
1632
|
-
return copy.copy(_cached_base_core)
|
|
1539
|
+
return _isolated_copy(_cached_base_core)
|
|
1633
1540
|
|
|
1634
1541
|
core = Core(BASE_TYPES)
|
|
1635
1542
|
core = discover_packages(core, top)
|
|
1636
1543
|
|
|
1637
1544
|
if top is None:
|
|
1638
1545
|
_cached_base_core = core
|
|
1639
|
-
|
|
1640
|
-
return copy.copy(core)
|
|
1546
|
+
return _isolated_copy(core)
|
|
1641
1547
|
|
|
1642
1548
|
return core
|
|
@@ -6,6 +6,9 @@ Edge
|
|
|
6
6
|
Base class for all edges in the bigraph schema.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
+
import inspect
|
|
10
|
+
|
|
11
|
+
|
|
9
12
|
def default_wires(schema):
|
|
10
13
|
"""
|
|
11
14
|
Create default wiring for a schema by connecting each port to a store of the same name.
|
|
@@ -28,6 +31,12 @@ class Edge:
|
|
|
28
31
|
|
|
29
32
|
config_schema = {}
|
|
30
33
|
|
|
34
|
+
# Canonical formal description of this edge — a string (markdown / LaTeX) that
|
|
35
|
+
# states what the process/step computes (governing equations, assumptions).
|
|
36
|
+
# Override on subclasses. Tooling reads it via `describe()`, which falls back
|
|
37
|
+
# to the class docstring when this is left empty.
|
|
38
|
+
description = ''
|
|
39
|
+
|
|
31
40
|
def __init__(self, config=None, core=None):
|
|
32
41
|
"""
|
|
33
42
|
Initialize the edge with a config and simulation core.
|
|
@@ -135,4 +144,16 @@ class Edge:
|
|
|
135
144
|
'outputs': self.outputs()
|
|
136
145
|
}
|
|
137
146
|
|
|
147
|
+
def describe(self):
|
|
148
|
+
"""
|
|
149
|
+
Return this edge's formal description.
|
|
150
|
+
|
|
151
|
+
Returns the ``description`` class attribute — the canonical formal
|
|
152
|
+
(mathematical) description of what this process/step computes. Falls
|
|
153
|
+
back to the class docstring when ``description`` is unset, so existing
|
|
154
|
+
edges still surface something. Subclasses should set ``description`` to
|
|
155
|
+
a string (markdown / LaTeX) for a precise, standardized description.
|
|
156
|
+
"""
|
|
157
|
+
return self.description or inspect.getdoc(type(self)) or ''
|
|
158
|
+
|
|
138
159
|
|
|
@@ -74,6 +74,100 @@ REF_KEY = '$bundle_ref'
|
|
|
74
74
|
MIN_ARRAY_BYTES = 10_000 # ~10 KB threshold for externalizing
|
|
75
75
|
|
|
76
76
|
|
|
77
|
+
# ---------------------------------------------------------------------------
|
|
78
|
+
# Parquet array I/O
|
|
79
|
+
# ---------------------------------------------------------------------------
|
|
80
|
+
# The bundle WRITE side lives here because bigraph-schema owns the bundle
|
|
81
|
+
# format (``BundleContext`` + the dispatched ``bundle`` method). The matching
|
|
82
|
+
# READ side, ``_load_array_parquet`` / ``resolve_refs`` / ``load_bundle``, lives
|
|
83
|
+
# in ``process_bigraph.bundle`` (the layer that orchestrates loading). The two
|
|
84
|
+
# are a format pair — keep their column/metadata layout in sync. pyarrow is
|
|
85
|
+
# imported lazily so bigraph-schema does not gain a hard pyarrow dependency
|
|
86
|
+
# (bundling is opt-in).
|
|
87
|
+
|
|
88
|
+
def _save_array_parquet(arr: "np.ndarray", filepath: str) -> None:
|
|
89
|
+
"""Save a numpy array to a Parquet file.
|
|
90
|
+
|
|
91
|
+
Strategy:
|
|
92
|
+
- 1D/2D numeric arrays: store as columnar Parquet (one column per
|
|
93
|
+
array column, or a single column for 1D).
|
|
94
|
+
- Higher-dimensional or structured arrays: store as a binary blob
|
|
95
|
+
with shape/dtype metadata.
|
|
96
|
+
|
|
97
|
+
Counterpart reader: ``process_bigraph.bundle._load_array_parquet``.
|
|
98
|
+
"""
|
|
99
|
+
import pyarrow as pa
|
|
100
|
+
import pyarrow.parquet as pq
|
|
101
|
+
|
|
102
|
+
if arr.dtype.names:
|
|
103
|
+
# Structured array — each field becomes one or more Parquet columns.
|
|
104
|
+
# Sub-array fields (e.g. shape (N, 2)) are flattened into separate
|
|
105
|
+
# columns: field.0, field.1, etc.
|
|
106
|
+
columns = {}
|
|
107
|
+
subarray_meta = {} # field_name -> {'shape': [...], 'dtype': '...'}
|
|
108
|
+
|
|
109
|
+
for name in arr.dtype.names:
|
|
110
|
+
field_data = arr[name]
|
|
111
|
+
if field_data.dtype.kind == 'U':
|
|
112
|
+
columns[name] = pa.array(field_data.tolist(), type=pa.string())
|
|
113
|
+
elif field_data.ndim > 1:
|
|
114
|
+
# Sub-array field: flatten into N columns
|
|
115
|
+
sub_shape = field_data.shape[1:]
|
|
116
|
+
sub_count = int(np.prod(sub_shape))
|
|
117
|
+
flat = field_data.reshape(len(field_data), sub_count)
|
|
118
|
+
for i in range(sub_count):
|
|
119
|
+
columns[f'{name}.{i}'] = pa.array(flat[:, i])
|
|
120
|
+
subarray_meta[name] = {
|
|
121
|
+
'shape': list(sub_shape),
|
|
122
|
+
'dtype': str(field_data.dtype.base),
|
|
123
|
+
'count': sub_count,
|
|
124
|
+
}
|
|
125
|
+
else:
|
|
126
|
+
columns[name] = pa.array(field_data)
|
|
127
|
+
|
|
128
|
+
table = pa.table(columns)
|
|
129
|
+
|
|
130
|
+
# Store the original dtype string and sub-array info in file metadata
|
|
131
|
+
file_meta = {
|
|
132
|
+
'dtype': str(arr.dtype),
|
|
133
|
+
'subarray_fields': subarray_meta,
|
|
134
|
+
}
|
|
135
|
+
schema_meta = table.schema.metadata or {}
|
|
136
|
+
schema_meta[b'bundle_structured'] = json.dumps(file_meta).encode()
|
|
137
|
+
table = table.replace_schema_metadata(schema_meta)
|
|
138
|
+
|
|
139
|
+
pq.write_table(table, filepath, compression='zstd')
|
|
140
|
+
|
|
141
|
+
elif arr.ndim == 1 or (arr.ndim == 2 and arr.shape[1] <= 500):
|
|
142
|
+
# Dense 1D or moderate 2D array — columnar storage.
|
|
143
|
+
if arr.ndim == 1:
|
|
144
|
+
table = pa.table({'c0': pa.array(arr)})
|
|
145
|
+
else:
|
|
146
|
+
columns = {
|
|
147
|
+
f'c{i}': pa.array(arr[:, i])
|
|
148
|
+
for i in range(arr.shape[1])
|
|
149
|
+
}
|
|
150
|
+
table = pa.table(columns)
|
|
151
|
+
|
|
152
|
+
# Store original dtype in metadata
|
|
153
|
+
meta = {b'dtype': str(arr.dtype).encode(),
|
|
154
|
+
b'shape': json.dumps(list(arr.shape)).encode()}
|
|
155
|
+
table = table.replace_schema_metadata(meta)
|
|
156
|
+
pq.write_table(table, filepath, compression='zstd')
|
|
157
|
+
|
|
158
|
+
else:
|
|
159
|
+
# Wide 2D (>500 cols) or 3D+ — binary blob with compression.
|
|
160
|
+
# Storing as raw bytes in a single Parquet row with zstd
|
|
161
|
+
# compression. This is still very efficient: a 50MB int8 array
|
|
162
|
+
# compresses to ~15MB.
|
|
163
|
+
table = pa.table({
|
|
164
|
+
'data': [arr.tobytes()],
|
|
165
|
+
'shape': [json.dumps(list(arr.shape))],
|
|
166
|
+
'dtype': [str(arr.dtype)],
|
|
167
|
+
})
|
|
168
|
+
pq.write_table(table, filepath, compression='zstd')
|
|
169
|
+
|
|
170
|
+
|
|
77
171
|
@dataclass
|
|
78
172
|
class BundleContext:
|
|
79
173
|
"""Accumulates array files during a bundle operation.
|
|
@@ -93,8 +187,6 @@ class BundleContext:
|
|
|
93
187
|
If an array with identical content has already been saved, the
|
|
94
188
|
existing file is reused (content-addressed deduplication).
|
|
95
189
|
"""
|
|
96
|
-
from process_bigraph.bundle import _save_array_parquet
|
|
97
|
-
|
|
98
190
|
os.makedirs(self.arrays_dir, exist_ok=True)
|
|
99
191
|
|
|
100
192
|
content_id = hashlib.sha256(
|
|
@@ -358,17 +358,6 @@ def _value_is_empty(value):
|
|
|
358
358
|
return not value
|
|
359
359
|
|
|
360
360
|
|
|
361
|
-
def tuplify_dict(d):
|
|
362
|
-
if isinstance(d, dict):
|
|
363
|
-
tulip = []
|
|
364
|
-
for key, value in d.items():
|
|
365
|
-
tulip.append((
|
|
366
|
-
key,
|
|
367
|
-
tuplify_dict(value)))
|
|
368
|
-
else:
|
|
369
|
-
return d
|
|
370
|
-
|
|
371
|
-
|
|
372
361
|
@dispatch
|
|
373
362
|
def merge(schema: dict, current, update, path=()):
|
|
374
363
|
if path:
|
|
@@ -189,7 +189,15 @@ def resolve(current: Wrap, update: Wrap, path=None):
|
|
|
189
189
|
return schema
|
|
190
190
|
else:
|
|
191
191
|
# TODO: resolve wrappings somehow?
|
|
192
|
-
|
|
192
|
+
import traceback as _tb
|
|
193
|
+
_frames = ''.join(_tb.format_stack(limit=12)[:-1])
|
|
194
|
+
raise Exception(
|
|
195
|
+
f'cannot resolve two different wrappings at path={path}:\n'
|
|
196
|
+
f' current ({type(current).__name__}, _default={getattr(current, "_default", "?")}, '
|
|
197
|
+
f'_value type={type(getattr(current, "_value", None)).__name__})\n'
|
|
198
|
+
f' update ({type(update).__name__}, _default={getattr(update, "_default", "?")}, '
|
|
199
|
+
f'_value type={type(getattr(update, "_value", None)).__name__})\n'
|
|
200
|
+
f' call stack:\n{_frames}')
|
|
193
201
|
|
|
194
202
|
|
|
195
203
|
@dispatch
|
|
@@ -661,7 +669,6 @@ def resolve(current: Array, update: Node, path=None):
|
|
|
661
669
|
# @dispatch
|
|
662
670
|
# def resolve(current: Node, update: Array, path=None):
|
|
663
671
|
# if path:
|
|
664
|
-
# import ipdb; ipdb.set_trace()
|
|
665
672
|
|
|
666
673
|
# return resolve_array_path(update, current, path=path)
|
|
667
674
|
|
|
@@ -840,7 +847,6 @@ def resolve(current: String, update: Node, path=None):
|
|
|
840
847
|
|
|
841
848
|
# @dispatch
|
|
842
849
|
# def resolve(current: Node, update: String):
|
|
843
|
-
# # import ipdb; ipdb.set_trace()
|
|
844
850
|
# if update._default:
|
|
845
851
|
# current = replace(current, **{'_default': update._default})
|
|
846
852
|
# return current
|
|
@@ -9,6 +9,8 @@ such as parameterized types, nested trees, merges (`|`), and unions (`~`).
|
|
|
9
9
|
|
|
10
10
|
from parsimonious.grammar import Grammar
|
|
11
11
|
from parsimonious.nodes import NodeVisitor
|
|
12
|
+
from bigraph_schema.schema import Union, Tuple, Node
|
|
13
|
+
from bigraph_schema.methods import handle_parameters
|
|
12
14
|
|
|
13
15
|
# --- Example Expressions -----------------------------------------------------
|
|
14
16
|
parameter_examples = {
|
|
@@ -122,6 +124,122 @@ class ParameterVisitor(NodeVisitor):
|
|
|
122
124
|
return {'node': node, 'visit': visit}
|
|
123
125
|
|
|
124
126
|
# --- API ---------------------------------------------------------------------
|
|
127
|
+
class CoreVisitor(NodeVisitor):
|
|
128
|
+
"""Visitor that converts parsed bigraph expressions into schema node structures.
|
|
129
|
+
|
|
130
|
+
Operates within a `Core` context, mapping grammar constructs
|
|
131
|
+
(unions, merges, type parameters, and defaults) into dataclass-based nodes.
|
|
132
|
+
Handles normalization of nested expressions (e.g. `tuple[int,float]`,
|
|
133
|
+
`link[a:int|b:string]`, `(x:y|z:w)`) into instances of `Union`, `Tuple`,
|
|
134
|
+
or structured dicts.
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
def __init__(self, operation):
|
|
138
|
+
"""Initialize with the active `Core`."""
|
|
139
|
+
self.operation = operation
|
|
140
|
+
|
|
141
|
+
def visit_expression(self, node, visit):
|
|
142
|
+
"""Top-level entry; returns first child."""
|
|
143
|
+
return visit[0]
|
|
144
|
+
|
|
145
|
+
def visit_union(self, node, visit):
|
|
146
|
+
"""Parse `a~b~c` into a `Union(_options=[a,b,c])`."""
|
|
147
|
+
head = [visit[0]]
|
|
148
|
+
tail = [tree['visit'][1] for tree in visit[1]['visit']]
|
|
149
|
+
return Union(_options=head + tail)
|
|
150
|
+
|
|
151
|
+
def visit_merge(self, node, visit):
|
|
152
|
+
"""Parse `a|b|c`; dicts merge to one mapping, others form a `Tuple`."""
|
|
153
|
+
head = [visit[0]]
|
|
154
|
+
tail = [tree['visit'][1] for tree in visit[1]['visit']]
|
|
155
|
+
nodes = head + tail
|
|
156
|
+
if all(isinstance(tree, dict) for tree in nodes):
|
|
157
|
+
merged = {}
|
|
158
|
+
for tree in nodes:
|
|
159
|
+
merged.update(tree)
|
|
160
|
+
return merged
|
|
161
|
+
else:
|
|
162
|
+
try:
|
|
163
|
+
values = tuple([int(x) for x in nodes])
|
|
164
|
+
|
|
165
|
+
return values
|
|
166
|
+
except Exception as e:
|
|
167
|
+
return Tuple(_values=nodes)
|
|
168
|
+
|
|
169
|
+
def visit_tree(self, node, visit):
|
|
170
|
+
"""Delegate directly to nested element."""
|
|
171
|
+
return visit[0]
|
|
172
|
+
|
|
173
|
+
def visit_bigraph(self, node, visit):
|
|
174
|
+
"""Alias for tree; allows recursion within nested bigraphs."""
|
|
175
|
+
return visit[0]
|
|
176
|
+
|
|
177
|
+
def visit_group(self, node, visit):
|
|
178
|
+
"""Handle grouped subexpression `( ... )`; return tuple or dict."""
|
|
179
|
+
group_value = visit[1]
|
|
180
|
+
return group_value if isinstance(group_value, (list, tuple, dict, Tuple)) else (group_value,)
|
|
181
|
+
|
|
182
|
+
def visit_nest(self, node, visit):
|
|
183
|
+
"""Handle `key:subtype` pairs (used in trees/maps).
|
|
184
|
+
|
|
185
|
+
The left side of the colon is a dict key — always a plain string,
|
|
186
|
+
not a resolved type. We use the raw text from the parse node
|
|
187
|
+
rather than the visited (type-resolved) value, since key names
|
|
188
|
+
like ``process`` may collide with registered type names.
|
|
189
|
+
"""
|
|
190
|
+
key = node.children[0].text
|
|
191
|
+
return {key: visit[2]}
|
|
192
|
+
|
|
193
|
+
def visit_type_name(self, node, visit):
|
|
194
|
+
"""Resolve base type, parameters, and defaults into schema nodes."""
|
|
195
|
+
schema = visit[0]
|
|
196
|
+
|
|
197
|
+
# Parse parameter list
|
|
198
|
+
type_parameters = [
|
|
199
|
+
parameter
|
|
200
|
+
for parameter in visit[1]['visit']]
|
|
201
|
+
|
|
202
|
+
if type_parameters:
|
|
203
|
+
schema = handle_parameters(self.operation, schema, type_parameters[0])
|
|
204
|
+
|
|
205
|
+
# Parse default value `{...}`
|
|
206
|
+
default_visit = visit[2]['visit']
|
|
207
|
+
if default_visit:
|
|
208
|
+
default = default_visit[0]
|
|
209
|
+
if isinstance(schema, Node):
|
|
210
|
+
schema._default = default
|
|
211
|
+
elif isinstance(schema, dict):
|
|
212
|
+
schema['_default'] = default
|
|
213
|
+
|
|
214
|
+
return schema
|
|
215
|
+
|
|
216
|
+
def visit_parameter_list(self, node, visit):
|
|
217
|
+
"""Return ordered list of parameters `[A,B,C]`."""
|
|
218
|
+
first = [visit[1]]
|
|
219
|
+
rest = [inner['visit'][1] for inner in visit[2]['visit']]
|
|
220
|
+
return first + rest
|
|
221
|
+
|
|
222
|
+
def visit_default_block(self, node, visit):
|
|
223
|
+
"""Extract contents of `{...}` blocks."""
|
|
224
|
+
return visit[1]
|
|
225
|
+
|
|
226
|
+
def visit_default(self, node, visit):
|
|
227
|
+
"""Return text inside default braces as string."""
|
|
228
|
+
return node.text
|
|
229
|
+
|
|
230
|
+
def visit_symbol(self, node, visit):
|
|
231
|
+
"""Resolve bare symbol names via the operation registry or parse visitor."""
|
|
232
|
+
return self.operation.access(node.text)
|
|
233
|
+
|
|
234
|
+
def visit_nothing(self, node, visit):
|
|
235
|
+
"""Handle empty productions (e.g., trailing commas)."""
|
|
236
|
+
return None
|
|
237
|
+
|
|
238
|
+
def generic_visit(self, node, visit):
|
|
239
|
+
"""Fallback: return raw parse node and visited children."""
|
|
240
|
+
return {'node': node, 'visit': visit}
|
|
241
|
+
|
|
242
|
+
|
|
125
243
|
def parsed_leftmost_leaf(parsed):
|
|
126
244
|
if len(parsed.children) == 0:
|
|
127
245
|
return parsed
|
|
@@ -170,7 +288,7 @@ def render_expression(expression):
|
|
|
170
288
|
return str(expression) # fallback for unknown structures
|
|
171
289
|
|
|
172
290
|
# --- Debugging/Testing ---------------------------------------------------------
|
|
173
|
-
def
|
|
291
|
+
def _demo_parse_parameters():
|
|
174
292
|
"""Test parsing and rendering for all example expressions."""
|
|
175
293
|
for label, expr in parameter_examples.items():
|
|
176
294
|
parsed = parse_expression(expr)
|
|
@@ -180,4 +298,4 @@ def test_parse_parameters():
|
|
|
180
298
|
print(f' Rendered: {render_expression(parsed)}')
|
|
181
299
|
|
|
182
300
|
if __name__ == '__main__':
|
|
183
|
-
|
|
301
|
+
_demo_parse_parameters()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bigraph-schema
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.2
|
|
4
4
|
Summary: A serializable type schema for compositional systems biology
|
|
5
5
|
Author: Eran Agmon, Ryan Spangler
|
|
6
6
|
Requires-Python: >=3.9
|
|
@@ -9,7 +9,6 @@ License-File: LICENSE
|
|
|
9
9
|
License-File: AUTHORS.md
|
|
10
10
|
Requires-Dist: fire
|
|
11
11
|
Requires-Dist: numpy
|
|
12
|
-
Requires-Dist: orjson
|
|
13
12
|
Requires-Dist: pandas
|
|
14
13
|
Requires-Dist: parsimonious
|
|
15
14
|
Requires-Dist: pint
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "bigraph-schema"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.4.2"
|
|
8
8
|
description = "A serializable type schema for compositional systems biology"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -15,7 +15,6 @@ authors = [
|
|
|
15
15
|
dependencies = [
|
|
16
16
|
"fire",
|
|
17
17
|
"numpy",
|
|
18
|
-
"orjson",
|
|
19
18
|
"pandas",
|
|
20
19
|
"parsimonious",
|
|
21
20
|
"pint",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|