mirror of
https://github.com/josdejong/mathjs.git
synced 2025-12-08 19:46:04 +00:00
239 lines
8.8 KiB
JavaScript
239 lines
8.8 KiB
JavaScript
import assert from 'assert'
|
|
import math from '../../../../src/defaultInstance.js'
|
|
import { approxDeepEqual } from '../../../../tools/approx.js'
|
|
|
|
const solveODE = math.solveODE
|
|
const matrix = math.matrix
|
|
const add = math.add
|
|
const subtract = math.subtract
|
|
const exp = math.exp
|
|
const min = math.min
|
|
const max = math.max
|
|
const tol = 1e-3
|
|
const smallerEq = math.smallerEq
|
|
const largerEq = math.largerEq
|
|
const multiply = math.multiply
|
|
const dotMultiply = math.dotMultiply
|
|
const divide = math.divide
|
|
const diff = math.diff
|
|
const unit = math.unit
|
|
const map = math.map
|
|
const transpose = math.transpose
|
|
const isMatrix = math.isMatrix
|
|
const bignumber = math.bignumber
|
|
const number = math.number
|
|
|
|
describe('solveODE', function () {
|
|
function f (t, y) { return subtract(y, t) }
|
|
function exactSol (T, y0) { return add(dotMultiply(subtract(y0, 1), map(transpose([T]), exp)), transpose([T]), 1) } // this is only valid for y' = y - t
|
|
const tspan = [0, 4]
|
|
const y0 = [1.5, 2]
|
|
|
|
it('should throw an error if the first argument is not a function', function () {
|
|
assert.throws(function () {
|
|
solveODE('notAFunction', tspan, y0)
|
|
}, /TypeError: Unexpected type of argument in function solveODE \(expected: function.*/)
|
|
})
|
|
|
|
it('should throw an error if the second argument is not an array like', function () {
|
|
assert.throws(function () {
|
|
solveODE(f, 'NotAnArrayLike', y0)
|
|
}, /TypeError: Unexpected type of argument in function solveODE \(expected: Array or Matrix,.*/)
|
|
})
|
|
|
|
it('should throw an error if the second argument does not have a second member', function () {
|
|
assert.throws(function () {
|
|
const wrongTSpan = [tspan[0]]
|
|
solveODE(f, wrongTSpan, y0)
|
|
}, /Error: "tspan" must be an Array of two numeric values.*/)
|
|
})
|
|
|
|
it('should throw an error if the name of the method is wrong', function () {
|
|
assert.throws(function () {
|
|
const method = 'wrongMethodName'
|
|
solveODE(f, tspan, y0, { method })
|
|
}, /Unavailable method.*/)
|
|
})
|
|
|
|
it('should throw an error if the maximum number of iterations is reached', function () {
|
|
assert.throws(function () {
|
|
solveODE(f, tspan, y0, { maxIter: 1 })
|
|
}, /Maximum number of iterations reached.*/)
|
|
})
|
|
|
|
it('should throw an error if the firstStep is not positive', function () {
|
|
assert.throws(function () {
|
|
solveODE(f, tspan, y0, { firstStep: -1 })
|
|
}, /"firstStep" must be positive/)
|
|
assert.throws(function () {
|
|
solveODE(f, tspan, y0, { firstStep: 0 })
|
|
}, /"firstStep" must be positive/)
|
|
assert.throws(function () {
|
|
solveODE(f, tspan, y0, { firstStep: unit(-1, 's') })
|
|
}, /"firstStep" must be positive/)
|
|
assert.throws(function () {
|
|
solveODE(f, tspan, y0, { firstStep: unit(0, 's') })
|
|
}, /"firstStep" must be positive/)
|
|
})
|
|
|
|
it('should throw an error if the minStep is not positive or zero', function () {
|
|
assert.throws(function () {
|
|
solveODE(f, tspan, y0, { minStep: -1 })
|
|
}, /"minStep" must be positive or zero/)
|
|
assert.throws(function () {
|
|
solveODE(f, tspan, y0, { minStep: unit(-1, 's') })
|
|
}, /"minStep" must be positive or zero/)
|
|
})
|
|
|
|
it('should solve when minStep is zero', function () {
|
|
const sol = solveODE(f, tspan, y0, { minStep: 0 })
|
|
approxDeepEqual(sol.y, exactSol(sol.t, y0), tol)
|
|
const seconds = unit('s')
|
|
const meters = unit('m')
|
|
function fWithUnits (t, y) { return subtract(divide(y, seconds), multiply(t, divide(meters, multiply(seconds, seconds)))) }
|
|
const tspanWithUnits = multiply(tspan, seconds)
|
|
const y0withUnits = multiply(y0, meters)
|
|
const solU = solveODE(fWithUnits, tspanWithUnits, y0withUnits, { minStep: unit(0, 's') })
|
|
approxDeepEqual(divide(solU.y, meters), exactSol(divide(solU.t, seconds), y0), tol)
|
|
})
|
|
|
|
it('should throw an error if the maxStep is not positive', function () {
|
|
assert.throws(function () {
|
|
solveODE(f, tspan, y0, { maxStep: -1 })
|
|
}, /"maxStep" must be positive/)
|
|
assert.throws(function () {
|
|
solveODE(f, tspan, y0, { maxStep: 0 })
|
|
}, /"maxStep" must be positive/)
|
|
assert.throws(function () {
|
|
solveODE(f, tspan, y0, { maxStep: unit(-1, 's') })
|
|
}, /"maxStep" must be positive/)
|
|
assert.throws(function () {
|
|
solveODE(f, tspan, y0, { maxStep: unit(0, 's') })
|
|
}, /"maxStep" must be positive/)
|
|
})
|
|
|
|
it('should throw an error if the time dependant variables do not match', function () {
|
|
assert.throws(function () {
|
|
solveODE(f, tspan, y0, { firstStep: unit(1, 's') })
|
|
}, /Inconsistent type of "t" dependant variables/)
|
|
assert.throws(function () {
|
|
solveODE(f, [unit(tspan[0], 's'), unit(tspan[1], 's')], y0, { maxStep: unit(2, 's'), firstStep: 1 })
|
|
}, /Inconsistent type of "t" dependant variables/)
|
|
assert.throws(function () {
|
|
solveODE(f, tspan, y0, { firstStep: unit(1, 's') })
|
|
}, /Inconsistent type of "t" dependant variables/)
|
|
assert.throws(function () {
|
|
solveODE(f, [unit(tspan[0], 's'), unit(tspan[1], 's')], y0, { firstStep: 1 })
|
|
}, /Inconsistent type of "t" dependant variables/)
|
|
assert.throws(function () {
|
|
solveODE(f, tspan, y0, { maxStep: unit(1, 's') })
|
|
}, /Inconsistent type of "t" dependant variables/)
|
|
assert.throws(function () {
|
|
solveODE(f, [unit(tspan[0], 's'), unit(tspan[1], 's')], y0, { maxStep: 1 })
|
|
}, /Inconsistent type of "t" dependant variables/)
|
|
assert.throws(function () {
|
|
solveODE(f, tspan, y0, { minStep: unit(1, 's') })
|
|
}, /Inconsistent type of "t" dependant variables/)
|
|
assert.throws(function () {
|
|
solveODE(f, [unit(tspan[0], 's'), unit(tspan[1], 's')], y0, { minStep: 1 })
|
|
}, /Inconsistent type of "t" dependant variables/)
|
|
})
|
|
|
|
it('should solve close to the analytical solution', function () {
|
|
const sol = solveODE(f, tspan, y0)
|
|
approxDeepEqual(sol.y, exactSol(sol.t, y0), tol)
|
|
})
|
|
|
|
it('should solve backwards', function () {
|
|
const exactSolEnd = exactSol([4], y0)[0]
|
|
const sol = solveODE(f, [tspan[1], tspan[0]], exactSolEnd)
|
|
approxDeepEqual(sol.y, exactSol(sol.t, sol.y[sol.y.length - 1]), tol)
|
|
})
|
|
|
|
it('should solve if the arguments are matrices', function () {
|
|
const sol = solveODE(f, matrix(tspan), matrix(y0))
|
|
approxDeepEqual(sol.y, matrix(exactSol(sol.t, y0)), tol)
|
|
assert.deepStrictEqual(
|
|
isMatrix(sol.t) && isMatrix(sol.y),
|
|
true
|
|
)
|
|
})
|
|
|
|
it('should solve if the arguments have bignumbers', function () {
|
|
const sol = solveODE(f, bignumber(tspan), bignumber(y0))
|
|
approxDeepEqual(number(sol.y), exactSol(number(sol.t), y0), tol)
|
|
})
|
|
|
|
it('should solve with options even if they are empty', function () {
|
|
const options = {}
|
|
const sol = solveODE(f, tspan, y0, options)
|
|
approxDeepEqual(sol.y, exactSol(sol.t, y0), tol)
|
|
})
|
|
|
|
it('should solve when y0 is a scalar', function () {
|
|
const sol = solveODE(f, tspan, y0[0])
|
|
approxDeepEqual(sol.y, exactSol(sol.t, [y0[0]]).map(x => x[0]), tol)
|
|
})
|
|
|
|
it('should solve with units', function () {
|
|
const seconds = unit('s')
|
|
const meters = unit('m')
|
|
function fWithUnits (t, y) { return subtract(divide(y, seconds), multiply(t, divide(meters, multiply(seconds, seconds)))) }
|
|
const tspanWithUnits = multiply(tspan, seconds)
|
|
const y0withUnits = multiply(y0, meters)
|
|
const sol = solveODE(fWithUnits, tspanWithUnits, y0withUnits)
|
|
approxDeepEqual(divide(sol.y, meters), exactSol(divide(sol.t, seconds), y0), tol)
|
|
})
|
|
|
|
it('should solve close to the analytical solution with RK23 method', function () {
|
|
const sol = solveODE(f, tspan, y0, { method: 'RK23' })
|
|
approxDeepEqual(sol.y, exactSol(sol.t, y0), tol)
|
|
})
|
|
|
|
it('should solve close to the analytical solution with RK45 method', function () {
|
|
const sol = solveODE(f, tspan, y0, { method: 'RK45' })
|
|
approxDeepEqual(sol.y, exactSol(sol.t, y0), tol)
|
|
})
|
|
|
|
it('should solve with method name in lower case', function () {
|
|
const sol = solveODE(f, tspan, y0, { method: 'rk45' })
|
|
approxDeepEqual(sol.y, exactSol(sol.t, y0), tol)
|
|
})
|
|
|
|
it('should solve with few steps if a higher tolerance is specified', function () {
|
|
const sol = solveODE(f, tspan, y0, { method: 'RK45', tol: 1e-2 })
|
|
assert.deepStrictEqual(
|
|
smallerEq(sol.y.length, 6),
|
|
true
|
|
)
|
|
})
|
|
|
|
it('should respect maxStep', function () {
|
|
const maxStep = 0.4
|
|
const sol = solveODE(f, tspan, y0, { maxStep })
|
|
assert.deepStrictEqual(
|
|
smallerEq(max(diff(sol.t)), maxStep),
|
|
true
|
|
)
|
|
})
|
|
|
|
it('should respect minStep', function () {
|
|
const minStep = 0.1
|
|
const sol = solveODE(f, tspan, y0, { minStep })
|
|
assert.deepStrictEqual(
|
|
// slicing to exclude the last step
|
|
largerEq(min(diff(sol.t.slice(0, sol.t.length - 1))), minStep),
|
|
true
|
|
)
|
|
})
|
|
|
|
it('should respect first step', function () {
|
|
const firstStep = 0.1
|
|
const sol = solveODE(f, tspan, y0, { firstStep })
|
|
assert.deepStrictEqual(
|
|
subtract(sol.t[1], sol.t[0]),
|
|
firstStep
|
|
)
|
|
})
|
|
})
|