diff --git a/interfaces/cython/cantera/_cantera.pxd b/interfaces/cython/cantera/_cantera.pxd index 0de7e11b6..b980e8ab9 100644 --- a/interfaces/cython/cantera/_cantera.pxd +++ b/interfaces/cython/cantera/_cantera.pxd @@ -254,6 +254,7 @@ cdef class _SolutionBase: cdef CxxThermoPhase* thermo cdef CxxKinetics* kinetics cdef CxxTransport* transport + cdef int thermoBasis cdef class Mixture: cdef CxxMultiPhase* mix diff --git a/interfaces/cython/cantera/thermo.pyx b/interfaces/cython/cantera/thermo.pyx index cd30357b9..b8cd0aa1d 100644 --- a/interfaces/cython/cantera/thermo.pyx +++ b/interfaces/cython/cantera/thermo.pyx @@ -1,13 +1,51 @@ cdef enum ThermoBasis: - mass = 0 - molar = 1 + massBasis = 0 + molarBasis = 1 ctypedef void (*thermoMethod1d)(CxxThermoPhase*, double*) except + cdef class ThermoPhase(_SolutionBase): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.thermoBasis = massBasis + def report(self, show_thermo=True): return pystr(self.thermo.report(bool(show_thermo))) + def __call__(self): + print(self.report()) + + property basis: + def __get__(self): + if self.thermoBasis == massBasis: + return 'mass' + else: + return 'molar' + + def __set__(self, value): + if value == 'mass': + self.thermoBasis = massBasis + elif value == 'molar': + self.thermoBasis = molarBasis + else: + raise ValueError("Valid choices are 'mass' or 'molar'.") + + cdef double _massFactor(self): + """ Conversion factor from current basis to kg """ + if self.thermoBasis == molarBasis: + return self.thermo.meanMolecularWeight() + else: + return 1.0 + + cdef double _moleFactor(self): + """ Conversion factor from current basis to moles """ + if self.thermoBasis == massBasis: + return 1.0/self.thermo.meanMolecularWeight() + else: + return 1.0 + + ####### Composition, species, and elements ######## + property nElements: def __get__(self): return self.thermo.nElements() @@ -28,17 +66,12 @@ cdef class ThermoPhase(_SolutionBase): def speciesIndex(self, name): return self.thermo.speciesIndex(stringify(name)) - property P: - def __get__(self): - return self.thermo.pressure() - - property T: - def __get__(self): - return self.thermo.temperature() - - property rho: - def __get__(self): - return self.thermo.density() + def nAtoms(self, species, element): + if isinstance(element, str): + element = self.elementIndex(element) + if isinstance(species, str): + species = self.speciesIndex(species) + return self.thermo.nAtoms(species, element) cdef np.ndarray _getArray1(self, thermoMethod1d method): cdef np.ndarray[np.double_t, ndim=1] data = np.empty(self.nSpecies) @@ -57,6 +90,9 @@ cdef class ThermoPhase(_SolutionBase): def __get__(self): return self._getArray1(thermo_getMolecularWeights) + def molecularWeight(self, int k): + return self.thermo.molecularWeight(k) + property Y: def __get__(self): return self._getArray1(thermo_getMassFractions) @@ -66,8 +102,11 @@ cdef class ThermoPhase(_SolutionBase): else: self._setArray1(thermo_setMassFractions, Y) - def massFraction(self, int k): - return self.thermo.massFraction(k) + def massFraction(self, species): + if isinstance(species, str): + return self.thermo.massFraction(stringify(species)) + else: + return self.thermo.massFraction(species) property X: def __get__(self): @@ -78,8 +117,11 @@ cdef class ThermoPhase(_SolutionBase): else: self._setArray1(thermo_setMoleFractions, X) - def moleFraction(self, int k): - return self.thermo.moleFraction(k) + def moleFraction(self, species): + if isinstance(species, str): + return self.thermo.moleFraction(stringify(species)) + else: + return self.thermo.moleFraction(species) property concentrations: def __get__(self): @@ -87,6 +129,200 @@ cdef class ThermoPhase(_SolutionBase): def __set__(self, C): self._setArray1(thermo_setConcentrations, C) + ######## Read-only thermodynamic properties ######## + + property P: + def __get__(self): + return self.thermo.pressure() + + property T: + def __get__(self): + return self.thermo.temperature() + + property density: + def __get__(self): + return self.thermo.density() / self._massFactor() + property density_mass: + def __get__(self): + return self.thermo.density() + property density_mole: + def __get__(self): + return self.thermo.molarDensity() + + property v: + def __get__(self): + return self._massFactor() / self.thermo.density() + property volume_mass: + def __get__(self): + return 1.0 / self.thermo.density() + property volume_mole: + def __get__(self): + return self.thermo.molarVolume() + + property u: + def __get__(self): + return self.thermo.intEnergy_mole() * self._moleFactor() + property intEnergy_mole: + def __get__(self): + return self.thermo.intEnergy_mole() + property intEnergy_mass: + def __get__(self): + return self.thermo.intEnergy_mass() + + property h: + def __get__(self): + return self.thermo.enthalpy_mole() * self._moleFactor() + property enthalpy_mole: + def __get__(self): + return self.thermo.enthalpy_mole() + property enthalpy_mass: + def __get__(self): + return self.thermo.enthalpy_mass() + + property s: + def __get__(self): + return self.thermo.entropy_mole() * self._moleFactor() + property entropy_mole: + def __get__(self): + return self.thermo.entropy_mole() + property entropy_mass: + def __get__(self): + return self.thermo.entropy_mass() + + property g: + def __get__(self): + return self.thermo.gibbs_mole() * self._moleFactor() + property gibbs_mole: + def __get__(self): + return self.thermo.gibbs_mole() + + property gibbs_mass: + def __get__(self): + return self.thermo.gibbs_mass() + + property cv: + def __get__(self): + return self.thermo.cv_mole() * self._moleFactor() + property cv_mole: + def __get__(self): + return self.thermo.cv_mole() + property cv_mass: + def __get__(self): + return self.thermo.cv_mass() + + property cp: + def __get__(self): + return self.thermo.cp_mole() * self._moleFactor() + property cp_mole: + def __get__(self): + return self.thermo.cp_mole() + property cp_mass: + def __get__(self): + return self.thermo.cp_mass() + + ######## Methods to get/set the complete thermodynamic state ######## + + property TD: + def __get__(self): + return self.T, self.density + def __set__(self, values): + self.thermo.setState_TR(values[0], values[1] * self._massFactor()) + + property TDX: + def __get__(self): + return self.T, self.density, self.X + def __set__(self, values): + self.X = values[2] + self.TD = values[:2] + + property TDY: + def __get__(self): + return self.T, self.density, self.Y + def __set__(self, values): + self.Y = values[2] + self.TD = values[:2] + + property TP: + def __get__(self): + return self.T, self.P + def __set__(self, values): + self.thermo.setState_TP(values[0], values[1]) + + property TPX: + def __get__(self): + return self.T, self.P, self.X + def __set__(self, values): + self.X = values[2] + self.TP = values[:2] + + property TPY: + def __get__(self): + return self.T, self.P, self.Y + def __set__(self, values): + self.Y = values[2] + self.TP = values[:2] + + property UV: + def __get__(self): + return self.u, self.v + def __set__(self, values): + self.thermo.setState_UV(values[0] / self._massFactor(), + values[1] / self._massFactor()) + + property UVX: + def __get__(self): + return self.u, self.v, self.X + def __set__(self, values): + self.X = values[2] + self.UV = values[:2] + + property UVY: + def __get__(self): + return self.u, self.v, self.Y + def __set__(self, values): + self.Y = values[2] + self.UV = values[:2] + + property HP: + def __get__(self): + return self.h, self.P + def __set__(self, values): + self.thermo.setState_HP(values[0] / self._massFactor(), values[1]) + + property HPX: + def __get__(self): + return self.h, self.P, self.X + def __set__(self, values): + self.X = values[2] + self.HP = values[:2] + + property HPY: + def __get__(self): + return self.h, self.P, self.Y + def __set__(self, values): + self.Y = values[2] + self.HP = values[:2] + + property SP: + def __get__(self): + return self.s, self.P + def __set__(self, values): + self.thermo.setState_SP(values[0] / self._massFactor(), values[1]) + + property SPX: + def __get__(self): + return self.s, self.P, self.X + def __set__(self, values): + self.X = values[2] + self.SP = values[:2] + + property SPY: + def __get__(self): + return self.s, self.P, self.Y + def __set__(self, values): + self.Y = values[2] + self.SP = values[:2] + cdef class InterfacePhase(ThermoPhase): cdef CxxSurfPhase* surf @@ -113,14 +349,28 @@ cdef class PureFluid(ThermoPhase): property critDensity: def __get__(self): - return self.thermo.critDensity() + return self.thermo.critDensity() / self._massFactor() - property vaporFraction: + property Psat: + def __get__(self): + return self.thermo.satPressure(self.T) + + property Tsat: + def __get__(self): + return self.thermo.satTemperature(self.P) + + property X: def __get__(self): return self.thermo.vaporFraction() - def _setState_Psat(self, double P, double x): - self.thermo.setState_Psat(P, x) + property TX: + def __get__(self): + return self.T, self.X + def __set__(self, values): + self.thermo.setState_Tsat(values[0], values[1]) - def _setState_Tsat(self, double T, double x): - self.thermo.setState_Tsat(T, x) + property PX: + def __get__(self): + return self.P, self.X + def __set__(self, values): + self.thermo.setState_Psat(values[0], values[1])