function _rk(butcherTableau) {
const subtract = math.subtract;
const isUnit = math.isUnit;
const isBigNumber = math.isBigNumber;
const isNumeric = math.isNumeric
const bignumber = math.bignumber
const divide = math.divide
const add = math.add
const multiply = math.multiply
const max = math.max
const abs = math.abs
const map = math.map
const smaller = math.smaller
const larger = math.larger
const isPositive = math.isPositive
return function (f, tspan, y0, options) {
const hasBigNumbers = [
...tspan,
...y0,
options.maxStep,
options.minStep
].some(isBigNumber);
const wrongTSpan = !(
tspan.length === 2 &&
((isNumeric(tspan[0]) && isNumeric(tspan[1])) ||
(isUnit(tspan[0]) && isUnit(tspan[1])))
);
if (wrongTSpan) {
throw new Error(
'"tspan" must be an Array of two numeric values or two units [tStart, tEnd]'
);
}
const t0 = tspan[0];
const tf = tspan[1]; // final time
const steps = 1; // divide time in this number of steps
const tol = options.tol ? options.tol : 1e-4; // define a tolerance (must be an option)
const maxStep = options.maxStep;
const minStep = options.minStep;
const minDelta = options.minDelta ? options.minDelta : 0.2;
const maxDelta = options.maxDelta ? options.maxDelta : 5;
const maxIter = options.maxIter ? options.maxIter : 10_000; // stop inifite evaluation if something goes wrong
const [a, c, b, bp] = hasBigNumbers
? [
bignumber(butcherTableau.a),
bignumber(butcherTableau.c),
bignumber(butcherTableau.b),
bignumber(butcherTableau.bp)
]
: [
butcherTableau.a,
butcherTableau.c,
butcherTableau.b,
butcherTableau.bp
];
let h = options.firstStep
? options.firstStep
: divide(subtract(tf, t0), steps); // define the first step size
const t = [t0]; // start the time array
const y = [y0]; // start the solution array
const dletaB = subtract(b, bp); // b - bp
let n = 0;
let iter = 0;
// iterate unitil it reaches either the final time or maximum iterations
while (ongoing(t[n], tf, h)) {
const k = [];
// trim the time step so that it doesn't overshoot
h = trimStep(t[n], tf, h);
// calculate the first value of k
k.push(f(t[n], y[n]));
// calculate the rest of the values of k
for (let i = 1; i < c.length; ++i) {
k.push(
f(add(t[n], multiply(c[i], h)), add(y[n], multiply(h, a[i], k)))
);
}
// estimate the error by comparing solutions of different orders
const TE = max(
abs(map(multiply(dletaB, k), (X) => (isUnit(X) ? X.value : X)))
);
if (TE < tol && tol / TE > 1 / 4) {
// if within tol then push everything
t.push(add(t[n], h));
y.push(add(y[n], multiply(h, b, k)));
n++;
}
// estimate the delta value that will affect the step size
let delta = 0.84 * (tol / TE) ** (1 / 5);
if (smaller(delta, minDelta)) {
delta = minDelta;
} else if (larger(delta, maxDelta)) {
delta = maxDelta;
}
delta = hasBigNumbers ? bignumber(delta) : delta;
h = multiply(h, delta);
if (maxStep && larger(abs(h), abs(maxStep))) {
h = isPositive(h) ? abs(maxStep) : multiply(-1, abs(maxStep));
} else if (minStep && smaller(abs(h), abs(minStep))) {
h = isPositive(h) ? abs(minStep) : multiply(-1, abs(minStep));
}
iter++;
if (iter > maxIter) {
throw new Error(
"Maximum number of iterations reached, try changing options"
);
}
}
return { t, y };
};
}