
Product
Introducing Scala and Kotlin Support in Socket
Socket now supports Scala and Kotlin, bringing AI-powered threat detection to JVM projects with easy manifest generation and fast, accurate scans.
@cortex-js/compute-engine
Advanced tools
Symbolic computing and numeric evaluations for JavaScript and Node.js
MathJSON is a lightweight mathematical notation interchange format based on JSON.
The Cortex Compute Engine can parse LaTeX to MathJSON, serialize MathJSON to LaTeX, format, simplify and evaluate MathJSON expressions.
Reference documentation and guides at cortexjs.io/compute-engine.
$ npm install --save @cortex-js/compute-engine
import { parse, evaluate } from "@cortex-js/compute-engine";
const expr = parse("2^{11}-1 \\in \\P");
console.log(expr);
// ➔ ["Element", ["Subtract", ["Power", 2, 11] , 1], "PrimeNumber"]
console.log(evaluate(expr));
// ➔ "False"
This project is licensed under the MIT License.
0.26.0 2024-10-01
The property expr.head
has been deprecated. Use expr.operator
instead.
expr.head
is still supported in this version but will be removed in a future
update.
The MathJSON utility functions head()
and op()
have been renamed to
operator()
and operand()
respectively.
The methods for algebraic operations (add
, div
, mul
, etc...) have been
moved from the Compute Engine to the Boxed Expression class. Instead of
calling ce.add(a, b)
, call a.add(b)
.
Those methods also behave more consistently: they apply some additional
simplication rules over canonicalization. For example, while
ce.parse('1 + 2')
return ["Add", 1, 2]
, ce.box(1).add(2)
will return
3
.
The ce.numericMode
option has been removed. Instead, set the ce.precision
property to the desired precision. Set the precision to "machine"
for
machine precision calculations (about 15 digits). Set it to "auto"
for a
default of 21 digits. Set it to a number for a greater fixed precision.
The MathJSON Dictionary element has been deprecated. Use a Dictionary
expression instead.
The ExtendedRealNumbers
, ExtendedComplexNumbers
domains have been
deprecated. Use the RealNumbers
and ComplexNumbers
domains instead.
The "Domain" expression has been deprecated. Use types instead (see below).
Some BoxedExpression
properties have been removed:
expr.isZero
, use expr.is(0)
.expr.isNotZero
, use !expr.is(0)
.expr.isOne
, use expr.is(1)
.expr.isNegativeOne
, use expr.is(-1)
.The signature of ce.declare()
has changed. In particular, the N
handler
has been replaced with evaluate
.
// Before
ce.declare('Mean', {
N: (ce: IComputeEngine): BoxedExpression => {
return ce.number(1);
},
});
// Now
ce.declare('Mean', { evaluate: (ops, { engine }) => ce.number(1) });
New Simplification Engine
The way expressions are simplified has been completely rewritten. The new engine is more powerful and more flexible.
The core API remains the same: to simplify an expression, use
expr.simplify()
.
To use a custom set of rules, pass the rules as an argument to simplify()
:
expr.simplify({rules: [
"|x:<0| -> -x",
"|x:>=0| -> x",
]});
There are a few changes to the way rules are represented. The priority
property has been removed. Instead, rules are applied in the order in which
they are defined.
A rule can also now be a function that takes an expression and returns a new expression. For example:
expr.simplify({rules: [
(expr) => {
if (expr.operator !== 'Abs') return undefined;
const x = expr.args[0];
return x.isNegative ? x.negate() : expr;
}
]});
This can be used to perform more complex transformations at the cost of more verbose JavaScript code.
The algorithm for simplification has been simplified. It attempts to apply each rule in the rule set in turn, then restarts the process until no more rules can be applied or the result of applying a rule returns a previously seen expression.
Function definitions previously included a simplify
handler that could be
used to perform simplifications specific to this function. This has been
removed. Instead, use a rule that matches the function and returns the
simplified expression.
Types
Previously, an expression was associated with a domain such as RealNumbers
or ComplexNumbers
. This has been replaced with a more flexible system of
types.
A type is a set of values that an expression can take. For example, the type
real
is the set of real numbers, the type integer
is the set of integers,
The type of an expression can be set with the type
property. For example:
const expr = ce.parse('\\sqrt{-1}');
console.info(expr.type); // -> imaginary
The type of a symbol can be set when declaring the symbol. For example:
ce.declare('x', 'imaginary');
In addition to primitive types, the type system supports more complex types such union types, intersection types, and function types.
For example, the type real|imaginary
is the union of the real and imaginary
numbers.
When declaring a function, the type of the arguments and the return value can
be specified. For example, to declare a function f
that takes two integers
and returns a real number:
ce.declare('f', '(integer, integer) -> real');
The sets of numbers are defined as follows:
number
- any number, real or complex, including NaN and infinitynon_finite_number
- NaN or infinityreal
finite_real
- finite real numbers (exclude NaN and infinity)imaginary
- imaginary numbers (complex numbers with a real part of 0)finite_imaginary
complex
- complex numbers with a real and imaginary part not equal to 0finite_complex
rational
finite_rational
integer
finite_integer
To check the type of an expression, use the isSubtypeOf()
method. For
example:
let expr = ce.parse('5');
console.info(expr.type.isSubtypeOf('rational')); // -> true
console.info(expr.type.isSubtypeOf('integer')); // -> true
expr = ce.parse('\\frac{1}{2}');
console.info(expr.type.isSubtypeOf('rational')); // -> true
console.info(expr.type.isSubtypeOf('integer')); // -> false
As a shortcut, the properties isReal
, isRational
, isInteger
are
available on boxed expressions. For example:
let expr = ce.parse('5');
console.info(expr.isInteger); // -> true
console.info(expr.isRational); // -> true
They are equivalent to expr.type.isSubtypeOf('integer')
and
expr.type.isSubtypeOf('rational')
respectively.
To check if a number has a non-zero imaginary part, use:
let expr = ce.parse('5i');
console.info(expr.isNumber && expr.isReal === false); // -> true
Collections
Support for collections has been improved. Collections include List
, Set
,
Tuple
, Range
, Interval
, Linspace
and Dictionary
.
It is now possible to check if an element is contained in a collection using
an Element
expression. For example:
let expr = ce.parse('[1, 2, 3]');
ce.box(['Element', 3, expr]).print(); // -> True
ce.box(['Element', 5, expr]).print(); // -> False
To check if a collection is a subset of another collection, use the Subset
expression. For example:
ce.box(['Subset', 'Integers', 'RealNumbers']).print(); // -> True
Collections can also be compared for equality. For example:
let set1 = ce.parse('\\lbrace 1, 2, 3 \\rbrace');
let set2 = ce.parse('\\lbrace 3, 2, 1 \\rbrace');
console.info(set1.isEqual(set2)); // -> true
There are also additional convenience methods on boxed expressions:
expr.isCollection
expr.contains(element)
expr.size
expr.isSubsetOf(other)
expr.indexOf(element)
expr.at(index)
expr.each()
expr.get(key)
Exact calculations
The Compute Engine has a new backed for numerical calculations. The new backed
can handle arbitrary precision calculations, including real and complex
numbers. It can also handle exact calculations, preserving calculations with
rationals and radicals (square root of integers). For example 1/2 + 1/3
is
evaluated to 5/6
instead of 0.8(3)
.
To get an approximate result, use the N()
method, for example
ce.parse("\\frac12 + \\frac13").N()
.
Previously the result of calculations was not always an exact number but returned a numerical approximation instead.
This has now been improved by introducing a NumericValue
type that
encapsulates exact numbers and by doing all calculations in this type.
Previously the calculations were handled manually in the various evaluation
functions. This made the code complicated and error prone.
A NumericValue
is made of:
For example:
While this is a significant change internally, the external API remains the same. The result of calculations should be more predictable and more accurate.
One change to the public API is that the expr.numericValue
property is now
either a machine precision number or a NumericValue
object.
Rule Wildcards
When defining a rule as a LaTeX expression, single character identifiers are
interpreted as wildcards. For example, the rule x + x -> 2x
will match any
expression with two identical terms. The wildcard corresponding to x
is
_x
.
It is now possible to define sequence wildcards and optional sequence wildcards. Sequence wildcards match 1 or more expressions, while optional sequence wildcards match 0 or more expressions.
They are indicated in LaTeX as ...x
and ...x?
respectively. For example:
expr.simplify("x + ...y -> 2x");
If expr
is a + b + c
the rule will match and return 2a
expr.simplify("x + ...y? -> 3x");
If expr
is a + b + c
the rule will match and return 3a
. If expr
is a
the rule will match and return 3a
.
Conditional Rules
Rules can now include conditions that are evaluated at runtime. If the condition is not satisfied, the rules does not apply.
For example, to simplify the expression |x|
:
expr.simplify({rules: [
"|x_{>=0}| -> x",
"|x_{<0}| -> -x",
]});
The condition is indicated as a subscript of the wildcard. The condition can be one of:
boolean
- a boolean value, True or False
string
- a string of characters
number
- a number literal
symbol
expression
numeric
- an expression that has a numeric value, i.e. 2√3, 1/2, 3.14
integer
- an integer value, -2, -1, 0, 1, 2, 3, ...
natural
- a natural number, 0, 1, 2, 3, ...
real
- real numbers, including integers
imaginary
- imaginary numbers, i.e. 2i, 3√-1 (not including real numbers)
complex
- complex numbers, including real and imaginary
rational
- rational numbers, 1/2, 3/4, 5/6, ...
irrational
- irrational numbers, √2, √3, π, ...
algebraic
- algebraic numbers, rational and irrational
transcendental
- transcendental numbers, π, e, ...
positive
- positive real numbers, > 0
negative
- negative real numbers, < 0
nonnegative
- nonnegative real numbers, >= 0
nonpositive
- nonpositive real numbers, <= 0
even
- even integers, 0, 2, 4, 6, ...
odd
- odd integers, 1, 3, 5, 7, ...
prime
:A000040 - prime numbers, 2, 3, 5, 7, 11, ...
composite
:A002808 - composite numbers, 4, 6, 8, 9, 10, ...
notzero
- a value that is not zero
notone
- a value that is not one
finite
- a finite value, not infinite
infinite
constant
variable
function
operator
relation
- an equation or inequality
equation
inequality
vector
- a tensor of rank 1
matrix
- a tensor of rank 2
list
- a collection of values
set
- a collection of unique values
tuple
- a fixed length list
single
- a tuple of length 1
pair
- a tuple of length 2
triple
- a tuple of length 3
collection
- a list, set, or tuple
tensor
- a nested list of values of the same type
scalar
- not a tensor or list
or one of the following expressions:
>0'
-> positive
,\gt0'
-> positive
,<0'
-> negative
,\lt0'
-> negative
,>=0'
-> nonnegative
,\geq0'
-> nonnegative
,<=0'
-> nonpositive
,\leq0'
-> nonpositive
,!=0'
-> notzero
,\neq0'
-> notzero
,!=1'
-> notone
,\neq1'
-> notone
,\in\Z'
-> integer
,\in\mathbb{Z}'
-> integer
,\in\N'
-> natural
,\in\mathbb{N}'
-> natural
,\in\R'
-> real
,\in\mathbb{R}'
-> real
,\in\C'
-> complex
,\in\mathbb{C}'
-> complex
,\in\Q'
-> rational
,\in\mathbb{Q}'
-> rational
,\in\Z^+'
-> integer,positive
,\in\Z^-'
-> intger,negative
,\in\Z^*'
-> nonzero
,\in\R^+'
-> positive
,\in\R^-'
-> negative
,\in\R^*'
-> real,nonzero
,\in\N^*'
-> integer,positive
,\in\N_0'
-> integer,nonnegative
,\in\R\backslash\Q'
-> irrational
,More complex conditions can be specified following a semi-colon, for example:
expr.simplify({x -> 2x; x < 10});
Note that this syntax complements the existing rule syntax, and can be used together with the existing, more verbose, rule syntax.
expr.simplify({rules: [
{match: "x + x", replace: "2x", condition: "x < 10"}
]});
This advanced syntax can specify more complex conditions, for example above
the rule will only apply if x
is less than 10.
Improved results for Expand
. In some cases the expression was not fully
expanded. For example, 4x(3x+2)-5(5x-4)
now returns 12x^2 - 17x + 20
.
Previously it returned 4x(3x+2)+25x-20
.
AsciiMath serialization The expr.toString()
method now returns a
serialization of the expression using the AsciiMath
format.
The serialization to AsciiMath can be customized using the toAsciiMath()
method. For example:
console.log(ce.box(['Sigma', 2]).toAsciiMath({functions: {Sigma: 'sigma'}}));
// -> sigma(2)
The tolerance can now be specified with a value of "auto"
which will use the
precision to determine a reasonable tolerance. The tolerance is used when
comparing two numbers for equality. The tolerance can be specified with the
ce.tolerance
property or in the Compute Engine constructor.
Boxed expressions have some additional properties:
expr.isNumberLiteral
- true if the expression is a number literal.This is
equivalent to checking if expr.numericValue
is not null
.expr.re
- the real part of the expression, if it is a number literal,
undefined
if not a number literal.expr.im
- the imaginary part of the expression, if it is a number literal,
undefined
if not a number literal.expr.bignumRe
- the real part of the expression as a bignum, if it is a
number literal, undefined
if not a number literal or a bignum
representation is not available.expr.bignumIm
- the imaginary part of the expression as a bignum, if it is
a number literal, undefined
if not a number literal or if a bignum
representation is not available.expr.root()
to get the root of the expression. For example, expr.root(3)
will return the cube root of the expression.expr.isLess(), expr.isEqual()
,
etc...) now accept a number argument. For example, expr.isGreater(1)
will
return true if the expression is greater than 1.Added LaTeX syntax to index collections. If a
is a collection:
a[i]
is parsed as ["At", "a", "i"]
.a[i,j]
is parsed as ["At", "a", "i", "j"]
.a_i
is parsed as ["At", "a", "i"]
.a_{i,j}
is parsed as ["At", "a", "i", "j"]
.Added support for Kronecker delta notation, i.e. \delta_{ij}
, which is
parsed as ["KroneckerDelta", "i", "j"]
and is equal to 1 if i = j
and 0
otherwise.
When a single index is provided the value of the function is 1 if the index is 0 and 0 otherwise
When multiple index are provided, the value of the function is 1 if all the indexes are equal and 0 otherwise.
Added support for Iverson Bracket notation, i.e. [a = b]
, which is parsed as
["Boole", ["Equal", "a", "b"]]
and is equal to 1 if its argument is true and
0 otherwise. The argument is expected to be a relational expression.
Implemented Unique
and Tally
on collections. Unique
returns a collection
with only the unique elements of the input collection, and Tally
returns a
collection with the count of each unique element.
console.log(ce.box(['Unique', ['List', 1, 2, 3, 1, 2, 3, 4, 5]]).value);
// -> [1, 2, 3, 4, 5]
console.log(ce.box(['Tally', ['List', 1, 2, 3, 1, 2, 3, 4, 5]]).value);
// -> [['List', 1, 2, 3, 4, 5], ['List', 2, 2, 2, 1, 1]]
Implemented the Map
, Filter
and Tabulate
functions. These functions can
be used to transform collections, for example:
// Using LaTeX
console.log(ce.parse('\\mathrm{Map}([3, 5, 7], x \\mapsto x^2)').toString());
// -> [9, 25, 49]
// Using boxed expressions
console.log(
ce.box(['Map', ['List', 3, 5, 7], ['Square', '_']]).value
);
// -> [9, 25, 49]
console.log(ce.box(['Tabulate',['Square', '_'], 5]).value);
// -> [1, 4, 9, 16, 25]
Tabulate
can be used with multiple indexes. For example, to generate a 4x4
unit matrix:
console.log(ce.box(['Tabulate', ['If', ['Equal', '_1', '_2'], 1, 0]], 4, 4).value);
// -> [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]
// Using the Kronecker delta notation:
console.log(ce.parse('\\mathrm{Tabulate}(i, j \\mapsto \\delta_{ij}, 4, 4)').value);
// -> [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]
Added Random
function. ["Random"]
returns a real pseudo-random number
betwen 0 and 1. ["Random", 10]
returns an integer between 0 and 9,
["Random", 5, 10]
returns an integer between 5 and 10.
Extended the definition of expr.isConstant
. Previously, it only applied to
symbols, e.g. Pi
. Now it apply to all expressions. expr.isConstant
is true
if the expression is a number literal, a symbol with a constant value, or a
pure function with constant arguments.
The boxed expression properties isPositive
, isNegative
, isNonNegative
,
isNonPositive
, isZero
, isNotZero
now return a useful value for most
function expressions. For example, ce.parse('|x + 1|').isPositive
is true.
If the value cannot be determined, the property will return undefined
. For
example, ce.parse('|x + 1|').isZero
is undefined
.
If the expression is not a real number, the property will return NaN
. For
example, ce.parse('i').isPositive
is NaN
.
Added Choose
function to compute binomial coefficients, i.e. Choose(5, 2)
is equal to 10.
The fallback for non-constructible complex values of trigonometric functions is now implemented via rules.
The canonical order of the arguments has changed and should be more consistent and predictable. In particular, for polynomials, the monomial order is now degrevlex.
Canonical expressions can now include a Root
expression. For example, the
canonical form of \\sqrt[3]{5}
is ["Root", 5, 3]
. Previously, these were
represented as ["Power", 5, ["Divide", 1, 3]]
.
The function definitions no longer have a N
handler. Instead the evaluate
handler has an optional {numericApproximation}
argument.
#188 Throw an error when invalid expressions are boxed, for example
ce.box(["Add", ["3"]])
.
Some LaTeX renderer can't render \/
, so use /
instead.
When definitions are added to the LaTeX dictionary, they now take precedence over the built-in definitions. This allows users to override the built-in definitions.
Improved parsing of functions, including when a mixture of named and positional arguments are used.
#175 Matching some patterns when the target had not enough operands would result in a runtime error.
FAQs
Symbolic computing and numeric evaluations for JavaScript and Node.js
The npm package @cortex-js/compute-engine receives a total of 34,824 weekly downloads. As such, @cortex-js/compute-engine popularity was classified as popular.
We found that @cortex-js/compute-engine demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Product
Socket now supports Scala and Kotlin, bringing AI-powered threat detection to JVM projects with easy manifest generation and fast, accurate scans.
Application Security
/Security News
Socket CEO Feross Aboukhadijeh and a16z partner Joel de la Garza discuss vibe coding, AI-driven software development, and how the rise of LLMs, despite their risks, still points toward a more secure and innovative future.
Research
/Security News
Threat actors hijacked Toptal’s GitHub org, publishing npm packages with malicious payloads that steal tokens and attempt to wipe victim systems.