myokit 1.37.0__py3-none-any.whl → 1.37.2__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 (45) hide show
  1. myokit/__init__.py +2 -2
  2. myokit/_aux.py +4 -0
  3. myokit/_datablock.py +10 -10
  4. myokit/_datalog.py +55 -11
  5. myokit/_myokit_version.py +1 -1
  6. myokit/_sim/cvodessim.py +3 -3
  7. myokit/formats/axon/_abf.py +11 -4
  8. myokit/formats/diffsl/__init__.py +60 -0
  9. myokit/formats/diffsl/_ewriter.py +145 -0
  10. myokit/formats/diffsl/_exporter.py +435 -0
  11. myokit/formats/heka/__init__.py +4 -0
  12. myokit/formats/heka/_patchmaster.py +408 -156
  13. myokit/formats/sbml/__init__.py +21 -1
  14. myokit/formats/sbml/_api.py +160 -6
  15. myokit/formats/sbml/_exporter.py +53 -0
  16. myokit/formats/sbml/_writer.py +355 -0
  17. myokit/gui/datalog_viewer.py +17 -3
  18. myokit/tests/data/io/bad1d-2-no-header.zip +0 -0
  19. myokit/tests/data/io/bad1d-3-no-data.zip +0 -0
  20. myokit/tests/data/io/bad1d-4-not-a-zip.zip +1 -105
  21. myokit/tests/data/io/bad1d-5-bad-data-type.zip +0 -0
  22. myokit/tests/data/io/bad1d-6-time-too-short.zip +0 -0
  23. myokit/tests/data/io/bad1d-7-0d-too-short.zip +0 -0
  24. myokit/tests/data/io/bad1d-8-1d-too-short.zip +0 -0
  25. myokit/tests/data/io/bad2d-2-no-header.zip +0 -0
  26. myokit/tests/data/io/bad2d-3-no-data.zip +0 -0
  27. myokit/tests/data/io/bad2d-4-not-a-zip.zip +1 -105
  28. myokit/tests/data/io/bad2d-5-bad-data-type.zip +0 -0
  29. myokit/tests/data/io/bad2d-8-2d-too-short.zip +0 -0
  30. myokit/tests/data/io/block1d.mmt +187 -0
  31. myokit/tests/data/io/datalog-18-duplicate-keys.csv +4 -0
  32. myokit/tests/test_aux.py +4 -0
  33. myokit/tests/test_datablock.py +6 -6
  34. myokit/tests/test_datalog.py +20 -0
  35. myokit/tests/test_formats_diffsl.py +728 -0
  36. myokit/tests/test_formats_exporters_run.py +6 -0
  37. myokit/tests/test_formats_sbml.py +57 -1
  38. myokit/tests/test_sbml_api.py +90 -0
  39. myokit/tests/test_sbml_export.py +327 -0
  40. {myokit-1.37.0.dist-info → myokit-1.37.2.dist-info}/LICENSE.txt +1 -1
  41. {myokit-1.37.0.dist-info → myokit-1.37.2.dist-info}/METADATA +4 -4
  42. {myokit-1.37.0.dist-info → myokit-1.37.2.dist-info}/RECORD +45 -36
  43. {myokit-1.37.0.dist-info → myokit-1.37.2.dist-info}/WHEEL +1 -1
  44. {myokit-1.37.0.dist-info → myokit-1.37.2.dist-info}/entry_points.txt +0 -0
  45. {myokit-1.37.0.dist-info → myokit-1.37.2.dist-info}/top_level.txt +0 -0
@@ -104,6 +104,9 @@ class ExportTest(unittest.TestCase):
104
104
  name = 'test_' + name + '_exporter'
105
105
  self.assertIn(name, methods)
106
106
 
107
+ def test_sbml_exporter(self):
108
+ self._test(myokit.formats.exporter('sbml'))
109
+
107
110
  def test_ansic_exporter(self):
108
111
  self._test(myokit.formats.exporter('ansic'))
109
112
 
@@ -128,6 +131,9 @@ class ExportTest(unittest.TestCase):
128
131
  def test_cuda_kernel_rl_exporter(self):
129
132
  self._test(myokit.formats.exporter('cuda-kernel-rl'))
130
133
 
134
+ def test_diffsl_exporter(self):
135
+ self._test(myokit.formats.exporter('diffsl'))
136
+
131
137
  def test_easyml_exporter(self):
