mathjs
Version:
Math.js is an extensive math library for JavaScript and Node.js. It features a flexible expression parser with support for symbolic computation, comes with a large set of built-in functions and constants, and offers an integrated solution to work with dif
844 lines (734 loc) • 21 kB
text/typescript
import {
create,
factory,
all,
MathJsFunctionName,
fractionDependencies,
addDependencies,
divideDependencies,
formatDependencies,
} from 'mathjs';
import * as assert from 'assert';
import { expectTypeOf } from 'expect-type'
// This file serves a dual purpose:
// 1) examples of how to use math.js in TypeScript
// 2) tests for the TypeScript declarations provided by math.js
/*
Basic usage examples
*/
{
const math = create(all);
// functions and constants
math.round(math.e, 3);
math.round(100.123, 3);
math.atan2(3, -3) / math.pi;
math.log(10000, 10);
math.sqrt(-4);
math.pow([[-1, 2], [3, 1]], 2);
const angle = 0.2;
math.add(math.pow(math.sin(angle), 2), math.pow(math.cos(angle), 2));
// std and variance check
math.std(1, 2, 3)
math.std([1, 2, 3])
math.std([1, 2, 3], "biased")
math.std([1,2, 3], 0, "biased")
math.std([[1,2,3], [4,5,6]], 1, "unbiased")
math.std([[1,2,3], [4,5,6]], 1, "uncorrected")
math.variance(1, 2, 3)
math.variance([1, 2, 3])
math.variance([1, 2, 3], "biased")
math.variance([1,2, 3], 0, "biased")
math.variance([[1,2,3], [4,5,6]], 1, "unbiased")
math.variance([[1,2,3], [4,5,6]], 1, "uncorrected")
// std and variance on chain
math.chain([1, 2, 3]).std("unbiased")
math.chain([[1, 2, 3], [4, 5, 6]]).std(0, "biased").std(0, "uncorrected")
math.chain([[1, 2, 3], [4, 5, 6]]).std(0, "biased").std(0, "uncorrected")
math.chain([1, 2, 3]).std("unbiased")
math.chain([[1, 2, 3], [4, 5, 6]]).variance(0, "biased")
math.chain([[1, 2, 3], [4, 5, 6]]).variance(1, "uncorrected").variance("unbiased")
// expressions
math.evaluate('1.2 * (2 + 4.5)');
// chained operations
const a = math.chain(3).add(4).multiply(2).done();
assert.strictEqual(a, 14);
// mixed use of different data types in functions
assert.deepStrictEqual(math.add(4, [5, 6]), [9, 10]); // number + Array
assert.deepStrictEqual(math.multiply(math.unit('5 mm'), 3), math.unit('15 mm')); // Unit * number
assert.deepStrictEqual(math.subtract([2, 3, 4], 5), [-3, -2, -1]); // Array - number
assert.deepStrictEqual(math.add(math.matrix([2, 3]), [4, 5]), math.matrix([6, 8])); // Matrix + Array
// narrowed type inference
const b: math.Matrix = math.add(math.matrix([2]), math.matrix([3]));
const c: math.Matrix = math.subtract(math.matrix([4]), math.matrix([5]));
}
/*
Bignumbers examples
*/
{
// configure the default type of numbers as BigNumbers
const math = create(all, {
number: 'BigNumber',
precision: 20,
});
{
assert.deepStrictEqual(math.add(math.bignumber(0.1), math.bignumber(0.2)), math.bignumber(0.3));
assert.deepStrictEqual(math.divide(math.bignumber(0.3), math.bignumber(0.2)), math.bignumber(1.5));
}
}
/*
Chaining examples
*/
{
const math = create(all, {});
const a = math.chain(3).add(4).multiply(2).done();
assert.strictEqual(a, 14);
// Another example, calculate square(sin(pi / 4))
const b = math.chain(math.pi).divide(4).sin().square().done();
// toString will return a string representation of the chain's value
const chain = math.chain(2).divide(3);
const str: string = chain.toString();
assert.strictEqual(str, "0.6666666666666666");
chain.valueOf();
// the function subset can be used to get or replace sub matrices
const array = [
[1, 2],
[3, 4],
];
const v = math.chain(array).subset(math.index(1, 0)).done();
assert.strictEqual(v, 3);
const m = math.chain(array).subset(math.index(0, 0), 8).multiply(3).done();
// filtering
assert.deepStrictEqual(
math
.chain([-1, 0, 1.1, 2, 3, 1000])
.filter(math.isPositive)
.filter(math.isInteger)
.filter((n) => n !== 1000)
.done(),
[2, 3]
);
}
/*
Simplify examples
*/
{
const math = create(all);
math.simplify("2 * 1 * x ^ (2 - 1)");
math.simplify("2 * 3 * x", { x: 4 });
const f = math.parse("2 * 1 * x ^ (2 - 1)");
math.simplify(f);
math.simplify("0.4 * x", {}, { exactFractions: true });
math.simplify("0.4 * x", {}, { exactFractions: false });
}
/*
Complex numbers examples
*/
{
const math = create(all, {});
const a = math.complex(2, 3);
// create a complex number by providing a string with real and complex parts
const b = math.complex('3 - 7i');
// read the real and complex parts of the complex number
{
const x: number = a.re;
const y: number = a.im;
// adjust the complex value
a.re = 5;
}
// clone a complex value
{
const clone = a.clone();
}
// perform operations with complex numbers
{
math.add(a, b);
math.multiply(a, b);
math.sin(a);
}
// create a complex number from polar coordinates
{
const p: math.PolarCoordinates = { r: math.sqrt(2), phi: math.pi / 4 };
const c: math.Complex = math.complex(p);
}
// get polar coordinates of a complex number
{
const p: math.PolarCoordinates = math.complex(3, 4).toPolar();
}
}
/*
Expressions examples
*/
{
const math = create(all, {});
// evaluate expressions
{
math.evaluate('sqrt(3^2 + 4^2)');
}
// evaluate multiple expressions at once
{
math.evaluate(['f = 3', 'g = 4', 'f * g']);
}
// get content of a parenthesis node
{
const node = math.parse('(1)');
if (node.type !== 'ParenthesisNode') {
throw Error(`expected ParenthesisNode, got ${node.type}`);
}
const innerNode = node.content;
}
// scope can contain both variables and functions
{
const scope = { hello: (name: string) => `hello, ${name}!` };
assert.strictEqual(math.evaluate('hello("hero")', scope), "hello, hero!");
}
// define a function as an expression
{
const scope: any = {
a: 3,
b: 4,
};
const f = math.evaluate('f(x) = x ^ a', scope);
f(2);
scope.f(2);
}
{
const node2 = math.parse('x^a');
const code2: math.EvalFunction = node2.compile();
node2.toString();
}
// 3. using function math.compile
// parse an expression
{
// provide a scope for the variable assignment
const code2 = math.compile('a = a + 3');
const scope = { a: 7 };
code2.evaluate(scope);
}
// 4. using a parser
const parser = math.parser();
// get and set variables and functions
{
assert.strictEqual(parser.evaluate('x = 7 / 2'), 3.5);
assert.strictEqual(parser.evaluate('x + 3'), 6.5);
parser.evaluate('f(x, y) = x^y'); // f(x, y)
assert.strictEqual(parser.evaluate('f(2, 3)'), 8);
const x = parser.get('x');
const f = parser.get('f');
const y = parser.getAll();
const g = f(3, 3);
parser.set('h', 500);
parser.set('hello', (name: string) => `hello, ${name}!`);
}
// clear defined functions and variables
parser.clear();
}
/*
Fractions examples
*/
{
// configure the default type of numbers as Fractions
const math = create(all, {
number: 'Fraction',
});
const x = math.fraction(0.125);
const y = math.fraction('1/3');
math.fraction(2, 3);
math.add(x, y);
math.divide(x, y);
// output formatting
const a = math.fraction('2/3');
}
/*
Matrices examples
*/
{
const math = create(all, {});
// create matrices and arrays. a matrix is just a wrapper around an Array,
// providing some handy utilities.
const a: math.Matrix = math.matrix([1, 4, 9, 16, 25]);
const b: math.Matrix = math.matrix(math.ones([2, 3]));
b.size();
// the Array data of a Matrix can be retrieved using valueOf()
const array = a.valueOf();
// Matrices can be cloned
const clone: math.Matrix = a.clone();
// perform operations with matrices
math.sqrt(a);
math.factorial(a);
// create and manipulate matrices. Arrays and Matrices can be used mixed.
{
const a = [
[1, 2],
[3, 4],
];
const b: math.Matrix = math.matrix([
[5, 6],
[1, 1],
]);
b.subset(math.index(1, [0, 1]), [[7, 8]]);
const c = math.multiply(a, b);
const f: math.Matrix = math.matrix([1, 0]);
const d: math.Matrix = f.subset(math.index(1));
}
// get a sub matrix
{
const a: math.Matrix = math.diag(math.range(1, 4));
a.subset(math.index([1, 2], [1, 2]));
const b: math.Matrix = math.range(1, 6);
b.subset(math.index(math.range(1, 4)));
}
// resize a multi dimensional matrix
{
const a = math.matrix();
a.resize([2, 2, 2], 0);
a.size();
a.resize([2, 2]);
a.size();
}
// can set a subset of a matrix to uninitialized
{
const m = math.matrix();
m.subset(math.index(2), 6, math.uninitialized);
}
// create ranges
{
math.range(1, 6);
math.range(0, 18, 3);
math.range('2:-1:-3');
math.factorial(math.range('1:6'));
}
// map matrix
{
assert.deepStrictEqual(
math.map([1, 2, 3], function (value) {
return value * value;
}),
[1, 4, 9]
);
}
// filter matrix
{
assert.deepStrictEqual(
math.filter([6, -2, -1, 4, 3], function (x) {
return x > 0;
}),
[6, 4, 3]
)
assert.deepStrictEqual(math.filter(['23', 'foo', '100', '55', 'bar'], /[0-9]+/), ["23", "100", "55"]);
}
// concat matrix
{
assert.deepStrictEqual(math.concat([[0, 1, 2]], [[1, 2, 3]]), [[ 0, 1, 2, 1, 2, 3 ]]);
assert.deepStrictEqual(math.concat([[0, 1, 2]], [[1, 2, 3]], 0), [[ 0, 1, 2 ], [ 1, 2, 3 ]]);
}
// Matrix is available as a constructor for instanceof checks
{
assert.strictEqual(math.matrix([1, 2, 3]) instanceof math.Matrix, true)
}
}
/*
Sparse matrices examples
*/
{
const math = create(all, {});
// create a sparse matrix
const a = math.identity(1000, 1000, 'sparse');
// do operations with a sparse matrix
const b = math.multiply(a, a);
const c = math.multiply(b, math.complex(2, 2));
const d = math.matrix([0, 1]);
const e = math.transpose(d);
const f = math.multiply(e, d);
}
/*
Units examples
*/
{
const math = create(all, {});
// units can be created by providing a value and unit name, or by providing
// a string with a valued unit.
const a = math.unit(45, 'cm'); // 450 mm
const b = math.unit('0.1m'); // 100 mm
const c = math.unit(b)
// creating units
math.createUnit('foo');
math.createUnit('furlong', '220 yards');
math.createUnit('furlong', '220 yards', { override: true });
math.createUnit('testunit', { definition: '0.555556 kelvin', offset: 459.67 });
math.createUnit('testunit', { definition: '0.555556 kelvin', offset: 459.67 }, { override: true });
math.createUnit('knot', { definition: '0.514444 m/s', aliases: ['knots', 'kt', 'kts'] });
math.createUnit('knot', { definition: '0.514444 m/s', aliases: ['knots', 'kt', 'kts'] }, { override: true });
math.createUnit(
'knot',
{
definition: '0.514444 m/s',
aliases: ['knots', 'kt', 'kts'],
prefixes: 'long',
},
{ override: true }
);
math.createUnit(
{
foo2: {
prefixes: 'long',
},
bar: '40 foo',
baz: {
definition: '1 bar/hour',
prefixes: 'long',
},
},
{
override: true,
}
);
// use Unit as definition
math.createUnit('c', { definition: b });
math.createUnit('c', { definition: b }, { override: true });
// units can be added, subtracted, and multiplied or divided by numbers and by other units
math.add(a, b);
math.multiply(b, 2);
math.divide(math.unit('1 m'), math.unit('1 s'));
math.pow(math.unit('12 in'), 3);
// units can be converted to a specific type, or to a number
b.to('cm');
math.to(b, 'inch');
b.toNumber('cm');
math.number(b, 'cm');
// the expression parser supports units too
math.evaluate('2 inch to cm');
// units can be converted to SI
math.unit('1 inch').toSI();
// units can be split into other units
math.unit('1 m').splitUnit(['ft', 'in']);
}
/*
Expression tree examples
*/
{
const math = create(all, {});
// Filter an expression tree
const node: math.MathNode = math.parse('x^2 + x/4 + 3*y');
const filtered: math.MathNode[] = node.filter((node: math.MathNode) => node.type === 'SymbolNode' && node.name === 'x');
const arr: string[] = filtered.map((node: math.MathNode) => node.toString());
// Traverse an expression tree
const node1: math.MathNode = math.parse('3 * x + 2');
node1.traverse((node: math.MathNode, path: string, parent: math.MathNode) => {
switch (node.type) {
case 'OperatorNode':
return node.type === 'OperatorNode';
case 'ConstantNode':
return node.type === 'ConstantNode';
case 'SymbolNode':
return node.type === 'SymbolNode';
default:
return;
}
});
}
/*
Function floor examples
*/
{
const math = create(all, {});
// number input
assert.strictEqual(math.floor(3.2), 3);
assert.strictEqual(math.floor(-4.2), -5);
// number input
// roundoff result to 2 decimals
assert.strictEqual(math.floor(3.212, 2), 3.21);
assert.strictEqual(math.floor(-4.212, 2), -4.22);
// Complex input
const c = math.complex(3.24, -2.71);
assert.deepStrictEqual(math.floor(c), math.complex(3, -3));
assert.deepStrictEqual(math.floor(c, 1), math.complex(3.2, -2.8));
//array input
assert.deepStrictEqual(math.floor([3.2, 3.8, -4.7]), [3, 3, -5]);
assert.deepStrictEqual(math.floor([3.21, 3.82, -4.71], 1), [3.2, 3.8, -4.8]);
}
/*
JSON serialization/deserialization
*/
{
const math = create(all, {});
const data = {
bigNumber: math.bignumber('1.5'),
};
const stringified = JSON.stringify(data);
const parsed = JSON.parse(stringified, math.reviver);
assert.deepStrictEqual(parsed.bigNumber, math.bignumber('1.5'));
}
/*
Extend functionality with import
*/
declare module 'mathjs' {
interface MathJsStatic {
testFun(): number;
value: number;
}
}
{
const math = create(all, {});
const testFun = () => 5;
math.import(
{
testFun,
value: 10,
},
{}
);
math.testFun();
const a = math.value * 2;
}
/*
Renamed functions from v5 => v6
*/
{
const math = create(all, {});
math.typeOf(1);
math.variance([1, 2, 3, 4]);
math.evaluate('1 + 2');
// chained operations
math.chain(3).typeOf().done();
math.chain([1, 2, 3]).variance().done();
math.chain('1 + 2').evaluate().done();
}
/*
Factory Test
*/
{
// create a factory function
const name = 'negativeSquare';
const dependencies: MathJsFunctionName[] = ['multiply', 'unaryMinus'];
const createNegativeSquare = factory(name, dependencies, (injected) => {
const { multiply, unaryMinus } = injected;
return function negativeSquare(x: number): number {
return unaryMinus(multiply(x, x));
};
});
// create an instance of the function yourself:
const multiply = (a: number, b: number) => a * b;
const unaryMinus = (a: number) => -a;
const negativeSquare = createNegativeSquare({ multiply, unaryMinus });
negativeSquare(3);
}
/**
* Dependency map typing test from mathjs official document:
* https://mathjs.org/docs/custom_bundling.html#using-just-a-few-functions
*/
{
const config = {
// optionally, you can specify configuration
};
// Create just the functions we need
const { fraction, add, divide, format } = create(
{
fractionDependencies,
addDependencies,
divideDependencies,
formatDependencies,
},
config
);
// Use the created functions
const a = fraction(1, 3);
const b = fraction(3, 7);
const c = add(a, b);
const d = divide(a, b);
assert.strictEqual(format(c), "16/21");
assert.strictEqual(format(d), "7/9");
}
/**
* Custom parsing functions
* https://mathjs.org/docs/expressions/customization.html#customize-supported-characters
*/
{
const math = create(all, {});
const isAlphaOriginal = math.parse.isAlpha;
math.parse.isAlpha = (c, cPrev, cNext) => {
return isAlphaOriginal(c, cPrev, cNext) || c === "\u260E";
};
// now we can use the \u260E (phone) character in expressions
const result = math.evaluate("\u260Efoo", { "\u260Efoo": 42 });
assert.strictEqual(result, 42);
}
/**
* Util functions
* https://mathjs.org/docs/reference/functions.html#utils-functions
*/
{
const math = create(all, {});
// hasNumericValue function
assert. strictEqual(math.hasNumericValue(2), true);
assert. strictEqual(math.hasNumericValue('2'), true);
assert. strictEqual(math.isNumeric('2'), false);
assert. strictEqual(math.hasNumericValue(0), true);
assert. strictEqual(math.hasNumericValue(math.bignumber(500)), true);
assert.deepStrictEqual(math.hasNumericValue([2.3, 'foo', false]), [true, false, true]);
assert. strictEqual(math.hasNumericValue(math.fraction(4)), true);
assert. strictEqual(math.hasNumericValue(math.complex('2-4i')), false);
}
/**
* src/util/is functions
*/
{
const math = create(all, {});
type IsFunc = (x: unknown) => boolean;
const isFuncs: IsFunc[] = [
math.isNumber,
math.isBigNumber,
math.isComplex,
math.isFraction,
math.isUnit,
math.isString,
math.isArray,
math.isMatrix,
math.isCollection,
math.isDenseMatrix,
math.isSparseMatrix,
math.isRange,
math.isIndex,
math.isBoolean,
math.isResultSet,
math.isHelp,
math.isFunction,
math.isDate,
math.isRegExp,
math.isObject,
math.isNull,
math.isUndefined,
math.isAccessorNode,
math.isArrayNode,
math.isAssignmentNode,
math.isBlockNode,
math.isConditionalNode,
math.isConstantNode,
math.isFunctionAssignmentNode,
math.isFunctionNode,
math.isIndexNode,
math.isNode,
math.isObjectNode,
math.isOperatorNode,
math.isParenthesisNode,
math.isRangeNode,
math.isSymbolNode,
math.isChain
]
isFuncs.forEach(f => {
const result = f(1);
const isResultBoolean = result === true || result === false;
assert.ok(isResultBoolean);
})
// Check guards do type refinement
let x: unknown
if (math.isNumber(x)) {
expectTypeOf(x).toMatchTypeOf<number>()
}
if (math.isBigNumber(x)) {
expectTypeOf(x).toMatchTypeOf<math.BigNumber>()
}
if (math.isComplex(x)) {
expectTypeOf(x).toMatchTypeOf<math.Complex>()
}
if (math.isFraction(x)) {
expectTypeOf(x).toMatchTypeOf<math.Fraction>()
}
if (math.isUnit(x)) {
expectTypeOf(x).toMatchTypeOf<math.Unit>()
}
if (math.isString(x)) {
expectTypeOf(x).toMatchTypeOf<string>()
}
if (math.isArray(x)) {
expectTypeOf(x).toMatchTypeOf<unknown[]>()
}
if (math.isMatrix(x)) {
expectTypeOf(x).toMatchTypeOf<math.Matrix>()
}
if (math.isDenseMatrix(x)) {
expectTypeOf(x).toMatchTypeOf<math.Matrix>()
}
if (math.isSparseMatrix(x)) {
expectTypeOf(x).toMatchTypeOf<math.Matrix>()
}
if (math.isIndex(x)) {
expectTypeOf(x).toMatchTypeOf<math.Index>()
}
if (math.isBoolean(x)) {
expectTypeOf(x).toMatchTypeOf<boolean>()
}
if (math.isHelp(x)) {
expectTypeOf(x).toMatchTypeOf<math.Help>()
}
if (math.isDate(x)) {
expectTypeOf(x).toMatchTypeOf<Date>()
}
if (math.isRegExp(x)) {
expectTypeOf(x).toMatchTypeOf<RegExp>()
}
if (math.isNull(x)) {
expectTypeOf(x).toMatchTypeOf<null>()
}
if (math.isUndefined(x)) {
expectTypeOf(x).toMatchTypeOf<undefined>()
}
if (math.isAccessorNode(x)) {
expectTypeOf(x).toMatchTypeOf<math.AccessorNode>()
}
if (math.isArrayNode(x)) {
expectTypeOf(x).toMatchTypeOf<math.ArrayNode>()
}
if (math.isAssignmentNode(x)) {
expectTypeOf(x).toMatchTypeOf<math.AssignmentNode>()
}
if (math.isBlockNode(x)) {
expectTypeOf(x).toMatchTypeOf<math.BlockNode>()
}
if (math.isConditionalNode(x)) {
expectTypeOf(x).toMatchTypeOf<math.ConditionalNode>()
}
if (math.isConstantNode(x)) {
expectTypeOf(x).toMatchTypeOf<math.ConstantNode>()
}
if (math.isFunctionAssignmentNode(x)) {
expectTypeOf(x).toMatchTypeOf<math.FunctionAssignmentNode>()
}
if (math.isFunctionNode(x)) {
expectTypeOf(x).toMatchTypeOf<math.FunctionNode>()
}
if (math.isIndexNode(x)) {
expectTypeOf(x).toMatchTypeOf<math.IndexNode>()
}
if (math.isNode(x)) {
expectTypeOf(x).toMatchTypeOf<math.MathNodeCommon>()
}
if (math.isNode(x)) {
expectTypeOf(x).toMatchTypeOf<math.MathNodeCommon>()
}
if (math.isObjectNode(x)) {
expectTypeOf(x).toMatchTypeOf<math.ObjectNode>()
}
if (math.isOperatorNode(x)) {
expectTypeOf(x).toMatchTypeOf<math.OperatorNode>()
}
if (math.isParenthesisNode(x)) {
expectTypeOf(x).toMatchTypeOf<math.ParenthesisNode>()
}
if (math.isRangeNode(x)) {
expectTypeOf(x).toMatchTypeOf<math.RangeNode>()
}
if (math.isSymbolNode(x)) {
expectTypeOf(x).toMatchTypeOf<math.SymbolNode>()
}
if (math.isChain(x)) {
expectTypeOf(x).toMatchTypeOf<math.MathJsChain>()
}
}
/*
Probability function examples
*/
{
const math = create(all, {});
expectTypeOf(math.lgamma(1.5)).toMatchTypeOf<number>()
expectTypeOf(math.lgamma(math.complex(1.5, -1.5))).toMatchTypeOf<math.Complex>()
}