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.
bigraph_schema/edge.py ADDED
@@ -0,0 +1,138 @@
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(
54
+ self._config)
55
+
56
+
57
+ @property
58
+ def config(self):
59
+ return self._config
60
+
61
+ @config.setter
62
+ def config(self, config):
63
+ self._config = config
64
+
65
+ @property
66
+ def composition(self):
67
+ return self._composition
68
+
69
+ @composition.setter
70
+ def composition(self, composition):
71
+ self._composition = composition
72
+
73
+ @property
74
+ def state(self):
75
+ return self._state
76
+
77
+ @state.setter
78
+ def state(self, state):
79
+ self._state = state
80
+
81
+ def initialize(self, config):
82
+ """Optional hook for subclass-specific initialization."""
83
+ pass
84
+
85
+ def initial_state(self):
86
+ """Return initial state values, if applicable."""
87
+ return {}
88
+
89
+ @staticmethod
90
+ def generate_state(config=None):
91
+ """Generate static initial state for user configuration or inspection."""
92
+ return {}
93
+
94
+ def inputs(self):
95
+ """
96
+ Return a dictionary mapping input port names to bigraph types.
97
+
98
+ Example:
99
+ {'glucose': 'float', 'biomass': 'map[float]'}
100
+ """
101
+ return {}
102
+
103
+ def outputs(self):
104
+ """
105
+ Return a dictionary mapping output port names to bigraph types.
106
+
107
+ Example:
108
+ {'growth_rate': 'float'}
109
+ """
110
+ return {}
111
+
112
+ def default_config(self):
113
+ """ get the default of the config_schema for this edge """
114
+ return self.core.default(
115
+ self.config_schema)
116
+
117
+ def default_inputs(self):
118
+ """Generate default wire paths for inputs: {port: [port]}"""
119
+ return default_wires(self.inputs())
120
+
121
+ def default_outputs(self):
122
+ """Generate default wire paths for outputs: {port: [port]}"""
123
+ return default_wires(self.outputs())
124
+
125
+ def interface(self):
126
+ """
127
+ Return combined interface schema as a dict:
128
+ {
129
+ 'inputs': {port: type, ...},
130
+ 'outputs': {port: type, ...}
131
+ }
132
+ """
133
+ return {
134
+ 'inputs': self.inputs(),
135
+ 'outputs': self.outputs()
136
+ }
137
+
138
+
@@ -0,0 +1,12 @@
1
+ from bigraph_schema.methods.infer import infer, set_default
2
+ from bigraph_schema.methods.default import default, default_link
3
+ from bigraph_schema.methods.resolve import resolve
4
+ from bigraph_schema.methods.generalize import generalize
5
+ from bigraph_schema.methods.check import check
6
+ from bigraph_schema.methods.validate import validate
7
+ from bigraph_schema.methods.serialize import serialize, render, wrap_default
8
+ from bigraph_schema.methods.realize import realize, realize_link, load_protocol, load_local_protocol
9
+ from bigraph_schema.methods.merge import merge, merge_update
10
+ from bigraph_schema.methods.jump import jump, traverse
11
+ from bigraph_schema.methods.handle_parameters import schema_keys, align_parameters, reify_schema, handle_parameters
12
+ from bigraph_schema.methods.apply import apply
@@ -0,0 +1,276 @@
1
+ from plum import dispatch
2
+ import numpy as np
3
+
4
+ from bigraph_schema.schema import (
5
+ Node,
6
+ Atom,
7
+ Union,
8
+ Tuple,
9
+ Boolean,
10
+ Or,
11
+ And,
12
+ Xor,
13
+ Number,
14
+ Integer,
15
+ Float,
16
+ Delta,
17
+ Nonnegative,
18
+ String,
19
+ Enum,
20
+ Wrap,
21
+ Maybe,
22
+ Overwrite,
23
+ List,
24
+ Map,
25
+ Tree,
26
+ Array,
27
+ Key,
28
+ Path,
29
+ Wires,
30
+ Schema,
31
+ Link,
32
+ )
33
+
34
+
35
+ @dispatch
36
+ def apply(schema: Maybe, state, update, path):
37
+ if state is None:
38
+ if update is not None:
39
+ return update, []
40
+ elif update is None:
41
+ return state, []
42
+ else:
43
+ return apply(schema._value, state, update, path)
44
+
45
+
46
+ @dispatch
47
+ def apply(schema: Wrap, state, update, path):
48
+ return apply(schema._value, state, update, path)
49
+
50
+
51
+ @dispatch
52
+ def apply(schema: Overwrite, state, update, path):
53
+ return update, []
54
+
55
+
56
+ @dispatch
57
+ def apply(schema: Union, state, update, path):
58
+ found = None
59
+ for option in schema._options:
60
+ if check(option, state) and check(option, update):
61
+ found = option
62
+ break
63
+
64
+ if found is not None:
65
+ return apply(found, state, update, path)
66
+ else:
67
+ return update, []
68
+
69
+
70
+ @dispatch
71
+ def apply(schema: Tuple, state, update, path):
72
+ merges = []
73
+ result = []
74
+ for index, value in enumerate(schema._values):
75
+ if index < len(state):
76
+ if update and index < len(update):
77
+ substate, submerges = apply(
78
+ value,
79
+ state[index],
80
+ update[index],
81
+ path+(index,))
82
+ result.append(substate)
83
+ merges += submerges
84
+ else:
85
+ result.append(state[index])
86
+ elif index < len(update):
87
+ result.append(update[index])
88
+ else:
89
+ result.append(default(value))
90
+
91
+ return tuple(result), merges
92
+
93
+
94
+ @dispatch
95
+ def apply(schema: List, state, update, path):
96
+ merges = []
97
+ if isinstance(update, dict):
98
+ if '_remove' in update:
99
+ indexes = update['_remove']
100
+ if indexes == 'all':
101
+ result = []
102
+ else:
103
+ result = [
104
+ item
105
+ for index, item in enumerate(state)
106
+ if index not in indexes]
107
+ if '_add' in update:
108
+ result += update['_add']
109
+ else:
110
+ result = state + update
111
+
112
+ return result, merges
113
+
114
+
115
+ @dispatch
116
+ def apply(schema: Map, state, update, path):
117
+ result = state.copy()
118
+ merges = []
119
+
120
+ if update is None:
121
+ return state, merges
122
+
123
+ if '_add' in update:
124
+ add_update = update['_add']
125
+ if isinstance(add_update, list):
126
+ for add_key, add_value in update['_add']:
127
+ result[add_key] = add_value
128
+ elif isinstance(add_update, dict):
129
+ for add_key, add_value in update['_add'].items():
130
+ result[add_key] = add_value
131
+
132
+ for key, value in result.items():
133
+ if key in update:
134
+ result[key], submerges = apply(
135
+ schema._value,
136
+ value,
137
+ update[key],
138
+ path+(key,))
139
+ merges += submerges
140
+
141
+ if '_remove' in update:
142
+ for remove_key in update['_remove']:
143
+ if remove_key in result:
144
+ del result[remove_key]
145
+
146
+ return result, merges
147
+
148
+
149
+ @dispatch
150
+ def apply(schema: Tree, state, update, path):
151
+ if check(schema._leaf, state):
152
+ return apply(schema._leaf, state, update, path)
153
+
154
+ result = state.copy()
155
+ if '_remove' in update:
156
+ for remove_key in update['_remove']:
157
+ del result[remove_key]
158
+
159
+ if '_add' in update:
160
+ for add_key, add_value in update['_add']:
161
+ result[add_key] = add_value
162
+
163
+ for key, value in result:
164
+ if key in update:
165
+ result[key], submerges = apply(
166
+ schema,
167
+ value,
168
+ update[key],
169
+ path+(key,))
170
+ merges += submerges
171
+
172
+ return result, merges
173
+
174
+
175
+ @dispatch
176
+ def apply(schema: Atom, state, update, path):
177
+ if update is None:
178
+ return state, []
179
+ if state is None:
180
+ return update, []
181
+
182
+ return (state + update), []
183
+
184
+
185
+ @dispatch
186
+ def apply(schema: String, state, update, path):
187
+ return update, []
188
+
189
+ @dispatch
190
+ def apply(schema: Boolean, state, update, path):
191
+ return update, []
192
+
193
+
194
+ @dispatch
195
+ def apply(schema: Or, state, update, path):
196
+ return state or update, []
197
+
198
+
199
+ @dispatch
200
+ def apply(schema: And, state, update, path):
201
+ return state and update, []
202
+
203
+
204
+ @dispatch
205
+ def apply(schema: Xor, state, update, path):
206
+ return (state or update) and not (state and update), []
207
+
208
+
209
+ @dispatch
210
+ def apply(schema: Array, state, update, path):
211
+ index = tuple([
212
+ slice(0, dimension)
213
+ for dimension in update.shape])
214
+ state[index] += update
215
+ return state, []
216
+
217
+ @dispatch
218
+ def apply(schema: dict, state: np.ndarray, update, path):
219
+ merges = []
220
+ for key, subschema in schema.items():
221
+ if key in update:
222
+ substate = update[key]
223
+ state[key], submerges = apply(subschema, state[key], substate, path+(key,))
224
+ merges += submerges
225
+ return state, merges
226
+
227
+
228
+ @dispatch
229
+ def apply(schema: dict, state, update, path):
230
+ if update is None:
231
+ return state, []
232
+ if state is None:
233
+ return update, []
234
+
235
+ merges = []
236
+ result = {}
237
+
238
+ for key, subschema in schema.items():
239
+ if key in ('_inherit',):
240
+ continue
241
+
242
+ if key not in state:
243
+ continue
244
+
245
+ result[key], submerges = apply(
246
+ subschema,
247
+ state.get(key),
248
+ update.get(key),
249
+ path+(key,))
250
+ merges += submerges
251
+
252
+ for key in state.keys():
253
+ if not key in result and not key in schema:
254
+ result[key] = state[key]
255
+
256
+ return result, merges
257
+
258
+
259
+ @dispatch
260
+ def apply(schema: Node, state, update, path):
261
+ merges = []
262
+ if isinstance(state, dict) and isinstance(update, dict):
263
+ result = {}
264
+ for key in schema.__dataclass_fields__:
265
+ subschema = getattr(schema, key)
266
+ result[key], submerges = apply(
267
+ subschema,
268
+ state.get(key),
269
+ update.get(key),
270
+ path+(key,))
271
+ merges += submerges
272
+
273
+ else:
274
+ result = update
275
+
276
+ return result, merges
@@ -0,0 +1,213 @@
1
+ from plum import dispatch
2
+ import numpy as np
3
+
4
+ from bigraph_schema.schema import (
5
+ Node,
6
+ Atom,
7
+ Empty,
8
+ Union,
9
+ Tuple,
10
+ Boolean,
11
+ Number,
12
+ Integer,
13
+ Float,
14
+ Delta,
15
+ Nonnegative,
16
+ String,
17
+ Enum,
18
+ Wrap,
19
+ Maybe,
20
+ Overwrite,
21
+ List,
22
+ Map,
23
+ Tree,
24
+ Array,
25
+ Key,
26
+ Path,
27
+ Wires,
28
+ Schema,
29
+ Link,
30
+ )
31
+
32
+
33
+ @dispatch
34
+ def check(schema: Empty, state):
35
+ return state is None
36
+
37
+
38
+ @dispatch
39
+ def check(schema: Maybe, state):
40
+ if state is None:
41
+ return True
42
+ else:
43
+ return check(schema._value, state)
44
+
45
+
46
+ @dispatch
47
+ def check(schema: Wrap, state):
48
+ return check(schema._value, state)
49
+
50
+
51
+ @dispatch
52
+ def check(schema: Union, state):
53
+ for option in schema._options:
54
+ if check(option, state):
55
+ return True
56
+
57
+ return False
58
+
59
+
60
+ @dispatch
61
+ def check(schema: Tuple, state):
62
+ if not isinstance(state, (list, tuple)):
63
+ return False
64
+
65
+ elif len(schema._values) == len(state):
66
+ return all([
67
+ check(subschema, value)
68
+ for subschema, value in zip(schema._values, state)])
69
+
70
+ else:
71
+ return False
72
+
73
+
74
+ @dispatch
75
+ def check(schema: Boolean, state):
76
+ return isinstance(state, bool)
77
+
78
+
79
+ @dispatch
80
+ def check(schema: Integer, state):
81
+ return isinstance(state, int)
82
+
83
+
84
+ @dispatch
85
+ def check(schema: Float, state):
86
+ return isinstance(state, float)
87
+
88
+
89
+ @dispatch
90
+ def check(schema: Nonnegative, state):
91
+ return state >= 0
92
+
93
+
94
+ @dispatch
95
+ def check(schema: String, state):
96
+ return isinstance(state, str)
97
+
98
+
99
+ @dispatch
100
+ def check(schema: Enum, state):
101
+ if not isinstance(state, str):
102
+ return False
103
+
104
+ return state in schema._values
105
+
106
+
107
+ @dispatch
108
+ def check(schema: List, state):
109
+ if not isinstance(state, (list, tuple)):
110
+ return False
111
+
112
+ return all([
113
+ check(schema._element, element)
114
+ for element in state])
115
+
116
+
117
+ @dispatch
118
+ def check(schema: Map, state):
119
+ if not isinstance(state, dict):
120
+ return False
121
+
122
+ all_values = all([
123
+ check(schema._value, value)
124
+ for value in state.values()])
125
+
126
+ if isinstance(schema._key, String):
127
+ return all_values
128
+
129
+ else:
130
+ # if the keys are not strings, we must realize
131
+ # them all to tell if they pass the check?
132
+ # - this seems expensive?
133
+ all_keys = all([
134
+ # TODO: if realize needs core this will fail
135
+ # does that matter?
136
+ check(schema._key, realize(None, schema._key, key))
137
+ for key in state.keys()])
138
+
139
+ return all_keys and all_values
140
+
141
+
142
+ @dispatch
143
+ def check(schema: Tree, state):
144
+ if check(schema._leaf, state):
145
+ return True
146
+
147
+ elif isinstance(state, dict):
148
+ return all([
149
+ isinstance(key, str) and check(schema, branch)
150
+ for key, branch in state.items()])
151
+ else:
152
+ return False
153
+
154
+
155
+
156
+
157
+ @dispatch
158
+ def check(schema: Array, state):
159
+ if not isinstance(state, np.ndarray):
160
+ return False
161
+
162
+ shape_match = tuple(schema._shape) == state.shape
163
+ data_match = schema._data == state.dtype
164
+
165
+ return shape_match and data_match
166
+
167
+
168
+ @dispatch
169
+ def check(schema: Key, state):
170
+ return isinstance(state, int) or isinstance(state, str)
171
+
172
+
173
+ @dispatch
174
+ def check(schema: Node, state):
175
+ fields = [
176
+ field
177
+ for field in schema.__dataclass_fields__
178
+ if not field.startswith('_')]
179
+
180
+ if fields:
181
+ if isinstance(state, dict):
182
+ for key in schema.__dataclass_fields__:
183
+ if not key.startswith('_'):
184
+ if key not in state:
185
+ return False
186
+ else:
187
+ down = check(
188
+ getattr(schema, key),
189
+ state[key])
190
+ if down is False:
191
+ return False
192
+ return True
193
+ else:
194
+ return False
195
+ else:
196
+ return True
197
+
198
+
199
+ @dispatch
200
+ def check(schema: dict, state):
201
+ for key, subschema in schema.items():
202
+ if key not in state:
203
+ continue
204
+ # return False
205
+ elif not check(subschema, state[key]):
206
+ return False
207
+
208
+ return True
209
+
210
+
211
+ @dispatch
212
+ def check(schema, state):
213
+ raise Exception(f'not a valid schema: {schema}')