gotranx 2.4.0__tar.gz → 2.6.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.4.0/src/gotranx.egg-info → gotranx-2.6.0}/PKG-INFO +2 -1
- {gotranx-2.4.0 → gotranx-2.6.0}/pyproject.toml +5 -3
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/cli/__init__.py +14 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/cli/gotran2cytozoo.py +5 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/cli/utils.py +55 -20
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/codegen/base.py +6 -4
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/codegen/c.py +1 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/codegen/cytozoo.py +26 -3
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/myokit.py +29 -14
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/singularities.py +289 -21
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/templates/c.py +4 -2
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/templates/cytozoo.py +32 -12
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/templates/julia.py +4 -2
- {gotranx-2.4.0 → gotranx-2.6.0/src/gotranx.egg-info}/PKG-INFO +2 -1
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx.egg-info/SOURCES.txt +2 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx.egg-info/requires.txt +1 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/tests/test_cli.py +47 -0
- gotranx-2.6.0/tests/test_julia_runs.py +116 -0
- gotranx-2.6.0/tests/test_missing_variables.py +372 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/tests/test_myokit.py +31 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/tests/test_singularities.py +131 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/tests/test_singularity_models.py +79 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/LICENSE +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/README.md +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/setup.cfg +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/__init__.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/__main__.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/_enum.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/atoms.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/cli/cellml2ode.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/cli/gotran2c.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/cli/gotran2julia.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/cli/gotran2md.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/cli/gotran2mtk.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/cli/gotran2py.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/cli/gotran2ufl.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/cli/ode2cellml.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/codegen/__init__.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/codegen/jax.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/codegen/julia.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/codegen/markdown.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/codegen/mtk.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/codegen/ode.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/codegen/python.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/codegen/ufl.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/exceptions.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/expressions.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/linearization.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/load.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/ode.lark +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/ode.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/ode_component.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/parser.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/py.typed +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/save.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/schemes.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/sympytools.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/templates/__init__.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/templates/jax.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/templates/markdown.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/templates/mtk.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/templates/python.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/templates/ufl.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/transformer.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx/units.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx.egg-info/dependency_links.txt +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx.egg-info/entry_points.txt +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx.egg-info/not-zip-safe +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/src/gotranx.egg-info/top_level.txt +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/tests/test_atoms.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/tests/test_benchmarks.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/tests/test_c_codegen.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/tests/test_comments.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/tests/test_conditionals.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/tests/test_cytozoo_codegen.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/tests/test_deep_linearization_models.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/tests/test_demos.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/tests/test_determinism.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/tests/test_expressions.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/tests/test_julia_codegen.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/tests/test_linearization.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/tests/test_load.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/tests/test_markdown_codegen.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/tests/test_min_max.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/tests/test_mtk_codegen.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/tests/test_ode.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/tests/test_ode_component.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/tests/test_operations.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/tests/test_python_codegen.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/tests/test_save.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/tests/test_schemes.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/tests/test_subodes.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/tests/test_sympytools.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/tests/test_transformer_assignments.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/tests/test_transformer_parameters.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/tests/test_transformer_states.py +0 -0
- {gotranx-2.4.0 → gotranx-2.6.0}/tests/test_ufl_codegen.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gotranx
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.6.0
|
|
4
4
|
Summary: A declarative language describing ordinary differential equations
|
|
5
5
|
Author-email: Henrik Finsberg <henriknf@simula.no>
|
|
6
6
|
License: MIT
|
|
@@ -12,6 +12,7 @@ Requires-Python: >=3.9
|
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
License-File: LICENSE
|
|
14
14
|
Requires-Dist: attrs
|
|
15
|
+
Requires-Dist: black
|
|
15
16
|
Requires-Dist: lark
|
|
16
17
|
Requires-Dist: pint
|
|
17
18
|
Requires-Dist: rich-click
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "gotranx"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.6.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"}
|
|
@@ -17,6 +17,7 @@ urls = {Homepage = "https://github.com/finsberg/gotranx"}
|
|
|
17
17
|
requires-python = ">=3.9"
|
|
18
18
|
dependencies = [
|
|
19
19
|
"attrs",
|
|
20
|
+
"black",
|
|
20
21
|
"lark",
|
|
21
22
|
"pint",
|
|
22
23
|
"rich-click",
|
|
@@ -97,7 +98,8 @@ test = "pytest"
|
|
|
97
98
|
testpaths = ["tests"]
|
|
98
99
|
markers = [
|
|
99
100
|
"benchmark: mark a test as a benchmark.",
|
|
100
|
-
"example: mark a test as an example."
|
|
101
|
+
"example: mark a test as an example.",
|
|
102
|
+
"julia: mark a test that runs generated code in Julia."
|
|
101
103
|
]
|
|
102
104
|
|
|
103
105
|
|
|
@@ -172,7 +174,7 @@ tag = true
|
|
|
172
174
|
sign_tags = false
|
|
173
175
|
tag_name = "v{new_version}"
|
|
174
176
|
tag_message = "Bump version: {current_version} → {new_version}"
|
|
175
|
-
current_version = "2.
|
|
177
|
+
current_version = "2.6.0"
|
|
176
178
|
|
|
177
179
|
|
|
178
180
|
[[tool.bumpversion.files]]
|
|
@@ -669,6 +669,18 @@ def ode2cytozoo(
|
|
|
669
669
|
None, "--model-name", help="Julia struct name (default: the ODE name in PascalCase)"
|
|
670
670
|
),
|
|
671
671
|
v_name: str = typer.Option("v", "--v-name", help="State holding the transmembrane potential"),
|
|
672
|
+
remove_component: typing.Optional[typing.List[str]] = typer.Option(
|
|
673
|
+
None,
|
|
674
|
+
"--remove-component",
|
|
675
|
+
help="Generate the model without this component (repeatable). What the rest "
|
|
676
|
+
"reads from it becomes an input: a trailing writable parameter, NaN until set",
|
|
677
|
+
),
|
|
678
|
+
keep_component: typing.Optional[str] = typer.Option(
|
|
679
|
+
None,
|
|
680
|
+
"--keep-component",
|
|
681
|
+
help="Generate only this component. What it reads from the other components "
|
|
682
|
+
"becomes an input: a trailing writable parameter, NaN until set",
|
|
683
|
+
),
|
|
672
684
|
remove_unused: bool = typer.Option(False, "--remove-unused", help="Remove unused variables"),
|
|
673
685
|
remove_singularities: bool = typer.Option(
|
|
674
686
|
True,
|
|
@@ -720,6 +732,8 @@ def ode2cytozoo(
|
|
|
720
732
|
remove_unused=remove_unused,
|
|
721
733
|
remove_singularities=remove_singularities,
|
|
722
734
|
verbose=verbose,
|
|
735
|
+
remove_component=remove_component,
|
|
736
|
+
keep_component=keep_component,
|
|
723
737
|
)
|
|
724
738
|
|
|
725
739
|
|
|
@@ -6,6 +6,7 @@ import structlog
|
|
|
6
6
|
from ..codegen.cytozoo import CytoZooCodeGenerator
|
|
7
7
|
from ..load import load_ode
|
|
8
8
|
from ..ode import ODE
|
|
9
|
+
from .utils import select_components
|
|
9
10
|
|
|
10
11
|
logger = structlog.get_logger()
|
|
11
12
|
|
|
@@ -87,6 +88,7 @@ def get_code(
|
|
|
87
88
|
"import CytoZoo: num_monitors, monitor_names, monitor_values!",
|
|
88
89
|
f"const NUM_STATES = {len(ode.states)};",
|
|
89
90
|
f"const NUM_PARAMS = {len(ode.parameters)};",
|
|
91
|
+
*([f"const NUM_MISSING = {len(ode.missing_variables)};"] if ode.missing_variables else []),
|
|
90
92
|
codegen.initial_parameter_values(),
|
|
91
93
|
codegen.initial_state_values(),
|
|
92
94
|
codegen.rhs(),
|
|
@@ -104,10 +106,13 @@ def main(
|
|
|
104
106
|
remove_unused: bool = False,
|
|
105
107
|
remove_singularities: bool = True,
|
|
106
108
|
verbose: bool = False,
|
|
109
|
+
remove_component: list[str] | None = None,
|
|
110
|
+
keep_component: str | None = None,
|
|
107
111
|
) -> None:
|
|
108
112
|
loglevel = logging.DEBUG if verbose else logging.INFO
|
|
109
113
|
structlog.configure(wrapper_class=structlog.make_filtering_bound_logger(loglevel))
|
|
110
114
|
ode = load_ode(fname, remove_singularities=remove_singularities)
|
|
115
|
+
ode = select_components(ode, remove=remove_component, keep=keep_component)
|
|
111
116
|
code = get_code(
|
|
112
117
|
ode,
|
|
113
118
|
model_name=model_name,
|
|
@@ -5,6 +5,7 @@ from pathlib import Path
|
|
|
5
5
|
import typer
|
|
6
6
|
|
|
7
7
|
from ..codegen import CodeGenerator
|
|
8
|
+
from ..ode import ODE
|
|
8
9
|
from ..schemes import Scheme, get_scheme
|
|
9
10
|
|
|
10
11
|
|
|
@@ -29,14 +30,34 @@ def add_schemes(
|
|
|
29
30
|
return comp
|
|
30
31
|
|
|
31
32
|
|
|
32
|
-
def
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
def _toml_loads(text: str) -> dict[str, Any]:
|
|
34
|
+
try:
|
|
35
|
+
import tomllib # Python >= 3.11
|
|
36
|
+
except ImportError:
|
|
37
|
+
import toml as tomllib # type: ignore
|
|
35
38
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
return tomllib.loads(text)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def find_pyproject_toml_config(start: Path | None = None) -> Path | None:
|
|
43
|
+
"""The nearest `pyproject.toml` with a `[tool.gotranx]` table, searching from
|
|
44
|
+
`start` (by default the current directory) up to the root of the file system.
|
|
45
|
+
|
|
46
|
+
A `pyproject.toml` that cannot be parsed is returned too, so that
|
|
47
|
+
`read_config` reports it rather than skipping it silently.
|
|
48
|
+
"""
|
|
49
|
+
start = Path.cwd() if start is None else start
|
|
50
|
+
for directory in (start, *start.parents):
|
|
51
|
+
path = directory / "pyproject.toml"
|
|
52
|
+
if not path.is_file():
|
|
53
|
+
continue
|
|
54
|
+
try:
|
|
55
|
+
config = _toml_loads(path.read_text())
|
|
56
|
+
except Exception:
|
|
57
|
+
return path
|
|
58
|
+
if "gotranx" in config.get("tool", {}):
|
|
59
|
+
return path
|
|
60
|
+
return None
|
|
40
61
|
|
|
41
62
|
|
|
42
63
|
def read_config(path: Path | None) -> dict[str, Any]:
|
|
@@ -50,20 +71,8 @@ def read_config(path: Path | None) -> dict[str, Any]:
|
|
|
50
71
|
if path is None:
|
|
51
72
|
return {}
|
|
52
73
|
|
|
53
|
-
# Try to read the configuration file
|
|
54
74
|
try:
|
|
55
|
-
|
|
56
|
-
import tomllib as toml
|
|
57
|
-
except ImportError:
|
|
58
|
-
# If tomllib is not available, try to use toml
|
|
59
|
-
try:
|
|
60
|
-
import toml # type: ignore
|
|
61
|
-
except ImportError:
|
|
62
|
-
typer.echo("Please install 'tomllib' or 'toml' to read configuration files")
|
|
63
|
-
return {}
|
|
64
|
-
|
|
65
|
-
try:
|
|
66
|
-
config = toml.loads(Path(path).read_text())
|
|
75
|
+
config = _toml_loads(Path(path).read_text())
|
|
67
76
|
except Exception:
|
|
68
77
|
typer.echo(f"Could not read configuration file {path}")
|
|
69
78
|
return {}
|
|
@@ -109,3 +118,29 @@ def validate_scheme(scheme: list[Scheme] | list[str]) -> list[Scheme]:
|
|
|
109
118
|
else:
|
|
110
119
|
lst.append(s)
|
|
111
120
|
return lst
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def select_components(ode: ODE, remove: list[str] | None = None, keep: str | None = None) -> ODE:
|
|
124
|
+
"""The part of `ode` to generate code for: without the components in `remove`
|
|
125
|
+
(`ode - ode.get_component(name)` for each), or only the component `keep`
|
|
126
|
+
(`ode.get_component(keep).to_ode()`). Names the parts share become missing
|
|
127
|
+
variables of the result."""
|
|
128
|
+
if remove and keep is not None:
|
|
129
|
+
raise typer.BadParameter("give --remove-component or --keep-component, not both")
|
|
130
|
+
names = [c.name for c in ode.components]
|
|
131
|
+
for name in [*(remove or []), *([keep] if keep is not None else [])]:
|
|
132
|
+
if name not in names:
|
|
133
|
+
raise typer.BadParameter(
|
|
134
|
+
f"no component {name!r} in {ode.name}; it has {', '.join(names)}",
|
|
135
|
+
param_hint="--remove-component" if keep is None else "--keep-component",
|
|
136
|
+
)
|
|
137
|
+
if keep is not None:
|
|
138
|
+
return ode.get_component(keep).to_ode()
|
|
139
|
+
if not remove:
|
|
140
|
+
return ode
|
|
141
|
+
# `ode - component` is named "<ode> - <component>", which is not a valid default
|
|
142
|
+
# model name (ode2cytozoo derives the struct name from it).
|
|
143
|
+
name = f"{ode.name}_without_{'_'.join(remove)}"
|
|
144
|
+
for comp in remove:
|
|
145
|
+
ode = ode - ode.get_component(comp)
|
|
146
|
+
return ODE(components=ode.components, t=ode.t, name=name, comments=ode.comments)
|
|
@@ -106,6 +106,8 @@ class Shape(str, Enum):
|
|
|
106
106
|
|
|
107
107
|
class CodeGenerator(abc.ABC):
|
|
108
108
|
variable_prefix = ""
|
|
109
|
+
#: How a function declares its missing-variables argument (C needs a type).
|
|
110
|
+
missing_variables_argument = "missing_variables"
|
|
109
111
|
|
|
110
112
|
def __init__(
|
|
111
113
|
self,
|
|
@@ -321,7 +323,7 @@ class CodeGenerator(abc.ABC):
|
|
|
321
323
|
|
|
322
324
|
arguments = rhs.arguments
|
|
323
325
|
if self._missing_variables:
|
|
324
|
-
arguments += [
|
|
326
|
+
arguments += [self.missing_variables_argument]
|
|
325
327
|
|
|
326
328
|
values_lst = []
|
|
327
329
|
index = 0
|
|
@@ -383,7 +385,7 @@ class CodeGenerator(abc.ABC):
|
|
|
383
385
|
|
|
384
386
|
arguments = rhs.arguments
|
|
385
387
|
if self._missing_variables:
|
|
386
|
-
arguments += [
|
|
388
|
+
arguments += [self.missing_variables_argument]
|
|
387
389
|
|
|
388
390
|
values_lst = []
|
|
389
391
|
index = 0
|
|
@@ -429,7 +431,7 @@ class CodeGenerator(abc.ABC):
|
|
|
429
431
|
|
|
430
432
|
arguments = rhs.arguments
|
|
431
433
|
if self._missing_variables:
|
|
432
|
-
arguments += [
|
|
434
|
+
arguments += [self.missing_variables_argument]
|
|
433
435
|
|
|
434
436
|
values_lst = []
|
|
435
437
|
N = len(values)
|
|
@@ -495,7 +497,7 @@ class CodeGenerator(abc.ABC):
|
|
|
495
497
|
|
|
496
498
|
arguments = rhs.arguments
|
|
497
499
|
if self._missing_variables:
|
|
498
|
-
arguments += [
|
|
500
|
+
arguments += [self.missing_variables_argument]
|
|
499
501
|
|
|
500
502
|
dt = sympy.Symbol("dt")
|
|
501
503
|
eqs = f(
|
|
@@ -81,6 +81,7 @@ class GotranCCodePrinter(C99CodePrinter):
|
|
|
81
81
|
|
|
82
82
|
class CCodeGenerator(CodeGenerator):
|
|
83
83
|
variable_prefix = "const double "
|
|
84
|
+
missing_variables_argument = "const double *__restrict missing_variables"
|
|
84
85
|
|
|
85
86
|
def __init__(
|
|
86
87
|
self, ode: ODE, format: Format = Format.clang_format, remove_unused: bool = False
|
|
@@ -68,7 +68,10 @@ class CytoZooCodeGenerator(JuliaCodeGenerator):
|
|
|
68
68
|
return [s.name for s in self.ode.sorted_states()]
|
|
69
69
|
|
|
70
70
|
def _parameter_name_list(self) -> list[str]:
|
|
71
|
-
|
|
71
|
+
# The missing variables follow the parameters, in missing_index order, because
|
|
72
|
+
# the adapter stores them in the parameter vector (templates.cytozoo.model_struct).
|
|
73
|
+
missing = sorted(self._missing_variables, key=self._missing_variables.__getitem__)
|
|
74
|
+
return [p.name for p in self.ode.parameters] + missing
|
|
72
75
|
|
|
73
76
|
def _monitor_name_list(self) -> list[str]:
|
|
74
77
|
# Mirror codegen.base.CodeGenerator.monitor_index (base.py:186) exactly:
|
|
@@ -142,18 +145,38 @@ class CytoZooCodeGenerator(JuliaCodeGenerator):
|
|
|
142
145
|
)
|
|
143
146
|
return "\n".join(lines)
|
|
144
147
|
|
|
148
|
+
def _missing_variables_assignments(self) -> str:
|
|
149
|
+
"""Every missing-variable local, converted to the state's element type.
|
|
150
|
+
|
|
151
|
+
The missing variables are trailing entries of the parameter vector (see
|
|
152
|
+
`templates.cytozoo.model_struct`), so they are converted like the parameters:
|
|
153
|
+
a model built in Float64 and evaluated on Float32 states would otherwise
|
|
154
|
+
compute every term that reads one in Float64.
|
|
155
|
+
"""
|
|
156
|
+
if not self._missing_variables:
|
|
157
|
+
return ""
|
|
158
|
+
lines = [
|
|
159
|
+
f"{name} = TYPE(missing_variables[{index + 1}])"
|
|
160
|
+
for name, index in self._missing_variables.items()
|
|
161
|
+
]
|
|
162
|
+
return "\n".join(["", self._comment("Assign missing variables"), *lines, ""])
|
|
163
|
+
|
|
145
164
|
def adapter(self) -> str:
|
|
146
165
|
"""The struct, name tuples, index lookups and interface methods."""
|
|
147
166
|
name = self._model_name
|
|
148
167
|
monitors = self._monitor_name_list()
|
|
149
168
|
parts = [
|
|
150
|
-
templates.cytozoo.model_struct(
|
|
169
|
+
templates.cytozoo.model_struct(
|
|
170
|
+
name, len(self.ode.parameters), len(self._missing_variables)
|
|
171
|
+
),
|
|
151
172
|
templates.cytozoo.name_tuples(
|
|
152
173
|
name, self._state_name_list(), self._parameter_name_list(), monitors
|
|
153
174
|
),
|
|
154
175
|
templates.cytozoo.index_lookups(
|
|
155
176
|
name, self._state_name_list(), self._parameter_name_list()
|
|
156
177
|
),
|
|
157
|
-
templates.cytozoo.interface_methods(
|
|
178
|
+
templates.cytozoo.interface_methods(
|
|
179
|
+
name, self._potential_index(), len(monitors), len(self._missing_variables)
|
|
180
|
+
),
|
|
158
181
|
]
|
|
159
182
|
return self._format("\n".join(parts))
|
|
@@ -4,18 +4,14 @@ import functools
|
|
|
4
4
|
import re
|
|
5
5
|
import warnings
|
|
6
6
|
from pathlib import Path
|
|
7
|
-
from
|
|
7
|
+
from types import ModuleType
|
|
8
|
+
from typing import TYPE_CHECKING, overload
|
|
8
9
|
from collections.abc import Sequence
|
|
9
10
|
|
|
10
11
|
import sympy as sp
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
if TYPE_CHECKING:
|
|
13
14
|
import myokit
|
|
14
|
-
import myokit.formats.sympy
|
|
15
|
-
import myokit.lib.guess
|
|
16
|
-
import myokit.formats.cellml
|
|
17
|
-
except ImportError:
|
|
18
|
-
myokit = None
|
|
19
15
|
|
|
20
16
|
from . import atoms
|
|
21
17
|
from .ode_component import BaseComponent, MyokitComponent
|
|
@@ -25,9 +21,24 @@ from .exceptions import GotranxImportError
|
|
|
25
21
|
reserved_names = {name for name in dir(sp) if not name.startswith("_")}
|
|
26
22
|
|
|
27
23
|
|
|
24
|
+
def _import_myokit() -> ModuleType:
|
|
25
|
+
"""myokit, with the submodules used here, imported by the functions that need it.
|
|
26
|
+
|
|
27
|
+
Importing myokit imports numpy and lxml, which only reading and writing CellML
|
|
28
|
+
and `.mmt` files needs, so `import gotranx` leaves it alone.
|
|
29
|
+
"""
|
|
30
|
+
try:
|
|
31
|
+
import myokit
|
|
32
|
+
import myokit.formats.cellml
|
|
33
|
+
import myokit.formats.sympy
|
|
34
|
+
import myokit.lib.guess
|
|
35
|
+
except ImportError as e:
|
|
36
|
+
raise GotranxImportError("myokit") from e
|
|
37
|
+
return myokit
|
|
38
|
+
|
|
39
|
+
|
|
28
40
|
def assert_has_myokit():
|
|
29
|
-
|
|
30
|
-
raise GotranxImportError("myokit")
|
|
41
|
+
_import_myokit()
|
|
31
42
|
|
|
32
43
|
|
|
33
44
|
_INVALID_NAME_CHARS = re.compile(r"\W+")
|
|
@@ -224,7 +235,7 @@ def mmt_to_gotran(filename: str | Path) -> ODE:
|
|
|
224
235
|
gotranx.ode.ODE
|
|
225
236
|
The gotran ODE
|
|
226
237
|
"""
|
|
227
|
-
|
|
238
|
+
myokit = _import_myokit()
|
|
228
239
|
model, protocol, _ = myokit.load(filename)
|
|
229
240
|
|
|
230
241
|
return myokit_to_gotran(model, protocol=protocol)
|
|
@@ -245,7 +256,7 @@ def myokit_to_gotran(model: "myokit.Model", protocol=None) -> ODE:
|
|
|
245
256
|
gotranx.ode.ODE
|
|
246
257
|
The gotran ODE
|
|
247
258
|
"""
|
|
248
|
-
|
|
259
|
+
myokit = _import_myokit()
|
|
249
260
|
# Embed protocol
|
|
250
261
|
if protocol is not None:
|
|
251
262
|
model = model.clone()
|
|
@@ -358,6 +369,10 @@ def _sympy_reader(model: "myokit.Model") -> "myokit.formats.sympy.SymPyExpressio
|
|
|
358
369
|
each ``Min``/``Max`` is read into nested myokit ``If`` expressions, and ``And``/``Or`` are
|
|
359
370
|
folded into nested binary ones.
|
|
360
371
|
"""
|
|
372
|
+
_import_myokit()
|
|
373
|
+
# An import statement rather than `myokit = _import_myokit()`, so that mypy can
|
|
374
|
+
# resolve the base class below.
|
|
375
|
+
import myokit.formats.sympy
|
|
361
376
|
|
|
362
377
|
class Reader(myokit.formats.sympy.SymPyExpressionReader):
|
|
363
378
|
def _build_op_map(self):
|
|
@@ -397,7 +412,7 @@ def gotran_to_myokit(ode: ODE, time_component="engine", time_unit="s") -> "myoki
|
|
|
397
412
|
myokit.Model
|
|
398
413
|
The myokit model
|
|
399
414
|
"""
|
|
400
|
-
|
|
415
|
+
myokit = _import_myokit()
|
|
401
416
|
model = myokit.Model(ode.name)
|
|
402
417
|
model.meta["author"] = "GotranX API"
|
|
403
418
|
comp = model.add_component(time_component)
|
|
@@ -506,7 +521,7 @@ def cellml_to_gotran(filename: str | Path) -> ODE:
|
|
|
506
521
|
gotranx.ode.ODE
|
|
507
522
|
The gotran ODE
|
|
508
523
|
"""
|
|
509
|
-
|
|
524
|
+
myokit = _import_myokit()
|
|
510
525
|
myokit_model = myokit.formats.cellml.CellMLImporter().model(filename)
|
|
511
526
|
return myokit_to_gotran(myokit_model)
|
|
512
527
|
|
|
@@ -521,6 +536,6 @@ def gotran_to_cellml(ode: ODE, filename: str | Path) -> None:
|
|
|
521
536
|
filename : str | Path
|
|
522
537
|
The filename of the cellml file
|
|
523
538
|
"""
|
|
524
|
-
|
|
539
|
+
myokit = _import_myokit()
|
|
525
540
|
model = gotran_to_myokit(ode)
|
|
526
541
|
myokit.formats.cellml.CellMLExporter().model(filename, model, version="1.0")
|