Cantera  4.0.0a2
Loading...
Searching...
No Matches
Flow1D.cpp
Go to the documentation of this file.
1//! @file Flow1D.cpp
2
3// This file is part of Cantera. See License.txt in the top-level directory or
4// at https://cantera.org/license.txt for license and copyright information.
5
8#include "cantera/oneD/refine.h"
13#include "cantera/numerics/eigen_sparse.h"
14#include "cantera/numerics/eigen_dense.h"
15#include "cantera/base/global.h"
16
17using namespace std;
18
19namespace Cantera
20{
21
22namespace { // restrict scope of auxiliary variables to local translation unit
23
24const map<string, size_t> componentMap = {
25 {"velocity", c_offset_U}, // axial velocity [m/s]
26 {"spreadRate", c_offset_V}, // strain rate
27 {"T", c_offset_T}, // temperature [kelvin]
28 {"Lambda", c_offset_L}, // (1/r)dP/dr
29 {"eField", c_offset_E}, // electric field
30 {"Uo", c_offset_Uo}, // oxidizer axial velocity [m/s]
31};
32
33} // end unnamed namespace
34
35Flow1D::Flow1D(shared_ptr<Solution> phase, const string& id, size_t points)
36 : Domain1D(phase->thermo()->nSpecies()+c_offset_Y, points)
37 , m_nsp(phase->thermo()->nSpecies())
38 , m_thermo(phase->thermo().get())
39{
40 // make a local copy of the species molecular weight vector
41 m_wt.assign(m_thermo->molecularWeights().begin(),
42 m_thermo->molecularWeights().end());
43
44 // set pressure based on associated thermo object
46
47 // the species mass fractions are the last components in the solution
48 // vector, so the total number of components is the number of species
49 // plus the offset of the first mass fraction.
51
52 // Turn off the energy equation at all points
53 m_do_energy.resize(m_points,false);
54
55 m_diff.resize(m_nsp*m_points);
60 m_dhk_dz.resize(m_nsp, m_points - 1, 0.0);
61 m_ybar.resize(m_nsp);
62 m_qdotRadiation.resize(m_points, 0.0);
63
64 //-------------- default solution bounds --------------------
65 setBounds(c_offset_U, -1e20, 1e20); // no bounds on u
66 setBounds(c_offset_V, -1e20, 1e20); // no bounds on V
67 setBounds(c_offset_T, 200.0, 2*m_thermo->maxTemp()); // temperature bounds
68 setBounds(c_offset_L, -1e20, 1e20); // Lambda should be negative
69 setBounds(c_offset_E, -1e20, 1e20); // no bounds on electric field
70 setBounds(c_offset_Uo, -1e20, 1e20); // no bounds on Uo
71 // mass fraction bounds
72 for (size_t k = 0; k < m_nsp; k++) {
73 setBounds(c_offset_Y+k, -1.0e-7, 1.0e5);
74 }
75
76 //-------------------- grid refinement -------------------------
77 m_refiner->setActive(c_offset_U, false);
78 m_refiner->setActive(c_offset_V, false);
79 m_refiner->setActive(c_offset_T, false);
80 m_refiner->setActive(c_offset_L, false);
81 m_refiner->setActive(c_offset_Uo, false);
82
83 vector<double> gr;
84 for (size_t ng = 0; ng < m_points; ng++) {
85 gr.push_back(1.0*ng/m_points);
86 }
87 setupGrid(gr);
88
89 // Find indices for radiating species
90 m_kRadiating.resize(2, npos);
91 m_kRadiating[0] = m_thermo->speciesIndex("CO2", false);
92 m_kRadiating[1] = m_thermo->speciesIndex("H2O", false);
93
95 m_solution->thermo()->addSpeciesLock();
96 m_id = id;
97 m_kin = m_solution->kinetics().get();
98 m_trans = m_solution->transport().get();
99 if (m_trans->transportModel() == "none") {
100 throw CanteraError("Flow1D::Flow1D",
101 "An appropriate transport model\nshould be set when instantiating the "
102 "Solution ('gas') object.");
103 }
104 m_solution->registerChangedCallback(this, [this]() {
105 _setKinetics(m_solution->kinetics());
106 _setTransport(m_solution->transport());
107 });
108}
109
110Flow1D::~Flow1D()
111{
112 if (m_solution) {
113 m_solution->removeChangedCallback(this);
114 }
115}
116
117string Flow1D::domainType() const {
118 if (m_isFree) {
119 return "free-flow";
120 }
121 if (m_usesLambda) {
122 return "axisymmetric-flow";
123 }
124 return "unstrained-flow";
125}
126
127void Flow1D::_setKinetics(shared_ptr<Kinetics> kin)
128{
129 m_kin = kin.get();
130 m_solution->setKinetics(kin);
131}
132
133void Flow1D::_setTransport(shared_ptr<Transport> trans)
134{
135 if (!trans) {
136 throw CanteraError("Flow1D::_setTransport", "Unable to set empty transport.");
137 }
138 m_trans = trans.get();
139 if (m_trans->transportModel() == "none") {
140 throw CanteraError("Flow1D::_setTransport", "Invalid Transport model 'none'.");
141 }
142 m_do_multicomponent = (m_trans->transportModel() == "multicomponent" ||
143 m_trans->transportModel() == "multicomponent-CK");
144
145 m_diff.resize(m_nsp * m_points);
148 }
150 m_solution->setTransport(trans);
151}
152
153void Flow1D::resize(size_t ncomponents, size_t points)
154{
155 Domain1D::resize(ncomponents, points);
156 m_rho.resize(m_points, 0.0);
157 m_wtm.resize(m_points, 0.0);
158 m_cp.resize(m_points, 0.0);
159 m_visc.resize(m_points, 0.0);
160 m_tcon.resize(m_points, 0.0);
161
162 m_diff.resize(m_nsp*m_points);
165 }
169 m_hk.resize(m_nsp, m_points, 0.0);
170 m_dhk_dz.resize(m_nsp, m_points - 1, 0.0);
171 m_do_energy.resize(m_points,false);
172 m_qdotRadiation.resize(m_points, 0.0);
173 m_fixedtemp.resize(m_points);
174
175 m_dz.resize(m_points-1);
176 m_z.resize(m_points);
177
178 // analytic Jacobian workspace. m_ddC (sparse) is intentionally NOT sized
179 // here: its pattern is built on the first netProductionRates_ddCi(m_ddC)
180 // call. Reset it to empty so a grid/state change rebuilds a fresh pattern.
181 // Note: the pattern is assumed fixed for the lifetime of the grid. Changing
182 // the kinetics object's derivativeSettings() (e.g. toggling skip-third-bodies)
183 // to a *larger* pattern without an intervening resize() would leave m_ddC
184 // missing slots, producing incorrect Jacobian entries (silently in optimized
185 // builds; netProductionRates_ddCi() flags missing slots in debug builds).
186 // Callers must re-grid or re-create the domain after such a change.
187 // (Shrinking is safe.)
188 m_ddC = Eigen::SparseMatrix<double>();
189 m_dwdY.resize(m_nsp * m_nsp);
190 m_dFm_dYp.resize(m_nsp * m_nsp);
191 m_dF0_dYp.resize(m_nsp * m_nsp);
192 m_jacBlockWork.resize(m_nsp * m_nsp);
193 m_jacXwork.resize(2 * m_nsp);
194 m_jacColWork.resize(m_nsp);
195 m_jacCpWork.resize(m_nsp);
196}
197
198void Flow1D::setupGrid(span<const double> z)
199{
200 resize(m_nv, z.size());
201
202 m_z[0] = z[0];
203 for (size_t j = 1; j < m_points; j++) {
204 if (z[j] <= z[j-1]) {
205 throw CanteraError("Flow1D::setupGrid",
206 "grid points must be monotonically increasing");
207 }
208 m_z[j] = z[j];
209 m_dz[j-1] = m_z[j] - m_z[j-1];
210 }
211}
212
213void Flow1D::resetBadValues(span<double> xg)
214{
215 span<double> x = xg.subspan(loc(), size());
216 for (size_t j = 0; j < m_points; j++) {
219 }
220}
221
222void Flow1D::setTransportModel(const string& model)
223{
224 if (model == "none") {
225 throw CanteraError("Flow1D::setTransportModel",
226 "Invalid Transport model 'none'.");
227 }
228 m_solution->setTransportModel(model);
229 Flow1D::_setTransport(m_solution->transport());
230}
231
233 return m_trans->transportModel();
234}
235
238 if (transportModel() != "mixture-averaged-CK" &&
239 transportModel() != "mixture-averaged") {
240 warn_user("Flow1D::setFluxGradientBasis",
241 "Setting fluxGradientBasis only affects "
242 "the mixture-averaged diffusion model.");
243 }
244}
245
246void Flow1D::_getInitialSoln(span<double> x)
247{
248 for (size_t j = 0; j < m_points; j++) {
249 T(x,j) = m_thermo->temperature();
251 m_rho[j] = m_thermo->density();
252 }
253}
254
255void Flow1D::setGas(span<const double> x, size_t j)
256{
260}
261
262void Flow1D::setGasAtMidpoint(span<const double> x, size_t j)
263{
264 m_thermo->setTemperature(0.5*(T(x,j)+T(x,j+1)));
265 auto yy_j = Y(x, j);
266 auto yy_j_plus1 = Y(x, j+1);
267 for (size_t k = 0; k < m_nsp; k++) {
268 m_ybar[k] = 0.5*(yy_j[k] + yy_j_plus1[k]);
269 }
272}
273
274void Flow1D::_finalize(span<const double> x)
275{
276 if (!(m_do_multicomponent ||
277 m_trans->transportModel() == "mixture-averaged" ||
278 m_trans->transportModel() == "mixture-averaged-CK")
279 && m_do_soret) {
280
281 throw CanteraError("Flow1D::_finalize",
282 "Thermal diffusion (the Soret effect) is enabled, but it "
283 "only ompatible with the mixture-averaged and multicomponent "
284 "transport models.");
285 }
286
287 size_t nz = m_zfix.size();
288 bool e = m_do_energy[0];
289 for (size_t j = 0; j < m_points; j++) {
290 if (e || nz == 0) {
291 m_fixedtemp[j] = T(x, j);
292 } else {
293 double zz = (z(j) - z(0))/(z(m_points - 1) - z(0));
294 double tt = linearInterp(zz, m_zfix, m_tfix);
295 m_fixedtemp[j] = tt;
296 }
297 }
298 if (e) {
300 }
301
302 if (m_isFree) {
303 // If the domain contains the temperature fixed point, make sure that it
304 // is correctly set. This may be necessary when the grid has been modified
305 // externally.
306 if (m_tfixed != Undef) {
307 for (size_t j = 0; j < m_points; j++) {
308 if (z(j) == m_zfixed) {
309 return; // fixed point is already set correctly
310 }
311 }
312
313 for (size_t j = 0; j < m_points - 1; j++) {
314 // Find where the temperature profile crosses the current
315 // fixed temperature.
316 if ((T(x, j) - m_tfixed) * (T(x, j+1) - m_tfixed) <= 0.0) {
317 m_tfixed = T(x, j+1);
318 m_zfixed = z(j+1);
319 return;
320 }
321 }
322 }
323 }
324}
325
326void Flow1D::eval(size_t jGlobal, span<const double> xGlobal, span<double> rsdGlobal,
327 span<int> diagGlobal, double rdt)
328{
329 // If evaluating a Jacobian, and the global point is outside the domain of
330 // influence for this domain, then skip evaluating the residual
331 if (jGlobal != npos && (jGlobal + 1 < firstPoint() || jGlobal > lastPoint() + 1)) {
332 return;
333 }
334
335 // start of local part of global arrays
336 span<const double> x = xGlobal.subspan(loc(), size());
337 span<double> rsd = rsdGlobal.subspan(loc(), size());
338 span<int> diag = diagGlobal.subspan(loc(), size());
339
340 size_t jmin, jmax;
341 if (jGlobal == npos) { // evaluate all points
342 jmin = 0;
343 jmax = m_points - 1;
344 } else { // evaluate points for Jacobian
345 size_t jpt = (jGlobal == 0) ? 0 : jGlobal - firstPoint();
346 jmin = std::max<size_t>(jpt, 1) - 1;
347 jmax = std::min(jpt+1,m_points-1);
348 }
349
350 updateProperties(jGlobal, x, jmin, jmax);
351
352 if (m_do_radiation) { // Calculation of qdotRadiation
353 computeRadiation(x, jmin, jmax);
354 }
355
356 evalContinuity(x, rsd, diag, rdt, jmin, jmax);
357 evalMomentum(x, rsd, diag, rdt, jmin, jmax);
358 evalEnergy(x, rsd, diag, rdt, jmin, jmax);
359 evalLambda(x, rsd, diag, rdt, jmin, jmax);
360 evalElectricField(x, rsd, diag, rdt, jmin, jmax);
361 evalUo(x, rsd, diag, rdt, jmin, jmax);
362 evalSpecies(x, rsd, diag, rdt, jmin, jmax);
363}
364
365void Flow1D::updateProperties(size_t jg, span<const double> x, size_t jmin, size_t jmax)
366{
367 // properties are computed for grid points from j0 to j1
368 size_t j0 = std::max<size_t>(jmin, 1) - 1;
369 size_t j1 = std::min(jmax+1,m_points-1);
370
371 updateThermo(x, j0, j1);
372 if (jg == npos || m_force_full_update) {
373 // update transport properties only if a Jacobian is not being
374 // evaluated, or if specifically requested
375 updateTransport(x, j0, j1);
376 }
377 if (jg == npos) {
378 auto yLeft = Y(x, jmin);
379 auto yRight = Y(x, jmax);
380 m_kExcessLeft = distance(yLeft.begin(), ranges::max_element(yLeft));
381 m_kExcessRight = distance(yRight.begin(), ranges::max_element(yRight));
382 }
383
384 // update the species diffusive mass fluxes whether or not a
385 // Jacobian is being evaluated
386 updateDiffFluxes(x, j0, j1);
387}
388
389void Flow1D::updateTransport(span<const double> x, size_t j0, size_t j1)
390{
392 for (size_t j = j0; j < j1; j++) {
393 setGasAtMidpoint(x,j);
394 double wtm = m_thermo->meanMolecularWeight();
395 double rho = m_thermo->density();
396 m_visc[j] = (m_dovisc ? m_trans->viscosity() : 0.0);
398 span<double>(&m_multidiff[mindex(0,0,j)], m_nsp * m_nsp));
399
400 // Use m_diff as storage for the factor outside the summation
401 for (size_t k = 0; k < m_nsp; k++) {
402 m_diff[k+j*m_nsp] = m_wt[k] * rho / (wtm*wtm);
403 }
404
406 if (m_do_soret) {
408 }
409 }
410 } else { // mixture averaged transport
411 for (size_t j = j0; j < j1; j++) {
412 setGasAtMidpoint(x,j);
413 m_visc[j] = (m_dovisc ? m_trans->viscosity() : 0.0);
414
415 if (m_fluxGradientBasis == ThermoBasis::molar) {
416 m_trans->getMixDiffCoeffs(span<double>(&m_diff[j*m_nsp], m_nsp));
417 } else {
418 m_trans->getMixDiffCoeffsMass(span<double>(&m_diff[j*m_nsp], m_nsp));
419 }
420
421 double rho = m_thermo->density();
422
423 if (m_fluxGradientBasis == ThermoBasis::molar) {
424 double wtm = m_thermo->meanMolecularWeight();
425 for (size_t k=0; k < m_nsp; k++) {
426 m_diff[k+j*m_nsp] *= m_wt[k] * rho / wtm;
427 }
428 } else {
429 for (size_t k=0; k < m_nsp; k++) {
430 m_diff[k+j*m_nsp] *= rho;
431 }
432 }
434 if (m_do_soret) {
436 }
437 }
438 }
439}
440
441void Flow1D::updateDiffFluxes(span<const double> x, size_t j0, size_t j1)
442{
444 for (size_t j = j0; j < j1; j++) {
445 double dz = z(j+1) - z(j);
446 for (size_t k = 0; k < m_nsp; k++) {
447 double sum = 0.0;
448 for (size_t m = 0; m < m_nsp; m++) {
449 sum += m_wt[m] * m_multidiff[mindex(k,m,j)] * (X(x,m,j+1)-X(x,m,j));
450 }
451 m_flux(k,j) = sum * m_diff[k+j*m_nsp] / dz;
452 }
453 }
454 } else {
455 for (size_t j = j0; j < j1; j++) {
456 double sum = 0.0;
457 double dz = z(j+1) - z(j);
458 if (m_fluxGradientBasis == ThermoBasis::molar) {
459 for (size_t k = 0; k < m_nsp; k++) {
460 m_flux(k,j) = m_diff[k+m_nsp*j] * (X(x,k,j) - X(x,k,j+1))/dz;
461 sum -= m_flux(k,j);
462 }
463 } else {
464 for (size_t k = 0; k < m_nsp; k++) {
465 m_flux(k,j) = m_diff[k+m_nsp*j] * (Y(x,k,j) - Y(x,k,j+1))/dz;
466 sum -= m_flux(k,j);
467 }
468 }
469 // correction flux to ensure that \sum_k Y_k V_k = 0.
470 for (size_t k = 0; k < m_nsp; k++) {
471 m_flux(k,j) += sum*Y(x,k,j);
472 }
473 }
474 }
475
476 if (m_do_soret) {
477 for (size_t m = j0; m < j1; m++) {
478 double gradlogT = 2.0 * (T(x,m+1) - T(x,m)) /
479 ((T(x,m+1) + T(x,m)) * (z(m+1) - z(m)));
480 for (size_t k = 0; k < m_nsp; k++) {
481 m_flux(k,m) -= m_dthermal(k,m)*gradlogT;
482 }
483 }
484 }
485}
486
487double Flow1D::radiationPolyFactor(span<const double> x, size_t j, int s) const
488{
489 // Polynomial coefficients for Planck-mean absorption, from the RADCAL program
490 // and TNF Workshop data.
491 static const double c_CO2[6] = {18.741, -121.310, 273.500, -194.050,
492 56.310, -5.8169};
493 static const double c_H2O[6] = {-0.23093, -1.12390, 9.41530, -2.99880,
494 0.51382, -1.86840e-5};
495 const double* c = (s == 0) ? c_CO2 : c_H2O;
496 double f = 0.0;
497 for (size_t n = 0; n <= 5; n++) {
498 f += c[n] * pow(1000.0 / T(x, j), (double) n);
499 }
500 return f / OneAtm; // k_P_ref = 1 atm
501}
502
503void Flow1D::computeRadiation(span<const double> x, size_t jmin, size_t jmax)
504{
505 // Calculation of the two boundary values
506 double boundary_Rad_left = m_epsilon_left * StefanBoltz * pow(T(x, 0), 4);
507 double boundary_Rad_right = m_epsilon_right * StefanBoltz * pow(T(x, m_points - 1), 4);
508
509 for (size_t j = jmin; j < jmax; j++) {
510 // calculation of the mean Planck absorption coefficient
511 double k_P = 0;
512 // Absorption coefficient for H2O (m_kRadiating[1])
513 if (m_kRadiating[1] != npos) {
514 k_P += m_press * X(x, m_kRadiating[1], j) * radiationPolyFactor(x, j, 1);
515 }
516 // Absorption coefficient for CO2 (m_kRadiating[0])
517 if (m_kRadiating[0] != npos) {
518 k_P += m_press * X(x, m_kRadiating[0], j) * radiationPolyFactor(x, j, 0);
519 }
520
521 // Calculation of the radiative heat loss term
522 double radiative_heat_loss = 2 * k_P *(2 * StefanBoltz * pow(T(x, j), 4)
523 - boundary_Rad_left - boundary_Rad_right);
524
525 // set the radiative heat loss vector
526 m_qdotRadiation[j] = radiative_heat_loss;
527 }
528}
529
530void Flow1D::evalContinuity(span<const double> x, span<double> rsd, span<int> diag,
531 double rdt, size_t jmin, size_t jmax)
532{
533 // The left boundary has the same form for all cases.
534 if (jmin == 0) { // left boundary
535 rsd[index(c_offset_U, jmin)] = -(rho_u(x, jmin+1) - rho_u(x, jmin))/m_dz[jmin]
536 -(density(jmin+1)*V(x, jmin+1)
537 + density(jmin)*V(x, jmin));
538 diag[index(c_offset_U, jmin)] = 0; // Algebraic constraint
539 }
540
541 if (jmax == m_points - 1) { // right boundary
542 if (m_usesLambda) { // zero mass flux
543 rsd[index(c_offset_U, jmax)] = rho_u(x, jmax);
544 } else { // zero gradient, same for unstrained or free-flow
545 rsd[index(c_offset_U, jmax)] = rho_u(x, jmax) - rho_u(x, jmax-1);
546 }
547 diag[index(c_offset_U, jmax)] = 0; // Algebraic constraint
548 }
549
550 // j0 and j1 are constrained to only interior points
551 size_t j0 = std::max<size_t>(jmin, 1);
552 size_t j1 = std::min(jmax, m_points-2);
553 if (m_usesLambda) { // "axisymmetric-flow"
554 for (size_t j = j0; j <= j1; j++) { // interior points
555 // For "axisymmetric-flow", the continuity equation propagates the
556 // mass flow rate information to the left (j+1 -> j) from the value
557 // specified at the right boundary. The Lambda information propagates
558 // in the opposite direction.
559 rsd[index(c_offset_U, j)] = -(rho_u(x, j+1) - rho_u(x, j))/m_dz[j]
560 -(density(j+1)*V(x, j+1) + density(j)*V(x, j));
561 diag[index(c_offset_U, j)] = 0; // Algebraic constraint
562 }
563 } else if (m_isFree) { // "free-flow"
564 for (size_t j = j0; j <= j1; j++) {
565 // terms involving V are zero as V=0 by definition
566 if (z(j) > m_zfixed) {
567 rsd[index(c_offset_U, j)] = -(rho_u(x, j) - rho_u(x, j-1))/m_dz[j-1];
568 } else if (z(j) == m_zfixed) {
569 if (m_do_energy[j]) {
570 rsd[index(c_offset_U, j)] = (T(x, j) - m_tfixed);
571 } else {
572 rsd[index(c_offset_U, j)] = (rho_u(x, j) - m_rho[0]*0.3); // why 0.3?
573 }
574 } else { // z(j) < m_zfixed
575 rsd[index(c_offset_U, j)] = -(rho_u(x, j+1) - rho_u(x, j))/m_dz[j];
576 }
577 diag[index(c_offset_U, j)] = 0; // Algebraic constraint
578 }
579 } else { // "unstrained-flow" (fixed mass flow rate)
580 for (size_t j = j0; j <= j1; j++) {
581 rsd[index(c_offset_U, j)] = rho_u(x, j) - rho_u(x, j-1);
582 diag[index(c_offset_U, j)] = 0; // Algebraic constraint
583 }
584 }
585}
586
587void Flow1D::evalMomentum(span<const double> x, span<double> rsd, span<int> diag,
588 double rdt, size_t jmin, size_t jmax)
589{
590 if (!m_usesLambda) { //disable this equation
591 for (size_t j = jmin; j <= jmax; j++) {
592 rsd[index(c_offset_V, j)] = V(x, j);
593 diag[index(c_offset_V, j)] = 0;
594 }
595 return;
596 }
597
598 if (jmin == 0) { // left boundary
599 rsd[index(c_offset_V, jmin)] = V(x, jmin);
600 }
601
602 if (jmax == m_points - 1) { // right boundary
603 rsd[index(c_offset_V, jmax)] = V(x, jmax);
604 diag[index(c_offset_V, jmax)] = 0;
605 }
606
607 // j0 and j1 are constrained to only interior points
608 size_t j0 = std::max<size_t>(jmin, 1);
609 size_t j1 = std::min(jmax, m_points-2);
610 for (size_t j = j0; j <= j1; j++) { // interior points
611 rsd[index(c_offset_V, j)] = (shear(x, j) - Lambda(x, j)
612 - rho_u(x, j) * dVdz(x, j)
613 - m_rho[j] * V(x, j) * V(x, j)) / m_rho[j];
614 if (!m_twoPointControl) {
615 rsd[index(c_offset_V, j)] -= rdt * (V(x, j) - V_prev(j));
616 diag[index(c_offset_V, j)] = 1;
617 } else {
618 diag[index(c_offset_V, j)] = 0;
619 }
620 }
621}
622
623void Flow1D::evalLambda(span<const double> x, span<double> rsd, span<int> diag,
624 double rdt, size_t jmin, size_t jmax)
625{
626 if (!m_usesLambda) { // disable this equation
627 for (size_t j = jmin; j <= jmax; j++) {
628 rsd[index(c_offset_L, j)] = Lambda(x, j);
629 diag[index(c_offset_L, j)] = 0;
630 }
631 return;
632 }
633
634 if (jmin == 0) { // left boundary
635 if (m_twoPointControl) {
636 rsd[index(c_offset_L, jmin)] = Lambda(x, jmin+1) - Lambda(x, jmin);
637 } else {
638 rsd[index(c_offset_L, jmin)] = -rho_u(x, jmin);
639 }
640 }
641
642 if (jmax == m_points - 1) { // right boundary
643 rsd[index(c_offset_L, jmax)] = Lambda(x, jmax) - Lambda(x, jmax-1);
644 diag[index(c_offset_L, jmax)] = 0;
645 }
646
647 // j0 and j1 are constrained to only interior points
648 size_t j0 = std::max<size_t>(jmin, 1);
649 size_t j1 = std::min(jmax, m_points-2);
650 for (size_t j = j0; j <= j1; j++) { // interior points
651 if (m_twoPointControl) {
652 if (z(j) == m_zLeft) {
653 rsd[index(c_offset_L, j)] = T(x,j) - m_tLeft;
654 } else if (z(j) > m_zLeft) {
655 rsd[index(c_offset_L, j)] = Lambda(x, j) - Lambda(x, j-1);
656 } else if (z(j) < m_zLeft) {
657 rsd[index(c_offset_L, j)] = Lambda(x, j) - Lambda(x, j+1);
658 }
659 } else {
660 rsd[index(c_offset_L, j)] = Lambda(x, j) - Lambda(x, j-1);
661 }
662 diag[index(c_offset_L, j)] = 0;
663 }
664}
665
666void Flow1D::evalEnergy(span<const double> x, span<double> rsd, span<int> diag,
667 double rdt, size_t jmin, size_t jmax)
668{
669 if (jmin == 0) { // left boundary
670 rsd[index(c_offset_T, jmin)] = T(x, jmin);
671 }
672
673 if (jmax == m_points - 1) { // right boundary
674 rsd[index(c_offset_T, jmax)] = T(x, jmax);
675 }
676
677 // j0 and j1 are constrained to only interior points
678 size_t j0 = std::max<size_t>(jmin, 1);
679 size_t j1 = std::min(jmax, m_points-2);
680 for (size_t j = j0; j <= j1; j++) {
681 if (m_do_energy[j]) {
682 grad_hk(x, j);
683 double sum = 0.0;
684 for (size_t k = 0; k < m_nsp; k++) {
685 double flxk = 0.5*(m_flux(k, j-1) + m_flux(k, j));
686 sum += m_wdot(k, j)*m_hk(k, j);
687 sum += flxk * m_dhk_dz(k, j) / m_wt[k];
688 }
689
690 rsd[index(c_offset_T, j)] = - m_cp[j]*rho_u(x, j)*dTdz(x, j)
691 - conduction(x, j) - sum;
692 rsd[index(c_offset_T, j)] /= (m_rho[j]*m_cp[j]);
693 rsd[index(c_offset_T, j)] -= (m_qdotRadiation[j] / (m_rho[j] * m_cp[j]));
694 if (!m_twoPointControl || (m_z[j] != m_tLeft && m_z[j] != m_tRight)) {
695 rsd[index(c_offset_T, j)] -= rdt*(T(x, j) - T_prev(j));
696 diag[index(c_offset_T, j)] = 1;
697 } else {
698 diag[index(c_offset_T, j)] = 0;
699 }
700 } else {
701 // residual equations if the energy equation is disabled
702 rsd[index(c_offset_T, j)] = T(x, j) - T_fixed(j);
703 diag[index(c_offset_T, j)] = 0;
704 }
705 }
706}
707
708void Flow1D::evalUo(span<const double> x, span<double> rsd, span<int> diag,
709 double rdt, size_t jmin, size_t jmax)
710{
711 if (!m_twoPointControl) { // disable this equation
712 for (size_t j = jmin; j <= jmax; j++) {
713 rsd[index(c_offset_Uo, j)] = Uo(x, j);
714 diag[index(c_offset_Uo, j)] = 0;
715 }
716 return;
717 }
718
719 if (jmin == 0) { // left boundary
720 rsd[index(c_offset_Uo, jmin)] = Uo(x, jmin+1) - Uo(x, jmin);
721 diag[index(c_offset_Uo, jmin)] = 0;
722 }
723
724 if (jmax == m_points - 1) { // right boundary
726 rsd[index(c_offset_Uo, jmax)] = Uo(x, jmax) - Uo(x, jmax-1);
727 }
728 diag[index(c_offset_Uo, jmax)] = 0;
729 }
730
731 // j0 and j1 are constrained to only interior points
732 size_t j0 = std::max<size_t>(jmin, 1);
733 size_t j1 = std::min(jmax, m_points-2);
734 for (size_t j = j0; j <= j1; j++) { // interior points
735 if (m_twoPointControl) {
736 if (z(j) == m_zRight) {
737 rsd[index(c_offset_Uo, j)] = T(x, j) - m_tRight;
738 } else if (z(j) > m_zRight) {
739 rsd[index(c_offset_Uo, j)] = Uo(x, j) - Uo(x, j-1);
740 } else if (z(j) < m_zRight) {
741 rsd[index(c_offset_Uo, j)] = Uo(x, j) - Uo(x, j+1);
742 }
743 }
744 diag[index(c_offset_Uo, j)] = 0;
745 }
746}
747
748void Flow1D::evalSpecies(span<const double> x, span<double> rsd, span<int> diag,
749 double rdt, size_t jmin, size_t jmax)
750{
751 if (jmin == 0) { // left boundary
752 double sum = 0.0;
753 for (size_t k = 0; k < m_nsp; k++) {
754 sum += Y(x,k,jmin);
755 rsd[index(c_offset_Y+k, jmin)] = -(m_flux(k, jmin) +
756 rho_u(x, jmin) * Y(x, k, jmin));
757 }
758 rsd[index(c_offset_Y + leftExcessSpecies(), jmin)] = 1.0 - sum;
759 diag[index(c_offset_Y + leftExcessSpecies(), jmin)] = 0;
760 }
761
762 if (jmax == m_points - 1) { // right boundary
763 double sum = 0.0;
764 for (size_t k = 0; k < m_nsp; k++) {
765 sum += Y(x,k,jmax);
766 rsd[index(k+c_offset_Y, jmax)] = m_flux(k, jmax-1) +
767 rho_u(x, jmax)*Y(x, k, jmax);
768 }
769 rsd[index(c_offset_Y + rightExcessSpecies(), jmax)] = 1.0 - sum;
770 diag[index(c_offset_Y + rightExcessSpecies(), jmax)] = 0;
771 }
772
773 // j0 and j1 are constrained to only interior points
774 size_t j0 = std::max<size_t>(jmin, 1);
775 size_t j1 = std::min(jmax, m_points-2);
776 for (size_t j = j0; j <= j1; j++) {
777 for (size_t k = 0; k < m_nsp; k++) {
778 double convec = rho_u(x, j)*dYdz(x, k, j);
779 double diffus = 2*(m_flux(k, j) - m_flux(k, j-1)) / (z(j+1) - z(j-1));
780 rsd[index(c_offset_Y + k, j)] = (m_wt[k]*m_wdot(k, j)
781 - convec - diffus) / m_rho[j]
782 - rdt*(Y(x, k, j) - Y_prev(k, j));
783 diag[index(c_offset_Y + k, j)] = 1;
784 }
785 }
786}
787
788void Flow1D::evalElectricField(span<const double> x, span<double> rsd, span<int> diag,
789 double rdt, size_t jmin, size_t jmax)
790{
791 for (size_t j = jmin; j <= jmax; j++) {
792 // The same value is used for left/right/interior points
793 rsd[index(c_offset_E, j)] = x[index(c_offset_E, j)];
794 }
795}
796
797void Flow1D::show(span<const double> x)
798{
799 writelog(" Pressure: {:10.4g} Pa\n", m_press);
800
802
803 if (m_do_radiation) {
804 writeline('-', 79, false, true);
805 writelog("\n z radiative heat loss");
806 writeline('-', 79, false, true);
807 for (size_t j = 0; j < m_points; j++) {
808 writelog("\n {:10.4g} {:10.4g}", m_z[j], m_qdotRadiation[j]);
809 }
810 writelog("\n");
811 }
812}
813
814string Flow1D::componentName(size_t n) const
815{
816 switch (n) {
817 case c_offset_U:
818 return "velocity";
819 case c_offset_V:
820 return "spreadRate";
821 case c_offset_T:
822 return "T";
823 case c_offset_L:
824 return "Lambda";
825 case c_offset_E:
826 return "eField";
827 case c_offset_Uo:
828 return "Uo";
829 default:
830 if (n >= c_offset_Y && n < (c_offset_Y + m_nsp)) {
831 return m_thermo->speciesName(n - c_offset_Y);
832 } else {
833 throw IndexError("Flow1D::componentName", "component", n, m_nv);
834 }
835 }
836}
837
838size_t Flow1D::componentIndex(const string& name, bool checkAlias) const
839{
840 if (componentMap.count(name)) {
841 return componentMap.at(name);
842 }
843 for (size_t n = c_offset_Y; n < m_nsp + c_offset_Y; n++) {
844 if (componentName(n) == name) {
845 return n;
846 }
847 }
848 if (checkAlias) {
849 const auto& aliasMap = _componentAliasMap();
850 if (aliasMap.count(name)) {
851 return componentIndex(aliasMap.at(name), false);
852 }
853 }
854 throw CanteraError("Flow1D::componentIndex",
855 "Component '{}' not found", name);
856}
857
858bool Flow1D::hasComponent(const string& name, bool checkAlias) const
859{
860 if (componentMap.count(name)) {
861 return true;
862 }
863 for (size_t n = c_offset_Y; n < m_nsp + c_offset_Y; n++) {
864 if (componentName(n) == name) {
865 return true;
866 }
867 }
868 if (checkAlias && _componentAliasMap().count(name)
869 && componentMap.count(_componentAliasMap().at(name)))
870 {
871 return true;
872 }
873 return false;
874}
875
876bool Flow1D::componentActive(size_t n) const
877{
878 switch (n) {
879 case c_offset_V: // spreadRate
880 return m_usesLambda;
881 case c_offset_L: // Lambda
882 return m_usesLambda;
883 case c_offset_E: // eField
884 return false;
885 case c_offset_Uo: // oxidizer velocity for two-point control
886 return twoPointControlEnabled();
887 default:
888 return true;
889 }
890}
892{
893 AnyMap state = Domain1D::getMeta();
894 state["transport-model"] = m_trans->transportModel();
895
896 state["phase"]["name"] = m_thermo->name();
897 AnyValue source = m_thermo->input().getMetadata("filename");
898 state["phase"]["source"] = source.empty() ? "<unknown>" : source.asString();
899
900 state["radiation-enabled"] = m_do_radiation;
901 if (m_do_radiation) {
902 state["emissivity-left"] = m_epsilon_left;
903 state["emissivity-right"] = m_epsilon_right;
904 }
905
906 set<bool> energy_flags(m_do_energy.begin(), m_do_energy.end());
907 if (energy_flags.size() == 1) {
908 state["energy-enabled"] = m_do_energy[0];
909 } else {
910 state["energy-enabled"] = m_do_energy;
911 }
912
913 state["Soret-enabled"] = m_do_soret;
914
915 state["flux-gradient-basis"] = static_cast<long int>(m_fluxGradientBasis);
916
917 state["refine-criteria"]["ratio"] = m_refiner->maxRatio();
918 state["refine-criteria"]["slope"] = m_refiner->maxDelta();
919 state["refine-criteria"]["curve"] = m_refiner->maxSlope();
920 state["refine-criteria"]["prune"] = m_refiner->prune();
921 state["refine-criteria"]["grid-min"] = m_refiner->gridMin();
922 state["refine-criteria"]["max-points"] =
923 static_cast<long int>(m_refiner->maxPoints());
924
925 if (m_zfixed != Undef) {
926 state["fixed-point"]["location"] = m_zfixed;
927 state["fixed-point"]["temperature"] = m_tfixed;
928 }
929
930 // Two-point control meta data
931 if (m_twoPointControl) {
932 state["continuation-method"]["type"] = "two-point";
933 state["continuation-method"]["left-location"] = m_zLeft;
934 state["continuation-method"]["right-location"] = m_zRight;
935 state["continuation-method"]["left-temperature"] = m_tLeft;
936 state["continuation-method"]["right-temperature"] = m_tRight;
937 }
938
939 return state;
940}
941
942void Flow1D::updateState(size_t loc)
943{
944 if (!m_state) {
945 throw CanteraError("Flow1D::updateState",
946 "Domain needs to be installed in a container.");
947 }
948 if (!m_solution) {
949 throw CanteraError("Flow1D::updateState",
950 "Domain does not have associated Solution object.");
951 }
952 const double* soln = m_state->data() + m_iloc + m_nv * loc;
953 m_solution->thermo()->setMassFractions_NoNorm(
954 span<const double>(soln + c_offset_Y, m_nsp));
955 m_solution->thermo()->setState_TP(*(soln + c_offset_T), m_press);
956}
957
958void Flow1D::getValues(const string& component, span<double> values) const
959{
960 if (!m_state) {
961 throw CanteraError("Flow1D::getValues",
962 "Domain needs to be installed in a container.");
963 }
964 if (values.size() != nPoints()) {
965 throw ArraySizeError("Flow1D::getValues", values.size(), nPoints());
966 }
967 auto i = componentIndex(component);
968 if (!componentActive(i)) {
969 warn_user(
970 "Flow1D::getValues", "Component '{}' is not used by '{}'.",
971 component, domainType());
972 }
973 const double* soln = m_state->data() + m_iloc;
974 for (size_t j = 0; j < nPoints(); j++) {
975 values[j] = soln[index(i,j)];
976 }
977}
978
979void Flow1D::setValues(const string& component, span<const double> values)
980{
981 if (!m_state) {
982 throw CanteraError("Flow1D::setValues",
983 "Domain needs to be installed in a container.");
984 }
985 if (values.size() != nPoints()) {
986 throw ArraySizeError("Flow1D::_etValues", values.size(), nPoints());
987 }
988 auto i = componentIndex(component);
989 if (!componentActive(i)) {
990 throw CanteraError(
991 "Flow1D::setValues", "Component '{}' is not used by '{}'.",
992 component, domainType());
993 }
994 double* soln = m_state->data() + m_iloc;
995 for (size_t j = 0; j < nPoints(); j++) {
996 soln[index(i,j)] = values[j];
997 }
998}
999
1000void Flow1D::getResiduals(const string& component, span<double> values) const
1001{
1002 if (!m_state) {
1003 throw CanteraError("Flow1D::getResiduals",
1004 "Domain needs to be installed in a container.");
1005 }
1006 if (values.size() != nPoints()) {
1007 throw ArraySizeError("Flow1D::getResiduals", values.size(), nPoints());
1008 }
1009 auto i = componentIndex(component);
1010 if (!componentActive(i)) {
1011 warn_user(
1012 "Flow1D::getResiduals", "Component '{}' is not used by '{}'.",
1013 component, domainType());
1014 }
1015 const double* soln = m_container->_workVector().data() + m_iloc;
1016 for (size_t j = 0; j < nPoints(); j++) {
1017 values[j] = soln[index(i,j)];
1018 }
1019}
1020
1021void Flow1D::setProfile(const string& component,
1022 span<const double> pos, span<const double> values)
1023{
1024 if (!m_state) {
1025 throw CanteraError("Flow1D::setProfile",
1026 "Domain needs to be installed in a container.");
1027 }
1028 if (pos.size() != values.size()) {
1029 throw CanteraError(
1030 "Flow1D::setProfile", "Vectors for positions and values must have same "
1031 "size.\nSizes are {} and {}, respectively.", pos.size(), values.size());
1032 }
1033 if (pos.front() != 0.0 || pos.back() != 1.0) {
1034 throw CanteraError("Flow1D::setProfile",
1035 "'pos' vector must span the range [0, 1]. Got a vector spanning "
1036 "[{}, {}] instead.", pos.front(), pos.back());
1037 }
1038 auto i = componentIndex(component);
1039 if (!componentActive(i)) {
1040 throw CanteraError(
1041 "Flow1D::setProfile", "Component '{}' is not used by '{}'.",
1042 component, domainType());
1043 }
1044 double* soln = m_state->data() + m_iloc;
1045 double z0 = zmin();
1046 double zDelta = zmax() - z0;
1047 for (size_t j = 0; j < nPoints(); j++) {
1048 double frac = (z(j) - z0)/zDelta;
1049 double v = linearInterp(frac, pos, values);
1050 soln[index(i,j)] = v;
1051 }
1052}
1053
1054void Flow1D::setFlatProfile(const string& component, double value)
1055{
1056 if (!m_state) {
1057 throw CanteraError("Flow1D::setFlatProfile",
1058 "Domain needs to be installed in a container.");
1059 }
1060 auto i = componentIndex(component);
1061 if (!componentActive(i)) {
1062 throw CanteraError(
1063 "Flow1D::setFlatProfile", "Component '{}' is not used by '{}'.",
1064 component, domainType());
1065 }
1066 double* soln = m_state->data() + m_iloc;
1067 for (size_t j = 0; j < nPoints(); j++) {
1068 soln[index(i,j)] = value;
1069 }
1070}
1071
1072shared_ptr<SolutionArray> Flow1D::toArray(bool normalize)
1073{
1074 if (!m_state) {
1075 throw CanteraError("Flow1D::toArray",
1076 "Domain needs to be installed in a container before calling toArray.");
1077 }
1078 double* soln = m_state->data() + m_iloc;
1079 auto arr = SolutionArray::create(
1080 m_solution, static_cast<int>(nPoints()), getMeta());
1081 arr->addExtra("grid", false); // leading entry
1083 value = m_z;
1084 arr->setComponent("grid", value);
1085 vector<double> data(nPoints());
1086 for (size_t i = 0; i < nComponents(); i++) {
1087 if (componentActive(i)) {
1088 auto name = componentName(i);
1089 for (size_t j = 0; j < nPoints(); j++) {
1090 data[j] = soln[index(i, j)];
1091 }
1092 if (!arr->hasComponent(name)) {
1093 arr->addExtra(name, componentIndex(name) > c_offset_Y);
1094 }
1095 value = data;
1096 arr->setComponent(name, value);
1097 }
1098 }
1099 updateThermo(span<const double>(soln, size()), 0, m_points-1);
1100 value = m_rho;
1101 arr->setComponent("D", value); // use density rather than pressure
1102
1103 if (m_do_radiation) {
1104 arr->addExtra("radiative-heat-loss", true); // add at end
1106 arr->setComponent("radiative-heat-loss", value);
1107 }
1108
1109 if (normalize) {
1110 arr->normalize();
1111 }
1112 return arr;
1113}
1114
1115void Flow1D::fromArray(const shared_ptr<SolutionArray>& arr)
1116{
1117 if (!m_state) {
1118 throw CanteraError("Domain1D::fromArray",
1119 "Domain needs to be installed in a container before calling fromArray.");
1120 }
1121 resize(nComponents(), arr->size());
1123 span<double> soln(m_state->data() + m_iloc, size());
1124
1125 Domain1D::setMeta(arr->meta());
1126 arr->setLoc(0);
1127 auto phase = arr->thermo();
1128 m_press = phase->pressure();
1129
1130 const auto grid = arr->getComponent("grid").as<vector<double>>();
1131 setupGrid(grid);
1132 setMeta(arr->meta()); // can affect which components are active
1133
1134 for (size_t i = 0; i < nComponents(); i++) {
1135 if (!componentActive(i)) {
1136 continue;
1137 }
1138 string name = componentName(i);
1139 if (arr->hasComponent(name)) {
1140 const vector<double> data = arr->getComponent(name).as<vector<double>>();
1141 for (size_t j = 0; j < nPoints(); j++) {
1142 soln[index(i,j)] = data[j];
1143 }
1144 } else if (name == "Lambda" && arr->hasComponent("lambda")) {
1145 // edge case: 'lambda' is renamed to 'Lambda' in Cantera 3.2
1146 const auto data = arr->getComponent("lambda").as<vector<double>>();
1147 for (size_t j = 0; j < nPoints(); j++) {
1148 soln[index(i,j)] = data[j];
1149 }
1150 } else {
1151 warn_user("Flow1D::fromArray", "Saved state does not contain values for "
1152 "component '{}' in domain '{}'.", name, id());
1153 }
1154 }
1155
1156 updateProperties(npos, soln, 0, m_points - 1);
1157 _finalize(soln);
1158}
1159
1160void Flow1D::setMeta(const AnyMap& state)
1161{
1162 if (state.hasKey("energy-enabled")) {
1163 const AnyValue& ee = state["energy-enabled"];
1164 if (ee.isScalar()) {
1165 m_do_energy.assign(nPoints(), ee.asBool());
1166 } else {
1167 m_do_energy = ee.asVector<bool>(nPoints());
1168 }
1169 }
1170
1171 if (state.hasKey("transport-model")) {
1172 setTransportModel(state["transport-model"].asString());
1173 }
1174
1175 if (state.hasKey("Soret-enabled")) {
1176 m_do_soret = state["Soret-enabled"].asBool();
1177 }
1178
1179 if (state.hasKey("flux-gradient-basis")) {
1180 m_fluxGradientBasis = static_cast<ThermoBasis>(
1181 state["flux-gradient-basis"].asInt());
1182 }
1183
1184 if (state.hasKey("radiation-enabled")) {
1185 m_do_radiation = state["radiation-enabled"].asBool();
1186 if (m_do_radiation) {
1187 m_epsilon_left = state["emissivity-left"].asDouble();
1188 m_epsilon_right = state["emissivity-right"].asDouble();
1189 }
1190 }
1191
1192 if (state.hasKey("refine-criteria")) {
1193 const AnyMap& criteria = state["refine-criteria"].as<AnyMap>();
1194 double ratio = criteria.getDouble("ratio", m_refiner->maxRatio());
1195 double slope = criteria.getDouble("slope", m_refiner->maxDelta());
1196 double curve = criteria.getDouble("curve", m_refiner->maxSlope());
1197 double prune = criteria.getDouble("prune", m_refiner->prune());
1198 m_refiner->setCriteria(ratio, slope, curve, prune);
1199
1200 if (criteria.hasKey("grid-min")) {
1201 m_refiner->setGridMin(criteria["grid-min"].asDouble());
1202 }
1203 if (criteria.hasKey("max-points")) {
1204 m_refiner->setMaxPoints(criteria["max-points"].asInt());
1205 }
1206 }
1207
1208 if (state.hasKey("fixed-point")) {
1209 m_zfixed = state["fixed-point"]["location"].asDouble();
1210 m_tfixed = state["fixed-point"]["temperature"].asDouble();
1211 }
1212
1213 // Two-point control meta data
1214 if (state.hasKey("continuation-method")) {
1215 const AnyMap& cm = state["continuation-method"].as<AnyMap>();
1216 if (cm["type"] == "two-point") {
1217 m_twoPointControl = true;
1218 m_zLeft = cm["left-location"].asDouble();
1219 m_zRight = cm["right-location"].asDouble();
1220 m_tLeft = cm["left-temperature"].asDouble();
1221 m_tRight = cm["right-temperature"].asDouble();
1222 } else {
1223 warn_user("Flow1D::setMeta", "Unknown continuation method '{}'.",
1224 cm["type"].asString());
1225 }
1226 }
1227}
1228
1230{
1231 bool changed = false;
1232 if (j == npos) {
1233 for (size_t i = 0; i < m_points; i++) {
1234 if (!m_do_energy[i]) {
1235 changed = true;
1236 }
1237 m_do_energy[i] = true;
1238 }
1239 } else {
1240 if (!m_do_energy[j]) {
1241 changed = true;
1242 }
1243 m_do_energy[j] = true;
1244 }
1245 m_refiner->setActive(c_offset_U, true);
1246 m_refiner->setActive(c_offset_V, true);
1247 m_refiner->setActive(c_offset_T, true);
1248 if (changed) {
1249 needJacUpdate();
1250 }
1251}
1252
1254{
1255 throw NotImplementedError("Flow1D::solveElectricField",
1256 "Not used by '{}' objects.", domainType());
1257}
1258
1260{
1261 throw NotImplementedError("Flow1D::fixElectricField",
1262 "Not used by '{}' objects.", domainType());
1263}
1264
1266{
1267 throw NotImplementedError("Flow1D::doElectricField",
1268 "Not used by '{}' objects.", domainType());
1269}
1270
1271void Flow1D::setBoundaryEmissivities(double e_left, double e_right)
1272{
1273 if (e_left < 0 || e_left > 1) {
1274 throw CanteraError("Flow1D::setBoundaryEmissivities",
1275 "The left boundary emissivity must be between 0.0 and 1.0!");
1276 } else if (e_right < 0 || e_right > 1) {
1277 throw CanteraError("Flow1D::setBoundaryEmissivities",
1278 "The right boundary emissivity must be between 0.0 and 1.0!");
1279 } else {
1280 m_epsilon_left = e_left;
1281 m_epsilon_right = e_right;
1282 }
1283}
1284
1286{
1287 bool changed = false;
1288 if (j == npos) {
1289 for (size_t i = 0; i < m_points; i++) {
1290 if (m_do_energy[i]) {
1291 changed = true;
1292 }
1293 m_do_energy[i] = false;
1294 }
1295 } else {
1296 if (m_do_energy[j]) {
1297 changed = true;
1298 }
1299 m_do_energy[j] = false;
1300 }
1301 m_refiner->setActive(c_offset_U, false);
1302 m_refiner->setActive(c_offset_V, false);
1303 m_refiner->setActive(c_offset_T, false);
1304 if (changed) {
1305 needJacUpdate();
1306 }
1307}
1308
1309void Flow1D::grad_hk(span<const double> x, size_t j)
1310{
1311 size_t jloc = (u(x, j) > 0.0 ? j : j + 1);
1312 for(size_t k = 0; k < m_nsp; k++) {
1313 m_dhk_dz(k, j) = (m_hk(k, jloc) - m_hk(k, jloc-1))/m_dz[jloc-1];
1314 }
1315}
1316
1317// Two-point control functions
1319{
1320 if (m_twoPointControl) {
1321 if (m_zLeft != Undef) {
1322 return m_tLeft;
1323 } else {
1324 throw CanteraError("Flow1D::leftControlPointTemperature",
1325 "Invalid operation: left control point location is not set");
1326 }
1327 } else {
1328 throw CanteraError("Flow1D::leftControlPointTemperature",
1329 "Invalid operation: two-point control is not enabled.");
1330 }
1331}
1332
1334{
1335 if (m_twoPointControl) {
1336 if (m_zLeft != Undef) {
1337 return m_zLeft;
1338 } else {
1339 throw CanteraError("Flow1D::leftControlPointCoordinate",
1340 "Invalid operation: left control point location is not set");
1341 }
1342 } else {
1343 throw CanteraError("Flow1D::leftControlPointCoordinate",
1344 "Invalid operation: two-point control is not enabled.");
1345 }
1346}
1347
1349{
1350 if (m_twoPointControl) {
1351 if (m_zLeft != Undef) {
1352 m_tLeft = temperature;
1353 } else {
1354 throw CanteraError("Flow1D::setLeftControlPointTemperature",
1355 "Invalid operation: left control point location is not set");
1356 }
1357 } else {
1358 throw CanteraError("Flow1D::setLeftControlPointTemperature",
1359 "Invalid operation: two-point control is not enabled.");
1360 }
1361}
1362
1364{
1365 if (m_twoPointControl) {
1366 m_zLeft = z_left;
1367 } else {
1368 throw CanteraError("Flow1D::setLeftControlPointCoordinate",
1369 "Invalid operation: two-point control is not enabled.");
1370 }
1371}
1372
1374{
1375 if (m_twoPointControl) {
1376 if (m_zRight != Undef) {
1377 return m_tRight;
1378 } else {
1379 throw CanteraError("Flow1D::rightControlPointTemperature",
1380 "Invalid operation: right control point location is not set");
1381 }
1382 } else {
1383 throw CanteraError("Flow1D::rightControlPointTemperature",
1384 "Invalid operation: two-point control is not enabled.");
1385 }
1386}
1387
1389{
1390 if (m_twoPointControl) {
1391 if (m_zRight != Undef) {
1392 return m_zRight;
1393 } else {
1394 throw CanteraError("Flow1D::rightControlPointCoordinate",
1395 "Invalid operation: right control point location is not set");
1396 }
1397 } else {
1398 throw CanteraError("Flow1D::rightControlPointCoordinate",
1399 "Invalid operation: two-point control is not enabled.");
1400 }
1401}
1402
1404{
1405 if (m_twoPointControl) {
1406 if (m_zRight != Undef) {
1407 m_tRight = temperature;
1408 } else {
1409 throw CanteraError("Flow1D::setRightControlPointTemperature",
1410 "Invalid operation: right control point location is not set");
1411 }
1412 } else {
1413 throw CanteraError("Flow1D::setRightControlPointTemperature",
1414 "Invalid operation: two-point control is not enabled.");
1415 }
1416}
1417
1419{
1420 if (m_twoPointControl) {
1421 m_zRight = z_right;
1422 } else {
1423 throw CanteraError("Flow1D::setRightControlPointCoordinate",
1424 "Invalid operation: two-point control is not enabled.");
1425 }
1426}
1427
1428void Flow1D::enableTwoPointControl(bool twoPointControl)
1429{
1430 if (isStrained()) {
1431 m_twoPointControl = twoPointControl;
1432 // Prevent finding spurious solutions with negative velocity (outflow) at either
1433 // inlet.
1434 setBounds(c_offset_V, -1e-5, 1e20);
1435 } else {
1436 throw CanteraError("Flow1D::enableTwoPointControl",
1437 "Invalid operation: two-point control can only be used"
1438 "with axisymmetric flames.");
1439 }
1440}
1441
1442//---------------------------------------------------------------------------
1443// Analytic Jacobian (species Y-columns)
1444//---------------------------------------------------------------------------
1445
1447{
1448 if (!analyticRequested() || m_analyticJacCapable != -1) {
1449 return;
1450 }
1451 // Probe once whether the kinetics object supports the composition
1452 // derivatives. The thermo object is left in a valid state by the base
1453 // residual evaluation that precedes the Jacobian's FD column loop, so we
1454 // can build the sparse pattern of m_ddC here without re-setting the state.
1455 try {
1458 } catch (NotImplementedError&) {
1459 // Capability gap; "auto" falls back silently and an explicit
1460 // "analytic" request raises in checkAnalyticJacobian().
1462 m_ddC = Eigen::SparseMatrix<double>();
1463 }
1464}
1465
1467{
1469 return false;
1470 }
1472 return m_analyticJacCapable == 1
1474 && m_points >= 3;
1475}
1476
1478{
1479 if (m_jacobianMode != "analytic") {
1480 // "auto" and "finite-difference" never raise; "auto" degrades silently.
1481 return;
1482 }
1484 throw CanteraError("Flow1D::checkAnalyticJacobian",
1485 "The analytic Jacobian was explicitly requested "
1486 "(jacobian_mode = 'analytic'), but it is not implemented for this "
1487 "flow domain type. Set jacobian_mode = 'auto' to fall back to "
1488 "finite differences automatically.");
1489 }
1491 if (m_analyticJacCapable != 1) {
1492 throw CanteraError("Flow1D::checkAnalyticJacobian",
1493 "The analytic Jacobian was explicitly requested "
1494 "(jacobian_mode = 'analytic'), but the kinetics object does not "
1495 "provide the required composition derivatives "
1496 "(Kinetics::netProductionRates_ddCi). Set jacobian_mode = 'auto' to "
1497 "fall back to finite differences automatically.");
1498 }
1499 if (m_do_multicomponent) {
1500 throw CanteraError("Flow1D::checkAnalyticJacobian",
1501 "The analytic Jacobian was explicitly requested "
1502 "(jacobian_mode = 'analytic'), but it is not supported with "
1503 "multicomponent transport. Set jacobian_mode = 'auto' to fall back "
1504 "to finite differences automatically, or use mixture-averaged "
1505 "transport.");
1506 }
1507 // force_full_update (adjoint sensitivity) and fewer than 3 grid points are
1508 // transient/internal conditions: usingAnalyticJacobian() degrades to finite
1509 // differences for them without raising.
1510}
1511
1512bool Flow1D::hasAnalyticJacobian(size_t j, size_t n) const
1513{
1514 return usingAnalyticJacobian() && n >= c_offset_Y && j >= 1 && j + 2 <= m_points;
1515}
1516
1517void Flow1D::evalJacobianAnalytic(span<const double> xGlobal, SystemJacobian& jac)
1518{
1519 if (!usingAnalyticJacobian()) {
1520 return;
1521 }
1522 span<const double> x = xGlobal.subspan(loc(), size());
1523
1524 // Refresh cached thermo (rho, wtm, cp, hk, wdot) and diffusive fluxes at
1525 // the base state; FD point-evaluations may have left them perturbed at a
1526 // few points. Transport coefficients (m_diff, m_visc, m_tcon) are NOT
1527 // updated, matching the frozen-transport convention of the FD Jacobian.
1528 updateThermo(x, 0, m_points - 1);
1529 updateDiffFluxes(x, 0, m_points - 2);
1530
1531 for (size_t p = 1; p + 2 <= m_points; p++) {
1532 setGas(x, p);
1533 m_kin->netProductionRates_ddCi(m_ddC); // sparse, pattern reused across p
1535 addSpeciesJacEntries(x, p, jac);
1536 addEnergyJacEntries(x, p, jac);
1537 addFlowJacEntries(x, p, jac);
1538 }
1539}
1540
1542{
1543 // dwdot_k/dY_m = (rho * ddC[k][m] - wbar * (ddC . C)_k) / W_m
1544 // (constant T and P; perturbations of unnormalized Y)
1545 size_t K = m_nsp;
1546 double rho = m_rho[j];
1547 double wbar = m_wtm[j];
1548 vector<double>& conc = m_jacColWork;
1549 m_thermo->getConcentrations(conc); // state was set by caller via setGas
1550 Eigen::Map<Eigen::MatrixXd> JY(m_dwdY.data(), K, K);
1551 Eigen::Map<const Eigen::VectorXd> c(conc.data(), K);
1552 Eigen::VectorXd g = m_ddC * c;
1553 // dense rank-1 part for every column
1554 for (size_t m = 0; m < K; m++) {
1555 JY.col(m) = (-wbar / m_wt[m]) * g;
1556 }
1557 // add the sparse ∂ω̇/∂C column scaled by rho/W_m
1558 for (int m = 0; m < m_ddC.outerSize(); m++) {
1559 for (Eigen::SparseMatrix<double>::InnerIterator it(m_ddC, m); it; ++it) {
1560 JY(it.row(), m) += rho / m_wt[m] * it.value();
1561 }
1562 }
1563}
1564
1565template <bool AtQ>
1566void Flow1D::fluxJacobian(span<const double> x, size_t q, span<double> out)
1567{
1568 // dF_k(q)/dY_m(q) if AtQ=true, dF_k(q)/dY_m(q+1) if AtQ=false.
1569 // K×K column-major, flux prefactors m_diff frozen (frozen-transport approximation).
1570 size_t K = m_nsp;
1571 double dz = m_dz[q];
1572 std::fill(out.begin(), out.end(), 0.0);
1573
1574 const double* d = &m_diff[q * K]; // frozen diffusion prefactors
1575
1576 if (m_fluxGradientBasis == ThermoBasis::mass) {
1577 // Mass-basis flux: F_k(q) = d_kq*(Y_k(q)-Y_k(q+1))/dz + Y_k(q)*S_q
1578 // with S_q = -sum_n (d_nq/dz)(Y_n(q)-Y_n(q+1)). No wbar/W_m factors.
1579 double S = 0.0;
1580 for (size_t n = 0; n < K; n++) {
1581 S -= d[n] / dz * (Y(x, n, q) - Y(x, n, q + 1));
1582 }
1583 if constexpr (AtQ) {
1584 for (size_t m = 0; m < K; m++) {
1585 for (size_t k = 0; k < K; k++) {
1586 double v = -Y(x, k, q) * d[m] / dz;
1587 if (k == m) {
1588 v += d[k] / dz + S;
1589 }
1590 out[m * K + k] = v;
1591 }
1592 }
1593 } else {
1594 for (size_t m = 0; m < K; m++) {
1595 for (size_t k = 0; k < K; k++) {
1596 out[m * K + k] = (k == m ? -d[k] / dz : 0.0)
1597 + Y(x, k, q) * d[m] / dz;
1598 }
1599 }
1600 }
1601 return;
1602 }
1603
1604 // X and wbar at the two end points (unnormalized convention, matching the
1605 // residual's X(x,k,j) accessor: X_n(p) = Y_n(p) * wbar_p / W_n)
1606 double wbq = m_wtm[q];
1607 double wbq1 = m_wtm[q + 1];
1608 double* Xq = m_jacXwork.data();
1609 double* Xq1 = m_jacXwork.data() + K;
1610 for (size_t n = 0; n < K; n++) {
1611 Xq[n] = Y(x, n, q) * wbq / m_wt[n];
1612 Xq1[n] = Y(x, n, q + 1) * wbq1 / m_wt[n];
1613 }
1614
1615 // S_q = -sum_n (d_n/dz)(X_n(q) - X_n(q+1)); a_p = sum_n (d_n/dz)X_n(p)
1616 double S = 0.0, aq = 0.0, aq1 = 0.0;
1617 for (size_t n = 0; n < K; n++) {
1618 S -= d[n] / dz * (Xq[n] - Xq1[n]);
1619 aq += d[n] / dz * Xq[n];
1620 aq1 += d[n] / dz * Xq1[n];
1621 }
1622
1623 for (size_t m = 0; m < K; m++) {
1624 if constexpr (AtQ) {
1625 double fq = wbq / m_wt[m]; // dX/dY scale at point q
1626 double dS_dYmq = -fq * (d[m] / dz - aq);
1627 for (size_t k = 0; k < K; k++) {
1628 double v = (d[k] / dz) * fq * ((k == m) - Xq[k])
1629 + Y(x, k, q) * dS_dYmq;
1630 if (k == m) {
1631 v += S;
1632 }
1633 out[m * K + k] = v;
1634 }
1635 } else {
1636 double fq1 = wbq1 / m_wt[m]; // dX/dY scale at point q+1
1637 double dS_dYmq1 = fq1 * (d[m] / dz - aq1);
1638 for (size_t k = 0; k < K; k++) {
1639 out[m * K + k] = -(d[k] / dz) * fq1 * ((k == m) - Xq1[k])
1640 + Y(x, k, q) * dS_dYmq1;
1641 }
1642 }
1643 }
1644}
1645
1646void Flow1D::addSpeciesJacEntries(span<const double> x, size_t p, SystemJacobian& jac)
1647{
1648 size_t K = m_nsp;
1649 // p-1 interval: d(F(p-1))/dY(p) = right-endpoint derivative (AtQ=false)
1650 fluxJacobian<false>(x, p - 1, m_dFm_dYp);
1651 // p interval: d(F(p))/dY(p) = left-endpoint derivative (AtQ=true)
1652 fluxJacobian<true>(x, p, m_dF0_dYp);
1653
1654 // rows at j = p-1, p, p+1; columns are Y_m at p. Accumulate the full K×K
1655 // block first, then emit each (row, col) entry exactly once (MultiJac::
1656 // setValue overwrites; EigenSparseJacobian sums duplicate triplets).
1657 for (size_t j = p - 1; j <= p + 1; j++) {
1658 // Boundary residuals (j == 0 or j == m_points-1) have no dependence on
1659 // interior Y; skip them rather than computing rDz2 out of bounds.
1660 if (j == 0 || j + 1 == m_points) {
1661 continue;
1662 }
1663 double* blk = m_jacBlockWork.data(); // blk[m*K + k] = d rsd(Y_k, j)/d Y_m(p)
1664 std::fill(m_jacBlockWork.begin(), m_jacBlockWork.end(), 0.0);
1665 double rDz2 = 2.0 / ((z(j + 1) - z(j - 1)) * m_rho[j]);
1666 double uj = u(x, j);
1667 size_t jloc = (uj > 0.0) ? j : j + 1;
1668
1669 // diffusion flux-derivative terms
1670 if (j == p - 1) {
1671 for (size_t i = 0; i < K * K; i++) {
1672 blk[i] -= rDz2 * m_dFm_dYp[i];
1673 }
1674 } else if (j == p) {
1675 for (size_t i = 0; i < K * K; i++) {
1676 blk[i] -= rDz2 * (m_dF0_dYp[i] - m_dFm_dYp[i]);
1677 }
1678 } else { // j == p + 1
1679 for (size_t i = 0; i < K * K; i++) {
1680 blk[i] += rDz2 * m_dF0_dYp[i];
1681 }
1682 }
1683
1684 if (j == p) {
1685 for (size_t m = 0; m < K; m++) {
1686 double rW = m_wtm[j] / m_wt[m];
1687 for (size_t k = 0; k < K; k++) {
1688 // reaction term and the 1/rho chain on the reaction and
1689 // diffusion parts of the residual
1690 double diffus = 2.0 * (m_flux(k, j) - m_flux(k, j - 1))
1691 / (z(j + 1) - z(j - 1));
1692 blk[m * K + k] += m_wt[k] / m_rho[j] * m_dwdY[m * K + k]
1693 + (m_wt[k] * m_wdot(k, j) - diffus) / m_rho[j] * rW;
1694 }
1695 }
1696 }
1697
1698 // convection: -u_j * dYdz(k, j), upwinded
1699 if (jloc == p) {
1700 for (size_t k = 0; k < K; k++) {
1701 blk[k * K + k] -= uj / m_dz[jloc - 1];
1702 }
1703 } else if (jloc - 1 == p) {
1704 for (size_t k = 0; k < K; k++) {
1705 blk[k * K + k] += uj / m_dz[jloc - 1];
1706 }
1707 }
1708
1709 for (size_t m = 0; m < K; m++) {
1710 size_t gcol = loc() + index(c_offset_Y + m, p);
1711 for (size_t k = 0; k < K; k++) {
1712 // Always emit the steady-state diagonal (j == p, k == m), even
1713 // when it evaluates to exactly zero, so that MultiJac::m_ssdiag
1714 // is refreshed for the banded solver's transient diagonal. This
1715 // mirrors the forced-diagonal write in OneDim::evalJacobian's FD
1716 // loop (the `m+iloc == ipt` term).
1717 if (blk[m * K + k] != 0.0 || (j == p && k == m)) {
1718 jac.setValue(loc() + index(c_offset_Y + k, j), gcol,
1719 blk[m * K + k]);
1720 }
1721 }
1722 }
1723 }
1724}
1725
1726void Flow1D::addEnergyJacEntries(span<const double> x, size_t p, SystemJacobian& jac)
1727{
1728 size_t K = m_nsp;
1729 // m_dFm_dYp / m_dF0_dYp are fresh from addSpeciesJacEntries(p).
1730 // rows j = p-1, p, p+1; energy rows only exist at interior points
1731 for (size_t j = p - 1; j <= p + 1; j++) {
1732 if (j == 0 || j + 1 == m_points || !m_do_energy[j]) {
1733 continue; // T - T_fixed residual has no Y dependence
1734 }
1735 double rcp = 1.0 / (m_rho[j] * m_cp[j]);
1736 grad_hk(x, j); // fills m_dhk_dz(:, j)
1737 double* col = m_jacColWork.data(); // col[m] = d rsd(T, j)/d Y_m(p)
1738 std::fill(m_jacColWork.begin(), m_jacColWork.end(), 0.0);
1739
1740 // enthalpy-flux term: -(1/(rho cp)) sum_k (dhk_dz_k/W_k) * dFbar_k/dY_m
1741 // Fbar_k = 0.5(F_k(j-1) + F_k(j)); the dF/dY(p) blocks for the two
1742 // intervals adjacent to point p map to rows j as for species diffusion.
1743 const double* dFa = nullptr;
1744 const double* dFb = nullptr;
1745 if (j == p - 1) {
1746 dFa = m_dFm_dYp.data(); // dF(p-1)/dY(p) contributes to Fbar(p-1)
1747 } else if (j == p) {
1748 dFa = m_dFm_dYp.data(); // dF(p-1)/dY(p)
1749 dFb = m_dF0_dYp.data(); // dF(p)/dY(p)
1750 } else { // j == p + 1
1751 dFa = m_dF0_dYp.data(); // dF(p)/dY(p) contributes to Fbar(p+1)
1752 }
1753 for (size_t m = 0; m < K; m++) {
1754 double s = 0.0;
1755 for (size_t k = 0; k < K; k++) {
1756 double dF = (dFa ? dFa[m * K + k] : 0.0)
1757 + (dFb ? dFb[m * K + k] : 0.0);
1758 s += m_dhk_dz(k, j) / m_wt[k] * 0.5 * dF;
1759 }
1760 col[m] -= rcp * s;
1761 }
1762
1763 if (j == p) {
1764 // B = cond + sum_k(wdot_k h_k + Fbar_k dhk_dz_k/W_k) + qdot; the
1765 // -u*dTdz part of the residual has no Y dependence (rho cancels)
1766 double B = conduction(x, j);
1767 for (size_t k = 0; k < K; k++) {
1768 double flxk = 0.5 * (m_flux(k, j - 1) + m_flux(k, j));
1769 B += m_wdot(k, j) * m_hk(k, j);
1770 B += flxk * m_dhk_dz(k, j) / m_wt[k];
1771 }
1772 B += m_qdotRadiation[j]; // zero unless radiation enabled
1773 // species heat capacities (mass basis) at point j; setGas was
1774 // called for point p == j by evalJacobianAnalytic
1776 for (size_t m = 0; m < K; m++) {
1777 double cpm = m_jacCpWork[m] / m_wt[m]; // J/kg/K
1778 // 1/(rho cp) chain on -B/(rho cp). The residual is
1779 // rsd = -u dTdz - B/(rho cp); since d(1/(rho cp))/dY_m =
1780 // (1/(rho cp))(wtm/W_m - cpm/cp) (FD-verified), the chain term
1781 // is -B*rcp*(wtm/W_m - cpm/cp).
1782 col[m] -= B * rcp * (m_wtm[j] / m_wt[m] - cpm / m_cp[j]);
1783 // reaction chain: -(1/(rho cp)) sum_k h_k dwdot_k/dY_m
1784 double s = 0.0;
1785 for (size_t k = 0; k < K; k++) {
1786 s += m_hk(k, j) * m_dwdY[m * K + k];
1787 }
1788 col[m] -= rcp * s;
1789 }
1790 if (m_do_radiation) {
1791 // qdot(j) = 2 kP (2 sigma T^4 - BL - BR)
1792 // kP = sum_s P * X_s * f_s(T), d(qdot)/dX_s = 2 P f_s(T) * bracket
1793 // dX_s/dY_m = (wbar/W_m)(delta_sm - X_s)
1794 // The energy residual contribution is -qdot/(rho cp), so
1795 // col[m] gets -(1/(rho cp)) * d(qdot)/dY_m
1796 double bracket = 2.0 * StefanBoltz * pow(T(x, j), 4)
1797 - m_epsilon_left * StefanBoltz * pow(T(x, 0), 4)
1798 - m_epsilon_right * StefanBoltz * pow(T(x, m_points - 1), 4);
1799 for (int s : {0, 1}) { // m_kRadiating: 0 = CO2, 1 = H2O
1800 size_t ks = m_kRadiating[s];
1801 if (ks == npos) {
1802 continue;
1803 }
1804 double fs = radiationPolyFactor(x, j, s);
1805 double Xs = Y(x, ks, j) * m_wtm[j] / m_wt[ks];
1806 for (size_t m = 0; m < K; m++) {
1807 double dX = (m_wtm[j] / m_wt[m]) * ((m == ks ? 1.0 : 0.0) - Xs);
1808 col[m] -= rcp * 2.0 * m_press * fs * bracket * dX;
1809 }
1810 }
1811 }
1812 }
1813
1814 size_t grow = loc() + index(c_offset_T, j);
1815 for (size_t m = 0; m < K; m++) {
1816 if (col[m] != 0.0) {
1817 jac.setValue(grow, loc() + index(c_offset_Y + m, p), col[m]);
1818 }
1819 }
1820 }
1821}
1822
1823void Flow1D::addFlowJacEntries(span<const double> x, size_t p, SystemJacobian& jac)
1824{
1825 size_t K = m_nsp;
1826 // d(rho_q)/dY_m(q) = -rho_q * wbar_q / W_m
1827 auto addRhoChain = [&](size_t row_j, size_t q, double drsd_drho) {
1828 // emits d(rsd[c_offset_U, row_j])/dY_m(q) for q == p only
1829 if (q != p) {
1830 return;
1831 }
1832 size_t grow = loc() + index(c_offset_U, row_j);
1833 for (size_t m = 0; m < K; m++) {
1834 double v = drsd_drho * (-m_rho[q] * m_wtm[q] / m_wt[m]);
1835 jac.setValue(grow, loc() + index(c_offset_Y + m, p), v);
1836 }
1837 };
1838
1839 for (size_t j = p - 1; j <= p + 1; j++) {
1840 if (j == 0 || j + 1 == m_points) {
1841 continue;
1842 }
1843 if (m_usesLambda) { // axisymmetric
1844 // rsd_U(j) = -(rho_u(j+1)-rho_u(j))/m_dz[j] - (rho(j+1)V(j+1)+rho(j)V(j))
1845 addRhoChain(j, j, u(x, j) / m_dz[j] - V(x, j));
1846 addRhoChain(j, j + 1, -u(x, j + 1) / m_dz[j] - V(x, j + 1));
1847 } else if (m_isFree) {
1848 if (z(j) > m_zfixed) {
1849 addRhoChain(j, j, -u(x, j) / m_dz[j - 1]);
1850 addRhoChain(j, j - 1, u(x, j - 1) / m_dz[j - 1]);
1851 } else if (z(j) == m_zfixed) {
1852 if (!m_do_energy[j]) {
1853 addRhoChain(j, j, u(x, j));
1854 } // else: T-based residual, no Y dependence
1855 } else { // z(j) < m_zfixed
1856 addRhoChain(j, j + 1, -u(x, j + 1) / m_dz[j]);
1857 addRhoChain(j, j, u(x, j) / m_dz[j]);
1858 }
1859 } else { // unstrained
1860 addRhoChain(j, j, u(x, j));
1861 addRhoChain(j, j - 1, -u(x, j - 1));
1862 }
1863
1864 // momentum row (axisymmetric only): ((shear - Lambda)/rho_j) chain
1865 if (m_usesLambda && j == p) {
1866 double Mj = shear(x, j) - Lambda(x, j);
1867 size_t grow = loc() + index(c_offset_V, j);
1868 for (size_t m = 0; m < K; m++) {
1869 double v = Mj / m_rho[j] * m_wtm[j] / m_wt[m];
1870 jac.setValue(grow, loc() + index(c_offset_Y + m, p), v);
1871 }
1872 }
1873 }
1874}
1875
1876} // namespace
Declarations for class SystemJacobian.
Header file defining class TransportFactory (see TransportFactory)
Headers for the Transport object, which is the virtual base class for all transport property evaluato...
const AnyValue & getMetadata(const string &key) const
Get a value from the metadata applicable to the AnyMap tree containing this node.
Definition AnyMap.cpp:623
A map of string keys to values whose type can vary at runtime.
Definition AnyMap.h:431
double getDouble(const string &key, double default_) const
If key exists, return it as a double, otherwise return default_.
Definition AnyMap.cpp:1580
bool hasKey(const string &key) const
Returns true if the map contains an item named key.
Definition AnyMap.cpp:1477
A wrapper for a variable whose type is determined at runtime.
Definition AnyMap.h:88
const string & asString() const
Return the held value, if it is a string.
Definition AnyMap.cpp:782
bool & asBool()
Return the held value, if it is a bool.
Definition AnyMap.cpp:914
bool empty() const
Return boolean indicating whether AnyValue is empty.
Definition AnyMap.cpp:690
bool isScalar() const
Returns true if the held value is a scalar type (such as double, long int, string,...
Definition AnyMap.cpp:694
const vector< T > & asVector(size_t nMin=npos, size_t nMax=npos) const
Return the held value, if it is a vector of type T.
Definition AnyMap.inl.h:109
span< double > col(size_t j)
Return a writable span over column j.
Definition Array.h:189
virtual void resize(size_t n, size_t m, double v=0.0)
Resize the array, and fill the new entries with 'v'.
Definition Array.cpp:52
Array size error.
Base class for exceptions thrown by Cantera classes.
Base class for one-dimensional domains.
Definition Domain1D.h:30
size_t lastPoint() const
The index of the last (that is, right-most) grid point belonging to this domain.
Definition Domain1D.h:590
size_t m_iloc
Starting location within the solution vector for unknowns that correspond to this domain.
Definition Domain1D.h:754
shared_ptr< Solution > m_solution
Composite thermo/kinetics/transport handler.
Definition Domain1D.h:772
OneDim * m_container
Parent OneDim simulation containing this and adjacent domains.
Definition Domain1D.h:745
size_t nComponents() const
Number of components at each grid point.
Definition Domain1D.h:143
size_t size() const
Return the size of the solution vector (the product of m_nv and m_points).
Definition Domain1D.h:569
virtual void setMeta(const AnyMap &meta)
Retrieve meta data.
Definition Domain1D.cpp:155
string id() const
Returns the identifying tag for this domain.
Definition Domain1D.h:635
double zmin() const
Get the coordinate [m] of the first (leftmost) grid point in this domain.
Definition Domain1D.h:653
span< double > grid()
Access the array of grid coordinates [m].
Definition Domain1D.h:663
size_t m_nv
Number of solution components.
Definition Domain1D.h:733
size_t nPoints() const
Number of grid points in this domain.
Definition Domain1D.h:148
bool m_force_full_update
see forceFullUpdate()
Definition Domain1D.h:768
shared_ptr< vector< double > > m_state
data pointer shared from OneDim
Definition Domain1D.h:730
string m_jacobianMode
see setJacobianMode()
Definition Domain1D.h:769
vector< double > values(const string &component) const
Retrieve component values.
Definition Domain1D.h:424
virtual void resize(size_t nv, size_t np)
Resize the domain to have nv components and np grid points.
Definition Domain1D.cpp:36
double z(size_t jlocal) const
Get the coordinate [m] of the point with local index jlocal
Definition Domain1D.h:648
double m_press
pressure [Pa]
Definition Domain1D.h:728
virtual double value(const string &component) const
Set a single component value at a boundary.
Definition Domain1D.h:400
vector< double > m_z
1D spatial grid coordinates
Definition Domain1D.h:742
size_t m_points
Number of grid points.
Definition Domain1D.h:734
virtual void show(span< const double > x)
Print the solution.
Definition Domain1D.cpp:224
string m_id
Identity tag for the domain.
Definition Domain1D.h:764
unique_ptr< Refiner > m_refiner
Refiner object used for placing grid points.
Definition Domain1D.h:765
void setBounds(size_t n, double lower, double upper)
Set the upper and lower bounds for a solution component, n.
Definition Domain1D.h:198
double zmax() const
Get the coordinate [m] of the last (rightmost) grid point in this domain.
Definition Domain1D.h:658
size_t firstPoint() const
The index of the first (that is, left-most) grid point belonging to this domain.
Definition Domain1D.h:585
void needJacUpdate()
Set this if something has changed in the governing equations (for example, the value of a constant ha...
Definition Domain1D.cpp:119
size_t index(size_t n, size_t j) const
Returns the index of the solution vector, which corresponds to component n at grid point j.
Definition Domain1D.h:390
virtual size_t loc(size_t j=0) const
Location of the start of the local solution vector in the global solution vector.
Definition Domain1D.h:580
virtual AnyMap getMeta() const
Retrieve meta data.
Definition Domain1D.cpp:127
void fluxJacobian(span< const double > x, size_t q, span< double > out)
d(F_k(q))/dY_m(q) (AtQ=true) or d(F_k(q))/dY_m(q+1) (AtQ=false), K×K column-major.
Definition Flow1D.cpp:1566
void setLeftControlPointTemperature(double temperature)
Sets the temperature of the left control point.
Definition Flow1D.cpp:1348
virtual void updateDiffFluxes(span< const double > x, size_t j0, size_t j1)
Update the diffusive mass fluxes.
Definition Flow1D.cpp:441
ThermoPhase * m_thermo
Phase object used for calculating thermodynamic properties.
Definition Flow1D.h:994
void setLeftControlPointCoordinate(double z_left)
Sets the coordinate of the left control point.
Definition Flow1D.cpp:1363
bool usingAnalyticJacobian() const
true if analytic Y-columns are active for this domain
Definition Flow1D.cpp:1466
vector< double > m_zfix
Relative coordinates used to specify a fixed temperature profile.
Definition Flow1D.h:1073
virtual void evalEnergy(span< const double > x, span< double > rsd, span< int > diag, double rdt, size_t jmin, size_t jmax)
Evaluate the energy equation residual.
Definition Flow1D.cpp:666
double Lambda(span< const double > x, size_t j) const
Get the radial pressure gradient [N/m⁴] at point j from the local state vector x
Definition Flow1D.h:707
double density(size_t j) const
Get the density [kg/m³] at point j
Definition Flow1D.h:376
void setupGrid(span< const double > z) override
Set up initial grid.
Definition Flow1D.cpp:198
size_t m_kExcessLeft
Index of species with a large mass fraction at the left boundary, for which the mass fraction may be ...
Definition Flow1D.h:1081
void setMeta(const AnyMap &state) override
Retrieve meta data.
Definition Flow1D.cpp:1160
double m_zLeft
Location of the left control point when two-point control is enabled.
Definition Flow1D.h:1088
void fixTemperature(size_t j=npos)
Specify that the the temperature should be held fixed at point j.
Definition Flow1D.cpp:1285
vector< double > m_tfix
Fixed temperature values at the relative coordinates specified in m_zfix.
Definition Flow1D.h:1077
void setRightControlPointCoordinate(double z_right)
Sets the coordinate of the right control point.
Definition Flow1D.cpp:1418
void eval(size_t jGlobal, span< const double > xGlobal, span< double > rsdGlobal, span< int > diagGlobal, double rdt) override
Evaluate the residual functions for axisymmetric stagnation flow.
Definition Flow1D.cpp:326
vector< double > m_dFm_dYp
d(F_k(p-1))/dY_m(p) and d(F_k(p))/dY_m(p), column-major K×K.
Definition Flow1D.h:945
ThermoPhase & phase()
Access the phase object used to compute thermodynamic properties for points in this domain.
Definition Flow1D.h:69
double T_prev(size_t j) const
Get the temperature at point j from the previous time step.
Definition Flow1D.h:680
bool twoPointControlEnabled() const
Returns the status of the two-point control.
Definition Flow1D.h:354
size_t rightExcessSpecies() const
Index of the species on the right boundary with the largest mass fraction.
Definition Flow1D.h:435
bool m_do_soret
true if the Soret diffusion term should be calculated.
Definition Flow1D.h:1023
Kinetics * m_kin
Kinetics object used for calculating species production rates.
Definition Flow1D.h:997
vector< double > m_qdotRadiation
radiative heat loss vector
Definition Flow1D.h:1058
size_t componentIndex(const string &name, bool checkAlias=true) const override
Index of component with name name.
Definition Flow1D.cpp:838
double Uo(span< const double > x, size_t j) const
Get the oxidizer inlet velocity [m/s] linked to point j from the local state vector x.
Definition Flow1D.h:715
double m_tLeft
Temperature of the left control point when two-point control is enabled.
Definition Flow1D.h:1091
void setRightControlPointTemperature(double temperature)
Sets the temperature of the right control point.
Definition Flow1D.cpp:1403
virtual bool doElectricField() const
Retrieve flag indicating whether electric field is solved or not (used by IonFlow specialization)
Definition Flow1D.cpp:1265
bool hasComponent(const string &name, bool checkAlias=true) const override
Check whether the Domain contains a component.
Definition Flow1D.cpp:858
double V(span< const double > x, size_t j) const
Get the spread rate (tangential velocity gradient) [1/s] at point j from the local state vector x.
Definition Flow1D.h:696
virtual void updateProperties(size_t jg, span< const double > x, size_t jmin, size_t jmax)
Update the properties (thermo, transport, and diffusion flux).
Definition Flow1D.cpp:365
void resize(size_t components, size_t points) override
Change the grid size. Called after grid refinement.
Definition Flow1D.cpp:153
void setValues(const string &component, span< const double > values) override
Specify component values.
Definition Flow1D.cpp:979
void addFlowJacEntries(span< const double > x, size_t p, SystemJacobian &jac)
Add continuity- and momentum-row Jacobian entries for grid point p.
Definition Flow1D.cpp:1823
void checkAnalyticJacobian() const override
Validate that an explicitly requested analytic Jacobian (jacobian_mode == "analytic") can be used for...
Definition Flow1D.cpp:1477
bool m_usesLambda
Flag that is true for counterflow configurations that use the pressure eigenvalue in the radial mome...
Definition Flow1D.h:1051
vector< double > m_fixedtemp
Fixed values of the temperature at each grid point that are used when solving with the energy equatio...
Definition Flow1D.h:1066
double conduction(span< const double > x, size_t j) const
Compute the conduction term from the energy equation using a central three-point differencing scheme.
Definition Flow1D.h:856
virtual void evalLambda(span< const double > x, span< double > rsd, span< int > diag, double rdt, size_t jmin, size_t jmax)
Evaluate the radial pressure gradient equation residual.
Definition Flow1D.cpp:623
vector< double > m_cp
Specific heat capacity at each grid point.
Definition Flow1D.h:897
virtual void evalElectricField(span< const double > x, span< double > rsd, span< int > diag, double rdt, size_t jmin, size_t jmax)
Evaluate the electric field equation residual to be zero everywhere.
Definition Flow1D.cpp:788
void enableTwoPointControl(bool twoPointControl)
Sets the status of the two-point control.
Definition Flow1D.cpp:1428
void addSpeciesJacEntries(span< const double > x, size_t p, SystemJacobian &jac)
Add species-row Jacobian entries for grid point p.
Definition Flow1D.cpp:1646
double m_tRight
Temperature of the right control point when two-point control is enabled.
Definition Flow1D.h:1097
void setBoundaryEmissivities(double e_left, double e_right)
Set the emissivities for the boundary values.
Definition Flow1D.cpp:1271
ThermoBasis m_fluxGradientBasis
Determines whether diffusive fluxes are computed using gradients of mass fraction or mole fraction.
Definition Flow1D.h:1028
void addEnergyJacEntries(span< const double > x, size_t p, SystemJacobian &jac)
Add energy-row Jacobian entries for grid point p.
Definition Flow1D.cpp:1726
void setFluxGradientBasis(ThermoBasis fluxGradientBasis)
Compute species diffusive fluxes with respect to their mass fraction gradients (fluxGradientBasis = T...
Definition Flow1D.cpp:236
shared_ptr< SolutionArray > toArray(bool normalize=false) override
Save the state of this domain to a SolutionArray.
Definition Flow1D.cpp:1072
void solveEnergyEqn(size_t j=npos)
Specify that the energy equation should be solved at point j.
Definition Flow1D.cpp:1229
void setGasAtMidpoint(span< const double > x, size_t j)
Set the gas state to be consistent with the solution at the midpoint between j and j + 1.
Definition Flow1D.cpp:262
virtual void evalSpecies(span< const double > x, span< double > rsd, span< int > diag, double rdt, size_t jmin, size_t jmax)
Evaluate the species equations' residuals.
Definition Flow1D.cpp:748
vector< double > m_rho
Density at each grid point.
Definition Flow1D.h:894
double rho_u(span< const double > x, size_t j) const
Get the axial mass flux [kg/m²/s] at point j from the local state vector x.
Definition Flow1D.h:685
void _setTransport(shared_ptr< Transport > trans) override
Update transport model to existing instance.
Definition Flow1D.cpp:133
vector< bool > m_do_energy
For each point in the domain, true if energy equation is solved or false if temperature is held const...
Definition Flow1D.h:1020
double m_epsilon_right
Emissivity of the surface to the right of the domain.
Definition Flow1D.h:1008
vector< double > m_tcon
Thermal conductivity at each grid point [W/m/K].
Definition Flow1D.h:901
vector< double > m_diff
Coefficient used in diffusion calculations for each species at each grid point.
Definition Flow1D.h:909
double Y_prev(size_t k, size_t j) const
Get the mass fraction of species k at point j from the previous time step.
Definition Flow1D.h:740
double Y(span< const double > x, size_t k, size_t j) const
Get the mass fraction of species k at point j from the local state vector x.
Definition Flow1D.h:721
vector< double > m_dz
Grid spacing. Element j holds the value of z(j+1) - z(j).
Definition Flow1D.h:891
double T(span< const double > x, size_t j) const
Get the temperature at point j from the local state vector x.
Definition Flow1D.h:671
Array2D m_flux
Array of size m_nsp by m_points for saving diffusive mass fluxes.
Definition Flow1D.h:919
double dTdz(span< const double > x, size_t j) const
Calculates the spatial derivative of temperature T with respect to z at point j using upwind differen...
Definition Flow1D.h:813
ThermoBasis fluxGradientBasis() const
Compute species diffusive fluxes with respect to their mass fraction gradients (fluxGradientBasis = T...
Definition Flow1D.h:119
int m_analyticJacCapable
true if analytic Jacobian columns are supported for the current configuration (set lazily; see usingA...
Definition Flow1D.h:936
vector< double > m_visc
Dynamic viscosity at each grid point [Pa∙s].
Definition Flow1D.h:900
double dVdz(span< const double > x, size_t j) const
Calculates the spatial derivative of velocity V with respect to z at point j using upwind differencin...
Definition Flow1D.h:784
double m_epsilon_left
Emissivity of the surface to the left of the domain.
Definition Flow1D.h:1004
Transport * m_trans
Transport object used for calculating transport properties.
Definition Flow1D.h:1000
double m_tfixed
Temperature at the point used to fix the flame location.
Definition Flow1D.h:1104
virtual bool componentActive(size_t n) const
Returns true if the specified component is an active part of the solver state.
Definition Flow1D.cpp:876
vector< double > m_jacBlockWork
per-(row, col-point) accumulation block (K×K)
Definition Flow1D.h:947
void computeRadiation(span< const double > x, size_t jmin, size_t jmax)
Computes the radiative heat loss vector over points jmin to jmax and stores the data in the qdotRadia...
Definition Flow1D.cpp:503
Array2D m_wdot
Array of size m_nsp by m_points for saving species production rates.
Definition Flow1D.h:928
Array2D m_hk
Array of size m_nsp by m_points for saving molar enthalpies.
Definition Flow1D.h:922
void _setKinetics(shared_ptr< Kinetics > kin) override
Update transport model to existing instance.
Definition Flow1D.cpp:127
double dYdz(span< const double > x, size_t k, size_t j) const
Calculates the spatial derivative of the species mass fraction with respect to z for species k at po...
Definition Flow1D.h:799
vector< double > m_dwdY
dense dwdot/dY at one point, column-major K×K
Definition Flow1D.h:941
void computeWdotDerivatives(size_t j)
Chain rule: fill m_dwdY from m_ddC at point j (state already set by caller)
Definition Flow1D.cpp:1541
virtual bool analyticJacobianSupported() const
true if this flow domain type provides analytic Jacobian species columns consistent with its residual...
Definition Flow1D.h:966
vector< double > m_jacXwork
mole fractions at two interval endpoints (2K)
Definition Flow1D.h:948
void getValues(const string &component, span< double > values) const override
Retrieve component values.
Definition Flow1D.cpp:958
void fromArray(const shared_ptr< SolutionArray > &arr) override
Restore the solution for this domain from a SolutionArray.
Definition Flow1D.cpp:1115
virtual void updateTransport(span< const double > x, size_t j0, size_t j1)
Update the transport properties at grid points in the range from j0 to j1, based on solution x.
Definition Flow1D.cpp:389
size_t mindex(size_t k, size_t j, size_t m)
Array access mapping for a 3D array stored in a 1D vector.
Definition Flow1D.h:870
void updateState(size_t loc) override
Update state at given location to state of associated Solution object.
Definition Flow1D.cpp:942
bool m_do_multicomponent
true if transport fluxes are computed using the multicomponent diffusion coefficients,...
Definition Flow1D.h:1032
double V_prev(size_t j) const
Get the spread rate [1/s] at point j from the previous time step.
Definition Flow1D.h:701
Eigen::SparseMatrix< double > m_ddC
Sparse dwdot/dC (∂ω̇_k/∂C_m) at one point; pattern built on first fill and reused across points/calls...
Definition Flow1D.h:940
bool hasAnalyticJacobian(size_t j, size_t n) const override
Returns true if this domain computes the Jacobian column for component n at (domain-local) grid point...
Definition Flow1D.cpp:1512
vector< double > m_wt
Molecular weight of each species.
Definition Flow1D.h:896
void show(span< const double > x) override
Print the solution.
Definition Flow1D.cpp:797
virtual void evalMomentum(span< const double > x, span< double > rsd, span< int > diag, double rdt, size_t jmin, size_t jmax)
Evaluate the momentum equation residual.
Definition Flow1D.cpp:587
size_t leftExcessSpecies() const
Index of the species on the left boundary with the largest mass fraction.
Definition Flow1D.h:430
bool m_isFree
Flag that is true for freely propagating flames anchored by a temperature fixed point.
Definition Flow1D.h:1046
Array2D m_dhk_dz
Array of size m_nsp by m_points-1 for saving enthalpy fluxes.
Definition Flow1D.h:925
vector< double > m_wtm
Mean molecular weight at each grid point.
Definition Flow1D.h:895
vector< double > m_multidiff
Vector of size m_nsp × m_nsp × m_points for saving multicomponent diffusion coefficients.
Definition Flow1D.h:913
bool m_twoPointControl
Flag for activating two-point flame control.
Definition Flow1D.h:1054
double m_zfixed
Location of the point where temperature is fixed.
Definition Flow1D.h:1101
size_t m_nsp
Number of species in the mechanism.
Definition Flow1D.h:991
virtual void evalContinuity(span< const double > x, span< double > rsd, span< int > diag, double rdt, size_t jmin, size_t jmax)
Evaluate the continuity equation residual.
Definition Flow1D.cpp:530
double u(span< const double > x, size_t j) const
Get the axial velocity [m/s] at point j from the local state vector x.
Definition Flow1D.h:690
void setProfile(const string &component, span< const double > pos, span< const double > values) override
Specify a profile for a component.
Definition Flow1D.cpp:1021
void getResiduals(const string &component, span< double > values) const override
Retrieve internal work array values for a component.
Definition Flow1D.cpp:1000
double shear(span< const double > x, size_t j) const
Compute the shear term from the momentum equation using a central three-point differencing scheme.
Definition Flow1D.h:841
void _finalize(span< const double > x) override
In some cases, a domain may need to set parameters that depend on the initial solution estimate.
Definition Flow1D.cpp:274
double leftControlPointCoordinate() const
Returns the z-coordinate of the left control point.
Definition Flow1D.cpp:1333
AnyMap getMeta() const override
Retrieve meta data.
Definition Flow1D.cpp:891
double X(span< const double > x, size_t k, size_t j) const
Get the mole fraction of species k at point j from the local state vector x.
Definition Flow1D.h:746
double radiationPolyFactor(span< const double > x, size_t j, int s) const
Planck-mean absorption polynomial factor for radiating species s (0: CO2, 1: H2O) at grid point j.
Definition Flow1D.cpp:487
bool analyticRequested() const
true if the analytic Jacobian is requested, either explicitly (jacobian_mode == "analytic") or by the...
Definition Flow1D.h:957
double leftControlPointTemperature() const
Returns the temperature at the left control point.
Definition Flow1D.cpp:1318
string componentName(size_t n) const override
Name of component n. May be overloaded.
Definition Flow1D.cpp:814
void updateThermo(span< const double > x, size_t j0, size_t j1)
Update the thermodynamic properties from point j0 to point j1 (inclusive), based on solution x.
Definition Flow1D.h:466
bool isStrained() const
Retrieve flag indicating whether flow uses radial momentum.
Definition Flow1D.h:397
void resetBadValues(span< double > x) override
When called, this function should reset "bad" values in the state vector such as negative species con...
Definition Flow1D.cpp:213
vector< double > m_jacColWork
concentrations / energy-row accumulator (K)
Definition Flow1D.h:949
string transportModel() const
Retrieve transport model.
Definition Flow1D.cpp:232
double rightControlPointCoordinate() const
Returns the z-coordinate of the right control point.
Definition Flow1D.cpp:1388
Array2D m_dthermal
Array of size m_nsp by m_points for saving thermal diffusion coefficients.
Definition Flow1D.h:916
void evalJacobianAnalytic(span< const double > xGlobal, SystemJacobian &jac) override
Add this domain's analytic Jacobian entries (for columns claimed by hasAnalyticJacobian()) to jac via...
Definition Flow1D.cpp:1517
virtual void evalUo(span< const double > x, span< double > rsd, span< int > diag, double rdt, size_t jmin, size_t jmax)
Evaluate the oxidizer axial velocity equation residual.
Definition Flow1D.cpp:708
vector< double > m_jacCpWork
species heat capacities (K)
Definition Flow1D.h:950
string domainType() const override
Domain type flag.
Definition Flow1D.cpp:117
void _getInitialSoln(span< double > x) override
Write the initial solution estimate into array x.
Definition Flow1D.cpp:246
bool m_dovisc
Determines whether the viscosity term in the momentum equation is calculated.
Definition Flow1D.h:1041
Flow1D(shared_ptr< Solution > phase, const string &id="", size_t points=1)
Create a new flow domain.
Definition Flow1D.cpp:35
void setPressure(double p)
Set the pressure.
Definition Flow1D.h:125
double m_zRight
Location of the right control point when two-point control is enabled.
Definition Flow1D.h:1094
virtual void solveElectricField()
Set to solve electric field in a point (used by IonFlow specialization)
Definition Flow1D.cpp:1253
virtual void fixElectricField()
Set to fix voltage in a point (used by IonFlow specialization)
Definition Flow1D.cpp:1259
void probeAnalyticJacobian() const
Probe (once) whether the kinetics object supports the composition derivatives required for the analyt...
Definition Flow1D.cpp:1446
size_t m_kExcessRight
Index of species with a large mass fraction at the right boundary, for which the mass fraction may be...
Definition Flow1D.h:1085
vector< size_t > m_kRadiating
Indices within the ThermoPhase of the radiating species.
Definition Flow1D.h:1012
void setTransportModel(const string &model) override
Set transport model by name.
Definition Flow1D.cpp:222
void setFlatProfile(const string &component, double value) override
Specify a flat profile for a component.
Definition Flow1D.cpp:1054
void setGas(span< const double > x, size_t j)
Set the gas object state to be consistent with the solution at point j.
Definition Flow1D.cpp:255
double rightControlPointTemperature() const
Returns the temperature at the right control point.
Definition Flow1D.cpp:1373
double T_fixed(size_t j) const
The fixed temperature value at point j.
Definition Flow1D.h:162
vector< double > m_ybar
Holds the average of the species mass fractions between grid points j and j+1.
Definition Flow1D.h:1110
bool m_do_radiation
Determines whether radiative heat loss is calculated.
Definition Flow1D.h:1036
virtual void grad_hk(span< const double > x, size_t j)
Compute the spatial derivative of species specific molar enthalpies using upwind differencing.
Definition Flow1D.cpp:1309
An array index is out of range.
An error indicating that an unimplemented function has been called.
void resize() override
Call to set the size of internal data structures after first defining the system or if the problem si...
Definition OneDim.cpp:150
const vector< double > & _workVector() const
Access internal work array.
Definition OneDim.h:247
void getMassFractions(span< double > y) const
Get the species mass fractions.
Definition Phase.cpp:489
size_t speciesIndex(const string &name, bool raise=true) const
Returns the index of a species named 'name' within the Phase object.
Definition Phase.cpp:127
span< const double > molecularWeights() const
Return a const reference to the internal vector of molecular weights.
Definition Phase.cpp:411
double temperature() const
Temperature (K).
Definition Phase.h:586
virtual void setPressure(double p)
Set the internally stored pressure (Pa) at constant temperature and composition.
Definition Phase.h:640
double meanMolecularWeight() const
The mean molecular weight. Units: (kg/kmol)
Definition Phase.h:677
string speciesName(size_t k) const
Name of the species with index k.
Definition Phase.cpp:143
virtual void getConcentrations(span< double > c) const
Get the species concentrations (kmol/m^3).
Definition Phase.cpp:501
virtual double density() const
Density (kg/m^3).
Definition Phase.h:611
virtual void setTemperature(double temp)
Set the internally stored temperature of the phase (K).
Definition Phase.h:647
virtual void setMassFractions(span< const double > y)
Set the mass fractions to the specified values and normalize them.
Definition Phase.cpp:342
virtual void setMassFractions_NoNorm(span< const double > y)
Set the mass fractions to the specified values without normalizing.
Definition Phase.cpp:362
virtual double pressure() const
Return the thermodynamic pressure (Pa).
Definition Phase.h:604
string name() const
Return the name of the phase.
Definition Phase.cpp:20
static shared_ptr< SolutionArray > create(const shared_ptr< Solution > &sol, int size=0, const AnyMap &meta={})
Instantiate a new SolutionArray reference.
Abstract base class representing Jacobian matrices and preconditioners used in nonlinear solvers.
virtual void setValue(size_t row, size_t col, double value)
Set a value at the specified row and column of the jacobian triplet vector.
virtual void getPartialMolarCp(span< double > cpbar) const
Return an array of partial molar heat capacities for the species in the mixture.
virtual double maxTemp(size_t k=npos) const
Maximum temperature for which the thermodynamic data for the species are valid.
const AnyMap & input() const
Access input data associated with the phase description.
virtual void getThermalDiffCoeffs(span< double > dt)
Return a vector of thermal diffusion coefficients [kg/m/s].
Definition Transport.h:261
virtual string transportModel() const
Identifies the model represented by this Transport object.
Definition Transport.h:101
virtual double thermalConductivity()
Get the mixture thermal conductivity [W/m/K].
Definition Transport.h:152
virtual double viscosity()
Get the dynamic viscosity [Pa·s].
Definition Transport.h:126
virtual void getMixDiffCoeffsMass(span< double > d)
Returns a vector of mixture averaged diffusion coefficients [m²/s].
Definition Transport.h:323
virtual void getMixDiffCoeffs(span< double > d)
Return a vector of mixture averaged diffusion coefficients [m²/s].
Definition Transport.h:311
virtual void getMultiDiffCoeffs(const size_t ld, span< double > d)
Return the multicomponent diffusion coefficients [m²/s].
Definition Transport.h:296
Header for a file containing miscellaneous numerical functions.
This file contains definitions for utility functions and text for modules, inputfiles and logging,...
Eigen::SparseMatrix< double > netProductionRates_ddCi()
Calculate derivatives for species net production rates with respect to species concentration at const...
Definition Kinetics.cpp:609
void writelog(const string &fmt, const Args &... args)
Write a formatted message to the screen.
Definition global.h:171
double linearInterp(double x, span< const double > xpts, span< const double > fpts)
Linearly interpolate a function defined on a discrete grid.
Definition funcs.cpp:13
const double OneAtm
One atmosphere [Pa].
Definition ct_defs.h:99
const double StefanBoltz
Stefan-Boltzmann constant [W/m2/K4].
Definition ct_defs.h:131
void warn_user(const string &method, const string &msg, const Args &... args)
Print a user warning raised from method as CanteraWarning.
Definition global.h:263
Namespace for the Cantera kernel.
Definition AnyMap.cpp:595
const size_t npos
index returned by functions to indicate "no position"
Definition ct_defs.h:183
const map< string, string > & _componentAliasMap()
Return mapping of component alias names to standardized component names.
const double Undef
Fairly random number to be used to initialize variables against to see if they are subsequently defin...
Definition ct_defs.h:167
@ c_offset_U
axial velocity [m/s]
Definition Flow1D.h:26
@ c_offset_L
(1/r)dP/dr
Definition Flow1D.h:29
@ c_offset_V
strain rate
Definition Flow1D.h:27
@ c_offset_E
electric field
Definition Flow1D.h:30
@ c_offset_Y
mass fractions
Definition Flow1D.h:32
@ c_offset_Uo
oxidizer axial velocity [m/s]
Definition Flow1D.h:31
@ c_offset_T
temperature [kelvin]
Definition Flow1D.h:28
ThermoBasis
Differentiate between mole fractions and mass fractions for input mixture composition.