myokit 1.38.0__py3-none-any.whl → 1.39.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 (44) hide show
  1. myokit/__init__.py +5 -0
  2. myokit/_config.py +18 -19
  3. myokit/_datablock.py +6 -5
  4. myokit/_expressions.py +6 -1
  5. myokit/_model_api.py +44 -18
  6. myokit/_myokit_version.py +1 -1
  7. myokit/_parsing.py +8 -2
  8. myokit/_sim/cvodessim.py +26 -0
  9. myokit/formats/__init__.py +37 -0
  10. myokit/formats/ansic/_ewriter.py +1 -1
  11. myokit/formats/axon/_abf.py +43 -9
  12. myokit/formats/cellml/v1/__init__.py +5 -5
  13. myokit/formats/cellml/v1/_api.py +220 -122
  14. myokit/formats/cellml/v1/_parser.py +91 -87
  15. myokit/formats/cellml/v1/_writer.py +13 -6
  16. myokit/formats/cellml/v2/__init__.py +5 -8
  17. myokit/formats/cellml/v2/_api.py +182 -106
  18. myokit/formats/cellml/v2/_parser.py +68 -64
  19. myokit/formats/cellml/v2/_writer.py +7 -3
  20. myokit/formats/heka/_patchmaster.py +71 -14
  21. myokit/formats/mathml/_parser.py +106 -67
  22. myokit/gui/source.py +18 -12
  23. myokit/lib/hh.py +21 -37
  24. myokit/tests/test_cellml_v1_api.py +227 -33
  25. myokit/tests/test_cellml_v1_parser.py +48 -17
  26. myokit/tests/test_cellml_v1_writer.py +14 -4
  27. myokit/tests/test_cellml_v2_api.py +132 -114
  28. myokit/tests/test_cellml_v2_parser.py +31 -1
  29. myokit/tests/test_cellml_v2_writer.py +8 -1
  30. myokit/tests/test_datalog.py +17 -0
  31. myokit/tests/test_expressions.py +61 -0
  32. myokit/tests/test_formats.py +99 -0
  33. myokit/tests/test_formats_mathml_content.py +97 -37
  34. myokit/tests/test_formats_python.py +1 -1
  35. myokit/tests/test_model_building.py +2 -0
  36. myokit/tests/test_parsing.py +32 -0
  37. myokit/tests/test_simulation_cvodes.py +10 -4
  38. myokit/tests/test_variable.py +10 -7
  39. {myokit-1.38.0.dist-info → myokit-1.39.1.dist-info}/METADATA +22 -7
  40. {myokit-1.38.0.dist-info → myokit-1.39.1.dist-info}/RECORD +44 -44
  41. {myokit-1.38.0.dist-info → myokit-1.39.1.dist-info}/WHEEL +1 -1
  42. {myokit-1.38.0.dist-info → myokit-1.39.1.dist-info}/entry_points.txt +0 -0
  43. {myokit-1.38.0.dist-info → myokit-1.39.1.dist-info/licenses}/LICENSE.txt +0 -0
  44. {myokit-1.38.0.dist-info → myokit-1.39.1.dist-info}/top_level.txt +0 -0
@@ -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 is_valid_identifier(name):
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 is_valid_identifier(name):
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 is_valid_identifier(clean):
53
+ if is_identifier(clean):
52
54
  return clean
53
55
  raise ValueError(
54
- 'Unable to create a valid CellML identifier from "' + str(name) + '".')
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 is_valid_identifier(name):
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' + str(int(multiplier10))
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 += '_times_' + multiplier
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 "' + 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 is_valid_identifier(name):
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="' + self._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
- str(self) + ' has state variables, but no local variable'
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.0'):
485
+ def __init__(self, name, version='1.1'):
474
486
  super().__init__(self)
475
487
 
476
488
  # Check and store name
477
- if not is_valid_identifier(name):
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('Argument variable_1 must be a'
528
- ' cellml.v1.Variable.')
539
+ raise ValueError(
540
+ 'Argument variable_1 must be a cellml.v1.Variable.')
529
541
  if not isinstance(variable_2, Variable):
530
- raise ValueError('Argument variable_2 must be a'
531
- ' cellml.v1.Variable.')
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('Argument variable_1 must be a variable from this'
534
- ' model.')
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('Argument variable_2 must be a variable from this'
537
- ' model.')
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: ' + str(variable_2) + ' is already'
577
- ' connected to ' + str(variable_1) + '.')
588
+ f'Invalid connection: {variable_2} is already connected to'
589
+ f' {variable_1}.')
578
590
  else:
579
591
  raise CellMLError(
580
- 'Invalid connection: ' + str(variable_2) + ' has a '
581
- + string_1 + '_interface of "in" and is already connected'
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: ' + str(variable_1) + ' is already'
589
- ' connected to ' + str(variable_2) + '.')
600
+ f'Invalid connection: {variable_1} is already connected to'
601
+ f' {variable_2}.')
590
602
  else:
591
603
  raise CellMLError(
592
- 'Invalid connection: ' + str(variable_1) + ' has a '
593
- + string_2 + '_interface of "in" and is already connected'
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: ' + str(variable_1) + ' has a ' + string_1
598
- + '_interface of "' + interface_1 + '", while '
599
- + str(variable_2) + ' has a ' + string_2 + '_interface of "'
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.0'):
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: dict() for component in model}
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 isinstance(rhs, myokit.Number):
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['author'] = 'Myokit CellML 1 API'
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 init)
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
- + str(v) + ', from ' + str(v.unit()) + ' to '
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="' + self._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
- + free.name() + '", but "' + self._free_variable.name()
1156
- + '" is listed as the free variable.')
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 is_valid_identifier(name):
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 "' + name + '" overlaps with a predefined 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 "' + str(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^' + str(p))
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="' + self._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 is_valid_identifier(name):
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 "' + e.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 "' + str(units) + '" (3.4.3.3).')
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 a :class:`myokit.Number`
1589
- representing the initial value if no RHS is set. For states always
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 myokit.Number(
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 (must be a number or ``None``).
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 ' + str(self) + ', which'
1613
- ' has ' + i + '_interface="in" (3.4.3.8).')
1614
-
1615
- # Check and store
1616
- try:
1617
- self._initial_value = float(value)
1618
- except ValueError:
1619
- if self._model.version() == '1.0':
1620
- raise CellMLError(
1621
- 'If given, a variable initial_value must be a real number'
1622
- ' (3.4.3.7).')
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
- 'If given, a variable initial_value must be a real number'
1626
- ' (using variables as initial values is not supported).')
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` tree where any
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 ' + str(self) + ', which has '
1653
- + i + '_interface="in" (4.4.4).')
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
- if rhs is not None:
1657
- for ref in rhs.references():
1658
- var = ref.var()
1659
- if var._component is not self._component:
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
- 'A variable RHS can only reference variables from the'
1662
- ' same component, found: ' + str(var) + '.')
1663
-
1664
- # Check all units are known
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
- str(self) + ' has ' + i + '_interface="in", but is not'
1716
- ' connected to a variable with an appropriate "out"')
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 ' + str(self) + ' must have a defining equation.')
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 ' + str(self) + '.')
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: ' + str(self) + ' has both an initial value and'
1738
- ' a defining equation (which is not an ODE).')
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
  """