From 55fe9d9f4fdfefc808ee69611d1f3cb33cacedfd Mon Sep 17 00:00:00 2001 From: Ingmar Schoegl Date: Sun, 12 Oct 2025 21:23:50 -0500 Subject: [PATCH 01/26] [base] Update exception handling for elementIndex --- include/cantera/equil/MultiPhase.h | 11 ++++++++ include/cantera/thermo/Phase.h | 13 ++++++++++ interfaces/cython/cantera/mixture.pxd | 2 +- interfaces/cython/cantera/mixture.pyx | 16 ++++++------ interfaces/cython/cantera/thermo.pxd | 2 +- interfaces/cython/cantera/thermo.pyx | 17 ++++++------- .../src/sourcegen/headers/ctmix.yaml | 1 + .../src/sourcegen/headers/ctthermo.yaml | 1 + src/equil/MultiPhase.cpp | 17 +++++++++++-- src/kinetics/ReactionPath.cpp | 8 +++--- src/thermo/PDSS_HKFT.cpp | 5 +--- src/thermo/Phase.cpp | 25 ++++++++++++++----- src/thermo/ThermoPhase.cpp | 10 ++++---- src/thermo/WaterSSTP.cpp | 12 ++------- test/python/test_mixture.py | 2 +- test/python/test_thermo.py | 8 +++--- test/thermo/ThermoPhase_Test.cpp | 16 ++++++------ test/thermo/phaseConstructors.cpp | 6 ++--- 18 files changed, 106 insertions(+), 66 deletions(-) diff --git a/include/cantera/equil/MultiPhase.h b/include/cantera/equil/MultiPhase.h index 33d9a27e543..bf545821290 100644 --- a/include/cantera/equil/MultiPhase.h +++ b/include/cantera/equil/MultiPhase.h @@ -121,6 +121,7 @@ class MultiPhase //! Check that an array size is at least nElements(). //! Throws an exception if mm is less than nElements(). Used before calls //! which take an array pointer. + //! @deprecated To be removed after %Cantera 3.2. Only used by legacy CLib. void checkElementArraySize(size_t mm) const; //! Returns the name of the global element *m*. @@ -135,6 +136,16 @@ class MultiPhase */ size_t elementIndex(const string& name) const; + //! Returns the index of the element with name @e name. + /*! + * @param name String name of the global element + * @since Added the `force` argument in %Cantera 3.2. If not specified, the default + * behavior in %Cantera 3.2 is to return `npos` if an element is not found. + * After %Cantera 3.2, the default behavior will be to throw an exception. + * @exception Throws an IndexError. + */ + size_t elementIndex(const string& name, bool force) const; + //! Number of species, summed over all phases. size_t nSpecies() const { return m_nsp; diff --git a/include/cantera/thermo/Phase.h b/include/cantera/thermo/Phase.h index 8ab76caaf21..d484466fbba 100644 --- a/include/cantera/thermo/Phase.h +++ b/include/cantera/thermo/Phase.h @@ -151,6 +151,18 @@ class Phase //! @param name Name of the element size_t elementIndex(const string& name) const; + //! Return the index of element named 'name'. + /*! + * The index is an integer assigned to each element in the order it was added. + * Returns @ref npos if the specified element is not found. + * @param name Name of the element. + * @since Added the `force` argument in %Cantera 3.2. If not specified, the default + * behavior in %Cantera 3.2 is to return `npos` if an element is not found. + * After %Cantera 3.2, the default behavior will be to throw an exception. + * @exception Throws an IndexError. + */ + size_t elementIndex(const string& name, bool force) const; + //! Return a read-only reference to the vector of element names. const vector& elementNames() const; @@ -205,6 +217,7 @@ class Phase //! Check that an array size is at least nElements(). //! Throws an exception if mm is less than nElements(). Used before calls //! which take an array pointer. + //! @deprecated To be removed after %Cantera 3.2. Only used by legacy CLib. void checkElementArraySize(size_t mm) const; //! Number of atoms of element @c m in species @c k. diff --git a/interfaces/cython/cantera/mixture.pxd b/interfaces/cython/cantera/mixture.pxd index 152194742b4..442ff03bac7 100644 --- a/interfaces/cython/cantera/mixture.pxd +++ b/interfaces/cython/cantera/mixture.pxd @@ -19,7 +19,7 @@ cdef extern from "cantera/equil/MultiPhase.h" namespace "Cantera": size_t nSpecies() size_t nElements() size_t nPhases() - size_t elementIndex(string) except +translate_exception + size_t elementIndex(string, cbool) except +translate_exception size_t speciesIndex(size_t, size_t) except +translate_exception string speciesName(size_t) except +translate_exception double nAtoms(size_t, size_t) except +translate_exception diff --git a/interfaces/cython/cantera/mixture.pyx b/interfaces/cython/cantera/mixture.pyx index e4c00d579a9..175cb2d89ff 100644 --- a/interfaces/cython/cantera/mixture.pyx +++ b/interfaces/cython/cantera/mixture.pyx @@ -98,19 +98,19 @@ cdef class Mixture: """Index of element with name 'element'. >>> mix.element_index('H') - 2 """ if isinstance(element, (str, bytes)): - index = self.mix.elementIndex(stringify(element)) - elif isinstance(element, (int, float)): + return self.mix.elementIndex(stringify(element), True) + + if isinstance(element, (int, float)): index = element + if 0 <= index < self.n_elements: + return index else: - raise TypeError("'element' must be a string or a number") - - if not 0 <= index < self.n_elements: - raise ValueError('No such element.') + raise TypeError("'element' must be a string or a number. " + f"Got {element!r}.") - return index + raise ValueError(f"No such element {element!r}.") property n_species: """Number of species.""" diff --git a/interfaces/cython/cantera/thermo.pxd b/interfaces/cython/cantera/thermo.pxd index 80fdaa4e322..c66656e042c 100644 --- a/interfaces/cython/cantera/thermo.pxd +++ b/interfaces/cython/cantera/thermo.pxd @@ -82,7 +82,7 @@ cdef extern from "cantera/thermo/ThermoPhase.h" namespace "Cantera": # element properties size_t nElements() - size_t elementIndex(string) except +translate_exception + size_t elementIndex(string, cbool) except +translate_exception string elementName(size_t) except +translate_exception double atomicWeight(size_t) except +translate_exception diff --git a/interfaces/cython/cantera/thermo.pyx b/interfaces/cython/cantera/thermo.pyx index c1746ba1bc5..6f47c142c84 100644 --- a/interfaces/cython/cantera/thermo.pyx +++ b/interfaces/cython/cantera/thermo.pyx @@ -463,17 +463,16 @@ cdef class ThermoPhase(_SolutionBase): returned. If no such element is present, an exception is thrown. """ if isinstance(element, (str, bytes)): - index = self.thermo.elementIndex(stringify(element)) - elif isinstance(element, (int, float)): + return self.thermo.elementIndex(stringify(element), True) + if isinstance(element, (int, float)): index = element + if 0 <= index < self.n_elements: + return index else: - raise TypeError("'element' must be a string or a number." - " Got {!r}.".format(element)) + raise TypeError("'element' must be a string or a number. " + f"Got {element!r}.") - if not 0 <= index < self.n_elements: - raise ValueError('No such element {!r}.'.format(element)) - - return index + raise ValueError(f"No such element {element!r}.") def element_name(self, m): """Name of the element with index ``m``.""" @@ -533,7 +532,7 @@ cdef class ThermoPhase(_SolutionBase): " Got {!r}.".format(species)) if not 0 <= index < self.n_species: - raise ValueError('No such species {!r}.'.format(species)) + raise ValueError(f"No such species {species!r}.") return index diff --git a/interfaces/sourcegen/src/sourcegen/headers/ctmix.yaml b/interfaces/sourcegen/src/sourcegen/headers/ctmix.yaml index a818ed590db..acc9d3262f4 100644 --- a/interfaces/sourcegen/src/sourcegen/headers/ctmix.yaml +++ b/interfaces/sourcegen/src/sourcegen/headers/ctmix.yaml @@ -15,6 +15,7 @@ recipes: - name: updatePhases - name: nElements - name: elementIndex + wraps: elementIndex(const string&, bool) - name: nSpecies - name: speciesIndex wraps: speciesIndex(size_t, size_t) diff --git a/interfaces/sourcegen/src/sourcegen/headers/ctthermo.yaml b/interfaces/sourcegen/src/sourcegen/headers/ctthermo.yaml index e3dcf105e74..b429a27ccd7 100644 --- a/interfaces/sourcegen/src/sourcegen/headers/ctthermo.yaml +++ b/interfaces/sourcegen/src/sourcegen/headers/ctthermo.yaml @@ -46,6 +46,7 @@ recipes: - name: elementName # Renamed in Cantera 3.2 (previously getElementName) - name: speciesName # Renamed in Cantera 3.2 (previously getSpeciesName) - name: elementIndex + wraps: elementIndex(const string&, bool) - name: speciesIndex - name: nAtoms - name: addElement diff --git a/src/equil/MultiPhase.cpp b/src/equil/MultiPhase.cpp index bb980b5f661..a5efc3d9d12 100644 --- a/src/equil/MultiPhase.cpp +++ b/src/equil/MultiPhase.cpp @@ -139,7 +139,7 @@ void MultiPhase::init() for (size_t ip = 0; ip < nPhases(); ip++) { ThermoPhase* p = m_phase[ip]; size_t nsp = p->nSpecies(); - size_t mlocal = p->elementIndex(m_enames[m]); + size_t mlocal = p->elementIndex(m_enames[m], false); for (size_t kp = 0; kp < nsp; kp++) { if (mlocal != npos) { m_atoms(m, k) = p->nAtoms(kp, mlocal); @@ -726,6 +726,8 @@ void MultiPhase::checkElementIndex(size_t m) const void MultiPhase::checkElementArraySize(size_t mm) const { + warn_deprecated("Phase::checkElementArraySize", + "To be removed after Cantera 3.2. Only used by legacy CLib."); if (m_nel > mm) { throw ArraySizeError("MultiPhase::checkElementArraySize", mm, m_nel); } @@ -737,13 +739,24 @@ string MultiPhase::elementName(size_t m) const } size_t MultiPhase::elementIndex(const string& name) const +{ + warn_deprecated("MultiPhase::elementIndex", "'force' argument not specified; " + "Default behavior will change from returning npos to throwing an exception " + "after Cantera 3.2."); + return elementIndex(name, false); +} + +size_t MultiPhase::elementIndex(const string& name, bool force) const { for (size_t e = 0; e < m_nel; e++) { if (m_enames[e] == name) { return e; } } - return npos; + if (!force) { + return npos; + } + throw CanteraError("MultiPhase::elementIndex", "Element {} not found.", name); } void MultiPhase::checkSpeciesIndex(size_t k) const diff --git a/src/kinetics/ReactionPath.cpp b/src/kinetics/ReactionPath.cpp index 48cbfc272ed..139629ae04b 100644 --- a/src/kinetics/ReactionPath.cpp +++ b/src/kinetics/ReactionPath.cpp @@ -587,12 +587,12 @@ void ReactionPathBuilder::findElements(Kinetics& kin) // iterate over the phases for (size_t ip = 0; ip < kin.nPhases(); ip++) { ThermoPhase* p = &kin.thermo(ip); - size_t mlocal = p->elementIndex(m_elementSymbols[m]); - for (size_t kp = 0; kp < p->nSpecies(); kp++) { - if (mlocal != npos) { + size_t mlocal = p->elementIndex(m_elementSymbols[m], false); + if (mlocal != npos) { + for (size_t kp = 0; kp < p->nSpecies(); kp++) { m_atoms(k, m) = p->nAtoms(kp, mlocal); + k++; } - k++; } } } diff --git a/src/thermo/PDSS_HKFT.cpp b/src/thermo/PDSS_HKFT.cpp index 7df92c9b564..a90d40ce771 100644 --- a/src/thermo/PDSS_HKFT.cpp +++ b/src/thermo/PDSS_HKFT.cpp @@ -560,10 +560,7 @@ double PDSS_HKFT::gstar(const double temp, const double pres, const int ifunc) c double PDSS_HKFT::LookupGe(const string& elemName) { - size_t iE = m_tp->elementIndex(elemName); - if (iE == npos) { - throw CanteraError("PDSS_HKFT::LookupGe", "element " + elemName + " not found"); - } + size_t iE = m_tp->elementIndex(elemName, true); double geValue = m_tp->entropyElement298(iE); if (geValue == ENTROPY298_UNKNOWN) { throw CanteraError("PDSS_HKFT::LookupGe", diff --git a/src/thermo/Phase.cpp b/src/thermo/Phase.cpp index 78da500abe7..cc015fab7a1 100644 --- a/src/thermo/Phase.cpp +++ b/src/thermo/Phase.cpp @@ -41,6 +41,8 @@ void Phase::checkElementIndex(size_t m) const void Phase::checkElementArraySize(size_t mm) const { + warn_deprecated("Phase::checkElementArraySize", + "To be removed after Cantera 3.2. Only used by legacy CLib."); if (m_mm > mm) { throw ArraySizeError("Phase::checkElementArraySize", mm, m_mm); } @@ -52,14 +54,25 @@ string Phase::elementName(size_t m) const return m_elementNames[m]; } -size_t Phase::elementIndex(const string& elementName) const +size_t Phase::elementIndex(const string& name) const +{ + warn_deprecated("Phase::elementIndex", "'force' argument not specified; " + "Default behavior will change from returning npos to throwing an exception " + "after Cantera 3.2."); + return elementIndex(name, false); +} + +size_t Phase::elementIndex(const string& elementName, bool force) const { for (size_t i = 0; i < m_mm; i++) { if (m_elementNames[i] == elementName) { return i; } } - return npos; + if (!force) { + return npos; + } + throw CanteraError("Phase::elementIndex", "Element {} not found.", elementName); } const vector& Phase::elementNames() const @@ -743,7 +756,7 @@ bool Phase::addSpecies(shared_ptr spec) vector comp(nElements()); for (const auto& [eName, stoich] : spec->composition) { - size_t m = elementIndex(eName); + size_t m = elementIndex(eName, false); if (m == npos) { // Element doesn't exist in this phase switch (m_undefinedElementBehavior) { case UndefElement::ignore: @@ -752,7 +765,7 @@ bool Phase::addSpecies(shared_ptr spec) case UndefElement::add: addElement(eName); comp.resize(nElements()); - m = elementIndex(eName); + m = elementIndex(eName, true); break; case UndefElement::error: @@ -768,7 +781,7 @@ bool Phase::addSpecies(shared_ptr spec) size_t ne = nElements(); const vector& aw = atomicWeights(); if (spec->charge != 0.0) { - size_t eindex = elementIndex("E"); + size_t eindex = elementIndex("E", false); if (eindex != npos) { double ecomp = comp[eindex]; if (fabs(spec->charge + ecomp) > 0.001) { @@ -785,7 +798,7 @@ bool Phase::addSpecies(shared_ptr spec) } else { addElement("E", 0.000545, 0, 0.0, CT_ELEM_TYPE_ELECTRONCHARGE); ne = nElements(); - eindex = elementIndex("E"); + eindex = elementIndex("E", true); comp.resize(ne); comp[ne - 1] = - spec->charge; } diff --git a/src/thermo/ThermoPhase.cpp b/src/thermo/ThermoPhase.cpp index b24a9a1a079..6654612117b 100644 --- a/src/thermo/ThermoPhase.cpp +++ b/src/thermo/ThermoPhase.cpp @@ -680,9 +680,9 @@ void ThermoPhase::setState_SPorSV(double Starget, double p, double ThermoPhase::o2Required(const double* y) const { // indices of fuel elements - size_t iC = elementIndex("C"); - size_t iS = elementIndex("S"); - size_t iH = elementIndex("H"); + size_t iC = elementIndex("C", false); + size_t iS = elementIndex("S", false); + size_t iH = elementIndex("H", false); double o2req = 0.0; double sum = 0.0; @@ -708,7 +708,7 @@ double ThermoPhase::o2Required(const double* y) const double ThermoPhase::o2Present(const double* y) const { - size_t iO = elementIndex("O"); + size_t iO = elementIndex("O", true); double o2pres = 0.0; double sum = 0.0; for (size_t k = 0; k != m_kk; ++k) { @@ -1009,7 +1009,7 @@ double ThermoPhase::mixtureFraction(const double* fuelComp, const double* oxComp return Z_m; }; - size_t m = elementIndex(element); + size_t m = elementIndex(element, true); double Z_m_fuel = elementalFraction(m, fuelComp)/sum_yf; double Z_m_ox = elementalFraction(m, oxComp)/sum_yo; double Z_m_mix = elementalFraction(m, massFractions()); diff --git a/src/thermo/WaterSSTP.cpp b/src/thermo/WaterSSTP.cpp index cf8f09c50c4..8148fdc1d9f 100644 --- a/src/thermo/WaterSSTP.cpp +++ b/src/thermo/WaterSSTP.cpp @@ -34,17 +34,9 @@ void WaterSSTP::initThermo() // codes exhibiting mass loss issues. We need to grab the elemental atomic // weights used in the Element class and calculate a consistent H2O // molecular weight based on that. - size_t nH = elementIndex("H"); - if (nH == npos) { - throw CanteraError("WaterSSTP::initThermo", - "H not an element"); - } + size_t nH = elementIndex("H", true); double mw_H = atomicWeight(nH); - size_t nO = elementIndex("O"); - if (nO == npos) { - throw CanteraError("WaterSSTP::initThermo", - "O not an element"); - } + size_t nO = elementIndex("O", true); double mw_O = atomicWeight(nO); m_mw = 2.0 * mw_H + mw_O; setMolecularWeight(0,m_mw); diff --git a/test/python/test_mixture.py b/test/python/test_mixture.py index 8de4ddb913f..ba0010168eb 100644 --- a/test/python/test_mixture.py +++ b/test/python/test_mixture.py @@ -28,7 +28,7 @@ def test_sizes(self, mix, phase1, phase2): assert len(E) == mix.n_elements def test_element_index(self, mix): - with pytest.raises(ValueError, match='No such element'): + with pytest.raises(ct.CanteraError, match="Element W not found."): mix.element_index('W') with pytest.raises(ValueError, match='No such element'): diff --git a/test/python/test_thermo.py b/test/python/test_thermo.py index ade15f61c58..76765ae7cbc 100644 --- a/test/python/test_thermo.py +++ b/test/python/test_thermo.py @@ -102,9 +102,9 @@ def test_n_atoms(self): kSpec = self.phase.species_index(species) assert self.phase.n_atoms(kSpec, mElem) == n - with pytest.raises(ValueError, match='No such species'): + with pytest.raises(ValueError, match="No such species 'C'"): self.phase.n_atoms('C', 'H2') - with pytest.raises(ValueError, match='No such element'): + with pytest.raises(ct.CanteraError, match='Element CH4 not found.'): self.phase.n_atoms('H', 'CH4') def test_elemental_mass_fraction(self): @@ -119,7 +119,7 @@ def test_elemental_mass_fraction(self): assert Zh == approx(0.5 * (2.016 / 18.015)) assert Zar == 0.0 - with pytest.raises(ValueError, match='No such element'): + with pytest.raises(ct.CanteraError, match="Element C not found."): self.phase.elemental_mass_fraction('C') with pytest.raises(ValueError, match='No such element'): self.phase.elemental_mass_fraction(5) @@ -136,7 +136,7 @@ def test_elemental_mole_fraction(self): assert Zh == approx((2 * 0.5) / (0.5 * 3 + 0.5 * 2)) assert Zar == 0.0 - with pytest.raises(ValueError, match='No such element'): + with pytest.raises(ct.CanteraError, match="Element C not found."): self.phase.elemental_mole_fraction('C') with pytest.raises(ValueError, match='No such element'): self.phase.elemental_mole_fraction(5) diff --git a/test/thermo/ThermoPhase_Test.cpp b/test/thermo/ThermoPhase_Test.cpp index ad846f56abd..21cea403f53 100644 --- a/test/thermo/ThermoPhase_Test.cpp +++ b/test/thermo/ThermoPhase_Test.cpp @@ -162,15 +162,15 @@ class EquilRatio_MixFrac_Test : public testing::Test } else { gas.setState_TPX(300.0, 1e5, m_fuel); } - double Y_Cf = gas.elementalMassFraction(gas.elementIndex("C")); - double Y_Of = gas.elementalMassFraction(gas.elementIndex("O")); + double Y_Cf = gas.elementalMassFraction(gas.elementIndex("C", true)); + double Y_Of = gas.elementalMassFraction(gas.elementIndex("O", true)); if (basis == ThermoBasis::mass) { gas.setState_TPY(300.0, 1e5, m_ox); } else { gas.setState_TPX(300.0, 1e5, m_ox); } - double Y_Co = gas.elementalMassFraction(gas.elementIndex("C")); - double Y_Oo = gas.elementalMassFraction(gas.elementIndex("O")); + double Y_Co = gas.elementalMassFraction(gas.elementIndex("C", true)); + double Y_Oo = gas.elementalMassFraction(gas.elementIndex("O", true)); gas.setEquivalenceRatio(1.3, m_fuel, m_ox, basis); double T = gas.temperature(); @@ -179,8 +179,8 @@ class EquilRatio_MixFrac_Test : public testing::Test // mixture fraction are independent of reaction progress gas.equilibrate("HP"); test_mixture_results(T, basis, 1.3, 1.1726068608195617, 0.13415725911057605, - (gas.elementalMassFraction(gas.elementIndex("C"))-Y_Co)/(Y_Cf-Y_Co), - (gas.elementalMassFraction(gas.elementIndex("O"))-Y_Oo)/(Y_Of-Y_Oo), + (gas.elementalMassFraction(gas.elementIndex("C", true))-Y_Co)/(Y_Cf-Y_Co), + (gas.elementalMassFraction(gas.elementIndex("O", true))-Y_Oo)/(Y_Of-Y_Oo), 8.3901204498353561, m_fuel, m_ox); gas.setState_TP(300.0,1e5); @@ -188,8 +188,8 @@ class EquilRatio_MixFrac_Test : public testing::Test T = gas.temperature(); gas.equilibrate("HP"); test_mixture_results(T, basis, 1.3, 1.1726068608195617, 0.13415725911057605, - (gas.elementalMassFraction(gas.elementIndex("C"))-Y_Co)/(Y_Cf-Y_Co), - (gas.elementalMassFraction(gas.elementIndex("O"))-Y_Oo)/(Y_Of-Y_Oo), + (gas.elementalMassFraction(gas.elementIndex("C", true))-Y_Co)/(Y_Cf-Y_Co), + (gas.elementalMassFraction(gas.elementIndex("O", true))-Y_Oo)/(Y_Of-Y_Oo), 8.3901204498353561, m_fuel, m_ox); } diff --git a/test/thermo/phaseConstructors.cpp b/test/thermo/phaseConstructors.cpp index 6fe5451a467..93ae78e0497 100644 --- a/test/thermo/phaseConstructors.cpp +++ b/test/thermo/phaseConstructors.cpp @@ -98,7 +98,7 @@ TEST_F(ConstructFromScratch, AddElements) p.addElement("O"); ASSERT_EQ((size_t) 2, p.nElements()); ASSERT_EQ("H", p.elementName(0)); - ASSERT_EQ((size_t) 1, p.elementIndex("O")); + ASSERT_EQ((size_t) 1, p.elementIndex("O", true)); } TEST_F(ConstructFromScratch, throwUndefindElements) @@ -156,8 +156,8 @@ TEST_F(ConstructFromScratch, addUndefinedElements) p.addSpecies(sCO2); ASSERT_EQ((size_t) 4, p.nSpecies()); ASSERT_EQ((size_t) 3, p.nElements()); - ASSERT_EQ((size_t) 1, p.nAtoms(p.speciesIndex("CO2"), p.elementIndex("C"))); - ASSERT_EQ((size_t) 2, p.nAtoms(p.speciesIndex("co2"), p.elementIndex("O"))); + ASSERT_EQ((size_t) 1, p.nAtoms(p.speciesIndex("CO2"), p.elementIndex("C", true))); + ASSERT_EQ((size_t) 2, p.nAtoms(p.speciesIndex("co2"), p.elementIndex("O", true))); p.setMassFractionsByName("H2:0.5, CO2:0.5"); ASSERT_DOUBLE_EQ(0.5, p.massFraction("CO2")); } From 4bdc6e7de4c86c345091a2c1a89385814e719a00 Mon Sep 17 00:00:00 2001 From: Ingmar Schoegl Date: Mon, 13 Oct 2025 07:11:18 -0500 Subject: [PATCH 02/26] [base] Make element index checking more consistent --- include/cantera/equil/MultiPhase.h | 18 ++++++++++++------ include/cantera/thermo/Phase.h | 14 ++++++++++---- interfaces/cython/cantera/mixture.pxd | 1 + interfaces/cython/cantera/mixture.pyx | 4 +--- interfaces/cython/cantera/thermo.pxd | 1 + interfaces/cython/cantera/thermo.pyx | 4 +--- src/equil/MultiPhase.cpp | 13 +++++++------ src/thermo/Phase.cpp | 24 ++++++++++-------------- test/python/test_mixture.py | 2 +- test/python/test_thermo.py | 4 ++-- 10 files changed, 46 insertions(+), 39 deletions(-) diff --git a/include/cantera/equil/MultiPhase.h b/include/cantera/equil/MultiPhase.h index bf545821290..1597c177140 100644 --- a/include/cantera/equil/MultiPhase.h +++ b/include/cantera/equil/MultiPhase.h @@ -115,8 +115,11 @@ class MultiPhase } //! Check that the specified element index is in range. - //! Throws an exception if m is greater than nElements()-1 - void checkElementIndex(size_t m) const; + /*! + * @since After %Cantera 3.2, returns verified element index. + * @exception Throws an exception if m is greater than nElements()-1 + */ + size_t checkElementIndex(size_t m) const; //! Check that an array size is at least nElements(). //! Throws an exception if mm is less than nElements(). Used before calls @@ -133,18 +136,21 @@ class MultiPhase //! Returns the index of the element with name @e name. /*! * @param name String name of the global element + * @deprecated To be removed after %Cantera 3.2. Use 2-parameter version instead. */ size_t elementIndex(const string& name) const; //! Returns the index of the element with name @e name. /*! - * @param name String name of the global element - * @since Added the `force` argument in %Cantera 3.2. If not specified, the default - * behavior in %Cantera 3.2 is to return `npos` if an element is not found. + * @param name String name of the global element. + * @param raise If `true`, raise exception if the specified element is not found; + * otherwise, return @ref npos. + * @since Added the `raise` argument in %Cantera 3.2. If not specified, the default + * behavior if an element is not found in %Cantera 3.2 is to return `npos`. * After %Cantera 3.2, the default behavior will be to throw an exception. * @exception Throws an IndexError. */ - size_t elementIndex(const string& name, bool force) const; + size_t elementIndex(const string& name, bool raise) const; //! Number of species, summed over all phases. size_t nSpecies() const { diff --git a/include/cantera/thermo/Phase.h b/include/cantera/thermo/Phase.h index d484466fbba..c75d8c9dbbb 100644 --- a/include/cantera/thermo/Phase.h +++ b/include/cantera/thermo/Phase.h @@ -149,6 +149,7 @@ class Phase //! assigned to each element in the order it was added. Returns @ref npos //! if the specified element is not found. //! @param name Name of the element + //! @deprecated To be removed after %Cantera 3.2. Use 2-parameter version instead. size_t elementIndex(const string& name) const; //! Return the index of element named 'name'. @@ -156,8 +157,10 @@ class Phase * The index is an integer assigned to each element in the order it was added. * Returns @ref npos if the specified element is not found. * @param name Name of the element. - * @since Added the `force` argument in %Cantera 3.2. If not specified, the default - * behavior in %Cantera 3.2 is to return `npos` if an element is not found. + * @param raise If `true`, raise exception if the specified element is not found; + * otherwise, return @ref npos. + * @since Added the `raise` argument in %Cantera 3.2. If not specified, the default + * behavior if an element is not found in %Cantera 3.2 is to return `npos`. * After %Cantera 3.2, the default behavior will be to throw an exception. * @exception Throws an IndexError. */ @@ -211,8 +214,11 @@ class Phase size_t nElements() const; //! Check that the specified element index is in range. - //! Throws an exception if m is greater than nElements()-1 - void checkElementIndex(size_t m) const; + /*! + * @since After %Cantera 3.2, returns verified element index. + * @exception Throws an exception if m is greater than nElements()-1 + */ + size_t checkElementIndex(size_t m) const; //! Check that an array size is at least nElements(). //! Throws an exception if mm is less than nElements(). Used before calls diff --git a/interfaces/cython/cantera/mixture.pxd b/interfaces/cython/cantera/mixture.pxd index 442ff03bac7..7e2d9c16700 100644 --- a/interfaces/cython/cantera/mixture.pxd +++ b/interfaces/cython/cantera/mixture.pxd @@ -20,6 +20,7 @@ cdef extern from "cantera/equil/MultiPhase.h" namespace "Cantera": size_t nElements() size_t nPhases() size_t elementIndex(string, cbool) except +translate_exception + size_t checkElementIndex(size_t) except +translate_exception size_t speciesIndex(size_t, size_t) except +translate_exception string speciesName(size_t) except +translate_exception double nAtoms(size_t, size_t) except +translate_exception diff --git a/interfaces/cython/cantera/mixture.pyx b/interfaces/cython/cantera/mixture.pyx index 175cb2d89ff..7c716de69c4 100644 --- a/interfaces/cython/cantera/mixture.pyx +++ b/interfaces/cython/cantera/mixture.pyx @@ -103,9 +103,7 @@ cdef class Mixture: return self.mix.elementIndex(stringify(element), True) if isinstance(element, (int, float)): - index = element - if 0 <= index < self.n_elements: - return index + return self.mix.checkElementIndex(element) else: raise TypeError("'element' must be a string or a number. " f"Got {element!r}.") diff --git a/interfaces/cython/cantera/thermo.pxd b/interfaces/cython/cantera/thermo.pxd index c66656e042c..43b635d340c 100644 --- a/interfaces/cython/cantera/thermo.pxd +++ b/interfaces/cython/cantera/thermo.pxd @@ -83,6 +83,7 @@ cdef extern from "cantera/thermo/ThermoPhase.h" namespace "Cantera": # element properties size_t nElements() size_t elementIndex(string, cbool) except +translate_exception + size_t checkElementIndex(size_t) except +translate_exception string elementName(size_t) except +translate_exception double atomicWeight(size_t) except +translate_exception diff --git a/interfaces/cython/cantera/thermo.pyx b/interfaces/cython/cantera/thermo.pyx index 6f47c142c84..1fcab7aeedc 100644 --- a/interfaces/cython/cantera/thermo.pyx +++ b/interfaces/cython/cantera/thermo.pyx @@ -465,9 +465,7 @@ cdef class ThermoPhase(_SolutionBase): if isinstance(element, (str, bytes)): return self.thermo.elementIndex(stringify(element), True) if isinstance(element, (int, float)): - index = element - if 0 <= index < self.n_elements: - return index + return self.thermo.checkElementIndex(element) else: raise TypeError("'element' must be a string or a number. " f"Got {element!r}.") diff --git a/src/equil/MultiPhase.cpp b/src/equil/MultiPhase.cpp index a5efc3d9d12..19b699a11b9 100644 --- a/src/equil/MultiPhase.cpp +++ b/src/equil/MultiPhase.cpp @@ -717,11 +717,12 @@ void MultiPhase::setTemperature(const double T) updatePhases(); } -void MultiPhase::checkElementIndex(size_t m) const +size_t MultiPhase::checkElementIndex(size_t m) const { - if (m >= m_nel) { - throw IndexError("MultiPhase::checkElementIndex", "elements", m, m_nel); + if (m < m_nel) { + return m; } + throw IndexError("MultiPhase::checkElementIndex", "elements", m, m_nel); } void MultiPhase::checkElementArraySize(size_t mm) const @@ -740,20 +741,20 @@ string MultiPhase::elementName(size_t m) const size_t MultiPhase::elementIndex(const string& name) const { - warn_deprecated("MultiPhase::elementIndex", "'force' argument not specified; " + warn_deprecated("MultiPhase::elementIndex", "'raise' argument not specified; " "Default behavior will change from returning npos to throwing an exception " "after Cantera 3.2."); return elementIndex(name, false); } -size_t MultiPhase::elementIndex(const string& name, bool force) const +size_t MultiPhase::elementIndex(const string& name, bool raise) const { for (size_t e = 0; e < m_nel; e++) { if (m_enames[e] == name) { return e; } } - if (!force) { + if (!raise) { return npos; } throw CanteraError("MultiPhase::elementIndex", "Element {} not found.", name); diff --git a/src/thermo/Phase.cpp b/src/thermo/Phase.cpp index cc015fab7a1..41868d79d46 100644 --- a/src/thermo/Phase.cpp +++ b/src/thermo/Phase.cpp @@ -32,11 +32,12 @@ size_t Phase::nElements() const return m_mm; } -void Phase::checkElementIndex(size_t m) const +size_t Phase::checkElementIndex(size_t m) const { - if (m >= m_mm) { - throw IndexError("Phase::checkElementIndex", "elements", m, m_mm); + if (m < m_mm) { + return m; } + throw IndexError("Phase::checkElementIndex", "elements", m, m_mm); } void Phase::checkElementArraySize(size_t mm) const @@ -50,26 +51,25 @@ void Phase::checkElementArraySize(size_t mm) const string Phase::elementName(size_t m) const { - checkElementIndex(m); - return m_elementNames[m]; + return m_elementNames[checkElementIndex(m)]; } size_t Phase::elementIndex(const string& name) const { - warn_deprecated("Phase::elementIndex", "'force' argument not specified; " + warn_deprecated("Phase::elementIndex", "'raise' argument not specified; " "Default behavior will change from returning npos to throwing an exception " "after Cantera 3.2."); return elementIndex(name, false); } -size_t Phase::elementIndex(const string& elementName, bool force) const +size_t Phase::elementIndex(const string& elementName, bool raise) const { for (size_t i = 0; i < m_mm; i++) { if (m_elementNames[i] == elementName) { return i; } } - if (!force) { + if (!raise) { return npos; } throw CanteraError("Phase::elementIndex", "Element {} not found.", elementName); @@ -87,8 +87,7 @@ double Phase::atomicWeight(size_t m) const double Phase::entropyElement298(size_t m) const { - checkElementIndex(m); - return m_entropy298[m]; + return m_entropy298[checkElementIndex(m)]; } const vector& Phase::atomicWeights() const @@ -115,9 +114,8 @@ int Phase::changeElementType(int m, int elem_type) double Phase::nAtoms(size_t k, size_t m) const { - checkElementIndex(m); checkSpeciesIndex(k); - return m_speciesComp[m_mm * k + m]; + return m_speciesComp[m_mm * k + checkElementIndex(m)]; } size_t Phase::findSpeciesLower(const string& name) const @@ -587,7 +585,6 @@ void Phase::setMolesNoTruncate(const double* const N) double Phase::elementalMassFraction(const size_t m) const { - checkElementIndex(m); double Z_m = 0.0; for (size_t k = 0; k != m_kk; ++k) { Z_m += nAtoms(k, m) * atomicWeight(m) / molecularWeight(k) @@ -598,7 +595,6 @@ double Phase::elementalMassFraction(const size_t m) const double Phase::elementalMoleFraction(const size_t m) const { - checkElementIndex(m); double denom = 0; for (size_t k = 0; k < m_kk; k++) { double atoms = 0; diff --git a/test/python/test_mixture.py b/test/python/test_mixture.py index ba0010168eb..4c91dd9ba71 100644 --- a/test/python/test_mixture.py +++ b/test/python/test_mixture.py @@ -31,7 +31,7 @@ def test_element_index(self, mix): with pytest.raises(ct.CanteraError, match="Element W not found."): mix.element_index('W') - with pytest.raises(ValueError, match='No such element'): + with pytest.raises(ct.CanteraError, match="outside valid range"): mix.element_index(41) with pytest.raises(TypeError, match='must be a string or a number'): diff --git a/test/python/test_thermo.py b/test/python/test_thermo.py index 76765ae7cbc..38cb9b2f7ac 100644 --- a/test/python/test_thermo.py +++ b/test/python/test_thermo.py @@ -121,7 +121,7 @@ def test_elemental_mass_fraction(self): with pytest.raises(ct.CanteraError, match="Element C not found."): self.phase.elemental_mass_fraction('C') - with pytest.raises(ValueError, match='No such element'): + with pytest.raises(ct.CanteraError, match="outside valid range"): self.phase.elemental_mass_fraction(5) def test_elemental_mole_fraction(self): @@ -138,7 +138,7 @@ def test_elemental_mole_fraction(self): with pytest.raises(ct.CanteraError, match="Element C not found."): self.phase.elemental_mole_fraction('C') - with pytest.raises(ValueError, match='No such element'): + with pytest.raises(ct.CanteraError, match="outside valid range"): self.phase.elemental_mole_fraction(5) def test_elemental_mass_mole_fraction(self): From b2d649068d98cecd8ff5c2c322bd2531794f39fc Mon Sep 17 00:00:00 2001 From: Ingmar Schoegl Date: Mon, 13 Oct 2025 08:48:02 -0500 Subject: [PATCH 03/26] [base] Improve exception handling for speciesIndex --- include/cantera/thermo/Phase.h | 19 +++++- interfaces/cython/cantera/thermo.pxd | 2 +- interfaces/cython/cantera/thermo.pyx | 2 +- .../src/sourcegen/headers/ctthermo.yaml | 1 + src/base/SolutionArray.cpp | 6 +- src/equil/MultiPhase.cpp | 5 +- src/kinetics/ElectronCollisionPlasmaRate.cpp | 2 +- src/kinetics/InterfaceRate.cpp | 2 +- src/kinetics/Kinetics.cpp | 6 +- src/kinetics/Reaction.cpp | 12 ++-- src/oneD/Boundary1D.cpp | 2 +- src/oneD/Flow1D.cpp | 4 +- src/oneD/IonFlow.cpp | 4 +- src/thermo/BinarySolutionTabulatedThermo.cpp | 2 +- src/thermo/CoverageDependentSurfPhase.cpp | 6 +- src/thermo/DebyeHuckel.cpp | 12 +--- src/thermo/HMWSoln.cpp | 68 +++++-------------- src/thermo/IdealSolidSolnPhase.cpp | 2 +- src/thermo/LatticePhase.cpp | 2 +- src/thermo/LatticeSolidPhase.cpp | 2 +- src/thermo/MargulesVPSSTP.cpp | 4 +- src/thermo/MolalityVPSSTP.cpp | 2 +- src/thermo/PengRobinson.cpp | 23 ++----- src/thermo/Phase.cpp | 43 ++++++------ src/thermo/PlasmaPhase.cpp | 2 +- src/thermo/RedlichKisterVPSSTP.cpp | 11 +-- src/thermo/RedlichKwongMFTP.cpp | 23 ++----- src/thermo/StoichSubstance.cpp | 2 +- src/thermo/VPStandardStateTP.cpp | 2 +- src/transport/IonGasTransport.cpp | 8 +-- src/zeroD/FlowDevice.cpp | 8 +-- src/zeroD/Reactor.cpp | 4 +- test/python/test_thermo.py | 8 +-- test/thermo/ThermoPhase_Test.cpp | 16 ++--- test/thermo/phaseConstructors.cpp | 9 +-- .../stoichSolidKinetics.cpp | 2 +- 36 files changed, 138 insertions(+), 190 deletions(-) diff --git a/include/cantera/thermo/Phase.h b/include/cantera/thermo/Phase.h index c75d8c9dbbb..0cb66a18331 100644 --- a/include/cantera/thermo/Phase.h +++ b/include/cantera/thermo/Phase.h @@ -164,7 +164,7 @@ class Phase * After %Cantera 3.2, the default behavior will be to throw an exception. * @exception Throws an IndexError. */ - size_t elementIndex(const string& name, bool force) const; + size_t elementIndex(const string& name, bool raise) const; //! Return a read-only reference to the vector of element names. const vector& elementNames() const; @@ -238,8 +238,25 @@ class Phase //! phaseName:speciesName //! @return The index of the species. If the name is not found, //! the value @ref npos is returned. + //! @deprecated To be removed after %Cantera 3.2. Use 2-parameter version instead. size_t speciesIndex(const string& name) const; + //! Returns the index of a species named 'name' within the Phase object. + /*! + * The first species in the phase will have an index 0, and the last one + * will have an index of nSpecies() - 1. + * @param name String name of the species. It may also be in the form + * phaseName:speciesName + * @param raise If `true`, raise exception if the specified element is not found; + * otherwise, return @ref npos. + * @return The index of the species. + * @since Added the `raise` argument in %Cantera 3.2. If not specified, the default + * behavior if an element is not found in %Cantera 3.2 is to return `npos`. + * After %Cantera 3.2, the default behavior will be to throw an exception. + * @exception Throws an IndexError. + */ + size_t speciesIndex(const string& name, bool raise) const; + //! Name of the species with index k //! @param k index of the species string speciesName(size_t k) const; diff --git a/interfaces/cython/cantera/thermo.pxd b/interfaces/cython/cantera/thermo.pxd index 43b635d340c..9fb77efc306 100644 --- a/interfaces/cython/cantera/thermo.pxd +++ b/interfaces/cython/cantera/thermo.pxd @@ -91,7 +91,7 @@ cdef extern from "cantera/thermo/ThermoPhase.h" namespace "Cantera": size_t nSpecies() shared_ptr[CxxSpecies] species(string) except +translate_exception shared_ptr[CxxSpecies] species(size_t) except +translate_exception - size_t speciesIndex(string) except +translate_exception + size_t speciesIndex(string, cbool) except +translate_exception string speciesName(size_t) except +translate_exception double nAtoms(size_t, size_t) except +translate_exception void getAtoms(size_t, double*) except +translate_exception diff --git a/interfaces/cython/cantera/thermo.pyx b/interfaces/cython/cantera/thermo.pyx index 1fcab7aeedc..b8474c530c4 100644 --- a/interfaces/cython/cantera/thermo.pyx +++ b/interfaces/cython/cantera/thermo.pyx @@ -522,7 +522,7 @@ cdef class ThermoPhase(_SolutionBase): returned. If no such species is present, an exception is thrown. """ if isinstance(species, (str, bytes)): - index = self.thermo.speciesIndex(stringify(species)) + index = self.thermo.speciesIndex(stringify(species), False) elif isinstance(species, (int, float)): index = species else: diff --git a/interfaces/sourcegen/src/sourcegen/headers/ctthermo.yaml b/interfaces/sourcegen/src/sourcegen/headers/ctthermo.yaml index b429a27ccd7..317d7e1ea4c 100644 --- a/interfaces/sourcegen/src/sourcegen/headers/ctthermo.yaml +++ b/interfaces/sourcegen/src/sourcegen/headers/ctthermo.yaml @@ -48,6 +48,7 @@ recipes: - name: elementIndex wraps: elementIndex(const string&, bool) - name: speciesIndex + wraps: speciesIndex(const string&, bool) - name: nAtoms - name: addElement - name: refPressure diff --git a/src/base/SolutionArray.cpp b/src/base/SolutionArray.cpp index 5b88dd716c3..80eecbe2697 100644 --- a/src/base/SolutionArray.cpp +++ b/src/base/SolutionArray.cpp @@ -680,7 +680,7 @@ bool SolutionArray::hasComponent(const string& name, bool checkAlias) const // auxiliary data return true; } - if (m_sol->thermo()->speciesIndex(name) != npos) { + if (m_sol->thermo()->speciesIndex(name, false) != npos) { // species return true; } @@ -741,7 +741,7 @@ AnyValue SolutionArray::getComponent(const string& name) const // component is part of state information vector data(m_size); - size_t ix = m_sol->thermo()->speciesIndex(_name); + size_t ix = m_sol->thermo()->speciesIndex(_name, false); if (ix == npos) { // state other than species ix = m_sol->thermo()->nativeState()[_name]; @@ -787,7 +787,7 @@ void SolutionArray::setComponent(const string& name, const AnyValue& data) } auto& vec = data.asVector(); - size_t ix = m_sol->thermo()->speciesIndex(name); + size_t ix = m_sol->thermo()->speciesIndex(name, false); if (ix == npos) { ix = m_sol->thermo()->nativeState()[name]; } else { diff --git a/src/equil/MultiPhase.cpp b/src/equil/MultiPhase.cpp index 19b699a11b9..6ae94e0e3d5 100644 --- a/src/equil/MultiPhase.cpp +++ b/src/equil/MultiPhase.cpp @@ -227,10 +227,7 @@ size_t MultiPhase::speciesIndex(const string& speciesName, const string& phaseNa if (p == npos) { throw CanteraError("MultiPhase::speciesIndex", "phase not found: " + phaseName); } - size_t k = m_phase[p]->speciesIndex(speciesName); - if (k == npos) { - throw CanteraError("MultiPhase::speciesIndex", "species not found: " + speciesName); - } + size_t k = m_phase[p]->speciesIndex(speciesName, true); return m_spstart[p] + k; } diff --git a/src/kinetics/ElectronCollisionPlasmaRate.cpp b/src/kinetics/ElectronCollisionPlasmaRate.cpp index 696fa4f5d09..9bd761a2765 100644 --- a/src/kinetics/ElectronCollisionPlasmaRate.cpp +++ b/src/kinetics/ElectronCollisionPlasmaRate.cpp @@ -204,7 +204,7 @@ void ElectronCollisionPlasmaRate::setContext(const Reaction& rxn, const Kinetics if (p == electronName) { continue; } - double q = thermo.charge(thermo.speciesIndex(p)); + double q = thermo.charge(thermo.speciesIndex(p, true)); if (q > 0) { m_kind = "ionization"; } else if (q < 0) { diff --git a/src/kinetics/InterfaceRate.cpp b/src/kinetics/InterfaceRate.cpp index 9abaf0df2d8..cf03a0eb723 100644 --- a/src/kinetics/InterfaceRate.cpp +++ b/src/kinetics/InterfaceRate.cpp @@ -382,7 +382,7 @@ void StickingCoverage::setContext(const Reaction& rxn, const Kinetics& kin) for (const auto& [name, stoich] : rxn.reactants) { size_t iPhase = kin.speciesPhaseIndex(kin.kineticsSpeciesIndex(name)); const ThermoPhase& p = kin.thermo(iPhase); - size_t k = p.speciesIndex(name); + size_t k = p.speciesIndex(name, true); if (name == sticking_species) { multiplier *= sqrt(GasConstant / (2 * Pi * p.molecularWeight(k))); } else { diff --git a/src/kinetics/Kinetics.cpp b/src/kinetics/Kinetics.cpp index ba587bcab6e..e2340c5e074 100644 --- a/src/kinetics/Kinetics.cpp +++ b/src/kinetics/Kinetics.cpp @@ -314,7 +314,7 @@ size_t Kinetics::kineticsSpeciesIndex(const string& nm) const { for (size_t n = 0; n < m_thermo.size(); n++) { // Check the ThermoPhase object for a match - size_t k = thermo(n).speciesIndex(nm); + size_t k = thermo(n).speciesIndex(nm, false); if (k != npos) { return k + m_start[n]; } @@ -325,7 +325,7 @@ size_t Kinetics::kineticsSpeciesIndex(const string& nm) const ThermoPhase& Kinetics::speciesPhase(const string& nm) { for (size_t n = 0; n < m_thermo.size(); n++) { - size_t k = thermo(n).speciesIndex(nm); + size_t k = thermo(n).speciesIndex(nm, false); if (k != npos) { return thermo(n); } @@ -336,7 +336,7 @@ ThermoPhase& Kinetics::speciesPhase(const string& nm) const ThermoPhase& Kinetics::speciesPhase(const string& nm) const { for (const auto& thermo : m_thermo) { - if (thermo->speciesIndex(nm) != npos) { + if (thermo->speciesIndex(nm, false) != npos) { return *thermo; } } diff --git a/src/kinetics/Reaction.cpp b/src/kinetics/Reaction.cpp index cbc3d68e720..7c45d684207 100644 --- a/src/kinetics/Reaction.cpp +++ b/src/kinetics/Reaction.cpp @@ -633,7 +633,7 @@ void Reaction::checkBalance(const Kinetics& kin) const // iterate over products and reactants for (const auto& [name, stoich] : products) { const ThermoPhase& ph = kin.speciesPhase(name); - size_t k = ph.speciesIndex(name); + size_t k = ph.speciesIndex(name, true); for (size_t m = 0; m < ph.nElements(); m++) { balr[ph.elementName(m)] = 0.0; // so that balr contains all species balp[ph.elementName(m)] += stoich * ph.nAtoms(k, m); @@ -641,7 +641,7 @@ void Reaction::checkBalance(const Kinetics& kin) const } for (const auto& [name, stoich] : reactants) { const ThermoPhase& ph = kin.speciesPhase(name); - size_t k = ph.speciesIndex(name); + size_t k = ph.speciesIndex(name, true); for (size_t m = 0; m < ph.nElements(); m++) { balr[ph.elementName(m)] += stoich * ph.nAtoms(k, m); } @@ -674,13 +674,13 @@ void Reaction::checkBalance(const Kinetics& kin) const double prod_sites = 0.0; auto& surf = dynamic_cast(kin.thermo(0)); for (const auto& [name, stoich] : reactants) { - size_t k = surf.speciesIndex(name); + size_t k = surf.speciesIndex(name, false); if (k != npos) { reac_sites += stoich * surf.size(k); } } for (const auto& [name, stoich] : products) { - size_t k = surf.speciesIndex(name); + size_t k = surf.speciesIndex(name, false); if (k != npos) { prod_sites += stoich * surf.size(k); } @@ -746,7 +746,7 @@ bool Reaction::usesElectrochemistry(const Kinetics& kin) const for (const auto& [name, stoich] : products) { size_t kkin = kin.kineticsSpeciesIndex(name); size_t i = kin.speciesPhaseIndex(kkin); - size_t kphase = kin.thermo(i).speciesIndex(name); + size_t kphase = kin.thermo(i).speciesIndex(name, true); e_counter[i] += stoich * kin.thermo(i).charge(kphase); } @@ -754,7 +754,7 @@ bool Reaction::usesElectrochemistry(const Kinetics& kin) const for (const auto& [name, stoich] : reactants) { size_t kkin = kin.kineticsSpeciesIndex(name); size_t i = kin.speciesPhaseIndex(kkin); - size_t kphase = kin.thermo(i).speciesIndex(name); + size_t kphase = kin.thermo(i).speciesIndex(name, true); e_counter[i] -= stoich * kin.thermo(i).charge(kphase); } diff --git a/src/oneD/Boundary1D.cpp b/src/oneD/Boundary1D.cpp index 1d2e5fff489..b9b76433546 100644 --- a/src/oneD/Boundary1D.cpp +++ b/src/oneD/Boundary1D.cpp @@ -656,7 +656,7 @@ string ReactingSurf1D::componentName(size_t n) const size_t ReactingSurf1D::componentIndex(const string& name, bool checkAlias) const { - return m_sphase->speciesIndex(name); + return m_sphase->speciesIndex(name, true); } void ReactingSurf1D::init() diff --git a/src/oneD/Flow1D.cpp b/src/oneD/Flow1D.cpp index f42897389f4..0719307f055 100644 --- a/src/oneD/Flow1D.cpp +++ b/src/oneD/Flow1D.cpp @@ -103,8 +103,8 @@ void Flow1D::_init(ThermoPhase* ph, size_t nsp, size_t points) // Find indices for radiating species m_kRadiating.resize(2, npos); - m_kRadiating[0] = m_thermo->speciesIndex("CO2"); - m_kRadiating[1] = m_thermo->speciesIndex("H2O"); + m_kRadiating[0] = m_thermo->speciesIndex("CO2", false); + m_kRadiating[1] = m_thermo->speciesIndex("H2O", false); } Flow1D::Flow1D(shared_ptr th, size_t nsp, size_t points) diff --git a/src/oneD/IonFlow.cpp b/src/oneD/IonFlow.cpp index 3ccb807eb96..715a4b40c0a 100644 --- a/src/oneD/IonFlow.cpp +++ b/src/oneD/IonFlow.cpp @@ -40,8 +40,8 @@ void IonFlow::_init(ThermoPhase* ph, size_t nsp, size_t points) } // Find the index of electron - if (m_thermo->speciesIndex("E") != npos ) { - m_kElectron = m_thermo->speciesIndex("E"); + if (m_thermo->speciesIndex("E", false) != npos ) { + m_kElectron = m_thermo->speciesIndex("E", true); } // no bound for electric potential diff --git a/src/thermo/BinarySolutionTabulatedThermo.cpp b/src/thermo/BinarySolutionTabulatedThermo.cpp index 31b54324752..670389d2027 100644 --- a/src/thermo/BinarySolutionTabulatedThermo.cpp +++ b/src/thermo/BinarySolutionTabulatedThermo.cpp @@ -80,7 +80,7 @@ bool BinarySolutionTabulatedThermo::addSpecies(shared_ptr spec) void BinarySolutionTabulatedThermo::initThermo() { if (m_input.hasKey("tabulated-thermo")) { - m_kk_tab = speciesIndex(m_input["tabulated-species"].asString()); + m_kk_tab = speciesIndex(m_input["tabulated-species"].asString(), true); if (nSpecies() != 2) { throw InputFileError("BinarySolutionTabulatedThermo::initThermo", m_input["species"], diff --git a/src/thermo/CoverageDependentSurfPhase.cpp b/src/thermo/CoverageDependentSurfPhase.cpp index b942c397e77..73219a4c224 100644 --- a/src/thermo/CoverageDependentSurfPhase.cpp +++ b/src/thermo/CoverageDependentSurfPhase.cpp @@ -172,8 +172,8 @@ void CoverageDependentSurfPhase::initThermo() if (item.second->input.hasKey("coverage-dependencies")) { auto& cov_map = item.second->input["coverage-dependencies"]; for (const auto& item2 : cov_map) { - size_t k = speciesIndex(item.first); - size_t j = speciesIndex(item2.first); + size_t k = speciesIndex(item.first, false); + size_t j = speciesIndex(item2.first, false); if (k == npos) { throw InputFileError("CoverageDependentSurfPhase::initThermo", item.second->input, "Unknown species '{}'.", item.first); @@ -239,7 +239,7 @@ void CoverageDependentSurfPhase::getSpeciesParameters(const string& name, AnyMap& speciesNode) const { SurfPhase::getSpeciesParameters(name, speciesNode); - size_t k = speciesIndex(name); + size_t k = speciesIndex(name, true); // Get linear and polynomial model parameters from PolynomialDependency vector for (auto& item : m_PolynomialDependency) { if (item.k == k) { diff --git a/src/thermo/DebyeHuckel.cpp b/src/thermo/DebyeHuckel.cpp index c96d9fc06f8..5e50418bc09 100644 --- a/src/thermo/DebyeHuckel.cpp +++ b/src/thermo/DebyeHuckel.cpp @@ -313,14 +313,8 @@ void DebyeHuckel::setDefaultIonicRadius(double value) void DebyeHuckel::setBeta(const string& sp1, const string& sp2, double value) { - size_t k1 = speciesIndex(sp1); - if (k1 == npos) { - throw CanteraError("DebyeHuckel::setBeta", "Species '{}' not found", sp1); - } - size_t k2 = speciesIndex(sp2); - if (k2 == npos) { - throw CanteraError("DebyeHuckel::setBeta", "Species '{}' not found", sp2); - } + size_t k1 = speciesIndex(sp1, true); + size_t k2 = speciesIndex(sp2, true); m_Beta_ij(k1, k2) = value; m_Beta_ij(k2, k1) = value; } @@ -451,7 +445,7 @@ void DebyeHuckel::getParameters(AnyMap& phaseNode) const void DebyeHuckel::getSpeciesParameters(const string& name, AnyMap& speciesNode) const { MolalityVPSSTP::getSpeciesParameters(name, speciesNode); - size_t k = speciesIndex(name); + size_t k = speciesIndex(name, true); checkSpeciesIndex(k); AnyMap dhNode; if (m_Aionic[k] != m_Aionic_default) { diff --git a/src/thermo/HMWSoln.cpp b/src/thermo/HMWSoln.cpp index b00198b3c1e..4c4c2752da1 100644 --- a/src/thermo/HMWSoln.cpp +++ b/src/thermo/HMWSoln.cpp @@ -317,13 +317,8 @@ void HMWSoln::setBinarySalt(const string& sp1, const string& sp2, size_t nParams, double* beta0, double* beta1, double* beta2, double* Cphi, double alpha1, double alpha2) { - size_t k1 = speciesIndex(sp1); - size_t k2 = speciesIndex(sp2); - if (k1 == npos) { - throw CanteraError("HMWSoln::setBinarySalt", "Species '{}' not found", sp1); - } else if (k2 == npos) { - throw CanteraError("HMWSoln::setBinarySalt", "Species '{}' not found", sp2); - } + size_t k1 = speciesIndex(sp1, true); + size_t k2 = speciesIndex(sp2, true); if (charge(k1) < 0 && charge(k2) > 0) { std::swap(k1, k2); } else if (charge(k1) * charge(k2) >= 0) { @@ -351,13 +346,8 @@ void HMWSoln::setBinarySalt(const string& sp1, const string& sp2, void HMWSoln::setTheta(const string& sp1, const string& sp2, size_t nParams, double* theta) { - size_t k1 = speciesIndex(sp1); - size_t k2 = speciesIndex(sp2); - if (k1 == npos) { - throw CanteraError("HMWSoln::setTheta", "Species '{}' not found", sp1); - } else if (k2 == npos) { - throw CanteraError("HMWSoln::setTheta", "Species '{}' not found", sp2); - } + size_t k1 = speciesIndex(sp1, true); + size_t k2 = speciesIndex(sp2, true); if (charge(k1) * charge(k2) <= 0) { throw CanteraError("HMWSoln::setTheta", "Species '{}' and '{}' " "should both have the same (non-zero) charge ({}, {})", sp1, sp2, @@ -374,16 +364,9 @@ void HMWSoln::setTheta(const string& sp1, const string& sp2, void HMWSoln::setPsi(const string& sp1, const string& sp2, const string& sp3, size_t nParams, double* psi) { - size_t k1 = speciesIndex(sp1); - size_t k2 = speciesIndex(sp2); - size_t k3 = speciesIndex(sp3); - if (k1 == npos) { - throw CanteraError("HMWSoln::setPsi", "Species '{}' not found", sp1); - } else if (k2 == npos) { - throw CanteraError("HMWSoln::setPsi", "Species '{}' not found", sp2); - } else if (k3 == npos) { - throw CanteraError("HMWSoln::setPsi", "Species '{}' not found", sp3); - } + size_t k1 = speciesIndex(sp1, true); + size_t k2 = speciesIndex(sp2, true); + size_t k3 = speciesIndex(sp3, true); if (!charge(k1) || !charge(k2) || !charge(k3) || std::abs(sign(charge(k1) + sign(charge(k2)) + sign(charge(k3)))) != 1) { @@ -410,13 +393,8 @@ void HMWSoln::setPsi(const string& sp1, const string& sp2, void HMWSoln::setLambda(const string& sp1, const string& sp2, size_t nParams, double* lambda) { - size_t k1 = speciesIndex(sp1); - size_t k2 = speciesIndex(sp2); - if (k1 == npos) { - throw CanteraError("HMWSoln::setLambda", "Species '{}' not found", sp1); - } else if (k2 == npos) { - throw CanteraError("HMWSoln::setLambda", "Species '{}' not found", sp2); - } + size_t k1 = speciesIndex(sp1, true); + size_t k2 = speciesIndex(sp2, true); if (charge(k1) != 0 && charge(k2) != 0) { throw CanteraError("HMWSoln::setLambda", "Expected at least one neutral" @@ -436,10 +414,7 @@ void HMWSoln::setLambda(const string& sp1, const string& sp2, void HMWSoln::setMunnn(const string& sp, size_t nParams, double* munnn) { - size_t k = speciesIndex(sp); - if (k == npos) { - throw CanteraError("HMWSoln::setMunnn", "Species '{}' not found", sp); - } + size_t k = speciesIndex(sp, true); if (charge(k) != 0) { throw CanteraError("HMWSoln::setMunnn", "Expected a neutral species," @@ -455,16 +430,9 @@ void HMWSoln::setMunnn(const string& sp, size_t nParams, double* munnn) void HMWSoln::setZeta(const string& sp1, const string& sp2, const string& sp3, size_t nParams, double* psi) { - size_t k1 = speciesIndex(sp1); - size_t k2 = speciesIndex(sp2); - size_t k3 = speciesIndex(sp3); - if (k1 == npos) { - throw CanteraError("HMWSoln::setZeta", "Species '{}' not found", sp1); - } else if (k2 == npos) { - throw CanteraError("HMWSoln::setZeta", "Species '{}' not found", sp2); - } else if (k3 == npos) { - throw CanteraError("HMWSoln::setZeta", "Species '{}' not found", sp3); - } + size_t k1 = speciesIndex(sp1, true); + size_t k2 = speciesIndex(sp2, true); + size_t k3 = speciesIndex(sp3, true); if (charge(k1)*charge(k2)*charge(k3) != 0 || sign(charge(k1)) + sign(charge(k2)) + sign(charge(k3)) != 0) { @@ -573,9 +541,9 @@ void HMWSoln::initThermo() for (auto& item : actData["interactions"].asVector()) { auto& species = item["species"].asVector(1, 3); size_t nsp = species.size(); - double q0 = charge(speciesIndex(species[0])); - double q1 = (nsp > 1) ? charge(speciesIndex(species[1])) : 0; - double q2 = (nsp == 3) ? charge(speciesIndex(species[2])) : 0; + double q0 = charge(speciesIndex(species[0], true)); + double q1 = (nsp > 1) ? charge(speciesIndex(species[1], true)) : 0; + double q2 = (nsp == 3) ? charge(speciesIndex(species[2], true)) : 0; if (nsp == 2 && q0 * q1 < 0) { // Two species with opposite charges - binary salt vector beta0 = getSizedVector(item, "beta0", nCoeffs); @@ -658,8 +626,8 @@ void HMWSoln::initThermo() kMaxC = k; } } - size_t kHp = speciesIndex("H+"); - size_t kOHm = speciesIndex("OH-"); + size_t kHp = speciesIndex("H+", false); + size_t kOHm = speciesIndex("OH-", false); if (fabs(sum) > 1.0E-30) { if (kHp != npos) { diff --git a/src/thermo/IdealSolidSolnPhase.cpp b/src/thermo/IdealSolidSolnPhase.cpp index d4240f32c76..09dd87f9bd5 100644 --- a/src/thermo/IdealSolidSolnPhase.cpp +++ b/src/thermo/IdealSolidSolnPhase.cpp @@ -347,7 +347,7 @@ void IdealSolidSolnPhase::getSpeciesParameters(const string &name, AnyMap& speciesNode) const { ThermoPhase::getSpeciesParameters(name, speciesNode); - size_t k = speciesIndex(name); + size_t k = speciesIndex(name, true); const auto S = species(k); auto& eosNode = speciesNode["equation-of-state"].getMapWhere( "model", "constant-volume", true); diff --git a/src/thermo/LatticePhase.cpp b/src/thermo/LatticePhase.cpp index 36cf20488a7..8ff1fb1dd66 100644 --- a/src/thermo/LatticePhase.cpp +++ b/src/thermo/LatticePhase.cpp @@ -298,7 +298,7 @@ void LatticePhase::getParameters(AnyMap& phaseNode) const void LatticePhase::getSpeciesParameters(const string& name, AnyMap& speciesNode) const { ThermoPhase::getSpeciesParameters(name, speciesNode); - size_t k = speciesIndex(name); + size_t k = speciesIndex(name, true); // Output volume information in a form consistent with the input const auto S = species(k); if (S->input.hasKey("equation-of-state")) { diff --git a/src/thermo/LatticeSolidPhase.cpp b/src/thermo/LatticeSolidPhase.cpp index 24b57c65865..e5beea42593 100644 --- a/src/thermo/LatticeSolidPhase.cpp +++ b/src/thermo/LatticeSolidPhase.cpp @@ -340,7 +340,7 @@ void LatticeSolidPhase::getSpeciesParameters(const string& name, // Use child lattice phases to determine species parameters so that these // are set consistently for (const auto& phase : m_lattice) { - if (phase->speciesIndex(name) != npos) { + if (phase->speciesIndex(name, false) != npos) { phase->getSpeciesParameters(name, speciesNode); break; } diff --git a/src/thermo/MargulesVPSSTP.cpp b/src/thermo/MargulesVPSSTP.cpp index c9bfa737283..576a9e18763 100644 --- a/src/thermo/MargulesVPSSTP.cpp +++ b/src/thermo/MargulesVPSSTP.cpp @@ -204,8 +204,8 @@ void MargulesVPSSTP::addBinaryInteraction(const string& speciesA, const string& speciesB, double h0, double h1, double s0, double s1, double vh0, double vh1, double vs0, double vs1) { - size_t kA = speciesIndex(speciesA); - size_t kB = speciesIndex(speciesB); + size_t kA = speciesIndex(speciesA, false); + size_t kB = speciesIndex(speciesB, false); // The interaction is silently ignored if either species is not defined in // the current phase. if (kA == npos || kB == npos) { diff --git a/src/thermo/MolalityVPSSTP.cpp b/src/thermo/MolalityVPSSTP.cpp index b232ecb8299..3acac131ac8 100644 --- a/src/thermo/MolalityVPSSTP.cpp +++ b/src/thermo/MolalityVPSSTP.cpp @@ -383,7 +383,7 @@ string MolalityVPSSTP::report(bool show_thermo, double threshold) const getMolalityActivityCoefficients(&acMolal[0]); getActivities(&actMolal[0]); - size_t iHp = speciesIndex("H+"); + size_t iHp = speciesIndex("H+", false); if (iHp != npos) { double pH = -log(actMolal[iHp]) / log(10.0); fmt_append(b, diff --git a/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp index 82d0db4be66..a645fa69eda 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -28,11 +28,7 @@ PengRobinson::PengRobinson(const string& infile, const string& id_) void PengRobinson::setSpeciesCoeffs(const string& species, double a, double b, double w) { - size_t k = speciesIndex(species); - if (k == npos) { - throw CanteraError("PengRobinson::setSpeciesCoeffs", - "Unknown species '{}'.", species); - } + size_t k = speciesIndex(species, true); // Calculate value of kappa (independent of temperature) // w is an acentric factor of species @@ -74,16 +70,8 @@ void PengRobinson::setSpeciesCoeffs(const string& species, double a, double b, d void PengRobinson::setBinaryCoeffs(const string& species_i, const string& species_j, double a0) { - size_t ki = speciesIndex(species_i); - if (ki == npos) { - throw CanteraError("PengRobinson::setBinaryCoeffs", - "Unknown species '{}'.", species_i); - } - size_t kj = speciesIndex(species_j); - if (kj == npos) { - throw CanteraError("PengRobinson::setBinaryCoeffs", - "Unknown species '{}'.", species_j); - } + size_t ki = speciesIndex(species_i, true); + size_t kj = speciesIndex(species_j, true); m_a_coeffs(ki, kj) = m_a_coeffs(kj, ki) = a0; m_binaryParameters[species_i][species_j] = a0; @@ -343,7 +331,7 @@ void PengRobinson::initThermo() for (auto& [name, species] : m_species) { auto& data = species->input; - size_t k = speciesIndex(name); + size_t k = speciesIndex(name, true); if (m_a_coeffs(k, k) != 0.0) { continue; } @@ -423,8 +411,7 @@ void PengRobinson::initThermo() void PengRobinson::getSpeciesParameters(const string& name, AnyMap& speciesNode) const { MixtureFugacityTP::getSpeciesParameters(name, speciesNode); - size_t k = speciesIndex(name); - checkSpeciesIndex(k); + size_t k = speciesIndex(name, true); // Pure species parameters if (m_coeffSource[k] == CoeffSource::EoS) { diff --git a/src/thermo/Phase.cpp b/src/thermo/Phase.cpp index 41868d79d46..8363ae6b599 100644 --- a/src/thermo/Phase.cpp +++ b/src/thermo/Phase.cpp @@ -137,16 +137,28 @@ size_t Phase::findSpeciesLower(const string& name) const return loc; } -size_t Phase::speciesIndex(const string& nameStr) const +size_t Phase::speciesIndex(const string& name) const { - size_t loc = npos; + warn_deprecated("Phase::speciesIndex", "'raise' argument not specified; " + "Default behavior will change from returning npos to throwing an exception " + "after Cantera 3.2."); + return speciesIndex(name, false); +} - auto it = m_speciesIndices.find(nameStr); +size_t Phase::speciesIndex(const string& name, bool raise) const +{ + size_t loc = npos; + auto it = m_speciesIndices.find(name); if (it != m_speciesIndices.end()) { + checkSpeciesIndex(it->second); return it->second; } else if (!m_caseSensitiveSpecies) { - loc = findSpeciesLower(nameStr); + loc = findSpeciesLower(name); + } + if (loc==npos && raise) { + throw CanteraError("Phase::speciesIndex", "Species {} not found.", name); } + return loc; } @@ -483,7 +495,7 @@ double Phase::moleFraction(size_t k) const double Phase::moleFraction(const string& nameSpec) const { - size_t iloc = speciesIndex(nameSpec); + size_t iloc = speciesIndex(nameSpec, false); if (iloc != npos) { return moleFraction(iloc); } else { @@ -499,7 +511,7 @@ double Phase::massFraction(size_t k) const double Phase::massFraction(const string& nameSpec) const { - size_t iloc = speciesIndex(nameSpec); + size_t iloc = speciesIndex(nameSpec, false); if (iloc != npos) { return massFractions()[iloc]; } else { @@ -867,11 +879,11 @@ void Phase::modifySpecies(size_t k, shared_ptr spec) void Phase::addSpeciesAlias(const string& name, const string& alias) { - if (speciesIndex(alias) != npos) { + if (speciesIndex(alias, false) != npos) { throw CanteraError("Phase::addSpeciesAlias", "Invalid alias '{}': species already exists", alias); } - size_t k = speciesIndex(name); + size_t k = speciesIndex(name, false); if (k != npos) { m_speciesIndices[alias] = k; } else { @@ -910,13 +922,8 @@ vector Phase::findIsomers(const string& comp) const shared_ptr Phase::species(const string& name) const { - size_t k = speciesIndex(name); - if (k != npos) { - return m_species.at(speciesName(k)); - } else { - throw CanteraError("Phase::species", - "Unknown species '{}'", name); - } + size_t k = speciesIndex(name, true); + return m_species.at(speciesName(k)); } shared_ptr Phase::species(size_t k) const @@ -965,11 +972,7 @@ vector Phase::getCompositionFromMap(const Composition& comp) const { vector X(m_kk); for (const auto& [name, value] : comp) { - size_t loc = speciesIndex(name); - if (loc == npos) { - throw CanteraError("Phase::getCompositionFromMap", - "Unknown species '{}'", name); - } + size_t loc = speciesIndex(name, true); X[loc] = value; } return X; diff --git a/src/thermo/PlasmaPhase.cpp b/src/thermo/PlasmaPhase.cpp index 9b67d1df8c2..891782ec7cf 100644 --- a/src/thermo/PlasmaPhase.cpp +++ b/src/thermo/PlasmaPhase.cpp @@ -419,7 +419,7 @@ void PlasmaPhase::addCollision(shared_ptr collision) for (const auto& [name, _] : collision->reactants) { // Reactants are expected to be electrons and the target species if (name != electronSpeciesName()) { - m_targetSpeciesIndices.emplace_back(speciesIndex(name)); + m_targetSpeciesIndices.emplace_back(speciesIndex(name, true)); target = name; break; } diff --git a/src/thermo/RedlichKisterVPSSTP.cpp b/src/thermo/RedlichKisterVPSSTP.cpp index ecc1e2d0a2b..353bd4beb3b 100644 --- a/src/thermo/RedlichKisterVPSSTP.cpp +++ b/src/thermo/RedlichKisterVPSSTP.cpp @@ -424,15 +424,8 @@ void RedlichKisterVPSSTP::addBinaryInteraction( const double* excess_enthalpy, size_t n_enthalpy, const double* excess_entropy, size_t n_entropy) { - size_t kA = speciesIndex(speciesA); - size_t kB = speciesIndex(speciesB); - if (kA == npos) { - throw CanteraError("RedlichKisterVPSSTP::addBinaryInteraction", - "Species '{}' not present in phase", speciesA); - } else if (kB == npos) { - throw CanteraError("RedlichKisterVPSSTP::addBinaryInteraction", - "Species '{}' not present in phase", speciesB); - } + size_t kA = speciesIndex(speciesA, true); + size_t kB = speciesIndex(speciesB, true); if (charge(kA) != 0) { throw CanteraError("RedlichKisterVPSSTP::addBinaryInteraction", "Species '{}' should be neutral", speciesA); diff --git a/src/thermo/RedlichKwongMFTP.cpp b/src/thermo/RedlichKwongMFTP.cpp index b3e05de7c2e..d7b172a5e9c 100644 --- a/src/thermo/RedlichKwongMFTP.cpp +++ b/src/thermo/RedlichKwongMFTP.cpp @@ -29,11 +29,7 @@ RedlichKwongMFTP::RedlichKwongMFTP(const string& infile, const string& id_) void RedlichKwongMFTP::setSpeciesCoeffs(const string& species, double a0, double a1, double b) { - size_t k = speciesIndex(species); - if (k == npos) { - throw CanteraError("RedlichKwongMFTP::setSpeciesCoeffs", - "Unknown species '{}'.", species); - } + size_t k = speciesIndex(species, true); if (a1 != 0.0) { m_formTempParam = 1; // expression is temperature-dependent @@ -71,16 +67,8 @@ void RedlichKwongMFTP::setSpeciesCoeffs(const string& species, void RedlichKwongMFTP::setBinaryCoeffs(const string& species_i, const string& species_j, double a0, double a1) { - size_t ki = speciesIndex(species_i); - if (ki == npos) { - throw CanteraError("RedlichKwongMFTP::setBinaryCoeffs", - "Unknown species '{}'.", species_i); - } - size_t kj = speciesIndex(species_j); - if (kj == npos) { - throw CanteraError("RedlichKwongMFTP::setBinaryCoeffs", - "Unknown species '{}'.", species_j); - } + size_t ki = speciesIndex(species_i, true); + size_t kj = speciesIndex(species_j, true); if (a1 != 0.0) { m_formTempParam = 1; // expression is temperature-dependent @@ -377,7 +365,7 @@ void RedlichKwongMFTP::initThermo() for (auto& [name, species] : m_species) { auto& data = species->input; - size_t k = speciesIndex(name); + size_t k = speciesIndex(name, true); if (!isnan(a_coeff_vec(0, k + m_kk * k))) { continue; } @@ -472,8 +460,7 @@ void RedlichKwongMFTP::getSpeciesParameters(const string& name, AnyMap& speciesNode) const { MixtureFugacityTP::getSpeciesParameters(name, speciesNode); - size_t k = speciesIndex(name); - checkSpeciesIndex(k); + size_t k = speciesIndex(name, true); if (m_coeffSource[k] == CoeffSource::EoS) { auto& eosNode = speciesNode["equation-of-state"].getMapWhere( "model", "Redlich-Kwong", true); diff --git a/src/thermo/StoichSubstance.cpp b/src/thermo/StoichSubstance.cpp index 4b4b6307ded..cf6723cc428 100644 --- a/src/thermo/StoichSubstance.cpp +++ b/src/thermo/StoichSubstance.cpp @@ -154,7 +154,7 @@ void StoichSubstance::getSpeciesParameters(const string& name, AnyMap& speciesNode) const { SingleSpeciesTP::getSpeciesParameters(name, speciesNode); - size_t k = speciesIndex(name); + size_t k = speciesIndex(name, true); const auto S = species(k); auto& eosNode = speciesNode["equation-of-state"].getMapWhere( "model", "constant-volume", true); diff --git a/src/thermo/VPStandardStateTP.cpp b/src/thermo/VPStandardStateTP.cpp index 9480603c77b..a201e310f9e 100644 --- a/src/thermo/VPStandardStateTP.cpp +++ b/src/thermo/VPStandardStateTP.cpp @@ -156,7 +156,7 @@ void VPStandardStateTP::getSpeciesParameters(const string& name, AnyMap& speciesNode) const { AnyMap eos; - providePDSS(speciesIndex(name))->getParameters(eos); + providePDSS(speciesIndex(name, true))->getParameters(eos); speciesNode["equation-of-state"].getMapWhere( "model", eos.getString("model", ""), true) = std::move(eos); } diff --git a/src/transport/IonGasTransport.cpp b/src/transport/IonGasTransport.cpp index 0f54a42bde7..e9815ae2276 100644 --- a/src/transport/IonGasTransport.cpp +++ b/src/transport/IonGasTransport.cpp @@ -201,10 +201,10 @@ void IonGasTransport::fitDiffCoeffs(MMCollisionInt& integrals) // Stockmayer-(n,6,4) model is not suitable for collision // between O2/O2- due to resonant charge transfer. // Therefore, the experimental collision data is used instead. - if ((k == m_thermo->speciesIndex("O2-") || - j == m_thermo->speciesIndex("O2-")) && - (k == m_thermo->speciesIndex("O2") || - j == m_thermo->speciesIndex("O2"))) { + if ((k == m_thermo->speciesIndex("O2-", false) || + j == m_thermo->speciesIndex("O2-", false)) && + (k == m_thermo->speciesIndex("O2", false) || + j == m_thermo->speciesIndex("O2", false))) { om11 = poly5(t, m_om11_O2.data()) / 1e20; } double diffcoeff = 3.0/16.0 * sqrt(2.0 * Pi/m_reducedMass(k,j)) diff --git a/src/zeroD/FlowDevice.cpp b/src/zeroD/FlowDevice.cpp index 00779652062..e915a17dd10 100644 --- a/src/zeroD/FlowDevice.cpp +++ b/src/zeroD/FlowDevice.cpp @@ -35,12 +35,12 @@ FlowDevice::FlowDevice(shared_ptr r0, shared_ptr r1, size_t ki, ko; for (ki = 0; ki < m_nspin; ki++) { nm = mixin.speciesName(ki); - ko = mixout.speciesIndex(nm); + ko = mixout.speciesIndex(nm, false); m_in2out.push_back(ko); } for (ko = 0; ko < m_nspout; ko++) { nm = mixout.speciesName(ko); - ki = mixin.speciesIndex(nm); + ki = mixin.speciesIndex(nm, false); m_out2in.push_back(ki); } } @@ -68,12 +68,12 @@ bool FlowDevice::install(ReactorBase& in, ReactorBase& out) size_t ki, ko; for (ki = 0; ki < m_nspin; ki++) { nm = mixin.speciesName(ki); - ko = mixout.speciesIndex(nm); + ko = mixout.speciesIndex(nm, false); m_in2out.push_back(ko); } for (ko = 0; ko < m_nspout; ko++) { nm = mixout.speciesName(ko); - ki = mixin.speciesIndex(nm); + ki = mixin.speciesIndex(nm, false); m_out2in.push_back(ki); } return true; diff --git a/src/zeroD/Reactor.cpp b/src/zeroD/Reactor.cpp index f17906069b0..fe3bbff3411 100644 --- a/src/zeroD/Reactor.cpp +++ b/src/zeroD/Reactor.cpp @@ -464,7 +464,7 @@ void Reactor::addSensitivitySpeciesEnthalpy(size_t k) size_t Reactor::speciesIndex(const string& nm) const { // check for a gas species name - size_t k = m_thermo->speciesIndex(nm); + size_t k = m_thermo->speciesIndex(nm, false); if (k != npos) { return k; } @@ -473,7 +473,7 @@ size_t Reactor::speciesIndex(const string& nm) const size_t offset = m_nsp; for (auto& S : m_surfaces) { ThermoPhase* th = S->thermo(); - k = th->speciesIndex(nm); + k = th->speciesIndex(nm, false); if (k != npos) { return k + offset; } else { diff --git a/test/python/test_thermo.py b/test/python/test_thermo.py index 38cb9b2f7ac..d2a09f4e8c7 100644 --- a/test/python/test_thermo.py +++ b/test/python/test_thermo.py @@ -198,7 +198,7 @@ def test_setCompositionString(self): assert X[0] == approx(0.5) assert X[3] == approx(0.5) - with pytest.raises(ct.CanteraError, match='Unknown species'): + with pytest.raises(ct.CanteraError, match="Species CO2 not found."): self.phase.X = 'H2:1.0, CO2:1.5' def test_setCompositionStringBad(self): @@ -264,7 +264,7 @@ def test_setCompositionNoNormBad(self): self.phase.set_unnormalized_mass_fractions([1, 2, 3]) def test_setCompositionDict_bad1(self): - with pytest.raises(ct.CanteraError, match='Unknown species'): + with pytest.raises(ct.CanteraError, match="Species HCl not found."): self.phase.X = {'H2': 1.0, 'HCl': 3.0} def test_setCompositionDict_bad2(self): @@ -1931,9 +1931,9 @@ def test_case_sensitive_names(self): assert gas.case_sensitive_species_names with pytest.raises(ValueError): gas.species_index('h2') - with pytest.raises(ct.CanteraError, match='Unknown species'): + with pytest.raises(ct.CanteraError, match="Species h2 not found."): gas.X = 'h2:1.0, o2:1.0' - with pytest.raises(ct.CanteraError, match='Unknown species'): + with pytest.raises(ct.CanteraError, match="Species h2 not found."): gas.Y = 'h2:1.0, o2:1.0' gas_yaml = """ diff --git a/test/thermo/ThermoPhase_Test.cpp b/test/thermo/ThermoPhase_Test.cpp index 21cea403f53..ad846f56abd 100644 --- a/test/thermo/ThermoPhase_Test.cpp +++ b/test/thermo/ThermoPhase_Test.cpp @@ -162,15 +162,15 @@ class EquilRatio_MixFrac_Test : public testing::Test } else { gas.setState_TPX(300.0, 1e5, m_fuel); } - double Y_Cf = gas.elementalMassFraction(gas.elementIndex("C", true)); - double Y_Of = gas.elementalMassFraction(gas.elementIndex("O", true)); + double Y_Cf = gas.elementalMassFraction(gas.elementIndex("C")); + double Y_Of = gas.elementalMassFraction(gas.elementIndex("O")); if (basis == ThermoBasis::mass) { gas.setState_TPY(300.0, 1e5, m_ox); } else { gas.setState_TPX(300.0, 1e5, m_ox); } - double Y_Co = gas.elementalMassFraction(gas.elementIndex("C", true)); - double Y_Oo = gas.elementalMassFraction(gas.elementIndex("O", true)); + double Y_Co = gas.elementalMassFraction(gas.elementIndex("C")); + double Y_Oo = gas.elementalMassFraction(gas.elementIndex("O")); gas.setEquivalenceRatio(1.3, m_fuel, m_ox, basis); double T = gas.temperature(); @@ -179,8 +179,8 @@ class EquilRatio_MixFrac_Test : public testing::Test // mixture fraction are independent of reaction progress gas.equilibrate("HP"); test_mixture_results(T, basis, 1.3, 1.1726068608195617, 0.13415725911057605, - (gas.elementalMassFraction(gas.elementIndex("C", true))-Y_Co)/(Y_Cf-Y_Co), - (gas.elementalMassFraction(gas.elementIndex("O", true))-Y_Oo)/(Y_Of-Y_Oo), + (gas.elementalMassFraction(gas.elementIndex("C"))-Y_Co)/(Y_Cf-Y_Co), + (gas.elementalMassFraction(gas.elementIndex("O"))-Y_Oo)/(Y_Of-Y_Oo), 8.3901204498353561, m_fuel, m_ox); gas.setState_TP(300.0,1e5); @@ -188,8 +188,8 @@ class EquilRatio_MixFrac_Test : public testing::Test T = gas.temperature(); gas.equilibrate("HP"); test_mixture_results(T, basis, 1.3, 1.1726068608195617, 0.13415725911057605, - (gas.elementalMassFraction(gas.elementIndex("C", true))-Y_Co)/(Y_Cf-Y_Co), - (gas.elementalMassFraction(gas.elementIndex("O", true))-Y_Oo)/(Y_Of-Y_Oo), + (gas.elementalMassFraction(gas.elementIndex("C"))-Y_Co)/(Y_Cf-Y_Co), + (gas.elementalMassFraction(gas.elementIndex("O"))-Y_Oo)/(Y_Of-Y_Oo), 8.3901204498353561, m_fuel, m_ox); } diff --git a/test/thermo/phaseConstructors.cpp b/test/thermo/phaseConstructors.cpp index 93ae78e0497..ed4cbba7c3e 100644 --- a/test/thermo/phaseConstructors.cpp +++ b/test/thermo/phaseConstructors.cpp @@ -98,7 +98,7 @@ TEST_F(ConstructFromScratch, AddElements) p.addElement("O"); ASSERT_EQ((size_t) 2, p.nElements()); ASSERT_EQ("H", p.elementName(0)); - ASSERT_EQ((size_t) 1, p.elementIndex("O", true)); + ASSERT_EQ((size_t) 1, p.elementIndex("O")); } TEST_F(ConstructFromScratch, throwUndefindElements) @@ -137,7 +137,8 @@ TEST_F(ConstructFromScratch, ignoreUndefinedElements) p.addSpecies(sCO2); ASSERT_EQ((size_t) 2, p.nSpecies()); ASSERT_EQ((size_t) 2, p.nElements()); - ASSERT_EQ(npos, p.speciesIndex("CO2")); + ASSERT_EQ(npos, p.speciesIndex("CO2", false)); + ASSERT_THROW(p.speciesIndex("CO2", true), CanteraError); } TEST_F(ConstructFromScratch, addUndefinedElements) @@ -156,8 +157,8 @@ TEST_F(ConstructFromScratch, addUndefinedElements) p.addSpecies(sCO2); ASSERT_EQ((size_t) 4, p.nSpecies()); ASSERT_EQ((size_t) 3, p.nElements()); - ASSERT_EQ((size_t) 1, p.nAtoms(p.speciesIndex("CO2"), p.elementIndex("C", true))); - ASSERT_EQ((size_t) 2, p.nAtoms(p.speciesIndex("co2"), p.elementIndex("O", true))); + ASSERT_EQ((size_t) 1, p.nAtoms(p.speciesIndex("CO2"), p.elementIndex("C"))); + ASSERT_EQ((size_t) 2, p.nAtoms(p.speciesIndex("co2"), p.elementIndex("O"))); p.setMassFractionsByName("H2:0.5, CO2:0.5"); ASSERT_DOUBLE_EQ(0.5, p.massFraction("CO2")); } diff --git a/test_problems/stoichSolidKinetics/stoichSolidKinetics.cpp b/test_problems/stoichSolidKinetics/stoichSolidKinetics.cpp index 3044ce8748c..2c59e501397 100644 --- a/test_problems/stoichSolidKinetics/stoichSolidKinetics.cpp +++ b/test_problems/stoichSolidKinetics/stoichSolidKinetics.cpp @@ -59,7 +59,7 @@ void testProblem() fe3o4_s->setState_TP(Temp, OneAtm); soln->thermo()->setState_TP(Temp, OneAtm); - size_t igco2 = gasTP->speciesIndex("CO2"); + size_t igco2 = gasTP->speciesIndex("CO2", true); vector work(gasTP->nSpecies(), 0.0); From d58a32d3babe9677d4b3fbae01955b6c70e0c372 Mon Sep 17 00:00:00 2001 From: Ingmar Schoegl Date: Mon, 13 Oct 2025 10:11:54 -0500 Subject: [PATCH 04/26] [base] Make checkSpeciesIndex more useful --- include/cantera/equil/MultiPhase.h | 7 +++++-- include/cantera/kinetics/Kinetics.h | 7 +++++-- include/cantera/thermo/Phase.h | 7 +++++-- include/cantera/transport/Transport.h | 9 ++++++--- interfaces/cython/cantera/mixture.pyx | 6 +----- interfaces/cython/cantera/thermo.pxd | 1 + interfaces/cython/cantera/thermo.pyx | 16 +++++----------- src/equil/MultiPhase.cpp | 7 ++++--- src/kinetics/Kinetics.cpp | 7 ++++--- src/thermo/DebyeHuckel.cpp | 1 - src/thermo/Phase.cpp | 13 +++++++------ src/transport/Transport.cpp | 7 ++++--- test/python/test_mixture.py | 6 +++--- test/python/test_thermo.py | 6 +++--- 14 files changed, 53 insertions(+), 47 deletions(-) diff --git a/include/cantera/equil/MultiPhase.h b/include/cantera/equil/MultiPhase.h index 1597c177140..90a56c093ac 100644 --- a/include/cantera/equil/MultiPhase.h +++ b/include/cantera/equil/MultiPhase.h @@ -158,8 +158,11 @@ class MultiPhase } //! Check that the specified species index is in range. - //! Throws an exception if k is greater than nSpecies()-1 - void checkSpeciesIndex(size_t k) const; + /*! + * @since After %Cantera 3.2, returns verified species index. + * @exception Throws an exception if k is greater than nSpecies()-1 + */ + size_t checkSpeciesIndex(size_t k) const; //! Check that an array size is at least nSpecies(). //! Throws an exception if kk is less than nSpecies(). Used before calls diff --git a/include/cantera/kinetics/Kinetics.h b/include/cantera/kinetics/Kinetics.h index 18b945a84c5..297b8c22570 100644 --- a/include/cantera/kinetics/Kinetics.h +++ b/include/cantera/kinetics/Kinetics.h @@ -172,8 +172,11 @@ class Kinetics void checkReactionArraySize(size_t ii) const; //! Check that the specified species index is in range - //! Throws an exception if k is greater than nSpecies()-1 - void checkSpeciesIndex(size_t k) const; + /*! + * @since After %Cantera 3.2, returns verified species index. + * @exception Throws an exception if k is greater than nSpecies()-1 + */ + size_t checkSpeciesIndex(size_t k) const; //! Check that an array size is at least nSpecies() //! Throws an exception if kk is less than nSpecies(). Used before calls diff --git a/include/cantera/thermo/Phase.h b/include/cantera/thermo/Phase.h index 0cb66a18331..b16583b792b 100644 --- a/include/cantera/thermo/Phase.h +++ b/include/cantera/thermo/Phase.h @@ -270,8 +270,11 @@ class Phase } //! Check that the specified species index is in range. - //! Throws an exception if k is greater than nSpecies()-1 - void checkSpeciesIndex(size_t k) const; + /*! + * @since After %Cantera 3.2, returns verified species index. + * @exception Throws an exception if k is greater than nSpecies()-1 + */ + size_t checkSpeciesIndex(size_t k) const; //! Check that an array size is at least nSpecies(). //! Throws an exception if kk is less than nSpecies(). Used before calls diff --git a/include/cantera/transport/Transport.h b/include/cantera/transport/Transport.h index 9b84d4549c1..33e19e11cad 100644 --- a/include/cantera/transport/Transport.h +++ b/include/cantera/transport/Transport.h @@ -112,9 +112,12 @@ class Transport return *m_thermo; } - //! Check that the specified species index is in range. Throws an exception - //! if k is greater than #m_nsp. - void checkSpeciesIndex(size_t k) const; + //! Check that the specified species index is in range. + /*! + * @since After %Cantera 3.2, returns verified species index. + * @exception Throws an exception if k is greater than #m_nsp. + */ + size_t checkSpeciesIndex(size_t k) const; //! Check that an array size is at least #m_nsp. Throws an exception if //! kk is less than #m_nsp. Used before calls which take an array diff --git a/interfaces/cython/cantera/mixture.pyx b/interfaces/cython/cantera/mixture.pyx index 7c716de69c4..1206931a821 100644 --- a/interfaces/cython/cantera/mixture.pyx +++ b/interfaces/cython/cantera/mixture.pyx @@ -136,12 +136,8 @@ cdef class Mixture: """ p = self.phase_index(phase) - if isinstance(species, (str, bytes)): + if isinstance(species, (str, bytes, int, float)): k = self.phase(p).species_index(species) - elif isinstance(species, (int, float)): - k = species - if not 0 <= k < self.n_species: - raise ValueError('Species index out of range') else: raise TypeError("'species' must be a string or number") diff --git a/interfaces/cython/cantera/thermo.pxd b/interfaces/cython/cantera/thermo.pxd index 9fb77efc306..8faae017570 100644 --- a/interfaces/cython/cantera/thermo.pxd +++ b/interfaces/cython/cantera/thermo.pxd @@ -92,6 +92,7 @@ cdef extern from "cantera/thermo/ThermoPhase.h" namespace "Cantera": shared_ptr[CxxSpecies] species(string) except +translate_exception shared_ptr[CxxSpecies] species(size_t) except +translate_exception size_t speciesIndex(string, cbool) except +translate_exception + size_t checkSpeciesIndex(size_t) except +translate_exception string speciesName(size_t) except +translate_exception double nAtoms(size_t, size_t) except +translate_exception void getAtoms(size_t, double*) except +translate_exception diff --git a/interfaces/cython/cantera/thermo.pyx b/interfaces/cython/cantera/thermo.pyx index b8474c530c4..5984df85aff 100644 --- a/interfaces/cython/cantera/thermo.pyx +++ b/interfaces/cython/cantera/thermo.pyx @@ -522,17 +522,11 @@ cdef class ThermoPhase(_SolutionBase): returned. If no such species is present, an exception is thrown. """ if isinstance(species, (str, bytes)): - index = self.thermo.speciesIndex(stringify(species), False) - elif isinstance(species, (int, float)): - index = species - else: - raise TypeError("'species' must be a string or a number." - " Got {!r}.".format(species)) - - if not 0 <= index < self.n_species: - raise ValueError(f"No such species {species!r}.") - - return index + return self.thermo.speciesIndex(stringify(species), True) + if isinstance(species, (int, float)): + return self.thermo.checkSpeciesIndex(species) + raise TypeError("'species' must be a string or a number." + f" Got {species!r}.") property case_sensitive_species_names: """Enforce case-sensitivity for look up of species names""" diff --git a/src/equil/MultiPhase.cpp b/src/equil/MultiPhase.cpp index 6ae94e0e3d5..561110b6efe 100644 --- a/src/equil/MultiPhase.cpp +++ b/src/equil/MultiPhase.cpp @@ -757,11 +757,12 @@ size_t MultiPhase::elementIndex(const string& name, bool raise) const throw CanteraError("MultiPhase::elementIndex", "Element {} not found.", name); } -void MultiPhase::checkSpeciesIndex(size_t k) const +size_t MultiPhase::checkSpeciesIndex(size_t k) const { - if (k >= m_nsp) { - throw IndexError("MultiPhase::checkSpeciesIndex", "species", k, m_nsp); + if (k < m_nsp) { + return k; } + throw IndexError("MultiPhase::checkSpeciesIndex", "species", k, m_nsp); } void MultiPhase::checkSpeciesArraySize(size_t kk) const diff --git a/src/kinetics/Kinetics.cpp b/src/kinetics/Kinetics.cpp index e2340c5e074..e31c65fb494 100644 --- a/src/kinetics/Kinetics.cpp +++ b/src/kinetics/Kinetics.cpp @@ -90,11 +90,12 @@ shared_ptr Kinetics::reactionPhase() const return m_thermo[0]; } -void Kinetics::checkSpeciesIndex(size_t k) const +size_t Kinetics::checkSpeciesIndex(size_t k) const { - if (k >= m_kk) { - throw IndexError("Kinetics::checkSpeciesIndex", "species", k, m_kk); + if (k < m_kk) { + return k; } + throw IndexError("Kinetics::checkSpeciesIndex", "species", k, m_kk); } void Kinetics::checkSpeciesArraySize(size_t kk) const diff --git a/src/thermo/DebyeHuckel.cpp b/src/thermo/DebyeHuckel.cpp index 5e50418bc09..b050b378c96 100644 --- a/src/thermo/DebyeHuckel.cpp +++ b/src/thermo/DebyeHuckel.cpp @@ -446,7 +446,6 @@ void DebyeHuckel::getSpeciesParameters(const string& name, AnyMap& speciesNode) { MolalityVPSSTP::getSpeciesParameters(name, speciesNode); size_t k = speciesIndex(name, true); - checkSpeciesIndex(k); AnyMap dhNode; if (m_Aionic[k] != m_Aionic_default) { dhNode["ionic-radius"].setQuantity(m_Aionic[k], "m"); diff --git a/src/thermo/Phase.cpp b/src/thermo/Phase.cpp index 8363ae6b599..18bcae6fe3a 100644 --- a/src/thermo/Phase.cpp +++ b/src/thermo/Phase.cpp @@ -114,8 +114,9 @@ int Phase::changeElementType(int m, int elem_type) double Phase::nAtoms(size_t k, size_t m) const { + checkElementIndex(m); checkSpeciesIndex(k); - return m_speciesComp[m_mm * k + checkElementIndex(m)]; + return m_speciesComp[m_mm * k + m]; } size_t Phase::findSpeciesLower(const string& name) const @@ -150,8 +151,7 @@ size_t Phase::speciesIndex(const string& name, bool raise) const size_t loc = npos; auto it = m_speciesIndices.find(name); if (it != m_speciesIndices.end()) { - checkSpeciesIndex(it->second); - return it->second; + return checkSpeciesIndex(it->second); } else if (!m_caseSensitiveSpecies) { loc = findSpeciesLower(name); } @@ -173,11 +173,12 @@ const vector& Phase::speciesNames() const return m_speciesNames; } -void Phase::checkSpeciesIndex(size_t k) const +size_t Phase::checkSpeciesIndex(size_t k) const { - if (k >= m_kk) { - throw IndexError("Phase::checkSpeciesIndex", "species", k, m_kk); + if (k < m_kk) { + return k; } + throw IndexError("Phase::checkSpeciesIndex", "species", k, m_kk); } void Phase::checkSpeciesArraySize(size_t kk) const diff --git a/src/transport/Transport.cpp b/src/transport/Transport.cpp index 5f92d51d02d..d78186e03a2 100644 --- a/src/transport/Transport.cpp +++ b/src/transport/Transport.cpp @@ -18,11 +18,12 @@ shared_ptr Transport::clone(shared_ptr thermo) const return newTransport(thermo, transportModel()); } -void Transport::checkSpeciesIndex(size_t k) const +size_t Transport::checkSpeciesIndex(size_t k) const { - if (k >= m_nsp) { - throw IndexError("Transport::checkSpeciesIndex", "species", k, m_nsp); + if (k < m_nsp) { + return k; } + throw IndexError("Transport::checkSpeciesIndex", "species", k, m_nsp); } void Transport::checkSpeciesArraySize(size_t kk) const diff --git a/test/python/test_mixture.py b/test/python/test_mixture.py index 4c91dd9ba71..5a9438149cd 100644 --- a/test/python/test_mixture.py +++ b/test/python/test_mixture.py @@ -54,13 +54,13 @@ def test_speciesIndex(self, mix, phase1, phase2): with pytest.raises(IndexError, match='out of range'): mix.species_index(3, 'OH') - with pytest.raises(ValueError, match='No such species'): + with pytest.raises(ct.CanteraError, match="Species OH not found."): mix.species_index(1, 'OH') - with pytest.raises(ValueError, match='out of range'): + with pytest.raises(ct.CanteraError, match="outside valid range"): mix.species_index(0, -2) - with pytest.raises(ValueError, match='No such species'): + with pytest.raises(ct.CanteraError, match="Species CO2 not found."): mix.species_index(1, 'CO2') def test_n_atoms(self, mix): diff --git a/test/python/test_thermo.py b/test/python/test_thermo.py index d2a09f4e8c7..388e52e7e1d 100644 --- a/test/python/test_thermo.py +++ b/test/python/test_thermo.py @@ -102,7 +102,7 @@ def test_n_atoms(self): kSpec = self.phase.species_index(species) assert self.phase.n_atoms(kSpec, mElem) == n - with pytest.raises(ValueError, match="No such species 'C'"): + with pytest.raises(ct.CanteraError, match="Species C not found."): self.phase.n_atoms('C', 'H2') with pytest.raises(ct.CanteraError, match='Element CH4 not found.'): self.phase.n_atoms('H', 'CH4') @@ -1929,7 +1929,7 @@ def test_case_sensitive_names(self): gas.case_sensitive_species_names = True assert gas.case_sensitive_species_names - with pytest.raises(ValueError): + with pytest.raises(ct.CanteraError, match="Species h2 not found."): gas.species_index('h2') with pytest.raises(ct.CanteraError, match="Species h2 not found."): gas.X = 'h2:1.0, o2:1.0' @@ -1949,7 +1949,7 @@ def test_case_sensitive_names(self): with pytest.raises(ct.CanteraError, match='is not unique'): gas.species_index('cs') gas.case_sensitive_species_names = True - with pytest.raises(ValueError): + with pytest.raises(ct.CanteraError, match="Species cs not found."): gas.species_index('cs') def test_unstable_element_in_phase(self): From b23315e669ca32dd3358e47cd6000a6e7b46aeea Mon Sep 17 00:00:00 2001 From: Ingmar Schoegl Date: Mon, 13 Oct 2025 10:54:33 -0500 Subject: [PATCH 05/26] [base] Make phaseIndex more consistent --- include/cantera/equil/MultiPhase.h | 19 +++++++++++--- include/cantera/kinetics/Kinetics.h | 25 ++++++++++++++++++- include/cantera/thermo/Phase.h | 12 ++++----- include/cantera/transport/Transport.h | 2 +- interfaces/cython/cantera/kinetics.pxd | 2 +- interfaces/cython/cantera/kinetics.pyx | 2 +- .../src/sourcegen/headers/ctkin.yaml | 2 ++ .../src/sourcegen/headers/ctthermo.yaml | 2 ++ src/base/Solution.cpp | 2 +- src/equil/MultiPhase.cpp | 18 +++++++++---- src/zeroD/MoleReactor.cpp | 4 +-- .../stoichSolidKinetics.cpp | 2 +- 12 files changed, 70 insertions(+), 22 deletions(-) diff --git a/include/cantera/equil/MultiPhase.h b/include/cantera/equil/MultiPhase.h index 90a56c093ac..d83dd0d4473 100644 --- a/include/cantera/equil/MultiPhase.h +++ b/include/cantera/equil/MultiPhase.h @@ -117,7 +117,7 @@ class MultiPhase //! Check that the specified element index is in range. /*! * @since After %Cantera 3.2, returns verified element index. - * @exception Throws an exception if m is greater than nElements()-1 + * @exception Throws an IndexError if m is greater than nElements()-1 */ size_t checkElementIndex(size_t m) const; @@ -148,7 +148,7 @@ class MultiPhase * @since Added the `raise` argument in %Cantera 3.2. If not specified, the default * behavior if an element is not found in %Cantera 3.2 is to return `npos`. * After %Cantera 3.2, the default behavior will be to throw an exception. - * @exception Throws an IndexError. + * @exception Throws a CanteraError if the specified element is not found. */ size_t elementIndex(const string& name, bool raise) const; @@ -160,7 +160,7 @@ class MultiPhase //! Check that the specified species index is in range. /*! * @since After %Cantera 3.2, returns verified species index. - * @exception Throws an exception if k is greater than nSpecies()-1 + * @exception Throws an IndexError if k is greater than nSpecies()-1 */ size_t checkSpeciesIndex(size_t k) const; @@ -212,9 +212,22 @@ class MultiPhase /*! * @param pName Name of the phase * @returns the index. A value of -1 means the phase isn't in the object. + * @deprecated To be removed after %Cantera 3.2. Use 2-parameter version instead. */ int phaseIndex(const string& pName) const; + //! Returns the index, given the phase name + /*! + * @param pName Name of the phase + * @param raise If `true`, raise exception if the specified phase is not found. + * @returns the index. A value of -1 means the phase isn't in the object. + * @since Added the `raise` argument in %Cantera 3.2. If not specified, the default + * behavior if a phase is not found in %Cantera 3.2 is to return -1. + * After %Cantera 3.2, the default behavior will be to throw an exception. + * @exception Throws a CanteraError if the phase is not found. + */ + int phaseIndex(const string& pName, bool raise) const; + //! Return the number of moles in phase n. /*! * @param n Index of the phase. diff --git a/include/cantera/kinetics/Kinetics.h b/include/cantera/kinetics/Kinetics.h index 297b8c22570..5f0f49a688b 100644 --- a/include/cantera/kinetics/Kinetics.h +++ b/include/cantera/kinetics/Kinetics.h @@ -174,7 +174,7 @@ class Kinetics //! Check that the specified species index is in range /*! * @since After %Cantera 3.2, returns verified species index. - * @exception Throws an exception if k is greater than nSpecies()-1 + * @exception Throws an IndexError if k is greater than nSpecies()-1 */ size_t checkSpeciesIndex(size_t k) const; @@ -214,9 +214,32 @@ class Kinetics * * If a -1 is returned, then the phase is not defined in the Kinetics * object. + * @deprecated To be removed after %Cantera 3.2. Use 2-parameter version instead. */ size_t phaseIndex(const string& ph) const { + warn_deprecated("Kinetics::phaseIndex", "'raise' argument not specified; " + "Default behavior will change from returning -1 to throwing an exception " + "after Cantera 3.2."); + return phaseIndex(ph, false); + } + + /** + * Return the phase index of a phase in the list of phases defined within + * the object. + * + * @param ph string name of the phase + * @param raise If `true`, raise exception if the specified phase is not defined + * in the Kinetics object. + * @since Added the `raise` argument in %Cantera 3.2. If not specified, the default + * behavior if a phase is not found in %Cantera 3.2 is to return `npos`. + * After %Cantera 3.2, the default behavior will be to throw an exception. + * @exception Throws a CanteraError if the specified phase is not defined. + */ + size_t phaseIndex(const string& ph, bool raise) const { if (m_phaseindex.find(ph) == m_phaseindex.end()) { + if (raise) { + throw CanteraError("Kinetics::phaseIndex", "Phase {} not found.", ph); + } return npos; } else { return m_phaseindex.at(ph) - 1; diff --git a/include/cantera/thermo/Phase.h b/include/cantera/thermo/Phase.h index b16583b792b..a0a6069d3b4 100644 --- a/include/cantera/thermo/Phase.h +++ b/include/cantera/thermo/Phase.h @@ -162,7 +162,7 @@ class Phase * @since Added the `raise` argument in %Cantera 3.2. If not specified, the default * behavior if an element is not found in %Cantera 3.2 is to return `npos`. * After %Cantera 3.2, the default behavior will be to throw an exception. - * @exception Throws an IndexError. + * @exception Throws a CanteraError if the specified element is not found. */ size_t elementIndex(const string& name, bool raise) const; @@ -216,7 +216,7 @@ class Phase //! Check that the specified element index is in range. /*! * @since After %Cantera 3.2, returns verified element index. - * @exception Throws an exception if m is greater than nElements()-1 + * @exception Throws an IndexError if m is greater than nElements()-1 */ size_t checkElementIndex(size_t m) const; @@ -247,13 +247,13 @@ class Phase * will have an index of nSpecies() - 1. * @param name String name of the species. It may also be in the form * phaseName:speciesName - * @param raise If `true`, raise exception if the specified element is not found; + * @param raise If `true`, raise exception if the specified species is not found; * otherwise, return @ref npos. * @return The index of the species. * @since Added the `raise` argument in %Cantera 3.2. If not specified, the default - * behavior if an element is not found in %Cantera 3.2 is to return `npos`. + * behavior if a species is not found in %Cantera 3.2 is to return `npos`. * After %Cantera 3.2, the default behavior will be to throw an exception. - * @exception Throws an IndexError. + * @exception Throws a CanteraError if the specifieds species is not found. */ size_t speciesIndex(const string& name, bool raise) const; @@ -272,7 +272,7 @@ class Phase //! Check that the specified species index is in range. /*! * @since After %Cantera 3.2, returns verified species index. - * @exception Throws an exception if k is greater than nSpecies()-1 + * @exception Throws an IndexError if k is greater than nSpecies()-1 */ size_t checkSpeciesIndex(size_t k) const; diff --git a/include/cantera/transport/Transport.h b/include/cantera/transport/Transport.h index 33e19e11cad..79e0f32dc69 100644 --- a/include/cantera/transport/Transport.h +++ b/include/cantera/transport/Transport.h @@ -115,7 +115,7 @@ class Transport //! Check that the specified species index is in range. /*! * @since After %Cantera 3.2, returns verified species index. - * @exception Throws an exception if k is greater than #m_nsp. + * @exception Throws an IndexError if k is greater than #m_nsp. */ size_t checkSpeciesIndex(size_t k) const; diff --git a/interfaces/cython/cantera/kinetics.pxd b/interfaces/cython/cantera/kinetics.pxd index e341f762ed1..9cb5c99a12e 100644 --- a/interfaces/cython/cantera/kinetics.pxd +++ b/interfaces/cython/cantera/kinetics.pxd @@ -24,7 +24,7 @@ cdef extern from "cantera/kinetics/Kinetics.h" namespace "Cantera": int nTotalSpecies() int nReactions() int nPhases() - int phaseIndex(string) + int phaseIndex(string&, cbool) int kineticsSpeciesIndex(int, int) int kineticsSpeciesIndex(string) string kineticsSpeciesName(int) diff --git a/interfaces/cython/cantera/kinetics.pyx b/interfaces/cython/cantera/kinetics.pyx index 7eda5729f5c..ed160988c81 100644 --- a/interfaces/cython/cantera/kinetics.pyx +++ b/interfaces/cython/cantera/kinetics.pyx @@ -928,7 +928,7 @@ cdef class InterfaceKinetics(Kinetics): def _setup_phase_indices(self): self._phase_indices = {} for name, phase in list(self.adjacent.items()) + [(self.name, self)]: - i = self.kinetics.phaseIndex(stringify(name)) + i = self.kinetics.phaseIndex(stringify(name), True) self._phase_indices[phase] = i self._phase_indices[name] = i self._phase_indices[i] = i diff --git a/interfaces/sourcegen/src/sourcegen/headers/ctkin.yaml b/interfaces/sourcegen/src/sourcegen/headers/ctkin.yaml index ba3396e6f78..800632d2b40 100644 --- a/interfaces/sourcegen/src/sourcegen/headers/ctkin.yaml +++ b/interfaces/sourcegen/src/sourcegen/headers/ctkin.yaml @@ -21,6 +21,8 @@ recipes: - name: reactionPhase # New in Cantera 3.2 what: accessor - name: phaseIndex + # alternate version to be removed after Cantera 3.2 + wraps: phaseIndex(const string&, bool) - name: nTotalSpecies # Renamed in Cantera 3.2 (previously nSpecies) - name: reactantStoichCoeff - name: productStoichCoeff diff --git a/interfaces/sourcegen/src/sourcegen/headers/ctthermo.yaml b/interfaces/sourcegen/src/sourcegen/headers/ctthermo.yaml index 317d7e1ea4c..c381bd66f8e 100644 --- a/interfaces/sourcegen/src/sourcegen/headers/ctthermo.yaml +++ b/interfaces/sourcegen/src/sourcegen/headers/ctthermo.yaml @@ -46,8 +46,10 @@ recipes: - name: elementName # Renamed in Cantera 3.2 (previously getElementName) - name: speciesName # Renamed in Cantera 3.2 (previously getSpeciesName) - name: elementIndex + # alternate version to be removed after Cantera 3.2 wraps: elementIndex(const string&, bool) - name: speciesIndex + # alternate version to be removed after Cantera 3.2 wraps: speciesIndex(const string&, bool) - name: nAtoms - name: addElement diff --git a/src/base/Solution.cpp b/src/base/Solution.cpp index 1bef1ad9707..96d4f90910c 100644 --- a/src/base/Solution.cpp +++ b/src/base/Solution.cpp @@ -32,7 +32,7 @@ shared_ptr Solution::clone(const vector>& adjacen map> adjacentByName; for (const auto& soln : adjacent) { adjacentByName[soln->name()] = soln; - if (m_kinetics->phaseIndex(soln->name()) == -1) { + if (m_kinetics->phaseIndex(soln->name(),false) == npos) { throw CanteraError("Solution::clone", "Provided adjacent phase '{}'" " not found in Kinetics object.", soln->name()); } diff --git a/src/equil/MultiPhase.cpp b/src/equil/MultiPhase.cpp index 561110b6efe..f65118a13e0 100644 --- a/src/equil/MultiPhase.cpp +++ b/src/equil/MultiPhase.cpp @@ -223,10 +223,7 @@ size_t MultiPhase::speciesIndex(const string& speciesName, const string& phaseNa if (!m_init) { init(); } - size_t p = phaseIndex(phaseName); - if (p == npos) { - throw CanteraError("MultiPhase::speciesIndex", "phase not found: " + phaseName); - } + size_t p = phaseIndex(phaseName, true); size_t k = m_phase[p]->speciesIndex(speciesName, true); return m_spstart[p] + k; } @@ -793,13 +790,24 @@ string MultiPhase::phaseName(const size_t iph) const } int MultiPhase::phaseIndex(const string& pName) const +{ + warn_deprecated("MultiPhase::phaseIndex", "'raise' argument not specified; " + "Default behavior will change from returning -1 to throwing an exception " + "after Cantera 3.2."); + return phaseIndex(pName, false); +} + +int MultiPhase::phaseIndex(const string& pName, bool raise) const { for (int iph = 0; iph < (int) nPhases(); iph++) { if (m_phase[iph]->name() == pName) { return iph; } } - return -1; + if (!raise) { + return -1; + } + throw CanteraError("MultiPhase::phaseIndex", "Phase {} not found.", pName); } double MultiPhase::phaseMoles(const size_t n) const diff --git a/src/zeroD/MoleReactor.cpp b/src/zeroD/MoleReactor.cpp index 72cecf5b749..cd579af052d 100644 --- a/src/zeroD/MoleReactor.cpp +++ b/src/zeroD/MoleReactor.cpp @@ -104,8 +104,8 @@ void MoleReactor::addSurfaceJacobian(vector> &triplets) size_t col = it.col(); auto& rowPhase = kin->speciesPhase(row); auto& colPhase = kin->speciesPhase(col); - size_t rpi = kin->phaseIndex(rowPhase.name()); - size_t cpi = kin->phaseIndex(colPhase.name()); + size_t rpi = kin->phaseIndex(rowPhase.name(), true); + size_t cpi = kin->phaseIndex(colPhase.name(), true); // check if the reactor kinetics object contains both phases to avoid // any solid phases which may be included then use phases to map surf // kinetics indicies to reactor kinetic indices diff --git a/test_problems/stoichSolidKinetics/stoichSolidKinetics.cpp b/test_problems/stoichSolidKinetics/stoichSolidKinetics.cpp index 2c59e501397..3044ce8748c 100644 --- a/test_problems/stoichSolidKinetics/stoichSolidKinetics.cpp +++ b/test_problems/stoichSolidKinetics/stoichSolidKinetics.cpp @@ -59,7 +59,7 @@ void testProblem() fe3o4_s->setState_TP(Temp, OneAtm); soln->thermo()->setState_TP(Temp, OneAtm); - size_t igco2 = gasTP->speciesIndex("CO2", true); + size_t igco2 = gasTP->speciesIndex("CO2"); vector work(gasTP->nSpecies(), 0.0); From acced3d1e4083156150deb15bfa85c577c7282fa Mon Sep 17 00:00:00 2001 From: Ingmar Schoegl Date: Mon, 13 Oct 2025 11:25:40 -0500 Subject: [PATCH 06/26] [base] Address additional indices/deprecations --- include/cantera/equil/MultiPhase.h | 9 +++++++-- include/cantera/kinetics/Kinetics.h | 17 +++++++++++++---- include/cantera/thermo/Phase.h | 1 + include/cantera/transport/Transport.h | 1 + interfaces/cython/cantera/kinetics.pxd | 2 +- src/equil/MultiPhase.cpp | 13 +++++++++---- src/kinetics/Kinetics.cpp | 20 ++++++++++++++------ src/thermo/Phase.cpp | 2 ++ src/transport/Transport.cpp | 2 ++ 9 files changed, 50 insertions(+), 17 deletions(-) diff --git a/include/cantera/equil/MultiPhase.h b/include/cantera/equil/MultiPhase.h index d83dd0d4473..9ed647fc278 100644 --- a/include/cantera/equil/MultiPhase.h +++ b/include/cantera/equil/MultiPhase.h @@ -167,6 +167,7 @@ class MultiPhase //! Check that an array size is at least nSpecies(). //! Throws an exception if kk is less than nSpecies(). Used before calls //! which take an array pointer. + //! @deprecated To be removed after %Cantera 3.2. Only used by legacy CLib. void checkSpeciesArraySize(size_t kk) const; //! Name of species with global index @e kGlob @@ -252,12 +253,16 @@ class MultiPhase ThermoPhase& phase(size_t n); //! Check that the specified phase index is in range - //! Throws an exception if m is greater than nPhases() - void checkPhaseIndex(size_t m) const; + /*! + * @since After %Cantera 3.2, returns verified species index. + * @exception Throws an IndexError if m is greater than nPhases()-1 + */ + size_t checkPhaseIndex(size_t m) const; //! Check that an array size is at least nPhases() //! Throws an exception if mm is less than nPhases(). Used before calls //! which take an array pointer. + //! @deprecated To be removed after %Cantera 3.2. Unused void checkPhaseArraySize(size_t mm) const; //! Returns the moles of global species @c k. units = kmol diff --git a/include/cantera/kinetics/Kinetics.h b/include/cantera/kinetics/Kinetics.h index 5f0f49a688b..60534c4ba0d 100644 --- a/include/cantera/kinetics/Kinetics.h +++ b/include/cantera/kinetics/Kinetics.h @@ -163,12 +163,16 @@ class Kinetics } //! Check that the specified reaction index is in range - //! Throws an exception if i is greater than nReactions() - void checkReactionIndex(size_t m) const; + /*! + * @since After %Cantera 3.2, returns verified reaction index. + * @exception Throws an IndexError if m is greater than nReactions()-1 + */ + size_t checkReactionIndex(size_t m) const; //! Check that an array size is at least nReactions() //! Throws an exception if ii is less than nReactions(). Used before calls //! which take an array pointer. + //! @deprecated To be removed after %Cantera 3.2. Only used by legacy CLib. void checkReactionArraySize(size_t ii) const; //! Check that the specified species index is in range @@ -181,6 +185,7 @@ class Kinetics //! Check that an array size is at least nSpecies() //! Throws an exception if kk is less than nSpecies(). Used before calls //! which take an array pointer. + //! @deprecated To be removed after %Cantera 3.2. Only used by legacy CLib. void checkSpeciesArraySize(size_t mm) const; //! @} @@ -198,12 +203,16 @@ class Kinetics } //! Check that the specified phase index is in range - //! Throws an exception if m is greater than nPhases() - void checkPhaseIndex(size_t m) const; + /*! + * @since After %Cantera 3.2, returns verified species index. + * @exception Throws an IndexError if m is greater than nPhases()-1 + */ + size_t checkPhaseIndex(size_t m) const; //! Check that an array size is at least nPhases() //! Throws an exception if mm is less than nPhases(). Used before calls //! which take an array pointer. + //! @deprecated To be removed after %Cantera 3.2. Unused void checkPhaseArraySize(size_t mm) const; /** diff --git a/include/cantera/thermo/Phase.h b/include/cantera/thermo/Phase.h index a0a6069d3b4..b79a4a55f4f 100644 --- a/include/cantera/thermo/Phase.h +++ b/include/cantera/thermo/Phase.h @@ -279,6 +279,7 @@ class Phase //! Check that an array size is at least nSpecies(). //! Throws an exception if kk is less than nSpecies(). Used before calls //! which take an array pointer. + //! @deprecated To be removed after %Cantera 3.2. Only used by legacy CLib. void checkSpeciesArraySize(size_t kk) const; //! @} end group Element and Species Information diff --git a/include/cantera/transport/Transport.h b/include/cantera/transport/Transport.h index 79e0f32dc69..e75b8113abd 100644 --- a/include/cantera/transport/Transport.h +++ b/include/cantera/transport/Transport.h @@ -122,6 +122,7 @@ class Transport //! Check that an array size is at least #m_nsp. Throws an exception if //! kk is less than #m_nsp. Used before calls which take an array //! pointer. + //! @deprecated To be removed after %Cantera 3.2. Only used by legacy CLib. void checkSpeciesArraySize(size_t kk) const; //! @name Transport Properties diff --git a/interfaces/cython/cantera/kinetics.pxd b/interfaces/cython/cantera/kinetics.pxd index 9cb5c99a12e..406ab29da06 100644 --- a/interfaces/cython/cantera/kinetics.pxd +++ b/interfaces/cython/cantera/kinetics.pxd @@ -24,7 +24,7 @@ cdef extern from "cantera/kinetics/Kinetics.h" namespace "Cantera": int nTotalSpecies() int nReactions() int nPhases() - int phaseIndex(string&, cbool) + int phaseIndex(string&, cbool) except +translate_exception int kineticsSpeciesIndex(int, int) int kineticsSpeciesIndex(string) string kineticsSpeciesName(int) diff --git a/src/equil/MultiPhase.cpp b/src/equil/MultiPhase.cpp index f65118a13e0..d4939f6fdc1 100644 --- a/src/equil/MultiPhase.cpp +++ b/src/equil/MultiPhase.cpp @@ -174,15 +174,18 @@ ThermoPhase& MultiPhase::phase(size_t n) return *m_phase[n]; } -void MultiPhase::checkPhaseIndex(size_t m) const +size_t MultiPhase::checkPhaseIndex(size_t m) const { - if (m >= nPhases()) { - throw IndexError("MultiPhase::checkPhaseIndex", "phase", m, nPhases()); + if (m < nPhases()) { + return m; } + throw IndexError("MultiPhase::checkPhaseIndex", "phase", m, nPhases()); } void MultiPhase::checkPhaseArraySize(size_t mm) const { + warn_deprecated("MultiPhase::checkPhaseArraySize", + "To be removed after Cantera 3.2. Unused."); if (nPhases() > mm) { throw ArraySizeError("MultiPhase::checkPhaseIndex", mm, nPhases()); } @@ -721,7 +724,7 @@ size_t MultiPhase::checkElementIndex(size_t m) const void MultiPhase::checkElementArraySize(size_t mm) const { - warn_deprecated("Phase::checkElementArraySize", + warn_deprecated("MultiPhase::checkElementArraySize", "To be removed after Cantera 3.2. Only used by legacy CLib."); if (m_nel > mm) { throw ArraySizeError("MultiPhase::checkElementArraySize", mm, m_nel); @@ -764,6 +767,8 @@ size_t MultiPhase::checkSpeciesIndex(size_t k) const void MultiPhase::checkSpeciesArraySize(size_t kk) const { + warn_deprecated("MultiPhase::checkSpeciesArraySize", + "To be removed after Cantera 3.2. Only used by legacy CLib."); if (m_nsp > kk) { throw ArraySizeError("MultiPhase::checkSpeciesArraySize", kk, m_nsp); } diff --git a/src/kinetics/Kinetics.cpp b/src/kinetics/Kinetics.cpp index e31c65fb494..add116fdce8 100644 --- a/src/kinetics/Kinetics.cpp +++ b/src/kinetics/Kinetics.cpp @@ -39,11 +39,12 @@ shared_ptr Kinetics::clone( return newKinetics(phases, phaseNode, rootNode, phases[0]->root()); } -void Kinetics::checkReactionIndex(size_t i) const +size_t Kinetics::checkReactionIndex(size_t i) const { - if (i >= nReactions()) { - throw IndexError("Kinetics::checkReactionIndex", "reactions", i, nReactions()); + if (i < nReactions()) { + return i; } + throw IndexError("Kinetics::checkReactionIndex", "reactions", i, nReactions()); } void Kinetics::resizeReactions() @@ -65,21 +66,26 @@ void Kinetics::resizeReactions() void Kinetics::checkReactionArraySize(size_t ii) const { + warn_deprecated("Kinetics::checkReactionArraySize", + "To be removed after Cantera 3.2. Only used by legacy CLib."); if (nReactions() > ii) { throw ArraySizeError("Kinetics::checkReactionArraySize", ii, nReactions()); } } -void Kinetics::checkPhaseIndex(size_t m) const +size_t Kinetics::checkPhaseIndex(size_t m) const { - if (m >= nPhases()) { - throw IndexError("Kinetics::checkPhaseIndex", "phase", m, nPhases()); + if (m < nPhases()) { + return m; } + throw IndexError("Kinetics::checkPhaseIndex", "phase", m, nPhases()); } void Kinetics::checkPhaseArraySize(size_t mm) const { + warn_deprecated("Kinetics::checkPhaseArraySize", + "To be removed after Cantera 3.2. Unused."); if (nPhases() > mm) { throw ArraySizeError("Kinetics::checkPhaseArraySize", mm, nPhases()); } @@ -100,6 +106,8 @@ size_t Kinetics::checkSpeciesIndex(size_t k) const void Kinetics::checkSpeciesArraySize(size_t kk) const { + warn_deprecated("Kinetics::checkSpeciesArraySize", + "To be removed after Cantera 3.2. Only used by legacy CLib."); if (m_kk > kk) { throw ArraySizeError("Kinetics::checkSpeciesArraySize", kk, m_kk); } diff --git a/src/thermo/Phase.cpp b/src/thermo/Phase.cpp index 18bcae6fe3a..7160a9b7acd 100644 --- a/src/thermo/Phase.cpp +++ b/src/thermo/Phase.cpp @@ -183,6 +183,8 @@ size_t Phase::checkSpeciesIndex(size_t k) const void Phase::checkSpeciesArraySize(size_t kk) const { + warn_deprecated("Phase::checkSpeciesArraySize", + "To be removed after Cantera 3.2. Only used by legacy CLib."); if (m_kk > kk) { throw ArraySizeError("Phase::checkSpeciesArraySize", kk, m_kk); } diff --git a/src/transport/Transport.cpp b/src/transport/Transport.cpp index d78186e03a2..5f7079fde55 100644 --- a/src/transport/Transport.cpp +++ b/src/transport/Transport.cpp @@ -28,6 +28,8 @@ size_t Transport::checkSpeciesIndex(size_t k) const void Transport::checkSpeciesArraySize(size_t kk) const { + warn_deprecated("Transport::checkSpeciesArraySize", + "To be removed after Cantera 3.2. Only used by legacy CLib."); if (m_nsp > kk) { throw ArraySizeError("Transport::checkSpeciesArraySize", kk, m_nsp); } From 99140c68ff1bfa072c962c784679e152ae8d2114 Mon Sep 17 00:00:00 2001 From: Ingmar Schoegl Date: Mon, 13 Oct 2025 13:24:30 -0500 Subject: [PATCH 07/26] [zeroD] Always raise exception for invalid entries --- src/zeroD/ConstPressureMoleReactor.cpp | 13 +++++----- src/zeroD/ConstPressureReactor.cpp | 16 ++++++------ src/zeroD/FlowReactor.cpp | 22 +++++++++------- .../IdealGasConstPressureMoleReactor.cpp | 19 ++++++++------ src/zeroD/IdealGasConstPressureReactor.cpp | 16 ++++++------ src/zeroD/IdealGasMoleReactor.cpp | 16 ++++++------ src/zeroD/IdealGasReactor.cpp | 19 ++++++++------ src/zeroD/MoleReactor.cpp | 16 ++++++------ src/zeroD/Reactor.cpp | 25 ++++++++++--------- test/python/test_reactor.py | 4 +-- 10 files changed, 93 insertions(+), 73 deletions(-) diff --git a/src/zeroD/ConstPressureMoleReactor.cpp b/src/zeroD/ConstPressureMoleReactor.cpp index 39efb34b231..cd6d1ea341b 100644 --- a/src/zeroD/ConstPressureMoleReactor.cpp +++ b/src/zeroD/ConstPressureMoleReactor.cpp @@ -109,13 +109,14 @@ void ConstPressureMoleReactor::eval(double time, double* LHS, double* RHS) size_t ConstPressureMoleReactor::componentIndex(const string& nm) const { - size_t k = speciesIndex(nm); - if (k != npos) { - return k + m_sidx; - } else if (nm == "enthalpy") { + if (nm == "enthalpy") { return 0; - } else { - return npos; + } + try { + return speciesIndex(nm) + m_sidx; + } catch (const CanteraError&) { + throw CanteraError("ConstPressureMoleReactor::componentIndex", + "Unknown component '{}'", nm); } } diff --git a/src/zeroD/ConstPressureReactor.cpp b/src/zeroD/ConstPressureReactor.cpp index f3deb64a50b..408115a1b81 100644 --- a/src/zeroD/ConstPressureReactor.cpp +++ b/src/zeroD/ConstPressureReactor.cpp @@ -130,15 +130,17 @@ vector ConstPressureReactor::steadyConstraints() const { size_t ConstPressureReactor::componentIndex(const string& nm) const { - size_t k = speciesIndex(nm); - if (k != npos) { - return k + 2; - } else if (nm == "mass") { + if (nm == "mass") { return 0; - } else if (nm == "enthalpy") { + } + if (nm == "enthalpy") { return 1; - } else { - return npos; + } + try { + return speciesIndex(nm) + 2; + } catch (const CanteraError&) { + throw CanteraError("ConstPressureReactor::componentIndex", + "Unknown component '{}'", nm); } } diff --git a/src/zeroD/FlowReactor.cpp b/src/zeroD/FlowReactor.cpp index 68792eb46a9..9a95607dd49 100644 --- a/src/zeroD/FlowReactor.cpp +++ b/src/zeroD/FlowReactor.cpp @@ -339,19 +339,23 @@ void FlowReactor::getConstraints(double* constraints) { size_t FlowReactor::componentIndex(const string& nm) const { - size_t k = speciesIndex(nm); - if (k != npos) { - return k + m_offset_Y; - } else if (nm == "density") { + if (nm == "density") { return 0; - } else if (nm == "speed") { + } + if (nm == "speed") { return 1; - } else if (nm == "pressure") { + } + if (nm == "pressure") { return 2; - } else if (nm == "temperature") { + } + if (nm == "temperature") { return 3; - } else { - return npos; + } + try { + return speciesIndex(nm) + m_offset_Y; + } catch (const CanteraError&) { + throw CanteraError("FlowReactor::componentIndex", + "Unknown component '{}'", nm); } } diff --git a/src/zeroD/IdealGasConstPressureMoleReactor.cpp b/src/zeroD/IdealGasConstPressureMoleReactor.cpp index 85edebd55a6..9e96937bc5d 100644 --- a/src/zeroD/IdealGasConstPressureMoleReactor.cpp +++ b/src/zeroD/IdealGasConstPressureMoleReactor.cpp @@ -151,9 +151,11 @@ Eigen::SparseMatrix IdealGasConstPressureMoleReactor::jacobian() vector prod_rates(curr_kin->nTotalSpecies()); curr_kin->getNetProductionRates(prod_rates.data()); for (size_t i = 0; i < curr_kin->nTotalSpecies(); i++) { - size_t row = speciesIndex(curr_kin->kineticsSpeciesName(i)); - if (row != npos) { + try { + size_t row = speciesIndex(curr_kin->kineticsSpeciesName(i)); netProductionRates[row] += prod_rates[i]; + } catch (...) { + // species do not map } } } @@ -233,13 +235,14 @@ Eigen::SparseMatrix IdealGasConstPressureMoleReactor::jacobian() size_t IdealGasConstPressureMoleReactor::componentIndex(const string& nm) const { - size_t k = speciesIndex(nm); - if (k != npos) { - return k + m_sidx; - } else if (nm == "temperature") { + if (nm == "temperature") { return 0; - } else { - return npos; + } + try { + return speciesIndex(nm) + m_sidx; + } catch (const CanteraError&) { + throw CanteraError("IdealGasConstPressureReactor::componentIndex", + "Unknown component '{}'", nm); } } diff --git a/src/zeroD/IdealGasConstPressureReactor.cpp b/src/zeroD/IdealGasConstPressureReactor.cpp index 660f8acce8f..12c4fb4678c 100644 --- a/src/zeroD/IdealGasConstPressureReactor.cpp +++ b/src/zeroD/IdealGasConstPressureReactor.cpp @@ -141,15 +141,17 @@ vector IdealGasConstPressureReactor::steadyConstraints() const size_t IdealGasConstPressureReactor::componentIndex(const string& nm) const { - size_t k = speciesIndex(nm); - if (k != npos) { - return k + 2; - } else if (nm == "mass") { + if (nm == "mass") { return 0; - } else if (nm == "temperature") { + } + if (nm == "temperature") { return 1; - } else { - return npos; + } + try { + return speciesIndex(nm) + 2; + } catch (const CanteraError&) { + throw CanteraError("IdealGasConstPressureReactor::componentIndex", + "Unknown component '{}'", nm); } } diff --git a/src/zeroD/IdealGasMoleReactor.cpp b/src/zeroD/IdealGasMoleReactor.cpp index d9ddb9d79db..7f0ffb4b2d7 100644 --- a/src/zeroD/IdealGasMoleReactor.cpp +++ b/src/zeroD/IdealGasMoleReactor.cpp @@ -43,15 +43,17 @@ void IdealGasMoleReactor::getState(double* y) size_t IdealGasMoleReactor::componentIndex(const string& nm) const { - size_t k = speciesIndex(nm); - if (k != npos) { - return k + m_sidx; - } else if (nm == "temperature") { + if (nm == "temperature") { return 0; - } else if (nm == "volume") { + } + if (nm == "volume") { return 1; - } else { - return npos; + } + try { + return speciesIndex(nm) + m_sidx; + } catch (const CanteraError&) { + throw CanteraError("IdealGasMoleReactor::componentIndex", + "Unknown component '{}'", nm); } } diff --git a/src/zeroD/IdealGasReactor.cpp b/src/zeroD/IdealGasReactor.cpp index e462b6aaf7a..6ef08b04722 100644 --- a/src/zeroD/IdealGasReactor.cpp +++ b/src/zeroD/IdealGasReactor.cpp @@ -147,17 +147,20 @@ vector IdealGasReactor::steadyConstraints() const size_t IdealGasReactor::componentIndex(const string& nm) const { - size_t k = speciesIndex(nm); - if (k != npos) { - return k + 3; - } else if (nm == "mass") { + if (nm == "mass") { return 0; - } else if (nm == "volume") { + } + if (nm == "volume") { return 1; - } else if (nm == "temperature") { + } + if (nm == "temperature") { return 2; - } else { - return npos; + } + try { + return speciesIndex(nm) + 3; + } catch (const CanteraError&) { + throw CanteraError("IdealGasReactor::componentIndex", + "Unknown component '{}'", nm); } } diff --git a/src/zeroD/MoleReactor.cpp b/src/zeroD/MoleReactor.cpp index cd579af052d..1993dead2b1 100644 --- a/src/zeroD/MoleReactor.cpp +++ b/src/zeroD/MoleReactor.cpp @@ -283,15 +283,17 @@ void MoleReactor::eval(double time, double* LHS, double* RHS) size_t MoleReactor::componentIndex(const string& nm) const { - size_t k = speciesIndex(nm); - if (k != npos) { - return k + m_sidx; - } else if (nm == "int_energy") { + if (nm == "int_energy") { return 0; - } else if (nm == "volume") { + } + if (nm == "volume") { return 1; - } else { - return npos; + } + try { + return speciesIndex(nm) + m_sidx; + } catch (const CanteraError&) { + throw CanteraError("MoleReactor::componentIndex", + "Unknown component '{}'", nm); } } diff --git a/src/zeroD/Reactor.cpp b/src/zeroD/Reactor.cpp index fe3bbff3411..5ff7053139c 100644 --- a/src/zeroD/Reactor.cpp +++ b/src/zeroD/Reactor.cpp @@ -480,22 +480,26 @@ size_t Reactor::speciesIndex(const string& nm) const offset += th->nSpecies(); } } - return npos; + throw CanteraError("Reactor::speciesIndex", + "Unknown species '{}'", nm); } size_t Reactor::componentIndex(const string& nm) const { - size_t k = speciesIndex(nm); - if (k != npos) { - return k + 3; - } else if (nm == "mass") { + if (nm == "mass") { return 0; - } else if (nm == "volume") { + } + if (nm == "volume") { return 1; - } else if (nm == "int_energy") { + } + if (nm == "int_energy") { return 2; - } else { - return npos; + } + try { + return speciesIndex(nm) + 3; + } catch (const CanteraError&) { + throw CanteraError("Reactor::componentIndex", + "Unknown component '{}'", nm); } } @@ -631,9 +635,6 @@ bool Reactor::getAdvanceLimits(double *limits) const void Reactor::setAdvanceLimit(const string& nm, const double limit) { size_t k = componentIndex(nm); - if (k == npos) { - throw CanteraError("Reactor::setAdvanceLimit", "No component named '{}'", nm); - } if (m_thermo == 0) { throw CanteraError("Reactor::setAdvanceLimit", diff --git a/test/python/test_reactor.py b/test/python/test_reactor.py index 88f89167a51..e811b55c4fc 100644 --- a/test/python/test_reactor.py +++ b/test/python/test_reactor.py @@ -359,7 +359,7 @@ def test_heat_transfer1(self): def test_advance_limits_invalid(self): self.make_reactors(n_reactors=1) - with pytest.raises(ct.CanteraError, match="No component named 'spam'"): + with pytest.raises(ct.CanteraError, match="Unknown component 'spam'"): self.r1.set_advance_limit("spam", 0.1) def test_advance_reverse(self): @@ -1786,7 +1786,7 @@ def test_component_names(self): name = r.component_name(i) assert r.component_index(name) == i - with pytest.raises(IndexError, match="No such component: 'spam'"): + with pytest.raises(ct.CanteraError, match="Unknown component 'spam'"): r.component_index('spam') with pytest.raises(ct.CanteraError, match='out of bounds'): From 1eb8e3c79191a796e404ec852bd412d9551c6f80 Mon Sep 17 00:00:00 2001 From: Ingmar Schoegl Date: Mon, 13 Oct 2025 14:00:58 -0500 Subject: [PATCH 08/26] [base] Make exception messages consistent --- include/cantera/kinetics/Kinetics.h | 2 +- src/equil/MultiPhase.cpp | 4 ++-- src/oneD/Domain1D.cpp | 4 ++-- src/oneD/Flow1D.cpp | 2 +- src/oneD/OneDim.cpp | 2 +- src/thermo/Phase.cpp | 4 ++-- src/zeroD/ConstPressureMoleReactor.cpp | 2 +- src/zeroD/ConstPressureReactor.cpp | 2 +- src/zeroD/FlowReactor.cpp | 2 +- .../IdealGasConstPressureMoleReactor.cpp | 2 +- src/zeroD/IdealGasConstPressureReactor.cpp | 2 +- src/zeroD/IdealGasMoleReactor.cpp | 2 +- src/zeroD/IdealGasReactor.cpp | 2 +- src/zeroD/MoleReactor.cpp | 2 +- src/zeroD/Reactor.cpp | 4 ++-- test/python/test_mixture.py | 6 +++--- test/python/test_onedim.py | 2 +- test/python/test_reactor.py | 4 ++-- test/python/test_thermo.py | 20 +++++++++---------- 19 files changed, 35 insertions(+), 35 deletions(-) diff --git a/include/cantera/kinetics/Kinetics.h b/include/cantera/kinetics/Kinetics.h index 60534c4ba0d..265091b26d4 100644 --- a/include/cantera/kinetics/Kinetics.h +++ b/include/cantera/kinetics/Kinetics.h @@ -247,7 +247,7 @@ class Kinetics size_t phaseIndex(const string& ph, bool raise) const { if (m_phaseindex.find(ph) == m_phaseindex.end()) { if (raise) { - throw CanteraError("Kinetics::phaseIndex", "Phase {} not found.", ph); + throw CanteraError("Kinetics::phaseIndex", "Phase '{}' not found", ph); } return npos; } else { diff --git a/src/equil/MultiPhase.cpp b/src/equil/MultiPhase.cpp index d4939f6fdc1..f4351ca9038 100644 --- a/src/equil/MultiPhase.cpp +++ b/src/equil/MultiPhase.cpp @@ -754,7 +754,7 @@ size_t MultiPhase::elementIndex(const string& name, bool raise) const if (!raise) { return npos; } - throw CanteraError("MultiPhase::elementIndex", "Element {} not found.", name); + throw CanteraError("MultiPhase::elementIndex", "Element '{}' not found", name); } size_t MultiPhase::checkSpeciesIndex(size_t k) const @@ -812,7 +812,7 @@ int MultiPhase::phaseIndex(const string& pName, bool raise) const if (!raise) { return -1; } - throw CanteraError("MultiPhase::phaseIndex", "Phase {} not found.", pName); + throw CanteraError("MultiPhase::phaseIndex", "Phase '{}' not found", pName); } double MultiPhase::phaseMoles(const size_t n) const diff --git a/src/oneD/Domain1D.cpp b/src/oneD/Domain1D.cpp index 3cb5f428c11..e00c15a903b 100644 --- a/src/oneD/Domain1D.cpp +++ b/src/oneD/Domain1D.cpp @@ -89,7 +89,7 @@ size_t Domain1D::componentIndex(const string& name, bool checkAlias) const } } throw CanteraError("Domain1D::componentIndex", - "no component named '{}'", name); + "Component '{}' not found", name); } bool Domain1D::hasComponent(const string& name, bool checkAlias) const @@ -100,7 +100,7 @@ bool Domain1D::hasComponent(const string& name, bool checkAlias) const } } throw CanteraError("Domain1D::hasComponent", - "no component named '{}'", name); + "Component '{}' not found", name); } void Domain1D::setTransientTolerances(double rtol, double atol, size_t n) diff --git a/src/oneD/Flow1D.cpp b/src/oneD/Flow1D.cpp index 0719307f055..35c62ffe16c 100644 --- a/src/oneD/Flow1D.cpp +++ b/src/oneD/Flow1D.cpp @@ -890,7 +890,7 @@ size_t Flow1D::componentIndex(const string& name, bool checkAlias) const } } throw CanteraError("Flow1D::componentIndex", - "No component named '{}'", name); + "Component '{}' not found", name); } bool Flow1D::hasComponent(const string& name, bool checkAlias) const diff --git a/src/oneD/OneDim.cpp b/src/oneD/OneDim.cpp index 2b8eec13440..06d7a9a13ef 100644 --- a/src/oneD/OneDim.cpp +++ b/src/oneD/OneDim.cpp @@ -33,7 +33,7 @@ size_t OneDim::domainIndex(const string& name) const return n; } } - throw CanteraError("OneDim::domainIndex","no domain named >>"+name+"<<"); + throw CanteraError("OneDim::domainIndex", "Domain '{}' not found", name); } std::tuple OneDim::component(size_t i) const { diff --git a/src/thermo/Phase.cpp b/src/thermo/Phase.cpp index 7160a9b7acd..d7e238c92ce 100644 --- a/src/thermo/Phase.cpp +++ b/src/thermo/Phase.cpp @@ -72,7 +72,7 @@ size_t Phase::elementIndex(const string& elementName, bool raise) const if (!raise) { return npos; } - throw CanteraError("Phase::elementIndex", "Element {} not found.", elementName); + throw CanteraError("Phase::elementIndex", "Element '{}' not found", elementName); } const vector& Phase::elementNames() const @@ -156,7 +156,7 @@ size_t Phase::speciesIndex(const string& name, bool raise) const loc = findSpeciesLower(name); } if (loc==npos && raise) { - throw CanteraError("Phase::speciesIndex", "Species {} not found.", name); + throw CanteraError("Phase::speciesIndex", "Species '{}' not found", name); } return loc; diff --git a/src/zeroD/ConstPressureMoleReactor.cpp b/src/zeroD/ConstPressureMoleReactor.cpp index cd6d1ea341b..17c9ebee586 100644 --- a/src/zeroD/ConstPressureMoleReactor.cpp +++ b/src/zeroD/ConstPressureMoleReactor.cpp @@ -116,7 +116,7 @@ size_t ConstPressureMoleReactor::componentIndex(const string& nm) const return speciesIndex(nm) + m_sidx; } catch (const CanteraError&) { throw CanteraError("ConstPressureMoleReactor::componentIndex", - "Unknown component '{}'", nm); + "Component '{}' not found", nm); } } diff --git a/src/zeroD/ConstPressureReactor.cpp b/src/zeroD/ConstPressureReactor.cpp index 408115a1b81..6f89bc10bdf 100644 --- a/src/zeroD/ConstPressureReactor.cpp +++ b/src/zeroD/ConstPressureReactor.cpp @@ -140,7 +140,7 @@ size_t ConstPressureReactor::componentIndex(const string& nm) const return speciesIndex(nm) + 2; } catch (const CanteraError&) { throw CanteraError("ConstPressureReactor::componentIndex", - "Unknown component '{}'", nm); + "Component '{}' not found", nm); } } diff --git a/src/zeroD/FlowReactor.cpp b/src/zeroD/FlowReactor.cpp index 9a95607dd49..ea4e76e642a 100644 --- a/src/zeroD/FlowReactor.cpp +++ b/src/zeroD/FlowReactor.cpp @@ -355,7 +355,7 @@ size_t FlowReactor::componentIndex(const string& nm) const return speciesIndex(nm) + m_offset_Y; } catch (const CanteraError&) { throw CanteraError("FlowReactor::componentIndex", - "Unknown component '{}'", nm); + "Component '{}' not found", nm); } } diff --git a/src/zeroD/IdealGasConstPressureMoleReactor.cpp b/src/zeroD/IdealGasConstPressureMoleReactor.cpp index 9e96937bc5d..4e46ab596e0 100644 --- a/src/zeroD/IdealGasConstPressureMoleReactor.cpp +++ b/src/zeroD/IdealGasConstPressureMoleReactor.cpp @@ -242,7 +242,7 @@ size_t IdealGasConstPressureMoleReactor::componentIndex(const string& nm) const return speciesIndex(nm) + m_sidx; } catch (const CanteraError&) { throw CanteraError("IdealGasConstPressureReactor::componentIndex", - "Unknown component '{}'", nm); + "Component '{}' not found", nm); } } diff --git a/src/zeroD/IdealGasConstPressureReactor.cpp b/src/zeroD/IdealGasConstPressureReactor.cpp index 12c4fb4678c..583479171ad 100644 --- a/src/zeroD/IdealGasConstPressureReactor.cpp +++ b/src/zeroD/IdealGasConstPressureReactor.cpp @@ -151,7 +151,7 @@ size_t IdealGasConstPressureReactor::componentIndex(const string& nm) const return speciesIndex(nm) + 2; } catch (const CanteraError&) { throw CanteraError("IdealGasConstPressureReactor::componentIndex", - "Unknown component '{}'", nm); + "Component '{}' not found", nm); } } diff --git a/src/zeroD/IdealGasMoleReactor.cpp b/src/zeroD/IdealGasMoleReactor.cpp index 7f0ffb4b2d7..d7d39a7140c 100644 --- a/src/zeroD/IdealGasMoleReactor.cpp +++ b/src/zeroD/IdealGasMoleReactor.cpp @@ -53,7 +53,7 @@ size_t IdealGasMoleReactor::componentIndex(const string& nm) const return speciesIndex(nm) + m_sidx; } catch (const CanteraError&) { throw CanteraError("IdealGasMoleReactor::componentIndex", - "Unknown component '{}'", nm); + "Component '{}' not found", nm); } } diff --git a/src/zeroD/IdealGasReactor.cpp b/src/zeroD/IdealGasReactor.cpp index 6ef08b04722..890f5bb0f22 100644 --- a/src/zeroD/IdealGasReactor.cpp +++ b/src/zeroD/IdealGasReactor.cpp @@ -160,7 +160,7 @@ size_t IdealGasReactor::componentIndex(const string& nm) const return speciesIndex(nm) + 3; } catch (const CanteraError&) { throw CanteraError("IdealGasReactor::componentIndex", - "Unknown component '{}'", nm); + "Component '{}' not found", nm); } } diff --git a/src/zeroD/MoleReactor.cpp b/src/zeroD/MoleReactor.cpp index 1993dead2b1..8da27a6845b 100644 --- a/src/zeroD/MoleReactor.cpp +++ b/src/zeroD/MoleReactor.cpp @@ -293,7 +293,7 @@ size_t MoleReactor::componentIndex(const string& nm) const return speciesIndex(nm) + m_sidx; } catch (const CanteraError&) { throw CanteraError("MoleReactor::componentIndex", - "Unknown component '{}'", nm); + "Component '{}' not found", nm); } } diff --git a/src/zeroD/Reactor.cpp b/src/zeroD/Reactor.cpp index 5ff7053139c..d516069dd92 100644 --- a/src/zeroD/Reactor.cpp +++ b/src/zeroD/Reactor.cpp @@ -481,7 +481,7 @@ size_t Reactor::speciesIndex(const string& nm) const } } throw CanteraError("Reactor::speciesIndex", - "Unknown species '{}'", nm); + "Species '{}' not found", nm); } size_t Reactor::componentIndex(const string& nm) const @@ -499,7 +499,7 @@ size_t Reactor::componentIndex(const string& nm) const return speciesIndex(nm) + 3; } catch (const CanteraError&) { throw CanteraError("Reactor::componentIndex", - "Unknown component '{}'", nm); + "Component '{}' not found", nm); } } diff --git a/test/python/test_mixture.py b/test/python/test_mixture.py index 5a9438149cd..d690278ef62 100644 --- a/test/python/test_mixture.py +++ b/test/python/test_mixture.py @@ -28,7 +28,7 @@ def test_sizes(self, mix, phase1, phase2): assert len(E) == mix.n_elements def test_element_index(self, mix): - with pytest.raises(ct.CanteraError, match="Element W not found."): + with pytest.raises(ct.CanteraError, match="Element 'W' not found"): mix.element_index('W') with pytest.raises(ct.CanteraError, match="outside valid range"): @@ -54,13 +54,13 @@ def test_speciesIndex(self, mix, phase1, phase2): with pytest.raises(IndexError, match='out of range'): mix.species_index(3, 'OH') - with pytest.raises(ct.CanteraError, match="Species OH not found."): + with pytest.raises(ct.CanteraError, match="Species 'OH' not found"): mix.species_index(1, 'OH') with pytest.raises(ct.CanteraError, match="outside valid range"): mix.species_index(0, -2) - with pytest.raises(ct.CanteraError, match="Species CO2 not found."): + with pytest.raises(ct.CanteraError, match="Species 'CO2' not found"): mix.species_index(1, 'CO2') def test_n_atoms(self, mix): diff --git a/test/python/test_onedim.py b/test/python/test_onedim.py index fc2094baf66..15c1a253f34 100644 --- a/test/python/test_onedim.py +++ b/test/python/test_onedim.py @@ -113,7 +113,7 @@ def test_tolerances(self): # Some things don't work until the domains have been added to a Sim1D sim = ct.Sim1D((left, flame, right)) - with pytest.raises(ct.CanteraError, match='No component'): + with pytest.raises(ct.CanteraError, match="Component 'foobar' not found"): flame.set_steady_tolerances(foobar=(3e-4, 3e-6)) flame.set_steady_tolerances(default=(5e-3, 5e-5), diff --git a/test/python/test_reactor.py b/test/python/test_reactor.py index e811b55c4fc..b931e151ae0 100644 --- a/test/python/test_reactor.py +++ b/test/python/test_reactor.py @@ -359,7 +359,7 @@ def test_heat_transfer1(self): def test_advance_limits_invalid(self): self.make_reactors(n_reactors=1) - with pytest.raises(ct.CanteraError, match="Unknown component 'spam'"): + with pytest.raises(ct.CanteraError, match="Component 'spam' not found"): self.r1.set_advance_limit("spam", 0.1) def test_advance_reverse(self): @@ -1786,7 +1786,7 @@ def test_component_names(self): name = r.component_name(i) assert r.component_index(name) == i - with pytest.raises(ct.CanteraError, match="Unknown component 'spam'"): + with pytest.raises(ct.CanteraError, match="Component 'spam' not found"): r.component_index('spam') with pytest.raises(ct.CanteraError, match='out of bounds'): diff --git a/test/python/test_thermo.py b/test/python/test_thermo.py index 388e52e7e1d..7b71bc59f3d 100644 --- a/test/python/test_thermo.py +++ b/test/python/test_thermo.py @@ -102,9 +102,9 @@ def test_n_atoms(self): kSpec = self.phase.species_index(species) assert self.phase.n_atoms(kSpec, mElem) == n - with pytest.raises(ct.CanteraError, match="Species C not found."): + with pytest.raises(ct.CanteraError, match="Species 'C' not found"): self.phase.n_atoms('C', 'H2') - with pytest.raises(ct.CanteraError, match='Element CH4 not found.'): + with pytest.raises(ct.CanteraError, match="Element 'CH4' not found"): self.phase.n_atoms('H', 'CH4') def test_elemental_mass_fraction(self): @@ -119,7 +119,7 @@ def test_elemental_mass_fraction(self): assert Zh == approx(0.5 * (2.016 / 18.015)) assert Zar == 0.0 - with pytest.raises(ct.CanteraError, match="Element C not found."): + with pytest.raises(ct.CanteraError, match="Element 'C' not found"): self.phase.elemental_mass_fraction('C') with pytest.raises(ct.CanteraError, match="outside valid range"): self.phase.elemental_mass_fraction(5) @@ -136,7 +136,7 @@ def test_elemental_mole_fraction(self): assert Zh == approx((2 * 0.5) / (0.5 * 3 + 0.5 * 2)) assert Zar == 0.0 - with pytest.raises(ct.CanteraError, match="Element C not found."): + with pytest.raises(ct.CanteraError, match="Element 'C' not found"): self.phase.elemental_mole_fraction('C') with pytest.raises(ct.CanteraError, match="outside valid range"): self.phase.elemental_mole_fraction(5) @@ -198,7 +198,7 @@ def test_setCompositionString(self): assert X[0] == approx(0.5) assert X[3] == approx(0.5) - with pytest.raises(ct.CanteraError, match="Species CO2 not found."): + with pytest.raises(ct.CanteraError, match="Species 'CO2' not found"): self.phase.X = 'H2:1.0, CO2:1.5' def test_setCompositionStringBad(self): @@ -264,7 +264,7 @@ def test_setCompositionNoNormBad(self): self.phase.set_unnormalized_mass_fractions([1, 2, 3]) def test_setCompositionDict_bad1(self): - with pytest.raises(ct.CanteraError, match="Species HCl not found."): + with pytest.raises(ct.CanteraError, match="Species 'HCl' not found"): self.phase.X = {'H2': 1.0, 'HCl': 3.0} def test_setCompositionDict_bad2(self): @@ -1929,11 +1929,11 @@ def test_case_sensitive_names(self): gas.case_sensitive_species_names = True assert gas.case_sensitive_species_names - with pytest.raises(ct.CanteraError, match="Species h2 not found."): + with pytest.raises(ct.CanteraError, match="Species 'h2' not found"): gas.species_index('h2') - with pytest.raises(ct.CanteraError, match="Species h2 not found."): + with pytest.raises(ct.CanteraError, match="Species 'h2' not found"): gas.X = 'h2:1.0, o2:1.0' - with pytest.raises(ct.CanteraError, match="Species h2 not found."): + with pytest.raises(ct.CanteraError, match="Species 'h2' not found"): gas.Y = 'h2:1.0, o2:1.0' gas_yaml = """ @@ -1949,7 +1949,7 @@ def test_case_sensitive_names(self): with pytest.raises(ct.CanteraError, match='is not unique'): gas.species_index('cs') gas.case_sensitive_species_names = True - with pytest.raises(ct.CanteraError, match="Species cs not found."): + with pytest.raises(ct.CanteraError, match="Species 'cs' not found"): gas.species_index('cs') def test_unstable_element_in_phase(self): From 131cf7bdab232133c80b4cf7849b39f834692fd3 Mon Sep 17 00:00:00 2001 From: Ingmar Schoegl Date: Fri, 17 Oct 2025 16:28:24 -0500 Subject: [PATCH 09/26] [Python] Simplify Sim1D.domain_index --- interfaces/cython/cantera/_onedim.pyx | 22 ++++++---------------- test/python/test_onedim.py | 11 ++++++++--- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/interfaces/cython/cantera/_onedim.pyx b/interfaces/cython/cantera/_onedim.pyx index c72dc29deee..2f2bf7d3c49 100644 --- a/interfaces/cython/cantera/_onedim.pyx +++ b/interfaces/cython/cantera/_onedim.pyx @@ -1074,24 +1074,14 @@ cdef class Sim1D: def domain_index(self, dom): """ - Get the index of a domain, specified either by name or as a Domain1D - object. + Get the index of a domain, specified either by name or as a Domain1D object. """ if isinstance(dom, Domain1D): - idom = self.domains.index(dom) - elif isinstance(dom, int): - idom = dom - else: - idom = None - for i,d in enumerate(self.domains): - if d.name == dom: - idom = i - dom = d - if idom is None: - raise KeyError('Domain named "{0}" not found.'.format(dom)) - - assert 0 <= idom < len(self.domains) - return idom + return self.domains.index(dom) + if isinstance(dom, (str, bytes)): + return self.sim.domainIndex(stringify(dom)) + assert 0 <= dom < len(self.domains) + return dom def _get_indices(self, dom, comp): idom = self.domain_index(dom) diff --git a/test/python/test_onedim.py b/test/python/test_onedim.py index 15c1a253f34..45d6fdc90fe 100644 --- a/test/python/test_onedim.py +++ b/test/python/test_onedim.py @@ -34,11 +34,16 @@ def test_instantiateSurface(self): def test_boundaryProperties(self): gas1 = ct.Solution("h2o2.yaml") gas2 = ct.Solution("h2o2.yaml") - inlet = ct.Inlet1D(name='something', phase=gas1) - flame = ct.FreeFlow(gas1) + inlet = ct.Inlet1D(gas1, name="spam") + flame = ct.FreeFlow(gas1, name="eggs") sim = ct.Sim1D((inlet, flame)) - assert inlet.name == 'something' + assert sim.domain_index("spam") == 0 + assert sim.domain_index(inlet) == 0 + assert sim.domain_index("eggs") == 1 + assert inlet.name == "spam" + with pytest.raises(ct.CanteraError, match="Domain 'bacon' not found"): + sim.domain_index("bacon") gas2.TPX = 400, 101325, 'H2:0.3, O2:0.5, AR:0.2' Xref = gas2.X From 2d205b92451db913fe05fa36f54167c0656936e0 Mon Sep 17 00:00:00 2001 From: Ingmar Schoegl Date: Mon, 13 Oct 2025 14:34:16 -0500 Subject: [PATCH 10/26] [unittest] Fix legacy CLib unit tests --- samples/clib_legacy/demo.c | 4 +--- src/clib/ct.cpp | 6 +++--- src/clib/ctmultiphase.cpp | 2 +- test/clib_legacy/test_clib.cpp | 4 ++++ 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/samples/clib_legacy/demo.c b/samples/clib_legacy/demo.c index 15b798d4ce8..08a80c4559c 100644 --- a/samples/clib_legacy/demo.c +++ b/samples/clib_legacy/demo.c @@ -43,7 +43,7 @@ void exit_with_error() int main(int argc, char** argv) { - ct_suppress_deprecation_warnings(0); // throw errors for deprecated functions + ct_suppress_deprecation_warnings(1); // suppress all deprecation warnings int soln = soln_newSolution("gri30.yaml", "gri30", "default"); // In principle, one ought to check for errors after every Cantera call. But this @@ -92,9 +92,7 @@ int main(int argc, char** argv) printf("\ntime Temperature\n"); int reactor = reactor_new("IdealGasReactor", soln, "test"); int net = reactornet_new(); - ct_suppress_deprecation_warnings(1); reactornet_addreactor(net, reactor); - ct_suppress_deprecation_warnings(0); double t = 0.0; int ret = 0; diff --git a/src/clib/ct.cpp b/src/clib/ct.cpp index e0409b0a0b0..ed42e14e6b0 100644 --- a/src/clib/ct.cpp +++ b/src/clib/ct.cpp @@ -321,7 +321,7 @@ extern "C" { size_t thermo_elementIndex(int n, const char* nm) { try { - size_t k = ThermoCabinet::at(n)->elementIndex(nm); + size_t k = ThermoCabinet::at(n)->elementIndex(nm, false); if (k == npos) { throw CanteraError("thermo_elementIndex", "No such element {}.", nm); @@ -335,7 +335,7 @@ extern "C" { size_t thermo_speciesIndex(int n, const char* nm) { try { - size_t k = ThermoCabinet::at(n)->speciesIndex(nm); + size_t k = ThermoCabinet::at(n)->speciesIndex(nm, false); if (k == npos) { throw CanteraError("thermo_speciesIndex", "No such species {}.", nm); @@ -1179,7 +1179,7 @@ extern "C" { size_t kin_phaseIndex(int n, const char* ph) { try { - size_t k = KineticsCabinet::at(n)->phaseIndex(ph); + size_t k = KineticsCabinet::at(n)->phaseIndex(ph, false); if (k == npos) { throw CanteraError("kin_phaseIndex", "No such phase {}.", ph); diff --git a/src/clib/ctmultiphase.cpp b/src/clib/ctmultiphase.cpp index 653e1c388ac..6b3feb9dab0 100644 --- a/src/clib/ctmultiphase.cpp +++ b/src/clib/ctmultiphase.cpp @@ -93,7 +93,7 @@ extern "C" { size_t mix_elementIndex(int i, const char* name) { try { - return mixCabinet::at(i)->elementIndex(name); + return mixCabinet::at(i)->elementIndex(name, false); } catch (...) { return handleAllExceptions(npos, npos); } diff --git a/test/clib_legacy/test_clib.cpp b/test/clib_legacy/test_clib.cpp index 65f4dc869cf..e83ba1d1daf 100644 --- a/test/clib_legacy/test_clib.cpp +++ b/test/clib_legacy/test_clib.cpp @@ -178,6 +178,7 @@ TEST(ct, new_interface_auto) TEST(ct, thermo) { + suppress_deprecation_warnings(); int ret; int sol = soln_newSolution("gri30.yaml", "gri30", "none"); int thermo = soln_thermo(sol); @@ -223,6 +224,7 @@ TEST(ct, thermo) thermo_getPartialMolarVolumes(thermo, ns, work.data()); prod = std::inner_product(X.begin(), X.end(), work.begin(), 0.0); ASSERT_NEAR(prod, 1./thermo_molarDensity(thermo), 1e-6); + make_deprecation_warnings_fatal(); } TEST(ct, kinetics) @@ -259,6 +261,7 @@ TEST(ct, kinetics) TEST(ct, transport) { + suppress_deprecation_warnings(); int sol0 = soln_newSolution("gri30.yaml", "gri30", "default"); int thermo = soln_thermo(sol0); int tran = soln_transport(sol0); @@ -276,6 +279,7 @@ TEST(ct, transport) for (size_t n = 0; n < nsp; n++) { ASSERT_NEAR(cpp_dkm[n], c_dkm[n], 1e-10); } + make_deprecation_warnings_fatal(); } From 024a3cf4858bb50e2e5bbf5f49c9b90118832f44 Mon Sep 17 00:00:00 2001 From: Ingmar Schoegl Date: Fri, 17 Oct 2025 16:48:06 -0500 Subject: [PATCH 11/26] [mix] Make return type consistent --- include/cantera/equil/MultiPhase.h | 8 ++++---- src/equil/MultiPhase.cpp | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/include/cantera/equil/MultiPhase.h b/include/cantera/equil/MultiPhase.h index 9ed647fc278..b938bd9aa16 100644 --- a/include/cantera/equil/MultiPhase.h +++ b/include/cantera/equil/MultiPhase.h @@ -222,12 +222,12 @@ class MultiPhase * @param pName Name of the phase * @param raise If `true`, raise exception if the specified phase is not found. * @returns the index. A value of -1 means the phase isn't in the object. - * @since Added the `raise` argument in %Cantera 3.2. If not specified, the default - * behavior if a phase is not found in %Cantera 3.2 is to return -1. - * After %Cantera 3.2, the default behavior will be to throw an exception. + * @since Added the `raise` argument in %Cantera 3.2 and changed return type. If + * not specified, the default behavior if a phase is not found in %Cantera 3.2 + * is to return @ref npos. * @exception Throws a CanteraError if the phase is not found. */ - int phaseIndex(const string& pName, bool raise) const; + size_t phaseIndex(const string& pName, bool raise) const; //! Return the number of moles in phase n. /*! diff --git a/src/equil/MultiPhase.cpp b/src/equil/MultiPhase.cpp index f4351ca9038..14e99cb4242 100644 --- a/src/equil/MultiPhase.cpp +++ b/src/equil/MultiPhase.cpp @@ -798,11 +798,11 @@ int MultiPhase::phaseIndex(const string& pName) const { warn_deprecated("MultiPhase::phaseIndex", "'raise' argument not specified; " "Default behavior will change from returning -1 to throwing an exception " - "after Cantera 3.2."); + "after Cantera 3.2. 'npos' will be return instead of -1 if 'raise=true'."); return phaseIndex(pName, false); } -int MultiPhase::phaseIndex(const string& pName, bool raise) const +size_t MultiPhase::phaseIndex(const string& pName, bool raise) const { for (int iph = 0; iph < (int) nPhases(); iph++) { if (m_phase[iph]->name() == pName) { @@ -810,7 +810,7 @@ int MultiPhase::phaseIndex(const string& pName, bool raise) const } } if (!raise) { - return -1; + return npos; } throw CanteraError("MultiPhase::phaseIndex", "Phase '{}' not found", pName); } From df45cd30f7b6777471758233f37d9b61167da690 Mon Sep 17 00:00:00 2001 From: Ingmar Schoegl Date: Sun, 19 Oct 2025 00:09:56 -0500 Subject: [PATCH 12/26] Address review comments --- include/cantera/equil/MultiPhase.h | 12 +++++++----- include/cantera/kinetics/Kinetics.h | 26 +++++++++----------------- include/cantera/thermo/Phase.h | 10 ++++++---- include/cantera/transport/Transport.h | 2 +- interfaces/cython/cantera/mixture.pyx | 7 +++---- interfaces/cython/cantera/thermo.pyx | 6 ++---- src/kinetics/Kinetics.cpp | 12 ++++++++++++ src/thermo/Phase.cpp | 2 +- src/zeroD/FlowReactor.cpp | 9 +++------ 9 files changed, 44 insertions(+), 42 deletions(-) diff --git a/include/cantera/equil/MultiPhase.h b/include/cantera/equil/MultiPhase.h index b938bd9aa16..cfb2b7e0ef5 100644 --- a/include/cantera/equil/MultiPhase.h +++ b/include/cantera/equil/MultiPhase.h @@ -116,7 +116,7 @@ class MultiPhase //! Check that the specified element index is in range. /*! - * @since After %Cantera 3.2, returns verified element index. + * @since Starting in %Cantera 3.2, returns the input element index, if valid. * @exception Throws an IndexError if m is greater than nElements()-1 */ size_t checkElementIndex(size_t m) const; @@ -148,7 +148,8 @@ class MultiPhase * @since Added the `raise` argument in %Cantera 3.2. If not specified, the default * behavior if an element is not found in %Cantera 3.2 is to return `npos`. * After %Cantera 3.2, the default behavior will be to throw an exception. - * @exception Throws a CanteraError if the specified element is not found. + * @exception Throws a CanteraError if the specified element is not found and + * `raise` is `true`. */ size_t elementIndex(const string& name, bool raise) const; @@ -159,7 +160,7 @@ class MultiPhase //! Check that the specified species index is in range. /*! - * @since After %Cantera 3.2, returns verified species index. + * @since Starting in %Cantera 3.2, returns the input species index, if valid. * @exception Throws an IndexError if k is greater than nSpecies()-1 */ size_t checkSpeciesIndex(size_t k) const; @@ -225,7 +226,8 @@ class MultiPhase * @since Added the `raise` argument in %Cantera 3.2 and changed return type. If * not specified, the default behavior if a phase is not found in %Cantera 3.2 * is to return @ref npos. - * @exception Throws a CanteraError if the phase is not found. + * @exception Throws a CanteraError if the specified phase is not found and + * `raise` is `true`. */ size_t phaseIndex(const string& pName, bool raise) const; @@ -254,7 +256,7 @@ class MultiPhase //! Check that the specified phase index is in range /*! - * @since After %Cantera 3.2, returns verified species index. + * @since Starting in %Cantera 3.2, returns the input species index, if valid. * @exception Throws an IndexError if m is greater than nPhases()-1 */ size_t checkPhaseIndex(size_t m) const; diff --git a/include/cantera/kinetics/Kinetics.h b/include/cantera/kinetics/Kinetics.h index 265091b26d4..e63da8467f0 100644 --- a/include/cantera/kinetics/Kinetics.h +++ b/include/cantera/kinetics/Kinetics.h @@ -164,7 +164,7 @@ class Kinetics //! Check that the specified reaction index is in range /*! - * @since After %Cantera 3.2, returns verified reaction index. + * @since Starting in %Cantera 3.2, returns the input reaction index, if valid. * @exception Throws an IndexError if m is greater than nReactions()-1 */ size_t checkReactionIndex(size_t m) const; @@ -177,7 +177,7 @@ class Kinetics //! Check that the specified species index is in range /*! - * @since After %Cantera 3.2, returns verified species index. + * @since Starting in %Cantera 3.2, returns the input species index, if valid. * @exception Throws an IndexError if k is greater than nSpecies()-1 */ size_t checkSpeciesIndex(size_t k) const; @@ -204,7 +204,7 @@ class Kinetics //! Check that the specified phase index is in range /*! - * @since After %Cantera 3.2, returns verified species index. + * @since Starting in %Cantera 3.2, returns the input phase index, if valid. * @exception Throws an IndexError if m is greater than nPhases()-1 */ size_t checkPhaseIndex(size_t m) const; @@ -233,8 +233,8 @@ class Kinetics } /** - * Return the phase index of a phase in the list of phases defined within - * the object. + * Return the index of a phase among the phases participating in this kinetic + * mechanism. * * @param ph string name of the phase * @param raise If `true`, raise exception if the specified phase is not defined @@ -242,18 +242,10 @@ class Kinetics * @since Added the `raise` argument in %Cantera 3.2. If not specified, the default * behavior if a phase is not found in %Cantera 3.2 is to return `npos`. * After %Cantera 3.2, the default behavior will be to throw an exception. - * @exception Throws a CanteraError if the specified phase is not defined. - */ - size_t phaseIndex(const string& ph, bool raise) const { - if (m_phaseindex.find(ph) == m_phaseindex.end()) { - if (raise) { - throw CanteraError("Kinetics::phaseIndex", "Phase '{}' not found", ph); - } - return npos; - } else { - return m_phaseindex.at(ph) - 1; - } - } + * @exception Throws a CanteraError if the specified phase is not found and + * `raise` is `true`. + */ + size_t phaseIndex(const string& ph, bool raise) const; /** * Return pointer to phase where the reactions occur. diff --git a/include/cantera/thermo/Phase.h b/include/cantera/thermo/Phase.h index b79a4a55f4f..1e27c24bef0 100644 --- a/include/cantera/thermo/Phase.h +++ b/include/cantera/thermo/Phase.h @@ -162,7 +162,8 @@ class Phase * @since Added the `raise` argument in %Cantera 3.2. If not specified, the default * behavior if an element is not found in %Cantera 3.2 is to return `npos`. * After %Cantera 3.2, the default behavior will be to throw an exception. - * @exception Throws a CanteraError if the specified element is not found. + * @exception Throws a CanteraError if the specified element is not found and + * `raise` is `true`. */ size_t elementIndex(const string& name, bool raise) const; @@ -215,7 +216,7 @@ class Phase //! Check that the specified element index is in range. /*! - * @since After %Cantera 3.2, returns verified element index. + * @since Starting in %Cantera 3.2, returns the input element index, if valid. * @exception Throws an IndexError if m is greater than nElements()-1 */ size_t checkElementIndex(size_t m) const; @@ -253,7 +254,8 @@ class Phase * @since Added the `raise` argument in %Cantera 3.2. If not specified, the default * behavior if a species is not found in %Cantera 3.2 is to return `npos`. * After %Cantera 3.2, the default behavior will be to throw an exception. - * @exception Throws a CanteraError if the specifieds species is not found. + * @exception Throws a CanteraError if the specified species is not found and + * `raise` is `true`. */ size_t speciesIndex(const string& name, bool raise) const; @@ -271,7 +273,7 @@ class Phase //! Check that the specified species index is in range. /*! - * @since After %Cantera 3.2, returns verified species index. + * @since Starting in %Cantera 3.2, returns the input phase index, if valid. * @exception Throws an IndexError if k is greater than nSpecies()-1 */ size_t checkSpeciesIndex(size_t k) const; diff --git a/include/cantera/transport/Transport.h b/include/cantera/transport/Transport.h index e75b8113abd..25b3c6bf8bf 100644 --- a/include/cantera/transport/Transport.h +++ b/include/cantera/transport/Transport.h @@ -114,7 +114,7 @@ class Transport //! Check that the specified species index is in range. /*! - * @since After %Cantera 3.2, returns verified species index. + * @since Starting in %Cantera 3.2, returns the input species index, if valid. * @exception Throws an IndexError if k is greater than #m_nsp. */ size_t checkSpeciesIndex(size_t k) const; diff --git a/interfaces/cython/cantera/mixture.pyx b/interfaces/cython/cantera/mixture.pyx index 1206931a821..b6ef4175c8b 100644 --- a/interfaces/cython/cantera/mixture.pyx +++ b/interfaces/cython/cantera/mixture.pyx @@ -98,17 +98,16 @@ cdef class Mixture: """Index of element with name 'element'. >>> mix.element_index('H') + 2 """ if isinstance(element, (str, bytes)): return self.mix.elementIndex(stringify(element), True) if isinstance(element, (int, float)): return self.mix.checkElementIndex(element) - else: - raise TypeError("'element' must be a string or a number. " - f"Got {element!r}.") - raise ValueError(f"No such element {element!r}.") + raise TypeError("'element' must be a string or a number. " + f"Got {element!r}.") property n_species: """Number of species.""" diff --git a/interfaces/cython/cantera/thermo.pyx b/interfaces/cython/cantera/thermo.pyx index 5984df85aff..82785a54eec 100644 --- a/interfaces/cython/cantera/thermo.pyx +++ b/interfaces/cython/cantera/thermo.pyx @@ -466,11 +466,9 @@ cdef class ThermoPhase(_SolutionBase): return self.thermo.elementIndex(stringify(element), True) if isinstance(element, (int, float)): return self.thermo.checkElementIndex(element) - else: - raise TypeError("'element' must be a string or a number. " - f"Got {element!r}.") - raise ValueError(f"No such element {element!r}.") + raise TypeError("'element' must be a string or a number. " + f"Got {element!r}.") def element_name(self, m): """Name of the element with index ``m``.""" diff --git a/src/kinetics/Kinetics.cpp b/src/kinetics/Kinetics.cpp index add116fdce8..17169a6979a 100644 --- a/src/kinetics/Kinetics.cpp +++ b/src/kinetics/Kinetics.cpp @@ -91,6 +91,18 @@ void Kinetics::checkPhaseArraySize(size_t mm) const } } +size_t Kinetics::phaseIndex(const string& ph, bool raise) const +{ + if (m_phaseindex.find(ph) == m_phaseindex.end()) { + if (raise) { + throw CanteraError("Kinetics::phaseIndex", "Phase '{}' not found", ph); + } + return npos; + } else { + return m_phaseindex.at(ph) - 1; + } +} + shared_ptr Kinetics::reactionPhase() const { return m_thermo[0]; diff --git a/src/thermo/Phase.cpp b/src/thermo/Phase.cpp index d7e238c92ce..b5f1064714d 100644 --- a/src/thermo/Phase.cpp +++ b/src/thermo/Phase.cpp @@ -151,7 +151,7 @@ size_t Phase::speciesIndex(const string& name, bool raise) const size_t loc = npos; auto it = m_speciesIndices.find(name); if (it != m_speciesIndices.end()) { - return checkSpeciesIndex(it->second); + return it->second; } else if (!m_caseSensitiveSpecies) { loc = findSpeciesLower(name); } diff --git a/src/zeroD/FlowReactor.cpp b/src/zeroD/FlowReactor.cpp index ea4e76e642a..0c2cbb79a44 100644 --- a/src/zeroD/FlowReactor.cpp +++ b/src/zeroD/FlowReactor.cpp @@ -341,14 +341,11 @@ size_t FlowReactor::componentIndex(const string& nm) const { if (nm == "density") { return 0; - } - if (nm == "speed") { + } else if (nm == "speed") { return 1; - } - if (nm == "pressure") { + } else if (nm == "pressure") { return 2; - } - if (nm == "temperature") { + } else if (nm == "temperature") { return 3; } try { From 786f6c2fe3c448c6a8bb302b4766842767b85997 Mon Sep 17 00:00:00 2001 From: Ingmar Schoegl Date: Sun, 19 Oct 2025 01:31:28 -0500 Subject: [PATCH 13/26] [base] Only issue warnings if Index behavior changes Only raise deprecation warnings if npos or -1 is expected, and the new 'raise' argument is needed. --- include/cantera/kinetics/Kinetics.h | 7 +------ src/equil/MultiPhase.cpp | 22 ++++++++++++++-------- src/kinetics/Kinetics.cpp | 11 +++++++++++ src/thermo/Phase.cpp | 22 ++++++++++++++-------- 4 files changed, 40 insertions(+), 22 deletions(-) diff --git a/include/cantera/kinetics/Kinetics.h b/include/cantera/kinetics/Kinetics.h index e63da8467f0..0046c03e05c 100644 --- a/include/cantera/kinetics/Kinetics.h +++ b/include/cantera/kinetics/Kinetics.h @@ -225,12 +225,7 @@ class Kinetics * object. * @deprecated To be removed after %Cantera 3.2. Use 2-parameter version instead. */ - size_t phaseIndex(const string& ph) const { - warn_deprecated("Kinetics::phaseIndex", "'raise' argument not specified; " - "Default behavior will change from returning -1 to throwing an exception " - "after Cantera 3.2."); - return phaseIndex(ph, false); - } + size_t phaseIndex(const string& ph) const; /** * Return the index of a phase among the phases participating in this kinetic diff --git a/src/equil/MultiPhase.cpp b/src/equil/MultiPhase.cpp index 14e99cb4242..f4100cca61a 100644 --- a/src/equil/MultiPhase.cpp +++ b/src/equil/MultiPhase.cpp @@ -738,10 +738,13 @@ string MultiPhase::elementName(size_t m) const size_t MultiPhase::elementIndex(const string& name) const { - warn_deprecated("MultiPhase::elementIndex", "'raise' argument not specified; " - "Default behavior will change from returning npos to throwing an exception " - "after Cantera 3.2."); - return elementIndex(name, false); + size_t ix = elementIndex(name, false); + if (ix == npos) { + warn_deprecated("MultiPhase::elementIndex", "'raise' argument not specified; " + "Default behavior will change from returning npos to throwing an exception " + "after Cantera 3.2."); + } + return ix; } size_t MultiPhase::elementIndex(const string& name, bool raise) const @@ -796,10 +799,13 @@ string MultiPhase::phaseName(const size_t iph) const int MultiPhase::phaseIndex(const string& pName) const { - warn_deprecated("MultiPhase::phaseIndex", "'raise' argument not specified; " - "Default behavior will change from returning -1 to throwing an exception " - "after Cantera 3.2. 'npos' will be return instead of -1 if 'raise=true'."); - return phaseIndex(pName, false); + size_t ix = phaseIndex(pName, false); + if (ix == npos) { + warn_deprecated("MultiPhase::phaseIndex", "'raise' argument not specified; " + "Default behavior will change from returning -1 to throwing an exception " + "after Cantera 3.2. 'npos' will be return instead of -1 if 'raise=true'."); + } + return ix; } size_t MultiPhase::phaseIndex(const string& pName, bool raise) const diff --git a/src/kinetics/Kinetics.cpp b/src/kinetics/Kinetics.cpp index 17169a6979a..ac19a48e4ed 100644 --- a/src/kinetics/Kinetics.cpp +++ b/src/kinetics/Kinetics.cpp @@ -91,6 +91,17 @@ void Kinetics::checkPhaseArraySize(size_t mm) const } } +size_t Kinetics::phaseIndex(const string& ph) const +{ + size_t ix = phaseIndex(ph, false); + if (ix == npos) { + warn_deprecated("Kinetics::phaseIndex", "'raise' argument not specified; " + "Default behavior will change from returning -1 to throwing an " + "exception after Cantera 3.2."); + } + return ix; +} + size_t Kinetics::phaseIndex(const string& ph, bool raise) const { if (m_phaseindex.find(ph) == m_phaseindex.end()) { diff --git a/src/thermo/Phase.cpp b/src/thermo/Phase.cpp index b5f1064714d..1d3bd0ab9bb 100644 --- a/src/thermo/Phase.cpp +++ b/src/thermo/Phase.cpp @@ -56,10 +56,13 @@ string Phase::elementName(size_t m) const size_t Phase::elementIndex(const string& name) const { - warn_deprecated("Phase::elementIndex", "'raise' argument not specified; " - "Default behavior will change from returning npos to throwing an exception " - "after Cantera 3.2."); - return elementIndex(name, false); + size_t ix = elementIndex(name, false); + if (ix == npos) { + warn_deprecated("Phase::elementIndex", "'raise' argument not specified; " + "Default behavior will change from returning npos to throwing an exception " + "after Cantera 3.2."); + } + return ix; } size_t Phase::elementIndex(const string& elementName, bool raise) const @@ -140,10 +143,13 @@ size_t Phase::findSpeciesLower(const string& name) const size_t Phase::speciesIndex(const string& name) const { - warn_deprecated("Phase::speciesIndex", "'raise' argument not specified; " - "Default behavior will change from returning npos to throwing an exception " - "after Cantera 3.2."); - return speciesIndex(name, false); + size_t ix = speciesIndex(name, false); + if (ix == npos) { + warn_deprecated("Phase::speciesIndex", "'raise' argument not specified; " + "Default behavior will change from returning npos to throwing an exception " + "after Cantera 3.2."); + } + return ix; } size_t Phase::speciesIndex(const string& name, bool raise) const From 80ed3dbf4d8693545c4f13d4d94524bbbeb8fbe2 Mon Sep 17 00:00:00 2001 From: Ingmar Schoegl Date: Sun, 19 Oct 2025 02:01:54 -0500 Subject: [PATCH 14/26] [legacy CLib] Add pragma deprecation warnings --- include/cantera/clib/ct.h | 3 +++ include/cantera/clib/ctfunc.h | 3 +++ include/cantera/clib/ctmatlab.h | 3 +++ include/cantera/clib/ctmultiphase.h | 3 +++ include/cantera/clib/ctonedim.h | 3 +++ include/cantera/clib/ctreactor.h | 3 +++ include/cantera/clib/ctrpath.h | 3 +++ include/cantera/clib/ctsurf.h | 3 +++ 8 files changed, 24 insertions(+) diff --git a/include/cantera/clib/ct.h b/include/cantera/clib/ct.h index 304d69e2e09..ce51fabe538 100644 --- a/include/cantera/clib/ct.h +++ b/include/cantera/clib/ct.h @@ -11,6 +11,9 @@ #ifndef CTC_CT_H #define CTC_CT_H +#pragma message("warning: The legacy CLib library ct.h is deprecated and " \ + "will be removed after Cantera 3.2. Use generated CLib instead.") + #include "clib_defs.h" #ifdef __cplusplus diff --git a/include/cantera/clib/ctfunc.h b/include/cantera/clib/ctfunc.h index 40b33a7e449..0e7d81d378b 100644 --- a/include/cantera/clib/ctfunc.h +++ b/include/cantera/clib/ctfunc.h @@ -11,6 +11,9 @@ #ifndef CTC_FUNC1_H #define CTC_FUNC1_H +#pragma message("warning: The legacy CLib library ctfunc.h is deprecated and " \ + "will be removed after Cantera 3.2. Use generated CLib instead.") + #include "clib_defs.h" #ifdef __cplusplus diff --git a/include/cantera/clib/ctmatlab.h b/include/cantera/clib/ctmatlab.h index 7666c9b3cec..8c6bb62ab8a 100644 --- a/include/cantera/clib/ctmatlab.h +++ b/include/cantera/clib/ctmatlab.h @@ -8,6 +8,9 @@ // This file is part of Cantera. See License.txt in the top-level directory or // at https://cantera.org/license.txt for license and copyright information. +#pragma message("warning: The legacy CLib library ctmatlab.h is deprecated and " \ + "will be removed after Cantera 3.2.") + #include "ct.h" #include "ctfunc.h" #include "ctmultiphase.h" diff --git a/include/cantera/clib/ctmultiphase.h b/include/cantera/clib/ctmultiphase.h index 5e96c94fdab..8649ea08ed9 100644 --- a/include/cantera/clib/ctmultiphase.h +++ b/include/cantera/clib/ctmultiphase.h @@ -11,6 +11,9 @@ #ifndef CTC_MULTIPHASE_H #define CTC_MULTIPHASE_H +#pragma message("warning: The legacy CLib library ctmultiphase.h is deprecated and " \ + "will be removed after Cantera 3.2. Use generated CLib instead.") + #include "clib_defs.h" #ifdef __cplusplus diff --git a/include/cantera/clib/ctonedim.h b/include/cantera/clib/ctonedim.h index 18e6d048d0f..e9784b3180a 100644 --- a/include/cantera/clib/ctonedim.h +++ b/include/cantera/clib/ctonedim.h @@ -11,6 +11,9 @@ #ifndef CTC_ONEDIM_H #define CTC_ONEDIM_H +#pragma message("warning: The legacy CLib library ctonedim.h is deprecated and " \ + "will be removed after Cantera 3.2. Use generated CLib instead.") + #include "clib_defs.h" #ifdef __cplusplus diff --git a/include/cantera/clib/ctreactor.h b/include/cantera/clib/ctreactor.h index 76d1eb0d809..84823975128 100644 --- a/include/cantera/clib/ctreactor.h +++ b/include/cantera/clib/ctreactor.h @@ -11,6 +11,9 @@ #ifndef CTC_REACTOR_H #define CTC_REACTOR_H +#pragma message("warning: The legacy CLib library ctreactor.h is deprecated and " \ + "will be removed after Cantera 3.2. Use generated CLib instead.") + #include "clib_defs.h" #ifdef __cplusplus diff --git a/include/cantera/clib/ctrpath.h b/include/cantera/clib/ctrpath.h index 0f0133aac84..01e99682060 100644 --- a/include/cantera/clib/ctrpath.h +++ b/include/cantera/clib/ctrpath.h @@ -11,6 +11,9 @@ #ifndef CTC_RXNPATH_H #define CTC_RXNPATH_H +#pragma message("warning: The legacy CLib library ctrpath.h is deprecated and " \ + "will be removed after Cantera 3.2. Use generated CLib instead.") + #include "clib_defs.h" #ifdef __cplusplus diff --git a/include/cantera/clib/ctsurf.h b/include/cantera/clib/ctsurf.h index fb333371c27..b3f27f5ce24 100644 --- a/include/cantera/clib/ctsurf.h +++ b/include/cantera/clib/ctsurf.h @@ -11,6 +11,9 @@ #ifndef CTC_SURF_H #define CTC_SURF_H +#pragma message("warning: The legacy CLib library ctsurf.h is deprecated and " \ + "will be removed after Cantera 3.2. Use generated CLib instead.") + #include "clib_defs.h" #ifdef __cplusplus From 61f25b797da7e08ab07082c3d0a8bc132d52e9cf Mon Sep 17 00:00:00 2001 From: Ingmar Schoegl Date: Mon, 20 Oct 2025 14:15:41 -0500 Subject: [PATCH 15/26] [Kinetics] Implement raise option for kineticsSpeciesIndex --- include/cantera/kinetics/Kinetics.h | 17 +++++++++++++++++ src/kinetics/BulkKinetics.cpp | 2 +- src/kinetics/Kinetics.cpp | 19 +++++++++++++++++-- src/kinetics/Reaction.cpp | 6 +++--- 4 files changed, 38 insertions(+), 6 deletions(-) diff --git a/include/cantera/kinetics/Kinetics.h b/include/cantera/kinetics/Kinetics.h index 0046c03e05c..1c1f34b6fcb 100644 --- a/include/cantera/kinetics/Kinetics.h +++ b/include/cantera/kinetics/Kinetics.h @@ -326,9 +326,26 @@ class Kinetics * - If no match is found, the value -1 is returned. * * @param nm Input string name of the species + * @deprecated To be removed after %Cantera 3.2. Use 2-parameter version instead. */ size_t kineticsSpeciesIndex(const string& nm) const; + /** + * Return the index of a species within the phases participating in this kinetic + * mechanism. + * This routine will look up a species number based on the input string `nm`. The + * lookup of species will occur for all phases listed in the Kinetics object. + * @param nm Name of the species. + * @param raise If `true`, raise exception if the specified species is not defined + * in the Kinetics object. + * @since Added the `raise` argument in %Cantera 3.2. If not specified, the default + * behavior if a phase is not found in %Cantera 3.2 is to return `npos`. + * After %Cantera 3.2, the default behavior will be to throw an exception. + * @exception Throws a CanteraError if the specified species is not found and + * `raise` is `true`. + */ + size_t kineticsSpeciesIndex(const string& nm, bool raise) const; + /** * This function looks up the name of a species and returns a * reference to the ThermoPhase object of the phase where the species diff --git a/src/kinetics/BulkKinetics.cpp b/src/kinetics/BulkKinetics.cpp index ba11ce331b1..069b1d4c48f 100644 --- a/src/kinetics/BulkKinetics.cpp +++ b/src/kinetics/BulkKinetics.cpp @@ -66,7 +66,7 @@ void BulkKinetics::addThirdBody(shared_ptr r) { map efficiencies; for (const auto& [name, efficiency] : r->thirdBody()->efficiencies) { - size_t k = kineticsSpeciesIndex(name); + size_t k = kineticsSpeciesIndex(name, false); if (k != npos) { efficiencies[k] = efficiency; } else if (!m_skipUndeclaredThirdBodies) { diff --git a/src/kinetics/Kinetics.cpp b/src/kinetics/Kinetics.cpp index ac19a48e4ed..6004d7401f0 100644 --- a/src/kinetics/Kinetics.cpp +++ b/src/kinetics/Kinetics.cpp @@ -95,8 +95,8 @@ size_t Kinetics::phaseIndex(const string& ph) const { size_t ix = phaseIndex(ph, false); if (ix == npos) { - warn_deprecated("Kinetics::phaseIndex", "'raise' argument not specified; " - "Default behavior will change from returning -1 to throwing an " + warn_deprecated("Kinetics::phaseIndex", "'raise' argument not specified. " + "Default behavior will change from returning npos to throwing an " "exception after Cantera 3.2."); } return ix; @@ -343,6 +343,17 @@ string Kinetics::kineticsSpeciesName(size_t k) const } size_t Kinetics::kineticsSpeciesIndex(const string& nm) const +{ + size_t ix = kineticsSpeciesIndex(nm, false); + if (ix == npos) { + warn_deprecated("Kinetics::kineticsSpeciesIndex", "'raise' argument not " + "specified. Default behavior will change from returning npos to throwing " + "an exception after Cantera 3.2."); + } + return ix; +} + +size_t Kinetics::kineticsSpeciesIndex(const string& nm, bool raise) const { for (size_t n = 0; n < m_thermo.size(); n++) { // Check the ThermoPhase object for a match @@ -351,6 +362,10 @@ size_t Kinetics::kineticsSpeciesIndex(const string& nm) const return k + m_start[n]; } } + if (raise) { + throw CanteraError("Kinetics::kineticsSpeciesIndex", + "Species '{}' not found", nm); + } return npos; } diff --git a/src/kinetics/Reaction.cpp b/src/kinetics/Reaction.cpp index 7c45d684207..8dd86c9569f 100644 --- a/src/kinetics/Reaction.cpp +++ b/src/kinetics/Reaction.cpp @@ -295,7 +295,7 @@ void Reaction::setParameters(const AnyMap& node, const Kinetics& kin) if (node.hasKey("orders")) { for (const auto& [name, order] : node["orders"].asMap()) { orders[name] = order; - if (kin.kineticsSpeciesIndex(name) == npos) { + if (kin.kineticsSpeciesIndex(name, false) == npos) { setValid(false); } } @@ -620,7 +620,7 @@ void updateUndeclared(vector& undeclared, const Composition& comp, const Kinetics& kin) { for (const auto& [name, stoich]: comp) { - if (kin.kineticsSpeciesIndex(name) == npos) { + if (kin.kineticsSpeciesIndex(name, false) == npos) { undeclared.emplace_back(name); } } @@ -936,7 +936,7 @@ void parseReactionEquation(Reaction& R, const string& equation, equation, tokens[i], (last_used == npos) ? "n/a" : tokens[last_used]); } - if (!kin || (kin->kineticsSpeciesIndex(species) == npos + if (!kin || (kin->kineticsSpeciesIndex(species, false) == npos && mass_action && species != "M")) { R.setValid(false); From 5e351a1d8ed3b037a8d7725b9d59d5ca08c03a0a Mon Sep 17 00:00:00 2001 From: Ingmar Schoegl Date: Mon, 20 Oct 2025 12:22:56 -0500 Subject: [PATCH 16/26] [CLib] Implement temporary custom code Prevents future changes in the CLib code despite changes in the C++ API. --- .../src/sourcegen/headers/ctkin.yaml | 29 ++++++++++++++++--- .../src/sourcegen/headers/ctmix.yaml | 13 ++++++++- .../src/sourcegen/headers/ctthermo.yaml | 28 +++++++++++++++--- 3 files changed, 61 insertions(+), 9 deletions(-) diff --git a/interfaces/sourcegen/src/sourcegen/headers/ctkin.yaml b/interfaces/sourcegen/src/sourcegen/headers/ctkin.yaml index 800632d2b40..ec0596e0b2c 100644 --- a/interfaces/sourcegen/src/sourcegen/headers/ctkin.yaml +++ b/interfaces/sourcegen/src/sourcegen/headers/ctkin.yaml @@ -21,8 +21,18 @@ recipes: - name: reactionPhase # New in Cantera 3.2 what: accessor - name: phaseIndex - # alternate version to be removed after Cantera 3.2 - wraps: phaseIndex(const string&, bool) + # temporary custom code due to changing C++ API: replaceable by + # wraps: phaseIndex(const string&) + # with implicit `raise=true` default after Cantera 3.2 + brief: Return the phase index of a phase in the list of phases defined within the object. + what: method + declaration: int32_t kin_phaseIndex(int32_t handle, const char* ph); + parameters: + handle: Handle to queried Kinetics object. + ph: string name of the phase + uses: phaseIndex(const string&, bool) + code: |- + return KineticsCabinet::at(handle)->phaseIndex(ph, true); - name: nTotalSpecies # Renamed in Cantera 3.2 (previously nSpecies) - name: reactantStoichCoeff - name: productStoichCoeff @@ -38,8 +48,19 @@ recipes: - name: multiplier - name: setMultiplier - name: isReversible -- name: speciesIndex - wraps: kineticsSpeciesIndex(const string&) # inconsistent API (preexisting) +- name: kineticsSpeciesIndex # Renamed in Cantera 3.2 (previously speciesIndex) + # temporary custom code due to changing C++ API: replaceable by + # wraps: kineticsSpeciesIndex(const string&) + # with implicit `raise=true` default after Cantera 3.2 + brief: Return the index of a species within the phases participating in this kinetic mechanism. + what: method + declaration: int32_t kin_kineticsSpeciesIndex(int32_t handle, const char* nm); + parameters: + handle: Handle to queried Kinetics object. + nm: name of the species + uses: kineticsSpeciesIndex(const string&, bool) + code: |- + return KineticsCabinet::at(handle)->kineticsSpeciesIndex(nm, true); - name: advanceCoverages wraps: advanceCoverages(double) - name: getDeltaEnthalpy # Changed in Cantera 3.2 (previously part of getDelta) diff --git a/interfaces/sourcegen/src/sourcegen/headers/ctmix.yaml b/interfaces/sourcegen/src/sourcegen/headers/ctmix.yaml index acc9d3262f4..e6f28b59e82 100644 --- a/interfaces/sourcegen/src/sourcegen/headers/ctmix.yaml +++ b/interfaces/sourcegen/src/sourcegen/headers/ctmix.yaml @@ -15,7 +15,18 @@ recipes: - name: updatePhases - name: nElements - name: elementIndex - wraps: elementIndex(const string&, bool) + # temporary custom code due to changing C++ API: replaceable by + # wraps: elementIndex(const string&) + # with implicit `raise=true` default after Cantera 3.2 + brief: Returns the index of the element with name + what: method + declaration: int32_t mix_elementIndex(int32_t handle, const char* name) + parameters: + handle: Handle to queried MultiPhase object. + name: String name of the global element + uses: elementIndex(const string&, bool) + code: |- + return MultiPhaseCabinet::at(handle)->elementIndex(name, true); - name: nSpecies - name: speciesIndex wraps: speciesIndex(size_t, size_t) diff --git a/interfaces/sourcegen/src/sourcegen/headers/ctthermo.yaml b/interfaces/sourcegen/src/sourcegen/headers/ctthermo.yaml index c381bd66f8e..6cfef4a49ca 100644 --- a/interfaces/sourcegen/src/sourcegen/headers/ctthermo.yaml +++ b/interfaces/sourcegen/src/sourcegen/headers/ctthermo.yaml @@ -46,11 +46,31 @@ recipes: - name: elementName # Renamed in Cantera 3.2 (previously getElementName) - name: speciesName # Renamed in Cantera 3.2 (previously getSpeciesName) - name: elementIndex - # alternate version to be removed after Cantera 3.2 - wraps: elementIndex(const string&, bool) + # temporary custom code due to changing C++ API: replaceable by + # wraps: elementIndex(const string&) + # with implicit `raise=true` default after Cantera 3.2 + brief: Return the index of element named 'name'. + what: method + declaration: int32_t thermo_elementIndex(int32_t handle, const char* name) + parameters: + handle: Handle to queried Phase object. + name: Name of the element + uses: elementIndex(const string&, bool) + code: |- + return ThermoPhaseCabinet::as(handle)->elementIndex(name, true); - name: speciesIndex - # alternate version to be removed after Cantera 3.2 - wraps: speciesIndex(const string&, bool) + # temporary custom code due to changing C++ API: replaceable by + # wraps: speciesIndex(const string&) + # with implicit `raise=true` default after Cantera 3.2 + brief: Returns the index of a species named 'name' within the Phase object. + what: method + declaration: int32_t thermo_speciesIndex(int32_t handle, const char* name) + parameters: + handle: Handle to queried Phase object. + name: String name of the species. It may also be in the form phaseName:speciesName + uses: speciesIndex(const string&, bool) + code: |- + return ThermoPhaseCabinet::as(handle)->speciesIndex(name, true); - name: nAtoms - name: addElement - name: refPressure From 2dc569eabd2cbc5796c6fabf732836cf01e750d9 Mon Sep 17 00:00:00 2001 From: "Tim E. Dawson" Date: Fri, 11 Jul 2025 13:46:57 -0500 Subject: [PATCH 17/26] Initial cut at Python type stubs. Adding stubs alongside the Python files to provide type hints for external use. These are intended to cover the public interface, including all dynamically-generated attributes. --- interfaces/cython/SConscript | 5 + interfaces/cython/cantera/__init__.pyi | 28 + interfaces/cython/cantera/_cantera.pyi | 17 + interfaces/cython/cantera/_onedim.pyi | 367 +++++ interfaces/cython/cantera/_types.pyi | 110 ++ interfaces/cython/cantera/_utils.pyi | 39 + interfaces/cython/cantera/ck2yaml.pyi | 424 ++++++ interfaces/cython/cantera/composite.pyi | 1257 +++++++++++++++++ interfaces/cython/cantera/constants.pyi | 15 + interfaces/cython/cantera/cti2yaml.pyi | 678 +++++++++ interfaces/cython/cantera/ctml2yaml.py | 605 ++++---- interfaces/cython/cantera/data.py | 2 +- interfaces/cython/cantera/data.pyi | 4 + interfaces/cython/cantera/delegator.pyi | 10 + interfaces/cython/cantera/drawnetwork.pyi | 67 + interfaces/cython/cantera/func1.pyi | 34 + interfaces/cython/cantera/interrupts.pyi | 1 + interfaces/cython/cantera/jacobians.pyi | 41 + interfaces/cython/cantera/kinetics.pyi | 192 +++ interfaces/cython/cantera/liquidvapor.pyi | 12 + interfaces/cython/cantera/lxcat2yaml.py | 90 +- interfaces/cython/cantera/mixture.pyi | 62 + interfaces/cython/cantera/onedim.pyi | 446 ++++++ interfaces/cython/cantera/py.typed | 2 + interfaces/cython/cantera/reaction.pyi | 407 ++++++ interfaces/cython/cantera/reactionpath.pyi | 76 + interfaces/cython/cantera/reactor.pyi | 535 +++++++ interfaces/cython/cantera/solutionbase.pyi | 162 +++ interfaces/cython/cantera/speciesthermo.pyi | 56 + interfaces/cython/cantera/thermo.pyi | 595 ++++++++ interfaces/cython/cantera/transport.pyi | 190 +++ interfaces/cython/cantera/units.pyi | 89 ++ interfaces/cython/cantera/utils.pyi | 4 + .../cython/cantera/with_units/__init__.pyi | 39 + .../cython/cantera/with_units/solution.pyi | 671 +++++++++ interfaces/cython/cantera/yamlwriter.pyi | 27 + interfaces/cython/setup.cfg.in | 3 +- interfaces/python_sdist/pyproject.toml.in | 1 + 38 files changed, 7082 insertions(+), 281 deletions(-) create mode 100644 interfaces/cython/cantera/__init__.pyi create mode 100644 interfaces/cython/cantera/_cantera.pyi create mode 100644 interfaces/cython/cantera/_onedim.pyi create mode 100644 interfaces/cython/cantera/_types.pyi create mode 100644 interfaces/cython/cantera/_utils.pyi create mode 100644 interfaces/cython/cantera/ck2yaml.pyi create mode 100644 interfaces/cython/cantera/composite.pyi create mode 100644 interfaces/cython/cantera/constants.pyi create mode 100644 interfaces/cython/cantera/cti2yaml.pyi create mode 100644 interfaces/cython/cantera/data.pyi create mode 100644 interfaces/cython/cantera/delegator.pyi create mode 100644 interfaces/cython/cantera/drawnetwork.pyi create mode 100644 interfaces/cython/cantera/func1.pyi create mode 100644 interfaces/cython/cantera/interrupts.pyi create mode 100644 interfaces/cython/cantera/jacobians.pyi create mode 100644 interfaces/cython/cantera/kinetics.pyi create mode 100644 interfaces/cython/cantera/liquidvapor.pyi create mode 100644 interfaces/cython/cantera/mixture.pyi create mode 100644 interfaces/cython/cantera/onedim.pyi create mode 100644 interfaces/cython/cantera/py.typed create mode 100644 interfaces/cython/cantera/reaction.pyi create mode 100644 interfaces/cython/cantera/reactionpath.pyi create mode 100644 interfaces/cython/cantera/reactor.pyi create mode 100644 interfaces/cython/cantera/solutionbase.pyi create mode 100644 interfaces/cython/cantera/speciesthermo.pyi create mode 100644 interfaces/cython/cantera/thermo.pyi create mode 100644 interfaces/cython/cantera/transport.pyi create mode 100644 interfaces/cython/cantera/units.pyi create mode 100644 interfaces/cython/cantera/utils.pyi create mode 100644 interfaces/cython/cantera/with_units/__init__.pyi create mode 100644 interfaces/cython/cantera/with_units/solution.pyi create mode 100644 interfaces/cython/cantera/yamlwriter.pyi diff --git a/interfaces/cython/SConscript b/interfaces/cython/SConscript index 1a51f682764..edda511bf3f 100644 --- a/interfaces/cython/SConscript +++ b/interfaces/cython/SConscript @@ -22,6 +22,11 @@ if localenv["example_data"]: localenv.Command(f"cantera/data/example_data/{yaml.name}", yaml.abspath, Copy("$TARGET", "$SOURCE")))) +# Add type stubs as dataFiles +for stub_location in ["cantera", "cantera/with_units"]: + for pyifile in multi_glob(localenv, f"#interfaces/cython/{stub_location}", "pyi"): + dataFiles.append(build(localenv.Command(f"{stub_location}/{pyifile.name}", pyifile.abspath, Copy("$TARGET", "$SOURCE")))) +dataFiles.append(build(localenv.Command("cantera/py.typed", "#interfaces/cython/cantera/py.typed", Copy("$TARGET", "$SOURCE")))) # Install Python samples install(localenv.RecursiveInstall, "$inst_sampledir/python", "#samples/python") diff --git a/interfaces/cython/cantera/__init__.pyi b/interfaces/cython/cantera/__init__.pyi new file mode 100644 index 00000000000..771f741f363 --- /dev/null +++ b/interfaces/cython/cantera/__init__.pyi @@ -0,0 +1,28 @@ +# This file is part of Cantera. See License.txt in the top-level directory or +# at https://cantera.org/license.txt for license and copyright information. + +from ._onedim import * +from ._utils import * +from ._utils import __git_commit__ as __git_commit__ +from ._utils import __sundials_version__ as __sundials_version__ +from ._utils import __version__ as __version__ +from .composite import * +from .constants import * +from .data import * +from .delegator import * +from .func1 import * +from .jacobians import * +from .kinetics import * +from .liquidvapor import * +from .mixture import * +from .onedim import * +from .reaction import * +from .reactionpath import * +from .reactor import * +from .solutionbase import * +from .speciesthermo import * +from .thermo import * +from .transport import * +from .units import * +from .utils import * +from .yamlwriter import * diff --git a/interfaces/cython/cantera/_cantera.pyi b/interfaces/cython/cantera/_cantera.pyi new file mode 100644 index 00000000000..f521f912bd8 --- /dev/null +++ b/interfaces/cython/cantera/_cantera.pyi @@ -0,0 +1,17 @@ +# This file is part of Cantera. See License.txt in the top-level directory or +# at https://cantera.org/license.txt for license and copyright information. + +import importlib.abc +from importlib.machinery import ModuleSpec +from types import ModuleType +from typing import Sequence, override + +class CythonPackageMetaPathFinder(importlib.abc.MetaPathFinder): + def __init__(self, name_filter: str) -> None: ... + @override + def find_spec( + self, + fullname: str, + path: Sequence[str] | None, + target: ModuleType | None = None, + ) -> ModuleSpec | None: ... diff --git a/interfaces/cython/cantera/_onedim.pyi b/interfaces/cython/cantera/_onedim.pyi new file mode 100644 index 00000000000..1055113ebf6 --- /dev/null +++ b/interfaces/cython/cantera/_onedim.pyi @@ -0,0 +1,367 @@ +# This file is part of Cantera. See License.txt in the top-level directory or +# at https://cantera.org/license.txt for license and copyright information. + +from collections.abc import Sequence +from typing import ( + Any, + Callable, + Literal, + TypedDict, + overload, +) + +from typing_extensions import Never, override + +from ._types import ( + Array, + ArrayLike, + Basis, + CompositionLike, + CompressionLevel, + LogLevel, + RefineCriteria, +) +from .jacobians import SystemJacobian +from .solutionbase import SolutionArrayBase, _SolutionBase +from .transport import TransportModel + +ToleranceSettings = TypedDict( + "ToleranceSettings", + { + "transient-abstol": float, + "steady-abstol": float, + "transient-reltol": float, + "steady-reltol": float, + }, +) + +class PhaseSettings(TypedDict): + name: str + source: str + +class FixedPointSettings(TypedDict): + location: float + temperature: float + +Domain1DSettings = TypedDict( + "Domain1DSettings", + { + "type": str, + "points": int, + "tolerances": ToleranceSettings, + "transport-model": TransportModel, + "phase": PhaseSettings, + "radiation-enabled": bool, + "energy-enabled": bool, + "Soret-enabled": bool, + "flux-gradient-basis": Literal[0, 1], + "refine-criteria": RefineCriteria, + "fixed-point": FixedPointSettings, + }, +) + +class Domain1D: + def __init__(self, phase: _SolutionBase, *args: Any, **kwargs: Any) -> None: ... + @property + def phase(self) -> _SolutionBase: ... + @property + def index(self) -> int: ... + @property + def domain_type(self) -> str: ... + @property + def n_components(self) -> int: ... + @property + def n_points(self) -> int: ... + def _to_array( + self, dest: SolutionArrayBase, normalize: bool + ) -> SolutionArrayBase: ... + def _from_array(self, arr: SolutionArrayBase) -> None: ... + def component_name(self, n: int) -> str: ... + @property + def component_names(self) -> list[str]: ... + def component_index(self, name: str) -> int: ... + def set_bounds( + self, + *, + default: tuple[float, float] | None = None, + Y: tuple[float, float] | None = None, + **kwargs: tuple[float, float], + ) -> None: ... + def set_steady_tolerances( + self, + *, + default: tuple[float, float] | None = None, + Y: tuple[float, float] | None = None, + abs: tuple[float, float] | None = None, + rel: tuple[float, float] | None = None, + **kwargs: tuple[float, float], + ) -> None: ... + def set_transient_tolerances( + self, + *, + default: tuple[float, float] | None = None, + Y: tuple[float, float] | None = None, + abs: tuple[float, float] | None = None, + rel: tuple[float, float] | None = None, + **kwargs: tuple[float, float], + ) -> None: ... + def set_default_tolerances(self) -> None: ... + def bounds(self, component: str) -> tuple[float, float]: ... + def tolerances(self, component: str) -> tuple[float, float]: ... + @overload + def steady_reltol(self, component: str) -> float: ... + @overload + def steady_reltol(self, component: None = None) -> Array: ... + @overload + def steady_abstol(self, component: str) -> float: ... + @overload + def steady_abstol(self, component: None = None) -> Array: ... + @overload + def transient_reltol(self, component: str) -> float: ... + @overload + def transient_reltol(self, component: None = None) -> Array: ... + @overload + def transient_abstol(self, component: str) -> float: ... + @overload + def transient_abstol(self, component: None = None) -> Array: ... + @property + def grid(self) -> Array: ... + @grid.setter + def grid(self, grid: ArrayLike) -> None: ... + @property + def name(self) -> str: ... + @name.setter + def name(self, name: str) -> None: ... + @override + def __reduce__(self) -> Never: ... + def __copy__(self) -> Never: ... + @property + def settings(self) -> Domain1DSettings: ... + +class Boundary1D(Domain1D): + def __init__(self, phase: _SolutionBase, name: str | None = None) -> None: ... + @property + def T(self) -> float: ... + @T.setter + def T(self, T: float) -> None: ... + @property + def mdot(self) -> float: ... + @mdot.setter + def mdot(self, mdot: float) -> None: ... + @property + def X(self) -> Array: ... + @X.setter + def X(self, X: CompositionLike) -> None: ... + @property + def Y(self) -> Array: ... + @Y.setter + def Y(self, Y: CompositionLike) -> None: ... + @property + def spread_rate(self) -> float: ... + @spread_rate.setter + def spread_rate(self, spread_rate: float) -> None: ... + +class Inlet1D(Boundary1D): ... +class Outlet1D(Boundary1D): ... +class OutletReservoir1D(Boundary1D): ... +class SymmetryPlane1D(Boundary1D): ... +class Surface1D(Boundary1D): ... + +class ReactingSurface1D(Boundary1D): + @property + def coverage_enabled(self) -> bool: ... + @coverage_enabled.setter + def coverage_enabled(self, value: bool) -> None: ... + +class FlowBase(Domain1D): + def __init__(self, *args: Any, **kwargs: Any) -> None: ... + @property + def P(self) -> float: ... + @P.setter + def P(self, P: float) -> None: ... + @property + def transport_model(self) -> str: ... + @transport_model.setter + def transport_model(self, model: str) -> None: ... + @property + def soret_enabled(self) -> bool: ... + @soret_enabled.setter + def soret_enabled(self, enable: bool) -> None: ... + @property + def flux_gradient_basis(self) -> Basis: ... + @flux_gradient_basis.setter + def flux_gradient_basis(self, basis: Basis) -> None: ... + @property + def energy_enabled(self) -> bool: ... + @energy_enabled.setter + def energy_enabled(self, enable: bool) -> None: ... + def set_fixed_temp_profile(self, pos: Array, temp: Array) -> None: ... + def get_settings3(self) -> Domain1DSettings: ... + @property + def boundary_emissivities(self) -> tuple[float, float]: ... + @boundary_emissivities.setter + def boundary_emissivities(self, epsilon: tuple[float, float]) -> None: ... + @property + def radiation_enabled(self) -> bool: ... + @radiation_enabled.setter + def radiation_enabled(self, enable: bool) -> None: ... + @property + def radiative_heat_loss(self) -> Array: ... + def set_free_flow(self) -> None: ... + def set_axisymmetric_flow(self) -> None: ... + @property + def type(self) -> str: ... + @property + def solving_stage(self) -> Literal[1, 2]: ... + @solving_stage.setter + def solving_stage(self, stage: Literal[1, 2]) -> None: ... + @property + def electric_field_enabled(self) -> bool: ... + @electric_field_enabled.setter + def electric_field_enabled(self, enable: bool) -> None: ... + @property + def left_control_point_temperature(self) -> float: ... + @left_control_point_temperature.setter + def left_control_point_temperature(self, T: float) -> None: ... + @property + def right_control_point_temperature(self) -> float: ... + @right_control_point_temperature.setter + def right_control_point_temperature(self, T: float) -> None: ... + @property + def left_control_point_coordinate(self) -> float: ... + @property + def right_control_point_coordinate(self) -> float: ... + @property + def two_point_control_enabled(self) -> bool: ... + @two_point_control_enabled.setter + def two_point_control_enabled(self, enable: bool) -> None: ... + +class FreeFlow(FlowBase): ... +class UnstrainedFlow(FlowBase): ... +class AxisymmetricFlow(FlowBase): ... + +class Sim1D: + domains: tuple[Domain1D] + def __init__(self, domains: Sequence[Domain1D], *args: Any, **kwargs: Any) -> None: ... + def phase(self, domain: int | None = None) -> _SolutionBase: ... + def set_interrupt(self, f: Callable[[float], float]) -> None: ... + def set_time_step_callback(self, f: Callable[[float], float]) -> None: ... + def set_steady_callback(self, f: Callable[[float], float]) -> None: ... + def domain_index(self, dom: Domain1D | str | int) -> int: ... + def value( + self, domain: Domain1D | str | int, component: str | int, point: int + ) -> float: ... + def set_value( + self, + domain: Domain1D | str | int, + component: str | int, + point: int, + value: float, + ) -> None: ... + def eval(self, rdt: float = 0.0) -> None: ... + def work_value( + self, domain: Domain1D | str | int, component: str | int, point: int + ) -> float: ... + def profile(self, domain: Domain1D | str | int, component: str | int) -> Array: ... + def set_profile( + self, + domain: Domain1D | str | int, + component: str | int, + positions: Sequence[float], + values: Sequence[float], + ) -> None: ... + def set_flat_profile( + self, + domain: Domain1D | str | int, + component: str | int, + value: float, + ) -> None: ... + def show(self) -> None: ... + def set_time_step(self, stepsize: float, n_steps: Sequence[int]) -> None: ... + @property + def max_time_step_count(self) -> int: ... + @max_time_step_count.setter + def max_time_step_count(self, nmax: int) -> None: ... + def set_jacobian_perturbation( + self, relative: float, absolute: float, threshold: float + ) -> None: ... + @property + def linear_solver(self) -> SystemJacobian: ... + @linear_solver.setter + def linear_solver(self, precon: SystemJacobian) -> None: ... + def set_initial_guess(self, *args: Any, **kwargs: Any) -> None: ... + def extinct(self) -> bool: ... + def solve( + self, + loglevel: LogLevel = 1, + refine_grid: bool = True, + auto: bool = False, + ) -> None: ... + def refine(self, loglevel: LogLevel) -> None: ... + def set_refine_criteria( + self, + domain: Domain1D | str | int, + ratio: float = 10.0, + slope: float = 0.8, + curve: float = 0.8, + prune: float = 0.0, + ) -> None: ... + def get_refine_criteria(self, domain: Domain1D | str | int) -> RefineCriteria: ... + def set_grid_min( + self, dz: float, domain: Domain1D | str | int | None = None + ) -> None: ... + def set_max_jac_age(self, ss_age: int, ts_age: int) -> None: ... + def set_time_step_factor(self, tfactor: float) -> None: ... + def set_min_time_step(self, tsmin: float) -> None: ... + def set_max_time_step(self, tsmax: float) -> None: ... + @property + def fixed_temperature(self) -> float: ... + @fixed_temperature.setter + def fixed_temperature(self, T: float) -> None: ... + @property + def fixed_temperature_location(self) -> float: ... + def set_left_control_point(self, T: float) -> None: ... + def set_right_control_point(self, T: float) -> None: ... + def save( + self, + filename: str = "soln.yaml", + name: str = "solution", + description: str | None = None, + loglevel: LogLevel | None = None, + *, + overwrite: bool = False, + compression: CompressionLevel = 0, + basis: Basis | Literal["X", "Y"] | None = None, + ) -> None: ... + def restore( + self, + filename: str = "soln.yaml", + name: str = "solution", + loglevel: LogLevel | None = None, + ) -> dict[str, str]: ... + def restore_time_stepping_solution(self) -> None: ... + def restore_steady_solution(self) -> None: ... + def show_state(self, print_time: bool = True) -> None: ... + def clear_stats(self) -> None: ... + def solve_adjoint( + self, + perturb: Callable[[Sim1D, int, float], None], + n_params: int, + dgdx: Array, + g: Callable[[Sim1D], float] | None = None, + dp: float = 1e-5, + ) -> None: ... + @property + def grid_size_stats(self) -> list[int]: ... + @property + def jacobian_time_stats(self) -> list[float]: ... + @property + def jacobian_count_stats(self) -> list[int]: ... + @property + def eval_time_stats(self) -> list[float]: ... + @property + def eval_count_stats(self) -> list[int]: ... + @property + def time_step_stats(self) -> list[int]: ... + def set_max_grid_points(self, domain: Domain1D | str | int, npmax: int) -> None: ... + def get_max_grid_points(self, domain: Domain1D | str | int) -> int: ... diff --git a/interfaces/cython/cantera/_types.pyi b/interfaces/cython/cantera/_types.pyi new file mode 100644 index 00000000000..f63844cd3b1 --- /dev/null +++ b/interfaces/cython/cantera/_types.pyi @@ -0,0 +1,110 @@ +# This file is part of Cantera. See License.txt in the top-level directory or +# at https://cantera.org/license.txt for license and copyright information. + +from types import EllipsisType +from typing import Literal, TypeAlias, TypedDict + +import numpy as np +from numpy.typing import ArrayLike, NDArray + +__all__: list[str] = [ + "Array", + "ArrayLike", + "Basis", + "CompositionLike", + "CompositionVariable", + "CompressionLevel", + "EquilibriumSolver", + "FullState", + "Index", + "LogLevel", + "LogLevel7", + "StateDefinition", + "StateSetter", + "StateVariable", +] + +Array: TypeAlias = NDArray[np.float64] +Index: TypeAlias = EllipsisType | int | slice | tuple[EllipsisType | int | slice, ...] + +Basis: TypeAlias = Literal["mole", "molar", "mass"] +EquilibriumSolver: TypeAlias = Literal["element_potential", "gibbs", "vcs", "auto"] +LogLevel: TypeAlias = Literal[0, 1, 2, 3, 4, 5] +LogLevel7: TypeAlias = Literal[0, 1, 2, 3, 4, 5, 6, 7] +CompressionLevel: TypeAlias = Literal[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + +# ThermoPhase state definitions +StateVariable: TypeAlias = Literal["T", "D", "P", "U", "H", "S", "V"] +PropertyPair: TypeAlias = Literal["TP", "TV", "HP", "SP", "SV", "UV"] +CompositionVariable: TypeAlias = Literal["X", "Y"] +FullState: TypeAlias = Literal[ + "TDX", + "TDY", + "TPX", + "TPY", + "UVX", + "UVY", + "DPX", + "DPY", + "HPX", + "HPY", + "SPX", + "SPY", + "SVX", + "SVY", +] +CompositionLike: TypeAlias = str | dict[str, float] | ArrayLike +StateSetter: TypeAlias = tuple[float, float, CompositionLike] +ArrayCompositionLike: TypeAlias = str | dict[str, Array | float] | ArrayLike +ArrayStateSetter: TypeAlias = tuple[Array | float, Array | float, ArrayCompositionLike] + +# PureFluid state definitions +PureFluidStateVariable: TypeAlias = Literal[StateVariable, "Q"] +PureFluidPropertyPair: TypeAlias = Literal[ + PropertyPair, "TQ", "PQ", "ST", "TV", "PV", "UP", "VH", "TH", "SH" +] +PureFluidFullState: TypeAlias = Literal[ + FullState, "TDQ", "TPQ", "UVQ", "DPQ", "HPQ", "SPQ", "SVQ" +] +PureFluidStateSetter: TypeAlias = tuple[float, float, float] +ArrayPureFluidStateSetter: TypeAlias = tuple[ + Array | float, Array | float, Array | float +] + +RefineCriteria = TypedDict( + "RefineCriteria", + { + "ratio": float, + "slope": float, + "curve": float, + "prune": float, + "grid-min": float, + "max-points": int, + }, + total=False, +) + +class StateDefinition(TypedDict, total=False): + D: float + H: float + P: float + S: float + T: float + V: float + X: CompositionLike + Y: CompositionLike + + DPX: StateSetter + DPY: StateSetter + HPX: StateSetter + HPY: StateSetter + SPX: StateSetter + SPY: StateSetter + SVX: StateSetter + SVY: StateSetter + TDX: StateSetter + TDY: StateSetter + TPX: StateSetter + TPY: StateSetter + UVX: StateSetter + UVY: StateSetter diff --git a/interfaces/cython/cantera/_utils.pyi b/interfaces/cython/cantera/_utils.pyi new file mode 100644 index 00000000000..a78972af71e --- /dev/null +++ b/interfaces/cython/cantera/_utils.pyi @@ -0,0 +1,39 @@ +# This file is part of Cantera. See License.txt in the top-level directory or +# at https://cantera.org/license.txt for license and copyright information. + +from typing import Any + +from ._types import Array +from .units import UnitDictBytes, Units, UnitStack, UnitSystem + +def add_directory(directory: str) -> None: ... +def get_data_directories() -> list[str]: ... + +__sundials_version__: str +__version__: str +__git_commit__: str +_USE_SPARSE: bool + +def debug_mode_enabled() -> bool: ... +def print_stack_trace_on_segfault() -> None: ... +def appdelete() -> None: ... +def use_sparse(sparse: bool = True) -> None: ... +def suppress_deprecation_warnings() -> None: ... +def make_deprecation_warnings_fatal() -> None: ... +def suppress_thermo_warnings(suppress: bool = True) -> None: ... +def use_legacy_rate_constants(legacy: bool) -> None: ... +def hdf_support() -> set[str]: ... + +class CanteraError(RuntimeError): + @staticmethod + def set_stack_trace_depth(depth: int) -> None: ... + +class AnyMap(dict[str, Any]): + def default_units(self) -> UnitDictBytes: ... + @property + def units(self) -> UnitSystem: ... + def convert(self, key: str, dest: str | Units) -> float | Array | list[float]: ... + def convert_activation_energy(self, key: str, dest: str | Units) -> float: ... + def convert_rate_coeff(self, key: str, dest: str | Units | UnitStack) -> float: ... + def set_quantity(self, key: str, value: float, src: Units) -> None: ... + def set_activation_energy(self, key: str, value: float, src: Units) -> None: ... diff --git a/interfaces/cython/cantera/ck2yaml.pyi b/interfaces/cython/cantera/ck2yaml.pyi new file mode 100644 index 00000000000..60715565012 --- /dev/null +++ b/interfaces/cython/cantera/ck2yaml.pyi @@ -0,0 +1,424 @@ +# This file is part of Cantera. See License.txt in the top-level directory or +# at https://cantera.org/license.txt for license and copyright information. + +import logging +from argparse import ArgumentParser +from collections.abc import Callable, Sequence +from pathlib import Path +from typing import Any, ClassVar, Concatenate, Literal + +from ruamel.yaml.comments import CommentedMap, CommentedSeq +from ruamel.yaml.nodes import MappingNode, ScalarNode +from ruamel.yaml.representer import SafeRepresenter +from typing_extensions import Never + +from ._types import Array + +yaml_version: str +yaml_min_version: tuple[int, int, int] +BlockMap: CommentedMap + +class ErrorFormatter(logging.Formatter): + def format(self, record: logging.LogRecord) -> str: ... + +logger: logging.Logger + +def FlowMap(*args: Any, **kwargs: Any) -> CommentedMap: ... +def FlowList(*args: Any, **kwargs: Any) -> CommentedSeq: ... +def represent_float(self: SafeRepresenter, data: float) -> ScalarNode: ... + +QUANTITY_UNITS: dict[str, str] +ENERGY_UNITS: dict[str, str] + +def strip_nonascii(s: str) -> str: ... +def compatible_quantities( + quantity_basis: Literal["mol", "molec"], units: str +) -> bool: ... + +class InputError(Exception): + def __init__(self, message: str) -> None: ... + +class ParserLogger(logging.Handler): + parser: Parser + errors: list[str] + def __init__(self, parser: Parser) -> None: ... + def emit(self, record: logging.LogRecord) -> None: ... + +class Species: + label: str + thermo: Nasa7 | Nasa9 | None + transport: TransportData | None + sites: float | None + composition: dict[str, float] | None + note: str | None + def __init__(self, label: str, sites: float | None = None) -> None: ... + def __str__(self) -> str: ... + @classmethod + def to_yaml(cls, representer: SafeRepresenter, node: Species) -> dict[str, str]: ... + +class Nasa7: + Tmin: float + Tmax: float + Tmid: float | None + low_coeffs: Sequence[float] + high_coeffs: Sequence[float] | None + note: str + def __init__( + self, + *, + Tmin: float, + Tmax: float, + Tmid: float | None, + low_coeffs: Sequence[float], + high_coeffs: Sequence[float], + note: str = "", + ) -> None: ... + @classmethod + def to_yaml(cls, representer: SafeRepresenter, node: Nasa7) -> MappingNode: ... + +class Nasa9: + note: str + data: list[tuple[list[float], list[float]]] + Tranges: list[float] + def __init__( + self, + *, + parser: Parser, + data: list[tuple[list[float], list[float]]], + note: str = "", + ) -> None: ... + @classmethod + def to_yaml(cls, representer: SafeRepresenter, node: Nasa9) -> MappingNode: ... + +class Reaction: + index: int + reactants: list[tuple[float, Species]] + products: list[tuple[float, Species]] + kinetics: KineticsModel + reversible: bool + duplicate: bool + forward_orders: dict[str, float] + third_body: str + comment: str + def __init__( + self, + index: int = -1, + reactants: list[tuple[float, Species]] | None = None, + products: list[tuple[float, Species]] | None = None, + kinetics: KineticsModel | None = None, + reversible: bool = True, + duplicate: bool = False, + forward_orders: dict[str, float] | None = None, + third_body: str | None = None, + ) -> None: ... + def _coeff_string(self, coeffs: list[tuple[float, Species]]) -> str: ... + def __str__(self) -> str: ... + @classmethod + def to_yaml(cls, representer: SafeRepresenter, node: Reaction) -> MappingNode: ... + +class KineticsModel: + pressure_dependent: ClassVar[bool | None] = None + efficiencies: dict[str, float] + def __init__(self) -> None: ... + def reaction_string_suffix(self, species: Species) -> Literal[""]: ... + def reduce(self, output: CommentedMap) -> Never: ... + +class Arrhenius: + A: tuple[float, str] + b: float + Ea: tuple[float, str] + parser: Parser + def __init__( + self, + A: tuple[float, str] | float = 0.0, + b: float = 0.0, + Ea: tuple[float, str] | float = 0.0, + *, + parser: Parser, + ) -> None: ... + def as_yaml(self, extra: Any = ()) -> CommentedMap: ... + +class ElementaryRate(KineticsModel): + pressure_dependent: ClassVar[bool | None] = False + rate: Arrhenius + def __init__(self, rate: Arrhenius, **kwargs: Any) -> None: ... + def reduce(self, output: CommentedMap) -> None: ... # type: ignore[override] + +class SurfaceRate(KineticsModel): + pressure_dependent: ClassVar[bool | None] = False + rate: Arrhenius + coverages: list[tuple[str, float, float, float]] + is_sticking: bool + motz_wise: bool + def __init__( + self, + *, + rate: Arrhenius, + coverages: list[tuple[str, float, float, float]], + is_sticking: bool, + motz_wise: bool, + **kwargs: Any, + ) -> None: ... + def reduce(self, output: CommentedMap) -> None: ... # type: ignore[override] + +class PDepArrhenius(KineticsModel): + pressure_dependent: ClassVar[bool | None] = True + pressures: list[float] + pressure_units: str + arrhenius: list[Arrhenius] + def __init__( + self, + *, + pressures: list[float], + pressure_units: str, + arrhenius: list[Arrhenius], + **kwargs: Any, + ) -> None: ... + def reduce(self, output: CommentedMap) -> None: ... # type: ignore[override] + +class Chebyshev(KineticsModel): + pressure_dependent: ClassVar[bool | None] = True + Tmin: float + Tmax: float + Pmin: tuple[float, str] + Pmax: tuple[float, str] + coeffs: Array + quantity_units: str + def __init__( + self, + coeffs: Array, + *, + Tmin: float, + Tmax: float, + Pmin: tuple[float, str], + Pmax: tuple[float, str], + quantity_units: str, + **kwargs: Any, + ) -> None: ... + def reduce(self, output: CommentedMap) -> None: ... # type: ignore[override] + +class ThreeBody(KineticsModel): + pressure_dependent: ClassVar[bool | None] = True + high_rate: Arrhenius + efficiencies: dict[str, float] + def __init__( + self, + high_rate: Arrhenius | None = None, + efficiencies: dict[str, float] | None = None, + **kwargs: Any, + ) -> None: ... + def reaction_string_suffix(self, species: Species) -> Literal[" + M"]: ... # type: ignore[override] + def reduce(self, output: CommentedMap) -> None: ... # type: ignore[override] + +class Falloff(ThreeBody): + low_rate: Arrhenius + high_rate: Arrhenius + efficiencies: dict[str, float] + F: Troe | Sri + def __init__( + self, + low_rate: Arrhenius | None = None, + high_rate: Arrhenius | None = None, + efficiencies: dict[str, float] | None = None, + F: Troe | Sri | None = None, + **kwargs: Any, + ) -> None: ... + def reaction_string_suffix(self, species: Species) -> str: ... # type: ignore[override] + def reduce(self, output: CommentedMap) -> None: ... # type: ignore[override] + +class ChemicallyActivated(ThreeBody): + low_rate: Arrhenius + high_rate: Arrhenius + efficiencies: dict[str, float] + F: Troe | Sri + def __init__( + self, + low_rate: Arrhenius | None = None, + high_rate: Arrhenius | None = None, + efficiencies: dict[str, float] | None = None, + F: Troe | Sri | None = None, + **kwargs: Any, + ) -> None: ... + def reaction_string_suffix(self, species: Species) -> str: ... # type: ignore[override] + def reduce(self, output: CommentedMap) -> None: ... # type: ignore[override] + +class Troe: + A: float + T3: float + T1: float + T2: float | None + def __init__( + self, + A: float = 0.0, + T3: float = 0.0, + T1: float = 0.0, + T2: float | None = None, + ) -> None: ... + def reduce(self, output: CommentedMap) -> None: ... + +class Sri: + A: float + B: float + C: float + D: float | None + E: float | None + def __init__( + self, + *, + A: float, + B: float, + C: float, + D: float | None = None, + E: float | None = None, + ) -> None: ... + def reduce(self, output: CommentedMap) -> None: ... + +class TransportData: + geometry_flags: ClassVar[list[str]] = ["atom", "linear", "nonlinear"] + geometry: Literal["atom", "linear", "nonlinear"] + well_depth: float + collision_diameter: float + dipole_moment: float + polarizability: float + z_rot: float + note: str + def __init__( + self, + parser: Parser, + label: str, + geometry: Literal[0, 1, 2, "0", "1", "2"], + well_depth: str | float, + collision_diameter: str | float, + dipole_moment: str | float, + polarizability: str | float, + z_rot: str | float, + note: str = "", + ) -> None: ... + @classmethod + def to_yaml( + cls, representer: SafeRepresenter, node: TransportData + ) -> MappingNode: ... + +def fortFloat(s: str) -> float: ... +def get_index(seq: str | Sequence[str], value: str) -> int | None: ... +def contains(seq: str | Sequence[str], value: str) -> bool: ... + +class Surface: + name: str + site_density: float + species_list: list[Species] + reactions: list[Reaction] + def __init__(self, name: str, site_density: float) -> None: ... + +class Parser: + max_loglevel: int + handler: ParserLogger + processed_units: bool + energy_units: str + output_energy_units: str + quantity_units: str + output_quantity_units: str + motz_wise: bool | None + single_intermediate_temperature: bool + skip_undeclared_species: bool + exit_on_error: bool + permissive: bool + verbose: bool + elements: list[str] + element_weights: dict[str, float] + species_list: list[Species] + species_dict: dict[str, Species] + surfaces: list[Surface] + reactions: list[Reaction] + header_lines: list[str] + extra: CommentedMap + files: list[str] + raw_lines: list[str] + current_range: list[int] + def __init__(self) -> None: ... + def __del__(self) -> None: ... + def entry(self, where: str = "entry", kind: str = "Error") -> str: ... + @staticmethod + def parse_composition( + elements: str, nElements: int, width: int + ) -> dict[str, int]: ... + @staticmethod + def get_rate_constant_units( + length_dims: int, + length_units: str, + quantity_dims: int, + quantity_units: str, + time_dims: int = 1, + time_units: str = "s", + ) -> str: ... + def read_NASA7_entry( + self, lines: list[str], TintDefault: float, comments: list[str] + ) -> tuple[str, Nasa7, dict[str, float]]: ... + def parse_nasa7_section(self, lines: list[tuple[int, str, str, str]]) -> None: ... + def read_NASA9_entry( + self, entry: list[str], comments: list[str] + ) -> tuple[str, Nasa9, dict[str, float]]: ... + def read_kinetics_entry( + self, entry: list[str], surface: bool + ) -> tuple[Reaction, Reaction | None]: ... + def load_extra_file(self, path: Path | str) -> None: ... + def load_data_file( + self, + path: Path | str, + load_method: Callable[Concatenate[str, ...], None], + kind: str, + **kwargs: Any, + ) -> None: ... + def load_chemkin_file(self, path: Path | str, surface: bool = False) -> None: ... + def parse_elements_section( + self, lines: list[tuple[int, str, str, str]] + ) -> None: ... + def parse_species_section(self, lines: list[tuple[int, str, str, str]]) -> None: ... + def parse_site_section(self, lines: list[tuple[int, str, str, str]]) -> None: ... + def parse_nasa9_section(self, lines: list[tuple[int, str, str, str]]) -> None: ... + species_tokens: set[str] + other_tokens: dict[str, str] + Slen: int + def parse_reactions_section( + self, lines: list[tuple[int, str, str, str]], surface: bool + ) -> None: ... + def load_transport_file(self, path: Path | str) -> None: ... + def parse_transport_section( + self, lines: list[tuple[int, str, str, str]] + ) -> None: ... + def write_yaml( + self, name: str = "gas", out_name: str = "mech.yaml" + ) -> list[str]: ... + @staticmethod + def convert_mech( + input_file: Path | str, + thermo_file: Path | str | None = None, + transport_file: Path | str | None = None, + surface_file: Path | str | None = None, + phase_name: str = "gas", + extra_file: str | None = None, + out_name: str | None = None, + single_intermediate_temperature: bool = False, + quiet: bool = False, + permissive: bool | None = None, + verbose: bool = False, + exit_on_error: bool = False, + ) -> tuple[Parser, list[str]]: ... + def show_duplicate_reactions(self, error_message: str) -> None: ... + +def convert( + input_file: Path | str, + thermo_file: Path | str | None = None, + transport_file: Path | str | None = None, + surface_file: Path | str | None = None, + *, + phase_name: str = "gas", + extra_file: str | None = None, + out_name: Path | str | None = None, + single_intermediate_temperature: bool = False, + quiet: bool = False, + permissive: bool | None = None, + verbose: bool = False, +) -> list[str]: ... +def main(argv: Sequence[str] | None = None) -> None: ... +def create_argparser() -> ArgumentParser: ... diff --git a/interfaces/cython/cantera/composite.pyi b/interfaces/cython/cantera/composite.pyi new file mode 100644 index 00000000000..0cbfaf7faab --- /dev/null +++ b/interfaces/cython/cantera/composite.pyi @@ -0,0 +1,1257 @@ +# This file is part of Cantera. See License.txt in the top-level directory or +# at https://cantera.org/license.txt for license and copyright information. + +from collections.abc import Sequence +from typing import ( + Any, + Generic, + Literal, + TypeVar, + overload, +) + +from pandas import DataFrame +from typing_extensions import Never, Unpack, override + +from ._types import ( + Array, + ArrayCompositionLike, + ArrayLike, + ArrayPureFluidStateSetter, + ArrayStateSetter, + Basis, + CompositionLike, + CompressionLevel, + EquilibriumSolver, + Index, + LogLevel, + PropertyPair, + StateDefinition, + StateSetter, +) +from .kinetics import DerivativeSettings, InterfaceKinetics, Kinetics +from .reaction import Reaction +from .solutionbase import SolutionArrayBase, _SolutionBase +from .thermo import ( + InterfacePhase, + PhaseOfMatter, + PureFluid, + QuadratureMethod, + Species, + ThermoPhase, + ThermoType, +) +from .transport import ( + DustyGasTransport, + Transport, + TransportFittingErrors, + TransportModel, +) +from .units import Units + +# Generic representing a valid "phase" input +P = TypeVar("P", bound=_SolutionBase) + +class Solution(Transport, Kinetics, ThermoPhase): ... +class Interface(InterfaceKinetics, InterfacePhase): ... +class DustyGas(DustyGasTransport, Kinetics, ThermoPhase): ... + +class Quantity: + @property + def state(self) -> Array: ... + @state.setter + def state(self, state: ArrayLike) -> None: ... + mass: float + constant: PropertyPair + def __init__( + self, + phase: Solution, + mass: float | None = None, + moles: float | None = None, + constant: PropertyPair = "UV", + ) -> None: ... + @property + def phase(self) -> Solution: ... + @property + def moles(self) -> float: ... + @moles.setter + def moles(self, n: float) -> None: ... + @property + def volume(self) -> float: ... + @property + def int_energy(self) -> float: ... + @property + def enthalpy(self) -> float: ... + @property + def entropy(self) -> float: ... + @property + def gibbs(self) -> float: ... + def equilibrate( + self, + XY: PropertyPair | None = None, + solver: EquilibriumSolver = "auto", + rtol: float = 1e-9, + max_steps: int = 1000, + max_iter: int = 100, + estimate_equil: int = 0, + log_level: LogLevel = 0, + ) -> None: ... + def set_equivalence_ratio( + self, + phi: float, + fuel: CompositionLike, + oxidizer: CompositionLike, + basis: Basis = "mole", + *, + diluent: CompositionLike | None = None, + fraction: str + | dict[Literal["fuel", "oxidizer", "diluent"], float] + | None = None, + ) -> None: ... + def set_mixture_fraction( + self, + mixture_fraction: float, + fuel: CompositionLike, + oxidizer: CompositionLike, + basis: Basis = "mole", + ) -> None: ... + def __imul__(self, other: float) -> Quantity: ... + def __mul__(self, other: float) -> Quantity: ... + def __rmul__(self, other: float) -> Quantity: ... + def __iadd__(self, other: Quantity) -> Quantity: ... + def __add__(self, other: Quantity) -> Quantity: ... + # Synonyms for total properties + @property + def V(self) -> float: ... + @property + def U(self) -> float: ... + @property + def H(self) -> float: ... + @property + def S(self) -> float: ... + @property + def G(self) -> float: ... + # Dynamically-added properties acting as pass-throughs to Solution class + # From Transport: + @property + def transport_model(self) -> TransportModel: ... + @transport_model.setter + def transport_model(self, model: TransportModel) -> None: ... + @property + def CK_mode(self) -> bool: ... + @property + def viscosity(self) -> float: ... + @property + def species_viscosities(self) -> Array: ... + @property + def electrical_conductivity(self) -> float: ... + @property + def thermal_conductivity(self) -> float: ... + @property + def mix_diff_coeffs(self) -> Array: ... + @property + def mix_diff_coeffs_mass(self) -> Array: ... + @property + def mix_diff_coeffs_mole(self) -> Array: ... + @property + def thermal_diff_coeffs(self) -> Array: ... + @property + def multi_diff_coeffs(self) -> Array: ... + @property + def binary_diff_coeffs(self) -> Array: ... + @property + def mobilities(self) -> Array: ... + def get_viscosity_polynomial(self, i: int) -> Array: ... + def get_thermal_conductivity_polynomial(self, i: int) -> Array: ... + def get_binary_diff_coeffs_polynomial(self, i: int, j: int) -> Array: ... + def get_collision_integral_polynomials( + self, i: int, j: int + ) -> tuple[Array, Array, Array]: ... + def set_viscosity_polynomial(self, i: int, values: ArrayLike) -> None: ... + def set_thermal_conductivity_polynomial( + self, i: int, values: ArrayLike + ) -> None: ... + def set_binary_diff_coeffs_polynomial( + self, i: int, j: int, values: ArrayLike + ) -> None: ... + def set_collision_integral_polynomial( + self, + i: int, + j: int, + avalues: ArrayLike, + bvalues: ArrayLike, + cvalues: ArrayLike, + actualT: bool = False, + ) -> None: ... + @property + def transport_fitting_errors(self) -> TransportFittingErrors: ... + + # From Kinetics: + @property + def kinetics_model(self) -> str: ... + @property + def n_total_species(self) -> int: ... + @property + def n_reactions(self) -> int: ... + @property + def n_phases(self) -> int: ... + def kinetics_species_name(self, k: int) -> str: ... + @property + def kinetics_species_names(self) -> list[str]: ... + def reaction(self, i_reaction: int) -> Reaction: ... + def reactions(self) -> list[Reaction]: ... + def modify_reaction(self, irxn: int, rxn: Reaction) -> None: ... + def add_reaction(self, rxn: Reaction) -> None: ... + def multiplier(self, i_reaction: int) -> float: ... + def set_multiplier(self, value: float, i_reaction: int = -1) -> None: ... + def reaction_equations(self, indices: Sequence[int] | None = None) -> list[str]: ... + def reactant_stoich_coeff( + self, k_spec: str | bytes | int, i_reaction: int + ) -> float: ... + def product_stoich_coeff( + self, k_spec: str | bytes | int, i_reaction: int + ) -> float: ... + @property + def reactant_stoich_coeffs(self) -> Array: ... + @property + def product_stoich_coeffs(self) -> Array: ... + @property + def product_stoich_coeffs_reversible(self) -> Array: ... + @property + def forward_rates_of_progress(self) -> Array: ... + @property + def reverse_rates_of_progress(self) -> Array: ... + @property + def net_rates_of_progress(self) -> Array: ... + @property + def equilibrium_constants(self) -> Array: ... + @property + def forward_rate_constants(self) -> Array: ... + @property + def reverse_rate_constants(self) -> Array: ... + @property + def creation_rates(self) -> Array: ... + @property + def destruction_rates(self) -> Array: ... + @property + def net_production_rates(self) -> Array: ... + @property + def derivative_settings(self) -> DerivativeSettings: ... + @derivative_settings.setter + def derivative_settings(self, settings: DerivativeSettings) -> None: ... + @property + def forward_rate_constants_ddT(self) -> Array: ... + @property + def forward_rate_constants_ddP(self) -> Array: ... + @property + def forward_rate_constants_ddC(self) -> Array: ... + @property + def forward_rates_of_progress_ddT(self) -> Array: ... + @property + def forward_rates_of_progress_ddP(self) -> Array: ... + @property + def forward_rates_of_progress_ddC(self) -> Array: ... + @property + def forward_rates_of_progress_ddX(self) -> Array: ... + @property + def forward_rates_of_progress_ddCi(self) -> Array: ... + @property + def reverse_rates_of_progress_ddT(self) -> Array: ... + @property + def reverse_rates_of_progress_ddP(self) -> Array: ... + @property + def reverse_rates_of_progress_ddC(self) -> Array: ... + @property + def reverse_rates_of_progress_ddX(self) -> Array: ... + @property + def reverse_rates_of_progress_ddCi(self) -> Array: ... + @property + def net_rates_of_progress_ddT(self) -> Array: ... + @property + def net_rates_of_progress_ddP(self) -> Array: ... + @property + def net_rates_of_progress_ddC(self) -> Array: ... + @property + def net_rates_of_progress_ddX(self) -> Array: ... + @property + def net_rates_of_progress_ddCi(self) -> Array: ... + @property + def creation_rates_ddT(self) -> Array: ... + @property + def creation_rates_ddP(self) -> Array: ... + @property + def creation_rates_ddC(self) -> Array: ... + @property + def creation_rates_ddX(self) -> Array: ... + @property + def creation_rates_ddCi(self) -> Array: ... + @property + def destruction_rates_ddT(self) -> Array: ... + @property + def destruction_rates_ddP(self) -> Array: ... + @property + def destruction_rates_ddC(self) -> Array: ... + @property + def destruction_rates_ddX(self) -> Array: ... + @property + def destruction_rates_ddCi(self) -> Array: ... + @property + def net_production_rates_ddT(self) -> Array: ... + @property + def net_production_rates_ddP(self) -> Array: ... + @property + def net_production_rates_ddC(self) -> Array: ... + @property + def net_production_rates_ddX(self) -> Array: ... + @property + def net_production_rates_ddCi(self) -> Array: ... + @property + def delta_enthalpy(self) -> Array: ... + @property + def delta_gibbs(self) -> Array: ... + @property + def delta_entropy(self) -> Array: ... + @property + def delta_standard_enthalpy(self) -> Array: ... + @property + def third_body_concentrations(self) -> Array: ... + @property + def delta_standard_gibbs(self) -> Array: ... + @property + def delta_standard_entropy(self) -> Array: ... + @property + def heat_release_rate(self) -> float: ... + @property + def heat_production_rates(self) -> Array: ... + + # From ThermoPhase: + @property + def thermo_model(self) -> ThermoType: ... + @property + def phase_of_matter(self) -> PhaseOfMatter: ... + def report(self, show_thermo: bool = True, threshold: float = 1e-14) -> str: ... + @property + def is_pure(self) -> bool: ... + @property + def has_phase_transition(self) -> bool: ... + @property + def is_compressible(self) -> bool: ... + @property + def basis(self) -> Basis: ... + @basis.setter + def basis(self, value: Basis) -> None: ... + ####### Composition, species, and elements ######## + @property + def n_elements(self) -> int: ... + def element_index(self, element: str | bytes | float) -> int: ... + def element_name(self, m: int) -> str: ... + @property + def element_names(self) -> list[str]: ... + def atomic_weight(self, m: int) -> float: ... + @property + def atomic_weights(self) -> Array: ... + @property + def n_species(self) -> int: ... + @property + def n_selected_species(self) -> int: ... + def species_name(self, k: int) -> str: ... + @property + def species_names(self) -> list[str]: ... + def species_index(self, species: str | bytes | float) -> int: ... + @property + def case_sensitive_species_names(self) -> bool: ... + @case_sensitive_species_names.setter + def case_sensitive_species_names(self, val: bool) -> None: ... + @overload + def species(self, k: None = None) -> list[Species]: ... + @overload + def species(self, k: str | bytes | float) -> Species: ... + def modify_species(self, k: int, species: Species) -> None: ... + def add_species(self, species: Species) -> None: ... + def add_species_alias(self, name: str, alias: str) -> None: ... + def find_isomers(self, comp: CompositionLike) -> list[str]: ... + def n_atoms( + self, species: str | bytes | float, element: str | bytes | float + ) -> int: ... + @property + def molecular_weights(self) -> Array: ... + @property + def charges(self) -> Array: ... + @property + def mean_molecular_weight(self) -> float: ... + @property + def Y(self) -> Array: ... + @Y.setter + def Y(self, Y: CompositionLike) -> None: ... + @property + def X(self) -> Array: ... + @X.setter + def X(self, X: CompositionLike) -> None: ... + @property + def concentrations(self) -> Array: ... + @concentrations.setter + def concentrations(self, C: ArrayLike) -> None: ... + def equivalence_ratio( + self, + fuel: CompositionLike, + oxidizer: CompositionLike, + basis: Basis = "mole", + include_species: list[str | bytes | float] | None = None, + ) -> float: ... + def mixture_fraction( + self, + fuel: CompositionLike, + oxidizer: CompositionLike, + basis: Basis = "mole", + element: str | bytes | float = "Bilger", + ) -> float: ... + def stoich_air_fuel_ratio( + self, + fuel: CompositionLike, + oxidizer: CompositionLike, + basis: Basis = "mole", + ) -> float: ... + def elemental_mass_fraction(self, m: str | bytes | float) -> float: ... + def elemental_mole_fraction(self, m: str | bytes | float) -> float: ... + def mass_fraction_dict(self, threshold: float = 0.0) -> dict[str, float]: ... + def mole_fraction_dict(self, threshold: float = 0.0) -> dict[str, float]: ... + ######## Read-only thermodynamic properties ######## + @property + def P(self) -> float: ... + @property + def T(self) -> float: ... + @property + def density(self) -> float: ... + @property + def density_mass(self) -> float: ... + @property + def density_mole(self) -> float: ... + @property + def v(self) -> float: ... + @property + def volume_mass(self) -> float: ... + @property + def volume_mole(self) -> float: ... + @property + def u(self) -> float: ... + @property + def int_energy_mole(self) -> float: ... + @property + def int_energy_mass(self) -> float: ... + @property + def h(self) -> float: ... + @property + def enthalpy_mole(self) -> float: ... + @property + def enthalpy_mass(self) -> float: ... + @property + def s(self) -> float: ... + @property + def entropy_mole(self) -> float: ... + @property + def entropy_mass(self) -> float: ... + @property + def g(self) -> float: ... + @property + def gibbs_mole(self) -> float: ... + @property + def gibbs_mass(self) -> float: ... + @property + def cv(self) -> float: ... + @property + def cv_mole(self) -> float: ... + @property + def cv_mass(self) -> float: ... + @property + def cp(self) -> float: ... + @property + def cp_mole(self) -> float: ... + @property + def cp_mass(self) -> float: ... + @property + def critical_temperature(self) -> float: ... + @property + def critical_pressure(self) -> float: ... + @property + def critical_density(self) -> float: ... + @property + def P_sat(self) -> float: ... + @property + def T_sat(self) -> float: ... + @property + def auxiliary_data(self) -> dict[str, Any]: ... + ######## Methods to get/set the complete thermodynamic state ######## + @property + def state_size(self) -> int: ... + @property + def TD(self) -> tuple[float, float]: ... + @TD.setter + def TD(self, values: Sequence[float]) -> None: ... + @property + def TDX(self) -> tuple[float, float, Array]: ... + @TDX.setter + def TDX(self, values: StateSetter) -> None: ... + @property + def TDY(self) -> tuple[float, float, Array]: ... + @TDY.setter + def TDY(self, values: StateSetter) -> None: ... + @property + def TP(self) -> tuple[float, float]: ... + @TP.setter + def TP(self, values: Sequence[float]) -> None: ... + @property + def TPX(self) -> tuple[float, float, Array]: ... + @TPX.setter + def TPX(self, values: StateSetter) -> None: ... + @property + def TPY(self) -> tuple[float, float, Array]: ... + @TPY.setter + def TPY(self, values: StateSetter) -> None: ... + @property + def UV(self) -> tuple[float, float]: ... + @UV.setter + def UV(self, values: Sequence[float]) -> None: ... + @property + def UVX(self) -> tuple[float, float, Array]: ... + @UVX.setter + def UVX(self, values: StateSetter) -> None: ... + @property + def UVY(self) -> tuple[float, float, Array]: ... + @UVY.setter + def UVY(self, values: StateSetter) -> None: ... + @property + def DP(self) -> tuple[float, float]: ... + @DP.setter + def DP(self, values: Sequence[float]) -> None: ... + @property + def DPX(self) -> tuple[float, float, Array]: ... + @DPX.setter + def DPX(self, values: StateSetter) -> None: ... + @property + def DPY(self) -> tuple[float, float, Array]: ... + @DPY.setter + def DPY(self, values: StateSetter) -> None: ... + @property + def HP(self) -> tuple[float, float]: ... + @HP.setter + def HP(self, values: Sequence[float]) -> None: ... + @property + def HPX(self) -> tuple[float, float, Array]: ... + @HPX.setter + def HPX(self, values: StateSetter) -> None: ... + @property + def HPY(self) -> tuple[float, float, Array]: ... + @HPY.setter + def HPY(self, values: StateSetter) -> None: ... + @property + def SP(self) -> tuple[float, float]: ... + @SP.setter + def SP(self, values: Sequence[float]) -> None: ... + @property + def SPX(self) -> tuple[float, float, Array]: ... + @SPX.setter + def SPX(self, values: StateSetter) -> None: ... + @property + def SPY(self) -> tuple[float, float, Array]: ... + @SPY.setter + def SPY(self, values: StateSetter) -> None: ... + @property + def SV(self) -> tuple[float, float]: ... + @SV.setter + def SV(self, values: Sequence[float]) -> None: ... + @property + def SVX(self) -> tuple[float, float, Array]: ... + @SVX.setter + def SVX(self, values: StateSetter) -> None: ... + @property + def SVY(self) -> tuple[float, float, Array]: ... + @SVY.setter + def SVY(self, values: StateSetter) -> None: ... + # partial molar / non-dimensional properties + @property + def partial_molar_enthalpies(self) -> Array: ... + @property + def partial_molar_entropies(self) -> Array: ... + @property + def partial_molar_int_energies(self) -> Array: ... + @property + def chemical_potentials(self) -> Array: ... + @property + def electrochemical_potentials(self) -> Array: ... + @property + def partial_molar_cp(self) -> Array: ... + @property + def partial_molar_volumes(self) -> Array: ... + @property + def standard_enthalpies_RT(self) -> Array: ... + @property + def standard_entropies_R(self) -> Array: ... + @property + def standard_int_energies_RT(self) -> Array: ... + @property + def standard_gibbs_RT(self) -> Array: ... + @property + def standard_cp_R(self) -> Array: ... + @property + def activities(self) -> Array: ... + @property + def activity_coefficients(self) -> Array: ... + ######## Miscellaneous properties ######## + @property + def isothermal_compressibility(self) -> float: ... + @property + def thermal_expansion_coeff(self) -> float: ... + @property + def sound_speed(self) -> float: ... + @property + def min_temp(self) -> float: ... + @property + def max_temp(self) -> float: ... + @property + def reference_pressure(self) -> float: ... + @property + def electric_potential(self) -> float: ... + @electric_potential.setter + def electric_potential(self, value: float) -> None: ... + @property + def standard_concentration_units(self) -> Units: ... + # methods for plasma + @property + def Te(self) -> float: ... + @Te.setter + def Te(self, value: float) -> None: ... + @property + def Pe(self) -> float: ... + def set_discretized_electron_energy_distribution( + self, levels: ArrayLike, distribution: ArrayLike + ) -> None: ... + @property + def n_electron_energy_levels(self) -> int: ... + @property + def electron_energy_levels(self) -> Array: ... + @electron_energy_levels.setter + def electron_energy_levels(self, levels: Array) -> None: ... + @property + def electron_energy_distribution(self) -> Array: ... + @property + def isotropic_shape_factor(self) -> float: ... + @isotropic_shape_factor.setter + def isotropic_shape_factor(self, x: float) -> None: ... + @property + def electron_energy_distribution_type(self) -> str: ... + @electron_energy_distribution_type.setter + def electron_energy_distribution_type(self, distribution_type: str) -> None: ... + @property + def mean_electron_energy(self) -> float: ... + @mean_electron_energy.setter + def mean_electron_energy(self, energy: float) -> None: ... + @property + def quadrature_method(self) -> QuadratureMethod: ... + @quadrature_method.setter + def quadrature_method(self, method: QuadratureMethod) -> None: ... + @property + def normalize_electron_energy_distribution_enabled(self) -> bool: ... + @normalize_electron_energy_distribution_enabled.setter + def normalize_electron_energy_distribution_enabled(self, enable: bool) -> None: ... + @property + def electron_species_name(self) -> str: ... + @property + def elastic_power_loss(self) -> float: ... + +class SolutionArray(SolutionArrayBase, Generic[P]): + _phase: P + def __init__( + self, + phase: P, + shape: int | tuple[int, ...] = (0,), + states: ArrayLike | None = None, + extra: str | Sequence[str] | dict[str, ArrayLike] | None = None, + meta: dict[str, Any] = {}, + init: bool = True, + ) -> None: ... + def __getitem__(self, index: Index) -> SolutionArray[P]: ... + def __call__(self, *species: str) -> SolutionArray[P]: ... + @property + def ndim(self) -> int: ... + @property + def shape(self) -> tuple[int, ...]: ... + @shape.setter + def shape(self, shp: tuple[int, ...]) -> None: ... + def append( + self, + state: ArrayLike | None = None, + normalize: bool = True, + **kwargs: Unpack[StateDefinition], + ) -> None: ... + def sort(self, col: str, reverse: bool = False) -> None: ... + def equilibrate( + self, + XY: PropertyPair | None = None, + solver: EquilibriumSolver = "auto", + rtol: float = 1e-9, + max_steps: int = 1000, + max_iter: int = 100, + estimate_equil: int = 0, + log_level: LogLevel = 0, + ) -> None: ... + def restore_data(self, data: dict[str, Array], normalize: bool = True) -> None: ... + def set_equivalence_ratio( + self, + phi: float | Array, + fuel: CompositionLike, + oxidizer: CompositionLike, + basis: Basis = "mole", + *, + diluent: CompositionLike | None = None, + fraction: str + | dict[Literal["fuel", "oxidizer", "diluent"], float | Array] + | None = None, + ) -> None: ... + def set_mixture_fraction( + self, + mixture_fraction: float | Array, + fuel: CompositionLike, + oxidizer: CompositionLike, + basis: Basis = "mole", + ) -> None: ... + def collect_data( + self, + cols: Sequence[str] | None = None, + tabular: bool = False, + threshold: int = 0, + species: Literal["X", "Y"] | None = None, + ) -> dict[str, Array]: ... + def read_csv(self, filename: str, normalize: bool = True) -> None: ... + def to_pandas( + self, + cols: Sequence[str] | None = None, + tabular: bool = False, + threshold: int = 0, + species: Literal["X", "Y"] | None = None, + ) -> DataFrame: ... + def from_pandas(self, df: DataFrame, normalize: bool = True) -> None: ... + def save( + self, + fname: str, + name: str | None = None, + sub: str | None = None, + description: str | None = None, + *, + overwrite: bool = False, + compression: CompressionLevel = 0, + basis: Basis | None = None, + ) -> None: ... + def restore( + self, fname: str, name: str | None = None, sub: str | None = None + ) -> None: ... + @override + def __reduce__(self) -> Never: ... + def __copy__(self) -> Never: ... + + # Dynamically-added properties acting as pass-throughs to underlying Solution class + # State setters/getters + @property + def TD(self) -> tuple[Array, Array]: ... + @TD.setter + def TD(self, values: Sequence[Array | float]) -> None: ... + @property + def TDX(self) -> tuple[Array, Array, Array]: ... + @TDX.setter + def TDX(self, values: ArrayStateSetter) -> None: ... + @property + def TDY(self) -> tuple[Array, Array, Array]: ... + @TDY.setter + def TDY(self, values: ArrayStateSetter) -> None: ... + @property + def TP(self) -> tuple[Array, Array]: ... + @TP.setter + def TP(self, values: Sequence[Array | float]) -> None: ... + @property + def TPX(self) -> tuple[Array, Array, Array]: ... + @TPX.setter + def TPX(self, values: ArrayStateSetter) -> None: ... + @property + def TPY(self) -> tuple[Array, Array, Array]: ... + @TPY.setter + def TPY(self, values: ArrayStateSetter) -> None: ... + @property + def UV(self) -> tuple[Array, Array]: ... + @UV.setter + def UV(self, values: Sequence[Array | float]) -> None: ... + @property + def UVX(self) -> tuple[Array, Array, Array]: ... + @UVX.setter + def UVX(self, values: ArrayStateSetter) -> None: ... + @property + def UVY(self) -> tuple[Array, Array, Array]: ... + @UVY.setter + def UVY(self, values: ArrayStateSetter) -> None: ... + @property + def DP(self) -> tuple[Array, Array]: ... + @DP.setter + def DP(self, values: Sequence[Array | float]) -> None: ... + @property + def DPX(self) -> tuple[Array, Array, Array]: ... + @DPX.setter + def DPX(self, values: ArrayStateSetter) -> None: ... + @property + def DPY(self) -> tuple[Array, Array, Array]: ... + @DPY.setter + def DPY(self, values: ArrayStateSetter) -> None: ... + @property + def HP(self) -> tuple[Array, Array]: ... + @HP.setter + def HP(self, values: Sequence[Array | float]) -> None: ... + @property + def HPX(self) -> tuple[Array, Array, Array]: ... + @HPX.setter + def HPX(self, values: ArrayStateSetter) -> None: ... + @property + def HPY(self) -> tuple[Array, Array, Array]: ... + @HPY.setter + def HPY(self, values: ArrayStateSetter) -> None: ... + @property + def SP(self) -> tuple[Array, Array]: ... + @SP.setter + def SP(self, values: Sequence[Array | float]) -> None: ... + @property + def SPX(self) -> tuple[Array, Array, Array]: ... + @SPX.setter + def SPX(self, values: ArrayStateSetter) -> None: ... + @property + def SPY(self) -> tuple[Array, Array, Array]: ... + @SPY.setter + def SPY(self, values: ArrayStateSetter) -> None: ... + @property + def SV(self) -> tuple[Array, Array]: ... + @SV.setter + def SV(self, values: Sequence[Array | float]) -> None: ... + @property + def SVX(self) -> tuple[Array, Array, Array]: ... + @SVX.setter + def SVX(self, values: ArrayStateSetter) -> None: ... + @property + def SVY(self) -> tuple[Array, Array, Array]: ... + @SVY.setter + def SVY(self, values: ArrayStateSetter) -> None: ... + @property + def TQ(self) -> tuple[Array, Array]: ... + @TQ.setter + def TQ(self, values: Sequence[Array | float]) -> None: ... + @property + def PQ(self) -> tuple[Array, Array]: ... + @PQ.setter + def PQ(self, values: Sequence[Array | float]) -> None: ... + @property + def ST(self) -> tuple[Array, Array]: ... + @ST.setter + def ST(self, values: Sequence[Array | float]) -> None: ... + @property + def TV(self) -> tuple[Array, Array]: ... + @TV.setter + def TV(self, values: Sequence[Array | float]) -> None: ... + @property + def PV(self) -> tuple[Array, Array]: ... + @PV.setter + def PV(self, values: Sequence[Array | float]) -> None: ... + @property + def UP(self) -> tuple[Array, Array]: ... + @UP.setter + def UP(self, values: Sequence[Array | float]) -> None: ... + @property + def VH(self) -> tuple[Array, Array]: ... + @VH.setter + def VH(self, values: Sequence[Array | float]) -> None: ... + @property + def TH(self) -> tuple[Array, Array]: ... + @TH.setter + def TH(self, values: Sequence[Array | float]) -> None: ... + @property + def SH(self) -> tuple[Array, Array]: ... + @SH.setter + def SH(self, values: Sequence[Array | float]) -> None: ... + @property + def TDQ(self) -> tuple[Array, Array, Array]: ... + @TDQ.setter + def TDQ( + self: SolutionArray[PureFluid], values: ArrayPureFluidStateSetter + ) -> None: ... + @property + def TPQ(self) -> tuple[Array, Array, Array]: ... + @TPQ.setter + def TPQ( + self: SolutionArray[PureFluid], values: ArrayPureFluidStateSetter + ) -> None: ... + @property + def UVQ(self) -> tuple[Array, Array, Array]: ... + @UVQ.setter + def UVQ( + self: SolutionArray[PureFluid], values: ArrayPureFluidStateSetter + ) -> None: ... + @property + def DPQ(self) -> tuple[Array, Array, Array]: ... + @DPQ.setter + def DPQ( + self: SolutionArray[PureFluid], values: ArrayPureFluidStateSetter + ) -> None: ... + @property + def HPQ(self) -> tuple[Array, Array, Array]: ... + @HPQ.setter + def HPQ( + self: SolutionArray[PureFluid], values: ArrayPureFluidStateSetter + ) -> None: ... + @property + def SPQ(self) -> tuple[Array, Array, Array]: ... + @SPQ.setter + def SPQ( + self: SolutionArray[PureFluid], values: ArrayPureFluidStateSetter + ) -> None: ... + @property + def SVQ(self) -> tuple[Array, Array, Array]: ... + @SVQ.setter + def SVQ( + self: SolutionArray[PureFluid], values: ArrayPureFluidStateSetter + ) -> None: ... + + # scalar + # From ThermoPhase + @property + def mean_molecular_weight(self) -> Array: ... + @property + def P(self) -> Array: ... + @property + def T(self) -> Array: ... + @property + def Te(self) -> Array: ... + @Te.setter + def Te(self, value: ArrayLike) -> None: ... + @property + def density(self) -> Array: ... + @property + def density_mass(self) -> Array: ... + @property + def density_mole(self) -> Array: ... + @property + def v(self) -> Array: ... + @property + def volume_mass(self) -> Array: ... + @property + def volume_mole(self) -> Array: ... + @property + def u(self) -> Array: ... + @property + def int_energy_mole(self) -> Array: ... + @property + def int_energy_mass(self) -> Array: ... + @property + def h(self) -> Array: ... + @property + def enthalpy_mole(self) -> Array: ... + @property + def enthalpy_mass(self) -> Array: ... + @property + def s(self) -> Array: ... + @property + def entropy_mole(self) -> Array: ... + @property + def entropy_mass(self) -> Array: ... + @property + def g(self) -> Array: ... + @property + def gibbs_mole(self) -> Array: ... + @property + def gibbs_mass(self) -> Array: ... + @property + def cv(self) -> Array: ... + @property + def cv_mole(self) -> Array: ... + @property + def cv_mass(self) -> Array: ... + @property + def cp(self) -> Array: ... + @property + def cp_mole(self) -> Array: ... + @property + def cp_mass(self) -> Array: ... + @property + def critical_temperature(self) -> Array: ... + @property + def critical_pressure(self) -> Array: ... + @property + def critical_density(self) -> Array: ... + @property + def P_sat(self) -> Array: ... + @property + def T_sat(self) -> Array: ... + @property + def isothermal_compressibility(self) -> Array: ... + @property + def thermal_expansion_coeff(self) -> Array: ... + @property + def sound_speed(self) -> Array: ... + @property + def electric_potential(self) -> Array: ... + # From Kinetics + @property + def heat_release_rate(self: SolutionArray[Kinetics]) -> Array: ... + # From Transport + @property + def viscosity(self) -> Array: ... + @property + def electrical_conductivity(self) -> Array: ... + @property + def thermal_conductivity(self) -> Array: ... + # strings + @property + def phase_of_matter(self) -> PhaseOfMatter: ... + + # n_species + # from ThermoPhase + @property + def Y(self) -> Array: ... + @Y.setter + def Y(self, Y: ArrayCompositionLike) -> None: ... + @property + def X(self) -> Array: ... + @X.setter + def X(self, X: ArrayCompositionLike) -> None: ... + @property + def concentrations(self) -> Array: ... + @property + def partial_molar_enthalpies(self) -> Array: ... + @property + def partial_molar_entropies(self) -> Array: ... + @property + def partial_molar_int_energies(self) -> Array: ... + @property + def chemical_potentials(self) -> Array: ... + @property + def electrochemical_potentials(self) -> Array: ... + @property + def partial_molar_cp(self) -> Array: ... + @property + def partial_molar_volumes(self) -> Array: ... + @property + def standard_enthalpies_RT(self) -> Array: ... + @property + def standard_entropies_R(self) -> Array: ... + @property + def standard_int_energies_RT(self) -> Array: ... + @property + def standard_gibbs_RT(self) -> Array: ... + @property + def standard_cp_R(self) -> Array: ... + @property + def activities(self) -> Array: ... + @property + def activity_coefficients(self) -> Array: ... + # From Transport + @property + def mix_diff_coeffs(self) -> Array: ... + @property + def mix_diff_coeffs_mass(self) -> Array: ... + @property + def mix_diff_coeffs_mole(self) -> Array: ... + @property + def thermal_diff_coeffs(self) -> Array: ... + @property + def mobilities(self) -> Array: ... + @property + def species_viscosities(self) -> Array: ... + + # n_total_species + @property + def creation_rates(self: SolutionArray[Kinetics]) -> Array: ... + @property + def destruction_rates(self: SolutionArray[Kinetics]) -> Array: ... + @property + def net_production_rates(self: SolutionArray[Kinetics]) -> Array: ... + @property + def creation_rates_ddC(self: SolutionArray[Kinetics]) -> Array: ... + @property + def creation_rates_ddP(self: SolutionArray[Kinetics]) -> Array: ... + @property + def creation_rates_ddT(self: SolutionArray[Kinetics]) -> Array: ... + @property + def destruction_rates_ddC(self: SolutionArray[Kinetics]) -> Array: ... + @property + def destruction_rates_ddP(self: SolutionArray[Kinetics]) -> Array: ... + @property + def destruction_rates_ddT(self: SolutionArray[Kinetics]) -> Array: ... + @property + def net_production_rates_ddC(self: SolutionArray[Kinetics]) -> Array: ... + @property + def net_production_rates_ddP(self: SolutionArray[Kinetics]) -> Array: ... + @property + def net_production_rates_ddT(self: SolutionArray[Kinetics]) -> Array: ... + + # n_species2 + @property + def multi_diff_coeffs(self: SolutionArray[Kinetics]) -> Array: ... + @property + def binary_diff_coeffs(self: SolutionArray[Kinetics]) -> Array: ... + @property + def creation_rates_ddX(self: SolutionArray[Kinetics]) -> Array: ... + @property + def destruction_rates_ddX(self: SolutionArray[Kinetics]) -> Array: ... + @property + def net_production_rates_ddX(self: SolutionArray[Kinetics]) -> Array: ... + @property + def creation_rates_ddCi(self: SolutionArray[Kinetics]) -> Array: ... + @property + def destruction_rates_ddCi(self: SolutionArray[Kinetics]) -> Array: ... + @property + def net_production_rates_ddCi(self: SolutionArray[Kinetics]) -> Array: ... + + # n_reactions + @property + def forward_rates_of_progress(self: SolutionArray[Kinetics]) -> Array: ... + @property + def reverse_rates_of_progress(self: SolutionArray[Kinetics]) -> Array: ... + @property + def net_rates_of_progress(self: SolutionArray[Kinetics]) -> Array: ... + @property + def equilibrium_constants(self: SolutionArray[Kinetics]) -> Array: ... + @property + def forward_rate_constants(self: SolutionArray[Kinetics]) -> Array: ... + @property + def reverse_rate_constants(self: SolutionArray[Kinetics]) -> Array: ... + @property + def delta_enthalpy(self: SolutionArray[Kinetics]) -> Array: ... + @property + def delta_gibbs(self: SolutionArray[Kinetics]) -> Array: ... + @property + def delta_entropy(self: SolutionArray[Kinetics]) -> Array: ... + @property + def delta_standard_enthalpy(self: SolutionArray[Kinetics]) -> Array: ... + @property + def delta_standard_gibbs(self: SolutionArray[Kinetics]) -> Array: ... + @property + def delta_standard_entropy(self: SolutionArray[Kinetics]) -> Array: ... + @property + def heat_production_rates(self: SolutionArray[Kinetics]) -> Array: ... + @property + def forward_rate_constants_ddC(self: SolutionArray[Kinetics]) -> Array: ... + @property + def forward_rate_constants_ddP(self: SolutionArray[Kinetics]) -> Array: ... + @property + def forward_rate_constants_ddT(self: SolutionArray[Kinetics]) -> Array: ... + @property + def forward_rates_of_progress_ddC(self: SolutionArray[Kinetics]) -> Array: ... + @property + def forward_rates_of_progress_ddP(self: SolutionArray[Kinetics]) -> Array: ... + @property + def forward_rates_of_progress_ddT(self: SolutionArray[Kinetics]) -> Array: ... + @property + def net_rates_of_progress_ddC(self: SolutionArray[Kinetics]) -> Array: ... + @property + def net_rates_of_progress_ddP(self: SolutionArray[Kinetics]) -> Array: ... + @property + def net_rates_of_progress_ddT(self: SolutionArray[Kinetics]) -> Array: ... + @property + def reverse_rates_of_progress_ddC(self: SolutionArray[Kinetics]) -> Array: ... + @property + def reverse_rates_of_progress_ddP(self: SolutionArray[Kinetics]) -> Array: ... + @property + def reverse_rates_of_progress_ddT(self: SolutionArray[Kinetics]) -> Array: ... + @property + def third_body_concentrations(self: SolutionArray[Kinetics]) -> Array: ... + # call_scalar + def elemental_mass_fraction(self, m: str | bytes | float) -> Array: ... + def elemental_mole_fraction(self, m: str | bytes | float) -> Array: ... + + # passthrough + # from ThermoPhase + @property + def name(self) -> str: ... + @property + def source(self) -> str: ... + @property + def basis(self) -> Basis: ... + @property + def n_elements(self) -> int: ... + def element_index(self, element: str | bytes | float) -> int: ... + def element_name(self, m: int) -> str: ... + @property + def element_names(self) -> list[str]: ... + def atomic_weight(self, m: int) -> float: ... + @property + def atomic_weights(self) -> Array: ... + @property + def n_species(self) -> int: ... + def species_name(self, k: int) -> str: ... + @property + def species_names(self) -> list[str]: ... + def species_index(self, species: str | bytes | float) -> int: ... + @overload + def species(self, k: None = None) -> list[Species]: ... + @overload + def species(self, k: str | bytes | float) -> Species: ... + @overload + def species( + self, k: str | bytes | float | None = None + ) -> Species | list[Species]: ... + def n_atoms( + self, species: str | bytes | float, element: str | bytes | float + ) -> int: ... + @property + def molecular_weights(self) -> Array: ... + @property + def min_temp(self) -> float: ... + @property + def max_temp(self) -> float: ... + @property + def reference_pressure(self) -> float: ... + @property + def charges(self) -> Array: ... + # From Kinetics + @property + def n_total_species(self: SolutionArray[Kinetics]) -> int: ... + @property + def n_reactions(self: SolutionArray[Kinetics]) -> int: ... + @property + def n_phases(self: SolutionArray[Kinetics]) -> int: ... + def kinetics_species_index( + self: SolutionArray[Kinetics], species: str | bytes | int, phase: int = 0 + ) -> int: ... + def reaction(self: SolutionArray[Kinetics], i_reaction: int) -> Reaction: ... + def reactions(self: SolutionArray[Kinetics]) -> list[Reaction]: ... + def modify_reaction(self: SolutionArray[Kinetics], irxn: int, rxn: Reaction) -> None: ... + def multiplier(self: SolutionArray[Kinetics], i_reaction: int) -> float: ... + def set_multiplier(self: SolutionArray[Kinetics], value: float, i_reaction: int = -1) -> None: ... + def reaction_equations(self: SolutionArray[Kinetics], indices: Sequence[int] | None = None) -> list[str]: ... + def reactant_stoich_coeff( + self: SolutionArray[Kinetics], k_spec: str | bytes | int, i_reaction: int + ) -> float: ... + def product_stoich_coeff( + self: SolutionArray[Kinetics], k_spec: str | bytes | int, i_reaction: int + ) -> float: ... + @property + def reactant_stoich_coeffs(self: SolutionArray[Kinetics]) -> Array: ... + @property + def product_stoich_coeffs(self: SolutionArray[Kinetics]) -> Array: ... + @property + def product_stoich_coeffs_reversible(self: SolutionArray[Kinetics]) -> Array: ... + # from Transport + @property + def transport_model(self) -> str: ... + + # interface_passthrough + @property + def site_density(self: SolutionArray[Interface]) -> Array: ... + @site_density.setter + def site_density(self: SolutionArray[Interface], value: Array) -> None: ... + + # interface_n_species + @property + def coverages(self: SolutionArray[Interface]) -> Array: ... + @coverages.setter + def coverages( + self: SolutionArray[Interface], theta: ArrayCompositionLike + ) -> None: ... + + # purefluid_scalar + @property + def Q(self: SolutionArray[PureFluid]) -> Array: ... + @Q.setter + def Q(self: SolutionArray[PureFluid], Q: Array) -> None: ... diff --git a/interfaces/cython/cantera/constants.pyi b/interfaces/cython/cantera/constants.pyi new file mode 100644 index 00000000000..52af710065a --- /dev/null +++ b/interfaces/cython/cantera/constants.pyi @@ -0,0 +1,15 @@ +# This file is part of Cantera. See License.txt in the top-level directory or +# at https://cantera.org/license.txt for license and copyright information. + +avogadro: float +boltzmann: float +electron_charge: float +electron_mass: float +epsilon_0: float +faraday: float +gas_constant: float +light_speed: float +one_atm: float +permeability_0: float +planck: float +stefan_boltzmann: float diff --git a/interfaces/cython/cantera/cti2yaml.pyi b/interfaces/cython/cantera/cti2yaml.pyi new file mode 100644 index 00000000000..8526332ce8e --- /dev/null +++ b/interfaces/cython/cantera/cti2yaml.pyi @@ -0,0 +1,678 @@ +# This file is part of Cantera. See License.txt in the top-level directory or +# at https://cantera.org/license.txt for license and copyright information. + +from argparse import ArgumentParser +from collections import OrderedDict +from collections.abc import Sequence +from pathlib import Path +from typing import Any, Literal, TypeAlias, TypedDict + +from ruamel.yaml.comments import CommentedMap, CommentedSeq +from ruamel.yaml.representer import SafeRepresenter + +yaml_version: tuple[int, int, int] +yaml_min_version: tuple[int, int, int] + +class InputError(Exception): + def __init__(self, msg: str, *args: Any) -> None: ... + +BlockMap: CommentedMap + +def FlowMap(*args: Any, **kwargs: Any) -> CommentedMap: ... +def FlowList(*args: Any, **kwargs: Any) -> CommentedSeq: ... +def float2string(data: float) -> str: ... +def represent_float(self: SafeRepresenter, data: float) -> str: ... +def applyUnits(value: float | tuple[float, str]) -> float | str: ... + +OldKineticsModel: TypeAlias = Literal["GasKinetics", "Interface", "Edge"] +KineticsModel: TypeAlias = Literal["gas", "surface", "edge"] +OldTransportModel: TypeAlias = Literal["Mix", "Multi", "Ion"] +TransportModel: TypeAlias = Literal["mixture-averaged", "multicomponent", "ionized-gas"] +OldConcentrationBasis: TypeAlias = Literal["molar_volume", "molar_volume", "unity"] +ConcentrationBasis: TypeAlias = Literal[ + "species-molar-volume", "solvent-molar-volume", "unity" +] + +OneAtm: float +OneBar: float +eV: float +ElectronMass: float + +def enable_motz_wise() -> None: ... +def disable_motz_wise() -> None: ... +def validate(species: str = "yes", reactions: str = "yes") -> None: ... +def dataset(nm: str) -> None: ... +def standard_pressure(p0: float) -> None: ... +def units( + length: str = "", + quantity: str = "", + mass: str = "", + time: str = "", + act_energy: str = "", + energy: str = "", + pressure: str = "", +) -> None: ... +def get_composition( + atoms: dict[str, float] | str, +) -> dict[str, float] | OrderedDict[str, float]: ... + +class element: + symbol: str + atomic_weight: float + atomic_number: int | None + def __init__( + self, + symbol: str = "", + atomic_mass: float = 0.01, + atomic_number: int | None = None, + ) -> None: ... + @classmethod + def to_yaml(cls, representer: SafeRepresenter, node: element) -> CommentedMap: ... + +class RkPure(TypedDict): + a: tuple[float, str] | float + b: float + +class RkBinary(TypedDict): + a: float + b: float + +class species: + name: str + atoms: OrderedDict[str, float] + size: float + comment: str + transport: gas_transport | None + standard_state: constantIncompressible | None + rk_pure: RkPure + rk_binary: RkBinary + density: float | str | None + def __init__( + self, + name: str, + atoms: str = "", + note: str = "", + thermo: thermo | Sequence[thermo] | None = None, + transport: gas_transport | None = None, + charge: float | None = None, + size: float = 1.0, + standardState: constantIncompressible | None = None, + ) -> None: ... + thermo: thermo # Note: If this is moved above __init__, it overrides the class `thermo` for the static type checker + @classmethod + def to_yaml(cls, representer: SafeRepresenter, node: species) -> CommentedMap: ... + +class thermo: + @classmethod + def to_yaml(cls, representer: SafeRepresenter, node: thermo) -> CommentedMap: ... + def get_yaml(self, out: CommentedMap) -> None: ... + +class NASA(thermo): + model: Literal["NASA7"] + T_range: Sequence[float] + pref: float + coeffs: Sequence[float] + def __init__( + self, + Trange: Sequence[float] = (0.0, 0.0), + coeffs: Sequence[float] = (), + p0: float | None = None, + ) -> None: ... + +class NASA9(thermo): + model: Literal["NASA9"] + T_range: Sequence[float] + pref: float + coeffs: Sequence[float] + def __init__( + self, + Trange: Sequence[float] = (0.0, 0.0), + coeffs: Sequence[float] = (), + p0: float | None = None, + ) -> None: ... + +class MultiPolyThermo(thermo): + pref: float + Tranges: Sequence[float] + model: str + data: Sequence[float] + def __init__(self, regions: Sequence[thermo]) -> None: ... + def get_yaml(self, out: CommentedMap) -> None: ... + +class Shomate(thermo): + model: Literal["Shomate"] + T_range: Sequence[float] + pref: float + coeffs: Sequence[float] + def __init__( + self, + Trange: Sequence[float] = (0.0, 0.0), + coeffs: Sequence[float] = (), + p0: float | None = None, + ) -> None: ... + +class const_cp(thermo): + model: Literal["constant-cp"] + pref: None + t0: float + h0: float + s0: float + cp0: float + tmin: float + tmax: float + def __init__( + self, + t0: float | None = None, + cp0: float | None = None, + h0: float | None = None, + s0: float | None = None, + tmax: float | None = None, + tmin: float | None = None, + ) -> None: ... + def get_yaml(self, out: CommentedMap) -> None: ... + +class gas_transport: + geometry: Literal["atom", "linear", "nonlinear"] + diameter: float + well_depth: float + dipole: float + polarizability: float + rot_relax: float + acentric_factor: float + disp_coeff: float + quad_polar: float + def __init__( + self, + geom: Literal["atom", "linear", "nonlinear"], + diam: float, + well_depth: float, + dipole: float = 0.0, + polar: float = 0.0, + rot_relax: float = 0.0, + acentric_factor: float | None = None, + disp_coeff: float = 0.0, + quad_polar: float = 0.0, + ) -> None: ... + @classmethod + def to_yaml( + cls, representer: SafeRepresenter, node: gas_transport + ) -> CommentedMap: ... + +CoverageParameters: TypeAlias = Sequence[str | float] + +class Arrhenius: + A: float + b: float + E: float + coverage: list[CoverageParameters] | None + def __init__( + self, + A: float = 0.0, + b: float = 0.0, + E: float = 0.0, + coverage: CoverageParameters | list[CoverageParameters] = (), + ) -> None: ... + @classmethod + def to_yaml(cls, representer: SafeRepresenter, node: Arrhenius) -> CommentedMap: ... + +class stick(Arrhenius): + motz_wise: bool | None + def __init__(self, *args: Any, **kwargs: Any) -> None: ... + +ReactionOptions: TypeAlias = Literal[ + "duplicate", "negative_A", "negative_orders", "nonreactant_orders" +] + +class reaction: + equation: str + order: dict[str, float] | OrderedDict[str, float] + number: int + id: str + options: Sequence[ReactionOptions] + kf: Arrhenius + type: str = "elementary" + def __init__( + self, + equation: str, + kf: Sequence[float] | Arrhenius, + id: str = "", + order: str = "", + options: ReactionOptions | Sequence[ReactionOptions] = (), + ) -> None: ... + @classmethod + def to_yaml(cls, representer: SafeRepresenter, node: reaction) -> CommentedMap: ... + def get_yaml(self, out: CommentedMap) -> None: ... + +class three_body_reaction(reaction): + type: Literal["three-body"] + efficiencies: dict[str, float] | OrderedDict[str, float] + def __init__( + self, + equation: str, + kf: Sequence[float] | Arrhenius, + efficiencies: str = "", + id: str = "", + options: ReactionOptions | Sequence[ReactionOptions] = (), + ) -> None: ... + def get_yaml(self, out: CommentedMap) -> None: ... + +FalloffFunction: TypeAlias = Troe | SRI | Lindemann + +class falloff_base(reaction): + k_low: Arrhenius + k_high: Arrhenius + falloff: FalloffFunction | None + efficiencies: dict[str, float] | OrderedDict[str, float] + def __init__( + self, + equation: str, + klow: Sequence[float] | Arrhenius, + khigh: Sequence[float] | Arrhenius, + efficiencies: str, + falloff: FalloffFunction | None, + id: str, + options: ReactionOptions | Sequence[ReactionOptions], + ) -> None: ... + def get_yaml(self, out: CommentedMap) -> None: ... + +class falloff_reaction(falloff_base): + type: Literal["falloff"] + def __init__( + self, + equation: str, + kf0: Sequence[float] | Arrhenius, + kf: Sequence[float] | Arrhenius, + efficiencies: str = "", + falloff: FalloffFunction | None = None, + id: str = "", + options: ReactionOptions | Sequence[ReactionOptions] = (), + ) -> None: ... + +class chemically_activated_reaction(falloff_base): + type: Literal["chemically-activated"] + def __init__( + self, + equation: str, + kLow: Sequence[float] | Arrhenius, + kHigh: Sequence[float] | Arrhenius, + efficiencies: str = "", + falloff: FalloffFunction | None = None, + id: str = "", + options: ReactionOptions | Sequence[ReactionOptions] = (), + ) -> None: ... + +class pdep_arrhenius(reaction): + arrhenius: tuple[Sequence[float]] + type: Literal["pressure-dependent-Arrhenius"] + def __init__( + self, equation: str, *args: Sequence[float], **kwargs: Any + ) -> None: ... + def get_yaml(self, out: CommentedMap) -> None: ... + +class chebyshev_reaction(reaction): + type: Literal["Chebyshev"] + Pmin: tuple[float, str] + Pmax: tuple[float, str] + Tmin: float + Tmax: float + coeffs: Sequence[Sequence[float]] + def __init__( + self, + equation: str, + Tmin: float = 300.0, + Tmax: float = 2500.0, + Pmin: tuple[float, str] = (0.001, "atm"), + Pmax: tuple[float, str] = (100.0, "atm"), + coeffs: Sequence[Sequence[float]] = (), + **kwargs: Any, + ) -> None: ... + def get_yaml(self, out: CommentedMap) -> None: ... + +class surface_reaction(reaction): + type: Literal["surface", "edge"] = "surface" + sticking: bool + beta: float | None + rate_coeff_type: Literal["", "exchangecurrentdensity"] + def __init__( + self, + equation: str, + kf: Sequence[float] | Arrhenius, + id: str = "", + order: str = "", + beta: float | None = None, + options: ReactionOptions | Sequence[ReactionOptions] = (), + rate_coeff_type: Literal["", "exchangecurrentdensity"] = "", + ) -> None: ... + def get_yaml(self, out: CommentedMap) -> None: ... + +class edge_reaction(surface_reaction): + type: Literal["edge"] + def __init__( + self, + equation: str, + kf: Sequence[float] | Arrhenius, + id: str = "", + order: str = "", + beta: float | None = None, + options: ReactionOptions | Sequence[ReactionOptions] = (), + rate_coeff_type: Literal["", "exchangecurrentdensity"] = "", + ) -> None: ... + +class state: + t: float + rho: float + p: float + X: str + Y: str + coverages: str + molalities: str + def __init__( + self, + temperature: float | None = None, + pressure: float | None = None, + mole_fractions: str | None = None, + mass_fractions: str | None = None, + density: float | None = None, + coverages: str | None = None, + solute_molalities: str | None = None, + ) -> None: ... + @classmethod + def to_yaml(cls, representer: SafeRepresenter, node: state) -> CommentedMap: ... + +PhaseOptions: TypeAlias = Literal[ + "skip_undeclared_elements", "skip_undeclared_third_bodies" +] + +class phase: + name: str + elements: str + species: list[tuple[str, CommentedSeq]] + reactions: list[list[str]] + thermo_model: str | None = None + kinetics: KineticsModel | None = None + transport: TransportModel | None = None + comment: str + options: Sequence[PhaseOptions] + initial_state: state | None + def __init__( + self, + name: str = "", + elements: str = "", + species: str | Sequence[str] = "", + note: str = "", + reactions: str | Sequence[str] = "none", + initial_state: state | None = None, + options: PhaseOptions | Sequence[PhaseOptions] = (), + ) -> None: ... + @classmethod + def to_yaml(cls, representer: SafeRepresenter, node: phase) -> CommentedMap: ... + def get_yaml(self, out: CommentedMap) -> None: ... + +class ideal_gas(phase): + kinetics: KineticsModel | None + transport: TransportModel | None + thermo_model: Literal["ideal-gas"] + def __init__( + self, + name: str = "", + elements: str = "", + species: str | Sequence[str] = "", + note: str = "", + reactions: str | Sequence[str] = "none", + kinetics: OldKineticsModel | Literal["None"] = "GasKinetics", + transport: OldTransportModel | Literal["None"] | None = None, + initial_state: state | None = None, + options: PhaseOptions | Sequence[PhaseOptions] = (), + ) -> None: ... + +class stoichiometric_solid(phase): + thermo_model: Literal["fixed-stoichiometry"] + density: float + transport: TransportModel | None + def __init__( + self, + name: str = "", + elements: str = "", + species: str | Sequence[str] = "", + note: str = "", + density: float | None = None, + transport: OldTransportModel | Literal["None"] = "None", + initial_state: state | None = None, + options: PhaseOptions | Sequence[PhaseOptions] = (), + ) -> None: ... + def get_yaml(self, out: CommentedMap) -> None: ... + +class stoichiometric_liquid(stoichiometric_solid): ... + +class metal(phase): + thermo_model: Literal["electron-cloud"] + density: float + def __init__( + self, + name: str = "", + elements: str = "", + species: str | Sequence[str] = "", + note: str = "", + density: float = -1.0, + transport: OldTransportModel | Literal["None"] = "None", + initial_state: state | None = None, + options: PhaseOptions | Sequence[PhaseOptions] = (), + ) -> None: ... + def get_yaml(self, out: CommentedMap) -> None: ... + +class liquid_vapor(phase): + pure_fluids: dict[int, str] + thermo_model: Literal["pure-fluid"] + substance_flag: int + def __init__( + self, + name: str = "", + elements: str = "", + species: str | Sequence[str] = "", + note: str = "", + substance_flag: int = 0, + initial_state: state | None = None, + options: PhaseOptions | Sequence[PhaseOptions] = (), + ) -> None: ... + def get_yaml(self, out: CommentedMap) -> None: ... + +class pureFluidParameters: + species: str + a_coeff: Sequence[float] + b_coeff: float + def __init__( + self, + species: str | None = None, + a_coeff: Sequence[float] = (), + b_coeff: float = 0, + ) -> None: ... + +class crossFluidParameters: + species1: str + species2: str + a_coeff: Sequence[float] + b_coeff: Sequence[float] + def __init__( + self, + species: str | None = None, + a_coeff: Sequence[float] = (), + b_coeff: Sequence[float] = (), + ) -> None: ... + +class RedlichKwongMFTP(phase): + thermo_model: Literal["Redlich-Kwong"] + kinetics: KineticsModel | None + transport: TransportModel | None + activity_coefficients: Sequence[pureFluidParameters | crossFluidParameters] + def __init__( + self, + name: str = "", + elements: str = "", + species: str | Sequence[str] = "", + note: str = "", + reactions: str | Sequence[str] = "none", + kinetics: OldKineticsModel = "GasKinetics", + initial_state: state | None = None, + activity_coefficients: Sequence[pureFluidParameters | crossFluidParameters] + | None = None, + transport: OldTransportModel | Literal["None"] = "None", + options: PhaseOptions | Sequence[PhaseOptions] = (), + ) -> None: ... + def get_yaml(self, out: CommentedMap) -> None: ... + +class constantIncompressible: + molar_volume: float + def __init__(self, molarVolume: float = 0.0) -> None: ... + +class IdealSolidSolution(phase): + thermo_model: Literal["ideal-condensed", "binary-solution-tabulated"] = ( + "ideal-condensed" + ) + standard_concentration: ConcentrationBasis + transport: TransportModel | None + def __init__( + self, + name: str = "", + elements: str = "", + species: str | Sequence[str] = "", + note: str = "", + transport: OldTransportModel | Literal["None"] = "None", + initial_state: state | None = None, + standard_concentration: OldConcentrationBasis | None = None, + options: PhaseOptions | Sequence[PhaseOptions] = (), + ) -> None: ... + def get_yaml(self, out: CommentedMap) -> None: ... + +class table: + x: tuple[Sequence[float], str] + h: tuple[Sequence[float], str] + s: tuple[Sequence[float], str] + def __init__( + self, + moleFraction: tuple[Sequence[float], str] = ([], ""), + enthalpy: tuple[Sequence[float], str] = ([], ""), + entropy: tuple[Sequence[float], str] = ([], ""), + ) -> None: ... + +class BinarySolutionTabulatedThermo(IdealSolidSolution): + thermo_model: Literal["binary-solution-tabulated"] + tabulated_species: str + tabulated_thermo: table + def __init__( + self, + name: str = "", + elements: str = "", + species: str | Sequence[str] = "", + note: str = "", + transport: OldTransportModel | Literal["None"] = "None", + initial_state: state | None = None, + standard_concentration: OldConcentrationBasis | None = None, + tabulated_species: str | None = None, + tabulated_thermo: table | None = None, + options: PhaseOptions | Sequence[PhaseOptions] = (), + ) -> None: ... + def get_yaml(self, out: CommentedMap) -> None: ... + +class lattice(phase): + thermo_model: Literal["lattice"] + site_density: float + def __init__( + self, + name: str = "", + elements: str = "", + species: str | Sequence[str] = "", + note: str = "", + reactions: str | Sequence[str] = "none", + transport: OldTransportModel | Literal["None"] = "None", + initial_state: state | None = None, + options: PhaseOptions | Sequence[PhaseOptions] = (), + site_density: float | None = None, + ) -> None: ... + def get_yaml(self, out: CommentedMap) -> None: ... + +class ideal_interface(phase): + thermo_model: Literal["ideal-surface", "edge"] = "ideal-surface" + kinetics: KineticsModel + transport: TransportModel + site_density: float + adjacent_phases: list[str] + def __init__( + self, + name: str = "", + elements: str = "", + species: str = "", + note: str = "", + reactions: str = "none", + site_density: float = 0.0, + phases: str + | Sequence[str] = (), # Note: Does not actually accept Sequence input + kinetics: OldKineticsModel | Literal["None"] = "Interface", + transport: OldTransportModel | Literal["None"] = "None", + initial_state: state | None = None, + options: PhaseOptions | Sequence[PhaseOptions] = (), + ) -> None: ... + def get_yaml(self, out: CommentedMap) -> None: ... + +class edge(ideal_interface): + thermo_model: Literal["edge"] + def __init__( + self, + name: str = "", + elements: str = "", + species: str = "", + note: str = "", + reactions: str = "none", + site_density: float = 0.0, + phases: str + | Sequence[str] = (), # Note: Does not actually accept Sequence input + kinetics: OldKineticsModel | Literal["None"] = "Edge", + transport: OldTransportModel | Literal["None"] = "None", + initial_state: state | None = None, + options: PhaseOptions | Sequence[PhaseOptions] = (), + ) -> None: ... + +class Troe: + A: float + T3: float + T1: float + T2: float | None + def __init__( + self, + A: float = 0.0, + T3: float = 0.0, + T1: float = 0.0, + T2: float | None = None, + ) -> None: ... + def get_yaml(self, out: CommentedMap) -> None: ... + +class SRI: + A: float + B: float + C: float + D: float | None + E: float | None + def __init__( + self, + A: float = 0.0, + B: float = 0.0, + C: float = 0.0, + D: float | None = None, + E: float | None = None, + ) -> None: ... + def get_yaml(self, out: CommentedMap) -> None: ... + +class Lindemann: + def get_yaml(self, out: CommentedMap) -> None: ... + +# Note: Many more encodings available, but you probably don't want most of them. +# See: https://docs.python.org/3/library/codecs.html#standard-encodings +Encoding: TypeAlias = Literal["utf-8", "latin-1", "ascii"] + +def convert( + filename: Path | str | None = None, + output_name: str | None = None, + text: str | None = None, + encoding: Encoding = "latin-1", +) -> tuple[int, int, list[ideal_interface], Path]: ... +def create_argparser() -> ArgumentParser: ... +def main() -> None: ... diff --git a/interfaces/cython/cantera/ctml2yaml.py b/interfaces/cython/cantera/ctml2yaml.py index 4a15f5bdea2..0166a5a7ef8 100644 --- a/interfaces/cython/cantera/ctml2yaml.py +++ b/interfaces/cython/cantera/ctml2yaml.py @@ -3,6 +3,7 @@ # This file is part of Cantera. See License.txt in the top-level directory or # at https://cantera.org/license.txt for license and copyright information. + """Convert legacy CTML input to YAML format. There are two main entry points to this script, `main` and `convert`. The former is @@ -11,27 +12,40 @@ content. """ -from pathlib import Path -import sys -import re -import argparse +from __future__ import annotations +import copy +import re +import sys +import warnings import xml.etree.ElementTree as etree +from argparse import ArgumentParser +from collections.abc import Iterable from email.utils import formatdate -import warnings -import copy - -from typing import Any, Dict, Union, Iterable, Optional, List, Tuple, TypedDict -from typing import TYPE_CHECKING +from pathlib import Path +from typing import ( + ClassVar, + Literal, + TypeAlias, + TypedDict, + TypeGuard, + TypeVar, + cast, + get_args, +) import numpy as np from ruamel import yaml +from ruamel.yaml.comments import CommentedMap, CommentedSeq +from ruamel.yaml.nodes import MappingNode, ScalarNode +from ruamel.yaml.representer import RoundTripRepresenter, SafeRepresenter +from typing_extensions import Required, TypeForm # yaml.version_info is a tuple with the three parts of the version -yaml_version = yaml.version_info +yaml_version: tuple[int, int, int] = yaml.version_info # We choose ruamel.yaml 0.17.16 as the minimum version since it is the highest version # available in the Ubuntu 22.04 repositories. -yaml_min_version = (0, 17, 16) +yaml_min_version: tuple[int, int, int] = (0, 17, 16) if yaml_version < yaml_min_version: raise RuntimeError( "The minimum supported version of ruamel.yaml is 0.17.16. If you " @@ -39,66 +53,144 @@ "please install an updated version using pip or conda." ) - -if TYPE_CHECKING: - QUANTITY = Union[float, str] - - RK_EOS_DICT = TypedDict( - "RK_EOS_DICT", - {"a": List[QUANTITY], "b": QUANTITY, "binary-a": Dict[str, List[QUANTITY]]}, - total=False, - ) - DH_BETA_MATRIX = TypedDict( - "DH_BETA_MATRIX", {"species": List[str], "beta": QUANTITY}, total=False - ) - ARRHENIUS_PARAMS = Dict[str, Union[str, QUANTITY]] - EFFICIENCY_PARAMS = Dict[str, float] - LINDEMANN_PARAMS = Union[str, ARRHENIUS_PARAMS, EFFICIENCY_PARAMS] - TROE_PARAMS = Dict[str, float] - SRI_PARAMS = Dict[str, float] - COVERAGE_PARAMS = Dict[str, ARRHENIUS_PARAMS] - - ARRHENIUS_TYPE = Dict[str, ARRHENIUS_PARAMS] - INTERFACE_TYPE = Dict[ - str, Union[ARRHENIUS_PARAMS, bool, str, COVERAGE_PARAMS, float] - ] - NESTED_LIST_OF_FLOATS = List[List[float]] - CHEBYSHEV_TYPE = Dict[str, Union[List[float], NESTED_LIST_OF_FLOATS, str]] - PLOG_TYPE = Dict[str, Union[str, List[ARRHENIUS_PARAMS]]] - CHEMACT_TYPE = Dict[ - str, Union[str, ARRHENIUS_PARAMS, EFFICIENCY_PARAMS, TROE_PARAMS] - ] - LINDEMANN_TYPE = Dict[str, LINDEMANN_PARAMS] - TROE_TYPE = Dict[str, Union[LINDEMANN_PARAMS, TROE_PARAMS]] - THREEBODY_TYPE = Dict[str, Union[ARRHENIUS_PARAMS, EFFICIENCY_PARAMS]] - SRI_TYPE = Dict[str, Union[LINDEMANN_PARAMS, SRI_PARAMS]] - - THERMO_POLY_TYPE = Union[List[List[float]], List[float]] - HKFT_THERMO_TYPE = Union[str, QUANTITY, List[QUANTITY]] - # The last Union[str, float] here is not a QUANTITY - HMW_THERMO_TYPE = Union[ - str, QUANTITY, bool, Dict[str, Union[float, List[Union[str, float]]]] - ] - -BlockMap = yaml.comments.CommentedMap - - -def FlowMap(*args, **kwargs): +QuantityType: TypeAlias = float | str + +RedlichKwongInput = TypedDict( + "RedlichKwongInput", + { + "a": list[QuantityType], + "b": QuantityType, + "binary-a": dict[str, list[QuantityType]], + }, + total=False, +) + +class DebyeHuckelBetaMatrix(TypedDict, total=False): + species: list[str] + beta: QuantityType + +ReactionInput = TypedDict( + "ReactionInput", + { + "type": Required[ + Literal[ + "arrhenius", + "threebody", + "Lindemann", + "Troe", + "SRI", + "plog", + "chebyshev", + "interface", + ] + ], + "temperature-ranges": list[float], + }, + total=False, +) +ArrheniusParams: TypeAlias = dict[str, QuantityType] +EfficiencyParams: TypeAlias = dict[str, float] +LindemannParams: TypeAlias = str | ArrheniusParams | EfficiencyParams +TroeParams: TypeAlias = dict[str, float] +SriParams: TypeAlias = dict[str, float] +CoverageParams: TypeAlias = dict[str, ArrheniusParams] + +ArrheniusType: TypeAlias = dict[str, ArrheniusParams] +InterfaceType: TypeAlias = dict[ + str, ArrheniusParams | bool | str | CoverageParams | float +] +ChebyshevType = TypedDict( + "ChebyshevType", + { + "type": str, + "temperature-range": list[QuantityType], + "pressure-range": list[QuantityType], + "data": list[list[float]], + }, +) +PlogType: TypeAlias = dict[str, str | list[ArrheniusParams]] +ChemActType: TypeAlias = dict[ + str, str | ArrheniusParams | EfficiencyParams | TroeParams +] +LindemannType: TypeAlias = dict[str, LindemannParams] +TroeType: TypeAlias = dict[str, LindemannParams | TroeParams] +ThreebodyType: TypeAlias = dict[str, ArrheniusParams | EfficiencyParams] +SriType: TypeAlias = dict[str, LindemannParams | SriParams] + +ThermoPolyType: TypeAlias = list[list[float]] | list[float] +SpeciesThermoInput = TypedDict( + "SpeciesThermoInput", + { + "model": Required[str], + "temperature-ranges": list[float], + "data": ThermoPolyType, + }, + total=False, +) +ConstCpThermoInput = TypedDict( + "ConstCpThermoInput", + { + "model": Required[str], + "T0": QuantityType, + "h0": QuantityType, + "s0": QuantityType, + "cp0": QuantityType, + "T-min": float, + "T-max": float, + }, + total=False, +) +Mu0ThermoInput = TypedDict( + "Mu0ThermoInput", + { + "model": Required[str], + "reference-pressure": float, + "h0": float | str, + "T-min": float, + "T-max": float, + "temperature-ranges": list[float], + "data": dict[float, str | float], + "dimensionless": bool, + }, + total=False, +) +HkftThermoType: TypeAlias = QuantityType | list[QuantityType] +# The last str | float here is not a QUANTITY +HmwThermoType: TypeAlias = ( + QuantityType | bool | dict[str, float | list[str | float]] +) + +_T = TypeVar("_T") + + +def literal_type_guard(tag: str, literal: TypeForm[_T]) -> TypeGuard[_T]: + """Utility function for narrowing strings to specified literals.""" + return tag in get_args(literal) + + +BlockMap: type[CommentedMap] = CommentedMap + + +_KT = TypeVar("_KT") # Key type. +_VT = TypeVar("_VT") # Value type. + + +def FlowMap(*args: dict[_KT, _VT], **kwargs: _VT) -> dict[_KT, _VT]: """A YAML mapping that flows onto one line.""" - m = yaml.comments.CommentedMap(*args, **kwargs) + m: CommentedMap = CommentedMap(*args, **kwargs) m.fa.set_flow_style() return m -def FlowList(*args, **kwargs): +def FlowList(*args: Iterable[_VT], **kwargs: _VT) -> list[_VT]: """A YAML sequence that flows onto one line.""" - lst = yaml.comments.CommentedSeq(*args, **kwargs) + lst: CommentedSeq = CommentedSeq(*args, **kwargs) lst.fa.set_flow_style() - return lst + return cast(list[_VT], lst) class MissingXMLNode(LookupError): - def __init__(self, message: str = "", node: Optional[etree.Element] = None): + def __init__(self, message: str = "", node: etree.Element | None = None) -> None: """Error raised when a required node is missing in the XML tree. :param message: @@ -117,7 +209,7 @@ def __init__(self, message: str = "", node: Optional[etree.Element] = None): class MissingXMLAttribute(LookupError): - def __init__(self, message: str = "", node: Optional[etree.Element] = None): + def __init__(self, message: str = "", node: etree.Element | None = None) -> None: """Error raised when a required attribute is missing in the XML node. :param message: @@ -136,7 +228,7 @@ def __init__(self, message: str = "", node: Optional[etree.Element] = None): class MissingNodeText(LookupError): - def __init__(self, message: str = "", node: Optional[etree.Element] = None): + def __init__(self, message: str = "", node: etree.Element | None = None) -> None: """Error raised when the text of an XML node is missing. :param message: @@ -171,7 +263,7 @@ def float2string(data: float) -> str: return np.format_float_scientific(data, trim="0") -def represent_float(self: Any, data: Any) -> Any: +def represent_float(self: SafeRepresenter, data: float) -> ScalarNode: """Format floating point numbers for ruamel YAML. :param data: @@ -192,10 +284,10 @@ def represent_float(self: Any, data: Any) -> Any: return self.represent_scalar("tag:yaml.org,2002:float", value) -yaml.RoundTripRepresenter.add_representer(float, represent_float) +RoundTripRepresenter.add_representer(float, represent_float) -def get_float_or_quantity(node: etree.Element) -> "QUANTITY": +def get_float_or_quantity(node: etree.Element) -> QuantityType: """Process an XML node into a float value or a value with units. :param node: @@ -220,12 +312,12 @@ def get_float_or_quantity(node: etree.Element) -> "QUANTITY": if units: units = re.sub(r"([A-Za-z])-([A-Za-z])", r"\1*\2", units) units = re.sub(r"([A-Za-z])([-\d])", r"\1^\2", units) - return "{} {}".format(float2string(value), units) + return f"{float2string(value)} {units}" else: return value -def split_species_value_string(node: etree.Element) -> Dict[str, float]: +def split_species_value_string(node: etree.Element) -> dict[str, float]: """Split a string of species:value pairs into a dictionary. :param node: @@ -238,7 +330,7 @@ def split_species_value_string(node: etree.Element) -> Dict[str, float]: The algorithm is reimplemented from :ct:`parseCompString`. """ text = clean_node_text(node) - pairs = FlowMap({}) + pairs: dict[str, float] = FlowMap({}) start, stop, left = 0, 0, 0 # \S matches the first non-whitespace character non_whitespace = re.compile(r"\S") @@ -300,7 +392,7 @@ def clean_node_text(node: etree.Element) -> str: class Phase: - thermo_model_mapping = { + thermo_model_mapping: ClassVar[dict[str, str]] = { "IdealGas": "ideal-gas", "Incompressible": "constant-density", "Surface": "ideal-surface", @@ -334,7 +426,7 @@ class Phase: "Water": "liquid-water-IAPWS95", "BinarySolutionTabulatedThermo": "binary-solution-tabulated", } - kinetics_model_mapping = { + kinetics_model_mapping: ClassVar[dict[str, str | None]] = { "GasKinetics": "gas", "Interface": "surface", "none": None, @@ -342,7 +434,7 @@ class Phase: "None": None, "SolidKinetics": None, } - transport_model_mapping = { + transport_model_mapping: ClassVar[dict[str | None, str | None]] = { "Mix": "mixture-averaged", "Multi": "multicomponent", "None": None, @@ -356,7 +448,7 @@ class Phase: "HighP": "high-pressure", } - state_properties_mapping = { + state_properties_mapping: ClassVar[dict[str, str]] = { "moleFractions": "X", "massFractions": "Y", "temperature": "T", @@ -365,7 +457,7 @@ class Phase: "soluteMolalities": "molalities", } - pure_fluid_mapping = { + pure_fluid_mapping: ClassVar[dict[str, str]] = { "0": "water", "1": "nitrogen", "2": "methane", @@ -379,9 +471,9 @@ class Phase: def __init__( self, phase: etree.Element, - species_data: Dict[str, List["Species"]], - reaction_data: Dict[str, List["Reaction"]], - ): + species_data: dict[str, list[Species]], + reaction_data: dict[str, list[Reaction]], + ) -> None: """Represent an XML ``phase`` node. :param phase: @@ -401,7 +493,7 @@ def __init__( raise MissingXMLAttribute( "The 'phase' node requires an 'id' attribute.", phase ) - self.attribs = BlockMap({"name": phase_name}) + self.attribs: CommentedMap = BlockMap({"name": phase_name}) elem_text = phase.findtext("elementArray") if elem_text is not None: @@ -523,7 +615,7 @@ def __init__( "The 'LatticeSolid' phase thermo requires a 'LatticeArray' node.", phase_thermo, ) - self.lattice_nodes = [] # type: List[Phase] + self.lattice_nodes: list[Phase] = [] for lattice_phase_node in lattice_array_node.findall("phase"): self.lattice_nodes.append( Phase(lattice_phase_node, species_data, reaction_data) @@ -603,7 +695,7 @@ def __init__( state_node = phase.find("state") if state_node is not None: - phase_state = FlowMap() + phase_state: dict[str, float | str | dict[str, float]] = FlowMap() for prop in state_node: property_name = self.state_properties_mapping[prop.tag] if prop.tag in [ @@ -634,7 +726,7 @@ def __init__( def ideal_molal_solution( self, activity_coeffs: etree.Element - ) -> Dict[str, Union[str, "QUANTITY"]]: + ) -> dict[str, QuantityType]: """Process the cutoff data in an ``IdealMolalSolution`` phase-thermo type. :param activity_coeffs: @@ -646,7 +738,7 @@ def ideal_molal_solution( dictionary will be empty when there are no cutoff nodes in the ``activityCoefficients`` node. """ - cutoff = {} # type: Dict[str, Union[str, QUANTITY]] + cutoff: dict[str, QuantityType] = {} cutoff_node = activity_coeffs.find("idealMolalSolnCutoff") if cutoff_node is not None: cutoff_model = cutoff_node.get("model") @@ -660,7 +752,7 @@ def ideal_molal_solution( def margules( self, activity_coeffs: etree.Element - ) -> List[Dict[str, List[Union[str, "QUANTITY"]]]]: + ) -> list[dict[str, list[QuantityType]]]: """Process activity coefficients for a ``Margules`` phase-thermo type. :param activity_coeffs: @@ -674,7 +766,7 @@ def margules( because this function would have to go through the same nodes again. """ all_binary_params = activity_coeffs.findall("binaryNeutralSpeciesParameters") - interactions = [] + interactions: list[dict[str, list[QuantityType]]] = [] for binary_params in all_binary_params: species_A = binary_params.get("speciesA") species_B = binary_params.get("speciesB") @@ -684,9 +776,9 @@ def margules( "'speciesB' attributes", binary_params, ) - this_node = { + this_node: dict[str, list[QuantityType]] = { "species": FlowList([species_A, species_B]) - } # type: Dict[str, List[Union[str, QUANTITY]]] + } excess_enthalpy_node = binary_params.find("excessEnthalpy") if excess_enthalpy_node is not None: excess_enthalpy = clean_node_text(excess_enthalpy_node).split(",") @@ -745,7 +837,7 @@ def margules( def redlich_kister( self, activity_coeffs: etree.Element - ) -> List[Dict[str, List[Union[str, "QUANTITY"]]]]: + ) -> list[dict[str, list[QuantityType]]]: """Process activity coefficients for a Redlich-Kister phase-thermo type. :param activity_coeffs: @@ -762,7 +854,7 @@ def redlich_kister( "'binaryNeutralSpeciesParameters' node", activity_coeffs, ) - interactions = [] + interactions: list[dict[str, list[QuantityType]]] = [] for binary_params in all_binary_params: species_A = binary_params.get("speciesA") species_B = binary_params.get("speciesB") @@ -772,9 +864,9 @@ def redlich_kister( "'speciesB' attributes", binary_params, ) - this_node = { + this_node: dict[str, list[QuantityType]] = { "species": FlowList([species_A, species_B]) - } # type: Dict[str, List[Union[str, QUANTITY]]] + } excess_enthalpy_node = binary_params.find("excessEnthalpy") if excess_enthalpy_node is not None: excess_enthalpy = clean_node_text(excess_enthalpy_node).split(",") @@ -802,8 +894,8 @@ def redlich_kister( def check_elements( self, - this_phase_species: List[Dict[str, Iterable[str]]], - species_data: Dict[str, List["Species"]], + this_phase_species: list[dict[str, Iterable[str]]], + species_data: dict[str, list[Species]], ) -> None: """Check the species elements for inclusion in the `Phase`-level specification. @@ -841,9 +933,9 @@ def check_elements( def move_RK_coeffs_to_species( self, - this_phase_species: List[Dict[str, Iterable[str]]], + this_phase_species: list[dict[str, Iterable[str]]], activity_coeffs: etree.Element, - species_data: Dict[str, List["Species"]], + species_data: dict[str, list[Species]], ) -> None: """Move the Redlich-Kwong activity coefficient data from phase to species. @@ -862,9 +954,11 @@ def move_RK_coeffs_to_species( parameters from the `Phase` node into the `Species` nodes. This modifies the `Species` objects in-place in the ``species_data`` list. """ - all_species_eos = {} # type: Dict[str, RK_EOS_DICT] + all_species_eos: dict[str, RedlichKwongInput] = {} for pure_param in activity_coeffs.iterfind("pureFluidParameters"): - eq_of_state = BlockMap({"model": "Redlich-Kwong"}) + eq_of_state: RedlichKwongInput = cast( + RedlichKwongInput, BlockMap({"model": "Redlich-Kwong"}) + ) pure_species = pure_param.get("species") if pure_species is None: raise MissingXMLAttribute( @@ -963,9 +1057,9 @@ def move_RK_coeffs_to_species( def move_density_to_species( self, - this_phase_species: List[Dict[str, Iterable[str]]], + this_phase_species: list[dict[str, Iterable[str]]], phase_thermo: etree.Element, - species_data: Dict[str, List["Species"]], + species_data: dict[str, list[Species]], ) -> None: """Move the phase density information into each species definition. @@ -1019,7 +1113,7 @@ def move_density_to_species( def get_species_array( self, speciesArray_node: etree.Element - ) -> Dict[str, Iterable[str]]: + ) -> dict[str, Iterable[str]]: """Process a list of species from a ``speciesArray`` node. :param speciesArray_node: @@ -1041,15 +1135,15 @@ def get_species_array( name = str(Path(filename).with_suffix(".yaml")) if location == "species_data": location = "species" - new_datasrc = "{}/{}".format(name, location) + new_datasrc = f"{name}/{location}" return {new_datasrc: species_list} def get_reaction_array( self, reactionArray_node: etree.Element, - reaction_data: Dict[str, List["Reaction"]], - ) -> Dict[str, str]: + reaction_data: dict[str, list[Reaction]], + ) -> dict[str, str]: """Process reactions from a ``reactionArray`` node in a phase definition. :param reactionArray_node: @@ -1115,7 +1209,7 @@ def get_reaction_array( return {datasrc: reaction_option} def filter_reaction_list( - self, datasrc: str, filter_text: str, reaction_data: Dict[str, List["Reaction"]] + self, datasrc: str, filter_text: str, reaction_data: dict[str, list[Reaction]] ) -> str: """Filter the reaction_data list to only include specified reactions. @@ -1147,11 +1241,12 @@ def filter_reaction_list( if not hits: raise ValueError( - "The filter text '{}' resulted in an empty set of " - "reactions".format(filter_text) + "The filter text '{}' resulted in an empty set of reactions".format( + filter_text + ) ) else: - new_datasrc = self.attribs["name"] + "-reactions" + new_datasrc: str = self.attribs["name"] + "-reactions" reaction_data[new_datasrc] = hits # If misses is not empty, replace the old list of reactions with # a new list where filtered out reactions are removed. If there @@ -1164,7 +1259,7 @@ def filter_reaction_list( return new_datasrc - def get_tabulated_thermo(self, tab_thermo_node: etree.Element) -> Dict[str, str]: + def get_tabulated_thermo(self, tab_thermo_node: etree.Element) -> dict[str, str]: """Process data from the ``tabulatedThermo`` node. :param tab_thermo_node: @@ -1180,7 +1275,8 @@ def get_tabulated_thermo(self, tab_thermo_node: etree.Element) -> Dict[str, str] enthalpy_units = enthalpy_node.get("units", "").split("/") if not enthalpy_units: raise MissingXMLAttribute( - "The 'enthalpy' node must have a 'units' attribute.", enthalpy_node, + "The 'enthalpy' node must have a 'units' attribute.", + enthalpy_node, ) entropy_node = tab_thermo_node.find("entropy") if entropy_node is None: @@ -1191,7 +1287,8 @@ def get_tabulated_thermo(self, tab_thermo_node: etree.Element) -> Dict[str, str] entropy_units = entropy_node.get("units", "").split("/") if not entropy_units: raise MissingXMLAttribute( - "The 'entropy' node must have a 'units' attribute.", enthalpy_node, + "The 'entropy' node must have a 'units' attribute.", + enthalpy_node, ) if enthalpy_units[:2] != entropy_units[:2]: raise ValueError("Tabulated thermo must have the same units.") @@ -1228,9 +1325,7 @@ def get_tabulated_thermo(self, tab_thermo_node: etree.Element) -> Dict[str, str] return tab_thermo - def hmw_electrolyte( - self, activity_node: etree.Element - ) -> Dict[str, "HMW_THERMO_TYPE"]: + def hmw_electrolyte(self, activity_node: etree.Element) -> dict[str, HmwThermoType]: """Process the activity coefficients for an ``HMW`` phase-thermo type. :param activity_coeffs: @@ -1271,7 +1366,9 @@ def hmw_electrolyte( "zetaCation", ]: continue - this_interaction = {"species": FlowList([i[1] for i in inter_node.items()])} + this_interaction: dict[str, list[str] | list[float] | float] = { + "species": FlowList([i[1] for i in inter_node.items()]) + } for param_node in inter_node: data = clean_node_text(param_node).split(",") param_name = param_node.tag.lower() @@ -1287,10 +1384,10 @@ def hmw_electrolyte( def debye_huckel( self, - this_phase_species: List[Dict[str, Iterable[str]]], + this_phase_species: list[dict[str, Iterable[str]]], activity_node: etree.Element, - species_data: Dict[str, List["Species"]], - ) -> Dict[str, Union[str, "QUANTITY", bool]]: + species_data: dict[str, list["Species"]], + ) -> dict[str, QuantityType | bool]: """Process the activity coefficients for the ``DebyeHuckel`` phase-thermo type. :param this_phase_species: @@ -1343,7 +1440,7 @@ def debye_huckel( activity_data["B-dot"] = get_float_or_quantity(B_dot_node) ionic_radius_node = activity_node.find("ionicRadius") - species_ionic_radii = {} # type: Dict[str, QUANTITY] + species_ionic_radii: dict[str, QuantityType] = {} if ionic_radius_node is not None: default_radius = ionic_radius_node.get("default") radius_units = ionic_radius_node.get("units") @@ -1374,9 +1471,9 @@ def debye_huckel( # names in this matrix do not contain colons, so we retain that # behavior here. species_1, species_2, beta_value = beta_text.split(":") - beta_dict = { + beta_dict: DebyeHuckelBetaMatrix = { "species": FlowList([species_1, species_2]) - } # type: DH_BETA_MATRIX + } if beta_units is not None: beta_units = re.sub(r"([A-Za-z])-([A-Za-z])", r"\1*\2", beta_units) beta_units = re.sub(r"([A-Za-z])([-\d])", r"\1^\2", beta_units) @@ -1446,7 +1543,7 @@ def debye_huckel( return activity_data @classmethod - def to_yaml(cls, representer, data): + def to_yaml(cls, representer: SafeRepresenter, data: Phase) -> MappingNode: """Serialize the class instance to YAML format suitable for ruamel.yaml. :param representer: @@ -1477,11 +1574,11 @@ def __init__(self, thermo: etree.Element) -> None: if thermo_type not in ["NASA", "NASA9", "const_cp", "Shomate", "Mu0"]: raise TypeError("Unknown thermo model type: '{}'".format(thermo[0].tag)) func = getattr(self, thermo_type) - self.attribs = func(thermo) + self.attribs: SpeciesThermoInput = func(thermo) def process_polynomial( self, thermo: etree.Element, poly_type: str - ) -> Tuple[List[List[float]], List[float]]: + ) -> tuple[list[list[float]], list[float]]: """Process the `Species` thermodynamic polynomial for several types. :param thermo: @@ -1494,7 +1591,7 @@ def process_polynomial( This method converts the polynomial data for the ``NASA``, ``NASA9``, and ``Shomate`` thermodynamic types into the appropriate YAML structure. """ - temperature_ranges = set() + temperature_ranges: set[float] = set() model_nodes = thermo.findall(poly_type) unsorted_data = {} for node in model_nodes: @@ -1526,55 +1623,53 @@ def process_polynomial( return data, FlowList(sorted(temperature_ranges)) - def Shomate( - self, thermo: etree.Element - ) -> Dict[str, Union[str, "THERMO_POLY_TYPE"]]: + def Shomate(self, thermo: etree.Element) -> SpeciesThermoInput: """Process a Shomate `Species` thermodynamic polynomial. :param thermo: A ``species/thermo`` XML node. There must be one or more child nodes with the tag ``Shomate``. """ - thermo_attribs = BlockMap({"model": "Shomate"}) + thermo_attribs = cast(SpeciesThermoInput, BlockMap({"model": "Shomate"})) data, temperature_ranges = self.process_polynomial(thermo, "Shomate") thermo_attribs["temperature-ranges"] = temperature_ranges thermo_attribs["data"] = data return thermo_attribs - def NASA(self, thermo: etree.Element) -> Dict[str, Union[str, "THERMO_POLY_TYPE"]]: + def NASA(self, thermo: etree.Element) -> SpeciesThermoInput: """Process a NASA 7-coefficient thermodynamic polynomial. :param thermo: A ``species/thermo`` XML node. There must be one or more child nodes with the tag ``NASA``. """ - thermo_attribs = BlockMap({"model": "NASA7"}) + thermo_attribs = cast(SpeciesThermoInput, BlockMap({"model": "NASA7"})) data, temperature_ranges = self.process_polynomial(thermo, "NASA") thermo_attribs["temperature-ranges"] = temperature_ranges thermo_attribs["data"] = data return thermo_attribs - def NASA9(self, thermo: etree.Element) -> Dict[str, Union[str, "THERMO_POLY_TYPE"]]: + def NASA9(self, thermo: etree.Element) -> SpeciesThermoInput: """Process a NASA 9-coefficient thermodynamic polynomial. :param thermo: A ``species/thermo`` XML node. There must be one or more child nodes with the tag ``NASA9``. """ - thermo_attribs = BlockMap({"model": "NASA9"}) + thermo_attribs = cast(SpeciesThermoInput, BlockMap({"model": "NASA9"})) data, temperature_ranges = self.process_polynomial(thermo, "NASA9") thermo_attribs["temperature-ranges"] = temperature_ranges thermo_attribs["data"] = data return thermo_attribs - def const_cp(self, thermo: etree.Element) -> Dict[str, Union[str, "QUANTITY"]]: + def const_cp(self, thermo: etree.Element) -> ConstCpThermoInput: """Process a `Species` thermodynamic type with constant specific heat. :param thermo: A ``species/thermo`` XML node. There must be one child node with the tag ``const_cp``. """ - thermo_attribs = BlockMap({"model": "constant-cp"}) + thermo_attribs = cast(ConstCpThermoInput, BlockMap({"model": "constant-cp"})) const_cp_node = thermo.find("const_cp") if const_cp_node is None: raise MissingXMLNode( @@ -1584,27 +1679,27 @@ def const_cp(self, thermo: etree.Element) -> Dict[str, Union[str, "QUANTITY"]]: tag = node.tag if tag == "t0": tag = "T0" - thermo_attribs[tag] = get_float_or_quantity(node) - tmin = const_cp_node.get('Tmin') - if tmin is not None and tmin != '100.0': - thermo_attribs['T-min'] = float(tmin) - tmax = const_cp_node.get('Tmax') - if tmax is not None and tmax != '5000.0': - thermo_attribs['T-max'] = float(tmax) + if literal_type_guard(tag, Literal["T0", "h0", "s0", "cp0"]): + thermo_attribs[tag] = get_float_or_quantity(node) + + tmin = const_cp_node.get("Tmin") + if tmin is not None and tmin != "100.0": + thermo_attribs["T-min"] = float(tmin) + tmax = const_cp_node.get("Tmax") + if tmax is not None and tmax != "5000.0": + thermo_attribs["T-max"] = float(tmax) return thermo_attribs - def Mu0( - self, thermo: etree.Element - ) -> Dict[str, Union[str, Dict[float, Iterable]]]: + def Mu0(self, thermo: etree.Element) -> Mu0ThermoInput: """Process a piecewise Gibbs Free Energy thermodynamic polynomial. :param thermo: A ``species/thermo`` XML node. There must be one child node with the tag ``Mu0``. """ - thermo_attribs = BlockMap({"model": "piecewise-Gibbs"}) + thermo_attribs = cast(Mu0ThermoInput, BlockMap({"model": "piecewise-Gibbs"})) Mu0_node = thermo.find("Mu0") if Mu0_node is None: raise MissingXMLNode("The 'thermo' node must contain a 'Mu0' node.", thermo) @@ -1620,12 +1715,14 @@ def Mu0( "The 'Mu0' node must contain an 'H298' node.", Mu0_node ) thermo_attribs["h0"] = get_float_or_quantity(H298_node) - tmin = Mu0_node.get('Tmin') + tmin = Mu0_node.get("Tmin") if tmin is not None: - thermo_attribs['T-min'] = float(tmin) - tmax = Mu0_node.get('Tmax') + thermo_attribs["T-min"] = float(tmin) + tmax = Mu0_node.get("Tmax") if tmax is not None: - thermo_attribs['T-max'] = float(tmax) + thermo_attribs["T-max"] = float(tmax) + values: Iterable[float] | Iterable[str] = [] + temperatures: map[float] = map(float, ()) for float_node in Mu0_node.iterfind("floatArray"): title = float_node.get("title") if title == "Mu0Values": @@ -1633,19 +1730,18 @@ def Mu0( if dimensions == "Dimensionless": thermo_attribs["dimensionless"] = True dimensions = "" - values = [] # type: Union[Iterable[float], Iterable[str]] values = map(float, clean_node_text(float_node).split(",")) if dimensions: values = [float2string(v) + " " + dimensions for v in values] elif title == "Mu0Temperatures": temperatures = map(float, clean_node_text(float_node).split(",")) - thermo_attribs["data"] = dict(zip(temperatures, values)) + thermo_attribs["data"] = dict(zip(temperatures, values, strict=True)) return thermo_attribs @classmethod - def to_yaml(cls, representer, data): + def to_yaml(cls, representer: SafeRepresenter, data: SpeciesThermo) -> MappingNode: """Serialize the class instance to YAML format suitable for ruamel.yaml. :param representer: @@ -1661,8 +1757,8 @@ def to_yaml(cls, representer, data): class SpeciesTransport: - species_transport_mapping = {"gas_transport": "gas"} - transport_properties_mapping = { + species_transport_mapping: ClassVar[dict[str, str]] = {"gas_transport": "gas"} + transport_properties_mapping: ClassVar[dict[str, str]] = { "LJ_welldepth": "well-depth", "LJ_diameter": "diameter", "polarizability": "polarizability", @@ -1680,7 +1776,7 @@ def __init__(self, transport: etree.Element): This class only supports one type of transport model, ``gas_transport``. """ - self.attribs = BlockMap({}) + self.attribs: CommentedMap = BlockMap({}) transport_model = transport.get("model") if transport_model not in self.species_transport_mapping: raise TypeError( @@ -1702,7 +1798,9 @@ def __init__(self, transport: etree.Element): self.attribs[name] = value @classmethod - def to_yaml(cls, representer, data): + def to_yaml( + cls, representer: SafeRepresenter, data: SpeciesTransport + ) -> MappingNode: """Serialize the class instance to YAML format suitable for ruamel.yaml. :param representer: @@ -1718,7 +1816,7 @@ def to_yaml(cls, representer, data): class Species: - standard_state_model_mapping = { + standard_state_model_mapping: ClassVar[dict[str, str]] = { "ideal-gas": "ideal-gas", "constant_incompressible": "constant-volume", "constant-incompressible": "constant-volume", @@ -1727,7 +1825,7 @@ class Species: "temperature_polynomial": "molar-volume-temperature-polynomial", "density_temperature_polynomial": "density-temperature-polynomial", } - electrolyte_species_type_mapping = { + electrolyte_species_type_mapping: ClassVar[dict[str, str]] = { "weakAcidAssociated": "weak-acid-associated", "chargedSpecies": "charged-species", "strongAcidAssociated": "strong-acid-associated", @@ -1735,7 +1833,7 @@ class Species: "nonpolarNeutral": "nonpolar-neutral", } - def __init__(self, species_node: etree.Element): + def __init__(self, species_node: etree.Element) -> None: """Represent an XML ``species`` node. :param species_node: @@ -1746,7 +1844,7 @@ def __init__(self, species_node: etree.Element): attribute and automatically formatted to YAML by the `~Species.to_yaml` class method. """ - self.attribs = BlockMap() + self.attribs: CommentedMap = BlockMap() species_name = species_node.get("name") if species_name is None: raise MissingXMLAttribute( @@ -1813,7 +1911,7 @@ def __init__(self, species_node: etree.Element): self.process_standard_state_node(species_node) electrolyte = species_node.findtext("electrolyteSpeciesType") - debye_huckel = {} + debye_huckel: dict[str, str | float] = {} if electrolyte is not None: electrolyte = self.electrolyte_species_type_mapping[electrolyte.strip()] debye_huckel["electrolyte-species-type"] = electrolyte @@ -1824,7 +1922,7 @@ def __init__(self, species_node: etree.Element): if debye_huckel: self.attribs["Debye-Huckel"] = debye_huckel - def hkft(self, species_node: etree.Element) -> Dict[str, "HKFT_THERMO_TYPE"]: + def hkft(self, species_node: etree.Element) -> dict[str, HkftThermoType]: """Process a species with HKFT thermo type. :param species_node: @@ -1895,9 +1993,9 @@ def process_standard_state_node(self, species_node: etree.Element) -> None: # species __init__ function return - eqn_of_state = { + eqn_of_state: dict[str, QuantityType | list[QuantityType]] = { "model": self.standard_state_model_mapping[std_state_model] - } # type: Dict[str, Union[str, QUANTITY, List[QUANTITY]]] + } if std_state_model == "constant_incompressible": molar_volume_node = std_state.find("molarVolume") if molar_volume_node is None: @@ -1934,15 +2032,17 @@ def process_standard_state_node(self, species_node: etree.Element) -> None: # (for example, if the units are g/L) and there's no way to specify # YAML node-level units of volume. data = [] - for v, suffix in zip(values, ("", "/K", "/K^2", "/K^3")): - data.append("{} {}{}".format(v.strip(), poly_units, suffix)) + for v, suffix in zip( + values, ("", "/K", "/K^2", "/K^3"), strict=False + ): + data.append(f"{v.strip()} {poly_units}{suffix}") eqn_of_state["data"] = FlowList(data) self.attribs["equation-of-state"] = eqn_of_state @classmethod - def to_yaml(cls, representer, data): + def to_yaml(cls, representer: SafeRepresenter, data: Species) -> MappingNode: """Serialize the class instance to YAML format suitable for ruamel.yaml. :param representer: @@ -1958,7 +2058,7 @@ def to_yaml(cls, representer, data): class Reaction: - def __init__(self, reaction: etree.Element, node_motz_wise: bool): + def __init__(self, reaction: etree.Element, node_motz_wise: bool) -> None: """Represent an XML ``reaction`` node. :param reaction: @@ -1970,8 +2070,8 @@ def __init__(self, reaction: etree.Element, node_motz_wise: bool): `Phase`-level option because the reactions are processed before the phases, so it isn't known at this point what phase these reactions will apply to. """ - self.attribs = BlockMap({}) - reaction_id = reaction.get("id", False) # type: Union[str, int, bool] + self.attribs: CommentedMap = BlockMap({}) + reaction_id: str | int | bool = reaction.get("id", False) if reaction_id: # If the reaction_id can be converted to an integer, it was likely # added automatically, so there's no need to include it in the @@ -2036,8 +2136,9 @@ def __init__(self, reaction: etree.Element, node_motz_wise: bool): reaction_type = "plog" elif reaction_type == "chebyshev": # Remove deprecated '(+M)' third body notation - self.attribs["equation"] = re.sub(r" *\( *\+ *M *\)", "", - self.attribs["equation"]) + self.attribs["equation"] = re.sub( + r" *\( *\+ *M *\)", "", self.attribs["equation"] + ) # There's only one way to spell Chebyshev, so no need to change the # reaction_type. elif reaction_type in [ @@ -2106,7 +2207,7 @@ def __init__(self, reaction: etree.Element, node_motz_wise: bool): self.attribs["duplicate"] = True @classmethod - def to_yaml(cls, representer, data): + def to_yaml(cls, representer: SafeRepresenter, data: Reaction) -> MappingNode: """Serialize the class instance to YAML format suitable for ruamel.yaml. :param representer: @@ -2120,7 +2221,7 @@ def to_yaml(cls, representer, data): """ return representer.represent_dict(data.attribs) - def sri(self, rate_coeff: etree.Element) -> "SRI_TYPE": + def sri(self, rate_coeff: etree.Element) -> SriType: """Process an SRI reaction. :param rate_coeff: @@ -2138,13 +2239,13 @@ def sri(self, rate_coeff: etree.Element) -> "SRI_TYPE": reaction_attribs["SRI"] = SRI_data return reaction_attribs - def threebody(self, rate_coeff: etree.Element) -> "THREEBODY_TYPE": + def threebody(self, rate_coeff: etree.Element) -> ThreebodyType: """Process a three-body reaction. :param rate_coeff: The XML node with rate coefficient information for this reaction. """ - reaction_attribs = FlowMap({"type": "three-body"}) + reaction_attribs = cast(ThreebodyType, FlowMap({"type": "three-body"})) reaction_attribs["rate-constant"] = self.process_arrhenius_parameters( rate_coeff.find("Arrhenius") ) @@ -2154,22 +2255,22 @@ def threebody(self, rate_coeff: etree.Element) -> "THREEBODY_TYPE": return reaction_attribs - def lindemann(self, rate_coeff: etree.Element) -> "LINDEMANN_TYPE": + def lindemann(self, rate_coeff: etree.Element) -> LindemannType: """Process a Lindemann falloff reaction. :param rate_coeff: The XML node with rate coefficient information for this reaction. """ - reaction_attribs = FlowMap({"type": "falloff"}) + reaction_attribs = cast(LindemannType, FlowMap({"type": "falloff"})) for arr_coeff in rate_coeff.iterfind("Arrhenius"): if arr_coeff.get("name") == "k0": - reaction_attribs[ - "low-P-rate-constant" - ] = self.process_arrhenius_parameters(arr_coeff) + reaction_attribs["low-P-rate-constant"] = ( + self.process_arrhenius_parameters(arr_coeff) + ) elif arr_coeff.get("name") is None: - reaction_attribs[ - "high-P-rate-constant" - ] = self.process_arrhenius_parameters(arr_coeff) + reaction_attribs["high-P-rate-constant"] = ( + self.process_arrhenius_parameters(arr_coeff) + ) else: raise TypeError("Too many 'Arrhenius' nodes") eff_node = rate_coeff.find("efficiencies") @@ -2178,14 +2279,14 @@ def lindemann(self, rate_coeff: etree.Element) -> "LINDEMANN_TYPE": return reaction_attribs - def troe(self, rate_coeff: etree.Element) -> "TROE_TYPE": + def troe(self, rate_coeff: etree.Element) -> TroeType: """Process a Troe falloff reaction. :param rate_coeff: The XML node with rate coefficient information for this reaction. """ # This gets the low-p and high-p rate constants and the efficiencies - reaction_attribs = self.lindemann(rate_coeff) + reaction_attribs: TroeType = self.lindemann(rate_coeff) troe_node = rate_coeff.find("falloff") if troe_node is None: @@ -2194,31 +2295,32 @@ def troe(self, rate_coeff: etree.Element) -> "TROE_TYPE": ) troe_params = clean_node_text(troe_node).split() troe_names = ["A", "T3", "T1", "T2"] - reaction_attribs["Troe"] = FlowMap() + reaction_attribs["Troe"] = cast(TroeParams, FlowMap()) + assert not isinstance(reaction_attribs["Troe"], str) # zip stops when the shortest iterable is exhausted. If T2 is not present # in the Troe parameters (that is, troe_params is three elements long), it # will be omitted here as well. - for name, param in zip(troe_names, troe_params): - reaction_attribs["Troe"].update({name: float(param)}) # type: ignore + for name, param in zip(troe_names, troe_params, strict=False): + reaction_attribs["Troe"].update({name: float(param)}) return reaction_attribs - def chemact(self, rate_coeff: etree.Element) -> "CHEMACT_TYPE": + def chemact(self, rate_coeff: etree.Element) -> ChemActType: """Process a chemically activated falloff reaction. :param rate_coeff: The XML node with rate coefficient information for this reaction. """ - reaction_attribs = FlowMap({"type": "chemically-activated"}) + reaction_attribs = cast(ChemActType, FlowMap({"type": "chemically-activated"})) for arr_coeff in rate_coeff.iterfind("Arrhenius"): if arr_coeff.get("name") == "kHigh": - reaction_attribs[ - "high-P-rate-constant" - ] = self.process_arrhenius_parameters(arr_coeff) + reaction_attribs["high-P-rate-constant"] = ( + self.process_arrhenius_parameters(arr_coeff) + ) elif arr_coeff.get("name") is None: - reaction_attribs[ - "low-P-rate-constant" - ] = self.process_arrhenius_parameters(arr_coeff) + reaction_attribs["low-P-rate-constant"] = ( + self.process_arrhenius_parameters(arr_coeff) + ) else: raise TypeError("Too many 'Arrhenius' nodes") eff_node = rate_coeff.find("efficiencies") @@ -2233,22 +2335,25 @@ def chemact(self, rate_coeff: etree.Element) -> "CHEMACT_TYPE": ) troe_params = clean_node_text(troe_node).split() troe_names = ["A", "T3", "T1", "T2"] - reaction_attribs["Troe"] = FlowMap() + reaction_attribs["Troe"] = cast(TroeParams, FlowMap()) # zip stops when the shortest iterable is exhausted. If T2 is not present # in the Troe parameters (that is, troe_params is three elements long), it # will be omitted here as well. - for name, param in zip(troe_names, troe_params): + for name, param in zip(troe_names, troe_params, strict=False): + assert isinstance(reaction_attribs["Troe"], dict) reaction_attribs["Troe"].update({name: float(param)}) return reaction_attribs - def plog(self, rate_coeff: etree.Element) -> "PLOG_TYPE": + def plog(self, rate_coeff: etree.Element) -> PlogType: """Process a PLOG reaction. :param rate_coeff: The XML node with rate coefficient information for this reaction. """ - reaction_attributes = FlowMap({"type": "pressure-dependent-Arrhenius"}) + reaction_attributes = cast( + PlogType, FlowMap({"type": "pressure-dependent-Arrhenius"}) + ) rate_constants = [] for arr_coeff in rate_coeff.iterfind("Arrhenius"): rate_constant = self.process_arrhenius_parameters(arr_coeff) @@ -2263,25 +2368,28 @@ def plog(self, rate_coeff: etree.Element) -> "PLOG_TYPE": return reaction_attributes - def chebyshev(self, rate_coeff: etree.Element) -> "CHEBYSHEV_TYPE": + def chebyshev(self, rate_coeff: etree.Element) -> ChebyshevType: """Process a Chebyshev reaction. :param rate_coeff: The XML node with rate coefficient information for this reaction. """ - reaction_attributes = FlowMap( - { - "type": "Chebyshev", - "temperature-range": FlowList(), - "pressure-range": FlowList(), - } + reaction_attributes: ChebyshevType = cast( + ChebyshevType, + FlowMap( + { + "type": "Chebyshev", + "temperature-range": FlowList(), + "pressure-range": FlowList(), + "data": FlowList(), + } + ), ) for range_tag in ["Tmin", "Tmax", "Pmin", "Pmax"]: range_node = rate_coeff.find(range_tag) if range_node is None: raise MissingXMLNode( - "A Chebyshev 'reaction' node must include a '{}' " - "node".format(range_tag), + f"A Chebyshev 'reaction' node must include a '{range_tag}' node", rate_coeff, ) if range_tag.startswith("T"): @@ -2320,7 +2428,7 @@ def chebyshev(self, rate_coeff: etree.Element) -> "CHEBYSHEV_TYPE": return reaction_attributes - def interface(self, rate_coeff: etree.Element) -> "INTERFACE_TYPE": + def interface(self, rate_coeff: etree.Element) -> InterfaceType: """Process an interface reaction. :param rate_coeff: @@ -2334,8 +2442,15 @@ def interface(self, rate_coeff: etree.Element) -> "INTERFACE_TYPE": "An interface 'reaction' node requires an 'Arrhenius' node", rate_coeff ) if arr_node.get("type", "").lower() == "stick": - reaction_attributes = FlowMap( - {"sticking-coefficient": self.process_arrhenius_parameters(arr_node)} + reaction_attributes = cast( + InterfaceType, + FlowMap( + { + "sticking-coefficient": self.process_arrhenius_parameters( + arr_node + ) + } + ), ) species = arr_node.get("species", "") if species: @@ -2346,12 +2461,14 @@ def interface(self, rate_coeff: etree.Element) -> "INTERFACE_TYPE": elif motz_wise == "false": reaction_attributes["Motz-Wise"] = False else: - reaction_attributes = FlowMap( - {"rate-constant": self.process_arrhenius_parameters(arr_node)} + reaction_attributes = cast( + InterfaceType, + FlowMap({"rate-constant": self.process_arrhenius_parameters(arr_node)}), ) cov_node = arr_node.find("coverage") if cov_node is not None: cov_species = cov_node.get("species") + assert cov_species is not None cov_a = cov_node.find("a") if cov_a is None: raise MissingXMLNode( @@ -2385,7 +2502,7 @@ def interface(self, rate_coeff: etree.Element) -> "INTERFACE_TYPE": return reaction_attributes - def arrhenius(self, rate_coeff: etree.Element) -> "ARRHENIUS_TYPE": + def arrhenius(self, rate_coeff: etree.Element) -> ArrheniusType: """Process a standard Arrhenius-type reaction. :param rate_coeff: @@ -2400,8 +2517,8 @@ def arrhenius(self, rate_coeff: etree.Element) -> "ARRHENIUS_TYPE": ) def process_arrhenius_parameters( - self, arr_node: Optional[etree.Element] - ) -> "ARRHENIUS_PARAMS": + self, arr_node: etree.Element | None + ) -> ArrheniusParams: """Process the parameters from an ``Arrhenius`` child of a ``rateCoeff`` node. :param arr_node: @@ -2427,7 +2544,7 @@ def process_arrhenius_parameters( } ) - def process_efficiencies(self, eff_node: etree.Element) -> "EFFICIENCY_PARAMS": + def process_efficiencies(self, eff_node: etree.Element) -> EfficiencyParams: """Process the efficiency information about a reaction. :param eff_node: @@ -2438,7 +2555,7 @@ def process_efficiencies(self, eff_node: etree.Element) -> "EFFICIENCY_PARAMS": return FlowMap({s: float(e) for s, e in efficiencies}) -def create_species_from_data_node(ctml_tree: etree.Element) -> Dict[str, List[Species]]: +def create_species_from_data_node(ctml_tree: etree.Element) -> dict[str, list[Species]]: """Generate lists of `Species` instances mapped to the ``speciesData`` id string. :param ctml_tree: @@ -2455,7 +2572,7 @@ def create_species_from_data_node(ctml_tree: etree.Element) -> Dict[str, List[Sp If ``speciesData`` nodes with the same ``id`` attribute are found, only the first section with that ``id`` is put into the YAML output file. """ - species = {} # type: Dict[str, List[Species]] + species: dict[str, list[Species]] = {} for species_data_node in ctml_tree.iterfind("speciesData"): this_data_node_id = species_data_node.get("id", "") if this_data_node_id in species: @@ -2473,7 +2590,7 @@ def create_species_from_data_node(ctml_tree: etree.Element) -> Dict[str, List[Sp def create_reactions_from_data_node( ctml_tree: etree.Element, -) -> Dict[str, List[Reaction]]: +) -> dict[str, list[Reaction]]: """Generate lists of `Reaction` instances mapped to the ``reactionData`` id string. :param ctml_tree: @@ -2490,7 +2607,7 @@ def create_reactions_from_data_node( If ``reactionData`` nodes with the same ``id`` attribute are found, only the first section with that ``id`` is put into the YAML output file. """ - reactions = {} # type: Dict[str, List[Reaction]] + reactions: dict[str, list[Reaction]] = {} for reactionData_node in ctml_tree.iterfind("reactionData"): node_motz_wise = False if reactionData_node.get("motz_wise", "").lower() == "true": @@ -2511,9 +2628,9 @@ def create_reactions_from_data_node( def create_phases_from_data_node( ctml_tree: etree.Element, - species_data: Dict[str, List[Species]], - reaction_data: Dict[str, List[Reaction]], -) -> List[Phase]: + species_data: dict[str, list[Species]], + reaction_data: dict[str, list[Reaction]], +) -> list[Phase]: """Generate a list of `Phase` instances from XML ``phase`` nodes. :param ctml_tree: @@ -2527,7 +2644,7 @@ def create_phases_from_data_node( instances. For any Lattice-type phases, the child ``phase`` nodes are un-nested from their parent node. """ - phases = [ + phases: list[Phase] = [ Phase(node, species_data, reaction_data) for node in ctml_tree.iterfind("phase") ] l_nodes = [] @@ -2541,9 +2658,9 @@ def create_phases_from_data_node( def convert( - inpfile: Union[str, Path] = None, - outfile: Union[str, Path] = None, - text: str = None, + inpfile: str | Path | None = None, + outfile: str | Path | None = None, + text: str | None = None, ) -> None: """Convert an input legacy CTML file to a YAML file. @@ -2587,39 +2704,43 @@ def convert( ctml_text = re.sub("&(?!amp;|quot;|apos;|lt;|gt;)", "&", ctml_text) ctml_tree = etree.fromstring(ctml_text) - species_data = create_species_from_data_node(ctml_tree) - reaction_data = create_reactions_from_data_node(ctml_tree) - phases = create_phases_from_data_node(ctml_tree, species_data, reaction_data) + species_data: dict[str, list[Species]] = create_species_from_data_node(ctml_tree) + reaction_data: dict[str, list[Reaction]] = create_reactions_from_data_node( + ctml_tree + ) + phases: list[Phase] = create_phases_from_data_node( + ctml_tree, species_data, reaction_data + ) # This should be done after phase processing - output_species = BlockMap({}) + output_species: dict[str, list[Species]] = BlockMap({}) for species_node_id, species_list in species_data.items(): if not species_list: continue if species_node_id == "species_data": species_node_id = "species" output_species[species_node_id] = species_list - output_species.yaml_set_comment_before_after_key(species_node_id, before="\n") + output_species.yaml_set_comment_before_after_key(species_node_id, before="\n") # type: ignore[attr-defined] - output_reactions = BlockMap({}) + output_reactions: dict[str, list[Reaction]] = BlockMap({}) for reaction_node_id, reaction_list in reaction_data.items(): if not reaction_list: continue if reaction_node_id == "reaction_data": reaction_node_id = "reactions" output_reactions[reaction_node_id] = reaction_list - output_reactions.yaml_set_comment_before_after_key( + output_reactions.yaml_set_comment_before_after_key( # type: ignore[attr-defined] reaction_node_id, before="\n" ) - output_phases = BlockMap({"phases": phases}) + output_phases: CommentedMap = BlockMap({"phases": phases}) output_phases.yaml_set_comment_before_after_key("phases", before="\n") emitter = yaml.YAML() for cl in [Phase, Species, SpeciesThermo, SpeciesTransport, Reaction]: emitter.register_class(cl) - metadata = BlockMap( + metadata: CommentedMap = BlockMap( { "generator": "ctml2yaml", "cantera-version": "3.2.0a4", @@ -2637,11 +2758,11 @@ def convert( emitter.dump(output_reactions, output_file) -def create_argparser(): +def create_argparser() -> ArgumentParser: """ Create argparse parser """ - parser = argparse.ArgumentParser( + parser: ArgumentParser = ArgumentParser( description="Convert legacy CTML input files to YAML format", epilog=( "The 'output' argument is optional. If it is not given, an output " @@ -2655,14 +2776,14 @@ def create_argparser(): return parser -def main(): +def main() -> None: """Parse command line arguments and pass them to `convert`.""" parser = create_argparser() if len(sys.argv) not in [2, 3]: if len(sys.argv) > 3: print( "ctml2yaml.py: error: unrecognized arguments:", - ' '.join(sys.argv[3:]), + " ".join(sys.argv[3:]), file=sys.stderr, ) parser.print_help(sys.stderr) diff --git a/interfaces/cython/cantera/data.py b/interfaces/cython/cantera/data.py index 2832015560d..594a125d7c3 100644 --- a/interfaces/cython/cantera/data.py +++ b/interfaces/cython/cantera/data.py @@ -15,7 +15,7 @@ def list_data_files(ext=".yaml"): :return: List of input data files. """ - data_files = set() + data_files: set[str] = set() for folder in get_data_directories(): here = _Path(folder) if folder == ".": diff --git a/interfaces/cython/cantera/data.pyi b/interfaces/cython/cantera/data.pyi new file mode 100644 index 00000000000..d75486ce65a --- /dev/null +++ b/interfaces/cython/cantera/data.pyi @@ -0,0 +1,4 @@ +from cantera._utils import add_directory as add_directory +from cantera._utils import get_data_directories as get_data_directories + +def list_data_files(ext: str = ".yaml") -> list[str]: ... diff --git a/interfaces/cython/cantera/delegator.pyi b/interfaces/cython/cantera/delegator.pyi new file mode 100644 index 00000000000..1981082cdd2 --- /dev/null +++ b/interfaces/cython/cantera/delegator.pyi @@ -0,0 +1,10 @@ +# This file is part of Cantera. See License.txt in the top-level directory or +# at https://cantera.org/license.txt for license and copyright information. + +from typing import Callable + +from cantera.reaction import ExtensibleRateData + +def extension( + *, name: str, data: ExtensibleRateData | None = None +) -> Callable[[type], type]: ... diff --git a/interfaces/cython/cantera/drawnetwork.pyi b/interfaces/cython/cantera/drawnetwork.pyi new file mode 100644 index 00000000000..c1cbe752248 --- /dev/null +++ b/interfaces/cython/cantera/drawnetwork.pyi @@ -0,0 +1,67 @@ +# This file is part of Cantera. See License.txt in the top-level directory or +# at https://cantera.org/license.txt for license and copyright information. + +from collections.abc import Iterable +from functools import _Wrapped +from typing import Callable, Literal + +from graphviz import Digraph + +from .reactor import FlowDevice, ReactorBase, ReactorNet, ReactorSurface, Wall + +def _needs_graphviz( + func: Callable[..., Digraph], +) -> _Wrapped[..., Digraph, ..., Digraph]: ... +@_needs_graphviz +def draw_reactor( + r: ReactorBase, + graph: Digraph | None = None, + graph_attr: dict[str, str] | None = None, + node_attr: dict[str, str] | None = None, + print_state: bool = False, + species: Literal["X", "Y"] | bool | Iterable[str] | None = None, + species_units: Literal["percent", "ppm"] = "percent", +) -> Digraph: ... +@_needs_graphviz +def draw_reactor_net( + n: ReactorNet, + graph_attr: dict[str, str] | None = None, + node_attr: dict[str, str] | None = None, + edge_attr: dict[str, str] | None = None, + heat_flow_attr: dict[str, str] | None = None, + mass_flow_attr: dict[str, str] | None = None, + moving_wall_edge_attr: dict[str, str] | None = None, + surface_edge_attr: dict[str, str] | None = None, + show_wall_velocity: bool = True, + print_state: bool = False, + species: Literal["X", "Y"] | bool | Iterable[str] | None = None, + species_units: Literal["percent", "ppm"] = "percent", +) -> Digraph: ... +def draw_surface( + surface: ReactorSurface, + graph: Digraph | None = None, + graph_attr: dict[str, str] | None = None, + node_attr: dict[str, str] | None = None, + surface_edge_attr: dict[str, str] | None = None, + print_state: bool = False, + species: Literal["X", "Y"] | bool | Iterable[str] | None = None, + species_units: Literal["percent", "ppm"] = "percent", +) -> Digraph: ... +@_needs_graphviz +def draw_flow_controllers( + flow_controllers: list[FlowDevice], + graph: Digraph | None = None, + graph_attr: dict[str, str] | None = None, + node_attr: dict[str, str] | None = None, + edge_attr: dict[str, str] | None = None, +) -> Digraph: ... +@_needs_graphviz +def draw_walls( + walls: list[Wall], + graph: Digraph | None = None, + graph_attr: dict[str, str] | None = None, + node_attr: dict[str, str] | None = None, + edge_attr: dict[str, str] | None = None, + moving_wall_edge_attr: dict[str, str] | None = None, + show_wall_velocity: bool = True, +) -> Digraph: ... diff --git a/interfaces/cython/cantera/func1.pyi b/interfaces/cython/cantera/func1.pyi new file mode 100644 index 00000000000..528f5d0170d --- /dev/null +++ b/interfaces/cython/cantera/func1.pyi @@ -0,0 +1,34 @@ +# This file is part of Cantera. See License.txt in the top-level directory or +# at https://cantera.org/license.txt for license and copyright information. + +from collections.abc import Iterable +from typing import Any, Callable, Literal + +from typing_extensions import Never, override + +from ._types import ArrayLike + +class Func1: + callable: Callable[[float], float] + def __init__( + self, + c: str | Callable[[float], float] | ArrayLike, + *args: Any, + init: bool = True, + ) -> None: ... + @property + def type(self) -> str: ... + @property + def cxx_type(self) -> str: ... + def write(self, name: str = "x") -> str: ... + @override + def __reduce__(self) -> Never: ... + def __copy__(self) -> Never: ... + +class Tabulated1(Func1): + def __init__( + self, + time: Iterable[float], + fval: Iterable[float], + method: Literal["linear", "previous"], + ) -> None: ... diff --git a/interfaces/cython/cantera/interrupts.pyi b/interfaces/cython/cantera/interrupts.pyi new file mode 100644 index 00000000000..1dd017bc83a --- /dev/null +++ b/interfaces/cython/cantera/interrupts.pyi @@ -0,0 +1 @@ +def no_op(t: float) -> float: ... diff --git a/interfaces/cython/cantera/jacobians.pyi b/interfaces/cython/cantera/jacobians.pyi new file mode 100644 index 00000000000..60de1aecd6c --- /dev/null +++ b/interfaces/cython/cantera/jacobians.pyi @@ -0,0 +1,41 @@ +# This file is part of Cantera. See License.txt in the top-level directory or +# at https://cantera.org/license.txt for license and copyright information. + +from typing import Any, Literal + +from ._types import Array + +class SystemJacobian: + _type: str + linear_solver_type: Literal["GMRES", "direct"] + + def __init__(self, *args: Any, init: bool = True, **kwargs: Any) -> None: ... + @property + def side(self) -> Literal["none", "left", "right", "both"]: ... + @side.setter + def side(self, side: Literal["none", "left", "right", "both"]) -> None: ... + +class EigenSparseJacobian(SystemJacobian): + def print_contents(self) -> None: ... + @property + def matrix(self) -> Array: ... + @property + def jacobian(self) -> Array: ... + +class EigenSparseDirectJacobian(EigenSparseJacobian): ... + +class AdaptivePreconditioner(EigenSparseJacobian): + @property + def threshold(self) -> float: ... + @threshold.setter + def threshold(self, val: float) -> None: ... + @property + def ilut_fill_factor(self) -> float: ... + @ilut_fill_factor.setter + def ilut_fill_factor(self, val: float) -> None: ... + @property + def ilut_drop_tol(self) -> float: ... + @ilut_drop_tol.setter + def ilut_drop_tol(self, val: float) -> None: ... + +class BandedJacobian(SystemJacobian): ... diff --git a/interfaces/cython/cantera/kinetics.pyi b/interfaces/cython/cantera/kinetics.pyi new file mode 100644 index 00000000000..ed50f041fde --- /dev/null +++ b/interfaces/cython/cantera/kinetics.pyi @@ -0,0 +1,192 @@ +# This file is part of Cantera. See License.txt in the top-level directory or +# at https://cantera.org/license.txt for license and copyright information. + +from collections.abc import Sequence +from typing import Literal, TypeAlias, TypedDict + +from ._types import Array +from .reaction import CustomRate, Reaction +from .solutionbase import _SolutionBase +from .thermo import ThermoPhase +from .units import UnitDict, UnitDictBytes, UnitSystem + +KineticsType: TypeAlias = Literal["none", "bulk", "edge", "surface"] + +class DerivativeSettings(TypedDict, total=False): + skip_third_bodies: bool + skip_falloff: bool + rtol_delta: float + +class Kinetics(_SolutionBase): + _custom_rates: list[CustomRate] + @property + def kinetics_model(self) -> str: ... + @property + def n_total_species(self) -> int: ... + @property + def n_reactions(self) -> int: ... + @property + def n_phases(self) -> int: ... + def kinetics_species_index( + self, species: str | bytes | int, phase: int = 0 + ) -> int: ... + def kinetics_species_name(self, k: int) -> str: ... + @property + def kinetics_species_names(self) -> list[str]: ... + def reaction(self, i_reaction: int) -> Reaction: ... + def reactions(self) -> list[Reaction]: ... + def modify_reaction(self, irxn: int, rxn: Reaction) -> None: ... + def add_reaction(self, rxn: Reaction) -> None: ... + def multiplier(self, i_reaction: int) -> float: ... + def set_multiplier(self, value: float, i_reaction: int = -1) -> None: ... + def reaction_equations(self, indices: Sequence[int] | None = None) -> list[str]: ... + def reactant_stoich_coeff( + self, k_spec: str | bytes | int, i_reaction: int + ) -> float: ... + def product_stoich_coeff( + self, k_spec: str | bytes | int, i_reaction: int + ) -> float: ... + @property + def reactant_stoich_coeffs(self) -> Array: ... + @property + def product_stoich_coeffs(self) -> Array: ... + @property + def product_stoich_coeffs_reversible(self) -> Array: ... + @property + def forward_rates_of_progress(self) -> Array: ... + @property + def reverse_rates_of_progress(self) -> Array: ... + @property + def net_rates_of_progress(self) -> Array: ... + @property + def equilibrium_constants(self) -> Array: ... + @property + def forward_rate_constants(self) -> Array: ... + @property + def reverse_rate_constants(self) -> Array: ... + @property + def creation_rates(self) -> Array: ... + @property + def destruction_rates(self) -> Array: ... + @property + def net_production_rates(self) -> Array: ... + @property + def derivative_settings(self) -> DerivativeSettings: ... + @derivative_settings.setter + def derivative_settings(self, settings: DerivativeSettings) -> None: ... + @property + def forward_rate_constants_ddT(self) -> Array: ... + @property + def forward_rate_constants_ddP(self) -> Array: ... + @property + def forward_rate_constants_ddC(self) -> Array: ... + @property + def forward_rates_of_progress_ddT(self) -> Array: ... + @property + def forward_rates_of_progress_ddP(self) -> Array: ... + @property + def forward_rates_of_progress_ddC(self) -> Array: ... + @property + def forward_rates_of_progress_ddX(self) -> Array: ... + @property + def forward_rates_of_progress_ddCi(self) -> Array: ... + @property + def reverse_rates_of_progress_ddT(self) -> Array: ... + @property + def reverse_rates_of_progress_ddP(self) -> Array: ... + @property + def reverse_rates_of_progress_ddC(self) -> Array: ... + @property + def reverse_rates_of_progress_ddX(self) -> Array: ... + @property + def reverse_rates_of_progress_ddCi(self) -> Array: ... + @property + def net_rates_of_progress_ddT(self) -> Array: ... + @property + def net_rates_of_progress_ddP(self) -> Array: ... + @property + def net_rates_of_progress_ddC(self) -> Array: ... + @property + def net_rates_of_progress_ddX(self) -> Array: ... + @property + def net_rates_of_progress_ddCi(self) -> Array: ... + @property + def creation_rates_ddT(self) -> Array: ... + @property + def creation_rates_ddP(self) -> Array: ... + @property + def creation_rates_ddC(self) -> Array: ... + @property + def creation_rates_ddX(self) -> Array: ... + @property + def creation_rates_ddCi(self) -> Array: ... + @property + def destruction_rates_ddT(self) -> Array: ... + @property + def destruction_rates_ddP(self) -> Array: ... + @property + def destruction_rates_ddC(self) -> Array: ... + @property + def destruction_rates_ddX(self) -> Array: ... + @property + def destruction_rates_ddCi(self) -> Array: ... + @property + def net_production_rates_ddT(self) -> Array: ... + @property + def net_production_rates_ddP(self) -> Array: ... + @property + def net_production_rates_ddC(self) -> Array: ... + @property + def net_production_rates_ddX(self) -> Array: ... + @property + def net_production_rates_ddCi(self) -> Array: ... + @property + def delta_enthalpy(self) -> Array: ... + @property + def delta_gibbs(self) -> Array: ... + @property + def delta_entropy(self) -> Array: ... + @property + def delta_standard_enthalpy(self) -> Array: ... + @property + def third_body_concentrations(self) -> Array: ... + @property + def delta_standard_gibbs(self) -> Array: ... + @property + def delta_standard_entropy(self) -> Array: ... + @property + def heat_release_rate(self) -> float: ... + @property + def heat_production_rates(self) -> Array: ... + +class InterfaceKinetics(Kinetics): + def __init__( + self, + infile: str = "", + name: str = "", + adjacent: Sequence[ThermoPhase] = (), + ) -> None: ... + def advance_coverages( + self, + dt: float, + rtol: float = 1e-7, + atol: float = 1e-14, + max_step_size: float = 0, + max_steps: int = 20000, + max_error_test_failures: int = 7, + ) -> None: ... + def advance_coverages_to_steady_state(self) -> None: ... + def phase_index(self, phase: ThermoPhase | str | int) -> int: ... + def _phase_slice(self, phase: ThermoPhase | str | int) -> slice[int, int, None]: ... + def get_creation_rates(self, phase: ThermoPhase | str | int) -> Array: ... + def get_destruction_rates(self, phase: ThermoPhase | str | int) -> Array: ... + def get_net_production_rates(self, phase: ThermoPhase | str | int) -> Array: ... + def interface_current(self, phase: ThermoPhase | str | int) -> float: ... + def write_yaml( # type: ignore[override] + self, + filename: str, + phases: Sequence[ThermoPhase] | None = None, + units: UnitSystem | UnitDict | UnitDictBytes | None = None, + precision: int | None = None, + skip_user_defined: bool | None = None, + ) -> None: ... diff --git a/interfaces/cython/cantera/liquidvapor.pyi b/interfaces/cython/cantera/liquidvapor.pyi new file mode 100644 index 00000000000..fe4b4ada149 --- /dev/null +++ b/interfaces/cython/cantera/liquidvapor.pyi @@ -0,0 +1,12 @@ +from typing import Literal + +from cantera.thermo import PureFluid + +def Water(backend: Literal["Reynolds", "IAPWS95"] = "Reynolds") -> PureFluid: ... +def Nitrogen() -> PureFluid: ... +def Methane() -> PureFluid: ... +def Hydrogen() -> PureFluid: ... +def Oxygen() -> PureFluid: ... +def Hfc134a() -> PureFluid: ... +def CarbonDioxide() -> PureFluid: ... +def Heptane() -> PureFluid: ... diff --git a/interfaces/cython/cantera/lxcat2yaml.py b/interfaces/cython/cantera/lxcat2yaml.py index 5ed55275d85..eda3b873da0 100644 --- a/interfaces/cython/cantera/lxcat2yaml.py +++ b/interfaces/cython/cantera/lxcat2yaml.py @@ -21,33 +21,41 @@ --phase=isotropic-electron-energy-plasma --insert --output=oxygen-itikawa-plasma.yaml """ +from __future__ import annotations -from pathlib import Path import argparse +import sys import textwrap import xml.etree.ElementTree as etree -from typing import Union, Optional, List -import sys +from collections.abc import Iterable, Sequence +from pathlib import Path +from typing import TypeAlias, TypeVar, cast + from ruamel import yaml +from ruamel.yaml.comments import CommentedMap, CommentedSeq +from ruamel.yaml.nodes import MappingNode +from ruamel.yaml.representer import SafeRepresenter + try: import cantera as ct - Solution = ct.Solution + OptionalSolutionType: TypeAlias = ct.Solution | None + Solution: type[ct.Solution] | None = ct.Solution except ImportError: print("The Cantera Python module was not found" ", so the mechanism file cannot be used.") Solution = None -BlockMap = yaml.comments.CommentedMap +BlockMap: type[CommentedMap] = CommentedMap class Process: """A class of YAML data for collision of a target species""" - def __init__(self, equation, energy_levels, cross_sections): + def __init__(self, equation: str, energy_levels: list[float], cross_sections: list[float]) -> None: self.equation = equation self.energy_levels = energy_levels self.cross_sections = cross_sections @classmethod - def to_yaml(cls, representer, node): + def to_yaml(cls, representer: SafeRepresenter, node: Process) -> MappingNode: out = BlockMap([('equation', node.equation), ('type', 'electron-collision-plasma'), ('energy-levels', node.energy_levels), @@ -56,21 +64,23 @@ def to_yaml(cls, representer, node): return representer.represent_dict(out) # Define YAML emitter -emitter = yaml.YAML() +emitter: yaml.YAML = yaml.YAML() emitter.register_class(Process) # Return indices of a child name -def get_children(parent, child_name): +def get_children(parent: etree.Element[str], child_name: str) -> list[etree.Element[str]]: return [child for child in parent if child.tag.find(child_name) != -1] -def FlowList(*args, **kwargs): +_VT = TypeVar("_VT") # Value type. + +def Flowlist(*args: Iterable[_VT], **kwargs: _VT) -> list[_VT]: """A YAML sequence that flows onto one line.""" - lst = yaml.comments.CommentedSeq(*args, **kwargs) + lst: CommentedSeq = CommentedSeq(*args, **kwargs) lst.fa.set_flow_style() - return lst + return cast(list[_VT], lst) class IncorrectXMLNode(LookupError): - def __init__(self, message: str = "", node: Optional[etree.Element] = None): + def __init__(self, message: str = "", node: etree.Element | None = None) -> None: """Error raised when a required node is incorrect in the XML tree. :param message: @@ -91,13 +101,13 @@ def __init__(self, message: str = "", node: Optional[etree.Element] = None): super().__init__(message) def convert( - inpfile: Optional[Union[str, Path]] = None, - database: Optional[str] = None, - mechfile: Optional[str] = None, - phase: Optional[str] = None, - insert: Optional[bool] = True, - outfile: Optional[Union[str, Path]] = None, - ) -> None: + inpfile: str | Path | None = None, + database: str | None = None, + mechfile: str | None = None, + phase: str | None = None, + insert: bool | None = True, + outfile: str | Path | None = None, +) -> None: """Convert an LXCat XML file to a YAML file. :param inpfile: @@ -128,7 +138,7 @@ def convert( if insert and mechfile is None: raise ValueError("'mech' must be specified if 'insert' is used") - gas = None + gas: OptionalSolutionType = None if mechfile is not None: if Solution is None: print("Cantera is not used, so the mechanism file cannot be used.") @@ -145,7 +155,7 @@ def convert( # If insert key word is used, create a process list, # and append all processes together - process_list = None + process_list: list[Process] | None = None if not insert: process_list = [] @@ -169,15 +179,17 @@ def convert( else: # Get mechanism file unit system units = None + assert mechfile is not None with open(mechfile, "r") as mech: data = yaml.YAML(typ="rt").load(mech) if "units" in data: units = data["units"] + assert gas is not None gas.write_yaml(outfile, units=units) def registerProcess(process: etree.Element, - process_list: List[Process], - gas: Solution): + process_list: list[Process] | None, + gas: OptionalSolutionType) -> None: """ Add a collision process (electron collision reaction) to process_list and gas object if it exists. @@ -198,18 +210,20 @@ def registerProcess(process: etree.Element, if len(get_children(parameters_node, "parameter")) == 1: parameter = get_children(parameters_node, "parameter")[0] if parameter.attrib["name"] == 'E': + assert parameter.text is not None threshold = float(parameter.text) # Parse the equation - product_array=[] + product_array: list[str] = [] - products = get_children(process, "products") + products: list[etree.Element[str]] = get_children(process, "products") if products: for product_node in products[0]: if product_node.tag.find("electron") != -1: product_array.append(electron_name) if product_node.tag.find("molecule") != -1: + assert product_node.text is not None product_name = product_node.text if "state" in product_node.attrib: state = product_node.attrib["state"].replace(" ","-") @@ -236,12 +250,12 @@ def registerProcess(process: etree.Element, return if product_array: # not empty - products = " + ".join(product_array) + products_string = " + ".join(product_array) else: # No product is identified. Use the reactant as the product. - products = f"{reactant} + {electron_name}" + products_string = f"{reactant} + {electron_name}" - equation = f"{reactant} + {electron_name} => {products}" + equation = f"{reactant} + {electron_name} => {products_string}" # Parse the cross-section data data_x_node = get_children(process, "data_x")[0] @@ -252,8 +266,10 @@ def registerProcess(process: etree.Element, if data_y_node is None: raise IncorrectXMLNode("The 'process' node requires the 'data_y' node.", process) - energy_levels = FlowList(map(float, data_x_node.text.split())) - cross_sections = FlowList(map(float, data_y_node.text.split())) + assert data_x_node.text is not None + assert data_y_node.text is not None + energy_levels = Flowlist(map(float, data_x_node.text.split())) + cross_sections = Flowlist(map(float, data_y_node.text.split())) # Edit energy levels and cross section if len(energy_levels) != len(cross_sections): @@ -261,9 +277,9 @@ def registerProcess(process: etree.Element, "(data_y) must have the same length.", process) if energy_levels[0] > threshold: - # Use FlowList again to ensure correct YAML format - energy_levels = FlowList([threshold, *energy_levels]) - cross_sections = FlowList([0.0, *cross_sections]) + # Use Flowlist again to ensure correct YAML format + energy_levels = Flowlist([threshold, *energy_levels]) + cross_sections = Flowlist([0.0, *cross_sections]) else: cross_sections[0] = 0.0 @@ -281,7 +297,7 @@ def registerProcess(process: etree.Element, energy_levels=energy_levels, cross_sections=cross_sections)) -def create_argparser(): +def create_argparser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser( description=( "Convert the LXCat integral cross-section data in XML format (LXCATML) to " @@ -336,7 +352,7 @@ def create_argparser(): return parser -def main(argv=None): +def main(argv: Sequence[str] | None = None) -> None: """Parse command line arguments and pass them to `convert`.""" parser = create_argparser() if argv is None and len(sys.argv) < 2: @@ -346,7 +362,7 @@ def main(argv=None): input_file = Path(args.input) - output_file = args.output or input_file.with_suffix(".yaml") + output_file: Path | str = args.output or input_file.with_suffix(".yaml") convert(input_file, args.database, args.mech, args.phase, args.insert, output_file) if args.insert and Solution is not None: diff --git a/interfaces/cython/cantera/mixture.pyi b/interfaces/cython/cantera/mixture.pyi new file mode 100644 index 00000000000..1d7c88c7de1 --- /dev/null +++ b/interfaces/cython/cantera/mixture.pyi @@ -0,0 +1,62 @@ +# This file is part of Cantera. See License.txt in the top-level directory or +# at https://cantera.org/license.txt for license and copyright information. + +from collections.abc import Sequence +from typing import Literal + +from ._types import Array, ArrayLike, EquilibriumSolver, PropertyPair +from .thermo import ThermoPhase + +class Mixture: + def __init__(self, phases: Sequence[tuple[ThermoPhase, float]]) -> None: ... + def report(self, threshold: float = 1e-14) -> str: ... + def __call__(self) -> None: ... + @property + def n_elements(self) -> int: ... + def element_index(self, element: str | bytes | float) -> int: ... + @property + def n_species(self) -> int: ... + @property + def species_names(self) -> list[str]: ... + def species_index( + self, phase: ThermoPhase | str | int, species: str | bytes | int + ) -> int: ... + def n_atoms(self, k: int, m: int) -> int: ... + @property + def n_phases(self) -> int: ... + def phase(self, n: int) -> ThermoPhase: ... + def phase_index(self, p: str) -> int: ... + @property + def phase_names(self) -> list[str]: ... + @property + def T(self) -> float: ... + @T.setter + def T(self, T: float) -> None: ... + @property + def max_temp(self) -> float: ... + @property + def P(self) -> float: ... + @P.setter + def P(self, P: float) -> None: ... + @property + def charge(self) -> float: ... + def phase_charge(self, p: str) -> float: ... + def phase_moles(self, p: str | None = None) -> list[float] | float: ... + def set_phase_moles(self, p: str, moles: float) -> None: ... + @property + def species_moles(self) -> Array: ... + @species_moles.setter + def species_moles(self, moles: str | bytes | ArrayLike) -> None: ... + def element_moles(self, e: str) -> float: ... + @property + def chemical_potentials(self) -> Array: ... + def equilibrate( + self, + XY: PropertyPair, + solver: EquilibriumSolver, + rtol: float = 1e-9, + max_steps: int = 1000, + max_iter: int = 100, + estimate_equil: Literal[-1, 0, 1] = 0, + log_level: int = 0, + ) -> None: ... diff --git a/interfaces/cython/cantera/onedim.pyi b/interfaces/cython/cantera/onedim.pyi new file mode 100644 index 00000000000..153015905a7 --- /dev/null +++ b/interfaces/cython/cantera/onedim.pyi @@ -0,0 +1,446 @@ +# This file is part of Cantera. See License.txt in the top-level directory or +# at https://cantera.org/license.txt for license and copyright information. + +from collections.abc import Iterable, Sequence +from pathlib import Path +from typing import Any, Literal + +from pandas import DataFrame + +from ._onedim import ( + AxisymmetricFlow, + Domain1D, + FreeFlow, + Inlet1D, + Outlet1D, + ReactingSurface1D, + Sim1D, + Surface1D, + SymmetryPlane1D, + UnstrainedFlow, +) +from ._types import ( + Array, + ArrayLike, + Basis, + CompositionLike, + LogLevel, + RefineCriteria, +) +from ._utils import __git_commit__ as __git_commit__ +from ._utils import __version__ as __version__ +from ._utils import hdf_support as hdf_support +from .composite import Solution, SolutionArray +from .kinetics import Kinetics + +class FlameBase(Sim1D): + gas: Solution + def __init__( + self, domains: Iterable[Domain1D], gas: Solution, grid: ArrayLike | None = None + ) -> None: ... + def set_refine_criteria( # type: ignore[override] + self, + ratio: float = 10.0, + slope: float = 0.8, + curve: float = 0.8, + prune: float = 0.0, + ) -> None: ... + def get_refine_criteria(self) -> RefineCriteria: ... # type: ignore[override] + def set_initial_guess( + self, + *args: Any, + data: SolutionArray[Solution] | DataFrame | str | Path | None = None, + group: str | None = None, + **kwargs: Any, + ) -> None: ... + def set_profile( # type: ignore[override] + self, component: str | int, positions: Sequence[float], values: Sequence[float] + ) -> None: ... + @property + def max_grid_points(self) -> int: ... + @max_grid_points.setter + def max_grid_points(self, npmax: int) -> None: ... + @property + def transport_model(self) -> str: ... + @transport_model.setter + def transport_model(self, model: str) -> None: ... + @property + def energy_enabled(self) -> bool: ... + @energy_enabled.setter + def energy_enabled(self, enable: bool) -> None: ... + @property + def soret_enabled(self) -> bool: ... + @soret_enabled.setter + def soret_enabled(self, enable: bool) -> None: ... + @property + def flux_gradient_basis(self) -> Basis: ... + @flux_gradient_basis.setter + def flux_gradient_basis(self, basis: Basis) -> None: ... + @property + def radiation_enabled(self) -> bool: ... + @radiation_enabled.setter + def radiation_enabled(self, enable: bool) -> None: ... + @property + def boundary_emissivities(self) -> tuple[float, float]: ... + @boundary_emissivities.setter + def boundary_emissivities(self, epsilon: Sequence[float]) -> None: ... + @property + def grid(self) -> Array: ... + @property + def P(self) -> float: ... + @P.setter + def P(self, P: float) -> None: ... + @property + def T(self) -> Array: ... + @property + def velocity(self) -> Array: ... + @property + def spread_rate(self) -> Array: ... + @property + def L(self) -> Array: ... + @property + def E(self) -> Array: ... + @property + def Uo(self) -> Array: ... + @property + def left_control_point_temperature(self) -> float: ... + @left_control_point_temperature.setter + def left_control_point_temperature(self, T: float) -> None: ... + @property + def left_control_point_coordinate(self) -> float: ... + @property + def right_control_point_temperature(self) -> float: ... + @right_control_point_temperature.setter + def right_control_point_temperature(self, T: float) -> None: ... + @property + def right_control_point_coordinate(self) -> float: ... + def elemental_mass_fraction(self, m: str) -> Array: ... + def elemental_mole_fraction(self, m: str) -> Array: ... + def set_gas_state(self, point: int) -> None: ... + def to_array( + self, domain: Domain1D | str | int | None = None, normalize: bool = False + ) -> SolutionArray[Solution]: ... + def from_array( + self, arr: SolutionArray[Solution], domain: Domain1D | str | int | None = None + ) -> None: ... + def to_pandas( + self, species: Literal["X", "Y"] = "X", normalize: bool = True + ) -> Array: ... + @property + def electric_field_enabled(self) -> bool: ... + @electric_field_enabled.setter + def electric_field_enabled(self, enable: bool) -> None: ... + @property + def two_point_control_enabled(self) -> bool: ... + @two_point_control_enabled.setter + def two_point_control_enabled(self, enable: bool) -> None: ... + + # Dynamically-added properties + @property + def density(self) -> Array: ... + @property + def density_mass(self) -> Array: ... + @property + def density_mole(self) -> Array: ... + @property + def volume_mass(self) -> Array: ... + @property + def volume_mole(self) -> Array: ... + @property + def int_energy_mole(self) -> Array: ... + @property + def int_energy_mass(self) -> Array: ... + @property + def h(self) -> Array: ... + @property + def enthalpy_mole(self) -> Array: ... + @property + def enthalpy_mass(self) -> Array: ... + @property + def s(self) -> Array: ... + @property + def entropy_mole(self) -> Array: ... + @property + def entropy_mass(self) -> Array: ... + @property + def g(self) -> Array: ... + @property + def gibbs_mole(self) -> Array: ... + @property + def gibbs_mass(self) -> Array: ... + @property + def cv(self) -> Array: ... + @property + def cv_mole(self) -> Array: ... + @property + def cv_mass(self) -> Array: ... + @property + def cp(self) -> Array: ... + @property + def cp_mole(self) -> Array: ... + @property + def cp_mass(self) -> Array: ... + @property + def isothermal_compressibility(self) -> Array: ... + @property + def thermal_expansion_coeff(self) -> Array: ... + @property + def sound_speed(self) -> Array: ... + @property + def viscosity(self) -> Array: ... + @property + def thermal_conductivity(self) -> Array: ... + @property + def heat_release_rate(self) -> Array: ... + @property + def mean_molecular_weight(self) -> Array: ... + @property + def volume(self) -> Array: ... + @property + def int_energy(self) -> Array: ... + @property + def X(self) -> Array: ... + @property + def Y(self) -> Array: ... + @property + def concentrations(self) -> Array: ... + @property + def partial_molar_enthalpies(self) -> Array: ... + @property + def partial_molar_entropies(self) -> Array: ... + @property + def partial_molar_int_energies(self) -> Array: ... + @property + def chemical_potentials(self) -> Array: ... + @property + def electrochemical_potentials(self) -> Array: ... + @property + def partial_molar_cp(self) -> Array: ... + @property + def partial_molar_volumes(self) -> Array: ... + @property + def standard_enthalpies_RT(self) -> Array: ... + @property + def standard_entropies_R(self) -> Array: ... + @property + def standard_int_energies_RT(self) -> Array: ... + @property + def standard_gibbs_RT(self) -> Array: ... + @property + def standard_cp_R(self) -> Array: ... + @property + def creation_rates(self) -> Array: ... + @property + def destruction_rates(self) -> Array: ... + @property + def net_production_rates(self) -> Array: ... + @property + def creation_rates_ddC(self) -> Array: ... + @property + def creation_rates_ddP(self) -> Array: ... + @property + def creation_rates_ddT(self) -> Array: ... + @property + def destruction_rates_ddC(self) -> Array: ... + @property + def destruction_rates_ddP(self) -> Array: ... + @property + def destruction_rates_ddT(self) -> Array: ... + @property + def net_production_rates_ddC(self) -> Array: ... + @property + def net_production_rates_ddP(self) -> Array: ... + @property + def net_production_rates_ddT(self) -> Array: ... + @property + def mix_diff_coeffs(self) -> Array: ... + @property + def mix_diff_coeffs_mass(self) -> Array: ... + @property + def mix_diff_coeffs_mole(self) -> Array: ... + @property + def thermal_diff_coeffs(self) -> Array: ... + @property + def activities(self) -> Array: ... + @property + def activity_coefficients(self) -> Array: ... + @property + def mobilities(self) -> Array: ... + @property + def species_viscosities(self) -> Array: ... + @property + def forward_rates_of_progress(self) -> Array: ... + @property + def reverse_rates_of_progress(self) -> Array: ... + @property + def net_rates_of_progress(self) -> Array: ... + @property + def equilibrium_constants(self) -> Array: ... + @property + def forward_rate_constants(self) -> Array: ... + @property + def reverse_rate_constants(self) -> Array: ... + @property + def delta_enthalpy(self) -> Array: ... + @property + def delta_gibbs(self) -> Array: ... + @property + def delta_entropy(self) -> Array: ... + @property + def delta_standard_enthalpy(self) -> Array: ... + @property + def delta_standard_gibbs(self) -> Array: ... + @property + def delta_standard_entropy(self) -> Array: ... + @property + def heat_production_rates(self) -> Array: ... + @property + def third_body_concentrations(self) -> Array: ... + @property + def forward_rate_constants_ddC(self) -> Array: ... + @property + def forward_rate_constants_ddP(self) -> Array: ... + @property + def forward_rate_constants_ddT(self) -> Array: ... + @property + def forward_rates_of_progress_ddC(self) -> Array: ... + @property + def forward_rates_of_progress_ddP(self) -> Array: ... + @property + def forward_rates_of_progress_ddT(self) -> Array: ... + @property + def net_rates_of_progress_ddC(self) -> Array: ... + @property + def net_rates_of_progress_ddP(self) -> Array: ... + @property + def net_rates_of_progress_ddT(self) -> Array: ... + @property + def reverse_rates_of_progress_ddC(self) -> Array: ... + @property + def reverse_rates_of_progress_ddP(self) -> Array: ... + @property + def reverse_rates_of_progress_ddT(self) -> Array: ... + +class FreeFlame(FlameBase): + inlet: Inlet1D + outlet: Outlet1D + flame: FreeFlow + def __init__( + self, gas: Solution, grid: ArrayLike | None = None, width: float | None = None + ) -> None: ... + def set_initial_guess( # type: ignore[override] + self, + locs: ArrayLike = [0.0, 0.3, 0.5, 1.0], + data: SolutionArray[Solution] | DataFrame | str | Path | None = None, + group: str | None = None, + ) -> None: ... + def solve( + self, + loglevel: LogLevel = 1, + refine_grid: bool = True, + auto: bool = False, + stage: Literal[1, 2] = 1, + ) -> None: ... + def get_flame_speed_reaction_sensitivities(self) -> Array: ... + +class BurnerFlame(FlameBase): + burner: Inlet1D + outlet: Outlet1D + flame: UnstrainedFlow + def __init__( + self, gas: Solution, grid: ArrayLike | None = None, width: float | None = None + ) -> None: ... + def set_initial_guess( # type: ignore[override] + self, + data: SolutionArray[Solution] | DataFrame | str | Path | None = None, + group: str | None = None, + ) -> None: ... + def solve( + self, + loglevel: LogLevel = 1, + refine_grid: bool = True, + auto: bool = False, + stage: Literal[1, 2] = 1, + ) -> None: ... + +class CounterflowDiffusionFlame(FlameBase): + fuel_inlet: Inlet1D + oxidizer_inlet: Inlet1D + flame: AxisymmetricFlow + def __init__( + self, gas: Solution, grid: ArrayLike | None = None, width: float | None = None + ) -> None: ... + def set_initial_guess( # type: ignore[override] + self, + data: SolutionArray[Solution] | DataFrame | str | Path | None = None, + group: str | None = None, + ) -> None: ... + def extinct(self) -> bool: ... + def solve( + self, + loglevel: LogLevel = 1, + refine_grid: bool = True, + auto: bool = False, + stage: Literal[1, 2] = 1, + ) -> None: ... + def strain_rate( + self, + definition: Literal[ + "mean", + "max", + "stoichiometric", + "potential_flow_fuel", + "potential_flow_oxidizer", + ], + fuel: CompositionLike | None = None, + oxidizer: CompositionLike = "O2", + stoich: float | None = None, + ) -> float: ... + def mixture_fraction(self, m: str | int) -> Array: ... + @property + def equivalence_ratio(self) -> Array: ... + +class ImpingingJet(FlameBase): + inlet: Inlet1D + flame: AxisymmetricFlow + surface: Surface1D | ReactingSurface1D + def __init__( + self, + gas: Solution, + grid: ArrayLike | None = None, + width: float | None = None, + surface: Kinetics | None = None, + ) -> None: ... + def set_initial_guess( # type: ignore[override] + self, + products: Literal["inlet", "equil"] = "inlet", + data: SolutionArray[Solution] | DataFrame | str | Path | None = None, + group: str | None = None, + ) -> None: ... + +class CounterflowPremixedFlame(FlameBase): + reactants: Inlet1D + products: Inlet1D + flame: AxisymmetricFlow + def __init__( + self, gas: Solution, grid: ArrayLike | None = None, width: float | None = None + ) -> None: ... + def set_initial_guess( # type: ignore[override] + self, + equilibrate: bool = True, + data: SolutionArray[Solution] | DataFrame | str | Path | None = None, + group: str | None = None, + ) -> None: ... + +class CounterflowTwinPremixedFlame(FlameBase): + reactants: Inlet1D + flame: AxisymmetricFlow + products: SymmetryPlane1D + def __init__( + self, gas: Solution, grid: ArrayLike | None = None, width: float | None = None + ) -> None: ... + def set_initial_guess( # type: ignore[override] + self, + data: SolutionArray[Solution] | DataFrame | str | Path | None = None, + group: str | None = None, + ) -> None: ... diff --git a/interfaces/cython/cantera/py.typed b/interfaces/cython/cantera/py.typed new file mode 100644 index 00000000000..139597f9cb0 --- /dev/null +++ b/interfaces/cython/cantera/py.typed @@ -0,0 +1,2 @@ + + diff --git a/interfaces/cython/cantera/reaction.pyi b/interfaces/cython/cantera/reaction.pyi new file mode 100644 index 00000000000..ece98f5069c --- /dev/null +++ b/interfaces/cython/cantera/reaction.pyi @@ -0,0 +1,407 @@ +# This file is part of Cantera. See License.txt in the top-level directory or +# at https://cantera.org/license.txt for license and copyright information. + +from collections.abc import Callable, Iterable, Sequence +from typing import ( + Generic, + TypeAlias, + TypedDict, + TypeVar, +) + +from typing_extensions import NotRequired + +from ._types import Array, ArrayLike +from .composite import Solution +from .kinetics import Kinetics +from .units import Units + +class ArrheniusParameters(TypedDict): + A: float + b: float + Ea: float + +class BlowersMaselParameters(TypedDict): + A: float + b: float + Ea0: float + w: float + +class TwoTempPlasmaParameters(TypedDict): + A: float + b: float + Ea_gas: float + Ea_electron: float + +class ElectronCollisionPlasmaParameters(TypedDict): + energy_levels: list[float] + cross_sections: list[float] + +class PlogParameters(ArrheniusParameters): + P: float + +ReactionRateParameters: TypeAlias = ( + ArrheniusParameters + | BlowersMaselParameters + | TwoTempPlasmaParameters + | ElectronCollisionPlasmaParameters + | PlogParameters +) + +class TroeParameters(TypedDict): + A: float + T3: float + T1: float + T2: NotRequired[float] + +class CoverageParameters(TypedDict): + a: float + m: float + E: float + +T = TypeVar("T") + +class ReactionRateInput(TypedDict, Generic[T], total=False): + type: str + rate_constant: T + efficiencies: dict[str, float] + Troe: TroeParameters + coverage_dependencies: dict[str, CoverageParameters] + +class ReactionInput(ReactionRateInput[T]): + equation: str + +class FalloffRateInput(TypedDict, total=False): + type: str + low_P_rate_constant: ArrheniusParameters + high_P_rate_constant: ArrheniusParameters + Troe: TroeParameters + +class PlogRateInput(TypedDict, total=False): + type: str + rate_constants: Sequence[PlogParameters] + +class ReactionRate: + def __call__(self, temperature: float) -> float: ... + @property + def type(self) -> str: ... + @property + def sub_type(self) -> str: ... + @classmethod + def from_dict( + cls, data: ReactionRateInput[ReactionRateParameters], hyphenize: bool = True + ) -> ReactionRate: ... + @classmethod + def from_yaml(cls, data: str, hyphenize: bool = True) -> ReactionRate: ... + @property + def input_data(self) -> ReactionRateInput[ReactionRateParameters]: ... + @property + def conversion_units(self) -> Units: ... + +class ArrheniusRateBase(ReactionRate): + @property + def pre_exponential_factor(self) -> float: ... + @property + def temperature_exponent(self) -> float: ... + @property + def activation_energy(self) -> float: ... + @property + def allow_negative_pre_exponential_factor(self) -> bool: ... + @allow_negative_pre_exponential_factor.setter + def allow_negative_pre_exponential_factor(self, allow: bool) -> None: ... + +class ArrheniusRate(ArrheniusRateBase): + def __init__( + self, + A: float | None = None, + b: float | None = None, + Ea: float | None = None, + input_data: ReactionRateInput[ArrheniusParameters] | None = None, + init: bool = True, + ) -> None: ... + def _from_parameters(self, A: float, b: float, Ea: float) -> None: ... + +class BlowersMaselRate(ArrheniusRateBase): + def __init__( + self, + A: float | None = None, + b: float | None = None, + Ea0: float | None = None, + w: float | None = None, + input_data: ReactionRateInput[BlowersMaselParameters] | None = None, + init: bool = True, + ) -> None: ... + def _from_parameters(self, A: float, b: float, Ea0: float, w: float) -> None: ... + @property + def bond_energy(self) -> float: ... + @property + def delta_enthalpy(self) -> float: ... + @delta_enthalpy.setter + def delta_enthalpy(self, delta_H: float) -> None: ... + +class TwoTempPlasmaRate(ArrheniusRateBase): + def __init__( + self, + A: float | None = None, + b: float | None = None, + Ea_gas: float = 0.0, + Ea_electron: float = 0.0, + input_data: ReactionRateInput[TwoTempPlasmaParameters] | None = None, + init: bool = True, + ) -> None: ... + def _from_parameters( + self, A: float, b: float, Ea_gas: float, Ea_electron: float + ) -> None: ... + @property + def activation_electron_energy(self) -> float: ... + +class ElectronCollisionPlasmaRate(ReactionRate): + def __init__( + self, + energy_levels: ArrayLike | None = None, + cross_sections: ArrayLike | None = None, + input_data: ReactionRateInput[ElectronCollisionPlasmaParameters] | None = None, + init: bool = True, + ) -> None: ... + def _from_parameters( + self, energy_levels: ArrayLike, cross_sections: ArrayLike + ) -> None: ... + @property + def energy_levels(self) -> Array: ... + @property + def cross_sections(self) -> Array: ... + +class FalloffRate(ReactionRate): + def __init__( + self, + low: ArrheniusParameters | None = None, + high: ArrheniusParameters | None = None, + falloff_coeffs: Sequence[float] | None = None, + input_data: ReactionRateInput[FalloffRateInput] | None = None, + init: bool = True, + ) -> None: ... + def __call__(self, temperature: float, concm: float) -> float: ... # type: ignore[override] + @property + def low_rate(self) -> Arrhenius: ... + @low_rate.setter + def low_rate(self, rate: Arrhenius) -> None: ... + @property + def high_rate(self) -> Arrhenius: ... + @high_rate.setter + def high_rate(self, rate: Arrhenius) -> None: ... + @property + def falloff_coeffs(self) -> Array: ... + @falloff_coeffs.setter + def falloff_coeffs(self, data: Iterable[float]) -> None: ... + @property + def allow_negative_pre_exponential_factor(self) -> bool: ... + @allow_negative_pre_exponential_factor.setter + def allow_negative_pre_exponential_factor(self, allow: bool) -> None: ... + @property + def chemically_activated(self) -> bool: ... + @chemically_activated.setter + def chemically_activated(self, activated: bool) -> None: ... + def falloff_function(self, temperature: float, conc3b: float) -> float: ... + +class LindemannRate(FalloffRate): ... +class TroeRate(FalloffRate): ... +class SriRate(FalloffRate): ... +class TsangRate(FalloffRate): ... + +class PlogRate(ReactionRate): + def __init__( + self, + rates: list[tuple[float, Arrhenius]] | None = None, + input_data: PlogRateInput | None = None, + init: bool = True, + ) -> None: ... + def __call__(self, temperature: float, pressure: float) -> float: ... # type: ignore[override] + @property + def rates(self) -> list[tuple[float, Arrhenius]]: ... + @rates.setter + def rates(self, data: Iterable[tuple[float, Arrhenius]]) -> None: ... + +class LinearBurkeRate(ReactionRate): ... +class ChebyshevRate(ReactionRate): ... +class CustomRate(ReactionRate): ... +class ExtensibleRate(ReactionRate): ... + +class ExtensibleRateData: + def update(self, soln: Solution) -> bool: ... + +class InterfaceRateBase(ArrheniusRateBase): + def __call__(self, temperature: float, coverages: Array) -> float: ... # type: ignore[override] + @property + def coverage_dependencies(self) -> dict[str, CoverageParameters]: ... + @coverage_dependencies.setter + def coverage_dependencies(self, deps: dict[str, CoverageParameters]) -> None: ... + def set_species(self, species: Iterable[str]) -> None: ... + @property + def site_density(self) -> float: ... + @site_density.setter + def site_density(self, site_density: float) -> None: ... + @property + def uses_electrochemistry(self) -> bool: ... + @property + def beta(self) -> float: ... + +class InterfaceArrheniusRate(InterfaceRateBase): + def __init__( + self, + A: float | None = None, + b: float | None = None, + Ea: float | None = None, + input_data: ReactionRateInput[ArrheniusParameters] | None = None, + init: bool = True, + ) -> None: ... + def _from_parameters(self, A: float, b: float, Ea: float) -> None: ... + +class InterfaceBlowersMaselRate(InterfaceRateBase): + def __init__( + self, + A: float | None = None, + b: float | None = None, + Ea0: float | None = None, + w: float | None = None, + input_data: ReactionRateInput[BlowersMaselParameters] | None = None, + init: bool = True, + ) -> None: ... + def _from_dict( + self, input_data: ReactionRateInput[BlowersMaselParameters] + ) -> None: ... + def _from_parameters(self, A: float, b: float, Ea0: float, w: float) -> None: ... + @property + def bond_energy(self) -> float: ... + @property + def delta_enthalpy(self) -> float: ... + @delta_enthalpy.setter + def delta_enthalpy(self, delta_H: float) -> None: ... + +class StickRateBase(InterfaceRateBase): ... +class StickingArrheniusRate(StickRateBase): ... + +class StickingBlowersMaselRate(StickRateBase): + def __init__( + self, + A: float | None = None, + b: float | None = None, + Ea0: float | None = None, + w: float | None = None, + input_data: ReactionRateInput[BlowersMaselParameters] | None = None, + init: bool = True, + ) -> None: ... + +class ThirdBody: + def __init__( + self, + collider: str = "M", + *, + efficiencies: dict[str, float] | None = None, + default_efficiency: float | None = None, + init: bool = True, + ) -> None: ... + @property + def name(self) -> str: ... + @property + def mass_action(self) -> bool: ... + @property + def efficiencies(self) -> dict[str, float]: ... + @efficiencies.setter + def efficiencies(self, eff: dict[str, float]) -> None: ... + @property + def default_efficiency(self) -> float: ... + @default_efficiency.setter + def default_efficiency(self, default_eff: float) -> None: ... + def efficiency(self, species: str) -> float: ... + +class Reaction: + def __init__( + self, + reactants: dict[str, float] | None = None, + products: dict[str, float] | None = None, + rate: ReactionRate + | ReactionRateInput[ReactionRateParameters] + | ArrheniusParameters + | Callable[[float], float] + | None = None, + *, + equation: str | None = None, + init: bool = True, + third_body: ThirdBody | str | None = None, + ) -> None: ... + @classmethod + def from_dict( + cls, + data: ReactionRateInput[ReactionRateParameters], + kinetics: Kinetics, + hyphenize: bool = True, + ) -> Reaction: ... + @classmethod + def from_yaml(cls, data: str, kinetics: Kinetics) -> Reaction: ... + @staticmethod + def list_from_file( + filename: str, kinetics: Kinetics, section: str = "reactions" + ) -> list[Reaction]: ... + @staticmethod + def list_from_yaml(text: str, kinetics: Kinetics) -> list[Reaction]: ... + @property + def reactant_string(self) -> str: ... + @property + def product_string(self) -> str: ... + @property + def equation(self) -> str: ... + @property + def reactants(self) -> dict[str, float]: ... + @property + def products(self) -> dict[str, float]: ... + def __contains__(self, species: str) -> bool: ... + @property + def orders(self) -> dict[str, float]: ... + @orders.setter + def orders(self, orders: dict[str, float]) -> None: ... + @property + def ID(self) -> str: ... + @ID.setter + def ID(self, ID: str) -> None: ... + @property + def reaction_type(self) -> str: ... + @property + def rate(self) -> ReactionRate: ... + @rate.setter + def rate(self, rate: ReactionRate | Callable[[float], float]) -> None: ... + @property + def reversible(self) -> bool: ... + @reversible.setter + def reversible(self, reversible: bool) -> None: ... + @property + def duplicate(self) -> bool: ... + @duplicate.setter + def duplicate(self, duplicate: bool) -> None: ... + @property + def allow_nonreactant_orders(self) -> bool: ... + @allow_nonreactant_orders.setter + def allow_nonreactant_orders(self, allow: bool) -> None: ... + @property + def allow_negative_orders(self) -> bool: ... + @allow_negative_orders.setter + def allow_negative_orders(self, allow: bool) -> None: ... + @property + def input_data(self) -> ReactionRateInput[ReactionRateParameters]: ... + def update_user_data(self, data: dict[str, str | float]) -> None: ... + def clear_user_data(self) -> None: ... + @property + def third_body(self) -> ThirdBody | None: ... + @property + def third_body_name(self) -> str | None: ... + +class Arrhenius: + def __init__( + self, A: float = 0, b: float = 0, E: float = 0, init: bool = True + ) -> None: ... + @property + def pre_exponential_factor(self) -> float: ... + @property + def temperature_exponent(self) -> float: ... + @property + def activation_energy(self) -> float: ... + def __call__(self, T: float) -> float: ... diff --git a/interfaces/cython/cantera/reactionpath.pyi b/interfaces/cython/cantera/reactionpath.pyi new file mode 100644 index 00000000000..7d3b50ed72e --- /dev/null +++ b/interfaces/cython/cantera/reactionpath.pyi @@ -0,0 +1,76 @@ +# This file is part of Cantera. See License.txt in the top-level directory or +# at https://cantera.org/license.txt for license and copyright information. + +from pathlib import Path +from typing import Any, Literal + +from .solutionbase import _SolutionBase + +class ReactionPathDiagram: + def __init__( + self, contents: _SolutionBase, element: str, *args: Any, **kwargs: Any + ) -> None: ... + @property + def show_details(self) -> bool: ... + @show_details.setter + def show_details(self, value: bool) -> None: ... + @property + def threshold(self) -> float: ... + @threshold.setter + def threshold(self, value: float) -> None: ... + @property + def bold_threshold(self) -> float: ... + @bold_threshold.setter + def bold_threshold(self, value: float) -> None: ... + @property + def normal_threshold(self) -> float: ... + @normal_threshold.setter + def normal_threshold(self, value: float) -> None: ... + @property + def label_threshold(self) -> float: ... + @label_threshold.setter + def label_threshold(self, value: float) -> None: ... + @property + def bold_color(self) -> str: ... + @bold_color.setter + def bold_color(self, value: str) -> None: ... + @property + def normal_color(self) -> str: ... + @normal_color.setter + def normal_color(self, value: str) -> None: ... + @property + def dashed_color(self) -> str: ... + @dashed_color.setter + def dashed_color(self, value: str) -> None: ... + @property + def dot_options(self) -> str: ... + @dot_options.setter + def dot_options(self, value: str) -> None: ... + @property + def font(self) -> str: ... + @font.setter + def font(self, value: str) -> None: ... + @property + def scale(self) -> float: ... + @scale.setter + def scale(self, value: float) -> None: ... + @property + def flow_type(self) -> Literal["NetFlow", "OneWayFlow"]: ... + @flow_type.setter + def flow_type(self, value: Literal["NetFlow", "OneWayFlow"]) -> None: ... + @property + def arrow_width(self) -> float: ... + @arrow_width.setter + def arrow_width(self, value: float) -> None: ... + @property + def title(self) -> str: ... + @title.setter + def title(self, value: str) -> None: ... + def add(self, other: ReactionPathDiagram) -> None: ... + def display_only(self, k: int) -> None: ... + def get_dot(self) -> str: ... + def write_dot(self, filename: Path | str) -> None: ... + def get_data(self) -> str: ... + def build(self, verbose: bool = False) -> None: ... + @property + def log(self) -> str: ... diff --git a/interfaces/cython/cantera/reactor.pyi b/interfaces/cython/cantera/reactor.pyi new file mode 100644 index 00000000000..9adfd45aa75 --- /dev/null +++ b/interfaces/cython/cantera/reactor.pyi @@ -0,0 +1,535 @@ +# This file is part of Cantera. See License.txt in the top-level directory or +# at https://cantera.org/license.txt for license and copyright information. + +from collections.abc import Iterable, Sequence +from typing import ( + Any, + Callable, + ClassVar, + Literal, + TypeAlias, + overload, +) + +from graphviz import Digraph +from typing_extensions import Never, override + +from ._types import Array, ArrayLike, LogLevel7 +from .composite import Solution +from .func1 import Func1 +from .jacobians import SystemJacobian +from .kinetics import DerivativeSettings, Kinetics +from .solutionbase import _SolutionBase +from .thermo import ThermoPhase + +_Func1Like: TypeAlias = Func1 | Callable[[float], float] | float + +class ReactorBase: + reactor_type: ClassVar[str] + def __init__( + self, + contents: _SolutionBase | None = None, + *args: Any, + name: str = "(none)", + volume: float | None, + node_attr: dict[str, str] | None = None, + ) -> None: ... + def insert(self, solution: _SolutionBase) -> None: ... + @property + def type(self) -> str: ... + @property + def name(self) -> str: ... + @name.setter + def name(self, name: str) -> None: ... + def syncState(self) -> None: ... + @property + def thermo(self) -> ThermoPhase: ... + @property + def volume(self) -> float: ... + @volume.setter + def volume(self, volume: float) -> None: ... + @property + def T(self) -> float: ... + @property + def density(self) -> float: ... + @property + def mass(self) -> float: ... + @property + def Y(self) -> Array: ... + def add_sensitivity_reaction(self, m: int) -> None: ... + @property + def inlets(self) -> list[FlowDevice]: ... + @property + def outlets(self) -> list[FlowDevice]: ... + @property + def walls(self) -> list[Wall]: ... + @property + def surfaces(self) -> list[ReactorSurface]: ... + def _add_inlet(self, inlet: FlowDevice) -> None: ... + def _add_outlet(self, outlet: FlowDevice) -> None: ... + def _add_wall(self, wall: Wall) -> None: ... + def draw( + self, + graph: Digraph | None = None, + graph_attr: dict[str, str] | None = None, + node_attr: dict[str, str] | None = None, + print_state: bool = False, + species: Literal["X", "Y"] | bool | Iterable[str] | None = None, + species_units: Literal["percent", "ppm"] = "percent", + ) -> Digraph: ... + @override + def __reduce__(self) -> Never: ... + def __copy__(self) -> Never: ... + +class Reactor(ReactorBase): + def __init__( + self, + contents: Solution, + *, + name: str = "(none)", + energy: Literal["on", "off"] = "on", + group_name: str = "", + **kwargs: Any, + ) -> None: ... + @property + def kinetics(self) -> Kinetics: ... + @property + def chemistry_enabled(self) -> bool: ... + @chemistry_enabled.setter + def chemistry_enabled(self, value: bool) -> None: ... + @property + def energy_enabled(self) -> bool: ... + @energy_enabled.setter + def energy_enabled(self, value: bool) -> None: ... + def add_sensitivity_species_enthalpy(self, k: int) -> None: ... + def component_index(self, name: str) -> int: ... + def component_name(self, i: int) -> str: ... + @property + def n_vars(self) -> int: ... + def get_state(self) -> Array: ... + @property + def jacobian(self) -> Array: ... + @property + def finite_difference_jacobian(self) -> Array: ... + def set_advance_limit(self, name: str, limit: float | None) -> None: ... + +class MoleReactor(Reactor): ... +class Reservoir(ReactorBase): ... +class ConstPressureReactor(Reactor): ... +class ConstPressureMoleReactor(Reactor): ... +class IdealGasReactor(Reactor): ... +class IdealGasMoleReactor(Reactor): ... +class IdealGasConstPressureReactor(Reactor): ... +class IdealGasConstPressureMoleReactor(Reactor): ... + +class FlowReactor(Reactor): + @property + def mass_flow_rate(self) -> Never: ... + @mass_flow_rate.setter + def mass_flow_rate(self, value: float) -> None: ... + @property + def area(self) -> float: ... + @area.setter + def area(self, area: float) -> None: ... + @property + def inlet_surface_atol(self) -> float: ... + @inlet_surface_atol.setter + def inlet_surface_atol(self, atol: float) -> None: ... + @property + def inlet_surface_rtol(self) -> float: ... + @inlet_surface_rtol.setter + def inlet_surface_rtol(self, rtol: float) -> None: ... + @property + def inlet_surface_max_steps(self) -> int: ... + @inlet_surface_max_steps.setter + def inlet_surface_max_steps(self, nsteps: int) -> None: ... + @property + def inlet_surface_max_error_failures(self) -> int: ... + @inlet_surface_max_error_failures.setter + def inlet_surface_max_error_failures(self, nsteps: int) -> None: ... + @property + def surface_area_to_volume_ratio(self) -> float: ... + @surface_area_to_volume_ratio.setter + def surface_area_to_volume_ratio(self, sa_to_vol: float) -> None: ... + @property + def speed(self) -> float: ... + +class ExtensibleReactor(Reactor): + delegatable_methods: dict[str, tuple[str, str]] + def __init__(self, *args: Any, **kwargs: Any) -> None: ... + @property + @override + def n_vars(self) -> int: ... + @n_vars.setter + def n_vars(self, n: int) -> None: ... + @property + def expansion_rate(self) -> float: ... + @expansion_rate.setter + def expansion_rate(self, vdot: float) -> None: ... + @property + def heat_rate(self) -> float: ... + @heat_rate.setter + def heat_rate(self, qdot: float) -> None: ... + def restore_thermo_state(self) -> None: ... + def restore_surface_state(self, n: int) -> None: ... + +class ExtensibleIdealGasReactor(ExtensibleReactor): ... +class ExtensibleConstPressureReactor(ExtensibleReactor): ... +class ExtensibleIdealGasConstPressureReactor(ExtensibleReactor): ... +class ExtensibleMoleReactor(ExtensibleReactor): ... +class ExtensibleIdealGasMoleReactor(ExtensibleReactor): ... +class ExtensibleConstPressureMoleReactor(ExtensibleReactor): ... +class ExtensibleIdealGasConstPressureMoleReactor(ExtensibleReactor): ... + +class ReactorSurface: + reactor_type: ClassVar[str] + def __init__( + self, + contents: _SolutionBase | None = None, + r: Reactor | None = None, + *, + name: str = "(none)", + A: float | None = None, + node_attr: dict[str, str] | None = None, + ) -> None: ... + def install(self, r: Reactor) -> None: ... + @property + def area(self) -> float: ... + @area.setter + def area(self, A: float) -> None: ... + @property + def kinetics(self) -> Kinetics: ... + @property + def coverages(self) -> Array: ... + @coverages.setter + def coverages(self, coverages: Array) -> None: ... + @property + def reactor(self) -> Reactor: ... + def draw( + self, + graph: Digraph | None = None, + *, + graph_attr: dict[str, str] | None = None, + node_attr: dict[str, str] | None = None, + print_state: bool = False, + species: Literal["X", "Y"] | bool | Iterable[str] | None = None, + species_units: Literal["percent", "ppm"] = "percent", + ) -> Digraph: ... + +class ConnectorNode: + node_type: ClassVar[str] + def __init__( + self, + left: ReactorBase | None = None, + right: ReactorBase | None = None, + upstream: ReactorBase | None = None, + downstream: ReactorBase | None = None, + name: str = "(none)", + **kwargs: Any, + ) -> None: ... + @property + def type(self) -> str: ... + @property + def name(self) -> str: ... + @name.setter + def name(self, name: str) -> None: ... + @override + def __reduce__(self) -> Never: ... + def __copy__(self) -> Never: ... + +class WallBase(ConnectorNode): + def __init__( + self, + left: ReactorBase, + right: ReactorBase, + *, + name: str = "(none)", + A: float | None = None, + K: float | None = None, + U: float | None = None, + Q: Callable[[float], float] | None = None, + velocity: Callable[[float], float] | None = None, + edge_attr: dict[str, str] | None = None, + ) -> None: ... + @property + def area(self) -> float: ... + @area.setter + def area(self, value: float) -> None: ... + @property + def left_reactor(self) -> ReactorBase: ... + @property + def right_reactor(self) -> ReactorBase: ... + @property + def expansion_rate(self) -> float: ... + @property + def heat_rate(self) -> float: ... + def draw( + self, + graph: Digraph | None = None, + *, + graph_attr: dict[str, str] | None = None, + node_attr: dict[str, str] | None = None, + edge_attr: dict[str, str] | None = None, + moving_wall_edge_attr: dict[str, str] | None = None, + show_wall_velocity: bool = True, + ) -> Digraph: ... + +class Wall(WallBase): + @property + def expansion_rate_coeff(self) -> float: ... + @expansion_rate_coeff.setter + def expansion_rate_coeff(self, val: float) -> None: ... + @property + def heat_transfer_coeff(self) -> float: ... + @heat_transfer_coeff.setter + def heat_transfer_coeff(self, value: float) -> None: ... + @property + def emissivity(self) -> float: ... + @emissivity.setter + def emissivity(self, value: float) -> None: ... + @property + def velocity(self) -> float: ... + @velocity.setter + def velocity(self, val: _Func1Like) -> None: ... + @property + def heat_flux(self) -> float: ... + @heat_flux.setter + def heat_flux(self, val: _Func1Like) -> None: ... + +class FlowDevice: + def __init__( + self, + upstream: ReactorBase, + downstream: ReactorBase, + *, + name: str = "(none)", + edge_attr: dict[str, str] | None = None, + ) -> None: ... + @property + def upstream(self) -> ReactorBase: ... + @property + def downstream(self) -> ReactorBase: ... + @property + def mass_flow_rate(self) -> float: ... + @property + def pressure_function(self) -> float: ... + @pressure_function.setter + def pressure_function(self, val: _Func1Like) -> None: ... + @property + def time_function(self) -> float: ... + @time_function.setter + def time_function(self, val: _Func1Like) -> None: ... + @property + def device_coefficient(self) -> float: ... + @device_coefficient.setter + def device_coefficient(self, val: float) -> None: ... + def draw( + self, + graph: Digraph | None = None, + *, + graph_attr: dict[str, str] | None = None, + node_attr: dict[str, str] | None = None, + edge_attr: dict[str, str] | None = None, + ) -> Digraph: ... + +class MassFlowController(FlowDevice): + node_type: ClassVar[str] + def __init__( + self, + upstream: ReactorBase, + downstream: ReactorBase, + *, + name: str = "(none)", + mdot: _Func1Like = 1.0, + edge_attr: dict[str, str] | None = None, + ) -> None: ... + @property + def mass_flow_coeff(self) -> float: ... + @mass_flow_coeff.setter + def mass_flow_coeff(self, value: float) -> None: ... + @property + @override + def mass_flow_rate(self) -> float: ... + @mass_flow_rate.setter + def mass_flow_rate(self, m: _Func1Like) -> None: ... + +class Valve(FlowDevice): + node_type: ClassVar[str] + def __init__( + self, + upstream: ReactorBase, + downstream: ReactorBase, + *, + name: str = "(none)", + K: _Func1Like = 1.0, + edge_attr: dict[str, str] | None = None, + ) -> None: ... + @property + def valve_coeff(self) -> float: ... + @valve_coeff.setter + def valve_coeff(self, value: float) -> None: ... + +class PressureController(FlowDevice): + node_type: ClassVar[str] + def __init__( + self, + upstream: ReactorBase, + downstream: ReactorBase, + *, + name: str = "(none)", + primary: FlowDevice | None = None, + K: _Func1Like = 1.0, + edge_attr: dict[str, str] | None = None, + ) -> None: ... + @property + def pressure_coeff(self) -> float: ... + @pressure_coeff.setter + def pressure_coeff(self, value: float) -> None: ... + @property + def primary(self) -> Never: ... + @primary.setter + def primary(self, d: FlowDevice) -> None: ... + +class ReactorNet: + def __init__(self, reactors: Sequence[Reactor] = ()) -> None: ... + def add_reactor(self, r: Reactor) -> None: ... + def advance(self, t: float, apply_limit: bool = True) -> float: ... + def step(self) -> float: ... + def solve_steady(self, loglevel: LogLevel7) -> None: ... + def steady_jacobian(self, rdt: float = 0.0) -> Array: ... + def initialize(self) -> None: ... + def reinitialize(self) -> None: ... + @property + def reactors(self) -> list[Reactor]: ... + @property + def time(self) -> float: ... + @property + def distance(self) -> float: ... + @property + def initial_time(self) -> float: ... + @initial_time.setter + def initial_time(self, t: float) -> None: ... + @property + def max_time_step(self) -> float: ... + @max_time_step.setter + def max_time_step(self, t: float) -> None: ... + @property + def max_err_test_fails(self) -> Never: ... + @max_err_test_fails.setter + def max_err_test_fails(self, n: int) -> None: ... + @property + def max_nonlinear_iterations(self) -> int: ... + @max_nonlinear_iterations.setter + def max_nonlinear_iterations(self, n: int) -> None: ... + @property + def max_nonlinear_convergence_failures(self) -> int: ... + @max_nonlinear_convergence_failures.setter + def max_nonlinear_convergence_failures(self, n: int) -> None: ... + @property + def include_algebraic_in_error_test(self) -> bool: ... + @include_algebraic_in_error_test.setter + def include_algebraic_in_error_test(self, yesno: bool) -> None: ... + @property + def max_order(self) -> Literal[1, 2, 3, 4, 5]: ... + @max_order.setter + def max_order(self, n: Literal[1, 2, 3, 4, 5]) -> None: ... + @property + def max_steps(self) -> int: ... + @max_steps.setter + def max_steps(self, nsteps: int) -> None: ... + @property + def rtol(self) -> float: ... + @rtol.setter + def rtol(self, tol: float) -> None: ... + @property + def atol(self) -> float: ... + @atol.setter + def atol(self, tol: float) -> None: ... + @property + def rtol_sensitivity(self) -> float: ... + @rtol_sensitivity.setter + def rtol_sensitivity(self, tol: float) -> None: ... + @property + def atol_sensitivity(self) -> float: ... + @atol_sensitivity.setter + def atol_sensitivity(self, tol: float) -> None: ... + @property + def verbose(self) -> bool: ... + @verbose.setter + def verbose(self, v: bool) -> None: ... + def global_component_index(self, name: str, reactor: int) -> int: ... + def component_name(self, i: int) -> str: ... + def sensitivity( + self, component: int | str | bytes, p: int, r: int = 0 + ) -> float: ... + def sensitivities(self) -> Array: ... + def sensitivity_parameter_name(self, p: int) -> str: ... + @property + def n_sensitivity_params(self) -> int: ... + @property + def n_vars(self) -> int: ... + def get_state(self) -> Array: ... + def get_derivative(self, k: int) -> Array: ... + @property + def advance_limits(self) -> Array: ... + @advance_limits.setter + def advance_limits(self, limits: ArrayLike | None) -> None: ... + @overload + def advance_to_steady_state( + self, + max_steps: int, + residual_threshold: float, + atol: float, + return_residual: Literal[False] = False, + ) -> None: ... + @overload + def advance_to_steady_state( + self, + max_steps: int, + residual_threshold: float, + atol: float, + return_residual: Literal[True], + ) -> Array: ... + @overload + def advance_to_steady_state( + self, + max_steps: int = 10000, + residual_threshold: float = 0.0, + atol: float = 0.0, + return_residual: bool = False, + ) -> Array | None: ... + @override + def __reduce__(self) -> Never: ... + def __copy__(self) -> Never: ... + @property + def preconditioner(self) -> SystemJacobian: ... + @preconditioner.setter + def preconditioner(self, precon: SystemJacobian) -> None: ... + @property + def linear_solver_type(self) -> Literal["DENSE", "GMRES", "BAND", "DIAG"]: ... + @linear_solver_type.setter + def linear_solver_type( + self, linear_solver_type: Literal["DENSE", "GMRES", "BAND", "DIAG"] + ) -> None: ... + @property + def solver_stats(self) -> dict[str, int]: ... + @property + def derivative_settings(self) -> Never: ... + @derivative_settings.setter + def derivative_settings(self, value: DerivativeSettings) -> None: ... + def draw( + self, + graph: Digraph | None = None, + *, + graph_attr: dict[str, str] | None = None, + node_attr: dict[str, str] | None = None, + edge_attr: dict[str, str] | None = None, + heat_flow_attr: dict[str, str] | None = None, + mass_flow_attr: dict[str, str] | None = None, + moving_wall_edge_attr: dict[str, str] | None = None, + surface_edge_attr: dict[str, str] | None = None, + show_wall_velocity: bool = True, + print_state: bool = False, + species: Literal["X", "Y"] | bool | Iterable[str] | None = None, + species_units: Literal["percent", "ppm"] = "percent", + ) -> Digraph: ... diff --git a/interfaces/cython/cantera/solutionbase.pyi b/interfaces/cython/cantera/solutionbase.pyi new file mode 100644 index 00000000000..d4073b5c1a9 --- /dev/null +++ b/interfaces/cython/cantera/solutionbase.pyi @@ -0,0 +1,162 @@ +# This file is part of Cantera. See License.txt in the top-level directory or +# at https://cantera.org/license.txt for license and copyright information. + +from collections.abc import Sequence +from pathlib import Path +from typing import ( + Any, + Literal, + TypeAlias, + overload, +) + +from typing_extensions import Never, Self + +from ._types import Array, ArrayLike, Basis, CompressionLevel +from .kinetics import Kinetics, KineticsType +from .reaction import Reaction +from .thermo import Species, ThermoPhase, ThermoType +from .transport import TransportModel +from .units import UnitDict, UnitDictBytes, UnitSystem + +_SORTING_TYPE: TypeAlias = Literal["alphabetical", "molar-mass"] | None + +class _SolutionBase: + def __init__( + self, + infile: Path | str = "", + name: str = "", + adjacent: Sequence[ThermoPhase] = (), + *, + origin: _SolutionBase | None = None, + yaml: str | None = None, + thermo: ThermoPhase | None = None, + species: Sequence[Species] | None = None, + kinetics: Kinetics | None = None, + reactions: Sequence[Reaction] | None = (), + init: bool = True, + **kwargs: Any, + ) -> None: ... + @property + def name(self) -> str: ... + @name.setter + def name(self, name: str) -> None: ... + @property + def source(self) -> str: ... + @property + def composite( + self, + ) -> tuple[ThermoType | None, KineticsType | None, TransportModel | None]: ... + @property + def input_data( + self, + ) -> dict[str, str | list[str] | dict[str, float | dict[str, float]]]: ... + @property + def input_header(self) -> dict[str, str | list[str]]: ... + def update_user_data(self, data: dict[str, Any]) -> None: ... + def clear_user_data(self) -> None: ... + def update_user_header(self, data: dict[str, str | list[str]]) -> None: ... + def clear_user_header(self) -> None: ... + @overload + def write_yaml( + self, + filename: None, + phases: Sequence[ThermoPhase] | None = None, + units: UnitSystem | UnitDict | UnitDictBytes | None = None, + precision: int | None = None, + skip_user_defined: bool | None = None, + header: bool = True, + ) -> str: ... + @overload + def write_yaml( + self, + filename: str | Path, + phases: Sequence[ThermoPhase] | None = None, + units: UnitSystem | UnitDict | UnitDictBytes | None = None, + precision: int | None = None, + skip_user_defined: bool | None = None, + header: bool = True, + ) -> None: ... + @overload + def write_yaml( + self, + filename: str | Path | None, + phases: Sequence[ThermoPhase] | None = None, + units: UnitSystem | UnitDict | UnitDictBytes | None = None, + precision: int | None = None, + skip_user_defined: bool | None = None, + header: bool = True, + ) -> str | None: ... + def write_chemkin( + self, + mechanism_path: str | Path | None = None, + thermo_path: str | Path | None = None, + transport_path: str | Path | None = None, + sort_species: _SORTING_TYPE = None, + sort_elements: _SORTING_TYPE = None, + overwrite: bool = False, + quiet: bool = False, + ) -> None: ... + def __getitem__(self, selection: slice) -> Self: ... + @property + def selected_species(self) -> list[int]: ... + @selected_species.setter + def selected_species( + self, species: str | int | Sequence[str] | Sequence[int] + ) -> None: ... + def __getstate__(self) -> str: ... + def __setstate__(self, pkl: str) -> None: ... + def __copy__(self) -> Never: ... + +class SolutionArrayBase: + def __init__( + self, + phase: _SolutionBase, + shape: int | tuple[int, ...] = (0,), + states: ArrayLike | None = None, + extra: str | Sequence[str] | dict[str, ArrayLike] | None = None, + meta: dict[str, Any] = {}, + init: bool = True, + ) -> None: ... + @property + def size(self) -> int: ... + def _api_shape(self) -> tuple[int, ...]: ... + def _set_api_shape(self, shape: Sequence[int]) -> None: ... + def info( + self, + keys: Sequence[str] | None = None, + rows: int = 10, + width: int | None = None, + ) -> str: ... + @property + def meta(self) -> dict[str, Any]: ... + @meta.setter + def meta(self, meta: dict[str, Any]) -> None: ... + @property + def extra(self) -> list[str]: ... + @property + def component_names(self) -> list[str]: ... + def resize(self, size: int | tuple[int, ...]) -> None: ... + def _has_component(self, name: str) -> bool: ... + def _get_component(self, name: str) -> Array: ... + def _set_component(self, name: str, data: Array) -> None: ... + def _set_loc(self, loc: int) -> None: ... + def _update_state(self, loc: int) -> None: ... + def _get_state(self, loc: int) -> Array: ... + def _set_state(self, loc: int, data: Array) -> None: ... + def _has_extra(self, name: str) -> bool: ... + def _add_extra(self, name: str, back: bool = True) -> None: ... + def get_auxiliary(self, loc: int) -> dict[str, Any]: ... + def set_auxiliary(self, loc: int, data: dict[str, Any]) -> None: ... + def _append(self, state: Array, extra: dict[str, Any]) -> None: ... + def _cxx_save( + self, + filename: str, + name: str, + sub: str, + description: str, + overwrite: bool, + compression: CompressionLevel, + basis: Basis, + ) -> None: ... + def _cxx_restore(self, filename: str, name: str, sub: str) -> dict[str, str]: ... diff --git a/interfaces/cython/cantera/speciesthermo.pyi b/interfaces/cython/cantera/speciesthermo.pyi new file mode 100644 index 00000000000..8db7d562f68 --- /dev/null +++ b/interfaces/cython/cantera/speciesthermo.pyi @@ -0,0 +1,56 @@ +# This file is part of Cantera. See License.txt in the top-level directory or +# at https://cantera.org/license.txt for license and copyright information. + +from typing import Any, ClassVar, TypedDict + +from typing_extensions import Required + +from ._types import Array, ArrayLike + +SpeciesThermoInput = TypedDict( + "SpeciesThermoInput", + { + "model": Required[str], + "temperature-ranges": list[float], + "data": list[list[float]], + "note": str, + }, + total=False, +) + +class SpeciesThermo: + derived_type: ClassVar[int] + def _check_n_coeffs(self, n: int) -> bool: ... + def __init__( + self, + T_low: float | None = None, + T_high: float | None = None, + P_ref: float | None = None, + coeffs: ArrayLike | None = None, + *args: Any, + init: bool = True, + **kwargs: Any, + ) -> None: ... + @property + def min_temp(self) -> float: ... + @property + def max_temp(self) -> float: ... + @property + def reference_pressure(self) -> float: ... + @property + def n_coeffs(self) -> int: ... + @property + def coeffs(self) -> Array: ... + @property + def input_data(self) -> SpeciesThermoInput: ... + def update_user_data(self, data: dict[str, Any]) -> None: ... + def clear_user_data(self) -> None: ... + def cp(self, T: float) -> float: ... + def h(self, T: float) -> float: ... + def s(self, T: float) -> float: ... + +class ConstantCp(SpeciesThermo): ... +class Mu0Poly(SpeciesThermo): ... +class NasaPoly2(SpeciesThermo): ... +class Nasa9PolyMultiTempRegion(SpeciesThermo): ... +class ShomatePoly2(SpeciesThermo): ... diff --git a/interfaces/cython/cantera/thermo.pyi b/interfaces/cython/cantera/thermo.pyi new file mode 100644 index 00000000000..46e72935e32 --- /dev/null +++ b/interfaces/cython/cantera/thermo.pyi @@ -0,0 +1,595 @@ +# This file is part of Cantera. See License.txt in the top-level directory or +# at https://cantera.org/license.txt for license and copyright information. + +from collections.abc import Sequence +from typing import Any, Literal, TypeAlias, TypedDict, overload + +from typing_extensions import Required + +from ._types import ( + Array, + ArrayLike, + Basis, + CompositionLike, + CompositionVariable, + EquilibriumSolver, + FullState, + LogLevel, + PropertyPair, + StateSetter, + StateVariable, +) +from .solutionbase import _SolutionBase +from .speciesthermo import SpeciesThermo, SpeciesThermoInput +from .transport import GasTransportData, GasTransportInput +from .units import Units + +ThermoType: TypeAlias = Literal[ + "Debye-Huckel", + "HMW-electrolyte", + "Margules", + "MixtureFugacity", + "Peng-Robinson", + "Phase", + "Redlich-Kister", + "Redlich-Kwong", + "SingleSpecies", + "none", + "binary-solution-tabulated", + "compound-lattice", + "coverage-dependent-surface", + "edge", + "electron-cloud", + "fixed-stoichiometry", + "ideal-condensed", + "ideal-gas", + "ideal-molal-solution", + "ideal-solution-VPSS", + "ideal-surface", + "liquid-water-IAPWS95", + "lattice", + "plasma", + "pure-fluid", +] + +PhaseOfMatter: TypeAlias = Literal[ + "gas", + "liquid", + "solid", + "supercritical", + "unstable-liquid", + "unstable-gas", + "liquid-gas-mix", + "unspecified", +] + +QuadratureMethod: TypeAlias = Literal["simpson", "trapezoidal"] + +SpeciesInput = TypedDict( + "SpeciesInput", + { + "name": Required[str], + "composition": Required[dict[str, float]], + "thermo": SpeciesThermoInput, + "transport": GasTransportInput, + "equation-of-state": dict[str, Any], + "critical-parameters": dict[str, float], + "Debye-Huckel": dict[str, float], + }, + total=False, +) + +class ThermoModelMethodError(Exception): + def __init__(self, thermo_model: str) -> None: ... + +class Species: + def __init__( + self, + name: str | None = None, + composition: CompositionLike | None = None, + charge: float | None = None, + size: float | None = None, + init: bool = True, + ) -> None: ... + @staticmethod + def from_dict(data: SpeciesInput) -> Species: ... + @staticmethod + def from_yaml(text: str) -> Species: ... + @staticmethod + def list_from_file(filename: str, section: str = "species") -> list[Species]: ... + @staticmethod + def list_from_yaml(text: str, section: str | None = None) -> list[Species]: ... + @property + def name(self) -> str: ... + @property + def composition(self) -> dict[str, float]: ... + @property + def charge(self) -> float: ... + @property + def size(self) -> float: ... + @property + def molecular_weight(self) -> float: ... + @property + def thermo(self) -> SpeciesThermo: ... + @thermo.setter + def thermo(self, value: SpeciesThermo) -> None: ... + @property + def transport(self) -> GasTransportData: ... + @transport.setter + def transport(self, value: GasTransportData) -> None: ... + @property + def input_data(self) -> SpeciesInput: ... + def update_user_data(self, data: dict[str, Any]) -> None: ... + def clear_user_data(self) -> None: ... + +class ThermoPhase(_SolutionBase): + @property + def thermo_model(self) -> ThermoType: ... + @property + def phase_of_matter(self) -> PhaseOfMatter: ... + def report(self, show_thermo: bool = True, threshold: float = 1e-14) -> str: ... + def __call__(self, *args: Any, **kwargs: Any) -> None: ... + @property + def is_pure(self) -> bool: ... + @property + def has_phase_transition(self) -> bool: ... + @property + def is_compressible(self) -> bool: ... + @property + def _native_mode(self) -> FullState: ... + @property + def _native_state( + self, + ) -> tuple[StateVariable, StateVariable, CompositionVariable]: ... + @property + def _full_states( + self, + ) -> dict[frozenset[StateVariable | CompositionVariable], FullState]: ... + @property + def _partial_states(self) -> dict[frozenset[StateVariable], PropertyPair]: ... + @property + def basis(self) -> Basis: ... + @basis.setter + def basis(self, value: Basis) -> None: ... + def equilibrate( + self, + XY: PropertyPair, + solver: EquilibriumSolver = "auto", + rtol: float = 1e-9, + max_steps: int = 1000, + max_iter: int = 100, + estimate_equil: int = 0, + log_level: LogLevel = 0, + ) -> None: ... + ####### Composition, species, and elements ######## + @property + def n_elements(self) -> int: ... + def element_index(self, element: str | bytes | float) -> int: ... + def element_name(self, m: int) -> str: ... + @property + def element_names(self) -> list[str]: ... + def atomic_weight(self, m: int) -> float: ... + @property + def atomic_weights(self) -> Array: ... + @property + def n_species(self) -> int: ... + @property + def n_selected_species(self) -> int: ... + def species_name(self, k: int) -> str: ... + @property + def species_names(self) -> list[str]: ... + def species_index(self, species: str | bytes | float) -> int: ... + @property + def case_sensitive_species_names(self) -> bool: ... + @case_sensitive_species_names.setter + def case_sensitive_species_names(self, val: bool) -> None: ... + @overload + def species(self, k: None = None) -> list[Species]: ... + @overload + def species(self, k: str | bytes | float) -> Species: ... + @overload + def species( + self, k: str | bytes | float | None = None + ) -> Species | list[Species]: ... + def modify_species(self, k: int, species: Species) -> None: ... + def add_species(self, species: Species) -> None: ... + def add_species_alias(self, name: str, alias: str) -> None: ... + def find_isomers(self, comp: CompositionLike) -> list[str]: ... + def n_atoms( + self, species: str | bytes | float, element: str | bytes | float + ) -> int: ... + @property + def molecular_weights(self) -> Array: ... + @property + def charges(self) -> Array: ... + @property + def mean_molecular_weight(self) -> float: ... + @property + def Y(self) -> Array: ... + @Y.setter + def Y(self, Y: CompositionLike) -> None: ... + @property + def X(self) -> Array: ... + @X.setter + def X(self, X: CompositionLike) -> None: ... + @property + def concentrations(self) -> Array: ... + @concentrations.setter + def concentrations(self, C: ArrayLike) -> None: ... + def __composition_to_array(self, comp: CompositionLike, basis: Basis) -> Array: ... + def set_equivalence_ratio( + self, + phi: float, + fuel: CompositionLike, + oxidizer: CompositionLike, + basis: Basis = "mole", + *, + diluent: CompositionLike | None = None, + fraction: str + | dict[Literal["fuel", "oxidizer", "diluent"], float] + | None = None, + ) -> None: ... + def set_mixture_fraction( + self, + mixture_fraction: float, + fuel: CompositionLike, + oxidizer: CompositionLike, + basis: Basis = "mole", + ) -> None: ... + def equivalence_ratio( + self, + fuel: CompositionLike, + oxidizer: CompositionLike, + basis: Basis = "mole", + include_species: list[str | bytes | float] | None = None, + ) -> float: ... + def mixture_fraction( + self, + fuel: CompositionLike, + oxidizer: CompositionLike, + basis: Basis = "mole", + element: str | bytes | float = "Bilger", + ) -> float: ... + def stoich_air_fuel_ratio( + self, + fuel: CompositionLike, + oxidizer: CompositionLike, + basis: Basis = "mole", + ) -> float: ... + def elemental_mass_fraction(self, m: str | bytes | float) -> float: ... + def elemental_mole_fraction(self, m: str | bytes | float) -> float: ... + def set_unnormalized_mass_fractions(self, Y: ArrayLike) -> None: ... + def set_unnormalized_mole_fractions(self, X: ArrayLike) -> None: ... + def mass_fraction_dict(self, threshold: float = 0.0) -> dict[str, float]: ... + def mole_fraction_dict(self, threshold: float = 0.0) -> dict[str, float]: ... + ######## Read-only thermodynamic properties ######## + @property + def P(self) -> float: ... + @property + def T(self) -> float: ... + @property + def density(self) -> float: ... + @property + def density_mass(self) -> float: ... + @property + def density_mole(self) -> float: ... + @property + def v(self) -> float: ... + @property + def volume_mass(self) -> float: ... + @property + def volume_mole(self) -> float: ... + @property + def u(self) -> float: ... + @property + def int_energy_mole(self) -> float: ... + @property + def int_energy_mass(self) -> float: ... + @property + def h(self) -> float: ... + @property + def enthalpy_mole(self) -> float: ... + @property + def enthalpy_mass(self) -> float: ... + @property + def s(self) -> float: ... + @property + def entropy_mole(self) -> float: ... + @property + def entropy_mass(self) -> float: ... + @property + def g(self) -> float: ... + @property + def gibbs_mole(self) -> float: ... + @property + def gibbs_mass(self) -> float: ... + @property + def cv(self) -> float: ... + @property + def cv_mole(self) -> float: ... + @property + def cv_mass(self) -> float: ... + @property + def cp(self) -> float: ... + @property + def cp_mole(self) -> float: ... + @property + def cp_mass(self) -> float: ... + @property + def critical_temperature(self) -> float: ... + @property + def critical_pressure(self) -> float: ... + @property + def critical_density(self) -> float: ... + @property + def P_sat(self) -> float: ... + @property + def T_sat(self) -> float: ... + @property + def auxiliary_data(self) -> dict[str, Any]: ... + ######## Methods to get/set the complete thermodynamic state ######## + @property + def state_size(self) -> int: ... + @property + def state(self) -> Array: ... + @state.setter + def state(self, state: ArrayLike) -> None: ... + @property + def TD(self) -> tuple[float, float]: ... + @TD.setter + def TD(self, values: Sequence[float]) -> None: ... + @property + def TDX(self) -> tuple[float, float, Array]: ... + @TDX.setter + def TDX(self, values: StateSetter) -> None: ... + @property + def TDY(self) -> tuple[float, float, Array]: ... + @TDY.setter + def TDY(self, values: StateSetter) -> None: ... + @property + def TP(self) -> tuple[float, float]: ... + @TP.setter + def TP(self, values: Sequence[float]) -> None: ... + @property + def TPX(self) -> tuple[float, float, Array]: ... + @TPX.setter + def TPX(self, values: StateSetter) -> None: ... + @property + def TPY(self) -> tuple[float, float, Array]: ... + @TPY.setter + def TPY(self, values: StateSetter) -> None: ... + @property + def UV(self) -> tuple[float, float]: ... + @UV.setter + def UV(self, values: Sequence[float]) -> None: ... + @property + def UVX(self) -> tuple[float, float, Array]: ... + @UVX.setter + def UVX(self, values: StateSetter) -> None: ... + @property + def UVY(self) -> tuple[float, float, Array]: ... + @UVY.setter + def UVY(self, values: StateSetter) -> None: ... + @property + def DP(self) -> tuple[float, float]: ... + @DP.setter + def DP(self, values: Sequence[float]) -> None: ... + @property + def DPX(self) -> tuple[float, float, Array]: ... + @DPX.setter + def DPX(self, values: StateSetter) -> None: ... + @property + def DPY(self) -> tuple[float, float, Array]: ... + @DPY.setter + def DPY(self, values: StateSetter) -> None: ... + @property + def HP(self) -> tuple[float, float]: ... + @HP.setter + def HP(self, values: Sequence[float]) -> None: ... + @property + def HPX(self) -> tuple[float, float, Array]: ... + @HPX.setter + def HPX(self, values: StateSetter) -> None: ... + @property + def HPY(self) -> tuple[float, float, Array]: ... + @HPY.setter + def HPY(self, values: StateSetter) -> None: ... + @property + def SP(self) -> tuple[float, float]: ... + @SP.setter + def SP(self, values: Sequence[float]) -> None: ... + @property + def SPX(self) -> tuple[float, float, Array]: ... + @SPX.setter + def SPX(self, values: StateSetter) -> None: ... + @property + def SPY(self) -> tuple[float, float, Array]: ... + @SPY.setter + def SPY(self, values: StateSetter) -> None: ... + @property + def SV(self) -> tuple[float, float]: ... + @SV.setter + def SV(self, values: Sequence[float]) -> None: ... + @property + def SVX(self) -> tuple[float, float, Array]: ... + @SVX.setter + def SVX(self, values: StateSetter) -> None: ... + @property + def SVY(self) -> tuple[float, float, Array]: ... + @SVY.setter + def SVY(self, values: StateSetter) -> None: ... + # partial molar / non-dimensional properties + @property + def partial_molar_enthalpies(self) -> Array: ... + @property + def partial_molar_entropies(self) -> Array: ... + @property + def partial_molar_int_energies(self) -> Array: ... + @property + def chemical_potentials(self) -> Array: ... + @property + def electrochemical_potentials(self) -> Array: ... + @property + def partial_molar_cp(self) -> Array: ... + @property + def partial_molar_volumes(self) -> Array: ... + @property + def standard_enthalpies_RT(self) -> Array: ... + @property + def standard_entropies_R(self) -> Array: ... + @property + def standard_int_energies_RT(self) -> Array: ... + @property + def standard_gibbs_RT(self) -> Array: ... + @property + def standard_cp_R(self) -> Array: ... + @property + def activities(self) -> Array: ... + @property + def activity_coefficients(self) -> Array: ... + ######## Miscellaneous properties ######## + @property + def isothermal_compressibility(self) -> float: ... + @property + def thermal_expansion_coeff(self) -> float: ... + @property + def sound_speed(self) -> float: ... + @property + def min_temp(self) -> float: ... + @property + def max_temp(self) -> float: ... + @property + def reference_pressure(self) -> float: ... + @property + def electric_potential(self) -> float: ... + @electric_potential.setter + def electric_potential(self, value: float) -> None: ... + @property + def standard_concentration_units(self) -> Units: ... + # methods for plasma + @property + def Te(self) -> float: ... + @Te.setter + def Te(self, value: float) -> None: ... + @property + def Pe(self) -> float: ... + def set_discretized_electron_energy_distribution( + self, levels: ArrayLike, distribution: ArrayLike + ) -> None: ... + @property + def n_electron_energy_levels(self) -> int: ... + @property + def electron_energy_levels(self) -> Array: ... + @electron_energy_levels.setter + def electron_energy_levels(self, levels: Array) -> None: ... + @property + def electron_energy_distribution(self) -> Array: ... + @property + def isotropic_shape_factor(self) -> float: ... + @isotropic_shape_factor.setter + def isotropic_shape_factor(self, x: float) -> None: ... + @property + def electron_energy_distribution_type(self) -> str: ... + @electron_energy_distribution_type.setter + def electron_energy_distribution_type(self, distribution_type: str) -> None: ... + @property + def mean_electron_energy(self) -> float: ... + @mean_electron_energy.setter + def mean_electron_energy(self, energy: float) -> None: ... + @property + def quadrature_method(self) -> QuadratureMethod: ... + @quadrature_method.setter + def quadrature_method(self, method: QuadratureMethod) -> None: ... + @property + def normalize_electron_energy_distribution_enabled(self) -> bool: ... + @normalize_electron_energy_distribution_enabled.setter + def normalize_electron_energy_distribution_enabled(self, enable: bool) -> None: ... + @property + def electron_species_name(self) -> str: ... + @property + def elastic_power_loss(self) -> float: ... + +class InterfacePhase(ThermoPhase): + @property + def adjacent(self) -> dict[str, ThermoPhase]: ... + @property + def site_density(self) -> float: ... + @site_density.setter + def site_density(self, value: float) -> None: ... + @property + def coverages(self) -> Array: ... + @coverages.setter + def coverages(self, theta: CompositionLike) -> None: ... + def set_unnormalized_coverages(self, cov: ArrayLike) -> None: ... + +class PureFluid(ThermoPhase): + @property + def Q(self) -> float: ... + @Q.setter + def Q(self, Q: float) -> None: ... + @property + def TQ(self) -> tuple[float, float]: ... + @TQ.setter + def TQ(self, values: Sequence[float]) -> None: ... + @property + def PQ(self) -> tuple[float, float]: ... + @PQ.setter + def PQ(self, values: Sequence[float]) -> None: ... + @property + def ST(self) -> tuple[float, float]: ... + @ST.setter + def ST(self, values: Sequence[float]) -> None: ... + @property + def TV(self) -> tuple[float, float]: ... + @TV.setter + def TV(self, values: Sequence[float]) -> None: ... + @property + def PV(self) -> tuple[float, float]: ... + @PV.setter + def PV(self, values: Sequence[float]) -> None: ... + @property + def UP(self) -> tuple[float, float]: ... + @UP.setter + def UP(self, values: Sequence[float]) -> None: ... + @property + def VH(self) -> tuple[float, float]: ... + @VH.setter + def VH(self, values: Sequence[float]) -> None: ... + @property + def TH(self) -> tuple[float, float]: ... + @TH.setter + def TH(self, values: Sequence[float]) -> None: ... + @property + def SH(self) -> tuple[float, float]: ... + @SH.setter + def SH(self, values: Sequence[float]) -> None: ... + @property + def TDQ(self) -> tuple[float, float, float]: ... + @property + def TPQ(self) -> tuple[float, float, float]: ... + @TPQ.setter + def TPQ(self, values: Sequence[float]) -> None: ... + @property + def UVQ(self) -> tuple[float, float, float]: ... + @property + def DPQ(self) -> tuple[float, float, float]: ... + @property + def HPQ(self) -> tuple[float, float, float]: ... + @property + def SPQ(self) -> tuple[float, float, float]: ... + @property + def SVQ(self) -> tuple[float, float, float]: ... + +class Element: + num_elements_defined: int + element_symbols: tuple[str, ...] + element_names: tuple[str, ...] + def __init__(self, arg: str | bytes | int) -> None: ... + @property + def name(self) -> str: ... + @property + def atomic_number(self) -> int: ... + @property + def symbol(self) -> str: ... + @property + def weight(self) -> float: ... diff --git a/interfaces/cython/cantera/transport.pyi b/interfaces/cython/cantera/transport.pyi new file mode 100644 index 00000000000..5dda611863e --- /dev/null +++ b/interfaces/cython/cantera/transport.pyi @@ -0,0 +1,190 @@ +# This file is part of Cantera. See License.txt in the top-level directory or +# at https://cantera.org/license.txt for license and copyright information. + +from typing import Literal, TypeAlias, TypedDict + +from ._types import Array, ArrayLike +from .solutionbase import _SolutionBase + +TransportModel: TypeAlias = Literal[ + "none", + "unity-Lewis-number", + "mixture-averaged", + "mixture-averaged-CK", + "multicomponent", + "multicomponent-CK", + "ionized-gas", + "water", + "high-pressure", + "high-pressure=Chung", +] + +GeometryOptions: TypeAlias = Literal["atom", "linear", "nonlinear"] + +class GasTransportInput(TypedDict, total=False): + model: Literal["gas"] + geometry: GeometryOptions + diameter: float + well_depth: float + dipole: float + polarizability: float + rotational_relaxation: float + acentric_factor: float + dispersion_coefficient: float + quadrupole_polarizability: float + +TransportFittingErrors = TypedDict( + "TransportFittingErrors", + { + "viscosity-max-abs-error": float, + "viscosity-max-rel-error": float, + "conductivity-max-abs-error": float, + "conductivity-max-rel-error": float, + "diff-coeff-max-abs-error": float, + "diff-coeff-max-rel-error": float, + }, +) + +class GasTransportData: + def __init__( + self, + geometry: GeometryOptions | Literal[""] = "", + diameter: float = -1, + well_depth: float = -1, + dipole: float = 0.0, + polarizability: float = 0.0, + rotational_relaxation: float = 0.0, + acentric_factor: float = 0.0, + dispersion_coefficient: float = 0.0, + quadrupole_polarizability: float = 0.0, + *, + init: bool = True, + ) -> None: ... + def set_customary_units( + self, + geometry: str, + diameter: float, + well_depth: float, + dipole: float = 0.0, + polarizability: float = 0.0, + rotational_relaxation: float = 0.0, + acentric_factor: float = 0.0, + dispersion_coefficient: float = 0.0, + quadrupole_polarizability: float = 0.0, + ) -> None: ... + @property + def input_data(self) -> GasTransportInput: ... + def update_user_data(self, data: GasTransportData) -> None: ... + def clear_user_data(self) -> None: ... + @property + def geometry(self) -> GeometryOptions: ... + @geometry.setter + def geometry(self, geometry: GeometryOptions) -> None: ... + @property + def diameter(self) -> float: ... + @diameter.setter + def diameter(self, diameter: float) -> None: ... + @property + def well_depth(self) -> float: ... + @well_depth.setter + def well_depth(self, well_depth: float) -> None: ... + @property + def dipole(self) -> float: ... + @dipole.setter + def dipole(self, dipole: float) -> None: ... + @property + def polarizability(self) -> float: ... + @polarizability.setter + def polarizability(self, polarizability: float) -> None: ... + @property + def rotational_relaxation(self) -> float: ... + @rotational_relaxation.setter + def rotational_relaxation(self, rotational_relaxation: float) -> None: ... + @property + def acentric_factor(self) -> float: ... + @acentric_factor.setter + def acentric_factor(self, acentric_factor: float) -> None: ... + @property + def dispersion_coefficient(self) -> float: ... + @dispersion_coefficient.setter + def dispersion_coefficient(self, dispersion_coefficient: float) -> None: ... + @property + def quadrupole_polarizability(self) -> float: ... + @quadrupole_polarizability.setter + def quadrupole_polarizability(self, quadrupole_polarizability: float) -> None: ... + +class Transport(_SolutionBase): + @property + def transport_model(self) -> TransportModel: ... + @transport_model.setter + def transport_model(self, model: TransportModel) -> None: ... + @property + def CK_mode(self) -> bool: ... + @property + def viscosity(self) -> float: ... + @property + def species_viscosities(self) -> Array: ... + @property + def electrical_conductivity(self) -> float: ... + @property + def thermal_conductivity(self) -> float: ... + @property + def mix_diff_coeffs(self) -> Array: ... + @property + def mix_diff_coeffs_mass(self) -> Array: ... + @property + def mix_diff_coeffs_mole(self) -> Array: ... + @property + def thermal_diff_coeffs(self) -> Array: ... + @property + def multi_diff_coeffs(self) -> Array: ... + @property + def binary_diff_coeffs(self) -> Array: ... + @property + def mobilities(self) -> Array: ... + def get_viscosity_polynomial(self, i: int) -> Array: ... + def get_thermal_conductivity_polynomial(self, i: int) -> Array: ... + def get_binary_diff_coeffs_polynomial(self, i: int, j: int) -> Array: ... + def get_collision_integral_polynomials( + self, i: int, j: int + ) -> tuple[Array, Array, Array]: ... + def set_viscosity_polynomial(self, i: int, values: ArrayLike) -> None: ... + def set_thermal_conductivity_polynomial( + self, i: int, values: ArrayLike + ) -> None: ... + def set_binary_diff_coeffs_polynomial( + self, i: int, j: int, values: ArrayLike + ) -> None: ... + def set_collision_integral_polynomial( + self, + i: int, + j: int, + avalues: ArrayLike, + bvalues: ArrayLike, + cvalues: ArrayLike, + actualT: bool = False, + ) -> None: ... + @property + def transport_fitting_errors(self) -> TransportFittingErrors: ... + +class DustyGasTransport(Transport): + @property + def porosity(self) -> float: ... + @property + def tortuosity(self) -> float: ... + @property + def mean_pore_radius(self) -> float: ... + @property + def mean_particle_diameter(self) -> float: ... + @property + def permeability(self) -> float: ... + def molar_fluxes( + self, + T1: float, + T2: float, + rho1: float, + rho2: float, + Y1: ArrayLike, + Y2: ArrayLike, + delta: float, + ) -> Array: ... diff --git a/interfaces/cython/cantera/units.pyi b/interfaces/cython/cantera/units.pyi new file mode 100644 index 00000000000..56667ac5dbc --- /dev/null +++ b/interfaces/cython/cantera/units.pyi @@ -0,0 +1,89 @@ +# This file is part of Cantera. See License.txt in the top-level directory or +# at https://cantera.org/license.txt for license and copyright information. + +from typing import TypedDict, overload + +from ._types import Array + +UnitDict = TypedDict( + "UnitDict", + { + "activation-energy": str, + "current": str, + "energy": str, + "length": str, + "mass": str, + "pressure": str, + "quantity": str, + "temperature": str, + "time": str, + }, + total=False, +) +UnitDictBytes = TypedDict( + "UnitDictBytes", + { + "activation-energy": bytes, + "current": bytes, + "energy": bytes, + "length": bytes, + "mass": bytes, + "pressure": bytes, + "quantity": bytes, + "temperature": bytes, + "time": bytes, + }, + total=False, +) + +class Units: + def dimension(self, primary: str) -> float: ... + @property + def dimensions(self) -> dict[str, float]: ... + @property + def factor(self) -> float: ... + +class UnitStack: + def product(self) -> Units: ... + def join(self, exponent: float) -> None: ... + +class UnitSystem: + def defaults(self) -> UnitDictBytes: ... + @property + def units(self) -> UnitDict: ... + @units.setter + def units(self, units: UnitDict | UnitDictBytes) -> None: ... + @overload + def convert_to(self, quantity: str | float, dest: str | Units) -> float: ... + @overload + def convert_to(self, quantity: Array, dest: str | Units) -> Array: ... + @overload + def convert_to( + self, quantity: list[str | float] | tuple[str | float, ...], dest: str | Units + ) -> list[float]: ... + @overload + def convert_activation_energy_to( + self, quantity: str | float, dest: str | Units + ) -> float: ... + @overload + def convert_activation_energy_to( + self, quantity: Array, dest: str | Units + ) -> Array: ... + @overload + def convert_activation_energy_to( + self, quantity: list[str | float] | tuple[str | float, ...], dest: str | Units + ) -> list[float]: ... + @overload + def convert_rate_coeff_to( + self, quantity: str | float, dest: str | Units | UnitStack + ) -> float: ... + @overload + def convert_rate_coeff_to( + self, quantity: Array, dest: str | Units | UnitStack + ) -> Array: ... + @overload + def convert_rate_coeff_to( + self, + quantity: list[str | float] | tuple[str | float, ...], + dest: str | Units | UnitStack, + ) -> list[float]: ... diff --git a/interfaces/cython/cantera/utils.pyi b/interfaces/cython/cantera/utils.pyi new file mode 100644 index 00000000000..8312416fdbf --- /dev/null +++ b/interfaces/cython/cantera/utils.pyi @@ -0,0 +1,4 @@ +from cantera.composite import Solution + +def import_phases(filename: str, phase_names: list[str]) -> list[Solution]: ... +def add_module_directory() -> None: ... diff --git a/interfaces/cython/cantera/with_units/__init__.pyi b/interfaces/cython/cantera/with_units/__init__.pyi new file mode 100644 index 00000000000..8a86fa66630 --- /dev/null +++ b/interfaces/cython/cantera/with_units/__init__.pyi @@ -0,0 +1,39 @@ +# This file is part of Cantera. See License.txt in the top-level directory or +# at https://cantera.org/license.txt for license and copyright information. + +from pint import UnitRegistry +from pint import set_application_registry as set_application_registry + +from .solution import ( + Q_, + CarbonDioxide, + Heptane, + Hfc134a, + Hydrogen, + Methane, + Nitrogen, + Oxygen, + PureFluid, + Solution, + Water, + units, +) + +cantera_units_registry: UnitRegistry + +__all__: list[str] = [ + "Q_", + "CarbonDioxide", + "Heptane", + "Hfc134a", + "Hydrogen", + "Methane", + "Nitrogen", + "Oxygen", + "PureFluid", + "Solution", + "Water", + "cantera_units_registry", + "set_application_registry", + "units", +] diff --git a/interfaces/cython/cantera/with_units/solution.pyi b/interfaces/cython/cantera/with_units/solution.pyi new file mode 100644 index 00000000000..0b35fb6cea1 --- /dev/null +++ b/interfaces/cython/cantera/with_units/solution.pyi @@ -0,0 +1,671 @@ +# This file is part of Cantera. See License.txt in the top-level directory or +# at https://cantera.org/license.txt for license and copyright information. + +from pathlib import Path +from typing import Any, Callable, Literal, ParamSpec, TypeAlias, TypeVar + +from pint import Quantity +from pint.registry import ApplicationRegistry + +from .._types import Array +from .._utils import CanteraError as CanteraError + +__all__: list[str] = [ + "Q_", + "CanteraError", + "CarbonDioxide", + "Heptane", + "Hfc134a", + "Hydrogen", + "Methane", + "Nitrogen", + "Oxygen", + "PureFluid", + "Solution", + "Water", + "units", +] + +# Define some helpful type aliases +_PropertyPairSetter: TypeAlias = tuple[Quantity | None, Quantity | None] +_StateSetter: TypeAlias = tuple[ + Quantity | None, Quantity | None, Quantity | Array | None +] + +units: ApplicationRegistry +Q_: Quantity + +P = ParamSpec("P") +T = TypeVar("T") +def copy_doc(method: Callable[P, T]) -> Callable[P, T]: ... + +class Solution: + def __init__( + self, infile: Path | str = "", name: str = "", *, yaml: str | None = None + ) -> None: ... + @copy_doc + def report(self, *args: Any, **kwargs: Any) -> str: ... + def __call__(self, *args: Any, **kwargs: Any) -> None: ... + @property + def basis_units(self) -> Literal["kg", "kmol"]: ... + @property + @copy_doc + def X(self) -> Quantity: ... + @X.setter + def X(self, value: Quantity | Array | None) -> None: ... + @property + @copy_doc + def Y(self) -> Quantity: ... + @Y.setter + def Y(self, value: Quantity | Array | None) -> None: ... + @property + @copy_doc + def density_mass(self) -> Quantity: ... + @property + @copy_doc + def density_mole(self) -> Quantity: ... + @property + @copy_doc + def enthalpy_mass(self) -> Quantity: ... + @property + @copy_doc + def enthalpy_mole(self) -> Quantity: ... + @property + @copy_doc + def entropy_mass(self) -> Quantity: ... + @property + @copy_doc + def entropy_mole(self) -> Quantity: ... + @property + @copy_doc + def int_energy_mass(self) -> Quantity: ... + @property + @copy_doc + def int_energy_mole(self) -> Quantity: ... + @property + @copy_doc + def volume_mass(self) -> Quantity: ... + @property + @copy_doc + def volume_mole(self) -> Quantity: ... + @property + @copy_doc + def gibbs_mass(self) -> Quantity: ... + @property + @copy_doc + def gibbs_mole(self) -> Quantity: ... + @property + @copy_doc + def cp_mass(self) -> Quantity: ... + @property + @copy_doc + def cp_mole(self) -> Quantity: ... + @property + @copy_doc + def cv_mass(self) -> Quantity: ... + @property + @copy_doc + def cv_mole(self) -> Quantity: ... + @property + @copy_doc + def P(self) -> Quantity: ... + @property + @copy_doc + def P_sat(self) -> Quantity: ... + @property + @copy_doc + def T(self) -> Quantity: ... + @property + @copy_doc + def T_sat(self) -> Quantity: ... + @property + @copy_doc + def atomic_weight(self) -> Quantity: ... + @property + @copy_doc + def chemical_potentials(self) -> Quantity: ... + @property + @copy_doc + def concentrations(self) -> Quantity: ... + @property + @copy_doc + def critical_pressure(self) -> Quantity: ... + @property + @copy_doc + def critical_temperature(self) -> Quantity: ... + @property + @copy_doc + def critical_density(self) -> Quantity: ... + @property + @copy_doc + def electric_potential(self) -> Quantity: ... + @property + @copy_doc + def electrochemical_potentials(self) -> Quantity: ... + @property + @copy_doc + def isothermal_compressibility(self) -> Quantity: ... + @property + @copy_doc + def sound_speed(self) -> Quantity: ... + @property + @copy_doc + def max_temp(self) -> Quantity: ... + @property + @copy_doc + def mean_molecular_weight(self) -> Quantity: ... + @property + @copy_doc + def min_temp(self) -> Quantity: ... + @property + @copy_doc + def molecular_weights(self) -> Quantity: ... + @property + @copy_doc + def partial_molar_cp(self) -> Quantity: ... + @property + @copy_doc + def partial_molar_enthalpies(self) -> Quantity: ... + @property + @copy_doc + def partial_molar_entropies(self) -> Quantity: ... + @property + @copy_doc + def partial_molar_int_energies(self) -> Quantity: ... + @property + @copy_doc + def partial_molar_volumes(self) -> Quantity: ... + @property + @copy_doc + def reference_pressure(self) -> Quantity: ... + @property + @copy_doc + def thermal_expansion_coeff(self) -> Quantity: ... + @property + @copy_doc + def cp(self) -> Quantity: ... + @property + @copy_doc + def cv(self) -> Quantity: ... + @property + @copy_doc + def density(self) -> Quantity: ... + @property + @copy_doc + def h(self) -> Quantity: ... + @property + @copy_doc + def s(self) -> Quantity: ... + @property + @copy_doc + def g(self) -> Quantity: ... + @property + @copy_doc + def u(self) -> Quantity: ... + @property + @copy_doc + def v(self) -> Quantity: ... + @property + @copy_doc + def TP(self) -> tuple[Quantity, Quantity]: ... + @TP.setter + def TP(self, value: _PropertyPairSetter) -> None: ... + @property + @copy_doc + def DP(self) -> tuple[Quantity, Quantity]: ... + @DP.setter + def DP(self, value: _PropertyPairSetter) -> None: ... + @property + @copy_doc + def HP(self) -> tuple[Quantity, Quantity]: ... + @HP.setter + def HP(self, value: _PropertyPairSetter) -> None: ... + @property + @copy_doc + def SP(self) -> tuple[Quantity, Quantity]: ... + @SP.setter + def SP(self, value: _PropertyPairSetter) -> None: ... + @property + @copy_doc + def SV(self) -> tuple[Quantity, Quantity]: ... + @SV.setter + def SV(self, value: _PropertyPairSetter) -> None: ... + @property + @copy_doc + def TD(self) -> tuple[Quantity, Quantity]: ... + @TD.setter + def TD(self, value: _PropertyPairSetter) -> None: ... + @property + @copy_doc + def UV(self) -> tuple[Quantity, Quantity]: ... + @UV.setter + def UV(self, value: _PropertyPairSetter) -> None: ... + @property + @copy_doc + def TPX(self) -> tuple[Quantity, Quantity, Quantity]: ... + @TPX.setter + def TPX(self, value: _StateSetter) -> None: ... + @property + @copy_doc + def TPY(self) -> tuple[Quantity, Quantity, Quantity]: ... + @TPY.setter + def TPY(self, value: _StateSetter) -> None: ... + @property + @copy_doc + def DPX(self) -> tuple[Quantity, Quantity, Quantity]: ... + @DPX.setter + def DPX(self, value: _StateSetter) -> None: ... + @property + @copy_doc + def DPY(self) -> tuple[Quantity, Quantity, Quantity]: ... + @DPY.setter + def DPY(self, value: _StateSetter) -> None: ... + @property + @copy_doc + def HPX(self) -> tuple[Quantity, Quantity, Quantity]: ... + @HPX.setter + def HPX(self, value: _StateSetter) -> None: ... + @property + @copy_doc + def HPY(self) -> tuple[Quantity, Quantity, Quantity]: ... + @HPY.setter + def HPY(self, value: _StateSetter) -> None: ... + @property + @copy_doc + def SPX(self) -> tuple[Quantity, Quantity, Quantity]: ... + @SPX.setter + def SPX(self, value: _StateSetter) -> None: ... + @property + @copy_doc + def SPY(self) -> tuple[Quantity, Quantity, Quantity]: ... + @SPY.setter + def SPY(self, value: _StateSetter) -> None: ... + @property + @copy_doc + def SVX(self) -> tuple[Quantity, Quantity, Quantity]: ... + @SVX.setter + def SVX(self, value: _StateSetter) -> None: ... + @property + @copy_doc + def SVY(self) -> tuple[Quantity, Quantity, Quantity]: ... + @SVY.setter + def SVY(self, value: _StateSetter) -> None: ... + @property + @copy_doc + def TDX(self) -> tuple[Quantity, Quantity, Quantity]: ... + @TDX.setter + def TDX(self, value: _StateSetter) -> None: ... + @property + @copy_doc + def TDY(self) -> tuple[Quantity, Quantity, Quantity]: ... + @TDY.setter + def TDY(self, value: _StateSetter) -> None: ... + @property + @copy_doc + def UVX(self) -> tuple[Quantity, Quantity, Quantity]: ... + @UVX.setter + def UVX(self, value: _StateSetter) -> None: ... + @property + @copy_doc + def UVY(self) -> tuple[Quantity, Quantity, Quantity]: ... + @UVY.setter + def UVY(self, value: _StateSetter) -> None: ... + +class PureFluid: + def __init__( + self, + infile: Path | str, + name: str = "", + *, + yaml: str | None = None, + **kwargs: Any, + ) -> None: ... + @copy_doc + def report(self, *args: Any, **kwargs: Any) -> str: ... + def __call__(self, *args: Any, **kwargs: Any) -> None: ... + @property + def basis_units(self) -> Literal["kg", "kmol"]: ... + @property + @copy_doc + def X(self) -> Quantity: ... + @X.setter + def X(self, value: Quantity | Array | None) -> None: ... + @property + @copy_doc + def Y(self) -> Quantity: ... + @Y.setter + def Y(self, value: Quantity | Array | None) -> None: ... + @property + @copy_doc + def Q(self) -> Quantity: ... + @Q.setter + def Q(self, value: Quantity | None) -> None: ... + @property + @copy_doc + def density_mass(self) -> Quantity: ... + @property + @copy_doc + def density_mole(self) -> Quantity: ... + @property + @copy_doc + def enthalpy_mass(self) -> Quantity: ... + @property + @copy_doc + def enthalpy_mole(self) -> Quantity: ... + @property + @copy_doc + def entropy_mass(self) -> Quantity: ... + @property + @copy_doc + def entropy_mole(self) -> Quantity: ... + @property + @copy_doc + def int_energy_mass(self) -> Quantity: ... + @property + @copy_doc + def int_energy_mole(self) -> Quantity: ... + @property + @copy_doc + def volume_mass(self) -> Quantity: ... + @property + @copy_doc + def volume_mole(self) -> Quantity: ... + @property + @copy_doc + def gibbs_mass(self) -> Quantity: ... + @property + @copy_doc + def gibbs_mole(self) -> Quantity: ... + @property + @copy_doc + def cp_mass(self) -> Quantity: ... + @property + @copy_doc + def cp_mole(self) -> Quantity: ... + @property + @copy_doc + def cv_mass(self) -> Quantity: ... + @property + @copy_doc + def cv_mole(self) -> Quantity: ... + @property + @copy_doc + def P(self) -> Quantity: ... + @property + @copy_doc + def P_sat(self) -> Quantity: ... + @property + @copy_doc + def T(self) -> Quantity: ... + @property + @copy_doc + def T_sat(self) -> Quantity: ... + @property + @copy_doc + def atomic_weight(self) -> Quantity: ... + @property + @copy_doc + def chemical_potentials(self) -> Quantity: ... + @property + @copy_doc + def concentrations(self) -> Quantity: ... + @property + @copy_doc + def critical_pressure(self) -> Quantity: ... + @property + @copy_doc + def critical_temperature(self) -> Quantity: ... + @property + @copy_doc + def critical_density(self) -> Quantity: ... + @property + @copy_doc + def electric_potential(self) -> Quantity: ... + @property + @copy_doc + def electrochemical_potentials(self) -> Quantity: ... + @property + @copy_doc + def isothermal_compressibility(self) -> Quantity: ... + @property + @copy_doc + def sound_speed(self) -> Quantity: ... + @property + @copy_doc + def max_temp(self) -> Quantity: ... + @property + @copy_doc + def mean_molecular_weight(self) -> Quantity: ... + @property + @copy_doc + def min_temp(self) -> Quantity: ... + @property + @copy_doc + def molecular_weights(self) -> Quantity: ... + @property + @copy_doc + def partial_molar_cp(self) -> Quantity: ... + @property + @copy_doc + def partial_molar_enthalpies(self) -> Quantity: ... + @property + @copy_doc + def partial_molar_entropies(self) -> Quantity: ... + @property + @copy_doc + def partial_molar_int_energies(self) -> Quantity: ... + @property + @copy_doc + def partial_molar_volumes(self) -> Quantity: ... + @property + @copy_doc + def reference_pressure(self) -> Quantity: ... + @property + @copy_doc + def thermal_expansion_coeff(self) -> Quantity: ... + @property + @copy_doc + def cp(self) -> Quantity: ... + @property + @copy_doc + def cv(self) -> Quantity: ... + @property + @copy_doc + def density(self) -> Quantity: ... + @property + @copy_doc + def h(self) -> Quantity: ... + @property + @copy_doc + def s(self) -> Quantity: ... + @property + @copy_doc + def g(self) -> Quantity: ... + @property + @copy_doc + def u(self) -> Quantity: ... + @property + @copy_doc + def v(self) -> Quantity: ... + @property + @copy_doc + def TP(self) -> tuple[Quantity, Quantity]: ... + @TP.setter + def TP(self, value: _PropertyPairSetter) -> None: ... + @property + @copy_doc + def DP(self) -> tuple[Quantity, Quantity]: ... + @DP.setter + def DP(self, value: _PropertyPairSetter) -> None: ... + @property + @copy_doc + def HP(self) -> tuple[Quantity, Quantity]: ... + @HP.setter + def HP(self, value: _PropertyPairSetter) -> None: ... + @property + @copy_doc + def SP(self) -> tuple[Quantity, Quantity]: ... + @SP.setter + def SP(self, value: _PropertyPairSetter) -> None: ... + @property + @copy_doc + def SV(self) -> tuple[Quantity, Quantity]: ... + @SV.setter + def SV(self, value: _PropertyPairSetter) -> None: ... + @property + @copy_doc + def TD(self) -> tuple[Quantity, Quantity]: ... + @TD.setter + def TD(self, value: _PropertyPairSetter) -> None: ... + @property + @copy_doc + def UV(self) -> tuple[Quantity, Quantity]: ... + @UV.setter + def UV(self, value: _PropertyPairSetter) -> None: ... + @property + @copy_doc + def TPX(self) -> tuple[Quantity, Quantity, Quantity]: ... + @TPX.setter + def TPX(self, value: _StateSetter) -> None: ... + @property + @copy_doc + def TPY(self) -> tuple[Quantity, Quantity, Quantity]: ... + @TPY.setter + def TPY(self, value: _StateSetter) -> None: ... + @property + @copy_doc + def DPX(self) -> tuple[Quantity, Quantity, Quantity]: ... + @DPX.setter + def DPX(self, value: _StateSetter) -> None: ... + @property + @copy_doc + def DPY(self) -> tuple[Quantity, Quantity, Quantity]: ... + @DPY.setter + def DPY(self, value: _StateSetter) -> None: ... + @property + @copy_doc + def HPX(self) -> tuple[Quantity, Quantity, Quantity]: ... + @HPX.setter + def HPX(self, value: _StateSetter) -> None: ... + @property + @copy_doc + def HPY(self) -> tuple[Quantity, Quantity, Quantity]: ... + @HPY.setter + def HPY(self, value: _StateSetter) -> None: ... + @property + @copy_doc + def SPX(self) -> tuple[Quantity, Quantity, Quantity]: ... + @SPX.setter + def SPX(self, value: _StateSetter) -> None: ... + @property + @copy_doc + def SPY(self) -> tuple[Quantity, Quantity, Quantity]: ... + @SPY.setter + def SPY(self, value: _StateSetter) -> None: ... + @property + @copy_doc + def SVX(self) -> tuple[Quantity, Quantity, Quantity]: ... + @SVX.setter + def SVX(self, value: _StateSetter) -> None: ... + @property + @copy_doc + def SVY(self) -> tuple[Quantity, Quantity, Quantity]: ... + @SVY.setter + def SVY(self, value: _StateSetter) -> None: ... + @property + @copy_doc + def TDX(self) -> tuple[Quantity, Quantity, Quantity]: ... + @TDX.setter + def TDX(self, value: _StateSetter) -> None: ... + @property + @copy_doc + def TDY(self) -> tuple[Quantity, Quantity, Quantity]: ... + @TDY.setter + def TDY(self, value: _StateSetter) -> None: ... + @property + @copy_doc + def UVX(self) -> tuple[Quantity, Quantity, Quantity]: ... + @UVX.setter + def UVX(self, value: _StateSetter) -> None: ... + @property + @copy_doc + def UVY(self) -> tuple[Quantity, Quantity, Quantity]: ... + @UVY.setter + def UVY(self, value: _StateSetter) -> None: ... + @property + @copy_doc + def TPQ(self) -> tuple[Quantity, Quantity, Quantity]: ... + @TPQ.setter + def TPQ(self, value: _StateSetter) -> None: ... + @property + @copy_doc + def PQ(self) -> tuple[Quantity, Quantity]: ... + @PQ.setter + def PQ(self, value: _PropertyPairSetter) -> None: ... + @property + @copy_doc + def TQ(self) -> tuple[Quantity, Quantity]: ... + @TQ.setter + def TQ(self, value: _PropertyPairSetter) -> None: ... + @property + @copy_doc + def PV(self) -> tuple[Quantity, Quantity]: ... + @PV.setter + def PV(self, value: _PropertyPairSetter) -> None: ... + @property + @copy_doc + def SH(self) -> tuple[Quantity, Quantity]: ... + @SH.setter + def SH(self, value: _PropertyPairSetter) -> None: ... + @property + @copy_doc + def ST(self) -> tuple[Quantity, Quantity]: ... + @ST.setter + def ST(self, value: _PropertyPairSetter) -> None: ... + @property + @copy_doc + def TH(self) -> tuple[Quantity, Quantity]: ... + @TH.setter + def TH(self, value: _PropertyPairSetter) -> None: ... + @property + @copy_doc + def TV(self) -> tuple[Quantity, Quantity]: ... + @TV.setter + def TV(self, value: _PropertyPairSetter) -> None: ... + @property + @copy_doc + def UP(self) -> tuple[Quantity, Quantity]: ... + @UP.setter + def UP(self, value: _PropertyPairSetter) -> None: ... + @property + @copy_doc + def VH(self) -> tuple[Quantity, Quantity]: ... + @VH.setter + def VH(self, value: _PropertyPairSetter) -> None: ... + @property + @copy_doc + def DPQ(self) -> tuple[Quantity, Quantity, Quantity]: ... + @property + @copy_doc + def HPQ(self) -> tuple[Quantity, Quantity, Quantity]: ... + @property + @copy_doc + def SPQ(self) -> tuple[Quantity, Quantity, Quantity]: ... + @property + @copy_doc + def SVQ(self) -> tuple[Quantity, Quantity, Quantity]: ... + @property + @copy_doc + def TDQ(self) -> tuple[Quantity, Quantity, Quantity]: ... + @property + @copy_doc + def UVQ(self) -> tuple[Quantity, Quantity, Quantity]: ... + +def Heptane() -> PureFluid: ... +def CarbonDioxide() -> PureFluid: ... +def Hfc134a() -> PureFluid: ... +def Hydrogen() -> PureFluid: ... +def Methane() -> PureFluid: ... +def Nitrogen() -> PureFluid: ... +def Oxygen() -> PureFluid: ... +def Water(backend: Literal["Reynolds", "IAPWS95"] = "Reynolds") -> PureFluid: ... diff --git a/interfaces/cython/cantera/yamlwriter.pyi b/interfaces/cython/cantera/yamlwriter.pyi new file mode 100644 index 00000000000..6c3e3609ead --- /dev/null +++ b/interfaces/cython/cantera/yamlwriter.pyi @@ -0,0 +1,27 @@ +# This file is part of Cantera. See License.txt in the top-level directory or +# at https://cantera.org/license.txt for license and copyright information. + +from typing_extensions import Never + +from .solutionbase import _SolutionBase +from .units import UnitSystem + +class YamlWriter: + def set_header(self, soln: _SolutionBase) -> None: ... + def add_solution(self, soln: _SolutionBase) -> None: ... + def to_file(self, filename: str) -> None: ... + def to_string(self) -> str: ... + @property + def precision(self) -> Never: ... + @precision.setter + def precision(self, precision: int) -> None: ... + @property + def skip_user_defined(self) -> Never: ... + @skip_user_defined.setter + def skip_user_defined(self, skip: bool) -> None: ... + @property + def output_units(self) -> Never: ... + @output_units.setter + def output_units(self, units: UnitSystem) -> None: ... + def __reduce__(self) -> Never: ... + def __copy__(self) -> Never: ... diff --git a/interfaces/cython/setup.cfg.in b/interfaces/cython/setup.cfg.in index 2b6d677af03..7587cce4ba3 100644 --- a/interfaces/cython/setup.cfg.in +++ b/interfaces/cython/setup.cfg.in @@ -30,6 +30,7 @@ classifiers = Programming Language :: Python :: Implementation :: CPython Topic :: Scientific/Engineering :: Chemistry Topic :: Scientific/Engineering :: Physics + Typing :: Typed project_urls = Documentation = https://cantera.org/stable/reference Funding = https://numfocus.org/donate-to-cantera @@ -54,7 +55,7 @@ packages = # The module extension needs to be here since we don't want setuptools to compile # the extension, so there are no ``source`` files in the setup.py ``extension`` and # we have to treat the module as package data. -cantera = *.pxd, *.dll, *@py_module_ext@ +cantera = *.pxd, *.dll, *@py_module_ext@, py.typed, *.pyi, **/*.pyi cantera.data = *.* cantera.data.example_data = *.* cantera.test = *.txt diff --git a/interfaces/python_sdist/pyproject.toml.in b/interfaces/python_sdist/pyproject.toml.in index 93f92d55b02..59003f439c6 100644 --- a/interfaces/python_sdist/pyproject.toml.in +++ b/interfaces/python_sdist/pyproject.toml.in @@ -31,6 +31,7 @@ classifiers = [ "Programming Language :: Python :: Free Threading :: 1 - Unstable", "Topic :: Scientific/Engineering :: Chemistry", "Topic :: Scientific/Engineering :: Physics", + "Typing :: Typed", ] requires-python = "@py_requires_ver_str@" dependencies = [ From 9ee0d2f0db508f240131ba4b91415aecf67ea387 Mon Sep 17 00:00:00 2001 From: "Tim E. Dawson" Date: Thu, 31 Jul 2025 23:59:13 -0500 Subject: [PATCH 18/26] Add myself to AUTHORS.md --- AUTHORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.md b/AUTHORS.md index 0c6ea987fad..c50a72096cc 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -23,6 +23,7 @@ update, please report on Cantera's - **Parker Clayton** [@parkerclayton](https://github.com/parkerclayton) - **Ryan Crisanti** [@rcrisanti](https://github.com/rcrisanti) - **Nicholas Curtis** [@arghdos](https://github.com/arghdos) +- **Tim Dawson** [@TimothyEDawson](https://github.com/TimothyEDawson) - CFD Research Corporation - **Steven DeCaluwe** [@decaluwe](https://github.com/decaluwe) - Colorado School of Mines - **Vishesh Devgan** [@vdevgan](https://github.com/vdevgan) - **Matthias Diener** [@matthiasdiener](https://github.com/matthiasdiener) - University of Illinois at Urbana-Champaign From c033407b70992828467035892b218d16ab9e7a4f Mon Sep 17 00:00:00 2001 From: "Tim E. Dawson" Date: Sat, 16 Aug 2025 16:55:01 -0500 Subject: [PATCH 19/26] Merge simple stubs into code. --- interfaces/cython/cantera/_cantera.pyi | 17 --------- interfaces/cython/cantera/data.py | 2 +- interfaces/cython/cantera/data.pyi | 4 --- interfaces/cython/cantera/delegator.pyi | 8 ++--- interfaces/cython/cantera/interrupts.py | 2 +- interfaces/cython/cantera/interrupts.pyi | 1 - interfaces/cython/cantera/liquidvapor.py | 43 ++++++++++++----------- interfaces/cython/cantera/liquidvapor.pyi | 12 ------- interfaces/cython/cantera/utils.py | 8 +++-- interfaces/cython/cantera/utils.pyi | 4 --- 10 files changed, 33 insertions(+), 68 deletions(-) delete mode 100644 interfaces/cython/cantera/_cantera.pyi delete mode 100644 interfaces/cython/cantera/data.pyi delete mode 100644 interfaces/cython/cantera/interrupts.pyi delete mode 100644 interfaces/cython/cantera/liquidvapor.pyi delete mode 100644 interfaces/cython/cantera/utils.pyi diff --git a/interfaces/cython/cantera/_cantera.pyi b/interfaces/cython/cantera/_cantera.pyi deleted file mode 100644 index f521f912bd8..00000000000 --- a/interfaces/cython/cantera/_cantera.pyi +++ /dev/null @@ -1,17 +0,0 @@ -# This file is part of Cantera. See License.txt in the top-level directory or -# at https://cantera.org/license.txt for license and copyright information. - -import importlib.abc -from importlib.machinery import ModuleSpec -from types import ModuleType -from typing import Sequence, override - -class CythonPackageMetaPathFinder(importlib.abc.MetaPathFinder): - def __init__(self, name_filter: str) -> None: ... - @override - def find_spec( - self, - fullname: str, - path: Sequence[str] | None, - target: ModuleType | None = None, - ) -> ModuleSpec | None: ... diff --git a/interfaces/cython/cantera/data.py b/interfaces/cython/cantera/data.py index 594a125d7c3..cb7f5573157 100644 --- a/interfaces/cython/cantera/data.py +++ b/interfaces/cython/cantera/data.py @@ -5,7 +5,7 @@ from ._utils import get_data_directories, add_data_directory -def list_data_files(ext=".yaml"): +def list_data_files(ext: str = ".yaml") -> list[str]: """ Lists input data files. Includes files in subdirectories, except for subdirectories of the current working directory. diff --git a/interfaces/cython/cantera/data.pyi b/interfaces/cython/cantera/data.pyi deleted file mode 100644 index d75486ce65a..00000000000 --- a/interfaces/cython/cantera/data.pyi +++ /dev/null @@ -1,4 +0,0 @@ -from cantera._utils import add_directory as add_directory -from cantera._utils import get_data_directories as get_data_directories - -def list_data_files(ext: str = ".yaml") -> list[str]: ... diff --git a/interfaces/cython/cantera/delegator.pyi b/interfaces/cython/cantera/delegator.pyi index 1981082cdd2..2591cac5a01 100644 --- a/interfaces/cython/cantera/delegator.pyi +++ b/interfaces/cython/cantera/delegator.pyi @@ -1,10 +1,10 @@ # This file is part of Cantera. See License.txt in the top-level directory or # at https://cantera.org/license.txt for license and copyright information. -from typing import Callable +from collections.abc import Callable -from cantera.reaction import ExtensibleRateData +from .reaction import ExtensibleRate, ExtensibleRateData def extension( - *, name: str, data: ExtensibleRateData | None = None -) -> Callable[[type], type]: ... + *, name: str, data: type[ExtensibleRateData] | None = None +) -> Callable[[type[ExtensibleRate]], type[ExtensibleRate]]: ... diff --git a/interfaces/cython/cantera/interrupts.py b/interfaces/cython/cantera/interrupts.py index 7171d3f5389..80780763dd2 100644 --- a/interfaces/cython/cantera/interrupts.py +++ b/interfaces/cython/cantera/interrupts.py @@ -2,7 +2,7 @@ # at https://cantera.org/license.txt for license and copyright information. -def no_op(t): +def no_op(t: float) -> float: """ This function does nothing. It is used as an interrupt in the 1D solver C++ loop where a pure Python function is needed in order for diff --git a/interfaces/cython/cantera/interrupts.pyi b/interfaces/cython/cantera/interrupts.pyi deleted file mode 100644 index 1dd017bc83a..00000000000 --- a/interfaces/cython/cantera/interrupts.pyi +++ /dev/null @@ -1 +0,0 @@ -def no_op(t: float) -> float: ... diff --git a/interfaces/cython/cantera/liquidvapor.py b/interfaces/cython/cantera/liquidvapor.py index 5b583972da7..b11e27b6edd 100644 --- a/interfaces/cython/cantera/liquidvapor.py +++ b/interfaces/cython/cantera/liquidvapor.py @@ -3,8 +3,9 @@ from .thermo import PureFluid from .transport import Transport +from typing import Literal as _Literal -def Water(backend='Reynolds'): +def Water(backend: _Literal["Reynolds", "IAPWS95"] = "Reynolds") -> PureFluid: """ Create a `PureFluid` object using the equation of state for water and the `WaterTransport` class for viscosity and thermal conductivity. @@ -44,17 +45,17 @@ def Water(backend='Reynolds'): class WaterWithTransport(Transport, PureFluid): __slots__ = () - if backend == 'Reynolds': - return WaterWithTransport('liquidvapor.yaml', 'water', - transport_model='water') - if backend == 'IAPWS95': - return WaterWithTransport('liquidvapor.yaml', 'liquid-water-IAPWS95', - transport_model='water') + if backend == "Reynolds": + return WaterWithTransport("liquidvapor.yaml", "water", transport_model="water") + if backend == "IAPWS95": + return WaterWithTransport( + "liquidvapor.yaml", "liquid-water-IAPWS95", transport_model="water" + ) raise KeyError("Unknown backend '{}'".format(backend)) -def Nitrogen(): +def Nitrogen() -> PureFluid: """ Create a `PureFluid` object using the equation of state for nitrogen. @@ -70,10 +71,10 @@ def Nitrogen(): For more details, see classes :ct:`PureFluidPhase` and :ct:`tpx::nitrogen` in the Cantera C++ source code documentation. """ - return PureFluid('liquidvapor.yaml', 'nitrogen') + return PureFluid("liquidvapor.yaml", "nitrogen") -def Methane(): +def Methane() -> PureFluid: """ Create a `PureFluid` object using the equation of state for methane. @@ -89,10 +90,10 @@ def Methane(): For more details, see classes :ct:`PureFluidPhase` and :ct:`tpx::methane` in the Cantera C++ source code documentation. """ - return PureFluid('liquidvapor.yaml', 'methane') + return PureFluid("liquidvapor.yaml", "methane") -def Hydrogen(): +def Hydrogen() -> PureFluid: """ Create a `PureFluid` object using the equation of state for hydrogen. @@ -108,10 +109,10 @@ def Hydrogen(): For more details, see classes :ct:`PureFluidPhase` and :ct:`tpx::hydrogen` in the Cantera C++ source code documentation. """ - return PureFluid('liquidvapor.yaml', 'hydrogen') + return PureFluid("liquidvapor.yaml", "hydrogen") -def Oxygen(): +def Oxygen() -> PureFluid: """ Create a `PureFluid` object using the equation of state for oxygen. @@ -127,10 +128,10 @@ def Oxygen(): For more details, see classes :ct:`PureFluidPhase` and :ct:`tpx::oxygen` in the Cantera C++ source code documentation. """ - return PureFluid('liquidvapor.yaml', 'oxygen') + return PureFluid("liquidvapor.yaml", "oxygen") -def Hfc134a(): +def Hfc134a() -> PureFluid: """ Create a `PureFluid` object using the equation of state for HFC-134a. @@ -148,10 +149,10 @@ def Hfc134a(): For more details, see classes :ct:`PureFluidPhase` and :ct:`tpx::HFC134a` in the Cantera C++ source code documentation. """ - return PureFluid('liquidvapor.yaml', 'HFC-134a') + return PureFluid("liquidvapor.yaml", "HFC-134a") -def CarbonDioxide(): +def CarbonDioxide() -> PureFluid: """ Create a `PureFluid` object using the equation of state for carbon dioxide. @@ -167,10 +168,10 @@ def CarbonDioxide(): For more details, see classes :ct:`PureFluidPhase` and :ct:`tpx::CarbonDioxide` in the Cantera C++ source code documentation. """ - return PureFluid('liquidvapor.yaml', 'carbon-dioxide') + return PureFluid("liquidvapor.yaml", "carbon-dioxide") -def Heptane(): +def Heptane() -> PureFluid: """ Create a `PureFluid` object using the equation of state for heptane. @@ -186,4 +187,4 @@ def Heptane(): For more details, see classes :ct:`PureFluidPhase` and :ct:`tpx::Heptane` in the Cantera C++ source code documentation. """ - return PureFluid('liquidvapor.yaml', 'heptane') + return PureFluid("liquidvapor.yaml", "heptane") diff --git a/interfaces/cython/cantera/liquidvapor.pyi b/interfaces/cython/cantera/liquidvapor.pyi deleted file mode 100644 index fe4b4ada149..00000000000 --- a/interfaces/cython/cantera/liquidvapor.pyi +++ /dev/null @@ -1,12 +0,0 @@ -from typing import Literal - -from cantera.thermo import PureFluid - -def Water(backend: Literal["Reynolds", "IAPWS95"] = "Reynolds") -> PureFluid: ... -def Nitrogen() -> PureFluid: ... -def Methane() -> PureFluid: ... -def Hydrogen() -> PureFluid: ... -def Oxygen() -> PureFluid: ... -def Hfc134a() -> PureFluid: ... -def CarbonDioxide() -> PureFluid: ... -def Heptane() -> PureFluid: ... diff --git a/interfaces/cython/cantera/utils.py b/interfaces/cython/cantera/utils.py index 3a8b70da6f0..2ccd01c8df5 100644 --- a/interfaces/cython/cantera/utils.py +++ b/interfaces/cython/cantera/utils.py @@ -1,13 +1,15 @@ # This file is part of Cantera. See License.txt in the top-level directory or # at https://cantera.org/license.txt for license and copyright information. -import os import inspect as _inspect +import os +from collections.abc import Iterable as _Iterable +from pathlib import Path from ._utils import add_data_directory from .composite import Solution -def import_phases(filename, phase_names): +def import_phases(filename: Path | str, phase_names: _Iterable[str]) -> list[Solution]: """ Import multiple phases from one file. The phase names should be entered as a list of strings. @@ -15,7 +17,7 @@ def import_phases(filename, phase_names): return [Solution(filename, p) for p in phase_names] -def add_module_directory(): +def add_module_directory() -> None: """ Add the directory containing the module from which this function is called to the Cantera input file search path. diff --git a/interfaces/cython/cantera/utils.pyi b/interfaces/cython/cantera/utils.pyi deleted file mode 100644 index 8312416fdbf..00000000000 --- a/interfaces/cython/cantera/utils.pyi +++ /dev/null @@ -1,4 +0,0 @@ -from cantera.composite import Solution - -def import_phases(filename: str, phase_names: list[str]) -> list[Solution]: ... -def add_module_directory() -> None: ... From 56999f3b895dcaeb3466048c03aa96c7e58e5d50 Mon Sep 17 00:00:00 2001 From: "Tim E. Dawson" Date: Wed, 20 Aug 2025 17:01:50 -0500 Subject: [PATCH 20/26] Add initial pyright and mypy settings to pyproject.toml. --- interfaces/cython/pyproject.toml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/interfaces/cython/pyproject.toml b/interfaces/cython/pyproject.toml index 643c60a5ad2..10b79a8c242 100644 --- a/interfaces/cython/pyproject.toml +++ b/interfaces/cython/pyproject.toml @@ -1,3 +1,20 @@ [build-system] requires = ["setuptools>=43.0.0", "wheel", "Cython>=0.29.12,!=3.1.2"] build-backend = "setuptools.build_meta" + +[tool.pyright] +enableExperimentalFeatures = true + +[tool.mypy] +files = ["cantera"] +python_version = "3.10" +warn_unused_configs = true +strict = true +enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] +warn_unreachable = true +disallow_untyped_defs = true +disallow_incomplete_defs = true + +[[tool.mypy.overrides]] +module = "graphviz.*" +follow_untyped_imports = true From 0141249745d1376539c6a1aa4eeaee724ea2f2c2 Mon Sep 17 00:00:00 2001 From: "Tim E. Dawson" Date: Wed, 15 Oct 2025 11:17:43 -0500 Subject: [PATCH 21/26] Add typing utility functions and improve yaml2ck.py. Moved literal_type_guard to a new _types.py file and added a new function add_args_to_signature. Made several minor typing improvements in the course of updating yaml2ck.py. --- interfaces/cython/cantera/_types.py | 88 ++++++++++++++++++++++ interfaces/cython/cantera/_types.pyi | 48 +++++++++++- interfaces/cython/cantera/_utils.pyi | 4 +- interfaces/cython/cantera/ck2yaml.py | 2 +- interfaces/cython/cantera/ctml2yaml.py | 13 +--- interfaces/cython/cantera/reaction.pyi | 20 ++++- interfaces/cython/cantera/solutionbase.pyi | 16 +++- interfaces/cython/cantera/transport.pyi | 1 + interfaces/cython/cantera/yaml2ck.py | 36 +++++---- 9 files changed, 197 insertions(+), 31 deletions(-) create mode 100644 interfaces/cython/cantera/_types.py diff --git a/interfaces/cython/cantera/_types.py b/interfaces/cython/cantera/_types.py new file mode 100644 index 00000000000..445113780d4 --- /dev/null +++ b/interfaces/cython/cantera/_types.py @@ -0,0 +1,88 @@ +# This file is part of Cantera. See License.txt in the top-level directory or +# at https://cantera.org/license.txt for license and copyright information. + +from typing import get_args + + +def add_args_to_signature(*_, **__): + """Decorator which prepends (positional-only) arguments to a function signature. + + Adapted from: https://stackoverflow.com/a/75072701 + + Typically used when creating a subclass which adds new arguments to an + already-large __init__ method. Allows the original function signature to be + passed through without recreating it within the subclass. + + Use it as a decorator on your extended method, passing in the original + method as the first argument followed by the type of each argument to be + prepended. In this example, we add two new arguments of type `int` to the + `__init__` method: + + .. code-block:: python + + class BaseClass: + def __init__(self, a: str, b: int, c: int | None = None) -> None: ... + + class DerivedClass1(BaseClass): + def __init__(self, d: int, e: int, /, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + reveal_type(DerivedClass1.__init__) + # Type of "DerivedClass1.__init__" is "(self: DerivedClass1, d: int, e: int, /, ...) -> None" + + class DerivedClass2(BaseClass): + @add_args_to_signature(BaseClass.__init__, int, int) + def __init__(self, d: int, e: int, /, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + reveal_type(DerivedClass2.__init__) + # Type of "DerivedClass2.__init__" is "(BaseClass, int, int, a: str, b: int, c: int | None = None) -> None" + + Note the limitation that added arguments are prepended as positional-only, + and lose their names in the revealed signatures. Currently supports between + 1 and 3 arguments, but more could be added as additional overload options + (see `_types.pyi`). + + :param to_signature: Original method to which arguments will be appended. + :param new_arg_type0: Type of first new argument. + :param new_arg_type1: (Optional) Type of second new argument. + :param new_arg_type3: (Optional) Type of third new argument. + + .. versionadded:: 3.2 + """ + return lambda f: f + + +def literal_type_guard(tag, literal): + """Utility function for narrowing strings to specified literals. + + Typically used to check a string against the permissible keys of a + TypedDict before accessing the corresponding value. For example, + running pyright against the following code gives: + + .. code-block:: python + + class Inputs(TypedDict): + A: float + B: int + + inputs: Inputs = {"A": 1.0, "B": 2} + option: str = "test" + reveal_type(option) # Type of "option" is "Literal['test']" + + if option in ["A", "B"]: + reveal_type(option) # Type of "option" is "Literal['test']" + value = inputs[option] # error: Could not access item in TypedDict + reveal_type(value) # Type of "value" is "Unknown" + + if literal_type_guard(option, Literal["A", "B"]): + reveal_type(option) # Type of "option" is "Literal['A', 'B']" + value = inputs[option] + reveal_type(value) # Type of "value" is "float | int" + + :param tag: Input string to be checked and narrowed + :param literal: String literal to check the tag against + + .. versionadded:: 3.2 + """ + return tag in get_args(literal) diff --git a/interfaces/cython/cantera/_types.pyi b/interfaces/cython/cantera/_types.pyi index f63844cd3b1..ecdb8e41149 100644 --- a/interfaces/cython/cantera/_types.pyi +++ b/interfaces/cython/cantera/_types.pyi @@ -1,11 +1,21 @@ # This file is part of Cantera. See License.txt in the top-level directory or # at https://cantera.org/license.txt for license and copyright information. +from collections.abc import Callable from types import EllipsisType -from typing import Literal, TypeAlias, TypedDict +from typing import ( + Concatenate, + Literal, + TypeAlias, + TypedDict, + TypeGuard, + TypeVar, + overload, +) import numpy as np from numpy.typing import ArrayLike, NDArray +from typing_extensions import ParamSpec, TypeForm __all__: list[str] = [ "Array", @@ -22,6 +32,8 @@ __all__: list[str] = [ "StateDefinition", "StateSetter", "StateVariable", + "add_args_to_signature", + "literal_type_guard", ] Array: TypeAlias = NDArray[np.float64] @@ -108,3 +120,37 @@ class StateDefinition(TypedDict, total=False): TPY: StateSetter UVX: StateSetter UVY: StateSetter + +# Convenience functions provided in _types.py: +P = ParamSpec("P") + +TSelf = TypeVar("TSelf") +TReturn = TypeVar("TReturn") +T0 = TypeVar("T0") +T1 = TypeVar("T1") +T2 = TypeVar("T2") + +@overload +def add_args_to_signature( + to_signature: Callable[Concatenate[TSelf, P], TReturn], new_arg_type: type[T0] +) -> Callable[ + [Callable[..., TReturn]], Callable[Concatenate[TSelf, T0, P], TReturn] +]: ... +@overload +def add_args_to_signature( + to_signature: Callable[Concatenate[TSelf, P], TReturn], + new_arg_type0: type[T0], + new_arg_type1: type[T1], +) -> Callable[ + [Callable[..., TReturn]], Callable[Concatenate[TSelf, T0, T1, P], TReturn] +]: ... +@overload +def add_args_to_signature( + to_signature: Callable[Concatenate[TSelf, P], TReturn], + new_arg_type0: type[T0], + new_arg_type1: type[T1], + new_arg_type2: type[T2], +) -> Callable[ + [Callable[..., TReturn]], Callable[Concatenate[TSelf, T0, T1, P], TReturn] +]: ... +def literal_type_guard(tag: str, literal: TypeForm[T0]) -> TypeGuard[T0]: ... diff --git a/interfaces/cython/cantera/_utils.pyi b/interfaces/cython/cantera/_utils.pyi index a78972af71e..12413fb56e9 100644 --- a/interfaces/cython/cantera/_utils.pyi +++ b/interfaces/cython/cantera/_utils.pyi @@ -1,12 +1,14 @@ # This file is part of Cantera. See License.txt in the top-level directory or # at https://cantera.org/license.txt for license and copyright information. +from pathlib import Path from typing import Any from ._types import Array from .units import UnitDictBytes, Units, UnitStack, UnitSystem -def add_directory(directory: str) -> None: ... +def add_directory(directory: Path | str) -> None: ... +def add_data_directory(directory: Path | str) -> None: ... def get_data_directories() -> list[str]: ... __sundials_version__: str diff --git a/interfaces/cython/cantera/ck2yaml.py b/interfaces/cython/cantera/ck2yaml.py index 97e0caa889f..8892f8832bf 100644 --- a/interfaces/cython/cantera/ck2yaml.py +++ b/interfaces/cython/cantera/ck2yaml.py @@ -2252,7 +2252,7 @@ def show_duplicate_reactions(self, error_message): def convert(input_file, thermo_file=None, transport_file=None, - surface_file=None, phase_name='gas', extra_file=None, + surface_file=None, *, phase_name='gas', extra_file=None, out_name=None, single_intermediate_temperature=False, quiet=False, permissive=None, verbose=False): _, surface_names = Parser.convert_mech( diff --git a/interfaces/cython/cantera/ctml2yaml.py b/interfaces/cython/cantera/ctml2yaml.py index 0166a5a7ef8..cb6bc51d441 100644 --- a/interfaces/cython/cantera/ctml2yaml.py +++ b/interfaces/cython/cantera/ctml2yaml.py @@ -28,10 +28,8 @@ Literal, TypeAlias, TypedDict, - TypeGuard, TypeVar, cast, - get_args, ) import numpy as np @@ -39,7 +37,9 @@ from ruamel.yaml.comments import CommentedMap, CommentedSeq from ruamel.yaml.nodes import MappingNode, ScalarNode from ruamel.yaml.representer import RoundTripRepresenter, SafeRepresenter -from typing_extensions import Required, TypeForm +from typing_extensions import Required + +from ._types import literal_type_guard # yaml.version_info is a tuple with the three parts of the version yaml_version: tuple[int, int, int] = yaml.version_info @@ -160,13 +160,6 @@ class DebyeHuckelBetaMatrix(TypedDict, total=False): QuantityType | bool | dict[str, float | list[str | float]] ) -_T = TypeVar("_T") - - -def literal_type_guard(tag: str, literal: TypeForm[_T]) -> TypeGuard[_T]: - """Utility function for narrowing strings to specified literals.""" - return tag in get_args(literal) - BlockMap: type[CommentedMap] = CommentedMap diff --git a/interfaces/cython/cantera/reaction.pyi b/interfaces/cython/cantera/reaction.pyi index ece98f5069c..4758dcd09e0 100644 --- a/interfaces/cython/cantera/reaction.pyi +++ b/interfaces/cython/cantera/reaction.pyi @@ -211,18 +211,30 @@ class TsangRate(FalloffRate): ... class PlogRate(ReactionRate): def __init__( self, - rates: list[tuple[float, Arrhenius]] | None = None, + rates: list[tuple[float, ArrheniusRate]] | None = None, input_data: PlogRateInput | None = None, init: bool = True, ) -> None: ... def __call__(self, temperature: float, pressure: float) -> float: ... # type: ignore[override] @property - def rates(self) -> list[tuple[float, Arrhenius]]: ... + def rates(self) -> list[tuple[float, ArrheniusRate]]: ... @rates.setter - def rates(self, data: Iterable[tuple[float, Arrhenius]]) -> None: ... + def rates(self, data: Iterable[tuple[float, ArrheniusRate]]) -> None: ... class LinearBurkeRate(ReactionRate): ... -class ChebyshevRate(ReactionRate): ... + +class ChebyshevRate(ReactionRate): + @property + def temperature_range(self) -> tuple[float, float]: ... + @property + def pressure_range(self) -> tuple[float, float]: ... + @property + def n_temperature(self) -> int: ... + @property + def n_pressure(self) -> int: ... + @property + def data(self) -> Array: ... + class CustomRate(ReactionRate): ... class ExtensibleRate(ReactionRate): ... diff --git a/interfaces/cython/cantera/solutionbase.pyi b/interfaces/cython/cantera/solutionbase.pyi index d4073b5c1a9..d67c2a9d526 100644 --- a/interfaces/cython/cantera/solutionbase.pyi +++ b/interfaces/cython/cantera/solutionbase.pyi @@ -7,6 +7,7 @@ from typing import ( Any, Literal, TypeAlias, + TypedDict, overload, ) @@ -21,6 +22,19 @@ from .units import UnitDict, UnitDictBytes, UnitSystem _SORTING_TYPE: TypeAlias = Literal["alphabetical", "molar-mass"] | None +YamlHeader = TypedDict( + "YamlHeader", + { + "description": str, + "generator": str, + "input-files": list[str], + "cantera-version": str, + "git-commit": str, + "date": str, + }, + total=False, +) + class _SolutionBase: def __init__( self, @@ -52,7 +66,7 @@ class _SolutionBase: self, ) -> dict[str, str | list[str] | dict[str, float | dict[str, float]]]: ... @property - def input_header(self) -> dict[str, str | list[str]]: ... + def input_header(self) -> YamlHeader: ... def update_user_data(self, data: dict[str, Any]) -> None: ... def clear_user_data(self) -> None: ... def update_user_header(self, data: dict[str, str | list[str]]) -> None: ... diff --git a/interfaces/cython/cantera/transport.pyi b/interfaces/cython/cantera/transport.pyi index 5dda611863e..9c873731d04 100644 --- a/interfaces/cython/cantera/transport.pyi +++ b/interfaces/cython/cantera/transport.pyi @@ -32,6 +32,7 @@ class GasTransportInput(TypedDict, total=False): acentric_factor: float dispersion_coefficient: float quadrupole_polarizability: float + note: str TransportFittingErrors = TypedDict( "TransportFittingErrors", diff --git a/interfaces/cython/cantera/yaml2ck.py b/interfaces/cython/cantera/yaml2ck.py index b9cc03083a8..e6214c5128e 100644 --- a/interfaces/cython/cantera/yaml2ck.py +++ b/interfaces/cython/cantera/yaml2ck.py @@ -61,7 +61,10 @@ from textwrap import fill, dedent, TextWrapper import cantera as ct from email.utils import formatdate -from typing import Optional, Iterable, Literal +from typing import Any, Literal +from collections.abc import Iterable + +from ._types import add_args_to_signature if sys.version_info < (3, 9): class BooleanOptionalAction(argparse.Action): @@ -87,7 +90,7 @@ def format_usage(self): else: BooleanOptionalAction = argparse.BooleanOptionalAction -_SORTING_TYPE = Optional[Literal["alphabetical", "molar-mass"]] +_SORTING_TYPE = Literal["alphabetical", "molar-mass"] | None # number of calories in 1000 Joules CALORIES_CONSTANT = 4184.0 @@ -105,7 +108,8 @@ class HeaderTextWrapper(TextWrapper): .. versionadded:: 3.0 """ - def __init__(self, input_files: Iterable[str], *args, **kwargs): + @add_args_to_signature(TextWrapper.__init__, Iterable[str]) + def __init__(self, input_files: Iterable[str], /, *args: Any, **kwargs: Any) -> None: self.input_files = input_files super().__init__(*args, **kwargs) @@ -141,7 +145,7 @@ def wrap(self, text: str) -> list[str]: return metadata + wrapped_text -def build_elements_text(elements: Iterable[ct.Element], max_width=80) -> str: +def build_elements_text(elements: Iterable[ct.Element], max_width: int = 80) -> str: """Create element definition text. :param elements: @@ -160,7 +164,7 @@ def build_elements_text(elements: Iterable[ct.Element], max_width=80) -> str: return "ELEM\n" + elements_text + "\nEND\n" -def build_species_text(species: Iterable[ct.Species], max_width=80) -> str: +def build_species_text(species: Iterable[ct.Species], max_width: int = 80) -> str: """Create species definition text. :param species: @@ -177,7 +181,7 @@ def build_species_text(species: Iterable[ct.Species], max_width=80) -> str: if any(len(s) > 6 for s in species_names.values()): max_species_len = max(len(s) for s in species_names.keys()) max_species_len = max(5, max_species_len) - species_lines = [] + species_lines: list[str] = [] for name, note in species_names.items(): if note and len(note) > 6: species_lines.append(f"{name:<{max_species_len}} ! {note}") @@ -337,7 +341,7 @@ def build_thermodynamics_text( ) -def build_reactions_text(reactions: Iterable[ct.Reaction], species: Iterable[ct.Species]): +def build_reactions_text(reactions: Iterable[ct.Reaction], species: Iterable[ct.Species]) -> str: """ Create the reaction definition section of this file. @@ -359,7 +363,7 @@ def build_reactions_text(reactions: Iterable[ct.Reaction], species: Iterable[ct. high_line = "HIGH /{A} {b} {E_a}/" PLOG_line = "PLOG /{pressure} {A} {b} {E_a}/" max_reaction_length = max(len(r.equation) for r in reactions) - reaction_lines = [] + reaction_lines: list[str] = [] for reac in reactions: reaction_order = sum( v for k, v in reac.reactants.items() if k not in reac.orders @@ -367,7 +371,9 @@ def build_reactions_text(reactions: Iterable[ct.Reaction], species: Iterable[ct. reaction_order += sum(reac.orders.values()) unit_conversion_factor = 1_000.0 ** (reaction_order - 1) + rate: ct.ReactionRate if reac.rate.type == "Chebyshev": + assert isinstance(reac.rate, ct.ChebyshevRate) reaction_lines.append( arrhenius_line.format( equation=reac.equation, @@ -393,6 +399,7 @@ def build_reactions_text(reactions: Iterable[ct.Reaction], species: Iterable[ct. reaction_lines.append(f"CHEB /{' '.join(map(str, row))}/") elif reac.rate.type == "Arrhenius": + assert isinstance(reac.rate, ct.ArrheniusRate) if reac.reaction_type.startswith("three-body"): unit_conversion_factor *= 1_000.0 rate = reac.rate @@ -407,6 +414,7 @@ def build_reactions_text(reactions: Iterable[ct.Reaction], species: Iterable[ct. ) elif reac.rate.type == "pressure-dependent-Arrhenius": + assert isinstance(reac.rate, ct.PlogRate) rate = reac.rate.rates[-1][1] reaction_lines.append( arrhenius_line.format( @@ -428,6 +436,7 @@ def build_reactions_text(reactions: Iterable[ct.Reaction], species: Iterable[ct. ) elif reac.rate.type in ["falloff", "chemically-activated"]: + assert isinstance(reac.rate, ct.FalloffRate) rate = reac.rate if reac.rate.type == "falloff": rate1 = rate.high_rate @@ -479,6 +488,7 @@ def build_reactions_text(reactions: Iterable[ct.Reaction], species: Iterable[ct. third = reac.third_body if third is not None and third.name == "M" and len(third.efficiencies): + assert reac.third_body is not None reaction_lines.append( " ".join( f"{spec}/{value:.3E}/" @@ -496,7 +506,7 @@ def build_reactions_text(reactions: Iterable[ct.Reaction], species: Iterable[ct. return "REACTIONS CAL/MOLE MOLE\n" + "\n".join(reaction_lines) + "\nEND\n" -def build_transport_text(species: Iterable[ct.Species], separate_file: bool = False): +def build_transport_text(species: Iterable[ct.Species], separate_file: bool = False) -> str: """ Create the transport section of this file. @@ -509,7 +519,7 @@ def build_transport_text(species: Iterable[ct.Species], separate_file: bool = Fa .. versionadded:: 3.0 """ if separate_file: - text = [] + text: list[str] = [] else: text = ["\n\nTRANSPORT"] @@ -653,7 +663,7 @@ def convert( all_elements = [ct.Element(e) for e in solution.element_names] if sort_elements == "alphabetical": all_elements = sorted(all_elements, key=lambda e: e.symbol) - elif sort_elements == "atomic-mass": + elif sort_elements == "molar-mass": all_elements = sorted(all_elements, key=lambda e: e.weight) elif sort_elements is not None: raise ValueError( @@ -723,7 +733,7 @@ def convert( return output_files -def create_argparser(): +def create_argparser() -> argparse.ArgumentParser: """ Create argparse parser """ @@ -801,7 +811,7 @@ def create_argparser(): return parser -def main(): +def main() -> None: """ Parse command line arguments and pass them to `convert` From ae129a0d7ac08185d41a7205a872d59094a674d5 Mon Sep 17 00:00:00 2001 From: "Tim E. Dawson" Date: Wed, 15 Oct 2025 13:48:46 -0500 Subject: [PATCH 22/26] Remove unnecessary __all__ from _types.pyi. Only numpy.typing.ArrayLike needs to be explicitly exported, which can be achieved with the `from X import Y as Y` syntax. --- interfaces/cython/cantera/_types.pyi | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/interfaces/cython/cantera/_types.pyi b/interfaces/cython/cantera/_types.pyi index ecdb8e41149..832403f104a 100644 --- a/interfaces/cython/cantera/_types.pyi +++ b/interfaces/cython/cantera/_types.pyi @@ -14,28 +14,10 @@ from typing import ( ) import numpy as np -from numpy.typing import ArrayLike, NDArray +from numpy.typing import ArrayLike as ArrayLike +from numpy.typing import NDArray from typing_extensions import ParamSpec, TypeForm -__all__: list[str] = [ - "Array", - "ArrayLike", - "Basis", - "CompositionLike", - "CompositionVariable", - "CompressionLevel", - "EquilibriumSolver", - "FullState", - "Index", - "LogLevel", - "LogLevel7", - "StateDefinition", - "StateSetter", - "StateVariable", - "add_args_to_signature", - "literal_type_guard", -] - Array: TypeAlias = NDArray[np.float64] Index: TypeAlias = EllipsisType | int | slice | tuple[EllipsisType | int | slice, ...] From ed4fa6d9edf7ec68536e17c619d9edb1615bf73d Mon Sep 17 00:00:00 2001 From: "Tim E. Dawson" Date: Thu, 16 Oct 2025 12:33:13 -0500 Subject: [PATCH 23/26] Add type ignores until Mypy gets TypeForm support. TODO: Update add_args_to_signature to support TypeForm[T] instead of only type[T] so it can properly accept e.g. Iterable[str]. --- interfaces/cython/cantera/_types.pyi | 2 +- interfaces/cython/cantera/ctml2yaml.py | 2 +- interfaces/cython/cantera/yaml2ck.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/interfaces/cython/cantera/_types.pyi b/interfaces/cython/cantera/_types.pyi index 832403f104a..7659105392d 100644 --- a/interfaces/cython/cantera/_types.pyi +++ b/interfaces/cython/cantera/_types.pyi @@ -135,4 +135,4 @@ def add_args_to_signature( ) -> Callable[ [Callable[..., TReturn]], Callable[Concatenate[TSelf, T0, T1, P], TReturn] ]: ... -def literal_type_guard(tag: str, literal: TypeForm[T0]) -> TypeGuard[T0]: ... +def literal_type_guard(tag: str, literal: TypeForm[T0]) -> TypeGuard[T0]: ... # type: ignore[valid-type] diff --git a/interfaces/cython/cantera/ctml2yaml.py b/interfaces/cython/cantera/ctml2yaml.py index cb6bc51d441..d9aa3d850c2 100644 --- a/interfaces/cython/cantera/ctml2yaml.py +++ b/interfaces/cython/cantera/ctml2yaml.py @@ -1674,7 +1674,7 @@ def const_cp(self, thermo: etree.Element) -> ConstCpThermoInput: tag = "T0" if literal_type_guard(tag, Literal["T0", "h0", "s0", "cp0"]): - thermo_attribs[tag] = get_float_or_quantity(node) + thermo_attribs[tag] = get_float_or_quantity(node) # type: ignore[literal-required] tmin = const_cp_node.get("Tmin") if tmin is not None and tmin != "100.0": diff --git a/interfaces/cython/cantera/yaml2ck.py b/interfaces/cython/cantera/yaml2ck.py index e6214c5128e..05c897f76a9 100644 --- a/interfaces/cython/cantera/yaml2ck.py +++ b/interfaces/cython/cantera/yaml2ck.py @@ -108,7 +108,7 @@ class HeaderTextWrapper(TextWrapper): .. versionadded:: 3.0 """ - @add_args_to_signature(TextWrapper.__init__, Iterable[str]) + @add_args_to_signature(TextWrapper.__init__, Iterable[str]) # type: ignore[type-abstract] def __init__(self, input_files: Iterable[str], /, *args: Any, **kwargs: Any) -> None: self.input_files = input_files super().__init__(*args, **kwargs) From 6c435166ba24f2041b4233882d8bbe33178d78ee Mon Sep 17 00:00:00 2001 From: "Tim E. Dawson" Date: Tue, 14 Oct 2025 16:06:08 -0500 Subject: [PATCH 24/26] Add Python API type checking to CI. Includes an initial whitelist to make stubtest pass located at interfaces/cython/.mypyignore, which should be reduced to the minimal set of items which cannot be correctly inferred at runtime (e.g. __cinit__ signatures). --- .github/workflows/main.yml | 83 +++++++- interfaces/cython/.mypyignore | 59 ++++++ interfaces/cython/cantera/_onedim.pyi | 98 ++++++--- interfaces/cython/cantera/_types.pyi | 36 ++-- interfaces/cython/cantera/_utils.pyi | 4 +- interfaces/cython/cantera/ck2yaml.pyi | 4 +- interfaces/cython/cantera/composite.pyi | 166 +++++++++++---- interfaces/cython/cantera/cti2yaml.pyi | 138 ++++++------ interfaces/cython/cantera/ctml2yaml.py | 196 +++++++++--------- interfaces/cython/cantera/drawnetwork.pyi | 4 +- interfaces/cython/cantera/func1.pyi | 18 +- interfaces/cython/cantera/kinetics.pyi | 14 +- interfaces/cython/cantera/liquidvapor.py | 6 +- interfaces/cython/cantera/mixture.pyi | 9 +- interfaces/cython/cantera/onedim.pyi | 6 +- interfaces/cython/cantera/reaction.pyi | 160 ++++++++------ interfaces/cython/cantera/reactor.pyi | 29 +-- interfaces/cython/cantera/solutionbase.pyi | 40 ++-- interfaces/cython/cantera/speciesthermo.pyi | 7 +- interfaces/cython/cantera/speciesthermo.pyx | 2 +- interfaces/cython/cantera/thermo.pyi | 43 ++-- interfaces/cython/cantera/transport.pyi | 26 +-- interfaces/cython/cantera/units.pyi | 14 +- .../cython/cantera/with_units/__init__.py | 4 +- .../cython/cantera/with_units/__init__.pyi | 39 ---- .../cython/cantera/with_units/solution.py.in | 4 +- .../cython/cantera/with_units/solution.pyi | 11 +- interfaces/cython/cantera/yaml2ck.py | 2 +- 28 files changed, 765 insertions(+), 457 deletions(-) create mode 100644 interfaces/cython/.mypyignore delete mode 100644 interfaces/cython/cantera/with_units/__init__.pyi diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f9a678c9ce0..dc922f30759 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -433,6 +433,87 @@ jobs: files: ./coverage.xml,./build/pycov.xml,./interfaces/dotnet/coverage.cobertura.xml,./test/matlab_experimental/matlabCoverage.xml fail_ci_if_error: true + type-checking: + name: Verify typing correctness and coverage of the public Python API + runs-on: ubuntu-22.04 + timeout-minutes: 20 + needs: ["ubuntu-multiple-pythons"] + strategy: + fail-fast: false + steps: + # Only need pyproject.toml for type checker settings + - name: Checkout the repository + uses: actions/checkout@v5 + with: + fetch-depth: 0 + persist-credentials: false + - name: Setup Python + uses: actions/setup-python@v6 + with: + python-version: "3.10" + architecture: x64 + - name: Install Apt dependencies + run: | + sudo apt update + sudo apt install graphviz libhdf5-103 libfmt-dev libopenblas0-openmp + - name: Download the wheel artifact + uses: actions/download-artifact@v5 + with: + name: cantera-wheel-3.10-ubuntu-22.04 + path: dist + - name: Download the Cantera shared library (.so) + uses: actions/download-artifact@v5 + with: + name: libcantera_shared-ubuntu-22.04.so + path: build/lib + - name: Set up environment + run: | + ln -s libcantera_shared.so build/lib/libcantera_shared.so.3 + echo "LD_LIBRARY_PATH=build/lib" >> $GITHUB_ENV + mkdir -p type_check + - name: Upgrade pip + run: python3 -m pip install -U pip setuptools wheel + - name: Install Python dependencies + run: | + python3 -m pip install numpy ruamel.yaml pandas pandas-stubs \ + scipy pint graphviz typing_extensions mypy[reports] + python3 -m pip install --pre --no-index --find-links dist cantera + - name: Check static type correctness with mypy + run: | + mypy --config-file interfaces/cython/pyproject.toml \ + -p cantera >> type_check/mypy_check.txt + - name: Generate coverage report with mypy + if: failure() || success() + run: | + mypy --config-file interfaces/cython/pyproject.toml \ + --txt-report type_check/ interfaces/cython/cantera + cat type_check/index.txt >> $GITHUB_STEP_SUMMARY + - name: Generate coverage report with pyright + if: failure() || success() + uses: jakebailey/pyright-action@6cabc0f01c4994be48fd45cd9dbacdd6e1ee6e5e # v2.3.3 + with: + ignore-external: true + verify-types: cantera + - name: Check type correctness against runtime with stubtest + if: failure() || success() + run: | + stubtest --mypy-config-file interfaces/cython/pyproject.toml \ + --ignore-disjoint-bases --allowlist interfaces/cython/.mypyignore \ + --concise cantera >> type_check/stubtest_concise.txt + stubtest --mypy-config-file interfaces/cython/pyproject.toml \ + --ignore-disjoint-bases --allowlist interfaces/cython/.mypyignore \ + cantera >> type_check/stubtest_detailed.txt + stubtest --mypy-config-file interfaces/cython/pyproject.toml \ + --ignore-disjoint-bases --allowlist interfaces/cython/.mypyignore \ + --generate-allowlist cantera >> type_check/stubtest_allowlist.txt + - name: Archive Python API type checking results + if: failure() || success() + uses: actions/upload-artifact@v4 + with: + name: python-type-check-reports + path: type_check/* + retention-days: 5 + docs: name: Build docs runs-on: ubuntu-latest @@ -863,7 +944,7 @@ jobs: - name: Install Python dependencies run: | python -m pip install -U pip setuptools wheel - python -m pip install 'scons<4.4.0' pypiwin32 numpy ruamel.yaml cython!=3.1.2 pandas graphviz pytest pytest-xdist pytest-github-actions-annotate-failures + python -m pip install 'scons<4.4.0' pypiwin32 numpy ruamel.yaml cython!=3.1.2 pandas graphviz pytest pytest-xdist pytest-github-actions-annotate-failures typing_extensions - name: Restore Boost cache uses: actions/cache@v4 id: cache-boost diff --git a/interfaces/cython/.mypyignore b/interfaces/cython/.mypyignore new file mode 100644 index 00000000000..e96c4da1793 --- /dev/null +++ b/interfaces/cython/.mypyignore @@ -0,0 +1,59 @@ +cantera._types.* + +# Exclude ArgumentParser, Path, and various types +((_|\w)+\.)+_?(ArgumentParser|Path|Any|Iterable|Sequence|TypedDict|TypeVar).* + +# Exclude all dunder methods +# cantera\.((_|\w)+\.)+__[a-zA-Z0-9_]*__ + +# Exclude all dunder methods except for __init__ +# cantera\.((_|\w)+\.)+__(?!init__$)[a-zA-Z0-9_]*__ + +# Exclude specific dunder methods +cantera\.((_|\w)+\.)+__(.*cython|test|pyx_capi|mutable_keys|readonly_keys)__ + +# Current output of stubtest --generate-allowlist +cantera.Arrhenius.__init__ +cantera.ArrheniusRate.__init__ +cantera.BlowersMaselRate.__init__ +cantera.ConnectorNode.__init__ +cantera.ElectronCollisionPlasmaRate.__init__ +cantera.ExtensibleRate.__init__ +cantera.FalloffRate.__init__ +cantera.GasTransportData.__init__ +cantera.InterfaceArrheniusRate.__init__ +cantera.InterfaceBlowersMaselRate.__init__ +cantera.Mixture.__init__ +cantera.PlogRate.__init__ +cantera.Reaction.__init__ +cantera.ReactionPathDiagram.__init__ +cantera.SolutionArray.__init__ +cantera.SolutionArrayBase.__init__ +cantera.SpeciesThermo.__init__ +cantera.StickingBlowersMaselRate.__init__ +cantera.SystemJacobian.__init__ +cantera.ThirdBody.__init__ +cantera.TwoTempPlasmaRate.__init__ +cantera.ck2yaml.ChemicallyActivated.__init__ +cantera.ck2yaml.Falloff.__init__ +cantera.composite.SolutionArray.__init__ +cantera.jacobians.SystemJacobian.__init__ +cantera.mixture.Mixture.__init__ +cantera.reaction.Arrhenius.__init__ +cantera.reaction.ArrheniusRate.__init__ +cantera.reaction.BlowersMaselRate.__init__ +cantera.reaction.ElectronCollisionPlasmaRate.__init__ +cantera.reaction.ExtensibleRate.__init__ +cantera.reaction.FalloffRate.__init__ +cantera.reaction.InterfaceArrheniusRate.__init__ +cantera.reaction.InterfaceBlowersMaselRate.__init__ +cantera.reaction.PlogRate.__init__ +cantera.reaction.Reaction.__init__ +cantera.reaction.StickingBlowersMaselRate.__init__ +cantera.reaction.ThirdBody.__init__ +cantera.reaction.TwoTempPlasmaRate.__init__ +cantera.reactionpath.ReactionPathDiagram.__init__ +cantera.reactor.ConnectorNode.__init__ +cantera.solutionbase.SolutionArrayBase.__init__ +cantera.speciesthermo.SpeciesThermo.__init__ +cantera.transport.GasTransportData.__init__ diff --git a/interfaces/cython/cantera/_onedim.pyi b/interfaces/cython/cantera/_onedim.pyi index 1055113ebf6..191c3d452a5 100644 --- a/interfaces/cython/cantera/_onedim.pyi +++ b/interfaces/cython/cantera/_onedim.pyi @@ -1,10 +1,10 @@ # This file is part of Cantera. See License.txt in the top-level directory or # at https://cantera.org/license.txt for license and copyright information. -from collections.abc import Sequence +from collections.abc import Callable, Sequence from typing import ( Any, - Callable, + ClassVar, Literal, TypedDict, overload, @@ -23,10 +23,10 @@ from ._types import ( ) from .jacobians import SystemJacobian from .solutionbase import SolutionArrayBase, _SolutionBase -from .transport import TransportModel +from .transport import _TransportModel -ToleranceSettings = TypedDict( - "ToleranceSettings", +_ToleranceSettings = TypedDict( + "_ToleranceSettings", { "transient-abstol": float, "steady-abstol": float, @@ -35,32 +35,34 @@ ToleranceSettings = TypedDict( }, ) -class PhaseSettings(TypedDict): +class _PhaseSettings(TypedDict): name: str source: str -class FixedPointSettings(TypedDict): +class _FixedPointSettings(TypedDict): location: float temperature: float -Domain1DSettings = TypedDict( - "Domain1DSettings", +_Domain1DSettings = TypedDict( + "_Domain1DSettings", { "type": str, "points": int, - "tolerances": ToleranceSettings, - "transport-model": TransportModel, - "phase": PhaseSettings, + "tolerances": _ToleranceSettings, + "transport-model": _TransportModel, + "phase": _PhaseSettings, "radiation-enabled": bool, "energy-enabled": bool, "Soret-enabled": bool, "flux-gradient-basis": Literal[0, 1], "refine-criteria": RefineCriteria, - "fixed-point": FixedPointSettings, + "fixed-point": _FixedPointSettings, }, ) class Domain1D: + _domain_type: ClassVar[str] + have_user_tolerances: bool def __init__(self, phase: _SolutionBase, *args: Any, **kwargs: Any) -> None: ... @property def phase(self) -> _SolutionBase: ... @@ -80,6 +82,37 @@ class Domain1D: @property def component_names(self) -> list[str]: ... def component_index(self, name: str) -> int: ... + def _has_component(self, name: str) -> bool: ... + @overload + def info( + self, + keys: Sequence[str] | None = None, + rows: int = 10, + width: int | None = None, + display: Literal[False] = False, + ) -> str: ... + @overload + def info( + self, + keys: Sequence[str] | None = None, + rows: int = 10, + width: int | None = None, + display: bool = True, + ) -> None: ... + def update_state(self, loc: int) -> None: ... + @property + def grid(self) -> Array: ... + @grid.setter + def grid(self, grid: ArrayLike) -> None: ... + def value(self, component: str) -> float: ... + def set_value(self, component: str, value: float) -> None: ... + def values(self, component: str) -> Array: ... + def set_values(self, component: str, values: Array) -> None: ... + def residuals(self, component: str) -> Array: ... + def set_profile( + self, component: str, positions: Sequence[float], values: Sequence[float] + ) -> None: ... + def set_flat_profile(self, component: str, value: float) -> None: ... def set_bounds( self, *, @@ -125,10 +158,6 @@ class Domain1D: @overload def transient_abstol(self, component: None = None) -> Array: ... @property - def grid(self) -> Array: ... - @grid.setter - def grid(self, grid: ArrayLike) -> None: ... - @property def name(self) -> str: ... @name.setter def name(self, name: str) -> None: ... @@ -136,7 +165,7 @@ class Domain1D: def __reduce__(self) -> Never: ... def __copy__(self) -> Never: ... @property - def settings(self) -> Domain1DSettings: ... + def settings(self) -> _Domain1DSettings: ... class Boundary1D(Domain1D): def __init__(self, phase: _SolutionBase, name: str | None = None) -> None: ... @@ -168,6 +197,9 @@ class SymmetryPlane1D(Boundary1D): ... class Surface1D(Boundary1D): ... class ReactingSurface1D(Boundary1D): + surface: _SolutionBase + @property + def phase(self) -> _SolutionBase: ... @property def coverage_enabled(self) -> bool: ... @coverage_enabled.setter @@ -176,9 +208,21 @@ class ReactingSurface1D(Boundary1D): class FlowBase(Domain1D): def __init__(self, *args: Any, **kwargs: Any) -> None: ... @property - def P(self) -> float: ... + def P(self) -> Array: ... @P.setter - def P(self, P: float) -> None: ... + def P(self, P: Array) -> None: ... + @property + def T(self) -> Array: ... + @property + def velocity(self) -> Array: ... + @property + def spread_rate(self) -> Array: ... + @property + def radial_pressure_gradient(self) -> Array: ... + @property + def electric_field(self) -> Array: ... + @property + def oxidizer_velocity(self) -> Array: ... @property def transport_model(self) -> str: ... @transport_model.setter @@ -195,8 +239,8 @@ class FlowBase(Domain1D): def energy_enabled(self) -> bool: ... @energy_enabled.setter def energy_enabled(self, enable: bool) -> None: ... - def set_fixed_temp_profile(self, pos: Array, temp: Array) -> None: ... - def get_settings3(self) -> Domain1DSettings: ... + def set_fixed_temp_profile(self, pos: Array, T: Array) -> None: ... + def get_settings3(self) -> _Domain1DSettings: ... @property def boundary_emissivities(self) -> tuple[float, float]: ... @boundary_emissivities.setter @@ -242,7 +286,9 @@ class AxisymmetricFlow(FlowBase): ... class Sim1D: domains: tuple[Domain1D] - def __init__(self, domains: Sequence[Domain1D], *args: Any, **kwargs: Any) -> None: ... + def __init__( + self, domains: Sequence[Domain1D], *args: Any, **kwargs: Any + ) -> None: ... def phase(self, domain: int | None = None) -> _SolutionBase: ... def set_interrupt(self, f: Callable[[float], float]) -> None: ... def set_time_step_callback(self, f: Callable[[float], float]) -> None: ... @@ -297,14 +343,14 @@ class Sim1D: refine_grid: bool = True, auto: bool = False, ) -> None: ... - def refine(self, loglevel: LogLevel) -> None: ... + def refine(self, loglevel: LogLevel = 1) -> None: ... def set_refine_criteria( self, domain: Domain1D | str | int, ratio: float = 10.0, slope: float = 0.8, curve: float = 0.8, - prune: float = 0.0, + prune: float = 0.05, ) -> None: ... def get_refine_criteria(self, domain: Domain1D | str | int) -> RefineCriteria: ... def set_grid_min( @@ -341,7 +387,7 @@ class Sim1D: ) -> dict[str, str]: ... def restore_time_stepping_solution(self) -> None: ... def restore_steady_solution(self) -> None: ... - def show_state(self, print_time: bool = True) -> None: ... + def show_stats(self, print_time: bool = True) -> None: ... def clear_stats(self) -> None: ... def solve_adjoint( self, diff --git a/interfaces/cython/cantera/_types.pyi b/interfaces/cython/cantera/_types.pyi index 7659105392d..d789ef78305 100644 --- a/interfaces/cython/cantera/_types.pyi +++ b/interfaces/cython/cantera/_types.pyi @@ -104,35 +104,35 @@ class StateDefinition(TypedDict, total=False): UVY: StateSetter # Convenience functions provided in _types.py: -P = ParamSpec("P") +_P = ParamSpec("_P") -TSelf = TypeVar("TSelf") -TReturn = TypeVar("TReturn") -T0 = TypeVar("T0") -T1 = TypeVar("T1") -T2 = TypeVar("T2") +_TSelf = TypeVar("_TSelf") +_TReturn = TypeVar("_TReturn") +_T0 = TypeVar("_T0") +_T1 = TypeVar("_T1") +_T2 = TypeVar("_T2") @overload def add_args_to_signature( - to_signature: Callable[Concatenate[TSelf, P], TReturn], new_arg_type: type[T0] + to_signature: Callable[Concatenate[_TSelf, _P], _TReturn], new_arg_type: type[_T0] ) -> Callable[ - [Callable[..., TReturn]], Callable[Concatenate[TSelf, T0, P], TReturn] + [Callable[..., _TReturn]], Callable[Concatenate[_TSelf, _T0, _P], _TReturn] ]: ... @overload def add_args_to_signature( - to_signature: Callable[Concatenate[TSelf, P], TReturn], - new_arg_type0: type[T0], - new_arg_type1: type[T1], + to_signature: Callable[Concatenate[_TSelf, _P], _TReturn], + new_arg_type0: type[_T0], + new_arg_type1: type[_T1], ) -> Callable[ - [Callable[..., TReturn]], Callable[Concatenate[TSelf, T0, T1, P], TReturn] + [Callable[..., _TReturn]], Callable[Concatenate[_TSelf, _T0, _T1, _P], _TReturn] ]: ... @overload def add_args_to_signature( - to_signature: Callable[Concatenate[TSelf, P], TReturn], - new_arg_type0: type[T0], - new_arg_type1: type[T1], - new_arg_type2: type[T2], + to_signature: Callable[Concatenate[_TSelf, _P], _TReturn], + new_arg_type0: type[_T0], + new_arg_type1: type[_T1], + new_arg_type2: type[_T2], ) -> Callable[ - [Callable[..., TReturn]], Callable[Concatenate[TSelf, T0, T1, P], TReturn] + [Callable[..., _TReturn]], Callable[Concatenate[_TSelf, _T0, _T1, _P], _TReturn] ]: ... -def literal_type_guard(tag: str, literal: TypeForm[T0]) -> TypeGuard[T0]: ... # type: ignore[valid-type] +def literal_type_guard(tag: str, literal: TypeForm[_T0]) -> TypeGuard[_T0]: ... # type: ignore[valid-type] diff --git a/interfaces/cython/cantera/_utils.pyi b/interfaces/cython/cantera/_utils.pyi index 12413fb56e9..bfc6ae451f5 100644 --- a/interfaces/cython/cantera/_utils.pyi +++ b/interfaces/cython/cantera/_utils.pyi @@ -5,7 +5,7 @@ from pathlib import Path from typing import Any from ._types import Array -from .units import UnitDictBytes, Units, UnitStack, UnitSystem +from .units import Units, UnitStack, UnitSystem, _UnitDictBytes def add_directory(directory: Path | str) -> None: ... def add_data_directory(directory: Path | str) -> None: ... @@ -31,7 +31,7 @@ class CanteraError(RuntimeError): def set_stack_trace_depth(depth: int) -> None: ... class AnyMap(dict[str, Any]): - def default_units(self) -> UnitDictBytes: ... + def default_units(self) -> _UnitDictBytes: ... @property def units(self) -> UnitSystem: ... def convert(self, key: str, dest: str | Units) -> float | Array | list[float]: ... diff --git a/interfaces/cython/cantera/ck2yaml.pyi b/interfaces/cython/cantera/ck2yaml.pyi index 60715565012..9a8e3723204 100644 --- a/interfaces/cython/cantera/ck2yaml.pyi +++ b/interfaces/cython/cantera/ck2yaml.pyi @@ -14,9 +14,9 @@ from typing_extensions import Never from ._types import Array -yaml_version: str +yaml_version: tuple[int, int, int] yaml_min_version: tuple[int, int, int] -BlockMap: CommentedMap +BlockMap: type[CommentedMap] class ErrorFormatter(logging.Formatter): def format(self, record: logging.LogRecord) -> str: ... diff --git a/interfaces/cython/cantera/composite.pyi b/interfaces/cython/cantera/composite.pyi index 0cbfaf7faab..07340fde0f2 100644 --- a/interfaces/cython/cantera/composite.pyi +++ b/interfaces/cython/cantera/composite.pyi @@ -2,6 +2,7 @@ # at https://cantera.org/license.txt for license and copyright information. from collections.abc import Sequence +from pathlib import Path from typing import ( Any, Generic, @@ -11,11 +12,10 @@ from typing import ( ) from pandas import DataFrame -from typing_extensions import Never, Unpack, override +from typing_extensions import Never, Self, Unpack, override from ._types import ( Array, - ArrayCompositionLike, ArrayLike, ArrayPureFluidStateSetter, ArrayStateSetter, @@ -29,28 +29,28 @@ from ._types import ( StateDefinition, StateSetter, ) -from .kinetics import DerivativeSettings, InterfaceKinetics, Kinetics +from .kinetics import InterfaceKinetics, Kinetics, _DerivativeSettings, _KineticsType from .reaction import Reaction -from .solutionbase import SolutionArrayBase, _SolutionBase +from .solutionbase import SolutionArrayBase, _SolutionBase, _SortingType, _YamlHeader from .thermo import ( InterfacePhase, - PhaseOfMatter, PureFluid, - QuadratureMethod, Species, ThermoPhase, - ThermoType, + _PhaseOfMatter, + _QuadratureMethod, + _ThermoType, ) from .transport import ( DustyGasTransport, Transport, - TransportFittingErrors, - TransportModel, + _TransportFittingErrors, + _TransportModel, ) -from .units import Units +from .units import Units, UnitSystem, _UnitDict, _UnitDictBytes # Generic representing a valid "phase" input -P = TypeVar("P", bound=_SolutionBase) +_P = TypeVar("_P", bound=_SolutionBase) class Solution(Transport, Kinetics, ThermoPhase): ... class Interface(InterfaceKinetics, InterfacePhase): ... @@ -132,11 +132,80 @@ class Quantity: @property def G(self) -> float: ... # Dynamically-added properties acting as pass-throughs to Solution class + # From _SolutionBase: + @property + def name(self) -> str: ... + @name.setter + def name(self, name: str) -> None: ... + @property + def source(self) -> str: ... + @property + def composite( + self, + ) -> tuple[_ThermoType | None, _KineticsType | None, _TransportModel | None]: ... + @property + def input_data( + self, + ) -> dict[str, str | list[str] | dict[str, float | dict[str, float]]]: ... + @property + def input_header(self) -> _YamlHeader: ... + def update_user_data(self, data: dict[str, Any]) -> None: ... + def clear_user_data(self, *args: Any, **kwargs: Any) -> None: ... + def update_user_header(self, data: dict[str, str | list[str]]) -> None: ... + def clear_user_header(self, *args: Any, **kwargs: Any) -> None: ... + @overload + def write_yaml( + self, + filename: None, + phases: Sequence[ThermoPhase] | None = None, + units: UnitSystem | _UnitDict | _UnitDictBytes | None = None, + precision: int | None = None, + skip_user_defined: bool | None = None, + header: bool = True, + ) -> str: ... + @overload + def write_yaml( + self, + filename: str | Path, + phases: Sequence[ThermoPhase] | None = None, + units: UnitSystem | _UnitDict | _UnitDictBytes | None = None, + precision: int | None = None, + skip_user_defined: bool | None = None, + header: bool = True, + ) -> None: ... + @overload + def write_yaml( + self, + filename: str | Path | None = None, + phases: Sequence[ThermoPhase] | None = None, + units: UnitSystem | _UnitDict | _UnitDictBytes | None = None, + precision: int | None = None, + skip_user_defined: bool | None = None, + header: bool = True, + ) -> str | None: ... + def write_chemkin( + self, + mechanism_path: str | Path | None = None, + thermo_path: str | Path | None = None, + transport_path: str | Path | None = None, + sort_species: _SortingType = None, + sort_elements: _SortingType = None, + overwrite: bool = False, + quiet: bool = False, + ) -> None: ... + @property + def selected_species(self) -> list[int]: ... + @selected_species.setter + def selected_species( + self, species: str | int | Sequence[str] | Sequence[int] + ) -> None: ... + def __getstate__(self) -> str: ... + # From Transport: @property - def transport_model(self) -> TransportModel: ... + def transport_model(self) -> _TransportModel: ... @transport_model.setter - def transport_model(self, model: TransportModel) -> None: ... + def transport_model(self, model: _TransportModel) -> None: ... @property def CK_mode(self) -> bool: ... @property @@ -184,7 +253,7 @@ class Quantity: actualT: bool = False, ) -> None: ... @property - def transport_fitting_errors(self) -> TransportFittingErrors: ... + def transport_fitting_errors(self) -> _TransportFittingErrors: ... # From Kinetics: @property @@ -195,11 +264,14 @@ class Quantity: def n_reactions(self) -> int: ... @property def n_phases(self) -> int: ... + def kinetics_species_index( + self, species: str | bytes | int, phase: int = 0 + ) -> int: ... def kinetics_species_name(self, k: int) -> str: ... @property def kinetics_species_names(self) -> list[str]: ... def reaction(self, i_reaction: int) -> Reaction: ... - def reactions(self) -> list[Reaction]: ... + def reactions(self, *args: Any, **kwargs: Any) -> list[Reaction]: ... def modify_reaction(self, irxn: int, rxn: Reaction) -> None: ... def add_reaction(self, rxn: Reaction) -> None: ... def multiplier(self, i_reaction: int) -> float: ... @@ -236,9 +308,9 @@ class Quantity: @property def net_production_rates(self) -> Array: ... @property - def derivative_settings(self) -> DerivativeSettings: ... + def derivative_settings(self) -> _DerivativeSettings: ... @derivative_settings.setter - def derivative_settings(self, settings: DerivativeSettings) -> None: ... + def derivative_settings(self, settings: _DerivativeSettings) -> None: ... @property def forward_rate_constants_ddT(self) -> Array: ... @property @@ -326,9 +398,9 @@ class Quantity: # From ThermoPhase: @property - def thermo_model(self) -> ThermoType: ... + def thermo_model(self) -> _ThermoType: ... @property - def phase_of_matter(self) -> PhaseOfMatter: ... + def phase_of_matter(self) -> _PhaseOfMatter: ... def report(self, show_thermo: bool = True, threshold: float = 1e-14) -> str: ... @property def is_pure(self) -> bool: ... @@ -622,9 +694,20 @@ class Quantity: def Te(self, value: float) -> None: ... @property def Pe(self) -> float: ... + @property + def reduced_electric_field(self) -> float: ... + @reduced_electric_field.setter + def reduced_electric_field(self, value: float) -> None: ... + @property + def electric_field(self) -> float: ... + @electric_field.setter + def electric_field(self, value: float) -> None: ... def set_discretized_electron_energy_distribution( self, levels: ArrayLike, distribution: ArrayLike ) -> None: ... + def update_electron_energy_distribution( + self, *args: Any, **kwargs: Any + ) -> None: ... @property def n_electron_energy_levels(self) -> int: ... @property @@ -646,9 +729,9 @@ class Quantity: @mean_electron_energy.setter def mean_electron_energy(self, energy: float) -> None: ... @property - def quadrature_method(self) -> QuadratureMethod: ... + def quadrature_method(self) -> _QuadratureMethod: ... @quadrature_method.setter - def quadrature_method(self, method: QuadratureMethod) -> None: ... + def quadrature_method(self, method: _QuadratureMethod) -> None: ... @property def normalize_electron_energy_distribution_enabled(self) -> bool: ... @normalize_electron_energy_distribution_enabled.setter @@ -658,19 +741,20 @@ class Quantity: @property def elastic_power_loss(self) -> float: ... -class SolutionArray(SolutionArrayBase, Generic[P]): - _phase: P +class SolutionArray(SolutionArrayBase, Generic[_P]): + _phase: _P | None def __init__( self, - phase: P, + phase: _P, shape: int | tuple[int, ...] = (0,), states: ArrayLike | None = None, extra: str | Sequence[str] | dict[str, ArrayLike] | None = None, meta: dict[str, Any] = {}, init: bool = True, ) -> None: ... - def __getitem__(self, index: Index) -> SolutionArray[P]: ... - def __call__(self, *species: str) -> SolutionArray[P]: ... + def __getitem__(self, index: Index) -> SolutionArray[_P]: ... + def __call__(self, *species: str) -> SolutionArray[_P]: ... + def __len__(self) -> int: ... @property def ndim(self) -> int: ... @property @@ -923,8 +1007,6 @@ class SolutionArray(SolutionArrayBase, Generic[P]): def T(self) -> Array: ... @property def Te(self) -> Array: ... - @Te.setter - def Te(self, value: ArrayLike) -> None: ... @property def density(self) -> Array: ... @property @@ -1003,18 +1085,14 @@ class SolutionArray(SolutionArrayBase, Generic[P]): def thermal_conductivity(self) -> Array: ... # strings @property - def phase_of_matter(self) -> PhaseOfMatter: ... + def phase_of_matter(self) -> _PhaseOfMatter: ... # n_species # from ThermoPhase @property def Y(self) -> Array: ... - @Y.setter - def Y(self, Y: ArrayCompositionLike) -> None: ... @property def X(self) -> Array: ... - @X.setter - def X(self, X: ArrayCompositionLike) -> None: ... @property def concentrations(self) -> Array: ... @property @@ -1215,11 +1293,19 @@ class SolutionArray(SolutionArrayBase, Generic[P]): self: SolutionArray[Kinetics], species: str | bytes | int, phase: int = 0 ) -> int: ... def reaction(self: SolutionArray[Kinetics], i_reaction: int) -> Reaction: ... - def reactions(self: SolutionArray[Kinetics]) -> list[Reaction]: ... - def modify_reaction(self: SolutionArray[Kinetics], irxn: int, rxn: Reaction) -> None: ... + def reactions( + self: SolutionArray[Kinetics], *args: Any, **kwargs: Any + ) -> list[Reaction]: ... + def modify_reaction( + self: SolutionArray[Kinetics], irxn: int, rxn: Reaction + ) -> None: ... def multiplier(self: SolutionArray[Kinetics], i_reaction: int) -> float: ... - def set_multiplier(self: SolutionArray[Kinetics], value: float, i_reaction: int = -1) -> None: ... - def reaction_equations(self: SolutionArray[Kinetics], indices: Sequence[int] | None = None) -> list[str]: ... + def set_multiplier( + self: SolutionArray[Kinetics], value: float, i_reaction: int = -1 + ) -> None: ... + def reaction_equations( + self: SolutionArray[Kinetics], indices: Sequence[int] | None = None + ) -> list[str]: ... def reactant_stoich_coeff( self: SolutionArray[Kinetics], k_spec: str | bytes | int, i_reaction: int ) -> float: ... @@ -1245,13 +1331,7 @@ class SolutionArray(SolutionArrayBase, Generic[P]): # interface_n_species @property def coverages(self: SolutionArray[Interface]) -> Array: ... - @coverages.setter - def coverages( - self: SolutionArray[Interface], theta: ArrayCompositionLike - ) -> None: ... # purefluid_scalar @property def Q(self: SolutionArray[PureFluid]) -> Array: ... - @Q.setter - def Q(self: SolutionArray[PureFluid], Q: Array) -> None: ... diff --git a/interfaces/cython/cantera/cti2yaml.pyi b/interfaces/cython/cantera/cti2yaml.pyi index 8526332ce8e..76fa028ef85 100644 --- a/interfaces/cython/cantera/cti2yaml.pyi +++ b/interfaces/cython/cantera/cti2yaml.pyi @@ -16,7 +16,7 @@ yaml_min_version: tuple[int, int, int] class InputError(Exception): def __init__(self, msg: str, *args: Any) -> None: ... -BlockMap: CommentedMap +BlockMap: type[CommentedMap] def FlowMap(*args: Any, **kwargs: Any) -> CommentedMap: ... def FlowList(*args: Any, **kwargs: Any) -> CommentedSeq: ... @@ -24,12 +24,12 @@ def float2string(data: float) -> str: ... def represent_float(self: SafeRepresenter, data: float) -> str: ... def applyUnits(value: float | tuple[float, str]) -> float | str: ... -OldKineticsModel: TypeAlias = Literal["GasKinetics", "Interface", "Edge"] -KineticsModel: TypeAlias = Literal["gas", "surface", "edge"] -OldTransportModel: TypeAlias = Literal["Mix", "Multi", "Ion"] -TransportModel: TypeAlias = Literal["mixture-averaged", "multicomponent", "ionized-gas"] -OldConcentrationBasis: TypeAlias = Literal["molar_volume", "molar_volume", "unity"] -ConcentrationBasis: TypeAlias = Literal[ +_OldKineticsModel: TypeAlias = Literal["GasKinetics", "Interface", "Edge"] +_KineticsModel: TypeAlias = Literal["gas", "surface", "edge"] +_OldTransportModel: TypeAlias = Literal["Mix", "Multi", "Ion"] +_TransportModel: TypeAlias = Literal["mixture-averaged", "multicomponent", "ionized-gas"] +_OldConcentrationBasis: TypeAlias = Literal["molar_volume", "molar_volume", "unity"] +_ConcentrationBasis: TypeAlias = Literal[ "species-molar-volume", "solvent-molar-volume", "unity" ] @@ -69,11 +69,11 @@ class element: @classmethod def to_yaml(cls, representer: SafeRepresenter, node: element) -> CommentedMap: ... -class RkPure(TypedDict): +class _RkPure(TypedDict): a: tuple[float, str] | float b: float -class RkBinary(TypedDict): +class _RkBinary(TypedDict): a: float b: float @@ -84,8 +84,8 @@ class species: comment: str transport: gas_transport | None standard_state: constantIncompressible | None - rk_pure: RkPure - rk_binary: RkBinary + rk_pure: _RkPure + rk_binary: _RkBinary density: float | str | None def __init__( self, @@ -198,19 +198,19 @@ class gas_transport: cls, representer: SafeRepresenter, node: gas_transport ) -> CommentedMap: ... -CoverageParameters: TypeAlias = Sequence[str | float] +_CoverageParameters: TypeAlias = Sequence[str | float] class Arrhenius: A: float b: float E: float - coverage: list[CoverageParameters] | None + coverage: list[_CoverageParameters] | None def __init__( self, A: float = 0.0, b: float = 0.0, E: float = 0.0, - coverage: CoverageParameters | list[CoverageParameters] = (), + coverage: _CoverageParameters | list[_CoverageParameters] = (), ) -> None: ... @classmethod def to_yaml(cls, representer: SafeRepresenter, node: Arrhenius) -> CommentedMap: ... @@ -219,7 +219,7 @@ class stick(Arrhenius): motz_wise: bool | None def __init__(self, *args: Any, **kwargs: Any) -> None: ... -ReactionOptions: TypeAlias = Literal[ +_ReactionOptions: TypeAlias = Literal[ "duplicate", "negative_A", "negative_orders", "nonreactant_orders" ] @@ -228,7 +228,7 @@ class reaction: order: dict[str, float] | OrderedDict[str, float] number: int id: str - options: Sequence[ReactionOptions] + options: Sequence[_ReactionOptions] kf: Arrhenius type: str = "elementary" def __init__( @@ -237,7 +237,7 @@ class reaction: kf: Sequence[float] | Arrhenius, id: str = "", order: str = "", - options: ReactionOptions | Sequence[ReactionOptions] = (), + options: _ReactionOptions | Sequence[_ReactionOptions] = (), ) -> None: ... @classmethod def to_yaml(cls, representer: SafeRepresenter, node: reaction) -> CommentedMap: ... @@ -252,16 +252,16 @@ class three_body_reaction(reaction): kf: Sequence[float] | Arrhenius, efficiencies: str = "", id: str = "", - options: ReactionOptions | Sequence[ReactionOptions] = (), + options: _ReactionOptions | Sequence[_ReactionOptions] = (), ) -> None: ... def get_yaml(self, out: CommentedMap) -> None: ... -FalloffFunction: TypeAlias = Troe | SRI | Lindemann +_FalloffFunction: TypeAlias = Troe | SRI | Lindemann class falloff_base(reaction): k_low: Arrhenius k_high: Arrhenius - falloff: FalloffFunction | None + falloff: _FalloffFunction | None efficiencies: dict[str, float] | OrderedDict[str, float] def __init__( self, @@ -269,9 +269,9 @@ class falloff_base(reaction): klow: Sequence[float] | Arrhenius, khigh: Sequence[float] | Arrhenius, efficiencies: str, - falloff: FalloffFunction | None, + falloff: _FalloffFunction | None, id: str, - options: ReactionOptions | Sequence[ReactionOptions], + options: _ReactionOptions | Sequence[_ReactionOptions], ) -> None: ... def get_yaml(self, out: CommentedMap) -> None: ... @@ -283,9 +283,9 @@ class falloff_reaction(falloff_base): kf0: Sequence[float] | Arrhenius, kf: Sequence[float] | Arrhenius, efficiencies: str = "", - falloff: FalloffFunction | None = None, + falloff: _FalloffFunction | None = None, id: str = "", - options: ReactionOptions | Sequence[ReactionOptions] = (), + options: _ReactionOptions | Sequence[_ReactionOptions] = (), ) -> None: ... class chemically_activated_reaction(falloff_base): @@ -296,9 +296,9 @@ class chemically_activated_reaction(falloff_base): kLow: Sequence[float] | Arrhenius, kHigh: Sequence[float] | Arrhenius, efficiencies: str = "", - falloff: FalloffFunction | None = None, + falloff: _FalloffFunction | None = None, id: str = "", - options: ReactionOptions | Sequence[ReactionOptions] = (), + options: _ReactionOptions | Sequence[_ReactionOptions] = (), ) -> None: ... class pdep_arrhenius(reaction): @@ -340,7 +340,7 @@ class surface_reaction(reaction): id: str = "", order: str = "", beta: float | None = None, - options: ReactionOptions | Sequence[ReactionOptions] = (), + options: _ReactionOptions | Sequence[_ReactionOptions] = (), rate_coeff_type: Literal["", "exchangecurrentdensity"] = "", ) -> None: ... def get_yaml(self, out: CommentedMap) -> None: ... @@ -354,7 +354,7 @@ class edge_reaction(surface_reaction): id: str = "", order: str = "", beta: float | None = None, - options: ReactionOptions | Sequence[ReactionOptions] = (), + options: _ReactionOptions | Sequence[_ReactionOptions] = (), rate_coeff_type: Literal["", "exchangecurrentdensity"] = "", ) -> None: ... @@ -379,7 +379,7 @@ class state: @classmethod def to_yaml(cls, representer: SafeRepresenter, node: state) -> CommentedMap: ... -PhaseOptions: TypeAlias = Literal[ +_PhaseOptions: TypeAlias = Literal[ "skip_undeclared_elements", "skip_undeclared_third_bodies" ] @@ -389,10 +389,10 @@ class phase: species: list[tuple[str, CommentedSeq]] reactions: list[list[str]] thermo_model: str | None = None - kinetics: KineticsModel | None = None - transport: TransportModel | None = None + kinetics: _KineticsModel | None = None + transport: _TransportModel | None = None comment: str - options: Sequence[PhaseOptions] + options: Sequence[_PhaseOptions] initial_state: state | None def __init__( self, @@ -402,15 +402,15 @@ class phase: note: str = "", reactions: str | Sequence[str] = "none", initial_state: state | None = None, - options: PhaseOptions | Sequence[PhaseOptions] = (), + options: _PhaseOptions | Sequence[_PhaseOptions] = (), ) -> None: ... @classmethod def to_yaml(cls, representer: SafeRepresenter, node: phase) -> CommentedMap: ... def get_yaml(self, out: CommentedMap) -> None: ... class ideal_gas(phase): - kinetics: KineticsModel | None - transport: TransportModel | None + kinetics: _KineticsModel | None + transport: _TransportModel | None thermo_model: Literal["ideal-gas"] def __init__( self, @@ -419,16 +419,16 @@ class ideal_gas(phase): species: str | Sequence[str] = "", note: str = "", reactions: str | Sequence[str] = "none", - kinetics: OldKineticsModel | Literal["None"] = "GasKinetics", - transport: OldTransportModel | Literal["None"] | None = None, + kinetics: _OldKineticsModel | Literal["None"] = "GasKinetics", + transport: _OldTransportModel | Literal["None"] | None = None, initial_state: state | None = None, - options: PhaseOptions | Sequence[PhaseOptions] = (), + options: _PhaseOptions | Sequence[_PhaseOptions] = (), ) -> None: ... class stoichiometric_solid(phase): thermo_model: Literal["fixed-stoichiometry"] density: float - transport: TransportModel | None + transport: _TransportModel | None def __init__( self, name: str = "", @@ -436,9 +436,9 @@ class stoichiometric_solid(phase): species: str | Sequence[str] = "", note: str = "", density: float | None = None, - transport: OldTransportModel | Literal["None"] = "None", + transport: _OldTransportModel | Literal["None"] = "None", initial_state: state | None = None, - options: PhaseOptions | Sequence[PhaseOptions] = (), + options: _PhaseOptions | Sequence[_PhaseOptions] = (), ) -> None: ... def get_yaml(self, out: CommentedMap) -> None: ... @@ -454,9 +454,9 @@ class metal(phase): species: str | Sequence[str] = "", note: str = "", density: float = -1.0, - transport: OldTransportModel | Literal["None"] = "None", + transport: _OldTransportModel | Literal["None"] = "None", initial_state: state | None = None, - options: PhaseOptions | Sequence[PhaseOptions] = (), + options: _PhaseOptions | Sequence[_PhaseOptions] = (), ) -> None: ... def get_yaml(self, out: CommentedMap) -> None: ... @@ -472,7 +472,7 @@ class liquid_vapor(phase): note: str = "", substance_flag: int = 0, initial_state: state | None = None, - options: PhaseOptions | Sequence[PhaseOptions] = (), + options: _PhaseOptions | Sequence[_PhaseOptions] = (), ) -> None: ... def get_yaml(self, out: CommentedMap) -> None: ... @@ -501,8 +501,8 @@ class crossFluidParameters: class RedlichKwongMFTP(phase): thermo_model: Literal["Redlich-Kwong"] - kinetics: KineticsModel | None - transport: TransportModel | None + kinetics: _KineticsModel | None + transport: _TransportModel | None activity_coefficients: Sequence[pureFluidParameters | crossFluidParameters] def __init__( self, @@ -511,12 +511,12 @@ class RedlichKwongMFTP(phase): species: str | Sequence[str] = "", note: str = "", reactions: str | Sequence[str] = "none", - kinetics: OldKineticsModel = "GasKinetics", + kinetics: _OldKineticsModel = "GasKinetics", initial_state: state | None = None, activity_coefficients: Sequence[pureFluidParameters | crossFluidParameters] | None = None, - transport: OldTransportModel | Literal["None"] = "None", - options: PhaseOptions | Sequence[PhaseOptions] = (), + transport: _OldTransportModel | Literal["None"] = "None", + options: _PhaseOptions | Sequence[_PhaseOptions] = (), ) -> None: ... def get_yaml(self, out: CommentedMap) -> None: ... @@ -528,18 +528,18 @@ class IdealSolidSolution(phase): thermo_model: Literal["ideal-condensed", "binary-solution-tabulated"] = ( "ideal-condensed" ) - standard_concentration: ConcentrationBasis - transport: TransportModel | None + standard_concentration: _ConcentrationBasis + transport: _TransportModel | None def __init__( self, name: str = "", elements: str = "", species: str | Sequence[str] = "", note: str = "", - transport: OldTransportModel | Literal["None"] = "None", + transport: _OldTransportModel | Literal["None"] = "None", initial_state: state | None = None, - standard_concentration: OldConcentrationBasis | None = None, - options: PhaseOptions | Sequence[PhaseOptions] = (), + standard_concentration: _OldConcentrationBasis | None = None, + options: _PhaseOptions | Sequence[_PhaseOptions] = (), ) -> None: ... def get_yaml(self, out: CommentedMap) -> None: ... @@ -564,12 +564,12 @@ class BinarySolutionTabulatedThermo(IdealSolidSolution): elements: str = "", species: str | Sequence[str] = "", note: str = "", - transport: OldTransportModel | Literal["None"] = "None", + transport: _OldTransportModel | Literal["None"] = "None", initial_state: state | None = None, - standard_concentration: OldConcentrationBasis | None = None, + standard_concentration: _OldConcentrationBasis | None = None, tabulated_species: str | None = None, tabulated_thermo: table | None = None, - options: PhaseOptions | Sequence[PhaseOptions] = (), + options: _PhaseOptions | Sequence[_PhaseOptions] = (), ) -> None: ... def get_yaml(self, out: CommentedMap) -> None: ... @@ -583,17 +583,17 @@ class lattice(phase): species: str | Sequence[str] = "", note: str = "", reactions: str | Sequence[str] = "none", - transport: OldTransportModel | Literal["None"] = "None", + transport: _OldTransportModel | Literal["None"] = "None", initial_state: state | None = None, - options: PhaseOptions | Sequence[PhaseOptions] = (), + options: _PhaseOptions | Sequence[_PhaseOptions] = (), site_density: float | None = None, ) -> None: ... def get_yaml(self, out: CommentedMap) -> None: ... class ideal_interface(phase): thermo_model: Literal["ideal-surface", "edge"] = "ideal-surface" - kinetics: KineticsModel - transport: TransportModel + kinetics: _KineticsModel + transport: _TransportModel site_density: float adjacent_phases: list[str] def __init__( @@ -606,10 +606,10 @@ class ideal_interface(phase): site_density: float = 0.0, phases: str | Sequence[str] = (), # Note: Does not actually accept Sequence input - kinetics: OldKineticsModel | Literal["None"] = "Interface", - transport: OldTransportModel | Literal["None"] = "None", + kinetics: _OldKineticsModel | Literal["None"] = "Interface", + transport: _OldTransportModel | Literal["None"] = "None", initial_state: state | None = None, - options: PhaseOptions | Sequence[PhaseOptions] = (), + options: _PhaseOptions | Sequence[_PhaseOptions] = (), ) -> None: ... def get_yaml(self, out: CommentedMap) -> None: ... @@ -625,10 +625,10 @@ class edge(ideal_interface): site_density: float = 0.0, phases: str | Sequence[str] = (), # Note: Does not actually accept Sequence input - kinetics: OldKineticsModel | Literal["None"] = "Edge", - transport: OldTransportModel | Literal["None"] = "None", + kinetics: _OldKineticsModel | Literal["None"] = "Edge", + transport: _OldTransportModel | Literal["None"] = "None", initial_state: state | None = None, - options: PhaseOptions | Sequence[PhaseOptions] = (), + options: _PhaseOptions | Sequence[_PhaseOptions] = (), ) -> None: ... class Troe: @@ -666,13 +666,13 @@ class Lindemann: # Note: Many more encodings available, but you probably don't want most of them. # See: https://docs.python.org/3/library/codecs.html#standard-encodings -Encoding: TypeAlias = Literal["utf-8", "latin-1", "ascii"] +_Encoding: TypeAlias = Literal["utf-8", "latin-1", "ascii"] def convert( filename: Path | str | None = None, output_name: str | None = None, text: str | None = None, - encoding: Encoding = "latin-1", + encoding: _Encoding = "latin-1", ) -> tuple[int, int, list[ideal_interface], Path]: ... def create_argparser() -> ArgumentParser: ... def main() -> None: ... diff --git a/interfaces/cython/cantera/ctml2yaml.py b/interfaces/cython/cantera/ctml2yaml.py index d9aa3d850c2..bcf0df4b165 100644 --- a/interfaces/cython/cantera/ctml2yaml.py +++ b/interfaces/cython/cantera/ctml2yaml.py @@ -53,24 +53,24 @@ "please install an updated version using pip or conda." ) -QuantityType: TypeAlias = float | str +_QuantityType: TypeAlias = float | str -RedlichKwongInput = TypedDict( - "RedlichKwongInput", +_RedlichKwongInput = TypedDict( + "_RedlichKwongInput", { - "a": list[QuantityType], - "b": QuantityType, - "binary-a": dict[str, list[QuantityType]], + "a": list[_QuantityType], + "b": _QuantityType, + "binary-a": dict[str, list[_QuantityType]], }, total=False, ) -class DebyeHuckelBetaMatrix(TypedDict, total=False): +class _DebyeHuckelBetaMatrix(TypedDict, total=False): species: list[str] - beta: QuantityType + beta: _QuantityType -ReactionInput = TypedDict( - "ReactionInput", +_ReactionInput = TypedDict( + "_ReactionInput", { "type": Required[ Literal[ @@ -88,60 +88,60 @@ class DebyeHuckelBetaMatrix(TypedDict, total=False): }, total=False, ) -ArrheniusParams: TypeAlias = dict[str, QuantityType] -EfficiencyParams: TypeAlias = dict[str, float] -LindemannParams: TypeAlias = str | ArrheniusParams | EfficiencyParams -TroeParams: TypeAlias = dict[str, float] -SriParams: TypeAlias = dict[str, float] -CoverageParams: TypeAlias = dict[str, ArrheniusParams] - -ArrheniusType: TypeAlias = dict[str, ArrheniusParams] -InterfaceType: TypeAlias = dict[ - str, ArrheniusParams | bool | str | CoverageParams | float +_ArrheniusParams: TypeAlias = dict[str, _QuantityType] +_EfficiencyParams: TypeAlias = dict[str, float] +_LindemannParams: TypeAlias = str | _ArrheniusParams | _EfficiencyParams +_TroeParams: TypeAlias = dict[str, float] +_SriParams: TypeAlias = dict[str, float] +_CoverageParams: TypeAlias = dict[str, _ArrheniusParams] + +_ArrheniusType: TypeAlias = dict[str, _ArrheniusParams] +_InterfaceType: TypeAlias = dict[ + str, _ArrheniusParams | bool | str | _CoverageParams | float ] -ChebyshevType = TypedDict( - "ChebyshevType", +_ChebyshevType = TypedDict( + "_ChebyshevType", { "type": str, - "temperature-range": list[QuantityType], - "pressure-range": list[QuantityType], + "temperature-range": list[_QuantityType], + "pressure-range": list[_QuantityType], "data": list[list[float]], }, ) -PlogType: TypeAlias = dict[str, str | list[ArrheniusParams]] -ChemActType: TypeAlias = dict[ - str, str | ArrheniusParams | EfficiencyParams | TroeParams +_PlogType: TypeAlias = dict[str, str | list[_ArrheniusParams]] +_ChemActType: TypeAlias = dict[ + str, str | _ArrheniusParams | _EfficiencyParams | _TroeParams ] -LindemannType: TypeAlias = dict[str, LindemannParams] -TroeType: TypeAlias = dict[str, LindemannParams | TroeParams] -ThreebodyType: TypeAlias = dict[str, ArrheniusParams | EfficiencyParams] -SriType: TypeAlias = dict[str, LindemannParams | SriParams] - -ThermoPolyType: TypeAlias = list[list[float]] | list[float] -SpeciesThermoInput = TypedDict( - "SpeciesThermoInput", +_LindemannType: TypeAlias = dict[str, _LindemannParams] +_TroeType: TypeAlias = dict[str, _LindemannParams | _TroeParams] +_ThreebodyType: TypeAlias = dict[str, _ArrheniusParams | _EfficiencyParams] +_SriType: TypeAlias = dict[str, _LindemannParams | _SriParams] + +_ThermoPolyType: TypeAlias = list[list[float]] | list[float] +_SpeciesThermoInput = TypedDict( + "_SpeciesThermoInput", { "model": Required[str], "temperature-ranges": list[float], - "data": ThermoPolyType, + "data": _ThermoPolyType, }, total=False, ) -ConstCpThermoInput = TypedDict( - "ConstCpThermoInput", +_ConstCpThermoInput = TypedDict( + "_ConstCpThermoInput", { "model": Required[str], - "T0": QuantityType, - "h0": QuantityType, - "s0": QuantityType, - "cp0": QuantityType, + "T0": _QuantityType, + "h0": _QuantityType, + "s0": _QuantityType, + "cp0": _QuantityType, "T-min": float, "T-max": float, }, total=False, ) -Mu0ThermoInput = TypedDict( - "Mu0ThermoInput", +_Mu0ThermoInput = TypedDict( + "_Mu0ThermoInput", { "model": Required[str], "reference-pressure": float, @@ -154,10 +154,10 @@ class DebyeHuckelBetaMatrix(TypedDict, total=False): }, total=False, ) -HkftThermoType: TypeAlias = QuantityType | list[QuantityType] +_HkftThermoType: TypeAlias = _QuantityType | list[_QuantityType] # The last str | float here is not a QUANTITY -HmwThermoType: TypeAlias = ( - QuantityType | bool | dict[str, float | list[str | float]] +_HmwThermoType: TypeAlias = ( + _QuantityType | bool | dict[str, float | list[str | float]] ) @@ -280,7 +280,7 @@ def represent_float(self: SafeRepresenter, data: float) -> ScalarNode: RoundTripRepresenter.add_representer(float, represent_float) -def get_float_or_quantity(node: etree.Element) -> QuantityType: +def get_float_or_quantity(node: etree.Element) -> _QuantityType: """Process an XML node into a float value or a value with units. :param node: @@ -719,7 +719,7 @@ def __init__( def ideal_molal_solution( self, activity_coeffs: etree.Element - ) -> dict[str, QuantityType]: + ) -> dict[str, _QuantityType]: """Process the cutoff data in an ``IdealMolalSolution`` phase-thermo type. :param activity_coeffs: @@ -731,7 +731,7 @@ def ideal_molal_solution( dictionary will be empty when there are no cutoff nodes in the ``activityCoefficients`` node. """ - cutoff: dict[str, QuantityType] = {} + cutoff: dict[str, _QuantityType] = {} cutoff_node = activity_coeffs.find("idealMolalSolnCutoff") if cutoff_node is not None: cutoff_model = cutoff_node.get("model") @@ -745,7 +745,7 @@ def ideal_molal_solution( def margules( self, activity_coeffs: etree.Element - ) -> list[dict[str, list[QuantityType]]]: + ) -> list[dict[str, list[_QuantityType]]]: """Process activity coefficients for a ``Margules`` phase-thermo type. :param activity_coeffs: @@ -759,7 +759,7 @@ def margules( because this function would have to go through the same nodes again. """ all_binary_params = activity_coeffs.findall("binaryNeutralSpeciesParameters") - interactions: list[dict[str, list[QuantityType]]] = [] + interactions: list[dict[str, list[_QuantityType]]] = [] for binary_params in all_binary_params: species_A = binary_params.get("speciesA") species_B = binary_params.get("speciesB") @@ -769,7 +769,7 @@ def margules( "'speciesB' attributes", binary_params, ) - this_node: dict[str, list[QuantityType]] = { + this_node: dict[str, list[_QuantityType]] = { "species": FlowList([species_A, species_B]) } excess_enthalpy_node = binary_params.find("excessEnthalpy") @@ -830,7 +830,7 @@ def margules( def redlich_kister( self, activity_coeffs: etree.Element - ) -> list[dict[str, list[QuantityType]]]: + ) -> list[dict[str, list[_QuantityType]]]: """Process activity coefficients for a Redlich-Kister phase-thermo type. :param activity_coeffs: @@ -847,7 +847,7 @@ def redlich_kister( "'binaryNeutralSpeciesParameters' node", activity_coeffs, ) - interactions: list[dict[str, list[QuantityType]]] = [] + interactions: list[dict[str, list[_QuantityType]]] = [] for binary_params in all_binary_params: species_A = binary_params.get("speciesA") species_B = binary_params.get("speciesB") @@ -857,7 +857,7 @@ def redlich_kister( "'speciesB' attributes", binary_params, ) - this_node: dict[str, list[QuantityType]] = { + this_node: dict[str, list[_QuantityType]] = { "species": FlowList([species_A, species_B]) } excess_enthalpy_node = binary_params.find("excessEnthalpy") @@ -947,10 +947,10 @@ def move_RK_coeffs_to_species( parameters from the `Phase` node into the `Species` nodes. This modifies the `Species` objects in-place in the ``species_data`` list. """ - all_species_eos: dict[str, RedlichKwongInput] = {} + all_species_eos: dict[str, _RedlichKwongInput] = {} for pure_param in activity_coeffs.iterfind("pureFluidParameters"): - eq_of_state: RedlichKwongInput = cast( - RedlichKwongInput, BlockMap({"model": "Redlich-Kwong"}) + eq_of_state: _RedlichKwongInput = cast( + _RedlichKwongInput, BlockMap({"model": "Redlich-Kwong"}) ) pure_species = pure_param.get("species") if pure_species is None: @@ -1318,7 +1318,7 @@ def get_tabulated_thermo(self, tab_thermo_node: etree.Element) -> dict[str, str] return tab_thermo - def hmw_electrolyte(self, activity_node: etree.Element) -> dict[str, HmwThermoType]: + def hmw_electrolyte(self, activity_node: etree.Element) -> dict[str, _HmwThermoType]: """Process the activity coefficients for an ``HMW`` phase-thermo type. :param activity_coeffs: @@ -1380,7 +1380,7 @@ def debye_huckel( this_phase_species: list[dict[str, Iterable[str]]], activity_node: etree.Element, species_data: dict[str, list["Species"]], - ) -> dict[str, QuantityType | bool]: + ) -> dict[str, _QuantityType | bool]: """Process the activity coefficients for the ``DebyeHuckel`` phase-thermo type. :param this_phase_species: @@ -1433,7 +1433,7 @@ def debye_huckel( activity_data["B-dot"] = get_float_or_quantity(B_dot_node) ionic_radius_node = activity_node.find("ionicRadius") - species_ionic_radii: dict[str, QuantityType] = {} + species_ionic_radii: dict[str, _QuantityType] = {} if ionic_radius_node is not None: default_radius = ionic_radius_node.get("default") radius_units = ionic_radius_node.get("units") @@ -1464,7 +1464,7 @@ def debye_huckel( # names in this matrix do not contain colons, so we retain that # behavior here. species_1, species_2, beta_value = beta_text.split(":") - beta_dict: DebyeHuckelBetaMatrix = { + beta_dict: _DebyeHuckelBetaMatrix = { "species": FlowList([species_1, species_2]) } if beta_units is not None: @@ -1567,7 +1567,7 @@ def __init__(self, thermo: etree.Element) -> None: if thermo_type not in ["NASA", "NASA9", "const_cp", "Shomate", "Mu0"]: raise TypeError("Unknown thermo model type: '{}'".format(thermo[0].tag)) func = getattr(self, thermo_type) - self.attribs: SpeciesThermoInput = func(thermo) + self.attribs: _SpeciesThermoInput = func(thermo) def process_polynomial( self, thermo: etree.Element, poly_type: str @@ -1616,53 +1616,53 @@ def process_polynomial( return data, FlowList(sorted(temperature_ranges)) - def Shomate(self, thermo: etree.Element) -> SpeciesThermoInput: + def Shomate(self, thermo: etree.Element) -> _SpeciesThermoInput: """Process a Shomate `Species` thermodynamic polynomial. :param thermo: A ``species/thermo`` XML node. There must be one or more child nodes with the tag ``Shomate``. """ - thermo_attribs = cast(SpeciesThermoInput, BlockMap({"model": "Shomate"})) + thermo_attribs = cast(_SpeciesThermoInput, BlockMap({"model": "Shomate"})) data, temperature_ranges = self.process_polynomial(thermo, "Shomate") thermo_attribs["temperature-ranges"] = temperature_ranges thermo_attribs["data"] = data return thermo_attribs - def NASA(self, thermo: etree.Element) -> SpeciesThermoInput: + def NASA(self, thermo: etree.Element) -> _SpeciesThermoInput: """Process a NASA 7-coefficient thermodynamic polynomial. :param thermo: A ``species/thermo`` XML node. There must be one or more child nodes with the tag ``NASA``. """ - thermo_attribs = cast(SpeciesThermoInput, BlockMap({"model": "NASA7"})) + thermo_attribs = cast(_SpeciesThermoInput, BlockMap({"model": "NASA7"})) data, temperature_ranges = self.process_polynomial(thermo, "NASA") thermo_attribs["temperature-ranges"] = temperature_ranges thermo_attribs["data"] = data return thermo_attribs - def NASA9(self, thermo: etree.Element) -> SpeciesThermoInput: + def NASA9(self, thermo: etree.Element) -> _SpeciesThermoInput: """Process a NASA 9-coefficient thermodynamic polynomial. :param thermo: A ``species/thermo`` XML node. There must be one or more child nodes with the tag ``NASA9``. """ - thermo_attribs = cast(SpeciesThermoInput, BlockMap({"model": "NASA9"})) + thermo_attribs = cast(_SpeciesThermoInput, BlockMap({"model": "NASA9"})) data, temperature_ranges = self.process_polynomial(thermo, "NASA9") thermo_attribs["temperature-ranges"] = temperature_ranges thermo_attribs["data"] = data return thermo_attribs - def const_cp(self, thermo: etree.Element) -> ConstCpThermoInput: + def const_cp(self, thermo: etree.Element) -> _ConstCpThermoInput: """Process a `Species` thermodynamic type with constant specific heat. :param thermo: A ``species/thermo`` XML node. There must be one child node with the tag ``const_cp``. """ - thermo_attribs = cast(ConstCpThermoInput, BlockMap({"model": "constant-cp"})) + thermo_attribs = cast(_ConstCpThermoInput, BlockMap({"model": "constant-cp"})) const_cp_node = thermo.find("const_cp") if const_cp_node is None: raise MissingXMLNode( @@ -1685,14 +1685,14 @@ def const_cp(self, thermo: etree.Element) -> ConstCpThermoInput: return thermo_attribs - def Mu0(self, thermo: etree.Element) -> Mu0ThermoInput: + def Mu0(self, thermo: etree.Element) -> _Mu0ThermoInput: """Process a piecewise Gibbs Free Energy thermodynamic polynomial. :param thermo: A ``species/thermo`` XML node. There must be one child node with the tag ``Mu0``. """ - thermo_attribs = cast(Mu0ThermoInput, BlockMap({"model": "piecewise-Gibbs"})) + thermo_attribs = cast(_Mu0ThermoInput, BlockMap({"model": "piecewise-Gibbs"})) Mu0_node = thermo.find("Mu0") if Mu0_node is None: raise MissingXMLNode("The 'thermo' node must contain a 'Mu0' node.", thermo) @@ -1915,7 +1915,7 @@ def __init__(self, species_node: etree.Element) -> None: if debye_huckel: self.attribs["Debye-Huckel"] = debye_huckel - def hkft(self, species_node: etree.Element) -> dict[str, HkftThermoType]: + def hkft(self, species_node: etree.Element) -> dict[str, _HkftThermoType]: """Process a species with HKFT thermo type. :param species_node: @@ -1986,7 +1986,7 @@ def process_standard_state_node(self, species_node: etree.Element) -> None: # species __init__ function return - eqn_of_state: dict[str, QuantityType | list[QuantityType]] = { + eqn_of_state: dict[str, _QuantityType | list[_QuantityType]] = { "model": self.standard_state_model_mapping[std_state_model] } if std_state_model == "constant_incompressible": @@ -2214,7 +2214,7 @@ def to_yaml(cls, representer: SafeRepresenter, data: Reaction) -> MappingNode: """ return representer.represent_dict(data.attribs) - def sri(self, rate_coeff: etree.Element) -> SriType: + def sri(self, rate_coeff: etree.Element) -> _SriType: """Process an SRI reaction. :param rate_coeff: @@ -2232,13 +2232,13 @@ def sri(self, rate_coeff: etree.Element) -> SriType: reaction_attribs["SRI"] = SRI_data return reaction_attribs - def threebody(self, rate_coeff: etree.Element) -> ThreebodyType: + def threebody(self, rate_coeff: etree.Element) -> _ThreebodyType: """Process a three-body reaction. :param rate_coeff: The XML node with rate coefficient information for this reaction. """ - reaction_attribs = cast(ThreebodyType, FlowMap({"type": "three-body"})) + reaction_attribs = cast(_ThreebodyType, FlowMap({"type": "three-body"})) reaction_attribs["rate-constant"] = self.process_arrhenius_parameters( rate_coeff.find("Arrhenius") ) @@ -2248,13 +2248,13 @@ def threebody(self, rate_coeff: etree.Element) -> ThreebodyType: return reaction_attribs - def lindemann(self, rate_coeff: etree.Element) -> LindemannType: + def lindemann(self, rate_coeff: etree.Element) -> _LindemannType: """Process a Lindemann falloff reaction. :param rate_coeff: The XML node with rate coefficient information for this reaction. """ - reaction_attribs = cast(LindemannType, FlowMap({"type": "falloff"})) + reaction_attribs = cast(_LindemannType, FlowMap({"type": "falloff"})) for arr_coeff in rate_coeff.iterfind("Arrhenius"): if arr_coeff.get("name") == "k0": reaction_attribs["low-P-rate-constant"] = ( @@ -2272,14 +2272,14 @@ def lindemann(self, rate_coeff: etree.Element) -> LindemannType: return reaction_attribs - def troe(self, rate_coeff: etree.Element) -> TroeType: + def troe(self, rate_coeff: etree.Element) -> _TroeType: """Process a Troe falloff reaction. :param rate_coeff: The XML node with rate coefficient information for this reaction. """ # This gets the low-p and high-p rate constants and the efficiencies - reaction_attribs: TroeType = self.lindemann(rate_coeff) + reaction_attribs: _TroeType = self.lindemann(rate_coeff) troe_node = rate_coeff.find("falloff") if troe_node is None: @@ -2288,7 +2288,7 @@ def troe(self, rate_coeff: etree.Element) -> TroeType: ) troe_params = clean_node_text(troe_node).split() troe_names = ["A", "T3", "T1", "T2"] - reaction_attribs["Troe"] = cast(TroeParams, FlowMap()) + reaction_attribs["Troe"] = cast(_TroeParams, FlowMap()) assert not isinstance(reaction_attribs["Troe"], str) # zip stops when the shortest iterable is exhausted. If T2 is not present # in the Troe parameters (that is, troe_params is three elements long), it @@ -2298,13 +2298,13 @@ def troe(self, rate_coeff: etree.Element) -> TroeType: return reaction_attribs - def chemact(self, rate_coeff: etree.Element) -> ChemActType: + def chemact(self, rate_coeff: etree.Element) -> _ChemActType: """Process a chemically activated falloff reaction. :param rate_coeff: The XML node with rate coefficient information for this reaction. """ - reaction_attribs = cast(ChemActType, FlowMap({"type": "chemically-activated"})) + reaction_attribs = cast(_ChemActType, FlowMap({"type": "chemically-activated"})) for arr_coeff in rate_coeff.iterfind("Arrhenius"): if arr_coeff.get("name") == "kHigh": reaction_attribs["high-P-rate-constant"] = ( @@ -2328,7 +2328,7 @@ def chemact(self, rate_coeff: etree.Element) -> ChemActType: ) troe_params = clean_node_text(troe_node).split() troe_names = ["A", "T3", "T1", "T2"] - reaction_attribs["Troe"] = cast(TroeParams, FlowMap()) + reaction_attribs["Troe"] = cast(_TroeParams, FlowMap()) # zip stops when the shortest iterable is exhausted. If T2 is not present # in the Troe parameters (that is, troe_params is three elements long), it # will be omitted here as well. @@ -2338,14 +2338,14 @@ def chemact(self, rate_coeff: etree.Element) -> ChemActType: return reaction_attribs - def plog(self, rate_coeff: etree.Element) -> PlogType: + def plog(self, rate_coeff: etree.Element) -> _PlogType: """Process a PLOG reaction. :param rate_coeff: The XML node with rate coefficient information for this reaction. """ reaction_attributes = cast( - PlogType, FlowMap({"type": "pressure-dependent-Arrhenius"}) + _PlogType, FlowMap({"type": "pressure-dependent-Arrhenius"}) ) rate_constants = [] for arr_coeff in rate_coeff.iterfind("Arrhenius"): @@ -2361,14 +2361,14 @@ def plog(self, rate_coeff: etree.Element) -> PlogType: return reaction_attributes - def chebyshev(self, rate_coeff: etree.Element) -> ChebyshevType: + def chebyshev(self, rate_coeff: etree.Element) -> _ChebyshevType: """Process a Chebyshev reaction. :param rate_coeff: The XML node with rate coefficient information for this reaction. """ - reaction_attributes: ChebyshevType = cast( - ChebyshevType, + reaction_attributes: _ChebyshevType = cast( + _ChebyshevType, FlowMap( { "type": "Chebyshev", @@ -2421,7 +2421,7 @@ def chebyshev(self, rate_coeff: etree.Element) -> ChebyshevType: return reaction_attributes - def interface(self, rate_coeff: etree.Element) -> InterfaceType: + def interface(self, rate_coeff: etree.Element) -> _InterfaceType: """Process an interface reaction. :param rate_coeff: @@ -2436,7 +2436,7 @@ def interface(self, rate_coeff: etree.Element) -> InterfaceType: ) if arr_node.get("type", "").lower() == "stick": reaction_attributes = cast( - InterfaceType, + _InterfaceType, FlowMap( { "sticking-coefficient": self.process_arrhenius_parameters( @@ -2455,7 +2455,7 @@ def interface(self, rate_coeff: etree.Element) -> InterfaceType: reaction_attributes["Motz-Wise"] = False else: reaction_attributes = cast( - InterfaceType, + _InterfaceType, FlowMap({"rate-constant": self.process_arrhenius_parameters(arr_node)}), ) cov_node = arr_node.find("coverage") @@ -2495,7 +2495,7 @@ def interface(self, rate_coeff: etree.Element) -> InterfaceType: return reaction_attributes - def arrhenius(self, rate_coeff: etree.Element) -> ArrheniusType: + def arrhenius(self, rate_coeff: etree.Element) -> _ArrheniusType: """Process a standard Arrhenius-type reaction. :param rate_coeff: @@ -2511,7 +2511,7 @@ def arrhenius(self, rate_coeff: etree.Element) -> ArrheniusType: def process_arrhenius_parameters( self, arr_node: etree.Element | None - ) -> ArrheniusParams: + ) -> _ArrheniusParams: """Process the parameters from an ``Arrhenius`` child of a ``rateCoeff`` node. :param arr_node: @@ -2537,7 +2537,7 @@ def process_arrhenius_parameters( } ) - def process_efficiencies(self, eff_node: etree.Element) -> EfficiencyParams: + def process_efficiencies(self, eff_node: etree.Element) -> _EfficiencyParams: """Process the efficiency information about a reaction. :param eff_node: diff --git a/interfaces/cython/cantera/drawnetwork.pyi b/interfaces/cython/cantera/drawnetwork.pyi index c1cbe752248..601ff99ba2a 100644 --- a/interfaces/cython/cantera/drawnetwork.pyi +++ b/interfaces/cython/cantera/drawnetwork.pyi @@ -1,9 +1,9 @@ # This file is part of Cantera. See License.txt in the top-level directory or # at https://cantera.org/license.txt for license and copyright information. -from collections.abc import Iterable +from collections.abc import Callable, Iterable from functools import _Wrapped -from typing import Callable, Literal +from typing import Literal from graphviz import Digraph diff --git a/interfaces/cython/cantera/func1.pyi b/interfaces/cython/cantera/func1.pyi index 528f5d0170d..9b0c5495034 100644 --- a/interfaces/cython/cantera/func1.pyi +++ b/interfaces/cython/cantera/func1.pyi @@ -1,26 +1,36 @@ # This file is part of Cantera. See License.txt in the top-level directory or # at https://cantera.org/license.txt for license and copyright information. -from collections.abc import Iterable -from typing import Any, Callable, Literal +from typing import TypeAlias +from collections.abc import Callable, Iterable +from typing import Any, Literal from typing_extensions import Never, override -from ._types import ArrayLike +_Func1Like: TypeAlias = str | Callable[[float], float] | float class Func1: callable: Callable[[float], float] def __init__( self, - c: str | Callable[[float], float] | ArrayLike, + c: _Func1Like, *args: Any, init: bool = True, ) -> None: ... @property def type(self) -> str: ... + def __add__(self, other: _Func1Like) -> Func1: ... + def __radd__(self, other: _Func1Like) -> Func1: ... + def __sub__(self, other: _Func1Like) -> Func1: ... + def __rsub__(self, other: _Func1Like) -> Func1: ... + def __mul__(self, other: _Func1Like) -> Func1: ... + def __rmul__(self, other: _Func1Like) -> Func1: ... + def __truediv__(self, other: _Func1Like) -> Func1: ... + def __rtruediv__(self, other: _Func1Like) -> Func1: ... @property def cxx_type(self) -> str: ... def write(self, name: str = "x") -> str: ... + def __call__(self, t: float) -> float: ... @override def __reduce__(self) -> Never: ... def __copy__(self) -> Never: ... diff --git a/interfaces/cython/cantera/kinetics.pyi b/interfaces/cython/cantera/kinetics.pyi index ed50f041fde..4333388edcc 100644 --- a/interfaces/cython/cantera/kinetics.pyi +++ b/interfaces/cython/cantera/kinetics.pyi @@ -8,11 +8,11 @@ from ._types import Array from .reaction import CustomRate, Reaction from .solutionbase import _SolutionBase from .thermo import ThermoPhase -from .units import UnitDict, UnitDictBytes, UnitSystem +from .units import UnitSystem, _UnitDict, _UnitDictBytes -KineticsType: TypeAlias = Literal["none", "bulk", "edge", "surface"] +_KineticsType: TypeAlias = Literal["none", "bulk", "edge", "surface"] -class DerivativeSettings(TypedDict, total=False): +class _DerivativeSettings(TypedDict, total=False): skip_third_bodies: bool skip_falloff: bool rtol_delta: float @@ -71,9 +71,9 @@ class Kinetics(_SolutionBase): @property def net_production_rates(self) -> Array: ... @property - def derivative_settings(self) -> DerivativeSettings: ... + def derivative_settings(self) -> _DerivativeSettings: ... @derivative_settings.setter - def derivative_settings(self, settings: DerivativeSettings) -> None: ... + def derivative_settings(self, settings: _DerivativeSettings) -> None: ... @property def forward_rate_constants_ddT(self) -> Array: ... @property @@ -171,7 +171,7 @@ class InterfaceKinetics(Kinetics): dt: float, rtol: float = 1e-7, atol: float = 1e-14, - max_step_size: float = 0, + max_step_size: float = 0.0, max_steps: int = 20000, max_error_test_failures: int = 7, ) -> None: ... @@ -186,7 +186,7 @@ class InterfaceKinetics(Kinetics): self, filename: str, phases: Sequence[ThermoPhase] | None = None, - units: UnitSystem | UnitDict | UnitDictBytes | None = None, + units: UnitSystem | _UnitDict | _UnitDictBytes | None = None, precision: int | None = None, skip_user_defined: bool | None = None, ) -> None: ... diff --git a/interfaces/cython/cantera/liquidvapor.py b/interfaces/cython/cantera/liquidvapor.py index b11e27b6edd..786b46a4409 100644 --- a/interfaces/cython/cantera/liquidvapor.py +++ b/interfaces/cython/cantera/liquidvapor.py @@ -42,13 +42,13 @@ def Water(backend: _Literal["Reynolds", "IAPWS95"] = "Reynolds") -> PureFluid: :ct:`WaterSSTP` and :ct:`WaterTransport` in the Cantera C++ source code documentation. """ - class WaterWithTransport(Transport, PureFluid): + class _WaterWithTransport(Transport, PureFluid): __slots__ = () if backend == "Reynolds": - return WaterWithTransport("liquidvapor.yaml", "water", transport_model="water") + return _WaterWithTransport("liquidvapor.yaml", "water", transport_model="water") if backend == "IAPWS95": - return WaterWithTransport( + return _WaterWithTransport( "liquidvapor.yaml", "liquid-water-IAPWS95", transport_model="water" ) diff --git a/interfaces/cython/cantera/mixture.pyi b/interfaces/cython/cantera/mixture.pyi index 1d7c88c7de1..529dd5fb548 100644 --- a/interfaces/cython/cantera/mixture.pyi +++ b/interfaces/cython/cantera/mixture.pyi @@ -2,7 +2,7 @@ # at https://cantera.org/license.txt for license and copyright information. from collections.abc import Sequence -from typing import Literal +from typing import Any, Literal from ._types import Array, ArrayLike, EquilibriumSolver, PropertyPair from .thermo import ThermoPhase @@ -10,12 +10,13 @@ from .thermo import ThermoPhase class Mixture: def __init__(self, phases: Sequence[tuple[ThermoPhase, float]]) -> None: ... def report(self, threshold: float = 1e-14) -> str: ... - def __call__(self) -> None: ... + def __call__(self, *args: Any, **kwargs: Any) -> None: ... @property def n_elements(self) -> int: ... def element_index(self, element: str | bytes | float) -> int: ... @property def n_species(self) -> int: ... + def species_name(self, k: int) -> str: ... @property def species_names(self) -> list[str]: ... def species_index( @@ -33,6 +34,8 @@ class Mixture: @T.setter def T(self, T: float) -> None: ... @property + def min_temp(self) -> float: ... + @property def max_temp(self) -> float: ... @property def P(self) -> float: ... @@ -53,7 +56,7 @@ class Mixture: def equilibrate( self, XY: PropertyPair, - solver: EquilibriumSolver, + solver: EquilibriumSolver = "auto", rtol: float = 1e-9, max_steps: int = 1000, max_iter: int = 100, diff --git a/interfaces/cython/cantera/onedim.pyi b/interfaces/cython/cantera/onedim.pyi index 153015905a7..c696b0a25eb 100644 --- a/interfaces/cython/cantera/onedim.pyi +++ b/interfaces/cython/cantera/onedim.pyi @@ -339,7 +339,7 @@ class FreeFlame(FlameBase): loglevel: LogLevel = 1, refine_grid: bool = True, auto: bool = False, - stage: Literal[1, 2] = 1, + stage: Literal[1, 2] | None = None, ) -> None: ... def get_flame_speed_reaction_sensitivities(self) -> Array: ... @@ -360,7 +360,7 @@ class BurnerFlame(FlameBase): loglevel: LogLevel = 1, refine_grid: bool = True, auto: bool = False, - stage: Literal[1, 2] = 1, + stage: Literal[1, 2] | None = None, ) -> None: ... class CounterflowDiffusionFlame(FlameBase): @@ -381,7 +381,7 @@ class CounterflowDiffusionFlame(FlameBase): loglevel: LogLevel = 1, refine_grid: bool = True, auto: bool = False, - stage: Literal[1, 2] = 1, + stage: Literal[1, 2] | None = None, ) -> None: ... def strain_rate( self, diff --git a/interfaces/cython/cantera/reaction.pyi b/interfaces/cython/cantera/reaction.pyi index 4758dcd09e0..1776bbe1f03 100644 --- a/interfaces/cython/cantera/reaction.pyi +++ b/interfaces/cython/cantera/reaction.pyi @@ -3,85 +3,88 @@ from collections.abc import Callable, Iterable, Sequence from typing import ( + ClassVar, Generic, TypeAlias, TypedDict, TypeVar, ) -from typing_extensions import NotRequired +from typing_extensions import NotRequired, deprecated from ._types import Array, ArrayLike +from ._utils import AnyMap from .composite import Solution from .kinetics import Kinetics -from .units import Units +from .units import Units, UnitStack -class ArrheniusParameters(TypedDict): +class _ArrheniusParameters(TypedDict): A: float b: float Ea: float -class BlowersMaselParameters(TypedDict): +class _BlowersMaselParameters(TypedDict): A: float b: float Ea0: float w: float -class TwoTempPlasmaParameters(TypedDict): +class _TwoTempPlasmaParameters(TypedDict): A: float b: float Ea_gas: float Ea_electron: float -class ElectronCollisionPlasmaParameters(TypedDict): +class _ElectronCollisionPlasmaParameters(TypedDict): energy_levels: list[float] cross_sections: list[float] -class PlogParameters(ArrheniusParameters): +class _PlogParameters(_ArrheniusParameters): P: float -ReactionRateParameters: TypeAlias = ( - ArrheniusParameters - | BlowersMaselParameters - | TwoTempPlasmaParameters - | ElectronCollisionPlasmaParameters - | PlogParameters +_ReactionRateParameters: TypeAlias = ( + _ArrheniusParameters + | _BlowersMaselParameters + | _TwoTempPlasmaParameters + | _ElectronCollisionPlasmaParameters + | _PlogParameters ) -class TroeParameters(TypedDict): +class _TroeParameters(TypedDict): A: float T3: float T1: float T2: NotRequired[float] -class CoverageParameters(TypedDict): +class _CoverageParameters(TypedDict): a: float m: float E: float -T = TypeVar("T") +_T = TypeVar("_T") -class ReactionRateInput(TypedDict, Generic[T], total=False): +class _ReactionRateInput(TypedDict, Generic[_T], total=False): type: str - rate_constant: T + rate_constant: _T efficiencies: dict[str, float] - Troe: TroeParameters - coverage_dependencies: dict[str, CoverageParameters] + Troe: _TroeParameters + coverage_dependencies: dict[str, _CoverageParameters] -class ReactionInput(ReactionRateInput[T]): +class _ReactionInput(_ReactionRateInput[_T]): equation: str -class FalloffRateInput(TypedDict, total=False): +class _FalloffRateInput(TypedDict, total=False): type: str - low_P_rate_constant: ArrheniusParameters - high_P_rate_constant: ArrheniusParameters - Troe: TroeParameters + low_P_rate_constant: _ArrheniusParameters + high_P_rate_constant: _ArrheniusParameters + Troe: _TroeParameters -class PlogRateInput(TypedDict, total=False): +class _PlogRateInput(TypedDict, total=False): type: str - rate_constants: Sequence[PlogParameters] + rate_constants: Sequence[_PlogParameters] class ReactionRate: + _reaction_rate_type: ClassVar[str] def __call__(self, temperature: float) -> float: ... @property def type(self) -> str: ... @@ -89,12 +92,12 @@ class ReactionRate: def sub_type(self) -> str: ... @classmethod def from_dict( - cls, data: ReactionRateInput[ReactionRateParameters], hyphenize: bool = True + cls, data: _ReactionRateInput[_ReactionRateParameters], hyphenize: bool = True ) -> ReactionRate: ... @classmethod - def from_yaml(cls, data: str, hyphenize: bool = True) -> ReactionRate: ... + def from_yaml(cls, text: str) -> ReactionRate: ... @property - def input_data(self) -> ReactionRateInput[ReactionRateParameters]: ... + def input_data(self) -> _ReactionRateInput[_ReactionRateParameters]: ... @property def conversion_units(self) -> Units: ... @@ -116,7 +119,7 @@ class ArrheniusRate(ArrheniusRateBase): A: float | None = None, b: float | None = None, Ea: float | None = None, - input_data: ReactionRateInput[ArrheniusParameters] | None = None, + input_data: _ReactionRateInput[_ArrheniusParameters] | None = None, init: bool = True, ) -> None: ... def _from_parameters(self, A: float, b: float, Ea: float) -> None: ... @@ -128,7 +131,7 @@ class BlowersMaselRate(ArrheniusRateBase): b: float | None = None, Ea0: float | None = None, w: float | None = None, - input_data: ReactionRateInput[BlowersMaselParameters] | None = None, + input_data: _ReactionRateInput[_BlowersMaselParameters] | None = None, init: bool = True, ) -> None: ... def _from_parameters(self, A: float, b: float, Ea0: float, w: float) -> None: ... @@ -146,7 +149,7 @@ class TwoTempPlasmaRate(ArrheniusRateBase): b: float | None = None, Ea_gas: float = 0.0, Ea_electron: float = 0.0, - input_data: ReactionRateInput[TwoTempPlasmaParameters] | None = None, + input_data: _ReactionRateInput[_TwoTempPlasmaParameters] | None = None, init: bool = True, ) -> None: ... def _from_parameters( @@ -160,7 +163,8 @@ class ElectronCollisionPlasmaRate(ReactionRate): self, energy_levels: ArrayLike | None = None, cross_sections: ArrayLike | None = None, - input_data: ReactionRateInput[ElectronCollisionPlasmaParameters] | None = None, + input_data: _ReactionRateInput[_ElectronCollisionPlasmaParameters] + | None = None, init: bool = True, ) -> None: ... def _from_parameters( @@ -174,21 +178,21 @@ class ElectronCollisionPlasmaRate(ReactionRate): class FalloffRate(ReactionRate): def __init__( self, - low: ArrheniusParameters | None = None, - high: ArrheniusParameters | None = None, + low: _ArrheniusParameters | None = None, + high: _ArrheniusParameters | None = None, falloff_coeffs: Sequence[float] | None = None, - input_data: ReactionRateInput[FalloffRateInput] | None = None, + input_data: _ReactionRateInput[_FalloffRateInput] | None = None, init: bool = True, ) -> None: ... def __call__(self, temperature: float, concm: float) -> float: ... # type: ignore[override] @property - def low_rate(self) -> Arrhenius: ... + def low_rate(self) -> ArrheniusRate: ... @low_rate.setter - def low_rate(self, rate: Arrhenius) -> None: ... + def low_rate(self, rate: ArrheniusRate) -> None: ... @property - def high_rate(self) -> Arrhenius: ... + def high_rate(self) -> ArrheniusRate: ... @high_rate.setter - def high_rate(self, rate: Arrhenius) -> None: ... + def high_rate(self, rate: ArrheniusRate) -> None: ... @property def falloff_coeffs(self) -> Array: ... @falloff_coeffs.setter @@ -212,7 +216,7 @@ class PlogRate(ReactionRate): def __init__( self, rates: list[tuple[float, ArrheniusRate]] | None = None, - input_data: PlogRateInput | None = None, + input_data: _PlogRateInput | None = None, init: bool = True, ) -> None: ... def __call__(self, temperature: float, pressure: float) -> float: ... # type: ignore[override] @@ -235,18 +239,26 @@ class ChebyshevRate(ReactionRate): @property def data(self) -> Array: ... -class CustomRate(ReactionRate): ... -class ExtensibleRate(ReactionRate): ... +class CustomRate(ReactionRate): + def set_rate_function(self, k: int) -> None: ... + +class ExtensibleRate(ReactionRate): + delegatable_methods: dict[str, tuple[str, str, str]] + def set_parameters(self, params: AnyMap, rate_coeff_units: UnitStack) -> None: ... + def get_parameters(self, params: AnyMap) -> None: ... + def eval(self, data: ExtensibleRateData) -> float: ... + def validate(self, equation: str, soln: Solution) -> None: ... class ExtensibleRateData: + delegatable_methods: dict[str, tuple[str, str, str]] def update(self, soln: Solution) -> bool: ... class InterfaceRateBase(ArrheniusRateBase): def __call__(self, temperature: float, coverages: Array) -> float: ... # type: ignore[override] @property - def coverage_dependencies(self) -> dict[str, CoverageParameters]: ... + def coverage_dependencies(self) -> dict[str, _CoverageParameters]: ... @coverage_dependencies.setter - def coverage_dependencies(self, deps: dict[str, CoverageParameters]) -> None: ... + def coverage_dependencies(self, deps: dict[str, _CoverageParameters]) -> None: ... def set_species(self, species: Iterable[str]) -> None: ... @property def site_density(self) -> float: ... @@ -263,7 +275,7 @@ class InterfaceArrheniusRate(InterfaceRateBase): A: float | None = None, b: float | None = None, Ea: float | None = None, - input_data: ReactionRateInput[ArrheniusParameters] | None = None, + input_data: _ReactionRateInput[_ArrheniusParameters] | None = None, init: bool = True, ) -> None: ... def _from_parameters(self, A: float, b: float, Ea: float) -> None: ... @@ -275,11 +287,11 @@ class InterfaceBlowersMaselRate(InterfaceRateBase): b: float | None = None, Ea0: float | None = None, w: float | None = None, - input_data: ReactionRateInput[BlowersMaselParameters] | None = None, + input_data: _ReactionRateInput[_BlowersMaselParameters] | None = None, init: bool = True, ) -> None: ... def _from_dict( - self, input_data: ReactionRateInput[BlowersMaselParameters] + self, input_data: _ReactionRateInput[_BlowersMaselParameters] ) -> None: ... def _from_parameters(self, A: float, b: float, Ea0: float, w: float) -> None: ... @property @@ -289,7 +301,24 @@ class InterfaceBlowersMaselRate(InterfaceRateBase): @delta_enthalpy.setter def delta_enthalpy(self, delta_H: float) -> None: ... -class StickRateBase(InterfaceRateBase): ... +class StickRateBase(InterfaceRateBase): + @property + def motz_wise_correction(self) -> bool: ... + @motz_wise_correction.setter + def motz_wise_correction(self, motz_wise: bool) -> None: ... + @property + def sticking_species(self) -> str: ... + @sticking_species.setter + def sticking_species(self, species: str) -> None: ... + @property + def sticking_order(self) -> float: ... + @sticking_order.setter + def sticking_order(self, order: float) -> None: ... + @property + def sticking_weight(self) -> float: ... + @sticking_weight.setter + def sticking_weight(self, weight: float) -> None: ... + class StickingArrheniusRate(StickRateBase): ... class StickingBlowersMaselRate(StickRateBase): @@ -299,9 +328,19 @@ class StickingBlowersMaselRate(StickRateBase): b: float | None = None, Ea0: float | None = None, w: float | None = None, - input_data: ReactionRateInput[BlowersMaselParameters] | None = None, + input_data: _ReactionRateInput[_BlowersMaselParameters] | None = None, init: bool = True, ) -> None: ... + def _from_dict( + self, input_data: _ReactionRateInput[_BlowersMaselParameters] + ) -> None: ... + def _from_parameters(self, A: float, b: float, Ea0: float, w: float) -> None: ... + @property + def bond_energy(self) -> float: ... + @property + def delta_enthalpy(self) -> float: ... + @delta_enthalpy.setter + def delta_enthalpy(self, delta_H: float) -> None: ... class ThirdBody: def __init__( @@ -332,8 +371,8 @@ class Reaction: reactants: dict[str, float] | None = None, products: dict[str, float] | None = None, rate: ReactionRate - | ReactionRateInput[ReactionRateParameters] - | ArrheniusParameters + | _ReactionRateInput[_ReactionRateParameters] + | _ArrheniusParameters | Callable[[float], float] | None = None, *, @@ -344,12 +383,12 @@ class Reaction: @classmethod def from_dict( cls, - data: ReactionRateInput[ReactionRateParameters], + data: _ReactionRateInput[_ReactionRateParameters], kinetics: Kinetics, hyphenize: bool = True, ) -> Reaction: ... @classmethod - def from_yaml(cls, data: str, kinetics: Kinetics) -> Reaction: ... + def from_yaml(cls, text: str, kinetics: Kinetics) -> Reaction: ... @staticmethod def list_from_file( filename: str, kinetics: Kinetics, section: str = "reactions" @@ -398,18 +437,21 @@ class Reaction: @allow_negative_orders.setter def allow_negative_orders(self, allow: bool) -> None: ... @property - def input_data(self) -> ReactionRateInput[ReactionRateParameters]: ... + def input_data(self) -> _ReactionRateInput[_ReactionRateParameters]: ... def update_user_data(self, data: dict[str, str | float]) -> None: ... def clear_user_data(self) -> None: ... @property + def rate_coeff_units(self) -> Units: ... + @property def third_body(self) -> ThirdBody | None: ... @property def third_body_name(self) -> str | None: ... +@deprecated( + "class Arrhenius: To be removed after Cantera 3.2. Replace with 'ArrheniusRate'" +) class Arrhenius: - def __init__( - self, A: float = 0, b: float = 0, E: float = 0, init: bool = True - ) -> None: ... + def __init__(self, A: float = 0, b: float = 0, E: float = 0) -> None: ... @property def pre_exponential_factor(self) -> float: ... @property diff --git a/interfaces/cython/cantera/reactor.pyi b/interfaces/cython/cantera/reactor.pyi index 9adfd45aa75..afe93851d86 100644 --- a/interfaces/cython/cantera/reactor.pyi +++ b/interfaces/cython/cantera/reactor.pyi @@ -1,10 +1,9 @@ # This file is part of Cantera. See License.txt in the top-level directory or # at https://cantera.org/license.txt for license and copyright information. -from collections.abc import Iterable, Sequence +from collections.abc import Callable, Iterable, Sequence from typing import ( Any, - Callable, ClassVar, Literal, TypeAlias, @@ -16,16 +15,15 @@ from typing_extensions import Never, override from ._types import Array, ArrayLike, LogLevel7 from .composite import Solution -from .func1 import Func1 +from .func1 import _Func1Like from .jacobians import SystemJacobian -from .kinetics import DerivativeSettings, Kinetics +from .kinetics import Kinetics, _DerivativeSettings from .solutionbase import _SolutionBase from .thermo import ThermoPhase -_Func1Like: TypeAlias = Func1 | Callable[[float], float] | float - class ReactorBase: reactor_type: ClassVar[str] + node_attr: dict[str, str] | None def __init__( self, contents: _SolutionBase | None = None, @@ -45,6 +43,8 @@ class ReactorBase: @property def thermo(self) -> ThermoPhase: ... @property + def phase(self) -> ThermoPhase: ... + @property def volume(self) -> float: ... @volume.setter def volume(self, volume: float) -> None: ... @@ -71,6 +71,7 @@ class ReactorBase: def draw( self, graph: Digraph | None = None, + *, graph_attr: dict[str, str] | None = None, node_attr: dict[str, str] | None = None, print_state: bool = False, @@ -82,6 +83,7 @@ class ReactorBase: def __copy__(self) -> Never: ... class Reactor(ReactorBase): + group_name: str def __init__( self, contents: Solution, @@ -205,12 +207,15 @@ class ReactorSurface: def coverages(self, coverages: Array) -> None: ... @property def reactor(self) -> Reactor: ... + @property + def reactors(self) -> list[Reactor]: ... def draw( self, graph: Digraph | None = None, *, graph_attr: dict[str, str] | None = None, node_attr: dict[str, str] | None = None, + surface_edge_attr: dict[str, str] | None = None, print_state: bool = False, species: Literal["X", "Y"] | bool | Iterable[str] | None = None, species_units: Literal["percent", "ppm"] = "percent", @@ -218,6 +223,7 @@ class ReactorSurface: class ConnectorNode: node_type: ClassVar[str] + edge_attr: dict[str, str] def __init__( self, left: ReactorBase | None = None, @@ -395,7 +401,7 @@ class ReactorNet: def add_reactor(self, r: Reactor) -> None: ... def advance(self, t: float, apply_limit: bool = True) -> float: ... def step(self) -> float: ... - def solve_steady(self, loglevel: LogLevel7) -> None: ... + def solve_steady(self, loglevel: LogLevel7 = 0) -> None: ... def steady_jacobian(self, rdt: float = 0.0) -> Array: ... def initialize(self) -> None: ... def reinitialize(self) -> None: ... @@ -480,7 +486,7 @@ class ReactorNet: max_steps: int, residual_threshold: float, atol: float, - return_residual: Literal[False] = False, + return_residuals: Literal[False] = False, ) -> None: ... @overload def advance_to_steady_state( @@ -488,7 +494,7 @@ class ReactorNet: max_steps: int, residual_threshold: float, atol: float, - return_residual: Literal[True], + return_residuals: Literal[True], ) -> Array: ... @overload def advance_to_steady_state( @@ -496,7 +502,7 @@ class ReactorNet: max_steps: int = 10000, residual_threshold: float = 0.0, atol: float = 0.0, - return_residual: bool = False, + return_residuals: bool = False, ) -> Array | None: ... @override def __reduce__(self) -> Never: ... @@ -516,10 +522,9 @@ class ReactorNet: @property def derivative_settings(self) -> Never: ... @derivative_settings.setter - def derivative_settings(self, value: DerivativeSettings) -> None: ... + def derivative_settings(self, value: _DerivativeSettings) -> None: ... def draw( self, - graph: Digraph | None = None, *, graph_attr: dict[str, str] | None = None, node_attr: dict[str, str] | None = None, diff --git a/interfaces/cython/cantera/solutionbase.pyi b/interfaces/cython/cantera/solutionbase.pyi index d67c2a9d526..ef93f69d0d7 100644 --- a/interfaces/cython/cantera/solutionbase.pyi +++ b/interfaces/cython/cantera/solutionbase.pyi @@ -14,16 +14,16 @@ from typing import ( from typing_extensions import Never, Self from ._types import Array, ArrayLike, Basis, CompressionLevel -from .kinetics import Kinetics, KineticsType +from .kinetics import Kinetics, _KineticsType from .reaction import Reaction -from .thermo import Species, ThermoPhase, ThermoType -from .transport import TransportModel -from .units import UnitDict, UnitDictBytes, UnitSystem +from .thermo import Species, ThermoPhase, _ThermoType +from .transport import _TransportModel +from .units import UnitSystem, _UnitDict, _UnitDictBytes -_SORTING_TYPE: TypeAlias = Literal["alphabetical", "molar-mass"] | None +_SortingType: TypeAlias = Literal["alphabetical", "molar-mass"] | None -YamlHeader = TypedDict( - "YamlHeader", +_YamlHeader = TypedDict( + "_YamlHeader", { "description": str, "generator": str, @@ -60,13 +60,13 @@ class _SolutionBase: @property def composite( self, - ) -> tuple[ThermoType | None, KineticsType | None, TransportModel | None]: ... + ) -> tuple[_ThermoType | None, _KineticsType | None, _TransportModel | None]: ... @property def input_data( self, ) -> dict[str, str | list[str] | dict[str, float | dict[str, float]]]: ... @property - def input_header(self) -> YamlHeader: ... + def input_header(self) -> _YamlHeader: ... def update_user_data(self, data: dict[str, Any]) -> None: ... def clear_user_data(self) -> None: ... def update_user_header(self, data: dict[str, str | list[str]]) -> None: ... @@ -76,7 +76,7 @@ class _SolutionBase: self, filename: None, phases: Sequence[ThermoPhase] | None = None, - units: UnitSystem | UnitDict | UnitDictBytes | None = None, + units: UnitSystem | _UnitDict | _UnitDictBytes | None = None, precision: int | None = None, skip_user_defined: bool | None = None, header: bool = True, @@ -86,7 +86,7 @@ class _SolutionBase: self, filename: str | Path, phases: Sequence[ThermoPhase] | None = None, - units: UnitSystem | UnitDict | UnitDictBytes | None = None, + units: UnitSystem | _UnitDict | _UnitDictBytes | None = None, precision: int | None = None, skip_user_defined: bool | None = None, header: bool = True, @@ -94,9 +94,9 @@ class _SolutionBase: @overload def write_yaml( self, - filename: str | Path | None, + filename: str | Path | None = None, phases: Sequence[ThermoPhase] | None = None, - units: UnitSystem | UnitDict | UnitDictBytes | None = None, + units: UnitSystem | _UnitDict | _UnitDictBytes | None = None, precision: int | None = None, skip_user_defined: bool | None = None, header: bool = True, @@ -106,8 +106,8 @@ class _SolutionBase: mechanism_path: str | Path | None = None, thermo_path: str | Path | None = None, transport_path: str | Path | None = None, - sort_species: _SORTING_TYPE = None, - sort_elements: _SORTING_TYPE = None, + sort_species: _SortingType = None, + sort_elements: _SortingType = None, overwrite: bool = False, quiet: bool = False, ) -> None: ... @@ -136,12 +136,22 @@ class SolutionArrayBase: def size(self) -> int: ... def _api_shape(self) -> tuple[int, ...]: ... def _set_api_shape(self, shape: Sequence[int]) -> None: ... + @overload def info( self, keys: Sequence[str] | None = None, rows: int = 10, width: int | None = None, + display: Literal[False] = False, ) -> str: ... + @overload + def info( + self, + keys: Sequence[str] | None = None, + rows: int = 10, + width: int | None = None, + display: bool = True, + ) -> None: ... @property def meta(self) -> dict[str, Any]: ... @meta.setter diff --git a/interfaces/cython/cantera/speciesthermo.pyi b/interfaces/cython/cantera/speciesthermo.pyi index 8db7d562f68..c4aa53b8822 100644 --- a/interfaces/cython/cantera/speciesthermo.pyi +++ b/interfaces/cython/cantera/speciesthermo.pyi @@ -6,9 +6,10 @@ from typing import Any, ClassVar, TypedDict from typing_extensions import Required from ._types import Array, ArrayLike +from .constants import gas_constant as gas_constant -SpeciesThermoInput = TypedDict( - "SpeciesThermoInput", +_SpeciesThermoInput = TypedDict( + "_SpeciesThermoInput", { "model": Required[str], "temperature-ranges": list[float], @@ -42,7 +43,7 @@ class SpeciesThermo: @property def coeffs(self) -> Array: ... @property - def input_data(self) -> SpeciesThermoInput: ... + def input_data(self) -> _SpeciesThermoInput: ... def update_user_data(self, data: dict[str, Any]) -> None: ... def clear_user_data(self) -> None: ... def cp(self, T: float) -> float: ... diff --git a/interfaces/cython/cantera/speciesthermo.pyx b/interfaces/cython/cantera/speciesthermo.pyx index d35a90f57e1..6fae14b2f2a 100644 --- a/interfaces/cython/cantera/speciesthermo.pyx +++ b/interfaces/cython/cantera/speciesthermo.pyx @@ -5,7 +5,7 @@ cimport numpy as np import numpy as np from ._utils cimport * -from .constants import * +from .constants import gas_constant # These match the definitions in speciesThermoTyeps.h cdef int SPECIES_THERMO_CONSTANT_CP = 1 diff --git a/interfaces/cython/cantera/thermo.pyi b/interfaces/cython/cantera/thermo.pyi index 46e72935e32..06d959d4213 100644 --- a/interfaces/cython/cantera/thermo.pyi +++ b/interfaces/cython/cantera/thermo.pyi @@ -20,11 +20,11 @@ from ._types import ( StateVariable, ) from .solutionbase import _SolutionBase -from .speciesthermo import SpeciesThermo, SpeciesThermoInput -from .transport import GasTransportData, GasTransportInput +from .speciesthermo import SpeciesThermo, _SpeciesThermoInput +from .transport import GasTransportData, _GasTransportInput from .units import Units -ThermoType: TypeAlias = Literal[ +_ThermoType: TypeAlias = Literal[ "Debye-Huckel", "HMW-electrolyte", "Margules", @@ -52,7 +52,7 @@ ThermoType: TypeAlias = Literal[ "pure-fluid", ] -PhaseOfMatter: TypeAlias = Literal[ +_PhaseOfMatter: TypeAlias = Literal[ "gas", "liquid", "solid", @@ -63,15 +63,15 @@ PhaseOfMatter: TypeAlias = Literal[ "unspecified", ] -QuadratureMethod: TypeAlias = Literal["simpson", "trapezoidal"] +_QuadratureMethod: TypeAlias = Literal["simpson", "trapezoidal"] -SpeciesInput = TypedDict( - "SpeciesInput", +_SpeciesInput = TypedDict( + "_SpeciesInput", { "name": Required[str], "composition": Required[dict[str, float]], - "thermo": SpeciesThermoInput, - "transport": GasTransportInput, + "thermo": _SpeciesThermoInput, + "transport": _GasTransportInput, "equation-of-state": dict[str, Any], "critical-parameters": dict[str, float], "Debye-Huckel": dict[str, float], @@ -92,7 +92,7 @@ class Species: init: bool = True, ) -> None: ... @staticmethod - def from_dict(data: SpeciesInput) -> Species: ... + def from_dict(data: _SpeciesInput) -> Species: ... @staticmethod def from_yaml(text: str) -> Species: ... @staticmethod @@ -118,15 +118,15 @@ class Species: @transport.setter def transport(self, value: GasTransportData) -> None: ... @property - def input_data(self) -> SpeciesInput: ... + def input_data(self) -> _SpeciesInput: ... def update_user_data(self, data: dict[str, Any]) -> None: ... def clear_user_data(self) -> None: ... class ThermoPhase(_SolutionBase): @property - def thermo_model(self) -> ThermoType: ... + def thermo_model(self) -> _ThermoType: ... @property - def phase_of_matter(self) -> PhaseOfMatter: ... + def phase_of_matter(self) -> _PhaseOfMatter: ... def report(self, show_thermo: bool = True, threshold: float = 1e-14) -> str: ... def __call__(self, *args: Any, **kwargs: Any) -> None: ... @property @@ -238,8 +238,8 @@ class ThermoPhase(_SolutionBase): ) -> None: ... def equivalence_ratio( self, - fuel: CompositionLike, - oxidizer: CompositionLike, + fuel: CompositionLike | None = None, + oxidizer: CompositionLike | None = None, basis: Basis = "mole", include_species: list[str | bytes | float] | None = None, ) -> float: ... @@ -473,9 +473,18 @@ class ThermoPhase(_SolutionBase): def Te(self, value: float) -> None: ... @property def Pe(self) -> float: ... + @property + def reduced_electric_field(self) -> float: ... + @reduced_electric_field.setter + def reduced_electric_field(self, value: float) -> None: ... + @property + def electric_field(self) -> float: ... + @electric_field.setter + def electric_field(self, value: float) -> None: ... def set_discretized_electron_energy_distribution( self, levels: ArrayLike, distribution: ArrayLike ) -> None: ... + def update_electron_energy_distribution(self) -> None: ... @property def n_electron_energy_levels(self) -> int: ... @property @@ -497,9 +506,9 @@ class ThermoPhase(_SolutionBase): @mean_electron_energy.setter def mean_electron_energy(self, energy: float) -> None: ... @property - def quadrature_method(self) -> QuadratureMethod: ... + def quadrature_method(self) -> _QuadratureMethod: ... @quadrature_method.setter - def quadrature_method(self, method: QuadratureMethod) -> None: ... + def quadrature_method(self, method: _QuadratureMethod) -> None: ... @property def normalize_electron_energy_distribution_enabled(self) -> bool: ... @normalize_electron_energy_distribution_enabled.setter diff --git a/interfaces/cython/cantera/transport.pyi b/interfaces/cython/cantera/transport.pyi index 9c873731d04..1a632cf2cc1 100644 --- a/interfaces/cython/cantera/transport.pyi +++ b/interfaces/cython/cantera/transport.pyi @@ -6,7 +6,7 @@ from typing import Literal, TypeAlias, TypedDict from ._types import Array, ArrayLike from .solutionbase import _SolutionBase -TransportModel: TypeAlias = Literal[ +_TransportModel: TypeAlias = Literal[ "none", "unity-Lewis-number", "mixture-averaged", @@ -19,11 +19,11 @@ TransportModel: TypeAlias = Literal[ "high-pressure=Chung", ] -GeometryOptions: TypeAlias = Literal["atom", "linear", "nonlinear"] +_GeometryOptions: TypeAlias = Literal["atom", "linear", "nonlinear"] -class GasTransportInput(TypedDict, total=False): +class _GasTransportInput(TypedDict, total=False): model: Literal["gas"] - geometry: GeometryOptions + geometry: _GeometryOptions diameter: float well_depth: float dipole: float @@ -34,8 +34,8 @@ class GasTransportInput(TypedDict, total=False): quadrupole_polarizability: float note: str -TransportFittingErrors = TypedDict( - "TransportFittingErrors", +_TransportFittingErrors = TypedDict( + "_TransportFittingErrors", { "viscosity-max-abs-error": float, "viscosity-max-rel-error": float, @@ -49,7 +49,7 @@ TransportFittingErrors = TypedDict( class GasTransportData: def __init__( self, - geometry: GeometryOptions | Literal[""] = "", + geometry: _GeometryOptions | Literal[""] = "", diameter: float = -1, well_depth: float = -1, dipole: float = 0.0, @@ -74,13 +74,13 @@ class GasTransportData: quadrupole_polarizability: float = 0.0, ) -> None: ... @property - def input_data(self) -> GasTransportInput: ... + def input_data(self) -> _GasTransportInput: ... def update_user_data(self, data: GasTransportData) -> None: ... def clear_user_data(self) -> None: ... @property - def geometry(self) -> GeometryOptions: ... + def geometry(self) -> _GeometryOptions: ... @geometry.setter - def geometry(self, geometry: GeometryOptions) -> None: ... + def geometry(self, geometry: _GeometryOptions) -> None: ... @property def diameter(self) -> float: ... @diameter.setter @@ -116,9 +116,9 @@ class GasTransportData: class Transport(_SolutionBase): @property - def transport_model(self) -> TransportModel: ... + def transport_model(self) -> _TransportModel: ... @transport_model.setter - def transport_model(self, model: TransportModel) -> None: ... + def transport_model(self, model: _TransportModel) -> None: ... @property def CK_mode(self) -> bool: ... @property @@ -166,7 +166,7 @@ class Transport(_SolutionBase): actualT: bool = False, ) -> None: ... @property - def transport_fitting_errors(self) -> TransportFittingErrors: ... + def transport_fitting_errors(self) -> _TransportFittingErrors: ... class DustyGasTransport(Transport): @property diff --git a/interfaces/cython/cantera/units.pyi b/interfaces/cython/cantera/units.pyi index 56667ac5dbc..06df7366771 100644 --- a/interfaces/cython/cantera/units.pyi +++ b/interfaces/cython/cantera/units.pyi @@ -5,8 +5,8 @@ from typing import TypedDict, overload from ._types import Array -UnitDict = TypedDict( - "UnitDict", +_UnitDict = TypedDict( + "_UnitDict", { "activation-energy": str, "current": str, @@ -20,8 +20,8 @@ UnitDict = TypedDict( }, total=False, ) -UnitDictBytes = TypedDict( - "UnitDictBytes", +_UnitDictBytes = TypedDict( + "_UnitDictBytes", { "activation-energy": bytes, "current": bytes, @@ -48,11 +48,11 @@ class UnitStack: def join(self, exponent: float) -> None: ... class UnitSystem: - def defaults(self) -> UnitDictBytes: ... + def defaults(self) -> _UnitDictBytes: ... @property - def units(self) -> UnitDict: ... + def units(self) -> _UnitDict: ... @units.setter - def units(self, units: UnitDict | UnitDictBytes) -> None: ... + def units(self, units: _UnitDict | _UnitDictBytes) -> None: ... @overload def convert_to(self, quantity: str | float, dest: str | Units) -> float: ... @overload diff --git a/interfaces/cython/cantera/with_units/__init__.py b/interfaces/cython/cantera/with_units/__init__.py index d3d6aa7a476..8b37d97790d 100644 --- a/interfaces/cython/cantera/with_units/__init__.py +++ b/interfaces/cython/cantera/with_units/__init__.py @@ -5,8 +5,8 @@ # use the registry is imported. In particular, it has to come before any of our code # that uses units! from pint import UnitRegistry, set_application_registry -cantera_units_registry = UnitRegistry() -set_application_registry(cantera_units_registry) +cantera_units_registry: UnitRegistry = UnitRegistry() +set_application_registry(cantera_units_registry) # type: ignore[no-untyped-call] # Now we can import our code from .solution import * diff --git a/interfaces/cython/cantera/with_units/__init__.pyi b/interfaces/cython/cantera/with_units/__init__.pyi deleted file mode 100644 index 8a86fa66630..00000000000 --- a/interfaces/cython/cantera/with_units/__init__.pyi +++ /dev/null @@ -1,39 +0,0 @@ -# This file is part of Cantera. See License.txt in the top-level directory or -# at https://cantera.org/license.txt for license and copyright information. - -from pint import UnitRegistry -from pint import set_application_registry as set_application_registry - -from .solution import ( - Q_, - CarbonDioxide, - Heptane, - Hfc134a, - Hydrogen, - Methane, - Nitrogen, - Oxygen, - PureFluid, - Solution, - Water, - units, -) - -cantera_units_registry: UnitRegistry - -__all__: list[str] = [ - "Q_", - "CarbonDioxide", - "Heptane", - "Hfc134a", - "Hydrogen", - "Methane", - "Nitrogen", - "Oxygen", - "PureFluid", - "Solution", - "Water", - "cantera_units_registry", - "set_application_registry", - "units", -] diff --git a/interfaces/cython/cantera/with_units/solution.py.in b/interfaces/cython/cantera/with_units/solution.py.in index d6aa2993ceb..9f8d958aab4 100644 --- a/interfaces/cython/cantera/with_units/solution.py.in +++ b/interfaces/cython/cantera/with_units/solution.py.in @@ -8,9 +8,9 @@ from .. import (Heptane as _Heptane, Water as _Water, Hfc134a as _Hfc134a, Methane as _Methane, Nitrogen as _Nitrogen, Oxygen as _Oxygen) from pint import get_application_registry -__all__ = ("units", "Q_", "Solution", "PureFluid", "Heptane", "CarbonDioxide", +__all__ = ["units", "Q_", "Solution", "PureFluid", "Heptane", "CarbonDioxide", "Hfc134a", "Hydrogen", "Methane", "Nitrogen", "Oxygen", "Water", - "CanteraError") + "CanteraError"] units = get_application_registry() Q_ = units.Quantity diff --git a/interfaces/cython/cantera/with_units/solution.pyi b/interfaces/cython/cantera/with_units/solution.pyi index 0b35fb6cea1..4e46adeb61a 100644 --- a/interfaces/cython/cantera/with_units/solution.pyi +++ b/interfaces/cython/cantera/with_units/solution.pyi @@ -1,8 +1,9 @@ # This file is part of Cantera. See License.txt in the top-level directory or # at https://cantera.org/license.txt for license and copyright information. +from collections.abc import Callable from pathlib import Path -from typing import Any, Callable, Literal, ParamSpec, TypeAlias, TypeVar +from typing import Any, Literal, ParamSpec, TypeAlias, TypeVar from pint import Quantity from pint.registry import ApplicationRegistry @@ -33,11 +34,11 @@ _StateSetter: TypeAlias = tuple[ ] units: ApplicationRegistry -Q_: Quantity +Q_: type[Quantity] -P = ParamSpec("P") -T = TypeVar("T") -def copy_doc(method: Callable[P, T]) -> Callable[P, T]: ... +_P = ParamSpec("_P") +_T = TypeVar("_T") +def copy_doc(method: Callable[_P, _T]) -> Callable[_P, _T]: ... class Solution: def __init__( diff --git a/interfaces/cython/cantera/yaml2ck.py b/interfaces/cython/cantera/yaml2ck.py index 05c897f76a9..94f32bb4164 100644 --- a/interfaces/cython/cantera/yaml2ck.py +++ b/interfaces/cython/cantera/yaml2ck.py @@ -552,7 +552,7 @@ def build_transport_text(species: Iterable[ct.Species], separate_file: bool = Fa def convert( - solution: str | Path | ct.Solution, + solution: str | Path | ct.Solution | ct.Interface, phase_name: str = "", mechanism_path: str | Path | None = None, thermo_path: str | Path | None = None, From 04a4779d8d3c3e4ab9ab9448166185d90045e2d0 Mon Sep 17 00:00:00 2001 From: "Tim E. Dawson" Date: Mon, 20 Oct 2025 16:45:52 -0500 Subject: [PATCH 25/26] Address several review comments. --- .github/workflows/main.yml | 32 ++++++----- interfaces/cython/.mypyignore | 12 +++++ interfaces/cython/cantera/_onedim.pyi | 11 ++-- interfaces/cython/cantera/_types.pyi | 8 ++- interfaces/cython/cantera/composite.pyi | 63 ++++++++++------------ interfaces/cython/cantera/func1.pyi | 1 - interfaces/cython/cantera/kinetics.pyi | 5 +- interfaces/cython/cantera/mixture.pyi | 12 ++--- interfaces/cython/cantera/solutionbase.pyi | 1 - 9 files changed, 78 insertions(+), 67 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index dc922f30759..36c362e03d5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -435,7 +435,7 @@ jobs: type-checking: name: Verify typing correctness and coverage of the public Python API - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 timeout-minutes: 20 needs: ["ubuntu-multiple-pythons"] strategy: @@ -455,16 +455,16 @@ jobs: - name: Install Apt dependencies run: | sudo apt update - sudo apt install graphviz libhdf5-103 libfmt-dev libopenblas0-openmp + sudo apt install graphviz libhdf5-dev libfmt-dev libopenblas-openmp-dev - name: Download the wheel artifact uses: actions/download-artifact@v5 with: - name: cantera-wheel-3.10-ubuntu-22.04 + name: cantera-wheel-3.10-ubuntu-24.04 path: dist - name: Download the Cantera shared library (.so) uses: actions/download-artifact@v5 with: - name: libcantera_shared-ubuntu-22.04.so + name: libcantera_shared-ubuntu-24.04.so path: build/lib - name: Set up environment run: | @@ -476,30 +476,36 @@ jobs: - name: Install Python dependencies run: | python3 -m pip install numpy ruamel.yaml pandas pandas-stubs \ - scipy pint graphviz typing_extensions mypy[reports] + scipy pint graphviz typing_extensions pyright mypy[reports] python3 -m pip install --pre --no-index --find-links dist cantera - name: Check static type correctness with mypy run: | mypy --config-file interfaces/cython/pyproject.toml \ - -p cantera >> type_check/mypy_check.txt + -p cantera | tee type_check/mypy_check.txt - name: Generate coverage report with mypy if: failure() || success() run: | mypy --config-file interfaces/cython/pyproject.toml \ - --txt-report type_check/ interfaces/cython/cantera - cat type_check/index.txt >> $GITHUB_STEP_SUMMARY + --cobertura-xml-report type_check/ interfaces/cython/cantera + - name: Summarize the report + uses: danielpalme/ReportGenerator-GitHub-Action@9870ed167742d546b99962ff815fcc1098355ed8 # v5.4.17 + with: + reports: './type_check/cobertura.xml' + targetdir: './type_check' + reporttypes: 'MarkdownSummaryGithub' + - run: cat ./type_check/SummaryGithub.md >> $GITHUB_STEP_SUMMARY + shell: bash - name: Generate coverage report with pyright if: failure() || success() - uses: jakebailey/pyright-action@6cabc0f01c4994be48fd45cd9dbacdd6e1ee6e5e # v2.3.3 - with: - ignore-external: true - verify-types: cantera + run: | + pyright --ignoreexternal --verifytypes cantera \ + | tee type_check/pyright_verifytypes.txt - name: Check type correctness against runtime with stubtest if: failure() || success() run: | stubtest --mypy-config-file interfaces/cython/pyproject.toml \ --ignore-disjoint-bases --allowlist interfaces/cython/.mypyignore \ - --concise cantera >> type_check/stubtest_concise.txt + --concise cantera | tee type_check/stubtest_concise.txt stubtest --mypy-config-file interfaces/cython/pyproject.toml \ --ignore-disjoint-bases --allowlist interfaces/cython/.mypyignore \ cantera >> type_check/stubtest_detailed.txt diff --git a/interfaces/cython/.mypyignore b/interfaces/cython/.mypyignore index e96c4da1793..8a399a0a01b 100644 --- a/interfaces/cython/.mypyignore +++ b/interfaces/cython/.mypyignore @@ -23,11 +23,17 @@ cantera.FalloffRate.__init__ cantera.GasTransportData.__init__ cantera.InterfaceArrheniusRate.__init__ cantera.InterfaceBlowersMaselRate.__init__ +cantera.Mixture.__call__ cantera.Mixture.__init__ cantera.PlogRate.__init__ +cantera.Quantity.clear_user_data +cantera.Quantity.clear_user_header +cantera.Quantity.reactions +cantera.Quantity.update_electron_energy_distribution cantera.Reaction.__init__ cantera.ReactionPathDiagram.__init__ cantera.SolutionArray.__init__ +cantera.SolutionArray.reactions cantera.SolutionArrayBase.__init__ cantera.SpeciesThermo.__init__ cantera.StickingBlowersMaselRate.__init__ @@ -36,8 +42,14 @@ cantera.ThirdBody.__init__ cantera.TwoTempPlasmaRate.__init__ cantera.ck2yaml.ChemicallyActivated.__init__ cantera.ck2yaml.Falloff.__init__ +cantera.composite.Quantity.clear_user_data +cantera.composite.Quantity.clear_user_header +cantera.composite.Quantity.reactions +cantera.composite.Quantity.update_electron_energy_distribution cantera.composite.SolutionArray.__init__ +cantera.composite.SolutionArray.reactions cantera.jacobians.SystemJacobian.__init__ +cantera.mixture.Mixture.__call__ cantera.mixture.Mixture.__init__ cantera.reaction.Arrhenius.__init__ cantera.reaction.ArrheniusRate.__init__ diff --git a/interfaces/cython/cantera/_onedim.pyi b/interfaces/cython/cantera/_onedim.pyi index 191c3d452a5..4d7b15683b6 100644 --- a/interfaces/cython/cantera/_onedim.pyi +++ b/interfaces/cython/cantera/_onedim.pyi @@ -2,6 +2,7 @@ # at https://cantera.org/license.txt for license and copyright information. from collections.abc import Callable, Sequence +from pathlib import Path from typing import ( Any, ClassVar, @@ -208,9 +209,9 @@ class ReactingSurface1D(Boundary1D): class FlowBase(Domain1D): def __init__(self, *args: Any, **kwargs: Any) -> None: ... @property - def P(self) -> Array: ... + def P(self) -> float: ... @P.setter - def P(self, P: Array) -> None: ... + def P(self, P: float) -> None: ... @property def T(self) -> Array: ... @property @@ -239,7 +240,7 @@ class FlowBase(Domain1D): def energy_enabled(self) -> bool: ... @energy_enabled.setter def energy_enabled(self, enable: bool) -> None: ... - def set_fixed_temp_profile(self, pos: Array, T: Array) -> None: ... + def set_fixed_temp_profile(self, pos: ArrayLike, T: ArrayLike) -> None: ... def get_settings3(self) -> _Domain1DSettings: ... @property def boundary_emissivities(self) -> tuple[float, float]: ... @@ -370,7 +371,7 @@ class Sim1D: def set_right_control_point(self, T: float) -> None: ... def save( self, - filename: str = "soln.yaml", + filename: Path | str = "soln.yaml", name: str = "solution", description: str | None = None, loglevel: LogLevel | None = None, @@ -381,7 +382,7 @@ class Sim1D: ) -> None: ... def restore( self, - filename: str = "soln.yaml", + filename: Path | str = "soln.yaml", name: str = "solution", loglevel: LogLevel | None = None, ) -> dict[str, str]: ... diff --git a/interfaces/cython/cantera/_types.pyi b/interfaces/cython/cantera/_types.pyi index d789ef78305..8a9f5c96c78 100644 --- a/interfaces/cython/cantera/_types.pyi +++ b/interfaces/cython/cantera/_types.pyi @@ -49,8 +49,8 @@ FullState: TypeAlias = Literal[ ] CompositionLike: TypeAlias = str | dict[str, float] | ArrayLike StateSetter: TypeAlias = tuple[float, float, CompositionLike] -ArrayCompositionLike: TypeAlias = str | dict[str, Array | float] | ArrayLike -ArrayStateSetter: TypeAlias = tuple[Array | float, Array | float, ArrayCompositionLike] +ArrayCompositionLike: TypeAlias = str | dict[str, ArrayLike] | ArrayLike +ArrayStateSetter: TypeAlias = tuple[ArrayLike, ArrayLike, ArrayCompositionLike] # PureFluid state definitions PureFluidStateVariable: TypeAlias = Literal[StateVariable, "Q"] @@ -61,9 +61,7 @@ PureFluidFullState: TypeAlias = Literal[ FullState, "TDQ", "TPQ", "UVQ", "DPQ", "HPQ", "SPQ", "SVQ" ] PureFluidStateSetter: TypeAlias = tuple[float, float, float] -ArrayPureFluidStateSetter: TypeAlias = tuple[ - Array | float, Array | float, Array | float -] +ArrayPureFluidStateSetter: TypeAlias = tuple[ArrayLike, ArrayLike, ArrayLike] RefineCriteria = TypedDict( "RefineCriteria", diff --git a/interfaces/cython/cantera/composite.pyi b/interfaces/cython/cantera/composite.pyi index 07340fde0f2..a3def198a73 100644 --- a/interfaces/cython/cantera/composite.pyi +++ b/interfaces/cython/cantera/composite.pyi @@ -12,7 +12,7 @@ from typing import ( ) from pandas import DataFrame -from typing_extensions import Never, Self, Unpack, override +from typing_extensions import Never, Unpack, override from ._types import ( Array, @@ -150,9 +150,9 @@ class Quantity: @property def input_header(self) -> _YamlHeader: ... def update_user_data(self, data: dict[str, Any]) -> None: ... - def clear_user_data(self, *args: Any, **kwargs: Any) -> None: ... + def clear_user_data(self) -> None: ... def update_user_header(self, data: dict[str, str | list[str]]) -> None: ... - def clear_user_header(self, *args: Any, **kwargs: Any) -> None: ... + def clear_user_header(self) -> None: ... @overload def write_yaml( self, @@ -199,7 +199,6 @@ class Quantity: def selected_species( self, species: str | int | Sequence[str] | Sequence[int] ) -> None: ... - def __getstate__(self) -> str: ... # From Transport: @property @@ -271,7 +270,7 @@ class Quantity: @property def kinetics_species_names(self) -> list[str]: ... def reaction(self, i_reaction: int) -> Reaction: ... - def reactions(self, *args: Any, **kwargs: Any) -> list[Reaction]: ... + def reactions(self) -> list[Reaction]: ... def modify_reaction(self, irxn: int, rxn: Reaction) -> None: ... def add_reaction(self, rxn: Reaction) -> None: ... def multiplier(self, i_reaction: int) -> float: ... @@ -558,7 +557,7 @@ class Quantity: @property def TD(self) -> tuple[float, float]: ... @TD.setter - def TD(self, values: Sequence[float]) -> None: ... + def TD(self, values: tuple[float, float]) -> None: ... @property def TDX(self) -> tuple[float, float, Array]: ... @TDX.setter @@ -570,7 +569,7 @@ class Quantity: @property def TP(self) -> tuple[float, float]: ... @TP.setter - def TP(self, values: Sequence[float]) -> None: ... + def TP(self, values: tuple[float, float]) -> None: ... @property def TPX(self) -> tuple[float, float, Array]: ... @TPX.setter @@ -582,7 +581,7 @@ class Quantity: @property def UV(self) -> tuple[float, float]: ... @UV.setter - def UV(self, values: Sequence[float]) -> None: ... + def UV(self, values: tuple[float, float]) -> None: ... @property def UVX(self) -> tuple[float, float, Array]: ... @UVX.setter @@ -594,7 +593,7 @@ class Quantity: @property def DP(self) -> tuple[float, float]: ... @DP.setter - def DP(self, values: Sequence[float]) -> None: ... + def DP(self, values: tuple[float, float]) -> None: ... @property def DPX(self) -> tuple[float, float, Array]: ... @DPX.setter @@ -606,7 +605,7 @@ class Quantity: @property def HP(self) -> tuple[float, float]: ... @HP.setter - def HP(self, values: Sequence[float]) -> None: ... + def HP(self, values: tuple[float, float]) -> None: ... @property def HPX(self) -> tuple[float, float, Array]: ... @HPX.setter @@ -618,7 +617,7 @@ class Quantity: @property def SP(self) -> tuple[float, float]: ... @SP.setter - def SP(self, values: Sequence[float]) -> None: ... + def SP(self, values: tuple[float, float]) -> None: ... @property def SPX(self) -> tuple[float, float, Array]: ... @SPX.setter @@ -630,7 +629,7 @@ class Quantity: @property def SV(self) -> tuple[float, float]: ... @SV.setter - def SV(self, values: Sequence[float]) -> None: ... + def SV(self, values: tuple[float, float]) -> None: ... @property def SVX(self) -> tuple[float, float, Array]: ... @SVX.setter @@ -705,9 +704,7 @@ class Quantity: def set_discretized_electron_energy_distribution( self, levels: ArrayLike, distribution: ArrayLike ) -> None: ... - def update_electron_energy_distribution( - self, *args: Any, **kwargs: Any - ) -> None: ... + def update_electron_energy_distribution(self) -> None: ... @property def n_electron_energy_levels(self) -> int: ... @property @@ -837,7 +834,7 @@ class SolutionArray(SolutionArrayBase, Generic[_P]): @property def TD(self) -> tuple[Array, Array]: ... @TD.setter - def TD(self, values: Sequence[Array | float]) -> None: ... + def TD(self, values: tuple[ArrayLike, ArrayLike]) -> None: ... @property def TDX(self) -> tuple[Array, Array, Array]: ... @TDX.setter @@ -849,7 +846,7 @@ class SolutionArray(SolutionArrayBase, Generic[_P]): @property def TP(self) -> tuple[Array, Array]: ... @TP.setter - def TP(self, values: Sequence[Array | float]) -> None: ... + def TP(self, values: tuple[ArrayLike, ArrayLike]) -> None: ... @property def TPX(self) -> tuple[Array, Array, Array]: ... @TPX.setter @@ -861,7 +858,7 @@ class SolutionArray(SolutionArrayBase, Generic[_P]): @property def UV(self) -> tuple[Array, Array]: ... @UV.setter - def UV(self, values: Sequence[Array | float]) -> None: ... + def UV(self, values: tuple[ArrayLike, ArrayLike]) -> None: ... @property def UVX(self) -> tuple[Array, Array, Array]: ... @UVX.setter @@ -873,7 +870,7 @@ class SolutionArray(SolutionArrayBase, Generic[_P]): @property def DP(self) -> tuple[Array, Array]: ... @DP.setter - def DP(self, values: Sequence[Array | float]) -> None: ... + def DP(self, values: tuple[ArrayLike, ArrayLike]) -> None: ... @property def DPX(self) -> tuple[Array, Array, Array]: ... @DPX.setter @@ -885,7 +882,7 @@ class SolutionArray(SolutionArrayBase, Generic[_P]): @property def HP(self) -> tuple[Array, Array]: ... @HP.setter - def HP(self, values: Sequence[Array | float]) -> None: ... + def HP(self, values: tuple[ArrayLike, ArrayLike]) -> None: ... @property def HPX(self) -> tuple[Array, Array, Array]: ... @HPX.setter @@ -897,7 +894,7 @@ class SolutionArray(SolutionArrayBase, Generic[_P]): @property def SP(self) -> tuple[Array, Array]: ... @SP.setter - def SP(self, values: Sequence[Array | float]) -> None: ... + def SP(self, values: tuple[ArrayLike, ArrayLike]) -> None: ... @property def SPX(self) -> tuple[Array, Array, Array]: ... @SPX.setter @@ -909,7 +906,7 @@ class SolutionArray(SolutionArrayBase, Generic[_P]): @property def SV(self) -> tuple[Array, Array]: ... @SV.setter - def SV(self, values: Sequence[Array | float]) -> None: ... + def SV(self, values: tuple[ArrayLike, ArrayLike]) -> None: ... @property def SVX(self) -> tuple[Array, Array, Array]: ... @SVX.setter @@ -921,39 +918,39 @@ class SolutionArray(SolutionArrayBase, Generic[_P]): @property def TQ(self) -> tuple[Array, Array]: ... @TQ.setter - def TQ(self, values: Sequence[Array | float]) -> None: ... + def TQ(self, values: tuple[ArrayLike, ArrayLike]) -> None: ... @property def PQ(self) -> tuple[Array, Array]: ... @PQ.setter - def PQ(self, values: Sequence[Array | float]) -> None: ... + def PQ(self, values: tuple[ArrayLike, ArrayLike]) -> None: ... @property def ST(self) -> tuple[Array, Array]: ... @ST.setter - def ST(self, values: Sequence[Array | float]) -> None: ... + def ST(self, values: tuple[ArrayLike, ArrayLike]) -> None: ... @property def TV(self) -> tuple[Array, Array]: ... @TV.setter - def TV(self, values: Sequence[Array | float]) -> None: ... + def TV(self, values: tuple[ArrayLike, ArrayLike]) -> None: ... @property def PV(self) -> tuple[Array, Array]: ... @PV.setter - def PV(self, values: Sequence[Array | float]) -> None: ... + def PV(self, values: tuple[ArrayLike, ArrayLike]) -> None: ... @property def UP(self) -> tuple[Array, Array]: ... @UP.setter - def UP(self, values: Sequence[Array | float]) -> None: ... + def UP(self, values: tuple[ArrayLike, ArrayLike]) -> None: ... @property def VH(self) -> tuple[Array, Array]: ... @VH.setter - def VH(self, values: Sequence[Array | float]) -> None: ... + def VH(self, values: tuple[ArrayLike, ArrayLike]) -> None: ... @property def TH(self) -> tuple[Array, Array]: ... @TH.setter - def TH(self, values: Sequence[Array | float]) -> None: ... + def TH(self, values: tuple[ArrayLike, ArrayLike]) -> None: ... @property def SH(self) -> tuple[Array, Array]: ... @SH.setter - def SH(self, values: Sequence[Array | float]) -> None: ... + def SH(self, values: tuple[ArrayLike, ArrayLike]) -> None: ... @property def TDQ(self) -> tuple[Array, Array, Array]: ... @TDQ.setter @@ -1293,9 +1290,7 @@ class SolutionArray(SolutionArrayBase, Generic[_P]): self: SolutionArray[Kinetics], species: str | bytes | int, phase: int = 0 ) -> int: ... def reaction(self: SolutionArray[Kinetics], i_reaction: int) -> Reaction: ... - def reactions( - self: SolutionArray[Kinetics], *args: Any, **kwargs: Any - ) -> list[Reaction]: ... + def reactions(self: SolutionArray[Kinetics]) -> list[Reaction]: ... def modify_reaction( self: SolutionArray[Kinetics], irxn: int, rxn: Reaction ) -> None: ... diff --git a/interfaces/cython/cantera/func1.pyi b/interfaces/cython/cantera/func1.pyi index 9b0c5495034..466c950fe3e 100644 --- a/interfaces/cython/cantera/func1.pyi +++ b/interfaces/cython/cantera/func1.pyi @@ -10,7 +10,6 @@ from typing_extensions import Never, override _Func1Like: TypeAlias = str | Callable[[float], float] | float class Func1: - callable: Callable[[float], float] def __init__( self, c: _Func1Like, diff --git a/interfaces/cython/cantera/kinetics.pyi b/interfaces/cython/cantera/kinetics.pyi index 4333388edcc..5de5402b265 100644 --- a/interfaces/cython/cantera/kinetics.pyi +++ b/interfaces/cython/cantera/kinetics.pyi @@ -2,6 +2,7 @@ # at https://cantera.org/license.txt for license and copyright information. from collections.abc import Sequence +from pathlib import Path from typing import Literal, TypeAlias, TypedDict from ._types import Array @@ -162,7 +163,7 @@ class Kinetics(_SolutionBase): class InterfaceKinetics(Kinetics): def __init__( self, - infile: str = "", + infile: Path | str = "", name: str = "", adjacent: Sequence[ThermoPhase] = (), ) -> None: ... @@ -184,7 +185,7 @@ class InterfaceKinetics(Kinetics): def interface_current(self, phase: ThermoPhase | str | int) -> float: ... def write_yaml( # type: ignore[override] self, - filename: str, + filename: Path | str, phases: Sequence[ThermoPhase] | None = None, units: UnitSystem | _UnitDict | _UnitDictBytes | None = None, precision: int | None = None, diff --git a/interfaces/cython/cantera/mixture.pyi b/interfaces/cython/cantera/mixture.pyi index 529dd5fb548..2d7ab266de7 100644 --- a/interfaces/cython/cantera/mixture.pyi +++ b/interfaces/cython/cantera/mixture.pyi @@ -10,7 +10,7 @@ from .thermo import ThermoPhase class Mixture: def __init__(self, phases: Sequence[tuple[ThermoPhase, float]]) -> None: ... def report(self, threshold: float = 1e-14) -> str: ... - def __call__(self, *args: Any, **kwargs: Any) -> None: ... + def __call__(self) -> None: ... @property def n_elements(self) -> int: ... def element_index(self, element: str | bytes | float) -> int: ... @@ -22,11 +22,11 @@ class Mixture: def species_index( self, phase: ThermoPhase | str | int, species: str | bytes | int ) -> int: ... - def n_atoms(self, k: int, m: int) -> int: ... + def n_atoms(self, k: int, m: str | int) -> int: ... @property def n_phases(self) -> int: ... def phase(self, n: int) -> ThermoPhase: ... - def phase_index(self, p: str) -> int: ... + def phase_index(self, p: ThermoPhase | str | int) -> int: ... @property def phase_names(self) -> list[str]: ... @property @@ -43,9 +43,9 @@ class Mixture: def P(self, P: float) -> None: ... @property def charge(self) -> float: ... - def phase_charge(self, p: str) -> float: ... - def phase_moles(self, p: str | None = None) -> list[float] | float: ... - def set_phase_moles(self, p: str, moles: float) -> None: ... + def phase_charge(self, p: ThermoPhase | str | int) -> float: ... + def phase_moles(self, p: ThermoPhase | str | int | None = None) -> list[float] | float: ... + def set_phase_moles(self, p: ThermoPhase | str | int, moles: float) -> None: ... @property def species_moles(self) -> Array: ... @species_moles.setter diff --git a/interfaces/cython/cantera/solutionbase.pyi b/interfaces/cython/cantera/solutionbase.pyi index ef93f69d0d7..bd187f7d003 100644 --- a/interfaces/cython/cantera/solutionbase.pyi +++ b/interfaces/cython/cantera/solutionbase.pyi @@ -118,7 +118,6 @@ class _SolutionBase: def selected_species( self, species: str | int | Sequence[str] | Sequence[int] ) -> None: ... - def __getstate__(self) -> str: ... def __setstate__(self, pkl: str) -> None: ... def __copy__(self) -> Never: ... From 190168481c2e8760d6d97681ebedddb819d7bafc Mon Sep 17 00:00:00 2001 From: "Tim E. Dawson" Date: Mon, 20 Oct 2025 18:02:30 -0500 Subject: [PATCH 26/26] Trim CI pipeline for testing. --- .github/workflows/linters.yml | 33 +- .github/workflows/main.yml | 999 +------------------------ .github/workflows/packaging.yml | 62 -- .github/workflows/post-merge-tests.yml | 425 ----------- 4 files changed, 14 insertions(+), 1505 deletions(-) delete mode 100644 .github/workflows/packaging.yml delete mode 100644 .github/workflows/post-merge-tests.yml diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml index 902de3ba955..4ef3fb9eb19 100644 --- a/.github/workflows/linters.yml +++ b/.github/workflows/linters.yml @@ -65,25 +65,16 @@ jobs: git config --global core.whitespace \ -cr-at-eol,tab-in-indent,blank-at-eol,blank-at-eof git diff --check ${{ github.event.pull_request.base.sha }} - example-data: - name: Check for unmerged example-data pull request - runs-on: ubuntu-latest - if: github.event_name == 'pull_request' - steps: - - uses: actions/checkout@v5 - name: Checkout the repository - with: - fetch-depth: 100 - persist-credentials: true - - - name: Find matching PR in example_data - env: - BASE_PR: ${{ github.event.number }} - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # Static type checking for Python interface + - name: Install mypy, basedpyright, and dependencies run: | - cd data/example_data - DATA_PR=$(gh pr list --repo Cantera/cantera-example-data --jq '.[] | select(.title | test("Cantera/cantera#'${BASE_PR}'")) | .number' --json title,number) - if [ -n "$DATA_PR" ]; then - echo ":exclamation: Merge https://github.com/Cantera/cantera-example-data/pull/${DATA_PR} and update the submodule commit before merging this PR" >> $GITHUB_STEP_SUMMARY - exit 1 - fi + python -m pip install --upgrade pip + pip install mypy basedpyright numpy ruamel.yaml pandas pandas-stubs \ + pint graphviz setuptools typing_extensions + - name: Run mypy + working-directory: interfaces/cython + run: mypy --config-file pyproject.toml --python-version 3.10 + - name: Run basedpyright + if: failure() || success() + working-directory: interfaces/cython + run: basedpyright --pythonversion 3.10 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 36c362e03d5..b81b1a820fa 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -26,86 +26,16 @@ concurrency: cancel-in-progress: true jobs: - matlab-test-ubuntu: - name: MATLAB on ubuntu-latest with Python 3.10 - runs-on: ubuntu-latest - env: - CANTERA_ROOT: ${{ github.workspace }} - CANTERA_DATA: ${{ github.workspace }}/data - steps: - - uses: actions/checkout@v5 - name: Checkout the repository - with: - submodules: recursive - persist-credentials: false - - name: Setup Python - uses: actions/setup-python@v6 - with: - python-version: '3.10' - architecture: x64 - - name: Install Apt dependencies - run: | - sudo apt update - sudo apt install libboost-dev gfortran libopenmpi-dev libopenblas-openmp-dev \ - libhdf5-dev - gcc --version - - name: Upgrade pip - run: python3 -m pip install -U pip setuptools wheel - - name: Install Python dependencies - run: | - python3 -m pip install ruamel.yaml scons==4.0.1 packaging - - name: Build Cantera with legacy CLib - run: | - python3 `which scons` build env_vars=all -j4 debug=n --debug=time \ - cc_flags=-D_GLIBCXX_ASSERTIONS clib_legacy=y - - name: Set LD_PRELOAD environment variable for MATLAB - run: | - LIB_STDCXX=$(ldconfig -p | grep libstdc++.so.6 | awk '{print $4}' | head -n 1) - echo "LD_PRELOAD=$LIB_STDCXX" >> $GITHUB_ENV - - name: Set up MATLAB - uses: matlab-actions/setup-matlab@39293085af7de88b5775bbeee38e6946b6709873 # v2.6.0 - - name: Build MATLAB C++ Interface - uses: matlab-actions/run-command@7102c36f8d9b02c376168901511f20260327693e # v2.3.0 - with: - command: | - ctDir = getenv('CANTERA_ROOT'); - includeDir = fullfile(ctDir, 'include'); - ctLibDir = fullfile(ctDir, 'build', 'lib'); - addpath(genpath(fullfile(ctDir, 'interfaces', 'matlab_experimental'))); - ctBuildInterface(ctDir, includeDir, ctLibDir); - - name: Run tests - uses: matlab-actions/run-tests@a80b208946040c701ae65c1bce73ba7ec4810757 # v2.1.2 - with: - select-by-folder: ${{ github.workspace }}/test/matlab_experimental - - ubuntu-multiple-pythons: name: ${{ matrix.os }} with Python ${{ matrix.python-version }}, Numpy ${{ matrix.numpy || 'latest' }}, Cython ${{ matrix.cython || 'latest' }} runs-on: ${{ matrix.os }} timeout-minutes: 60 strategy: matrix: - python-version: ['3.10', '3.11', '3.12', '3.13'] + python-version: ['3.10'] os: ['ubuntu-24.04'] numpy: [''] cython: ['!=3.1.2'] # Specifier can be dropped after 3.1.3 or 3.2.0 is released - include: - # Keep some test cases with NumPy 1.x until we drop support - - python-version: '3.12' - os: 'ubuntu-22.04' - numpy: "'<2.0'" - cython: '!=3.1.2' # Specifier can be dropped after 3.1.3 or 3.2.0 is released - # Keep some test cases with older Cython versions - - python-version: '3.10' - os: 'ubuntu-22.04' - cython: "==0.29.31" # minimum supported version - - python-version: '3.11' - os: 'ubuntu-22.04' - cython: "==3.0.8" # System version for Ubuntu 24.04 - - python-version: '3.13' - os: 'ubuntu-22.04' - numpy: "'<2.2'" - cython: '!=3.1.2' # Specifier can be dropped after 3.1.3 or 3.2.0 is released fail-fast: false steps: - uses: actions/checkout@v5 @@ -167,7 +97,7 @@ jobs: # Pin to 4.3.4 to resolve errors around only uploading symlinks. # See https://github.com/actions/upload-artifact/issues/589 uses: actions/upload-artifact@v4.3.4 - if: matrix.python-version == '3.11' + if: matrix.python-version == '3.10' with: path: build/lib/libcantera_shared.so name: libcantera_shared-${{ matrix.os }}.so @@ -193,246 +123,6 @@ jobs: name: cantera-wheel-${{ matrix.python-version }}-${{ matrix.os }} if-no-files-found: error - clang-compiler: - name: LLVM/Clang with Python 3.13 - runs-on: ubuntu-22.04 - timeout-minutes: 60 - steps: - - uses: actions/checkout@v5 - name: Checkout the repository - with: - submodules: recursive - persist-credentials: false - - name: Setup Python - uses: actions/setup-python@v6 - with: - python-version: '3.13' - architecture: x64 - - name: Install Apt dependencies - run: | - sudo apt update - sudo apt install libboost-dev gfortran libomp-dev libomp5 \ - libopenblas-openmp-dev libhdf5-dev doxygen graphviz - - name: Upgrade pip - run: python3 -m pip install -U pip setuptools wheel - - name: Install Python dependencies - run: python3 -m pip install ruamel.yaml scons numpy cython!=3.1.2 pandas pytest Jinja2 - pytest-xdist pytest-github-actions-annotate-failures pint graphviz - - name: Build Cantera - run: python3 `which scons` build env_vars=all - CXX=clang++-14 CC=clang-14 f90_interface=n extra_lib_dirs=/usr/lib/llvm/lib - -j4 debug=n --debug=time logging=debug - warning_flags='-Wall -Werror -Wsuggest-override' - - name: Build Tests - run: - python3 `which scons` -j4 build-tests --debug=time - - name: Run compiled tests - run: python3 `which scons` test-gtest test-legacy --debug=time - - name: Run Python tests - run: | - LD_LIBRARY_PATH=build/lib - python3 -m pytest -raP -v -n auto --durations=50 test/python - - name: Run googletests - run: python3 `which scons` test-clib --debug=time - - name: Run sample demo - run: python3 `which scons` test-clib-demo --debug=time - - macos-multiple-pythons: - name: ${{ matrix.macos-version }} with Python ${{ matrix.python-version }} - runs-on: ${{ matrix.macos-version }} - timeout-minutes: 90 - strategy: - matrix: - macos-version: ['macos-14', 'macos-15'] - python-version: ['3.12', '3.13'] - include: - - macos-version: 'macos-14' - python-version: '3.10' - extra-build-args: cxx_flags='-std=c++20' - - macos-version: 'macos-15' - python-version: '3.11' - # - macos-version: 'macos-15-intel' - # python-version: '3.1x' # sporadic failures: tracked in post-merge-tests - fail-fast: false - steps: - - uses: actions/checkout@v5 - name: Checkout the repository - with: - submodules: recursive - persist-credentials: false - - name: Write dependencies to a file for caching - run: | - echo "scons ruamel.yaml numpy cython!=3.1.2 pandas pytest pytest-xdist pytest-github-actions-annotate-failures pint graphviz Jinja2" | tr " " "\n" > requirements.txt - - name: Set up Python - uses: actions/setup-python@v6 - with: - python-version: ${{ matrix.python-version }} - cache: pip - cache-dependency-path: requirements.txt - - name: Install Brew dependencies - run: brew install --display-times boost libomp hdf5 doxygen - - name: Set Include folder - run: - echo "BOOST_INC_DIR=$(brew --prefix)/include" >> $GITHUB_ENV - - name: Upgrade pip - run: python -m pip install -U pip setuptools wheel - - name: Install Python dependencies - run: python -m pip install -r requirements.txt - - name: Build Cantera with legacy CLib - run: scons build env_vars=all -j3 debug=n --debug=time - boost_inc_dir=${BOOST_INC_DIR} ${{ matrix.extra-build-args }} clib_legacy=y - - name: Build Tests - run: scons -j3 build-tests --debug=time - - name: Run compiled tests - run: scons test-gtest test-legacy --debug=time - - name: Run Python tests - run: | - export DYLD_LIBRARY_PATH=build/lib - python3 -m pytest -raP -v -n auto --durations=50 test/python - - name: Rebuild Cantera with generated CLib - run: | - scons build clib_legacy=n -j3 - if: matrix.python-version == '3.12' && matrix.macos-version == 'macos-14' - - name: Run googletests for generated CLib - run: scons test-clib --debug=time - if: matrix.python-version == '3.12' && matrix.macos-version == 'macos-14' - - name: Upload shared library - uses: actions/upload-artifact@v4 - if: matrix.python-version == '3.12' && matrix.macos-version == 'macos-14' - with: - path: build/lib/libcantera_shared.dylib - name: libcantera_shared.dylib - retention-days: 2 - - # Coverage is its own job because macOS builds of the samples - # use Homebrew gfortran which is not compatible for coverage - # with XCode clang. Also, turning off optimization really - # slows down the tests - coverage: - name: Coverage - runs-on: ubuntu-latest - timeout-minutes: 90 - steps: - - uses: actions/checkout@v5 - name: Checkout the repository - with: - submodules: recursive - persist-credentials: false - - name: Setup python - uses: actions/setup-python@v6 - with: - python-version: '3.11' - architecture: x64 - - name: Install Apt dependencies - run: | - sudo apt update - sudo apt install libboost-dev gfortran libopenblas-openmp-dev libsundials-dev \ - libhdf5-dev doxygen - gcc --version - - name: Upgrade pip - run: python3 -m pip install -U pip setuptools wheel - - name: Install Python dependencies - run: | - python3 -m pip install ruamel.yaml scons numpy cython!=3.1.2 pandas scipy pytest \ - pytest-github-actions-annotate-failures pytest-cov gcovr!=7.0.0 pint graphviz \ - Jinja2 - - name: Setup .NET Core SDK - uses: actions/setup-dotnet@v5 - with: - dotnet-version: '8.x' - - name: Build Cantera with legacy CLib - run: | - python3 `which scons` build blas_lapack_libs=lapack,blas coverage=y \ - optimize=n skip_slow_tests=y no_optimize_flags='-DNDEBUG -O0' \ - FORTRANFLAGS='-O0' env_vars=all clib_legacy=y -j4 --debug=time - - name: Build Tests - run: python3 `which scons` -j4 build-tests --debug=time - - name: Run compiled tests - run: python3 `which scons` test-gtest test-legacy --debug=time - - name: Run Python tests - run: python3 `which scons` test-python show_long_tests=yes verbose_tests=yes - - name: Set environment variables for MATLAB - # MATLAB requires the legacy CLib - run: | - LIB_STDCXX=$(ldconfig -p | grep libstdc++.so.6 | awk '{print $4}' | head -n 1) - echo "LD_PRELOAD=$LIB_STDCXX" >> $GITHUB_ENV - - name: Set up MATLAB - uses: matlab-actions/setup-matlab@39293085af7de88b5775bbeee38e6946b6709873 # v2.6.0 - - name: Build MATLAB C++ Interface - uses: matlab-actions/run-command@7102c36f8d9b02c376168901511f20260327693e # v2.3.0 - env: - CANTERA_ROOT: ${{ github.workspace }} - CANTERA_DATA: ${{ github.workspace }}/data - with: - command: | - ctDir = getenv('CANTERA_ROOT'); - includeDir = fullfile(ctDir, 'include'); - ctLibDir = fullfile(ctDir, 'build', 'lib'); - addpath(genpath(fullfile(ctDir, 'interfaces', 'matlab_experimental'))); - ctBuildInterface(ctDir, includeDir, ctLibDir); - - name: Test the MATLAB interface and generate coverage report - uses: matlab-actions/run-command@7102c36f8d9b02c376168901511f20260327693e # v2.3.0 - env: - CANTERA_ROOT: ${{ github.workspace }} - CANTERA_DATA: ${{ github.workspace }}/data - with: - command: | - ctDir = getenv('CANTERA_ROOT'); - addpath(fullfile(ctDir, 'test', 'matlab_experimental')); - matlabCoverage; - - name: Rebuild Cantera with generated CLib - run: scons build clib_legacy=n -j4 - - name: Run googletests for generated CLib - run: scons test-clib --debug=time - - name: Build the .NET interface - run: dotnet build - working-directory: interfaces/dotnet - - name: Test the .NET interface - # Collect coverage info using Coverlet (identified by magic string below) - run: | - dotnet test --collect:"XPlat Code Coverage" - mv Cantera.Tests/TestResults/*/coverage.cobertura.xml . - dotnet new tool-manifest - dotnet tool install --local dotnet-reportgenerator-globaltool - dotnet reportgenerator -reports:"coverage.cobertura.xml" -targetdir:"coveragereport" -reporttypes:Html - working-directory: interfaces/dotnet - - name: Process coverage files - run: | - gcovr --root . --exclude-unreachable-branches --exclude-throw-branches \ - --exclude-directories '\.sconf_temp' --exclude-directories 'build/ext$' \ - --exclude '.*ext.*' --exclude '(.+/)?_cantera\.cpp$' --exclude '^test.*' \ - --xml coverage.xml --html-details htmlcoverage.html --txt - - name: Archive C++ coverage results - uses: actions/upload-artifact@v4 - with: - name: cxx-coverage-report - path: htmlcoverage* - retention-days: 5 - - name: Archive Python coverage results - uses: actions/upload-artifact@v4 - with: - name: python-coverage-report - path: build/python-coverage* - retention-days: 5 - - name: Archive .NET coverage results - uses: actions/upload-artifact@v4 - with: - name: dotnet-coverage-report - path: interfaces/dotnet/coveragereport* - retention-days: 5 - - name: Archive MATLAB coverage results - uses: actions/upload-artifact@v4 - with: - name: matlab-coverage-report - path: test/matlab_experimental/matlabCoverage* - retention-days: 5 - - name: Upload Coverage to Codecov - uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1 - with: - verbose: true - files: ./coverage.xml,./build/pycov.xml,./interfaces/dotnet/coverage.cobertura.xml,./test/matlab_experimental/matlabCoverage.xml - fail_ci_if_error: true - type-checking: name: Verify typing correctness and coverage of the public Python API runs-on: ubuntu-24.04 @@ -519,688 +209,3 @@ jobs: name: python-type-check-reports path: type_check/* retention-days: 5 - - docs: - name: Build docs - runs-on: ubuntu-latest - timeout-minutes: 60 - defaults: - run: - shell: bash -l {0} - steps: - - uses: actions/checkout@v5 - name: Checkout the repository - with: - submodules: recursive - persist-credentials: false - - name: Check out updated example data - if: github.event_name == 'pull_request' - env: - BASE_PR: ${{ github.event.number }} - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - cd data/example_data - DATA_PR=$(gh pr list --repo Cantera/cantera-example-data --jq '.[] | select(.title | test("Cantera/cantera#'${BASE_PR}'")) | .number' --json title,number) - if [ -n "$DATA_PR" ]; then - gh pr checkout --repo Cantera/cantera-example-data $DATA_PR - fi - - name: Set up micromamba - uses: mamba-org/setup-micromamba@add3a49764cedee8ee24e82dfde87f5bc2914462 # v2.0.7 - with: - environment-name: test-env - create-args: >- - python=3.11 doxygen=1.9.7 scons pip scikits.odes - post-cleanup: none - - name: Install Apt dependencies - run: | - sudo apt update - sudo apt install libboost-dev graphviz texlive-bibtex-extra \ - libopenblas-openmp-dev libhdf5-dev libfmt-dev libsundials-dev - - name: Upgrade pip - run: pip install -U pip setuptools wheel - - name: Install Python dependencies - # Pinned sphinx and pydata-sphinx-theme versions are for the Cantera 3.1.x - # series. These versions can be relaxed on main branch afterwards. - run: | - pip install ruamel.yaml scons numpy cython!=3.1.2 'sphinx>=7.3.7,<7.4' sphinx-gallery \ - sphinxcontrib-matlabdomain sphinxcontrib-doxylink sphinxcontrib-bibtex \ - 'pydata-sphinx-theme>=0.15.3,<0.16' sphinx-argparse sphinx_design myst-nb \ - sphinx-copybutton matplotlib pandas scipy pint coolprop graphviz - pip install "git+https://github.com/Cantera/sphinx-tags.git@main" - - name: Build Cantera - run: scons build -j4 debug=n optimize=y use_pch=n - - name: Build documentation - run: | - scons sphinx doxygen logging=debug \ - sphinx_options="-W --keep-going --warning-file=sphinx-warnings.txt" - - name: Show Sphinx warnings - run: | - cat sphinx-warnings.txt - if: failure() - - name: Ensure 'scons help' options work - run: | - scons help --options - scons help --list-options - scons help --option=prefix - - name: Create archive for docs output - run: | - cd build/doc - tar --exclude="*.map" --exclude="*.md5" -czf docs.tar.gz html - if: failure() || success() - - name: Store a copy of docs output - uses: actions/upload-artifact@v4 - with: - path: build/doc/docs.tar.gz - name: docs - retention-days: 14 - if: failure() || success() - - name: Determine whether to deploy - id: deploy-conf - if: github.event_name == 'push' && github.repository_owner == 'Cantera' - run: | - if [[ ${GITHUB_REF} =~ ^refs\/heads\/([0-9]+\.[0-9]+)$ ]]; then - echo "match=true" >> $GITHUB_OUTPUT - echo "rsync_dest=cantera/${BASH_REMATCH[1]}" >> $GITHUB_OUTPUT - echo "commit=true" >> $GITHUB_OUTPUT - echo "git_dest=${BASH_REMATCH[1]}" >> $GITHUB_OUTPUT - echo "git_branch=staging" >> $GITHUB_OUTPUT - elif [[ ${GITHUB_REF} == "refs/heads/main" ]]; then - echo "match=true" >> $GITHUB_OUTPUT - echo "rsync_dest=cantera/dev" >> $GITHUB_OUTPUT - elif [[ ${GITHUB_REF} == "refs/heads/testing" ]]; then - echo "match=true" >> $GITHUB_OUTPUT - echo "rsync_dest=testing.cantera.org/dev" >> $GITHUB_OUTPUT - echo "commit=true" >> $GITHUB_OUTPUT - echo "git_dest=testing" >> $GITHUB_OUTPUT - echo "git_branch=testing" >> $GITHUB_OUTPUT - fi - env: - GITHUB_REF: ${{ github.ref }} - # The known_hosts key is generated with `ssh-keygen -F cantera.org` from a - # machine that has previously logged in to cantera.org and trusts - # that it logged in to the right machine - - name: Set up SSH key and host for deploy - if: steps.deploy-conf.outputs.match == 'true' - uses: shimataro/ssh-key-action@d4fffb50872869abe2d9a9098a6d9c5aa7d16be4 # v2.7.0 - with: - key: ${{ secrets.CTDEPLOY_KEY }} - known_hosts: ${{ secrets.CTDEPLOY_HOST }} - - name: Commit docs to api-docs repo - if: steps.deploy-conf.outputs.commit == 'true' - env: - CANTERABOT_DOCS_TOKEN: ${{ secrets.CANTERABOT_DOCS_TOKEN }} - # Directory in api-docs repo: either the version (X.Y) or "testing" - GIT_DEST: ${{ steps.deploy-conf.outputs.git_dest }} - # Branch in api-docs to commit to: either 'staging' or 'testing' - BRANCH: ${{ steps.deploy-conf.outputs.git_branch }} - run: | - set -ex - REPO_SHA=`git rev-parse --short=8 HEAD` - git clone https://canterabot:${CANTERABOT_DOCS_TOKEN}@github.com/Cantera/api-docs - - cd api-docs - git config user.name "CanteraBot" - git config user.email "96191898+CanteraBot@users.noreply.github.com" - - git checkout --track origin/${BRANCH} - mkdir -p ${GIT_DEST} - tar -zxf ../build/doc/docs.tar.gz --strip-components=1 -C ${GIT_DEST} - git add . - git commit -m "Update for Cantera ${GIT_DEST} - Cantera/cantera@${REPO_SHA}" - git show --stat HEAD - git push origin - - name: Upload the docs to cantera.org - if: steps.deploy-conf.outputs.match == 'true' - env: - RSYNC_USER: "ctdeploy" - RSYNC_SERVER: "cantera.org" - RSYNC_DEST: ${{ steps.deploy-conf.outputs.rsync_dest }} - DOCS_OUTPUT_DIR: "./build/doc/html/" - run: | - rsync -avzP --checksum --exclude='*.map' --exclude='*.md5' \ - --delete --delete-excluded --filter='P .htaccess' \ - "${DOCS_OUTPUT_DIR}" ${RSYNC_USER}@${RSYNC_SERVER}:${RSYNC_DEST} - - run-examples: - name: Run Python ${{ matrix.python-version }} examples on ${{ matrix.os }}, NumPy ${{ matrix.numpy || 'latest' }} - runs-on: ${{ matrix.os }} - timeout-minutes: 60 - needs: ["ubuntu-multiple-pythons"] - strategy: - matrix: - # Keep some test cases with NumPy 1.x until we explicitly drop support - include: - - os: "ubuntu-22.04" - python-version: "3.10" - numpy: "" - libhdf5: "libhdf5-103" - - os: "ubuntu-24.04" - python-version: "3.10" - numpy: "==1.21.6" - libhdf5: "libhdf5-103-1t64" - - os: "ubuntu-22.04" - python-version: "3.11" - numpy: "==1.23.5" - libhdf5: "libhdf5-103" - - os: "ubuntu-24.04" - python-version: "3.11" - numpy: "" - libhdf5: "libhdf5-103-1t64" - - os: "ubuntu-22.04" - python-version: "3.12" - numpy: "==1.26.4" - libhdf5: "libhdf5-103" - - os: "ubuntu-24.04" - python-version: "3.12" - numpy: "" - libhdf5: "libhdf5-103-1t64" - - os: "ubuntu-22.04" - python-version: "3.13" - numpy: "" - libhdf5: "libhdf5-103" - fail-fast: false - steps: - # We're not building Cantera here, we only need the checkout for the samples - # folder, so no need to do a recursive checkout - - uses: actions/checkout@v5 - name: Checkout the repository - with: - persist-credentials: false - - name: Setup Python - uses: actions/setup-python@v6 - with: - python-version: ${{ matrix.python-version }} - architecture: x64 - - name: Install Apt dependencies - run: | - sudo apt update - sudo apt install graphviz ${{ matrix.libhdf5 }} libfmt-dev libopenblas0-openmp - - name: Download the wheel artifact - uses: actions/download-artifact@v5 - with: - name: cantera-wheel-${{ matrix.python-version }}-${{ matrix.os }} - path: dist - - name: Download the Cantera shared library (.so) - uses: actions/download-artifact@v5 - with: - name: libcantera_shared-${{ matrix.os }}.so - path: build/lib - - name: Upgrade pip - run: python3 -m pip install -U pip setuptools wheel - - name: Install Python dependencies - run: | - python3 -m pip install numpy${{ matrix.numpy }} ruamel.yaml pandas pyarrow \ - matplotlib scipy pint graphviz CoolProp - python3 -m pip install --pre --no-index --find-links dist cantera - - name: Run the examples - # See https://unix.stackexchange.com/a/392973 for an explanation of the -exec part. - # Skip 1D_packed_bed.py due to difficulty installing scikits.odes. - # Increase figure limit to handle flame_speed_convergence_analysis.py. - # Skip equations_of_state.py in cases where CoolProp is broken (temporarily, for Python 3.13) - run: | - ln -s libcantera_shared.so build/lib/libcantera_shared.so.3 - rm samples/python/reactors/1D_packed_bed.py - python3 -m CoolProp || rm samples/python/thermo/equations_of_state.py - echo "figure.max_open_warning: 100" > matplotlibrc - export LD_LIBRARY_PATH=build/lib - find samples/python -type f -iname "*.py" \ - -exec sh -c 'for n; do echo "$n" | tee -a results.txt && python3 "$n" >> results.txt || exit 1; done' sh {} + - env: - # The pyparsing ignore setting is due to a new warning introduced in Matplotlib==3.6.0 - # @todo: Remove the trapz-related ignore when dropping support for NumPy 1.x - # and replacing np.trapz with np.trapezoid - # Ignore NasaPoly2 warnings from n-hexane-NUIG-2015.yaml - PYTHONWARNINGS: "error,ignore:warn_name_set_on_empty_Forward::pyparsing,ignore:datetime.datetime.utcfromtimestamp:DeprecationWarning:,ignore:`trapz`:DeprecationWarning,ignore:NasaPoly2:UserWarning:" - MPLBACKEND: Agg - - name: Save the results file for inspection - uses: actions/upload-artifact@v4 - with: - path: results.txt - retention-days: 2 - name: example-results-${{ matrix.python-version }}-${{ matrix.os }}-np${{ matrix.numpy || 'latest' }} - # Run this step if the job was successful or failed, but not if it was cancelled - # Using always() would run this step if the job was cancelled as well. - if: failure() || success() - - multiple-sundials: - name: Sundials ${{ matrix.sundials-ver }} / fmt ${{ matrix.fmt-ver }} - runs-on: ubuntu-latest - timeout-minutes: 60 - env: - PYTHON_VERSION: '3.13' - defaults: - run: - shell: bash -l {0} - strategy: - matrix: - include: - - sundials-ver: 5.8 - fmt-ver: 9.1 - - sundials-ver: 6.4.1 - fmt-ver: 10 - - sundials-ver: 6.6 - fmt-ver: 10 - - sundials-ver: 7.2 - fmt-ver: 11 - extra-build-args: cxx_flags='-std=c++20' - fail-fast: false - steps: - - uses: actions/checkout@v5 - name: Checkout the repository - with: - submodules: recursive - persist-credentials: false - - name: Set up micromamba - uses: mamba-org/setup-micromamba@add3a49764cedee8ee24e82dfde87f5bc2914462 # v2.0.7 - with: - environment-name: test-env - # See https://github.com/conda-forge/boost-cpp-feedstock/issues/41 for why we - # use boost-cpp rather than boost from conda-forge - create-args: >- - python=${{ env.PYTHON_VERSION }} sundials=${{ matrix.sundials-ver }} scons - numpy ruamel.yaml cython!=3.1.2 boost-cpp fmt=${{ matrix.fmt-ver }} eigen yaml-cpp - pandas libgomp openblas pytest pytest-xdist highfive python-graphviz - setuptools Jinja2 doxygen - post-cleanup: none - - name: Build Cantera - run: | - scons build system_fmt=y system_eigen=y system_yamlcpp=y system_sundials=y \ - system_highfive=y blas_lapack_libs='lapack,blas' -j4 logging=debug debug=n \ - optimize_flags='-O3 -ffast-math -fno-finite-math-only' \ - ${{ matrix.extra-build-args}} - - name: Test Cantera - run: scons test-zeroD test-python-reactor show_long_tests=yes verbose_tests=yes - - name: Test Install - # spot-check installation locations - run: | - scons install - test -f ${CONDA_PREFIX}/lib/libcantera_shared.so - test -f ${CONDA_PREFIX}/include/cantera/base/Solution.h - test -f ${CONDA_PREFIX}/include/cantera_clib/ctreactor.h - test -f ${CONDA_PREFIX}/bin/ck2yaml - test -f ${CONDA_PREFIX}/share/cantera/data/gri30.yaml - test -d ${CONDA_PREFIX}/share/cantera/samples - test -d ${CONDA_PREFIX}/share/cantera/samples/clib - test -d ${CONDA_PREFIX}/share/cantera/samples/python - test -d ${CONDA_PREFIX}/lib/python${{ env.PYTHON_VERSION }}/site-packages/cantera - - name: Test Essentials - # ensure that Python package loads and converter scripts work - run: | - python -c 'import cantera as ct; import sys; sys.exit(0) if ct.__version__.startswith("3.1.0") else sys.exit(1)' - ck2yaml --input=test/data/h2o2.inp --output=h2o2-test.yaml - test -f h2o2-test.yaml - cti2yaml test/data/ch4_ion.cti ch4_ion-test.yaml - test -f ch4_ion-test.yaml - yaml2ck data/h2o2.yaml --mechanism=h2o2-test.ck - test -f h2o2-test.ck - - windows-2022: - name: Windows 2022, Python ${{ matrix.python-version }}, fmt ${{ matrix.fmt-ver }} - runs-on: windows-2022 - timeout-minutes: 60 - strategy: - matrix: - include: - - python-version: '3.13' - fmt-ver: '10' - - python-version: '3.10' - fmt-ver: '8.1' - - python-version: '3.11' - fmt-ver: '9.1' - - python-version: '3.12' - fmt-ver: '10.0' - - python-version: '3.13' - fmt-ver: '11.0' - extra-build-args: cxx_flags='/EHsc /std:c++20 /utf-8' - fail-fast: false - steps: - - uses: actions/checkout@v5 - name: Checkout the repository - with: - submodules: recursive - persist-credentials: false - - name: Set up micromamba - uses: mamba-org/setup-micromamba@add3a49764cedee8ee24e82dfde87f5bc2914462 # v2.0.7 - with: - environment-name: test-env - # See https://github.com/conda-forge/boost-cpp-feedstock/issues/41 for why we - # use boost-cpp rather than boost from conda-forge - # Install SCons >=4.4.0 to make sure that MSVC_TOOLSET_VERSION variable is present - create-args: >- - python=${{ matrix.python-version }} scons numpy cython!=3.1.2 ruamel.yaml boost-cpp - eigen yaml-cpp pandas pytest pytest-xdist highfive pint python-graphviz - fmt=${{ matrix.fmt-ver }} setuptools Jinja2 doxygen openblas - post-cleanup: none - init-shell: powershell - - name: Build Cantera with legacy CLib - run: scons build system_eigen=y system_yamlcpp=y system_highfive=y logging=debug - toolchain=msvc f90_interface=n debug=n ${{ matrix.extra-build-args }} - --debug=time clib_legacy=y -j4 blas_lapack_libs=openblas optimize=n - - name: Build Tests - run: scons -j4 build-tests --debug=time - - name: Run compiled tests - run: scons test-gtest test-legacy --debug=time - - name: Run Python tests - run: | - pytest -raP -v -n auto --durations=50 test/python - - name: Test Install - # spot-check installation locations - run: | - scons install - $paths = @('Library/bin/cantera_shared.dll', - 'Library/include/cantera/base/Solution.h', 'Scripts/ck2yaml.exe', - 'share/cantera/data/gri30.yaml', 'share/cantera/samples', - 'share/cantera/samples/python', 'Lib/site-packages/cantera') - Foreach ($path in $paths) { - if (-not (Test-Path $Env:CONDA_PREFIX/$path)) { - echo "$path not found in install directory" - exit 1 - } - } - - name: Test Essentials - # ensure that Python package loads and converter scripts work - run: | - python -c 'import cantera as ct; import sys; sys.exit(0) if ct.__version__.startswith("3.1.0") else sys.exit(1)' - ck2yaml --input=test/data/h2o2.inp --output=h2o2-test.yaml - if (-not (Test-Path h2o2-test.yaml)) { exit 1 } - cti2yaml test/data/ch4_ion.cti ch4_ion-test.yaml - if (-not (Test-Path ch4_ion-test.yaml)) { exit 1 } - yaml2ck data/h2o2.yaml --mechanism=h2o2-test.ck - if (-not (Test-Path h2o2-test.ck)) { exit 1 } - - name: Rebuild Cantera with generated CLib - run: | - scons build clib_legacy=n -j4 - if: matrix.python-version == '3.11' - - name: Run googletests for generated CLib - run: scons test-clib --debug=time - if: matrix.python-version == '3.11' - - name: Upload shared library - # Pin to 4.3.4 to resolve errors around only uploading symlinks. - # See https://github.com/actions/upload-artifact/issues/589 - uses: actions/upload-artifact@v4.3.4 - if: matrix.python-version == '3.11' - with: - path: build/lib/cantera_shared.dll - name: cantera_shared.dll - retention-days: 2 - - windows: - name: "Windows 2025, Python ${{ matrix.python-version }}, MSVC" - runs-on: windows-2025 - timeout-minutes: 60 - env: - BOOST_ROOT: ${{github.workspace}}/3rdparty/boost - BOOST_URL: https://github.com/boostorg/boost/releases/download/boost-1.87.0/boost-1.87.0-b2-nodocs.7z - strategy: - matrix: - python-version: ["3.10", "3.11", "3.12", "3.13"] - fail-fast: false - steps: - - uses: actions/checkout@v5 - name: Checkout the repository - with: - submodules: recursive - persist-credentials: false - - name: Set Up Python - uses: actions/setup-python@v6 - with: - python-version: ${{ matrix.python-version }} - architecture: x64 - check-latest: true - - name: Install Python dependencies - run: | - python -m pip install -U pip setuptools wheel - python -m pip install 'scons<4.4.0' pypiwin32 numpy ruamel.yaml cython!=3.1.2 pandas graphviz pytest pytest-xdist pytest-github-actions-annotate-failures typing_extensions - - name: Restore Boost cache - uses: actions/cache@v4 - id: cache-boost - with: - path: ${{env.BOOST_ROOT}} - key: boost-187-win - - name: Install Boost Headers - if: steps.cache-boost.outputs.cache-hit != 'true' - run: | - BOOST_ROOT=$(echo $BOOST_ROOT | sed 's/\\/\//g') - mkdir -p $BOOST_ROOT - curl --progress-bar --location --output $BOOST_ROOT/download.7z -L $BOOST_URL - 7z -o$BOOST_ROOT x $BOOST_ROOT/download.7z -y -bd boost-1.87.0/boost - mv $BOOST_ROOT/boost-1.87.0/boost $BOOST_ROOT/boost - rm $BOOST_ROOT/download.7z - shell: bash - - name: Build Cantera - run: scons build -j4 boost_inc_dir=$Env:BOOST_ROOT debug=n logging=debug - python_package=y env_vars=USERPROFILE,GITHUB_ACTIONS clib_legacy=y - f90_interface=n toolchain=msvc --debug=time optimize=n - - name: Build Tests - run: scons -j4 build-tests --debug=time - - name: Run compiled tests - run: scons test-gtest test-legacy --debug=time - - name: Run Python tests - run: python -m pytest -raP -v -n auto --durations=50 test/python - - # Adapted from https://www.scivision.dev/intel-oneapi-github-actions/ - linux-intel-oneapi: - name: intel-oneAPI on Ubuntu, Python 3.12 - runs-on: ubuntu-latest - timeout-minutes: 60 - env: - INTEL_REPO: https://apt.repos.intel.com - INTEL_KEY: GPG-PUB-KEY-INTEL-SW-PRODUCTS-2023.PUB - steps: - - name: Intel Apt repository - timeout-minutes: 1 - run: | - wget ${INTEL_REPO}/intel-gpg-keys/${INTEL_KEY} - sudo apt-key add ${INTEL_KEY} - rm ${INTEL_KEY} - echo "deb ${INTEL_REPO}/oneapi all main" | sudo tee /etc/apt/sources.list.d/oneAPI.list - sudo apt-get update - - name: Install Intel oneAPI - timeout-minutes: 15 - run: | - sudo apt-get install intel-oneapi-compiler-fortran intel-oneapi-compiler-dpcpp-cpp-and-cpp-classic \ - intel-oneapi-mpi intel-oneapi-mpi-devel intel-oneapi-mkl ninja-build libboost-dev libhdf5-dev \ - doxygen - - uses: actions/checkout@v5 - name: Checkout the repository - with: - submodules: recursive - persist-credentials: false - - name: Setup Python - uses: actions/setup-python@v6 - with: - python-version: '3.12' - architecture: x64 - - name: Upgrade pip - run: python3 -m pip install -U pip setuptools wheel - - name: Install Python dependencies - run: | - python3 -m pip install ruamel.yaml scons numpy cython!=3.1.2 pandas pytest \ - pytest-xdist pytest-github-actions-annotate-failures pint graphviz Jinja2 - - name: Setup Intel oneAPI environment - run: | - source /opt/intel/oneapi/setvars.sh - printenv >> $GITHUB_ENV - - name: Build Cantera - run: python3 `which scons` build env_vars=all CC=icx CXX=icpx -j4 debug=n - --debug=time f90_interface=n # FORTRAN=ifx - - name: Build Tests - run: python3 `which scons` -j4 build-tests --debug=time - - name: Run compiled tests - run: python3 `which scons` test-gtest test-legacy --debug=time - - name: Run Python tests - run: | - LD_LIBRARY_PATH=$LD_LIBRARY_PATH:build/lib - python3 -m pytest -raP -v -n auto --durations=50 test/python - - windows-mingw: - name: mingw on Windows, Python 3.10 - runs-on: windows-2022 - timeout-minutes: 120 # MinGW is slooooow - env: - BOOST_ROOT: ${{github.workspace}}/3rdparty/boost - BOOST_URL: https://github.com/boostorg/boost/releases/download/boost-1.87.0/boost-1.87.0-b2-nodocs.7z - steps: - - uses: actions/checkout@v5 - name: Checkout the repository - with: - submodules: recursive - persist-credentials: false - - name: Set Up Python - uses: actions/setup-python@v6 - with: - python-version: "3.10" - architecture: x64 - - name: Install Python dependencies - run: | - python -m pip install -U pip setuptools wheel - python -m pip install scons pypiwin32 numpy ruamel.yaml cython!=3.1.2 h5py pandas pytest pytest-xdist pytest-github-actions-annotate-failures pint graphviz - - name: Restore Boost cache - uses: actions/cache@v4 - id: cache-boost - with: - path: ${{env.BOOST_ROOT}} - key: boost-187-win - - name: Set up MinGW - uses: egor-tensin/setup-mingw@84c781b557efd538dec66bde06988d81cd3138cf # v2.2.0 - with: - platform: x64 - static: false - version: 12.2 - - name: Install Boost Headers - if: steps.cache-boost.outputs.cache-hit != 'true' - run: | - BOOST_ROOT=$(echo $BOOST_ROOT | sed 's/\\/\//g') - mkdir -p $BOOST_ROOT - curl --progress-bar --location --output $BOOST_ROOT/download.7z -L $BOOST_URL - 7z -o$BOOST_ROOT x $BOOST_ROOT/download.7z -y -bd boost-1.87.0/boost - mv $BOOST_ROOT/boost-1.87.0/boost $BOOST_ROOT/boost - rm $BOOST_ROOT/download.7z - shell: bash - - name: Build Cantera - run: scons build -j4 boost_inc_dir=$Env:BOOST_ROOT debug=n logging=debug - python_package=y env_vars=USERPROFILE,PYTHONPATH,GITHUB_ACTIONS - toolchain=mingw f90_interface=n --debug=time clib_legacy=y - - name: Upload Wheel - uses: actions/upload-artifact@v4 - with: - path: build\python\dist\cantera*.whl - name: cantera-win_amd64.whl - retention-days: 2 - - name: Build Tests - run: scons -j4 build-tests --debug=time - - name: Run compiled tests - run: scons test-gtest test-legacy --debug=time - - name: Run Python tests - run: python -m pytest -raP -v -n auto --durations=50 test/python - - name: Upload Test binaries - if: always() - uses: actions/upload-artifact@v4 - with: - path: | - build/test/**/*.exe - build/lib/*.dll - name: mingw-gtest-binaries - retention-days: 2 - - dotnet: - name: .NET on ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-22.04, windows-2022, macos-14] - fail-fast: false - runs-on: ${{ matrix.os }} - needs: [ubuntu-multiple-pythons, macos-multiple-pythons, windows-2022] - timeout-minutes: 60 - steps: - - uses: actions/checkout@v5 - name: Checkout the repository - with: - persist-credentials: false - - name: Set Python version (Windows) - uses: actions/setup-python@v6 - with: - python-version: "3.10" - architecture: x64 - if: matrix.os == 'windows-2022' - - name: Set Python version (macOS) - uses: actions/setup-python@v6 - with: - python-version: "3.12" - if: matrix.os == 'macos-14' - # Ubuntu uses default system Python 3 - - name: Install library dependencies with micromamba (Windows) - uses: mamba-org/setup-micromamba@add3a49764cedee8ee24e82dfde87f5bc2914462 # v2.0.7 - with: - environment-name: test-env - # fmt needs to match the version of the windows-2022 runner selected to upload - # the cantera_shared.dll artifact - create-args: >- - yaml-cpp openblas highfive fmt=9.1 - init-shell: bash powershell - post-cleanup: none - if: matrix.os == 'windows-2022' - - name: Install Brew dependencies (macOS) - run: brew install --display-times hdf5 - if: matrix.os == 'macos-14' - - name: Install Apt dependencies (Ubuntu) - run: | - sudo apt update - sudo apt install libhdf5-dev libfmt-dev libopenblas0-openmp - if: matrix.os == 'ubuntu-22.04' - - name: Install Python dependencies - # Install Python dependencies, which are used by 'dotnet build' - run: | - python3 -m pip install --upgrade pip setuptools wheel build - python3 -m pip install ruamel.yaml Jinja2 typing-extensions - - name: Setup .NET Core SDK - uses: actions/setup-dotnet@v5 - with: - dotnet-version: '8.x' - - name: Download the Cantera shared library (.so) - uses: actions/download-artifact@v5 - with: - name: libcantera_shared-ubuntu-22.04.so - path: build/lib - - name: Download the Cantera shared library (.dylib) - uses: actions/download-artifact@v5 - with: - name: libcantera_shared.dylib - path: build/lib - - name: Download the Cantera shared library (.dll) - uses: actions/download-artifact@v5 - with: - name: cantera_shared.dll - path: build/lib - - name: Download the Doxygen artifacts - uses: actions/download-artifact@v5 - with: - name: doxygen-tree - path: build/doc - - name: Extract Doxygen artifacts - run: tar -xzf doxygen.tar.gz - working-directory: build/doc - - name: Build the .NET interface - run: dotnet build - working-directory: interfaces/dotnet - - name: Test the .NET interface - run: dotnet test - working-directory: interfaces/dotnet - - name: Run the .NET samples - run: | - dotnet run --project examples/Application - dotnet run --project examples/SoundSpeed - working-directory: interfaces/dotnet - - name: Clean .NET - run: | - dotnet clean - if [ -e Cantera/obj/sourcegen/* ]; then - echo 'Cantera/obj/sourcegen not cleaned as expected!' >&2 - exit 1 - fi - shell: bash - working-directory: interfaces/dotnet - - name: Install sourcegen - run: python -m pip install -e interfaces/sourcegen - - name: Test YAML output generation - run: sourcegen --api=yaml --output=build/yaml -v diff --git a/.github/workflows/packaging.yml b/.github/workflows/packaging.yml deleted file mode 100644 index 85bcd336200..00000000000 --- a/.github/workflows/packaging.yml +++ /dev/null @@ -1,62 +0,0 @@ -name: Packaging Tests -on: - workflow_dispatch: # allow manual triggering of this workflow - inputs: - outgoing_ref: - description: "The ref to be built. Can be a tag, commit hash, or branch name" - required: true - default: "main" - upload_to_pypi: - description: "Try to upload wheels and sdist to PyPI after building" - required: false - default: "false" - push: - # Run on tags that look like releases - tags: - - v* - # Run when main is pushed to - branches: - - main - pull_request: - # Run on pull requests when this file is modified - branches: - - main - paths: - - .github/workflows/packaging.yml - -permissions: {} - -jobs: - build-packages: - name: Trigger building packages from external repos - runs-on: ubuntu-22.04 - steps: - - name: Set up the job - id: setup - run: | - if [ -n "${OUTGOING_REF}" ]; then - echo "REF=${OUTGOING_REF}" >> $GITHUB_OUTPUT - else - echo "REF=${GITHUB_REF}" >> $GITHUB_OUTPUT - fi - if [ -n "${UPLOAD_TO_PYPI}" ]; then - echo "UPLOAD_TO_PYPI=${UPLOAD_TO_PYPI}" >> $GITHUB_OUTPUT - elif [[ "${GITHUB_REF}" == refs/tags* ]]; then - echo "UPLOAD_TO_PYPI=true" >> $GITHUB_OUTPUT - else - echo "UPLOAD_TO_PYPI=false" >> $GITHUB_OUTPUT - fi - env: - OUTGOING_REF: ${{ github.event.inputs.outgoing_ref }} - UPLOAD_TO_PYPI: ${{ github.event.inputs.upload_to_pypi }} - GITHUB_REF: ${{ github.ref }} - - name: Trigger PyPI/Wheel builds - run: > - gh workflow run -R cantera/pypi-packages - python-package.yml - -f incoming_ref=${REF} - -f upload=${UPLOAD_TO_PYPI} - env: - GITHUB_TOKEN: ${{ secrets.PYPI_PACKAGE_PAT }} - REF: ${{ steps.setup.outputs.REF }} - UPLOAD_TO_PYPI: ${{ steps.setup.outputs.UPLOAD_TO_PYPI }} diff --git a/.github/workflows/post-merge-tests.yml b/.github/workflows/post-merge-tests.yml deleted file mode 100644 index edd94b4285d..00000000000 --- a/.github/workflows/post-merge-tests.yml +++ /dev/null @@ -1,425 +0,0 @@ -name: Post-merge Tests -on: - workflow_dispatch: # allow manual triggering of this workflow - inputs: - outgoing_ref: - description: "The ref to be built. Can be a tag, commit hash, or branch name" - required: true - default: "main" - push: - # Run when the main branch is pushed to - branches: - - main - # Run on pull requests when this file is modified - pull_request: - branches: - - main - paths: - - .github/workflows/post-merge-tests.yml - -permissions: - contents: read - -env: - UV_INDEX: https://pypi.anaconda.org/scientific-python-nightly-wheels/simple - REQUIREMENTS_TXT_FILE: "platform/ci_support/post_merge_reqs.txt" - -jobs: - prerelease-dependencies: - name: Pre-release ${{ matrix.name }} - runs-on: ubuntu-24.04 - timeout-minutes: 60 - strategy: - matrix: - name: [cython, eigen, libfmt, SUNDIALS, yaml-cpp, HighFive, SCons] - fail-fast: false - steps: - - uses: actions/checkout@v5 - name: Checkout the repository - with: - submodules: recursive - persist-credentials: false - - name: Setup Python - uses: actions/setup-python@v6 - with: - python-version: 3.13 - - name: Install uv - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0 - with: - python-version: 3.13 - cache-dependency-glob: | - ${{ env.REQUIREMENTS_TXT_FILE }} - enable-cache: true - - name: Install Apt dependencies - run: | - sudo apt update - sudo apt install libboost-dev libopenblas-dev libhdf5-dev doxygen - - name: Install Python dependencies - run: | - uv venv --python 3.13 --seed - uv pip install -r "${REQUIREMENTS_TXT_FILE}" - - name: Install pre-release Cython - if: matrix.name == 'cython' - run: | - uv pip uninstall cython - uv pip install --prerelease=allow --only-binary=":all:" cython - - name: Install pre-release Eigen - if: matrix.name == 'eigen' - run: | - git fetch origin - git reset --hard origin/HEAD - working-directory: ext/eigen - - name: Build prerelease libfmt - if: matrix.name == 'libfmt' - run: | - echo "system_fmt = 'y'" >> cantera.conf - echo "extra_inc_dirs = 'fmt/install/include'" >> cantera.conf - echo "extra_lib_dirs = 'fmt/install/lib'" >> cantera.conf - git clone https://github.com/fmtlib/fmt - mkdir fmt/build - cd fmt/build - cmake -DCMAKE_INSTALL_PREFIX=../install -DBUILD_SHARED_LIBS=Y .. - make -j4 - make install - - name: Build prerelease SUNDIALS - if: matrix.name == 'SUNDIALS' - run: | - echo "system_sundials = 'y'" >> cantera.conf - echo "extra_inc_dirs = 'sundials/install/include'" >> cantera.conf - echo "extra_lib_dirs = 'sundials/install/lib'" >> cantera.conf - git clone https://github.com/LLNL/sundials - cd sundials - git switch develop - mkdir build - cd build - cmake -DCMAKE_INSTALL_PREFIX=../install -DBUILD_SHARED_LIBS=Y .. - make -j4 - make install - - name: Build prerelease yaml-cpp - if: matrix.name == 'yaml-cpp' - run: | - echo "system_yamlcpp = 'y'" >> cantera.conf - echo "extra_inc_dirs = 'yaml-cpp/install/include'" >> cantera.conf - echo "extra_lib_dirs = 'yaml-cpp/install/lib'" >> cantera.conf - git clone https://github.com/jbeder/yaml-cpp - cd yaml-cpp - mkdir build - cd build - cmake -DCMAKE_INSTALL_PREFIX=../install -DBUILD_SHARED_LIBS=Y .. - make -j4 - make install - - name: Build prerelease HighFive - if: matrix.name == 'HighFive' - run: | - echo "system_highfive = 'y'" >> cantera.conf - echo "extra_inc_dirs = 'highfive/install/include'" >> cantera.conf - git clone --recursive https://github.com/highfive-devs/highfive - cd highfive - mkdir build - cd build - cmake -DCMAKE_INSTALL_PREFIX=../install -DHIGHFIVE_EXAMPLES=N \ - -DHIGHFIVE_UNIT_TESTS=N -DHIGHFIVE_BUILD_DOCS=N .. - make - make install - - name: Install prerelease SCons - if: matrix.name == 'SCons' - run: | - uv pip uninstall scons - git clone https://github.com/SCons/scons - cd scons - uv pip install -e . - - name: Build Cantera - run: uv run scons build env_vars=all f90_interface=n -j4 debug=n - --debug=time logging=debug python_package=y - - name: Build Tests - run: uv run scons -j4 build-tests - - name: Run compiled tests - run: uv run scons test-gtest test-legacy --debug=time - - name: Run Python tests - run: uv run pytest -raP -v -n auto --durations=50 test/python - env: - LD_LIBRARY_PATH: "${LD_LIBRARY_PATH}:build/lib" - PYTHONPATH: build/python - - debuntu-docker: - name: Docker '${{ matrix.image }}' image with Sundials MPI ${{ matrix.mpi }} - strategy: - matrix: - include: - - image: "ubuntu:devel" - options: "" - mpi: override # fall back to bundled Sundials (system_sundials=default) - - image: "ubuntu:rolling" - options: "system_sundials=y CC=mpicc CXX=mpicxx FORTRAN=mpifort" - mpi: enabled # use MPI compiler wrappers - - image: "debian:stable" - options: "system_sundials=y CC=mpicc CXX=mpicxx FORTRAN=mpifort" - mpi: enabled # use MPI compiler wrappers - - image: "debian:testing" - options: "system_sundials=y CC=mpicc CXX=mpicxx FORTRAN=mpifort" - mpi: enabled # use MPI compiler wrappers - - image: "debian:unstable" - options: "system_sundials=y CC=mpicc CXX=mpicxx FORTRAN=mpifort" - mpi: enabled # use MPI compiler wrappers - fail-fast: false - runs-on: ubuntu-latest - timeout-minutes: 60 - container: - image: ${{ matrix.image }} # zizmor: ignore[unpinned-images] - steps: - - name: Install git on ${{ matrix.image }} - run: | - apt update -y - apt install -y --no-install-recommends git ca-certificates - git config --global init.defaultBranch main - git config --global --add safe.directory /__w/cantera/cantera - - uses: actions/checkout@v5 - name: Checkout the repository - with: - submodules: recursive - persist-credentials: false - - name: Install Apt dependencies - # Use packages from ubuntu image when possible - run: | - apt install -y --no-install-recommends python3 python3-pip pipenv scons \ - build-essential libboost-dev gfortran libopenmpi-dev libpython3-dev \ - libblas-dev liblapack-dev libhdf5-dev libfmt-dev libyaml-cpp-dev \ - libgtest-dev libgmock-dev libeigen3-dev libsundials-dev \ - openmpi-bin libopenmpi-dev \ - cython3 python3-numpy python3-pandas python3-pint python3-graphviz \ - python3-ruamel.yaml python3-setuptools python3-wheel python3-pytest \ - python3-pytest-xdist doxygen python3-jinja2 - gcc --version - - name: Install Python dependencies - run: | - pipenv install pytest-github-actions-annotate-failures - - name: Build Cantera - run: | - scons build env_vars=all -j4 debug=n --debug=time logging=debug \ - system_eigen=y system_fmt=y ${{ matrix.options }} \ - system_yamlcpp=y system_blas_lapack=y hdf_support=y \ - cc_flags=-D_GLIBCXX_ASSERTIONS python_package=y f90_interface=y - - name: Build Tests - run: scons -j4 build-tests - - name: Run compiled tests - run: scons test-gtest test-legacy --debug=time - - name: Run Python tests - run: python3 -m pytest -raP -v -n auto --durations=50 test/python - env: - LD_LIBRARY_PATH: "${LD_LIBRARY_PATH}:build/lib" - PYTHONPATH: build/python - - fedora-docker: - name: Docker 'fedora:${{ matrix.image }}' image - strategy: - matrix: - image: [rawhide, latest] - fail-fast: false - runs-on: ubuntu-latest - timeout-minutes: 60 - container: - image: fedora:${{ matrix.image }} - steps: - - name: Install git on fedora:${{ matrix.image }} - run: | - dnf install -y git - git config --global init.defaultBranch main - git config --global --add safe.directory /__w/cantera/cantera - - uses: actions/checkout@v5 - name: Checkout the repository - with: - persist-credentials: false - - name: Install dependencies - # Use packages from Fedora - run: | - dnf install -y boost-devel eigen3-devel fmt-devel gcc gcc-c++ \ - gcc-fortran gmock-devel gtest-devel python3 python3-cython \ - python3-devel python3-numpy python3-pandas python3-pint python3-pip \ - python3-pytest python3-pytest-xdist python3-ruamel-yaml python3-scipy \ - python3-scons python3-wheel sundials-devel yaml-cpp-devel hdf5-devel \ - highfive-devel python3-graphviz python3-packaging python3-setuptools \ - python3-jinja2 doxygen - - name: Build Cantera - run: | - scons build -j4 debug=n --debug=time logging=debug python_package=y \ - f90_interface=y extra_inc_dirs=/usr/include/eigen3 libdirname=/usr/lib64 \ - system_eigen=y system_fmt=y system_blas_lapack=y system_sundials=y \ - system_yamlcpp=y system_blas_lapack=y hdf_support=y - # note: 'system_highfive=y' is omitted as the current packaged version is too old; - # once newer version is available in 'latest', this should be tested as well - - name: Build Tests - run: scons -j4 build-tests - - name: Run compiled tests - run: scons test-gtest test-legacy --debug=time - - name: Run Python tests - run: python3 -m pytest -raP -v -n auto --durations=50 test/python - env: - LD_LIBRARY_PATH: build/lib - PYTHONPATH: build/python - - ubuntu-python-prerelease: - name: ${{ matrix.os }} with Python ${{ matrix.python-version }} - runs-on: ${{ matrix.os }} - timeout-minutes: 60 - strategy: - matrix: - python-version: ['3.14'] - os: ['ubuntu-22.04', 'ubuntu-24.04'] - fail-fast: false - steps: - - uses: actions/checkout@v5 - name: Checkout the repository - with: - submodules: recursive - persist-credentials: false - - name: Setup Python - uses: actions/setup-python@v6 - with: - python-version: ${{ matrix.python-version }} - allow-prereleases: true - - name: Install uv - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0 - with: - python-version: ${{ matrix.python-version }} - enable-cache: true - cache-dependency-glob: | - ${{ env.REQUIREMENTS_TXT_FILE }} - - name: Install Apt dependencies - run: | - sudo apt update - sudo apt install libboost-dev gfortran libopenmpi-dev \ - libblas-dev liblapack-dev libhdf5-dev libfmt-dev doxygen \ - libopenblas-dev - gcc --version - - name: Install Python dependencies - run: | - uv venv --python ${{ matrix.python-version }} --seed - sed -i 's/cython.*\|pandas\|numpy\|scipy//g' "${REQUIREMENTS_TXT_FILE}" - uv pip install -r "${REQUIREMENTS_TXT_FILE}" - uv pip install --prerelease=allow numpy cython pandas scipy - - name: Build Cantera - run: | - uv run scons build env_vars=all -j4 debug=n --debug=time logging=debug \ - system_fmt=y system_blas_lapack=y hdf_support=y cc_flags=-D_GLIBCXX_ASSERTIONS \ - python_package=y - - name: Build Tests - run: uv run scons -j4 build-tests - - name: Run compiled tests - run: uv run scons test-gtest test-legacy --debug=time - - name: Run Python tests - run: uv run pytest -raP -v -n auto --durations=50 test/python - env: - LD_LIBRARY_PATH: "${LD_LIBRARY_PATH}:build/lib" - PYTHONPATH: build/python - - macos-homebrew: - name: Homebrew Python/GCC on ${{ matrix.os }} - runs-on: ${{ matrix.os }} - timeout-minutes: 90 - strategy: - matrix: - os: ['macos-14', 'macos-15'] - fail-fast: false - steps: - - uses: actions/checkout@v5 - name: Checkout the repository - with: - submodules: recursive - persist-credentials: false - - name: Setup uv - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0 - with: - enable-cache: true - cache-dependency-glob: | - ${{ env.REQUIREMENTS_TXT_FILE }} - # - NumPy is installed here for the Python custom rate tests. - # - Do not install brew SUNDIALS because it requires open-mpi and the brew open-mpi - # package uses the system Clang to provide mpicc/mpicxx. - # - Do not install brew dependencies for C++ because these are built with libc++ - # while GCC will use libstdc++, and combining these will lead to linker errors - - name: Install Brew dependencies - run: - brew install --display-times boost libomp hdf5 python numpy doxygen gcc eigen - # This is necessary because of PEP 668 https://peps.python.org/pep-0668/ - # PEP 668 prohibits installation to system Python packages via pip - - name: Create a virtualenv for dependencies - run: | - uv venv --python "$(brew --prefix)/bin/python3" --seed - - name: Install Python dependencies - # SCons must be installed into the virtualenv because packaging is a dependency - # and we can't install packaging for the homebrew Python - run: | - uv pip install -r "${REQUIREMENTS_TXT_FILE}" - - name: Build Cantera - run: | - BREW_PREFIX=`brew --prefix` - GCC_VER=`brew info gcc --json | jq -r '.[0]["installed"][0]["version"] | split(".")[0]'` - echo "CC = '$BREW_PREFIX/bin/gcc-$GCC_VER'" >> cantera.conf - echo "CXX = '$BREW_PREFIX/bin/g++-$GCC_VER'" >> cantera.conf - echo "AR = '$BREW_PREFIX/bin/gcc-ar-$GCC_VER'" >> cantera.conf - echo "extra_inc_dirs = '${BREW_PREFIX}/include'" >> cantera.conf - echo "extra_lib_dirs = '${BREW_PREFIX}/lib'" >> cantera.conf - uv run scons build env_vars=all -j3 debug=n --debug=time logging=debug \ - python_package=y system_eigen=y - - name: Build Tests - run: uv run scons -j3 build-tests - - name: Run compiled tests - run: uv run scons test-gtest test-legacy --debug=time - - name: Run Python tests - run: uv run pytest -raP -v -n auto --durations=50 test/python - env: - DYLD_LIBRARY_PATH: "${DYLD_LIBRARY_PATH}:build/lib" - PYTHONPATH: build/python - - macos-sporadic-failures: - # exact clone of 'macos-multiple-pythons' runner on main.yml - # used to test status of GH issue Cantera/cantera#1916 - name: ${{ matrix.macos-version }} with Python ${{ matrix.python-version }} - runs-on: ${{ matrix.macos-version }} - timeout-minutes: 90 - strategy: - matrix: - macos-version: ['macos-15-intel'] - python-version: ['3.10', '3.11', '3.12', '3.13', '3.14'] - fail-fast: false - steps: - - uses: actions/checkout@v5 - name: Checkout the repository - with: - submodules: recursive - persist-credentials: false - - name: Write dependencies to a file for caching - run: | - echo "scons ruamel.yaml numpy cython!=3.1.2 pandas pytest pytest-xdist pytest-github-actions-annotate-failures pint graphviz Jinja2" | tr " " "\n" > requirements.txt - - name: Set up Python - uses: actions/setup-python@v6 - with: - python-version: ${{ matrix.python-version }} - cache: pip - cache-dependency-path: requirements.txt - allow-prereleases: true - - name: Install Brew dependencies - run: brew install --display-times boost libomp hdf5 doxygen - - name: Set Include folder - run: - echo "BOOST_INC_DIR=$(brew --prefix)/include" >> $GITHUB_ENV - - name: Upgrade pip - run: python -m pip install -U pip setuptools wheel - - name: Install Python dependencies - run: python -m pip install -r requirements.txt - - name: Build Cantera with legacy CLib - run: scons build env_vars=all -j3 debug=n --debug=time - boost_inc_dir=${BOOST_INC_DIR} clib_legacy=y - - name: Build Tests - run: scons -j3 build-tests --debug=time - - name: Run compiled tests - run: scons test-gtest test-legacy --debug=time - - name: Run Python tests - run: | - export DYLD_LIBRARY_PATH=build/lib - python3 -m pytest -raP -v -n auto --durations=50 test/python - - name: Rebuild Cantera with generated CLib - run: | - scons build clib_legacy=n -j3 - - name: Run googletests for generated CLib - run: scons test-clib --debug=time