myokit 1.38.0__py3-none-any.whl → 1.39.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 +5 -0
- myokit/_datablock.py +6 -5
- myokit/_expressions.py +6 -1
- myokit/_model_api.py +44 -18
- myokit/_myokit_version.py +1 -1
- myokit/_parsing.py +8 -2
- myokit/_sim/cvodessim.py +26 -0
- myokit/formats/__init__.py +37 -0
- myokit/formats/ansic/_ewriter.py +1 -1
- myokit/formats/axon/_abf.py +43 -9
- myokit/formats/cellml/v1/__init__.py +5 -5
- myokit/formats/cellml/v1/_api.py +220 -122
- myokit/formats/cellml/v1/_parser.py +91 -87
- myokit/formats/cellml/v1/_writer.py +13 -6
- myokit/formats/cellml/v2/__init__.py +5 -8
- myokit/formats/cellml/v2/_api.py +182 -106
- myokit/formats/cellml/v2/_parser.py +68 -64
- myokit/formats/cellml/v2/_writer.py +7 -3
- myokit/formats/heka/_patchmaster.py +71 -14
- myokit/formats/mathml/_parser.py +106 -67
- myokit/gui/source.py +18 -12
- myokit/lib/hh.py +21 -37
- myokit/tests/test_cellml_v1_api.py +227 -33
- myokit/tests/test_cellml_v1_parser.py +48 -17
- myokit/tests/test_cellml_v1_writer.py +14 -4
- myokit/tests/test_cellml_v2_api.py +132 -114
- myokit/tests/test_cellml_v2_parser.py +31 -1
- myokit/tests/test_cellml_v2_writer.py +8 -1
- myokit/tests/test_datalog.py +17 -0
- myokit/tests/test_expressions.py +61 -0
- myokit/tests/test_formats.py +99 -0
- myokit/tests/test_formats_mathml_content.py +97 -37
- myokit/tests/test_formats_python.py +1 -1
- myokit/tests/test_model_building.py +2 -0
- myokit/tests/test_parsing.py +32 -0
- myokit/tests/test_simulation_cvodes.py +10 -4
- myokit/tests/test_variable.py +10 -7
- {myokit-1.38.0.dist-info → myokit-1.39.0.dist-info}/METADATA +22 -7
- {myokit-1.38.0.dist-info → myokit-1.39.0.dist-info}/RECORD +43 -43
- {myokit-1.38.0.dist-info → myokit-1.39.0.dist-info}/WHEEL +1 -1
- {myokit-1.38.0.dist-info → myokit-1.39.0.dist-info}/entry_points.txt +0 -0
- {myokit-1.38.0.dist-info → myokit-1.39.0.dist-info/licenses}/LICENSE.txt +0 -0
- {myokit-1.38.0.dist-info → myokit-1.39.0.dist-info}/top_level.txt +0 -0
myokit/formats/cellml/v2/_api.py
CHANGED
|
@@ -10,13 +10,11 @@ import warnings
|
|
|
10
10
|
|
|
11
11
|
import myokit
|
|
12
12
|
|
|
13
|
+
from myokit.formats import is_integer_string, is_real_number_string
|
|
14
|
+
|
|
13
15
|
|
|
14
16
|
# Data types
|
|
15
17
|
_cellml_identifier = re.compile(r'^[a-zA-Z][a-zA-Z0-9_]*$')
|
|
16
|
-
_cellml_integer = re.compile(r'^[+-]?[0-9]+$')
|
|
17
|
-
_real = r'[+-]?(([0-9]*\.[0-9]+)|([0-9]+\.?[0-9]*))'
|
|
18
|
-
_cellml_basic_real = re.compile(r'^' + _real + r'$')
|
|
19
|
-
_cellml_real = re.compile(r'^' + _real + r'([eE][+-]?[0-9]+)?$')
|
|
20
18
|
|
|
21
19
|
|
|
22
20
|
def is_identifier(name):
|
|
@@ -32,27 +30,6 @@ def is_identifier(name):
|
|
|
32
30
|
return _cellml_identifier.match(name) is not None
|
|
33
31
|
|
|
34
32
|
|
|
35
|
-
def is_integer_string(text):
|
|
36
|
-
"""
|
|
37
|
-
Tests if the given ``text`` is a valid CellML 2.0 integer string.
|
|
38
|
-
"""
|
|
39
|
-
return _cellml_integer.match(text) is not None
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def is_basic_real_number_string(text):
|
|
43
|
-
"""
|
|
44
|
-
Tests if the given ``text`` is a valid CellML 2.0 basic real number string.
|
|
45
|
-
"""
|
|
46
|
-
return _cellml_basic_real.match(text) is not None
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def is_real_number_string(text):
|
|
50
|
-
"""
|
|
51
|
-
Tests if the given ``text`` is a valid CellML 2.0 basic real number string.
|
|
52
|
-
"""
|
|
53
|
-
return _cellml_real.match(text) is not None
|
|
54
|
-
|
|
55
|
-
|
|
56
33
|
def clean_identifier(name):
|
|
57
34
|
"""
|
|
58
35
|
Checks if ``name`` is a valid CellML 2.0 identifier and if not attempts to
|
|
@@ -70,8 +47,7 @@ def clean_identifier(name):
|
|
|
70
47
|
if is_identifier(clean):
|
|
71
48
|
return clean
|
|
72
49
|
raise ValueError(
|
|
73
|
-
'Unable to create a valid CellML 2.0 identifier from "'
|
|
74
|
-
+ '".')
|
|
50
|
+
f'Unable to create a valid CellML 2.0 identifier from "{name}".')
|
|
75
51
|
|
|
76
52
|
|
|
77
53
|
def create_unit_name(unit):
|
|
@@ -133,6 +109,16 @@ def create_unit_name(unit):
|
|
|
133
109
|
return name
|
|
134
110
|
|
|
135
111
|
|
|
112
|
+
def is_prefixed_number(expr):
|
|
113
|
+
"""
|
|
114
|
+
Checks if ``expr`` is a :class:`myokit.Number`, wrapped in any number of
|
|
115
|
+
prefix plus or minus operators.
|
|
116
|
+
"""
|
|
117
|
+
while isinstance(expr, (myokit.PrefixPlus, myokit.PrefixMinus)):
|
|
118
|
+
expr = expr[0]
|
|
119
|
+
return isinstance(expr, myokit.Number)
|
|
120
|
+
|
|
121
|
+
|
|
136
122
|
class AnnotatableElement:
|
|
137
123
|
"""
|
|
138
124
|
Represents a CellML 2.0 element that can be annotated (using a public dict
|
|
@@ -262,7 +248,7 @@ class Component(AnnotatableElement):
|
|
|
262
248
|
parent._children.add(self)
|
|
263
249
|
|
|
264
250
|
def __str__(self):
|
|
265
|
-
return 'Component[@name="
|
|
251
|
+
return f'Component[@name="{self._name}"]'
|
|
266
252
|
|
|
267
253
|
def _validate(self):
|
|
268
254
|
"""
|
|
@@ -298,7 +284,8 @@ class Model(AnnotatableElement):
|
|
|
298
284
|
|
|
299
285
|
- Imports are not supported.
|
|
300
286
|
- Reset rules are not supported.
|
|
301
|
-
- Using variables in ``initial_value`` attributes is
|
|
287
|
+
- Using variables in ``initial_value`` attributes is supported, as long as
|
|
288
|
+
they have constant values.
|
|
302
289
|
- Defining new base units is not supported.
|
|
303
290
|
- All equations must be of the form ``x = ...`` or ``dx/dt = ...``.
|
|
304
291
|
- Models that take derivatives with respect to more than one variable are
|
|
@@ -401,39 +388,36 @@ class Model(AnnotatableElement):
|
|
|
401
388
|
interface_2 = 'public'
|
|
402
389
|
else:
|
|
403
390
|
raise CellMLError(
|
|
404
|
-
'Unable to connect
|
|
405
|
-
|
|
406
|
-
'
|
|
407
|
-
' relationship.')
|
|
391
|
+
f'Unable to connect {variable_1} to {variable_2}: connections'
|
|
392
|
+
' can only be made between components that are siblings or'
|
|
393
|
+
' have a parent-child relationship.')
|
|
408
394
|
|
|
409
395
|
# Check the variables' interfaces.
|
|
410
396
|
if interface_1 not in variable_1.interface():
|
|
411
397
|
raise CellMLError(
|
|
412
|
-
'Unable to connect
|
|
413
|
-
|
|
414
|
-
|
|
398
|
+
f'Unable to connect {variable_1} to {variable_2}: variable_1'
|
|
399
|
+
f' requires the {interface_1} interface, but is set to'
|
|
400
|
+
f' {variable_1.interface()}.')
|
|
415
401
|
if interface_2 not in variable_2.interface():
|
|
416
402
|
raise CellMLError(
|
|
417
|
-
'Unable to connect
|
|
418
|
-
|
|
419
|
-
|
|
403
|
+
f'Unable to connect {variable_1} to {variable_2}: variable_2'
|
|
404
|
+
f' requires the {interface_2} interface, but is set to'
|
|
405
|
+
f' {variable_2.interface()}.')
|
|
420
406
|
|
|
421
407
|
# Check the variables' units.
|
|
422
408
|
unit_1 = variable_1.units().myokit_unit()
|
|
423
409
|
unit_2 = variable_2.units().myokit_unit()
|
|
424
410
|
if not myokit.Unit.can_convert(unit_1, unit_2):
|
|
425
411
|
raise CellMLError(
|
|
426
|
-
'Unable to connect
|
|
427
|
-
|
|
428
|
-
'
|
|
429
|
-
+ ' and ' + str(variable_2.units()) + '.')
|
|
412
|
+
f'Unable to connect {variable_1} to {variable_2}: Connected'
|
|
413
|
+
' variables must have compatible units. Found'
|
|
414
|
+
f' {variable_1.units()} and {variable_2.units()}.')
|
|
430
415
|
|
|
431
416
|
# Check the variables aren't already connected
|
|
432
417
|
if variable_1 in variable_2._cset:
|
|
433
418
|
raise CellMLError(
|
|
434
|
-
'Variables cannot be connected twice:
|
|
435
|
-
|
|
436
|
-
+ str(variable_2) + '.')
|
|
419
|
+
f'Variables cannot be connected twice: {variable_1} is already'
|
|
420
|
+
f' in the connected variable set of {variable_2}.')
|
|
437
421
|
|
|
438
422
|
# Connect the variables, by merging their connected variable sets.
|
|
439
423
|
ConnectedVariableSet._merge(variable_1._cset, variable_2._cset)
|
|
@@ -600,6 +584,35 @@ class Model(AnnotatableElement):
|
|
|
600
584
|
# Create CellML model
|
|
601
585
|
m = Model(name, version)
|
|
602
586
|
|
|
587
|
+
# Check if we need to create a Myokit model where all initial variables
|
|
588
|
+
# are either numbers or names of local variables.
|
|
589
|
+
states_to_fix = []
|
|
590
|
+
for state in model.states():
|
|
591
|
+
e = state.initial_value()
|
|
592
|
+
if isinstance(e, myokit.Number):
|
|
593
|
+
continue
|
|
594
|
+
if isinstance(e, myokit.Name):
|
|
595
|
+
# Compare parents: note, nested variables can't be referenced
|
|
596
|
+
# here, so this check is sufficient
|
|
597
|
+
if e.var().parent() == state.parent():
|
|
598
|
+
continue
|
|
599
|
+
states_to_fix.append(state.qname())
|
|
600
|
+
if states_to_fix:
|
|
601
|
+
model = model.clone()
|
|
602
|
+
for state in states_to_fix:
|
|
603
|
+
state = model.get(state)
|
|
604
|
+
value = state.initial_value()
|
|
605
|
+
if is_prefixed_number(value):
|
|
606
|
+
# Don't make variables for x = -1
|
|
607
|
+
state.set_initial_value(value.eval())
|
|
608
|
+
else:
|
|
609
|
+
# But do for `1 + exp(3)`, or `a + b`
|
|
610
|
+
init_var = state.parent().add_variable_allow_renaming(
|
|
611
|
+
state.name() + '_init')
|
|
612
|
+
init_var.set_rhs(value)
|
|
613
|
+
init_var.set_unit(state.unit())
|
|
614
|
+
state.set_initial_value(init_var.lhs())
|
|
615
|
+
|
|
603
616
|
# Valid model always has a time variable
|
|
604
617
|
time = model.time()
|
|
605
618
|
|
|
@@ -793,7 +806,12 @@ class Model(AnnotatableElement):
|
|
|
793
806
|
|
|
794
807
|
# Promote states and set rhs and initial value
|
|
795
808
|
elif variable.is_state():
|
|
796
|
-
|
|
809
|
+
init = variable.initial_value()
|
|
810
|
+
if is_prefixed_number(init):
|
|
811
|
+
# Pass in float, in case unit not specified
|
|
812
|
+
v.set_initial_value(init.eval())
|
|
813
|
+
else:
|
|
814
|
+
v.set_initial_value(init.clone(subst=subst))
|
|
797
815
|
v.set_equation(myokit.Equation(lhs, rhs))
|
|
798
816
|
|
|
799
817
|
# Store literals (single number) in initial value
|
|
@@ -842,7 +860,7 @@ class Model(AnnotatableElement):
|
|
|
842
860
|
|
|
843
861
|
# Create model
|
|
844
862
|
m = myokit.Model(cmodel.name())
|
|
845
|
-
m.meta['
|
|
863
|
+
m.meta['mmt_authors'] = 'Myokit CellML 2 API'
|
|
846
864
|
|
|
847
865
|
# Copy meta data
|
|
848
866
|
for key, value in cmodel.meta.items():
|
|
@@ -926,8 +944,9 @@ class Model(AnnotatableElement):
|
|
|
926
944
|
|
|
927
945
|
# Promote states
|
|
928
946
|
if variable.is_state():
|
|
929
|
-
init = variable.initial_value()
|
|
930
|
-
v.promote(
|
|
947
|
+
init = variable.initial_value()
|
|
948
|
+
v.promote(
|
|
949
|
+
0 if init is None else init.clone(subst=var_map))
|
|
931
950
|
|
|
932
951
|
# Set time variable
|
|
933
952
|
if variable is voi:
|
|
@@ -967,11 +986,11 @@ class Model(AnnotatableElement):
|
|
|
967
986
|
elif variable not in self._voi._cset:
|
|
968
987
|
voi = self.variable_of_integration()
|
|
969
988
|
raise CellMLError(
|
|
970
|
-
'Cannot set
|
|
971
|
-
'
|
|
989
|
+
f'Cannot set {variable} as variable of integration, the'
|
|
990
|
+
f' variable {voi} has already been set.')
|
|
972
991
|
|
|
973
992
|
def __str__(self):
|
|
974
|
-
return 'Model[@name="
|
|
993
|
+
return f'Model[@name="{self._name}"]'
|
|
975
994
|
|
|
976
995
|
def units(self):
|
|
977
996
|
"""
|
|
@@ -1006,7 +1025,7 @@ class Model(AnnotatableElement):
|
|
|
1006
1025
|
free.add(cset)
|
|
1007
1026
|
if self._voi not in cset:
|
|
1008
1027
|
for var in cset:
|
|
1009
|
-
warnings.warn('No value set for
|
|
1028
|
+
warnings.warn(f'No value set for {var}.')
|
|
1010
1029
|
|
|
1011
1030
|
# Check that there's at most one free variable.
|
|
1012
1031
|
if len(free) > 1:
|
|
@@ -1015,20 +1034,20 @@ class Model(AnnotatableElement):
|
|
|
1015
1034
|
# Check that the variable of integration is a free variable
|
|
1016
1035
|
voi = self.variable_of_integration()
|
|
1017
1036
|
if not (voi is None or voi.is_free()):
|
|
1018
|
-
msg = 'Variable of integration
|
|
1019
|
-
|
|
1037
|
+
msg = (f'Variable of integration {voi} must be a free variable,'
|
|
1038
|
+
' but has ')
|
|
1020
1039
|
if voi.has_equation():
|
|
1021
1040
|
var = voi.equation_variable()
|
|
1022
1041
|
if voi is var:
|
|
1023
1042
|
msg += 'equation.'
|
|
1024
1043
|
else:
|
|
1025
|
-
msg += 'equation (set by
|
|
1044
|
+
msg += f'equation (set by {var}).'
|
|
1026
1045
|
else:
|
|
1027
1046
|
var = voi.initial_value_variable()
|
|
1028
1047
|
if voi is var:
|
|
1029
1048
|
msg += 'initial value.'
|
|
1030
1049
|
else:
|
|
1031
|
-
msg += 'initial value (set by
|
|
1050
|
+
msg += f'initial value (set by {var}).'
|
|
1032
1051
|
raise CellMLError(msg)
|
|
1033
1052
|
|
|
1034
1053
|
def variable_of_integration(self):
|
|
@@ -1083,7 +1102,7 @@ class Units:
|
|
|
1083
1102
|
'Units name must be a valid CellML identifier.')
|
|
1084
1103
|
if not predefined and name in self._si_units:
|
|
1085
1104
|
raise CellMLError(
|
|
1086
|
-
'Units name "
|
|
1105
|
+
f'Units name "{name}" overlaps with a predefined name.')
|
|
1087
1106
|
self._name = name
|
|
1088
1107
|
|
|
1089
1108
|
# Check and store Myokit unit
|
|
@@ -1110,7 +1129,7 @@ class Units:
|
|
|
1110
1129
|
# If not raise error (and one that makes sense even if this was
|
|
1111
1130
|
# called via a model or component units lookup).
|
|
1112
1131
|
if myokit_unit is None:
|
|
1113
|
-
raise CellMLError('Unknown units name "
|
|
1132
|
+
raise CellMLError(f'Unknown units name "{name}".')
|
|
1114
1133
|
|
|
1115
1134
|
# Create and store object
|
|
1116
1135
|
obj = cls(name, myokit_unit, predefined=True)
|
|
@@ -1130,7 +1149,7 @@ class Units:
|
|
|
1130
1149
|
return cls._si_units_r[myokit_unit]
|
|
1131
1150
|
except KeyError:
|
|
1132
1151
|
raise CellMLError(
|
|
1133
|
-
'No name found for myokit unit
|
|
1152
|
+
f'No name found for myokit unit f{myokit_unit}.')
|
|
1134
1153
|
|
|
1135
1154
|
def myokit_unit(self):
|
|
1136
1155
|
"""
|
|
@@ -1189,14 +1208,13 @@ class Units:
|
|
|
1189
1208
|
except KeyError:
|
|
1190
1209
|
raise CellMLError(
|
|
1191
1210
|
'Units prefix must be a string from the list of known'
|
|
1192
|
-
' prefixes or an integer string, got "'
|
|
1193
|
-
+ '".')
|
|
1211
|
+
f' prefixes or an integer string, got "{prefix}".')
|
|
1194
1212
|
|
|
1195
1213
|
# Apply prefix to unit
|
|
1196
1214
|
|
|
1197
1215
|
# float(10**309) is the first int that doesn't fit in a float
|
|
1198
1216
|
if p > 309:
|
|
1199
|
-
raise CellMLError('Unit prefix too large: 10^'
|
|
1217
|
+
raise CellMLError(f'Unit prefix too large: 10^{p}')
|
|
1200
1218
|
unit *= 10**int(p)
|
|
1201
1219
|
|
|
1202
1220
|
# Handle exponent (note: prefix is exponentiated, multiplier is not).
|
|
@@ -1205,8 +1223,8 @@ class Units:
|
|
|
1205
1223
|
|
|
1206
1224
|
if not is_real_number_string(str(exponent).strip()):
|
|
1207
1225
|
raise CellMLError(
|
|
1208
|
-
'Unit exponent must be a real number string, got
|
|
1209
|
-
|
|
1226
|
+
'Unit exponent must be a real number string, got'
|
|
1227
|
+
f' "{multiplier}".')
|
|
1210
1228
|
e = float(exponent)
|
|
1211
1229
|
|
|
1212
1230
|
# Apply exponent to unit
|
|
@@ -1216,8 +1234,8 @@ class Units:
|
|
|
1216
1234
|
if multiplier is not None:
|
|
1217
1235
|
if not is_real_number_string(str(multiplier).strip()):
|
|
1218
1236
|
raise CellMLError(
|
|
1219
|
-
'Unit multiplier must be a real number string, got
|
|
1220
|
-
|
|
1237
|
+
'Unit multiplier must be a real number string, got'
|
|
1238
|
+
f' "{multiplier}".')
|
|
1221
1239
|
m = float(multiplier)
|
|
1222
1240
|
|
|
1223
1241
|
# Apply multiplier to unit
|
|
@@ -1234,7 +1252,7 @@ class Units:
|
|
|
1234
1252
|
return cls._si_units.keys()
|
|
1235
1253
|
|
|
1236
1254
|
def __str__(self):
|
|
1237
|
-
return 'Units[@name="
|
|
1255
|
+
return f'Units[@name="{self._name}"]'
|
|
1238
1256
|
|
|
1239
1257
|
# Predefined units in CellML, name to Unit
|
|
1240
1258
|
_si_units = {
|
|
@@ -1368,8 +1386,8 @@ class Variable(AnnotatableElement):
|
|
|
1368
1386
|
except CellMLError:
|
|
1369
1387
|
raise CellMLError(
|
|
1370
1388
|
'Variable units attribute must reference a units element in'
|
|
1371
|
-
' the model, or one of the predefined units, found
|
|
1372
|
-
|
|
1389
|
+
' the model, or one of the predefined units, found'
|
|
1390
|
+
f' "{units}".')
|
|
1373
1391
|
|
|
1374
1392
|
# Check and store interfaces
|
|
1375
1393
|
interfaces = ('none', 'public', 'private', 'public_and_private')
|
|
@@ -1398,6 +1416,19 @@ class Variable(AnnotatableElement):
|
|
|
1398
1416
|
"""
|
|
1399
1417
|
Returns the equation for this variable (or its connected variable set),
|
|
1400
1418
|
in the correct units.
|
|
1419
|
+
|
|
1420
|
+
Note that this method is primarily intended to *extract* equations from
|
|
1421
|
+
a CellML model, and care must be taken when using it to manipulate a
|
|
1422
|
+
CellML model. Specifically:
|
|
1423
|
+
|
|
1424
|
+
1. If the equation was not set on this variable, but on a variable in
|
|
1425
|
+
the connected variable set, the returned equation may refer to
|
|
1426
|
+
non-local variables.
|
|
1427
|
+
2. Similarly, if unit conversion is required, the conversion factor may
|
|
1428
|
+
have a unit not defined in this model.
|
|
1429
|
+
|
|
1430
|
+
As a result, calling :meth:`set_equation` with the value returned by
|
|
1431
|
+
:meth:`equation` is not always possible.
|
|
1401
1432
|
"""
|
|
1402
1433
|
eq = self._cset.equation()
|
|
1403
1434
|
if eq is None or eq.lhs.var() is self:
|
|
@@ -1451,6 +1482,11 @@ class Variable(AnnotatableElement):
|
|
|
1451
1482
|
set), in the correct units.
|
|
1452
1483
|
|
|
1453
1484
|
The returned value is a :class:`myokit.Expression`.
|
|
1485
|
+
|
|
1486
|
+
Note that, like :meth:`equation`, this method is primarily intended to
|
|
1487
|
+
*extract* initial values from a CellML model. As a result, the returned
|
|
1488
|
+
value may be a referenced to a variable in another component, and may
|
|
1489
|
+
contain a unit conversion multiplication.
|
|
1454
1490
|
"""
|
|
1455
1491
|
value = self._cset.initial_value()
|
|
1456
1492
|
if value is None or self._cset.initial_value_variable() is self:
|
|
@@ -1534,6 +1570,9 @@ class Variable(AnnotatableElement):
|
|
|
1534
1570
|
value.
|
|
1535
1571
|
|
|
1536
1572
|
If neither is found, ``None`` will be returned.
|
|
1573
|
+
|
|
1574
|
+
Note that the caveats applying to :meth:`equation` and
|
|
1575
|
+
:meth:`initial_value` also apply here.
|
|
1537
1576
|
"""
|
|
1538
1577
|
eq = self.equation()
|
|
1539
1578
|
return eq.rhs if eq is not None else self.initial_value()
|
|
@@ -1566,8 +1605,7 @@ class Variable(AnnotatableElement):
|
|
|
1566
1605
|
' of the form `x = ...` or `dx/dt = ...`.')
|
|
1567
1606
|
if lhs.var() is not self:
|
|
1568
1607
|
raise CellMLError(
|
|
1569
|
-
'Equation for
|
|
1570
|
-
+ str(self) + '.')
|
|
1608
|
+
f'Equation for f{lhs.var()} passed to variable {self}.')
|
|
1571
1609
|
|
|
1572
1610
|
# Check all references are local
|
|
1573
1611
|
for ref in lhs.references() | rhs.references():
|
|
@@ -1575,7 +1613,7 @@ class Variable(AnnotatableElement):
|
|
|
1575
1613
|
if var._component is not self._component:
|
|
1576
1614
|
raise CellMLError(
|
|
1577
1615
|
'An equation can only reference variables from the'
|
|
1578
|
-
' same component, found:
|
|
1616
|
+
f' same component, found: {var}.')
|
|
1579
1617
|
|
|
1580
1618
|
# Check all units in the RHS are known, and replace numbers
|
|
1581
1619
|
# without units with numbers in units 'dimensionless'.
|
|
@@ -1590,7 +1628,7 @@ class Variable(AnnotatableElement):
|
|
|
1590
1628
|
except CellMLError:
|
|
1591
1629
|
raise CellMLError(
|
|
1592
1630
|
'All units appearing in a variable\'s RHS must'
|
|
1593
|
-
' be known, found:
|
|
1631
|
+
f' be known, found: {x.unit()}.')
|
|
1594
1632
|
if numbers_without_units:
|
|
1595
1633
|
rhs = rhs.clone(subst=numbers_without_units)
|
|
1596
1634
|
equation = myokit.Equation(lhs, rhs)
|
|
@@ -1600,31 +1638,71 @@ class Variable(AnnotatableElement):
|
|
|
1600
1638
|
|
|
1601
1639
|
def set_initial_value(self, value):
|
|
1602
1640
|
"""
|
|
1603
|
-
Sets this variable's intial value
|
|
1641
|
+
Sets this variable's intial value: must be a number, a local variable,
|
|
1642
|
+
or ``None``.
|
|
1643
|
+
|
|
1644
|
+
Numbers can be specified as number types, strings, or
|
|
1645
|
+
:class:`myokit.Number` objects (wrapped in prefix plus or minus
|
|
1646
|
+
operators). If a :class:`myokit.Number` is passed in, it should have
|
|
1647
|
+
the same units as this variable. Variables can be passed in as strings
|
|
1648
|
+
or :class:`myokit.Name` objects.
|
|
1604
1649
|
"""
|
|
1605
1650
|
# Allow unsetting with ``None``
|
|
1606
|
-
if value is
|
|
1651
|
+
if value is None:
|
|
1652
|
+
self._cset.set_initial_value(self, None)
|
|
1653
|
+
return
|
|
1607
1654
|
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1655
|
+
# Allow string input
|
|
1656
|
+
if isinstance(value, str):
|
|
1657
|
+
if is_real_number_string(value):
|
|
1658
|
+
value = myokit.Number(value, self._units.myokit_unit())
|
|
1659
|
+
elif is_identifier(value):
|
|
1660
|
+
try:
|
|
1661
|
+
value = self._component.variable(value)
|
|
1662
|
+
except KeyError:
|
|
1663
|
+
raise CellMLError('Unknown local variable specified as'
|
|
1664
|
+
f' variable initial_value: "{value}".')
|
|
1665
|
+
value = myokit.Name(value)
|
|
1666
|
+
else:
|
|
1667
|
+
raise CellMLError(
|
|
1668
|
+
'If given, a variable initial_value must be a real number'
|
|
1669
|
+
f' or the name of a local variable, found "{value}".')
|
|
1670
|
+
|
|
1671
|
+
# Allow expression input
|
|
1672
|
+
elif isinstance(value, myokit.Expression):
|
|
1673
|
+
if is_prefixed_number(value):
|
|
1674
|
+
if self._units.myokit_unit() != value.eval_unit():
|
|
1613
1675
|
raise CellMLError(
|
|
1614
|
-
'If
|
|
1615
|
-
'
|
|
1616
|
-
'
|
|
1676
|
+
'If specified as a myokit.Number, an initial value'
|
|
1677
|
+
' must have the same units as the variable, found'
|
|
1678
|
+
f' {value.eval_unit()} for variable in'
|
|
1679
|
+
f' {self._units.myokit_unit()}.')
|
|
1680
|
+
value = myokit.Number(value.eval(), self._units.myokit_unit())
|
|
1681
|
+
elif isinstance(value, myokit.Name):
|
|
1682
|
+
if value.var()._component is not self._component:
|
|
1683
|
+
raise CellMLError(
|
|
1684
|
+
'Non-local variable specified as variable initial'
|
|
1685
|
+
f' value "{value.var()}".')
|
|
1617
1686
|
else:
|
|
1618
|
-
|
|
1687
|
+
raise CellMLError(
|
|
1688
|
+
'If given, a variable initial_value must be a real number'
|
|
1689
|
+
f' or a local variable, found {type(value)}.')
|
|
1619
1690
|
|
|
1620
|
-
|
|
1691
|
+
# Allow numeric input
|
|
1692
|
+
else:
|
|
1693
|
+
try:
|
|
1694
|
+
value = myokit.Number(value, self._units.myokit_unit())
|
|
1695
|
+
except (ValueError, TypeError):
|
|
1696
|
+
raise CellMLError(
|
|
1697
|
+
'If given, a variable initial_value must be a real number'
|
|
1698
|
+
f' or a local variable, found "{value}".')
|
|
1621
1699
|
|
|
1622
1700
|
# Store
|
|
1623
1701
|
self._cset.set_initial_value(self, value)
|
|
1624
1702
|
|
|
1625
1703
|
def __str__(self):
|
|
1626
1704
|
return (
|
|
1627
|
-
'Variable[@name="
|
|
1705
|
+
f'Variable[@name="{self._name}"] in {self._component}')
|
|
1628
1706
|
|
|
1629
1707
|
def units(self):
|
|
1630
1708
|
"""
|
|
@@ -1705,9 +1783,8 @@ class ConnectedVariableSet:
|
|
|
1705
1783
|
equation_variable = set2._equation_variable
|
|
1706
1784
|
elif set2._equation is not None:
|
|
1707
1785
|
raise CellMLError(
|
|
1708
|
-
'Multiple equations defined in connected variable set:
|
|
1709
|
-
|
|
1710
|
-
+ str(set2._equation_variable) + '.')
|
|
1786
|
+
'Multiple equations defined in connected variable set:'
|
|
1787
|
+
f' {set1._equation_variable} and {set2._equation_variable}.')
|
|
1711
1788
|
|
|
1712
1789
|
# Get initial value
|
|
1713
1790
|
initial_value = set1._initial_value
|
|
@@ -1717,9 +1794,9 @@ class ConnectedVariableSet:
|
|
|
1717
1794
|
initial_value_variable = set2._initial_value_variable
|
|
1718
1795
|
elif set2._initial_value is not None:
|
|
1719
1796
|
raise CellMLError(
|
|
1720
|
-
'Multiple initial values defined in connected variable set:
|
|
1721
|
-
|
|
1722
|
-
|
|
1797
|
+
'Multiple initial values defined in connected variable set:'
|
|
1798
|
+
f' {set1._initial_value_variable} and'
|
|
1799
|
+
f' {set2._initial_value_variable}.')
|
|
1723
1800
|
|
|
1724
1801
|
# Create new set
|
|
1725
1802
|
cset = ConnectedVariableSet()
|
|
@@ -1741,8 +1818,8 @@ class ConnectedVariableSet:
|
|
|
1741
1818
|
if self._equation_variable not in (variable, None):
|
|
1742
1819
|
raise CellMLError(
|
|
1743
1820
|
'Unable to change equation: the equation in this connected'
|
|
1744
|
-
' variable set is already defined by
|
|
1745
|
-
|
|
1821
|
+
' variable set is already defined by'
|
|
1822
|
+
f' {self._equation_variable}.')
|
|
1746
1823
|
|
|
1747
1824
|
# Update
|
|
1748
1825
|
self._equation = equation
|
|
@@ -1750,14 +1827,14 @@ class ConnectedVariableSet:
|
|
|
1750
1827
|
|
|
1751
1828
|
def set_initial_value(self, variable, value=None):
|
|
1752
1829
|
"""
|
|
1753
|
-
Sets the ``initial_value`` for this variable set, as defined
|
|
1830
|
+
Sets the ``initial_value`` for this variable set, as defined in
|
|
1754
1831
|
``variable``.
|
|
1755
1832
|
"""
|
|
1756
1833
|
if self._initial_value_variable not in (variable, None):
|
|
1757
1834
|
raise CellMLError(
|
|
1758
1835
|
'Unable to change initial value: the initial value in this'
|
|
1759
|
-
' connected variable set is already defined by
|
|
1760
|
-
|
|
1836
|
+
' connected variable set is already defined by'
|
|
1837
|
+
f' {self._initial_value_variable}.')
|
|
1761
1838
|
|
|
1762
1839
|
# Update
|
|
1763
1840
|
self._initial_value = value
|
|
@@ -1774,13 +1851,12 @@ class ConnectedVariableSet:
|
|
|
1774
1851
|
if isinstance(self._equation.lhs, myokit.Derivative):
|
|
1775
1852
|
if self._initial_value is None:
|
|
1776
1853
|
raise CellMLError(
|
|
1777
|
-
'No initial value set for state variable
|
|
1778
|
-
|
|
1854
|
+
'No initial value set for state variable'
|
|
1855
|
+
f' {self._equation_variable}.')
|
|
1779
1856
|
elif self._initial_value is not None:
|
|
1780
|
-
msg = 'Overdefined variable:
|
|
1781
|
-
|
|
1857
|
+
msg = (f'Overdefined variable: {self._equation_variable} has both'
|
|
1858
|
+
' a (non-ODE) equation and an initial value')
|
|
1782
1859
|
if self._initial_value_variable is not self._equation_variable:
|
|
1783
|
-
msg += ' (set by
|
|
1784
|
-
|
|
1785
|
-
raise CellMLError(msg)
|
|
1860
|
+
msg += f' (set by {self._initial_value_variable})'
|
|
1861
|
+
raise CellMLError(f'{msg}.')
|
|
1786
1862
|
|