bigraph-schema 0.0.71__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.

Potentially problematic release.


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

@@ -0,0 +1,8 @@
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
+ from bigraph_schema.utilities import get_path, visit_method
5
+ from bigraph_schema.edge import Edge
6
+ from bigraph_schema.type_system import TypeSystem
7
+ from bigraph_schema.protocols import local_lookup_module
8
+ from bigraph_schema.type_functions import FUNCTION_TYPE, METHOD_TYPE, type_schema_keys, resolve_path
bigraph_schema/edge.py ADDED
@@ -0,0 +1,129 @@
1
+ """
2
+ ====
3
+ Edge
4
+ ====
5
+
6
+ Base class for all edges in the bigraph schema.
7
+ """
8
+
9
+ def default_wires(schema):
10
+ """
11
+ Create default wiring for a schema by connecting each port to a store of the same name.
12
+ """
13
+ return {
14
+ key: [key]
15
+ for key in schema}
16
+
17
+
18
+ class Edge:
19
+ """
20
+ Base class for all computational edges in the bigraph schema.
21
+
22
+ Edges define the interface between simulation processes and the global state,
23
+ specifying the structure of input and output ports using bigraph types
24
+ (e.g., 'float', 'map[float]', 'list[integer]', etc.).
25
+
26
+ Upon instantiation, each edge registers its port types with the core.
27
+ """
28
+
29
+ config_schema = {}
30
+
31
+ def __init__(self, config=None, core=None):
32
+ """
33
+ Initialize the edge with a config and simulation core.
34
+
35
+ Args:
36
+ config (dict): Optional configuration dictionary. Defaults to empty dict.
37
+ core: The core simulation engine that manages this edge and its types.
38
+
39
+ Raises:
40
+ Exception: If `core` is not provided.
41
+ """
42
+ if core is None:
43
+ raise Exception('must provide a core')
44
+ self.core = core
45
+
46
+ if config is None:
47
+ config = {}
48
+
49
+ self._config = self.core.fill(self.config_schema, config)
50
+ self._composition = 'edge'
51
+ self._state = {}
52
+
53
+ self.initialize(self._config)
54
+
55
+ @property
56
+ def config(self):
57
+ return self._config
58
+
59
+ @config.setter
60
+ def config(self, config):
61
+ self._config = config
62
+
63
+ @property
64
+ def composition(self):
65
+ return self._composition
66
+
67
+ @composition.setter
68
+ def composition(self, composition):
69
+ self._composition = composition
70
+
71
+ @property
72
+ def state(self):
73
+ return self._state
74
+
75
+ @state.setter
76
+ def state(self, state):
77
+ self._state = state
78
+
79
+ def initialize(self, config):
80
+ """Optional hook for subclass-specific initialization."""
81
+ pass
82
+
83
+ def initial_state(self):
84
+ """Return initial state values, if applicable."""
85
+ return {}
86
+
87
+ @staticmethod
88
+ def generate_state(config=None):
89
+ """Generate static initial state for user configuration or inspection."""
90
+ return {}
91
+
92
+ def inputs(self):
93
+ """
94
+ Return a dictionary mapping input port names to bigraph types.
95
+
96
+ Example:
97
+ {'glucose': 'float', 'biomass': 'map[float]'}
98
+ """
99
+ return {}
100
+
101
+ def outputs(self):
102
+ """
103
+ Return a dictionary mapping output port names to bigraph types.
104
+
105
+ Example:
106
+ {'growth_rate': 'float'}
107
+ """
108
+ return {}
109
+
110
+ def default_inputs(self):
111
+ """Generate default wire paths for inputs: {port: [port]}"""
112
+ return default_wires(self.inputs())
113
+
114
+ def default_outputs(self):
115
+ """Generate default wire paths for outputs: {port: [port]}"""
116
+ return default_wires(self.outputs())
117
+
118
+ def interface(self):
119
+ """
120
+ Return combined interface schema as a dict:
121
+ {
122
+ 'inputs': {port: type, ...},
123
+ 'outputs': {port: type, ...}
124
+ }
125
+ """
126
+ return {
127
+ 'inputs': self.inputs(),
128
+ 'outputs': self.outputs()
129
+ }
@@ -0,0 +1,165 @@
1
+ """
2
+ ======================
3
+ Parse Bigraph Notation
4
+ ======================
5
+
6
+ Parses and renders bigraph-style type expressions used in schema definitions,
7
+ such as parameterized types, nested trees, merges (`|`), and unions (`~`).
8
+ """
9
+
10
+ from parsimonious.grammar import Grammar
11
+ from parsimonious.nodes import NodeVisitor
12
+
13
+ # --- Example Expressions -----------------------------------------------------
14
+ parameter_examples = {
15
+ 'no-parameters': 'simple',
16
+ 'one-parameter': 'parameterized[A]',
17
+ 'three-parameters': 'parameterized[A,B,C]',
18
+ 'nested-parameters': 'nested[outer[inner]]',
19
+ 'multiple-nested-parameters': 'nested[outer[inner],other,later[on,there[is],more]]',
20
+ 'bars': 'a|b|c|zzzz',
21
+ 'union': 'a~b~c~zzzz',
22
+ 'typed': 'a:field[yellow,tree,snake]|b:(x:earth|y:cloud|z:sky)',
23
+ 'typed_parameters': 'edge[a:int|b:(x:length|y:float),v[zz:float|xx:what]]',
24
+ 'inputs_and_outputs': 'edge[input1:float|input2:int,output1:float|output2:int]',
25
+ 'tuple': 'what[is,happening|(with:yellow|this:green)|this:now]',
26
+ 'single': 'hello[(3),over]',
27
+ 'double': 'hello[(3|4),over]',
28
+ 'units_type': 'length^2*mass/time^1_5',
29
+ 'nothing': '()'}
30
+
31
+ # --- Grammar -----------------------------------------------------------------
32
+ parameter_grammar = Grammar(
33
+ """
34
+ expression = merge / union / tree / nothing
35
+ merge = tree (bar tree)+
36
+ union = tree (tilde tree)+
37
+ tree = bigraph / type_name
38
+ bigraph = group / nest
39
+ group = paren_left expression paren_right
40
+ nest = symbol colon tree
41
+ type_name = symbol parameter_list?
42
+ parameter_list = square_left expression (comma expression)* square_right
43
+ symbol = ~r"[\\w\\d-_/*&^%$#@!`+ ]+"
44
+ dot = "."
45
+ colon = ":"
46
+ bar = "|"
47
+ paren_left = "("
48
+ paren_right = ")"
49
+ square_left = "["
50
+ square_right = "]"
51
+ comma = ","
52
+ tilde = "~"
53
+ not_newline = ~r"[^\\n\\r]"*
54
+ newline = ~"[\\n\\r]+"
55
+ ws = ~r"\\s*"
56
+ nothing = ""
57
+ """)
58
+
59
+
60
+ # --- Visitor -----------------------------------------------------------------
61
+ class ParameterVisitor(NodeVisitor):
62
+ """Visitor that walks a parsed tree and builds structured type expressions."""
63
+
64
+ def visit_expression(self, node, visit):
65
+ return visit[0]
66
+
67
+ def visit_union(self, node, visit):
68
+ head = [visit[0]]
69
+ tail = [tree['visit'][1] for tree in visit[1]['visit']]
70
+ return {'_union': head + tail}
71
+
72
+ def visit_merge(self, node, visit):
73
+ head = [visit[0]]
74
+ tail = [tree['visit'][1] for tree in visit[1]['visit']]
75
+ nodes = head + tail
76
+
77
+ if all(isinstance(tree, dict) for tree in nodes):
78
+ merged = {}
79
+ for tree in nodes:
80
+ merged.update(tree)
81
+ return merged
82
+ else:
83
+ return tuple(nodes)
84
+
85
+ def visit_tree(self, node, visit):
86
+ return visit[0]
87
+
88
+ def visit_bigraph(self, node, visit):
89
+ return visit[0]
90
+
91
+ def visit_group(self, node, visit):
92
+ group_value = visit[1]
93
+ return group_value if isinstance(group_value, (list, tuple, dict)) else (group_value,)
94
+
95
+ def visit_nest(self, node, visit):
96
+ return {visit[0]: visit[2]}
97
+
98
+ def visit_type_name(self, node, visit):
99
+ type_name = visit[0]
100
+ type_parameters = visit[1]['visit']
101
+ if type_parameters:
102
+ return [type_name, type_parameters[0]]
103
+ return type_name
104
+
105
+ def visit_parameter_list(self, node, visit):
106
+ first = [visit[1]]
107
+ rest = [inner['visit'][1] for inner in visit[2]['visit']]
108
+ return first + rest
109
+
110
+ def visit_symbol(self, node, visit):
111
+ return node.text
112
+
113
+ def visit_nothing(self, node, visit):
114
+ return {}
115
+
116
+ def generic_visit(self, node, visit):
117
+ return {'node': node, 'visit': visit}
118
+
119
+ # --- API ---------------------------------------------------------------------
120
+ def parse_expression(expression):
121
+ """
122
+ Parse a bigraph-style type expression into a structured Python object.
123
+ """
124
+ parsed = parameter_grammar.parse(expression)
125
+ visitor = ParameterVisitor()
126
+ return visitor.visit(parsed)
127
+
128
+ def is_type_expression(expression):
129
+ """
130
+ Return True if the expression is a parameterized type list.
131
+ """
132
+ return isinstance(expression, list) and len(expression) == 2 and isinstance(expression[1], list)
133
+
134
+ def render_expression(expression):
135
+ """
136
+ Render a structured type expression back into a bigraph-style string.
137
+ """
138
+ if isinstance(expression, str):
139
+ return expression
140
+ elif isinstance(expression, list):
141
+ type_name, parameters = expression
142
+ inner = ','.join(render_expression(p) for p in parameters)
143
+ return f'{type_name}[{inner}]'
144
+ elif isinstance(expression, tuple):
145
+ return '(' + '|'.join(render_expression(e) for e in expression) + ')'
146
+ elif isinstance(expression, dict):
147
+ if '_union' in expression:
148
+ return '~'.join(render_expression(p) for p in expression['_union'])
149
+ else:
150
+ parts = [f'{k}:{render_expression(v)}' for k, v in expression.items()]
151
+ return f'({ "|".join(parts) })'
152
+ return str(expression) # fallback for unknown structures
153
+
154
+ # --- Debugging/Testing ---------------------------------------------------------
155
+ def test_parse_parameters():
156
+ """Test parsing and rendering for all example expressions."""
157
+ for label, expr in parameter_examples.items():
158
+ parsed = parse_expression(expr)
159
+ print(f'{label}: {expr}')
160
+ if parsed:
161
+ print(f' Parsed: {parsed}')
162
+ print(f' Rendered: {render_expression(parsed)}')
163
+
164
+ if __name__ == '__main__':
165
+ test_parse_parameters()
@@ -0,0 +1,35 @@
1
+ """
2
+ =========
3
+ Protocols
4
+ =========
5
+
6
+ This module contains the protocols for retrieving processes from address.
7
+ """
8
+
9
+ import sys
10
+ import inspect
11
+ import importlib
12
+
13
+
14
+ def function_module(function):
15
+ """
16
+ Retrieves the fully qualified name of a given function.
17
+ """
18
+ module = inspect.getmodule(function)
19
+
20
+ return f'{module.__name__}.{function.__name__}'
21
+
22
+
23
+ def local_lookup_module(address):
24
+ """Local Module Protocol
25
+
26
+ Retrieves local module
27
+ """
28
+ if '.' in address:
29
+ module_name, class_name = address.rsplit('.', 1)
30
+ module = importlib.import_module(module_name)
31
+ return getattr(module, class_name)
32
+ else:
33
+ module = sys.modules[__name__]
34
+ if hasattr(module, address):
35
+ return getattr(sys.modules[__name__], address)
@@ -0,0 +1,287 @@
1
+ """
2
+ ========
3
+ Registry
4
+ ========
5
+
6
+ Utilities for managing registry entries, hierarchical state structures,
7
+ deep merging, and schema cleaning. Includes the `Registry` class for storing
8
+ type declarations or functions by key.
9
+ """
10
+
11
+ import inspect
12
+ import copy
13
+ import collections
14
+ import traceback
15
+ import functools
16
+ import numpy as np
17
+ import pytest
18
+ from pprint import pformat as pf
19
+
20
+ from bigraph_schema.protocols import local_lookup_module, function_module
21
+
22
+ # --- Merge Utilities ---------------------------------------------------------
23
+
24
+ def deep_merge_copy(dct, merge_dct):
25
+ """Return a deep copy of `dct` with `merge_dct` deeply merged into it."""
26
+ return deep_merge(copy.deepcopy(dct), merge_dct)
27
+
28
+ def deep_merge(dct, merge_dct):
29
+ """
30
+ Deep merge `merge_dct` into `dct`, modifying `dct` in-place.
31
+
32
+ Nested dictionaries are recursively merged.
33
+ """
34
+ if dct is None:
35
+ dct = {}
36
+ if merge_dct is None:
37
+ merge_dct = {}
38
+ if not isinstance(merge_dct, dict):
39
+ return merge_dct
40
+
41
+ for k, v in merge_dct.items():
42
+ if (k in dct and isinstance(dct[k], dict)
43
+ and isinstance(v, collections.abc.Mapping)):
44
+ deep_merge(dct[k], v)
45
+ else:
46
+ dct[k] = v
47
+ return dct
48
+
49
+ def validate_merge(state, dct, merge_dct):
50
+ """
51
+ Like `deep_merge`, but raises an exception if values conflict and are not in `state`.
52
+ """
53
+ dct = dct or {}
54
+ merge_dct = merge_dct or {}
55
+ state = state or {}
56
+
57
+ for k, v in merge_dct.items():
58
+ if (k in dct and isinstance(dct[k], dict)
59
+ and isinstance(v, collections.abc.Mapping)):
60
+ if k not in state:
61
+ state[k] = {}
62
+ validate_merge(state[k], dct[k], v)
63
+ else:
64
+ if k in state:
65
+ dct[k] = state[k]
66
+ elif k in dct and dct[k] != v:
67
+ raise Exception(f'cannot merge dicts at key "{k}":\n{dct}\n{merge_dct}')
68
+ else:
69
+ dct[k] = v
70
+ return dct
71
+
72
+ # --- Tree Path Utilities -----------------------------------------------------
73
+
74
+ def establish_path(tree, path, top=None, cursor=()):
75
+ """
76
+ Create or traverse a path in a nested dictionary (tree structure).
77
+ """
78
+ if tree is None:
79
+ tree = {}
80
+ if top is None:
81
+ top = tree
82
+ if path is None or len(path) == 0:
83
+ return tree
84
+ if isinstance(path, str):
85
+ path = (path,)
86
+ head = path[0]
87
+ if head == '..':
88
+ if len(cursor) == 0:
89
+ raise Exception(f'trying to travel above the top of the tree: {path}')
90
+ return establish_path(top, cursor[:-1])
91
+ if head not in tree:
92
+ tree[head] = {}
93
+ return establish_path(tree[head], path[1:], top=top, cursor=cursor + (head,))
94
+
95
+ def set_path(tree, path, value, top=None, cursor=None):
96
+ """
97
+ Set `value` at the given `path` in a tree-like dictionary.
98
+ """
99
+ if value is None:
100
+ return None
101
+ if len(path) == 0:
102
+ return value
103
+ final = path[-1]
104
+ destination = establish_path(tree, path[:-1])
105
+ destination[final] = value
106
+ return tree
107
+
108
+ def set_star_path(tree, path, value, top=None, cursor=()):
109
+ """
110
+ Set a value at a wildcard (`*`) path, filling in multiple keys.
111
+ """
112
+ if tree is None:
113
+ tree = {}
114
+ if top is None:
115
+ top = tree
116
+ if isinstance(path, str):
117
+ path = (path,)
118
+ if len(path) == 0:
119
+ return tree
120
+
121
+ head, tail = path[0], path[1:]
122
+ if head == '..':
123
+ if len(cursor) == 0:
124
+ raise Exception(f'trying to travel above the top of the tree: {path}')
125
+ return set_star_path(top, cursor[:-1], value)
126
+ elif head == '*':
127
+ for key in value:
128
+ tree[key] = set_star_path({}, tail, value[key], cursor=(key,))
129
+ return top
130
+ else:
131
+ if len(tail) == 0:
132
+ tree[head] = value
133
+ else:
134
+ if head not in tree:
135
+ tree[head] = {}
136
+ set_star_path(tree[head], tail, value, top=top, cursor=cursor + (head,))
137
+ return top
138
+
139
+ def transform_path(tree, path, transform):
140
+ """
141
+ Apply a transformation function to a value at a specific path in a tree.
142
+ """
143
+ before = establish_path(tree, path)
144
+ after = transform(before)
145
+ return set_path(tree, path, after)
146
+
147
+ def hierarchy_depth(hierarchy, path=()):
148
+ """
149
+ Recursively collect all node paths in a hierarchy.
150
+ """
151
+ base = {}
152
+ for key, inner in hierarchy.items():
153
+ down = path + (key,)
154
+ if is_schema_key(key):
155
+ base[path] = inner
156
+ elif isinstance(inner, dict) and 'instance' not in inner:
157
+ base.update(hierarchy_depth(inner, down))
158
+ else:
159
+ base[down] = inner
160
+ return base
161
+
162
+ # --- Schema Tools ------------------------------------------------------------
163
+
164
+ def remove_omitted(before, after, tree):
165
+ """
166
+ Remove keys from `tree` that exist in `before` but not in `after`.
167
+ """
168
+ if isinstance(before, dict):
169
+ if not isinstance(tree, dict):
170
+ raise Exception(f'trying to remove an entry from non-dict: {tree}')
171
+ if not isinstance(after, dict):
172
+ return after
173
+ for key, down in before.items():
174
+ if not key.startswith('_'):
175
+ if key not in after:
176
+ tree.pop(key, None)
177
+ else:
178
+ tree[key] = remove_omitted(down, after[key], tree[key])
179
+ return tree
180
+
181
+ def is_schema_key(key):
182
+ """Check if a key is a schema key (starts with underscore)."""
183
+ return isinstance(key, str) and key.startswith('_')
184
+
185
+ def non_schema_keys(schema):
186
+ """Return list of non-schema keys from a dictionary."""
187
+ return [k for k in schema if not is_schema_key(k)]
188
+
189
+ def strip_schema_keys(state):
190
+ """
191
+ Recursively remove all schema keys from a dictionary.
192
+ """
193
+ if isinstance(state, dict):
194
+ return {k: strip_schema_keys(v) for k, v in state.items() if not is_schema_key(k)}
195
+ return state
196
+
197
+ def type_parameter_key(schema, key):
198
+ """Check if a key is a special parameter override."""
199
+ return key.strip('_') not in schema.get('_type_parameters', []) and key.startswith('_')
200
+
201
+ def default(type, default):
202
+ """Return a schema dictionary with a type and default."""
203
+ return {'_type': type, '_default': default}
204
+
205
+ # --- Registry ----------------------------------------------------------------
206
+
207
+ class Registry:
208
+ """
209
+ A registry for managing keyed objects or functions (e.g., schema types).
210
+
211
+ Supports registering items under multiple keys, deep merging for dicts,
212
+ and loading functions by string-qualified names.
213
+ """
214
+
215
+ def __init__(self, function_keys=None):
216
+ self.registry = {}
217
+ self.main_keys = set()
218
+ self.function_keys = set(function_keys or [])
219
+
220
+ def register(self, key, item, alternate_keys=(), strict=False):
221
+ """
222
+ Register an item under a key (and optional alternate keys).
223
+ Optionally enforce strict uniqueness.
224
+
225
+ If the item is a dict and already registered, a deep merge is attempted.
226
+ """
227
+ keys = [key] + list(alternate_keys)
228
+
229
+ for registry_key in keys:
230
+ if registry_key in self.registry:
231
+ if item != self.registry[registry_key]:
232
+ if strict:
233
+ raise Exception(
234
+ f'Registry conflict for {registry_key}: {self.registry[registry_key]} vs {item}')
235
+ elif isinstance(item, dict):
236
+ self.registry[registry_key] = deep_merge(self.registry[registry_key], item)
237
+ else:
238
+ self.registry[registry_key] = item
239
+ else:
240
+ self.registry[registry_key] = item
241
+
242
+ self.main_keys.add(key)
243
+
244
+ def register_function(self, function):
245
+ """
246
+ Register a function by name or function object.
247
+
248
+ Returns:
249
+ (function_name, module_key)
250
+ """
251
+ if isinstance(function, str):
252
+ module_key = function
253
+ found = self.find(module_key) or local_lookup_module(module_key)
254
+ if found is None:
255
+ raise Exception(f'Function "{module_key}" not found for type data')
256
+ elif inspect.isfunction(function):
257
+ found = function
258
+ module_key = function_module(found)
259
+ else:
260
+ raise TypeError(f'Unsupported function type: {type(function)}')
261
+
262
+ function_name = module_key.split('.')[-1]
263
+ self.register(function_name, found)
264
+ self.register(module_key, found)
265
+
266
+ return function_name, module_key
267
+
268
+ def register_multiple(self, schemas, strict=False):
269
+ """Register multiple schemas from a dictionary of {key: item}."""
270
+ for key, schema in schemas.items():
271
+ self.register(key, schema, strict=strict)
272
+
273
+ def find(self, key):
274
+ """Retrieve an item from the registry by key (or None)."""
275
+ return self.registry.get(key)
276
+
277
+ def access(self, key):
278
+ """Alias for `find()`."""
279
+ return self.find(key)
280
+
281
+ def list(self):
282
+ """List all primary (non-alternate) keys in the registry."""
283
+ return list(self.main_keys)
284
+
285
+ def validate(self, item):
286
+ """Stub for validation logic (currently always True)."""
287
+ return True