gotranx 0.2.2__tar.gz → 0.2.3__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-0.2.2 → gotranx-0.2.3}/PKG-INFO +2 -1
- {gotranx-0.2.2 → gotranx-0.2.3}/pyproject.toml +16 -12
- {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/__init__.py +2 -2
- {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/atoms.py +7 -2
- {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/cli/cellml2ode.py +3 -3
- gotranx-0.2.3/src/gotranx/codegen/__init__.py +26 -0
- {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/codegen/base.py +1 -2
- {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/codegen/c.py +21 -8
- gotranx-0.2.2/src/gotranx/save.py → gotranx-0.2.3/src/gotranx/codegen/ode.py +71 -32
- {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/codegen/python.py +39 -10
- {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/exceptions.py +17 -9
- {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/expressions.py +1 -1
- gotranx-0.2.3/src/gotranx/myokit.py +214 -0
- {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/ode.lark +3 -1
- {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/ode.py +17 -5
- {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/ode_component.py +62 -24
- gotranx-0.2.3/src/gotranx/save.py +23 -0
- {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx.egg-info/PKG-INFO +2 -1
- {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx.egg-info/SOURCES.txt +3 -4
- {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx.egg-info/requires.txt +1 -0
- gotranx-0.2.3/tests/test_conditionals.py +85 -0
- gotranx-0.2.3/tests/test_myokit.py +32 -0
- gotranx-0.2.2/src/gotranx/cellml/__init__.py +0 -23
- gotranx-0.2.2/src/gotranx/cellml/cellml.py +0 -1709
- gotranx-0.2.2/src/gotranx/cellml/mathml.py +0 -341
- gotranx-0.2.2/src/gotranx/codegen/__init__.py +0 -18
- gotranx-0.2.2/tests/test_cellml.py +0 -20
- gotranx-0.2.2/tests/test_conditionals.py +0 -29
- {gotranx-0.2.2 → gotranx-0.2.3}/README.md +0 -0
- {gotranx-0.2.2 → gotranx-0.2.3}/setup.cfg +0 -0
- {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/__main__.py +0 -0
- {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/cli/__init__.py +0 -0
- {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/cli/gotran2c.py +0 -0
- {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/cli/gotran2py.py +0 -0
- {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/codecomponent.py +0 -0
- {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/load.py +0 -0
- {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/parser.py +0 -0
- {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/schemes.py +0 -0
- {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/sympytools.py +0 -0
- {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/templates/__init__.py +0 -0
- {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/templates/c.py +0 -0
- {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/templates/python.py +0 -0
- {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/transformer.py +0 -0
- {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/units.py +0 -0
- {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx.egg-info/dependency_links.txt +0 -0
- {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx.egg-info/entry_points.txt +0 -0
- {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx.egg-info/not-zip-safe +0 -0
- {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx.egg-info/top_level.txt +0 -0
- {gotranx-0.2.2 → gotranx-0.2.3}/tests/test_atoms.py +0 -0
- {gotranx-0.2.2 → gotranx-0.2.3}/tests/test_c_codegen.py +0 -0
- {gotranx-0.2.2 → gotranx-0.2.3}/tests/test_cli.py +0 -0
- {gotranx-0.2.2 → gotranx-0.2.3}/tests/test_expressions.py +0 -0
- {gotranx-0.2.2 → gotranx-0.2.3}/tests/test_load.py +0 -0
- {gotranx-0.2.2 → gotranx-0.2.3}/tests/test_ode.py +0 -0
- {gotranx-0.2.2 → gotranx-0.2.3}/tests/test_ode_component.py +0 -0
- {gotranx-0.2.2 → gotranx-0.2.3}/tests/test_python_codegen.py +0 -0
- {gotranx-0.2.2 → gotranx-0.2.3}/tests/test_save.py +0 -0
- {gotranx-0.2.2 → gotranx-0.2.3}/tests/test_schemes.py +0 -0
- {gotranx-0.2.2 → gotranx-0.2.3}/tests/test_sympytools.py +0 -0
- {gotranx-0.2.2 → gotranx-0.2.3}/tests/test_transformer_assignments.py +0 -0
- {gotranx-0.2.2 → gotranx-0.2.3}/tests/test_transformer_parameters.py +0 -0
- {gotranx-0.2.2 → gotranx-0.2.3}/tests/test_transformer_states.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: gotranx
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: A declarative language describing ordinary differential equations
|
|
5
5
|
Author-email: Henrik Finsberg <henriknf@simula.no>
|
|
6
6
|
License: MIT
|
|
@@ -23,6 +23,7 @@ Requires-Dist: rich-click
|
|
|
23
23
|
Requires-Dist: structlog
|
|
24
24
|
Requires-Dist: sympy
|
|
25
25
|
Requires-Dist: typer
|
|
26
|
+
Requires-Dist: myokit
|
|
26
27
|
Requires-Dist: graphlib-backport; python_version < "3.9"
|
|
27
28
|
Requires-Dist: typing_extensions; python_version < "3.9"
|
|
28
29
|
Provides-Extra: dev
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "gotranx"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.3"
|
|
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"}
|
|
@@ -29,6 +29,7 @@ dependencies = [
|
|
|
29
29
|
"structlog",
|
|
30
30
|
"sympy",
|
|
31
31
|
"typer",
|
|
32
|
+
"myokit",
|
|
32
33
|
'graphlib-backport;python_version < "3.9"',
|
|
33
34
|
'typing_extensions;python_version < "3.9"',
|
|
34
35
|
]
|
|
@@ -91,13 +92,6 @@ omit = ["src/gotranx/__main__.py", "src/gotranx/templates/__init__.py"]
|
|
|
91
92
|
|
|
92
93
|
|
|
93
94
|
[tool.ruff]
|
|
94
|
-
# Enable pycodestyle (`E`) and Pyflakes (`F`) codes by default.
|
|
95
|
-
select = ["E", "F"]
|
|
96
|
-
ignore = ["E402", "E741", "E743", "E731"]
|
|
97
|
-
|
|
98
|
-
# Allow autofix for all enabled rules (when `--fix`) is provided.
|
|
99
|
-
fixable = ["A", "B", "C", "D", "E", "F"]
|
|
100
|
-
unfixable = []
|
|
101
95
|
|
|
102
96
|
# Exclude a variety of commonly ignored directories.
|
|
103
97
|
exclude = [
|
|
@@ -126,13 +120,23 @@ exclude = [
|
|
|
126
120
|
# Same as Black.
|
|
127
121
|
line-length = 100
|
|
128
122
|
|
|
123
|
+
# Assume Python 3.10.
|
|
124
|
+
target-version = "py310"
|
|
125
|
+
|
|
126
|
+
[tool.ruff.lint]
|
|
127
|
+
# Enable pycodestyle (`E`) and Pyflakes (`F`) codes by default.
|
|
128
|
+
select = ["E", "F"]
|
|
129
|
+
ignore = ["E402", "E741", "E743", "E731"]
|
|
130
|
+
|
|
131
|
+
# Allow autofix for all enabled rules (when `--fix`) is provided.
|
|
132
|
+
fixable = ["A", "B", "C", "D", "E", "F"]
|
|
133
|
+
unfixable = []
|
|
134
|
+
|
|
129
135
|
# Allow unused variables when underscore-prefixed.
|
|
130
136
|
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
|
131
137
|
|
|
132
|
-
# Assume Python 3.10.
|
|
133
|
-
target-version = "py310"
|
|
134
138
|
|
|
135
|
-
[tool.ruff.mccabe]
|
|
139
|
+
[tool.ruff.lint.mccabe]
|
|
136
140
|
# Unlike Flake8, default to a complexity level of 10.
|
|
137
141
|
max-complexity = 10
|
|
138
142
|
|
|
@@ -145,7 +149,7 @@ tag = true
|
|
|
145
149
|
sign_tags = false
|
|
146
150
|
tag_name = "v{new_version}"
|
|
147
151
|
tag_message = "Bump version: {current_version} → {new_version}"
|
|
148
|
-
current_version = "0.2.
|
|
152
|
+
current_version = "0.2.3"
|
|
149
153
|
|
|
150
154
|
|
|
151
155
|
[[tool.bumpversion.files]]
|
|
@@ -13,7 +13,7 @@ from . import units
|
|
|
13
13
|
from . import sympytools
|
|
14
14
|
from . import schemes
|
|
15
15
|
from . import templates
|
|
16
|
-
from . import
|
|
16
|
+
from . import myokit
|
|
17
17
|
from .load import load_ode
|
|
18
18
|
from .ode import ODE
|
|
19
19
|
from .ode_component import Component
|
|
@@ -47,5 +47,5 @@ __all__ = [
|
|
|
47
47
|
"sympytools",
|
|
48
48
|
"schemes",
|
|
49
49
|
"templates",
|
|
50
|
-
"
|
|
50
|
+
"myokit",
|
|
51
51
|
]
|
|
@@ -8,6 +8,7 @@ from structlog import get_logger
|
|
|
8
8
|
|
|
9
9
|
from .expressions import build_expression
|
|
10
10
|
from .units import ureg
|
|
11
|
+
from . import exceptions
|
|
11
12
|
|
|
12
13
|
logger = get_logger()
|
|
13
14
|
|
|
@@ -52,7 +53,7 @@ class Atom:
|
|
|
52
53
|
"""Base class for atoms"""
|
|
53
54
|
|
|
54
55
|
name: str = attr.ib()
|
|
55
|
-
value: float | Expression | sp.core.Number = attr.ib()
|
|
56
|
+
value: float | Expression | sp.core.Number | None = attr.ib()
|
|
56
57
|
components: tuple[str, ...] = attr.ib(default=("",))
|
|
57
58
|
description: str | None = attr.ib(None)
|
|
58
59
|
symbol: sp.Symbol = attr.ib(None)
|
|
@@ -132,10 +133,12 @@ class Expression:
|
|
|
132
133
|
class Assignment(Atom):
|
|
133
134
|
"""Assignments are object of the form `name = value`."""
|
|
134
135
|
|
|
135
|
-
value: Expression = attr.ib()
|
|
136
|
+
value: Expression | None = attr.ib()
|
|
136
137
|
expr: sp.Expr = attr.ib(sp.S.Zero)
|
|
137
138
|
|
|
138
139
|
def resolve_expression(self, symbols: dict[str, sp.Symbol]) -> Assignment:
|
|
140
|
+
if self.value is None:
|
|
141
|
+
raise exceptions.ResolveExpressionError(name=self.name)
|
|
139
142
|
expr = self.value.resolve(symbols)
|
|
140
143
|
return type(self)(
|
|
141
144
|
name=self.name,
|
|
@@ -201,6 +204,8 @@ class StateDerivative(Assignment):
|
|
|
201
204
|
state: State = attr.ib()
|
|
202
205
|
|
|
203
206
|
def resolve_expression(self, symbols: dict[str, sp.Symbol]) -> Assignment:
|
|
207
|
+
if self.value is None:
|
|
208
|
+
raise exceptions.ResolveExpressionError(name=self.name)
|
|
204
209
|
expr = self.value.resolve(symbols)
|
|
205
210
|
return StateDerivative(
|
|
206
211
|
name=self.name,
|
|
@@ -3,7 +3,7 @@ from pathlib import Path
|
|
|
3
3
|
import logging
|
|
4
4
|
import structlog
|
|
5
5
|
|
|
6
|
-
from ..
|
|
6
|
+
from ..myokit import cellml_to_gotran
|
|
7
7
|
|
|
8
8
|
logger = structlog.get_logger()
|
|
9
9
|
|
|
@@ -20,7 +20,7 @@ def main(
|
|
|
20
20
|
assert fname.suffix == ".cellml", f"File {fname} must be a cellml file"
|
|
21
21
|
assert fname.exists(), f"File {fname} does not exist"
|
|
22
22
|
logger.info(f"Converting {fname} to gotran ode file")
|
|
23
|
-
|
|
23
|
+
ode = cellml_to_gotran(fname)
|
|
24
24
|
out = fname.with_suffix(".ode") if outname is None else Path(outname)
|
|
25
|
-
|
|
25
|
+
ode.save(out)
|
|
26
26
|
logger.info(f"Wrote {out}")
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from . import base
|
|
2
|
+
from . import c
|
|
3
|
+
from . import python
|
|
4
|
+
from . import ode
|
|
5
|
+
|
|
6
|
+
from .c import CCodeGenerator, GotranCCodePrinter
|
|
7
|
+
from .python import PythonCodeGenerator, GotranPythonCodePrinter
|
|
8
|
+
from .base import CodeGenerator, Func, RHSArgument, SchemeArgument
|
|
9
|
+
from .ode import GotranODECodePrinter, BaseGotranODECodePrinter
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"base",
|
|
13
|
+
"c",
|
|
14
|
+
"CCodeGenerator",
|
|
15
|
+
"CodeGenerator",
|
|
16
|
+
"Func",
|
|
17
|
+
"RHSArgument",
|
|
18
|
+
"SchemeArgument",
|
|
19
|
+
"python",
|
|
20
|
+
"PythonCodeGenerator",
|
|
21
|
+
"GotranPythonCodePrinter",
|
|
22
|
+
"ode",
|
|
23
|
+
"GotranODECodePrinter",
|
|
24
|
+
"BaseGotranODECodePrinter",
|
|
25
|
+
"GotranCCodePrinter",
|
|
26
|
+
]
|
|
@@ -21,14 +21,27 @@ class GotranCCodePrinter(C99CodePrinter):
|
|
|
21
21
|
return self._print(str(float(flt)))
|
|
22
22
|
|
|
23
23
|
def _print_Piecewise(self, expr):
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
f"{super()._print(
|
|
31
|
-
|
|
24
|
+
if isinstance(expr.args[0][0], Assignment):
|
|
25
|
+
result = []
|
|
26
|
+
lhs = super()._print(expr.args[0][0].lhs)
|
|
27
|
+
result.append(f"{super()._print(lhs)} = ")
|
|
28
|
+
all_lsh_equal = True
|
|
29
|
+
for arg in expr.args:
|
|
30
|
+
result.append(f"({super()._print(arg[1])}) ? ")
|
|
31
|
+
result.append(f"{super()._print(arg[0].rhs)}")
|
|
32
|
+
result.append(" : ")
|
|
33
|
+
all_lsh_equal = all_lsh_equal and super()._print(arg[0].lhs) == lhs
|
|
34
|
+
|
|
35
|
+
assert all_lsh_equal, "All assignments in Piecewise must have the same lhs"
|
|
36
|
+
|
|
37
|
+
if super()._print(arg[1]) == "true":
|
|
38
|
+
result = result[:-3]
|
|
39
|
+
result.append(f"{super()._print(arg[0].rhs)}")
|
|
40
|
+
else:
|
|
41
|
+
raise ValueError("Last condition in Piecewise must be True")
|
|
42
|
+
|
|
43
|
+
result.append(";")
|
|
44
|
+
value = "".join(result)
|
|
32
45
|
else:
|
|
33
46
|
value = bool_to_int(super()._print_Piecewise(expr))
|
|
34
47
|
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from collections import defaultdict
|
|
4
|
-
from pathlib import Path
|
|
5
4
|
from typing import TypeVar, Callable
|
|
6
5
|
from functools import reduce
|
|
7
|
-
|
|
6
|
+
from sympy.printing.str import StrPrinter
|
|
8
7
|
from structlog import get_logger
|
|
9
8
|
|
|
10
|
-
from
|
|
11
|
-
from
|
|
12
|
-
from .
|
|
9
|
+
from .. import atoms
|
|
10
|
+
from ..ode import ODE
|
|
11
|
+
from .base import _print_Piecewise
|
|
13
12
|
|
|
14
13
|
T = TypeVar("T")
|
|
15
14
|
logger = get_logger()
|
|
@@ -25,16 +24,74 @@ def break_comment_at_80(acc, x):
|
|
|
25
24
|
return acc + " " + x
|
|
26
25
|
|
|
27
26
|
|
|
28
|
-
class
|
|
27
|
+
class BaseGotranODECodePrinter(StrPrinter):
|
|
28
|
+
def _print_Relational(self, expr):
|
|
29
|
+
# v = super()._print_Relational(expr)
|
|
30
|
+
lhs = self._print(expr.lhs)
|
|
31
|
+
rhs = self._print(expr.rhs)
|
|
32
|
+
|
|
33
|
+
relop2str = {
|
|
34
|
+
"<": "Lt",
|
|
35
|
+
"<=": "Le",
|
|
36
|
+
">": "Gt",
|
|
37
|
+
">=": "Ge",
|
|
38
|
+
"==": "Eq",
|
|
39
|
+
"!=": "Ne",
|
|
40
|
+
}
|
|
41
|
+
relop = relop2str[expr.rel_op]
|
|
42
|
+
print(expr, f"{relop}({lhs}, {rhs})")
|
|
43
|
+
return f"{relop}({lhs}, {rhs})"
|
|
44
|
+
|
|
45
|
+
def _print_Or(self, expr):
|
|
46
|
+
return f"Or({', '.join(self._print(a) for a in expr.args)})"
|
|
47
|
+
|
|
48
|
+
def _print_And(self, expr):
|
|
49
|
+
return f"And({', '.join(self._print(a) for a in expr.args)})"
|
|
50
|
+
|
|
51
|
+
def _print_BooleanFalse(self, expr):
|
|
52
|
+
return "0"
|
|
53
|
+
|
|
54
|
+
def _print_BooleanTrue(self, expr):
|
|
55
|
+
return "1"
|
|
56
|
+
|
|
57
|
+
def _print_Piecewise(self, expr):
|
|
58
|
+
conds, exprs = _print_Piecewise(self, expr)
|
|
59
|
+
|
|
60
|
+
result = []
|
|
61
|
+
|
|
62
|
+
for c, e in zip(conds, exprs):
|
|
63
|
+
result.append("Conditional(")
|
|
64
|
+
result.append(f"{c}")
|
|
65
|
+
result.append(", ")
|
|
66
|
+
result.append(f"{e}")
|
|
67
|
+
result.append(", ")
|
|
68
|
+
|
|
69
|
+
if c == "1":
|
|
70
|
+
result = result[:-6]
|
|
71
|
+
result.append(f", {e}")
|
|
72
|
+
else:
|
|
73
|
+
raise ValueError("Last condition in Piecewise must be True")
|
|
74
|
+
|
|
75
|
+
result.append(")" * (len(conds) - 1))
|
|
76
|
+
return "".join(result)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class GotranODECodePrinter(BaseGotranODECodePrinter):
|
|
80
|
+
def __init__(self, ode: ODE, *args, **kwargs) -> None:
|
|
81
|
+
super().__init__(*args, **kwargs)
|
|
82
|
+
self.ode = ode
|
|
83
|
+
|
|
29
84
|
def print_comments(self) -> str:
|
|
30
85
|
if len(self.ode.comments) == 0:
|
|
31
86
|
return ""
|
|
32
|
-
text = "
|
|
87
|
+
text = ""
|
|
33
88
|
|
|
34
89
|
for comment in self.ode.comments:
|
|
35
90
|
text += reduce(break_comment_at_80, comment.text.split(" "))
|
|
36
91
|
|
|
37
|
-
text
|
|
92
|
+
if len(text) > 0:
|
|
93
|
+
text = "# " + "\n# ".join(text.strip().split("\n"))
|
|
94
|
+
text += "\n\n"
|
|
38
95
|
return text
|
|
39
96
|
|
|
40
97
|
def print_states(self) -> str:
|
|
@@ -45,7 +102,7 @@ class GotranODECodePrinter(PythonCodeGenerator):
|
|
|
45
102
|
text = ""
|
|
46
103
|
for components, states in d.items():
|
|
47
104
|
text += start_odeblock("states", names=components) + "\n"
|
|
48
|
-
text += ", ".join([print_ScalarParam(s, doprint=self.
|
|
105
|
+
text += ", ".join([print_ScalarParam(s, doprint=self.doprint) for s in states])
|
|
49
106
|
text += "\n)\n\n"
|
|
50
107
|
return text
|
|
51
108
|
|
|
@@ -57,9 +114,7 @@ class GotranODECodePrinter(PythonCodeGenerator):
|
|
|
57
114
|
text = ""
|
|
58
115
|
for components, parameters in d.items():
|
|
59
116
|
text += start_odeblock("parameters", names=components) + "\n"
|
|
60
|
-
text += ",\n".join(
|
|
61
|
-
[print_ScalarParam(p, doprint=self.printer.doprint) for p in parameters]
|
|
62
|
-
)
|
|
117
|
+
text += ",\n".join([print_ScalarParam(p, doprint=self.doprint) for p in parameters])
|
|
63
118
|
text += "\n)\n\n"
|
|
64
119
|
|
|
65
120
|
return text
|
|
@@ -72,14 +127,11 @@ class GotranODECodePrinter(PythonCodeGenerator):
|
|
|
72
127
|
text = ""
|
|
73
128
|
for components, intermediates in d.items():
|
|
74
129
|
text += start_odeblock("expressions", names=components, is_expression=True) + "\n"
|
|
75
|
-
text += "\n".join([print_assignment(i) for i in intermediates])
|
|
130
|
+
text += "\n".join([print_assignment(i, doprint=self.doprint) for i in intermediates])
|
|
76
131
|
text += "\n\n"
|
|
77
132
|
|
|
78
133
|
return text
|
|
79
134
|
|
|
80
|
-
def format(self, code: str) -> str:
|
|
81
|
-
return self._formatter(code)
|
|
82
|
-
|
|
83
135
|
|
|
84
136
|
def start_odeblock(case: str, names: tuple[str, ...] = (), is_expression: bool = False):
|
|
85
137
|
if len(names) == 0 or (len(names) == 1 and names[0] == ""):
|
|
@@ -118,21 +170,8 @@ def print_ScalarParam(p: atoms.Atom, doprint: Callable[[str], str]) -> str:
|
|
|
118
170
|
return ret
|
|
119
171
|
|
|
120
172
|
|
|
121
|
-
def print_assignment(a: atoms.Assignment) -> str:
|
|
122
|
-
s = f"{a.name} = {a.expr}"
|
|
123
|
-
if a.unit_str is not None:
|
|
173
|
+
def print_assignment(a: atoms.Assignment, doprint: Callable[[str], str]) -> str:
|
|
174
|
+
s = f"{a.name} = {doprint(a.expr)}"
|
|
175
|
+
if a.unit_str is not None and a.unit_str != "1":
|
|
124
176
|
s += f" # {a.unit_str}"
|
|
125
177
|
return s
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
def write_ODE_to_ode_file(ode: ODE, path: Path) -> None:
|
|
129
|
-
# Just make sure it is a Path object
|
|
130
|
-
printer = GotranODECodePrinter(ode)
|
|
131
|
-
path = Path(path)
|
|
132
|
-
text = ""
|
|
133
|
-
text += printer.print_comments()
|
|
134
|
-
text += printer.print_states()
|
|
135
|
-
text += printer.print_parameters()
|
|
136
|
-
text += printer.print_assignments()
|
|
137
|
-
path.write_text(text)
|
|
138
|
-
logger.info(f"Wrote {path}")
|
|
@@ -30,20 +30,49 @@ class GotranPythonCodePrinter(PythonCodePrinter):
|
|
|
30
30
|
return self._print(str(float(flt)))
|
|
31
31
|
|
|
32
32
|
def _print_Piecewise(self, expr):
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
33
|
+
result = []
|
|
34
|
+
|
|
35
|
+
if isinstance(expr.args[0][0], Assignment):
|
|
36
|
+
lhs = super()._print(expr.args[0][0].lhs)
|
|
37
|
+
result.append(f"{super()._print(lhs)} = ")
|
|
38
|
+
all_lsh_equal = True
|
|
39
|
+
for arg in expr.args:
|
|
40
|
+
result.append("numpy.where(")
|
|
41
|
+
result.append(f"{super()._print(arg[1])}")
|
|
42
|
+
result.append(", ")
|
|
43
|
+
result.append(f"{super()._print(arg[0].rhs)}")
|
|
44
|
+
result.append(", ")
|
|
45
|
+
all_lsh_equal = all_lsh_equal and super()._print(arg[0].lhs) == lhs
|
|
46
|
+
|
|
47
|
+
assert all_lsh_equal, "All assignments in Piecewise must have the same lhs"
|
|
48
|
+
|
|
49
|
+
if super()._print(arg[1]) == "True":
|
|
50
|
+
result = result[:-6]
|
|
51
|
+
result.append(f", {super()._print(arg[0].rhs)}")
|
|
52
|
+
else:
|
|
53
|
+
raise ValueError("Last condition in Piecewise must be True")
|
|
54
|
+
|
|
55
|
+
result.append(")" * (len(expr.args) - 1))
|
|
41
56
|
|
|
42
57
|
else:
|
|
43
58
|
conds, exprs = _print_Piecewise(self, expr)
|
|
44
|
-
value = f"numpy.where({conds[0]}, {exprs[0]}, {exprs[1]})"
|
|
45
59
|
|
|
46
|
-
|
|
60
|
+
for c, e in zip(conds, exprs):
|
|
61
|
+
result.append("numpy.where(")
|
|
62
|
+
result.append(f"{c}")
|
|
63
|
+
result.append(", ")
|
|
64
|
+
result.append(f"{e}")
|
|
65
|
+
result.append(", ")
|
|
66
|
+
|
|
67
|
+
if c == "True":
|
|
68
|
+
result = result[:-6]
|
|
69
|
+
result.append(f", {e}")
|
|
70
|
+
else:
|
|
71
|
+
raise ValueError("Last condition in Piecewise must be True")
|
|
72
|
+
|
|
73
|
+
result.append(")" * (len(conds) - 1))
|
|
74
|
+
|
|
75
|
+
return "".join(result)
|
|
47
76
|
|
|
48
77
|
def _print_And(self, expr):
|
|
49
78
|
if len(expr.args) == 2:
|
|
@@ -6,17 +6,17 @@ from pathlib import Path
|
|
|
6
6
|
import lark
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
class
|
|
9
|
+
class GotranxError(Exception):
|
|
10
10
|
pass
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
@dataclass
|
|
14
|
-
class ODEFileNotFound(
|
|
14
|
+
class ODEFileNotFound(GotranxError):
|
|
15
15
|
fname: Path
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
@dataclass
|
|
19
|
-
class StateNotFoundInComponent(
|
|
19
|
+
class StateNotFoundInComponent(GotranxError):
|
|
20
20
|
state_name: str
|
|
21
21
|
component_name: str
|
|
22
22
|
|
|
@@ -28,7 +28,7 @@ class StateNotFoundInComponent(GotranParserError):
|
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
@dataclass
|
|
31
|
-
class ParameterNotFoundInComponent(
|
|
31
|
+
class ParameterNotFoundInComponent(GotranxError):
|
|
32
32
|
parameter_name: str
|
|
33
33
|
component_name: str
|
|
34
34
|
|
|
@@ -40,7 +40,7 @@ class ParameterNotFoundInComponent(GotranParserError):
|
|
|
40
40
|
|
|
41
41
|
|
|
42
42
|
@dataclass
|
|
43
|
-
class ComponentNotCompleteError(
|
|
43
|
+
class ComponentNotCompleteError(GotranxError):
|
|
44
44
|
component_name: str
|
|
45
45
|
missing_state_derivatives: list[str]
|
|
46
46
|
|
|
@@ -52,7 +52,7 @@ class ComponentNotCompleteError(GotranParserError):
|
|
|
52
52
|
|
|
53
53
|
|
|
54
54
|
@dataclass
|
|
55
|
-
class DuplicateSymbolError(
|
|
55
|
+
class DuplicateSymbolError(GotranxError):
|
|
56
56
|
duplicates: set[str]
|
|
57
57
|
|
|
58
58
|
def __str__(self) -> str:
|
|
@@ -64,7 +64,7 @@ class DuplicateSymbolError(GotranParserError):
|
|
|
64
64
|
|
|
65
65
|
|
|
66
66
|
@dataclass
|
|
67
|
-
class UnknownTreeTypeError(
|
|
67
|
+
class UnknownTreeTypeError(GotranxError):
|
|
68
68
|
datatype: str
|
|
69
69
|
atom: str
|
|
70
70
|
|
|
@@ -73,7 +73,7 @@ class UnknownTreeTypeError(GotranParserError):
|
|
|
73
73
|
|
|
74
74
|
|
|
75
75
|
@dataclass
|
|
76
|
-
class InvalidTreeError(
|
|
76
|
+
class InvalidTreeError(GotranxError):
|
|
77
77
|
tree: lark.Tree
|
|
78
78
|
|
|
79
79
|
def __str__(self) -> str:
|
|
@@ -81,9 +81,17 @@ class InvalidTreeError(GotranParserError):
|
|
|
81
81
|
|
|
82
82
|
|
|
83
83
|
@dataclass
|
|
84
|
-
class MissingSymbolError(
|
|
84
|
+
class MissingSymbolError(GotranxError, KeyError):
|
|
85
85
|
symbol: str
|
|
86
86
|
line_no: int
|
|
87
87
|
|
|
88
88
|
def __str__(self) -> str:
|
|
89
89
|
return f"Symbol {self.symbol!r} not found in line {self.line_no}"
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@dataclass
|
|
93
|
+
class ResolveExpressionError(GotranxError, ValueError):
|
|
94
|
+
name: str
|
|
95
|
+
|
|
96
|
+
def __str__(self) -> str:
|
|
97
|
+
return f"Unable to resolve expression for {self.name!r}"
|
|
@@ -139,7 +139,7 @@ def build_expression(
|
|
|
139
139
|
# Only exceptions is 'abs' which is 'Abs'
|
|
140
140
|
funcname = "Abs"
|
|
141
141
|
|
|
142
|
-
return getattr(sp, funcname)(expr2symbols(tree.children[1])
|
|
142
|
+
return getattr(sp, funcname)(*[expr2symbols(c) for c in tree.children[1:]])
|
|
143
143
|
|
|
144
144
|
if tree.data == "logicalfunc":
|
|
145
145
|
if tree.children[0] == "Conditional":
|