myokit 1.35.0__py3-none-any.whl → 1.35.2__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 (47) hide show
  1. myokit/__init__.py +11 -14
  2. myokit/__main__.py +0 -3
  3. myokit/_config.py +1 -3
  4. myokit/_datablock.py +914 -12
  5. myokit/_model_api.py +1 -3
  6. myokit/_myokit_version.py +1 -1
  7. myokit/_protocol.py +14 -28
  8. myokit/_sim/cable.c +1 -1
  9. myokit/_sim/cable.py +3 -2
  10. myokit/_sim/cmodel.h +1 -0
  11. myokit/_sim/cvodessim.c +79 -42
  12. myokit/_sim/cvodessim.py +20 -8
  13. myokit/_sim/fiber_tissue.c +1 -1
  14. myokit/_sim/fiber_tissue.py +3 -2
  15. myokit/_sim/openclsim.c +1 -1
  16. myokit/_sim/openclsim.py +8 -11
  17. myokit/_sim/pacing.h +121 -106
  18. myokit/_unit.py +1 -1
  19. myokit/formats/__init__.py +178 -0
  20. myokit/formats/axon/_abf.py +911 -841
  21. myokit/formats/axon/_atf.py +62 -59
  22. myokit/formats/axon/_importer.py +2 -2
  23. myokit/formats/heka/__init__.py +38 -0
  24. myokit/formats/heka/_importer.py +39 -0
  25. myokit/formats/heka/_patchmaster.py +2512 -0
  26. myokit/formats/wcp/_wcp.py +318 -133
  27. myokit/gui/datablock_viewer.py +144 -77
  28. myokit/gui/datalog_viewer.py +212 -231
  29. myokit/tests/ansic_event_based_pacing.py +3 -3
  30. myokit/tests/{ansic_fixed_form_pacing.py → ansic_time_series_pacing.py} +6 -6
  31. myokit/tests/data/formats/abf-v2.abf +0 -0
  32. myokit/tests/test_datablock.py +84 -0
  33. myokit/tests/test_datalog.py +2 -1
  34. myokit/tests/test_formats_axon.py +589 -136
  35. myokit/tests/test_formats_wcp.py +191 -22
  36. myokit/tests/test_pacing_system_c.py +51 -23
  37. myokit/tests/test_pacing_system_py.py +18 -0
  38. myokit/tests/test_simulation_1d.py +62 -22
  39. myokit/tests/test_simulation_cvodes.py +52 -3
  40. myokit/tests/test_simulation_fiber_tissue.py +35 -4
  41. myokit/tests/test_simulation_opencl.py +28 -4
  42. {myokit-1.35.0.dist-info → myokit-1.35.2.dist-info}/LICENSE.txt +1 -1
  43. {myokit-1.35.0.dist-info → myokit-1.35.2.dist-info}/METADATA +1 -1
  44. {myokit-1.35.0.dist-info → myokit-1.35.2.dist-info}/RECORD +47 -44
  45. {myokit-1.35.0.dist-info → myokit-1.35.2.dist-info}/WHEEL +0 -0
  46. {myokit-1.35.0.dist-info → myokit-1.35.2.dist-info}/entry_points.txt +0 -0
  47. {myokit-1.35.0.dist-info → myokit-1.35.2.dist-info}/top_level.txt +0 -0
@@ -10,9 +10,77 @@ import unittest
10
10
 
11
11
  import numpy as np
12
12
 
13
+ import myokit
13
14
  import myokit.formats.wcp as wcp
14
15
 
