ml-levenberg-marquardt
Version:
Curve fitting method in javascript
113 lines (98 loc) • 2.8 kB
text/typescript
import checkOptions from './check_options.ts';
import errorCalculation from './error_calculation.ts';
import step from './step.ts';
import type {
Data2D,
LevenbergMarquardtOptions,
LevenbergMarquardtReturn,
ParameterizedFunction,
} from './types.ts';
/**
* Curve fitting algorithm
* @param data - Array of points to fit in the format [x1, x2, ... ], [y1, y2, ... ]
* @param parameterizedFunction - Takes an array of parameters and returns a function with the independent variable as its sole argument
* @param options - Options object
*/
export function levenbergMarquardt(
data: Data2D,
parameterizedFunction: ParameterizedFunction,
options: LevenbergMarquardtOptions,
): LevenbergMarquardtReturn {
const checkedOptions = checkOptions(data, options);
const {
checkTimeout,
minValues,
maxValues,
parameters,
weightSquare,
dampingStepUp,
dampingStepDown,
maxIterations,
errorTolerance,
centralDifference,
gradientDifference,
improvementThreshold,
} = checkedOptions;
let damping = checkedOptions.damping;
let error = errorCalculation(
data,
parameters,
parameterizedFunction,
weightSquare,
);
let optimalError = error;
let optimalParameters = parameters.slice();
let converged = error <= errorTolerance;
let iteration = 0;
for (; iteration < maxIterations && !converged; iteration++) {
const previousError = error;
const { perturbations, jacobianWeightResidualError } = step(
data,
parameters,
damping,
gradientDifference,
parameterizedFunction,
centralDifference,
weightSquare,
);
for (let k = 0; k < parameters.length; k++) {
parameters[k] = Math.min(
Math.max(minValues[k], parameters[k] - perturbations.get(k, 0)),
maxValues[k],
);
}
error = errorCalculation(
data,
parameters,
parameterizedFunction,
weightSquare,
);
if (isNaN(error)) break;
if (error < optimalError - errorTolerance) {
optimalError = error;
optimalParameters = parameters.slice();
}
const improvementMetric =
(previousError - error) /
perturbations
.transpose()
.mmul(perturbations.mul(damping).add(jacobianWeightResidualError))
.get(0, 0);
if (improvementMetric > improvementThreshold) {
damping = Math.max(damping / dampingStepDown, 1e-7);
} else {
damping = Math.min(damping * dampingStepUp, 1e7);
}
if (checkTimeout()) {
throw new Error(
`The execution time is over to ${options.timeout} seconds`,
);
}
converged = error <= errorTolerance;
}
return {
parameterValues: optimalParameters,
parameterError: optimalError,
iterations: iteration,
};
}