@cortex-js/compute-engine
Advanced tools
Changelog
0.30.2 2025-07-15
The expr.value
property reflects the value of the expression if it is a
number literal or a symbol with a literal value. If you previously used the
expr.value
property to get the value of an expression, you should now use
the expr.N().valueOf()
method instead. The valueOf()
method is suitable
for interoperability with JavaScript, but it may result in a loss of precision
for numbers with more than 15 digits.
BoxedExpr.sgn
now returns undefined for complex numbers, or symbols with a
complex-number value.
The ce.assign()
method previously accepted
ce.assign("f(x, y)", ce.parse("x+y"))
. This is now deprecated. Use
ce.assign("f", ce.parse("(x, y) \\mapsto x+y")
instead.
It was previously possible to invoke expr.evaluate()
or expr.N()
on a
non-canonical expression. This will now return the expression itself.
To evaluate a non-canonical expression, use expr.canonical.evaluate()
or
expr.canonical.N()
.
That's also the case for the methods numeratorDenominator()
, numerator()
,
and denominator()
.
In addition, invoking the methods inv()
, abs()
, add()
, mul()
, div()
,
pow()
, root()
, ln()
will throw an error if the expression is not
canonical.
Collections now support lazy materialization. This means that the elements of some collection are not computed until they are needed. This can significantly improve performance when working with large collections, and allow working with infinite collections. For example:
ce.box(['Map', 'Integers', 'Square']).evaluate().print();
// -> [0, 1, 4, 9, 16, ...]
Materialization can be controlled with the materialization
option of the
evaluate()
method. Lazy collections are materialized by default when
converted to a string or LaTeX, or when assigned to a variable.
The bindings of symbols and function expressions is now consistently done during canonicalization.
It was previously not possible to change the type of an identifier from a function to a value or vice versa. This is now possible.
Antiderivatives are now computed symbolically:
ce.parse(`\\int_0^1 \\sin(\\pi x) dx`).evaluate().print();
// -> 2 / pi
ce.parse(`\\int \\sin(\\pi x) dx`).evaluate().print();
// -> -cos(pi * x) / pi
Requesting a numeric approximation of the integral will use a Monte Carlo method:
ce.parse(`\\int_0^1 \\sin(\\pi x) dx`).N().print();
// -> 0.6366
Numeric approximations of integrals is several order of magnitude faster.
Added Number Theory functions: Totient
, Sigma0
, Sigma1
,
SigmaMinus1
, IsPerfect
, Eulerian
, Stirling
, NPartition
,
IsTriangular
, IsSquare
, IsOctahedral
, IsCenteredSquare
, IsHappy
,
IsAbundant
.
Added Combinatorics functions: Choose
, Fibonacci
, Binomial
,
CartesianProduct
, PowerSet
, Permutations
, Combinations
, Multinomial
,
Subfactorial
and BellNumber
.
The symbol
type can be refined to match a specific symbol. For example
symbol<True>
. The type expression
can be refined to match expressions with
a specific operator, for example expression<Add>
is a type that matches
expressions with the Add
operator. The numeric types can be refined with a
lower and upper bound. For example integer<0..10>
is a type that matches
integers between 0 and 10. The type real<1..>
matches real numbers greater
than 1 and rational<..0>
matches non-positive rational numbers.
Numeric types can now be constrained with a lower and upper bound. For
example, real<0..10>
is a type that matches real numbers between 0 and 10.
The type integer<1..>
matches integers greater than or equal to 1.
Collections that can be indexed (list
, tuple
) are now a subtype of
indexed_collection
.
The map
type has been replaced with dictionary
for collections of
arbitrary key-value pairs and record
for collections of structured key-value
pairs.
Support for structural typing has been added. To define a structural type, use
ce.declareType()
with the alias
flag, for example:
ce.declareType(
"point", "tuple<x: integer, y: integer>",
{ alias: true }
);
Recursive types are now supported by using the type
keyword to forward
reference types. For example, to define a type for a binary tree:
ce.declareType(
"binary_tree",
"tuple<value: integer, left: type binary_tree?, right: type binary_tree?>",
);
The syntax for variadic arguments has changeed. To indicate a variadic
argument, use a +
or *
after the type, for example:
ce.declare('f', '(number+) -> number');
Use +
for a non-empty list of arguments and *
for a possibly empty list.
Added a rule to solve the equation a^x + b = 0
The LaTeX parser now supports the \placeholder[]{}
, \phantom{}
,
\hphantom{}
, \vphantom{}
, \mathstrut
, \strut
and \smash{}
commands.
The range of recognized sign values, i.e. as returned from
BoxedExpression.sgn
has been simplified (e.g. '...-infinity' and 'nan' have
been removed)
The Power canonical-form is less aggressive - only carrying-out ops. as listed
in doc. - is much more careful in its consideration of operand types &
values... (for example, typically, exponents are required to be numbers:
e.g. x^1
will simplify, but x^y
(where y===0
), or x^{1+0}
, will not)
Ensure expression LaTeX serialization is based on MathJSON generated with
matching "pretty" formatting (or not), therefore resulting in LaTeX with less
prettification, where prettify === false
(#daef87f)
Symbols declare with a constant
flag are now not marked as "inferred"
Some BoxedSymbols
properties now more consistently return undefined
,
instead of a boolean
(i.e. because the symbol is non-bound)
Some expr.root()
computations
Canonical-forms
Number
formNumber
, Power
) do not mistakenly fully canonicalize
operandsholdUntil
value of "never"
during/prior-to canonicalization (i.e. just
like for full canonicalization)Changelog
0.29.1 2025-03-31
10e-15
were
incorrectly rounded to 0.Changelog
0.28.0 2025-02-06
#211 More consistent canonicalization and serialization of exact numeric
values of the form (a√b)/c
.
#219 The invisibleOperator
canonicalization previously also
canonicalized some multiplication.
#218 Improved performance of parsing invisible operators, including fixing some cases where the parsing was incorrect.
#216 Correctly parse subscripts with a single character, for example
x_1
.
#216 Parse some non-standard integral signs, for example
\int x \cdot \differentialD x
(both the \cdot
and the \differentialD
are
non-standard).
#210 Numeric approximation of odd nth roots of negative numbers evaluate correctly.
#153 Correctly parse integrals with \limits
, e.g.
\int\limits_0^1 x^2 \mathrm{d} x
.
Correctly serialize to ASCIIMath Delimiter
expressions.
When inferring the type of numeric values do not constrain them to be real
.
As a result:
ce.assign('a', ce.parse('i'));
ce.parse('a+1').evaluate().print();
now returns 1 + i
instead of throwing a type error.
Correctly parse and evaluate unary and binary \pm
and \mp
operators.
expr.isEqual()
will now return true/false if the expressions include the
same unknowns and are structurally equal after expansion and simplifications.
For example:
console.info(ce.parse('(x+1)^2').isEqual(ce.parse('x^2+2x+1')));
// -> true
Some computations can be time-consuming, for example, computing a very large factorial. To prevent the browser from freezing, the Compute Engine can now perform some operations asynchronously.
To perform an asynchronous operation, use the expr.evaluateAsync
method. For
example:
try {
const fact = ce.parse('(70!)!');
const factResult = await fact.evaluateAsync();
factResult.print();
} catch (e) {
console.error(e);
}
It is also possible to interrupt an operation, for example by providing a
pause/cancel button that the user can press. To do so, use an AbortController
object and a signal
. For example:
const abort = new AbortController();
const signal = abort.signal;
setTimeout(() => abort.abort(), 500);
try {
const fact = ce.parse('(70!)!');
const factResult = await fact.evaluateAsync({ signal });
factResult.print();
} catch (e) {
console.error(e);
}
In the example above, we trigger an abort after 500ms.
It is also possible to control how long an operation can run by setting the
ce.timeLimit
property with a value in milliseconds. For example:
ce.timeLimit = 1000;
try {
const fact = ce.parse('(70!)!');
fact.evaluate().print();
} catch (e) {
console.error(e);
}
The time limit applies to either the synchronous or asynchronous evaluation.
The default time limit is 2,000ms (2 seconds).
When an operation is canceled either because of a timeout or an abort, a
CancellationError
is thrown.
Changelog
0.27.0 2024-12-02
#217 Correctly parse LaTeX expressions that include a command followed by
a *
such as \\pi*2
.
#217 Correctly calculate the angle of trigonometric expressions with an
expression containing a reference to Pi
, for example \\sin(\\pi^2)
.
The Factorial
function will now time out if the argument is too large. The
timeout is signaled by throwing a CancellationError
.
When specifying exp.toMathJSON({shorthands:[]})
, i.e., not to use shorthands
in the MathJSON, actually avoid using shorthands.
Correctly use custom multiply, plus, etc. for LaTeX serialization.
When comparing two numeric values, the tolerance is now used to determine if
the values are equal. The tolerance can be set with the ce.tolerance
property.
When comparing two expressions with isEqual()
the values are compared
structurally when necessary, or with a stochastic test when the expressions
are too complex to compare structurally.
Correctly serialize nested superscripts, e.g. x^{y^z}
.
The result of evaluating a Hold
expression is now the expression itself.
To prevent evaluation of an expression temporarily, use the Unevaluated
function. The result of evaluating an Unevaluated
expression is its
argument.
The type of a Hold
expression was incorrectly returned as string
. It now
returns the type of its argument.
The statistics function (Mean
, Median
, Variance
, StandardDeviation
,
Kurtosis
, Skewness
, Mode
, Quartiles
and InterQuartileRange
) now
accept as argument either a collection or a sequence of values.
ce.parse("\\mathrm{Mean}([7, 2, 11])").evaluate().print();
// -> 20/3
ce.parse("\\mathrm{Mean}(7, 2, 11)").evaluate().print();
// -> 20/3
The Variance
and StandardDeviation
functions now have variants for
population statistics, PopulationVariance
and PopulationStandardDeviation
.
The default is to use sample statistics.
ce.parse("\\mathrm{PopulationVariance}([7, 2, 11])").evaluate().print();
// -> 13.555
ce.parse("\\mathrm{Variance}([7, 2, 11])").evaluate().print();
// -> 20.333
The statistics function can now be compiled to JavaScript:
const code = ce.parse("\\mathrm{Mean}(7, 2, 11)").compile();
console.log(code());
// -> 13.555
The statistics function calculate either using machine numbers or bignums
depending on the precision. The precision can be set with the precision
property of the Compute Engine.
The argument of compiled function is now optional.
Compiled expressions can now reference external JavaScript functions. For example:
ce.defineFunction('Foo', {
signature: 'number -> number',
evaluate: ([x]) => ce.box(['Add', x, 1]),
});
const fn = ce.box(['Foo', 3]).compile({
functions: { Foo: (x) => x + 1 },
})!;
console.info(fn());
// -> 4
ce.defineFunction('Foo', {
signature: 'number -> number',
evaluate: ([x]) => ce.box(['Add', x, 1]),
});
function foo(x) {
return x + 1;
}
const fn = ce.box(['Foo', 3]).compile({
functions: { Foo: foo },
})!;
console.info(fn());
// -> 4
Additionally, functions can be implicitly imported (in case they are needed by other JavaScript functions):
ce.defineFunction('Foo', {
signature: 'number -> number',
evaluate: ([x]) => ce.box(['Add', x, 1]),
});
function bar(x, y) {
return x + y;
}
function foo(x) {
return bar(x, 1);
}
const fn = ce.box(['Foo', 3]).compile({
functions: { Foo: 'foo' },
imports: [foo, bar],
})!;
console.info(fn());
// -> 4
Compiled expression can now include an arbitrary preamble (JavaScript source) that is executed before the compiled function is executed. This can be used to define additional functions or constants.
ce.defineFunction('Foo', {
signature: 'number -> number',
evaluate: ([x]) => ce.box(['Add', x, 1]),
});
const code = ce.box(['Foo', 3]).compile({
preamble: "function Foo(x) { return x + 1};",
});
The hold
function definition flag has been renamed to lazy
Changelog
0.26.4 2024-10-17
A_\text{1}
were not parsed correctly.Changelog
0.26.3 2024-10-17
fractionalDigits
when formatting numbers.\\lnot\\forall
and \\lnot\\exists
.\\sqrt[3]{125}
.1/ln(0)
was incorrectly evaluated to 1
. It now returns 0
.