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
@@ -0,0 +1,175 @@
1
+ #!/usr/bin/env python3
2
+ #
3
+ # Tests writing of Stan equations.
4
+ #
5
+ # This file is part of Myokit.
6
+ # See http://myokit.org for copyright, sharing, and licensing details.
7
+ #
8
+ import unittest
9
+
10
+ import myokit
11
+ import myokit.formats.stan
12
+
13
+ from myokit import (
14
+ Number, PrefixPlus, PrefixMinus, Plus, Minus,
15
+ Multiply, Divide, Quotient, Remainder, Power, Sqrt,
16
+ Exp, Log, Log10, Sin, Cos, Tan, ASin, ACos, ATan, Floor, Ceil, Abs,
17
+ Not, And, Or, Equal, NotEqual, More, Less, MoreEqual, LessEqual,
18
+ If, Piecewise,
19
+ )
20
+
21
+ import myokit.tests
22
+
23
+
24
+ class StanExpressionWriterTest(myokit.tests.ExpressionWriterTestCase):
25
+ """ Test conversion of expressions to Stan. """
26
+ _name = 'stan'
27
+ _target = myokit.formats.stan.StanExpressionWriter
28
+
29
+ def test_number(self):
30
+ self.eq(Number(1), '1.0')
31
+ self.eq(Number(-1.3274924373284374), '-1.32749243732843736e+00')
32
+ self.eq(Number(+1.3274924373284374), '1.32749243732843736e+00')
33
+ self.eq(Number(-2), '-2.0')
34
+ self.eq(Number(13, 'mV'), '13.0')
35
+
36
+ def test_name(self):
37
+ self.eq(self.a, 'a')
38
+ w = self._target()
39
+ w.set_lhs_function(lambda v: v.var().qname().upper())
40
+ self.assertEqual(w.ex(self.a), 'COMP.A')
41
+
42
+ def test_derivative(self):
43
+ self.eq(myokit.Derivative(self.a), 'dot(a)')
44
+
45
+ def test_partial_derivative(self):
46
+ e = myokit.PartialDerivative(self.a, self.b)
47
+ self.assertRaisesRegex(NotImplementedError, 'Partial', self.w.ex, e)
48
+
49
+ def test_initial_value(self):
50
+ e = myokit.InitialValue(self.a)
51
+ self.assertRaisesRegex(NotImplementedError, 'Initial', self.w.ex, e)
52
+
53
+ def test_prefix_plus_minus(self):
54
+ # Test with numbers
55
+ p = Number(11, 'kV')
56
+ self.eq(PrefixPlus(p), '+11.0')
57
+ self.eq(PrefixPlus(PrefixPlus(p)), '++11.0')
58
+ self.eq(PrefixMinus(p), '-11.0')
59
+ self.eq(PrefixMinus(PrefixMinus(p)), '--11.0')
60
+ self.eq(PrefixMinus(Number(-1)), '--1.0')
61
+
62
+ a, b, c = self.abc
63
+ self.eq(PrefixMinus(Plus(a, b)), '-(a + b)')
64
+ self.eq(Divide(PrefixPlus(Plus(a, b)), c), '+(a + b) / c')
65
+ self.eq(Power(PrefixMinus(a), b), '(-a)^b')
66
+ self.eq(Power(PrefixPlus(Power(b, a)), c), '(+b^a)^c')
67
+ self.eq(Power(a, PrefixMinus(Power(b, c))), 'a^(-b^c)')
68
+
69
+ def test_plus_minus(self):
70
+ a, b, c = self.abc
71
+ self.eq(Plus(a, b), 'a + b')
72
+ self.eq(Plus(Plus(a, b), c), 'a + b + c')
73
+ self.eq(Plus(a, Plus(b, c)), 'a + (b + c)')
74
+ self.eq(Minus(a, b), 'a - b')
75
+ self.eq(Minus(Minus(a, b), c), 'a - b - c')
76
+ self.eq(Minus(a, Minus(b, c)), 'a - (b - c)')
77
+ self.eq(Minus(a, b), 'a - b')
78
+ self.eq(Plus(Minus(a, b), c), 'a - b + c')
79
+ self.eq(Minus(a, Plus(b, c)), 'a - (b + c)')
80
+ self.eq(Minus(Plus(a, b), c), 'a + b - c')
81
+ self.eq(Minus(a, Plus(b, c)), 'a - (b + c)')
82
+
83
+ def test_multiply_divide(self):
84
+ a, b, c = self.abc
85
+ self.eq(Multiply(a, b), 'a * b')
86
+ self.eq(Multiply(Multiply(a, b), c), 'a * b * c')
87
+ self.eq(Multiply(a, Multiply(b, c)), 'a * (b * c)')
88
+ self.eq(Divide(a, b), 'a / b')
89
+ self.eq(Divide(Divide(a, b), c), 'a / b / c')
90
+ self.eq(Divide(a, Divide(b, c)), 'a / (b / c)')
91
+
92
+ self.eq(Divide(Multiply(a, b), c), 'a * b / c')
93
+ self.eq(Multiply(Divide(a, b), c), 'a / b * c')
94
+ self.eq(Divide(a, Multiply(b, c)), 'a / (b * c)')
95
+ self.eq(Multiply(a, Divide(b, c)), 'a * (b / c)')
96
+
97
+ self.eq(Multiply(Minus(a, b), c), '(a - b) * c')
98
+ self.eq(Divide(a, Plus(b, c)), 'a / (b + c)')
99
+ self.eq(Minus(Multiply(a, b), c), 'a * b - c')
100
+ self.eq(Plus(a, Divide(b, c)), 'a + b / c')
101
+
102
+ def test_quotient_remainder(self):
103
+ a, b, c = self.abc
104
+
105
+ self.eq(Quotient(c, a), 'floor(c / a)')
106
+ self.eq(Remainder(c, a), '(c - a * floor(c / a))')
107
+
108
+ def test_power(self):
109
+ a, b, c = self.abc
110
+ self.eq(Power(a, b), 'a^b')
111
+ self.eq(Power(Plus(a, b), c), '(a + b)^c')
112
+ self.eq(Power(a, Minus(b, c)), 'a^(b - c)')
113
+ self.eq(Power(Multiply(a, b), c), '(a * b)^c')
114
+ self.eq(Power(a, Divide(b, c)), 'a^(b / c)')
115
+
116
+ # Stan has a right-associative power operator, so must add brackets
117
+ # to get Myokit behavuour
118
+ self.eq(Power(Power(a, b), c), '(a^b)^c')
119
+ self.eq(Power(a, Power(b, c)), 'a^b^c')
120
+
121
+ def test_functions(self):
122
+ a, b = self.ab
123
+
124
+ self.eq(Sqrt(a), 'sqrt(a)')
125
+ self.eq(Exp(a), 'exp(a)')
126
+ self.eq(Log(a), 'log(a)')
127
+ self.eq(Log(a, b), '(log(a) / log(b))')
128
+ self.eq(Log10(a), 'log10(a)')
129
+ self.eq(Sin(a), 'sin(a)')
130
+ self.eq(Cos(a), 'cos(a)')
131
+ self.eq(Tan(a), 'tan(a)')
132
+ self.eq(ASin(a), 'asin(a)')
133
+ self.eq(ACos(a), 'acos(a)')
134
+ self.eq(ATan(a), 'atan(a)')
135
+ self.eq(Floor(a), 'floor(a)')
136
+ self.eq(Ceil(a), 'ceil(a)')
137
+ self.eq(Abs(a), 'abs(a)')
138
+
139
+ def test_conditions(self):
140
+ a, b, c, d = self.abcd
141
+
142
+ self.eq(And(a, b), '(a && b)')
143
+ self.eq(Or(d, c), '(d || c)')
144
+ self.eq(Not(c), '(!c)')
145
+
146
+ self.eq(Equal(a, b), '(a == b)')
147
+ self.eq(NotEqual(a, b), '(a != b)')
148
+ self.eq(More(b, a), '(b > a)')
149
+ self.eq(Less(d, c), '(d < c)')
150
+ self.eq(MoreEqual(c, a), '(c >= a)')
151
+ self.eq(LessEqual(b, d), '(b <= d)')
152
+
153
+ self.eq(And(Equal(a, b), NotEqual(c, d)), '((a == b) && (c != d))')
154
+ self.eq(Or(More(d, c), Less(b, a)), '((d > c) || (b < a))')
155
+ self.eq(Not(Equal(d, d)), '(!(d == d))')
156
+ self.eq(Not(Or(Number(1), Number(2))), '(!(1.0 || 2.0))')
157
+
158
+ self.eq(Equal(Equal(Number(0), Number(0)), Number(0)),
159
+ '((0.0 == 0.0) == 0.0)')
160
+
161
+ def test_conditionals(self):
162
+ a, b, c, d = self.abcd
163
+ self.eq(If(Equal(a, b), d, c), '((a == b) ? d : c)')
164
+ self.eq(Piecewise(NotEqual(d, c), b, a), '((d != c) ? b : a)')
165
+ self.eq(Piecewise(Equal(a, b), c, Equal(a, d), Number(3), Number(4)),
166
+ '((a == b) ? c : ((a == d) ? 3.0 : 4.0))')
167
+
168
+ # Extra parentheses if condition is not a condition
169
+ self.eq(If(a, d, c), '(a ? d : c)')
170
+ self.eq(Piecewise(a, b, c, d, Number(4)),
171
+ '(a ? b : (c ? d : 4.0))')
172
+
173
+
174
+ if __name__ == '__main__':
175
+ unittest.main()
@@ -64,6 +64,9 @@ class SymPyReadWriteTest(unittest.TestCase):
64
64
  self.assertEqual(w.ex(x), cb)
