# File: unitdef.py
# Project: core
# Author: Jan Range
# License: BSD-2 clause
# Copyright (c) 2022 Institute of Biochemistry and Technical Biochemistry Stuttgart
from pydantic import Field, validator, validate_arguments
from typing import List, TYPE_CHECKING, Optional
from dataclasses import dataclass
from pyenzyme.enzymeml.core.enzymemlbase import EnzymeMLBase
from pyenzyme.enzymeml.core.utils import type_checking, deprecated_getter
if TYPE_CHECKING: # pragma: no cover
static_check_init_args = dataclass
else:
static_check_init_args = type_checking
[docs]@static_check_init_args
class BaseUnit(EnzymeMLBase):
"""Base unit description including kind, exponent, scale and multiplier"""
kind: str = Field(
...,
description="Unit kind used to write SBML.",
)
exponent: float = Field(
...,
description="Unit exponent.",
)
scale: int = Field(
...,
description="Unit scale.",
)
multiplier: float = Field(
...,
description="Unit multiplier.",
)
[docs] def get_id(self) -> str:
"""Internal usage to get IDs from objects without ID attribute"""
if self.kind:
return self.kind
else:
raise AttributeError("No species ID given.")
[docs] def get_name(self) -> str:
"""Returns the appropriate name of the unit"""
# Get mappings
prefix_mapping, kind_mapping = self._setup_mappings()
# Retrieve values to generate the name
prefix = prefix_mapping[self.scale]
unit = kind_mapping[self.kind]
# Special case for time
if unit == "s":
if self.multiplier == 60:
unit = "min"
if self.multiplier == 60 * 60:
unit = "hours"
if abs(self.exponent) != 1:
exponent = f"^{abs(int(self.exponent))}"
else:
exponent = ""
return f"{prefix}{unit}{exponent}"
@staticmethod
def _setup_mappings():
# TODO integrate this to unitcreator
# Create a mappings
prefix_mapping = {
-15: "f",
-12: "p",
-9: "n",
-6: "u",
-3: "m",
-2: "c",
-1: "d",
1: "",
3: "k",
}
kind_mapping = {
"litre": "l",
"gram": "g",
"second": "s",
"kelvin": "K",
"dimensionless": "dimensionless",
"mole": "mole",
}
return prefix_mapping, kind_mapping
[docs]@static_check_init_args
class UnitDef(EnzymeMLBase):
name: Optional[str] = Field(
None,
description="Name of the SI unit.",
)
id: Optional[str] = Field(
None,
description="Interal Identifier of the SI unit.",
)
meta_id: Optional[str] = Field(
None,
description="Interal meta identifier of the SI unit.",
)
units: List[BaseUnit] = Field(
default_factory=list,
description="List of SI baseunits.",
)
ontology: Optional[str] = Field(
None,
description="Ontology of the SI unit.",
)
# ! Validators
def _get_unit_name(self):
"""Generates the unit name based of the given baseunits"""
nominator, denominator = [], []
for unit in self.units:
if unit.exponent > 0:
nominator.append(unit.get_name())
elif unit.exponent < 0:
denominator.append(unit.get_name())
# Catch empty nominators
if not nominator:
nominator = "1"
# Combine each side and construct the SI string
nominator = " ".join(nominator)
denominator = " ".join(denominator)
if denominator:
return " / ".join([nominator, denominator])
else:
return nominator
# ! Adders
[docs] @validate_arguments
def addBaseUnit(
self, kind: str, exponent: float, scale: int, multiplier: float
) -> None:
"""Adds a base unit to the units element and sort the units.
Args:
kind (str): SBML unit kind string.
exponent (float): Exponent of the unit.
scale (float): Scale of the unit.
multiplier (float): Muliplier of the unit.
"""
# Create baseunit
baseunit = BaseUnit(
kind=kind, exponent=exponent, scale=scale, multiplier=multiplier
)
# Merge both and sort them via kind
if baseunit not in self.units:
self.units.append(baseunit)
self.units = sorted(self.units, key=lambda unit: unit.kind)
# ! Utilities
def _getNewName(self) -> str:
"""Internal function used to derive a units new name. Will be assigned using enzmldoc._convertTounitDef.
Returns:
str: The new name of the unit definition.
"""
# Mapping for abbreviations
kind_mapping = {
"mole": "mole",
"second": "s",
"liter": "l",
"litre": "l",
}
prefix_mapping = {
-15: "f",
-12: "p",
-9: "n",
-6: "u",
-3: "m",
-2: "c",
-1: "d",
1: "",
3: "k",
}
nominator = list(filter(lambda base_unit: base_unit.exponent > 0, self.units))
denominator = list(filter(lambda base_unit: base_unit.exponent < 0, self.units))
# Create new unit name
def constructName(base_unit: BaseUnit) -> str:
return f"{prefix_mapping[base_unit.scale]}{kind_mapping[base_unit.kind]}"
nominator_string = " ".join(
[constructName(base_unit) for base_unit in nominator]
)
denominator_string = " ".join(
[constructName(base_unit) for base_unit in denominator]
)
return " / ".join([nominator_string, denominator_string])
# ! Getters
[docs] @deprecated_getter("units")
def getUnits(self):
return self.units
[docs] @deprecated_getter("name")
def getName(self):
return self.name
[docs] @deprecated_getter("id")
def getId(self):
return self.id
[docs] @deprecated_getter("ontology")
def getOntology(self):
return self.ontology