Source code for BioSimSpace.Types._temperature

######################################################################
# BioSimSpace: Making biomolecular simulation a breeze!
#
# Copyright: 2017-2024
#
# 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 temperature type."""

__author__ = "Lester Hedges"
__email__ = "lester.hedges@gmail.com"

__all__ = ["Temperature"]

from sire.legacy import Units as _SireUnits

from ._type import Type as _Type


[docs] class Temperature(_Type): """A temperature type.""" # A list of the supported Sire unit names. _sire_units = ["kelvin", "celsius", "fahrenheit"] # Dictionary of allowed units. _supported_units = { "KELVIN": _SireUnits.kelvin, "CELSIUS": _SireUnits.celsius, "FAHRENHEIT": _SireUnits.fahrenheit, } # Map unit abbreviations to the full name. _abbreviations = {"K": "KELVIN", "C": "CELSIUS", "F": "FAHRENHEIT"} # Print formatting. _print_format = {"KELVIN": "K", "CELSIUS": "C", "FAHRENHEIT": "F"} # Documentation strings. _doc_strings = { "KELVIN": "A temperature in Kelvin.", "CELSIUS": "A temperature in Celsius.", "FAHRENHEIT": "A temperature in Fahrenheit.", } # Null type unit for avoiding issue printing configargparse help. _default_unit = "KELVIN" # 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 temperature, e.g. "298 K". Parameters ---------- value : float The value. unit : str The unit. string : str A string representation of the temperature. Examples -------- Create an object representing a temperature of 298 Kelvin then print the temperature in Celsius. >>> import BioSimSpace as BSS >>> temperature = BSS.Types.Temperature(298, "K") >>> print(temperature.celsius()) The same as above, except passing a string representation of the temperature to the constructor. >>> import BioSimSpace as BSS >>> time = BSS.Types.Temperature("298 K") >>> print(temperature.celsius()) The string matching is extremeley flexible, so all of the following would be valid arguments: "298 K", "298 kelvin", "2.98e2 k". """ # Call the base class constructor. super().__init__(*args)
def __add__(self, other): """Addition operator.""" from ..Units import allow_offset # Addition of another object of the same type. if isinstance(other, self): # The temperatures have the same unit. if self._unit == other._unit: if self._unit != "KELVIN": if not allow_offset: raise ValueError( "Ambiguous operation with offset unit: '%s'" % self._unit ) else: # Add the value in the original unit. mag = self._value + other._value # Return a new object of the same type with the original unit. return Temperature(mag, self._unit) else: return super().__add__(other) else: if not allow_offset: raise ValueError( "Ambiguous operation with offset unit: '%s'" % self._unit ) else: # Left-hand operand takes precedence. mag = self._value + other._convert_to(self._unit).value() # Return a new object of the same type with the original unit. return Temperature(mag, self._unit) # Addition of a string. elif isinstance(other, str): temp = self._from_string(other) return self + temp else: raise TypeError( "unsupported operand type(s) for +: '%s' and '%s'" % (self.__class__.__qualname__, other.__class__.__qualname__) ) def __sub__(self, other): """Subtraction operator.""" from ..Units import allow_offset # Subtraction of another object of the same type. if isinstance(other, self): # The temperatures have the same unit. if self._unit == other._unit: if self._unit != "KELVIN": if not allow_offset: raise ValueError( "Ambiguous operation with offset unit: '%s'" % self._unit ) else: # Subtract the value in the original unit. mag = self._value - other._value # Return a new object of the same type with the original unit. return Temperature(mag, self._unit) else: return super().__sub__(other) else: if not allow_offset: raise ValueError( "Ambiguous operation with offset unit: '%s'" % self._unit ) else: # Left-hand operand takes precedence. mag = self._value - other._convert_to(self._unit).value() # Return a new object of the same type with the original unit. return Temperature(mag, self._unit) # Addition of a string. elif isinstance(other, str): temp = self._from_string(other) return self - temp else: raise TypeError( "unsupported operand type(s) for -: '%s' and '%s'" % (self.__class__.__qualname__, other.__class__.__qualname__) ) def __mul__(self, other): """Multiplication operator.""" if self._unit != "KELVIN": from ..Units import allow_offset if not allow_offset: raise ValueError( "Ambiguous operation with offset unit: '%s'" % self._unit ) else: # 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): # Multiply value. mag = self._value * other # Return a new object of the same type with the original unit. return Temperature(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()) else: raise TypeError( "unsupported operand type(s) for *: '%s' and '%s'" % (self.__class__.__qualname__, other.__class__.__qualname__) ) else: return super().__mul__(other) def __rmul__(self, other): """Multiplication operator.""" # Multiplication is commutative: a*b = b*a return self.__mul__(other) def __truediv__(self, other): """Division operator.""" if self._unit != "KELVIN": from ..Units import allow_offset if not allow_offset: raise ValueError( "Ambiguous operation with offset unit: '%s'" % self._unit ) else: # Convert int to float. if type(other) is int: other = float(other) # Float division. if isinstance(other, float): # Divide value. mag = self._value / other # Return a new object of the same type with the original unit. return Temperature(mag, self._unit) # Division by another object of the same type. elif isinstance(other, self): return self._value / other._convert_to(self._unit).value() # Division by another type. elif isinstance(other, _Type): from ._general_unit import GeneralUnit as _GeneralUnit return _GeneralUnit(self._to_sire_unit() / other._to_sire_unit()) # Division by a string. elif isinstance(other, str): obj = self._from_string(other) return self / obj else: raise TypeError( "unsupported operand type(s) for /: '%s' and '%s'" % (self.__class__.__qualname__, other.__class__.__qualname__) ) else: return super().__truediv__(other) def _kelvin(self): """Return the value of the temperature in Kelvin.""" return (self._value * self._supported_units[self._unit]).value()
[docs] def kelvin(self): """ Return the temperature in Kelvin. Returns ------- temperature : :class:`Temperature <BioSimSpace.Types.Temperature>` The temperature in Kelvin. """ return Temperature( (self._value * self._supported_units[self._unit]).value(), "KELVIN" )
[docs] def celsius(self): """ Return the temperature in Celsius. Returns ------- temperature : :class:`Temperature <BioSimSpace.Types.Temperature>` The temperature in Celsius. """ return Temperature( (self._value * self._supported_units[self._unit]).to(_SireUnits.celsius), "CELSIUS", )
[docs] def fahrenheit(self): """ Return the temperature in Fahrenheit. Returns ------- temperature : :class:`Temperature <BioSimSpace.Types.Temperature>` The temperature in Fahrenheit. """ return Temperature( (self._value * self._supported_units[self._unit]).to(_SireUnits.fahrenheit), "FAHRENHEIT", )
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 ------- temperature : :class:`Temperature <BioSimSpace.Types.Temperature>` The temperature in the default unit of Kelvin. """ if mag is None: return self.kelvin() else: return Temperature(mag, "KELVIN") def _convert_to(self, unit): """ Return the temperature in a different unit. Parameters ---------- unit : str The unit to convert to. Returns ------- temperature : :class:`Temperature <BioSimSpace.Types.Temperature>` The temperature in the specified unit. """ if unit == "KELVIN": return self.kelvin() elif unit == "CELSIUS": return self.celsius() elif unit == "FAHRENHEIT": return self.fahrenheit() 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 all instances of "DEGREES", "DEGREE", "DEGS", & "DEG". unit = unit.replace("DEGREES", "") unit = unit.replace("DEGREE", "") unit = unit.replace("DEGS", "") unit = unit.replace("DEG", "") # Check that the unit is supported. if unit in cls._supported_units: return unit elif unit in cls._abbreviations: return cls._abbreviations[unit] elif len(unit) == 0: raise ValueError(f"Unit is not given. You must supply the unit.") else: raise ValueError( "Unsupported unit '%s'. Supported units are: '%s'" % (unit, list(cls._supported_units.keys())) ) def _to_sire_unit(self): """ Return the internal Sire Unit object to which this type corresponds. Returns ------- sire_unit : sire.units.GeneralUnit The internal Sire Unit object that is being wrapped. """ return self.kelvin().value() * _SireUnits.kelvin @classmethod def _from_sire_unit(cls, sire_unit): """ Convert from a Sire Units object. Parameters ---------- sire_unit : sire.units.GeneralUnit, sire.units.Celsius, sire.units.Fahrenheit The temperature as a Sire Units object. """ if isinstance(sire_unit, _SireUnits.GeneralUnit): # Create a mask for the dimensions of the object. dimensions = ( sire_unit.MASS(), sire_unit.LENGTH(), sire_unit.TIME(), sire_unit.CHARGE(), sire_unit.TEMPERATURE(), sire_unit.QUANTITY(), sire_unit.ANGLE(), ) # Make sure the dimensions match. if dimensions != cls._dimensions: raise ValueError( "The dimensions of the passed 'sire_unit' are incompatible with " f"'{cls.__name__}'" ) # Get the value in the default Sire unit for this type. value = sire_unit.to(cls._supported_units[cls._default_unit]) # Return an object of this type using the value and unit. return cls(value, cls._default_unit) elif isinstance(sire_unit, (_SireUnits.Celsius, _SireUnits.Fahrenheit)): # Return an object of this type using the value and unit. return cls(sire_unit.value(), cls._default_unit) else: raise TypeError( "'sire_unit' must be of type 'sire.units.GeneralUnit', " "'sire.units.Celsius', or 'sire.units.Fahrenheit'" ) @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. """ # Convert everything to Kelvin. unit = unit.replace("celsius", "274.15*kelvin") unit = unit.replace("fahrenheit", "255.9278*kelvin") # Convert plural to singular. unit = unit.replace("kelvins", "kelvin") # Convert powers. (Just 2nd and third for now.) unit = unit.replace("kelvin2", "(kelvin*kelvin)") unit = unit.replace("kelvin3", "(kelvin*kelvin*kelvin)") unit = unit.replace("kelvin-1", "(1/kelvin)") unit = unit.replace("kelvin-2", "(1/(kelvin*kelvin))") unit = unit.replace("kelvin-3", "(1/(kelvin*kelvin*kelvin))") return unit