These values were selected for optimal performance across a large set of flames in each configuration and using mechanisms of various sizes to give good average performance. It should not generally be necessary for users to change these values.
443 lines
11 KiB
C++
443 lines
11 KiB
C++
//! @file OneDim.cpp
|
|
#include "cantera/oneD/OneDim.h"
|
|
#include "cantera/numerics/Func1.h"
|
|
#include "cantera/base/ctml.h"
|
|
#include "cantera/oneD/MultiNewton.h"
|
|
|
|
#include <fstream>
|
|
#include <ctime>
|
|
|
|
using namespace std;
|
|
|
|
namespace Cantera
|
|
{
|
|
|
|
OneDim::OneDim()
|
|
: m_tmin(1.0e-16), m_tmax(1e8), m_tfactor(0.5),
|
|
m_rdt(0.0), m_jac_ok(false),
|
|
m_bw(0), m_size(0),
|
|
m_init(false), m_pts(0), m_solve_time(0.0),
|
|
m_ss_jac_age(20), m_ts_jac_age(20),
|
|
m_interrupt(0), m_nevals(0), m_evaltime(0.0), m_nsteps(0),
|
|
m_nsteps_max(500)
|
|
{
|
|
m_newt.reset(new MultiNewton(1));
|
|
}
|
|
|
|
OneDim::OneDim(vector<Domain1D*> domains) :
|
|
m_tmin(1.0e-16), m_tmax(1e8), m_tfactor(0.5),
|
|
m_rdt(0.0), m_jac_ok(false),
|
|
m_bw(0), m_size(0),
|
|
m_init(false), m_solve_time(0.0),
|
|
m_ss_jac_age(20), m_ts_jac_age(20),
|
|
m_interrupt(0), m_nevals(0), m_evaltime(0.0), m_nsteps(0),
|
|
m_nsteps_max(500)
|
|
{
|
|
// create a Newton iterator, and add each domain.
|
|
m_newt.reset(new MultiNewton(1));
|
|
for (size_t i = 0; i < domains.size(); i++) {
|
|
addDomain(domains[i]);
|
|
}
|
|
init();
|
|
resize();
|
|
}
|
|
|
|
OneDim::~OneDim()
|
|
{
|
|
}
|
|
|
|
size_t OneDim::domainIndex(const std::string& name)
|
|
{
|
|
for (size_t n = 0; n < m_dom.size(); n++) {
|
|
if (domain(n).id() == name) {
|
|
return n;
|
|
}
|
|
}
|
|
throw CanteraError("OneDim::domainIndex","no domain named >>"+name+"<<");
|
|
}
|
|
|
|
std::tuple<std::string, size_t, std::string> OneDim::component(size_t i) {
|
|
size_t n;
|
|
for (n = nDomains()-1; n != npos; n--) {
|
|
if (i >= start(n)) {
|
|
break;
|
|
}
|
|
}
|
|
Domain1D& dom = domain(n);
|
|
size_t offset = i - start(n);
|
|
size_t pt = offset / dom.nComponents();
|
|
size_t comp = offset - pt*dom.nComponents();
|
|
return make_tuple(dom.id(), pt, dom.componentName(comp));
|
|
}
|
|
|
|
void OneDim::addDomain(Domain1D* d)
|
|
{
|
|
// if 'd' is not the first domain, link it to the last domain
|
|
// added (the rightmost one)
|
|
size_t n = m_dom.size();
|
|
if (n > 0) {
|
|
m_dom.back()->append(d);
|
|
}
|
|
|
|
// every other domain is a connector
|
|
if (n % 2 == 0) {
|
|
m_connect.push_back(d);
|
|
} else {
|
|
m_bulk.push_back(d);
|
|
}
|
|
|
|
// add it also to the global domain list, and set its container and position
|
|
m_dom.push_back(d);
|
|
d->setContainer(this, m_dom.size()-1);
|
|
resize();
|
|
}
|
|
|
|
MultiJac& OneDim::jacobian()
|
|
{
|
|
return *m_jac;
|
|
}
|
|
MultiNewton& OneDim::newton()
|
|
{
|
|
return *m_newt;
|
|
}
|
|
|
|
void OneDim::writeStats(int printTime)
|
|
{
|
|
saveStats();
|
|
writelog("\nStatistics:\n\n Grid Timesteps Functions Time Jacobians Time\n");
|
|
size_t n = m_gridpts.size();
|
|
for (size_t i = 0; i < n; i++) {
|
|
if (printTime) {
|
|
writelog("{:5d} {:5d} {:6d} {:9.4f} {:5d} {:9.4f}\n",
|
|
m_gridpts[i], m_timeSteps[i], m_funcEvals[i], m_funcElapsed[i],
|
|
m_jacEvals[i], m_jacElapsed[i]);
|
|
} else {
|
|
writelog("{:5d} {:5d} {:6d} NA {:5d} NA\n",
|
|
m_gridpts[i], m_timeSteps[i], m_funcEvals[i], m_jacEvals[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void OneDim::saveStats()
|
|
{
|
|
if (m_jac) {
|
|
int nev = m_jac->nEvals();
|
|
if (nev > 0 && m_nevals > 0) {
|
|
m_gridpts.push_back(m_pts);
|
|
m_jacEvals.push_back(m_jac->nEvals());
|
|
m_jacElapsed.push_back(m_jac->elapsedTime());
|
|
m_funcEvals.push_back(m_nevals);
|
|
m_nevals = 0;
|
|
m_funcElapsed.push_back(m_evaltime);
|
|
m_evaltime = 0.0;
|
|
m_timeSteps.push_back(m_nsteps);
|
|
m_nsteps = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
void OneDim::clearStats()
|
|
{
|
|
m_gridpts.clear();
|
|
m_jacEvals.clear();
|
|
m_jacElapsed.clear();
|
|
m_funcEvals.clear();
|
|
m_funcElapsed.clear();
|
|
m_timeSteps.clear();
|
|
m_nevals = 0;
|
|
m_evaltime = 0.0;
|
|
m_nsteps = 0;
|
|
}
|
|
|
|
void OneDim::resize()
|
|
{
|
|
m_bw = 0;
|
|
m_nvars.clear();
|
|
m_loc.clear();
|
|
size_t lc = 0;
|
|
|
|
// save the statistics for the last grid
|
|
saveStats();
|
|
m_pts = 0;
|
|
for (size_t i = 0; i < nDomains(); i++) {
|
|
Domain1D* d = m_dom[i];
|
|
|
|
size_t np = d->nPoints();
|
|
size_t nv = d->nComponents();
|
|
for (size_t n = 0; n < np; n++) {
|
|
m_nvars.push_back(nv);
|
|
m_loc.push_back(lc);
|
|
lc += nv;
|
|
m_pts++;
|
|
}
|
|
|
|
// update the Jacobian bandwidth
|
|
|
|
// bandwidth of the local block
|
|
size_t bw1 = d->bandwidth();
|
|
if (bw1 == npos) {
|
|
bw1 = 2*d->nComponents() - 1;
|
|
}
|
|
m_bw = std::max(m_bw, bw1);
|
|
|
|
// bandwidth of the block coupling the first point of this
|
|
// domain to the last point of the previous domain
|
|
if (i > 0) {
|
|
size_t bw2 = m_dom[i-1]->bandwidth();
|
|
if (bw2 == npos) {
|
|
bw2 = m_dom[i-1]->nComponents();
|
|
}
|
|
bw2 += d->nComponents() - 1;
|
|
m_bw = std::max(m_bw, bw2);
|
|
}
|
|
m_size = d->loc() + d->size();
|
|
}
|
|
|
|
m_newt->resize(size());
|
|
m_mask.resize(size());
|
|
|
|
// delete the current Jacobian evaluator and create a new one
|
|
m_jac.reset(new MultiJac(*this));
|
|
m_jac_ok = false;
|
|
|
|
for (size_t i = 0; i < nDomains(); i++) {
|
|
m_dom[i]->setJac(m_jac.get());
|
|
}
|
|
}
|
|
|
|
int OneDim::solve(doublereal* x, doublereal* xnew, int loglevel)
|
|
{
|
|
if (!m_jac_ok) {
|
|
eval(npos, x, xnew, 0.0, 0);
|
|
m_jac->eval(x, xnew, 0.0);
|
|
m_jac->updateTransient(m_rdt, m_mask.data());
|
|
m_jac_ok = true;
|
|
}
|
|
return m_newt->solve(x, xnew, *this, *m_jac, loglevel);
|
|
}
|
|
|
|
void OneDim::evalSSJacobian(doublereal* x, doublereal* xnew)
|
|
{
|
|
doublereal rdt_save = m_rdt;
|
|
m_jac_ok = false;
|
|
setSteadyMode();
|
|
eval(npos, x, xnew, 0.0, 0);
|
|
m_jac->eval(x, xnew, 0.0);
|
|
m_rdt = rdt_save;
|
|
}
|
|
|
|
Domain1D* OneDim::pointDomain(size_t i)
|
|
{
|
|
Domain1D* d = right();
|
|
while (d) {
|
|
if (d->loc() <= i) {
|
|
return d;
|
|
}
|
|
d = d->left();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void OneDim::eval(size_t j, double* x, double* r, doublereal rdt, int count)
|
|
{
|
|
clock_t t0 = clock();
|
|
if (m_interrupt) {
|
|
m_interrupt->eval(m_nevals);
|
|
}
|
|
fill(r, r + m_size, 0.0);
|
|
if (j == npos) {
|
|
fill(m_mask.begin(), m_mask.end(), 0);
|
|
}
|
|
if (rdt < 0.0) {
|
|
rdt = m_rdt;
|
|
}
|
|
|
|
// iterate over the bulk domains first
|
|
for (const auto& d : m_bulk) {
|
|
d->eval(j, x, r, m_mask.data(), rdt);
|
|
}
|
|
|
|
// then over the connector domains
|
|
for (const auto& d : m_connect) {
|
|
d->eval(j, x, r, m_mask.data(), rdt);
|
|
}
|
|
|
|
// increment counter and time
|
|
if (count) {
|
|
clock_t t1 = clock();
|
|
m_evaltime += double(t1 - t0)/CLOCKS_PER_SEC;
|
|
m_nevals++;
|
|
}
|
|
}
|
|
|
|
doublereal OneDim::ssnorm(doublereal* x, doublereal* r)
|
|
{
|
|
eval(npos, x, r, 0.0, 0);
|
|
doublereal ss = 0.0;
|
|
for (size_t i = 0; i < m_size; i++) {
|
|
ss = std::max(fabs(r[i]),ss);
|
|
}
|
|
return ss;
|
|
}
|
|
|
|
void OneDim::initTimeInteg(doublereal dt, doublereal* x)
|
|
{
|
|
doublereal rdt_old = m_rdt;
|
|
m_rdt = 1.0/dt;
|
|
|
|
// if the stepsize has changed, then update the transient part of the
|
|
// Jacobian
|
|
if (fabs(rdt_old - m_rdt) > Tiny) {
|
|
m_jac->updateTransient(m_rdt, m_mask.data());
|
|
}
|
|
|
|
// iterate over all domains, preparing each one to begin time stepping
|
|
Domain1D* d = left();
|
|
while (d) {
|
|
d->initTimeInteg(dt, x);
|
|
d = d->right();
|
|
}
|
|
}
|
|
|
|
void OneDim::setSteadyMode()
|
|
{
|
|
if (m_rdt == 0) {
|
|
return;
|
|
}
|
|
|
|
m_rdt = 0.0;
|
|
m_jac->updateTransient(m_rdt, m_mask.data());
|
|
|
|
// iterate over all domains, preparing them for steady-state solution
|
|
Domain1D* d = left();
|
|
while (d) {
|
|
d->setSteadyMode();
|
|
d = d->right();
|
|
}
|
|
}
|
|
|
|
void OneDim::init()
|
|
{
|
|
if (!m_init) {
|
|
Domain1D* d = left();
|
|
while (d) {
|
|
d->init();
|
|
d = d->right();
|
|
}
|
|
}
|
|
m_init = true;
|
|
}
|
|
|
|
doublereal OneDim::timeStep(int nsteps, doublereal dt, doublereal* x,
|
|
doublereal* r, int loglevel)
|
|
{
|
|
// set the Jacobian age parameter to the transient value
|
|
newton().setOptions(m_ts_jac_age);
|
|
|
|
debuglog("\n\n step size (s) log10(ss) \n", loglevel);
|
|
debuglog("===============================\n", loglevel);
|
|
|
|
int n = 0;
|
|
int successiveFailures = 0;
|
|
|
|
while (n < nsteps) {
|
|
if (loglevel > 0) {
|
|
doublereal ss = ssnorm(x, r);
|
|
writelog(" {:>4d} {:10.4g} {:10.4g}", n, dt, log10(ss));
|
|
}
|
|
|
|
// set up for time stepping with stepsize dt
|
|
initTimeInteg(dt,x);
|
|
|
|
// solve the transient problem
|
|
int m = solve(x, r, loglevel-1);
|
|
|
|
// successful time step. Copy the new solution in r to
|
|
// the current solution in x.
|
|
if (m >= 0) {
|
|
successiveFailures = 0;
|
|
m_nsteps++;
|
|
n += 1;
|
|
debuglog("\n", loglevel);
|
|
copy(r, r + m_size, x);
|
|
if (m == 100) {
|
|
dt *= 1.5;
|
|
}
|
|
dt = std::min(dt, m_tmax);
|
|
if (m_nsteps == m_nsteps_max) {
|
|
throw CanteraError("OneDim::timeStep",
|
|
"Took maximum number of timesteps allowed ({}) without "
|
|
"reaching steady-state solution.", m_nsteps_max);
|
|
}
|
|
} else {
|
|
successiveFailures++;
|
|
// No solution could be found with this time step.
|
|
// Decrease the stepsize and try again.
|
|
debuglog("...failure.\n", loglevel);
|
|
if (successiveFailures > 2) {
|
|
//debuglog("Resetting negative species concentrations.\n", loglevel);
|
|
resetBadValues(x);
|
|
successiveFailures = 0;
|
|
} else {
|
|
dt *= m_tfactor;
|
|
if (dt < m_tmin) {
|
|
throw CanteraError("OneDim::timeStep",
|
|
"Time integration failed.");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// return the value of the last stepsize, which may be smaller
|
|
// than the initial stepsize
|
|
return dt;
|
|
}
|
|
|
|
void OneDim::resetBadValues(double* x)
|
|
{
|
|
for (auto dom : m_dom) {
|
|
dom->resetBadValues(x);
|
|
}
|
|
}
|
|
|
|
void OneDim::save(const std::string& fname, std::string id,
|
|
const std::string& desc, doublereal* sol,
|
|
int loglevel)
|
|
{
|
|
time_t aclock;
|
|
::time(&aclock); // Get time in seconds
|
|
struct tm* newtime = localtime(&aclock); // Convert time to struct tm form
|
|
|
|
XML_Node root("ctml");
|
|
ifstream fin(fname);
|
|
if (fin) {
|
|
root.build(fin);
|
|
// Remove existing solution with the same id
|
|
XML_Node* same_ID = root.findID(id);
|
|
if (same_ID) {
|
|
same_ID->parent()->removeChild(same_ID);
|
|
}
|
|
fin.close();
|
|
}
|
|
XML_Node& sim = root.addChild("simulation");
|
|
sim.addAttribute("id",id);
|
|
addString(sim,"timestamp",asctime(newtime));
|
|
if (desc != "") {
|
|
addString(sim,"description",desc);
|
|
}
|
|
|
|
Domain1D* d = left();
|
|
while (d) {
|
|
d->save(sim, sol);
|
|
d = d->right();
|
|
}
|
|
ofstream s(fname);
|
|
if (!s) {
|
|
throw CanteraError("OneDim::save","could not open file "+fname);
|
|
}
|
|
root.write(s);
|
|
s.close();
|
|
debuglog("Solution saved to file "+fname+" as solution "+id+".\n", loglevel);
|
|
}
|
|
|
|
}
|