15
- from myokit.tests import DIR_FORMATS
16
+ from myokit.tests import DIR_FORMATS, WarningCollector
17
+
18
+
19
+ INFO = '''
20
+ WinWCP file: wcp-file.wcp
21
+ WinWCP Format version 9
22
+ Recorded on: 21/11/2014 14:18:28
23
+ Number of records: 11
24
+ Channels per record: 2
25
+ Samples per channel: 256
26
+ Sampling interval: 0.001 s
27
+ A/D channel: Im
28
+ Unit: pA
29
+ A/D channel: Vm
30
+ Unit: mV
31
+ Records: Type, Status, Sampling Interval, Start, Marker
32
+ Record 0: TEST, ACCEPTED, 0.0, ""
33
+ Record 1: TEST, ACCEPTED, 0.5, ""
34
+ Record 2: TEST, ACCEPTED, 1.0, ""
35
+ Record 3: TEST, ACCEPTED, 1.5, ""
36
+ Record 4: TEST, ACCEPTED, 2.0615234375, ""
37
+ Record 5: TEST, ACCEPTED, 3.0615234375, ""
38
+ Record 6: TEST, ACCEPTED, 3.5615234375, ""
39
+ Record 7: TEST, ACCEPTED, 4.0615234375, ""
40
+ Record 8: TEST, ACCEPTED, 4.5615234375, ""
41
+ Record 9: TEST, ACCEPTED, 5.125, ""
42
+ Record 10: TEST, ACCEPTED, 5.625, ""
43
+ '''.strip()
44
+
45
+
46
+ INFO_LONG = INFO + '''
47
+ ----------------------------------- header -----------------------------------
48
+ ver: 9
49
+ ctime: 21/11/2014 12:43:08
50
+ rtime: 21/11/2014 14:18:28
51
+ nbh: 1024
52
+ adcmax: 32677
53
+ nc: 2
54
+ nba: 2
55
+ nbd: 2
56
+ ad: 10.0
57
+ nr: 11
58
+ dt: 0.001
59
+ nz: 20
60
+ id: ''' + '''
61
+ --------------------------------- raw header ---------------------------------
62
+ rtimesecs: 9062.11
63
+ yo0: 0
64
+ yu0: pA
65
+ yn0: Im
66
+ yg0: 0.0005
67
+ yz0: 0
68
+ yr0: 0
69
+ yo1: 1
70
+ yu1: mV
71
+ yn1: Vm
72
+ yg1: 0.01
73
+ yz1: 0
74
+ yr1: 0
75
+ txperc: 0
76
+ pkpavg: 1
77
+ nsvchan: 0
78
+ nsvalign: 0
79
+ nsvtypr: 0
80
+ nsvs2p: F
81
+ nsvcur0: 0
82
+ nsvcur1: 0
83
+ '''.rstrip()
16
84
 
17
85
 
18
86
  class WcpTest(unittest.TestCase):
@@ -23,43 +91,144 @@ class WcpTest(unittest.TestCase):
23
91
  def test_data_file(self):
24
92
  # Test basic wcp file reading.
25
93
 
26
- # Load old file from Maastricht
94
+ # Load test file
27
95
  fname = 'wcp-file.wcp'
28
96
  path = os.path.join(DIR_FORMATS, fname)
29
97
  w = wcp.WcpFile(path)
98
+ self.assertEqual(w.version(), '9')
30
99
 
31
- self.assertEqual(w.channels(), 2)
32
- self.assertEqual(w.channel_names(), ['Im', 'Vm'])
33
100
  self.assertEqual(w.filename(), fname)
34
101
  self.assertEqual(w.path(), path)
35
- self.assertEqual(w.records(), 11)
36
- #print(w.sampling_interval())
102
+ self.assertEqual(w.record_count(), 11)
103
+ self.assertEqual(w.sample_count(), 256)
37
104
  self.assertEqual(len(w.times()), 256)
38
105
  self.assertEqual(len(w.values(0, 0)), 256)
39
106
 
40
- def test_data_log_conversion(self):
41
- # Test conversion to a data log.
107
+ # Test meta data
108
+ self.maxDiff = None
109
+ self.assertEqual(w.meta_str(), INFO)
110
+ self.assertEqual(w.meta_str(True), INFO_LONG)
111
+ with WarningCollector() as c:
112
+ self.assertEqual(w.info(), INFO)
113
+ self.assertIn('deprecated', c.text())
42
114
 