65
65
  # Note: Sympy doesn't seem to have a prefix plus
66
66
  self.assertEqual(r.ex(cb), b)
67
+ x = myokit.Divide(myokit.PrefixPlus(myokit.Plus(a, b)), self._b)
68
+ y = r.ex(w.ex(x))
69
+ self.assertEqual(y.code(), 'c.b^-1 * (12 + c.a)')
67
70
 
68
71
  # Prefix minus
69
72
  # Note: SymPy treats -x as Mul(NegativeOne, x)
@@ -105,8 +108,12 @@ class SymPyReadWriteTest(unittest.TestCase):
105
108
 
106
109
  # Power
107
110
  x = myokit.Power(a, b)
108
- self.assertEqual(w.ex(x), ca ** cb)
109
- self.assertEqual(float(r.ex(ca ** cb)), float(x))
111
+ self.assertEqual(w.ex(x), ca**cb)
112
+ self.assertEqual(float(r.ex(ca**cb)), float(x))
113
+ x = myokit.Power(myokit.Power(a, b), self._b)
114
+ self.assertEqual(w.ex(x), (ca**cb)**sp.Symbol('c.b'))
115
+ x = myokit.Power(a, myokit.Power(b, self._b))
116
+ self.assertEqual(w.ex(x), ca**(cb**sp.Symbol('c.b')))
110
117
 
