"""
Units: Extends the Unum units package.
TODO: Unum is a defunct project. Its source repo is no longer online. Either
switch to a newer package like Pint or copy and improve the Unum source code
from its Python package.
"""
from typing import TypeGuard
import scipy.constants
import numpy as np
# noinspection PyUnresolvedReferences
from unum.units import mol, mmol, g, h, L, fg, min, s, umol, dmol, J, K # noqa: F401
from unum.units import * # noqa: F403
from unum import Unum
count = Unum.unit("count", mol / scipy.constants.Avogadro)
nt = Unum.unit("nucleotide", count)
aa = Unum.unit("amino_acid", count)
def __truediv__(self, other):
"""Replacement Unum method that truly implements true division."""
other = Unum.coerceToUnum(other)
if not other._unit:
unit = self._unit
else:
unit = self._unit.copy()
for u, exp in list(other._unit.items()):
exp -= unit.get(u, 0)
if exp:
unit[u] = -exp
else:
del unit[u]
return Unum(unit, self._value / other._value)
def __rtruediv__(self, other):
return Unum.coerceToUnum(other).__truediv__(self)
# Allow boolean testing on all Unum objects
def __bool__(self):
return bool(self._value)
Unum.__bool__ = Unum.__nonzero__ = __bool__
# #244 workaround: Monkey patch Unum if it still has the broken implementation.
# The test also ensures this only patches it once.
# For some reason, `is` won't work here.
# See also https://github.com/CovertLab/wcEcoli/issues/433
if Unum.__truediv__ == Unum.__div__:
Unum.__truediv__ = __truediv__
Unum.__rtruediv__ = __rtruediv__
# noinspection PyShadowingBuiltins
[docs]
def sum(array, axis=None, dtype=None, out=None, keepdims=False):
if not isinstance(array, Unum):
raise Exception("Only works on Unum!")
units = getUnit(array)
return units * np.sum(array.asNumber(), axis, dtype, out, keepdims)
# noinspection PyShadowingBuiltins
[docs]
def abs(array):
if not isinstance(array, Unum):
raise Exception("Only works on Unum!")
units = getUnit(array)
return units * np.abs(array.asNumber())
[docs]
def dot(a, b, out=None):
if not isinstance(a, Unum):
a_units = 1
else:
a_units = getUnit(a)
a = a.asNumber()
if not isinstance(b, Unum):
b_units = 1
else:
b_units = getUnit(b)
b = b.asNumber()
return a_units * b_units * np.dot(a, b, out)
[docs]
def matmul(a, b, out=None):
if not isinstance(a, Unum):
a_units = 1
else:
a_units = getUnit(a)
a = a.asNumber()
if not isinstance(b, Unum):
b_units = 1
else:
b_units = getUnit(b)
b = b.asNumber()
return a_units * b_units * np.matmul(a, b, out)
Unum.__matmul__ = matmul
[docs]
def multiply(a, b):
if not isinstance(a, Unum):
a_units = 1
else:
a_units = getUnit(a)
a = a.asNumber()
if not isinstance(b, Unum):
b_units = 1
else:
b_units = getUnit(b)
b = b.asNumber()
return a_units * b_units * np.multiply(a, b)
[docs]
def divide(a, b):
if not isinstance(a, Unum):
a_units = 1
else:
a_units = getUnit(a)
a = a.asNumber()
if not isinstance(b, Unum):
b_units = 1
else:
b_units = getUnit(b)
b = b.asNumber()
return a_units / b_units * np.divide(a, b)
[docs]
def floor(x):
if not hasUnit(x):
raise Exception("Only works on Unum!")
x_unit = getUnit(x)
x = x.asNumber()
return x_unit * np.floor(x)
[docs]
def transpose(array, axis=None):
units = getUnit(array)
return units * np.transpose(array.asNumber(), axis)
[docs]
def hstack(tup):
unit = getUnit(tup[0])
value = []
for array in tup:
if not isinstance(array, Unum):
raise Exception("Only works on Unum!")
else:
array.normalize()
value.append(array.matchUnits(unit)[0].asNumber())
value = tuple(value)
return unit * np.hstack(value)
[docs]
def getUnit(value):
if not hasUnit(value):
raise Exception("Only works on Unum!")
value.normalize()
value_units = value.copy()
value_units._value = 1
return value_units
[docs]
def hasUnit(value) -> TypeGuard[Unum]:
return isinstance(value, Unum)
[docs]
def strip_empty_units(value):
if hasUnit(value):
value.normalize()
value.checkNoUnit()
value = value.asNumber()
return value
[docs]
def isnan(value):
return np.isnan(value._value) if hasUnit(value) else np.isnan(value)
[docs]
def isfinite(value):
return np.isfinite(value._value) if hasUnit(value) else np.isfinite(value)