#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
file : quantity.py
created time : 2021/09/28
author : Zhenyu Wei
copyright : (C)Copyright 2021-present, mdpy organization
"""
import numpy as np
import cupy as cp
from copy import deepcopy
from mdpy.unit import Unit, QUANTITY_PRECISION
from mdpy.unit.unit_definition import *
from mdpy.error import *
from mdpy.environment import *
[docs]class Quantity:
[docs] def __init__(self, value, unit: Unit = no_unit) -> None:
"""
Parameters
----------
value : int, float, array, Quantity
the value of quantity
unit : Unit
the unit of quantity
"""
if isinstance(value, Quantity):
value = value.convert_to(unit)
self._value = value.value
self._unit = value.unit
else:
if isinstance(value, np.ndarray):
self._value = value.astype(NUMPY_FLOAT)
elif isinstance(value, cp.ndarray):
self._value = value.get().astype(NUMPY_FLOAT)
else:
self._value = np.array(value).astype(NUMPY_FLOAT)
if self._value.shape == ():
self._value = np.array([self._value.item()]).astype(NUMPY_FLOAT)
if unit.is_dimension_less():
self._value *= unit.relative_value
self._unit = deepcopy(no_unit)
else:
self._unit = deepcopy(unit)
def __repr__(self) -> str:
return "<Quantity object: %s %s at 0x%x>" % (
self._value * self._unit.relative_value,
self._unit.base_dimension,
id(self),
)
def __str__(self) -> str:
return "%s %s" % (
self._value * self._unit.relative_value,
self._unit.base_dimension,
)
def is_dimension_less(self):
"""
is_dimension_less judges wether ``self`` is dimensionless
Returns
-------
bool
- True, the quantity is dimensionless
- False, the quantity isn't dimensionless
"""
if self._unit.is_dimension_less():
return True
else:
return False
def convert_to(self, target_unit: Unit):
"""
convert_to converts ``self`` to the unit of ``target_unit``
Parameters
----------
target_unit : mdpy.Unit
the unit defined by mdpy or users
Returns
-------
mdpy.Quantity
Quantity with the same absolute value but new unit
Raises
------
ValueError
If ``self._unit.base_dimension != target_unit.unit.base_dimension``. E.g ``(10*meter).convert_to(second)``
"""
if self._unit.base_dimension != target_unit.base_dimension:
raise UnitDimensionMismatchedError(
"Quantity in %s can not be converted to %s"
% (self._unit.base_dimension, target_unit.base_dimension)
)
else:
return self / target_unit * target_unit
def __getitem__(self, key):
return Quantity(self._value[key], self._unit)
def __setitem__(self, key, value):
if isinstance(value, Quantity):
self._value[key] = value.convert_to(self._unit).value
else:
self._value[key] = Quantity(value, self._unit).value
def __eq__(self, other) -> bool:
if isinstance(other, Quantity):
if self._unit == other.unit:
return np.isclose(self.value, other.value)
elif self._unit.base_dimension == other.unit.base_dimension:
diff = self.value - (
other.unit.relative_value / self._unit.relative_value * other.value
)
diff = np.abs(diff / self.value)
return diff < QUANTITY_PRECISION
else:
raise UnitDimensionMismatchedError(
"Quantity in %s can not be compared with quantity in %s"
% (self._unit.base_dimension, other.unit.base_dimension)
)
# Value judgement, without relative value like 10*angstrom == 10
elif self.is_dimension_less():
return np.isclose(self.value, other)
else:
return NotImplementedError(
"== between %s and mdpy.unit.Quantity is not implemented"
% (type(other))
)
def __ne__(self, other) -> bool:
return ~(self == other) # Invert the result
def __lt__(self, other) -> bool:
if isinstance(other, Quantity):
if self._unit == other.unit:
return self._value < other.value
elif self._unit.base_dimension == other.unit.base_dimension:
return (
self._value
< other.unit.relative_value
/ self._unit.relative_value
* other.value
)
else:
raise UnitDimensionMismatchedError(
"Quantity in %s can not be compared with quantity in %s"
% (self._unit.base_dimension, other.unit.base_dimension)
)
else:
return NotImplementedError(
"< between %s and mdpy.unit.Quantity is not implemented" % (type(other))
)
def __le__(self, other) -> bool:
if isinstance(other, Quantity):
if self._unit == other.unit:
return self._value <= other.value
elif self._unit.base_dimension == other.unit.base_dimension:
return (
self._value
<= other.unit.relative_value
/ self._unit.relative_value
* other.value
)
else:
raise UnitDimensionMismatchedError(
"Quantity in %s can not be compared with quantity in %s"
% (self._unit.base_dimension, other.unit.base_dimension)
)
else:
return NotImplementedError(
"<= between %s and mdpy.unit.Quantity is not implemented"
% (type(other))
)
def __gt__(self, other) -> bool:
if isinstance(other, Quantity):
if self._unit == other.unit:
return self._value > other.value
elif self._unit.base_dimension == other.unit.base_dimension:
return (
self._value
> other.unit.relative_value
/ self._unit.relative_value
* other.value
)
else:
raise UnitDimensionMismatchedError(
"Quantity in %s can not be compared with quantity in %s"
% (self._unit.base_dimension, other.unit.base_dimension)
)
else:
return NotImplementedError(
"> between %s and mdpy.unit.Quantity is not implemented" % (type(other))
)
def __ge__(self, other) -> bool:
if isinstance(other, Quantity):
if self._unit == other.unit:
return self._value >= other.value
elif self._unit.base_dimension == other.unit.base_dimension:
return (
self._value
>= other.unit.relative_value
/ self._unit.relative_value
* other.value
)
else:
raise UnitDimensionMismatchedError(
"Quantity in %s can not be compared with quantity in %s"
% (self._unit.base_dimension, other.unit.base_dimension)
)
else:
return NotImplementedError(
">= between %s and mdpy.unit.Quantity is not implemented"
% (type(other))
)
def __add__(self, other):
if isinstance(other, Quantity):
return Quantity(
self._value
+ other.value * (other.unit.relative_value / self._unit.relative_value),
self._unit
+ other.unit, # Test wether the base dimension is same Or the dimension will be changed in the next step
)
else:
return NotImplementedError(
"+ between %s and mdpy.unit.Quantity is not implemented" % (type(other))
)
__iadd__ = __add__
__radd__ = __add__
def __sub__(self, other):
if isinstance(other, Quantity):
return Quantity(
self._value
- other.value * (other.unit.relative_value / self._unit.relative_value),
self._unit
- other.unit, # Test wether the base dimension is same Or the dimension will be changed in the next step
)
else:
return NotImplementedError(
"- between %s and mdpy.unit.Quantity is not implemented" % (type(other))
)
__isub__ = __sub__
def __rsub__(self, other):
if isinstance(other, Quantity):
return Quantity(
other.value
- self._value * (self._unit.relative_value / other.unit.relative_value),
other.unit - self._unit,
)
else:
return NotImplementedError(
"- between mdpy.unit.Quantity and %s is not implemented" % (type(other))
)
def __neg__(self):
return Quantity(-self._value, self._unit)
def __mul__(self, other):
if isinstance(other, Quantity):
return Quantity(self._value * other.value, self._unit * other.unit)
elif isinstance(other, Unit):
return Quantity(self._value, self._unit * other)
else:
return NotImplementedError(
"* between %s and mdpy.unit.Quantity is not implemented" % (type(other))
)
__imul__ = __mul__
__rmul__ = __mul__
def __truediv__(self, other):
if isinstance(other, Quantity):
return Quantity(self._value / other.value, self._unit / other.unit)
elif isinstance(other, Unit):
return Quantity(self._value, self._unit / other)
else:
return NotImplementedError(
"/ between %s and mdpy.unit.Quantity is not implemented" % (type(other))
)
__itruediv__ = __truediv__
def __rtruediv__(self, other):
if isinstance(other, Quantity):
return Quantity(other.value / self._value, other.unit / self._unit)
elif isinstance(other, Unit):
return Quantity(1 / self._value, other / self._unit)
else:
return NotImplementedError(
"-/between mdpy.unit.Quantity and %s is not implemented" % (type(other))
)
def __pow__(self, value):
try:
len(value)
except:
return Quantity(self._value**value, self._unit**value)
raise ValueError("The power term should be a single number")
def sqrt(self):
"""
sqrt returns square root of Quantity
Returns
-------
Unit
square root of ``self``
"""
return Quantity(np.sqrt(self._value), self._unit.sqrt())
def sum(self, *keys):
return Quantity(np.sum(self._value, *keys), self.unit)
def __abs__(self):
return Quantity(abs(self._value), self._unit)
@property
def value(self):
self._value = self._value.astype(NUMPY_FLOAT)
if self._value.size == 1:
return self._value.flatten()[0]
return self._value
@property
def unit(self):
return self._unit
@value.setter
def value(self, val):
self._value = np.array(val).astype(NUMPY_FLOAT)