def test_properties_Fluid(): props = Fluid.ALL_PROPERTIES fluid_names = ['water', 'methane', 'R134a'] Ts = [ 25, 0, -1, -100, np.nan, [25, 30], [np.nan, 25], [np.nan, np.nan], [np.inf, np.nan], np.linspace(0, 10, 10), np.linspace(-10, 10, 10) ] Ps = [ 1, 0, -1, -100, np.nan, [3, 4], [np.nan, 3], [np.nan, np.nan], [np.inf, np.nan], np.linspace(0, 10, 10), np.linspace(-10, 10, 10) ] for fluid_name in fluid_names: for T, P in zip(Ts, Ps): fluid = Fluid(fluid_name, T=Q(T, 'C'), P=Q(P, 'bar')) repr(fluid) for p in props: getattr(fluid, p)
def test_properties_HumidAir(): props = HumidAir.ALL_PROPERTIES Ts = [ 25, 0, -1, -100, np.nan, [25, 30], [np.nan, 25], [np.nan, np.nan], [np.inf, np.nan], np.linspace(0, 10, 10), np.linspace(-10, 10, 10) ] Ps = [ 1, 0, -1, -100, np.nan, [3, 4], [np.nan, 3], [np.nan, np.nan], [np.inf, np.nan], np.linspace(0, 10, 10), np.linspace(-10, 10, 10) ] Rs = [ 0.5, 0.1, -1, -100, np.nan, -0.5, 0.00001, -0.0001, 0.99999, 1, 1.00001, [0.3, 0.4], [np.nan, 0.3], [np.nan, np.nan], [np.inf, np.nan], np.linspace(0, 1, 10), np.linspace(-0.5, 0.5, 10) ] for T, P, R in zip(Ts, Ps, Rs): ha = HumidAir(T=Q(T, 'C'), P=Q(P, 'bar'), R=Q(R)) repr(ha) for p in props: getattr(ha, p)
def test_Quantity_integration(): x, y, z = sp.symbols('x, y, z') expr = x * y / z result = expr.subs({ x: Q(235, 'yard'), y: Q(98, 'K/m²'), z: Q(0.4, 'minutes') }) Q.from_expr(result).dimensionality
def test_check(): assert not Q(1, 'kg').check('[energy]') assert Q(1, 'kg').check(Mass) assert not Q(1, 'kg').check(Energy) @check('[length]', '[mass]') def func(a, b): return a * b func(Q(1, 'yd'), Q(20, 'lbs'))
def test_context(): with silence_stdout(): print('silenced') with quantity_format('~Lx'): s = str(Q(1, 'kPa')) assert s == '\\SI[]{1}{\\kilo\\pascal}' s = str(Q(1, 'kPa')) assert s == '1 kPa'
def test_get_function(): x, y, z = sp.symbols('x, y, z') expr = 25 * x * y / z fcn = get_function(expr, units=True) result_arr = fcn({ x: Q(np.array([235, 335]), 'yard'), y: Q(np.array([2, 5]), 'm²'), z: Q(0.4, 'm³/kg') })
def test_wraps(): # @wraps(ret, args, strict=True|False) is a convenience # decorator for making the input/output of a function into Quantity # however, it does not enforce the return value @wraps('kg', ('m', 'kg'), strict=True) def func(a, b): # this is incorrect, cannot add 1 to a dimensional Quantity return a * b**2 + 1 assert isinstance(func(Q(1, 'yd'), Q(20, 'lbs')), Q['Mass']) assert Q(1, 'bar').check(Pressure)
def test_heat_balance(): assert isinstance(heat_balance(Q(2, 'kg/s'), Q(2, 'kJ/s')), Q['Temperature']) assert isinstance(heat_balance(Q(2, 'K'), Q(2, 'kJ/s')), Q['MassFlow']) assert isinstance(heat_balance(Q(2, 'kg'), Q(2, 'delta_degF')), Q['Energy']) assert isinstance(heat_balance(Q(2, 'kg/s'), Q(2, 'delta_degF')), Q['Power'])
def test_typechecked(): @typechecked def func_a(a: Quantity['Temperature']) -> Quantity['Pressure']: return Q(2, 'bar') assert func_a(Q(2, 'degC')) == Q(2, 'bar') with pytest.raises(TypeError): func_a(Q(2, 'meter')) @typechecked def func_b(a: Quantity) -> Quantity['Pressure']: return a assert func_b(Q(2, 'bar')) == Q(2, 'bar') assert func_b(Q(2, 'psi')) == Q(2, 'psi') assert func_b(Q(2, 'mmHg')) == Q(2, 'mmHg') with pytest.raises(TypeError): func_a(Q(2, 'meter'))
def test_shapes(): N = 16 T = Q(np.linspace(50, 60, N).reshape(4, 4), 'C') P = Q(np.linspace(2, 4, N).reshape(4, 4), 'bar') water = Fluid('water', T=T, P=P) assert water.D.m.shape == P.m.shape assert water.D.m.shape == T.m.shape N = 27 T = Q(np.linspace(50, 60, N).reshape(3, 3, 3), 'C') P = Q(np.linspace(2, 4, N).reshape(3, 3, 3), 'bar') water = Fluid('water', T=T, P=P) assert water.D.m.shape == P.m.shape assert water.D.m.shape == T.m.shape
def test_invalid_areas(): N = 10 T = Q(np.linspace(-100, -50, N), 'K') P = Q(np.linspace(-1, -2, N), 'bar') water = Fluid('water', T=T, P=P) assert water.D.check(Density) assert isinstance(water.D.m, np.ndarray) T = Q(np.linspace(-100, 300, N), 'K') P = Q(np.linspace(-1, 2, N), 'bar') water = Fluid('water', T=T, P=P) assert water.D.check(Density) assert isinstance(water.D.m, np.ndarray) assert np.isnan(water.D.m[0]) assert not np.isnan(water.D.m[-1]) arr1 = np.linspace(-100, 400, N) arr2 = np.linspace(-1, 2, N) arr1[-2] = np.nan arr2[-1] = np.nan arr2[-3] = np.nan T = Q(arr1, 'K') P = Q(arr2, 'bar') water = Fluid('water', T=T, P=P) assert water.D.m.size == N
def test_intermediate_temperatures(): T1, T2 = intermediate_temperatures(Q(25, 'degC'), Q(10, 'degC'), Q(0.05, 'W/m/K'), Q(10, 'cm'), Q(1, 'W/m²/K'), Q(2, 'W/m²/K'), 0.7) assert isinstance(T1, Q['Temperature']) assert isinstance(T2, Q[Temperature]) assert T2.check(Temperature) assert T2.check('K')
class Constants: """ Collection of constants. Use a single instance of this class to refer to these constants. """ R = Q(8.3144598, 'kg*m²/K/mol/s²') SIGMA = Q(5.670374419e-8, 'W/m**2/K**4') default_density: Quantity[Density] = Q(997, 'kg/m³') normal_conditions_pressure: Quantity[Pressure] = Q(1, 'atm') normal_conditions_temperature: Quantity[Temperature] = Q(0, '°C').to('K') standard_conditions_pressure: Quantity[Pressure] = Q(1, 'atm') standard_conditions_temperature: Quantity[Temperature] = Q(15, 'degC').to('K')
def test_Water(): water_single = Water(T=Q(25, '°C'), P=Q(5, 'bar')) repr(water_single) water_multi = Water(T=Q(np.linspace(25, 50), '°C'), P=Q(5, 'bar')) repr(water_multi) water_mixed_phase = Water(T=Q(np.linspace(25, 500, 10), '°C'), P=Q(np.linspace(0.5, 10, 10), 'bar')) repr(water_mixed_phase) with pytest.raises(Exception): # mismatching sizes # must access an attribute before it's actually evaluated Water(T=Q(np.linspace(25, 500, 10), '°C'), P=Q(np.linspace(0.5, 10, 50), 'bar')).P
def test_custom_units(): # "ton" should always default to metric ton assert (Q(1, 'ton') == Q(1, 'Ton') == Q(1, 'TON') == Q( 1, 'tonne') == Q(1, 'metric_ton') == Q(1000, 'kg')) assert Q(1, 'US_ton') == Q(907.1847400000001, 'kg') assert (Q(1, 'ton/hour') == Q(1, 'Ton/hour') == Q(1, 'TON/hour') == Q(1, 'tonne/hour') == Q(1, 'metric_ton/hour') == Q(1000, 'kg/hour')) v1 = (Q(1000, 'liter') * Q(1, 'normal')).to_base_units().m v2 = Q(1000, 'normal liter').to_base_units().m v3 = Q(1, 'nm3').m v4 = Q(1, 'Nm3').m # floating point accuracy assert round(v1, 10) == round(v2, 10) == round(v3, 10) == round(v4, 10) factor = Q(12, 'Nm3 water/ (normal liter air)') (Q(1, 'kg water') / factor).to('pound air') Q['NormalVolume'](2, 'nm**3') with pytest.raises(DimensionalityError): Q['NormalVolumeFlow'](2, 'm**3/hour') Q['NormalVolumeFlow'](2, 'Nm**3/hour').to('normal liter/sec') Q[Normal * VolumeFlow](2, 'Nm**3/hour').to('normal liter/sec') Q(2, 'normal liter air / day') Q(2, '1/Nm3').to('1 / (liter normal)')
def test_HumidAir(): T = Q(20, 'C') P = Q(20, 'bar') R = Q(20, '%') ha = HumidAir(T=T, P=P, R=R) ha.V T = Q([25, 34], 'C') P = Q(20, 'bar') R = Q(20, '%') ha = HumidAir(T=T, P=P, R=R) ha.V T = Q([25, 34], 'C') P = Q([20, 30], 'bar') R = Q([20, 40], '%') ha = HumidAir(T=T, P=P, R=R) ha.V T = Q([25, 34], 'C') P = Q([20, 30], 'bar') R = Q([20, np.nan], '%') ha = HumidAir(T=T, P=P, R=R) ha.V T = Q([np.nan, 34], 'C') P = Q([np.nan, 30], 'bar') R = Q([20, np.nan], '%') ha = HumidAir(T=T, P=P, R=R) ha.V T = Q([20, 40], 'C') P = Q([20, 1], 'bar') R = Q([20, 101], '%') ha = HumidAir(T=T, P=P, R=R) val = ha.V.m assert not np.isnan(val[0]) assert np.isnan(val[1])
def test_Fluid(): fld = Fluid('R123', P=Q(2, 'bar'), T=Q(25, '°C')) assert fld.get('S') == Q(1087.7758824621442, 'J/(K kg)') assert fld.D == fld.get('D') water = Fluid('water', P=Q(2, 'bar'), T=Q(25, '°C')) assert water.T.u == Q.get_unit('degC') assert water.T.m == 25 HumidAir(T=Q(25, 'degC'), P=Q(125, 'kPa'), R=Q(0.2, 'dimensionless')) Water(P=Q(1, 'bar'), Q=Q(0.9, '')) Water(P=Q(1, 'bar'), T=Q(0.9, 'degC')) Water(T=Q(1, 'bar'), Q=Q(0.9, '')) with pytest.raises(Exception): # cannot fix all of P, T, Q Water(P=Q(1, 'bar'), T=Q(150, 'degC'), Q=(0.4, '')) # incorrect argument name Water(T=Q(1, 'bar'), P=Q(9, 'degC')) Fluid('water', T=Q([25, 95], 'C'), P=Q([1, 2], 'bar')).H Fluid('water', T=Q([25, np.nan], 'C'), P=Q([1, 2], 'bar')).H Fluid('water', T=Q([np.nan, np.nan], 'C'), P=Q([1, 2], 'bar')).H Fluid('water', T=Q([np.nan, np.nan], 'C'), P=Q([np.nan, np.nan], 'bar')).H Fluid('water', T=Q(23, 'C'), P=Q([1, 2], 'bar')).H Fluid('water', T=Q(23, 'C'), P=Q([1], 'bar')).H Fluid('water', T=Q([23, 25], 'C'), P=Q([1], 'bar')).H Fluid('water', T=Q([23, 25], 'C'), P=Q(np.nan, 'bar')).H Fluid('water', T=Q([23, 25], 'C'), P=Q([1, np.nan], 'bar')).H Water(T=Q([25, 25, 63], 'C'), Q=Q([np.nan, np.nan, 0.4], '')).H Water(T=Q([25, np.nan, 63], 'C'), Q=Q([np.nan, 0.2, 0.5], '')).H Water(T=Q([25, np.nan, np.nan], 'C'), Q=Q([np.nan, 0.2, np.nan], '')).H # returns empty array (not nan) ret = Fluid('water', T=Q([], 'C'), P=Q([], 'bar')).H.m assert isinstance(ret, np.ndarray) and ret.size == 0 ret = Fluid('water', T=Q([], 'C'), P=Q((), 'bar')).H.m assert isinstance(ret, np.ndarray) and ret.size == 0 ret = Fluid('water', T=Q([], 'C'), P=Q(np.array([]), 'bar')).H.m assert isinstance(ret, np.ndarray) and ret.size == 0 # 1-element list or array works in the same way as scalar, # except that the output is also a 1-element list or array ret = Water(P=Q([2, 3], 'bar'), Q=Q([0.5])).D.m assert isinstance(ret, np.ndarray) and ret.size == 2 ret = Water(P=Q([2, 3], 'bar'), Q=Q(0.5)).D.m assert isinstance(ret, np.ndarray) and ret.size == 2 ret = Water(P=Q([2], 'bar'), Q=Q([0.5])).D.m assert isinstance(ret, np.ndarray) and ret.size == 1 ret = Water(P=Q([2], 'bar'), Q=Q(0.5)).D.m assert isinstance(ret, np.ndarray) and ret.size == 1 ret = Water(P=Q(2, 'bar'), Q=Q([0.5])).D.m assert isinstance(ret, np.ndarray) and ret.size == 1 ret = Water(P=Q(2, 'bar'), Q=Q(0.5)).D.m assert isinstance(ret, float) ret = Water(P=Q([], 'bar'), Q=Q([0.5])).D.m assert isinstance(ret, np.ndarray) and ret.size == 0 ret = Water(P=Q([], 'bar'), Q=Q([])).D.m assert isinstance(ret, np.ndarray) and ret.size == 0 ret = Water(P=Q(np.array([]), 'bar'), Q=Q(np.array([]))).D.m assert isinstance(ret, np.ndarray) and ret.size == 0 # returns 1-element list assert isinstance( Fluid('water', T=Q([23], 'C'), P=Q([1], 'bar')).H.m, np.ndarray) assert isinstance( Fluid('water', T=Q(23, 'C'), P=Q([1], 'bar')).H.m, np.ndarray) assert isinstance( Fluid('water', T=Q([23], 'C'), P=Q(1, 'bar')).H.m, np.ndarray) # returns float assert isinstance(Fluid('water', T=Q(23, 'C'), P=Q(1, 'bar')).H.m, float) with pytest.raises(ValueError): Fluid('water', T=Q([np.nan, np.nan], 'C'), P=Q([np.nan, np.nan, np.nan], 'bar')).H Fluid('water', T=Q([np.nan, np.nan], 'C'), P=Q([], 'bar')).H
def test_convert_gas_volume(): ret = convert_gas_volume(Q(1, 'm3'), 'N', (Q(2, 'bar'), Q(25, 'degC'))) assert ret.check(Q(0, 'liter'))
def test_Q(): # test that Quantity objects can be constructed Q(1, 'dimensionless') Q(1, 'kg') Q(1, 'bar') Q(1, 'h') Q(1, 'newton') Q(1, 'cSt') # make sure that the alias Q behaves identically to Quantity assert Q(1) == Quantity(1) assert type(Q(1)) is type(Quantity(1)) assert type(Q) is type(Quantity) # ensure that the inputs can be nested Q(Q(1, 'kg')) mass = Q(12, 'kg') Q(Q(Q(Q(mass)))) Q(Q(Q(Q(mass), 'lbs'))) Q(Q(Q(Q(mass), 'lbs')), 'stone') # no unit input defaults to dimensionless assert Q(12).check('') assert Q(1) == Q(100, '%') Q['Dimensionless'](21) assert isinstance(Q(21), Q['Dimensionless']) assert Q(1) == Q('1') assert Q(1) == Q('\n1\n') assert Q(1) == Q('1 dimensionless') # check type of "m" assert isinstance(Q(1, 'meter').m, int) assert isinstance(Q(2.3, 'meter').m, float) assert isinstance(Q([2, 3.4], 'meter').m, np.ndarray) assert isinstance(Q(np.array([2, 3.4]), 'meter').m, np.ndarray) # input Quantity as unit Q(1, Q(2, 'bar')) # input Quantity as val Q(Q(2, 'bar'), 'kPa') # input Quantity as both val and unit Q(Q(2, 'bar'), Q(3, 'kPa')) Q(Q(2, 'bar'), Q(3, 'mmHg')) # check that the dimensionality constraints work Q[Length](1, 'm') Q[Pressure](1, 'kPa') Q[Temperature](1, '°C') # the dimensionalities can also be specified as strings Q['Temperature'](1, '°C') P = Q(1, 'bar') # this Quantity must have the same dimensionality as P Q[P](2, 'kPa') with pytest.raises(DimensionalityError): Q[Temperature](1, 'kg') Q[Pressure](1, 'meter') Q[Mass](1, P) # in-place conversion # NOTE: don't use this for objects that are passed in by the user P3 = Q(1, 'bar') P3.ito('kPa') P3.ito(Q(123123, 'kPa')) assert P3.m == approx(100, rel=1e-12) # test conversions to np.ndarray with int/float dtypes a = Q([1, 2, 3], 'bar') a.ito('kPa') a = Q(np.array([1, 2, 3.0]), 'bar') a.ito('kPa') a = Q(np.array([1.0, 2.0, 3.0]), 'bar') a.ito('kPa') # conversion to new object P4 = Q(1, 'bar') P4_b = P4.to('kPa') P4_b = P4.to(Q(123123, 'kPa')) assert P4_b.m == approx(100, rel=1e-12) assert Q(1, 'bar') == Q(100, 'kPa') == Q('0.1 MPa') == Q('1e5', 'Pa') # check that nested Quantity objects can be used as input # only the first value is used as magnitude, the other Quantity # objects are only used to determine unit P2 = Q(Q(2, 'feet_water'), Q(321321, 'kPa')).to(Q(123123, 'feet_water')) # floating point math might make this off at the N:th decimal assert P2.m == approx(2, rel=1e-12) assert isinstance(P2, Q['Pressure']) with pytest.raises(Exception): # incorrect dimensionalities should raise Exception Q(Q(2, 'feet_water'), Q(321321, 'kg')).to(Q(123123, 'feet_water')) # the UnitsContainer objects can be used to construct new dimensionalities Q[Length * Length * Length / Temperature](1, 'm³/K') with pytest.raises(Exception): Q[Pressure / Area](1, 'bar/m') # percent or % Q(1.124124e-3, '').to('%').to('percent') Q(1.124124e-3).to('%').to('percent') # pd.Series is converted to np.ndarray vals = [2, 3, 4] s = pd.Series(vals, name='Pressure') arr = Q(s, 'bar').to('kPa').m assert isinstance(arr, np.ndarray) assert arr[0] == 200 # np.ndarray magnitudes equality check assert (Q(s, 'bar') == Q(vals, 'bar').to('kPa')).all() # support a single string as input if the # magnitude and units are separated by one or more spaces assert Q('1 meter').check(Length) assert Q('1 meter per second').check(Velocity) assert (Q('1 m') ** 3).check(Volume)
def test_dataframe_assign(): df_multiple_rows = pd.DataFrame( { 'A': [1, 2, 3], 'B': [1, 2, 3], } ) df_single_row = pd.DataFrame( { 'A': [1], 'B': [1], } ) df_empty = pd.DataFrame( { 'A': [], 'B': [], } ) for df in [df_multiple_rows, df_single_row, df_empty]: df['C'] = Q(df.A, 'bar') * Q(25, 'meter') df['Temp'] = Q(df.A, 'degC') with pytest.raises(AttributeError): density = Water( # this is pd.Series[float] T=df.Temp, Q=Q(0.5) ).D density = Water( # wrap in Quantity() to use the Water class T=Q(df.Temp, 'degC'), Q=Q(0.5) ).D # implicitly strips magnitude in whatever unit the Quantity happens to have df['density'] = density # assigns a column with specific unit df['density_with_unit'] = density.to('kg/m3') # the .m accessor is not necessary for vectors df['density_with_unit_magnitude'] = density.to('kg/m3').m # this does not work -- pandas function is_list_like(Q(4, 'bar')) -> True # which means that this fails internally in pandas # ValueError: Length of values (1) does not match length of index (3) # df['D'] = Q(4, 'bar') # i.e. the .m accessor must be used for scalar Quantity assignment df['E'] = Q(df.A, 'bar').m df['F'] = Q(4, 'bar').m
def func_a(a: Quantity['Temperature']) -> Quantity['Pressure']: return Q(2, 'bar')