132
138
  self._test(myokit.formats.exporter('easyml'))
133
139
 
@@ -15,7 +15,63 @@ import myokit.formats
15
15
  import myokit.formats.sbml
16
16
 
17
17
  # from shared import DIR_FORMATS, WarningCollector
18
- from myokit.tests import DIR_FORMATS, WarningCollector
18
+ from myokit.tests import DIR_FORMATS, WarningCollector, TemporaryDirectory
19
+
20
+
21
+ class SBMLExporterTest(unittest.TestCase):
22
+ """
23
+ Tests for :class:`myokit.formats.sbml.SBMLExporter`.
24
+ """
25
+
26
+ def test_capability_reporting(self):
27
+ # Test if the right capabilities are reported.
28
+ e = myokit.formats.exporter('sbml')
29
+ self.assertTrue(e.supports_model())
30
+ self.assertFalse(e.supports_runnable())
31
+
32
+ def test_stimulus_generation(self):
33
+ # Tests if protocols allow a stimulus current to be added
34
+
35
+ e = myokit.formats.exporter('sbml')
36
+ i = myokit.formats.importer('sbml')
37
+
38
+ # Load input model
39
+ m1, p1, _ = myokit.load('example')
40
+ org_code = m1.code()
41
+
42
+ # 1. Export without a protocol
43
+ with TemporaryDirectory() as d:
44
+ path = d.path('model.sbml')
45
+ with WarningCollector() as w:
46
+ e.model(path, m1)
47
+ m2 = i.model(path)
48
+ self.assertFalse(w.has_warnings())
49
+ self.assertTrue(isinstance(m2.get('global.pace').rhs(), myokit.Number))
50
+
51
+ # 2. Export with protocol, but without variable bound to pacing
52
+ m1.get('engine.pace').set_binding(None)
53
+ with TemporaryDirectory() as d:
54
+ path = d.path('model.sbml')
55
+ with WarningCollector() as w:
56
+ e.model(path, m1, p1)
57
+ m2 = i.model(path)
58
+ self.assertTrue(w.has_warnings())
59
+ self.assertTrue(isinstance(m2.get('global.pace').rhs(), myokit.Number))
60
+
61
+ # 3. Export with protocol and variable bound to pacing
62
+ m1.get('engine.pace').set_binding('pace')
63
+ with TemporaryDirectory() as d:
64
+ path = d.path('model.cellml')
65
+ with WarningCollector() as w:
66
+ e.model(path, m1, p1)
67
+ m2 = i.model(path)
68
+ self.assertFalse(w.has_warnings())
69
+ rhs = m2.get('global.i_stim').rhs()
70
+ self.assertTrue(rhs, myokit.Multiply)
71
+ self.assertTrue(isinstance(rhs[0], myokit.Piecewise))
72
+
73
+ # Check original model is unchanged
74
+ self.assertEqual(org_code, m1.code())
19
75
 
20
76
 
21
77
  class SBMLImporterTest(unittest.TestCase):
@@ -2181,6 +2181,96 @@ class SBMLTestMyokitModel(unittest.TestCase):
2181
2181
  myokit.Plus(myokit.Number(1), myokit.Name(m.time())))
2182
2182
 
2183
2183
 
