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.
@@ -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._base_units or unitsid == 'celsius':
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._base_units[unitsid]
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
- _base_units = {
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