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,326 @@
|
|
|
1
|
+
"""
|
|
2
|
+
===========================
|
|
3
|
+
Emitter Utilities & Classes
|
|
4
|
+
===========================
|
|
5
|
+
|
|
6
|
+
Emitters are steps that observe a composite simulation's state and emit data to an external source
|
|
7
|
+
(e.g., console, memory, or file). This module provides tools to:
|
|
8
|
+
- Define emitter steps programmatically
|
|
9
|
+
- Insert emitters into a running composite
|
|
10
|
+
- Collect data from emitter steps
|
|
11
|
+
- Implement concrete emitters (RAM, console, JSON)
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import os
|
|
15
|
+
import json
|
|
16
|
+
import copy
|
|
17
|
+
import uuid
|
|
18
|
+
import pytest
|
|
19
|
+
import numpy as np
|
|
20
|
+
from typing import Dict
|
|
21
|
+
|
|
22
|
+
from bigraph_schema import get_path, set_path, is_schema_key, Edge
|
|
23
|
+
from process_bigraph.composite import Composite, Step, find_instance_paths
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# ==========================
|
|
27
|
+
# Emitter Spec Construction
|
|
28
|
+
# ==========================
|
|
29
|
+
|
|
30
|
+
def anyize_paths(tree):
|
|
31
|
+
"""Recursively convert all leaves of a nested path tree to 'any'."""
|
|
32
|
+
if isinstance(tree, dict):
|
|
33
|
+
return {key: anyize_paths(value) for key, value in tree.items()}
|
|
34
|
+
return 'any'
|
|
35
|
+
|
|
36
|
+
def emitter_from_wires(wires, address='local:ram-emitter'):
|
|
37
|
+
"""Create an emitter step spec from wire mappings."""
|
|
38
|
+
return {
|
|
39
|
+
'_type': 'step',
|
|
40
|
+
'address': address,
|
|
41
|
+
'config': {
|
|
42
|
+
'emit': anyize_paths(wires)},
|
|
43
|
+
'inputs': wires}
|
|
44
|
+
|
|
45
|
+
def collect_input_ports(state, path=None):
|
|
46
|
+
"""Recursively collect all valid input ports from state tree, skipping processes and schema keys."""
|
|
47
|
+
process_paths = find_instance_paths(state, 'process_bigraph.composite.Process')
|
|
48
|
+
step_paths = find_instance_paths(state, 'process_bigraph.composite.Step')
|
|
49
|
+
path = path or ()
|
|
50
|
+
input_ports = {}
|
|
51
|
+
for key, value in state.items():
|
|
52
|
+
full_path = path + (key,) if path else (key,)
|
|
53
|
+
full_key = '/'.join(full_path)
|
|
54
|
+
|
|
55
|
+
if is_schema_key(key):
|
|
56
|
+
continue
|
|
57
|
+
if full_path in process_paths or full_path in step_paths:
|
|
58
|
+
continue
|
|
59
|
+
if isinstance(value, dict):
|
|
60
|
+
input_ports.update(collect_input_ports(value, full_path))
|
|
61
|
+
else:
|
|
62
|
+
input_ports[full_key] = list(full_path)
|
|
63
|
+
return input_ports
|
|
64
|
+
|
|
65
|
+
def generate_emitter_state(composite, emitter_mode="all", address="local:ram-emitter"):
|
|
66
|
+
"""
|
|
67
|
+
Generate emitter state for a given composite and mode.
|
|
68
|
+
Modes:
|
|
69
|
+
- "all": observe all valid inputs
|
|
70
|
+
- "none": observe nothing
|
|
71
|
+
- {"paths": [...]}: custom paths to observe
|
|
72
|
+
"""
|
|
73
|
+
config = {}
|
|
74
|
+
input_ports = {}
|
|
75
|
+
|
|
76
|
+
if emitter_mode == "all":
|
|
77
|
+
input_ports = collect_input_ports(composite.state)
|
|
78
|
+
elif emitter_mode == "none":
|
|
79
|
+
input_ports = {}
|
|
80
|
+
elif isinstance(emitter_mode, dict) and "paths" in emitter_mode:
|
|
81
|
+
for path in emitter_mode["paths"]:
|
|
82
|
+
if isinstance(path, str):
|
|
83
|
+
input_ports[path] = [path]
|
|
84
|
+
elif isinstance(path, list):
|
|
85
|
+
input_ports[path[0]] = path
|
|
86
|
+
else:
|
|
87
|
+
raise ValueError(f"Invalid mode: {emitter_mode}.")
|
|
88
|
+
|
|
89
|
+
if "global_time" not in input_ports:
|
|
90
|
+
input_ports["global_time"] = ["global_time"]
|
|
91
|
+
|
|
92
|
+
if "emit" not in config:
|
|
93
|
+
config["emit"] = {port: "any" for port in input_ports}
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
"_type": "step",
|
|
97
|
+
"address": address,
|
|
98
|
+
"config": config,
|
|
99
|
+
"inputs": input_ports
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
def gather_emitter_results(composite, queries=None):
|
|
103
|
+
"""Retrieve query results from all emitter steps in a composite."""
|
|
104
|
+
emitter_paths = find_instance_paths(composite.state, 'process_bigraph.emitter.Emitter')
|
|
105
|
+
queries = queries or {path: None for path in emitter_paths}
|
|
106
|
+
|
|
107
|
+
results = {}
|
|
108
|
+
for path, query in queries.items():
|
|
109
|
+
emitter = get_path(composite.state, path)
|
|
110
|
+
results[path] = emitter['instance'].query(query)
|
|
111
|
+
return results
|
|
112
|
+
|
|
113
|
+
def add_emitter_to_composite(composite, core, emitter_mode='all', address="local:ram-emitter"):
|
|
114
|
+
"""Insert an emitter into a composite and rebuild the step network."""
|
|
115
|
+
path = ('emitter',)
|
|
116
|
+
emitter_state = generate_emitter_state(composite, emitter_mode=emitter_mode, address=address)
|
|
117
|
+
composite.merge({}, set_path({}, path, emitter_state))
|
|
118
|
+
|
|
119
|
+
# TODO -- this is a hack to get the emitter to show up in the state
|
|
120
|
+
_, instance = core.slice(composite.composition, composite.state, path)
|
|
121
|
+
composite.step_paths[path] = instance
|
|
122
|
+
composite.build_step_network()
|
|
123
|
+
return composite
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
# =====================
|
|
127
|
+
# Emitter Base Classes
|
|
128
|
+
# =====================
|
|
129
|
+
|
|
130
|
+
class Emitter(Step):
|
|
131
|
+
"""Base emitter class: defines schema and stub methods."""
|
|
132
|
+
config_schema = {'emit': 'schema'}
|
|
133
|
+
|
|
134
|
+
def inputs(self) -> Dict:
|
|
135
|
+
return self.config['emit']
|
|
136
|
+
|
|
137
|
+
def query(self, query=None):
|
|
138
|
+
"""
|
|
139
|
+
Query the history of the emitter.
|
|
140
|
+
:param query: a list of paths to query from the history. If None, the entire history is returned.
|
|
141
|
+
:return: results of the query in a list
|
|
142
|
+
"""
|
|
143
|
+
return {}
|
|
144
|
+
|
|
145
|
+
def update(self, state) -> Dict:
|
|
146
|
+
return {}
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
# ========================
|
|
150
|
+
# Emitter Implementations
|
|
151
|
+
# ========================
|
|
152
|
+
|
|
153
|
+
class ConsoleEmitter(Emitter):
|
|
154
|
+
"""Print state to console each timestep."""
|
|
155
|
+
def update(self, state) -> Dict:
|
|
156
|
+
print(state)
|
|
157
|
+
return {}
|
|
158
|
+
|
|
159
|
+
def tree_copy(state):
|
|
160
|
+
"""Deep copy utility for nested simulation state (excluding Edge instances)."""
|
|
161
|
+
if isinstance(state, dict):
|
|
162
|
+
return {k: v for k, v in ((k, tree_copy(v)) for k, v in state.items()) if v is not None}
|
|
163
|
+
if isinstance(state, np.ndarray):
|
|
164
|
+
return state.copy()
|
|
165
|
+
if isinstance(state, Edge):
|
|
166
|
+
return None
|
|
167
|
+
return copy.deepcopy(state)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class RAMEmitter(Emitter):
|
|
171
|
+
"""Store historical states in memory."""
|
|
172
|
+
def __init__(self, config, core):
|
|
173
|
+
super().__init__(config, core)
|
|
174
|
+
self.history = []
|
|
175
|
+
|
|
176
|
+
def update(self, state) -> Dict:
|
|
177
|
+
self.history.append(tree_copy(state))
|
|
178
|
+
return {}
|
|
179
|
+
|
|
180
|
+
def query(self, query=None, schema=None):
|
|
181
|
+
schema = schema or self.inputs()
|
|
182
|
+
if isinstance(query, list):
|
|
183
|
+
results = []
|
|
184
|
+
for t in self.history:
|
|
185
|
+
result = {}
|
|
186
|
+
for path in query:
|
|
187
|
+
_, value = self.core.slice(schema, t, path)
|
|
188
|
+
result = set_path(result, path, value)
|
|
189
|
+
results.append(result)
|
|
190
|
+
return results
|
|
191
|
+
return self.history
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class JSONEmitter(Emitter):
|
|
195
|
+
"""Append simulation state to a persistent JSON file each timestep."""
|
|
196
|
+
config_schema = {
|
|
197
|
+
**Emitter.config_schema,
|
|
198
|
+
'file_path': {'_type': 'string', '_default': './out'},
|
|
199
|
+
'simulation_id': {'_type': 'string', '_default': None}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
def __init__(self, config, core):
|
|
203
|
+
super().__init__(config, core)
|
|
204
|
+
self.simulation_id = config.get('simulation_id') or str(uuid.uuid4())
|
|
205
|
+
self.file_path = config.get('file_path', './out')
|
|
206
|
+
os.makedirs(self.file_path, exist_ok=True)
|
|
207
|
+
self.filepath = os.path.join(self.file_path, f"history_{self.simulation_id}.json")
|
|
208
|
+
if not os.path.exists(self.filepath):
|
|
209
|
+
with open(self.filepath, 'w') as f:
|
|
210
|
+
json.dump([], f)
|
|
211
|
+
|
|
212
|
+
def update(self, state) -> dict:
|
|
213
|
+
with open(self.filepath, 'r+') as f:
|
|
214
|
+
try:
|
|
215
|
+
data = json.load(f)
|
|
216
|
+
except json.JSONDecodeError:
|
|
217
|
+
data = []
|
|
218
|
+
data.append(copy.deepcopy(state))
|
|
219
|
+
f.seek(0)
|
|
220
|
+
json.dump(data, f, indent=4)
|
|
221
|
+
return {}
|
|
222
|
+
|
|
223
|
+
def query(self, query=None):
|
|
224
|
+
if not os.path.exists(self.filepath):
|
|
225
|
+
return []
|
|
226
|
+
with open(self.filepath, 'r') as f:
|
|
227
|
+
try:
|
|
228
|
+
data = json.load(f)
|
|
229
|
+
except json.JSONDecodeError:
|
|
230
|
+
return []
|
|
231
|
+
|
|
232
|
+
if isinstance(query, list):
|
|
233
|
+
results = []
|
|
234
|
+
for t in data:
|
|
235
|
+
result = {}
|
|
236
|
+
for path in query:
|
|
237
|
+
element = get_path(t, path)
|
|
238
|
+
result = set_path(result, path, element)
|
|
239
|
+
results.append(result)
|
|
240
|
+
return results
|
|
241
|
+
return data
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
# ====================
|
|
245
|
+
# Base Emitter Mapping
|
|
246
|
+
# ====================
|
|
247
|
+
|
|
248
|
+
BASE_EMITTERS = {
|
|
249
|
+
'console-emitter': ConsoleEmitter,
|
|
250
|
+
'ram-emitter': RAMEmitter,
|
|
251
|
+
'json-emitter': JSONEmitter,
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
# ==========
|
|
256
|
+
# Unit Tests
|
|
257
|
+
# ==========
|
|
258
|
+
|
|
259
|
+
@pytest.fixture
|
|
260
|
+
def core():
|
|
261
|
+
from process_bigraph import register_types, ProcessTypes
|
|
262
|
+
core = ProcessTypes()
|
|
263
|
+
return register_types(core)
|
|
264
|
+
|
|
265
|
+
def test_ram_emitter(core):
|
|
266
|
+
composite_spec = {
|
|
267
|
+
'increase': {
|
|
268
|
+
'_type': 'process',
|
|
269
|
+
'address': 'local:!process_bigraph.tests.IncreaseProcess',
|
|
270
|
+
'config': {'rate': 0.3},
|
|
271
|
+
'inputs': {'level': ['valueA']},
|
|
272
|
+
'outputs': {'level': ['valueA']}},
|
|
273
|
+
'increase2': {
|
|
274
|
+
'_type': 'process',
|
|
275
|
+
'address': 'local:!process_bigraph.tests.IncreaseProcess',
|
|
276
|
+
'config': {'rate': 0.1},
|
|
277
|
+
'inputs': {'level': ['valueB']},
|
|
278
|
+
'outputs': {'level': ['valueB']}},
|
|
279
|
+
'emitter': emitter_from_wires({
|
|
280
|
+
'time': ['global_time'],
|
|
281
|
+
'valueA': ['valueA'],
|
|
282
|
+
'valueB': ['valueB']})}
|
|
283
|
+
|
|
284
|
+
composite = Composite({'state': composite_spec}, core=core)
|
|
285
|
+
composite.run(10)
|
|
286
|
+
|
|
287
|
+
results = composite.state['emitter']['instance'].query()
|
|
288
|
+
assert len(results) == 11
|
|
289
|
+
assert results[-1]['time'] == 10
|
|
290
|
+
assert 'valueA' in results[0] and 'valueB' in results[0]
|
|
291
|
+
|
|
292
|
+
composite_spec['emitter'] = emitter_from_wires({
|
|
293
|
+
'time': ['global_time'],
|
|
294
|
+
'valueA': ['valueA']})
|
|
295
|
+
composite2 = Composite({'state': composite_spec}, core=core)
|
|
296
|
+
composite2.run(10)
|
|
297
|
+
|
|
298
|
+
results2 = composite2.state['emitter']['instance'].query()
|
|
299
|
+
assert 'valueA' in results2[0] and 'valueB' not in results2[0]
|
|
300
|
+
print(results2)
|
|
301
|
+
|
|
302
|
+
def test_json_emitter(core):
|
|
303
|
+
composite_spec = {
|
|
304
|
+
'increase': {
|
|
305
|
+
'_type': 'process',
|
|
306
|
+
'address': 'local:!process_bigraph.tests.IncreaseProcess',
|
|
307
|
+
'config': {'rate': 0.3},
|
|
308
|
+
'interval': 1.0,
|
|
309
|
+
'inputs': {'level': ['value']},
|
|
310
|
+
'outputs': {'level': ['value']}}}
|
|
311
|
+
composite = Composite({'state': composite_spec}, core)
|
|
312
|
+
composite = add_emitter_to_composite(composite, core, emitter_mode='all', address='local:json-emitter')
|
|
313
|
+
composite.run(10)
|
|
314
|
+
|
|
315
|
+
results = composite.state['emitter']['instance'].query()
|
|
316
|
+
assert len(results) == 10
|
|
317
|
+
assert results[-1]['global_time'] == 10
|
|
318
|
+
print(results)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
if __name__ == '__main__':
|
|
322
|
+
from process_bigraph import register_types, ProcessTypes
|
|
323
|
+
core = ProcessTypes()
|
|
324
|
+
core = register_types(core)
|
|
325
|
+
test_ram_emitter(core)
|
|
326
|
+
test_json_emitter(core)
|
|
File without changes
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
""" Toy Stochastic Transcription Process
|
|
2
|
+
Toy model of Gillespie algorithm-based transcription,
|
|
3
|
+
and a composite with deterministic translation.
|
|
4
|
+
|
|
5
|
+
Note: This Process is primarily for testing multi-timestepping.
|
|
6
|
+
variables and parameters are hard-coded. Do not use this as a
|
|
7
|
+
general stochastic transcription.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
import pytest
|
|
13
|
+
|
|
14
|
+
from process_bigraph.composite import Step, Process, Composite, ProcessEnsemble
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class GillespieInterval(Step):
|
|
18
|
+
config_schema = {
|
|
19
|
+
'ktsc': {
|
|
20
|
+
'_type': 'float',
|
|
21
|
+
'_default': '5e0'},
|
|
22
|
+
'kdeg': {
|
|
23
|
+
'_type': 'float',
|
|
24
|
+
'_default': '1e-1'}}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def inputs(self):
|
|
28
|
+
return {
|
|
29
|
+
'DNA': 'map[default 1]',
|
|
30
|
+
'mRNA': {
|
|
31
|
+
'A mRNA': 'default 1',
|
|
32
|
+
'B mRNA': 'default 1'}}
|
|
33
|
+
|
|
34
|
+
# {
|
|
35
|
+
# '_type': 'map',
|
|
36
|
+
# '_value': 'float(default:1.0)'},
|
|
37
|
+
|
|
38
|
+
# 'G': {
|
|
39
|
+
# '_type': 'float',
|
|
40
|
+
# '_default': '1.0'}},
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def outputs(self):
|
|
44
|
+
return {
|
|
45
|
+
'interval': 'interval'}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def initial_state(self):
|
|
49
|
+
return {
|
|
50
|
+
'mRNA': {
|
|
51
|
+
'A mRNA': 2.0,
|
|
52
|
+
'B mRNA': 3.0}}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def update(self, input):
|
|
56
|
+
# retrieve the state values
|
|
57
|
+
g = input['DNA']['A gene']
|
|
58
|
+
c = input['mRNA']['A mRNA']
|
|
59
|
+
|
|
60
|
+
array_state = np.array([g, c])
|
|
61
|
+
|
|
62
|
+
# Calculate propensities
|
|
63
|
+
propensities = [
|
|
64
|
+
self.config['ktsc'] * array_state[0],
|
|
65
|
+
self.config['kdeg'] * array_state[1]]
|
|
66
|
+
prop_sum = sum(propensities)
|
|
67
|
+
|
|
68
|
+
# The wait time is distributed exponentially
|
|
69
|
+
interval = np.random.exponential(scale=prop_sum)
|
|
70
|
+
|
|
71
|
+
output = {
|
|
72
|
+
'interval': interval}
|
|
73
|
+
|
|
74
|
+
# print(f'produced interval: {output}')
|
|
75
|
+
|
|
76
|
+
return output
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class GillespieEvent(Process):
|
|
80
|
+
"""stochastic toy transcription"""
|
|
81
|
+
config_schema = {
|
|
82
|
+
'ktsc': {
|
|
83
|
+
'_type': 'float',
|
|
84
|
+
'_default': '5e0'},
|
|
85
|
+
'kdeg': {
|
|
86
|
+
'_type': 'float',
|
|
87
|
+
'_default': '1e-1'}}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def initialize(self, config=None):
|
|
91
|
+
self.stoichiometry = np.array([[0, 1], [0, -1]])
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def initial_state(self):
|
|
95
|
+
return {
|
|
96
|
+
'mRNA': {
|
|
97
|
+
'C mRNA': 11.111},
|
|
98
|
+
'DNA': {
|
|
99
|
+
'A gene': 3.0,
|
|
100
|
+
'B gene': 5.0}}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def inputs(self):
|
|
104
|
+
return {
|
|
105
|
+
'mRNA': 'map[float]',
|
|
106
|
+
'DNA': {
|
|
107
|
+
'A gene': 'float',
|
|
108
|
+
'B gene': 'float'}}
|
|
109
|
+
|
|
110
|
+
def outputs(self):
|
|
111
|
+
return {
|
|
112
|
+
'mRNA': 'map[float]'}
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def next_reaction(self, x):
|
|
116
|
+
"""get the next reaction and return a new state"""
|
|
117
|
+
|
|
118
|
+
propensities = [
|
|
119
|
+
self.config['ktsc'] * x[0],
|
|
120
|
+
self.config['kdeg'] * x[1]]
|
|
121
|
+
prop_sum = sum(propensities)
|
|
122
|
+
|
|
123
|
+
# Choose the next reaction
|
|
124
|
+
r_rxn = np.random.uniform()
|
|
125
|
+
i = 0
|
|
126
|
+
for i, _ in enumerate(propensities):
|
|
127
|
+
if r_rxn < propensities[i] / prop_sum:
|
|
128
|
+
# This means propensity i fires
|
|
129
|
+
break
|
|
130
|
+
x += self.stoichiometry[i]
|
|
131
|
+
return x
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def update(self, state, interval):
|
|
135
|
+
|
|
136
|
+
# retrieve the state values, put them in array
|
|
137
|
+
g = state['DNA']['A gene']
|
|
138
|
+
c = state['mRNA']['A mRNA']
|
|
139
|
+
array_state = np.array([g, c])
|
|
140
|
+
|
|
141
|
+
# calculate the next reaction
|
|
142
|
+
new_state = self.next_reaction(array_state)
|
|
143
|
+
|
|
144
|
+
# get delta mRNA
|
|
145
|
+
c1 = new_state[1]
|
|
146
|
+
d_c = c1 - c
|
|
147
|
+
|
|
148
|
+
update = {
|
|
149
|
+
'mRNA': {
|
|
150
|
+
'A mRNA': d_c}}
|
|
151
|
+
|
|
152
|
+
# print(f'received interval: {interval}')
|
|
153
|
+
|
|
154
|
+
return update
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class GillespieSimulation(ProcessEnsemble):
|
|
158
|
+
def __init__(self, config=None, core=None):
|
|
159
|
+
super.__init__(config, core)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def inputs_interval(self):
|
|
163
|
+
return {
|
|
164
|
+
'DNA': 'map[default 1]',
|
|
165
|
+
'mRNA': {
|
|
166
|
+
'A mRNA': 'default 1',
|
|
167
|
+
'B mRNA': 'default 1'}}
|
|
168
|
+
|
|
169
|
+
# {
|
|
170
|
+
# '_type': 'map',
|
|
171
|
+
# '_value': 'float(default:1.0)'},
|
|
172
|
+
|
|
173
|
+
# 'G': {
|
|
174
|
+
# '_type': 'float',
|
|
175
|
+
# '_default': '1.0'}},
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def outputs_interval(self):
|
|
179
|
+
return {
|
|
180
|
+
'interval': 'interval'}
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
# def interface_interval(self):
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def calculate_interval(self, inputs):
|
|
187
|
+
# retrieve the state values
|
|
188
|
+
g = input['DNA']['A gene']
|
|
189
|
+
c = input['mRNA']['A mRNA']
|
|
190
|
+
|
|
191
|
+
array_state = np.array([g, c])
|
|
192
|
+
|
|
193
|
+
# Calculate propensities
|
|
194
|
+
propensities = [
|
|
195
|
+
self.config['ktsc'] * array_state[0],
|
|
196
|
+
self.config['kdeg'] * array_state[1]]
|
|
197
|
+
prop_sum = sum(propensities)
|
|
198
|
+
|
|
199
|
+
# The wait time is distributed exponentially
|
|
200
|
+
interval = np.random.exponential(scale=prop_sum)
|
|
201
|
+
|
|
202
|
+
output = {
|
|
203
|
+
'interval': interval}
|
|
204
|
+
|
|
205
|
+
print(f'produced interval: {output}')
|
|
206
|
+
|
|
207
|
+
return output
|