43
- w = wcp.WcpFile(os.path.join(DIR_FORMATS, 'wcp-file.wcp'))
44
- d = w.myokit_log()
45
- keys = ['time']
46
- for cn in w.channel_names():
47
- for i in range(w.records()):
48
- keys.append(str(i) + '.' + cn)
49
- self.assertEqual(set(d.keys()), set(keys))
50
- self.assertEqual(len(d.time()), 256)
51
- self.assertTrue(np.all(d.time() == w.times()))
52
-
53
- def test_plot_method(self):
54
- # Test the plot method.
115
+ # Test Sequence interface
116
+ self.assertEqual(len(w), w.record_count())
117
+ self.assertEqual(np.array(w[0]).shape, (2, 256))
118
+ self.assertEqual(len([r for r in w]), w.record_count())
119
+
120
+ # Test SweepSource interface
121
+ self.assertEqual(w.channel_count(), 2)
122
+ self.assertEqual(w.channel_names(), ['Im', 'Vm'])
123
+ self.assertEqual(w.channel_units(), [myokit.units.pA, myokit.units.mV])
124
+ self.assertEqual(w.channel_names(0), 'Im')
125
+ self.assertEqual(w.channel_names(1), 'Vm')
126
+ self.assertEqual(w.channel_units(0), myokit.units.pA)
127
+ self.assertEqual(w.channel_units(1), myokit.units.mV)
128
+ self.assertEqual(w.sweep_count(), w.record_count())
129
+ self.assertTrue(w.equal_length_sweeps())
130
+ self.assertEqual(w.time_unit(), myokit.units.s)
131
+
132
+ # Test SweepSource.channel()
133
+ # Without joining
134
+ t0, v0 = w.channel(0)
135
+ t1, v1 = w.channel(1)
136
+ self.assertEqual(len(t0), len(t1), w.sweep_count())
137
+ self.assertEqual(len(v0), len(v1), w.sweep_count())
138
+ self.assertEqual(len(t0[0]), w.sample_count())
139
+ self.assertEqual(len(t1[1]), w.sample_count())
140
+ self.assertEqual(len(v0[2]), w.sample_count())
141
+ self.assertEqual(len(v1[3]), w.sample_count())
142
+ self.assertTrue(np.all(v0[0] == w.channel('Im')[1][0]))
143
+ self.assertTrue(np.all(v0[0] != w.channel('Vm')[1][0]))
144
+ self.assertTrue(np.all(v1[1] == w.channel('Vm')[1][1]))
145
+
146
+ # With joining
147
+ x = w.channel(0, join_sweeps=True)
148
+ y = w.channel(1, join_sweeps=True)
149
+ self.assertEqual(len(x), len(y), 2)
150
+ self.assertEqual(len(x[0]), w.sample_count() * w.sweep_count())
151
+ self.assertEqual(len(x[0]), len(x[1]))
152
+ self.assertEqual(len(x[0]), len(y[0]), len(y[1]))
153
+ self.assertTrue(np.all(x[1] == w.channel('Im', True)[1]))
154
+ self.assertTrue(np.all(x[1] != w.channel('Vm', True)[1]))
155
+ self.assertTrue(np.all(y[1] == w.channel('Vm', True)[1]))
156
+
157
+ # Channel doesn't exist
158
+ self.assertRaises(IndexError, w.channel, -1)
159
+ self.assertRaises(IndexError, w.channel, 2)
160
+ self.assertRaises(KeyError, w.channel, 'Tom')
55
161
 
162
+ # Conversion to log
163
+ # Without joining
164
+ d = w.log()
165
+ k = ['time']
166
+ for r in range(w.record_count()):
167
+ for c in range(w.channel_count()):
168
+ k.append(f'{r}.{c}.channel')
169
+ self.assertEqual(set(k), set(d.keys()))
170
+ self.assertTrue(np.all(d['time'] == w.channel(0)[0][0]))
171
+ self.assertTrue(np.all(d['1.0.channel'] == w.channel(0)[1][1]))
172
+
173
+ # Without joining, use names
174
+ d = w.log(use_names=True)
175
+ k = ['time']
176
+ for c in w.channel_names():
177
+ for r in range(w.record_count()):
178
+ k.append(f'{r}.{c}')
179
+ self.assertEqual(set(k), set(d.keys()))
180
+ self.assertTrue(np.all(d['time'] == w.channel(0)[0][0]))
181
+ self.assertTrue(np.all(d['1.Im'] == w.channel(0)[1][1]))
182
+
183
+ # With joining
184
+ d = w.log(join_sweeps=True)
185
+ self.assertEqual(list(d.keys()), ['time', '0.channel', '1.channel'])
186
+ self.assertTrue(np.all(d['time'] == w.channel(0, True)[0]))
187
+ self.assertTrue(np.all(d['0.channel'] == w.channel(0, True)[1]))
188
+
189
+ # With joining, use names
190
+ d = w.log(join_sweeps=True, use_names=True)
191
+ self.assertEqual(list(d.keys()), ['time', 'Im', 'Vm'])
192
+ self.assertTrue(np.all(d['time'] == w.channel(0, True)[0]))
193
+ self.assertTrue(np.all(d['Vm'] == w.channel(1, True)[1]))
194
+
195
+ # Deprecated methods
196
+ with WarningCollector() as c:
197
+ self.assertEqual(w.records(), w.record_count())
198
+ self.assertIn('deprecated', c.text())
199
+ with WarningCollector() as c:
200
+ self.assertEqual(w.channels(), w.channel_count())
201
+ self.assertIn('deprecated', c.text())
202
+ with WarningCollector() as c:
203
+ e = w.myokit_log()
204
+ self.assertIn('deprecated', c.text())
205
+
206
+ # Unsupported da methods
207
+ self.assertEqual(w.da_count(), 0)
208
+ self.assertRaises(NotImplementedError, w.da, 0)
209
+ self.assertRaises(NotImplementedError, w.da_names)
210
+ self.assertRaises(NotImplementedError, w.da_units)
211
+ self.assertRaises(NotImplementedError, w.da_protocol)
212
+
213
+ def test_figure_method(self):
214
+ # Tests matplotlib_figure
56
215
  # Select matplotlib backend that doesn't require a screen
