myokit 1.37.0__py3-none-any.whl → 1.37.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 (35) hide show
  1. myokit/_aux.py +4 -0
  2. myokit/_datablock.py +10 -10
  3. myokit/_datalog.py +40 -5
  4. myokit/_myokit_version.py +1 -1
  5. myokit/formats/axon/_abf.py +11 -4
  6. myokit/formats/diffsl/__init__.py +60 -0
  7. myokit/formats/diffsl/_ewriter.py +145 -0
  8. myokit/formats/diffsl/_exporter.py +435 -0
  9. myokit/formats/heka/_patchmaster.py +337 -113
  10. myokit/gui/datalog_viewer.py +17 -3
  11. myokit/tests/data/io/bad1d-2-no-header.zip +0 -0
  12. myokit/tests/data/io/bad1d-3-no-data.zip +0 -0
  13. myokit/tests/data/io/bad1d-4-not-a-zip.zip +1 -105
  14. myokit/tests/data/io/bad1d-5-bad-data-type.zip +0 -0
  15. myokit/tests/data/io/bad1d-6-time-too-short.zip +0 -0
  16. myokit/tests/data/io/bad1d-7-0d-too-short.zip +0 -0
  17. myokit/tests/data/io/bad1d-8-1d-too-short.zip +0 -0
  18. myokit/tests/data/io/bad2d-2-no-header.zip +0 -0
  19. myokit/tests/data/io/bad2d-3-no-data.zip +0 -0
  20. myokit/tests/data/io/bad2d-4-not-a-zip.zip +1 -105
  21. myokit/tests/data/io/bad2d-5-bad-data-type.zip +0 -0
  22. myokit/tests/data/io/bad2d-8-2d-too-short.zip +0 -0
  23. myokit/tests/data/io/block1d.mmt +187 -0
  24. myokit/tests/data/io/datalog-18-duplicate-keys.csv +4 -0
  25. myokit/tests/test_aux.py +4 -0
  26. myokit/tests/test_datablock.py +6 -6
  27. myokit/tests/test_datalog.py +20 -0
  28. myokit/tests/test_formats_diffsl.py +728 -0
  29. myokit/tests/test_formats_exporters_run.py +3 -0
  30. {myokit-1.37.0.dist-info → myokit-1.37.1.dist-info}/METADATA +1 -1
  31. {myokit-1.37.0.dist-info → myokit-1.37.1.dist-info}/RECORD +35 -29
  32. {myokit-1.37.0.dist-info → myokit-1.37.1.dist-info}/LICENSE.txt +0 -0
  33. {myokit-1.37.0.dist-info → myokit-1.37.1.dist-info}/WHEEL +0 -0
  34. {myokit-1.37.0.dist-info → myokit-1.37.1.dist-info}/entry_points.txt +0 -0
  35. {myokit-1.37.0.dist-info → myokit-1.37.1.dist-info}/top_level.txt +0 -0
@@ -851,6 +851,10 @@ class CsvTab(GraphTabWidget):
851
851
  for k in keys:
852
852
  self.addTab(self.create_graph_tab(k, groups.get(k)), k)
853
853
 
854
+ # Add meta data tab
855
+ if log.meta:
856
+ self.addTab(self.create_meta_tab(log.meta), 'info')
857
+
854
858
  def create_graph_tab(self, key, indices=None):
855
859
  """ Creates a widget displaying the data stored under ``key``. """
856
860
  widget = QtWidgets.QWidget(self)
@@ -880,6 +884,18 @@ class CsvTab(GraphTabWidget):
880
884
  self._axes.append(axes)
881
885
  return widget
882
886
 
887
+ def create_meta_tab(self, meta):
888
+ """
889
+ Returns a tab showing information from a DataLog meta data object.
890
+ """
891
+ widget = QtWidgets.QTextEdit(self)
892
+ lines = []
893
+ for k, v in meta.items():
894
+ lines.append(f'{k}: {v}')
895
+ widget.setText('\n'.join(lines))
896
+ widget.setReadOnly(True)
897
+ return widget
898
+
883
899
 
884
900
  class MatTab(GraphTabWidget):
885
901
  """ A widget displaying a .mat file. """
