Cohan
Cohan

Reputation: 4564

Decorator Not Returning Value in Expected Units

The following code uses pint to convert a mass flow to a volumetric flow. To convert from mass to volume, the density of the gas must be calculated. The calculation returns the correct values in the desired units.

import pint
ureg = pint.UnitRegistry()
Q_ = ureg.Quantity


def density(T):
    R = 287 * ureg('J/kg/K')
    P = 101325 * ureg('Pa')
    return (P / R / T.to(ureg.kelvin)).to(ureg('kg/m^3'))


@ureg.wraps(ret='m^3/sec', args=['kg/sec', 'kg/m^3'])
def volumetric_flow_rate(mass_flow_rate, rho):
    return mass_flow_rate / rho


mfr = 1 * ureg('kg/s')
temperature = Q_(25, ureg.degC)

rho = density(temperature)
print(rho)
# 1.1841314120000166 kilogram / meter ** 3

v = volumetric_flow_rate(mfr, rho)
print(v)
# 0.8445008635578583 meter ** 3 / second

However, when I apply to use the @ureg.wraps decorator on the density function in the same way used for volumetric_flow_rate, the value is correct, but the units are not the desired units.

@ureg.wraps(ret='kg/m^3', args=['K'])
def density(T):
    R = 287 * ureg('J/kg/K')
    P = 101325 * ureg('Pa')
    return P / R / T

rho = density(temperature)
print(rho)
# 1.1841314120000166 kelvin * kilogram * pascal / joule kilogram / meter ** 3

v = volumetric_flow_rate(mfr, rho)
print(v)
# 0.8445008635578583 joule / kelvin / kilogram / pascal meter ** 3 / second

The following versions of the decorator all yield the same results (incorrect return units) for the density function:

@ureg.wraps(ret='kg/m^3', args='K')
@ureg.wraps(ret='kg/m^3', args=['K'])
@ureg.wraps(ret='kg/m^3', args=ureg.kelvin)
@ureg.wraps(ret='kg/m^3', args='K', strict=True)
@ureg.wraps(ret='kg/m^3', args=['K'], strict=True)
@ureg.wraps(ret='kg/m^3', args=ureg.kelvin, strict=True)
@ureg.wraps(ret='kg/m^3', args='K', strict=False)
@ureg.wraps(ret='kg/m^3', args=['K'], strict=False)
@ureg.wraps(ret='kg/m^3', args=ureg.kelvin, strict=False)

The following version of the decorator raises an error

@ureg.wraps(ret=ureg('kg/m^3'), args=ureg.kelvin)

TypeError: wraps 'ret' argument must by of type str or Unit, not <class 'pint.quantity.build_quantity_class.<locals>.Quantity'> (1.0 kilogram / meter ** 3)

With that error message, I defined a custom unit mass_flow

ureg.define('mass_flow = 1 * kg / m^3')
print(ureg.mass_flow)
# mass_flow
print(type(ureg.mass_flow))
# <class 'pint.unit.build_unit_class.<locals>.Unit'>
print(1 * ureg.mass_flow)
# 1 mass_flow

The following decorators don't raise an error, however they still yiled the incorrect units (though now with the mass_flow "units".

@ureg.wraps(ret=ureg.mass_flow, args='K')
@ureg.wraps(ret='mass_flow', args='K')
# 1.1841314120000166 kelvin * kilogram * pascal / joule mass_flow

One last ditch attempt I tried was to define R and P outside of the function. Didn't expect it to change the answer, and expectations were correct.

R = 287 * ureg('J/kg/K')
P = 101325 * ureg('Pa')
@ureg.wraps(ret='kg/m^3', args='K')
def density(T):
    return P / R / T

Question:

How can I define the decorator for the density function in a similar manner to the volumetric_flow_rate function so that the body of the function is cleaner and does not require me to do the internal unit conversions?

Furthermore, if the volumetric_flow_rate function has a complex return unit m^3/sec why would kg/m^3 not work for the density function?

Python 3.7.0, Pint 0.10.1

Upvotes: 1

Views: 119

Answers (1)

Cohan
Cohan

Reputation: 4564

Pint appears to strip away all units before entering the function and then applies units when the function is over. As such, either of the following works.

@ureg.wraps(ret='kg/m^3', args=['K', 'Pa', 'J/kg/K'])
def density(T, P=101325 * ureg.Pa, R=287 * ureg('J/kg/K')):
    return P / R / T

@ureg.wraps(ret='kg/m^3', args=['K', 'Pa', 'J/kg/K'], strict=False)
def density(T, P=101325, R=287):
    return P / R / T

The key is to ensure everything in the function is dimensionless. If you call another function within a @ureg.wraps decorated function, and that function returns a dimensional quantity, convert it to the required units and then strip the units by using the .magnitude attribute.

Upvotes: 1

Related Questions