2184
+ class SBMLTestModelFromMyokit(unittest.TestCase):
2185
+ def test_basic(self):
2186
+ m = myokit.Model()
2187
+ c = m.add_component('comp')
2188
+ v = c.add_variable('var')
2189
+ v.set_rhs(myokit.Number(3))
2190
+ t = c.add_variable('time')
2191
+ t.set_binding('time')
2192
+ t.set_rhs(myokit.Number(0))
2193
+
2194
+ s = sbml.Model.from_myokit_model(m)
2195
+ compartment_names = [c.sid() for c in s.compartments()]
2196
+ self.assertCountEqual(compartment_names, [])
2197
+ parameter_names = [v.sid() for v in s.parameters()]
2198
+ self.assertCountEqual(parameter_names, ['var'])
2199
+
2200
+ def test_vars_with_same_name(self):
2201
+ m = myokit.Model()
2202
+ c = m.add_component('comp')
2203
+ v = c.add_variable('var')
2204
+ v.set_rhs(myokit.Number(3))
2205
+ c = m.add_component('comp2')
2206
+ v = c.add_variable('var')
2207
+ v.set_rhs(myokit.Number(3))
2208
+ t = c.add_variable('time')
2209
+ t.set_binding('time')
2210
+ t.set_rhs(myokit.Number(0))
2211
+
2212
+ s = sbml.Model.from_myokit_model(m)
2213
+ compartment_names = [c.sid() for c in s.compartments()]
2214
+ self.assertCountEqual(compartment_names, [])
2215
+ parameter_names = [v.sid() for v in s.parameters()]
2216
+ self.assertCountEqual(parameter_names, ['comp2_var', 'comp_var'])
2217
+
2218
+ def test_rate_eqn_with_unit(self):
2219
+ m = myokit.Model()
2220
+ c = m.add_component('comp')
2221
+
2222
+ t = c.add_variable('time', rhs=myokit.Number(0))
2223
+ t.set_unit(myokit.units.second)
2224
+ t.set_binding('time')
2225
+
2226
+ p = c.add_variable('param')
2227
+ p.set_rhs(myokit.Number(2))
2228
+
2229
+ v = c.add_variable('var', initial_value=myokit.Number(1))
2230
+ v_unit = 1e3 * myokit.units.meter
2231
+ v.set_unit(v_unit)
2232
+ v.set_rhs(myokit.Multiply(myokit.Number(4), myokit.Name(p)))
2233
+
2234
+ v = c.add_variable('var2', initial_value=myokit.Number(1))
2235
+ v.set_unit(myokit.units.meter)
2236
+ v.set_rhs(myokit.Number(4))
2237
+
2238
+ s = sbml.Model.from_myokit_model(m)
2239
+ parameter_names = [v.sid() for v in s.parameters()]
2240
+ self.assertCountEqual(parameter_names, ['var', 'var2', 'param'])
2241
+ # only non-base unit is kilometer
2242
+ self.assertCountEqual(s.units().values(), [v_unit])
2243
+ self.assertEqual(s.time_units(), myokit.units.second)
2244
+ v = s.parameter('var')
2245
+ self.assertEqual(v.initial_value(), myokit.Number(1))
2246
+ self.assertEqual(
2247
+ v.value(),
2248
+ myokit.Multiply(myokit.Number(4), myokit.Name(p))
2249
+ )
2250
+
2251
+ def test_incompatible_unit(self):
2252
+ m = myokit.Model()
2253
+ c = m.add_component('comp')
2254
+
2255
+ t = c.add_variable('time', rhs=myokit.Number(0))
2256
+ t.set_unit(myokit.units.second)
2257
+ t.set_binding('time')
2258
+
2259
+ p = c.add_variable('param')
2260
+ p.set_rhs(myokit.Number(2))
2261
+ p.set_unit(myokit.units.meter)
2262
+
2263
+ v = c.add_variable('var', initial_value=myokit.Number(1))
2264
+ v.set_rhs(myokit.Plus(myokit.Name(p), myokit.Name(t)))
2265
+
2266
+ s = sbml.Model.from_myokit_model(m)
2267
+ parameter_names = [v.sid() for v in s.parameters()]
2268
+ self.assertCountEqual(parameter_names, ['var', 'param'])
2269
+
2270
+ p = s.parameter('var')
2271
+ self.assertIsNone(p.units())
2272
+
2273
+
2184
2274
  if __name__ == '__main__':
2185
2275
  import warnings
2186
2276
  warnings.simplefilter('always')
