process-bigraph 0.0.43__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,350 @@
1
+ import copy
2
+ import numpy as np
3
+
4
+ from bigraph_schema import get_path, set_path, transform_path
5
+ from process_bigraph.composite import Step, Process, Composite, interval_time_precision, deep_merge
6
+ from process_bigraph.emitter import gather_emitter_results
7
+
8
+
9
+ class ToySystem(Process):
10
+ config_schema = {
11
+ 'rates': {
12
+ '_type': 'map',
13
+ '_value': {
14
+ 'kdeg': 'float',
15
+ 'ksynth': 'float'}}}
16
+
17
+ def inputs(self):
18
+ return {
19
+ 'species': 'map[float]'}
20
+
21
+ def outputs(self):
22
+ return {
23
+ 'species': 'map[float]'}
24
+
25
+ def update(self, inputs, interval):
26
+ species = {
27
+ key: input * (self.config['rates'][key]['ksynth'] - self.config['rates'][key]['kdeg'])
28
+ for key, input in inputs['species'].items()}
29
+
30
+ return {
31
+ 'species': species}
32
+
33
+
34
+ class ODE(Process):
35
+ config_schema = 'ode_config'
36
+
37
+ def initialize(self, config=None):
38
+ self.reactions_count = len(self.config['rates'])
39
+
40
+ self.species_count = len(
41
+ self.config['species_name'])
42
+
43
+ self.config_schema['rates']['_shape'] = (
44
+ self.species_count,
45
+ self.species_count)
46
+
47
+ def inputs(self):
48
+ return {
49
+ 'species': f'array[({self.species_count}), float]'}
50
+
51
+ def outputs(self):
52
+ return {
53
+ 'species': f'array[({self.species_count}), float]'}
54
+
55
+ def update(self, state, interval):
56
+ total = np.dot(
57
+ self.config['rates'],
58
+ state['species'])
59
+
60
+ delta = total - state['species']
61
+ return delta
62
+
63
+
64
+ class RunProcess(Step):
65
+ config_schema = {
66
+ 'process_address': 'string',
67
+ 'process_config': 'tree[any]',
68
+ 'observables': 'list[path]',
69
+ 'initial_state': 'any',
70
+ 'timestep': 'float',
71
+ 'runtime': 'float'}
72
+
73
+ def initialize(self, config):
74
+
75
+ self.process = self.core.deserialize('process', {
76
+ '_type': 'process',
77
+ 'address': self.config['process_address'],
78
+ 'config': self.config['process_config'],
79
+ 'inputs': {},
80
+ 'outputs': {}})['instance']
81
+
82
+ global_time_precision = interval_time_precision(
83
+ self.config['timestep'])
84
+
85
+ process_outputs = self.process.outputs()
86
+ self.observables_schema = {}
87
+ self.results_schema = {}
88
+ self.inputs_config = {}
89
+
90
+ for observable in self.config['observables']:
91
+ subschema, _ = self.core.slice(
92
+ process_outputs,
93
+ {},
94
+ observable)
95
+
96
+ set_path(
97
+ self.observables_schema,
98
+ observable,
99
+ subschema)
100
+
101
+ set_path(
102
+ self.results_schema,
103
+ observable, {
104
+ '_type': 'list',
105
+ '_element': subschema})
106
+
107
+ set_path(
108
+ self.inputs_config,
109
+ observable,
110
+ observable)
111
+ # [observable[-1]])
112
+
113
+ emit_config = dict(
114
+ {'time': 'float'},
115
+ **self.observables_schema)
116
+
117
+ state = copy.deepcopy(
118
+ config['initial_state'])
119
+
120
+ state['process'] = {
121
+ '_type': 'process',
122
+ 'address': self.config['process_address'],
123
+ 'config': self.config['process_config'],
124
+ 'instance': self.process,
125
+ 'interval': self.config['timestep'],
126
+ '_inputs': self.process.inputs(),
127
+ '_outputs': self.process.outputs(),
128
+ 'inputs': {
129
+ key: [key]
130
+ for key in self.process.inputs()},
131
+ 'outputs': {
132
+ key: [key]
133
+ for key in process_outputs}}
134
+
135
+ state['emitter'] = {
136
+ '_type': 'step',
137
+ '_inputs': emit_config,
138
+ 'address': 'local:ram-emitter',
139
+ 'config': {
140
+ 'emit': emit_config},
141
+ 'inputs': dict(
142
+ {'time': ['global_time']},
143
+ **self.inputs_config),
144
+ 'outputs': {}}
145
+
146
+ self.document = {
147
+ 'global_time_precision': global_time_precision,
148
+ 'state': state}
149
+
150
+ # wait to initialize until we get our first update
151
+ self.composite = None
152
+
153
+
154
+ def inputs(self):
155
+ return self.process.inputs()
156
+
157
+
158
+ def outputs(self):
159
+ return {
160
+ 'results': dict(
161
+ {'time': 'list[float]'},
162
+ **self.results_schema)}
163
+
164
+
165
+ def update(self, inputs):
166
+ # TODO: instead of the composite being a reference it is
167
+ # instead read through some port and lives in
168
+ # the state of the simulation (??)
169
+
170
+ if self.composite is None:
171
+ self.document['state'] = deep_merge(
172
+ self.document['state'],
173
+ inputs)
174
+
175
+ self.composite = Composite(
176
+ self.document,
177
+ core=self.core)
178
+
179
+ self.composite.merge(
180
+ self.inputs(),
181
+ inputs)
182
+
183
+ self.composite.run(
184
+ self.config['runtime'])
185
+
186
+ histories = gather_emitter_results(
187
+ self.composite)
188
+
189
+ results = {
190
+ key: timeseries_from_history(
191
+ history,
192
+ self.config['observables'] + [['time']])
193
+ for key, history in histories.items()}
194
+
195
+ all_results = {}
196
+ for timeseries in results.values():
197
+ all_results = deep_merge(all_results, timeseries)
198
+
199
+ return {
200
+ 'results': all_results}
201
+
202
+
203
+ def timeseries_from_history(history, observables):
204
+ results = {}
205
+ for moment in history:
206
+ for observable in observables:
207
+ def transform(before):
208
+ if not before:
209
+ before = []
210
+ value = get_path(moment, observable)
211
+ before.append(value)
212
+ return before
213
+
214
+ transform_path(results, observable, transform)
215
+
216
+ return results
217
+
218
+
219
+ def generate_key(parameters):
220
+ if isinstance(parameters, dict):
221
+ pairs = []
222
+ for key, value in parameters.items():
223
+ pairs.append((key, generate_key(value)))
224
+ tokens = [f'{key}:{value}' for key, value in pairs]
225
+ join = ','.join(tokens)
226
+ return '{' + join + '}'
227
+
228
+ elif isinstance(parameters, str):
229
+ return parameters
230
+
231
+ else:
232
+ return str(parameters)
233
+
234
+
235
+ class ParameterScan(Step):
236
+ config_schema = {
237
+ 'parameter_ranges': 'list[tuple[path,list[float]]]',
238
+ 'process_address': 'string',
239
+ 'process_config': 'tree[any]',
240
+ 'initial_state': 'tree[any]',
241
+ 'observables': 'list[path]',
242
+ 'timestep': 'float',
243
+ 'runtime': 'float'}
244
+
245
+ def initialize(self, config=None):
246
+
247
+ self.steps_count = int(
248
+ self.config['runtime'] / self.config['timestep']) + 1
249
+ self.observables_count = len(
250
+ self.config['observables'])
251
+
252
+ # TODO: test two parameters scanning simultaneously
253
+
254
+ self.process_parameters = [
255
+ self.config['process_config']]
256
+
257
+ for parameter_path, parameter_range in self.config['parameter_ranges']:
258
+ configs = []
259
+ for process_parameter in self.process_parameters:
260
+ for parameter_value in parameter_range:
261
+ next_parameters = copy.deepcopy(process_parameter)
262
+ set_path(next_parameters, parameter_path, parameter_value)
263
+ configs.append(next_parameters)
264
+ self.process_parameters = configs
265
+
266
+ bridge = {'outputs': {}}
267
+ state = {}
268
+ for parameters in self.process_parameters:
269
+ parameters_key = generate_key(parameters)
270
+ bridge['outputs'][f'{parameters_key}'] = [f'{parameters_key}']
271
+
272
+ for initial_key, initial_value in self.config['initial_state'].items():
273
+ state[f'{initial_key}_{parameters_key}'] = initial_value
274
+
275
+ state[f'process_{parameters_key}'] = {
276
+ '_type': 'step',
277
+ 'address': 'local:RunProcess',
278
+ 'config': {
279
+ 'process_address': self.config['process_address'],
280
+ 'process_config': parameters,
281
+ 'observables': self.config['observables'],
282
+ 'timestep': self.config['timestep'],
283
+ 'runtime': self.config['runtime']},
284
+ # TODO: these could be the same if the internal process uses its own state
285
+ # for calculating history?
286
+ 'inputs': {
287
+ initial_key: [f'{initial_key}_{parameters_key}']
288
+ for initial_key in self.config['initial_state'].keys()},
289
+ 'outputs': {'results': [f'{parameters_key}']}}
290
+
291
+ # TODO: perform parallelization on the independent steps
292
+ self.scan = Composite({
293
+ 'bridge': bridge,
294
+ 'state': state},
295
+ core=self.core)
296
+
297
+ results_schema = {}
298
+ process = self.first_process()
299
+ for parameters in self.process_parameters:
300
+ parameters_key = generate_key(parameters)
301
+ results_schema[parameters_key] = {
302
+ 'time': 'list[float]'}
303
+
304
+ for observable_path in self.config['observables']:
305
+ observable_schema, _ = self.core.slice(
306
+ process.outputs(),
307
+ {},
308
+ observable_path)
309
+
310
+ set_path(
311
+ results_schema[parameters_key],
312
+ observable_path,
313
+ {'_type': 'list', '_element': observable_schema})
314
+
315
+ self.results_schema = results_schema
316
+
317
+ def first_process(self):
318
+ for key, value in self.scan.state.items():
319
+ if key.startswith('process_'):
320
+ return value['instance'].composite.state['process']['instance']
321
+
322
+ def outputs(self):
323
+ return {
324
+ 'results': self.results_schema}
325
+
326
+ def update(self, inputs):
327
+ results = self.scan.update({}, 0.0)
328
+
329
+ update = {}
330
+ for result in results:
331
+ observable_list = []
332
+ key = list(result.keys())[0]
333
+ values = list(result.values())[0]
334
+ update[key] = {'time': values['time']}
335
+
336
+ for observable in self.config['observables']:
337
+ subschema = self.results_schema[key]
338
+ value_schema, value = self.core.slice(
339
+ subschema,
340
+ values,
341
+ observable)
342
+
343
+ set_path(
344
+ update[key],
345
+ observable,
346
+ value)
347
+
348
+ return {
349
+ 'results': update}
350
+