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.
Files changed (43) hide show
  1. myokit/__init__.py +5 -0
  2. myokit/_datablock.py +6 -5
  3. myokit/_expressions.py +6 -1
  4. myokit/_model_api.py +44 -18
  5. myokit/_myokit_version.py +1 -1
  6. myokit/_parsing.py +8 -2
  7. myokit/_sim/cvodessim.py +26 -0
  8. myokit/formats/__init__.py +37 -0
  9. myokit/formats/ansic/_ewriter.py +1 -1
  10. myokit/formats/axon/_abf.py +43 -9
  11. myokit/formats/cellml/v1/__init__.py +5 -5
  12. myokit/formats/cellml/v1/_api.py +220 -122
  13. myokit/formats/cellml/v1/_parser.py +91 -87
  14. myokit/formats/cellml/v1/_writer.py +13 -6
  15. myokit/formats/cellml/v2/__init__.py +5 -8
  16. myokit/formats/cellml/v2/_api.py +182 -106
  17. myokit/formats/cellml/v2/_parser.py +68 -64
  18. myokit/formats/cellml/v2/_writer.py +7 -3
  19. myokit/formats/heka/_patchmaster.py +71 -14
  20. myokit/formats/mathml/_parser.py +106 -67
  21. myokit/gui/source.py +18 -12
  22. myokit/lib/hh.py +21 -37
  23. myokit/tests/test_cellml_v1_api.py +227 -33
  24. myokit/tests/test_cellml_v1_parser.py +48 -17
  25. myokit/tests/test_cellml_v1_writer.py +14 -4
  26. myokit/tests/test_cellml_v2_api.py +132 -114
  27. myokit/tests/test_cellml_v2_parser.py +31 -1
  28. myokit/tests/test_cellml_v2_writer.py +8 -1
  29. myokit/tests/test_datalog.py +17 -0
  30. myokit/tests/test_expressions.py +61 -0
  31. myokit/tests/test_formats.py +99 -0
  32. myokit/tests/test_formats_mathml_content.py +97 -37
  33. myokit/tests/test_formats_python.py +1 -1
  34. myokit/tests/test_model_building.py +2 -0
  35. myokit/tests/test_parsing.py +32 -0
  36. myokit/tests/test_simulation_cvodes.py +10 -4
  37. myokit/tests/test_variable.py +10 -7
  38. {myokit-1.37.5.dist-info → myokit-1.39.0.dist-info}/METADATA +22 -7
  39. {myokit-1.37.5.dist-info → myokit-1.39.0.dist-info}/RECORD +43 -43
  40. {myokit-1.37.5.dist-info → myokit-1.39.0.dist-info}/WHEEL +1 -1
  41. {myokit-1.37.5.dist-info → myokit-1.39.0.dist-info}/entry_points.txt +0 -0
  42. {myokit-1.37.5.dist-info → myokit-1.39.0.dist-info/licenses}/LICENSE.txt +0 -0
  43. {myokit-1.37.5.dist-info → myokit-1.39.0.dist-info}/top_level.txt +0 -0
@@ -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 "' + str(name)
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="' + self._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 not supported.
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 ' + str(variable_1) + ' to '
405
- + str(variable_2) + ': connections can only be made between'
406
- ' components that are siblings or have a parent-child'
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 ' + str(variable_1) + ' to '
413
- + str(variable_2) + ': variable_1 requires the ' + interface_1
414
- + ' interface, but is set to ' + variable_1.interface() + '.')
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 ' + str(variable_1) + ' to '
418
- + str(variable_2) + ': variable_2 requires the ' + interface_2
419
- + ' interface, but is set to ' + variable_2.interface() + '.')
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 ' + str(variable_1) + ' to '
427
- + str(variable_2) + ': Connected variables must have'
428
- ' compatible units. Found ' + str(variable_1.units())
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: ' + str(variable_1)
435
- + ' is already in the connected variable set of '
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
- v.set_initial_value(variable.initial_value(True))
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['author'] = 'Myokit CellML 2 API'
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().eval()
930
- v.promote(0 if init is None else init)
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 ' + str(variable) + ' as variable of integration,'
971
- ' the variable ' + str(voi) + ' has already been set.')
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="' + self._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 ' + str(var) + '.')
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 ' + str(voi) + ' must be a free'
1019
- msg += ' variable, but has '
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 ' + str(var) + ').'
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 ' + str(var) + ').'
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 "' + name + '" overlaps with a predefined 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 "' + str(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 ' + str(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 "' + str(prefix)
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^' + str(p))
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
- + str(multiplier) + '"')
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
- + str(multiplier) + '"')
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="' + self._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
- + str(units) + '".')
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 ' + str(lhs.var()) + ' passed to variable '
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: ' + str(var) + '.')
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: ' + str(x.unit()) + '.')
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 (must be a number or ``None``).
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 not None:
1651
+ if value is None:
1652
+ self._cset.set_initial_value(self, None)
1653
+ return
1607
1654
 
1608
- # Check value is a real number string
1609
- if isinstance(value, str):
1610
- if is_real_number_string(value):
1611
- value = float(value)
1612
- else:
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 given, a variable initial_value must be a real'
1615
- ' number (using variables as initial values is not'
1616
- ' supported).')
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
- value = float(value)
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
- value = myokit.Number(value, self._units.myokit_unit())
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="' + self._name + '"] in ' + str(self._component))
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
- + str(set1._equation_variable) + ' and '
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
- + str(set1._initial_value_variable) + ' and '
1722
- + str(set2._initial_value_variable) + '.')
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
- + str(self._equation_variable) + '.')
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 for
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
- + str(self._initial_value_variable) + '.')
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
- + str(self._equation_variable) + '.')
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: ' + str(self._equation_variable)
1781
- msg += ' has both a (non-ODE) equation and an initial value'
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 ' + str(self._initial_value_variable) + ')'
1784
- msg += '.'
1785
- raise CellMLError(msg)
1860
+ msg += f' (set by {self._initial_value_variable})'
1861
+ raise CellMLError(f'{msg}.')
1786
1862