Skip to content

Commit

Permalink
adds custom tolerance function
Browse files Browse the repository at this point in the history
  • Loading branch information
adam-hanna committed Apr 22, 2021
1 parent c28854d commit 0b59ef6
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 15 deletions.
4 changes: 4 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[*.{js,jsx,ts,tsx,py,go}]
charset = utf-8
indent_style = space
indent_size = 2
43 changes: 37 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ The package exports two error types, the function parameter type and one functio
```typescript
export const IsNanError = TypeError('resulted in NaN');
export const FailedToConvergeError = Error('failed to converge');
export const InvalidInputsError = Error('invalid inputs');

export type Params = {
fn: (...inputs: any[]) => number;
fnParams: any[];
percentTolerance: number;
percentTolerance?: number;
customToleranceFn?: (arg0: number) => boolean;
maxIterations: number;
maxStep: number;
goal: number;
Expand All @@ -50,11 +52,12 @@ The `goalSeek` function takes one object argument with the following keys:

1. `fn` - the function, "f(x)" that is being evaluated.
2. `fnParams` - an array of parameters that are to be used as inputs to `fn`.
3. `percentTolerance` - the acceptable error range to the stated goal. For example, if `goal: 100` and `percentTolerance: 1`, then any values in the range [99, 101] will be accepted as correct (± 1% of 100).
4. `maxIterations` - the maximum number of attempts to make.
5. `maxStep` - the maximum magnitude step size to move the independent variable `x` for the next guess.
6. `goal` - the desired output of the `fn`.
7. `independentVariableIdx` - the index position of the independent variable `x` in the `fnParams` array.
3. `percentTolerance` - the acceptable error range to the stated goal. For example, if `goal: 100` and `percentTolerance: 1`, then any values in the range [99, 101] will be accepted as correct (± 1% of 100). If used, `customToleranceFn` should be left undefined.
4. `customToleranceFn` - a custom function that can be used to check the validity of a result. If used, `percentTolerance` should be left undefined.
5. `maxIterations` - the maximum number of attempts to make.
6. `maxStep` - the maximum magnitude step size to move the independent variable `x` for the next guess.
7. `goal` - the desired output of the `fn`.
8. `independentVariableIdx` - the index position of the independent variable `x` in the `fnParams` array.

To use the function, for example, with a simple linear equeation:

Expand Down Expand Up @@ -117,6 +120,34 @@ try {
console.log('error', e);
}

// result: 7
```

```javascript
import goalSeek from 'goal-seek';

const fn = (x,y,z) => x * y * z;
const fnParams = [4,5,6];
const customToleranceFn = (x: number): boolean => {
return x < 1
}

try {
const result = goalSeek({
fn,
fnParams,
customToleranceFn,
maxIterations: 1000,
maxStep: 0.01,
goal: 0.5,
independentVariableIdx: 2
});

console.log(`result: ${result}`);
} catch (e) {
console.log('error', e);
}

// result: 7
```

Expand Down
2 changes: 1 addition & 1 deletion dist/index.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 19 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
export type Params = {
fn: (...inputs: any[]) => number;
fnParams: any[];
percentTolerance: number;
percentTolerance?: number;
customToleranceFn?: (arg0: number) => boolean;
maxIterations: number;
maxStep: number;
goal: number;
Expand All @@ -10,32 +11,46 @@ export type Params = {

export const IsNanError = TypeError('resulted in NaN');
export const FailedToConvergeError = Error('failed to converge');
export const InvalidInputsError = Error('invalid inputs');

const goalSeek = ({
fn,
fnParams,
percentTolerance,
customToleranceFn,
maxIterations,
maxStep,
goal,
independentVariableIdx,
}: Params): number => {
if (typeof customToleranceFn !== 'function') {
if (!percentTolerance) {
throw InvalidInputsError
}
}

let g: number;
let y: number;
let y1: number;
let oldGuess: number;
let newGuess: number;
let res: number;

const absoluteTolerance = (percentTolerance / 100) * goal;
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
y = fn.apply(null, fnParams) - goal;
res = fn.apply(null, fnParams)
y = res - goal;
if (isNaN(y)) throw IsNanError;

// was our initial guess a good one?
if (Math.abs(y) <= Math.abs(absoluteTolerance)) return fnParams[independentVariableIdx];
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];
Expand Down
57 changes: 53 additions & 4 deletions tests/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import goalSeek, { IsNanError, FailedToConvergeError } from '../src';
import goalSeek, { IsNanError, FailedToConvergeError, InvalidInputsError } from '../src';

describe('goalSeek', () => {
test('a simple linear equation', () => {
Expand Down Expand Up @@ -39,11 +39,11 @@ describe('goalSeek', () => {

test('throws on NaN', done => {
let x = 10;
const fn = (x: number): number => Math.log(-1);
const fn = (_x: number): number => Math.log(-1);
const fnParams = [x];

try {
const result = goalSeek({
const _result = goalSeek({
fn,
fnParams,
percentTolerance: 1,
Expand All @@ -66,7 +66,7 @@ describe('goalSeek', () => {
const fnParams = [x];

try {
const result = goalSeek({
const _result = goalSeek({
fn,
fnParams,
percentTolerance: 1,
Expand All @@ -82,4 +82,53 @@ describe('goalSeek', () => {
done()
}
})

test('throws an invalid inputs', done => {
let x = 10;
const fn = (x: number): number => x * x;
const fnParams = [x];

try {
const _result = goalSeek({
fn,
fnParams,
maxIterations: 10,
maxStep: 10,
goal: -1,
independentVariableIdx: 0
})

done.fail('expected to throw')
} catch(e) {
expect(e).toEqual(InvalidInputsError);
done()
}
})

test('custom tolerance function', done => {
let x = 6
const fn = (x: number, y: number, z: number) => x * y * z;
const fnParams = [4,5,x];
const customToleranceFn = (x: number): boolean => {
return x < 1
}

try {
const result = goalSeek({
fn,
fnParams,
customToleranceFn,
maxIterations: 1000,
maxStep: 0.1,
goal: 0.5,
independentVariableIdx: 0
})

expect(result).toBeLessThan(1);
done()
} catch(e) {
console.error(e)
done.fail("expected not to throw")
}
})
});

0 comments on commit 0b59ef6

Please sign in to comment.