/// <summary> /// Implementation of Gear's BDF method with dynamically changed step size and order. Order changes between 1 and 3. /// </summary> /// <param name="t0">Initial time point</param> /// <param name="x0">Initial phase vector</param> /// <param name="f">Right parts of the system</param> /// <param name="opts">Options for accuracy control and initial step size</param> /// <returns>Sequence of infinite number of solution points.</returns> private void InternalInitialize(double t0, double[] x0, Action <double, double[], double[]> f, GearsBDFOptions opts) { if (null == _denseJacobianEvaluation && null == _sparseJacobianEvaluation) { throw new InvalidProgramException("Ooops, how could this happen?"); } double t = t0; var x = (double[])x0.Clone(); n = x0.Length; this.f = f; this.opts = opts; //Initial step size. _dydt = _dydt ?? new double[n]; var dt = EvaluateRatesAndGetDt(t0, x0, _dydt); var resdt = dt; int qmax = 5; int qcurr = 2; _zn_saved = new DoubleMatrix(n, qmax + 1); currstate = new NordsieckState(n, qmax, qcurr, dt, t, x0, _dydt); isIterationFailed = false; tout = t0; xout = (double[])x0.Clone(); // --------------------------------------------------------------------------------------------------- // End of initialize // --------------------------------------------------------------------------------------------------- // Firstly, return initial point // EvaluateInternally(t0, true, out t0, xout); _initializationState = InitializationState.NotInitialized; }
/// <summary> /// Initialize Gear's BDF method with dynamically changed step size and order. /// </summary> /// <param name="t0">Initial time point</param> /// <param name="y0">Initial values (at time <paramref name="t0"/>).</param> /// <param name="dydt">Evaluation function for the derivatives. First argument is the time, second argument are the current y values. The third argument is an array where the derivatives are expected to be placed into.</param> /// <param name="opts">Options for the ODE method (can be null).</param> public void Initialize(double t0, double[] y0, Action <double, double[], double[]> dydt, GearsBDFOptions opts) { if (null == y0) { throw new ArgumentNullException(nameof(y0)); } if (null == dydt) { throw new ArgumentNullException(nameof(dydt)); } _denseJacobianEvaluation = new DenseJacobianEvaluator(y0.Length, dydt).Jacobian; InternalInitialize(t0, y0, dydt, opts ?? GearsBDFOptions.Default); }
/// <summary> /// Initialize Gear's BDF method with dynamically changed step size and order. /// </summary> /// <param name="t0">Initial time point</param> /// <param name="y0">Initial values (at time <paramref name="t0"/>).</param> /// <param name="dydt">Evaluation function for the derivatives. First argument is the time, second argument are the current y values. The third argument is an array where the derivatives are expected to be placed into.</param> /// <param name="sparseJacobianEvaluation">Evaluation for the dense jacobian matrix. First argument is the time, second argument are the current y values. If null is passed for this argument, a default evaluator is used.</param> /// <param name="opts">Options for the ODE method (can be null).</param> public void InitializeSparse(double t0, double[] y0, Action <double, double[], double[]> dydt, Func <double, double[], SparseDoubleMatrix> sparseJacobianEvaluation, GearsBDFOptions opts) { if (null == y0) { throw new ArgumentNullException(nameof(y0)); } if (null == dydt) { throw new ArgumentNullException(nameof(dydt)); } _sparseJacobianEvaluation = sparseJacobianEvaluation ?? new SparseJacobianEvaluator(y0.Length, dydt).Jacobian; InternalInitialize(t0, y0, dydt, opts ?? GearsBDFOptions.Default); }
/// <summary> /// Execute predictor-corrector scheme for Nordsieck's method /// </summary> /// <param name="flag"></param> /// <param name="f">Evaluation of the deriatives. First argument is time, second arg are the state variables, and 3rd arg is the array to accomodate the derivatives.</param> /// <param name="denseJacobianEvaluation">Evaluation of the jacobian.</param> /// <param name="sparseJacobianEvaluation">Evaluation of the jacobian as a sparse matrix. Either this or the previous arg must be valid.</param> /// <param name="opts">current options</param> /// <returns>en - current error vector</returns> internal void PredictorCorrectorScheme( ref bool flag, Action <double, double[], double[]> f, Func <double, double[], IROMatrix <double> > denseJacobianEvaluation, Func <double, double[], SparseDoubleMatrix> sparseJacobianEvaluation, GearsBDFOptions opts ) { NordsieckState currstate = this; NordsieckState newstate = this; int n = currstate._xn.Length; VectorMath.Copy(currstate._en, ecurr); VectorMath.Copy(currstate._xn, xcurr); var x0 = currstate._xn; MatrixMath.Copy(currstate._zn, zcurr); // zcurr now is old nordsieck matrix var qcurr = currstate._qn; // current degree var qmax = currstate._qmax; // max degree var dt = currstate._dt; var t = currstate._tn; MatrixMath.Copy(currstate._zn, z0); // save Nordsieck matrix //Tolerance computation factors double Cq = Math.Pow(qcurr + 1, -1.0); double tau = 1.0 / (Cq * Factorial(qcurr) * l[qcurr - 1][qcurr]); int count = 0; double Dq = 0.0, DqUp = 0.0, DqDown = 0.0; double delta = 0.0; //Scaling factors for the step size changing //with new method order q' = q, q + 1, q - 1, respectively double rSame, rUp, rDown; if (null != denseJacobianEvaluation) { var J = denseJacobianEvaluation(t + dt, xcurr); if (J.GetType() != P?.GetType()) { AllocatePMatrixForJacobian(J); } do { MatrixMath.MapIndexed(J, dt * b[qcurr - 1], (i, j, aij, factor) => (i == j ? 1 : 0) - aij * factor, P, Zeros.AllowSkip); // P = Identity - J*dt*b[qcurr-1] VectorMath.Copy(xcurr, xprev); f(t + dt, xcurr, ftdt); MatrixMath.CopyColumn(z0, 1, colExtract); // 1st derivative/dt VectorMath.Map(ftdt, colExtract, ecurr, dt, (ff, c, e, local_dt) => local_dt * ff - c - e, gm); // gm = dt * f(t + dt, xcurr) - z0.GetColumn(1) - ecurr; gaussSolver.SolveDestructive(P, gm, tmpVec1); VectorMath.Add(ecurr, tmpVec1, ecurr); // ecurr = ecurr + P.SolveGE(gm); VectorMath.Map(x0, ecurr, b[qcurr - 1], (x, e, local_b) => x + e * local_b, xcurr); // xcurr = x0 + b[qcurr - 1] * ecurr; //Row dimension is smaller than zcurr has int M_Rows = ecurr.Length; int M_Columns = l[qcurr - 1].Length; //So, "expand" the matrix MatrixMath.MapIndexed(z0, (i, j, z) => z + (i < M_Rows && j < M_Columns ? ecurr[i] * l[qcurr - 1][j] : 0.0d), zcurr); Dq = ToleranceNorm(ecurr, opts.RelativeTolerance, opts.AbsoluteTolerance, xprev); var factor_deltaE = (1.0 / (qcurr + 2) * l[qcurr - 1][qcurr - 1]); VectorMath.Map(ecurr, currstate._en, factor_deltaE, (e, c, factor) => (e - c) * factor, deltaE); // deltaE = (ecurr - currstate.en)*(1.0 / (qcurr + 2) * l[qcurr - 1][qcurr - 1]) DqUp = ToleranceNorm(deltaE, opts.RelativeTolerance, opts.AbsoluteTolerance, xcurr); zcurr.CopyColumn(qcurr - 1, colExtract); DqDown = ToleranceNorm(colExtract, opts.RelativeTolerance, opts.AbsoluteTolerance, xcurr); delta = Dq / (tau / (2 * (qcurr + 2))); count++; } while (delta > 1.0d && count < opts.NumberOfIterations); } else if (null != sparseJacobianEvaluation) { SparseDoubleMatrix J = sparseJacobianEvaluation(t + dt, xcurr); var P = new SparseDoubleMatrix(J.RowCount, J.ColumnCount); do { J.MapSparseIncludingDiagonal((x, i, j) => (i == j ? 1 : 0) - x * dt * b[qcurr - 1], P); VectorMath.Copy(xcurr, xprev); f(t + dt, xcurr, ftdt); MatrixMath.CopyColumn(z0, 1, colExtract); VectorMath.Map(ftdt, colExtract, ecurr, (ff, c, e) => dt * ff - c - e, gm); // gm = dt * f(t + dt, xcurr) - z0.GetColumn(1) - ecurr; gaussSolver.SolveDestructive(P, gm, tmpVec1); VectorMath.Add(ecurr, tmpVec1, ecurr); // ecurr = ecurr + P.SolveGE(gm); VectorMath.Map(x0, ecurr, (x, e) => x + e * b[qcurr - 1], xcurr); // xcurr = x0 + b[qcurr - 1] * ecurr; //Row dimension is smaller than zcurr has int M_Rows = ecurr.Length; int M_Columns = l[qcurr - 1].Length; //So, "expand" the matrix MatrixMath.MapIndexed(z0, (i, j, z) => z + (i < M_Rows && j < M_Columns ? ecurr[i] * l[qcurr - 1][j] : 0.0d), zcurr); Dq = ToleranceNorm(ecurr, opts.RelativeTolerance, opts.AbsoluteTolerance, xprev); var factor_deltaE = (1.0 / (qcurr + 2) * l[qcurr - 1][qcurr - 1]); VectorMath.Map(ecurr, currstate._en, (e, c) => (e - c) * factor_deltaE, deltaE); // deltaE = (ecurr - currstate.en)*(1.0 / (qcurr + 2) * l[qcurr - 1][qcurr - 1]) DqUp = ToleranceNorm(deltaE, opts.RelativeTolerance, opts.AbsoluteTolerance, xcurr); DqDown = ToleranceNorm(zcurr.GetColumn(qcurr - 1), opts.RelativeTolerance, opts.AbsoluteTolerance, xcurr); delta = Dq / (tau / (2 * (qcurr + 2))); count++; } while (delta > 1.0d && count < opts.NumberOfIterations); } else // neither denseJacobianEvaluation nor sparseJacobianEvaluation valid { throw new ArgumentNullException(nameof(denseJacobianEvaluation), "Either denseJacobianEvaluation or sparseJacobianEvaluation must be set!"); } //====================================== var nsuccess = count < opts.NumberOfIterations ? currstate._nsuccess + 1 : 0; if (count < opts.NumberOfIterations) { flag = false; MatrixMath.Copy(zcurr, newstate._zn); zcurr.CopyColumn(0, newstate._xn); VectorMath.Copy(ecurr, newstate._en); } else { flag = true; // MatrixMath.Copy(currstate.zn, newstate.zn); // null operation since currstate and newstate are identical currstate._zn.CopyColumn(0, newstate._xn); VectorMath.Copy(currstate._en, newstate._en); // null operation since currstate and newstate are identical } //Compute step size scaling factors rUp = 0.0; if (currstate._qn < currstate._qmax) { rUp = rUp = 1.0 / 1.4 / (Math.Pow(DqUp, 1.0 / (qcurr + 2)) + 1e-6); } rSame = 1.0 / 1.2 / (Math.Pow(Dq, 1.0 / (qcurr + 1)) + 1e-6); rDown = 0.0; if (currstate._qn > 1) { rDown = 1.0 / 1.3 / (Math.Pow(DqDown, 1.0 / (qcurr)) + 1e-6); } //====================================== _nsuccess = nsuccess >= _qn ? 0 : nsuccess; //Step size scale operations if (rSame >= rUp) { if (rSame <= rDown && nsuccess >= _qn && _qn > 1) { _qn = _qn - 1; _Dq = DqDown; for (int i = 0; i < n; i++) { for (int j = newstate._qn + 1; j < qmax + 1; j++) { _zn[i, j] = 0.0; } } nsuccess = 0; _rFactor = rDown; } else { // _qn = _qn; _Dq = Dq; _rFactor = rSame; } } else { if (rUp >= rDown) { if (rUp >= rSame && nsuccess >= _qn && _qn < _qmax) { _qn = _qn + 1; _Dq = DqUp; _rFactor = rUp; nsuccess = 0; } else { // _qn = _qn; _Dq = Dq; _rFactor = rSame; } } else { if (nsuccess >= _qn && _qn > 1) { _qn = _qn - 1; _Dq = DqDown; for (int i = 0; i < n; i++) { for (int j = newstate._qn + 1; j < qmax + 1; j++) { _zn[i, j] = 0.0; } } nsuccess = 0; _rFactor = rDown; } else { // _qn = _qn; _Dq = Dq; _rFactor = rSame; } } } _dt = dt; _tn = t; }