@@ -0,0 +1,327 @@
1
+ #!/usr/bin/env python3
2
+ #
3
+ # Tests Myokit's SBML support.
4
+ #
5
+ # This file is part of Myokit.
6
+ # See http://myokit.org for copyright, sharing, and licensing details.
7
+ #
8
+ import unittest
9
+
10
+ import myokit
11
+ import myokit.formats
12
+
13
+ import myokit.formats.sbml
14
+ from myokit.formats.sbml._api import Model
15
+ from myokit.formats.sbml._writer import write_string
16
+ import myokit.formats.sbml as sbml
17
+
18
+
19
+ class TestSBMLExport(unittest.TestCase):
20
+ """
21
+ Unit tests for the SBML export functionality.
22
+ """
23
+
24
+ def test_empty_model(self):
25
+ # Test exporting an empty model
26
+ model = Model()
27
+ sbml_str = write_string(model).decode("utf8")
28
+ self.assertIn("<sbml", sbml_str)
29
+ self.assertIn("</sbml>", sbml_str)
30
+ self.assertIn('<model id="unnamed_model"/>', sbml_str)
31
+
32
+ def test_time_unit(self):
33
+ # Test setting the time unit
34
+ model = Model()
35
+ model.set_time_units(myokit.units.second)
36
+ sbml_str = write_string(model).decode("utf8")
37
+ self.assertIn('timeUnits="second"', sbml_str)
38
+
39
+ model = Model()
40
+ model.set_time_units(1e3 * myokit.units.second)
41
+ sbml_str = write_string(model).decode("utf8")
42
+ self.assertIn('<listOfUnitDefinitions>', sbml_str)
43
+ self.assertIn('<unitDefinition id="s_times_1e3">', sbml_str)
44
+
45
+ def test_area_unit(self):
46
+ # Test setting the area unit
47
+ model = Model()
48
+ model.set_area_units(myokit.units.metre)
49
+ sbml_str = write_string(model).decode("utf8")
50
+ self.assertIn('areaUnits="metre"', sbml_str)
51
+
52
+ def test_volume_unit(self):
53
+ # Test setting the volume unit
54
+ model = Model()
55
+ model.set_volume_units(myokit.units.litre)
56
+ sbml_str = write_string(model).decode("utf8")
57
+ self.assertIn('volumeUnits="litre"', sbml_str)
58
+
59
+ def test_substance_unit(self):
60
+ # Test setting the substance unit
61
+ model = Model()
62
+ model.set_substance_units(myokit.units.mole)
63
+ sbml_str = write_string(model).decode("utf8")
64
+ self.assertIn('substanceUnits="mole"', sbml_str)
65
+
66
+ def test_extent_unit(self):
67
+ # Test setting the extent unit
68
+ model = Model()
69
+ model.set_extent_units(myokit.units.mole)
70
+ sbml_str = write_string(model).decode("utf8")
71
+ self.assertIn('extentUnits="mole"', sbml_str)
72
+
73
+ def test_length_unit(self):
74
+ # Test setting the length unit
75
+ model = Model()
76
+ model.set_length_units(myokit.units.metre)
77
+ sbml_str = write_string(model).decode("utf8")
78
+ self.assertIn('lengthUnits="metre"', sbml_str)
79
+
80
+ def test_list_of_unit_definitions(self):
81
+ # Test setting a list of unit definitions
82
+ model = Model()
83
+ model.add_unit("my_unit", myokit.units.ampere)
84
+ model.add_unit("my_unit2", 2 * myokit.units.dimensionless)
85
+ sbml_str = write_string(model).decode("utf8")
86
+ self.assertIn("<listOfUnitDefinitions>", sbml_str)
87
+ self.assertIn('<unitDefinition id="my_unit">', sbml_str)
88
+ self.assertIn(
89
+ '<unit kind="ampere" exponent="1.0" multiplier="1.0"/>',
90
+ sbml_str
91
+ )
92
+ self.assertIn('<unitDefinition id="my_unit2">', sbml_str)
93
+ self.assertIn(
94
+ '<unit kind="dimensionless" multiplier="2.0"/>',
95
+ sbml_str
96
+ )
97
+
98
+ def test_list_of_compartments(self):
99
+ # Test setting a list of compartments
100
+ model = Model()
101
+ c = model.add_compartment("my_compartment")
102
+ c.set_size_units(myokit.units.litre)
103
+ c.set_spatial_dimensions(3)
104
+ c = model.add_compartment("my_compartment2")
105
+ c.set_size_units(1e3 * myokit.units.metre ** 3)
106
+ sbml_str = write_string(model).decode("utf8")
107
+ self.assertIn("<listOfCompartments>", sbml_str)
108
+ self.assertIn(
109
+ '<compartment id="my_compartment" units="litre" spatialDimensions="3.0"/>', # noqa: E501
110
+ sbml_str
111
+ )
112
+ self.assertIn(
113
+ '<compartment id="my_compartment2" units="m3_times_1e3"/>',
114
+ sbml_str
115
+ )
116
+ self.assertIn("<listOfUnitDefinitions>", sbml_str)
117
+ self.assertIn('<unitDefinition id="m3_times_1e3">', sbml_str)
118
+
119
+ def test_constants_and_literals(self):
120
+ m = myokit.Model()
121
+ c = m.add_component('comp')
122
+ t = c.add_variable('time', rhs=myokit.Number(0))
123
+ t.set_unit(myokit.units.second)
124
+ t.set_binding('time')
125
+ v = c.add_variable('my_parameter')
126
+ v.set_rhs(myokit.Number(1))
127
+ v2 = c.add_variable('my_parameter2')
128
+ v2.set_rhs(myokit.Multiply(myokit.Number(2), myokit.Name(v)))
129
+
130
+ # check that the equation is exported correctly to sbml.Model
131
+ s = sbml.Model.from_myokit_model(m)
132
+ parameter_names = [v.sid() for v in s.parameters()]
133
+ self.assertCountEqual(
134
+ parameter_names, ['my_parameter', 'my_parameter2']
135
+ )
136
+ sbml_v = s.parameter('my_parameter')
137
+ sbml_v2 = s.parameter('my_parameter2')
138
+ self.assertEqual(sbml_v.value(), myokit.Number(1))
139
+ self.assertIsNone(sbml_v.initial_value())
140
+ self.assertEqual(
141
+ sbml_v2.initial_value(),
142
+ myokit.Multiply(myokit.Number(2), myokit.Name(v))
143
+ )
144
+ self.assertIsNone(sbml_v2.value())
145
+ self.assertTrue(sbml_v.is_constant())
146
+ self.assertTrue(sbml_v2.is_constant())
147
+ self.assertFalse(sbml_v2.is_literal())
148
+ self.assertTrue(sbml_v.is_literal())
149
+
150
+ def test_list_of_parameters(self):
151
+ # Test setting a list of parameters
152
+ model = Model()
153
+ p = model.add_parameter("my_parameter")
154
+ p.set_value(myokit.Number(1))
155
+ p = model.add_parameter("my_parameter2")
156
+ p.set_units(1e3 * myokit.units.metre / myokit.units.second)
157
+ p.set_value(myokit.Number(2))
158
+ sbml_str = write_string(model).decode("utf8")
159
+ self.assertIn("<listOfParameters>", sbml_str)
160
+ self.assertIn(
161
+ '<parameter id="my_parameter" constant="true" value="1.0"/>',
162
+ sbml_str
163
+ )
164
+ self.assertIn(
165
+ '<parameter id="my_parameter2" units="m_per_s_times_1e3" constant="true" value="2.0"/>', # noqa: E501
166
+ sbml_str
167
+ )
168
+ self.assertNotIn("<listOfRules>", sbml_str)
169
+ self.assertNotIn("<listOfInitialAssignments>", sbml_str)
170
+ self.assertIn("<listOfUnitDefinitions>", sbml_str)
171
+ self.assertIn('<unitDefinition id="m_per_s_times_1e3">', sbml_str)
172
+
173
+ # Test setting a constant parameter that depends on another parameter
174
+ model = Model()
175
+ p = model.add_parameter("my_parameter")
176
+ p.set_value(myokit.Number(1))
177
+ p = model.add_parameter("my_parameter2")
178
+ p.set_initial_value(
179
+ myokit.Multiply(myokit.Name("my_parameter"), myokit.Number(2))
180
+ )
181
+ sbml_str = write_string(model).decode("utf8")
182
+ self.assertIn("<listOfParameters>", sbml_str)
183
+ self.assertIn(
184
+ '<parameter id="my_parameter" constant="true" value="1.0"/>',
185
+ sbml_str
186
+ )
187
+ self.assertIn(
188
+ '<parameter id="my_parameter2" constant="true"/>', # noqa: E501
189
+ sbml_str
190
+ )
191
+ self.assertNotIn("<listOfRules>", sbml_str)
192
+ self.assertIn("<listOfInitialAssignments>", sbml_str)
193
+ self.assertIn('<initialAssignment symbol="my_parameter2">', sbml_str)
194
+ self.assertIn("<times/>\n <ci>my_parameter</ci>\n <cn>2.0</cn>", sbml_str) # noqa: E501
195
+
196
+ model = Model()
197
+ p = model.add_parameter("my_parameter", is_constant=False)
198
+ p.set_initial_value(myokit.Number(1))
199
+ p.set_value(myokit.Number(2))
200
+ sbml_str = write_string(model).decode("utf8")
201
+ self.assertIn("<listOfRules>", sbml_str)
202
+ self.assertIn('<assignmentRule variable="my_parameter">', sbml_str)
203
+ self.assertIn("<cn>2.0</cn>", sbml_str)
204
+ self.assertIn("<listOfInitialAssignments>", sbml_str)
205
+ self.assertIn('<initialAssignment symbol="my_parameter">', sbml_str)
206
+ self.assertIn("<cn>1.0</cn>", sbml_str)
207
+
208
+ def test_list_of_species(self):
209
+ # Test setting a list of species
210
+ model = Model()
211
+ c = model.add_compartment("my_compartment")
212
+ s = model.add_species(c, "my_species")
213
+ s.set_substance_units(myokit.units.mole / myokit.units.litre)
214
+ s.set_value(myokit.Number(1), True)
215
+ s.set_initial_value(myokit.Number(2))
216
+ s = model.add_species(c, "my_species2")
217
+ s.set_substance_units(myokit.units.mole)
218
+ s.set_value(myokit.Number(1), True)
219
+ s.set_initial_value(myokit.Number(2), in_amount=True)
220
+ s = model.add_species(c, "my_species3", is_amount=True)
221
+ s.set_value(myokit.Number(1), True)
222
+ s.set_initial_value(myokit.Number(2))
223
+ s = model.add_species(c, "my_species4", is_amount=True)
224
+ s.set_value(myokit.Number(1), True)
225
+ s.set_initial_value(myokit.Number(2), in_amount=False)
226
+ s = model.add_species(c, "my_species5")
227
+ s.set_value(myokit.Number(1), False)
228
+ sbml_str = write_string(model).decode("utf8")
229
+ self.assertIn("<listOfSpecies>", sbml_str)
230
+ self.assertIn(
231
+ '<species id="my_species" compartment="my_compartment" initialConcentration="2.0" constant="False" units="M" boundaryCondition="False"/>', # noqa: E501
232
+ sbml_str,
233
+ )
234
+ self.assertIn(
235
+ '<species id="my_species2" compartment="my_compartment" initialAmount="2.0" constant="False" units="mole" boundaryCondition="False"/>', # noqa: E501
236
+ sbml_str,
237
+ )
238
+ self.assertIn(
239
+ '<species id="my_species3" compartment="my_compartment" initialAmount="2.0" constant="False" boundaryCondition="False"/>', # noqa: E501
240
+ sbml_str,
241
+ )
242
+ self.assertIn(
243
+ '<species id="my_species4" compartment="my_compartment" initialConcentration="2.0" constant="False" boundaryCondition="False"/>', # noqa: E501
244
+ sbml_str,
245
+ )
246
+ self.assertIn("<listOfRules>", sbml_str)
247
+ self.assertIn('<rateRule variable="my_species">', sbml_str)
248
+ self.assertIn("<cn>1.0</cn>", sbml_str)
249
+ self.assertIn('<assignmentRule variable="my_species5">', sbml_str)
250
+
251
+ def test_list_of_reactions(self):
252
+ # test kinetic law
253
+ model = Model()
254
+ c = model.add_compartment("my_compartment")
255
+ s = model.add_species(c, "my_species")
256
+ s2 = model.add_species(c, "my_species2")
257
+ r = model.add_reaction("my_reaction")
258
+ r.add_reactant(s, "my_reaction_reactant")
259
+ r.add_product(s)
260
+ r.add_modifier(s)
261
+ r.add_modifier(s2, "my_modifier")
262
+ r.set_kinetic_law(myokit.Number(1))
263
+ sbml_str = write_string(model).decode("utf8")
264
+ self.assertIn("<listOfReactions>", sbml_str)
265
+ self.assertIn('<reaction id="my_reaction">', sbml_str)
266
+ self.assertIn("<listOfReactants>", sbml_str)
267
+ self.assertIn("<listOfProducts>", sbml_str)
268
+ self.assertIn('<speciesReference species="my_species"/>', sbml_str)
269
+ self.assertIn('<speciesReference species="my_species" id="my_reaction_reactant"/>', sbml_str) # noqa: E501
270
+ self.assertIn("<listOfModifiers>", sbml_str)
271
+ self.assertIn(
272
+ '<modifierSpeciesReference species="my_species"/>',
273
+ sbml_str
274
+ )
275
+ self.assertIn(
276
+ '<modifierSpeciesReference species="my_species2" id="my_modifier"/>', # noqa: E501
277
+ sbml_str
278
+ )
279
+ self.assertIn("<kineticLaw>", sbml_str)
280
+ self.assertIn("<cn>1.0</cn>", sbml_str)
281
+
282
+ # test stochoimetry
283
+ model = Model()
284
+ c = model.add_compartment("my_compartment")
285
+ s = model.add_species(c, "my_species")
286
+ r = model.add_reaction("my_reaction")
287
+ react = r.add_reactant(s)
288
+ react.set_value(myokit.Number(1))
289
+ r.add_product(s)
290
+ sbml_str = write_string(model).decode("utf8")
291
+ self.assertIn(
292
+ '<speciesReference species="my_species" stoichiometry="1.0"/>',
293
+ sbml_str
294
+ )
295
+
296
+ def test_expressions_multiple_compartments(self):
297
+ m = myokit.Model()
298
+ c = m.add_component('comp')
299
+ t = c.add_variable('time', rhs=myokit.Number(0))
300
+ t.set_unit(myokit.units.second)
301
+ t.set_binding('time')
302
+ c2 = m.add_component('comp2')
303
+ v = c.add_variable('var', initial_value=3)
304
+ v.set_rhs(myokit.Name(t))
305
+ v2 = c2.add_variable('var', initial_value=3)
306
+ v2.set_rhs(myokit.Name(v))
307
+
308
+ # check that the equation is exported correctly to sbml.Model
309
+ s = sbml.Model.from_myokit_model(m)
310
+ parameter_names = [v.sid() for v in s.parameters()]
311
+ self.assertCountEqual(parameter_names, ['comp_var', 'comp2_var'])
312
+ v2_sbml = s.parameter('comp2_var')
313
+ self.assertEqual(
314
+ v2_sbml.value(),
315
+ myokit.Name(v)
316
+ )
317
+
318
+ # check that the equation is exported correctly to sbml string
319
+ sbml_str = write_string(s).decode("utf8")
320
+ self.assertIn('<rateRule variable="comp2_var">', sbml_str)
321
+ self.assertIn('<ci>comp_var</ci>', sbml_str)
322
+ self.assertIn('<rateRule variable="comp_var">', sbml_str)
323
+ self.assertIn(
324
+ '<ci>http://www.sbml.org/sbml/symbols/time</ci>',
325
+ sbml_str
326
+ )
327
+
@@ -3,7 +3,7 @@ BSD 3-Clause License
3
3
  Copyright (c) 2011-2017 Maastricht University. All rights reserved.
