1 /**
2 * @file Sim1D.cpp
3 */
4
5 // This file is part of Cantera. See License.txt in the top-level directory or
6 // at https://cantera.org/license.txt for license and copyright information.
7
8 #include "cantera/oneD/Sim1D.h"
9 #include "cantera/oneD/MultiJac.h"
10 #include "cantera/oneD/StFlow.h"
11 #include "cantera/oneD/MultiNewton.h"
12 #include "cantera/oneD/refine.h"
13 #include "cantera/numerics/funcs.h"
14 #include "cantera/base/xml.h"
15 #include "cantera/base/stringUtils.h"
16 #include "cantera/numerics/Func1.h"
17 #include <limits>
18
19 using namespace std;
20
21 namespace Cantera
22 {
23
Sim1D(vector<Domain1D * > & domains)24 Sim1D::Sim1D(vector<Domain1D*>& domains) :
25 OneDim(domains),
26 m_steady_callback(0)
27 {
28 // resize the internal solution vector and the work array, and perform
29 // domain-specific initialization of the solution vector.
30 resize();
31 for (size_t n = 0; n < nDomains(); n++) {
32 domain(n)._getInitialSoln(&m_x[start(n)]);
33 }
34
35 // set some defaults
36 m_tstep = 1.0e-5;
37 m_steps = { 10 };
38 }
39
setInitialGuess(const std::string & component,vector_fp & locs,vector_fp & vals)40 void Sim1D::setInitialGuess(const std::string& component, vector_fp& locs, vector_fp& vals)
41 {
42 for (size_t dom=0; dom<nDomains(); dom++) {
43 Domain1D& d = domain(dom);
44 size_t ncomp = d.nComponents();
45 for (size_t comp=0; comp<ncomp; comp++) {
46 if (d.componentName(comp)==component) {
47 setProfile(dom,comp,locs,vals);
48 }
49 }
50 }
51 }
52
setValue(size_t dom,size_t comp,size_t localPoint,doublereal value)53 void Sim1D::setValue(size_t dom, size_t comp, size_t localPoint, doublereal value)
54 {
55 size_t iloc = domain(dom).loc() + domain(dom).index(comp, localPoint);
56 AssertThrowMsg(iloc < m_x.size(), "Sim1D::setValue",
57 "Index out of bounds: {} > {}", iloc, m_x.size());
58 m_x[iloc] = value;
59 }
60
value(size_t dom,size_t comp,size_t localPoint) const61 doublereal Sim1D::value(size_t dom, size_t comp, size_t localPoint) const
62 {
63 size_t iloc = domain(dom).loc() + domain(dom).index(comp, localPoint);
64 AssertThrowMsg(iloc < m_x.size(), "Sim1D::value",
65 "Index out of bounds: {} > {}", iloc, m_x.size());
66 return m_x[iloc];
67 }
68
workValue(size_t dom,size_t comp,size_t localPoint) const69 doublereal Sim1D::workValue(size_t dom, size_t comp, size_t localPoint) const
70 {
71 size_t iloc = domain(dom).loc() + domain(dom).index(comp, localPoint);
72 AssertThrowMsg(iloc < m_x.size(), "Sim1D::workValue",
73 "Index out of bounds: {} > {}", iloc, m_x.size());
74 return m_xnew[iloc];
75 }
76
setProfile(size_t dom,size_t comp,const vector_fp & pos,const vector_fp & values)77 void Sim1D::setProfile(size_t dom, size_t comp,
78 const vector_fp& pos, const vector_fp& values)
79 {
80 if (pos.front() != 0.0 || pos.back() != 1.0) {
81 throw CanteraError("Sim1D::setProfile",
82 "`pos` vector must span the range [0, 1]. Got a vector spanning "
83 "[{}, {}] instead.", pos.front(), pos.back());
84 }
85 Domain1D& d = domain(dom);
86 doublereal z0 = d.zmin();
87 doublereal z1 = d.zmax();
88 for (size_t n = 0; n < d.nPoints(); n++) {
89 double zpt = d.z(n);
90 double frac = (zpt - z0)/(z1 - z0);
91 double v = linearInterp(frac, pos, values);
92 setValue(dom, comp, n, v);
93 }
94 }
95
save(const std::string & fname,const std::string & id,const std::string & desc,int loglevel)96 void Sim1D::save(const std::string& fname, const std::string& id,
97 const std::string& desc, int loglevel)
98 {
99 OneDim::save(fname, id, desc, m_x.data(), loglevel);
100 }
101
saveResidual(const std::string & fname,const std::string & id,const std::string & desc,int loglevel)102 void Sim1D::saveResidual(const std::string& fname, const std::string& id,
103 const std::string& desc, int loglevel)
104 {
105 vector_fp res(m_x.size(), -999);
106 OneDim::eval(npos, &m_x[0], &res[0], 0.0);
107 OneDim::save(fname, id, desc, &res[0], loglevel);
108 }
109
restore(const std::string & fname,const std::string & id,int loglevel)110 void Sim1D::restore(const std::string& fname, const std::string& id,
111 int loglevel)
112 {
113 XML_Node root;
114 root.build(fname);
115
116 XML_Node* f = root.findID(id);
117 if (!f) {
118 throw CanteraError("Sim1D::restore","No solution with id = "+id);
119 }
120
121 vector<XML_Node*> xd = f->getChildren("domain");
122 if (xd.size() != nDomains()) {
123 throw CanteraError("Sim1D::restore", "Solution does not contain the "
124 " correct number of domains. Found {} expected {}.\n",
125 xd.size(), nDomains());
126 }
127 for (size_t m = 0; m < nDomains(); m++) {
128 Domain1D& dom = domain(m);
129 if (loglevel > 0 && xd[m]->attrib("id") != dom.id()) {
130 warn_user("Sim1D::restore", "Domain names do not match: "
131 "'{} and '{}'", (*xd[m])["id"], dom.id());
132 }
133 dom.resize(domain(m).nComponents(), intValue((*xd[m])["points"]));
134 }
135 resize();
136 m_xlast_ts.clear();
137 for (size_t m = 0; m < nDomains(); m++) {
138 domain(m).restore(*xd[m], &m_x[start(m)], loglevel);
139 }
140 finalize();
141 }
142
setFlatProfile(size_t dom,size_t comp,doublereal v)143 void Sim1D::setFlatProfile(size_t dom, size_t comp, doublereal v)
144 {
145 size_t np = domain(dom).nPoints();
146 for (size_t n = 0; n < np; n++) {
147 setValue(dom, comp, n, v);
148 }
149 }
150
showSolution(ostream & s)151 void Sim1D::showSolution(ostream& s)
152 {
153 for (size_t n = 0; n < nDomains(); n++) {
154 if (domain(n).domainType() != cEmptyType) {
155 domain(n).showSolution_s(s, &m_x[start(n)]);
156 }
157 }
158 }
159
showSolution()160 void Sim1D::showSolution()
161 {
162 for (size_t n = 0; n < nDomains(); n++) {
163 if (domain(n).domainType() != cEmptyType) {
164 writelog("\n\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> "+domain(n).id()
165 +" <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n\n");
166 domain(n).showSolution(&m_x[start(n)]);
167 }
168 }
169 }
170
restoreTimeSteppingSolution()171 void Sim1D::restoreTimeSteppingSolution()
172 {
173 if (m_xlast_ts.empty()) {
174 throw CanteraError("Sim1D::restoreTimeSteppingSolution",
175 "No successful time steps taken on this grid.");
176 }
177 m_x = m_xlast_ts;
178 }
179
restoreSteadySolution()180 void Sim1D::restoreSteadySolution()
181 {
182 if (m_xlast_ss.empty()) {
183 throw CanteraError("Sim1D::restoreSteadySolution",
184 "No successful steady state solution");
185 }
186 m_x = m_xlast_ss;
187 for (size_t n = 0; n < nDomains(); n++) {
188 vector_fp& z = m_grid_last_ss[n];
189 domain(n).setupGrid(z.size(), z.data());
190 }
191 }
192
getInitialSoln()193 void Sim1D::getInitialSoln()
194 {
195 for (size_t n = 0; n < nDomains(); n++) {
196 domain(n)._getInitialSoln(&m_x[start(n)]);
197 }
198 }
199
finalize()200 void Sim1D::finalize()
201 {
202 for (size_t n = 0; n < nDomains(); n++) {
203 domain(n)._finalize(&m_x[start(n)]);
204 }
205 }
206
setTimeStep(double stepsize,size_t n,const int * tsteps)207 void Sim1D::setTimeStep(double stepsize, size_t n, const int* tsteps)
208 {
209 m_tstep = stepsize;
210 m_steps.resize(n);
211 for (size_t i = 0; i < n; i++) {
212 m_steps[i] = tsteps[i];
213 }
214 }
215
newtonSolve(int loglevel)216 int Sim1D::newtonSolve(int loglevel)
217 {
218 int m = OneDim::solve(m_x.data(), m_xnew.data(), loglevel);
219 if (m >= 0) {
220 m_x = m_xnew;
221 return 0;
222 } else if (m > -10) {
223 return -1;
224 } else {
225 throw CanteraError("Sim1D::newtonSolve",
226 "ERROR: OneDim::solve returned m = {}", m);
227 }
228 }
229
solve(int loglevel,bool refine_grid)230 void Sim1D::solve(int loglevel, bool refine_grid)
231 {
232 int new_points = 1;
233 doublereal dt = m_tstep;
234 m_nsteps = 0;
235 int soln_number = -1;
236 finalize();
237
238 while (new_points > 0) {
239 size_t istep = 0;
240 int nsteps = m_steps[istep];
241
242 bool ok = false;
243 if (loglevel > 0) {
244 writeline('.', 78, true, true);
245 }
246 while (!ok) {
247 // Attempt to solve the steady problem
248 setSteadyMode();
249 newton().setOptions(m_ss_jac_age);
250 debuglog("Attempt Newton solution of steady-state problem...", loglevel);
251 int status = newtonSolve(loglevel-1);
252
253 if (status == 0) {
254 if (loglevel > 0) {
255 writelog(" success.\n\n");
256 writelog("Problem solved on [");
257 for (size_t mm = 1; mm < nDomains(); mm+=2) {
258 writelog("{}", domain(mm).nPoints());
259 if (mm + 2 < nDomains()) {
260 writelog(", ");
261 }
262 }
263 writelog("] point grid(s).\n");
264 }
265 if (m_steady_callback) {
266 m_steady_callback->eval(0);
267 }
268
269 if (loglevel > 6) {
270 save("debug_sim1d.xml", "debug",
271 "After successful Newton solve");
272 }
273 if (loglevel > 7) {
274 saveResidual("debug_sim1d.xml", "residual",
275 "After successful Newton solve");
276 }
277 ok = true;
278 soln_number++;
279 } else {
280 debuglog(" failure. \n", loglevel);
281 if (loglevel > 6) {
282 save("debug_sim1d.xml", "debug",
283 "After unsuccessful Newton solve");
284 }
285 if (loglevel > 7) {
286 saveResidual("debug_sim1d.xml", "residual",
287 "After unsuccessful Newton solve");
288 }
289 if (loglevel > 0) {
290 writelog("Take {} timesteps ", nsteps);
291 }
292 dt = timeStep(nsteps, dt, m_x.data(), m_xnew.data(),
293 loglevel-1);
294 m_xlast_ts = m_x;
295 if (loglevel > 6) {
296 save("debug_sim1d.xml", "debug", "After timestepping");
297 }
298 if (loglevel > 7) {
299 saveResidual("debug_sim1d.xml", "residual",
300 "After timestepping");
301 }
302
303 if (loglevel == 1) {
304 writelog(" {:10.4g} {:10.4g}\n", dt,
305 log10(ssnorm(m_x.data(), m_xnew.data())));
306 }
307 istep++;
308 if (istep >= m_steps.size()) {
309 nsteps = m_steps.back();
310 } else {
311 nsteps = m_steps[istep];
312 }
313 dt = std::min(dt, m_tmax);
314 }
315 }
316 if (loglevel > 0) {
317 writeline('.', 78, true, true);
318 }
319 if (loglevel > 2) {
320 showSolution();
321 }
322
323 if (refine_grid) {
324 new_points = refine(loglevel);
325 if (new_points) {
326 // If the grid has changed, preemptively reduce the timestep
327 // to avoid multiple successive failed time steps.
328 dt = m_tstep;
329 }
330 if (new_points && loglevel > 6) {
331 save("debug_sim1d.xml", "debug", "After regridding");
332 }
333 if (new_points && loglevel > 7) {
334 saveResidual("debug_sim1d.xml", "residual",
335 "After regridding");
336 }
337 } else {
338 debuglog("grid refinement disabled.\n", loglevel);
339 new_points = 0;
340 }
341 }
342 }
343
refine(int loglevel)344 int Sim1D::refine(int loglevel)
345 {
346 int ianalyze, np = 0;
347 vector_fp znew, xnew;
348 std::vector<size_t> dsize;
349
350 m_xlast_ss = m_x;
351 m_grid_last_ss.clear();
352
353 for (size_t n = 0; n < nDomains(); n++) {
354 Domain1D& d = domain(n);
355 Refiner& r = d.refiner();
356
357 // Save the old grid corresponding to the converged solution
358 m_grid_last_ss.push_back(d.grid());
359
360 // determine where new points are needed
361 ianalyze = r.analyze(d.grid().size(), d.grid().data(), &m_x[start(n)]);
362 if (ianalyze < 0) {
363 return ianalyze;
364 }
365
366 if (loglevel > 0) {
367 r.show();
368 }
369
370 np += r.nNewPoints();
371 size_t comp = d.nComponents();
372
373 // loop over points in the current grid
374 size_t npnow = d.nPoints();
375 size_t nstart = znew.size();
376 for (size_t m = 0; m < npnow; m++) {
377 if (r.keepPoint(m)) {
378 // add the current grid point to the new grid
379 znew.push_back(d.grid(m));
380
381 // do the same for the solution at this point
382 for (size_t i = 0; i < comp; i++) {
383 xnew.push_back(value(n, i, m));
384 }
385
386 // now check whether a new point is needed in the interval to
387 // the right of point m, and if so, add entries to znew and xnew
388 // for this new point
389 if (r.newPointNeeded(m) && m + 1 < npnow) {
390 // add new point at midpoint
391 double zmid = 0.5*(d.grid(m) + d.grid(m+1));
392 znew.push_back(zmid);
393 np++;
394
395 // for each component, linearly interpolate
396 // the solution to this point
397 for (size_t i = 0; i < comp; i++) {
398 double xmid = 0.5*(value(n, i, m) + value(n, i, m+1));
399 xnew.push_back(xmid);
400 }
401 }
402 } else {
403 if (loglevel > 0) {
404 writelog("refine: discarding point at {}\n", d.grid(m));
405 }
406 }
407 }
408 dsize.push_back(znew.size() - nstart);
409 }
410
411 // At this point, the new grid znew and the new solution vector xnew have
412 // been constructed, but the domains themselves have not yet been modified.
413 // Now update each domain with the new grid.
414
415 size_t gridstart = 0, gridsize;
416 for (size_t n = 0; n < nDomains(); n++) {
417 Domain1D& d = domain(n);
418 gridsize = dsize[n];
419 d.setupGrid(gridsize, &znew[gridstart]);
420 gridstart += gridsize;
421 }
422
423 // Replace the current solution vector with the new one
424 m_x = xnew;
425 resize();
426 finalize();
427 return np;
428 }
429
setFixedTemperature(double t)430 int Sim1D::setFixedTemperature(double t)
431 {
432 int np = 0;
433 vector_fp znew, xnew;
434 double zfixed = 0.0;
435 double z1 = 0.0, z2 = 0.0;
436 std::vector<size_t> dsize;
437
438 for (size_t n = 0; n < nDomains(); n++) {
439 Domain1D& d = domain(n);
440 size_t comp = d.nComponents();
441 size_t mfixed = npos;
442
443 // loop over current grid to determine where new point is needed
444 StFlow* d_free = dynamic_cast<StFlow*>(&domain(n));
445 size_t npnow = d.nPoints();
446 size_t nstart = znew.size();
447 if (d_free && d_free->domainType() == cFreeFlow) {
448 for (size_t m = 0; m < npnow - 1; m++) {
449 bool fixedpt = false;
450 double t1 = value(n, 2, m);
451 double t2 = value(n, 2, m + 1);
452 // threshold to avoid adding new point too close to existing point
453 double thresh = min(1., 1.e-1 * (t2 - t1));
454 z1 = d.grid(m);
455 z2 = d.grid(m + 1);
456 if (fabs(t - t1) <= thresh) {
457 zfixed = z1;
458 fixedpt = true;
459 } else if (fabs(t2 - t) <= thresh) {
460 zfixed = z2;
461 fixedpt = true;
462 } else if ((t1 < t) && (t < t2)) {
463 mfixed = m;
464 zfixed = (z1 - z2) / (t1 - t2) * (t - t2) + z2;
465 fixedpt = true;
466 }
467
468 if (fixedpt) {
469 d_free->m_zfixed = zfixed;
470 d_free->m_tfixed = t;
471 break;
472 }
473 }
474 }
475
476 // copy solution domain and push back values
477 for (size_t m = 0; m < npnow; m++) {
478 // add the current grid point to the new grid
479 znew.push_back(d.grid(m));
480
481 // do the same for the solution at this point
482 for (size_t i = 0; i < comp; i++) {
483 xnew.push_back(value(n, i, m));
484 }
485 if (m == mfixed) {
486 // add new point at zfixed (mfixed is not npos)
487 znew.push_back(zfixed);
488 np++;
489 double interp_factor = (zfixed - z2) / (z1 - z2);
490 // for each component, linearly interpolate
491 // the solution to this point
492 for (size_t i = 0; i < comp; i++) {
493 double xmid = interp_factor*(value(n, i, m) - value(n, i, m+1)) + value(n,i,m+1);
494 xnew.push_back(xmid);
495 }
496 }
497 }
498 dsize.push_back(znew.size() - nstart);
499 }
500
501 // At this point, the new grid znew and the new solution vector xnew have
502 // been constructed, but the domains themselves have not yet been modified.
503 // Now update each domain with the new grid.
504 size_t gridstart = 0;
505 for (size_t n = 0; n < nDomains(); n++) {
506 Domain1D& d = domain(n);
507 size_t gridsize = dsize[n];
508 d.setupGrid(gridsize, &znew[gridstart]);
509 gridstart += gridsize;
510 }
511
512 // Replace the current solution vector with the new one
513 m_x = xnew;
514
515 resize();
516 finalize();
517 return np;
518 }
519
fixedTemperature()520 double Sim1D::fixedTemperature()
521 {
522 double t_fixed = std::numeric_limits<double>::quiet_NaN();
523 for (size_t n = 0; n < nDomains(); n++) {
524 StFlow* d = dynamic_cast<StFlow*>(&domain(n));
525 if (d && d->domainType() == cFreeFlow && d->m_tfixed > 0) {
526 t_fixed = d->m_tfixed;
527 break;
528 }
529 }
530 return t_fixed;
531 }
532
fixedTemperatureLocation()533 double Sim1D::fixedTemperatureLocation()
534 {
535 double z_fixed = std::numeric_limits<double>::quiet_NaN();
536 for (size_t n = 0; n < nDomains(); n++) {
537 StFlow* d = dynamic_cast<StFlow*>(&domain(n));
538 if (d && d->domainType() == cFreeFlow && d->m_tfixed > 0) {
539 z_fixed = d->m_zfixed;
540 break;
541 }
542 }
543 return z_fixed;
544 }
545
setRefineCriteria(int dom,double ratio,double slope,double curve,double prune)546 void Sim1D::setRefineCriteria(int dom, double ratio,
547 double slope, double curve, double prune)
548 {
549 if (dom >= 0) {
550 Refiner& r = domain(dom).refiner();
551 r.setCriteria(ratio, slope, curve, prune);
552 } else {
553 for (size_t n = 0; n < nDomains(); n++) {
554 Refiner& r = domain(n).refiner();
555 r.setCriteria(ratio, slope, curve, prune);
556 }
557 }
558 }
559
getRefineCriteria(int dom)560 vector_fp Sim1D::getRefineCriteria(int dom)
561 {
562 if (dom >= 0) {
563 Refiner& r = domain(dom).refiner();
564 return r.getCriteria();
565 } else {
566 throw CanteraError("Sim1D::getRefineCriteria",
567 "Must specify domain to get criteria from");
568 }
569 }
570
setGridMin(int dom,double gridmin)571 void Sim1D::setGridMin(int dom, double gridmin)
572 {
573 if (dom >= 0) {
574 Refiner& r = domain(dom).refiner();
575 r.setGridMin(gridmin);
576 } else {
577 for (size_t n = 0; n < nDomains(); n++) {
578 Refiner& r = domain(n).refiner();
579 r.setGridMin(gridmin);
580 }
581 }
582 }
583
setMaxGridPoints(int dom,int npoints)584 void Sim1D::setMaxGridPoints(int dom, int npoints)
585 {
586 if (dom >= 0) {
587 Refiner& r = domain(dom).refiner();
588 r.setMaxPoints(npoints);
589 } else {
590 for (size_t n = 0; n < nDomains(); n++) {
591 Refiner& r = domain(n).refiner();
592 r.setMaxPoints(npoints);
593 }
594 }
595 }
596
maxGridPoints(size_t dom)597 size_t Sim1D::maxGridPoints(size_t dom)
598 {
599 Refiner& r = domain(dom).refiner();
600 return r.maxPoints();
601 }
602
jacobian(int i,int j)603 doublereal Sim1D::jacobian(int i, int j)
604 {
605 return OneDim::jacobian().value(i,j);
606 }
607
evalSSJacobian()608 void Sim1D::evalSSJacobian()
609 {
610 OneDim::evalSSJacobian(m_x.data(), m_xnew.data());
611 }
612
solveAdjoint(const double * b,double * lambda)613 void Sim1D::solveAdjoint(const double* b, double* lambda)
614 {
615 for (auto& D : m_dom) {
616 D->forceFullUpdate(true);
617 }
618 evalSSJacobian();
619 for (auto& D : m_dom) {
620 D->forceFullUpdate(false);
621 }
622
623 // Form J^T
624 size_t bw = bandwidth();
625 BandMatrix Jt(size(), bw, bw);
626 for (size_t i = 0; i < size(); i++) {
627 size_t j1 = (i > bw) ? i - bw : 0;
628 size_t j2 = (i + bw >= size()) ? size() - 1: i + bw;
629 for (size_t j = j1; j <= j2; j++) {
630 Jt(j,i) = m_jac->value(i,j);
631 }
632 }
633
634 Jt.solve(b, lambda);
635 }
636
resize()637 void Sim1D::resize()
638 {
639 OneDim::resize();
640 m_x.resize(size(), 0.0);
641 m_xnew.resize(size(), 0.0);
642 }
643
644 }
645