//! @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(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(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 ""; } } } 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 ignored; size_t nsp = m_thermo->nSpecies(); vector_int did_species(nsp, 0); vector 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 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