myokit 1.35.4__py3-none-any.whl → 1.36.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- myokit/__init__.py +5 -3
- myokit/__main__.py +9 -159
- myokit/_config.py +2 -2
- myokit/_expressions.py +6 -6
- myokit/_model_api.py +11 -7
- myokit/_myokit_version.py +1 -1
- myokit/_protocol.py +4 -0
- myokit/_sim/__init__.py +1 -0
- myokit/_sim/cvodessim.c +321 -177
- myokit/_sim/cvodessim.py +107 -43
- myokit/_sim/mcl.h +54 -0
- myokit/formats/__init__.py +63 -12
- myokit/formats/ansic/__init__.py +2 -1
- myokit/formats/ansic/_ewriter.py +159 -40
- myokit/formats/cpp/_ewriter.py +12 -1
- myokit/formats/cuda/_ewriter.py +15 -51
- myokit/formats/easyml/_ewriter.py +26 -54
- myokit/formats/heka/_patchmaster.py +15 -3
- myokit/formats/latex/_ewriter.py +103 -88
- myokit/formats/latex/_exporter.py +1 -1
- myokit/formats/mathml/_ewriter.py +2 -2
- myokit/formats/matlab/_ewriter.py +50 -28
- myokit/formats/opencl/_ewriter.py +61 -78
- myokit/formats/python/_ewriter.py +81 -50
- myokit/formats/stan/_ewriter.py +29 -37
- myokit/gui/source.py +1 -1
- myokit/lib/hh.py +3 -0
- myokit/lib/markov.py +6 -0
- myokit/tests/__init__.py +70 -0
- myokit/tests/data/decker.model +59 -59
- myokit/tests/test_formats.py +115 -7
- myokit/tests/test_formats_ansic.py +344 -0
- myokit/tests/test_formats_axon.py +17 -0
- myokit/tests/test_formats_cpp.py +97 -0
- myokit/tests/test_formats_cuda.py +226 -0
- myokit/tests/test_formats_easyml.py +169 -152
- myokit/tests/{test_formats_exporters.py → test_formats_exporters_run.py} +1 -69
- myokit/tests/test_formats_html.py +1 -3
- myokit/tests/test_formats_latex.py +211 -0
- myokit/tests/test_formats_mathml_content.py +13 -0
- myokit/tests/test_formats_mathml_presentation.py +54 -42
- myokit/tests/test_formats_matlab.py +218 -0
- myokit/tests/test_formats_opencl.py +206 -380
- myokit/tests/test_formats_python.py +557 -0
- myokit/tests/test_formats_stan.py +175 -0
- myokit/tests/test_formats_sympy.py +9 -2
- myokit/tests/test_lib_hh.py +36 -0
- myokit/tests/test_lib_plots.py +0 -16
- myokit/tests/test_model.py +21 -1
- myokit/tests/test_simulation_cvodes.py +137 -56
- myokit/tools.py +3 -2
- {myokit-1.35.4.dist-info → myokit-1.36.1.dist-info}/LICENSE.txt +1 -1
- {myokit-1.35.4.dist-info → myokit-1.36.1.dist-info}/METADATA +19 -8
- {myokit-1.35.4.dist-info → myokit-1.36.1.dist-info}/RECORD +57 -52
- {myokit-1.35.4.dist-info → myokit-1.36.1.dist-info}/WHEEL +1 -1
- myokit/tests/test_formats_expression_writers.py +0 -1281
- myokit/tests/test_formats_importers.py +0 -53
- {myokit-1.35.4.dist-info → myokit-1.36.1.dist-info}/entry_points.txt +0 -0
- {myokit-1.35.4.dist-info → myokit-1.36.1.dist-info}/top_level.txt +0 -0
|
@@ -6,47 +6,60 @@
|
|
|
6
6
|
#
|
|
7
7
|
import myokit
|
|
8
8
|
|
|
9
|
-
from myokit.formats.
|
|
9
|
+
from myokit.formats.ansic import CBasedExpressionWriter
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
class OpenCLExpressionWriter(
|
|
12
|
+
class OpenCLExpressionWriter(CBasedExpressionWriter):
|
|
13
13
|
"""
|
|
14
14
|
This :class:`ExpressionWriter <myokit.formats.ExpressionWriter>` translates
|
|
15
15
|
Myokit :class:`expressions <myokit.Expression>` to OpenCL syntax.
|
|
16
|
+
|
|
17
|
+
Arguments:
|
|
18
|
+
|
|
19
|
+
``precision``
|
|
20
|
+
By default, numbers are shown as e.g. ``1.23f``, denoting single
|
|
21
|
+
precision literals. To use double precision instead, set ``precision``
|
|
22
|
+
to ``myokit.DOUBLE_PRECISION``.
|
|
23
|
+
``native_math``
|
|
24
|
+
By default, the software implementations of functions like ``log`` and
|
|
25
|
+
``exp`` are used. To use the native version instead, set
|
|
26
|
+
``native_math`` to ``True``.
|
|
27
|
+
|
|
16
28
|
"""
|
|
17
29
|
def __init__(self, precision=myokit.SINGLE_PRECISION, native_math=True):
|
|
18
30
|
super().__init__()
|
|
19
|
-
self._function_prefix = ''
|
|
20
31
|
self._sp = (precision == myokit.SINGLE_PRECISION)
|
|
21
32
|
self._nm = bool(native_math)
|
|
22
33
|
|
|
23
34
|
def _exc(self, e):
|
|
24
35
|
"""Returns ``ex(e)`` if ``e`` is a Condition, else ``ex(e != 0)``."""
|
|
36
|
+
# Can be removed after https://github.com/myokit/myokit/issues/1056
|
|
25
37
|
if isinstance(e, myokit.Condition):
|
|
26
38
|
return self.ex(e)
|
|
27
39
|
return self.ex(myokit.NotEqual(e, myokit.Number(0)))
|
|
28
40
|
|
|
29
|
-
def
|
|
41
|
+
def _ex_infix_comparison(self, e, op):
|
|
30
42
|
"""Handles ex() for infix condition operators (==, !=, > etc.)."""
|
|
43
|
+
# Can be removed after https://github.com/myokit/myokit/issues/1056
|
|
31
44
|
c1 = isinstance(e[0], myokit.Condition)
|
|
32
45
|
c2 = isinstance(e[1], myokit.Condition)
|
|
33
46
|
if (c1 and c2) or not (c1 or c2):
|
|
34
|
-
|
|
35
|
-
b = self.ex(e[1])
|
|
47
|
+
return f'({self.ex(e[0])} {op} {self.ex(e[1])})'
|
|
36
48
|
else:
|
|
37
|
-
|
|
38
|
-
b = self._exc(e[1])
|
|
39
|
-
return '(' + a + ' ' + op + ' ' + b + ')'
|
|
49
|
+
return f'({self._exc(e[0])} {op} {self._exc(e[1])})'
|
|
40
50
|
|
|
41
51
|
def _ex_infix_logical(self, e, op):
|
|
42
|
-
|
|
43
|
-
return '(
|
|
52
|
+
# Can be removed after https://github.com/myokit/myokit/issues/1056
|
|
53
|
+
return f'({self._exc(e[0])} {op} {self._exc(e[1])})'
|
|
44
54
|
|
|
45
55
|
#def _ex_name(self, e):
|
|
46
56
|
#def _ex_derivative(self, e):
|
|
57
|
+
#def _ex_initial_value(self, e):
|
|
58
|
+
#def _ex_partial_derivative(self, e):
|
|
47
59
|
|
|
48
60
|
def _ex_number(self, e):
|
|
49
|
-
|
|
61
|
+
x = super()._ex_number(e)
|
|
62
|
+
return x + 'f' if self._sp else x
|
|
50
63
|
|
|
51
64
|
#def _ex_prefix_plus(self, e):
|
|
52
65
|
#def _ex_prefix_minus(self, e):
|
|
@@ -54,34 +67,15 @@ class OpenCLExpressionWriter(PythonExpressionWriter):
|
|
|
54
67
|
#def _ex_minus(self, e):
|
|
55
68
|
#def _ex_multiply(self, e):
|
|
56
69
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def
|
|
65
|
-
|
|
66
|
-
# See myokit.Quotient.
|
|
67
|
-
# Assuming it follows C and so we need a custom implementation.
|
|
68
|
-
return self.ex(myokit.Floor(myokit.Divide(e[0], e[1])))
|
|
69
|
-
|
|
70
|
-
def _ex_remainder(self, e):
|
|
71
|
-
# Note that this _must_ use the same round-to-neg-inf convention as
|
|
72
|
-
# myokit.Quotient.
|
|
73
|
-
# Assuming it follows C and so we need a custom implementation.
|
|
74
|
-
return self.ex(myokit.Minus(
|
|
75
|
-
e[0], myokit.Multiply(e[1], myokit.Quotient(e[0], e[1]))))
|
|
76
|
-
|
|
77
|
-
def _ex_power(self, e):
|
|
78
|
-
if e[1] == myokit.Number(2):
|
|
79
|
-
if e.bracket(e[0]):
|
|
80
|
-
return '((' + self.ex(e[0]) + ') * (' + self.ex(e[0]) + '))'
|
|
81
|
-
else:
|
|
82
|
-
return '(' + self.ex(e[0]) + ' * ' + self.ex(e[0]) + ')'
|
|
83
|
-
else:
|
|
84
|
-
return 'pow(' + self.ex(e[0]) + ', ' + self.ex(e[1]) + ')'
|
|
70
|
+
# Native divide seemed to cause some issues
|
|
71
|
+
#def _ex_divide(self, e):
|
|
72
|
+
# if self._nm:
|
|
73
|
+
# return 'native_divide(' + self.ex(e[0]) +', '+ self.ex(e[1]) + ')'
|
|
74
|
+
# return self._ex_infix(e, '/')
|
|
75
|
+
|
|
76
|
+
#def _ex_quotient(self, e):
|
|
77
|
+
#def _ex_remainder(self, e):
|
|
78
|
+
#def _ex_power(self, e):
|
|
85
79
|
|
|
86
80
|
def _ex_sqrt(self, e):
|
|
87
81
|
f = 'native_sqrt' if self._nm else 'sqrt'
|
|
@@ -99,41 +93,29 @@ class OpenCLExpressionWriter(PythonExpressionWriter):
|
|
|
99
93
|
f = 'native_tan' if self._nm else 'tan'
|
|
100
94
|
return self._ex_function(e, f)
|
|
101
95
|
|
|
102
|
-
def _ex_asin(self, e):
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
def _ex_acos(self, e):
|
|
106
|
-
return self._ex_function(e, 'acos')
|
|
107
|
-
|
|
108
|
-
def _ex_atan(self, e):
|
|
109
|
-
return self._ex_function(e, 'atan')
|
|
96
|
+
#def _ex_asin(self, e):
|
|
97
|
+
#def _ex_acos(self, e):
|
|
98
|
+
#def _ex_atan(self, e):
|
|
110
99
|
|
|
111
100
|
def _ex_exp(self, e):
|
|
112
101
|
f = 'native_exp' if self._nm else 'exp'
|
|
113
102
|
return self._ex_function(e, f)
|
|
114
103
|
|
|
115
104
|
def _ex_log(self, e):
|
|
116
|
-
|
|
105
|
+
log = 'native_log' if self._nm else 'log'
|
|
117
106
|
if len(e) == 1:
|
|
118
|
-
return self._ex_function(e,
|
|
119
|
-
|
|
120
|
-
|
|
107
|
+
return self._ex_function(e, log)
|
|
108
|
+
# Always add brackets: parent was expecting a function so will never
|
|
109
|
+
# have added them.
|
|
110
|
+
return f'({log}({self.ex(e[0])}) / {log}({self.ex(e[1])}))'
|
|
121
111
|
|
|
122
112
|
def _ex_log10(self, e):
|
|
123
113
|
f = 'native_log10' if self._nm else 'log10'
|
|
124
114
|
return self._ex_function(e, f)
|
|
125
115
|
|
|
126
|
-
def _ex_floor(self, e):
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
def _ex_ceil(self, e):
|
|
130
|
-
return self._ex_function(e, 'ceil')
|
|
131
|
-
|
|
132
|
-
def _ex_abs(self, e):
|
|
133
|
-
return self._ex_function(e, 'fabs')
|
|
134
|
-
|
|
135
|
-
def _ex_not(self, e):
|
|
136
|
-
return '!(' + self._exc(e[0]) + ')'
|
|
116
|
+
#def _ex_floor(self, e):
|
|
117
|
+
#def _ex_ceil(self, e):
|
|
118
|
+
#def _ex_abs(self, e):
|
|
137
119
|
|
|
138
120
|
#def _ex_equal(self, e):
|
|
139
121
|
#def _ex_not_equal(self, e):
|
|
@@ -142,26 +124,27 @@ class OpenCLExpressionWriter(PythonExpressionWriter):
|
|
|
142
124
|
#def _ex_more_equal(self, e):
|
|
143
125
|
#def _ex_less_equal(self, e):
|
|
144
126
|
|
|
145
|
-
def _ex_and(self, e):
|
|
146
|
-
|
|
127
|
+
#def _ex_and(self, e):
|
|
128
|
+
#def _ex_or(self, e):
|
|
147
129
|
|
|
148
|
-
def
|
|
149
|
-
|
|
130
|
+
def _ex_not(self, e):
|
|
131
|
+
# Can be removed after https://github.com/myokit/myokit/issues/1056
|
|
132
|
+
return f'(!{self._exc(e[0])})'
|
|
150
133
|
|
|
151
134
|
def _ex_if(self, e):
|
|
152
|
-
|
|
153
|
-
|
|
135
|
+
# Can be removed after https://github.com/myokit/myokit/issues/1056
|
|
136
|
+
_if, _then, _else = self._exc(e._i), self.ex(e._t), self.ex(e._e)
|
|
137
|
+
return f'({_if} ? {_then} : {_else})'
|
|
154
138
|
|
|
155
139
|
def _ex_piecewise(self, e):
|
|
140
|
+
# Can be removed after https://github.com/myokit/myokit/issues/1056
|
|
141
|
+
_ifs = [self._exc(x) for x in e._i]
|
|
142
|
+
_thens = [self.ex(x) for x in e._e]
|
|
156
143
|
s = []
|
|
157
|
-
n = len(
|
|
158
|
-
for
|
|
159
|
-
s.append('(')
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
s.append(self.ex(e._e[i]))
|
|
163
|
-
s.append(' : ')
|
|
164
|
-
s.append(self.ex(e._e[n]))
|
|
165
|
-
s.append(')' * n)
|
|
144
|
+
n = len(_ifs)
|
|
145
|
+
for _if, _then in zip(_ifs, _thens):
|
|
146
|
+
s.append(f'({_if} ? {_then} : ')
|
|
147
|
+
s.append(_thens[-1])
|
|
148
|
+
s.append(')' * len(_ifs))
|
|
166
149
|
return ''.join(s)
|
|
167
150
|
|
|
@@ -31,31 +31,37 @@ class PythonExpressionWriter(myokit.formats.ExpressionWriter):
|
|
|
31
31
|
"""
|
|
32
32
|
self._flhs = f
|
|
33
33
|
|
|
34
|
+
def _ex_prefix(self, e, op):
|
|
35
|
+
""" Handles ex() for prefix operators. """
|
|
36
|
+
# No simplifications should be made here for PrefixPlus, see
|
|
37
|
+
# https://github.com/myokit/myokit/issues/1054
|
|
38
|
+
if e.bracket(e[0]):
|
|
39
|
+
return f'{op}({self.ex(e[0])})'
|
|
40
|
+
return f'{op}{self.ex(e[0])}'
|
|
41
|
+
|
|
34
42
|
def _ex_infix(self, e, op):
|
|
35
|
-
"""
|
|
36
|
-
Handles ex() for infix operators
|
|
37
|
-
"""
|
|
43
|
+
""" Handles ex() for infix operators, except Power. """
|
|
38
44
|
if e.bracket(e[0]):
|
|
39
|
-
out = '(
|
|
45
|
+
out = f'({self.ex(e[0])}) {op}'
|
|
40
46
|
else:
|
|
41
|
-
out = self.ex(e[0])
|
|
47
|
+
out = f'{self.ex(e[0])} {op}'
|
|
42
48
|
if e.bracket(e[1]):
|
|
43
|
-
return out
|
|
49
|
+
return f'{out} ({self.ex(e[1])})'
|
|
44
50
|
else:
|
|
45
|
-
return out
|
|
51
|
+
return f'{out} {self.ex(e[1])}'
|
|
46
52
|
|
|
47
53
|
def _ex_function(self, e, func):
|
|
48
|
-
"""
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
return self._function_prefix + func \
|
|
52
|
-
+ '(' + ', '.join([self.ex(x) for x in e]) + ')'
|
|
54
|
+
""" Handles ex() for function operators with operands. """
|
|
55
|
+
args = ', '.join([self.ex(x) for x in e])
|
|
56
|
+
return f'{self._function_prefix}{func}({args})'
|
|
53
57
|
|
|
54
|
-
def
|
|
55
|
-
"""
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
def _ex_infix_comparison(self, e, op):
|
|
59
|
+
""" Handles ex() for infix comparisons (``==``, ``>=``, etc.). """
|
|
60
|
+
return f'({self.ex(e[0])} {op} {self.ex(e[1])})'
|
|
61
|
+
|
|
62
|
+
def _ex_infix_logical(self, e, op):
|
|
63
|
+
""" Handles ex() for ``and`` and ``or``. """
|
|
64
|
+
return f'({self.ex(e[0])} {op} {self.ex(e[1])})'
|
|
59
65
|
|
|
60
66
|
def _ex_name(self, e):
|
|
61
67
|
return self._flhs(e)
|
|
@@ -64,22 +70,21 @@ class PythonExpressionWriter(myokit.formats.ExpressionWriter):
|
|
|
64
70
|
return self._flhs(e)
|
|
65
71
|
|
|
66
72
|
def _ex_initial_value(self, e):
|
|
67
|
-
|
|
73
|
+
raise NotImplementedError(
|
|
74
|
+
'Initial values are not supported by this expression writer.')
|
|
68
75
|
|
|
69
76
|
def _ex_partial_derivative(self, e):
|
|
70
|
-
|
|
77
|
+
raise NotImplementedError(
|
|
78
|
+
'Partial derivatives are not supported by this expression writer.')
|
|
71
79
|
|
|
72
80
|
def _ex_number(self, e):
|
|
73
|
-
return myokit.float.str(e)
|
|
81
|
+
return myokit.float.str(e).lstrip()
|
|
74
82
|
|
|
75
83
|
def _ex_prefix_plus(self, e):
|
|
76
|
-
return self.
|
|
84
|
+
return self._ex_prefix(e, '+')
|
|
77
85
|
|
|
78
86
|
def _ex_prefix_minus(self, e):
|
|
79
|
-
|
|
80
|
-
return '(-(' + self.ex(e[0]) + '))'
|
|
81
|
-
else:
|
|
82
|
-
return '(-' + self.ex(e[0]) + ')'
|
|
87
|
+
return self._ex_prefix(e, '-')
|
|
83
88
|
|
|
84
89
|
def _ex_plus(self, e):
|
|
85
90
|
return self._ex_infix(e, '+')
|
|
@@ -100,7 +105,17 @@ class PythonExpressionWriter(myokit.formats.ExpressionWriter):
|
|
|
100
105
|
return self._ex_infix(e, '%')
|
|
101
106
|
|
|
102
107
|
def _ex_power(self, e):
|
|
103
|
-
|
|
108
|
+
# Note: Python uses a right-to-left order of operations for power, so
|
|
109
|
+
# that a**b**c means a**(b**c). In Myokit, a^b^c means (a^b)^c, so we
|
|
110
|
+
# need to reverse this for powers only.
|
|
111
|
+
if e.bracket(e[0]) or isinstance(e[0], myokit.Power):
|
|
112
|
+
out = f'({self.ex(e[0])})'
|
|
113
|
+
else:
|
|
114
|
+
out = f'{self.ex(e[0])}'
|
|
115
|
+
if e.bracket(e[1]) and not isinstance(e[1], myokit.Power):
|
|
116
|
+
return f'{out}**({self.ex(e[1])})'
|
|
117
|
+
else:
|
|
118
|
+
return f'{out}**{self.ex(e[1])}'
|
|
104
119
|
|
|
105
120
|
def _ex_sqrt(self, e):
|
|
106
121
|
return self._ex_function(e, 'sqrt')
|
|
@@ -139,46 +154,44 @@ class PythonExpressionWriter(myokit.formats.ExpressionWriter):
|
|
|
139
154
|
return self._ex_function(e, 'ceil')
|
|
140
155
|
|
|
141
156
|
def _ex_abs(self, e):
|
|
142
|
-
return 'abs(
|
|
143
|
-
|
|
144
|
-
def _ex_not(self, e):
|
|
145
|
-
return 'not (' + self.ex(e[0]) + ')'
|
|
157
|
+
return f'abs({self.ex(e[0])})'
|
|
146
158
|
|
|
147
159
|
def _ex_equal(self, e):
|
|
148
|
-
return self.
|
|
160
|
+
return self._ex_infix_comparison(e, '==')
|
|
149
161
|
|
|
150
162
|
def _ex_not_equal(self, e):
|
|
151
|
-
return self.
|
|
163
|
+
return self._ex_infix_comparison(e, '!=')
|
|
152
164
|
|
|
153
165
|
def _ex_more(self, e):
|
|
154
|
-
return self.
|
|
166
|
+
return self._ex_infix_comparison(e, '>')
|
|
155
167
|
|
|
156
168
|
def _ex_less(self, e):
|
|
157
|
-
return self.
|
|
169
|
+
return self._ex_infix_comparison(e, '<')
|
|
158
170
|
|
|
159
171
|
def _ex_more_equal(self, e):
|
|
160
|
-
return self.
|
|
172
|
+
return self._ex_infix_comparison(e, '>=')
|
|
161
173
|
|
|
162
174
|
def _ex_less_equal(self, e):
|
|
163
|
-
return self.
|
|
175
|
+
return self._ex_infix_comparison(e, '<=')
|
|
164
176
|
|
|
165
177
|
def _ex_and(self, e):
|
|
166
|
-
return self.
|
|
178
|
+
return self._ex_infix_logical(e, 'and')
|
|
167
179
|
|
|
168
180
|
def _ex_or(self, e):
|
|
169
|
-
return self.
|
|
181
|
+
return self._ex_infix_logical(e, 'or')
|
|
182
|
+
|
|
183
|
+
def _ex_not(self, e):
|
|
184
|
+
return f'(not {self.ex(e[0])})'
|
|
170
185
|
|
|
171
186
|
def _ex_if(self, e):
|
|
172
|
-
return '(
|
|
173
|
-
+ self.ex(e._e) + ')'
|
|
187
|
+
return f'({self.ex(e._t)} if {self.ex(e._i)} else {self.ex(e._e)})'
|
|
174
188
|
|
|
175
189
|
def _ex_piecewise(self, e):
|
|
176
190
|
s = ''
|
|
177
191
|
n = len(e) // 2
|
|
178
192
|
for i in range(0, n):
|
|
179
|
-
s += '(
|
|
180
|
-
s += self.ex(e._e[n])
|
|
181
|
-
s += ')' * n
|
|
193
|
+
s += f'({self.ex(e._e[i])} if {self.ex(e._i[i])} else '
|
|
194
|
+
s += self.ex(e._e[n]) + ')' * n
|
|
182
195
|
return s
|
|
183
196
|
|
|
184
197
|
|
|
@@ -216,25 +229,43 @@ class NumPyExpressionWriter(PythonExpressionWriter):
|
|
|
216
229
|
|
|
217
230
|
def _ex_atan(self, e):
|
|
218
231
|
return self._ex_function(e, 'arctan')
|
|
232
|
+
|
|
219
233
|
#def _ex_exp(self, e):
|
|
220
|
-
|
|
234
|
+
|
|
235
|
+
def _ex_log(self, e):
|
|
236
|
+
if len(e) == 1:
|
|
237
|
+
return self._ex_function(e, 'log')
|
|
238
|
+
# Always add brackets here: The parent element will have been expecting
|
|
239
|
+
# a function (which never needs brackets) and so won't have added any.
|
|
240
|
+
return f'(numpy.log({self.ex(e[0])}) / numpy.log({self.ex(e[1])}))'
|
|
241
|
+
|
|
221
242
|
#def _ex_log10(self, e):
|
|
222
243
|
#def _ex_floor(self, e):
|
|
223
244
|
#def _ex_ceil(self, e):
|
|
224
|
-
|
|
225
|
-
|
|
245
|
+
|
|
246
|
+
def _ex_abs(self, e):
|
|
247
|
+
# Can't use default abs here, must be numpy.abs
|
|
248
|
+
return self._ex_function(e, 'abs')
|
|
249
|
+
|
|
226
250
|
#def _ex_equal(self, e):
|
|
227
251
|
#def _ex_not_equal(self, e):
|
|
228
252
|
#def _ex_more(self, e):
|
|
229
253
|
#def _ex_less(self, e):
|
|
230
254
|
#def _ex_more_equal(self, e):
|
|
231
255
|
#def _ex_less_equal(self, e):
|
|
232
|
-
|
|
233
|
-
|
|
256
|
+
|
|
257
|
+
def _ex_and(self, e):
|
|
258
|
+
return self._ex_function(e, 'logical_and')
|
|
259
|
+
|
|
260
|
+
def _ex_or(self, e):
|
|
261
|
+
return self._ex_function(e, 'logical_or')
|
|
262
|
+
|
|
263
|
+
def _ex_not(self, e):
|
|
264
|
+
return self._ex_function(e, 'logical_not')
|
|
234
265
|
|
|
235
266
|
def _ex_if(self, e):
|
|
236
|
-
return self._function_prefix
|
|
237
|
-
|
|
267
|
+
return (f'{self._function_prefix}select('
|
|
268
|
+
f'[{self.ex(e._i)}], [{self.ex(e._t)}], {self.ex(e._e)})')
|
|
238
269
|
|
|
239
270
|
def _ex_piecewise(self, e):
|
|
240
271
|
n = len(e._i)
|
myokit/formats/stan/_ewriter.py
CHANGED
|
@@ -20,20 +20,6 @@ class StanExpressionWriter(PythonExpressionWriter):
|
|
|
20
20
|
super().__init__()
|
|
21
21
|
self._function_prefix = ''
|
|
22
22
|
|
|
23
|
-
self._fcond = None
|
|
24
|
-
self.set_condition_function('ifthenelse')
|
|
25
|
-
|
|
26
|
-
def set_condition_function(self, func=None):
|
|
27
|
-
"""
|
|
28
|
-
Sets a function name to use for if statements
|
|
29
|
-
|
|
30
|
-
By setting func to None you can revert back to the default behavior
|
|
31
|
-
(the ternary operator). Any other value will be interpreted as the
|
|
32
|
-
name of a function taking arguments (condition, value_if_true,
|
|
33
|
-
value_if_false).
|
|
34
|
-
"""
|
|
35
|
-
self._fcond = func
|
|
36
|
-
|
|
37
23
|
#def _ex_name(self, e):
|
|
38
24
|
#def _ex_derivative(self, e):
|
|
39
25
|
#def _ex_number(self, e):
|
|
@@ -49,11 +35,22 @@ class StanExpressionWriter(PythonExpressionWriter):
|
|
|
49
35
|
return self.ex(myokit.Floor(myokit.Divide(e[0], e[1])))
|
|
50
36
|
|
|
51
37
|
def _ex_remainder(self, e):
|
|
52
|
-
#
|
|
53
|
-
|
|
38
|
+
# Extra brackets needed: Minus has lower precedence than division.
|
|
39
|
+
i = myokit.Minus(e[0], myokit.Multiply(
|
|
40
|
+
e[1], myokit.Floor(myokit.Divide(e[0], e[1]))))
|
|
41
|
+
return f'({self.ex(i)})'
|
|
54
42
|
|
|
55
43
|
def _ex_power(self, e):
|
|
56
|
-
|
|
44
|
+
# Like Python (and unlike Myokit), Stan uses a right-associative power
|
|
45
|
+
# operator.
|
|
46
|
+
if e.bracket(e[0]) or isinstance(e[0], myokit.Power):
|
|
47
|
+
out = f'({self.ex(e[0])})'
|
|
48
|
+
else:
|
|
49
|
+
out = f'{self.ex(e[0])}'
|
|
50
|
+
if e.bracket(e[1]) and not isinstance(e[1], myokit.Power):
|
|
51
|
+
return f'{out}^({self.ex(e[1])})'
|
|
52
|
+
else:
|
|
53
|
+
return f'{out}^{self.ex(e[1])}'
|
|
57
54
|
|
|
58
55
|
#def _ex_sqrt(self, e):
|
|
59
56
|
#def _ex_sin(self, e):
|
|
@@ -65,22 +62,16 @@ class StanExpressionWriter(PythonExpressionWriter):
|
|
|
65
62
|
#def _ex_exp(self, e):
|
|
66
63
|
|
|
67
64
|
def _ex_log(self, e):
|
|
68
|
-
if len(e) ==
|
|
69
|
-
return self.
|
|
70
|
-
|
|
71
|
-
return '(log(' + self.ex(e[0]) + ') / log(' + self.ex(e[1]) + '))'
|
|
65
|
+
if len(e) == 2:
|
|
66
|
+
return f'(log({self.ex(e[0])}) / log({self.ex(e[1])}))'
|
|
67
|
+
return f'log({self.ex(e[0])})'
|
|
72
68
|
|
|
73
69
|
def _ex_log10(self, e):
|
|
74
|
-
return 'log10(
|
|
70
|
+
return f'log10({self.ex(e[0])})'
|
|
75
71
|
|
|
76
72
|
#def _ex_floor(self, e):
|
|
77
73
|
#def _ex_ceil(self, e):
|
|
78
|
-
|
|
79
|
-
def _ex_abs(self, e):
|
|
80
|
-
return self._ex_function(e, 'abs')
|
|
81
|
-
|
|
82
|
-
def _ex_not(self, e):
|
|
83
|
-
return '!(' + self.ex(e[0]) + ')'
|
|
74
|
+
#def _ex_abs(self, e):
|
|
84
75
|
|
|
85
76
|
#def _ex_equal(self, e):
|
|
86
77
|
#def _ex_not_equal(self, e):
|
|
@@ -90,21 +81,22 @@ class StanExpressionWriter(PythonExpressionWriter):
|
|
|
90
81
|
#def _ex_less_equal(self, e):
|
|
91
82
|
|
|
92
83
|
def _ex_and(self, e):
|
|
93
|
-
return self.
|
|
84
|
+
return self._ex_infix_logical(e, '&&')
|
|
94
85
|
|
|
95
86
|
def _ex_or(self, e):
|
|
96
|
-
return self.
|
|
87
|
+
return self._ex_infix_logical(e, '||')
|
|
88
|
+
|
|
89
|
+
def _ex_not(self, e):
|
|
90
|
+
return f'(!{self.ex(e[0])})'
|
|
97
91
|
|
|
98
92
|
def _ex_if(self, e):
|
|
99
|
-
return
|
|
100
|
-
self.ex(e._e) + ')')
|
|
93
|
+
return f'({self.ex(e._i)} ? {self.ex(e._t)} : {self.ex(e._e)})'
|
|
101
94
|
|
|
102
95
|
def _ex_piecewise(self, e):
|
|
103
96
|
s = []
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
s.append(
|
|
108
|
-
s.append(')' * n)
|
|
97
|
+
for _if, _then in zip(e._i, e._e):
|
|
98
|
+
s.append(f'({self.ex(_if)} ? {self.ex(_then)} : ')
|
|
99
|
+
s.append(self.ex(e._e[-1]))
|
|
100
|
+
s.append(')' * len(e._i))
|
|
109
101
|
return ''.join(s)
|
|
110
102
|
|
myokit/gui/source.py
CHANGED
|
@@ -985,7 +985,7 @@ class ModelHighlighter(QtGui.QSyntaxHighlighter):
|
|
|
985
985
|
|
|
986
986
|
# Headers
|
|
987
987
|
name = r'[a-zA-Z]+[a-zA-Z0-9_]*'
|
|
988
|
-
self._rule_head = R(r'^\s*(\[{1,2}' + name + '\]{1,2})')
|
|
988
|
+
self._rule_head = R(r'^\s*(\[{1,2}' + name + r'\]{1,2})')
|
|
989
989
|
|
|
990
990
|
# Simple rules
|
|
991
991
|
self._rules = []
|
myokit/lib/hh.py
CHANGED
|
@@ -298,6 +298,9 @@ class HHModel:
|
|
|
298
298
|
'_y[' + k + '] = ' + state.uname() + ' = '
|
|
299
299
|
+ inf + ' + (_y0[' + k + '] - ' + inf
|
|
300
300
|
+ ') * numpy.exp(-_t / ' + tau + ')')
|
|
301
|
+
f.append(
|
|
302
|
+
'_y[' + k + '][_t == 0] = ' + state.uname() + '[_t == 0] = '
|
|
303
|
+
+ '_y0[' + k + ']')
|
|
301
304
|
|
|
302
305
|
# Add current calculation
|
|
303
306
|
if self._current is not None:
|
myokit/lib/markov.py
CHANGED
|
@@ -975,6 +975,10 @@ class AnalyticalSimulation:
|
|
|
975
975
|
"""
|
|
976
976
|
self._parameters[self._parameter_map[variable]] = float(value)
|
|
977
977
|
|
|
978
|
+
# Invalidate cache
|
|
979
|
+
self._cached_matrices = {}
|
|
980
|
+
self._cached_solution = {}
|
|
981
|
+
|
|
978
982
|
def set_default_state(self, state):
|
|
979
983
|
"""
|
|
980
984
|
Changes this simulation's default state.
|
|
@@ -1492,6 +1496,8 @@ class DiscreteSimulation:
|
|
|
1492
1496
|
Updates a single parameter to a new value.
|
|
1493
1497
|
"""
|
|
1494
1498
|
self._parameters[self._parameter_map[variable]] = float(value)
|
|
1499
|
+
self._cached_rates = None
|
|
1500
|
+
self._cached_matrix = None
|
|
1495
1501
|
|
|
1496
1502
|
def set_default_state(self, state):
|
|
1497
1503
|
"""
|
myokit/tests/__init__.py
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
#
|
|
11
11
|
import os
|
|
12
12
|
import tempfile
|
|
13
|
+
import unittest
|
|
13
14
|
import warnings
|
|
14
15
|
|
|
15
16
|
import myokit
|
|
@@ -256,3 +257,72 @@ def test_case_pk_model(parameters, times):
|
|
|
256
257
|
partials = np.vstack([damount_dinitial_amount, damount_delimination_rate])
|
|
257
258
|
|
|
258
259
|
return amount, partials
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
class ExpressionWriterTestCase(unittest.TestCase):
|
|
263
|
+
""" Abstract class for expression writer tests. """
|
|
264
|
+
|
|
265
|
+
_name = None
|
|
266
|
+
_target = None
|
|
267
|
+
_update_lhs_function = True
|
|
268
|
+
|
|
269
|
+
@classmethod
|
|
270
|
+
def setUpClass(cls):
|
|
271
|
+
# Create a model with some variables for testing
|
|
272
|
+
cls.model = m = myokit.Model()
|
|
273
|
+
cls.component = c = m.add_component('comp')
|
|
274
|
+
cls.a = myokit.Name(c.add_variable('a', rhs=1))
|
|
275
|
+
cls.b = myokit.Name(c.add_variable('b', rhs=2))
|
|
276
|
+
cls.c = myokit.Name(c.add_variable('c', rhs=3))
|
|
277
|
+
cls.d = myokit.Name(c.add_variable('d', rhs=4))
|
|
278
|
+
cls.e = myokit.Name(c.add_variable('e', rhs=5))
|
|
279
|
+
cls.f = myokit.Name(c.add_variable('f', rhs=6))
|
|
280
|
+
cls.g = myokit.Name(c.add_variable('g', rhs=7))
|
|
281
|
+
cls.t = c.add_variable('t', rhs=0, binding='time')
|
|
282
|
+
|
|
283
|
+
# Set unames
|
|
284
|
+
m.validate()
|
|
285
|
+
|
|
286
|
+
# Create writer
|
|
287
|
+
cls.w = cls._target()
|
|
288
|
+
if cls._update_lhs_function:
|
|
289
|
+
cls.w.set_lhs_function(cls.lhs)
|
|
290
|
+
|
|
291
|
+
# Easy access to properties
|
|
292
|
+
cls.ab = (cls.a, cls.b)
|
|
293
|
+
cls.abc = (cls.a, cls.b, cls.c)
|
|
294
|
+
cls.abcd = (cls.a, cls.b, cls.c, cls.d)
|
|
295
|
+
cls.efg = (cls.e, cls.f, cls.g)
|
|
296
|
+
|
|
297
|
+
@classmethod
|
|
298
|
+
def lhs(cls, ex):
|
|
299
|
+
"""
|
|
300
|
+
Easier to read LHS function: ignores components.
|
|
301
|
+
|
|
302
|
+
All LHS types are supported here: Lack of support for e.g. partial
|
|
303
|
+
derivatives should be implemented in the expression writers themselves.
|
|
304
|
+
"""
|
|
305
|
+
if isinstance(ex, myokit.Name):
|
|
306
|
+
return ex.var().name()
|
|
307
|
+
elif isinstance(ex, myokit.Derivative):
|
|
308
|
+
return f'dot({ex.var().name()})'
|
|
309
|
+
elif isinstance(ex, myokit.InitialValue):
|
|
310
|
+
return f'initial({ex.var().name()})'
|
|
311
|
+
elif isinstance(ex, myokit.PartialDerivative):
|
|
312
|
+
v1 = ex.dependent_expression()
|
|
313
|
+
v2 = ex.independent_expression()
|
|
314
|
+
return f'partial({v1.var().name()}, {v2.var().name()})'
|
|
315
|
+
raise ValueError(f'Untested LHS type {type(ex)}')
|
|
316
|
+
|
|
317
|
+
def test_fetching(self):
|
|
318
|
+
# Test fetching using ewriter method
|
|
319
|
+
w = myokit.formats.ewriter(self._name)
|
|
320
|
+
self.assertIsInstance(w, self._target)
|
|
321
|
+
|
|
322
|
+
def test_bad_argument(self):
|
|
323
|
+
# Test without a Myokit expression
|
|
324
|
+
self.assertRaisesRegex(
|
|
325
|
+
ValueError, 'Unknown expression type', self.w.ex, 7)
|
|
326
|
+
|
|
327
|
+
def eq(self, expression, expected_output):
|
|
328
|
+
self.assertEqual(self.w.ex(expression), expected_output)
|