feat(math-engine): implement NumericalMethods with derivative, integral, and solve functions
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
// src/core/math/NumericalMethods.js
|
||||
/**
|
||||
* Numerical methods for the scientific calculator
|
||||
* Provides derivative, integral, and solver functions
|
||||
*/
|
||||
|
||||
const DEFAULT_TOLERANCE = 1e-6;
|
||||
const MAX_ITERATIONS = 100;
|
||||
const DEFAULT_H = 1e-5;
|
||||
|
||||
/**
|
||||
* Compute the derivative of an expression with respect to a variable using central difference.
|
||||
* @param {Function} exprFunc - A function that takes a variable value and returns the expression value
|
||||
* @param {number} x - The point at which to evaluate the derivative
|
||||
* @param {number} h - The step size (optional, default 1e-5)
|
||||
* @returns {number} The derivative value
|
||||
*/
|
||||
function derivative(exprFunc, x, h = DEFAULT_H) {
|
||||
return (exprFunc(x + h) - exprFunc(x - h)) / (2 * h);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the definite integral of an expression from a to b using adaptive Simpson's rule.
|
||||
* @param {Function} exprFunc - A function that takes a variable value and returns the expression value
|
||||
* @param {number} a - Lower limit
|
||||
* @param {number} b - Upper limit
|
||||
* @param {number} tolerance - Error tolerance (optional, default 1e-6)
|
||||
* @param {number} maxRecursionDepth - Maximum recursion depth (optional, default 10)
|
||||
* @returns {number} The integral value
|
||||
*/
|
||||
function integral(exprFunc, a, b, tolerance = DEFAULT_TOLERANCE, maxRecursionDepth = 10) {
|
||||
// Helper function for Simpson's rule
|
||||
function simpson(fa, fm, fb, width) {
|
||||
return (width * (fa + 4 * fm + fb)) / 6;
|
||||
}
|
||||
|
||||
// Recursive adaptive function
|
||||
function adaptive(l, r, fl, fr, fmid, whole, depth) {
|
||||
const leftMid = (l + (l + r) / 2) / 2;
|
||||
const rightMid = ((l + r) / 2 + r) / 2;
|
||||
const fleftmid = exprFunc(leftMid);
|
||||
const frightmid = exprFunc(rightMid);
|
||||
const left = simpson(fl, fleftmid, fmid, (r - l) / 4);
|
||||
const right = simpson(fmid, frightmid, fr, (r - l) / 4);
|
||||
if (depth >= maxRecursionDepth || Math.abs(left + right - whole) <= 15 * tolerance) {
|
||||
return left + right + (left + right - whole) / 15;
|
||||
}
|
||||
return adaptive(l, (l + r) / 2, fl, fmid, fleftmid, left, depth + 1) +
|
||||
adaptive((l + r) / 2, r, fmid, fr, frightmid, right, depth + 1);
|
||||
}
|
||||
|
||||
const fa = exprFunc(a);
|
||||
const fb = exprFunc(b);
|
||||
const fmid = exprFunc((a + b) / 2);
|
||||
const whole = simpson(fa, fmid, fb, b - a);
|
||||
return adaptive(a, b, fa, fb, fmid, whole, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Solve for a root of the expression f(x) = 0 using Newton-Raphson with secant fallback.
|
||||
* @param {Function} exprFunc - A function that takes a variable value and returns the expression value
|
||||
* @param {number} initialGuess - Initial guess for the root (optional, default 0)
|
||||
* @param {number} tolerance - Convergence tolerance (optional, default 1e-10)
|
||||
* @param {number} maxIterations - Maximum iterations (optional, default 100)
|
||||
* @returns {number} The root value
|
||||
*/
|
||||
function solve(exprFunc, initialGuess = 0, tolerance = 1e-10, maxIterations = 100) {
|
||||
let x0 = initialGuess;
|
||||
let x1 = x0 + 0.1; // Second point for secant method
|
||||
let f0 = exprFunc(x0);
|
||||
let f1 = exprFunc(x1);
|
||||
|
||||
for (let i = 0; i < maxIterations; i++) {
|
||||
// Check for convergence
|
||||
if (Math.abs(f1) < tolerance) {
|
||||
return x1;
|
||||
}
|
||||
|
||||
// Avoid division by zero
|
||||
if (Math.abs(f1 - f0) < Number.EPSILON) {
|
||||
// Fallback to bisection if derivative is zero
|
||||
// For simplicity, we'll just perturb and try again
|
||||
x0 = x0 + 0.1;
|
||||
x1 = x0 + 0.1;
|
||||
f0 = exprFunc(x0);
|
||||
f1 = exprFunc(x1);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Newton-Raphson step (requires derivative, which we don't have directly)
|
||||
// Instead, we'll use the secant method
|
||||
const x2 = x1 - f1 * (x1 - x0) / (f1 - f0);
|
||||
const f2 = exprFunc(x2);
|
||||
|
||||
// Check for convergence
|
||||
if (Math.abs(f2) < tolerance) {
|
||||
return x2;
|
||||
}
|
||||
|
||||
// Shift for next iteration
|
||||
x0 = x1;
|
||||
x1 = x2;
|
||||
f0 = f1;
|
||||
f1 = f2;
|
||||
}
|
||||
|
||||
// If we reach here, we didn't converge
|
||||
throw new Error('Solver did not converge within the maximum number of iterations');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
derivative,
|
||||
integral,
|
||||
solve
|
||||
};
|
||||
Reference in New Issue
Block a user