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.
Files changed (59) hide show
  1. myokit/__init__.py +5 -3
  2. myokit/__main__.py +9 -159
  3. myokit/_config.py +2 -2
  4. myokit/_expressions.py +6 -6
  5. myokit/_model_api.py +11 -7
  6. myokit/_myokit_version.py +1 -1
  7. myokit/_protocol.py +4 -0
  8. myokit/_sim/__init__.py +1 -0
  9. myokit/_sim/cvodessim.c +321 -177
  10. myokit/_sim/cvodessim.py +107 -43
  11. myokit/_sim/mcl.h +54 -0
  12. myokit/formats/__init__.py +63 -12
  13. myokit/formats/ansic/__init__.py +2 -1
  14. myokit/formats/ansic/_ewriter.py +159 -40
  15. myokit/formats/cpp/_ewriter.py +12 -1
  16. myokit/formats/cuda/_ewriter.py +15 -51
  17. myokit/formats/easyml/_ewriter.py +26 -54
  18. myokit/formats/heka/_patchmaster.py +15 -3
  19. myokit/formats/latex/_ewriter.py +103 -88
  20. myokit/formats/latex/_exporter.py +1 -1
  21. myokit/formats/mathml/_ewriter.py +2 -2
  22. myokit/formats/matlab/_ewriter.py +50 -28
  23. myokit/formats/opencl/_ewriter.py +61 -78
  24. myokit/formats/python/_ewriter.py +81 -50
  25. myokit/formats/stan/_ewriter.py +29 -37
  26. myokit/gui/source.py +1 -1
  27. myokit/lib/hh.py +3 -0
  28. myokit/lib/markov.py +6 -0
  29. myokit/tests/__init__.py +70 -0
  30. myokit/tests/data/decker.model +59 -59
  31. myokit/tests/test_formats.py +115 -7
  32. myokit/tests/test_formats_ansic.py +344 -0
  33. myokit/tests/test_formats_axon.py +17 -0
  34. myokit/tests/test_formats_cpp.py +97 -0
  35. myokit/tests/test_formats_cuda.py +226 -0
  36. myokit/tests/test_formats_easyml.py +169 -152
  37. myokit/tests/{test_formats_exporters.py → test_formats_exporters_run.py} +1 -69
  38. myokit/tests/test_formats_html.py +1 -3
  39. myokit/tests/test_formats_latex.py +211 -0
  40. myokit/tests/test_formats_mathml_content.py +13 -0
  41. myokit/tests/test_formats_mathml_presentation.py +54 -42
  42. myokit/tests/test_formats_matlab.py +218 -0
  43. myokit/tests/test_formats_opencl.py +206 -380
  44. myokit/tests/test_formats_python.py +557 -0
  45. myokit/tests/test_formats_stan.py +175 -0
  46. myokit/tests/test_formats_sympy.py +9 -2
  47. myokit/tests/test_lib_hh.py +36 -0
  48. myokit/tests/test_lib_plots.py +0 -16
  49. myokit/tests/test_model.py +21 -1
  50. myokit/tests/test_simulation_cvodes.py +137 -56
  51. myokit/tools.py +3 -2
  52. {myokit-1.35.4.dist-info → myokit-1.36.1.dist-info}/LICENSE.txt +1 -1
  53. {myokit-1.35.4.dist-info → myokit-1.36.1.dist-info}/METADATA +19 -8
  54. {myokit-1.35.4.dist-info → myokit-1.36.1.dist-info}/RECORD +57 -52
  55. {myokit-1.35.4.dist-info → myokit-1.36.1.dist-info}/WHEEL +1 -1
  56. myokit/tests/test_formats_expression_writers.py +0 -1281
  57. myokit/tests/test_formats_importers.py +0 -53
  58. {myokit-1.35.4.dist-info → myokit-1.36.1.dist-info}/entry_points.txt +0 -0
  59. {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.python import PythonExpressionWriter
9
+ from myokit.formats.ansic import CBasedExpressionWriter
10
10
 
11
11
 
12
- class OpenCLExpressionWriter(PythonExpressionWriter):
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 _ex_infix_condition(self, e, op):
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
- a = self.ex(e[0])
35
- b = self.ex(e[1])
47
+ return f'({self.ex(e[0])} {op} {self.ex(e[1])})'
36
48
  else:
37
- a = self._exc(e[0])
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
- """Handles ex() for infix logical operators."""
43
- return '(' + self._exc(e[0]) + ' ' + op + ' ' + self._exc(e[1]) + ')'
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
- return myokit.float.str(e) + 'f' if self._sp else myokit.float.str(e)
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
- def _ex_divide(self, e):
58
- # Native divide seemed to cause some issues
59
- #if self._nm:
60
- # return 'native_divide(' + self.ex(e[0]) +', '+ self.ex(e[1]) + ')'
61
- #else:
62
- return self._ex_infix(e, '/')
63
-
64
- def _ex_quotient(self, e):
65
- # Note that this _must_ round towards minus infinity.
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
- return self._ex_function(e, 'asin')
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
- f = 'native_log' if self._nm else 'log'
105
+ log = 'native_log' if self._nm else 'log'
117
106
  if len(e) == 1:
118
- return self._ex_function(e, f)
119
- return '(' + f + '(' + self.ex(e[0]) + ') / ' + f + '(' \
120
- + self.ex(e[1]) + '))'
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
- return self._ex_function(e, 'floor')
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
- return self._ex_infix_logical(e, '&&')
127
+ #def _ex_and(self, e):
128
+ #def _ex_or(self, e):
147
129
 
148
- def _ex_or(self, e):
149
- return self._ex_infix_logical(e, '||')
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
- return '(%s ? %s : %s)' % (
153
- self._exc(e._i), self.ex(e._t), self.ex(e._e))
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(e._i)
158
- for i in range(0, n):
159
- s.append('(')
160
- s.append(self._exc(e._i[i]))
161
- s.append(' ? ')
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 = '(' + self.ex(e[0]) + ') ' + op
45
+ out = f'({self.ex(e[0])}) {op}'
40
46
  else:
41
- out = self.ex(e[0]) + ' ' + op
47
+ out = f'{self.ex(e[0])} {op}'
42
48
  if e.bracket(e[1]):
43
- return out + ' (' + self.ex(e[1]) + ')'
49
+ return f'{out} ({self.ex(e[1])})'
44
50
  else:
45
- return out + ' ' + self.ex(e[1])
51
+ return f'{out} {self.ex(e[1])}'
46
52
 
47
53
  def _ex_function(self, e, func):
48
- """
49
- Handles ex() for function operators
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 _ex_infix_condition(self, e, op):
55
- """
56
- Handles ex() for infix condition operators
57
- """
58
- return '(' + self.ex(e[0]) + ' ' + op + ' ' + self.ex(e[1]) + ')'
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
- return self._flhs(e)
73
+ raise NotImplementedError(
74
+ 'Initial values are not supported by this expression writer.')
68
75
 
69
76
  def _ex_partial_derivative(self, e):
70
- return self._flhs(e)
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.ex(e[0])
84
+ return self._ex_prefix(e, '+')
77
85
 
78
86
  def _ex_prefix_minus(self, e):
79
- if e.bracket(e[0]):
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
- return self._ex_infix(e, '**')
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(' + self.ex(e[0]) + ')'
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._ex_infix_condition(e, '==')
160
+ return self._ex_infix_comparison(e, '==')
149
161
 
150
162
  def _ex_not_equal(self, e):
151
- return self._ex_infix_condition(e, '!=')
163
+ return self._ex_infix_comparison(e, '!=')
152
164
 
153
165
  def _ex_more(self, e):
154
- return self._ex_infix_condition(e, '>')
166
+ return self._ex_infix_comparison(e, '>')
155
167
 
156
168
  def _ex_less(self, e):
157
- return self._ex_infix_condition(e, '<')
169
+ return self._ex_infix_comparison(e, '<')
158
170
 
159
171
  def _ex_more_equal(self, e):
160
- return self._ex_infix_condition(e, '>=')
172
+ return self._ex_infix_comparison(e, '>=')
161
173
 
162
174
  def _ex_less_equal(self, e):
163
- return self._ex_infix_condition(e, '<=')
175
+ return self._ex_infix_comparison(e, '<=')
164
176
 
165
177
  def _ex_and(self, e):
166
- return self._ex_infix_condition(e, 'and')
178
+ return self._ex_infix_logical(e, 'and')
167
179
 
168
180
  def _ex_or(self, e):
169
- return self._ex_infix_condition(e, 'or')
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 '(' + self.ex(e._t) + ' if ' + self.ex(e._i) + ' else ' \
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 += '(' + self.ex(e._e[i]) + ' if ' + self.ex(e._i[i]) + ' else '
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
- #def _ex_log(self, e):
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
- #def _ex_abs(self, e):
225
- #def _ex_not(self, e):
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
- #def _ex_and(self, e):
233
- #def _ex_or(self, e):
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 + 'select([' + self.ex(e._i) + '], [' \
237
- + self.ex(e._t) + '], ' + self.ex(e._e) + ')'
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)
@@ -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
- # fmod uses correct convention
53
- return self._ex_function(e, 'fmod')
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
- return self._ex_infix(e, '^')
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) == 1:
69
- return self._ex_function(e, 'log')
70
- else:
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(' + self.ex(e[0]) + ')'
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._ex_infix_condition(e, '&&')
84
+ return self._ex_infix_logical(e, '&&')
94
85
 
95
86
  def _ex_or(self, e):
96
- return self._ex_infix_condition(e, '||')
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 ('(' + self.ex(e._i) + ' ? ' + self.ex(e._t) + ' : ' +
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
- n = len(e._i)
105
- for i in range(0, n):
106
- s.append('(%s ? %s : ' % (self.ex(e._i[i]), self.ex(e._e[i])))
107
- s.append(self.ex(e._e[n]))
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)