myokit 1.37.5__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.37.5.dist-info → myokit-1.39.0.dist-info}/METADATA +22 -7
- {myokit-1.37.5.dist-info → myokit-1.39.0.dist-info}/RECORD +43 -43
- {myokit-1.37.5.dist-info → myokit-1.39.0.dist-info}/WHEEL +1 -1
- {myokit-1.37.5.dist-info → myokit-1.39.0.dist-info}/entry_points.txt +0 -0
- {myokit-1.37.5.dist-info → myokit-1.39.0.dist-info/licenses}/LICENSE.txt +0 -0
- {myokit-1.37.5.dist-info → myokit-1.39.0.dist-info}/top_level.txt +0 -0
myokit/formats/cellml/v1/_api.py
CHANGED
|
@@ -10,12 +10,14 @@ import warnings
|
|
|
10
10
|
|
|
11
11
|
import myokit
|
|
12
12
|
|
|
13
|
+
from myokit.formats import is_real_number_string
|
|
14
|
+
|
|
13
15
|
|
|
14
16
|
# Identifier validation
|
|
15
17
|
_cellml_identifier = re.compile('^([_][0-9_]*)?[a-zA-Z][a-zA-Z0-9_]*$')
|
|
16
18
|
|
|
17
19
|
|
|
18
|
-
def
|
|
20
|
+
def is_identifier(name):
|
|
19
21
|
"""
|
|
20
22
|
Tests if the given ``name`` is a valid CellML 1.1 identifier.
|
|
21
23
|
|
|
@@ -41,17 +43,17 @@ def clean_identifier(name):
|
|
|
41
43
|
|
|
42
44
|
Raises a ``ValueError`` if it can't create a valid identifier.
|
|
43
45
|
"""
|
|
44
|
-
if
|
|
46
|
+
if is_identifier(name):
|
|
45
47
|
return name
|
|
46
48
|
|
|
47
49
|
# Replace spaces and hyphens with underscores
|
|
48
50
|
clean = re.sub(r'[\s-]', '_', name)
|
|
49
51
|
|
|
50
52
|
# Check if valid and return
|
|
51
|
-
if
|
|
53
|
+
if is_identifier(clean):
|
|
52
54
|
return clean
|
|
53
55
|
raise ValueError(
|
|
54
|
-
'Unable to create a valid CellML identifier from "
|
|
56
|
+
f'Unable to create a valid CellML identifier from "{name}".')
|
|
55
57
|
|
|
56
58
|
|
|
57
59
|
def create_unit_name(unit):
|
|
@@ -63,7 +65,7 @@ def create_unit_name(unit):
|
|
|
63
65
|
name = str(unit)[1:-1]
|
|
64
66
|
|
|
65
67
|
# If this is a valid name, return
|
|
66
|
-
if
|
|
68
|
+
if is_identifier(name):
|
|
67
69
|
return name
|
|
68
70
|
|
|
69
71
|
# Not allowed: could be because of a multiplier, e.g. [m (0.0254)]
|
|
@@ -94,7 +96,7 @@ def create_unit_name(unit):
|
|
|
94
96
|
# Use e-notation if multiple of 10
|
|
95
97
|
multiplier10 = unit.multiplier_log_10()
|
|
96
98
|
if myokit.float.eq(multiplier10, int(multiplier10)):
|
|
97
|
-
multiplier = '1e
|
|
99
|
+
multiplier = f'1e{int(multiplier10)}'
|
|
98
100
|
|
|
99
101
|
# Format as integer
|
|
100
102
|
elif myokit.float.eq(multiplier, int(multiplier)):
|
|
@@ -108,11 +110,21 @@ def create_unit_name(unit):
|
|
|
108
110
|
multiplier = multiplier.replace('+', '')
|
|
109
111
|
multiplier = multiplier.replace('-', '_minus_')
|
|
110
112
|
multiplier = multiplier.replace('.', '_dot_')
|
|
111
|
-
name
|
|
113
|
+
name = f'{name}_times_{multiplier}'
|
|
112
114
|
|
|
113
115
|
return name
|
|
114
116
|
|
|
115
117
|
|
|
118
|
+
def is_prefixed_number(expr):
|
|
119
|
+
"""
|
|
120
|
+
Checks if ``expr`` is a :class:`myokit.Number`, wrapped in any number of
|
|
121
|
+
prefix plus or minus operators.
|
|
122
|
+
"""
|
|
123
|
+
while isinstance(expr, (myokit.PrefixPlus, myokit.PrefixMinus)):
|
|
124
|
+
expr = expr[0]
|
|
125
|
+
return isinstance(expr, myokit.Number)
|
|
126
|
+
|
|
127
|
+
|
|
116
128
|
class AnnotatableElement:
|
|
117
129
|
"""
|
|
118
130
|
Represents a CellML 1.0 or 1.1 element that can have a cmeta:id.
|
|
@@ -185,7 +197,7 @@ class UnsupportedBaseUnitsError(UnitsError):
|
|
|
185
197
|
def __init__(self, units):
|
|
186
198
|
self.units = units
|
|
187
199
|
super().__init__(
|
|
188
|
-
'Unsupported base units "
|
|
200
|
+
f'Unsupported base units "{units}".')
|
|
189
201
|
|
|
190
202
|
|
|
191
203
|
class UnsupportedUnitOffsetError(UnitsError):
|
|
@@ -209,7 +221,7 @@ class Component(AnnotatableElement):
|
|
|
209
221
|
self._model = model
|
|
210
222
|
|
|
211
223
|
# Check and store name
|
|
212
|
-
if not
|
|
224
|
+
if not is_identifier(name):
|
|
213
225
|
raise CellMLError(
|
|
214
226
|
'Component name must be a valid CellML identifier (3.4.2.2).')
|
|
215
227
|
self._name = name
|
|
@@ -373,7 +385,7 @@ class Component(AnnotatableElement):
|
|
|
373
385
|
parent._children.add(self)
|
|
374
386
|
|
|
375
387
|
def __str__(self):
|
|
376
|
-
return 'Component[@name="
|
|
388
|
+
return f'Component[@name="{self._name}"]'
|
|
377
389
|
|
|
378
390
|
def units(self):
|
|
379
391
|
"""
|
|
@@ -419,7 +431,7 @@ class Component(AnnotatableElement):
|
|
|
419
431
|
' with Model.set_free_variable().')
|
|
420
432
|
if not has_free:
|
|
421
433
|
raise CellMLError(
|
|
422
|
-
|
|
434
|
+
f'{self} has state variables, but no local variable'
|
|
423
435
|
' connected to the free variable.')
|
|
424
436
|
|
|
425
437
|
def variable(self, name):
|
|
@@ -451,14 +463,14 @@ class Model(AnnotatableElement):
|
|
|
451
463
|
|
|
452
464
|
Support notes for 1.1:
|
|
453
465
|
|
|
454
|
-
- The new feature of using variables in ``initial_value`` attributes is not
|
|
455
|
-
supported.
|
|
456
466
|
- Imports (CellML 1.1) are not supported.
|
|
467
|
+
- Initial values can be local variable names, as long as those variables
|
|
468
|
+
are constants (or depend only on constants).
|
|
457
469
|
|
|
458
470
|
Support notes for 1.0:
|
|
459
471
|
|
|
460
472
|
- The stricter 1.1 rule for identifiers is used for both CellML 1.0 and
|
|
461
|
-
1.1: a valid identifier not start with a number, and must contain at
|
|
473
|
+
1.1: a valid identifier may not start with a number, and must contain at
|
|
462
474
|
least one letter.
|
|
463
475
|
|
|
464
476
|
Arguments:
|
|
@@ -470,11 +482,11 @@ class Model(AnnotatableElement):
|
|
|
470
482
|
'1.0' or '1.1').
|
|
471
483
|
|
|
472
484
|
"""
|
|
473
|
-
def __init__(self, name, version='1.
|
|
485
|
+
def __init__(self, name, version='1.1'):
|
|
474
486
|
super().__init__(self)
|
|
475
487
|
|
|
476
488
|
# Check and store name
|
|
477
|
-
if not
|
|
489
|
+
if not is_identifier(name):
|
|
478
490
|
raise CellMLError(
|
|
479
491
|
'Model name must be a valid CellML identifier (3.4.1.2).')
|
|
480
492
|
self._name = name
|
|
@@ -524,17 +536,17 @@ class Model(AnnotatableElement):
|
|
|
524
536
|
"""
|
|
525
537
|
# Check both are variables, and from this model
|
|
526
538
|
if not isinstance(variable_1, Variable):
|
|
527
|
-
raise ValueError(
|
|
528
|
-
|
|
539
|
+
raise ValueError(
|
|
540
|
+
'Argument variable_1 must be a cellml.v1.Variable.')
|
|
529
541
|
if not isinstance(variable_2, Variable):
|
|
530
|
-
raise ValueError(
|
|
531
|
-
|
|
542
|
+
raise ValueError(
|
|
543
|
+
'Argument variable_2 must be a cellml.v1.Variable.')
|
|
532
544
|
if variable_1._model is not self:
|
|
533
|
-
raise ValueError(
|
|
534
|
-
|
|
545
|
+
raise ValueError(
|
|
546
|
+
'Argument variable_1 must be a variable from this model.')
|
|
535
547
|
if variable_2._model is not self:
|
|
536
|
-
raise ValueError(
|
|
537
|
-
|
|
548
|
+
raise ValueError(
|
|
549
|
+
'Argument variable_2 must be a variable from this model.')
|
|
538
550
|
|
|
539
551
|
# Check variables are distinct
|
|
540
552
|
if variable_1 is variable_2:
|
|
@@ -573,31 +585,30 @@ class Model(AnnotatableElement):
|
|
|
573
585
|
variable_2._source = variable_1
|
|
574
586
|
elif variable_2._source is variable_1:
|
|
575
587
|
raise CellMLError(
|
|
576
|
-
'Invalid connection:
|
|
577
|
-
'
|
|
588
|
+
f'Invalid connection: {variable_2} is already connected to'
|
|
589
|
+
f' {variable_1}.')
|
|
578
590
|
else:
|
|
579
591
|
raise CellMLError(
|
|
580
|
-
'Invalid connection:
|
|
581
|
-
|
|
592
|
+
f'Invalid connection: {variable_2} has a'
|
|
593
|
+
f' {string_1}_interface of "in" and is already connected'
|
|
582
594
|
' to a variable with an interface of "out".')
|
|
583
595
|
elif interface_1 == 'in' and interface_2 == 'out':
|
|
584
596
|
if variable_1._source is None:
|
|
585
597
|
variable_1._source = variable_2
|
|
586
598
|
elif variable_1._source is variable_2:
|
|
587
599
|
raise CellMLError(
|
|
588
|
-
'Invalid connection:
|
|
589
|
-
'
|
|
600
|
+
f'Invalid connection: {variable_1} is already connected to'
|
|
601
|
+
f' {variable_2}.')
|
|
590
602
|
else:
|
|
591
603
|
raise CellMLError(
|
|
592
|
-
'Invalid connection:
|
|
593
|
-
|
|
604
|
+
f'Invalid connection: {variable_1} has a'
|
|
605
|
+
f' {string_2}_interface of "in" and is already connected'
|
|
594
606
|
' to a variable with an interface of "out".')
|
|
595
607
|
else:
|
|
596
608
|
raise CellMLError(
|
|
597
|
-
'Invalid connection:
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
+ interface_2 + '" (3.4.6.4).')
|
|
609
|
+
f'Invalid connection: {variable_1} has a {string_1}_interface'
|
|
610
|
+
f' of "{interface_1}", while {variable_2} has a'
|
|
611
|
+
f' {string_2}_interface of "{interface_2}" (3.4.6.4).')
|
|
601
612
|
|
|
602
613
|
def add_units(self, name, myokit_unit):
|
|
603
614
|
"""
|
|
@@ -679,7 +690,7 @@ class Model(AnnotatableElement):
|
|
|
679
690
|
return self._free_variable
|
|
680
691
|
|
|
681
692
|
@staticmethod
|
|
682
|
-
def from_myokit_model(model, version='1.
|
|
693
|
+
def from_myokit_model(model, version='1.1'):
|
|
683
694
|
"""
|
|
684
695
|
Creates a CellML :class:`Model` from a :class:`myokit.Model`.
|
|
685
696
|
|
|
@@ -703,6 +714,39 @@ class Model(AnnotatableElement):
|
|
|
703
714
|
# Create CellML model
|
|
704
715
|
m = Model(name, version)
|
|
705
716
|
|
|
717
|
+
# For version 1.1 only, check if we need to create a Myokit model where
|
|
718
|
+
# all initial values are either numbers or names of local variables
|
|
719
|
+
# For version 1.0, anything that's not a number will be converted to a
|
|
720
|
+
# literal at a later stage
|
|
721
|
+
if version == '1.1':
|
|
722
|
+
to_fix = []
|
|
723
|
+
for state in model.states():
|
|
724
|
+
e = state.initial_value()
|
|
725
|
+
if isinstance(e, myokit.Number):
|
|
726
|
+
continue
|
|
727
|
+
if isinstance(e, myokit.Name):
|
|
728
|
+
# Compare parents: note, nested variables can't be
|
|
729
|
+
# referenced here, so this check is sufficient
|
|
730
|
+
if e.var().parent() == state.parent():
|
|
731
|
+
continue
|
|
732
|
+
to_fix.append(state.qname())
|
|
733
|
+
|
|
734
|
+
if to_fix:
|
|
735
|
+
model = model.clone()
|
|
736
|
+
for state in to_fix:
|
|
737
|
+
state = model.get(state)
|
|
738
|
+
value = state.initial_value()
|
|
739
|
+
if is_prefixed_number(value):
|
|
740
|
+
# Don't make variables for x = -1
|
|
741
|
+
state.set_initial_value(value.eval())
|
|
742
|
+
else:
|
|
743
|
+
# But do for `1 + exp(3)`, or `a + b`
|
|
744
|
+
init_var = state.parent().add_variable_allow_renaming(
|
|
745
|
+
state.name() + '_init')
|
|
746
|
+
init_var.set_rhs(value)
|
|
747
|
+
init_var.set_unit(state.unit())
|
|
748
|
+
state.set_initial_value(init_var.lhs())
|
|
749
|
+
|
|
706
750
|
# Valid model always has a time variable
|
|
707
751
|
time = model.time()
|
|
708
752
|
|
|
@@ -777,7 +821,7 @@ class Model(AnnotatableElement):
|
|
|
777
821
|
in_variables = {component: set() for component in model}
|
|
778
822
|
|
|
779
823
|
# Dict mapping Myokit variables to CellML variables, per component
|
|
780
|
-
var_map = {component:
|
|
824
|
+
var_map = {component: {} for component in model}
|
|
781
825
|
|
|
782
826
|
# Add components
|
|
783
827
|
for component in model:
|
|
@@ -792,7 +836,7 @@ class Model(AnnotatableElement):
|
|
|
792
836
|
# Check if this variable is needed in other components
|
|
793
837
|
interface = 'none'
|
|
794
838
|
|
|
795
|
-
# Get all refs to variable's LHS
|
|
839
|
+
# Get all refs to this variable's LHS
|
|
796
840
|
refs = set(variable.refs_by())
|
|
797
841
|
if variable.is_state():
|
|
798
842
|
# If it's a state, refs to dot(x) also require the time
|
|
@@ -895,11 +939,27 @@ class Model(AnnotatableElement):
|
|
|
895
939
|
# Promote states and set rhs and initial value
|
|
896
940
|
elif variable.is_state():
|
|
897
941
|
v.set_is_state(True)
|
|
898
|
-
v.set_initial_value(variable.initial_value(True))
|
|
899
942
|
v.set_rhs(rhs)
|
|
900
943
|
|
|
944
|
+
init = variable.initial_value()
|
|
945
|
+
if is_prefixed_number(init):
|
|
946
|
+
v.set_initial_value(init.eval())
|
|
947
|
+
elif version == '1.0':
|
|
948
|
+
# In 1.0, evaluate any other expression, warn if
|
|
949
|
+
# variables are involved
|
|
950
|
+
if not init.is_literal():
|
|
951
|
+
warnings.warn(
|
|
952
|
+
f'Incompatible expression "{init}", of type'
|
|
953
|
+
f' {type(init)} specified for initial value of'
|
|
954
|
+
f' {variable}, replacing by its evaluation'
|
|
955
|
+
f' "{init.eval()}".')
|
|
956
|
+
v.set_initial_value(myokit.Number(init.eval()))
|
|
957
|
+
else:
|
|
958
|
+
assert isinstance(init, myokit.Name)
|
|
959
|
+
v.set_initial_value(subst[init])
|
|
960
|
+
|
|
901
961
|
# Store literals (single number) in initial value
|
|
902
|
-
elif
|
|
962
|
+
elif is_prefixed_number(rhs):
|
|
903
963
|
v.set_initial_value(rhs.eval())
|
|
904
964
|
|
|
905
965
|
# For all other use rhs
|
|
@@ -927,7 +987,7 @@ class Model(AnnotatableElement):
|
|
|
927
987
|
|
|
928
988
|
# Create model
|
|
929
989
|
m = myokit.Model(self.name())
|
|
930
|
-
m.meta['
|
|
990
|
+
m.meta['mmt_authors'] = 'Myokit CellML 1 API'
|
|
931
991
|
|
|
932
992
|
# Copy meta data
|
|
933
993
|
for key, value in self.meta.items():
|
|
@@ -1021,8 +1081,12 @@ class Model(AnnotatableElement):
|
|
|
1021
1081
|
|
|
1022
1082
|
v.set_rhs(rhs)
|
|
1023
1083
|
if variable.is_state():
|
|
1084
|
+
# Note: In this case, rhs_or_initial_value() always
|
|
1085
|
+
# returns the RHS, so ``rhs`` is guaranteed not to
|
|
1086
|
+
# be the initial value
|
|
1024
1087
|
init = variable.initial_value()
|
|
1025
|
-
v.promote(0 if init is None else
|
|
1088
|
+
v.promote(0 if init is None else
|
|
1089
|
+
init.clone(subst=var_map))
|
|
1026
1090
|
|
|
1027
1091
|
# Add local copies of variables requiring unit conversion
|
|
1028
1092
|
elif variable in needs_conversion:
|
|
@@ -1040,9 +1104,8 @@ class Model(AnnotatableElement):
|
|
|
1040
1104
|
f = myokit.Number(f)
|
|
1041
1105
|
except myokit.IncompatibleUnitError:
|
|
1042
1106
|
warnings.warn(
|
|
1043
|
-
'Unable to determine unit conversion factor for
|
|
1044
|
-
|
|
1045
|
-
+ str(r.unit()) + '.')
|
|
1107
|
+
'Unable to determine unit conversion factor for'
|
|
1108
|
+
f' {v}, from {v.unit()} to {r.unit()}.')
|
|
1046
1109
|
f = myokit.Number(1)
|
|
1047
1110
|
|
|
1048
1111
|
# Add equation
|
|
@@ -1109,7 +1172,7 @@ class Model(AnnotatableElement):
|
|
|
1109
1172
|
variable._is_free = True
|
|
1110
1173
|
|
|
1111
1174
|
def __str__(self):
|
|
1112
|
-
return 'Model[@name="
|
|
1175
|
+
return f'Model[@name="{self._name}"]'
|
|
1113
1176
|
|
|
1114
1177
|
def units(self):
|
|
1115
1178
|
"""
|
|
@@ -1151,9 +1214,9 @@ class Model(AnnotatableElement):
|
|
|
1151
1214
|
free = free.pop()
|
|
1152
1215
|
if self._free_variable is not free:
|
|
1153
1216
|
warnings.warn(
|
|
1154
|
-
'No value is defined for the variable "'
|
|
1155
|
-
|
|
1156
|
-
|
|
1217
|
+
f'No value is defined for the variable "{free.name()}",'
|
|
1218
|
+
f' but "{self._free_variable.name()}" is listed as the'
|
|
1219
|
+
' free variable.')
|
|
1157
1220
|
|
|
1158
1221
|
def version(self):
|
|
1159
1222
|
"""
|
|
@@ -1181,12 +1244,12 @@ class Units:
|
|
|
1181
1244
|
def __init__(self, name, myokit_unit, predefined=False):
|
|
1182
1245
|
|
|
1183
1246
|
# Check and store name
|
|
1184
|
-
if not
|
|
1247
|
+
if not is_identifier(name):
|
|
1185
1248
|
raise CellMLError(
|
|
1186
1249
|
'Units name must be a valid CellML identifier (5.4.1.2).')
|
|
1187
1250
|
if not predefined and name in self._si_units:
|
|
1188
1251
|
raise CellMLError(
|
|
1189
|
-
'Units name "
|
|
1252
|
+
f'Units name "{name}" overlaps with a predefined name'
|
|
1190
1253
|
' (5.4.1.2).')
|
|
1191
1254
|
self._name = name
|
|
1192
1255
|
|
|
@@ -1218,7 +1281,7 @@ class Units:
|
|
|
1218
1281
|
# If not raise error (and one that makes sense even if this was
|
|
1219
1282
|
# called via a model or component units lookup).
|
|
1220
1283
|
if myokit_unit is None:
|
|
1221
|
-
raise CellMLError('Unknown units name "
|
|
1284
|
+
raise CellMLError(f'Unknown units name "{name}".')
|
|
1222
1285
|
|
|
1223
1286
|
# Create and store object
|
|
1224
1287
|
obj = cls(name, myokit_unit, predefined=True)
|
|
@@ -1237,8 +1300,7 @@ class Units:
|
|
|
1237
1300
|
try:
|
|
1238
1301
|
return cls._si_units_r[myokit_unit]
|
|
1239
1302
|
except KeyError:
|
|
1240
|
-
raise CellMLError(
|
|
1241
|
-
'No name found for myokit unit ' + str(myokit_unit) + '.')
|
|
1303
|
+
raise CellMLError(f'No name found for myokit unit {myokit_unit}.')
|
|
1242
1304
|
|
|
1243
1305
|
def myokit_unit(self):
|
|
1244
1306
|
"""
|
|
@@ -1308,7 +1370,7 @@ class Units:
|
|
|
1308
1370
|
|
|
1309
1371
|
# float(10**309) is the first int that doesn't fit in a float
|
|
1310
1372
|
if p > 309:
|
|
1311
|
-
raise CellMLError('Unit prefix too large: 10^'
|
|
1373
|
+
raise CellMLError(f'Unit prefix too large: 10^{p}')
|
|
1312
1374
|
unit *= 10**int(p)
|
|
1313
1375
|
|
|
1314
1376
|
# Handle exponent (note: prefix is exponentiated, multiplier is not).
|
|
@@ -1344,7 +1406,7 @@ class Units:
|
|
|
1344
1406
|
return cls._si_units.keys()
|
|
1345
1407
|
|
|
1346
1408
|
def __str__(self):
|
|
1347
|
-
return 'Units[@name="
|
|
1409
|
+
return f'Units[@name="{self._name}"]'
|
|
1348
1410
|
|
|
1349
1411
|
# Predefined units in CellML, name to Unit
|
|
1350
1412
|
_si_units = {
|
|
@@ -1473,7 +1535,7 @@ class Variable(AnnotatableElement):
|
|
|
1473
1535
|
self._component = component
|
|
1474
1536
|
|
|
1475
1537
|
# Check and store name
|
|
1476
|
-
if not
|
|
1538
|
+
if not is_identifier(name):
|
|
1477
1539
|
raise CellMLError(
|
|
1478
1540
|
'Variable name must be a valid CellML identifier (3.4.3.2).')
|
|
1479
1541
|
self._name = name
|
|
@@ -1484,12 +1546,12 @@ class Variable(AnnotatableElement):
|
|
|
1484
1546
|
except UnsupportedBaseUnitsError as e:
|
|
1485
1547
|
raise UnsupportedBaseUnitsError(
|
|
1486
1548
|
'Variable units attribute references the unsupported base'
|
|
1487
|
-
' units "
|
|
1549
|
+
f' units "{e.units}".')
|
|
1488
1550
|
except CellMLError:
|
|
1489
1551
|
raise CellMLError(
|
|
1490
1552
|
'Variable units attribute must reference a units element in'
|
|
1491
1553
|
' the current component or model, or one of the predefined'
|
|
1492
|
-
' units, found "
|
|
1554
|
+
f' units, found "{units}" (3.4.3.3).')
|
|
1493
1555
|
|
|
1494
1556
|
# Check and store interfaces
|
|
1495
1557
|
if public_interface not in ['none', 'in', 'out']:
|
|
@@ -1579,26 +1641,30 @@ class Variable(AnnotatableElement):
|
|
|
1579
1641
|
|
|
1580
1642
|
def rhs(self):
|
|
1581
1643
|
"""
|
|
1582
|
-
Returns this variable's right-hand side.
|
|
1644
|
+
Returns this variable's right-hand side expression, if set.
|
|
1583
1645
|
"""
|
|
1584
1646
|
return self._rhs
|
|
1585
1647
|
|
|
1586
1648
|
def rhs_or_initial_value(self):
|
|
1587
1649
|
"""
|
|
1588
|
-
For non-states, returns this variable's RHS or
|
|
1589
|
-
|
|
1590
|
-
returns the RHS.
|
|
1650
|
+
For non-states, returns this variable's RHS or its initial value if no
|
|
1651
|
+
RHS is set. For states always returns the RHS.
|
|
1591
1652
|
"""
|
|
1592
1653
|
if self._is_state:
|
|
1593
1654
|
return self._rhs
|
|
1594
1655
|
if self._rhs is None and self._initial_value is not None:
|
|
1595
|
-
return
|
|
1596
|
-
self._initial_value, self._units.myokit_unit())
|
|
1656
|
+
return self._initial_value
|
|
1597
1657
|
return self._rhs
|
|
1598
1658
|
|
|
1599
1659
|
def set_initial_value(self, value):
|
|
1600
1660
|
"""
|
|
1601
|
-
Sets this variable's intial value
|
|
1661
|
+
Sets this variable's intial value.
|
|
1662
|
+
|
|
1663
|
+
In CellML 1.0, this must be a :class:`myokit.Number` or ``None``. In
|
|
1664
|
+
CellML 1.1 it can also be a :class:`myokit.Name` referencing a variable
|
|
1665
|
+
in the same component.
|
|
1666
|
+
|
|
1667
|
+
To stay close to the specification, numbers are stored without units.
|
|
1602
1668
|
"""
|
|
1603
1669
|
# Allow unsetting with ``None``
|
|
1604
1670
|
if value is None:
|
|
@@ -1609,21 +1675,51 @@ class Variable(AnnotatableElement):
|
|
|
1609
1675
|
if self._public_interface == 'in' or self._private_interface == 'in':
|
|
1610
1676
|
i = 'public' if self._public_interface == 'in' else 'private'
|
|
1611
1677
|
raise CellMLError(
|
|
1612
|
-
'An initial value cannot be set for
|
|
1613
|
-
'
|
|
1614
|
-
|
|
1615
|
-
#
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1678
|
+
f'An initial value cannot be set for {self}, which has'
|
|
1679
|
+
f' {i}_interface="in" (3.4.3.8).')
|
|
1680
|
+
|
|
1681
|
+
# Reusable error
|
|
1682
|
+
e10 = ('In CellML 1.0, an initial value (if set) must be a real number'
|
|
1683
|
+
f' (3.4.3.7), found "{value}".')
|
|
1684
|
+
e11 = ('Initial value (if set) must be a real number or a variable'
|
|
1685
|
+
f' from the same component (3.4.3.7), found "{value}".')
|
|
1686
|
+
|
|
1687
|
+
# Allow string input
|
|
1688
|
+
if isinstance(value, str):
|
|
1689
|
+
if is_real_number_string(value):
|
|
1690
|
+
value = myokit.Number(value)
|
|
1691
|
+
elif is_identifier(value):
|
|
1692
|
+
if self._model.version() == '1.0':
|
|
1693
|
+
raise CellMLError(e10)
|
|
1694
|
+
try:
|
|
1695
|
+
value = self._component.variable(value)
|
|
1696
|
+
except KeyError:
|
|
1697
|
+
raise CellMLError(e11)
|
|
1698
|
+
value = myokit.Name(value)
|
|
1623
1699
|
else:
|
|
1624
|
-
raise CellMLError(
|
|
1625
|
-
|
|
1626
|
-
|
|
1700
|
+
raise CellMLError(e11)
|
|
1701
|
+
|
|
1702
|
+
# Allow expression input
|
|
1703
|
+
elif isinstance(value, myokit.Expression):
|
|
1704
|
+
if is_prefixed_number(value):
|
|
1705
|
+
value = myokit.Number(value.eval())
|
|
1706
|
+
elif isinstance(value, myokit.Name):
|
|
1707
|
+
if self._model.version() == '1.0':
|
|
1708
|
+
raise CellMLError(e10)
|
|
1709
|
+
if value.var()._component is not self._component:
|
|
1710
|
+
raise CellMLError(e11)
|
|
1711
|
+
else:
|
|
1712
|
+
raise CellMLError(e11)
|
|
1713
|
+
|
|
1714
|
+
# Allow numeric input
|
|
1715
|
+
else:
|
|
1716
|
+
try:
|
|
1717
|
+
value = myokit.Number(float(value))
|
|
1718
|
+
except (ValueError, TypeError):
|
|
1719
|
+
raise CellMLError(e11)
|
|
1720
|
+
|
|
1721
|
+
# Store
|
|
1722
|
+
self._initial_value = value
|
|
1627
1723
|
|
|
1628
1724
|
def set_is_state(self, state):
|
|
1629
1725
|
"""
|
|
@@ -1631,7 +1727,6 @@ class Variable(AnnotatableElement):
|
|
|
1631
1727
|
"""
|
|
1632
1728
|
# Check interface
|
|
1633
1729
|
if self._public_interface == 'in' or self._private_interface == 'in':
|
|
1634
|
-
i = 'public' if self._public_interface == 'in' else 'private'
|
|
1635
1730
|
raise CellMLError(
|
|
1636
1731
|
'State variables can not have an "in" interface.')
|
|
1637
1732
|
|
|
@@ -1641,43 +1736,50 @@ class Variable(AnnotatableElement):
|
|
|
1641
1736
|
"""
|
|
1642
1737
|
Sets a right-hand side expression for this variable.
|
|
1643
1738
|
|
|
1644
|
-
The given ``rhs`` must be a :class:`myokit.Expression`
|
|
1739
|
+
The given ``rhs`` must be a :class:`myokit.Expression` where any
|
|
1645
1740
|
:class:`myokit.Name` objects have a CellML :class:`Variable` as their
|
|
1646
1741
|
value.
|
|
1647
1742
|
"""
|
|
1743
|
+
# Type check and unsetting
|
|
1744
|
+
if rhs is None:
|
|
1745
|
+
self._rhs = None
|
|
1746
|
+
return
|
|
1747
|
+
if not isinstance(rhs, myokit.Expression):
|
|
1748
|
+
raise ValueError(
|
|
1749
|
+
'RHS expressions must be specified as myokit.Expression'
|
|
1750
|
+
f' objects, but got {type(rhs)}.')
|
|
1751
|
+
|
|
1648
1752
|
# Check interface
|
|
1649
1753
|
if self._public_interface == 'in' or self._private_interface == 'in':
|
|
1650
1754
|
i = 'public' if self._public_interface == 'in' else 'private'
|
|
1651
1755
|
raise CellMLError(
|
|
1652
|
-
'An equation cannot be set for
|
|
1653
|
-
|
|
1756
|
+
f'An equation cannot be set for {self}, which has'
|
|
1757
|
+
f' {i}_interface="in" (4.4.4).')
|
|
1654
1758
|
|
|
1655
1759
|
# Check all references in equation are known and local
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1760
|
+
for ref in rhs.references():
|
|
1761
|
+
var = ref.var()
|
|
1762
|
+
if var._component is not self._component:
|
|
1763
|
+
raise CellMLError(
|
|
1764
|
+
'A variable RHS can only reference variables from the'
|
|
1765
|
+
f' same component, found: {var}.')
|
|
1766
|
+
|
|
1767
|
+
# Check all units are known
|
|
1768
|
+
numbers_without_units = {}
|
|
1769
|
+
for x in rhs.walk(myokit.Number):
|
|
1770
|
+
# Replace None with dimensionless
|
|
1771
|
+
if x.unit() is None:
|
|
1772
|
+
numbers_without_units[x] = myokit.Number(
|
|
1773
|
+
x.eval(), myokit.units.dimensionless)
|
|
1774
|
+
else:
|
|
1775
|
+
try:
|
|
1776
|
+
self._component.find_units_name(x.unit())
|
|
1777
|
+
except CellMLError:
|
|
1660
1778
|
raise CellMLError(
|
|
1661
|
-
'
|
|
1662
|
-
'
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
numbers_without_units = {}
|
|
1666
|
-
for x in rhs.walk(myokit.Number):
|
|
1667
|
-
# Replace None with dimensionless
|
|
1668
|
-
if x.unit() is None:
|
|
1669
|
-
numbers_without_units[x] = myokit.Number(
|
|
1670
|
-
x.eval(), myokit.units.dimensionless)
|
|
1671
|
-
else:
|
|
1672
|
-
try:
|
|
1673
|
-
self._component.find_units_name(x.unit())
|
|
1674
|
-
except CellMLError:
|
|
1675
|
-
raise CellMLError(
|
|
1676
|
-
'All units appearing in a variable\'s RHS must be'
|
|
1677
|
-
' known to its component, found: ' + str(x.unit())
|
|
1678
|
-
+ '.')
|
|
1679
|
-
if numbers_without_units:
|
|
1680
|
-
rhs = rhs.clone(subst=numbers_without_units)
|
|
1779
|
+
'All units appearing in a variable\'s RHS must be'
|
|
1780
|
+
f' known to its component, found: {x.unit()}.')
|
|
1781
|
+
if numbers_without_units:
|
|
1782
|
+
rhs = rhs.clone(subst=numbers_without_units)
|
|
1681
1783
|
|
|
1682
1784
|
# Store
|
|
1683
1785
|
self._rhs = rhs
|
|
@@ -1692,8 +1794,7 @@ class Variable(AnnotatableElement):
|
|
|
1692
1794
|
return self._source
|
|
1693
1795
|
|
|
1694
1796
|
def __str__(self):
|
|
1695
|
-
return (
|
|
1696
|
-
'Variable[@name="' + self._name + '"] in ' + str(self._component))
|
|
1797
|
+
return (f'Variable[@name="{self._name}"] in {self._component}')
|
|
1697
1798
|
|
|
1698
1799
|
def units(self):
|
|
1699
1800
|
"""
|
|
@@ -1709,33 +1810,30 @@ class Variable(AnnotatableElement):
|
|
|
1709
1810
|
# Check that variables with an in interface are connected
|
|
1710
1811
|
# Sort of allowed in the spec ?
|
|
1711
1812
|
if self._public_interface == 'in' or self._private_interface == 'in':
|
|
1712
|
-
i = 'public' if self._public_interface == 'in' else 'private'
|
|
1713
1813
|
if self._source is None:
|
|
1814
|
+
i = 'public' if self._public_interface == 'in' else 'private'
|
|
1714
1815
|
warnings.warn(
|
|
1715
|
-
|
|
1716
|
-
'
|
|
1816
|
+
f'{self} has {i}_interface="in", but is not connected to a'
|
|
1817
|
+
' variable with an appropriate "out"')
|
|
1717
1818
|
|
|
1718
1819
|
# Check that state variables define two values
|
|
1719
1820
|
elif self._is_state:
|
|
1720
|
-
|
|
1721
1821
|
if self._initial_value is None:
|
|
1722
|
-
warnings.warn(
|
|
1723
|
-
'State ' + str(self) + ' has no initial value.')
|
|
1724
|
-
|
|
1822
|
+
warnings.warn(f'State {self} has no initial value.')
|
|
1725
1823
|
if self._rhs is None:
|
|
1726
1824
|
raise CellMLError(
|
|
1727
|
-
'State
|
|
1825
|
+
f'State {self} must have a defining equation.')
|
|
1728
1826
|
|
|
1729
1827
|
# Check that other variables define a value
|
|
1730
1828
|
elif self._rhs is None:
|
|
1731
1829
|
if self._initial_value is None and not self._is_free:
|
|
1732
|
-
warnings.warn('No value set for
|
|
1830
|
+
warnings.warn(f'No value set for {self}.')
|
|
1733
1831
|
|
|
1734
1832
|
# And only one value
|
|
1735
1833
|
elif self._initial_value is not None:
|
|
1736
1834
|
raise CellMLError(
|
|
1737
|
-
'Overdefined:
|
|
1738
|
-
'
|
|
1835
|
+
f'Overdefined: {self} has both an initial value and a defining'
|
|
1836
|
+
' equation which is not an ODE.')
|
|
1739
1837
|
|
|
1740
1838
|
def value_source(self):
|
|
1741
1839
|
"""
|