/** * @file BEulerInt.cpp * */ /* * 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 "cantera/numerics/BEulerInt.h" #include "cantera/base/mdp_allo.h" #include using namespace std; using namespace mdp; #define SAFE_DELETE(a) if (a) { delete (a); a = 0; } /* * Blas routines */ extern "C" { extern void dcopy_(int*, double*, int*, double*, int*); } namespace Cantera { //================================================================================================ /* * Exception thrown when a BEuler error is encountered. We just call the * Cantera Error handler in the initialization list */ BEulerErr::BEulerErr(std::string msg) : CanteraError("BEulerInt", msg) { } //================================================================================================ /* * Constructor. Default settings: dense jacobian, no user-supplied * Jacobian function, Newton iteration. */ BEulerInt::BEulerInt() : m_iter(Newton_Iter), m_method(BEulerVarStep), m_jacFormMethod(BEULER_JAC_NUM), m_rowScaling(true), m_colScaling(false), m_matrixConditioning(false), m_itol(0), m_reltol(1.e-4), m_abstols(1.e-10), m_abstol(0), m_ewt(0), m_hmax(0.0), m_maxord(0), m_time_step_num(0), m_time_step_attempts(0), m_max_time_step_attempts(11000000), m_numInitialConstantDeltaTSteps(0), m_failure_counter(0), m_min_newt_its(0), m_printSolnStepInterval(1), m_printSolnNumberToTout(1), m_printSolnFirstSteps(0), m_dumpJacobians(false), m_neq(0), m_y_n(0), m_y_nm1(0), m_y_pred_n(0), m_ydot_n(0), m_ydot_nm1(0), m_t0(0.0), m_time_final(0.0), time_n(0.0), time_nm1(0.0), time_nm2(0.0), delta_t_n(0.0), delta_t_nm1(0.0), delta_t_nm2(0.0), delta_t_np1(1.0E-8), delta_t_max(1.0E300), m_resid(0), m_residWts(0), m_wksp(0), m_func(0), m_rowScales(0), m_colScales(0), tdjac_ptr(0), m_print_flag(3), m_nfe(0), m_nJacEval(0), m_numTotalNewtIts(0), m_numTotalLinearSolves(0), m_numTotalConvFails(0), m_numTotalTruncFails(0), num_failures(0) { } //================================================================================================ /* * Destructor */ BEulerInt::~BEulerInt() { mdp::mdp_safe_free((void**) &m_y_n); mdp::mdp_safe_free((void**) &m_y_nm1); mdp::mdp_safe_free((void**) &m_y_pred_n); mdp::mdp_safe_free((void**) &m_ydot_n); mdp::mdp_safe_free((void**) &m_ydot_nm1); mdp::mdp_safe_free((void**) &m_resid); mdp::mdp_safe_free((void**) &m_residWts); mdp::mdp_safe_free((void**) &m_wksp); mdp::mdp_safe_free((void**) &m_ewt); mdp::mdp_safe_free((void**) &m_abstol); mdp::mdp_safe_free((void**) &m_rowScales); mdp::mdp_safe_free((void**) &m_colScales); SAFE_DELETE(tdjac_ptr); } //================================================================================================ void BEulerInt::setTolerances(double reltol, size_t n, double* abstol) { m_itol = 1; if (!m_abstol) { m_abstol = mdp_alloc_dbl_1(m_neq, MDP_DBL_NOINIT); } if (static_cast(n) != m_neq) { printf("ERROR n is wrong\n"); exit(-1); } for (int i = 0; i < m_neq; i++) { m_abstol[i] = abstol[i]; } m_reltol = reltol; } //================================================================================================ void BEulerInt::setTolerances(double reltol, double abstol) { m_itol = 0; m_reltol = reltol; m_abstols = abstol; } //================================================================================================ void BEulerInt::setProblemType(int jacFormMethod) { m_jacFormMethod = jacFormMethod; } //================================================================================================ void BEulerInt::setMethodBEMT(BEulerMethodType t) { m_method = t; } //================================================================================================ void BEulerInt::setMaxStep(doublereal hmax) { m_hmax = hmax; } //================================================================================================ void BEulerInt::setMaxNumTimeSteps(int maxNumTimeSteps) { m_max_time_step_attempts = maxNumTimeSteps; } //================================================================================================ void BEulerInt::setNumInitialConstantDeltaTSteps(int num) { m_numInitialConstantDeltaTSteps = num; } //================================================================================================ /* * * setPrintSolnOptins(): * * This routine controls when the solution is printed * * @param printStepInterval If greater than 0, then the * soln is printed every printStepInterval * steps. * * @param printNumberToTout The solution is printed at * regular invervals a total of * "printNumberToTout" times. * * @param printSolnFirstSteps The solution is printed out * the first "printSolnFirstSteps" * steps. After these steps the other * parameters determine the printing. * default = 0 * * @param dumpJacobians Dump jacobians to disk. * * default = false * */ void BEulerInt::setPrintSolnOptions(int printSolnStepInterval, int printSolnNumberToTout, int printSolnFirstSteps, bool dumpJacobians) { m_printSolnStepInterval = printSolnStepInterval; m_printSolnNumberToTout = printSolnNumberToTout; m_printSolnFirstSteps = printSolnFirstSteps; m_dumpJacobians = dumpJacobians; } //================================================================================================ void BEulerInt::setIterator(IterType t) { m_iter = t; } //================================================================================================ /* * * setNonLinOptions() * * Set the options for the nonlinear method * * Defaults are set in the .h file. These are the defaults: * min_newt_its = 0 * matrixConditioning = false * colScaling = false * rowScaling = true */ void BEulerInt::setNonLinOptions(int min_newt_its, bool matrixConditioning, bool colScaling, bool rowScaling) { m_min_newt_its = min_newt_its; m_matrixConditioning = matrixConditioning; m_colScaling = colScaling; m_rowScaling = rowScaling; if (m_colScaling) { if (!m_colScales) { m_colScales = mdp_alloc_dbl_1(m_neq, 1.0); } } if (m_rowScaling) { if (!m_rowScales) { m_rowScales = mdp_alloc_dbl_1(m_neq, 1.0); } } } //================================================================================================ /* * * setInitialTimeStep(): * * Set the initial time step. Right now, we set the * time step by setting delta_t_np1. */ void BEulerInt::setInitialTimeStep(double deltaT) { delta_t_np1 = deltaT; } //================================================================================================ /* * setPrintFlag(): * */ void BEulerInt::setPrintFlag(int print_flag) { m_print_flag = print_flag; } //================================================================================================ /* * * initialize(): * * Find the initial conditions for y and ydot. */ void BEulerInt::initializeRJE(double t0, ResidJacEval& func) { m_neq = func.nEquations(); m_t0 = t0; internalMalloc(); /* * Get the initial conditions. */ func.getInitialConditions(m_t0, m_y_n, m_ydot_n); // Store a pointer to the residual routine in the object m_func = &func; /* * Initialize the various time counters in the object */ time_n = t0; time_nm1 = time_n; time_nm2 = time_nm1; delta_t_n = 0.0; delta_t_nm1 = 0.0; } //================================================================================================ /* * * reinitialize(): * */ void BEulerInt::reinitializeRJE(double t0, ResidJacEval& func) { m_neq = func.nEquations(); m_t0 = t0; internalMalloc(); /* * At the initial time, get the initial conditions and time and store * them into internal storage in the object, my[]. */ m_t0 = t0; func.getInitialConditions(m_t0, m_y_n, m_ydot_n); /** * Set up the internal weights that are used for testing convergence */ setSolnWeights(); // Store a pointer to the function m_func = &func; } //================================================================================================ /* * * getPrintTime(): * */ double BEulerInt::getPrintTime(double time_current) { double tnext; if (m_printSolnNumberToTout > 0) { double dt = (m_time_final - m_t0) / m_printSolnNumberToTout; for (int i = 0; i <= m_printSolnNumberToTout; i++) { tnext = m_t0 + dt * i; if (tnext >= time_current) { return tnext; } } } return 1.0E300; } //================================================================================================ /* * nEvals(): * * Return the total number of function evaluations */ int BEulerInt::nEvals() const { return m_nfe; } //================================================================================================ /* * * internalMalloc(): * * Internal routine that sets up the fixed length storage based on * the size of the problem to solve. */ void BEulerInt::internalMalloc() { mdp_realloc_dbl_1(&m_ewt, m_neq, 0, 0.0); mdp_realloc_dbl_1(&m_y_n, m_neq, 0, 0.0); mdp_realloc_dbl_1(&m_y_nm1, m_neq, 0, 0.0); mdp_realloc_dbl_1(&m_y_pred_n, m_neq, 0, 0.0); mdp_realloc_dbl_1(&m_ydot_n, m_neq, 0, 0.0); mdp_realloc_dbl_1(&m_ydot_nm1, m_neq, 0, 0.0); mdp_realloc_dbl_1(&m_resid, m_neq, 0, 0.0); mdp_realloc_dbl_1(&m_residWts, m_neq, 0, 0.0); mdp_realloc_dbl_1(&m_wksp, m_neq, 0, 0.0); if (m_rowScaling) { mdp_realloc_dbl_1(&m_rowScales, m_neq, 0, 1.0); } if (m_colScaling) { mdp_realloc_dbl_1(&m_colScales, m_neq, 0, 1.0); } tdjac_ptr = new SquareMatrix(m_neq); } //================================================================================================ /* * setSolnWeights(): * * Set the solution weights * This is a very important routine as it affects quite a few * operations involving convergence. * */ void BEulerInt::setSolnWeights() { int i; if (m_itol == 1) { /* * Adjust the atol vector if we are using vector * atol conditions. */ // m_func->adjustAtol(m_abstol); for (i = 0; i < m_neq; i++) { m_ewt[i] = m_abstol[i] + m_reltol * 0.5 * (fabs(m_y_n[i]) + fabs(m_y_pred_n[i])); } } else { for (i = 0; i < m_neq; i++) { m_ewt[i] = m_abstols + m_reltol * 0.5 * (fabs(m_y_n[i]) + fabs(m_y_pred_n[i])); } } } //================================================================================================ /* * * setColumnScales(): * * Set the column scaling vector at the current time */ void BEulerInt::setColumnScales() { m_func->calcSolnScales(time_n, m_y_n, m_y_nm1, m_colScales); } //================================================================================================ /* * computeResidWts(): * * We compute residual weights here, which we define as the L_0 norm * of the Jacobian Matrix, weighted by the solution weights. * This is the proper way to guage the magnitude of residuals. However, * it does need the evaluation of the jacobian, and the implementation * below is slow, but doesn't take up much memory. * * Here a small weighting indicates that the change in solution is * very sensitive to that equation. */ void BEulerInt::computeResidWts(GeneralMatrix& jac) { int i, j; double* data = &(*(jac.begin())); double value; for (i = 0; i < m_neq; i++) { m_residWts[i] = fabs(data[i] * m_ewt[0]); for (j = 1; j < m_neq; j++) { value = fabs(data[j*m_neq + i] * m_ewt[j]); m_residWts[i] = std::max(m_residWts[i], value); } } } //================================================================================================ /* * filterNewStep(): * * void BEulerInt:: * */ double BEulerInt::filterNewStep(double timeCurrent, double* y_current, double* ydot_current) { return 0.0; } //================================================================================================== static void print_line(const char* str, int n) { for (int i = 0; i < n; i++) { printf("%s", str); } printf("\n"); } //================================================================================================== /* * Print out for relevant time step information */ static void print_time_step1(int order, int n_time_step, double time, double delta_t_n, double delta_t_nm1, bool step_failed, int num_failures) { const char* string = 0; if (order == 0) { string = "Backward Euler"; } else if (order == 1) { string = "Forward/Backward Euler"; } else if (order == 2) { string = "Adams-Bashforth/TR"; } printf("\n"); print_line("=", 80); printf("\nStart of Time Step: %5d Time_n = %9.5g Time_nm1 = %9.5g\n", n_time_step, time, time - delta_t_n); printf("\tIntegration method = %s\n", string); if (step_failed) { printf("\tPreviously attempted step was a failure\n"); } if (delta_t_n > delta_t_nm1) { string = "(Increased from previous iteration)"; } else if (delta_t_n < delta_t_nm1) { string = "(Decreased from previous iteration)"; } else { string = "(same as previous iteration)"; } printf("\tdelta_t_n = %8.5e %s", delta_t_n, string); if (num_failures > 0) { printf("\t(Bad_History Failure Counter = %d)", num_failures); } printf("\n\tdelta_t_nm1 = %8.5e\n", delta_t_nm1); } //================================================================================================ /* * Print out for relevant time step information */ static void print_time_step2(int time_step_num, int order, double time, double time_error_factor, double delta_t_n, double delta_t_np1) { printf("\tTime Step Number %5d was a success: time = %10g\n", time_step_num, time); printf("\t\tEstimated Error\n"); printf("\t\t-------------------- = %8.5e\n", time_error_factor); printf("\t\tTolerated Error\n\n"); printf("\t- Recommended next delta_t (not counting history) = %g\n", delta_t_np1); printf("\n"); print_line("=", 80); printf("\n"); } //================================================================================================ /* * Print Out descriptive information on why the current step failed */ static void print_time_fail(bool convFailure, int time_step_num, double time, double delta_t_n, double delta_t_np1, double time_error_factor) { printf("\n"); print_line("=", 80); if (convFailure) { printf("\tTime Step Number %5d experienced a convergence " "failure\n", time_step_num); printf("\tin the non-linear or linear solver\n"); printf("\t\tValue of time at failed step = %g\n", time); printf("\t\tdelta_t of the failed step = %g\n", delta_t_n); printf("\t\tSuggested value of delta_t to try next = %g\n", delta_t_np1); } else { printf("\tTime Step Number %5d experienced a truncation error " "failure!\n", time_step_num); printf("\t\tValue of time at failed step = %g\n", time); printf("\t\tdelta_t of the failed step = %g\n", delta_t_n); printf("\t\tSuggested value of delta_t to try next = %g\n", delta_t_np1); printf("\t\tCalculated truncation error factor = %g\n", time_error_factor); } printf("\n"); print_line("=", 80); } //================================================================================================ /* * Print out the final results and counters */ static void print_final(double time, int step_failed, int time_step_num, int num_newt_its, int total_linear_solves, int numConvFails, int numTruncFails, int nfe, int nJacEval) { printf("\n"); print_line("=", 80); printf("TIME INTEGRATION ROUTINE HAS FINISHED: "); if (step_failed) { printf(" IT WAS A FAILURE\n"); } else { printf(" IT WAS A SUCCESS\n"); } printf("\tEnding time = %g\n", time); printf("\tNumber of time steps = %d\n", time_step_num); printf("\tNumber of newt its = %d\n", num_newt_its); printf("\tNumber of linear solves = %d\n", total_linear_solves); printf("\tNumber of convergence failures= %d\n", numConvFails); printf("\tNumber of TimeTruncErr fails = %d\n", numTruncFails); printf("\tNumber of Function evals = %d\n", nfe); printf("\tNumber of Jacobian evals/solvs= %d\n", nJacEval); printf("\n"); print_line("=", 80); } //================================================================================================ /* * Header info for one line comment about a time step */ static void print_lvl1_Header(int nTimes) { printf("\n"); if (nTimes) { print_line("-", 80); } printf("time Time Time Time "); if (nTimes == 0) { printf(" START"); } else { printf(" (continued)"); } printf("\n"); printf("step (sec) step Newt Aztc bktr trunc "); printf("\n"); printf(" No. Rslt size Its Its stps error |"); printf(" comment"); printf("\n"); print_line("-", 80); } //================================================================================================ /* * One line entry about time step * rslt -> 4 letter code */ static void print_lvl1_summary( int time_step_num, double time, const char* rslt, double delta_t_n, int newt_its, int aztec_its, int bktr_stps, double time_error_factor, const char* comment) { printf("%6d %11.6g %4s %10.4g %4d %4d %4d %11.4g", time_step_num, time, rslt, delta_t_n, newt_its, aztec_its, bktr_stps, time_error_factor); if (comment) { printf(" | %s", comment); } printf("\n"); } //================================================================================================ /* * subtractRD(): * 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. */ double subtractRD(double a, double b) { double diff = a - b; double d = std::min(fabs(a), fabs(b)); d *= 1.0E-14; double 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. * */ void BEulerInt::beuler_jac(GeneralMatrix& J, double* const f, double time_curr, double CJ, double* const y, double* const ydot, int num_newt_its) { int i, j; double* col_j; double ysave, ydotsave, dy; /** * Clear the factor flag */ J.clearFactorFlag(); if (m_jacFormMethod & BEULER_JAC_ANAL) { /******************************************************************** * Call the function to get a jacobian. */ m_func->evalJacobian(time_curr, delta_t_n, CJ, y, ydot, J, f); #ifdef DEBUG_HKM //double dddd = J(89, 89); //checkFinite(dddd); #endif m_nJacEval++; m_nfe++; } else { /******************************************************************* * Generic algorithm to calculate a numerical Jacobian */ /* * Calculate the current value of the rhs given the * current conditions. */ m_func->evalResidNJ(time_curr, delta_t_n, y, ydot, f, JacBase_ResidEval); m_nfe++; 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. */ double* dyVector = mdp::mdp_alloc_dbl_1(m_neq, MDP_DBL_NOINIT); m_func->calcDeltaSolnVariables(time_curr, y, m_y_nm1, dyVector, m_ewt); #ifdef DEBUG_HKM bool print_NumJac = false; if (print_NumJac) { FILE* idy = fopen("NumJac.csv", "w"); fprintf(idy, "Unk m_ewt y " "dyVector ResN\n"); for (int iii = 0; iii < m_neq; iii++) { fprintf(idy, " %4d %16.8e %16.8e %16.8e %16.8e \n", iii, m_ewt[iii], y[iii], dyVector[iii], f[iii]); } fclose(idy); } #endif /* * 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 (j = 0; j < m_neq; j++) { /* * Get a pointer into the column of the matrix */ col_j = (double*) 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; ydotsave = ydot[j]; ydot[j] += dy * CJ; /* * Call the functon */ m_func->evalResidNJ(time_curr, delta_t_n, y, ydot, m_wksp, JacDelta_ResidEval, j, dy); m_nfe++; double diff; for (i = 0; i < m_neq; i++) { diff = subtractRD(m_wksp[i], f[i]); col_j[i] = diff / dy; //col_j[i] = (m_wksp[i] - f[i])/dy; } y[j] = ysave; ydot[j] = ydotsave; } /* * Release memory */ mdp::mdp_safe_free((void**) &dyVector); } } /* * Function to calculate the predicted solution vector, m_y_pred_n for the * (n+1)th time step. This routine can be used by a first order - forward * Euler / backward Euler predictor / corrector method or for a second order * Adams-Bashforth / Trapezoidal Rule predictor / corrector method. See Nachos * documentation Sand86-1816 and Gresho, Lee, Sani LLNL report UCRL - 83282 for * more information. * * variables: * * on input: * * N - number of unknowns * order - indicates order of method * = 1 -> first order forward Euler/backward Euler * predictor/corrector * = 2 -> second order Adams-Bashforth/Trapezoidal Rule * predictor/corrector * * delta_t_n - magnitude of time step at time n (i.e., = t_n+1 - t_n) * delta_t_nm1 - magnitude of time step at time n - 1 (i.e., = t_n - t_n-1) * y_n[] - solution vector at time n * y_dot_n[] - acceleration vector from the predictor at time n * y_dot_nm1[] - acceleration vector from the predictor at time n - 1 * * on output: * * m_y_pred_n[] - predicted solution vector at time n + 1 */ void BEulerInt::calc_y_pred(int order) { int i; double c1, c2; switch (order) { case 0: case 1: c1 = delta_t_n; for (i = 0; i < m_neq; i++) { m_y_pred_n[i] = m_y_n[i] + c1 * m_ydot_n[i]; } break; case 2: c1 = delta_t_n * (2.0 + delta_t_n / delta_t_nm1) / 2.0; c2 = (delta_t_n * delta_t_n) / (delta_t_nm1 * 2.0); for (i = 0; i < m_neq; i++) { m_y_pred_n[i] = m_y_n[i] + c1 * m_ydot_n[i] - c2 * m_ydot_nm1[i]; } break; } /* * Filter the predictions. */ m_func->filterSolnPrediction(time_n, m_y_pred_n); } /* calc_y_pred */ /* Function to calculate the acceleration vector ydot for the first or * second order predictor/corrector time integrator. This routine can be * called by a first order - forward Euler / backward Euler predictor / * corrector or for a second order Adams - Bashforth / Trapezoidal Rule * predictor / corrector. See Nachos documentation Sand86-1816 and Gresho, * Lee, Sani LLNL report UCRL - 83282 for more information. * * variables: * * on input: * * N - number of local unknowns on the processor * This is equal to internal plus border unknowns. * order - indicates order of method * = 1 -> first order forward Euler/backward Euler * predictor/corrector * = 2 -> second order Adams-Bashforth/Trapezoidal Rule * predictor/corrector * * delta_t_n - Magnitude of the current time step at time n * (i.e., = t_n - t_n-1) * y_curr[] - Current Solution vector at time n * y_nm1[] - Solution vector at time n-1 * ydot_nm1[] - Acceleration vector at time n-1 * * on output: * * ydot_curr[] - Current acceleration vector at time n * * Note we use the current attribute to denote the possibility that * y_curr[] may not be equal to m_y_n[] during the nonlinear solve * because we may be using a look-ahead scheme. */ void BEulerInt:: calc_ydot(int order, double* y_curr, double* ydot_curr) { int i; double c1; switch (order) { case 0: case 1: /* First order forward Euler/backward Euler */ c1 = 1.0 / delta_t_n; for (i = 0; i < m_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 (i = 0; i < m_neq; i++) { ydot_curr[i] = c1 * (y_curr[i] - m_y_nm1[i]) - m_ydot_nm1[i]; } return; } } /************* END calc_ydot () ****************************************/ /* This function calculates the time step truncation error estimate * from a very simple formula based on Gresho et al. This routine can be * called for a * first order - forward Euler/backward Euler predictor/ corrector and * for a * second order Adams- Bashforth/Trapezoidal Rule predictor/corrector. See * Nachos documentation Sand86-1816 and Gresho, Lee, LLNL report * UCRL - 83282 * for more information. * * variables: * * on input: * * abs_error - Generic absolute error tolerance * rel_error - Generic realtive error tolerance * x_coor[] - Solution vector from the implicit corrector * x_pred_n[] - Solution vector from the explicit predictor * * on output: * * delta_t_n - Magnitude of next time step at time t_n+1 * delta_t_nm1 - Magnitude of previous time step at time t_n */ double BEulerInt::time_error_norm() { int i; double rel_norm, error; #ifdef DEBUG_HKM #define NUM_ENTRIES 5 if (m_print_flag > 2) { int imax[NUM_ENTRIES], j, jnum; double dmax; bool used; printf("\t\ttime step truncation error contributors:\n"); printf("\t\t I entry actual predicted " " weight ydot\n"); printf("\t\t"); print_line("-", 70); for (j = 0; j < NUM_ENTRIES; j++) { imax[j] = -1; } for (jnum = 0; jnum < NUM_ENTRIES; jnum++) { dmax = -1.0; for (i = 0; i < m_neq; i++) { used = false; for (j = 0; j < jnum; j++) { if (imax[j] == i) { used = true; } } if (!used) { error = (m_y_n[i] - m_y_pred_n[i]) / m_ewt[i]; rel_norm = sqrt(error * error); if (rel_norm > dmax) { imax[jnum] = i; dmax = rel_norm; } } } if (imax[jnum] >= 0) { i = imax[jnum]; printf("\t\t%4d %12.4e %12.4e %12.4e %12.4e %12.4e\n", i, dmax, m_y_n[i], m_y_pred_n[i], m_ewt[i], m_ydot_n[i]); } } printf("\t\t"); print_line("-", 70); } #endif rel_norm = 0.0; for (i = 0; i < m_neq; i++) { error = (m_y_n[i] - m_y_pred_n[i]) / m_ewt[i]; rel_norm += (error * error); } rel_norm = sqrt(rel_norm / m_neq); return rel_norm; } /************************************************************************* * Time step control function for the selection of the time step size based on * a desired accuracy of time integration and on an estimate of the relative * error of the time integration process. This routine can be called for a * first order - forward Euler/backward Euler predictor/ corrector and for a * second order Adams- Bashforth/Trapezoidal Rule predictor/corrector. See * Nachos documentation Sand86-1816 and Gresho, Lee, Sani LLNL report UCRL - * 83282 for more information. * * variables: * * on input: * * order - indicates order of method * = 1 -> first order forward Euler/backward Euler * predictor/corrector * = 2 -> second order forward Adams-Bashforth/Trapezoidal * rule predictor/corrector * * delta_t_n - Magnitude of time step at time t_n * delta_t_nm1 - Magnitude of time step at time t_n-1 * rel_error - Generic realtive error tolerance * time_error_factor - Estimated value of the time step truncation error * factor. This value is a ratio of the computed * error norms. The premultiplying constants * and the power are not yet applied to normalize the * predictor/corrector ratio. (see output value) * * on output: * * return - delta_t for the next time step * If delta_t is negative, then the current time step is * rejected because the time-step truncation error is * too large. The return value will contain the negative * of the recommended next time step. * * time_error_factor - This output value is normalized so that * values greater than one indicate the current time * integration error is greater than the user * specified magnitude. */ double BEulerInt::time_step_control(int order, double time_error_factor) { double factor = 0.0, power = 0.0, delta_t; const char* yo = "time_step_control"; /* * Special case time_error_factor so that zeroes don't cause a problem. */ time_error_factor = std::max(1.0E-50, time_error_factor); /* * Calculate the factor for the change in magnitude of time step. */ switch (order) { case 1: factor = 1.0/(2.0 *(time_error_factor)); power = 0.5; break; case 2: factor = 1.0/(3.0 * (1.0 + delta_t_nm1 / delta_t_n) * (time_error_factor)); power = 0.3333333333333333; } factor = pow(factor, power); if (factor < 0.5) { if (m_print_flag > 1) { printf("\t%s: WARNING - Current time step will be chucked\n", yo); printf("\t\tdue to a time step truncation error failure.\n"); } delta_t = - 0.5 * delta_t_n; } else { factor = std::min(factor, 1.5); delta_t = factor * delta_t_n; } return delta_t; } /************ END of time_step_control()********************************/ //================================================================================================ /************************************************************************** * * integrate(): * * defaults are located in the .h file. They are as follows: * time_init = 0.0 */ double BEulerInt::integrateRJE(double tout, double time_init) { double time_current; bool weAreNotFinished = true; m_time_final = tout; int flag = SUCCESS; /** * Initialize the time step number to zero. step will increment so that * the first time step is number 1 */ m_time_step_num = 0; /* * Do the integration a step at a time */ int istep = 0; int printStep = 0; bool doPrintSoln = false; time_current = time_init; time_n = time_init; time_nm1 = time_init; time_nm2 = time_init; m_func->evalTimeTrackingEqns(time_current, 0.0, m_y_n, m_ydot_n); double print_time = getPrintTime(time_current); if (print_time == time_current) { m_func->writeSolution(4, time_current, delta_t_n, istep, m_y_n, m_ydot_n); } /* * We print out column headers here for the case of */ if (m_print_flag == 1) { print_lvl1_Header(0); } /* * Call a different user routine at the end of each step, * that will probably print to a file. */ m_func->user_out2(0, time_current, 0.0, m_y_n, m_ydot_n); do { print_time = getPrintTime(time_current); if (print_time >= tout) { print_time = tout; } /************************************************************ * Step the solution */ time_current = step(tout); istep++; printStep++; /***********************************************************/ if (time_current < 0.0) { if (time_current == -1234.) { time_current = 0.0; } else { time_current = -time_current; } flag = FAILURE; } if (flag != FAILURE) { bool retn = m_func->evalStoppingCritera(time_current, delta_t_n, m_y_n, m_ydot_n); if (retn) { weAreNotFinished = false; doPrintSoln = true; } } /* * determine conditional printing of soln */ if (time_current >= print_time) { doPrintSoln = true; } if (m_printSolnStepInterval == printStep) { doPrintSoln = true; } if (m_printSolnFirstSteps > istep) { doPrintSoln = true; } /* * Evaluate time integrated quantities that are calculated at the * end of every successful time step. */ if (flag != FAILURE) { m_func->evalTimeTrackingEqns(time_current, delta_t_n, m_y_n, m_ydot_n); } /* * Call the printout routine. */ if (doPrintSoln) { m_func->writeSolution(1, time_current, delta_t_n, istep, m_y_n, m_ydot_n); printStep = 0; doPrintSoln = false; if (m_print_flag == 1) { print_lvl1_Header(1); } } /* * Call a different user routine at the end of each step, * that will probably print to a file. */ if (flag == FAILURE) { m_func->user_out2(-1, time_current, delta_t_n, m_y_n, m_ydot_n); } else { m_func->user_out2(1, time_current, delta_t_n, m_y_n, m_ydot_n); } } while (time_current < tout && m_time_step_attempts < m_max_time_step_attempts && flag == SUCCESS && weAreNotFinished); /* * Check current time against the max solution time. */ if (time_current >= tout) { printf("Simulation completed time integration in %d time steps\n", m_time_step_num); printf("Final Time: %e\n\n", time_current); } else if (m_time_step_attempts >= m_max_time_step_attempts) { printf("Simulation ran into time step attempt limit in" "%d time steps\n", m_time_step_num); printf("Final Time: %e\n\n", time_current); } else if (flag == FAILURE) { printf("ERROR: time stepper failed at time = %g\n", time_current); } /* * Print out the final results and counters. */ print_final(time_n, flag, m_time_step_num, m_numTotalNewtIts, m_numTotalLinearSolves, m_numTotalConvFails, m_numTotalTruncFails, m_nfe, m_nJacEval); /* * Call a different user routine at the end of each step, * that will probably print to a file. */ m_func->user_out2(2, time_current, delta_t_n, m_y_n, m_ydot_n); if (flag != SUCCESS) { throw BEulerErr(" BEuler error encountered."); } return time_current; } /************************************************************************** * * step(): * * This routine advances the calculations one step using a predictor * corrector approach. We use an implicit algorithm here. * */ double BEulerInt::step(double t_max) { double CJ; int one = 1; bool step_failed = false; bool giveUp = false; bool convFailure = false; const char* rslt; double time_error_factor = 0.0; double normFilter = 0.0; int numTSFailures = 0; int bktr_stps = 0; int nonlinearloglevel = m_print_flag; int num_newt_its = 0; int aztec_its = 0; string comment; /* * Increment the time counter - May have to be taken back, * if time step is found to be faulty. */ m_time_step_num++; /** * Loop here until we achieve a successful step or we set the giveUp * flag indicating that repeated errors have occurred. */ do { m_time_step_attempts++; comment.clear(); /* * Possibly adjust the delta_t_n value for this time step from the * recommended delta_t_np1 value determined in the previous step * due to maximum time step constraints or other occurences, * known to happen at a given time. */ if ((time_n + delta_t_np1) >= t_max) { delta_t_np1 =t_max - time_n; } if (delta_t_np1 >= delta_t_max) { delta_t_np1 = delta_t_max; } /* * Increment the delta_t counters and the time for the current * time step. */ delta_t_nm2 = delta_t_nm1; delta_t_nm1 = delta_t_n; delta_t_n = delta_t_np1; time_n += delta_t_n; /* * Determine the integration order of the current step. * * Special case for start-up of time integration procedure * First time step = Do a predictor step as we * have recently added an initial * ydot input option. And, setting ydot=0 * is equivalent to not doing a * predictor step. * Second step = If 2nd order method, do a first order * step for this time-step, only. * * If 2nd order method with a constant time step, the * first and second steps are 1/10 the specified step, and * the third step is 8/10 the specified step. This reduces * the error asociated with using lower order * integration on the first two steps. (RCS 11-6-97) * * If the previous time step failed for one reason or another, * do a linear step. It's more robust. */ if (m_time_step_num == 1) { m_order = 1; /* Backward Euler */ } else if (m_time_step_num == 2) { m_order = 1; /* Forward/Backward Euler */ } else if (step_failed) { m_order = 1; /* Forward/Backward Euler */ } else if (m_time_step_num > 2) { m_order = 1; /* Specified Predictor/Corrector - not implemented */ } /* * Print out an initial statement about the step. */ if (m_print_flag > 1) { print_time_step1(m_order, m_time_step_num, time_n, delta_t_n, delta_t_nm1, step_failed, m_failure_counter); } /* * Calculate the predicted solution, m_y_pred_n, for the current * time step. */ calc_y_pred(m_order); /* * HKM - Commented this out. I may need it for particles later. * If Solution bounds checking is turned on, we need to crop the * predicted solution to make sure bounds are enforced * * * cropNorm = 0.0; * if (Cur_Realm->Realm_Nonlinear.Constraint_Backtracking_Flag == * Constraint_Backtrack_Enable) { * cropNorm = cropPredictor(mesh, x_pred_n, abs_time_error, * m_reltol); */ /* * Save the old solution, before overwriting with the new solution * - use */ mdp_copy_dbl_1(m_y_nm1, m_y_n, m_neq); /* * Use the predicted value as the initial guess for the corrector * loop, for * every step other than the first step. */ if (m_order > 0) { mdp_copy_dbl_1(m_y_n, m_y_pred_n, m_neq); } /* * Save the old time derivative, if necessary, before it is * overwritten. * This overwrites ydot_nm1, losing information from the previous time * step. */ mdp_copy_dbl_1(m_ydot_nm1, m_ydot_n, m_neq); /* * Calculate the new time derivative, ydot_n, that is consistent * with the * initial guess for the corrected solution vector. * */ calc_ydot(m_order, m_y_n, m_ydot_n); /* * Calculate CJ, the coefficient for the jacobian corresponding to the * derivative of the residual wrt to the acceleration vector. */ if (m_order < 2) { CJ = 1.0 / delta_t_n; } else { CJ = 2.0 / delta_t_n; } /* * Calculate a new Solution Error Weighting vector */ setSolnWeights(); /* * Solve the system of equations at the current time step. * Note - x_corr_n and x_dot_n are considered to be updated, * on return from this solution. */ int ierror = solve_nonlinear_problem(m_y_n, m_ydot_n, CJ, time_n, *tdjac_ptr, num_newt_its, aztec_its, bktr_stps, nonlinearloglevel); /* * Set the appropriate flags if a convergence failure is detected. */ if (ierror < 0) { /* Step failed */ convFailure = true; step_failed = true; rslt = "fail"; m_numTotalConvFails++; m_failure_counter +=3; if (m_print_flag > 1) { printf("\tStep is Rejected, nonlinear problem didn't converge," "ierror = %d\n", ierror); } } else { /* Step succeeded */ convFailure = false; step_failed = false; rslt = "done"; /* * Apply a filter to a new successful step */ normFilter = filterNewStep(time_n, m_y_n, m_ydot_n); if (normFilter > 1.0) { convFailure = true; step_failed = true; rslt = "filt"; if (m_print_flag > 1) { printf("\tStep is Rejected, too large filter adjustment = %g\n", normFilter); } } else if (normFilter > 0.0) { if (normFilter > 0.3) { if (m_print_flag > 1) { printf("\tStep was filtered, norm = %g, next " "time step adjusted\n", normFilter); } } else { if (m_print_flag > 1) { printf("\tStep was filtered, norm = %g\n", normFilter); } } } } /* * Calculate the time step truncation error for the current step. */ if (!step_failed) { time_error_factor = time_error_norm(); } else { time_error_factor = 1000.; } /* * Dynamic time step control- delta_t_n, delta_t_nm1 are set here. */ if (step_failed) { /* * For convergence failures, decrease the step-size by a factor of * 4 and try again. */ delta_t_np1 = 0.25 * delta_t_n; } else if (m_method == BEulerVarStep) { /* * If we are doing a predictor/corrector method, and we are * past a certain number of time steps given by the input file * then either correct the DeltaT for the next time step or * */ if ((m_order > 0) && (m_time_step_num > m_numInitialConstantDeltaTSteps)) { delta_t_np1 = time_step_control(m_order, time_error_factor); if (normFilter > 0.1) { if (delta_t_np1 > delta_t_n) { delta_t_np1 = delta_t_n; } } /* * Check for Current time step failing due to violation of * time step * truncation bounds. */ if (delta_t_np1 < 0.0) { m_numTotalTruncFails++; step_failed = true; delta_t_np1 = -delta_t_np1; m_failure_counter += 2; comment += "TIME TRUNC FAILURE"; rslt = "TRNC"; } /* * Prevent churning of the time step by not increasing the * time step, * if the recent "History" of the time step behavior is still bad */ else if (m_failure_counter > 0) { delta_t_np1 = std::min(delta_t_np1, delta_t_n); } } else { delta_t_np1 = delta_t_n; } /* Decrease time step if a lot of Newton Iterations are * taken. * The idea being if more or less Newton iteration are taken * than the target number of iterations, then adjust the time * step downwards so that the target number of iterations or lower * is achieved. This * should prevent step failure by too many Newton iterations because * the time step becomes too large. CCO * hkm -> put in num_new_its min of 3 because the time step * was being altered even when num_newt_its == 1 */ int max_Newton_steps = 10000; int target_num_iter = 5; if (num_newt_its > 3000 && !step_failed) { if (max_Newton_steps != target_num_iter) { double iter_diff = num_newt_its - target_num_iter; double iter_adjust_zone = max_Newton_steps - target_num_iter; double target_time_step = delta_t_n *(1.0 - iter_diff*fabs(iter_diff)/ ((2.0*iter_adjust_zone*iter_adjust_zone))); target_time_step = std::max(0.5*delta_t_n, target_time_step); if (target_time_step < delta_t_np1) { printf("\tNext time step will be decreased from %g to %g" " because of new its restraint\n", delta_t_np1, target_time_step); delta_t_np1 = target_time_step; } } } } /* * The final loop in the time stepping algorithm depends on whether the * current step was a success or not. */ if (step_failed) { /* * Increment the counter indicating the number of consecutive * failures */ numTSFailures++; /* * Print out a statement about the failure of the time step. */ if (m_print_flag > 1) { print_time_fail(convFailure, m_time_step_num, time_n, delta_t_n, delta_t_np1, time_error_factor); } else if (m_print_flag == 1) { print_lvl1_summary(m_time_step_num, time_n, rslt, delta_t_n, num_newt_its, aztec_its, bktr_stps, time_error_factor, comment.c_str()); } /* * Change time step counters back to the previous step before * the failed * time step occurred. */ time_n -= delta_t_n; delta_t_n = delta_t_nm1; delta_t_nm1 = delta_t_nm2; /* * Replace old solution vector and time derivative solution vector. */ dcopy_(&m_neq, m_y_nm1, &one, m_y_n, &one); dcopy_(&m_neq, m_ydot_nm1, &one, m_ydot_n, &one); /* * Decide whether to bail on the whole loop */ if (numTSFailures > 35) { giveUp = true; } } /* * Do processing for a successful step. */ else { /* * Decrement the number of consequative failure counter. */ m_failure_counter = std::max(0, m_failure_counter-1); /* * Print out final results of a successfull time step. */ if (m_print_flag > 1) { print_time_step2(m_time_step_num, m_order, time_n, time_error_factor, delta_t_n, delta_t_np1); } else if (m_print_flag == 1) { print_lvl1_summary(m_time_step_num, time_n, " ", delta_t_n, num_newt_its, aztec_its, bktr_stps, time_error_factor, comment.c_str()); } /* * Output information at the end of every successful time step, if * requested. * * fill in */ } } while (step_failed && !giveUp); /* * Send back the overall result of the time step. */ if (step_failed) { if (time_n == 0.0) { return -1234.0; } return -time_n; } return time_n; } //----------------------------------------------------------- // Constants //----------------------------------------------------------- const double DampFactor = 4; const int NDAMP = 10; //----------------------------------------------------------- // MultiNewton methods //----------------------------------------------------------- /** * L2 Norm of a delta in the solution * * The second argument has a default of false. However, * if true, then a table of the largest values is printed * out to standard output. */ double BEulerInt::soln_error_norm(const double* const delta_y, bool printLargest) { int i; double sum_norm = 0.0, error; for (i = 0; i < m_neq; i++) { error = delta_y[i] / m_ewt[i]; sum_norm += (error * error); } sum_norm = sqrt(sum_norm / m_neq); if (printLargest) { const int num_entries = 8; double dmax1, normContrib; int j; int* imax = mdp_alloc_int_1(num_entries, -1); printf("\t\tPrintout of Largest Contributors to norm " "of value (%g)\n", sum_norm); printf("\t\t I ysoln deltaY weightY " "Error_Norm**2\n"); printf("\t\t "); print_line("-", 80); for (int jnum = 0; jnum < num_entries; jnum++) { dmax1 = -1.0; for (i = 0; i < m_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; } } } i = imax[jnum]; if (i >= 0) { printf("\t\t %4d %12.4e %12.4e %12.4e %12.4e\n", i, m_y_n[i], delta_y[i], m_ewt[i], dmax1); } } printf("\t\t "); print_line("-", 80); mdp_safe_free((void**) &imax); } return sum_norm; } #ifdef DEBUG_HKM_JAC SquareMatrix jacBack(); #endif /************************************************************************** * * doNewtonSolve(): * * 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, and the solution time derivative, m_ydot_n, * but the Jacobian is not recomputed. */ void BEulerInt::doNewtonSolve(double time_curr, double* y_curr, double* ydot_curr, double* delta_y, GeneralMatrix& jac, int loglevel) { int irow, jcol; m_func->evalResidNJ(time_curr, delta_t_n, y_curr, ydot_curr, delta_y, Base_ResidEval); m_nfe++; int sz = m_func->nEquations(); for (int n = 0; n < sz; n++) { delta_y[n] = -delta_y[n]; } /* * Column scaling -> We scale the columns of the Jacobian * by the nominal important change in the solution vector */ if (m_colScaling) { if (!jac.factored()) { /* * Go get new scales */ setColumnScales(); /* * Scale the new Jacobian */ double* jptr = &(*(jac.begin())); for (jcol = 0; jcol < m_neq; jcol++) { for (irow = 0; irow < m_neq; irow++) { *jptr *= m_colScales[jcol]; jptr++; } } } } if (m_matrixConditioning) { if (jac.factored()) { m_func->matrixConditioning(0, sz, delta_y); } else { double* jptr = &(*(jac.begin())); m_func->matrixConditioning(jptr, sz, delta_y); } } /* * row sum scaling -> Note, this is an unequivocal success * at keeping the small numbers well balanced and * nonnegative. */ if (m_rowScaling) { if (! jac.factored()) { /* * Ok, this is ugly. jac.begin() returns an vector iterator * to the first data location. * Then &(*()) reverts it to a double *. */ double* jptr = &(*(jac.begin())); for (irow = 0; irow < m_neq; irow++) { m_rowScales[irow] = 0.0; } for (jcol = 0; jcol < m_neq; jcol++) { for (irow = 0; irow < m_neq; irow++) { m_rowScales[irow] += fabs(*jptr); jptr++; } } jptr = &(*(jac.begin())); for (jcol = 0; jcol < m_neq; jcol++) { for (irow = 0; irow < m_neq; irow++) { *jptr /= m_rowScales[irow]; jptr++; } } } for (irow = 0; irow < m_neq; irow++) { delta_y[irow] /= m_rowScales[irow]; } } #ifdef DEBUG_HKM_JAC bool printJacContributions = false; if (m_time_step_num > 304) { printJacContributions = false; } int focusRow = 10; int numRows = 2; double RRow[2]; bool freshJac = true; RRow[0] = delta_y[focusRow]; RRow[1] = delta_y[focusRow+1]; double Pcutoff = 1.0E-70; if (!jac.m_factored) { jacBack = jac; } else { freshJac = false; } #endif /* * Solve the system -> This also involves inverting the * matrix */ (void) jac.solve(delta_y); /* * reverse the column scaling if there was any. */ if (m_colScaling) { for (irow = 0; irow < m_neq; irow++) { delta_y[irow] *= m_colScales[irow]; } } #ifdef DEBUG_HKM_JAC if (printJacContributions) { for (int iNum = 0; iNum < numRows; iNum++) { if (iNum > 0) { focusRow++; } double dsum = 0.0; vector_fp& Jdata = jacBack.data(); double dRow = Jdata[m_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 < m_neq; ii++) { if (ii != focusRow) { double aij = Jdata[m_neq * ii + focusRow]; double 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++; } //================================================================================================ // Bound the Newton step while relaxing the solution /* * 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 * * @param y Current value of the solution * @param step0 Current raw step change in y[] * @param loglevel Log level. This routine produces output if loglevel * is greater than one * * @return Returns the damping coefficient */ double BEulerInt::boundStep(const double* const y, const double* const step0, int loglevel) { int i, i_lower = -1, ifbd = 0, i_fbd = 0; double fbound = 1.0, f_lowbounds = 1.0, f_delta_bounds = 1.0; double ff, y_new, ff_alt; for (i = 0; i < m_neq; i++) { y_new = y[i] + step0[i]; if ((y_new < (-0.01 * m_ewt[i])) && y[i] >= 0.0) { ff = 0.9 * (y[i] / (y[i] - y_new)); if (ff < f_lowbounds) { f_lowbounds = ff; i_lower = i; } } /* * Now do a delta bounds * Increase variables by a factor of 2 only * decrease variables by a factor of 5 only */ ff = 1.0; if ((fabs(y_new) > 2.0 * fabs(y[i])) && (fabs(y_new-y[i]) > m_ewt[i])) { ff = fabs(y[i]/(y_new - y[i])); ff_alt = fabs(m_ewt[i] / (y_new - y[i])); ff = std::max(ff, ff_alt); ifbd = 1; } if ((fabs(5.0 * y_new) < fabs(y[i])) && (fabs(y_new - y[i]) > m_ewt[i])) { ff = y[i]/(y_new-y[i]) * (1.0 - 5.0)/5.0; ff_alt = fabs(m_ewt[i] / (y_new - y[i])); ff = std::max(ff, ff_alt); ifbd = 0; } if (ff < f_delta_bounds) { f_delta_bounds = ff; i_fbd = ifbd; } f_delta_bounds = std::min(f_delta_bounds, ff); } fbound = std::min(f_lowbounds, f_delta_bounds); /* * Report on any corrections */ if (loglevel > 1) { if (fbound != 1.0) { if (f_lowbounds < f_delta_bounds) { printf("\t\tboundStep: Variable %d causing lower bounds " "damping of %g\n", i_lower, f_lowbounds); } else { if (ifbd) { printf("\t\tboundStep: Decrease of Variable %d causing " "delta damping of %g\n", i_fbd, f_delta_bounds); } else { printf("\t\tboundStep: Increase of variable %d causing" "delta damping of %g\n", i_fbd, f_delta_bounds); } } } } return fbound; } //================================================================================================ /************************************************************************** * * dampStep(): * * On entry, step0 must contain an undamped Newton step for the * solution x0. This method attempts to find a damping coefficient * such that the next undamped step would have a norm smaller than * that of step0. If successful, the new solution after taking the * damped step is returned in y1, and the undamped step at y1 is * returned in step1. */ int BEulerInt::dampStep(double time_curr, const double* y0, const double* ydot0, const double* step0, double* y1, double* ydot1, double* step1, double& s1, GeneralMatrix& jac, int& loglevel, bool writetitle, int& num_backtracks) { // Compute the weighted norm of the undamped step size step0 double s0 = soln_error_norm(step0); // 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. double fbound = boundStep(y0, step0, loglevel); // 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 (fbound < 1.e-10) { if (loglevel > 1) { printf("\t\t\tdampStep: At limits.\n"); } return -3; } //-------------------------------------------- // Attempt damped step //-------------------------------------------- // damping coefficient starts at 1.0 double damp = 1.0; int j, m; double ff; num_backtracks = 0; for (m = 0; m < NDAMP; m++) { ff = fbound*damp; // step the solution by the damped step size /* * Whenever we update the solution, we must also always * update the time derivative. */ for (j = 0; j < m_neq; j++) { y1[j] = y0[j] + ff*step0[j]; // HKM setting intermediate y's to zero was a tossup. // slightly different, equivalent results //#ifdef DEBUG_HKM // y1[j] = MAX(0.0, y1[j]); //#endif } calc_ydot(m_order, y1, ydot1); // compute the next undamped step, step1[], that would result // if y1[] were accepted. doNewtonSolve(time_curr, y1, ydot1, step1, jac, loglevel); #ifdef DEBUG_HKM for (j = 0; j < m_neq; j++) { checkFinite(step1[j]); checkFinite(y1[j]); } #endif // compute the weighted norm of step1 s1 = soln_error_norm(step1); // write log information if (loglevel > 3) { print_solnDelta_norm_contrib((const double*) step0, "DeltaSolnTrial", (const double*) step1, "DeltaSolnTrialTest", "dampNewt: Important Entries for " "Weighted Soln Updates:", y0, y1, ff, 5); } if (loglevel > 1) { printf("\t\t\tdampNewt: s0 = %g, s1 = %g, fbound = %g," "damp = %g\n", s0, s1, fbound, damp); } #ifdef DEBUG_HKM if (loglevel > 2) { if (s1 > 1.00000001 * s0 && s1 > 1.0E-5) { printf("WARNING: Possible Jacobian Problem " "-> turning on more debugging for this step!!!\n"); print_solnDelta_norm_contrib((const double*) step0, "DeltaSolnTrial", (const double*) step1, "DeltaSolnTrialTest", "dampNewt: Important Entries for " "Weighted Soln Updates:", y0, y1, ff, 5); loglevel = 4; } } #endif // if the norm of s1 is less than the norm of s0, then // accept this damping coefficient. Also accept it if this // step would result in a converged solution. Otherwise, // decrease the damping coefficient and try again. if (s1 < 1.0E-5 || s1 < s0) { if (loglevel > 2) { if (s1 > s0) { if (s1 > 1.0) { printf("\t\t\tdampStep: current trial step and damping" " coefficient accepted because test step < 1\n"); printf("\t\t\t s1 = %g, s0 = %g\n", s1, s0); } } } break; } else { if (loglevel > 1) { printf("\t\t\tdampStep: current step rejected: (s1 = %g > " "s0 = %g)", s1, s0); if (m < (NDAMP-1)) { printf(" Decreasing damping factor and retrying"); } else { printf(" Giving up!!!"); } printf("\n"); } } num_backtracks++; damp /= DampFactor; } // If a damping coefficient was found, return 1 if the // solution after stepping by the damped step would represent // a converged solution, and return 0 otherwise. If no damping // coefficient could be found, return -2. if (m < NDAMP) { if (s1 > 1.0) { return 0; } else { return 1; } } else { if (s1 < 0.5 && (s0 < 0.5)) { return 1; } if (s1 < 1.0) { return 0; } return -2; } } //================================================================================================ // Solve a nonlinear system /* * Find the solution to F(X, xprime) = 0 by damped Newton iteration. On * entry, y_comm[] contains an initial estimate of the solution and * ydot_comm[] contains an estimate of the derivative. * On successful return, y_comm[] contains the converged solution * and ydot_comm[] contains the derivative * * * @param y_comm[] Contains the input solution. On output y_comm[] contains * the converged solution * @param ydot_comm Contains the input derivative solution. On output y_comm[] contains * the converged derivative solution * @param CJ Inverse of the time step * @param time_curr Current value of the time * @param jac Jacobian * @param num_newt_its number of newton iterations * @param num_linear_solves number of linear solves * @param num_backtracks number of backtracs * @param loglevel Log level */ int BEulerInt::solve_nonlinear_problem(double* const y_comm, double* const ydot_comm, double CJ, double time_curr, GeneralMatrix& jac, int& num_newt_its, int& num_linear_solves, int& num_backtracks, int loglevel) { int m = 0; bool forceNewJac = false; double s1=1.e30; double* y_curr = mdp_alloc_dbl_1(m_neq, 0.0); double* ydot_curr = mdp_alloc_dbl_1(m_neq, 0.0); double* stp = mdp_alloc_dbl_1(m_neq, 0.0); double* stp1 = mdp_alloc_dbl_1(m_neq, 0.0); double* y_new = mdp_alloc_dbl_1(m_neq, 0.0); double* ydot_new = mdp_alloc_dbl_1(m_neq, 0.0); mdp_copy_dbl_1(y_curr, y_comm, m_neq); mdp_copy_dbl_1(ydot_curr, ydot_comm, m_neq); bool frst = true; num_newt_its = 0; num_linear_solves = - m_numTotalLinearSolves; num_backtracks = 0; int i_backtracks; while (1 > 0) { /* * Increment Newton Solve counter */ m_numTotalNewtIts++; num_newt_its++; if (loglevel > 1) { printf("\t\tSolve_Nonlinear_Problem: iteration %d:\n", num_newt_its); } // Check whether the Jacobian should be re-evaluated. forceNewJac = true; if (forceNewJac) { if (loglevel > 1) { printf("\t\t\tGetting a new Jacobian and solving system\n"); } beuler_jac(jac, m_resid, time_curr, CJ, y_curr, ydot_curr, num_newt_its); } else { if (loglevel > 1) { printf("\t\t\tSolving system with old jacobian\n"); } } // compute the undamped Newton step doNewtonSolve(time_curr, y_curr, ydot_curr, stp, jac, loglevel); // damp the Newton step m = dampStep(time_curr, y_curr, ydot_curr, stp, y_new, ydot_new, stp1, s1, jac, loglevel, frst, i_backtracks); frst = false; num_backtracks += i_backtracks; /* * Impose the minimum number of newton iterations critera */ if (num_newt_its < m_min_newt_its) { if (m == 1) { m = 0; } } /* * Impose max newton iteration */ if (num_newt_its > 20) { m = -1; if (loglevel > 1) { printf("\t\t\tDampnewton unsuccessful (max newts exceeded) sfinal = %g\n", s1); } } if (loglevel > 1) { if (m == 1) { printf("\t\t\tDampNewton iteration successful, nonlin " "converged sfinal = %g\n", s1); } else if (m == 0) { printf("\t\t\tDampNewton iteration successful, get new" "direction, sfinal = %g\n", s1); } else { printf("\t\t\tDampnewton unsuccessful sfinal = %g\n", s1); } } // If we are converged, then let's use the best solution possible // for an end result. We did a resolve in dampStep(). Let's update // the solution to reflect that. // HKM 5/16 -> Took this out, since if the last step was a // damped step, then adding stp1[j] is undamped, and // may lead to oscillations. It kind of defeats the // purpose of dampStep() anyway. // if (m == 1) { // for (int j = 0; j < m_neq; j++) { // y_new[j] += stp1[j]; // HKM setting intermediate y's to zero was a tossup. // slightly different, equivalent results // #ifdef DEBUG_HKM // y_new[j] = MAX(0.0, y_new[j]); // #endif // } // } bool m_filterIntermediate = false; if (m_filterIntermediate) { if (m == 0) { (void) filterNewStep(time_n, y_new, ydot_new); } } // Exchange new for curr solutions if (m == 0 || m == 1) { mdp_copy_dbl_1(y_curr, y_new, m_neq); calc_ydot(m_order, y_curr, ydot_curr); } // convergence if (m == 1) { goto done; } // If dampStep fails, first try a new Jacobian if an old // one was being used. If it was a new Jacobian, then // return -1 to signify failure. else if (m < 0) { goto done; } } done: // Copy into the return vectors mdp_copy_dbl_1(y_comm, y_curr, m_neq); mdp_copy_dbl_1(ydot_comm, ydot_curr, m_neq); // Increment counters num_linear_solves += m_numTotalLinearSolves; // Free memory mdp_safe_free((void**) &y_curr); mdp_safe_free((void**) &ydot_curr); mdp_safe_free((void**) &stp); mdp_safe_free((void**) &stp1); mdp_safe_free((void**) &y_new); mdp_safe_free((void**) &ydot_new); double time_elapsed = 0.0; if (loglevel > 1) { if (m == 1) { printf("\t\tNonlinear problem solved successfully in " "%d its, time elapsed = %g sec\n", num_newt_its, time_elapsed); } } return m; } //================================================================================================ /* * * */ void BEulerInt:: print_solnDelta_norm_contrib(const double* const solnDelta0, const char* const s0, const double* const solnDelta1, const char* const s1, const char* const title, const double* const y0, const double* const y1, double damp, int num_entries) { int i, j, jnum; bool used; double dmax0, dmax1, error, rel_norm; printf("\t\t%s currentDamp = %g\n", title, damp); printf("\t\t I ysoln %10s ysolnTrial " "%10s weight relSoln0 relSoln1\n", s0, s1); int* imax = mdp_alloc_int_1(num_entries, -1); printf("\t\t "); print_line("-", 90); for (jnum = 0; jnum < num_entries; jnum++) { dmax1 = -1.0; for (i = 0; i < m_neq; i++) { used = false; for (j = 0; j < jnum; j++) { if (imax[j] == i) { used = true; } } if (!used) { error = solnDelta0[i] / m_ewt[i]; rel_norm = sqrt(error * error); error = solnDelta1[i] / m_ewt[i]; rel_norm += sqrt(error * error); if (rel_norm > dmax1) { imax[jnum] = i; dmax1 = rel_norm; } } } if (imax[jnum] >= 0) { i = imax[jnum]; error = solnDelta0[i] / m_ewt[i]; dmax0 = sqrt(error * error); error = solnDelta1[i] / m_ewt[i]; dmax1 = sqrt(error * error); printf("\t\t %4d %12.4e %12.4e %12.4e %12.4e " "%12.4e %12.4e %12.4e\n", i, y0[i], solnDelta0[i], y1[i], solnDelta1[i], m_ewt[i], dmax0, dmax1); } } printf("\t\t "); print_line("-", 90); mdp_safe_free((void**) &imax); } //=============================================================================================== } // End of namespace Cantera