ode2tn 1.0.0__py3-none-any.whl → 1.0.1__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.
ode2tn/transform.py CHANGED
@@ -10,7 +10,7 @@ from scipy.integrate._ivp.ivp import OdeResult # noqa
10
10
 
11
11
  def plot_tn(
12
12
  odes: dict[sp.Symbol | str, sp.Expr | str | float],
13
- initial_values: dict[sp.Symbol | str, float],
13
+ initial_values: dict[sp.Symbol | str | gp.Specie, float],
14
14
  t_eval: Iterable[float] | None = None,
15
15
  *,
16
16
  gamma: float,
@@ -77,44 +77,19 @@ def plot_tn(
77
77
  Typically None, but if return_ode_result is True, returns the result of the ODE integration.
78
78
  See documentation of `gpac.plot` for details.
79
79
  """
80
- tn_odes, tn_inits, tn_ratios = ode2tn(odes, initial_values, gamma=gamma, beta=beta, scale=scale)
80
+ tn_odes, tn_inits, tn_syms = ode2tn(odes, initial_values, gamma=gamma, beta=beta, scale=scale)
81
81
  dependent_symbols_tn = dict(dependent_symbols) if dependent_symbols is not None else {}
82
+ tn_ratios = {sym: sym_t/sym_b for sym, (sym_t, sym_b) in tn_syms.items()}
82
83
  dependent_symbols_tn.update(tn_ratios)
83
84
  symbols_to_plot = dependent_symbols_tn if symbols_to_plot is None else symbols_to_plot
84
85
 
86
+ legend = {}
87
+ for sym, (sym_t, sym_b) in tn_syms.items():
88
+ legend[sym_t] = f'${sym}^\\top$'
89
+ legend[sym_b] = f'${sym}^\\bot$'
90
+
85
91
  if resets is not None:
86
- # make copy since we are going to change it
87
- new_resets = {}
88
- for time, reset in resets.items():
89
- new_resets[time] = {}
90
- for x, val in reset.items():
91
- new_resets[time][x] = val
92
- resets = new_resets
93
- # normalize resets keys and check that variables are valid
94
- for reset in resets.values():
95
- for x, val in reset.items():
96
- if isinstance(x, str):
97
- del reset[x]
98
- x = sp.symbols(x)
99
- reset[x] = val
100
- if x not in odes.keys() and x not in tn_odes.keys():
101
- raise ValueError(f"Symbol {x} not found in original variables: {', '.join(odes.keys())},\n"
102
- f"nor found in transcription network variables: {', '.join(tn_odes.keys())}")
103
- # ensure if original variable x is in resets, then neither x_top nor x_bot are in the resets
104
- # and substitute x_top and x_bot for x in resets
105
- for x, ratio in tn_ratios.items():
106
- # x is an original; so make sure neither x_top nor x_bot are in the reset dict
107
- if x in reset:
108
- xt,xb = sp.fraction(ratio)
109
- if xt in reset:
110
- raise ValueError(f'Cannot use "top" variable {xt} in resets '
111
- f'if original variable {x} is also used')
112
- if xb in reset:
113
- raise ValueError(f'Cannot use "bottom" variable {xb} in resets '
114
- f'if original variable {x} is also used')
115
- reset[xt] = reset[x] * scale
116
- reset[xb] = scale
117
- del reset[x]
92
+ resets = update_resets_with_ratios(odes, resets, tn_odes, tn_syms, scale)
118
93
 
119
94
  return gp.plot(
120
95
  odes=tn_odes,
@@ -125,6 +100,7 @@ def plot_tn(
125
100
  resets=resets,
126
101
  figure_size=figure_size,
127
102
  symbols_to_plot=symbols_to_plot,
103
+ legend=legend,
128
104
  show=show,
129
105
  method=method,
130
106
  dense_output=dense_output,
@@ -137,14 +113,51 @@ def plot_tn(
137
113
  )
138
114
 
139
115
 
116
+ def update_resets_with_ratios(odes, resets, tn_odes, tn_syms, scale: float = 1.0) -> dict[float, dict[sp.Symbol, float]]:
117
+ tn_ratios = {sym: sym_t / sym_b for sym, (sym_t, sym_b) in tn_syms.items()}
118
+ # make copy since we are going to change it
119
+ new_resets = {}
120
+ for time, reset in resets.items():
121
+ new_resets[time] = {}
122
+ for x, val in reset.items():
123
+ new_resets[time][x] = val
124
+ resets = new_resets
125
+ # normalize resets keys and check that variables are valid
126
+ for reset in resets.values():
127
+ for x, val in reset.items():
128
+ if isinstance(x, str):
129
+ del reset[x]
130
+ x = sp.symbols(x)
131
+ reset[x] = val
132
+ if x not in odes.keys() and x not in tn_odes.keys():
133
+ raise ValueError(f"Symbol {x} not found in original variables: {', '.join(odes.keys())},\n"
134
+ f"nor found in transcription network variables: {', '.join(tn_odes.keys())}")
135
+ # ensure if original variable x is in resets, then neither x_top nor x_bot are in the resets
136
+ # and substitute x_top and x_bot for x in resets
137
+ for x, ratio in tn_ratios.items():
138
+ # x is an original; so make sure neither x_top nor x_bot are in the reset dict
139
+ if x in reset:
140
+ xt, xb = sp.fraction(ratio)
141
+ if xt in reset:
142
+ raise ValueError(f'Cannot use "top" variable {xt} in resets '
143
+ f'if original variable {x} is also used')
144
+ if xb in reset:
145
+ raise ValueError(f'Cannot use "bottom" variable {xb} in resets '
146
+ f'if original variable {x} is also used')
147
+ reset[xt] = reset[x] * scale
148
+ reset[xb] = scale
149
+ del reset[x]
150
+ return resets
151
+
152
+
140
153
  def ode2tn(
141
154
  odes: dict[sp.Symbol | str, sp.Expr | str | float],
142
- initial_values: dict[sp.Symbol | str, float],
155
+ initial_values: dict[sp.Symbol | str | gp.Specie, float],
143
156
  *,
144
157
  gamma: float,
145
158
  beta: float,
146
159
  scale: float = 1.0,
147
- ) -> tuple[dict[sp.Symbol, sp.Expr], dict[sp.Symbol, float], dict[sp.Symbol, sp.Expr]]:
160
+ ) -> tuple[dict[sp.Symbol, sp.Expr], dict[sp.Symbol, float], dict[sp.Symbol, tuple[sp.Symbol, sp.Symbol]]]:
148
161
  """
149
162
  Maps polynomial ODEs and and initial values to transcription network (represented by ODEs with positive
150
163
  Laurent polynomials and negative linear term) simulating it, as well as initial values.
@@ -155,7 +168,8 @@ def ode2tn(
155
168
  (representing RHS of ODEs)
156
169
  Raises ValueError if any of the ODEs RHS is not a polynomial
157
170
  initial_values: initial values,
158
- dict of sympy symbols or strings (representing symbols) to floats
171
+ dict of sympy symbols or strings (representing symbols) or gpac.Specie (representing chemical
172
+ species, if the ODEs were derived from `gpac.crn_to_odes`) to floats
159
173
  gamma: coefficient of the negative linear term in the transcription network
160
174
  beta: additive constant in x_top ODE
161
175
  scale: "scaling factor" for the transcription network ODEs. Each variable `x` is replaced by a pair
@@ -163,15 +177,21 @@ def ode2tn(
163
177
  `x*scale`.
164
178
 
165
179
  Return:
166
- triple (`tn_odes`, `tn_inits`, `tn_ratios`), where `tn_ratios` is a dict mapping each original symbol ``x``
167
- in the original ODEs to the sympy.Expr ``x_top / x_bot``.
180
+ triple (`tn_odes`, `tn_inits`, `tn_syms`), where `tn_syms` is a dict mapping each original symbol ``x``
181
+ in the original ODEs to the pair ``(x_top, x_bot)``.
168
182
  """
169
183
  # normalize initial values dict to use symbols as keys
170
- initial_values = {sp.Symbol(symbol) if isinstance(symbol, str) else symbol: value
171
- for symbol, value in initial_values.items()}
184
+ initial_values_norm = {}
185
+ for symbol, value in initial_values.items():
186
+ if isinstance(symbol, str):
187
+ symbol = sp.symbols(symbol)
188
+ if isinstance(symbol, gp.Specie):
189
+ symbol = sp.symbols(symbol.name)
190
+ initial_values_norm[symbol] = value
191
+ initial_values = initial_values_norm
172
192
 
173
193
  # normalize odes dict to use symbols as keys
174
- odes_symbols = {}
194
+ odes_normalized = {}
175
195
  symbols_found_in_expressions = set()
176
196
  for symbol, expr in odes.items():
177
197
  if isinstance(symbol, str):
@@ -179,11 +199,12 @@ def ode2tn(
179
199
  if isinstance(expr, (str, int, float)):
180
200
  expr = sp.sympify(expr)
181
201
  symbols_found_in_expressions.update(expr.free_symbols)
182
- odes_symbols[symbol] = expr
202
+ odes_normalized[symbol] = expr
203
+ odes = odes_normalized
183
204
 
184
205
  # ensure that all symbols that are keys in `initial_values` are also keys in `odes`
185
206
  initial_values_keys = set(initial_values.keys())
186
- odes_keys = set(odes_symbols.keys())
207
+ odes_keys = set(odes.keys())
187
208
  diff = initial_values_keys - odes_keys
188
209
  if len(diff) > 0:
189
210
  raise ValueError(f"\nInitial_values contains symbols that are not in odes: "
@@ -199,7 +220,7 @@ def ode2tn(
199
220
  f"The keys in the odes dict are: {odes_keys}")
200
221
 
201
222
  # ensure all odes are polynomials
202
- for symbol, expr in odes_symbols.items():
223
+ for symbol, expr in odes_normalized.items():
203
224
  if not expr.is_polynomial():
204
225
  raise ValueError(f"ODE for {symbol}' is not a polynomial: {expr}")
205
226
 
@@ -213,16 +234,14 @@ def normalized_ode2tn(
213
234
  gamma: float,
214
235
  beta: float,
215
236
  scale: float,
216
- ) -> tuple[dict[sp.Symbol, sp.Expr], dict[sp.Symbol, float], dict[sp.Symbol, sp.Expr]]:
237
+ ) -> tuple[dict[sp.Symbol, sp.Expr], dict[sp.Symbol, float], dict[sp.Symbol, tuple[sp.Symbol, sp.Symbol]]]:
217
238
  # Assumes ode2tn has normalized and done error-checking
218
239
 
219
- sym2pair: dict[sp.Symbol, tuple[sp.Symbol, sp.Symbol]] = {}
220
- tn_ratios: dict[sp.Symbol, sp.Expr] = {}
240
+ tn_syms: dict[sp.Symbol, tuple[sp.Symbol, sp.Symbol]] = {}
221
241
  for x in odes.keys():
222
242
  # create x_t, x_b for each symbol x
223
- x_top, x_bot = sp.symbols(f'{x}_t {x}_b')
224
- sym2pair[x] = (x_top, x_bot)
225
- tn_ratios[x] = x_top / x_bot
243
+ x_t, x_b = sp.symbols(f'{x}_t {x}_b')
244
+ tn_syms[x] = (x_t, x_b)
226
245
 
227
246
  tn_odes: dict[sp.Symbol, sp.Expr] = {}
228
247
  tn_inits: dict[sp.Symbol, float] = {}
@@ -231,19 +250,20 @@ def normalized_ode2tn(
231
250
 
232
251
  # replace sym with sym_top / sym_bot for each original symbol sym
233
252
  for sym in odes.keys():
234
- sym_top, sym_bot = sym2pair[sym]
235
- p_pos = p_pos.subs(sym, sym_top / sym_bot)
236
- p_neg = p_neg.subs(sym, sym_top / sym_bot)
253
+ sym_top, sym_bot = tn_syms[sym]
254
+ ratio = sym_top / sym_bot
255
+ p_pos = p_pos.subs(sym, ratio)
256
+ p_neg = p_neg.subs(sym, ratio)
237
257
 
238
- x_top, x_bot = sym2pair[x]
258
+ x_t, x_b = tn_syms[x]
239
259
  # tn_odes[x_top] = beta + p_pos * x_bot - gamma * x_top
240
260
  # tn_odes[x_bot] = p_neg * x_bot ** 2 / x_top + beta * x_bot / x_top - gamma * x_bot
241
- tn_odes[x_top] = beta * x_top / x_bot + p_pos * x_bot - gamma * x_top
242
- tn_odes[x_bot] = beta + p_neg * x_bot ** 2 / x_top - gamma * x_bot
243
- tn_inits[x_top] = initial_values[x]*scale
244
- tn_inits[x_bot] = scale
261
+ tn_odes[x_t] = beta * x_t / x_b + p_pos * x_b - gamma * x_t
262
+ tn_odes[x_b] = beta + p_neg * x_b ** 2 / x_t - gamma * x_b
263
+ tn_inits[x_t] = initial_values.get(x, 0) * scale
264
+ tn_inits[x_b] = scale
245
265
 
246
- return tn_odes, tn_inits, tn_ratios
266
+ return tn_odes, tn_inits, tn_syms
247
267
 
248
268
 
249
269
  def split_polynomial(expr: sp.Expr) -> tuple[sp.Expr, sp.Expr]:
@@ -324,60 +344,46 @@ def comma_separated(elts: Iterable[Any]) -> str:
324
344
 
325
345
 
326
346
  def main():
327
- x_sp, y_sp = gp.species('x y')
328
- rxns = [
329
- x_sp >> x_sp + y_sp,
330
- (3 * y_sp | 2 * y_sp).k(11).r(16.5),
331
- (y_sp >> gp.empty).k(6.5),
332
- ]
333
- odes = gp.crn_to_odes(rxns)
334
- # extract symbols from odes
335
- for var in odes.keys():
336
- if var.name == 'x':
337
- x = var
338
- if var.name == 'y':
339
- y = var
340
- # for v,ode in odes.items():
341
- # print(f"{v}' = {ode}")
347
+ import gpac as gp
348
+ import numpy as np
349
+ import sympy as sp
350
+ from ode2tn import plot_tn, ode2tn
351
+
352
+ P = 1
353
+ I = 1
354
+ D = 1
355
+ val, setpoint, bias, integral, derivative_p, derivative_m, delayed_val = \
356
+ sp.symbols('val setpoint bias integral derivative_p derivative_m delayed_val')
357
+ proportional_term = P * (setpoint - val)
358
+ integral_term = I * (integral - 10)
359
+ # derivative_term = D * (temperature - delayed_temperature)
360
+ c = 1
361
+ odes = {
362
+ val: proportional_term + integral_term, # + derivative_term,
363
+ integral: setpoint - val,
364
+ delayed_val: c * (val - delayed_val),
365
+ setpoint: 0,
366
+ }
342
367
  inits = {
343
- x: 1,
344
- y: 0.5,
368
+ val: 5,
369
+ setpoint: 7,
370
+ integral: 10,
345
371
  }
346
- gamma = 20
347
- beta = 1
348
- scale = 0.1
349
- import numpy as np
350
- t_eval = np.linspace(0, 20, 500)
351
-
352
- tn_odes, tn_inits, tn_ratios = ode2tn(odes, inits, gamma=gamma, beta=beta, scale=scale)
353
- for ratio_symbol in tn_inits.keys():
354
- if ratio_symbol.name == 'x_t':
355
- xt = ratio_symbol
356
- if ratio_symbol.name == 'x_b':
357
- xb = ratio_symbol
358
- if ratio_symbol.name == 'y_t':
359
- yt = ratio_symbol
360
- if ratio_symbol.name == 'y_b':
361
- yb = ratio_symbol
362
- from IPython.display import display
363
- for sym, expr in tn_odes.items():
364
- print(f"{sym}' = ", end='')
365
- display(sp.simplify(expr))
366
- print(f'{tn_inits=}')
367
- print(f'{tn_ratios=}')
368
- # for var, val in tn_inits.items():
369
- # tn_inits[var] *= 0.1
370
- # print(f'after reducing t/b values by 10x: {tn_inits=}')
371
- figsize = (15, 6)
372
+ t_eval = np.linspace(0, 40, 500)
373
+ figsize = (16, 4)
372
374
  resets = {
373
- 5: {x: 0.5},
374
- 10: {x: 0.01},
375
- 15: {x: 0.5},
375
+ 10: {val: 9},
376
+ 20: {val: 3},
377
+ 30: {bias: 2},
376
378
  }
377
- scale = 0.1
378
- plot_tn(odes, inits, gamma=gamma, beta=beta, scale=scale,
379
- t_eval=t_eval, resets=resets, figure_size=figsize,
380
- symbols_to_plot=[[x, y], [xt, xb, yt, yb]])
379
+ gamma = 1
380
+ beta = 1
381
+ # plot_tn(odes, inits, t_eval, gamma=gamma, beta=beta, resets=resets, figure_size=figsize)
382
+ tn_odes, tn_inits, tn_syms = ode2tn(odes, inits, gamma=gamma, beta=beta)
383
+ val_top = tn_syms[val][0]
384
+ tn_odes[val_top] += bias
385
+ tn_odes[bias] = 0
386
+ gp.plot(tn_odes, tn_inits, t_eval, resets=resets, figure_size=figsize)
381
387
 
382
388
 
383
389
  if __name__ == '__main__':
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ode2tn
3
- Version: 1.0.0
3
+ Version: 1.0.1
4
4
  Summary: A Python package to turn arbitrary polynomial ODEs into a transcriptional network simulating it.
5
5
  Author-email: Dave Doty <doty@ucdavis.edu>
6
6
  License: MIT
@@ -15,7 +15,7 @@ Requires-Dist: sympy>=1.13
15
15
  Dynamic: license-file
16
16
 
17
17
  # ode-to-transcription-network
18
- ode2tn is a Python package to turn arbitrary polynomial ODEs into a transcriptional network simulating it.
18
+ ode2tn is a Python package to compile arbitrary polynomial ODEs into a transcriptional network simulating the ODEs.
19
19
 
20
20
  See this paper for details: TODO
21
21
 
@@ -53,12 +53,12 @@ inits = { # inits maps each symbol to its initial value
53
53
  gamma = 2 # uniform decay constant; should be set sufficiently large that ???
54
54
  beta = 1 # constant introduced to keep values from going to infinity or 0
55
55
  t_eval = np.linspace(0, 6*pi, 1000)
56
- tn_odes, tn_inits, tn_ratios = ode2tn(odes, inits, gamma, beta)
57
- for sym, expr in tn_odes.items():
58
- print(f"{sym}' = {expr}")
59
- print(f'{tn_inits=}')
60
- print(f'{tn_ratios=}')
61
- plot_tn(odes, inits, gamma, beta, t_eval=t_eval)
56
+ # tn_odes, tn_inits, tn_syms = ode2tn(odes, inits, gamma, beta)
57
+ # for sym, expr in tn_odes.items():
58
+ # print(f"{sym}' = {expr}")
59
+ # print(f'{tn_inits=}')
60
+ # print(f'{tn_syms=}')
61
+ plot_tn(odes, inits, gamma=gamma, beta=beta, t_eval=t_eval)
62
62
  ```
63
63
 
64
64
  This will print
@@ -69,7 +69,7 @@ x_b' = 2*x_b**2/x_t - 2*x_b + 1
69
69
  y_t' = 2*y_b - 2*y_t + y_t/y_b
70
70
  y_b' = -2*y_b + 1 + x_t*y_b**2/(x_b*y_t)
71
71
  tn_inits={x_t: 2, x_b: 1, y_t: 1, y_b: 1}
72
- tn_ratios={x: x_t/x_b, y: y_t/y_b}
72
+ tn_syms={x: (x_t, x_b), y: (y_t, y_b)}
73
73
  ```
74
74
 
75
75
  showing that the variables `x` and `y` have been replace by pairs `x_t,x_b` and `y_t,y_b`, whose ratios `x_t/x_b` and `y_t/y_b` will track the values of the original variable `x` and `y` over time.
@@ -85,7 +85,7 @@ The `OdeResult` object returned by `gpac.integrate_odes` is the same returned by
85
85
  t_eval = np.linspace(0, 2*pi, 5)
86
86
  sol = gp.integrate_odes(tn_odes, tn_inits, t_eval)
87
87
  print(f'times = {sol.t}')
88
- print(f'x_y = {sol.y[0]}')
88
+ print(f'x_t = {sol.y[0]}')
89
89
  print(f'x_b = {sol.y[1]}')
90
90
  print(f'y_t = {sol.y[2]}')
91
91
  print(f'y_b = {sol.y[3]}')
@@ -95,7 +95,7 @@ which would print
95
95
 
96
96
  ```
97
97
  times = [0. 1.57079633 3.14159265 4.71238898 6.28318531]
98
- x_y = [2. 1.78280757 3.67207594 2.80592514 1.71859172]
98
+ x_t = [2. 1.78280757 3.67207594 2.80592514 1.71859172]
99
99
  x_b = [1. 1.78425369 1.83663725 0.93260227 0.859926 ]
100
100
  y_t = [1. 1.87324904 2.14156469 2.10338162 2.74383426]
101
101
  y_b = [1. 0.93637933 0.71348949 1.05261915 2.78279691]
@@ -0,0 +1,7 @@
1
+ ode2tn/__init__.py,sha256=b_mINIsNfCWzgG7QVYMsRsWKDLvp2QKFAzRqWtYqwDA,30
2
+ ode2tn/transform.py,sha256=0ok_lcHA2lOV8yrB-MBC9es-i7zVP50EktKeuUuhs0k,16148
3
+ ode2tn-1.0.1.dist-info/licenses/LICENSE,sha256=VV9UH0kkG-2edZvwJOqgtN12bZIzs2vn9_cq1SjoUJc,1091
4
+ ode2tn-1.0.1.dist-info/METADATA,sha256=klLivWzfNVnR3VDVSkWvSuGQDnBrdLCpL7lUOpTCTlc,4228
5
+ ode2tn-1.0.1.dist-info/WHEEL,sha256=lTU6B6eIfYoiQJTZNc-fyaR6BpL6ehTzU3xGYxn2n8k,91
6
+ ode2tn-1.0.1.dist-info/top_level.txt,sha256=fPQ9s5yLIYfazJS7wBBfU9EsWa9RGALq8VL-wUYRlao,7
7
+ ode2tn-1.0.1.dist-info/RECORD,,
@@ -1,7 +0,0 @@
1
- ode2tn/__init__.py,sha256=b_mINIsNfCWzgG7QVYMsRsWKDLvp2QKFAzRqWtYqwDA,30
2
- ode2tn/transform.py,sha256=k_yzuUck-RGgS-WI7cUzNRfC4nKIMip_q5mJfVukg28,15847
3
- ode2tn-1.0.0.dist-info/licenses/LICENSE,sha256=VV9UH0kkG-2edZvwJOqgtN12bZIzs2vn9_cq1SjoUJc,1091
4
- ode2tn-1.0.0.dist-info/METADATA,sha256=UQ7FvLFnOyzWs9qK3fS8NZtsVxd8ESDwT3_QdHpl-cE,4198
5
- ode2tn-1.0.0.dist-info/WHEEL,sha256=lTU6B6eIfYoiQJTZNc-fyaR6BpL6ehTzU3xGYxn2n8k,91
6
- ode2tn-1.0.0.dist-info/top_level.txt,sha256=fPQ9s5yLIYfazJS7wBBfU9EsWa9RGALq8VL-wUYRlao,7
7
- ode2tn-1.0.0.dist-info/RECORD,,
File without changes