##########################################################################################
# polymath/unit.py
##########################################################################################
import math
import numpy as np
import numbers
[docs]
class Unit():
"""Class to represent units and provide conversion methods.
Attributes:
exponents (tuple): Three integers representing the exponents on dimensions of
length, time, and angle, respectively.
triple (tuple): Three integers representing the exact factor that one must
multiply a value in this unit by to a value in standard units involving (km,
seconds, and radians). This factor is represented by three numbers,
(**numer**, **denom**, and **expo**), where the exact factor equals
(`numer/denom * pi**expo`).
name (str, dict or None): An optional name for this unit. Alternatively, a name
can be defined by a dictionary keyed by unit names, returning exponents. For
example, the name "km/s" can be given by `{"km":1, "s":-1}`.
Examples:
* `degree` unit is represented by exponents (0, 0, 1) and triple (1, 180, 1),
because one multiplies degrees by pi/180 to obtain radians.
* `m/s` uni is represented by exponents (1, -1, 0) and triple (1, 1000, 0),
because one multiples meters per second by 1/1000 to obtain km per second.
Notes:
* Most common unit values are defined as class constants, e.g., **Unit.DEGREE**
and **Unit.STER**.
* In most situations, a given Unit value of None is equivalent to
**Unit.UNITLESS**, the Unit associated with a dimensionless value.
"""
[docs]
def __init__(self, exponents, triple, name=None):
"""Initialize a Unit object.
Parameters:
exponents (tuple): A tuple of integers defining the exponents on distance,
time and angle that are used for this unit.
triple (tuple): A tuple containing:
* [0] The numerator of a factor that converts from a value in this unit to
a value using standard units of km, seconds, and radians.
* [1] The denominator of this same factor.
* [2] The exponent on pi that should multiply the numerator of this
factor.
name (str or dict, optional): The name of the unit. It is represented by a
string or by a dictionary of unit exponents keyed by the unit names.
Notes:
For example, a unit of degrees would have a triple (1,180,1). This defines a
factor pi/180, which converts from degrees to radians.
"""
self.exponents = tuple(exponents)
# Convert to coefficients to ints with lowest common denominator if possible
(numer, denom) = triple[:2]
# Scale by 256 to compensate for possible floats that can be represented exactly
numer = int(triple[0] * 256)
denom = int(triple[1] * 256)
gcd_value = math.gcd(numer, denom)
numer //= gcd_value
denom //= gcd_value
if numer * triple[1] != denom * triple[0]:
numer = triple[0]
denom = triple[1]
pi_expo = triple[2]
self.triple = (numer, denom, pi_expo)
# Factor to convert from these units to standard units
self.factor = (numer / denom) * np.pi**pi_expo
# Factor to convert from standard units to these units
self.factor_inv = (denom / numer) / np.pi**pi_expo
# Fill in the name
self.name = name
@property
def from_unit_factor(self):
return self.factor
@property
def into_unit_factor(self):
return self.factor_inv
[docs]
@staticmethod
def as_unit(arg):
"""Convert the given argument to a Unit object.
Parameters:
arg: The argument to convert. Can be an object of class Unit, one of the
standard unit names, or None.
Returns:
Unit or None: The converted Unit object, or None if arg is None.
Raises:
ValueError: If the argument is not a recognized unit.
"""
if arg is None:
return None
elif isinstance(arg, str):
return Unit.NAME_TO_UNIT[arg]
elif isinstance(arg, Unit):
return arg
else:
raise ValueError("not a recognized unit: " + str(arg))
[docs]
@staticmethod
def can_match(first, second):
"""Check if the unit can match.
Parameters:
first (Unit or None): The first unit object.
second (Unit or None): The second unit object.
Returns:
bool: True if the units can match, meaning that either they have the same
exponents or one or both are None.
"""
if first is None or second is None:
return True
return first.exponents == second.exponents
[docs]
@staticmethod
def require_compatible(first, second, info=''):
"""Raise a ValueError if the arguments are not compatible units.
Parameters:
first (Unit or None): The first unit object.
second (Unit or None): The second unit object.
info (str, optional): Info to embed into the error message.
Raises:
ValueError: If the units are not compatible.
"""
if not Unit.can_match(first, second):
info_ = info + ' ' if info else ''
raise ValueError(f'{info_}units are not compatible: {first}, {second}')
[docs]
@staticmethod
def do_match(first, second):
"""Check if the units match.
Parameters:
first (Unit or None): The first unit object.
second (Unit or None): The second unit object.
Returns:
bool: True if the units match, meaning that they have the same exponents.
Values of None are treated as equivalent to unitless.
"""
if first is None:
first = Unit.UNITLESS
if second is None:
second = Unit.UNITLESS
return first.exponents == second.exponents
[docs]
@staticmethod
def require_match(first, second, info=''):
"""Raise a ValueError if the units are not the same.
Parameters:
first (Unit or None): The first unit object.
second (Unit or None): The second unit object.
info (str, optional): Info to embed into the error message.
Raises:
ValueError: If the units are not compatible.
"""
if not Unit.do_match(first, second):
info_ = info + ' ' if info else ''
raise ValueError(f'{info_}units do not match: {first}, {second}')
[docs]
@staticmethod
def is_angle(arg):
"""Check if the argument could be used as an angle.
Parameters:
arg (Unit or None): The unit object to check.
Returns:
bool: True if the argument could be used as an angle.
"""
if arg is None:
return True
return (arg.exponents in ((0, 0, 0), (0, 0, 1)))
[docs]
@staticmethod
def require_angle(arg, info=''):
"""Raise a ValueError if the argument could not be used as an angle.
Parameters:
arg (Unit or None): The unit object to check.
info (str, optional): Info to embed into the error message.
Raises:
ValueError: If the units are incompatible with an angle.
"""
if not Unit.is_angle(arg):
info_ = info + ' ' if info else ''
raise ValueError(f'{info_}unit is not incompatible with an angle')
[docs]
@staticmethod
def is_unitless(arg):
"""True if the argument is unitless.
Parameters:
arg (Unit or None): The unit object to check.
Returns:
bool: True if the argument is unitless.
"""
if arg is None:
return True
return (arg.exponents == (0, 0, 0))
[docs]
@staticmethod
def require_unitless(arg, info=''):
"""Raise a ValueError if the argument is not unitless.
Parameters:
arg (Unit or None): The unit object to check.
info (str, optional): Info to embed into the error message.
Raises:
ValueError: If a unit is not permitted.
"""
if Unit.is_unitless(arg):
return
info_ = info + ' ' if info else ''
raise ValueError(f'{info_}unit is not permitted: {arg}')
[docs]
def from_this(self, value):
"""Convert a scalar or numpy array in this unit to a standard unit.
Parameters:
value (scalar or ndarray): The value to convert from this unit to standard
units of km, seconds and radians.
Returns:
scalar or ndarray: The value converted to a standard unit.
"""
return self.factor * value
[docs]
def into_this(self, value):
"""Convert a scalar or numpy array from a standard unit to this unit.
Parameters:
value (scalar or ndarray): The value to convert from a standard unit to this
unit.
Returns:
scalar or ndarray: The converted value in this unit.
"""
return self.factor_inv * value
[docs]
@staticmethod
def from_unit(unit, value):
"""Convert a scalar or numpy array in the given unit to a standard unit.
Parameters:
unit (Unit or None): The unit to convert from.
value (scalar or ndarray): The value to convert.
Returns:
scalar or ndarray: The converted value in standard unit of km, seconds and
radians.
"""
if unit is None:
return value
return unit.factor * value
[docs]
@staticmethod
def into_unit(unit, value):
"""Convert a scalar or numpy array from a standard unit to given unit.
Parameters:
unit (Unit or None): The unit to convert to.
value (scalar or ndarray): The value to convert.
Returns:
scalar or ndarray: The converted value in the given unit.
"""
if unit is None:
return value
return unit.factor_inv * value
[docs]
def convert(self, value, unit, info=''):
"""Convert the unit of a scalar or NumPy array.
The value is assumed to be in this unit, and it is returned in the new unit
specified. Conversions are exact whenever possible.
Parameters:
value (scalar or ndarray): The value to convert.
unit (Unit or None): The target unit. If None, converts to unitless.
info (str, optional): Info to embed into the error message.
Returns:
scalar or ndarray: The converted value in the target unit.
Raises:
ValueError: If the units are incompatible for conversion.
"""
if unit is None:
unit = Unit.UNITLESS
if self.exponents != unit.exponents:
_info = ' ' + info if info else ''
raise ValueError(f'cannot convert unit {self} to {unit} in {_info}')
# If the factor is unity, return the value without modification
if (self.triple[2] == unit.triple[2] and
self.triple[0] * unit.triple[1] == self.triple[1] * unit.triple[0]): # noqa
return value
return ((self.triple[0] * unit.triple[1]) * value /
(self.triple[1] * unit.triple[0]) *
np.pi**(self.triple[2] - unit.triple[2]))
######################################################################################
# Arithmetic operators
######################################################################################
[docs]
def __mul__(self, arg):
"""Multiply this Unit object by another Unit object or scalar.
Parameters:
arg (Unit, None, or numbers.Real): The object to multiply by.
Returns:
Unit: The product of the unit multiplication.
Raises:
NotImplementedError: If the argument type is not supported.
"""
if isinstance(arg, Unit):
return Unit((self.exponents[0] + arg.exponents[0],
self.exponents[1] + arg.exponents[1],
self.exponents[2] + arg.exponents[2]),
(self.triple[0] * arg.triple[0],
self.triple[1] * arg.triple[1],
self.triple[2] + arg.triple[2]),
Unit._mul_names(self.name, arg.name))
if arg is None:
return self
if isinstance(arg, numbers.Real):
return self * (Unit((0, 0, 0), (arg, 1, 0)))
return NotImplemented
[docs]
def __rmul__(self, arg):
return self.__mul__(arg)
[docs]
def __div__(self, arg):
return self.__truediv__(arg)
[docs]
def __rdiv__(self, arg):
return self.__rtruediv__(arg)
[docs]
def __truediv__(self, arg):
"""Divide this Unit object by another Unit object or scalar.
Parameters:
arg (Unit, None, or numbers.Real): The object to divide by.
Returns:
Unit: The quotient of the unit division.
Raises:
NotImplementedError: If the argument type is not supported.
"""
if isinstance(arg, Unit):
return Unit((self.exponents[0] - arg.exponents[0],
self.exponents[1] - arg.exponents[1],
self.exponents[2] - arg.exponents[2]),
(self.triple[0] * arg.triple[1],
self.triple[1] * arg.triple[0],
self.triple[2] - arg.triple[2]),
Unit.div_names(self.name, arg.name))
if arg is None:
return self
if isinstance(arg, numbers.Real):
return self * (Unit((0, 0, 0), (1, arg, 0)))
return NotImplemented
[docs]
def __rtruediv__(self, arg):
"""Divide a scalar by this Unit object.
Parameters:
arg (None or numbers.Real): The scalar to divide.
Returns:
Unit: The reciprocal of this Unit object multiplied by arg.
Raises:
NotImplementedError: If the argument type is not supported.
"""
if arg is None:
arg = 1.
if isinstance(arg, numbers.Real):
return (self / arg)**(-1)
return NotImplemented
[docs]
def __pow__(self, power):
"""Raise this Unit object to the specified power.
Parameters:
power (int or float): The exponent. Must be an integer or half-integer.
Returns:
Unit: This Unit object raised to the specified power.
Raises:
ValueError: If the power is not an integer or half-integer.
"""
ipower = int(power)
if power != ipower:
if 2*power == int(2*power):
return self.sqrt()**(int(2 * power))
else:
raise ValueError('units can only be raised to integer or half-integer '
f'powers: {power}')
else:
power = ipower
if power > 0:
return Unit((power * self.exponents[0],
power * self.exponents[1],
power * self.exponents[2]),
(self.triple[0]**power,
self.triple[1]**power,
power * self.triple[2]),
Unit.name_power(self.name, power))
else:
return Unit((power * self.exponents[0],
power * self.exponents[1],
power * self.exponents[2]),
(self.triple[1]**(-power),
self.triple[0]**(-power),
power * self.triple[2]),
Unit.name_power(self.name, power))
[docs]
def sqrt(self, name=None):
"""Return the square root of this Unit object.
Parameters:
name (str or dict, optional): The name for the resulting unit.
Returns:
Unit: The square root of this Unit object.
Raises:
ValueError: If the exponents are not even numbers.
"""
if (self.exponents[0] % 2 != 0 or
self.exponents[1] % 2 != 0 or
self.exponents[2] % 2 != 0): # noqa
raise ValueError("illegal unit for sqrt(): " + self.get_name())
exponents = (self.exponents[0]//2, self.exponents[1]//2, self.exponents[2]//2)
numer = np.sqrt(self.triple[0])
denom = np.sqrt(self.triple[1])
if numer % 1 == 0:
numer = int(numer)
if denom % 1 == 0:
denom = int(denom)
pi_expo = self.triple[2] // 2
if self.triple[2] != 2*pi_expo:
numer *= np.pi**(self.triple[2] / 2.)
pi_expo = 0
if name is None:
name = Unit.name_power(self.name, 0.5)
return Unit(exponents, (numer, denom, pi_expo), name)
#####################################################
# Static versions of arithmetic operations
#####################################################
[docs]
@staticmethod
def mul_units(arg1, arg2, name=None):
"""Multiply two Unit objects.
Parameters:
arg1 (Unit or None): The first Unit object.
arg2 (Unit or None): The second Unit object.
name (str or dict, optional): The name for the resulting unit.
Returns:
Unit or None: The product of the two Unit objects, or None if both arguments
are None.
"""
if arg2 is None:
result = arg1
elif arg1 is None:
result = arg2
else:
result = arg1 * arg2
if result is not None:
result.name = name
return result
[docs]
@staticmethod
def div_units(arg1, arg2, name=None):
"""Divide two Unit objects.
Parameters:
arg1 (Unit or None): The numerator Unit object.
arg2 (Unit or None): The denominator Unit object.
name (str or dict, optional): The name for the resulting unit.
Returns:
Unit or None: The quotient of the two Unit objects, or None if both arguments
are None.
"""
if arg2 is None:
result = arg1
elif arg1 is None:
result = arg2**(-1)
else:
result = arg1 / arg2
if result is not None:
result.name = name
return result
[docs]
@staticmethod
def sqrt_unit(unit, name=None):
"""Return the square root of a Unit object.
Parameters:
unit (Unit or None): The Unit object to take the square root of.
name (str or dict, optional): The name for the resulting unit.
Returns:
Unit or None: The square root of the Unit object, or None if unit is None.
Raises:
ValueError: If the exponents are not even numbers.
"""
if unit is None:
return None
return unit.sqrt(name)
[docs]
@staticmethod
def unit_power(unit, power, name=None):
"""Raise a Unit object to the specified power.
Parameters:
unit (Unit or None): The Unit object to raise to a power.
power (int or float): The exponent. Must be an integer or half-integer.
name (str or dict, optional): The name for the resulting unit.
Returns:
Unit or None: The Unit object raised to the specified power, or None if unit
is None.
Raises:
ValueError: If the power is not an integer or half-integer.
"""
if unit is None:
return None
result = unit**power
result.set_name(name)
return result
######################################################################################
# Comparison operators
######################################################################################
[docs]
def __eq__(self, arg):
"""Check if this Unit object equals another.
Parameters:
arg (Unit or None): The Unit object to compare with.
Returns:
bool: True if the Unit objects are equal, False otherwise.
"""
if not isinstance(arg, Unit):
return False
return (self.exponents == arg.exponents and self.factor == arg.factor)
[docs]
def __ne__(self, arg):
"""Check if this Unit object does not equal another.
Parameters:
arg (Unit or None): The Unit object to compare with.
Returns:
bool: True if the Unit objects are not equal, False otherwise.
"""
if not isinstance(arg, Unit):
return True
return (self.exponents != arg.exponents or self.factor != arg.factor)
######################################################################################
# Copy operations
######################################################################################
[docs]
def __copy__(self):
return Unit(self.exponents, self.triple, self.name)
[docs]
def copy(self):
"""Return a copy of this Unit object.
Returns:
Unit: A copy of this Unit object.
"""
return self.__copy__()
######################################################################################
# String operations
######################################################################################
[docs]
def __str__(self):
"""Return a string representation of this Unit object.
Returns:
str: A string representation of the unit.
"""
return self.get_name()
[docs]
def __repr__(self):
"""Return a detailed string representation of this Unit object.
Returns:
str: A detailed string representation of the unit.
"""
return f'Unit({self})'
@staticmethod
def _mul_names(name1, name2):
"""Multiply two unit names.
Parameters:
name1 (str, dict, or None): The first unit name.
name2 (str, dict, or None): The second unit name.
Returns:
str or dict or None: The product of the two unit names, or None if both
arguments are None.
"""
if name1 is None or name2 is None:
return None
name1 = Unit.name_to_dict(name1)
name2 = Unit.name_to_dict(name2)
new_name = name1.copy()
for key, expo in name2.items():
if key in new_name:
expo += new_name[key]
if expo == 0:
del new_name[key]
else:
new_name[key] = expo
return new_name
[docs]
@staticmethod
def div_names(name1, name2):
"""Divide two unit names.
Parameters:
name1 (str, dict, or None): The numerator unit name.
name2 (str, dict, or None): The denominator unit name.
Returns:
str or dict or None: The quotient of the two unit names, or None if both
arguments are None.
"""
if name1 is None or name2 is None:
return None
name1 = Unit.name_to_dict(name1)
name2 = Unit.name_to_dict(name2)
new_name = name1.copy()
for key, expo in name2.items():
if key in new_name:
expo -= new_name[key]
if expo == 0:
del new_name[key]
else:
new_name[key] = -expo
return new_name
[docs]
@staticmethod
def name_power(name, power):
"""Raise a unit name to the specified power.
Parameters:
name (str, dict, or None): The unit name to raise to a power.
power (int or float): The exponent.
Returns:
str or dict or None: The unit name raised to the specified power, or None if
name is None.
"""
if name is None:
return None
name = Unit.name_to_dict(name)
if isinstance(power, str):
power = Unit.name_to_dict(power)
if not isinstance(power, int):
raise ValueError('fnon-integer power on unit "{old_power}"')
new_name = {}
for key, expo in name.items():
new_power = expo * power
int_power = int(new_power)
if new_power != int_power:
raise ValueError(f'non-integer power {new_power} on unit "{key}"')
new_name[key] = int_power
return new_name
[docs]
@staticmethod
def name_to_dict(name):
"""Convert a unit name string to a dictionary.
Parameters:
name (str or dict): The unit name to convert.
Returns:
dict: A dictionary representation of the unit name.
Raises:
ValueError: If the name format is invalid.
"""
BIGNUM = 99999
if isinstance(name, dict):
return name
if not isinstance(name, str):
raise ValueError(f'unit is not a string: "{name}"')
name = name.strip()
if name == '':
return {}
# Return a named unit
if name.isalpha():
return {name: 1}
# Return an integer exponent
try:
return int(name)
except ValueError:
pass
# If the name starts with a left parenthensis, find the end of the
# expression and process the interior
if name[0] == '(':
depth = 0
for i, c in enumerate(name):
if c == '(':
depth += 1
if c == ')':
depth -= 1
if depth == 0:
break
left = name[1:i]
right = name[i+1:].lstrip()
# Otherwise, jump to the first operator
else:
imul = name.find('*') % BIGNUM
idiv = name.find('/') % BIGNUM
first = min(imul, idiv)
if first >= BIGNUM - 1:
raise ValueError(f'illegal unit syntax: "{name}"')
left = name[:first]
right = name[first:].lstrip()
# Handle the operator if it is an exponent
if right.startswith('**'):
right = right[2:].lstrip()
imul = right.find('*') % BIGNUM
idiv = right.find('/') % BIGNUM
first = min(imul, idiv)
if first >= BIGNUM - 1:
return Unit.name_power(left, right)
power = right[:first].lstrip()
left = Unit.name_power(left, power)
right = right[first:].lstrip()
if right == '':
if left == name.strip(): # if no progress was made...
raise ValueError(f'illegal unit syntax: "{name}"')
return Unit.name_to_dict(left)
if right.startswith('**'):
raise ValueError(f'illegal unit syntax: "{name}"')
op = right[0]
right = right[1:].lstrip()
if op == '*':
return Unit._mul_names(left, right)
else:
return Unit.div_names(left, right)
[docs]
@staticmethod
def name_to_str(namedict):
"""Convert a unit name dictionary to a string.
Parameters:
namedict (dict or None): The unit name dictionary to convert.
Returns:
str: A string representation of the unit name, or empty string if namedict is
None.
Notes:
This method contains nested helper functions for ordering keys and
concatenating units.
"""
def order_keys(namelist):
"""Internal method to order the units sensibly."""
sorted = []
# Coefficient first
if '' in namelist:
sorted.append('')
# Distances first
templist = []
for key in namelist:
if key in Unit._NAME_TO_UNIT:
expo = Unit._NAME_TO_UNIT[key].exponents
if expo[0]:
templist.append(key)
templist.sort()
sorted += templist
# Angles second
templist = []
for key in namelist:
if key in Unit._NAME_TO_UNIT:
expo = Unit._NAME_TO_UNIT[key].exponents
if expo[2] and key not in sorted:
templist.append(key)
templist.sort()
sorted += templist
# Time units next
templist = []
for key in namelist:
if key in Unit._NAME_TO_UNIT:
expo = Unit._NAME_TO_UNIT[key].exponents
if expo[1] and key not in sorted:
templist.append(key)
templist.sort()
sorted += templist
# Unrecognized units last
templist = []
for key in namelist:
if key not in sorted:
templist.append(key)
templist.sort()
sorted += templist
return sorted
def cat_units(namelist, negate=False):
"""A string of names and exponents."""
unitlist = []
for key in namelist:
expo = namedict[key]
if key == '':
if expo != 1:
unitlist.append(str(expo))
continue
if negate:
expo = -expo
if expo == 1:
unitlist.append(key)
elif expo > 1:
unitlist.append(key + '**' + str(expo))
else:
unitlist.append(key + '**(' + str(expo) + ')')
return '*'.join(unitlist)
# Return a string immediately
if isinstance(namedict, str):
return namedict
# Make list of numerator and denominator units
numers = []
denoms = []
for key, expo in namedict.items():
if key == '':
numers.append(key)
elif expo > 0:
numers.append(key)
elif expo < 0:
denoms.append(key)
# Sort the units
numers = order_keys(numers)
denoms = order_keys(denoms)
if numers:
if denoms:
return cat_units(numers) + '/' + cat_units(denoms, negate=True)
else:
return cat_units(numers)
else:
if denoms:
return cat_units(denoms, negate=False)
else:
return ''
[docs]
def create_name(self):
"""Create a name for this Unit object based on its exponents.
Returns:
str: A name for this Unit object.
"""
# Return the internal name, if defined
if self.name is not None:
return self.name
# Return the name from the dictionary, if found
try:
name = Unit._TUPLES_TO_UNIT[(self.exponents, self.triple)].name
if name is not None:
return name
except KeyError:
pass
expo = self.exponents
# Search for combinations that might work
options = [[], [], []]
for i in range(3):
target_power = self.exponents[i]
if target_power:
for unit in Unit._UNITS_BY_EXPO[i]:
actual_power = unit.exponents[i]
p = target_power // actual_power
if p * actual_power == target_power:
if p > 0:
new_triple = (unit.triple[0]**p,
unit.triple[1]**p,
unit.triple[2] * p)
else:
new_triple = (unit.triple[1]**(-p), # swapped!
unit.triple[0]**(-p),
unit.triple[2] * p)
options[i].append((unit, p, new_triple))
else:
options[i].append((Unit._UNITS_BY_EXPO[i][0], 0, (1, 1, 0)))
# Check every possible combination for the one that yields the correct
# coefficient
successes = []
for d, d_option in enumerate(options[0]):
d_unit, d_power, d_triple = d_option
d_numer, d_denom, d_expo = d_triple
for t, t_option in enumerate(options[1]):
t_unit, t_power, t_triple = t_option
t_numer, t_denom, t_expo = t_triple
for a, a_option in enumerate(options[2]):
a_unit, a_power, a_triple = a_option
a_numer, a_denom, a_expo = a_triple
numer = d_numer * t_numer * a_numer
denom = d_denom * t_denom * a_denom
expo = d_expo + t_expo + a_expo
gcd_value = math.gcd(numer, denom)
numer //= gcd_value
denom //= gcd_value
if (numer, denom, expo) == self.triple:
successes.append({d_unit.name: d_power,
t_unit.name: t_power,
a_unit.name: a_power})
# Return the success with the fewest keys
if successes:
lengths = [len(k) for k in successes]
best = min(lengths)
for k, length in enumerate(lengths):
if length == best:
return successes[k]
# Failing that, use a standard unit and define the coefficient too
(numer, denom, pi_expo) = self.triple
if denom == 1 and pi_expo == 0:
coefft = numer
else:
coefft = numer / denom * np.pi**pi_expo
new_dict = {'' : coefft,
'km' : self.exponents[0],
's' : self.exponents[1],
'rad': self.exponents[2]}
return new_dict
[docs]
def get_name(self):
"""Get the name of this Unit object.
Returns:
str or dict or None: The name of this Unit object.
"""
name = self.name or self.create_name()
return Unit.name_to_str(name)
[docs]
def set_name(self, name):
"""Set the name of this Unit object.
Parameters:
name (str or dict): The new name for this Unit object.
"""
self.name = name
return self
##########################################################################################
# Define the most common units and their names
##########################################################################################
Unit.UNITLESS = Unit((0, 0, 0), (1, 1, 0), '')
Unit.KM = Unit((1, 0, 0), (1, 1, 0), 'km')
Unit.KILOMETER = Unit((1, 0, 0), (1, 1, 0), 'kilometer')
Unit.KILOMETERS = Unit((1, 0, 0), (1, 1, 0), 'kilometers')
Unit.M = Unit((1, 0, 0), (1, 1000, 0), 'm')
Unit.METER = Unit((1, 0, 0), (1, 1000, 0), 'meter')
Unit.METERS = Unit((1, 0, 0), (1, 1000, 0), 'meters')
Unit.CM = Unit((1, 0, 0), (1, 100000, 0), 'cm')
Unit.CENTIMETER = Unit((1, 0, 0), (1, 100000, 0), 'centimeter')
Unit.CENTIMETERS = Unit((1, 0, 0), (1, 100000, 0), 'centimeters')
Unit.MM = Unit((1, 0, 0), (1, 1000000, 0), 'mm')
Unit.MILLIMETER = Unit((1, 0, 0), (1, 1000000, 0), 'millimeter')
Unit.MILLIMETERS = Unit((1, 0, 0), (1, 1000000, 0), 'millimeters')
Unit.MICRON = Unit((1, 0, 0), (1, 1000000000, 0), 'micron')
Unit.MICRONS = Unit((1, 0, 0), (1, 1000000000, 0), 'microns')
Unit.S = Unit((0, 1, 0), ( 1, 1, 0), 's')
Unit.SEC = Unit((0, 1, 0), ( 1, 1, 0), 'sec')
Unit.SECOND = Unit((0, 1, 0), ( 1, 1, 0), 'second ')
Unit.SECONDS = Unit((0, 1, 0), ( 1, 1, 0), 'seconds')
Unit.MIN = Unit((0, 1, 0), ( 60, 1, 0), 'min')
Unit.MINUTE = Unit((0, 1, 0), ( 60, 1, 0), 'minute')
Unit.MINUTES = Unit((0, 1, 0), ( 60, 1, 0), 'minutes')
Unit.H = Unit((0, 1, 0), ( 3600, 1, 0), 'h')
Unit.HOUR = Unit((0, 1, 0), ( 3600, 1, 0), 'hour')
Unit.HOURS = Unit((0, 1, 0), ( 3600, 1, 0), 'hours')
Unit.D = Unit((0, 1, 0), (86400, 1, 0), 'd')
Unit.DAY = Unit((0, 1, 0), (86400, 1, 0), 'day')
Unit.DAYS = Unit((0, 1, 0), (86400, 1, 0), 'days')
Unit.MS = Unit((0, 1, 0), ( 1, 1000, 0), 'ms')
Unit.MSEC = Unit((0, 1, 0), ( 1, 1000, 0), 'msec')
Unit.RAD = Unit((0, 0, 1), (1, 1, 0), 'rad')
Unit.RADIAN = Unit((0, 0, 1), (1, 1, 0), 'radian')
Unit.RADIANS = Unit((0, 0, 1), (1, 1, 0), 'radians')
Unit.MRAD = Unit((0, 0, 1), (1, 1000, 0), 'mrad')
Unit.MILLIRAD = Unit((0, 0, 1), (1, 1000, 0), 'millirad')
Unit.DEG = Unit((0, 0, 1), (1, 180, 1), 'deg')
Unit.DEGREE = Unit((0, 0, 1), (1, 180, 1), 'degree')
Unit.DEGREES = Unit((0, 0, 1), (1, 180, 1), 'degrees')
Unit.ARCHOUR = Unit((0, 0, 1), (1, 12, 1), 'archour')
Unit.ARCHOURS = Unit((0, 0, 1), (1, 12, 1), 'archours')
Unit.ARCMIN = Unit((0, 0, 1), (1, 180*60, 1), 'arcmin')
Unit.ARCMINUTE = Unit((0, 0, 1), (1, 180*60, 1), 'arcminute')
Unit.ARCMINUTES = Unit((0, 0, 1), (1, 180*60, 1), 'arcminutes')
Unit.ARCSEC = Unit((0, 0, 1), (1, 180*3600, 1), 'arcsec')
Unit.ARCSECOND = Unit((0, 0, 1), (1, 180*3600, 1), 'arcsecond')
Unit.ARCSECONDS = Unit((0, 0, 1), (1, 180*3600, 1), 'arcseconds')
Unit.REV = Unit((0, 0, 1), (2, 1, 1), 'rev')
Unit.REVS = Unit((0, 0, 1), (2, 1, 1), 'revs')
Unit.ROTATION = Unit((0, 0, 1), (2, 1, 1), 'rotation')
Unit.ROTATIONS = Unit((0, 0, 1), (2, 1, 1), 'rotations')
Unit.CYCLE = Unit((0, 0, 1), (2, 1, 1), 'cycle')
Unit.CYCLES = Unit((0, 0, 1), (2, 1, 1), 'cycles')
Unit.STER = Unit((0, 0, 2), (1, 1, 0), 'ster')
# Create dictionaries to convert between name and units
Unit._NAME_TO_UNIT = {}
Unit._TUPLES_TO_UNIT = {}
# Assemble a list of all the recognized units
Unit._DISTANCE_LIST = [Unit.KM, Unit.M, Unit.CM, Unit.MM, Unit.MICRON]
Unit._TIME_LIST = [Unit.S, Unit.D, Unit.H, Unit.MIN, Unit.MSEC]
Unit._ANGLE_LIST = [Unit.RAD, Unit.MRAD, Unit.DEG, Unit.ARCSEC, Unit.ARCMIN, Unit.ARCHOUR,
Unit.CYCLES, Unit.STER]
Unit._UNITS_BY_EXPO = [Unit._DISTANCE_LIST, # index = 0
Unit._TIME_LIST, # index = 1
Unit._ANGLE_LIST] # index = 2
Unit._STANDARD_LIST = ([Unit.UNITLESS] +
Unit._DISTANCE_LIST +
Unit._TIME_LIST +
Unit._ANGLE_LIST)
# Fill in the dictionaries
for unit in Unit._STANDARD_LIST:
Unit._NAME_TO_UNIT[unit.name] = unit
Unit._TUPLES_TO_UNIT[(unit.exponents, unit.triple)] = unit
##########################################################################################