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
|
@@ -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
|
|
109
|
-
self.assertEqual(float(r.ex(ca
|
|
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)
|
myokit/tests/test_lib_hh.py
CHANGED
|
@@ -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()
|
myokit/tests/test_lib_plots.py
CHANGED
|
@@ -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
|
|
myokit/tests/test_model.py
CHANGED
|
@@ -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,
|
|
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
|
|
526
|
-
#
|
|
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.
|
|
533
|
+
self.assertIsNone(s.crash_state())
|
|
534
|
+
self.assertIsNone(s.crash_inputs())
|
|
533
535
|
s.run(1)
|
|
534
|
-
self.assertIsNone(s.
|
|
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(
|
|
539
|
-
self.assertEqual(
|
|
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
|
|
595
|
-
# Test :meth:`Simulation.
|
|
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
|
-
|
|
599
|
-
|
|
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
|
-
|
|
602
|
-
|
|
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
|
-
|
|
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.
|
|
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-
|
|
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:
|