######################################################################
# 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/>.
#####################################################################
"""An area type."""
__author__ = "Lester Hedges"
__email__ = "lester.hedges@gmail.com"
__all__ = ["Area"]
from sire.legacy import Units as _SireUnits
from ._type import Type as _Type
[docs]
class Area(_Type):
"""An area type."""
# A list of the supported Sire unit names.
_sire_units = ["meter2", "nanometer2", "angstrom2", "picometer2"]
# Dictionary of allowed units.
_supported_units = {
"METER2": _SireUnits.meter2,
"NANOMETER2": _SireUnits.nanometer2,
"ANGSTROM2": _SireUnits.angstrom2,
"PICOMETER2": _SireUnits.picometer2,
}
# Map unit abbreviations to the full name.
_abbreviations = {
"M2": "METER2",
"NM2": "NANOMETER2",
"A2": "ANGSTROM2",
"PM2": "PICOMETER2",
}
# Print format.
_print_format = {
"METER2": "m^2",
"NANOMETER2": "nm^2",
"ANGSTROM2": "A^2",
"PICOMETER2": "pm^2",
}
# Documentation strings.
_doc_strings = {
"METER2": "An area in square meters.",
"NANOMETER2": "An area in square nanometers.",
"ANGSTROM2": "An area in square Angstrom.",
"PICOMETER2": "An area in square picometers.",
}
# Null type unit for avoiding issue printing configargparse help.
_default_unit = "ANGSTROM2"
# 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 area, e.g. "30 nm^2".
Parameters
----------
value : float
The value.
unit : str
The unit.
string : str
A string representation of the area.
Examples
--------
Create an object representing an area of 30 square nanometers then
print the area in square Angstrom.
>>> import BioSimSpace as BSS
>>> area = BSS.Types.Area(30, "nm^2")
>>> print(area.angstroms2())
The same as above, except passing a string representation of the
area to the constructor.
>>> import BioSimSpace as BSS
>>> area = BSS.Types.Area("30 nm^2")
>>> print(area.angstroms2())
The string matching is extremeley flexible, so all of the following
would be valid arguments: "30 nm^2", "30 square nanometers",
"30 nanometers squared".
"""
# 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 Area(mag, self._unit)
# Multiplication by a Length.
elif isinstance(other, _Length):
mag = self.angstroms2().value() * other.angstroms().value()
return _Volume(mag, "A3")
# 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:
length = _Length(other)
return self * length
except:
raise ValueError(
"Could not convert the string to a 'BioSimSpace.Types.Length'"
)
else:
raise TypeError(
"unsupported operand type(s) for *: '%s' and '%s'"
% (self.__class__.__qualname__, other.__class__.__qualname__)
)
def __rmul__(self, other):
"""Multiplication operator."""
# Multiplication is commutative: a*b = b*a
return self.__mul__(other)
def __truediv__(self, other):
"""Division operator."""
# Convert int to float.
if type(other) is int:
other = float(other)
# Float division.
if isinstance(other, float):
mag = self._value / other
return Area(mag, self._unit)
# Division by another Area.
elif isinstance(other, Area):
return self.angstroms2().value() / other.angstroms2().value()
# Division by a Length.
elif isinstance(other, _Length):
mag = self.angstroms2().value() / other.angstroms().value()
return _Length(mag, "A")
# 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):
try:
length = _Length(other)
return self / length
except:
try:
area = Area(other)
return self / area
except:
raise ValueError(
"Could not convert the string to a "
"'BioSimSpace.Types.Length' or a 'BioSimSpace.Types.Area'."
)
else:
raise TypeError(
"unsupported operand type(s) for /: '%s' and '%s'"
% (self.__class__.__qualname__, other.__class__.__qualname__)
)
[docs]
def meters2(self):
"""
Return the area in square meters.
Returns
-------
area : :class:`Area <BioSimSpace.Types.Area>`
The area in square meters.
"""
return Area(
(self._value * self._supported_units[self._unit]).to(_SireUnits.meter2),
"METER2",
)
[docs]
def nanometers2(self):
"""
Return the area in square nanometers.
Returns
-------
area : :class:`Area <BioSimSpace.Types.Area>`
The area in square nanometers.
"""
return Area(
(self._value * self._supported_units[self._unit]).to(_SireUnits.nanometer2),
"NANOMETER2",
)
[docs]
def angstroms2(self):
"""
Return the area in square Angstrom.
Returns
-------
area : :class:`Area <BioSimSpace.Types.Area>`
The area in square Angstrom.
"""
return Area(
(self._value * self._supported_units[self._unit]).to(_SireUnits.angstrom2),
"ANGSTROM2",
)
[docs]
def picometers2(self):
"""
Return the area in square picometers.
Returns
-------
area : :class:`Area <BioSimSpace.Types.Area>`
The area in square picometers.
"""
return Area(
(self._value * self._supported_units[self._unit]).to(_SireUnits.picometer2),
"PICOMETER2",
)
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
-------
area : :class:`Area <BioSimSpace.Types.Area>`
The area in the default unit of square Angstrom.
"""
if mag is None:
return self.angstroms2()
else:
return Area(mag, "ANGSTROM2")
def _convert_to(self, unit):
"""
Return the area in a different unit.
Parameters
----------
unit : str
The unit to convert to.
Returns
-------
area : :class:`Area <BioSimSpace.Types.Area>`
The area in the specified unit.
"""
if unit == "METER2":
return self.meters2()
elif unit == "NANOMETER2":
return self.nanometers2()
elif unit == "ANGSTROM2":
return self.angstroms2()
elif unit == "PICOMETER2":
return self.picometers2()
else:
raise ValueError(
"Supported units are: '%s'" % list(self._supported_units.keys())
)
@classmethod
def _validate_unit(cls, unit):
"""Validate that the unit is supported."""
# Strip whitespace and convert to upper case.
unit = unit.replace(" ", "").upper()
# Replace any occurrence of squared with 2.
unit = unit.replace("SQUARED", "2")
unit = unit.replace("SQUARE", "2")
# Strip "^" character.
unit = unit.replace("^", "")
# Strip "**" characters.
unit = unit.replace("**", "")
# Strip any "S" characters.
unit = unit.replace("S", "")
# Fix for ANGSTROM (since it contains an "S").
if unit[0:3] == "ANG":
unit = "ANGS" + unit[3:]
# Make sure that the "2" character appears last. This allows the user
# to write, e.g. "square nm" or "nm squared".
index = unit.find("2")
if index != -1:
unit = unit[0:index] + unit[index + 1 :] + "2"
# 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("angstroms", "angstrom")
unit = unit.replace("meters", "meter")
unit = unit.replace("nm", "nanometer")
unit = unit.replace("pm", "picometer")
# Convert powers.
unit = unit.replace("angstrom-2", "(1/angstrom2)")
unit = unit.replace("picometer-2", "(1/picometer2)")
unit = unit.replace("nanometer-2", "(1/nanometer2)")
unit = unit.replace("meter-2", "(1/meter2)")
return unit
# Import at bottom of module to avoid circular dependency.
from ._length import Length as _Length
from ._volume import Volume as _Volume