@@ -997,9 +1013,7 @@ class TxtTab(GraphTabWidget):
997
1013
  return
998
1014
 
999
1015
  def create_graph_tab(self, time, data):
1000
- """
1001
- Creates a widget displaying a time series.
1002
- """
1016
+ """ Creates a widget displaying a time series. """
1003
1017
  widget = QtWidgets.QWidget(self)
1004
1018
  # Create figure
1005
1019
  figure = matplotlib.figure.Figure()
Binary file
Binary file
@@ -1,105 +1 @@
1
- [[model]]
2
- name: beeler-1977
3
- author: Michael Clerx
4
- desc: """
5
- The 1997 Beeler Reuter model of the AP in ventricular myocytes
6
-
7
- Reference:
8
-
9
- Beeler, Reuter (1976) Reconstruction of the action potential of ventricular
10
- myocardial fibres
11
- """
12
- f(x) = 4 * x
13
- # Initial values:
14
- membrane.V = -84.622
15
- calcium.Cai = 2e-7
16
- ina.m = 0.01
17
- ina.h = 0.99
18
- ina.j = 0.98
19
- isi.d = 0.003
20
- isi.f = 0.99
21
- ix1.x1 = 0.0004
22
-
23
-
24
- [engine]
25
- time = 0 in [ms] bind time
26
- pace = 0 bind pace
27
-
28
- [membrane]
29
- C = 1 [uF/cm^2] : The membrane capacitance
30
- dot(V) = -(1/C) * (i_ion + i_stim)
31
- in [mV]
32
- label membrane_potential
33
- desc: Membrane potential
34
- i_ion = ik1.IK1 + ix1.Ix1 + ina.INa + isi.Isi
35
- label cellular_current
36
- in [uA/cm^2]
37
- i_stim = engine.pace * amplitude
38
- amplitude = -25 [uA/cm^2]
39
-
40
- [ina]
41
- use membrane.V as V
42
- gNaBar = 4 [mS/cm^2]
43
- gNaC = 0.003 [mS/cm^2]
44
- ENa = 50 [mV]
45
- INa = (gNaBar * m^3 * h * j + gNaC) * (V - ENa)
46
- in [uA/cm^2]
47
- desc: The excitatory inward sodium current
48
- dot(m) = alpha * (1 - m) - beta * m
49
- alpha = (V + 47) / (1 - exp(-0.1 * (V + 47)))
50
- beta = 40 * exp(-0.056 * (V + 72))
51
- desc: The activation parameter
52
- dot(h) = alpha * (1 - h) - beta * h
53
- alpha = 0.126 * exp(-0.25 * (V + 77))
54
- beta = 1.7 / (1 + exp(-0.082 * (V + 22.5)))
55
- desc: An inactivation parameter
56
- dot(j) = alpha * (1 - j) - beta * j
57
- alpha = 0.055 * exp(-0.25 * (V + 78)) / (1 + exp(-0.2 * (V + 78)))
58
- beta = 0.3 / (1 + exp(-0.1 * (V + 32)))
59
- desc: An inactivation parameter
60
-
61
- [isi]
62
- use membrane.V as V
63
- gsBar = 0.09
64
- Es = -82.3 - 13.0287 * log(calcium.Cai)
65
- in [mV]
66
- Isi = gsBar * d * f * (V - Es)
67
- in [uA/cm^2]
68
- desc: """
69
- The slow inward current, primarily carried by calcium ions. Called either
70
- "iCa" or "is" in the paper.
71
- """
72
- dot(d) = alpha * (1 - d) - beta * d
73
- alpha = 0.095 * exp(-0.01 * (V + -5)) / (exp(-0.072 * (V + -5)) + 1)
74
- beta = 0.07 * exp(-0.017 * (V + 44)) / (exp(0.05 * (V + 44)) + 1)
75
- dot(f) = alpha * (1 - f) - beta * f
76
- alpha = 0.012 * exp(-0.008 * (V + 28)) / (exp(0.15 * (V + 28)) + 1)
77
- beta = 0.0065 * exp(-0.02 * (V + 30)) / (exp(-0.2 * (V + 30)) + 1)
78
-
79
- [calcium]
80
- dot(Cai) = -1e-7 * isi.Isi + 0.07 * (1e-7 - Cai)
81
- desc: The intracellular Calcium concentration
82
- in [mol/L]
83
-
84
- [ik1]
85
- use membrane.V as V
86
- IK1 = 0.35 * (
87
- 4 * (exp(0.04 * (V + 85)) - 1)
88
- / (exp(0.08 * (V + 53)) + exp(0.04 * (V + 53)))
89
- + 0.2 * (V + 23)
90
- / (1 - exp(-0.04 * (V + 23)))
91
- )
92
- in [uA/cm^2]
93
- desc: """A time-independent outward potassium current exhibiting
94
- inward-going rectification"""
95
-
96
- [ix1]
97
- use membrane.V as V
98
- Ix1 = x1 * 0.8 * (exp(0.04 * (V + 77)) - 1) / exp(0.04 * (V + 35))
99
- in [uA/cm^2]
100
- desc: """A voltage- and time-dependent outward current, primarily carried
101
- by potassium ions"""
102
- dot(x1) = alpha * (1 - x1) - beta * x1
103
- alpha = 0.0005 * exp(0.083 * (V + 50)) / (exp(0.057 * (V + 50)) + 1)
104
- beta = 0.0013 * exp(-0.06 * (V + 20)) / (exp(-0.04 * (V + 333)) + 1)
105
-
1
+ ;
Binary file
Binary file
@@ -1,105 +1 @@
1
- [[model]]
2
- name: beeler-1977
3
- author: Michael Clerx
4
- desc: """
5
- The 1997 Beeler Reuter model of the AP in ventricular myocytes
6
-
7
- Reference:
8
-
9
- Beeler, Reuter (1976) Reconstruction of the action potential of ventricular
10
- myocardial fibres
11
- """
12
- f(x) = 4 * x
13
- # Initial values:
14
- membrane.V = -84.622
15
- calcium.Cai = 2e-7
16
- ina.m = 0.01
17
- ina.h = 0.99
18
- ina.j = 0.98
19
- isi.d = 0.003
20
- isi.f = 0.99
21
- ix1.x1 = 0.0004
22
-
23
-
24
- [engine]
25
- time = 0 in [ms] bind time
26
- pace = 0 bind pace
27
-
28
- [membrane]
29
- C = 1 [uF/cm^2] : The membrane capacitance
30
- dot(V) = -(1/C) * (i_ion + i_stim)
31
- in [mV]
32
- label membrane_potential
33
- desc: Membrane potential
34
- i_ion = ik1.IK1 + ix1.Ix1 + ina.INa + isi.Isi
35
- label cellular_current
36
- in [uA/cm^2]
37
- i_stim = engine.pace * amplitude
38
- amplitude = -25 [uA/cm^2]
39
-
40
- [ina]
41
- use membrane.V as V
42
- gNaBar = 4 [mS/cm^2]
43
- gNaC = 0.003 [mS/cm^2]
44
- ENa = 50 [mV]
45
- INa = (gNaBar * m^3 * h * j + gNaC) * (V - ENa)
46
- in [uA/cm^2]
47
- desc: The excitatory inward sodium current
48
- dot(m) = alpha * (1 - m) - beta * m
49
- alpha = (V + 47) / (1 - exp(-0.1 * (V + 47)))
50
- beta = 40 * exp(-0.056 * (V + 72))
51
- desc: The activation parameter
52
- dot(h) = alpha * (1 - h) - beta * h
53
- alpha = 0.126 * exp(-0.25 * (V + 77))
54
- beta = 1.7 / (1 + exp(-0.082 * (V + 22.5)))
55
- desc: An inactivation parameter
56
- dot(j) = alpha * (1 - j) - beta * j
57
- alpha = 0.055 * exp(-0.25 * (V + 78)) / (1 + exp(-0.2 * (V + 78)))
58
- beta = 0.3 / (1 + exp(-0.1 * (V + 32)))
59
- desc: An inactivation parameter
60
-
61
- [isi]
62
- use membrane.V as V
63
- gsBar = 0.09
64
- Es = -82.3 - 13.0287 * log(calcium.Cai)
65
- in [mV]
66
- Isi = gsBar * d * f * (V - Es)
67
- in [uA/cm^2]
68
- desc: """
69
- The slow inward current, primarily carried by calcium ions. Called either
70
- "iCa" or "is" in the paper.
71
- """
72
- dot(d) = alpha * (1 - d) - beta * d
73
- alpha = 0.095 * exp(-0.01 * (V + -5)) / (exp(-0.072 * (V + -5)) + 1)
74
- beta = 0.07 * exp(-0.017 * (V + 44)) / (exp(0.05 * (V + 44)) + 1)
75
- dot(f) = alpha * (1 - f) - beta * f
76
- alpha = 0.012 * exp(-0.008 * (V + 28)) / (exp(0.15 * (V + 28)) + 1)
77
- beta = 0.0065 * exp(-0.02 * (V + 30)) / (exp(-0.2 * (V + 30)) + 1)
78
-
79
- [calcium]
80
- dot(Cai) = -1e-7 * isi.Isi + 0.07 * (1e-7 - Cai)
81
- desc: The intracellular Calcium concentration
82
- in [mol/L]
83
-
84
- [ik1]
85
- use membrane.V as V
86
- IK1 = 0.35 * (
87
- 4 * (exp(0.04 * (V + 85)) - 1)
88
- / (exp(0.08 * (V + 53)) + exp(0.04 * (V + 53)))
89
- + 0.2 * (V + 23)
90
- / (1 - exp(-0.04 * (V + 23)))
91
- )
92
- in [uA/cm^2]
93
- desc: """A time-independent outward potassium current exhibiting
94
- inward-going rectification"""
95
-
96
- [ix1]
97
- use membrane.V as V
98
- Ix1 = x1 * 0.8 * (exp(0.04 * (V + 77)) - 1) / exp(0.04 * (V + 35))
99
- in [uA/cm^2]
100
- desc: """A voltage- and time-dependent outward current, primarily carried
101
- by potassium ions"""
102
- dot(x1) = alpha * (1 - x1) - beta * x1
103
- alpha = 0.0005 * exp(0.083 * (V + 50)) / (exp(0.057 * (V + 50)) + 1)
104
- beta = 0.0013 * exp(-0.06 * (V + 20)) / (exp(-0.04 * (V + 333)) + 1)
105
-
1
+ ;
@@ -0,0 +1,187 @@
1
+ [[model]]
2
+ author: Michael Clerx
3
+ desc: """
4
+ Implementation of the Luo-Rudy model I for the ventricular myocyte.
5
+ Based on an updated formulation downloaded from http://rudylab.wustl.edu
6
+
7
+ Original copyright notice:
8
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
9
+ Copyright (C) 2006 by Leonid Livshitz and Yoram Rudy
10
+ Email rudy@wustl.edu
11
+
12
+ This program is free software; you can redistribute it and/or modify
13
+ it under the terms of the GNU General Public License as published by
14
+ the Free Software Foundation; either version 2 of the License, or
15
+ (at your option) any later version.
16
+
17
+ This program is distributed in the hope that it will be useful,
18
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
19
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20
+ GNU General Public License for more details.
21
+
22
+ You should have received a copy of the GNU General Public License
23
+ along with this program; if not, write to the
24
+ Free Software Foundation, Inc.,
25
+ 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
26
+ """
27
+ name: Luo-Rudy model (1991)
28
+ # Initial conditions
29
+ membrane.V = -84.5286
30
+ ina.m = 0.0017
31
+ ina.h = 0.9832
32
+ ina.j = 0.995484
33
+ ica.d = 0.000003
34
+ ica.f = 1
35
+ ik.x = 0.0057
36
+ ica.Ca_i = 0.0002
37
+
38
+ # Engine variables
39
+ [engine]
40
+ time = 0 bind time
41
+ pace = 1 bind pace # Set to 1 to test if it becomes 0 without a protocol
42
+
43
+ # Membrane potential and stimulus
44
+ [membrane]
45
+ dot(V) = -(i_ion + i_stim + i_diff)
46
+ label membrane_potential
47
+ in [mV]
48
+ desc: The membrane potential
49
+ i_ion = ina.INa + ik.IK + ib.Ib + ikp.IKp + ik1.IK1 + ica.ICa
50
+ i_stim = engine.pace * stim_amplitude
51
+ stim_amplitude = -80 [uA/cm^2]
52
+ desc: """
53
+ Stimulus amplitude.
54
+ The online matlab implementation features this 80 uA/cm^2, 0.5 millisecond
55
+ pulse. The cellml version uses a 2 millisecond 25.5 uA/cm^2 pulse.
56
+ """
57
+ i_diff = 0 bind diffusion_current
58
+ in [uA/cm^2]
59
+
60
+ # Fast Sodium current
61
+ [ina]
62
+ use membrane.V as V
63
+ ENa = cell.RTF * log(cell.Na_o / cell.Na_i)
64
+ desc: Nernst potential of Na
65
+ in [mV]
66
+ a = 1 - 1 / (1 + exp(-(V + 40) / 0.24))
67
+ desc: Used instead of if statement.
68
+ dot(m) = alpha * (1 - m) - beta * m
69
+ alpha = if(V == -47.13, -3.2,
70
+ 0.32 * (V + 47.13) / (1 - exp(-0.1 * (V + 47.13))))
71
+ beta = 0.08 * exp(-V / 11)
72
+ desc: """
73
+ Activation parameter of the fast sodium channel, based on Ebihara-Johnson
74
+ (E-J) model of cardiac cells (chicken embryo)
75
+ """
76
+ dot(h) = alpha * (1 - h) - beta * h
77
+ alpha = a * 0.135 * exp((80 + V) / -6.8)
78
+ beta = a * (3.56 * exp(0.079 * V) + 3.1e5 * exp(0.35 * V)) \
79
+ + (1 - a) / (0.13 * (1 + exp((V + 10.66) / -11.1)))
80
+ desc: """
81
+ (Fast) Inactivation parameter of the fast sodium channel, based on
82
+ Ebihara-Johnson (E-J) model of cardiac cells (chicken embryo)
83
+ """
84
+ dot(j) = alpha * (1 - j) - beta * j
85
+ alpha = a * ( -127140 * exp(0.2444 * V) - 3.474e-5 * exp(-0.04391 * V)) \
86
+ * (V + 37.78) / (1 + exp(0.311 * (V + 79.23)))
87
+ beta = a * (0.1212 * exp(-0.01052 * V) / (1 + exp(-0.1378 * (V + 40.14)))) \
88
+ + (1 - a) * (0.3 * exp(-2.535e-7 * V) / (1 + exp(-0.1 * (V + 32))))
89
+ desc: """
90
+ Slow inactivation gate. Inspired by Beeler-Reuter and Haas. Steady-state
91
+ value (j_inf) is set to value from h gate (h_inf). Time constant obtained
92
+ from Beeler-Reuter model.
93
+ """
94
+ gNa = 16
95
+ INa = gNa * m^3 * h * j * (V - ENa)
96
+ desc: Fast sodium current
97
+ in [uA/cm^2]
98
+
99
+ # Time-dependent Potassium current
100
+ [ik]
101
+ use membrane.V as V
102
+ PNa_K = 0.01833 : Permability ratio of Na to K
103
+ gK = 0.282 * sqrt(cell.K_o / 5.4)
104
+ in [mS/uF]
105
+ E = cell.RTF * log((cell.K_o + PNa_K * cell.Na_o) / (cell.K_i + PNa_K * cell.Na_i))
106
+ in [mV]
107
+ xi = if(V < -100,
108
+ 1,
109
+ if(V == -77,
110
+ 2.837 * 0.04 / exp(0.04 * (V + 35)),
111
+ 2.837 * (exp(0.04 * (V + 77)) - 1) / ((V + 77) * exp(0.04 * (V + 35)))
112
+ ))
113
+ dot(x) = alpha * (1 - x) - beta * x
114
+ alpha = 0.0005 * exp(0.083 * (V + 50)) / (1 + exp(0.057 * (V + 50)))
115
+ beta = 0.0013 * exp(-0.06 * (V + 20)) / (1 + exp(-0.04 * (V + 20)))
116
+ IK = gK * xi * x * (V - E)
117
+ desc: Time-dependent Potassium current
118
+
119
+ # Plateau Potassium current
120
+ [ikp]
121
+ use membrane.V as V
122
+ gKp = 0.0183
123
+ IKp = gKp * (V + 87.8789) / (1 + exp((7.488 - V) / 5.98))
124
+ desc: Plateau Potassium current
125
+
126
+ # Slow inward Calcium current
127
+ [ica]
128
+ use membrane.V as V
129
+ dot(Ca_i) = -1e-4 * ICa + 0.07 * (1e-4 - Ca_i)
130
+ desc: Intracellular Calcium concentration
131
+ E = 7.7 - 13.0287 * log(Ca_i / cell.Ca_o)
132
+ desc: Nernst potential
133
+ in [mV]
134
+ dot(d) = alpha * (1 - d) - beta * d
135
+ alpha = 0.095 * exp(-0.01 * (V - 5)) / (1 + exp(-0.072 * (V - 5)))
136
+ beta = 0.07 * exp(-0.017 * (V + 44)) / (1 + exp(0.05 * (V + 44)))
137
+ dot(f) = alpha * (1-f) - beta * f
138
+ alpha = 0.012 * exp(-0.008 * (V + 28)) / (1 + exp(0.15 * (V + 28)))
139
+ beta = 0.0065 * exp(-0.02 * (V + 30)) / (1 + exp(-0.2 * (V + 30)))
140
+ gCa = 0.09
141
+ ICa = gCa * d * f * (V - E)
142
+
143
+ # Time-independent potassium current
144
+ [ik1]
145
+ use membrane.V as V
146
+ E = cell.RTF * log(cell.K_o / cell.K_i)
147
+ gK1 = 0.6047 * sqrt(cell.K_o / 5.4) * alpha / (alpha + beta)
148
+ alpha = 1.02 / (1 + exp(0.2385 * (V - E - 59.215)))
149
+ beta = (0.49124 * exp(0.08032 * (V - E + 5.476)) + exp(0.06175 * (V - E - 594.31))) \
150
+ / (1 + exp(-0.5143 * (V - E + 4.753)))
151
+ IK1 = gK1 * (V - E)
152
+
153
+ # Background current
154
+ [ib]
155
+ gb = 0.03921
156
+ Ib = gb * (membrane.V + 59.87) : Background current
157
+
158
+ # Cell parameters (ventricular mammalian cell)
159
+ [cell]
160
+ K_o = 5.4 [mM]
161
+ K_i = 145 [mM]
162
+ Na_o = 140 [mM]
163
+ Na_i = 10 [mM]
164
+ Ca_o = 1.8 [mM]
165
+ RTF = R*T/F
166
+ R = 8.314 [J/K] : Gas constant
167
+ T = 273 + 37 [K] : Absolute temperature
168
+ F = 96.5 [C/mol] : Faraday constant
169
+
170
+ [[protocol]]
171
+ # Level Start Length Period Multiplier
172
+ 1.0 1 0.5 1000 0
173
+
174
+ [[script]]
175
+ import matplotlib.pyplot as pl
176
+ import myokit
177
+
178
+ # Create simulation
179
+ m = get_model()
180
+ p = get_protocol()
181
+ s = myokit.SimulationOpenCL(m, p, 16)
182
+
183
+ # Run
184
+ d = s.run(5, log=['engine.time', 'membrane.V', 'engine.pace'])
185
+ b = d.block1d()
186
+ b.save('block1d.zip')
187
+
@@ -0,0 +1,4 @@
1
+ "time","x","x","ys-2","x","ys-2","x-2"
2
+ 1, 0, 2, 4, 6, 8, 5
3
+ 2, 1, 3, 5, 7, 9, 4
4
+
myokit/tests/test_aux.py CHANGED
@@ -526,6 +526,10 @@ class AuxTest(unittest.TestCase):
526
526
  self.assertEqual(a, b)
