import { inverse, Matrix } from 'ml-matrix'; import gradientFunction from './gradient_function.ts'; import type { Data2D, ParameterizedFunction } from './types.ts'; /** * Matrix function over the samples * * @param data - Array of points to fit in the format [x1, x2, ... ], [y1, y2, ... ] * @param evaluatedData - Array of previous evaluated function values */ function matrixFunction(data: Data2D, evaluatedData: Float64Array): Matrix { const m = data.x.length; const ans = new Matrix(m, 1); for (let point = 0; point < m; point++) { ans.set(point, 0, data.y[point] - evaluatedData[point]); } return ans; } /** * Iteration for Levenberg-Marquardt * * @param data - Array of points to fit in the format [x1, x2, ... ], [y1, y2, ... ] * @param params - Array of previous parameter values * @param damping - Levenberg-Marquardt parameter * @param gradientDifference - The step size to approximate the jacobian matrix * @param centralDifference - If true the jacobian matrix is approximated by central differences otherwise by forward differences * @param parameterizedFunction - The parameters and returns a function with the independent variable as a parameter * @param weights - scale the gradient and residual error by weights */ export default function step( data: Data2D, params: number[], damping: number, gradientDifference: number[], parameterizedFunction: ParameterizedFunction, centralDifference: boolean, weights?: ArrayLike, ) { const identity = Matrix.eye(params.length, params.length, damping); const func = parameterizedFunction(params); const evaluatedData = new Float64Array(data.x.length); for (let i = 0; i < data.x.length; i++) { evaluatedData[i] = func(data.x[i]); } const gradientFunc = gradientFunction( data, evaluatedData, params, gradientDifference, parameterizedFunction, centralDifference, ); const residualError = matrixFunction(data, evaluatedData); const inverseMatrix = inverse( identity.add( gradientFunc.mmul( gradientFunc.transpose().scale('row', { scale: weights }), ), ), ); const jacobianWeightResidualError = gradientFunc.mmul( residualError.scale('row', { scale: weights }), ); const perturbations = inverseMatrix.mmul(jacobianWeightResidualError); return { perturbations, jacobianWeightResidualError, }; }