2.1.0
ExprTk.js
supports both synchronous and asynchronous background execution of thunks precompiled from a string including asynchronous and multithreaded versions of TypedArray.prototype.map
and TypedArray.prototype.reduce
and a synchronous multi-threaded version of TypedArray.prototype.map
.
Its main advantage is that it allows deferring of heavy computation for asynchronous execution in a background thread - something that Node.js/V8 does not allow without the very complex mechanisms of worker_threads
.
Even in single-threaded synchronous mode ExprTk.js
outperforms the native JS TypedArray.prototype.map
running in V8 by a significant margin for all types and array sizes and it comes very close to a direct iterative for
loop.
It also supports being directly called from native add-ons, including native threads, without any synchronization with V8, allowing JS code to drive multi-threaded mathematical transformations.
npm install exprtk.js
const Float64Expression = require('exprtk.js').Float64;
const expr = new Float64Expression('(a + b) / 2');
const inputArray = new Float64Array(1e6);
// Happens in a background thread, iterates over 'a', with b=4
const outputArray = await expr.mapAsync(inputArray, 'a', { 'b': 4 });
// or with 4 threads on 4 cores
const outputArray = await expr.mapAsync(4, inputArray, 'a', { 'b': 4 });
Refer to the ExprTk manual for the full expression syntax.
(string)
function
(Array<string>?)
An array containing all the scalar variables' names, will be determined automatically if omitted, however order won't be guaranteed, scalars are passed by value
(Record<string, number>?)
An object containing all the vector variables' names and their sizes, vector size must be known at compilation (object construction), vectors are passed by reference and can be modified by the ExprTk expression
Expression
:
The
Expression
represents an expression compiled to an AST from a string. Expressions come in different flavors depending on the internal type used.
// This determines the internally used type
const expr = require("exprtk.js").Float64;
// arithmetic mean of 2 variables
const mean = new Expression('(a+b)/2', ['a', 'b']);
// naive stddev of an array of 1024 elements
const stddev = new Expression(
'var sum := 0; var sumsq := 0; ' +
'for (var i := 0; i < x[]; i += 1) { sum += x[i]; sumsq += x[i] * x[i] }; ' +
'(sumsq - (sum*sum) / x[]) / (x[] - 1);',
[], {x: 1024})
Return the data type constructor
Type: TypedArrayConstructor
Get the number of threads available for evaluating expressions.
Set by the EXPRTKJS_THREADS
environment variable.
Type: number
Get a string representation of this object
string
:
Generic vector operation with implicit traversal.
Supports automatic type conversions, multiple inputs, strided N-dimensional arrays and writing into a pre-existing array.
If using N-dimensional arrays, all arrays must have the same shape. The result is always in positive row-major order. When mixing linear vectors and N-dimensional arrays, the linear vectors are considered to be in positive row-major order in relation to the N-dimensional arrays.
(number?)
(Record<string, (number | TypedArray<any> | ndarray.NdArray<any> | stdlib.ndarray)>)
(TypedArray<T>?)
TypedArray<T>
:
// Air density of humid air from relative humidity (phi), temperature (T) and pressure (P)
// rho = ( Pd * Md + Pv * Mv ) / ( R * (T + 273.15) // density (Avogadro's law)
// Pv = phi * Ps // vapor pressure of water
// Ps = 6.1078 * 10 ^ (7.5 * T / (T + 237.3)) // saturation vapor pressure (Tetens' equation)
// Pd = P - Pv // partial pressure of dry air
// R = 0.0831446 // universal gas constant
// Md = 0.0289652 // molar mass of water vapor
// Mv = 0.018016 // molar mass of dry air
// ( this is the weather science form of the equation and not the hard physics one with T in C° )
// phi, T and P are arbitrary TypedArrays of the same size
//
// Calculation uses Float64 internally
// Result is stored in Float32
const R = 0.0831446;
const Md = 0.0289652;
const Mv = 0.018016;
// cwise()/cwiseAsync() accept and automatically convert all data types
const phi = new Float32Array([0, 0.2, 0.5, 0.9, 0.5]);
const P = new Uint16Array([1013, 1013, 1013, 1013, 995]);
const T = new Uint16Array([25, 25, 25, 25, 25]);
const density = new Float64Expression(
'Pv := ( phi * 6.1078 * pow(10, (7.5 * T / (T + 237.3))) ); ' + // compute Pv and store it
'( (P - Pv) * Md + Pv * Mv ) / ( R * (T + 273.13) )', // return expression
['P', 'T', 'phi', 'R', 'Md', 'Mv']
);
const result = new Float32Array(P.length);
// sync
density.cwise({phi, T, P, R, Md, Mv}, result);
// sync multithreaded
density.cwise(os.cpus().length, {phi, T, P, R, Md, Mv}, result);
// async
await density.cwiseAsync({phi, T, P, R, Md, Mv}, result);
// async multithreaded
await density.cwiseAsync(os.cpus().length, {phi, T, P, R, Md, Mv}, result);
Evaluate the expression.
All arrays must match the internal data type.
(...(Array<(number | TypedArray<T>)> | Record<string, (number | TypedArray<T>)>))
of the function
number
:
// These two are equivalent
const r1 = expr.eval({a: 2, b: 5}); // less error-prone
const r2 = expr.eval(2, 5); // slightly faster
// These two are equivalent
expr.evalAsync({a: 2, b: 5}, (e,r) => console.log(e, r));
expr.evalAsync(2, 5, (e,r) => console.log(e, r));
Evaluate the expression for every element of a TypedArray.
Evaluation and traversal happens entirely in C++ so this will be much
faster than calling array.map(expr.eval)
.
All arrays must match the internal data type.
If target is specified, it will write the data into a preallocated array. This can be used when multiple operations are chained to avoid reallocating a new array at every step. Otherwise it will return a new array.
(TypedArray<T>?)
number of threads to use, 1 if not specified
(TypedArray<T>?)
array in which the data is to be written, will allocate a new array if none is specified
(TypedArray<T>)
for the expression to be iterated over
(string)
variable name
(...(Array<(number | TypedArray<T>)> | Record<string, (number | TypedArray<T>)>))
of the function, iterator removed
TypedArray<T>
:
// Clamp values in an array to [0..1000]
const expr = new Expression('clamp(f, x, c)', ['f', 'x', 'c']);
// In a preallocated array
const r = new array.constructor(array.length);
// These two are equivalent
expr.map(r, array, 'x', 0, 1000);
expr.map(r, array, 'x', {f: 0, c: 0});
expr.mapAsync(r, array, 'x', 0, 1000, (e,r) => console.log(e, r));
expr.mapAsync(r, array, 'x', {f: 0, c: 0}, (e,r) => console.log(e, r));
// In a new array
// r1/r2 will be TypedArray's of the same type
const r1 = expr.map(array, 'x', 0, 1000);
const r2 = expr.map(array, 'x', {f: 0, c: 0});
expr.mapAsync(array, 'x', 0, 1000, (e,r) => console.log(e, r));
expr.mapAsync(array, 'x', {f: 0, c: 0}, (e,r) => console.log(e, r));
// Using multiple (4) parallel threads (OpenMP-style parallelism)
const r1 = expr.map(4, array, 'x', 0, 1000);
const r2 = await expr.mapAsync(4, array, 'x', {f: 0, c: 0});
Evaluate the expression for every element of a TypedArray passing a scalar accumulator to every evaluation.
Evaluation and traversal happens entirely in C++ so this will be much
faster than calling array.reduce(expr.eval)
.
All arrays must match the internal data type.
(TypedArray<T>)
for the expression to be iterated over
(string)
variable name
(string)
variable name
(number)
for the accumulator
(...(Array<(number | TypedArray<T>)> | Record<string, (number | TypedArray<T>)>))
of the function, iterator removed
number
:
// n-power sum of an array
const sum = new Expression('a + pow(x, p)', ['a', 'x', 'p']);
// sumSq will be a scalar number
// These are equivalent
const sumSq = sum.reduce(array, 'x', 'a', 0, {'p': 2});
const sumSq = sum.reduce(array, 'x', 'a', 0, 2);
sum.reduceAsync(array, 'x', {'a' : 0}, (e,r) => console.log(e, r));
const sumSq = await sum.reduceAsync(array, 'x', {'a' : 0}, (e,r) => console.log(e, r));
Return the data type constructor
Type: TypedArrayConstructor
Return the expression as a string
Type: string
Get the currently reached peak of simultaneously running instances for this Expression
Type: number
Get/set the maximum allowed parallel instances for this Expression
Type: number
Return the scalar arguments as an array
Type: Array<string>
Return the type as a string
Type: string
Return the type as a string
Type: string
Return the vector arguments as an object
Type: Record<string, Array<number>>