title | slug |
---|---|
Compiling Expressions |
/compute-engine/guides/compiling/ |
Some expressions can take a long time to evaluate numerically, for example if they contain a large number of terms or involve a loop \((\sum\) or \(\prod\)).
In this case, it is useful to compile the expression into a JavaScript function that can be evaluated much faster.
For example this approximation: $ \pi \approx \textstyle\sqrt{6\sum^{10^6}_{n=1}\frac{1}{n^2}} $
const expr = ce.parse("\\sqrt{6\\sum^{10^2}_{n=1}\\frac{1}{n^2}}");
// Numerical evaluation using the Compute Engine
console.time('evaluate');
console.timeEnd('evaluate', expr.evaluate());
// Compilation to a JavaScript function and execution
console.time('compile');
const fn = expr.compile();
console.timeEnd('compile', fn());
To get a compiled version of an expression use the expr.compile()
method:
const expr = ce.parse("2\\prod_{n=1}^{\\infty} \\frac{4n^2}{4n^2-1}");
const fn = expr.compile();
To evaluate the compiled expression call the function returned by
expr.compile()
:
console.log(fn());
// ➔ 3.141592653589793
If the expression cannot be compiled, the compile()
method will throw an error.
The function returned by expr.compile()
can be called with an object literal
containing the value of the arguments:
const expr = ce.parse("n^2");
const fn = expr.compile();
for (let i = 1; i < 10; i++) console.log(fn({ n: i }));
To get a list of the unknows of an expression use the expr.unknowns
property:
console.log(ce.parse("n^2").unknowns);
console.log(ce.parse("a^2+b^3").unknowns);
The calculations are only performed using machine precision numbers.
Complex numbers, arbitrary precision numbers, and symbolic calculations are not supported.
Some functions are not supported.
If the expression cannot be compiled, the compile()
method will throw an
error. The expression can be numerically evaluated as a fallback:
function compileOrEvaluate(expr) {
try {
const fn = expr.compile();
return fn() + " (compiled)";
} catch (e) {
return expr.N().numericValue + " (evaluated)";
}
}
// `expr.compile()` can handle this expression
console.log(compileOrEvaluate(ce.parse("\\frac{\\sqrt{5}+1}{2}")));
// `expr.compile()` cannot handle complex numbers, so it throws
// and we fall back to numerical evaluation with expr.N()
console.log(compileOrEvaluate(ce.parse("-i\\sqrt{-1}")));
title: Execution Constraints slug: /compute-engine/guides/execution-constraints/ date: Last Modified
Symbol | Description |
---|---|
timeLimit |
|
iterationLimit |
The functions in this section produce a visual difference that is not material to the interpretation of an expression such as text color and size or other typographic variations.
They are inert and the value of a ["Function", _expr_]
expression is expr
.
expr
expr, delim
Visually group expressions with an open delimiter, a close delimiter and separators between elements of the expression.
When serializing to LaTeX, render expr wrapped in delimiters.
The Delimiter
function is inert and the value of a ["Delimiter", _expr_]
expression is expr
.
expr is a function expression, usually a ["Sequence"]
. It should
not be a symbol or a number.
delim is an optional string:
- when it is a single character it is a separator
- when it is two characters, the first is the opening delimiter and the second is the closing delimiter
- when it is three characters, the first is the opening delimiter, the second is the separator, and the third is the closing delimiter
The delimiters are rendered to LaTeX.
The open and close delimiters are a single character, one of: ()[]{}<>|‖⌈⌉⌊⌋⌜⌝⌞⌟⎰⎱"
. The open and close delimiters do not have to match.
For example, "')]'"
is a valid delimiter.
If an open or close delimiter is .
, it is ignored.
The separator delimiter is also a single character, one of ,;.&:|-
or U+00B7
(middle dot), U+2022
(bullet) or U+2026
(ellipsis).
If no delim is provided, a default delimiter is used based on the type of expr:
["Sequence"]
->(,)
["Tuple"]
,["Single"]
,["Pair"]
,["Triple"]
->(,)
["List"]
->[,]
["Set"]
->{,}
width
When serializing to LaTeX, width
is the dimension of the spacing, in 1/18 em.
The Spacing
function is inert and the value of a ["Spacing", _expr_]
expression is expr
.
expr, dictionary
expr
an expressiondictionary
a dictionary with one or more of the following keys:_"display"_
:"inline"
for\textstyle
"block"
for\displaystyle
"script"
for\scriptstyle
"scriptscript"
for\scriptscriptstyle
_"size"_
:1
...10
. Size5
is normal, size1
is smallest_"color"_
The Style
function is inert and the value of a ["Style", _expr_]
expression is expr
.
Symbol | Description | |
---|---|---|
ImaginaryUnit |
$$ \imaginaryI $$ | The imaginary unit, solution of |
z
Evaluate to the real part of a complex number.
["Real", ["Complex", 3, 4]]
// ➔ 3
z
Evaluate to the imaginary part of a complex number. If z is a real number, the imaginary part is zero.
["Imaginary", ["Complex", 3, 4]]
// ➔ 4
["Imaginary", "Pi"]
// ➔ 0
z
Evaluate to the complex conjugate of a complex number. The conjugates of complex numbers give the mirror image of the complex number about the real axis.
["Conjugate", ["Complex", 3, 4]]
// ➔ ["Complex", 3, -4]
z
Evaluate to the magnitude of a complex number.
The magnitude of a complex number is the distance from the origin to the point representing the complex number in the complex plane.
["Abs", ["Complex", 3, 4]]
// ➔ 5
z
Evaluate to the argument of a complex number.
The argument of a complex number is the angle between the positive real axis and the line joining the origin to the point representing the complex number in the complex plane.
["Arg", ["Complex", 3, 4]]
// ➔ 0.9272952180016122
z
Return a tuple of the magnitude and argument of a complex number.
This corresponds to the polar representation of a complex number.
["AbsArg", ["Complex", 3, 4]]
// ➔ [5, 0.9272952180016122]
z, n
Retrurn a list of the nth roots of a number z.
The complex roots of a number are the solutions of the equation
- Wikipedia: nth root
- Wikipedia: Root of unity
- Wolfram MathWorld: Root of unity
// The three complex roots of unity (1)
["ComplexRoots", 1, 3]
// ➔ [1, -1/2 + sqrt(3)/2, -1/2 - sqrt(3)/2]
title: Compute Engine API Reference sidebar_label: API Reference slug: /compute-engine/api/ toc_max_heading_level: 3
import APIFilter from '@site/src/components/APIFilter'; import MemberCard from '@site/src/components/MemberCard';
type AngularUnit = "rad" | "deg" | "grad" | "turn";
When a unitless value is passed to or returned from a trigonometric function, the angular unit of the value.
rad
: radians, 2π radians is a full circledeg
: degrees, 360 degrees is a full circlegrad
: gradians, 400 gradians is a full circleturn
: turns, 1 turn is a full circle
type AssignValue =
| boolean
| number
| bigint
| SemiBoxedExpression
| (args, options) => BoxedExpression
| undefined;
type EvalContext = {
lexicalScope: Scope;
assumptions: ExpressionMapInterface<boolean>;
values: Record<string, BoxedExpression | undefined>;
name: undefined | string;
};
An evaluation context is a set of bindings mapping symbols to their values. It also includes a reference to the lexical scope of the context, as well as a set of assumptions about the values of the symbols.
Eval contexts are arranged in a stack structure. When a new context is created, it is pushed on the top of the stack.
A new eval context is created when a function expression that needs to track its own local variables and named arguments is evaluated. This kind of function is a "scoped" function, meaning that it has its own local variables and named arguments.
For example, the Sum
function creates a new eval context to track the local
variable used as the index of the sum.
The eval context stack is used to resolve the value of symbols.
When a scoped recursive function is called, a new context is created for each recursive call.
In contrast, the lexical scope is used to resolve the metadata about symbols, such as their type, whether they are constant, etc... A new scope is not created for recursive calls, since the metadata does not change, only the values of the symbols change.
The name of the eval context is used to print a "stack trace" for debugging.
:::info[THEORY OF OPERATIONS]
The BoxedExpression
interface includes the methods and properties
applicable to all kinds of expression. For example it includes expr.symbol
which only applies to symbols or expr.ops
which only applies to
function expressions.
When a property is not applicable to this BoxedExpression
its value is
null
. For example expr.symbol
for a BoxedNumber
is null
.
This convention makes it convenient to manipulate expressions without having to check what kind of instance they are before manipulating them. :::
:::info[THEORY OF OPERATIONS]
A boxed expression can represent a canonical or a non-canonical
expression. A non-canonical expression is a "raw" form of the
expression. For example, the non-canonical representation of \frac{10}{20}
is ["Divide", 10, 20]
. The canonical representation of the same
expression is the boxed number 1/2
.
The canonical representation of symbols and function expressions are bound to a definition. The definition contains metadata about the symbol or function operator, such as its type, its signature, and other attributes. The value of symbols are tracked in a separate table for each evaluation context.
The binding only occurs when the expression is constructed, if it is created as a canonical expression. If the expression is constructed as a non-canonical expression, no binding is done.
:::
:::info[THEORY OF OPERATIONS] The value of an expression is a number, a string, a boolean or a tensor.
The value of number literals and strings are themselves.
A symbol can have a value associated with it, in which case the value of the symbol is the value associated with it.
Some symbols (unknowns) are purely symbolic and have no value associated with them.
Function expressions do not have a value associated with them.
For example, ["Add", 2, 3]
has no value associated with it, it is a
symbolic expression.
Some properties of a Boxed Expression are only applicable if the expression
has a value associated with it. For example, expr.isNumber
is only
applicable if the value of the expression is a number, that is if the
expression is a number literal or a symbol with a numeric value.
The following properties are applicable to expressions with a value:
expr.isNumber
:::
To create a boxed expression:
Use ce.box()
or ce.parse()
.
Use ce.parse()
to get a boxed expression from a LaTeX string.
Use ce.box()
to get a boxed expression from a MathJSON expression.
By default, the result of these methods is a canonical expression. For example, if it is a rational literal, it is reduced to its canonical form. If it is a function expression:
- the arguments are put in canonical form
- the arguments of commutative functions are sorted
- invisible operators are made explicit
- a limited number of core simplifications are applied, for example rationals are reduced
- sequences are flattened:
["Add", 1, ["Sequence", 2, 3]]
is transformed to["Add", 1, 2, 3]
- associative functions are flattened:
["Add", 1, ["Add", 2, 3]]
is transformed to["Add", 1, 2, 3]
- symbols are not replaced with their values (unless they have
a
holdUntil
flag set tonever
).
This is a specialized version of ce.box()
for creating a new function
expression.
The canonical handler of the operator is called.
The boxed expression have some algebraic methods, i.e. add()
, mul()
,
div()
, pow()
, etc. These methods are suitable for
internal calculations, although they may be used as part of the public
API as well.
- a runtime error is thrown if the expression is not canonical
- the arguments are not evaluated
- the canonical handler (of the corresponding operation) is not called
- some additional simplifications over canonicalization are applied.
For example number literals are combined.
However, the result is exact, and no approximation is made. Use
.N()
to get an approximate value. This is equivalent to callingsimplify()
on the expression (but without simplifying the arguments). - sequences were already flattened as part of the canonicalization process
For 'add()' and 'mul()', which take multiple arguments, separate functions
are provided that take an array of arguments. They are equivalent
to calling the boxed algebraic method, i.e. ce.Zero.add(1, 2, 3)
and
add(1, 2, 3)
are equivalent.
These methods are not equivalent to calling expr.evaluate()
on the
expression: evaluate will replace symbols with their values, and
evaluate the expression.
For algebraic functions (add()
, mul()
, etc..), use the corresponding
canonicalization function, i.e. canonicalAdd(a, b)
instead of
ce.function('Add', [a, b])
.
Another option is to use the algebraic methods directly, i.e. a.add(b)
instead of ce.function('Add', [a, b])
. However, the algebraic methods will
apply further simplifications which may or may not be desirable. For
example, number literals will be combined.
This method is a low level method to create a new function expression which is typically invoked in the canonical handler of an operator definition.
The arguments are not modified. The expression is not put in canonical form. The canonical handler is not called.
A canonical flag can be set when calling this method, but it only asserts that the function expression is canonical. The caller is responsible for ensuring that is the case.
Canonical handlers are responsible for:
- validating the signature: this can involve checking the number of arguments. It is recommended to avoid checking the type of non-literal arguments, since the type of symbols or function expressions may change. Similarly, the canonicalization process should not rely on the value of or assumptions about non-literal arguments.
- flattening sequences
- flattening arguments if the function is associative
- sort the arguments (if the function is commutative)
- calling
ce._fn()
to create a new function expression
When the canonical handler is invoked, the arguments have been put in
canonical form unless the lazy
flag is set to true
.
Note that the result of a canonical handler should be a canonical expression,
but not all arguments need to be canonical. For example, the arguments of
["Declare", "x", 2]
are not canonical, since x
refers to the name
of the symbol, not its value.
readonly isFunctionExpression: boolean;
Return true
if this expression is a function expression.
If true
, expr.ops
is not null
, and expr.operator
is the name
of the function.
readonly operator: string;
The name of the operator of the expression.
For example, the name of the operator of ["Add", 2, 3]
is "Add"
.
A string literal has a "String"
operator.
A symbol has a "Symbol"
operator.
A number has a "Number"
, "Real"
, "Rational"
or "Integer"
operator; amongst some others.
Practically speaking, for fully canonical and valid expressions, all of these are likely to
collapse to "Number"
.
readonly ops: readonly BoxedExpression[];
The list of operands of the function.
If the expression is not a function, return null
.
:::info[Note] Applicable to canonical and non-canonical expressions. :::
readonly nops: number;
If this expression is a function, the number of operands, otherwise 0.
Note that a function can have 0 operands, so to check if this expression
is a function, check if this.ops !== null
instead.
:::info[Note] Applicable to canonical and non-canonical expressions. :::
readonly op1: BoxedExpression;
First operand, i.e.this.ops[0]
.
If there is no first operand, return the symbol Nothing
.
:::info[Note] Applicable to canonical and non-canonical expressions. :::
readonly op2: BoxedExpression;
Second operand, i.e.this.ops[1]
If there is no second operand, return the symbol Nothing
.
:::info[Note] Applicable to canonical and non-canonical expressions. :::
readonly op3: BoxedExpression;
Third operand, i.e. this.ops[2]
If there is no third operand, return the symbol Nothing
.
:::info[Note] Applicable to canonical and non-canonical expressions. :::
readonly isNumberLiteral: boolean;
Return true
if this expression is a number literal, for example
2
, 3.14
, 1/2
, √2
etc.
When true
, expr.numericValue
is not null
.
readonly numericValue: number | NumericValue;
Return the value of this expression, if a number literal.
Note it is possible for expr.numericValue
to be null
, and for
expr.isNotZero
to be true. For example, when a symbol has been
defined with an assumption.
Conversely, expr.isNumber
may be true even if expr.numericValue
is
null
, for example the symbol Pi
return true
for isNumber
but
expr.numericValue
is null
(it's a symbol, not a number literal).
Its value can be accessed with expr.value
.
To check if an expression is a number literal, use expr.isNumberLiteral
.
If expr.isNumberLiteral
is true
, expr.numericValue
is not null
.
readonly isEven: boolean;
If the value of this expression is not an integer return undefined
.
readonly isOdd: boolean;
If the value of this expression is not an integer return undefined
.
readonly re: number;
Return the real part of the value of this expression, if a number.
Otherwise, return NaN
(not a number).
readonly im: number;
If value of this expression is a number, return the imaginary part of the value. If the value is a real number, the imaginary part is 0.
Otherwise, return NaN
(not a number).
readonly bignumRe: Decimal;
If the value of this expression is a number, return the real part of the
value as a BigNum
.
If the value is not available as a bignum return undefined
. That is,
the value is not upconverted to a bignum.
To get the real value either as a bignum or a number, use
expr.bignumRe ?? expr.re
.
When using this pattern, the value is returned as a bignum if available,
otherwise as a number or NaN
if the value is not a number.
readonly bignumIm: Decimal;
If the value of this expression is a number, return the imaginary part as
a BigNum
.
It may be 0 if the number is real.
If the value of the expression is not a number or the value is not
available as a bignum return undefined
. That is, the value is not
upconverted to a bignum.
To get the imaginary value either as a bignum or a number, use
expr.bignumIm ?? expr.im
.
When using this pattern, the value is returned as a bignum if available, otherwise as a number or NaN
if the value is not a number.
readonly sgn: Sign;
Return the sign of the expression.
Note that complex numbers have no natural ordering, so if the value is an
imaginary number (a complex number with a non-zero imaginary part),
this.sgn
will return unsigned
.
If a symbol, this does take assumptions into account, that is this.sgn
will return positive
if the symbol is assumed to be positive
using ce.assume()
.
Non-canonical expressions return undefined
.
readonly isPositive: boolean;
The value of this expression is > 0, same as isGreaterEqual(0)
readonly isNonNegative: boolean;
The value of this expression is >= 0, same as isGreaterEqual(0)
readonly isNegative: boolean;
The value of this expression is < 0, same as isLess(0)
readonly isNonPositive: boolean;
The value of this expression is <= 0, same as isLessEqual(0)
readonly isNaN: boolean;
If true, the value of this expression is "Not a Number".
A value representing undefined result of computations, such as 0/0
,
as per the floating point format standard IEEE-754.
Note that if isNaN
is true, isNumber
is also true (yes, NaN
is a
number).
readonly isInfinity: boolean;
The numeric value of this expression is ±Infinity
or ComplexInfinity.
readonly isFinite: boolean;
This expression is a number, but not ±Infinity
, ComplexInfinity
or
NaN
readonly engine: ComputeEngine;
The Compute Engine instance associated with this expression provides a context in which to interpret it, such as definition of symbols and functions.
toLatex(options?): string
Serialize to a LaTeX string.
Will ignore any LaTeX metadata.
####### options?
Partial
<SerializeLatexOptions
>
LaTeX representation of this expression.
If the expression was parsed from LaTeX, the LaTeX representation is the same as the input LaTeX.
To customize the serialization, use expr.toLatex()
.
:::info[Note] Applicable to canonical and non-canonical expressions. :::
toMathJson(options?): Expression
Serialize to a MathJSON expression with specified options
####### options?
Readonly
<Partial
<JsonSerializationOptions
>>
readonly json: Expression;
MathJSON representation of this expression.
This representation always use shorthands when possible. Metadata is not included.
Numbers are converted to JavaScript numbers and may lose precision.
The expression is represented exactly and no sugaring is applied. For
example, ["Power", "x", 2]
is not represented as ["Square", "x"]
.
For more control over the serialization, use expr.toMathJson()
.
:::info[Note] Applicable to canonical and non-canonical expressions. :::
print(): void
Output to the console a string representation of the expression.
optional verbatimLatex: string;
If the expression was constructed from a LaTeX string, the verbatim LaTeX string it was parsed from.
If true
, this expression is in a canonical form.
If true
, this expression is in a structural form.
The structural form of an expression is used when applying rules to
an expression. For example, a rational number is represented as a
function expression instead of a BoxedExpression
object.
Return the canonical form of this expression.
If a function expression or symbol, they are first bound with a definition in the current scope.
When determining the canonical form the following operator definition flags are applied:
associative
: \( f(a, f(b), c) \longrightarrow f(a, b, c) \)idempotent
: \( f(f(a)) \longrightarrow f(a) \)involution
: \( f(f(a)) \longrightarrow a \)commutative
: sort the arguments.
If this expression is already canonical, the value of canonical is
this
.
The arguments of a canonical function expression may not all be
canonical, for example in the ["Declare", "i", 2]
expression,
i
is not canonical since it is used only as the name of a symbol, not
as a (potentially) existing symbol.
:::info[Note]
Partially canonical expressions, such as those produced through
CanonicalForm
, also yield an expression which is marked as canonical
.
This means that, likewise for partially canonical expressions, the
canonical
property will return the self-same expression (and
'isCanonical' will also be true).
:::
Return the structural form of this expression.
Some expressions, such as rational numbers, are represented with
a BoxedExpression
object. In some cases, for example when doing a
structural comparison of two expressions, it is useful to have a
structural representation of the expression where the rational numbers
is represented by a function expression instead.
If there is a structural representation of the expression, return it,
otherwise return this
.
readonly isValid: boolean;
false
if this expression or any of its subexpressions is an ["Error"]
expression.
:::info[Note] Applicable to canonical and non-canonical expressions. For non-canonical expression, this may indicate a syntax error while parsing LaTeX. For canonical expression, this may indicate argument type mismatch, or missing or unexpected arguments. :::
readonly isPure: boolean;
If true, evaluating this expression has no side-effects (does not change the state of the Compute Engine).
If false, evaluating this expression may change the state of the Compute Engine or it may return a different value each time it is evaluated, even if the state of the Compute Engine is the same.
As an example, the ["Add", 2, 3]function expression is pure, but the
["Random"]` function expression is not pure.
For a function expression to be pure, the function itself (its operator) must be pure, and all of its arguments must be pure too.
A pure function expression may return a different value each time it is
evaluated if its arguments are not constant. For example, the
["Add", "x", 1]
function expression is pure, but it is not
constant, because x
is not constant.
:::info[Note] Applicable to canonical expressions only :::
readonly isConstant: boolean;
True
if evaluating this expression always returns the same value.
If true and a function expression, implies that it is pure and that all of its arguments are constant.
Number literals, symbols with constant values, and pure numeric functions with constant arguments are all constant, i.e.:
42
is constantPi
is constant["Divide", "Pi", 2]
is constantx
is not constant, unless declared with a constant flag.["Add", "x", 2]
is either constant only ifx
is constant.
readonly errors: readonly BoxedExpression[];
All the ["Error"]
subexpressions.
If an expression includes an error, the expression is also an error.
In that case, the this.isValid
property is false
.
:::info[Note] Applicable to canonical and non-canonical expressions. :::
getSubexpressions(operator): readonly BoxedExpression[]
All the subexpressions matching the named operator, recursively.
:::info[Note] Applicable to canonical and non-canonical expressions. :::
####### operator
string
readonly subexpressions: readonly BoxedExpression[];
All the subexpressions in this expression, recursively
:::info[Note] Applicable to canonical and non-canonical expressions. :::
readonly symbols: readonly string[];
All the symbols in the expression, recursively
:::info[Note] Applicable to canonical and non-canonical expressions. :::
readonly unknowns: readonly string[];
All the symbols used in the expression that do not have a value associated with them, i.e. they are declared but not defined.
toNumericValue(): [NumericValue, BoxedExpression]
Attempt to factor a numeric coefficient c
and a rest
out of a
canonical expression such that rest.mul(c)
is equal to this
.
Attempts to make rest
a positive value (i.e. pulls out negative sign).
['Multiply', 2, 'x', 3, 'a']
-> [NumericValue(6), ['Multiply', 'x', 'a']]
['Divide', ['Multiply', 2, 'x'], ['Multiply', 3, 'y', 'a']]
-> [NumericValue({rational: [2, 3]}), ['Divide', 'x', ['Multiply, 'y', 'a']]]
neg(): BoxedExpression
Negate (additive inverse)
inv(): BoxedExpression
Inverse (multiplicative inverse)
abs(): BoxedExpression
Absolute value
add(rhs): BoxedExpression
Addition
####### rhs
number
| BoxedExpression
sub(rhs): BoxedExpression
Subtraction
####### rhs
mul(rhs): BoxedExpression
Multiplication
####### rhs
number
| NumericValue
| BoxedExpression
div(rhs): BoxedExpression
Division
####### rhs
number
| BoxedExpression
pow(exp): BoxedExpression
Power
####### exp
number
| BoxedExpression
root(exp): BoxedExpression
Exponentiation
####### exp
number
| BoxedExpression
sqrt(): BoxedExpression
Square root
ln(base?): BoxedExpression
Logarithm (natural by default)
####### base?
number
| BoxedExpression
Return this expression expressed as a numerator.
Return this expression expressed as a denominator.
Return this expression expressed as a numerator and denominator.
readonly isScoped: boolean;
If true, the expression has its own local scope that can be used for local variables and arguments. Only true if the expression is a function expression.
If this expression has a local scope, return it.
subs(sub, options?): BoxedExpression
Replace all the symbols in the expression as indicated.
Note the same effect can be achieved with this.replace()
, but
using this.subs()
is more efficient and simpler, but limited
to replacing symbols.
The result is bound to the current scope, not to this.scope
.
If options.canonical
is not set, the result is canonical if this
is canonical.
:::info[Note] Applicable to canonical and non-canonical expressions. :::
####### sub
Substitution
<SemiBoxedExpression
>
####### options?
####### canonical
map(fn, options?): BoxedExpression
Recursively replace all the subexpressions in the expression as indicated.
To remove a subexpression, return an empty ["Sequence"]
expression.
The canonical
option is applied to each function subexpression after
the substitution is applied.
If no options.canonical
is set, the result is canonical if this
is canonical.
Default: { canonical: this.isCanonical, recursive: true }
:::info[Note] Applicable to canonical and non-canonical expressions. :::
####### fn
(expr
) => BoxedExpression
####### options?
####### canonical
####### recursive
boolean
replace(rules, options?): BoxedExpression
Transform the expression by applying one or more replacement rules:
-
If the expression matches the
match
pattern and thecondition
predicate is true, replace it with thereplace
pattern. -
If no rules apply, return
null
.
See also expr.subs()
for a simple substitution of symbols.
If options.canonical
is not set, the result is canonical if this
is canonical.
:::info[Note] Applicable to canonical and non-canonical expressions. :::
####### rules
BoxedRuleSet
| Rule
| Rule
[]
####### options?
Partial
<ReplaceOptions
>
has(v): boolean
True if the expression includes a symbol v
or a function operator v
.
:::info[Note] Applicable to canonical and non-canonical expressions. :::
####### v
string
| string
[]
match(pattern, options?): BoxedSubstitution
If this expression matches pattern
, return a substitution that makes
pattern
equal to this
. Otherwise return null
.
If pattern
includes wildcards (symbols that start
with _
), the substitution will include a prop for each matching named
wildcard.
If this expression matches pattern
but there are no named wildcards,
return the empty substitution, {}
.
Read more about patterns and rules.
:::info[Note] Applicable to canonical and non-canonical expressions. :::
####### pattern
####### options?
readonly wikidata: string;
Wikidata identifier.
If not a canonical expression, return undefined
.
readonly description: string[];
An optional short description if a symbol or function expression.
May include markdown. Each string is a paragraph.
If not a canonical expression, return undefined
.
readonly url: string;
An optional URL pointing to more information about the symbol or function operator.
If not a canonical expression, return undefined
.
readonly complexity: number;
Expressions with a higher complexity score are sorted first in commutative functions
If not a canonical expression, return undefined
.
readonly baseDefinition: BoxedBaseDefinition;
For symbols and functions, a definition associated with the
expression. this.baseDefinition
is the base class of symbol and function
definition.
If not a canonical expression, return undefined
.
readonly operatorDefinition: BoxedOperatorDefinition;
For function expressions, the definition of the operator associated with
the expression. For symbols, the definition of the symbol if it is an
operator, for example "Sin"
.
If not a canonical expression or not a function expression,
its value is undefined
.
readonly valueDefinition: BoxedValueDefinition;
For symbols, a definition associated with the expression, if it is not an operator.
If not a canonical expression, or not a value, its value is undefined
.
simplify(options?): BoxedExpression
Return a simpler form of this expression.
A series of rewriting rules are applied repeatedly, until no more rules apply.
The values assigned to symbols and the assumptions about symbols may be
used, for example expr.isInteger
or expr.isPositive
.
No calculations involving decimal numbers (numbers that are not integers) are performed but exact calculations may be performed, for example:
$$ \sin(\frac{\pi}{4}) \longrightarrow \frac{\sqrt{2}}{2} $$.
The result is canonical.
To manipulate symbolically non-canonical expressions, use expr.replace()
.
####### options?
Partial
<SimplifyOptions
>
expand(): BoxedExpression
Expand the expression: distribute multiplications over additions, and expand powers.
evaluate(options?): BoxedExpression
Return the value of the canonical form of this expression.
A pure expression always returns the same value (provided that it remains constant / values of sub-expressions or symbols do not change), and has no side effects.
Evaluating an impure expression may return a varying value, and may have some side effects such as adjusting symbol assumptions.
To perform approximate calculations, use expr.N()
instead,
or call with options.numericApproximation
to true
.
It is possible that the result of expr.evaluate()
may be the same as
expr.simplify()
.
The result is in canonical form.
####### options?
Partial
<EvaluateOptions
>
evaluateAsync(options?): Promise<BoxedExpression>
Asynchronous version of evaluate()
.
The options
argument can include a signal
property, which is an
AbortSignal
object. If the signal is aborted, a CancellationError
is thrown.
####### options?
Partial
<EvaluateOptions
>
N(): BoxedExpression
Return a numeric approximation of the canonical form of this expression.
Any necessary calculations, including on decimal numbers (non-integers), are performed.
The calculations are performed according to the
precision
property of the ComputeEngine
.
To only perform exact calculations, use this.evaluate()
instead.
If the function is not numeric, the result of this.N()
is the same as
this.evaluate()
.
The result is in canonical form.
compile(options?): (args?) => CompiledType
Compile the expression to a JavaScript function.
The function takes an object as argument, with the keys being the symbols in the expression, and returns the value of the expression.
const expr = ce.parse("x^2 + y^2");
const f = expr.compile();
console.log(f({x: 2, y: 3}));
####### options?
####### to
"javascript"
####### functions
Record
<string
, string
| (...any
) => any
>
####### vars
Record
<string
, CompiledType
>
####### imports
(...any
) => any
[]
####### preamble
string
solve(vars?): readonly BoxedExpression[]
If this is an equation, solve the equation for the variables in vars.
Otherwise, solve the equation this = 0
for the variables in vars.
const expr = ce.parse("x^2 + 2*x + 1 = 0");
console.log(expr.solve("x"));
####### vars?
string
| Iterable
<string
> | BoxedExpression
| Iterable
<BoxedExpression
>
get value(): BoxedExpression
set value(value:
| string
| number
| boolean
| number[]
| Decimal
| OnlyFirst<{
re: number;
im: number;
}, {
re: number;
im: number;
} & {
num: number;
denom: number;
} & BoxedExpression>
| OnlyFirst<{
num: number;
denom: number;
}, {
re: number;
im: number;
} & {
num: number;
denom: number;
} & BoxedExpression>
| OnlyFirst<BoxedExpression, {
re: number;
im: number;
} & {
num: number;
denom: number;
} & BoxedExpression>): void
If this expression is a number literal, a string literal or a function literal, return the expression.
If the expression is a symbol, return the value of the symbol.
Otherwise, the expression is a symbolic expression, including an unknown
symbol, i.e. a symbol with no value, return undefined
.
If the expression is a symbol, set the value of the symbol.
Will throw a runtime error if either not a symbol, or a symbol with the
constant
flag set to true
.
Setting the value of a symbol results in the forgetting of all assumptions about it in the current scope.
isCollection: boolean;
Return true if the expression is a collection: a list, a vector, a matrix, a map, a tuple, etc...
contains(rhs): boolean
If this is a collection, return true if the rhs
expression is in the
collection.
Return undefined
if the membership cannot be determined.
####### rhs
If this is a collection, return the number of elements in the collection.
If the collection is infinite, return Infinity
.
each: (start?, count?) => Iterator<BoxedExpression, undefined>;
If this is a collection, return an iterator over the elements of the collection.
If start
is not specified, start from the first element.
If count
is not specified or negative, return all the elements from start
to the end.
const expr = ce.parse('[1, 2, 3, 4]');
for (const e of expr.each()) {
console.log(e);
}
at(index): BoxedExpression
If this is an indexable collection, return the element at the specified index.
If the index is negative, return the element at index size() + index + 1
.
####### index
number
get(key): BoxedExpression
If this is a map or a tuple, return the value of the corresponding key.
If key
is a BoxedExpression
, it should be a string.
####### key
string
| BoxedExpression
indexOf(expr): number
If this is an indexable collection, return the index of the first element that matches the target expression.
####### expr
valueOf(): string | number | boolean | number[] | number[][] | number[][][]
Return a JavaScript primitive value for the expression, based on
Object.valueOf()
.
This method is intended to make it easier to work with JavaScript primitives, for example when mixing JavaScript computations with symbolic computations from the Compute Engine.
If the expression is a machine number, a bignum, or a rational
that can be converted to a machine number, return a JavaScript number
.
This conversion may result in a loss of precision.
If the expression is the symbol "True"
or the symbol "False"
,
return true
or false
, respectively.
If the expression is a symbol with a numeric value, return the numeric value of the symbol.
If the expression is a string literal, return the string value.
If the expression is a tensor (list of number or multidimensional array or matrix), return an array of numbers, or an array of arrays of numbers, or an array of arrays of arrays of numbers.
If the expression is a function expression return a string representation of the expression.
toPrimitive: string | number
Similar toexpr.valueOf()
but includes a hint.
####### hint
"string"
| "number"
| "default"
toString(): string
Return an ASCIIMath representation of the expression. This string is suitable to be output to the console for debugging, for example.
Based on Object.toString()
.
To get a LaTeX representation of the expression, use expr.latex
.
Used when coercing a BoxedExpression
to a String
.
toJSON(): Expression
Used by JSON.stringify()
to serialize this object to JSON.
Method version of expr.json
.
Based on Object.toJSON()
.
is(other): boolean
Equivalent to BoxedExpression.isSame()
but the argument can be
a JavaScript primitive. For example, expr.is(2)
is equivalent to
expr.isSame(ce.number(2))
.
####### other
string
| number
| bigint
| boolean
| BoxedExpression
isSame(rhs): boolean
Structural/symbolic equality (weak equality).
ce.parse('1+x', {canonical: false}).isSame(ce.parse('x+1', {canonical: false}))
is false
.
See expr.isEqual()
for mathematical equality.
:::info[Note] Applicable to canonical and non-canonical expressions. :::
####### rhs
isLess(other): boolean
The value of both expressions are compared.
If the expressions cannot be compared, return undefined
####### other
number
| BoxedExpression
isLessEqual(other): boolean
The value of both expressions are compared.
If the expressions cannot be compared, return undefined
####### other
number
| BoxedExpression
isGreater(other): boolean
The value of both expressions are compared.
If the expressions cannot be compared, return undefined
####### other
number
| BoxedExpression
isGreaterEqual(other): boolean
The value of both expressions are compared.
If the expressions cannot be compared, return undefined
####### other
number
| BoxedExpression
isEqual(other): boolean
Mathematical equality (strong equality), that is the value
of this expression and the value of other
are numerically equal.
Both expressions are evaluated and the result is compared numerically.
Numbers whose difference is less than engine.tolerance
are
considered equal. This tolerance is set when the engine.precision
is
changed to be such that the last two digits are ignored.
Evaluating the expressions may be expensive. Other options to consider to compare two expressions include:
expr.isSame(other)
for a structural comparison which does not involve evaluating the expressions.expr.is(other)
for a comparison of a number literal
Examples
let expr = ce.parse('2 + 2');
console.log(expr.isEqual(4)); // true
console.log(expr.isSame(ce.parse(4))); // false
console.log(expr.is(4)); // false
expr = ce.parse('4');
console.log(expr.isEqual(4)); // true
console.log(expr.isSame(ce.parse(4))); // true
console.log(expr.is(4)); // true (fastest)
####### other
number
| BoxedExpression
readonly string: string;
If this expression is a string, return the value of the string.
Otherwise, return null
.
:::info[Note] Applicable to canonical and non-canonical expressions. :::
readonly symbol: string;
If this expression is a symbol, return the name of the symbol as a string.
Otherwise, return null
.
:::info[Note] Applicable to canonical and non-canonical expressions. :::
readonly tensor: Tensor<any>;
If this expression is a tensor, return the tensor data.
Otherwise, return null
.
:::info[Note] Applicable to canonical and non-canonical expressions. :::
readonly shape: number[];
The shape describes the axes of the expression, where each axis represent a way to index the elements of the expression.
When the expression is a scalar (number), the shape is []
.
When the expression is a vector of length n
, the shape is [n]
.
When the expression is a n
by m
matrix, the shape is [n, m]
.
readonly rank: number;
The rank refers to the number of dimensions (or axes) of the expression.
Return 0 for a scalar, 1 for a vector, 2 for a matrix, > 2 for a multidimensional matrix.
The rank is equivalent to the length of expr.shape
:::info[Note] There are several definitions of rank in the literature. For example, the row rank of a matrix is the number of linearly independent rows. The rank can also refer to the number of non-zero singular values of a matrix. :::
get type(): BoxedType
set type(type:
| string
| AlgebraicType
| NegationType
| CollectionType
| ListType
| SetType
| MapType
| TupleType
| FunctionSignature
| ValueType
| TypeReference
| BoxedType): void
The type of the value of this expression.
If a symbol the type of the value of the symbol.
If a function expression, the type of the value of the function (the result type).
If a symbol with a "function"
type (a function literal), returns the
signature.
If not valid, return "error"
.
If the type is not known, return "unknown"
.
readonly isNumber: boolean;
true
if the value of this expression is a number.
Note that in a fateful twist of cosmic irony, NaN
("Not a Number")
is a number.
If isNumber
is true
, this indicates that evaluating the expression
will return a number.
This does not indicate that the expression is a number literal. To check
if the expression is a number literal, use expr.isNumberLiteral
.
For example, the expression ["Add", 1, "x"]
is a number if "x" is a
number and expr.isNumber
is true
, but isNumberLiteral
is false
.
readonly isInteger: boolean;
The value of this expression is an element of the set ℤ: ...,-2, -1, 0, 1, 2...
Note that ±∞ and NaN are not integers.
readonly isRational: boolean;
The value of this expression is an element of the set ℚ, p/q with p ∈ ℕ, q ∈ ℤ ⃰ q >= 1
Note that every integer is also a rational.
This is equivalent to this.type === "rational" || this.type === "integer"
Note that ±∞ and NaN are not rationals.
readonly isReal: boolean;
The value of this expression is a real number.
This is equivalent to this.type === "rational" || this.type === "integer" || this.type === "real"
Note that ±∞ and NaN are not real numbers.
type SemiBoxedExpression =
| number
| bigint
| string
| BigNum
| MathJsonNumberObject
| MathJsonStringObject
| MathJsonSymbolObject
| MathJsonFunctionObject
| readonly [MathJsonSymbol, ...SemiBoxedExpression[]]
| BoxedExpression;
A semi boxed expression is a MathJSON expression which can include some boxed terms.
This is convenient when creating new expressions from portions
of an existing BoxedExpression
while avoiding unboxing and reboxing.
type ReplaceOptions = {
recursive: boolean;
once: boolean;
useVariations: boolean;
iterationLimit: number;
canonical: CanonicalOptions;
};
recursive: boolean;
If true
, apply replacement rules to all sub-expressions.
If false
, only consider the top-level expression.
Default: false
once: boolean;
If true
, stop after the first rule that matches.
If false
, apply all the remaining rules even after the first match.
Default: false
useVariations: boolean;
If true
the rule will use some equivalent variations to match.
For example when useVariations
is true:
x
matchesa + x
with a = 0x
matchesax
with a = 1- etc...
Setting this to true
can save time by condensing multiple rules
into one. This can be particularly useful when describing equations
solutions. However, it can lead to infinite recursion and should be
used with caution.
iterationLimit: number;
If iterationLimit
> 1, the rules will be repeatedly applied
until no rules apply, up to maxIterations
times.
Note that if once
is true, iterationLimit
has no effect.
Default: 1
canonical: CanonicalOptions;
Indicate if the expression should be canonicalized after the replacement. If not provided, the expression is canonicalized if the expression that matched the pattern is canonical.
type SimplifyOptions = {
rules: | null
| Rule
| ReadonlyArray<BoxedRule | Rule>
| BoxedRuleSet;
costFunction: (expr) => number;
};
Options for BoxedExpression.simplify()
optional rules:
| null
| Rule
| ReadonlyArray<BoxedRule | Rule>
| BoxedRuleSet;
The set of rules to apply. If null
, use no rules. If not provided,
use the default simplification rules.
optional costFunction: (expr) => number;
Use this cost function to determine if a simplification is worth it.
If not provided, ce.costFunction
, the cost function of the engine is
used.
type CanonicalForm =
| "InvisibleOperator"
| "Number"
| "Multiply"
| "Add"
| "Power"
| "Divide"
| "Flatten"
| "Order";
When provided, canonical forms are used to put an expression in a "standard" form.
Each canonical form applies some transformation to an expression. When specified as an array, each transformation is done in the order in which it was provided.
InvisibleOperator
: replace use of theInvisibleOperator
with another operation, such as multiplication (i.e.2x
or function application (f(x)
). Also replaces ['InvisibleOperator', real, imaginary] instances with complex (imaginary) numbers.Number
: replace all numeric values with their canonical representation, for example, reduce rationals and replace complex numbers with no imaginary part with a real number.Multiply
: replace negation with multiplication by -1, remove 1 from multiplications, simplify signs (-y \times -x
->x \times y
), complex numbers are promoted (['Multiply', 2, 'ImaginaryUnit'] ->["Complex", 0, 2]
)Add
: replaceSubtract
withAdd
, removes 0 in addition, promote complex numbers (["Add", "a", ["Complex", 0, "b"] ->["Complex", "a", "b"]
)Power
: simplifyPower
expression, for example,x^{-1}
->\frac{1}{x}
,x^0
->1
,x^1
->x
,1^x
->1
,x^{\frac{1}{2}}
->\sqrt{x}
,a^b^c
->a^{bc}
...Divide
: replace with aRational
number if numerator and denominator are integers, simplify, e.g.\frac{x}{1}
->x
...Flatten
: remove any unnecessaryDelimiter
expression, and flatten any associative functions, for example["Add", ["Add", "a", "b"], "c"]
->["Add", "a", "b", "c"]
Order
: when applicable, sort the arguments in a specific order, for example for addition and multiplication.
type CanonicalOptions =
| boolean
| CanonicalForm
| CanonicalForm[];
type EvaluateOptions = {
numericApproximation: boolean;
signal: AbortSignal;
withArguments: Record<MathJsonSymbol, BoxedExpression>;
};
Options for BoxedExpression.evaluate()
type Metadata = {
latex: string;
wikidata: string;
};
Metadata that can be associated with an MathJSON expression.
type PatternMatchOptions = {
substitution: BoxedSubstitution;
recursive: boolean;
useVariations: boolean;
};
Control how a pattern is matched to an expression.
-
substitution
: if present, assumes these values for the named wildcards, and ensure that subsequent occurrence of the same wildcard have the same value. -
recursive
: if true, match recursively, otherwise match only the top level. -
useVariations
: if false, only match expressions that are structurally identical. If true, match expressions that are structurally identical or equivalent.For example, when true,
["Add", '_a', 2]
matches2
, with a value of_a
of0
. If false, the expression does not match. Default:false
type Substitution<T> = {};
A substitution describes the values of the wildcards in a pattern so that the pattern is equal to a target expression.
A substitution can also be considered a more constrained version of a
rule whose match
is always a symbol.
• T = SemiBoxedExpression
type BoxedSubstitution = Substitution<BoxedExpression>;
type RuleReplaceFunction = (expr, wildcards) => BoxedExpression | undefined;
Given an expression and set of wildcards, return a new expression.
For example:
{
match: '_x',
replace: (expr, {_x}) => { return ['Add', 1, _x] }
}
type RuleConditionFunction = (wildcards, ce) => boolean;
type RuleFunction = (expr) =>
| undefined
| BoxedExpression
| RuleStep;
type RuleStep = {
value: BoxedExpression;
because: string;
};
type RuleSteps = RuleStep[];
type Rule =
| string
| RuleFunction
| {
match: | LatexString
| SemiBoxedExpression
| BoxedExpression;
replace: | LatexString
| SemiBoxedExpression
| RuleReplaceFunction
| RuleFunction;
condition: | LatexString
| RuleConditionFunction;
useVariations: boolean;
id: string;
onBeforeMatch: (rule, expr) => void;
onMatch: (rule, expr, replace) => void;
};
A rule describes how to modify an expressions that matches a pattern match
into a new expression replace
.
x-1
( \to )1-x
(x+1)(x-1)
( \to ) `x^2-1
The patterns can be expressed as LaTeX strings or a MathJSON expressions.
As a shortcut, a rule can be defined as a LaTeX string: x-1 -> 1-x
.
The expression to the left of ->
is the match
and the expression to the
right is the replace
. When using LaTeX strings, single character variables
are assumed to be wildcards.
When using MathJSON expressions, anonymous wildcards (_
) will match any
expression. Named wildcards (_x
, _a
, etc...) will match any expression
and bind the expression to the wildcard name.
In addition the sequence wildcard (__1
, __a
, etc...) will match
a sequence of one or more expressions, and bind the sequence to the
wildcard name.
Sequence wildcards are useful when the number of elements in the sequence
is not known in advance. For example, in a sum, the number of terms is
not known in advance. ["Add", 0, __a
] will match two or more terms and
the __a
wildcard will be a sequence of the matchign terms.
If exact
is false, the rule will match variants.
For example 'x' will match 'a + x', 'x' will match 'ax', etc...
For simplification rules, you generally want exact
to be true, but
to solve equations, you want it to be false. Default to true.
When set to false, infinite recursion is possible.
type BoxedRule = {
match: undefined | BoxedExpression;
replace: | BoxedExpression
| RuleReplaceFunction
| RuleFunction;
condition: undefined | RuleConditionFunction;
useVariations: boolean;
id: string;
onBeforeMatch: (rule, expr) => void;
onMatch: (rule, expr, replace) => void;
};
If the match
property is undefined
, all expressions match this rule
and condition
should also be undefined
. The replace
property should
be a BoxedExpression
or a RuleFunction
, and further filtering can be
done in the replace
function.
type BoxedRuleSet = {
rules: ReadonlyArray<BoxedRule>;
};
To create a BoxedRuleSet use the ce.rules()
method.
Do not create a BoxedRuleSet
directly.
isPositive: boolean;
isNonNegative: boolean;
isNegative: boolean;
isNonPositive: boolean;
isNumber: boolean;
isInteger: boolean;
isRational: boolean;
isReal: boolean;
isComplex: boolean;
isImaginary: boolean;
isFinite: boolean;
isInfinite: boolean;
isNaN: boolean;
isZero: boolean;
matches(t): boolean
####### t
string
| BoxedType
isGreater(other): boolean
####### other
isGreaterEqual(other): boolean
####### other
isLess(other): boolean
####### other
isLessEqual(other): boolean
####### other
isEqual(other): boolean
####### other
toExpression(ce, x): BoxedExpression
####### ce
ComputeEngine
####### x
string
has(expr): boolean
####### expr
get(expr): U
####### expr
set(expr, value): void
####### expr
####### value
U
delete(expr): void
####### expr
clear(): void
iterator: IterableIterator<[BoxedExpression, U]>
entries(): IterableIterator<[BoxedExpression, U]>
type AssumeResult =
| "internal-error"
| "not-a-predicate"
| "contradiction"
| "tautology"
| "ok";
type CompiledType = boolean | number | string | object;
type JSSource = string;
type CompiledExpression = {
evaluate: (scope) => number | BoxedExpression;
};
These handlers compare two expressions.
If only one of the handlers is provided, the other is derived from it.
Having both may be useful if comparing non-equality is faster than equality.
eq: (a, b) => boolean;
neq: (a, b) => boolean;
type Hold = "none" | "all" | "first" | "rest" | "last" | "most";
type ValueDefinition = BaseDefinition & {
holdUntil: "never" | "evaluate" | "N";
type: | Type
| TypeString
| BoxedType;
inferred: boolean;
value: | LatexString
| SemiBoxedExpression
| (ce) => BoxedExpression | null;
eq: (a) => boolean | undefined;
neq: (a) => boolean | undefined;
cmp: (a) => "=" | ">" | "<" | undefined;
collection: Partial<CollectionHandlers>;
};
A bound symbol (i.e. one with an associated definition) has either a type (e.g. ∀ x ∈ ℝ), a value (x = 5) or both (π: value = 3.14... type = 'real').
inferred: boolean;
If true, the type is inferred, and could be adjusted later as more information becomes available or if the symbol is explicitly declared.
value:
| LatexString
| SemiBoxedExpression
| (ce) => BoxedExpression | null;
value
can be a JS function since for some constants, such as
Pi
, the actual value depends on the precision
setting of the
ComputeEngine
and possible other environment settings
type OperatorDefinition = Partial<BaseDefinition> & Partial<OperatorDefinitionFlags> & {
signature: | Type
| TypeString
| BoxedType;
type: (ops, options) =>
| Type
| TypeString
| BoxedType
| undefined;
sgn: (ops, options) => Sign | undefined;
isPositive: boolean;
isNonNegative: boolean;
isNegative: boolean;
isNonPositive: boolean;
even: (ops, options) => boolean | undefined;
complexity: number;
canonical: (ops, options) => BoxedExpression | null;
evaluate: | (ops, options) => BoxedExpression | undefined
| BoxedExpression;
evaluateAsync: (ops, options) => Promise<BoxedExpression | undefined>;
evalDimension: (args, options) => BoxedExpression;
compile: (expr) => CompiledExpression;
eq: (a, b) => boolean | undefined;
neq: (a, b) => boolean | undefined;
collection: Partial<CollectionHandlers>;
};
Definition record for a function.
optional signature:
| Type
| TypeString
| BoxedType;
The function signature, describing the type of the arguments and the return type.
If a type
handler is provided, the return type of the function should
be a subtype of the return type in the signature.
optional type: (ops, options) =>
| Type
| TypeString
| BoxedType
| undefined;
The type of the result (return type) based on the type of the arguments.
Should be a subtype of the type indicated by the signature.
For example, if the signature is (number) -> real
, the type of the
result could be real
or integer
, but not complex
.
:::info[Note] Do not evaluate the arguments.
However, the type of the arguments can be used to determine the type of the result. :::
optional sgn: (ops, options) => Sign | undefined;
Return the sign of the function expression.
If the sign cannot be determined, return undefined
.
When determining the sign, only literal values and the values of symbols, if they are literals, should be considered.
Do not evaluate the arguments.
However, the type and sign of the arguments can be used to determine the sign.
readonly optional isPositive: boolean;
The value of this expression is > 0, same as isGreater(0)
readonly optional isNonNegative: boolean;
The value of this expression is >= 0, same as isGreaterEqual(0)
readonly optional isNegative: boolean;
The value of this expression is < 0, same as isLess(0)
readonly optional isNonPositive: boolean;
The value of this expression is <= 0, same as isLessEqual(0)
optional even: (ops, options) => boolean | undefined;
Return true
if the function expression is even, false
if it is odd
and undefined
if it is neither (for example if it is not a number,
or if it is a complex number).
optional complexity: number;
A number used to order arguments.
Argument with higher complexity are placed after arguments with lower complexity when ordered canonically in commutative functions.
- Additive functions: 1000-1999
- Multiplicative functions: 2000-2999
- Root and power functions: 3000-3999
- Log functions: 4000-4999
- Trigonometric functions: 5000-5999
- Hypertrigonometric functions: 6000-6999
- Special functions (factorial, Gamma, ...): 7000-7999
- Collections: 8000-8999
- Inert and styling: 9000-9999
- Logic: 10000-10999
- Relational: 11000-11999
Default: 100,000
optional canonical: (ops, options) => BoxedExpression | null;
Return the canonical form of the expression with the arguments args
.
The arguments (args
) may not be in canonical form. If necessary, they
can be put in canonical form.
This handler should validate the type and number of the arguments (arity).
If a required argument is missing, it should be indicated with a
["Error", "'missing"]
expression. If more arguments than expected
are present, this should be indicated with an
["Error", "'unexpected-argument'"]
error expression
If the type of an argument is not compatible, it should be indicated
with an incompatible-type
error.
["Sequence"]
expressions are not folded and need to be handled
explicitly.
If the function is associative, idempotent or an involution, this handler should account for it. Notably, if it is commutative, the arguments should be sorted in canonical order.
Values of symbols should not be substituted, unless they have
a holdUntil
attribute of "never"
.
The handler should not consider the value or any assumptions about any
of the arguments that are symbols or functions (i.e. arg.isZero
,
arg.isInteger
, etc...) since those may change over time.
The result of the handler should be a canonical expression.
If the arguments do not match, they should be replaced with an
appropriate ["Error"]
expression. If the expression cannot be put in
canonical form, the handler should return null
.
optional evaluate:
| (ops, options) => BoxedExpression | undefined
| BoxedExpression;
Evaluate a function expression.
When the handler is invoked, the arguments have been evaluated, except
if the lazy
option is set to true
.
It is not necessary to further simplify or evaluate the arguments.
If performing numerical calculations and options.numericalApproximation
is false
return an exact numeric value, for example return a rational
number or a square root, rather than a floating point approximation.
Use ce.number()
to create the numeric value.
If the expression cannot be evaluated, due to the values, types, or
assumptions about its arguments, return undefined
or
an ["Error"]
expression.
optional evaluateAsync: (ops, options) => Promise<BoxedExpression | undefined>;
An asynchronous version of evaluate
.
optional evalDimension: (args, options) => BoxedExpression;
Experimental
Dimensional analysis
optional compile: (expr) => CompiledExpression;
Return a compiled (optimized) expression.
Metadata common to both symbols and functions.
description: string | string[];
If a string, a short description, about one line long.
Otherwise, a list of strings, each string a paragraph.
May contain Markdown.
url: string;
A URL pointing to more information about this symbol or operator.
wikidata: string;
A short string representing an entry in a wikibase.
For example "Q167"
is the wikidata entry
for the Pi
constant.
readonly optional isConstant: boolean;
If true, the value or type of the definition cannot be changed
type SymbolDefinition = OneOf<[ValueDefinition, OperatorDefinition]>;
A table mapping symbols to their definition.
Symbols should be valid MathJSON symbols. In addition, the following rules are recommended:
- Use only latin letters, digits and
-
:/[a-zA-Z0-9-]+/
- The first character should be a letter:
/^[a-zA-Z]/
- Functions and symbols exported from a library should start with an uppercase letter
/^[A-Z]/
type SymbolDefinitions = Readonly<{}>;
type CollectionHandlers = {
size: (collection) => number;
contains: (collection, target) => boolean;
iterator: (collection, start?, count?) => Iterator<BoxedExpression, undefined>;
at: (collection, index) => undefined | BoxedExpression;
keys: (collection) => undefined | Iterable<string>;
indexOf: (collection, target, from?) => number | undefined;
subsetOf: (collection, target, strict) => boolean;
eltsgn: (collection) => Sign | undefined;
elttype: (collection) => Type | undefined;
};
These handlers are the primitive operations that can be performed on collections.
There are two types of collections:
-
finite collections, such as lists, tuples, sets, matrices, etc... The
size()
handler of finite collections returns the number of elements -
infinite collections, such as sequences, ranges, etc... The
size()
handler of infinite collections returnsInfinity
Infinite collections are not indexable: they have noat()
handler.
iterator: (collection, start?, count?) => Iterator<BoxedExpression, undefined>;
Return an iterator
- start is optional and is a 1-based index.
- if start is not specified, start from index 1
- count is optional and is the number of elements to return
- if count is not specified or negative, return all the elements from start to the end
If there is a keys()
handler, there is no iterator()
handler.
size: (collection) => number;
Return the number of elements in the collection.
An empty collection has a size of 0.
contains: (collection, target) => boolean;
Return true
if the target
expression is in the collection, false
otherwise.
at: (collection, index) => undefined | BoxedExpression;
Return the element at the specified index.
The first element is at(1)
, the last element is at(-1)
.
If the index is <0, return the element at index size() + index + 1
.
The index can also be a string for example for maps. The set of valid keys
is returned by the keys()
handler.
If the index is invalid, return undefined
.
keys: (collection) => undefined | Iterable<string>;
If the collection can be indexed by strings, return the valid values for the index.
indexOf: (collection, target, from?) => number | undefined;
Return the index of the first element that matches the target expression.
The comparison is done using the target.isEqual()
method.
If the expression is not found, return undefined
.
If the expression is found, return the index, 1-based.
Return the index of the first match.
from
is the starting index for the search. If negative, start from
the end and search backwards.
subsetOf: (collection, target, strict) => boolean;
Return true
if all theelements of target
are in expr
.
Both expr
and target
are collections.
If strict is true
, the subset must be strict, that is, expr
must
have more elements than target
.
eltsgn: (collection) => Sign | undefined;
Return the sign of all the elements of the collection.
elttype: (collection) => Type | undefined;
Return the widest type of all the elements in the collection
type BoxedDefinition =
| TaggedValueDefinition
| TaggedOperatorDefinition;
A boxed definition can be either a value or an operator.
It is collected in a tagged object literal, instead of being a simple union type, so that the type of the definition can be changed while keeping references to the definition in bound expressions.
Partial
<BaseDefinition
>
optional collection: Partial<CollectionHandlers>;
If this is the definition of a collection, the set of primitive operations that can be performed on this collection (counting the number of elements, enumerating it, etc...).
holdUntil: "never" | "evaluate" | "N";
If the symbol has a value, it is held as indicated in the table below. A green checkmark indicate that the symbol is substituted.
Operation | "never" |
"evaluate" |
"N" |
---|---|---|---|
canonical() |
(X) | ||
evaluate() |
(X) | (X) | |
"N()" |
(X) | (X) | (X) |
Some examples:
ImaginaryUnit
hasholdUntil: 'never'
: it is substituted during canonicalizationx
hasholdUntil: 'evaluate'
(variables)Pi
hasholdUntil: 'N'
(special numeric constant)
Default: evaluate
readonly value: BoxedExpression;
This is either the initial value of the symbol (i.e. when a new evaluation context is created), or its constant value, if a constant. Otherwise, the current value is tracked in the evaluation context.
optional eq: (a) => boolean;
optional neq: (a) => boolean;
optional cmp: (a) => ">" | "<" | "=";
inferredType: boolean;
True if the type has been inferred. An inferred type can be updated as more information becomes available.
A type that is not inferred, but has been set explicitly, cannot be updated.
type: BoxedType;
type OperatorDefinitionFlags = {
lazy: boolean;
scoped: boolean;
broadcastable: boolean;
associative: boolean;
commutative: boolean;
commutativeOrder: (a, b) => number | undefined;
idempotent: boolean;
involution: boolean;
pure: boolean;
};
An operator definition can have some flags to indicate specific properties of the operator.
lazy: boolean;
If true
, the arguments to this operator are not automatically
evaluated. The default is false
(the arguments are evaluated).
This can be useful for example for operators that take symbolic
expressions as arguments, such as Declare
or Integrate
.
This is also useful for operators that take an argument that is potentially an infinite collection.
It will be up to the evaluate()
handler to evaluate the arguments as
needed. This is convenient to pass symbolic expressions as arguments
to operators without having to explicitly use a Hold
expression.
This also applies to the canonical()
handler.
scoped: boolean;
If true
, the operator requires a new lexical scope when canonicalized.
This will allow it to declare variables that are not visible outside
the function expression using the operator.
Default: false
broadcastable: boolean;
If true
, the operator is applied element by element to lists, matrices
(["List"]
or ["Tuple"]
expressions) and equations (relational
operators).
Default: false
associative: boolean;
If true
, ["f", ["f", a], b]
simplifies to ["f", a, b]
Default: false
commutative: boolean;
If true
, ["f", a, b]
equals ["f", b, a]
. The canonical
version of the function will order the arguments.
Default: false
commutativeOrder: (a, b) => number | undefined;
If commutative
is true
, the order of the arguments is determined by
this function.
If the function is not provided, the arguments are ordered by the default order of the arguments.
idempotent: boolean;
If true
, ["f", ["f", x]]
simplifies to ["f", x]
.
Default: false
involution: boolean;
If true
, ["f", ["f", x]]
simplifies to x
.
Default: false
pure: boolean;
If true
, the value of this operator is always the same for a given
set of arguments and it has no side effects.
An expression using this operator is pure if the operator and all its arguments are pure.
For example Sin
is pure, Random
isn't.
This information may be used to cache the value of expressions.
Default: true
The definition includes information specific about an operator, such as handlers to canonicalize or evaluate a function expression with this operator.
complexity: number;
inferredSignature: boolean;
If true, the signature was inferred from usage and may be modified as more information becomes available.
signature: BoxedType;
The type of the arguments and return value of this function
optional type: (ops, options) =>
| string
| AlgebraicType
| NegationType
| CollectionType
| ListType
| SetType
| MapType
| TupleType
| FunctionSignature
| ValueType
| TypeReference
| BoxedType;
If present, this handler can be used to more precisely determine the return type based on the type of the arguments. The arguments themselves should not be evaluated, only their types should be used.
optional sgn: (ops, options) => Sign;
If present, this handler can be used to determine the sign of the return value of the function, based on the sign and type of its arguments.
The arguments themselves should not be evaluated, only their types and sign should be used.
This can be used in some case for example to determine when certain simplifications are valid.
optional eq: (a, b) => boolean;
optional neq: (a, b) => boolean;
optional canonical: (ops, options) => BoxedExpression;
optional evaluate: (ops, options) => BoxedExpression;
optional evaluateAsync: (ops, options?) => Promise<BoxedExpression>;
optional evalDimension: (ops, options) => BoxedExpression;
optional compile: (expr) => CompiledExpression;
type Scope = {
parent: Scope | null;
bindings: Map<string, BoxedDefinition>;
};
A lexical scope is a table mapping symbols to their definitions. The symbols are the names of the variables, unknowns and functions in the scope.
The lexical scope is used to resolve the metadata about symbols, such as their type, whether they are constant, etc...
It does not resolve the values of the symbols, since those depend on the evaluation context. For example, the local variables of a recursive function will have the same lexical scope, but different values in each evaluation context.
type LatexToken = string | "<{>" | "<}>" | "<space>" | "<$>" | "<$$>";
A LatexToken
is a token as returned by Parser.peek
.
It can be one of the indicated tokens, or a string that starts with a `` for LaTeX commands, or a LaTeX character which includes digits, letters and punctuation.
type LatexString = string;
A LatexString is a regular string of LaTeX, for example:
\frac{\pi}{2}
type Delimiter =
| ")"
| "("
| "]"
| "["
| "{"
| "}"
| "<"
| ">"
| "|"
| "||"
| "\lceil"
| "\rceil"
| "\lfloor"
| "\rfloor"
| "\llbracket"
| "\rrbracket";
Open and close delimiters that can be used with MatchfixEntry
record to define new LaTeX dictionary entries.
type DelimiterScale = "normal" | "scaled" | "big" | "none";
type LibraryCategory =
| "algebra"
| "arithmetic"
| "calculus"
| "collections"
| "control-structures"
| "combinatorics"
| "complex"
| "core"
| "data-structures"
| "dimensions"
| "domains"
| "linear-algebra"
| "logic"
| "numeric"
| "other"
| "physics"
| "polynomials"
| "relop"
| "sets"
| "statistics"
| "styling"
| "symbols"
| "trigonometry"
| "units";
type Precedence = number;
:::info[THEORY OF OPERATIONS]
The precedence of an operator is a number that indicates the order in which operators are applied.
For example, in 1 + 2 * 3
, the *
operator has a higher precedence
than the +
operator, so it is applied first.
The precedence range from 0 to 1000. The larger the number, the higher the precedence, the more "binding" the operator is.
Here are some rough ranges for the precedence:
- 800: prefix and postfix operators:
\lnot
etc...POSTFIX_PRECEDENCE
= 810:!
,'
- 700: some arithmetic operators
EXPONENTIATION_PRECEDENCE
= 700:^
- 600: some binary operators
DIVISION_PRECEDENCE
= 600:\div
- 500: not used
- 400: not used
- 300: some logic and arithmetic operators:
\land
,\lor
,\times
, etc...MULTIPLICATION_PRECEDENCE
= 390:\times
- 200: arithmetic operators, inequalities:
ADDITION_PRECEDENCE
= 275:+
-
ARROW_PRECEDENCE
= 270:\to
\rightarrow
ASSIGNMENT_PRECEDENCE
= 260::=
COMPARISON_PRECEDENCE
= 245:\lt
\gt
- 241:
\leq
- 100: not used
- 0:
,
,;
, etc...
Some constants are defined below for common precedence values.
Note: MathML defines some operator precedence, but it has some issues and inconsistencies. However, whenever possible we adopted the MathML precedence.
The JavaScript operator precedence is documented here.
:::
type Terminator = {
minPrec: Precedence;
condition: (parser) => boolean;
};
This indicates a condition under which parsing should stop:
- an operator of a precedence higher than specified has been encountered
- the last token has been reached
- or if a condition is provided, the condition returns true
type ParseHandler =
| ExpressionParseHandler
| SymbolParseHandler
| FunctionParseHandler
| EnvironmentParseHandler
| PostfixParseHandler
| InfixParseHandler
| MatchfixParseHandler;
Custom parsing handler.
When this handler is invoked the parser points right after the LaTeX fragment that triggered it.
Tokens can be consumed with parser.nextToken()
and other parser methods
such as parser.parseGroup()
, parser.parseOptionalGroup()
, etc...
If it was in an infix or postfix context, lhs
will represent the
left-hand side argument. In a prefix or matchfix context, lhs
is null
.
In a superfix (^
) or subfix (_
) context (that is if the first token of
the trigger is ^
or _
), lhs
is ["Superscript", lhs, rhs]
and ["Subscript", lhs, rhs]
, respectively.
The handler should return null
if the tokens could not be parsed
(didn't match the syntax that was expected), or the matching expression
otherwise.
If the tokens were parsed but should be ignored, the handler should
return Nothing
.
type ExpressionParseHandler = (parser, until?) => Expression | null;
type PrefixParseHandler = (parser, until?) => Expression | null;
type SymbolParseHandler = (parser, until?) => Expression | null;
type FunctionParseHandler = (parser, until?) => Expression | null;
type EnvironmentParseHandler = (parser, until?) => Expression | null;
type PostfixParseHandler = (parser, lhs, until?) => Expression | null;
type InfixParseHandler = (parser, lhs, until) => Expression | null;
type MatchfixParseHandler = (parser, body) => Expression | null;
type LatexArgumentType =
| "{expression}"
| "[expression]"
| "{text}"
| "[text]"
| "{unit}"
| "[unit]"
| "{glue}"
| "[glue]"
| "{string}"
| "[string]"
| "{color}"
| "[color]";
type Trigger = {
latexTrigger: | LatexString
| LatexToken[];
symbolTrigger: MathJsonSymbol;
};
A trigger is the set of tokens that will make an entry in the
LaTeX dictionary eligible to parse the stream and generate an expression.
If the trigger matches, the parse
handler is called, if available.
The trigger can be specified either as a LaTeX string (latexTrigger
) or
as an symbol (symbolTrigger
). A symbol match several
LaTeX expressions that are equivalent, for example \operatorname{gcd}
or
\mathbin{gcd}
, match the "gcd"
symbol
matchfix
operators use openTrigger
and closeTrigger
instead.
type BaseEntry = {
name: MathJsonSymbol;
serialize: | LatexString
| SerializeHandler;
};
Maps a string of LaTeX tokens to a function or symbol and vice-versa.
optional name: MathJsonSymbol;
Map a MathJSON symbol to this entry.
Each entry should have at least a name
or a parse
handler.
An entry with no name
cannot be serialized: the name
is used to map
a MathJSON function or symbol name to the appropriate entry for
serializing.
However, an entry with no name
can be used to define a synonym (for
example for the symbol \varnothing
which is a synonym for \emptyset
).
If no parse
handler is provided, only the trigger is used to select this
entry. Otherwise, if the trigger of the entry matches the current
token, the parse
handler is invoked.
optional serialize:
| LatexString
| SerializeHandler;
Transform an expression into a LaTeX string.
If no serialize
handler is provided, the trigger is used.
type DefaultEntry = BaseEntry & Trigger & {
parse: | Expression
| ExpressionParseHandler;
};
type ExpressionEntry = BaseEntry & Trigger & {
kind: "expression";
parse: | Expression
| ExpressionParseHandler;
precedence: Precedence;
};
type MatchfixEntry = BaseEntry & {
kind: "matchfix";
openTrigger: Delimiter | LatexToken[];
closeTrigger: Delimiter | LatexToken[];
parse: MatchfixParseHandler;
};
openTrigger: Delimiter | LatexToken[];
If kind
is 'matchfix'
: the openTrigger
and closeTrigger
properties are required.
optional parse: MatchfixParseHandler;
When invoked, the parser is pointing after the close delimiter. The argument of the handler is the body, i.e. the content between the open delimiter and the close delimiter.
type InfixEntry = BaseEntry & Trigger & {
kind: "infix";
associativity: "right" | "left" | "none" | "any";
precedence: Precedence;
parse: string | InfixParseHandler;
};
kind: "infix";
Infix position, with an operand before and an operand after: a ⊛ b
.
Example: +
, \times
.
optional associativity: "right" | "left" | "none" | "any";
-
none
: a ? b ? c -> syntax error -
any
: a + b + c -> +(a, b, c) -
left
: a / b / c -> /(/(a, b), c) -
right
: a = b = c -> =(a, =(b, c)) -
any
-associative operators have an unlimited number of arguments -
left
,right
ornone
associative operators have two arguments
type PostfixEntry = BaseEntry & Trigger & {
kind: "postfix";
precedence: Precedence;
parse: string | PostfixParseHandler;
};
kind: "postfix";
Postfix position, with an operand before: a ⊛
Example: !
.
type PrefixEntry = BaseEntry & Trigger & {
kind: "prefix";
precedence: Precedence;
parse: string | PrefixParseHandler;
};
kind: "prefix";
Prefix position, with an operand after: ⊛ a
Example: -
, \not
.
type EnvironmentEntry = BaseEntry & {
kind: "environment";
parse: EnvironmentParseHandler;
symbolTrigger: MathJsonSymbol;
};
A LaTeX dictionary entry for an environment, that is a LaTeX
construct using \begin{...}...\end{...}
.
type SymbolEntry = BaseEntry & Trigger & {
kind: "symbol";
precedence: Precedence;
parse: | Expression
| SymbolParseHandler;
};
optional precedence: Precedence;
Used for appropriate wrapping (i.e. when to surround it with parens)
type FunctionEntry = BaseEntry & Trigger & {
kind: "function";
parse: | Expression
| FunctionParseHandler;
};
A function is a symbol followed by:
- some postfix operators such as
\prime
- an optional list of arguments in an enclosure (parentheses)
For more complex situations, for example implicit arguments or
inverse functions postfix (i.e. ^{-1}), use a custom parse handler with a
entry of kind expression
.
type LatexDictionaryEntry = OneOf<[
| ExpressionEntry
| MatchfixEntry
| InfixEntry
| PostfixEntry
| PrefixEntry
| SymbolEntry
| FunctionEntry
| EnvironmentEntry
| DefaultEntry]>;
A dictionary entry is a record that maps a LaTeX token or string of tokens ( a trigger) to a MathJSON expression or to a parsing handler.
Set the ComputeEngine.latexDictionary property to an array of dictionary entries to define custom LaTeX parsing and serialization.
type ParseLatexOptions = NumberFormat & {
skipSpace: boolean;
parseNumbers: "auto" | "rational" | "decimal" | "never";
getSymbolType: (symbol) => BoxedType;
parseUnexpectedToken: (lhs, parser) => Expression | null;
preserveLatex: boolean;
};
The LaTeX parsing options can be used with the ce.parse()
method.
skipSpace: boolean;
If true, ignore space characters in math mode.
Default: true
parseNumbers: "auto" | "rational" | "decimal" | "never";
When parsing a decimal number, e.g. 3.1415
:
"auto"
or"decimal"
: if a decimal number, parse it as an approximate decimal number with a whole part and a fractional part"rational"
: if a decimal number, parse it as an exact rational number with a numerator and a denominator. If not a decimal number, parse it as a regular number."never"
: do not parse numbers, instead return each token making up the number (minus sign, digits, decimal marker, etc...).
Note: if the number includes repeating digits (e.g. 1.33(333)
),
it will be parsed as a decimal number even if this setting is "rational"
.
Default: "auto"
getSymbolType: (symbol) => BoxedType;
This handler is invoked when the parser encounters a that has not yet been declared.
The symbol
argument is a valid symbol.
parseUnexpectedToken: (lhs, parser) => Expression | null;
This handler is invoked when the parser encounters an unexpected token.
The lhs
argument is the left-hand side of the token, if any.
The handler can access the unexpected token with parser.peek
. If
it is a token that should be recognized, the handler can consume it
by calling parser.nextToken()
.
The handler should return an expression or null
if the token is not
recognized.
preserveLatex: boolean;
If true, the expression will be decorated with the LaTeX fragments corresponding to each elements of the expression.
The top-level expression, that is the one returned by parse()
, will
include the verbatim LaTeX input that was parsed. The sub-expressions
may contain a slightly different LaTeX, for example with consecutive spaces
replaced by one, with comments removed and with some low-level LaTeX
commands replaced, for example \egroup
and \bgroup
.
Default: false
An instance of Parser
is provided to the parse
handlers of custom
LaTeX dictionary entries.
readonly options: Required<ParseLatexOptions>;
index: number;
The index of the current token
readonly atEnd: boolean;
True if the last token has been reached.
Consider also atTerminator()
.
readonly peek: string;
Return the next token, without advancing the index
getSymbolType(id): BoxedType
####### id
string
pushSymbolTable(): void
popSymbolTable(): void
addSymbol(id, type): void
####### id
string
####### type
string
| BoxedType
atTerminator(t): boolean
Return true if the terminator condition is met or if the last token has been reached.
####### t
nextToken(): string
Return the next token and advance the index
latex(start, end?): string
Return a string representation of the expression
between start
and end
(default: the whole expression)
####### start
number
####### end?
number
error(code, fromToken): Expression
Return an error expression with the specified code and arguments
####### code
string
| [string
, ...Expression[]
]
####### fromToken
number
skipSpace(): boolean
If there are any space, advance the index until a non-space is encountered
skipVisualSpace(): void
Skip over "visual space" which
includes space tokens, empty groups {}
, and commands such as \,
and \!
match(token): boolean
If the next token matches the target advance and return true. Otherwise return false
####### token
string
matchAll(tokens): boolean
Return true if the next tokens match the argument, an array of tokens, or null otherwise
####### tokens
string
[]
matchAny(tokens): string
Return the next token if it matches any of the token in the argument or null otherwise
####### tokens
string
[]
matchChar(): string
If the next token is a character, return it and advance the index
This includes plain characters (e.g. 'a', '+'...), characters
defined in hex (^^ and ^^^^), the \char
and \unicode
command.
parseGroup(): Expression
Parse an expression in a LaTeX group enclosed in curly brackets {}
.
These are often used as arguments to LaTeX commands, for example
\frac{1}{2}
.
Return null
if none was found
Return Nothing
if an empty group {}
was found
parseToken(): Expression
Some LaTeX commands (but not all) can accept arguments as single
tokens (i.e. without braces), for example ^2
, \sqrt3
or \frac12
This argument will usually be a single token, but can be a sequence of
tokens (e.g. \sqrt\frac12
or \sqrt\operatorname{speed}
).
The following tokens are excluded from consideration in order to fail
early when encountering a likely syntax error, for example x^(2)
instead of x^{2}
. With (
in the list of excluded tokens, the
match will fail and the error can be recovered.
The excluded tokens include !"#$%&(),/;:?@[]
|~", \left
, \bigl
, etc...
parseOptionalGroup(): Expression
Parse an expression enclosed in a LaTeX optional group enclosed in square brackets []
.
Return null
if none was found.
parseEnclosure(): Expression
Parse an enclosure (open paren/close paren, etc..) and return the expression inside the enclosure
parseStringGroup(optional?): string
Some LaTeX commands have arguments that are not interpreted as
expressions, but as strings. For example, \begin{array}{ccc}
(both
array
and ccc
are strings), \color{red}
or \operatorname{lim sup}
.
If the next token is the start of a group ({
), return the content
of the group as a string. This may include white space, and it may need
to be trimmed at the start and end of the string.
LaTeX commands are typically not allowed inside a string group (for example,
\alpha
would result in an error), but we do not enforce this.
If optional
is true, this should be an optional group in square brackets
otherwise it is a regular group in braces.
####### optional?
boolean
parseSymbol(until?): Expression
A symbol can be:
- a single-letter symbol:
x
- a single LaTeX command:
\pi
- a multi-letter symbol:
\operatorname{speed}
####### until?
Partial
<Terminator
>
parseTabular(): Expression[][]
Parse an expression in a tabular format, where rows are separated by \\
and columns by &
.
Return rows of sparse columns: empty rows are indicated with Nothing
,
and empty cells are also indicated with Nothing
.
parseArguments(kind?, until?): readonly Expression[]
Parse an argument list, for example: (12, x+1)
or \left(x\right)
- 'enclosure' : will look for arguments inside an enclosure (an open/close fence) (default)
- 'implicit': either an expression inside a pair of
()
, or just a primary (i.e. we interpret\cos x + 1
as\cos(x) + 1
)
Return an array of expressions, one for each argument, or null
if no
argument was found.
####### kind?
"implicit"
| "enclosure"
####### until?
parsePostfixOperator(lhs, until?): Expression
Parse a postfix operator, such as '
or !
.
Prefix, infix and matchfix operators are handled by parseExpression()
####### lhs
####### until?
Partial
<Terminator
>
parseExpression(until?): Expression
Parse an expression:
<expression> ::=
| <primary> ( <infix-op> <expression> )?
| <prefix-op> <expression>
<primary> :=
(<number> | <symbol> | <function-call> | <matchfix-expr>)
(<subsup> | <postfix-operator>)*
<matchfix-expr> :=
<matchfix-op-open> <expression> <matchfix-op-close>
<function-call> ::=
| <function><matchfix-op-group-open><expression>[',' <expression>]<matchfix-op-group-close>
This is the top-level parsing entry point.
Stop when an operator of precedence less than until.minPrec
or the sequence of tokens until.tokens
is encountered
until
is { minPrec:0 }
by default.
####### until?
Partial
<Terminator
>
parseNumber(): Expression
Parse a number.
addBoundary(boundary): void
Boundaries are used to detect the end of an expression.
They are used for unusual syntactic constructs, for example
\int \sin x dx
where the dx
is not an argument to the \sin
function, but a boundary of the integral.
They are also useful when handling syntax errors and recovery.
For example, \begin{bmatrix} 1 & 2 { \end{bmatrix}
has an
extraneous {
, but the parser will attempt to recover and continue
parsing when it encounters the \end{bmatrix}
boundary.
####### boundary
string
[]
removeBoundary(): void
matchBoundary(): boolean
boundaryError(msg): Expression
####### msg
string
| [string
, ...Expression[]
]
type SerializeLatexOptions = NumberSerializationFormat & {
prettify: boolean;
invisibleMultiply: LatexString;
invisiblePlus: LatexString;
multiply: LatexString;
missingSymbol: LatexString;
applyFunctionStyle: (expr, level) => DelimiterScale;
groupStyle: (expr, level) => DelimiterScale;
rootStyle: (expr, level) => "radical" | "quotient" | "solidus";
fractionStyle: (expr, level) =>
| "quotient"
| "block-quotient"
| "inline-quotient"
| "inline-solidus"
| "nice-solidus"
| "reciprocal"
| "factor";
logicStyle: (expr, level) => "word" | "boolean" | "uppercase-word" | "punctuation";
powerStyle: (expr, level) => "root" | "solidus" | "quotient";
numericSetStyle: (expr, level) => "compact" | "regular" | "interval" | "set-builder";
};
The LaTeX serialization options can used with the expr.toLatex()
method.
prettify: boolean;
If true, prettify the LaTeX output.
For example, render \frac{a}{b}\frac{c}{d}
as \frac{ac}{bd}
invisibleMultiply: LatexString;
LaTeX string used to render an invisible multiply, e.g. in '2x'.
If empty, both operands are concatenated, i.e. 2x
.
Use \cdot
to insert a \cdot
operator between them, i.e. 2 \cdot x
.
Empty by default.
invisiblePlus: LatexString;
LaTeX string used to render mixed numbers e.g. '1 3/4'.
Leave it empty to join the main number and the fraction, i.e. render it
as 1\frac{3}{4}
.
Use +
to insert an explicit +
operator between them,
i.e. 1+\frac{3}{4}
Empty by default.
multiply: LatexString;
LaTeX string used to render an explicit multiply operator.
For example, \times
, \cdot
, etc...
Default: \times
missingSymbol: LatexString;
Serialize the expression ["Error", "'missing'"]
, with this LaTeX string
An instance of Serializer
is provided to the serialize
handlers of custom
LaTeX dictionary entries.
readonly options: Required<SerializeLatexOptions>;
readonly dictionary: IndexedLatexDictionary;
level: number;
"depth" of the expression:
- 0 for the root
- 1 for a subexpression of the root
- 2 for subexpressions of the subexpressions of the root
- etc...
This allows the serialized LaTeX to vary depending on the depth of the expression.
For example use \Bigl(
for the top level, and \bigl(
or (
for others.
serialize: (expr) => string;
Output a LaTeX string representing the expression
wrap: (expr, prec?) => string;
Add a group fence around the expression if it is
an operator of precedence less than or equal to prec
.
applyFunctionStyle: (expr, level) => DelimiterScale;
Styles
groupStyle: (expr, level) => DelimiterScale;
rootStyle: (expr, level) => "radical" | "quotient" | "solidus";
fractionStyle: (expr, level) =>
| "quotient"
| "block-quotient"
| "inline-quotient"
| "inline-solidus"
| "nice-solidus"
| "reciprocal"
| "factor";
logicStyle: (expr, level) => "boolean" | "word" | "uppercase-word" | "punctuation";
powerStyle: (expr, level) => "quotient" | "solidus" | "root";
numericSetStyle: (expr, level) => "interval" | "compact" | "regular" | "set-builder";
serializeFunction(expr, def?): string
####### expr
####### def?
IndexedLatexDictionaryEntry
serializeSymbol(expr): string
####### expr
wrapString(s, style, delimiters?): string
Output s
surrounded by delimiters.
If delimiters
is not specified, use ()
####### s
string
####### style
####### delimiters?
string
wrapArguments(expr): string
A string with the arguments of expr fenced appropriately and separated by commas.
####### expr
wrapShort(expr): string
Add a group fence around the expression if it is short (not a function)
####### expr
type SerializeHandler = (serializer, expr) => string;
The serialize
handler of a custom LaTeX dictionary entry can be
a function of this type.
type Sign =
| "zero"
| "positive"
| "negative"
| "non-negative"
| "non-positive"
| "not-zero"
| "unsigned";
type ExactNumericValueData = {
rational: Rational;
radical: number;
};
The value is equal to (decimal * rational * sqrt(radical)) + im * i
type NumericValueData = {
re: Decimal | number;
im: number;
};
type NumericValueFactory = (data) => NumericValue;
new NumericValue(): NumericValue
im: number;
The imaginary part of this numeric value.
Can be negative, zero or positive.
True if numeric value is the product of a rational and the square root of an integer.
This includes: 3/4√5, -2, √2, etc...
But it doesn't include 0.5, 3.141592, etc...
If isExact()
, returns an ExactNumericValue, otherwise returns undefined.
The real part of this numeric value.
Can be negative, 0 or positive.
bignum version of .re, if available
isZeroWithTolerance(_tolerance): boolean
####### _tolerance
number
| Decimal
abstract sgn(): -1 | 0 | 1
The sign of complex numbers is undefined
abstract N(): NumericValue
Return a non-exact representation of the numeric value
abstract neg(): NumericValue
abstract inv(): NumericValue
abstract add(other): NumericValue
####### other
number
| NumericValue
abstract sub(other): NumericValue
####### other
abstract mul(other): NumericValue
####### other
number
| Decimal
| NumericValue
abstract div(other): NumericValue
####### other
number
| NumericValue
abstract pow(n): NumericValue
####### n
number
| NumericValue
| {
re
: number
;
im
: number
;
}
abstract root(n): NumericValue
####### n
number
abstract sqrt(): NumericValue
abstract gcd(other): NumericValue
####### other
abstract abs(): NumericValue
abstract ln(base?): NumericValue
####### base?
number
abstract exp(): NumericValue
abstract floor(): NumericValue
abstract ceil(): NumericValue
abstract round(): NumericValue
abstract eq(other): boolean
####### other
number
| NumericValue
abstract lt(other): boolean
####### other
number
| NumericValue
abstract lte(other): boolean
####### other
number
| NumericValue
abstract gt(other): boolean
####### other
number
| NumericValue
abstract gte(other): boolean
####### other
number
| NumericValue
valueOf(): string | number
Object.valueOf(): returns a primitive value, preferably a JavaScript number over a string, even if at the expense of precision
toPrimitive: string | number
Object.toPrimitive()
####### hint
"string"
| "number"
| "default"
toJSON(): any
Object.toJSON
print(): void
type SmallInteger = IsInteger<number>;
A SmallInteger
is an integer < 1e6
type Rational =
| [SmallInteger, SmallInteger]
| [bigint, bigint];
A rational number is a number that can be expressed as the quotient or fraction p/q of two integers, a numerator p and a non-zero denominator q.
A rational can either be represented as a pair of small integers or a pair of big integers.
type BigNum = Decimal;
readonly _BIGNUM_NAN: Decimal;
readonly _BIGNUM_ZERO: Decimal;
readonly _BIGNUM_ONE: Decimal;
readonly _BIGNUM_TWO: Decimal;
readonly _BIGNUM_HALF: Decimal;
readonly _BIGNUM_PI: Decimal;
readonly _BIGNUM_NEGATIVE_ONE: Decimal;
bignum(value): Decimal
####### value
string
| number
| bigint
| Decimal
type TaggedValueDefinition = {
value: BoxedValueDefinition;
};
type TaggedOperatorDefinition = {
operator: BoxedOperatorDefinition;
};
type SymbolTable = {
parent: SymbolTable | null;
ids: {};
};
type BigNumFactory = (value) => Decimal;
type JsonSerializationOptions = {
prettify: boolean;
exclude: string[];
shorthands: ("all" | "number" | "symbol" | "function" | "string")[];
metadata: ("all" | "wikidata" | "latex")[];
repeatingDecimal: boolean;
fractionalDigits: "auto" | "max" | number;
};
Options to control the serialization to MathJSON when using BoxedExpression.toMathJson()
.
prettify: boolean;
If true, the serialization applies some transformations to make
the JSON more readable. For example, ["Power", "x", 2]
is serialized
as ["Square", "x"]
.
exclude: string[];
A list of space separated function names that should be excluded from the JSON output.
Those functions are replaced with an equivalent, for example, Square
with
Power
, etc...
Possible values include Sqrt
, Root
, Square
, Exp
, Subtract
,
Rational
, Complex
Default: []
(none)
shorthands: ("all" | "number" | "symbol" | "function" | "string")[];
A list of space separated keywords indicating which MathJSON expressions can use a shorthand.
Default: ["all"]
metadata: ("all" | "wikidata" | "latex")[];
A list of space separated keywords indicating which metadata should be included in the MathJSON. If metadata is included, shorthand notation is not used.
Default: []
(none)
repeatingDecimal: boolean;
If true, repeating decimals are detected and serialized accordingly For example:
1.3333333333333333
( \to )1.(3)
0.142857142857142857142857142857142857142857142857142
( \to )0.(1428571)
Default: true
fractionalDigits: "auto" | "max" | number;
The maximum number of significant digits in serialized numbers.
"max"
: all availabe digits are serialized."auto"
: use the same precision as the compute engine.
Default: "auto"
type NumberFormat = {
positiveInfinity: LatexString;
negativeInfinity: LatexString;
notANumber: LatexString;
imaginaryUnit: LatexString;
decimalSeparator: LatexString;
digitGroupSeparator: | LatexString
| [LatexString, LatexString];
digitGroup: "lakh" | number | [number | "lakh", number];
exponentProduct: LatexString;
beginExponentMarker: LatexString;
endExponentMarker: LatexString;
truncationMarker: LatexString;
repeatingDecimal: "auto" | "vinculum" | "dots" | "parentheses" | "arc" | "none";
};
These options control how numbers are parsed and serialized.
decimalSeparator: LatexString;
A string representing the decimal separator, the string separating the whole portion of a number from the fractional portion, i.e. the "." in "3.1415".
Some countries use a comma rather than a dot. In this case it is
recommended to use "{,}"
as the separator: the surrounding brackets
ensure there is no additional gap after the comma.
Default: "."
digitGroupSeparator:
| LatexString
| [LatexString, LatexString];
A string representing the separator between groups of digits, to make numbers with many digits easier to read.
If a single string is provided, it is used to group digits in the whole and the fractional part of the number. If two strings are provided, the first is used for the whole part and the second for the fractional part.
Caution: some values may lead to unexpected results.
For example, if the digitGroupSeparator
is ,
(comma) the expression
\operatorname{Hypot}(1,2)
will parse as ["Hypot", 1.2]
rather than
["Hypot", 1, 2]
. You can however use {,}
which will avoid this issue
and display with correct spacing.
Default: "\\,"
(thin space, 3/18mu) (Resolution 7 of the 1948 CGPM)
digitGroup: "lakh" | number | [number | "lakh", number];
Maximum length of digits between digit group separators.
If a single number is provided, it is used for the whole and the fractional part of the number. If two numbers are provided, the first is used for the whole part and the second for the fractional part.
If '"lakh"
' is provided, the number is grouped in groups of 2 digits,
except for the last group which has 3 digits. For example: 1,00,00,000
.
Default: 3
type NumberSerializationFormat = NumberFormat & {
fractionalDigits: "auto" | "max" | number;
notation: "auto" | "engineering" | "scientific";
avoidExponentsInRange: undefined | null | [number, number];
};
fractionalDigits: "auto" | "max" | number;
The maximum number of significant digits in serialized numbers.
"max"
: all availabe digits are serialized."auto"
: use the same precision as the compute engine.
Default: "auto"
type DataTypeMap = {
float64: number;
float32: number;
int32: number;
uint8: number;
complex128: Complex;
complex64: Complex;
bool: boolean;
expression: BoxedExpression;
};
Map of TensorDataType
to JavaScript type.
type TensorDataType = keyof DataTypeMap;
The type of the cells in a tensor.
A record representing the type, shape and data of a tensor.
dtype: DT;
shape: number[];
optional rank: number;
data: DataTypeMap[DT][];
readonly one: T;
readonly zero: T;
readonly nan: T;
cast(x, dtype): number
####### x
T
####### dtype
"float64"
cast(x, dtype): number
####### x
T
####### dtype
"float32"
cast(x, dtype): number
####### x
T
####### dtype
"int32"
cast(x, dtype): number
####### x
T
####### dtype
"uint8"
cast(x, dtype): any
####### x
T
####### dtype
"complex128"
cast(x, dtype): any
####### x
T
####### dtype
"complex64"
cast(x, dtype): boolean
####### x
T
####### dtype
"bool"
cast(x, dtype): BoxedExpression
####### x
T
####### dtype
"expression"
cast(x, dtype): number[]
####### x
T
[]
####### dtype
"float64"
cast(x, dtype): number[]
####### x
T
[]
####### dtype
"float32"
cast(x, dtype): number[]
####### x
T
[]
####### dtype
"int32"
cast(x, dtype): number[]
####### x
T
[]
####### dtype
"uint8"
cast(x, dtype): Complex[]
####### x
T
[]
####### dtype
"complex128"
cast(x, dtype): Complex[]
####### x
T
[]
####### dtype
"complex64"
cast(x, dtype): boolean[]
####### x
T
[]
####### dtype
"bool"
cast(x, dtype): BoxedExpression[]
####### x
T
[]
####### dtype
"expression"
cast(x, dtype): any
####### x
T
| T
[]
####### dtype
keyof DataTypeMap
expression(x): BoxedExpression
####### x
T
isZero(x): boolean
####### x
T
isOne(x): boolean
####### x
T
equals(lhs, rhs): boolean
####### lhs
T
####### rhs
T
add(lhs, rhs): T
####### lhs
T
####### rhs
T
addn(...xs): T
####### xs
...T
[]
neg(x): T
####### x
T
sub(lhs, rhs): T
####### lhs
T
####### rhs
T
mul(lhs, rhs): T
####### lhs
T
####### rhs
T
muln(...xs): T
####### xs
...T
[]
div(lhs, rhs): T
####### lhs
T
####### rhs
T
pow(rhs, n): T
####### rhs
T
####### n
number
conjugate(x): T
####### x
T
TensorData
<DT
>
dtype: DT;
shape: number[];
rank: number;
data: DataTypeMap[DT][];
readonly field: TensorField<DT>;
readonly expression: BoxedExpression;
readonly array: NestedArray<DataTypeMap[DT]>;
readonly isSquare: boolean;
readonly isSymmetric: boolean;
readonly isSkewSymmetric: boolean;
readonly isDiagonal: boolean;
readonly isUpperTriangular: boolean;
readonly isLowerTriangular: boolean;
readonly isTriangular: boolean;
readonly isIdentity: boolean;
readonly isZero: boolean;
at(...indices): DataTypeMap[DT]
####### indices
...number
[]
diagonal(axis1?, axis2?): DataTypeMap[DT][]
####### axis1?
number
####### axis2?
number
trace(axis1?, axis2?): DataTypeMap[DT]
####### axis1?
number
####### axis2?
number
reshape(...shape): Tensor<DT>
####### shape
...number
[]
flatten(): DataTypeMap[DT][]
upcast<DT>(dtype): Tensor<DT>
• DT extends keyof DataTypeMap
####### dtype
DT
transpose(axis1?, axis2?): Tensor<DT>
####### axis1?
number
####### axis2?
number
conjugateTranspose(axis1?, axis2?): Tensor<DT>
####### axis1?
number
####### axis2?
number
determinant(): DataTypeMap[DT]
inverse(): Tensor<DT>
pseudoInverse(): Tensor<DT>
adjugateMatrix(): Tensor<DT>
minor(axis1, axis2): DataTypeMap[DT]
####### axis1
number
####### axis2
number
map1(fn, scalar): Tensor<DT>
####### fn
(lhs
, rhs
) => DataTypeMap
[DT
]
####### scalar
DataTypeMap
[DT
]
map2(fn, rhs): Tensor<DT>
####### fn
(lhs
, rhs
) => DataTypeMap
[DT
]
####### rhs
Tensor
<DT
>
add(other): Tensor<DT>
####### other
Tensor
<DT
> | DataTypeMap
[DT
]
subtract(other): Tensor<DT>
####### other
Tensor
<DT
> | DataTypeMap
[DT
]
multiply(other): Tensor<DT>
####### other
Tensor
<DT
> | DataTypeMap
[DT
]
divide(other): Tensor<DT>
####### other
Tensor
<DT
> | DataTypeMap
[DT
]
power(other): Tensor<DT>
####### other
Tensor
<DT
> | DataTypeMap
[DT
]
equals(other): boolean
####### other
Tensor
<DT
>
new BoxedType(type): BoxedType
####### type
string
| AlgebraicType
| NegationType
| CollectionType
| ListType
| SetType
| MapType
| TupleType
| FunctionSignature
| ValueType
| TypeReference
static unknown: BoxedType;
static number: BoxedType;
static non_finite_number: BoxedType;
static finite_number: BoxedType;
static finite_integer: BoxedType;
static finite_real: BoxedType;
static string: BoxedType;
type: Type;
matches(other): boolean
####### other
string
| AlgebraicType
| NegationType
| CollectionType
| ListType
| SetType
| MapType
| TupleType
| FunctionSignature
| ValueType
| TypeReference
| BoxedType
is(other): boolean
####### other
string
| AlgebraicType
| NegationType
| CollectionType
| ListType
| SetType
| MapType
| TupleType
| FunctionSignature
| ValueType
| TypeReference
toString(): string
toJSON(): string
toPrimitive: string
####### hint
string
valueOf(): string
type MathJsonAttributes = {
comment: string;
documentation: string;
latex: string;
wikidata: string;
wikibase: string;
openmathSymbol: string;
openmathCd: string;
sourceUrl: string;
sourceContent: string;
sourceOffsets: [number, number];
};
optional comment: string;
A human readable string to annotate this expression, since JSON does not allow comments in its encoding
optional documentation: string;
A Markdown-encoded string providing documentation about this expression.
optional latex: string;
A visual representation of this expression as a LaTeX string.
This can be useful to preserve non-semantic details, for example parentheses in an expression or styling attributes.
optional wikidata: string;
A short string referencing an entry in a wikibase.
For example:
"Q167"
is the wikidata entry
for the Pi
constant.
optional wikibase: string;
A base URL for the wikidata
key.
A full URL can be produced by concatenating this key with the wikidata
key. This key applies to this node and all its children.
The default value is "https://www.wikidata.org/wiki/"
optional openmathSymbol: string;
A short string indicating an entry in an OpenMath Content Dictionary.
For example: arith1/#abs
.
optional openmathCd: string;
A base URL for an OpenMath content dictionary. This key applies to this node and all its children.
The default value is "http://www.openmath.org/cd".
optional sourceUrl: string;
A URL to the source code from which this expression was generated.
optional sourceContent: string;
The source code from which this expression was generated.
It could be a LaTeX expression, or some other source language.
optional sourceOffsets: [number, number];
A character offset in sourceContent
or sourceUrl
from which this
expression was generated.
type MathJsonSymbol = string;
type MathJsonNumberObject = {
num: "NaN" | "-Infinity" | "+Infinity" | string;
} & MathJsonAttributes;
A MathJSON numeric quantity.
The num
string is made of:
- an optional
-
minus sign - a string of decimal digits
- an optional fraction part (a
.
decimal marker followed by decimal digits) - an optional repeating decimal pattern: a string of digits enclosed in parentheses
- an optional exponent part (a
e
orE
exponent marker followed by an optional-
minus sign, followed by a string of digits)
It can also consist of the value NaN
, -Infinity
and +Infinity
to
represent these respective values.
A MathJSON number may contain more digits or an exponent with a greater range than can be represented in an IEEE 64-bit floating-point.
For example:
-12.34
0.234e-56
1.(3)
123456789123456789.123(4567)e999
type MathJsonSymbolObject = {
sym: MathJsonSymbol;
} & MathJsonAttributes;
type MathJsonStringObject = {
str: string;
} & MathJsonAttributes;
type MathJsonFunctionObject = {
fn: [MathJsonSymbol, ...Expression[]];
} & MathJsonAttributes;
type ExpressionObject =
| MathJsonNumberObject
| MathJsonStringObject
| MathJsonSymbolObject
| MathJsonFunctionObject;
type Expression =
| ExpressionObject
| number
| MathJsonSymbol
| string
| readonly [MathJsonSymbol, ...Expression[]];
A MathJSON expression is a recursive data structure.
The leaf nodes of an expression are numbers, strings and symbols. The dictionary and function nodes can contain expressions themselves.
type PrimitiveType =
| NumericType
| "collection"
| "list"
| "set"
| "map"
| "tuple"
| "value"
| "scalar"
| "function"
| "symbol"
| "boolean"
| "string"
| "expression"
| "unknown"
| "error"
| "nothing"
| "never"
| "any";
A primitive type is a simple type that represents a concrete value.
-
any
: the top typeexpression
error
: an invalid value, such as["Error", "missing"]
nothing
: the type of theNothing
symbol, the unit typenever
: the bottom typeunknown
: a value whose type is not known
-
expression
:- a symbolic expression, such as
["Add", "x", 1]
<value>
symbol
: a symbol, such asx
.function
: a function literal such as["Function", ["Add", "x", 1], "x"]
.
- a symbolic expression, such as
-
value
scalar
<number>
boolean
: a boolean value:True
orFalse
.string
: a string of characters.
collection
list
: a collection of expressions, possibly recursive, with optional dimensions, e.g.[number]
,[boolean^32]
,[number^(2x3)]
. Used to represent a vector, a matrix or a tensor when the type of its elements is a numberset
: a collection of unique expressions, e.g.set<string>
.tuple
: a fixed-size collection of named or unnamed elements, e.g.tuple<number, boolean>
,tuple<x: number, y: boolean>
.map
: a set key-value pairs, e.g.map<x: number, y: boolean>
.
type NumericType =
| "number"
| "finite_number"
| "complex"
| "finite_complex"
| "imaginary"
| "real"
| "finite_real"
| "rational"
| "finite_rational"
| "integer"
| "finite_integer"
| "non_finite_number";
number
: any numeric value =complex
+real
plusNaN
complex
: a number with non-zero real and imaginary parts =finite_complex
plusComplexInfinity
finite_complex
: a finite complex number =imaginary
+finite_real
imaginary
: a complex number with a real part of 0 (pure imaginary)finite_number
: a finite numeric value =finite_complex
finite_real
: a finite real number =finite_rational
+finite_integer
finite_rational
: a pure rational numberfinite_integer
: a whole numberreal
: a complex number with an imaginary part of 0 =finite_real
+non_finite_number
non_finite_number
:PositiveInfinity
,NegativeInfinity
integer
: a whole number =finite_integer
+non_finite_number
rational
: a pure rational number (not an integer) =finite_rational
+non_finite_number
type NamedElement = {
name: string;
type: Type;
};
type FunctionSignature = {
kind: "signature";
args: NamedElement[];
optArgs: NamedElement[];
restArg: NamedElement;
result: Type;
};
type AlgebraicType = {
kind: "union" | "intersection";
types: Type[];
};
type NegationType = {
kind: "negation";
type: Type;
};
type ValueType = {
kind: "value";
value: any;
};
type MapType = {
kind: "map";
elements: Record<string, Type>;
};
Map is a non-indexable collection of key/value pairs.
An element of a map whose type is a subtype of nothing
is optional.
For example, in {x: number, y: boolean | nothing}
the element y
is optional.
type CollectionType = {
kind: "collection";
elements: Type;
};
Collection, List, Set, Tuple and Map are collections.
CollectionType
is a generic collection of elements of a certain type.
type ListType = {
kind: "list";
elements: Type;
dimensions: number[];
};
The elements of a list are ordered.
All elements of a list have the same type, but it can be a broad type,
up to any
.
The same element can be present in the list more than once.
A list can be multi-dimensional. For example, a list of integers with dimensions 2x3x4 is a 3D tensor with 2 layers, 3 rows and 4 columns.
type SetType = {
kind: "set";
elements: Type;
};
Each element of a set is unique (is not present in the set more than once). The elements of a set are not ordered.
type TupleType = {
kind: "tuple";
elements: NamedElement[];
};
type TypeReference = {
kind: "reference";
ref: string;
};
Nominal typing
type Type =
| PrimitiveType
| AlgebraicType
| NegationType
| CollectionType
| ListType
| SetType
| MapType
| TupleType
| FunctionSignature
| ValueType
| TypeReference;
type TypeString = string;
The type of a boxed expression indicates the kind of expression it is and the value it represents.
The type is represented either by a primitive type (e.g. number, complex, collection, etc.), or a compound type (e.g. tuple, function signature, etc.).
Types are described using the following BNF grammar:
<type> ::= <union_type> | "(" <type> ")"
<union_type> ::= <intersection_type> (" | " <intersection_type>)*
<intersection_type> ::= <primary_type> (" & " <primary_type>)*
<primary_type> ::= <primitive>
| <tuple_type>
| <signature>
| <list_type>
<primitive> ::= "any" | "unknown" | <value-type> | <symbolic-type> | <numeric-type>
<numeric-type> ::= "number" | "complex" | "imaginary" | "real" | "rational" | "integer"
<value-type> ::= "value" | <numeric-type> | "collection" | "boolean" | "string"
<symbolic-type> ::= "expression" | "function" | "symbol"
<tuple_type> ::= "tuple<" (<name> <type> "," <named_tuple_elements>*) ">"
| "tuple<" (<type> "," <unnamed_tuple_elements>*) ">" |
| "tuple<" <tuple_elements> ">"
<tuple_elements> ::= <unnamed_tuple_elements> | <named_tuple_elements>
<unnamed_tuple_elements> ::= <type> ("," <type>)*
<named_tuple_elements> ::= <name> <type> ("," <name> <type>)*
<signature> ::= <arguments> " -> " <type>
<arguments> ::= "()"
| <argument>
| "(" <argument-list> ")"
<argument> ::= <type>
| <name> <type>
<rest_argument> ::= "..." <type>
| <name> "..." <type>
<optional_argument> ::= <argument> "?"
<optional_arguments> ::= <optional_argument> ("," <optional_argument>)*
<required_arguments> ::= <argument> ("," <argument>)*
<argument-list> ::= <required_arguments> ("," <rest_argument>)?
| <required_arguments> <optional_arguments>?
| <optional_arguments>?
| <rest_argument>
<list_type> ::= "list<" <type> <dimensions>? ">"
<dimensions> ::= "^" <fixed_size>
| "^(" <multi_dimensional_size> ")"
<fixed_size> ::= <positive-integer_literal>
<multi_dimensional_size> ::= <positive-integer_literal> "x" <positive-integer_literal> ("x" <positive-integer_literal>)*
<map> ::= "map" | "map<" <map_elements> ">"
<map_elements> ::= <name> <type> ("," <name> <type>)*
<set> ::= "set<" <type> ">"
<collection ::= "collection<" <type> ">"
<name> ::= <identifier> ":"
<identifier> ::= [a-zA-Z_][a-zA-Z0-9_]*
<positive-integer_literal> ::= [1-9][0-9]*
Examples of types strings:
"number"
-- a simple type primitive"(number, boolean)"
-- a tuple type"(x: number, y:boolean)"
-- a named tuple/record type. Either all arguments are named, or none are"collection<any>"
-- an arbitrary collection type, with no length or element type restrictions"collection<integer>"
-- a collection type where all the elements are integers"collection<(number, boolean)>"
-- a collection of tuples"collection<(value:number, seen:boolean)>"
-- a collection of named tuples"[boolean]^32"
-- a collection type with a fixed size of 32 elements"[integer]^(2x3)"
-- an integer matrix of 2 columns and 3 rows"[integer]^(2x3x4)"
-- a tensor of dimensions 2x3x4"number -> number"
-- a signature with a single argument"(x: number, number) -> number"
-- a signature with a named argument"(number, y:number?) -> number"
-- a signature with an optional named argument (can have several optional arguments, at the end)"(number, ...number) -> number"
-- a signature with a rest argument (can have only one, and no optional arguments if there is a rest argument)."() -> number"
-- a signature with an empty argument list"number | boolean"
-- a union type"(x: number) & (y: number)"
-- an intersection type"number | ((x: number) & (y: number))"
-- a union type with an intersection type"(number -> number) | number"
-- a union type with a signature and a primitive type
type TypeCompatibility = "covariant" | "contravariant" | "bivariant" | "invariant";
type TypeResolver = (name) => Type | undefined;
Symbol | Value |
---|---|
Degrees |
$$ \frac{\pi}{180} = 0.017453292519943295769236907\ldots $$ |
Pi |
$$ \pi \approx 3.14159265358979323\ldots $$ |
Function | Inverse | Hyperbolic | Area Hyperbolic |
---|---|---|---|
Sin |
Arcsin |
Sinh |
Arsinh |
Cos |
Arccos |
Cosh |
Arcosh |
Tan |
Arctan Arctan2 |
Tanh |
Artanh |
Cot |
Acot |
Coth |
Arcoth |
Sec |
Asec |
Sech |
Asech |
Csc |
Acsc |
Csch |
Acsch |
Function | |
---|---|
FromPolarCoordinates |
Converts $$ (\operatorname{radius}, \operatorname{angle}) \longrightarrow (x, y)$$ |
ToPolarCoordinates |
Converts The following macros are not allowed: operatorname
$$(x, y) \longrightarrow (\operatorname{radius}, \operatorname{angle})$$ |
Hypot |
The following macros are not allowed: operatorname
$$\operatorname{Hypot}(x,y) = \sqrt{x^2+y^2}$$ |
Haversine |
$$ \operatorname{Haversine}(z) = \sin(\frac{z}{2})^2 $$ The Haversine function was important in navigation because it appears in the haversine formula, which is used to reasonably accurately compute distances on an astronomic spheroid given angular positions (e.g., longitude and latitude). |
InverseHaversine |
The following macros are not allowed: operatorname
$$\operatorname{InverseHaversine}(z) = 2 \operatorname{Arcsin}(\sqrt{z})$$ |
Symbol | LaTeX | Notation |
---|---|---|
True |
\top |
$$ \top $$ |
’’ | \mathsf{T} |
$$ \mathsf{T}$$ |
’’ | \operator{True} |
$$ \operatorname{True}$$ |
False |
\bot |
$$ \bot $$ |
’’ | \mathsf{F} |
$$ \mathsf{F}$$ |
’’ | \operatorname{False} |
$$ \operatorname{False}$$ |
Symbol | LaTeX | Notation | |
---|---|---|---|
And |
p \land q |
$$ p \land q$$ | Conjunction |
’’ | p \operatorname{and} q |
$$ p \operatorname{and} q$$ | |
Or |
p \lor q |
$$ p \lor q$$ | Disjunction |
’’ | p \operatorname{or} q |
$$ p \operatorname{or} q$$ | |
Xor |
p \veebar q |
$$ p \veebar q$$ | Exclusive Or |
Nand |
p \barwedge q |
$$ p \barwedge q$$ | Not And |
Nor |
p \char"22BD q |
$$ p \char"22BD q$$ | Not Or |
Not |
\lnot p |
$$ \lnot p$$ | Negation |
’’ | \operatorname{not} p |
$$ \operatorname{not} p$$ | |
Equivalent |
p \iff q |
$$ p \iff q$$ | |
’’ | p \Leftrightarrow q |
$$ p \Leftrightarrow q$$ | |
Implies |
p \implies q |
||
’’ | p \Rightarrow q |
||
Proves |
p \vdash q |
||
Entails |
p \vDash q |
||
Satisfies |
p \models q |
condition, predicate
The ForAll
function represents the universal quantifier.
The condition is the variable or variables that are being quantified over or the set of elements that the variable can take.
The predicate is the statement that is being quantified.
The condition and the predicate are separated by a comma, a colon, or a vertical bar. The predicate can also be enclosed in parentheses after the condition.
["ForAll", "x", ["Greater", ["Add", "x", 1], "x"]]
["ForAll", ["Element", "x", "RealNumbers"], ["Greater", ["Square", "x"], 0]]
condition, predicate
The Exists
function represents the existential quantifier.
The condition is the variable or variables that are being quantified over, and the predicate is the statement that is being quantified.
The condition and the predicate are separated by a comma, a colon, or a vertical bar. The predicate can also be enclosed in parentheses after the condition.
["Exists", "x", ["Equal", ["Square", "x"], 1]]
["Exists", ["Element", "x", "RealNumbers"], ["Equal", ["Square", "x"], 1]]
condition, predicate
The ExistsUnique
function represents the unique existential quantifier.
The symbols below are inert constants. They are used as tags and have no value other than themselves.
Symbol | Description |
---|---|
All |
All the possible values apply |
None |
None of the possible values apply |
Nothing |
An optional expression is not present. Used in sparse list to indicate skipped elements. |
Undefined |
The result is not defined. Note that for numbers, the equivalent is NaN (Not a Number) |
["List", 2, "Nothing", 3]
A ["Declare"]
expression is used to declare a symbol in the current scope.
Once a symbol has been declared, its value can be changed using an
["Assign"]
expression.
An ["Assume"]
expression is used to assert a predicate about an expression. It is
used to provide additional information to the system, for example to indicate
that a variable is positive.
symbol, type_
symbol, type, value
Declare a new symbol in the current scope, and set its value and type.
If the symbol already has a definition in the current scope, evaluate to an
error, otherwise evaluate to value
.
This is equivalent to let
in JavaScript or var
in Python.
To change the value of an existing symbol, use an ["Assign"]
expression.
Declare
is not a pure function
since it changes the state of the Compute Engine.
Read more about using
ce.declare()
to declare a new symbol.
symbol, value
Set the value of symbol
to value
.
If symbol
has not been declared in the current scope, consider parent
scopes until a definition for the symbol is found.
If a definition is found, change the value of the symbol to value
if the
value is compatible with the type of the symbol: once set, the type of
a symbol cannot be changed.
If there is no definition for the symbol, add a new definition in the
current scope, and use the value
to infer the type of the symbol.
This is equivalent to =
in may programming languages.
Assign
is not a pure function.
Read more about using
Assign
to change the value of a symbol or function.
predicate
The predicate is an expression that evaluates to True
or False
.
The symbols in the predicate expression may be free, i.e. they may not have been declared yet. Asserting an assumption does not declare the symbols in the predicate.
The predicate can take the form of:
- an equality:
["Assume", ["Equal", "x", 3]]
- an inequality:
["Assume", ["Greater", "x", 0]]
- a membership expression:
["Assume", ["Element", "x", "Integers"]]
Assign
is not a pure function
since it changes the state of the Compute Engine.
The following functions can be applied to non-canonical expressions. The do not depend on the canonical form, but reflect the structure of the expression.
symbol_
Evaluate to a dictionary expression containing information about a symbol such as its type, its attributes, its value, etc...
expression
Evaluate to the head of expression
["Head", ["Add", 2, 3]]
// ➔ "Add"
expression
Evaluate to a sequence of the arguments of expression.
["Tail", ["Add", 2, 3]]
// ➔ ["Sequence", 2, 3]
Tail
can be used to change the head of an expression, for example:
["Multiply", ["Tail", ["Add", 2, 3]]]
// ➔ ["Multiply", 2, 3]
expression
Tag an expression that should be kept in an unevaluated form
expression
Evaluate to its argument
In the mathematical sense, this is an operator (a function that takes a function as an argument and returns a function).
The following functions can be used to obtain information about an expression.
expression
Evaluate to the domain of expression
["Domain", 2.4531]
// ➔ "RealNumbers"
expression1, expression2
Evaluate to True
if the two expressions are structurally identical, otherwise
evaluate to False
.
["IsSame", ["Add", 2, 3], ["Add", 2, 3]]
// ➔ True
To compare two expressions for mathematical equality, use Equal
.
To compare two expressions structurally, but ignoring the order of the arguments
of commutative functions, use CanonicalForm
.
See Comparing Expressions for other options to compare two expressions, such
as the Equal
function.
expression
Apply a sequence of definitions to an expression in order to reduce, simplify
and calculate its value. Overrides Hold
and hold attributes of a function.
Evaluate
only performs exact calculations. To perform numerical
approximations, use N
.
Read more about exact calculations and approximate calculations.
expression
Apply the distributive law if the expression is a product or power of sums.
For example: a(b + c) = ab + ac
- Expand the terms of the expression if it is a sum or negate.
- If the expression is a fraction, expand the numerator.
["Expand", ["Power", ["Add", "x", 1], 2]]
// ➔ ["Add", 1, ["Multiply", 2, "x"], ["Power", "x", 2]]
expression
Expand an expression, recursively.
["ExpandAll", ["Power", ["Multiply", ["Add", "x", 1], 3], 2]]
// ➔ ["Add", 1, ["Multiply", 6, "x"], ["Multiply", 6, ["Power", "x", 2]], ["Power", "x", 3]]
expression
Factor an expression.
["Factor", ["Add", ["Multiply", 2, "x"], ["Multiply", 2, "y"]]]
// ➔ ["Multiply", 2, ["Add", "x", "y"]]
expression
Combine the terms of a sum of fractions into a single fraction.
["Together", ["Add", ["Divide", 1, 2], ["Divide", 1, 3]]]
// ➔ ["Divide", 5, 6]
expression
The Simplify
function applies a sequence of transformations to an expression
in order to reduce, simplify and calculate its value.
expression
expression, form-1, form-2, ...
If expression is already canonical, this function has no effect.
If there are no form-n arguments, the expression is transformed to its canonical form.
If some form-n arguments are provided, they indicate one or more canonical transformations to apply to the expression. The following canonical forms are supported:
Order
: If expression is a commutative function, sort the arguments according to the canonical order of the arguments of the function.
["CanonicalForm", ["Add", 3, 2, 1], "Order"]
// -> ["Add", 1, 2, 3]
This can be useful to compare two non-canonical expressions for equality, for example:
["IsSame",
["Add", 1, "x"],
["Add", "x", 1]
]
// -> False
["IsSame",
["CanonicalForm", ["Add", 1, "x"], "Order"],
["CanonicalForm", ["Add", "x", 1], "Order"]
]
// -> True
Flatten
: Simplify associative expressions, remove any unnecessary delimiters indicating the order of operations, flattens anySequence
expressions.
["CanonicalForm", ["Add", 1, ["Add", 2, 3]], "Flatten"]
// -> ["Add", 1, 2, 3]
["CanonicalForm", ["Add", 1, ["Delimiter", ["Sequence", 2, 3]]], "Flatten"]
// -> ["Add", 1, 2, 3]
["CanonicalForm", ["Add", 1, ["Sequence", 2, 3]], "Flatten"]
// -> ["Add", 1, 2, 3]
-
Number
: Transform some number forms, for example["Add", 2, ["Multiply", 3, "ImaginaryI"]]
to["Complex", 2, 3]
, simplify and normalize numerator and denominator of rational numbers, etc... -
InvisibleOperator
: Remove any invisible operators that may be contained in the expression and replace them withMultiply
or function application, depending on the context
["CanonicalForm", ["InvisibleOperator", "2", "x"], "InvisibleOperator"]
// -> ["Multiply", 2, "x"]
Multiply
: If expression is aMultiply
function, simplify it by combining the coefficients and the factors, transform product to aPower
expression when possible.
["CanonicalForm", ["Multiply", 2, 3, "x"], "Multiply"]
// -> ["Multiply", 6, "x"]
-
Add
: If expression is anAdd
function, remove any0
, transform sum into multiplication when possible. If expression is aSubtract
transform it into anAdd
. If expression is aNegate
transform it into aMultiply
or negate number literals. -
Power
: TransformExp
,Square
,Sqrt
,Root
function to aPower
expression;
["CanonicalForm", ["Exp", "x"], "Power"]
```json example
["CanonicalForm", ["Power", 2, 3], "Power"]
// -> ["Power", 8]
To compare the input from a mathfield with an expected answer, you could use:
const correct = ce.parse(mf.value, {canonical: "Order"})
.isSame(ce.parse("1+x"))
Both 1+x
and x+1
will return true, but 2-1+x
will return false.
Note: see also the options for the canonical
option of ce.parse()
and
ce.box()
which can also be used to specify a custom canonical form:
const correct = ce.parse(mf.value, {canonical: "Order"})
.isSame(ce.parse("x+1"))
expression
Evaluate to a numerical approximation of the expression.
["N", "Pi"]
// ➔ 3.141592653589793
error-code, context
Tag an expression that could not be interpreted correctly. It may have a syntax error, a reference to an unknown symbol or some other problem.
The first argument, error-code
is either a string, or an ["ErrorCode"]
expression.
The context is an optional expression that provides additional information about the error.
symbol
Evaluate to the inverse function of its argument for example Arcsin
for Sin
.
[["InverseFunction", "Sin"], "x"]
In the mathematical sense, this is an operator (a function that takes a function as an argument and returns a function).
expression
Evaluate to a string made from the concatenation of the arguments converted to strings
["String", "x", 2]
// ➔ "'x2'"
expression
Evaluate to a new symbol made of a concatenation of the arguments.
["Symbol", "x", 2]
// ➔ "x2"
The symbol is not declared, it remains a free variable. To declare the symbol
use Declare
.
["Declare", ["Symbol", "x", 2], "RealNumbers"]
string
If expr is a ["LatexString"]
expression, evaluate to a MathJSON expression
corresponding to the LaTeX string.
["Parse", ["LatexString", "'\frac{\pi}{2}'"]]
// ➔ ["Divide", "Pi", 2]
expression
Evaluate to a LatexString
which is the expression serialized to LaTeX
string
Tag a string as a LaTeX string
These functions are all inert functions, that is they evaluate to themselves.
Function | Description | |
---|---|---|
Subminus |
$$ x_- $$ | |
Subplus |
$$ x_+$$ | |
Subscript |
$$ x_{n} $$ | |
Substar |
$$ x_*$$ | |
Superdagger |
$$ x^\dagger$$ | |
Superminus |
$$ x^-$$ | |
Superplus |
$$ x^+$$ | |
Superstar |
$$ x^*$$ | When the argument is a complex number, indicate the conjugate. |
import ChangeLog from '@site/src/components/ChangeLog';
## Coming Soon-
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 theexpr.value
property to get the value of an expression, you should now use theexpr.N().valueOf()
method instead. ThevalueOf()
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 acceptedce.assign("f(x, y)", ce.parse("x+y"))
. This is now deprecated. Usece.assign("f", ce.parse("(x, y) \\mapsto x+y")
instead. -
It was previously possible to invoke
expr.evaluate()
orexpr.N()
on a non-canonical expression. This will now return the expression itself.To evaluate a non-canonical expression, use
expr.canonical.evaluate()
orexpr.canonical.N()
.That's also the case for the methods
numeratorDenominator()
,numerator()
, anddenominator()
.In addition, invoking the methods
inv()
,abs()
,add()
,mul()
,div()
,pow()
,root()
,ln()
will throw an error if the expression is not canonical.
-
The
symbol
type can be refined to match a specific symbol. For examplesymbol<True>
. The typeexpression
can be refined to match expressions with a specific operator, for exampleexpression<Add>
is a type that matches expressions with theAdd
operator. The numeric types can be refined with a lower and upper bound. For exampleinteger<0..10>
is a type that matches integers between 0 and 10. The typereal<1..>
matches real numbers greater than 1 andrational<..0>
matches non-positive rational numbers. -
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.
-
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, butx^y
(where y===0), orx^{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
- Fixes the
Number
form - Forms (at least, 'Number', 'Power') do not mistakenly fully canonicalize operands
- This (partial canonicalization) now substitutes symbols (constants) with a
holdUntil
value of never during/prior-to canonicalization (i.e. just like for full canonicalization)
- Fixes the
- #231 During evaluation, some numbers, for example
10e-15
were incorrectly rounded to 0.
-
#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.
-
#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 aCancellationError
. -
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 anUnevaluated
expression is its argument. -
The type of a
Hold
expression was incorrectly returned asstring
. It now returns the type of its argument. -
The statistics function (
Mean
,Median
,Variance
,StandardDeviation
,Kurtosis
,Skewness
,Mode
,Quartiles
andInterQuartileRange
) 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
andStandardDeviation
functions now have variants for population statistics,PopulationVariance
andPopulationStandardDeviation
. 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 tolazy
- #201 Identifiers of the form
A_\text{1}
were not parsed correctly. - #202 Fixed serialization of integrals and bigops.
- Correctly account for
fractionalDigits
when formatting numbers. - #191 Correctly handle
\\lnot\\forall
and\\lnot\\exists
. - #206 The square root of 1000000 was canonicalized to 0.
- #207 When a square root with a literal base greater than 1e6 was preceded by a non-integer literal number, the literal number was ignored during canonicalization.
- #208 #204 Correctly evaluate numeric approximation of roots, e.g.
\\sqrt[3]{125}
. - #205
1/ln(0)
was incorrectly evaluated to1
. It now returns0
.
- #194 Correctly handle the precedence of unary negate, for example in
-5^{\frac12}
or-5!
. - When using a function definition with
ce.declare()
, do not generate a runtime error.
- Added
.expand()
method to boxed expression. This method expands the expression, for examplece.parse("(x+1)^2").expand()
will returnx^2 + 2x + 1
.
-
The property
expr.head
has been deprecated. Useexpr.operator
instead.expr.head
is still supported in this version but will be removed in a future update. -
The MathJSON utility functions
head()
andop()
have been renamed tooperator()
andoperand()
respectively. -
The methods for algebraic operations (
add
,div
,mul
, etc...) have been moved from the Compute Engine to the Boxed Expression class. Instead of callingce.add(a, b)
, calla.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 return3
. -
The
ce.numericMode
option has been removed. Instead, set thece.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 theRealNumbers
andComplexNumbers
domains instead. -
The "Domain" expression has been deprecated. Use types instead (see below).
-
Some
BoxedExpression
properties have been removed:- Instead of
expr.isZero
, useexpr.is(0)
. - Instead of
expr.isNotZero
, use!expr.is(0)
. - Instead of
expr.isOne
, useexpr.is(1)
. - Instead of
expr.isNegativeOne
, useexpr.is(-1)
.
- Instead of
-
The signature of
ce.declare()
has changed. In particular, theN
handler has been replaced withevaluate
.
// 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
orComplexNumbers
. 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 typeinteger
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')
andexpr.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
andDictionary
.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 to5/6
instead of0.8(3)
.To get an approximate result, use the
N()
method, for examplece.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:- an imaginary part, represented as a fixed-precision number
- a real part, represented either as a fixed or arbitrary precision number or as the product of a rational number and the square root of an integer.
For example:
- 234.567
- 1/2
- 3√5
- √7/3
- 4-3i
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 aNumericValue
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 tox
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
isa + b + c
the rule will match and return2a
expr.simplify("x + ...y? -> 3x");
If
expr
isa + b + c
the rule will match and return3a
. Ifexpr
isa
the rule will match and return3a
. -
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 returns12x^2 - 17x + 20
. Previously it returned4x(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 thece.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 ifexpr.numericValue
is notnull
.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.- Additionally, the relational operators (
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 ifi = 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
andTally
on collections.Unique
returns a collection with only the unique elements of the input collection, andTally
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
andTabulate
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
isundefined
.If the expression is not a real number, the property will return
NaN
. For example,ce.parse('i').isPositive
isNaN
. -
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 theevaluate
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.
- #174 Fixed some simplifications, such as
\frac{a^n}{a^m} = a^{n-m)
-
Rules can be defined using a new shorthand syntax, where each rule is a string of LaTeX:
expr.simplify(["\\frac{x}{x} -> 1", "x + x -> 2x"]);
Single letter variables are assumed to be wildcards, so x
is interpreted as
the wildcard _x
.
Additionally, the expanded form can also include LaTeX strings. The previous syntax using expressions can still be used, and the new and old syntax can be mixed.
For example:
expr.simplify([
{
match: "\\frac{x}{x}",
replace: "1"
},
{
match: ["Add", "x", "x"],
replace: "2x"
}
]);
The condition
function can also be expressed as a LaTeX string.
expr.simplify([ { match: "\\frac{x}{x}", replace: 1, condition: "x != 0" }, ]);
The shorthand syntax can be used any where a ruleset is expected, including with
the ce.rule()
function.
- A new
ce.getRuleSet()
method gives access to the built-in rules. - #171 The
Subtract
andDivide
function can now accept an arbitrary number of arguments. For example,["Subtract", 1, 2, 3]
is equivalent to["Subtract", ["Subtract", 1, 2], 3]
.
-
The canonical form of expressions has changed. It is now more consistent and simpler and should produce more predictable results.
For example, previously
ce.parse("1-x^2")
would produce["Subtract", 1, ["Square", "x"]]
.While this is a readable form, it introduces some complications when manipulating the expression: both the
Subtract
andSquare
functions have to be handled, in addition toAdd
andPower
.The new canonical form of this expression is
["Add", 1, ["Negate", ["Power", "x", 2]]]
. It is a bit more verbose, but it is simpler to manipulate. -
The
ce.serialize()
method has been replaced withexpr.toLatex()
andexpr.toMathJson()
. Thece.latexOptions
andce.jsonSerializationOptions
properties have been removed. Instead, pass the formating options directly to thetoLatex()
andtoMathJson()
methods. Thece.parse()
method now takes an optional argument to specify the format of the input string. -
The default JSON serialization of an expression has changed.
Previously, the default JSON serialization, accessed via the
.json
property, had some transformations applied to it (sugaring) to make the JSON more human readable.For example,
ce.parse("\frac12").json
would return the symbol"Half"
instead of["Divide", 1, 2]
.However, this could lead to some confusion when manipulating the JSON directly. Since the JSON is intended to be used by machine more than humans, these additional transformations have been removed.
The
expr.json
property now returns the JSON representing the expression, without any transformations.To get a version of JSON with some transformations applied use the
ce.toMathJson()
function.expr = ce.box(["Subtract", 1, ["Square", "x"]]); console.log(expr.json); // -> ["Add", 1, ["Negate", ["Power", "x", 2]]] expr.toMathJson() // -> ["Subtract", 1, ["Square", "x"]] expr.toMathJson({exclude: "Square"}) // -> ["Subtract", 1, ["Power", "x", 2]]
In practice, the impact of both of these changes should be minimal. If you were manipulating expressions using
BoxedExpression
, the new canonical form should make it easier to manipulate expressions. You can potentially simplify your code by removing special cases for functions such asSquare
andSubtract
.If you were using the JSON serialization directly, you may also be able to simplify you code since the default output from
expr.json
is now more consistent and simpler. -
The name of some number formatting options has changed. The number formatting options are an optional argument of
ce.parse()
andce.toLatex()
. See the
NumberFormat
andNumberSerializationFormat
types. -
The values +infinity, -infinity and NaN are now represented preferably with the symbols
PositiveInfinity
,NegativeInfinity
andNaN
respectively. Previously they were represented with numeric values, i.e.{num: "+Infinity"}
,{num: "-Infinity"}
and{num: "NaN"}
. The numeric values are still supported, but the symbols are preferred. -
The method
expr.isNothing
has been removed. Instead, useexpr.symbol === "Nothing"
.
-
When serializing to LaTeX, the output can be "prettified". This involves modifying the LaTeX output to make it more pleasant to read, for example:
a+\\frac{-b}{c}
->a-\\frac{b}{c}
a\\times b^{-1}
->\\frac{a}{b}
\\frac{a}{b}\\frac{c}{d}
->\\frac{a\\cdot c}{b\\cdot d}
--2
->2
This is on by default and can be turned off by setting the
prettify
option tofalse
. For example:ce.parse("a+\\frac{-b}{c}").toLatex({prettify: true}) // -> "a-\\frac{b}{c}" ce.parse("a+\\frac{-b}{c}").toLatex({prettify: false}) // -> "a+\\frac{-b}{c}"
-
Numbers can have a different digit group length for the whole and fractional part of a number. For example,
ce.toLatex(ce.parse("1234.5678"), {digitGroup: [3, 0]})
will return1\,234.5678
. -
Numbers can now be formatted using South-East Asian Numbering System, i.e. lakh and crore. For example:
ce.toLatex(ce.parse("12345678"), {digitGroup: "lakh"}) // -> "1,23,45,678"
-
Expressions with Integrate functions can now be compiled to JavaScript. The compiled function can be used to evaluate the integral numerically. For example:
const f = ce.parse("\\int_0^1 x^2 dx"); const compiled = f.compile(); console.log(compiled()); // -> 0.33232945619482307
-
#82 Support for angular units. The default is radians, but degrees can be used by setting
ce.angularUnit = "deg"
. Other possible values are "grad" and "turn". This affects how unitless numbers with a trigonometric function are interpreted. For example,sin(90)
will return 1 whence.angularUnit
is "deg", 0.8939966636005579 whence.angularUnit
is "grad" and 0 whence.angularUnit
is "turn". -
Added
expr.map(fn)
method to apply a function to each subexpression of an expression. This can be useful to apply custom canonical forms and compare two expressions. -
An optional canonical form can now be specified with the
ce.function()
.
- #173 Parsing
1++2
would result in an expression with aPreIncrement
function. It is now correctly parsed as["Add", 1, 2]
. - #161 Power expressions would not be processed when their argument was a Divide expression.
- #165 More aggressive simplification of expressions with exponent greater than 3.
- #169 Calculating a constant integral (and integral that did not depend on the variable) would result in a runtime error.
- #164 Negative mixed fractions (e.g.
-1\frac23
) are now parsed correctly. - #162 Numeric evaluation of expressions with large exponents could result in machine precision numbers instead of bignum numbers.
- #155 The expression
["Subtract", ["Multiply", 0.5, "x"], ["Divide", "x", 2]]
will now evaluate to0
. - #154 In some cases, parsing implicit argument of trig function return more
natural results, for example
\cos a \sin b
is now parsed as(\cos a)(\sin b)
and not\cos (a \sin b)
. - #147 The associativity of some operators, including
/
was not applied correctly, resulting in unexpected results. For example,1/2/3
would be parsed as["Divide", 1, ["Divide", 2, 3]]
instead of["Divide", ["Divide", 1, 2], 3]
. - #146 When parsing an expression like
x(x+1)
wherex
is an undeclared symbol, do not infer thatx
is a function. Instead, infer thatx
is a variable and that the expression is a product. - #145 The expression
["Or", "False", "False"]
, that is when all the arguments areFalse
, is now evaluates toFalse
. - Fixed canonical form of
e^x^2
, and more generally apply power rule in more cases. - Added missing "Sech" and "Csch" functions.
- The digit grouping serializing would place the separator in the wrong place for some numbers.
- The
avoidExponentsInRange
formating option would not always avoid exponents in the specified range.
- Fix parsing of very deeply nested expressions.
- Correctly apply rules to deeply nested expressions.
expr.print()
now correctly prints the expression when using the minified version of the library.expr.isEqual()
now correctly compares equalities and inequalities.expr.match()
has been improved and works correctly in more cases. The signature of thematch
function has been changed so that the pattern is the first argument, i.e. instead ofpattern.match(expr)
useexpr.match(pattern)
.- Fix
expr.print()
when using the minified version of the library. - #142 Accept complex expressions as the subcript of
\ln
and\log
in LaTeX. - #139 Parse quantifiers
\forall
and\exists
in LaTeX.
- Using a custom canonical order of
"Multiply"
would not distribute theNegate
function. - #141 The canonical form
"Order"
was applied to non-commutative functions.
- Added
ExpandAll
function to expand an expression recursively. - Added
Factor
function to factor an expression. - Added
Together
function to combine rational expressions into a single fraction.
- The expression
\frac5 7
is now parsed correctly as\frac{5}{7}
instead of\frac{5}{}7
. - Do not sugar non-canonical expression. Previously,
ce.parse('\\frac{1}{2}', {canonical: false})
would returnHalf
instead of['Divide', '1', '2']
. - #132 Attempting to set a value to 0 with
ce.defineSymbol("count", {value: 0})
would fail: the symbol would be undefined. - Correctly evaluate power expressions in some cases, for example
(\sqrt2 + \sqrt2)^2
. - Comparison of expressions containing non-exact numbers could fail. For
example:
2(13.1+3.1x)
and26.2+6.2x
would not be considered equal.
- Significant improvements to symbolic computation. Now, boxing, canonicalization and evaluation are more consistent and produce more predictable results.
- Adedd the
\neg
command, synonym for\lnot
->Not
. - Relational expressions (inequalities, etc...) are now properly factored.
- Integers are now factored when simplifying, i.e.
2x = 4x
->x = 2x
.
-
Rule Syntax
The syntax to describe rules has changed. The syntax for a rule was previously a tuple
[lhs, rhs, {condition} ]
. The new syntax is an object with the propertiesmatch
,replace
andcondition
. For example:- previous syntax:
[["Add", "_x", "_x"], ["Multiply", 2, "_x"]]
- new syntax:
{match: ["Add", "_x", "_x"], replace: ["Multiply", 2, "_x"]}
The
condition
property is optional, and is either a boxed function or a JavaScript function. For example, to add a condition that checks that_x
is a number literal:{ match: ["Add", "_x", "_x"], replace: ["Multiply", 2, "_x"], condition: ({_x}) => _x.isNumberLiteral }
- previous syntax:
-
CanonicalForm
The
CanonicalOrder
function has been replaced by the more flexibleCanonicalForm
function. TheCanonicalForm
function takes an expression and a list of transformations to apply. To apply the same transformations asCanonicalOrder
, use:['CanonicalForm', expr, 'Order']
These canonical forms can also be specified with
box()
andparse()
options:ce.box(expr, { canonical: "Order" }); ce.parse("x^2 + 2x + 1", { canonical: "Order" });
- Linear algebra functions:
Rank
,Shape
,Reshape
,Flatten
,Determinant
,Trace
,Transpose
,ConjugateTranspose
,Inverse
. See the Linear Algebra reference guide. Some of these function may not yet return correct result in all cases.
- Added a
expr.print()
method as a synonym forconsole.log(expr.toString())
. - Added an
exact
option (false by default) to theexpr.match()
pattern matching method. Whentrue
some additional patterns are automatically recognized, for example,x
will match["Multiply", '_a', 'x']
whenexact
isfalse
, but not whenexact
istrue
.
- The equation solver used by
expr.solve()
has been improved and can now solve more equations. - The pattern matching engine has been improved and can now match more expressions, including sequences for commutative functions.
-
#125 Parse and serialize environemnts, i.e.
\begin{matrix} 1 & 2 \\ 3 & 4 \end{matrix}
will be parsed as["Matrix", ["List", ["List", 1, 2], ["List", 3, 4]]]
.A new section on Linear Algebra has some details on the supported formats.
The linear algebra operations are limited at the moment, but will be expanded in the future.
-
Added
IsSame
function, which is the function expression corresponding toexpr.isSame()
. -
AddedCanonicalOrder
function, which sorts the arguments of commutative functions into canonical order. This is useful to compare two non-canonical expressions for equality.
ce.box(["CanonicalOrder", ["Add", 1, "x"]]).isSame(
ce.box(["CanonicalOrder", ["Add", "x", 1]])
);
// -> true
- When evaluating a sum (
\sum
) with a bound that is not a number, return the sum expression instead of an error.
- Fixed numerical evaluation of integrals and limits when parsed from LaTeX.
console.info(ce.parse("\\lim_{x \\to 0} \\frac{\\sin(x)}{x}").value);
// -> 1
console.info(ce.parse("\\int_{0}^{2} x^2 dx").value);
// -> 2.6666666666666665
- Fixed evaluation of functions with multiple arguments
- Fixed compilation of some function assignments
- Improved serialization of function assignment
-
Architectural changes: the invisible operator is used to represent the multiplication of two adjacent symbols, i.e.
2x
. It was previously handled during parsing, but it is now handled during canonicalization. This allows more complex syntactic structures to be handled correctly, for examplef(x) := 2x
: previously, the left-hand-side argument would have been parsed as a function application, while in this case it should be interpreted as a function definition.A new
InvisibleOperator
function has been added to support this.The
applyInvisibleOperator
parsing option has been removed. To support custom invisible operators, use theInvisibleOperator
function.
- #25 Correctly parse chained relational operators, i.e.
a < b <= c
- #126 Logic operators only accepted up to two arguments.
- #127 Correctly compile
Log
with bases other than 10. - Correctly parse numbers with repeating patterns but no fractional digits, i.e.
0.(1234)
- Correctly parse
|1+|a|+2|
- Function assignment can now be done with this syntax:
f(x) := 2x+1
. This syntax is equivalent tof := x -> 2x+1
. - Implement the
Mod
andCongruent
function. - Correctly parse
11 \bmod 5
(Mod
) and26\equiv 11 \pmod5
(Congruent
) - Better handle empty argument lists, i.e.
f()
- When a function is used before being declared, infer that the symbol is a
function, e.g.
f(12)
will infer thatf
is a function (and not a variablef
multiplied by 12) - When a constant is followed by some parentheses, don't assume this is a
function application, e.g.
\pi(3+n)
is now parsed as["Multiply", "Pi", ["Add", 3, "n"]]
instead of["Pi", ["Add", 3, "n"]]
- Improved parsing of nested lists, sequences and sets.
- Improved error messages when syntax errors are encountered during LaTeX parsing.
- When parsing with the canonical option set to false, preserve more closely the original LaTeX syntax.
- When parsing text strings, convert some LaTeX commands to Unicode, including
spacing commands. As a result,
ce.parse("\\text{dead\;beef}_{16}")
correctly gets evaluated to 3,735,928,559.
- Assigning a function to an indentifier works correctly now, i.e.
ce.parse("\\operatorname{f} := x \\mapsto 2x").evaluate();
- The
domain
property of the function definitionsignature
is deprecated and replaced with theparams
,optParams
,restParam
andresult
properties instead. Thedomain
property is still supported for backward compatibility, but will be removed in a future version.
- When invoking a declared function in a numeric operation, correctly infer the result type.
["Assign", "f", ["Add", "_", 1]]
["Add", ["f", 1], 1]
// -> 3
Previously a domain error was returned, now f
is inferred to have a numeric
return type.
- Fixed a runtime error when inverting a fraction, i.e.
\frac{3}{4}^{-1}
- The tangent of π/2 now correctly returns
ComplexInfinity
. - The exact values of some constructible trigonometric operations (e.g.
\tan 18\degree = \frac{\sqrt{25-10\sqrt5}}{5}
) returned incorrect results. The unit test case was incorrect and did not detect the problem. The unit test case has been fixed and the returned values are now correct.
- Implemented
Union
andIntersection
of collections, for example:
["Intersection", ["List", 3, 5, 7], ["List", 2, 5, 9]]
// -> ["Set", 5]
["Union", ["List", 3, 5, 7], ["List", 2, 5, 9]]
// -> ["Set", 3, 5, 7, 2, 9]
-
Parse ranges, for example
1..5
or1, 3..10
. Ranges are collections and can be used anywhere collections can be used. -
The functions
Sum
,Product
,Min
,Max
, and the statistics functions (Mean
,Median
,Variance
, etc...) now handle collection arguments: collections:["Range"]
,["Interval"]
,["Linspace"]
expressions["List"]
or["Set"]
expressions["Tuple"]
,["Pair"]
,["Pair"]
,["Triple"]
expressions["Sequence"]
expressions
-
Most mathematical functions are now threadable, that is their arguments can be collections, for example:
["Sin", ["List", 0, 1, 5]]
// -> ["List", 0, 0.8414709848078965, -0.9589242746631385]
["Add", ["List", 1, 2], ["List", 3, 4]]
// -> ["List", 4, 6]
- Added
GCD
andLCM
functions
["GCD", 10, 5, 15]
// -> 5
["LCM", 10, 5, 15]
// -> 30
-
Added
Numerator
,Denominator
,NumeratorDenominator
functions. These functions can be used on non-canonical expressions. -
Added
Head
andTail
functions which can be used on non-canonical expressions. -
Added
display-quotient
andinline-quotient
style for formatting of division expressions in LaTeX.
- Improved parsing of
\degree
command
ce.parse("30\\degree)
// -> ["Divide", "Pi", 6]
- Improved interoperability with JavaScript:
expr.value
will return a JavaScript primitive (number
,boolean
,string
, etc...) when possible. This is a more succinct version ofexpr.N().valueOf()
.
- Parsing of whole numbers while in
rational
mode would return incorrect results. - The
ND
function to evaluate derivatives numerically now return correct values.
ce.parse("\\mathrm{ND}(x \\mapsto 3x^2+5x+7, 2)").N();
// -> 17.000000000001
- Speed up
NIntegrate
by temporarily switching the numeric mode tomachine
while computing the Monte Carlo approximation.
- Expanded LaTeX dictionary with
\max
,\min
,\sup
,\inf
and\lim
functions - Added
Supremum
andInfimum
functions - Compilation of
Block
expressions, local variables, return statements and conditionalsIf
. - Added numerical evaluation of limits with
Limit
functions andNLimit
functions, using a Richardson Extrapolation.
console.info(ce.parse("\\lim_{x\\to0} \\frac{\\sin x}{x}").N().json);
// -> 1
console.info(
ce.box(["NLimit", ["Divide", ["Sin", "_"], "_"], 0]).evaluate().json
);
// -> 1
console.info(ce.parse("\\lim_{x\\to \\infty} \\cos \\frac{1}{x}").N().json);
// -> 1
-
Added
Assign
andDeclare
functions to assign values to symbols and declare symbols with a domain. -
Block
evaluations with local variables work now. For example:
ce.box(["Block", ["Assign", "c", 5], ["Multiply", "c", 2]]).evaluate().json;
// -> 10
-
When decimal numbers are parsed they are interpreted as inexact numbers by default, i.e. "1.2" ->
{num: "1.2"}
. To force the number to be interpreted as a rational number, setce.latexOptions.parseNumbers = "rational"
. In that case, "1.2" ->["Rational", 12, 10]
, an exact number.While regular decimals are considered "inexact" numbers (i.e. they are assumed to be an approximation), rationals are assumed to be exact. In most cases, the safest thing to do is to consider decimal numbers as inexact to avoid introducing errors in calculations. If you know that the decimal numbers you parse are exact, you can use this option to consider them as exact numbers.
- LaTeX parser: empty superscripts are now ignored, e.g.
4^{}
is interpreted as4
.
- The
Nothing
domain has been renamed toNothingDomain
- The
Functions
,Maybe
,Sequence
,Dictionary
,List
andTuple
domain constructors have been renamed toFunctionOf
,OptArg
,VarArg
,DictionaryOf
,ListOf
andTupleOf
, respectively. - Domains no longer require a
["Domain"]
expression wrapper, so for examplece.box("Pi").domain
returns"TranscendentalNumbers"
instead of["Domain", "TranscendentalNumbers"]
. - The
VarArg
domain constructor now indicates the presence of 0 or more arguments, instead of 1 or more arguments. - The
MaybeBooleans
domain has been dropped. Use["Union", "Booleans", "NothingDomain"]
instead. - The
ce.defaultDomain
has been dropped. The domain of a symbol is now determined by the context in which it is used, or by thece.assume()
method. In some circumstances, the domain of a symbol can beundefined
.
- Symbolic derivatives of expressions can be calculated using the
D
function. For example,ce.box(["D", ce.parse("x^2 + 3x + 1"), "x"]).evaluate().latex
returns"2x + 3"
.
- Some frequently used expressions are now available as predefined constants,
for example
ce.Pi
,ce.True
andce.Numbers
. - Improved type checking and inference, especially for functions with complicated or non-numeric signatures.
- Invoking a function repeatedly would invoke the function in the original scope rather than using a new scope for each invocation.
- The methods
ce.let()
andce.set()
have been renamed toce.declare()
andce.assign()
respectively. - The method
ce.assume()
requires a predicate. - The signatures of
ce.assume()
andce.ask()
have been simplified. - The signature of
ce.pushScope()
has been simplified. - The
expr.freeVars
property has been renamed toexpr.unknowns
. It returns the identifiers used in the expression that do not have a value associated with them. Theexpr.freeVariables
property now return the identifiers used in the expression that are defined outside of the local scope and are not arguments of the function, if a function.
-
Domain Inference when the domain of a symbol is not set explicitly (for example with
ce.declare()
), the domain is inferred from the value of the symbol or from the context of its usage. -
Added
Assume
,Identity
,Which
,Parse
,N
,Evaluate
,Simplify
,Domain
. -
Assignments in LaTeX:
x \\coloneq 42
produce["Assign", "x", 42]
-
Added
ErfInv
(inverse error function) -
Added
Factorial2
(double factorial)
-
Functions can now be defined:
- using
ce.assign()
orce.declare()
- evaluating LaTeX:
(x, y) \mapsto x^2 + y^2
- evaluating MathJSON:
["Function", ["Add", ["Power", "x", 2], ["Power", "y", 2]]], "x", "y"]
- using
-
Function can be applied using
\operatorname{apply}
or the operators\rhd
and\lhd
:\operatorname{apply}(f, x)
f \rhd x
x \lhd f
See Adding New Definitions and Functions.
- Added
FixedPoint
,Block
,If
,Loop
- Added
Break
,Continue
andReturn
statements
- Added numeric approximation of derivatives, using an 8-th order centered
difference approximation, with the
ND
function. - Added numeric approximation of integrals, using a Monte Carlo method with
rebasing for improper integrals, with the
NIntegrate
function - Added symbolic calculation of derivatives with the
D
function.
Added support for collections such as lists, tuples, ranges, etc...
See Collections
Collections can be used to represent various data structures, such as lists, vectors, matrixes and more.
They can be iterated, sliced, filtered, mapped, etc...
["Length", ["List", 19, 23, 5]]
// -> 3
["IsEmpty", ["Range", 1, 10]]
// -> "False"
["Take", ["Linspace", 0, 100, 50], 4]
// -> ["List", 0, 2, 4, 6]
["Map", ["List", 1, 2, 3], ["Function", "x", ["Power", "x", 2]]]
// -> ["List", 1, 4, 9]
["Exclude", ["List", 33, 45, 12, 89, 65], -2, 2]
// -> ["List", 33, 12, 65]
["First", ["List", 33, 45, 12, 89, 65]]
// -> 33
- The documentation has been significantly rewritten with help from an AI-powered writing assistant.
- The LaTeX string returned in
["Error"]
expression was incorrectly tagged asLatex
instead ofLatexString
.
- The
ce.serialize()
function now takes an optionalcanonical
argument. Set it tofalse
to prevent some transformations that are done to produce more readable LaTeX, but that may not match exactly the MathJSON. For example, by defaultce.serialize(["Power", "x", -1])
returns\frac{1}{x}
whilece.serialize(["Power", "x", -1], {canonical: false})
returnsx^{-1}
. - Improved parsing of delimiters, i.e.
\left(
,\right]
, etc... - Added complex functions
Real
,Imaginary
,Arg
,Conjugate
,AbsArg
. See Complex - Added parsing and evaluation of
\Re
,\Im
,\arg
,^\star
(Conjugate). - #104 Added the
["ComplexRoots", x, n]
function which returns the nthroot ofx
. - Added parsing and evaluation of statistics functions
Mean
,Median
,StandardDeviation
,Variance
,Skewness
,Kurtosis
,Quantile
,Quartiles
,InterquartileRange
,Mode
,Count
,Erf
,Erfc
. See Statistics
- The entries in the LaTeX syntax dictionary can now have LaTeX triggers
(
latexTrigger
) or triggers based on identifiers (symbolTrigger
). The former replaces thetrigger
property. The latter is new. An entry with atriggerIdentifier
ofaverage
will match\operatorname{average}
,\mathrm{average}
and other variants. - The
ce.latexOptions
andce.jsonSerializationOptions
properties are more robust. They can be modified directly or one of their properties can be modified.
-
Added more functions and symbols supported by
expr.compile()
:Factorial
postfix operator5!
Gamma
function\Gamma(2)
LogGamma
function\operatorname{LogGamma}(2)
Gcd
function\operatorname{gcd}(20, 5)
Lcm
function\operatorname{lcm}(20, 5)
Chop
function\operatorname{chop}(0.00000000001)
Half
constant\frac{1}{2}
- 'MachineEpsilon' constant
GoldenRatio
constantCatalanConstant
constantEulerGamma
constant\gamma
Max
function\operatorname{max}(1, 2, 3)
Min
function\operatorname{min}(13, 5, 7)
- Relational operators:
Less
,Greater
,LessEqual
,GreaterEqual
, 'Equal', 'NotEqual' - Some logical operators and constants:
And
,Or
,Not
,True
,False
-
More complex identifiers syntax are recognized, including
\mathbin{}
,\mathord{}
, etc...\operatorname{}
is the recommended syntax, though: it will display the identifier in upright font and with the propert spacing, and is properly enclosing. Some commands, such as\mathrm{}
are not properly enclosing: two adjacent\mathrm{}
command could be merged into one. -
Environments are now parsed and serialized correctly.
-
When parsing LaTeX, function application is properly handled in more cases, including custom functions, e.g.
f(x)
-
When parsing LaTeX, multiple arguments are properly handled, e.g.
f(x, y)
-
Add LaTeX syntax for logical operators:
And
:\land
,\operatorname{and}
(infix or function)Or
:\lor
,\operatorname{or}
(infix or function)Not
:\lnot
,\operatorname{not}
(prefix or function)Xor
:\veebar
(infix)Nand
:\barwedge
(infix)Nor
:^^^^22BD
(infix)Implies
:\implies
(infix)Equivalent
:\iff
(infix)
-
When a postfix operator is defined in the LaTeX syntax dictionary of the form
^
plus a single token, a definition with braces is added automatically so that both forms will be recognized. -
Extended the LaTeX dictionary with:
floor
ceil
round
sgn
exp
abs
gcd
lcm
apply
-
Properly handle inverse and derivate notations, e.g.
\sin^{-1}(x)
,\sin'(x)
,\cos''(x)
, \cos^{(4)}(x)or even
\sin^{-1}''(x)`
- Compilation Some expressions can be compiled to Javascript. This is useful
to evaluate an expression many times, for example in a loop. The compiled
expression is faster to evaluate than the original expression. To get the
compiled expression, use
expr.compile()
. Read more at Compiling
- Fixed parsing and serialization of extended LaTeX synonyms for
e
andi
. - Fixed serialization of
Half
. - Fixed serialization of
Which
- Improved serialization of
["Delimiter"]
expressions.
- Made customization of the LaTeX dictionary simpler. The
ce.latexDictionary
property can be used to access and modify the dictionary. The documentation has been updated.
- New API for the
Parser
class.
- The
ComputeEngine
now exports thebignum()
andcomplex()
methods that can be used to create bignum and complex numbers from strings or numbers. The methodsisBigNum()
andisComplex()
have also been added to check if a value is a bignum (Decimal
) or complex (Complex
) number, for example as returned byexpr.numericValue
. - #69
\leq
was incorrectly parsed asEquals
instead ofLessEqual
- #94 The
\exp
command was not parsed correctly. - Handle
PlusMinus
in infix and prefix position, i.e.a\pm b
and\pm a
. - Improved parsing, serialization
- Improved simplification
- Improved evaluation of
Sum
andProduct
- Support complex identifiers (i.e. non-latin scripts, emojis).
- Fixed serialization of mixed numbers.
Work around unpckg.com issue with libraries using BigInt.
- The
expr.symbols
property return an array ofstring
. Previously it returned an array ofBoxedExpression
.
- Rewrote the rational computation engine to use JavaScript
bigint
instead ofDecimal
instances. Performance improvements of up to 100x. expr.freeVars
provides the free variables in an expression.- Improved performance of prime factorization of big num by x100.
- Added
["RandomExpression"]
- Improved accuracy of some operations, for example
expr.parse("1e999 + 1").simplify()
- When
ce.numericMode === "auto"
, square roots of negative numbers would return an expression instead of a complex number. - The formatting of LaTeX numbers when using
ce.latexOptions.notation = "engineering"
or"scientific"
was incorrect. - The trig functions no longer "simplify" to the less simple exponential formulas.
- The canonical order of polynomials now orders non-lexicographic terms of degree 1 last, i.e. "ax^2+ bx+ c" instead of "x + ax^2 + bx".
- Fixed evaluation of inverse functions
- Fixed
expr.isLess
,expr.isGreater
,expr.isLessEqual
,expr.isGreaterEqual
and["Min"]
,["Max"]
- The signature of
ce.defineSymbol()
,ce.defineFunction()
andce.pushScope()
have changed
- When a constant should be held or substituted with its value can now be more
precisely controlled. The
hold
symbol attribute is nowholdUntil
and can specify at which stage the substitution should take place.
- Some constants would return a value as bignum or complex even when the
numericMode
did not allow it. - Changing the value or domain of a symbol is now correctly taken into account.
Changes can be made with
ce.assume()
,ce.set()
orexpr.value
. - When a symbol does not have a value associated with it, assumptions about it (e.g. "x > 0") are now correctly tracked and reflected.
expr.isLiteral
has been removed. Useexpr.numericValue !== null
andexpr.string !== null
instead.
- Calling
ce.forget()
would not affect expressions that previously referenced the symbol.
- More accurate calculations of some trig functions when using bignums.
- Improved performance when changing a value with
ce.set()
. Up to 10x faster when evaluating a simple polynomial in a loop. ce.strict
can be set tofalse
to bypass some domain and validity checks.
- The head of a number expression is always
Number
. Useexpr.domain
to be get more specific info about what kind of number this is. - By default,
ce.box()
andce.parse()
return a canonical expression. A flag can be used if a non-canonical expression is desired. - The API surface of
BoxedExpression
has been reduced. The propertiesmachineValue
,bignumValue
,asFloat
,asSmallInteger
,asRational
etc... have been replaced with a singlenumericValue
property. parseUnknownSymbol
is nowparseUnknownIdentifier
-
Support angles in degrees with
30\degree
,30^\circ
and\ang{30}
. -
More accurate error expressions, for example if there is a missing closing delimiter an
["Error", ["ErrorCode", "'expected-closing-delimiter'", "')'"]]
is produced. -
["Expand"]
handles more cases -
The trig functions can now have a regular exponent, i.e.
\cos^2(x)
in addition to-1
for inverse, and a combination of\prime
,\doubleprime
and'
for derivatives. -
ce.assume()
handle more expressions and can be used to define new symbols by domain or value. -
Better error message when parsing, e.g.
\sqrt(2)
(instead of\sqrt{2}
) -
Better simplification for square root expressions:
\sqrt{25x^2}
->5x
-
Improved evaluation of
["Power"]
expressions, including for negative arguments and non-integer exponents and complex arguments and exponents. -
Added
Arccot
,Arcoth
,Arcsch
,Arcscc
,Arsech
andArccsc
-
expr.solve()
returns result for polynomials of order up to 2. -
The
pattern.match()
function now work correctly for commutative functions, i.e.ce.pattern(['Add', '_a', 'x']).match(ce.parse('x+y')) -> {"_a": "y"}
-
Added
ce.let()
andce.set()
to declare and assign values to identifiers. -
Preserve exact calculations involving rationals or square root of rationals.
\sqrt{\frac{49}{25}}
->\frac{7}{5}
-
Addition and multiplication provide more consistent results for
evaluate()
andN()
. Evaluate returns an exact result when possible.- EXACT
- 2 + 5 -> 7
- 2 + 5/7 -> 19/7
- 2 + √2 -> 2 + √2
- 2 + √(5/7) -> 2 + √(5/7)
- 5/7 + 9/11 -> 118/77
- 5/7 + √2 -> 5/7 + √2
- 10/14 + √(18/9) -> 5/7 + √2
- √2 + √5 -> √2 + √5
- √2 + √2 -> 2√2
- sin(2) -> sin(2)
- sin(π/3) -> √3/2
- APPROXIMATE
- 2 + 2.1 -> 4.1
- 2 + √2.1 -> 3.44914
- 5/7 + √2.1 -> 2.16342
- sin(2) + √2.1 -> 2.35844
- EXACT
-
More consistent behavior of the
auto
numeric mode: calculations are done withbignum
andcomplex
in most cases. -
JsonSerializationOptions
has a new option to specify the numeric precision in the MathJSON serialization. -
Shorthand numbers can now be strings if they do not fit in a float-64:
// Before
["Rational", { "num": "1234567890123456789"}, { "num": "2345678901234567889"}]
// Now
["Rational", "1234567890123456789", "2345678901234567889"]
\sum
is now correctly parsed and evaluated. This includes creating a local scope with the index and expression value of the sum.
- The parsing and evaluation of log functions could produce unexpected results
- The
\gamma
command now correctly maps to["Gamma"]
- Fixed numeric evaluation of the
["Gamma"]
function when using bignum - #57 Substituting
0
(i.e. withexpr.subs({})
) did not work. - #60 Correctly parse multi-char symbols with underscore, i.e.
\mathrm{V_a}
- Parsing a number with repeating decimals and an exponent would drop the exponent.
- Correct calculation of complex square roots
\sqrt{-49}
->7i
- Calculations were not always performed as bignum in
"auto"
numeric mode if the precision was less than 15. Now, if the numeric mode is"auto"
, calculations are done as bignum or complex numbers. - If an identifier contained multiple strings of digits, it would not be
rendered to LaTeX correctly, e.g.
V20_20
. - Correctly return
isReal
for real numbers
-
Corrected the implementation of
expr.toJSON()
,expr.valueOf()
and added the esoteric[Symbol.toPrimitive]()
method. These are used by JavaScript when interacting with other primitive types. A major change is thatexpr.toJSON()
now returns anExpression
as an object literal, and not a string serialization of theExpression
. -
Changed from "decimal" to "bignum". "Decimal" is a confusing name, since it is used to represent both integers and floating point numbers. Its key characteristic is that it is an arbitrary precision number, aka "bignum". This affects
ce.numericMode
which now usesbignum
instead ofdecimal',
expr.decimalValue->
expr.bignumValue,
decimalValue()->
bignumValue()`
- Numerical evaluation of expressions containing complex numbers when in
decimal
orauto
mode produced incorrect results. Example:e^{i\\pi}
- The
ce.latexOptions.preserveLatex
default value is nowfalse
- The first argument of the
["Error"]
expression (default value) has been dropped. The first argument is now an error code, either as a string or an["ErrorCode"]
expression.
- Much improved LaTeX parser, in particular when parsing invalid LaTeX. The
parser now avoids throwing, but will return a partial expression with
["Error"]
subexpressions indicating where the problems were. - Implemented new domain computation system (similar to type systems in programming languages)
- Added support for multiple signatures per function (ad-hoc polymorphism)
- Added
FixedPoint
,Loop
,Product
,Sum
,Break
,Continue
,Block
,If
,Let
,Set
,Function
,Apply
,Return
- Added
Min
,Max
,Clamp
- Parsing of
\sum
,\prod
,\int
. - Added parsing of log functions,
\lb
,\ln
,\ln_{10}
,\ln_2
, etc... - Added
expr.
subexpressions,
expr.getSubexpressions(),
expr.errors,
expr.symbols,
expr.isValid`. - Symbols can now be used to represent functions, i.e.
ce.box('Sin').domain
correctly returns["Domain", "Function"]
. - Correctly handle rational numbers with a numerator or denominator outside the range of a 64-bit float.
- Instead of a
Missing
symbol an["Error", "'missing'"]
expression is used. - Name binding is now done lazily
- Correctly handle MathJSON numbers with repeating decimals, e.g.
1.(3)
. - Correctly evaluate inverse functions, e.g.
ce.parse('\\sin^{-1}(.5)).N()
- Fixed some LaTeX serialization issues
Read more at Core Reference and [Arithmetic Reference] (https://cortexjs.io/compute-engine/reference/arithmetic/)
- #43 If the input of
ce.parse()
is an empty string, return an empty string forexpr.latex
orexpr.json.latex
: that is, ensure verbatim LaTeX round-tripping - Evaluating some functions, such as
\arccos
would result in a crash - Correctly handle parsing of multi-token decimal markers, e.g.
{,}
- Parse more cases of tabular environments
- Handle simplify and evaluate of inert functions by default
- Avoid unnecessary wrapping of functions when serializing LaTeX
- Parse arguments of LaTeX commands (e.g.
\vec{}
) - #42 Export static
ComputeEngine.getLatexDictionary
- Parse multi-character constants and variables, e.g.
\mathit{speed}
and\mathrm{radius}
- Parse/serialize some LaTeX styling commands:
\displaystyle
,\tiny
and more
- Correctly parse tabular content (for example in
\begin{pmatrix}...\end{pmatrix}
- Correctly parse LaTeX groups, i.e.
{...}
- Ensure constructible trigonometric values are canonical
- Correct and simplify evaluation loop for
simplify()
,evaluate()
andN()
. - #41 Preserve the parsed LaTeX verbatim for top-level expressions
- #40 Correctly calculate the synthetic LaTeX metadata for numbers
- Only require Node LTS (16.14.2)
- Improved documentation, including Dark Mode support
Release Date: 2022-03-27
- Added option to specify custom LaTeX dictionaries in
ComputeEngine
constructor expr.valueOf
returns rational numbers as[number, number]
when applicable- The non-ESM builds (
compute-engine.min.js
) now targets vintage JavaScript for improved compatibility with outdated toolchains (e.g. Webpack 4) and environments. The ESM build (compute-engine.min.esm.js
) targets evergreen JavaScript (currently ECMAScript 2020).
Release Date: 2022-03-21
The API has changed substantially between 0.4.2 and 0.4.3, however adapting code to the new API is very straightforward.
The two major changes are the introduction of the BoxedExpression
class and
the removal of top level functions.
The BoxedExpression
class is a immutable box (wrapper) that encapsulates a
MathJSON Expression
. It provides some member functions that can be used to
manipulate the expression, for example expr.simplify()
or expr.evaluate()
.
The boxed expresson itself is immutable. For example, calling expr.simplify()
will return a new, simplified, expression, without modifying expr
.
To create a "boxed" expression from a "raw" MathJSON expression, use ce.box()
.
To create a boxed expression from a LaTeX string, use ce.parse()
.
To access the "raw" MathJSON expression, use the expr.json
property. To
serialize the expression to LaTeX, use the expr.latex
property.
The top level functions such as parse()
and evaluate()
are now member
functions of the ComputeEngine
class or the BoxedExpression
class.
There are additional member functions to examine the content of a boxed
expression. For example, expr.symbol
will return null
if the expression is
not a MathJSON symbol, otherwise it will return the name of the symbol as a
string. Similarly, expr.ops
return the arguments (operands) of a function,
expr.asFloat
return null
if the expression does not have a numeric value
that can be represented by a float, a number
otherwise, etc...
Use expr.canonical
to obtain the canonical form of an expression rather than
the ce.format()
method.
The canonical form is less aggressive in its attempt to simplify than what was
performed by ce.format()
.
The canonical form still accounts for distributive and associative functions,
and will collapse some integer constants. However, in some cases it may be
necessary to invoke expr.simplify()
in order to get the same results as
ce.format(expr)
.
In addition to machine floating points, arbitrary precision numbers and complex numbers, the Compute Engine now also recognize and process rational numbers.
This is mostly an implementation detail, although you may see
["Rational", 3, 4]
, for example, in the value of a expr.json
property.
If you do not want rational numbers represented in the value of the .json
property, you can exclude the Rational
function from the serialization of JSON
(see below) in which case Divide
will be used instead.
Note also that internally (as a result of boxing), Divide
is represented as a
product of a power with a negative exponent. This makes some pattern detection
and simplifications easier. However, when the .json
property is accessed,
product of powers with a negative exponents are converted to a Divide
, unless
you have included Divide
as an excluded function for serialization.
Similarly, Subtract
is converted internally to Add
, but may be serialized
unless excluded.
Rather than using a separate instance of the LatexSyntax
class to customize
the parsing or serialization, use a ComputeEngine
instance and its
ce.parse()
method and the expr.latex
property.
Custom dictionaries (to parse/serialize custom LaTeX syntax) can be passed as an
argument to the ComputeEngine
constructor.
For more advanced customizations, use ce.latexOptions = {...}
. For example, to
change the formatting options of numbers, how the invisible operator is
interpreted, how unknown commands and symbols are interpreted, etc...
Note that there are also now options available for the "serialization" to
MathJSON, i.e. when the expr.json
property is used. It is possible to control
for example if metadata should be included, if shorthand forms are allowed, or
whether some functions should be avoided (Divide
, Sqrt
, Subtract
, etc...).
These options can be set using ce.jsonSerializationOptions = {...}
.
There are more options to compare two expressions.
Previously, match()
could be used to check if one expression matched another
as a pattern.
If match()
returned null
, the first expression could not be matched to the
second. If it returned an object literal, the two expressions matched.
The top-level match()
function is replaced by the expr.match()
method.
However, there are two other options that may offer better results:
expr.isSame(otherExpr)
return true ifexpr
andotherExpr
are structurally identical. Structural identity is closely related to the concept of pattern matching, that is["Add", 1, "x"]
and["Add", "x", 1]
are not the same, since the order of the arguments is different. It is useful for example to compare some input to an answer that is expected to have a specific form.expr.isEqual(otherExpr)
return true ifexpr
andotherExpr
are mathematically identical. For examplece.parse("1+1").isEqual(ce.parse("2"))
will return true. This is useful if the specific structure of the expression is not important.
It is also possible to evaluate a boolean expression with a relational operator,
such as Equal
:
console.log(ce.box(["Equal", expr, 2]).evaluate().symbol);
// -> "True"
console.log(expr.isEqual(ce.box(2)));
// -> true
Before | After |
---|---|
expr = ["Add", 1, 2] |
expr = ce.box(["Add", 1, 2]) |
expr = ce.evaluate(expr) |
expr = expr.evaluate() |
console.log(expr) |
console.log(expr.json) |
expr = new LatexSyntax().parse("x^2+1") |
expr = ce.parse("x^2+1") |
new LatexSyntax().serialize(expr) |
expr.latex |
ce.simplify(expr) |
expr.simplify() |
await ce.evaluate(expr) |
expr.evaluate() |
ce.N(expr) |
expr.N() |
ce.domain(expr) |
expr.domain |
ce.format(expr...) |
expr.canonical expr.simplify() |
Release Date: 2021-06-18
- In LaTeX, parse
\operatorname{foo}
as the MathJSON symbol"foo"
.
import Mathfield from '@site/src/components/Mathfield'; import { useEffect, useState } from 'react'; import { clsx } from 'clsx'; import CodePlayground from '@site/src/components/CodePlayground'; import ConsoleMarkup from '@site/src/components/ConsoleMarkup';
export function ToggleButton ({toggle, className, style}) { return <button className={clsx(className, "toggle")} style={style} onClick={() => toggle()}> ; }
export function InfoSection({ label, children, secondary }) { const [visible, setVisible] = useState(false); return (
export function Console({children}) { return
{children}; }
export function ExampleSelector({ onSelect, index }) {
const examples = [
{
latex: "\frac{165}{315}",
json: '["Divide", 165, 315]',
preamble: To create a boxed expression from a MathJSON expression, use the \
ce.box()` function.
By default expressions are put in canonical form. The canonical form of fractions is their reduced form.`, template: "as-json" },
{
latex: 'e^{i\\pi}',
preamble: `To create a boxed expression from a LaTeX string,
use the `ce.parse()` function.
The `expr.N()` function evaluates the expression numerically, including complex numbers.`, template: "N" },
{
latex: '\\sqrt{6\\sum_{n=1}^{\\infty} \\frac{1}{n^2}}',
preamble: 'Evaluate a sum',
template: 'eval-async'
},
{
latex: '2\\prod_{n=1}^{\\infty} \\frac{4n^2}{4n^2-1}',
preamble: 'Evaluate a product',
template: 'eval-async'
},
{
latex: '\\mathrm{Expand}((a+b)^5)',
preamble: `Symbolically expand an expression.
Use the `latex` property to get the result in LaTeX format.`, template: 'eval-string' },
{
label: 'Rational',
json: '["Rational", "Pi"]',
preamble: `The \`Rational\` function with a single argument
approximates a number as a rational.` },
{
latex: '\\cos\\left(\\frac{\\pi}{6}\\right)',
preamble: 'Exact trigonometric values',
template: 'eval-string'
},
{
latex: '\\sin(30\\degree)',
preamble: 'Use degrees unit in trig functions',
template: 'eval-string'
},
{
latex: `\\begin{cases}
0 & n = 0\\
1 & n = 1\\
n^2+1 & n \geq 2
\end{cases}`,
label: "Piecewise"
},
{
latex: '\\lfloor \\pi \\rfloor = \\lceil \\exponentialE \\rceil',
preamble: "Evaluate boolean expressions",
template: "eval-string"
},
{
latex: '(x = \\pi) + 2',
label: "Errors",
preamble: 'Syntax errors are represented as \`Error\` functions'
}
];
useEffect(() => { onSelect(examples[index], index)}, []);
return (
$$${x.latex}$$
}
export function ComputeEngineDemo() {
const TEMPLATES = {
evaluate: $0 const expr = $1; console.info(expr.evaluate().json);
,
"as-json": $0 const expr = $1; console.info(expr.json);
,
"eval-string": $0 const expr = $1; console.info(expr.evaluate());
,
"eval-latex": $0 const expr = $1; console.info(expr.evaluate().latex);
,
N: $0 const expr = $1; console.info(expr.N());
,
"eval-async": $0 const expr = $1; console.info(await expr.evaluateAsync());
};
let [index, setIndex] = useState(0); let [value, setValue] = useState('');
const handleSelect = (example, exampleIndex) => { let code = TEMPLATES[example.template] ?? TEMPLATES["evaluate"];
if (example.preamble) {
const lines = example.preamble.split('\n');
const preamble = lines.map(x => `// ${x}`).join('\n') + '\n';
code = code.replace('$0', preamble);
} else {
code = code.replace('$0', '');
}
// Prioritize JSON over LaTeX if we have both. The LaTeX will be used
// as a label.
if (example.json) {
code = code.replace("$1", `ce.box(${example.json})`);
} else if (example.latex) {
if (example.latex.includes('\n')) {
const escaped = example.latex.replace(/\\/g, '\\\\');
code = code.replace('$1', `ce.parse(\`${escaped}\`)` );
} else {
code = code.replace('$1', `ce.parse(${JSON.stringify(example.latex)})`);
}
}
setValue(code);
setIndex(exampleIndex);
};
return (
Evaluating an expression is the process of determining the value of the expression. This involves looking up the definitions of symbols and functions, evaluating the arguments of functions, and applying the function to the arguments.
To evaluate an expression, use the expr.evaluate()
method.
const expr = ce.parse('2 + 2');
expr.evaluate().print();
The expr.value
property does not evaluate the expression. If the expression
is a literal, it returns the literal value. If the expression is a symbol, it
looks up the symbol in the current scope and returns its value.
ce.box('x').value = 314;
console.info(ce.parse('42').value)
console.info(ce.parse('x').value)
console.info(ce.parse('2 + 2').value)
The expr.N()
method is a shorthand for expr.evaluate({numericApproximation: true})
.
const expr = ce.parse('2\\pi');
expr.evaluate().print();
expr.N().print();
An expression can be evaluated by compiling it to JavaScript using the expr.compile()
method.
The method returns a JavaScript function that can be called to evaluate the expression.
const f = ce.parse('2\\pi').compile();
console.log(f());
Some computations can be time-consuming. For example, computing a very large factorial. To prevent the browser from freezing, the Compute Engine can perform some operations asynchronously.
To perform an asynchronous evaluation, use the expr.evaluateAsync()
method.
try {
const fact = ce.parse('(70!)!');
const factResult = await fact.evaluateAsync();
factResult.print();
} catch (e) {
console.error(e);
}
The expr.evaluateAsync()
method returns a Promise
that resolves to the result
of the evaluation. It accepts the same numericApproximation
options as expr.evaluate()
.
It is also possible to interrupt an evaluation, for example by providing the user with a pause/cancel button.
To make an evaluation interruptible, use an AbortController
object and a signal
.
For example, to interrupt an evaluation after 500ms:
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);
}
:::html
<div class="stack">
<div class="row">
<button id="evaluate-button">Evaluate</button>
<button id="cancel-button" disabled>Cancel</button>
</div>
<div id="output"></div>
</div>
:::js
const abort = new AbortController();
document.getElementById('cancel-button').addEventListener('click',
() => abort.abort()
);
document.getElementById('evaluate-button').addEventListener('click', async () => {
try {
document.getElementById('evaluate-button').disabled = true;
document.getElementById('cancel-button').disabled = false;
const fact = ce.parse('(70!)!');
const factResult = await fact.evaluateAsync({ signal: abort.signal });
document.getElementById('output').textContent = factResult.toString();
document.getElementById('evaluate-button').disabled = false;
document.getElementById('cancel-button').disabled = true;
} catch (e) {
document.getElementById('evaluate-button').disabled = false;
document.getElementById('cancel-button').disabled = true;
console.error(e);
}
});
To set a time limit for an operation, use the ce.timeLimit
option, which
is a number of milliseconds.
ce.timeLimit = 1000;
try {
const fact = ce.parse('(70!)!');
fact.evaluate().print();
} catch (e) {
console.error(e);
}
The time limit applies to both 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.
The Compute Engine supports lexical scoping.
A lexical scope is a region of the code where a symbol is defined. Each scope has its own bindings table, which is a mapping of symbols to their definitions. The definition includes the type of the symbol, whether it is a constant and other properties.
An evaluation context is a snapshot of the current state of the Compute Engine. It includes the values of all symbols currently in scope and a chain of lexical scopes.
Evaluation contexts are arranged in a stack, with the current (top-most)
evaluation context available with ce.context
.
Evaluation contexts are created automatically, for example when a new scope is created, or each time a recursive function is called.
Some functions may create their own scope. These functions have the scoped
flag set to true
. For example, the Block
function creates a new scope
when it is called: any declarations made in the block are only visible within the
block. Similarly, the Sum
function creates a new scope so that the index
variable is not visible outside the sum.
Name Binding is the process of associating a symbol with a definition.
Name Binding should not be confused with value binding which is the process of associating a value to a symbol.
To bind a symbol to a definition, the Compute Engine looks up the bindings table of the current scope. If the symbol is not found in the table, the parent scope is searched, and so on until a definition is found.
If no definition is found, the symbol is declared with a type of
unknown
, or a more specific type if the context allows it.
If the symbol is found, the definition record is used to evaluate the expression.
The definition record contains information about the symbol, such as its type, whether it is a constant, and how to evaluate it.
There are two kind of definition records:
- Value Definition Record: This record contains information about the symbol, such as its type and whether it is a constant.
- Operator Definition Record: This record contains information about the operator, such as its signature and how to evaluate it.
Name binding is done during canonicalization. If name binding failed, the
isValid
property of the expression is false
.
To get a list of the errors in an expression use the expr.errors
property.
Read more about the errors
The Compute Engine has a set of default scopes that are used to look up symbols. These scopes are created automatically when the Compute Engine is initialized. The default scopes include the system scope, and the global scope.
The system scope contains the definitions of all the built-in functions and operators. The global scope is initially empty, but can be used to store user-defined functions and symbols.
The global scope is the default scope used when the Compute Engine is initialized.
Additional scopes can be created using the ce.pushScope()
method.
To add a new scope use ce.pushScope()
.
ce.assign('x', 100); // "x" is defined in the current scope
ce.pushScope();
ce.assign('x', 500); // "x" is defined in the new scope
ce.box('x').print(); // 500
ce.popScope();
ce.box('x').print(); // 100
To exit a scope use ce.popScope()
.
This will invalidate any definitions associated with the scope, and restore the symbol table from previous scopes that may have been shadowed by the current scope.
:::info
This is an advanced topic. You don't need to know the details of how the evaluation loop works, unless you're interested in extending the standard library and providing your own function definitions.
:::
Each symbol is bound to a definition within a lexical scope during
canonicalization. This usually happens when calling ce.box()
or ce.parse()
,
or if accessing the .canonical
property of a
non-canonical expression.
When a function is evaluated, the following steps are followed:
-
If the expression is not canonical, it cannot be evaluated and an error is thrown. The expression must be canonicalized first.
-
Each argument of the function are evaluated, left to right, unless the function has a
lazy
flag. If the function is lazy, the arguments are not evaluated.-
An argument can be held, in which case it is not evaluated. Held arguments can be useful when you need to pass a symbolic expression to a function. If it wasn't held, the result of evaluating the expression would be used, not the symbolic expression.
Alternatively, using the
Hold
function will prevent its argument from being evaluated. Conversely, theReleaseHold
function will force an evaluation. -
If an argument is a
["Sequence"]
expression, treat each argument of the sequence expression as if it was an argument of the function. If the sequence is empty, ignore the argument.
-
-
If the function is associative, flatten its arguments as necessary. \[ f(f(a, b), c) \to f(a, b, c) \]
-
Apply the function to the arguments
-
Return the result in canonical form.
Assumptions are statements about symbols that are assumed to be true. For example, the assumption that \\(x\\) is a positive real number can be used to simplify \\(|x|\\) to \\(x\\).
Assumptions can be used to describe the range of values that a symbol can take.
The assumptions knowledge base is then used by the Compute Engine to simplify expressions.
Read more about **Simplifying Expressions**For example, the assumption that \(x\) is positive is used to simplify \(\sqrt{x^2}\) to \(x\).
ce.assume(["Greater", "x", 0]);
ce.parse("\\sqrt{x^2}").simplify().print();
// ➔ x
Assumptions can be used for other operations as well, such as solving equations or integrating.
To make an assumption about a symbol, use the ce.assume()
method.
For example, to indicate \(\beta \neq 0\):
ce.assume(ce.parse("\\beta \\neq 0"));
Each call to ce.assume()
replaces the previous assumptions about the symbol.
Assumptions are stored in a knowledge base which is tied to the current evaluation context.
Any assumptions made in the current context are automatically inherited by all child contexts.
When a context is exited, all assumptions made in that context are forgotten.
To remove previous assumptions, use ce.forget()
.
- Invoking
ce.forget()
with no arguments will remove all assumptions. - Passing an array of symbol names will remove assumptions about each of the symbols.
- Passing a symbol name will only remove assumptions about that particular symbol.
ce.declare("x", "number");
ce.assume(ce.parse("x > 0"));
ce.is(ce.parse("x > 2"));
// ➔ true
ce.forget("x");
ce.is(ce.parse("x > 2"));
// ➔ undefined
To temporarily define a series of assumptions, create a new scope.
ce.declare("x", "number");
ce.is(ce.parse("x > 2"));
// ➔ undefined
ce.pushScope();
ce.assume(ce.parse("x > 0"));
ce.is(ce.parse("x > 2"));
// ➔ true
ce.popScope(); // all assumptions made in the current scope are forgotten
ce.is(ce.parse("x > 2"));
// ➔ undefined
The argument of the ce.assume()
method is a proposition. A proposition is a
statement that can be either true or false. Assumption propositions can take
the following forms:
Operator | |
---|---|
Element NotElement |
Indicate the domain of a symbol |
Less LessEqual Greater GreaterEqual |
Inequality. Both sides are assumed to be RealNumbers |
Equal NotEqual |
Equality |
Some propositions can be described in several equivalent ways. You can use whichever form you prefer. Similarly, when querying the knowledge base later, you can use any form you'd like.
ce.assume(ce.parse("x > 0"));
// Equivalent to...
ce.assume(ce.parse("0 < x"));
To test if a particular assumption is valid call the ce.verify()
method.
ce.verify(ce.parse("x > 0"));
The method ce.verify()
return true
if the assumption is true, false
if it is
not, and undefined
if it cannot be determined.
While ce.verify()
is appropriate to get boolean answers, more complex queries can
also be made.
To query the assumptions knowledge base call the ce.ask()
method.
The argument of ce.ask()
can be a pattern, and it returns an array of matches as
Substitution
objects.
// "x is a positive integer"
ce.assume(ce.parse("x > 0"));
// "What is x greater than?"
ce.ask(["Greater", "x", "_val"]);
// -> [{"val": 0}] "It is greater than 0"
title: MathJSON Format slug: /math-json/ description: MathJSON is a lightweight data interchange format for mathematical notation hide_title: true sidebar_class_name: "sdk-icon"
import Mathfield from '@site/src/components/Mathfield'; import ConsoleMarkup from '@site/src/components/ConsoleMarkup'; import {useState, useEffect} from 'react'; import {BrowserOnly} from '@docusaurus/BrowserOnly';
export function setupComputeEngine() { if (window.ce !== undefined) return; // If we're not ready, try again in 50ms if (!("ComputeEngine" in window)) { setTimeout(setupComputeEngine, 50); return; } const [value, setValue] = useState(children); const [json, setJson] = useState({}); window.ce = new ComputeEngine.ComputeEngine(); setJson(window.ce?.parse(value).json); } export function MathJSONOutput({children}) { const [value, setValue] = useState(children); const [json, setJson] = useState({}); useEffect(setupComputeEngine, []); // We need to use useEffect so that the MathfieldElement is available // when this code runs (in the client). useEffect(() => { setupComputeEngine(); setJson(window.ce?.parse(value).json); }, [value]); return<> <Mathfield style={{ width: "100%", borderRadius: "8px", padding: "8px", marginBottom: "1em" }} onChange={(ev) => setValue(ev.target.value)} >{value} </>; }
# MathJSON A lightweight data interchange format for mathematical notation.Math | MathJSON |
---|---|
["Divide", "n", ["Add", 1, "n"]] |
|
Double exponent: use braces to clarify
$$\sin^{-1}^\prime(x)$$ |
["Apply", ["Derivative", ["InverseFunction", "Sin"]], "x"] |
MathJSON is built on the JSON format. Its focus is on interoperability between software programs to facilitate the exchange of mathematical data and the building of scientific software through the integration of software components communicating with a common format.
It is human-readable, while being easy for machines to generate and parse. It is simple enough that it can be generated, consumed and manipulated using any programming languages.
MathJSON can be transformed from (parsing) and to (serialization) other formats.
:::info[Demo]
Type an expression in the mathfield below to see its MathJSON representation.
{e^{i\\pi}+1=0
}
:::
The Cortex Compute Engine library provides an implementation in JavaScript/TypeScript of utilities that parse LaTeX to MathJSON, serialize MathJSON to LaTeX, and provide a collection of functions for symbolic manipulation and numeric evaluations of MathJSON expressions.
Try a demo of the **Compute Engine** Read more about the **Compute Engine** LaTeX syntax parsing and serializingMathematical notation is used in a broad array of fields, from elementary school arithmetic, engineering, applied mathematics to physics and more. New notations are invented regularly and MathJSON endeavors to be flexible and extensible to account for those notations.
The Compute Engine includes a standard library of functions and symbols which can be extended with custom libraries.
Read more about the **MathJSON Standard Library**MathJSON is not intended to be suitable as a visual representation of arbitrary mathematical notations, and as such is not a replacement for LaTeX or MathML.
A MathJSON expression is a combination of numbers, symbols, strings and functions.
Number
3.14
314e-2
{"num": "3.14159265358979323846264338327950288419716939937510"}
{"num": "-Infinity"}
Symbol
"x"
"Pi"
"🍎"
"半径"
{"sym": "Pi", "wikidata": "Q167"}
String
"'Diameter of a circle'"
{"str": "Srinivasa Ramanujan"}
Function
["Add", 1, "x"]
{"fn": [{"sym": "Add"}, {"num": "1"}, {"sym": "x"}]}
Numbers, symbols, strings and functions are expressed either as
object literals with a "num"
"str"
"sym"
or "fn"
key, respectively, or
using a shorthand notation as a a JSON number, string or array.
The shorthand notation is more concise and easier to read, but it cannot include metadata properties.
A MathJSON number is either:
- an object literal with a
"num"
key - a JSON number
- a JSON string starting with
+
,-
or the digits0
-9
. Using a string is useful to represent numbers with a higher precision or greater range than JSON numbers.
Numbers may be represented as an object literal with a "num"
key. The
value of the key is a string representation of the number.
{
"num": <string>
}
The string representing a number follows the JSON syntax for number, with the following differences:
- The range or precision of MathJSON numbers may be greater than the range and precision supported by IEEE 754 64-bit float.
{ "num": "1.1238976755823478721365872345683247563245876e-4567" }
- The string values
"NaN"
"+Infinity"
and"-Infinity"
are used to represent respectively an undefined result as per IEEE 754,, and .
{ "num": "+Infinity" }
- If the string includes the pattern
/\([0-9]+\)/
, that is a series of one or more digits enclosed in parentheses, that pattern is interpreted as repeating digits.
{ "num": "1.(3)" }
{ "num": "0.(142857)" }
{ "num": "0.(142857)e7" }
- The following characters in a string representing a number are ignored:
U+0009 | TAB |
U+000A | LINE FEED |
U+000B | VERTICAL TAB |
U+000C | FORM FEED |
U+000D | CARRIAGE RETURN |
U+0020 | SPACE |
U+00A0 | UNBREAKABLE SPACE |
When a number is compatible with the JSON representation of numbers and has no metadata, a JSON number literal may be used.
Specifically:
- the number fits in a 64-bit binary floating point, as per IEEE 754-2008, with a
53-bit significand (about 15 digits of precision) and 11-bit exponent.
If negative, its range is from
to and if positive from to - the number is finite: it is not
+Infinity
-Infinity
orNaN
.
0
-234.534e-46
The numeric values below may not be represented as JSON number literals:
// Exponent out of bounds
{ "num": "5.78e400" }
// Too many digits
{ "num": "3.14159265358979323846264338327950288419716" }
// Non-finite numeric value
{ "num": "-Infinity" }
An alternate representation of a number with no extra metadata is as a string following the format described above.
This allows for a shorthand representation of numbers with a higher precision or greater range than JSON numbers.
"3.14159265358979323846264338327950288419716"
"+Infinity"
A MathJSON string is either:
- an object literal with a
"str"
key - a JSON string that starts and
ends with U+0027
'
APOSTROPHE .
Strings may contain any character represented by a Unicode scalar value (a
codepoint in the [0...0x10FFFF]
range, except for [0xD800...0xDFFF]
), but
the following characters must be escaped as indicated:
Codepoint | Name | Escape Sequence |
---|---|---|
U+0000 to U+001F | \u0000 to \u001f |
|
U+0008 | BACKSPACE | \b or \u0008 |
U+0009 | TAB | \t or \u0009 |
U+000A | LINE FEED | \n or \u000a |
U+000C | FORM FEED | \f or \u000c |
U+000D | CARRIAGE RETURN | \r or \u000d |
U+0027 | APOSTROPHE | \' or \u0027 |
U+005C | REVERSE SOLIDUS (backslash) | \\ or \u005c |
The encoding of the string follows the encoding of the JSON payload: UTF-8, UTF-16LE, UTF-16BE, etc...
"'Alan Turing'"
A MathJSON function expression is either:
- an object literal with a
"fn"
key. - a JSON array
Function expressions in the context of MathJSON may be used to represent mathematical functions but are more generally used to represent the application of a function to some arguments.
The function expression ["Add", 2, 3]
applies the function named Add
to the
arguments 2
and 3
.
The default representation of function expressions is an object literal with
a "fn"
key. The value of the fn
key is an array representing the function
operator (its name) and its arguments (its operands).
{
"fn": [Operator, ...Operands[]]
}
For example:
-
: { "fn": ["Add", 2, "x"] }
-
: { "fn": ["Sin", ["Add", ["Multiply", 2, "x"], "Pi"]] }
-
: { "fn": ["Add", ["Power", "x", 2], ["Multiply", -3, "x"], 5] }
If a function expression has no extra metadata it may be represented as a JSON array.
For example these two expressions are equivalent:
{ "fn": ["Cos", ["Add", "x", 1]] }
["Cos", ["Add", "x", 1]]
:::note
An array representing a function must have at least one element, the operator of the
function. Therefore []
is not a valid expression.
:::
The operator of the function expression is the first element in the array. Its presence is required. It indicates the name of the function: this is what the function is about.
The operator is a symbol following the conventions for function names (see below).
// Apply the function "Sin" to the argument "x"
["Sin", "x"]
// Apply "Cos" to a function expression
["Cos", ["Divide", "Pi", 2]]
Following the operator are zero or more arguments (or operands), which are expressions.
:::warning[CAUTION]
The arguments of a function are expressions. To represent an
argument which is a list, use a ["List"]
expression, do not use a JSON array.
:::
The expression corresponding to $ \sin^{-1}(x) $ is:
["Apply", ["InverseFunction", "Sin"], "x"]
The operator of this expression is "Apply"
and its argument are the expressions
["InverseFunction", "Sin"]
and "x"
.
The following shorthands are allowed:
- A
["Dictionary"]
expression may be represented as a string starting with U+007B{
LEFT CURLY BRACKET and ending with U+007D}
RIGHT CURLY BRACKET. The string must be a valid JSON object literal. - A
["List"]
expression may be represented as a string starting with U+005B[
LEFT SQUARE BRACKET and ending with U+005D]
RIGHT SQUARE BRACKET. The string must be a valid JSON array.
"{\"x\": 2, \"y\": 3}"
// ➔ ["Dictionary", ["Tuple", "x", 2], ["Tuple", "y", 3]]
"[1, 2, 3]"
// ➔ ["List", 1, 2, 3]
A MathJSON symbol is either:
- an object literal with a
"sym"
key - a JSON string
Symbols are JSON strings that represent the names of symbols, variables, constants, wildcards and functions.
Before they are used, JSON escape sequences (such as \u
sequences, \\
, etc.)
are decoded.
The symbols are then normalized to the Unicode Normalization Form C (NFC). They are stored internally and compared using the Unicode NFC.
For example, these four JSON strings represent the same symbol:
"Å"
"A\u030a"
U+0041A
LATIN CAPITAL LETTER + U+030Å
COMBINING RING ABOVE"\u00c5"
U+00C5Å
LATIN CAPITAL LETTER A WITH RING ABOVE"\u0041\u030a"
U+0041A
LATIN CAPITAL LETTER A + U+030Å
COMBINING RING ABOVE
Symbols conform to a profile of UAX31-R1-1 with the following modifications:
- The character U+005F
_
LOW LINE is added to theStart
character set - The characters should belong to a recommended script
- A symbol can be a sequence of one or more emojis. Characters that have both the Emoji and XIDC property are only considered emojis when they are preceded with emoji modifiers. The definition below is based on Unicode TR51 but modified to exclude invalid symbols.
Symbols match either the NON_EMOJI_SYMBOL
or the EMOJI_SYMBOL
regex patterns below:
const NON_EMOJI_SYMBOL = /^[\p{XIDS}_]\p{XIDC}*$/u;
(from Unicode TR51)
or
const VS16 = "\\u{FE0F}"; // Variation Selector-16, forces emoji presentation
const KEYCAP = "\\u{20E3}"; // Combining Enclosing Keycap
const ZWJ = "\\u{200D}"; // Zero Width Joiner
const FLAG_SEQUENCE = "\\p{RI}\\p{RI}";
const TAG_MOD = `(?:[\\u{E0020}-\\u{E007E}]+\\u{E007F})`;
const EMOJI_MOD = `(?:\\p{EMod}|${VS16}${KEYCAP}?|${TAG_MOD})`;
const EMOJI_NOT_SYMBOL = `(?:(?=\\P{XIDC})\\p{Emoji})`;
const ZWJ_ELEMENT = `(?:${EMOJI_NOT_SYMBOL}${EMOJI_MOD}*|\\p{Emoji}${EMOJI_MOD}+|${FLAG_SEQUENCE})`;
const POSSIBLE_EMOJI = `(?:${ZWJ_ELEMENT})(${ZWJ}${ZWJ_ELEMENT})*`;
const EMOJI_SYMBOL = new RegExp(`^(?:${POSSIBLE_EMOJI})+$`, "u");
In summary, when using Latin characters, symbols can start with a letter or an underscore, followed by zero or more letters, digits and underscores.
Carefully consider when to use non-latin characters. Use non-latin characters
for whole words, for example: "半径"
(radius), "מְהִירוּת"
(speed), "直徑"
(diameter) or "सतह"
(surface).
Avoid mixing Unicode characters from different scripts in the same symbols.
Do not include bidi markers such as U+200E LTR
* or U+200F RTL
in
symbols. LTR and RTL marks should be added as needed by the client
displaying the symbol. They should be ignored when parsing symbols.
Avoid visual ambiguity issues that might arise with some Unicode characters. For example:
- prefer using
"gamma"
rather than U+0194ɣ
LATIN SMALL LETTER GAMMA or U+03B3γ
GREEK SMALL LETTER GAMMA - prefer using
"Sum"
rather than U+2211∑
N-ARY SUMMATION, which can be visually confused with U+03A3Σ
GREEK CAPITAL LETTER SIGMA.
The following naming convention for wildcards, variables, constants and function names are recommendations.
Symbols that begin with U+005F _
LOW LINE (underscore) should be used to
denote wildcards and other placeholders.
For example, they may be used to denote the positional parameter in a function expression. They may also denote placeholders and captured expression in patterns.
Wildcard | |
---|---|
"_" |
Wildcard for a single expression or for the first positional argument |
"_1" |
Wildcard for a positional argument |
"_ _" |
Wildcard for a sequence of 1 or more expression |
"___" |
Wildcard for a sequence of 0 or more expression |
"_a" |
Capturing an expression as a wildcard named a |
-
If a variable is made of several words, use camelCase. For example
"newDeterminant"
-
Prefer clarity over brevity and avoid obscure abbreviations.
Use
"newDeterminant"
rather than"newDet"
or"nDet"
-
If using latin characters, the first character of a constant should be an uppercase letter
A
-Z
-
If the name of a constant is made up of several words, use PascalCase. For example
"SpeedOfLight"
-
The name of the functions in the MathJSON Standard Library starts with an uppercase letter
A
-Z
. For example"Sin"
,"Fold"
. -
The name of your own functions can start with a lowercase or uppercase letter.
-
If the name of a function is made up of several words, use PascalCase or camelCase. For example
"InverseFunction"
or"inverseFunction"
.
The following recommendations may be followed by clients displaying MathJSON symbols with LaTeX, or parsing LaTeX to MathJSON symbols.
These recommendations do not affect computation or manipulation of expressions following these conventions.
- A symbol may be composed of a main body, some modifiers, some style
variants, some subscripts and superscripts. For example:
-
"alpha_0__prime"
-
"x_vec"
$$ \vec{x} $$ -
"Re_fraktur"
.
-
- Subscripts are indicated by an underscore
_
and superscripts by a double-underscore__
. There may be more than one superscript or subscript, but they get concatenated. For example"a_b__c_q__p"
->a_{b, q}^{c, p}
\( a_{b, q}^{c, p} \). - Modifiers after a superscript or subscript apply to the closest preceding
superscript or subscript. For example
"a_b_prime"
->a_{b^{\prime}}
Modifiers include:
Modifier | LaTeX | |
---|---|---|
_deg |
\degree |
\( x\degree \) |
_prime |
{}^\prime |
\( x^{\prime} \) |
_dprime |
{}^\doubleprime |
\( x^{\doubleprime} \) |
_ring |
\mathring{} |
\( \mathring{x} \) |
_hat |
\hat{} |
\( \hat{x} \) |
_tilde |
\tilde{} |
\( \tilde{x} \) |
_vec |
\vec{} |
\( \vec{x} \) |
_bar |
\overline{} |
\( \overline{x} \) |
_underbar |
\underline{} |
\( \underline{x} \) |
_dot |
\dot{} |
\( \dot{x} \) |
_ddot |
\ddot{} |
\( \ddot{x} \) |
_tdot |
\dddot{} |
\( \dddot{x} \) |
_qdot |
\ddddot{} |
\( \dddodt{x} \) |
_operator |
\operatorname{} |
\( \operatorname{x} \) |
_upright |
\mathrm{} |
\( \mathrm{x} \) |
_italic |
\mathit{} |
\( \mathit{x} \) |
_bold |
\mathbf{} |
\( \mathbf{x} \) |
_doublestruck |
\mathbb{} |
\( \mathbb{x} \) |
_fraktur |
\mathfrak{} |
\( \mathfrak{x} \) |
_script |
\mathscr{} |
\( \mathscr{x} \) |
- The following common names, when they appear as the body or in a subscript/superscript of a symbol, may be replaced with a corresponding LaTeX command:
Common Names | LaTeX | |
---|---|---|
alpha |
\alpha |
\( \alpha \) |
beta |
\beta |
\( \beta \) |
gamma |
\gamma |
\( \gamma \) |
delta |
\delta |
\( \delta \) |
epsilon |
\epsilon |
\( \epsilon \) |
epsilonSymbol |
\varepsilon |
\( \varepsilon \) |
zeta |
\zeta |
\( \zeta \) |
eta |
\eta |
\( \eta \) |
theta |
\theta |
\( \theta \) |
thetaSymbol |
\vartheta |
\( \vartheta \) |
iota |
\iota |
\( \iota \) |
kappa |
\kappa |
\( \kappa \) |
kappaSymbol |
\varkappa |
\( \varkappa \) |
mu |
\mu |
\( \mu \) |
nu |
\nu |
\( \nu \) |
xi |
\xi |
\( \xi \) |
omicron |
\omicron |
\( \omicron \) |
piSymbol |
\varpi |
\( \varpi \) |
rho |
\rho |
\( \rho \) |
rhoSymbol |
\varrho |
\( \varrho \) |
sigma |
\sigma |
\( \sigma \) |
finalSigma |
\varsigma |
\( \varsigma \) |
tau |
\tau |
\( \tau \) |
phi |
\phi |
\( \phi \) |
phiLetter |
\varphi |
\( \varphi \) |
upsilon |
\upsilon |
\( \upsilon \) |
chi |
\chi |
\( \chi \) |
psi |
\psi |
\( \psi \) |
omega |
\omega |
\( \omega \) |
Alpha |
\Alpha |
\( \Alpha \) |
Beta |
\Beta |
\( \Beta \) |
Gamma |
\Gamma |
\( \Gamma \) |
Delta |
\Delta |
\( \Delta \) |
Epsilon |
\Epsilon |
\( \Epsilon \) |
Zeta |
\Zeta |
\( \Zeta \) |
Eta |
\Eta |
\( \Eta \) |
Theta |
\Theta |
\( \Theta \) |
Iota |
\Iota |
\( \Iota \) |
Kappa |
\Kappa |
\( \Kappa \) |
Lambda |
\Lambda |
\( \Lambda \) |
Mu |
\Mu |
\( \Mu \) |
Nu |
\Nu |
\( \Nu \) |
Xi |
\Xi |
\( \Xi \) |
Omicron |
\Omicron |
\( \Omicron \) |
Pi |
\Pi |
\( \Pi \) |
Rho |
\Rho |
\( \Rho \) |
Sigma |
\Sigma |
\( \Sigma \) |
Tau |
\Tau |
\( \Tau \) |
Phi |
\Phi |
\( \Phi \) |
Upsilon |
\Upsilon |
\( \Upsilon \) |
Chi |
\Chi |
\( \Chi \) |
Psi |
\Psi |
\( \Psi \) |
Omega |
\Omega |
\( \Omega \) |
digamma |
\digamma |
\( \digamma \) |
aleph |
\aleph |
\( \aleph \) |
lambda |
\lambda |
\( \lambda \) |
bet |
\beth |
\( \beth \) |
gimel |
\gimel |
\( \gimel \) |
dalet |
\dalet |
\( \dalet \) |
ell |
\ell |
\( \ell \) |
turnedCapitalF |
\Finv |
\( \Finv \) |
turnedCapitalG |
\Game |
\( \Game \) |
weierstrass |
\wp |
\( \wp \) |
eth |
\eth |
\( \eth \) |
invertedOhm |
\mho |
\( \mho \) |
hBar |
\hbar |
\( \hbar \) |
hSlash |
\hslash |
\( \hslash \) |
blacksquare |
\hslash |
\( \hslash \) |
bottom |
\bot |
\( \bot \) |
bullet |
\bullet |
\( \bullet \) |
circle |
\circ |
\( \circ \) |
diamond |
\diamond |
\( \diamond \) |
times |
\times |
\( \times \) |
top |
\top |
\( \top \) |
square |
\square |
\( \square \) |
star |
\star |
\( \star \) |
- The following names, when used as a subscript or superscript, may be replaced with a corresponding LaTeX command:
Subscript/Supscript | LaTeX | |
---|---|---|
plus |
{}_{+} / {}^{+} |
\( x_{+} x^+\) |
minus |
{}_{-} /{}^{-} |
\( x_{-} x^-\) |
pm |
{}_\pm /{}^\pm |
\( x_{\pm} x^\pm \) |
ast |
{}_\ast /{}^\ast |
\( {x}_\ast x^\ast \) |
dag |
{}_\dag /{}^\dag |
\( {x}_\dag x^\dag \) |
ddag |
{}_\ddag {}^\ddag |
\( {x}_\ddag x^\ddag \) |
hash |
{}_\# {}^\# |
\( {x}_# x^#\) |
-
Multi-letter symbols may be rendered with a
\mathit{}
,\mathrm{}
or\operatorname{}
command. -
Symbol fragments ending in digits may be rendered with a corresponding subscript.
Symbol | LaTeX | |
---|---|---|
time |
\mathrm{time} |
\( \mathrm{time} \) |
speed_italic |
\mathit{speed} |
\( \mathit{speed} \) |
P_blackboard__plus |
\mathbb{P}^{+} |
$$ \mathbb{P}^+ $$ |
alpha |
\alpha |
\( \alpha \) |
mu0 |
\mu_{0} |
\( \mu_0 \) |
m56 |
m_{56} |
\( m_{56} \) |
c_max |
\mathrm{c_{max}} |
\( \mathrm{c_{max}} \) |
MathJSON object literals may be annotated with supplemental information.
A number represented as a JSON number literal, a symbol or string represented as a JSON string literal, or a function represented as a JSON array must be transformed into the equivalent object literal to be annotated.
The following metadata keys are recommended:
Key | Note |
---|---|
wikidata |
A short string indicating an entry in a wikibase. This information can be used to disambiguate the meaning of a symbol. Unless otherwise specified, the entry in this key refers to an entry in the wikidata.org wikibase |
comment |
A human readable plain string to annotate an expression, since JSON does not allow comments in its encoding |
documentation |
A Markdown-encoded string providing documentation about this expression. |
latex |
A visual representation in LaTeX of the expression. This can be useful to preserve non-semantic details, for example parentheses in an expression or styling attributes |
sourceUrl |
A URL to the source of this expression |
sourceContent |
The source from which this expression was generated. It could be a LaTeX expression, or some other source language. |
sourceOffsets |
A pair of character offsets in sourceContent or sourceUrl from which this expression was produced |
hash |
A string representing a digest of this expression. |
{
"sym": "Pi",
"comment": "The ratio of the circumference of a circle to its diameter",
"wikidata": "Q167",
"latex": "\\pi"
}
{
"sym": "Pi",
"comment": "The greek letter ∏",
"wikidata": "Q168",
}
This document defines the structure of MathJSON expression. The MathJSON Standard Library defines a recommended vocabulary to use in MathJSON expressions.
Before considering inventing your own vocabulary, check if the MathJSON Standard Library already provides relevant definitions.
The MathJSON Standard Library includes definitions for:
Topic | |
---|---|
Arithmetic | Add Multiply Power Exp Log ExponentialE ImaginaryUnit ... |
Calculus | D Derivative Integrate ... |
Collections | List Reverse Filter ... |
Complex | Real Conjugate ComplexRoots ... |
Control Structures | If Block Loop ... |
Core | Declare Assign Error LatexString ... |
Functions | Function Apply Return ... |
Logic | And Or Not True False ForAll ... |
Sets | Union Intersection EmptySet RealNumbers Integers ... |
Special Functions | Gamma Factorial ... |
Statistics | StandardDeviation Mean Erf ... |
Styling | Delimiter Style ... |
Trigonometry | Pi Cos Sin Tan ... |
When defining a new function, avoid using a name already defined in the Standard Library.
Read more about **Adding New Definitions** --- title: Calculus slug: /compute-engine/reference/calculus/ --- Calculus is the mathematical study of continuous change.It has two main branches: differential calculus and integral calculus. These two branches are related by the fundamental theorem of calculus:
$$\int_a^b f(x) \,\mathrm{d}x = F(b) - F(a)$$...where $ F $ is an antiderivative of $ f $, that is $ F' = f $.
To calculate the derivative of a function, use the D
function to
calculate a symbolic derivative or ND
to calculate a numerical approximation
To calculate the integral (antiderivative) of a function, use the
Integrate
function to calculate a symbolic integral or NIntegrate
to
calculate a numerical approximation.
To calculate the limit of a function, use the Limit
function.
The derivative of a function is a measure of how the function changes as its input changes. It is the ratio of the change in the value of a function to the change in its input value. The derivative of a function $$ f(x) $$ with respect to its input $$ x $$ is denoted by:
The derivative of a function $$ f(x) $$ is defined as:
$$f'(x) = \lim_{h \to 0} \frac{f(x + h) - f(x)}{h}$$where $$ \lim_{h \to 0} \frac{f(x + h) - f(x)}{h} $$ is the limit of the ratio of the change in the value of the function to the change in its input value as $$ h $$ approaches $$ 0 $$.
The limit is taken as $$ h $$ approaches $$ 0 $$ because the derivative is the instantaneous rate of change of the function at a point, and the change in the input value must be infinitesimally small to be instantaneous.
Reference
- Wikipedia: Derivative
- Wikipedia: Notation for Differentiation, Leibniz's Notation, Lagrange's Notation, Newton's Notation
- Wolfram Mathworld: Derivative
- NIST: Derivative
Lagrange Notation
LaTeX | MathJSON |
---|---|
f'(x) |
["Derivative", "f", "x"] |
f\prime(x) |
["Derivative", "f", "x"] |
f^{\prime}(x) |
["Derivative", "f", "x"] |
f''(x) |
["Derivative", "f", "x", "x"] |
f\prime\prime(x) |
["Derivative", "f", "x", "x"] |
f^{\prime\prime}(x) |
["Derivative", "f", "x", "x"] |
f\doubleprime(x) |
["Derivative", "f", "x", "x"] |
f^{(n)}(x) |
["Derivative", "f", "x", n] |
The Derivative
function represents a derivative of a function with respect to a single variable.
The D
function is used to calculate the symbolic derivative of a function with respect to one or more variables.
The ND
function is used to calculate a numerical approximation of the derivative of a function.
f: number -> number, var: symbol
The D
function represents the partial derivative of a function f
with respect to
the variable var
.
["D", "f", "x"]
f, ...var: symbol
Multiple variables can be specified to compute the partial derivative of a multivariate function.
["D", "f", "x", "y"]
A variable can be repeated to compute the second derivative of a function.
["D", "f", "x", "x"]
f: number -> number, x: number
The ND
function returns a numerical approximation of the partial derivative of a function
["ND", "Sin", 1]
// ➔ 0.5403023058681398
Note: ["ND", "Sin", 1]
is equivalent to ["Apply", ["D", "Sin"], 1]
.
f: number -> number
The Derivative
function represents the derivative of a function
["Apply", ["Derivative", "f"], "x"]
f: number -> number, n: integer
When an argument
["Apply", ["Derivative", "f", n], "x"]
Derivative
is an operator in the mathematical sense, that is, a function that takes a function
as an argument and returns a function.
The Derivative
function is used to represent the derivative of a function in a symbolic form. It is not used to calculate the derivative of a function. To calculate the derivative of a function, use the D
function or ND
to calculate a numerical approximation.
["Derivative", "f", "x"]
is equivalent to
["D", ["f", "x"], "x"]
The integral of a function $$ f(x) $$ is denoted by:
:::info[Note]
The commands \!
and \,
adjust the spacing. The \!
command reduces the space between the integral sign and the integrand, while the \,
command increases the space before the differential operator d
.
The \mathrm
command is used to typeset the differential operator d
in an upright font.
These typesetting conventions are part of the ISO 80000-2:2009 standard for mathematical notation, but are not universally adopted. :::
The indefinite integral of a function $$ f(x) $$ is the family of all antiderivatives of a function:
$$\int f(x) \,\mathrm{d}x = F(x) + C$$where
A definite integral of a function $$ f(x) $$ is the signed area under the curve of the function between two points $$ a $$ and $$ b $$:
$$\int_a^b f(x) \,\mathrm{d}x = F(b) - F(a)$$The \limits
command controls the placement of the limits of integration.
A double integral of a function $$ f(x, y) $$ is the signed volume under the surface of the function between two points $$ a $$ and $$ b $$ in the x-direction and two points $$ c $$ and $$ d $$ in the y-direction:
The \iint
command is used to typeset the double integral symbol.
To calculate the symbolic integral of a function, use the Integrate
function.
To calculate a numerical approximation of the integral of a function, use the NIntegrate
function.
Reference
- Wikipedia: Integral, Antiderivative, Integral Symbol
- Wolfram Mathworld: Integral
- NIST: Integral
Evaluates to a symbolic indefinite integral of a function $$ f $$.
["Integrate", "Sin"]
The argument f
, the integrand, is a function literal, which can be expressed in different ways:
- As a symbol whose value is a function:
["Integrate", "f"]
- As a symbol for a built-in function:
["Integrate", "Sin"]
- As a
["Function"]
expression:["Integrate", ["Function", ["Sin", "x"], "x"]]
- As a shorthand function literal:
["Integrate", ["Power", "_", 2]]
- As an expression with unknowns:
["Integrate", ["Power", "x", 2]]
f: function, ...var:symbol
Symbolic indefinite integral of a function $$ f $$ with respect to a variable $$ x $$.
["Integrate", ["Sin", "x"], "x"]
Symbolic indefinite integral of a function $$ f $$ with respect to a variable $ x $ and $ y $.
["Integrate",
["Add", ["Sin", ["Power", "x", 2]], ["Power", "y", 2]],
"x", "y"
]
Symbolic indefinite integral of a function $$ f $$ with respect to a variable $$ x $$, applied twice.
["Integrate", ["Sin", "x"], "x", "x"]
f: function, ...limits:tuple
A definite integral of a function $$ f $$. The function is evaluated symbolically as:
$$\int_a^b f(x) \,\mathrm{d}x = F(b) - F(a)$$where $$ F $$ is the antiderivative of $$ f $$.
The limits
tuples indicate the variable of integration and the limits of integration.
The first element of the tuple is the variable of integration, and the second and third elements are the lower and upper limits of integration, respectively.
["Integrate",
["Power", "x", 2],
["Tuple", "x", 0, 2]
]
The variable of integration can be omitted if it is the same as the argument of the function.
["Integrate",
["Power", "x", 2],
["Tuple", 0, 2]
]
Double integrals can be computed by specifying more than one limit.
["Integrate",
["Add", ["Power", "x", 2], ["Power", "y", 2]],
["Tuple", "x", 0, 2],
["Tuple", "y", 1, 3]
]
Some functions do not have a closed form for their antiderivative, and the integral cannot be computed symbolically. In this case, the Integrate
function returns a symbolic representation of the integral. Use NIntegrate
to compute a numerical approximation of the integral.
Calculate a numerical approximation of the definite integral of a function.
["NIntegrate", ["Power", "x", 2], ["Tuple", 0, 2]]
// -> 2.6666666666666665
The limits
tuples indicate the variable of integration and the limits of integration.
The first element of the tuple is the variable of integration, and the second and third elements are the lower and upper limits of integration, respectively.
The variable of integration can be omitted if it is the same as the argument of the function.
["NIntegrate", ["Power", "x", 2], ["Tuple", 0, 2]]
// -> 2.6666666666666665
A double integral can be computed by specifying more than one limit.
["NIntegrate",
["Add", ["Power", "x", 2], ["Power", "y", 2]],
["Tuple", 0, 2],
["Tuple", 1, 3]
]
// -> 20.666666666666668
The numerical approximation is computed using a Monte Carlo method.
The limit of a function $$ f(x) $$ as $$ x $$ approaches a value $$ a $$ is the value that $$ f(x) $$ approaches as $$ x $$ gets arbitrarily close to $$ a $$.
It is denoted by:
["Limit", ["f", "x"], "a"]
f: function, value: number
Evaluate the function f as it approaches the value value.
["Limit", ["Divide", ["Sin", "_"], "_"], 0]
["Limit", ["Function", ["Divide", ["Sin", "x"], "x"], "x"], 0]
This function evaluates to a numerical approximation when using expr.N()
. To
get a numerical evaluation with expr.evaluate()
, use NLimit
.
f: function, value: number
Evaluate the function f as it approaches the value value.
["NLimit", ["Divide", ["Sin", "_"], "_"], 0]
// ➔ 1
["NLimit", ["Function", ["Divide", ["Sin", "x"], "x"], "x"], 0]
// ➔ 1
The numerical approximation is computed using a Richardson extrapolation algorithm.
title: MathJSON Standard Library slug: /compute-engine/standard-library/ hide_table_of_contents: true
The MathJSON standard library defines the vocabulary used by a MathJSON expression.
This library defines the meaning of the symbols used in a MathJSON expression. It is independent of the syntax used to parse/serialize from another language such as LaTeX.
It includes definitions such as:
- "
Pi
is a transcendental number whose value is approximately 3.14159265..." - "The
Add
function is associative, commutative, pure, idempotent and can be applied to an arbitrary number of Real or Complex numbers".
The MathJSON Standard Library is organized by topics, each topic is a separate page in the documentation.
Topic | |
---|---|
Arithmetic | Add Multiply Power Exp Log ExponentialE ImaginaryUnit ... |
Calculus | D Derivative Integrate ... |
Collections | List Reverse Filter ... |
Complex | Real Conjugate , ComplexRoots ... |
Control Structures | If Block Loop ... |
Core | Declare Assign Error LatexString ... |
Functions | Function Apply Return ... |
Logic | And Or Not True False ForAll ... |
Sets | Union Intersection EmptySet RealNumbers Integers ... |
Special Functions | Gamma Factorial ... |
Statistics | StandardDeviation Mean Erf ... |
Styling | Delimiter Style ... |
Trigonometry | Pi Cos Sin Tan ... |
The MathJSON Standard Library can be extended by defining new functions:
// Declare that the symbol "f" is a function,
// but without giving it a definition
ce.declare("f", "function");
// Define a new function `double` that returns twice its input
ce.assign("double(x)", ["Multiply", "x", 2]);
// LaTeX can be used for the definition as well...
ce.assign("half(x)", ce.parse("\\frac{x}{2}"));
You can also customize the LaTeX syntax, that is how to parse and serialize LaTeX to MathJSON.
Read more about Parsing and Serializing LaTeX --- title: Patterns and Rules slug: /compute-engine/guides/patterns-and-rules/ ---Recognizing patterns and applying rules is a powerful symbolic computing tool to identify and manipulate the structure of expressions.
Wildcards are placeholders symbols in an expression. They start with a _
.
The "_"
universal wildcard matches anything that is in the corresponding
position in an expression.
The "__"
wildcard matches any sequence of 1 or more expressions in its
corresponding position. It is useful to capture the arguments of a function.
The "___"
wildcard matches any sequence of 0 or more expressions in its
corresponding position.
A wildcard symbol may include a name which is used to capture the matching
expression, for example _a
.
When using a named wildcard, all instances of the named wildcard must match. In contrast, an un-named wildcard
(a universal wildcard such as "_"
"__"
or "___"
) can be used multiple
times to match different values.
A pattern is an expression which can include one or more placeholders in the form of wildcards.
Patterns are similar to Regular Expressions in traditional programming languages but they are tailored to deal with MathJSON expressions instead of strings.
Given a pattern and an expression the goal of pattern matching is to find a substitution for all the wildcards such that the pattern becomes the expression.
An expression is said to match a pattern if there exists a set of values such that replacing the wildcards with those values is equal to the expression. This set of values is called a substitution.
For example, the pattern ["Add", 3, "_c"]
becomes the expression
["Add", 3, "x"]
by replacing the wildcard "_c"
with "x"
. The substitution
is therefore {_c : "x"}
.
On the other hand, the expression ["Divide", "x", 2]
does not match the
pattern ["Add", 3, "_c"]
: no substitution exists to transform the pattern
into the expression by substituting the wildcards.
To check if an expression matches a pattern, use the
_expression_.match(_pattern_)
method.
If there is no match, the method returns null
.
If there is a match, a Substitution
object literal with
keys corresponding to the matching named wildcards is returned.
If no named wildcards are used and there is a match, an empty object literal is returned.
For convenience, the pattern argument can be an unboxed MathJSON expression.
The commutativity of functions is taken into account when matching patterns.
const pattern = ce.box(["Add", "_", "x"]);
console.log("x+1 ➔", ce.box(["Add", 1, "x"]).match(pattern));
// ➔ { } : the expression matches the pattern
console.log("x+42 ➔", ce.box(["Add", "x", 42]).match(pattern));
// ➔ { } : the expression matches the pattern by commutativity
console.log("5*x ➔", ce.box(["Multiply", 5, "x"]).match(pattern));
// ➔ null : the expression does not match the pattern
By default, the expr.match()
method will match some variants of the same
expression, for example x+_a
and x
are considered to match (with the
substitution {_a : 0}
).
const pattern = ce.box(["Add", "x", "_a"]);
const expr = ce.box("x");
console.log("x ➔", expr.match(pattern));
// ➔ { _a: 0 } : the expression matches the pattern
To prevent the matching of variants, set the exact
property to true
.
const pattern = ce.box(["Add", "x", "_a"]);
const expr = ce.box("x");
console.log("exact: x ➔", expr.match(pattern, {exact: true}));
// ➔ null : the expression does not match the pattern
The variants can be applied to the whole expression or to sub-expressions.
const pattern = ce.box(["Add", ["Multiply", "_a", "x"], "_b"]);
console.log("x ➔", ce.box("x").match(pattern));
// ➔ { _a: 1, _b: 0 } : the expression matches the pattern
By default, the expr.match()
method does not consider sub-expressions:
it is not recursive.
const pattern = ce.box(["Add", "_", "x"]);
const expr = ce.box(["Multiply", 2, ["Add", 1, "x"]]);
console.log("2(1+x) ➔", expr.match(pattern));
// ➔ null : the expression does not match the pattern
To match sub-expressions recursively, set the recursive
property to
true
.
const pattern = ce.box(["Add", "_", "x"]);
const expr = ce.box(["Multiply", 2, ["Add", 1, "x"]]);
console.log("recursive: 2(1+x) ➔", expr.match(pattern, {recursive: true}));
// ➔ { } : the expression matches the pattern
If a named wildcard is referenced multiple times in a pattern, all its values must match.
console.log(ce.box(["Add", 1, "x"]).match(ce.box(["Add", '_a', '_a'])));
// ➔ null
console.log(ce.box(["Add", "x", "x"]).match(ce.box(["Add", '_a', '_a'])));
// ➔ { _a: "x" }
Wildcards can be used to capture the head of functions:
console.log(ce.box(["Add", 1, "x"]).match(ce.box(["_f", "__args"])));
// ➔ { _f: "Add", __args: ["Sequence", [1, "x"]] }
The return value of the expr.match()
function is a Substitution
object: a
mapping from wildcards to expressions.
If there is no match, expr.match()
returns null
.
To apply a substitution to a pattern, and therefore recover the expression
it was derived from, use the subs()
function.
const expr = ce.box(["Add", 1, "x"]);
const pattern = ce.box(["Add", 1, "_a"]);
const subs = expr.match(pattern);
console.log(subs);
// ➔ { _a: "x" }
pattern.subs(subs).print();
// ➔ ["Add", 1, "x"]
A rewrite rule is an object with three properties:
match
: a matching patternreplace
: a substitution patterncondition
: an optional condition predicate
To apply a set of rules to an expression, call the expr.replace()
function.
Each rule in the ruleset is applied to the expression in turn. If a rule matches, the expression is replaced by the substitution pattern of the rule.
const squareRule = {
match: ["Multiply", "_x", "_x"],
replace: ["Square", "_x"],
};
const expr = ce.box(["Multiply", 7, 7], { canonical: false });
(expr.replace(squareRule) ?? expr).print();
// ➔ ["Square", 7]
The expr.replace()
function continues applying all the rules in the ruleset
until no rules are applicable.
If expr
is not canonical, the result of the replace operation is not
canonical either.
The expr.simplify()
function applies a collection of built-in rewrite rules.
You can define your own rules and apply them using expr.replace()
.
If a pattern does not contain any named wildcards and only symbols, the
expr.subs()
function can be used to replace all occurrences of matching symbols.
const expr = ce.box(["Add", ["Multiply", "a", "x"], "b"]);
expr.replace([
{ match: "a", replace: 2 },
{ match: "b", replace: 3 }
],
{ recursive: true }
)?.print();
// ➔ 2x + 3
expr.subs({"a": 2, "b": 3}).print();
// ➔ 2x + 3
title: Changelog - Compute Engine sidebar_label: Changelog slug: /compute-engine/changelog/ toc_max_heading_level: 2
import ChangeLog from '@site/src/components/ChangeLog';
--- title: Linear Algebra slug: /compute-engine/reference/linear-algebra/ --- [Linear algebra](https://en.wikipedia.org/wiki/Linear_algebra) is the branch of mathematics that studies vector spaces and linear transformations between them like adding and scaling. It uses matrixes to represent linear maps. Linear algebra is widely used in science and engineering.In the Compute Engine matrices are represented as lists of lists.
For example the matrix above is represented as the following list of lists:
["List", ["List", 1, 3, ["List", 5, 0]]]
An axis is a dimension of a tensor. A vector has one axis, a matrix has two axes, a tensor (multi-dimensional matrix) has more than two axes.
The shape of a tensor is the length of each of its axes. For example, a
matrix with 3 rows and 4 columns has a shape of (3, 4)
.
The rank of a tensor is the number of axes of the tensor. For example, a matrix has a rank of 2. Note that rank is sometimes used with a different meaning. For example, the rank of a matrix can also be defined as the maximum number of linearly independent rows or columns of the matrix. In the Compute Engine, rank is always used with the meaning of the number of axes.
In the Compute Engine, matrices are stored in row-major order. This means that the first element of the outer axis is the first row of the matrix, the second element of the list is the second row of the matrix, etc.
Since
matrixes are List
collections, some collection operations
can also be applied to them such as At
, Fold
and Map
.
An extension of linear algebra is tensor algebra which studies tensors, which are multidimensional arrays.
For example, a grayscale image can be represented by a matrix of grayscale values. But a color image is represented by a rank 3 tensor, an array of RGB triplets. Tensors are also represented as nested lists.
The Compute Engine provides a number of functions for working with matrices.
Vectors (row vectors) are represented as lists, that is an expression with the
head List
.
Matrixes are represented as lists of lists.
Tensors (multi-dimensional matrixes) are represented as nested lists.
:::info[Note]
Tensors are represented internally using an optimized format that is more
efficient than nested lists. Because of this, some operations on tensors
such as Reshape
and Transpose
can be done in O(1) time.
:::
Vector
is a convenience function that interprets a list of elements as a
column vector.
Matrix
is an optional "tag" inert function that is used to influence the visual
representation of a matrix. It has not impact on the value of the matrix.
In LaTeX notation, a matrix is represented with "environments" (with command
\begin
and \end
) such as pmatrix
or bmatrix
.:
In LaTeX, each column is separated by an &
and each row is separated by
\
.
x-1, ...x-2
Vector
interprets the elements x-1... as a column vector
This is essentially a shortcut for ["Matrix", ["List", ["List", _x-1_], ["List, _x-2_], ...]]]
.
["Vector", 1, 3, 5, 0]
A row vector can be represented with a simple list or a tuple.
["List", 1, 3, 5, 0]
matrix
matrix, delimiters, columns-format
Matrix
is an inert function: its value is the value of its first argument.
It influences the visual representation of a matrix.
matrix is a matrix represented by a list of rows. Each row is represented by a list of elements.
delimiters is an optional string of two characters. The first character represent the opening delimiter and the second character represents the closing delimiter.
The delimiters can be any of the following characters:
(
,)
,[
,]
,{
,}
,<
,>
⟦
(U+27E6
),⟧
(U+27E7
)|
,‖
(U+2016
)\
⌈
(U+2308
),⌉
(U+2309
),⌊
(U+230A
),⌋
(U+230B
)⌜
(U+231C
),⌝
(U+231D
),⌞
(U+231E
),⌟
(U+231F
)⎰
(U+23B0
),⎱
(U+23B1
).
In addition, the character .
can be used to indicate no delimiter.
Some commom combinations may be represented using some standard LaTeX environments:
Delimiters | LaTeX Environment | Example |
---|---|---|
() |
pmatrix |
$$ \begin{pmatrix} a & b \ c & d \end{pmatrix} $$ |
[] |
bmatrix |
$$ \begin{bmatrix} a & b \ c & d \end{bmatrix} $$ |
{} |
Bmatrix |
$$ \begin{Bmatrix} a & b \ c & d \end{Bmatrix} $$ |
` | ` | |
‖‖ |
Vmatrix |
$$ \begin{Vmatrix} a & b \ c & d \end{Vmatrix} $$ |
{. |
dcases |
$$ \begin{dcases} a & b \ c & d \end{dcases} $$ |
.} |
rcases |
$$ \begin{rcases} a & b \ c & d \end{rcases} $$ |
columns_format is an optional string indicating the format of each column.
A character =
indicates a centered column, <
indicates a left-aligned
column, and >
indicates a right-aligned column.
A character of |
indicate a solid line between two
columns and :
indicate a dashed lines between two columns.
matrix
Returns the shape of a matrix, a tuple of the lengths of the matrix along each of its axis.
A list (or vector) has a single axis. A matrix has two axes. A tensor has more than two axes.
For a scalar, Shape
returns an empty tuple.
["Shape", 5]
// ➔ ["Tuple"]
["Shape", ["List", 5, 2, 10, 18]]
// ➔ ["Tuple", 4]
["Shape", ["List", ["List", 5, 2, 10, 18], ["List", 1, 2, 3]]]
// ➔ ["Tuple", 2, 4]
Note: The shape of a matrix is also sometimes called "dimensions". However, this terminology is ambiguous because the word "dimension" is also used to refer to the length of a matrix along a specific axis.
matrix
Returns the number of axes of a matrix.
A scalar (a number, for example) has rank 0.
A vector or list has rank 1.
A matrix has rank 2, a tensor has rank 3, etc.
The rank is the length of the shape of the tensor.
["Rank", 5]
// ➔ 0
["Rank", ["List", 5, 2, 10, 18]]
// ➔ 1
["Rank", ["List", ["List", 5, 2, 10], ["List", 1, 2, 3]]]
// ➔ 2
matrix, index-1, index-2, ...
Returns the element of the matrix at the specified indexes.
index-1, ... is a sequence of integers, one for each axis of the matrix.
Indexes start at 1. Negative indexes count elements from the end. A negative
index is equivalent to adding the length of the axis to the index. So -1
is
the last element of the axis, -2
is the second to last element, etc.
["At", ["List", 5, 2, 10, 18], 3]
// ➔ 10
["At", ["List", ["List", 5, 2, 10, 18], ["List", 1, 2, 3]], 2, 3]
// ➔ 3
["At", ["List", ["List", 5, 2, 10, 18], ["List", 1, 2, 3]], 2, -1]
// ➔ 3
In a list (or vector), there is only one axis, so there is only one index.
In a matrix, the first index is the row, the second is the column.
In LaTeX, accessing the element of a matrix is done with a subscript or square brackets following a matrix.
matrix
Returns a list of all the elements of the matrix, recursively, in row-major order.
This function can also be applied to any collection.
Only elements with the same head as the collection are flattened.
Matrixes have a head of List
, so only other List
elements
are flattened.
["Flatten", ["List", ["List", 5, 2, 10, 18], ["List", 1, 2, 3]]]
// ➔ ["List", 5, 2, 10, 18, 1, 2, 3]
Flatten
is similar to the APL ,
Ravel operator or numpy.ravel
Numpy.
matrix, shape
Returns a matrix with the specified shape.
matrix can be a list, a matrix, a tensor or a collection.
shape is a tuple of integers, one for each axis of the result matrix.
Reshape
can be used to convert a list or collection into a matrix.
["Reshape", ["Range", 9], ["Tuple", 3, 3]]
// ➔ ["List", ["List", 1, 2, 3], ["List", 4, 5, 6], ["List", 7, 8, 9]]
This is similar to the APL ⍴
Reshape operator or numpy.reshape
Numpy.
The result may have fewer or more elements than the original tensor.
When reshaping, the elements are taken from the original tensor in row-major
order, that is the order of elements as returned by Flatten
.
If the result has fewer elements, the elements are dropped from the end of the element list. If the result has more elements, the lists of elements is filled cyclically.
This is the same behavior as APL, but other environment may behave differently.
For example, by default Mathematica ArrayReshape
will fill the missing elements
with zeros.
matrix
Returns the transpose of the matrix.
["Transpose", ["List", ["List", 1, 2, 3], ["List", 4, 5, 6]]]
// ➔ ["List", ["List", 1, 4], ["List", 2, 5], ["List", 3, 6]]
tensor, axis-1, axis-2
Swap the two specified axes of the tensor. Note that axis indexes start at 1.
matrix
Returns the conjugate transpose of the matrix, that is the transpose of the matrix with all its (complex) elements conjugated. Also known as the Hermitian transpose.
["ConjugateTranspose", ["List", ["List", 1, 2, 3], ["List", 4, 5, 6]]]
// ➔ ["List", ["List", 1, 4], ["List", 2, 5], ["List", 3, 6]]
matrix, axis-1, axis-2
Swap the two specified axes of the matrix. Note that axis indexes start at 1. In addition, all the (complex) elements of the tensor are conjugated.
matrix
Returns the inverse of the matrix.
["Inverse", ["List", ["List", 1, 2], ["List", 3, 4]]]
// ➔ ["List", ["List", -2, 1], ["List", 1.5, -0.5]]
matrix
Returns the Moore-Penrose pseudoinverse of the matrix.
["PseudoInverse", ["List", ["List", 1, 2], ["List", 3, 4]]]
// ➔ ["List", ["List", -2, 1], ["List", 1.5, -0.5]]
matrix
Returns the diagonal of the matrix, that is the list of all the elements on the diagonal of the matrix.
["Diagonal", ["List", ["List", 1, 2], ["List", 3, 4]]]
// ➔ ["List", 1, 4]
matrix
Returns the determinant of the matrix.
["Determinant", ["List", ["List", 1, 2], ["List", 3, 4]]]
// ➔ -2
matrix
Returns the adjugate matrix of the input matrix, that is the inverse of the cofactor matrix.
The cofactor matrix is a matrix of the determinants of the minors of the matrix multiplied by $$ (-1)^{i+j} $$. That is, for each element of the matrix, the cofactor is the determinant of the matrix without the row and column of the element.
["AdjugateMatrix", ["List", ["List", 1, 2], ["List", 3, 4]]]
// ➔ ["List", ["List", 4, -2], ["List", -3, 1]]
matrix
Returns the trace of the matrix, the sum of the elements on the diagonal of the matrix. The trace is only defined for square matrices. The trace is also the sum of the eigenvalues of the matrix.
["Trace", ["List", ["List", 1, 2], ["List", 3, 4]]]
// ➔ 5
A **set** is a collection of distinct elements.
The Compute Engine standard library includes definitions for common numeric sets. Checking if a value belongs to a set is done using the Element
expression, or the \in
) command in LaTeX.
ce.box(['Element', 3.14, 'NegativeIntegers']).evaluate().print();
// ➔ False
ce.parse("42 \\in \\Z").evaluate().print();
// ➔ True
Checking if an element is in a set is equivalent to checking if the type of the element matches the type associated with the set.
const x = ce.box(42);
x.type;
// ➔ "finite_integer"
x.type.matches("integer");
// ➔ true
x.isInteger;
// ➔ true
ce.box(['Element', x, 'Integers']).evaluate().print();
// ➔ True
ce.parse("42 \\in \\Z").evaluate().print();
// ➔ True
Symbol | Notation | Definition | |
---|---|---|---|
EmptySet |
\varnothing or \emptyset
|
$$ \varnothing $$ or $$ \emptyset $$ | A set that has no elements |
Numbers |
\mathrm{Numbers} |
$$ \mathrm{Numbers} $$ | Any number, real, imaginary, or complex |
ComplexNumbers |
\C |
$$ \C $$ | Real or imaginary numbers |
ExtendedComplexNumbers |
\overline\C |
$$ \overline\C $$ | Real or imaginary numbers, including |
ImaginaryNumbers |
\imaginaryI\R |
$$ \imaginaryI\R $$ | Complex numbers with a non-zero imaginary part and no real part |
RealNumbers |
\R |
$$ \R $$ | Numbers that form the unique Dedekind-complete ordered field $$ \left( \mathbb{R} ; + ; \cdot ; \lt \right) $$, up to an isomorphism (does not include |
ExtendedRealNumbers |
\overline\R |
$$ \overline\R $$ | Real numbers extended to include |
Integers |
\Z |
$$ \Z$$ | Whole numbers and their additive inverse |
ExtendedIntegers |
\overline\Z |
$$ \overline\Z$$ | Integers extended to include |
RationalNumbers |
\Q |
$$ \Q $$ | Numbers which can be expressed as the quotient $$ \nicefrac{p}{q}$$ of two integers |
ExtendedRationalNumbers |
\overline\Q |
$$ \overline\Q$$ | Rational numbers extended to include |
NegativeNumbers |
\R_{<0} |
$$ \R_{<0} $$ | Real numbers $$ \lt 0 $$ |
NonPositiveNumbers |
\R_{\leq0} |
$$ \R_{\leq0} $$ | Real numbers $$ \leq 0 $$ |
NonNegativeNumbers |
\R_{\geq0} |
$$ \R_{\geq0} $$ | Real numbers $$ \geq 0 $$ |
PositiveNumbers |
\R_{>0} |
$$ \R_{>0} $$ | Real numbers $$ \gt 0$$ |
NegativeIntegers |
\Z_{<0} |
$$ \Z_{<0} $$ | Integers $$ \lt 0$$, |
NonPositiveIntegers |
\Z_{\le0} |
$$ \Z_{\le0} $$ | Integers $$ \leq 0 |
NonNegativeIntegers |
\N |
$$ \N $$ | Integers $$ \geq 0 |
PositiveIntegers |
\N^* |
$$ \N^* $$ | Integers $$ \gt 0 |
New sets can be defined using one of the following operators.
Function | Operation | |
---|---|---|
CartesianProduct |
$$ \operatorname{A} \times \operatorname{B} $$ | A.k.a the product set, the set direct product or cross product. Q173740 |
Complement |
$$ \operatorname{A}^\complement $$ | The set of elements that are not in $$ \operatorname{A} |
Intersection |
$$ \operatorname{A} \cap \operatorname{B} $$ | The set of elements that are in The following macros are not allowed: operatorname
$$\operatorname{A}$$ The following macros are not allowed: operatorname
$$\operatorname{B}$$ |
Union |
$$ \operatorname{A} \cup \operatorname{B} $$ | The set of elements that are in The following macros are not allowed: operatorname
$$\operatorname{A}$$ The following macros are not allowed: operatorname
$$\operatorname{B}$$ |
Set |
Set builder notation | |
SetMinus |
$$ \operatorname{A} \setminus \operatorname{B} $$ | Q18192442 |
SymmetricDifference |
$$ \operatorname{A} \triangle \operatorname{B} $$ | Disjunctive union = $$ (\operatorname{A} \setminus \operatorname{B}) \cup (\operatorname{B} \setminus \operatorname{A})$$ Q1147242 |
To check the membership of an element in a set or the relationship between two sets using the following operators.
Function | Notation | |
---|---|---|
Element |
$$ x \in \operatorname{A} $$ | x \in \operatorname{A} |
NotElement |
$$ x \not\in \operatorname{A} $$ | x \not\in \operatorname{A} |
NotSubset |
$$ \operatorname{A} \nsubset \operatorname{B} $$ | \operatorname{A} \nsubset \operatorname{B} |
NotSuperset |
$$ \operatorname{A} \nsupset \operatorname{B} $$ | \operatorname{A} \nsupset \operatorname{B} |
Subset |
$$ \operatorname{A} \subset \operatorname{B} $$ $$ \operatorname{A} \subsetneq \operatorname{B} $$ $$ \operatorname{A} \varsubsetneqq \operatorname{B} $$ |
\operatorname{A} \subset \operatorname{B} \operatorname{A} \subsetneq \operatorname{B} \operatorname{A} \varsubsetneqq \operatorname{B}
|
SubsetEqual |
$$ \operatorname{A} \subseteq \operatorname{B} $$ | \operatorname{A} \subseteq \operatorname{B} |
Superset |
$$ \operatorname{A} \supset \operatorname{B} $$ $$ \operatorname{A} \supsetneq \operatorname{B} $$ $$ \operatorname{A} \varsupsetneq \operatorname{B} $$ |
\operatorname{A} \supset \operatorname{B} \operatorname{A} \supsetneq \operatorname{B} \operatorname{A} \varsupsetneq \operatorname{B}
|
SupersetEqual |
$$ \operatorname{A} \supseteq \operatorname{B} $$ | \operatorname{A} \supseteq \operatorname{B} |
The MathJSON Standard Library is a
collection of definitions for symbols such as Pi
, Add
,
Sin
, Power
, List
, etc...
In this guide we discuss how to augment the MathJSON Standard Library with your own symbols.
You may also be interested in **augmenting the LaTeX dictionary** which defines how LaTeX is parsed from and serialized to MathJSON.This is useful if you want to add support for custom LaTeX macros that you'd like to parse to MathJSON.
When an symbol such as Pi
or Sin
is encountered in an expression, the
Compute Engine will look up its definition in the set of known
symbols, including the Standard Library.
If a matching definition is found, it will be bound to the symbol and used later to evaluate the expression.
If no definition is found, an automatic declaration will be made of the
symbol with a type unknown
or a more specific type if the context allows it.
To provide a more explicit definition for the symbol, you can define it
using a LaTeX expression, or an explicit declaration using the ce.declare()
method.
The declaration of a symbol is done within a lexical scope. A scope is a hierarchical collection of definitions.
Read more about **scopes**The simplest way to define a new symbol is to use LaTeX.
For example, to define a new symbol
ce.parse("m := 42").evaluate();
ce.parse("m").value.print();
// ➔ 42
Note: the assignment expression must be evaluated to take effect.
To define a new function
ce.parse("f(x) := 2x").evaluate();
ce.parse("f(3)").evaluate().print();
// ➔ 6
The \mapsto
operator is an alternative syntax to define a function:
ce.parse("f := x \\mapsto 2x").evaluate();
ce.parse("f(3)").evaluate().print();
// ➔ 6
To define multiletter symbols, use the \operatorname{}
command:
ce.parse('\\operatorname{double}(x) := 2x').evaluate().print();
ce.parse('\\operatorname{double}(3)').evaluate().print();
// ➔ 6
Note: you can also use the \mathrm{}
or \mathit{}
commands to wrap
multiletter symbols.
The LaTeX identifiers are mapped to MathJSON symbols. For example,
the LaTeX \operatorname{double}
is mapped to the MathJSON symbol double
.
console.info(ce.parse('\\operatorname{double}(3)').json);
// ➔ ["double", 3]
To have more control over the definition of a symbol use
the ce.declare()
method.
When declaring a symbol, you can specify the type of the symbol, its value and other properties.
// Declaring a symbol "m"
ce.declare("m", "integer");
// Declaring a function "f"
ce.declare("f", {
signature: "number -> number",
evaluate: ce.parse("x \\mapsto 2x"),
});
To declare a symbol use the ce.declare()
method with the name of the
symmbol as the first argument and a type as the second argument.
ce.declare("n", "integer");
The type specifies the
valid values of the symbol. For example, boolean
, integer
, rational
, function
, string
etc.. Learn more about types.
Alternatively, you can provide an object literal with the additional properties
value
, type
, isConstant
, and more.
ce.declare("m", {
type: "integer",
value: 42,
});
If you do not provide a type
property for a symbol, the type will be
inferred from the value of the symbol. If no type and no value are
provided, the type of the symbol will be unknown
.
As a shorthand, a symbol can be declared by assigning it a value using ce.assign()
:
ce.assign("m", 42);
If the symbol was not previously defined, this is equivalent to:
ce.declare("m", { value: 42 });
Alternatively, you can set the value of a symbol using the value
property:
ce.box("m").value = 42;
To prevent the value of a symbol from being changed, set the constant
property to true
:
ce.declare("m_e", {
value: 9.1e-31,
constant: true,
});
To declare a function, associate an evaluate
handler, which
is the body of the function, with a symbol.
ce.declare("double", {
evaluate: ce.parse("x \\mapsto 2x")
});
:::caution[Caution]
The first argument of declare()
is a MathJSON symbol, not a LaTeX command.
For example, use double
instead of \operatorname{double}
.
:::
The evaluate handler can be either a MathJSON expression as above or a JavaScript function.
ce.declare("double", { evaluate: ([x]) => x.mul(2) });
The signature of the evaluate
handler is (args[], options)
, where:
args
: an array of the arguments that have been applied to the function. Each argument is a boxed expression. The array may be empty if there are no arguments.options
: an object literal which includes anengine
property that is the Compute Engine instance that is evaluating the expression and anumericApproximation
property that is true if the result should be a numeric approximation.
Since args
is an array, you can use destructuring to get the arguments:
ce.declare("double", { evaluate: (args) => args[0].mul(2) });
// or
ce.declare("double", { evaluate: ([x]) => x.mul(2) });
In addition to the evaluate
handler the function definition can include
a signature
type that describes the arguments and return value of the
function.
ce.declare("double", {
signature: "number -> number",
evaluate: ([x]) => x.mul(2),
});
See FunctionDefinition
for more details on the other handlers and
properties that can be provided when defining a function.
To define a function without specifying a body for it, specify
the signature of the function as the second argument of ce.declare()
or
use the "function"
type.
ce.declare("double", "function");
Functions that do not have an evaluate handler or a function literal as a value remain unchanged when evaluated.
You can set the body of the function later using ce.assign()
:
When using ce.assign()
to define a function, the value can be a JavaScript
function, a MathJSON expression or a LaTeX expression.
ce.assign("double", ([x]) => x.mul(2));
ce.assign("double", ["Function", ["Multiply", "x", 2], "x"]);
ce.assign("double",ce.parse("x \\mapsto 2x"));
Overloading is the ability to define multiple functions with the same name.
To overload a function, use the ce.declare()
methods.
For example, to overload the Sqrt
function to return NaN
for
non-real numbers, use the following code:
const originalSqrtDefinition = ce.box('Sqrt').operatorDefinition!;
ce.declare('Sqrt', {
...originalSqrtDefinition,
evaluate: (x, options) => {
const y = originalSqrtDefinition.evaluate!(x, options);
return y?.isReal ? y : ce.NaN;
},
});
In general, re-declaring a function in the same scope is not allowed and
will throw an error. However, the standard functions are in a system
scope
so a new declaration in the global
scope or a child scope will
override the original declaration.
To define multiple functions and symbols, use the ce.declare()
method
with a dictionary of definitions.
:::info[Note]
The keys to ce.declare()
(m
, f
, etc...) are MathJSON
symbols, not LaTeX commands. For example, if you have a symbol α
, use
alpha
, not \alpha
:::
ce.declare({
m: { type: "number", value: 5 },
f: { type: "function" },
g: { type: "function" },
Smallfrac: {
signature: "(number, number) -> number",
evaluate: ([x,y]) => x.div(y),
},
});
To assign multiple functions and symbols, use the ce.assign()
method with
a dictionary of values.
ce.assign({
"m": 10,
"f": ce.parse("x \\mapsto x^2 + x + 41"),
"g": ce.parse("t \\mapsto t^3 + t^2 + 17"),
});
The Compute Engine essentially performs computation by applying rewriting rules to a MathJSON expression.
There are three common transformations that can be applied to an expression:
Transformation | |
---|---|
expr.simplify() |
Eliminate constants and common sub-expressions. Use available assumptions to determine which rules are applicable. Limit calculations to exact results. |
expr.evaluate() |
Calculate the exact value of an expression. Replace symbols with their value. |
expr.N() |
Calculate a numeric approximation of an expression using floating point numbers. |
A key difference between expr.evaluate()
and expr.N()
is that the former
will use the exact value of symbols, while the latter will use their numeric
approximation. An exact value is a rational number, an integer, the square root
of an integer and some constants such as \(\pi\) or \(e\). A numeric
approximation is a floating point number.
expr.simplify() |
expr.evaluate() |
expr.N() |
|
---|---|---|---|
Use assumptions on symbols | |||
Exact calculations | |||
Floating-point approximations |
Read more about Simplify
Read more about Evaluate
Read more about Numerical Evaluation
Other operations can be performed on an expression: comparing it to a pattern, replacing part of it, and applying conditional rewrite rules.
const expr = ce.parse('3x^2 + 2x^2 + x + 5');
console.log(expr, '=', expr.simplify());
There are two useful ways to compare symbolic expressions:
- structural equality
- mathematical equality
Structural equality (or syntactic equality) considers the symbolic structure used to represent an expression.
The symbolic structure of an expression is the tree of symbols and functions that make up the expression.
For example, the symbolic structure of \(2 + 1\) is a sum of two terms,
the first term is the number 2
and the second term is the number 1
.
The symbolic structure of \(3\) is a number 3
.
The symbolic structure of \(2 + 1\) and \(3\) are different, even though they represent the same mathematical object.
The lhs.isSame(rhs)
function returns true if lhs
and rhs
are structurally
exactly identical, that is each sub-expression is recursively identical in lhs
and rhs
.
- \(1 + 1 \) and \( 2 \) are not structurally equal, one is a sum of two integers, the other is an integer
- \( (x + 1)^2 \) and \( x^2 + 2x + 1 \) are not structural equal, one is a power of a sum, the other a sum of terms.
const a = ce.parse('2 + 1');
const b = ce.parse('3');
console.log('isSame?', a.isSame(b));
By default, when parsing or boxing an expression, they are put in canonical form. For example, fractions are automatically reduced to their simplest form, and arguments are sorted in a standard way.
The expressions \( \frac{1}{10} \) and \( \frac{2}{20} \) are structurally equal because they get put into a canonical form when parsed, in which the fractions are reduced.
Similarly, \( x^2 - 3x + 4 \) and \( 4 - 3x + x^2 \) are structurally equal
(isSame
returns true) because the arguments of the sum are sorted in a standard
way.
To compare two expressions without canonicalizing them, parse or box
them with the canonical
option set to false
.
const a = ce.parse('\\frac{1}{10}');
const b = ce.parse('\\frac{2}{20}');
console.log('Canonical isSame?', a.isSame(b));
const aPrime = ce.parse('\\frac{1}{10}', {canonical: false});
const bPrime = ce.parse('\\frac{2}{20}', {canonical: false});
console.log('Non-canonical isSame?', aPrime.isSame(bPrime));
In some cases you may want to compare two expressions with a weak form of canonicalization, for example to ignore the order of the arguments of a sum.
You can achieve this by comparing the expressions in their canonical order:
ce.box(["CanonicalForm", ["Add", 1, "x"], "Order"]).isSame(
["CanonicalForm", ["Add", "x", 1], "Order"]
)
It turns out that comparing two arbitrary mathematical expressions is a complex problem.
In fact, Richardson's Theorem proves that it is impossible to determine if two symbolic expressions are identical in general.
However, there are many cases where it is possible to make a comparison between two expressions to check if they represent the same mathematical object.
The lhs.isEqual(rhs)
function return true if lhs
and rhs
represent the
same mathematical object.
If lhs
and rhs
are numeric expressions, they are evaluated before being
compared. They are considered equal if the absolute value of the difference
between them is less than ce.tolerance
.
The expressions \( x^2 - 3x + 4 \) and \( 4 - 3x + x^2 \) will be considered
equal (isEqual
returns true) because the difference between them is zero,
i.e. \( (x^2 - 3x + 4) - (4 - 3x + x^2) \) is zero once the expression has
been simplified.
Note that unlike expr.isSame()
, expr.isEqual()
can return true
, false
or
undefined
. The latter value indicates that there is not enough information to
determine if the two expressions are mathematically equal.
const a = ce.parse('1 + 2');
const b = ce.parse('3');
console.log('isEqual?', a.isEqual(b));
lhs === rhs |
If true, same box expression instances |
lhs.isSame(rhs) |
Structural equality |
lhs.isEqual(rhs) |
Mathematical equality |
lhs.match(rhs) !== null |
Pattern match |
lhs.is(rhs) |
Synonym for lhs.isSame(rhs) , but the argument of is() can be a boolean or number. |
ce.box(["Equal", lhs, rhs]).evaluate() |
Synonym for lhs.isEqual(rhs) |
ce.box(["Same", lhs, rhs]).evaluate() |
Synonym for lhs.isSame(rhs) |
To replace a symbol in an expression use the subs()
function.
The argument of the subs()
function is an object literal. Each key/value
pairs is a symbol and the value to be substituted with. The value can be either
a number or a boxed expression.
let expr = ce.parse('\\sqrt{\\frac{1}{x+1}}');
console.log(expr.json);
expr = expr.subs({x: 3});
console.log("Substitute x -> 3\n", expr.json);
console.log("Numerical Evaluation:", expr.N());
There are a number of operations that can be performed on an expression:
- creating an expression from a raw MathJSON expression or from a LaTeX string
- simplifying an expression
- evaluating an expression
- applying a substitution to an expression
- applying conditional rewrite rules to an expression
- checking if an expression matches a pattern
- checking if an expression is a number, a symbol, a function, etc...
- checking if an expression is zero, positive, negative, etc...
- checking if an expression is an integer, a rational, etc...
- and more...
We've introduced some of these operations in this guide, but there are many more that are available.
Read more about Expressions, their properties and methods
To check if an expression matches a pattern, apply a substitution to some elements in an expression or apply conditional rewriting rules to an expression.
Functions are first-class values in the Compute Engine. This means that functions can be passed as arguments to other functions, returned from functions, and assigned to variables.The standard library can be extended with your own functions.
Read more about adding new definitions to the Compute Engine.Term | Definition |
---|---|
Function Expression | An expression representing a function application where a function (or operator) is evaluated with arguments (the arguments are applied to the function). For example ["Add", 1, 2] applies the arguments 1 and 2 to the operator Add . |
Function Signature | A type describing the function's inputs and outputs, e.g. (real, integer) -> boolean |
Function Literal | A first-class function value, defined using a construct like ["Function", body, params...] , which may or may not capture variables from its lexical scope. |
Shorthand Function Literal | A compact way to write a function literal using placeholders (e.g. _ or _2 ) instead of explicitly listing parameters, e.g. ["Add", "_", 1] desugared to ["Function", ["Add", "_", 1], "_"] . |
Closure | A function literal that captures one or more free variables from its defining lexical scope, preserving their values at the time of definition. The capture happens by simply referencing the variables in the function body. |
Anonymous Function | A function that is not bound to a symbol, often used as an argument to other functions. |
A function literal is a first-class function value, defined using a
["Function"]
expression. It can be passed as an argument to other functions,
returned from a function, or assigned to a variable.
The ["Function"]
expression takes a body and a list of parameters.
["Sum", ["Function", ["Multiply", "x", 2], "x"]]
To specify a function literal with LaTeX use the \mapsto
command:
["Function", ["Multiply", "x", 2], "x"]
["Function", ["Add", ["Multiply", "x", 2], "y"], "x", "y"]
The examples in this section define functions as a simple expression, but function literals can include more complex control structures, including blocks, local variables, loops and conditionals.
For example, here's a simple "clamp" function, using a ["Block"]
expression.
["Function",
["Block",
["Assign", "x", ["Max", "x", "min"]],
["Min", "x", "max"]
],
"x", "min", "max"
]
Learn more about control structures.
A shorthand function literal is a compact way to write a function literal without explicitly listing parameters.
A shorthand function literal can use either wildcards (_
, _2
, etc...) or
unknowns (symbols that have no value) as implicit parameters.
The shorthand function literal is desugared to a function literal.
For example the shorthand function literal ["Multiply", "_", 2]
is desugared
to ["Function", ["Multiply", "_", 2], "_"]
.
To use wildcards in LaTeX, they must be wrapped with an \operatorname
command except for \_
.
["Add", "_", "_2"]
The shorthand function literal ["Multiply", "x", 2]
which uses the unknown x
is equivalent to the function literal ["Function", ["Multiply", "x", 2], "x"]
.
When using this form, make sure that the symbol x
is not defined in the current scope.
A symbol which is the name of an operator (for example Sin
) is also a valid
function literal shorthand.
This expression will apply the Sin
function to the elements of xs
.
["Map", "xs", "Sin"]
It is equivalent to ["Map", "xs", ["Sin", "_"]]
which is desugared to
["Map", "xs", ["Function", ["Sin", "_"], "_"]]
.
A function that is not bound to a symbol is called an anonymous function.
Anonymous functions are frequently used as arguments to other functions.
In the example below, the second argument of the Map
function is an
anonymous function expressed as a function literal that multiplies its argument by 2
.
["Map", "xs", ["Function", ["Multiply", "x", 2], "x"]]
The same function can be expressed using a shorthand function literal, which
uses _
as a wildcard for the parameter.
["Map", "xs", ["Multiply", "_", 2]]
To apply a function literal to some arguments use an ["Apply"]
expression.
["Apply", ["Function", ["Add", 2, "x"], "x"], 11]
// ➔ 22
["Apply", ["Add", 2, "_"], 4]
// ➔ 6
["Apply", "Power", 2, 3]
// ➔ 8
The first argument of Apply
is a function literal. The rest of the arguments are the
arguments that will be applied to the function literal.
A closure is a function literal that captures one or more free variables
from its defining lexical scope, preserving their values at the time of definition.
The capture happens by simply referencing the variables in the function body.
For example, the following function literal captures the variable a
from its
lexical scope:
["Function", ["Add", "a", "_"], "_"]
This function literal captures the value of a
at the time of definition, and
when the function is applied, it will use that value of a
in the computation.
["Block"
["Assign", "f",
["Block",
["Declare", "a", "integer"],
["Assign", "a", 10],
["Function", ["Add", "a", "_"], "_"]
]
]
["Declare", "a", "integer"],
["Assign", "a", 100]
["f", 1]
]
// ➔ 1 + 10
Note that the value of a
is 3
when the function is defined, and it
is 10
when the function is applied. The function will always use the value of
a
that was in scope when the function was defined, not the value of a
at the
time the function is applied. In fact, the out a
is a different variable
which is unrelated to the a
in the scope of the function, but with the same
name.
body
body, arg-1, arg-2, ...
Create a function literal which can be used as an anonymous function.
The arg-n
arguments are symbols which are the bound variables (parameters) of the
function literal.
:::info[Note]
The Function
operator has the lazy
flag set to true
, which means
that neither the body nor the parameters are evaluated until the function is
applied to some arguments.
:::
The body is an expression that is evaluated when the function is applied to some arguments.
To apply some arguments to a function expression, use ["Apply"]
.
["Function", ["Multiply", "x", 2], "x"]
["Function", ["Add", ["Multiply", "x", 2], "y"], "x", "y"]
symbol, fn
Assign the function literal fn
to the symbol symbol
.
Assign
is not a pure function,
as it changes the state of the Compute Engine.
The fn is a function literal, which can be created using the ["Function"]
expression or the shorthand function literal.
["Assign", "double", ["Function", ["Multiply", "x", 2], "x"]]
function, expr-1, ...expr-n
Apply a list of arguments to a function. The function is a function literal, or a symbol whose value is a function literal.
The expr-n arguments are the arguments of the function literal.
["Apply", ["Multiply", "_", "_"], 3]
// ➔ 9
["Apply", ["Function", ["Multiply", "x", "x"], "x"], 3]
// ➔ 9
With LaTeX, the \lhd
and \rhd
commands can be used to apply a function to a single
argument on the left or right respectively.
["Apply", "f", ["Apply", "g", "x"]]
title: Parsing and Serializing LaTeX sidebar_label: LaTeX Syntax slug: /compute-engine/guides/latex-syntax/ date: Last Modified
:::info[Note]
In this documentation, functions such as ce.box()
and ce.parse()
require a
ComputeEngine
instance which is denoted by a ce.
prefix.
Functions that
apply to a boxed expression, such as expr.simplify()
are denoted with a
expr.
prefix.
:::
To create a new instance of the Compute Engine, use the
new ComputeEngine()
constructor.
const ce = new ComputeEngine();
To input math using an interactive mathfield, use the Mathfield library.
A <math-field>
DOM element works like a <textarea>
in HTML, but for
math. It provides its content as a LaTeX string, ready to be used with the
Compute Engine.
All the mathfields on the page share a Compute Engine instance, which is
available as MathfieldElement.computeEngine
.
const ce = MathfieldElement.computeEngine;
You can associate a customized compute engine with the mathfields in the document:
const ce = new ComputeEngine();
MathfieldElement.computeEngine = ce;
console.log(mfe.expression.json);
To parse a LaTeX string as a MathJSON expression, call the ce.parse()
function.
console.log(ce.parse("5x + 1").json);
// ➔ ["Add", ["Multiply", 5, "x"], 1]
By default, ce.parse()
return a
canonical expression. To get a
non-canonical expression instead, use the {canonical: false}
option: The
non-canonical form is closer to the literal LaTeX input.
ce.parse("\\frac{7}{-4}").json;
// ➔ ["Rational", -7, 4]
ce.parse("\\frac{7}{-4}", { canonical: false }).json;
// ➔ ["Divide", 7, -4]
Unlike a programming language, mathematical notation is surprisingly ambiguous and full of idiosyncrasies. Mathematicians frequently invent new notations, or have their own preferences to represent even common concepts.
The Compute Engine Natural Parser interprets expressions using the notation you are already familiar with. Write as you would on a blackboard, and get back a semantic representation as an expression ready to be processed.
LaTeX | MathJSON |
---|---|
$$ \sin 3t + \cos 2t $$ \sin 3t + \cos 2t
|
["Add", ["Sin", ["Multiply", 3, "t"]], ["Cos", ["Multiply", 2, "t"]]] |
$$ \int \frac{dx}{x} $$\int \frac{dx}{x}
|
["Integrate", ["Divide", 1, "x"], "x"] |
$$ 123.4(567) $$ 123.4(567)
|
123.4(567) |
$$ 123.4\overline{567} $$ 123.4\overline{567}
|
123.4(567) |
$$ \vert a+\vert b\vert+c\vert $$ |a+|b|+c|
|
["Abs", ["Add", "a", ["Abs", "b"], "c"]] |
$$ \vert\vert a\vert\vert+\vert b\vert $$ ||a||+|b|
|
["Add", ["Norm", "a"], ["Abs", "b"]] |
The Compute Engine Natural Parser will apply maximum effort to parse the input
string as LaTeX, even if it includes errors. If errors are encountered, the
resulting expression will have its expr.isValid
property set to false
. An
["Error"]
expression will be produced where a problem was encountered. To get
the list of all the errors in an expression, use expr.errors
which will return
an array of ["Error"]
expressions.
To serialize an expression to a LaTeX string, read the expr.latex
property.
console.log(ce.box(["Add", ["Power", "x", 3], 2]).latex);
// ➔ "x^3 + 2"
The LaTeX parsing can be customized by providing a ParseLatexOptions
object as
the second argument to the ce.parse()
function.
See the Number Formatting section for details on how to customize the parsing of numbers. Most of the same options are available for parsing as for serialization.
Key | Description |
---|---|
skipSpace |
If true , ignore space characters in a math zone. Default is true . |
parseNumbers |
When parsing a decimal number, e.g. 3.1415 :- "auto" or "decimal" : if a decimal number, parse it as an approximate decimal number with a whole part and a fractional part- "rational" : if a decimal number, parse it as an exact rational number with a numerator and a denominator. If not a decimal number, parse it as a regular number.- "never" : do not parse numbers, instead return each token making up the number (minus sign, digits, decimal marker, etc...).Note: if the number includes repeating digits (e.g. 1.33(333) ), it will be parsed as a decimal number even if this setting is "rational" . Default: "auto" |
preserveLatex |
If true , the expression will be decorated with the LaTeX fragments corresponding to each element of the expression. The top-level expression, that is the one returned by parse() , will include the verbatim LaTeX input that was parsed. The sub-expressions may contain a slightly different LaTeX, for example with consecutive spaces replaced by one, with comments removed, and with some low-level LaTeX commands replaced, for example \egroup and \bgroup . Default: false |
ce.parse('x + 0.5', { parseNumbers: "rational" }).print();
// ➔ x + 1/2
This handler is invoked when the parser encounters a symbol that has not yet been declared.
The symbol
argument is a valid symbol.
The handler should return the type of the symbol.
console.info(ce.parse("f(x)", {
getSymbolType: (symbol) => {
if (symbol === "f") {
return "function";
}
return "unknown";
},
}).json);
This handler is invoked when the parser encounters a token that it does not know how to handle.
The lhs
argument is the previous token, if any.
The handler can access the unexpected token with parser.peek
. If
it is a token that should be recognized, the handler can consume it
by calling parser.nextToken()
.
The handler should return an expression or null
if the token is not
recognized.
console.info(ce.parse("3\\frac{1}{\\foo}", {
parseUnexpectedToken: (lhs, parser) => {
if (parser.peek === '\\foo') {
parser.nextToken();
return "foo";
}
return null;
},
}).json);
While expr.latex
provides a simple, default serialization to LaTeX, it may not
always be the most suitable for your needs.
To customize the serialization to LaTeX, use the expr.toLatex()
method.
The argument of the expr.toLatex()
method is a SerializeLatexOptions
object
that can be used to customize the serialization. The keys are explained in the
sections below.
Key | Description |
---|---|
fractionalDigits |
The number of decimal places to use when formatting numbers. Use "max" to include all available digits and "auto" to use the same precision as for evaluation. Default is "auto" . |
notation |
The notation to use for numbers. Use "auto" , "scientific" , or "engineering" . Default is "auto" . |
avoidExponentsInRange |
A tuple of two values representing a range of exponents. If the exponent for the number is within this range, a decimal notation is used. Otherwise, the number is displayed with an exponent. Default is [-6, 20] . |
digitGroupSeparator |
The LaTeX string used to separate group of digits, for example thousands. Default is "\," . To turn off group separators, set to "" . If a string tuple is provide, the first string is used to group digits in the whole part and the second string to group digits in the fractional part. |
digitGroupSize |
The number of digits in a group. If set to "lakh" the digits are in groups of 2, except for the last group which has 3 digits. If a tupe is provided, the first element is used for the whole part and the second element for the fractional part. Default is 3 . |
exponentProduct |
A LaTeX string inserted before an exponent, if necessary. Default is "\cdot" . |
beginExponentMarker |
A LaTeX string used as template to format an exponent. Default value is "10^{" . |
endExponentMarker |
A LaTeX string used as template to format an exponent. Default value is "}" . |
truncationMarker |
A LaTeX string used to indicate that a number has more precision than what is displayed. Default is "\ldots" . |
repeatingDecimal |
The decoration around repeating digits. Valid values are "auto" , "vinculum" , "dots" , "parentheses" , "arc" and "none" . Default is "auto" . |
console.log(ce.parse("\\pi").N().toLatex({
fractionalDigits: 6,
}));
console.log(ce.box(700).toLatex({
notation: "scientific",
avoidExponentsInRange: null,
exponentProduct: "\\times"
}));
// ➔ "7\times10^{2}"
console.log(ce.box(123456.789).toLatex({
notation: "scientific",
avoidExponentsInRange: null,
exponentProduct: "\\times",
}));
// ➔ "1.234\,567\,89\times10^{5}"
The world is about evenly split between using a dot or a comma as a decimal marker.
By default, the ComputeEngine is configured to use a dot, i.e. $ 3.1415 $.
To use a comma as a decimal marker, set the decimalSeparator
option:
console.log(ce.box(3.141).toLatex({
decimalSeparator: "{,}"
}));
Note that in LaTeX, in order to get the correct spacing around the comma, it must be surrounded by curly brackets.
Key | Description |
---|---|
positiveInfinity |
The LaTeX string used to represent positive infinity. Default is "\infty" $ \infty $. |
negativeInfinity |
The LaTeX string used to represent negative infinity. Default is "-\infty" $ -\infty $. |
imaginaryUnit |
The LaTeX string used to represent the imaginary unit symbol. Default is "\imaginaryI" $ \imaginaryI $ |
notANumber |
The LaTeX string to represent the number NaN. Default value is "\operatorname{NaN}" $ \operatorname{NaN} $. |
prettify |
If true , the output will be formatted to be more human-readable. Default is false . |
invisibleMultiply |
A LaTeX string to use as an invisible multiply operator between expressions. Use "\cdot" to use a $ \cdot $. Default is "" . |
invisiblePlus |
A LaTeX string to use as an invisible plus operator between expressions, for example with mixed numbers. Leave it empty to join the main number and the fraction. Use "+" to insert an explicit $ + $ operator between them. Default is "" . |
multiply |
A LaTeX string to use as a multiply operator between expressions. Use "\cdot" to use a $ \cdot $. Default is "\times" $ \times $. |
missingSymbol |
A LaTeX string to use when a symbol is missing. Default is "\placeholder{}" $ \placeholder{} $. |
console.log(ce.parse("3\\frac{1}{4}").toLatex({
invisiblePlus: "+"
}));
In addition, the keys applyFunctionStyle
, groupStyle
, powerStyle
,
rootStyle
, fractionStyle
, logicStyle
and numericSetStyle
can be used to customize the serialization of specific types of expressions.
For example, a group can be indicated by simple parentheses, or by a
\left...\right
command. A fraction can be indicated by a
\frac{}{}
command or by a {}{}^{-1}
.
The Compute Engine includes some built-in defaults, but they can be customized as desired. These style options are functions that take an expression fragment and return a string indicating the desired style.
For example to always represent fractions with a solidus (forward slash) use:
console.log(ce.parse("\\frac{3}{5}").toLatex({
fractionStyle: () => "quotient"
}));
console.log(ce.parse("\\frac{3}{5}").toLatex({
fractionStyle: () => "inline-solidus"
}));
The style option handler has two arguments:
- the expression fragment being styled
- the depth/level of the expression in the overall expression
For example, to serialize fractions deeper than level 0 as an inline solidus:
console.log(ce.parse("\\frac{a}{b}+\\sqrt{\\frac{c}{d}}").toLatex({
fractionStyle: (expr, level) =>
level > 0 ? "inline-solidus" : "quotient"
}));
To customize the serialization of function application, use the
applyFunctionStyle
style option handler.
console.log(ce.parse("\\sin x").toLatex({
applyFunctionStyle: () => "big"
}));
"paren" |
\sin(x) |
|
"leftright" |
\sin\left(x\right) |
|
"big" |
\sin\bigl(x\bigr) |
|
"none" |
\sin x |
To customize the serialization of groups, use the groupStyle
style option
handler.
console.log(ce.parse("(a+b)", {canonical: false}).toLatex({
groupStyle: () => "big"
}));
"paren" |
x(a+b) |
|
"leftright" |
x\left(a+b\right) |
|
"big" |
x\bigl(a+b\bigr) |
|
"none" |
x a+b |
$$ x a+b$$ |
To customize how roots are serialized, use the rootStyle
style option
handler.
console.log(ce.parse("\\sqrt{2}").toLatex({
rootStyle: () => "solidus"
}));
"radical" |
||
"quotient" |
||
"solidus" |
To customize how fractions are serialized, use the fractionStyle
style
option handler.
console.log(ce.parse("\\frac{3}{5}").toLatex({
fractionStyle: () => "nice-solidus"
}));
"quotient" |
||
"inline-solidus" |
||
"nice-solidus" |
||
"reciprocal" |
||
"factor" |
To customize how logic expressions are serialized, use the logicStyle
style
option handler.
console.log(ce.parse("p\\land q").toLatex({
logicStyle: () => "word"
}));
"word" |
a \text{ and } b |
|
"boolean" |
||
"uppercase-word" |
p \text{ AND } q |
$ p \text{ AND } q $ |
"punctuation" |
To customize how powers are serialized, use the powerStyle
style option
handler.
console.log(ce.parse("x^2").toLatex({
powerStyle: () => "solidus"
}));
"root" |
||
"solidus" |
||
"quotient" |
To customize how numeric sets are serialized, use the numericSetStyle
style
option handler.
console.log(ce.parse("x \\in \\Z").toLatex({
numericSetStyle: () => "interval"
}));
"compact" |
||
"regular" |
||
"interval" |
||
"set-builder" |
The MathJSON format is independent of any source or
target language (LaTeX, MathASCII, Python, etc...) or of any specific
interpretation of the symbols used in a MathJSON expression ("Pi"
,
"Sin"
, etc...).
A LaTeX dictionary defines how a MathJSON expression can be expressed as a LaTeX string (serialization) or constructed from a LaTeX string (parsing).
The Compute Engine includes a default LaTeX dictionary to parse and serialize common math expressions.
It includes definitions such as:
- "The
Power
function is represented as "x^{n}
"" - "The
Divide
function is represented as "\frac{x}{y}
"".
Note that the dictionary will include LaTeX commands as triggers. LaTeX commands
are usually prefixed with a backslash, such as \frac
or \pm
. It will also
reference MathJSON symbols. MathJSON symbols are usually capitalized,
such as Divide
or PlusMinus
and are not prefixed with a backslash.
To extend the LaTeX syntax update the latexDictionary
property of the
Compute Engine
ce.latexDictionary = [
// Include all the entries from the default dictionary...
...ce.latexDictionary,
// ...and add the `\smoll{}{}` command
{
// The parse handler below will be invoked when this LaTeX command
// is encountered
latexTrigger: '\\smoll',
parse: (parser) => {
// We're expecting two arguments, so we're calling
// `parseGroup()` twice. If `parseGroup()` returns `null`,
// we assume that the argument is missing.
return [
"Divide",
parser.parseGroup() ?? ["Error", "'missing'"],
parser.parseGroup() ?? ["Error", "'missing'"],
];
},
},
];
console.log(ce.parse('\\smoll{1}{5}').json);
// The "Divide" get represented as a "Rational" by default when
// both arguments are integers.
// ➔ ["Rational", 1, 5]
To override an existing entry, create a new array that includes the default entries and add your own entry at the end of the array.
Entries at the end of the array will override earlier entries. When parsing an expression, the first entry (starting at the bottom) whose trigger matches is selected.
ce.latexDictionary = [
...ce.latexDictionary,
// The entry below will override the default entry for the `\times` command
{
latexTrigger: ['\\times'],
name: 'CrossProduct',
kind: 'infix',
associativity: 'none'
precedence: 390,
},
];
:::caution
Do not modify the ce.latexDictionary
array, or the entries in the array,
directly. Instead, create a new array that includes the entries from the
default dictionary, and add your own
entries. Later entries will override earlier ones, so you can replace or
modify existing entries by providing a new definition for them.
:::
The precedence
property is used to determine the order of operations when parsing
expressions, but it does not impact whether an entry is used for parsing. Only the
latexTrigger
or symbolTrigger
properties are used to determine if an entry
is used for parsing.
Note that latexTrigger
can be an array of tokens. However, the tokens
are not interpreted as alternatives. The array is treated as a sequence of tokens
that must be matched in order.
Each entry in the LaTeX dictionary is an object with the following properties:
-
kind
The kind of expression associated with this entry.
Valid values are
prefix
,postfix
,infix
,expression
,function
,symbol
,environment
andmatchfix
.If not provided, the default is
expression
.The meaning of the values and how to use them is explained below.
Note that it is possible to provide multiple entries with the same
latexTrigger
orsymbolTrigger
but with differentkind
properties. For example, the+
operator is both aninfix
(binary) and aprefix
(unary) operator. -
latexTrigger
A LaTeX fragment that will trigger the entry. For example,
^{+}
or\mathbb{D}
. -
symbolTrigger
A string, usually wrapped in a LaTeX command, that will trigger the entry.
For example, if
symbolTrigger
isfloor
, the LaTeX command\mathrm{floor}
or\operatorname{floor}
will trigger the entry.Only one of
latexTrigger
orsymbolTrigger
should be provided.If
kind
is"environment"
, onlysymbolTrigger
is valid, and it represents the name of the environment.If kind is
matchfix
, bothopenTrigger
andcloseTrigger
must be provided instead. -
parse
A handler that will be invoked when the trigger is encountered in the LaTeX input.
It will be passed a
parser
object that can be used to parse the input.The
parse
handler is invoked when the preconditions for the entry are met. For example, aninfix
entry will only be invoked if the trigger is encountered in the LaTeX input and there is a left-hand side to the operator.The signature of the
parse
handler will vary depending on thekind
. For example, for an entry of kindinfix
the left-hand side argument will be passed to theparse
handler. See below for more info about parsing for eachkind
.The
parse
handler should return a MathJSON expression ornull
if the expression is not recognized. Whennull
is returned, the Compute Engine Natural Parser will backtrack and attempt to find another handler that matches the current token. If there can be no ambiguity and the expression is not recognized, theparse
handler should return an["Error"]
expression. In general, it is better to returnnull
and let the Compute Engine Natural Parser attempt to find another handler that matches the current token. If none is found, an["Error"]
expression will be returned. -
serialize
A handler that will be invoked when the
expr.latex
property is read. It will be passed aSerializer
object that can be used to serialize the expression. Theserialize
handler should return a LaTeX string. See below for more info about serialization.If a
serialize
handler is provided, thename
property must be provided as well. -
name
The name of the MathJSON symbol associated with this entry.
If provided, a default
parse
handler will be used that is equivalent to:parse: name
.It is possible to have multiple definitions with the same triggers, but the
name
property must be unique. The record with thename
property will be used to serialize the expression. Aserialize
handler is invalid if thename
property is not provided.The
name
property must be unique. However, multiple entries can have different triggers that produce the same expression. This is useful for synonyms, such as\operatorname{floor}
and\lfloor
...\rfloor
.
The most general type of entry is one of kind expression
. If no kind
property is provided, the kind is assumed to be expression
.
For entries of kind expression
the parse
handler is invoked when the trigger
is encountered in the LaTeX input. The parse
handler is passed a parser
object that can be used to parse the input.
The kind expression
is suitable for a simple symbol, for example a
mathematical constant. It can also be used for more complex constructs, such as
to parse a series of tokens representing an integral expression. In this case,
the parse
handler would be responsible for parsing the entire expression and
would use the parser
object to parse the tokens.
If the tokens are not recognized, the parse
handler should return null
and
the parser will continue to look for another handler that matches the current
token.
The function
kind is a special case of expression
where the expression is a
function, possibly using multi-character symbols, as in
\operatorname{concat}
.
Unlike an expression
entry, after the parse
handler is invoked, the
parser will look for a pair of parentheses to parse the arguments of the
function and apply them to the function.
The parse handler should return the symbol corresponding to the function,
such as Concatenate
. As a shortcut, the parse
handler can be provided as an
Expression. For example:
{
kind: "function",
symbolTrigger: "concat",
parse: "Concatenate"
}
The prefix
, infix
and postfix
kinds are used for operators.
Entries for prefix
, infix
and postfix
operators must include a
precedence
property. The precedence
property is a number that indicates the
precedence of the operator. The higher the number, the higher the precedence,
that is the more "binding" the operator is.
For example, the precedence
of the Add
operator is 275
(ADDITION_PRECEDENCE
), while the precedence
of the Multiply
operator is
390 (MULTIPLICATION_PRECEDENCE
).
In 1 + 2 * 3
, the Multiply
operator has a higher precedence than the
Add
operator, so it is applied first.
The precedence range is an integer from 0 to 1000.
Here are some rough ranges for the precedence:
- 800: prefix and postfix operators:
\lnot
etc...POSTFIX_PRECEDENCE
= 810:!
,'
- 700: some arithmetic operators
EXPONENTIATION_PRECEDENCE
= 700:^
- 600: some binary operators
DIVISION_PRECEDENCE
= 600:\div
- 300: some logic and arithmetic operators:
\land
,\lor
etc...MULTIPLICATION_PRECEDENCE
= 390:\times
- 200: arithmetic operators, inequalities:
ADDITION_PRECEDENCE
= 275:+
-
ARROW_PRECEDENCE
= 270:\to
\rightarrow
ASSIGNMENT_PRECEDENCE
= 260::=
COMPARISON_PRECEDENCE
= 245:\lt
\gt
- 241:
\leq
- 0:
,
,;
, etc...
The infix
kind is used for binary operators (operators with a left-hand-side
and right-hand-side).
The parse
handler will be passed a parser
object and
the left-hand side of the operator, for postfix
and infix
operators.
The parser
object can be used to parse the right-hand side of the expression.
{
kind: "infix",
latexTrigger: '\\oplus',
precedence: ADDITION_PRECEDENCE,
parse: (parser, lhs) => {
return ["Concatenate", lhs, parser.parseExpression()];
},
}
The prefix
kind is used for unary operators.
The parse
handler will be passed a parser
object.
{
kind: "prefix",
latexTrigger: '\\neg',
precedence: ADDITION_PRECEDENCE,
parse: (parser, lhs) => {
return ["Negate", lhs];
},
}
The postfix
kind is used for postfix operators. The parse
handler will be
passed a parser
object and the left-hand side of the operator.
{
kind: "postfix",
latexTrigger: '\\!',
parse: (parser, lhs) => {
return ["Factorial", lhs];
},
}
The environment
kind is used for LaTeX environments.
The `symbolTrigger property in that case is the name of the environment.
The parse
handler wil be passed a parser
object. The parseTabular()
method can be used to parse the rows and columns of the environment. It
returns a two dimensional array of expressions.
The parse
handler should return a MathJSON expression.
{
kind: "environment",
symbolTrigger: "matrix",
parse: (parser) => {
const content = parser.parseTabular();
return ["Matrix", ["List", content.map(row => ["List", row.map(cell => cell)])]];
},
}
The matchfix
kind is used for LaTeX commands that are used to enclose an
expression.
The openTrigger
and closeTrigger
indicate the LaTeX commands
that enclose the expression. The parse
handler is passed a parser
object and
the "body" (the expression between the open and close delimiters). The parse
handler should return a MathJSON expression.
{
kind: "matchfix",
openTrigger: '\\lvert',
closeTrigger: '\\rvert',
parse: (parser, body) => {
return ["Abs", body];
},
}
When parsing a LaTeX string, the first step is to tokenize the string according
to the LaTeX syntax. For example, the input string \frac{ab}{10}
will result
in the tokens ["\\frac", "{", "a", "b", "}", "{", "1", "0", "}"]
.
Note that each LaTeX command is a single token, but that digits and ordinary letters are each separate tokens.
The parse
handler is invoked when the trigger is encountered in the LaTeX
token strings.
A common case is to return from the parse handler a MathJSON symbol.
For example, let's say you wanted to map the LaTeX command \div
to the
MathJSON Divide
symbol. You would write:
{
latexTrigger: '\\div',
parse: (parser) => {
return "Divide";
},
}
As a shortcut, you can also write:
{
latexTrigger: '\\div',
parse: () => "Divide"
}
Or even more succintly:
{
latexTrigger: '\\div',
parse: "Divide"
}
The LaTeX \div(1, 2)
would then produce the MathJSON expression
["Divide", 1, 2]
. Note that the arguments are provided as comma-separated,
parenthesized expressions, not as LaTeX arguments in curly brackets.
If you need to parse some more complex LaTeX syntax, you can use the parser
argument of the parse
handler. The parser
object has numerous methods to
help you parse the LaTeX string:
parser.peek
is the current token.parser.index
is the index of the current token. If backtracking is necessary, it is possible to set the index to a previous value.parser.nextToken()
returns the next token and advances the index.parser.skipSpace()
in LaTeX math mode, skip over "space" which includes space tokens, and empty groups{}
. Whether space tokens are skipped or not depends on theskipSpace
option.parser.skipVisualSpace()
skip over "visual space" which includes space tokens, empty groups{}
, and commands such as\,
and\!
.parser.match(token: LatexToken)
return true if the next token matches the argument, ornull
otherwise.parser.matchAll(tokens)
return true if the next tokens match the argument, an array of tokens, ornull
otherwise.parser.matchAny(tokens: LatexToken[])
return the next token if it matches any of the token in the argument ornull
otherwise.parser.matchChar()
return the next token if it is a plain character (e.g. "a", '+'...), or the character corresponding to a hex literal (^^ and ^^^^) or the\char
and\unicode
commandsparser.parseGroup()
return an expression if the next token is a group begin token{
followed by a sequence of LaTeX tokens until a group end token}
is encountered, ornull
otherwise.parser.parseToken()
return an expression if the next token can be parsed as a MathJSON expression, ornull
otherwise. This is useful when the argument of a LaTeX command can be a single token, for example for\sqrt5
. Some, but not all, LaTeX commands accept a single token as an argument.parser.parseOptionalGroup()
return an expression if the next token is an optional group begin token[
followed by a sequence of LaTeX tokens until an optional group end token]
is encountered, ornull
otherwise.parser.parseExpression()
return an expression if the next tokens can be parsed as a MathJSON expression, ornull
otherwise. After this call, there may be some tokens left to parse.parser.parseArguments()
return an array of expressions if the next tokens can be parsed as a sequence of MathJSON expressions separated by a comma, ornull
otherwise. This is useful to parse the argument of a function. For example withf(x, y, z)
, the arguments would be[x, y, z]
.
If the parse()
handler returns null
, the parser will continue to look for
another handler that matches the current token.
Note there is a pattern in the names of the methods of the parser. The match
prefix means that the method will return the next token if it matches the
argument, or null
otherwise. These methods are more primitive. The parse
prefix indicates that the method will return a MathJSON expression or null
.
The most common usage is to call parser.parseGroup()
to parse a group of
tokens as an argument to a LaTeX command.
For example:
{
latexTrigger: '\\div',
parse: (parser) => {
return ["Divide", parser.parseGroup(), parser.parseGroup()];
},
}
In this case, the LaTeX input \div{1}{2}
would produce the MathJSON expression
["Divide", 1, 2]
(note the use of the curly brackets, rather than the
parentheses in the LaTeX input).
If we wanted instead to treat the \div
command as a binary operator, we could
write:
{
latexTrigger: '\\div',
kind: "infix",
parse: (parser, lhs) => {
return ["Divide", lhs, parser.parseExpression()];
},
}
By using the kind: "infix"
option, the parser will automatically insert the
left-hand side of the operator as the first argument to the parse
handler.
When serializing a MathJSON expression to a LaTeX string, the serialize()
handler is invoked. You must specify a name
property to associate the
serialization handler with a MathJSON symbol.
{
name: "Concatenate",
latexTrigger: "\\oplus",
serialize: (serializer, expr) =>
"\\oplus" + serializer.wrapArguments(expr),
evaluate: (ce, args) => {
let result = '';
for (const arg of args) {
val = arg.numericValue;
if (val === null || ce.isComplex(val) || Array.isArray(val)) return null;
if (ce.isBignum(val)) {
if (!val.isInteger() || val.isNegative()) return null;
result += val.toString();
} else if (typeof val === "number") {
if (!Number.isInteger(val) || val < 0) return null;
result += val.toString();
}
}
return ce.parse(result);
},
}
In the example above, the LaTeX command \oplus
is associated with the
Concatenate
function. The serialize()
handler will be invoked when the
expr.latex
property is read.
Note that we did not provide a parse()
handler: if a name
property is
provided, a default parse
handler will be used that is equivalent to:
parse: name
.
It is possible to have multiple definitions with the same triggers, but the
name
property must be unique. The record with the name
property will be used
to serialize the expression. A serialize
handler is invalid if the name
property is not provided.
You may also want to use your new function with a mathfield.
First you need to define a LaTeX macro so that the mathfield knows how to render
this command. Let's define the \smallfrac
macro.
const mfe = document.querySelector("math-field");
mfe.macros = {
...mfe.macros,
smallfrac: {
args: 2,
def: "{}^{#1}\\!\\!/\\!{}_{#2}",
},
};
The content of the def
property is a LaTeX fragment that will be used to
render the \\smallfrac
command.
The #1
token in def
is a reference to the first argument and #2
to the
second one.
You may also want to define an inline shortcut to make it easier to input the command.
With the code below, we define a shortcut "smallfrac".
When typed, the shortcut is replaced with the associated LaTeX.
The #@
token represents the argument to the left of the shortcut, and the #?
token represents a placeholder to be filled by the user.
mfe.inlineShortcuts = {
...mfe.inlineShortcuts,
smallfrac: "\\smallfrac{#@}{#?}",
};
You can now parse the input from a mathfield using:
console.log(ce.parse(mfe.value).json);
Alternatively, the customized compute engine can be associated with the mathfields in the document:
MathfieldElement.computeEngine = ce;
console.log(mfe.getValue("math-json"));
title: Canonical Form slug: /compute-engine/guides/canonical-form/ layout: single date: Last Modified sidebar:
- nav: "universal" toc: true render_math_in_document: true
Many mathematical objects can be represented by several equivalent expressions. A **canonical form** is a unique representation of an object that is chosen as the standard representation.
For example, the expressions in each row below represent the same mathematical object:
\(215.3465\) | \(2.15346\operatorname{e}2\) | \(2.15346 \times 10^2\) |
\(1 - x\) | \(-x + 1\) | \(1 + (-x)\) |
\(-2x^{-1}\) | \(-\frac{2}{x}\) | \(\frac{-2}{x}\) |
The Compute Engine stores expressions internally in a canonical form to make it easier to work with symbolic expressions.
The canonical form is intended to be “stable”, that is it does not depend on the values of non-constant symbols or on assumptions about a symbol or expression.
The type of symbols can be used during canonicalization of expressions referencing the symbol, as the type can only be narrowed later and thus would not change the result of the canonicalization. The value of variables (non-constant symbols) is never used during canonicalization as it could be changed later.
ce.assign("x", -2);
console.info(ce.parse("\\frac{10}{x}").json);
// ➔ ["Rational", 10, "x"]
// and not `["Rational", -10, ["Negate", "x"]]` or `5`
Future versions of the Compute Engine could have different canonical forms, however a given version of the Compute Engine will always produce the same canonical form for a given expression, given the same type information about symbols and the same dictionary.
To check if an expression is canonical use expr.isCanonical
.
To obtain the canonical representation of a non-canonical expression, use
the expr.canonical
property.
If the expression is already canonical, expr.canonical
immediately returns
expr
.
The return value of expr.simplify()
, expr.evaluate()
and expr.N()
are
canonical expressions.
The ce.box()
and ce.parse()
functions return a canonical expression by
default, which is the desirable behavior in most cases.
To get a non-canonical version of an expression use
of ce.parse(s, {canonical: false})
or ce.box(expr, {canonical: false})
.
You can further customize the canonical form of an expression by using the
["CanonicalForm"]
function
or by specifying the form you want to use. See below for more details.
The non-canonical version will be closer to the literal LaTeX input, which may be desirable to compare a "raw" user input with an expected answer.
console.info(ce.parse('\\frac{30}{-50}').json);
// ➔ ["Rational", -3, 5]
// The canonical version moves the sign to the numerator
// and reduces the numerator and denominator
console.info(ce.parse('\\frac{30}{-50}', { canonical: false }).json);
// ➔ ["Divide", 30, -50]
// The non-canonical version does not change the arguments,
// so this is interpreted as a regular fraction ("Divide"),
// not as a rational number.
The value of expr.json
(the plain JSON representation of an expression) may
not be in canonical form: some "sugaring" is applied to the internal
representation before being returned, for example ["Power", "x", 2]
is
returned as ["Square", "x"]
.
To customize how an expression is serialized to plain JSON use
expr.toMathJson()
.
const expr = ce.parse("\\frac{3}{5}");
console.log(expr.toMathJson());
// ➔ ["Rational", 3, 5]
console.log(expr.expr.toMathJson({ exclude: ["Rational"] }));
// ➔ ["Divide", 3, 5]
// We have excluded `["Rational"]` expressions, so it
// is interepreted as a division instead.
const expr = ce.parse("\\frac{10}{30}", { canonical: false });
console.log(expr.json);
// ➔ ["Divide", 10, 30]
console.log(expr.isCanonical);
// ➔ false
console.log(expr.canonical.json);
// ➔ ["Rational", 1, 3]
The canonical form of an expression may not be valid. A canonical expression
may include ["Error"]
expressions, for example, indicating missing arguments,
excess arguments, or arguments of the wrong type.
For example the canonical form of ["Ln"]
is ["Ln", ["Error", "'missing'"]]
and it is not a valid expression.
To check if an expression is valid use expr.isValid
.
To get a list of errors in an expression use expr.errors
.
const expr = ce.parse("Ln");
console.log(expr.json);
// ➔ ["Ln", ["Error", "'missing'"]]
// The canonical form of `Ln` is not valid
console.log(expr.isCanonical);
// ➔ true
console.log(expr.isValid);
// ➔ false
console.log(expr.errors);
// ➔ [["Error", "'missing'"]]
The canonical form used by the Compute Engine follows common conventions. However, it is not always "the simplest" way to represent an expression.
Calculating the canonical form of an expression involves applying some
rewriting rules to an expression to put sums, products, numbers, roots,
etc... in canonical form. In that sense, it is similar to simplifying an
expression with expr.simplify()
, but it is more conservative in the
transformations it applies.
Below is a list of some of the transformations applied to obtain the canonical form:
- Literal Numbers
- Rationals are reduced, e.g. \( \frac{6}{4} \to \frac{3}{2}\)
- The denominator of rationals is made positive, e.g. \(\frac{5}{-11} \to \frac{-5}{11}\)
- A rational with a denominator of 1 is replaced with the numerator, e.g. \(\frac{19}{1} \to 19\)
- Complex numbers with no imaginary component are replaced with the real component
Add
- Literal 0 is removed
- Sum of a literal and the product of a literal with the imaginary unit are replaced with a complex number.
- Associativity is applied
- Arguments are sorted
Multiply
- Literal 1 is removed
- Product of a literal and the imaginary unit are replaced with a complex number.
- Literal -1 multiplied by an expression is replaced with the negation of the expression.
- Signs are simplified: (-x)(-y) -> xy
- Associativity is applied
- Arguments are sorted
Negate
- Literal numbers are negated
- Negate of a negation is removed
Power
- \(x^n)^m \to x^{nm}\)
- \(x^{\tilde\infty} \to \operatorname{NaN}\)
- \(x^0 \to 1\)
- \(x^1 \to x\)
- \((\pm 1)^{-1} \to -1\)
- \((\pm\infty)^{-1} \to 0\)
- \(0^{\infty} \to \tilde\infty\)
- \((\pm 1)^{\pm \infty} \to \operatorname{NaN}\)
- \(\infty^{\infty} \to \infty\)
- \(\infty^{-\infty} \to 0\)
- \((-\infty)^{\pm \infty} \to \operatorname{NaN}\)
Square
:["Power", "x", 2]
\(\to\)["Square", "x"]
Sqrt
:["Sqrt", "x"]
\(\to\)["Power", "x", "Half"]
Root
:["Root", "x", 3]
\(\to\)["Power", "x", ["Rational", 1, 3]]
Subtract
- Replaced with addition, e.g.
["Subtract", "a", "b"]
\(\to\)["Add", ["Negate", "b"], "a"]
- Replaced with addition, e.g.
- Other functions:
- Simplified if idempotent: \( f(f(x)) \to f(x) \)
- Simplified if an involution: \( f(f(x)) \to x \)
- Simplified if associative: \( f(a, f(b, c)) \to f(a, b, c) \)
The full canonical form of an expression is not always the most convenient representation for a given application. For example, if you want to check the answers from a quiz, you may want to compare the user input with a canonical form that is closer to the user input.
To get the non-canonical form, use ce.box(expr, { canonical: false })
or
ce.parse(s, { canonical: false })
.
console.log(ce.parse("2(0+x\\times x-1)", {canonical: false}).json);
To get the full canonical form, use ce.box(expr, { canonical: true })
or
ce.parse(s, { canonical: true })
. The canonical
option can be omitted
as it is true
by default.
console.log(ce.parse("2(0+x\\times x-1)", {canonical: true}).json);
console.log(ce.parse("2(0+x\\times x-1)").json);
To get a custom canonical form of an expression, use the
["CanonicalForm"]
function
or specify the form you want to use with the canonical
option of ce.box()
and ce.parse()
.
To order the arguments in a canonical order, use ce.box(expr, { canonical: "Order" })
or ce.parse(s, { canonical: "Order" })
.
ce.parse("0+1+x+2+\\sqrt{5}",
{canonical: "Order"}
).print();
Note in particular that the 0
is preserved in the expression, which is not
the case in the full canonical form.
There are other forms that can be used to customize the canonical form of an
expression. See the documentation of
["CanonicalForm"]
for more details.
For example:
const latex = "3(2+x)";
ce.parse(latex, {canonical: false}).print();
ce.parse(latex, {canonical: ["InvisibleOperator"]}).print();
ce.parse(latex,
{canonical: ["InvisibleOperator", "Add", "Order", ]}
).print();
To obtain an exact numeric evaluation of an expression use `expr.evaluate()`. To obtain a numeric approximation use `expr.N()`.
An evaluation with expr.evaluate()
preserves exact values.
Exact values are:
Type | Examples |
---|---|
Integers |
|
Rationals |
|
Square Roots of Integers |
|
Constants |
ExponentialE ), Pi ) |
console.log(ce.parse('1/3 + 1/4').evaluate());
console.log(ce.parse('\\sqrt{2} + \\sqrt{3} + \\sqrt{75}').evaluate());
To force the evaluation of an expression to be a numeric approximation, use expr.N()
.
console.log(ce.parse('1/3 + 1/4').N());
console.log(ce.parse('\\sqrt{2} + \\sqrt{3} + \\sqrt{75}').N());
When using expr.evaluate()
, if one of the arguments is not an exact value
the expression is automatically evaluated as a numeric approximation.
console.log(ce.parse('1/3 + 1/4 + 1.24').evaluate());
The result of expr.evaluate()
and expr.N()
is a boxed expression.
The numericValue
property of this expression is either a machine number
(a JavaScript number
), a NumericValue
object or null
if the expression
is not a number.
While a NumericValue
object can represent arbitrary precision numbers, for
use with JavaScript, a reduced precision approximation can be accessed using
the re
(for the real part of the number) and im
(for the imaginary part)
properties.
const expr = ce.parse('1/3 + 1/4');
console.log(expr.N().re);
// ➔ 0.5833333333333334
Another way to obtain a JavaScript compatible representation of an expression
is to use the valueOf()
method of the boxed expression.
const expr = ce.parse('1/3 + 1/4');
console.log(expr.N().valueOf());
// ➔ 0.5833333333333334
The valueOf()
property of a boxed expression can be used in JavaScript
expressions.
const expr = ce.parse('1/3 + 1/4');
console.log(expr.N().valueOf() + 1);
Unlike the `.re` property, the `.value` property can also return a `boolean`
or a `string`, depending on the value of the expression.
**To get a boxed number from a JavaScript number**, use `ce.box()` or `ce.number()`.
```live
const boxed = ce.box(1.5);
console.log(boxed.valueOf());
The number of significant digits used in numeric calculations is controlled
by the ce.precision
property of the ComputeEngine
instance.
ce.precision = 30;
console.log(ce.parse('\\pi').N().json);
The default precision is 21.
To set the precision to the default value, use ce.precision = "auto"
.
If the precision is 15 or less, the Compute Engine uses a 64-bit binary floating point format for its internal calculations.
This format is implemented in hardware and well suited to do fast computations. It uses a fixed amount of memory and represent significant digits in base-2 with about 15 digits of precision and with a minimum value of $ \pm5\times 10^{-324} $ and a maximum value of $ \pm1.7976931348623157\times 10^{+308} $
To change the precision to machine precision, use
ce.precision = "machine"
.
With this precision, some calculations that have a value very close to 0 may return 0 and some calculations that have a value greater than the maximum value representable by a machine number may return $ \pm\infty $.
:::warning Some numeric evaluations using machine numbers cannot produce exact results.
ce.precision = 'machine';
console.log(ce.parse('0.1 + 0.2').N().json);
// ➔ "0.30000000000000004"
While $ 0.1 $ and $ 0.2 $ look like "round numbers" in base-10, they can only be represented by an approximation in base-2, which introduces cascading errors when manipulating them.
Read **"What Every Computer Scientist Should Know About Floating-Point Arithmetic"** by David Goldberg:::
If ce.precision
is greater than 15, numbers are represented as bignum numbers,
a string of base-10 digits and an exponent.
Bignum numbers have a minimum value of $ \pm 10^{-9,000,000,000,000,000} $ and a maximum value of $ \pm9.99999\ldots \times 10^{+9,000,000,000,000,000} $.
ce.precision = 21;
console.log(ce.parse('0.1 + 0.2').N().json);
// ➔ "0.3"
Trigonometric operations are accurate for precision up to 1,000.
The precision
property affects how the computations are performed, but not how
they are serialized.
To change the number of digits when serializing to LaTeX, use
expr.toLatex({ precision: 6 })
to set it to 6 significant digits, for
example.
The LaTeX precision is adjusted automatically when the precision
is changed so
that the display precision is never greater than the computation precision.
When the precision is greater than 15, the return value of expr.N().json
may
be a MathJSON number that looks like this:
{
"num": "3.141592653589793238462643383279502884197169399375105820974944
5923078164062862089986280348253421170679821480865132823066470938446095
5058223172535940812848111745028410270193852110555964462294895493038196
4428810975665933446128475648233786783165271201909145648566923460348610
4543266482133936072602491412737245870066063155881748815209209628292540
9173643678925903600113305305488204665213841469519415116094330572703657
5959195309218611738193261179310511854807446237996274956735188575272489
1227938180119491298336733624406566430860213949463952247371907021798609
4370277053921717629317675238467481846766940513200056812714526356082778
5771342757789609173637178721468440901224953430146549585371050792279689
2589235420199561121290219608640344181598136297747713099605187072113499
9999837297804995105973173281609631859502445945534690830264252230825334
4685035261931188171010003137838752886587533208381420617177669147303598
2534904287554687311595628638823537875937519577818577805321712268066130
01927876611195909216420199"
}
To repeatedly evaluate an expression use ce.assign()
to change the value
of variables. ce.assign()
changes the value associated with one or more
variables in the current scope.
const expr = ce.parse("3x^2+4x+2");
for (let x = 0; x < 1; x += 0.01) {
ce.assign('x', x);
console.log(`f(${x}) = ${expr.evaluate()}`);
}
You can also use expr.subs()
, but this will create a brand new expression on
each iteration, and will be much slower.
const expr = ce.parse("3x^2+4x+2");
for (let x = 0; x < 1; x += 0.01) {
console.log(`f(${x}) = ${expr.subs({ x }).evaluate()}`);
}
To reset a variable to be unbound to a value use ce.assign()
ce.assign("x", null);
console.log(ce.parse("3x^2+4x+2").N());
// ➔ "3x^2+4x+c"
To change the value of a variable set its value
property:
ce.symbol("x").value = 5;
ce.symbol("x").value = undefined;
If performance is important, the expression can be compiled to a JavaScript
To get a compiled version of an expression use the expr.compile()
method:
const expr = ce.parse("3x^2+4x+2");
const fn = expr.compile();
for (const x = 0; x < 1; x += 0.01) console.log(fn({ x }));
:::info[Note]
The syntax {x}
is a shortcut for {"x": x}
, in other words it defines an
argument named "x"
(which is used in the definition of the expression expr
)
with the value of the JavaScript variable x
(which is used in the for loop).
:::
This will usually result in a much faster evaluation than using expr.N()
but
this approach has some limitations.
Read more about Compiling Expressions to JavaScript
When using expr.N()
, no rewriting of the expression is done before it is
evaluated.
Because of the limitations of machine numbers, this may produce surprising results.
For example:
ce.precision = "machine";
const x = ce.parse("0.1 + 0.2").N();
console.log(ce.box(["Subtract", x, x]).N());
// ➔ 2.7755575615628914e-17
However, the result of $ x - x $ from ce.simplify()
is $ 0 $ since the
simplification is done symbolically, before any floating point calculations are
made.
const x = ce.parse('0.1 + 0.2').N();
console.log(ce.parse('x - x').simplify());
// ➔ 0
In some cases, it may be advantageous to invoke expr.simplify()
before using
expr.N()
.
Two numbers that are sufficiently close to each other are considered equal.
To control how close two numbers have to be before they are considered
equal, set the tolerance
property of a ComputeEngine
instance.
By default, the tolerance is $ 10^{-10} $.
The tolerance is accounted for by the Chop
function to determine when to
replace a number of a small magnitude with the exact integer 0.
It is also used when doing some comparison to zero: a number whose absolute value is smaller than the tolerance will be considered equal to 0.
The topics below from the MathJSON Standard Library can provide numeric evaluations for their numeric functions:
Topic | Symbols/Functions |
---|---|
Arithmetic | Add Multiply Power Exp Log ExponentialE ImaginaryUnit ... |
Calculus | Derivative Integrate ... |
Complex | Real Conjugate , ComplexRoots ... |
Special Functions | Gamma Factorial ... |
Statistics | StandardDeviation Mean Erf ... |
Trigonometry | Pi Cos Sin Tan ... |
The expr.simplify()
function tries expanding, factoring and applying many
other transformations to find a simple a simpler form of a symbolic expression.
Before the transformation rules are applied, the expression is put into a canonical form.
When a function is simplified, its arguments are simplified as well, unless the
argument is "held". Which arguments are held is specified by the hold
property
of the function definition. In addition, any argument wrapped with a Hold
function will be held, that is, not simplified. Conversely, a held argument
wrapped with a ReleaseHold
function will not be held, and it will be
simplified.
An expression may be represented by several equivalent forms.
For example \( (x + 4)(x-5) \) and \(x^2 -x -20\) represent the same expression.
Determining which is "the simplest" depends on how the complexity is measured.
By default, the complexity of an expression is measured by counting the number of operations in the expression, and giving an increasing cost to:
- integers with fewer digits
- integers with more digits
- other numeric values
- add, multiply, divide
- subtract and negate
- square root and root
- exp
- power and log
- trigonometric function
- inverse trigonometric function
- hyperbolic functions
- inverse hyperbolic functions
- other functions
To influence how the complexity of an expression is measured, set the
costFunction
property of the compute engine to a function assigning a cost to
an expression.
The expr.simplify()
function will apply some numeric simplifications, such as
combining small integer and rational values, simplifying division by 1, addition
or subtraction of 0, etc...
It avoids making any simplification that could result in a loss of precision.
For example, \( 10^{300} + 1\) cannot be simplified without losing the least
significant digit, so expr.simplify()
will return the expression unmodified.
Assumptions are additional information available about some symbols, for example \( x > 0 \) or \(n \in \N\).
Some transformations are only applicable if some assumptions can be verified.
For example, if no assumptions about \(x \) is available the expression \( \sqrt{x^2} \) cannot be simplified. However, if an assumption that \( x \geq 0 \) is available, then the expression can be simplified to \( x \).
In the Compute Engine, the **type** of an expression is the set of the possible values of that expression.The Compute Engine uses a type system to ensure that operations are performed on the correct types of values.
A type is represented by a type expression, which is a string with a specific syntax. For example:
"integer"
"boolean"
"matrix<3x3>"
"integer & !0"
"integer -> integer"
A type expression is either a primitive type represented by an identifier
such as "integer"
or "boolean"
or a constructed type.
To check the type of an expression, use the expr.type
property.
console.log(ce.parse("3.14").type);
The type of a symbol can be declared explicitly, or it can be inferred from the context in which it is used, such as the value that is assigned to it or the operation that is performed on it.
To explicitly declare the type of a symbol, use the ce.declare()
function.
ce.declare("n", "integer");
ce.parse("n").type;
// ➔ "integer"
Alternatively, to declare the type of a symbol you can evaluate a
["Declare"]
expression
ce.box(["Declare", "n", "'integer'"]).evaluate();
ce.parse("n").type;
// ➔ "integer"
The type system is based on the concept of subtyping, which allows for a hierarchy of types, where a type can be a subtype of another type. This allows for more flexible and expressive type definitions, as well as better error checking and type inference.
any
├── error
├── nothing
├── never
├── unknown
└── expression
├── symbol
├── function
└── value
├── scalar
│ ├── boolean
│ ├── string
│ └── number
│ └── complex
│ ├── imaginary
│ └── real
│ └── rational
│ └── integer
└── collection
├── tuple
├── set
├── map
└── list
└── tensor
├── vector
└── matrix
Note: this diagram is simplified and does not accurately reflect the finite vs non-finite distinction for the numeric types.
The unknown
type is a placeholder for an expression whose type has not yet
been determined, typically during type inference or partial evaluation. It is
compatible with all types, and all types are compatible with it. It serves as
a wildcard in type matching and can be replaced or refined as more information
becomes available.
A primitive type is a type that is not defined in terms of other types.
The Compute Engine supports the following primitive types:
Type | Description |
---|---|
any |
The universal type, it contains all possible values. It has the following sub-types: error , nothing , never , unknown and expression . No other type matches any |
error |
The type of an invalid expression, such as ["Error"] |
nothing |
The type whose only member is the symbol Nothing ; the unit type |
never |
The type that has no values; the empty type or bottom type |
unknown |
The type of an expression whose type is not known. An expression whose type is unknown can have its type modified (narrowed or broadened) at any time. Every other type matches unknown |
expression |
A symbolic expression that represents a mathematical object, such as ["Add", 1, "x"] , a symbol , a function or a value |
symbol |
A named object, for example a constant or variable in an expression such as x or alpha |
function |
A function literal: an expression that applies some arguments to a body to produce a result, such as ["Function", ["Add", "x", 1], "x"] |
value |
A constant value, such as 1 , True , 'hello' or Pi : a scalar or a collection |
collection |
A collection of values: a list , a set , a tuple , or a map |
scalar |
A single value: a boolean , a string , or a number |
boolean |
The symbol True or False |
string |
A string of Unicode characters |
number |
A numeric value |
Type | Description | Assignable To | Assignable From |
---|---|---|---|
any |
All possible values | ✓ | ✓ |
unknown |
Undetermined type, placeholder for inference | ✓ | ✓ |
never |
No values at all (bottom type) | ✓ | ✗ |
nothing |
Singleton unit type (Nothing ) |
✓ | Only Nothing |
error |
Invalid or ill-formed expression | ✗ | ✗ |
The type number
represent all numeric values, including NaN
.
More specific types of numeric values are represented by subtypes of number
.
Some numeric types have a variant that excludes non-finite values, such as
PositiveInfinity
, NegativeInfinity
and ComplexInfinity
.
Type | Description |
---|---|
number |
All numeric values: a real or complex number or |
non_finite_number |
The values PositiveInfinity and NegativeInfinity ) |
complex |
A number with non-zero real and imaginary parts, such as ComplexInfinity ) |
imaginary |
A pure imaginary number, such as |
real |
A real number, such as |
rational |
A number that can be expressed as the quotient of two integers such as |
integer |
A whole number, such as |
finite_number |
A real or complex number, except |
finite_complex |
A complex number, except |
finite_real |
A real number, except |
finite_rational |
A rational number, except |
finite_integer |
An integer, except |
Here is the type of various numeric values:
Value | Type |
---|---|
finite_integer |
|
finite_real |
|
finite_rational |
|
imaginary |
|
finite_complex |
|
non_finite_number |
|
number |
The Compute Engine Standard Library includes definitions for sets that correspond to some numeric types.
Read more about the **sets** included in the Standard LibraryA collection type is the type of an object that contains multiple values.
The Compute Engine supports the following collection types: set
, tuple
,
list
(including vector
, matrix
and tensor
), map
and collection
.
A set is an unordered collection of unique values.
The type of a set is represented by the type expression set<T>
, where T
is the type of the elements of the set.
ce.parse("\\{1, 2, 3\\}").type
// ➔ "set<finite_integer>"
A tuple is an ordered collection of values, representing a fixed number of elements.
The type of a tuple is represented by the type expression tuple<T1, T2, ...>
,
where T1
, T2
, ... are the types of the elements of the tuple.
ce.parse("(1, 2, 3)").type
// ➔ "tuple<finite_integer, finite_integer, finite_integer>"
The elements of a tuple can be named: tuple<x: integer, y: integer>
. If
an element is named, all elements must be named and the names must be unique.
The name of the elements of a tuple must use the letters a
to z
or A
to
Z
, the digits 0
to 9
or the underscore _
and must start with a letter
or an underscore.
For two tuples to be compatible, each element must have the same type and the names must match.
ce.parse("(x: 1, y: 2)").type.matches("tuple<x: integer, y: integer>");
// ➔ true
ce.parse("(x: 1, y: 2)").type.matches("tuple<a: integer, b: integer>");
// ➔ false
A list is an ordered collection of values, used to represent vectors, matrices, and sequences.
The type of a list is represented by the type expression list<T>
, where T
is the type of the elements of the list.
ce.parse("[1, 2, 3]").type.toString();
// ➔ "list<number>"
The shorthand list
is equivalent to list<any>
, a list of values of any type.
ce.parse("[1, 2, 3]").matches("list");
// ➔ true
The shorthand vector
is a list of numbers, equivalent to list<number>
.
ce.parse("[1, 2, 3]").matches("vector");
// ➔ true
A vector<n>
is a list of n
numbers.
ce.parse("[1, 2, 3]").type.matches("vector<3>");
// ➔ true
A vector<T^n>
is a list of n
elements of type T
.
ce.parse("[1, 2, 3]").type.matches("vector<integer^3>");
// ➔ true
Similarly, a matrix
is a list of lists.
- The shorthand
matrix
ismatrix<number^?x?>
, a matrix of elements of typeT
, a list of lists of numbers, of rank 2 but of any dimensions. The?
symbol is a wildcard that matches any number of elements. matrix<T>
: A matrix of elements of typeT
, of any dimensions.matrix<nxm>
: A matrix ofn
rows andm
columns (e.g.matrix<3x3>
)matrix<T^nxm>
: A matrix ofn
rows andm
columns of elements of typeT
(e.g.matrix<boolean^3x3>
)
And finally, a tensor
is a multi-dimensional array of any values, of any rank,
and tensor<T>
is a tensor of elements of type T
.
A map is a collection of key-value pairs, used to represent a dictionary, also known as an associative array, a hash table or a record.
The keys of a map must use the letters a
to z
or A
to Z
, the digits
0
to 9
or the underscore _
. Keys containing other characters must be
enclosed in backticks.
Keys must be unique within a map, but they are not ordered.
The type of a map is represented by the type expression map<K1: T1, K2: T2, ...>
,
where K1
, K2
, ... are the keys and T1
, T2
, ... are the types of the values.
For example: map<red: integer, green: integer, blue: integer>
is a map that
contains three elements with keys red
, green
and blue
, and values of type integer
.
For a map A
to be compatible with a map B
, the keys of A
must be a
subset of the keys of B
and the values of A
must be compatible with the
values of B
.
ce.type("map<red: integer, green: integer>")
.matches("map<red: integer, green: integer>");
// ➔ true
ce.type("map<red: integer, green: integer>")
.matches("map<red: integer, green: integer, blue: integer>");
// ➔ false
ce.type("map<red: integer, green: integer, blue: integer>")
.matches("map<red: integer, green: integer>");
// ➔ true
The type map
matches any map.
The type collection
represent any collection of values, such as a list
,
a set
, a tuple
, or a map
.
The type collection<T>
is a collection of values of type T
.
The collection type is an abstract type that is not directly instantiated. It can be used to check if an expression is a collection of values, without specifying the exact type of the collection.
A function signature is the type of functions literals.
A function signature is represented by the type expression (T1) -> T2
, where
T1
is the type of the input values of the function literal and T2
is the
type of the output value, or return type, of the function literal.
If the function never returns, the function signature is (T) -> never
.
If the function does not return a value, the function signature is (T) -> nothing
.
If there is a single input argument, the parentheses can be omitted: T1 -> T2
.
For example, real -> integer
is the type of functions that map real numbers to integers.
If there are no input arguments, use () -> T
, for example () -> integer
is
the type of functions that return an integer and have no input arguments.
If there are multiple input arguments, the function signature is (T1, T2, ...) -> T
,
for example (integer, integer) -> integer
is the type of functions that map two integers to an integer.
Optionally, the input arguments can be named, for example: (x: integer, y: integer) -> integer
.
The name of the argument must use the letters a
to z
and A
to Z
, the digits 0
to 9
or the underscore _
and must start with a letter or an underscore.
An optional argument is indicated by a question mark after the type.
For example (integer, integer?) -> integer
indicates a function literal accepting
one or two integers as input and returning an integer.
If there are any optional arguments, they must be at the end of the argument list.
ce.type("(integer, integer?) -> number").matches("(integer) -> number");
// ➔ true
A function signature can include a variable number of arguments, also known as
a rest argument, indicated by an ellipsis ...
before the type of the last argument.
For example (string, ...integer) -> integer
is a function that accepts a
string as a first argument followed by any number of integers and returns an integer.
To indicate that the function accepts a variable number of arguments of any
type, use ...any
.
ce.type("(integer, ...integer) -> number").matches("(integer, integer) -> number");
// ➔ true
The type function
matches any function literal, it is a shorthand for (...any) -> unknown
.
A value type is a type that represents a single value.
A value can be:
- a boolean:
true
orfalse
- a number, such as
42
,-3.14
, or6.022e23
- a string, such as
"yellow"
,
Value types can be used in conjunction with a union to represent a type that can be one of multiple values, for example:
0 | 1
is the type of values that are either0
or1
.integer | false
is the type of values that are integers orFalse
."red" | "green" | "blue"
is the type of values that are either of the strings"red"
,"green"
or"blue"
.
Types can be combined to form new types using the following operations:
A union is the type of values that are in either of two types.
The type of a union is represented by the type expression T1 | T2
, where T1
and T2
are the types of the values.
For example, number | boolean
is the type of values that are numbers or booleans.
An intersection is the type of values that are in both of two types.
The type of an intersection is represented by the type expression T1 & T2
, where T1
and T2
are the types of the values.
For example, map<length: integer> & map<size: integer>
is the type of values
that are dictionaries with both a length
and a size
key.
A negation is the type of values that are not of a given type.
A type negation is represented by the type expression !T
, where T
is a type.
For example, !integer
is the type of values that are not integers.
The type integer & !0
is the type of values that are integers but not 0
.
Two types can be evaluated for their compatibility. A type A
is
compatible with a type B
(or matches it) if all values of type A
are also
values of type B
. In other words, if A
is a non-strict subtype of B
.
To check if two types are compatible, use the type.matches()
method.
ce.type("integer").matches("number");
// ➔ true
ce.type("number").matches("integer");
// ➔ false
ce.parse("3.14").type.matches("real");
// ➔ true
:::warning Do not check for type compatibility by comparing the type strings directly.
Type strings may represent refined or derived types
(e.g. real
vs finite_real
), so use .matches()
for compatibility checks
instead of strict equality.
ce.parse("3.14").type === "real";
// ➔ false (the type is actually "finite_real")
ce.parse("3.14").type.matches("real");
// ➔ true
:::
Maps are compatible if they have the same keys and the values are compatible.
ce.parse("{red: 1, green: 2}").type
.matches("map<red: integer, green: integer>");
// ➔ true
Width subtyping is supported for maps, meaning that a map with more keys is compatible with a map with fewer keys.
ce.parse("{red: 1, green: 2, blue: 3}").type
.matches("map<red: integer, green: integer>");
// ➔ true
Tuples are compatible if they have the same length and the elements are compatible.
ce.parse("(1, 2, 3)").type
.matches("tuple<integer, integer, integer>");
// ➔ true
If the elements of a tuple are named, the names must match.
ce.parse("(x: 1, y: 2)").type
.matches("tuple<x: integer, y: integer>");
// ➔ true
ce.parse("(x: 1, y: 2)").type
.matches("tuple<a: integer, b: integer>");
// ➔ false
Lists are compatible if they have the same length and the elements are compatible.
ce.parse("[1, 2, 3]").type
.matches("list<finite_integer>");
// ➔ true
Function literals are compatible if the input types are compatible and the output types are compatible.
ce.type("integer -> integer")
.matches("number -> number");
// ➔ true
The name of the arguments of a function signature is not taken into account when checking for compatibility.
ce.type("x: integer -> integer")
.matches("integer -> integer");
// ➔ true
The properties expr.isNumber
, expr.isInteger
, expr.isRational
and
expr.isReal
are shortcuts to check if the type of an expression matches the
types "number"
, "integer"
, "rational"
and "real"
respectively.
console.info(ce.box(3.14).type);
// ➔ "finite_real"
console.info(ce.box(3.14).type.matches("finite_real"))
// ➔ true
console.info(ce.box(3.14).type.matches("real"))
// ➔ true
console.info(ce.box(3.14).isReal)
// ➔ true
console.info(ce.box(3.14).type.matches("integer"))
// ➔ false
console.info(ce.box(3.14).isInteger)
// ➔ false
To define new types use the ce.declareType()
function.
For example, to defines a new type point
that is a tuple of two
integers, x
and y
:
ce.declareType("point", "tuple<x: integer, y: integer>");
The type is defined in the current lexical scope.
By default, types are nominal, meaning that to be compatible, they must have the same name and not just the same structure.
ce.type("tuple<x: integer, y: integer>")
.matches("point");
// ➔ false
To make a type structural, use the ce.declareType()
function with the
alias
option. Two structural types are compatible if they have the same structure,
regardless of their names.
ce.declareType("pointData", "tuple<x: integer, y: integer>", { alias: true });
ce.type("tuple<x: integer, y: integer>")
.matches("pointData");
// ➔ true
A recursive type is a type that refers to itself in its definition.
For example, a binary tree can be defined as a tuple of a value and two subtrees:
ce.declareType("tree", "tuple<value: integer, left: tree, right: tree>");
A set of types can be mutually recursive, meaning that they can refer to each other in their definitions.
In this case, you can use a type before declaring it by prefacing if with the type
keyword.
For example, a definition of a JSON object could be:
ce.declareType("json_value", `
nothing
| boolean
| number
| string
| type json_array
| type json_object
`);
ce.declareType("json_object", "map<string, type json>");
ce.declareType("json_array", "list<type json>");
ce.declareType("json", "json_object | json_array");
When using type json_array
or type json_object
, the type is not yet defined, but it will be
defined later in the code. This allows you to use the type before declaring it,
but it is not necessary to use the type
keyword if the type is already defined.
Symbol | Value | |
---|---|---|
ExponentialE |
Euler's number | |
MachineEpsilon |
$$ 2^{−52}$$ | The difference between 1 and the next larger floating point number. See Machine Epsilon on Wikipedia |
CatalanConstant |
$$ \sum_{n=0}^{\infty} \frac{(-1)^{n}}{(2n+1)^2} $$ See Catalan's Constant on Wikipedia |
|
GoldenRatio |
$$ \frac{1+\sqrt{5}}{2} $$ See Golden Ratio on Wikipedia |
|
EulerGamma |
See Euler-Mascheroni Constant on Wikipedia |
Function | Notation | |
---|---|---|
Add |
$$ a + b$$ | Addition |
Subtract |
$$ a - b$$ | Subtraction |
Negate |
Additive inverse | |
Multiply |
$$ a\times b $$ | Multiplication |
Divide |
$$ \frac{a}{b} $$ | Divide |
Power |
$$ a^b $$ | Exponentiation |
Root |
nth root | |
Sqrt |
Square root | |
Square |
xs: collection
Evaluate to a sum of all the elements in collection. If all the elements are
numbers, the result is a number. Otherwise it is an ["Add"]
expression.
["Sum", ["List", 5, 7, 11]]
// ➔ 23
["Sum", ["List", 5, 7, "x" , "y"]]
// ➔ ["Add", 12, "x", "y"]
Note this is equivalent to:
["Reduce", ["List", 5, 7, 11], "Add"]
body: function, ...bounds: tuple
Evaluate to the sum of body
for each value in bounds
.
["Sum", ["Add", "i", 1], ["Tuple", "i", 1, 10]]
// ➔ 65
xs: collection
Evaluate to a product of all the elements in collection
.
If all the elements are numbers, the result is a number. Otherwise it is a ["Multiply"]
expression.
["Product", ["List", 5, 7, 11]]
// ➔ 385
["Product", ["List", 5, "x", 11]]
// ➔ ["Multiply", 55, "x"]
Note this is equivalent to:
["Reduce", ["List", 5, 7, 11], "Multiply"]
f: function, bounds:tuple
Return the product of body
for each value in bounds
.
["Product", ["Add", "x", 1], ["Tuple", "x", 1, 10]]
// ➔ 39916800
Function | Notation | |
---|---|---|
Exp |
Exponential function | |
Ln |
Logarithm function, the natural logarithm, the inverse of Exp
|
|
Log |
["Log", <v>, <b>] Logarithm of base b, default 10 |
|
Lb |
Binary logarithm function, the base-2 logarithm | |
Lg |
Common logarithm, the base-10 logarithm | |
LogOnePlus |
Function | Notation | |
---|---|---|
Abs |
Absolute value, magnitude | |
Ceil |
Rounds a number up to the next largest integer | |
Floor |
Round a number to the greatest integer less than the input value | |
Chop |
Replace real numbers that are very close to 0 (less than |
|
Round |
a, b, modulus
Evaluate to True
if a
is congruent to b
modulo modulus
.
["Congruent", 26, 11, 5]
// ➔ True
value:integer
value:integer, base
Format an integer in a specific base, such as hexadecimal or binary.
If no base is specified, use base-10.
The sign of integer is ignored.
- value should be an integer.
- base should be an integer from 2 to 36.
["Latex", ["BaseForm", 42, 16]]
// ➔ (\text(2a))_{16}
Latex(BaseForm(42, 16))
// ➔ (\text(2a))_{16}
String(BaseForm(42, 16))
// ➔ "'0x2a'"
value
value, lower, upper
- If
value
is less thanlower
, evaluate tolower
- If
value
is greater thanupper
, evaluate toupper
- Otherwise, evaluate to
value
If lower
and upper
are not provided, they take the default values of -1 and
+1.
["Clamp", 0.42]
// ➔ 1
["Clamp", 4.2]
// ➔ 1
["Clamp", -5, 0, "+Infinity"]
// ➔ 0
["Clamp", 100, 0, 11]
// ➔ 11
x1, x2, ...
list
If all the arguments are real numbers, excluding NaN
, evaluate to the largest
of the arguments.
Otherwise, simplify the expression by removing values that are smaller than or equal to the largest real number.
["Max", 5, 2, -1]
// ➔ 5
["Max", 0, 7.1, "NaN", "x", 3]
// ➔ ["Max", 7.1, "NaN", "x"]
x1, x2, ...
list
If all the arguments are real numbers, excluding NaN
, evaluate to the smallest
of the arguments.
Otherwise, simplify the expression by removing values that are greater than or equal to the smallest real number.
["Min", 5, 2, -1]
// ➔ -1
["Min", 0, 7.1, "x", 3]
// ➔ ["Min", 0, "x"]
a, b
Evaluate to the Euclidian division (modulus) of a
by b
.
When a
and b
are positive integers, this is equivalent to the %
operator in
JavaScript, and returns the remainder of the division of a
by b
.
However, when a
and b
are not positive integers, the result is different.
The result is always the same sign as b
, or 0.
["Mod", 7, 5]
// ➔ 2
["Mod", -7, 5]
// ➔ 3
["Mod", 7, -5]
// ➔ -3
["Mod", -7, -5]
// ➔ -2
n
Evaluate to a rational approximating the value of the number n
.
["Rational", 0.42]
// ➔ ["Rational", 21, 50]
numerator, denominator
Represent a rational number equal to numerator
over denominator
.
expr
Return the numerator of expr
.
Note that expr
may be a non-canonical form.
["Numerator", ["Rational", 4, 5]]
// ➔ 4
expr
Return the denominator of expr
.
Note that expr
may be a non-canonical form.
["Denominator", ["Rational", 4, 5]]
// ➔ 5
expr
Return the numerator and denominator of expr
as a sequence.
Note that expr
may be a non-canonical form.
["NumeratorDenominator", ["Rational", 4, 5]]
// ➔ ["Sequence", 4, 5]
The sequence can be used with another function, for example GCD to check if the fraction is in its canonical form:
["GCD", ["NumeratorDenominator", ["Rational", 4, 5]]]
// ➔ 1
["GCD", ["NumeratorDenominator", ["Rational", 8, 10]]]
// ➔ 2
Function | Notation | |
---|---|---|
Equal |
$$ x = y $$ | Mathematical relationship asserting that two quantities have the same value |
NotEqual |
$$ x \ne y $$ | |
Greater |
$$ x \gt y $$ | |
GreaterEqual |
$$ x \geq y $$ | |
Less |
$$ x \lt y $$ | |
LessEqual |
$$ x \leq y $$ |
See below for additonal relational operators: Congruent
, etc...
:::info[Note] To use Compute Engine, you must write JavaScript or TypeScript code. This guide assumes you’re familiar with these languages. :::
console.log("exp(i*pi) =", ce.parse("e^{i\\pi}").evaluate());
const expr = ce.parse("(a+b)^2");
ce.box(["Expand", expr]).evaluate().print();
const lhs = ce.parse("1 + x(1 + 2x) + 2x");
const rhs = ce.parse("2x^2 + 3x + 1");
console.log(lhs, lhs.isEqual(rhs) ? "=" : "≠", rhs);
The Compute Engine is for educators, students, scientists, and engineers who need to run technical computing apps in browsers or server-side JavaScript environments like Node.js.
The Compute Engine manipulates math expressions represented with the MathJSON format.
The expression \(x^2 + 2x + 1\) is represented in MathJSON as:
["Add", ["Power", "x", 2], ["Multiply", 2, "x"], 1]
The Compute Engine can:
- parse and serialize expressions from and to LaTeX.
- simplify symbolic expressions
- evaluate expression symbolically or numerically
- solve equations, calculate derivatives and integrals, and perform other calculus operations
- compile expressions to JavaScript
The easiest way to get started is to load the Compute Engine JavaScript module
from a CDN, then create a ComputeEngine
instance.
To load the Compute Engine module from the jsdelivr CDN, use a <script>
tag with the
type="module"
attribute and an import
statement.
<script type="module">
import { ComputeEngine } from "https://esm.run/@cortex-js/compute-engine";
const ce = new ComputeEngine();
ce.parse("e^{i\\pi}").evaluate().print();
// ➔ "-1"
</script>
Alternatively, you can use the unpkg CDN to load the module:
import { ComputeEngine } from
"https://unpkg.com/@cortex-js/compute-engine?module";
The ESM (module) version is also available in the npm package as /compute-engine.min.esm.js
If you are using a vintage environment, or if your toolchain does not support modern JavaScript features, use the UMD version.
For example, WebPack 4 does not support the optional chaining operator, using the UMD version will make use of polyfills as necessary.
To load the UMD version, use a <script>
tag with the src
attribute.
<script
src="https://cdn.jsdelivr.net/npm/@cortex-js/compute-engine/compute-engine.min.js">
</script>
<script>
window.onload = function() {
const ce = new ComputeEngine.ComputeEngine();
console.log(ce.parse("e^{i\\pi}").evaluate());
// ➔ "-1"
}
</script>
Alternatively, use the unpkg CDN to load the library:
<script src="//unpkg.com/@cortex-js/compute-engine"></script>
The UMD version is also available in the npm package in /compute-engine.min.js
A non-minified module which may be useful for debugging is available in
the npm package as /compute-engine.esm.js
.
The operators in a MathJSON expression are defined in libraries. The
MathJSON Standard Library is a collection of functions and symbols that are
available by default to a ComputeEngine
instance.
Topic | |
---|---|
Arithmetic | Add Multiply Power Exp Log ExponentialE ImaginaryUnit ... |
Calculus | D Derivative Integrate ... |
Collections | List Reverse Filter ... |
Complex | Real Conjugate , ComplexRoots ... |
Control Structures | If Block Loop ... |
Core | Declare Assign Error LatexString ... |
Functions | Function Apply Return ... |
Logic | And Or Not True False ... |
Sets | Union Intersection EmptySet RealNumbers Integers ... |
Special Functions | Gamma Factorial ... |
Statistics | StandardDeviation Mean Erf ... |
Styling | Delimiter Style ... |
Trigonometry | Pi Cos Sin Tan ... |
:::info[Note]
In this guide, the ce.
prefix in ce.box()
or ce.parse()
indicates
that the function is a method of the ComputeEngine
class.
To create a new ComputeEngine
instance use ce = new ComputeEngine()
The expr.
prefix in expr.evaluate()
or expr.simplify()
indicates that the
function is a method of the BoxedExpression
class.
To create a new boxed expression use expr = ce.parse()
or expr = ce.box()
:::
Read more about the **MathJSON Standard Library**You can add your own definitions to the built-in definitions from the MathJSON Standard Library.
Read more about **Extending the MathJSON Standard Library**If you use a custom LaTeX syntax, such as macros, you can add your own definitions to the LaTeX dictionary, which defines how to parse and serialize LaTeX to MathJSON.
Read more about **Parsing and Serializing LaTeX** --- title: Statistics slug: /compute-engine/reference/statistics/ ---For the following functions, when the input is a collection, it can take the following forms:
- Multiple arguments, e.g.
["Mean", 1, 2, 3]
- A list of numbers, e.g.
["Mean", ["List", 1, 2, 3]]
- A matrix, e.g.
["Mean", ["List", ["List", 1, 2], ["List", 3, 4]]]
- A range, e.g.
["Mean", ["Range", 1, 10]]
- A linear space:
["Mean", ["Linspace", 1, 5, 10]]
collection
Evaluate to the arithmetic mean of a collection of numbers.
The arithmetic mean is the average of the list of numbers. The mean is calculated by dividing the sum of the numbers by the number of numbers in the list.
The formula for the mean of a list of numbers is
["Mean", ["List", 7, 8, 3.1, 12, 77]]
// 21.02
collection
Evaluate to the median of a collection of numbers.
The median is the value separating the higher half from the lower half of a data sample. For a list of numbers sorted in ascending order, the median is the middle value of the list. If the list has an odd number of elements, the median is the middle element. If the list has an even number of elements, the median is the average of the two middle elements.
["Median", ["List", 1, 2, 3, 4, 5]]
// 3
collection
Evaluate to the mode of a collection of numbers.
The mode is the value that appears most often in a list of numbers. A list of numbers can have more than one mode. If there are two modes, the list is called bimodal. For example $$ \lbrack 2, 5, 5, 3, 2\rbrack$$. If there are three modes, the list is called trimodal. If there are more than three modes, the list is called multimodal.
collection
Evaluate to the variance of a collection of numbers.
The variance is a measure of the amount of variation or dispersion of a set of values. A low variance indicates that the values tend to be close to the mean of the set, while a high variance indicates that the values are spread out over a wider range.
The formula for the variance of a list of numbers is
where
collection
Evaluate to the standard deviation of a collection of numbers.
The standard deviation is a measure of the amount of variation or dispersion of a set of values. A low standard deviation indicates that the values tend to be close to the mean of the set, while a high standard deviation indicates that the values are spread out over a wider range.
The formula for the standard deviation of a collection of numbers is
where
collection
Evaluate to the skewness of a list of numbers.
The skewness is a measure of the asymmetry of the distribution of a real-valued random variable about its mean. The skewness value can be positive or negative, or undefined.
The formula for the skewness of a collection of numbers is:
where
collection
Evaluate to the kurtosis of a collection of numbers.
The kurtosis is a measure of the "tailedness" of the distribution of a real-valued random variable. The kurtosis value can be positive or negative, or undefined.
The formula for the kurtosis of a collection of numbers is
where
collection, q:number
Evaluate to the quantile of a collection of numbers.
The quantile is a value that divides a collection of numbers into equal-sized groups. The quantile is a generalization of the median, which divides a collection of numbers into two equal-sized groups.
So,
$$\operatorname{median} = \operatorname{quantile}(0.5)$$
collection
Evaluate to the quartiles of a collection of numbers.
The quartiles are the three points that divide a collection of numbers into four equal groups, each group comprising a quarter of the collection.
collection
Evaluate to the interquartile range (IRQ) of a collection of numbers.
The interquartile range is the difference between the third quartile and the first quartile.
z:complex
Evaluate to the error function of a complex number.
The error function is an odd function ( $$ \operatorname{erf} -z = - \operatorname{erf} z$$ ) that is used in statistics to calculate probabilities of normally distributed events.
The formula for the error function of a complex number is:
$$ \operatorname{erf} z = \frac{2}{\sqrt{\pi}} \int_0^z e^{-t^2} dt$$
where
z:complex
Evaluate to the complementary error function of a complex number.
It is defined as $$ \operatorname{erfc} z = 1 - \operatorname {erf} z $$.
x:real
Evaluate to the inverse error function of a real number $$ -1 < x < 1 $$
It is defined as $$ \operatorname{erf} \left(\operatorname{erf} ^{-1}x\right) = x $$.
title: Expressions slug: /compute-engine/guides/expressions/ layout: single date: Last Modified sidebar:
- nav: "universal" toc: true
The Compute Engine produces and manipulates [symbolic expressions]() such as numbers, constants, variables and functions.
In the Compute Engine, expressions are represented internally using the MathJSON format.
They are wrapped in a JavaScript object, a process called boxing, and the resulting expressions are Boxed Expressions.
Boxed Expressions improve performance by implementing caching to avoid repetitive calculations. They also ensure that expressions are valid and in a standard format.
Unlike the plain data types used by JSON, Boxed Expressions allow an IDE, such as VSCode Studio, to provide hints in the editor regarding the methods and properties available for a given expression.
Boxed Expressions can be created from a LaTeX string or from a raw MathJSON expression.
To create a BoxedExpression
from a MathJSON expression use the ce.box()
method.
The input of ce.box()
can be:
- a MathJSON expression
- a
BoxedExpression
(in which case it is returned as-is) - a
SemiBoxedExpression
, that is a MathJSON expression with some of its subexpressions already boxed.
The result is an instance of a BoxedExpression
.
let expr = ce.box(1.729e3);
console.log(expr.re);
// ➔ 1729
console.log(expr.isPositive);
// ➔ true
expr = ce.box({ num: "+Infinity" });
console.log(expr.latex);
// ➔ "+\infty"
expr = ce.box(["Add", 3, "x"]);
console.log(expr.operator);
// ➔ "Add"
To create a Boxed Expression from a LaTeX string use the ce.parse()
function.
const expr = ce.parse("3 + x + y");
console.log(expr.operator);
// ➔ "Add"
console.log(expr.json);
// ➔ ["Add", 3, "x", "y"]
To get a Boxed Expression representing the content of a mathfield
use the mf.expression
property:
const mf = document.getElementById("input");
mf.value = "\\frac{10}{5}";
const expr = mf.expression;
console.log(expr.evaluate());
// ➔ 2
The canonical form of an expression is a conventional way of writing an expression.
For example, the canonical form of a fraction of two integers is a reduced rational number, written as a tuple of two integers, such that the GCD of the numerator and denominator is 1, and the denominator is positive.
const expr = ce.parse("\\frac{30}{-50}");
console.log(expr.json);
// ➔ ["Rational", -3, 5]
The canonical form of a rational with a denominator of 1 is an integer.
const expr = ce.parse("\\frac{17}{1}");
console.log(expr.json);
// ➔ 17
To determine if a non-canonical expression is a reduced (canonical) rational number check that the GCD of the numerator and denominator is 1.
const input = ce.parse("\\frac{30}{50}", {canonical: false});
console.info(ce.box(
["GCD", ["NumeratorDenominator", input]]
).evaluate().valueOf() === 1);
// ➔ false
The canonical form of an addition or multiplication will have its arguments ordered in a canonical way.
const expr = ce.parse("2+x+\\pi+\\sqrt2+1");
console.log(expr.json);
// ➔ ["Add", "Pi", ["Sqrt", 2], "x", 1, 2]
By default, ce.box()
and ce.parse()
produce a canonical expression.
To get a non-canonical expression instead, use
ce.box(expr, {canonical: false})
or ce.parse(latex, {canonical: false})
.
When using ce.parse()
, the non-canonical form sticks closer to the original
LaTeX input. When using ce.box()
, the non-canonical form matches the
input MathJSON.
const latex = "\\frac{30}{-50}";
ce.parse(latex);
// canonical form ➔ ["Rational", -3, 5]
ce.parse(latex, { canonical: false });
// non-canonical form ➔ ["Divide", 30, -50]
ce.box(["Divide", 30, -50], { canonical: false });
// non-canonical form ➔ ["Divide", 30, -50]
To obtain the canonical representation of a non-canonical expression use
expr.canonical
.
A non-canonical expression may include errors as a result of parsing from LaTeX, if the LaTeX input contained LaTeX syntax errors.
A canonical expression may include additional errors compared to a non-canonical
expression, for example ["Divide", 2, 5, 6]
(three arguments instead of two),
["Add", 2, "True"]
(mismatched argument type, expected a number but got a
boolean).
The canonical form of an expression which is not valid will include one or more
["Error"]
expressions indicating the nature of the problem.
To check if an expression contains errors use expr.isValid
. The expr.errors
property returns a list of all the ["Error"]
subexpressions.
When doing this check on a canonical expression it takes into consideration not only possible syntax errors, but also semantic errors (incorrect number or type of arguments, etc...).
The expr.toString()
method returns an AsciiMath string representation of the expression.
let expr = ce.parse("3x^2+\\sqrt{2}");
console.log(expr.toString());
When used in a context where a string is expected, the expr.toString()
method
is called automatically.
let expr = ce.parse("3x^2+\\sqrt{2}");
console.log(expr);
To output an AsciiMath representation of the expression to the console use
expr.print()
.
let expr = ce.parse("\\frac{1+\\sqrt{5}}{2}");
expr.print();
To obtain a LaTeX representation of the expression use expr.latex
or
expr.toLatex()
for additional formatting options.
let expr = ce.parse("3x^2+\\sqrt{2}");
console.log(expr.latex);
To access the MathJSON expression of a boxed expression as plain JSON use
the expr.json
property. This property is an "unboxed" version of the
expression.
const expr = ce.box(["Add", 3, "x"]);
console.log(expr.json);
// ➔ ["Add", 3, "x"]
To customize the format of the MathJSON expression returned by expr.json
use the ce.toMathJson()
method.
Use this option to control:
- which metadata, if any, should be included
- whether to use shorthand notation
- to exclude some functions
See JsonSerializationOptions for more info about the formatting options available.
const expr = ce.parse("2 + \\frac{q}{p}");
console.log("expr.json:", expr.json);
console.log("expr.toMathJson():", expr.toMathJson({
exclude: ["Divide"], // Don't use `Divide` functions,
// use `Multiply`/`Power` instead
shorthands: [], // Don't use any shorthands
}));
Unless otherwise specified, expressions are immutable.
The functions that manipulate Boxed Expressions, such as expr.simplify()
,
expr.evaluate()
, expr.N()
return a new Boxed Expression, without modifying
expr
.
However, the properties of the expression may change, since some of them may depend on contextual information which can change over time.
For example, ce.box('n').isPositive
may return undefined
if nothing is known
about the symbol n
. But if an assumption about the symbol is made later, or a value
assigned to it, then ce.box('n').isPositive
may take a different value.
const expr = ce.box("n");
console.log(expr.isPositive);
// ➔ undefined
ce.assume(ce.parse("n > 0"));
console.log(expr.isPositive);
// ➔ true
What doesn't change is the fact that expr
represents the symbol "n"
.
A pure expression is an expression that produces no side effect (doesn't change the state of the Compute Engine) and always evaluates to the same value when the same arguments are applied to it.
The Sin
function is pure: it evaluates to the same value when the
same arguments are applied to it.
On the other hand, the Random
function is not pure: by
its nature it evaluates to a different value on every evaluation.
Numbers, symbols and strings are pure. A function expression is pure if the function itself is pure, and all its arguments are pure as well.
To check if an expression is pure use expr.isPure
.
To identify if an expression is a number literal, a symbol, a function expression or a string use the following boolean expressions:
Kind | Boolean Expression |
---|---|
Number Literal | expr.isNumberLiteral |
Function Expression | expr.isFunctionExpression |
Symbol | expr.symbol !== null |
String | expr.string !== null |
To access the value of an expression as a MathJSON expression use
expr.json
.
To access the value of an expression as a JavaScript primitive use
expr.valueOf()
. The result is a JavaScript primitive, such as a number, string or
boolean. When converting to a number, the result may have lost precision if the
original expression had more than 15 digits of precision.
To access the value of an expression as a JavaScript number use
expr.re
. The result is the real part of the number, as a JavaScript number,
or NaN
if the expression is not a number. Use expr.im
to get the imaginary part.
In general, expressions need to be evaluated before they can be converted to a
JavaScript primitive. For example, ce.parse("2 + 3").valueOf()
will return
"2 + 3"
, while ce.parse("2 + 3").evaluate().valueOf()
will return 5
.
If the expression is a number literal or a symbol with a numeric value, the
expr.value
property will return the value of the expression as BoxedExpression
or undefined
if the expression is not a number.
Sometimes, things go wrong.
If a boxed expression is not valid, the expr.isValid
property will be set to
false
, and the expr.errors
property will contain a list of all the
["Error"]
subexpressions.
When something goes wrong the Compute Engine uses an
["Error", <cause>, <location>]
expression.
The <cause>
argument provides details about the nature of the problem. This
can be either a string or an ["ErrorCode"]
expression if there are additional
arguments to the error.
For example if the problem is that an argument of a function expression is a
boolean when a number was expected, an expression such as
["Error", ["ErrorCode", "'incompatible-type'", "'number'", "'boolean'"]]
could
be returned.
The <location>
argument indicates the context of the error. This can be a
["Latex"]
expression when the problem occurred while parsing a LaTeX string,
or another expression if the problem was detected later.
When parsing a LaTeX expression, the Compute Engine uses the maximum effort doctrine. That is, even partially complete expressions are parsed, and as much of the input as possible is reflected in the MathJSON result.
If required operands are missing (the denominator of a fraction, for example), a
["Error", ""missing""]
error expression is inserted where the missing operand
should have been.
Problems that occur while parsing a LaTeX string will usually indicate a LaTeX
syntax error or typo: missing }
, mistyped command name, etc...
Some errors are not caught until an expression is bound, that is until an attempt is made to associate its symbols to definitions. This could include errors such as missing or mismatched arguments.
Some errors that could be considered LaTeX syntax errors may not surface until binding occurs.
For example \frac{1}{2=x}
(instead of \frac{1}{2}=x
) will be parsed as
["Divide", 1, ["Equal", 2, x]]
. The fact that the second argument of the
"Divide"
function is a boolean and not a number will not be detected until the
definition for "Divide"
has been located.
Name binding is done lazily, not upon boxing. To force the binding to occur, request the canonical version of the expression.
To check if an expression includes an ["Error"]
subexpression check the
expr.isValid
property.
To get the list of all the ["Error"]
subexpression use the expr.errors
property.
Error Code | Meaning |
---|---|
syntax-error |
the parsing could not continue |
missing |
an expression was expected |
unexpected-argument |
too many arguments provided |
expected-argument |
not enough arguments provided |
expected-expression |
an expression was expected inside an enclosure (parentheses) |
unexpected-command |
the command is unknown, or not applicable in the current parsing context |
unexpected-token |
the character does not apply to the current parsing context |
incompatible-type |
the type of the provided argument does not match the expected type |
invalid-symbol |
the symbol cannot be used (see MathJSON Symbols) |
expected-closing-delimiter |
a closing } was expected, but is missing |
unexpected-closing-delimiter |
a closing } was encountered, but not expected |
expected-environment-name |
the name of an environment should be provided with a \begin or \end command |
unknown-environment |
the environment name provided cannot be parsed |
unbalanced-environment |
the named used with the \begin and \end commands should match |
unexpected-operator |
the operator does not apply to the current parsing context. Could be an infix or postfix operator without a rhs. |
unexpected-digit |
the string included some characters outside of the range of expected digits |
expected-string-argument |
the argument was expected to be a string |
unexpected-base |
the base is outside of the expected range (2..36) |
iteration-limit-exceeded |
a loop has reached the maximum iteration limit |
console.log(ce.parse("\\oops").json);
// ➔ ["Error", ["ErrorCode","'unexpected-command'","'\\oops'"], ["Latex","'\\oops'"]
console.log(ce.parse("\\oops{bar}+2").json);
// ➔ ["Add",
// ["Error",
// ["ErrorCode","'unexpected-command'","'\\oops'"],
// ["Latex","'\\oops{bar}'"]
// ],
// 2
// ]
console.log(ce.parse("\\begin{oops}\\end{oops}").json);
// ➔ ["Error",["ErrorCode","'unknown-environment'",""oops""],["Latex","'\\\\begin{oops}\\\\end{oops}'"]
console.log(ce.parse("1+\\sqrt").json);
// ➔ ["Add", 1 ,["Sqrt", ["Error", ""missing""]]]
console.log(ce.parse("1+\\frac{2}").json);
// ➔ ["Add", 1, ["Divide", 2, ["Error",""missing""]]]
console.log(ce.parse("1+(2=2)+2").json);
// ➔ ["Add", 1, ["Delimiter", ["Equal", 2, 2]]]
console.log(ce.parse("1+(2=2)+3").canonical.json);
// ➔ ["Add",
// 1,
// ["Error",
// ["ErrorCode", "'incompatible-domain'", "Numbers", "Booleans"],
// ["Delimiter", ["Equal", 2, 2]]
// ],
// 3
// ]
console.log(ce.parse("\\times 3").json);
// ➔ ["Sequence", ["Error", ["ErrorCode", "'unexpected-operator'", "'\\times'"], ["Latex","'\\times'"]], 3]
console.log(ce.parse("x__+1").json);
// ➔ ["Add", ["Subscript", "x", ["Error","'syntax-error'", ["Latex","'_'"]]], 1]
console.log(ce.parse("x_{a").json);
// ➔ ["Subscript", "x", ["Error", "'expected-closing-delimiter'", ["Latex","'{a'"]]]
console.log(ce.parse("x@2").json);
// ➔ ["Sequence", "x", ["Error", ["ErrorCode", "'unexpected-token'", "'@'"], ["Latex", "'@2'"]]]
In the Compute Engine, **collections** are used to represent data structures. They group together multiple elements into one unit. Each element in a collection is a [**Boxed Expression**](/compute-engine/guides/expressions/).
Collections are immutable. They cannot be modified. Operations on collections return new collections.
A ["List"]
expression can represent an heterogeneous collection of elements.
["List", 42, 3.14, "x", "y"]
List of numbers can be used to represent vectors.
["List", 1, 2, 3]
A matrix is represented using a List
of List
of numbers.
["List", ["List", 1, 2, 3], ["List", 4, 5, 6], ["List", 7, 8, 9]]
Lists of lists can also be represented using a ;
separator:
And matrixes can be represented using LaTeX environments:
See also the **Linear Algebra** section for operations on vectors, matrixes and tensors.Another common collection is the Range
which is used to represent a sequence
of numbers from a lower bound to an upper bound. Both lower and upper bounds are
included in the range.
["Range", 1, 10]
Collections operations such as IsEmpty
, Extract
, IndexOf
can be applied to
any collection types.
["Extract", ["List", 2, 5, 7], 2]
// ➔ ["List", 5]
["Extract", ["Range", 2, 10], 5]
// ➔ ["List", 7]
This section contains functions that return a collection, but whose arguments are not collections. They are used to create collections.
x-1, ...x-2
A List
is an ordered, indexable collection of elements. An element in
a list may be repeated.
The visual presentation of a List
expression can be customized using the
Delimiter
function.
ce.box(["List", 5, 2, 10, 18]).latex;
// ➔ "\lbrack 5, 2, 10, 18 \rbrack"
ce.box(["Delimiter", ["List", 5, 2, 10, 18], "<;>"]).latex;
// ➔ "\langle5; 2; 10; 18\rangle"
MathJSON | LaTeX |
---|---|
["List", "x", "y", 7, 11] |
$$ \lbrack x, y, 7, 11\rbrack $$ |
["List", "x", "Nothing", "y"] |
$$ \lbrack x,,y\rbrack $$ |
upper
lower, upper
lower, upper, step
A sequence of numbers, starting with lower
, ending with upper
, and
incrementing by step
.
If the step
is not specified, it is assumed to be 1
.
If there is a single argument, it is assumed to be the upper
bound, and the
lower
bound is assumed to be 1
.
["Range", 3, 9]
// ➔ ["List", 3, 4, 5, 6, 7, 8, 9]
["Range", 7]
// ➔ ["List", 1, 2, 3, 4, 5, 6, 7]
["Range", 1, 10, 2]
// ➔ ["List", 1, 3, 5, 7, 9]
["Range", 10, 1, -1]
// ➔ ["List", 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
upper
lower, upper
lower, upper, count
Short for "linearly spaced", from the (MATLAB function of the same name)[mathworks.com/help/matlab/ref/linspace.html].
A sequence of numbers. Similar to Range
but the number of elements in the
collection is specified with count
instead of a step
value.
If the count
is not specified, it is assumed to be 50
.
If there is a single argument, it is assumed to be the upper
bound, and the
lower
bound is assumed to be 1
.
["Linspace", 3, 9]
// ➔ ["List", 3, 3.163265306122449, 3.326530612244898, 3.489795918367347, 3.653061224489796, 3.816326530612245, 3.979591836734694, 4.142857142857143, 4.3061224489795915, 4.469387755102041, 4.63265306122449, 4.795918367346939, 4.959183673469388, 5.122448979591837, 5.285714285714286, 5.448979591836735, 5.612244897959184, 5.775510204081633, 5.938775510204081, 6.1020408163265305, 6.26530612244898, 6.428571428571429, 6.591836734693878, 6.755102040816326, 6.918367346938775, 7.081632653061225, 7.244897959183673, 7.408163265306122, 7.571428571428571, 7.73469387755102, 7.8979591836734695, 8.061224489795919, 8.224489795918368, 8.387755102040817, 8.551020408163266, 8.714285714285714, 8.877551020408163, 9.040816326530612, 9.204081632653061, 9.36734693877551, 9.53061224489796, 9.693877551020408, 9.857142857142858, 10]
["Linspace", 7]
// ➔ ["List", 1, 1.1428571428571428, 1.2857142857142858, 1.4285714285714286, 1.5714285714285714, 1.7142857142857142, 1.8571428571428572, 2]
["Linspace", 1, 10, 5]
// ➔ ["List", 1, 3.25, 5.5, 7.75, 10]
["Linspace", 10, 1, 10]
// ➔ ["List", 10, 9.11111111111111, 8.222222222222221, 7.333333333333333, 6.444444444444445, 5.555555555555555, 4.666666666666666, 3.7777777777777777, 2.888888888888889, 2]
expr-1, ...expr-2
An unordered collection of unique elements.
["Set", 12, 15, 17]
dimensions, value
dimensions, function
Create a list of the specified dimensions.
If a value
is provided, the elements of the list are all set to that value.
If a function
is provided, the elements of the list are computed by applying
the function to the index of the element.
If dimensions
is a number, a list of that length is created.
["Fill", 3, 0]
// ➔ ["List", 0, 0, 0]
If dimension is a tuple, a matrix of the specified dimensions is created.
["Fill", ["Tuple", 2, 3], 0]
// ➔ ["List", ["List", 0, 0, 0], ["List", 0, 0, 0]]
If a function
is specified, it is applied to the index of the element to
compute the value of the element.
["Fill", ["Tuple", 2, 3], ["Function", ["Add", "i", "j"], "i", "j"]]
// ➔ ["List", ["List", 0, 1, 2], ["List", 1, 2, 3]]
expr
An infinite collection of the same element.
["Repeat", 0]
// ➔ ["List", 0, 0, 0, ...]
Use Take
to get a finite number of elements.
["Take", ["Repeat", 42], 3]
// ➔ ["List", 42, 42, 42]
Note: ["Repeat", n]
is equivalent to ["Cycle", ["List", n]]
.
seed
A collection that repeats the elements of the seed
collection. The input
collection must be finite.
["Cycle", ["List", 5, 7, 2]]
// ➔ ["List", 5, 7, 2, 5, 7, 2, 5, 7, ...]
["Cycle", ["Range", 3]]
// ➔ ["List", 1, 2, 3, 1, 2, 3, 1, 2, ...]
Use Take
to get a finite number of elements.
["Take", ["Cycle", ["List", 5, 7, 2]], 5]
// ➔ ["List", 5, 7, 2, 5, 7]
function
function, initial
An infinite collection of the results of applying function
to the initial
value.
If the initial
value is not specified, it is assumed to be 0
["Iterate", ["Function", ["Multiply", "_", 2]], 1]
// ➔ ["List", 1, 2, 4, 8, 16, ...]
Use Take
to get a finite number of elements.
["Take", ["Iterate", ["Function", ["Add", "_", 2]], 7], 5]
// ➔ ["List", 7, 9, 11, 13, 15]
This section contains functions whose argument is a collection and which return a collection made of a subset of the elements of the input.
collection, n
Return a list without the first n
elements.
If n
is negative, it returns a list without the last n
elements.
["Drop", ["List", 5, 2, 10, 18], 2]
// ➔ ["List", 10, 18]
["Drop", ["List", 5, 2], -2]
// ➔ ["List", 5, 2]
collection, index
collection, index1, index2
collection, range
Exclude
is the opposite of Extract
. It returns a list of the elements that
are not at the specified indexes.
The elements are returned in the same order as they appear in the collection.
["Exclude", ["List", 5, 2, 10, 18], 2]
// ➔ ["List", 5, 10, 18]
["Exclude", ["List", 5, 2, 10, 18], -2, 1]
// ➔ ["List", 2, 18]
["Exclude", ["List", 5, 2, 10, 18], ["Range", 2, 3]]
// ➔ ["List", 5, 2]
["Exclude", ["List", 5, 2, 10, 18], ["Range", 1, -1, 2]]
// ➔ ["List", 2, 18]
An index may be repeated, but the corresponding element will only be dropped once.
["Exclude", ["List", 5, 2, 10, 18], 3, 3, 1]
// ➔ ["List", 2, 18]
collection, index
collection, index1, index2
collection, range
Returns a list of the elements at the specified indexes.
Extract
is a flexible function that can be used to extract a single element, a
range of elements, or a list of elements.
Extract
always return a list, even if the result is a single element. If no
elements match, an empty list is returned.
["Extract", ["List", 5, 2, 10, 18], 2]
// ➔ ["List", 10]
["Extract", ["List", 5, 2, 10, 18], -2, 1]
// ➔ ["List", 10, 5]
["Extract", ["List", 5, 2, 10, 18], 17]
// ➔ ["List"]
When using a range, it is specified as a Range
expression.
// Elements 2 to 3
["Extract", ["List", 5, 2, 10, 18], ["Range", 2, 4]]
// ➔ ["List", 2, 10, 18]
// From start to end, every other element
["Extract", ["List", 5, 2, 10, 18], ["Range", 1, -1, 2]]
// ➔ ["List", 5, 10]
The elements are returned in the order in which they're specified. Using negative indexes (or ranges) reverses the order of the elements.
// From last to first = reverse
["Extract", ["List", 5, 2, 10, 18], ["Range", -1, 1]]
// ➔ ["List", 18, 10, 2, 5]
// From last to first = reverse
["Extract", ""desserts"", ["Range", -1, 1]]
// ➔ ""stressed""
An index can be repeated to extract the same element multiple times.
["Extract", ["List", 5, 2, 10, 18], 3, 3, 1]
// ➔ ["List", 10, 10, 5]
collection
Return the first element of the collection.
It's equivalent to ["Take", _collection_, 1]
.
["First", ["List", 5, 2, 10, 18]]
// ➔ 5
["First", ["Tuple", "x", "y"]]
// ➔ "x"
collection-1, collection-2, ...
Returns a collection that contains the elements of the first collection followed by the elements of the second collection.
All the collections should have the same head.
["Join", ["List", 5, 2, 10, 18], ["List", 1, 2, 3]]
// ➔ ["List", 5, 2, 10, 18, 1, 2, 3]
collection, n
Return a list of the first n
elements of the collection.
If n
is negative, it returns the last n
elements.
["Take", ["List", 5, 2, 10, 18], 2]
// ➔ ["List", 5, 2]
["Take", ["List", 5, 2, 10, 18], -2]
// ➔ ["List", 18, 10]
collection
Return the last element of the collection.
["Last", ["List", 5, 2, 10, 18]]
// ➔ 18
collection, n
Return the last n elements of the collection.
["Last", ["List", 5, 2, 10, 18], 2]
// ➔ ["List", 10, 18]
collection
Return everything but the last element of the collection.
It's equivalent to ["Drop", _collection_, -1]
.
["Most", ["List", 5, 2, 10, 18]]
// ➔ ["List", 5, 2, 10]
collection
Return everything but the first element of the collection.
It's equivalent to ["Drop", _collection_, 1]
.
["Rest", ["List", 5, 2, 10, 18]]
// ➔ ["List", 2, 10, 18]
collection
Return the collection in reverse order.
["Reverse", ["List", 5, 2, 10, 18]]
// ➔ ["List", 18, 10, 2, 5]
It's equivalent to ["Extract", _collection_, ["Tuple", -1, 1]]
.
collection, count
Returns a collection where the elements are rotated to the left by the specified count.
["RotateLeft", ["List", 5, 2, 10, 18], 2]
// ➔ ["List", 10, 18, 5, 2]
collection, count
Returns a collection where the elements are rotated to the right by the specified count.
["RotateRight", ["List", 5, 2, 10, 18], 2]
// ➔ ["List", 10, 18, 5, 2]
collection
Return the second element of the collection.
["Second", ["Tuple", "x", "y"]]
// ➔ "y"
collection
Return the collection in random order.
["Shuffle", ["List", 5, 2, 10, 18]]
// ➔ ["List", 10, 18, 5, 5]
collection
collection, order-function
Return the collection in sorted order.
["Sort", ["List", 5, 2, 10, 18]]
// ➔ ["List", 2, 5, 10, 18]
collection
Returns a list of the elements in collection
without duplicates.
This is equivalent to the first element of the result of Tally
:
["First", ["Tally", _collection_]]
.
["Unique", ["List", 5, 2, 10, 18, 5, 2, 5]]
// ➔ ["List", 5, 2, 10, 18]
The section contains functions whose argument is a collection, but whose return value is not a collection.
collection, index
collection, index1, index2, ...
Returns the element at the specified index.
There can be multiple indexes, up to the rank of the collection.
["At", ["List", 5, 2, 10, 18], 2]
// ➔ 10
["At", ["List", 5, 2, 10, 18], -2]
// ➔ 10
["At", ["List", ["List", 1, 2], ["List", 3, 4]], 2, 1]
// ➔ 3
collection, function
Returns a collection where function is applied to each element of the
collection. Only the elements for which the function returns "True"
are kept.
["Filter", ["List", 5, 2, 10, 18], ["Function", ["Less", "_", 10]]]
// ➔ ["List", 5, 2]
collection, fn
collection, fn, initial
Returns a collection where the reducing function fn is applied to each element of the collection.
Fold
performs a left fold operation: the reducing function is applied to the
first two elements, then to the result of the previous application and the next
element, etc...
When an initial
value is provided, the reducing function is applied to the
initial value and the first element of the collection, then to the result of the
previous application and the next element, etc...
[
"Fold",
["List", 5, 2, 10, 18]
["Function", ["Add", "_1", "_2"]],
]
// ➔ 35
The name of a function can be used as a shortcut for a function that takes two arguments.
["Fold", ["List", 5, 2, 10, 18], "Add"]
// ➔ 35
collection
Returns the number of elements in the collection.
When the collection is a matrix (list of lists), Length
returns the number of
rows.
["Length", ["List", 5, 2, 10, 18]]
// ➔ 4
When the collection is a string, Length
returns the number of characters in
the string.
["Length", { "str": "Hello" }]
// ➔ 5
collection
Returns the symbol True
if the collection is empty.
["IsEmpty", ["List", 5, 2, 10, 18]]
// ➔ "False"
["IsEmpty", ["List"]]
// ➔ "True"
["IsEmpty", "x"]
// ➔ "True"
["IsEmpty", {str: "Hello"]
// ➔ "False"
collection, function
Returns a collection where function is applied to each element of the input collection.
["Map", ["Function", ["Add", "x", 1], "x"], ["List", 5, 2, 10, 18]]
// ➔ ["List", 6, 3, 11, 19]
["Map", ["List", 5, 2, 10, 18], ["Function", ["Add", "_", 1]]]
// ➔ ["List", 6, 3, 11, 19]
collection
collection, order-function
Return the indexes of the collection in sorted order.
["Ordering", ["List", 5, 2, 10, 18]]
// ➔ ["List", 2, 1, 3, 4]
To get the values in sorted order, use Extract
:
["Assign", "l", ["List", 5, 2, 10, 18]]
["Extract", "l", ["Ordering", "l"]]
// ➔ ["List", 2, 5, 10, 18]
// Same as Sort:
["Sort", "l"]
// ➔ ["List", 2, 5, 10, 18]
collection
Returns a tuples of two lists:
- The first list contains the unique elements of the collection.
- The second list contains the number of times each element appears in the collection.
["Tally", ["List", 5, 2, 10, 18, 5, 2, 5]]
// ➔ ["Tuple", ["List", 5, 2, 10, 18], ["List", 3, 2, 1, 1]]
collection-1, collection-2, ...
Returns a collection of tuples where the first element of each tuple is the first element of the first collection, the second element of each tuple is the second element of the second collection, etc.
The length of the resulting collection is the length of the shortest collection.
["Zip", ["List", 1, 2, 3], ["List", 4, 5, 6]]
// ➔ ["List", ["Tuple", 1, 4], ["Tuple", 2, 5], ["Tuple", 3, 6]]
Control Structures define how a sequence of expressions is evaluated.
The flow of a program is controlled by control structures. Control structures are expressions that define how a sequence of expressions is evaluated.
There are three kind of control structures:
- Sequential:
Block
, the most common where expressions are evaluated one after the other - Conditional
If
orWhich
, where expressions are evaluated depending on the value of a condition - Iterative
Loop
orFixedPoint
, where expressions are evaluated repeatedly
expr-1, ...expr-n
A ["Block"]
expression is a sequence of expressions that are evaluated
sequentially.
A new scope is created for the ["Block"]
expression. The scope is destroyed
when the ["Block"]
expression is finished evaluating. This means that
variables defined in the ["Block"]
expression are not accessible outside of
the ["Block"]
expression.
The value of the ["Block"]
expression is the value of the last expression
expr-n
.
If one of the expression in the block is a ["Return"]
expression, a
["Break"]
expression or a ["Continue"]
expression, no more expressions are
evaluated and the value of the ["Block"]
is this expression.
["Block"]
expressions can be nested as necessary.
["Block", ["Assign", "c", 5], ["Multiply", "c", 2]]
// ➔ 10
condition, expr-1
If the value of condition
is the symbol True
, the value of the ["If"]
expression is expr-1
, otherwise Nothing
.
condition, expr-1, expr-2
If the value of condition
is the symbol True
, the value of the ["If"]
expression is expr-1
, otherwise expr-2
.
Here's an example of a function that returns the absoluve value of a number:
["Function", ["If", ["Greater", "n", 0], "n", ["Negate", "n"]], "n"]
["If"]
expressions can be nested as necessary.
condition-1, expr-1, ...condition-n, expr-n
The value of the ["Which"]
expression is the value of the first expression
expr-n
for which the corresponding condition condition-n
is True
.
["Block",
["Assign", "n", -10]
["Which", ["Greater", "n", 0], "n", ["Negate", "n"], "n"]
]
// ➔ 10
A ["Which"]
expression is equivalent to the following ["If"]
expression:
["If", ["Equal", condition-1, "True"], expr-1,
["If", ["Equal", condition-2, "True"], _expr-2,
... ["If", ["Equal", condition-n, "True"],
expr-n,
"Nothing"
]
]
]
A ["Which"]
expression is equivalent to a switch
statement in JavaScript or
the Which[]
function in Mathematica.
body
Repeatedly evaluate body
until the value of body
is a ["Break"]
expression,
or a ["Return"]
expression.
["Break"]
exits the loop immediately. The value of the["Loop"]
expression is the value of the["Break"]
expression.["Return"]
exits the loop and returns the value of the["Return"]
expression.
To exit the loop, a ["Break"]
or ["Return"]
expression must be evaluated.
Loop
with only a body argument is equivalent to a while(true)
in
JavaScript or a While[True, ...]
in Mathematica.
body, collection
Iterates over the elements of collection
and evaluates body
with an implicit
argument _
whose value is the current element. The value of the ["Loop"]
expression is the value of the last iteration of the loop, or the value of the
["Break"]
expression if the loop was exited with a ["Break"]
expression.
["Loop", ["Print", ["Square", "_"]], ["Range", 5]]
// ➔ 1 4 9 16 25
["Loop", ["Function", ["Print", ["Square", "x"], "x"]], ["Range", 5]]
// ➔ 1 4 9 16 25
Loop
with a body
and collection
to iterate is equivalent to a forEach()
in JavaScript. It is somewhat similar to a Do[...]
in Mathematica.
body, initial-value
body, initial-value, max-iterations
Assumes body
is an expression using an implicit argument _
.
Apply body
to initial-value
, then apply body
to the result until the result
no longer changes.
To determine if a fixed point has been reached and the loop should terminate,
the previous and current values are compared with Equal
.
Inside body
, use a ["Break"]
expression to exit the loop immediately or
Return
to exit the enclosing ["Function"]
expression.
To exit a function, use Return
.
To control the flow of a loop expression, use Break
and Continue
.
value
Interupts the evaluation of a ["Function"]
expression. The value of the
["Function"]
expression is value
.
The ["Return"]
expression is useful when used with functions that have
multiple exit points, conditional logic, loops, etc...
Here's a contrived example of a function that returns the sign of a number:
[
"Function",
[
"Block",
["If", ["Greater", "x", 0], ["Return", 1]],
["If", ["Less", "x", 0], ["Return", -1]],
0
],
"x"
]
value
When in a loop exit the loop immediately. The final value of the loop is
value
or Nothing
if not provided.
value
When in a loop, skip to the next iteration of the loop. The value of the
iteration is value
or Nothing
if not provided.
A **symbol** is a named object in the Compute Engine. It has a type and may hold a value. A symbol without a value represents a mathematical unknown in an expression.
To change the value or type of a symbol, use the value
and type
properties of the symbol.
A symbol does not have to be declared before it can be used. The type of a
symbol will be inferred based on its usage or its value. If its type cannot be
inferred, the type will be unknown
.
const n = ce.box("n");
n.value = 5;
console.log("n =", n.value.toString(), ":", n.type);
To get a list of all the symbols in an expression use expr.symbols
.
Symbols are defined within a lexical scope.
Read more about **scopes**A symbol that has been declared, but has no values associated with it, is said to be an unknown.
A symbol whose value cannot be changed is a constant. Constants are identified by a special flag in their definition.
To check if a symbol is a constant, use the expr.isConstant
property.
console.log(ce.box("x").isConstant);
// ➔ false
console.log(ce.box("Pi").isConstant);
// ➔ true
:::warning
The value of constants may depend on settings of the Compute Engine. For
example, the value of Pi
is determined based on the value of the precision
property. The values of constants in scope when the precision
setting is
changed will be updated.
:::
ce.precision = 4;
const smallPi = ce.box("Pi"); // π with 4 digits
console.log(smallPi.latex);
// ➔ 3.1415
ce.precision = 10;
const bigPi = ce.box("Pi"); // π with 10 digits
console.log(bigPi.latex);
// ➔ 3.1415926535
ce.precision = 100; // Future computations will be done with 100 digits
console.log("pi = ", smallPi, "=", bigPi);
// ➔ pi = 3.1415 = 3.1415926535
An unknown symbol is automatically declared when it is first used in an expression.
The symbol has a type of unknown
and no value associated with it,
so the symbol will be an unknown.
const symbol = ce.box("m"); // m for mystery
console.log(symbol.type);
// ➔ "unknown"
symbol.value = 5;
console.log(symbol.type);
// ➔ "finite_integer"
If the type of a symbol is inferred from its usage, the type can be adjusted later as further information is provided. However, if the type is provided in the declaration, the type cannot be changed later.
To reset what is known about a symbol use the ce.forget()
function.
The ce.forget()
function will remove any
assumptions associated with a symbol, and
remove its value. Howeve, the symbol will remained declared, since other
expressions may depend on it.
To forget about a specific symbol, pass the name of the symbol as an
argument to ce.forget()
.
To forget about all the symbols in the current scope, use ce.forget()
without any arguments.
:::info[Note] Note that only symbols in the current scope are forgotten. If assumptions about the symbol existed in a previous scope, those assumptions will be in effect when returning to the previous scope. :::
n
["Factorial", 5]
// -> 120
n
The double factorial of n
: $$ n!! = n \cdot (n-2) \cdot (n-4) \times
\cdots$$, that is the product of all the positive integers up to n
that have
the same parity (odd or even) as n
.
["Factorial2", 5]
// -> 15
It can also be written in terms of the $$ \Gamma $$ function:
This is not the same as the factorial of the factorial of n
(i.e.
Reference
- WikiPedia: Double Factorial
z
The Gamma Function is an extension of the factorial function, with its argument shifted by 1, to real and complex numbers.
$$
\operatorname{\Gamma}\left(z\right) = \int\limits_{0}^{\infty} t^{z-1}
\mathrm{e}^{-t} , \mathrm{d}t
$$
- Wikidata: Q190573
- NIST: http://dlmf.nist.gov/5.2.E1
["Gamma", 5]
// 24
z
This function is called gammaln
in MatLab and SciPy and LogGamma
in
Mathematica.
See also Statistics for the Error Functions