From 829d7c7ef609aebb52ac7055170edb6767d65d96 Mon Sep 17 00:00:00 2001 From: rjg18 Date: Fri, 4 Jun 2021 22:57:25 -0400 Subject: [PATCH 01/17] MATLODEV2 ERK and Infrastructure Added --- CHANGE_LOG.md | 7 ++ README.md | 27 +++++ src/+matlode/+rk/+erk/ClassicRK4.m | 31 ++++++ src/+matlode/+rk/+erk/DormandPrince.m | 34 +++++++ src/+matlode/+rk/+erk/ERK.m | 99 +++++++++++++++++++ src/+matlode/+rk/+erk/ForwardEuler.m | 27 +++++ src/+matlode/+rk/RungeKutta.m | 45 +++++++++ src/+matlode/+stepsizecontroller/Fixed.m | 34 +++++++ .../+stepsizecontroller/StepSizeController.m | 15 +++ src/+matlode/+util/+errnorm/standardNorm.m | 9 ++ src/+matlode/Integrator.m | 53 ++++++++++ src/+matlode/OneStepIntegrator.m | 23 +++++ 12 files changed, 404 insertions(+) create mode 100644 CHANGE_LOG.md create mode 100644 README.md create mode 100644 src/+matlode/+rk/+erk/ClassicRK4.m create mode 100644 src/+matlode/+rk/+erk/DormandPrince.m create mode 100644 src/+matlode/+rk/+erk/ERK.m create mode 100644 src/+matlode/+rk/+erk/ForwardEuler.m create mode 100644 src/+matlode/+rk/RungeKutta.m create mode 100644 src/+matlode/+stepsizecontroller/Fixed.m create mode 100644 src/+matlode/+stepsizecontroller/StepSizeController.m create mode 100644 src/+matlode/+util/+errnorm/standardNorm.m create mode 100644 src/+matlode/Integrator.m create mode 100644 src/+matlode/OneStepIntegrator.m diff --git a/CHANGE_LOG.md b/CHANGE_LOG.md new file mode 100644 index 0000000..d6002b0 --- /dev/null +++ b/CHANGE_LOG.md @@ -0,0 +1,7 @@ + + +### 2.0.0beta1: ++ Change log ++ ERK, RungeKutta, DormanPrince, ClassicRK4, ForwardEuler ++ Fixed +- Removed MATLODE v1 \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..ded399f --- /dev/null +++ b/README.md @@ -0,0 +1,27 @@ + +# MATLODE V2.0.0beta1 + +##Example Code +*Using ODE Test Problems + +```matlab +problem = otp.hires.presets.Canonical; +integrator = matlode.rk.erk.ClassicRK4; +options = integrator.matlodeSets('StepsN', 100000); +sol = integrator.integrate(problem.Rhs.F, problem.TimeSpan, problem.Y0); +``` +OR +```matlab +problem = otp.hires.presets.Canonical; +integrator = matlode.rk.erk.ClassicRK4; +sol = integrator.integrate(problem.Rhs.F, problem.TimeSpan, problem.Y0, 'StepsN', 100000); +``` +OR +```matlab +problem = otp.hires.presets.Canonical; +integrator = matlode.rk.erk.ClassicRK4; +options = integrator.matlodeSets('StepsN', 100000); +sol = integrator.integrate(problem.Rhs.F, problem.TimeSpan, problem.Y0, options, 'AbsTol', 1e-8); +``` + +[insert other useful stuff] \ No newline at end of file diff --git a/src/+matlode/+rk/+erk/ClassicRK4.m b/src/+matlode/+rk/+erk/ClassicRK4.m new file mode 100644 index 0000000..245b82f --- /dev/null +++ b/src/+matlode/+rk/+erk/ClassicRK4.m @@ -0,0 +1,31 @@ +classdef ClassicRK4 < matlode.rk.erk.ERK + methods + function obj = ClassicRK4() + + %TODO + %include reference + + a = [[0, 0, 0, 0]; + [1/2, 0, 0, 0]; + [0, 1/2, 0, 0]; + [0, 0, 0, 1]]; + + b = [1/6, 1/3, 1/3, 1/6]; + + bHat = []; + + c = [0, 1/2, 1/2, 1]; + + order = 4; + + embbededOrder = 0; + + obj = obj@matlode.rk.erk.ERK(a, b, bHat, c, order, embbededOrder); + end + + function sol = integrate(obj, f, tspan, y0, varargin) + sol = integrate@matlode.rk.erk.ERK(obj, f, tspan, y0, 'StepSizeController', matlode.stepsizecontroller.Fixed(1000), varargin{:}); + end + end +end + diff --git a/src/+matlode/+rk/+erk/DormandPrince.m b/src/+matlode/+rk/+erk/DormandPrince.m new file mode 100644 index 0000000..eb7215c --- /dev/null +++ b/src/+matlode/+rk/+erk/DormandPrince.m @@ -0,0 +1,34 @@ +classdef DormandPrince < matlode.rk.erk.ERK + methods + function obj = DormandPrince() + + %TODO + %include reference + + a = [[0, 0, 0, 0, 0, 0, 0]; + [1/5, 0, 0, 0, 0, 0, 0]; + [3/40, 9/40, 0, 0, 0, 0, 0]; + [44/45, -56/15, 32/9, 0, 0, 0, 0]; + [19372/6561, -25360/2187, 64448/6561, -212/729, 0, 0, 0]; + [9017/3168, -355/33, 46732/5247, 49/176, -5103/18656, 0, 0]; + [35/384, 0, 500/1113, 125/192, -2187/6784, 11/84, 0]]; + + b = [35/384, 0, 500/1113, 125/192, -2187/6784, 11/84, 0]; + + bHat = [5179/57600, 0, 7571/16695, 393/640, -92097/339200, 187/2100, 1/40]; + + c = [0, 1/5, 3/10, 4/5, 8/9, 1, 1]; + + order = 5; + + embbededOrder = 4; + + obj = obj@matlode.rk.erk.ERK(a, b, bHat, c, order, embbededOrder); + end + + function sol = integrate(obj, f, tspan, y0, varargin) + sol = integrate@matlode.rk.erk.ERK(obj, f, tspan, y0, 'StepSizeController', matlode.stepsizecontroller.Fixed(1000), varargin{:}); + end + end +end + diff --git a/src/+matlode/+rk/+erk/ERK.m b/src/+matlode/+rk/+erk/ERK.m new file mode 100644 index 0000000..745491d --- /dev/null +++ b/src/+matlode/+rk/+erk/ERK.m @@ -0,0 +1,99 @@ +classdef ERK < matlode.rk.RungeKutta + %Explicit Runge Kutta class + + properties (SetAccess = immutable) + a + b + bHat + c + stage + order + embeddedOrder + adaptable + FSAL + end + + methods + + + + function obj = ERK(a, b, bHat, c, order, embeddedOrder) + + obj.a = a; + obj.b = b; + obj.c = c; + obj.bHat = bHat; + obj.embeddedOrder = embeddedOrder; + obj.order = order; + obj.stage = length(b); + obj.FSAL = all(a(end, :) == b) && all(a(1, :) == 0); + obj.adaptable = ~isempty(bHat); + end + + function sol = integrate(obj, f, tspan, y0, varargin) + + opts = obj.matlodeSets(varargin{:}); + + numVars = length(y0); + t = tspan(1); + k = zeros(numVars, obj.stage); + sol.y = y0; + + sol.stats.nsteps = 0; + sol.stats.nfailed = 0; + sol.stats.nfevals = 0; + + fsalStart = uint32(obj.FSAL) + 1; + fevalIterCounts = obj.stage - fsalStart + 1; + + + [h, k(:,1)] = opts.StepSizeController.startingStep(f, tspan, y0, obj.order, opts); + + + while t < tspan(end) + + for i = fsalStart:obj.stage + k(:, i) = f(t + h * obj.c(i), sol.y + h * (k(:, 1:i-1) * obj.a(i, 1:i-1)')); + end + + sol.stats.nfevals = sol.stats.nfevals + fevalIterCounts; + + yCurrent = sol.y + h * (k * obj.b'); + err = 0; + + + if opts.StepSizeController.adaptive + yEmbbeded = sol.y + h * (k * obj.bHat); + err = opts.ErrNorm(yCurrent, yEmbbeded, opts); + end + + [accept, h, t] = opts.StepSizeController.newStepSize(t, tspan, h, err, obj.embeddedOrder, sol.stats, opts); + + if accept + sol.y = yCurrent; + sol.stats.nsteps = sol.stats.nsteps + 1; + + + if obj.FSAL + k(:, 1) = k(:, end); + end + else + + sol.stats.nfailed = sol.stats.nfailed + 1; + end + end + + + end + + function opts = matlodeSets(obj, varargin) + p = inputParser; + + %ERK specific options + + opts = obj.matlodeSetsHelper(p, varargin{:}); + end + end + +end + diff --git a/src/+matlode/+rk/+erk/ForwardEuler.m b/src/+matlode/+rk/+erk/ForwardEuler.m new file mode 100644 index 0000000..1218a2d --- /dev/null +++ b/src/+matlode/+rk/+erk/ForwardEuler.m @@ -0,0 +1,27 @@ +classdef ForwardEuler < matlode.rk.erk.ERK + methods + function obj = ForwardEuler() + %TODO + %include reference + + a = [[0]]; + + b = [1]; + + bHat = []; + + c = [0]; + + order = 1; + + embbededOrder = 0; + + obj = obj@matlode.rk.erk.ERK(a, b, bHat, c, order, embbededOrder); + end + + function sol = integrate(obj, f, tspan, y0, varargin) + sol = integrate@matlode.rk.erk.ERK(obj, f, tspan, y0, 'StepSizeController', matlode.stepsizecontroller.Fixed(1000), varargin{:}); + end + end +end + diff --git a/src/+matlode/+rk/RungeKutta.m b/src/+matlode/+rk/RungeKutta.m new file mode 100644 index 0000000..a08eee4 --- /dev/null +++ b/src/+matlode/+rk/RungeKutta.m @@ -0,0 +1,45 @@ +classdef (Abstract) RungeKutta < matlode.OneStepIntegrator + %RungeKutta integrator + + properties (Abstract, SetAccess = immutable) + bHat + embeddedOrder + order + FSAL + end + + methods + function obj = fromCoeffs(a, b, bHat, c, order, embededOrder) + + + if istril(a) + if any(diag(a)) + %TODO + % set as DIRK + return + end + + obj = ERK(a, b, bHat, c, order, embededOrder); + return + end + + %TODO + + %Set as FIRK + + end + end + + methods (Access = protected) + function opts = matlodeSetsHelper(obj, p, varargin) + + %RungeKutta sepcific options + + opts = matlodeSetsHelper@matlode.OneStepIntegrator(obj, p, varargin{:}); + + end + end + + +end + diff --git a/src/+matlode/+stepsizecontroller/Fixed.m b/src/+matlode/+stepsizecontroller/Fixed.m new file mode 100644 index 0000000..e65b775 --- /dev/null +++ b/src/+matlode/+stepsizecontroller/Fixed.m @@ -0,0 +1,34 @@ +classdef Fixed < matlode.stepsizecontroller.StepSizeController + %Fixed stepsize controller + + properties (Constant) + adaptive = false; + end + + properties (SetAccess = immutable, GetAccess = private) + steps + end + + methods + + function obj = Fixed(steps) + obj.steps = steps; + end + + function [h0, f0] = startingStep(obj, f, tspan, y0, ~, options) + if options.InitialStep ~= 0 + warning('Ignoring initialStep option, Controller is Fixed'); + end + + f0 = f(tspan(1), y0); + h0 = (tspan(2) - tspan(1)) / obj.steps; + end + + function [accept, hCur, tNew] = newStepSize(~, ~, tspan, hCur, ~, ~, stepdlx, ~) + accept = true; + tNew = (stepdlx.nsteps + 1) * hCur + tspan(1); + end + + end +end + diff --git a/src/+matlode/+stepsizecontroller/StepSizeController.m b/src/+matlode/+stepsizecontroller/StepSizeController.m new file mode 100644 index 0000000..b6f45d0 --- /dev/null +++ b/src/+matlode/+stepsizecontroller/StepSizeController.m @@ -0,0 +1,15 @@ +classdef (Abstract) StepSizeController < handle + %Template for a step size controller + + properties (Abstract, Constant) + adaptive + end + + methods (Abstract) + + [h0, f0] = startingStep(obj, f, tspan, y0, order, options); + [accept, hNew, tNew] = newStepSize(obj, tCur, tspan, hCur, err, embeddedOrder, stepdlx, options); + end + +end + diff --git a/src/+matlode/+util/+errnorm/standardNorm.m b/src/+matlode/+util/+errnorm/standardNorm.m new file mode 100644 index 0000000..e711a8d --- /dev/null +++ b/src/+matlode/+util/+errnorm/standardNorm.m @@ -0,0 +1,9 @@ +function err = standardNorm(y, yHat, options) + %procedure described in solving ODEs I Harrier + %cannot assume that user has signal processing toolbox + + sc = (options.AbsTol + max(abs(y), abs(yHat)) .* options.RelTol); + + err = sqrt(1/length(y)) * norm((y - yHat) ./ sc); +end + diff --git a/src/+matlode/Integrator.m b/src/+matlode/Integrator.m new file mode 100644 index 0000000..5d341c6 --- /dev/null +++ b/src/+matlode/Integrator.m @@ -0,0 +1,53 @@ +classdef (Abstract) Integrator < handle + %INTEGRATOR Summary of this class goes here + % Detailed explanation goes here + + properties (Abstract, SetAccess = immutable) + c + adaptable + end + + methods (Abstract) + + %integrate + sol = integrate(obj, func, tspan, y0, varargin) + opts = matlodeSets(obj, varargin) + + end + + methods (Access = protected) + + + function opts = matlodeSetsHelper(obj, p, varargin) + + p.addParameter('AbsTol', 1e-6); + p.addParameter('RelTol', 1e-6); + p.addParameter('InitialStep', 0); + p.addParameter('StepSizeController', matlode.stepsizecontroller.Fixed(1000)); + p.addParameter('StepsN', 0); + p.addParameter('ErrNorm', @(y, yHat, options) matlode.util.errnorm.standardNorm(y, yHat, options)); + p.addParameter('NewtonTol', 1e-7); + p.addParameter('NewtonMaxItr', 25); + p.addParameter('Jacobian', []); + + p.parse(varargin{:}); + + opts = p.Results; + + %shorthanded fixed steps + if opts.StepsN > 0 + opts.StepSizeController = matlode.stepsizecontroller.Fixed(opts.StepsN); + end + opts = rmfield(opts, 'StepsN'); + + %make sure a method can use a adaptive controller + if ~obj.adaptable && opts.StepSizeController.adaptive + warning('Current method does not have the ability to use a adaptive step size controller'); + %overrider user choice??? + end + + end + end + +end + diff --git a/src/+matlode/OneStepIntegrator.m b/src/+matlode/OneStepIntegrator.m new file mode 100644 index 0000000..f6653e6 --- /dev/null +++ b/src/+matlode/OneStepIntegrator.m @@ -0,0 +1,23 @@ +classdef (Abstract) OneStepIntegrator < matlode.Integrator + %One Step Integrator template + + properties (Abstract, SetAccess = immutable) + a + b + stage + end + + methods (Access = protected) + function opts = matlodeSetsHelper(obj, p, varargin) + + + + %OneStepIntegrator Specific options + + opts = matlodeSetsHelper@matlode.Integrator(obj, p, varargin{:}); + + end + + end +end + From b16d3f4f366ea84bfd0a14f1f79f8fb33606d722 Mon Sep 17 00:00:00 2001 From: Steven Roberts Date: Wed, 23 Jun 2021 10:47:29 -0400 Subject: [PATCH 02/17] Add linear solver framework --- .gitignore | 7 +++ .../+linearsolver/DecompositionLinearSolver.m | 22 +++++++++ src/+matlode/+linearsolver/LinearSolver.m | 8 +++ .../+linearsolver/MatrixFreeLinearSolver.m | 34 +++++++++++++ .../+linearsolver/MatrixLinearSolver.m | 49 +++++++++++++++++++ 5 files changed, 120 insertions(+) create mode 100644 .gitignore create mode 100644 src/+matlode/+linearsolver/DecompositionLinearSolver.m create mode 100644 src/+matlode/+linearsolver/LinearSolver.m create mode 100644 src/+matlode/+linearsolver/MatrixFreeLinearSolver.m create mode 100644 src/+matlode/+linearsolver/MatrixLinearSolver.m diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b352972 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +*.asv +*.m~ +*.mex* +*.mlappinstall +*.mltbx +octave-workspace + diff --git a/src/+matlode/+linearsolver/DecompositionLinearSolver.m b/src/+matlode/+linearsolver/DecompositionLinearSolver.m new file mode 100644 index 0000000..f0d58bb --- /dev/null +++ b/src/+matlode/+linearsolver/DecompositionLinearSolver.m @@ -0,0 +1,22 @@ +classdef DecompositionLinearSolver < matlode.linearsolver.MatrixLinearSolver + properties (SetAccess = immutable, GetAccess = private) + Args + end + + methods + function obj = DecompositionLinearSolver(varargin) + obj = obj@matlode.linearsolver.MatrixLinearSolver(); + obj.Args = varargin; + end + + function system = preprocess(obj, updateState, updateTimestep, system, t, y, f, m1, mass, m2, jac) + system = preprocess@matlode.linearsolver.MatrixLinearSolver( ... + obj, updateState, updateTimestep, system, t, y, f, m1, mass, m2, jac); + + if updateState || updateTimestep + system.matrix = decomposition(system.matrix, obj.Args{:}); + end + end + end +end + diff --git a/src/+matlode/+linearsolver/LinearSolver.m b/src/+matlode/+linearsolver/LinearSolver.m new file mode 100644 index 0000000..e5aee67 --- /dev/null +++ b/src/+matlode/+linearsolver/LinearSolver.m @@ -0,0 +1,8 @@ +classdef (Abstract) LinearSolver + methods (Abstract) + system = preprocess(obj, updateState, updateTimestep, oldSystem, t, y, f, m1, mass, m2, jac); + + sol = solve(obj, x, system, t, y, f, m1, mass, m2, jac); + end +end + diff --git a/src/+matlode/+linearsolver/MatrixFreeLinearSolver.m b/src/+matlode/+linearsolver/MatrixFreeLinearSolver.m new file mode 100644 index 0000000..311f535 --- /dev/null +++ b/src/+matlode/+linearsolver/MatrixFreeLinearSolver.m @@ -0,0 +1,34 @@ +classdef MatrixFreeLinearSolver < matlode.linearsolver.LinearSolver + properties (SetAccess = immutable, GetAccess = private) + Solver + end + + methods + function obj = MatrixFreeLinearSolver(solver) + if nargin < 1 + solver = @gmres; + end + + obj.Solver = solver; + end + + function system = preprocess(~, ~, ~, system, ~, ~, ~, ~, ~, ~, ~) + end + + function sol = solve(obj, x, ~, t, y, ~, m1, mass, m2, jac) + if length(m1) == 1 + if isempty(jac) + jvp = @(v) m1 * mass(t, y, v); + elseif isempty(mass) + jvp = @(v) m1 * v - m2 * jac(t, y, v); + else + jvp = @(v) m1 * mass(t, y, v) - m2 * jac(t, y, v); + end + else + error('Matrix-free block systems are not supported yet'); + end + sol = obj.Solver(jvp, x); + end + end +end + diff --git a/src/+matlode/+linearsolver/MatrixLinearSolver.m b/src/+matlode/+linearsolver/MatrixLinearSolver.m new file mode 100644 index 0000000..f036bc8 --- /dev/null +++ b/src/+matlode/+linearsolver/MatrixLinearSolver.m @@ -0,0 +1,49 @@ +classdef MatrixLinearSolver < matlode.linearsolver.LinearSolver + properties (SetAccess = immutable, GetAccess = private) + Solver + end + + methods + function obj = MatrixLinearSolver(solver) + if nargin < 1 + solver = @mldivide; + end + + obj.Solver = solver; + end + + function system = preprocess(~, updateState, updateTimestep, system, t, y, ~, m1, mass, m2, jac) + init = isempty(system); + + if updateState + if ~isnumeric(jac) + system.jac = jac(t, y); + elseif init + system.jac = jac; + end + + if ~isnumeric(mass) + system.mass = mass(t, y); + elseif init + if isempty(mass) + system.mass = eye(size(system.jac), 'like', system.jac); + else + system.mass = mass; + end + end + end + + if updateState || updateTimestep + system.matrix = kron(m1, system.mass); + if m2 ~= 0 + system.matrix = system.matrix - kron(m2, jac); + end + end + end + + function sol = solve(obj, x, system, ~, ~, ~, ~, ~, ~, ~) + sol = obj.Solver(system.matrix, x); + end + end +end + From d35bbf5a82dd9ae77bbe0e3b0a555cb1021688af Mon Sep 17 00:00:00 2001 From: rjg18 Date: Tue, 29 Jun 2021 14:26:40 -0400 Subject: [PATCH 03/17] Added StepSizeControllers, ErrNorm, DenseOutput, and Optimizations --- CHANGE_LOG.md | 23 +- README.md | 22 +- src/+matlode/+denseoutput/DenseOutput.m | 6 + src/+matlode/+denseoutput/Direct.m | 24 ++ src/+matlode/+denseoutput/Hermite.m | 18 + src/+matlode/+denseoutput/Linear.m | 20 ++ src/+matlode/+denseoutput/RKDense.m | 22 ++ src/+matlode/+errnorm/HistNorm.m | 11 + src/+matlode/+errnorm/InfNorm.m | 11 + src/+matlode/+errnorm/NormErr.m | 6 + src/+matlode/+errnorm/StandardNorm.m | 11 + src/+matlode/+rk/+erk/ClassicRK4.m | 38 ++- src/+matlode/+rk/+erk/DormandPrince.m | 58 +++- src/+matlode/+rk/+erk/ERK.m | 315 +++++++++++++++--- src/+matlode/+rk/+erk/ForwardEuler.m | 24 +- src/+matlode/+rk/RungeKutta.m | 19 +- .../+stepsizecontroller/+soberpresets/H321.m | 17 + src/+matlode/+stepsizecontroller/Fixed.m | 21 +- src/+matlode/+stepsizecontroller/Gustafson.m | 70 ++++ .../+stepsizecontroller/SoberlandController.m | 78 +++++ .../+stepsizecontroller/StandardController.m | 49 +++ .../+stepsizecontroller/StartingController.m | 41 +++ .../+stepsizecontroller/StepSizeController.m | 13 +- src/+matlode/+util/+errnorm/standardNorm.m | 9 - src/+matlode/+util/CoefficentTransformers.m | 15 + src/+matlode/Integrator.m | 93 ++++-- src/+matlode/OneStepIntegrator.m | 11 +- 27 files changed, 881 insertions(+), 164 deletions(-) create mode 100644 src/+matlode/+denseoutput/DenseOutput.m create mode 100644 src/+matlode/+denseoutput/Direct.m create mode 100644 src/+matlode/+denseoutput/Hermite.m create mode 100644 src/+matlode/+denseoutput/Linear.m create mode 100644 src/+matlode/+denseoutput/RKDense.m create mode 100644 src/+matlode/+errnorm/HistNorm.m create mode 100644 src/+matlode/+errnorm/InfNorm.m create mode 100644 src/+matlode/+errnorm/NormErr.m create mode 100644 src/+matlode/+errnorm/StandardNorm.m create mode 100644 src/+matlode/+stepsizecontroller/+soberpresets/H321.m create mode 100644 src/+matlode/+stepsizecontroller/Gustafson.m create mode 100644 src/+matlode/+stepsizecontroller/SoberlandController.m create mode 100644 src/+matlode/+stepsizecontroller/StandardController.m create mode 100644 src/+matlode/+stepsizecontroller/StartingController.m delete mode 100644 src/+matlode/+util/+errnorm/standardNorm.m create mode 100644 src/+matlode/+util/CoefficentTransformers.m diff --git a/CHANGE_LOG.md b/CHANGE_LOG.md index d6002b0..699fe6d 100644 --- a/CHANGE_LOG.md +++ b/CHANGE_LOG.md @@ -1,7 +1,20 @@ -### 2.0.0beta1: -+ Change log -+ ERK, RungeKutta, DormanPrince, ClassicRK4, ForwardEuler -+ Fixed -- Removed MATLODE v1 \ No newline at end of file +### 2.0.0beta2: ### +\+ StepSizeControllers: Standard, Gustafson, Soberland, H321 +\+ DenseOutput: DirectFunctions, RKDense, Linear, Hermite +\+ ErrNorm: Standard, History, Inf +\+ CoefficentCasting +\+ IntegratTo +\~ Updated Options +\~ Improved Time Loop Structure +\~ Optimizations +\~ DenseOuput Coefficents +\- Removed MatlodeSets and some option changes + + +### 2.0.0beta1: ### +\+ Change log +\+ ERK, RungeKutta, DormanPrince, ClassicRK4, ForwardEuler +\+ Fixed +\- Removed MATLODE v1 \ No newline at end of file diff --git a/README.md b/README.md index ded399f..e6383ea 100644 --- a/README.md +++ b/README.md @@ -6,22 +6,12 @@ ```matlab problem = otp.hires.presets.Canonical; -integrator = matlode.rk.erk.ClassicRK4; -options = integrator.matlodeSets('StepsN', 100000); -sol = integrator.integrate(problem.Rhs.F, problem.TimeSpan, problem.Y0); -``` -OR -```matlab -problem = otp.hires.presets.Canonical; -integrator = matlode.rk.erk.ClassicRK4; -sol = integrator.integrate(problem.Rhs.F, problem.TimeSpan, problem.Y0, 'StepsN', 100000); -``` -OR -```matlab -problem = otp.hires.presets.Canonical; -integrator = matlode.rk.erk.ClassicRK4; -options = integrator.matlodeSets('StepsN', 100000); -sol = integrator.integrate(problem.Rhs.F, problem.TimeSpan, problem.Y0, options, 'AbsTol', 1e-8); +integrator = matlode.rk.erk.DormanPrince; + +options.ErrNorm = matlode.errnorm.HistNorm.errEstimate(1e-6, 1e-6); +options.StepSizeController = matlode.stepsizecontroller.Gustafson; + +sol = integrator.integrate(problem.Rhs.F, problem.TimeSpan, problem.Y0, options); ``` [insert other useful stuff] \ No newline at end of file diff --git a/src/+matlode/+denseoutput/DenseOutput.m b/src/+matlode/+denseoutput/DenseOutput.m new file mode 100644 index 0000000..f904736 --- /dev/null +++ b/src/+matlode/+denseoutput/DenseOutput.m @@ -0,0 +1,6 @@ +classdef (Abstract) DenseOutput < handle + methods (Abstract) + [denseY, fEvals] = denseOut(obj, f, t, tNeeded, yC, yN, k, h) + end +end + diff --git a/src/+matlode/+denseoutput/Direct.m b/src/+matlode/+denseoutput/Direct.m new file mode 100644 index 0000000..c879b2e --- /dev/null +++ b/src/+matlode/+denseoutput/Direct.m @@ -0,0 +1,24 @@ +classdef Direct < matlode.denseoutput.DenseOutput + %This class is for custom denseoutputs that methods could require + %The custum dense output cannot require f evaluations + %Function must be in row format + + properties (SetAccess = immutable) + kFuncs + end + + methods + function obj = Direct(kfuncs) + obj.kFuncs = kfuncs; + end + + function [denseY, fEvals] = denseOut(obj, ~, t, tNeeded, yC, ~, k, h) + + theta = (tNeeded - t) / h; + + denseY = yC + (k * h) * obj.kFuncs(theta); + fEvals = 0; + end + end +end + diff --git a/src/+matlode/+denseoutput/Hermite.m b/src/+matlode/+denseoutput/Hermite.m new file mode 100644 index 0000000..90a2281 --- /dev/null +++ b/src/+matlode/+denseoutput/Hermite.m @@ -0,0 +1,18 @@ +classdef Hermite < matlode.denseoutput.DenseOutput + %HERMITE Formula obtained from Solving ODEs I Chapter II.6 + + methods + function obj = Hermite() + end + + function [denseY, fEvals] = denseOut(~, f, t, tNeeded, yC, yN, ~, h) + + theta = (tNeeded - t) / h; + f0 = f(t, yC); + f1 = f(t + h, yN); + denseY = (1 - theta) * yC + theta * yN + theta*(theta - 1) * ((1-2*theta) * (yN - yC) + (theta - 1) * (h * f0 + theta * h * f1)); + fEvals = 2; + end + end +end + diff --git a/src/+matlode/+denseoutput/Linear.m b/src/+matlode/+denseoutput/Linear.m new file mode 100644 index 0000000..acfed0f --- /dev/null +++ b/src/+matlode/+denseoutput/Linear.m @@ -0,0 +1,20 @@ +classdef Linear < matlode.denseoutput.DenseOutput + + properties (SetAccess = immutable) + b + end + + methods + function obj = Linear(b) + obj.b = b; + end + + function [denseY, fEvals] = denseOut(obj, ~, t, tNeeded, yC, ~, k, h) + + theta = (tNeeded - t) / h; + denseY = yC + (k * h) * (obj.b' * theta) ; + fEvals = 0; + end + end +end + diff --git a/src/+matlode/+denseoutput/RKDense.m b/src/+matlode/+denseoutput/RKDense.m new file mode 100644 index 0000000..d28301a --- /dev/null +++ b/src/+matlode/+denseoutput/RKDense.m @@ -0,0 +1,22 @@ +classdef RKDense < matlode.denseoutput.DenseOutput + %RKDENSE Used for dense output computed from RK trees + + properties (SetAccess = immutable) + bTheta + end + + methods + function obj = RKDense(b) + obj.bTheta = @(theta) b * theta.^(1:size(b,2))'; + end + + function [denseY, fEvals] = denseOut(obj, ~, t, tNeeded, yC, ~, k, h) + + theta = (tNeeded - t) / h; + + denseY = yC + (k * h) * obj.bTheta(theta); + fEvals = 0; + end + end +end + diff --git a/src/+matlode/+errnorm/HistNorm.m b/src/+matlode/+errnorm/HistNorm.m new file mode 100644 index 0000000..6c44923 --- /dev/null +++ b/src/+matlode/+errnorm/HistNorm.m @@ -0,0 +1,11 @@ +classdef HistNorm < matlode.errnorm.NormErr + methods (Static) + function errFunc = errEstimate(AbsTol, RelTol) + + scFunc = @(y) (AbsTol + max(abs(y(:, 1)), abs(y(:, 2))) * RelTol); + errFunc = @(y, yE) sqrt(1/size(y,1)) * norm(yE ./ scFunc(y)); + + end + end +end + diff --git a/src/+matlode/+errnorm/InfNorm.m b/src/+matlode/+errnorm/InfNorm.m new file mode 100644 index 0000000..e83702a --- /dev/null +++ b/src/+matlode/+errnorm/InfNorm.m @@ -0,0 +1,11 @@ +classdef InfNorm < matlode.errnorm.NormErr + methods (Static) + function errFunc = errEstimate(AbsTol, RelTol) + + scFunc = @(y) (AbsTol + abs(y(:, 1)) * RelTol); + errFunc = @(y, yE) norm(yE ./ scFunc(y), 'Inf'); + + end + end +end + diff --git a/src/+matlode/+errnorm/NormErr.m b/src/+matlode/+errnorm/NormErr.m new file mode 100644 index 0000000..5f5a08a --- /dev/null +++ b/src/+matlode/+errnorm/NormErr.m @@ -0,0 +1,6 @@ +classdef (Abstract) NormErr + methods (Abstract,Static) + errFunc = errEstimate(AbsTol, RelTol) + end +end + diff --git a/src/+matlode/+errnorm/StandardNorm.m b/src/+matlode/+errnorm/StandardNorm.m new file mode 100644 index 0000000..c6eb454 --- /dev/null +++ b/src/+matlode/+errnorm/StandardNorm.m @@ -0,0 +1,11 @@ +classdef StandardNorm < matlode.errnorm.NormErr + methods (Static) + function errFunc = errEstimate(AbsTol, RelTol) + + scFunc = @(y) (AbsTol + abs(y(:, 1)) * RelTol); + errFunc = @(y, yE) sqrt(1/size(y,1)) * norm(yE ./ scFunc(y)); + + end + end +end + diff --git a/src/+matlode/+rk/+erk/ClassicRK4.m b/src/+matlode/+rk/+erk/ClassicRK4.m index 245b82f..ae72c45 100644 --- a/src/+matlode/+rk/+erk/ClassicRK4.m +++ b/src/+matlode/+rk/+erk/ClassicRK4.m @@ -1,26 +1,48 @@ classdef ClassicRK4 < matlode.rk.erk.ERK + properties (SetAccess = immutable) + DenseOut + end + methods - function obj = ClassicRK4() + function obj = ClassicRK4(datatype) %TODO %include reference - a = [[0, 0, 0, 0]; - [1/2, 0, 0, 0]; - [0, 1/2, 0, 0]; - [0, 0, 0, 1]]; + % p = 4 & hatp = 0 & s = 4 + + if nargin == 0 + datatype = 'double'; + end + + caster = @(x) matlode.util.CoefficentTransformers.transform(x,datatype); + + a = caster(join(['[[0, 0, 0, 0];',... + '[1/2, 0, 0, 0];',... + '[0, 1/2, 0, 0];',... + '[0, 0, 0, 1]]'])); - b = [1/6, 1/3, 1/3, 1/6]; + b = caster('[1/6, 1/3, 1/3, 1/6]'); bHat = []; - c = [0, 1/2, 1/2, 1]; + e = []; + + c = caster('[0, 1/2, 1/2, 1]'); order = 4; embbededOrder = 0; - obj = obj@matlode.rk.erk.ERK(a, b, bHat, c, order, embbededOrder); + obj = obj@matlode.rk.erk.ERK(a, b, bHat, c, e, order, embbededOrder); + + %p* = 3 & s* = 3 + denseMatrix = caster(join(['[1, -3/2, 2/3];',... + '[0, 1, -2/3]',... + '[0, 1, -2/3]',... + '[0, 1/2, 2/3]'])); + + obj.DenseOut = matlode.denseoutput.RKDense(denseMatrix); end function sol = integrate(obj, f, tspan, y0, varargin) diff --git a/src/+matlode/+rk/+erk/DormandPrince.m b/src/+matlode/+rk/+erk/DormandPrince.m index eb7215c..7a313a4 100644 --- a/src/+matlode/+rk/+erk/DormandPrince.m +++ b/src/+matlode/+rk/+erk/DormandPrince.m @@ -1,33 +1,61 @@ classdef DormandPrince < matlode.rk.erk.ERK + properties (SetAccess = immutable) + DenseOut + end + methods - function obj = DormandPrince() + function obj = DormandPrince(datatype) %TODO - %include reference - - a = [[0, 0, 0, 0, 0, 0, 0]; - [1/5, 0, 0, 0, 0, 0, 0]; - [3/40, 9/40, 0, 0, 0, 0, 0]; - [44/45, -56/15, 32/9, 0, 0, 0, 0]; - [19372/6561, -25360/2187, 64448/6561, -212/729, 0, 0, 0]; - [9017/3168, -355/33, 46732/5247, 49/176, -5103/18656, 0, 0]; - [35/384, 0, 500/1113, 125/192, -2187/6784, 11/84, 0]]; + %include reference for method + + if nargin == 0 + datatype = 'double'; + end + + caster = @(x) matlode.util.CoefficentTransformers.transform(x,datatype); + + + a = caster(join(['[[0, 0, 0, 0, 0, 0, 0];',... + '[1/5, 0, 0, 0, 0, 0, 0];',... + '[3/40, 9/40, 0, 0, 0, 0, 0];',... + '[44/45, -56/15, 32/9, 0, 0, 0, 0];',... + '[19372/6561, -25360/2187, 64448/6561, -212/729, 0, 0, 0];',... + '[9017/3168, -355/33, 46732/5247, 49/176, -5103/18656, 0, 0];',... + '[35/384, 0, 500/1113, 125/192, -2187/6784, 11/84, 0]]'])); - b = [35/384, 0, 500/1113, 125/192, -2187/6784, 11/84, 0]; + b = caster('[35/384, 0, 500/1113, 125/192, -2187/6784, 11/84, 0]'); + + bHat = caster('[5179/57600, 0, 7571/16695, 393/640, -92097/339200, 187/2100, 1/40]'); - bHat = [5179/57600, 0, 7571/16695, 393/640, -92097/339200, 187/2100, 1/40]; + e = caster('[71/57600, 0, -71/16695, 71/1920, -17253/339200, 22/525, -1/40]'); - c = [0, 1/5, 3/10, 4/5, 8/9, 1, 1]; + c = caster('[0, 1/5, 3/10, 4/5, 8/9, 1, 1]'); order = 5; embbededOrder = 4; - obj = obj@matlode.rk.erk.ERK(a, b, bHat, c, order, embbededOrder); + obj = obj@matlode.rk.erk.ERK(a, b, bHat, c, e, order, embbededOrder); + + %Shampien formula for dense output given in the Solving ODE + %book on pg192 (6.12) + %the Dense output has been transformed from a function form to + %a matrix form + %p* == 4 s* == 5 + denseMatrix = caster(join(['[[1, -(4034104133/1410260304), 105330401/33982176, -(13107642775/11282082432), 6542295/470086768];',... + '[ 0, 0, 0, 0, 0];',... + '[ 0, 132343189600/32700410799, -(833316000/131326951), 91412856700/32700410799, -(523383600/10900136933)];',... + '[ 0, -(115792950/29380423), 185270875/16991088, -(12653452475/1880347072), 98134425/235043384];',... + '[ 0, 70805911779/24914598704, -(4531260609/600351776), 988140236175/199316789632, -(14307999165/24914598704)];',... + '[ 0, -(331320693/205662961), 31361737/7433601, -(2426908385/822651844), 97305120/205662961];',... + '[ 0, 44764047/29380423, -(1532549/353981), 90730570/29380423, -(8293050/29380423)]]'])); + obj.DenseOut = matlode.denseoutput.RKDense(denseMatrix); + end function sol = integrate(obj, f, tspan, y0, varargin) - sol = integrate@matlode.rk.erk.ERK(obj, f, tspan, y0, 'StepSizeController', matlode.stepsizecontroller.Fixed(1000), varargin{:}); + sol = integrate@matlode.rk.erk.ERK(obj, f, tspan, y0, 'StepSizeController', matlode.stepsizecontroller.StandardController, 'Dense', obj.DenseOut, varargin{:}); end end end diff --git a/src/+matlode/+rk/+erk/ERK.m b/src/+matlode/+rk/+erk/ERK.m index 745491d..0e3007f 100644 --- a/src/+matlode/+rk/+erk/ERK.m +++ b/src/+matlode/+rk/+erk/ERK.m @@ -1,99 +1,304 @@ classdef ERK < matlode.rk.RungeKutta %Explicit Runge Kutta class + properties (Constant) + PartitionMethod = false + end + properties (SetAccess = immutable) - a - b - bHat - c - stage - order - embeddedOrder - adaptable + A + B + BHat + C + E + Stage + Order + EmbeddedOrder + Adaptive + Datatype FSAL end methods - - - function obj = ERK(a, b, bHat, c, order, embeddedOrder) - - obj.a = a; - obj.b = b; - obj.c = c; - obj.bHat = bHat; - obj.embeddedOrder = embeddedOrder; - obj.order = order; - obj.stage = length(b); + function obj = ERK(a, b, bHat, c, e, order, embeddedOrder) + + obj.A = a; + obj.B = b; + obj.BHat = bHat; + obj.C = c; + obj.E = e; + obj.EmbeddedOrder = embeddedOrder; + obj.Order = order; + obj.Stage = length(b); obj.FSAL = all(a(end, :) == b) && all(a(1, :) == 0); - obj.adaptable = ~isempty(bHat); + obj.Adaptive = ~isempty(bHat); + obj.Datatype = class(a); end - function sol = integrate(obj, f, tspan, y0, varargin) + + end + + methods (Access = protected) + function opts = matlodeSets(obj, p, varargin) - opts = obj.matlodeSets(varargin{:}); + %ERK specific options + + opts = matlodeSets@matlode.rk.RungeKutta(obj, p, varargin{:}); + end + + function [t, y, stats] = timeLoop(obj, f, tspan, y0, opts, varargin) numVars = length(y0); - t = tspan(1); - k = zeros(numVars, obj.stage); - sol.y = y0; + multiTspan = length(tspan) > 2; + isDense = ~isempty(opts.Dense); + overstep = false; + startupErr = true; + + %Structure access optimizations + errNormFunc = opts.ErrNorm; + stepController = opts.StepSizeController; + newStepFunc = @(prevAccept, tCur, tspan, hHist, err, q, nSteps, nFailed) opts.StepSizeController.newStepSize(prevAccept, tCur, tspan, hHist, err, q, nSteps, nFailed); + + + k = zeros(numVars, obj.Stage); + y = []; + t = []; - sol.stats.nsteps = 0; - sol.stats.nfailed = 0; - sol.stats.nfevals = 0; + if multiTspan + y = zeros(numVars, length(tspan)); + t = zeros(1, length(tspan)); + else + y = zeros(numVars, opts.ChunkSize); + t = zeros(1, opts.ChunkSize); + end + + %inital values + t(1,1) = tspan(1); + tCur = tspan(1); + tNext = tCur; + ti = 2; + hmax = opts.MaxStep; + + y(:, 1) = y0; + yNext = y0; + + %temporary stats + nSteps = 1; + nFailed = 0; + nFevals = 1; + nSmallSteps = 0; + + tdir = sign(tspan(end) - tspan(1)); + + q = min(obj.Order, obj.EmbeddedOrder); + + %%%% + % Start of method specific intializing code + %%%% fsalStart = uint32(obj.FSAL) + 1; - fevalIterCounts = obj.stage - fsalStart + 1; + fevalIterCounts = double(obj.Stage - fsalStart + 1); + %%%% + % End of method specific intializing code + %%%% - [h, k(:,1)] = opts.StepSizeController.startingStep(f, tspan, y0, obj.order, opts); + %First step + %allocates memory for first step + [hHist, k(:,end)] = stepController.startingStep(f, tspan, y0, obj.Order, errNormFunc, opts.InitialStep); + hN = hHist(1); + err = zeros(1, length(hHist)); + accept = true; - while t < tspan(end) + %Time Loop + while ti <= length(tspan) + %Cycle history + err = circshift(err, 1, 2); + hHist = circshift(hHist, 1, 2); - for i = fsalStart:obj.stage - k(:, i) = f(t + h * obj.c(i), sol.y + h * (k(:, 1:i-1) * obj.a(i, 1:i-1)')); - end + yCur = yNext; + tCur = tNext; + hC = hN; + hHist(1) = hN; - sol.stats.nfevals = sol.stats.nfevals + fevalIterCounts; + %Subject to change + hmin = 64 * eps(tCur); - yCurrent = sol.y + h * (k * obj.b'); - err = 0; + %%%% + % Start of method specific before time loop code + %%%% - - if opts.StepSizeController.adaptive - yEmbbeded = sol.y + h * (k * obj.bHat); - err = opts.ErrNorm(yCurrent, yEmbbeded, opts); + if obj.FSAL + k(:, 1) = k(:, end); end - [accept, h, t] = opts.StepSizeController.newStepSize(t, tspan, h, err, obj.embeddedOrder, sol.stats, opts); + %%%% + % End of method specific before time loop code + %%%% - if accept - sol.y = yCurrent; - sol.stats.nsteps = sol.stats.nsteps + 1; + %Accept Loop + %Will keep looping until accepted step + while true + + + %%%% + % Start of method specific time loop code + %%%% + + for i = fsalStart:obj.Stage + k(:, i) = f(tCur + hC * obj.C(i), yCur + k(:, 1:i-1) * (hC * obj.A(i, 1:i-1)')); + end + + yNext = yCur + k * (hC * obj.B'); + + nFevals = nFevals + fevalIterCounts; + prevAccept = accept; + + %Perform error estimates + %Could maybe leave out of method specific zone? + if stepController.Adaptive + yEmbbeded = k * (hC * obj.E'); + err(:, 1) = errNormFunc([yNext, yCur], yEmbbeded); + + if startupErr + err(:, 2:end) = err(:, 1); + end + startupErr = false; + end + + %%%% + % End of method specific time loop code + %%%% + + %Find next Step + [accept, hN, tNext] = newStepFunc(prevAccept, tCur, tspan, hHist, err, q, nSteps, nFailed); + + %set new step to be in range + if stepController.Adaptive + hN = min(hmax, hN); + end + + %Check if step is really small + if hN < hmin + if nSmallSteps == 0 + warning('The step the integrator is taking is extremely small, results may not be good') + end + %accept step since the step cannot get any smaller + nSmallSteps = nSmallSteps + 1; + hN = hmin; + tNext = tCur + hmin; + break; + end - if obj.FSAL - k(:, 1) = k(:, end); + %check step acception + if accept + break; end - else - sol.stats.nfailed = sol.stats.nfailed + 1; + nFailed = nFailed + 1; + hC = hN; + hHist(1) = hN; + + end + + nSteps = nSteps + 1; + + %check if controller failed to overstep for dense + if isDense && overstep && tNext < tspan(ti) + overstep = false; + end + + %Check for all memory allocation + if ~multiTspan + + %Allocate more memory if non-dense + if nSteps > length(t) + y = [y, zeros(numVars, opts.ChunkSize)]; + t = [t, zeros(1, opts.ChunkSize)]; + end + + t(:, nSteps) = tNext; + y(:, nSteps) = yNext; + end + + %check if condition holds + %used for end checking,integrate to, and dense output + if ~(tspan(ti) * tdir > (tNext + hN) * tdir) + %Dense Output + if isDense && multiTspan && ti ~= length(tspan) + + if overstep + while tNext > tspan(ti) && ti ~= length(tspan) + %Call Dense output function and record + + %y0 = yCur y1 = yNext + t(:, ti) = tspan(ti); + [y(:, ti), tempFevals] = opts.Dense.denseOut(f, tCur, tspan(ti), yCur, yNext, k, hC); + nFevals = nFevals + tempFevals; + + ti = ti + 1; + end + + if tspan(ti) - tNext < hmin + t(:, ti) = tspan(ti); + y(:, ti) = yNext; + ti = ti + 1; + end + + overstep = false; + else + overstep = true; + + %check for tspan(end) to hit exactly + if stepController.Adaptive && ~(tspan(end) * tdir > (tNext + hN) * tdir) + hN = tspan(end) - tNext; + end + end + else + %integrate to/ End point + %check if close enough with hmin + if tspan(ti) - tNext < hmin + + if multiTspan + t(:, ti) = tspan(ti); + y(:, ti) = yNext; + end + ti = ti + 1; + else + if stepController.Adaptive + hN = tspan(ti) - tNext; + end + + end + end end end + %End of Timeloop work + %Truncate extra memory + if ~multiTspan + t = t(:, 1:nSteps); + y = y(:, 1:nSteps); + else + t(:, end) = tspan(end); + y(:, end) = yNext; + end - end - - function opts = matlodeSets(obj, varargin) - p = inputParser; + %Create Stats + stats.nSteps = nSteps - 1; + stats.nFailed = nFailed; + stats.nFevals = nFevals; + stats.nSmallSteps = nSmallSteps; - %ERK specific options - opts = obj.matlodeSetsHelper(p, varargin{:}); end + + end + end diff --git a/src/+matlode/+rk/+erk/ForwardEuler.m b/src/+matlode/+rk/+erk/ForwardEuler.m index 1218a2d..5038c6d 100644 --- a/src/+matlode/+rk/+erk/ForwardEuler.m +++ b/src/+matlode/+rk/+erk/ForwardEuler.m @@ -1,26 +1,38 @@ classdef ForwardEuler < matlode.rk.erk.ERK methods - function obj = ForwardEuler() + function obj = ForwardEuler(datatype) %TODO %include reference - a = [[0]]; + %p = 1 & hatp = 0 & s = 1 + + if nargin == 0 + datatype = 'double'; + end + + caster = @(x) matlode.util.CoefficentTransformers.transform(x,datatype); + + a = caster('[[0]]'); - b = [1]; + b = caster('[1]'); bHat = []; - c = [0]; + e = []; + + c = caster('[0]'); order = 1; embbededOrder = 0; - obj = obj@matlode.rk.erk.ERK(a, b, bHat, c, order, embbededOrder); + obj = obj@matlode.rk.erk.ERK(a, b, bHat, c, e, order, embbededOrder); + + %p* = 1 & s* = 1 end function sol = integrate(obj, f, tspan, y0, varargin) - sol = integrate@matlode.rk.erk.ERK(obj, f, tspan, y0, 'StepSizeController', matlode.stepsizecontroller.Fixed(1000), varargin{:}); + sol = integrate@matlode.rk.erk.ERK(obj, f, tspan, y0,'Dense', matlode.denseoutput.Linear(obj.B), 'StepSizeController', matlode.stepsizecontroller.Fixed(1000), varargin{:}); end end end diff --git a/src/+matlode/+rk/RungeKutta.m b/src/+matlode/+rk/RungeKutta.m index a08eee4..0fa4a8e 100644 --- a/src/+matlode/+rk/RungeKutta.m +++ b/src/+matlode/+rk/RungeKutta.m @@ -2,14 +2,19 @@ %RungeKutta integrator properties (Abstract, SetAccess = immutable) - bHat - embeddedOrder - order + A + B + C + BHat + E + Stage + EmbeddedOrder + Order FSAL end methods - function obj = fromCoeffs(a, b, bHat, c, order, embededOrder) + function obj = fromCoeffs(a, b, bHat, c, e, bTheta, order, embededOrder) if istril(a) @@ -19,7 +24,7 @@ return end - obj = ERK(a, b, bHat, c, order, embededOrder); + obj = ERK(a, b, bHat, c, e, bTheta, order, embededOrder); return end @@ -31,11 +36,11 @@ end methods (Access = protected) - function opts = matlodeSetsHelper(obj, p, varargin) + function opts = matlodeSets(obj, p, varargin) %RungeKutta sepcific options - opts = matlodeSetsHelper@matlode.OneStepIntegrator(obj, p, varargin{:}); + opts = matlodeSets@matlode.OneStepIntegrator(obj, p, varargin{:}); end end diff --git a/src/+matlode/+stepsizecontroller/+soberpresets/H321.m b/src/+matlode/+stepsizecontroller/+soberpresets/H321.m new file mode 100644 index 0000000..15a743f --- /dev/null +++ b/src/+matlode/+stepsizecontroller/+soberpresets/H321.m @@ -0,0 +1,17 @@ +classdef H321 < matlode.stepsizecontroller.SoberlandController + %TODO Reference NASA Paper and Soberland paper + %This controller works and provides a smooth solution + + methods + function obj = H321() + a = [-1/3, -1/18, 5/18]; + b = [5/6, 1/6]; + + AQfunc = @(q, A) (A / q); + + %This is stupid, it ignores the first parameter for some reason + obj = obj@matlode.stepsizecontroller.SoberlandController(0, 'A', a, 'B', b, 'AQfunc', AQfunc); + end + end +end + diff --git a/src/+matlode/+stepsizecontroller/Fixed.m b/src/+matlode/+stepsizecontroller/Fixed.m index e65b775..aecb339 100644 --- a/src/+matlode/+stepsizecontroller/Fixed.m +++ b/src/+matlode/+stepsizecontroller/Fixed.m @@ -2,31 +2,36 @@ %Fixed stepsize controller properties (Constant) - adaptive = false; + Adaptive = false; end properties (SetAccess = immutable, GetAccess = private) - steps + Steps + end + + properties (SetAccess = immutable) + History end methods function obj = Fixed(steps) - obj.steps = steps; + obj.Steps = steps; + obj.History = 1; end - function [h0, f0] = startingStep(obj, f, tspan, y0, ~, options) - if options.InitialStep ~= 0 + function [h0, f0] = startingStep(obj, f, tspan, y0, ~, ~, intialStep) + if intialStep ~= 0 warning('Ignoring initialStep option, Controller is Fixed'); end f0 = f(tspan(1), y0); - h0 = (tspan(2) - tspan(1)) / obj.steps; + h0 = (tspan(2) - tspan(1)) / obj.Steps; end - function [accept, hCur, tNew] = newStepSize(~, ~, tspan, hCur, ~, ~, stepdlx, ~) + function [accept, h, tNew] = newStepSize(~, ~, ~, tspan, h, ~, ~, nSteps, ~, ~) accept = true; - tNew = (stepdlx.nsteps + 1) * hCur + tspan(1); + tNew = (nSteps) * h + tspan(1); end end diff --git a/src/+matlode/+stepsizecontroller/Gustafson.m b/src/+matlode/+stepsizecontroller/Gustafson.m new file mode 100644 index 0000000..389f2fe --- /dev/null +++ b/src/+matlode/+stepsizecontroller/Gustafson.m @@ -0,0 +1,70 @@ +classdef Gustafson < matlode.stepsizecontroller.StartingController + %TODO Reference Gustaffson paper(1991) + %Slight derivation of the Gustafson paper on the coefficents + + properties (Constant) + Adaptive = true; + end + + properties (SetAccess = immutable) + Fac + FacMin + FacMax + Ki + Kp + A + History + end + + methods + function obj = Gustafson(varargin) + + obj.History = 2; + + p = inputParser; + + p.addParameter('Fac', 0.95); + p.addParameter('FacMin', 0.1); + p.addParameter('FacMax', 2); + p.addParameter('Ki', 0.3); + p.addParameter('Kp', -0.4); + p.addParameter('A', 1); + + p.parse(varargin{:}); + + opts = p.Results; + + obj.Fac = opts.Fac; + obj.FacMin = opts.FacMin; + obj.FacMax = opts.FacMax; + obj.Ki = opts.Ki; + obj.Kp = opts.Kp; + obj.A = opts.A; + + end + + function [accept, hNew, tNew] = newStepSize(obj, prevAccept, t, ~, h, err, q, ~, ~) + accept = err(1) <= 1; + + scalh = 1; + + if accept + tNew = t + h(1); + if ~prevAccept + %H_0220 Controller + scalh = (h(1) / h(2))^obj.A; + end + %PI Controller + scalerr = err(1)^(obj.Ki/q) * err(2)^(obj.Kp/q); + else + tNew = t; + %Standard Controller(I controller) + scalerr = err(1)^(-1/(q + 1)); + end + + hNew = h(1) * min(obj.FacMax, max(obj.FacMin, obj.Fac * scalerr * scalh)); + + end + end +end + diff --git a/src/+matlode/+stepsizecontroller/SoberlandController.m b/src/+matlode/+stepsizecontroller/SoberlandController.m new file mode 100644 index 0000000..24c1136 --- /dev/null +++ b/src/+matlode/+stepsizecontroller/SoberlandController.m @@ -0,0 +1,78 @@ +classdef SoberlandController < matlode.stepsizecontroller.StartingController + %SOBERLANDCONTROLLER + %A general controller for all history based non-conditional controllers + %can be expanded up to any amount of history gien proper coefficents + + properties (Constant) + Adaptive = true; + end + + properties (SetAccess = immutable) + Fac + FacMax + FacMin + A + B + AQfunc % Geneall @(q, A) (A / (1+q)) + History + end + + methods + function obj = SoberlandController(varargin) + + p = inputParser; + + p.addParameter('Fac', 0.95); + p.addParameter('FacMin', 0.1); + p.addParameter('FacMax', 2); + p.addParameter('B', [0]); + p.addParameter('A', [1]); + p.addRequired('AQfunc'); + + p.parse(varargin{:}); + + opts = p.Results; + + obj.Fac = opts.Fac; + obj.FacMin = opts.FacMin; + obj.FacMax = opts.FacMax; + + if isempty(opts.A) + error('A cannot be empty') + end + + obj.A = opts.A; + obj.B = opts.B; + + %This is need because input parser struggles with + %function_handles + if ~isa(opts.AQfunc, 'function_handle') + error('AQfunc must be a function handle') + end + + obj.AQfunc = opts.AQfunc; + obj.History = length(obj.A); + end + + function [accept, hNew, tNew] = newStepSize(obj, ~, t, ~, h, err, q, ~, ~) + + accept = mean(err) <= 1; + if accept + tNew = t + h(1); + + else + tNew = t; + end + + + facScal = prod(err(1:obj.History).^obj.AQfunc(q,obj.A)); + hScal = prod((h(1:obj.History - 1) ./ h(2:obj.History)).^obj.B); + prediction = obj.Fac * facScal * hScal; + + hNew = h(1) * min(obj.FacMax, max(obj.FacMin, prediction)); + + + end + end +end + diff --git a/src/+matlode/+stepsizecontroller/StandardController.m b/src/+matlode/+stepsizecontroller/StandardController.m new file mode 100644 index 0000000..9f1058b --- /dev/null +++ b/src/+matlode/+stepsizecontroller/StandardController.m @@ -0,0 +1,49 @@ +classdef StandardController < matlode.stepsizecontroller.StartingController + %Standard Error Controller as described in Solving ODES I book + %Seperate from Soberland to help with performance(TODO test if true) + + properties (Constant) + Adaptive = true; + end + + properties (SetAccess = immutable) + Fac + FacMin + FacMax + History + end + + methods + function obj = StandardController(varargin) + + obj.History = 1; + p = inputParser; + + p.addParameter('Fac', 0.95); + p.addParameter('FacMin', 0.1); + p.addParameter('FacMax', 2); + + p.parse(varargin{:}); + + opts = p.Results; + + obj.Fac = opts.Fac; + obj.FacMin = opts.FacMin; + obj.FacMax = opts.FacMax; + end + + function [accept, hNew, tNew] = newStepSize(obj, ~, t, ~, h, err, q, ~, ~) + accept = err <= 1; + + if accept + tNew = t + h; + else + tNew = t; + end + + hNew = h * min(obj.FacMax, max(obj.FacMin, obj.Fac * err^(-1 / (q + 1)))); + + end + end +end + diff --git a/src/+matlode/+stepsizecontroller/StartingController.m b/src/+matlode/+stepsizecontroller/StartingController.m new file mode 100644 index 0000000..502898e --- /dev/null +++ b/src/+matlode/+stepsizecontroller/StartingController.m @@ -0,0 +1,41 @@ +classdef (Abstract) StartingController < matlode.stepsizecontroller.StepSizeController + %Adaptive controller + % This class is mostly used to contian the starting procedure of all + % adaptive controllers and some properties + + methods + function [h0, f0] = startingStep(obj, f, tspan, y0, order, errFunc, intialStep) + if intialStep ~= 0 + f0 = f(tspan(1), y0); + h0 = intialStep; + else + f0 = f(tspan(1), y0); + d0 = errFunc([y0, y0], 0); + d1 = errFunc([f0, f0], 0); + if any([d0, d1] < 1e-5) + h0 = 1e-6; + else + h0 = 0.01 * d0 / d1; + end + + y1 = y0 + h0 * f0; + f1 = f(tspan(1) + h0, y1); + err0 = errFunc([f1 - f0, f1 - f0], 0); + d2 = err0 / h0; + dm = max(d1, d2); + + if dm <= 1e-15 + h1 = max(1e-6, h0 * 1e-3); + else + h1 = (0.01 / dm)^(1 / (order + 1)); + end + + h0 = min(100 * h0, h1); + end + + %allocate for length of history + h0 = ones(1, obj.History) * h0; + end + end +end + diff --git a/src/+matlode/+stepsizecontroller/StepSizeController.m b/src/+matlode/+stepsizecontroller/StepSizeController.m index b6f45d0..e29c0ca 100644 --- a/src/+matlode/+stepsizecontroller/StepSizeController.m +++ b/src/+matlode/+stepsizecontroller/StepSizeController.m @@ -2,13 +2,18 @@ %Template for a step size controller properties (Abstract, Constant) - adaptive + Adaptive + end + + properties (Abstract, SetAccess = immutable) + History end methods (Abstract) - - [h0, f0] = startingStep(obj, f, tspan, y0, order, options); - [accept, hNew, tNew] = newStepSize(obj, tCur, tspan, hCur, err, embeddedOrder, stepdlx, options); + %accept0, h0, and err0, respect the amount of history needed for a + %controller + [h0, f0] = startingStep(obj, f, tspan, y0, order, errFunc, intialStep); + [accept, hNew, tNew] = newStepSize(obj, prevAccept, t, tspan, h, err, q, nSteps, nFailed); end end diff --git a/src/+matlode/+util/+errnorm/standardNorm.m b/src/+matlode/+util/+errnorm/standardNorm.m deleted file mode 100644 index e711a8d..0000000 --- a/src/+matlode/+util/+errnorm/standardNorm.m +++ /dev/null @@ -1,9 +0,0 @@ -function err = standardNorm(y, yHat, options) - %procedure described in solving ODEs I Harrier - %cannot assume that user has signal processing toolbox - - sc = (options.AbsTol + max(abs(y), abs(yHat)) .* options.RelTol); - - err = sqrt(1/length(y)) * norm((y - yHat) ./ sc); -end - diff --git a/src/+matlode/+util/CoefficentTransformers.m b/src/+matlode/+util/CoefficentTransformers.m new file mode 100644 index 0000000..9b0846e --- /dev/null +++ b/src/+matlode/+util/CoefficentTransformers.m @@ -0,0 +1,15 @@ +classdef CoefficentTransformers + methods (Static) + + function valuetype = transform(str, datatype) + + if isa(datatype, 'sym') + valuetype = str2sym(str); + else + %Better than str2double as it can deal with arrays and sqrt + valuetype = cast(str2num(str), datatype); + end + end + end +end + diff --git a/src/+matlode/Integrator.m b/src/+matlode/Integrator.m index 5d341c6..142308b 100644 --- a/src/+matlode/Integrator.m +++ b/src/+matlode/Integrator.m @@ -2,51 +2,100 @@ %INTEGRATOR Summary of this class goes here % Detailed explanation goes here + properties (Abstract, Constant) + PartitionMethod + end + properties (Abstract, SetAccess = immutable) - c - adaptable + Adaptive + Datatype end - methods (Abstract) - - %integrate - sol = integrate(obj, func, tspan, y0, varargin) - opts = matlodeSets(obj, varargin) - + methods (Abstract, Access = protected) + [t, y, stats] = timeLoop(obj, f, tspan, y0, opts, varargin); + end + + methods + function sol = integrate(obj, f, tspan, y0, varargin) + + if(length(tspan) < 2) + error('Unsupported tspan entry'); + end + + if ~issorted(tspan) && ~issorted(tspan, 'descend') + error('tspan must be sorted in order') + end + + %TODO + %optimize options for when only options struct is given + + %Create options + p = inputParser; + opts = obj.matlodeSets(p, varargin{:}); + + if isempty(opts.Dense) && length(tspan) > 2 && isa(opts.StepSizeController, 'matlode.stepsizecontroller.Fixed') + error('IntegrateTo is not supported for fixed step size, please use DenseOutput instead') + end + + t = []; + y = []; + stats = []; + + %Compute + if isa(f, 'function_handle') && ~obj.PartitionMethod + + [t, y, stats] = obj.timeLoop(f, tspan, y0, opts); + + elseif isa(f, 'cell') && cellfun(@(x) isa(x, 'function_handle'), f) + + %determine size of partition + if length(f) == 1 && ~obj.PartitionMethod + [t, y, stats] = obj.timeLoop(f{1}, tspan, y0, opts); + elseif length(f) > 1 + %TODO + %paritioning setup + + else + error('Need at least one function provided') + end + + else + error('f must be a ''function_handle'' or a ''cell'''); + end + + sol.t = t; + sol.y = y; + sol.stats = stats; + end end methods (Access = protected) - function opts = matlodeSetsHelper(obj, p, varargin) + function opts = matlodeSets(obj, p, varargin) - p.addParameter('AbsTol', 1e-6); - p.addParameter('RelTol', 1e-6); + %Assign to controllers p.addParameter('InitialStep', 0); p.addParameter('StepSizeController', matlode.stepsizecontroller.Fixed(1000)); - p.addParameter('StepsN', 0); - p.addParameter('ErrNorm', @(y, yHat, options) matlode.util.errnorm.standardNorm(y, yHat, options)); - p.addParameter('NewtonTol', 1e-7); - p.addParameter('NewtonMaxItr', 25); + p.addParameter('ErrNorm', matlode.errnorm.HistNorm.errEstimate(sqrt(eps), sqrt(eps))); p.addParameter('Jacobian', []); + p.addParameter('ChunkSize', 1000); + p.addParameter('Dense', [] ); + p.addParameter('MaxStep', inf); p.parse(varargin{:}); opts = p.Results; - %shorthanded fixed steps - if opts.StepsN > 0 - opts.StepSizeController = matlode.stepsizecontroller.Fixed(opts.StepsN); - end - opts = rmfield(opts, 'StepsN'); - %make sure a method can use a adaptive controller - if ~obj.adaptable && opts.StepSizeController.adaptive + if ~obj.Adaptive && opts.StepSizeController.Adaptive warning('Current method does not have the ability to use a adaptive step size controller'); %overrider user choice??? end + end + end end diff --git a/src/+matlode/OneStepIntegrator.m b/src/+matlode/OneStepIntegrator.m index f6653e6..c45ce68 100644 --- a/src/+matlode/OneStepIntegrator.m +++ b/src/+matlode/OneStepIntegrator.m @@ -1,20 +1,13 @@ classdef (Abstract) OneStepIntegrator < matlode.Integrator %One Step Integrator template - properties (Abstract, SetAccess = immutable) - a - b - stage - end methods (Access = protected) - function opts = matlodeSetsHelper(obj, p, varargin) - - + function opts = matlodeSets(obj, p, varargin) %OneStepIntegrator Specific options - opts = matlodeSetsHelper@matlode.Integrator(obj, p, varargin{:}); + opts = matlodeSets@matlode.Integrator(obj, p, varargin{:}); end From d0a294f65dfef611cde80aa69aea59d7aa341b8b Mon Sep 17 00:00:00 2001 From: rjg18 Date: Tue, 13 Jul 2021 10:57:18 -0400 Subject: [PATCH 04/17] Added Generic Starting Step, Updates to Backwards, Removed Abstract Immutable Properties --- src/+matlode/+errnorm/HistNorm.m | 11 --- src/+matlode/+errnorm/InfNorm.m | 2 +- src/+matlode/+errnorm/StandardNorm.m | 2 +- src/+matlode/+rk/+erk/ERK.m | 82 ++++++++----------- src/+matlode/+rk/RungeKutta.m | 18 +++- src/+matlode/+startingstep/BookStarting.m | 43 ++++++++++ src/+matlode/+startingstep/SetStep.m | 31 +++++++ src/+matlode/+startingstep/StartingStep.m | 19 +++++ src/+matlode/+startingstep/WattsStarting.m | 70 ++++++++++++++++ .../+stepsizecontroller/+soberpresets/H321.m | 17 ---- .../+stepsizecontroller/+soderpresets/H321.m | 16 ++++ src/+matlode/+stepsizecontroller/Fixed.m | 6 +- src/+matlode/+stepsizecontroller/Gustafson.m | 23 ++---- ...landController.m => SoderlandController.m} | 46 +++++------ .../+stepsizecontroller/StandardController.m | 26 +----- .../+stepsizecontroller/StartingController.m | 66 ++++++++------- .../+stepsizecontroller/StepSizeController.m | 17 +++- src/+matlode/Integrator.m | 44 +++++----- src/+matlode/OneStepIntegrator.m | 4 + 19 files changed, 337 insertions(+), 206 deletions(-) delete mode 100644 src/+matlode/+errnorm/HistNorm.m create mode 100644 src/+matlode/+startingstep/BookStarting.m create mode 100644 src/+matlode/+startingstep/SetStep.m create mode 100644 src/+matlode/+startingstep/StartingStep.m create mode 100644 src/+matlode/+startingstep/WattsStarting.m delete mode 100644 src/+matlode/+stepsizecontroller/+soberpresets/H321.m create mode 100644 src/+matlode/+stepsizecontroller/+soderpresets/H321.m rename src/+matlode/+stepsizecontroller/{SoberlandController.m => SoderlandController.m} (62%) diff --git a/src/+matlode/+errnorm/HistNorm.m b/src/+matlode/+errnorm/HistNorm.m deleted file mode 100644 index 6c44923..0000000 --- a/src/+matlode/+errnorm/HistNorm.m +++ /dev/null @@ -1,11 +0,0 @@ -classdef HistNorm < matlode.errnorm.NormErr - methods (Static) - function errFunc = errEstimate(AbsTol, RelTol) - - scFunc = @(y) (AbsTol + max(abs(y(:, 1)), abs(y(:, 2))) * RelTol); - errFunc = @(y, yE) sqrt(1/size(y,1)) * norm(yE ./ scFunc(y)); - - end - end -end - diff --git a/src/+matlode/+errnorm/InfNorm.m b/src/+matlode/+errnorm/InfNorm.m index e83702a..a065cb2 100644 --- a/src/+matlode/+errnorm/InfNorm.m +++ b/src/+matlode/+errnorm/InfNorm.m @@ -2,7 +2,7 @@ methods (Static) function errFunc = errEstimate(AbsTol, RelTol) - scFunc = @(y) (AbsTol + abs(y(:, 1)) * RelTol); + scFunc = @(y) (AbsTol + max(abs(y(:, 1)), abs(y(:, 2))) * RelTol); errFunc = @(y, yE) norm(yE ./ scFunc(y), 'Inf'); end diff --git a/src/+matlode/+errnorm/StandardNorm.m b/src/+matlode/+errnorm/StandardNorm.m index c6eb454..b96befe 100644 --- a/src/+matlode/+errnorm/StandardNorm.m +++ b/src/+matlode/+errnorm/StandardNorm.m @@ -2,7 +2,7 @@ methods (Static) function errFunc = errEstimate(AbsTol, RelTol) - scFunc = @(y) (AbsTol + abs(y(:, 1)) * RelTol); + scFunc = @(y) (AbsTol + max(abs(y(:, 1)), abs(y(:, 2))) * RelTol); errFunc = @(y, yE) sqrt(1/size(y,1)) * norm(yE ./ scFunc(y)); end diff --git a/src/+matlode/+rk/+erk/ERK.m b/src/+matlode/+rk/+erk/ERK.m index 0e3007f..3409397 100644 --- a/src/+matlode/+rk/+erk/ERK.m +++ b/src/+matlode/+rk/+erk/ERK.m @@ -5,38 +5,10 @@ PartitionMethod = false end - properties (SetAccess = immutable) - A - B - BHat - C - E - Stage - Order - EmbeddedOrder - Adaptive - Datatype - FSAL - end - methods - function obj = ERK(a, b, bHat, c, e, order, embeddedOrder) - - obj.A = a; - obj.B = b; - obj.BHat = bHat; - obj.C = c; - obj.E = e; - obj.EmbeddedOrder = embeddedOrder; - obj.Order = order; - obj.Stage = length(b); - obj.FSAL = all(a(end, :) == b) && all(a(1, :) == 0); - obj.Adaptive = ~isempty(bHat); - obj.Datatype = class(a); + obj = obj@matlode.rk.RungeKutta(a, b, bHat, c, e, order, embeddedOrder); end - - end methods (Access = protected) @@ -78,7 +50,10 @@ tCur = tspan(1); tNext = tCur; ti = 2; - hmax = opts.MaxStep; + + tdir = sign(tspan(end) - tspan(1)); + hmax = opts.MaxStep * tdir; + hmin = 64 * eps(tspan(1)) * tdir; y(:, 1) = y0; yNext = y0; @@ -86,13 +61,20 @@ %temporary stats nSteps = 1; nFailed = 0; - nFevals = 1; nSmallSteps = 0; - tdir = sign(tspan(end) - tspan(1)); - q = min(obj.Order, obj.EmbeddedOrder); + %First step + %allocates memory for first step + [h0, f0, nFevals] = stepController.startingStep(f, tspan, y0, obj.Order, errNormFunc, hmin, hmax); + + hHist = ones(1, stepController.History) * h0; + hN = hHist(1); + err = zeros(1, length(hHist)); + + accept = true; + %%%% % Start of method specific intializing code %%%% @@ -100,18 +82,19 @@ fsalStart = uint32(obj.FSAL) + 1; fevalIterCounts = double(obj.Stage - fsalStart + 1); + if obj.FSAL + if nFevals == 0 + k(:, end) = f(tspan(1), y0); + nFevals = nFevals + 1; + else + k(:, end) = f0; + end + end + %%%% % End of method specific intializing code %%%% - %First step - %allocates memory for first step - [hHist, k(:,end)] = stepController.startingStep(f, tspan, y0, obj.Order, errNormFunc, opts.InitialStep); - hN = hHist(1); - err = zeros(1, length(hHist)); - - accept = true; - %Time Loop while ti <= length(tspan) %Cycle history @@ -124,7 +107,7 @@ hHist(1) = hN; %Subject to change - hmin = 64 * eps(tCur); + hmin = 64 * eps(tCur) * tdir; %%%% % Start of method specific before time loop code @@ -178,13 +161,13 @@ %set new step to be in range if stepController.Adaptive - hN = min(hmax, hN); + hN = min(hmax * tdir, hN * tdir) * tdir; end %Check if step is really small - if hN < hmin + if hN * tdir < hmin * tdir if nSmallSteps == 0 - warning('The step the integrator is taking is extremely small, results may not be good') + warning('The step the integrator is taking extremely small, results may not be optimum') end %accept step since the step cannot get any smaller nSmallSteps = nSmallSteps + 1; @@ -207,7 +190,7 @@ nSteps = nSteps + 1; %check if controller failed to overstep for dense - if isDense && overstep && tNext < tspan(ti) + if isDense && overstep && tNext * tdir < tspan(ti) * tdir overstep = false; end @@ -231,10 +214,9 @@ if isDense && multiTspan && ti ~= length(tspan) if overstep - while tNext > tspan(ti) && ti ~= length(tspan) + while tNext * tdir > tspan(ti) * tdir && ti ~= length(tspan) %Call Dense output function and record - %y0 = yCur y1 = yNext t(:, ti) = tspan(ti); [y(:, ti), tempFevals] = opts.Dense.denseOut(f, tCur, tspan(ti), yCur, yNext, k, hC); nFevals = nFevals + tempFevals; @@ -242,7 +224,7 @@ ti = ti + 1; end - if tspan(ti) - tNext < hmin + if (tspan(ti) - tNext) * tdir < hmin * tdir t(:, ti) = tspan(ti); y(:, ti) = yNext; ti = ti + 1; @@ -260,7 +242,7 @@ else %integrate to/ End point %check if close enough with hmin - if tspan(ti) - tNext < hmin + if (tspan(ti) - tNext) * tdir < hmin * tdir if multiTspan t(:, ti) = tspan(ti); diff --git a/src/+matlode/+rk/RungeKutta.m b/src/+matlode/+rk/RungeKutta.m index 0fa4a8e..3eb9cd5 100644 --- a/src/+matlode/+rk/RungeKutta.m +++ b/src/+matlode/+rk/RungeKutta.m @@ -1,7 +1,7 @@ classdef (Abstract) RungeKutta < matlode.OneStepIntegrator %RungeKutta integrator - properties (Abstract, SetAccess = immutable) + properties (SetAccess = immutable) A B C @@ -14,6 +14,22 @@ end methods + function obj = RungeKutta(a, b, bHat, c, e, order, embeddedOrder) + + obj = obj@matlode.OneStepIntegrator(~isempty(bHat), class(a)); + + obj.A = a; + obj.B = b; + obj.BHat = bHat; + obj.C = c; + obj.E = e; + obj.EmbeddedOrder = embeddedOrder; + obj.Order = order; + obj.Stage = size(b, 2); + obj.FSAL = all(a(end, :) == b) && all(a(1, :) == 0); + + end + function obj = fromCoeffs(a, b, bHat, c, e, bTheta, order, embededOrder) diff --git a/src/+matlode/+startingstep/BookStarting.m b/src/+matlode/+startingstep/BookStarting.m new file mode 100644 index 0000000..0d630af --- /dev/null +++ b/src/+matlode/+startingstep/BookStarting.m @@ -0,0 +1,43 @@ +classdef BookStarting < matlode.startingstep.StartingStep + %This starting step procedure is derived from the Solving ODEs I book + + + methods + function obj = BookStarting(varargin) + obj = obj@matlode.startingstep.StartingStep(varargin{:}); + end + + function [h0, f0, fevals] = startingStep(obj, f, tspan, y0, order, errFunc, minStep, maxStep) + fevals = 2; + tdir = sign(tspan(end) - tspan(1)); + + f0 = f(tspan(1), y0); + + d0 = errFunc([y0, y0], 0); + d1 = errFunc([f0, f0], 0); + + if any([d0, d1] < 1e-5) + h0 = 1e-6; + else + h0 = 0.01 * d0 / d1; + end + + y1 = y0 + h0 * f0 * tdir; + f1 = f(tspan(1) + h0 * tdir, y1); + err0 = errFunc([f1 - f0, f1 - f0], 0); + d2 = err0 / h0; + dm = max(d1, d2); + + if dm <= eps + h1 = max(1e-6, h0 * 1e-3); + else + h1 = (0.01 / dm)^(1 / (order + 1)); + end + + + h0 = min(100 * h0, h1); + h0 = max([min([h0, maxStep * tdir]), minStep * tdir]) * tdir; + end + end +end + diff --git a/src/+matlode/+startingstep/SetStep.m b/src/+matlode/+startingstep/SetStep.m new file mode 100644 index 0000000..73f1f92 --- /dev/null +++ b/src/+matlode/+startingstep/SetStep.m @@ -0,0 +1,31 @@ +classdef SetStep < matlode.startingstep.StartingStep + %SETSTEP Summary of this class goes here + % Detailed explanation goes here + + properties + StartingStep + end + + methods + function obj = SetStep(ssp, varargin) + obj = obj@matlode.startingstep.StartingStep(varargin{:}); + obj.StartingStep = ssp; + end + + function [h0, f0, fevals] = startingStep(obj, ~, tspan, ~, ~, ~, minStep, maxStep) + fevals = 0; + f0 = []; + if obj.StartingStep > maxStep + error('The starting step is greater then the max step') + end + if obj.StartingStep < minStep + error('The starting step cannot be so small') + end + if sign(obj.StartingStep) ~= sign(tspan(end) - tspan(1)) + error('The starting step cannot go in the opposite direction') + end + h0 = max([min([obj.StartingStep, maxStep]), minStep]); + end + end +end + diff --git a/src/+matlode/+startingstep/StartingStep.m b/src/+matlode/+startingstep/StartingStep.m new file mode 100644 index 0000000..81779c2 --- /dev/null +++ b/src/+matlode/+startingstep/StartingStep.m @@ -0,0 +1,19 @@ +classdef StartingStep + %This is an abstract setup for dealing with different kinds of starting + %step procedures + + methods + function obj = StartingStep(varargin) + p = inputParser; + p.KeepUnmatched = false; + + %error checking using the parser + p.parse(varargin{:}); + end + end + + methods (Abstract) + [h0, f0, fevals] = startingStep(obj, f, tspan, y0, order, errFunc, minStep, maxStep); + end +end + diff --git a/src/+matlode/+startingstep/WattsStarting.m b/src/+matlode/+startingstep/WattsStarting.m new file mode 100644 index 0000000..353ac4e --- /dev/null +++ b/src/+matlode/+startingstep/WattsStarting.m @@ -0,0 +1,70 @@ +classdef WattsStarting < matlode.startingstep.StartingStep + %Based of Watts starting procedure + %Watts, H. A. (1983). Starting step size for an ODE solver. Journal of Computational and Applied Mathematics + + properties (SetAccess = immutable) + InitTol + InitEpsMax + InitEpsMin + Fac + end + + methods + function obj = WattsStarting(varargin) + + p = inputParser; + p.KeepUnmatched = true; + + p.addParameter('InitTol', eps^(1/2)); + p.addParameter('InitEpsMax', eps^(1/2)); + p.addParameter('InitEpsMin', 100 * eps); + p.addParameter('Fac', 0.8); + + p.parse(varargin{:}); + + opts = p.Results; + varargout = p.Unmatched; + + obj = obj@matlode.startingstep.StartingStep(varargout); + + obj.InitTol = opts.InitTol; + obj.InitEpsMax = opts.InitEpsMax; + obj.InitEpsMin = opts.InitEpsMin; + obj.Fac = opts.Fac; + + end + + function [h0, f0, fevals] = startingStep(obj, f, tspan, y0, order, errFunc, minStep, maxStep) + fevals = 2; + tdir = sign(tspan(end) - tspan(1)); + + f0 = f(tspan(1), y0); + fnorm = errFunc([f0, f0], 0); + + hstep = abs(tspan(1) - tspan(end)); + + h0 = max([min([obj.InitEpsMax * abs(tspan(1)), hstep]), obj.InitEpsMin * abs(tspan(1))]); + + if h0 < eps + h0 = eps^obj.InitEpsMax * hstep; + end + + y1 = y0 + h0 * tdir * f0; + + ydd = (1/(h0)) * (f(tspan(1) + h0 * tdir, y1) - f0) ; + + fddnorm = errFunc([ydd, ydd], 0); + + if fddnorm > eps + h0 = obj.Fac * sqrt(2) * obj.InitTol^(1/(order + 1)) / sqrt(fddnorm); + elseif fnorm > eps + h0 = obj.Fac * obj.InitTol^(1/(order + 1)) / fnorm; + else + h0 = obj.Fac * obj.InitTol^(1/(order + 1)) * abs(tspan(2) - tspan(1)); + end + + h0 = max([min([h0, maxStep * tdir]), minStep * tdir]) * tdir; + end + end +end + diff --git a/src/+matlode/+stepsizecontroller/+soberpresets/H321.m b/src/+matlode/+stepsizecontroller/+soberpresets/H321.m deleted file mode 100644 index 15a743f..0000000 --- a/src/+matlode/+stepsizecontroller/+soberpresets/H321.m +++ /dev/null @@ -1,17 +0,0 @@ -classdef H321 < matlode.stepsizecontroller.SoberlandController - %TODO Reference NASA Paper and Soberland paper - %This controller works and provides a smooth solution - - methods - function obj = H321() - a = [-1/3, -1/18, 5/18]; - b = [5/6, 1/6]; - - AQfunc = @(q, A) (A / q); - - %This is stupid, it ignores the first parameter for some reason - obj = obj@matlode.stepsizecontroller.SoberlandController(0, 'A', a, 'B', b, 'AQfunc', AQfunc); - end - end -end - diff --git a/src/+matlode/+stepsizecontroller/+soderpresets/H321.m b/src/+matlode/+stepsizecontroller/+soderpresets/H321.m new file mode 100644 index 0000000..e5b3e16 --- /dev/null +++ b/src/+matlode/+stepsizecontroller/+soderpresets/H321.m @@ -0,0 +1,16 @@ +classdef H321 < matlode.stepsizecontroller.SoderlandController + %Kennedy, C. A., & Carpenter, M. H. (2016). + %Diagonally Implicit Runge-Kutta methods for ordinary differential equations, a review + + methods + function obj = H321(varargin) + a = [-1/3, -1/18, 5/18]; + b = [5/6, 1/6]; + + AQfunc = @(q, A) (A / q); + + obj = obj@matlode.stepsizecontroller.SoderlandController('A', a, 'B', b, 'AQfunc', AQfunc, varargin{:}); + end + end +end + diff --git a/src/+matlode/+stepsizecontroller/Fixed.m b/src/+matlode/+stepsizecontroller/Fixed.m index aecb339..35aa4da 100644 --- a/src/+matlode/+stepsizecontroller/Fixed.m +++ b/src/+matlode/+stepsizecontroller/Fixed.m @@ -9,15 +9,11 @@ Steps end - properties (SetAccess = immutable) - History - end - methods function obj = Fixed(steps) + obj = obj@matlode.stepsizecontroller.StepSizeController(1); obj.Steps = steps; - obj.History = 1; end function [h0, f0] = startingStep(obj, f, tspan, y0, ~, ~, intialStep) diff --git a/src/+matlode/+stepsizecontroller/Gustafson.m b/src/+matlode/+stepsizecontroller/Gustafson.m index 389f2fe..c0052ea 100644 --- a/src/+matlode/+stepsizecontroller/Gustafson.m +++ b/src/+matlode/+stepsizecontroller/Gustafson.m @@ -1,31 +1,20 @@ classdef Gustafson < matlode.stepsizecontroller.StartingController - %TODO Reference Gustaffson paper(1991) - %Slight derivation of the Gustafson paper on the coefficents + %Gustafsson, K. (1991). Control theoretic techniques for stepsize + % selection in explicit Runge-Kutta methods. ACM Transactions on Mathematical Software - properties (Constant) - Adaptive = true; - end properties (SetAccess = immutable) - Fac - FacMin - FacMax Ki Kp A - History end methods function obj = Gustafson(varargin) - obj.History = 2; - p = inputParser; + p.KeepUnmatched = true; - p.addParameter('Fac', 0.95); - p.addParameter('FacMin', 0.1); - p.addParameter('FacMax', 2); p.addParameter('Ki', 0.3); p.addParameter('Kp', -0.4); p.addParameter('A', 1); @@ -33,10 +22,10 @@ p.parse(varargin{:}); opts = p.Results; + varargout = p.Unmatched; + + obj = obj@matlode.stepsizecontroller.StartingController(2, varargout); - obj.Fac = opts.Fac; - obj.FacMin = opts.FacMin; - obj.FacMax = opts.FacMax; obj.Ki = opts.Ki; obj.Kp = opts.Kp; obj.A = opts.A; diff --git a/src/+matlode/+stepsizecontroller/SoberlandController.m b/src/+matlode/+stepsizecontroller/SoderlandController.m similarity index 62% rename from src/+matlode/+stepsizecontroller/SoberlandController.m rename to src/+matlode/+stepsizecontroller/SoderlandController.m index 24c1136..543692f 100644 --- a/src/+matlode/+stepsizecontroller/SoberlandController.m +++ b/src/+matlode/+stepsizecontroller/SoderlandController.m @@ -1,57 +1,49 @@ -classdef SoberlandController < matlode.stepsizecontroller.StartingController +classdef SoderlandController < matlode.stepsizecontroller.StartingController %SOBERLANDCONTROLLER %A general controller for all history based non-conditional controllers %can be expanded up to any amount of history gien proper coefficents - properties (Constant) - Adaptive = true; - end + %Söderlind, G. (2003). Digital filters in adaptive time-stepping. + % ACM Transactions on Mathematical Software properties (SetAccess = immutable) - Fac - FacMax - FacMin A B - AQfunc % Geneall @(q, A) (A / (1+q)) - History + AQfunc % Geneall @(q, A) (A / q) end methods - function obj = SoberlandController(varargin) + function obj = SoderlandController(varargin) - p = inputParser; + p = inputParser; + p.KeepUnmatched = true; - p.addParameter('Fac', 0.95); - p.addParameter('FacMin', 0.1); - p.addParameter('FacMax', 2); - p.addParameter('B', [0]); p.addParameter('A', [1]); - p.addRequired('AQfunc'); + p.addParameter('B', [0]); + p.addParameter('AQfunc', @(q, A) (A/q)); p.parse(varargin{:}); opts = p.Results; - - obj.Fac = opts.Fac; - obj.FacMin = opts.FacMin; - obj.FacMax = opts.FacMax; + varargout = p.Unmatched; if isempty(opts.A) - error('A cannot be empty') + error('A cannot be empty'); end - obj.A = opts.A; - obj.B = opts.B; + if isempty(opts.B) + error('B cannots be empty'); + end - %This is need because input parser struggles with - %function_handles if ~isa(opts.AQfunc, 'function_handle') error('AQfunc must be a function handle') end + obj = obj@matlode.stepsizecontroller.StartingController(max(length(opts.A), length(opts.B)), varargout); + + obj.A = opts.A; + obj.B = opts.B; obj.AQfunc = opts.AQfunc; - obj.History = length(obj.A); end function [accept, hNew, tNew] = newStepSize(obj, ~, t, ~, h, err, q, ~, ~) @@ -66,7 +58,7 @@ facScal = prod(err(1:obj.History).^obj.AQfunc(q,obj.A)); - hScal = prod((h(1:obj.History - 1) ./ h(2:obj.History)).^obj.B); + hScal = prod(abs(h(1:obj.History - 1) ./ h(2:obj.History)).^obj.B); prediction = obj.Fac * facScal * hScal; hNew = h(1) * min(obj.FacMax, max(obj.FacMin, prediction)); diff --git a/src/+matlode/+stepsizecontroller/StandardController.m b/src/+matlode/+stepsizecontroller/StandardController.m index 9f1058b..517a220 100644 --- a/src/+matlode/+stepsizecontroller/StandardController.m +++ b/src/+matlode/+stepsizecontroller/StandardController.m @@ -1,35 +1,13 @@ classdef StandardController < matlode.stepsizecontroller.StartingController %Standard Error Controller as described in Solving ODES I book - %Seperate from Soberland to help with performance(TODO test if true) + %Seperate from Soberland to help with performance - properties (Constant) - Adaptive = true; - end - - properties (SetAccess = immutable) - Fac - FacMin - FacMax - History - end methods function obj = StandardController(varargin) - obj.History = 1; - p = inputParser; - - p.addParameter('Fac', 0.95); - p.addParameter('FacMin', 0.1); - p.addParameter('FacMax', 2); - - p.parse(varargin{:}); - - opts = p.Results; + obj = obj@matlode.stepsizecontroller.StartingController(1, varargin{:}); - obj.Fac = opts.Fac; - obj.FacMin = opts.FacMin; - obj.FacMax = opts.FacMax; end function [accept, hNew, tNew] = newStepSize(obj, ~, t, ~, h, err, q, ~, ~) diff --git a/src/+matlode/+stepsizecontroller/StartingController.m b/src/+matlode/+stepsizecontroller/StartingController.m index 502898e..a44b612 100644 --- a/src/+matlode/+stepsizecontroller/StartingController.m +++ b/src/+matlode/+stepsizecontroller/StartingController.m @@ -3,38 +3,44 @@ % This class is mostly used to contian the starting procedure of all % adaptive controllers and some properties + properties (Constant) + Adaptive = true; + end + + properties (SetAccess = immutable) + Fac + FacMax + FacMin + InitalStep + end + + methods - function [h0, f0] = startingStep(obj, f, tspan, y0, order, errFunc, intialStep) - if intialStep ~= 0 - f0 = f(tspan(1), y0); - h0 = intialStep; - else - f0 = f(tspan(1), y0); - d0 = errFunc([y0, y0], 0); - d1 = errFunc([f0, f0], 0); - if any([d0, d1] < 1e-5) - h0 = 1e-6; - else - h0 = 0.01 * d0 / d1; - end - - y1 = y0 + h0 * f0; - f1 = f(tspan(1) + h0, y1); - err0 = errFunc([f1 - f0, f1 - f0], 0); - d2 = err0 / h0; - dm = max(d1, d2); - - if dm <= 1e-15 - h1 = max(1e-6, h0 * 1e-3); - else - h1 = (0.01 / dm)^(1 / (order + 1)); - end - - h0 = min(100 * h0, h1); - end + function obj = StartingController(hist, varargin) + p = inputParser; + p.KeepUnmatched = true; + + p.addParameter('Fac', 0.8); + p.addParameter('FacMin', 0.1); + p.addParameter('FacMax', 2); + p.addParameter('InitalStep', matlode.startingstep.BookStarting()); + + p.parse(varargin{:}); - %allocate for length of history - h0 = ones(1, obj.History) * h0; + opts = p.Results; + varargout = p.Unmatched; + + obj = obj@matlode.stepsizecontroller.StepSizeController(hist, varargout); + + obj.Fac = opts.Fac; + obj.FacMin = opts.FacMin; + obj.FacMax = opts.FacMax; + obj.InitalStep = opts.InitalStep; + + end + + function [h0, f0, fevals] = startingStep(obj, f, tspan, y0, order, errFunc, minStep, maxStep) + [h0, f0, fevals] = obj.InitalStep.startingStep(f, tspan, y0, order, errFunc, minStep, maxStep); end end end diff --git a/src/+matlode/+stepsizecontroller/StepSizeController.m b/src/+matlode/+stepsizecontroller/StepSizeController.m index e29c0ca..0677b9b 100644 --- a/src/+matlode/+stepsizecontroller/StepSizeController.m +++ b/src/+matlode/+stepsizecontroller/StepSizeController.m @@ -5,14 +5,27 @@ Adaptive end - properties (Abstract, SetAccess = immutable) + properties (SetAccess = immutable) History end + methods (Access = protected) + function obj = StepSizeController(hist, varargin) + + p = inputParser; + p.KeepUnmatched = false; + + %Gives better error checking + p.parse(varargin{:}); + + obj.History = hist; + end + end + methods (Abstract) %accept0, h0, and err0, respect the amount of history needed for a %controller - [h0, f0] = startingStep(obj, f, tspan, y0, order, errFunc, intialStep); + [h0, f0, fevals] = startingStep(obj, f, tspan, y0, order, errFunc, minStep, maxStep); [accept, hNew, tNew] = newStepSize(obj, prevAccept, t, tspan, h, err, q, nSteps, nFailed); end diff --git a/src/+matlode/Integrator.m b/src/+matlode/Integrator.m index 142308b..2a4c36a 100644 --- a/src/+matlode/Integrator.m +++ b/src/+matlode/Integrator.m @@ -6,7 +6,7 @@ PartitionMethod end - properties (Abstract, SetAccess = immutable) + properties (SetAccess = immutable) Adaptive Datatype end @@ -16,25 +16,32 @@ end methods + function obj = Integrator(adap, datatype) + + obj.Adaptive = adap; + obj.Datatype = datatype; + end + function sol = integrate(obj, f, tspan, y0, varargin) - if(length(tspan) < 2) - error('Unsupported tspan entry'); - end + [n,m] = size(tspan); - if ~issorted(tspan) && ~issorted(tspan, 'descend') - error('tspan must be sorted in order') + if ~ismatrix(tspan) + error('tspan cannot have this many dimensions'); + elseif n ~= 1 && m ~= 1 + error('tspan must be a vector') + elseif (length(tspan) < 2) + error('tspan must have a initial and final entry'); + elseif ~issorted(tspan) && ~issorted(tspan, 'descend') + error('tspan must be sorted in either descending or ascending order') end - %TODO - %optimize options for when only options struct is given - %Create options p = inputParser; opts = obj.matlodeSets(p, varargin{:}); - if isempty(opts.Dense) && length(tspan) > 2 && isa(opts.StepSizeController, 'matlode.stepsizecontroller.Fixed') - error('IntegrateTo is not supported for fixed step size, please use DenseOutput instead') + if isempty(opts.Dense) && length(tspan) > 2 && ~opts.StepSizeController.Adaptive + error('IntegrateTo is not supported for a non-adaptable controller, please use DenseOutput instead') end t = []; @@ -51,12 +58,11 @@ %determine size of partition if length(f) == 1 && ~obj.PartitionMethod [t, y, stats] = obj.timeLoop(f{1}, tspan, y0, opts); - elseif length(f) > 1 + elseif isempty(f) + error('Partitioned system solves, require atleast one partition to be provided') + else %TODO %paritioning setup - - else - error('Need at least one function provided') end else @@ -75,12 +81,11 @@ function opts = matlodeSets(obj, p, varargin) %Assign to controllers - p.addParameter('InitialStep', 0); p.addParameter('StepSizeController', matlode.stepsizecontroller.Fixed(1000)); - p.addParameter('ErrNorm', matlode.errnorm.HistNorm.errEstimate(sqrt(eps), sqrt(eps))); + p.addParameter('ErrNorm', matlode.errnorm.InfNorm.errEstimate(sqrt(eps), sqrt(eps))); p.addParameter('Jacobian', []); p.addParameter('ChunkSize', 1000); - p.addParameter('Dense', [] ); + p.addParameter('Dense', []); p.addParameter('MaxStep', inf); p.parse(varargin{:}); @@ -89,8 +94,7 @@ %make sure a method can use a adaptive controller if ~obj.Adaptive && opts.StepSizeController.Adaptive - warning('Current method does not have the ability to use a adaptive step size controller'); - %overrider user choice??? + error('Current method does not have the ability to use a adaptive step size controller'); end diff --git a/src/+matlode/OneStepIntegrator.m b/src/+matlode/OneStepIntegrator.m index c45ce68..65ded7e 100644 --- a/src/+matlode/OneStepIntegrator.m +++ b/src/+matlode/OneStepIntegrator.m @@ -3,6 +3,10 @@ methods (Access = protected) + function obj = OneStepIntegrator(varargin) + obj = obj@matlode.Integrator(varargin{:}); + end + function opts = matlodeSets(obj, p, varargin) %OneStepIntegrator Specific options From 5aaa799933ea642cc5ab1de329f8fbe1b180ef66 Mon Sep 17 00:00:00 2001 From: rjg18 Date: Wed, 14 Jul 2021 13:06:06 -0400 Subject: [PATCH 05/17] Simplifications to the time loop, Validations, small changes --- src/+matlode/+rk/+erk/ERK.m | 115 +++++++----------- src/+matlode/+startingstep/BookStarting.m | 4 +- src/+matlode/+startingstep/SetStep.m | 12 +- src/+matlode/+startingstep/WattsStarting.m | 10 +- src/+matlode/+stepsizecontroller/Gustafson.m | 6 +- .../+stepsizecontroller/StartingController.m | 14 +-- src/+matlode/+util/scalarValidationFunc.m | 5 + src/+matlode/Integrator.m | 11 +- 8 files changed, 83 insertions(+), 94 deletions(-) create mode 100644 src/+matlode/+util/scalarValidationFunc.m diff --git a/src/+matlode/+rk/+erk/ERK.m b/src/+matlode/+rk/+erk/ERK.m index 3409397..69d5fc9 100644 --- a/src/+matlode/+rk/+erk/ERK.m +++ b/src/+matlode/+rk/+erk/ERK.m @@ -24,8 +24,6 @@ numVars = length(y0); multiTspan = length(tspan) > 2; isDense = ~isempty(opts.Dense); - overstep = false; - startupErr = true; %Structure access optimizations errNormFunc = opts.ErrNorm; @@ -33,6 +31,7 @@ newStepFunc = @(prevAccept, tCur, tspan, hHist, err, q, nSteps, nFailed) opts.StepSizeController.newStepSize(prevAccept, tCur, tspan, hHist, err, q, nSteps, nFailed); + k = zeros(numVars, obj.Stage); y = []; t = []; @@ -50,9 +49,15 @@ tCur = tspan(1); tNext = tCur; ti = 2; + tl = 2; + if isDense + ti = length(tspan); + else + tl = length(tspan); + end tdir = sign(tspan(end) - tspan(1)); - hmax = opts.MaxStep * tdir; + hmax = min([abs(opts.MaxStep), abs(tspan(end) - tspan(1))]) * tdir; hmin = 64 * eps(tspan(1)) * tdir; y(:, 1) = y0; @@ -71,7 +76,7 @@ hHist = ones(1, stepController.History) * h0; hN = hHist(1); - err = zeros(1, length(hHist)); + err = ones(1, length(hHist)); accept = true; @@ -125,7 +130,6 @@ %Will keep looping until accepted step while true - %%%% % Start of method specific time loop code %%%% @@ -145,11 +149,6 @@ if stepController.Adaptive yEmbbeded = k * (hC * obj.E'); err(:, 1) = errNormFunc([yNext, yCur], yEmbbeded); - - if startupErr - err(:, 2:end) = err(:, 1); - end - startupErr = false; end %%%% @@ -159,15 +158,10 @@ %Find next Step [accept, hN, tNext] = newStepFunc(prevAccept, tCur, tspan, hHist, err, q, nSteps, nFailed); - %set new step to be in range - if stepController.Adaptive - hN = min(hmax * tdir, hN * tdir) * tdir; - end - %Check if step is really small - if hN * tdir < hmin * tdir + if abs(hN) < abs(hmin) if nSmallSteps == 0 - warning('The step the integrator is taking extremely small, results may not be optimum') + warning('The step the integrator is taking extremely small, results may not be optimal') end %accept step since the step cannot get any smaller nSmallSteps = nSmallSteps + 1; @@ -187,13 +181,13 @@ end - nSteps = nSteps + 1; - - %check if controller failed to overstep for dense - if isDense && overstep && tNext * tdir < tspan(ti) * tdir - overstep = false; + %set new step to be in range + if stepController.Adaptive + hN = min(abs(hmax), abs(hN)) * tdir; end + nSteps = nSteps + 1; + %Check for all memory allocation if ~multiTspan @@ -209,53 +203,41 @@ %check if condition holds %used for end checking,integrate to, and dense output - if ~(tspan(ti) * tdir > (tNext + hN) * tdir) - %Dense Output - if isDense && multiTspan && ti ~= length(tspan) - - if overstep - while tNext * tdir > tspan(ti) * tdir && ti ~= length(tspan) - %Call Dense output function and record - - t(:, ti) = tspan(ti); - [y(:, ti), tempFevals] = opts.Dense.denseOut(f, tCur, tspan(ti), yCur, yNext, k, hC); - nFevals = nFevals + tempFevals; - - ti = ti + 1; - end - - if (tspan(ti) - tNext) * tdir < hmin * tdir - t(:, ti) = tspan(ti); - y(:, ti) = yNext; - ti = ti + 1; - end + if ~(tspan(ti) * tdir > (tNext + hN) * tdir) && tl == length(tspan) + + %integrate to/ End point + %check if close enough with hmin + if (tspan(ti) - tNext) * tdir < hmin * tdir - overstep = false; - else - overstep = true; - - %check for tspan(end) to hit exactly - if stepController.Adaptive && ~(tspan(end) * tdir > (tNext + hN) * tdir) - hN = tspan(end) - tNext; - end + if multiTspan + t(:, ti) = tspan(ti); + y(:, ti) = yNext; end + ti = ti + 1; else - %integrate to/ End point - %check if close enough with hmin - if (tspan(ti) - tNext) * tdir < hmin * tdir - - if multiTspan - t(:, ti) = tspan(ti); - y(:, ti) = yNext; - end - ti = ti + 1; - else - if stepController.Adaptive - hN = tspan(ti) - tNext; - end - + + if stepController.Adaptive + hN = tspan(ti) - tNext; end end + + elseif isDense && multiTspan && tl ~= length(tspan) && ~(tspan(tl) * tdir > (tNext + hN) * tdir) + + while tNext * tdir > tspan(tl) * tdir && tl ~= length(tspan) + %Call Dense output function and record + + t(:, tl) = tspan(tl); + [y(:, tl), tempFevals] = opts.Dense.denseOut(f, tCur, tspan(tl), yCur, yNext, k, hC); + nFevals = nFevals + tempFevals; + + tl = tl + 1; + end + + if tl == length(tspan) && (tspan(ti) - tNext) * tdir < hmin * tdir + t(:, ti) = tspan(ti); + y(:, ti) = yNext; + ti = ti + 1; + end end end @@ -275,12 +257,7 @@ stats.nFevals = nFevals; stats.nSmallSteps = nSmallSteps; - end - - end - - end diff --git a/src/+matlode/+startingstep/BookStarting.m b/src/+matlode/+startingstep/BookStarting.m index 0d630af..4afd5bc 100644 --- a/src/+matlode/+startingstep/BookStarting.m +++ b/src/+matlode/+startingstep/BookStarting.m @@ -7,7 +7,7 @@ obj = obj@matlode.startingstep.StartingStep(varargin{:}); end - function [h0, f0, fevals] = startingStep(obj, f, tspan, y0, order, errFunc, minStep, maxStep) + function [h0, f0, fevals] = startingStep(~, f, tspan, y0, order, errFunc, minStep, maxStep) fevals = 2; tdir = sign(tspan(end) - tspan(1)); @@ -21,6 +21,8 @@ else h0 = 0.01 * d0 / d1; end + + h0 = max([min([h0, maxStep * tdir]), minStep * tdir]); y1 = y0 + h0 * f0 * tdir; f1 = f(tspan(1) + h0 * tdir, y1); diff --git a/src/+matlode/+startingstep/SetStep.m b/src/+matlode/+startingstep/SetStep.m index 73f1f92..2373fd9 100644 --- a/src/+matlode/+startingstep/SetStep.m +++ b/src/+matlode/+startingstep/SetStep.m @@ -15,15 +15,15 @@ function [h0, f0, fevals] = startingStep(obj, ~, tspan, ~, ~, ~, minStep, maxStep) fevals = 0; f0 = []; - if obj.StartingStep > maxStep - error('The starting step is greater then the max step') - end - if obj.StartingStep < minStep - error('The starting step cannot be so small') - end if sign(obj.StartingStep) ~= sign(tspan(end) - tspan(1)) error('The starting step cannot go in the opposite direction') end + if obj.StartingStep > maxStep + error('The starting step is greater then the maximum step %f', maxStep) + elseif obj.StartingStep < minStep + error('The starting step cannot be smaller than the minimum step %f', minStep) + end + h0 = max([min([obj.StartingStep, maxStep]), minStep]); end end diff --git a/src/+matlode/+startingstep/WattsStarting.m b/src/+matlode/+startingstep/WattsStarting.m index 353ac4e..39f117d 100644 --- a/src/+matlode/+startingstep/WattsStarting.m +++ b/src/+matlode/+startingstep/WattsStarting.m @@ -1,5 +1,5 @@ classdef WattsStarting < matlode.startingstep.StartingStep - %Based of Watts starting procedure + %Based of Watt's starting procedure %Watts, H. A. (1983). Starting step size for an ODE solver. Journal of Computational and Applied Mathematics properties (SetAccess = immutable) @@ -15,10 +15,10 @@ p = inputParser; p.KeepUnmatched = true; - p.addParameter('InitTol', eps^(1/2)); - p.addParameter('InitEpsMax', eps^(1/2)); - p.addParameter('InitEpsMin', 100 * eps); - p.addParameter('Fac', 0.8); + p.addParameter('InitTol', eps^(1/2), matlode.util.scalarValidationFunc); + p.addParameter('InitEpsMax', eps^(1/2), matlode.util.scalarValidationFunc); + p.addParameter('InitEpsMin', 100 * eps, matlode.util.scalarValidationFunc); + p.addParameter('Fac', 0.8, matlode.util.scalarValidationFunc); p.parse(varargin{:}); diff --git a/src/+matlode/+stepsizecontroller/Gustafson.m b/src/+matlode/+stepsizecontroller/Gustafson.m index c0052ea..f13d01e 100644 --- a/src/+matlode/+stepsizecontroller/Gustafson.m +++ b/src/+matlode/+stepsizecontroller/Gustafson.m @@ -15,9 +15,9 @@ p = inputParser; p.KeepUnmatched = true; - p.addParameter('Ki', 0.3); - p.addParameter('Kp', -0.4); - p.addParameter('A', 1); + p.addParameter('Ki', 0.3, matlode.util.scalarValidationFunc); + p.addParameter('Kp', -0.4, matlode.util.scalarValidationFunc); + p.addParameter('A', 1, matlode.util.scalarValidationFunc); p.parse(varargin{:}); diff --git a/src/+matlode/+stepsizecontroller/StartingController.m b/src/+matlode/+stepsizecontroller/StartingController.m index a44b612..87f6093 100644 --- a/src/+matlode/+stepsizecontroller/StartingController.m +++ b/src/+matlode/+stepsizecontroller/StartingController.m @@ -1,7 +1,7 @@ classdef (Abstract) StartingController < matlode.stepsizecontroller.StepSizeController - %Adaptive controller - % This class is mostly used to contian the starting procedure of all - % adaptive controllers and some properties + % This class is used to contain common adaptive stepsizecontroler + % properties and to allow the user to change the starting step + % procedure properties (Constant) Adaptive = true; @@ -20,10 +20,10 @@ p = inputParser; p.KeepUnmatched = true; - p.addParameter('Fac', 0.8); - p.addParameter('FacMin', 0.1); - p.addParameter('FacMax', 2); - p.addParameter('InitalStep', matlode.startingstep.BookStarting()); + p.addParameter('Fac', 0.8, matlode.util.scalarValidationFunc); + p.addParameter('FacMin', 0.1, matlode.util.scalarValidationFunc); + p.addParameter('FacMax', 2, matlode.util.scalarValidationFunc); + p.addParameter('InitalStep', matlode.startingstep.WattsStarting()); p.parse(varargin{:}); diff --git a/src/+matlode/+util/scalarValidationFunc.m b/src/+matlode/+util/scalarValidationFunc.m new file mode 100644 index 0000000..8ef24b8 --- /dev/null +++ b/src/+matlode/+util/scalarValidationFunc.m @@ -0,0 +1,5 @@ +function validFunc = scalarValidationFunc() +msg = 'Value must be a scalar'; +validFunc = @(x) assert(isscalar(x), msg); +end + diff --git a/src/+matlode/Integrator.m b/src/+matlode/Integrator.m index 2a4c36a..e4ae86e 100644 --- a/src/+matlode/Integrator.m +++ b/src/+matlode/Integrator.m @@ -32,8 +32,8 @@ error('tspan must be a vector') elseif (length(tspan) < 2) error('tspan must have a initial and final entry'); - elseif ~issorted(tspan) && ~issorted(tspan, 'descend') - error('tspan must be sorted in either descending or ascending order') + elseif ~issorted(tspan, 'strictascend') && ~issorted(tspan, 'strictdescend') + error('tspan must have unique values and be sorted in either descending or ascending order') end %Create options @@ -44,6 +44,7 @@ error('IntegrateTo is not supported for a non-adaptable controller, please use DenseOutput instead') end + t = []; y = []; stats = []; @@ -61,8 +62,12 @@ elseif isempty(f) error('Partitioned system solves, require atleast one partition to be provided') else - %TODO %paritioning setup + if obj.PartitionMethod + [t, y, stats] = obj.timeLoop(f, tspan, y0, opts); + else + error('This method does not work with a partitioned f'); + end end else From c7f70cab9902c6d328319e8867741356db7d78bd Mon Sep 17 00:00:00 2001 From: rjg18 Date: Thu, 15 Jul 2021 13:06:55 -0400 Subject: [PATCH 06/17] Quick Fixes, and Time Loop Performance --- src/+matlode/+rk/+erk/ERK.m | 73 +++++++++++-------- src/+matlode/+startingstep/SetStep.m | 2 +- src/+matlode/+stepsizecontroller/Fixed.m | 9 +-- .../+stepsizecontroller/SoderlandController.m | 2 +- 4 files changed, 46 insertions(+), 40 deletions(-) diff --git a/src/+matlode/+rk/+erk/ERK.m b/src/+matlode/+rk/+erk/ERK.m index 69d5fc9..48e24ea 100644 --- a/src/+matlode/+rk/+erk/ERK.m +++ b/src/+matlode/+rk/+erk/ERK.m @@ -36,14 +36,19 @@ y = []; t = []; + tlen = 0; + if multiTspan y = zeros(numVars, length(tspan)); t = zeros(1, length(tspan)); else y = zeros(numVars, opts.ChunkSize); t = zeros(1, opts.ChunkSize); + tlen = length(t); end + + %inital values t(1,1) = tspan(1); tCur = tspan(1); @@ -56,6 +61,8 @@ tl = length(tspan); end + tspanlen = length(tspan); + tdir = sign(tspan(end) - tspan(1)); hmax = min([abs(opts.MaxStep), abs(tspan(end) - tspan(1))]) * tdir; hmin = 64 * eps(tspan(1)) * tdir; @@ -101,7 +108,7 @@ %%%% %Time Loop - while ti <= length(tspan) + while ti <= tspanlen %Cycle history err = circshift(err, 1, 2); hHist = circshift(hHist, 1, 2); @@ -137,17 +144,14 @@ for i = fsalStart:obj.Stage k(:, i) = f(tCur + hC * obj.C(i), yCur + k(:, 1:i-1) * (hC * obj.A(i, 1:i-1)')); end - - yNext = yCur + k * (hC * obj.B'); - - nFevals = nFevals + fevalIterCounts; - prevAccept = accept; + khc = k * hC; + yNext = yCur + khc * obj.B'; %Perform error estimates %Could maybe leave out of method specific zone? if stepController.Adaptive - yEmbbeded = k * (hC * obj.E'); + yEmbbeded = khc * obj.E'; err(:, 1) = errNormFunc([yNext, yCur], yEmbbeded); end @@ -155,6 +159,10 @@ % End of method specific time loop code %%%% + nFevals = nFevals + fevalIterCounts; + + prevAccept = accept; + %Find next Step [accept, hN, tNext] = newStepFunc(prevAccept, tCur, tspan, hHist, err, q, nSteps, nFailed); @@ -192,53 +200,54 @@ if ~multiTspan %Allocate more memory if non-dense - if nSteps > length(t) + if nSteps > tlen y = [y, zeros(numVars, opts.ChunkSize)]; t = [t, zeros(1, opts.ChunkSize)]; + tlen = length(t); end t(:, nSteps) = tNext; y(:, nSteps) = yNext; + elseif isDense + %Dense output + while tl ~= tspanlen && tNext * tdir > tspan(tl) * tdir + + %incase lucky and land on point, can be cheaper + %than dense output if dense requires fevals + if abs(tspan(tl) - tNext) < abs(hmin) + t(:, tl) = tspan(tl); + y(:, tl) = yNext; + else + %Call Dense output function and record + t(:, tl) = tspan(tl); + [y(:, tl), tempFevals] = opts.Dense.denseOut(f, tCur, tspan(tl), yCur, yNext, k, hC); + nFevals = nFevals + tempFevals; + end + tl = tl + 1; + end end %check if condition holds - %used for end checking,integrate to, and dense output - if ~(tspan(ti) * tdir > (tNext + hN) * tdir) && tl == length(tspan) + %used for end checking and integrate to + if tspan(ti) * tdir <= (tNext + hN) * tdir %integrate to/ End point %check if close enough with hmin - if (tspan(ti) - tNext) * tdir < hmin * tdir + if abs(tspan(ti) - tNext) < abs(hmin) if multiTspan t(:, ti) = tspan(ti); y(:, ti) = yNext; end ti = ti + 1; - else + elseif stepController.Adaptive - if stepController.Adaptive - hN = tspan(ti) - tNext; - end + hN = tspan(ti) - tNext; end - elseif isDense && multiTspan && tl ~= length(tspan) && ~(tspan(tl) * tdir > (tNext + hN) * tdir) - - while tNext * tdir > tspan(tl) * tdir && tl ~= length(tspan) - %Call Dense output function and record - - t(:, tl) = tspan(tl); - [y(:, tl), tempFevals] = opts.Dense.denseOut(f, tCur, tspan(tl), yCur, yNext, k, hC); - nFevals = nFevals + tempFevals; - - tl = tl + 1; - end - - if tl == length(tspan) && (tspan(ti) - tNext) * tdir < hmin * tdir - t(:, ti) = tspan(ti); - y(:, ti) = yNext; - ti = ti + 1; - end end + + end %End of Timeloop work diff --git a/src/+matlode/+startingstep/SetStep.m b/src/+matlode/+startingstep/SetStep.m index 2373fd9..352fb8b 100644 --- a/src/+matlode/+startingstep/SetStep.m +++ b/src/+matlode/+startingstep/SetStep.m @@ -16,7 +16,7 @@ fevals = 0; f0 = []; if sign(obj.StartingStep) ~= sign(tspan(end) - tspan(1)) - error('The starting step cannot go in the opposite direction') + error('The starting step cannot go in the opposite direction, %f', obj.StartingStep) end if obj.StartingStep > maxStep error('The starting step is greater then the maximum step %f', maxStep) diff --git a/src/+matlode/+stepsizecontroller/Fixed.m b/src/+matlode/+stepsizecontroller/Fixed.m index 35aa4da..df8dd8c 100644 --- a/src/+matlode/+stepsizecontroller/Fixed.m +++ b/src/+matlode/+stepsizecontroller/Fixed.m @@ -16,13 +16,10 @@ obj.Steps = steps; end - function [h0, f0] = startingStep(obj, f, tspan, y0, ~, ~, intialStep) - if intialStep ~= 0 - warning('Ignoring initialStep option, Controller is Fixed'); - end - - f0 = f(tspan(1), y0); + function [h0, f0, fevals] = startingStep(obj, ~, tspan, ~, ~, ~, ~, ~) h0 = (tspan(2) - tspan(1)) / obj.Steps; + f0 = []; + fevals = 0; end function [accept, h, tNew] = newStepSize(~, ~, ~, tspan, h, ~, ~, nSteps, ~, ~) diff --git a/src/+matlode/+stepsizecontroller/SoderlandController.m b/src/+matlode/+stepsizecontroller/SoderlandController.m index 543692f..9b7a27a 100644 --- a/src/+matlode/+stepsizecontroller/SoderlandController.m +++ b/src/+matlode/+stepsizecontroller/SoderlandController.m @@ -48,7 +48,7 @@ function [accept, hNew, tNew] = newStepSize(obj, ~, t, ~, h, err, q, ~, ~) - accept = mean(err) <= 1; + accept = err(1) <= 1; if accept tNew = t + h(1); From 50dfaa0079a6e8aeb36b5f84e46869626f955363 Mon Sep 17 00:00:00 2001 From: rjg18 Date: Tue, 20 Jul 2021 12:36:45 -0400 Subject: [PATCH 07/17] Setup for general time loop, PI Controller update --- src/+matlode/+errnorm/InfNorm.m | 19 +- src/+matlode/+errnorm/NormErr.m | 16 +- src/+matlode/+errnorm/StandardNorm.m | 19 +- src/+matlode/+rk/+erk/ERK.m | 282 +++--------------- src/+matlode/+rk/RungeKutta.m | 25 +- src/+matlode/+rosenbrock/Rosenbrock.m | 55 ++++ src/+matlode/+startingstep/BookStarting.m | 8 +- src/+matlode/+startingstep/StartingStep.m | 2 +- src/+matlode/+startingstep/WattsStarting.m | 6 +- .../+stepsizecontroller/+soderpresets/PI.m | 15 + src/+matlode/+stepsizecontroller/Gustafson.m | 4 +- src/+matlode/Integrator.m | 8 +- src/+matlode/OneStepIntegrator.m | 199 ++++++++++++ 13 files changed, 394 insertions(+), 264 deletions(-) create mode 100644 src/+matlode/+rosenbrock/Rosenbrock.m create mode 100644 src/+matlode/+stepsizecontroller/+soderpresets/PI.m diff --git a/src/+matlode/+errnorm/InfNorm.m b/src/+matlode/+errnorm/InfNorm.m index a065cb2..6bc68c9 100644 --- a/src/+matlode/+errnorm/InfNorm.m +++ b/src/+matlode/+errnorm/InfNorm.m @@ -1,10 +1,17 @@ classdef InfNorm < matlode.errnorm.NormErr - methods (Static) - function errFunc = errEstimate(AbsTol, RelTol) - - scFunc = @(y) (AbsTol + max(abs(y(:, 1)), abs(y(:, 2))) * RelTol); - errFunc = @(y, yE) norm(yE ./ scFunc(y), 'Inf'); - + methods + function obj = InfNorm(abstol, reltol) + obj = obj@matlode.errnorm.NormErr(abstol, reltol); + end + + function err = errEstimate(obj, y, yE) + persistent abstol reltol + if isempty(abstol) + abstol = obj.AbsTol; + reltol = obj.RelTol; + end + sc = abstol + max(abs(y(:, 1)), abs(y(:, 2))) * reltol; + err = norm(yE ./ sc, inf); end end end diff --git a/src/+matlode/+errnorm/NormErr.m b/src/+matlode/+errnorm/NormErr.m index 5f5a08a..647b607 100644 --- a/src/+matlode/+errnorm/NormErr.m +++ b/src/+matlode/+errnorm/NormErr.m @@ -1,6 +1,18 @@ classdef (Abstract) NormErr - methods (Abstract,Static) - errFunc = errEstimate(AbsTol, RelTol) + properties (SetAccess = immutable) + RelTol + AbsTol + end + + methods (Abstract) + errFunc = errEstimate(y, yE) + end + + methods + function obj = NormErr(absTol, relTol) + obj.AbsTol = absTol; + obj.RelTol = relTol; + end end end diff --git a/src/+matlode/+errnorm/StandardNorm.m b/src/+matlode/+errnorm/StandardNorm.m index b96befe..064b6b7 100644 --- a/src/+matlode/+errnorm/StandardNorm.m +++ b/src/+matlode/+errnorm/StandardNorm.m @@ -1,10 +1,17 @@ classdef StandardNorm < matlode.errnorm.NormErr - methods (Static) - function errFunc = errEstimate(AbsTol, RelTol) - - scFunc = @(y) (AbsTol + max(abs(y(:, 1)), abs(y(:, 2))) * RelTol); - errFunc = @(y, yE) sqrt(1/size(y,1)) * norm(yE ./ scFunc(y)); - + methods + function obj = StandardNorm(abstol, reltol) + obj = obj@matlode.errnorm.NormErr(abstol, reltol); + end + + function err = errEstimate(obj, y, yE) + persistent abstol reltol + if isempty(abstol) + abstol = obj.AbsTol; + reltol = obj.RelTol; + end + sc = abstol + max(abs(y(:, 1)), abs(y(:, 2))) * reltol; + err = sqrt(1/size(y,1)) * norm(yE ./ sc); end end end diff --git a/src/+matlode/+rk/+erk/ERK.m b/src/+matlode/+rk/+erk/ERK.m index 48e24ea..d40600e 100644 --- a/src/+matlode/+rk/+erk/ERK.m +++ b/src/+matlode/+rk/+erk/ERK.m @@ -19,254 +19,62 @@ opts = matlodeSets@matlode.rk.RungeKutta(obj, p, varargin{:}); end - function [t, y, stats] = timeLoop(obj, f, tspan, y0, opts, varargin) - - numVars = length(y0); - multiTspan = length(tspan) > 2; - isDense = ~isempty(opts.Dense); - - %Structure access optimizations - errNormFunc = opts.ErrNorm; - stepController = opts.StepSizeController; - newStepFunc = @(prevAccept, tCur, tspan, hHist, err, q, nSteps, nFailed) opts.StepSizeController.newStepSize(prevAccept, tCur, tspan, hHist, err, q, nSteps, nFailed); - - - - k = zeros(numVars, obj.Stage); - y = []; - t = []; - - tlen = 0; - - if multiTspan - y = zeros(numVars, length(tspan)); - t = zeros(1, length(tspan)); - else - y = zeros(numVars, opts.ChunkSize); - t = zeros(1, opts.ChunkSize); - tlen = length(t); + function [k, yN, err] = timeStep(obj, f, t, y, h, k, ~, prevAccept) + persistent fsal s a b c + if isempty(fsal) + fsal = obj.FsalStart; + s = obj.Stage; + a = obj.A; + b = obj.B; + c = obj.C; end - - - %inital values - t(1,1) = tspan(1); - tCur = tspan(1); - tNext = tCur; - ti = 2; - tl = 2; - if isDense - ti = length(tspan); - else - tl = length(tspan); + if fsal && prevAccept + k(:, 1) = k(:, end); end - tspanlen = length(tspan); - - tdir = sign(tspan(end) - tspan(1)); - hmax = min([abs(opts.MaxStep), abs(tspan(end) - tspan(1))]) * tdir; - hmin = 64 * eps(tspan(1)) * tdir; - - y(:, 1) = y0; - yNext = y0; - - %temporary stats - nSteps = 1; - nFailed = 0; - nSmallSteps = 0; - - q = min(obj.Order, obj.EmbeddedOrder); - - %First step - %allocates memory for first step - [h0, f0, nFevals] = stepController.startingStep(f, tspan, y0, obj.Order, errNormFunc, hmin, hmax); - - hHist = ones(1, stepController.History) * h0; - hN = hHist(1); - err = ones(1, length(hHist)); - - accept = true; - - %%%% - % Start of method specific intializing code - %%%% - - fsalStart = uint32(obj.FSAL) + 1; - fevalIterCounts = double(obj.Stage - fsalStart + 1); - - if obj.FSAL - if nFevals == 0 - k(:, end) = f(tspan(1), y0); - nFevals = nFevals + 1; - else - k(:, end) = f0; + for i = fsal:s + z = y; + for j = 1:i-1 + z = z + k(:, j) .* (h .* a(i, j)); end + thc = t + h .* c(i); + k(:, i) = f(thc, z); end - - %%%% - % End of method specific intializing code - %%%% - - %Time Loop - while ti <= tspanlen - %Cycle history - err = circshift(err, 1, 2); - hHist = circshift(hHist, 1, 2); - - yCur = yNext; - tCur = tNext; - hC = hN; - hHist(1) = hN; - - %Subject to change - hmin = 64 * eps(tCur) * tdir; - - %%%% - % Start of method specific before time loop code - %%%% - - if obj.FSAL - k(:, 1) = k(:, end); - end - - %%%% - % End of method specific before time loop code - %%%% - - %Accept Loop - %Will keep looping until accepted step - while true - - %%%% - % Start of method specific time loop code - %%%% - - for i = fsalStart:obj.Stage - k(:, i) = f(tCur + hC * obj.C(i), yCur + k(:, 1:i-1) * (hC * obj.A(i, 1:i-1)')); - end - - khc = k * hC; - yNext = yCur + khc * obj.B'; - - %Perform error estimates - %Could maybe leave out of method specific zone? - if stepController.Adaptive - yEmbbeded = khc * obj.E'; - err(:, 1) = errNormFunc([yNext, yCur], yEmbbeded); - end - - %%%% - % End of method specific time loop code - %%%% - - nFevals = nFevals + fevalIterCounts; - - prevAccept = accept; - - %Find next Step - [accept, hN, tNext] = newStepFunc(prevAccept, tCur, tspan, hHist, err, q, nSteps, nFailed); - - %Check if step is really small - if abs(hN) < abs(hmin) - if nSmallSteps == 0 - warning('The step the integrator is taking extremely small, results may not be optimal') - end - %accept step since the step cannot get any smaller - nSmallSteps = nSmallSteps + 1; - hN = hmin; - tNext = tCur + hmin; - break; - end - - %check step acception - if accept - break; - end - - nFailed = nFailed + 1; - hC = hN; - hHist(1) = hN; - - end - - %set new step to be in range - if stepController.Adaptive - hN = min(abs(hmax), abs(hN)) * tdir; - end - - nSteps = nSteps + 1; - - %Check for all memory allocation - if ~multiTspan - - %Allocate more memory if non-dense - if nSteps > tlen - y = [y, zeros(numVars, opts.ChunkSize)]; - t = [t, zeros(1, opts.ChunkSize)]; - tlen = length(t); - end - - t(:, nSteps) = tNext; - y(:, nSteps) = yNext; - elseif isDense - %Dense output - while tl ~= tspanlen && tNext * tdir > tspan(tl) * tdir - - %incase lucky and land on point, can be cheaper - %than dense output if dense requires fevals - if abs(tspan(tl) - tNext) < abs(hmin) - t(:, tl) = tspan(tl); - y(:, tl) = yNext; - else - %Call Dense output function and record - t(:, tl) = tspan(tl); - [y(:, tl), tempFevals] = opts.Dense.denseOut(f, tCur, tspan(tl), yCur, yNext, k, hC); - nFevals = nFevals + tempFevals; - end - tl = tl + 1; - end - end - - %check if condition holds - %used for end checking and integrate to - if tspan(ti) * tdir <= (tNext + hN) * tdir - - %integrate to/ End point - %check if close enough with hmin - if abs(tspan(ti) - tNext) < abs(hmin) - - if multiTspan - t(:, ti) = tspan(ti); - y(:, ti) = yNext; - end - ti = ti + 1; - elseif stepController.Adaptive - - hN = tspan(ti) - tNext; - end - - end - - + yN = y + k * (h .* b'); + err = 1; + end + + function [k, yN, err] = timeStepErr(obj, f, t, y, h, k, ErrNorm, prevAccept) + persistent fsal s a b c e + if isempty(fsal) + fsal = obj.FsalStart; + s = obj.Stage; + a = obj.A; + b = obj.B; + c = obj.C; + e = obj.E; end - %End of Timeloop work - %Truncate extra memory - if ~multiTspan - t = t(:, 1:nSteps); - y = y(:, 1:nSteps); - else - t(:, end) = tspan(end); - y(:, end) = yNext; + if fsal && prevAccept + k(:, 1) = k(:, end); end - %Create Stats - stats.nSteps = nSteps - 1; - stats.nFailed = nFailed; - stats.nFevals = nFevals; - stats.nSmallSteps = nSmallSteps; - + for i = fsal:s + z = y; + for j = 1:i-1 + z = z + k(:, j) .* (h .* a(i, j)); + end + thc = t + h .* c(i); + k(:, i) = f(thc, z); + end + kh = k .* h; + yN = y + kh * b'; + yE = kh * e'; + err = ErrNorm.errEstimate([yN, y], yE); end + + end end diff --git a/src/+matlode/+rk/RungeKutta.m b/src/+matlode/+rk/RungeKutta.m index 3eb9cd5..6bd53cd 100644 --- a/src/+matlode/+rk/RungeKutta.m +++ b/src/+matlode/+rk/RungeKutta.m @@ -11,6 +11,7 @@ EmbeddedOrder Order FSAL + FsalStart end methods @@ -27,7 +28,7 @@ obj.Order = order; obj.Stage = size(b, 2); obj.FSAL = all(a(end, :) == b) && all(a(1, :) == 0); - + obj.FsalStart = uint32(obj.FSAL) + 1; end function obj = fromCoeffs(a, b, bHat, c, e, bTheta, order, embededOrder) @@ -59,6 +60,28 @@ opts = matlodeSets@matlode.OneStepIntegrator(obj, p, varargin{:}); end + + + function [k, fevals, fevalIterCounts] = timeLoopBeforeLoop(obj, f0, t0, y0) + + k = zeros(length(y0), obj.Stage); + + fevalIterCounts = double(obj.Stage - obj.FsalStart + 1); + + if obj.FSAL + if isempty(f0) + k(:, end) = f(t0, y0); + fevals = 1; + else + k(:, end) = f0; + fevals = 0; + end + end + end + + function [q] = timeLoopInit(obj) + q = min(obj.Order, obj.EmbeddedOrder); + end end diff --git a/src/+matlode/+rosenbrock/Rosenbrock.m b/src/+matlode/+rosenbrock/Rosenbrock.m new file mode 100644 index 0000000..ba8f1e6 --- /dev/null +++ b/src/+matlode/+rosenbrock/Rosenbrock.m @@ -0,0 +1,55 @@ +classdef Rosenbrock < matlode.OneStepIntegrator + %ROSENBROCK Summary of this class goes here + % Detailed explanation goes here + + properties (SetAccess = immutable) + A + B + C + G + BHat + E + Stage + EmbeddedOrder + Order + FSAL + FsalStart + end + + methods + function obj = Rosenbrock(a, b, bHat, c, e, g, order, embeddedOrder) + obj.A = a; + obj.B = b; + obj.BHat = bHat; + obj.C = c; + obj.E = e; + obj.G = g; + obj.EmbeddedOrder = embeddedOrder; + obj.Order = order; + obj.Stage = size(b, 2); + obj.FSAL = all(a(end, :) == b) && all(a(1, :) == 0); + obj.FsalStart = uint32(obj.FSAL) + 1; + end + + + end + + methods (Access = protected) + function [k, yN, err] = timeStep(obj, f, t, y, h, k, ErrNorm, prevAccept) + + end + + function [k, yN, err] = timeStepErr(obj, f, t, y, h, k, ErrNorm, prevAccept) + + end + + function [k, fevals, fevalIterCounts] = timeLoopBeforeLoop(obj, f0, t0, y0) + + end + + function [q] = timeLoopInit(obj) + + end + end +end + diff --git a/src/+matlode/+startingstep/BookStarting.m b/src/+matlode/+startingstep/BookStarting.m index 4afd5bc..181116c 100644 --- a/src/+matlode/+startingstep/BookStarting.m +++ b/src/+matlode/+startingstep/BookStarting.m @@ -7,14 +7,14 @@ obj = obj@matlode.startingstep.StartingStep(varargin{:}); end - function [h0, f0, fevals] = startingStep(~, f, tspan, y0, order, errFunc, minStep, maxStep) + function [h0, f0, fevals] = startingStep(~, f, tspan, y0, order, ErrNorm, minStep, maxStep) fevals = 2; tdir = sign(tspan(end) - tspan(1)); f0 = f(tspan(1), y0); - d0 = errFunc([y0, y0], 0); - d1 = errFunc([f0, f0], 0); + d0 = ErrNorm.errEstimate([y0, y0], 0); + d1 = ErrNorm.errEstimate([f0, f0], 0); if any([d0, d1] < 1e-5) h0 = 1e-6; @@ -26,7 +26,7 @@ y1 = y0 + h0 * f0 * tdir; f1 = f(tspan(1) + h0 * tdir, y1); - err0 = errFunc([f1 - f0, f1 - f0], 0); + err0 = ErrNorm.errEstimate([f1 - f0, f1 - f0], 0); d2 = err0 / h0; dm = max(d1, d2); diff --git a/src/+matlode/+startingstep/StartingStep.m b/src/+matlode/+startingstep/StartingStep.m index 81779c2..c214d1f 100644 --- a/src/+matlode/+startingstep/StartingStep.m +++ b/src/+matlode/+startingstep/StartingStep.m @@ -13,7 +13,7 @@ end methods (Abstract) - [h0, f0, fevals] = startingStep(obj, f, tspan, y0, order, errFunc, minStep, maxStep); + [h0, f0, fevals] = startingStep(obj, f, tspan, y0, order, ErrNorm, minStep, maxStep); end end diff --git a/src/+matlode/+startingstep/WattsStarting.m b/src/+matlode/+startingstep/WattsStarting.m index 39f117d..67ef0dc 100644 --- a/src/+matlode/+startingstep/WattsStarting.m +++ b/src/+matlode/+startingstep/WattsStarting.m @@ -34,12 +34,12 @@ end - function [h0, f0, fevals] = startingStep(obj, f, tspan, y0, order, errFunc, minStep, maxStep) + function [h0, f0, fevals] = startingStep(obj, f, tspan, y0, order, ErrNorm, minStep, maxStep) fevals = 2; tdir = sign(tspan(end) - tspan(1)); f0 = f(tspan(1), y0); - fnorm = errFunc([f0, f0], 0); + fnorm = ErrNorm.errEstimate([f0, f0], 0); hstep = abs(tspan(1) - tspan(end)); @@ -53,7 +53,7 @@ ydd = (1/(h0)) * (f(tspan(1) + h0 * tdir, y1) - f0) ; - fddnorm = errFunc([ydd, ydd], 0); + fddnorm = ErrNorm.errEstimate([ydd, ydd], 0); if fddnorm > eps h0 = obj.Fac * sqrt(2) * obj.InitTol^(1/(order + 1)) / sqrt(fddnorm); diff --git a/src/+matlode/+stepsizecontroller/+soderpresets/PI.m b/src/+matlode/+stepsizecontroller/+soderpresets/PI.m new file mode 100644 index 0000000..01506a9 --- /dev/null +++ b/src/+matlode/+stepsizecontroller/+soderpresets/PI.m @@ -0,0 +1,15 @@ +classdef PI < matlode.stepsizecontroller.SoderlandController + %PI controller + % From Solving ODE II Chapter IV.2 + + methods + function obj = PI(varargin) + a = [-0.7, 0.4]; + + AQfunc = @(q, A) (A / q); + + obj = obj@matlode.stepsizecontroller.SoderlandController('A', a, 'AQfunc', AQfunc, varargin{:}); + end + end +end + diff --git a/src/+matlode/+stepsizecontroller/Gustafson.m b/src/+matlode/+stepsizecontroller/Gustafson.m index f13d01e..d236ad8 100644 --- a/src/+matlode/+stepsizecontroller/Gustafson.m +++ b/src/+matlode/+stepsizecontroller/Gustafson.m @@ -15,8 +15,8 @@ p = inputParser; p.KeepUnmatched = true; - p.addParameter('Ki', 0.3, matlode.util.scalarValidationFunc); - p.addParameter('Kp', -0.4, matlode.util.scalarValidationFunc); + p.addParameter('Ki', -0.7, matlode.util.scalarValidationFunc); + p.addParameter('Kp', 0.4, matlode.util.scalarValidationFunc); p.addParameter('A', 1, matlode.util.scalarValidationFunc); p.parse(varargin{:}); diff --git a/src/+matlode/Integrator.m b/src/+matlode/Integrator.m index e4ae86e..156b10e 100644 --- a/src/+matlode/Integrator.m +++ b/src/+matlode/Integrator.m @@ -12,7 +12,11 @@ end methods (Abstract, Access = protected) - [t, y, stats] = timeLoop(obj, f, tspan, y0, opts, varargin); + [t, y, stats] = timeLoop(obj, f, tspan, y0, opts); + [k, yN, err] = timeStep(obj, f, t, y, h, k, ErrNorm, prevAccept); + [k, yN, err] = timeStepErr(obj, f, t, y, h, k, ErrNorm, prevAccept); + [k, fevals, fevalIterCounts] = timeLoopBeforeLoop(obj, f0, t0, y0); + [q] = timeLoopInit(obj); end methods @@ -87,7 +91,7 @@ %Assign to controllers p.addParameter('StepSizeController', matlode.stepsizecontroller.Fixed(1000)); - p.addParameter('ErrNorm', matlode.errnorm.InfNorm.errEstimate(sqrt(eps), sqrt(eps))); + p.addParameter('ErrNorm', matlode.errnorm.InfNorm(sqrt(eps), sqrt(eps))); p.addParameter('Jacobian', []); p.addParameter('ChunkSize', 1000); p.addParameter('Dense', []); diff --git a/src/+matlode/OneStepIntegrator.m b/src/+matlode/OneStepIntegrator.m index 65ded7e..2f9bc89 100644 --- a/src/+matlode/OneStepIntegrator.m +++ b/src/+matlode/OneStepIntegrator.m @@ -15,6 +15,205 @@ end + function [t, y, stats] = timeLoop(obj, f, tspan, y0, opts) + + numVars = length(y0); + multiTspan = length(tspan) > 2; + isDense = ~isempty(opts.Dense); + + + %Structure access optimizations + errNorm = opts.ErrNorm; + stepController = opts.StepSizeController; + adap = stepController.Adaptive; + + tlen = 0; + + if multiTspan + y = zeros(numVars, length(tspan)); + t = zeros(1, length(tspan)); + else + y = zeros(numVars, opts.ChunkSize); + t = zeros(1, opts.ChunkSize); + tlen = length(t); + end + + %inital values + t(1,1) = tspan(1); + tCur = tspan(1); + tNext = tCur; + ti = 2; + tl = 2; + if isDense + ti = length(tspan); + else + tl = length(tspan); + end + + tspanlen = length(tspan); + + tdir = sign(tspan(end) - tspan(1)); + hmax = min([abs(opts.MaxStep), abs(tspan(end) - tspan(1))]) * tdir; + hmin = 64 * eps(tspan(1)) * tdir; + + y(:, 1) = y0; + yNext = y0; + + %temporary stats + nSteps = 1; + nFailed = 0; + nSmallSteps = 0; + + [q] = obj.timeLoopInit; + + %First step + %allocates memory for first step + [h0, f0, nFevals] = stepController.startingStep(f, tspan, y0, q, errNorm, hmin, hmax); + + hHist = ones(1, stepController.History) * h0; + hN = hHist(1); + err = ones(1, length(hHist)); + + accept = true; + + [k, fevals, fevalIterCounts] = obj.timeLoopBeforeLoop(f0, tspan(1), y0); + + nFevals = nFevals + fevals; + + %Time Loop + while ti <= tspanlen + %Cycle history + err = circshift(err, 1, 2); + hHist = circshift(hHist, 1, 2); + + yCur = yNext; + tCur = tNext; + hC = hN; + hHist(1) = hN; + + %Subject to change + hmin = 64 * eps(tCur) * tdir; + + %Accept Loop + %Will keep looping until accepted step + while true + + prevAccept = accept; + + if adap + [k, yNext, err(:, 1)] = timeStepErr(obj, f, tCur, yCur, hC, k, errNorm, prevAccept); + else + [k, yNext, err(:, 1)] = timeStep(obj, f, tCur, yCur, hC, k, errNorm, prevAccept); + end + + nFevals = nFevals + fevalIterCounts; + + %Find next Step + [accept, hN, tNext] = stepController.newStepSize(prevAccept, tCur, tspan, hHist, err, q, nSteps, nFailed); + + %Check if step is really small + if abs(hN) < abs(hmin) + if nSmallSteps == 0 + warning('The step the integrator is taking extremely small, results may not be optimal') + end + %accept step since the step cannot get any smaller + nSmallSteps = nSmallSteps + 1; + hN = hmin; + tNext = tCur + hmin; + accept = true; + break; + end + + %check step acception + if accept + break; + end + + nFailed = nFailed + 1; + hC = hN; + hHist(1) = hN; + + end + + %set new step to be in range + if adap + hN = min(abs(hmax), abs(hN)) * tdir; + end + + nSteps = nSteps + 1; + + %Check for all memory allocation + if ~multiTspan + + %Allocate more memory if non-dense + if nSteps > tlen + y = [y, zeros(numVars, opts.ChunkSize)]; + t = [t, zeros(1, opts.ChunkSize)]; + tlen = length(t); + end + + t(:, nSteps) = tNext; + y(:, nSteps) = yNext; + elseif isDense + %Dense output + while tl ~= tspanlen && tNext * tdir > tspan(tl) * tdir + + %incase lucky and land on point, can be cheaper + %than dense output if dense requires fevals + if abs(tspan(tl) - tNext) < abs(hmin) + t(:, tl) = tspan(tl); + y(:, tl) = yNext; + else + %Call Dense output function and record + t(:, tl) = tspan(tl); + [y(:, tl), tempFevals] = opts.Dense.denseOut(f, tCur, tspan(tl), yCur, yNext, k, hC); + nFevals = nFevals + tempFevals; + end + tl = tl + 1; + end + end + + %check if condition holds + %used for end checking and integrate to + if tspan(ti) * tdir <= (tNext + hN) * tdir + + %integrate to/ End point + %check if close enough with hmin + if abs(tspan(ti) - tNext) < abs(hmin) + + if multiTspan + t(:, ti) = tspan(ti); + y(:, ti) = yNext; + end + ti = ti + 1; + elseif stepController.Adaptive + + hN = tspan(ti) - tNext; + end + + end + + + end + + %End of Timeloop work + %Truncate extra memory + if ~multiTspan + t = t(:, 1:nSteps); + y = y(:, 1:nSteps); + else + t(:, end) = tspan(end); + y(:, end) = yNext; + end + + %Create Stats + stats.nSteps = nSteps - 1; + stats.nFailed = nFailed; + stats.nFevals = nFevals; + stats.nSmallSteps = nSmallSteps; + + end + end end From 552b09427f8271fda9f9fd36362986210ee78750 Mon Sep 17 00:00:00 2001 From: reid-g Date: Fri, 21 Oct 2022 12:07:23 -0400 Subject: [PATCH 08/17] Rosenbrock Method and Linear Solvers +Added Rosenbrock methods +Added Linear solver +Added Model Object +Added OTP Integration +Added Fixed Time step loop +Added Operator Types +Added Testing Scripts /Modified Time loop for adaptive only /Modified Format of StepSizeController, Starting Step, and Error Norms -Removed Fixed Step Size controller --- src/+matlode/+denseoutput/DenseOutput.m | 2 +- src/+matlode/+denseoutput/Direct.m | 6 +- src/+matlode/+denseoutput/Hermite.m | 10 +- src/+matlode/+denseoutput/Linear.m | 6 +- src/+matlode/+denseoutput/RKDense.m | 6 +- src/+matlode/+errnorm/InfNorm.m | 11 +- src/+matlode/+errnorm/NormErr.m | 2 +- src/+matlode/+errnorm/StandardNorm.m | 11 +- .../+linearsolver/DecompositionLinearSolver.m | 27 ++- src/+matlode/+linearsolver/LinearSolver.m | 33 ++- .../+linearsolver/MatrixFreeLinearSolver.m | 46 ++-- .../+linearsolver/MatrixLinearSolver.m | 64 +++--- src/+matlode/+rk/+erk/DormandPrince.m | 27 ++- src/+matlode/+rk/+erk/ERK.m | 76 +++--- src/+matlode/+rk/RungeKutta.m | 17 +- src/+matlode/+rosenbrock/LinBackEuler.m | 47 ++++ src/+matlode/+rosenbrock/RODAS3.m | 49 ++++ src/+matlode/+rosenbrock/ROS3P.m | 42 ++++ src/+matlode/+rosenbrock/Rosenbrock.m | 199 ++++++++++++++-- src/+matlode/+startingstep/BookStarting.m | 10 +- src/+matlode/+startingstep/WattsStarting.m | 8 +- .../+stepsizecontroller/+soderpresets/PID.m | 17 ++ src/+matlode/+stepsizecontroller/Fixed.m | 32 --- src/+matlode/+stepsizecontroller/Gustafson.m | 6 +- .../+stepsizecontroller/SoderlandController.m | 6 +- .../+stepsizecontroller/StandardController.m | 6 +- .../+stepsizecontroller/StartingController.m | 47 ---- .../+stepsizecontroller/StepSizeController.m | 31 ++- src/+matlode/+util/OrderChecking.m | 53 +++++ src/+matlode/Integrator.m | 153 +++++++++---- src/+matlode/LinearOperatorType.m | 11 + src/+matlode/Model.m | 216 ++++++++++++++++++ src/+matlode/OneStepIntegrator.m | 215 +++++++++-------- src/+matlode/OperatorType.m | 9 + src/+testingscripts/+OTPtest/TestERK.m | 20 ++ src/+testingscripts/+OTPtest/TestROS.m | 20 ++ .../+modelverification/ModelOTPTest.m | 22 ++ .../+modelverification/ModelTest.m | 26 +++ .../+orderchecking/ERKOrderTest.m | 0 .../+orderchecking/ROSOrderTest.m | 0 src/+testingscripts/+simpletest/Test_ERK.m | 17 ++ src/+testingscripts/+simpletest/Test_ROS.m | 24 ++ .../+testproblems/+ODE/EulerProblem.m | 33 +++ src/+testingscripts/+testproblems/+ODE/ODE.m | 21 ++ 44 files changed, 1262 insertions(+), 422 deletions(-) create mode 100644 src/+matlode/+rosenbrock/LinBackEuler.m create mode 100644 src/+matlode/+rosenbrock/RODAS3.m create mode 100644 src/+matlode/+rosenbrock/ROS3P.m create mode 100644 src/+matlode/+stepsizecontroller/+soderpresets/PID.m delete mode 100644 src/+matlode/+stepsizecontroller/Fixed.m delete mode 100644 src/+matlode/+stepsizecontroller/StartingController.m create mode 100644 src/+matlode/+util/OrderChecking.m create mode 100644 src/+matlode/LinearOperatorType.m create mode 100644 src/+matlode/Model.m create mode 100644 src/+matlode/OperatorType.m create mode 100644 src/+testingscripts/+OTPtest/TestERK.m create mode 100644 src/+testingscripts/+OTPtest/TestROS.m create mode 100644 src/+testingscripts/+modelverification/ModelOTPTest.m create mode 100644 src/+testingscripts/+modelverification/ModelTest.m create mode 100644 src/+testingscripts/+orderchecking/ERKOrderTest.m create mode 100644 src/+testingscripts/+orderchecking/ROSOrderTest.m create mode 100644 src/+testingscripts/+simpletest/Test_ERK.m create mode 100644 src/+testingscripts/+simpletest/Test_ROS.m create mode 100644 src/+testingscripts/+testproblems/+ODE/EulerProblem.m create mode 100644 src/+testingscripts/+testproblems/+ODE/ODE.m diff --git a/src/+matlode/+denseoutput/DenseOutput.m b/src/+matlode/+denseoutput/DenseOutput.m index f904736..0ee0cd5 100644 --- a/src/+matlode/+denseoutput/DenseOutput.m +++ b/src/+matlode/+denseoutput/DenseOutput.m @@ -1,6 +1,6 @@ classdef (Abstract) DenseOutput < handle methods (Abstract) - [denseY, fEvals] = denseOut(obj, f, t, tNeeded, yC, yN, k, h) + [denseY, fEvals] = denseOut(obj, f, t, tneed, y0, y1, stages, dt) end end diff --git a/src/+matlode/+denseoutput/Direct.m b/src/+matlode/+denseoutput/Direct.m index c879b2e..74caf8d 100644 --- a/src/+matlode/+denseoutput/Direct.m +++ b/src/+matlode/+denseoutput/Direct.m @@ -12,11 +12,11 @@ obj.kFuncs = kfuncs; end - function [denseY, fEvals] = denseOut(obj, ~, t, tNeeded, yC, ~, k, h) + function [denseY, fEvals] = denseOut(obj, ~, t, tneed, y0, ~, stages, dt) - theta = (tNeeded - t) / h; + theta = (tneed - t) / h; - denseY = yC + (k * h) * obj.kFuncs(theta); + denseY = y0 + (stages * dt) * obj.kFuncs(theta); fEvals = 0; end end diff --git a/src/+matlode/+denseoutput/Hermite.m b/src/+matlode/+denseoutput/Hermite.m index 90a2281..2244f39 100644 --- a/src/+matlode/+denseoutput/Hermite.m +++ b/src/+matlode/+denseoutput/Hermite.m @@ -5,12 +5,12 @@ function obj = Hermite() end - function [denseY, fEvals] = denseOut(~, f, t, tNeeded, yC, yN, ~, h) + function [denseY, fEvals] = denseOut(~, f, t, tneed, y0, y1, ~, dt) - theta = (tNeeded - t) / h; - f0 = f(t, yC); - f1 = f(t + h, yN); - denseY = (1 - theta) * yC + theta * yN + theta*(theta - 1) * ((1-2*theta) * (yN - yC) + (theta - 1) * (h * f0 + theta * h * f1)); + theta = (tneed - t) / h; + f0 = f.F(t, y0); + f1 = f.F(t + h, y1); + denseY = (1 - theta) * y0 + theta * y1 + theta*(theta - 1) * ((1-2*theta) * (y1 - y0) + (theta - 1) * (dt * f0 + theta * dt * f1)); fEvals = 2; end end diff --git a/src/+matlode/+denseoutput/Linear.m b/src/+matlode/+denseoutput/Linear.m index acfed0f..a4b78b0 100644 --- a/src/+matlode/+denseoutput/Linear.m +++ b/src/+matlode/+denseoutput/Linear.m @@ -9,10 +9,10 @@ obj.b = b; end - function [denseY, fEvals] = denseOut(obj, ~, t, tNeeded, yC, ~, k, h) + function [denseY, fEvals] = denseOut(obj, ~, t, tneed, y0, ~, stages, dt) - theta = (tNeeded - t) / h; - denseY = yC + (k * h) * (obj.b' * theta) ; + theta = (tneed - t) / dt; + denseY = y0 + (stages * dt) * (obj.b' * theta) ; fEvals = 0; end end diff --git a/src/+matlode/+denseoutput/RKDense.m b/src/+matlode/+denseoutput/RKDense.m index d28301a..5cdfda9 100644 --- a/src/+matlode/+denseoutput/RKDense.m +++ b/src/+matlode/+denseoutput/RKDense.m @@ -10,11 +10,11 @@ obj.bTheta = @(theta) b * theta.^(1:size(b,2))'; end - function [denseY, fEvals] = denseOut(obj, ~, t, tNeeded, yC, ~, k, h) + function [denseY, fEvals] = denseOut(obj, ~, t, tneed, y0, ~, stages, dt) - theta = (tNeeded - t) / h; + theta = (tneed - t) / dt; - denseY = yC + (k * h) * obj.bTheta(theta); + denseY = y0 + (stages * dt) * obj.bTheta(theta); fEvals = 0; end end diff --git a/src/+matlode/+errnorm/InfNorm.m b/src/+matlode/+errnorm/InfNorm.m index 6bc68c9..ea57565 100644 --- a/src/+matlode/+errnorm/InfNorm.m +++ b/src/+matlode/+errnorm/InfNorm.m @@ -4,14 +4,9 @@ obj = obj@matlode.errnorm.NormErr(abstol, reltol); end - function err = errEstimate(obj, y, yE) - persistent abstol reltol - if isempty(abstol) - abstol = obj.AbsTol; - reltol = obj.RelTol; - end - sc = abstol + max(abs(y(:, 1)), abs(y(:, 2))) * reltol; - err = norm(yE ./ sc, inf); + function err = errEstimate(obj, y0, y1, yerror) + sc = obj.AbsTol + max(abs(y0), abs(y1)) * obj.RelTol; + err = norm(yerror ./ sc, inf); end end end diff --git a/src/+matlode/+errnorm/NormErr.m b/src/+matlode/+errnorm/NormErr.m index 647b607..3266aa8 100644 --- a/src/+matlode/+errnorm/NormErr.m +++ b/src/+matlode/+errnorm/NormErr.m @@ -5,7 +5,7 @@ end methods (Abstract) - errFunc = errEstimate(y, yE) + errFunc = errEstimate(obj, y0, y1, yerror) end methods diff --git a/src/+matlode/+errnorm/StandardNorm.m b/src/+matlode/+errnorm/StandardNorm.m index 064b6b7..b96415e 100644 --- a/src/+matlode/+errnorm/StandardNorm.m +++ b/src/+matlode/+errnorm/StandardNorm.m @@ -4,14 +4,9 @@ obj = obj@matlode.errnorm.NormErr(abstol, reltol); end - function err = errEstimate(obj, y, yE) - persistent abstol reltol - if isempty(abstol) - abstol = obj.AbsTol; - reltol = obj.RelTol; - end - sc = abstol + max(abs(y(:, 1)), abs(y(:, 2))) * reltol; - err = sqrt(1/size(y,1)) * norm(yE ./ sc); + function err = errEstimate(obj, y0, y1, yerror) + sc = obj.AbsTol + max(abs(y0), abs(y1)) * obj.RelTol; + err = sqrt(1/size(y0,1)) * norm(yerror ./ sc); end end end diff --git a/src/+matlode/+linearsolver/DecompositionLinearSolver.m b/src/+matlode/+linearsolver/DecompositionLinearSolver.m index f0d58bb..3c7bf35 100644 --- a/src/+matlode/+linearsolver/DecompositionLinearSolver.m +++ b/src/+matlode/+linearsolver/DecompositionLinearSolver.m @@ -1,21 +1,28 @@ classdef DecompositionLinearSolver < matlode.linearsolver.MatrixLinearSolver properties (SetAccess = immutable, GetAccess = private) - Args + DecompositionType + DecompositionArgs end methods - function obj = DecompositionLinearSolver(varargin) - obj = obj@matlode.linearsolver.MatrixLinearSolver(); - obj.Args = varargin; + function obj = DecompositionLinearSolver(type, solver, decompArgs) + arguments + type(1,1) string = 'lu'; + solver(1,1) function_handle = @mldivide; + decompArgs(1,:) cell = {}; + end + obj = obj@matlode.linearsolver.MatrixLinearSolver(solver, {}); + obj.DecompositionType = type; + obj.DecompositionArgs = decompArgs; end - function system = preprocess(obj, updateState, updateTimestep, system, t, y, f, m1, mass, m2, jac) - system = preprocess@matlode.linearsolver.MatrixLinearSolver( ... - obj, updateState, updateTimestep, system, t, y, f, m1, mass, m2, jac); + function [stats] = preprocess(obj, t, y, reeval, mass_scale, jac_scale, stats) - if updateState || updateTimestep - system.matrix = decomposition(system.matrix, obj.Args{:}); - end + [stats] = preprocess@matlode.linearsolver.MatrixLinearSolver(obj, t, y, reeval, mass_scale, jac_scale, stats); + + obj.system = decomposition(obj.system, obj.DecompositionType, obj.DecompositionArgs); + + stats.nDecompositions = stats.nDecompositions + 1; end end end diff --git a/src/+matlode/+linearsolver/LinearSolver.m b/src/+matlode/+linearsolver/LinearSolver.m index e5aee67..ca66b2e 100644 --- a/src/+matlode/+linearsolver/LinearSolver.m +++ b/src/+matlode/+linearsolver/LinearSolver.m @@ -1,8 +1,35 @@ -classdef (Abstract) LinearSolver +classdef (Abstract) LinearSolver < handle + + properties (SetAccess = immutable, GetAccess = protected) + Solver + SolverArgs + end + + properties (SetAccess = protected) + mass + jac + system + end + + methods + function obj = LinearSolver(solver, solverArgs) + arguments + solver(1,1) function_handle; + solverArgs(1,:) cell = {}; + end + + obj.Solver = solver; + obj.SolverArgs = solverArgs; + end + end + methods (Abstract) - system = preprocess(obj, updateState, updateTimestep, oldSystem, t, y, f, m1, mass, m2, jac); + % mass_scale*mass + jac_scale*jac + [stats] = preprocess(obj, f, t, y, reeval, mass_scale, jac_scale, stats); + + [sol, stats] = solve(obj, x, stats); + - sol = solve(obj, x, system, t, y, f, m1, mass, m2, jac); end end diff --git a/src/+matlode/+linearsolver/MatrixFreeLinearSolver.m b/src/+matlode/+linearsolver/MatrixFreeLinearSolver.m index 311f535..c330a4a 100644 --- a/src/+matlode/+linearsolver/MatrixFreeLinearSolver.m +++ b/src/+matlode/+linearsolver/MatrixFreeLinearSolver.m @@ -1,33 +1,31 @@ classdef MatrixFreeLinearSolver < matlode.linearsolver.LinearSolver - properties (SetAccess = immutable, GetAccess = private) - Solver - end - + methods - function obj = MatrixFreeLinearSolver(solver) - if nargin < 1 - solver = @gmres; - end - - obj.Solver = solver; + function obj = MatrixFreeLinearSolver(solver, solverArgs) + arguments + solver(1,1) function_handle = @gmres; + solverArgs(1,:) cell = {}; + end + + obj = obj@matlode.linearsolver.LinearSolver(solver, solverArgs{:}); end - function system = preprocess(~, ~, ~, system, ~, ~, ~, ~, ~, ~, ~) + function [stats] = preprocess(obj, t, y, reeval, mass_scale, jac_scale, stats) + if reeval + obj.mass = @(v) f.MVectorProduct(t, y, v); + stats.nMassEvals = stats.nMassEvals + 1; + + obj.jac = @(v) f.JVectorProduct(t, y, v); + stats.nJacobianEvals = stats.nJacobianEvals + 1; + end + + obj.system = @(v) mass_scale * obj.mass(v) + jac_scale * obj.jac(v); end - function sol = solve(obj, x, ~, t, y, ~, m1, mass, m2, jac) - if length(m1) == 1 - if isempty(jac) - jvp = @(v) m1 * mass(t, y, v); - elseif isempty(mass) - jvp = @(v) m1 * v - m2 * jac(t, y, v); - else - jvp = @(v) m1 * mass(t, y, v) - m2 * jac(t, y, v); - end - else - error('Matrix-free block systems are not supported yet'); - end - sol = obj.Solver(jvp, x); + function [sol, stats] = solve(obj, x, stats) + sol = obj.Solver(obj.system, x, obj.SolverArgs{:}); + + stats.nLinearSolve = stats.nLinearSolve + 1; end end end diff --git a/src/+matlode/+linearsolver/MatrixLinearSolver.m b/src/+matlode/+linearsolver/MatrixLinearSolver.m index f036bc8..c8d6847 100644 --- a/src/+matlode/+linearsolver/MatrixLinearSolver.m +++ b/src/+matlode/+linearsolver/MatrixLinearSolver.m @@ -1,48 +1,40 @@ classdef MatrixLinearSolver < matlode.linearsolver.LinearSolver - properties (SetAccess = immutable, GetAccess = private) - Solver - end + methods - function obj = MatrixLinearSolver(solver) - if nargin < 1 - solver = @mldivide; - end + function obj = MatrixLinearSolver(solver, solverArgs) + arguments + solver(1,1) function_handle = @mldivide; + solverArgs(1,:) cell = {}; + end - obj.Solver = solver; + obj = obj@matlode.linearsolver.LinearSolver(solver, solverArgs{:}); end - function system = preprocess(~, updateState, updateTimestep, system, t, y, ~, m1, mass, m2, jac) - init = isempty(system); - - if updateState - if ~isnumeric(jac) - system.jac = jac(t, y); - elseif init - system.jac = jac; - end - - if ~isnumeric(mass) - system.mass = mass(t, y); - elseif init - if isempty(mass) - system.mass = eye(size(system.jac), 'like', system.jac); - else - system.mass = mass; - end - end - end - - if updateState || updateTimestep - system.matrix = kron(m1, system.mass); - if m2 ~= 0 - system.matrix = system.matrix - kron(m2, jac); - end + function [stats] = preprocess(obj, f, t, y, reeval, mass_scale, jac_scale, stats) + if reeval + if f.MLinearOperatorType ~= matlode.LinearOperatorType.Identity && f.MLinearOperatorType ~= matlode.LinearOperatorType.Constant + obj.mass = f.Mass(t, y); + stats.nMassEvals = stats.nMassEvals + 1; + elseif isempty(obj.mass) + obj.mass = f.Mass; + end + + if f.JLinearOperatorType ~= matlode.LinearOperatorType.Identity && f.JLinearOperatorType ~= matlode.LinearOperatorType.Constant + obj.jac = f.Jacobian(t, y); + stats.nJacobianEvals = stats.nJacobianEvals + 1; + elseif isempty(obj.jac) + obj.jac = f.Jacobian; + end end + + obj.system = mass_scale * obj.mass + jac_scale * obj.jac; end - function sol = solve(obj, x, system, ~, ~, ~, ~, ~, ~, ~) - sol = obj.Solver(system.matrix, x); + function [sol, stats] = solve(obj, x, stats) + sol = obj.Solver(obj.system, x, obj.SolverArgs{:}); + + stats.nLinearSolves = stats.nLinearSolves + 1; end end end diff --git a/src/+matlode/+rk/+erk/DormandPrince.m b/src/+matlode/+rk/+erk/DormandPrince.m index 7a313a4..7f5280f 100644 --- a/src/+matlode/+rk/+erk/DormandPrince.m +++ b/src/+matlode/+rk/+erk/DormandPrince.m @@ -4,25 +4,32 @@ end methods - function obj = DormandPrince(datatype) + + function obj = DormandPrince(varargin) %TODO %include reference for method - if nargin == 0 - datatype = 'double'; - end + p = inputParser; + p.KeepUnmatched = true; + + p.addParameter('datatype', 'double'); + + p.parse(varargin{:}); + opts = p.Results; + + datatype = opts.datatype; caster = @(x) matlode.util.CoefficentTransformers.transform(x,datatype); a = caster(join(['[[0, 0, 0, 0, 0, 0, 0];',... - '[1/5, 0, 0, 0, 0, 0, 0];',... - '[3/40, 9/40, 0, 0, 0, 0, 0];',... - '[44/45, -56/15, 32/9, 0, 0, 0, 0];',... - '[19372/6561, -25360/2187, 64448/6561, -212/729, 0, 0, 0];',... - '[9017/3168, -355/33, 46732/5247, 49/176, -5103/18656, 0, 0];',... - '[35/384, 0, 500/1113, 125/192, -2187/6784, 11/84, 0]]'])); + '[1/5, 0, 0, 0, 0, 0, 0];',... + '[3/40, 9/40, 0, 0, 0, 0, 0];',... + '[44/45, -56/15, 32/9, 0, 0, 0, 0];',... + '[19372/6561, -25360/2187, 64448/6561, -212/729, 0, 0, 0];',... + '[9017/3168, -355/33, 46732/5247, 49/176, -5103/18656, 0, 0];',... + '[35/384, 0, 500/1113, 125/192, -2187/6784, 11/84, 0]]'])); b = caster('[35/384, 0, 500/1113, 125/192, -2187/6784, 11/84, 0]'); diff --git a/src/+matlode/+rk/+erk/ERK.m b/src/+matlode/+rk/+erk/ERK.m index d40600e..679e46d 100644 --- a/src/+matlode/+rk/+erk/ERK.m +++ b/src/+matlode/+rk/+erk/ERK.m @@ -2,9 +2,11 @@ %Explicit Runge Kutta class properties (Constant) - PartitionMethod = false + PartitionMethod = false; + PartitionNum = 1; + MultirateMethod = false; end - + methods function obj = ERK(a, b, bHat, c, e, order, embeddedOrder) obj = obj@matlode.rk.RungeKutta(a, b, bHat, c, e, order, embeddedOrder); @@ -19,62 +21,56 @@ opts = matlodeSets@matlode.rk.RungeKutta(obj, p, varargin{:}); end - function [k, yN, err] = timeStep(obj, f, t, y, h, k, ~, prevAccept) - persistent fsal s a b c + function [ynew, stages, stats] = timeStep(obj, f, t, y, dt, stages, prevAccept, stats) + persistent fsal s a b c fevalIterCounts if isempty(fsal) fsal = obj.FsalStart; - s = obj.Stage; + s = obj.StageNum; a = obj.A; b = obj.B; c = obj.C; + fevalIterCounts = double(obj.StageNum - obj.FsalStart + 1); end - if fsal && prevAccept - k(:, 1) = k(:, end); - end + if fsal && prevAccept + stages(:, 1) = stages(:, end); + end for i = fsal:s - z = y; + ynew = y; for j = 1:i-1 - z = z + k(:, j) .* (h .* a(i, j)); + if a(i,j) ~= 0 + ynew = ynew + stages(:, j) .* (dt .* a(i, j)); + end end - thc = t + h .* c(i); - k(:, i) = f(thc, z); + thc = t + dt .* c(i); + stages(:, i) = f.F(thc, ynew); end - yN = y + k * (h .* b'); - err = 1; + ynew = y; + for i = 1:s + if b(i) ~= 0 + ynew = ynew + stages(:, i) .* (dt .* b(i)); + end + end + + stats.nFevals = stats.nFevals + fevalIterCounts; end - function [k, yN, err] = timeStepErr(obj, f, t, y, h, k, ErrNorm, prevAccept) - persistent fsal s a b c e - if isempty(fsal) - fsal = obj.FsalStart; - s = obj.Stage; - a = obj.A; - b = obj.B; - c = obj.C; + function [err, stats] = timeStepErr(obj, ~, ~, y, ynew, dt, stages, ErrNorm, stats) + persistent e s + if isempty(e) e = obj.E; + s = obj.StageNum; end - if fsal && prevAccept - k(:, 1) = k(:, end); - end - - for i = fsal:s - z = y; - for j = 1:i-1 - z = z + k(:, j) .* (h .* a(i, j)); - end - thc = t + h .* c(i); - k(:, i) = f(thc, z); - end - kh = k .* h; - yN = y + kh * b'; - yE = kh * e'; - err = ErrNorm.errEstimate([yN, y], yE); + yerror = 0; + for i = 1:s + if e(i) ~= 0 + yerror = yerror + stages(:, i) .* (dt .* e(i)); + end + end + err = ErrNorm.errEstimate(y, ynew, yerror); end - - end end diff --git a/src/+matlode/+rk/RungeKutta.m b/src/+matlode/+rk/RungeKutta.m index 6bd53cd..678393b 100644 --- a/src/+matlode/+rk/RungeKutta.m +++ b/src/+matlode/+rk/RungeKutta.m @@ -7,7 +7,7 @@ C BHat E - Stage + StageNum EmbeddedOrder Order FSAL @@ -26,7 +26,7 @@ obj.E = e; obj.EmbeddedOrder = embeddedOrder; obj.Order = order; - obj.Stage = size(b, 2); + obj.StageNum = size(b, 2); obj.FSAL = all(a(end, :) == b) && all(a(1, :) == 0); obj.FsalStart = uint32(obj.FSAL) + 1; end @@ -62,19 +62,16 @@ end - function [k, fevals, fevalIterCounts] = timeLoopBeforeLoop(obj, f0, t0, y0) + function [stages, stats] = timeLoopBeforeLoop(obj, f, f0, t0, y0, stats) - k = zeros(length(y0), obj.Stage); - - fevalIterCounts = double(obj.Stage - obj.FsalStart + 1); + stages = zeros(length(y0), obj.StageNum); if obj.FSAL if isempty(f0) - k(:, end) = f(t0, y0); - fevals = 1; + stages(:, end) = f.F(t0, y0); + stats.FEvals = stats.FEvals + 1; else - k(:, end) = f0; - fevals = 0; + stages(:, end) = f0; end end end diff --git a/src/+matlode/+rosenbrock/LinBackEuler.m b/src/+matlode/+rosenbrock/LinBackEuler.m new file mode 100644 index 0000000..c10c850 --- /dev/null +++ b/src/+matlode/+rosenbrock/LinBackEuler.m @@ -0,0 +1,47 @@ +classdef LinBackEuler < matlode.rosenbrock.Rosenbrock + %LINBACKEULER Summary of this class goes here + % Detailed explanation goes here + + properties + Dense + end + + methods + function obj = LinBackEuler(varargin) + + p = inputParser; + p.KeepUnmatched = true; + + p.addParameter('datatype', 'double'); + + p.parse(varargin{:}); + opts = p.Results; + + datatype = opts.datatype; + + caster = @(x) matlode.util.CoefficentTransformers.transform(x,datatype); + + alpha = caster('[0]'); + gamma = caster('[1]'); + b = caster('[1]'); + be = []; + + [gammadia, gammasum, alphasum, a, c, m, me] = RosCoefMethTrans(gamma, alpha, b, be); + + order = 1; + + embbededOrder = 0; + + + obj = obj@matlode.rosenbrock.Rosenbrock(gammadia, gammasum, alphasum, a, c, m, me, order, embbededOrder); + + obj.Dense = matlode.denseoutput.Linear(b); + + end + + function sol = integrate(obj, f, tspan, y0, varargin) + sol = integrate@matlode.rosenbrock.Rosenbrock(obj, f, tspan, y0, 'StepSizeController', matlode.stepsizecontroller.Fixed(1000), 'Dense', obj.DenseOut, varargin{:}); + end + end +end + diff --git a/src/+matlode/+rosenbrock/RODAS3.m b/src/+matlode/+rosenbrock/RODAS3.m new file mode 100644 index 0000000..6ead8c4 --- /dev/null +++ b/src/+matlode/+rosenbrock/RODAS3.m @@ -0,0 +1,49 @@ +classdef RODAS3 < matlode.rosenbrock.Rosenbrock + %RODAS3 Summary of this class goes here + % Detailed explanation goes here + + properties (SetAccess = immutable) + Dense + end + + methods + function obj = RODAS3(datatype) + if nargin == 0 + datatype = 'double'; + end + + + caster = @(x) matlode.util.CoefficentTransformers.transform(x,datatype); + + gammadia = caster('[1/2, 1/2, 1/2, 1/2]'); + alphasum = caster('[0, 0, 1, 1]'); + gammasum = caster('[1/2, 3/2, 0, 0]'); + + a = caster(join(['[[0, 0, 0, 0];',... + '[0, 0, 0, 0];',... + '[2, 0, 0, 0];',... + '[2, 0, 1, 0]]'])); + + c = caster(join(['[[0, 0, 0, 0];',... + '[4, 0, 0, 0];',... + '[1, -1, 0, 0];',... + '[1, -1, -8/3, 0]]'])); + m = caster('[2, 0, 1, 1]'); + me = caster('[0, 0, 0, 1]'); + + order = 3; + + embbededOrder = 2; + + obj = obj@matlode.rosenbrock.Rosenbrock(gammadia, gammasum, alphasum, a, c, m, me, order, embbededOrder); + + + obj.Dense = matlode.denseoutput.Linear(m); + end + + function sol = integrate(obj, f, tspan, y0, varargin) + sol = integrate@matlode.rosenbrock.Rosenbrock(obj, f, tspan, y0, 'StepSizeController', matlode.stepsizecontroller.StandardController, 'Dense', obj.Dense, varargin{:}); + end + end +end + diff --git a/src/+matlode/+rosenbrock/ROS3P.m b/src/+matlode/+rosenbrock/ROS3P.m new file mode 100644 index 0000000..d7178fd --- /dev/null +++ b/src/+matlode/+rosenbrock/ROS3P.m @@ -0,0 +1,42 @@ +classdef ROS3P < matlode.rosenbrock.Rosenbrock + properties + Dense + end + + methods + function obj = ROS3P(datatype) + if nargin == 0 + datatype = 'double'; + end + + caster = @(x) matlode.util.CoefficentTransformers.transform(x,datatype); + + gammadia = caster('[7.886751345948129e−01, 7.886751345948129e−01, 7.886751345948129e−01]'); + alphasum = caster('[0, 1, 0]'); + gammasum = caster('[7.886751345948129e−01, −2.113248654051871e−01, −1.077350269189626]'); + + a = caster(join(['[[0, 0, 0];',... + '[1.267949192431123, 0, 0];',... + '[1.267949192431123, 0, 0]'])); + + c = caster(join(['[[0, 0, 0];',... + '[−1.607695154586736, 0, 0];',... + '[−3.464101615137755, −1.732050807568877, 0]'])); + m = caster('[2, 5.773502691896258e−01, 4.226497308103742e−01]'); + me = caster('[2.113248654051871, 1, 4.226497308103742e−01]'); + + order = 3; + + embbededOrder = 2; + + obj = obj@matlode.rosenbrock.Rosenbrock(gammadia, gammasum, alphasum, a, c, m, me, order, embbededOrder); + + obj.Dense = matlode.denseoutput.Linear(m); + end + + function sol = integrate(obj, f, tspan, y0, varargin) + sol = integrate@matlode.rosenbrock.Rosenbrock(obj, f, tspan, y0, 'StepSizeController', matlode.stepsizecontroller.StandardController, 'Dense', obj.DenseOut, varargin{:}); + end + end +end + diff --git a/src/+matlode/+rosenbrock/Rosenbrock.m b/src/+matlode/+rosenbrock/Rosenbrock.m index ba8f1e6..8eb40a6 100644 --- a/src/+matlode/+rosenbrock/Rosenbrock.m +++ b/src/+matlode/+rosenbrock/Rosenbrock.m @@ -2,54 +2,209 @@ %ROSENBROCK Summary of this class goes here % Detailed explanation goes here - properties (SetAccess = immutable) + properties (Constant) + PartitionMethod = false; + PartitionNum = 1; + MultirateMethod = false; + end + + properties (SetAccess = private) + GammaDia + GammaSum A - B + AlphaSum C - G - BHat + M E - Stage - EmbeddedOrder Order + StageNum + EmbeddedOrder FSAL + end + + properties (SetAccess = immutable, GetAccess = private) FsalStart end + properties + LinearSolver + end + + methods (Static) + %% Coefficent Transformation Methods + %Coefficent transformer + function [gammadia, gammasum, alphasum, a, c, m, me] = RosCoefMethTrans(gamma, alpha, b, be) + gammadia = diag(gamma); + gammasum = sum(gamma, 2); + alphasum = sum(alpha, 2); + + m = mrdivide(gamma, b); + %TODO: Fix + if isempty(be) + me = []; + else + me = mrdivide(be, gamma); + end + + eta = gamma * diag(1 ./ gammadia) + eye(length(gammadia)); + + c = gamma; + a = alpha; + + for i = 1:length(gamma) + c(:,i) = gamma \ eta(:,i); + end + + for i = 1:length(gamma) + a(i,:) = mrdivide(alpha(i,:), gamma); + end + + end + + %Coefficent transformer + function [gamma, alpha, b, be] = RosCoefBaseTrans(gammadia, a, c, m, me) + gamma = c; + + eta = eye(length(gammadia)); + xi = diag(gammadia) - c; + for i = 1:length(c) + gamma(:,i) = xi \ eta(:,i); + end + + alpha = a * gamma; + + b = m * gamma; + if isempty(me) + be = []; + else + be = me * gamma; + end + + + end + end + methods - function obj = Rosenbrock(a, b, bHat, c, e, g, order, embeddedOrder) - obj.A = a; - obj.B = b; - obj.BHat = bHat; - obj.C = c; - obj.E = e; - obj.G = g; + + function obj = Rosenbrock(gammadia, gammasum, alphasum, a, c, m, me, order, embeddedOrder) + + obj = obj@matlode.OneStepIntegrator(~isempty(me), class(gammadia)); + + obj.GammaDia = gammadia; + obj.GammaSum = gammasum; + obj.AlphaSum = alphasum; + + obj.A = a; + obj.C = c; + obj.M = m; + obj.E = me; + obj.EmbeddedOrder = embeddedOrder; obj.Order = order; - obj.Stage = size(b, 2); - obj.FSAL = all(a(end, :) == b) && all(a(1, :) == 0); + obj.StageNum = size(obj.M, 2); + obj.FSAL = all(obj.A(end, :) == obj.M) && all(obj.A(1, :) == 0) && all(obj.C(1, :) == 0) && obj.AlphaSum(1) == 0; obj.FsalStart = uint32(obj.FSAL) + 1; - end - + end - end + end methods (Access = protected) - function [k, yN, err] = timeStep(obj, f, t, y, h, k, ErrNorm, prevAccept) + function opts = matlodeSets(obj, p, varargin) + + %Rosenbrock sepcific options + p.addParameter('LinearSolver', matlode.linearsolver.MatrixLinearSolver()); + + opts = matlodeSets@matlode.OneStepIntegrator(obj, p, varargin{:}); + + if isempty(opts.LinearSolver) + error('Please provide appropiate parameters and a linear solver') + end + obj.LinearSolver = opts.LinearSolver; end - function [k, yN, err] = timeStepErr(obj, f, t, y, h, k, ErrNorm, prevAccept) + %% Time step + function [ynew, stages, stats] = timeStep(obj, f, t, y, dt, stages, prevAccept, stats) + + if obj.FSAL && prevAccept + stages(:, 1) = stages(:, end); + end + + %Check if time derivative is avalible + if f.PDTOperatorType ~= matlode.OperatorType.Zero + dfdt_0 = f.PartialDerivativeTime(t, y); + stats.nPDTEval = stats.nPDTEval + 1; + end + for i = obj.FsalStart:obj.StageNum + ynew = y; + for j = 1:(i-1) + if obj.A(i,j) ~= 0 + ynew = ynew + obj.A(i,j) * stages(:,j); + end + end + + ynew = f.F(t + obj.AlphaSum(i) * dt, ynew); + + for j = 1:(i-1) + if obj.C(i,j) ~= 0 + ynew = ynew + (obj.C(i,j) / dt) * stages(:,j); + end + end + + + if f.PDTOperatorType ~= matlode.OperatorType.Zero && obj.GammaSum(i) ~= 0 + ynew = ynew + obj.GammaSum(i) * dt * dfdt_0; + end + + % Update Linear Solver + if i == obj.FsalStart || obj.GammaDia(i) ~= obj.GammaDia(i-1) + stats = obj.LinearSolver.preprocess(f, t, y, i==obj.FsalStart, 1/(dt * obj.GammaDia(i)), -1, stats); + end + + [stages(:, i), stats] = obj.LinearSolver.solve(ynew, stats); + end + + ynew = y; + for i = 1:obj.StageNum + if obj.M(i) ~= 0 + ynew = ynew + obj.M(i) * stages(:,i); + end + end + + stats.nFevals = stats.nFevals + obj.StageNum - uint16(prevAccept); end - function [k, fevals, fevalIterCounts] = timeLoopBeforeLoop(obj, f0, t0, y0) + %% Error Estimate + function [err, stats] = timeStepErr(obj, ~, ~, y, ynew, ~, stages, ErrNorm, stats) + + y_error = 0; + for i = 1:obj.StageNum + if obj.E(i) ~= 0 + y_error = y_error + obj.E(i) * stages(:,i); + end + end + err = ErrNorm.errEstimate(y, ynew, y_error); end - function [q] = timeLoopInit(obj) + function [stages, stats] = timeLoopBeforeLoop(obj, f, f0, t0, y0, stats) + stages = zeros(length(y0), obj.StageNum); + + if obj.FSAL + if isempty(f0) + stages(:, end) = f.F(t0, y0); + stats.FEvals = stats.FEvals + 1; + else + stages(:, end) = f0; + end + end end + + function [q] = timeLoopInit(obj) + q = min(obj.Order, obj.EmbeddedOrder); + end end end diff --git a/src/+matlode/+startingstep/BookStarting.m b/src/+matlode/+startingstep/BookStarting.m index 181116c..054b885 100644 --- a/src/+matlode/+startingstep/BookStarting.m +++ b/src/+matlode/+startingstep/BookStarting.m @@ -11,10 +11,10 @@ fevals = 2; tdir = sign(tspan(end) - tspan(1)); - f0 = f(tspan(1), y0); + f0 = f.F(tspan(1), y0); - d0 = ErrNorm.errEstimate([y0, y0], 0); - d1 = ErrNorm.errEstimate([f0, f0], 0); + d0 = ErrNorm.errEstimate(y0, y0, 0); + d1 = ErrNorm.errEstimate(f0, f0, 0); if any([d0, d1] < 1e-5) h0 = 1e-6; @@ -25,8 +25,8 @@ h0 = max([min([h0, maxStep * tdir]), minStep * tdir]); y1 = y0 + h0 * f0 * tdir; - f1 = f(tspan(1) + h0 * tdir, y1); - err0 = ErrNorm.errEstimate([f1 - f0, f1 - f0], 0); + f1 = f.F(tspan(1) + h0 * tdir, y1); + err0 = ErrNorm.errEstimate(f1 - f0, f1 - f0, 0); d2 = err0 / h0; dm = max(d1, d2); diff --git a/src/+matlode/+startingstep/WattsStarting.m b/src/+matlode/+startingstep/WattsStarting.m index 67ef0dc..0192028 100644 --- a/src/+matlode/+startingstep/WattsStarting.m +++ b/src/+matlode/+startingstep/WattsStarting.m @@ -38,8 +38,8 @@ fevals = 2; tdir = sign(tspan(end) - tspan(1)); - f0 = f(tspan(1), y0); - fnorm = ErrNorm.errEstimate([f0, f0], 0); + f0 = f.F(tspan(1), y0); + fnorm = ErrNorm.errEstimate(f0, f0, 0); hstep = abs(tspan(1) - tspan(end)); @@ -51,9 +51,9 @@ y1 = y0 + h0 * tdir * f0; - ydd = (1/(h0)) * (f(tspan(1) + h0 * tdir, y1) - f0) ; + ydd = (1/(h0)) * (f.F(tspan(1) + h0 * tdir, y1) - f0) ; - fddnorm = ErrNorm.errEstimate([ydd, ydd], 0); + fddnorm = ErrNorm.errEstimate(ydd, ydd, 0); if fddnorm > eps h0 = obj.Fac * sqrt(2) * obj.InitTol^(1/(order + 1)) / sqrt(fddnorm); diff --git a/src/+matlode/+stepsizecontroller/+soderpresets/PID.m b/src/+matlode/+stepsizecontroller/+soderpresets/PID.m new file mode 100644 index 0000000..11310e8 --- /dev/null +++ b/src/+matlode/+stepsizecontroller/+soderpresets/PID.m @@ -0,0 +1,17 @@ +classdef PID < matlode.stepsizecontroller.SoderlandController + % Gustaffson "Control-Theoretic Techniques for Stepsize Selection in + % Implicit Runge-Kutta Methods" + % Solving ODEs II Chapter IV pg 124 + methods + function obj = PID(varargin) + + a = [-2, 1]; + b = [1]; + + AQfunc = @(q, A) (A / q); + + obj = obj@matlode.stepsizecontroller.SoderlandController('A', a, 'B', b, 'AQfunc', AQfunc, varargin{:}); + end + end +end + diff --git a/src/+matlode/+stepsizecontroller/Fixed.m b/src/+matlode/+stepsizecontroller/Fixed.m deleted file mode 100644 index df8dd8c..0000000 --- a/src/+matlode/+stepsizecontroller/Fixed.m +++ /dev/null @@ -1,32 +0,0 @@ -classdef Fixed < matlode.stepsizecontroller.StepSizeController - %Fixed stepsize controller - - properties (Constant) - Adaptive = false; - end - - properties (SetAccess = immutable, GetAccess = private) - Steps - end - - methods - - function obj = Fixed(steps) - obj = obj@matlode.stepsizecontroller.StepSizeController(1); - obj.Steps = steps; - end - - function [h0, f0, fevals] = startingStep(obj, ~, tspan, ~, ~, ~, ~, ~) - h0 = (tspan(2) - tspan(1)) / obj.Steps; - f0 = []; - fevals = 0; - end - - function [accept, h, tNew] = newStepSize(~, ~, ~, tspan, h, ~, ~, nSteps, ~, ~) - accept = true; - tNew = (nSteps) * h + tspan(1); - end - - end -end - diff --git a/src/+matlode/+stepsizecontroller/Gustafson.m b/src/+matlode/+stepsizecontroller/Gustafson.m index d236ad8..f976d6a 100644 --- a/src/+matlode/+stepsizecontroller/Gustafson.m +++ b/src/+matlode/+stepsizecontroller/Gustafson.m @@ -1,4 +1,4 @@ -classdef Gustafson < matlode.stepsizecontroller.StartingController +classdef Gustafson < matlode.stepsizecontroller.StepSizeController %Gustafsson, K. (1991). Control theoretic techniques for stepsize % selection in explicit Runge-Kutta methods. ACM Transactions on Mathematical Software @@ -24,7 +24,7 @@ opts = p.Results; varargout = p.Unmatched; - obj = obj@matlode.stepsizecontroller.StartingController(2, varargout); + obj = obj@matlode.stepsizecontroller.StepSizeController(2, varargout); obj.Ki = opts.Ki; obj.Kp = opts.Kp; @@ -32,7 +32,7 @@ end - function [accept, hNew, tNew] = newStepSize(obj, prevAccept, t, ~, h, err, q, ~, ~) + function [accept, hNew, tNew] = newStepSize(obj, prevAccept, t, ~, h, err, q) accept = err(1) <= 1; scalh = 1; diff --git a/src/+matlode/+stepsizecontroller/SoderlandController.m b/src/+matlode/+stepsizecontroller/SoderlandController.m index 9b7a27a..8ec1ff7 100644 --- a/src/+matlode/+stepsizecontroller/SoderlandController.m +++ b/src/+matlode/+stepsizecontroller/SoderlandController.m @@ -1,4 +1,4 @@ -classdef SoderlandController < matlode.stepsizecontroller.StartingController +classdef SoderlandController < matlode.stepsizecontroller.StepSizeController %SOBERLANDCONTROLLER %A general controller for all history based non-conditional controllers %can be expanded up to any amount of history gien proper coefficents @@ -39,14 +39,14 @@ error('AQfunc must be a function handle') end - obj = obj@matlode.stepsizecontroller.StartingController(max(length(opts.A), length(opts.B)), varargout); + obj = obj@matlode.stepsizecontroller.StepSizeController(max(length(opts.A), length(opts.B)), varargout); obj.A = opts.A; obj.B = opts.B; obj.AQfunc = opts.AQfunc; end - function [accept, hNew, tNew] = newStepSize(obj, ~, t, ~, h, err, q, ~, ~) + function [accept, hNew, tNew] = newStepSize(obj, ~, t, ~, h, err, q) accept = err(1) <= 1; if accept diff --git a/src/+matlode/+stepsizecontroller/StandardController.m b/src/+matlode/+stepsizecontroller/StandardController.m index 517a220..4310789 100644 --- a/src/+matlode/+stepsizecontroller/StandardController.m +++ b/src/+matlode/+stepsizecontroller/StandardController.m @@ -1,4 +1,4 @@ -classdef StandardController < matlode.stepsizecontroller.StartingController +classdef StandardController < matlode.stepsizecontroller.StepSizeController %Standard Error Controller as described in Solving ODES I book %Seperate from Soberland to help with performance @@ -6,11 +6,11 @@ methods function obj = StandardController(varargin) - obj = obj@matlode.stepsizecontroller.StartingController(1, varargin{:}); + obj = obj@matlode.stepsizecontroller.StepSizeController(1, varargin{:}); end - function [accept, hNew, tNew] = newStepSize(obj, ~, t, ~, h, err, q, ~, ~) + function [accept, hNew, tNew] = newStepSize(obj, ~, t, ~, h, err, q) accept = err <= 1; if accept diff --git a/src/+matlode/+stepsizecontroller/StartingController.m b/src/+matlode/+stepsizecontroller/StartingController.m deleted file mode 100644 index 87f6093..0000000 --- a/src/+matlode/+stepsizecontroller/StartingController.m +++ /dev/null @@ -1,47 +0,0 @@ -classdef (Abstract) StartingController < matlode.stepsizecontroller.StepSizeController - % This class is used to contain common adaptive stepsizecontroler - % properties and to allow the user to change the starting step - % procedure - - properties (Constant) - Adaptive = true; - end - - properties (SetAccess = immutable) - Fac - FacMax - FacMin - InitalStep - end - - - methods - function obj = StartingController(hist, varargin) - p = inputParser; - p.KeepUnmatched = true; - - p.addParameter('Fac', 0.8, matlode.util.scalarValidationFunc); - p.addParameter('FacMin', 0.1, matlode.util.scalarValidationFunc); - p.addParameter('FacMax', 2, matlode.util.scalarValidationFunc); - p.addParameter('InitalStep', matlode.startingstep.WattsStarting()); - - p.parse(varargin{:}); - - opts = p.Results; - varargout = p.Unmatched; - - obj = obj@matlode.stepsizecontroller.StepSizeController(hist, varargout); - - obj.Fac = opts.Fac; - obj.FacMin = opts.FacMin; - obj.FacMax = opts.FacMax; - obj.InitalStep = opts.InitalStep; - - end - - function [h0, f0, fevals] = startingStep(obj, f, tspan, y0, order, errFunc, minStep, maxStep) - [h0, f0, fevals] = obj.InitalStep.startingStep(f, tspan, y0, order, errFunc, minStep, maxStep); - end - end -end - diff --git a/src/+matlode/+stepsizecontroller/StepSizeController.m b/src/+matlode/+stepsizecontroller/StepSizeController.m index 0677b9b..a66e7f5 100644 --- a/src/+matlode/+stepsizecontroller/StepSizeController.m +++ b/src/+matlode/+stepsizecontroller/StepSizeController.m @@ -1,12 +1,12 @@ classdef (Abstract) StepSizeController < handle %Template for a step size controller - properties (Abstract, Constant) - Adaptive - end - properties (SetAccess = immutable) History + Fac + FacMax + FacMin + InitalStep end methods (Access = protected) @@ -15,18 +15,33 @@ p = inputParser; p.KeepUnmatched = false; + p.addParameter('Fac', 0.8, matlode.util.scalarValidationFunc); + p.addParameter('FacMin', 0.1, matlode.util.scalarValidationFunc); + p.addParameter('FacMax', 2, matlode.util.scalarValidationFunc); + p.addParameter('InitalStep', matlode.startingstep.WattsStarting()); + %Gives better error checking p.parse(varargin{:}); + opts = p.Results; + + obj.Fac = opts.Fac; + obj.FacMin = opts.FacMin; + obj.FacMax = opts.FacMax; + obj.InitalStep = opts.InitalStep; + obj.History = hist; end end methods (Abstract) - %accept0, h0, and err0, respect the amount of history needed for a - %controller - [h0, f0, fevals] = startingStep(obj, f, tspan, y0, order, errFunc, minStep, maxStep); - [accept, hNew, tNew] = newStepSize(obj, prevAccept, t, tspan, h, err, q, nSteps, nFailed); + [accept, hNew, tNew] = newStepSize(obj, prevAccept, t, tspan, h, err, q); + end + + methods + function [h0, f0, fevals] = startingStep(obj, f, tspan, y0, order, errFunc, minStep, maxStep) + [h0, f0, fevals] = obj.InitalStep.startingStep(f, tspan, y0, order, errFunc, minStep, maxStep); + end end end diff --git a/src/+matlode/+util/OrderChecking.m b/src/+matlode/+util/OrderChecking.m new file mode 100644 index 0000000..6cc8ab9 --- /dev/null +++ b/src/+matlode/+util/OrderChecking.m @@ -0,0 +1,53 @@ +classdef OrderChecking < handle + %ORDERCHECKING Summary of this class goes here + % Detailed explanation goes here + + properties + Integrator + end + + methods + function obj = OrderChecking(int) + obj.Integrator = int; + end + + function [error, statsCumlative] = getErrorWRTExactValue(obj, model, t0, tf, y0, ytrue, stepamount, opts) + + error = zeros(1,length(step_amount)); + statsCumlative = cell(1, length(step_amount)); + + for i = length(error) + steps = t0:((tf - t0) / stepamount):tf; + [~, yf, statsCumlative{i}] = obj.Integrator.timeLoopFixed(model, steps, y0, opts); + error(i) = norm(yf - ytrue) / norm(ytrue); + end + end + + function [f, polyError] = plot_error_single(obj, stepamount, error, fig_num) + f = figure(fig_num); + + %% For Total Error + loglog(stepamount, error) + + title(['Total Error of: ', class(obj.Integrator)]) + xlabel('Time step') + ylabel('Relative Error') + + polyError = nan_fix(error); + + legend(['p = ', num2str(polyError(1))]) + + function [p_cn] = nan_fix(error) + %step amount becomes block variable + + %shift arrays to remove nan for poly fit for coefficents + nanshift_t = rmmissing(error); + err_size = length(error); + + %gives the linear polynomials of the errors + p_cn = polyfit(log(stepamount((err_size + 1) - length(nanshift_t):err_size)), log(nanshift_t), 1); + end + end + end +end + diff --git a/src/+matlode/Integrator.m b/src/+matlode/Integrator.m index 156b10e..d3d4d15 100644 --- a/src/+matlode/Integrator.m +++ b/src/+matlode/Integrator.m @@ -4,6 +4,8 @@ properties (Abstract, Constant) PartitionMethod + PartitionNum + MultirateMethod end properties (SetAccess = immutable) @@ -13,13 +15,15 @@ methods (Abstract, Access = protected) [t, y, stats] = timeLoop(obj, f, tspan, y0, opts); - [k, yN, err] = timeStep(obj, f, t, y, h, k, ErrNorm, prevAccept); - [k, yN, err] = timeStepErr(obj, f, t, y, h, k, ErrNorm, prevAccept); - [k, fevals, fevalIterCounts] = timeLoopBeforeLoop(obj, f0, t0, y0); + [t, y, stats] = timeLoopFixed(obj, f, tspan, y0, opts); + [ynew, stages, stats] = timeStep(obj, f, t, y, dt, stages, prevAccept, stats); + [err, stats] = timeStepErr(obj, f, t, y, ynew, dt, stages, ErrNorm, stats); + [stages, stats] = timeLoopBeforeLoop(obj, f, f0, t0, y0, stats); [q] = timeLoopInit(obj); end methods + %Integrate over a adaptive step function obj = Integrator(adap, datatype) obj.Adaptive = adap; @@ -27,85 +31,154 @@ end function sol = integrate(obj, f, tspan, y0, varargin) + + if ~obj.Adaptive + error('Method does not support adaptive step control. Call integrateFixed for integration') + end [n,m] = size(tspan); - if ~ismatrix(tspan) - error('tspan cannot have this many dimensions'); - elseif n ~= 1 && m ~= 1 - error('tspan must be a vector') - elseif (length(tspan) < 2) - error('tspan must have a initial and final entry'); + if (n == 1 && m == 1) + error('tspan must be a vector'); + elseif (n > 1 && m > 1) + error('tspan must be a vector'); elseif ~issorted(tspan, 'strictascend') && ~issorted(tspan, 'strictdescend') error('tspan must have unique values and be sorted in either descending or ascending order') end %Create options - p = inputParser; + + p = inputParser(); + p.KeepUnmatched = true; opts = obj.matlodeSets(p, varargin{:}); - if isempty(opts.Dense) && length(tspan) > 2 && ~opts.StepSizeController.Adaptive - error('IntegrateTo is not supported for a non-adaptable controller, please use DenseOutput instead') - end - - t = []; y = []; stats = []; + + new_model = false; %Compute - if isa(f, 'function_handle') && ~obj.PartitionMethod + if isa(f, 'matlode.Model') + if isempty(f.Mass) + f.Mass = eye(length(y0)); + end [t, y, stats] = obj.timeLoop(f, tspan, y0, opts); + elseif isa(f, 'function_handle') || isa(f, 'cell') + if isa(f, 'cell') && ~obj.PartitionMethod + error('This method does not support partitioning') + end + f_mod = matlode.Model(f, varargin{:}); + new_model = true; + if isempty(f_mod.Mass) + f_mod.Mass = eye(length(y0)); + end + + [t, y, stats] = obj.timeLoop(f_mod, tspan, y0, opts); + + elseif isa(f, 'otp.RHS') + f_mod = matlode.Model(f, varargin{:}); + new_model = true; + if isempty(f_mod.Mass) + f_mod.Mass = eye(length(y0)); + end + + [t, y, stats] = obj.timeLoop(f_mod, tspan, y0, opts); - elseif isa(f, 'cell') && cellfun(@(x) isa(x, 'function_handle'), f) + else + error('f must be a ''function_handle'', ''cell'', OTP RHS, or Model object'); + end + + sol.t = t; + sol.y = y; + sol.stats = stats; + + if new_model + sol.model = f_mod; + else + sol.model = f; + end + end + + %Integrate over a fixed interval + function sol = integrateFixed(obj, f, tspan, y0, varargin) + + [n,m] = size(tspan); + + if (n == 1 && m == 1) + error('tspan must be a vector'); + elseif (n > 1 && m > 1) + error('tspan must be a vector'); + elseif ~issorted(tspan, 'strictascend') && ~issorted(tspan, 'strictdescend') + error('tspan must have unique values and be sorted in either descending or ascending order') + end + + %Create options + p = inputParser(); + p.KeepUnmatched = true; + opts = obj.matlodeSets(p, varargin{:}); + + + t = []; + y = []; + stats = []; + + new_model = false; + + %Compute + if isa(f, 'matlode.Model') - %determine size of partition - if length(f) == 1 && ~obj.PartitionMethod - [t, y, stats] = obj.timeLoop(f{1}, tspan, y0, opts); - elseif isempty(f) - error('Partitioned system solves, require atleast one partition to be provided') - else - %paritioning setup - if obj.PartitionMethod - [t, y, stats] = obj.timeLoop(f, tspan, y0, opts); - else - error('This method does not work with a partitioned f'); - end - end + [t, y, stats] = obj.timeLoopFixed(f, tspan, y0, opts); + elseif isa(f, 'function_handle') || isa(f, 'cell') + if isa(f, 'cell') && ~obj.PartitionMethod + error('This method does not support partitioning') + end + f_mod = matlode.Model(f, varargin{:}); + new_model = true; + + [t, y, stats] = obj.timeLoopFixed(f_mod, tspan, y0, opts); + + elseif isa(f, 'otp.RHS') + f_mod = matlode.Model(f, varargin{:}); + new_model = true; + + [t, y, stats] = obj.timeLoopFixed(f_mod, tspan, y0, opts); else - error('f must be a ''function_handle'' or a ''cell'''); + error('f must be a ''function_handle'', ''cell'', OTP RHS, or Model object'); end sol.t = t; sol.y = y; sol.stats = stats; - end + + if new_model + sol.model = f_mod; + else + sol.model = f; + end + end end methods (Access = protected) - function opts = matlodeSets(obj, p, varargin) %Assign to controllers - p.addParameter('StepSizeController', matlode.stepsizecontroller.Fixed(1000)); + p.addParameter('OTPPath', []); + p.addParameter('StepSizeController', matlode.stepsizecontroller.StandardController); p.addParameter('ErrNorm', matlode.errnorm.InfNorm(sqrt(eps), sqrt(eps))); - p.addParameter('Jacobian', []); p.addParameter('ChunkSize', 1000); + p.addParameter('FullTrajectory', false); p.addParameter('Dense', []); p.addParameter('MaxStep', inf); + p.parse(varargin{:}); opts = p.Results; - %make sure a method can use a adaptive controller - if ~obj.Adaptive && opts.StepSizeController.Adaptive - error('Current method does not have the ability to use a adaptive step size controller'); - end - end diff --git a/src/+matlode/LinearOperatorType.m b/src/+matlode/LinearOperatorType.m new file mode 100644 index 0000000..44bc513 --- /dev/null +++ b/src/+matlode/LinearOperatorType.m @@ -0,0 +1,11 @@ +classdef LinearOperatorType + %Defines operators R x F^n -> F^{n x n} + %There are multiple kinds of linear operators + %This class simplifies check what type the linear operator is + %Does not identify if singular or not + %TODO: Add Jet Depedency + enumeration + Empty, Zero, Identity, Constant, TimeDependent, StateDependent, TSDepedent + end +end + diff --git a/src/+matlode/Model.m b/src/+matlode/Model.m new file mode 100644 index 0000000..2f7e153 --- /dev/null +++ b/src/+matlode/Model.m @@ -0,0 +1,216 @@ +classdef Model < handle + %Model describes the model to propagate forward in time + %Will contain all properties that are associated with the model such as + %F,Jacobian, Mass Matrix, etc.. + %All time Integration methods are passed this object + + properties + F + FOperatorType + + Jacobian + JLinearOperatorType + JacobianVectorProduct + JacobianAdjointVectorProduct + JPattern + Vectorized + + Mass + MLinearOperatorType + MVectorProduct + MassSingular + MStateDependence + MvPattern + + PartialDerivativeTime + PDTOperatorType + + PartialDerivativeParameters + PDPOperatorType + + HessianVectorProduct + HessianAdjointVectorProduct + + %FIXME: Not Supported + Events + OnEvent + + + NonNegative + end + + properties(GetAccess = protected, Constant) + %% Constant properties for conversions + MATLABSetVars = {'Jacobian', 'JPattern', 'Vectorized', 'Mass', 'MassSingular', 'MStateDependence', ... + 'MvPattern', 'MassSingular', 'Events', 'NonNegative'} + OTPSetVars = {'Jacobian', 'JPattern', 'Vectorized', 'Mass', 'MStateDependence', ... + 'MvPattern', 'MassSingular', 'Events', 'F', 'JacobianVectorProduct', 'JacobianAdjointVectorProduct', ... + 'PartialDerivativeTime' ,'PartialDerivativeParameters', 'HessianVectorProduct', 'HessianAdjointVectorProduct', ... + 'OnEvent', 'NonNegative'} + end + + methods + function obj = Model(f,varargin) + + %Handle varargin + p = inputParser; + p.KeepUnmatched = true; + + %Handle what f could be + if isa(f, 'function_handle') || (isa(f, 'cell') && cellfun(@(x) isa(x, 'function_handle'), f)) + %% Create new model based of f + obj.F = f; + + p.addParameter('FOperatorType', matlode.OperatorType.TSDep); + + p.addParameter('Jacobian', []); + p.addParameter('JLinearOperatorType', matlode.LinearOperatorType.Empty); + p.addParameter('JPattern', []); + p.addParameter('JacobianVectorProduct', []); + p.addParameter('Vectorized', []); + + p.addParameter('Mass', []); + p.addParameter('MLinearOperatorType', matlode.LinearOperatorType.Identity); + p.addParameter('MassSingular', false); + p.addParameter('MStateDependence', false); + p.addParameter('MvPattern', []); + + p.addParameter('PartialDerivativeTime', []); + p.addParameter('PDTOperatorType', matlode.OperatorType.Zero); + + p.addParameter('PartialDerivativeParameters', []); + p.addParameter('PDPOperatorType', matlode.OperatorType.Zero); + + p.addParameter('HessianVectorProduct', []); + p.addParameter('HessianAdjointVectorProduct', []); + + + p.addParameter('Events', []); + p.addParameter('OnEvent', []); + + p.addParameter('NonNegative', []); + elseif isa(f, 'otp.RHS') + %% OTP RHS Parsing + obj = otpRHSToModel(obj, f); + + for i = 1:length(obj.OTPSetVars) + p.addParameter(obj.OTPSetVars{i}, obj.(obj.OTPSetVars{i})); + end + + p.addParameter('PDTOperatorType', matlode.OperatorType.Zero); + p.addParameter('PDPOperatorType', matlode.OperatorType.Zero); + p.addParameter('MLinearOperatorType', matlode.LinearOperatorType.Identity); + p.addParameter('JLinearOperatorType', matlode.LinearOperatorType.Empty); + p.addParameter('FOperatorType', matlode.OperatorType.TSDep); + + elseif isa(f, 'matlode.Model') + %% Model Copying + + obj = modelCopy(obj, f); + + props = properties(obj); + + for i = 1:length(props) + p.addParameter(props{i}, obj.(props{i})); + end + end + + %Handles the varargin + %Will overwrite model with new parameters + p.parse(varargin{:}); + + parms = p.Results; + + fields = fieldnames(parms); + props = properties(obj); + for i = 1:length(fields) + f = fields{i}; + index = find(strcmp(props, f), 1); + if ~isempty(index) + obj.(f) = parms.(f); + end + end + + obj = modelOperatorTypeSet(obj); + + end + + function obj = setF(obj, f) + obj.F = f; + end + + %Take a preexsisting model and copy into a new one + function obj = modelCopy(obj, otherModel) + + props = properties(obj); + + for i = 1:length(props) + obj.(props{i}) = otherModel.(props{i}); + end + end + + %Convert Model to matlab ode set + function [f, odeopts] = modelToMATLABOdeSets(obj) + f = obj.F; + odeopts = odeset([]); + + for i = 1:length(obj.MATLABSetVars) + odeopts.(obj.MATLABSetVars{i}) = obj.(obj.MATLABSetVars{i}); + end + + end + + %Convert matlab ode set to Model + function obj = matlabOdeSetsToModel(obj, f, odeopts) + obj.F = f; + + for i = 1:length(obj.MATLABSetVars) + obj.(obj.MATLABSetVars{i}) = odeopts.(obj.MATLABSetVars{i}); + end + end + + %Convert OTP RHS object to Model + function obj = otpRHSToModel(obj, rhs) + + for i = 1:length(obj.OTPSetVars) + obj.(obj.OTPSetVars{i}) = rhs.(obj.OTPSetVars{i}); + end + end + + end + + methods (Access = protected) + + function obj = modelOperatorTypeSet(obj) + if isnumeric(obj.Jacobian) + obj.JLinearOperatorType = matlode.LinearOperatorType.Constant; + end + + %% Operator Type Checking + %TODO: Support Constant Operators + + obj = obj.opTypeSet('PartialDerivativeTime', 'PDTOperatorType', matlode.OperatorType.Zero, matlode.OperatorType.TSDep); + obj = obj.opTypeSet('Jacobian', 'JLinearOperatorType', matlode.LinearOperatorType.Empty, matlode.LinearOperatorType.TSDepedent); + % Currently only support constant Mass + % TODO: Support Time Dependent Mass + % TODO: Support State Dependent Mass + obj = obj.opTypeSet('Mass', 'MLinearOperatorType', matlode.LinearOperatorType.Identity, matlode.LinearOperatorType.Constant); + + switch(obj.MLinearOperatorType) + case matlode.LinearOperatorType.Zero + case matlode.LinearOperatorType.Identity + case matlode.LinearOperatorType.Constant + otherwise + error('Not Supported Functionality') + end + end + + function obj = opTypeSet(obj, op, optype, opttypedef, optnew) + if ~isempty(obj.(op)) && (obj.(optype) == opttypedef) + obj.(optype) = optnew; + end + + end + end +end + diff --git a/src/+matlode/OneStepIntegrator.m b/src/+matlode/OneStepIntegrator.m index 2f9bc89..d74e469 100644 --- a/src/+matlode/OneStepIntegrator.m +++ b/src/+matlode/OneStepIntegrator.m @@ -1,7 +1,6 @@ classdef (Abstract) OneStepIntegrator < matlode.Integrator %One Step Integrator template - methods (Access = protected) function obj = OneStepIntegrator(varargin) obj = obj@matlode.Integrator(varargin{:}); @@ -15,6 +14,7 @@ end + %% Time Loop function [t, y, stats] = timeLoop(obj, f, tspan, y0, opts) numVars = length(y0); @@ -25,170 +25,157 @@ %Structure access optimizations errNorm = opts.ErrNorm; stepController = opts.StepSizeController; - adap = stepController.Adaptive; tlen = 0; - if multiTspan + % TODO: Switch to variable and not default + if multiTspan y = zeros(numVars, length(tspan)); t = zeros(1, length(tspan)); - else + elseif opts.FullTrajectory y = zeros(numVars, opts.ChunkSize); t = zeros(1, opts.ChunkSize); tlen = length(t); - end + else + y = zeros(length(y0), 2); + t = tspan; + end + t(:,1) = tspan(:,1); + y(:, 1) = y0; %inital values - t(1,1) = tspan(1); - tCur = tspan(1); - tNext = tCur; - ti = 2; - tl = 2; - if isDense - ti = length(tspan); - else - tl = length(tspan); - end + tcur = tspan(1); + tnext = tcur; + tindex = 2; tspanlen = length(tspan); tdir = sign(tspan(end) - tspan(1)); - hmax = min([abs(opts.MaxStep), abs(tspan(end) - tspan(1))]) * tdir; - hmin = 64 * eps(tspan(1)) * tdir; + dtmax = min([abs(opts.MaxStep), abs(tspan(end) - tspan(1))]) * tdir; + dtmin = 64 * eps(tspan(1)) * tdir; - y(:, 1) = y0; - yNext = y0; + ynext = y0; - %temporary stats - nSteps = 1; - nFailed = 0; - nSmallSteps = 0; + %Create Stats Object + stats = obj.intalizeStats(); + stats.nSteps = 1; [q] = obj.timeLoopInit; %First step %allocates memory for first step - [h0, f0, nFevals] = stepController.startingStep(f, tspan, y0, q, errNorm, hmin, hmax); - - hHist = ones(1, stepController.History) * h0; - hN = hHist(1); - err = ones(1, length(hHist)); + [dt0, f0, stats.nFevals] = stepController.startingStep(f, tspan, y0, q, errNorm, dtmin, dtmax); - accept = true; + dthist = ones(1, stepController.History) * dt0; + dtnext = dthist(1); + err = ones(1, length(dthist)); - [k, fevals, fevalIterCounts] = obj.timeLoopBeforeLoop(f0, tspan(1), y0); + prevAccept = true; - nFevals = nFevals + fevals; + [stages, stats] = obj.timeLoopBeforeLoop(f, f0, tspan(1), y0, stats); %Time Loop - while ti <= tspanlen + while tindex <= tspanlen %Cycle history err = circshift(err, 1, 2); - hHist = circshift(hHist, 1, 2); + dthist = circshift(dthist, 1, 2); - yCur = yNext; - tCur = tNext; - hC = hN; - hHist(1) = hN; + ycur = ynext; + tcur = tnext; + dtcur = dtnext; + dthist(1) = dtnext; %Subject to change - hmin = 64 * eps(tCur) * tdir; + dtmin = 64 * eps(tcur) * tdir; %Accept Loop %Will keep looping until accepted step while true - prevAccept = accept; - - if adap - [k, yNext, err(:, 1)] = timeStepErr(obj, f, tCur, yCur, hC, k, errNorm, prevAccept); - else - [k, yNext, err(:, 1)] = timeStep(obj, f, tCur, yCur, hC, k, errNorm, prevAccept); - end - - nFevals = nFevals + fevalIterCounts; + [ynext, stages, stats] = timeStep(obj, f, tcur, ycur, dtcur, stages, prevAccept, stats); + [err(:, 1), stats] = timeStepErr(obj, f, tcur, ycur, ynext, dtcur, stages, errNorm, stats); %Find next Step - [accept, hN, tNext] = stepController.newStepSize(prevAccept, tCur, tspan, hHist, err, q, nSteps, nFailed); + %TODO: Update to take stats + [prevAccept, dtnext, tnext] = stepController.newStepSize(prevAccept, tcur, tspan, dthist, err, q); %Check if step is really small - if abs(hN) < abs(hmin) - if nSmallSteps == 0 + if abs(dtnext) < abs(dtmin) + %Prevents excessive output + if stats.nSmallSteps == 0 warning('The step the integrator is taking extremely small, results may not be optimal') end %accept step since the step cannot get any smaller - nSmallSteps = nSmallSteps + 1; - hN = hmin; - tNext = tCur + hmin; - accept = true; + stats.nSmallSteps = stats.nSmallSteps + 1; + dtnext = dtmin; + tnext = tcur + dtmin; + prevAccept = true; break; end %check step acception - if accept + if prevAccept break; end - nFailed = nFailed + 1; - hC = hN; - hHist(1) = hN; + stats.nFailed = stats.nFailed + 1; + dtcur = dtnext; + dthist(1) = dtnext; end %set new step to be in range - if adap - hN = min(abs(hmax), abs(hN)) * tdir; - end + dtnext = min(abs(dtmax), abs(dtnext)) * tdir; - nSteps = nSteps + 1; + stats.nSteps = stats.nSteps + 1; %Check for all memory allocation - if ~multiTspan + if ~multiTspan && opts.FullTrajectory %Allocate more memory if non-dense - if nSteps > tlen + if stats.nSteps > tlen y = [y, zeros(numVars, opts.ChunkSize)]; t = [t, zeros(1, opts.ChunkSize)]; tlen = length(t); end - t(:, nSteps) = tNext; - y(:, nSteps) = yNext; + t(:, stats.nSteps) = tnext; + y(:, stats.nSteps) = ynext; elseif isDense %Dense output - while tl ~= tspanlen && tNext * tdir > tspan(tl) * tdir + while tindex ~= tspanlen && tnext * tdir > tspan(tindex) * tdir %incase lucky and land on point, can be cheaper %than dense output if dense requires fevals - if abs(tspan(tl) - tNext) < abs(hmin) - t(:, tl) = tspan(tl); - y(:, tl) = yNext; + if abs(tspan(tindex) - tnext) < abs(dtmin) + t(:, tindex) = tspan(tindex); + y(:, tindex) = ynext; else %Call Dense output function and record - t(:, tl) = tspan(tl); - [y(:, tl), tempFevals] = opts.Dense.denseOut(f, tCur, tspan(tl), yCur, yNext, k, hC); - nFevals = nFevals + tempFevals; + t(:, tindex) = tspan(tindex); + [y(:, tindex), tempFevals] = opts.Dense.denseOut(f, tcur, tspan(tindex), ycur, ynext, stages, dtcur); + stats.nFevals = stats.nFevals + tempFevals; end - tl = tl + 1; + tindex = tindex + 1; end end %check if condition holds %used for end checking and integrate to - if tspan(ti) * tdir <= (tNext + hN) * tdir + if tspan(tindex) * tdir <= (tnext + dtnext) * tdir %integrate to/ End point %check if close enough with hmin - if abs(tspan(ti) - tNext) < abs(hmin) + if abs(tspan(tindex) - tnext) < abs(dtmin) if multiTspan - t(:, ti) = tspan(ti); - y(:, ti) = yNext; + t(:, tindex) = tspan(tindex); + y(:, tindex) = ynext; end - ti = ti + 1; - elseif stepController.Adaptive + tindex = tindex + 1; + else - hN = tspan(ti) - tNext; + dtnext = tspan(tindex) - tnext; end end @@ -198,21 +185,69 @@ %End of Timeloop work %Truncate extra memory - if ~multiTspan - t = t(:, 1:nSteps); - y = y(:, 1:nSteps); + if ~multiTspan && opts.FullTrajectory + t = t(:, 1:stats.nSteps); + y = y(:, 1:stats.nSteps); else t(:, end) = tspan(end); - y(:, end) = yNext; + y(:, end) = ynext; end %Create Stats - stats.nSteps = nSteps - 1; - stats.nFailed = nFailed; - stats.nFevals = nFevals; - stats.nSmallSteps = nSmallSteps; + stats.nSteps = stats.nSteps - 1; - end + end + + %% Fixed Time Loop + function [t, y, stats] = timeLoopFixed(obj, f, tspan, y0, opts) + + + if opts.FullTrajectory + y = zeros(length(y0), length(tspan)); + y(:, 1) = y0; + t = tspan; + else + y = zeros(length(y0), 2); + t = tspan; + end + + t(:,1) = tspan(:,1); + y(:, 1) = y0; + + %inital values + ynext = y0; + + %Start stats + stats = obj.intalizeStats; + stats.nSteps = length(tspan); + + + [stages, stats] = obj.timeLoopBeforeLoop(f0, tspan(1), y0, stats); + + %Time Loop + for i = 1:(length(tspan)-1) + yi = ynext; + tcur = tspan(i); + dtc = tspan(i+1) - tspan(i); + + [ynext, stages, stats] = timeStep(obj, f, tcur, yi, dtc, stages, true, stats); + + if opts.FullTrajectory || i == (length(tspan)-1) + y(:, i + 1) = ynext; + end + end + end + + function stats = intalizeStats(obj) + + stats.nSteps = 0; + stats.nFailed = 0; + stats.nSmallSteps = 0; + stats.nLinearSolves = 0; + stats.nMassEvals = 0; + stats.nJacobianEvals = 0; + stats.nPDTEval = 0; + end end end diff --git a/src/+matlode/OperatorType.m b/src/+matlode/OperatorType.m new file mode 100644 index 0000000..9547e53 --- /dev/null +++ b/src/+matlode/OperatorType.m @@ -0,0 +1,9 @@ +classdef OperatorType + %Defines operator type from R x F^n -> F^n + % Or in the implicit case for jets R x F^n x F^n -> F^n + enumeration + Zero, Constant, TimeDependent, StateDependent, JetDependent, TSDep, SJDep, TJDep, TSJDep + end + +end + diff --git a/src/+testingscripts/+OTPtest/TestERK.m b/src/+testingscripts/+OTPtest/TestERK.m new file mode 100644 index 0000000..dadc72e --- /dev/null +++ b/src/+testingscripts/+OTPtest/TestERK.m @@ -0,0 +1,20 @@ +clear +format long e +close all + +integrator = matlode.rk.erk.DormandPrince(); + +options.ErrNorm = matlode.errnorm.InfNorm(1e-12, 1e-12); +options.StepSizeController = matlode.stepsizecontroller.StandardController; + +problem = otp.linear.presets.Canonical; + +sol = integrator.integrate(problem.RHS, problem.TimeSpan, problem.Y0, options); + +sol_matlab = problem.solve('RelTol', 1e-12, 'AbsTol', 1e-12); + +true_sol = exp(-problem.TimeSpan(end)); + +norm(sol.y(end) - true_sol) +norm(sol_matlab.y(end) - true_sol) +norm(sol.y(end) - sol_matlab.y(end)) diff --git a/src/+testingscripts/+OTPtest/TestROS.m b/src/+testingscripts/+OTPtest/TestROS.m new file mode 100644 index 0000000..3596277 --- /dev/null +++ b/src/+testingscripts/+OTPtest/TestROS.m @@ -0,0 +1,20 @@ +clear +format long e +close all + +integrator = matlode.rosenbrock.RODAS3(); + +options.ErrNorm = matlode.errnorm.InfNorm(1e-12, 1e-12); +options.StepSizeController = matlode.stepsizecontroller.StandardController; + +problem = otp.linear.presets.Canonical; + +sol = integrator.integrate(problem.RHS, problem.TimeSpan, problem.Y0, options); + +sol_matlab = problem.solve('RelTol', 1e-12, 'AbsTol', 1e-12); + +true_sol = exp(-problem.TimeSpan(end)); + +norm(sol.y(end) - true_sol) +norm(sol_matlab.y(end) - true_sol) +norm(sol.y(end) - sol_matlab.y(end)) diff --git a/src/+testingscripts/+modelverification/ModelOTPTest.m b/src/+testingscripts/+modelverification/ModelOTPTest.m new file mode 100644 index 0000000..237521c --- /dev/null +++ b/src/+testingscripts/+modelverification/ModelOTPTest.m @@ -0,0 +1,22 @@ + +clear all +close all +format long e + +% To test the interactions between MATLODE, OTP, and standard matlab +% options + + +problem = otp.lorenz63.presets.Canonical; + +model = matlode.Model(problem.RHS); + +mass = 1; + +model2 = matlode.Model(problem.RHS, 'Mass', mass); + +otpsets = odeset('Mass', mass, 'RelTol', 1e-6, 'AbsTol', 1e-6); +dfdt = @(t,y) 0; + +model3 = matlode.Model(problem.RHS, otpsets, 'PartialDerivativeTime', dfdt); + diff --git a/src/+testingscripts/+modelverification/ModelTest.m b/src/+testingscripts/+modelverification/ModelTest.m new file mode 100644 index 0000000..587c3b2 --- /dev/null +++ b/src/+testingscripts/+modelverification/ModelTest.m @@ -0,0 +1,26 @@ + +clear all +close all +format long e + +%Test should ignore RelTol AbsTol without error +%Test will copy model 1 into model 2 +%Test should modify model and set operator types + +f = @(t,y) y; +jac = @(t,y) 1; +mass = 1; +dfdt = @(t,y) 0; + +model = matlode.Model(f, 'Jacobian', jac, 'Mass', mass, 'PartialDerivativeTime', dfdt, 'RelTol', 1e-6, 'AbsTol', 1e-6); + +f2 = @(t,y) 2*y; + +model2 = matlode.Model(model); + +jac2 = @(t,y) t; +mass2 = 2; + +otpsets = odeset('Jacobian', jac2, 'Mass', mass2, 'RelTol', 1e-6, 'AbsTol', 1e-6); + +model3 = matlode.Model(model, otpsets); \ No newline at end of file diff --git a/src/+testingscripts/+orderchecking/ERKOrderTest.m b/src/+testingscripts/+orderchecking/ERKOrderTest.m new file mode 100644 index 0000000..e69de29 diff --git a/src/+testingscripts/+orderchecking/ROSOrderTest.m b/src/+testingscripts/+orderchecking/ROSOrderTest.m new file mode 100644 index 0000000..e69de29 diff --git a/src/+testingscripts/+simpletest/Test_ERK.m b/src/+testingscripts/+simpletest/Test_ERK.m new file mode 100644 index 0000000..1c9e2a2 --- /dev/null +++ b/src/+testingscripts/+simpletest/Test_ERK.m @@ -0,0 +1,17 @@ +clear +format long e +close all + +integrator = matlode.rk.erk.DormandPrince(); + +options.ErrNorm = matlode.errnorm.InfNorm(1e-6, 1e-6); +options.StepSizeController = matlode.stepsizecontroller.StandardController; + +lambda = -1; +test_problem = testingscripts.testproblems.ODE.EulerProblem(lambda); + +t_f = 1; + +sol = integrator.integrate(@(t,y) test_problem.f(t,y), [test_problem.t_0, t_f], test_problem.y_0, options); + +norm(sol.y(end) - test_problem.y_exact(t_f)) \ No newline at end of file diff --git a/src/+testingscripts/+simpletest/Test_ROS.m b/src/+testingscripts/+simpletest/Test_ROS.m new file mode 100644 index 0000000..0666192 --- /dev/null +++ b/src/+testingscripts/+simpletest/Test_ROS.m @@ -0,0 +1,24 @@ +clear +format long e +close all + +%% Test Problem + +lambda = -1; +test_problem = testingscripts.testproblems.ODE.EulerProblem(lambda); + +t_f = 1; + +%% Integrator Parameters + +integrator = matlode.rosenbrock.LinBackEuler; + +options.ErrNorm = matlode.errnorm.InfNorm(1e-6, 1e-6); +options.StepSizeController = matlode.stepsizecontroller.StandardController; +options.LinearSolver = matlode.linearsolver.MatrixLinearSolver([], 'Jacobain', test_problem.jac); + +%% Integration + +sol = integrator.integrate(@(t,y) test_problem.f(t,y), [test_problem.t_0, t_f], test_problem.y_0, options); + +norm(sol.y(end) - test_problem.y_exact(t_f)) \ No newline at end of file diff --git a/src/+testingscripts/+testproblems/+ODE/EulerProblem.m b/src/+testingscripts/+testproblems/+ODE/EulerProblem.m new file mode 100644 index 0000000..1c04df1 --- /dev/null +++ b/src/+testingscripts/+testproblems/+ODE/EulerProblem.m @@ -0,0 +1,33 @@ +classdef EulerProblem < testingscripts.testproblems.ODE.ODE + % The one dimensional Euler problem with lambda parameter + + properties + lambda + end + + methods + function obj = EulerProblem(lambda) + obj.problem_name = 'Euler Problem'; + obj.lambda = lambda; + obj.y_0 = 1; + obj.t_0 = 0; + end + + function dy = f(obj, ~,y) + dy = obj.lambda * y; + end + + function df_y = jac(obj, ~,~) + df_y = obj.lambda; + end + + function df_t = f_t(~, ~,~) + df_t = 0; + end + + function y = y_exact(obj, t) + y = exp(obj.lambda * t); + end + end +end + diff --git a/src/+testingscripts/+testproblems/+ODE/ODE.m b/src/+testingscripts/+testproblems/+ODE/ODE.m new file mode 100644 index 0000000..f9b50a8 --- /dev/null +++ b/src/+testingscripts/+testproblems/+ODE/ODE.m @@ -0,0 +1,21 @@ +classdef (Abstract) ODE + %ODE Summary of this class goes here + % Detailed explanation goes here + + properties (SetAccess = public) + problem_name + y_0 + t_0 + end + + methods (Abstract) + dy = f(obj, t,y); + df_y = jac(obj, t,y); + df_t = f_t(obj, t,y); + + y = y_exact(obj, t); + end + + +end + From e7e14b430c49a6a416be9ac8da7d05ef6ab50432 Mon Sep 17 00:00:00 2001 From: reid-g Date: Fri, 21 Oct 2022 14:57:27 -0400 Subject: [PATCH 09/17] Added New Methods and Updates +Added new ERK methods +Added new ROS and ROS-W methods /Updated Integrators --- src/+matlode/+rk/+erk/BogackiShampine.m | 42 +++++++++++++++++++ src/+matlode/+rk/+erk/ClassicRK4.m | 20 ++++----- src/+matlode/+rk/+erk/DormandPrince.m | 24 ++++------- src/+matlode/+rk/+erk/ForwardEuler.m | 17 ++++---- src/+matlode/+rk/RungeKutta.m | 9 +++- src/+matlode/+rosenbrock/LinBackEuler.m | 33 +++++---------- src/+matlode/+rosenbrock/RODAS3.m | 24 ++++------- src/+matlode/+rosenbrock/RODAS4.m | 48 ++++++++++++++++++++++ src/+matlode/+rosenbrock/ROS34PW2.m | 44 ++++++++++++++++++++ src/+matlode/+rosenbrock/ROS3P.m | 17 ++++---- src/+matlode/+rosenbrock/Rosenbrock.m | 19 ++++++--- src/+testingscripts/+OTPtest/TestERK.m | 6 +-- src/+testingscripts/+OTPtest/TestROS.m | 2 +- src/+testingscripts/+simpletest/Test_ERK.m | 2 +- 14 files changed, 209 insertions(+), 98 deletions(-) create mode 100644 src/+matlode/+rk/+erk/BogackiShampine.m create mode 100644 src/+matlode/+rosenbrock/RODAS4.m create mode 100644 src/+matlode/+rosenbrock/ROS34PW2.m diff --git a/src/+matlode/+rk/+erk/BogackiShampine.m b/src/+matlode/+rk/+erk/BogackiShampine.m new file mode 100644 index 0000000..e3f9642 --- /dev/null +++ b/src/+matlode/+rk/+erk/BogackiShampine.m @@ -0,0 +1,42 @@ +classdef BogackiShampine < matlode.rk.erk.ERK +%Method: Dormand PRince +% p = 3 s = 4 pe = 2 +%Reference: + + methods + + function obj = BogackiShampine(datatype) + arguments + datatype(1,1) string = 'double'; + end + + caster = @(x) matlode.util.CoefficentTransformers.transform(x,datatype); + + + a = caster(join(['[[0, 0, 0, 0];',... + '[1/2, 0, 0, 0];',... + '[0, 3/4, 0, 0];',... + '[2/9, 1/3, 4/9, 0]]'])); + + b = caster('[2/9, 1/3, 4/9, 0]'); + + bHat = caster('[7/24, 1/4, 1/3, 1/8]'); + + e = caster('[-5/72, 1/12, 1/9, -1/8]'); + + c = caster('[0, 1/2, 3/4, 1]'); + + order = 3; + + embbededOrder = 2; + + obj = obj@matlode.rk.erk.ERK(a, b, bHat, c, e, order, embbededOrder); + + end + + function sol = integrate(obj, f, tspan, y0, varargin) + sol = integrate@matlode.rk.erk.ERK(obj, f, tspan, y0, 'StepSizeController', matlode.stepsizecontroller.StandardController, 'Dense', obj.DenseOut, varargin{:}); + end + end +end + diff --git a/src/+matlode/+rk/+erk/ClassicRK4.m b/src/+matlode/+rk/+erk/ClassicRK4.m index ae72c45..835adb2 100644 --- a/src/+matlode/+rk/+erk/ClassicRK4.m +++ b/src/+matlode/+rk/+erk/ClassicRK4.m @@ -1,19 +1,17 @@ classdef ClassicRK4 < matlode.rk.erk.ERK +%Method: Classic Runge Kutta +% p = 4 s = 4 pe = -1 +%Reference: + properties (SetAccess = immutable) DenseOut end methods function obj = ClassicRK4(datatype) - - %TODO - %include reference - - % p = 4 & hatp = 0 & s = 4 - - if nargin == 0 - datatype = 'double'; - end + arguments + datatype(1,1) string = 'double'; + end caster = @(x) matlode.util.CoefficentTransformers.transform(x,datatype); @@ -41,12 +39,10 @@ '[0, 1, -2/3]',... '[0, 1, -2/3]',... '[0, 1/2, 2/3]'])); - - obj.DenseOut = matlode.denseoutput.RKDense(denseMatrix); end function sol = integrate(obj, f, tspan, y0, varargin) - sol = integrate@matlode.rk.erk.ERK(obj, f, tspan, y0, 'StepSizeController', matlode.stepsizecontroller.Fixed(1000), varargin{:}); + sol = integrate@matlode.rk.erk.ERK(obj, f, tspan, y0, 'Dense', obj.DenseOut,varargin{:}); end end end diff --git a/src/+matlode/+rk/+erk/DormandPrince.m b/src/+matlode/+rk/+erk/DormandPrince.m index 7f5280f..4ec8d6f 100644 --- a/src/+matlode/+rk/+erk/DormandPrince.m +++ b/src/+matlode/+rk/+erk/DormandPrince.m @@ -1,24 +1,14 @@ classdef DormandPrince < matlode.rk.erk.ERK - properties (SetAccess = immutable) - DenseOut - end +%Method: Dormand PRince +% p = 5 s = 7 pe = 4 +%Reference: methods - function obj = DormandPrince(varargin) - - %TODO - %include reference for method - - p = inputParser; - p.KeepUnmatched = true; - - p.addParameter('datatype', 'double'); - - p.parse(varargin{:}); - opts = p.Results; - - datatype = opts.datatype; + function obj = DormandPrince(datatype) + arguments + datatype(1,1) string = 'double'; + end caster = @(x) matlode.util.CoefficentTransformers.transform(x,datatype); diff --git a/src/+matlode/+rk/+erk/ForwardEuler.m b/src/+matlode/+rk/+erk/ForwardEuler.m index 5038c6d..2fd0458 100644 --- a/src/+matlode/+rk/+erk/ForwardEuler.m +++ b/src/+matlode/+rk/+erk/ForwardEuler.m @@ -1,14 +1,13 @@ classdef ForwardEuler < matlode.rk.erk.ERK +%Method: Forward Euler +% p = 1 s = 1 pe = -1 +%Reference: + methods function obj = ForwardEuler(datatype) - %TODO - %include reference - - %p = 1 & hatp = 0 & s = 1 - - if nargin == 0 - datatype = 'double'; - end + arguments + datatype(1,1) string = 'double'; + end caster = @(x) matlode.util.CoefficentTransformers.transform(x,datatype); @@ -32,7 +31,7 @@ end function sol = integrate(obj, f, tspan, y0, varargin) - sol = integrate@matlode.rk.erk.ERK(obj, f, tspan, y0,'Dense', matlode.denseoutput.Linear(obj.B), 'StepSizeController', matlode.stepsizecontroller.Fixed(1000), varargin{:}); + sol = integrate@matlode.rk.erk.ERK(obj, f, tspan, y0,'Dense', obj.DenseOut, varargin{:}); end end end diff --git a/src/+matlode/+rk/RungeKutta.m b/src/+matlode/+rk/RungeKutta.m index 678393b..4fba523 100644 --- a/src/+matlode/+rk/RungeKutta.m +++ b/src/+matlode/+rk/RungeKutta.m @@ -1,7 +1,7 @@ classdef (Abstract) RungeKutta < matlode.OneStepIntegrator %RungeKutta integrator - properties (SetAccess = immutable) + properties (SetAccess = protected) A B C @@ -12,7 +12,11 @@ Order FSAL FsalStart - end + end + + properties (SetAccess = protected) + DenseOut + end methods function obj = RungeKutta(a, b, bHat, c, e, order, embeddedOrder) @@ -29,6 +33,7 @@ obj.StageNum = size(b, 2); obj.FSAL = all(a(end, :) == b) && all(a(1, :) == 0); obj.FsalStart = uint32(obj.FSAL) + 1; + obj.DenseOut = matlode.denseoutput.Linear(obj.B); end function obj = fromCoeffs(a, b, bHat, c, e, bTheta, order, embededOrder) diff --git a/src/+matlode/+rosenbrock/LinBackEuler.m b/src/+matlode/+rosenbrock/LinBackEuler.m index c10c850..5df8ba1 100644 --- a/src/+matlode/+rosenbrock/LinBackEuler.m +++ b/src/+matlode/+rosenbrock/LinBackEuler.m @@ -1,23 +1,13 @@ classdef LinBackEuler < matlode.rosenbrock.Rosenbrock - %LINBACKEULER Summary of this class goes here - % Detailed explanation goes here - - properties - Dense - end +%Method: Linearly Implicit Euler +% p = 1 s = 1 pe = 0 +%Reference: methods - function obj = LinBackEuler(varargin) - - p = inputParser; - p.KeepUnmatched = true; - - p.addParameter('datatype', 'double'); - - p.parse(varargin{:}); - opts = p.Results; - - datatype = opts.datatype; + function obj = LinBackEuler(datatype) + arguments + datatype(1,1) string = 'double'; + end caster = @(x) matlode.util.CoefficentTransformers.transform(x,datatype); @@ -26,21 +16,18 @@ b = caster('[1]'); be = []; - [gammadia, gammasum, alphasum, a, c, m, me] = RosCoefMethTrans(gamma, alpha, b, be); + [gammadia, gammasum, alphasum, a, c, m, me, e] = RosCoefMethTrans(gamma, alpha, b, be); order = 1; embbededOrder = 0; - - obj = obj@matlode.rosenbrock.Rosenbrock(gammadia, gammasum, alphasum, a, c, m, me, order, embbededOrder); - - obj.Dense = matlode.denseoutput.Linear(b); + obj = obj@matlode.rosenbrock.Rosenbrock(gammadia, gammasum, alphasum, a, c, m, e, order, embbededOrder); end function sol = integrate(obj, f, tspan, y0, varargin) - sol = integrate@matlode.rosenbrock.Rosenbrock(obj, f, tspan, y0, 'StepSizeController', matlode.stepsizecontroller.Fixed(1000), 'Dense', obj.DenseOut, varargin{:}); + sol = integrate@matlode.rosenbrock.Rosenbrock(obj, f, tspan, y0, 'Dense', obj.DenseOut, varargin{:}); end end end diff --git a/src/+matlode/+rosenbrock/RODAS3.m b/src/+matlode/+rosenbrock/RODAS3.m index 6ead8c4..95f0228 100644 --- a/src/+matlode/+rosenbrock/RODAS3.m +++ b/src/+matlode/+rosenbrock/RODAS3.m @@ -1,17 +1,13 @@ classdef RODAS3 < matlode.rosenbrock.Rosenbrock - %RODAS3 Summary of this class goes here - % Detailed explanation goes here - - properties (SetAccess = immutable) - Dense - end +%Method: RODAS3 +% p = 3 s = 4 pe = 2 +%Reference: methods function obj = RODAS3(datatype) - if nargin == 0 - datatype = 'double'; - end - + arguments + datatype(1,1) string = 'double'; + end caster = @(x) matlode.util.CoefficentTransformers.transform(x,datatype); @@ -29,20 +25,18 @@ '[1, -1, 0, 0];',... '[1, -1, -8/3, 0]]'])); m = caster('[2, 0, 1, 1]'); - me = caster('[0, 0, 0, 1]'); + e = caster('[0, 0, 0, 1]'); order = 3; embbededOrder = 2; - obj = obj@matlode.rosenbrock.Rosenbrock(gammadia, gammasum, alphasum, a, c, m, me, order, embbededOrder); - + obj = obj@matlode.rosenbrock.Rosenbrock(gammadia, gammasum, alphasum, a, c, m, e, order, embbededOrder); - obj.Dense = matlode.denseoutput.Linear(m); end function sol = integrate(obj, f, tspan, y0, varargin) - sol = integrate@matlode.rosenbrock.Rosenbrock(obj, f, tspan, y0, 'StepSizeController', matlode.stepsizecontroller.StandardController, 'Dense', obj.Dense, varargin{:}); + sol = integrate@matlode.rosenbrock.Rosenbrock(obj, f, tspan, y0, 'StepSizeController', matlode.stepsizecontroller.StandardController, 'Dense', obj.DenseOut, varargin{:}); end end end diff --git a/src/+matlode/+rosenbrock/RODAS4.m b/src/+matlode/+rosenbrock/RODAS4.m new file mode 100644 index 0000000..a81d2e6 --- /dev/null +++ b/src/+matlode/+rosenbrock/RODAS4.m @@ -0,0 +1,48 @@ +classdef RODAS4 < matlode.rosenbrock.Rosenbrock +%Method: RODAS4 +% p = 4 s = 6 pe = 3 +%Reference: + + methods + function obj = RODAS4(datatype) + arguments + datatype(1,1) string = 'double'; + end + + caster = @(x) matlode.util.CoefficentTransformers.transform(x,datatype); + + gammadia = caster('[1/4, 1/4, 1/4, 1/4, 1/4, 1/4]'); + alphasum = caster('[0, 0.386, 0.210, 0.630, 1, 1]'); + gammasum = caster('[1/4, -0.1043, 0.1035, -0.0362, 0, 0]'); + + a = caster(join(['[[0,0,0,0,0,0];',... + '[1.544,0,0,0,0,0];',... + '[0.9466785280815826, 0.2557011698983284, 0, 0, 0, 0];',... + '[0.3314825187068521e1, 0.2896124015972201e1, 0.9986419139977817, 0, 0, 0];',... + '[0.1221224509226641e1, 0.6019134481288629e1, 0.1253708332932087e2, -0.6878860361058950, 0, 0];',... + '[0.1221224509226641d+01, 0.6019134481288629e1, 0.1253708332932087e2, -0.6878860361058950, 1, 0]]'])); + + c = caster(join(['[[0, 0, 0, 0, 0, 0];',... + '[-0.5668800000000000e1, 0, 0, 0, 0, 0];',... + '[-0.2430093356833875e1, -0.2063599157091915, 0, 0, 0, 0];',... + '[-0.1073529058151375, -0.9594562251023355e1, -0.2047028614809616e2, 0, 0, 0];',... + '[0.7496443313967647e1, -0.1024680431464352e2, -0.3399990352819905e2, 0.1170890893206160e2, 0, 0];',... + '[0.8083246795921522e1, -0.7981132988064893e1, -0.3152159432874371e2, 0.1631930543123136e2, -0.6058818238834054e1, 0]]'])); + + m = caster('[0.1221224509226641d+01, 0.6019134481288629e1, 0.1253708332932087e2, -0.6878860361058950, 1, 1]'); + e = caster('[0, 0, 0, 0, 0, 1]'); + + order = 4; + + embbededOrder = 3; + + obj = obj@matlode.rosenbrock.Rosenbrock(gammadia, gammasum, alphasum, a, c, m, e, order, embbededOrder); + + end + + function sol = integrate(obj, f, tspan, y0, varargin) + sol = integrate@matlode.rosenbrock.Rosenbrock(obj, f, tspan, y0, 'StepSizeController', matlode.stepsizecontroller.StandardController, 'Dense', obj.DenseOut, varargin{:}); + end + end +end + diff --git a/src/+matlode/+rosenbrock/ROS34PW2.m b/src/+matlode/+rosenbrock/ROS34PW2.m new file mode 100644 index 0000000..0863050 --- /dev/null +++ b/src/+matlode/+rosenbrock/ROS34PW2.m @@ -0,0 +1,44 @@ +classdef ROS34PW2 < matlode.rosenbrock.Rosenbrock +%Method: ROS34PW2 +% Rosenbrock-w method +% p = 3 s = 4 pe = 2 +%Reference: + + methods + function obj = ROS34PW2(datatype) + arguments + datatype(1,1) string = 'double'; + end + + caster = @(x) matlode.util.CoefficentTransformers.transform(x,datatype); + + gammadia = caster('[0.435866521508459, 0.435866521508459, 0.435866521508459, 0.435866521508459]'); + alphasum = caster('[0, 0.871733043016918, 0.731579957788852, 1]'); + gammasum = caster('[0.435866521508459, -0.435866521508459, -0.413333376233886, -5.55111512312578e-17]'); + + a = caster(join(['[[0, 0, 0, 0];',... + '[2, 0, 0, 0];',... + '[1.41921731745576, -0.25923221167297, 0, 0];',... + '[4.18476048231916, -0.285192017355496, 2.29428036027904, 0]]'])); + + c = caster(join(['[[0, 0, 0, 0];',... + '[-4.58856072055809, 0, 0, 0];',... + '[-4.18476048231916, 0.285192017355496, 0, 0];',... + '[-6.36817920012836, -6.79562094446684, 2.87009860433106, 0]]'])); + m = caster('[4.18476048231916, -0.285192017355496, 2.29428036027904, 1]'); + e = caster('[0.277749947647967, -1.403239895176, 1.77263012766755, 0.5]'); + + order = 3; + + embbededOrder = 2; + + obj = obj@matlode.rosenbrock.Rosenbrock(gammadia, gammasum, alphasum, a, c, m, e, order, embbededOrder); + + end + + function sol = integrate(obj, f, tspan, y0, varargin) + sol = integrate@matlode.rosenbrock.Rosenbrock(obj, f, tspan, y0, 'StepSizeController', matlode.stepsizecontroller.StandardController, 'Dense', obj.DenseOut, varargin{:}); + end + end +end + diff --git a/src/+matlode/+rosenbrock/ROS3P.m b/src/+matlode/+rosenbrock/ROS3P.m index d7178fd..1777355 100644 --- a/src/+matlode/+rosenbrock/ROS3P.m +++ b/src/+matlode/+rosenbrock/ROS3P.m @@ -1,13 +1,13 @@ classdef ROS3P < matlode.rosenbrock.Rosenbrock - properties - Dense - end +%Method: ROS3P +% p = 3 s = 3 pe = 2 +%Reference: methods function obj = ROS3P(datatype) - if nargin == 0 - datatype = 'double'; - end + arguments + datatype(1,1) string = 'double'; + end caster = @(x) matlode.util.CoefficentTransformers.transform(x,datatype); @@ -23,15 +23,14 @@ '[−1.607695154586736, 0, 0];',... '[−3.464101615137755, −1.732050807568877, 0]'])); m = caster('[2, 5.773502691896258e−01, 4.226497308103742e−01]'); - me = caster('[2.113248654051871, 1, 4.226497308103742e−01]'); + e = caster('[2.113248654051871, 1, 4.226497308103742e−01]'); order = 3; embbededOrder = 2; - obj = obj@matlode.rosenbrock.Rosenbrock(gammadia, gammasum, alphasum, a, c, m, me, order, embbededOrder); + obj = obj@matlode.rosenbrock.Rosenbrock(gammadia, gammasum, alphasum, a, c, m, e, order, embbededOrder); - obj.Dense = matlode.denseoutput.Linear(m); end function sol = integrate(obj, f, tspan, y0, varargin) diff --git a/src/+matlode/+rosenbrock/Rosenbrock.m b/src/+matlode/+rosenbrock/Rosenbrock.m index 8eb40a6..8c750c8 100644 --- a/src/+matlode/+rosenbrock/Rosenbrock.m +++ b/src/+matlode/+rosenbrock/Rosenbrock.m @@ -24,16 +24,20 @@ properties (SetAccess = immutable, GetAccess = private) FsalStart - end + end + + properties (SetAccess = protected) + DenseOut + end - properties + properties LinearSolver end methods (Static) %% Coefficent Transformation Methods %Coefficent transformer - function [gammadia, gammasum, alphasum, a, c, m, me] = RosCoefMethTrans(gamma, alpha, b, be) + function [gammadia, gammasum, alphasum, a, c, m, me, e] = RosCoefMethTrans(gamma, alpha, b, be) gammadia = diag(gamma); gammasum = sum(gamma, 2); alphasum = sum(alpha, 2); @@ -42,8 +46,10 @@ %TODO: Fix if isempty(be) me = []; + e = []; else me = mrdivide(be, gamma); + e = m - me; end eta = gamma * diag(1 ./ gammadia) + eye(length(gammadia)); @@ -86,9 +92,9 @@ methods - function obj = Rosenbrock(gammadia, gammasum, alphasum, a, c, m, me, order, embeddedOrder) + function obj = Rosenbrock(gammadia, gammasum, alphasum, a, c, m, e, order, embeddedOrder) - obj = obj@matlode.OneStepIntegrator(~isempty(me), class(gammadia)); + obj = obj@matlode.OneStepIntegrator(~isempty(e), class(gammadia)); obj.GammaDia = gammadia; obj.GammaSum = gammasum; @@ -97,13 +103,14 @@ obj.A = a; obj.C = c; obj.M = m; - obj.E = me; + obj.E = e; obj.EmbeddedOrder = embeddedOrder; obj.Order = order; obj.StageNum = size(obj.M, 2); obj.FSAL = all(obj.A(end, :) == obj.M) && all(obj.A(1, :) == 0) && all(obj.C(1, :) == 0) && obj.AlphaSum(1) == 0; obj.FsalStart = uint32(obj.FSAL) + 1; + obj.DenseOut = matlode.denseoutput.Linear(obj.M); end end diff --git a/src/+testingscripts/+OTPtest/TestERK.m b/src/+testingscripts/+OTPtest/TestERK.m index dadc72e..cd5e66c 100644 --- a/src/+testingscripts/+OTPtest/TestERK.m +++ b/src/+testingscripts/+OTPtest/TestERK.m @@ -2,16 +2,16 @@ format long e close all -integrator = matlode.rk.erk.DormandPrince(); +integrator = matlode.rk.erk.Vener87; -options.ErrNorm = matlode.errnorm.InfNorm(1e-12, 1e-12); +options.ErrNorm = matlode.errnorm.InfNorm(1e-8, 1e-8); options.StepSizeController = matlode.stepsizecontroller.StandardController; problem = otp.linear.presets.Canonical; sol = integrator.integrate(problem.RHS, problem.TimeSpan, problem.Y0, options); -sol_matlab = problem.solve('RelTol', 1e-12, 'AbsTol', 1e-12); +sol_matlab = problem.solve('RelTol', 1e-8, 'AbsTol', 1e-8); true_sol = exp(-problem.TimeSpan(end)); diff --git a/src/+testingscripts/+OTPtest/TestROS.m b/src/+testingscripts/+OTPtest/TestROS.m index 3596277..bfb7aa5 100644 --- a/src/+testingscripts/+OTPtest/TestROS.m +++ b/src/+testingscripts/+OTPtest/TestROS.m @@ -2,7 +2,7 @@ format long e close all -integrator = matlode.rosenbrock.RODAS3(); +integrator = matlode.rosenbrock.ROS34PW2(); options.ErrNorm = matlode.errnorm.InfNorm(1e-12, 1e-12); options.StepSizeController = matlode.stepsizecontroller.StandardController; diff --git a/src/+testingscripts/+simpletest/Test_ERK.m b/src/+testingscripts/+simpletest/Test_ERK.m index 1c9e2a2..d8a07df 100644 --- a/src/+testingscripts/+simpletest/Test_ERK.m +++ b/src/+testingscripts/+simpletest/Test_ERK.m @@ -4,7 +4,7 @@ integrator = matlode.rk.erk.DormandPrince(); -options.ErrNorm = matlode.errnorm.InfNorm(1e-6, 1e-6); +options.ErrNorm = matlode.errnorm.InfNorm(1e-12, 1e-12); options.StepSizeController = matlode.stepsizecontroller.StandardController; lambda = -1; From c99229f7aa272b5ff773e1dacd876b77c9198207 Mon Sep 17 00:00:00 2001 From: reid-g Date: Fri, 4 Nov 2022 14:23:38 -0400 Subject: [PATCH 10/17] Improved Performance of Linear Solver +Improved Linear Solver Performance +LU Direct Decomposition +Matrix Free Solver +Add basic Order Verification --- .../+linearsolver/DecompositionLinearSolver.m | 10 ++- src/+matlode/+linearsolver/LUDecomposition.m | 30 +++++++ .../+linearsolver/MatrixFreeLinearSolver.m | 39 ++++++++-- .../+linearsolver/MatrixLinearSolver.m | 27 +++++-- src/+matlode/+rk/RungeKutta.m | 2 +- src/+matlode/+util/OrderChecking.m | 14 ++-- src/+matlode/Integrator.m | 78 ++----------------- src/+matlode/Model.m | 16 +++- src/+matlode/OneStepIntegrator.m | 15 ++-- src/+testingscripts/+OTPtest/TestERK.m | 6 +- src/+testingscripts/+OTPtest/TestROS.m | 8 +- .../+linsolvertest/TestDecompLinSolver.m | 17 ++++ .../+linsolvertest/TestLULinSolver.m | 18 +++++ .../+linsolvertest/TestMatrixFreeSolver.m | 21 +++++ .../+linsolvertest/TestMatrixLinSolver.m | 21 +++++ .../+orderchecking/ERKOrderTest.m | 28 +++++++ 16 files changed, 239 insertions(+), 111 deletions(-) create mode 100644 src/+matlode/+linearsolver/LUDecomposition.m create mode 100644 src/+testingscripts/+linsolvertest/TestDecompLinSolver.m create mode 100644 src/+testingscripts/+linsolvertest/TestLULinSolver.m create mode 100644 src/+testingscripts/+linsolvertest/TestMatrixFreeSolver.m create mode 100644 src/+testingscripts/+linsolvertest/TestMatrixLinSolver.m diff --git a/src/+matlode/+linearsolver/DecompositionLinearSolver.m b/src/+matlode/+linearsolver/DecompositionLinearSolver.m index 3c7bf35..82c3059 100644 --- a/src/+matlode/+linearsolver/DecompositionLinearSolver.m +++ b/src/+matlode/+linearsolver/DecompositionLinearSolver.m @@ -16,11 +16,15 @@ obj.DecompositionArgs = decompArgs; end - function [stats] = preprocess(obj, t, y, reeval, mass_scale, jac_scale, stats) + function [stats] = preprocess(obj, f, t, y, reeval, mass_scale, jac_scale, stats) - [stats] = preprocess@matlode.linearsolver.MatrixLinearSolver(obj, t, y, reeval, mass_scale, jac_scale, stats); + [stats] = preprocess@matlode.linearsolver.MatrixLinearSolver(obj, f, t, y, reeval, mass_scale, jac_scale, stats); - obj.system = decomposition(obj.system, obj.DecompositionType, obj.DecompositionArgs); + if isempty(obj.DecompositionArgs) + obj.system = decomposition(obj.system, obj.DecompositionType); + else + obj.system = decomposition(obj.system, obj.DecompositionType, obj.DecompositionArgs); + end stats.nDecompositions = stats.nDecompositions + 1; end diff --git a/src/+matlode/+linearsolver/LUDecomposition.m b/src/+matlode/+linearsolver/LUDecomposition.m new file mode 100644 index 0000000..7b2cac6 --- /dev/null +++ b/src/+matlode/+linearsolver/LUDecomposition.m @@ -0,0 +1,30 @@ +classdef LUDecomposition < matlode.linearsolver.MatrixLinearSolver + + methods + function obj = LUDecomposition() + obj = obj@matlode.linearsolver.MatrixLinearSolver(@mldivide, {}); + end + + function [stats] = preprocess(obj, f, t, y, reeval, mass_scale, jac_scale, stats) + + [stats] = preprocess@matlode.linearsolver.MatrixLinearSolver(obj, f, t, y, reeval, mass_scale, jac_scale, stats); + + if issparse(obj.system) + [L,U,P,Q,D] = lu(obj.system); + obj.system = @(x) Q*(U\(L\(P*(D \ x)))); + else + [L,U,P] = lu(obj.system); + obj.system = @(x) U\(L\(P*x)); + end + + stats.nDecompositions = stats.nDecompositions + 1; + end + + function [sol, stats] = solve(obj, x, stats) + sol = obj.system(x); + + stats.nLinearSolves = stats.nLinearSolves + 1; + end + end +end + diff --git a/src/+matlode/+linearsolver/MatrixFreeLinearSolver.m b/src/+matlode/+linearsolver/MatrixFreeLinearSolver.m index c330a4a..6c9393e 100644 --- a/src/+matlode/+linearsolver/MatrixFreeLinearSolver.m +++ b/src/+matlode/+linearsolver/MatrixFreeLinearSolver.m @@ -10,22 +10,47 @@ obj = obj@matlode.linearsolver.LinearSolver(solver, solverArgs{:}); end - function [stats] = preprocess(obj, t, y, reeval, mass_scale, jac_scale, stats) + function [stats] = preprocess(obj, f, t, y, reeval, mass_scale, jac_scale, stats) if reeval - obj.mass = @(v) f.MVectorProduct(t, y, v); - stats.nMassEvals = stats.nMassEvals + 1; + if ~isempty(f.JacobianVectorProduct) + obj.jac = @(v) f.JacobianVectorProduct(t, y, v); + stats.nJacobianEvals = stats.nJacobianEvals + 1; + else + if f.JLinearOperatorType ~= matlode.LinearOperatorType.Identity && f.JLinearOperatorType ~= matlode.LinearOperatorType.Constant + obj.jac = @(v) f.Jacobian(t, y) * v; + stats.nJacobianEvals = stats.nJacobianEvals + 1; + elseif isempty(obj.jac) + obj.jac = @(v) f.Jacobian * v; + end + end - obj.jac = @(v) f.JVectorProduct(t, y, v); - stats.nJacobianEvals = stats.nJacobianEvals + 1; + + if ~isempty(f.MVectorProduct) + obj.mass = @(v) f.MVectorProduct(t, y, v); + stats.nMassEvals = stats.nMassEvals + 1; + elseif f.MLinearOperatorType ~= matlode.LinearOperatorType.Empty + if f.MLinearOperatorType ~= matlode.LinearOperatorType.Identity && f.MLinearOperatorType ~= matlode.LinearOperatorType.Constant + obj.mass = @(v) f.Mass(t, y) * v; + stats.nMassEvals = stats.nMassEvals + 1; + else + if isempty(obj.mass) + obj.mass = @(v) f.Mass * v; + end + end + else + if isempty(obj.mass) + obj.mass = @(v) speye(size(y,1)) * v; + end + end end obj.system = @(v) mass_scale * obj.mass(v) + jac_scale * obj.jac(v); end function [sol, stats] = solve(obj, x, stats) - sol = obj.Solver(obj.system, x, obj.SolverArgs{:}); + [sol, flag, relres, iter] = obj.Solver(obj.system, x, obj.SolverArgs{:}); - stats.nLinearSolve = stats.nLinearSolve + 1; + stats.nLinearSolves = stats.nLinearSolves + 1; end end end diff --git a/src/+matlode/+linearsolver/MatrixLinearSolver.m b/src/+matlode/+linearsolver/MatrixLinearSolver.m index c8d6847..3fbf578 100644 --- a/src/+matlode/+linearsolver/MatrixLinearSolver.m +++ b/src/+matlode/+linearsolver/MatrixLinearSolver.m @@ -13,19 +13,32 @@ function [stats] = preprocess(obj, f, t, y, reeval, mass_scale, jac_scale, stats) if reeval - if f.MLinearOperatorType ~= matlode.LinearOperatorType.Identity && f.MLinearOperatorType ~= matlode.LinearOperatorType.Constant - obj.mass = f.Mass(t, y); - stats.nMassEvals = stats.nMassEvals + 1; - elseif isempty(obj.mass) - obj.mass = f.Mass; - end - if f.JLinearOperatorType ~= matlode.LinearOperatorType.Identity && f.JLinearOperatorType ~= matlode.LinearOperatorType.Constant obj.jac = f.Jacobian(t, y); stats.nJacobianEvals = stats.nJacobianEvals + 1; elseif isempty(obj.jac) obj.jac = f.Jacobian; end + + + if f.MLinearOperatorType ~= matlode.LinearOperatorType.Empty + if f.MLinearOperatorType ~= matlode.LinearOperatorType.Identity && f.MLinearOperatorType ~= matlode.LinearOperatorType.Constant + obj.mass = f.Mass(t, y); + stats.nMassEvals = stats.nMassEvals + 1; + else + if isempty(obj.mass) + obj.mass = f.Mass; + end + end + else + if isempty(obj.mass) + if issparse(obj.jac) + obj.mass = speye(length(obj.jac)); + else + obj.mass = eye(length(obj.jac)); + end + end + end end obj.system = mass_scale * obj.mass + jac_scale * obj.jac; diff --git a/src/+matlode/+rk/RungeKutta.m b/src/+matlode/+rk/RungeKutta.m index 4fba523..97bbee1 100644 --- a/src/+matlode/+rk/RungeKutta.m +++ b/src/+matlode/+rk/RungeKutta.m @@ -74,7 +74,7 @@ if obj.FSAL if isempty(f0) stages(:, end) = f.F(t0, y0); - stats.FEvals = stats.FEvals + 1; + stats.nFevals = stats.nFevals + 1; else stages(:, end) = f0; end diff --git a/src/+matlode/+util/OrderChecking.m b/src/+matlode/+util/OrderChecking.m index 6cc8ab9..3cd94bc 100644 --- a/src/+matlode/+util/OrderChecking.m +++ b/src/+matlode/+util/OrderChecking.m @@ -13,13 +13,15 @@ function [error, statsCumlative] = getErrorWRTExactValue(obj, model, t0, tf, y0, ytrue, stepamount, opts) - error = zeros(1,length(step_amount)); - statsCumlative = cell(1, length(step_amount)); + error = zeros(1,length(stepamount)); + statsCumlative = cell(1, length(stepamount)); + - for i = length(error) - steps = t0:((tf - t0) / stepamount):tf; - [~, yf, statsCumlative{i}] = obj.Integrator.timeLoopFixed(model, steps, y0, opts); - error(i) = norm(yf - ytrue) / norm(ytrue); + for i = 1:length(error) + steps = t0:((tf - t0) / stepamount(i)):tf; + sol = obj.Integrator.integrateFixed(model, steps, y0, opts); + statsCumlative{i} = sol.stats; + error(i) = norm(sol.y(end) - ytrue) / norm(ytrue); end end diff --git a/src/+matlode/Integrator.m b/src/+matlode/Integrator.m index d3d4d15..797e712 100644 --- a/src/+matlode/Integrator.m +++ b/src/+matlode/Integrator.m @@ -52,53 +52,17 @@ p.KeepUnmatched = true; opts = obj.matlodeSets(p, varargin{:}); - t = []; - y = []; - stats = []; - new_model = false; - - %Compute - if isa(f, 'matlode.Model') - if isempty(f.Mass) - f.Mass = eye(length(y0)); - end - - [t, y, stats] = obj.timeLoop(f, tspan, y0, opts); - elseif isa(f, 'function_handle') || isa(f, 'cell') - if isa(f, 'cell') && ~obj.PartitionMethod - error('This method does not support partitioning') - end - f_mod = matlode.Model(f, varargin{:}); - new_model = true; - if isempty(f_mod.Mass) - f_mod.Mass = eye(length(y0)); - end - - [t, y, stats] = obj.timeLoop(f_mod, tspan, y0, opts); + fmod = matlode.Model(f, varargin{:}); - elseif isa(f, 'otp.RHS') - f_mod = matlode.Model(f, varargin{:}); - new_model = true; - if isempty(f_mod.Mass) - f_mod.Mass = eye(length(y0)); - end + [t, y, stats] = obj.timeLoop(fmod, tspan, y0, opts); - [t, y, stats] = obj.timeLoop(f_mod, tspan, y0, opts); - - else - error('f must be a ''function_handle'', ''cell'', OTP RHS, or Model object'); - end sol.t = t; sol.y = y; sol.stats = stats; - if new_model - sol.model = f_mod; - else - sol.model = f; - end + sol.model = fmod; end %Integrate over a fixed interval @@ -118,46 +82,16 @@ p = inputParser(); p.KeepUnmatched = true; opts = obj.matlodeSets(p, varargin{:}); - - - t = []; - y = []; - stats = []; - new_model = false; - - %Compute - if isa(f, 'matlode.Model') - - [t, y, stats] = obj.timeLoopFixed(f, tspan, y0, opts); - elseif isa(f, 'function_handle') || isa(f, 'cell') - if isa(f, 'cell') && ~obj.PartitionMethod - error('This method does not support partitioning') - end - f_mod = matlode.Model(f, varargin{:}); - new_model = true; - - [t, y, stats] = obj.timeLoopFixed(f_mod, tspan, y0, opts); - - elseif isa(f, 'otp.RHS') - f_mod = matlode.Model(f, varargin{:}); - new_model = true; + fmod = matlode.Model(f, varargin{:}); - [t, y, stats] = obj.timeLoopFixed(f_mod, tspan, y0, opts); - - else - error('f must be a ''function_handle'', ''cell'', OTP RHS, or Model object'); - end + [t, y, stats] = obj.timeLoop(fmod, tspan, y0, opts); sol.t = t; sol.y = y; sol.stats = stats; - if new_model - sol.model = f_mod; - else - sol.model = f; - end + sol.model = fmod; end end diff --git a/src/+matlode/Model.m b/src/+matlode/Model.m index 2f7e153..ae1d1eb 100644 --- a/src/+matlode/Model.m +++ b/src/+matlode/Model.m @@ -70,7 +70,7 @@ p.addParameter('Vectorized', []); p.addParameter('Mass', []); - p.addParameter('MLinearOperatorType', matlode.LinearOperatorType.Identity); + p.addParameter('MLinearOperatorType', matlode.LinearOperatorType.Empty); p.addParameter('MassSingular', false); p.addParameter('MStateDependence', false); p.addParameter('MvPattern', []); @@ -99,7 +99,7 @@ p.addParameter('PDTOperatorType', matlode.OperatorType.Zero); p.addParameter('PDPOperatorType', matlode.OperatorType.Zero); - p.addParameter('MLinearOperatorType', matlode.LinearOperatorType.Identity); + p.addParameter('MLinearOperatorType', matlode.LinearOperatorType.Empty); p.addParameter('JLinearOperatorType', matlode.LinearOperatorType.Empty); p.addParameter('FOperatorType', matlode.OperatorType.TSDep); @@ -191,17 +191,25 @@ obj = obj.opTypeSet('PartialDerivativeTime', 'PDTOperatorType', matlode.OperatorType.Zero, matlode.OperatorType.TSDep); obj = obj.opTypeSet('Jacobian', 'JLinearOperatorType', matlode.LinearOperatorType.Empty, matlode.LinearOperatorType.TSDepedent); + if obj.JLinearOperatorType == matlode.LinearOperatorType.Empty && ~isempty(obj.JacobianVectorProduct) + obj.JLinearOperatorType = matlode.LinearOperatorType.TSDepedent; + end % Currently only support constant Mass % TODO: Support Time Dependent Mass % TODO: Support State Dependent Mass - obj = obj.opTypeSet('Mass', 'MLinearOperatorType', matlode.LinearOperatorType.Identity, matlode.LinearOperatorType.Constant); + obj = obj.opTypeSet('Mass', 'MLinearOperatorType', matlode.LinearOperatorType.Empty, matlode.LinearOperatorType.Constant); + if obj.MLinearOperatorType == matlode.LinearOperatorType.Empty && ~isempty(obj.MVectorProduct) + obj.MLinearOperatorType = matlode.LinearOperatorType.TSDepedent; + end switch(obj.MLinearOperatorType) case matlode.LinearOperatorType.Zero case matlode.LinearOperatorType.Identity case matlode.LinearOperatorType.Constant otherwise - error('Not Supported Functionality') + if strcmp(obj.MassSingular, 'yes') + error('Non-Constant Singular Mass Matrices are not Supported') + end end end diff --git a/src/+matlode/OneStepIntegrator.m b/src/+matlode/OneStepIntegrator.m index d74e469..ff51068 100644 --- a/src/+matlode/OneStepIntegrator.m +++ b/src/+matlode/OneStepIntegrator.m @@ -202,13 +202,13 @@ function [t, y, stats] = timeLoopFixed(obj, f, tspan, y0, opts) - if opts.FullTrajectory + if opts.FullTrajectory y = zeros(length(y0), length(tspan)); y(:, 1) = y0; t = tspan; else y = zeros(length(y0), 2); - t = tspan; + t = zeros(size(tspan,1),2); end t(:,1) = tspan(:,1); @@ -221,8 +221,8 @@ stats = obj.intalizeStats; stats.nSteps = length(tspan); - - [stages, stats] = obj.timeLoopBeforeLoop(f0, tspan(1), y0, stats); + + [stages, stats] = obj.timeLoopBeforeLoop(f, [], tspan(1), y0, stats); %Time Loop for i = 1:(length(tspan)-1) @@ -232,14 +232,18 @@ [ynext, stages, stats] = timeStep(obj, f, tcur, yi, dtc, stages, true, stats); - if opts.FullTrajectory || i == (length(tspan)-1) + if opts.FullTrajectory y(:, i + 1) = ynext; end end + + y(:, end) = ynext; end + %Statistics intialization function stats = intalizeStats(obj) + stats.nFevals = 0; stats.nSteps = 0; stats.nFailed = 0; stats.nSmallSteps = 0; @@ -247,6 +251,7 @@ stats.nMassEvals = 0; stats.nJacobianEvals = 0; stats.nPDTEval = 0; + stats.nDecompositions = 0; end end diff --git a/src/+testingscripts/+OTPtest/TestERK.m b/src/+testingscripts/+OTPtest/TestERK.m index cd5e66c..8d79875 100644 --- a/src/+testingscripts/+OTPtest/TestERK.m +++ b/src/+testingscripts/+OTPtest/TestERK.m @@ -2,16 +2,16 @@ format long e close all -integrator = matlode.rk.erk.Vener87; +integrator = matlode.rk.erk.DormandPrince; -options.ErrNorm = matlode.errnorm.InfNorm(1e-8, 1e-8); +options.ErrNorm = matlode.errnorm.InfNorm(1e-12, 1e-12); options.StepSizeController = matlode.stepsizecontroller.StandardController; problem = otp.linear.presets.Canonical; sol = integrator.integrate(problem.RHS, problem.TimeSpan, problem.Y0, options); -sol_matlab = problem.solve('RelTol', 1e-8, 'AbsTol', 1e-8); +sol_matlab = problem.solve('Solver', @ode45, 'RelTol', 1e-12, 'AbsTol', 1e-12); true_sol = exp(-problem.TimeSpan(end)); diff --git a/src/+testingscripts/+OTPtest/TestROS.m b/src/+testingscripts/+OTPtest/TestROS.m index bfb7aa5..7f04cb2 100644 --- a/src/+testingscripts/+OTPtest/TestROS.m +++ b/src/+testingscripts/+OTPtest/TestROS.m @@ -4,14 +4,16 @@ integrator = matlode.rosenbrock.ROS34PW2(); -options.ErrNorm = matlode.errnorm.InfNorm(1e-12, 1e-12); +problem = otp.allencahn.presets.Canonical; + +options.ErrNorm = matlode.errnorm.InfNorm(1e-6, 1e-6); options.StepSizeController = matlode.stepsizecontroller.StandardController; +options.Jacobian = problem.RHS.Jacobian(problem.TimeSpan(1),problem.Y0); -problem = otp.linear.presets.Canonical; sol = integrator.integrate(problem.RHS, problem.TimeSpan, problem.Y0, options); -sol_matlab = problem.solve('RelTol', 1e-12, 'AbsTol', 1e-12); +sol_matlab = problem.solve('RelTol', 1e-8, 'AbsTol', 1e-8); true_sol = exp(-problem.TimeSpan(end)); diff --git a/src/+testingscripts/+linsolvertest/TestDecompLinSolver.m b/src/+testingscripts/+linsolvertest/TestDecompLinSolver.m new file mode 100644 index 0000000..3f878df --- /dev/null +++ b/src/+testingscripts/+linsolvertest/TestDecompLinSolver.m @@ -0,0 +1,17 @@ +clear +format long e +close all + +integrator = matlode.rosenbrock.RODAS4(); + +problem = otp.allencahn.presets.Canonical; + +options.ErrNorm = matlode.errnorm.InfNorm(1e-6, 1e-6); +options.StepSizeController = matlode.stepsizecontroller.StandardController; +options.LinearSolver = matlode.linearsolver.DecompositionLinearSolver; + +sol = integrator.integrate(problem.RHS, problem.TimeSpan, problem.Y0, options); + +sol_matlab = problem.solve('Solver', @ode23t, 'RelTol', 1e-6, 'AbsTol', 1e-6); + +norm(sol.y(end) - sol_matlab.y(end)) diff --git a/src/+testingscripts/+linsolvertest/TestLULinSolver.m b/src/+testingscripts/+linsolvertest/TestLULinSolver.m new file mode 100644 index 0000000..e7df0d9 --- /dev/null +++ b/src/+testingscripts/+linsolvertest/TestLULinSolver.m @@ -0,0 +1,18 @@ +clear +format long e +close all + +integrator = matlode.rosenbrock.RODAS4(); + +problem = otp.allencahn.presets.Canonical; + +options.ErrNorm = matlode.errnorm.InfNorm(1e-6, 1e-6); +options.StepSizeController = matlode.stepsizecontroller.StandardController; +options.LinearSolver = matlode.linearsolver.LUDecomposition; + + +sol = integrator.integrate(problem.RHS, problem.TimeSpan, problem.Y0, options); + +sol_matlab = problem.solve('Solver', @ode23t, 'RelTol', 1e-6, 'AbsTol', 1e-6); + +norm(sol.y(end) - sol_matlab.y(end)) diff --git a/src/+testingscripts/+linsolvertest/TestMatrixFreeSolver.m b/src/+testingscripts/+linsolvertest/TestMatrixFreeSolver.m new file mode 100644 index 0000000..75f072c --- /dev/null +++ b/src/+testingscripts/+linsolvertest/TestMatrixFreeSolver.m @@ -0,0 +1,21 @@ +clear +format long e +close all + +integrator = matlode.rosenbrock.RODAS4; + +problem = otp.allencahn.presets.Canonical; + +options.ErrNorm = matlode.errnorm.InfNorm(1e-8, 1e-8); +options.StepSizeController = matlode.stepsizecontroller.StandardController; +options.LinearSolver = matlode.linearsolver.MatrixFreeLinearSolver; + +tic + +sol = integrator.integrate(problem.RHS, problem.TimeSpan, problem.Y0, options); + +toc + +sol_matlab = problem.solve('Solver', @ode23t ,'RelTol', 1e-8, 'AbsTol', 1e-8); + +norm(sol.y(end) - sol_matlab.y(end)) diff --git a/src/+testingscripts/+linsolvertest/TestMatrixLinSolver.m b/src/+testingscripts/+linsolvertest/TestMatrixLinSolver.m new file mode 100644 index 0000000..6ba4e16 --- /dev/null +++ b/src/+testingscripts/+linsolvertest/TestMatrixLinSolver.m @@ -0,0 +1,21 @@ +clear +format long e +close all + +integrator = matlode.rosenbrock.ROS34PW2(); + +problem = otp.allencahn.presets.Canonical; + +options.ErrNorm = matlode.errnorm.InfNorm(1e-6, 1e-6); +options.StepSizeController = matlode.stepsizecontroller.StandardController; +options.Jacobian = problem.RHS.Jacobian(problem.TimeSpan(1),problem.Y0); + +tic + +sol = integrator.integrate(problem.RHS, problem.TimeSpan, problem.Y0, options); + +toc + +sol_matlab = problem.solve('RelTol', 1e-12, 'AbsTol', 1e-12); + +norm(sol.y(end) - sol_matlab.y(end)) diff --git a/src/+testingscripts/+orderchecking/ERKOrderTest.m b/src/+testingscripts/+orderchecking/ERKOrderTest.m index e69de29..22528fe 100644 --- a/src/+testingscripts/+orderchecking/ERKOrderTest.m +++ b/src/+testingscripts/+orderchecking/ERKOrderTest.m @@ -0,0 +1,28 @@ +clear +format long e +close all + +integrator = matlode.rk.erk.DormandPrince(); + +options.ErrNorm = matlode.errnorm.InfNorm(1e-12, 1e-12); +options.StepSizeController = matlode.stepsizecontroller.StandardController; + +lambda = -1; +test_problem = testingscripts.testproblems.ODE.EulerProblem(lambda); + +t_f = 1; + +order_checker = matlode.util.OrderChecking(integrator); + +stepamount = 2.^(5:8); +fig_num = 1; + +[error, ~] = order_checker.getErrorWRTExactValue(@(t,y) test_problem.f(t,y), test_problem.t_0, t_f, test_problem.y_0, test_problem.y_exact(t_f), stepamount, options); + +[f, polyError] = order_checker.plot_error_single(stepamount, error, fig_num) + + +sol = integrator.integrate(@(t,y) test_problem.f(t,y), [test_problem.t_0, t_f], test_problem.y_0, options); + + +norm(sol.y(end) - test_problem.y_exact(t_f)) \ No newline at end of file From 1be04080b5b4e1adbc706c6d325e1ccb724a8737 Mon Sep 17 00:00:00 2001 From: reid-g Date: Tue, 18 Apr 2023 16:16:49 -0400 Subject: [PATCH 11/17] Simplified the Operator Check +Simplified Operator Checking with Model --- .../+linearsolver/MatrixFreeLinearSolver.m | 33 ++++---- .../+linearsolver/MatrixLinearSolver.m | 35 ++++----- src/+matlode/+nonlinearsolver/Broyden.m | 23 ++++++ src/+matlode/+nonlinearsolver/Chord.m | 23 ++++++ src/+matlode/+nonlinearsolver/Newton.m | 26 +++++++ .../+nonlinearsolver/NonlinearSolver.m | 35 +++++++++ src/+matlode/+nonlinearsolver/QuasiNewton.m | 23 ++++++ src/+matlode/+rk/+dirk/BackwardEuler.m | 23 ++++++ src/+matlode/+rk/+dirk/DIRK.m | 62 +++++++++++++++ src/+matlode/+rk/+erk/ERK.m | 18 +---- src/+matlode/+rk/RungeKutta.m | 16 ++++ src/+matlode/+rosenbrock/Rosenbrock.m | 4 +- src/+matlode/LinearOperatorType.m | 11 --- src/+matlode/Model.m | 75 ++----------------- src/+matlode/OperatorType.m | 9 --- 15 files changed, 276 insertions(+), 140 deletions(-) create mode 100644 src/+matlode/+nonlinearsolver/Broyden.m create mode 100644 src/+matlode/+nonlinearsolver/Chord.m create mode 100644 src/+matlode/+nonlinearsolver/Newton.m create mode 100644 src/+matlode/+nonlinearsolver/NonlinearSolver.m create mode 100644 src/+matlode/+nonlinearsolver/QuasiNewton.m create mode 100644 src/+matlode/+rk/+dirk/BackwardEuler.m create mode 100644 src/+matlode/+rk/+dirk/DIRK.m delete mode 100644 src/+matlode/LinearOperatorType.m delete mode 100644 src/+matlode/OperatorType.m diff --git a/src/+matlode/+linearsolver/MatrixFreeLinearSolver.m b/src/+matlode/+linearsolver/MatrixFreeLinearSolver.m index 6c9393e..a4e476f 100644 --- a/src/+matlode/+linearsolver/MatrixFreeLinearSolver.m +++ b/src/+matlode/+linearsolver/MatrixFreeLinearSolver.m @@ -12,34 +12,39 @@ function [stats] = preprocess(obj, f, t, y, reeval, mass_scale, jac_scale, stats) if reeval + + if ~isempty(f.JacobianVectorProduct) obj.jac = @(v) f.JacobianVectorProduct(t, y, v); stats.nJacobianEvals = stats.nJacobianEvals + 1; else - if f.JLinearOperatorType ~= matlode.LinearOperatorType.Identity && f.JLinearOperatorType ~= matlode.LinearOperatorType.Constant - obj.jac = @(v) f.Jacobian(t, y) * v; + if isa(f.Jacobian,'double') + %TODO: Replace with Data Type + if isempty(obj.jac) + obj.jac = @(v) f.Jacobian * v; + end + else + obj.jac = @(v) f.Jacobian(t, y) * v; stats.nJacobianEvals = stats.nJacobianEvals + 1; - elseif isempty(obj.jac) - obj.jac = @(v) f.Jacobian * v; end end - if ~isempty(f.MVectorProduct) obj.mass = @(v) f.MVectorProduct(t, y, v); stats.nMassEvals = stats.nMassEvals + 1; - elseif f.MLinearOperatorType ~= matlode.LinearOperatorType.Empty - if f.MLinearOperatorType ~= matlode.LinearOperatorType.Identity && f.MLinearOperatorType ~= matlode.LinearOperatorType.Constant - obj.mass = @(v) f.Mass(t, y) * v; - stats.nMassEvals = stats.nMassEvals + 1; - else + else + if isempty(f.Mass) + if isempty(obj.mass) + obj.mass = @(v) v; + end + elseif isa(f.Mass, 'double') + %TODO: Replace with Data Type if isempty(obj.mass) obj.mass = @(v) f.Mass * v; end - end - else - if isempty(obj.mass) - obj.mass = @(v) speye(size(y,1)) * v; + else + obj.mass = @(v) f.Mass(t,y) * v; + stats.nMassEvals = stats.nMassEvals + 1; end end end diff --git a/src/+matlode/+linearsolver/MatrixLinearSolver.m b/src/+matlode/+linearsolver/MatrixLinearSolver.m index 3fbf578..842ec41 100644 --- a/src/+matlode/+linearsolver/MatrixLinearSolver.m +++ b/src/+matlode/+linearsolver/MatrixLinearSolver.m @@ -13,31 +13,32 @@ function [stats] = preprocess(obj, f, t, y, reeval, mass_scale, jac_scale, stats) if reeval - if f.JLinearOperatorType ~= matlode.LinearOperatorType.Identity && f.JLinearOperatorType ~= matlode.LinearOperatorType.Constant - obj.jac = f.Jacobian(t, y); + if isa(f.Jacobian,'double') + %TODO: Replace with Data Type + if isempty(obj.jac) + obj.jac = f.Jacobian; + end + else + obj.jac = f.Jacobian(t, y); stats.nJacobianEvals = stats.nJacobianEvals + 1; - elseif isempty(obj.jac) - obj.jac = f.Jacobian; end - - if f.MLinearOperatorType ~= matlode.LinearOperatorType.Empty - if f.MLinearOperatorType ~= matlode.LinearOperatorType.Identity && f.MLinearOperatorType ~= matlode.LinearOperatorType.Constant - obj.mass = f.Mass(t, y); - stats.nMassEvals = stats.nMassEvals + 1; - else - if isempty(obj.mass) - obj.mass = f.Mass; - end - end - else + if isempty(f.Mass) if isempty(obj.mass) if issparse(obj.jac) - obj.mass = speye(length(obj.jac)); + obj.mass = speye(length(y)); else - obj.mass = eye(length(obj.jac)); + obj.mass = eye(length(y)); end end + elseif isa(f.Mass, 'double') + %TODO: Replace with Data Type + if isempty(obj.mass) + obj.mass = f.Mass; + end + else + obj.mass = f.Mass(t,y); + stats.nMassEvals = stats.nMassEvals + 1; end end diff --git a/src/+matlode/+nonlinearsolver/Broyden.m b/src/+matlode/+nonlinearsolver/Broyden.m new file mode 100644 index 0000000..1cb924a --- /dev/null +++ b/src/+matlode/+nonlinearsolver/Broyden.m @@ -0,0 +1,23 @@ +classdef untitled + %UNTITLED Summary of this class goes here + % Detailed explanation goes here + + properties + Property1 + end + + methods + function obj = untitled(inputArg1,inputArg2) + %UNTITLED Construct an instance of this class + % Detailed explanation goes here + obj.Property1 = inputArg1 + inputArg2; + end + + function outputArg = method1(obj,inputArg) + %METHOD1 Summary of this method goes here + % Detailed explanation goes here + outputArg = obj.Property1 + inputArg; + end + end +end + diff --git a/src/+matlode/+nonlinearsolver/Chord.m b/src/+matlode/+nonlinearsolver/Chord.m new file mode 100644 index 0000000..9082398 --- /dev/null +++ b/src/+matlode/+nonlinearsolver/Chord.m @@ -0,0 +1,23 @@ +classdef Chord + %CHORD Summary of this class goes here + % Detailed explanation goes here + + properties + Property1 + end + + methods + function obj = Chord(inputArg1,inputArg2) + %CHORD Construct an instance of this class + % Detailed explanation goes here + obj.Property1 = inputArg1 + inputArg2; + end + + function outputArg = method1(obj,inputArg) + %METHOD1 Summary of this method goes here + % Detailed explanation goes here + outputArg = obj.Property1 + inputArg; + end + end +end + diff --git a/src/+matlode/+nonlinearsolver/Newton.m b/src/+matlode/+nonlinearsolver/Newton.m new file mode 100644 index 0000000..d7996e1 --- /dev/null +++ b/src/+matlode/+nonlinearsolver/Newton.m @@ -0,0 +1,26 @@ +classdef Newton < matlode.nonlinearsolver.NonlinearSolver + %NEWTON Summary of this class goes here + % Detailed explanation goes here + + properties + Property1 + end + + methods + function obj = Newton(linsolve, args) + arguments + linsolve(1,1) matlode.linearsolver.LinearSolver = {}; + args(1,:) cell = {}; + end + + obj = obj@matlode.nonlinearsolver.NonlinearSolver(linsolve, args{:}); + end + + function outputArg = method1(obj,inputArg) + %METHOD1 Summary of this method goes here + % Detailed explanation goes here + outputArg = obj.Property1 + inputArg; + end + end +end + diff --git a/src/+matlode/+nonlinearsolver/NonlinearSolver.m b/src/+matlode/+nonlinearsolver/NonlinearSolver.m new file mode 100644 index 0000000..59b10a8 --- /dev/null +++ b/src/+matlode/+nonlinearsolver/NonlinearSolver.m @@ -0,0 +1,35 @@ +classdef NonlinearSolver < handle + % Abstract framework for the + + properties (SetAccess = immutable) + NonLinearArgs + end + + properties (SetAccess = protected) + LinearSolver + end + + methods + function obj = NonlinearSolver(linsolve, args, maxiter, tolerance) + arguments + linsolve(1,1) matlode.linearsolver.LinearSolver = {}; + args(1,:) cell = {}; + maxiter(1,1) int64 = 100 + tolerance(1,1) double = 1e-8 + end + + obj.NonLinearArgs = args; + obj.LinearSolver = linsolve; + end + + function obj = SetLinearSolver(obj,linsolve) + obj.LinearSolver = linsolve; + end + end + + methods(Abstract) + + [xf, optout, stats] = solve(nonlinsystem, f, mass_scale, jac_scale, x0, optin); + end +end + diff --git a/src/+matlode/+nonlinearsolver/QuasiNewton.m b/src/+matlode/+nonlinearsolver/QuasiNewton.m new file mode 100644 index 0000000..1cb924a --- /dev/null +++ b/src/+matlode/+nonlinearsolver/QuasiNewton.m @@ -0,0 +1,23 @@ +classdef untitled + %UNTITLED Summary of this class goes here + % Detailed explanation goes here + + properties + Property1 + end + + methods + function obj = untitled(inputArg1,inputArg2) + %UNTITLED Construct an instance of this class + % Detailed explanation goes here + obj.Property1 = inputArg1 + inputArg2; + end + + function outputArg = method1(obj,inputArg) + %METHOD1 Summary of this method goes here + % Detailed explanation goes here + outputArg = obj.Property1 + inputArg; + end + end +end + diff --git a/src/+matlode/+rk/+dirk/BackwardEuler.m b/src/+matlode/+rk/+dirk/BackwardEuler.m new file mode 100644 index 0000000..1cb924a --- /dev/null +++ b/src/+matlode/+rk/+dirk/BackwardEuler.m @@ -0,0 +1,23 @@ +classdef untitled + %UNTITLED Summary of this class goes here + % Detailed explanation goes here + + properties + Property1 + end + + methods + function obj = untitled(inputArg1,inputArg2) + %UNTITLED Construct an instance of this class + % Detailed explanation goes here + obj.Property1 = inputArg1 + inputArg2; + end + + function outputArg = method1(obj,inputArg) + %METHOD1 Summary of this method goes here + % Detailed explanation goes here + outputArg = obj.Property1 + inputArg; + end + end +end + diff --git a/src/+matlode/+rk/+dirk/DIRK.m b/src/+matlode/+rk/+dirk/DIRK.m new file mode 100644 index 0000000..db8741c --- /dev/null +++ b/src/+matlode/+rk/+dirk/DIRK.m @@ -0,0 +1,62 @@ +classdef DIRK < matlode.rk.RungeKutta + %DIRK Will support DIRK, ESDIRK, and SDIRK + + properties (Constant) + PartitionMethod = false; + PartitionNum = 1; + MultirateMethod = false; + end + + + methods + function obj = DIRK(a, b, bHat, c, e, order, embeddedOrder) + obj = obj@matlode.rk.RungeKutta(a, b, bHat, c, e, order, embeddedOrder); + end + end + + methods (Access = protected) + function opts = matlodeSets(obj, p, varargin) + + %DIRK specific options + + opts = matlodeSets@matlode.rk.RungeKutta(obj, p, varargin{:}); + end + + function [ynew, stages, stats] = timeStep(obj, f, t, y, dt, stages, prevAccept, stats) + persistent fsal s a b c fevalIterCounts + if isempty(fsal) + fsal = obj.FsalStart; + s = obj.StageNum; + a = obj.A; + b = obj.B; + c = obj.C; + fevalIterCounts = double(obj.StageNum - obj.FsalStart + 1); + end + + if fsal && prevAccept + stages(:, 1) = stages(:, end); + end + + for i = fsal:s + ynew = y; + for j = 1:i-1 + if a(i,j) ~= 0 + ynew = ynew + stages(:, j) .* (dt .* a(i, j)); + end + end + thc = t + dt .* c(i); + stages(:, i) = f.F(thc, ynew); + end + ynew = y; + for i = 1:s + if b(i) ~= 0 + ynew = ynew + stages(:, i) .* (dt .* b(i)); + end + end + + stats.nFevals = stats.nFevals + fevalIterCounts; + end + end + +end + diff --git a/src/+matlode/+rk/+erk/ERK.m b/src/+matlode/+rk/+erk/ERK.m index 679e46d..c0a59a4 100644 --- a/src/+matlode/+rk/+erk/ERK.m +++ b/src/+matlode/+rk/+erk/ERK.m @@ -54,23 +54,7 @@ end stats.nFevals = stats.nFevals + fevalIterCounts; - end - - function [err, stats] = timeStepErr(obj, ~, ~, y, ynew, dt, stages, ErrNorm, stats) - persistent e s - if isempty(e) - e = obj.E; - s = obj.StageNum; - end - - yerror = 0; - for i = 1:s - if e(i) ~= 0 - yerror = yerror + stages(:, i) .* (dt .* e(i)); - end - end - err = ErrNorm.errEstimate(y, ynew, yerror); - end + end end end diff --git a/src/+matlode/+rk/RungeKutta.m b/src/+matlode/+rk/RungeKutta.m index 97bbee1..7b511cf 100644 --- a/src/+matlode/+rk/RungeKutta.m +++ b/src/+matlode/+rk/RungeKutta.m @@ -83,6 +83,22 @@ function [q] = timeLoopInit(obj) q = min(obj.Order, obj.EmbeddedOrder); + end + + function [err, stats] = timeStepErr(obj, ~, ~, y, ynew, dt, stages, ErrNorm, stats) + persistent e s + if isempty(e) + e = obj.E; + s = obj.StageNum; + end + + yerror = 0; + for i = 1:s + if e(i) ~= 0 + yerror = yerror + stages(:, i) .* (dt .* e(i)); + end + end + err = ErrNorm.errEstimate(y, ynew, yerror); end end diff --git a/src/+matlode/+rosenbrock/Rosenbrock.m b/src/+matlode/+rosenbrock/Rosenbrock.m index 8c750c8..b7537d5 100644 --- a/src/+matlode/+rosenbrock/Rosenbrock.m +++ b/src/+matlode/+rosenbrock/Rosenbrock.m @@ -138,7 +138,7 @@ end %Check if time derivative is avalible - if f.PDTOperatorType ~= matlode.OperatorType.Zero + if ~isempty(f.PartialDerivativeTime) dfdt_0 = f.PartialDerivativeTime(t, y); stats.nPDTEval = stats.nPDTEval + 1; end @@ -160,7 +160,7 @@ end - if f.PDTOperatorType ~= matlode.OperatorType.Zero && obj.GammaSum(i) ~= 0 + if ~isempty(f.PartialDerivativeTime) && obj.GammaSum(i) ~= 0 ynew = ynew + obj.GammaSum(i) * dt * dfdt_0; end diff --git a/src/+matlode/LinearOperatorType.m b/src/+matlode/LinearOperatorType.m deleted file mode 100644 index 44bc513..0000000 --- a/src/+matlode/LinearOperatorType.m +++ /dev/null @@ -1,11 +0,0 @@ -classdef LinearOperatorType - %Defines operators R x F^n -> F^{n x n} - %There are multiple kinds of linear operators - %This class simplifies check what type the linear operator is - %Does not identify if singular or not - %TODO: Add Jet Depedency - enumeration - Empty, Zero, Identity, Constant, TimeDependent, StateDependent, TSDepedent - end -end - diff --git a/src/+matlode/Model.m b/src/+matlode/Model.m index ae1d1eb..46b6a7d 100644 --- a/src/+matlode/Model.m +++ b/src/+matlode/Model.m @@ -6,36 +6,29 @@ properties F - FOperatorType Jacobian - JLinearOperatorType JacobianVectorProduct JacobianAdjointVectorProduct JPattern Vectorized Mass - MLinearOperatorType MVectorProduct + + %For complatency with MATLAB standard. May have DAEModel instead MassSingular MStateDependence MvPattern PartialDerivativeTime - PDTOperatorType PartialDerivativeParameters - PDPOperatorType - - HessianVectorProduct - HessianAdjointVectorProduct %FIXME: Not Supported Events OnEvent - NonNegative end @@ -45,7 +38,7 @@ 'MvPattern', 'MassSingular', 'Events', 'NonNegative'} OTPSetVars = {'Jacobian', 'JPattern', 'Vectorized', 'Mass', 'MStateDependence', ... 'MvPattern', 'MassSingular', 'Events', 'F', 'JacobianVectorProduct', 'JacobianAdjointVectorProduct', ... - 'PartialDerivativeTime' ,'PartialDerivativeParameters', 'HessianVectorProduct', 'HessianAdjointVectorProduct', ... + 'PartialDerivativeTime' ,'PartialDerivativeParameters', ... 'OnEvent', 'NonNegative'} end @@ -60,29 +53,20 @@ if isa(f, 'function_handle') || (isa(f, 'cell') && cellfun(@(x) isa(x, 'function_handle'), f)) %% Create new model based of f obj.F = f; - - p.addParameter('FOperatorType', matlode.OperatorType.TSDep); p.addParameter('Jacobian', []); - p.addParameter('JLinearOperatorType', matlode.LinearOperatorType.Empty); p.addParameter('JPattern', []); p.addParameter('JacobianVectorProduct', []); p.addParameter('Vectorized', []); p.addParameter('Mass', []); - p.addParameter('MLinearOperatorType', matlode.LinearOperatorType.Empty); p.addParameter('MassSingular', false); p.addParameter('MStateDependence', false); p.addParameter('MvPattern', []); p.addParameter('PartialDerivativeTime', []); - p.addParameter('PDTOperatorType', matlode.OperatorType.Zero); p.addParameter('PartialDerivativeParameters', []); - p.addParameter('PDPOperatorType', matlode.OperatorType.Zero); - - p.addParameter('HessianVectorProduct', []); - p.addParameter('HessianAdjointVectorProduct', []); p.addParameter('Events', []); @@ -96,14 +80,8 @@ for i = 1:length(obj.OTPSetVars) p.addParameter(obj.OTPSetVars{i}, obj.(obj.OTPSetVars{i})); end - - p.addParameter('PDTOperatorType', matlode.OperatorType.Zero); - p.addParameter('PDPOperatorType', matlode.OperatorType.Zero); - p.addParameter('MLinearOperatorType', matlode.LinearOperatorType.Empty); - p.addParameter('JLinearOperatorType', matlode.LinearOperatorType.Empty); - p.addParameter('FOperatorType', matlode.OperatorType.TSDep); - - elseif isa(f, 'matlode.Model') + + elseif isa(f, 'matlode.model.Model') %% Model Copying obj = modelCopy(obj, f); @@ -131,8 +109,6 @@ end end - obj = modelOperatorTypeSet(obj); - end function obj = setF(obj, f) @@ -179,46 +155,5 @@ end - methods (Access = protected) - - function obj = modelOperatorTypeSet(obj) - if isnumeric(obj.Jacobian) - obj.JLinearOperatorType = matlode.LinearOperatorType.Constant; - end - - %% Operator Type Checking - %TODO: Support Constant Operators - - obj = obj.opTypeSet('PartialDerivativeTime', 'PDTOperatorType', matlode.OperatorType.Zero, matlode.OperatorType.TSDep); - obj = obj.opTypeSet('Jacobian', 'JLinearOperatorType', matlode.LinearOperatorType.Empty, matlode.LinearOperatorType.TSDepedent); - if obj.JLinearOperatorType == matlode.LinearOperatorType.Empty && ~isempty(obj.JacobianVectorProduct) - obj.JLinearOperatorType = matlode.LinearOperatorType.TSDepedent; - end - % Currently only support constant Mass - % TODO: Support Time Dependent Mass - % TODO: Support State Dependent Mass - obj = obj.opTypeSet('Mass', 'MLinearOperatorType', matlode.LinearOperatorType.Empty, matlode.LinearOperatorType.Constant); - if obj.MLinearOperatorType == matlode.LinearOperatorType.Empty && ~isempty(obj.MVectorProduct) - obj.MLinearOperatorType = matlode.LinearOperatorType.TSDepedent; - end - - switch(obj.MLinearOperatorType) - case matlode.LinearOperatorType.Zero - case matlode.LinearOperatorType.Identity - case matlode.LinearOperatorType.Constant - otherwise - if strcmp(obj.MassSingular, 'yes') - error('Non-Constant Singular Mass Matrices are not Supported') - end - end - end - - function obj = opTypeSet(obj, op, optype, opttypedef, optnew) - if ~isempty(obj.(op)) && (obj.(optype) == opttypedef) - obj.(optype) = optnew; - end - - end - end end diff --git a/src/+matlode/OperatorType.m b/src/+matlode/OperatorType.m deleted file mode 100644 index 9547e53..0000000 --- a/src/+matlode/OperatorType.m +++ /dev/null @@ -1,9 +0,0 @@ -classdef OperatorType - %Defines operator type from R x F^n -> F^n - % Or in the implicit case for jets R x F^n x F^n -> F^n - enumeration - Zero, Constant, TimeDependent, StateDependent, JetDependent, TSDep, SJDep, TJDep, TSJDep - end - -end - From 162de4a4e88d7a251dde64b63914cfc114dca7e9 Mon Sep 17 00:00:00 2001 From: reid-g Date: Wed, 19 Apr 2023 17:55:22 -0400 Subject: [PATCH 12/17] Basic Implementation of SDIRK and ESDIRK Methods + Added ESDIRK and SDIRK methods of order 2 and 4 and BE + Updated Nonlinear Solvers + Added Nonlinear Testing Scripts --- src/+matlode/+linearsolver/LUDecomposition.m | 4 ++ src/+matlode/+linearsolver/LinearSolver.m | 2 + .../+linearsolver/MatrixFreeLinearSolver.m | 47 +++++++------ .../+linearsolver/MatrixLinearSolver.m | 44 +++++++----- src/+matlode/+nonlinearsolver/Broyden.m | 53 ++++++++++---- src/+matlode/+nonlinearsolver/Chord.m | 69 ++++++++++++++++--- src/+matlode/+nonlinearsolver/Newton.m | 47 ++++++++++--- .../+nonlinearsolver/NonlinearSolver.m | 6 +- src/+matlode/+rk/+dirk/BackwardEuler.m | 50 +++++++++----- src/+matlode/+rk/+dirk/DIRK.m | 41 ++++++++--- src/+matlode/+rk/+dirk/ESDIRK_2_1_3.m | 39 +++++++++++ src/+matlode/+rk/+dirk/ESDIRK_4_3_6.m | 42 +++++++++++ src/+matlode/+rk/+dirk/SDIRK_2_1_2.m | 38 ++++++++++ src/+matlode/+rk/+dirk/SDIRK_4_3_5.m | 41 +++++++++++ src/+matlode/+rk/+erk/ERK.m | 1 + src/+matlode/+rk/RungeKutta.m | 1 + src/+matlode/Model.m | 12 ++++ src/+matlode/OneStepIntegrator.m | 1 + .../+nonlintest/TestChordDIRK.m | 20 ++++++ .../+nonlintest/TestNewtonDIRK.m | 21 ++++++ 20 files changed, 474 insertions(+), 105 deletions(-) create mode 100644 src/+matlode/+rk/+dirk/ESDIRK_2_1_3.m create mode 100644 src/+matlode/+rk/+dirk/ESDIRK_4_3_6.m create mode 100644 src/+matlode/+rk/+dirk/SDIRK_2_1_2.m create mode 100644 src/+matlode/+rk/+dirk/SDIRK_4_3_5.m create mode 100644 src/+testingscripts/+nonlintest/TestChordDIRK.m create mode 100644 src/+testingscripts/+nonlintest/TestNewtonDIRK.m diff --git a/src/+matlode/+linearsolver/LUDecomposition.m b/src/+matlode/+linearsolver/LUDecomposition.m index 7b2cac6..4880d25 100644 --- a/src/+matlode/+linearsolver/LUDecomposition.m +++ b/src/+matlode/+linearsolver/LUDecomposition.m @@ -20,6 +20,10 @@ stats.nDecompositions = stats.nDecompositions + 1; end + function [stats] = computeMass(obj, f, t, y, stats) + stats = computeMass@matlode.linearsolver.MatrixLinearSolver(obj, f, t, y, stats); + end + function [sol, stats] = solve(obj, x, stats) sol = obj.system(x); diff --git a/src/+matlode/+linearsolver/LinearSolver.m b/src/+matlode/+linearsolver/LinearSolver.m index ca66b2e..2f8fc81 100644 --- a/src/+matlode/+linearsolver/LinearSolver.m +++ b/src/+matlode/+linearsolver/LinearSolver.m @@ -26,6 +26,8 @@ methods (Abstract) % mass_scale*mass + jac_scale*jac [stats] = preprocess(obj, f, t, y, reeval, mass_scale, jac_scale, stats); + + [stats] = computeMass(obj, f, t, y, stats); [sol, stats] = solve(obj, x, stats); diff --git a/src/+matlode/+linearsolver/MatrixFreeLinearSolver.m b/src/+matlode/+linearsolver/MatrixFreeLinearSolver.m index a4e476f..aac3a26 100644 --- a/src/+matlode/+linearsolver/MatrixFreeLinearSolver.m +++ b/src/+matlode/+linearsolver/MatrixFreeLinearSolver.m @@ -11,6 +11,8 @@ end function [stats] = preprocess(obj, f, t, y, reeval, mass_scale, jac_scale, stats) + %TODO: Add Case for array of t and y and matrix jac_scale for + %FIRK if reeval @@ -29,32 +31,37 @@ end end - if ~isempty(f.MVectorProduct) - obj.mass = @(v) f.MVectorProduct(t, y, v); - stats.nMassEvals = stats.nMassEvals + 1; - else - if isempty(f.Mass) - if isempty(obj.mass) - obj.mass = @(v) v; - end - elseif isa(f.Mass, 'double') - %TODO: Replace with Data Type - if isempty(obj.mass) - obj.mass = @(v) f.Mass * v; - end - else - obj.mass = @(v) f.Mass(t,y) * v; - stats.nMassEvals = stats.nMassEvals + 1; - end - end + stats = obj.computeMass(f, t, y, stats); + end obj.system = @(v) mass_scale * obj.mass(v) + jac_scale * obj.jac(v); - end + end + + function [stats] = computeMass(obj, f, t, y, stats) + if ~isempty(f.MVectorProduct) + obj.mass = @(v) f.MVectorProduct(t, y, v); + stats.nMassEvals = stats.nMassEvals + 1; + else + if isempty(f.Mass) + if isempty(obj.mass) + obj.mass = @(v) v; + end + elseif isa(f.Mass, 'double') + %TODO: Replace with Data Type + if isempty(obj.mass) + obj.mass = @(v) f.Mass * v; + end + else + obj.mass = @(v) f.Mass(t,y) * v; + stats.nMassEvals = stats.nMassEvals + 1; + end + end + end function [sol, stats] = solve(obj, x, stats) [sol, flag, relres, iter] = obj.Solver(obj.system, x, obj.SolverArgs{:}); - + %TODO: Add Flag results stats.nLinearSolves = stats.nLinearSolves + 1; end end diff --git a/src/+matlode/+linearsolver/MatrixLinearSolver.m b/src/+matlode/+linearsolver/MatrixLinearSolver.m index 842ec41..92607f6 100644 --- a/src/+matlode/+linearsolver/MatrixLinearSolver.m +++ b/src/+matlode/+linearsolver/MatrixLinearSolver.m @@ -12,6 +12,8 @@ end function [stats] = preprocess(obj, f, t, y, reeval, mass_scale, jac_scale, stats) + %TODO: Add Case for array of t and y and matrix jac_scale for + %FIRK if reeval if isa(f.Jacobian,'double') %TODO: Replace with Data Type @@ -23,27 +25,33 @@ stats.nJacobianEvals = stats.nJacobianEvals + 1; end - if isempty(f.Mass) - if isempty(obj.mass) - if issparse(obj.jac) - obj.mass = speye(length(y)); - else - obj.mass = eye(length(y)); - end - end - elseif isa(f.Mass, 'double') - %TODO: Replace with Data Type - if isempty(obj.mass) - obj.mass = f.Mass; - end - else - obj.mass = f.Mass(t,y); - stats.nMassEvals = stats.nMassEvals + 1; - end + stats = obj.computeMass(f, t, y, stats); + end obj.system = mass_scale * obj.mass + jac_scale * obj.jac; - end + end + + function [stats] = computeMass(obj, f, t, y, stats) + if isempty(f.Mass) + if isempty(obj.mass) + if issparse(obj.jac) + obj.mass = speye(length(y)); + else + obj.mass = eye(length(y)); + end + end + + elseif isa(f.Mass, 'double') + %TODO: Replace with Data Type + if isempty(obj.mass) + obj.mass = f.Mass; + end + else + obj.mass = f.Mass(t,y); + stats.nMassEvals = stats.nMassEvals + 1; + end + end function [sol, stats] = solve(obj, x, stats) sol = obj.Solver(obj.system, x, obj.SolverArgs{:}); diff --git a/src/+matlode/+nonlinearsolver/Broyden.m b/src/+matlode/+nonlinearsolver/Broyden.m index 1cb924a..4dee687 100644 --- a/src/+matlode/+nonlinearsolver/Broyden.m +++ b/src/+matlode/+nonlinearsolver/Broyden.m @@ -1,22 +1,45 @@ -classdef untitled - %UNTITLED Summary of this class goes here - % Detailed explanation goes here - - properties - Property1 - end +classdef Broyden < matlode.nonlinearsolver.NonlinearSolver + %BROYDEN Classic Broyden Method. Works without a linear solver methods - function obj = untitled(inputArg1,inputArg2) - %UNTITLED Construct an instance of this class - % Detailed explanation goes here - obj.Property1 = inputArg1 + inputArg2; + function obj = Broyden(args) + arguments + args(1,:) cell = {}; + end + + obj = obj@matlode.nonlinearsolver.NonlinearSolver([], args{:}); end - function outputArg = method1(obj,inputArg) - %METHOD1 Summary of this method goes here - % Detailed explanation goes here - outputArg = obj.Property1 + inputArg; + function [xf, optout, stats] = solve(obj, f, t, x0, sys_const, mass_scale, jac_scale, ~, stats) + xf = x0; + i = 0; + while i < obj.MaxIterations + stats = obj.LinearSolver.preprocess(f, t, xf, true, mass_scale, jac_scale, stats); + if isa(obj.LinearSolver.mass,'function_handle') + mx = obj.LinearSolver.mass(xf); + else + mx = obj.LinearSolver.mass * xf; + end + + + b = mx .* (-mass_scale) - sys_const - jac_scale * f.F(t,xf); + [w_i, stats] = obj.LinearSolver.solve(b, stats); + + xtn = w_i + xf; + i = i + 1; + stats.nNonLinIterations = stats.nNonLinIterations + 1; + + if norm(w_i) < obj.Tolerance + return; + end + xf = xtn; + end + + if i == obj.MaxIterations + %TODO Add Flags + optout = false; + end + end end end diff --git a/src/+matlode/+nonlinearsolver/Chord.m b/src/+matlode/+nonlinearsolver/Chord.m index 9082398..7d5ea58 100644 --- a/src/+matlode/+nonlinearsolver/Chord.m +++ b/src/+matlode/+nonlinearsolver/Chord.m @@ -1,23 +1,70 @@ -classdef Chord - %CHORD Summary of this class goes here - % Detailed explanation goes here +classdef Chord < matlode.nonlinearsolver.NonlinearSolver + %CHORD Classic Chord method for solving nonlinear systems. Does not + %calculate the jacobian more than once properties Property1 end methods - function obj = Chord(inputArg1,inputArg2) - %CHORD Construct an instance of this class - % Detailed explanation goes here - obj.Property1 = inputArg1 + inputArg2; + function obj = Chord(linsolve, args) + arguments + linsolve(1,1) matlode.linearsolver.LinearSolver = matlode.linearsolver.MatrixLinearSolver(); + args(1,:) cell = {}; + end + + obj = obj@matlode.nonlinearsolver.NonlinearSolver(linsolve, args{:}); end - function outputArg = method1(obj,inputArg) - %METHOD1 Summary of this method goes here - % Detailed explanation goes here - outputArg = obj.Property1 + inputArg; + function [xn, xnf, optout, stats] = solve(obj, f, t, x0, sys_const, mass_scale, jac_scale, ~, stats) + xn = x0; + i = 0; + stats = obj.LinearSolver.preprocess(f, t, xn, true, mass_scale, jac_scale, stats); + if isa(obj.LinearSolver.mass,'function_handle') + mx = obj.LinearSolver.mass(xn); + else + mx = obj.LinearSolver.mass * xn; + end + + while i < obj.MaxIterations + if i ~= 0 + [stats] = obj.LinearSolver.computeMass(f, t, xn, stats); + if isa(obj.LinearSolver.mass,'function_handle') + mx = obj.LinearSolver.mass(xn); + else + mx = obj.LinearSolver.mass * xn; + end + end + + + xnf = f.F(t,xn); + b = -mx .* (mass_scale) + sys_const + jac_scale * xnf; + [w_i, stats] = obj.LinearSolver.solve(b, stats); + + xtn = w_i + xn; + i = i + 1; + + if norm(w_i) < obj.Tolerance + break; + end + xn = xtn; + end + stats.nNonLinIterations = stats.nNonLinIterations + i; + stats.nFevals = stats.nFevals + i; + + + if i == obj.MaxIterations + %TODO Add Flags + optout = false; + else + optout = true; + end + end end + + methods (Access=private) + + end end diff --git a/src/+matlode/+nonlinearsolver/Newton.m b/src/+matlode/+nonlinearsolver/Newton.m index d7996e1..b59f5f2 100644 --- a/src/+matlode/+nonlinearsolver/Newton.m +++ b/src/+matlode/+nonlinearsolver/Newton.m @@ -1,25 +1,50 @@ classdef Newton < matlode.nonlinearsolver.NonlinearSolver - %NEWTON Summary of this class goes here - % Detailed explanation goes here - - properties - Property1 - end + %NEWTON Classic Newton method for solving nonlinear systems methods function obj = Newton(linsolve, args) arguments - linsolve(1,1) matlode.linearsolver.LinearSolver = {}; + linsolve(1,1) matlode.linearsolver.LinearSolver = matlode.linearsolver.MatrixLinearSolver(); args(1,:) cell = {}; end obj = obj@matlode.nonlinearsolver.NonlinearSolver(linsolve, args{:}); end - function outputArg = method1(obj,inputArg) - %METHOD1 Summary of this method goes here - % Detailed explanation goes here - outputArg = obj.Property1 + inputArg; + function [xn, xnf, optout, stats] = solve(obj, f, t, x0, sys_const, mass_scale, jac_scale, ~, stats) + xn = x0; + i = 0; + while i < obj.MaxIterations + stats = obj.LinearSolver.preprocess(f, t, xn, true, mass_scale, jac_scale, stats); + if isa(obj.LinearSolver.mass,'function_handle') + mx = obj.LinearSolver.mass(xn); + else + mx = obj.LinearSolver.mass * xn; + end + + + xnf = f.F(t,xn); + b = -mx .* (mass_scale) + sys_const + jac_scale * xnf; + [w_i, stats] = obj.LinearSolver.solve(b, stats); + + xtn = w_i + xn; + i = i + 1; + + if norm(w_i) < obj.Tolerance + break; + end + xn = xtn; + end + stats.nNonLinIterations = stats.nNonLinIterations + i; + stats.nFevals = stats.nFevals + i; + + if i == obj.MaxIterations + %TODO Add Flags + optout = false; + else + optout = true; + end + end end end diff --git a/src/+matlode/+nonlinearsolver/NonlinearSolver.m b/src/+matlode/+nonlinearsolver/NonlinearSolver.m index 59b10a8..7fc76f0 100644 --- a/src/+matlode/+nonlinearsolver/NonlinearSolver.m +++ b/src/+matlode/+nonlinearsolver/NonlinearSolver.m @@ -7,6 +7,8 @@ properties (SetAccess = protected) LinearSolver + MaxIterations + Tolerance end methods @@ -20,6 +22,8 @@ obj.NonLinearArgs = args; obj.LinearSolver = linsolve; + obj.MaxIterations = maxiter; + obj.Tolerance = tolerance; end function obj = SetLinearSolver(obj,linsolve) @@ -29,7 +33,7 @@ methods(Abstract) - [xf, optout, stats] = solve(nonlinsystem, f, mass_scale, jac_scale, x0, optin); + [xn, xnf, optout, stats] = solve(obj,f, t, x0, sys_const, mass_scale, jac_scale, optin, stats); end end diff --git a/src/+matlode/+rk/+dirk/BackwardEuler.m b/src/+matlode/+rk/+dirk/BackwardEuler.m index 1cb924a..03f4ff6 100644 --- a/src/+matlode/+rk/+dirk/BackwardEuler.m +++ b/src/+matlode/+rk/+dirk/BackwardEuler.m @@ -1,23 +1,37 @@ -classdef untitled - %UNTITLED Summary of this class goes here - % Detailed explanation goes here - - properties - Property1 - end +classdef BackwardEuler < matlode.rk.dirk.DIRK + %BackwardEuler + % p = 1, s = 1 methods - function obj = untitled(inputArg1,inputArg2) - %UNTITLED Construct an instance of this class - % Detailed explanation goes here - obj.Property1 = inputArg1 + inputArg2; - end - - function outputArg = method1(obj,inputArg) - %METHOD1 Summary of this method goes here - % Detailed explanation goes here - outputArg = obj.Property1 + inputArg; - end + function obj = BackwardEuler(datatype) + arguments + datatype(1,1) string = 'double'; + end + + caster = @(x) matlode.util.CoefficentTransformers.transform(x,datatype); + + + a = caster('[[1]]'); + + b = caster('[1]'); + + bHat = []; + + e = []; + + c = caster('[1]'); + + order = 1; + + embbededOrder = 0; + + obj = obj@matlode.rk.dirk.DIRK(a, b, bHat, c, e, order, embbededOrder); + + end + + function sol = integrate(obj, f, tspan, y0, varargin) + sol = integrate@matlode.rk.dirk.DIRK(obj, f, tspan, y0, 'StepSizeController', matlode.stepsizecontroller.StandardController, 'Dense', obj.DenseOut, varargin{:}); + end end end diff --git a/src/+matlode/+rk/+dirk/DIRK.m b/src/+matlode/+rk/+dirk/DIRK.m index db8741c..bc3823c 100644 --- a/src/+matlode/+rk/+dirk/DIRK.m +++ b/src/+matlode/+rk/+dirk/DIRK.m @@ -6,11 +6,19 @@ PartitionNum = 1; MultirateMethod = false; end + + properties + NonLinearSolver + StifflyAccurate + end methods function obj = DIRK(a, b, bHat, c, e, order, embeddedOrder) obj = obj@matlode.rk.RungeKutta(a, b, bHat, c, e, order, embeddedOrder); + + obj.StifflyAccurate = all(obj.A(end,:) == obj.B(:)'); + end end @@ -18,43 +26,54 @@ function opts = matlodeSets(obj, p, varargin) %DIRK specific options + p.addParameter('NonLinearSolver', matlode.nonlinearsolver.Chord()); opts = matlodeSets@matlode.rk.RungeKutta(obj, p, varargin{:}); + + if isempty(opts.NonLinearSolver) + error('Please provide appropiate parameters and a non-linear solver') + end + obj.NonLinearSolver = opts.NonLinearSolver; end function [ynew, stages, stats] = timeStep(obj, f, t, y, dt, stages, prevAccept, stats) - persistent fsal s a b c fevalIterCounts + persistent fsal s a b c if isempty(fsal) fsal = obj.FsalStart; s = obj.StageNum; a = obj.A; b = obj.B; c = obj.C; - fevalIterCounts = double(obj.StageNum - obj.FsalStart + 1); end if fsal && prevAccept stages(:, 1) = stages(:, end); end - for i = fsal:s + %TODO: Update PorMAss + for i = fsal:s ynew = y; for j = 1:i-1 if a(i,j) ~= 0 - ynew = ynew + stages(:, j) .* (dt .* a(i, j)); + ynew = ynew + stages(:, j) .* (dt * a(i, j)); end end thc = t + dt .* c(i); - stages(:, i) = f.F(thc, ynew); - end - ynew = y; - for i = 1:s - if b(i) ~= 0 - ynew = ynew + stages(:, i) .* (dt .* b(i)); + + %TODO: Setup NonLin Flags + %Solve Nonlinear System + [ynew, stages(:,i), ~, stats] = obj.NonLinearSolver.solve(f, thc, ynew, ynew, 1, dt .* a(i,i), [], stats); + end + + if ~obj.StifflyAccurate + ynew = y; + for i = 1:s + if b(i) ~= 0 + ynew = ynew + stages(:, i) .* (dt .* b(i)); + end end end - stats.nFevals = stats.nFevals + fevalIterCounts; end end diff --git a/src/+matlode/+rk/+dirk/ESDIRK_2_1_3.m b/src/+matlode/+rk/+dirk/ESDIRK_2_1_3.m new file mode 100644 index 0000000..34d724e --- /dev/null +++ b/src/+matlode/+rk/+dirk/ESDIRK_2_1_3.m @@ -0,0 +1,39 @@ +classdef ESDIRK_2_1_3 < matlode.rk.dirk.DIRK + % ESDIRK 2(1)3 + % p = 2, s = 3, pe = 1 + + methods + function obj = ESDIRK_2_1_3(datatype) + arguments + datatype(1,1) string = 'double'; + end + + caster = @(x) matlode.util.CoefficentTransformers.transform(x,datatype); + + + a = caster(join(['[[0, 0, 0];',... + '[1 - (1/sqrt(2)), 1 - (1/sqrt(2)), 0];',... + '[1/(2*sqrt(2)), 1/(2*sqrt(2)), 1 - (1/sqrt(2))]]'])); + + b = caster('[1/(2*sqrt(2)), 1/(2*sqrt(2)), 1 - (1/sqrt(2))]'); + + bHat = caster('[3/10, 3/10, 2/5]'); + + e = caster('[1/(2*sqrt(2)) - 3/10, 1/(2*sqrt(2)) - 3/10 , 3/5 - 1/sqrt(2)]'); + + c = caster('[0, 2 - sqrt(2), 1]'); + + order = 2; + + embbededOrder = 1; + + obj = obj@matlode.rk.dirk.DIRK(a, b, bHat, c, e, order, embbededOrder); + + end + + function sol = integrate(obj, f, tspan, y0, varargin) + sol = integrate@matlode.rk.dirk.DIRK(obj, f, tspan, y0, 'StepSizeController', matlode.stepsizecontroller.StandardController, 'Dense', obj.DenseOut, varargin{:}); + end + end +end + diff --git a/src/+matlode/+rk/+dirk/ESDIRK_4_3_6.m b/src/+matlode/+rk/+dirk/ESDIRK_4_3_6.m new file mode 100644 index 0000000..89cf608 --- /dev/null +++ b/src/+matlode/+rk/+dirk/ESDIRK_4_3_6.m @@ -0,0 +1,42 @@ +classdef ESDIRK_4_3_6 < matlode.rk.dirk.DIRK + % ESDIRK 4(3)6 + % p = 4, s = 6, pe = 3 + + methods + function obj = ESDIRK_4_3_6(datatype) + arguments + datatype(1,1) string = 'double'; + end + + caster = @(x) matlode.util.CoefficentTransformers.transform(x,datatype); + + + a = caster(join(['[[0, 0, 0, 0, 0, 0];',... + '[1/4, 1/4, 0, 0, 0, 0];',... + '[(1-sqrt(2))/8, (1-sqrt(2))/8, 1/4, 0, 0, 0];',... + '[(5 - 7*sqrt(2))/64, (5 - 7*sqrt(2))/64, (1 + sqrt(2))/32, 1/4, 0, 0];',... + '[(-13796 - 54539*sqrt(2))/125000, (-13796 - 54539*sqrt(2))/125000, (506605 + 132109*sqrt(2))/437500, 166*(-97 - 376*sqrt(2))/109375, 1/4, 0];',... + '[(1181 - 987*sqrt(2))/13782, (1181 - 987*sqrt(2))/13782, 47*(-267 - 1783*sqrt(2))/273343, -16*(-22922 + 3525*sqrt(2))/571953, -15625*(97 + 376*sqrt(2))/90749876, 1/4]]'])); + + b = caster('[(1181 - 987*sqrt(2))/13782, (1181 - 987*sqrt(2))/13782, 47*(-267 - 1783*sqrt(2))/273343, -16*(-22922 + 3525*sqrt(2))/571953, -15625*(97 + 376*sqrt(2))/90749876, 1/4]'); + + bHat = caster('[(263980 - 483197*sqrt(2))/4515000, (263980 - 483197*sqrt(2))/4515000, 483197*(1 + sqrt(2))/2257500, 293362/564375, -1/12, 10/43]'); + + e = caster('[(367186009*sqrt(2))/10370955000 + 2352837/86424625, (367186009*sqrt(2))/10370955000 + 2352837/86424625, - (45894182153*sqrt(2))/88153117500 - 22915412153/88153117500, 13065461338/107598658125 - (18800*sqrt(2))/190651, 9070297/136124814 - (1468750*sqrt(2))/22687469, 3/172]'); + + c = caster('[0, 1/2, (2 - sqrt(2))/4, 5/8, 26/25, 1]'); + + order = 4; + + embbededOrder = 3; + + obj = obj@matlode.rk.dirk.DIRK(a, b, bHat, c, e, order, embbededOrder); + + end + + function sol = integrate(obj, f, tspan, y0, varargin) + sol = integrate@matlode.rk.dirk.DIRK(obj, f, tspan, y0, 'StepSizeController', matlode.stepsizecontroller.StandardController, 'Dense', obj.DenseOut, varargin{:}); + end + end +end + diff --git a/src/+matlode/+rk/+dirk/SDIRK_2_1_2.m b/src/+matlode/+rk/+dirk/SDIRK_2_1_2.m new file mode 100644 index 0000000..3bb263e --- /dev/null +++ b/src/+matlode/+rk/+dirk/SDIRK_2_1_2.m @@ -0,0 +1,38 @@ +classdef SDIRK_2_1_2 < matlode.rk.dirk.DIRK + %SDIRK 2(1)2 + % p = 2, s = 2, pe = 1 + + methods + function obj = SDIRK_2_1_2(datatype) + arguments + datatype(1,1) string = 'double'; + end + + caster = @(x) matlode.util.CoefficentTransformers.transform(x,datatype); + + + a = caster(join(['[[1 - (1/sqrt(2)), 0];',... + '[1/sqrt(2), 1 - (1/sqrt(2))]]'])); + + b = caster('[1/sqrt(2), 1 - (1/sqrt(2))]'); + + bHat = caster('[3/5, 2/5]'); + + e = caster('[1/sqrt(2) - 3/5, 3/5 - 1/sqrt(2)]'); + + c = caster('[1 - (1/sqrt(2)), 1]'); + + order = 2; + + embbededOrder = 1; + + obj = obj@matlode.rk.dirk.DIRK(a, b, bHat, c, e, order, embbededOrder); + + end + + function sol = integrate(obj, f, tspan, y0, varargin) + sol = integrate@matlode.rk.dirk.DIRK(obj, f, tspan, y0, 'StepSizeController', matlode.stepsizecontroller.StandardController, 'Dense', obj.DenseOut, varargin{:}); + end + end +end + diff --git a/src/+matlode/+rk/+dirk/SDIRK_4_3_5.m b/src/+matlode/+rk/+dirk/SDIRK_4_3_5.m new file mode 100644 index 0000000..e851c46 --- /dev/null +++ b/src/+matlode/+rk/+dirk/SDIRK_4_3_5.m @@ -0,0 +1,41 @@ +classdef SDIRK_4_3_5 < matlode.rk.dirk.DIRK + %SDIRK 4(3)5 + % p = 4, s = 5, pe = 3 + + methods + function obj = SDIRK_4_3_5(datatype) + arguments + datatype(1,1) string = 'double'; + end + + caster = @(x) matlode.util.CoefficentTransformers.transform(x,datatype); + + + a = caster(join(['[[1/4, 0, 0, 0, 0];',... + '[13/20, 1/4, 0, 0, 0];',... + '[580/1287, -175/5148, 1/4, 0, 0];',... + '[12698/37375, -201/2990, 891/11500, 1/4, 0];',... + '[944/1365, -400/819, 99/35, -575/252, 1/4]]'])); + + b = caster('[944/1365, -400/819, 99/35, -575/252, 1/4]'); + + bHat = caster('[41911/60060, -83975/144144, 3393/1120, -27025/11088, 103/352]'); + + e = caster('[-25/4004, 4525/48048, -45/224, 575/3696, -15/352]'); + + c = caster('[1/4, 9/10, 2/3, 3/5, 1]'); + + order = 4; + + embbededOrder = 3; + + obj = obj@matlode.rk.dirk.DIRK(a, b, bHat, c, e, order, embbededOrder); + + end + + function sol = integrate(obj, f, tspan, y0, varargin) + sol = integrate@matlode.rk.dirk.DIRK(obj, f, tspan, y0, 'StepSizeController', matlode.stepsizecontroller.StandardController, 'Dense', obj.DenseOut, varargin{:}); + end + end +end + diff --git a/src/+matlode/+rk/+erk/ERK.m b/src/+matlode/+rk/+erk/ERK.m index c0a59a4..ffa10f5 100644 --- a/src/+matlode/+rk/+erk/ERK.m +++ b/src/+matlode/+rk/+erk/ERK.m @@ -36,6 +36,7 @@ stages(:, 1) = stages(:, end); end + %TODO: Update PorMass for i = fsal:s ynew = y; for j = 1:i-1 diff --git a/src/+matlode/+rk/RungeKutta.m b/src/+matlode/+rk/RungeKutta.m index 7b511cf..ff626af 100644 --- a/src/+matlode/+rk/RungeKutta.m +++ b/src/+matlode/+rk/RungeKutta.m @@ -92,6 +92,7 @@ s = obj.StageNum; end + %TODO: Update for Mass yerror = 0; for i = 1:s if e(i) ~= 0 diff --git a/src/+matlode/Model.m b/src/+matlode/Model.m index 46b6a7d..d04b2d7 100644 --- a/src/+matlode/Model.m +++ b/src/+matlode/Model.m @@ -6,6 +6,8 @@ properties F + AddPartitionNum + ComponentPartitionNum Jacobian JacobianVectorProduct @@ -109,6 +111,16 @@ end end + %Check Dimension of F + %TODO Turn into Function + if isa(f, 'cell') + obj.AddPartitionNum = size(f,2); + obj.ComponentPartitionNum = size(f,1); + else + obj.AddPartitionNum = 1; + obj.ComponentPartitionNum = 1; + end + end function obj = setF(obj, f) diff --git a/src/+matlode/OneStepIntegrator.m b/src/+matlode/OneStepIntegrator.m index ff51068..fa9b49e 100644 --- a/src/+matlode/OneStepIntegrator.m +++ b/src/+matlode/OneStepIntegrator.m @@ -250,6 +250,7 @@ stats.nLinearSolves = 0; stats.nMassEvals = 0; stats.nJacobianEvals = 0; + stats.nNonLinIterations = 0; stats.nPDTEval = 0; stats.nDecompositions = 0; end diff --git a/src/+testingscripts/+nonlintest/TestChordDIRK.m b/src/+testingscripts/+nonlintest/TestChordDIRK.m new file mode 100644 index 0000000..f55ed82 --- /dev/null +++ b/src/+testingscripts/+nonlintest/TestChordDIRK.m @@ -0,0 +1,20 @@ +clear +format long e +close all + +integrator = matlode.rk.dirk.ESDIRK_4_3_6; + +options.ErrNorm = matlode.errnorm.InfNorm(1e-6, 1e-6); +options.StepSizeController = matlode.stepsizecontroller.StandardController; + +problem = otp.kpr.presets.Canonical; + +sol = integrator.integrate(problem.RHS, problem.TimeSpan, problem.Y0, options); + +sol_matlab = problem.solve('Solver', @ode45, 'RelTol', 1e-6, 'AbsTol', 1e-6); + +true_sol = problem.solve; + +norm(sol.y(:,end) - true_sol.y(:,end)) +norm(sol_matlab.y(:,end) - true_sol.y(:,end)) +norm(sol.y(:,end) - sol_matlab.y(:,end)) diff --git a/src/+testingscripts/+nonlintest/TestNewtonDIRK.m b/src/+testingscripts/+nonlintest/TestNewtonDIRK.m new file mode 100644 index 0000000..7247602 --- /dev/null +++ b/src/+testingscripts/+nonlintest/TestNewtonDIRK.m @@ -0,0 +1,21 @@ +clear +format long e +close all + +integrator = matlode.rk.dirk.SDIRK_4_3_5; + +options.ErrNorm = matlode.errnorm.InfNorm(1e-6, 1e-6); +options.StepSizeController = matlode.stepsizecontroller.StandardController; +options.NonLinearSolver = matlode.nonlinearsolver.Newton; + +problem = otp.kpr.presets.Canonical; + +sol = integrator.integrate(problem.RHS, problem.TimeSpan, problem.Y0, options); + +sol_matlab = problem.solve('Solver', @ode45, 'RelTol', 1e-6, 'AbsTol', 1e-6); + +true_sol = problem.solve; + +norm(sol.y(:,end) - true_sol.y(:,end)) +norm(sol_matlab.y(:,end) - true_sol.y(:,end)) +norm(sol.y(:,end) - sol_matlab.y(:,end)) From 70e0918a2efae006b0f8a1e8e60964d5e9fab21a Mon Sep 17 00:00:00 2001 From: reid-g Date: Mon, 24 Feb 2025 12:31:11 -0500 Subject: [PATCH 13/17] Multiple Fixes + Nonlinear Sovler Fix + Fixed Integrator Fix --- src/+matlode/+linearsolver/LinearSolver.m | 2 +- .../+linearsolver/MatrixFreeLinearSolver.m | 2 +- .../+linearsolver/MatrixLinearSolver.m | 2 +- src/+matlode/+nonlinearsolver/Broyden.m | 46 ------------- src/+matlode/+nonlinearsolver/Chord.m | 12 ++-- src/+matlode/+nonlinearsolver/Newton.m | 6 +- .../+nonlinearsolver/NonlinearSolver.m | 5 +- src/+matlode/+nonlinearsolver/QuasiNewton.m | 23 ------- src/+matlode/+rk/+dirk/DIRK.m | 42 ++++++------ src/+matlode/+rk/+erk/ERK.m | 33 ++++----- src/+matlode/+rk/RungeKutta.m | 37 ++-------- src/+matlode/+rosenbrock/Rosenbrock.m | 6 +- src/+matlode/Integrator.m | 6 +- src/+matlode/OneStepIntegrator.m | 67 +++++++++++-------- .../+nonlintest/TestChordDIRK.m | 11 +-- .../+nonlintest/TestNewtonDIRK.m | 4 +- 16 files changed, 108 insertions(+), 196 deletions(-) delete mode 100644 src/+matlode/+nonlinearsolver/Broyden.m delete mode 100644 src/+matlode/+nonlinearsolver/QuasiNewton.m diff --git a/src/+matlode/+linearsolver/LinearSolver.m b/src/+matlode/+linearsolver/LinearSolver.m index 2f8fc81..9b6fc04 100644 --- a/src/+matlode/+linearsolver/LinearSolver.m +++ b/src/+matlode/+linearsolver/LinearSolver.m @@ -29,7 +29,7 @@ [stats] = computeMass(obj, f, t, y, stats); - [sol, stats] = solve(obj, x, stats); + [sol, stats, out_opts] = solve(obj, x, stats); end diff --git a/src/+matlode/+linearsolver/MatrixFreeLinearSolver.m b/src/+matlode/+linearsolver/MatrixFreeLinearSolver.m index aac3a26..1f5b8b8 100644 --- a/src/+matlode/+linearsolver/MatrixFreeLinearSolver.m +++ b/src/+matlode/+linearsolver/MatrixFreeLinearSolver.m @@ -59,7 +59,7 @@ end end - function [sol, stats] = solve(obj, x, stats) + function [sol, stats, out_opts] = solve(obj, x, stats) [sol, flag, relres, iter] = obj.Solver(obj.system, x, obj.SolverArgs{:}); %TODO: Add Flag results stats.nLinearSolves = stats.nLinearSolves + 1; diff --git a/src/+matlode/+linearsolver/MatrixLinearSolver.m b/src/+matlode/+linearsolver/MatrixLinearSolver.m index 92607f6..90b07f0 100644 --- a/src/+matlode/+linearsolver/MatrixLinearSolver.m +++ b/src/+matlode/+linearsolver/MatrixLinearSolver.m @@ -53,7 +53,7 @@ end end - function [sol, stats] = solve(obj, x, stats) + function [sol, stats, out_opts] = solve(obj, x, stats) sol = obj.Solver(obj.system, x, obj.SolverArgs{:}); stats.nLinearSolves = stats.nLinearSolves + 1; diff --git a/src/+matlode/+nonlinearsolver/Broyden.m b/src/+matlode/+nonlinearsolver/Broyden.m deleted file mode 100644 index 4dee687..0000000 --- a/src/+matlode/+nonlinearsolver/Broyden.m +++ /dev/null @@ -1,46 +0,0 @@ -classdef Broyden < matlode.nonlinearsolver.NonlinearSolver - %BROYDEN Classic Broyden Method. Works without a linear solver - - methods - function obj = Broyden(args) - arguments - args(1,:) cell = {}; - end - - obj = obj@matlode.nonlinearsolver.NonlinearSolver([], args{:}); - end - - function [xf, optout, stats] = solve(obj, f, t, x0, sys_const, mass_scale, jac_scale, ~, stats) - xf = x0; - i = 0; - while i < obj.MaxIterations - stats = obj.LinearSolver.preprocess(f, t, xf, true, mass_scale, jac_scale, stats); - if isa(obj.LinearSolver.mass,'function_handle') - mx = obj.LinearSolver.mass(xf); - else - mx = obj.LinearSolver.mass * xf; - end - - - b = mx .* (-mass_scale) - sys_const - jac_scale * f.F(t,xf); - [w_i, stats] = obj.LinearSolver.solve(b, stats); - - xtn = w_i + xf; - i = i + 1; - stats.nNonLinIterations = stats.nNonLinIterations + 1; - - if norm(w_i) < obj.Tolerance - return; - end - xf = xtn; - end - - if i == obj.MaxIterations - %TODO Add Flags - optout = false; - end - - end - end -end - diff --git a/src/+matlode/+nonlinearsolver/Chord.m b/src/+matlode/+nonlinearsolver/Chord.m index 7d5ea58..0d28bcd 100644 --- a/src/+matlode/+nonlinearsolver/Chord.m +++ b/src/+matlode/+nonlinearsolver/Chord.m @@ -2,10 +2,6 @@ %CHORD Classic Chord method for solving nonlinear systems. Does not %calculate the jacobian more than once - properties - Property1 - end - methods function obj = Chord(linsolve, args) arguments @@ -16,7 +12,7 @@ obj = obj@matlode.nonlinearsolver.NonlinearSolver(linsolve, args{:}); end - function [xn, xnf, optout, stats] = solve(obj, f, t, x0, sys_const, mass_scale, jac_scale, ~, stats) + function [xn, xnf, out_opts, stats] = solve(obj, f, t, x0, sys_const, mass_scale, jac_scale, ~, stats) xn = x0; i = 0; stats = obj.LinearSolver.preprocess(f, t, xn, true, mass_scale, jac_scale, stats); @@ -38,7 +34,7 @@ xnf = f.F(t,xn); - b = -mx .* (mass_scale) + sys_const + jac_scale * xnf; + b = mx .* (-mass_scale) - sys_const + (-jac_scale) .* xnf; [w_i, stats] = obj.LinearSolver.solve(b, stats); xtn = w_i + xn; @@ -55,9 +51,9 @@ if i == obj.MaxIterations %TODO Add Flags - optout = false; + out_opts.convergenceFailure = true; else - optout = true; + out_opts.convergenceFailure = false; end end diff --git a/src/+matlode/+nonlinearsolver/Newton.m b/src/+matlode/+nonlinearsolver/Newton.m index b59f5f2..7ff9496 100644 --- a/src/+matlode/+nonlinearsolver/Newton.m +++ b/src/+matlode/+nonlinearsolver/Newton.m @@ -11,7 +11,7 @@ obj = obj@matlode.nonlinearsolver.NonlinearSolver(linsolve, args{:}); end - function [xn, xnf, optout, stats] = solve(obj, f, t, x0, sys_const, mass_scale, jac_scale, ~, stats) + function [xn, xnf, out_opts, stats] = solve(obj, f, t, x0, sys_const, mass_scale, jac_scale, ~, stats) xn = x0; i = 0; while i < obj.MaxIterations @@ -40,9 +40,9 @@ if i == obj.MaxIterations %TODO Add Flags - optout = false; + out_opts.convergenceFailure = true; else - optout = true; + out_opts.convergenceFailure = false; end end diff --git a/src/+matlode/+nonlinearsolver/NonlinearSolver.m b/src/+matlode/+nonlinearsolver/NonlinearSolver.m index 7fc76f0..36a9a95 100644 --- a/src/+matlode/+nonlinearsolver/NonlinearSolver.m +++ b/src/+matlode/+nonlinearsolver/NonlinearSolver.m @@ -9,6 +9,9 @@ LinearSolver MaxIterations Tolerance + AbsTol + RelTol + RatioTol end methods @@ -33,7 +36,7 @@ methods(Abstract) - [xn, xnf, optout, stats] = solve(obj,f, t, x0, sys_const, mass_scale, jac_scale, optin, stats); + [xn, xnf, out_opts, stats] = solve(obj,f, t, x0, sys_const, mass_scale, jac_scale, optin, stats); end end diff --git a/src/+matlode/+nonlinearsolver/QuasiNewton.m b/src/+matlode/+nonlinearsolver/QuasiNewton.m deleted file mode 100644 index 1cb924a..0000000 --- a/src/+matlode/+nonlinearsolver/QuasiNewton.m +++ /dev/null @@ -1,23 +0,0 @@ -classdef untitled - %UNTITLED Summary of this class goes here - % Detailed explanation goes here - - properties - Property1 - end - - methods - function obj = untitled(inputArg1,inputArg2) - %UNTITLED Construct an instance of this class - % Detailed explanation goes here - obj.Property1 = inputArg1 + inputArg2; - end - - function outputArg = method1(obj,inputArg) - %METHOD1 Summary of this method goes here - % Detailed explanation goes here - outputArg = obj.Property1 + inputArg; - end - end -end - diff --git a/src/+matlode/+rk/+dirk/DIRK.m b/src/+matlode/+rk/+dirk/DIRK.m index bc3823c..414a19f 100644 --- a/src/+matlode/+rk/+dirk/DIRK.m +++ b/src/+matlode/+rk/+dirk/DIRK.m @@ -18,6 +18,9 @@ obj = obj@matlode.rk.RungeKutta(a, b, bHat, c, e, order, embeddedOrder); obj.StifflyAccurate = all(obj.A(end,:) == obj.B(:)'); + if obj.FSAL + + end end end @@ -36,43 +39,40 @@ obj.NonLinearSolver = opts.NonLinearSolver; end - function [ynew, stages, stats] = timeStep(obj, f, t, y, dt, stages, prevAccept, stats) - persistent fsal s a b c - if isempty(fsal) - fsal = obj.FsalStart; - s = obj.StageNum; - a = obj.A; - b = obj.B; - c = obj.C; - end - - if fsal && prevAccept + function [ynew, stages, stats, out_opts] = timeStep(obj, f, t, y, dt, stages, prevAccept, stats) + if obj.FSAL && prevAccept stages(:, 1) = stages(:, end); end + ynew = y; %TODO: Update PorMAss - for i = fsal:s - ynew = y; + for i = obj.FsalStart:obj.StageNum + g_const = 0; for j = 1:i-1 - if a(i,j) ~= 0 - ynew = ynew + stages(:, j) .* (dt * a(i, j)); + if obj.A(i,j) ~= 0 + g_const = g_const + stages(:, j) .* (dt * obj.A(i, j)); end end - thc = t + dt .* c(i); + thc = t + dt .* obj.C(i); - %TODO: Setup NonLin Flags %Solve Nonlinear System - [ynew, stages(:,i), ~, stats] = obj.NonLinearSolver.solve(f, thc, ynew, ynew, 1, dt .* a(i,i), [], stats); + [ynew, stages(:,i), solver_opts, stats] = obj.NonLinearSolver.solve(f, thc, ynew, -ynew, 1, -dt .* obj.A(i,i), [], stats); + if solver_opts.convergenceFailure == true + out_opts.failure = true; + return; + end end if ~obj.StifflyAccurate ynew = y; - for i = 1:s - if b(i) ~= 0 - ynew = ynew + stages(:, i) .* (dt .* b(i)); + for i = 1:obj.StageNum + if obj.B(i) ~= 0 + ynew = ynew + stages(:, i) .* (dt .* obj.B(i)); end end end + + out_opts.failure = false; end end diff --git a/src/+matlode/+rk/+erk/ERK.m b/src/+matlode/+rk/+erk/ERK.m index ffa10f5..5d2684c 100644 --- a/src/+matlode/+rk/+erk/ERK.m +++ b/src/+matlode/+rk/+erk/ERK.m @@ -21,40 +21,33 @@ opts = matlodeSets@matlode.rk.RungeKutta(obj, p, varargin{:}); end - function [ynew, stages, stats] = timeStep(obj, f, t, y, dt, stages, prevAccept, stats) - persistent fsal s a b c fevalIterCounts - if isempty(fsal) - fsal = obj.FsalStart; - s = obj.StageNum; - a = obj.A; - b = obj.B; - c = obj.C; - fevalIterCounts = double(obj.StageNum - obj.FsalStart + 1); - end - - if fsal && prevAccept + function [ynew, stages, stats, out_opts] = timeStep(obj, f, t, y, dt, stages, prevAccept, stats) + + if obj.FsalStart && prevAccept stages(:, 1) = stages(:, end); end %TODO: Update PorMass - for i = fsal:s + for i = obj.FsalStart:obj.StageNum ynew = y; for j = 1:i-1 - if a(i,j) ~= 0 - ynew = ynew + stages(:, j) .* (dt .* a(i, j)); + if obj.A(i,j) ~= 0 + ynew = ynew + stages(:, j) .* (dt .* obj.A(i, j)); end end - thc = t + dt .* c(i); + thc = t + dt .* obj.C(i); stages(:, i) = f.F(thc, ynew); end ynew = y; - for i = 1:s - if b(i) ~= 0 - ynew = ynew + stages(:, i) .* (dt .* b(i)); + for i = 1:obj.StageNum + if obj.B(i) ~= 0 + ynew = ynew + stages(:, i) .* (dt .* obj.B(i)); end end - stats.nFevals = stats.nFevals + fevalIterCounts; + stats.nFevals = stats.nFevals + double(obj.StageNum - obj.FsalStart + 1); + + out_opts.failure = false; end end end diff --git a/src/+matlode/+rk/RungeKutta.m b/src/+matlode/+rk/RungeKutta.m index ff626af..fb24ec4 100644 --- a/src/+matlode/+rk/RungeKutta.m +++ b/src/+matlode/+rk/RungeKutta.m @@ -34,27 +34,7 @@ obj.FSAL = all(a(end, :) == b) && all(a(1, :) == 0); obj.FsalStart = uint32(obj.FSAL) + 1; obj.DenseOut = matlode.denseoutput.Linear(obj.B); - end - - function obj = fromCoeffs(a, b, bHat, c, e, bTheta, order, embededOrder) - - - if istril(a) - if any(diag(a)) - %TODO - % set as DIRK - return - end - - obj = ERK(a, b, bHat, c, e, bTheta, order, embededOrder); - return - end - - %TODO - - %Set as FIRK - - end + end end methods (Access = protected) @@ -85,21 +65,18 @@ q = min(obj.Order, obj.EmbeddedOrder); end - function [err, stats] = timeStepErr(obj, ~, ~, y, ynew, dt, stages, ErrNorm, stats) - persistent e s - if isempty(e) - e = obj.E; - s = obj.StageNum; - end + function [err, stats, out_opts] = timeStepErr(obj, ~, ~, y, ynew, dt, stages, ErrNorm, stats) + %TODO: Update for Mass yerror = 0; - for i = 1:s - if e(i) ~= 0 - yerror = yerror + stages(:, i) .* (dt .* e(i)); + for i = 1:obj.StageNum + if obj.E(i) ~= 0 + yerror = yerror + stages(:, i) .* (dt .* obj.E(i)); end end err = ErrNorm.errEstimate(y, ynew, yerror); + out_opts.failure = false; end end diff --git a/src/+matlode/+rosenbrock/Rosenbrock.m b/src/+matlode/+rosenbrock/Rosenbrock.m index b7537d5..d7a6e77 100644 --- a/src/+matlode/+rosenbrock/Rosenbrock.m +++ b/src/+matlode/+rosenbrock/Rosenbrock.m @@ -131,7 +131,7 @@ end %% Time step - function [ynew, stages, stats] = timeStep(obj, f, t, y, dt, stages, prevAccept, stats) + function [ynew, stages, stats, out_opts] = timeStep(obj, f, t, y, dt, stages, prevAccept, stats) if obj.FSAL && prevAccept stages(:, 1) = stages(:, end); @@ -180,10 +180,11 @@ end stats.nFevals = stats.nFevals + obj.StageNum - uint16(prevAccept); + out_opts.failure = false; end %% Error Estimate - function [err, stats] = timeStepErr(obj, ~, ~, y, ynew, ~, stages, ErrNorm, stats) + function [err, stats, out_opts] = timeStepErr(obj, ~, ~, y, ynew, ~, stages, ErrNorm, stats) y_error = 0; for i = 1:obj.StageNum @@ -193,6 +194,7 @@ end err = ErrNorm.errEstimate(y, ynew, y_error); + out_opts.failure = false; end function [stages, stats] = timeLoopBeforeLoop(obj, f, f0, t0, y0, stats) diff --git a/src/+matlode/Integrator.m b/src/+matlode/Integrator.m index 797e712..c708370 100644 --- a/src/+matlode/Integrator.m +++ b/src/+matlode/Integrator.m @@ -16,8 +16,8 @@ methods (Abstract, Access = protected) [t, y, stats] = timeLoop(obj, f, tspan, y0, opts); [t, y, stats] = timeLoopFixed(obj, f, tspan, y0, opts); - [ynew, stages, stats] = timeStep(obj, f, t, y, dt, stages, prevAccept, stats); - [err, stats] = timeStepErr(obj, f, t, y, ynew, dt, stages, ErrNorm, stats); + [ynew, stages, stats, out_opts] = timeStep(obj, f, t, y, dt, stages, prevAccept, stats); + [err, stats, out_opts] = timeStepErr(obj, f, t, y, ynew, dt, stages, ErrNorm, stats); [stages, stats] = timeLoopBeforeLoop(obj, f, f0, t0, y0, stats); [q] = timeLoopInit(obj); end @@ -85,7 +85,7 @@ fmod = matlode.Model(f, varargin{:}); - [t, y, stats] = obj.timeLoop(fmod, tspan, y0, opts); + [t, y, stats] = obj.timeLoopFixed(fmod, tspan, y0, opts); sol.t = t; sol.y = y; diff --git a/src/+matlode/OneStepIntegrator.m b/src/+matlode/OneStepIntegrator.m index fa9b49e..897f887 100644 --- a/src/+matlode/OneStepIntegrator.m +++ b/src/+matlode/OneStepIntegrator.m @@ -92,36 +92,45 @@ %Will keep looping until accepted step while true - [ynext, stages, stats] = timeStep(obj, f, tcur, ycur, dtcur, stages, prevAccept, stats); - [err(:, 1), stats] = timeStepErr(obj, f, tcur, ycur, ynext, dtcur, stages, errNorm, stats); - - %Find next Step - %TODO: Update to take stats - [prevAccept, dtnext, tnext] = stepController.newStepSize(prevAccept, tcur, tspan, dthist, err, q); - - %Check if step is really small - if abs(dtnext) < abs(dtmin) + [ynext, stages, stats, out_opts] = timeStep(obj, f, tcur, ycur, dtcur, stages, prevAccept, stats); + %Check if integration failed with the given time step + if out_opts.failure == false + [err(:, 1), stats, out_opts] = timeStepErr(obj, f, tcur, ycur, ynext, dtcur, stages, errNorm, stats); + + %Find next Step + %TODO: Update to take stats + [prevAccept, dtnext, tnext] = stepController.newStepSize(prevAccept, tcur, tspan, dthist, err, q); + else + %TODO setup with memory based time step controller + prevAccept = false; + %TODO: Allow factor to be choosen + dtnext = 0.1 * dtcur; + tnext = tcur; + end + + %Check if step is really small + if abs(dtnext) < abs(dtmin) %Prevents excessive output - if stats.nSmallSteps == 0 - warning('The step the integrator is taking extremely small, results may not be optimal') - end - %accept step since the step cannot get any smaller - stats.nSmallSteps = stats.nSmallSteps + 1; - dtnext = dtmin; - tnext = tcur + dtmin; - prevAccept = true; - break; - end - - %check step acception - if prevAccept - break; - end - - stats.nFailed = stats.nFailed + 1; - dtcur = dtnext; - dthist(1) = dtnext; - + if stats.nSmallSteps == 0 + warning('The step the integrator is taking extremely small, results may not be optimal') + end + %accept step since the step cannot get any smaller + stats.nSmallSteps = stats.nSmallSteps + 1; + dtnext = dtmin; + tnext = tcur + dtmin; + prevAccept = true; + break; + end + + %check step acception + if prevAccept + break; + end + + stats.nFailed = stats.nFailed + 1; + dtcur = dtnext; + dthist(1) = dtnext; + end %set new step to be in range diff --git a/src/+testingscripts/+nonlintest/TestChordDIRK.m b/src/+testingscripts/+nonlintest/TestChordDIRK.m index f55ed82..793ec36 100644 --- a/src/+testingscripts/+nonlintest/TestChordDIRK.m +++ b/src/+testingscripts/+nonlintest/TestChordDIRK.m @@ -2,18 +2,19 @@ format long e close all -integrator = matlode.rk.dirk.ESDIRK_4_3_6; +integrator = matlode.rk.dirk.SDIRK_2_1_2; -options.ErrNorm = matlode.errnorm.InfNorm(1e-6, 1e-6); +options.ErrNorm = matlode.errnorm.StandardNorm(1e-8, 1e-8); options.StepSizeController = matlode.stepsizecontroller.StandardController; -problem = otp.kpr.presets.Canonical; +problem = otp.robertson.presets.Canonical; sol = integrator.integrate(problem.RHS, problem.TimeSpan, problem.Y0, options); -sol_matlab = problem.solve('Solver', @ode45, 'RelTol', 1e-6, 'AbsTol', 1e-6); +sol_matlab = problem.solve('Solver', @ode23s, 'RelTol', 1e-8, 'AbsTol', 1e-8); -true_sol = problem.solve; +true_sol = problem.solve('RelTol', 1e-12, 'AbsTol', 1e-12); +% true_sol.y = exp(-1); norm(sol.y(:,end) - true_sol.y(:,end)) norm(sol_matlab.y(:,end) - true_sol.y(:,end)) diff --git a/src/+testingscripts/+nonlintest/TestNewtonDIRK.m b/src/+testingscripts/+nonlintest/TestNewtonDIRK.m index 7247602..535ff4b 100644 --- a/src/+testingscripts/+nonlintest/TestNewtonDIRK.m +++ b/src/+testingscripts/+nonlintest/TestNewtonDIRK.m @@ -2,13 +2,13 @@ format long e close all -integrator = matlode.rk.dirk.SDIRK_4_3_5; +integrator = matlode.rk.dirk.SDIRK_2_1_2; options.ErrNorm = matlode.errnorm.InfNorm(1e-6, 1e-6); options.StepSizeController = matlode.stepsizecontroller.StandardController; options.NonLinearSolver = matlode.nonlinearsolver.Newton; -problem = otp.kpr.presets.Canonical; +problem = otp.robertson.presets.Canonical; sol = integrator.integrate(problem.RHS, problem.TimeSpan, problem.Y0, options); From e5148b96aafc977a24e9deebf47f8e5713efddaf Mon Sep 17 00:00:00 2001 From: Alexander Novotny Date: Mon, 24 Feb 2025 12:39:41 -0500 Subject: [PATCH 14/17] Fixed issue with OneStepIntegrator time out --- src/+matlode/OneStepIntegrator.m | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/+matlode/OneStepIntegrator.m b/src/+matlode/OneStepIntegrator.m index 897f887..4ae2f06 100644 --- a/src/+matlode/OneStepIntegrator.m +++ b/src/+matlode/OneStepIntegrator.m @@ -243,10 +243,12 @@ if opts.FullTrajectory y(:, i + 1) = ynext; + t(i + 1) = tspan(:, i + 1); end end y(:, end) = ynext; + t(end) = tspan(:, end); end %Statistics intialization From de94c2bf5beb7b8e85c45df726710ce2dcb50df2 Mon Sep 17 00:00:00 2001 From: reid-g Date: Mon, 24 Feb 2025 20:07:26 -0500 Subject: [PATCH 15/17] Fixed Nonlinear Solve + Mass Working + Now works for Robertson Reaction + Now works for DAEs with Mass matrix --- .../+linearsolver/MatrixLinearSolver.m | 3 +- src/+matlode/+nonlinearsolver/Chord.m | 62 ++++++++++--------- src/+matlode/+nonlinearsolver/Newton.m | 54 ++++++++++------ .../+nonlinearsolver/NonlinearSolver.m | 4 +- src/+matlode/+rk/+dirk/DIRK.m | 10 ++- src/+matlode/+rk/+dirk/SDIRK_2_1_2.m | 4 +- src/+matlode/+rk/+erk/ClassicRK4.m | 2 +- src/+matlode/+rk/RungeKutta.m | 4 +- .../+nonlintest/TestChordDIRK.m | 4 +- .../+nonlintest/TestNewtonDIRK.m | 4 +- 10 files changed, 91 insertions(+), 60 deletions(-) diff --git a/src/+matlode/+linearsolver/MatrixLinearSolver.m b/src/+matlode/+linearsolver/MatrixLinearSolver.m index 90b07f0..cb543ec 100644 --- a/src/+matlode/+linearsolver/MatrixLinearSolver.m +++ b/src/+matlode/+linearsolver/MatrixLinearSolver.m @@ -48,7 +48,8 @@ obj.mass = f.Mass; end else - obj.mass = f.Mass(t,y); + %TODO: Only time matrix working + obj.mass = f.Mass(t); stats.nMassEvals = stats.nMassEvals + 1; end end diff --git a/src/+matlode/+nonlinearsolver/Chord.m b/src/+matlode/+nonlinearsolver/Chord.m index 0d28bcd..9568ebc 100644 --- a/src/+matlode/+nonlinearsolver/Chord.m +++ b/src/+matlode/+nonlinearsolver/Chord.m @@ -1,6 +1,10 @@ classdef Chord < matlode.nonlinearsolver.NonlinearSolver %CHORD Classic Chord method for solving nonlinear systems. Does not %calculate the jacobian more than once + properties(Access = protected) + q0 + t0_pre + end methods function obj = Chord(linsolve, args) @@ -11,51 +15,51 @@ obj = obj@matlode.nonlinearsolver.NonlinearSolver(linsolve, args{:}); end + + + function [out_opts, stats] = preprocess(obj, f, t0, y0, optin, stats) + % Preprocess to compute M(t_0) y_0 + [stats] = obj.LinearSolver.computeMass(f, t0, y0, stats); + if isempty(f.Mass) || ~isa(f.Mass, 'function_handle') + obj.q0 = 0; + else + obj.q0 = (obj.LinearSolver.mass * y0); + end + obj.t0_pre = t0; + out_opts = []; + end - function [xn, xnf, out_opts, stats] = solve(obj, f, t, x0, sys_const, mass_scale, jac_scale, ~, stats) + function [xn, out_opts, stats] = solve(obj, f, t, y0, x0, sys_const, mass_scale, jac_scale, ~, stats) + %Solve the nonlinear equation 0 = -M(t)z + -M(t)*y0 + q0 + const + a * f(t, y_0 + z) xn = x0; i = 0; - stats = obj.LinearSolver.preprocess(f, t, xn, true, mass_scale, jac_scale, stats); - if isa(obj.LinearSolver.mass,'function_handle') - mx = obj.LinearSolver.mass(xn); - else - mx = obj.LinearSolver.mass * xn; + y1 = y0 + x0; + + stats = obj.LinearSolver.preprocess(f, t, y1, true, -mass_scale, jac_scale, stats); + if ~(isempty(f.Mass) || ~isa(f.Mass, 'function_handle')) + sys_const = sys_const + mass_scale * (obj.q0 - obj.LinearSolver.mass * y0); end while i < obj.MaxIterations - if i ~= 0 - [stats] = obj.LinearSolver.computeMass(f, t, xn, stats); - if isa(obj.LinearSolver.mass,'function_handle') - mx = obj.LinearSolver.mass(xn); - else - mx = obj.LinearSolver.mass * xn; - end - end - + mx = obj.LinearSolver.mass * x0; + xnf = f.F(t, y1); + b = (-mass_scale) .* (mx) + sys_const + (jac_scale) .* xnf; + [w_i, stats] = obj.LinearSolver.solve(-b, stats); - xnf = f.F(t,xn); - b = mx .* (-mass_scale) - sys_const + (-jac_scale) .* xnf; - [w_i, stats] = obj.LinearSolver.solve(b, stats); - - xtn = w_i + xn; + xn = w_i + x0; + i = i + 1; if norm(w_i) < obj.Tolerance break; end - xn = xtn; + x0 = xn; + y1 = y0 + x0; end stats.nNonLinIterations = stats.nNonLinIterations + i; stats.nFevals = stats.nFevals + i; - - if i == obj.MaxIterations - %TODO Add Flags - out_opts.convergenceFailure = true; - else - out_opts.convergenceFailure = false; - end - + out_opts.convergenceFailure = i >= obj.MaxIterations; end end diff --git a/src/+matlode/+nonlinearsolver/Newton.m b/src/+matlode/+nonlinearsolver/Newton.m index 7ff9496..f4b12b1 100644 --- a/src/+matlode/+nonlinearsolver/Newton.m +++ b/src/+matlode/+nonlinearsolver/Newton.m @@ -1,5 +1,9 @@ classdef Newton < matlode.nonlinearsolver.NonlinearSolver %NEWTON Classic Newton method for solving nonlinear systems + properties(Access = protected) + q0 + t0_pre + end methods function obj = Newton(linsolve, args) @@ -10,41 +14,53 @@ obj = obj@matlode.nonlinearsolver.NonlinearSolver(linsolve, args{:}); end - - function [xn, xnf, out_opts, stats] = solve(obj, f, t, x0, sys_const, mass_scale, jac_scale, ~, stats) + + function [out_opts, stats] = preprocess(obj, f, t0, y0, optin, stats) + % Preprocess to compute M(t_0) y_0 + [stats] = obj.LinearSolver.computeMass(f, t0, y0, stats); + if isempty(f.Mass) || ~isa(f.Mass, 'function_handle') + obj.q0 = 0; + else + obj.q0 = (obj.LinearSolver.mass * y0); + end + obj.t0_pre = t0; + out_opts = []; + end + + + function [xn, out_opts, stats] = solve(obj, f, t, y0, x0, sys_const, mass_scale, jac_scale, ~, stats) + %Solve the nonlinear equation 0 = -M(t)z + -M(t)*y0 + q0 + const + a * f(t, y_0 + z) xn = x0; i = 0; + y1 = y0 + x0; + while i < obj.MaxIterations - stats = obj.LinearSolver.preprocess(f, t, xn, true, mass_scale, jac_scale, stats); - if isa(obj.LinearSolver.mass,'function_handle') - mx = obj.LinearSolver.mass(xn); + stats = obj.LinearSolver.preprocess(f, t, y1, true, -mass_scale, jac_scale, stats); + if ~(isempty(f.Mass) || ~isa(f.Mass, 'function_handle')) + b = sys_const + mass_scale * (obj.q0 - obj.LinearSolver.mass * y0); else - mx = obj.LinearSolver.mass * xn; + b = sys_const; end + mx = obj.LinearSolver.mass * x0; + xnf = f.F(t, y1); + b = b + (-mass_scale) .* (mx) + (jac_scale) .* xnf; + [w_i, stats] = obj.LinearSolver.solve(-b, stats); - xnf = f.F(t,xn); - b = -mx .* (mass_scale) + sys_const + jac_scale * xnf; - [w_i, stats] = obj.LinearSolver.solve(b, stats); - - xtn = w_i + xn; + xn = w_i + x0; + i = i + 1; if norm(w_i) < obj.Tolerance break; end - xn = xtn; + x0 = xn; + y1 = y0 + x0; end stats.nNonLinIterations = stats.nNonLinIterations + i; stats.nFevals = stats.nFevals + i; - if i == obj.MaxIterations - %TODO Add Flags - out_opts.convergenceFailure = true; - else - out_opts.convergenceFailure = false; - end - + out_opts.convergenceFailure = i >= obj.MaxIterations; end end end diff --git a/src/+matlode/+nonlinearsolver/NonlinearSolver.m b/src/+matlode/+nonlinearsolver/NonlinearSolver.m index 36a9a95..34caa30 100644 --- a/src/+matlode/+nonlinearsolver/NonlinearSolver.m +++ b/src/+matlode/+nonlinearsolver/NonlinearSolver.m @@ -35,8 +35,10 @@ end methods(Abstract) + + [out_opts, stats] = preprocess(obj, f, t0, y0, optin, stats); - [xn, xnf, out_opts, stats] = solve(obj,f, t, x0, sys_const, mass_scale, jac_scale, optin, stats); + [xn, xnf, out_opts, stats] = solve(obj, f, t, y0, x0, sys_const, mass_scale, jac_scale, optin, stats); end end diff --git a/src/+matlode/+rk/+dirk/DIRK.m b/src/+matlode/+rk/+dirk/DIRK.m index 414a19f..f86ada1 100644 --- a/src/+matlode/+rk/+dirk/DIRK.m +++ b/src/+matlode/+rk/+dirk/DIRK.m @@ -44,6 +44,9 @@ stages(:, 1) = stages(:, end); end ynew = y; + + %Setup Nonlinear Solver + [~, stats] = obj.NonLinearSolver.preprocess(f, t, y, [], stats); %TODO: Update PorMAss for i = obj.FsalStart:obj.StageNum @@ -56,18 +59,21 @@ thc = t + dt .* obj.C(i); %Solve Nonlinear System - [ynew, stages(:,i), solver_opts, stats] = obj.NonLinearSolver.solve(f, thc, ynew, -ynew, 1, -dt .* obj.A(i,i), [], stats); + [ydiff, solver_opts, stats] = obj.NonLinearSolver.solve(f, thc, y, zeros(length(y),1), g_const, 1, dt .* obj.A(i,i), [], stats); if solver_opts.convergenceFailure == true out_opts.failure = true; return; end + ynew = y + ydiff; + + stages(:,i) = f.F(thc, ynew); end if ~obj.StifflyAccurate ynew = y; for i = 1:obj.StageNum if obj.B(i) ~= 0 - ynew = ynew + stages(:, i) .* (dt .* obj.B(i)); + ynew = ynew + (dt .* obj.B(i)) .* stages(:, i); end end end diff --git a/src/+matlode/+rk/+dirk/SDIRK_2_1_2.m b/src/+matlode/+rk/+dirk/SDIRK_2_1_2.m index 3bb263e..acb70ff 100644 --- a/src/+matlode/+rk/+dirk/SDIRK_2_1_2.m +++ b/src/+matlode/+rk/+dirk/SDIRK_2_1_2.m @@ -16,9 +16,9 @@ b = caster('[1/sqrt(2), 1 - (1/sqrt(2))]'); - bHat = caster('[3/5, 2/5]'); + bHat = caster('[2/3, 1/3]'); - e = caster('[1/sqrt(2) - 3/5, 3/5 - 1/sqrt(2)]'); + e = caster('[1/sqrt(2) - 2/3, 2/3 - 1/sqrt(2)]'); c = caster('[1 - (1/sqrt(2)), 1]'); diff --git a/src/+matlode/+rk/+erk/ClassicRK4.m b/src/+matlode/+rk/+erk/ClassicRK4.m index 835adb2..107ad10 100644 --- a/src/+matlode/+rk/+erk/ClassicRK4.m +++ b/src/+matlode/+rk/+erk/ClassicRK4.m @@ -18,7 +18,7 @@ a = caster(join(['[[0, 0, 0, 0];',... '[1/2, 0, 0, 0];',... '[0, 1/2, 0, 0];',... - '[0, 0, 0, 1]]'])); + '[0, 0, 1, 0]]'])); b = caster('[1/6, 1/3, 1/3, 1/6]'); diff --git a/src/+matlode/+rk/RungeKutta.m b/src/+matlode/+rk/RungeKutta.m index fb24ec4..40cd9aa 100644 --- a/src/+matlode/+rk/RungeKutta.m +++ b/src/+matlode/+rk/RungeKutta.m @@ -71,8 +71,8 @@ %TODO: Update for Mass yerror = 0; for i = 1:obj.StageNum - if obj.E(i) ~= 0 - yerror = yerror + stages(:, i) .* (dt .* obj.E(i)); + if abs(obj.E(i)) > eps + yerror = yerror + (dt .* obj.E(i)) .* stages(:, i); end end err = ErrNorm.errEstimate(y, ynew, yerror); diff --git a/src/+testingscripts/+nonlintest/TestChordDIRK.m b/src/+testingscripts/+nonlintest/TestChordDIRK.m index 793ec36..ec20e29 100644 --- a/src/+testingscripts/+nonlintest/TestChordDIRK.m +++ b/src/+testingscripts/+nonlintest/TestChordDIRK.m @@ -9,9 +9,11 @@ problem = otp.robertson.presets.Canonical; +tic sol = integrator.integrate(problem.RHS, problem.TimeSpan, problem.Y0, options); +toc -sol_matlab = problem.solve('Solver', @ode23s, 'RelTol', 1e-8, 'AbsTol', 1e-8); +sol_matlab = problem.solve('Solver', @ode15s, 'RelTol', 1e-8, 'AbsTol', 1e-8); true_sol = problem.solve('RelTol', 1e-12, 'AbsTol', 1e-12); % true_sol.y = exp(-1); diff --git a/src/+testingscripts/+nonlintest/TestNewtonDIRK.m b/src/+testingscripts/+nonlintest/TestNewtonDIRK.m index 535ff4b..d3d8d57 100644 --- a/src/+testingscripts/+nonlintest/TestNewtonDIRK.m +++ b/src/+testingscripts/+nonlintest/TestNewtonDIRK.m @@ -4,7 +4,7 @@ integrator = matlode.rk.dirk.SDIRK_2_1_2; -options.ErrNorm = matlode.errnorm.InfNorm(1e-6, 1e-6); +options.ErrNorm = matlode.errnorm.InfNorm(1e-8, 1e-8); options.StepSizeController = matlode.stepsizecontroller.StandardController; options.NonLinearSolver = matlode.nonlinearsolver.Newton; @@ -12,7 +12,7 @@ sol = integrator.integrate(problem.RHS, problem.TimeSpan, problem.Y0, options); -sol_matlab = problem.solve('Solver', @ode45, 'RelTol', 1e-6, 'AbsTol', 1e-6); +sol_matlab = problem.solve('Solver', @ode15s, 'RelTol', 1e-8, 'AbsTol', 1e-8); true_sol = problem.solve; From eb839f6cd1b098092572ad247ea55ffdf874a77d Mon Sep 17 00:00:00 2001 From: reid-g Date: Wed, 26 Feb 2025 13:06:25 -0500 Subject: [PATCH 16/17] Improved DIRK formulation + ESDIRK + DAE Solving + Improved DIRK formulation + Added Z transformation + Added ESDIRK formulation + Added DAE solving capabilities (constant mass and time dependent mass) --- src/+matlode/+nonlinearsolver/Chord.m | 17 +- src/+matlode/+nonlinearsolver/Newton.m | 17 +- .../+nonlinearsolver/NonlinearSolver.m | 4 +- src/+matlode/+rk/+dirk/DIRK.m | 232 ++++++++++++++++-- src/+matlode/+rk/+erk/ERK.m | 15 ++ src/+matlode/+rk/RungeKutta.m | 14 -- .../+nonlintest/TestChordDIRK.m | 15 +- .../+nonlintest/TestNewtonDIRK.m | 7 +- 8 files changed, 257 insertions(+), 64 deletions(-) diff --git a/src/+matlode/+nonlinearsolver/Chord.m b/src/+matlode/+nonlinearsolver/Chord.m index 9568ebc..8d74803 100644 --- a/src/+matlode/+nonlinearsolver/Chord.m +++ b/src/+matlode/+nonlinearsolver/Chord.m @@ -29,21 +29,21 @@ out_opts = []; end - function [xn, out_opts, stats] = solve(obj, f, t, y0, x0, sys_const, mass_scale, jac_scale, ~, stats) - %Solve the nonlinear equation 0 = -M(t)z + -M(t)*y0 + q0 + const + a * f(t, y_0 + z) + function [xn, out_opts, stats] = solve(obj, f, t, dt, y0, x0, fn0, sys_const, mass_scale, jac_scale, ~, stats) + %Solve the nonlinear equation 0 = -M(t)z + const + a * f(t, y_0 + z) xn = x0; i = 0; y1 = y0 + x0; + intial_fun_cond = uint32(~isempty(fn0)); + if intial_fun_cond == 0 + fn0 = f.F(t, y1); + end stats = obj.LinearSolver.preprocess(f, t, y1, true, -mass_scale, jac_scale, stats); - if ~(isempty(f.Mass) || ~isa(f.Mass, 'function_handle')) - sys_const = sys_const + mass_scale * (obj.q0 - obj.LinearSolver.mass * y0); - end while i < obj.MaxIterations mx = obj.LinearSolver.mass * x0; - xnf = f.F(t, y1); - b = (-mass_scale) .* (mx) + sys_const + (jac_scale) .* xnf; + b = (-mass_scale) .* (mx) + sys_const + (jac_scale) .* fn0; [w_i, stats] = obj.LinearSolver.solve(-b, stats); xn = w_i + x0; @@ -55,9 +55,10 @@ end x0 = xn; y1 = y0 + x0; + fn0 = f.F(t, y1); end stats.nNonLinIterations = stats.nNonLinIterations + i; - stats.nFevals = stats.nFevals + i; + stats.nFevals = stats.nFevals + i - intial_fun_cond; out_opts.convergenceFailure = i >= obj.MaxIterations; end diff --git a/src/+matlode/+nonlinearsolver/Newton.m b/src/+matlode/+nonlinearsolver/Newton.m index f4b12b1..a6d3d07 100644 --- a/src/+matlode/+nonlinearsolver/Newton.m +++ b/src/+matlode/+nonlinearsolver/Newton.m @@ -28,23 +28,21 @@ end - function [xn, out_opts, stats] = solve(obj, f, t, y0, x0, sys_const, mass_scale, jac_scale, ~, stats) + function [xn, out_opts, stats] = solve(obj, f, t, dt, y0, x0, fn0, sys_const, mass_scale, jac_scale, ~, stats) %Solve the nonlinear equation 0 = -M(t)z + -M(t)*y0 + q0 + const + a * f(t, y_0 + z) xn = x0; i = 0; y1 = y0 + x0; + intial_fun_cond = uint32(~isempty(fn0)); + if intial_fun_cond == 0 + fn0 = f.F(t, y1); + end while i < obj.MaxIterations stats = obj.LinearSolver.preprocess(f, t, y1, true, -mass_scale, jac_scale, stats); - if ~(isempty(f.Mass) || ~isa(f.Mass, 'function_handle')) - b = sys_const + mass_scale * (obj.q0 - obj.LinearSolver.mass * y0); - else - b = sys_const; - end mx = obj.LinearSolver.mass * x0; - xnf = f.F(t, y1); - b = b + (-mass_scale) .* (mx) + (jac_scale) .* xnf; + b = (-mass_scale) .* (mx) + (jac_scale) .* fn0 + sys_const; [w_i, stats] = obj.LinearSolver.solve(-b, stats); xn = w_i + x0; @@ -56,9 +54,10 @@ end x0 = xn; y1 = y0 + x0; + fn0 = f.F(t, y1); end stats.nNonLinIterations = stats.nNonLinIterations + i; - stats.nFevals = stats.nFevals + i; + stats.nFevals = stats.nFevals + i - intial_fun_cond; out_opts.convergenceFailure = i >= obj.MaxIterations; end diff --git a/src/+matlode/+nonlinearsolver/NonlinearSolver.m b/src/+matlode/+nonlinearsolver/NonlinearSolver.m index 34caa30..f9255a6 100644 --- a/src/+matlode/+nonlinearsolver/NonlinearSolver.m +++ b/src/+matlode/+nonlinearsolver/NonlinearSolver.m @@ -5,7 +5,7 @@ NonLinearArgs end - properties (SetAccess = protected) + properties (SetAccess = protected, GetAccess = public) LinearSolver MaxIterations Tolerance @@ -38,7 +38,7 @@ [out_opts, stats] = preprocess(obj, f, t0, y0, optin, stats); - [xn, xnf, out_opts, stats] = solve(obj, f, t, y0, x0, sys_const, mass_scale, jac_scale, optin, stats); + [xn, xnf, out_opts, stats] = solve(obj, f, t, dt, y0, x0, fn0, sys_const, mass_scale, jac_scale, optin, stats); end end diff --git a/src/+matlode/+rk/+dirk/DIRK.m b/src/+matlode/+rk/+dirk/DIRK.m index f86ada1..4f12050 100644 --- a/src/+matlode/+rk/+dirk/DIRK.m +++ b/src/+matlode/+rk/+dirk/DIRK.m @@ -5,11 +5,27 @@ PartitionMethod = false; PartitionNum = 1; MultirateMethod = false; + + %Using Z_Transformation varies. At times false can be faster even + %with the more Feval. The nonlinear system seems better + %conditioned. However, z transformation works best for DAEs. SDIRK + %can work for M(t) DAEs only under Z-transformation + Z_Transformation = true; end properties NonLinearSolver + end + + properties (SetAccess = protected) + D + Theta + ET + Alpha StifflyAccurate + ESDIRK + + falseYDiff end @@ -18,8 +34,55 @@ obj = obj@matlode.rk.RungeKutta(a, b, bHat, c, e, order, embeddedOrder); obj.StifflyAccurate = all(obj.A(end,:) == obj.B(:)'); - if obj.FSAL - + %TODO: Change to coefficents provides to prevent accuracy loss. + %Similar to the Rosenbrock methods + + %Note is only for SDIRK and ESDIRK methods. FIRK and other + %will require more care. Firk requries full inverses + %ESDIRK only utilizes the sub matrices inverse + obj.ESDIRK = all(obj.A(1, :) == 0); + if (obj.ESDIRK) + + %Note the first stage is treated differently in the z + %transformation. Adding the effects to the intial stage are + %nesscary + q = -(obj.A(2:end, 2:end) \ obj.A(2:end, 1)); + + obj.D = zeros(1,obj.StageNum); + obj.D(1) = obj.B(1) + obj.B(1,2:end)*q; + if obj.StifflyAccurate + obj.D(1,end) = 1; + else + obj.D(1,2:end) = (obj.A(2:end, 2:end)' \ obj.B(2:end)')'; + end + + obj.ET = zeros(1, obj.StageNum); + if obj.Adaptive + obj.ET(1) = obj.E(1) + obj.E(1,2:end)*q; + obj.ET(1,2:end) = (obj.A(2:end, 2:end)' \ (obj.E(2:end))')'; + else + obj.ET = []; + end + + obj.Theta = zeros(obj.StageNum, obj.StageNum); + obj.Theta(2:end, 2:end) = obj.A(2:end, 2:end) \ tril(obj.A(2:end, 2:end), -1); + obj.Theta(2:end, 1) = obj.A(2:end,1) + tril(obj.A(2:end, 2:end), -1)*q; + + else + if obj.StifflyAccurate + obj.D = zeros(1,obj.StageNum); + obj.D(1,end) = 1; + else + obj.D = (obj.A' \ obj.B')'; + end + + if obj.Adaptive + obj.ET = (obj.A' \ (obj.E)')'; + else + obj.ET = obj.E; + end + + obj.Theta = obj.A \ tril(obj.A, -1); end end @@ -40,47 +103,164 @@ end function [ynew, stages, stats, out_opts] = timeStep(obj, f, t, y, dt, stages, prevAccept, stats) + if isa(f.Mass,'function_handle') && (~obj.Z_Transformation || obj.ESDIRK) + error('Not Supported method for time dependent Mass Matrix M(t)') + end + if obj.FSAL && prevAccept - stages(:, 1) = stages(:, end); + if obj.Z_Transformation + stages(:, 1) = f.F(t,y); + + stats.nFevals = stats.nFevals + 1; + else + stages(:, 1) = stages(:, end); + end end ynew = y; %Setup Nonlinear Solver [~, stats] = obj.NonLinearSolver.preprocess(f, t, y, [], stats); - %TODO: Update PorMAss - for i = obj.FsalStart:obj.StageNum - g_const = 0; - for j = 1:i-1 - if obj.A(i,j) ~= 0 - g_const = g_const + stages(:, j) .* (dt * obj.A(i, j)); + if obj.Z_Transformation + ydiff = zeros(length(y),1); + fd0 = []; + for i = obj.FsalStart:obj.StageNum + + g_const = zeros(length(y),1); + for j = 2:i-1 + if abs(obj.Theta(i,j)) > eps + g_const = g_const + (obj.Theta(i, j)) .* stages(:, j) ; + end + end + thc = t + obj.C(i) .* dt ; + + if ~obj.ESDIRK && i > 1 && abs(obj.Theta(i,1)) > eps + g_const = g_const + obj.Theta(i,1) .* stages(:,1); end - end - thc = t + dt .* obj.C(i); - - %Solve Nonlinear System - [ydiff, solver_opts, stats] = obj.NonLinearSolver.solve(f, thc, y, zeros(length(y),1), g_const, 1, dt .* obj.A(i,i), [], stats); - if solver_opts.convergenceFailure == true - out_opts.failure = true; - return; - end - ynew = y + ydiff; - stages(:,i) = f.F(thc, ynew); - end + [stats] = obj.NonLinearSolver.LinearSolver.computeMass(f, thc, y, stats); + g_const = (obj.NonLinearSolver.LinearSolver.mass * g_const); - if ~obj.StifflyAccurate - ynew = y; - for i = 1:obj.StageNum - if obj.B(i) ~= 0 - ynew = ynew + (dt .* obj.B(i)) .* stages(:, i); + if obj.ESDIRK && i > 1 && abs(obj.Theta(i,1)) > eps + g_const = g_const + dt * obj.Theta(i,1) .* stages(:,1); + end + + %Solve Nonlinear System + [stages(:,i), solver_opts, stats] = obj.NonLinearSolver.solve(f, thc, dt, y, ydiff, fd0, g_const, 1, dt .* obj.A(i,i), [], stats); + ydiff = stages(:,i); + obj.falseYDiff(:,i) = ydiff; + if solver_opts.convergenceFailure == true + out_opts.failure = true; + return; end end + + if obj.ESDIRK + if abs(obj.D(1)) > eps + ynew = ynew + dt * (obj.D(i)) .* stages(:, 1); + end + end + + if ~obj.StifflyAccurate + esdirkstart = uint32(obj.ESDIRK) + 1; + for i = esdirkstart:obj.StageNum + if abs(obj.D(i)) > eps + ynew = ynew + (obj.D(i)) .* stages(:, i); + end + end + else + ynew = ynew + stages(:,end); + end + else + obj.falseYDiff = zeros(length(y),obj.StageNum); + ydiff = zeros(length(y),1); + if obj.ESDIRK + fd0 = stages(:,1); + else + fd0 = []; + end + + for i = obj.FsalStart:obj.StageNum + g_const = zeros(length(y),1); + for j = 1:i-1 + if obj.A(i,j) ~= 0 + g_const = g_const + (dt * obj.A(i, j)) .* stages(:, j) ; + end + end + thc = t + obj.C(i) .* dt; + + %Solve Nonlinear System + [ydiff, solver_opts, stats] = obj.NonLinearSolver.solve(f, thc, dt, y, ydiff, fd0, g_const, 1, dt .* obj.A(i,i), [], stats); + if solver_opts.convergenceFailure == true + out_opts.failure = true; + return; + end + ynew = y + ydiff; + obj.falseYDiff(:,i) = ydiff; + + stages(:,i) = f.F(thc, ynew); + fd0 = stages(:,i); + end + stats.nFevals = stats.nFevals + obj.StageNum; + + if ~obj.StifflyAccurate + ynew = y; + for i = 1:obj.StageNum + if obj.B(i) ~= 0 + ynew = ynew + (dt .* obj.B(i)) .* stages(:, i); + end + end + end + end out_opts.failure = false; end + + + function [err, stats, out_opts] = timeStepErr(obj, f, t, y, ynew, dt, stages, ErrNorm, stats) + yerror = 0; + if obj.Z_Transformation + if obj.ESDIRK + if abs(obj.ET(1)) > eps + yerror = dt*(obj.ET(1)) .* stages(:, 1); + end + end + esdirkstart = uint32(obj.ESDIRK) + 1; + for i = esdirkstart:obj.StageNum + if abs(obj.ET(i)) > eps + yerror = yerror + (obj.ET(i)) .* obj.falseYDiff(:, i); + end + end + + else + % for i = 1:obj.StageNum + % if abs(obj.E(i)) > eps + % yerror = yerror + (dt .* obj.E(i)) .* stages(:, i); + % end + % end + + if obj.ESDIRK + if abs(obj.ET(1)) > eps + yerror = dt*(obj.ET(1)) .* stages(:, 1); + end + end + esdirkstart = uint32(obj.ESDIRK) + 1; + + for i = esdirkstart:obj.StageNum + if abs(obj.ET(i)) > eps + yerror = yerror + (obj.ET(i)) .* obj.falseYDiff(:, i); + end + end + % [stats] = obj.NonLinearSolver.LinearSolver.computeMass(f, t + dt, ynew, stats); + % yerror = (obj.NonLinearSolver.LinearSolver.mass * yerror); + % [yerror, stats] = obj.NonLinearSolver.LinearSolver.solve(yerror, stats); + end + + err = ErrNorm.errEstimate(y, ynew, yerror); + out_opts.failure = false; + end end end diff --git a/src/+matlode/+rk/+erk/ERK.m b/src/+matlode/+rk/+erk/ERK.m index 5d2684c..4e04a02 100644 --- a/src/+matlode/+rk/+erk/ERK.m +++ b/src/+matlode/+rk/+erk/ERK.m @@ -49,6 +49,21 @@ out_opts.failure = false; end + + + function [err, stats, out_opts] = timeStepErr(obj, ~, ~, y, ynew, dt, stages, ErrNorm, stats) + + + %TODO: Update for Mass + yerror = 0; + for i = 1:obj.StageNum + if abs(obj.E(i)) > eps + yerror = yerror + (dt .* obj.E(i)) .* stages(:, i); + end + end + err = ErrNorm.errEstimate(y, ynew, yerror); + out_opts.failure = false; + end end end diff --git a/src/+matlode/+rk/RungeKutta.m b/src/+matlode/+rk/RungeKutta.m index 40cd9aa..81460ca 100644 --- a/src/+matlode/+rk/RungeKutta.m +++ b/src/+matlode/+rk/RungeKutta.m @@ -64,20 +64,6 @@ function [q] = timeLoopInit(obj) q = min(obj.Order, obj.EmbeddedOrder); end - - function [err, stats, out_opts] = timeStepErr(obj, ~, ~, y, ynew, dt, stages, ErrNorm, stats) - - - %TODO: Update for Mass - yerror = 0; - for i = 1:obj.StageNum - if abs(obj.E(i)) > eps - yerror = yerror + (dt .* obj.E(i)) .* stages(:, i); - end - end - err = ErrNorm.errEstimate(y, ynew, yerror); - out_opts.failure = false; - end end diff --git a/src/+testingscripts/+nonlintest/TestChordDIRK.m b/src/+testingscripts/+nonlintest/TestChordDIRK.m index ec20e29..d9d0cff 100644 --- a/src/+testingscripts/+nonlintest/TestChordDIRK.m +++ b/src/+testingscripts/+nonlintest/TestChordDIRK.m @@ -4,20 +4,29 @@ integrator = matlode.rk.dirk.SDIRK_2_1_2; -options.ErrNorm = matlode.errnorm.StandardNorm(1e-8, 1e-8); +options.ErrNorm = matlode.errnorm.StandardNorm(1e-7, 1e-7); options.StepSizeController = matlode.stepsizecontroller.StandardController; -problem = otp.robertson.presets.Canonical; +problem = otp.ascherlineardae.presets.Canonical; tic sol = integrator.integrate(problem.RHS, problem.TimeSpan, problem.Y0, options); toc +tic sol_matlab = problem.solve('Solver', @ode15s, 'RelTol', 1e-8, 'AbsTol', 1e-8); +toc -true_sol = problem.solve('RelTol', 1e-12, 'AbsTol', 1e-12); +% beta = problem.Parameters.Beta; +% t = problem.TimeSpan(end); +% true_sol.y = [t .* sin(t) + (1 + beta * t) .* exp(-t); ... +% beta * exp(-t) + sin(t)]; + +true_sol = problem.solve( 'RelTol', 1e-10, 'AbsTol', 1e-10); % true_sol.y = exp(-1); norm(sol.y(:,end) - true_sol.y(:,end)) norm(sol_matlab.y(:,end) - true_sol.y(:,end)) norm(sol.y(:,end) - sol_matlab.y(:,end)) + +sol.stats diff --git a/src/+testingscripts/+nonlintest/TestNewtonDIRK.m b/src/+testingscripts/+nonlintest/TestNewtonDIRK.m index d3d8d57..2f40d84 100644 --- a/src/+testingscripts/+nonlintest/TestNewtonDIRK.m +++ b/src/+testingscripts/+nonlintest/TestNewtonDIRK.m @@ -2,15 +2,17 @@ format long e close all -integrator = matlode.rk.dirk.SDIRK_2_1_2; +integrator = matlode.rk.dirk.ESDIRK_2_1_3; -options.ErrNorm = matlode.errnorm.InfNorm(1e-8, 1e-8); +options.ErrNorm = matlode.errnorm.InfNorm(1e-7, 1e-7); options.StepSizeController = matlode.stepsizecontroller.StandardController; options.NonLinearSolver = matlode.nonlinearsolver.Newton; problem = otp.robertson.presets.Canonical; +tic sol = integrator.integrate(problem.RHS, problem.TimeSpan, problem.Y0, options); +toc sol_matlab = problem.solve('Solver', @ode15s, 'RelTol', 1e-8, 'AbsTol', 1e-8); @@ -19,3 +21,4 @@ norm(sol.y(:,end) - true_sol.y(:,end)) norm(sol_matlab.y(:,end) - true_sol.y(:,end)) norm(sol.y(:,end) - sol_matlab.y(:,end)) +sol.stats From 0f4c740d91080d21e119361bb9b52579b1b026f7 Mon Sep 17 00:00:00 2001 From: reid-g Date: Wed, 26 Feb 2025 18:31:58 -0500 Subject: [PATCH 17/17] Added Adaptive Nonlinear Solver + Fixed Point + Added basic Adaptive Chord method + Added Fixed Point Solve --- src/+matlode/+nonlinearsolver/AdaptiveChord.m | 96 +++++++++++++++++++ src/+matlode/+nonlinearsolver/Chord.m | 2 +- src/+matlode/+nonlinearsolver/FixedPoint.m | 67 +++++++++++++ src/+matlode/+nonlinearsolver/Newton.m | 2 +- .../+nonlinearsolver/NonlinearSolver.m | 2 +- src/+matlode/+rk/+dirk/DIRK.m | 24 ++++- .../+nonlintest/TestAdaptiveChordDIRK.m | 33 +++++++ .../+nonlintest/TestFixedPointDIRK.m | 33 +++++++ 8 files changed, 252 insertions(+), 7 deletions(-) create mode 100644 src/+matlode/+nonlinearsolver/AdaptiveChord.m create mode 100644 src/+matlode/+nonlinearsolver/FixedPoint.m create mode 100644 src/+testingscripts/+nonlintest/TestAdaptiveChordDIRK.m create mode 100644 src/+testingscripts/+nonlintest/TestFixedPointDIRK.m diff --git a/src/+matlode/+nonlinearsolver/AdaptiveChord.m b/src/+matlode/+nonlinearsolver/AdaptiveChord.m new file mode 100644 index 0000000..397dd18 --- /dev/null +++ b/src/+matlode/+nonlinearsolver/AdaptiveChord.m @@ -0,0 +1,96 @@ +classdef AdaptiveChord < matlode.nonlinearsolver.NonlinearSolver + %An adaptive nonlinear solver utilizing Chord. If the problem doesn't + %converge fast enough it will fail and ask for an easier problem + + properties(Access = protected) + t0_pre + end + + methods + function obj = AdaptiveChord(linsolve, args) + arguments + linsolve(1,1) matlode.linearsolver.LinearSolver = matlode.linearsolver.MatrixLinearSolver(); + args(1,:) cell = {}; + end + + obj = obj@matlode.nonlinearsolver.NonlinearSolver(linsolve, args{:}); + end + + + function [out_opts, stats] = preprocess(obj, f, t0, y0, mass_scale, jac_scale, optin, stats) + % Preprocess the system + + stats = obj.LinearSolver.preprocess(f, t0, y0, true, -mass_scale, jac_scale, stats); + + obj.t0_pre = t0; + out_opts = []; + end + + function [xn, out_opts, stats] = solve(obj, f, t, dt, y0, x0, fn0, sys_const, mass_scale, jac_scale, ~, stats) + %Solve the nonlinear equation 0 = -M(t)z + const + a * f(t, y_0 + z) + xn = x0; + i = 0; + y1 = y0 + x0; + intial_fun_cond = uint32(~isempty(fn0)); + if intial_fun_cond == 0 + fn0 = f.F(t, y1); + end + out_opts.convergenceFailure = false; + + %Cannot assume that the system has updated mass to use for RHS + %in the time depedent case + [stats] = obj.LinearSolver.computeMass(f, t, y1, stats); + + nuRate = 2; + deltaZ = 0; + deltaZold = 0; + + + while i < obj.MaxIterations + mx = obj.LinearSolver.mass * x0; + b = (-mass_scale) .* (mx) + sys_const + (jac_scale) .* fn0; + [w_i, stats] = obj.LinearSolver.solve(-b, stats); + + xn = w_i + x0; + + deltaZ = norm(w_i); + + if i > 0 + thetaRate = deltaZ ./ deltaZold; + if thetaRate < 0.99 + nuRate = thetaRate ./ (1.0 - thetaRate); + errPred = deltaZ .* thetaRate.^(obj.MaxIterations - i) ./ (1 - thetaRate); + + if errPred >= obj.Tolerance + out_opts.convergenceFailure = true; + break; + end + else + out_opts.convergenceFailure = true; + break; + end + end + + i = i + 1; + + if nuRate * deltaZ < obj.Tolerance + break; + end + + deltaZold = deltaZ; + x0 = xn; + y1 = y0 + x0; + fn0 = f.F(t, y1); + end + stats.nNonLinIterations = stats.nNonLinIterations + i; + stats.nFevals = stats.nFevals + i - intial_fun_cond; + + out_opts.convergenceFailure = (out_opts.convergenceFailure || i >= obj.MaxIterations); + end + end + + methods (Access=private) + + end +end + diff --git a/src/+matlode/+nonlinearsolver/Chord.m b/src/+matlode/+nonlinearsolver/Chord.m index 8d74803..ed436a9 100644 --- a/src/+matlode/+nonlinearsolver/Chord.m +++ b/src/+matlode/+nonlinearsolver/Chord.m @@ -17,7 +17,7 @@ end - function [out_opts, stats] = preprocess(obj, f, t0, y0, optin, stats) + function [out_opts, stats] = preprocess(obj, f, t0, y0, mass_scale, jac_scale, optin, stats) % Preprocess to compute M(t_0) y_0 [stats] = obj.LinearSolver.computeMass(f, t0, y0, stats); if isempty(f.Mass) || ~isa(f.Mass, 'function_handle') diff --git a/src/+matlode/+nonlinearsolver/FixedPoint.m b/src/+matlode/+nonlinearsolver/FixedPoint.m new file mode 100644 index 0000000..9d7bcd7 --- /dev/null +++ b/src/+matlode/+nonlinearsolver/FixedPoint.m @@ -0,0 +1,67 @@ +classdef FixedPoint < matlode.nonlinearsolver.NonlinearSolver + %CHORD Classic Fixed Point iteration + properties(Access = protected) + t0_pre + end + + methods + function obj = FixedPoint(linsolve, args) + arguments + linsolve(1,1) matlode.linearsolver.LinearSolver = matlode.linearsolver.MatrixLinearSolver(); + args(1,:) cell = {}; + end + + obj = obj@matlode.nonlinearsolver.NonlinearSolver(linsolve, args{:}); + end + + + function [out_opts, stats] = preprocess(obj, f, t0, y0, mass_scale, jac_scale, optin, stats) + % Preprocess to compute M(t_0) y_0 + [stats] = obj.LinearSolver.computeMass(f, t0, y0, stats); + %TODO: Fixed point can be formulated to support DAEs. Utilize + %QR factorization plus some transformation to get it. + if ~isempty(f.MassSingular) + error('Fixed Point Iteration does not support DAEs.') + end + + obj.t0_pre = t0; + out_opts = []; + end + + function [xn, out_opts, stats] = solve(obj, f, t, dt, y0, x0, fn0, sys_const, mass_scale, jac_scale, ~, stats) + %Solve the nonlinear equation 0 = -M(t)z + const + a * f(t, y_0 + z) + xn = x0; + i = 0; + y1 = y0 + x0; + intial_fun_cond = uint32(~isempty(fn0)); + if intial_fun_cond == 0 + fn0 = f.F(t, y1); + end + + stats = obj.LinearSolver.preprocess(f, t, y1, true, mass_scale, 0, stats); + + while i < obj.MaxIterations + b = sys_const + (jac_scale) .* fn0; + [xn, stats] = obj.LinearSolver.solve(b, stats); + + i = i + 1; + + if norm(xn - x0) < obj.Tolerance + break; + end + x0 = xn; + y1 = y0 + x0; + fn0 = f.F(t, y1); + end + stats.nNonLinIterations = stats.nNonLinIterations + i; + stats.nFevals = stats.nFevals + i - intial_fun_cond; + + out_opts.convergenceFailure = i >= obj.MaxIterations; + end + end + + methods (Access=private) + + end +end + diff --git a/src/+matlode/+nonlinearsolver/Newton.m b/src/+matlode/+nonlinearsolver/Newton.m index a6d3d07..dfc8501 100644 --- a/src/+matlode/+nonlinearsolver/Newton.m +++ b/src/+matlode/+nonlinearsolver/Newton.m @@ -15,7 +15,7 @@ obj = obj@matlode.nonlinearsolver.NonlinearSolver(linsolve, args{:}); end - function [out_opts, stats] = preprocess(obj, f, t0, y0, optin, stats) + function [out_opts, stats] = preprocess(obj, f, t0, y0, mass_scale, jac_scale, optin, stats) % Preprocess to compute M(t_0) y_0 [stats] = obj.LinearSolver.computeMass(f, t0, y0, stats); if isempty(f.Mass) || ~isa(f.Mass, 'function_handle') diff --git a/src/+matlode/+nonlinearsolver/NonlinearSolver.m b/src/+matlode/+nonlinearsolver/NonlinearSolver.m index f9255a6..12bd1b3 100644 --- a/src/+matlode/+nonlinearsolver/NonlinearSolver.m +++ b/src/+matlode/+nonlinearsolver/NonlinearSolver.m @@ -36,7 +36,7 @@ methods(Abstract) - [out_opts, stats] = preprocess(obj, f, t0, y0, optin, stats); + [out_opts, stats] = preprocess(obj, f, t0, y0, mass_scale, jac_scale, optin, stats); [xn, xnf, out_opts, stats] = solve(obj, f, t, dt, y0, x0, fn0, sys_const, mass_scale, jac_scale, optin, stats); end diff --git a/src/+matlode/+rk/+dirk/DIRK.m b/src/+matlode/+rk/+dirk/DIRK.m index 4f12050..57bc3d2 100644 --- a/src/+matlode/+rk/+dirk/DIRK.m +++ b/src/+matlode/+rk/+dirk/DIRK.m @@ -18,12 +18,14 @@ end properties (SetAccess = protected) + Gamma D Theta ET Alpha StifflyAccurate ESDIRK + SinglyImplicit falseYDiff end @@ -32,6 +34,8 @@ methods function obj = DIRK(a, b, bHat, c, e, order, embeddedOrder) obj = obj@matlode.rk.RungeKutta(a, b, bHat, c, e, order, embeddedOrder); + + adiag = diag(obj.A); obj.StifflyAccurate = all(obj.A(end,:) == obj.B(:)'); %TODO: Change to coefficents provides to prevent accuracy loss. @@ -42,6 +46,9 @@ %ESDIRK only utilizes the sub matrices inverse obj.ESDIRK = all(obj.A(1, :) == 0); if (obj.ESDIRK) + obj.Gamma = obj.A(2,2); + + obj.SinglyImplicit = all(abs(adiag(2:end) - obj.Gamma) < eps); %Note the first stage is treated differently in the z %transformation. Adding the effects to the intial stage are @@ -69,6 +76,10 @@ obj.Theta(2:end, 1) = obj.A(2:end,1) + tril(obj.A(2:end, 2:end), -1)*q; else + obj.Gamma = obj.A(1,1); + + obj.SinglyImplicit = all(abs(adiag - obj.Gamma) < eps ); + if obj.StifflyAccurate obj.D = zeros(1,obj.StageNum); obj.D(1,end) = 1; @@ -85,6 +96,10 @@ obj.Theta = obj.A \ tril(obj.A, -1); end + if ~obj.SinglyImplicit + error('General Dirk is not supported. Only SDIRK and ESDIRK.'); + end + end end @@ -119,7 +134,8 @@ ynew = y; %Setup Nonlinear Solver - [~, stats] = obj.NonLinearSolver.preprocess(f, t, y, [], stats); + %TODO:Currently assuming diagonals are the same. fix + [~, stats] = obj.NonLinearSolver.preprocess(f, t, y, 1, dt .* obj.Gamma, [], stats); if obj.Z_Transformation ydiff = zeros(length(y),1); @@ -146,7 +162,7 @@ end %Solve Nonlinear System - [stages(:,i), solver_opts, stats] = obj.NonLinearSolver.solve(f, thc, dt, y, ydiff, fd0, g_const, 1, dt .* obj.A(i,i), [], stats); + [stages(:,i), solver_opts, stats] = obj.NonLinearSolver.solve(f, thc, dt, y, ydiff, fd0, g_const, 1, dt .* obj.Gamma, [], stats); ydiff = stages(:,i); obj.falseYDiff(:,i) = ydiff; if solver_opts.convergenceFailure == true @@ -190,7 +206,7 @@ thc = t + obj.C(i) .* dt; %Solve Nonlinear System - [ydiff, solver_opts, stats] = obj.NonLinearSolver.solve(f, thc, dt, y, ydiff, fd0, g_const, 1, dt .* obj.A(i,i), [], stats); + [ydiff, solver_opts, stats] = obj.NonLinearSolver.solve(f, thc, dt, y, ydiff, fd0, g_const, 1, dt .* obj.Gamma, [], stats); if solver_opts.convergenceFailure == true out_opts.failure = true; return; @@ -230,7 +246,7 @@ esdirkstart = uint32(obj.ESDIRK) + 1; for i = esdirkstart:obj.StageNum if abs(obj.ET(i)) > eps - yerror = yerror + (obj.ET(i)) .* obj.falseYDiff(:, i); + yerror = yerror + (obj.ET(i)) .* stages(:, i); end end diff --git a/src/+testingscripts/+nonlintest/TestAdaptiveChordDIRK.m b/src/+testingscripts/+nonlintest/TestAdaptiveChordDIRK.m new file mode 100644 index 0000000..be692b2 --- /dev/null +++ b/src/+testingscripts/+nonlintest/TestAdaptiveChordDIRK.m @@ -0,0 +1,33 @@ +clear +format long e +close all + +integrator = matlode.rk.dirk.SDIRK_4_3_5; + +options.ErrNorm = matlode.errnorm.StandardNorm(1e-7, 1e-7); +options.StepSizeController = matlode.stepsizecontroller.StandardController; +options.NonLinearSolver = matlode.nonlinearsolver.AdaptiveChord; + +problem = otp.robertson.presets.Canonical; + +tic +sol = integrator.integrate(problem.RHS, problem.TimeSpan, problem.Y0, options); +toc + +tic +sol_matlab = problem.solve('Solver', @ode15s, 'RelTol', 1e-8, 'AbsTol', 1e-8); +toc + +% beta = problem.Parameters.Beta; +% t = problem.TimeSpan(end); +% true_sol.y = [t .* sin(t) + (1 + beta * t) .* exp(-t); ... +% beta * exp(-t) + sin(t)]; + +true_sol = problem.solve('Solver', @ode15s, 'RelTol', 1e-10, 'AbsTol', 1e-10); +% true_sol.y = exp(-1); + +norm(sol.y(:,end) - true_sol.y(:,end)) +norm(sol_matlab.y(:,end) - true_sol.y(:,end)) +norm(sol.y(:,end) - sol_matlab.y(:,end)) + +sol.stats diff --git a/src/+testingscripts/+nonlintest/TestFixedPointDIRK.m b/src/+testingscripts/+nonlintest/TestFixedPointDIRK.m new file mode 100644 index 0000000..88e7e93 --- /dev/null +++ b/src/+testingscripts/+nonlintest/TestFixedPointDIRK.m @@ -0,0 +1,33 @@ +clear +format long e +close all + +integrator = matlode.rk.dirk.SDIRK_2_1_2; + +options.ErrNorm = matlode.errnorm.StandardNorm(1e-7, 1e-7); +options.StepSizeController = matlode.stepsizecontroller.StandardController; +options.NonLinearSolver = matlode.nonlinearsolver.FixedPoint; + +problem = otp.quadratic.presets.Canonical; + +tic +sol = integrator.integrate(problem.RHS, problem.TimeSpan, problem.Y0, options); +toc + +tic +sol_matlab = problem.solve('Solver', @ode15s, 'RelTol', 1e-8, 'AbsTol', 1e-8); +toc + +% beta = problem.Parameters.Beta; +% t = problem.TimeSpan(end); +% true_sol.y = [t .* sin(t) + (1 + beta * t) .* exp(-t); ... +% beta * exp(-t) + sin(t)]; + +true_sol = problem.solve( 'RelTol', 1e-10, 'AbsTol', 1e-10); +% true_sol.y = exp(-1); + +norm(sol.y(:,end) - true_sol.y(:,end)) +norm(sol_matlab.y(:,end) - true_sol.y(:,end)) +norm(sol.y(:,end) - sol_matlab.y(:,end)) + +sol.stats