This improves interoperability when working with mechanisms which use differing conventions for naming species using uppercase or lowercase.
1004 lines
33 KiB
C++
1004 lines
33 KiB
C++
//! @file StFlow.cpp
|
|
|
|
// This file is part of Cantera. See License.txt in the top-level directory or
|
|
// at http://www.cantera.org/license.txt for license and copyright information.
|
|
|
|
#include "cantera/oneD/StFlow.h"
|
|
#include "cantera/base/ctml.h"
|
|
#include "cantera/transport/TransportBase.h"
|
|
#include "cantera/numerics/funcs.h"
|
|
|
|
using namespace std;
|
|
|
|
namespace Cantera
|
|
{
|
|
|
|
StFlow::StFlow(IdealGasPhase* ph, size_t nsp, size_t points) :
|
|
Domain1D(nsp+4, points),
|
|
m_press(-1.0),
|
|
m_nsp(nsp),
|
|
m_thermo(0),
|
|
m_kin(0),
|
|
m_trans(0),
|
|
m_epsilon_left(0.0),
|
|
m_epsilon_right(0.0),
|
|
m_do_soret(false),
|
|
m_do_multicomponent(false),
|
|
m_do_radiation(false),
|
|
m_kExcessLeft(0),
|
|
m_kExcessRight(0)
|
|
{
|
|
m_type = cFlowType;
|
|
m_points = points;
|
|
m_thermo = ph;
|
|
|
|
if (ph == 0) {
|
|
return; // used to create a dummy object
|
|
}
|
|
|
|
size_t nsp2 = m_thermo->nSpecies();
|
|
if (nsp2 != m_nsp) {
|
|
m_nsp = nsp2;
|
|
Domain1D::resize(m_nsp+4, points);
|
|
}
|
|
|
|
// make a local copy of the species molecular weight vector
|
|
m_wt = m_thermo->molecularWeights();
|
|
|
|
// the species mass fractions are the last components in the solution
|
|
// vector, so the total number of components is the number of species
|
|
// plus the offset of the first mass fraction.
|
|
m_nv = c_offset_Y + m_nsp;
|
|
|
|
// enable all species equations by default
|
|
m_do_species.resize(m_nsp, true);
|
|
|
|
// but turn off the energy equation at all points
|
|
m_do_energy.resize(m_points,false);
|
|
|
|
m_diff.resize(m_nsp*m_points);
|
|
m_multidiff.resize(m_nsp*m_nsp*m_points);
|
|
m_flux.resize(m_nsp,m_points);
|
|
m_wdot.resize(m_nsp,m_points, 0.0);
|
|
m_ybar.resize(m_nsp);
|
|
m_qdotRadiation.resize(m_points, 0.0);
|
|
|
|
//-------------- default solution bounds --------------------
|
|
setBounds(0, -1e20, 1e20); // no bounds on u
|
|
setBounds(1, -1e20, 1e20); // V
|
|
setBounds(2, 200.0, 1e9); // temperature bounds
|
|
setBounds(3, -1e20, 1e20); // lambda should be negative
|
|
|
|
// mass fraction bounds
|
|
for (size_t k = 0; k < m_nsp; k++) {
|
|
setBounds(4+k, -1.0e-7, 1.0e5);
|
|
}
|
|
|
|
//-------------------- grid refinement -------------------------
|
|
m_refiner->setActive(0, false);
|
|
m_refiner->setActive(1, false);
|
|
m_refiner->setActive(2, false);
|
|
m_refiner->setActive(3, false);
|
|
|
|
vector_fp gr;
|
|
for (size_t ng = 0; ng < m_points; ng++) {
|
|
gr.push_back(1.0*ng/m_points);
|
|
}
|
|
setupGrid(m_points, gr.data());
|
|
setID("stagnation flow");
|
|
|
|
// Find indices for radiating species
|
|
m_kRadiating.resize(2, npos);
|
|
m_kRadiating[0] = m_thermo->speciesIndex("CO2");
|
|
m_kRadiating[1] = m_thermo->speciesIndex("H2O");
|
|
}
|
|
|
|
void StFlow::resize(size_t ncomponents, size_t points)
|
|
{
|
|
Domain1D::resize(ncomponents, points);
|
|
m_rho.resize(m_points, 0.0);
|
|
m_wtm.resize(m_points, 0.0);
|
|
m_cp.resize(m_points, 0.0);
|
|
m_visc.resize(m_points, 0.0);
|
|
m_tcon.resize(m_points, 0.0);
|
|
|
|
m_diff.resize(m_nsp*m_points);
|
|
if (m_do_multicomponent) {
|
|
m_multidiff.resize(m_nsp*m_nsp*m_points);
|
|
m_dthermal.resize(m_nsp, m_points, 0.0);
|
|
}
|
|
m_flux.resize(m_nsp,m_points);
|
|
m_wdot.resize(m_nsp,m_points, 0.0);
|
|
m_do_energy.resize(m_points,false);
|
|
m_qdotRadiation.resize(m_points, 0.0);
|
|
m_fixedtemp.resize(m_points);
|
|
|
|
m_dz.resize(m_points-1);
|
|
m_z.resize(m_points);
|
|
}
|
|
|
|
void StFlow::setupGrid(size_t n, const doublereal* z)
|
|
{
|
|
resize(m_nv, n);
|
|
|
|
m_z[0] = z[0];
|
|
for (size_t j = 1; j < m_points; j++) {
|
|
if (z[j] <= z[j-1]) {
|
|
throw CanteraError("StFlow::setupGrid",
|
|
"grid points must be monotonically increasing");
|
|
}
|
|
m_z[j] = z[j];
|
|
m_dz[j-1] = m_z[j] - m_z[j-1];
|
|
}
|
|
}
|
|
|
|
void StFlow::resetBadValues(double* xg) {
|
|
double* x = xg + loc();
|
|
for (size_t j = 0; j < m_points; j++) {
|
|
double* Y = x + m_nv*j + c_offset_Y;
|
|
m_thermo->setMassFractions(Y);
|
|
m_thermo->getMassFractions(Y);
|
|
}
|
|
}
|
|
|
|
|
|
void StFlow::setTransport(Transport& trans, bool withSoret)
|
|
{
|
|
m_trans = &trans;
|
|
m_do_soret = withSoret;
|
|
m_do_multicomponent = (m_trans->transportType() == "Multi");
|
|
|
|
m_diff.resize(m_nsp*m_points);
|
|
if (m_do_multicomponent) {
|
|
m_multidiff.resize(m_nsp*m_nsp*m_points);
|
|
m_dthermal.resize(m_nsp, m_points, 0.0);
|
|
} else if (withSoret) {
|
|
throw CanteraError("setTransport",
|
|
"Thermal diffusion (the Soret effect) "
|
|
"requires using a multicomponent transport model.");
|
|
}
|
|
}
|
|
|
|
void StFlow::enableSoret(bool withSoret)
|
|
{
|
|
if (m_do_multicomponent) {
|
|
m_do_soret = withSoret;
|
|
} else {
|
|
throw CanteraError("setTransport",
|
|
"Thermal diffusion (the Soret effect) "
|
|
"requires using a multicomponent transport model.");
|
|
}
|
|
}
|
|
|
|
void StFlow::_getInitialSoln(double* x)
|
|
{
|
|
for (size_t j = 0; j < m_points; j++) {
|
|
T(x,j) = m_thermo->temperature();
|
|
m_thermo->getMassFractions(&Y(x, 0, j));
|
|
}
|
|
}
|
|
|
|
void StFlow::setGas(const doublereal* x, size_t j)
|
|
{
|
|
m_thermo->setTemperature(T(x,j));
|
|
const doublereal* yy = x + m_nv*j + c_offset_Y;
|
|
m_thermo->setMassFractions_NoNorm(yy);
|
|
m_thermo->setPressure(m_press);
|
|
}
|
|
|
|
void StFlow::setGasAtMidpoint(const doublereal* x, size_t j)
|
|
{
|
|
m_thermo->setTemperature(0.5*(T(x,j)+T(x,j+1)));
|
|
const doublereal* yyj = x + m_nv*j + c_offset_Y;
|
|
const doublereal* yyjp = x + m_nv*(j+1) + c_offset_Y;
|
|
for (size_t k = 0; k < m_nsp; k++) {
|
|
m_ybar[k] = 0.5*(yyj[k] + yyjp[k]);
|
|
}
|
|
m_thermo->setMassFractions_NoNorm(m_ybar.data());
|
|
m_thermo->setPressure(m_press);
|
|
}
|
|
|
|
void StFlow::_finalize(const doublereal* x)
|
|
{
|
|
size_t nz = m_zfix.size();
|
|
bool e = m_do_energy[0];
|
|
for (size_t j = 0; j < m_points; j++) {
|
|
if (e || nz == 0) {
|
|
m_fixedtemp[j] = T(x, j);
|
|
} else {
|
|
double zz = (z(j) - z(0))/(z(m_points - 1) - z(0));
|
|
double tt = linearInterp(zz, m_zfix, m_tfix);
|
|
m_fixedtemp[j] = tt;
|
|
}
|
|
}
|
|
if (e) {
|
|
solveEnergyEqn();
|
|
}
|
|
}
|
|
|
|
void StFlow::eval(size_t jg, doublereal* xg,
|
|
doublereal* rg, integer* diagg, doublereal rdt)
|
|
{
|
|
// if evaluating a Jacobian, and the global point is outside the domain of
|
|
// influence for this domain, then skip evaluating the residual
|
|
if (jg != npos && (jg + 1 < firstPoint() || jg > lastPoint() + 1)) {
|
|
return;
|
|
}
|
|
|
|
// if evaluating a Jacobian, compute the steady-state residual
|
|
if (jg != npos) {
|
|
rdt = 0.0;
|
|
}
|
|
|
|
// start of local part of global arrays
|
|
doublereal* x = xg + loc();
|
|
doublereal* rsd = rg + loc();
|
|
integer* diag = diagg + loc();
|
|
|
|
size_t jmin, jmax;
|
|
if (jg == npos) { // evaluate all points
|
|
jmin = 0;
|
|
jmax = m_points - 1;
|
|
} else { // evaluate points for Jacobian
|
|
size_t jpt = (jg == 0) ? 0 : jg - firstPoint();
|
|
jmin = std::max<size_t>(jpt, 1) - 1;
|
|
jmax = std::min(jpt+1,m_points-1);
|
|
}
|
|
|
|
// properties are computed for grid points from j0 to j1
|
|
size_t j0 = std::max<size_t>(jmin, 1) - 1;
|
|
size_t j1 = std::min(jmax+1,m_points-1);
|
|
|
|
// ------------ update properties ------------
|
|
|
|
updateThermo(x, j0, j1);
|
|
if (jg == npos) {
|
|
// update transport properties only if a Jacobian is not being evaluated
|
|
updateTransport(x, j0, j1);
|
|
|
|
double* Yleft = x + index(c_offset_Y, jmin);
|
|
m_kExcessLeft = distance(Yleft, max_element(Yleft, Yleft + m_nsp));
|
|
double* Yright = x + index(c_offset_Y, jmax);
|
|
m_kExcessRight = distance(Yright, max_element(Yright, Yright + m_nsp));
|
|
}
|
|
|
|
// update the species diffusive mass fluxes whether or not a
|
|
// Jacobian is being evaluated
|
|
updateDiffFluxes(x, j0, j1);
|
|
|
|
//----------------------------------------------------
|
|
// evaluate the residual equations at all required
|
|
// grid points
|
|
//----------------------------------------------------
|
|
|
|
// calculation of qdotRadiation
|
|
|
|
// The simple radiation model used was established by Y. Liu and B. Rogg [Y.
|
|
// Liu and B. Rogg, Modelling of thermally radiating diffusion flames with
|
|
// detailed chemistry and transport, EUROTHERM Seminars, 17:114-127, 1991].
|
|
// This model uses the optically thin limit and the gray-gas approximation
|
|
// to simply calculate a volume specified heat flux out of the Planck
|
|
// absorption coefficients, the boundary emissivities and the temperature.
|
|
// The model considers only CO2 and H2O as radiating species. Polynomial
|
|
// lines calculate the species Planck coefficients for H2O and CO2. The data
|
|
// for the lines is taken from the RADCAL program [Grosshandler, W. L.,
|
|
// RADCAL: A Narrow-Band Model for Radiation Calculations in a Combustion
|
|
// Environment, NIST technical note 1402, 1993]. The coefficients for the
|
|
// polynomials are taken from [http://www.sandia.gov/TNF/radiation.html].
|
|
|
|
if (m_do_radiation) {
|
|
// variable definitions for the Planck absorption coefficient and the
|
|
// radiation calculation:
|
|
doublereal k_P_ref = 1.0*OneAtm;
|
|
|
|
// polynomial coefficients:
|
|
const doublereal c_H2O[6] = {-0.23093, -1.12390, 9.41530, -2.99880,
|
|
0.51382, -1.86840e-5};
|
|
const doublereal c_CO2[6] = {18.741, -121.310, 273.500, -194.050,
|
|
56.310, -5.8169};
|
|
|
|
// calculation of the two boundary values
|
|
double boundary_Rad_left = m_epsilon_left * StefanBoltz * pow(T(x, 0), 4);
|
|
double boundary_Rad_right = m_epsilon_right * StefanBoltz * pow(T(x, m_points - 1), 4);
|
|
|
|
// loop over all grid points
|
|
for (size_t j = jmin; j < jmax; j++) {
|
|
// helping variable for the calculation
|
|
double radiative_heat_loss = 0;
|
|
|
|
// calculation of the mean Planck absorption coefficient
|
|
double k_P = 0;
|
|
// absorption coefficient for H2O
|
|
if (m_kRadiating[1] != npos) {
|
|
double k_P_H2O = 0;
|
|
for (size_t n = 0; n <= 5; n++) {
|
|
k_P_H2O += c_H2O[n] * pow(1000 / T(x, j), (double) n);
|
|
}
|
|
k_P_H2O /= k_P_ref;
|
|
k_P += m_press * X(x, m_kRadiating[1], j) * k_P_H2O;
|
|
}
|
|
// absorption coefficient for CO2
|
|
if (m_kRadiating[0] != npos) {
|
|
double k_P_CO2 = 0;
|
|
for (size_t n = 0; n <= 5; n++) {
|
|
k_P_CO2 += c_CO2[n] * pow(1000 / T(x, j), (double) n);
|
|
}
|
|
k_P_CO2 /= k_P_ref;
|
|
k_P += m_press * X(x, m_kRadiating[0], j) * k_P_CO2;
|
|
}
|
|
|
|
// calculation of the radiative heat loss term
|
|
radiative_heat_loss = 2 * k_P *(2 * StefanBoltz * pow(T(x, j), 4)
|
|
- boundary_Rad_left - boundary_Rad_right);
|
|
|
|
// set the radiative heat loss vector
|
|
m_qdotRadiation[j] = radiative_heat_loss;
|
|
}
|
|
}
|
|
|
|
for (size_t j = jmin; j <= jmax; j++) {
|
|
//----------------------------------------------
|
|
// left boundary
|
|
//----------------------------------------------
|
|
|
|
if (j == 0) {
|
|
// these may be modified by a boundary object
|
|
|
|
// Continuity. This propagates information right-to-left, since
|
|
// rho_u at point 0 is dependent on rho_u at point 1, but not on
|
|
// mdot from the inlet.
|
|
rsd[index(c_offset_U,0)] =
|
|
-(rho_u(x,1) - rho_u(x,0))/m_dz[0]
|
|
-(density(1)*V(x,1) + density(0)*V(x,0));
|
|
|
|
// the inlet (or other) object connected to this one will modify
|
|
// these equations by subtracting its values for V, T, and mdot. As
|
|
// a result, these residual equations will force the solution
|
|
// variables to the values for the boundary object
|
|
rsd[index(c_offset_V,0)] = V(x,0);
|
|
rsd[index(c_offset_T,0)] = T(x,0);
|
|
rsd[index(c_offset_L,0)] = -rho_u(x,0);
|
|
|
|
// The default boundary condition for species is zero flux. However,
|
|
// the boundary object may modify this.
|
|
double sum = 0.0;
|
|
for (size_t k = 0; k < m_nsp; k++) {
|
|
sum += Y(x,k,0);
|
|
rsd[index(c_offset_Y + k, 0)] =
|
|
-(m_flux(k,0) + rho_u(x,0)* Y(x,k,0));
|
|
}
|
|
rsd[index(c_offset_Y + leftExcessSpecies(), 0)] = 1.0 - sum;
|
|
} else if (j == m_points - 1) {
|
|
evalRightBoundary(x, rsd, diag, rdt);
|
|
} else { // interior points
|
|
evalContinuity(j, x, rsd, diag, rdt);
|
|
|
|
//------------------------------------------------
|
|
// Radial momentum equation
|
|
//
|
|
// \rho dV/dt + \rho u dV/dz + \rho V^2
|
|
// = d(\mu dV/dz)/dz - lambda
|
|
//-------------------------------------------------
|
|
rsd[index(c_offset_V,j)]
|
|
= (shear(x,j) - lambda(x,j) - rho_u(x,j)*dVdz(x,j)
|
|
- m_rho[j]*V(x,j)*V(x,j))/m_rho[j]
|
|
- rdt*(V(x,j) - V_prev(j));
|
|
diag[index(c_offset_V, j)] = 1;
|
|
|
|
//-------------------------------------------------
|
|
// Species equations
|
|
//
|
|
// \rho dY_k/dt + \rho u dY_k/dz + dJ_k/dz
|
|
// = M_k\omega_k
|
|
//-------------------------------------------------
|
|
getWdot(x,j);
|
|
for (size_t k = 0; k < m_nsp; k++) {
|
|
double convec = rho_u(x,j)*dYdz(x,k,j);
|
|
double diffus = 2.0*(m_flux(k,j) - m_flux(k,j-1))
|
|
/ (z(j+1) - z(j-1));
|
|
rsd[index(c_offset_Y + k, j)]
|
|
= (m_wt[k]*(wdot(k,j))
|
|
- convec - diffus)/m_rho[j]
|
|
- rdt*(Y(x,k,j) - Y_prev(k,j));
|
|
diag[index(c_offset_Y + k, j)] = 1;
|
|
}
|
|
|
|
//-----------------------------------------------
|
|
// energy equation
|
|
//
|
|
// \rho c_p dT/dt + \rho c_p u dT/dz
|
|
// = d(k dT/dz)/dz
|
|
// - sum_k(\omega_k h_k_ref)
|
|
// - sum_k(J_k c_p_k / M_k) dT/dz
|
|
//-----------------------------------------------
|
|
if (m_do_energy[j]) {
|
|
setGas(x,j);
|
|
|
|
// heat release term
|
|
const vector_fp& h_RT = m_thermo->enthalpy_RT_ref();
|
|
const vector_fp& cp_R = m_thermo->cp_R_ref();
|
|
double sum = 0.0;
|
|
double sum2 = 0.0;
|
|
for (size_t k = 0; k < m_nsp; k++) {
|
|
double flxk = 0.5*(m_flux(k,j-1) + m_flux(k,j));
|
|
sum += wdot(k,j)*h_RT[k];
|
|
sum2 += flxk*cp_R[k]/m_wt[k];
|
|
}
|
|
sum *= GasConstant * T(x,j);
|
|
double dtdzj = dTdz(x,j);
|
|
sum2 *= GasConstant * dtdzj;
|
|
|
|
rsd[index(c_offset_T, j)] = - m_cp[j]*rho_u(x,j)*dtdzj
|
|
- divHeatFlux(x,j) - sum - sum2;
|
|
rsd[index(c_offset_T, j)] /= (m_rho[j]*m_cp[j]);
|
|
rsd[index(c_offset_T, j)] -= rdt*(T(x,j) - T_prev(j));
|
|
rsd[index(c_offset_T, j)] -= (m_qdotRadiation[j] / (m_rho[j] * m_cp[j]));
|
|
diag[index(c_offset_T, j)] = 1;
|
|
} else {
|
|
// residual equations if the energy equation is disabled
|
|
rsd[index(c_offset_T, j)] = T(x,j) - T_fixed(j);
|
|
diag[index(c_offset_T, j)] = 0;
|
|
}
|
|
|
|
rsd[index(c_offset_L, j)] = lambda(x,j) - lambda(x,j-1);
|
|
diag[index(c_offset_L, j)] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
void StFlow::updateTransport(doublereal* x, size_t j0, size_t j1)
|
|
{
|
|
if (m_do_multicomponent) {
|
|
for (size_t j = j0; j < j1; j++) {
|
|
setGasAtMidpoint(x,j);
|
|
doublereal wtm = m_thermo->meanMolecularWeight();
|
|
doublereal rho = m_thermo->density();
|
|
m_visc[j] = (m_dovisc ? m_trans->viscosity() : 0.0);
|
|
m_trans->getMultiDiffCoeffs(m_nsp, &m_multidiff[mindex(0,0,j)]);
|
|
|
|
// Use m_diff as storage for the factor outside the summation
|
|
for (size_t k = 0; k < m_nsp; k++) {
|
|
m_diff[k+j*m_nsp] = m_wt[k] * rho / (wtm*wtm);
|
|
}
|
|
|
|
m_tcon[j] = m_trans->thermalConductivity();
|
|
if (m_do_soret) {
|
|
m_trans->getThermalDiffCoeffs(m_dthermal.ptrColumn(0) + j*m_nsp);
|
|
}
|
|
}
|
|
} else { // mixture averaged transport
|
|
for (size_t j = j0; j < j1; j++) {
|
|
setGasAtMidpoint(x,j);
|
|
m_visc[j] = (m_dovisc ? m_trans->viscosity() : 0.0);
|
|
m_trans->getMixDiffCoeffs(&m_diff[j*m_nsp]);
|
|
m_tcon[j] = m_trans->thermalConductivity();
|
|
}
|
|
}
|
|
}
|
|
|
|
void StFlow::showSolution(const doublereal* x)
|
|
{
|
|
writelog(" Pressure: {:10.4g} Pa\n", m_press);
|
|
|
|
Domain1D::showSolution(x);
|
|
|
|
if (m_do_radiation) {
|
|
writeline('-', 79, false, true);
|
|
writelog("\n z radiative heat loss");
|
|
writeline('-', 79, false, true);
|
|
for (size_t j = 0; j < m_points; j++) {
|
|
writelog("\n {:10.4g} {:10.4g}", m_z[j], m_qdotRadiation[j]);
|
|
}
|
|
writelog("\n");
|
|
}
|
|
}
|
|
|
|
void StFlow::updateDiffFluxes(const doublereal* x, size_t j0, size_t j1)
|
|
{
|
|
if (m_do_multicomponent) {
|
|
for (size_t j = j0; j < j1; j++) {
|
|
double dz = z(j+1) - z(j);
|
|
for (size_t k = 0; k < m_nsp; k++) {
|
|
doublereal sum = 0.0;
|
|
for (size_t m = 0; m < m_nsp; m++) {
|
|
sum += m_wt[m] * m_multidiff[mindex(k,m,j)] * (X(x,m,j+1)-X(x,m,j));
|
|
}
|
|
m_flux(k,j) = sum * m_diff[k+j*m_nsp] / dz;
|
|
}
|
|
}
|
|
} else {
|
|
for (size_t j = j0; j < j1; j++) {
|
|
double sum = 0.0;
|
|
double wtm = m_wtm[j];
|
|
double rho = density(j);
|
|
double dz = z(j+1) - z(j);
|
|
for (size_t k = 0; k < m_nsp; k++) {
|
|
m_flux(k,j) = m_wt[k]*(rho*m_diff[k+m_nsp*j]/wtm);
|
|
m_flux(k,j) *= (X(x,k,j) - X(x,k,j+1))/dz;
|
|
sum -= m_flux(k,j);
|
|
}
|
|
// correction flux to insure that \sum_k Y_k V_k = 0.
|
|
for (size_t k = 0; k < m_nsp; k++) {
|
|
m_flux(k,j) += sum*Y(x,k,j);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (m_do_soret) {
|
|
for (size_t m = j0; m < j1; m++) {
|
|
double gradlogT = 2.0 * (T(x,m+1) - T(x,m)) /
|
|
((T(x,m+1) + T(x,m)) * (z(m+1) - z(m)));
|
|
for (size_t k = 0; k < m_nsp; k++) {
|
|
m_flux(k,m) -= m_dthermal(k,m)*gradlogT;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
string StFlow::componentName(size_t n) const
|
|
{
|
|
switch (n) {
|
|
case 0:
|
|
return "u";
|
|
case 1:
|
|
return "V";
|
|
case 2:
|
|
return "T";
|
|
case 3:
|
|
return "lambda";
|
|
default:
|
|
if (n >= c_offset_Y && n < (c_offset_Y + m_nsp)) {
|
|
return m_thermo->speciesName(n - c_offset_Y);
|
|
} else {
|
|
return "<unknown>";
|
|
}
|
|
}
|
|
}
|
|
|
|
size_t StFlow::componentIndex(const std::string& name) const
|
|
{
|
|
if (name=="u") {
|
|
return 0;
|
|
} else if (name=="V") {
|
|
return 1;
|
|
} else if (name=="T") {
|
|
return 2;
|
|
} else if (name=="lambda") {
|
|
return 3;
|
|
} else {
|
|
for (size_t n=4; n<m_nsp+4; n++) {
|
|
if (componentName(n)==name) {
|
|
return n;
|
|
}
|
|
}
|
|
}
|
|
return npos;
|
|
}
|
|
|
|
void StFlow::restore(const XML_Node& dom, doublereal* soln, int loglevel)
|
|
{
|
|
Domain1D::restore(dom, soln, loglevel);
|
|
vector<string> ignored;
|
|
size_t nsp = m_thermo->nSpecies();
|
|
vector_int did_species(nsp, 0);
|
|
|
|
vector<XML_Node*> str = dom.getChildren("string");
|
|
for (size_t istr = 0; istr < str.size(); istr++) {
|
|
const XML_Node& nd = *str[istr];
|
|
writelog(nd["title"]+": "+nd.value()+"\n");
|
|
}
|
|
|
|
double pp = getFloat(dom, "pressure", "pressure");
|
|
setPressure(pp);
|
|
vector<XML_Node*> d = dom.child("grid_data").getChildren("floatArray");
|
|
vector_fp x;
|
|
size_t np = 0;
|
|
bool readgrid = false, wrote_header = false;
|
|
for (size_t n = 0; n < d.size(); n++) {
|
|
const XML_Node& fa = *d[n];
|
|
string nm = fa["title"];
|
|
if (nm == "z") {
|
|
getFloatArray(fa,x,false);
|
|
np = x.size();
|
|
if (loglevel >= 2) {
|
|
writelog("Grid contains {} points.\n", np);
|
|
}
|
|
readgrid = true;
|
|
setupGrid(np, x.data());
|
|
}
|
|
}
|
|
if (!readgrid) {
|
|
throw CanteraError("StFlow::restore",
|
|
"domain contains no grid points.");
|
|
}
|
|
|
|
debuglog("Importing datasets:\n", loglevel >= 2);
|
|
for (size_t n = 0; n < d.size(); n++) {
|
|
const XML_Node& fa = *d[n];
|
|
string nm = fa["title"];
|
|
getFloatArray(fa,x,false);
|
|
if (nm == "u") {
|
|
debuglog("axial velocity ", loglevel >= 2);
|
|
if (x.size() != np) {
|
|
throw CanteraError("StFlow::restore",
|
|
"axial velocity array size error");
|
|
}
|
|
for (size_t j = 0; j < np; j++) {
|
|
soln[index(0,j)] = x[j];
|
|
}
|
|
} else if (nm == "z") {
|
|
; // already read grid
|
|
} else if (nm == "V") {
|
|
debuglog("radial velocity ", loglevel >= 2);
|
|
if (x.size() != np) {
|
|
throw CanteraError("StFlow::restore",
|
|
"radial velocity array size error");
|
|
}
|
|
for (size_t j = 0; j < np; j++) {
|
|
soln[index(1,j)] = x[j];
|
|
}
|
|
} else if (nm == "T") {
|
|
debuglog("temperature ", loglevel >= 2);
|
|
if (x.size() != np) {
|
|
throw CanteraError("StFlow::restore",
|
|
"temperature array size error");
|
|
}
|
|
for (size_t j = 0; j < np; j++) {
|
|
soln[index(2,j)] = x[j];
|
|
}
|
|
|
|
// For fixed-temperature simulations, use the imported temperature
|
|
// profile by default. If this is not desired, call
|
|
// setFixedTempProfile *after* restoring the solution.
|
|
vector_fp zz(np);
|
|
for (size_t jj = 0; jj < np; jj++) {
|
|
zz[jj] = (grid(jj) - zmin())/(zmax() - zmin());
|
|
}
|
|
setFixedTempProfile(zz, x);
|
|
} else if (nm == "L") {
|
|
debuglog("lambda ", loglevel >= 2);
|
|
if (x.size() != np) {
|
|
throw CanteraError("StFlow::restore",
|
|
"lambda arary size error");
|
|
}
|
|
for (size_t j = 0; j < np; j++) {
|
|
soln[index(3,j)] = x[j];
|
|
}
|
|
} else if (m_thermo->speciesIndex(nm) != npos) {
|
|
debuglog(nm+" ", loglevel >= 2);
|
|
if (x.size() == np) {
|
|
size_t k = m_thermo->speciesIndex(nm);
|
|
did_species[k] = 1;
|
|
for (size_t j = 0; j < np; j++) {
|
|
soln[index(k+4,j)] = x[j];
|
|
}
|
|
}
|
|
} else {
|
|
ignored.push_back(nm);
|
|
}
|
|
}
|
|
|
|
if (loglevel >=2 && !ignored.empty()) {
|
|
writelog("\n\n");
|
|
writelog("Ignoring datasets:\n");
|
|
size_t nn = ignored.size();
|
|
for (size_t n = 0; n < nn; n++) {
|
|
writelog(ignored[n]+" ");
|
|
}
|
|
}
|
|
|
|
if (loglevel >= 1) {
|
|
for (size_t ks = 0; ks < nsp; ks++) {
|
|
if (did_species[ks] == 0) {
|
|
if (!wrote_header) {
|
|
writelog("Missing data for species:\n");
|
|
wrote_header = true;
|
|
}
|
|
writelog(m_thermo->speciesName(ks)+" ");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (dom.hasChild("energy_enabled")) {
|
|
getFloatArray(dom, x, false, "", "energy_enabled");
|
|
if (x.size() == nPoints()) {
|
|
for (size_t i = 0; i < x.size(); i++) {
|
|
m_do_energy[i] = (x[i] != 0);
|
|
}
|
|
} else if (!x.empty()) {
|
|
throw CanteraError("StFlow::restore", "energy_enabled is length {}"
|
|
"but should be length {}", x.size(), nPoints());
|
|
}
|
|
}
|
|
|
|
if (dom.hasChild("species_enabled")) {
|
|
getFloatArray(dom, x, false, "", "species_enabled");
|
|
if (x.size() == m_nsp) {
|
|
for (size_t i = 0; i < x.size(); i++) {
|
|
m_do_species[i] = (x[i] != 0);
|
|
}
|
|
} else if (!x.empty()) {
|
|
// This may occur when restoring from a mechanism with a different
|
|
// number of species.
|
|
if (loglevel > 0) {
|
|
writelog("\nWarning: StFlow::restore: species_enabled is "
|
|
"length {} but should be length {}. Enabling all species "
|
|
"equations by default.", x.size(), m_nsp);
|
|
}
|
|
m_do_species.assign(m_nsp, true);
|
|
}
|
|
}
|
|
|
|
if (dom.hasChild("refine_criteria")) {
|
|
XML_Node& ref = dom.child("refine_criteria");
|
|
refiner().setCriteria(getFloat(ref, "ratio"), getFloat(ref, "slope"),
|
|
getFloat(ref, "curve"), getFloat(ref, "prune"));
|
|
refiner().setGridMin(getFloat(ref, "grid_min"));
|
|
}
|
|
}
|
|
|
|
XML_Node& StFlow::save(XML_Node& o, const doublereal* const sol)
|
|
{
|
|
Array2D soln(m_nv, m_points, sol + loc());
|
|
XML_Node& flow = Domain1D::save(o, sol);
|
|
flow.addAttribute("type",flowType());
|
|
|
|
if (m_desc != "") {
|
|
addString(flow,"description",m_desc);
|
|
}
|
|
XML_Node& gv = flow.addChild("grid_data");
|
|
addFloat(flow, "pressure", m_press, "Pa", "pressure");
|
|
|
|
addFloatArray(gv,"z",m_z.size(), m_z.data(),
|
|
"m","length");
|
|
vector_fp x(soln.nColumns());
|
|
|
|
soln.getRow(0, x.data());
|
|
addFloatArray(gv,"u",x.size(),x.data(),"m/s","velocity");
|
|
|
|
soln.getRow(1, x.data());
|
|
addFloatArray(gv,"V",x.size(),x.data(),"1/s","rate");
|
|
|
|
soln.getRow(2, x.data());
|
|
addFloatArray(gv,"T",x.size(),x.data(),"K","temperature");
|
|
|
|
soln.getRow(3, x.data());
|
|
addFloatArray(gv,"L",x.size(),x.data(),"N/m^4");
|
|
|
|
for (size_t k = 0; k < m_nsp; k++) {
|
|
soln.getRow(4+k, x.data());
|
|
addFloatArray(gv,m_thermo->speciesName(k),
|
|
x.size(),x.data(),"","massFraction");
|
|
}
|
|
if (m_do_radiation) {
|
|
addFloatArray(gv, "radiative_heat_loss", m_z.size(),
|
|
m_qdotRadiation.data(), "W/m^3", "specificPower");
|
|
}
|
|
vector_fp values(nPoints());
|
|
for (size_t i = 0; i < nPoints(); i++) {
|
|
values[i] = m_do_energy[i];
|
|
}
|
|
addNamedFloatArray(flow, "energy_enabled", nPoints(), &values[0]);
|
|
|
|
values.resize(m_nsp);
|
|
for (size_t i = 0; i < m_nsp; i++) {
|
|
values[i] = m_do_species[i];
|
|
}
|
|
addNamedFloatArray(flow, "species_enabled", m_nsp, &values[0]);
|
|
|
|
XML_Node& ref = flow.addChild("refine_criteria");
|
|
addFloat(ref, "ratio", refiner().maxRatio());
|
|
addFloat(ref, "slope", refiner().maxDelta());
|
|
addFloat(ref, "curve", refiner().maxSlope());
|
|
addFloat(ref, "prune", refiner().prune());
|
|
addFloat(ref, "grid_min", refiner().gridMin());
|
|
return flow;
|
|
}
|
|
|
|
void StFlow::solveEnergyEqn(size_t j)
|
|
{
|
|
bool changed = false;
|
|
if (j == npos) {
|
|
for (size_t i = 0; i < m_points; i++) {
|
|
if (!m_do_energy[i]) {
|
|
changed = true;
|
|
}
|
|
m_do_energy[i] = true;
|
|
}
|
|
} else {
|
|
if (!m_do_energy[j]) {
|
|
changed = true;
|
|
}
|
|
m_do_energy[j] = true;
|
|
}
|
|
m_refiner->setActive(0, true);
|
|
m_refiner->setActive(1, true);
|
|
m_refiner->setActive(2, true);
|
|
if (changed) {
|
|
needJacUpdate();
|
|
}
|
|
}
|
|
|
|
void StFlow::setBoundaryEmissivities(doublereal e_left, doublereal e_right)
|
|
{
|
|
if (e_left < 0 || e_left > 1) {
|
|
throw CanteraError("setBoundaryEmissivities",
|
|
"The left boundary emissivity must be between 0.0 and 1.0!");
|
|
} else if (e_right < 0 || e_right > 1) {
|
|
throw CanteraError("setBoundaryEmissivities",
|
|
"The right boundary emissivity must be between 0.0 and 1.0!");
|
|
} else {
|
|
m_epsilon_left = e_left;
|
|
m_epsilon_right = e_right;
|
|
}
|
|
}
|
|
|
|
void StFlow::fixTemperature(size_t j)
|
|
{
|
|
bool changed = false;
|
|
if (j == npos) {
|
|
for (size_t i = 0; i < m_points; i++) {
|
|
if (m_do_energy[i]) {
|
|
changed = true;
|
|
}
|
|
m_do_energy[i] = false;
|
|
}
|
|
} else {
|
|
if (m_do_energy[j]) {
|
|
changed = true;
|
|
}
|
|
m_do_energy[j] = false;
|
|
}
|
|
m_refiner->setActive(0, false);
|
|
m_refiner->setActive(1, false);
|
|
m_refiner->setActive(2, false);
|
|
if (changed) {
|
|
needJacUpdate();
|
|
}
|
|
}
|
|
|
|
void AxiStagnFlow::evalRightBoundary(doublereal* x, doublereal* rsd,
|
|
integer* diag, doublereal rdt)
|
|
{
|
|
size_t j = m_points - 1;
|
|
// the boundary object connected to the right of this one may modify or
|
|
// replace these equations. The default boundary conditions are zero u, V,
|
|
// and T, and zero diffusive flux for all species.
|
|
rsd[index(0,j)] = rho_u(x,j);
|
|
rsd[index(1,j)] = V(x,j);
|
|
rsd[index(2,j)] = T(x,j);
|
|
rsd[index(c_offset_L, j)] = lambda(x,j) - lambda(x,j-1);
|
|
diag[index(c_offset_L, j)] = 0;
|
|
doublereal sum = 0.0;
|
|
for (size_t k = 0; k < m_nsp; k++) {
|
|
sum += Y(x,k,j);
|
|
rsd[index(k+4,j)] = m_flux(k,j-1) + rho_u(x,j)*Y(x,k,j);
|
|
}
|
|
rsd[index(c_offset_Y + rightExcessSpecies(), j)] = 1.0 - sum;
|
|
diag[index(c_offset_Y + rightExcessSpecies(), j)] = 0;
|
|
}
|
|
|
|
void AxiStagnFlow::evalContinuity(size_t j, doublereal* x, doublereal* rsd,
|
|
integer* diag, doublereal rdt)
|
|
{
|
|
//----------------------------------------------
|
|
// Continuity equation
|
|
//
|
|
// Note that this propagates the mass flow rate information to the left
|
|
// (j+1 -> j) from the value specified at the right boundary. The
|
|
// lambda information propagates in the opposite direction.
|
|
//
|
|
// d(\rho u)/dz + 2\rho V = 0
|
|
//------------------------------------------------
|
|
rsd[index(c_offset_U,j)] =
|
|
-(rho_u(x,j+1) - rho_u(x,j))/m_dz[j]
|
|
-(density(j+1)*V(x,j+1) + density(j)*V(x,j));
|
|
|
|
//algebraic constraint
|
|
diag[index(c_offset_U, j)] = 0;
|
|
}
|
|
|
|
FreeFlame::FreeFlame(IdealGasPhase* ph, size_t nsp, size_t points) :
|
|
StFlow(ph, nsp, points),
|
|
m_zfixed(Undef),
|
|
m_tfixed(Undef)
|
|
{
|
|
m_dovisc = false;
|
|
setID("flame");
|
|
}
|
|
|
|
void FreeFlame::evalRightBoundary(doublereal* x, doublereal* rsd,
|
|
integer* diag, doublereal rdt)
|
|
{
|
|
size_t j = m_points - 1;
|
|
|
|
// the boundary object connected to the right of this one may modify or
|
|
// replace these equations. The default boundary conditions are zero u, V,
|
|
// and T, and zero diffusive flux for all species.
|
|
|
|
// zero gradient
|
|
rsd[index(0,j)] = rho_u(x,j) - rho_u(x,j-1);
|
|
rsd[index(1,j)] = V(x,j);
|
|
rsd[index(2,j)] = T(x,j) - T(x,j-1);
|
|
doublereal sum = 0.0;
|
|
rsd[index(c_offset_L, j)] = lambda(x,j) - lambda(x,j-1);
|
|
diag[index(c_offset_L, j)] = 0;
|
|
for (size_t k = 0; k < m_nsp; k++) {
|
|
sum += Y(x,k,j);
|
|
rsd[index(k+4,j)] = m_flux(k,j-1) + rho_u(x,j)*Y(x,k,j);
|
|
}
|
|
rsd[index(c_offset_Y + rightExcessSpecies(), j)] = 1.0 - sum;
|
|
diag[index(c_offset_Y + rightExcessSpecies(), j)] = 0;
|
|
}
|
|
|
|
void FreeFlame::evalContinuity(size_t j, doublereal* x, doublereal* rsd,
|
|
integer* diag, doublereal rdt)
|
|
{
|
|
//----------------------------------------------
|
|
// Continuity equation
|
|
//
|
|
// d(\rho u)/dz + 2\rho V = 0
|
|
//----------------------------------------------
|
|
if (grid(j) > m_zfixed) {
|
|
rsd[index(c_offset_U,j)] =
|
|
- (rho_u(x,j) - rho_u(x,j-1))/m_dz[j-1]
|
|
- (density(j-1)*V(x,j-1) + density(j)*V(x,j));
|
|
} else if (grid(j) == m_zfixed) {
|
|
if (m_do_energy[j]) {
|
|
rsd[index(c_offset_U,j)] = (T(x,j) - m_tfixed);
|
|
} else {
|
|
rsd[index(c_offset_U,j)] = (rho_u(x,j)
|
|
- m_rho[0]*0.3);
|
|
}
|
|
} else if (grid(j) < m_zfixed) {
|
|
rsd[index(c_offset_U,j)] =
|
|
- (rho_u(x,j+1) - rho_u(x,j))/m_dz[j]
|
|
- (density(j+1)*V(x,j+1) + density(j)*V(x,j));
|
|
}
|
|
//algebraic constraint
|
|
diag[index(c_offset_U, j)] = 0;
|
|
}
|
|
|
|
void FreeFlame::_finalize(const doublereal* x)
|
|
{
|
|
StFlow::_finalize(x);
|
|
// If the domain contains the temperature fixed point, make sure that it
|
|
// is correctly set. This may be necessary when the grid has been modified
|
|
// externally.
|
|
if (m_tfixed != Undef) {
|
|
for (size_t j = 0; j < m_points; j++) {
|
|
if (z(j) == m_zfixed) {
|
|
return; // fixed point is already set correctly
|
|
}
|
|
}
|
|
|
|
for (size_t j = 0; j < m_points - 1; j++) {
|
|
// Find where the temperature profile crosses the current
|
|
// fixed temperature.
|
|
if ((T(x, j) - m_tfixed) * (T(x, j+1) - m_tfixed) <= 0.0) {
|
|
m_tfixed = T(x, j+1);
|
|
m_zfixed = z(j+1);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FreeFlame::restore(const XML_Node& dom, doublereal* soln, int loglevel)
|
|
{
|
|
StFlow::restore(dom, soln, loglevel);
|
|
getOptionalFloat(dom, "t_fixed", m_tfixed);
|
|
getOptionalFloat(dom, "z_fixed", m_zfixed);
|
|
}
|
|
|
|
XML_Node& FreeFlame::save(XML_Node& o, const doublereal* const sol)
|
|
{
|
|
XML_Node& flow = StFlow::save(o, sol);
|
|
if (m_zfixed != Undef) {
|
|
addFloat(flow, "z_fixed", m_zfixed, "m");
|
|
addFloat(flow, "t_fixed", m_tfixed, "K");
|
|
}
|
|
return flow;
|
|
}
|
|
|
|
} // namespace
|