57
216
  import matplotlib
58
217
  matplotlib.use('template')
218
+ w = wcp.WcpFile(os.path.join(DIR_FORMATS, 'wcp-file.wcp'))
219
+ f = w.matplotlib_figure()
220
+ self.assertIsInstance(f, matplotlib.figure.Figure)
59
221
 
60
- # Load and create plots
222
+ def test_figure_method_deprecated(self):
223
+ # Tests matplotlib_figure
224
+ # Select matplotlib backend that doesn't require a screen
225
+ import matplotlib
226
+ matplotlib.use('template')
61
227
  w = wcp.WcpFile(os.path.join(DIR_FORMATS, 'wcp-file.wcp'))
62
- w.plot()
228
+ with WarningCollector() as c:
229
+ f = w.plot()
230
+ self.assertIn('deprecated', c.text())
231
+ self.assertIsNone(f, matplotlib.figure.Figure)
63
232
 
64
233
 
65
234
  if __name__ == '__main__':
@@ -11,7 +11,7 @@ import numpy as np
11
11
  import myokit
12
12
 
13
13
  from myokit.tests.ansic_event_based_pacing import AnsicEventBasedPacing
14
- from myokit.tests.ansic_fixed_form_pacing import AnsicFixedFormPacing
14
+ from myokit.tests.ansic_time_series_pacing import AnsicTimeSeriesPacing
15
15
 
16
16
 
17
17
  class EventBasedPacingAnsicTest(unittest.TestCase):
@@ -96,12 +96,30 @@ class EventBasedPacingAnsicTest(unittest.TestCase):
96
96
  s = myokit.Simulation(m, p)
97
97
  self.assertRaises(myokit.SimultaneousProtocolEventError, s.run, 40)
98
98
 
99
+ def test_negative_time(self):
100
+ # Test starting from a negative time
99
101
 
100
- class FixedFormPacingAnsicTest(unittest.TestCase):
102
+ p = myokit.pacing.blocktrain(level=1, duration=1, period=2)
103
+ s = AnsicEventBasedPacing(p, initial_time=-100)
104
+ self.assertEqual(s.time(), -100)
105
+ self.assertEqual(s.next_time(), 0)
106
+ self.assertEqual(s.pace(), 0)
107
+
108
+ p = myokit.pacing.blocktrain(level=1, duration=1, period=2, offset=1)
109
+ s = AnsicEventBasedPacing(p, initial_time=-100)
110
+ self.assertEqual(s.time(), -100)
111
+ self.assertEqual(s.next_time(), 1)
112
+ self.assertEqual(s.pace(), 0)
113
+ s.advance(s.next_time())
114
+ self.assertEqual(s.time(), 1)
115
+ self.assertEqual(s.pace(), 1)
116
+
117
+
118
+ class TimeSeriesPacingAnsicTest(unittest.TestCase):
101
119
  """
102
- Test the Ansi-C fixed-form pacing system (aka point-list, aka data-clamp).
120
+ Test the Ansi-C time-series pacing system (aka point-list, aka data-clamp).
103
121
  """
104
- def test_fixed_form_pacing_ansic(self):
122
+ def test_time_series_pacing_ansic(self):
105
123
  # Tests if the basics work
106
124
 
107
125
  if False:
@@ -136,27 +154,37 @@ class FixedFormPacingAnsicTest(unittest.TestCase):
136
154
  sys.exit(1)
137
155
 
138
156
  # Test with small lists
139
- values = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
140
157
  times = [0, 0, 1, 1, 1, 2, 2, 2, 3, 4, 5, 7]
141
158
  values = list(range(len(times)))
