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.
- process_bigraph/__init__.py +53 -0
- process_bigraph/composite.py +1551 -0
- process_bigraph/emitter.py +326 -0
- process_bigraph/experiments/__init__.py +0 -0
- process_bigraph/experiments/minimal_gillespie.py +207 -0
- process_bigraph/process_types.py +473 -0
- process_bigraph/processes/__init__.py +26 -0
- process_bigraph/processes/growth_division.py +167 -0
- process_bigraph/processes/parameter_scan.py +350 -0
- process_bigraph/tests.py +1330 -0
- process_bigraph/units.py +25 -0
- process_bigraph-0.0.43.dist-info/METADATA +65 -0
- process_bigraph-0.0.43.dist-info/RECORD +17 -0
- process_bigraph-0.0.43.dist-info/WHEEL +5 -0
- process_bigraph-0.0.43.dist-info/licenses/AUTHORS.md +6 -0
- process_bigraph-0.0.43.dist-info/licenses/LICENSE +201 -0
- process_bigraph-0.0.43.dist-info/top_level.txt +1 -0
|
@@ -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
|
+
|