fsolve-js
Version:
A numerical solver for non-linear systems of equations, utilizing the n-dimensional Newton-Raphson method.
135 lines (134 loc) • 4.26 kB
JavaScript
import { Differentiator } from "./differentiator.js";
import { multiply, add, max, inv, abs, pinv } from "mathjs";
/**
* Solver class
* Represents an intance of the numeric
* n-dimensional newton - raphson solver
*/
export class Solver {
/**
* Solver constructor
* @param stopError - Minimum error to stop solver.
* @param maxIterations - Maximum number of iterations to stop solver.
* @param timeOut - Solver timeout (in ms)
* @param delta Numerical differentiation delta.
*/
constructor(stopError = 1e-6, maxIterations = 1e3, timeOut = 3.6e6, delta = 1e-9) {
this.stopError = stopError;
this.maxIterations = maxIterations;
this.timeOut = timeOut;
this.delta = delta;
this.diff = new Differentiator(delta);
}
/**
* Numerically finds the zeros of function f using Newton -
* Raphson method.
* @param f function to solve
* @param x0 initial guess
* @returns {Solution} use Solution.solved() to check
* if the solver found a solution, Solution.message() to
* get solver message and Solution.getX() to get solution's
* X vector.
*/
solve(f, x0) {
const n = x0.size()[0];
let error = Infinity;
let iter = 0;
const initialTime = Date.now();
while (error >= this.stopError) {
if (iter >= this.maxIterations) {
return Solution.maxIterReached(x0);
}
else if ((Date.now() - initialTime) >= this.timeOut) {
return Solution.timeOut(x0);
}
const y0 = f(x0);
const J = this.diff.jacobian(f, x0);
const b = multiply(y0, -1);
const invJ = inv(J);
const h = multiply(invJ, b);
x0 = add(x0, h);
error = max(abs(f(x0)));
iter += 1;
}
return Solution.success(x0);
}
/**
* Numerically finds the zeros of function f using Newton -
* Raphson method generalization for underdetermined systems.
* @param f function to solve
* @param x0 initial guess
* @returns {Solution} use Solution.solved() to check
* if the solver found a solution, Solution.message() to
* get solver message and Solution.getX() to get solution's
* X vector.
*/
solveUnderdetermined(f, x0) {
const n = x0.size()[0];
const m = f(x0).size()[0];
if (m > n) {
throw new Error("System has more equations than variables.");
}
if (m === n) {
console.warn("Using solveUnderdetermined() for system with same number of variables " +
"and equations. Use solve() for better performance.");
}
let error = Infinity;
let iter = 0;
const initialTime = Date.now();
while (error >= this.stopError) {
if (iter >= this.maxIterations) {
return Solution.maxIterReached(x0);
}
else if ((Date.now() - initialTime) >= this.timeOut) {
return Solution.timeOut(x0);
}
const y0 = f(x0);
const J = this.diff.jacobian(f, x0);
const b = multiply(y0, -1);
const invJ = pinv(J);
const h = multiply(invJ, b);
x0 = add(x0, h);
error = max(abs(f(x0)));
iter += 1;
}
return Solution.success(x0);
}
}
/**
* Represents a solution
*/
class Solution {
constructor(x, solved, msg) {
this.x = x;
this._solved = solved;
this.msg = msg;
}
/**
* Get solution's X Vector.
*/
getX() {
return this.x;
}
/**
* @returns boolean indicating if the solver found a solution
*/
solved() {
return this._solved;
}
/**
* @returns solver's message.
*/
message() {
return this.msg;
}
static success(x) {
return new Solution(x, true, "Solution achived");
}
static timeOut(x) {
return new Solution(x, false, "No solution found. Solver timed out");
}
static maxIterReached(x) {
return new Solution(x, false, "No solution found. Max number of iteratios reached");
}
}