527
527
  self.assertEqual(len(x), len(y))
528
528
 
529
+ # No issues when passing initial state from a model
530
+ # See https://github.com/myokit/myokit/pull/1083
531
+ myokit.step(m1, reference=m2, initial=m1.initial_values())
532
+
529
533
  def test_strfloat(self):
530
534
  # Deprecated alias of myokit.float.str
531
535
  args = ['-1.234', True, myokit.SINGLE_PRECISION]
@@ -397,19 +397,19 @@ class DataBlock1dTest(unittest.TestCase):
397
397
  # Not enough data: detected at time level
398
398
  path = os.path.join(DIR_IO, 'bad1d-6-time-too-short.zip')
399
399
  self.assertRaisesRegex(
400
- myokit.DataBlockReadError, 'larger data',
400
+ myokit.DataBlockReadError, 'more time data',
401
401
  myokit.DataBlock1d.load, path)
402
402
 
403
403
  # Not enoug data: detected at 0d level
404
404
  path = os.path.join(DIR_IO, 'bad1d-7-0d-too-short.zip')
405
405
  self.assertRaisesRegex(
406
- myokit.DataBlockReadError, 'larger data',
406
+ myokit.DataBlockReadError, 'more 0d data',
407
407
  myokit.DataBlock1d.load, path)
