Move includes from header to implementation files where possible, and remove unnecessary includes.
348 lines
12 KiB
C++
348 lines
12 KiB
C++
/**
|
|
* @file ChemEquil.h Chemical equilibrium.
|
|
*/
|
|
/*
|
|
* Copyright 2001 California Institute of Technology
|
|
*/
|
|
|
|
#ifndef CT_CHEM_EQUIL_H
|
|
#define CT_CHEM_EQUIL_H
|
|
|
|
// Cantera includes
|
|
#include "cantera/base/ct_defs.h"
|
|
#include "cantera/base/ctexceptions.h"
|
|
#include "cantera/thermo/ThermoPhase.h"
|
|
|
|
#include <memory>
|
|
|
|
namespace Cantera
|
|
{
|
|
|
|
class DenseMatrix;
|
|
/// map property strings to integers
|
|
int _equilflag(const char* xy);
|
|
|
|
/**
|
|
* Chemical equilibrium options. Used internally by class ChemEquil.
|
|
*/
|
|
class EquilOpt
|
|
{
|
|
public:
|
|
EquilOpt() : relTolerance(1.e-8), absElemTol(1.0E-70),maxIterations(1000),
|
|
iterations(0),
|
|
maxStepSize(10.0), propertyPair(TP), contin(false) {}
|
|
|
|
doublereal relTolerance; ///< Relative tolerance
|
|
doublereal absElemTol; ///< Abs Tol in element number
|
|
int maxIterations; ///< Maximum number of iterations
|
|
int iterations; ///< Iteration counter
|
|
|
|
/**
|
|
* Maximum step size. Largest change in any element potential or
|
|
* in log(T) allowed in one Newton step. Default: 10.0
|
|
*/
|
|
doublereal maxStepSize;
|
|
|
|
/**
|
|
* Property pair flag. Determines which two thermodynamic properties
|
|
* are fixed.
|
|
*/
|
|
int propertyPair;
|
|
|
|
/**
|
|
* Continuation flag. Set true if the calculation should be
|
|
* initialized from the last calculation. Otherwise, the
|
|
* calculation will be started from scratch and the initial
|
|
* composition and element potentials estimated.
|
|
*/
|
|
bool contin;
|
|
};
|
|
|
|
template<class M>
|
|
class PropertyCalculator;
|
|
|
|
/**
|
|
* @defgroup equil Chemical Equilibrium
|
|
*
|
|
*/
|
|
|
|
/**
|
|
* Class ChemEquil implements a chemical equilibrium solver for
|
|
* single-phase solutions. It is a "non-stoichiometric" solver in
|
|
* the terminology of Smith and Missen, meaning that every
|
|
* intermediate state is a valid chemical equilibrium state, but
|
|
* does not necessarily satisfy the element constraints. In
|
|
* contrast, the solver implemented in class MultiPhaseEquil uses
|
|
* a "stoichiometric" algorithm, in which each intermediate state
|
|
* satisfies the element constraints but is not a state of
|
|
* chemical equilibrium. Non-stoichiometric methods are faster
|
|
* when they converge, but stoichiometric ones tend to be more
|
|
* robust and can be used also for problems with multiple
|
|
* condensed phases. As expected, the ChemEquil solver is faster
|
|
* than MultiPhaseEquil for many single-phase equilibrium
|
|
* problems (particularly if there are only a few elements but
|
|
* very many species), but can be less stable. Problem
|
|
* situations include low temperatures where only a few species
|
|
* have non-zero mole fractions, precisely stoichiometric
|
|
* compositions (e.g. 2 H2 + O2). In general, if speed is
|
|
* important, this solver should be tried first, and if it fails
|
|
* then use MultiPhaseEquil.
|
|
* @ingroup equil
|
|
*/
|
|
class ChemEquil
|
|
{
|
|
public:
|
|
ChemEquil();
|
|
|
|
//! Constructor combined with the initialization function
|
|
/*!
|
|
* This constructor initializes the ChemEquil object with everything it
|
|
* needs to start solving equilibrium problems.
|
|
* @param s ThermoPhase object that will be used in the equilibrium calls.
|
|
*/
|
|
ChemEquil(thermo_t& s);
|
|
|
|
virtual ~ChemEquil();
|
|
|
|
/*!
|
|
* Equilibrate a phase, holding the elemental composition fixed
|
|
* at the initial value found within the ThermoPhase object *s*.
|
|
*
|
|
* The value of 2 specified properties are obtained by querying the
|
|
* ThermoPhase object. The properties must be already contained
|
|
* within the current thermodynamic state of the system.
|
|
*/
|
|
int equilibrate(thermo_t& s, const char* XY,
|
|
bool useThermoPhaseElementPotentials = false, int loglevel = 0);
|
|
|
|
/*!
|
|
* Compute the equilibrium composition for 2 specified
|
|
* properties and the specified element moles.
|
|
*
|
|
* The 2 specified properties are obtained by querying the
|
|
* ThermoPhase object. The properties must be already contained
|
|
* within the current thermodynamic state of the system.
|
|
*
|
|
* @param s phase object to be equilibrated
|
|
* @param XY property pair to hold constant
|
|
* @param elMoles specified vector of element abundances.
|
|
* @param useThermoPhaseElementPotentials get the initial estimate for the
|
|
* chemical potentials from the ThermoPhase object (true) or create
|
|
* our own estimate (false)
|
|
* @param loglevel Specify amount of debug logging (0 to disable)
|
|
* @return Successful returns are indicated by a return value of 0.
|
|
* Unsuccessful returns are indicated by a return value of -1 for lack
|
|
* of convergence or -3 for a singular jacobian.
|
|
*/
|
|
int equilibrate(thermo_t& s, const char* XY, vector_fp& elMoles,
|
|
bool useThermoPhaseElementPotentials = false, int loglevel = 0);
|
|
const vector_fp& elementPotentials() const {
|
|
return m_lambda;
|
|
}
|
|
|
|
/**
|
|
* Options controlling how the calculation is carried out.
|
|
* @see EquilOptions
|
|
*/
|
|
EquilOpt options;
|
|
|
|
|
|
protected:
|
|
|
|
//! Pointer to the %ThermoPhase object used to initialize this object.
|
|
|
|
/*!
|
|
* This %ThermoPhase object must be compatible with the %ThermoPhase
|
|
* objects input from the equilibrate function. Currently, this
|
|
* means that the 2 %ThermoPhases have to have consist of the same
|
|
* species and elements.
|
|
*/
|
|
thermo_t* m_phase;
|
|
|
|
//! number of atoms of element m in species k.
|
|
doublereal nAtoms(size_t k, size_t m) const {
|
|
return m_comp[k*m_mm + m];
|
|
}
|
|
|
|
/*!
|
|
* Prepare for equilibrium calculations.
|
|
* @param s object representing the solution phase.
|
|
*/
|
|
void initialize(thermo_t& s);
|
|
|
|
/*!
|
|
* Set mixture to an equilibrium state consistent with specified
|
|
* element potentials and temperature.
|
|
*
|
|
* @param s mixture to be updated
|
|
* @param x vector of non-dimensional element potentials
|
|
* \f[ \lambda_m/RT \f].
|
|
* @param t temperature in K.
|
|
*/
|
|
void setToEquilState(thermo_t& s,
|
|
const vector_fp& x, doublereal t);
|
|
|
|
//! Estimate the initial mole numbers. This version borrows from the
|
|
//! MultiPhaseEquil solver.
|
|
int setInitialMoles(thermo_t& s, vector_fp& elMoleGoal, int loglevel = 0);
|
|
|
|
//! Generate a starting estimate for the element potentials.
|
|
int estimateElementPotentials(thermo_t& s, vector_fp& lambda,
|
|
vector_fp& elMolesGoal, int loglevel = 0);
|
|
|
|
/*!
|
|
* Do a calculation of the element potentials using the Brinkley method,
|
|
* p. 129 Smith and Missen.
|
|
*
|
|
* We have found that the previous estimate may not be good enough to
|
|
* avoid drastic numerical issues associated with the use of a numerically
|
|
* generated jacobian used in the main algorithm.
|
|
*
|
|
* The Brinkley algorithm, here, assumes a constant T, P system and uses a
|
|
* linearized analytical Jacobian that turns out to be very stable even
|
|
* given bad initial guesses.
|
|
*
|
|
* The pressure and temperature to be used are in the ThermoPhase object
|
|
* input into the routine.
|
|
*
|
|
* The initial guess for the element potentials used by this routine is
|
|
* taken from the input vector, x.
|
|
*
|
|
* elMoles is the input element abundance vector to be matched.
|
|
*
|
|
* Nonideal phases are handled in principle. This is done by calculating
|
|
* the activity coefficients and adding them into the formula in the
|
|
* correct position. However, these are treated as a rhs contribution
|
|
* only. Therefore, convergence might be a problem. This has not been
|
|
* tested. Also molality based unit systems aren't handled.
|
|
*
|
|
* On return, int return value contains the success code:
|
|
* - 0 - successful
|
|
* - 1 - unsuccessful, max num iterations exceeded
|
|
* - -3 - unsuccessful, singular jacobian
|
|
*
|
|
* NOTE: update for activity coefficients.
|
|
*/
|
|
int estimateEP_Brinkley(thermo_t& s, vector_fp& lambda, vector_fp& elMoles);
|
|
|
|
//! Find an acceptable step size and take it.
|
|
/*!
|
|
* The original implementation employed a line search technique that
|
|
* enforced a reduction in the norm of the residual at every successful
|
|
* step. Unfortunately, this method created false convergence errors near
|
|
* the end of a significant number of steps, usually special conditions
|
|
* where there were stoichiometric constraints.
|
|
*
|
|
* This new method just does a delta damping approach, based on limiting
|
|
* the jump in the dimensionless element potentials. Mole fractions are
|
|
* limited to a factor of 2 jump in the values from this method. Near
|
|
* convergence, the delta damping gets out of the way.
|
|
*/
|
|
int dampStep(thermo_t& s, vector_fp& oldx,
|
|
double oldf, vector_fp& grad, vector_fp& step, vector_fp& x,
|
|
double& f, vector_fp& elmols, double xval, double yval);
|
|
|
|
/**
|
|
* Evaluates the residual vector F, of length #m_mm
|
|
*/
|
|
void equilResidual(thermo_t& s, const vector_fp& x,
|
|
const vector_fp& elmtotal, vector_fp& resid,
|
|
double xval, double yval, int loglevel = 0);
|
|
|
|
void equilJacobian(thermo_t& s, vector_fp& x,
|
|
const vector_fp& elmols, DenseMatrix& jac,
|
|
double xval, double yval, int loglevel = 0);
|
|
|
|
void adjustEloc(thermo_t& s, vector_fp& elMolesGoal);
|
|
|
|
//! Update internally stored state information.
|
|
void update(const thermo_t& s);
|
|
|
|
/**
|
|
* Given a vector of dimensionless element abundances, this routine
|
|
* calculates the moles of the elements and the moles of the species.
|
|
*
|
|
* @param[in] x = current dimensionless element potentials..
|
|
*/
|
|
double calcEmoles(thermo_t& s, vector_fp& x,
|
|
const double& n_t, const vector_fp& Xmol_i_calc,
|
|
vector_fp& eMolesCalc, vector_fp& n_i_calc,
|
|
double pressureConst);
|
|
|
|
size_t m_mm; //!< number of elements in the phase
|
|
size_t m_kk; //!< number of species in the phase
|
|
size_t m_skip;
|
|
|
|
/**
|
|
* This is equal to the rank of the stoichiometric coefficient
|
|
* matrix when it is computed. It's initialized to #m_mm.
|
|
*/
|
|
size_t m_nComponents;
|
|
|
|
std::auto_ptr<PropertyCalculator<thermo_t> > m_p1, m_p2;
|
|
|
|
/**
|
|
* Current value of the mole fractions in the single phase.
|
|
* -> length = #m_kk.
|
|
*/
|
|
vector_fp m_molefractions;
|
|
/**
|
|
* Current value of the dimensional element potentials
|
|
* -> length = #m_mm
|
|
*/
|
|
vector_fp m_lambda;
|
|
|
|
/*
|
|
* Current value of the sum of the element abundances given the
|
|
* current element potentials.
|
|
*/
|
|
doublereal m_elementTotalSum;
|
|
/*
|
|
* Current value of the element mole fractions. Note these aren't
|
|
* the goal element mole fractions.
|
|
*/
|
|
vector_fp m_elementmolefracs;
|
|
vector_fp m_reswork;
|
|
vector_fp m_jwork1;
|
|
vector_fp m_jwork2;
|
|
|
|
/*
|
|
* Storage of the element compositions
|
|
* natom(k,m) = m_comp[k*m_mm+ m];
|
|
*/
|
|
vector_fp m_comp;
|
|
doublereal m_temp, m_dens;
|
|
doublereal m_p0;
|
|
|
|
/**
|
|
* Index of the element id corresponding to the electric charge of each
|
|
* species. Equal to -1 if there is no such element id.
|
|
*/
|
|
size_t m_eloc;
|
|
|
|
vector_fp m_startSoln;
|
|
|
|
vector_fp m_grt;
|
|
vector_fp m_mu_RT;
|
|
|
|
/**
|
|
* Dimensionless values of the gibbs free energy for the
|
|
* standard state of each species, at the temperature and
|
|
* pressure of the solution (the star standard state).
|
|
*/
|
|
vector_fp m_muSS_RT;
|
|
std::vector<size_t> m_component;
|
|
|
|
//! element fractional cutoff, below which the element will be zeroed.
|
|
double m_elemFracCutoff;
|
|
bool m_doResPerturb;
|
|
|
|
std::vector<size_t> m_orderVectorElements;
|
|
std::vector<size_t> m_orderVectorSpecies;
|
|
};
|
|
|
|
extern int ChemEquil_print_lvl;
|
|
|
|
}
|
|
|
|
#endif
|