12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091 |
- const IsNanError = TypeError('resulted in NaN');
- const FailedToConvergeError = Error('failed to converge');
- const InvalidInputsError = Error('invalid inputs');
- export default async function GoalSeek({
- fn,
- fnParams,
- percentTolerance,
- customToleranceFn,
- maxIterations,
- maxStep,
- goal,
- independentVariableIdx,
- }) {
- if (typeof customToleranceFn !== 'function') {
- if (!percentTolerance) {
- throw InvalidInputsError;
- }
- }
- let g;
- let y;
- let y1;
- let oldGuess;
- let newGuess;
- let res;
- const absoluteTolerance = ((percentTolerance || 0) / 100) * goal;
- // iterate through the guesses
- for (let i = 0; i < maxIterations; i++) {
- // define the root of the function as the error
- res = await fn(...fnParams);
- y = res - goal;
- if (isNaN(y)) throw IsNanError;
- // was our initial guess a good one?
- if (typeof customToleranceFn !== 'function') {
- if (Math.abs(y) <= Math.abs(absoluteTolerance)) return fnParams[independentVariableIdx];
- } else {
- if (customToleranceFn(res)) return fnParams[independentVariableIdx];
- }
- // set the new guess, correcting for maxStep
- oldGuess = fnParams[independentVariableIdx];
- newGuess = oldGuess + y;
- if (Math.abs(newGuess - oldGuess) > maxStep) {
- if (newGuess > oldGuess) {
- newGuess = oldGuess + maxStep;
- } else {
- newGuess = oldGuess - maxStep;
- }
- }
- fnParams[independentVariableIdx] = newGuess;
- // re-run the fn with the new guess
- y1 = (await fn(...fnParams)) - goal;
- if (isNaN(y1)) throw IsNanError;
- // calculate the error
- g = (y1 - y) / y;
- if (g === 0) g = 0.0001;
- // set the new guess based on the error, correcting for maxStep
- newGuess = oldGuess - y / g;
- if (maxStep && Math.abs(newGuess - oldGuess) > maxStep) {
- if (newGuess > oldGuess) {
- newGuess = oldGuess + maxStep;
- } else {
- newGuess = oldGuess - maxStep;
- }
- }
- fnParams[independentVariableIdx] = newGuess;
- }
- // done with iterations, and we failed to converge
- throw FailedToConvergeError;
- }
- // const fn = (x, y) => x / y;
- // const fnParams = [2037375, 15897178];
- // const customToleranceFn = (x) => {
- // return x < 1;
- // };
- // try {
- // const result = goalSeek({
- // fn,
- // fnParams,
- // customToleranceFn,
- // maxIterations: 1000,
- // maxStep: 0.01,
- // goal: 0.15,
- // independentVariableIdx: 0,
- // });
- // console.log(`result: ${result}`);
- // } catch (e) {
- // console.log("error", e);
- // }
|