142
- pacing = AnsicFixedFormPacing(myokit.TimeSeriesProtocol(times, values))
143
-
144
- def test(value, index):
145
- self.assertEqual(pacing.pace(value), index)
146
-
147
- test(-1, 0)
148
- test(0, 1)
149
- test(1, 2)
150
- test(2, 5)
151
- test(3, 8)
152
- test(4, 9)
153
- test(5, 10)
154
- test(7, 11)
155
- test(8, 11)
156
- test(1.5, 4.5)
157
- test(1.75, 4.75)
158
- test(6, 10.5)
159
- test(5.5, 10.25)
159
+ pacing = AnsicTimeSeriesPacing(
160
+ myokit.TimeSeriesProtocol(times, values))
161
+
162
+ self.assertEqual(pacing.pace(-1), 0)
163
+ self.assertEqual(pacing.pace(0), 1)
164
+ self.assertEqual(pacing.pace(1), 2)
165
+ self.assertEqual(pacing.pace(2), 5)
166
+ self.assertEqual(pacing.pace(3), 8)
167
+ self.assertEqual(pacing.pace(4), 9)
168
+ self.assertEqual(pacing.pace(5), 10)
169
+ self.assertEqual(pacing.pace(7), 11)
170
+ self.assertEqual(pacing.pace(8), 11)
171
+ self.assertEqual(pacing.pace(1.5), 4.5)
172
+ self.assertEqual(pacing.pace(1.75), 4.75)
173
+ self.assertEqual(pacing.pace(6), 10.5)
174
+ self.assertEqual(pacing.pace(5.5), 10.25)
175
+
176
+ # Test not starting at 0
177
+ times = [1, 2]
178
+ values = [10, 20]
179
+ pacing = AnsicTimeSeriesPacing(
180
+ myokit.TimeSeriesProtocol(times, values))
181
+
182
+ self.assertEqual(pacing.pace(-1), 10)
183
+ self.assertEqual(pacing.pace(0), 10)
184
+ self.assertEqual(pacing.pace(1), 10)
185
+ self.assertEqual(pacing.pace(2), 20)
186
+ self.assertEqual(pacing.pace(3), 20)
187
+ self.assertEqual(pacing.pace(1.5), 15)
160
188
 
161
189
 
162
190
  if __name__ == '__main__':
@@ -89,6 +89,24 @@ class EventBasedPacingPythonTest(unittest.TestCase):
89
89
  m = str(e.exception)
90
90
  self.assertEqual(float(m[2 + m.index('t='):-1]), 3000)
91
91
 
92
+ def test_negative_time(self):
93
+ # Test starting from a negative time
94
+
95
+ p = myokit.pacing.blocktrain(level=1, duration=1, period=2)
96
+ s = myokit.PacingSystem(p, initial_time=-100)
97
+ self.assertEqual(s.time(), -100)
98
+ self.assertEqual(s.next_time(), 0)
99
+ self.assertEqual(s.pace(), 0)
100
+
101
+ p = myokit.pacing.blocktrain(level=1, duration=1, period=2, offset=1)
102
+ s = myokit.PacingSystem(p, initial_time=-100)
103
+ self.assertEqual(s.time(), -100)
104
+ self.assertEqual(s.next_time(), 1)
105
+ self.assertEqual(s.pace(), 0)
106
+ s.advance(s.next_time())
107
+ self.assertEqual(s.time(), 1)
108
+ self.assertEqual(s.pace(), 1)
109
+
92
110
 
93
111
  if __name__ == '__main__':
94
112
  unittest.main()
@@ -53,10 +53,6 @@ class Simulation1dTest(unittest.TestCase):
53
53
  s.pre(1)
54
54
  self.assertNotEqual(s.default_state(0), x0)
55
55
 
56
- # Test running without a protocol
57
- s.set_protocol(None)
58
- s.run(1)
59
-
60
56
  # Simulation time can't be negative
61
57
  self.assertRaises(ValueError, s.run, -1)
62
58
 
@@ -121,26 +117,49 @@ class Simulation1dTest(unittest.TestCase):
121
117
  self.assertEqual(x, m.initial_values(True) * 2)
122
118
  self.assertEqual(x, s.default_state())
123
119
 
124
- def test_with_progress_reporter(self):
125
- # Test running with a progress reporter.
126
- m, p, _ = myokit.load(os.path.join(DIR_DATA, 'lr-1991.mmt'))
120
+ def test_negative_time(self):
121
+ # Test starting at a negative time
127
122
 
