Expressions
The CortexJS Compute Engine produces and manipulates symbolic expressions such as numbers, constants, variables and functions.
Expressions are represented using the MathJSON format.
In the CortexJS Compute Engine, MathJSON expressions are wrapped in a JavaScript object, a processed called boxing, and the resulting expressions are Boxed Expressions.
Unlike the plain data types used by JSON, Boxed Expressions allow an IDE, such as VSCode Studio, to provide suitable hints in the editor, and to check that the correct functions and properties are used, particularly when using TypeScript.
Boxed Expressions also improve performance by implementing caching and avoiding repetitive calculations.
Boxing
To create a Boxed Expression from a MathJSON expression, use the ce.box()
function.
The result is an instance of a subclass of BoxedExpression
, such as
BoxedNumber
BoxedFunction
etc…
let expr = ce.box(1.729e3);
console.log(expr.machineNumber);
// ➔ 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.head);
// ➔ "Add"
console.log(expr.isPositive);
// undefined
To create a Boxed Expression from a LaTeX string, call the ce.parse()
function.
const expr = ce.parse('3 + x + y');
console.log(expr.head);
// ➔ "Add"
console.log(expr.json);
// ➔ ["Add", 3, "x", "y"]
If using MathLive, you can directly obtain an expression representing the
content of a mathfield using the expression
property:
const mf = document.getElementById('input');
mf.value = '\\frac{10}{5}';
const expr = mf.expression;
console.log(expr.evaluate().latex);
// ➔ 2
Unboxing
To access the MathJSON expression of a boxed expression, use the expr.json
property. Use this property to “look inside the box”.
const expr = ce.box(['Add', 3, 'x']);
console.log(expr.json);
// ➔ ['Add', 3, 'x']
To customize the format of the MathJSON expression use the
ce.jsonSerializationOptions
property.
You can use this option to control which metadata, if any, should be included, whether to use shorthand notation, and to exclude some functions.
const expr = ce.parse('2 + \\frac{q}{p}');
console.log(expr.canonical.json);
// ➔ ["Add", 2, ["Divide", "q", "p"]]
ce.jsonSerializationOptions = {
exclude: ['Divide'], // Don't use `Divide` functions,
// use `Multiply`/`Power` instead
shorthands: [], // Don't use any shorthands
};
console.log(expr.canonical.json);
// ➔ ["fn": ["Add", ["num": "2"],
// ["fn": ["Multiply",
// ["sym": "q"],
// ["fn": ["Power", ["sym": "p"], ["num": "-1"]]]]
// ]
// ]]
Immutability
Boxed expressions are immutable. Once a Boxed Expression has been created it cannot be changed to represent a different mathematical object.
The functions that manipulate Boxed Expressions, such as expr.simplify()
,
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, expr.isPositive
may return undefined
if nothing is known about
a symbol. But if an assumption about the symbol is made later, or a value
assigned to it, then expr.isPositive
may take a different value.
const expr = ce.box('x');
console.log(expr.isPositive);
// ➔ undefined
ce.assume('x > 0');
console.log(expr.isPositive);
// ➔ true
What doesn’t change is the fact that expr
represents the symbol "x"
.
Pure Expressions
A pure expression is an expression whose value is fixed. Evaluating it produces no side effect (doesn’t change something outside its arguments to do something).
The \( \sin() \) function is pure: if you invoke it with the same arguments, it will have the same value.
On the other hand, \( \mathrm{random}() \) is not pure: by its nature it returns a different result on every call.
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
.
Literal Expressions
A literal expression is one that has a fixed value that was provided directly when the expression was defined. Numbers and strings are literal. Symbols and functions are not. They may have a value, but their value is calculated indirectly.
To check if an expression is a literal, use expr.isLiteral
.
Canonical Expressions
The canonical form of an expression is a “standard” way of writing an expression.
To check if an expression is in canonical form, use expr.isCanonical
.
To obtain the canonical representation of an expression, use
expr.canonical
.
Checking the Kind of Expression
To identify if an expression is a number, symbol, function, string or dictionary, use the following boolean expressions:
Kind | Boolean Expression |
---|---|
Number | expr.isLiteral && expr.isNumber |
Symbol | expr.symbol !== null expr.head === 'Symbol' |
Function | expr.tail !== null |
String | expr.string !== null expr.head === 'String' |
Dictionary | expr.keys !== null expr.head === 'Dictionary' |
Note that symbols or functions can return true
for isNumber
, if their value
is a number. Use isLiteral
to distinguish literal numbers from other
expressions that may have a numeric value.
Incomplete Expressions
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
Missing
symbol is inserted where the missing operand should have been.
console.log(ce.parse('\\frac{1}').json);
// ➔ ["Divide", 1, "Missing"]
console.log(ce.parse('\\sqrt{}').json);
// ➔ ["Sqrt", "Missing"]
console.log(ce.parse('\\sqrt').json);
// ➔ ["Sqrt", "Missing"]
console.log(ce.parse('\\sqrt{').json);
// ➔ ["Error", ["Sqrt", "Missing"], "'syntax-error'", ["LatexForm", "'{'"]]
console.log(ce.parse('2 \\times').json);
// ➔ ["Multiply", 2, "Missing"]
The Missing
symbol can then be replaced with another expression. To remove it
altogether, repace it with the Nothing
symbol, then get the canonical form of
the resulting expression.
Errors
If an expression can only be partially parsed, a function with a Error
head is
returned.
The Error
function has the following arguments:
- A partial result, i.e. the part that was successfully parsed. When an error expression is evaluated, its value is its first argument
- A error code, as a string
- An expression representing where the error occurred, for example a LaTeX string representing where the LaTeX parsing failed.
Return Value Conventions
The Compute Engine API uses the following conventions for the return values of a function and the values of an object property:
null
: this property/function does not apply, and will never apply. You can think of this as an answer of “never”. Example:expr.isPositive
on a Boxed String.undefined
: at the moment, the result is not available, but it may be available later. You can think of this as an answer of “maybe”. For example,expr.sgn
for a symbol with no assigned value and no assumptions.this
: this function was not applicable, or there was nothing to do. For example, invokingexpr.simplify()
on a boxed string.