Getting started¶
A quick tour of the two core classes: Quantity (typed physical quantities on top of pint) and Fluid (thermodynamic properties via CoolProp).
Quantities¶
A Quantity combines a magnitude with a unit. The dimensionality is tracked as a type: quantities with incompatible dimensionalities cannot be combined or compared.
[1]:
from encomp.units import Quantity as Q
mf = Q(25, "t/h")
mf
[1]:
[2]:
# convert to another unit with .to()
mf.to("kg/s")
[2]:
[3]:
# arithmetic keeps track of the resulting dimensionality
energy_content = Q(4.5, "MWh/t")
power = (mf * energy_content).to("MW")
power, type(power)
[3]:
(<Quantity(112.5, 'megawatt')>, encomp.units.Quantity[Power, float])
[4]:
from encomp.utypes import MassFlow, Power
# check dimensionalities at runtime
power.check(Power), power.check(MassFlow)
[4]:
(True, False)
Incompatible operations raise a DimensionalityError (from pint.errors, so a single except clause catches everything unit-related):
[5]:
from pint.errors import DimensionalityError
try:
mf + power
except DimensionalityError as e:
print(e)
Quantities with different dimensionalities are not compatible: <class 'encomp.units.Quantity[MassFlow, float]'> and <class 'encomp.units.Quantity[Power, float]'>.
Vector magnitudes¶
The magnitude can also be a 1-D numpy array (or a polars Series/expression) instead of a single float:
[6]:
import numpy as np
T = Q(np.linspace(25, 95, 8), "degC")
T.to("K")
[6]:
| Magnitude | [298.15 308.15 318.15 328.15 338.15 348.15 358.15 368.15] |
|---|---|
| Units | K |
Temperature vs. temperature difference¶
Absolute temperatures and temperature differences share the [temperature] dimension but are distinct dimensionality types: subtracting two temperatures yields a TemperatureDifference, and the two cannot be mixed up silently.
[7]:
dT = Q(95, "degC") - Q(25, "degC")
dT, type(dT)
[7]:
(<Quantity(70.0, 'delta_degree_Celsius')>,
encomp.units.Quantity[TemperatureDifference, float])
[8]:
# a ΔT can be expressed in any multiplicative temperature unit (K, delta_degF, ...)
cp = Q(4.186, "kJ/(kg K)")
duty = (Q(2.0, "kg/s") * cp * dT).to("kW")
duty
[8]:
Fluid properties¶
Fluid (and the Water / HumidAir convenience classes) evaluate thermodynamic properties through CoolProp. A fluid is fixed by two state points (three for humid air), passed as quantities in any compatible unit:
[9]:
from encomp.fluids import Fluid, HumidAir, Water
water = Water(P=Q(4, "bar"), T=Q(140, "degC"))
water
[9]:
<Water (Liquid), P=400 kPa, T=140.0 °C, D=926.2 kg/m³, V=0.2 cP>
[10]:
# properties are Quantity objects with the correct dimensionality type
water.D, water.H, water.C
[10]:
(<Quantity(926.1529497875844, 'kilogram / meter ** 3')>,
<Quantity(589.2252002247815, 'kilojoule / kilogram')>,
<Quantity(4.2859264113875, 'kilojoule / kilogram / kelvin')>)
[11]:
# saturated steam: fix the state with pressure and vapor quality
steam = Water(P=Q(4, "bar"), Q=Q(1, ""))
steam.T, steam.H
[11]:
(<Quantity(143.61253299838273, 'degree_Celsius')>,
<Quantity(2738.0566229312813, 'kilojoule / kilogram')>)
[12]:
# any CoolProp fluid name works, with vector inputs too
co2 = Fluid("CO2", P=Q(np.linspace(50, 150, 4), "bar"), T=Q(25, "degC"))
co2.D
[12]:
| Magnitude | [131.27476733866456 785.0819246938266 841.3564483986074 876.4729275452014] |
|---|---|
| Units | kg/m3 |
[13]:
humid_air = HumidAir(P=Q(1, "atm"), T=Q(25, "degC"), R=Q(0.6, ""))
# W: humidity ratio, Vha: mixture volume per humid air
humid_air.W, humid_air.Vha
[13]:
(<Quantity(0.01194902651563823, 'dimensionless')>,
<Quantity(0.8503644116822783, 'meter ** 3 / kilogram')>)
Polars integration¶
Quantities can hold polars expressions, and fluid properties evaluate lazily inside a polars query through a GIL-free plugin – independent properties run in parallel. See the Parallel CoolProp evaluation with Polars section of the docs for details.
[14]:
import polars as pl
df = pl.DataFrame(
{
"P_bar": [1.0, 5.0, 10.0, 50.0],
"T_C": [25.0, 80.0, 160.0, 250.0],
}
)
# wrap columns as quantities to convert them to the units CoolProp expects
P = Q(pl.col("P_bar"), "bar").to("Pa")
T = Q(pl.col("T_C"), "degC").to("K")
water = Water(P=P, T=T)
df.with_columns(
rho=water.D.m,
h=water.H.to("kJ/kg").m,
)
[14]:
| P_bar | T_C | rho | h |
|---|---|---|---|
| f64 | f64 | f64 | f64 |
| 1.0 | 25.0 | 997.047435 | 104.928068 |
| 5.0 | 80.0 | 971.981068 | 335.308884 |
| 10.0 | 160.0 | 907.678747 | 675.797392 |
| 50.0 | 250.0 | 800.081429 | 1085.661995 |