128
- # Test using a progress reporter
129
- s = myokit.Simulation1d(m, p, ncells=5)
130
- s.set_step_size(0.05)
131
- with myokit.tools.capture() as c:
132
- s.run(110, progress=myokit.ProgressPrinter())
133
- c = c.text().splitlines()
134
- self.assertTrue(len(c) > 0)
135
-
136
- # Not a progress reporter
137
- self.assertRaisesRegex(
138
- ValueError, 'ProgressReporter', s.run, 5, progress=12)
123
+ m = myokit.load_model(os.path.join(DIR_DATA, 'lr-1991.mmt'))
124
+ p = myokit.pacing.blocktrain(duration=1, level=1, period=2)
125
+ n = 4
126
+ s = myokit.Simulation1d(m, p, ncells=n)
127
+
128
+ s.set_time(-10)
129
+ self.assertEqual(s.time(), -10)
130
+ d = s.run(5, log=['engine.pace']).npview()
131
+ self.assertEqual(s.time(), -5)
132
+ self.assertTrue(np.all(d['engine.pace'] == 0))
133
+ d = s.run(6, log=['engine.time', 'engine.pace']).npview()
134
+ #print(d['engine.time'])
135
+ #print(d['engine.pace'])
136
+ self.assertEqual(s.time(), 1)
137
+ self.assertTrue(np.all(d['engine.pace'][:-1] == 0))
138
+ self.assertEqual(d['engine.pace'][-1], 1)
139
+
140
+ def test_no_protocol(self):
141
+ # Test running without a protocol
142
+ m = myokit.load_model(os.path.join(DIR_DATA, 'lr-1991.mmt'))
143
+ x = 0.123
144
+ m.get('engine.pace').set_rhs(x)
145
+ n = 4
139
146
 
140
- # Cancel from reporter
141
- self.assertRaises(
142
- myokit.SimulationCancelledError, s.run, 1,
143
- progress=CancellingReporter(0))
147
+ # Create without a protocol
148
+ s = myokit.Simulation1d(m, ncells=n)
149
+ d = s.run(10, log=['engine.pace']).npview()
150
+ self.assertIn('engine.pace', d.keys())
151
+ self.assertTrue(np.all(d['engine.pace'] == 0))
152
+
153
+ # Set, then unset
154
+ y = 0.101
155
+ p = myokit.pacing.blocktrain(level=y, duration=100, period=100)
156
+ s.set_protocol(p)
157
+ d = s.run(5, log=['engine.pace']).npview()
158
+ self.assertTrue(np.all(d['engine.pace'] == y))
159
+ self.assertGreater(len(d['engine.pace']), 1)
160
+ s.set_protocol(None)
161
+ d = s.run(5, log=['engine.pace']).npview()
162
+ self.assertTrue(np.all(d['engine.pace'] == 0))
144
163
 
145
164
  def test_set_state(self):
146
165
  # Test :meth:`Simulation1d.set_state()`.
@@ -218,6 +237,27 @@ class Simulation1dTest(unittest.TestCase):
218
237
  self.assertRaises(ValueError, s.default_state, -1)
219
238
  self.assertRaises(ValueError, s.default_state, n)
220
239
 
240
+ def test_with_progress_reporter(self):
241
+ # Test running with a progress reporter.
242
+ m, p, _ = myokit.load(os.path.join(DIR_DATA, 'lr-1991.mmt'))
243
+
244
+ # Test using a progress reporter
245
+ s = myokit.Simulation1d(m, p, ncells=5)
246
+ s.set_step_size(0.05)
247
+ with myokit.tools.capture() as c:
248
+ s.run(110, progress=myokit.ProgressPrinter())
249
+ c = c.text().splitlines()
250
+ self.assertTrue(len(c) > 0)
251
+
252
+ # Not a progress reporter
253
+ self.assertRaisesRegex(
254
+ ValueError, 'ProgressReporter', s.run, 5, progress=12)
255
+
256
+ # Cancel from reporter
257
+ self.assertRaises(
258
+ myokit.SimulationCancelledError, s.run, 1,
259
+ progress=CancellingReporter(0))
260
+
221
261
  def test_against_cvode(self):
222
262
  # Compare the Simulation1d output with CVODE output
223
263
 
@@ -227,16 +227,65 @@ class SimulationTest(unittest.TestCase):
227
227
  # sine value, while the solver will be interpolating y
228
228
  np.testing.assert_array_almost_equal(d[b], b_e, decimal=3)
229
229
 
