######################################################################
# BioSimSpace: Making biomolecular simulation a breeze!
#
# Copyright: 2017-2025
#
# Authors: Lester Hedges <lester.hedges@gmail.com>
#
# BioSimSpace is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BioSimSpace is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BioSimSpace. If not, see <http://www.gnu.org/licenses/>.
#####################################################################
"""A mass type."""
__author__ = "Lester Hedges"
__email__ = "lester.hedges@gmail.com"
__all__ = ["Mass"]
from sire.legacy import Units as _SireUnits
from ._type import Type as _Type
[docs]
class Mass(_Type):
"""A mass type."""
# A list of the supported Sire unit names.
_sire_units = [
"kilogram",
"gram",
"milligram",
"microgram",
"nanogram",
"picogram",
"femtogram",
]
# Dictionary of allowed units.
_supported_units = {
"KILOGRAM": _SireUnits.kilogram,
"GRAM": _SireUnits.gram,
"MILLIGRAM": _SireUnits.milligram,
"MICROGRAM": _SireUnits.microgram,
"NANOGRAM": _SireUnits.nanogram,
"PICOGRAM": _SireUnits.picogram,
"FEMTOGRAM": _SireUnits.femtogram,
}
# Map unit abbreviations to the full name.
_abbreviations = {
"KG": "KILOGRAM",
"G": "GRAM",
"MG": "MILLIGRAM",
"UG": "MICROGRAM",
"NG": "NANOGRAM",
"PG": "PICOGRAM",
"FG": "FEMTOGRAM",
}
# Print format.
_print_format = {
"KILOGRAM": "kg",
"GRAM": "g",
"MILLIGRAM": "mg",
"MICROGRAM": "ug",
"NANOGRAM": "ng",
"PICOGRAM": "pg",
"FEMTOGRAM": "fg",
}
# Documentation strings.
_doc_strings = {
"KILOGRAM": "A mass in kilogram.",
"GRAM": "A mass in gram.",
"MILLIGRAM": "A mass in milligram.",
"MICROGRAM": "A mass in microgram.",
"NANOGRAM": "A mass in nanogram.",
"PICOGRAM": "A mass in picogram.",
"FEMTOGRAM": "A mass in femtogram.",
}
# Null type unit for avoiding issue printing configargparse help.
_default_unit = "GRAM"
# The dimension mask.
_dimensions = tuple(list(_supported_units.values())[0].dimensions())
[docs]
def __init__(self, *args):
"""
Constructor.
``*args`` can be a value and unit, or a string representation
of the mass, e.g. "12 grams".
Parameters
----------
value : float
The value.
unit : str
The unit.
string : str
A string representation of the mass.
Examples
--------
Create an object representing a mass of 12 grams then
print the mass in kilograms.
>>> import BioSimSpace as BSS
>>> mass = BSS.Types.Mass(12, "G")
>>> print(mass.kilograms())
The same as above, except passing a string representation of the
mass to the constructor.
>>> import BioSimSpace as BSS
>>> mass = BSS.Types.Mass("12 G")
>>> print(mass.kilograms())
The string matching is extremeley flexible, so all of the following
would be valid arguments: "12 G", "12 grams", "1.2e1 grams".
"""
# Call the base class constructor.
super().__init__(*args)
def __mul__(self, other):
"""Multiplication operator."""
# Handle containers by converting each item in the container to
# this type.
if isinstance(other, list):
return [self.__mul__(item) for item in other]
if isinstance(other, tuple):
return tuple([self.__mul__(item) for item in other])
# Convert int to float.
if type(other) is int:
other = float(other)
# Multiplication by float.
if isinstance(other, float):
mag = self._value * other
return Mass(mag, self._unit)
# Multiplication by another type.
elif isinstance(other, _Type):
from ._general_unit import GeneralUnit as _GeneralUnit
return _GeneralUnit(self._to_sire_unit() * other._to_sire_unit())
# Multiplication by a string.
elif isinstance(other, str):
try:
mass = Mass(other)
return self * mass
except:
raise ValueError(
"Could not convert the string to a 'BioSimSpace.Mass' type."
)
else:
raise TypeError(
"unsupported operand type(s) for *: '%s' and '%s'"
% (type(self), type(other))
)
def __rmul__(self, other):
"""Multiplication operator."""
# Multiplication is commutative: a*b = b*a
return self.__mul__(other)
[docs]
def kilograms(self):
"""
Return the mass in kilograms.
Returns
-------
mass : :class:`Mass <BioSimSpace.Types.Mass>`
The mass in kilograms.
"""
return Mass(
(self._value * self._supported_units[self._unit]).to(_SireUnits.kilogram),
"KILOGRAM",
)
[docs]
def grams(self):
"""
Return the mass in grams.
Returns
-------
mass : :class:`Mass <BioSimSpace.Types.Mass>`
The mass in grams.
"""
return Mass(
(self._value * self._supported_units[self._unit]).to(_SireUnits.gram),
"GRAM",
)
[docs]
def milligrams(self):
"""
Return the mass in milligrams.
Returns
-------
mass : :class:`Mass <BioSimSpace.Types.Mass>`
The mass in milligrams.
"""
return Mass(
(self._value * self._supported_units[self._unit]).to(_SireUnits.milligram),
"MILLIGRAM",
)
[docs]
def micrograms(self):
"""
Return the mass in micrograms.
Returns
-------
mass : :class:`Mass <BioSimSpace.Types.Mass>`
The mass in micrograms.
"""
return Mass(
(self._value * self._supported_units[self._unit]).to(_SireUnits.microgram),
"MICROGRAM",
)
[docs]
def nanograms(self):
"""
Return the mass in nanograms.
Returns
-------
mass : :class:`Mass <BioSimSpace.Types.Mass>`
The mass in nanograms.
"""
return Mass(
(self._value * self._supported_units[self._unit]).to(_SireUnits.nanogram),
"NANOGRAM",
)
[docs]
def picograms(self):
"""
Return the mass in picograms.
Returns
-------
mass : :class:`Mass <BioSimSpace.Types.Mass>`
The mass in picograms.
"""
return Mass(
(self._value * self._supported_units[self._unit]).to(_SireUnits.picogram),
"PICOGRAM",
)
[docs]
def femtograms(self):
"""
Return the mass in femtograms.
Returns
-------
mass : :class:`Mass <BioSimSpace.Types.Mass>`
The mass in femtograms.
"""
return Mass(
(self._value * self._supported_units[self._unit]).to(_SireUnits.femtogram),
"FEMTOGRAM",
)
def _to_default_unit(self, mag=None):
"""
Internal method to return an object of the same type in the default unit.
Parameters
----------
mag : float
The value (optional).
Returns
-------
mass : :class:`Mass <BioSimSpace.Types.Mass>`
The mass in the default unit of grams.
"""
if mag is None:
return self.grams()
else:
return Mass(mag, "GRAM")
def _convert_to(self, unit):
"""
Return the mass in a different unit.
Parameters
----------
unit : str
The unit to convert to.
Returns
-------
mass : :class:`Mass <BioSimSpace.Types.Mass>`
The mass in the specified unit.
"""
if unit == "KILOGRAM":
return self.kilograms()
elif unit == "GRAM":
return self.grams()
elif unit == "MILLIGRAM":
return self.milligrams()
elif unit == "MICROGRAM":
return self.micrograms()
elif unit == "NANOGRAM":
return self.nanograms()
elif unit == "PICOGRAM":
return self.picograms()
elif unit == "FEMTOGRAM":
return self.femtograms()
else:
raise ValueError(
"Supported units are: '%s'" % list(self._supported_units.keys())
)
@classmethod
def _validate_unit(cls, unit):
"""Validate that the unit are supported."""
# Strip whitespace and convert to upper case.
unit = unit.replace(" ", "").upper()
# Strip any "S" characters.
unit = unit.replace("S", "")
# Check that the unit is supported.
if unit in cls._supported_units:
return unit
elif unit in cls._abbreviations:
return cls._abbreviations[unit]
else:
raise ValueError(
"Supported units are: '%s'" % list(cls._supported_units.keys())
)
@staticmethod
def _to_sire_format(unit):
"""
Reformat the unit string so it adheres to the Sire unit formatting.
Parameters
----------
unit : str
A string representation of the unit.
Returns
-------
sire_unit : str
The unit string in Sire compatible format.
"""
unit = unit.replace("kg", "kilogram")
unit = unit.replace("ug", "microgram")
unit = unit.replace("mg", "milligram")
unit = unit.replace("ng", "nanogram")
unit = unit.replace("pg", "picogram")
unit = unit.replace("fg", "femtogram")
unit = unit.replace("g", "gram")
# Convert powers.
unit = unit.replace("kilogram-1", "(1/kilogram)")
unit = unit.replace("gram-1", "(1/gram)")
unit = unit.replace("milligram-1", "(1/milligram)")
unit = unit.replace("microgram-1", "(1/microgram)")
unit = unit.replace("nanogram-1", "(1/nanogram)")
unit = unit.replace("picogram-1", "(1/picogram)")
unit = unit.replace("femtogram-1", "(1/femtogram)")
return unit