Source code for AFL.automation.shared.units

import pint

units = pint.UnitRegistry()
units.default_system = 'cgs'
AVOGADROS_NUMBER = 6.0221409e+23 * units('1/mol')

DEFAULT_UNITS = {}
DEFAULT_UNITS['volume'] = 'ml'
DEFAULT_UNITS['mass'] = 'mg'
DEFAULT_UNITS['concentration'] = 'g/ml'
DEFAULT_UNITS['density'] = 'g/ml'
DEFAULT_UNITS['molarity'] = 'millimolar'

SUPPORTED_TYPES = ['volume', 'mass', 'density', 'molarity', 'concentration','dimensionless']


[docs] def has_units(value: pint.Quantity) -> bool: return hasattr(value, 'units')
[docs] def is_volume(value: pint.Quantity) -> bool: return (len(value.dimensionality) == 1) and (value.dimensionality['[length]'] == 3)
[docs] def is_molarity(value: pint.Quantity) -> bool: return ((len(value.dimensionality) == 2) and (value.dimensionality['[length]'] == -3) and ( value.dimensionality['[substance]'] == 1))
[docs] def is_mass(value: pint.Quantity) -> bool: return (len(value.dimensionality) == 1) and (value.dimensionality['[mass]'] == 1)
[docs] def is_density(value: pint.Quantity) -> bool: return ((len(value.dimensionality) == 2) and (value.dimensionality['[mass]'] == 1) and ( value.dimensionality['[length]'] == -3))
[docs] def is_concentration(value: pint.Quantity) -> bool: return ((len(value.dimensionality) == 2) and (value.dimensionality['[mass]'] == 1) and ( value.dimensionality['[length]'] == -3))
[docs] def is_dimensionless(value: pint.Quantity) -> bool: return len(value.dimensionality) == 0
[docs] def get_unit_type(value: pint.Quantity) -> str: if is_volume(value): return 'volume' elif is_molarity(value): return 'molarity' elif is_mass(value): return 'mass' elif is_density(value): return 'density' elif is_concentration(value): return 'concentration' elif is_dimensionless(value): return 'dimensionless' else: raise ValueError(f'Unit system ({value}) not recognized as one of: {SUPPORTED_TYPES}')
[docs] def to_quantity(value: str | pint.Quantity) -> pint.Quantity: """Convert a string to a pint quantity""" if isinstance(value, str): return units(value) return value
[docs] def enforce_units(value:None | str | pint.Quantity, unit_type:str) -> pint.Quantity: """Ensure that a number has units and convert to the default_units""" # None bypasses all unit testing if value is None: return value value = to_quantity(value) if unit_type.lower() not in SUPPORTED_TYPES: raise ValueError(f'Not configured to enforce unit_type: {unit_type}') if unit_type.lower() == 'dimensionless': # this should return the value as an int/float; pint converts strings to pure numerical types if no unit is provided return value if not has_units(value): raise ValueError('Supplied value must have units!') elif unit_type.lower() == 'volume': if not is_volume(value): raise ValueError(f'Supplied value must be a volume not {value.dimensionality}') else: value = value.to(DEFAULT_UNITS['volume']) elif unit_type.lower() == 'mass': if not is_mass(value): raise ValueError(f'Supplied value must be a mass not {value.dimensionality}') else: value = value.to(DEFAULT_UNITS['mass']) elif unit_type.lower() == 'density': if not is_density(value): raise ValueError(f'Supplied value must be a density not {value.dimensionality}') else: value = value.to(DEFAULT_UNITS['density']) elif unit_type.lower() == 'concentration': if not is_concentration(value): raise ValueError(f'Supplied value must be a concentration not {value.dimensionality}') else: value = value.to(DEFAULT_UNITS['concentration']) elif unit_type.lower() == 'molarity': if not is_molarity(value): raise ValueError(f'Supplied value must be a molarity not {value.dimensionality}') else: value = value.to(DEFAULT_UNITS['molarity']) return value