/** * * @file MultiNewton.cpp * * Damped Newton solver for 1D multi-domain problems */ /* * $Author$ * $Date$ * $Revision$ * * Copyright 2001 California Institute of Technology * */ #ifdef WIN32 #pragma warning(disable:4786) #pragma warning(disable:4503) #endif #include using namespace std; #include "MultiNewton.h" #include #include #include "../ctexceptions.h" #include "../vec_functions.h" #include "../stringUtils.h" #include namespace Cantera { //---------------------------------------------------------- // function declarations //---------------------------------------------------------- // declarations for functions in newton_utils.h doublereal bound_step(const doublereal* x, const doublereal* step, Resid1D& r, int loglevel=0); doublereal norm_square(const doublereal* x, const doublereal* step, Resid1D& r); //----------------------------------------------------------- // constants //----------------------------------------------------------- const string dashedline = "-----------------------------------------------------------------"; const doublereal DampFactor = sqrt(2.0); const int NDAMP = 7; //----------------------------------------------------------- // MultiNewton methods //----------------------------------------------------------- MultiNewton::MultiNewton(int sz) : m_maxAge(5) { m_n = sz; m_elapsed = 0.0; } MultiNewton::~MultiNewton() { int n = m_workarrays.size(); int i; for (i = 0; i < n; i++) { delete m_workarrays[i]; } } /** * Prepare for a new solution vector length. */ void MultiNewton::resize(int sz) { m_n = sz; int n = m_workarrays.size(); int i; for (i = 0; i < n; i++) { delete m_workarrays[i]; } m_workarrays.clear(); } /** * Compute the weighted 2-norm of 'step'. */ doublereal MultiNewton::norm2(const doublereal* x, const doublereal* step, OneDim& r) const { doublereal f, sum = 0.0;//, fmx = 0.0; int n; int nd = r.nDomains(); for (n = 0; n < nd; n++) { f = norm_square(x + r.start(n), step + r.start(n), r.domain(n)); sum += f; // if (f > fmx) fmx = f; } sum /= r.size(); return sqrt(sum); } /** * Compute the undamped Newton step. The residual function is * evaluated at x, but the Jacobian is not recomputed. */ void MultiNewton::step(doublereal* x, doublereal* step, OneDim& r, MultiJac& jac, int loglevel) { int n; int sz = r.size(); r.eval(-1, x, step); for (n = 0; n < sz; n++) { step[n] = -step[n]; } #ifdef DEBUG_STEP Resid1D* d; if (!ok) { for (n = 0; n < sz; n++) { d = r.pointDomain(n); int nvd = d->nComponents(); int pt = (n - d->loc())/nvd; cout << pt << " " << r.pointDomain(n)->componentName(n - d->loc() - nvd*pt) << " " << x[n] << " " << step[n] << endl; } if (!ok) throw "not ok"; } #endif jac.solve(sz, step, step); } /** * Return the factor by which the undamped Newton step 'step0' * must be multiplied in order to keep all solution components in * all domains between their specified lower and upper bounds. */ doublereal MultiNewton::boundStep(const doublereal* x0, const doublereal* step0, const OneDim& r, int loglevel) { int i; doublereal fbound = 1.0; int nd = r.nDomains(); for (i = 0; i < nd; i++) { fbound = fminn(fbound, bound_step(x0 + r.start(i), step0 + r.start(i), r.domain(i), loglevel)); } return fbound; } /** * On entry, step0 must contain an undamped Newton step for the * solution x0. This method attempts to find a damping coefficient * such that the next undamped step would have a norm smaller than * that of step0. If successful, the new solution after taking the * damped step is returned in x1, and the undamped step at x1 is * returned in step1. */ int MultiNewton::dampStep(const doublereal* x0, const doublereal* step0, doublereal* x1, doublereal* step1, doublereal& s1, OneDim& r, MultiJac& jac, int loglevel, bool writetitle) { // write header if (loglevel > 0 && writetitle) { writelog("\n\nDamped Newton iteration:\n"); writelog(dashedline); sprintf(m_buf,"\n%s %9s %9s %9s %9s %9s %5s\n", "m","F_damp","F_bound","log10(ss)", "log10(s0)","log10(s1)","N_jac"); writelog(m_buf); writelog(dashedline+"\n"); } // compute the weighted norm of the undamped step size step0 doublereal s0 = norm2(x0, step0, r); // compute the multiplier to keep all components in bounds doublereal fbound = boundStep(x0, step0, r, loglevel-1); // if fbound is very small, then x0 is already close to the // boundary and step0 points out of the allowed domain. In // this case, the Newton algorithm fails, so return an error // condition. if (fbound < 1.e-10) { if (loglevel > 0) writelog("\nAt limits.\n"); return -3; } //-------------------------------------------- // Attempt damped step //-------------------------------------------- // damping coefficient starts at 1.0 doublereal damp = 1.0; int j, m; doublereal ff; for (m = 0; m < NDAMP; m++) { ff = fbound*damp; // step the solution by the damped step size for (j = 0; j < m_n; j++) x1[j] = ff*step0[j] + x0[j]; // compute the next undamped step that would result if x1 // is accepted step(x1, step1, r, jac, loglevel-1); // compute the weighted norm of step1 s1 = norm2(x1, step1, r); // write log information if (loglevel > 0) { doublereal ss = r.ssnorm(x1,step1); sprintf(m_buf,"\n%d %9.5f %9.5f %9.5f %9.5f %9.5f %5d ", m,damp,fbound,log10(ss+SmallNumber), log10(s0+SmallNumber), log10(s1+SmallNumber), jac.nEvals()); writelog(m_buf); } // if the norm of s1 is less than the norm of s0, then // accept this damping coefficient. Also accept it if this // step would result in a converged solution. Otherwise, // decrease the damping coefficient and try again. if (s1 < 1.0 || s1 < s0) break; damp /= DampFactor; } // If a damping coefficient was found, return 1 if the // solution after stepping by the damped step would represent // a converged solution, and return 0 otherwise. If no damping // coefficient could be found, return -2. if (m < NDAMP) { if (s1 > 1.0) return 0; else return 1; } else { return -2; } } /** * Find the solution to F(X) = 0 by damped Newton iteration. On * entry, x0 contains an initial estimate of the solution. On * successful return, x1 contains the converged solution. */ int MultiNewton::solve(doublereal* x0, doublereal* x1, OneDim& r, MultiJac& jac, int loglevel) { clock_t t0 = clock(); int m = 0; bool forceNewJac = false; doublereal s1=1.e30; doublereal* x = getWorkArray(); doublereal* stp = getWorkArray(); doublereal* stp1 = getWorkArray(); copy(x0, x0 + m_n, x); bool frst = true; doublereal rdt = r.rdt(); int j0 = jac.nEvals(); while (1 > 0) { // Check whether the Jacobian should be re-evaluated. if (jac.age() > m_maxAge) { forceNewJac = true; } if (forceNewJac) { r.eval(-1, x, stp, 0.0, 0); jac.eval(x, stp, 0.0); jac.updateTransient(rdt, r.transientMask().begin()); forceNewJac = false; } // compute the undamped Newton step step(x, stp, r, jac, loglevel-1); // increment the Jacobian age jac.incrementAge(); // damp the Newton step m = dampStep(x, stp, x1, stp1, s1, r, jac, loglevel-1, frst); frst = false; if (loglevel == 1 && m >= 0) { doublereal ss = r.ssnorm(x, stp); sprintf(m_buf,"\n %10.4f %10.4f %d ", log10(ss),log10(s1),jac.nEvals()); writelog(m_buf); } // Successful step, but not converged yet. Take the damped // step, and try again. if (m == 0) { copy(x1, x1 + m_n, x); } // convergence else if (m == 1) goto done; // If dampStep fails, first try a new Jacobian if an old // one was being used. If it was a new Jacobian, then // return -1 to signify failure. else if (m < 0) { if (jac.age() > 1) forceNewJac = true; else goto done; } } done: if (m < 0) { copy(x, x + m_n, x1); } if (m > 0 && jac.nEvals() == j0) m = 100; releaseWorkArray(x); releaseWorkArray(stp); releaseWorkArray(stp1); m_elapsed += (clock() - t0)/(1.0*CLOCKS_PER_SEC); return m; } /** * Get a pointer to an array of length m_n for temporary work * space. */ doublereal* MultiNewton::getWorkArray() { doublereal* w = 0; if (!m_workarrays.empty()) { w = m_workarrays.back(); m_workarrays.pop_back(); } else { w = new doublereal[m_n]; } return w; } /** * Release a work array by pushing its pointer onto the stack of * available arrays. */ void MultiNewton::releaseWorkArray(doublereal* work) { m_workarrays.push_back(work); } } // $Log: Newton.cpp,v