Package: encomp
encompGeneral-purpose library forengineeringcomputations, with focus on clean and consistent interfaces.Documentation athttps://encomp.readthedocs.io/en/latest/encompis tested on Windows and Linux, with Python 3.11.FeaturesMain functionality of theencomplibrary:Handles physical quantities with magnitude(s), dimensionality and unitsModulesencomp.units,encomp.utypesExtends thepintlibraryUses Python's type system to validate dimensionalitiesCompatible withmypyand other type checkersIntegrates with Numpy arrays, Pandas series and Polars series and expressionsJSON serialization and decodingImplements a flexible interface toCoolPropModuleencomp.fluidsUses quantities for all inputs and outputsFluids are represented as class instances, the properties are class attributesExtendsSympyModuleencomp.sympyAdds convenience methods for creating symbols with sub- and superscriptsAdditional functions to convert (algebraic) expressions and systems to Python code that supports Numpy arraysThe other modules implement calculations related to process engineering and thermodynamics.
The moduleencomp.serializeimplements custom JSON serialization and decoding for classes used elsewhere in the library.InstallationInstall withpip:pipinstallencompThis will installencompalong with its dependencies into the currently active Python environment.
To also install optional dependencies:pipinstallencomp[optional]Getting startedTo useencompin a Jupyter Notebook (or REPL), star-import theencomp.sessionmodule:fromencomp.sessionimport*This will import commonly used functions and classes.TheQuantityclassThe fundamental building block ofencompis theencomp.units.Quantityclass (shorthand aliasQ), which is an extension ofpint.Quantity.
This class is used to construct objects with amagnitudeandunit.
Each unit also has adimensionality(combination of the base dimensions), and each dimensionality will have multiple associated units.fromencomp.unitsimportQuantityasQ# converts 1 bar to kPa, displays it in case it's the cell outputQ(1,'bar').to('kPa')# a single string with one numerical value can also be given as inputQ('0.1 MPa').to('bar')# list and tuple inputs are converted to np.ndarrayQ([1,2,3],'bar')*2# [2, 4, 6] bar# in case no unit is specified, the quantity is dimensionlessQ(0.1)==Q(10,'%')Quantitytype systemTheQuantityobject has an associatedDimensionalitytype parameter that is dynamically determined based on the unit.
Each dimensionality (for examplepressure,length,time,dimensionless) is represented by a subclass ofQuantity.Common dimensionalities can be statically determined based on overload variants of theQuantity.__new__method (seeencomp.utypes.get_registered_unitsfor a list of units that support this).
Additionally, operations using*,**and/are also defined using overload variants for combinations of the default dimensionalities.In case the dimensionality cannot be inferred, the type checker will use the dimensionalityUnknown.
At runtime, the dimensionality will be evaluated based on the unit that was specified.
TheUnknowndimensionality is also used for operations using*,**and/that are not explicitly defined as overload variants.If necessary, the dimensionality of a quantity can be explicitly specified by providing a subclass ofencomp.utypes.Dimensionalityas type parameter.Commonly used dimensionalities are defined in theencomp.utypesmodule.
When a new dimensionality is created, the classname will beDimensionality[...](for exampleQuantity[Dimensionality[[mass] ** 2 / [length] ** 3]]).fromencomp.unitsimportQuantityasQfromencomp.utypesimportVolume,MassFlow# the types are inferred by a static type checker like mypy# the unit "m" is registered as a Mass unitm=Q(12,'kg')# Quantity[Mass]V=Q(25,'liter')# Quantity[Volume]# common / and * operations are encoded as overloadsrho=m/V# Quantity[Density]# the unit "kg/week" is not registered by default# the individual units "kg" and "week" are registered, however# the type checker does not know how to combine these unitsm_=Q(25,'kg/week')# Quantity[Unknown]# at runtime, the dimensionality of m_ will be evaluated to MassFlowisinstance(m_,Q[MassFlow])# True# these operations (Mass**2 divided by Volume) are not explicitly defined as overloads# at runtime, the type will be evaluated to# Quantity[Dimensionality[[mass] ** 2 / [length] ** 3]]x=m**2/V# Quantity[Unknown]# the unit name "meter cubed" is not defined using an overload,# the type parameter Volume is instead used to infer the typey=Q[Volume](15,'meter cubed')# Quantity[Volume]# in case the explicitly defined dimensionality does# not match the unit, an error will be raised at runtimey=Q[MassFlow](15,'meter cubed')# ExpectedDimensionalityError: Quantity with unit "m³" has incorrect dimensionality# [length] ** 3, expected [mass] / [time]Runtime type checkingTheQuantitysubtypes can be used to restrict function and class attribute types at runtime.
Use thetypeguard.typecheckeddecorator to apply runtime typechecking to function inputs and outputs:fromtypeguardimporttypecheckedfromtypingimportTypedDictfromencomp.unitsimportQuantityasQfromencomp.utypesimportTemperature,Length,Pressure@typecheckeddefsome_func(T:Q[Temperature])->tuple[Q[Length],Q[Pressure]]:returnT*Q(12.4,'m/K'),Q(1,'bar')some_func(Q(12,'delta_degC'))# the dimensionalities check outsome_func(Q(26,'kW'))# raises an exception:# TypeError: type of argument "T" must be Quantity[Temperature];# got Quantity[Power] insteadclassOutputDict(TypedDict):P:Q[Pressure]T:Q[Temperature]@typecheckeddefanother_func(s:Q[Length])->OutputDict:return{'T':Q(25,'m'),'P':Q(25,'kPa')}another_func(Q(25,'m'))# TypeError: type of dict item "T" for the return value must be# encomp.units.Quantity[Temperature]; got encomp.units.Quantity[Length] insteadTo create a new dimensionality (for example temperature difference per mass flow rate), combine thepint.UnitsContainerobjects stored in thedimensionsclass attribute.fromencomp.unitsimportQuantityasQfromencomp.unitsimportDimensionalityErrorfromencomp.utypesimportTemperatureDifference,MassFlow,Volume,Dimensionality# the class name TemperaturePerMassFlow must be globally uniqueclassTemperaturePerMassFlow(Dimensionality):dimensions=TemperatureDifference.dimensions/MassFlow.dimensions# note the extra parentheses around (kg/s)qty=Q[TemperaturePerMassFlow](1,'delta_degC/(kg/s)')# raises an exception since liter is Length**3 and the Quantity expects Masstry:another_qty=Q[TemperaturePerMassFlow](1,'delta_degC/(liter/hour)')exceptDimensionalityError:pass# create a new subclass of Quantity with restricted input unitsCustomCoolingCapacity=Q[TemperaturePerMassFlow]# the pint library handles a wide range of input formats and unit names# the prefix "delta_" can be omitted in this caseq1=CustomCoolingCapacity(6,'°F per (lbs per week)')q2=Q('3 deltdegree_Fahrenheit per (pound per fortnight)')assertq1==q2asserttype(q1)istype(q2)TheFluidclassThe classencomp.fluids.Fluidis a wrapper around theCoolProplibrary.
The class uses two input points (three for humid air) that fix the state of the fluid.
Other fluid parameters can be evaluated using attribute access.
The outputs and inputs areQuantityobjects.
CoolProp property names and codes are used throughout.
Use the.search()method to find the correct name.fromencomp.unitsimportQuantityasQfromencomp.fluidsimportFluidair=Fluid('air',T=Q(25,'degC'),P=Q(2,'bar'))# common fluid properties have type hints, and show up using autocompleteair.D# 2.338399526231983 kilogram/meter3air.search('density')# ['DELTA, Delta: Reduced density (rho/rhoc) [dimensionless]',#  'DMOLAR, Dmolar: Molar density [mol/m³]',#  'D, DMASS, Dmass: Mass density [kg/m³]', ...# any of the names are valid attributes (case-sensitive)air.Dmolar# 80.73061937328056 mole/meter3The fluid name'water'(or the subclassWater) usesIAPWSto evaluate steam and water properties.fromencomp.unitsimportQuantityasQfromencomp.fluidsimportFluid,WaterFluid('water',P=Q(25,'bar'),T=Q(550,'°C'))# <Fluid "water", P=2500 kPa, T=550.0 °C, D=6.7 kg/m³, V=0.031 cP># note that the CoolProp property "Q" (vapor quality) has the same name as the class# the Water class has a slightly different string representationWater(Q=Q(0.5),T=Q(170,'degC'))# <Water (Two-phase), P=792 kPa, T=170.0 °C, D=8.2 kg/m³, V=0.0 cP>Water(H=Q(2800,'kJ/kg'),S=Q(7300,'J/kg/K'))# <Water (Gas), P=225 kPa, T=165.8 °C, D=1.1 kg/m³, V=0.0 cP>TheHumidAirclass requires three input points (Rmeans relative humidity):fromencomp.unitsimportQuantityasQfromencomp.fluidsimportHumidAirHumidAir(P=Q(1,'bar'),T=Q(100,'degC'),R=Q(0.5))# <HumidAir, P=100 kPa, T=100.0 °C, R=0.50, Vda=2.2 m³/kg, Vha=1.3 m³/kg, M=0.017 cP>TestsFirst, make sure the development dependencies are installed withpip install encomp[dev]orpip install encomp[full].
Run the tests withpytest-Wignore--pyargsencomp-pno:mypy-testingSettingsThe attributes in theencomp.settings.Settingsclass can be modified with an.env-file.
Place a file named.envin the current working directory to override the default settings.
The attribute names are prefixed withENCOMP_.TODODocument theQuantity[Dimensionality]type systemWhat is the license of this package?For example,pintuses3-Clause BSD License, this should be compatible withMITShould this package include thepintlicense text somewhere?Extending thepintpackage counts as modification