GoalSeek.js 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. const IsNanError = TypeError('resulted in NaN');
  2. const FailedToConvergeError = Error('failed to converge');
  3. const InvalidInputsError = Error('invalid inputs');
  4. export default async function GoalSeek(options) {
  5. const {
  6. fn,
  7. fnParams,
  8. percentTolerance,
  9. customToleranceFn,
  10. maxIterations,
  11. maxStep,
  12. goal,
  13. independentVariableIdx,
  14. } = options;
  15. if (typeof customToleranceFn !== 'function') {
  16. if (!percentTolerance) {
  17. throw InvalidInputsError;
  18. }
  19. }
  20. let g;
  21. let y;
  22. let oldY = 0;
  23. let oldGuess = fnParams[independentVariableIdx];
  24. let newGuess;
  25. let res;
  26. let max;
  27. let min;
  28. const absoluteTolerance = ((percentTolerance || 0) / 100) * goal;
  29. async function getMin(minGuest) {
  30. let copyParams = [...fnParams]
  31. copyParams[independentVariableIdx] = minGuest
  32. res = await fn(...copyParams);
  33. if (res > goal) {
  34. minGuest -= oldGuess / 2;
  35. await getMin(minGuest);
  36. }
  37. return minGuest;
  38. }
  39. async function getMax(maxGuest) {
  40. let copyParams = [...fnParams]
  41. copyParams[independentVariableIdx] = maxGuest
  42. res = await fn(...copyParams);
  43. if (res < goal) {
  44. maxGuest += oldGuess / 2;
  45. await getMax(maxGuest);
  46. }
  47. return maxGuest;
  48. }
  49. // iterate through the guesses
  50. for (let i = 0; i < maxIterations; i++) {
  51. // define the root of the function as the error
  52. res = await fn(...fnParams);
  53. y = res - goal;
  54. if (isNaN(y)) throw IsNanError;
  55. // 判断是否满足条件
  56. if (typeof customToleranceFn !== 'function') {
  57. if (Math.abs(y) <= Math.abs(absoluteTolerance)) return fnParams[independentVariableIdx];
  58. } else {
  59. if (customToleranceFn(res)) return fnParams[independentVariableIdx];
  60. }
  61. // 获取要改变的变量
  62. oldGuess = fnParams[independentVariableIdx];
  63. // newGuess = oldGuess + y;
  64. // if (Math.abs(newGuess - oldGuess) > maxStep) {
  65. // if (newGuess > oldGuess) {
  66. // newGuess = oldGuess + maxStep;
  67. // } else {
  68. // newGuess = oldGuess - maxStep;
  69. // }
  70. // }
  71. // 获取最大值和最小值
  72. if (y > 0) {
  73. max = oldGuess;
  74. if (!min) {
  75. min = await getMin(oldGuess / 2);
  76. }
  77. } else {
  78. min = oldGuess;
  79. if (!max) {
  80. max = await getMax(oldGuess * 2);
  81. }
  82. }
  83. // if(oldY) {
  84. // if(y > oldY) {
  85. // }
  86. // }
  87. // oldY = y;
  88. // 利用二分法查询
  89. newGuess = (min + max) / 2;
  90. fnParams[independentVariableIdx] = newGuess;
  91. }
  92. // done with iterations, and we failed to converge
  93. throw FailedToConvergeError;
  94. }
  95. // const fn = (x, y) => x / y;
  96. // const fnParams = [2037375, 15897178];
  97. // const customToleranceFn = (x) => {
  98. // return x < 1;
  99. // };
  100. // try {
  101. // const result = goalSeek({
  102. // fn,
  103. // fnParams,
  104. // customToleranceFn,
  105. // maxIterations: 1000,
  106. // maxStep: 0.01,
  107. // goal: 0.15,
  108. // independentVariableIdx: 0,
  109. // });
  110. // console.log(`result: ${result}`);
  111. // } catch (e) {
  112. // console.log("error", e);
  113. // }