Symbolic Computing
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 |
Other operations can be performed on an expression: comparing it to a pattern, replacing part of it, and applying conditional rewrite rules.
Comparing Expressions
There are two useful ways to compare symbolic expressions:
- structural equality
- mathematical equality
Structural Equality: isSame()
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. The argument can be an Expression or a JavaScript primitive
(number, bigint, boolean, string).
This is a fast, exact check — no evaluation is performed.
- $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 structurally equal, one is a power of a sum, the other a sum of terms.
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 \( \frac110 \) and \( \frac220 \) 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.
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"]
)
Mathematical Equality: isEqual()
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 returns 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.
Smart Comparison: is()
The lhs.is(rhs) method provides a convenient middle ground between isSame()
and isEqual(). It first tries an exact structural check (like isSame()), and
if that fails and the expression is constant (no free variables), it
evaluates numerically and compares within engine.tolerance.
This is useful when checking whether an expression evaluates to a known value
without the overhead of a full isEqual() call:
For literal numbers (created with ce.number()), is() behaves identically
to isSame() — no tolerance is applied. Tolerance only kicks in for expressions
that require evaluation, such as \sin(\pi).
Other Comparisons
lhs === rhs | If true, same box expression instances |
lhs.isSame(rhs) | Structural equality (fast, exact, no evaluation). Accepts primitives. |
lhs.is(rhs) | Smart check: structural first, then numeric evaluation fallback for constant expressions (within engine.tolerance). For literal numbers, same as isSame(). |
lhs.isEqual(rhs) | Mathematical equality (full evaluation). May return undefined. |
lhs.match(rhs) !== null | Pattern match |
ce.box(["Equal", lhs, rhs]).evaluate() | Synonym for lhs.isEqual(rhs) |
ce.box(["Same", lhs, rhs]).evaluate() | Synonym for lhs.isSame(rhs) |
- Use
isSame()when you know the exact value you're comparing against (e.g., checking if an operand is0or1in a simplification rule). - Use
is()when the expression might need evaluation to reveal its value (e.g., checking user input like\cos(\pi/2)against0). - Use
isEqual()for full mathematical equality, including symbolic expressions with unknowns.
Replacing a Symbol in an Expression
To replace a symbol in an expression use the subs() function.
The argument of the subs() function is an object literal. Each key/value
pair is a symbol and the value to be substituted with. The value can be either
a number or a expression.
Other Symbolic Manipulation
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.
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.