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.
Files changed (62) hide show
  1. {gotranx-0.2.2 → gotranx-0.2.3}/PKG-INFO +2 -1
  2. {gotranx-0.2.2 → gotranx-0.2.3}/pyproject.toml +16 -12
  3. {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/__init__.py +2 -2
  4. {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/atoms.py +7 -2
  5. {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/cli/cellml2ode.py +3 -3
  6. gotranx-0.2.3/src/gotranx/codegen/__init__.py +26 -0
  7. {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/codegen/base.py +1 -2
  8. {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/codegen/c.py +21 -8
  9. gotranx-0.2.2/src/gotranx/save.py → gotranx-0.2.3/src/gotranx/codegen/ode.py +71 -32
  10. {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/codegen/python.py +39 -10
  11. {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/exceptions.py +17 -9
  12. {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/expressions.py +1 -1
  13. gotranx-0.2.3/src/gotranx/myokit.py +214 -0
  14. {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/ode.lark +3 -1
  15. {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/ode.py +17 -5
  16. {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/ode_component.py +62 -24
  17. gotranx-0.2.3/src/gotranx/save.py +23 -0
  18. {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx.egg-info/PKG-INFO +2 -1
  19. {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx.egg-info/SOURCES.txt +3 -4
  20. {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx.egg-info/requires.txt +1 -0
  21. gotranx-0.2.3/tests/test_conditionals.py +85 -0
  22. gotranx-0.2.3/tests/test_myokit.py +32 -0
  23. gotranx-0.2.2/src/gotranx/cellml/__init__.py +0 -23
  24. gotranx-0.2.2/src/gotranx/cellml/cellml.py +0 -1709
  25. gotranx-0.2.2/src/gotranx/cellml/mathml.py +0 -341
  26. gotranx-0.2.2/src/gotranx/codegen/__init__.py +0 -18
  27. gotranx-0.2.2/tests/test_cellml.py +0 -20
  28. gotranx-0.2.2/tests/test_conditionals.py +0 -29
  29. {gotranx-0.2.2 → gotranx-0.2.3}/README.md +0 -0
  30. {gotranx-0.2.2 → gotranx-0.2.3}/setup.cfg +0 -0
  31. {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/__main__.py +0 -0
  32. {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/cli/__init__.py +0 -0
  33. {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/cli/gotran2c.py +0 -0
  34. {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/cli/gotran2py.py +0 -0
  35. {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/codecomponent.py +0 -0
  36. {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/load.py +0 -0
  37. {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/parser.py +0 -0
  38. {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/schemes.py +0 -0
  39. {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/sympytools.py +0 -0
  40. {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/templates/__init__.py +0 -0
  41. {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/templates/c.py +0 -0
  42. {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/templates/python.py +0 -0
  43. {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/transformer.py +0 -0
  44. {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx/units.py +0 -0
  45. {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx.egg-info/dependency_links.txt +0 -0
  46. {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx.egg-info/entry_points.txt +0 -0
  47. {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx.egg-info/not-zip-safe +0 -0
  48. {gotranx-0.2.2 → gotranx-0.2.3}/src/gotranx.egg-info/top_level.txt +0 -0
  49. {gotranx-0.2.2 → gotranx-0.2.3}/tests/test_atoms.py +0 -0
  50. {gotranx-0.2.2 → gotranx-0.2.3}/tests/test_c_codegen.py +0 -0
  51. {gotranx-0.2.2 → gotranx-0.2.3}/tests/test_cli.py +0 -0
  52. {gotranx-0.2.2 → gotranx-0.2.3}/tests/test_expressions.py +0 -0
  53. {gotranx-0.2.2 → gotranx-0.2.3}/tests/test_load.py +0 -0
  54. {gotranx-0.2.2 → gotranx-0.2.3}/tests/test_ode.py +0 -0
  55. {gotranx-0.2.2 → gotranx-0.2.3}/tests/test_ode_component.py +0 -0
  56. {gotranx-0.2.2 → gotranx-0.2.3}/tests/test_python_codegen.py +0 -0
  57. {gotranx-0.2.2 → gotranx-0.2.3}/tests/test_save.py +0 -0
  58. {gotranx-0.2.2 → gotranx-0.2.3}/tests/test_schemes.py +0 -0
  59. {gotranx-0.2.2 → gotranx-0.2.3}/tests/test_sympytools.py +0 -0
  60. {gotranx-0.2.2 → gotranx-0.2.3}/tests/test_transformer_assignments.py +0 -0
  61. {gotranx-0.2.2 → gotranx-0.2.3}/tests/test_transformer_parameters.py +0 -0
  62. {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.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.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.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 cellml
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
- "cellml",
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 ..cellml import cellml_to_gotran
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
- code = cellml_to_gotran(fname)
23
+ ode = cellml_to_gotran(fname)
24
24
  out = fname.with_suffix(".ode") if outname is None else Path(outname)
25
- out.write_text(code)
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
+ ]
@@ -87,8 +87,7 @@ def _print_Piecewise(
87
87
 
88
88
  exprs = [printer._print(arg.expr) for arg in expr.args]
89
89
  conds = [print_cond(arg.cond) for arg in expr.args]
90
- assert len(exprs) == 2
91
- assert len(conds) == 2
90
+
92
91
  return tuple(conds), tuple(exprs)
93
92
 
94
93
 
@@ -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
- fst, snd = expr.args
25
- if isinstance(fst[0], Assignment):
26
- value = (
27
- f"{super()._print(fst[0].args[0])} = "
28
- f"({super()._print(fst[1])}) ? "
29
- f"{super()._print(fst[0].args[1])} : "
30
- f"{super()._print(snd[0].args[1])};"
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 . import atoms
11
- from .ode import ODE
12
- from .codegen.python import PythonCodeGenerator
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 GotranODECodePrinter(PythonCodeGenerator):
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 += "\n\n"
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.printer.doprint) for s in states])
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
- fst, snd = expr.args
34
- if isinstance(fst[0], Assignment):
35
- value = (
36
- f"{super()._print(fst[0].lhs)} = "
37
- f"numpy.where({super()._print(fst[1])}, "
38
- f"{super()._print(fst[0].rhs)}, "
39
- f"{super()._print(snd[0].rhs)})"
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
- return value
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 GotranParserError(Exception):
9
+ class GotranxError(Exception):
10
10
  pass
11
11
 
12
12
 
13
13
  @dataclass
14
- class ODEFileNotFound(GotranParserError):
14
+ class ODEFileNotFound(GotranxError):
15
15
  fname: Path
16
16
 
17
17
 
18
18
  @dataclass
19
- class StateNotFoundInComponent(GotranParserError):
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(GotranParserError):
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(GotranParserError):
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(GotranParserError):
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(GotranParserError):
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(GotranParserError):
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(GotranParserError, KeyError):
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":