myokit 1.36.1__py3-none-any.whl → 1.37.0__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 +6 -19
- myokit/_datablock.py +45 -55
- myokit/_datalog.py +2 -2
- myokit/_err.py +26 -3
- myokit/_expressions.py +241 -127
- myokit/_model_api.py +19 -13
- myokit/_myokit_version.py +1 -1
- myokit/_sim/jacobian.py +3 -3
- myokit/_sim/openclsim.py +5 -5
- myokit/_sim/rhs.py +1 -1
- myokit/formats/__init__.py +4 -9
- myokit/formats/ansic/_ewriter.py +4 -20
- myokit/formats/heka/_patchmaster.py +16 -10
- myokit/formats/opencl/_ewriter.py +3 -42
- myokit/formats/opencl/template/minilog.py +1 -1
- myokit/formats/sympy/_ereader.py +2 -1
- myokit/formats/wcp/_wcp.py +3 -3
- myokit/gui/datalog_viewer.py +12 -7
- myokit/lib/markov.py +2 -2
- myokit/lib/plots.py +4 -4
- myokit/tests/data/formats/wcp-file-empty.wcp +0 -0
- myokit/tests/test_datablock.py +10 -10
- myokit/tests/test_datalog.py +4 -1
- myokit/tests/test_expressions.py +532 -251
- myokit/tests/test_formats_ansic.py +6 -18
- myokit/tests/test_formats_cpp.py +0 -5
- myokit/tests/test_formats_cuda.py +7 -15
- myokit/tests/test_formats_easyml.py +4 -9
- myokit/tests/test_formats_latex.py +10 -11
- myokit/tests/test_formats_matlab.py +0 -8
- myokit/tests/test_formats_opencl.py +0 -29
- myokit/tests/test_formats_python.py +2 -19
- myokit/tests/test_formats_stan.py +0 -13
- myokit/tests/test_formats_sympy.py +3 -3
- myokit/tests/test_formats_wcp.py +15 -0
- myokit/tests/test_model.py +20 -20
- myokit/tests/test_parsing.py +19 -0
- {myokit-1.36.1.dist-info → myokit-1.37.0.dist-info}/METADATA +1 -1
- {myokit-1.36.1.dist-info → myokit-1.37.0.dist-info}/RECORD +43 -42
- {myokit-1.36.1.dist-info → myokit-1.37.0.dist-info}/LICENSE.txt +0 -0
- {myokit-1.36.1.dist-info → myokit-1.37.0.dist-info}/WHEEL +0 -0
- {myokit-1.36.1.dist-info → myokit-1.37.0.dist-info}/entry_points.txt +0 -0
- {myokit-1.36.1.dist-info → myokit-1.37.0.dist-info}/top_level.txt +0 -0
myokit/_model_api.py
CHANGED
|
@@ -716,7 +716,10 @@ class VarOwner(ModelPart, VarProvider):
|
|
|
716
716
|
If ``recursive`` is ``True``, any child variables will be deleted as
|
|
717
717
|
well.
|
|
718
718
|
|
|
719
|
-
A :class:`myokit.IntegrityError` will be raised if
|
|
719
|
+
A :class:`myokit.IntegrityError` will be raised if the variable cannot
|
|
720
|
+
be removed because other variables depend on it. (Although dependencies
|
|
721
|
+
from child variables will be ignored if ``recursive`` is set to
|
|
722
|
+
``True``).
|
|
720
723
|
"""
|
|
721
724
|
if variable.parent() != self:
|
|
722
725
|
raise ValueError(
|
|
@@ -1095,9 +1098,7 @@ class Model(ObjectWithMetaData, VarProvider):
|
|
|
1095
1098
|
raise myokit.IncompatibleUnitError(msg, var._token)
|
|
1096
1099
|
|
|
1097
1100
|
def clone(self):
|
|
1098
|
-
"""
|
|
1099
|
-
Returns a (deep) clone of this model.
|
|
1100
|
-
"""
|
|
1101
|
+
""" Returns a (deep) clone of this model. """
|
|
1101
1102
|
clone = Model()
|
|
1102
1103
|
|
|
1103
1104
|
# Copy meta data
|
|
@@ -4520,16 +4521,10 @@ class Variable(VarOwner):
|
|
|
4520
4521
|
warnings.warn('The keyword argument `state_value` is deprecated.'
|
|
4521
4522
|
' Please use `initial_value` instead.')
|
|
4522
4523
|
|
|
4523
|
-
#
|
|
4524
|
-
|
|
4525
|
-
if not isinstance(initial_value, myokit.Expression):
|
|
4526
|
-
if isinstance(initial_value, str):
|
|
4527
|
-
# Expressions are evaluated in model context
|
|
4528
|
-
initial_value = myokit.parse_expression(
|
|
4529
|
-
initial_value, context=model)
|
|
4530
|
-
elif initial_value is not None:
|
|
4531
|
-
initial_value = myokit.Number(initial_value)
|
|
4524
|
+
# Parse initial value
|
|
4525
|
+
initial_value = self._set_initial_value(initial_value, False)
|
|
4532
4526
|
|
|
4527
|
+
model = self.model()
|
|
4533
4528
|
try:
|
|
4534
4529
|
# Set lhs to derivative expression
|
|
4535
4530
|
self._lhs = myokit.Derivative(myokit.Name(self))
|
|
@@ -4854,6 +4849,9 @@ class Variable(VarOwner):
|
|
|
4854
4849
|
x.set_rhs(myokit.Plus(myokit.Number(1), myokit.Name(y)))
|
|
4855
4850
|
x.set_rhs('1 + y')
|
|
4856
4851
|
|
|
4852
|
+
Expressions used as a variable's right-hand side must be numerical:
|
|
4853
|
+
:class:`myokit.Condition` operators can not be used as RHS.
|
|
4854
|
+
|
|
4857
4855
|
Calling `set_rhs` will reset the validation status of the model this
|
|
4858
4856
|
variable belongs to.
|
|
4859
4857
|
"""
|
|
@@ -5107,6 +5105,14 @@ class Equation:
|
|
|
5107
5105
|
def __init__(self, lhs, rhs):
|
|
5108
5106
|
self._lhs = lhs
|
|
5109
5107
|
self._rhs = rhs
|
|
5108
|
+
if not isinstance(lhs, myokit.Expression):
|
|
5109
|
+
raise myokit.IntegrityError(
|
|
5110
|
+
'Both sides of an equation must be myokit.Expression objects.'
|
|
5111
|
+
f' Found {type(lhs)} for LHS.')
|
|
5112
|
+
if not isinstance(rhs, myokit.Expression):
|
|
5113
|
+
raise myokit.IntegrityError(
|
|
5114
|
+
'Both sides of an equation must be myokit.Expression objects.'
|
|
5115
|
+
f' Found {type(lhs)} for RHS.')
|
|
5110
5116
|
|
|
5111
5117
|
def __eq__(self, other):
|
|
5112
5118
|
if not isinstance(other, Equation):
|
myokit/_myokit_version.py
CHANGED
|
@@ -14,7 +14,7 @@ __release__ = True
|
|
|
14
14
|
# incompatibility
|
|
15
15
|
# - Changes to revision indicate bugfixes, tiny new features
|
|
16
16
|
# - There is no significance to odd/even numbers
|
|
17
|
-
__version_tuple__ = 1,
|
|
17
|
+
__version_tuple__ = 1, 37, 0
|
|
18
18
|
|
|
19
19
|
# String version of the version number
|
|
20
20
|
__version__ = '.'.join([str(x) for x in __version_tuple__])
|
myokit/_sim/jacobian.py
CHANGED
|
@@ -166,7 +166,7 @@ class JacobianTracer(myokit.CppModule):
|
|
|
166
166
|
self._ext.calculate(state, bound, deriv, partial)
|
|
167
167
|
# Discard derivatives
|
|
168
168
|
# Convert partial derivatives to numpy array and store
|
|
169
|
-
partial = np.
|
|
169
|
+
partial = np.asarray(partial)
|
|
170
170
|
partial = partial.reshape((ns, ns))
|
|
171
171
|
partials.append(partial)
|
|
172
172
|
partials = np.array(partials)
|
|
@@ -282,8 +282,8 @@ class JacobianCalculator(myokit.CppModule):
|
|
|
282
282
|
self._ext.calculate(state, inputs, deriv, partial)
|
|
283
283
|
|
|
284
284
|
# Create numpy versions and return
|
|
285
|
-
deriv = np.
|
|
286
|
-
partial = np.
|
|
285
|
+
deriv = np.asarray(deriv)
|
|
286
|
+
partial = np.asarray(partial).reshape((n, n))
|
|
287
287
|
return deriv, partial
|
|
288
288
|
|
|
289
289
|
def newton_root(self, x=None, accuracy=0, max_iter=50, damping=1):
|
myokit/_sim/openclsim.py
CHANGED
|
@@ -534,7 +534,7 @@ class SimulationOpenCL(myokit.CModule):
|
|
|
534
534
|
lower, upper = safe_range
|
|
535
535
|
for dims in myokit._dimco(*self._dims):
|
|
536
536
|
key = '.'.join([str(x) for x in dims]) + post
|
|
537
|
-
ar = np.
|
|
537
|
+
ar = np.asarray(_log[key])
|
|
538
538
|
i = np.where(
|
|
539
539
|
(ar < lower)
|
|
540
540
|
| (ar > upper)
|
|
@@ -1080,7 +1080,7 @@ class SimulationOpenCL(myokit.CModule):
|
|
|
1080
1080
|
n = len(self._fields) * self._nx * self._ny
|
|
1081
1081
|
if n:
|
|
1082
1082
|
field_data = self._fields.values()
|
|
1083
|
-
field_data = [np.
|
|
1083
|
+
field_data = [np.asarray(x) for x in field_data]
|
|
1084
1084
|
field_data = np.vstack(field_data)
|
|
1085
1085
|
field_data = list(field_data.reshape(n, order='F'))
|
|
1086
1086
|
else:
|
|
@@ -1342,7 +1342,7 @@ class SimulationOpenCL(myokit.CModule):
|
|
|
1342
1342
|
'This method is unavailable when diffusion is disabled.')
|
|
1343
1343
|
|
|
1344
1344
|
# Check the field's size
|
|
1345
|
-
gx = np.
|
|
1345
|
+
gx = np.asarray(gx, dtype=float)
|
|
1346
1346
|
if len(self._dims) == 1:
|
|
1347
1347
|
s = self._nx - 1
|
|
1348
1348
|
if gx.shape != (s, ):
|
|
@@ -1360,7 +1360,7 @@ class SimulationOpenCL(myokit.CModule):
|
|
|
1360
1360
|
if gy is None:
|
|
1361
1361
|
raise ValueError(
|
|
1362
1362
|
'The argument `gy` must be set for 2-d simulations.')
|
|
1363
|
-
gy = np.
|
|
1363
|
+
gy = np.asarray(gy, dtype=float)
|
|
1364
1364
|
s = (self._ny - 1, self._nx)
|
|
1365
1365
|
if gy.shape != s:
|
|
1366
1366
|
raise ValueError(
|
|
@@ -1514,7 +1514,7 @@ class SimulationOpenCL(myokit.CModule):
|
|
|
1514
1514
|
if not var.is_constant():
|
|
1515
1515
|
raise ValueError('Only constants can be used for fields.')
|
|
1516
1516
|
# Check values
|
|
1517
|
-
values = np.
|
|
1517
|
+
values = np.asarray(values, dtype=float)
|
|
1518
1518
|
if len(self._dims) == 1:
|
|
1519
1519
|
if values.shape != (self._nx, ):
|
|
1520
1520
|
raise ValueError(
|
myokit/_sim/rhs.py
CHANGED
myokit/formats/__init__.py
CHANGED
|
@@ -128,15 +128,7 @@ class ExpressionWriter:
|
|
|
128
128
|
``a**b**c`` is interpreted as ``a**(b**c)``, necessitating a different
|
|
129
129
|
bracket-adding logic than used in Myokit.
|
|
130
130
|
|
|
131
|
-
3.
|
|
132
|
-
Myokit, ``0 == 0 == 0`` is a sequence of two binary operators,
|
|
133
|
-
interpreted as ``(0 == 0) == 0``. Because ``(0 == 0)`` evaluates to
|
|
134
|
-
``1``, this expression returns ``0`` (1 does not equal 0). In Python,
|
|
135
|
-
the expression ``0 == 0 == 0`` is a ternary (n-ary) operator,
|
|
136
|
-
interpreted as ``all_equal(0, 0, 0)``, which evaluates to ``1``. For
|
|
137
|
-
languages that use this convention, extra brackets must be added.
|
|
138
|
-
|
|
139
|
-
4. Myokit does not have increment or decrement operators ``--`` and ``++``,
|
|
131
|
+
3. Myokit does not have increment or decrement operators ``--`` and ``++``,
|
|
140
132
|
so the expression ``--x`` is interpreted as ``-(-x)``. This is the same
|
|
141
133
|
in Python. But in C-based languages, this is interpreted as a decrement
|
|
142
134
|
operator so care must be taken to add extra brackets.
|
|
@@ -886,6 +878,9 @@ class SweepSource:
|
|
|
886
878
|
|
|
887
879
|
Note that a source with zero recorded channels may still report a
|
|
888
880
|
non-zero number of sweeps if it can provide D/A outputs.
|
|
881
|
+
|
|
882
|
+
Similarly, formats like WCP can report zero sweeps but have a non-zero
|
|
883
|
+
channel count (if no data was recorded).
|
|
889
884
|
"""
|
|
890
885
|
raise NotImplementedError
|
|
891
886
|
|
myokit/formats/ansic/_ewriter.py
CHANGED
|
@@ -102,23 +102,14 @@ class CBasedExpressionWriter(PythonExpressionWriter):
|
|
|
102
102
|
|
|
103
103
|
def _ex_not(self, e):
|
|
104
104
|
# C conditions all have brackets, so don't add more
|
|
105
|
-
|
|
106
|
-
return f'(!{self.ex(e[0])})'
|
|
107
|
-
# But do add more if the user's being silly
|
|
108
|
-
return f'(!({self.ex(e[0])}))'
|
|
105
|
+
return f'(!{self.ex(e[0])})'
|
|
109
106
|
|
|
110
107
|
def _ex_if(self, e):
|
|
111
|
-
|
|
112
|
-
# If i is not a condtion (which always gets brackets from this writer)
|
|
113
|
-
# then add brackets
|
|
114
|
-
if not isinstance(e._i, myokit.Condition):
|
|
115
|
-
_if = f'({_if})'
|
|
116
|
-
return f'({_if} ? {_then} : {_else})'
|
|
108
|
+
return f'({self.ex(e._i)} ? {self.ex(e._t)} : {self.ex(e._e)})'
|
|
117
109
|
|
|
118
110
|
def _ex_piecewise(self, e):
|
|
119
111
|
# Render ifs; add extra bracket if not a condition (see _ex_if)
|
|
120
|
-
_ifs = [self.ex(x)
|
|
121
|
-
else f'({self.ex(x)})' for x in e._i]
|
|
112
|
+
_ifs = [self.ex(x) for x in e._i]
|
|
122
113
|
_thens = [self.ex(x) for x in e._e]
|
|
123
114
|
|
|
124
115
|
s = []
|
|
@@ -200,13 +191,7 @@ class AnsiCExpressionWriter(CBasedExpressionWriter):
|
|
|
200
191
|
#def _ex_not(self, e):
|
|
201
192
|
|
|
202
193
|
def _ex_if(self, e):
|
|
203
|
-
# Allow _fcond
|
|
204
|
-
|
|
205
194
|
_if, _then, _else = self.ex(e._i), self.ex(e._t), self.ex(e._e)
|
|
206
|
-
# If i is not a condtion (which always gets brackets from this writer)
|
|
207
|
-
# then add brackets
|
|
208
|
-
if not isinstance(e._i, myokit.Condition):
|
|
209
|
-
_if = f'({_if})'
|
|
210
195
|
|
|
211
196
|
# Use if-then-else function?
|
|
212
197
|
if self._fcond is not None:
|
|
@@ -219,8 +204,7 @@ class AnsiCExpressionWriter(CBasedExpressionWriter):
|
|
|
219
204
|
# Allow _fcond
|
|
220
205
|
|
|
221
206
|
# Render ifs; add extra bracket if not a condition (see _ex_if)
|
|
222
|
-
_ifs = [self.ex(x)
|
|
223
|
-
else f'({self.ex(x)})' for x in e._i]
|
|
207
|
+
_ifs = [self.ex(x) for x in e._i]
|
|
224
208
|
_thens = [self.ex(x) for x in e._e]
|
|
225
209
|
|
|
226
210
|
s = []
|
|
@@ -928,6 +928,7 @@ class Series(TreeNode, myokit.formats.SweepSource):
|
|
|
928
928
|
include_da = False
|
|
929
929
|
|
|
930
930
|
# Populate log
|
|
931
|
+
log.set_time_key('time')
|
|
931
932
|
if join_sweeps:
|
|
932
933
|
# Join sweeps
|
|
933
934
|
offsets = (self._sweep_starts_r if use_real_start_times
|
|
@@ -964,31 +965,34 @@ class Series(TreeNode, myokit.formats.SweepSource):
|
|
|
964
965
|
log.cmeta[name]['unit'] = self._da_units[0]
|
|
965
966
|
|
|
966
967
|
# Add meta data
|
|
967
|
-
log.
|
|
968
|
+
log.meta['time'] = self._time.strftime(myokit.DATE_FORMAT)
|
|
968
969
|
a = self.amplifier_state()
|
|
970
|
+
t = self[0][0] if len(self) and len(self[0]) else None
|
|
969
971
|
log.meta['current_gain_mV_per_pA'] = a.current_gain()
|
|
972
|
+
log.meta['filter1'] = a.filter1()
|
|
973
|
+
if t is not None:
|
|
974
|
+
log.meta['r_pipette_MOhm'] = t.r_pipette()
|
|
975
|
+
log.meta['r_seal_MOhm'] = t.r_seal()
|
|
970
976
|
log.meta['ljp_correction_mV'] = a.ljp()
|
|
971
|
-
log.meta['
|
|
977
|
+
log.meta['voltage_offset_mV'] = a.v_off()
|
|
972
978
|
if a.c_fast_enabled():
|
|
973
979
|
log.meta['c_fast_compensation_enabled'] = 'true'
|
|
974
980
|
log.meta['c_fast_pF'] = a.c_fast()
|
|
975
|
-
log.meta['
|
|
981
|
+
log.meta['c_fast_tau_us'] = a.c_fast_tau()
|
|
976
982
|
else:
|
|
977
983
|
log.meta['c_fast_compensation_enabled'] = 'false'
|
|
984
|
+
log.meta['c_slow_pF'] = a.c_slow()
|
|
978
985
|
log.meta['r_series_MOhm'] = a.r_series()
|
|
979
986
|
if a.r_series_enabled():
|
|
980
987
|
log.meta['r_series_compensation_enabled'] = 'true'
|
|
981
988
|
log.meta['r_series_compensation_percent'] = round(
|
|
982
989
|
a.r_series_fraction() * 100, 1)
|
|
990
|
+
log.meta['r_series_compensation_tau_us'] = a.r_series_tau()
|
|
991
|
+
if t is not None:
|
|
992
|
+
log.meta['r_series_post_compensation_MOhm'] = \
|
|
993
|
+
t.r_series_remaining()
|
|
983
994
|
else:
|
|
984
995
|
log.meta['r_series_compensation_enabled'] = 'false'
|
|
985
|
-
if len(self) and len(self[0]):
|
|
986
|
-
t = self[0][0]
|
|
987
|
-
log.meta['r_pipette_MOhm'] = t.r_pipette()
|
|
988
|
-
log.meta['r_seal_MOhm'] = t.r_series()
|
|
989
|
-
log.meta['r_series_post_compensation_MOhm'] = \
|
|
990
|
-
t.r_series_remaining()
|
|
991
|
-
log.meta['c_slow_pF'] = t.c_slow()
|
|
992
996
|
|
|
993
997
|
return log
|
|
994
998
|
|
|
@@ -1035,6 +1039,8 @@ class Series(TreeNode, myokit.formats.SweepSource):
|
|
|
1035
1039
|
if a.r_series_enabled():
|
|
1036
1040
|
p = round(a.r_series_fraction() * 100, 1)
|
|
1037
1041
|
out.append(f' R series compensation: {p} %.')
|
|
1042
|
+
p = round(a.r_series_tau(), 1)
|
|
1043
|
+
out.append(f' R series compensation tau: {p} us')
|
|
1038
1044
|
else:
|
|
1039
1045
|
out.append(' R series compensation: not enabled')
|
|
1040
1046
|
if len(self) and len(self[0]):
|
|
@@ -31,27 +31,6 @@ class OpenCLExpressionWriter(CBasedExpressionWriter):
|
|
|
31
31
|
self._sp = (precision == myokit.SINGLE_PRECISION)
|
|
32
32
|
self._nm = bool(native_math)
|
|
33
33
|
|
|
34
|
-
def _exc(self, e):
|
|
35
|
-
"""Returns ``ex(e)`` if ``e`` is a Condition, else ``ex(e != 0)``."""
|
|
36
|
-
# Can be removed after https://github.com/myokit/myokit/issues/1056
|
|
37
|
-
if isinstance(e, myokit.Condition):
|
|
38
|
-
return self.ex(e)
|
|
39
|
-
return self.ex(myokit.NotEqual(e, myokit.Number(0)))
|
|
40
|
-
|
|
41
|
-
def _ex_infix_comparison(self, e, op):
|
|
42
|
-
"""Handles ex() for infix condition operators (==, !=, > etc.)."""
|
|
43
|
-
# Can be removed after https://github.com/myokit/myokit/issues/1056
|
|
44
|
-
c1 = isinstance(e[0], myokit.Condition)
|
|
45
|
-
c2 = isinstance(e[1], myokit.Condition)
|
|
46
|
-
if (c1 and c2) or not (c1 or c2):
|
|
47
|
-
return f'({self.ex(e[0])} {op} {self.ex(e[1])})'
|
|
48
|
-
else:
|
|
49
|
-
return f'({self._exc(e[0])} {op} {self._exc(e[1])})'
|
|
50
|
-
|
|
51
|
-
def _ex_infix_logical(self, e, op):
|
|
52
|
-
# Can be removed after https://github.com/myokit/myokit/issues/1056
|
|
53
|
-
return f'({self._exc(e[0])} {op} {self._exc(e[1])})'
|
|
54
|
-
|
|
55
34
|
#def _ex_name(self, e):
|
|
56
35
|
#def _ex_derivative(self, e):
|
|
57
36
|
#def _ex_initial_value(self, e):
|
|
@@ -126,25 +105,7 @@ class OpenCLExpressionWriter(CBasedExpressionWriter):
|
|
|
126
105
|
|
|
127
106
|
#def _ex_and(self, e):
|
|
128
107
|
#def _ex_or(self, e):
|
|
129
|
-
|
|
130
|
-
def
|
|
131
|
-
|
|
132
|
-
return f'(!{self._exc(e[0])})'
|
|
133
|
-
|
|
134
|
-
def _ex_if(self, e):
|
|
135
|
-
# Can be removed after https://github.com/myokit/myokit/issues/1056
|
|
136
|
-
_if, _then, _else = self._exc(e._i), self.ex(e._t), self.ex(e._e)
|
|
137
|
-
return f'({_if} ? {_then} : {_else})'
|
|
138
|
-
|
|
139
|
-
def _ex_piecewise(self, e):
|
|
140
|
-
# Can be removed after https://github.com/myokit/myokit/issues/1056
|
|
141
|
-
_ifs = [self._exc(x) for x in e._i]
|
|
142
|
-
_thens = [self.ex(x) for x in e._e]
|
|
143
|
-
s = []
|
|
144
|
-
n = len(_ifs)
|
|
145
|
-
for _if, _then in zip(_ifs, _thens):
|
|
146
|
-
s.append(f'({_if} ? {_then} : ')
|
|
147
|
-
s.append(_thens[-1])
|
|
148
|
-
s.append(')' * len(_ifs))
|
|
149
|
-
return ''.join(s)
|
|
108
|
+
#def _ex_not(self, e):
|
|
109
|
+
#def _ex_if(self, e):
|
|
110
|
+
#def _ex_piecewise(self, e):
|
|
150
111
|
|
myokit/formats/sympy/_ereader.py
CHANGED
|
@@ -156,7 +156,8 @@ class SymPyExpressionReader:
|
|
|
156
156
|
def _ex_abs(self, e):
|
|
157
157
|
return myokit.Abs(self.ex(e.args[0]))
|
|
158
158
|
|
|
159
|
-
def _ex_not(self, e):
|
|
159
|
+
def _ex_not(self, e): # pragma: no cover
|
|
160
|
+
# Sympy turns not(a == b) into a != b etc., so can't test!
|
|
160
161
|
return myokit.Not(self.ex(e.args[0]))
|
|
161
162
|
|
|
162
163
|
def _ex_equal(self, e):
|
myokit/formats/wcp/_wcp.py
CHANGED
|
@@ -241,7 +241,7 @@ class WcpFile(myokit.formats.SweepSource):
|
|
|
241
241
|
|
|
242
242
|
def _channel_id(self, channel_id):
|
|
243
243
|
""" Checks an int or str channel id and returns a valid int. """
|
|
244
|
-
if self._nr == 0:
|
|
244
|
+
if self._nr == 0:
|
|
245
245
|
raise KeyError(f'Channel {channel_id} not found (empty file).')
|
|
246
246
|
|
|
247
247
|
# Handle string
|
|
@@ -314,7 +314,7 @@ class WcpFile(myokit.formats.SweepSource):
|
|
|
314
314
|
|
|
315
315
|
# Create log
|
|
316
316
|
log = myokit.DataLog()
|
|
317
|
-
if self._nr == 0:
|
|
317
|
+
if self._nr == 0:
|
|
318
318
|
return log
|
|
319
319
|
|
|
320
320
|
# Get channel names
|
|
@@ -433,7 +433,7 @@ class WcpFile(myokit.formats.SweepSource):
|
|
|
433
433
|
return self._nr
|
|
434
434
|
|
|
435
435
|
def records(self):
|
|
436
|
-
""" Deprecated alias of :meth:`
|
|
436
|
+
""" Deprecated alias of :meth:`record_count`. """
|
|
437
437
|
# Deprecated since 2023-06-22
|
|
438
438
|
import warnings
|
|
439
439
|
warnings.warn(
|
myokit/gui/datalog_viewer.py
CHANGED
|
@@ -641,13 +641,18 @@ class SweepSourceTab(GraphTabWidget):
|
|
|
641
641
|
def __init__(self, parent, source):
|
|
642
642
|
super().__init__(parent)
|
|
643
643
|
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
644
|
+
if source.sweep_count() > 0:
|
|
645
|
+
# Must have this condition to avoid getting exceptions with e.g.
|
|
646
|
+
# the empty WCP file in the test data set.
|
|
647
|
+
# myokit log myokit/tests/data/formats/wcp-file-empty.wcp
|
|
648
|
+
|
|
649
|
+
# Add A/D
|
|
650
|
+
for i in range(source.channel_count()):
|
|
651
|
+
self._add_graph_tab(source, i)
|
|
652
|
+
|
|
653
|
+
# Add D/A
|
|
654
|
+
for i in range(source.da_count()):
|
|
655
|
+
self._add_graph_tab(source, i, True)
|
|
651
656
|
|
|
652
657
|
# Add meta data
|
|
653
658
|
self._add_meta_tab(source)
|
myokit/lib/markov.py
CHANGED
|
@@ -1077,7 +1077,7 @@ class AnalyticalSimulation:
|
|
|
1077
1077
|
y0 = PI.dot(self._state.reshape((n, 1)))
|
|
1078
1078
|
|
|
1079
1079
|
# Reshape times array
|
|
1080
|
-
times = np.
|
|
1080
|
+
times = np.asarray(times).reshape((len(times),))
|
|
1081
1081
|
|
|
1082
1082
|
# Calculate state
|
|
1083
1083
|
x = P.dot(y0 * np.exp(times * E))
|
|
@@ -1223,7 +1223,7 @@ class DiscreteSimulation:
|
|
|
1223
1223
|
|
|
1224
1224
|
Returns a discretized state ``y`` where ``sum(y) = nchannels``.
|
|
1225
1225
|
"""
|
|
1226
|
-
x = np.
|
|
1226
|
+
x = np.asarray(x, dtype=float)
|
|
1227
1227
|
if (np.abs(1 - np.sum(x))) > 1e-6:
|
|
1228
1228
|
raise ValueError(
|
|
1229
1229
|
'The sum of fractions in the state to be discretized must'
|
myokit/lib/plots.py
CHANGED
|
@@ -62,28 +62,28 @@ def simulation_times(
|
|
|
62
62
|
def stair(ax, time, realtime, evaluations):
|
|
63
63
|
if time is None:
|
|
64
64
|
raise ValueError('This plotting mode requires "time" to be set.')
|
|
65
|
-
time = np.
|
|
65
|
+
time = np.asarray(time)
|
|
66
66
|
step = np.arange(0, len(time))
|
|
67
67
|
ax.step(time, step, label=label)
|
|
68
68
|
|
|
69
69
|
def stair_inverse(ax, time, realtime, evaluations):
|
|
70
70
|
if time is None:
|
|
71
71
|
raise ValueError('This plotting mode requires "time" to be set.')
|
|
72
|
-
time = np.
|
|
72
|
+
time = np.asarray(time)
|
|
73
73
|
step = np.arange(0, len(time))
|
|
74
74
|
ax.step(step, time, label=label)
|
|
75
75
|
|
|
76
76
|
def load(ax, time, realtime, evaluations):
|
|
77
77
|
if time is None:
|
|
78
78
|
raise ValueError('This plotting mode requires "time" to be set.')
|
|
79
|
-
time = np.
|
|
79
|
+
time = np.asarray(time)
|
|
80
80
|
size = np.log(1 / (time[1:] - time[:-1]))
|
|
81
81
|
ax.step(time[1:], size, label=label)
|
|
82
82
|
|
|
83
83
|
def histo(ax, time, realtime, evaluations):
|
|
84
84
|
if time is None:
|
|
85
85
|
raise ValueError('This plotting mode requires "time" to be set.')
|
|
86
|
-
time = np.
|
|
86
|
+
time = np.asarray(time)
|
|
87
87
|
zero = float(time[0])
|
|
88
88
|
bucket_w = (time[-1] - zero) / nbuckets
|
|
89
89
|
bucket_x = np.zeros(nbuckets)
|
|
Binary file
|
myokit/tests/test_datablock.py
CHANGED
|
@@ -167,11 +167,11 @@ class DataBlock1dTest(unittest.TestCase):
|
|
|
167
167
|
d = myokit.DataLog()
|
|
168
168
|
d.set_time_key('time')
|
|
169
169
|
d['time'] = time
|
|
170
|
-
d['x', 0] = np.
|
|
171
|
-
d['x', 1] = np.
|
|
172
|
-
d['x', 2] = np.
|
|
173
|
-
d['x', 3] = np.
|
|
174
|
-
d['x', 4] = np.
|
|
170
|
+
d['x', 0] = np.copy(down)
|
|
171
|
+
d['x', 1] = np.copy(down)
|
|
172
|
+
d['x', 2] = np.copy(down)
|
|
173
|
+
d['x', 3] = np.copy(down)
|
|
174
|
+
d['x', 4] = np.copy(down)
|
|
175
175
|
d['x', 1][10:] = 40
|
|
176
176
|
b = myokit.DataBlock1d.from_log(d)
|
|
177
177
|
self.assertEqual(b.cv('x'), 0)
|
|
@@ -180,11 +180,11 @@ class DataBlock1dTest(unittest.TestCase):
|
|
|
180
180
|
d = myokit.DataLog()
|
|
181
181
|
d.set_time_key('time')
|
|
182
182
|
d['time'] = time
|
|
183
|
-
d['x', 0] = np.
|
|
184
|
-
d['x', 1] = np.
|
|
185
|
-
d['x', 2] = np.
|
|
186
|
-
d['x', 3] = np.
|
|
187
|
-
d['x', 4] = np.
|
|
183
|
+
d['x', 0] = np.copy(down)
|
|
184
|
+
d['x', 1] = np.copy(down)
|
|
185
|
+
d['x', 2] = np.copy(down)
|
|
186
|
+
d['x', 3] = np.copy(down)
|
|
187
|
+
d['x', 4] = np.copy(down)
|
|
188
188
|
d['x', 1][10:] = 40
|
|
189
189
|
d['x', 2][10:] = 40
|
|
190
190
|
d['x', 3][10:] = 40
|
myokit/tests/test_datalog.py
CHANGED
|
@@ -2109,9 +2109,12 @@ class DataLogTest(unittest.TestCase):
|
|
|
2109
2109
|
self.assertRaises(ValueError, d.split_periodic, 0)
|
|
2110
2110
|
self.assertRaises(ValueError, d.split_periodic, -1)
|
|
2111
2111
|
|
|
2112
|
-
# Test larger period than log data
|
|
2112
|
+
# Test larger period than log data: Should return length-1 list
|
|
2113
2113
|
d['x'] = [4, 5, 6, 7]
|
|
2114
2114
|
e = d.split_periodic(100)
|
|
2115
|
+
self.assertIsInstance(e, list)
|
|
2116
|
+
self.assertEqual(len(e), 1)
|
|
2117
|
+
e = e[0]
|
|
2115
2118
|
self.assertEqual(set(d.keys()), set(e.keys()))
|
|
2116
2119
|
self.assertFalse(d is e)
|
|
2117
2120
|
|