230
+ def test_negative_time(self):
231
+ # Test starting at a negative time
232
+
233
+ self.sim.reset()
234
+ self.sim.set_protocol(
235
+ myokit.pacing.blocktrain(duration=1, level=1, period=2))
236
+ self.sim.set_time(-10)
237
+ self.assertEqual(self.sim.time(), -10)
238
+ d = self.sim.run(5, log=['engine.pace']).npview()
239
+ self.assertEqual(self.sim.time(), -5)
240
+ self.assertTrue(np.all(d['engine.pace'] == 0))
241
+ d = self.sim.run(5, log=['engine.pace']).npview()
242
+ self.assertEqual(self.sim.time(), 0)
243
+ self.assertTrue(np.all(d['engine.pace'][:-1] == 0))
244
+ self.assertEqual(d['engine.pace'][-1], 1)
245
+ self.sim.set_protocol(self.protocol)
246
+
230
247
  def test_no_protocol(self):
231
248
  # Test running without a protocol.
232
249
 
250
+ # Check if pace was set to zero (see technical notes).
233
251
  self.sim.reset()
234
252
  self.sim.pre(50)
235
253
  self.sim.set_protocol(None)
236
- d = self.sim.run(50).npview()
254
+ d = self.sim.run(50, log=['engine.pace']).npview()
255
+ self.assertTrue(np.all(d['engine.pace'] == 0))
237
256
 
238
- # Check if pace was set to zero (see prop 651 / technical docs).
239
- self.assertTrue(np.all(d['engine.pace'] == 0.0))
257
+ # Defined at the start? Then still counts as bound
258
+ self.assertRaisesRegex(
259
+ ValueError, 'not a literal',
260
+ self.sim.set_constant, 'engine.pace', 1)
261
+
262
+ # Check that pace is reset to zero when protocol is removed
263
+ x = 0.01 # Note: Must be low to stop sim crashing :D
264
+ self.sim.set_protocol(
265
+ myokit.pacing.blocktrain(duration=1000, level=x, period=1000))
266
+ d = self.sim.run(5, log=['engine.pace']).npview()
267
+ self.assertTrue(np.all(d['engine.pace'] == x))
268
+ self.sim.set_protocol(None)
269
+ d = self.sim.run(5, log=['engine.pace']).npview()
270
+ self.assertTrue(np.all(d['engine.pace'] == 0))
271
+ self.sim.set_protocol(self.protocol)
272
+
273
+ # No protocol set from the start? Then "pace" is set anyway for
274
+ # backwards compatibility
275
+ m = self.model.clone()
276
+ m.get('engine.pace').set_rhs(x)
277
+ s = myokit.Simulation(m)
278
+ d = s.run(50).npview()
279
+ self.assertIn('engine.pace', d.keys())
280
+ self.assertTrue(np.all(d['engine.pace'] == 0))
281
+
282
+ # But if user explicitly says no labels, then pace isn't treated in a
283
+ # special way.
284
+ s = myokit.Simulation(m, {})
285
+ self.assertRaises(ValueError, s.set_protocol, self.protocol)
286
+ d = s.run(50).npview()
287
+ self.assertNotIn('engine.pace', d.keys())
288
+ #self.assertTrue(np.all(d['engine.pace'] == x)) constant is not logged
240
289
 
241
290
  def test_wrong_label_set_pacing(self):
242
291
  # Test set_pacing with incorrect label
@@ -383,6 +383,27 @@ class FiberTissueSimulationTest(unittest.TestCase):
383
383
  ValueError, 'diffusion current must have the same unit',
384
384
  FT, mf, m2)
385
385
 
386
+ def test_negative_time(self):
387
+ # Test starting at a negative time
388
+
389
+ p = myokit.pacing.blocktrain(duration=1, level=1, period=2)
390
+ self.s0.reset()
391
+ self.s0.set_protocol(p)
392
+ self.s0.set_time(-10)
393
+ self.assertEqual(self.s0.time(), -10)
394
+ df, dt = self.s0.run(5, logf=['engine.pace'], logt=myokit.LOG_NONE)
395
+ df = df.npview()
396
+ self.assertEqual(self.s0.time(), -5)
397
+ self.assertTrue(np.all(df['engine.pace'] == 0))
398
+ df, dt = self.s0.run(
399
+ 6, logf=['engine.time', 'engine.pace'], logt=myokit.LOG_NONE)
400
+ df = df.npview()
401
+ #print(d['engine.time'])
402
+ #print(d['engine.pace'])
403
+ self.assertEqual(self.s0.time(), 1)
404
+ self.assertTrue(np.all(df['engine.pace'][:-1] == 0))
405
+ self.assertEqual(df['engine.pace'][-1], 1)
406
+
386
407
  def test_pre_reset(self):
387
408
  # Tests getting/setting of states and time, and use of pre() and run()
388
409
  try:
@@ -468,15 +489,25 @@ class FiberTissueSimulationTest(unittest.TestCase):
468
489
  self.assertGreater(df['membrane.V', 0, 0][-1], 0)