111
118
  # Sqrt
112
119
  x = myokit.Sqrt(a)
@@ -15,6 +15,8 @@ import myokit.lib.hh as hh
15
15
 
16
16
  from myokit.tests import DIR_DATA
17
17
 
18
+ from myokit.tests import WarningCollector
19
+
18
20
 
19
21
  MODEL = """
20
22
  [[model]]
@@ -914,6 +916,40 @@ class AnalyticalSimulationTest(unittest.TestCase):
914
916
  e = np.abs(d1['binding.I'] - d2['binding.I'])
915
917
  self.assertLess(np.max(e), 2e-4)
916
918
 
919
+ def test_tau_overflow(self):
920
+ # Overflows leading to tau=0 should still report current
921
+ # https://github.com/myokit/myokit/issues/1059
922
+
923
+ # Load model and convert to inf-tau form
924
+ fname = os.path.join(DIR_DATA, 'lr-1991-fitting.mmt')
925
+ model = hh.convert_hh_states_to_inf_tau_form(myokit.load_model(fname))
926
+
927
+ # Get initial state and set steady state
928
+ initial_state = model.get('ina.m').initial_value(as_float=True)
929
+ steady_state = 0.75
930
+ model.get('ina.m.inf').set_rhs(steady_state)
931
+
932
+ # Create an analytical simulation
933
+ model = hh.HHModel(model, states=['ina.m', 'ina.h', 'ina.j'],
934
+ parameters=['ina.p5'], current='ina.INa')
935
+ p = myokit.pacing.steptrain_linear(20, 40, 10, -80, 10, 10)
936
+ s = hh.AnalyticalSimulation(model, protocol=p)
937
+
938
+ # Setting p5=0.001 will trigger an overflow in ina.m.beta
939
+ s.set_parameters([0.001])
940
+ with WarningCollector() as wc:
941
+ # Log times chosen so that s._function varies length of _t
942
+ log = s.run(41, log_times=np.array([0, 1, 2, 10, 11, 12, 20, 40]))
943
+ self.assertIn('overflow', wc.text())
944
+
945
+ # Should be no NaNs in log
946
+ self.assertFalse(np.any(np.isnan(log['ina.INa'])))
947
+ self.assertFalse(np.any(np.isnan(log['ina.m'])))
948
+
949
+ # Should immediately jump from initial state to steady state
950
+ self.assertTrue(log['ina.m'][0] == initial_state)
951
+ self.assertTrue(np.all(log['ina.m'][1:] == steady_state))
952
+
917
953
 
918
954
  if __name__ == '__main__':
919
955
  unittest.main()
@@ -41,14 +41,12 @@ class LibPlotTest(unittest.TestCase):
41
41
  # Stair
42
42
  fig = plt.figure()
43
43
  plots.simulation_times(st, rt, ev, 'stair')
44
- plt.show()
45
44
  plt.close(fig)
46
45
  self.assertRaises(ValueError, plots.simulation_times, mode='stair')
47
46
 
48
47
  # Inverse stair
49
48
  fig = plt.figure()
50
49
  plots.simulation_times(st, rt, ev, 'stair_inverse')
51
- plt.show()
52
50
  plt.close(fig)
53
51
  self.assertRaises(
54
52
  ValueError, plots.simulation_times, mode='stair_inverse')
@@ -56,21 +54,18 @@ class LibPlotTest(unittest.TestCase):
56
54
  # Load
57
55
  fig = plt.figure()
58
56
  plots.simulation_times(st, rt, ev, 'load')
59
- plt.show()
60
57
  plt.close(fig)
61
58
  self.assertRaises(ValueError, plots.simulation_times, mode='load')
62
59
 
63
60
  # Histogram
64
61
  fig = plt.figure()
65
62
  plots.simulation_times(st, rt, ev, 'histo')
66
- plt.show()
67
63
  plt.close(fig)
68
64
  self.assertRaises(ValueError, plots.simulation_times, mode='histo')
69
65
 
70
66
  # Time per step
71
67
  fig = plt.figure()
72
68
  plots.simulation_times(st, rt, ev, 'time_per_step')
73
- plt.show()
74
69
  plt.close(fig)
75
70
  self.assertRaises(
76
71
  ValueError, plots.simulation_times, mode='time_per_step')
@@ -78,7 +73,6 @@ class LibPlotTest(unittest.TestCase):
78
73
  # Evaluations per step
79
74
  fig = plt.figure()
80
75
  plots.simulation_times(st, rt, ev, 'eval_per_step')
81
- plt.show()
82
76
  plt.close(fig)
83
77
  self.assertRaises(
84
78
  ValueError, plots.simulation_times, mode='eval_per_step')
@@ -111,7 +105,6 @@ class LibPlotTest(unittest.TestCase):
111
105
  fig = plt.figure()
112
106
  plots.current_arrows(d, 'membrane.V', currents)
113
107
  plt.close(fig)
114
- plt.show()
115
108
 
116
109
  # Massive peak at final point
117
110
  d = d.npview()
@@ -119,7 +112,6 @@ class LibPlotTest(unittest.TestCase):
119
112
  fig = plt.figure()
120
113
  plots.current_arrows(d, 'membrane.V', currents)
121
114
  plt.close(fig)
122
- plt.show()
123
115
 
124
116
  def test_cumulative_current(self):
125
117
  # Test the cumulative current plot.
@@ -140,7 +132,6 @@ class LibPlotTest(unittest.TestCase):
140
132
  plots.cumulative_current(d, currents)
141
133
  plt.legend()
142
134
  plt.close(fig)
143
- plt.show()
144
135
 
145
136
  # Labels set
146
137
  labels = ['I_Ca', 'I_K', 'I_K1', 'I_Kp', 'I_b']
@@ -148,7 +139,6 @@ class LibPlotTest(unittest.TestCase):
148
139
  plots.cumulative_current(d, currents, labels=labels)
149
140
  plt.legend()
150
141
  plt.close(fig)
151
- plt.show()
152
142
 
153
143
  # Colors set
154
144
  colors = ['green', 'blue', 'yellow', 'brown', 'gray']
@@ -156,7 +146,6 @@ class LibPlotTest(unittest.TestCase):
156
146
  plots.cumulative_current(d, currents, colors=colors)
157
147
  plt.legend()
158
148
  plt.close(fig)
159
- plt.show()
160
149
 
161
150
  # Not enough colors set (will repeat array)
162
151
  colors = ['green', 'blue']
@@ -164,35 +153,30 @@ class LibPlotTest(unittest.TestCase):
164
153
  plots.cumulative_current(d, currents, colors=colors)
165
154
  plt.legend()
166
155
  plt.close(fig)
167
- plt.show()
168
156
 
169
157
  # Integrate currents to charges
170
158
  fig = plt.figure()
171
159
  plots.cumulative_current(d, currents, integrate=True)
172
160
  plt.legend()
173
161
  plt.close(fig)
174
- plt.show()
175
162
 
176
163
  # Normalize currents
177
164
  fig = plt.figure()
178
165
  plots.cumulative_current(d, currents, normalize=True)
179
166
  plt.legend()
180
167
  plt.close(fig)
181
- plt.show()
182
168
 
183
169
  # Normalize currents and set maximum number of currents shown
184
170
  fig = plt.figure()
185
171
  plots.cumulative_current(d, currents, normalize=True, max_currents=3)
186
172
  plt.legend()
187
173
  plt.close(fig)
188
- plt.show()
189
174
 
190
175
  with WarningCollector() as w:
191
176
  fig = plt.figure()
192
177
  plots.cumulative_current(d, currents, normalise=True)
193
178
  plt.legend()
194
179
  plt.close(fig)
195
- plt.show()
196
180
  self.assertIn('deprecated', w.text())
197
181
 
198
182
 
@@ -438,6 +438,7 @@ class ModelTest(unittest.TestCase):
438
438
  b = component.add_variable('b')
439
439
  c = component.add_variable('c')
440
440
  d = component.add_variable('d')
441
+ e = component.add_variable('e')
441
442
  a.promote(1)
442
443
  a.set_rhs('1')
443
444
  b.promote(2)
@@ -445,6 +446,7 @@ class ModelTest(unittest.TestCase):
445
446
  c.promote(3)
446
447
  c.set_rhs('b + d')
447
448
  d.set_rhs('1 * c')
449
+ e.set_rhs(0)
448
450
  model.validate()
449
451
 
450
452
  # Test without input
@@ -476,13 +478,31 @@ class ModelTest(unittest.TestCase):
476
478
  model.evaluate_derivatives(
477
479
  state=[2, 3, 4], inputs={'time': 10}, ignore_errors=True),
478
480
  [1, 6, 17])
481
+ d.set_rhs(10)
482
+ e.set_rhs(20)
483
+ d.set_binding('realtime')
484
+ e.set_binding('evaluations')
485
+ c.set_rhs('d + e + time')
486
+ self.assertEqual(model.evaluate_derivatives(), [1, 4, 31])
487
+ self.assertEqual(
488
+ model.evaluate_derivatives(inputs={'evaluations': 1.2}),
489
+ [1, 4, 12.2])
490
+ e.set_binding(None)
491
+ self.assertEqual(model.evaluate_derivatives(), [1, 4, 31])
492
+ e.set_binding('pace')
493
+ self.assertEqual(model.evaluate_derivatives(), [1, 4, 31])
494
+ self.assertEqual(
495
+ model.evaluate_derivatives(inputs={'pace': 10}), [1, 4, 21])
496
+ self.assertEqual(
497
+ model.evaluate_derivatives(inputs={'pace': 10, 'time': 20}),
498
+ [1, 4, 40])
479
499
 
480
500
  # Deprecated name
481
501
  with WarningCollector() as w:
482
502
  self.assertEqual(
483
503
  model.eval_state_derivatives(
484
504
  state=[1, 1, 2], inputs={'time': 0}),
485
- [1, 2, 3])
505
+ [1, 2, 30])
486
506
  self.assertIn('deprecated', w.text())
487
507
 
488
508
  # Ignoring errors should lead to Nans
@@ -522,21 +522,62 @@ class SimulationTest(unittest.TestCase):
522
522
  ValueError, 'no `apd_variable` specified',
523
523
  self.sim.run, 1, apd_threshold=12)
524
524
 
525
- def test_last_state(self):
526
- # Returns the last state before an error, or None.
525
+ def test_crash_state_and_inputs(self):
526
+ # Tests Simulation.crash_state
527
527
 
528
528
  m = self.model.clone()
529
529
  istim = m.get('membrane.i_stim')
530
530
  istim.set_rhs('engine.pace / stim_amplitude')
531
+ '''
531
532
  s = myokit.Simulation(m, self.protocol)
532
- self.assertIsNone(s.last_state())
533
+ self.assertIsNone(s.crash_state())
534
+ self.assertIsNone(s.crash_inputs())
533
535
  s.run(1)
534
- self.assertIsNone(s.last_state())
536
+ self.assertIsNone(s.crash_state())
535
537
  s.set_constant('membrane.i_stim.stim_amplitude', 0)
536
538
  s.reset()
537
539
  self.assertRaisesRegex(myokit.SimulationError, "at t = 0", s.run, 5)
538
- self.assertEqual(len(s.last_state()), len(s.state()))
539
- self.assertEqual(s.last_state(), s.state())
540
+ self.assertEqual(s.crash_state(), s.state())
541
+ self.assertEqual(
542
+ set(s.crash_inputs()), {'time', 'pace', 'realtime', 'evaluations'})
543
+ t = s.crash_inputs()['time']
544
+ self.assertEqual(s.crash_inputs()['time'], 0)
545
+ self.assertEqual(s.crash_inputs()['pace'], 0)
546
+ self.assertGreaterEqual(s.crash_inputs()['realtime'], 0)
547
+ self.assertGreater(s.crash_inputs()['evaluations'], 0)
548
+ '''
549
+
550
+ # Test crash at later time
551
+ istim.set_rhs('if(engine.time == 5, 1 / (5 - engine.time), 0)')
552
+ # Ensure t=5 is visited
553
+ p = myokit.pacing.blocktrain(duration=0.1, period=5)
554
+ # Test evaluations and realtime only present if used
555
+ m.binding('evaluations').set_binding(None)
556
+ s = myokit.Simulation(m, p)
557
+ with myokit.tools.capture():
558
+ self.assertRaisesRegex(myokit.SimulationError, 'CV_CONV', s.run, 6)
559
+ self.assertEqual(
560
+ set(s.crash_inputs()), {'time', 'pace', 'realtime'})
561
+ self.assertEqual(s.crash_inputs()['time'], 5)
562
+ self.assertEqual(s.crash_inputs()['pace'], 1)
563
+ self.assertGreater(s.crash_inputs()['realtime'], 0)
564
+
565
+ # Above should both crash via cvode flag set. Next should be halted by
566
+ # C code for too many zero steps
567
+ istim.set_rhs('1 / (5 - engine.time)')
568
+ s = myokit.Simulation(m, p)
569
+ with myokit.tools.capture():
570
+ self.assertRaisesRegex(
571
+ myokit.SimulationError, 'zero-length', s.run, 6)
572
+ self.assertAlmostEqual(s.crash_inputs()['time'], 5)
573
+ self.assertEqual(s.crash_inputs()['pace'], 1)
574
+ self.assertGreater(s.crash_inputs()['realtime'], 0)
575
+
576
+ # Test deprecated alias of crash_state
577
+ x = s.crash_state()
578
+ with WarningCollector() as w:
579
+ self.assertEqual(s.last_state(), x)
580
+ self.assertIn('eprecated', w.text())
540
581
 
541
582
  def test_last_evaluations_and_steps(self):
542
583
  # Test :meth:`Simulation.last_number_of_evaluations()` and
@@ -591,59 +632,99 @@ class SimulationTest(unittest.TestCase):
591
632
  self.assertEqual(s[0][0], 1)
592
633
  self.assertEqual(s[1][0], 0)
593
634
 
594
- def test_eval_derivatives(self):
595
- # Test :meth:`Simulation.eval_derivatives()`.
635
+ def test_evaluate_derivatives(self):
636
+ # Test :meth:`Simulation.evaluate_derivatives()`.
596
637
 
638
+ m = myokit.Model()
639
+ z = m.add_component('z')
640
+ t = z.add_variable('t', rhs=0, binding='time')
641
+ a = z.add_variable('a', rhs=1, binding='pace')
642
+ b = z.add_variable('b', rhs=2, binding='pace1')
643
+ c = z.add_variable('c', rhs=4, binding='pace2')
644
+ d = z.add_variable('d', rhs=8, binding='evaluations')
645
+ e = z.add_variable('e', rhs=12, binding='realtime')
646
+ f = z.add_variable('f', rhs=100)
647
+ p = z.add_variable('p', rhs='p+t+a+b+c+d+e+f', initial_value=0)
648
+ q = z.add_variable('q', rhs='p + q', initial_value=1)
649
+
650
+ sim = myokit.Simulation(m)
651
+
652
+ # Test without input
653
+ self.assertEqual(sim.evaluate_derivatives(), [106, 1])
654
+
655
+ # Test with new state
656
+ self.assertEqual(sim.evaluate_derivatives([1, 2]), [107, 3])
657
+
658
+ # Test with changed constant
659
+ sim.set_constant('z.f', 200)
660
+ self.assertEqual(sim.evaluate_derivatives(), [206, 1])
661
+ sim.set_constant('z.f', 300)
662
+ self.assertEqual(sim.evaluate_derivatives([1, 2]), [307, 3])
663
+ sim.set_constant('z.f', 0)
664
+ self.assertEqual(sim.evaluate_derivatives([1, 2]), [7, 3])
665
+
666
+ # Test with inputs
667
+ d = {'time': 1}
668
+ self.assertEqual(sim.evaluate_derivatives([1, 2], d), [8, 3])
669
+ d = {'time': 2}
670
+ self.assertEqual(sim.evaluate_derivatives([1, 2], d), [9, 3])
671
+ d = {'time': 2, 'evaluations': 3}
672
+ self.assertEqual(sim.evaluate_derivatives([1, 2], d), [12, 3])
673
+ d = {'time': 2, 'evaluations': 3, 'realtime': 1.23}
674
+ self.assertEqual(sim.evaluate_derivatives([1, 2], d), [13.23, 3])
675
+
676
+ # Test with unknown inputs
677
+ d = {'toime': 1}
678
+ self.assertRaisesRegex(ValueError, 'Unknown binding or',
679
+ sim.evaluate_derivatives, [1, 2], d)
680
+
681
+ # Test with pacing: not passed on to sim
682
+ sim.set_protocol(myokit.pacing.constant(1000))
683
+
684
+ # Running doesn't change anything
685
+ self.assertEqual(sim.evaluate_derivatives(), [6, 1])
686
+ sim.run(1)
687
+ self.assertEqual(sim.evaluate_derivatives(), [6, 1])
688
+ self.assertEqual(sim.evaluate_derivatives([2, 3]), [8, 5])
689
+ sim.run(10)
690
+ self.assertEqual(sim.evaluate_derivatives(), [6, 1])
691
+ self.assertEqual(sim.evaluate_derivatives([2, 3]), [8, 5])
692
+ sim.reset()
693
+ self.assertEqual(sim.evaluate_derivatives([2, 3]), [8, 5])
694
+ d = {'time': 123}
695
+ self.assertEqual(sim.evaluate_derivatives([2, 3], d), [131, 5])
696
+
697
+ # Changing the pacing labels
698
+ d = {'pace': 1000}
699
+ self.assertEqual(sim.evaluate_derivatives(inputs=d), [1006, 1])
700
+ d = {'pace1': 1}
701
+ self.assertRaisesRegex(ValueError, 'Unknown binding or',
702
+ sim.evaluate_derivatives, inputs=d)
703
+
704
+ sim = myokit.Simulation(m, {
705
+ 'pace1': myokit.pacing.constant(1000),
706
+ 'pace2': myokit.pacing.constant(2000)})
707
+ d = {'pace': 1}
708
+ self.assertRaisesRegex(ValueError, 'Unknown binding or',
709
+ sim.evaluate_derivatives, [1, 2], d)
710
+ self.assertEqual(sim.evaluate_derivatives(), [101, 1])
711
+ d = {'pace1': 100}
712
+ self.assertEqual(sim.evaluate_derivatives(inputs=d), [201, 1])
713
+ d = {'pace2': 200}
714
+ self.assertEqual(sim.evaluate_derivatives(inputs=d), [301, 1])
715
+ d = {'pace1': 123, 'pace2': 200}
716
+ self.assertEqual(sim.evaluate_derivatives(inputs=d), [424, 1])
717
+
718
+ # Test deprecated method
597
719
  self.sim.reset()
598
- s1 = self.sim.state()
599
- d1 = self.sim.eval_derivatives()
720
+ d1 = self.sim.evaluate_derivatives()
721
+ with WarningCollector() as w:
722
+ self.assertEqual(self.sim.eval_derivatives(), d1)
723
+ self.assertIn('eprecated', w.text())
724
+ # ...which uses state() instead of default state
600
725
  self.sim.run(1)
601
- d2 = self.sim.eval_derivatives()
602
- self.assertNotEqual(d1, d2)
603
- self.assertEqual(d1, self.sim.eval_derivatives(s1))
604
- self.sim.set_state(s1)
605
- self.assertEqual(d1, self.sim.eval_derivatives())
606
-
607
- def test_eval_derivatives_with_pacing(self):
608
- # Test :meth:`Simulation.eval_derivatives()`.
609
-
610
- model = myokit.Model()
611
- c = model.add_component('c')
612
-
613
- a = c.add_variable('a')
614
- a.set_binding('a')
615
- a.set_rhs(0)
616
- b = c.add_variable('b')
617
- b.set_binding('b')
618
- b.set_rhs(0)
619
-
620
- y = c.add_variable('y')
621
- t = c.add_variable('t')
622
- t.set_binding('time')
623
- t.set_rhs(0)
624
- y.promote(1)
625
- y.set_rhs('- a * y - b * y')
626
-
627
- pa = myokit.Protocol()
628
- pa.schedule(1, 0, 0.5)
629
-
630
- pb = myokit.Protocol()
631
- pb.schedule(2, 1.0, 0.5)
632
-
633
- sim = myokit.Simulation(model, {'a': pa, 'b': pb})
634
- sim.run(1)
635
- d1 = sim.eval_derivatives(pacing={'a': 0.5, 'b': 0.5})
636
- d2 = sim.eval_derivatives(pacing={'a': 1.5, 'b': 0.5})
637
- self.assertNotEqual(d1, d2)
638
- d1 = sim.eval_derivatives(pacing={'b': 0.5})
639
- d2 = sim.eval_derivatives(pacing={'a': 0.0, 'b': 0.5})
640
- self.assertEqual(d1, d2)
641
- d1 = sim.eval_derivatives()
642
- d2 = sim.eval_derivatives(pacing={'a': 0.0, 'b': 0.0})
643
- self.assertEqual(d1, d2)
644
- d1 = sim.eval_derivatives(pacing={'a': 0.0, 'b': 0.5})
645
- d2 = sim.eval_derivatives(pacing={'aaaaa': 1.0, 'b': 0.5})
646
- self.assertEqual(d1, d2)
726
+ with WarningCollector() as w:
727
+ self.assertNotEqual(self.sim.eval_derivatives(), d1)
647
728
 
648
729
  def test_sensitivities_initial(self):
649
730
  # Test setting initial sensitivity values.
myokit/tools.py CHANGED
@@ -23,7 +23,8 @@ _natural_sort_regex = re.compile('([0-9]+)')
23
23
 
24
24
  class Benchmarker:
25
25
  """
26
- Allows benchmarking using the with statement.
26
+ Provides reasonably precise benchmarking (with times obtained from
27
+ ``timeit.default_timer()``) and formats times.
27
28
 
28
29
  Example::
29
30
 
@@ -34,7 +35,7 @@ class Benchmarker:
34
35
  print(b.time())
35
36
  b.reset()
36
37
  s.run()
37
- print(b.time())
38
+ print(b.format())
38
39
 
39
40
  """
40
41
  def __init__(self):
@@ -3,7 +3,7 @@ BSD 3-Clause License
3
3
  Copyright (c) 2011-2017 Maastricht University. All rights reserved.
4
4
  Copyright (c) 2017-2020 University of Oxford. All rights reserved.
5
5
  (University of Oxford means the Chancellor, Masters and Scholars of the University of Oxford, having an administrative office at Wellington Square, Oxford OX1 2JD, UK).
6
- Copyright (c) 2020-2023 University of Nottingham. All rights reserved.
6
+ Copyright (c) 2020-2024 University of Nottingham. All rights reserved.
7
7
 
8
8
  Redistribution and use in source and binary forms, with or without
9
9
  modification, are permitted provided that the following conditions are met: