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); // }