Skip to content

Commit

Permalink
Merge pull request #209 from ChristopherChudzicki/diff-improvements
Browse files Browse the repository at this point in the history
Diff improvements
  • Loading branch information
ChristopherChudzicki committed Jul 18, 2019
2 parents e804a4d + 73489dd commit bea5714
Show file tree
Hide file tree
Showing 9 changed files with 100 additions and 16 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Expand Up @@ -2,6 +2,11 @@

## Version 1.2

## Version 1.2.2
- Add `pdiff` function with signature `pdiff(f, x, y,..., 1)` for the partial derivative of `f(x, y,...)` with respect to first argument, namely `x`.
- Add `curl` function that accepts 3-dimensional vector fields `F(x, y, z)` with signature `curl(F, x, y, z)`
- Add `div` function that accepts vector fields `F(x, y, ...)` with signature `div(F, x, y, ...)`

## Version 1.2.1
- fixed a bug that caused some successfully loaded graphs to crash when new object added (#200)
- added inverse trig functions `arctan`, `arcsin`, `arccos`. The `arctan` function can be called with 1 or 2 arguments.
Expand Down
2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "math3d-server",
"version": "1.2.1",
"version": "1.2.2",
"description": "Express server for math3d-react app.",
"engines": {
"node": "8.9.x"
Expand Down
2 changes: 1 addition & 1 deletion react-ui/package.json
@@ -1,6 +1,6 @@
{
"name": "math3d-react",
"version": "1.2.1",
"version": "1.2.2",
"proxy": "http://localhost:5000",
"private": true,
"dependencies": {
Expand Down
Expand Up @@ -227,7 +227,7 @@ export default class MathInput extends React.PureComponent<Props, State> {
onEdit={this.onEdit}
size={this.props.size}
autoCommands='sqrt pi theta phi'
autoOperatorNames='diff unitT unitN unitB cos sin tan sec csc cot arcsin arccos arctan log ln exp mod abs'
autoOperatorNames='diff pdiff curl div unitT unitN unitB cos sin tan sec csc cot arcsin arccos arctan log ln exp mod abs norm'
/>
</MathInputContainer>
)
Expand Down
Expand Up @@ -17,6 +17,10 @@ import { replaceAll, findClosingBrace } from '../../helpers'
export default function mathquillToMathJS(fromMQ: string) {
const replacements = [
{ tex: '\\operatorname{diff}', mathjs: 'diff' },
{ tex: '\\operatorname{pdiff}', mathjs: 'pdiff' },
{ tex: '\\operatorname{curl}', mathjs: 'curl' },
{ tex: '\\operatorname{div}', mathjs: 'div' },
{ tex: '\\operatorname{norm}', mathjs: 'norm' },
{ tex: '\\operatorname{mod}', mathjs: 'mod' },
{ tex: '\\operatorname{abs}', mathjs: 'abs' },
{ tex: '\\operatorname{unitT}', mathjs: 'unitT' },
Expand Down
51 changes: 43 additions & 8 deletions react-ui/src/utils/mathjs/derivatives.js
Expand Up @@ -5,14 +5,21 @@ import math from './custom'
type NumericFunction = (...args: Array<Numeric>) => Numeric

const EPS = 0.0008
const EPS2 = EPS/2

// TODO: diff currently accepts two call signatures:
// 1. diff(f), returns a function
// 2. diff(f, x, y...), returns a numeric value
// The first signature was used in a very old version of math3d, but is never
// used now internally or in examples. Also, it causes a bunch of flow problems
// so let's remove it.
export const diff = (f: NumericFunction, ...values: [] | Array<Numeric>) => {

const derivative = (...args: Array<Numeric>): Numeric => {
const derivComponents = args.map((arg, j) => {
args[j] = math.add(arg, -0.5*EPS)
args[j] = math.add(arg, -EPS2)
const initialValue = f(...args)
args[j] = math.add(arg, +0.5*EPS)
args[j] = math.add(arg, +EPS2)
const finalValue = f(...args)
args[j] = arg
return math.divide(math.subtract(finalValue, initialValue), EPS)
Expand All @@ -23,21 +30,49 @@ export const diff = (f: NumericFunction, ...values: [] | Array<Numeric>) => {
}
Object.defineProperty(derivative, 'length', { value: f.length } )

// $FlowFixMe
return values.length === 0
? derivative
: derivative(...values)
}

type Func3to1 = (t: number) => [number, number, number]
export const unitT = (f: Func3to1, t: number) => {
type Func1to3 = (t: number) => [number, number, number]

export const unitT = (f: Func1to3, t: number) => {
// $FlowFixMe ... need to make add/subtract polymorhpic & length-preserving
const tangent = math.subtract(f(t + EPS/2), f(t - EPS/2))
const tangent = math.subtract(f(t + EPS2), f(t - EPS2))
return math.divide(tangent, math.norm(tangent))
}
export const unitN = (f: Func3to1, t: number) => {
const normal = math.subtract(unitT(f, t + EPS/2), unitT(f, t - EPS/2))
export const unitN = (f: Func1to3, t: number) => {
const normal = math.subtract(unitT(f, t + EPS2), unitT(f, t - EPS2))
return math.divide(normal, math.norm(normal))
}
export const unitB = (f: Func3to1, t: number) => {
export const unitB = (f: Func1to3, t: number) => {
return math.cross(unitT(f, t), unitN(f, t))
}

export const curl = (f: NumericFunction, x: number, y: number, z: number) => {
const [dxf, dyf, dzf] = diff(f, x, y, z)
// $FlowFixMe flow has trouble recognizing that diff() returned a 3 x 3 matrix
return [ dyf[2] - dzf[1], dzf[0] - dxf[2], dxf[1] - dyf[0] ]
}

export const div = (f: NumericFunction, ...values: Array<number>) => {
// $FlowFixMe
const fullDeriv: Numeric = diff(f, ...values)
return math.trace(fullDeriv)
}

export const pdiff = (f: NumericFunction, ...args: Array<Numeric>) => {
// The last element of args should be the argument number we're differentiating
// with respect to. For example, pdiff(f, x, y, 1)

// $FlowFixMe The last element of args should be a number
const diffNumber = args.pop() - 1
const original = args[diffNumber]
args[diffNumber] = math.add(original, EPS2)
const finalValue = f(...args)
args[diffNumber] = math.add(original, -EPS2)
const initialValue = f(...args)
return math.divide(math.subtract(finalValue, initialValue), EPS)
}
39 changes: 38 additions & 1 deletion react-ui/src/utils/mathjs/derivatives.test.js
@@ -1,4 +1,4 @@
import { diff, unitT, unitN, unitB } from './derivatives'
import { diff, unitT, unitN, unitB, curl, div, pdiff } from './derivatives'
import math from './custom'
import { toNearlyEqual } from 'utils/testing/matchers'

Expand Down Expand Up @@ -111,3 +111,40 @@ describe('unitB', () => {

} )
} )

describe('curl', () => {
it('is correct in special case', () => {
const f = (x, y, z) => [x*y*z, x**2*y**2*z**2, x**3*y**3*z**3]
const manualCurl = (x, y, z) => [
-2*x**2*y**2*z + 3*x**3*y**2*z**3,
x*y - 3*x**2*y**3*z**3,
-x*z + 2*x*y**2*z**2
]
const calcCurl = (x, y, z) => curl(f, x, y, z)
expect(calcCurl).toNearlyEqual(manualCurl)

} )
} )

describe('div', () => {
it('is correct in special case', () => {
const f = (x, y, z) => [x*y*z, x**2*y**2*z**2, x**3*y**3*z**3]
const manualDiv = (x, y, z) => y*z + 2*x**2*y*z**2 + 3*x**3*y**3*z**2
const calcDiv = (x, y, z) => div(f, x, y, z)
expect(calcDiv).toNearlyEqual(manualDiv)
} )
} )

describe('pdiff', () => {
it('is correct for scalar functions', () => {
const f = (x, y, z) => x * y**2 * z**3
expect((x, y, z) => pdiff(f, x, y, z, 1))
.toNearlyEqual((x, y, z) => y**2 * z**3)

expect((x, y, z) => pdiff(f, x, y, z, 2))
.toNearlyEqual((x, y, z) => 2 * x * y * z**3)

expect((x, y, z) => pdiff(f, x, y, z, 3))
.toNearlyEqual((x, y, z) => 3 * x * y**2 * z**2)
} )
} )
5 changes: 4 additions & 1 deletion react-ui/src/utils/mathjs/index.js
@@ -1,6 +1,6 @@
// @flow
import customMath from './custom'
import { diff, unitT, unitN, unitB } from './derivatives'
import { diff, pdiff, unitT, unitN, unitB, curl, div } from './derivatives'

function arctan(arg0: number, arg1?: number) {
return arg1 === undefined ? customMath.atan(arg0) : customMath.atan2(arg0, arg1)
Expand All @@ -9,9 +9,12 @@ function arctan(arg0: number, arg1?: number) {
const imaginaryUnit = customMath.i
customMath.import( {
diff,
pdiff,
unitT,
unitN,
unitB,
div,
curl,
i: [1, 0, 0],
j: [0, 1, 0],
k: [0, 0, 1],
Expand Down
6 changes: 3 additions & 3 deletions react-ui/src/utils/mathjs/types.js
Expand Up @@ -21,8 +21,7 @@ type NodeBase<node> = {
}

type GenericNode<node> = NodeBase<node> & {
type:
| "AccessorNode"
type: | "AccessorNode"
| "ArrayNode"
// | "AssignmentNode" // treated separately, as are others commented out
| "BlockNode"
Expand Down Expand Up @@ -100,5 +99,6 @@ export type Math = {
acos: (number) => number,
asin: (number) => number,
atan: (number) => number,
atan2: (y: number, x: number) => number
atan2: (y: number, x: number) => number,
trace: (Numeric) => number
}

0 comments on commit bea5714

Please sign in to comment.