From 2d6c15ae22401ac568383b1640bd166caa1381df Mon Sep 17 00:00:00 2001 From: Ray Speth Date: Sat, 30 Jul 2022 15:51:33 -0400 Subject: [PATCH 1/9] Avoid treating simple strings as format strings --- include/cantera/base/AnyMap.h | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/include/cantera/base/AnyMap.h b/include/cantera/base/AnyMap.h index e182fa838e..b20045fd89 100644 --- a/include/cantera/base/AnyMap.h +++ b/include/cantera/base/AnyMap.h @@ -709,8 +709,9 @@ class InputFileError : public CanteraError const std::string& message, const Args&... args) : CanteraError( procedure, - formatError(fmt::format(message, args...), - node.m_line, node.m_column, node.m_metadata)) + formatError( + (sizeof...(args) == 0) ? message : fmt::format(message, args...), + node.m_line, node.m_column, node.m_metadata)) { } @@ -723,13 +724,13 @@ class InputFileError : public CanteraError const Args&... args) : CanteraError( procedure, - formatError2(fmt::format(message, args...), - node1.m_line, node1.m_column, node1.m_metadata, - node2.m_line, node2.m_column, node2.m_metadata)) + formatError2( + (sizeof...(args) == 0) ? message : fmt::format(message, args...), + node1.m_line, node1.m_column, node1.m_metadata, + node2.m_line, node2.m_column, node2.m_metadata)) { } - virtual std::string getClass() const { return "InputFileError"; } From 5994e30d66f71fa03f2c3666faec36f1daed36a2 Mon Sep 17 00:00:00 2001 From: Ray Speth Date: Sat, 30 Jul 2022 15:58:09 -0400 Subject: [PATCH 2/9] Fix duplication of input context in InputFileError --- src/base/Units.cpp | 2 ++ test/general/test_units.cpp | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/base/Units.cpp b/src/base/Units.cpp index 73822cdb9d..b2520bc52a 100644 --- a/src/base/Units.cpp +++ b/src/base/Units.cpp @@ -626,6 +626,8 @@ double UnitSystem::convert(const AnyValue& v, const std::string& dest) const { try { return convert(v, Units(dest)); + } catch (InputFileError&) { + throw; // already have input file context from convert(AnyValue, Units) } catch (CanteraError& err) { throw InputFileError("UnitSystem::convert", v, err.getMessage()); } diff --git a/test/general/test_units.cpp b/test/general/test_units.cpp index 056d1027fc..06cd00222a 100644 --- a/test/general/test_units.cpp +++ b/test/general/test_units.cpp @@ -1,8 +1,10 @@ #include "gtest/gtest.h" +#include "gmock/gmock.h" #include "cantera/base/Units.h" #include "cantera/base/AnyMap.h" using namespace Cantera; +using namespace ::testing; TEST(Units, from_string) { EXPECT_EQ(Units("").str(), "1"); @@ -205,6 +207,28 @@ TEST(Units, from_anymap_default) { EXPECT_DOUBLE_EQ(m.convert("h1", "J/kmol", 999), 999); } +TEST(Units, from_anymap_bad) { + AnyMap m = AnyMap::fromYamlString( + "{p: 12 bar, v: 10, A: 1 cm^2, V: 1," + " k1: [5e2, 2, 29000], k2: [1e14, -1, 1300 cal/kmol]}"); + UnitSystem U({"mm", "min", "atm"}); + m.setUnits(U); + m.applyUnits(); + + try { + m.convert("p", "kg"); + FAIL() << "did not throw"; + } catch (InputFileError& err) { + EXPECT_THAT(err.what(), HasSubstr("Error on line")); + #ifdef GTEST_USES_POSIX_RE + EXPECT_THAT(err.what(), + Not(ContainsRegex("Error on line.*\\\nError on line"))); + #endif + } catch (...) { + FAIL() << "threw wrong exception type"; + } +} + TEST(Units, to_anymap) { UnitSystem U{"kcal", "mol", "cm"}; AnyMap m; From e6f37073c6986ad2c4fef0b0d2134ac7535a8860 Mon Sep 17 00:00:00 2001 From: Ray Speth Date: Fri, 29 Jul 2022 13:26:44 -0400 Subject: [PATCH 3/9] Make volume of SurfPhase consistent with h = u + P*v Defines molar volume to be zero. Density remains defined as being per unit area or length, depending on the dimensionality of the phase. Also fixes setting surface composition using mass/mole fractions so that the sum of concentrations always equals the site density. Partially resolves #1312 --- include/cantera/thermo/LatticeSolidPhase.h | 2 +- include/cantera/thermo/Phase.h | 14 +++++++++---- include/cantera/thermo/SurfPhase.h | 20 +++++++++++++++++-- interfaces/cython/cantera/thermo.pyx | 5 ++++- src/thermo/SurfPhase.cpp | 10 ++++++++-- test/data/consistency-cases.yaml | 2 -- test/python/test_thermo.py | 4 ++++ test/python/utilities.py | 2 ++ test/thermo/thermoToYaml.cpp | 8 ++++++-- test_problems/diamondSurf/runDiamond.cpp | 17 ++-------------- .../diamondSurf/runDiamond_blessed.out | 20 +++++++++---------- test_problems/fortran/f90_demo_blessed.txt | 18 ++++++++--------- 12 files changed, 74 insertions(+), 48 deletions(-) diff --git a/include/cantera/thermo/LatticeSolidPhase.h b/include/cantera/thermo/LatticeSolidPhase.h index 0de31b4279..67a7ac1b2a 100644 --- a/include/cantera/thermo/LatticeSolidPhase.h +++ b/include/cantera/thermo/LatticeSolidPhase.h @@ -311,7 +311,7 @@ class LatticeSolidPhase : public ThermoPhase throw NotImplementedError("LatticeSolidPhase::getConcentrations"); } - virtual doublereal concentration(int k) const { + virtual doublereal concentration(size_t k) const { throw NotImplementedError("LatticeSolidPhase::concentration"); } diff --git a/include/cantera/thermo/Phase.h b/include/cantera/thermo/Phase.h index e6d3e036c4..5d7c64431a 100644 --- a/include/cantera/thermo/Phase.h +++ b/include/cantera/thermo/Phase.h @@ -65,6 +65,12 @@ class Species; * An example of this is the function * Phase::setState_TRY(double t, double dens, const double* y). * + * For bulk (3-dimensional) phases, the mass density has units of kg/m^3, and the molar + * density and concentrations have units of kmol/m^3, and the units listed in the + * methods of the Phase class assume a bulk phase. However, for surface (2-dimensional) + * phases have units of kg/m^2 and kmol/m^2, respectively. And for edge (1-dimensional) + * phases, these units kg/m and kmol/m. + * * Class Phase contains methods for saving and restoring the full internal state * of a given phase. These are saveState() and restoreState(). These functions * operate on a state vector, which by default uses the first two entries for @@ -532,7 +538,7 @@ class Phase * kmol/m^3. The length of the vector must be greater than * or equal to the number of species within the phase. */ - void getConcentrations(double* const c) const; + virtual void getConcentrations(double* const c) const; //! Concentration of species k. //! If k is outside the valid range, an exception will be thrown. @@ -541,7 +547,7 @@ class Phase * * @returns the concentration of species k (kmol m-3). */ - double concentration(const size_t k) const; + virtual double concentration(const size_t k) const; //! Set the concentrations to the specified values within the phase. //! We set the concentrations here and therefore we set the overall density @@ -664,11 +670,11 @@ class Phase //! Molar density (kmol/m^3). //! @return The molar density of the phase - double molarDensity() const; + virtual double molarDensity() const; //! Molar volume (m^3/kmol). //! @return The molar volume of the phase - double molarVolume() const; + virtual double molarVolume() const; //! Set the internally stored density (kg/m^3) of the phase. //! Note the density of a phase is an independent variable. diff --git a/include/cantera/thermo/SurfPhase.h b/include/cantera/thermo/SurfPhase.h index a270ef4acc..f191cff2df 100644 --- a/include/cantera/thermo/SurfPhase.h +++ b/include/cantera/thermo/SurfPhase.h @@ -189,8 +189,8 @@ class SurfPhase : public ThermoPhase * * @param k Optional parameter indicating the species. The default * is to assume this refers to species 0. - * @return - * Returns the standard Concentration in units of m3 kmol-1. + * @return the standard concentration in units of kmol/m^2 for surface phases or + * kmol/m for edge phases. */ virtual doublereal standardConcentration(size_t k = 0) const; virtual doublereal logStandardConc(size_t k=0) const; @@ -200,6 +200,20 @@ class SurfPhase : public ThermoPhase virtual bool addSpecies(shared_ptr spec); + //! Since interface phases have no volume, this returns 0.0. + virtual double molarVolume() const { + return 0.0; + } + + //! Since interface phases have no volume, setting this to a value other than 0.0 + //! raises an exception. + virtual void setMolarDensity(const double vm) { + if (vm != 0.0) { + throw CanteraError("SurfPhase::setMolarDensity", + "The volume of an interface is zero"); + } + } + //! Returns the site density /*! * Site density kmol m-2 @@ -296,6 +310,8 @@ class SurfPhase : public ThermoPhase virtual void setState(const AnyMap& state); protected: + virtual void compositionChanged(); + //! Surface site density (kmol m-2) doublereal m_n0; diff --git a/interfaces/cython/cantera/thermo.pyx b/interfaces/cython/cantera/thermo.pyx index 386c1e5988..efb3a890f5 100644 --- a/interfaces/cython/cantera/thermo.pyx +++ b/interfaces/cython/cantera/thermo.pyx @@ -691,7 +691,10 @@ cdef class ThermoPhase(_SolutionBase): self._setArray1(thermo_setMoleFractions, X) property concentrations: - """Get/Set the species concentrations [kmol/m^3].""" + """ + Get/Set the species concentrations. Units are kmol/m^3 for bulk phases, kmol/m^2 + for surface phases, and kmol/m for edge phases. + """ def __get__(self): return self._getArray1(thermo_getConcentrations) def __set__(self, C): diff --git a/src/thermo/SurfPhase.cpp b/src/thermo/SurfPhase.cpp index bd9fc6ca9d..40ce9be31a 100644 --- a/src/thermo/SurfPhase.cpp +++ b/src/thermo/SurfPhase.cpp @@ -159,9 +159,8 @@ void SurfPhase::getCp_R(doublereal* cpr) const void SurfPhase::getStandardVolumes(doublereal* vol) const { - _updateThermo(); for (size_t k = 0; k < m_kk; k++) { - vol[k] = 1.0/standardConcentration(k); + vol[k] = 0.0; } } @@ -211,6 +210,7 @@ void SurfPhase::setSiteDensity(doublereal n0) "Site density must be positive. Got {}", n0); } m_n0 = n0; + assignDensity(n0 * meanMolecularWeight()); m_logn0 = log(m_n0); } @@ -281,6 +281,12 @@ void SurfPhase::setState(const AnyMap& state) { ThermoPhase::setState(state); } +void SurfPhase::compositionChanged() +{ + ThermoPhase::compositionChanged(); + assignDensity(m_n0 * meanMolecularWeight()); +} + void SurfPhase::_updateThermo(bool force) const { doublereal tnow = temperature(); diff --git a/test/data/consistency-cases.yaml b/test/data/consistency-cases.yaml index 2b6e6f17c3..17024c9038 100644 --- a/test/data/consistency-cases.yaml +++ b/test/data/consistency-cases.yaml @@ -271,7 +271,6 @@ ideal-surface: phase: Pt-surf atol_v: 1e-7 known-failures: - h_eq_u_plus_Pv: "Definition of volume is unclear. See GitHub Issue #1312" gk_eq_hk_minus_T_sk: "Implementation of s_k is incorrect. See GitHub Issue #1313" s_eq_sum_sk_Xk: @@ -292,7 +291,6 @@ ideal-edge: phase: TPB atol_v: 1e3 # site density of 5e-18 kmol/m = linear molar volume of 2e17 m/kmol known-failures: - h_eq_u_plus_Pv: "Definition of volume is unclear. See GitHub Issue #1312" dSdv_const_T_eq_dPdT_const_V: "Compressibility of phase leads to inconsistent results. See GitHub Issue #1312" states: diff --git a/test/python/test_thermo.py b/test/python/test_thermo.py index 27fee1f418..0463fb857f 100644 --- a/test/python/test_thermo.py +++ b/test/python/test_thermo.py @@ -1113,6 +1113,10 @@ def test_coverages_array(self): self.assertNear(C[4], 0.25) self.assertNear(sum(C), 1.0) + def test_mole_fractions(self): + self.interface.X = 'c6HM:0.3, c6H*:0.7' + self.assertNear(sum(self.interface.concentrations), self.interface.site_density) + def test_coverages_string(self): self.interface.coverages = 'c6HM:0.2, c6H*:0.8' C = self.interface.coverages diff --git a/test/python/utilities.py b/test/python/utilities.py index 6cddb0339e..ec184ff1c9 100644 --- a/test/python/utilities.py +++ b/test/python/utilities.py @@ -94,6 +94,8 @@ def assertIsNaN(self, value): self.fail(f"Value '{value}' is a number") def assertNear(self, a, b, rtol=1e-8, atol=1e-12, msg=None): + if a == b: + return # handles case where a == b == inf cmp = 2 * abs(a - b)/(abs(a) + abs(b) + 2 * atol / rtol) if not cmp < rtol: message = ('AssertNear: %.14g - %.14g = %.14g\n' % (a, b, a-b) + diff --git a/test/thermo/thermoToYaml.cpp b/test/thermo/thermoToYaml.cpp index 9cbf667e5b..dd158d8779 100644 --- a/test/thermo/thermoToYaml.cpp +++ b/test/thermo/thermoToYaml.cpp @@ -357,8 +357,12 @@ class ThermoYamlRoundTrip : public testing::Test duplicate->setState_TPX(T, P, X); } - EXPECT_NEAR(original->density(), duplicate->density(), - rtol * original->density()); + double rhoOrig = original->density(); + double rhoDup = duplicate->density(); + if (rhoOrig != rhoDup) { + EXPECT_NEAR(original->density(), duplicate->density(), + rtol * original->density()); + } if (!skip_cp) { EXPECT_NEAR(original->cp_mass(), duplicate->cp_mass(), rtol * original->cp_mass()); diff --git a/test_problems/diamondSurf/runDiamond.cpp b/test_problems/diamondSurf/runDiamond.cpp index c9d75a8e7d..74bcceb258 100644 --- a/test_problems/diamondSurf/runDiamond.cpp +++ b/test_problems/diamondSurf/runDiamond.cpp @@ -10,15 +10,6 @@ using namespace std; using namespace Cantera; -void printDbl(double val) -{ - if (fabs(val) < 5.0E-17) { - cout << " nil"; - } else { - cout << val; - } -} - int main(int argc, char** argv) { #if defined(_MSC_VER) && _MSC_VER < 1900 @@ -96,15 +87,11 @@ int main(int argc, char** argv) int itp = k - 5; naH = diamond100->nAtoms(itp, 0); } - cout << k << " " << naH << " " ; - printDbl(src[k]); - cout << endl; + writelog("{} {} {}\n", k, naH, src[k]); sum += naH * src[k]; } - cout << "sum = "; - printDbl(sum); - cout << endl; + writelog("sum = {}\n", sum); double mwd = diamond->molecularWeight(0); double dens = diamond->density(); double gr = src[4] * mwd / dens; diff --git a/test_problems/diamondSurf/runDiamond_blessed.out b/test_problems/diamondSurf/runDiamond_blessed.out index c3df420d46..90577f52e8 100644 --- a/test_problems/diamondSurf/runDiamond_blessed.out +++ b/test_problems/diamondSurf/runDiamond_blessed.out @@ -5,17 +5,17 @@ Number of reactions = 20 0 1 -8.96e-05 1 2 4.486e-05 2 3 -3.801e-08 -3 4 nil +3 4 0.0 4 0 3.801e-08 -5 2 nil -6 1 nil -7 1 nil -8 0 nil -9 4 nil -10 3 nil -11 3 nil -12 2 nil -sum = nil +5 2 0.0 +6 1 0.0 +7 1 0.0 +8 0 0.0 +9 4 0.0 +10 3 0.0 +11 3 0.0 +12 2 0.0 +sum = 0.0 growth rate = 0.4669 microns per hour Coverages: 0 c6HH 0.4622 diff --git a/test_problems/fortran/f90_demo_blessed.txt b/test_problems/fortran/f90_demo_blessed.txt index 1f2428d3f3..945d5cfeee 100644 --- a/test_problems/fortran/f90_demo_blessed.txt +++ b/test_problems/fortran/f90_demo_blessed.txt @@ -82,27 +82,27 @@ Thermal conductivity: 0.16396 W/m/K ******** Interface Kinetics Test ******** Equation k_fwd Net rate - H2 + 2 PT(S) => 2 H(S) 0.33111E-01 0.33111E-01 + H2 + 2 PT(S) => 2 H(S) 0.33954E-01 0.33954E-01 2 H(S) => H2 + 2 PT(S) 0.00000E+00 0.00000E+00 H + PT(S) => H(S) 0.00000E+00 0.00000E+00 - O2 + 2 PT(S) => 2 O(S) 0.15290E-01 0.15290E-01 - O2 + 2 PT(S) => 2 O(S) 0.20585E-01 0.20585E-01 - 2 O(S) => O2 + 2 PT(S) 0.14336E-07 0.14336E-07 + O2 + 2 PT(S) => 2 O(S) 0.16078E-01 0.16078E-01 + O2 + 2 PT(S) => 2 O(S) 0.21646E-01 0.21646E-01 + 2 O(S) => O2 + 2 PT(S) 0.15097E-07 0.15097E-07 O + PT(S) => O(S) 0.00000E+00 0.00000E+00 - H2O + PT(S) => H2O(S) 0.35283E+00 0.35283E+00 + H2O + PT(S) => H2O(S) 0.36181E+00 0.36181E+00 H2O(S) => H2O + PT(S) 0.00000E+00 0.00000E+00 OH + PT(S) => OH(S) 0.00000E+00 0.00000E+00 OH(S) => OH + PT(S) 0.00000E+00 0.00000E+00 H(S) + O(S) <=> OH(S) + PT(S) 0.00000E+00 0.00000E+00 H(S) + OH(S) <=> H2O(S) + PT(S) 0.00000E+00 0.00000E+00 2 OH(S) <=> H2O(S) + O(S) 0.00000E+00 0.00000E+00 - CO + PT(S) => CO(S) 0.12687E+00 0.12687E+00 - CO(S) => CO + PT(S) 0.17276E+00 0.17276E+00 + CO + PT(S) => CO(S) 0.13341E+00 0.13341E+00 + CO(S) => CO + PT(S) 0.17716E+00 0.17716E+00 CO2(S) => CO2 + PT(S) 0.00000E+00 0.00000E+00 - CO(S) + O(S) => CO2(S) + PT(S) 0.13165E-01 0.13165E-01 + CO(S) + O(S) => CO2(S) + PT(S) 0.13844E-01 0.13844E-01 CH4 + 2 PT(S) => CH3(S) + H(S) 0.00000E+00 0.00000E+00 CH3(S) + PT(S) => CH2(S)s + H(S) 0.00000E+00 0.00000E+00 CH2(S)s + PT(S) => CH(S) + H(S) 0.00000E+00 0.00000E+00 CH(S) + PT(S) => C(S) + H(S) 0.00000E+00 0.00000E+00 C(S) + O(S) => CO(S) + PT(S) 0.00000E+00 0.00000E+00 - CO(S) + PT(S) => C(S) + O(S) 0.10366E-06 0.10366E-06 + CO(S) + PT(S) => C(S) + O(S) 0.10900E-06 0.10900E-06 From 656dfe28eb096ccdce337fddafa1bc5a8c8eebf3 Mon Sep 17 00:00:00 2001 From: Ray Speth Date: Sun, 31 Jul 2022 11:56:10 -0400 Subject: [PATCH 4/9] Make SurfPhase incompressible Partially resolves #1312 --- include/cantera/thermo/SurfPhase.h | 4 ++++ interfaces/cython/cantera/onedim.py | 2 +- src/kinetics/solveSP.cpp | 7 ++++++- src/thermo/SurfPhase.cpp | 21 +++++++++++++++------ test/data/consistency-cases.yaml | 5 ----- test/python/test_kinetics.py | 2 +- 6 files changed, 27 insertions(+), 14 deletions(-) diff --git a/include/cantera/thermo/SurfPhase.h b/include/cantera/thermo/SurfPhase.h index f191cff2df..b25fb4c42a 100644 --- a/include/cantera/thermo/SurfPhase.h +++ b/include/cantera/thermo/SurfPhase.h @@ -110,6 +110,10 @@ class SurfPhase : public ThermoPhase return "Surf"; } + virtual bool isCompressible() const { + return false; + } + //! Return the Molar Enthalpy. Units: J/kmol. /*! * For an ideal solution, diff --git a/interfaces/cython/cantera/onedim.py b/interfaces/cython/cantera/onedim.py index 637d33deef..a5ab3ffd48 100644 --- a/interfaces/cython/cantera/onedim.py +++ b/interfaces/cython/cantera/onedim.py @@ -563,7 +563,7 @@ def write_hdf(self, filename, *args, group=None, species='X', mode='a', `SolutionArray.write_hdf` via `to_solution_array` and requires a working installation of *h5py* (``h5py`` can be installed using pip or conda). """ - cols = ('extra', 'T', 'D', species) + cols = ('extra', 'T', 'P', species) meta = self.settings meta['date'] = formatdate(localtime=True) meta['cantera_version'] = __version__ diff --git a/src/kinetics/solveSP.cpp b/src/kinetics/solveSP.cpp index 499621f3b8..e469cc6ccd 100644 --- a/src/kinetics/solveSP.cpp +++ b/src/kinetics/solveSP.cpp @@ -295,9 +295,14 @@ int solveSP::solveSurfProb(int ifunc, doublereal time_scale, doublereal TKelvin, void solveSP::updateState(const doublereal* CSolnSP) { + vector_fp X; size_t loc = 0; for (size_t n = 0; n < m_numSurfPhases; n++) { - m_ptrsSurfPhase[n]->setConcentrations(CSolnSP + loc); + X.resize(m_nSpeciesSurfPhase[n]); + for (size_t k = 0; k < X.size(); k++) { + X[k] = CSolnSP[loc + k] / m_ptrsSurfPhase[n]->siteDensity(); + } + m_ptrsSurfPhase[n]->setMoleFractions_NoNorm(X.data()); loc += m_nSpeciesSurfPhase[n]; } } diff --git a/src/thermo/SurfPhase.cpp b/src/thermo/SurfPhase.cpp index 40ce9be31a..b6867a8af6 100644 --- a/src/thermo/SurfPhase.cpp +++ b/src/thermo/SurfPhase.cpp @@ -218,25 +218,34 @@ void SurfPhase::setCoverages(const doublereal* theta) { double sum = 0.0; for (size_t k = 0; k < m_kk; k++) { - sum += theta[k]; + sum += theta[k] / size(k); } if (sum <= 0.0) { throw CanteraError("SurfPhase::setCoverages", "Sum of Coverage fractions is zero or negative"); } for (size_t k = 0; k < m_kk; k++) { - m_work[k] = m_n0*theta[k]/(sum*size(k)); + m_work[k] = theta[k] / (sum * size(k)); } - // Call the Phase:: class function setConcentrations. - setConcentrations(m_work.data()); + setMoleFractions(m_work.data()); } void SurfPhase::setCoveragesNoNorm(const doublereal* theta) { + double sum = 0.0; + double sum2 = 0.0; + for (size_t k = 0; k < m_kk; k++) { + sum += theta[k] / size(k); + sum2 += theta[k]; + } + if (sum <= 0.0) { + throw CanteraError("SurfPhase::setCoverages", + "Sum of Coverage fractions is zero or negative"); + } for (size_t k = 0; k < m_kk; k++) { - m_work[k] = m_n0*theta[k]/size(k); + m_work[k] = theta[k] * sum2 / (sum * size(k)); } - setConcentrationsNoNorm(m_work.data()); + setMoleFractions_NoNorm(m_work.data()); } void SurfPhase::getCoverages(doublereal* theta) const diff --git a/test/data/consistency-cases.yaml b/test/data/consistency-cases.yaml index 17024c9038..fac2e49a65 100644 --- a/test/data/consistency-cases.yaml +++ b/test/data/consistency-cases.yaml @@ -277,8 +277,6 @@ ideal-surface: "Implementation of s_k is incorrect. See GitHub Issue #1313" g_eq_sum_gk_Xk: "chemPotentials does not protect against inf. See GitHub Issue #1314" - dSdv_const_T_eq_dPdT_const_V: - "Compressibility of phase leads to inconsistent results. See GitHub Issue #1312" states: - {T: 800, P: 1 atm, coverages: {Pt(s): 0.5, H(s): 0.4, O(s): 0.1}} - {T: 800, P: 5 atm, coverages: {H(s): 1.0}} @@ -290,9 +288,6 @@ ideal-edge: file: surface-phases.yaml phase: TPB atol_v: 1e3 # site density of 5e-18 kmol/m = linear molar volume of 2e17 m/kmol - known-failures: - dSdv_const_T_eq_dPdT_const_V: - "Compressibility of phase leads to inconsistent results. See GitHub Issue #1312" states: - {T: 300, P: 1 atm} - {T: 900, P: 20 atm} diff --git a/test/python/test_kinetics.py b/test/python/test_kinetics.py index 2ee29733ef..18a325b4c0 100644 --- a/test/python/test_kinetics.py +++ b/test/python/test_kinetics.py @@ -228,7 +228,7 @@ def test_surface(self): adjacent=[gas]) surf1.site_density = surf2.site_density = 5e-9 gas.TP = surf2.TP = surf1.TP = 900, 2*ct.one_atm - surf2.concentrations = surf1.concentrations + surf2.coverages = surf1.coverages self.assertEqual(surf1.n_reactions, surf2.n_reactions) From c78804201f82c52ca8916542389bd6adbe13ecfe Mon Sep 17 00:00:00 2001 From: Ray Speth Date: Mon, 1 Aug 2022 23:06:49 -0400 Subject: [PATCH 5/9] Fix getCoverages for multi-site species --- src/thermo/SurfPhase.cpp | 10 ++++++++-- test/data/surface-phases.yaml | 28 ++++++++++++++++++++++++++++ test/python/test_thermo.py | 26 ++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 2 deletions(-) diff --git a/src/thermo/SurfPhase.cpp b/src/thermo/SurfPhase.cpp index b6867a8af6..deb9212ee3 100644 --- a/src/thermo/SurfPhase.cpp +++ b/src/thermo/SurfPhase.cpp @@ -250,9 +250,15 @@ void SurfPhase::setCoveragesNoNorm(const doublereal* theta) void SurfPhase::getCoverages(doublereal* theta) const { - getConcentrations(theta); + double sum_X = 0.0; + double sum_X_s = 0.0; + getMoleFractions(theta); for (size_t k = 0; k < m_kk; k++) { - theta[k] *= size(k)/m_n0; + sum_X += theta[k]; + sum_X_s += theta[k] * size(k); + } + for (size_t k = 0; k < m_kk; k++) { + theta[k] *= size(k) * sum_X / sum_X_s; } } diff --git a/test/data/surface-phases.yaml b/test/data/surface-phases.yaml index f4d0766cd4..05ef697a19 100644 --- a/test/data/surface-phases.yaml +++ b/test/data/surface-phases.yaml @@ -13,6 +13,18 @@ phases: site-density: 2.7063e-9 mol/cm^2 state: {T: 900 K, P: 1 atm, coverages: {Pt(s): 0.5, H(s): 0.4, O(s): 0.1}} +- name: Pt-multi-sites + thermo: ideal-surface + adjacent-phases: [gas] + kinetics: surface + reactions: [Pt-reactions, Pt-multisite-reactions] + species: [{Pt-surf-species: all}, {multi-site-species: all}] + site-density: 2.7063e-9 mol/cm^2 + state: + T: 900 K + P: 1 atm + coverages: {Pt(s): 0.35, H(s): 0.4, O(s): 0.1, O2(s): 0.15} + - name: TPB thermo: edge species: [{tpb-species: [(tpb)]}] @@ -102,6 +114,19 @@ Pt-surf-species: - [1.9454180E+00, 9.1761647E-04, -1.1226719E-07, -9.9099624E-11, 2.4307699E-14, -1.4005187E+04, -1.1531663E+01] +multi-site-species: +- name: O2(s) + composition: {O: 2, Pt: 2} + sites: 2 + thermo: + model: NASA7 + temperature-ranges: [300, 1000, 3000] + data: + - [-9.4986904E-01, 7.4042305E-03, -1.0451424E-06, -6.1120420E-09, + 3.3787992E-12, -1.3209912E+04, 3.6137905E+00] + - [1.9454180E+00, 9.1761647E-04, -1.1226719E-07, -9.9099624E-11, + 2.4307699E-14, -1.4005187E+04, -1.1531663E+01] + tpb-species: - name: (tpb) @@ -120,6 +145,9 @@ Pt-reactions: - equation: H + Pt(s) => H(s) sticking-coefficient: [1.0, 0.0, 0.0] +Pt-multisite-reactions: +- equation: 2 O(s) <=> O2(s) + rate-constant: {A: 3.1e9 cm^2/mol/s, b: 0.0, Ea: 0.0} graphite-anode-species: - name: EC(e) diff --git a/test/python/test_thermo.py b/test/python/test_thermo.py index 0463fb857f..87791a89f8 100644 --- a/test/python/test_thermo.py +++ b/test/python/test_thermo.py @@ -1130,6 +1130,32 @@ def test_coverages_dict(self): self.assertNear(C[self.interface.species_index('c6*M')], 0.75) +class TestInterfacePhase2(utilities.CanteraTest): + """ Test special cases of interface phases """ + def test_multi_site_species(self): + surf = ct.Interface("surface-phases.yaml", "Pt-multi-sites") + # O2(s) consumes two surface sites + surf.coverages = {"Pt(s)": 0.5, "H(s)": 0.1, "O2(s)": 0.4} + X = surf.mole_fraction_dict() + moles = 0.5 + 0.1 + 0.4 / 2 + assert np.isclose(X["Pt(s)"], 0.5 / moles) + assert np.isclose(X["H(s)"], 0.1 / moles) + assert np.isclose(X["O2(s)"], 0.2 / moles) + + def test_multi_site_unnormalized(self): + surf = ct.Interface("surface-phases.yaml", "Pt-multi-sites") + theta = [0.5, 0.1, 0.1, 0.32] + surf.set_unnormalized_coverages(theta) + self.assertArrayNear(surf.coverages, theta) + + X_unnormalized = surf.X + assert np.isclose(sum(theta), sum(surf.X)) + + surf.coverages = theta + X_normalized = surf.X + self.assertArrayNear(X_normalized * sum(theta), X_unnormalized) + + class TestPlasmaPhase(utilities.CanteraTest): def setUp(self): self.phase = ct.Solution('oxygen-plasma.yaml', From 16e4543f1440bc105bae38f13b8b01aa8cde2dcb Mon Sep 17 00:00:00 2001 From: Ray Speth Date: Sun, 7 Aug 2022 14:12:20 -0400 Subject: [PATCH 6/9] [Fortran] Fix compilation error from getReactionType --- src/fortran/cantera_kinetics.f90 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/fortran/cantera_kinetics.f90 b/src/fortran/cantera_kinetics.f90 index 99d3643888..d7a02df56d 100644 --- a/src/fortran/cantera_kinetics.f90 +++ b/src/fortran/cantera_kinetics.f90 @@ -114,13 +114,13 @@ double precision function ctkin_productStoichCoeff(self, k, i) ctkin_productstoichcoeff = kin_productstoichcoeff(self%kin_id, k, i) end function ctkin_productstoichcoeff - integer function ctkin_getReactionType(self, i, buf) + subroutine ctkin_getReactionType(self, i, buf) implicit none type(phase_t), intent(inout) :: self integer, intent(in) :: i character*(*), intent(out) :: buf self%err = kin_getreactiontype(self%kin_id, i, buf) - end function ctkin_getReactionType + end subroutine ctkin_getReactionType subroutine ctkin_getFwdRatesOfProgress(self, fwdROP) implicit none From beac37f7999600a6867e660b1331dc4fd23a4070 Mon Sep 17 00:00:00 2001 From: Ray Speth Date: Sun, 7 Aug 2022 14:12:41 -0400 Subject: [PATCH 7/9] [Fortran] Fix compilation warnings from Clang --- src/fortran/fct.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/fortran/fct.cpp b/src/fortran/fct.cpp index f80ea76473..4bd6b17dc6 100644 --- a/src/fortran/fct.cpp +++ b/src/fortran/fct.cpp @@ -30,6 +30,10 @@ typedef Cabinet TransportCabinet; typedef integer status_t; +template<> ThermoCabinet* ThermoCabinet::s_storage; // defined in ct.cpp +template<> KineticsCabinet* KineticsCabinet::s_storage; // defined in ct.cpp +template<> TransportCabinet* TransportCabinet::s_storage; // defined in ct.cpp + namespace { ThermoPhase* _fph(const integer* n) From 0847538f03418a0267e82d6cab46cc89c9add386 Mon Sep 17 00:00:00 2001 From: Ray Speth Date: Sun, 7 Aug 2022 14:33:14 -0400 Subject: [PATCH 8/9] Fix incorrect name/label in F90 sample --- samples/f90/demo.f90 | 8 ++++---- test_problems/fortran/f90_demo_blessed.txt | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/samples/f90/demo.f90 b/samples/f90/demo.f90 index 1038c0f7b5..0acc922ae1 100644 --- a/samples/f90/demo.f90 +++ b/samples/f90/demo.f90 @@ -155,19 +155,19 @@ subroutine demo_surf(surf, nsp, nrxn) type(phase_t) surf integer :: nsp, nrxn, i character*40 equation - double precision :: kf(nrxn), ropnet(nrxn) + double precision :: ropf(nrxn), ropnet(nrxn) write(*,*) write(*,*) '******** Interface Kinetics Test ********' write(*,*) - call getFwdRatesOfProgress(surf, kf) + call getFwdRatesOfProgress(surf, ropf) call getNetRatesOfProgress(surf, ropnet) - write(*,*) 'Equation k_fwd Net rate' + write(*,*) 'Equation Fwd rate Net rate' do i = 1, nrxn call getReactionString(surf, i, equation) - write(*,60) equation, kf(i), ropnet(i) + write(*,60) equation, ropf(i), ropnet(i) 60 format(' ',a40, e14.5, e14.5) end do end subroutine demo_surf diff --git a/test_problems/fortran/f90_demo_blessed.txt b/test_problems/fortran/f90_demo_blessed.txt index 945d5cfeee..2e34c86a6e 100644 --- a/test_problems/fortran/f90_demo_blessed.txt +++ b/test_problems/fortran/f90_demo_blessed.txt @@ -81,7 +81,7 @@ Thermal conductivity: 0.16396 W/m/K ******** Interface Kinetics Test ******** - Equation k_fwd Net rate + Equation Fwd rate Net rate H2 + 2 PT(S) => 2 H(S) 0.33954E-01 0.33954E-01 2 H(S) => H2 + 2 PT(S) 0.00000E+00 0.00000E+00 H + PT(S) => H(S) 0.00000E+00 0.00000E+00 From d317f12c8413864e23b9fe080bb9598005cffb0b Mon Sep 17 00:00:00 2001 From: Ray Speth Date: Sun, 7 Aug 2022 23:55:44 -0400 Subject: [PATCH 9/9] Restore test coverage of setConcentrations --- test/thermo/ThermoPhase_Test.cpp | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/test/thermo/ThermoPhase_Test.cpp b/test/thermo/ThermoPhase_Test.cpp index 9ec522eac4..d9db2fd471 100644 --- a/test/thermo/ThermoPhase_Test.cpp +++ b/test/thermo/ThermoPhase_Test.cpp @@ -92,6 +92,31 @@ TEST_F(TestThermoMethods, setState_AnyMap) EXPECT_NEAR(thermo->temperature(), 298.15, 1e-6); } +TEST_F(TestThermoMethods, setConcentrations) +{ + vector_fp C0(thermo->nSpecies()); + double ctot = 0.0; + for (size_t k = 0; k < thermo->nSpecies(); k++) { + if (k == 2) { + C0[k] = -1e-8; + } else { + C0[k] = 0.25 * k + 0.1; + } + ctot += C0[k]; + } + + thermo->setConcentrations(C0.data()); + EXPECT_NEAR(thermo->molarDensity(), ctot, 1e-7 * ctot); + EXPECT_DOUBLE_EQ(thermo->moleFraction(2), 0.0); + + for (size_t k = 0; k < thermo->nSpecies(); k++) { + C0[k] *= 1.5; + } + thermo->setConcentrationsNoNorm(C0.data()); + EXPECT_NEAR(thermo->molarDensity(), 1.5 * ctot, 1e-7 * ctot); + EXPECT_NEAR(thermo->moleFraction(2), -1e-8 / ctot, 1e-16); +} + class EquilRatio_MixFrac_Test : public testing::Test { public: