myokit 1.33.9__py3-none-any.whl → 1.35.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 +9 -36
- myokit/__main__.py +76 -142
- myokit/_aux.py +62 -16
- myokit/_bin/example.mmt +1 -2
- myokit/_bin/install-win/menu.json +7 -7
- myokit/_config.py +22 -31
- myokit/_datablock.py +30 -74
- myokit/_datalog.py +49 -72
- myokit/_err.py +25 -24
- myokit/_expressions.py +50 -68
- myokit/_io.py +15 -27
- myokit/_model_api.py +453 -249
- myokit/_myokit_version.py +1 -5
- myokit/_parsing.py +38 -44
- myokit/_progress.py +5 -8
- myokit/_protocol.py +99 -9
- myokit/_sim/__init__.py +7 -24
- myokit/_sim/cable.c +6 -8
- myokit/_sim/cable.py +6 -8
- myokit/_sim/cmodel.h +125 -70
- myokit/_sim/cmodel.py +12 -14
- myokit/_sim/compiler.py +1 -4
- myokit/_sim/cvodessim.c +196 -118
- myokit/_sim/cvodessim.py +130 -103
- myokit/_sim/differential.hpp +4 -4
- myokit/_sim/fiber_tissue.c +4 -8
- myokit/_sim/fiber_tissue.py +11 -13
- myokit/_sim/jacobian.cpp +2 -2
- myokit/_sim/jacobian.py +11 -8
- myokit/_sim/mcl.h +53 -55
- myokit/_sim/opencl.py +21 -27
- myokit/_sim/openclsim.c +3 -7
- myokit/_sim/openclsim.cl +3 -3
- myokit/_sim/openclsim.py +49 -40
- myokit/_sim/pacing.h +36 -16
- myokit/_sim/rhs.c +6 -13
- myokit/_sim/rhs.py +5 -14
- myokit/_sim/sundials.py +1 -4
- myokit/_system.py +10 -16
- myokit/_unit.py +4 -13
- myokit/float.py +0 -3
- myokit/formats/__init__.py +8 -10
- myokit/formats/ansic/__init__.py +0 -3
- myokit/formats/ansic/_ewriter.py +2 -4
- myokit/formats/ansic/_exporter.py +1 -4
- myokit/formats/ansic/template/cable.c +4 -4
- myokit/formats/ansic/template/euler.c +5 -5
- myokit/formats/ansic/template/sim.c +6 -6
- myokit/formats/axon/__init__.py +1 -3
- myokit/formats/axon/_abf.py +12 -17
- myokit/formats/axon/_atf.py +5 -6
- myokit/formats/axon/_importer.py +0 -3
- myokit/formats/cellml/__init__.py +0 -3
- myokit/formats/cellml/_ewriter.py +3 -6
- myokit/formats/cellml/_exporter.py +3 -6
- myokit/formats/cellml/_importer.py +1 -4
- myokit/formats/cellml/v1/__init__.py +0 -4
- myokit/formats/cellml/v1/_api.py +8 -11
- myokit/formats/cellml/v1/_parser.py +2 -5
- myokit/formats/cellml/v1/_writer.py +2 -11
- myokit/formats/cellml/v2/__init__.py +0 -3
- myokit/formats/cellml/v2/_api.py +8 -17
- myokit/formats/cellml/v2/_parser.py +2 -5
- myokit/formats/cellml/v2/_writer.py +1 -4
- myokit/formats/channelml/__init__.py +0 -3
- myokit/formats/channelml/_importer.py +11 -21
- myokit/formats/cpp/__init__.py +1 -3
- myokit/formats/cpp/_ewriter.py +0 -3
- myokit/formats/cuda/__init__.py +0 -3
- myokit/formats/cuda/_ewriter.py +2 -4
- myokit/formats/cuda/_exporter.py +0 -3
- myokit/formats/cuda/template/kernel.cu +8 -5
- myokit/formats/easyml/__init__.py +0 -3
- myokit/formats/easyml/_ewriter.py +9 -11
- myokit/formats/easyml/_exporter.py +2 -5
- myokit/formats/html/__init__.py +0 -3
- myokit/formats/html/_exporter.py +0 -3
- myokit/formats/html/_flatten.py +5 -21
- myokit/formats/latex/__init__.py +0 -3
- myokit/formats/latex/_ewriter.py +1 -4
- myokit/formats/latex/_exporter.py +4 -6
- myokit/formats/mathml/__init__.py +0 -3
- myokit/formats/mathml/_ewriter.py +2 -11
- myokit/formats/mathml/_parser.py +4 -6
- myokit/formats/matlab/__init__.py +0 -3
- myokit/formats/matlab/_ewriter.py +1 -4
- myokit/formats/matlab/_exporter.py +2 -5
- myokit/formats/matlab/template/main.m +3 -2
- myokit/formats/opencl/__init__.py +0 -3
- myokit/formats/opencl/_ewriter.py +2 -4
- myokit/formats/opencl/_exporter.py +2 -5
- myokit/formats/opencl/template/cable.c +10 -10
- myokit/formats/opencl/template/kernel.cl +1 -1
- myokit/formats/opencl/template/minilog.py +1 -1
- myokit/formats/python/__init__.py +0 -3
- myokit/formats/python/_ewriter.py +2 -5
- myokit/formats/python/_exporter.py +0 -3
- myokit/formats/python/template/sim.py +14 -14
- myokit/formats/sbml/__init__.py +0 -3
- myokit/formats/sbml/_api.py +50 -44
- myokit/formats/sbml/_importer.py +1 -4
- myokit/formats/sbml/_parser.py +2 -5
- myokit/formats/stan/__init__.py +0 -3
- myokit/formats/stan/_ewriter.py +2 -4
- myokit/formats/stan/_exporter.py +2 -5
- myokit/formats/stan/template/cell.stan +3 -3
- myokit/formats/sympy/__init__.py +0 -3
- myokit/formats/sympy/_ereader.py +1 -4
- myokit/formats/sympy/_ewriter.py +2 -5
- myokit/formats/wcp/__init__.py +0 -3
- myokit/formats/wcp/_wcp.py +2 -8
- myokit/formats/xml/__init__.py +0 -3
- myokit/formats/xml/_exporter.py +0 -3
- myokit/formats/xml/_split.py +0 -3
- myokit/gui/__init__.py +80 -246
- myokit/gui/datablock_viewer.py +103 -86
- myokit/gui/datalog_viewer.py +214 -66
- myokit/gui/explorer.py +15 -21
- myokit/gui/ide.py +171 -144
- myokit/gui/progress.py +9 -9
- myokit/gui/source.py +406 -375
- myokit/gui/vargrapher.py +2 -12
- myokit/lib/deps.py +12 -13
- myokit/lib/guess.py +3 -4
- myokit/lib/hh.py +20 -18
- myokit/lib/markov.py +21 -20
- myokit/lib/multi.py +1 -3
- myokit/lib/plots.py +20 -9
- myokit/pacing.py +0 -3
- myokit/pype.py +7 -18
- myokit/tests/__init__.py +3 -6
- myokit/tests/ansic_event_based_pacing.py +1 -4
- myokit/tests/ansic_fixed_form_pacing.py +3 -6
- myokit/tests/data/beeler-1977-model-compare-b.mmt +2 -2
- myokit/tests/data/clancy-1999-fitting.mmt +1 -0
- myokit/tests/test_aux.py +13 -28
- myokit/tests/test_cellml_v1_api.py +4 -19
- myokit/tests/test_cellml_v1_parser.py +0 -15
- myokit/tests/test_cellml_v1_writer.py +0 -9
- myokit/tests/test_cellml_v2_api.py +4 -19
- myokit/tests/test_cellml_v2_parser.py +0 -15
- myokit/tests/test_cellml_v2_writer.py +0 -9
- myokit/tests/test_cmodel.py +16 -22
- myokit/tests/test_compiler_detection.py +1 -11
- myokit/tests/test_component.py +108 -56
- myokit/tests/test_config.py +34 -67
- myokit/tests/test_datablock.py +1 -9
- myokit/tests/test_datalog.py +19 -24
- myokit/tests/test_dependency_checking.py +8 -23
- myokit/tests/test_expressions.py +0 -9
- myokit/tests/test_float.py +1 -5
- myokit/tests/test_formats.py +0 -9
- myokit/tests/test_formats_axon.py +1 -9
- myokit/tests/test_formats_cellml.py +0 -15
- myokit/tests/test_formats_channelml.py +0 -15
- myokit/tests/test_formats_easyml.py +0 -14
- myokit/tests/test_formats_exporters.py +1 -16
- myokit/tests/test_formats_expression_writers.py +1 -17
- myokit/tests/test_formats_html.py +0 -3
- myokit/tests/test_formats_importers.py +1 -16
- myokit/tests/test_formats_mathml_content.py +0 -9
- myokit/tests/test_formats_mathml_presentation.py +0 -9
- myokit/tests/test_formats_opencl.py +0 -10
- myokit/tests/test_formats_sbml.py +0 -15
- myokit/tests/test_formats_sympy.py +0 -9
- myokit/tests/test_formats_wcp.py +1 -3
- myokit/tests/test_io.py +27 -27
- myokit/tests/test_jacobian_calculator.py +6 -14
- myokit/tests/test_jacobian_tracer.py +0 -9
- myokit/tests/test_lib_deps.py +0 -9
- myokit/tests/test_lib_guess.py +0 -9
- myokit/tests/test_lib_hh.py +18 -12
- myokit/tests/test_lib_markov.py +21 -13
- myokit/tests/test_lib_multi.py +0 -9
- myokit/tests/test_lib_plots.py +13 -8
- myokit/tests/test_meta.py +0 -3
- myokit/tests/test_model.py +390 -96
- myokit/tests/test_model_building.py +44 -96
- myokit/tests/test_opencl_info.py +5 -14
- myokit/tests/test_pacing_factory.py +0 -3
- myokit/tests/test_pacing_system_c.py +1 -23
- myokit/tests/test_pacing_system_py.py +0 -9
- myokit/tests/test_parsing.py +139 -56
- myokit/tests/test_progress_reporters.py +0 -3
- myokit/tests/test_protocol.py +0 -9
- myokit/tests/test_protocol_floating_point.py +1 -10
- myokit/tests/test_protocol_time_series.py +82 -0
- myokit/tests/test_pype.py +0 -9
- myokit/tests/test_quantity.py +0 -9
- myokit/tests/test_rhs_benchmarker.py +1 -9
- myokit/tests/test_sbml_api.py +27 -42
- myokit/tests/test_sbml_parser.py +4 -19
- myokit/tests/test_simulation_1d.py +45 -25
- myokit/tests/test_simulation_cvodes.py +321 -55
- myokit/tests/test_simulation_cvodes_from_disk.py +0 -3
- myokit/tests/test_simulation_fiber_tissue.py +39 -12
- myokit/tests/test_simulation_log_interval.py +1 -431
- myokit/tests/test_simulation_opencl.py +69 -48
- myokit/tests/test_simulation_opencl_log_interval.py +1 -3
- myokit/tests/test_simulation_opencl_vs_cvode.py +1 -10
- myokit/tests/test_simulation_opencl_vs_sim1d.py +1 -10
- myokit/tests/test_system_info.py +1 -11
- myokit/tests/test_tools.py +0 -9
- myokit/tests/test_unit.py +1 -10
- myokit/tests/test_user_functions.py +0 -10
- myokit/tests/test_variable.py +231 -27
- myokit/tools.py +5 -21
- myokit/units.py +5 -3
- {myokit-1.33.9.dist-info → myokit-1.35.0.dist-info}/METADATA +12 -15
- myokit-1.35.0.dist-info/RECORD +391 -0
- {myokit-1.33.9.dist-info → myokit-1.35.0.dist-info}/WHEEL +1 -1
- {myokit-1.33.9.dist-info → myokit-1.35.0.dist-info}/entry_points.txt +0 -1
- myokit/_exec_new.py +0 -15
- myokit/_exec_old.py +0 -15
- myokit/_sim/cvodesim.c +0 -1551
- myokit/_sim/cvodesim.py +0 -674
- myokit/_sim/icsim.cpp +0 -563
- myokit/_sim/icsim.py +0 -363
- myokit/_sim/psim.cpp +0 -656
- myokit/_sim/psim.py +0 -493
- myokit/lib/common.py +0 -1094
- myokit/tests/test_lib_common.py +0 -130
- myokit/tests/test_simulation_cvode.py +0 -612
- myokit/tests/test_simulation_ic.py +0 -108
- myokit/tests/test_simulation_p.py +0 -223
- myokit-1.33.9.dist-info/RECORD +0 -403
- /myokit/formats/opencl/template/{test → test.sh} +0 -0
- {myokit-1.33.9.dist-info → myokit-1.35.0.dist-info}/LICENSE.txt +0 -0
- {myokit-1.33.9.dist-info → myokit-1.35.0.dist-info}/top_level.txt +0 -0
myokit/_model_api.py
CHANGED
|
@@ -4,25 +4,14 @@
|
|
|
4
4
|
# This file is part of Myokit.
|
|
5
5
|
# See http://myokit.org for copyright, sharing, and licensing details.
|
|
6
6
|
#
|
|
7
|
-
|
|
8
|
-
from __future__ import print_function, unicode_literals
|
|
9
|
-
|
|
10
|
-
from collections import OrderedDict
|
|
7
|
+
import io
|
|
11
8
|
import math
|
|
12
9
|
import re
|
|
13
|
-
import myokit
|
|
14
10
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
except ImportError:
|
|
19
|
-
from io import StringIO
|
|
11
|
+
from collections import OrderedDict
|
|
12
|
+
|
|
13
|
+
import myokit
|
|
20
14
|
|
|
21
|
-
# Strings in Python 2 and 3
|
|
22
|
-
try:
|
|
23
|
-
basestring
|
|
24
|
-
except NameError: # pragma: no cover
|
|
25
|
-
basestring = str
|
|
26
15
|
|
|
27
16
|
TAB = ' ' * 4
|
|
28
17
|
NAME = re.compile(r'^[a-zA-Z]\w*$')
|
|
@@ -34,8 +23,6 @@ def check_name(name):
|
|
|
34
23
|
Tests if the given name is a valid myokit name and raises a
|
|
35
24
|
:class:`myokit.InvalidNameError` if it isn't.
|
|
36
25
|
"""
|
|
37
|
-
# Note: Names are stored as str (so unicode in Python3)
|
|
38
|
-
# But the regex restriction means their format is compatible with ascii.
|
|
39
26
|
# Check str compatibility
|
|
40
27
|
name = str(name)
|
|
41
28
|
|
|
@@ -65,7 +52,7 @@ class MetaDataContainer(dict):
|
|
|
65
52
|
raise myokit.InvalidMetaDataNameError(
|
|
66
53
|
'The key <' + str(key) + '>'
|
|
67
54
|
' is not a valid meta-data property identifier.')
|
|
68
|
-
return super(
|
|
55
|
+
return super().__getitem__(key)
|
|
69
56
|
|
|
70
57
|
def __setitem__(self, key, item):
|
|
71
58
|
# Check item
|
|
@@ -75,10 +62,10 @@ class MetaDataContainer(dict):
|
|
|
75
62
|
raise myokit.InvalidMetaDataNameError(
|
|
76
63
|
'The key <' + str(key) + '>'
|
|
77
64
|
' is not a valid meta-data property identifier.')
|
|
78
|
-
super(
|
|
65
|
+
super().__setitem__(key, item)
|
|
79
66
|
|
|
80
67
|
|
|
81
|
-
class ObjectWithMeta
|
|
68
|
+
class ObjectWithMeta:
|
|
82
69
|
"""
|
|
83
70
|
Base class for objects with meta data.
|
|
84
71
|
|
|
@@ -87,7 +74,7 @@ class ObjectWithMeta(object):
|
|
|
87
74
|
"""
|
|
88
75
|
|
|
89
76
|
def __init__(self):
|
|
90
|
-
super(
|
|
77
|
+
super().__init__()
|
|
91
78
|
self.meta = MetaDataContainer()
|
|
92
79
|
|
|
93
80
|
def _clone_metadata(self, clone):
|
|
@@ -135,7 +122,7 @@ class ModelPart(ObjectWithMeta):
|
|
|
135
122
|
The given parent should be a ModelPart or None. The name should be
|
|
136
123
|
unique within the set of children for the given parent.
|
|
137
124
|
"""
|
|
138
|
-
super(
|
|
125
|
+
super().__init__()
|
|
139
126
|
self._parent = parent # This object's parent
|
|
140
127
|
self._model = None # The model this object belongs to
|
|
141
128
|
self._name = str(name) # Local name
|
|
@@ -153,7 +140,7 @@ class ModelPart(ObjectWithMeta):
|
|
|
153
140
|
"""
|
|
154
141
|
Returns this object in ``mmt`` syntax.
|
|
155
142
|
"""
|
|
156
|
-
b = StringIO()
|
|
143
|
+
b = io.StringIO()
|
|
157
144
|
self._code(b, 0)
|
|
158
145
|
return b.getvalue()
|
|
159
146
|
|
|
@@ -162,7 +149,7 @@ class ModelPart(ObjectWithMeta):
|
|
|
162
149
|
Internal version of _code(), to be implemented by all subclasses.
|
|
163
150
|
|
|
164
151
|
The argument ``t`` specifies the number of tabs to indent the code
|
|
165
|
-
with. The argument ``b`` is a
|
|
152
|
+
with. The argument ``b`` is a StringIO buffer.
|
|
166
153
|
"""
|
|
167
154
|
raise NotImplementedError
|
|
168
155
|
|
|
@@ -249,7 +236,7 @@ class ModelPart(ObjectWithMeta):
|
|
|
249
236
|
return self._uname
|
|
250
237
|
|
|
251
238
|
|
|
252
|
-
class VarProvider
|
|
239
|
+
class VarProvider:
|
|
253
240
|
"""
|
|
254
241
|
*Abstract class*
|
|
255
242
|
|
|
@@ -463,7 +450,7 @@ class VarOwner(ModelPart, VarProvider):
|
|
|
463
450
|
"""
|
|
464
451
|
|
|
465
452
|
def __init__(self, parent, name):
|
|
466
|
-
super(
|
|
453
|
+
super().__init__(parent, name)
|
|
467
454
|
self._variables = {}
|
|
468
455
|
# Set component
|
|
469
456
|
self._component = self
|
|
@@ -720,9 +707,7 @@ class VarOwner(ModelPart, VarProvider):
|
|
|
720
707
|
""" See :meth:`VarProvider._resolve(). """
|
|
721
708
|
def sa(name):
|
|
722
709
|
# Suggest alternative
|
|
723
|
-
|
|
724
|
-
(var, sug, msg) = m.suggest_variable(name)
|
|
725
|
-
return msg
|
|
710
|
+
return self.model().suggest_variable(name)[2]
|
|
726
711
|
|
|
727
712
|
# Try resolving as an alias
|
|
728
713
|
try:
|
|
@@ -772,7 +757,7 @@ class Model(ObjectWithMeta, VarProvider):
|
|
|
772
757
|
Variables stored inside components can be accessed using :meth:`get()` or
|
|
773
758
|
:meth:`values()`. Values defined through their derivative make up the
|
|
774
759
|
model state and can be accessed using :meth:`states()`. States have
|
|
775
|
-
initial values accessible through :meth:`
|
|
760
|
+
initial values accessible through :meth:`initial_values()`.
|
|
776
761
|
|
|
777
762
|
A model's validity can be checked using :meth:`is_valid()`, which returns
|
|
778
763
|
the latest validation status and :meth:`validate()`, which (re)validates
|
|
@@ -787,20 +772,20 @@ class Model(ObjectWithMeta, VarProvider):
|
|
|
787
772
|
For consistency with components, variables, and expressions, models cannot
|
|
788
773
|
be compared with ``==`` (which will only return ``True`` if both operands
|
|
789
774
|
are the same object). Checking if models are the same in other senses can
|
|
790
|
-
be done with :meth:`is_similar`. Models can be
|
|
775
|
+
be done with :meth:`is_similar`. Models can be serialized with ``pickle``.
|
|
791
776
|
"""
|
|
792
777
|
|
|
793
778
|
def __init__(self, name=None):
|
|
794
|
-
super(
|
|
779
|
+
super().__init__()
|
|
795
780
|
|
|
796
781
|
# A dictionary of components
|
|
797
782
|
self._components = {}
|
|
798
783
|
|
|
799
784
|
# The model's state variables
|
|
800
|
-
self.
|
|
785
|
+
self._state_vars = []
|
|
801
786
|
|
|
802
|
-
# The model's
|
|
803
|
-
self.
|
|
787
|
+
# The model's initial state, as a list of Expressions
|
|
788
|
+
self._state_init = []
|
|
804
789
|
|
|
805
790
|
# A dict mapping binding names to variables
|
|
806
791
|
self._bindings = {}
|
|
@@ -1087,9 +1072,11 @@ class Model(ObjectWithMeta, VarProvider):
|
|
|
1087
1072
|
for c in self._components.values():
|
|
1088
1073
|
c._clone1(clone)
|
|
1089
1074
|
|
|
1090
|
-
#
|
|
1091
|
-
|
|
1092
|
-
|
|
1075
|
+
# Create states
|
|
1076
|
+
# Note that the order in which promote() is called determines the
|
|
1077
|
+
# state ordering, so this happens here and not in the Variable class.
|
|
1078
|
+
for k, v in enumerate(self._state_vars):
|
|
1079
|
+
clone.get(v.qname()).promote()
|
|
1093
1080
|
|
|
1094
1081
|
# Create mapping of old var references to new references
|
|
1095
1082
|
var_map = {}
|
|
@@ -1110,6 +1097,11 @@ class Model(ObjectWithMeta, VarProvider):
|
|
|
1110
1097
|
for prefix, prepend in self._reserved_uname_prefixes.items():
|
|
1111
1098
|
clone.reserve_unique_name_prefix(prefix, prepend)
|
|
1112
1099
|
|
|
1100
|
+
# Copy initial state expressions
|
|
1101
|
+
for k, v in enumerate(self._state_vars):
|
|
1102
|
+
clone.get(v.qname()).set_initial_value(
|
|
1103
|
+
self._state_init[k].clone(subst=lhs_map))
|
|
1104
|
+
|
|
1113
1105
|
# Return
|
|
1114
1106
|
return clone
|
|
1115
1107
|
|
|
@@ -1119,7 +1111,7 @@ class Model(ObjectWithMeta, VarProvider):
|
|
|
1119
1111
|
|
|
1120
1112
|
Line numbers can be added by setting ``line_numbers=True``.
|
|
1121
1113
|
"""
|
|
1122
|
-
b = StringIO()
|
|
1114
|
+
b = io.StringIO()
|
|
1123
1115
|
b.write('[[model]]\n')
|
|
1124
1116
|
self._code(b, 0)
|
|
1125
1117
|
if line_numbers:
|
|
@@ -1141,17 +1133,15 @@ class Model(ObjectWithMeta, VarProvider):
|
|
|
1141
1133
|
self._code_meta(b, 0)
|
|
1142
1134
|
|
|
1143
1135
|
# Initial state
|
|
1144
|
-
if self.
|
|
1136
|
+
if self._state_vars:
|
|
1145
1137
|
pre = t * TAB
|
|
1146
1138
|
b.write(pre + '# Initial values\n')
|
|
1147
|
-
names = [
|
|
1139
|
+
names = [v.qname() for v in self._state_vars]
|
|
1140
|
+
values = [e.code() for e in self._state_init]
|
|
1148
1141
|
n = max([len(name) for name in names])
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
b.write(
|
|
1153
|
-
pre + name + ' ' * (n - len(name)) + ' = ' + eq.rhs.code()
|
|
1154
|
-
+ '\n')
|
|
1142
|
+
for name, value in zip(names, values):
|
|
1143
|
+
b.write(pre + name + ' ' * (n - len(name)) + ' = '
|
|
1144
|
+
+ value + '\n')
|
|
1155
1145
|
b.write(pre + '\n')
|
|
1156
1146
|
else:
|
|
1157
1147
|
# No initial state? Then add newline
|
|
@@ -1243,7 +1233,7 @@ class Model(ObjectWithMeta, VarProvider):
|
|
|
1243
1233
|
"""
|
|
1244
1234
|
Returns the number of state variables in this model.
|
|
1245
1235
|
"""
|
|
1246
|
-
return len(self.
|
|
1236
|
+
return len(self._state_vars)
|
|
1247
1237
|
|
|
1248
1238
|
def create_unique_names(self):
|
|
1249
1239
|
"""
|
|
@@ -1357,7 +1347,7 @@ class Model(ObjectWithMeta, VarProvider):
|
|
|
1357
1347
|
# Insert new state (if required)
|
|
1358
1348
|
if state is not None:
|
|
1359
1349
|
new_state = self.map_to_state(state)
|
|
1360
|
-
for state, value in zip(self.
|
|
1350
|
+
for state, value in zip(self._state_vars, new_state):
|
|
1361
1351
|
values[myokit.Name(state)] = value
|
|
1362
1352
|
state = None
|
|
1363
1353
|
|
|
@@ -1390,7 +1380,7 @@ class Model(ObjectWithMeta, VarProvider):
|
|
|
1390
1380
|
values[eq.lhs] = eq.rhs.eval(values, precision=precision)
|
|
1391
1381
|
|
|
1392
1382
|
# Return calculated state
|
|
1393
|
-
return [values[state.lhs()] for state in self.
|
|
1383
|
+
return [values[state.lhs()] for state in self._state_vars]
|
|
1394
1384
|
|
|
1395
1385
|
def eval_state_derivatives(
|
|
1396
1386
|
self, state=None, inputs=None, precision=myokit.DOUBLE_PRECISION,
|
|
@@ -1489,14 +1479,14 @@ class Model(ObjectWithMeta, VarProvider):
|
|
|
1489
1479
|
def format_state(self, state=None, state2=None,
|
|
1490
1480
|
precision=myokit.DOUBLE_PRECISION):
|
|
1491
1481
|
"""
|
|
1492
|
-
Converts
|
|
1493
|
-
|
|
1482
|
+
Converts a sequence of floating point numbers to a string where each
|
|
1483
|
+
line has the format ``<full_qualified_name> = <float_value>``.
|
|
1494
1484
|
|
|
1495
1485
|
Arguments:
|
|
1496
1486
|
|
|
1497
1487
|
``state=None``
|
|
1498
|
-
The state to
|
|
1499
|
-
|
|
1488
|
+
The state to display. If no state is given this model's (evaluated)
|
|
1489
|
+
:meth:`<initial_values()>initial values` are used.
|
|
1500
1490
|
``state2=None``
|
|
1501
1491
|
An optional second state, to be shown next to ``state`` for
|
|
1502
1492
|
comparison.
|
|
@@ -1505,19 +1495,19 @@ class Model(ObjectWithMeta, VarProvider):
|
|
|
1505
1495
|
:meth:`myokit.float.str` when formatting the state values.
|
|
1506
1496
|
|
|
1507
1497
|
"""
|
|
1508
|
-
n = len(self.
|
|
1509
|
-
if state is
|
|
1498
|
+
n = len(self._state_vars)
|
|
1499
|
+
if state is None:
|
|
1500
|
+
state = self.initial_values(as_floats=True)
|
|
1501
|
+
else:
|
|
1510
1502
|
if len(state) != n:
|
|
1511
1503
|
raise ValueError(
|
|
1512
|
-
'Argument `state` must be a
|
|
1504
|
+
'Argument `state` must be a sequence of (' + str(n)
|
|
1513
1505
|
+ ') floating point numbers.')
|
|
1514
|
-
else:
|
|
1515
|
-
state = self.state()
|
|
1516
1506
|
|
|
1517
1507
|
if state2 is not None:
|
|
1518
1508
|
if len(state2) != n:
|
|
1519
1509
|
raise ValueError(
|
|
1520
|
-
'Argument `state2` must be a
|
|
1510
|
+
'Argument `state2` must be a sequence of (' + str(n)
|
|
1521
1511
|
+ ') floating point numbers.')
|
|
1522
1512
|
|
|
1523
1513
|
out = []
|
|
@@ -1541,35 +1531,35 @@ class Model(ObjectWithMeta, VarProvider):
|
|
|
1541
1531
|
Like :meth:`format_state` but displays the derivatives along with
|
|
1542
1532
|
each state's value.
|
|
1543
1533
|
|
|
1544
|
-
|
|
1545
1534
|
Arguments:
|
|
1546
1535
|
|
|
1547
1536
|
``state=None``
|
|
1548
|
-
The state to
|
|
1549
|
-
:meth
|
|
1537
|
+
The state to show derivatives for. If no state is given this
|
|
1538
|
+
model's (evaluated) :meth:`<initial_values()>initial values` are
|
|
1539
|
+
used.
|
|
1550
1540
|
``derivatives=None``
|
|
1551
|
-
An optional list of evaluated derivatives. If not
|
|
1552
|
-
will be calculed from ``state`` using
|
|
1541
|
+
An optional list or other sequence of evaluated derivatives. If not
|
|
1542
|
+
given, the values will be calculed from ``state`` using
|
|
1543
|
+
:meth:`eval_derivatives()`.
|
|
1553
1544
|
``precision=myokit.DOUBLE_PRECISION``
|
|
1554
1545
|
An optional precision argument to use when evaluating the state
|
|
1555
1546
|
derivatives, and to pass into :meth:`myokit.float.str` when
|
|
1556
1547
|
formatting the state values and derivatives.
|
|
1557
1548
|
|
|
1558
1549
|
"""
|
|
1559
|
-
n = len(self.
|
|
1550
|
+
n = len(self._state_vars)
|
|
1560
1551
|
if state is None:
|
|
1561
|
-
state = self.
|
|
1552
|
+
state = self.initial_values(as_floats=True)
|
|
1562
1553
|
elif len(state) != n:
|
|
1563
1554
|
raise ValueError(
|
|
1564
|
-
'Argument `state` must be a
|
|
1555
|
+
'Argument `state` must be a sequence of (' + str(n)
|
|
1565
1556
|
+ ') floating point numbers.')
|
|
1566
1557
|
|
|
1567
1558
|
if derivatives is None:
|
|
1568
|
-
derivatives = self.evaluate_derivatives(
|
|
1569
|
-
state, precision=precision)
|
|
1559
|
+
derivatives = self.evaluate_derivatives(state, precision=precision)
|
|
1570
1560
|
elif len(derivatives) != n:
|
|
1571
1561
|
raise ValueError(
|
|
1572
|
-
'Argument `
|
|
1562
|
+
'Argument `derivatives` must be a sequence of (' + str(n)
|
|
1573
1563
|
+ ') floating point numbers.')
|
|
1574
1564
|
|
|
1575
1565
|
out = []
|
|
@@ -1746,7 +1736,7 @@ class Model(ObjectWithMeta, VarProvider):
|
|
|
1746
1736
|
'as external_component, or a string if only one '
|
|
1747
1737
|
'component is provided'
|
|
1748
1738
|
)
|
|
1749
|
-
if isinstance(new_name,
|
|
1739
|
+
if isinstance(new_name, str):
|
|
1750
1740
|
if len(external_component) != 1:
|
|
1751
1741
|
raise TypeError(new_name_error_str)
|
|
1752
1742
|
new_name = [new_name]
|
|
@@ -1754,7 +1744,7 @@ class Model(ObjectWithMeta, VarProvider):
|
|
|
1754
1744
|
try:
|
|
1755
1745
|
ok = (
|
|
1756
1746
|
len(new_name) == len(external_component) and
|
|
1757
|
-
all(isinstance(name,
|
|
1747
|
+
all(isinstance(name, str) for name in new_name)
|
|
1758
1748
|
)
|
|
1759
1749
|
if not ok:
|
|
1760
1750
|
raise TypeError(new_name_error_str)
|
|
@@ -1802,6 +1792,9 @@ class Model(ObjectWithMeta, VarProvider):
|
|
|
1802
1792
|
for var in comp.variables():
|
|
1803
1793
|
vars_ref.update(var.refs_to(state_refs=False))
|
|
1804
1794
|
vars_ref.update(var.refs_to(state_refs=True))
|
|
1795
|
+
if var.is_state():
|
|
1796
|
+
vars_ref.update(
|
|
1797
|
+
[e.var() for e in var.initial_value().references()])
|
|
1805
1798
|
vars_ref.update(comp._alias_map.values())
|
|
1806
1799
|
vars_ref -= set(comp.variables())
|
|
1807
1800
|
vars_ref = [x for x in vars_ref if not x.is_nested()]
|
|
@@ -1830,7 +1823,7 @@ class Model(ObjectWithMeta, VarProvider):
|
|
|
1830
1823
|
'The variable <' + self_var.qname() + '> in the'
|
|
1831
1824
|
' given var_map\'s values is not part of this'
|
|
1832
1825
|
' model.')
|
|
1833
|
-
elif isinstance(self_var,
|
|
1826
|
+
elif isinstance(self_var, str):
|
|
1834
1827
|
try:
|
|
1835
1828
|
self_var = self.var(self_var)
|
|
1836
1829
|
except KeyError:
|
|
@@ -1852,7 +1845,7 @@ class Model(ObjectWithMeta, VarProvider):
|
|
|
1852
1845
|
'The variable <' + ext_var.qname() + '> in the'
|
|
1853
1846
|
' given var_map\'s keys but is not part of the'
|
|
1854
1847
|
' source model.')
|
|
1855
|
-
elif isinstance(ext_var,
|
|
1848
|
+
elif isinstance(ext_var, str):
|
|
1856
1849
|
try:
|
|
1857
1850
|
ext_var = ext_model.var(ext_var)
|
|
1858
1851
|
except KeyError:
|
|
@@ -1877,8 +1870,8 @@ class Model(ObjectWithMeta, VarProvider):
|
|
|
1877
1870
|
if ext_var in vars_to_map:
|
|
1878
1871
|
var_map[ext_var] = self_var
|
|
1879
1872
|
|
|
1880
|
-
#
|
|
1881
|
-
#
|
|
1873
|
+
# Add variables to var_map that map to other imported components but
|
|
1874
|
+
# will be reassigned to the clone later
|
|
1882
1875
|
for l in map_to_clone:
|
|
1883
1876
|
for ext_var in l:
|
|
1884
1877
|
if ext_var not in var_map:
|
|
@@ -1963,19 +1956,29 @@ class Model(ObjectWithMeta, VarProvider):
|
|
|
1963
1956
|
' mismatch: ' + str(e))
|
|
1964
1957
|
|
|
1965
1958
|
# Clone component pt 1: create, meta data, empty variables
|
|
1966
|
-
new_component = []
|
|
1959
|
+
new_component = [] # List of components
|
|
1967
1960
|
for i, comp in enumerate(external_component):
|
|
1968
1961
|
new_component.append(comp._clone1(self, new_name[i]))
|
|
1969
1962
|
|
|
1970
|
-
for var in comp.variables(state=True):
|
|
1971
|
-
# Clone states
|
|
1972
|
-
# TODO: Not sure why clone() code doesn't do this?
|
|
1973
|
-
new_component[i].get(var.qname(comp)).promote(
|
|
1974
|
-
var.state_value())
|
|
1975
1963
|
# Now we can add variable to var_map if needed
|
|
1976
1964
|
for var in map_to_clone[i]:
|
|
1977
1965
|
var_map[var] = new_component[i].get(var.qname(comp))
|
|
1978
1966
|
|
|
1967
|
+
# Clone states, preserving the state order
|
|
1968
|
+
# Note: The order in which promote() is called determines the order
|
|
1969
|
+
# of the states in the new component. (This is one of the reasons that
|
|
1970
|
+
# the component._clone1 method called above doesn't call promote.)
|
|
1971
|
+
new_states = [] # New states, from all components
|
|
1972
|
+
state_map = {} # New-state to old state
|
|
1973
|
+
for old_comp, comp in zip(external_component, new_component):
|
|
1974
|
+
for ext_var in old_comp.variables(state=True):
|
|
1975
|
+
var = comp.get(ext_var.name())
|
|
1976
|
+
state_map[var] = ext_var
|
|
1977
|
+
new_states.append(var)
|
|
1978
|
+
new_states.sort(key=lambda var: state_map[var].index())
|
|
1979
|
+
for var in new_states:
|
|
1980
|
+
var.promote() # Initial value is set later
|
|
1981
|
+
|
|
1979
1982
|
# Create mapping of old var references to new references
|
|
1980
1983
|
# This is a mapping from Name(var) and Derivative(Name(var)) objects
|
|
1981
1984
|
# to new myokit.Expressions referencing the target model's variables
|
|
@@ -1991,9 +1994,10 @@ class Model(ObjectWithMeta, VarProvider):
|
|
|
1991
1994
|
lhs_map[myokit.Derivative(myokit.Name(ext_var))] = \
|
|
1992
1995
|
myokit.Derivative(myokit.Name(self_var))
|
|
1993
1996
|
|
|
1994
|
-
# Next, add all entries in the var_map. If unit
|
|
1995
|
-
# this may include the addition of unit
|
|
1996
|
-
|
|
1997
|
+
# Next, add all entries in the var_map to the lhs_map. If unit
|
|
1998
|
+
# conversion is enabled, this may include the addition of unit
|
|
1999
|
+
# conversion factors (so some Names in lhs_map will be mapped onto
|
|
2000
|
+
# Multiply expressions).
|
|
1997
2001
|
for ext_var, self_var in var_map.items():
|
|
1998
2002
|
# Substitute in either a reference to self_var, or an expression
|
|
1999
2003
|
# that converts self_var to the units ext_var's equation expects.
|
|
@@ -2022,6 +2026,11 @@ class Model(ObjectWithMeta, VarProvider):
|
|
|
2022
2026
|
for i, comp in enumerate(external_component):
|
|
2023
2027
|
comp._clone2(new_component[i], lhs_map, var_map)
|
|
2024
2028
|
|
|
2029
|
+
# Clone initial values
|
|
2030
|
+
for var in new_states:
|
|
2031
|
+
var.set_initial_value(
|
|
2032
|
+
state_map[var].initial_value().clone(subst=lhs_map))
|
|
2033
|
+
|
|
2025
2034
|
# Time unit conversion? Then update all derivatives.
|
|
2026
2035
|
if time_factor is not None:
|
|
2027
2036
|
for comp in new_component:
|
|
@@ -2031,15 +2040,32 @@ class Model(ObjectWithMeta, VarProvider):
|
|
|
2031
2040
|
var.set_rhs(
|
|
2032
2041
|
myokit.Multiply(rhs, myokit.Number(time_factor)))
|
|
2033
2042
|
|
|
2043
|
+
def initial_values(self, as_floats=False):
|
|
2044
|
+
"""
|
|
2045
|
+
Returns a list of the model's initial values.
|
|
2046
|
+
|
|
2047
|
+
By default, expressions are returned, but this can be changed to
|
|
2048
|
+
a list of floats by setting ``as_floats=True``.
|
|
2049
|
+
"""
|
|
2050
|
+
if as_floats:
|
|
2051
|
+
if any(not e.is_literal() for e in self._state_init):
|
|
2052
|
+
self.validate() # Check for cycles before evaluating
|
|
2053
|
+
return [float(y) for y in self._state_init]
|
|
2054
|
+
return list(self._state_init)
|
|
2055
|
+
|
|
2034
2056
|
def inits(self):
|
|
2035
2057
|
"""
|
|
2036
|
-
Returns an iterator over the ``Equation`` objects
|
|
2037
|
-
|
|
2058
|
+
Deprecated method: Returns an iterator over the ``Equation`` objects
|
|
2059
|
+
defining this model's initial values.
|
|
2038
2060
|
"""
|
|
2061
|
+
# Deprecated since 2023-02-22
|
|
2062
|
+
import warnings
|
|
2063
|
+
warnings.warn('The method `inits` is deprecated.')
|
|
2064
|
+
|
|
2039
2065
|
def StateDefIterator(model):
|
|
2040
|
-
for
|
|
2041
|
-
yield Equation(
|
|
2042
|
-
|
|
2066
|
+
for var, value in zip(model._state_vars, model._state_init):
|
|
2067
|
+
yield Equation(myokit.Name(var), value)
|
|
2068
|
+
|
|
2043
2069
|
return StateDefIterator(self)
|
|
2044
2070
|
|
|
2045
2071
|
def is_similar(self, other, check_unames=False):
|
|
@@ -2134,10 +2160,13 @@ class Model(ObjectWithMeta, VarProvider):
|
|
|
2134
2160
|
|
|
2135
2161
|
def load_state(self, filename):
|
|
2136
2162
|
"""
|
|
2137
|
-
Sets the model
|
|
2138
|
-
accepted by :func:`myokit.
|
|
2163
|
+
Deprecated method: Sets the model's initial values using data from a
|
|
2164
|
+
file formatted in any style accepted by :func:`myokit.map_to_state`.
|
|
2139
2165
|
"""
|
|
2140
|
-
|
|
2166
|
+
# Deprecated since 2023-02-22
|
|
2167
|
+
import warnings
|
|
2168
|
+
warnings.warn('The method `load_state` is deprecated.')
|
|
2169
|
+
self.set_initial_values(myokit.load_state(filename, self))
|
|
2141
2170
|
|
|
2142
2171
|
def map_component_dependencies(
|
|
2143
2172
|
self, omit_states=True, omit_constants=False):
|
|
@@ -2160,7 +2189,7 @@ class Model(ObjectWithMeta, VarProvider):
|
|
|
2160
2189
|
not ``[B,C]``.
|
|
2161
2190
|
|
|
2162
2191
|
By default, dependencies on state variables' current values are
|
|
2163
|
-
omitted. This
|
|
2192
|
+
omitted. This behavior can be changed by setting ``omit_states`` to
|
|
2164
2193
|
``False``.
|
|
2165
2194
|
|
|
2166
2195
|
To omit all dependencies on constants, set ``omit_constants`` to
|
|
@@ -2340,7 +2369,7 @@ class Model(ObjectWithMeta, VarProvider):
|
|
|
2340
2369
|
dependencies will be added to the dependency lists of their parents.
|
|
2341
2370
|
|
|
2342
2371
|
By default, dependencies on state variables' current values are
|
|
2343
|
-
omitted. This
|
|
2372
|
+
omitted. This behavior can be changed by setting ``omit_states`` to
|
|
2344
2373
|
``False``.
|
|
2345
2374
|
|
|
2346
2375
|
In case of a dependency such as::
|
|
@@ -2484,7 +2513,7 @@ class Model(ObjectWithMeta, VarProvider):
|
|
|
2484
2513
|
dependencies will be added to the dependency lists of their parents.
|
|
2485
2514
|
|
|
2486
2515
|
By default, dependencies on state variables' current values are
|
|
2487
|
-
omitted. This
|
|
2516
|
+
omitted. This behavior can be changed by setting ``omit_states`` to
|
|
2488
2517
|
``False``. Dependencies on constants are included by default, but this
|
|
2489
2518
|
can be changed by setting ``omit_constants`` to ``True``.
|
|
2490
2519
|
"""
|
|
@@ -2555,7 +2584,7 @@ class Model(ObjectWithMeta, VarProvider):
|
|
|
2555
2584
|
|
|
2556
2585
|
"""
|
|
2557
2586
|
n = self.count_states()
|
|
2558
|
-
if isinstance(state,
|
|
2587
|
+
if isinstance(state, str):
|
|
2559
2588
|
# String given. Parse into name:float map or list
|
|
2560
2589
|
state = myokit.parse_state(state)
|
|
2561
2590
|
if isinstance(state, dict):
|
|
@@ -2592,45 +2621,6 @@ class Model(ObjectWithMeta, VarProvider):
|
|
|
2592
2621
|
except KeyError:
|
|
2593
2622
|
return None
|
|
2594
2623
|
|
|
2595
|
-
def prepare_bindings(self, labels):
|
|
2596
|
-
"""
|
|
2597
|
-
Takes a mapping of binding labels to internal references as input and
|
|
2598
|
-
returns a mapping of variables to internal references. All variables
|
|
2599
|
-
appearing in the map will have their right hand side set to zero. All
|
|
2600
|
-
bindings not mapped to any internal reference will be deleted.
|
|
2601
|
-
|
|
2602
|
-
The argument ``mapping`` should take the form::
|
|
2603
|
-
|
|
2604
|
-
labels = {
|
|
2605
|
-
'binding_label_1' : internal_name_1,
|
|
2606
|
-
'binding_label_2' : internal_name_2,
|
|
2607
|
-
...
|
|
2608
|
-
}
|
|
2609
|
-
|
|
2610
|
-
The returned dictionary will have the form::
|
|
2611
|
-
|
|
2612
|
-
variables = {
|
|
2613
|
-
variable_x : internal_name_1,
|
|
2614
|
-
variable_y : internal_name_2,
|
|
2615
|
-
...
|
|
2616
|
-
}
|
|
2617
|
-
|
|
2618
|
-
Unsupported bindings (i.e. bindings not appearing in ``labels``) will
|
|
2619
|
-
be ignored.
|
|
2620
|
-
"""
|
|
2621
|
-
unused = []
|
|
2622
|
-
variables = {}
|
|
2623
|
-
for label, var in self._bindings.items():
|
|
2624
|
-
try:
|
|
2625
|
-
variables[var] = labels[label]
|
|
2626
|
-
except KeyError:
|
|
2627
|
-
unused.append(var)
|
|
2628
|
-
continue
|
|
2629
|
-
var.set_rhs(0)
|
|
2630
|
-
for var in unused:
|
|
2631
|
-
var.set_binding(None)
|
|
2632
|
-
return variables
|
|
2633
|
-
|
|
2634
2624
|
def __reduce__(self):
|
|
2635
2625
|
"""
|
|
2636
2626
|
Pickles the model.
|
|
@@ -2701,7 +2691,7 @@ class Model(ObjectWithMeta, VarProvider):
|
|
|
2701
2691
|
|
|
2702
2692
|
This method does not affect the model's validation status.
|
|
2703
2693
|
"""
|
|
2704
|
-
n = len(self.
|
|
2694
|
+
n = len(self._state_vars)
|
|
2705
2695
|
if len(order) != n:
|
|
2706
2696
|
raise ValueError(
|
|
2707
2697
|
'The given list must contain the same number of entries as'
|
|
@@ -2719,11 +2709,11 @@ class Model(ObjectWithMeta, VarProvider):
|
|
|
2719
2709
|
'Duplicate entry in order specification: "'
|
|
2720
2710
|
+ str(v.qname()) + '".')
|
|
2721
2711
|
state.append(v)
|
|
2722
|
-
current.append(self.
|
|
2723
|
-
self.
|
|
2724
|
-
self.
|
|
2712
|
+
current.append(self._state_init[v._index])
|
|
2713
|
+
self._state_vars = state
|
|
2714
|
+
self._state_init = current
|
|
2725
2715
|
for k, v in enumerate(state):
|
|
2726
|
-
v.
|
|
2716
|
+
v._index = k
|
|
2727
2717
|
|
|
2728
2718
|
def remove_component(self, component):
|
|
2729
2719
|
"""
|
|
@@ -2771,7 +2761,7 @@ class Model(ObjectWithMeta, VarProvider):
|
|
|
2771
2761
|
time_unit = time.unit()
|
|
2772
2762
|
|
|
2773
2763
|
# Scan all states
|
|
2774
|
-
for state in self.
|
|
2764
|
+
for state in self._state_vars:
|
|
2775
2765
|
|
|
2776
2766
|
# Search for references to dot(state)
|
|
2777
2767
|
refs = list(state.refs_by())
|
|
@@ -2842,8 +2832,8 @@ class Model(ObjectWithMeta, VarProvider):
|
|
|
2842
2832
|
"""
|
|
2843
2833
|
Resets the indices of this model's state variables.
|
|
2844
2834
|
"""
|
|
2845
|
-
for k, v in enumerate(self.
|
|
2846
|
-
v.
|
|
2835
|
+
for k, v in enumerate(self._state_vars):
|
|
2836
|
+
v._index = k
|
|
2847
2837
|
|
|
2848
2838
|
def _reset_validation(self):
|
|
2849
2839
|
"""
|
|
@@ -2853,7 +2843,11 @@ class Model(ObjectWithMeta, VarProvider):
|
|
|
2853
2843
|
|
|
2854
2844
|
def _resolve(self, name):
|
|
2855
2845
|
""" See :meth:`VarProvider._resolve(). """
|
|
2856
|
-
|
|
2846
|
+
try:
|
|
2847
|
+
return self.get(name)
|
|
2848
|
+
except KeyError:
|
|
2849
|
+
raise myokit.UnresolvedReferenceError(
|
|
2850
|
+
name, self.suggest_variable(name)[2])
|
|
2857
2851
|
|
|
2858
2852
|
def resolve_interdependent_components(self):
|
|
2859
2853
|
"""
|
|
@@ -2887,9 +2881,39 @@ class Model(ObjectWithMeta, VarProvider):
|
|
|
2887
2881
|
|
|
2888
2882
|
def save_state(self, filename):
|
|
2889
2883
|
"""
|
|
2890
|
-
Saves the model state to a file.
|
|
2884
|
+
Deprecated method: Saves the model state to a file (as floats).
|
|
2891
2885
|
"""
|
|
2892
|
-
|
|
2886
|
+
# Deprecated since 2023-02-22
|
|
2887
|
+
import warnings
|
|
2888
|
+
warnings.warn('The method `save_state` is deprecated.')
|
|
2889
|
+
|
|
2890
|
+
return myokit.save_state(
|
|
2891
|
+
filename, self.initial_values(as_floats=True), self)
|
|
2892
|
+
|
|
2893
|
+
def set_initial_values(self, values):
|
|
2894
|
+
"""
|
|
2895
|
+
Sets this model's initial values.
|
|
2896
|
+
|
|
2897
|
+
The ``values`` must be specified as either a list of floats,
|
|
2898
|
+
expressions, and/or strings; or as a dict or string in a format
|
|
2899
|
+
accepted by :meth:`map_to_state`.
|
|
2900
|
+
"""
|
|
2901
|
+
# Use map to state?
|
|
2902
|
+
if isinstance(values, str) or isinstance(values, dict):
|
|
2903
|
+
self._state_init = [
|
|
2904
|
+
myokit.Number(x) for x in self.map_to_state(values)]
|
|
2905
|
+
elif len(values) != len(self._state_vars):
|
|
2906
|
+
raise ValueError('Wrong number of initial values, expecting '
|
|
2907
|
+
+ str(len(self._state_vars)) + '.')
|
|
2908
|
+
else:
|
|
2909
|
+
# Parsing of arguments without making changes, in case it fails.
|
|
2910
|
+
expr = []
|
|
2911
|
+
for var, value in zip(self._state_vars, values):
|
|
2912
|
+
expr.append(var._set_initial_value(value, False))
|
|
2913
|
+
|
|
2914
|
+
# Set all at once, and reset validation status
|
|
2915
|
+
self._state_init = expr
|
|
2916
|
+
self._valid = None
|
|
2893
2917
|
|
|
2894
2918
|
def set_name(self, name=None):
|
|
2895
2919
|
"""
|
|
@@ -2914,10 +2938,14 @@ class Model(ObjectWithMeta, VarProvider):
|
|
|
2914
2938
|
|
|
2915
2939
|
def set_state(self, state):
|
|
2916
2940
|
"""
|
|
2917
|
-
|
|
2918
|
-
:meth:`map_to_state`.
|
|
2941
|
+
Deprecated method: use :meth:`set_initial_values` instead.
|
|
2919
2942
|
"""
|
|
2920
|
-
|
|
2943
|
+
# Deprecated since 2023-02-22
|
|
2944
|
+
import warnings
|
|
2945
|
+
warnings.warn(
|
|
2946
|
+
'The method `set_state` is deprecated. Please use'
|
|
2947
|
+
' `set_initial_values` instead.')
|
|
2948
|
+
self.set_initial_values(state)
|
|
2921
2949
|
|
|
2922
2950
|
def set_value(self, qname, value):
|
|
2923
2951
|
"""
|
|
@@ -2938,6 +2966,9 @@ class Model(ObjectWithMeta, VarProvider):
|
|
|
2938
2966
|
The variable's equation and value are displayed, along with the value
|
|
2939
2967
|
and formula of any nested variables and the values of all dependencies.
|
|
2940
2968
|
"""
|
|
2969
|
+
# Model must be valid, or cycles can occur
|
|
2970
|
+
self.validate()
|
|
2971
|
+
|
|
2941
2972
|
def format_float(number):
|
|
2942
2973
|
s = str(number)
|
|
2943
2974
|
if len(s) < 10:
|
|
@@ -2951,7 +2982,10 @@ class Model(ObjectWithMeta, VarProvider):
|
|
|
2951
2982
|
# Add initial value
|
|
2952
2983
|
rhs = var.rhs()
|
|
2953
2984
|
if var.is_state():
|
|
2954
|
-
|
|
2985
|
+
value = var.initial_value()
|
|
2986
|
+
out.append('Initial value = ' + value.code())
|
|
2987
|
+
if not isinstance(value, myokit.Number):
|
|
2988
|
+
out.append(' = ' + format_float(value))
|
|
2955
2989
|
out.append(spacer)
|
|
2956
2990
|
varname = var.lhs().code()
|
|
2957
2991
|
|
|
@@ -3237,17 +3271,22 @@ class Model(ObjectWithMeta, VarProvider):
|
|
|
3237
3271
|
|
|
3238
3272
|
def state(self):
|
|
3239
3273
|
"""
|
|
3240
|
-
|
|
3241
|
-
|
|
3274
|
+
Deprecated method, use
|
|
3275
|
+
:meth:`initial_values(as_floats=True)<initial_values>` instead.
|
|
3242
3276
|
"""
|
|
3243
|
-
|
|
3277
|
+
# Deprecated since 2023-02-22
|
|
3278
|
+
import warnings
|
|
3279
|
+
warnings.warn(
|
|
3280
|
+
'The method `state` is deprecated. Please use'
|
|
3281
|
+
' `initial_values(as_floats=True)` instead.')
|
|
3282
|
+
return self.initial_values(as_floats=True)
|
|
3244
3283
|
|
|
3245
3284
|
def states(self):
|
|
3246
3285
|
"""
|
|
3247
3286
|
Returns an iterator over this model's state :class:`variable
|
|
3248
3287
|
<myokit.Variable>` objects.
|
|
3249
3288
|
"""
|
|
3250
|
-
return iter(self.
|
|
3289
|
+
return iter(self._state_vars)
|
|
3251
3290
|
|
|
3252
3291
|
def suggest_variable(self, name):
|
|
3253
3292
|
"""
|
|
@@ -3328,10 +3367,7 @@ class Model(ObjectWithMeta, VarProvider):
|
|
|
3328
3367
|
"time". For a valid model, this method always returns a unique
|
|
3329
3368
|
variable. If no time variable has been declared ``None`` is returned.
|
|
3330
3369
|
"""
|
|
3331
|
-
|
|
3332
|
-
return self._bindings['time']
|
|
3333
|
-
except KeyError:
|
|
3334
|
-
return None
|
|
3370
|
+
return self._bindings.get('time')
|
|
3335
3371
|
|
|
3336
3372
|
def timex(self):
|
|
3337
3373
|
"""
|
|
@@ -3373,7 +3409,7 @@ class Model(ObjectWithMeta, VarProvider):
|
|
|
3373
3409
|
|
|
3374
3410
|
def validate(self, remove_unused_variables=False):
|
|
3375
3411
|
"""
|
|
3376
|
-
|
|
3412
|
+
Validates this model and raises errors if any issues are found.
|
|
3377
3413
|
|
|
3378
3414
|
Small issues (e.g. unused variables) will generate warnings, which
|
|
3379
3415
|
can be retrieved using :meth:`Model.warnings()` or
|
|
@@ -3399,6 +3435,15 @@ class Model(ObjectWithMeta, VarProvider):
|
|
|
3399
3435
|
'Invalid time variable set. Time variable must be bound to'
|
|
3400
3436
|
' external value "time".')
|
|
3401
3437
|
|
|
3438
|
+
# Test initial value expresions
|
|
3439
|
+
n = len(self._state_vars)
|
|
3440
|
+
if n != len(self._state_init): # pragma: no cover
|
|
3441
|
+
# Cover pragma: This can only happen if there's an API bug
|
|
3442
|
+
self._valid = False
|
|
3443
|
+
raise myokit.IntegrityError(
|
|
3444
|
+
'Initial values list must have same size as state variables'
|
|
3445
|
+
' list.')
|
|
3446
|
+
|
|
3402
3447
|
# Validation of components, variables
|
|
3403
3448
|
for c in self.components():
|
|
3404
3449
|
if c._parent != self: # pragma: no cover
|
|
@@ -3419,15 +3464,6 @@ class Model(ObjectWithMeta, VarProvider):
|
|
|
3419
3464
|
'Component called <' + c.qname() + '> found at index <'
|
|
3420
3465
|
+ n + '>.')
|
|
3421
3466
|
|
|
3422
|
-
# Test current state values
|
|
3423
|
-
n = len(self._state)
|
|
3424
|
-
if n != len(self._current_state): # pragma: no cover
|
|
3425
|
-
# Cover pragma: This can only happen if there's an API bug
|
|
3426
|
-
self._valid = False
|
|
3427
|
-
raise myokit.IntegrityError(
|
|
3428
|
-
'Current state values list must have same size as state'
|
|
3429
|
-
' variables list.')
|
|
3430
|
-
|
|
3431
3467
|
# Find cycles, warn of unused variables
|
|
3432
3468
|
self._validate_solvability(remove_unused_variables)
|
|
3433
3469
|
|
|
@@ -3481,7 +3517,7 @@ class Model(ObjectWithMeta, VarProvider):
|
|
|
3481
3517
|
|
|
3482
3518
|
# Follow all state variables (unless already visited), all bound
|
|
3483
3519
|
# variables and all used variables.
|
|
3484
|
-
used = [x for x in self.
|
|
3520
|
+
used = [x for x in self._state_vars]
|
|
3485
3521
|
used += [x for x in self._bindings.values()]
|
|
3486
3522
|
used += [x for x in self._labels.values()]
|
|
3487
3523
|
|
|
@@ -3547,7 +3583,7 @@ class Model(ObjectWithMeta, VarProvider):
|
|
|
3547
3583
|
if not isinstance(var, ModelPart):
|
|
3548
3584
|
var = self.suggest_variable(var)
|
|
3549
3585
|
if var[0] is None:
|
|
3550
|
-
if var[1] is None:
|
|
3586
|
+
if var[1] is None: # pragma: no cover
|
|
3551
3587
|
raise Exception(var[2])
|
|
3552
3588
|
var = var[1]
|
|
3553
3589
|
out.append(
|
|
@@ -3606,7 +3642,7 @@ class Component(VarOwner):
|
|
|
3606
3642
|
"""
|
|
3607
3643
|
|
|
3608
3644
|
def __init__(self, model, name):
|
|
3609
|
-
super(
|
|
3645
|
+
super().__init__(model, name)
|
|
3610
3646
|
self._alias_map = {} # Maps variable names to other variables names
|
|
3611
3647
|
|
|
3612
3648
|
def _clone1(self, model, new_name=None):
|
|
@@ -3692,7 +3728,7 @@ class Component(VarOwner):
|
|
|
3692
3728
|
var._delete(recursive=True, ignore_siblings=True)
|
|
3693
3729
|
|
|
3694
3730
|
# Delete links to parent
|
|
3695
|
-
super(
|
|
3731
|
+
super()._delete()
|
|
3696
3732
|
|
|
3697
3733
|
def alias(self, name):
|
|
3698
3734
|
"""
|
|
@@ -3824,10 +3860,10 @@ class Variable(VarOwner):
|
|
|
3824
3860
|
"""
|
|
3825
3861
|
|
|
3826
3862
|
def __init__(self, parent, name):
|
|
3827
|
-
super(
|
|
3863
|
+
super().__init__(parent, name)
|
|
3828
3864
|
|
|
3829
|
-
#
|
|
3830
|
-
self.
|
|
3865
|
+
# Index, only set if this is a state variable
|
|
3866
|
+
self._index = None
|
|
3831
3867
|
|
|
3832
3868
|
# This variable's unit, if given, else dimensionless
|
|
3833
3869
|
self._unit = None
|
|
@@ -3885,7 +3921,11 @@ class Variable(VarOwner):
|
|
|
3885
3921
|
"""
|
|
3886
3922
|
# Set value
|
|
3887
3923
|
if value is None:
|
|
3888
|
-
|
|
3924
|
+
self.model().validate() # Model must be valid before evaluations
|
|
3925
|
+
if self._is_state:
|
|
3926
|
+
value = self.initial_value(True)
|
|
3927
|
+
else:
|
|
3928
|
+
value = self._rhs.eval()
|
|
3889
3929
|
else:
|
|
3890
3930
|
value = float(value)
|
|
3891
3931
|
|
|
@@ -3920,7 +3960,7 @@ class Variable(VarOwner):
|
|
|
3920
3960
|
The argument ``lhs_map`` should be a dictionary mapping old
|
|
3921
3961
|
:class:`LhsExpression` objects their equivalents in the new model.
|
|
3922
3962
|
"""
|
|
3923
|
-
#
|
|
3963
|
+
# _index is set by promoting (done by model)
|
|
3924
3964
|
# _binding
|
|
3925
3965
|
if self._binding:
|
|
3926
3966
|
v.set_binding(self._binding)
|
|
@@ -3942,6 +3982,10 @@ class Variable(VarOwner):
|
|
|
3942
3982
|
for k in self.variables():
|
|
3943
3983
|
k._clone2(v[k.name()], lhs_map)
|
|
3944
3984
|
|
|
3985
|
+
# Note: initial values are stored inside the model, and the state order
|
|
3986
|
+
# depends on the order in which promoting occurs, so states are only
|
|
3987
|
+
# created by Model.clone()
|
|
3988
|
+
|
|
3945
3989
|
def _code(self, b, t):
|
|
3946
3990
|
"""
|
|
3947
3991
|
Create the code for this variable and any child variables.
|
|
@@ -4081,7 +4125,12 @@ class Variable(VarOwner):
|
|
|
4081
4125
|
|
|
4082
4126
|
# For states, update the current/initial value
|
|
4083
4127
|
if self._is_state:
|
|
4084
|
-
|
|
4128
|
+
# Number? Then just multiply. Else use expression.
|
|
4129
|
+
value = self.initial_value()
|
|
4130
|
+
if isinstance(value, myokit.Number):
|
|
4131
|
+
self.set_initial_value(float(value) * float(fw))
|
|
4132
|
+
else:
|
|
4133
|
+
self.set_initial_value(myokit.Multiply(value, fw))
|
|
4085
4134
|
|
|
4086
4135
|
# Update all references to the variable
|
|
4087
4136
|
old_ref = myokit.Name(self)
|
|
@@ -4159,6 +4208,29 @@ class Variable(VarOwner):
|
|
|
4159
4208
|
' can not be removed: it is used by ' + ' and '.join(
|
|
4160
4209
|
['<' + v.qname() + '>' for v in refs]) + '.')
|
|
4161
4210
|
|
|
4211
|
+
# Third check: Do initial values depend on this variable?
|
|
4212
|
+
# Note that, instead of using a cached set in every variable, this
|
|
4213
|
+
# reference is just checked by scanning all init expressions (which
|
|
4214
|
+
# contain a cached set of references).
|
|
4215
|
+
# Note that we don't optimise by checking if this variable is constant,
|
|
4216
|
+
# as it's possible to create (invalid) models where non-constants are
|
|
4217
|
+
# referenced in initial values (but validate() will pick this up!).
|
|
4218
|
+
refs = set()
|
|
4219
|
+
m = self.model()
|
|
4220
|
+
n = myokit.Name(self)
|
|
4221
|
+
for v, e in zip(m._state_vars, m._state_init):
|
|
4222
|
+
if n in e.references():
|
|
4223
|
+
refs.add(v)
|
|
4224
|
+
if ignore_siblings:
|
|
4225
|
+
# Refs from sibling variables are allowed
|
|
4226
|
+
refs = refs.difference(
|
|
4227
|
+
set([x for x in refs if x.has_ancestor(self._parent)]))
|
|
4228
|
+
if refs:
|
|
4229
|
+
raise myokit.IntegrityError(
|
|
4230
|
+
'Variable <' + self.qname() + '> can not be removed: it is'
|
|
4231
|
+
' used in the inital value(s) for ' + ' and '.join(
|
|
4232
|
+
['<' + v.qname() + '>' for v in refs]) + '.')
|
|
4233
|
+
|
|
4162
4234
|
# At this point it's OK to delete. Rest of the code makes changes,
|
|
4163
4235
|
# shouldn't raise errors.
|
|
4164
4236
|
|
|
@@ -4183,7 +4255,6 @@ class Variable(VarOwner):
|
|
|
4183
4255
|
self.set_label(None)
|
|
4184
4256
|
|
|
4185
4257
|
# Remove any aliases
|
|
4186
|
-
m = self.parent(Model)
|
|
4187
4258
|
for c in m.components():
|
|
4188
4259
|
c.remove_aliases_for(self)
|
|
4189
4260
|
|
|
@@ -4196,7 +4267,7 @@ class Variable(VarOwner):
|
|
|
4196
4267
|
self._remove_variable_internal(kid)
|
|
4197
4268
|
|
|
4198
4269
|
# Remove parent links
|
|
4199
|
-
super(
|
|
4270
|
+
super()._delete()
|
|
4200
4271
|
|
|
4201
4272
|
def demote(self):
|
|
4202
4273
|
"""
|
|
@@ -4205,7 +4276,7 @@ class Variable(VarOwner):
|
|
|
4205
4276
|
This will reset the validation status of the model this variable
|
|
4206
4277
|
belongs to.
|
|
4207
4278
|
"""
|
|
4208
|
-
if self.
|
|
4279
|
+
if self._index is None:
|
|
4209
4280
|
raise Exception('Variable is not a state variable.')
|
|
4210
4281
|
|
|
4211
4282
|
# Check that nobody has references to this var's derivative
|
|
@@ -4218,16 +4289,16 @@ class Variable(VarOwner):
|
|
|
4218
4289
|
model = self.model()
|
|
4219
4290
|
try:
|
|
4220
4291
|
# Remove initial value
|
|
4221
|
-
del model.
|
|
4292
|
+
del model._state_init[self._index]
|
|
4222
4293
|
|
|
4223
4294
|
# Remove this variable from the state
|
|
4224
|
-
del model.
|
|
4295
|
+
del model._state_vars[self._index]
|
|
4225
4296
|
|
|
4226
4297
|
# Set lhs to name expression
|
|
4227
4298
|
self._lhs = myokit.Name(self)
|
|
4228
4299
|
|
|
4229
|
-
# Remove this variable's
|
|
4230
|
-
self.
|
|
4300
|
+
# Remove this variable's index
|
|
4301
|
+
self._index = None
|
|
4231
4302
|
|
|
4232
4303
|
# Reset other states' indices
|
|
4233
4304
|
model._reset_indices()
|
|
@@ -4260,14 +4331,43 @@ class Variable(VarOwner):
|
|
|
4260
4331
|
"""
|
|
4261
4332
|
return self._rhs.eval()
|
|
4262
4333
|
|
|
4263
|
-
def
|
|
4334
|
+
def index(self):
|
|
4264
4335
|
"""
|
|
4265
4336
|
For state variables, this will return their index in the state vector.
|
|
4337
|
+
|
|
4266
4338
|
For all other variables, this will raise an exception.
|
|
4267
4339
|
"""
|
|
4268
|
-
if self.
|
|
4340
|
+
if self._index is None:
|
|
4341
|
+
raise Exception('Only state variables have initial values.')
|
|
4342
|
+
return self._index
|
|
4343
|
+
|
|
4344
|
+
def indice(self):
|
|
4345
|
+
""" Deprecated alias of :meth:`index`. """
|
|
4346
|
+
|
|
4347
|
+
# Deprecated on 2023-06-07
|
|
4348
|
+
import warnings
|
|
4349
|
+
warnings.warn(
|
|
4350
|
+
'The method `indice` is deprecated. Please use `index()` instead.')
|
|
4351
|
+
return self.index()
|
|
4352
|
+
|
|
4353
|
+
def initial_value(self, as_float=False):
|
|
4354
|
+
"""
|
|
4355
|
+
Returns a state variable's initial value, or raises an exception when
|
|
4356
|
+
called on a non-state variable.
|
|
4357
|
+
|
|
4358
|
+
By default, a :class:`myokit.Expression` is returned. To evaluate and
|
|
4359
|
+
return a float set ``as_float=True``.
|
|
4360
|
+
"""
|
|
4361
|
+
if not self._is_state:
|
|
4269
4362
|
raise Exception('Only state variables have initial values.')
|
|
4270
|
-
|
|
4363
|
+
|
|
4364
|
+
model = self.model()
|
|
4365
|
+
expr = model._state_init[self._index]
|
|
4366
|
+
if not as_float:
|
|
4367
|
+
return expr
|
|
4368
|
+
if not expr.is_literal():
|
|
4369
|
+
model.validate()
|
|
4370
|
+
return expr.eval()
|
|
4271
4371
|
|
|
4272
4372
|
def is_bound(self):
|
|
4273
4373
|
"""
|
|
@@ -4277,19 +4377,21 @@ class Variable(VarOwner):
|
|
|
4277
4377
|
|
|
4278
4378
|
def is_constant(self):
|
|
4279
4379
|
"""
|
|
4280
|
-
Returns ``True`` if this variable
|
|
4380
|
+
Returns ``True`` if this variable has a constant value (even if that
|
|
4381
|
+
value is defined in terms of other constants).
|
|
4281
4382
|
|
|
4282
4383
|
Myokit doesn't discern between mathematical and physical constants,
|
|
4283
4384
|
parameters etc. Anything that doesn't change during a simulation is
|
|
4284
|
-
termed a constant. Note that this specifically excludes variables
|
|
4285
|
-
to external
|
|
4385
|
+
termed a constant. Note that this specifically excludes variables that
|
|
4386
|
+
define a _binding_ to an external input.
|
|
4286
4387
|
"""
|
|
4287
4388
|
return self._is_constant
|
|
4288
4389
|
|
|
4289
4390
|
def is_intermediary(self):
|
|
4290
4391
|
"""
|
|
4291
4392
|
Returns ``True`` if this variable is an intermediary variable, i.e. not
|
|
4292
|
-
a constant
|
|
4393
|
+
a constant, not a state variable, and not bound to an external input
|
|
4394
|
+
such as time.
|
|
4293
4395
|
"""
|
|
4294
4396
|
return self._is_intermediary
|
|
4295
4397
|
|
|
@@ -4301,8 +4403,7 @@ class Variable(VarOwner):
|
|
|
4301
4403
|
|
|
4302
4404
|
def is_literal(self):
|
|
4303
4405
|
"""
|
|
4304
|
-
Returns ``True`` if this variable
|
|
4305
|
-
values.
|
|
4406
|
+
Returns ``True`` if this variable does not depend on other variables.
|
|
4306
4407
|
"""
|
|
4307
4408
|
return self._is_literal
|
|
4308
4409
|
|
|
@@ -4341,15 +4442,27 @@ class Variable(VarOwner):
|
|
|
4341
4442
|
"""
|
|
4342
4443
|
return self._lhs
|
|
4343
4444
|
|
|
4344
|
-
def promote(self, state_value=
|
|
4445
|
+
def promote(self, initial_value=0, state_value=None):
|
|
4345
4446
|
"""
|
|
4346
|
-
Turns this variable into a state variable with
|
|
4347
|
-
|
|
4447
|
+
Turns this variable into a state variable with an initial value given
|
|
4448
|
+
by ``initial_value``.
|
|
4348
4449
|
|
|
4349
|
-
|
|
4350
|
-
|
|
4450
|
+
The new ``initial_value`` should be:
|
|
4451
|
+
|
|
4452
|
+
1. A numerical value.
|
|
4453
|
+
2. A :class:`myokit.Expression`.
|
|
4454
|
+
3. A string which can be parsed to a :class:`myokit.Expression`. Any
|
|
4455
|
+
references to variables must be made using their fully qualified
|
|
4456
|
+
names.
|
|
4457
|
+
|
|
4458
|
+
Note that expressions can contain references to non-nested and
|
|
4459
|
+
constant-valued variables (i.e. their right-hand side is either a
|
|
4460
|
+
literal expression or refers only to constants).
|
|
4461
|
+
|
|
4462
|
+
Calling ``promote`` will reset the validation status of the model this
|
|
4463
|
+
variable belongs to.
|
|
4351
4464
|
"""
|
|
4352
|
-
if self.
|
|
4465
|
+
if self._index is not None:
|
|
4353
4466
|
raise Exception('Variable is already a state variable')
|
|
4354
4467
|
if not isinstance(self._parent, Component):
|
|
4355
4468
|
raise Exception('State variables can only be added to Components.')
|
|
@@ -4357,26 +4470,40 @@ class Variable(VarOwner):
|
|
|
4357
4470
|
raise Exception(
|
|
4358
4471
|
'State variables cannot be bound to an external value.')
|
|
4359
4472
|
|
|
4360
|
-
#
|
|
4361
|
-
if
|
|
4362
|
-
if
|
|
4363
|
-
raise
|
|
4364
|
-
|
|
4365
|
-
|
|
4473
|
+
# Deprecated on 2023-02-22
|
|
4474
|
+
if state_value is not None:
|
|
4475
|
+
if initial_value != 0:
|
|
4476
|
+
raise Exception('Deprecated keyword argument `state_value` can'
|
|
4477
|
+
' not be used at the same time as its'
|
|
4478
|
+
' replacement `initial_value`.')
|
|
4479
|
+
initial_value = state_value
|
|
4366
4480
|
|
|
4481
|
+
import warnings
|
|
4482
|
+
warnings.warn('The keyword argument `state_value` is deprecated.'
|
|
4483
|
+
' Please use `initial_value` instead.')
|
|
4484
|
+
|
|
4485
|
+
# Handle string and number rhs's
|
|
4367
4486
|
model = self.model()
|
|
4487
|
+
if not isinstance(initial_value, myokit.Expression):
|
|
4488
|
+
if isinstance(initial_value, str):
|
|
4489
|
+
# Expressions are evaluated in model context
|
|
4490
|
+
initial_value = myokit.parse_expression(
|
|
4491
|
+
initial_value, context=model)
|
|
4492
|
+
elif initial_value is not None:
|
|
4493
|
+
initial_value = myokit.Number(initial_value)
|
|
4494
|
+
|
|
4368
4495
|
try:
|
|
4369
4496
|
# Set lhs to derivative expression
|
|
4370
4497
|
self._lhs = myokit.Derivative(myokit.Name(self))
|
|
4371
4498
|
|
|
4372
|
-
# Get new
|
|
4373
|
-
self.
|
|
4499
|
+
# Get new index
|
|
4500
|
+
self._index = len(model._state_vars)
|
|
4374
4501
|
|
|
4375
4502
|
# Add to list of states
|
|
4376
|
-
model.
|
|
4503
|
+
model._state_vars.append(self)
|
|
4377
4504
|
|
|
4378
|
-
# Add
|
|
4379
|
-
model.
|
|
4505
|
+
# Add initial_value to list of current values
|
|
4506
|
+
model._state_init.append(initial_value)
|
|
4380
4507
|
|
|
4381
4508
|
# All references to this variable are now considered references to
|
|
4382
4509
|
# its state value
|
|
@@ -4448,9 +4575,9 @@ class Variable(VarOwner):
|
|
|
4448
4575
|
# Create function
|
|
4449
4576
|
local = {}
|
|
4450
4577
|
if use_numpy:
|
|
4451
|
-
|
|
4578
|
+
exec(func, {'numpy': numpy}, local)
|
|
4452
4579
|
else:
|
|
4453
|
-
|
|
4580
|
+
exec(func, {'math': math}, local)
|
|
4454
4581
|
handle = local['var_pyfunc_generated']
|
|
4455
4582
|
|
|
4456
4583
|
# Return
|
|
@@ -4524,7 +4651,7 @@ class Variable(VarOwner):
|
|
|
4524
4651
|
self._parent.move_variable(self, self._parent, new_name)
|
|
4525
4652
|
|
|
4526
4653
|
def __repr__(self):
|
|
4527
|
-
if self.
|
|
4654
|
+
if self._index is not None:
|
|
4528
4655
|
return '<State(' + self.qname() + ')>'
|
|
4529
4656
|
else:
|
|
4530
4657
|
return '<Var(' + self.qname() + ')>'
|
|
@@ -4539,7 +4666,7 @@ class Variable(VarOwner):
|
|
|
4539
4666
|
s_old = (self._is_bound, self._is_state, self._is_intermediary,
|
|
4540
4667
|
self._is_literal, self._is_constant, self._is_nested)
|
|
4541
4668
|
self._is_bound = self._binding is not None
|
|
4542
|
-
self._is_state = self.
|
|
4669
|
+
self._is_state = self._index is not None
|
|
4543
4670
|
self._is_nested = isinstance(self._parent, Variable)
|
|
4544
4671
|
if self._is_state or self._is_bound or self._rhs is None:
|
|
4545
4672
|
self._is_constant = False
|
|
@@ -4586,7 +4713,7 @@ class Variable(VarOwner):
|
|
|
4586
4713
|
' is already bound to "' + self._binding + '".')
|
|
4587
4714
|
|
|
4588
4715
|
# Check if not a state
|
|
4589
|
-
if self.
|
|
4716
|
+
if self._index is not None:
|
|
4590
4717
|
raise myokit.InvalidBindingError(
|
|
4591
4718
|
'State variables cannot be bound to an external value.')
|
|
4592
4719
|
|
|
@@ -4606,6 +4733,42 @@ class Variable(VarOwner):
|
|
|
4606
4733
|
# Reset model validation
|
|
4607
4734
|
model._reset_validation()
|
|
4608
4735
|
|
|
4736
|
+
def set_initial_value(self, value):
|
|
4737
|
+
"""
|
|
4738
|
+
Sets the initial value of a state variable, or raises an exception if
|
|
4739
|
+
called on a non-state variable.
|
|
4740
|
+
|
|
4741
|
+
The new value can be passed in as an expression, number, or a string
|
|
4742
|
+
(in which case it will be parsed as an expression). Expressions can
|
|
4743
|
+
refer to variables as long as they are not nested and are constant in
|
|
4744
|
+
time. Variable references in strings must be made using fully qualified
|
|
4745
|
+
names (``component.variable``).
|
|
4746
|
+
"""
|
|
4747
|
+
if not self._is_state:
|
|
4748
|
+
raise Exception('Only state variables have state values.')
|
|
4749
|
+
self._set_initial_value(value, True)
|
|
4750
|
+
|
|
4751
|
+
def _set_initial_value(self, value, make_the_change):
|
|
4752
|
+
""" Internal version of `set_initial_value`. """
|
|
4753
|
+
# Handle strings and floats
|
|
4754
|
+
model = self.model()
|
|
4755
|
+
if not isinstance(value, myokit.Expression):
|
|
4756
|
+
if isinstance(value, str):
|
|
4757
|
+
value = myokit.parse_expression(value, context=model)
|
|
4758
|
+
else:
|
|
4759
|
+
value = myokit.Number(value)
|
|
4760
|
+
|
|
4761
|
+
# Allow internal calls to parse `value` without making a change
|
|
4762
|
+
if not make_the_change:
|
|
4763
|
+
return value
|
|
4764
|
+
|
|
4765
|
+
# Update
|
|
4766
|
+
try:
|
|
4767
|
+
model._state_init[self._index] = value
|
|
4768
|
+
finally:
|
|
4769
|
+
# Reset model validation, but not the variable cache
|
|
4770
|
+
model._reset_validation()
|
|
4771
|
+
|
|
4609
4772
|
def set_label(self, label=None):
|
|
4610
4773
|
"""
|
|
4611
4774
|
Adds a unique ``label`` for this variable, indicated that its value can
|
|
@@ -4658,7 +4821,7 @@ class Variable(VarOwner):
|
|
|
4658
4821
|
"""
|
|
4659
4822
|
# Handle string and number rhs's
|
|
4660
4823
|
if not isinstance(rhs, myokit.Expression):
|
|
4661
|
-
if isinstance(rhs,
|
|
4824
|
+
if isinstance(rhs, str):
|
|
4662
4825
|
rhs = myokit.parse_expression(rhs, context=self)
|
|
4663
4826
|
elif rhs is not None:
|
|
4664
4827
|
rhs = myokit.Number(rhs)
|
|
@@ -4693,19 +4856,14 @@ class Variable(VarOwner):
|
|
|
4693
4856
|
|
|
4694
4857
|
def set_state_value(self, value):
|
|
4695
4858
|
"""
|
|
4696
|
-
|
|
4697
|
-
updated. For all other variables this raises an exception.
|
|
4859
|
+
Deprecated method, use :meth:`set_initial_value` instead.
|
|
4698
4860
|
"""
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
'Expressions for state values can not contain references'
|
|
4706
|
-
' to other variables.')
|
|
4707
|
-
model._current_state[self._indice] = float(value)
|
|
4708
|
-
# No need to reset validation status or cache here.
|
|
4861
|
+
# Deprecated since 2023-02-22
|
|
4862
|
+
import warnings
|
|
4863
|
+
warnings.warn(
|
|
4864
|
+
'The method `set_state_value` is deprecated. Please use'
|
|
4865
|
+
' `set_initial_value` instead.')
|
|
4866
|
+
self.set_initial_value(value)
|
|
4709
4867
|
|
|
4710
4868
|
def set_unit(self, unit=None):
|
|
4711
4869
|
"""
|
|
@@ -4714,7 +4872,7 @@ class Variable(VarOwner):
|
|
|
4714
4872
|
"""
|
|
4715
4873
|
if unit is None or isinstance(unit, myokit.Unit):
|
|
4716
4874
|
self._unit = unit
|
|
4717
|
-
elif isinstance(unit,
|
|
4875
|
+
elif isinstance(unit, str):
|
|
4718
4876
|
self._unit = myokit.parse_unit(unit)
|
|
4719
4877
|
else:
|
|
4720
4878
|
raise TypeError('Method set_unit() expects a myokit.Unit or None.')
|
|
@@ -4723,12 +4881,15 @@ class Variable(VarOwner):
|
|
|
4723
4881
|
|
|
4724
4882
|
def state_value(self):
|
|
4725
4883
|
"""
|
|
4726
|
-
|
|
4727
|
-
|
|
4884
|
+
Deprecated method, use
|
|
4885
|
+
:meth:`initial_value(as_float=True)<initial_value>` instead.
|
|
4728
4886
|
"""
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4887
|
+
# Deprecated since 2023-02-22
|
|
4888
|
+
import warnings
|
|
4889
|
+
warnings.warn(
|
|
4890
|
+
'The method `state_value` is deprecated. Please use'
|
|
4891
|
+
' `initial_value(as_float=True)` instead.')
|
|
4892
|
+
return self.initial_value(as_float=True)
|
|
4732
4893
|
|
|
4733
4894
|
def unit(self, mode=myokit.UNIT_TOLERANT):
|
|
4734
4895
|
"""
|
|
@@ -4752,46 +4913,89 @@ class Variable(VarOwner):
|
|
|
4752
4913
|
"""
|
|
4753
4914
|
Attempts to check this variable's validity, raises errors if it isn't.
|
|
4754
4915
|
"""
|
|
4916
|
+
#
|
|
4755
4917
|
# Validate rhs
|
|
4918
|
+
#
|
|
4756
4919
|
if self._rhs is None:
|
|
4757
4920
|
raise myokit.MissingRhsError(self)
|
|
4758
4921
|
self._rhs.validate()
|
|
4759
4922
|
|
|
4760
|
-
#
|
|
4923
|
+
# RHS: No PartialDerivative objects
|
|
4761
4924
|
if self._rhs.contains_type(myokit.PartialDerivative):
|
|
4762
4925
|
raise myokit.IntegrityError(
|
|
4763
4926
|
'Partial derivatives may not appear in expressions set as'
|
|
4764
|
-
' right-hand side of a variable.')
|
|
4927
|
+
' right-hand side of a variable: <' + self.qname() + '>.')
|
|
4765
4928
|
|
|
4766
|
-
#
|
|
4929
|
+
# RHS: No InitialValue objects
|
|
4767
4930
|
if self._rhs.contains_type(myokit.InitialValue):
|
|
4768
4931
|
raise myokit.IntegrityError(
|
|
4769
4932
|
'Initial value operators may not appear in expressions set as'
|
|
4770
|
-
' right-hand side of a variable.')
|
|
4933
|
+
' right-hand side of a variable: <' + self.qname() + '>.')
|
|
4771
4934
|
|
|
4772
|
-
#
|
|
4935
|
+
# RHS: Can't evaluate to True or False
|
|
4773
4936
|
if isinstance(self._rhs, myokit.Condition):
|
|
4774
4937
|
raise myokit.IntegrityError(
|
|
4775
4938
|
'The right-hand side expression for a variable can not be a'
|
|
4776
|
-
' condition.')
|
|
4939
|
+
' condition: <' + self.qname() + '>.')
|
|
4777
4940
|
|
|
4941
|
+
#
|
|
4778
4942
|
# Check state variables
|
|
4779
|
-
|
|
4943
|
+
#
|
|
4944
|
+
is_state = self._index is not None
|
|
4780
4945
|
is_deriv = self.lhs().is_derivative()
|
|
4781
4946
|
if is_state:
|
|
4947
|
+
# Derivative is set
|
|
4782
4948
|
if not is_deriv: # pragma: no cover
|
|
4783
4949
|
raise myokit.IntegrityError(
|
|
4784
4950
|
'Variable <' + self.qname() + '> is listed as a state'
|
|
4785
4951
|
' variable but its lhs is not a derivative.')
|
|
4952
|
+
|
|
4953
|
+
# Not nested
|
|
4786
4954
|
if self._is_nested: # pragma: no cover
|
|
4787
4955
|
raise myokit.IntegrityError(
|
|
4788
4956
|
'State variables should not be nested: <'
|
|
4789
4957
|
+ str(self.qname()) + '>.')
|
|
4958
|
+
|
|
4959
|
+
# Index matches model
|
|
4790
4960
|
m = self.model()
|
|
4791
|
-
if not m.
|
|
4961
|
+
if not m._state_vars[self._index] == self: # pragma: no cover
|
|
4792
4962
|
raise myokit.IntegrityError(
|
|
4793
4963
|
'State variable not listed in model state vector at'
|
|
4794
|
-
' correct
|
|
4964
|
+
' correct index: <' + self.qname() + '>.')
|
|
4965
|
+
|
|
4966
|
+
# Initial value is an expression
|
|
4967
|
+
i = m._state_init[self._index]
|
|
4968
|
+
if not isinstance(i, myokit.Expression): # pragma: no cover
|
|
4969
|
+
raise myokit.IntegrityError(
|
|
4970
|
+
'Initial value for <' + self.qname() + '> is not an'
|
|
4971
|
+
' expression.')
|
|
4972
|
+
|
|
4973
|
+
# Init: No PartialDerivative or InitialValue operators
|
|
4974
|
+
if i.contains_type(myokit.PartialDerivative):
|
|
4975
|
+
raise myokit.IntegrityError(
|
|
4976
|
+
'Partial derivatives may not appear in model expressions:'
|
|
4977
|
+
' initial value for <' + self.qname() + '>.')
|
|
4978
|
+
if i.contains_type(myokit.InitialValue):
|
|
4979
|
+
raise myokit.IntegrityError(
|
|
4980
|
+
'Initial values may not appear in model expressions:'
|
|
4981
|
+
' initial value for < ' + self.qname() + '>.')
|
|
4982
|
+
|
|
4983
|
+
# Init: Can't evaluate to True or False
|
|
4984
|
+
if isinstance(i, myokit.Condition):
|
|
4985
|
+
raise myokit.IntegrityError(
|
|
4986
|
+
'The initial value for a variable can not be a'
|
|
4987
|
+
' condition: <' + self.qname() + '>.')
|
|
4988
|
+
|
|
4989
|
+
# Init: No nested variables or non-constants
|
|
4990
|
+
for ref in i.references():
|
|
4991
|
+
var = ref.var()
|
|
4992
|
+
if var.is_nested():
|
|
4993
|
+
raise myokit.IllegalReferenceInInitialValueError(self, ref)
|
|
4994
|
+
if not var.is_constant():
|
|
4995
|
+
raise myokit.IntegrityError(
|
|
4996
|
+
'Initial value for variable <' + self.qname() + '> is'
|
|
4997
|
+
' not constant: ' + i.code() + '.')
|
|
4998
|
+
|
|
4795
4999
|
elif is_deriv: # pragma: no cover
|
|
4796
5000
|
raise myokit.IntegrityError(
|
|
4797
5001
|
'A derivative was set for <' + self.qname() + '> but this is'
|
|
@@ -4851,7 +5055,7 @@ class Variable(VarOwner):
|
|
|
4851
5055
|
return self._rhs.eval()
|
|
4852
5056
|
|
|
4853
5057
|
|
|
4854
|
-
class Equation
|
|
5058
|
+
class Equation:
|
|
4855
5059
|
"""
|
|
4856
5060
|
Defines an equation: a statement that a left-hand side (LHS) is equal to a
|
|
4857
5061
|
right-hand side (RHS) expression.
|
|
@@ -4886,7 +5090,7 @@ class Equation(object):
|
|
|
4886
5090
|
|
|
4887
5091
|
def code(self):
|
|
4888
5092
|
""" Returns an ``.mmt`` representation of this equation. """
|
|
4889
|
-
b = StringIO()
|
|
5093
|
+
b = io.StringIO()
|
|
4890
5094
|
self._lhs._code(b, None)
|
|
4891
5095
|
b.write(' = ')
|
|
4892
5096
|
self._rhs._code(b, None)
|
|
@@ -4932,7 +5136,7 @@ class EquationList(list, VarProvider):
|
|
|
4932
5136
|
return stream(self)
|
|
4933
5137
|
|
|
4934
5138
|
|
|
4935
|
-
class UserFunction
|
|
5139
|
+
class UserFunction:
|
|
4936
5140
|
"""
|
|
4937
5141
|
Represents a user function.
|
|
4938
5142
|
|