4
4
  Copyright (c) 2017-2020 University of Oxford. All rights reserved.
5
5
  (University of Oxford means the Chancellor, Masters and Scholars of the University of Oxford, having an administrative office at Wellington Square, Oxford OX1 2JD, UK).
6
- Copyright (c) 2020-2024 University of Nottingham. All rights reserved.
6
+ Copyright (c) 2020-2025 University of Nottingham. All rights reserved.
7
7
 
8
8
  Redistribution and use in source and binary forms, with or without
9
9
  modification, are permitted provided that the following conditions are met:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: myokit
3
- Version: 1.37.0
3
+ Version: 1.37.2
4
4
  Summary: A modeling and simulation tool for cardiac cellular electrophysiology
5
5
  Home-page: http://myokit.org
6
6
  Author: Michael Clerx
@@ -25,14 +25,14 @@ Description-Content-Type: text/markdown
25
25
  License-File: LICENSE.txt
26
26
  Requires-Dist: configparser
27
27
  Requires-Dist: lxml
28
- Requires-Dist: matplotlib (>=2.2)
28
+ Requires-Dist: matplotlib >=2.2
29
29
  Requires-Dist: numpy
30
30
  Requires-Dist: setuptools
31
31
  Provides-Extra: dev
32
32
  Requires-Dist: coverage ; extra == 'dev'
33
- Requires-Dist: flake8 (>=3) ; extra == 'dev'
33
+ Requires-Dist: flake8 >=3 ; extra == 'dev'
34
34
  Provides-Extra: docs
35
- Requires-Dist: sphinx (>=1.7.4) ; extra == 'docs'
35
+ Requires-Dist: sphinx >=1.7.4 ; extra == 'docs'
36
36
  Provides-Extra: gui
37
37
  Requires-Dist: pyqt6 ; extra == 'gui'
38
38
  Requires-Dist: sip ; extra == 'gui'