408
408
 
409
409
  # Not enough data: detected at 1d level
410
410
  path = os.path.join(DIR_IO, 'bad1d-8-1d-too-short.zip')
411
411
  self.assertRaisesRegex(
412
- myokit.DataBlockReadError, 'larger data',
412
+ myokit.DataBlockReadError, 'more 1d data',
413
413
  myokit.DataBlock1d.load, path)
414
414
 
415
415
  # Test progress reporter
@@ -1324,19 +1324,19 @@ class DataBlock2dTest(unittest.TestCase):
1324
1324
  # Unknown data type in data
1325
1325
  path = os.path.join(DIR_IO, 'bad2d-6-time-too-short.zip')
1326
1326
  self.assertRaisesRegex(
1327
- myokit.DataBlockReadError, 'larger data',
1327
+ myokit.DataBlockReadError, 'more time data',
1328
1328
  myokit.DataBlock2d.load, path)
1329
1329
 
1330
1330
  # Unknown data type in data
1331
1331
  path = os.path.join(DIR_IO, 'bad2d-7-0d-too-short.zip')
1332
1332
  self.assertRaisesRegex(
1333
- myokit.DataBlockReadError, 'larger data',
1333
+ myokit.DataBlockReadError, 'more 0d data',
1334
1334
  myokit.DataBlock2d.load, path)
