ml-levenberg-marquardt
Version:
Curve fitting method in javascript
81 lines (68 loc) • 2.41 kB
text/typescript
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<number>,
) {
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,
};
}