ode2tn 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.
ode2tn/__init__.py ADDED
@@ -0,0 +1 @@
1
+ from ode2tn.transform import *
ode2tn/transform.py ADDED
@@ -0,0 +1,384 @@
1
+ from typing import Any, Iterable
2
+ import re
3
+ from typing import Callable
4
+
5
+ from scipy.integrate import OdeSolver
6
+ import gpac as gp
7
+ import sympy as sp
8
+ from scipy.integrate._ivp.ivp import OdeResult # noqa
9
+
10
+
11
+ def plot_tn(
12
+ odes: dict[sp.Symbol | str, sp.Expr | str | float],
13
+ initial_values: dict[sp.Symbol | str, float],
14
+ t_eval: Iterable[float] | None = None,
15
+ *,
16
+ gamma: float,
17
+ beta: float,
18
+ scale: float = 1.0,
19
+ t_span: tuple[float, float] | None = None,
20
+ resets: dict[float, dict[sp.Symbol | str, float]] | None = None,
21
+ dependent_symbols: dict[sp.Symbol | str, sp.Expr | str] | None = None,
22
+ figure_size: tuple[float, float] = (10, 3),
23
+ symbols_to_plot: Iterable[sp.Symbol | str] |
24
+ Iterable[Iterable[sp.Symbol | str]] |
25
+ str |
26
+ re.Pattern |
27
+ Iterable[re.Pattern] |
28
+ None = None,
29
+ show: bool = False,
30
+ method: str | OdeSolver = 'RK45',
31
+ dense_output: bool = False,
32
+ events: Callable | Iterable[Callable] | None = None,
33
+ vectorized: bool = False,
34
+ return_ode_result: bool = False,
35
+ args: tuple | None = None,
36
+ loc: str | tuple[float, float] = 'best',
37
+ **options,
38
+ ) -> OdeResult | None:
39
+ """
40
+ Plot transcription network (TN) ODEs and initial values.
41
+
42
+ For arguments other than odes, initial_values, gamma, and beta, see the documentation for
43
+ `plot` in the gpac library.
44
+
45
+ Args:
46
+ odes: polynomial ODEs,
47
+ dict of sp symbols or strings (representing symbols) to sympy expressions or strings or floats
48
+ (representing RHS of ODEs)
49
+ Raises ValueError if any of the ODEs RHS is not a polynomial
50
+ initial_values: initial values,
51
+ dict of sympy symbols or strings (representing symbols) to floats
52
+ gamma: coefficient of the negative linear term in the transcription network
53
+ beta: additive constant in x_top ODE
54
+ scale: "scaling factor" for the transcription network ODEs. Each variable `x` is replaced by a pair
55
+ (`x_top`, `x_bot`). The initial `x_bot` value is `scale`, and the initial `x_top` value is
56
+ `x*scale`.
57
+ resets:
58
+ If specified, this is a dict mapping times to "configurations"
59
+ (i.e., dict mapping symbols/str to values).
60
+ The configurations are used to set the values of the symbols manually during the ODE integration
61
+ at specific times.
62
+ Any symbols not appearing as keys in `resets` are left at their current values.
63
+ The keys can either represent the `x_top` or `x_bot` variables whose ratio represents the original variable
64
+ (a key in parameter `odes`), or the original variables themselves.
65
+ If a new `x_top` or `x_bot` variable is used, its value is set directly.
66
+ If an original variable `x` is used, its then the `x_top` and `x_bot` variables are set
67
+ as with transforming `inits` to `tn_inits` in `ode2tn`:
68
+ `x_top` is set to `x*scale`, and `x_bot` is set to `scale`.
69
+ The OdeResult returned (the one returned by `solve_ivp` in scipy) will have two additional fields:
70
+ `reset_times` and `reset_indices`, which are lists of the times and indices in `sol.t`
71
+ corresponding to the times when the resets were applied.
72
+ Raises a ValueError if any time lies outside the integration interval, or if `resets` is empty,
73
+ if a symbol is invalid, or if there are symbols representing both an original variable `x` and one of
74
+ its `x_top` or `x_bot` variables.
75
+
76
+ Returns:
77
+ Typically None, but if return_ode_result is True, returns the result of the ODE integration.
78
+ See documentation of `gpac.plot` for details.
79
+ """
80
+ tn_odes, tn_inits, tn_ratios = ode2tn(odes, initial_values, gamma=gamma, beta=beta, scale=scale)
81
+ dependent_symbols_tn = dict(dependent_symbols) if dependent_symbols is not None else {}
82
+ dependent_symbols_tn.update(tn_ratios)
83
+ symbols_to_plot = dependent_symbols_tn if symbols_to_plot is None else symbols_to_plot
84
+
85
+ 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]
118
+
119
+ return gp.plot(
120
+ odes=tn_odes,
121
+ initial_values=tn_inits,
122
+ t_eval=t_eval,
123
+ t_span=t_span,
124
+ dependent_symbols=dependent_symbols_tn,
125
+ resets=resets,
126
+ figure_size=figure_size,
127
+ symbols_to_plot=symbols_to_plot,
128
+ show=show,
129
+ method=method,
130
+ dense_output=dense_output,
131
+ events=events,
132
+ vectorized=vectorized,
133
+ return_ode_result=return_ode_result,
134
+ args=args,
135
+ loc=loc,
136
+ **options,
137
+ )
138
+
139
+
140
+ def ode2tn(
141
+ odes: dict[sp.Symbol | str, sp.Expr | str | float],
142
+ initial_values: dict[sp.Symbol | str, float],
143
+ *,
144
+ gamma: float,
145
+ beta: float,
146
+ scale: float = 1.0,
147
+ ) -> tuple[dict[sp.Symbol, sp.Expr], dict[sp.Symbol, float], dict[sp.Symbol, sp.Expr]]:
148
+ """
149
+ Maps polynomial ODEs and and initial values to transcription network (represented by ODEs with positive
150
+ Laurent polynomials and negative linear term) simulating it, as well as initial values.
151
+
152
+ Args:
153
+ odes: polynomial ODEs,
154
+ dict of sympy symbols or strings (representing symbols) to sympy expressions or strings or floats
155
+ (representing RHS of ODEs)
156
+ Raises ValueError if any of the ODEs RHS is not a polynomial
157
+ initial_values: initial values,
158
+ dict of sympy symbols or strings (representing symbols) to floats
159
+ gamma: coefficient of the negative linear term in the transcription network
160
+ beta: additive constant in x_top ODE
161
+ scale: "scaling factor" for the transcription network ODEs. Each variable `x` is replaced by a pair
162
+ (`x_top`, `x_bot`). The initial `x_bot` value is `scale`, and the initial `x_top` value is
163
+ `x*scale`.
164
+
165
+ 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``.
168
+ """
169
+ # 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()}
172
+
173
+ # normalize odes dict to use symbols as keys
174
+ odes_symbols = {}
175
+ symbols_found_in_expressions = set()
176
+ for symbol, expr in odes.items():
177
+ if isinstance(symbol, str):
178
+ symbol = sp.symbols(symbol)
179
+ if isinstance(expr, (str, int, float)):
180
+ expr = sp.sympify(expr)
181
+ symbols_found_in_expressions.update(expr.free_symbols)
182
+ odes_symbols[symbol] = expr
183
+
184
+ # ensure that all symbols that are keys in `initial_values` are also keys in `odes`
185
+ initial_values_keys = set(initial_values.keys())
186
+ odes_keys = set(odes_symbols.keys())
187
+ diff = initial_values_keys - odes_keys
188
+ if len(diff) > 0:
189
+ raise ValueError(f"\nInitial_values contains symbols that are not in odes: "
190
+ f"{comma_separated(diff)}"
191
+ f"\nHere are the symbols of the ODES: "
192
+ f"{comma_separated(odes_keys)}")
193
+
194
+ # ensure all symbols in expressions are keys in the odes dict
195
+ symbols_in_expressions_not_in_odes_keys = symbols_found_in_expressions - odes_keys
196
+ if len(symbols_in_expressions_not_in_odes_keys) > 0:
197
+ raise ValueError(f"Found symbols in expressions that are not keys in the odes dict: "
198
+ f"{symbols_in_expressions_not_in_odes_keys}\n"
199
+ f"The keys in the odes dict are: {odes_keys}")
200
+
201
+ # ensure all odes are polynomials
202
+ for symbol, expr in odes_symbols.items():
203
+ if not expr.is_polynomial():
204
+ raise ValueError(f"ODE for {symbol}' is not a polynomial: {expr}")
205
+
206
+ return normalized_ode2tn(odes, initial_values, gamma=gamma, beta=beta, scale=scale)
207
+
208
+
209
+ def normalized_ode2tn(
210
+ odes: dict[sp.Symbol, sp.Expr],
211
+ initial_values: dict[sp.Symbol, float],
212
+ *,
213
+ gamma: float,
214
+ beta: float,
215
+ scale: float,
216
+ ) -> tuple[dict[sp.Symbol, sp.Expr], dict[sp.Symbol, float], dict[sp.Symbol, sp.Expr]]:
217
+ # Assumes ode2tn has normalized and done error-checking
218
+
219
+ sym2pair: dict[sp.Symbol, tuple[sp.Symbol, sp.Symbol]] = {}
220
+ tn_ratios: dict[sp.Symbol, sp.Expr] = {}
221
+ for x in odes.keys():
222
+ # 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
226
+
227
+ tn_odes: dict[sp.Symbol, sp.Expr] = {}
228
+ tn_inits: dict[sp.Symbol, float] = {}
229
+ for x, expr in odes.items():
230
+ p_pos, p_neg = split_polynomial(expr)
231
+
232
+ # replace sym with sym_top / sym_bot for each original symbol sym
233
+ 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)
237
+
238
+ x_top, x_bot = sym2pair[x]
239
+ # tn_odes[x_top] = beta + p_pos * x_bot - gamma * x_top
240
+ # 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
245
+
246
+ return tn_odes, tn_inits, tn_ratios
247
+
248
+
249
+ def split_polynomial(expr: sp.Expr) -> tuple[sp.Expr, sp.Expr]:
250
+ """
251
+ Split a polynomial into two parts:
252
+ p1: monomials with positive coefficients
253
+ p2: monomials with negative coefficients (made positive)
254
+
255
+ Args:
256
+ expr: A sympy Expression that is a polynomial
257
+
258
+ Returns:
259
+ pair of sympy Expressions (`p1`, `p2`) such that expr = p1 - p2
260
+
261
+ Raises:
262
+ ValueError: If `expr` is not a polynomial. Note that the constants (sympy type ``Number``)
263
+ are not considered polynomials by the ``is_polynomial`` method, but we do consider them polynomials
264
+ and do not raise an exception in this case.
265
+ """
266
+ if expr.is_constant():
267
+ if expr < 0:
268
+ return sp.S(0), -expr
269
+ else:
270
+ return expr, sp.S(0)
271
+
272
+ # Verify it's a polynomial
273
+ if not expr.is_polynomial():
274
+ raise ValueError(f"Expression {expr} is not a polynomial")
275
+
276
+ # Initialize empty expressions for positive and negative parts
277
+ p_pos = sp.S(0)
278
+ p_neg = sp.S(0)
279
+
280
+ # Convert to expanded form to make sure all terms are separate
281
+ expanded = sp.expand(expr)
282
+
283
+ # For a sum, we can process each term
284
+ if expanded.is_Add:
285
+ for term in expanded.args:
286
+ # Get the coefficient
287
+ if term.is_Mul:
288
+ # For products, find the numeric coefficient
289
+ coeff = next((arg for arg in term.args if arg.is_number), 1)
290
+ else:
291
+ # For non-products (like just x or just a number)
292
+ coeff = 1 if not term.is_number else term
293
+
294
+ # Add to the appropriate part based on sign
295
+ if coeff > 0:
296
+ p_pos += term
297
+ else:
298
+ # For negative coefficients, add the negated term to p2
299
+ p_neg += -term
300
+ elif expanded.is_Mul:
301
+ # If it's a single term, just check the sign; is_Mul for things like x*y or -x (represented as -1*x)
302
+ coeff = next((arg for arg in expanded.args if arg.is_number), 1)
303
+ if coeff > 0:
304
+ p_pos = expanded
305
+ else:
306
+ p_neg = -expanded
307
+ elif expanded.is_Atom:
308
+ # since negative terms are technically Mul, i.e., -1*x, if it is an atom then it is positive
309
+ p_pos = expanded
310
+ else:
311
+ # For single constant terms without multiplication, just check the sign;
312
+ # in tests a term like -x is actually represented as -1*x, so that's covered by the above elif,
313
+ # but in case of a negative constant like -2, this is handled here
314
+ if expanded > 0:
315
+ p_pos = expanded
316
+ else:
317
+ p_neg = -expanded
318
+
319
+ return p_pos, p_neg
320
+
321
+
322
+ def comma_separated(elts: Iterable[Any]) -> str:
323
+ return ', '.join(str(elt) for elt in elts)
324
+
325
+
326
+ 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}")
342
+ inits = {
343
+ x: 1,
344
+ y: 0.5,
345
+ }
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
+ resets = {
373
+ 5: {x: 0.5},
374
+ 10: {x: 0.01},
375
+ 15: {x: 0.5},
376
+ }
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]])
381
+
382
+
383
+ if __name__ == '__main__':
384
+ main()
@@ -0,0 +1,104 @@
1
+ Metadata-Version: 2.4
2
+ Name: ode2tn
3
+ Version: 1.0.0
4
+ Summary: A Python package to turn arbitrary polynomial ODEs into a transcriptional network simulating it.
5
+ Author-email: Dave Doty <doty@ucdavis.edu>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/UC-Davis-molecular-computing/ode-to-transcription-network
8
+ Project-URL: Issues, https://github.com/UC-Davis-molecular-computing/ode-to-transcription-network/issues
9
+ Requires-Python: >=3.10
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: gpac
13
+ Requires-Dist: scipy>=1.15
14
+ Requires-Dist: sympy>=1.13
15
+ Dynamic: license-file
16
+
17
+ # ode-to-transcription-network
18
+ ode2tn is a Python package to turn arbitrary polynomial ODEs into a transcriptional network simulating it.
19
+
20
+ See this paper for details: TODO
21
+
22
+ ## Installation
23
+
24
+ Type `pip install ode2tn` at the command line.
25
+
26
+ ## Usage
27
+
28
+ See the [notebook.ipynb](notebook.ipynb) for more examples of usage.
29
+
30
+ The functions `ode2tn` and `plot_tn` are the main elements of the package.
31
+ `ode2tn` converts a system of arbitrary polynomial ODEs into another system of ODEs representing a transcriptional network as defined in the paper above.
32
+ Each variable $x$ in the original ODEs is represented by a pair of variables $x^\top,x^\bot$, whose ratio $x^\top / x^\bot$ follows the same dynamics in the transcriptional network as $x$ does in the original ODEs.
33
+ `plot_tn` does this conversion and then plots the ratios by default, although it can be customized what exactly is plotted;
34
+ see the documentation for [gpac.plot](https://gpac.readthedocs.io/en/latest/#gpac.ode.plot) for a description of all options.
35
+
36
+ Here is a typical way to call each function:
37
+
38
+ ```python
39
+ from math import pi
40
+ import numpy as np
41
+ import sympy as sp
42
+ from transform import plot_tn, ode2tn
43
+
44
+ x,y = sp.symbols('x y')
45
+ odes = { # odes dict maps each symbol to an expression for its time derivative
46
+ x: y-2,
47
+ y: -x+2,
48
+ }
49
+ inits = { # inits maps each symbol to its initial value
50
+ x: 2,
51
+ y: 1,
52
+ }
53
+ gamma = 2 # uniform decay constant; should be set sufficiently large that ???
54
+ beta = 1 # constant introduced to keep values from going to infinity or 0
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)
62
+ ```
63
+
64
+ This will print
65
+
66
+ ```
67
+ x_t' = x_b*y_t/y_b - 2*x_t + x_t/x_b
68
+ x_b' = 2*x_b**2/x_t - 2*x_b + 1
69
+ y_t' = 2*y_b - 2*y_t + y_t/y_b
70
+ y_b' = -2*y_b + 1 + x_t*y_b**2/(x_b*y_t)
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}
73
+ ```
74
+
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.
76
+ The function `plot_tn` above does this conversion and then plots the ratios.
77
+ Running the code above in a Jupyter notebook will print the above text and show this figure:
78
+
79
+ ![](sine-cosine-plot.png)
80
+
81
+ One could also hand the transcriptional network ODEs to gpac to integrate, if you want to directly access the data being plotted above.
82
+ The `OdeResult` object returned by `gpac.integrate_odes` is the same returned by [`scipy.integrate.solve_ivp`](https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.solve_ivp.html), where the return value `sol` has a field `sol.y` that has the values of the variables in the order they were inserted into `tn_odes`, which will be the same as the order in which the original variables `x` and `y` were inserted, with `x_t` coming before `x_b`:
83
+
84
+ ```
85
+ t_eval = np.linspace(0, 2*pi, 5)
86
+ sol = gp.integrate_odes(tn_odes, tn_inits, t_eval)
87
+ print(f'times = {sol.t}')
88
+ print(f'x_y = {sol.y[0]}')
89
+ print(f'x_b = {sol.y[1]}')
90
+ print(f'y_t = {sol.y[2]}')
91
+ print(f'y_b = {sol.y[3]}')
92
+ ```
93
+
94
+ which would print
95
+
96
+ ```
97
+ times = [0. 1.57079633 3.14159265 4.71238898 6.28318531]
98
+ x_y = [2. 1.78280757 3.67207594 2.80592514 1.71859172]
99
+ x_b = [1. 1.78425369 1.83663725 0.93260227 0.859926 ]
100
+ y_t = [1. 1.87324904 2.14156469 2.10338162 2.74383426]
101
+ y_b = [1. 0.93637933 0.71348949 1.05261915 2.78279691]
102
+ ```
103
+
104
+
@@ -0,0 +1,7 @@
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,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (78.1.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 UC-Davis Molecular Computing Group
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ ode2tn