1074 lines
34 KiB
C++
1074 lines
34 KiB
C++
/**
|
|
* @file StFlow.cpp
|
|
*/
|
|
// Copyright 2002 California Institute of Technology
|
|
|
|
#include "cantera/oneD/StFlow.h"
|
|
#include "cantera/base/ctml.h"
|
|
#include "cantera/transport/TransportBase.h"
|
|
#include "cantera/numerics/funcs.h"
|
|
|
|
#include <cstdio>
|
|
|
|
using namespace ctml;
|
|
using namespace std;
|
|
|
|
namespace Cantera
|
|
{
|
|
|
|
StFlow::StFlow(IdealGasPhase* ph, size_t nsp, size_t points) :
|
|
Domain1D(nsp+4, points),
|
|
m_inlet_u(0.0),
|
|
m_inlet_V(0.0),
|
|
m_inlet_T(-1.0),
|
|
m_surface_T(-1.0),
|
|
m_press(-1.0),
|
|
m_nsp(nsp),
|
|
m_thermo(0),
|
|
m_kin(0),
|
|
m_trans(0),
|
|
m_jac(0),
|
|
m_ok(false),
|
|
m_epsilon_left(0.0),
|
|
m_epsilon_right(0.0),
|
|
m_do_soret(false),
|
|
m_transport_option(-1),
|
|
m_do_radiation(false)
|
|
{
|
|
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_surfdot.resize(m_nsp, 0.0);
|
|
m_ybar.resize(m_nsp);
|
|
|
|
|
|
//-------------- 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); // lamda should be negative
|
|
|
|
// mass fraction bounds
|
|
for (size_t k = 0; k < m_nsp; k++) {
|
|
setBounds(4+k, -1.0e-5, 1.0e5);
|
|
}
|
|
|
|
//-------------------- default error tolerances ----------------
|
|
setTransientTolerances(1.0e-8, 1.0e-15);
|
|
setSteadyTolerances(1.0e-8, 1.0e-15);
|
|
|
|
//-------------------- 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, DATA_PTR(gr));
|
|
setID("stagnation flow");
|
|
}
|
|
|
|
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_enth.resize(m_points, 0.0);
|
|
m_visc.resize(m_points, 0.0);
|
|
m_tcon.resize(m_points, 0.0);
|
|
|
|
if (m_transport_option == c_Mixav_Transport) {
|
|
m_diff.resize(m_nsp*m_points);
|
|
} else {
|
|
m_multidiff.resize(m_nsp*m_nsp*m_points);
|
|
m_diff.resize(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_fixedy.resize(m_nsp, m_points);
|
|
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);
|
|
size_t j;
|
|
|
|
m_z[0] = z[0];
|
|
for (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::setTransport(Transport& trans, bool withSoret)
|
|
{
|
|
m_trans = &trans;
|
|
m_do_soret = withSoret;
|
|
|
|
int model = m_trans->model();
|
|
if (model == cMulticomponent || model == CK_Multicomponent) {
|
|
m_transport_option = c_Multi_Transport;
|
|
m_multidiff.resize(m_nsp*m_nsp*m_points);
|
|
m_diff.resize(m_nsp*m_points);
|
|
m_dthermal.resize(m_nsp, m_points, 0.0);
|
|
} else if (model == cMixtureAveraged || model == CK_MixtureAveraged) {
|
|
m_transport_option = c_Mixav_Transport;
|
|
m_diff.resize(m_nsp*m_points);
|
|
if (withSoret)
|
|
throw CanteraError("setTransport",
|
|
"Thermal diffusion (the Soret effect) "
|
|
"requires using a multicomponent transport model.");
|
|
} else {
|
|
throw CanteraError("setTransport","unknown transport model.");
|
|
}
|
|
}
|
|
|
|
void StFlow::enableSoret(bool withSoret)
|
|
{
|
|
if (m_transport_option == c_Multi_Transport) {
|
|
m_do_soret = withSoret;
|
|
} else {
|
|
throw CanteraError("setTransport",
|
|
"Thermal diffusion (the Soret effect) "
|
|
"requires using a multicomponent transport model.");
|
|
}
|
|
}
|
|
|
|
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(DATA_PTR(m_ybar));
|
|
m_thermo->setPressure(m_press);
|
|
}
|
|
|
|
void StFlow::_finalize(const doublereal* x)
|
|
{
|
|
size_t k, j;
|
|
doublereal zz, tt;
|
|
size_t nz = m_zfix.size();
|
|
bool e = m_do_energy[0];
|
|
for (j = 0; j < m_points; j++) {
|
|
if (e || nz == 0) {
|
|
m_fixedtemp[j] = T(x, j);
|
|
} else {
|
|
zz = (z(j) - z(0))/(z(m_points - 1) - z(0));
|
|
tt = linearInterp(zz, m_zfix, m_tfix);
|
|
m_fixedtemp[j] = tt;
|
|
}
|
|
for (k = 0; k < m_nsp; k++) {
|
|
setMassFraction(j, k, Y(x, k, j));
|
|
}
|
|
}
|
|
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);
|
|
|
|
size_t j, k;
|
|
|
|
//-----------------------------------------------------
|
|
// update properties
|
|
//-----------------------------------------------------
|
|
|
|
updateThermo(x, j0, j1);
|
|
// update transport properties only if a Jacobian is not being evaluated
|
|
if (jg == npos) {
|
|
updateTransport(x, j0, j1);
|
|
}
|
|
|
|
// 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
|
|
//----------------------------------------------------
|
|
|
|
doublereal sum, sum2, dtdzj;
|
|
|
|
// 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].
|
|
|
|
// set the number of points in the radiative heat loss vector
|
|
m_qdotRadiation.resize(m_points);
|
|
|
|
if (m_do_radiation) {
|
|
// variable definitions for the Planck absorption coefficient and the
|
|
// radiation calculation:
|
|
doublereal k_P_ref = 1.0*OneAtm;
|
|
size_t position_H2O = 0;
|
|
size_t position_CO2 = 0;
|
|
size_t check_H2O = 0;
|
|
size_t check_CO2 = 0;
|
|
|
|
// 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);
|
|
|
|
// check if H2O and / or CO2 are in the mechanism and set their positions
|
|
for (size_t n_comp = 0; n_comp < m_nv; n_comp++) {
|
|
if (componentName(n_comp) == "H2O") {
|
|
position_H2O = componentIndex("H2O") - c_offset_Y;
|
|
check_H2O = 1;
|
|
} else if (componentName(n_comp) == "CO2") {
|
|
position_CO2 = componentIndex("CO2") - c_offset_Y;
|
|
check_CO2 = 1;
|
|
}
|
|
}
|
|
|
|
// loop over all grid points
|
|
for (size_t jnew = 0; jnew < m_points; jnew++) {
|
|
// helping variable for the calculation
|
|
double radiative_heat_loss = 0;
|
|
|
|
// calculation of the mean Planck absorption coefficient
|
|
double k_P_H2O = 0;
|
|
double k_P_CO2 = 0;
|
|
// absorption coefficient for H2O
|
|
if (check_H2O == 1) {
|
|
for (size_t n = 0; n <= 5; n++) {
|
|
k_P_H2O += c_H2O[n] * pow(1000 / T(x, jnew), (double) n);
|
|
}
|
|
}
|
|
// absorption coefficient for CO2
|
|
if (check_CO2 == 1) {
|
|
for (size_t n = 0; n <= 5; n++) {
|
|
k_P_CO2 += c_CO2[n] * pow(1000 / T(x, jnew), (double) n);
|
|
}
|
|
}
|
|
// normalizing the coefficients
|
|
k_P_H2O /= k_P_ref;
|
|
k_P_CO2 /= k_P_ref;
|
|
|
|
// calculation of k_P
|
|
double k_P = m_press * (X(x, position_H2O, jnew) * k_P_H2O * check_H2O
|
|
+ X(x, position_CO2, jnew) * k_P_CO2 * check_CO2);
|
|
// calculation of the radiative heat loss term
|
|
radiative_heat_loss = 2 * k_P *(2 * StefanBoltz * pow(T(x, jnew), 4)
|
|
- boundary_Rad_left - boundary_Rad_right);
|
|
|
|
// set the radiative heat loss vector
|
|
m_qdotRadiation[jnew] = radiative_heat_loss;
|
|
}
|
|
} else {
|
|
for (size_t jnew = 0; jnew < m_points; jnew++) {
|
|
m_qdotRadiation[jnew] = 0;
|
|
}
|
|
}
|
|
|
|
for (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.
|
|
sum = 0.0;
|
|
for (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, 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);
|
|
|
|
doublereal convec, diffus;
|
|
for (k = 0; k < m_nsp; k++) {
|
|
convec = rho_u(x,j)*dYdz(x,k,j);
|
|
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();
|
|
|
|
sum = 0.0;
|
|
sum2 = 0.0;
|
|
doublereal flxk;
|
|
for (k = 0; k < m_nsp; k++) {
|
|
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);
|
|
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_transport_option == c_Mixav_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(DATA_PTR(m_diff) + j*m_nsp);
|
|
m_tcon[j] = m_trans->thermalConductivity();
|
|
}
|
|
} else if (m_transport_option == c_Multi_Transport) {
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void StFlow::showSolution(const doublereal* x)
|
|
{
|
|
size_t nn = m_nv/5;
|
|
size_t i, j, n;
|
|
char buf[100];
|
|
|
|
// The mean molecular weight is needed to convert
|
|
updateThermo(x, 0, m_points-1);
|
|
|
|
sprintf(buf, " Pressure: %10.4g Pa \n", m_press);
|
|
writelog(buf);
|
|
for (i = 0; i < nn; i++) {
|
|
writeline('-', 79, false, true);
|
|
sprintf(buf, "\n z ");
|
|
writelog(buf);
|
|
for (n = 0; n < 5; n++) {
|
|
sprintf(buf, " %10s ",componentName(i*5 + n).c_str());
|
|
writelog(buf);
|
|
}
|
|
writeline('-', 79, false, true);
|
|
for (j = 0; j < m_points; j++) {
|
|
sprintf(buf, "\n %10.4g ",m_z[j]);
|
|
writelog(buf);
|
|
for (n = 0; n < 5; n++) {
|
|
sprintf(buf, " %10.4g ",component(x, i*5+n,j));
|
|
writelog(buf);
|
|
}
|
|
}
|
|
writelog("\n");
|
|
}
|
|
size_t nrem = m_nv - 5*nn;
|
|
writeline('-', 79, false, true);
|
|
sprintf(buf, "\n z ");
|
|
writelog(buf);
|
|
for (n = 0; n < nrem; n++) {
|
|
sprintf(buf, " %10s ", componentName(nn*5 + n).c_str());
|
|
writelog(buf);
|
|
}
|
|
writeline('-', 79, false, true);
|
|
for (j = 0; j < m_points; j++) {
|
|
sprintf(buf, "\n %10.4g ",m_z[j]);
|
|
writelog(buf);
|
|
for (n = 0; n < nrem; n++) {
|
|
sprintf(buf, " %10.4g ",component(x, nn*5+n,j));
|
|
writelog(buf);
|
|
}
|
|
}
|
|
writelog("\n");
|
|
if (m_do_radiation) {
|
|
writeline('-', 79, false, true);
|
|
sprintf(buf, "\n z radiative heat loss");
|
|
writelog(buf);
|
|
writeline('-', 79, false, true);
|
|
for (j = 0; j < m_points; j++) {
|
|
sprintf(buf, "\n %10.4g %10.4g", m_z[j], m_qdotRadiation[j]);
|
|
writelog(buf);
|
|
}
|
|
writelog("\n");
|
|
}
|
|
}
|
|
|
|
void StFlow::updateDiffFluxes(const doublereal* x, size_t j0, size_t j1)
|
|
{
|
|
size_t j, k, m;
|
|
doublereal sum, wtm, rho, dz, gradlogT;
|
|
|
|
switch (m_transport_option) {
|
|
|
|
case c_Mixav_Transport:
|
|
for (j = j0; j < j1; j++) {
|
|
sum = 0.0;
|
|
wtm = m_wtm[j];
|
|
rho = density(j);
|
|
dz = z(j+1) - z(j);
|
|
|
|
for (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 (k = 0; k < m_nsp; k++) {
|
|
m_flux(k,j) += sum*Y(x,k,j);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case c_Multi_Transport:
|
|
for (j = j0; j < j1; j++) {
|
|
dz = z(j+1) - z(j);
|
|
|
|
for (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;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
throw CanteraError("updateDiffFluxes","unknown transport model");
|
|
}
|
|
|
|
if (m_do_soret) {
|
|
for (m = j0; m < j1; m++) {
|
|
gradlogT = 2.0 * (T(x,m+1) - T(x,m)) /
|
|
((T(x,m+1) + T(x,m)) * (z(m+1) - z(m)));
|
|
for (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 = -1.0;
|
|
pp = getFloat(dom, "pressure", "pressure");
|
|
setPressure(pp);
|
|
|
|
vector<XML_Node*> d = dom.child("grid_data").getChildren("floatArray");
|
|
size_t nd = d.size();
|
|
|
|
vector_fp x;
|
|
size_t n, np = 0, j, ks, k;
|
|
string nm;
|
|
bool readgrid = false, wrote_header = false;
|
|
for (n = 0; n < nd; n++) {
|
|
const XML_Node& fa = *d[n];
|
|
nm = fa["title"];
|
|
if (nm == "z") {
|
|
getFloatArray(fa,x,false);
|
|
np = x.size();
|
|
writelog("Grid contains "+int2str(np)+" points.\n", loglevel >= 2);
|
|
readgrid = true;
|
|
setupGrid(np, DATA_PTR(x));
|
|
}
|
|
}
|
|
if (!readgrid) {
|
|
throw CanteraError("StFlow::restore",
|
|
"domain contains no grid points.");
|
|
}
|
|
|
|
writelog("Importing datasets:\n", loglevel >= 2);
|
|
for (n = 0; n < nd; n++) {
|
|
const XML_Node& fa = *d[n];
|
|
nm = fa["title"];
|
|
getFloatArray(fa,x,false);
|
|
if (nm == "u") {
|
|
writelog("axial velocity ", loglevel >= 2);
|
|
if (x.size() != np) {
|
|
throw CanteraError("StFlow::restore",
|
|
"axial velocity array size error");
|
|
}
|
|
for (j = 0; j < np; j++) {
|
|
soln[index(0,j)] = x[j];
|
|
}
|
|
} else if (nm == "z") {
|
|
; // already read grid
|
|
} else if (nm == "V") {
|
|
writelog("radial velocity ", loglevel >= 2);
|
|
if (x.size() != np) {
|
|
throw CanteraError("StFlow::restore",
|
|
"radial velocity array size error");
|
|
}
|
|
for (j = 0; j < np; j++) {
|
|
soln[index(1,j)] = x[j];
|
|
}
|
|
} else if (nm == "T") {
|
|
writelog("temperature ", loglevel >= 2);
|
|
if (x.size() != np) {
|
|
throw CanteraError("StFlow::restore",
|
|
"temperature array size error");
|
|
}
|
|
for (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") {
|
|
writelog("lambda ", loglevel >= 2);
|
|
if (x.size() != np) {
|
|
throw CanteraError("StFlow::restore",
|
|
"lambda arary size error");
|
|
}
|
|
for (j = 0; j < np; j++) {
|
|
soln[index(3,j)] = x[j];
|
|
}
|
|
} else if (m_thermo->speciesIndex(nm) != npos) {
|
|
writelog(nm+" ", loglevel >= 2);
|
|
if (x.size() == np) {
|
|
k = m_thermo->speciesIndex(nm);
|
|
did_species[k] = 1;
|
|
for (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 (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];
|
|
}
|
|
} else if (!x.empty()) {
|
|
throw CanteraError("StFlow::restore", "energy_enabled is length" +
|
|
int2str(x.size()) + "but should be length" +
|
|
int2str(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];
|
|
}
|
|
} 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 " +
|
|
int2str(x.size()) + " but should be length " +
|
|
int2str(m_nsp) + ". Enabling all species equations by default.");
|
|
}
|
|
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)
|
|
{
|
|
size_t k;
|
|
|
|
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(),DATA_PTR(m_z),
|
|
"m","length");
|
|
vector_fp x(soln.nColumns());
|
|
|
|
soln.getRow(0,DATA_PTR(x));
|
|
addFloatArray(gv,"u",x.size(),DATA_PTR(x),"m/s","velocity");
|
|
|
|
soln.getRow(1,DATA_PTR(x));
|
|
addFloatArray(gv,"V",
|
|
x.size(),DATA_PTR(x),"1/s","rate");
|
|
|
|
soln.getRow(2,DATA_PTR(x));
|
|
addFloatArray(gv,"T",x.size(),DATA_PTR(x),"K","temperature");
|
|
|
|
soln.getRow(3,DATA_PTR(x));
|
|
addFloatArray(gv,"L",x.size(),DATA_PTR(x),"N/m^4");
|
|
|
|
for (k = 0; k < m_nsp; k++) {
|
|
soln.getRow(4+k,DATA_PTR(x));
|
|
addFloatArray(gv,m_thermo->speciesName(k),
|
|
x.size(),DATA_PTR(x),"","massFraction");
|
|
}
|
|
if (m_do_radiation) {
|
|
addFloatArray(gv, "radiative_heat_loss", m_z.size(),
|
|
DATA_PTR(m_qdotRadiation), "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::setJac(MultiJac* jac)
|
|
{
|
|
m_jac = jac;
|
|
}
|
|
|
|
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(4,j)] = 1.0 - sum;
|
|
diag[index(4,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(4,j)] = 1.0 - sum;
|
|
diag[index(4,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
|