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

First Cython implementation of ReactorNet::advance_to_steady_state #303

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions doc/sphinx/reactors.rst
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,13 @@ by two methods:
Internally, several ``step()`` calls are typically performed to reach the
accurate state at time `t_{\rm new}`.

- ``advance_to_steady_state(max_steps, residual_threshold, atol,
write_residuals)`` [Python interface only]: If the steady state solution of a
reactor network is of interest, this method can be used. Internally, the
steady state is approached by time stepping. The network is considered to be
at steady state if the feature-scaled residual of the state vector is below a
given threshold value (which by default is 10 times the time step rtol).

The use of the ``advance`` method in a loop has the advantage that it produces
results corresponding to a predefined time series. These are associated with a
predefined memory consumption and well comparable between simulation runs with
Expand Down
10 changes: 10 additions & 0 deletions include/cantera/kinetics/ImplicitSurfChem.h
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ class ImplicitSurfChem : public FuncEval

//! Set the initial conditions for the solution vector
/*!
* Essentially calls getState()
*
* @param t0 Initial time
* @param leny Length of the solution vector
* @param y Value of the solution vector to be used.
Expand All @@ -143,6 +145,14 @@ class ImplicitSurfChem : public FuncEval
virtual void getInitialConditions(doublereal t0,
size_t leny, doublereal* y);

//! Get the current state of the solution vector
/*!
* @param y Value of the solution vector to be used.
* On output, this contains the initial value
* of the solution.
*/
virtual void getState(doublereal* y);

/*!
* Get the specifications for the problem from the values
* in the ThermoPhase objects for all phases.
Expand Down
1 change: 1 addition & 0 deletions include/cantera/zeroD/ConstPressureReactor.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class ConstPressureReactor : public Reactor

virtual void getInitialConditions(doublereal t0, size_t leny,
doublereal* y);
virtual void getState(doublereal* y);

virtual void initialize(doublereal t0 = 0.0);
virtual void evalEqs(doublereal t, doublereal* y,
Expand Down
1 change: 1 addition & 0 deletions include/cantera/zeroD/FlowReactor.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class FlowReactor : public Reactor

virtual void getInitialConditions(doublereal t0, size_t leny,
doublereal* y);
virtual void getState(doublereal* y);

virtual void initialize(doublereal t0 = 0.0);
virtual void evalEqs(doublereal t, doublereal* y,
Expand Down
1 change: 1 addition & 0 deletions include/cantera/zeroD/IdealGasConstPressureReactor.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class IdealGasConstPressureReactor : public ConstPressureReactor

virtual void getInitialConditions(doublereal t0, size_t leny,
doublereal* y);
virtual void getState(doublereal* y);

virtual void initialize(doublereal t0 = 0.0);
virtual void evalEqs(doublereal t, doublereal* y,
Expand Down
1 change: 1 addition & 0 deletions include/cantera/zeroD/IdealGasReactor.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class IdealGasReactor : public Reactor

virtual void getInitialConditions(doublereal t0, size_t leny,
doublereal* y);
virtual void getState(doublereal* y);

virtual void initialize(doublereal t0 = 0.0);

Expand Down
8 changes: 8 additions & 0 deletions include/cantera/zeroD/Reactor.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,13 +95,21 @@ class Reactor : public ReactorBase

//! Called by ReactorNet to get the initial conditions.
/*!
* Essentially calls function getState()
*
* @param[in] t0 Time at which initial conditions are determined
* @param[in] leny Length of *y* (unused)
* @param[out] y state vector representing the initial state of the reactor
*/
virtual void getInitialConditions(doublereal t0, size_t leny,
doublereal* y);

//! Get the the current state of the reactor.
/*!
* @param[out] y state vector representing the initial state of the reactor
*/
virtual void getState(doublereal* y);

virtual void initialize(doublereal t0 = 0.0);

