|
@@ -2,16 +2,17 @@ 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,
|
|
|
-}) {
|
|
|
+export default async function GoalSeek(options) {
|
|
|
+ const {
|
|
|
+ fn,
|
|
|
+ fnParams,
|
|
|
+ percentTolerance,
|
|
|
+ customToleranceFn,
|
|
|
+ maxIterations,
|
|
|
+ maxStep,
|
|
|
+ goal,
|
|
|
+ independentVariableIdx,
|
|
|
+ } = options;
|
|
|
if (typeof customToleranceFn !== 'function') {
|
|
|
if (!percentTolerance) {
|
|
|
throw InvalidInputsError;
|
|
@@ -19,49 +20,78 @@ export default async function GoalSeek({
|
|
|
}
|
|
|
let g;
|
|
|
let y;
|
|
|
- let y1;
|
|
|
- let oldGuess;
|
|
|
+ let oldY = 0;
|
|
|
+ let oldGuess = fnParams[independentVariableIdx];
|
|
|
let newGuess;
|
|
|
let res;
|
|
|
+ let max;
|
|
|
+ let min;
|
|
|
const absoluteTolerance = ((percentTolerance || 0) / 100) * goal;
|
|
|
+
|
|
|
+ async function getMin(minGuest) {
|
|
|
+ let copyParams = [...fnParams]
|
|
|
+ copyParams[independentVariableIdx] = minGuest
|
|
|
+ res = await fn(...copyParams);
|
|
|
+ if (res > goal) {
|
|
|
+ minGuest -= oldGuess / 2;
|
|
|
+ await getMin(minGuest);
|
|
|
+ }
|
|
|
+ return minGuest;
|
|
|
+ }
|
|
|
+
|
|
|
+ async function getMax(maxGuest) {
|
|
|
+ let copyParams = [...fnParams]
|
|
|
+ copyParams[independentVariableIdx] = maxGuest
|
|
|
+ res = await fn(...copyParams);
|
|
|
+ if (res < goal) {
|
|
|
+ maxGuest += oldGuess / 2;
|
|
|
+ await getMax(maxGuest);
|
|
|
+ }
|
|
|
+ return maxGuest;
|
|
|
+ }
|
|
|
// 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;
|
|
|
+
|
|
|
+ // newGuess = oldGuess + y;
|
|
|
+ // if (Math.abs(newGuess - oldGuess) > maxStep) {
|
|
|
+ // if (newGuess > oldGuess) {
|
|
|
+ // newGuess = oldGuess + maxStep;
|
|
|
+ // } else {
|
|
|
+ // newGuess = oldGuess - maxStep;
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+ // 获取最大值和最小值
|
|
|
+ if (y > 0) {
|
|
|
+ max = oldGuess;
|
|
|
+ if (!min) {
|
|
|
+ min = await getMin(oldGuess / 2);
|
|
|
}
|
|
|
- }
|
|
|
- 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;
|
|
|
+ } else {
|
|
|
+ min = oldGuess;
|
|
|
+ if (!max) {
|
|
|
+ max = await getMax(oldGuess * 2);
|
|
|
}
|
|
|
}
|
|
|
+ // if(oldY) {
|
|
|
+ // if(y > oldY) {
|
|
|
+
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+ // oldY = y;
|
|
|
+ // 利用二分法查询
|
|
|
+ newGuess = (min + max) / 2;
|
|
|
fnParams[independentVariableIdx] = newGuess;
|
|
|
}
|
|
|
// done with iterations, and we failed to converge
|