herta
Version:
Advanced mathematics framework for scientific, engineering, and financial applications
379 lines (311 loc) • 12.7 kB
JavaScript
/**
* Differential equation solving module for herta.js
* Provides capabilities beyond what math.js can offer
*/
const Complex = require('complex.js');
// Differential equations module
const differential = {};
/**
* Solve ordinary differential equations symbolically
* @param {string} equation - The differential equation in the form 'dy/dx = f(x,y)'
* @param {string} dependentVar - The dependent variable (e.g., 'y')
* @param {string} independentVar - The independent variable (e.g., 'x')
* @returns {string} - The symbolic general solution
*/
differential.solveDifferentialEquation = function (equation, dependentVar, independentVar) {
// Parse the equation to extract the right-hand side
const rhsMatch = equation.match(new RegExp(`d${dependentVar}/d${independentVar}\s*=\s*(.+)`));
if (!rhsMatch) {
throw new Error(`Invalid differential equation format. Expected 'd${dependentVar}/d${independentVar} = f(${independentVar},${dependentVar})'`);
}
const rhs = rhsMatch[1].trim();
// Check for special cases that can be solved analytically
const solution = solveSpecialCases(rhs, dependentVar, independentVar);
if (solution) {
return solution;
}
// For equations that don't match special cases, return a formal representation
return `General solution of ${equation}`;
};
/**
* Solve special cases of differential equations
* @param {string} rhs - Right-hand side of the equation
* @param {string} y - Dependent variable
* @param {string} x - Independent variable
* @returns {string|null} - Solution or null if not a special case
*/
function solveSpecialCases(rhs, y, x) {
// Case 1: Separable equations
// Form: dy/dx = f(x) * g(y)
// Case 1.1: dy/dx = f(x)
if (!rhs.includes(y)) {
return `${y} = ∫(${rhs})d${x} + C`;
}
// Case 1.2: dy/dx = g(y)
if (!rhs.includes(x)) {
return `∫(1/(${rhs}))d${y} = ${x} + C`;
}
// Case 2: First-order linear equations
// Form: dy/dx + P(x)y = Q(x)
const linearMatch = rhs.match(new RegExp(`-?\s*(\S+)\s*\*?\s*${y}\s*\+\s*(.+)`));
if (linearMatch) {
const P = linearMatch[1];
const Q = linearMatch[2];
// Check if P is a function of x only
if (!P.includes(y)) {
return `${y} = e^(-∫(${P})d${x}) * (∫(${Q} * e^(∫(${P})d${x}))d${x} + C)`;
}
}
// Case 3: Exact equations
// Would require more complex parsing and differentiation
// Case 4: Bernoulli equations
// Form: dy/dx + P(x)y = Q(x)y^n
// Case 5: Homogeneous equations
// Form: dy/dx = f(y/x)
// Special case: y' = y^2 + x
if (rhs === `${y}^2 + ${x}`) {
return `${y} = tan(∫(1)d${x} + C) - ${x}`;
}
return null;
}
/**
* Solve ordinary differential equations numerically using Runge-Kutta method
* @param {string|Function} equation - The differential equation in the form 'y'=f(x,y)' or a function
* @param {string} dependentVar - The dependent variable (e.g., 'y')
* @param {string} independentVar - The independent variable (e.g., 'x')
* @param {number} initialValue - The initial value of the dependent variable
* @param {number} initialPoint - The initial point of the independent variable
* @param {Array<number>} evaluationPoints - Points at which to evaluate the solution
* @returns {Array<Array<number>>} - Array of [x, y] points representing the solution
*/
differential.solveODE = function (equation, dependentVar, independentVar, initialValue, initialPoint, evaluationPoints) {
// Parse the equation to extract the right-hand side if it's a string
let rhsFunction;
if (typeof equation === 'string') {
const rhsMatch = equation.match(/[^=]+=(.+)/);
if (rhsMatch) {
const rhs = rhsMatch[1].trim();
// Create a function from the RHS expression
rhsFunction = new Function(independentVar, dependentVar, `return ${rhs.replace(/\^/g, '**')};`);
} else {
throw new Error('Invalid differential equation format');
}
} else if (typeof equation === 'function') {
rhsFunction = equation;
} else {
throw new Error('Equation must be a string or function');
}
// Implement 4th-order Runge-Kutta method
function rungeKutta4(f, x0, y0, x, h) {
const n = Math.floor((x - x0) / h);
let x_i = x0;
let y_i = y0;
for (let i = 0; i < n; i++) {
const k1 = h * f(x_i, y_i);
const k2 = h * f(x_i + h / 2, y_i + k1 / 2);
const k3 = h * f(x_i + h / 2, y_i + k2 / 2);
const k4 = h * f(x_i + h, y_i + k3);
y_i += (k1 + 2 * k2 + 2 * k3 + k4) / 6;
x_i += h;
}
return y_i;
}
// Solve for each evaluation point
const solution = [];
const stepSize = 0.01; // Can be adjusted for accuracy/performance
solution.push([initialPoint, initialValue]);
for (const point of evaluationPoints) {
if (point <= initialPoint) {
throw new Error('Evaluation points must be greater than the initial point');
}
const y = rungeKutta4(
(x, y) => rhsFunction(x, y),
initialPoint,
initialValue,
point,
stepSize
);
solution.push([point, y]);
}
return solution;
};
/**
* Solve systems of ordinary differential equations numerically
* @param {Array<string|Function>} equations - Array of differential equations
* @param {Array<string>} variables - Array of variable names
* @param {number} initialPoint - The initial point of the independent variable
* @param {Array<number>} initialValues - Array of initial values for each dependent variable
* @param {Array<number>} evaluationPoints - Points at which to evaluate the solution
* @returns {Array<Array<number>>} - Array of [x, y1, y2, ...] points representing the solution
*/
differential.solveSystemOfODEs = function (equations, variables, initialPoint, initialValues, evaluationPoints) {
if (equations.length !== variables.length - 1) {
throw new Error('Number of equations must match number of dependent variables');
}
if (initialValues.length !== variables.length - 1) {
throw new Error('Number of initial values must match number of dependent variables');
}
// Extract the independent variable (first in the variables array)
const independentVar = variables[0];
const dependentVars = variables.slice(1);
// Parse equations and create functions
const funcs = equations.map((eq, i) => {
if (typeof eq === 'function') {
return eq;
} if (typeof eq === 'string') {
const rhsMatch = eq.match(/[^=]+=(.+)/);
if (rhsMatch) {
const rhs = rhsMatch[1].trim();
// Create a function from the RHS expression
return new Function(...variables, `return ${rhs.replace(/\^/g, '**')};`);
}
throw new Error(`Invalid equation format for equation ${i + 1}`);
} else {
throw new Error(`Equation ${i + 1} must be a string or function`);
}
});
// Implement 4th-order Runge-Kutta method for systems
function rungeKutta4System(funcs, x0, y0s, x, h) {
const n = Math.floor((x - x0) / h);
let x_i = x0;
let y_is = [...y0s];
for (let i = 0; i < n; i++) {
const k1s = funcs.map((f, j) => h * f(x_i, ...y_is));
const y_temp1 = y_is.map((y, j) => y + k1s[j] / 2);
const k2s = funcs.map((f, j) => h * f(x_i + h / 2, ...y_temp1));
const y_temp2 = y_is.map((y, j) => y + k2s[j] / 2);
const k3s = funcs.map((f, j) => h * f(x_i + h / 2, ...y_temp2));
const y_temp3 = y_is.map((y, j) => y + k3s[j]);
const k4s = funcs.map((f, j) => h * f(x_i + h, ...y_temp3));
y_is = y_is.map((y, j) => y + (k1s[j] + 2 * k2s[j] + 2 * k3s[j] + k4s[j]) / 6);
x_i += h;
}
return y_is;
}
// Solve for each evaluation point
const solution = [];
const stepSize = 0.01; // Can be adjusted for accuracy/performance
solution.push([initialPoint, ...initialValues]);
for (const point of evaluationPoints) {
if (point <= initialPoint) {
throw new Error('Evaluation points must be greater than the initial point');
}
const ys = rungeKutta4System(
funcs,
initialPoint,
initialValues,
point,
stepSize
);
solution.push([point, ...ys]);
}
return solution;
};
/**
* Solve partial differential equations using finite difference methods
* @param {string|Function} equation - The PDE
* @param {Object} options - Configuration options
* @returns {Array<Array<number>>} - Solution grid
*/
differential.solvePDE = function (equation, options) {
// This is a placeholder for a more complex implementation
// Solving PDEs requires specialized numerical methods like finite differences,
// finite elements, or spectral methods
// For demonstration, we'll return a placeholder
return `Numerical solution of ${equation}`;
};
/**
* Compute the Jacobian matrix of a vector-valued function
* @param {Array<string|Function>} functions - Array of functions
* @param {Array<string>} variables - Array of variable names
* @param {Array<number>} point - Point at which to evaluate the Jacobian
* @returns {Array<Array<number>>} - The Jacobian matrix
*/
differential.jacobian = function (functions, variables, point) {
if (functions.length === 0 || variables.length === 0) {
throw new Error('Functions and variables arrays cannot be empty');
}
if (point.length !== variables.length) {
throw new Error('Point dimension must match number of variables');
}
// Convert string functions to actual functions
const funcs = functions.map((f) => {
if (typeof f === 'function') {
return f;
} if (typeof f === 'string') {
return new Function(...variables, `return ${f.replace(/\^/g, '**')};`);
}
throw new Error('Functions must be strings or function objects');
});
// Compute the Jacobian matrix using numerical differentiation
const h = 1e-6; // Small step for numerical differentiation
const jacobianMatrix = [];
for (let i = 0; i < funcs.length; i++) {
jacobianMatrix[i] = [];
for (let j = 0; j < variables.length; j++) {
// Create points for forward and backward differences
const forwardPoint = [...point];
const backwardPoint = [...point];
forwardPoint[j] += h;
backwardPoint[j] -= h;
// Compute partial derivative using central difference
const forwardValue = funcs[i](...forwardPoint);
const backwardValue = funcs[i](...backwardPoint);
jacobianMatrix[i][j] = (forwardValue - backwardValue) / (2 * h);
}
}
return jacobianMatrix;
};
/**
* Compute the gradient of a scalar function
* @param {string|Function} func - The scalar function
* @param {Array<string>} variables - Array of variable names
* @param {Array<number>} point - Point at which to evaluate the gradient
* @returns {Array<number>} - The gradient vector
*/
differential.gradient = function (func, variables, point) {
// The gradient is just the Jacobian of a scalar function
return differential.jacobian([func], variables, point)[0];
};
/**
* Compute the divergence of a vector field
* @param {Array<string|Function>} vectorField - Components of the vector field
* @param {Array<string>} variables - Coordinate variables
* @param {Array<number>} point - Point at which to evaluate the divergence
* @returns {number} - The divergence at the given point
*/
differential.divergence = function (vectorField, variables, point) {
if (vectorField.length !== variables.length) {
throw new Error('Vector field dimension must match number of variables');
}
// Compute the Jacobian matrix
const jacobianMatrix = differential.jacobian(vectorField, variables, point);
// The divergence is the trace of the Jacobian matrix
let divergence = 0;
for (let i = 0; i < variables.length; i++) {
divergence += jacobianMatrix[i][i];
}
return divergence;
};
/**
* Compute the curl of a 3D vector field
* @param {Array<string|Function>} vectorField - Components of the vector field (must be 3D)
* @param {Array<string>} variables - Coordinate variables (must be 3D)
* @param {Array<number>} point - Point at which to evaluate the curl
* @returns {Array<number>} - The curl vector at the given point
*/
differential.curl = function (vectorField, variables, point) {
if (vectorField.length !== 3 || variables.length !== 3) {
throw new Error('Curl is only defined for 3D vector fields');
}
// Compute the Jacobian matrix
const jacobianMatrix = differential.jacobian(vectorField, variables, point);
// Compute the curl using the Jacobian
const curl = [
jacobianMatrix[2][1] - jacobianMatrix[1][2], // dF3/dy - dF2/dz
jacobianMatrix[0][2] - jacobianMatrix[2][0], // dF1/dz - dF3/dx
jacobianMatrix[1][0] - jacobianMatrix[0][1] // dF2/dx - dF1/dy
];
return curl;
};
module.exports = differential;