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