/** * @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 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(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); 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 ""; } } } 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 = -1.0; pp = getFloat(dom, "pressure", "pressure"); setPressure(pp); vector 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