Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Individual Reactor Initialization - Segmentation Fault Fix. #1063

Merged
merged 8 commits into from
Jun 26, 2021
3 changes: 2 additions & 1 deletion include/cantera/zeroD/Reactor.h
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,8 @@ class Reactor : public ReactorBase
//! `true` for reactors where the pressure is a dependent property,
//! calculated from the state, and `false` when the pressure is constant
//! or an independent variable.
virtual void updateConnected(bool updatePressure);
//! @param t0 initialization time for the reactor if not obtained from the network
virtual void updateConnected(bool updatePressure, double t0=0.0);

//! Get initial conditions for SurfPhase objects attached to this reactor
virtual void getSurfaceInitialConditions(double* y);
Expand Down
6 changes: 3 additions & 3 deletions src/zeroD/Reactor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ void Reactor::initialize(doublereal t0)
m_thermo->restoreState(m_state);
m_sdot.resize(m_nsp, 0.0);
m_wdot.resize(m_nsp, 0.0);
updateConnected(true);
updateConnected(true, t0);

for (size_t n = 0; n < m_wall.size(); n++) {
WallBase* W = m_wall[n];
Expand Down Expand Up @@ -177,7 +177,7 @@ void Reactor::updateSurfaceState(double* y)
}
}

void Reactor::updateConnected(bool updatePressure) {
void Reactor::updateConnected(bool updatePressure, double t0) {
// save parameters needed by other connected reactors
m_enthalpy = m_thermo->enthalpy_mass();
if (updatePressure) {
Expand All @@ -187,7 +187,7 @@ void Reactor::updateConnected(bool updatePressure) {
m_thermo->saveState(m_state);

// Update the mass flow rate of connected flow devices
double time = m_net->time();
double time = (m_net != nullptr) ? m_net->time() : t0;
for (size_t i = 0; i < m_outlet.size(); i++) {
m_outlet[i]->updateMassFlowRate(time);
}
Expand Down
1 change: 1 addition & 0 deletions test/SConscript
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ addTestProgram('thermo', 'thermo')
addTestProgram('equil', 'equil')
addTestProgram('kinetics', 'kinetics')
addTestProgram('transport', 'transport')
addTestProgram('zeroD', 'zeroD')

python_subtests = ['']
test_root = '#interfaces/cython/cantera/test'
Expand Down
69 changes: 69 additions & 0 deletions test/zeroD/test_zeroD.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#include <cstdio>
#include <string>
#include "gtest/gtest.h"
#include "cantera/thermo.h"
#include "cantera/kinetics.h"
#include "cantera/zerodim.h"

using namespace Cantera;

// This test ensures that prior reactor initialization of a reactor does
// not affect later integration within a network. This example was
// adapted from test_reactor.py::test_equilibrium_HP.
TEST(ZeroDim, test_individual_reactor_initialization)
{
// Initial conditions
double T0 = 1100.0;
double P0 = 1013250;
std::string X0 = "H2:1.0, O2:0.5, AR:8.0";
// Reactor solution, phase, and kinetics objects
std::shared_ptr<Solution> sol1 = newSolution("h2o2.yaml");
std::shared_ptr<ThermoPhase> gas1 = sol1->thermo();
std::shared_ptr<Kinetics> kin1 = sol1->kinetics();
gas1->setState_TPX(T0, P0, X0);
// Set up reactor object
Reactor reactor1;
reactor1.setKineticsMgr(*kin1);
ischoegl marked this conversation as resolved.
Show resolved Hide resolved
reactor1.setThermoMgr(*gas1);
// Initialize reactor at arbitrary value prior to integration to ensure no impact
reactor1.initialize(0.1);
// Setup reactor network and integrate
ReactorNet network;
network.addReactor(reactor1);
network.advance(1.0);
// Secondary gas for comparison
std::shared_ptr<Solution> sol2 = newSolution("h2o2.yaml");
std::shared_ptr<ThermoPhase> gas2 = sol2->thermo();
std::shared_ptr<Kinetics> kin2 = sol2->kinetics();
gas2->setState_TPX(T0, P0, X0);
gas2->equilibrate("UV");
// Secondary reactor for comparison
Reactor reactor2;
reactor2.setKineticsMgr(*kin2);
reactor2.setThermoMgr(*gas2);
reactor2.initialize(0);
// Get state of reactors
std::vector<double> state1 (reactor1.neq());
std::vector<double> state2 (reactor2.neq());
reactor1.getState(state1.data());
reactor2.getState(state2.data());
// Compare the reactors.
EXPECT_EQ(reactor1.neq(), reactor2.neq());
double tol = 1e-14;
EXPECT_NEAR(state1[0], state2[0], tol);
EXPECT_NEAR(state1[1], state2[1], tol);
for(size_t i = 3; i < reactor1.neq(); i++)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing I hadn't noticed before: why are you skipping i=2? Shouldn't all state variables be comparable?

Also, as minor formatting suggestions: usually Cantera doesn't use a line break before { in a for loop (they're typically on the same line). There definitely should be a space after the for, though. Beyond, it may also help to add a blank line before each of your comments, as it makes your code easier to parse visually.

I'll let @bryanwweber make the final approval, as he was the one who started the review.

Copy link
Contributor Author

@anthony-walker anthony-walker Jun 25, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, I adapted this example from test_equilibrium_HP where pressure is held as a constant and not compared. When it is compared it causes the test to fail based on the specified tolerance in the test. I am not entirely certain why but I assumed is was due to the methods used and was justified at the time. This example is the same in that U is held constant but varies slightly on the order of 1e-8. I will look further into it though for a more thorough explanation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It’s actually interesting that this unit test works to the specified tolerances at all: the test compares a result that involves reaction kinetics with one that uses equilibrium. While there shouldn’t be much of a difference for these simple mixtures in theory, it’s not completely intuitive that comparisons are that close. I’m saying this as I’ve seen significant differences between equilibrium results and kinetics calculations for some more complex mixtures.

On the other hand, I f this is the exact equivalent to a Python example, why don’t you just use the ‘hp’ equilibrium case, plus IdealGasConstPressureReactor objects for this unit test? That it works for HP and not for UV could just be a quirk of some solvers.

Copy link
Contributor Author

@anthony-walker anthony-walker Jun 26, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ischoegl I could reduce the tolerance and the unit test would work. I believe it passes for 1e-7 but I didn't think that was a good answer. It doesn't work for HP either though if that was unclear. Also, Reactor was selected to be the test reactor because it is the base class and not a derived class.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO, 1e-7 is a pretty good tolerance on the in internal energy... We're talking about a value on the order of 1e6 right?

Copy link
Member

@ischoegl ischoegl Jun 26, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't work for HP either though if that was unclear.

no, I guess it wasn’t clear to me … in that case, I’d agree with @bryanwweber and just reduce the tolerance. 1e-14 is really tight if you’re not comparing apples to apples.

Otherwise, please have another look at the formatting as suggested. Also, note that there are some additional white spaces missing on line 17, I.e. double P0 = 10*OneAtm; should be double P0 = 10 * OneAtm;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ischoegl I will fix the formatting. I always forget that. Hopefully I will catch on to that eventually. I will also make the change to 1e-7. @bryanwweber the values are pretty large, 971244.30 to be more specific.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@anthony-walker Right, so that's ≈1e6. A tolerance of 1e-14 is on the order of machine precision, and applying several arithmetic operations (integrating or solving equilibrium) is going to decrease the precision of the answer. Since there's different methods being compared here, a tolerance down at machine precision is too tight. On top of that, since the values are so large, at some point the number of digits is no longer physically meaningful. If you look at some of the tests in the regression tests, the tolerances are on the order of 1e-2 to account for differences between compilers, platforms, etc.

Anyhow, all of which is to say that sometimes, changing the tolerance is a legitimate answer 😊

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bryanwweber Thank you for the explanation, it makes more sense now. I will have to look through those tests. Have all concerns been addressed with the PR then?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It’s actually interesting that this unit test works to the specified tolerances at all: the test compares a result that involves reaction kinetics with one that uses equilibrium. While there shouldn’t be much of a difference for these simple mixtures in theory, it’s not completely intuitive that comparisons are that close. I’m saying this as I’ve seen significant differences between equilibrium results and kinetics calculations for some more complex mixtures.

Since Cantera computes reverse reaction rates from equilibrium constants, if you have a reaction mechanism with at least as many independent, reversible reactions as species, I think you should always get "exactly" the same result from both a kinetic simulation out to steady state and the equilibrium solver. Given that both the equilibrium and ODE solvers are only accurate to within certain bounds, the level of exactness depends on the tolerances of these solvers, not to something approximating accumulated floating point roundoff.

So, I adapted this example from test_equilibrium_HP where pressure is held as a constant and not compared. When it is compared it causes the test to fail based on the specified tolerance in the test.

For future reference, note that the Python assertNear function is applying a combined relative and absolute tolerance. The GTest ASSERT_NEAR is just an absolute tolerance. To get a relative tolerance, which is generally more useful, we often end up writing something like ASSERT_NEAR(x0, x1, 1e-7 * std::abs(x0)).

{
EXPECT_NEAR(state1[i], state2[i], tol);
}
}

int main(int argc, char** argv)
{
printf("Running main() from test_zeroD.cpp\n");
testing::InitGoogleTest(&argc, argv);
Cantera::make_deprecation_warnings_fatal();
int result = RUN_ALL_TESTS();
Cantera::appdelete();
return result;
}