Source code for mass.core.units

# -*- coding: utf-8 -*-
r"""Unit and UnitDefinition implementation based on SBML specifications.

The :mod:`~.units` module contains the :class:`Unit` and
:class:`UnitDefinition` classes based on the implementation of units in
`SBML <https://sbml.org/>`_.

Note that :mod:`mass` does not support automatic unit tracking to ensure
unit consistency. Therefore, it is incumbent upon the user to maintain unit
consistency as they use the various :mod:`mass` modules and functions.

To view valid units, use the :func:`print_defined_unit_values` function.
Please send a PR if you want to add something to the pre-built
:class:`Unit` \s.
"""
from warnings import warn

import libsbml
from cobra.core.object import Object
from six import integer_types, iteritems, iterkeys, itervalues, string_types
from tabulate import tabulate

from mass.util.dict_with_id import DictWithID


[docs]SBML_BASE_UNIT_KINDS_DICT = DictWithID( id="SBML Base Unit Kinds", data_dict={ "ampere": libsbml.UNIT_KIND_AMPERE, "avogadro": libsbml.UNIT_KIND_AVOGADRO, "becquerel": libsbml.UNIT_KIND_BECQUEREL, "candela": libsbml.UNIT_KIND_CANDELA, "coulomb": libsbml.UNIT_KIND_COULOMB, "dimensionless": libsbml.UNIT_KIND_DIMENSIONLESS, "farad": libsbml.UNIT_KIND_FARAD, "gram": libsbml.UNIT_KIND_GRAM, "gray": libsbml.UNIT_KIND_GRAY, "henry": libsbml.UNIT_KIND_HENRY, "hertz": libsbml.UNIT_KIND_HERTZ, "item": libsbml.UNIT_KIND_ITEM, "joule": libsbml.UNIT_KIND_JOULE, "katal": libsbml.UNIT_KIND_KATAL, "kelvin": libsbml.UNIT_KIND_KELVIN, "kilogram": libsbml.UNIT_KIND_KILOGRAM, "liter": libsbml.UNIT_KIND_LITER, "litre": libsbml.UNIT_KIND_LITRE, "lumen": libsbml.UNIT_KIND_LUMEN, "lux": libsbml.UNIT_KIND_LUX, "meter": libsbml.UNIT_KIND_METER, "metre": libsbml.UNIT_KIND_METRE, "mole": libsbml.UNIT_KIND_MOLE, "newton": libsbml.UNIT_KIND_NEWTON, "ohm": libsbml.UNIT_KIND_OHM, "pascal": libsbml.UNIT_KIND_PASCAL, "radian": libsbml.UNIT_KIND_RADIAN, "second": libsbml.UNIT_KIND_SECOND, "siemens": libsbml.UNIT_KIND_SIEMENS, "sievert": libsbml.UNIT_KIND_SIEVERT, "steradian": libsbml.UNIT_KIND_STERADIAN, "tesla": libsbml.UNIT_KIND_TESLA, "volt": libsbml.UNIT_KIND_VOLT, "watt": libsbml.UNIT_KIND_WATT, "weber": libsbml.UNIT_KIND_WEBER, "invalid": libsbml.UNIT_KIND_INVALID,
}, ) """:class:`~.DictWithID`: Contains SBML base units and their ``int`` values."""
[docs]SI_PREFIXES_DICT = DictWithID( id="SI Unit Scale Prefixes", data_dict={ "atto": -18, "femto": -15, "pico": -12, "nano": -9, "micro": -6, "milli": -3, "centi": -2, "deci": -1, "deca": 1, "hecto": 2, "kilo": 3, "mega": 6, "giga": 9, "tera": 12, "peta": 15, "exa": 18,
}, ) """:class:`~.DictWithID`: Contains SI unit prefixes and scale values."""
[docs]class Unit: """Manage units via this implementation of the SBML Unit specifications. Parameters ---------- kind : str or int A string representing the SBML Level 3 recognized base unit or its corresponding SBML integer value as defined in :const:`SBML_BASE_UNIT_KINDS_DICT`. exponent : int The unit exponent. scale : int or str An integer representing the scale of the unit, or a string for one of the pre-defined SI scales in :const:`SI_PREFIXES_DICT`. multiplier : float A number used to multiply the unit by a real-numbered factor, enabling units that are not necessarily a power-of-ten multiple. """ def __init__(self, kind, exponent, scale, multiplier): """Initialize the Unit.""" object.__init__(self) # Initialize Unit kind, exponent, scale, and multiplier self._kind = None self._exponent = None self._scale = None self._multiplier = None for name, value in zip( ["kind", "exponent", "scale", "multiplier"], [kind, exponent, scale, multiplier], ): setattr(self, name, value) @property
[docs] def kind(self): """Return the unit kind of the :class:`Unit`. Parameters ---------- kind : str An SBML recognized unit kind as a string. """ return getattr(self, "_kind")
@kind.setter def kind(self, kind): """Set the unit kind of the :class:`Unit`.""" # Ensure input is SBML compliant, remove invalid kinds. valid_keys = list(iterkeys(SBML_BASE_UNIT_KINDS_DICT)) valid_keys.remove("invalid") valid_values = list(itervalues(SBML_BASE_UNIT_KINDS_DICT)) valid_values.remove(36) if isinstance(kind, string_types) and kind in valid_keys: pass elif isinstance(kind, integer_types) and kind in valid_values: # Get corresponding base unit kind string for the value kind = valid_keys[valid_values.index(kind)] else: # Raise an error if not SBML compliant raise ValueError( "Invalid SBML Base Unit Kind '{0}'. Allowable values can be " "viewed by passing the string 'BaseUnitKinds' to the function " "'print_defined_unit_values' from the mass.core.units " "submodule.".format(kind) ) setattr(self, "_kind", kind) @property
[docs] def exponent(self): """Return the exponent of the :class:`Unit`. Parameters ---------- exponent : int The exponent of the unit as an integer. """ return getattr(self, "_exponent")
@exponent.setter def exponent(self, exponent): """Set the exponent of the :class:`Unit`.""" if ( isinstance(exponent, (integer_types, float)) and float(exponent).is_integer() ): setattr(self, "_exponent", int(exponent)) else: raise TypeError("exponent must be an integer") @property
[docs] def scale(self): """Return the scale of the :class:`Unit`. Parameters ---------- scale : int or str An integer representing the scale of the unit, or a string from the pre-defined SI prefixes. Not case sensitive. """ return getattr(self, "_scale")
@scale.setter def scale(self, scale): """Set the scale of the :class:`Unit`.""" if isinstance(scale, string_types): if scale.lower() not in SI_PREFIXES_DICT: raise ValueError( "Invalid SI Scale Prefix '{0}'. Allowable values can be " "viewed by passing the string 'Scales' to the function " "'print_defined_unit_values' from the mass.core.units " "submodule.".format(scale) ) scale = SI_PREFIXES_DICT[scale.lower()] if isinstance(scale, (integer_types, float)) and float(scale).is_integer(): setattr(self, "_scale", int(scale)) else: raise TypeError("scale must be an integer") @property
[docs] def multiplier(self): """Get or set the multiplier of the :class:`Unit`. Parameters ---------- multiplier : float A numerical value representing a multiplier for the unit. """ return getattr(self, "_multiplier")
@multiplier.setter def multiplier(self, multiplier): """Set the multiplier of the :class:`Unit`.""" if not isinstance(multiplier, (integer_types, float)): raise TypeError("multiplier must be an int.") self._multiplier = multiplier
[docs] def __str__(self): """Override of default :class:`str` implementation. Warnings -------- This method is intended for internal use only. """ return "kind: %s; exponent: %s; scale: %s; multiplier: %s" % ( self.kind, self.exponent, self.scale, self.multiplier,
)
[docs] def __repr__(self): """Override of default :func:`repr` implementation. Warnings -------- This method is intended for internal use only. """ return "<%s at 0x%x %s>" % (self.__class__.__name__, id(self), str(self))
[docs]PREDEFINED_UNITS_DICT = DictWithID( id="Pre-defined Units", data_dict={ "mole": Unit(kind="mole", exponent=1, scale=0, multiplier=1), "millimole": Unit(kind="mole", exponent=1, scale=-3, multiplier=1), "litre": Unit(kind="litre", exponent=1, scale=0, multiplier=1), "per_litre": Unit(kind="litre", exponent=-1, scale=0, multiplier=1), "second": Unit(kind="second", exponent=1, scale=0, multiplier=1), "per_second": Unit(kind="second", exponent=-1, scale=0, multiplier=1), "hour": Unit(kind="second", exponent=1, scale=0, multiplier=3600), "per_hour": Unit(kind="second", exponent=-1, scale=0, multiplier=3600), "per_gDW": Unit(kind="gram", exponent=-1, scale=0, multiplier=1),
}, ) r""":class:`~.DictWithID`: Contains pre-built :class:`Unit`\ s."""
[docs]class UnitDefinition(Object): r"""Manage units via implementation of SBML UnitDefinition specifications. Parameters ---------- id : str The identifier to associate with the unit definition name : str A human readable name for the unit definition. Attributes ---------- list_of_units : list A list containing :class:`Unit`\ s that are needed to define the :class:`UnitDefinition`, or a string that corresponds with the pre-defined units. Invalid units are ignored. """ def __init__(self, id=None, name="", list_of_units=None): """Initialize a UnitDefinition object.""" super(UnitDefinition, self).__init__(id=id, name=name) if list_of_units is None: self.list_of_units = [] elif not isinstance(list_of_units, list): raise TypeError("list_of_units must be a list.") else: self.list_of_units = [] self.add_units(list_of_units)
[docs] def create_unit(self, kind, exponent=1, scale=0, multiplier=1): """Create a :class:`Unit` and add it to the :class:`UnitDefinition`. Parameters ---------- kind : str A string representing the SBML Level 3 recognized base unit. exponent : int The exponent on the unit. Default is ``1``. scale : int or str An integer representing the scale of the unit, or a string for one of the pre-defined scales. Default is ``0.`` multiplier : float A number used to multiply the unit by a real-numbered factor, enabling units that are not necessarily a power-of-ten multiple. Default is ``1.`` """ unit = Unit(kind=kind, exponent=exponent, scale=scale, multiplier=multiplier) self.add_units([unit])
[docs] def add_units(self, new_units): r"""Add :class:`Unit`\ s to the :attr:`list_of_units`. Parameters ---------- new_units : list A list of :class:`Unit`\ s and the string identifiers of pre-built units to add to the :attr:`list_of_units` """ # Get set of units to add to_add = self._units_to_alter(new_units) # Get current units in UnitDefinition current = set(self.list_of_units) # Remove units from UnitDefinition current.update(to_add) # Update attribute self.list_of_units = list(current)
[docs] def remove_units(self, units_to_remove): r"""Remove :class:`Unit`\ s from the :attr:`list_of_units`. Parameters ---------- units_to_remove : list A list of :class:`Unit`\ s and/or the string corresponding to the unit :attr:`.Unit.kind` to remove from the :attr:`list_of_units`. """ # Get set of units to remove to_remove = self._units_to_alter(units_to_remove) # Get current units in UnitDefinition current = set(self.list_of_units) # Remove units from UnitDefinition current.difference_update(to_remove) # Update attribute self.list_of_units = list(current)
[docs] def _units_to_alter(self, units): """Create a set of units to alter in the unit definition. Warnings -------- This method is intended for internal use only. """ # Ensure list input if isinstance(units, (string_types, Unit)): warn("needs to be passed as a list.") units = [units] if not isinstance(units, list): raise TypeError("must be a list.") list_of_units = self.list_of_units u_kinds = [u.kind for u in list_of_units] # Create a set of units to be altered and return the set. to_alter = set() for unit in units: # Add a predefined unit to alter if a string. if isinstance(unit, str) and unit in PREDEFINED_UNITS_DICT: to_alter.add(PREDEFINED_UNITS_DICT[unit]) elif isinstance(unit, str) and unit in u_kinds: unit = [u for u in list_of_units if u.kind == unit].pop() to_alter.add(unit) # Add user-defined unit to alter if a Unit object. elif isinstance(unit, Unit): to_alter.add(unit) # Otherwise raise an error and skip. else: warn("Skipping unrecognized unit: '{0}'.".format(str(unit))) continue return to_alter
[docs] def __repr__(self): """Override of default :func:`repr` implementation. Warnings -------- This method is intended for internal use only. """ if self.name: name = self.name + ' "' + self.id + '"' else: name = self.id return "<%s %s at 0x%x>" % (self.__class__.__name__, name, id(self))
[docs] def __iter__(self): """Override of default :func:`iter` implementation. Warnings -------- This method is intended for internal use only. """ for unit in self.list_of_units: yield unit
__all__ = ( "SBML_BASE_UNIT_KINDS_DICT", "SI_PREFIXES_DICT", "Unit", "PREDEFINED_UNITS_DICT", "UnitDefinition", "print_defined_unit_values", )