/*!
Expand Down
2 changes: 2 additions & 0 deletions include/cantera/zeroD/ReactorNet.h
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,8 @@ class ReactorNet : public FuncEval
doublereal* ydot, doublereal* p);
virtual void getInitialConditions(doublereal t0, size_t leny,
doublereal* y);
virtual void getState(doublereal* y);

virtual size_t nparams() {
return m_ntotpar;
}
Expand Down
3 changes: 3 additions & 0 deletions interfaces/cython/cantera/_cantera.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,8 @@ cdef extern from "cantera/zeroD/Reactor.h":
void setEnergy(int)
cbool energyEnabled()
size_t componentIndex(string&)
size_t neq()
void getState(double*)

void addSensitivityReaction(size_t) except +
size_t nSensParams()
Expand Down Expand Up @@ -550,6 +552,7 @@ cdef extern from "cantera/zeroD/ReactorNet.h":
cbool verbose()
void setVerbose(cbool)
size_t neq()
void getState(double*)

void setSensitivityTolerances(double, double)
double rtolSensitivity()
Expand Down
13 changes: 2 additions & 11 deletions interfaces/cython/cantera/examples/reactors/mix1.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,17 +59,8 @@
sim = ct.ReactorNet([mixer])

# Since the mixer is a reactor, we need to integrate in time to reach steady
# state. A few residence times should be enough.
print('{0:>14s} {1:>14s} {2:>14s} {3:>14s} {4:>14s}'.format(
't [s]', 'T [K]', 'h [J/kg]', 'P [Pa]', 'X_CH4'))

t = 0.0
for n in range(30):
tres = mixer.mass/(mfc1.mdot(t) + mfc2.mdot(t))
t += 0.5*tres
sim.advance(t)
print('{0:14.5g} {1:14.5g} {2:14.5g} {3:14.5g} {4:14.5g}'.format(
t, mixer.T, mixer.thermo.h, mixer.thermo.P, mixer.thermo['CH4'].X[0]))
# state
sim.advance_to_steady_state()

# view the state of the gas in the mixer
print(mixer.thermo.report())
15 changes: 2 additions & 13 deletions interfaces/cython/cantera/examples/reactors/pfr.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,19 +125,8 @@
gas2.TDY = r2.thermo.TDY
upstream.syncState()
# integrate the reactor forward in time until steady state is reached
sim2.set_initial_time(0) # forces reinitialization
time = 0
all_done = False
# determine steady state from H2 mole fraction
X_H2_previous = r2.thermo['H2'].X
while not all_done:
time += dt
sim2.advance(time)
if np.abs(r2.thermo['H2'].X - X_H2_previous) < 1.e-10:
# check whether surface coverages are in steady state.
all_done = True
else:
X_H2_previous = r2.thermo['H2'].X
sim2.reinitialize()
sim2.advance_to_steady_state()
# compute velocity and transform into time
u2[n] = mass_flow_rate2 / area / r2.thermo.density
t_r2[n] = r2.mass / mass_flow_rate2 # residence time in this reactor
Expand Down
29 changes: 2 additions & 27 deletions interfaces/cython/cantera/examples/reactors/surf_pfr.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,33 +110,8 @@
# Set the state of the reservoir to match that of the previous reactor
gas.TDY = r.thermo.TDY
upstream.syncState()

time = 0
all_done = False
sim.set_initial_time(0) # forces reinitialization
while not all_done:
time += dt
sim.advance(time)

if time > 10 * dt:
# check whether surface coverages are in steady state. This will be
# the case if the creation and destruction rates for a surface (but
# not gas) species are equal.
all_done = True

# Note: netProduction = creation - destruction. By supplying the
# surface object as an argument, only the values for the surface
# species are returned by these methods
sdot = surf.get_net_production_rates(surf)
cdot = surf.get_creation_rates(surf)
ddot = surf.get_destruction_rates(surf)

for ks in range(surf.n_species):
ratio = abs(sdot[ks]/(cdot[ks] + ddot[ks]))
if ratio > 1.0e-9:
all_done = False
break

sim.reinitialize()
sim.advance_to_steady_state()
dist = n * rlen * 1.0e3 # distance in mm

if not n % 10:
Expand Down
116 changes: 116 additions & 0 deletions interfaces/cython/cantera/reactor.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,48 @@ cdef class Reactor(ReactorBase):
raise IndexError('No such component: {!r}'.format(name))
return k

property n_vars:
"""
The number of state variables in the reactor.
Equal to:

`Reactor` and `IdealGasReactor`: `n_species` + 3 (mass, volume,
internal energy or temperature).

`ConstPressureReactor` and `IdealGasConstPressureReactor`:
`n_species` + 2 (mass, enthalpy or temperature).
"""
def __get__(self):
return self.reactor.neq()

def get_state(self):
"""
Get the state vector of the reactor.

The order of the variables (i.e. rows) is:

`Reactor` or `IdealGasReactor`:

- 0 - mass
- 1 - volume
- 2 - internal energy or temperature
- 3+ - mass fractions of the species

`ConstPressureReactor` or `IdealGasConstPressureReactor`:

- 0 - mass
- 1 - enthalpy or temperature
- 2+ - mass fractions of the species

You can use the function `component_index` to determine the location
of a specific component
"""
if not self.n_vars:
raise Exception('Reactor empty or network not initialized.')
cdef np.ndarray[np.double_t, ndim=1] y = np.zeros(self.n_vars)
self.reactor.getState(&y[0])
return y


cdef class Reservoir(ReactorBase):
"""
Expand Down Expand Up @@ -941,6 +983,80 @@ cdef class ReactorNet:
def __get__(self):
return self.net.neq()

def get_state(self):
"""
Get the combined state vector of the reactor network.

The combined state vector consists of the concatenated state vectors of
all entities contained.
"""
if not self.n_vars:
raise Exception('ReactorNet empty or not initialized.')
cdef np.ndarray[np.double_t, ndim=1] y = np.zeros(self.n_vars)
self.net.getState(&y[0])
return y

def advance_to_steady_state(self, int max_steps=10000,
double residual_threshold=0., double atol=0.,
pybool return_residuals=False):
r"""
Advance the reactor network in time until steady state is reached.

The steady state is defined by requiring that the state of the system
only changes below a certain threshold. The residual is computed using
feature scaling:

.. math:: r = \left| \frac{x(t + \Delta t) - x(t)}{\text{max}(x) + \text{atol}} \right| \cdot \frac{1}{n_x}

:param max_steps:
Maximum number of steps to be taken
:param residual_threshold:
Threshold below which the feature-scaled residual r should drop such
that the network is defines as steady state. By default,
residual_threshold is 10 times the solver rtol.
:param atol:
The smallest expected value of interest. Used for feature scaling.
By default, this atol is identical to the solver atol.
:param return_residuals:
If set to `True`, this function returns the residual time series
as a vector with length `max_steps`.

"""
# get default tolerances:
if not atol:
atol = self.rtol
if not residual_threshold:
residual_threshold = 10. * self.rtol
if residual_threshold <= self.rtol:
raise Exception('Residual threshold (' + str(residual_threshold) +
') should be below solver rtol (' +
str(self.rtol) + ')')
if return_residuals:
residuals = np.empty(max_steps)
# check if system is initialized
if not self.n_vars:
self.reinitialize()
max_state_values = self.get_state() # denominator for feature scaling
for step in range(max_steps):
previous_state = self.get_state()
# take 10 steps (just to increase speed)
for n1 in range(10):
self.step()
state = self.get_state()
max_state_values = np.maximum(max_state_values, state)
# determine feature_scaled residual
residual = np.linalg.norm((state - previous_state)
/ (max_state_values + atol)) / np.sqrt(self.n_vars)
if return_residuals:
residuals[step] = residual
if residual < residual_threshold:
break
if step == max_steps - 1:
raise Exception('Maximum number of steps reached before convergence'
' below maximum residual')
if return_residuals:
return residuals[:step + 1]

def __reduce__(self):
raise NotImplementedError('ReactorNet object is not picklable')

Expand Down
10 changes: 10 additions & 0 deletions interfaces/cython/cantera/test/test_reactor.py
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,16 @@ def test_ignition3(self):
t,T = self.integrate(100.0)
self.assertTrue(T[-1] < 910) # mixture did not ignite

def test_steady_state(self):
self.setup(900.0, 10*ct.one_atm, 1.0, 20.0)
residuals = self.net.advance_to_steady_state(return_residuals=True)
# test if steady state is reached
self.assertTrue(residuals[-1] < 10. * self.net.rtol)
# regression test; no external basis for these results
self.assertNear(self.combustor.T, 2486.14, 1e-5)
self.assertNear(self.combustor.thermo['H2O'].Y[0], 0.103804, 1e-5)
self.assertNear(self.combustor.thermo['HO2'].Y[0], 7.71296e-06, 1e-5)


class TestConstPressureReactor(utilities.CanteraTest):
"""
Expand Down
5 changes: 5 additions & 0 deletions src/kinetics/ImplicitSurfChem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@ int ImplicitSurfChem::checkMatch(std::vector<ThermoPhase*> m_vec, ThermoPhase* t

void ImplicitSurfChem::getInitialConditions(doublereal t0, size_t lenc,
doublereal* c)
{
getState(c);
}

void ImplicitSurfChem::getState(doublereal* c)
{
size_t loc = 0;
for (size_t n = 0; n < m_nsurf; n++) {
Expand Down
10 changes: 8 additions & 2 deletions src/zeroD/ConstPressureReactor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,16 @@ using namespace std;
namespace Cantera
{

void ConstPressureReactor::getInitialConditions(double t0, size_t leny, double* y)
void ConstPressureReactor::getInitialConditions(double t0, size_t leny,
double* y)
{
getState(y);
}

void ConstPressureReactor::getState(double* y)
{
if (m_thermo == 0) {
throw CanteraError("getInitialConditions",
throw CanteraError("getState",
"Error: reactor is empty.");
}
m_thermo->restoreState(m_state);
Expand Down
9 changes: 7 additions & 2 deletions src/zeroD/FlowReactor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,15 @@ FlowReactor::FlowReactor() :
}

void FlowReactor::getInitialConditions(double t0, size_t leny, double* y)
{
getState(y);
}

void FlowReactor::getState(double* y)
{
if (m_thermo == 0) {
writelog("Error: reactor is empty.\n");
return;
throw CanteraError("getState",
"Error: reactor is empty.");
}
m_thermo->restoreState(m_state);
m_thermo->getMassFractions(y+2);
Expand Down
Loading