/** * * @file NonlinearSolver.cpp * * Damped Newton solver for 0D and 1D problems */ /* * $Date$ * $Revision$ */ /* * Copywrite 2004 Sandia Corporation. Under the terms of Contract * DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government * retains certain rights in this software. * See file License.txt for licensing information. */ #include #include "cantera/numerics/SquareMatrix.h" #include "cantera/numerics/GeneralMatrix.h" #include "cantera/numerics/NonlinearSolver.h" #include "cantera/numerics/ctlapack.h" #include "cantera/base/clockWC.h" #include "cantera/base/vec_functions.h" #include "cantera/base/mdp_allo.h" #include #include #include #include #include //@{ #ifndef MAX #define MAX(x,y) (( (x) > (y) ) ? (x) : (y)) #define MIN(x,y) (( (x) < (y) ) ? (x) : (y)) #endif #ifndef CONSTD_DATA_PTR #define CONSTD_DATA_PTR(x) (( const doublereal *) (&x[0])) #endif //@} using namespace std; namespace Cantera { //==================================================================================================================== //----------------------------------------------------------- // Constants //----------------------------------------------------------- //! Dampfactor is the factor by which the damping factor is reduced by when a reduction in step length is warranted const doublereal DampFactor = 4.0; //! Number of damping steps that are carried out before the solution is deemed a failure const int NDAMP = 7; //==================================================================================================================== //! Print a line of a single repeated character string /*! * @param str Character string * @param n Iteration length */ static void print_line(const char* str, int n) { for (int i = 0; i < n; i++) { printf("%s", str); } printf("\n"); } bool NonlinearSolver::s_TurnOffTiming(false); #ifdef DEBUG_NUMJAC bool NonlinearSolver::s_print_NumJac(true); #else bool NonlinearSolver::s_print_NumJac(false); #endif // Turn off printing of dogleg information bool NonlinearSolver::s_print_DogLeg(false); // Turn off solving the system twice and comparing the answer. /* * Turn this on if you want to compare the Hessian and Newton solve results. */ bool NonlinearSolver::s_doBothSolvesAndCompare(false); // This toggle turns off the use of the Hessian when it is warranted by the condition number. /* * This is a debugging option. */ bool NonlinearSolver::s_alwaysAssumeNewtonGood(false); //==================================================================================================================== // Default constructor /* * @param func Residual and jacobian evaluator function object */ NonlinearSolver::NonlinearSolver(ResidJacEval* func) : m_func(func), solnType_(NSOLN_TYPE_STEADY_STATE), neq_(0), m_ewt(0), m_manualDeltaStepSet(0), m_deltaStepMinimum(0), m_y_n_curr(0), m_ydot_n_curr(0), m_y_nm1(0), m_y_n_1(0), m_ydot_n_1(0), m_colScales(0), m_rowScales(0), m_rowWtScales(0), m_resid(0), m_wksp(0), m_wksp_2(0), m_residWts(0), m_normResid_0(0.0), m_normResid_Bound(0.0), m_normResid_1(0.0), m_normDeltaSoln_Newton(0.0), m_normDeltaSoln_CP(0.0), m_normResidTrial(0.0), m_resid_scaled(false), m_y_high_bounds(0), m_y_low_bounds(0), m_dampBound(1.0), m_dampRes(1.0), delta_t_n(-1.0), m_nfe(0), m_colScaling(0), m_rowScaling(0), m_numTotalLinearSolves(0), m_numTotalNewtIts(0), m_min_newt_its(0), maxNewtIts_(100), m_jacFormMethod(NSOLN_JAC_NUM), m_nJacEval(0), time_n(0.0), m_matrixConditioning(0), m_order(1), rtol_(1.0E-3), atolBase_(1.0E-10), m_ydot_nm1(0), atolk_(0), userResidAtol_(0), userResidRtol_(1.0E-3), checkUserResidualTols_(0), m_print_flag(0), m_ScaleSolnNormToResNorm(0.001), jacCopyPtr_(0), HessianPtr_(0), deltaX_CP_(0), deltaX_Newton_(0), residNorm2Cauchy_(0.0), dogLegID_(0), dogLegAlpha_(1.0), RJd_norm_(0.0), lambdaStar_(0.0), Jd_(0), deltaX_trust_(0), norm_deltaX_trust_(0.0), trustDelta_(1.0), trustRegionInitializationMethod_(2), trustRegionInitializationFactor_(1.0), Nuu_(0.0), dist_R0_(0.0), dist_R1_(0.0), dist_R2_(0.0), dist_Total_(0.0), JdJd_norm_(0.0), normTrust_Newton_(0.0), normTrust_CP_(0.0), doDogLeg_(0), doAffineSolve_(0) , CurrentTrustFactor_(1.0), NextTrustFactor_(1.0), ResidWtsReevaluated_(false), ResidDecreaseSDExp_(0.0), ResidDecreaseSD_(0.0), ResidDecreaseNewtExp_(0.0), ResidDecreaseNewt_(0.0) { neq_ = m_func->nEquations(); m_ewt.resize(neq_, rtol_); m_deltaStepMinimum.resize(neq_, 0.001); m_deltaStepMaximum.resize(neq_, 1.0E10); m_y_n_curr.resize(neq_, 0.0); m_ydot_n_curr.resize(neq_, 0.0); m_y_nm1.resize(neq_, 0.0); m_y_n_1.resize(neq_, 0.0); m_ydot_n_1.resize(neq_, 0.0); m_colScales.resize(neq_, 1.0); m_rowScales.resize(neq_, 1.0); m_rowWtScales.resize(neq_, 1.0); m_resid.resize(neq_, 0.0); m_wksp.resize(neq_, 0.0); m_wksp_2.resize(neq_, 0.0); m_residWts.resize(neq_, 0.0); atolk_.resize(neq_, atolBase_); deltaX_Newton_.resize(neq_, 0.0); m_step_1.resize(neq_, 0.0); m_y_n_1.resize(neq_, 0.0); doublereal hb = std::numeric_limits::max(); m_y_high_bounds.resize(neq_, hb); m_y_low_bounds.resize(neq_, -hb); for (size_t i = 0; i < neq_; i++) { atolk_[i] = atolBase_; m_ewt[i] = atolk_[i]; } // jacCopyPtr_->resize(neq_, 0.0); deltaX_CP_.resize(neq_, 0.0); Jd_.resize(neq_, 0.0); deltaX_trust_.resize(neq_, 1.0); } //==================================================================================================================== NonlinearSolver::NonlinearSolver(const NonlinearSolver& right) : m_func(right.m_func), solnType_(NSOLN_TYPE_STEADY_STATE), neq_(0), m_ewt(0), m_manualDeltaStepSet(0), m_deltaStepMinimum(0), m_y_n_curr(0), m_ydot_n_curr(0), m_y_nm1(0), m_y_n_1(0), m_ydot_n_1(0), m_step_1(0), m_colScales(0), m_rowScales(0), m_rowWtScales(0), m_resid(0), m_wksp(0), m_wksp_2(0), m_residWts(0), m_normResid_0(0.0), m_normResid_Bound(0.0), m_normResid_1(0.0), m_normDeltaSoln_Newton(0.0), m_normDeltaSoln_CP(0.0), m_normResidTrial(0.0), m_resid_scaled(false), m_y_high_bounds(0), m_y_low_bounds(0), m_dampBound(1.0), m_dampRes(1.0), delta_t_n(-1.0), m_nfe(0), m_colScaling(0), m_rowScaling(0), m_numTotalLinearSolves(0), m_numTotalNewtIts(0), m_min_newt_its(0), maxNewtIts_(100), m_jacFormMethod(NSOLN_JAC_NUM), m_nJacEval(0), time_n(0.0), m_matrixConditioning(0), m_order(1), rtol_(1.0E-3), atolBase_(1.0E-10), m_ydot_nm1(0), atolk_(0), userResidAtol_(0), userResidRtol_(1.0E-3), checkUserResidualTols_(0), m_print_flag(0), m_ScaleSolnNormToResNorm(0.001), jacCopyPtr_(0), HessianPtr_(0), deltaX_CP_(0), deltaX_Newton_(0), residNorm2Cauchy_(0.0), dogLegID_(0), dogLegAlpha_(1.0), RJd_norm_(0.0), lambdaStar_(0.0), Jd_(0), deltaX_trust_(0), norm_deltaX_trust_(0.0), trustDelta_(1.0), trustRegionInitializationMethod_(2), trustRegionInitializationFactor_(1.0), Nuu_(0.0), dist_R0_(0.0), dist_R1_(0.0), dist_R2_(0.0), dist_Total_(0.0), JdJd_norm_(0.0), normTrust_Newton_(0.0), normTrust_CP_(0.0), doDogLeg_(0), doAffineSolve_(0), CurrentTrustFactor_(1.0), NextTrustFactor_(1.0), ResidWtsReevaluated_(false), ResidDecreaseSDExp_(0.0), ResidDecreaseSD_(0.0), ResidDecreaseNewtExp_(0.0), ResidDecreaseNewt_(0.0) { *this =operator=(right); } //==================================================================================================================== NonlinearSolver::~NonlinearSolver() { if (jacCopyPtr_) { delete jacCopyPtr_; } if (HessianPtr_) { delete HessianPtr_; } } //==================================================================================================================== NonlinearSolver& NonlinearSolver::operator=(const NonlinearSolver& right) { if (this == &right) { return *this; } // rely on the ResidJacEval duplMyselfAsresidJacEval() function to // create a deep copy m_func = right.m_func->duplMyselfAsResidJacEval(); solnType_ = right.solnType_; neq_ = right.neq_; m_ewt = right.m_ewt; m_manualDeltaStepSet = right.m_manualDeltaStepSet; m_deltaStepMinimum = right.m_deltaStepMinimum; m_y_n_curr = right.m_y_n_curr; m_ydot_n_curr = right.m_ydot_n_curr; m_y_nm1 = right.m_y_nm1; m_y_n_1 = right.m_y_n_1; m_ydot_n_1 = right.m_ydot_n_1; m_step_1 = right.m_step_1; m_colScales = right.m_colScales; m_rowScales = right.m_rowScales; m_rowWtScales = right.m_rowWtScales; m_resid = right.m_resid; m_wksp = right.m_wksp; m_wksp_2 = right.m_wksp_2; m_residWts = right.m_residWts; m_normResid_0 = right.m_normResid_0; m_normResid_Bound = right.m_normResid_Bound; m_normResid_1 = right.m_normResid_1; m_normDeltaSoln_Newton = right.m_normDeltaSoln_Newton; m_normDeltaSoln_CP = right.m_normDeltaSoln_CP; m_normResidTrial = right.m_normResidTrial; m_resid_scaled = right.m_resid_scaled; m_y_high_bounds = right.m_y_high_bounds; m_y_low_bounds = right.m_y_low_bounds; m_dampBound = right.m_dampBound; m_dampRes = right.m_dampRes; delta_t_n = right.delta_t_n; m_nfe = right.m_nfe; m_colScaling = right.m_colScaling; m_rowScaling = right.m_rowScaling; m_numTotalLinearSolves = right.m_numTotalLinearSolves; m_numTotalNewtIts = right.m_numTotalNewtIts; m_min_newt_its = right.m_min_newt_its; maxNewtIts_ = right.maxNewtIts_; m_jacFormMethod = right.m_jacFormMethod; m_nJacEval = right.m_nJacEval; time_n = right.time_n; m_matrixConditioning = right.m_matrixConditioning; m_order = right.m_order; rtol_ = right.rtol_; atolBase_ = right.atolBase_; atolk_ = right.atolk_; userResidAtol_ = right.userResidAtol_; userResidRtol_ = right.userResidRtol_; checkUserResidualTols_ = right.checkUserResidualTols_; m_print_flag = right.m_print_flag; m_ScaleSolnNormToResNorm = right.m_ScaleSolnNormToResNorm; if (jacCopyPtr_) { delete(jacCopyPtr_); } jacCopyPtr_ = (right.jacCopyPtr_)->duplMyselfAsGeneralMatrix(); if (HessianPtr_) { delete(HessianPtr_); } HessianPtr_ = (right.HessianPtr_)->duplMyselfAsGeneralMatrix(); deltaX_CP_ = right.deltaX_CP_; deltaX_Newton_ = right.deltaX_Newton_; residNorm2Cauchy_ = right.residNorm2Cauchy_; dogLegID_ = right.dogLegID_; dogLegAlpha_ = right.dogLegAlpha_; RJd_norm_ = right.RJd_norm_; lambdaStar_ = right.lambdaStar_; Jd_ = right.Jd_; deltaX_trust_ = right.deltaX_trust_; norm_deltaX_trust_ = right.norm_deltaX_trust_; trustDelta_ = right.trustDelta_; trustRegionInitializationMethod_ = right.trustRegionInitializationMethod_; trustRegionInitializationFactor_ = right.trustRegionInitializationFactor_; Nuu_ = right.Nuu_; dist_R0_ = right.dist_R0_; dist_R1_ = right.dist_R1_; dist_R2_ = right.dist_R2_; dist_Total_ = right.dist_Total_; JdJd_norm_ = right.JdJd_norm_; normTrust_Newton_ = right.normTrust_Newton_; normTrust_CP_ = right.normTrust_CP_; doDogLeg_ = right.doDogLeg_; doAffineSolve_ = right.doAffineSolve_; CurrentTrustFactor_ = right.CurrentTrustFactor_; NextTrustFactor_ = right.NextTrustFactor_; ResidWtsReevaluated_ = right.ResidWtsReevaluated_; ResidDecreaseSDExp_ = right.ResidDecreaseSDExp_; ResidDecreaseSD_ = right.ResidDecreaseSD_; ResidDecreaseNewtExp_ = right.ResidDecreaseNewtExp_; ResidDecreaseNewt_ = right.ResidDecreaseNewt_; return *this; } //==================================================================================================================== // Create solution weights for convergence criteria /* * We create soln weights from the following formula * * wt[i] = rtol * abs(y[i]) + atol[i] * * The program always assumes that atol is specific * to the solution component * * @param y vector of the current solution values */ void NonlinearSolver::createSolnWeights(const doublereal* const y) { for (size_t i = 0; i < neq_; i++) { m_ewt[i] = rtol_ * fabs(y[i]) + atolk_[i]; } } //==================================================================================================================== // set bounds constraints for all variables in the problem /* * * @param y_low_bounds Vector of lower bounds * @param y_high_bounds Vector of high bounds */ void NonlinearSolver::setBoundsConstraints(const doublereal* const y_low_bounds, const doublereal* const y_high_bounds) { for (size_t i = 0; i < neq_; i++) { m_y_low_bounds[i] = y_low_bounds[i]; m_y_high_bounds[i] = y_high_bounds[i]; } } //==================================================================================================================== void NonlinearSolver::setSolverScheme(int doDogLeg, int doAffineSolve) { doDogLeg_ = doDogLeg; doAffineSolve_ = doAffineSolve; } //==================================================================================================================== std::vector & NonlinearSolver::lowBoundsConstraintVector() { return m_y_low_bounds; } //==================================================================================================================== std::vector & NonlinearSolver::highBoundsConstraintVector() { return m_y_high_bounds; } //==================================================================================================================== // L2 norm of the delta of the solution vector /* * calculate the norm of the solution vector. This will * involve the column scaling of the matrix * * The third argument has a default of false. However, * if true, then a table of the largest values is printed * out to standard output. * * @param delta_y Vector to take the norm of * @param title Optional title to be printed out * @param printLargest int indicating how many specific lines should be printed out * @param dampFactor Current value of the damping factor. Defaults to 1. * only used for printout out a table. */ doublereal NonlinearSolver::solnErrorNorm(const doublereal* const delta_y, const char* title, int printLargest, const doublereal dampFactor) const { doublereal sum_norm = 0.0, error; for (size_t i = 0; i < neq_; i++) { error = delta_y[i] / m_ewt[i]; sum_norm += (error * error); } sum_norm = sqrt(sum_norm / neq_); if (printLargest) { if ((printLargest == 1) || (m_print_flag >= 4 && m_print_flag <= 5)) { printf("\t\t solnErrorNorm(): "); if (title) { printf("%s", title); } else { printf(" Delta soln norm "); } printf(" = %-11.4E\n", sum_norm); } else if (m_print_flag >= 6) { const int num_entries = printLargest; printf("\t\t "); print_line("-", 90); printf("\t\t solnErrorNorm(): "); if (title) { printf("%s", title); } else { printf(" Delta soln norm "); } printf(" = %-11.4E\n", sum_norm); doublereal dmax1, normContrib; int j; std::vector imax(num_entries, npos); printf("\t\t Printout of Largest Contributors: (damp = %g)\n", dampFactor); printf("\t\t I weightdeltaY/sqtN| deltaY " "ysolnOld ysolnNew Soln_Weights\n"); printf("\t\t "); print_line("-", 88); for (int jnum = 0; jnum < num_entries; jnum++) { dmax1 = -1.0; for (size_t i = 0; i < neq_; i++) { bool used = false; for (j = 0; j < jnum; j++) { if (imax[j] == i) { used = true; } } if (!used) { error = delta_y[i] / m_ewt[i]; normContrib = sqrt(error * error); if (normContrib > dmax1) { imax[jnum] = i; dmax1 = normContrib; } } } size_t i = imax[jnum]; if (i != npos) { error = delta_y[i] / m_ewt[i]; normContrib = sqrt(error * error); printf("\t\t %4d %12.4e | %12.4e %12.4e %12.4e %12.4e\n", i, normContrib/sqrt((double)neq_), delta_y[i], m_y_n_curr[i], m_y_n_curr[i] + dampFactor * delta_y[i], m_ewt[i]); } } printf("\t\t "); print_line("-", 90); } } return sum_norm; } //==================================================================================================================== /* * L2 Norm of the residual * * The second argument has a default of false. However, * if true, then a table of the largest values is printed * out to standard output. */ doublereal NonlinearSolver::residErrorNorm(const doublereal* const resid, const char* title, const int printLargest, const doublereal* const y) const { doublereal sum_norm = 0.0, error; for (size_t i = 0; i < neq_; i++) { #ifdef DEBUG_HKM mdp::checkFinite(resid[i]); #endif error = resid[i] / m_residWts[i]; #ifdef DEBUG_HKM mdp::checkFinite(error); #endif sum_norm += (error * error); } sum_norm = sqrt(sum_norm / neq_); #ifdef DEBUG_HKM mdp::checkFinite(sum_norm); #endif if (printLargest) { const int num_entries = printLargest; doublereal dmax1, normContrib; int j; std::vector imax(num_entries, npos); if (m_print_flag >= 4 && m_print_flag <= 5) { printf("\t\t residErrorNorm():"); if (title) { printf(" %s ", title); } else { printf(" residual L2 norm "); } printf("= %12.4E\n", sum_norm); } if (m_print_flag >= 6) { printf("\t\t "); print_line("-", 90); printf("\t\t residErrorNorm(): "); if (title) { printf(" %s ", title); } else { printf(" residual L2 norm "); } printf("= %12.4E\n", sum_norm); printf("\t\t Printout of Largest Contributors to norm:\n"); printf("\t\t I |Resid/ResWt| UnsclRes ResWt | y_curr\n"); printf("\t\t "); print_line("-", 88); for (int jnum = 0; jnum < num_entries; jnum++) { dmax1 = -1.0; for (size_t i = 0; i < neq_; i++) { bool used = false; for (j = 0; j < jnum; j++) { if (imax[j] == i) { used = true; } } if (!used) { error = resid[i] / m_residWts[i]; normContrib = sqrt(error * error); if (normContrib > dmax1) { imax[jnum] = i; dmax1 = normContrib; } } } size_t i = imax[jnum]; if (i != npos) { error = resid[i] / m_residWts[i]; normContrib = sqrt(error * error); printf("\t\t %4d %12.4e %12.4e %12.4e | %12.4e\n", i, normContrib, resid[i], m_residWts[i], y[i]); } } printf("\t\t "); print_line("-", 90); } } return sum_norm; } //==================================================================================================================== // Set the column scaling that are used for the inversion of the matrix /* * There are three ways to do this. * * The first method is to set the bool useColScaling to true, leaving the scaling factors unset. * Then, the column scales will be set to the solution error weighting factors. This has the * effect of ensuring that all delta variables will have the same order of magnitude at convergence * end. * * The second way is the explicity set the column factors in the second argument of this function call. * * The final way to input the scales is to override the ResidJacEval member function call, * * calcSolnScales(double time_n, const double *m_y_n_curr, const double *m_y_nm1, double *m_colScales) * * Overriding this function call will trump all other ways to specify the column scaling factors. * * @param useColScaling Turn this on if you want to use column scaling in the calculations * @param scaleFactors A vector of doubles that specifies the column factors. */ void NonlinearSolver::setColumnScaling(bool useColScaling, const double* const scaleFactors) { if (useColScaling) { if (scaleFactors) { m_colScaling = 2; for (size_t i = 0; i < neq_; i++) { m_colScales[i] = scaleFactors[i]; if (m_colScales[i] <= 1.0E-200) { throw CanteraError("NonlinearSolver::setColumnScaling() ERROR", "Bad column scale factor"); } } } else { m_colScaling = 1; } } else { m_colScaling = 0; } } //==================================================================================================================== // Set the rowscaling that are used for the inversion of the matrix /* * Row scaling is set here. Right now the row scaling is set internally in the code. * * @param useRowScaling Turn row scaling on or off. */ void NonlinearSolver::setRowScaling(bool useRowScaling) { m_rowScaling = useRowScaling; } //==================================================================================================================== /* * calcColumnScales(): * * Set the column scaling vector at the current time */ void NonlinearSolver::calcColumnScales() { if (m_colScaling == 1) { for (size_t i = 0; i < neq_; i++) { m_colScales[i] = m_ewt[i]; } } else { for (size_t i = 0; i < neq_; i++) { m_colScales[i] = 1.0; } } if (m_colScaling) { m_func->calcSolnScales(time_n, DATA_PTR(m_y_n_curr), DATA_PTR(m_y_nm1), DATA_PTR(m_colScales)); } } //==================================================================================================================== // Compute the current residual /* * @param time_curr Value of the time * @param typeCalc Type of the calculation * @param y_curr Current value of the solution vector * @param ydot_curr Current value of the time derivative of the solution vector * * @return Returns a flag to indicate that operation is successful. * 1 Means a successful operation * -0 or neg value Means an unsuccessful operation */ int NonlinearSolver::doResidualCalc(const doublereal time_curr, const int typeCalc, const doublereal* const y_curr, const doublereal* const ydot_curr, const ResidEval_Type_Enum evalType) const { int retn = m_func->evalResidNJ(time_curr, delta_t_n, y_curr, ydot_curr, DATA_PTR(m_resid), evalType); m_nfe++; m_resid_scaled = false; return retn; } //==================================================================================================================== // Scale the matrix /* * @param jac Jacobian * @param y_comm Current value of the solution vector * @param ydot_comm Current value of the time derivative of the solution vector * @param time_curr current value of the time */ void NonlinearSolver::scaleMatrix(GeneralMatrix& jac, doublereal* const y_comm, doublereal* const ydot_comm, doublereal time_curr, int num_newt_its) { size_t irow, jcol; size_t ku, kl; size_t ivec[2]; jac.nRowsAndStruct(ivec); double* colP_j; /* * Column scaling -> We scale the columns of the Jacobian * by the nominal important change in the solution vector */ if (m_colScaling) { if (!jac.factored()) { if (jac.matrixType_ == 0) { /* * Go get new scales -> Took this out of this inner loop. * Needs to be done at a larger scale. */ // setColumnScales(); /* * Scale the new Jacobian */ doublereal* jptr = &(*(jac.begin())); for (jcol = 0; jcol < neq_; jcol++) { for (irow = 0; irow < neq_; irow++) { *jptr *= m_colScales[jcol]; jptr++; } } } else if (jac.matrixType_ == 1) { kl = ivec[0]; ku = ivec[1]; for (jcol = 0; jcol < neq_; jcol++) { colP_j = (doublereal*) jac.ptrColumn(jcol); for (irow = jcol - ku; irow <= jcol + kl; irow++) { if (irow >= 0 && irow < neq_) { colP_j[kl + ku + irow - jcol] *= m_colScales[jcol]; } } } } } } /* * row sum scaling -> Note, this is an unequivical success * at keeping the small numbers well balanced and nonnegative. */ if (! jac.factored()) { /* * Ok, this is ugly. jac.begin() returns an vector iterator * to the first data location. * Then &(*()) reverts it to a doublereal *. */ doublereal* jptr = &(*(jac.begin())); for (irow = 0; irow < neq_; irow++) { m_rowScales[irow] = 0.0; m_rowWtScales[irow] = 0.0; } if (jac.matrixType_ == 0) { for (jcol = 0; jcol < neq_; jcol++) { for (irow = 0; irow < neq_; irow++) { if (m_rowScaling) { m_rowScales[irow] += fabs(*jptr); } if (m_colScaling) { // This is needed in order to mitgate the change in J_ij carried out just above this loop. // Alternatively, we could move this loop up to the top m_rowWtScales[irow] += fabs(*jptr) * m_ewt[jcol] / m_colScales[jcol]; } else { m_rowWtScales[irow] += fabs(*jptr) * m_ewt[jcol]; } jptr++; } } } else if (jac.matrixType_ == 1) { kl = ivec[0]; ku = ivec[1]; for (jcol = 0; jcol < neq_; jcol++) { colP_j = (doublereal*) jac.ptrColumn(jcol); for (irow = jcol - ku; irow <= jcol + kl; irow++) { if (irow >= 0 && irow < neq_) { double vv = fabs(colP_j[kl + ku + irow - jcol]); if (m_rowScaling) { m_rowScales[irow] += vv; } if (m_colScaling) { // This is needed in order to mitgate the change in J_ij carried out just above this loop. // Alternatively, we could move this loop up to the top m_rowWtScales[irow] += vv * m_ewt[jcol] / m_colScales[jcol]; } else { m_rowWtScales[irow] += vv * m_ewt[jcol]; } } } } } if (m_rowScaling) { for (irow = 0; irow < neq_; irow++) { m_rowScales[irow] = 1.0/m_rowScales[irow]; } } else { for (irow = 0; irow < neq_; irow++) { m_rowScales[irow] = 1.0; } } // What we have defined is a maximum value that the residual can be and still pass. // This isn't sufficient. if (m_rowScaling) { if (jac.matrixType_ == 0) { jptr = &(*(jac.begin())); for (jcol = 0; jcol < neq_; jcol++) { for (irow = 0; irow < neq_; irow++) { *jptr *= m_rowScales[irow]; jptr++; } } } else if (jac.matrixType_ == 1) { kl = ivec[0]; ku = ivec[1]; for (jcol = 0; jcol < neq_; jcol++) { colP_j = (doublereal*) jac.ptrColumn(jcol); for (irow = jcol - ku; irow <= jcol + kl; irow++) { if (irow >= 0 && irow < neq_) { colP_j[kl + ku + irow - jcol] *= m_rowScales[irow]; } } } } } if (num_newt_its % 5 == 1) { computeResidWts(); } } } //==================================================================================================================== // Calculate the scaling factor for translating residual norms into solution norms. /* * This routine calls computeResidWts() a couple of times in the calculation of m_ScaleSolnNormToResNorm. * A more sophisticated routine may do more with signs to get a better value. Perhaps, a series of calculations * with different signs attached may be in order. Then, m_ScaleSolnNormToResNorm would be calculated * as the minimum of a series of calculations. */ void NonlinearSolver::calcSolnToResNormVector() { if (! jacCopyPtr_->factored()) { if (checkUserResidualTols_ != 1) { doublereal sum = 0.0; for (size_t irow = 0; irow < neq_; irow++) { m_residWts[irow] = m_rowWtScales[irow] / neq_; sum += m_residWts[irow]; } sum /= neq_; for (size_t irow = 0; irow < neq_; irow++) { m_residWts[irow] = (m_residWts[irow] + atolBase_ * atolBase_ * sum); } if (checkUserResidualTols_ == 2) { for (size_t irow = 0; irow < neq_; irow++) { m_residWts[irow] = MIN(m_residWts[irow], userResidAtol_[irow] + userResidRtol_ * m_rowWtScales[irow] / neq_); } } } else { for (size_t irow = 0; irow < neq_; irow++) { m_residWts[irow] = userResidAtol_[irow] + userResidRtol_ * m_rowWtScales[irow] / neq_; } } for (size_t irow = 0; irow < neq_; irow++) { m_wksp[irow] = 0.0; } doublereal* jptr = &(jacCopyPtr_->operator()(0,0)); for (size_t jcol = 0; jcol < neq_; jcol++) { for (size_t irow = 0; irow < neq_; irow++) { m_wksp[irow] += (*jptr) * m_ewt[jcol]; jptr++; } } doublereal resNormOld = 0.0; doublereal error; for (size_t irow = 0; irow < neq_; irow++) { error = m_wksp[irow] / m_residWts[irow]; resNormOld += error * error; } resNormOld = sqrt(resNormOld / neq_); if (resNormOld > 0.0) { m_ScaleSolnNormToResNorm = resNormOld; } if (m_ScaleSolnNormToResNorm < 1.0E-8) { m_ScaleSolnNormToResNorm = 1.0E-8; } // Recalculate the residual weights now that we know the value of m_ScaleSolnNormToResNorm computeResidWts(); } else { throw CanteraError("NonlinearSolver::calcSolnToResNormVector()" , "Logic error"); } } //==================================================================================================================== // Compute the undamped Newton step based on the current jacobian and an input rhs /* * Compute the undamped Newton step. The residual function is * evaluated at the current time, t_n, at the current values of the * solution vector, m_y_n_curr, and the solution time derivative, m_ydot_n. * The Jacobian is not recomputed. * * A factored jacobian is reused, if available. If a factored jacobian * is not available, then the jacobian is factored. Before factoring, * the jacobian is row and column-scaled. Column scaling is not * recomputed. The row scales are recomputed here, after column * scaling has been implemented. */ int NonlinearSolver::doNewtonSolve(const doublereal time_curr, const doublereal* const y_curr, const doublereal* const ydot_curr, doublereal* const delta_y, GeneralMatrix& jac) { // multiply the residual by -1 if (m_rowScaling && !m_resid_scaled) { for (size_t n = 0; n < neq_; n++) { delta_y[n] = -m_rowScales[n] * m_resid[n]; } m_resid_scaled = true; } else { for (size_t n = 0; n < neq_; n++) { delta_y[n] = -m_resid[n]; } } /* * Solve the system -> This also involves inverting the * matrix */ int info = jac.solve(DATA_PTR(delta_y)); /* * reverse the column scaling if there was any. */ if (m_colScaling) { for (size_t irow = 0; irow < neq_; irow++) { delta_y[irow] = delta_y[irow] * m_colScales[irow]; } } #ifdef DEBUG_JAC if (printJacContributions) { for (size_t iNum = 0; iNum < numRows; iNum++) { if (iNum > 0) { focusRow++; } doublereal dsum = 0.0; vector_fp& Jdata = jacBack.data(); doublereal dRow = Jdata[neq_ * focusRow + focusRow]; printf("\n Details on delta_Y for row %d \n", focusRow); printf(" Value before = %15.5e, delta = %15.5e," "value after = %15.5e\n", y_curr[focusRow], delta_y[focusRow], y_curr[focusRow] + delta_y[focusRow]); if (!freshJac) { printf(" Old Jacobian\n"); } printf(" col delta_y aij " "contrib \n"); printf("--------------------------------------------------" "---------------------------------------------\n"); printf(" Res(%d) %15.5e %15.5e %15.5e (Res = %g)\n", focusRow, delta_y[focusRow], dRow, RRow[iNum] / dRow, RRow[iNum]); dsum += RRow[iNum] / dRow; for (size_t ii = 0; ii < neq_; ii++) { if (ii != focusRow) { doublereal aij = Jdata[neq_ * ii + focusRow]; doublereal contrib = aij * delta_y[ii] * (-1.0) / dRow; dsum += contrib; if (fabs(contrib) > Pcutoff) { printf("%6d %15.5e %15.5e %15.5e\n", ii, delta_y[ii] , aij, contrib); } } } printf("--------------------------------------------------" "---------------------------------------------\n"); printf(" %15.5e %15.5e\n", delta_y[focusRow], dsum); } } #endif m_numTotalLinearSolves++; m_numLocalLinearSolves++; return info; } //==================================================================================================================== // Compute the newton step, either by direct newton's or by solving a close problem that is represented // by a Hessian ( /* * This is algorith A.6.5.1 in Dennis / Schnabel * * Compute the QR decomposition * * Notes on banded Hessian solve: * The matrix for jT j has a larger band width. Both the top and bottom band widths * are doubled, going from KU to KU+KL and KL to KU+KL in size. This is not an impossible increase in cost, but * has to be considered. */ int NonlinearSolver::doAffineNewtonSolve(const doublereal* const y_curr, const doublereal* const ydot_curr, doublereal* const delta_y, GeneralMatrix& jac) { bool newtonGood = true; doublereal* delyNewton = 0; // We can default to QR here ( or not ) jac.useFactorAlgorithm(1); int useQR = jac.factorAlgorithm(); // multiplyl the residual by -1 // Scale the residual if there is row scaling. Note, the matrix has already been scaled if (m_rowScaling && !m_resid_scaled) { for (size_t n = 0; n < neq_; n++) { delta_y[n] = -m_rowScales[n] * m_resid[n]; } m_resid_scaled = true; } else { for (size_t n = 0; n < neq_; n++) { delta_y[n] = -m_resid[n]; } } // Factor the matrix using a standard Newton solve m_conditionNumber = 1.0E300; int info = 0; if (!jac.factored()) { if (useQR) { info = jac.factorQR(); } else { info = jac.factor(); } } /* * Find the condition number of the matrix * If we have failed to factor, we will fall back to calculating and factoring a modified Hessian */ if (info == 0) { doublereal rcond = 0.0; if (useQR) { rcond = jac.rcondQR(); } else { doublereal a1norm = jac.oneNorm(); rcond = jac.rcond(a1norm); } if (rcond > 0.0) { m_conditionNumber = 1.0 / rcond; } } else { m_conditionNumber = 1.0E300; newtonGood = false; if (m_print_flag >= 1) { printf("\t\t doAffineNewtonSolve: "); if (useQR) { printf("factorQR()"); } else { printf("factor()"); } printf(" returned with info = %d, indicating a zero row or column\n", info); } } bool doHessian = false; if (s_doBothSolvesAndCompare) { doHessian = true; } if (m_conditionNumber < 1.0E7) { if (m_print_flag >= 4) { printf("\t\t doAffineNewtonSolve: Condition number = %g during regular solve\n", m_conditionNumber); } /* * Solve the system -> This also involves inverting the matrix */ int info = jac.solve(DATA_PTR(delta_y)); if (info) { if (m_print_flag >= 2) { printf("\t\t doAffineNewtonSolve() ERROR: QRSolve returned INFO = %d. Switching to Hessian solve\n", info); } doHessian = true; newtonGood = false; } /* * reverse the column scaling if there was any on a successful solve */ if (m_colScaling) { for (size_t irow = 0; irow < neq_; irow++) { delta_y[irow] = delta_y[irow] * m_colScales[irow]; } } } else { if (jac.matrixType_ == 1) { newtonGood = true; if (m_print_flag >= 3) { printf("\t\t doAffineNewtonSolve() WARNING: Condition number too large, %g, But Banded Hessian solve " "not implemented yet \n", m_conditionNumber); } } else { doHessian = true; newtonGood = false; if (m_print_flag >= 3) { printf("\t\t doAffineNewtonSolve() WARNING: Condition number too large, %g. Doing a Hessian solve \n", m_conditionNumber); } } } if (doHessian) { // Store the old value for later comparison delyNewton = mdp::mdp_alloc_dbl_1((int) neq_, MDP_DBL_NOINIT); for (size_t irow = 0; irow < neq_; irow++) { delyNewton[irow] = delta_y[irow]; } // Get memory if not done before if (HessianPtr_ == 0) { HessianPtr_ = jac.duplMyselfAsGeneralMatrix(); } /* * Calculate the symmetric Hessian */ GeneralMatrix& hessian = *HessianPtr_; GeneralMatrix& jacCopy = *jacCopyPtr_; hessian.zero(); if (m_rowScaling) { for (size_t i = 0; i < neq_; i++) { for (size_t j = i; j < neq_; j++) { for (size_t k = 0; k < neq_; k++) { hessian(i,j) += jacCopy(k,i) * jacCopy(k,j) * m_rowScales[k] * m_rowScales[k]; } hessian(j,i) = hessian(i,j); } } } else { for (size_t i = 0; i < neq_; i++) { for (size_t j = i; j < neq_; j++) { for (size_t k = 0; k < neq_; k++) { hessian(i,j) += jacCopy(k,i) * jacCopy(k,j); } hessian(j,i) = hessian(i,j); } } } /* * Calculate the matrix norm of the Hessian */ doublereal hnorm = 0.0; doublereal hcol = 0.0; if (m_colScaling) { for (size_t i = 0; i < neq_; i++) { for (size_t j = i; j < neq_; j++) { hcol += fabs(hessian(j,i)) * m_colScales[j]; } for (size_t j = i+1; j < neq_; j++) { hcol += fabs(hessian(i,j)) * m_colScales[j]; } hcol *= m_colScales[i]; if (hcol > hnorm) { hnorm = hcol; } } } else { for (size_t i = 0; i < neq_; i++) { for (size_t j = i; j < neq_; j++) { hcol += fabs(hessian(j,i)); } for (size_t j = i+1; j < neq_; j++) { hcol += fabs(hessian(i,j)); } if (hcol > hnorm) { hnorm = hcol; } } } /* * Add junk to the Hessian diagonal * -> Note, testing indicates that this will get too big for ill-conditioned systems. */ hcol = sqrt(1.0*neq_) * 1.0E-7 * hnorm; #ifdef DEBUG_HKM_NOT if (hcol > 1.0) { hcol = 1.0E1; } #endif if (m_colScaling) { for (size_t i = 0; i < neq_; i++) { hessian(i,i) += hcol / (m_colScales[i] * m_colScales[i]); } } else { for (size_t i = 0; i < neq_; i++) { hessian(i,i) += hcol; } } /* * Factor the Hessian */ int info; ct_dpotrf(ctlapack::UpperTriangular, neq_, &(*(HessianPtr_->begin())), neq_, info); if (info) { if (m_print_flag >= 2) { printf("\t\t doAffineNewtonSolve() ERROR: Hessian isn't positive definate DPOTRF returned INFO = %d\n", info); } return info; } // doublereal *JTF = delta_y; doublereal* delyH = mdp::mdp_alloc_dbl_1((int) neq_, MDP_DBL_NOINIT); // First recalculate the scaled residual. It got wiped out doing the newton solve if (m_rowScaling) { for (size_t n = 0; n < neq_; n++) { delyH[n] = -m_rowScales[n] * m_resid[n]; } } else { for (size_t n = 0; n < neq_; n++) { delyH[n] = -m_resid[n]; } } if (m_rowScaling) { for (size_t j = 0; j < neq_; j++) { delta_y[j] = 0.0; for (size_t i = 0; i < neq_; i++) { delta_y[j] += delyH[i] * jacCopy(i,j) * m_rowScales[i]; } } } else { for (size_t j = 0; j < neq_; j++) { delta_y[j] = 0.0; for (size_t i = 0; i < neq_; i++) { delta_y[j] += delyH[i] * jacCopy(i,j); } } } /* * Solve the factored Hessian System */ ct_dpotrs(ctlapack::UpperTriangular, neq_, 1,&(*(hessian.begin())), neq_, delta_y, neq_, info); if (info) { if (m_print_flag >= 2) { printf("\t\t NonlinearSolver::doAffineNewtonSolve() ERROR: DPOTRS returned INFO = %d\n", info); } return info; } /* * reverse the column scaling if there was any. */ if (m_colScaling) { for (size_t irow = 0; irow < neq_; irow++) { delta_y[irow] = delta_y[irow] * m_colScales[irow]; } } if (doDogLeg_ && m_print_flag > 7) { double normNewt = solnErrorNorm(CONSTD_DATA_PTR(delyNewton)); double normHess = solnErrorNorm(CONSTD_DATA_PTR(delta_y)); printf("\t\t doAffineNewtonSolve(): Printout Comparison between Hessian deltaX and Newton deltaX\n"); printf("\t\t I Hessian+Junk Newton"); if (newtonGood || s_alwaysAssumeNewtonGood) { printf(" (USING NEWTON DIRECTION)\n"); } else { printf(" (USING HESSIAN DIRECTION)\n"); } printf("\t\t Norm: %12.4E %12.4E\n", normHess, normNewt); printf("\t\t --------------------------------------------------------\n"); for (size_t i =0; i < neq_; i++) { printf("\t\t %3d %13.5E %13.5E\n", i, delta_y[i], delyNewton[i]); } printf("\t\t --------------------------------------------------------\n"); } else if (doDogLeg_ && m_print_flag >= 4) { double normNewt = solnErrorNorm(CONSTD_DATA_PTR(delyNewton)); double normHess = solnErrorNorm(CONSTD_DATA_PTR(delta_y)); printf("\t\t doAffineNewtonSolve(): Hessian update norm = %12.4E \n" "\t\t Newton update norm = %12.4E \n", normHess, normNewt); if (newtonGood || s_alwaysAssumeNewtonGood) { printf("\t\t (USING NEWTON DIRECTION)\n"); } else { printf("\t\t (USING HESSIAN DIRECTION)\n"); } } /* * Choose the delta_y to use */ if (newtonGood || s_alwaysAssumeNewtonGood) { mdp::mdp_copy_dbl_1(DATA_PTR(delta_y), CONSTD_DATA_PTR(delyNewton), (int) neq_); } mdp::mdp_safe_free((void**) &delyH); mdp::mdp_safe_free((void**) &delyNewton); } #ifdef DEBUG_JAC if (printJacContributions) { for (int iNum = 0; iNum < numRows; iNum++) { if (iNum > 0) { focusRow++; } doublereal dsum = 0.0; vector_fp& Jdata = jacBack.data(); doublereal dRow = Jdata[neq_ * focusRow + focusRow]; printf("\n Details on delta_Y for row %d \n", focusRow); printf(" Value before = %15.5e, delta = %15.5e," "value after = %15.5e\n", y_curr[focusRow], delta_y[focusRow], y_curr[focusRow] + delta_y[focusRow]); if (!freshJac) { printf(" Old Jacobian\n"); } printf(" col delta_y aij " "contrib \n"); printf("-----------------------------------------------------------------------------------------------\n"); printf(" Res(%d) %15.5e %15.5e %15.5e (Res = %g)\n", focusRow, delta_y[focusRow], dRow, RRow[iNum] / dRow, RRow[iNum]); dsum += RRow[iNum] / dRow; for (int ii = 0; ii < neq_; ii++) { if (ii != focusRow) { doublereal aij = Jdata[neq_ * ii + focusRow]; doublereal contrib = aij * delta_y[ii] * (-1.0) / dRow; dsum += contrib; if (fabs(contrib) > Pcutoff) { printf("%6d %15.5e %15.5e %15.5e\n", ii, delta_y[ii] , aij, contrib); } } } printf("-----------------------------------------------------------------------------------------------\n"); printf(" %15.5e %15.5e\n", delta_y[focusRow], dsum); } } #endif m_numTotalLinearSolves++; m_numLocalLinearSolves++; return info; } //==================================================================================================================== // Do a steepest descent calculation /* * This call must be made on the unfactored jacobian! */ doublereal NonlinearSolver::doCauchyPointSolve(GeneralMatrix& jac) { doublereal rowFac = 1.0; doublereal colFac = 1.0; doublereal normSoln; // Calculate the descent direction /* * For confirmation of the scaling factors, see Dennis and Schnabel p, 152, p, 156 and my notes * * The colFac and rowFac values are used to eliminate the scaling of the matrix from the * actual equation * * Here we calculate the steepest descent direction. This is equation (11) in the notes. It is * storred in deltaX_CP_[].The value corresponds to d_descent[]. */ for (size_t j = 0; j < neq_; j++) { deltaX_CP_[j] = 0.0; if (m_colScaling) { colFac = 1.0 / m_colScales[j]; } for (size_t i = 0; i < neq_; i++) { if (m_rowScaling) { rowFac = 1.0 / m_rowScales[i]; } deltaX_CP_[j] -= m_resid[i] * jac(i,j) * colFac * rowFac * m_ewt[j] * m_ewt[j] / (m_residWts[i] * m_residWts[i]); #ifdef DEBUG_MODE mdp::checkFinite(deltaX_CP_[j]); #endif } } /* * Calculate J_hat d_y_descent. This is formula 18 in the notes. */ for (size_t i = 0; i < neq_; i++) { Jd_[i] = 0.0; if (m_rowScaling) { rowFac = 1.0 / m_rowScales[i]; } else { rowFac = 1.0; } for (size_t j = 0; j < neq_; j++) { if (m_colScaling) { colFac = 1.0 / m_colScales[j]; } Jd_[i] += deltaX_CP_[j] * jac(i,j) * rowFac * colFac / m_residWts[i]; } } /* * Calculate the distance along the steepest descent until the Cauchy point * This is Eqn. 17 in the notes. */ RJd_norm_ = 0.0; JdJd_norm_ = 0.0; for (size_t i = 0; i < neq_; i++) { RJd_norm_ += m_resid[i] * Jd_[i] / m_residWts[i]; JdJd_norm_ += Jd_[i] * Jd_[i]; } //if (RJd_norm_ > -1.0E-300) { // printf("we are here: zero residual\n"); //} if (fabs(JdJd_norm_) < 1.0E-290) { if (fabs(RJd_norm_) < 1.0E-300) { lambdaStar_ = 0.0; } else { throw CanteraError("NonlinearSolver::doCauchyPointSolve()", "Unexpected condition: norms are zero"); } } else { lambdaStar_ = - RJd_norm_ / (JdJd_norm_); } /* * Now we modify the steepest descent vector such that its length is equal to the * Cauchy distance. From now on, if we want to recreate the descent vector, we have * to unnormalize it by dividing by lambdaStar_. */ for (size_t i = 0; i < neq_; i++) { deltaX_CP_[i] *= lambdaStar_; } doublereal normResid02 = m_normResid_0 * m_normResid_0 * neq_; /* * Calculate the expected square of the risdual at the Cauchy point if the linear model is correct */ if (fabs(JdJd_norm_) < 1.0E-290) { residNorm2Cauchy_ = normResid02; } else { residNorm2Cauchy_ = normResid02 - RJd_norm_ * RJd_norm_ / (JdJd_norm_); } // Extra printout section if (m_print_flag > 2) { // Calculate the expected residual at the Cauchy point if the linear model is correct doublereal residCauchy = 0.0; if (residNorm2Cauchy_ > 0.0) { residCauchy = sqrt(residNorm2Cauchy_ / neq_); } else { if (fabs(JdJd_norm_) < 1.0E-290) { residCauchy = m_normResid_0; } else { residCauchy = m_normResid_0 - sqrt(RJd_norm_ * RJd_norm_ / (JdJd_norm_)); } } // Compute the weighted norm of the undamped step size descentDir_[] if ((s_print_DogLeg || doDogLeg_) && m_print_flag >= 6) { normSoln = solnErrorNorm(DATA_PTR(deltaX_CP_), "SteepestDescentDir", 10); } else { normSoln = solnErrorNorm(DATA_PTR(deltaX_CP_), "SteepestDescentDir", 0); } if ((s_print_DogLeg || doDogLeg_) && m_print_flag >= 5) { printf("\t\t doCauchyPointSolve: Steepest descent to Cauchy point: \n"); printf("\t\t\t R0 = %g \n", m_normResid_0); printf("\t\t\t Rpred = %g\n", residCauchy); printf("\t\t\t Rjd = %g\n", RJd_norm_); printf("\t\t\t JdJd = %g\n", JdJd_norm_); printf("\t\t\t deltaX = %g\n", normSoln); printf("\t\t\t lambda = %g\n", lambdaStar_); } } else { // Calculate the norm of the Cauchy solution update in any case normSoln = solnErrorNorm(DATA_PTR(deltaX_CP_), "SteepestDescentDir", 0); } return normSoln; } //=================================================================================================================== void NonlinearSolver::descentComparison(doublereal time_curr, doublereal* ydot0, doublereal* ydot1, int& numTrials) { doublereal ff = 1.0E-5; doublereal ffNewt = 1.0E-5; doublereal* y_n_1 = DATA_PTR(m_wksp); doublereal cauchyDistanceNorm = solnErrorNorm(DATA_PTR(deltaX_CP_)); if (cauchyDistanceNorm < 1.0E-2) { ff = 1.0E-9 / cauchyDistanceNorm; if (ff > 1.0E-2) { ff = 1.0E-2; } } for (size_t i = 0; i < neq_; i++) { y_n_1[i] = m_y_n_curr[i] + ff * deltaX_CP_[i]; } /* * Calculate the residual that would result if y1[] were the new solution vector * -> m_resid[] contains the result of the residual calculation */ if (solnType_ != NSOLN_TYPE_STEADY_STATE) { doResidualCalc(time_curr, solnType_, y_n_1, ydot1, Base_LaggedSolutionComponents); } else { doResidualCalc(time_curr, solnType_, y_n_1, ydot0, Base_LaggedSolutionComponents); } doublereal normResid02 = m_normResid_0 * m_normResid_0 * neq_; doublereal residSteep = residErrorNorm(DATA_PTR(m_resid)); doublereal residSteep2 = residSteep * residSteep * neq_; doublereal funcDecreaseSD = 0.5 * (residSteep2 - normResid02) / (ff * cauchyDistanceNorm); doublereal sNewt = solnErrorNorm(DATA_PTR(deltaX_Newton_)); if (sNewt > 1.0) { ffNewt = ffNewt / sNewt; } for (size_t i = 0; i < neq_; i++) { y_n_1[i] = m_y_n_curr[i] + ffNewt * deltaX_Newton_[i]; } /* * Calculate the residual that would result if y1[] were the new solution vector. * Here we use the lagged solution components in the residual calculation as well. We are * interested in the linear model and its agreement with the nonlinear model. * * -> m_resid[] contains the result of the residual calculation */ if (solnType_ != NSOLN_TYPE_STEADY_STATE) { doResidualCalc(time_curr, solnType_, y_n_1, ydot1, Base_LaggedSolutionComponents); } else { doResidualCalc(time_curr, solnType_, y_n_1, ydot0, Base_LaggedSolutionComponents); } doublereal residNewt = residErrorNorm(DATA_PTR(m_resid)); doublereal residNewt2 = residNewt * residNewt * neq_; doublereal funcDecreaseNewt2 = 0.5 * (residNewt2 - normResid02) / (ffNewt * sNewt); // This is the expected inital rate of decrease in the Cauchy direction. // -> This is Eqn. 29 = Rhat dot Jhat dy / || d || doublereal funcDecreaseSDExp = RJd_norm_ / cauchyDistanceNorm * lambdaStar_; doublereal funcDecreaseNewtExp2 = - normResid02 / sNewt; if (m_normResid_0 > 1.0E-100) { ResidDecreaseSDExp_ = funcDecreaseSDExp / neq_ / m_normResid_0; ResidDecreaseSD_ = funcDecreaseSD / neq_ / m_normResid_0; ResidDecreaseNewtExp_ = funcDecreaseNewtExp2 / neq_ / m_normResid_0; ResidDecreaseNewt_ = funcDecreaseNewt2 / neq_ / m_normResid_0; } else { ResidDecreaseSDExp_ = 0.0; ResidDecreaseSD_ = funcDecreaseSD / neq_; ResidDecreaseNewtExp_ = 0.0; ResidDecreaseNewt_ = funcDecreaseNewt2 / neq_; } numTrials += 2; /* * HKM These have been shown to exactly match up. * The steepest direction is always largest even when there are variable solution weights * * HKM When a hessian is used with junk on the diagonal, funcDecreaseNewtExp2 is no longer accurate as the * direction gets signficantly shorter with increasing condition number. This suggests an algorithm where the * newton step from the Hessian should be increased so as to match funcDecreaseNewtExp2 = funcDecreaseNewt2. * This roughly equals the ratio of the norms of the hessian and newton steps. This increased Newton step can * then be used with the trust region double dogleg algorithm. */ if ((s_print_DogLeg && m_print_flag >= 3) || (doDogLeg_ && m_print_flag >= 5)) { printf("\t\t descentComparison: initial rate of decrease of func in cauchy dir (expected) = %g\n", funcDecreaseSDExp); printf("\t\t descentComparison: initial rate of decrease of func in cauchy dir = %g\n", funcDecreaseSD); printf("\t\t descentComparison: initial rate of decrease of func in newton dir (expected) = %g\n", funcDecreaseNewtExp2); printf("\t\t descentComparison: initial rate of decrease of func in newton dir = %g\n", funcDecreaseNewt2); } if ((s_print_DogLeg && m_print_flag >= 3) || (doDogLeg_ && m_print_flag >= 4)) { printf("\t\t descentComparison: initial rate of decrease of Resid in cauchy dir (expected) = %g\n", ResidDecreaseSDExp_); printf("\t\t descentComparison: initial rate of decrease of Resid in cauchy dir = %g\n", ResidDecreaseSD_); printf("\t\t descentComparison: initial rate of decrease of Resid in newton dir (expected) = %g\n", ResidDecreaseNewtExp_); printf("\t\t descentComparison: initial rate of decrease of Resid in newton dir = %g\n", ResidDecreaseNewt_); } if ((s_print_DogLeg && m_print_flag >= 5) || (doDogLeg_ && m_print_flag >= 5)) { if (funcDecreaseNewt2 >= 0.0) { printf("\t\t %13.5E %22.16E\n", funcDecreaseNewtExp2, m_normResid_0); double ff = ffNewt * 1.0E-5; for (int ii = 0; ii < 13; ii++) { ff *= 10.; if (ii == 12) { ff = ffNewt; } for (size_t i = 0; i < neq_; i++) { y_n_1[i] = m_y_n_curr[i] + ff * deltaX_Newton_[i]; } numTrials += 1; if (solnType_ != NSOLN_TYPE_STEADY_STATE) { doResidualCalc(time_curr, solnType_, y_n_1, ydot1, Base_LaggedSolutionComponents); } else { doResidualCalc(time_curr, solnType_, y_n_1, ydot0, Base_LaggedSolutionComponents); } residNewt = residErrorNorm(DATA_PTR(m_resid)); residNewt2 = residNewt * residNewt * neq_; funcDecreaseNewt2 = 0.5 * (residNewt2 - normResid02) / (ff * sNewt); printf("\t\t %10.3E %13.5E %22.16E\n", ff, funcDecreaseNewt2, residNewt); } } } } //==================================================================================================================== // Setup the parameters for the double dog leg /* * The calls to the doCauchySolve() and doNewtonSolve() routines are done at the main level. This routine comes * after those calls. We calculate the point Nuu_ here, the distances of the dog-legs, * and the norms of the CP and Newton points in terms of the trust vectors. */ void NonlinearSolver::setupDoubleDogleg() { /* * Gamma = ||grad f ||**4 * --------------------------------------------- * (grad f)T H (grad f) (grad f)T H-1 (grad f) */ // doublereal sumG = 0.0; // doublereal sumH = 0.0; // for (int i = 0; i < neq_; i++) { // sumG = deltax_cp_[i] * deltax_cp_[i]; // sumH = deltax_cp_[i] * newtDir[i]; // } // double fac1 = sumG / lambdaStar_; // double fac2 = sumH / lambdaStar_; // double gamma = fac1 / fac2; // doublereal gamma = m_normDeltaSoln_CP / m_normDeltaSoln_Newton; /* * This hasn't worked. so will do it heuristically. One issue is that the newton * direction is not the inverse of the Hessian times the gradient. The Hession * is the matrix squared. Until I have the inverse of the Hessian from QR factorization * I may not be able to do it this way. */ /* * Heuristic algorithm - Find out where on the Newton line the residual is the same * as the residual at the cauchy point. Then, go halfway to * the newton point and call that Nuu. * Maybe we need to check that the linearized residual is * monotonic along that line. However, we haven't needed to yet. */ doublereal residSteepLin = expectedResidLeg(0, 1.0); doublereal Nres2CP = residSteepLin * residSteepLin * neq_; doublereal Nres2_o = m_normResid_0 * m_normResid_0 * neq_; doublereal a = Nres2CP / Nres2_o; doublereal betaEqual = (2.0 - sqrt(4.0 - 4 * (1.0 - a))) / 2.0; doublereal beta = (1.0 + betaEqual) / 2.0; Nuu_ = beta; dist_R0_ = m_normDeltaSoln_CP; for (size_t i = 0; i < neq_; i++) { m_wksp[i] = Nuu_ * deltaX_Newton_[i] - deltaX_CP_[i]; } dist_R1_ = solnErrorNorm(DATA_PTR(m_wksp)); dist_R2_ = (1.0 - Nuu_) * m_normDeltaSoln_Newton; dist_Total_ = dist_R0_ + dist_R1_ + dist_R2_; /* * Calculate the trust distances */ normTrust_Newton_ = calcTrustDistance(deltaX_Newton_); normTrust_CP_ = calcTrustDistance(deltaX_CP_); } //==================================================================================================================== // Change the global lambda coordinate into the (leg,alpha) coordinate for the double dogleg /* * @param lambda Global value of the distance along the double dogleg * @param alpha relative value along the particular leg * * @return Returns the leg number ( 0, 1, or 2). */ int NonlinearSolver::lambdaToLeg(const doublereal lambda, doublereal& alpha) const { if (lambda < dist_R0_ / dist_Total_) { alpha = lambda * dist_Total_ / dist_R0_; return 0; } else if (lambda < ((dist_R0_ + dist_R1_)/ dist_Total_)) { alpha = (lambda * dist_Total_ - dist_R0_) / dist_R1_; return 1; } alpha = (lambda * dist_Total_ - dist_R0_ - dist_R1_) / dist_R2_; return 2; } //==================================================================================================================== // Calculated the expected residual along the double dogleg curve. /* * @param leg 0, 1, or 2 representing the curves of the dogleg * @param alpha Relative distance along the particular curve. * * @return Returns the expected value of the residual at that point according to the quadratic model. * The residual at the newton point will always be zero. */ doublereal NonlinearSolver::expectedResidLeg(int leg, doublereal alpha) const { doublereal resD2, res2, resNorm; doublereal normResid02 = m_normResid_0 * m_normResid_0 * neq_; if (leg == 0) { /* * We are on the steepest descent line * along that line * R2 = R2 + 2 lambda R dot Jd + lambda**2 Jd dot Jd */ doublereal tmp = - 2.0 * alpha + alpha * alpha; doublereal tmp2 = - RJd_norm_ * lambdaStar_; resD2 = tmp2 * tmp; } else if (leg == 1) { /* * Same formula as above for lambda=1. */ doublereal tmp2 = - RJd_norm_ * lambdaStar_; doublereal RdotJS = - tmp2; doublereal JsJs = tmp2; doublereal res0_2 = m_normResid_0 * m_normResid_0 * neq_; res2 = res0_2 + (1.0 - alpha) * 2 * RdotJS - 2 * alpha * Nuu_ * res0_2 + (1.0 - alpha) * (1.0 - alpha) * JsJs + alpha * alpha * Nuu_ * Nuu_ * res0_2 - 2 * alpha * Nuu_ * (1.0 - alpha) * RdotJS; resNorm = sqrt(res2 / neq_); return resNorm; } else { doublereal beta = Nuu_ + alpha * (1.0 - Nuu_); doublereal tmp2 = normResid02; doublereal tmp = 1.0 - 2.0 * beta + 1.0 * beta * beta - 1.0; resD2 = tmp * tmp2; } res2 = m_normResid_0 * m_normResid_0 * neq_ + resD2; if (res2 < 0.0) { resNorm = m_normResid_0 - sqrt(resD2/neq_); } else { resNorm = sqrt(res2 / neq_); } return resNorm; } //==================================================================================================================== // Here we print out the residual at various points along the double dogleg, comparing against the quadratic model // in a table format /* * @param time_curr INPUT current time * @param ydot0 INPUT Current value of the derivative of the solution vector for non-time dependent * determinations * @param legBest OUTPUT leg of the dogleg that gives the lowest residual * @param alphaBest OUTPUT distance along dogleg for best result. */ void NonlinearSolver::residualComparisonLeg(const doublereal time_curr, const doublereal* const ydot0, int& legBest, doublereal& alphaBest) const { doublereal* y1 = DATA_PTR(m_wksp); doublereal* ydot1 = DATA_PTR(m_wksp_2); doublereal sLen; doublereal alpha; doublereal residSteepBest = 1.0E300; doublereal residSteepLinBest = 0.0; if (s_print_DogLeg || (doDogLeg_ && m_print_flag > 6)) { printf("\t\t residualComparisonLeg() \n"); printf("\t\t Point StepLen Residual_Actual Residual_Linear RelativeMatch\n"); } // First compare at 1/4 along SD curve std::vector alphaT; alphaT.push_back(0.00); alphaT.push_back(0.01); alphaT.push_back(0.1); alphaT.push_back(0.25); alphaT.push_back(0.50); alphaT.push_back(0.75); alphaT.push_back(1.0); for (size_t iteration = 0; iteration < alphaT.size(); iteration++) { alpha = alphaT[iteration]; for (size_t i = 0; i < neq_; i++) { y1[i] = m_y_n_curr[i] + alpha * deltaX_CP_[i]; } if (solnType_ != NSOLN_TYPE_STEADY_STATE) { calc_ydot(m_order, y1, ydot1); } sLen = alpha * solnErrorNorm(DATA_PTR(deltaX_CP_)); /* * Calculate the residual that would result if y1[] were the new solution vector * -> m_resid[] contains the result of the residual calculation */ if (solnType_ != NSOLN_TYPE_STEADY_STATE) { doResidualCalc(time_curr, solnType_, y1, ydot1, Base_LaggedSolutionComponents); } else { doResidualCalc(time_curr, solnType_, y1, ydot0, Base_LaggedSolutionComponents); } doublereal residSteep = residErrorNorm(DATA_PTR(m_resid)); doublereal residSteepLin = expectedResidLeg(0, alpha); if (residSteep < residSteepBest) { legBest = 0; alphaBest = alpha; residSteepBest = residSteep; residSteepLinBest = residSteepLin; } doublereal relFit = (residSteep - residSteepLin) / (fabs(residSteepLin) + 1.0E-10); if (s_print_DogLeg || (doDogLeg_ && m_print_flag > 6)) { printf("\t\t (%2d - % 10.3g) % 15.8E % 15.8E % 15.8E % 15.8E\n", 0, alpha, sLen, residSteep, residSteepLin , relFit); } } for (size_t iteration = 0; iteration < alphaT.size(); iteration++) { doublereal alpha = alphaT[iteration]; for (size_t i = 0; i < neq_; i++) { y1[i] = m_y_n_curr[i] + (1.0 - alpha) * deltaX_CP_[i]; y1[i] += alpha * Nuu_ * deltaX_Newton_[i]; } if (solnType_ != NSOLN_TYPE_STEADY_STATE) { calc_ydot(m_order, y1, ydot1); } /* * Calculate the residual that would result if y1[] were the new solution vector * -> m_resid[] contains the result of the residual calculation */ if (solnType_ != NSOLN_TYPE_STEADY_STATE) { doResidualCalc(time_curr, solnType_, y1, ydot1, Base_LaggedSolutionComponents); } else { doResidualCalc(time_curr, solnType_, y1, ydot0, Base_LaggedSolutionComponents); } for (size_t i = 0; i < neq_; i++) { y1[i] -= m_y_n_curr[i]; } sLen = solnErrorNorm(DATA_PTR(y1)); doublereal residSteep = residErrorNorm(DATA_PTR(m_resid)); doublereal residSteepLin = expectedResidLeg(1, alpha); if (residSteep < residSteepBest) { legBest = 1; alphaBest = alpha; residSteepBest = residSteep; residSteepLinBest = residSteepLin; } doublereal relFit = (residSteep - residSteepLin) / (fabs(residSteepLin) + 1.0E-10); if (s_print_DogLeg || (doDogLeg_ && m_print_flag > 6)) { printf("\t\t (%2d - % 10.3g) % 15.8E % 15.8E % 15.8E % 15.8E\n", 1, alpha, sLen, residSteep, residSteepLin , relFit); } } for (size_t iteration = 0; iteration < alphaT.size(); iteration++) { doublereal alpha = alphaT[iteration]; for (size_t i = 0; i < neq_; i++) { y1[i] = m_y_n_curr[i] + (Nuu_ + alpha * (1.0 - Nuu_))* deltaX_Newton_[i]; } if (solnType_ != NSOLN_TYPE_STEADY_STATE) { calc_ydot(m_order, y1, ydot1); } sLen = (Nuu_ + alpha * (1.0 - Nuu_)) * solnErrorNorm(DATA_PTR(deltaX_Newton_)); /* * Calculate the residual that would result if y1[] were the new solution vector * -> m_resid[] contains the result of the residual calculation */ if (solnType_ != NSOLN_TYPE_STEADY_STATE) { doResidualCalc(time_curr, solnType_, y1, ydot1, Base_LaggedSolutionComponents); } else { doResidualCalc(time_curr, solnType_, y1, ydot0, Base_LaggedSolutionComponents); } doublereal residSteep = residErrorNorm(DATA_PTR(m_resid)); doublereal residSteepLin = expectedResidLeg(2, alpha); if (residSteep < residSteepBest) { legBest = 2; alphaBest = alpha; residSteepBest = residSteep; residSteepLinBest = residSteepLin; } doublereal relFit = (residSteep - residSteepLin) / (fabs(residSteepLin) + 1.0E-10); if (s_print_DogLeg || (doDogLeg_ && m_print_flag > 6)) { printf("\t\t (%2d - % 10.3g) % 15.8E % 15.8E % 15.8E % 15.8E\n", 2, alpha, sLen, residSteep, residSteepLin , relFit); } } if (s_print_DogLeg || (doDogLeg_ && m_print_flag > 6)) { printf("\t\t Best Result: \n"); doublereal relFit = (residSteepBest - residSteepLinBest) / (fabs(residSteepLinBest) + 1.0E-10); if (m_print_flag <= 6) { printf("\t\t Leg %2d alpha %5g: NonlinResid = %g LinResid = %g, relfit = %g\n", legBest, alphaBest, residSteepBest, residSteepLinBest, relFit); } else { if (legBest == 0) { sLen = alpha * solnErrorNorm(DATA_PTR(deltaX_CP_)); } else if (legBest == 1) { for (size_t i = 0; i < neq_; i++) { y1[i] = (1.0 - alphaBest) * deltaX_CP_[i]; y1[i] += alphaBest * Nuu_ * deltaX_Newton_[i]; } sLen = solnErrorNorm(DATA_PTR(y1)); } else { sLen = (Nuu_ + alpha * (1.0 - Nuu_)) * solnErrorNorm(DATA_PTR(deltaX_Newton_)); } printf("\t\t (%2d - % 10.3g) % 15.8E % 15.8E % 15.8E % 15.8E\n", legBest, alphaBest, sLen, residSteepBest, residSteepLinBest , relFit); } } } //==================================================================================================================== // Calculate the length of the current trust region in terms of the solution error norm /* * We carry out a norm of deltaX_trust_ first. Then, we multiply that value * by trustDelta_ */ doublereal NonlinearSolver::trustRegionLength() const { norm_deltaX_trust_ = solnErrorNorm(DATA_PTR(deltaX_trust_)); return (trustDelta_ * norm_deltaX_trust_); } //==================================================================================================================== void NonlinearSolver::setDefaultDeltaBoundsMagnitudes() { for (size_t i = 0; i < neq_; i++) { m_deltaStepMinimum[i] = 1000. * atolk_[i]; m_deltaStepMinimum[i] = MAX(m_deltaStepMinimum[i], 0.1 * fabs(m_y_n_curr[i])); } } //==================================================================================================================== void NonlinearSolver::adjustUpStepMinimums() { for (size_t i = 0; i < neq_; i++) { doublereal goodVal = deltaX_trust_[i] * trustDelta_; if (deltaX_trust_[i] * trustDelta_ > m_deltaStepMinimum[i]) { m_deltaStepMinimum[i] = 1.1 * goodVal; } } } //==================================================================================================================== void NonlinearSolver::setDeltaBoundsMagnitudes(const doublereal* const deltaStepMinimum) { for (size_t i = 0; i < neq_; i++) { m_deltaStepMinimum[i] = deltaStepMinimum[i]; } m_manualDeltaStepSet = 1; } //==================================================================================================================== /* * * Return the factor by which the undamped Newton step 'step0' * must be multiplied in order to keep the update within the bounds of an accurate jacobian. * * The idea behind these is that the Jacobian couldn't possibly be representative, if the * variable is changed by a lot. (true for nonlinear systems, false for linear systems) * Maximum increase in variable in any one newton iteration: * factor of 1.5 * Maximum decrease in variable in any one newton iteration: * factor of 2 * * @param y_n_curr Initial value of the solution vector * @param step_1 initial proposed step size * * @return returns the damping factor */ double NonlinearSolver::deltaBoundStep(const doublereal* const y_n_curr, const doublereal* const step_1) { size_t i_fbounds = 0; int ifbd = 0; int i_fbd = 0; doublereal UPFAC = 2.0; doublereal sameSign = 0.0; doublereal ff; doublereal f_delta_bounds = 1.0; doublereal ff_alt; for (size_t i = 0; i < neq_; i++) { doublereal y_new = y_n_curr[i] + step_1[i]; sameSign = y_new * y_n_curr[i]; /* * Now do a delta bounds * Increase variables by a factor of UPFAC only * decrease variables by a factor of 2 only */ ff = 1.0; if (sameSign >= 0.0) { if ((fabs(y_new) > UPFAC * fabs(y_n_curr[i])) && (fabs(y_new - y_n_curr[i]) > m_deltaStepMinimum[i])) { ff = (UPFAC - 1.0) * fabs(y_n_curr[i]/(y_new - y_n_curr[i])); ff_alt = fabs(m_deltaStepMinimum[i] / (y_new - y_n_curr[i])); ff = MAX(ff, ff_alt); ifbd = 1; } if ((fabs(2.0 * y_new) < fabs(y_n_curr[i])) && (fabs(y_new - y_n_curr[i]) > m_deltaStepMinimum[i])) { ff = y_n_curr[i]/(y_new - y_n_curr[i]) * (1.0 - 2.0)/2.0; ff_alt = fabs(m_deltaStepMinimum[i] / (y_new - y_n_curr[i])); ff = MAX(ff, ff_alt); ifbd = 0; } } else { /* * This handles the case where the value crosses the origin. * - First we don't let it cross the origin until its shrunk to the size of m_deltaStepMinimum[i] */ if (fabs(y_n_curr[i]) > m_deltaStepMinimum[i]) { ff = y_n_curr[i]/(y_new - y_n_curr[i]) * (1.0 - 2.0)/2.0; ff_alt = fabs(m_deltaStepMinimum[i] / (y_new - y_n_curr[i])); ff = MAX(ff, ff_alt); if (y_n_curr[i] >= 0.0) { ifbd = 0; } else { ifbd = 1; } } /* * Second when it does cross the origin, we make sure that its magnitude is only 50% of the previous value. */ else if (fabs(y_new) > 0.5 * fabs(y_n_curr[i])) { ff = y_n_curr[i]/(y_new - y_n_curr[i]) * (-1.5); ff_alt = fabs(m_deltaStepMinimum[i] / (y_new - y_n_curr[i])); ff = MAX(ff, ff_alt); ifbd = 0; } } if (ff < f_delta_bounds) { f_delta_bounds = ff; i_fbounds = i; i_fbd = ifbd; } } /* * Report on any corrections */ if (m_print_flag >= 3) { if (f_delta_bounds < 1.0) { if (i_fbd) { printf("\t\tdeltaBoundStep: Increase of Variable %d causing " "delta damping of %g: origVal = %10.3g, undampedNew = %10.3g, dampedNew = %10.3g\n", i_fbounds, f_delta_bounds, y_n_curr[i_fbounds], y_n_curr[i_fbounds] + step_1[i_fbounds], y_n_curr[i_fbounds] + f_delta_bounds * step_1[i_fbounds]); } else { printf("\t\tdeltaBoundStep: Decrease of variable %d causing" "delta damping of %g: origVal = %10.3g, undampedNew = %10.3g, dampedNew = %10.3g\n", i_fbounds, f_delta_bounds, y_n_curr[i_fbounds], y_n_curr[i_fbounds] + step_1[i_fbounds], y_n_curr[i_fbounds] + f_delta_bounds * step_1[i_fbounds]); } } } return f_delta_bounds; } //==================================================================================================================== // Readjust the trust region vectors /* * The trust region is made up of the trust region vector calculation and the trustDelta_ value * We periodically recalculate the trustVector_ values so that they renormalize to the * correct length. */ void NonlinearSolver::readjustTrustVector() { doublereal trustDeltaOld = trustDelta_; doublereal wtSum = 0.0; for (size_t i = 0; i < neq_; i++) { wtSum += m_ewt[i]; } wtSum /= neq_; doublereal trustNorm = solnErrorNorm(DATA_PTR(deltaX_trust_)); doublereal deltaXSizeOld = trustNorm; doublereal trustNormGoal = trustNorm * trustDelta_; // This is the size of each component. // doublereal trustDeltaEach = trustDelta_ * trustNorm / neq_; doublereal oldVal; doublereal fabsy; // we use the old value of the trust region as an indicator for (size_t i = 0; i < neq_; i++) { oldVal = deltaX_trust_[i]; fabsy = fabs(m_y_n_curr[i]); // First off make sure that each trust region vector is 1/2 the size of each variable or smaller // unless overridden by the deltaStepMininum value. // doublereal newValue = trustDeltaEach * m_ewt[i] / wtSum; doublereal newValue = trustNormGoal * m_ewt[i]; if (newValue > 0.5 * fabsy) { if (fabsy * 0.5 > m_deltaStepMinimum[i]) { deltaX_trust_[i] = 0.5 * fabsy; } else { deltaX_trust_[i] = m_deltaStepMinimum[i]; } } else { if (newValue > 4.0 * oldVal) { newValue = 4.0 * oldVal; } else if (newValue < 0.25 * oldVal) { newValue = 0.25 * oldVal; if (deltaX_trust_[i] < m_deltaStepMinimum[i]) { newValue = m_deltaStepMinimum[i]; } } deltaX_trust_[i] = newValue; if (deltaX_trust_[i] > 0.75 * m_deltaStepMaximum[i]) { deltaX_trust_[i] = 0.75 * m_deltaStepMaximum[i]; } } } // Final renormalization. norm_deltaX_trust_ = solnErrorNorm(DATA_PTR(deltaX_trust_)); doublereal sum = trustNormGoal / trustNorm; for (size_t i = 0; i < neq_; i++) { deltaX_trust_[i] = deltaX_trust_[i] * sum; } norm_deltaX_trust_ = solnErrorNorm(DATA_PTR(deltaX_trust_)); trustDelta_ = trustNormGoal / norm_deltaX_trust_; if (doDogLeg_ && m_print_flag >= 4) { printf("\t\t reajustTrustVector(): Trust size = %11.3E: Old deltaX size = %11.3E trustDelta_ = %11.3E\n" "\t\t new deltaX size = %11.3E trustdelta_ = %11.3E\n", trustNormGoal, deltaXSizeOld, trustDeltaOld, norm_deltaX_trust_, trustDelta_); } } //==================================================================================================================== //! Initialize the size of the trust vector. /*! * The algorithm we use is to set it equal to the length of the Distance to the Cauchy point. */ void NonlinearSolver::initializeTrustRegion() { if (trustRegionInitializationMethod_ == 0) { return; } if (trustRegionInitializationMethod_ == 1) { for (size_t i = 0; i < neq_; i++) { deltaX_trust_[i] = m_ewt[i] * trustRegionInitializationFactor_; } trustDelta_ = 1.0; } if (trustRegionInitializationMethod_ == 2) { for (size_t i = 0; i < neq_; i++) { deltaX_trust_[i] = m_ewt[i] * m_normDeltaSoln_CP * trustRegionInitializationFactor_; } doublereal cpd = calcTrustDistance(deltaX_CP_); if ((doDogLeg_ && m_print_flag >= 4)) { printf("\t\t initializeTrustRegion(): Relative Distance of Cauchy Vector wrt Trust Vector = %g\n", cpd); } trustDelta_ = trustDelta_ * cpd * trustRegionInitializationFactor_; readjustTrustVector(); cpd = calcTrustDistance(deltaX_CP_); if ((doDogLeg_ && m_print_flag >= 4)) { printf("\t\t initializeTrustRegion(): Relative Distance of Cauchy Vector wrt Trust Vector = %g\n", cpd); } } if (trustRegionInitializationMethod_ == 3) { for (size_t i = 0; i < neq_; i++) { deltaX_trust_[i] = m_ewt[i] * m_normDeltaSoln_Newton * trustRegionInitializationFactor_; } doublereal cpd = calcTrustDistance(deltaX_Newton_); if ((doDogLeg_ && m_print_flag >= 4)) { printf("\t\t initializeTrustRegion(): Relative Distance of Newton Vector wrt Trust Vector = %g\n", cpd); } trustDelta_ = trustDelta_ * cpd; readjustTrustVector(); cpd = calcTrustDistance(deltaX_Newton_); if ((doDogLeg_ && m_print_flag >= 4)) { printf("\t\t initializeTrustRegion(): Relative Distance of Newton Vector wrt Trust Vector = %g\n", cpd); } } } //==================================================================================================================== // Fill a dogleg solution step vector /* * Previously, we have filled up deltaX_Newton_[], deltaX_CP_[], and Nuu_, so that * this routine is straightforward. * * @param leg Leg of the dog leg you are on (0, 1, or 2) * @param alpha Relative length along the dog length that you are on. * @param deltaX Vector to be filled up */ void NonlinearSolver::fillDogLegStep(int leg, doublereal alpha, std::vector & deltaX) const { if (leg == 0) { for (size_t i = 0; i < neq_; i++) { deltaX[i] = alpha * deltaX_CP_[i]; } } else if (leg == 2) { for (size_t i = 0; i < neq_; i++) { deltaX[i] = (alpha + (1.0 - alpha) * Nuu_) * deltaX_Newton_[i]; } } else { for (size_t i = 0; i < neq_; i++) { deltaX[i] = deltaX_CP_[i] * (1.0 - alpha) + alpha * Nuu_ * deltaX_Newton_[i]; } } } //==================================================================================================================== // Calculate the trust distance of a step in the solution variables /* * The trust distance is defined as the length of the step according to the norm wrt to the trust region. * We calculate the trust distance by the following method * * trustDist = || delta_x dot 1/trustDeltaX_ || / trustDelta_ * * @param deltaX Current value of deltaX */ doublereal NonlinearSolver::calcTrustDistance(std::vector const& deltaX) const { doublereal sum = 0.0; doublereal tmp = 0.0; for (size_t i = 0; i < neq_; i++) { tmp = deltaX[i] / deltaX_trust_[i]; sum += tmp * tmp; } sum = sqrt(sum / neq_) / trustDelta_; return sum; } //==================================================================================================================== // Given a trust distance, this routine calculates the intersection of the this distance with the // double dogleg curve /* * @param trustDelta (INPUT) Value of the trust distance * @param lambda (OUTPUT) Returns the internal coordinate of the double dogleg * @param alpha (OUTPUT) Returns the relative distance along the appropriate leg * @return leg (OUTPUT) Returns the leg ID (0, 1, or 2) */ int NonlinearSolver::calcTrustIntersection(doublereal trustDelta, doublereal& lambda, doublereal& alpha) const { doublereal dist; if (normTrust_Newton_ < trustDelta) { lambda = 1.0; alpha = 1.0; return 2; } if (normTrust_Newton_ * Nuu_ < trustDelta) { alpha = (trustDelta - normTrust_Newton_ * Nuu_) / (normTrust_Newton_ - normTrust_Newton_ * Nuu_); dist = dist_R0_ + dist_R1_ + alpha * dist_R2_; lambda = dist / dist_Total_; return 2; } if (normTrust_CP_ > trustDelta) { lambda = 1.0; dist = dist_R0_ * trustDelta / normTrust_CP_; lambda = dist / dist_Total_; alpha = trustDelta / normTrust_CP_; return 0; } doublereal sumv = 0.0; for (size_t i = 0; i < neq_; i++) { sumv += (deltaX_Newton_[i] / deltaX_trust_[i]) * (deltaX_CP_[i] / deltaX_trust_[i]); } doublereal a = normTrust_Newton_ * normTrust_Newton_ * Nuu_ * Nuu_; doublereal b = 2.0 * Nuu_ * sumv; doublereal c = normTrust_CP_ * normTrust_CP_ - trustDelta * trustDelta; alpha =(-b + sqrt(b * b - 4.0 * a * c)) / (2.0 * a); dist = dist_R0_ + alpha * dist_R1_; lambda = dist / dist_Total_; return 1; } //==================================================================================================================== /* * * boundStep(): * * 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. * Other bounds may be applied here as well. * * Currently the bounds are hard coded into this routine: * * Minimum value for all variables: - 0.01 * m_ewt[i] * Maximum value = none. * * Thus, this means that all solution components are expected * to be numerical greater than zero in the limit of time step * truncation errors going to zero. * * Delta bounds: The idea behind these is that the Jacobian * couldn't possibly be representative if the * variable is changed by a lot. (true for * nonlinear systems, false for linear systems) * Maximum increase in variable in any one newton iteration: * factor of 2 * Maximum decrease in variable in any one newton iteration: * factor of 5 */ doublereal NonlinearSolver::boundStep(const doublereal* const y, const doublereal* const step0) { size_t i_lower = npos; doublereal fbound = 1.0, f_bounds = 1.0; doublereal ff, y_new; for (size_t i = 0; i < neq_; i++) { y_new = y[i] + step0[i]; /* * Force the step to only take 80% a step towards the lower bounds */ if (step0[i] < 0.0) { if (y_new < (y[i] + 0.8 * (m_y_low_bounds[i] - y[i]))) { doublereal legalDelta = 0.8*(m_y_low_bounds[i] - y[i]); ff = legalDelta / step0[i]; if (ff < f_bounds) { f_bounds = ff; i_lower = i; } } } /* * Force the step to only take 80% a step towards the high bounds */ if (step0[i] > 0.0) { if (y_new > (y[i] + 0.8 * (m_y_high_bounds[i] - y[i]))) { doublereal legalDelta = 0.8*(m_y_high_bounds[i] - y[i]); ff = legalDelta / step0[i]; if (ff < f_bounds) { f_bounds = ff; i_lower = i; } } } } /* * Report on any corrections */ if (m_print_flag >= 3) { if (f_bounds != 1.0) { printf("\t\tboundStep: Variable %d causing bounds damping of %g\n", i_lower, f_bounds); } } doublereal f_delta_bounds = deltaBoundStep(y, step0); fbound = MIN(f_bounds, f_delta_bounds); return fbound; } //=================================================================================================================== // Find a damping coefficient through a look-ahead mechanism /* * * On entry, step0 must contain an undamped Newton step to the * current solution y0. 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 y1, and the undamped step at y1 is * returned in step1. * * * @return 1 Successful step was taken: Next step was less than previous step. * s1 is calculated * 2 Successful step: Next step's norm is less than 0.8 * 3 Success: The final residual is less than 1.0 * A predicted deltaSoln1 is not produced however. s1 is estimated. * 4 Success: The final residual is less than the residual * from the previous step. * A predicted deltaSoln1 is not produced however. s1 is estimated. * 0 Uncertain Success: s1 is about the same as s0 * NSOLN_RETN_FAIL_DAMPSTEP * Unsuccessful step. We can not find a damping factor that is suitable. */ int NonlinearSolver::dampStep(const doublereal time_curr, const doublereal* const y_n_curr, const doublereal* const ydot_n_curr, doublereal* const step_1, doublereal* const y_n_1, doublereal* const ydot_n_1, doublereal* const step_2, doublereal& stepNorm_2, GeneralMatrix& jac, bool writetitle, int& num_backtracks) { int m; int info = 0; int retnTrial = NSOLN_RETN_FAIL_DAMPSTEP; // Compute the weighted norm of the undamped step size step_1 doublereal stepNorm_1 = solnErrorNorm(step_1); doublereal* step_1_orig = DATA_PTR(m_wksp); for (size_t j = 0; j < neq_; j++) { step_1_orig[j] = step_1[j]; } // Compute the multiplier to keep all components in bounds.A value of one indicates that there is no limitation // on the current step size in the nonlinear method due to bounds constraints (either negative values of delta // bounds constraints. m_dampBound = boundStep(y_n_curr, step_1); // If fbound is very small, then y0 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 (m_dampBound < 1.e-30) { if (m_print_flag > 1) { printf("\t\t\tdampStep(): At limits.\n"); } return -3; } //-------------------------------------------- // Attempt damped step //-------------------------------------------- // damping coefficient starts at 1.0 m_dampRes = 1.0; doublereal ff = m_dampBound; num_backtracks = 0; for (m = 0; m < NDAMP; m++) { ff = m_dampBound * m_dampRes; // step the solution by the damped step size /* * Whenever we update the solution, we must also always * update the time derivative. */ for (size_t j = 0; j < neq_; j++) { step_1[j] = ff * step_1_orig[j]; y_n_1[j] = y_n_curr[j] + step_1[j]; } if (solnType_ != NSOLN_TYPE_STEADY_STATE) { calc_ydot(m_order, y_n_1, ydot_n_1); } /* * Calculate the residual that would result if y1[] were the new solution vector * -> m_resid[] contains the result of the residual calculation */ if (solnType_ != NSOLN_TYPE_STEADY_STATE) { info = doResidualCalc(time_curr, solnType_, y_n_1, ydot_n_1, Base_LaggedSolutionComponents); } else { info = doResidualCalc(time_curr, solnType_, y_n_1, ydot_n_curr, Base_LaggedSolutionComponents); } if (info != 1) { if (m_print_flag > 0) { printf("\t\t\tdampStep(): current trial step and damping led to Residual Calc ERROR %d. Bailing\n", info); } return -1; } m_normResidTrial = residErrorNorm(DATA_PTR(m_resid)); m_normResid_1 = m_normResidTrial; if (m == 0) { m_normResid_Bound = m_normResidTrial; } bool steepEnough = (m_normResidTrial < m_normResid_0 * (0.9 * (1.0 - ff) * (1.0 - ff)* (1.0 - ff) + 0.1)); if (m_normResidTrial < 1.0 || steepEnough) { if (m_print_flag >= 5) { if (m_normResidTrial < 1.0) { printf("\t dampStep(): Current trial step and damping" " coefficient accepted because residTrial test step < 1:\n"); printf("\t resid0 = %g, residTrial = %g\n", m_normResid_0, m_normResidTrial); } else if (steepEnough) { printf("\t dampStep(): Current trial step and damping" " coefficient accepted because resid0 > residTrial and steep enough:\n"); printf("\t resid0 = %g, residTrial = %g\n", m_normResid_0, m_normResidTrial); } else { printf("\t dampStep(): Current trial step and damping" " coefficient accepted because residual solution damping is turned off:\n"); printf("\t resid0 = %g, residTrial = %g\n", m_normResid_0, m_normResidTrial); } } /* * We aren't going to solve the system if we don't need to. Therefore, return an estimate * of the next solution update based on the ratio of the residual reduction. */ if (m_normResid_0 > 0.0) { stepNorm_2 = stepNorm_1 * m_normResidTrial / m_normResid_0; } else { stepNorm_2 = 0; } if (m_normResidTrial < 1.0) { retnTrial = 3; } else { retnTrial = 4; } break; } // Compute the next undamped step, step1[], that would result if y1[] were accepted. // We now have two steps that we have calculated step0[] and step1[] if (solnType_ != NSOLN_TYPE_STEADY_STATE) { info = doNewtonSolve(time_curr, y_n_1, ydot_n_1, step_2, jac); } else { info = doNewtonSolve(time_curr, y_n_1, ydot_n_curr, step_2, jac); } if (info) { if (m_print_flag > 0) { printf("\t\t\tdampStep: current trial step and damping led to LAPACK ERROR %d. Bailing\n", info); } return -1; } // compute the weighted norm of step1 stepNorm_2 = solnErrorNorm(step_2); // write log information if (m_print_flag >= 5) { print_solnDelta_norm_contrib((const doublereal*) step_1_orig, "DeltaSoln", (const doublereal*) step_2, "DeltaSolnTrial", "dampNewt: Important Entries for Weighted Soln Updates:", y_n_curr, y_n_1, ff, 5); } if (m_print_flag >= 4) { printf("\t\t\tdampStep(): s1 = %g, s2 = %g, dampBound = %g," "dampRes = %g\n", stepNorm_1, stepNorm_2, m_dampBound, m_dampRes); } // 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 (stepNorm_2 < 0.8 || stepNorm_2 < stepNorm_1) { if (stepNorm_2 < 1.0) { if (m_print_flag >= 3) { if (stepNorm_2 < 1.0) { printf("\t\t\tdampStep: current trial step and damping coefficient accepted because test step < 1\n"); printf("\t\t\t s2 = %g, s1 = %g\n", stepNorm_2, stepNorm_1); } } retnTrial = 2; } else { retnTrial = 1; } break; } else { if (m_print_flag > 1) { printf("\t\t\tdampStep: current step rejected: (s1 = %g > " "s0 = %g)", stepNorm_2, stepNorm_1); if (m < (NDAMP-1)) { printf(" Decreasing damping factor and retrying"); } else { printf(" Giving up!!!"); } printf("\n"); } } num_backtracks++; m_dampRes /= 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 NSOLN_RETN_FAIL_DAMPSTEP. if (m < NDAMP) { if (m_print_flag >= 4) { printf("\t dampStep(): current trial step accepted retnTrial = %d, its = %d, damp = %g\n", retnTrial, m+1, ff); } return retnTrial; } else { if (stepNorm_2 < 0.5 && (stepNorm_1 < 0.5)) { if (m_print_flag >= 4) { printf("\t dampStep(): current trial step accepted kindof retnTrial = %d, its = %d, damp = %g\n", 2, m+1, ff); } return 2; } if (stepNorm_2 < 1.0) { if (m_print_flag >= 4) { printf("\t dampStep(): current trial step accepted and soln converged retnTrial =" "%d, its = %d, damp = %g\n", 0, m+1, ff); } return 0; } } if (m_print_flag >= 4) { printf("\t dampStep(): current direction is rejected! retnTrial = %d, its = %d, damp = %g\n", NSOLN_RETN_FAIL_DAMPSTEP, m+1, ff); } return NSOLN_RETN_FAIL_DAMPSTEP; } //==================================================================================================================== // Damp using the dog leg approach /* * * @param time_curr INPUT Current value of the time * @param y_n_curr INPUT Current value of the solution vector * @param ydot_n_curr INPUT Current value of the derivative of the solution vector * @param step_1 INPUT First trial step for the first iteration * @param y_n_1 INPUT First trial value of the solution vector * @param ydot_n_1 INPUT First trial value of the derivative of the solution vector * @param s1 OUTPUT Norm of the vector step_1 * @param jac INPUT jacobian * @param numTrials OUTPUT number of trials taken in the current damping step * * * @return 1 Success: Good step was taken. The predicted residual norm is less than one * 2 Success: Good step: Next step's norm is less than 0.8 * 3 Success: The final residual is less than 1.0 * A predicted deltaSoln1 is not produced however. s1 is estimated. * 4 Success: The final residual is less than the residual from the previous step. * A predicted deltaSoln1 is not produced however. s1 is estimated. * 0 Unknown Uncertain Success: s1 is about the same as s0 * NSOLN_RETN_FAIL_DAMPSTEP * Unsuccessful step. Can not find a damping coefficient that is suitable */ int NonlinearSolver::dampDogLeg(const doublereal time_curr, const doublereal* y_n_curr, const doublereal* ydot_n_curr, std::vector & step_1, doublereal* const y_n_1, doublereal* const ydot_n_1, doublereal& stepNorm_1, doublereal& stepNorm_2, GeneralMatrix& jac, int& numTrials) { doublereal lambda; int info; bool success = false; bool haveASuccess = false; doublereal trustDeltaOld = trustDelta_; doublereal* stepLastGood = DATA_PTR(m_wksp); //-------------------------------------------- // Attempt damped step //-------------------------------------------- // damping coefficient starts at 1.0 m_dampRes = 1.0; int m; doublereal tlen; for (m = 0; m < NDAMP; m++) { numTrials++; /* * Find the initial value of lambda that satisfies the trust distance, trustDelta_ */ dogLegID_ = calcTrustIntersection(trustDelta_, lambda, dogLegAlpha_); if (m_print_flag >= 4) { tlen = trustRegionLength(); printf("\t\t dampDogLeg: trust region with length %13.5E has intersection at leg = %d, alpha = %g, lambda = %g\n", tlen, dogLegID_, dogLegAlpha_, lambda); } /* * Figure out the new step vector, step_1, based on (leg, alpha). Here we are using the * intersection of the trust oval with the dog-leg curve. */ fillDogLegStep(dogLegID_, dogLegAlpha_, step_1); /* * OK, now that we have step0, Bound the step */ m_dampBound = boundStep(y_n_curr, DATA_PTR(step_1)); /* * Decrease the step length if we are bound */ if (m_dampBound < 1.0) { for (size_t j = 0; j < neq_; j++) { step_1[j] = step_1[j] * m_dampBound; } } /* * Calculate the new solution value y1[] given the step size */ for (size_t j = 0; j < neq_; j++) { y_n_1[j] = y_n_curr[j] + step_1[j]; } /* * Calculate the new solution time derivative given the step size */ if (solnType_ != NSOLN_TYPE_STEADY_STATE) { calc_ydot(m_order, y_n_1, ydot_n_1); } /* * OK, we have the step0. Now, ask the question whether it satisfies the acceptance criteria * as a good step. The overall outcome is returned in the variable info. */ info = decideStep(time_curr, dogLegID_, dogLegAlpha_, y_n_curr, ydot_n_curr, step_1, y_n_1, ydot_n_1, trustDeltaOld); m_normResid_Bound = m_normResid_1; /* * The algorithm failed to find a solution vector sufficiently different than the current point */ if (info == -1) { if (m_print_flag >= 1) { doublereal stepNorm = solnErrorNorm(DATA_PTR(step_1)); printf("\t\t dampDogLeg: Current direction rejected, update became too small %g\n", stepNorm); success = false; break; } } if (info == -2) { if (m_print_flag >= 1) { printf("\t\t dampDogLeg: current trial step and damping led to LAPACK ERROR %d. Bailing\n", info); success = false; break; } } if (info == 0) { success = true; break; } if (info == 3) { haveASuccess = true; // Store the good results in stepLastGood mdp::mdp_copy_dbl_1(DATA_PTR(stepLastGood), CONSTD_DATA_PTR(step_1), (int) neq_); // Within the program decideStep(), we have already increased the value of trustDelta_. We store the // value of step0 in step1, recalculate a larger step0 in the next fillDogLegStep(), // and then attempt to see if the larger step works in the next iteration } if (info == 2) { // Step was a failure. If we had a previous success with a smaller stepsize, haveASuccess is true // and we execute the next block and break. If we didn't have a previous success, trustDelta_ has // already been decreased in the decideStep() routine. We go back and try another iteration with // a smaller trust region. if (haveASuccess) { mdp::mdp_copy_dbl_1(DATA_PTR(step_1), CONSTD_DATA_PTR(stepLastGood), (int) neq_); for (size_t j = 0; j < neq_; j++) { y_n_1[j] = y_n_curr[j] + step_1[j]; } if (solnType_ != NSOLN_TYPE_STEADY_STATE) { calc_ydot(m_order, y_n_1, ydot_n_1); } success = true; break; } else { } } } /* * Estimate s1, the norm after the next step */ stepNorm_1 = solnErrorNorm(DATA_PTR(step_1)); stepNorm_2 = stepNorm_1; if (m_dampBound < 1.0) { stepNorm_2 /= m_dampBound; } stepNorm_2 /= lambda; stepNorm_2 *= m_normResidTrial / m_normResid_0; if (success) { if (m_normResidTrial < 1.0) { if (normTrust_Newton_ < trustDelta_ && m_dampBound == 1.0) { return 1; } else { return 0; } } return 0; } return NSOLN_RETN_FAIL_DAMPSTEP; } //==================================================================================================================== // Decide whether the current step is acceptable and adjust the trust region size /* * This is an extension of algorithm 6.4.5 of Dennis and Schnabel. * * Here we decide whether to accept the current step * At the end of the calculation a new estimate of the trust region is calculated * * @param time_curr INPUT Current value of the time * @param leg INPUT Leg of the dogleg that we are on * @param alpha INPUT Distance down that leg that we are on * @param y0 INPUT Current value of the solution vector * @param ydot0 INPUT Current value of the derivative of the solution vector * @param step0 INPUT Trial step * @param y1 OUTPUT Solution values at the conditions which are evalulated for success * @param ydot1 OUTPUT Time derivates of solution at the conditions which are evalulated for success * @param trustDeltaOld INPUT Value of the trust length at the old conditions * * * @return This function returns a code which indicates whether the step will be accepted or not. * 3 Step passed with flying colors. Try redoing the calculation with a bigger trust region. * 2 Step didn't pass deltaF requirement. Decrease the size of the next trust region for a retry and return * 0 The step passed. * -1 The step size is now too small (||d || < 0.1). A really small step isn't decreasing the function. * This is an error condition. * -2 Current value of the solution vector caused a residual error in its evaluation. * Step is a failure, and the step size must be reduced in order to proceed further. */ int NonlinearSolver::decideStep(const doublereal time_curr, int leg, doublereal alpha, const doublereal* const y_n_curr, const doublereal* const ydot_n_curr, const std::vector & step_1, const doublereal* const y_n_1, const doublereal* const ydot_n_1, doublereal trustDeltaOld) { int retn = 2; int info; doublereal ll; // Calculate the solution step length doublereal stepNorm = solnErrorNorm(DATA_PTR(step_1)); // Calculate the initial (R**2 * neq) value for the old function doublereal normResid0_2 = m_normResid_0 * m_normResid_0 * neq_; // Calculate the distance to the cauchy point doublereal cauchyDistanceNorm = solnErrorNorm(DATA_PTR(deltaX_CP_)); // This is the expected inital rate of decrease in the cauchy direction. // -> This is Eqn. 29 = Rhat dot Jhat dy / || d || doublereal funcDecreaseSDExp = RJd_norm_ / cauchyDistanceNorm * lambdaStar_; if (funcDecreaseSDExp > 0.0) { if (m_print_flag >= 5) { printf("\t\tdecideStep(): Unexpected condition -> cauchy slope is positive\n"); } } /* * Calculate the residual that would result if y1[] were the new solution vector. * The Lagged solution components are kept lagged here. Unfortunately, it just doesn't work in some cases to use a * Jacobian from a lagged state and then use a residual from an unlagged condition. The linear model doesn't * agree with the nonlinear model. * -> m_resid[] contains the result of the residual calculation */ if (solnType_ != NSOLN_TYPE_STEADY_STATE) { info = doResidualCalc(time_curr, solnType_, y_n_1, ydot_n_1, Base_LaggedSolutionComponents); } else { info = doResidualCalc(time_curr, solnType_, y_n_1, ydot_n_curr, Base_LaggedSolutionComponents); } if (info != 1) { if (m_print_flag >= 2) { printf("\t\tdecideStep: current trial step and damping led to Residual Calc ERROR %d. Bailing\n", info); } return -2; } /* * Ok we have a successful new residual. Calculate the normalized residual value and store it in * m_normResidTrial */ m_normResidTrial = residErrorNorm(DATA_PTR(m_resid)); doublereal normResidTrial_2 = neq_ * m_normResidTrial * m_normResidTrial; /* * We have a minimal acceptance test for passage. deltaf < 1.0E-4 (CauchySlope) (deltS) * This is the condition that D&S use in 6.4.5 */ doublereal funcDecrease = 0.5 * (normResidTrial_2 - normResid0_2); doublereal acceptableDelF = funcDecreaseSDExp * stepNorm * 1.0E-4; if (funcDecrease < acceptableDelF) { m_normResid_1 = m_normResidTrial; m_normResid_1 = m_normResidTrial; retn = 0; if (m_print_flag >= 4) { printf("\t\t decideStep: Norm Residual(leg=%1d, alpha=%10.2E) = %11.4E passes\n", dogLegID_, dogLegAlpha_, m_normResidTrial); } } else { if (m_print_flag >= 4) { printf("\t\t decideStep: Norm Residual(leg=%1d, alpha=%10.2E) = %11.4E failes\n", dogLegID_, dogLegAlpha_, m_normResidTrial); } trustDelta_ *= 0.33; CurrentTrustFactor_ *= 0.33; retn = 2; // error condition if step is getting too small if (rtol_ * stepNorm < 1.0E-6) { retn = -1; } return retn; } /* * Figure out the next trust region. We are here iff retn = 0 * * If we had to bounds delta the update, decrease the trust region */ if (m_dampBound < 1.0) { // trustDelta_ *= 0.5; // NextTrustFactor_ *= 0.5; // ll = trustRegionLength(); // if (m_print_flag >= 5) { // printf("\t\tdecideStep(): Trust region decreased from %g to %g due to bounds constraint\n", ll*2, ll); //} } else { retn = 0; /* * Calculate the expected residual from the quadratic model */ doublereal expectedNormRes = expectedResidLeg(leg, alpha); doublereal expectedFuncDecrease = 0.5 * (neq_ * expectedNormRes * expectedNormRes - normResid0_2); if (funcDecrease > 0.1 * expectedFuncDecrease) { if ((m_normResidTrial > 0.5 * m_normResid_0) && (m_normResidTrial > 0.1)) { trustDelta_ *= 0.5; NextTrustFactor_ *= 0.5; ll = trustRegionLength(); if (m_print_flag >= 4) { printf("\t\t decideStep: Trust region decreased from %g to %g due to bad quad approximation\n", ll*2, ll); } } } else { /* * If we are doing well, consider increasing the trust region and recalculating */ if (funcDecrease < 0.8 * expectedFuncDecrease || (m_normResidTrial < 0.33 * m_normResid_0)) { if (trustDelta_ <= trustDeltaOld && (leg != 2 || alpha < 0.75)) { trustDelta_ *= 2.0; CurrentTrustFactor_ *= 2; adjustUpStepMinimums(); ll = trustRegionLength(); if (m_print_flag >= 4) { if (m_normResidTrial < 0.33 * m_normResid_0) { printf("\t\t decideStep: Redo line search with trust region increased from %g to %g due to good nonlinear behavior\n", ll*0.5, ll); } else { printf("\t\t decideStep: Redi line search with trust region increased from %g to %g due to good linear model approximation\n", ll*0.5, ll); } } retn = 3; } else { /* * Increase the size of the trust region for the next calculation */ if (m_normResidTrial < 0.99 * expectedNormRes || (m_normResidTrial < 0.20 * m_normResid_0) || (funcDecrease < -1.0E-50 && (funcDecrease < 0.9 *expectedFuncDecrease))) { if (leg == 2 && alpha == 1.0) { ll = trustRegionLength(); if (ll < 2.0 * m_normDeltaSoln_Newton) { trustDelta_ *= 2.0; NextTrustFactor_ *= 2.0; adjustUpStepMinimums(); ll = trustRegionLength(); if (m_print_flag >= 4) { printf("\t\t decideStep: Trust region further increased from %g to %g next step due to good linear model behavior\n", ll*0.5, ll); } } } else { ll = trustRegionLength(); trustDelta_ *= 2.0; NextTrustFactor_ *= 2.0; adjustUpStepMinimums(); ll = trustRegionLength(); if (m_print_flag >= 4) { printf("\t\t decideStep: Trust region further increased from %g to %g next step due to good linear model behavior\n", ll*0.5, ll); } } } } } } } return retn; } //==================================================================================================================== /* * solve_nonlinear_problem(): * * 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. * * SolnType = TRANSIENT -> we will assume we are relaxing a transient * equation system for now. Will make it more general later, * if an application comes up. * * @return A positive value indicates a successful convergence * -1 Failed convergence */ int NonlinearSolver::solve_nonlinear_problem(int SolnType, doublereal* const y_comm, doublereal* const ydot_comm, doublereal CJ, doublereal time_curr, GeneralMatrix& jac, int& num_newt_its, int& num_linear_solves, int& num_backtracks, int loglevelInput) { clockWC wc; int convRes = 0; solnType_ = SolnType; int info = 0; num_linear_solves -= m_numTotalLinearSolves; int retnDamp = 0; int retnCode = 0; bool forceNewJac = false; if (jacCopyPtr_) { delete jacCopyPtr_; } jacCopyPtr_ = jac.duplMyselfAsGeneralMatrix(); doublereal stepNorm_1; doublereal stepNorm_2; #ifdef DEBUG_MODE int legBest; doublereal alphaBest; #endif bool trInit = false; mdp::mdp_copy_dbl_1(DATA_PTR(m_y_n_curr), DATA_PTR(y_comm), (int) neq_); if (SolnType != NSOLN_TYPE_STEADY_STATE || ydot_comm) { mdp::mdp_copy_dbl_1(DATA_PTR(m_ydot_n_curr), ydot_comm, (int) neq_); mdp::mdp_copy_dbl_1(DATA_PTR(m_ydot_n_1), ydot_comm, (int) neq_); } // Redo the solution weights every time we enter the function createSolnWeights(DATA_PTR(m_y_n_curr)); m_normDeltaSoln_Newton = 1.0E1; bool frst = true; num_newt_its = 0; num_backtracks = 0; int i_numTrials; m_print_flag = loglevelInput; if (trustRegionInitializationMethod_ == 0) { trInit = true; } else if (trustRegionInitializationMethod_ == 1) { trInit = true; initializeTrustRegion(); } else { mdp::mdp_init_dbl_1(DATA_PTR(deltaX_trust_), 1.0, (int) neq_); trustDelta_ = 1.0; } if (m_print_flag == 2 || m_print_flag == 3) { printf("\tsolve_nonlinear_problem():\n\n"); if (doDogLeg_) { printf("\tWt Iter Resid NewJac log(CN)| dRdS_CDexp dRdS_CD dRdS_Newtexp dRdS_Newt |" "DS_Cauchy DS_Newton DS_Trust | legID legAlpha Fbound | CTF NTF | nTr|" "DS_Final ResidLag ResidFull\n"); printf("\t---------------------------------------------------------------------------------------------------" "--------------------------------------------------------------------------------\n"); } else { printf("\t Wt Iter Resid NewJac | Fbound ResidBound | DampIts Fdamp DS_Step1 DS_Step2" "ResidLag | DS_Damp DS_Newton ResidFull\n"); printf("\t--------------------------------------------------------------------------------------------------" "----------------------------------\n"); } } while (1 > 0) { CurrentTrustFactor_ = 1.0; NextTrustFactor_ = 1.0; ResidWtsReevaluated_ = false; i_numTrials = 0; /* * Increment Newton Solve counter */ m_numTotalNewtIts++; num_newt_its++; m_numLocalLinearSolves = 0; if (m_print_flag > 3) { printf("\t"); print_line("=", 119); printf("\tsolve_nonlinear_problem(): iteration %d:\n", num_newt_its); } /* * If we are far enough away from the solution, redo the solution weights and the trust vectors. */ if (m_normDeltaSoln_Newton > 1.0E2) { createSolnWeights(DATA_PTR(m_y_n_curr)); #ifdef DEBUG_MODE if (trInit) { readjustTrustVector(); } #else if (doDogLeg_ && trInit) { readjustTrustVector(); } #endif } else { // Do this stuff every 5 iterations if ((num_newt_its % 5) == 1) { createSolnWeights(DATA_PTR(m_y_n_curr)); #ifdef DEBUG_MODE if (trInit) { readjustTrustVector(); } #else if (doDogLeg_ && trInit) { readjustTrustVector(); } #endif } } /* * Set default values of Delta bounds constraints */ if (!m_manualDeltaStepSet) { setDefaultDeltaBoundsMagnitudes(); } // Check whether the Jacobian should be re-evaluated. forceNewJac = true; if (forceNewJac) { if (m_print_flag > 3) { printf("\t solve_nonlinear_problem(): Getting a new Jacobian\n"); } info = beuler_jac(jac, DATA_PTR(m_resid), time_curr, CJ, DATA_PTR(m_y_n_curr), DATA_PTR(m_ydot_n_curr), num_newt_its); if (info != 1) { if (m_print_flag > 0) { printf("\t solve_nonlinear_problem(): Jacobian Formation Error: %d Bailing\n", info); } retnDamp = NSOLN_RETN_JACOBIANFORMATIONERROR ; goto done; } } else { if (m_print_flag > 1) { printf("\t solve_nonlinear_problem(): Solving system with old jacobian\n"); } } /* * Go get new scales */ calcColumnScales(); /* * Calculate the base residual */ if (m_print_flag >= 6) { printf("\t solve_nonlinear_problem(): Calculate the base residual\n"); } info = doResidualCalc(time_curr, NSOLN_TYPE_STEADY_STATE, DATA_PTR(m_y_n_curr), DATA_PTR(m_ydot_n_curr)); if (info != 1) { if (m_print_flag > 0) { printf("\t solve_nonlinear_problem(): Residual Calc ERROR %d. Bailing\n", info); } retnDamp = NSOLN_RETN_RESIDUALFORMATIONERROR; goto done; } /* * Scale the matrix and the rhs, if they aren't already scaled * Figure out and store the residual scaling factors. */ scaleMatrix(jac, DATA_PTR(m_y_n_curr), DATA_PTR(m_ydot_n_curr), time_curr, num_newt_its); /* * Optional print out the initial residual */ if (m_print_flag >= 6) { m_normResid_0 = residErrorNorm(DATA_PTR(m_resid), "Initial norm of the residual", 10, DATA_PTR(m_y_n_curr)); } else { m_normResid_0 = residErrorNorm(DATA_PTR(m_resid), "Initial norm of the residual", 0, DATA_PTR(m_y_n_curr)); if (m_print_flag == 4 || m_print_flag == 5) { printf("\t solve_nonlinear_problem(): Initial Residual Norm = %13.4E\n", m_normResid_0); } } #ifdef DEBUG_MODE if (m_print_flag > 3) { printf("\t solve_nonlinear_problem(): Calculate the steepest descent direction and Cauchy Point\n"); } m_normDeltaSoln_CP = doCauchyPointSolve(jac); #else if (doDogLeg_) { if (m_print_flag > 3) { printf("\t solve_nonlinear_problem(): Calculate the steepest descent direction and Cauchy Point\n"); } m_normDeltaSoln_CP = doCauchyPointSolve(jac); } #endif // compute the undamped Newton step if (doAffineSolve_) { if (m_print_flag >= 4) { printf("\t solve_nonlinear_problem(): Calculate the Newton direction via an Affine solve\n"); } info = doAffineNewtonSolve(DATA_PTR(m_y_n_curr), DATA_PTR(m_ydot_n_curr), DATA_PTR(deltaX_Newton_), jac); } else { if (m_print_flag >= 4) { printf("\t solve_nonlinear_problem(): Calculate the Newton direction via a Newton solve\n"); } info = doNewtonSolve(time_curr, DATA_PTR(m_y_n_curr), DATA_PTR(m_ydot_n_curr), DATA_PTR(deltaX_Newton_), jac); } if (info) { retnDamp = NSOLN_RETN_MATRIXINVERSIONERROR; if (m_print_flag > 0) { printf("\t solve_nonlinear_problem(): Matrix Inversion Error: %d Bailing\n", info); } goto done; } mdp::mdp_copy_dbl_1(DATA_PTR(m_step_1), CONSTD_DATA_PTR(deltaX_Newton_), (int) neq_); if (m_print_flag >= 6) { m_normDeltaSoln_Newton = solnErrorNorm(DATA_PTR(deltaX_Newton_), "Initial Undamped Newton Step of the iteration", 10); } else { m_normDeltaSoln_Newton = solnErrorNorm(DATA_PTR(deltaX_Newton_), "Initial Undamped Newton Step of the iteration", 0); } if (m_numTotalNewtIts == 1) { if (trustRegionInitializationMethod_ == 2 || trustRegionInitializationMethod_ == 3) { if (m_print_flag > 3) { if (trustRegionInitializationMethod_ == 2) { printf("\t solve_nonlinear_problem(): Initialize the trust region size as the length of the Cauchy Vector times %f\n", trustRegionInitializationFactor_); } else { printf("\t solve_nonlinear_problem(): Initialize the trust region size as the length of the Newton Vector times %f\n", trustRegionInitializationFactor_); } } initializeTrustRegion(); trInit = true; } } if (doDogLeg_) { #ifdef DEBUG_MODE doublereal trustD = calcTrustDistance(m_step_1); if (m_print_flag >= 4) { if (trustD > trustDelta_) { printf("\t\t Newton's method step size, %g trustVectorUnits, larger than trust region, %g trustVectorUnits\n", trustD, trustDelta_); printf("\t\t Newton's method step size, %g trustVectorUnits, larger than trust region, %g trustVectorUnits\n", trustD, trustDelta_); } else { printf("\t\t Newton's method step size, %g trustVectorUnits, smaller than trust region, %g trustVectorUnits\n", trustD, trustDelta_); } } #endif } /* * Filter out bad directions */ filterNewStep(time_curr, DATA_PTR(m_y_n_curr), DATA_PTR(m_step_1)); if (s_print_DogLeg && m_print_flag >= 4) { printf("\t solve_nonlinear_problem(): Compare descent rates for Cauchy and Newton directions\n"); descentComparison(time_curr, DATA_PTR(m_ydot_n_curr), DATA_PTR(m_ydot_n_1), i_numTrials); } else { if (doDogLeg_) { descentComparison(time_curr, DATA_PTR(m_ydot_n_curr), DATA_PTR(m_ydot_n_1), i_numTrials); } } if (doDogLeg_) { setupDoubleDogleg(); #ifdef DEBUG_MODE if (s_print_DogLeg && m_print_flag >= 5) { printf("\t solve_nonlinear_problem(): Compare Linear and nonlinear residuals along double dog-leg path\n"); residualComparisonLeg(time_curr, DATA_PTR(m_ydot_n_curr), legBest, alphaBest); } #endif if (m_print_flag >= 4) { printf("\t solve_nonlinear_problem(): Calculate damping along dog-leg path to ensure residual decrease\n"); } retnDamp = dampDogLeg(time_curr, DATA_PTR(m_y_n_curr), DATA_PTR(m_ydot_n_curr), m_step_1, DATA_PTR(m_y_n_1), DATA_PTR(m_ydot_n_1), stepNorm_1, stepNorm_2, jac, i_numTrials); } #ifdef DEBUG_MODE else { if (s_print_DogLeg && m_print_flag >= 5) { printf("\t solve_nonlinear_problem(): Compare Linear and nonlinear residuals along double dog-leg path\n"); residualComparisonLeg(time_curr, DATA_PTR(m_ydot_n_curr), legBest, alphaBest); } } #endif // Damp the Newton step /* * On return the recommended new solution and derivatisve is located in: * y_new * y_dot_new * The update delta vector is located in * stp1 * The estimate of the solution update norm for the next step is located in * s1 */ if (!doDogLeg_) { retnDamp = dampStep(time_curr, DATA_PTR(m_y_n_curr), DATA_PTR(m_ydot_n_curr), DATA_PTR(m_step_1), DATA_PTR(m_y_n_1), DATA_PTR(m_ydot_n_1), DATA_PTR(m_wksp_2), stepNorm_2, jac, frst, i_numTrials); frst = false; num_backtracks += i_numTrials; stepNorm_1 = solnErrorNorm(DATA_PTR(m_step_1)); } /* * Impose the minimum number of newton iterations critera */ if (num_newt_its < m_min_newt_its) { if (retnDamp > NSOLN_RETN_CONTINUE) { if (m_print_flag > 2) { printf("\t solve_nonlinear_problem(): Damped Newton successful (m=%d) but minimum newton" "iterations not attained. Resolving ...\n", retnDamp); } retnDamp = NSOLN_RETN_CONTINUE; } } /* * Impose max newton iteration */ if (num_newt_its > maxNewtIts_) { retnDamp = NSOLN_RETN_MAXIMUMITERATIONSEXCEEDED; if (m_print_flag > 1) { printf("\t solve_nonlinear_problem(): Damped newton unsuccessful (max newts exceeded) sfinal = %g\n", stepNorm_1); } } /* * Do a full residual calculation with the unlagged solution components. * Then get the norm of the residual */ info = doResidualCalc(time_curr, NSOLN_TYPE_STEADY_STATE, DATA_PTR(m_y_n_1), DATA_PTR(m_ydot_n_1)); if (info != 1) { if (m_print_flag > 0) { printf("\t solve_nonlinear_problem(): current trial step and damping led to Residual Calc " "ERROR %d. Bailing\n", info); } retnDamp = NSOLN_RETN_RESIDUALFORMATIONERROR; goto done; } if (m_print_flag >= 4) { m_normResid_full = residErrorNorm(DATA_PTR(m_resid), " Resulting full residual norm", 10, DATA_PTR(m_y_n_1)); if (fabs(m_normResid_full - m_normResid_1) > 1.0E-3 * (m_normResid_1 + m_normResid_full + 1.0E-4)) { if (m_print_flag >= 4) { printf("\t solve_nonlinear_problem(): Full residual norm changed from %g to %g due to " "lagging of components\n", m_normResid_1, m_normResid_full); } } } else { m_normResid_full = residErrorNorm(DATA_PTR(m_resid)); } /* * Check the convergence criteria */ convRes = 0; if (retnDamp > NSOLN_RETN_CONTINUE) { convRes = convergenceCheck(retnDamp, stepNorm_1); } bool m_filterIntermediate = false; if (m_filterIntermediate) { if (retnDamp == NSOLN_RETN_CONTINUE) { (void) filterNewSolution(time_n, DATA_PTR(m_y_n_1), DATA_PTR(m_ydot_n_1)); } } // Exchange new for curr solutions if (retnDamp >= NSOLN_RETN_CONTINUE) { mdp::mdp_copy_dbl_1(DATA_PTR(m_y_n_curr), CONSTD_DATA_PTR(m_y_n_1), (int) neq_); if (solnType_ != NSOLN_TYPE_STEADY_STATE) { calc_ydot(m_order, DATA_PTR(m_y_n_curr), DATA_PTR(m_ydot_n_curr)); } } if (m_print_flag == 2 || m_print_flag == 3) { // printf("\t Iter Resid NewJac | Fbound | ResidBound | Fdamp DampIts | DeltaSolnNewton ResidFinal \n"); if (ResidWtsReevaluated_) { printf("\t*"); } else { printf("\t "); } printf(" %3d %11.3E", num_newt_its, m_normResid_0); bool m_jacAge = false; if (!m_jacAge) { printf(" Y "); } else { printf(" N "); } if (doDogLeg_) { printf("%5.1F |", log10(m_conditionNumber)); // printf("\t Iter Resid NewJac | DS_Cauchy DS_Newton DS_Trust | legID legAlpha Fbound | | DS_F ResidFinal \n"); printf("%10.3E %10.3E %10.3E %10.3E|", ResidDecreaseSDExp_, ResidDecreaseSD_, ResidDecreaseNewtExp_, ResidDecreaseNewt_); printf("%10.3E %10.3E %10.3E|", m_normDeltaSoln_CP , m_normDeltaSoln_Newton, norm_deltaX_trust_ * trustDelta_); printf("%2d %10.2E %10.2E", dogLegID_ , dogLegAlpha_, m_dampBound); printf("| %3.2f %3.2f |", CurrentTrustFactor_, NextTrustFactor_); printf(" %2d ", i_numTrials); printf("| %10.3E %10.3E %10.3E", stepNorm_1, m_normResid_1, m_normResid_full); } else { printf(" |"); printf("%10.2E %10.3E |", m_dampBound, m_normResid_Bound); printf("%2d %10.2E %10.3E %10.3E %10.3E", i_numTrials + 1, m_dampRes, stepNorm_1 / (m_dampRes * m_dampBound), stepNorm_2, m_normResid_1); printf("| %10.3E %10.3E %10.3E", stepNorm_1, m_normDeltaSoln_Newton, m_normResid_full); } printf("\n"); } if (m_print_flag >= 4) { if (doDogLeg_) { if (convRes > 0) { printf("\t solve_nonlinear_problem(): Problem Converged, stepNorm = %11.3E, reduction of res from %11.3E to %11.3E\n", stepNorm_1, m_normResid_0, m_normResid_full); printf("\t"); print_line("=", 119); } else { printf("\t solve_nonlinear_problem(): Successfull step taken with stepNorm = %11.3E, reduction of res from %11.3E to %11.3E\n", stepNorm_1, m_normResid_0, m_normResid_full); } } else { if (convRes > 0) { printf("\t solve_nonlinear_problem(): Damped Newton iteration successful, nonlin " "converged, final estimate of the next solution update norm = %-12.4E\n", stepNorm_2); printf("\t"); print_line("=", 119); } else if (retnDamp >= NSOLN_RETN_CONTINUE) { printf("\t solve_nonlinear_problem(): Damped Newton iteration successful, " "estimate of the next solution update norm = %-12.4E\n", stepNorm_2); } else { printf("\t solve_nonlinear_problem(): Damped Newton unsuccessful, final estimate " "of the next solution update norm = %-12.4E\n", stepNorm_2); } } } // convergence if (convRes) { 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 (retnDamp < NSOLN_RETN_CONTINUE) { goto done; } } done: if (m_print_flag == 2 || m_print_flag == 3) { if (convRes > 0) { if (doDogLeg_) { if (convRes == 3) { printf("\t | | " " | | converged = 3 |(%11.3E) \n", stepNorm_2); } else { printf("\t | | " " | | converged = %1d | %10.3E %10.3E\n", convRes, stepNorm_2, m_normResidTrial); } printf("\t-----------------------------------------------------------------------------------------------------" "------------------------------------------------------------------------------\n"); } else { if (convRes == 3) { printf("\t | " " | converged = 3 | (%11.3E) \n", stepNorm_2); } else { printf("\t | " " | converged = %1d | %10.3E %10.3E\n", convRes, stepNorm_2, m_normResidTrial); } printf("\t------------------------------------------------------------------------------------" "-----------------------------------------------\n"); } } } mdp::mdp_copy_dbl_1(y_comm, CONSTD_DATA_PTR(m_y_n_curr), (int) neq_); if (solnType_ != NSOLN_TYPE_STEADY_STATE) { mdp::mdp_copy_dbl_1(ydot_comm, CONSTD_DATA_PTR(m_ydot_n_curr), (int) neq_); } num_linear_solves += m_numTotalLinearSolves; doublereal time_elapsed = wc.secondsWC(); if (m_print_flag > 1) { if (retnDamp > 0) { if (NonlinearSolver::s_TurnOffTiming) { printf("\tNonlinear problem solved successfully in %d its\n", num_newt_its); } else { printf("\tNonlinear problem solved successfully in %d its, time elapsed = %g sec\n", num_newt_its, time_elapsed); } } else { printf("\tNonlinear problem failed to solve after %d its\n", num_newt_its); } } retnCode = retnDamp; if (retnDamp > 0) { retnCode = NSOLN_RETN_SUCCESS; } return retnCode; } //==================================================================================================================== // Print solution norm contribution /* * Prints out the most important entries to the update to the solution vector for the current step * * @param step_1 Raw update vector for the current nonlinear step * @param stepNorm_1 Norm of the vector step_1 * @param step_2 Raw update vector for the next solution value based on the old matrix * @param stepNorm_2 Norm of the vector step_2 * @param title title of the printout * @param y_n_curr Old value of the solution * @param y_n_1 New value of the solution after damping corrections * @param damp Value of the damping factor * @param num_entries Number of entries to print out */ void NonlinearSolver:: print_solnDelta_norm_contrib(const doublereal* const step_1, const char* const stepNorm_1, const doublereal* const step_2, const char* const stepNorm_2, const char* const title, const doublereal* const y_n_curr, const doublereal* const y_n_1, doublereal damp, size_t num_entries) { bool used; doublereal dmax0, dmax1, error, rel_norm; printf("\t\t%s currentDamp = %g\n", title, damp); printf("\t\t I ysolnOld %13s ysolnNewRaw | ysolnNewTrial " "%10s ysolnNewTrialRaw | solnWeight wtDelSoln wtDelSolnTrial\n", stepNorm_1, stepNorm_2); std::vector imax(num_entries, npos); printf("\t\t "); print_line("-", 125); for (size_t jnum = 0; jnum < num_entries; jnum++) { dmax1 = -1.0; for (size_t i = 0; i < neq_; i++) { used = false; for (size_t j = 0; j < jnum; j++) { if (imax[j] == i) { used = true; } } if (!used) { error = step_1[i] / m_ewt[i]; rel_norm = sqrt(error * error); error = step_2[i] / m_ewt[i]; rel_norm += sqrt(error * error); if (rel_norm > dmax1) { imax[jnum] = i; dmax1 = rel_norm; } } } if (imax[jnum] != npos) { size_t i = imax[jnum]; error = step_1[i] / m_ewt[i]; dmax0 = sqrt(error * error); error = step_2[i] / m_ewt[i]; dmax1 = sqrt(error * error); printf("\t\t %4d %12.4e %12.4e %12.4e | %12.4e %12.4e %12.4e |%12.4e %12.4e %12.4e\n", i, y_n_curr[i], step_1[i], y_n_curr[i] + step_1[i], y_n_1[i], step_2[i], y_n_1[i]+ step_2[i], m_ewt[i], dmax0, dmax1); } } printf("\t\t "); print_line("-", 125); } //==================================================================================================================== //! This routine subtracts two numbers for one another /*! * This routine subtracts 2 numbers. If the difference is less * than 1.0E-14 times the magnitude of the smallest number, then diff returns an exact zero. * It also returns an exact zero if the difference is less than * 1.0E-300. * * returns: a - b * * This routine is used in numerical differencing schemes in order * to avoid roundoff errors resulting in creating Jacobian terms. * Note: This is a slow routine. However, jacobian errors may cause * loss of convergence. Therefore, in practice this routine has proved cost-effective. * * @param a Value of a * @param b value of b * * @return returns the difference between a and b */ static inline doublereal subtractRD(doublereal a, doublereal b) { doublereal diff = a - b; doublereal d = MIN(fabs(a), fabs(b)); d *= 1.0E-14; doublereal ad = fabs(diff); if (ad < 1.0E-300) { diff = 0.0; } if (ad < d) { diff = 0.0; } return diff; } //==================================================================================================================== /* * * Function called by BEuler to evaluate the Jacobian matrix and the * current residual at the current time step. * @param N = The size of the equation system * @param J = Jacobian matrix to be filled in * @param f = Right hand side. This routine returns the current * value of the rhs (output), so that it does * not have to be computed again. * * @return Returns a flag to indicate that operation is successful. * 1 Means a successful operation * 0 Means an unsuccessful operation */ int NonlinearSolver::beuler_jac(GeneralMatrix& J, doublereal* const f, doublereal time_curr, doublereal CJ, doublereal* const y, doublereal* const ydot, int num_newt_its) { double* col_j; int info; doublereal ysave, ydotsave, dy; int retn = 1; /* * Clear the factor flag */ J.clearFactorFlag(); if (m_jacFormMethod == NSOLN_JAC_ANAL) { /******************************************************************** * Call the function to get a jacobian. */ info = m_func->evalJacobian(time_curr, delta_t_n, CJ, y, ydot, J, f); m_nJacEval++; m_nfe++; if (info != 1) { return info; } } else { if (J.matrixType_ == 0) { /******************************************************************* * Generic algorithm to calculate a numerical Jacobian */ /* * Calculate the current value of the rhs given the * current conditions. */ info = m_func->evalResidNJ(time_curr, delta_t_n, y, ydot, f, JacBase_ResidEval); m_nfe++; if (info != 1) { return info; } m_nJacEval++; /* * Malloc a vector and call the function object to return a set of * deltaY's that are appropriate for calculating the numerical * derivative. */ doublereal* dyVector = mdp::mdp_alloc_dbl_1((int) neq_, MDP_DBL_NOINIT); retn = m_func->calcDeltaSolnVariables(time_curr, y, ydot, dyVector, DATA_PTR(m_ewt)); if (s_print_NumJac) { if (m_print_flag >= 7) { if (neq_ < 20) { printf("\t\tUnk m_ewt y dyVector ResN\n"); for (size_t iii = 0; iii < neq_; iii++) { printf("\t\t %4d %16.8e %16.8e %16.8e %16.8e \n", iii, m_ewt[iii], y[iii], dyVector[iii], f[iii]); } } } } /* * Loop over the variables, formulating a numerical derivative * of the dense matrix. * For the delta in the variable, we will use a variety of approaches * The original approach was to use the error tolerance amount. * This may not be the best approach, as it could be overly large in * some instances and overly small in others. * We will first protect from being overly small, by using the usual * sqrt of machine precision approach, i.e., 1.0E-7, * to bound the lower limit of the delta. */ for (size_t j = 0; j < neq_; j++) { /* * Get a pointer into the column of the matrix */ col_j = (doublereal*) J.ptrColumn(j); ysave = y[j]; dy = dyVector[j]; //dy = fmaxx(1.0E-6 * m_ewt[j], fabs(ysave)*1.0E-7); y[j] = ysave + dy; dy = y[j] - ysave; if (solnType_ != NSOLN_TYPE_STEADY_STATE) { ydotsave = ydot[j]; ydot[j] += dy * CJ; } /* * Call the function */ info = m_func->evalResidNJ(time_curr, delta_t_n, y, ydot, DATA_PTR(m_wksp), JacDelta_ResidEval, j, dy); m_nfe++; if (info != 1) { mdp::mdp_safe_free((void**) &dyVector); return info; } doublereal diff; for (size_t i = 0; i < neq_; i++) { diff = subtractRD(m_wksp[i], f[i]); col_j[i] = diff / dy; } y[j] = ysave; if (solnType_ != NSOLN_TYPE_STEADY_STATE) { ydot[j] = ydotsave; } } /* * Release memory */ mdp::mdp_safe_free((void**) &dyVector); } else if (J.matrixType_ == 1) { size_t ku, kl; size_t ivec[2]; size_t n = J.nRowsAndStruct(ivec); kl = ivec[0]; ku = ivec[1]; if (n != neq_) { printf("we have probs\n"); exit(-1); } // --------------------------------- BANDED MATRIX BRAIN DEAD --------------------------------------------------- info = m_func->evalResidNJ(time_curr, delta_t_n, y, ydot, f, JacBase_ResidEval); m_nfe++; if (info != 1) { return info; } m_nJacEval++; doublereal* dyVector = mdp::mdp_alloc_dbl_1((int) neq_, MDP_DBL_NOINIT); retn = m_func->calcDeltaSolnVariables(time_curr, y, ydot, dyVector, DATA_PTR(m_ewt)); if (s_print_NumJac) { if (m_print_flag >= 7) { if (neq_ < 20) { printf("\t\tUnk m_ewt y dyVector ResN\n"); for (size_t iii = 0; iii < neq_; iii++) { printf("\t\t %4d %16.8e %16.8e %16.8e %16.8e \n", iii, m_ewt[iii], y[iii], dyVector[iii], f[iii]); } } } } for (size_t j = 0; j < neq_; j++) { col_j = (doublereal*) J.ptrColumn(j); ysave = y[j]; dy = dyVector[j]; y[j] = ysave + dy; dy = y[j] - ysave; if (solnType_ != NSOLN_TYPE_STEADY_STATE) { ydotsave = ydot[j]; ydot[j] += dy * CJ; } info = m_func->evalResidNJ(time_curr, delta_t_n, y, ydot, DATA_PTR(m_wksp), JacDelta_ResidEval, j, dy); m_nfe++; if (info != 1) { mdp::mdp_safe_free((void**) &dyVector); return info; } doublereal diff; for (size_t i = j - ku; i <= j + kl; i++) { if (i >= 0 && i < neq_) { diff = subtractRD(m_wksp[i], f[i]); col_j[kl + ku + i - j] = diff / dy; } } y[j] = ysave; if (solnType_ != NSOLN_TYPE_STEADY_STATE) { ydot[j] = ydotsave; } } mdp::mdp_safe_free((void**) &dyVector); double vSmall; size_t ismall = J.checkRows(vSmall); if (vSmall < 1.0E-100) { printf("WE have a zero row, %d\n", ismall); exit(-1); } ismall = J.checkColumns(vSmall); if (vSmall < 1.0E-100) { printf("WE have a zero column, %d\n", ismall); exit(-1); } // ---------------------BANDED MATRIX BRAIN DEAD ----------------------- } } if (m_print_flag >= 7 && s_print_NumJac) { if (neq_ < 30) { printf("\t\tCurrent Matrix and Residual:\n"); printf("\t\t I,J | "); for (size_t j = 0; j < neq_; j++) { printf(" %5d ", j); } printf("| Residual \n"); printf("\t\t --"); for (size_t j = 0; j < neq_; j++) { printf("------------"); } printf("| -----------\n"); for (size_t i = 0; i < neq_; i++) { printf("\t\t %4d |", i); for (size_t j = 0; j < neq_; j++) { printf(" % 11.4E", J(i,j)); } printf(" | % 11.4E\n", f[i]); } printf("\t\t --"); for (size_t j = 0; j < neq_; j++) { printf("------------"); } printf("--------------\n"); } } /* * Make a copy of the data. Note, this jacobian copy occurs before any matrix scaling operations. * It's the raw matrix producted by this routine. */ jacCopyPtr_->copyData(J); return retn; } //==================================================================================================================== // Internal function to calculate the time derivative of the solution at the new step /* * Previously, the user must have supplied information about the previous time step for this routine to * work as intended. * * @param order of the BDF method * @param y_curr current value of the solution * @param ydot_curr Calculated value of the solution derivative that is consistent with y_curr */ void NonlinearSolver:: calc_ydot(const int order, const doublereal* const y_curr, doublereal* const ydot_curr) const { if (!ydot_curr) { return; } doublereal c1; switch (order) { case 0: case 1: /* First order forward Euler/backward Euler */ c1 = 1.0 / delta_t_n; for (size_t i = 0; i < neq_; i++) { ydot_curr[i] = c1 * (y_curr[i] - m_y_nm1[i]); } return; case 2: /* Second order Adams-Bashforth / Trapezoidal Rule */ c1 = 2.0 / delta_t_n; for (size_t i = 0; i < neq_; i++) { ydot_curr[i] = c1 * (y_curr[i] - m_y_nm1[i]) - m_ydot_nm1[i]; } return; default: throw CanteraError("calc_ydot()", "Case not covered"); } } //==================================================================================================================== // Apply a filtering process to the new step /* * @param timeCurrent Current value of the time * @param y_current current value of the solution * @param ydot_current Current value of the solution derivative. * * @return Returns the norm of the value of the amount filtered */ doublereal NonlinearSolver::filterNewStep(const doublereal timeCurrent, const doublereal* const ybase, doublereal* const step0) { doublereal tmp = m_func->filterNewStep(timeCurrent, ybase, step0); return tmp; } //==================================================================================================================== // Apply a filtering process to the new solution /* * @param timeCurrent Current value of the time * @param y_current current value of the solution * @param ydot_current Current value of the solution derivative. * * @return Returns the norm of the value of the amount filtered */ doublereal NonlinearSolver::filterNewSolution(const doublereal timeCurrent, doublereal* const y_current, doublereal* const ydot_current) { doublereal tmp = m_func->filterSolnPrediction(timeCurrent, y_current); return tmp; } //==================================================================================================================== // Compute the Residual Weights /* * The residual weights are defined here to be equal to the inverse of the row scaling factors used to * row scale the matrix, after column scaling is used. They are multiplied by rtol and an atol factor * is added as well so that if the residual is less than 1, then the calculation is deemed to be converged. * * The basic idea is that a change in the solution vector on the order of the convergence tolerance * multiplied by [RJC] which is of order one after row scaling should give you the relative weight * of the row. Values of the residual for that row can then be normalized by the value of this weight. * When the tolerance in delta x is achieved, the tolerance in the residual should also be achieved * and should be checked. */ void NonlinearSolver::computeResidWts() { ResidWtsReevaluated_ = true; if (checkUserResidualTols_ == 1) { for (size_t i = 0; i < neq_; i++) { m_residWts[i] = userResidAtol_[i] + userResidRtol_ * m_rowWtScales[i] / neq_; } } else { doublereal sum = 0.0; for (size_t i = 0; i < neq_; i++) { m_residWts[i] = m_rowWtScales[i] / neq_; sum += m_residWts[i]; } sum /= neq_; for (size_t i = 0; i < neq_; i++) { m_residWts[i] = m_ScaleSolnNormToResNorm * (m_residWts[i] + atolBase_ * atolBase_ * sum); } if (checkUserResidualTols_ == 2) { for (size_t i = 0; i < neq_; i++) { double uR = userResidAtol_[i] + userResidRtol_ * m_rowWtScales[i] / neq_; m_residWts[i] = MIN(m_residWts[i], uR); } } } } //===================================================================================================================== // return the residual weights /* * @param residWts Vector of length neq_ */ void NonlinearSolver::getResidWts(doublereal* const residWts) const { for (size_t i = 0; i < neq_; i++) { residWts[i] = (m_residWts)[i]; } } //===================================================================================================================== // Check to see if the nonlinear problem has converged /* * * @return integer is returned. If positive, then the problem has converged * 1 Successful step was taken: Next step's norm is less than 1.0. * The final residual norm is less than 1.0. * 2 Successful step: Next step's norm is less than 0.8. * This step's norm is less than 1.0. * The residual norm can be anything. * 3 Success: The final residual is less than 1.0E-2 * The predicted deltaSoln is below 1.0E-2. * 0 Not converged yet */ int NonlinearSolver::convergenceCheck(int dampCode, doublereal s1) { int retn = 0; if (m_dampBound < 0.9999) { return retn; } if (m_dampRes < 0.9999) { return retn; } if (dampCode <= 0) { return retn; } if (dampCode == 3) { if (s1 < 1.0E-2) { if (m_normResidTrial < 1.0E-6) { return 3; } } if (s1 < 0.8) { if (m_normDeltaSoln_Newton < 1.0) { return 2; } } } if (dampCode == 4) { if (s1 < 1.0E-2) { if (m_normResidTrial < 1.0E-6) { return 3; } } } if (s1 < 0.8) { if (m_normDeltaSoln_Newton < 1.0) { return 2; } } if (dampCode == 1 || dampCode == 2) { if (s1 < 1.0) { if (m_normResidTrial < 1.0) { return 1; } } } return retn; } //===================================================================================================================== // Set the absolute tolerances for the solution variables /* * Set the absolute tolerances used in the calculation * * @param atol Vector of length neq_ that contains the tolerances to be used for the solution variables */ void NonlinearSolver::setAtol(const doublereal* const atol) { for (size_t i = 0; i < neq_; i++) { atolk_[i]= atol[i]; } } //===================================================================================================================== // Set the relative tolerances for the solution variables /* * Set the relative tolerances used in the calculation for the solution variables. * * @param rtol single double */ void NonlinearSolver::setRtol(const doublereal rtol) { rtol_ = rtol; } //===================================================================================================================== // Set the relative and absolute tolerances for the Residual norm comparisons, if used /* * * residWeightNorm[i] = residAtol[i] + residRtol * m_rowWtScales[i] / neq * * @param residNormHandling Parameter that sets the default handling of the residual norms * 0 The residual weighting vector is calculated to make sure that the solution * norms are roughly 1 when the residual norm is roughly 1. * This is the default if this routine is not called. * 1 Use the user residual norm specified by the parameters in this routine * 2 Use the minimum value of the residual weights calculcated by method 1 and 2. * This is the default if this routine is called and this parameter isn't specified. */ void NonlinearSolver::setResidualTols(double residRtol, double* residATol, int residNormHandling) { if (residNormHandling < 0 || residNormHandling > 2) { throw CanteraError("NonlinearSolver::setResidualTols()", "Unknown int for residNormHandling"); } checkUserResidualTols_ = residNormHandling; userResidRtol_ = residRtol; if (residATol) { userResidAtol_.resize(neq_); for (size_t i = 0; i < neq_; i++) { userResidAtol_[i] = residATol[i]; } } else { if (residNormHandling ==1 || residNormHandling == 2) { throw CanteraError("NonlinearSolver::setResidualTols()", "Must set residATol vector"); } } } //===================================================================================================================== void NonlinearSolver::setPrintLvl(int printLvl) { m_print_flag = printLvl; } //===================================================================================================================== }