469
490
  self.s0.reset()
470
491
 
471
- # Unset protocol, check Vm stays low
492
+ # Run with constant proto
493
+ x = 0.0123
494
+ self.s0.set_protocol(
495
+ myokit.pacing.blocktrain(level=x, duration=1000, period=1000))
496
+ self.s0.reset()
497
+ df, dt = self.s0.run(3, logf=['engine.pace'])
498
+ df = df.npview()
499
+ self.assertTrue(np.all(df['engine.pace'] == x))
500
+
501
+ # Unset: Must now be zero
472
502
  self.s0.set_protocol(None)
473
- df, dt = self.s0.run(
474
- 5, logf=['membrane.V'], logt=myokit.LOG_NONE, log_interval=1)
475
- self.assertLess(df['membrane.V', 0, 0][-1], 0)
476
503
  self.s0.reset()
504
+ df, dt = self.s0.run(3, logf=['engine.pace'])
505
+ df = df.npview()
506
+ self.assertTrue(np.all(df['engine.pace'] == 0))
477
507
 
478
508
  # Reset protocol, check Vm goes high
479
509
  self.s0.set_protocol(self.p)
510
+ self.s0.reset()
480
511
  df, dt = self.s0.run(
481
512
  5, logf=['membrane.V'], logt=myokit.LOG_NONE, log_interval=1)
482
513
  self.assertGreater(df['membrane.V', 0, 0][-1], 0)
@@ -682,6 +682,24 @@ class SimulationOpenCLTest(unittest.TestCase):
682
682
  self.assertEqual(x, m.initial_values(True) * 2)
683
683
  self.assertEqual(x, s.default_state())
684
684
 
685
+ def test_negative_time(self):
686
+ # Test starting at a negative time
687
+
688
+ p = myokit.pacing.blocktrain(duration=1, level=1, period=2)
689
+ self.s0.reset()
690
+ self.s0.set_protocol(p)
691
+ self.s0.set_time(-10)
692
+ self.assertEqual(self.s0.time(), -10)
693
+ d = self.s0.run(5, log=['engine.pace']).npview()
694
+ self.assertEqual(self.s0.time(), -5)
695
+ self.assertTrue(np.all(d['engine.pace'] == 0))
696
+ d = self.s0.run(6, log=['engine.time', 'engine.pace']).npview()
697
+ #print(d['engine.time'])
698
+ #print(d['engine.pace'])
699
+ self.assertEqual(self.s0.time(), 1)
700
+ self.assertTrue(np.all(d['engine.pace'][:-1] == 0))
701
+ self.assertEqual(d['engine.pace'][-1], 1)
702
+
685
703
  def test_neighbors_0d(self):
686
704
  # Test listing neighbors in a 0d simulation
687
705
 
@@ -813,11 +831,19 @@ class SimulationOpenCLTest(unittest.TestCase):
813
831
  self.assertEqual(np.max(d0['engine.pace']), 1)
814
832
 
815
833
  try:
816
- # Unset protocol
834
+ # Make pace high
835
+ x = 0.0123
836
+ self.s0.set_protocol(
837
+ myokit.pacing.blocktrain(level=x, duration=1000, period=1000))
838
+ self.s0.reset()
839
+ d0 = self.s0.run(3, log=['engine.pace']).npview()
840
+ self.assertTrue(np.all(d0['engine.pace'] == x))
841
+
842
+ # Unset protocol: pace must now be 0
817
843
  self.s0.reset()
818
844
  self.s0.set_protocol(None)
819
845
  d0 = self.s0.run(3, log=['engine.pace']).npview()
820
- self.assertEqual(np.max(d0['engine.pace']), 0)
846
+ self.assertTrue(np.all(d0['engine.pace'] == 0))
821
847
 
822
848
  # Add new protocol
823
849
  p = myokit.pacing.blocktrain(period=1000, duration=2, offset=3)
@@ -874,8 +900,6 @@ class SimulationOpenCLTest(unittest.TestCase):
874
900
  self.assertEqual(self.s0.time(), 10)
875
901
  self.s0.run(1)
876
902
  self.assertEqual(self.s0.time(), 11)
877
- self.assertRaisesRegex(
878
- ValueError, 'negative', self.s0.set_time, -1)
879
903
 
880
904
  # Test running for a negative amount of time
881
905
  self.assertRaisesRegex(
@@ -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-2022 University of Nottingham. All rights reserved.
6
+ Copyright (c) 2020-2023 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: