gotranx 2.0.0__tar.gz → 2.2.0__tar.gz
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.
- {gotranx-2.0.0/src/gotranx.egg-info → gotranx-2.2.0}/PKG-INFO +2 -1
- {gotranx-2.0.0 → gotranx-2.2.0}/pyproject.toml +3 -2
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx/atoms.py +0 -120
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx/cli/__init__.py +85 -1
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx/cli/gotran2c.py +2 -1
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx/cli/gotran2julia.py +2 -1
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx/cli/gotran2md.py +2 -1
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx/cli/gotran2mtk.py +2 -1
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx/cli/gotran2py.py +2 -1
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx/cli/gotran2ufl.py +2 -1
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx/cli/ode2cellml.py +4 -1
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx/cli/utils.py +12 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx/codegen/ufl.py +22 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx/load.py +18 -3
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx/ode.py +86 -7
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx/ode_component.py +0 -25
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx/schemes.py +6 -1
- gotranx-2.2.0/src/gotranx/singularities.py +1092 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx/sympytools.py +81 -0
- {gotranx-2.0.0 → gotranx-2.2.0/src/gotranx.egg-info}/PKG-INFO +2 -1
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx.egg-info/SOURCES.txt +4 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx.egg-info/requires.txt +1 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/tests/test_atoms.py +0 -77
- {gotranx-2.0.0 → gotranx-2.2.0}/tests/test_cli.py +67 -0
- gotranx-2.2.0/tests/test_determinism.py +89 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/tests/test_python_codegen.py +21 -16
- gotranx-2.2.0/tests/test_singularities.py +479 -0
- gotranx-2.2.0/tests/test_singularity_models.py +305 -0
- gotranx-2.2.0/tests/test_sympytools.py +177 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/tests/test_ufl_codegen.py +78 -0
- gotranx-2.0.0/tests/test_sympytools.py +0 -98
- {gotranx-2.0.0 → gotranx-2.2.0}/LICENSE +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/README.md +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/setup.cfg +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx/__init__.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx/__main__.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx/_enum.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx/cli/cellml2ode.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx/codegen/__init__.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx/codegen/base.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx/codegen/c.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx/codegen/jax.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx/codegen/julia.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx/codegen/markdown.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx/codegen/mtk.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx/codegen/ode.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx/codegen/python.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx/exceptions.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx/expressions.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx/linearization.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx/myokit.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx/ode.lark +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx/parser.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx/py.typed +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx/save.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx/templates/__init__.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx/templates/c.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx/templates/jax.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx/templates/julia.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx/templates/markdown.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx/templates/mtk.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx/templates/python.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx/templates/ufl.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx/transformer.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx/units.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx.egg-info/dependency_links.txt +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx.egg-info/entry_points.txt +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx.egg-info/not-zip-safe +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/src/gotranx.egg-info/top_level.txt +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/tests/test_benchmarks.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/tests/test_c_codegen.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/tests/test_comments.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/tests/test_conditionals.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/tests/test_deep_linearization_models.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/tests/test_demos.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/tests/test_expressions.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/tests/test_julia_codegen.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/tests/test_linearization.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/tests/test_load.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/tests/test_markdown_codegen.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/tests/test_mtk_codegen.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/tests/test_myokit.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/tests/test_ode.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/tests/test_ode_component.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/tests/test_operations.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/tests/test_save.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/tests/test_schemes.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/tests/test_subodes.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/tests/test_transformer_assignments.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/tests/test_transformer_parameters.py +0 -0
- {gotranx-2.0.0 → gotranx-2.2.0}/tests/test_transformer_states.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gotranx
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: A declarative language describing ordinary differential equations
|
|
5
5
|
Author-email: Henrik Finsberg <henriknf@simula.no>
|
|
6
6
|
License: MIT
|
|
@@ -47,6 +47,7 @@ Requires-Dist: pytest; extra == "test"
|
|
|
47
47
|
Requires-Dist: pytest-cov; extra == "test"
|
|
48
48
|
Requires-Dist: jax; extra == "test"
|
|
49
49
|
Requires-Dist: cmake; extra == "test"
|
|
50
|
+
Requires-Dist: fenics-ufl; extra == "test"
|
|
50
51
|
Requires-Dist: gotranx[formatters]; extra == "test"
|
|
51
52
|
Provides-Extra: benchmark
|
|
52
53
|
Requires-Dist: pytest-benchmark>=4.0.0; extra == "benchmark"
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "gotranx"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.2.0"
|
|
8
8
|
description = "A declarative language describing ordinary differential equations"
|
|
9
9
|
authors = [{name = "Henrik Finsberg", email = "henriknf@simula.no"}]
|
|
10
10
|
license = {text = "MIT"}
|
|
@@ -62,6 +62,7 @@ test = [
|
|
|
62
62
|
"pytest-cov",
|
|
63
63
|
"jax",
|
|
64
64
|
"cmake",
|
|
65
|
+
"fenics-ufl",
|
|
65
66
|
"gotranx[formatters]",
|
|
66
67
|
]
|
|
67
68
|
benchmark = [
|
|
@@ -171,7 +172,7 @@ tag = true
|
|
|
171
172
|
sign_tags = false
|
|
172
173
|
tag_name = "v{new_version}"
|
|
173
174
|
tag_message = "Bump version: {current_version} → {new_version}"
|
|
174
|
-
current_version = "2.
|
|
175
|
+
current_version = "2.2.0"
|
|
175
176
|
|
|
176
177
|
|
|
177
178
|
[[tool.bumpversion.files]]
|
|
@@ -192,53 +192,6 @@ class Expression:
|
|
|
192
192
|
return build_expression(self.tree, symbols=symbols)
|
|
193
193
|
|
|
194
194
|
|
|
195
|
-
@attr.s(frozen=True, kw_only=True, slots=True)
|
|
196
|
-
class Singularity:
|
|
197
|
-
symbol: sp.Symbol = attr.ib()
|
|
198
|
-
value: float | sp.core.Number = attr.ib()
|
|
199
|
-
replacement: sp.Expr = attr.ib()
|
|
200
|
-
|
|
201
|
-
@property
|
|
202
|
-
def is_infinite(self):
|
|
203
|
-
"""Return True if the replacement is potentially infinite"""
|
|
204
|
-
return self.replacement.has(sp.oo) or self.replacement.has(-sp.oo)
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
def remove_singularities(expr: sp.Expr, singularities: frozenset[Singularity]) -> sp.Expr:
|
|
208
|
-
"""Remove singularities from an expression recursively using Conditionals
|
|
209
|
-
|
|
210
|
-
Parameters
|
|
211
|
-
----------
|
|
212
|
-
expr : sp.Expr
|
|
213
|
-
The expression
|
|
214
|
-
singularities : frozenset[Singularity]
|
|
215
|
-
The singularities
|
|
216
|
-
|
|
217
|
-
Returns
|
|
218
|
-
-------
|
|
219
|
-
sp.Expr
|
|
220
|
-
The expression with singularities removed
|
|
221
|
-
"""
|
|
222
|
-
from .sympytools import Conditional
|
|
223
|
-
|
|
224
|
-
exprs = [
|
|
225
|
-
Conditional(
|
|
226
|
-
cond=sp.Eq(singularity.symbol, singularity.value),
|
|
227
|
-
true_value=singularity.replacement,
|
|
228
|
-
false_value=expr,
|
|
229
|
-
)
|
|
230
|
-
for singularity in singularities
|
|
231
|
-
if not singularity.is_infinite
|
|
232
|
-
]
|
|
233
|
-
if len(exprs) == 0:
|
|
234
|
-
logger.debug("No singularities to remove")
|
|
235
|
-
return expr
|
|
236
|
-
|
|
237
|
-
new_expr = sp.piecewise_fold(sum(exprs))
|
|
238
|
-
logger.debug("Removing singularities", new_expr=new_expr)
|
|
239
|
-
return new_expr
|
|
240
|
-
|
|
241
|
-
|
|
242
195
|
@attr.s(frozen=True, kw_only=True, slots=True)
|
|
243
196
|
class Assignment(Atom):
|
|
244
197
|
"""Assignments are object of the form `name = value`."""
|
|
@@ -266,79 +219,6 @@ class Assignment(Atom):
|
|
|
266
219
|
return True
|
|
267
220
|
return False
|
|
268
221
|
|
|
269
|
-
def singularities(self, lookup: dict[str, Atom]) -> frozenset[Singularity]:
|
|
270
|
-
"""Check if the expression has any singularities
|
|
271
|
-
and return a list of singularities"""
|
|
272
|
-
from sympy import singularities, limit
|
|
273
|
-
|
|
274
|
-
singularity_list: set[Singularity] = set()
|
|
275
|
-
if self.value is None:
|
|
276
|
-
return frozenset(singularity_list)
|
|
277
|
-
if self.expr == 0:
|
|
278
|
-
logger.warning(
|
|
279
|
-
f"Expression {self.name} is zero. Maybe you forgot to resolve the expression?"
|
|
280
|
-
)
|
|
281
|
-
return frozenset(singularity_list)
|
|
282
|
-
|
|
283
|
-
for dep in self.value.dependencies:
|
|
284
|
-
try:
|
|
285
|
-
var = lookup[dep]
|
|
286
|
-
except KeyError:
|
|
287
|
-
continue
|
|
288
|
-
|
|
289
|
-
if not var.is_stateful(lookup):
|
|
290
|
-
continue
|
|
291
|
-
|
|
292
|
-
values = singularities(self.expr, var.symbol)
|
|
293
|
-
|
|
294
|
-
if not values:
|
|
295
|
-
continue
|
|
296
|
-
|
|
297
|
-
if not isinstance(values, sp.sets.sets.FiniteSet):
|
|
298
|
-
continue
|
|
299
|
-
|
|
300
|
-
for value in values:
|
|
301
|
-
singularity_list.add(
|
|
302
|
-
Singularity(
|
|
303
|
-
symbol=var.symbol,
|
|
304
|
-
value=value,
|
|
305
|
-
replacement=limit(self.expr, var.symbol, value),
|
|
306
|
-
)
|
|
307
|
-
)
|
|
308
|
-
return frozenset(singularity_list)
|
|
309
|
-
|
|
310
|
-
def remove_singularities(self, lookup: dict[str, Atom]) -> Assignment:
|
|
311
|
-
"""Remove singularities from the assignment
|
|
312
|
-
|
|
313
|
-
Parameters
|
|
314
|
-
----------
|
|
315
|
-
lookup : dict[str, Atom]
|
|
316
|
-
A lookup table for atoms
|
|
317
|
-
|
|
318
|
-
Returns
|
|
319
|
-
-------
|
|
320
|
-
Assignment
|
|
321
|
-
A new assignment with singularities removed
|
|
322
|
-
"""
|
|
323
|
-
logger.debug("Removing singularities")
|
|
324
|
-
if singularities := self.singularities(lookup):
|
|
325
|
-
logger.debug("Singularities found", singularities=singularities)
|
|
326
|
-
new_expr = remove_singularities(self.expr, singularities)
|
|
327
|
-
return type(self)(
|
|
328
|
-
name=self.name,
|
|
329
|
-
value=self.value,
|
|
330
|
-
components=self.components,
|
|
331
|
-
unit_str=self.unit_str,
|
|
332
|
-
unit=self.unit,
|
|
333
|
-
expr=new_expr,
|
|
334
|
-
symbol=self.symbol,
|
|
335
|
-
description=self.description,
|
|
336
|
-
comment=self.comment,
|
|
337
|
-
)
|
|
338
|
-
|
|
339
|
-
# Do not make a copy if no singularities
|
|
340
|
-
return self
|
|
341
|
-
|
|
342
222
|
def resolve_expression(self, symbols: dict[str, sp.Symbol]) -> Assignment:
|
|
343
223
|
"""Resolve the expression of the assignment by
|
|
344
224
|
building the sympy expression from the expression tree
|
|
@@ -78,6 +78,14 @@ def convert(
|
|
|
78
78
|
"--remove-unused",
|
|
79
79
|
help="Remove unused variables",
|
|
80
80
|
),
|
|
81
|
+
remove_singularities: bool = typer.Option(
|
|
82
|
+
True,
|
|
83
|
+
"--remove-singularities/--no-remove-singularities",
|
|
84
|
+
help=(
|
|
85
|
+
"Replace a small neighborhood of every removable singularity "
|
|
86
|
+
"(e.g. x/(exp(x) - 1) at x = 0) with a truncated Taylor series"
|
|
87
|
+
),
|
|
88
|
+
),
|
|
81
89
|
jax: bool = typer.Option(
|
|
82
90
|
False,
|
|
83
91
|
"--jax",
|
|
@@ -148,6 +156,7 @@ def convert(
|
|
|
148
156
|
outname=outname,
|
|
149
157
|
scheme=scheme,
|
|
150
158
|
remove_unused=remove_unused,
|
|
159
|
+
remove_singularities=remove_singularities,
|
|
151
160
|
verbose=verbose,
|
|
152
161
|
stiff_states=stiff_states,
|
|
153
162
|
delta=delta,
|
|
@@ -160,6 +169,7 @@ def convert(
|
|
|
160
169
|
outname=outname,
|
|
161
170
|
scheme=scheme,
|
|
162
171
|
remove_unused=remove_unused,
|
|
172
|
+
remove_singularities=remove_singularities,
|
|
163
173
|
verbose=verbose,
|
|
164
174
|
stiff_states=stiff_states,
|
|
165
175
|
delta=delta,
|
|
@@ -302,6 +312,14 @@ def ode2py(
|
|
|
302
312
|
"--remove-unused",
|
|
303
313
|
help="Remove unused variables",
|
|
304
314
|
),
|
|
315
|
+
remove_singularities: bool = typer.Option(
|
|
316
|
+
True,
|
|
317
|
+
"--remove-singularities/--no-remove-singularities",
|
|
318
|
+
help=(
|
|
319
|
+
"Replace a small neighborhood of every removable singularity "
|
|
320
|
+
"(e.g. x/(exp(x) - 1) at x = 0) with a truncated Taylor series"
|
|
321
|
+
),
|
|
322
|
+
),
|
|
305
323
|
version: bool = typer.Option(
|
|
306
324
|
None,
|
|
307
325
|
"--version",
|
|
@@ -376,6 +394,9 @@ def ode2py(
|
|
|
376
394
|
delta = config_data.get("delta", delta)
|
|
377
395
|
stiff_states = config_data.get("stiff_states", stiff_states)
|
|
378
396
|
cse = utils.validate_cse(config_data.get("cse", cse))
|
|
397
|
+
remove_singularities = utils.validate_remove_singularities(
|
|
398
|
+
config_data.get("remove_singularities", remove_singularities)
|
|
399
|
+
)
|
|
379
400
|
scheme = config_data.get("scheme", scheme)
|
|
380
401
|
shape = Shape(config_data.get("shape", shape))
|
|
381
402
|
scheme = utils.validate_scheme(scheme)
|
|
@@ -388,6 +409,7 @@ def ode2py(
|
|
|
388
409
|
outname=outname,
|
|
389
410
|
scheme=scheme,
|
|
390
411
|
remove_unused=remove_unused,
|
|
412
|
+
remove_singularities=remove_singularities,
|
|
391
413
|
verbose=verbose,
|
|
392
414
|
stiff_states=stiff_states,
|
|
393
415
|
delta=delta,
|
|
@@ -425,6 +447,14 @@ def ode2c(
|
|
|
425
447
|
"--remove-unused",
|
|
426
448
|
help="Remove unused variables",
|
|
427
449
|
),
|
|
450
|
+
remove_singularities: bool = typer.Option(
|
|
451
|
+
True,
|
|
452
|
+
"--remove-singularities/--no-remove-singularities",
|
|
453
|
+
help=(
|
|
454
|
+
"Replace a small neighborhood of every removable singularity "
|
|
455
|
+
"(e.g. x/(exp(x) - 1) at x = 0) with a truncated Taylor series"
|
|
456
|
+
),
|
|
457
|
+
),
|
|
428
458
|
version: bool = typer.Option(
|
|
429
459
|
None,
|
|
430
460
|
"--version",
|
|
@@ -487,6 +517,9 @@ def ode2c(
|
|
|
487
517
|
delta = config_data.get("delta", delta)
|
|
488
518
|
stiff_states = config_data.get("stiff_states", stiff_states)
|
|
489
519
|
cse = utils.validate_cse(config_data.get("cse", cse))
|
|
520
|
+
remove_singularities = utils.validate_remove_singularities(
|
|
521
|
+
config_data.get("remove_singularities", remove_singularities)
|
|
522
|
+
)
|
|
490
523
|
scheme = config_data.get("scheme", scheme)
|
|
491
524
|
scheme = utils.validate_scheme(scheme)
|
|
492
525
|
c_config = config_data.get("c", {})
|
|
@@ -499,6 +532,7 @@ def ode2c(
|
|
|
499
532
|
outname=outname,
|
|
500
533
|
scheme=scheme,
|
|
501
534
|
remove_unused=remove_unused,
|
|
535
|
+
remove_singularities=remove_singularities,
|
|
502
536
|
verbose=verbose,
|
|
503
537
|
stiff_states=stiff_states,
|
|
504
538
|
delta=delta,
|
|
@@ -528,6 +562,14 @@ def ode2julia(
|
|
|
528
562
|
"--remove-unused",
|
|
529
563
|
help="Remove unused variables",
|
|
530
564
|
),
|
|
565
|
+
remove_singularities: bool = typer.Option(
|
|
566
|
+
True,
|
|
567
|
+
"--remove-singularities/--no-remove-singularities",
|
|
568
|
+
help=(
|
|
569
|
+
"Replace a small neighborhood of every removable singularity "
|
|
570
|
+
"(e.g. x/(exp(x) - 1) at x = 0) with a truncated Taylor series"
|
|
571
|
+
),
|
|
572
|
+
),
|
|
531
573
|
version: bool = typer.Option(
|
|
532
574
|
None,
|
|
533
575
|
"--version",
|
|
@@ -595,6 +637,9 @@ def ode2julia(
|
|
|
595
637
|
delta = config_data.get("delta", delta)
|
|
596
638
|
stiff_states = config_data.get("stiff_states", stiff_states)
|
|
597
639
|
cse = utils.validate_cse(config_data.get("cse", cse))
|
|
640
|
+
remove_singularities = utils.validate_remove_singularities(
|
|
641
|
+
config_data.get("remove_singularities", remove_singularities)
|
|
642
|
+
)
|
|
598
643
|
scheme = config_data.get("scheme", scheme)
|
|
599
644
|
scheme = utils.validate_scheme(scheme)
|
|
600
645
|
# c_config = config_data.get("c", {})
|
|
@@ -605,6 +650,7 @@ def ode2julia(
|
|
|
605
650
|
outname=outname,
|
|
606
651
|
scheme=scheme,
|
|
607
652
|
remove_unused=remove_unused,
|
|
653
|
+
remove_singularities=remove_singularities,
|
|
608
654
|
verbose=verbose,
|
|
609
655
|
stiff_states=stiff_states,
|
|
610
656
|
delta=delta,
|
|
@@ -635,6 +681,14 @@ def ode2mtk(
|
|
|
635
681
|
"--remove-unused",
|
|
636
682
|
help="Remove unused variables",
|
|
637
683
|
),
|
|
684
|
+
remove_singularities: bool = typer.Option(
|
|
685
|
+
True,
|
|
686
|
+
"--remove-singularities/--no-remove-singularities",
|
|
687
|
+
help=(
|
|
688
|
+
"Replace a small neighborhood of every removable singularity "
|
|
689
|
+
"(e.g. x/(exp(x) - 1) at x = 0) with a truncated Taylor series"
|
|
690
|
+
),
|
|
691
|
+
),
|
|
638
692
|
version: bool = typer.Option(
|
|
639
693
|
None,
|
|
640
694
|
"--version",
|
|
@@ -668,10 +722,14 @@ def ode2mtk(
|
|
|
668
722
|
config_data = utils.read_config(config)
|
|
669
723
|
verbose = config_data.get("verbose", verbose)
|
|
670
724
|
remove_unused = config_data.get("remove_unused", remove_unused)
|
|
725
|
+
remove_singularities = utils.validate_remove_singularities(
|
|
726
|
+
config_data.get("remove_singularities", remove_singularities)
|
|
727
|
+
)
|
|
671
728
|
gotran2mtk.main(
|
|
672
729
|
fname=fname,
|
|
673
730
|
outname=outname,
|
|
674
731
|
remove_unused=remove_unused,
|
|
732
|
+
remove_singularities=remove_singularities,
|
|
675
733
|
verbose=verbose,
|
|
676
734
|
)
|
|
677
735
|
|
|
@@ -750,11 +808,25 @@ def ode2md(
|
|
|
750
808
|
"--pdf",
|
|
751
809
|
help="Generate PDF output",
|
|
752
810
|
),
|
|
811
|
+
remove_singularities: bool = typer.Option(
|
|
812
|
+
True,
|
|
813
|
+
"--remove-singularities/--no-remove-singularities",
|
|
814
|
+
help=(
|
|
815
|
+
"Replace a small neighborhood of every removable singularity "
|
|
816
|
+
"(e.g. x/(exp(x) - 1) at x = 0) with a truncated Taylor series"
|
|
817
|
+
),
|
|
818
|
+
),
|
|
753
819
|
):
|
|
754
820
|
if fname is None:
|
|
755
821
|
return typer.echo("No file specified")
|
|
756
822
|
|
|
757
|
-
gotran2md.main(
|
|
823
|
+
gotran2md.main(
|
|
824
|
+
fname=fname,
|
|
825
|
+
outname=outname,
|
|
826
|
+
verbose=verbose,
|
|
827
|
+
pdf=pdf,
|
|
828
|
+
remove_singularities=remove_singularities,
|
|
829
|
+
)
|
|
758
830
|
|
|
759
831
|
|
|
760
832
|
@app.command()
|
|
@@ -779,6 +851,14 @@ def ode2ufl(
|
|
|
779
851
|
"--remove-unused",
|
|
780
852
|
help="Remove unused variables",
|
|
781
853
|
),
|
|
854
|
+
remove_singularities: bool = typer.Option(
|
|
855
|
+
True,
|
|
856
|
+
"--remove-singularities/--no-remove-singularities",
|
|
857
|
+
help=(
|
|
858
|
+
"Replace a small neighborhood of every removable singularity "
|
|
859
|
+
"(e.g. x/(exp(x) - 1) at x = 0) with a truncated Taylor series"
|
|
860
|
+
),
|
|
861
|
+
),
|
|
782
862
|
version: bool = typer.Option(
|
|
783
863
|
None,
|
|
784
864
|
"--version",
|
|
@@ -847,6 +927,9 @@ def ode2ufl(
|
|
|
847
927
|
delta = config_data.get("delta", delta)
|
|
848
928
|
stiff_states = config_data.get("stiff_states", stiff_states)
|
|
849
929
|
cse = utils.validate_cse(config_data.get("cse", cse))
|
|
930
|
+
remove_singularities = utils.validate_remove_singularities(
|
|
931
|
+
config_data.get("remove_singularities", remove_singularities)
|
|
932
|
+
)
|
|
850
933
|
scheme = config_data.get("scheme", scheme)
|
|
851
934
|
shape = Shape(config_data.get("shape", shape))
|
|
852
935
|
scheme = utils.validate_scheme(scheme)
|
|
@@ -858,6 +941,7 @@ def ode2ufl(
|
|
|
858
941
|
outname=outname,
|
|
859
942
|
scheme=scheme,
|
|
860
943
|
remove_unused=remove_unused,
|
|
944
|
+
remove_singularities=remove_singularities,
|
|
861
945
|
verbose=verbose,
|
|
862
946
|
stiff_states=stiff_states,
|
|
863
947
|
delta=delta,
|
|
@@ -97,6 +97,7 @@ def main(
|
|
|
97
97
|
outname: Path | str | None = None,
|
|
98
98
|
scheme: list[Scheme] | None = None,
|
|
99
99
|
remove_unused: bool = False,
|
|
100
|
+
remove_singularities: bool = True,
|
|
100
101
|
format: Format = Format.clang_format,
|
|
101
102
|
verbose: bool = False,
|
|
102
103
|
missing_values: dict[str, int] | None = None,
|
|
@@ -108,7 +109,7 @@ def main(
|
|
|
108
109
|
structlog.configure(
|
|
109
110
|
wrapper_class=structlog.make_filtering_bound_logger(loglevel),
|
|
110
111
|
)
|
|
111
|
-
ode = load_ode(fname)
|
|
112
|
+
ode = load_ode(fname, remove_singularities=remove_singularities)
|
|
112
113
|
code = get_code(
|
|
113
114
|
ode,
|
|
114
115
|
scheme=scheme,
|
|
@@ -97,6 +97,7 @@ def main(
|
|
|
97
97
|
outname: Path | str | None = None,
|
|
98
98
|
scheme: list[Scheme] | None = None,
|
|
99
99
|
remove_unused: bool = False,
|
|
100
|
+
remove_singularities: bool = True,
|
|
100
101
|
verbose: bool = False,
|
|
101
102
|
missing_values: dict[str, int] | None = None,
|
|
102
103
|
delta: float = 1e-8,
|
|
@@ -108,7 +109,7 @@ def main(
|
|
|
108
109
|
structlog.configure(
|
|
109
110
|
wrapper_class=structlog.make_filtering_bound_logger(loglevel),
|
|
110
111
|
)
|
|
111
|
-
ode = load_ode(fname)
|
|
112
|
+
ode = load_ode(fname, remove_singularities=remove_singularities)
|
|
112
113
|
code = get_code(
|
|
113
114
|
ode,
|
|
114
115
|
scheme=scheme,
|
|
@@ -14,13 +14,14 @@ def main(
|
|
|
14
14
|
outname: Path | str | None = None,
|
|
15
15
|
verbose: bool = False,
|
|
16
16
|
pdf: bool = False,
|
|
17
|
+
remove_singularities: bool = True,
|
|
17
18
|
) -> None:
|
|
18
19
|
loglevel = logging.DEBUG if verbose else logging.INFO
|
|
19
20
|
structlog.configure(
|
|
20
21
|
wrapper_class=structlog.make_filtering_bound_logger(loglevel),
|
|
21
22
|
)
|
|
22
23
|
|
|
23
|
-
ode = load_ode(fname)
|
|
24
|
+
ode = load_ode(fname, remove_singularities=remove_singularities)
|
|
24
25
|
generator = MarkdownGenerator(ode)
|
|
25
26
|
code = generator.generate()
|
|
26
27
|
|
|
@@ -24,13 +24,14 @@ def main(
|
|
|
24
24
|
fname: Path,
|
|
25
25
|
outname: Path | str | None = None,
|
|
26
26
|
remove_unused: bool = False,
|
|
27
|
+
remove_singularities: bool = True,
|
|
27
28
|
verbose: bool = False,
|
|
28
29
|
) -> None:
|
|
29
30
|
loglevel = logging.DEBUG if verbose else logging.INFO
|
|
30
31
|
structlog.configure(
|
|
31
32
|
wrapper_class=structlog.make_filtering_bound_logger(loglevel),
|
|
32
33
|
)
|
|
33
|
-
ode = load_ode(fname)
|
|
34
|
+
ode = load_ode(fname, remove_singularities=remove_singularities)
|
|
34
35
|
code = get_code(
|
|
35
36
|
ode,
|
|
36
37
|
remove_unused=remove_unused,
|
|
@@ -119,6 +119,7 @@ def main(
|
|
|
119
119
|
format: Format = Format.black,
|
|
120
120
|
scheme: list[Scheme] | None = None,
|
|
121
121
|
remove_unused: bool = False,
|
|
122
|
+
remove_singularities: bool = True,
|
|
122
123
|
verbose: bool = True,
|
|
123
124
|
stiff_states: list[str] | None = None,
|
|
124
125
|
cse: bool = True,
|
|
@@ -132,7 +133,7 @@ def main(
|
|
|
132
133
|
wrapper_class=structlog.make_filtering_bound_logger(loglevel),
|
|
133
134
|
)
|
|
134
135
|
|
|
135
|
-
ode = load_ode(fname)
|
|
136
|
+
ode = load_ode(fname, remove_singularities=remove_singularities)
|
|
136
137
|
|
|
137
138
|
code = get_code(
|
|
138
139
|
ode,
|
|
@@ -72,6 +72,7 @@ def main(
|
|
|
72
72
|
format: Format = Format.black,
|
|
73
73
|
scheme: list[Scheme] | None = None,
|
|
74
74
|
remove_unused: bool = False,
|
|
75
|
+
remove_singularities: bool = True,
|
|
75
76
|
verbose: bool = True,
|
|
76
77
|
stiff_states: list[str] | None = None,
|
|
77
78
|
cse: bool = True,
|
|
@@ -84,7 +85,7 @@ def main(
|
|
|
84
85
|
wrapper_class=structlog.make_filtering_bound_logger(loglevel),
|
|
85
86
|
)
|
|
86
87
|
|
|
87
|
-
ode = load_ode(fname)
|
|
88
|
+
ode = load_ode(fname, remove_singularities=remove_singularities)
|
|
88
89
|
|
|
89
90
|
code = get_code(
|
|
90
91
|
ode,
|
|
@@ -20,7 +20,10 @@ def main(
|
|
|
20
20
|
)
|
|
21
21
|
assert fname.suffix == ".ode", f"File {fname} must be an ode file"
|
|
22
22
|
assert fname.exists(), f"File {fname} does not exist"
|
|
23
|
-
|
|
23
|
+
# A model export, not code generation: writing gotranx's float guard
|
|
24
|
+
# windows into an interchange format would bake a numerical artifact
|
|
25
|
+
# into someone else's model.
|
|
26
|
+
ode = load_ode(fname, remove_singularities=False)
|
|
24
27
|
logger.info(f"Converting {fname} to CellML")
|
|
25
28
|
cellml_file = fname.with_suffix(".cellml") if outname is None else Path(outname)
|
|
26
29
|
gotran_to_cellml(ode, filename=cellml_file)
|
|
@@ -89,6 +89,18 @@ def validate_cse(cse: Any) -> bool:
|
|
|
89
89
|
return cse
|
|
90
90
|
|
|
91
91
|
|
|
92
|
+
def validate_remove_singularities(remove_singularities: Any) -> bool:
|
|
93
|
+
"""Reject a non-boolean `remove_singularities`, which can only reach here
|
|
94
|
+
from a config file -- where a string such as "false" is truthy and would
|
|
95
|
+
silently do the opposite of what it says."""
|
|
96
|
+
if not isinstance(remove_singularities, bool):
|
|
97
|
+
raise typer.BadParameter(
|
|
98
|
+
f"remove_singularities must be true or false, got {remove_singularities!r}",
|
|
99
|
+
param_hint="remove_singularities",
|
|
100
|
+
)
|
|
101
|
+
return remove_singularities
|
|
102
|
+
|
|
103
|
+
|
|
92
104
|
def validate_scheme(scheme: list[Scheme] | list[str]) -> list[Scheme]:
|
|
93
105
|
lst = []
|
|
94
106
|
for s in scheme:
|
|
@@ -74,6 +74,28 @@ class UFLPrinter(GotranPythonCodePrinter):
|
|
|
74
74
|
|
|
75
75
|
return "".join(result)
|
|
76
76
|
|
|
77
|
+
# Comparisons are printed as calls to the ufl functions, not as Python
|
|
78
|
+
# operators. With two numbers as operands (say a parameter passed as a
|
|
79
|
+
# float) an operator gives a Python bool, which ufl.conditional, ufl.And
|
|
80
|
+
# and ufl.Or refuse; the functions convert both operands with ufl.as_ufl.
|
|
81
|
+
# `==` and `!=` never give a condition: UFL keeps them for structural
|
|
82
|
+
# equality of expressions.
|
|
83
|
+
_comparisons = {
|
|
84
|
+
"==": "ufl.eq",
|
|
85
|
+
"!=": "ufl.ne",
|
|
86
|
+
"<": "ufl.lt",
|
|
87
|
+
"<=": "ufl.le",
|
|
88
|
+
">": "ufl.gt",
|
|
89
|
+
">=": "ufl.ge",
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
def _print_Relational(self, expr):
|
|
93
|
+
func = self._comparisons[expr.rel_op]
|
|
94
|
+
return f"{func}({self._print(expr.lhs)}, {self._print(expr.rhs)})"
|
|
95
|
+
|
|
96
|
+
# The Python printer's own _print_Equality would otherwise take precedence
|
|
97
|
+
_print_Equality = _print_Relational
|
|
98
|
+
|
|
77
99
|
def _print_And(self, expr):
|
|
78
100
|
args = [self._print(arg) for arg in expr.args]
|
|
79
101
|
return functools.reduce(lambda x, y: f"ufl.And({x}, {y})", args)
|
|
@@ -12,7 +12,7 @@ from .transformer import TreeToODE, LarkODE
|
|
|
12
12
|
logger = get_logger()
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
def ode_from_string(text: str, name="ode") -> ODE:
|
|
15
|
+
def ode_from_string(text: str, name="ode", *, remove_singularities: bool = True) -> ODE:
|
|
16
16
|
"""Create an ODE from a string
|
|
17
17
|
|
|
18
18
|
Parameters
|
|
@@ -21,6 +21,13 @@ def ode_from_string(text: str, name="ode") -> ODE:
|
|
|
21
21
|
The string to parse
|
|
22
22
|
name : str, optional
|
|
23
23
|
Name of the ODE, by default "ode"
|
|
24
|
+
remove_singularities : bool, optional
|
|
25
|
+
Replace a neighborhood of every removable singularity with a
|
|
26
|
+
truncated Taylor series, by default True. Pass False to get the
|
|
27
|
+
expressions exactly as written, which will divide by zero wherever the
|
|
28
|
+
model has a removable pole -- numpy warns and returns nan there, numba
|
|
29
|
+
crashes -- and whose derivative, which the Rush-Larsen schemes form, is
|
|
30
|
+
wrong by an O(1) amount with a possibly wrong sign well before that.
|
|
24
31
|
|
|
25
32
|
Returns
|
|
26
33
|
-------
|
|
@@ -37,18 +44,24 @@ def ode_from_string(text: str, name="ode") -> ODE:
|
|
|
37
44
|
name=name,
|
|
38
45
|
comments=result.comments,
|
|
39
46
|
)
|
|
47
|
+
if remove_singularities:
|
|
48
|
+
ode = ode.remove_singularities()
|
|
40
49
|
logger.info(f"Num states {ode.num_states}")
|
|
41
50
|
logger.info(f"Num parameters {ode.num_parameters}")
|
|
42
51
|
return ode
|
|
43
52
|
|
|
44
53
|
|
|
45
|
-
def load_ode(path: str | Path) -> ODE:
|
|
54
|
+
def load_ode(path: str | Path, *, remove_singularities: bool = True) -> ODE:
|
|
46
55
|
"""Load an ODE from a file
|
|
47
56
|
|
|
48
57
|
Parameters
|
|
49
58
|
----------
|
|
50
59
|
path : str | Path
|
|
51
60
|
Path to the file
|
|
61
|
+
remove_singularities : bool, optional
|
|
62
|
+
Replace a neighborhood of every removable singularity with a
|
|
63
|
+
truncated Taylor series, by default True. See
|
|
64
|
+
:func:`ode_from_string`.
|
|
52
65
|
|
|
53
66
|
Returns
|
|
54
67
|
-------
|
|
@@ -67,4 +80,6 @@ def load_ode(path: str | Path) -> ODE:
|
|
|
67
80
|
if not fname.is_file():
|
|
68
81
|
raise exceptions.ODEFileNotFound(fname)
|
|
69
82
|
|
|
70
|
-
return ode_from_string(
|
|
83
|
+
return ode_from_string(
|
|
84
|
+
fname.read_text(), name=fname.stem, remove_singularities=remove_singularities
|
|
85
|
+
)
|