/**
 *
 *  @file NonlinearSolver.cpp
 *
 *  Damped Newton solver for 0D and 1D problems
 */

/*
 *  $Date$
 *  $Revision$
 */
/*
 * Copyright 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 <limits>

#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 "cantera/base/stringUtils.h"

#include <cfloat>
#include <ctime>
#include <vector>
#include <cstdio>
#include <cmath>

//@{

#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<double>::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<doublereal> &  NonlinearSolver::lowBoundsConstraintVector()
{
    return  m_y_low_bounds;
}
//====================================================================================================================
std::vector<doublereal> &  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<size_t> 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     %4s %12.4e       | %12.4e %12.4e %12.4e %12.4e\n",
                           int2str(i).c_str(), 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<size_t> 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     %4s     %12.4e     %12.4e     %12.4e | %12.4e\n",
                           int2str(i).c_str(),  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 < neq_) {
                            colP_j[kl + ku + irow - jcol] *= m_colScales[jcol];
                        }
                    }
                }
            }
        }
    }
    /*
     * row sum scaling -> Note, this is an unequivocal success
     *      at keeping the small numbers well balanced and nonnegative.
     */
    if (! jac.factored()) {
        /*
         * Ok, this is ugly. jac.begin() returns an vector<double> 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 mitigate 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 < 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 mitigate 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 < 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] = std::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();
    // multiply 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 = 0;
        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             %3s  %13.5E %13.5E\n",
                       int2str(i).c_str(), 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
     *  stored 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 initial 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 significantly 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 = 0;

    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<doublereal> 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] = std::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 = std::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 = std::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 it's 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 = std::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 = std::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 %s causing "
                       "delta damping of %g: origVal = %10.3g, undampedNew = %10.3g, dampedNew = %10.3g\n",
                       int2str(i_fbounds).c_str(), 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 %s causing"
                       "delta damping of %g: origVal = %10.3g, undampedNew = %10.3g, dampedNew = %10.3g\n",
                       int2str(i_fbounds).c_str(), 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<doublereal>  & 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<doublereal> 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 %s causing bounds damping of %g\n",
                   int2str(i_lower).c_str(), f_bounds);
        }
    }

    doublereal f_delta_bounds = deltaBoundStep(y, step0);
    fbound = std::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<doublereal> & 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 evaluated for success
 * @param ydot1      OUTPUT   Time derivates of solution at the conditions which are evaluated 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<doublereal> & 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 initial 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<size_t> 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  %4s %12.4e %12.4e %12.4e |  %12.4e  %12.4e   %12.4e    |%12.4e %12.4e %12.4e\n",
                   int2str(i).c_str(), 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 = std::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 %4s       %16.8e   %16.8e   %16.8e  %16.8e \n",
                                   int2str(iii).c_str(), 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 %4s       %16.8e   %16.8e   %16.8e  %16.8e \n",
                                   int2str(iii).c_str(), 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 < 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, %s\n", int2str(ismall).c_str());
                exit(-1);
            }
            ismall = J.checkColumns(vSmall);
            if (vSmall < 1.0E-100) {
                printf("WE have a zero column, %s\n", int2str(ismall).c_str());
                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("  %5s     ", int2str(j).c_str());
            }
            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   %4s |", int2str(i).c_str());
                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] = std::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;
}
//=====================================================================================================================
}

