myokit 1.37.1__py3-none-any.whl → 1.37.3__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 +2 -2
- myokit/_datalog.py +15 -6
- myokit/_myokit_version.py +1 -1
- myokit/_sim/cvodessim.py +3 -3
- myokit/_unit.py +48 -40
- myokit/formats/heka/__init__.py +4 -0
- myokit/formats/heka/_patchmaster.py +128 -100
- myokit/formats/sbml/__init__.py +21 -1
- myokit/formats/sbml/_api.py +160 -6
- myokit/formats/sbml/_exporter.py +53 -0
- myokit/formats/sbml/_writer.py +355 -0
- myokit/tests/test_formats_exporters_run.py +3 -0
- myokit/tests/test_formats_sbml.py +57 -1
- myokit/tests/test_quantity.py +2 -2
- myokit/tests/test_sbml_api.py +90 -0
- myokit/tests/test_sbml_export.py +327 -0
- myokit/tests/test_unit.py +28 -7
- {myokit-1.37.1.dist-info → myokit-1.37.3.dist-info}/LICENSE.txt +1 -1
- {myokit-1.37.1.dist-info → myokit-1.37.3.dist-info}/METADATA +4 -4
- {myokit-1.37.1.dist-info → myokit-1.37.3.dist-info}/RECORD +23 -20
- {myokit-1.37.1.dist-info → myokit-1.37.3.dist-info}/WHEEL +1 -1
- {myokit-1.37.1.dist-info → myokit-1.37.3.dist-info}/entry_points.txt +0 -0
- {myokit-1.37.1.dist-info → myokit-1.37.3.dist-info}/top_level.txt +0 -0
myokit/formats/sbml/_api.py
CHANGED
|
@@ -5,11 +5,13 @@
|
|
|
5
5
|
# See http://myokit.org for copyright, sharing, and licensing details.
|
|
6
6
|
#
|
|
7
7
|
import collections
|
|
8
|
+
from typing import Dict
|
|
8
9
|
import warnings
|
|
9
10
|
import re
|
|
10
11
|
|
|
11
12
|
import myokit
|
|
12
13
|
import myokit.units
|
|
14
|
+
from myokit.formats.cellml.v2._api import create_unit_name as cellml_create_unit_name # noqa: E501
|
|
13
15
|
|
|
14
16
|
|
|
15
17
|
# Regex for id checking
|
|
@@ -156,6 +158,7 @@ class Compartment(Quantity):
|
|
|
156
158
|
'<' + str(units) + '> needs to be instance of myokit.Unit')
|
|
157
159
|
|
|
158
160
|
self._size_units = units
|
|
161
|
+
self._model.add_unit_if_possible(units)
|
|
159
162
|
|
|
160
163
|
def sid(self):
|
|
161
164
|
"""Returns this compartment's sid."""
|
|
@@ -247,6 +250,99 @@ class Model:
|
|
|
247
250
|
# CSymbolVariable for time
|
|
248
251
|
self._time = CSymbolVariable(_SBML_TIME)
|
|
249
252
|
|
|
253
|
+
@staticmethod
|
|
254
|
+
def create_unit_name(unit):
|
|
255
|
+
if unit in Model._base_units_reverse:
|
|
256
|
+
return Model._base_units_reverse[unit]
|
|
257
|
+
return cellml_create_unit_name(unit)
|
|
258
|
+
|
|
259
|
+
@staticmethod
|
|
260
|
+
def from_myokit_model(model: myokit.Model) -> 'Model':
|
|
261
|
+
"""
|
|
262
|
+
Creates an SBML model from a :class:`myokit.Model`.
|
|
263
|
+
"""
|
|
264
|
+
# Model must be valid
|
|
265
|
+
# Otherwise could have cycles, invalid references, etc.
|
|
266
|
+
model.validate()
|
|
267
|
+
|
|
268
|
+
# Get name for model
|
|
269
|
+
name = model.name()
|
|
270
|
+
if name is None:
|
|
271
|
+
name = 'unnamed_myokit_model'
|
|
272
|
+
|
|
273
|
+
# Create model
|
|
274
|
+
m = Model(name)
|
|
275
|
+
|
|
276
|
+
# Valid model always has a time variable
|
|
277
|
+
time = model.time()
|
|
278
|
+
|
|
279
|
+
# Method to obtain or infer variable unit
|
|
280
|
+
def variable_unit(variable, time_unit):
|
|
281
|
+
"""Returns variable.unit(), or attempts to infer it if not set."""
|
|
282
|
+
unit = variable.unit()
|
|
283
|
+
if unit is not None:
|
|
284
|
+
return unit
|
|
285
|
+
|
|
286
|
+
rhs = variable.rhs()
|
|
287
|
+
if rhs is not None:
|
|
288
|
+
try:
|
|
289
|
+
# If unit not set try to infer from the rhs.
|
|
290
|
+
# Result may be None.
|
|
291
|
+
unit = rhs.eval_unit(myokit.UNIT_TOLERANT)
|
|
292
|
+
except myokit.IncompatibleUnitError:
|
|
293
|
+
return None
|
|
294
|
+
if variable.is_state():
|
|
295
|
+
if unit is not None and time_unit is not None:
|
|
296
|
+
# RHS is divided by time unit, so multiply
|
|
297
|
+
unit *= time_unit
|
|
298
|
+
return unit
|
|
299
|
+
|
|
300
|
+
# Time unit, used to infer units of state variables
|
|
301
|
+
# (May itself be inferred)
|
|
302
|
+
time_unit = variable_unit(time, None)
|
|
303
|
+
if time_unit is not None:
|
|
304
|
+
m.set_time_units(time_unit)
|
|
305
|
+
|
|
306
|
+
# Create unames in model for nested variables
|
|
307
|
+
model.create_unique_names()
|
|
308
|
+
|
|
309
|
+
def add_variable(variable: myokit.Variable):
|
|
310
|
+
if variable is time:
|
|
311
|
+
return
|
|
312
|
+
v = m.add_parameter(
|
|
313
|
+
variable.uname(),
|
|
314
|
+
variable.is_constant(),
|
|
315
|
+
)
|
|
316
|
+
if variable.is_state() or variable.is_literal():
|
|
317
|
+
v.set_value(variable.rhs(), variable.is_state())
|
|
318
|
+
else:
|
|
319
|
+
# if variable is constant but not literal,
|
|
320
|
+
# set via initial value
|
|
321
|
+
v.set_initial_value(variable.rhs())
|
|
322
|
+
|
|
323
|
+
if variable.is_state():
|
|
324
|
+
v.set_initial_value(variable.initial_value())
|
|
325
|
+
|
|
326
|
+
unit = variable_unit(variable, time_unit)
|
|
327
|
+
if unit is not None:
|
|
328
|
+
v.set_units(unit)
|
|
329
|
+
|
|
330
|
+
# Add variables
|
|
331
|
+
for component in model:
|
|
332
|
+
for variable in component:
|
|
333
|
+
# Get variable unit, or infer from RHS if None
|
|
334
|
+
unit = variable_unit(variable, time_unit)
|
|
335
|
+
|
|
336
|
+
# Add variable
|
|
337
|
+
add_variable(variable)
|
|
338
|
+
|
|
339
|
+
# Add nested variables
|
|
340
|
+
for nested in variable.variables(deep=True):
|
|
341
|
+
add_variable(nested)
|
|
342
|
+
|
|
343
|
+
# Return model
|
|
344
|
+
return m
|
|
345
|
+
|
|
250
346
|
def add_compartment(self, sid):
|
|
251
347
|
"""Adds a :class:`myokit.formats.sbml.Compartment` to this model."""
|
|
252
348
|
|
|
@@ -258,17 +354,21 @@ class Model:
|
|
|
258
354
|
self._assignables[sid] = c
|
|
259
355
|
return c
|
|
260
356
|
|
|
261
|
-
def add_parameter(self, sid):
|
|
357
|
+
def add_parameter(self, sid, is_constant=True):
|
|
262
358
|
"""Adds a :class:`myokit.formats.sbml.Parameter` to this model."""
|
|
263
359
|
|
|
264
360
|
sid = str(sid)
|
|
265
361
|
|
|
266
362
|
self._register_sid(sid)
|
|
267
|
-
p = Parameter(self, sid)
|
|
363
|
+
p = Parameter(self, sid, is_constant)
|
|
268
364
|
self._parameters[sid] = p
|
|
269
365
|
self._assignables[sid] = p
|
|
270
366
|
return p
|
|
271
367
|
|
|
368
|
+
def parameters(self):
|
|
369
|
+
"""Returns a list of all parameters in this model."""
|
|
370
|
+
return list(self._parameters.values())
|
|
371
|
+
|
|
272
372
|
def add_reaction(self, sid):
|
|
273
373
|
"""Adds a :class:`myokit.formats.sbml.Reaction` to this model."""
|
|
274
374
|
self._register_sid(sid)
|
|
@@ -296,11 +396,24 @@ class Model:
|
|
|
296
396
|
|
|
297
397
|
return s
|
|
298
398
|
|
|
399
|
+
def add_unit_if_possible(self, unit):
|
|
400
|
+
"""
|
|
401
|
+
Adds a unit to the model if it's not already present or in the set
|
|
402
|
+
of base units, and returns the unit's name.
|
|
403
|
+
"""
|
|
404
|
+
unitsid = Model.create_unit_name(unit)
|
|
405
|
+
if unitsid in self.base_units or unitsid == 'celsius':
|
|
406
|
+
return
|
|
407
|
+
if unitsid in self._units:
|
|
408
|
+
return
|
|
409
|
+
self.add_unit(unitsid, unit)
|
|
410
|
+
return unitsid
|
|
411
|
+
|
|
299
412
|
def add_unit(self, unitsid, unit):
|
|
300
413
|
"""Adds a user unit with the given ``unitsid`` and myokit ``unit``."""
|
|
301
414
|
if not _re_id.match(unitsid):
|
|
302
415
|
raise SBMLError('Invalid UnitSId "' + str(unitsid) + '".')
|
|
303
|
-
if unitsid in self.
|
|
416
|
+
if unitsid in self.base_units or unitsid == 'celsius':
|
|
304
417
|
raise SBMLError(
|
|
305
418
|
'User unit overrides built-in unit: "' + str(unitsid) + '".')
|
|
306
419
|
if unitsid in self._units:
|
|
@@ -350,7 +463,7 @@ class Model:
|
|
|
350
463
|
|
|
351
464
|
try:
|
|
352
465
|
# Find and return
|
|
353
|
-
return self.
|
|
466
|
+
return self.base_units[unitsid]
|
|
354
467
|
except KeyError:
|
|
355
468
|
raise SBMLError(
|
|
356
469
|
'<' + unitsid + '> is not an SBML base unit.')
|
|
@@ -359,6 +472,10 @@ class Model:
|
|
|
359
472
|
"""Returns the compartment with the given sid."""
|
|
360
473
|
return self._compartments[sid]
|
|
361
474
|
|
|
475
|
+
def compartments(self):
|
|
476
|
+
"""Returns a list of all compartments in this model."""
|
|
477
|
+
return list(self._compartments.values())
|
|
478
|
+
|
|
362
479
|
def conversion_factor(self):
|
|
363
480
|
"""
|
|
364
481
|
Returns the :class:`Parameter` acting as global species conversion
|
|
@@ -424,6 +541,7 @@ class Model:
|
|
|
424
541
|
Sets the default compartment size units for 2-dimensional compartments.
|
|
425
542
|
"""
|
|
426
543
|
self._area_units = units
|
|
544
|
+
self.add_unit_if_possible(units)
|
|
427
545
|
|
|
428
546
|
def set_conversion_factor(self, factor):
|
|
429
547
|
"""
|
|
@@ -447,6 +565,7 @@ class Model:
|
|
|
447
565
|
'<' + str(units) + '> needs to be instance of myokit.Unit')
|
|
448
566
|
|
|
449
567
|
self._extent_units = units
|
|
568
|
+
self.add_unit_if_possible(units)
|
|
450
569
|
|
|
451
570
|
def set_length_units(self, units):
|
|
452
571
|
"""
|
|
@@ -457,6 +576,7 @@ class Model:
|
|
|
457
576
|
'<' + str(units) + '> needs to be instance of myokit.Unit')
|
|
458
577
|
|
|
459
578
|
self._length_units = units
|
|
579
|
+
self.add_unit_if_possible(units)
|
|
460
580
|
|
|
461
581
|
def set_notes(self, notes=None):
|
|
462
582
|
"""Sets an optional notes string for this model."""
|
|
@@ -469,6 +589,7 @@ class Model:
|
|
|
469
589
|
'<' + str(units) + '> needs to be instance of myokit.Unit')
|
|
470
590
|
|
|
471
591
|
self._substance_units = units
|
|
592
|
+
self.add_unit_if_possible(units)
|
|
472
593
|
|
|
473
594
|
def set_time_units(self, units):
|
|
474
595
|
"""Sets the time units used throughout the model."""
|
|
@@ -477,6 +598,7 @@ class Model:
|
|
|
477
598
|
'<' + str(units) + '> needs to be instance of myokit.Unit')
|
|
478
599
|
|
|
479
600
|
self._time_units = units
|
|
601
|
+
self.add_unit_if_possible(units)
|
|
480
602
|
|
|
481
603
|
def set_volume_units(self, units):
|
|
482
604
|
"""
|
|
@@ -487,6 +609,7 @@ class Model:
|
|
|
487
609
|
'<' + str(units) + '> needs to be instance of myokit.Unit')
|
|
488
610
|
|
|
489
611
|
self._volume_units = units
|
|
612
|
+
self.add_unit_if_possible(units)
|
|
490
613
|
|
|
491
614
|
def __str__(self):
|
|
492
615
|
if self._name is None:
|
|
@@ -497,6 +620,14 @@ class Model:
|
|
|
497
620
|
"""Returns the species with the given id."""
|
|
498
621
|
return self._species[sid]
|
|
499
622
|
|
|
623
|
+
def species_list(self):
|
|
624
|
+
""" returns a list of all species in this model."""
|
|
625
|
+
return list(self._species.values())
|
|
626
|
+
|
|
627
|
+
def reactions(self):
|
|
628
|
+
"""Returns a list of all reactions in this model."""
|
|
629
|
+
return list(self._reactions.values())
|
|
630
|
+
|
|
500
631
|
def substance_units(self):
|
|
501
632
|
"""
|
|
502
633
|
Returns the default units for reaction amounts (not concentrations), or
|
|
@@ -514,6 +645,14 @@ class Model:
|
|
|
514
645
|
"""Returns the default units for time, or dimensionless if not set."""
|
|
515
646
|
return self._time_units
|
|
516
647
|
|
|
648
|
+
def units(self) -> Dict[str, myokit.Unit]:
|
|
649
|
+
"""Returns a dict mapping sid to unit for all units in this model."""
|
|
650
|
+
return dict(self._units)
|
|
651
|
+
|
|
652
|
+
def has_units(self) -> bool:
|
|
653
|
+
"""Returns ``True`` if this model has any units defined."""
|
|
654
|
+
return bool(self._units)
|
|
655
|
+
|
|
517
656
|
def unit(self, unitsid):
|
|
518
657
|
"""Returns a user-defined or predefined unit."""
|
|
519
658
|
try:
|
|
@@ -534,7 +673,7 @@ class Model:
|
|
|
534
673
|
return self._volume_units
|
|
535
674
|
|
|
536
675
|
# SBML base units (except Celsius, because it's not defined in myokit)
|
|
537
|
-
|
|
676
|
+
base_units = {
|
|
538
677
|
'ampere': myokit.units.A,
|
|
539
678
|
'avogadro': myokit.parse_unit('1 (6.02214179e23)'),
|
|
540
679
|
'becquerel': myokit.units.Bq,
|
|
@@ -572,6 +711,10 @@ class Model:
|
|
|
572
711
|
'weber': myokit.units.Wb,
|
|
573
712
|
}
|
|
574
713
|
|
|
714
|
+
_base_units_reverse = {
|
|
715
|
+
v: k for k, v in base_units.items()
|
|
716
|
+
}
|
|
717
|
+
|
|
575
718
|
|
|
576
719
|
class Parameter(Quantity):
|
|
577
720
|
"""
|
|
@@ -586,7 +729,7 @@ class Parameter(Quantity):
|
|
|
586
729
|
This parameter's SId.
|
|
587
730
|
|
|
588
731
|
"""
|
|
589
|
-
def __init__(self, model, sid):
|
|
732
|
+
def __init__(self, model, sid, is_constant=True):
|
|
590
733
|
super().__init__()
|
|
591
734
|
|
|
592
735
|
if not isinstance(model, Model):
|
|
@@ -595,6 +738,7 @@ class Parameter(Quantity):
|
|
|
595
738
|
' myokit.formats.sbml.Model.')
|
|
596
739
|
|
|
597
740
|
self._model = model
|
|
741
|
+
self._is_constant = is_constant
|
|
598
742
|
self._sid = str(sid)
|
|
599
743
|
self._units = None
|
|
600
744
|
|
|
@@ -605,6 +749,15 @@ class Parameter(Quantity):
|
|
|
605
749
|
'<' + str(units) + '> needs to be instance of myokit.Unit')
|
|
606
750
|
|
|
607
751
|
self._units = units
|
|
752
|
+
self._model.add_unit_if_possible(units)
|
|
753
|
+
|
|
754
|
+
def is_constant(self):
|
|
755
|
+
"""Returns ``True`` if this parameter is constant, else ``False``."""
|
|
756
|
+
return self._is_constant
|
|
757
|
+
|
|
758
|
+
def is_literal(self):
|
|
759
|
+
"""Returns ``True`` if this parameter is a literal value."""
|
|
760
|
+
return self.is_constant() and self._value is not None
|
|
608
761
|
|
|
609
762
|
def sid(self):
|
|
610
763
|
"""Returns this parameter's sid."""
|
|
@@ -889,6 +1042,7 @@ class Species(Quantity):
|
|
|
889
1042
|
'<' + str(units) + '> needs to be instance of myokit.Unit')
|
|
890
1043
|
|
|
891
1044
|
self._units = units
|
|
1045
|
+
self._compartment._model.add_unit_if_possible(units)
|
|
892
1046
|
|
|
893
1047
|
def sid(self):
|
|
894
1048
|
"""Returns this species's sid."""
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Exports to SBML.
|
|
3
|
+
#
|
|
4
|
+
# This file is part of Myokit.
|
|
5
|
+
# See http://myokit.org for copyright, sharing, and licensing details.
|
|
6
|
+
#
|
|
7
|
+
import warnings
|
|
8
|
+
|
|
9
|
+
import myokit
|
|
10
|
+
from myokit.formats.sbml._writer import write_file
|
|
11
|
+
import myokit.lib.guess
|
|
12
|
+
import myokit.formats.sbml as sbml
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SBMLExporter(myokit.formats.Exporter):
|
|
16
|
+
"""
|
|
17
|
+
This :class:`Exporter<myokit.formats.Exporter>` creates an SBML model.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self):
|
|
21
|
+
super().__init__()
|
|
22
|
+
|
|
23
|
+
def model(self, path, model, protocol=None):
|
|
24
|
+
"""
|
|
25
|
+
Writes an SBML model to the given filename.
|
|
26
|
+
|
|
27
|
+
Arguments:
|
|
28
|
+
|
|
29
|
+
``path``
|
|
30
|
+
The path/filename to write the generated code to.
|
|
31
|
+
``model``
|
|
32
|
+
The model to export.
|
|
33
|
+
``protocol``
|
|
34
|
+
If given, an attempt will be made to convert the protocol to an
|
|
35
|
+
expression and insert it into the model before exporting. See
|
|
36
|
+
:meth:`myokit.lib.guess.add_embedded_protocol()` for details.
|
|
37
|
+
|
|
38
|
+
"""
|
|
39
|
+
# Embed protocol
|
|
40
|
+
if protocol is not None:
|
|
41
|
+
model = model.clone()
|
|
42
|
+
if not myokit.lib.guess.add_embedded_protocol(model, protocol):
|
|
43
|
+
warnings.warn("Unable to embed stimulus protocol.")
|
|
44
|
+
|
|
45
|
+
# Export
|
|
46
|
+
sbml_model = sbml.Model.from_myokit_model(model)
|
|
47
|
+
write_file(path, sbml_model)
|
|
48
|
+
|
|
49
|
+
def supports_model(self):
|
|
50
|
+
"""
|
|
51
|
+
Returns ``True``.
|
|
52
|
+
"""
|
|
53
|
+
return True
|