1335
1335
 
1336
1336
  # Unknown data type in data
1337
1337
  path = os.path.join(DIR_IO, 'bad2d-8-2d-too-short.zip')
1338
1338
  self.assertRaisesRegex(
1339
- myokit.DataBlockReadError, 'larger data',
1339
+ myokit.DataBlockReadError, 'more 2d data',
1340
1340
  myokit.DataBlock2d.load, path)
1341
1341
 
1342
1342
  # Test progress reporter
@@ -683,6 +683,26 @@ class DataLogTest(unittest.TestCase):
683
683
  d = myokit.DataLog.load(path, progress=p)
684
684
  self.assertIsNone(d)
685
685
 
686
+ def test_load_csv_duplicate_keys(self):
687
+ # Test loading a CSV with duplicate key names
688
+
689
+ path = os.path.join(DIR_IO, 'datalog-18-duplicate-keys.csv')
690
+ d = myokit.DataLog.load_csv(path)
691
+ self.assertEqual(len(d), 7)
692
+ self.assertEqual(
693
+ list(d.keys()),
694
+ ['time', 'x-1', 'x-3', 'ys-2-1', 'x-4', 'ys-2-2', 'x-2'])
695
+
696
+ def test_load_csv_ufeff(self):
697
+ # Test ignoring byte order marker (BOM) at start of file
698
+
699
+ with TemporaryDirectory() as td:
700
+ path = td.path('test.csv')
701
+ with open(path, 'w', encoding='utf-8-sig') as f:
702
+ f.write('time,x\n0,2\n1,3\n')
703
+ d = myokit.DataLog.load_csv(path)
704
+ self.assertEqual(list(d.keys()), ['time', 'x'])
705
+
686
706
  def test_load_csv_errors(self):
687
707
  # Test for errors during csv loading.
688
708