# MathLive — Compute Engine # Introduction - Compute Engine Source: https://mathlive.io/compute-engine/ # Compute Engine A JavaScript library for symbolic computing and numeric evaluation of mathematical expressions.
```live console.log(evaluate("e^{i\\pi}")); ``` ```live console.log(expand("(a+b)^2")); ``` ```live console.log(simplify( "(sin(alpha)**2 + cos(alpha)**2) * (x**2 + 2*x + 1) / (x + 1)" )); ``` 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: ```json ["Add", ["Power", "x", 2], ["Multiply", 2, "x"], 1] ``` The Compute Engine can: - **parse** and **serialize** expressions from and to LaTeX. - **simplify** symbolic expressions - evaluate expressions **symbolically** or **numerically** - solve equations, calculate derivatives and integrals, and perform other **calculus** operations - **compile** expressions to JavaScript ## Free Functions For common operations, use the free functions — no setup required: ```live console.log(simplify("x+x+1")); console.log(evaluate("2^{11} - 1")); console.log(N("\\frac{1}{3}")); ``` | Function | Purpose | | :------------------------------------- | :------------------------------------------------------------- | | `evaluate(expr \| latex)` | Evaluate an expression or LaTeX input symbolically. | | `N(expr \| latex)` | Numerically evaluate an expression or LaTeX input. | | `simplify(expr \| latex)` | Simplify an expression or LaTeX input. | | `assign(id, value)` / `assign(record)` | Assign one symbol value or many at once. | | `expand(expr \| latex)` | Expand distributively at the top level (`Expression \| null`). | | `expandAll(expr \| latex)` | Expand distributively recursively (`Expression \| null`). | | `factor(expr \| latex)` | Factor an expression. | | `solve(expr \| latex, vars?)` | Solve equations/systems (returns solve result variants). | | `compile(expr \| latex, options?)` | Compile to a target language with `CompilationResult`. | | `parse(latex)` | Parse a LaTeX string into an `Expression`. | You can use either regular LaTeX strings or a looser syntax similar to ASCIIMath or Typst: ```live console.log(N("(1+sqrt(5))/2")); ``` :::info[Note] These functions use a shared `ComputeEngine` instance created on first call. Use `getDefaultEngine()` to configure it. ::: Try the **interactive demo** now ## Getting Started **To load the Compute Engine module from the jsdelivr CDN**, use a ` ``` Alternatively, you can use the **unpkg** CDN: ```js import { ComputeEngine } from "https://unpkg.com/@cortex-js/compute-engine?module"; ``` **To find a Compute Engine that the page has already loaded**, without importing it yourself, read the global slot `Symbol.for("io.cortexjs.compute-engine")`. This is for a component, such as a web component in its own bundle, that must work with a copy of the Compute Engine loaded by the page. When the main entry point (`@cortex-js/compute-engine`) loads, it stores an object in this slot: ```js const global = globalThis[Symbol.for("io.cortexjs.compute-engine")]; if (global?.ComputeEngine) { const ce = new global.ComputeEngine(); console.log(global.version, ce.parse("1+1").evaluate().toString()); } ``` The object holds `ComputeEngine` (the class), `version`, `LatexSyntax`, `LATEX_DICTIONARY`, `compile`, the type guards (`isExpression`, `isNumber`, `isSymbol`, `isFunction`, `isString`, `isCharacter`, `isTensor`, `isDictionary`, `isObject`, `isCollection`, `isIndexedCollection`), and the helpers `numericValue` and `sym`. Its TypeScript type is `ComputeEngineGlobal`. The type guards test the structure of an expression, so they work on an expression made by another copy of the bundle. The contract is additive: a later version can add a member, but a member keeps its meaning and is not removed. Test each member before you use it, since an older version can lack a newer one. When several copies load, the last one to load owns the slot. The main entry point and the Epsil entry point (`/epsil`) store it; `/core` and the other entry points do not. ## MathJSON Standard Library The **MathJSON Standard Library** is a collection of functions and symbols that are available by default.
| Topic| | | :-------- | :---- | | [Arithmetic](/compute-engine/reference/arithmetic/) | `Add` `Multiply` `Power` `Exp` `Log` `ExponentialE` `ImaginaryUnit`... | | [Calculus](/compute-engine/reference/calculus/) | `D` `Derivative` `Integrate`... | | [Collections](/compute-engine/reference/collections/) | `List` `Reverse` `Filter`... | | [Complex](/compute-engine/reference/complex/) | `Real` `Conjugate`, `ComplexRoots`... | | [Control Structures](/compute-engine/reference/control-structures/) | `If` `Block` `Loop` ... | | [Core](/compute-engine/reference/core/) | `Declare` `Assign` `Error` `LatexString`... | | [Functions](/compute-engine/reference/functions/) | `Function` `Apply` `Return` ... | | [Logic](/compute-engine/reference/logic/) | `And` `Or` `Not` `True` `False` ... | | [Sets](/compute-engine/reference/sets/) | `Union` `Intersection` `EmptySet` `RealNumbers` `Integers` ... | | [Special Functions](/compute-engine/reference/special-functions/) | `Gamma` `Factorial`... | | [Statistics](/compute-engine/reference/statistics/) | `StandardDeviation` `Mean` `Erf`... | | [Strings and Text](/compute-engine/reference/strings/) | `Text` `Annotated`... | | [Trigonometry](/compute-engine/reference/trigonometry/) | `Pi` `Cos` `Sin` `Tan`... |
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** :::info[Note] In this guide, the `ce.` prefix in `ce.expr()` or `ce.parse()` indicates that the function is a method of the `ComputeEngine` class. Use `getDefaultEngine()` to access the shared `ComputeEngine` instance used by the free functions, or create your own instance with `new ComputeEngine()`. The `expr.` prefix in `expr.evaluate()` or `expr.simplify()` indicates that the function is a method of the `Expression` class. **To create a new expression** use `expr = ce.parse()` or `expr = ce.expr()` ::: --- # Compute Engine Demo Source: https://mathlive.io/compute-engine/demo/ 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'; import ErrorBoundary from '@site/src/components/ErrorBoundary'; export function ToggleButton ({toggle, className, style}) { return ; } export function InfoSection({ label, children, secondary }) { const [visible, setVisible] = useState(false); return (
{label &&
{label}
}
{children} {secondary && {setVisible(!visible)}}/>}
{visible && secondary}
); } 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 expression from a MathJSON expression, use the \`ce.expr()\` 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 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: '\\sum_{n=1}^{b} n^2', preamble: `Simplify a sum with symbolic bounds to a closed-form formula. The sum of squares simplifies to b(b+1)(2b+1)/6.`, template: 'simplify' }, { latex: '\\prod_{n=1}^{b} n', preamble: `Simplify a product with symbolic bounds. The product of consecutive integers simplifies to factorial.`, template: 'simplify' }, { 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: '(1,2,3)+(3,5,6)', preamble: 'Vector addition with element-wise operations', template: 'eval-string' }, { json: '["MatrixMultiply", ["List", ["List", 1, 2], ["List", 3, 4]], ["List", ["List", 5, 6], ["List", 7, 8]]]', label: 'Matrix Multiply', preamble: 'Matrix multiplication using MatrixMultiply function', template: 'as-json' }, { 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' }, { latex: 'F_{10}', label: "Fibonacci", preamble: `Define sequences using LaTeX assignment notation. Base cases and recurrence are automatically detected. Generate terms and compute sums over sequences.`, template: "sequence" } ]; useEffect(() => { onSelect(examples[index], index)}, []); return (
{ examples.map((x, i) => { return (
onSelect(x, i)} > {x.label ? x.label: `$$${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; expr.evaluateAsync().then(result => console.info(result)); `, "simplify": `$0 const expr = $1; console.info(expr.simplify()); `, "sequence": `$0 // Define Fibonacci sequence via LaTeX assignments ce.parse('F_0 := 0').evaluate(); ce.parse('F_1 := 1').evaluate(); ce.parse('F_n := F_{n-1} + F_{n-2}').evaluate(); // Evaluate specific terms console.info('F_10 =', ce.parse('F_{10}').evaluate().value); console.info('F_20 =', ce.parse('F_{20}').evaluate().value); // Generate a list of terms const terms = ce.getSequenceTerms('F', 0, 10); console.info('First 11 terms:', terms.map(t => t.value).join(', ')); // Sum over sequence terms const sum = ce.parse('\\\\sum_{k=0}^{10} F_k').evaluate(); console.info('Sum of F_0 to F_10 =', sum.value); ` }; 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.expr(${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)})`); } } if (!('ComputeEngine' in window)) { setValue(`// 😕 The Compute Engine could not be loaded. // Check your network connection or try again later.`); } else { setValue(code); } setIndex(exampleIndex); }; return (
); } # Compute Engine Demo --- # Expressions Source: https://mathlive.io/compute-engine/guides/expressions/ 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](/math-json/). They are wrapped in a JavaScript object, an instance of the `Expression` class. Unlike the plain data types used by JSON, `Expression` objects allow an IDE, such as **Visual Studio Code (VS Code)**, to provide hints in the editor regarding the methods and properties available. Expressions can be created from a LaTeX string or from a raw MathJSON expression. When using the Epsil language frontend, `%` is the infix `Mod` operator and a postfix `!` is `Factorial`: ```text a % b // Mod(a, b) n! // Factorial(n) ``` The postfix `!` must immediately follow its operand. Prefix `!x` remains logical `Not`, and `x != y` remains `NotEqual`. Epsil also supports chained collection indexing: `m[2][1]` is equivalent to `m[2, 1]`. ## Creating Expressions **To create a Expression from a LaTeX string** use the `parse()` or `ce.parse()` function. You would use `ce.parse()` if you want to use a custom instance of the `ComputeEngine` class. ```js const expr = parse("3 + x + y"); console.log(expr.operator); // ➔ "Add" console.log(expr.json); // ➔ ["Add", "x", "y", 3] ``` **To create an `Expression` object from a MathJSON expression** use the `ce.expr()` method. The input of `ce.expr()` can be: - a [MathJSON expression](/math-json/) - an `Expression` (in which case it is returned as-is) - an `ExpressionInput`, that is a MathJSON expression with some of its subexpressions already represented as `Expression` values. The result is an `Expression` object. ```js let expr = ce.expr(1.729e3); console.log(expr.re); // ➔ 1729 console.log(expr.isPositive); // ➔ true expr = ce.expr({ num: "+Infinity" }); console.log(expr.latex); // ➔ "\infty" expr = ce.expr(["Add", 3, "x"]); console.log(expr.operator); // ➔ "Add" ``` ## Canonical Expressions The **canonical form** of an expression is a conventional way of writing an expression. For example, the canonical form of a fraction 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. ```live 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. ```js const expr = ce.parse("\\frac{17}{1}"); console.log(expr); // ➔ 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. ```js example const input = ce.parse("\\frac{30}{50}", {form: 'raw'}); console.info(ce.expr( ["GCD", ["NumeratorDenominator", input]] ).evaluate().valueOf() === 1); // ➔ false ``` The canonical form of an addition or multiplication will have its arguments ordered in a canonical way. ```js const expr = ce.parse("2+x+\\pi+\\sqrt2+1"); console.log(expr.json); // ➔ ["Add", "Pi", ["Sqrt", 2], "x", 1, 2] ``` Read more about the **Canonical Form** By default, `ce.expr()` and `ce.parse()` produce a canonical expression. **To get a non-canonical expression instead**, use `ce.expr(expr, {form: 'raw'})` or `ce.parse(latex, {form: 'raw'})`. When using `ce.parse()`, the non-canonical form sticks closer to the original LaTeX input. When using `ce.expr()`, the non-canonical form matches the input MathJSON. ```js const latex = "\\frac{30}{-50}"; ce.parse(latex); // canonical form ➔ ["Rational", -3, 5] ce.parse(latex, { form: 'raw' }); // non-canonical form ➔ ["Divide", 30, -50] ce.expr(["Divide", 30, -50], { form: 'raw' }); // 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...). ## String Representation The `expr.toString()` method returns an [AsciiMath](https://asciimath.org/) string representation of the expression. ```live 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. ```live 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()`. ```live 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. ```live let expr = ce.parse("3x^2+\\sqrt{2}"); console.log(expr.latex); ``` ## Unboxing **To access the MathJSON expression of a expression as plain JSON** use the `expr.json` property. This property is an "unboxed" version of the expression. ```js const expr = ce.expr(["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](/compute-engine/api#jsonserializationoptions) for more info about the formatting options available. ```live 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 })); ``` ## Mutability Unless otherwise specified, expressions are immutable. The functions that manipulate Expressions, such as `expr.simplify()`, `expr.evaluate()`, `expr.N()` return a new 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.expr('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.expr('n').isPositive` may take a different value. ```js const expr = ce.expr("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"`. ## Pure Expressions 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. ### Effects Being impure is not a single thing: an expression is impure because it carries one or more **effects**. There are nine of them — `console`, `entropy`, `environment`, `fs_read`, `fs_write`, `network`, `random`, `scope` and `time`. `Random` carries `random` (it draws from the ambient random stream); `Assign`, `Declare` and `Assume` carry `scope` (they mutate a binding that outlives the call). An expression with no effects is pure. Effects can also be declared as part of a function's type — see [Effect Specifiers](/compute-engine/guides/types/#effect-specifiers). **To check if an expression is pure** use `expr.isPure`. **To find out *which* effects it carries** use `expr.effects`: `undefined` when there are none, `'any'` when the effects are not known, otherwise the labels in alphabetical order. It reports what evaluating the expression **does**, not what the value it produces **can do** if you later invoke it — a symbol bound to a drawing function has no effects, because evaluating it just yields the function. The latent effects of a callable live on its type, as `expr.type.effects`. ```js ce.parse("1 + x^2").effects; // ➔ undefined ce.box(["Random"]).effects; // ➔ ["random"] ce.box(["Assign", "q", 1]).effects; // ➔ ["scope"] ce.assign("rf", ce.box(["Function", ["Random"], "x"])); ce.box("rf").effects; // ➔ undefined (producing) ce.box("rf").type.effects; // ➔ ["random"] (invoking) ce.box(["Map", "rf", ["List", 1, 2]]).effects; // ➔ ["random"] ``` ### Purity Is Computed, Not Looked Up The effects of a function expression are not simply "the operator's effects plus every operand's effects". They are computed by looking at what the expression will actually *do*, following symbols through their current bindings. Four consequences are worth knowing: - **Held content contributes nothing.** `Hold` never evaluates its operand, so `Hold(Random())` is pure. The draw resurfaces where the content is forced: `ReleaseHold(Hold(Random()))` is impure. - **A frame discharges what it delimits.** `WithRandomSeed(42, Random())` is pure. The frame absorbs the draws inside it, and the whole block genuinely replays identically on re-evaluation — which is exactly what purity claims. (Two `Random()` calls *within* one frame still return different values; that is the frame's stream advancing, not an effect escaping.) A `scope` write is not discharged: `WithRandomSeed(42, Block(Assign(x, 1), Random()))` is impure. - **A function literal is pure; its arrow carries the effect.** `x |-> Random()` is pure — building the function draws nothing. The effect lives on its type, `(unknown) random -> number`, and fires when the function is applied. - **Callbacks are resolved through their bindings.** `Map(f, xs)` is pure exactly when `f` is. If `f` is currently bound to a drawing function, the whole expression is impure; reassign `f` to a pure function and it becomes pure. ```js ce.box(["Hold", ["Random"]]).isPure; // ➔ true ce.box(["WithRandomSeed", 42, ["Random"]]).isPure; // ➔ true ce.box(["Function", ["Random"], "x"]).isPure; // ➔ true ce.assign("f", ce.box(["Function", ["Random"], "x"])); ce.box(["Map", "f", ["List", 1, 2, 3]]).isPure; // ➔ false ce.assign("f", ce.box(["Function", ["Multiply", "x", 2], "x"])); ce.box(["Map", "f", ["List", 1, 2, 3]]).isPure; // ➔ true ``` ## Checking the Kind of Expression To identify if an expression is a number literal, a symbol, a function expression or a string, use **type guards**. Type guards narrow the type of the expression and provide type-safe access to properties specific to that expression type:
| Kind | Type Guard | | :------------- | :---------------------------------- | | **Number Literal** | `isNumber(expr)` | | **Function Expression** | `isFunction(expr)` or `isFunction(expr, 'Add')` | | **Symbol** | `isSymbol(expr)` or `isSymbol(expr, 'Pi')` | | **String** | `isString(expr)` |
After using a type guard, you can safely access properties specific to that type: ```js import { isNumber, isSymbol, isFunction } from '@cortex-js/compute-engine'; const expr = ce.parse("3.14"); // Check if it's a number and access its numeric value if (isNumber(expr)) { console.log(expr.numericValue); // Type-safe access console.log(expr.isNumberLiteral); // Always true for number literals } // Check if it's a symbol and access its name const sym = ce.parse("x"); if (isSymbol(sym)) { console.log(sym.symbol); // Type-safe access to symbol name } // Check for a specific symbol in one step if (isSymbol(sym, 'Pi')) { // sym is the Pi symbol } // Check if it's a function and access its operands const fn = ce.parse("2 + 3"); if (isFunction(fn)) { console.log(fn.operator); // "Add" console.log(fn.ops.length); // 2 console.log(fn.op1, fn.op2); // Access first and second operands } // Check for a specific operator in one step if (isFunction(fn, 'Add')) { // fn is a function expression with operator "Add" console.log(fn.op1, fn.op2); } ``` For convenience, use the `sym()` and `numericValue()` helpers to skip explicit type checking: ```js import { sym, numericValue } from '@cortex-js/compute-engine'; const expr = ce.parse("Pi"); if (sym(expr) === 'Pi') { // This is the Pi symbol } // Extract the numeric value of an expression, or undefined if not a number literal const val = numericValue(ce.parse("3.14")); // number | NumericValue | undefined ``` ## Accessing the Value of an Expression **To access the expression as a MathJSON expression** use `expr.json`. To access the evaluated value as MathJSON, evaluate first: `expr.evaluate().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 `Expression` or `undefined` if the expression is not a number. ## Errors Sometimes, things go wrong. If a 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", , ]` expression. The `` 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 `` 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. ### Parsing Errors 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... ### Semantic Errors 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) | | `expected-set-builder-separator` | a set builder `\{x \| …\}` uses a bar as the separator; write `\mid` or `:` instead | | `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](/math-json/#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 |
```ts 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'"]]] ``` --- # Evaluation of Expressions Source: https://mathlive.io/compute-engine/guides/evaluate/ # Evaluation 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. ## Evaluation Methods **To evaluate an expression**, use the `evaluate()` function. ```live // String expression console.log(evaluate("3^2")); // Expression object const expr = parse("2 + 2"); console.log(evaluate(expr)); ``` :::info[Note] 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. ::: ```live parse("x").value = 314; console.info(parse('42').value) console.info(parse('x').value) console.info(parse('2 + 2').value) ``` The `N()` function provides a numeric evaluation of its argument. ```live console.log(evaluate("2\\pi")); console.log(N("2\\pi")); ``` :::info[Note] The `N()` and `evaluate()` functions are shorthands for the `expr.N()` and `expr.evaluate()` methods. They use a common shared compute engine instance. The `expr.N()` method is a shorthand for `expr.evaluate({numericApproximation: true})`. ::: ### Compilation An expression can be evaluated by compiling it to JavaScript using the `compile()` function. The result includes a `run` function that can be called to evaluate the expression. ```live // import { compile } from '@cortex-js/compute-engine'; const result = compile('2\\pi'); console.log(result.success ? 'compiled' : 'fallback'); console.log(result.run?.()); ``` Read more about **compiling expressions** ## Asynchronous Evaluation 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. ```js try { const fact = parse('(70!)!'); const factResult = await fact.evaluateAsync(); console.log(factResult); } 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()`. Several `evaluateAsync()` calls can run at the same time on one engine. Each evaluation keeps the scopes it opens (the index of a `Sum`, the locals of a `Block`) to itself, so two sums over the same index name evaluated together do not see each other's index. One engine per concurrent evaluation is not necessary. ```js const a = ce.parse('\\sum_{k=1}^{100000} k').evaluateAsync(); const b = ce.parse('\\sum_{k=1}^{1000} k^2').evaluateAsync(); await Promise.all([a, b]); // ➔ [5000050000, 333833500] ``` 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: ```js const abort = new AbortController(); const signal = abort.signal; setTimeout(() => abort.abort(), 500); try { const fact = parse('(70!)!'); const factResult = await fact.evaluateAsync({ signal }); console.log(factResult); } catch (e) { console.error(e); } ``` ```live :::html
:::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**, wrap it in a span with `ce.withTimeLimit(limit, fn)`. The `limit` is either a number of milliseconds or an object `{ ms, label }`; everything evaluated inside the callback shares one deadline. ```js try { const fact = ce.parse('(70!)!'); const result = ce.withTimeLimit({ ms: 1000, label: 'my-app:eval' }, () => fact.evaluate() ); console.log(result); } catch (e) { console.error(e); } ``` Spans nest by taking the tighter deadline, and a `CancellationError` reports which span's budget expired through its `attribution` and `spans` fields. See the [Execution Constraints](/compute-engine/guides/execution-constraints/) guide for the full model, including the iteration and recursion budgets. (The former `ce.timeLimit` property has been removed — spans are the only way to arm a deadline.) When an operation is canceled either because of a timeout or an abort, a `CancellationError` is thrown. The class can be imported from the package to distinguish cancellations from other errors: ```js import { CancellationError } from "@cortex-js/compute-engine"; ``` The time limit is respected by long-running operations including iteration over large or infinite collections, big operators (`Sum`, `Product`, `Reduce`), number-theoretic functions (`Factorial`, `Totient`, `Sigma0`…), and numeric limit extraction (`Limit`, `NLimit`). Numerical integration is an exception: rather than throwing, Monte Carlo integration (`NIntegrate`) returns the estimate computed from the samples taken so far, with a correspondingly larger error bound. ## Errors An [`["Error"]`](/compute-engine/reference/core/#error) is a **value**, and it propagates along the ordinary value path. If an operand that a function evaluates strictly turns out to be an error, the whole expression evaluates to that error — not to a frozen expression wrapped around it. The propagated error carries an `["ErrorTrace"]` breadcrumb recording the operators it passed through, so the failure site is still recoverable. ```ts console.log(ce.parse('\\ln(\\text{a}) + 2').evaluate().json); // ➔ ["Error", // ["ErrorCode", "'incompatible-type'", "'number'", "'string'"], // ["ErrorTrace", ["ErrorFrame", "'Ln'", 1], ["ErrorFrame", "'Add'", 1]]] ``` A **collection** is the exception: an error among its elements stays in place, because a collection containing an error is still a well-formed collection. An operand that is never evaluated cannot propagate anything. In a selection — `["If"]`, `["Which"]` — or behind a short-circuit operator — `["And"]`, `["Or"]` — the branch that is not taken is **dead code**, and an error inside it never reaches the value: ```ts console.log(ce.box(['If', 'True', 5, ['Divide', 'x']]).evaluate().json); // ➔ 5 ``` The diagnostic is not lost, only unevaluated: the boxed expression still holds `["If", "True", 5, ["Divide", "x", ["Error", "'missing'"]]]`, so a tool that walks the expression still reports it. Errors do not spread past the tools that inspect them. `Type` reports `"error"`, [`IsError`](/compute-engine/reference/core/#iserror) answers `True`/`False`, and [`Match`](/compute-engine/reference/control-structures/#match) decides on an error subject — an `["Error", ...]` case destructures it, which is how a failure is rescued. `NaN` is **not** an error. It is an ordinary IEEE numeric value — it has its own type, `nan`, under `number` — so it does not propagate this way: a function applied to `NaN` runs and receives it, and is free to inspect it. `NaN` is also **inexact**, so the exactness contract sends it down the numeric branch: a numeric function of `NaN` numericizes under plain `evaluate()`, without waiting for `.N()`, and the result is `NaN`. ```ts console.log(ce.box(['Sin', NaN]).evaluate().json); // ➔ "NaN" console.log(ce.parse('\\ln(2)').evaluate().json); // ➔ ["Ln", 2] — an exact argument stays symbolic ``` A float operand makes the result a float, also when the value is a whole number: the exact `0` divided by a float is the float `0.0`, and a float exponent is kept. ```ts console.log(ce.parse('0/2.5').evaluate().json); // ➔ {"num": "0.0"} console.log(ce.parse('3x^{2.0}').evaluate().json); // ➔ ["Multiply", 3, ["Power", "x", {"num": "2.0"}]] — not 3x^2 ``` ## Lexical Scopes and Evaluation Contexts 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. ### Binding **[Name Binding](https://en.wikipedia.org/wiki/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. ```mermaid flowchart TD sym["symbol to bind"] --> cur["current scope"] cur -- "not found" --> parent["enclosing scopes,
innermost first"] parent -- "not found" --> global["global scope
(user definitions)"] global -- "not found" --> system["system scope
(the standard library)"] system -- "not found" --> auto["declared with type unknown,
or a more specific type
if the context allows it"] cur -- found --> def["definition record"] parent -- found --> def global -- found --> def system -- found --> def ``` 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: 1. **Value Definition Record**: This record contains information about the symbol, such as its type and whether it is a constant. 2. **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 ### Bound Variables and Assigned Values Some operators **bind** a variable of their own: `D`, `Integrate`, `Limit`, `Sum`, `Product`, `Solve`, and `Function` (its parameters), among others. The variable such an operator binds is a **pure symbol** — only its declared type and any in-scope assumptions apply. A **value** assigned to a same-named symbol does **not** apply to it, and the variable stays symbolic in the result. ```js ce.assign('x', 5); ce.parse('\\frac{d}{dx} x^2').evaluate().print(); // ➔ 2x (not 10 — the bound "x" ignores its assigned value) ce.parse('\\int x^2 \\,dx').evaluate().print(); // ➔ x^3/3 (not 125/3) ``` Every **other** symbol in the expression is **free**: its value always applies under evaluation. So with `a := 3` as well, `\frac{d}{dx}(a\,x^2)` is `6x` — the free `a` resolves, the bound `x` does not. To use an assumption instead of a value (which *does* inform simplification and solving), use `ce.assume()` rather than `ce.assign()`. ### Function Calls Are Bound Lexically A function call resolves its head (`f` in `f(3)`) through the binding the name had where the call was created, in the same way a symbol is bound to its definition during canonicalization. Declaring another function of the same name in a scope that is current when the call is evaluated does not redirect the call. Assigning a new function to the **original** binding does: a call reads the function that its binding holds when it runs. ```js ce.assign('f', ce.parse('x \\mapsto 2x')); const call = ce.parse('f(3)'); ce.pushScope(); ce.declare('f', 'function'); ce.assign('f', ce.parse('x \\mapsto 100x')); call.evaluate().print(); // ➔ 6 (the call keeps the binding it was created with) ce.popScope(); ce.assign('f', ce.parse('x \\mapsto 3x')); call.evaluate().print(); // ➔ 9 (the original binding now holds another function) ``` A call written before its function is defined is a **forward reference**. It waits for a definition of the name in its own scope chain: evaluated while the name has no value, it stays symbolic; evaluated after the definition, it applies the function. Within one evaluation, the symbolic result of such a call is a value: it is not resolved again when the name is assigned later in the same evaluation. So `Sum(Block(If(k = 2, h := z ↦ 2z), h(k)), k, 1, 3)` is `10 + h(1)`: the term `h(1)` was computed while `h` had no value, and the assignment at `k = 2` does not reach it. A later evaluation resolves the stored result again: ```js const call = ce.parse('g(3)'); const result = call.evaluate(); // ➔ g(3) ("g" has no definition yet) ce.assign('g', ce.parse('x \\mapsto x^2')); call.evaluate().print(); // ➔ 9 result.evaluate().print(); // ➔ 9 (a later evaluation resolves the stored result) ``` In a `Block`, a `DefineFunction` statement (the Epsil `function g(k) { … }`) defines its name for the whole block, including the statements written before it. An `Assign` of a function literal takes effect when it runs. A `Block` evaluated directly by the host is a program: each of its statements is an evaluation of its own, so a result stored by one statement is resolved again when a later statement reads it. ### Default Scopes 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. ### Creating New Scopes **To add a new scope** use `ce.pushScope()`. ```ts ce.assign('x', 100); // "x" is defined in the current scope ce.pushScope(); ce.assign('x', 500); // "x" is defined in the new scope console.log(ce.expr('x')); // 500 ce.popScope(); console.log(ce.expr('x')); // 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. ## Evaluation Loop :::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.expr()` or `ce.parse()`, or if accessing the `.canonical` property of a non-canonical expression. When a function is evaluated, the following steps are followed: ```mermaid flowchart TD start["evaluate f(a, b, …)"] --> canon{"canonical?"} canon -- no --> err["error: canonicalize first"] canon -- yes --> lazy{"f is lazy?"} lazy -- yes --> assoc lazy -- no --> args["evaluate each argument, left to right
a held argument, or a Hold, stays symbolic
a Sequence result is spliced in; an empty one is dropped"] args --> assoc{"f is associative?"} assoc -- yes --> flat["flatten: f(f(a, b), c) → f(a, b, c)"] assoc -- no --> apply flat --> apply["apply f to the arguments"] apply --> result["return the result in canonical form"] ``` 1. If the expression is not canonical, it cannot be evaluated and an error is thrown. The expression must be canonicalized first. 2. 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. 1. 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, the `ReleaseHold` function will force an evaluation. 2. 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. 3. If the function is associative, flatten its arguments as necessary. \\[ f(f(a, b), c) \to f(a, b, c) \\] 4. Apply the function to the arguments 5. Return the result in canonical form. --- # Symbolic Computing Source: https://mathlive.io/compute-engine/guides/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. Does not substitute assigned symbol values (that is `evaluate()`'s job). | | `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 | | | | | Substitute assigned symbol values | | | | | 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. ```live show-line-numbers const expr = ce.parse('3x^2 + 2x^2 + x + 5'); console.log(expr, '=', expr.simplify()); ``` ## Comparing Expressions There are three tiers of comparison, from the strictest and cheapest to the most powerful and most expensive:
| Tier | Method | Operator | Notation | Answers | | :--- | :--- | :--- | :--- | :--- | | Syntactic | `isSame()` | `Same` | `===`, `≣` (Epsil) | Always `True` or `False` | | Arithmetic | `isEqual()` | `Equal` | `=`, `==` (Epsil) | `True`, `False`, or undetermined | | Identity | `isIdenticallyEqual()` | `IdenticallyEqual` | `\equiv`, `≡` | `True`, `False`, or undetermined |
Each tier answers a different question: "are these the same expression?", "do these have the same value?", and "are these the same function of their free variables?". The operator, its LaTeX/Epsil notation and its JavaScript method share the same semantics at each tier. ### Syntactic Equality: `isSame()` Syntactic equality (or structural 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, and the answer is always `true` or `false`, never undetermined. `isSame()` is **strictly syntactic**: it compares the expressions as written (in canonical form) and never substitutes the value of a symbol. If `x` has been assigned the value `5`, `ce.symbol('x').isSame(5)` is `false` — one expression is a symbol, the other a number. Use `isEqual()` to compare values. - \\( x + x \\) and \\( 2x \\) are not structurally equal, one is a sum of two terms, the other a product - \\( (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. ```live show-line-numbers const a = ce.parse('x + x'); const b = ce.parse('2x'); 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`. ```live show-line-numbers mark-javascript-line="5-6" 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}', {form: 'raw'}); const bPrime = ce.parse('\\frac{2}{20}', {form: 'raw'}); 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: ```json example ce.expr(["CanonicalForm", ["Add", 1, "x"], "Order"]).isSame( ["CanonicalForm", ["Add", "x", 1], "Order"] ) ``` ### Arithmetic Equality: `isEqual()` The `lhs.isEqual(rhs)` function answers "do these two expressions have the same value?". It is the JavaScript counterpart of the `=` operator (`==` in Epsil). The comparison is deliberately **cheap and predictable**: 1. Both operands are evaluated (so assigned symbol values are substituted). 2. If the results are structurally identical, they are equal. 3. Otherwise, if both results are exact numbers (integers, rationals, or rational multiples of a square root), they are compared exactly. 4. Otherwise, if the difference between them has no unknowns, it is evaluated numerically: they are equal if the absolute value of the difference is less than `ce.tolerance`. 5. Otherwise the answer is `undefined`. ```mermaid flowchart TD s["lhs.isEqual(rhs)"] --> ev["evaluate both operands"] ev --> same{"structurally identical?"} same -- yes --> t["true"] same -- no --> exact{"both exact numbers?
(integer, rational, or a rational
multiple of a square root)"} exact -- yes --> cmp["compared exactly:
true or false"] exact -- no --> unk{"the difference has
no unknowns?"} unk -- yes --> tol["the difference evaluated numerically:
true if its magnitude is below ce.tolerance,
false otherwise"] unk -- no --> u["undefined"] ``` The tolerance is used only when a floating point number is involved. Two exact numbers are equal only if they have the same value, however close they are: ```live console.log(ce.parse('\\frac{1}{3} = \\frac{3333333333333}{10^{13}}').evaluate()); // ➔ False console.log(ce.parse('\\frac{1}{10} = 0.1').evaluate()); // ➔ True ``` The order comparisons (`isLess()`, `isGreater()`, …, and the `<`, `>`, `\le`, `\ge` operators) follow the same rule, so for two exact numbers exactly one of $a < b$, $a = b$ and $a > b$ is true. No expansion, no simplification and no sampling is attempted. In particular, `isEqual()` does **not** attempt to prove a symbolic identity: the expressions \\( (x+1)^2 \\) and \\( x^2 + 2x + 1 \\) have the same value for every \\( x \\), but `isEqual()` returns `undefined` for them. Use [`isIdenticallyEqual()`](#identity-isidenticallyequal) for that question. 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 have the same value. The corresponding `Equal` expression is **inert** when the answer is `undefined`: it stays unevaluated, since \\( x = y \\) is a *condition*, not a claim that is false until proven. This is what makes an equation usable as an argument to `Solve`. ```live show-line-numbers const a = ce.parse('1 + 2'); const b = ce.parse('3'); console.log('isEqual?', a.isEqual(b)); // Undetermined: the `Equal` expression stays unevaluated console.log(ce.parse('x = y').evaluate()); ``` Following IEEE 754, `NaN` is not equal to anything, including itself: `NaN = NaN` is `False`, and so is the comparison of two collections that contain it, `["Equal", ["List", "NaN"], ["List", "NaN"]]`. Only the syntactic tier reports those as identical (`["Same", "NaN", "NaN"]` is `True`). A pole has no value either: `1/0 = 1/0` is `False`. A value with no order decides an ordered comparison as `False`: `0 < \frac{1}{0}`, `0 < i` and `0 < \ln(-1)` are `False`, so a piecewise on such a comparison takes its next branch (`\{1/0 > 0: 1, 0\}` is `0`), as it does for `NaN`. Equality of complex numbers keeps its meaning (`i = i` is `True`), `\ne` is unchanged (`1/0 \ne 0` is `True`), and a free symbol leaves the comparison unevaluated. ### Identity: `isIdenticallyEqual()` Comparing two arbitrary symbolic expressions is a hard problem. In fact, [Richardson's Theorem](https://en.wikipedia.org/wiki/Richardson%27s_theorem) proves that it is impossible in general to determine if two symbolic expressions are identical. However, there are many cases where it is possible to decide it in practice. The `lhs.isIdenticallyEqual(rhs)` function asks whether `lhs` and `rhs` are the same function of their free variables, that is whether they have the same value for **every** value of those variables. It is the JavaScript counterpart of the `IdenticallyEqual` operator, written \\( \\equiv \\) in LaTeX. ```live show-line-numbers console.log(ce.parse('(x+1)^2').isIdenticallyEqual(ce.parse('x^2+2x+1'))); console.log(ce.parse('\\sin^2 x + \\cos^2 x \\equiv 1').evaluate()); ``` Like `isEqual()`, it is three-valued: `true`, `false` or `undefined` when the question could not be settled, in which case an `IdenticallyEqual` expression stays unevaluated. A `false` requires a definite disagreement of values: expressions that merely differ at the sampled points, such as \\( x \\) and \\( x + 1 \\), are `undefined`, since an assumption could still constrain them to be equal. :::warning[Stochastic verdicts] To settle an identity, this tier uses symbolic transformations (expansion and simplification) **and** numerical sampling: the two expressions are evaluated at a number of pseudo-random points. A `true` answer obtained by sampling is therefore a very strong indication, not a formal proof. This is the only tier that can answer from sampling; `isSame()` and `isEqual()` never do. ::: ### 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: ```live show-line-numbers console.log(ce.parse('\\cos(\\frac{\\pi}{2})').is(0)); // true console.log(ce.parse('\\sin(\\pi)').is(0)); // true console.log(ce.parse('\\cos(0)').is(1)); // true console.log(ce.number(1e-17).is(0)); // false (literal) console.log(ce.parse('x + 1').is(1)); // false (not constant) ``` 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)` | Syntactic 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)` | Arithmetic equality (evaluation, then an exact compare of exact numbers or a tolerance compare). May return `undefined`. | | `lhs.isIdenticallyEqual(rhs)` | Identity in all free variables (expansion, simplification, sampling). May return `undefined`. | | `lhs.match(rhs) !== null` | Pattern match | | `ce.expr(["Equal", lhs, rhs]).evaluate()` | Synonym for `lhs.isEqual(rhs)` | | `ce.expr(["IdenticallyEqual", lhs, rhs]).evaluate()` | Synonym for `lhs.isIdenticallyEqual(rhs)` | | `ce.expr(["IsSame", lhs, rhs]).evaluate()` | Synonym for `lhs.isSame(rhs)`, comparing the operands as written (they are not canonicalized) | | `ce.expr(["Same", lhs, rhs]).evaluate()` | Synonym for `lhs.isSame(rhs)` (Epsil `===`). Always decides. |
:::info[Choosing the Right Comparison] - Use **`isSame()`** when you know the exact expression you're comparing against (e.g., checking if an operand is `0` or `1` in a simplification rule). - Use **`is()`** when the expression might need evaluation to reveal its value (e.g., checking user input like `\cos(\pi/2)` against `0`). - Use **`isEqual()`** to compare values, including expressions that must be evaluated first. - Use **`isIdenticallyEqual()`** to check a symbolic identity, i.e. an equality that must hold for every value of the free variables. ::: ## 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. ```live show-line-numbers mark-javascript-line="4" 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()); ``` A substitution replaces the **free** occurrences of a symbol. A name bound by an operator is not replaced where that operator binds it: the index of a `Sum`, the parameter of a function literal, the variable of a definite integral, a local of a `Block`. So `j + \sum_{j=1}^{3} j` with `j` replaced by `10` is `10 + \sum_{j=1}^{3} j`. The variable of a derivative, of a series or of an indefinite integral is free in the result (the derivative of `x^2` is a function of `x`), so it is replaced, and the result is the value at that point rather than a derivative of a number: `\frac{d}{dx}x^2` with `x` replaced by `3` is `\left.\left(\frac{d}{dx}x^2\right)\right|_{x=3}` (an `EvaluateAt` expression), which evaluates to `6`. The replacement of any other symbol is put inside the derivative before it is computed. ## 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. 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. Read more about Patterns and Rules for these operations --- # Numeric Evaluation Source: https://mathlive.io/compute-engine/guides/numeric-evaluation/ To obtain an exact numeric evaluation of an expression use `expr.evaluate()`. To obtain a numeric approximation use `expr.N()`. ## Parse + Numeric Free Function For the common "parse then numeric approximation" flow, use the `N()` free function: ```live // import { N } from '@cortex-js/compute-engine'; console.log(N("\\sqrt{2}")); ``` The `N()` free function accepts either a LaTeX string or an `Expression`. It uses a shared `ComputeEngine` instance created on first call. ## Exact Evaluation An evaluation with `expr.evaluate()` preserves **exact values**. Exact values are:
| Type | Examples | |------------------------------|-----------------------------------------------| | Integers | $42$, $-1234$ | | Rationals | $\nicefrac{3}{4}$, $-\nicefrac{11}{7}$ | | Square Roots of Integers | $\sqrt{2}$, $-3\sqrt{5}$ | | Constants | $e$ (`ExponentialE`), $\pi$ (`Pi`) |
A number literal is exact or a float by the way it is written. A literal with no fraction part and no negative exponent is an exact integer: `1e3` is the exact `1000`. A literal with a fraction part is a float, also when the fraction is zero (`1.0`, `1.5e3`), and so is a literal with a negative exponent (`1e-3`). ```live console.log(parse('1/3 + 1/4').evaluate()); console.log(parse('\\sqrt{2} + \\sqrt{3} + \\sqrt{75}').evaluate()); ``` ## Numeric Approximation **To force the evaluation of an expression to be a numeric approximation**, use `expr.N()`. ```live console.log(parse('1/3 + 1/4').N()); console.log(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**. ```live console.log(parse('1/3 + 1/4 + 1.24').evaluate()); ``` An integer function of an exact integer argument gives the exact integer under `expr.N()` too, because an approximation of the argument would change the answer: `DigitSum`, `Binomial`, `Factorial`, `Fibonacci`, `Mod`, `GCD`, `LCM` and the integer predicates (`IsPrime`, `IsEven`, …) read an exact operand as written. Only an inexact (float) operand is read as a float. ```js ce.box(['DigitSum', ['Power', 2, 1000]]).N(); // ➔ 1366 (the same as evaluate()) ce.box(['GCD', ['Power', 2, 100], ['Power', 6, 50]]).N(); // ➔ 1125899906842624 (2^50) ce.box(['IsPrime', ['Subtract', ['Power', 2, 89], 1]]).N(); // ➔ True ``` ### Infinity and NaN The two non-finite results fall on opposite sides of the exactness rule above, which is why they behave differently under `evaluate()`. $\pm\infty$ **is an exact value.** A function of an infinity is therefore treated like a function of any other exact argument: if there is a closed form it is used, and if there is not, the expression stays symbolic rather than numericizing. ```live console.log(ce.parse('\\mathrm{Erf}(\\infty)').evaluate()); // ➔ 1 console.log(ce.parse('\\sin(\\infty)').evaluate()); // ➔ sin(+oo) ``` There is no inexact infinity. At machine precision, a float that is too large for a double, such as `1.0e800`, becomes $+\infty$, and that infinity is exact (`isExact` is `true`) like any other. At arbitrary precision the same literal stays a finite number. `NaN` **is not an exact value.** It is the argument that triggers the automatic numeric approximation, so any numeric function of `NaN` evaluates to `NaN` — under plain `evaluate()`, not only under `N()`. There is no exact value to hold on to: ```live console.log(ce.parse('\\sin(\\operatorname{NaN})').evaluate()); // ➔ NaN ``` Note that this is a statement about *values*, not about types. In the type system the bare numeric types — `integer`, `rational`, `real`, `complex` — denote **finite** values only, so neither an infinity nor `NaN` inhabits them. Both are still `number`. Read more about **the numeric types** ## Angular Units When a trigonometric function is given a unitless value, the Compute Engine interprets it using the current angular unit. Set it with `ce.angularUnit` (default: `"rad"`). Valid values: `"rad"`, `"deg"`, `"grad"`, `"turn"`. ```live ce.angularUnit = "deg"; console.log(ce.parse("\\cos 60").N()); // ➔ 0.5 ``` The angular unit applies to the trigonometric functions (`Sin`…`Csc`, `Haversine`), whose argument is an angle, and to the inverse trigonometric functions (`Arcsin`…`Arccsc`, `Arctan2`, `InverseHaversine`), whose result is an angle — in degree mode `\arcsin(1)` evaluates to the exact value `90`. Hyperbolic functions and their inverses are **not** affected: their argument is a dimensionless number, not an angle. Compiled functions honor the angular unit as well: with `ce.angularUnit = "deg"`, `ce.compile("\\sin(x)")` returns a function that interprets `x` in degrees, matching `evaluate()`. The symbolic integrator follows the angular unit as well. With `ce.angularUnit = "deg"`, `\int_0^{180} \sin x\,dx` evaluates to `360/\pi` and `\int \sin x\,dx` to `-(180/\pi)\cos x`. An integral with no trigonometric function, such as `\int_0^1 \frac{dx}{1 + x^2} = \pi/4`, has the same value in every unit. The same holds with the integration rules library loaded, and for the closed form the JavaScript target compiles for a definite integral. ## Rounding Ties `Round` rounds a number to the nearest integer. A value exactly halfway between two integers (a tie) is rounded with the rule of `ce.roundingTies` (default: `"away-from-zero"`). | Rule | `Round(2.5)` | `Round(3.5)` | `Round(-2.5)` | Used by | | :--- | :---: | :---: | :---: | :--- | | `"away-from-zero"` | `3` | `4` | `-3` | the default | | `"to-even"` | `2` | `4` | `-2` | IEEE 754, Python, NumPy, Mathematica | | `"toward-zero"` | `2` | `3` | `-2` | | | `"toward-positive-infinity"` | `3` | `4` | `-2` | JavaScript `Math.round` | | `"toward-negative-infinity"` | `2` | `3` | `-3` | | ```live ce.roundingTies = "to-even"; console.log(ce.parse("\\operatorname{round}(2.5)").evaluate()); // ➔ 2 ``` The rule applies at every precision, to an exact rational (`Round(5/2)`), and to the step form `Round(x, step)`, which rounds to the nearest multiple of the step: with `"to-even"`, `Round(0.125, 1/100)` is `3/25`. A value that is not a tie is not affected. Compiled functions honor the rule as well: the code is made with the rule in effect when the expression is compiled, and a function compiled before a change keeps its rule. `Remainder` does not use this rule: its quotient is always rounded with a tie toward `+∞`. ## JavaScript Interoperability The result of `expr.evaluate()` and `expr.N()` is a 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. ```js const expr = 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 expression. ```js const expr = parse('1/3 + 1/4'); console.log(expr.N().valueOf()); // ➔ 0.5833333333333334 ``` The `valueOf()` method of a expression can be used in JavaScript expressions. ```live const expr = parse('1/3 + 1/4'); console.log(expr.N().valueOf() + 1); ``` Unlike the `.re` property, `valueOf()` can also return a `boolean` or a `string`, depending on the value of the expression. **To get an `Expression` number literal from a JavaScript number**, use `ce.expr()` or `ce.number()`. ```live const expr = box(1.5); console.log(expr.valueOf()); ``` ## Numeric Precision The number of significant digits used in numeric calculations is controlled by the `ce.precision` property of the `ComputeEngine` instance. ```live 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"`. **To request a specific precision for a single evaluation**, use the `N` function with a precision argument: `["N", expr, digits]`. ```live console.log(ce.box(['N', 'Pi', 50]).evaluate().toString()); ``` The value is computed with the requested digits plus a few guard digits, then rounded to the requested digits. The working precision of the engine (`ce.precision`) does not change: after `ce.box(['N', 'Pi', 30]).evaluate()`, `ce.precision` is still `21`. A later operation on the result is computed at the working precision, so `["Add", ["N", "Pi", 30], 1]` evaluates to `4.14159265358979323846`. The reference page for [`N`](/compute-engine/reference/core/#n) describes the precision and accuracy goals `["N", expr, ["List", p, a]]`. ### Machine Precision If the precision is 15 or less, the Compute Engine uses a [64-bit binary floating point format](https://en.wikipedia.org/wiki/IEEE_754) 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 represents 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. ```ts 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 ::: ### Arbitrary Precision 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} $. ```ts 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. ### Serialization and Precision The `precision` property controls how computations are performed. It also affects how results are displayed: - **`.latex`** and **`.toString()`** round numeric output to `ce.precision` significant digits. This hides noise digits that arise from internal rounding in division and transcendental functions. - **`.json`** and **`toJSON()`** emit the full unrounded value for lossless data interchange. Use `expr.toMathJson({ digits: 'auto' })` to get precision-rounded MathJSON output. **To explicitly control digits in MathJSON output**, use `expr.toMathJson()` with the `digits` option: ```ts expr.toMathJson({ digits: 'auto' }); // rounded to ce.precision expr.toMathJson({ digits: 'max' }); // all digits (default) expr.toMathJson({ digits: { fractional: 5 } }); // 5 fractional digits expr.toMathJson({ digits: { significant: 5 } }); // 5 significant digits ``` A float keeps its digits in MathJSON, also when they repeat: the float `1.0/3` serializes as `"0.333333333333333333333"`. A number string with a repetend in parentheses (`"0.(3)"`) is an exact rational, so writing a float that way would make `ce.box()` read it back as the exact `1/3`. To mark the repeating digits of a float, use `expr.toMathJson({ repeatingDecimal: true })`. When the precision is greater than 15, the return value of `expr.N().json` may be a MathJSON number with a `num` string containing all available digits: ```json example { "num": "3.14159265358979323846264338327950288419716939937510" } ``` :::info[Note] The `.json` output may contain more digits than `ce.precision` because some arithmetic operations (addition, subtraction, multiplication) are exact and preserve all digits. Digits beyond the working precision are not guaranteed to be accurate. For display purposes, use `.latex` or `.toString()` which automatically round to the working precision. ::: ## Repeated Evaluation **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. `ce.assign()` accepts booleans, numbers, bigints, expressions, or functions. Use `undefined` to clear a value. ```live 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. ```live 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()` ```live ce.assign("x", undefined); console.log(ce.parse("3x^2+4x+2").N()); // ➔ "3x^2+4x+2" ``` **To change the value of a variable** set its `value` property: ```ts ce.symbol("x").value = 5; ce.symbol("x").value = undefined; ``` ## Compiling If performance is important, the expression can be compiled to a JavaScript function. **To get a compiled version of an expression** use the `compile()` function: ```js import { compile } from '@cortex-js/compute-engine'; const result = compile("3x^2+4x+2"); for (const x = 0; x < 1; x += 0.01) console.log(result.run({ 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** ## Simplifying Before Evaluating **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: ```js ce.precision = "machine"; const x = ce.parse("0.1 + 0.2").N(); console.log(ce.expr(["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. ```js 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()`. ## Tolerance 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. Two numbers whose difference is smaller than the tolerance are considered equal. This applies only when a floating point number is involved: two exact numbers (integers, rationals, or rational multiples of a square root) are compared exactly, so $\frac{1}{3}$ and $\frac{3333333333333}{10^{13}}$ are not equal, while $\frac{1}{10}$ and $0.1$ are. The tolerance is an absolute value, and it does not change with the precision: at 40 digits, two values that differ by $10^{-12}$ still compare as equal. A tolerance you set is kept when the precision changes. **To make the tolerance follow the precision**, set it to `"auto"`. The tolerance is then $10^{-(\text{precision} - 2)}$, two digits short of the working precision, and it is recomputed at each precision change: $10^{-19}$ at the default 21 digits, $10^{-38}$ at 40 digits. This mode is meant for big-decimal work: a value computed in machine floats (about 16 digits) then compares as different from its exact value. ```js const ce = new ComputeEngine({ precision: 40, tolerance: "auto" }); ce.tolerance; // 1e-38 ce.precision = 50; ce.tolerance; // 1e-48 ``` ## Numeric Functions The topics below from the [MathJSON Standard Library](/compute-engine/standard-library/) can provide numeric evaluations for their numeric functions:
| Topic | Symbols/Functions | | :---------------------------------------------------------------- | :--------------------------------------------------------------------- | | [Arithmetic](/compute-engine/reference/arithmetic/) | `Add` `Multiply` `Power` `Exp` `Log` `ExponentialE` `ImaginaryUnit`... | | [Calculus](/compute-engine/reference/calculus/) | `Derivative` `Integrate`... | | [Complex](/compute-engine/reference/complex/) | `Real` `Conjugate`, `ComplexRoots`... | | [Special Functions](/compute-engine/reference/special-functions/) | `Gamma` `Factorial`... | | [Statistics](/compute-engine/reference/statistics/) | `StandardDeviation` `Mean` `Erf`... | | [Trigonometry](/compute-engine/reference/trigonometry/) | `Pi` `Cos` `Sin` `Tan`... |
--- # Execution Constraints Source: https://mathlive.io/compute-engine/guides/execution-constraints/ # Execution Constraints Some innocuous-looking expressions can take a very long time — or forever — to evaluate: a sum over a huge range, a number-theory function applied to a large integer, a limit that fails to converge, or a loop that never terminates. To keep evaluation responsive, the Compute Engine offers two kinds of bounds: a **time budget**, expressed as a span, and **iteration/recursion budgets**, expressed as counts. ## Time Budgets: Spans A time budget is a **span**: a region of code you run with an explicit deadline. Everything evaluated inside the span shares one deadline; when it is exceeded, the evaluation in progress throws a `CancellationError` with `cause: "timeout"`. ```js import { ComputeEngine, CancellationError } from "@cortex-js/compute-engine"; const ce = new ComputeEngine(); try { ce.withTimeLimit({ ms: 500, label: "my-app:eval" }, () => ce.parse("\\sum_{i=1}^{100000000} \\sqrt{i}").evaluate() ); } catch (err) { if (err instanceof CancellationError && err.cause === "timeout") console.log("Evaluation exceeded its time budget"); } ``` `withTimeLimit(limit, fn)` runs `fn` and returns its value. The `limit` is either a number of milliseconds, or an object `{ ms, label }`. The object form is preferred for new code: the `label` reads before the callback, and it is what lets you attribute a timeout later (see below). The contract is **at most** `ms` milliseconds: > Runs `fn` with **at most** `ms` milliseconds. A tighter deadline may already > be in effect from an enclosing span, in which case that one preempts this > limit. Use the `label` and the `attribution` field on `CancellationError` to > determine which limit fired. Work performed **outside** any span is not time-bounded. A consumer that wants a blanket bound wraps its entry point in a single span — one line at the boundary — rather than relying on an implicit global limit. ### Nesting: the tighter deadline wins Spans nest. When you enter a span inside another, the effective deadline is the **minimum** of the two — a span can only shorten the budget, never extend it. This means an inner span can never defeat a bound its caller set: ```js ce.withTimeLimit({ ms: 100, label: "outer" }, () => // Even though this asks for 5000ms, the enclosing 100ms deadline preempts it. ce.withTimeLimit({ ms: 5000, label: "inner" }, () => ce.parse("\\sum_{i=1}^{100000000} \\sqrt{i}").evaluate() ) ); ``` Labels are for **attribution only**, never for control: a label cannot buy a span more time than its enclosing budget allows. ### Attribution: whose budget expired? When several spans are active, a `CancellationError` tells you which one owned the deadline that fired:
| Property | Description | | :--- | :--- | | `err.cause` | `"timeout"` for a time-budget breach | | `err.attribution` | The `label` of the span whose deadline fired (`undefined` for an unlabelled span) | | `err.spans` | All active span labels, outermost first |
`attribution` answers the practical question "was this **my** budget or my **caller's**?" — compare it directly against the label you passed. Because nesting takes the tighter deadline, the span that fires is the tighter one: ```js try { ce.withTimeLimit({ ms: 100, label: "outer" }, () => ce.withTimeLimit({ ms: 5000, label: "inner" }, () => ce.parse("\\sum_{i=1}^{100000000} \\sqrt{i}").evaluate() ) ); } catch (err) { if (err instanceof CancellationError) { console.log(err.attribution); // ➔ "outer" (the tighter, enclosing budget) console.log(err.spans); // ➔ ["outer", "inner"] } } ``` Here the outer 100ms budget preempts the inner 5000ms request, so `attribution` is `"outer"`. Had the inner span been the tighter one, `attribution` would be `"inner"`. Code catching the error inside the `inner` span can therefore tell "my own sub-budget expired — degrade gracefully" from "my caller's budget expired — propagate". The numeric form `withTimeLimit(500, fn)` stays valid and produces an **unlabelled** span: any timeout it owns has `attribution: undefined` and does not appear in `spans`. Supply a label only when you can act on it. ### Step budgets `ce.withStepBudget({ steps, label }, fn)` runs `fn` with at most `steps` steps of engine work. A step is one of the engine's cancellation checks. The count for one computation on one engine state is the same on every machine, so a step budget stops at the same point whatever the speed or the load of the machine. It is an opaque unit, not a cost, and it is not comparable across engine versions: tune the budget by measuring, and keep a `withTimeLimit` span outside it. A spent budget throws a `CancellationError` with `cause: "step-budget"` and the span's `label` as its `attribution`. ### Reading the state of the current span The engine checks its deadline only inside its own work (evaluation, canonicalization). Two read-only properties tell your own code where the current span stands:
| Property | Description | | :--- | :--- | | `ce.timeRemaining` | Milliseconds left before the earliest deadline of the active spans, `0` or less once it has passed. `Infinity` when no active span has a time limit (outside any span, or in a step budget with no time limit around it). A spent step budget does not change it. | | `ce.stepBudgetSpent` | `true` when a step budget of the current span, or of an enclosing one, is spent. Always `false` outside any span. |
Both properties describe the span that is active in the current synchronous code. Code that runs after an `await` is outside the span, even when the `await` is inside the `withTimeLimit` callback: it reads `Infinity` and `false`. A loop of your own that runs inside a span, and does work the engine does not see (walking expressions, building a list), reads `ce.timeRemaining` to stop on time: ```js ce.withTimeLimit({ ms: 50, label: "my-app:layout" }, () => { for (const item of items) { if (ce.timeRemaining <= 0) break; layOut(item); } }); ``` When a budget is spent, code between the throw and the end of the span can catch the `CancellationError` and return a value anyway. That value is partial. Before you store a value computed in the span in a cache, read `ce.stepBudgetSpent`, so that the cache does not keep a partial value. ### `ce.timeLimit` has been removed Earlier versions armed a global, implicit deadline around every `evaluate()`/`simplify()` through the `ce.timeLimit` property (default 2000ms). Its scope was never well-defined — "the current evaluation" is not a region you can point at — and a span silently overrode it with no indication in the API. `ce.timeLimit` was deprecated in the previous minor release and is now **removed**. There is no implicit deadline anymore: an `evaluate()` outside a span runs unbounded, and `withTimeLimit()` spans are the only way to arm one. If you relied on the old 2000ms ambient default, wrap the work you want bounded in a span: ```js // Before (no longer compiles) ce.timeLimit = 500; const r = expr.evaluate(); // After const r = ce.withTimeLimit({ ms: 500, label: "my-app:eval" }, () => expr.evaluate() ); ``` For a blanket bound, this is a single wrap at your application's entry point. **Numerical integration is special.** Monte Carlo integration (`NIntegrate`, or `N()` on an `Integrate` expression) does not throw when it runs out of time. Instead it returns the estimate computed from the samples taken so far, with a correspondingly larger error bound: a partial estimate is more useful than no answer. ## Iteration and Recursion Budgets Time is not the only way a computation can run away. Two count-based budgets bound structural growth independently of the clock:
| Property | Default | Description | | :--- | :--- | :--- | | `ce.iterationLimit` | 1 024 | Maximum iterations of a looping control structure (`Loop`, `Comprehension`, `While`, `FixedPoint`) and of collection iteration | | `ce.recursionLimit` | 1 024 | Maximum depth of user-function recursion |
When the iteration limit is exceeded, a `CancellationError` with cause `"iteration-limit-exceeded"` is thrown: ```js ce.iterationLimit = 10_000; // A loop over a billion elements: aborts after 10,000 iterations ce.box(["Loop", ["Add", "i", 1], ["Element", "i", ["Range", 1, 1e9]]]).evaluate(); // ➔ throws CancellationError, cause: "iteration-limit-exceeded" ``` When user-function recursion exceeds `ce.recursionLimit`, a `CancellationError` with cause `"recursion-depth-exceeded"` is thrown instead. Setting `ce.iterationLimit` to `0` or a negative value disables the iteration limit; a span (if any) still applies. The built-in symbolic integrator and the variation-of-parameters step of `DSolve` each have a fixed budget of 100,000 steps. The budget counts each step of the search, the simplification rules tried included, so the result is the same on every machine. When the budget is spent, the result is the unevaluated `Integrate` or `DSolve` expression; no error is thrown. A `withTimeLimit()` span of the caller still applies, and its expiry reaches the caller. An Epsil program has no iteration limit. The first time an engine runs a program (`executeEpsil`, or the `epsil` command), the default limit is removed, and it stays removed for every later evaluation on that engine, Epsil or not, so that a lazy collection a program returns can be read in full. A limit you assign to `ce.iterationLimit` yourself is kept, and it applies to Epsil programs too. A host that shares one engine between Epsil and other evaluation, and wants the cap for the latter, assigns `ce.iterationLimit` after the program has run. ## Asynchronous Evaluation For long-running computations, `evaluateAsync()` evaluates without blocking the event loop and accepts an `AbortSignal` so a caller can request cancellation: ```js const controller = new AbortController(); // Request cancellation if the evaluation is still running after one second setTimeout(() => controller.abort("user-canceled"), 1000); try { const result = await expr.evaluateAsync({ signal: controller.signal }); console.log(result.toString()); } catch (err) { if (err instanceof CancellationError) console.log("Canceled:", err.cause); } ``` Be aware of what async cancellation can and cannot do today: - **Cooperative, not preemptive.** An `abort()` is honored only at the points where the engine checks the signal. Today those checks are reached by `Loop`, `Factorial`, `Sum` and `Product`; other operators run their synchronous evaluation to completion and cannot be interrupted by a signal. - **A synchronous tight loop blocks delivery.** JavaScript is single-threaded. If an uninterruptible operator is spinning, the `abort()` callback cannot even run until it yields, so cancellation may be delayed indefinitely or never observed for that call. - **Not a runaway guard.** For untrusted or genuinely unbounded work, an `AbortSignal` is not sufficient protection. Bounding such work reliably requires isolating it in a Web Worker or a separate process that you can terminate from the outside. For most work, a time span (`withTimeLimit`) and the iteration/recursion budgets are the appropriate bounds; reach for async cancellation when you need a user-facing "cancel" affordance on the interruptible operators above. --- # Canonical Form Source: https://mathlive.io/compute-engine/guides/canonical-form/ 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. ```js 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.expr()` 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, {form: 'raw'})` or `ce.expr(expr, {form: 'raw'})`. You can further customize the canonical form of an expression by using the [`["CanonicalForm"]`](/compute-engine/reference/core/#canonicalform) function or by specifying the form you want to use. See [below](#custom-canonical-forms) 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. ```js 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}', { form: 'raw' }).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()`. ```js const expr = ce.parse("\\frac{3}{5}"); console.log(expr.toMathJson()); // ➔ ["Rational", 3, 5] console.log(expr.toMathJson({ exclude: ["Rational"] })); // ➔ ["Divide", 3, 5] // We have excluded `["Rational"]` expressions, so it // is interpreted as a division instead. ``` ```js const expr = ce.parse("\\frac{10}{30}", { form: 'raw' }); console.log(expr.json); // ➔ ["Divide", 10, 30] console.log(expr.isCanonical); // ➔ false console.log(expr.canonical.json); // ➔ ["Rational", 1, 3] ``` ## Canonical Form and Validity 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`. ```js 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'"]] ``` ## Canonical Form Transformations 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 - Exact numeric operands are folded: integers, rationals, and radicals are summed together, e.g. \\(2 + x + 5 \to x + 7\\), $\frac{1}{3} + x + \frac{2}{3} \to x + 1$, $\sqrt{2} + x + \sqrt{2} \to x + 2\sqrt{2}$. Machine floats (e.g. $1.5$) are not folded. - 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 - Exact numeric operands are folded: integers, rationals, and radicals are multiplied together, e.g. \\(2 \times x \times 5 \to 10x\\), $\frac{1}{2} \times x \times 2 \to x$. Machine floats (e.g. $1.5$) are not folded. - 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\\) - \\((-1)^\{-1\} \to -1\\) (and \\(1^\{-1\} \to 1\\)) - \\((\pm\infty)^\{-1\} \to 0\\) - \\(0^\{\infty\} \to 0\\) - \\((\pm 1)^\{\pm \infty\} \to \operatorname\{NaN\}\\) - \\(\infty^\{\infty\} \to \tilde\infty\\) - \\(\infty^\{-\infty\} \to 0\\) - \\((-\infty)^\{\infty\} \to \tilde\infty\\) - \\((-\infty)^\{-\infty\} \to 0\\) - `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"]` - 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) \\) ## Custom Canonical Forms 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.expr(expr, { form: 'raw' })` or `ce.parse(s, { form: 'raw' })`. ```live console.log(ce.parse("2(0+x\\times x-1)", {form: 'raw'}).json); ``` **To get the full canonical form**, use `ce.expr(expr, { form: 'canonical' })` or `ce.parse(s, { form: 'canonical' })`. The `form` option can be omitted as it defaults to `'canonical'`. ```live console.log(ce.parse("2(0+x\\times x-1)", {form: 'canonical'}).json); console.log(ce.parse("2(0+x\\times x-1)").json); ``` **To get a custom canonical form of an expression**, use the [`["CanonicalForm"]`](/compute-engine/reference/core/#canonicalform) function or specify the form you want to use with the `form` option of `ce.expr()` and `ce.parse()`. **To order the arguments in a canonical order**, use `ce.expr(expr, { form: "Order" })` or `ce.parse(s, { form: "Order" })`. ```live console.log(ce.parse("0+1+x+2+\\sqrt{5}", {form: "Order"} )); ``` 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"]`](/compute-engine/reference/core/#canonicalform) for more details. For example: ```live const latex = "3(2+x)"; console.log(ce.parse(latex, {form: 'raw'})); console.log(ce.parse(latex, {form: ["InvisibleOperator"]})); console.log(ce.parse(latex, {form: ["InvisibleOperator", "Add", "Order", ]} )); ``` ## Canonical Form Pipeline When you specify a list of canonical forms (e.g., `["Number", "Power", "Divide"]`), the forms are applied in the specified order. Each form recursively transforms sub-expressions that match its type. Some forms have dependencies on other forms: - The **Divide** form internally applies the **Power** form to its operands before calling `canonicalDivide`. This is because division canonicalization benefits from having power expressions already normalized. - All forms apply **symbol canonicalization** first (constant symbols with `holdUntil: 'never'` are substituted with their values). The result of partial canonicalization is a **structural** expression, not a fully canonical one. This means: - `expr.isCanonical` returns `false` - `expr.isStructural` returns `true` - Calling `expr.canonical` will perform full canonicalization - The expression can be used in arithmetic operations (`.add()`, `.mul()`, etc.) The order in which forms are specified matters. For example, applying `"Number"` before `"Power"` ensures that numeric literals are resolved before power simplifications are attempted. ## Comparing Expressions Structurally In some applications — for example checking that a student used the right *method* to solve a problem — you want to compare expressions by structure rather than by value. The expression \\(3 \times 2 + 1\\) should be equivalent to \\(1 + 2 \times 3\\) (same operations, different order), but **not** to \\(7\\) (the numeric result) or \\(6 + 1\\) (different operations). Use `form: ['Flatten', 'Order']` with `isSame()` for this: ```live const a = ce.parse('3\\times2+1', { form: ['Flatten', 'Order'] }); const b = ce.parse('1+2\\times3', { form: ['Flatten', 'Order'] }); const c = ce.parse('1+(3\\times2)', { form: ['Flatten', 'Order'] }); const d = ce.parse('7', { form: ['Flatten', 'Order'] }); console.log(a.isSame(b)); // ➔ true (commutativity) console.log(a.isSame(c)); // ➔ true (parentheses ignored) console.log(a.isSame(d)); // ➔ false (different structure) ``` This partial canonicalization: - **`Flatten`**: removes parentheses (Delimiters) and flattens associative operations like \\(a + (b + c) \to a + b + c\\) - **`Order`**: sorts commutative operands into a consistent order so that \\(a + b\\) and \\(b + a\\) have the same structure Unlike the full canonical form, it does **not** evaluate numeric expressions: \\(3 \times 2\\) stays as `["Multiply", 2, 3]` rather than being folded to `6`. You can combine this with `isIdenticallyEqual()` to give differentiated feedback: ```js const goodAnswer = ce.parse(answerLatex, { form: ['Flatten', 'Order'] }); const input = ce.parse(inputLatex, { form: ['Flatten', 'Order'] }); if (goodAnswer.isSame(input)) { // Correct method — structurally equivalent } else if (goodAnswer.isIdenticallyEqual(input)) { // Right answer but wrong method } else { // Incorrect } ``` Use `isIdenticallyEqual()` rather than `isEqual()` when the answer may contain unknowns: `isEqual()` compares values and returns `undefined` for a symbolic identity such as \\( (x+1)^2 \\) versus \\( x^2 + 2x + 1 \\), while `isIdenticallyEqual()` establishes it. See [Comparing Expressions](/compute-engine/guides/symbolic-computing/#comparing-expressions). If you only need binding (so that operator definitions are available and arithmetic methods like `.add()` and `.mul()` work) without any normalization, use `form: 'structural'`. This preserves the original operand order and structure. ## Custom Transformations You can define your own transformations to apply to an expression to obtain a custom form. The `expr.map()` method visits every sub-expression, which is useful for structural transforms: ```js import { isFunction } from '@cortex-js/compute-engine'; // Remove all Delimiter wrappers (parentheses) const deparenthesize = (expr) => expr.map((e) => (isFunction(e, 'Delimiter') ? e.op1 : e)); const expr1 = ce.parse('3+4\\times2', { form: 'raw' }); const expr2 = ce.parse('3+(4\\times(2))', { form: 'raw' }); console.log(deparenthesize(expr1).isSame(deparenthesize(expr2))); // ➔ true ``` --- # Symbols Source: https://mathlive.io/compute-engine/guides/symbols/ 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`. ```live show-line-numbers const n = ce.expr("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`. This includes all symbols, even those bound by scoping constructs like `Sum` or `Product`. **To get only the free variables** (symbols that are not constants, operators, or bound by a scoping construct), use `expr.unknowns` or `expr.freeVariables`. Read more about **adding definitions** for symbols and functions ## Scope Symbols are defined within a **lexical scope**. Read more about **scopes** ## Unknowns and Constants A symbol that has been declared, but has no values associated with it, is said to be an **unknown**. Use `expr.unknowns` or `expr.freeVariables` to get the list of unknowns in an expression. Symbols that are bound by scoping constructs (e.g., the index variable `k` in `\sum_{k=0}^{10} k \cdot x`) are excluded. 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. ```js console.log(ce.expr("x").isConstant); // ➔ false console.log(ce.expr("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. ::: ```js ce.precision = 4; const smallPi = ce.expr("Pi"); // π with 4 digits console.log(smallPi.latex); // ➔ 3.1415 ce.precision = 10; const bigPi = ce.expr("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 ``` ## Using `i` or `e` as a Variable Name `i` is the imaginary unit and `e` is Euler's number. Both are constants, and both are substituted during canonicalization — so a formula that uses one as an ordinary variable, most often as a list index, quietly computes with the constant instead: ```js console.log(ce.parse("A_{i,j}").json); // ➔ ["Subscript", "A", ["Sequence", ["Complex", 0, 1], "j"]] ``` The index became $\imaginaryI$. Nothing errors; an indexed access built this way simply yields `NaN`. **To use one of these names as a variable, declare it before parsing anything that mentions it.** A declaration shadows the constant for that scope: ```js ce.declare("i", "integer"); console.log(ce.parse("A_{i,j}").json); // ➔ ["Subscript", "A", ["Sequence", "i", "j"]] ce.assign("L", ce.box(["List", 10, 20, 30])); ce.assign("i", 2); console.log(ce.parse("L_i").evaluate().toString()); // ➔ 20 console.log(ce.parse("L[i]").evaluate().toString()); // ➔ 20 ``` Four things are worth knowing before you do this: - **Declare before parsing.** An expression parsed before the declaration has already had the constant substituted, and declaring afterwards does not change it. Declare at engine setup. - **Prefer `integer` for an index.** `integer` rejects a fractional value at assignment; a symbol declared `unknown` accepts `i = 1.5` and passes the fractional index through to the access with no diagnostic. - **Arithmetic with that name changes meaning in that scope**, and only there: with `i` declared and set to 2, `2i` is `4`, not the complex number. The dedicated spelling still works — `\imaginaryI` (and `\mathrm{i}`) always parses as the imaginary unit — and serialization is unaffected, because a complex value is written as `\imaginaryI`, never as a bare `i`. A complex result therefore survives a serialize-and-reparse round trip. - **Bound variables are unaffected.** `\sum_{i=1}^{3} i` is `6` whether or not `i` is declared: a binder introduces its own index. Declaring `e` follows the same rules but costs more, because it takes $e^{x}$ with it. Write exponentials as `\exp(x)` instead — it canonicalizes to `["Power", "ExponentialE", "x"]`, referring to the constant directly, so it is unaffected by a variable named `e`. To limit either declaration to part of a computation, declare it inside a scope (see [Scope](#scope)); the constant is restored when the scope is exited. ## Automatic Declaration 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**. ```js const symbol = ce.expr("m"); // m for mystery console.log(symbol.type); // ➔ "unknown" ce.assign("m", 5); console.log(ce.symbol("m").type); // ➔ "integer" ``` The inferred type is `integer`, not `"finite_integer"`: the numeric types are finite by default, so bare `integer` already promises a finite whole number. 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. ## Forgetting a Symbol **To _reset_ what is known about a symbol** use the `ce.forget()` function. The `ce.forget()` function will remove any [assumptions](/compute-engine/guides/assumptions) associated with a symbol, and remove its value. However, the symbol will remain 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. ::: --- # Custom Functions and Symbols Source: https://mathlive.io/compute-engine/guides/augmenting/ The [MathJSON Standard Library](/compute-engine/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. ## Introduction When a 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. ### Automatic Declaration 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. Learn more about **types**. To provide a more explicit definition for the symbol, you can [define it using a LaTeX](#definitions-using-latex) expression, or an [explicit declaration](#explicit-declarations) using the `ce.declare()` method. ### Declarations are Scoped The declaration of a symbol is done within a **lexical scope**. A scope is a hierarchical collection of definitions. Read more about **scopes** ## Definitions Using LaTeX The simplest way to define a new symbol is to use LaTeX. For example, to define a new symbol $m$ with a value of $42$, use the following LaTeX expression: ```js ce.parse("m := 42").evaluate(); console.log(ce.parse("m").value); // ➔ 42 ``` **Note**: the assignment expression must be evaluated to take effect. To define a new function $f$ that multiplies its argument by $2$, use the following LaTeX expression: ```js ce.parse("f(x) := 2x").evaluate(); console.log(ce.parse("f(3)").evaluate()); // ➔ 6 ``` The `\mapsto` operator is an alternative syntax to define a function: ```js ce.parse("f := x \\mapsto 2x").evaluate(); console.log(ce.parse("f(3)").evaluate()); // ➔ 6 ``` **To define multiletter symbols**, use the `\operatorname{}` command: ```js console.log(ce.parse('\\operatorname{double}(x) := 2x').evaluate()); console.log(ce.parse('\\operatorname{double}(3)').evaluate()); // ➔ 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`. ```js console.info(ce.parse('\\operatorname{double}(3)').json); // ➔ ["double", 3] ``` ## When Is the Value Captured? An assignment evaluates its right-hand side **eagerly**, when the assignment itself is evaluated. Any symbol that has a value at that moment is replaced by its value, permanently: ```js ce.parse("p := 1").evaluate(); ce.parse("a := p + 5").evaluate(); // a is 6: the value of p was captured ce.parse("p := 2").evaluate(); // changing p has no effect on a console.log(ce.parse("a").evaluate()); // ➔ 6 ``` A symbol that has **no value** at that moment is an unknown: it remains in the stored value as a symbol. The value of `a` below is the expression $p + 5$, and evaluating `a` evaluates that expression — so `p` resolves to whatever its value is **at evaluation time**: ```js ce.parse("a := p + 5").evaluate(); // p is unknown: a is the expression p + 5 ce.parse("p := 2").evaluate(); console.log(ce.parse("a").evaluate()); // ➔ 7 ``` The stored value itself never changes; what changes is the result of evaluating it. This is the usual convention in computer algebra systems (it matches `Set`, i.e. `=`, in Mathematica): defining a symbol in terms of an unknown means the symbol's value _is_ that symbolic expression. There is no way to "snapshot" an unknown other than as the symbol itself. **Assigning a symbol to another symbol captures its current value — it does not create an alias.** ```js ce.parse("a := p + 5").evaluate(); ce.parse("b := a").evaluate(); // b is the expression p + 5, not a reference to a ce.parse("p := 1").evaluate(); ce.parse("a := 100").evaluate(); // no effect on b console.log(ce.parse("b").evaluate()); // ➔ 6 ``` **Self-referential and mutually referential values are allowed.** Because assignment is eager, a cycle can end up baked into a stored value: evaluating the right-hand side of `q := p + 1` when `p` is already `q + 1` stores the self-referential expression $q + 2$ as the value of `q`. Evaluating a symbol whose value refers back to itself — directly or through other symbols — returns the stored expression rather than recursing: ```js ce.parse("s := s + 1").evaluate(); console.log(ce.parse("s").evaluate()); // ➔ s + 1 ``` **To store an expression as inert data**, without capturing or resolving anything, wrap it in `Hold`: the value of the symbol is then the held expression itself, which stays unchanged under evaluation. ```js ce.assign("a", ce.box(["Hold", ["Add", "p", 5]])); ce.parse("p := 2").evaluate(); console.log(ce.parse("a").evaluate()); // ➔ Hold(p + 5) ``` **To resolve a held expression**, apply `ReleaseHold`, which removes one layer of `Hold` and evaluates the result: ```js console.log(ce.box(["ReleaseHold", "a"]).evaluate()); // ➔ 7 ``` ## Explicit Declarations **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. ```js // Declaring a symbol "m" ce.declare("m", "integer"); // Declaring a function "f" ce.declare("f", { signature: "(number) -> number", evaluate: ce.parse("x \\mapsto 2x"), }); ``` ### Declaring a Symbol **To declare a symbol** use the `ce.declare()` method with the name of the symbol as the first argument and a type as the second argument. ```js 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. ```js 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()`: ```js ce.assign("m", 42); ``` If the symbol was not previously defined, this is equivalent to: ```js ce.declare("m", { value: 42 }); ``` Alternatively, you can set the value of a symbol using the `value` property: ```js ce.expr("m").value = 42; ``` **To prevent the value of a symbol from being changed**, set the `isConstant` property to `true`: ```js ce.declare("m_e", { value: 9.1e-31, isConstant: true, }); ``` ### Declaring a Function **To declare a function**, associate an `evaluate` handler, which is the body of the function, with a symbol. ```js 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. A name is declared once per scope, so to change the definition above, pass the fields to change with `{ extend: true }` (see below). ```js ce.declare("double", { evaluate: ([x]) => x.mul(2) }, { extend: true }); ``` 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 expression. The array may be empty if there are no arguments. - `options`: an object literal which includes an `engine` property that is the Compute Engine instance that is evaluating the expression, a `numericApproximation` property that is true if the result should be a numeric approximation, an `expression` property that is the expression being evaluated, and a `precision` property with the number of significant digits requested: inside `N(x, p)` it is `p`, also when `p` is lower than `ce.precision`; under `.N()` it is `ce.precision`; in an exact evaluation it is `undefined`. Since `args` is an array, you can use destructuring to get the arguments: ```js ce.declare("double", { evaluate: (args) => args[0].mul(2) }, { extend: true }); // or ce.declare("double", { evaluate: ([x]) => x.mul(2) }, { extend: true }); ``` **A name is declared once per scope.** Declaring it again throws `The symbol "double" is already declared in this scope`. This also applies when the first declaration had no `signature`: an `evaluate` handler alone, a flag such as `lazy`, or a description is enough to make the name taken. To change an existing definition, pass a patch with only the fields to change and the option `{ extend: true }`: ```js // On a new engine, the first declaration of the name ce.declare("double", { evaluate: ([x]) => x.mul(2) }); // Throws: The symbol "double" is already declared in this scope ce.declare("double", { evaluate: ([x]) => x.mul(3) }); // Changes the handler of the existing definition ce.declare("double", { evaluate: ([x]) => x.mul(3) }, { extend: true }); ``` A patch that changes a flag of a library operator keeps the library handlers that do not depend on that flag. Removing `commutative` from `Add` or `Multiply` (`ce.declare("Add", { commutative: false }, { extend: true })`) removes the library ordering handler, so the canonical form keeps the operand order (`b + a` stays `b + a`). But the library `evaluate` handler of `Add` and `Multiply` collects like terms, and it still reorders a symbolic sum: supply your own `evaluate` handler in the same patch if the order must also be kept when the expression is evaluated. A name first seen in use, such as `f` in `ce.parse("f(x)")`, is not declared yet and can be defined with `ce.declare("f", {...})`. See [Overloading Functions](#overloading-functions) for the fields a patch can carry. ### The `expression` option The `expression` option is the canonical expression node being evaluated. Its operands (`expression.ops`, `expression.op1`, ...) are the **raw** arguments: canonical and bound, but not yet evaluated. The `args` array, by contrast, holds the **evaluated** arguments — and when `numericApproximation` is true, those have already been turned into floating point numbers. When your handler needs to know something about an argument that evaluation destroys — above all whether it was *exact* — `expression` is where to look. Treat it as read-only. The `Power` operator uses it to pick the right branch for a negative base. The convention is that a rational exponent `p/q` in lowest terms with an **odd** `q` has a real value — `(-8)^(2/3) = 4` — while everything else takes the principal complex value. Under `.N()` the exponent reaches the handler as a double, from which `p/q` can only be guessed back; reading it from `expression.op2` instead keeps the exact terms, so `.N()`, the type and the compiled code all decide the same branch. `expression` is optional: a handler called outside the evaluation driver may not receive one, so guard for `undefined` and fall back to what `args` tells you. The operand accessors `op1`/`op2`/`ops` and the number properties such as `isExact` live on narrowed interfaces, so reach them through the `isFunction()` and `isNumber()` guards — the same narrowing the `Power` handler uses. **`expression.ops[i]` is not always the provenance of `args[i]`.** The evaluated arguments are produced by a pass that reindexes them: it flattens an **associative** operator (`f(a, f(b, c))` arrives as three arguments, one more than the node has), it unwraps `ReleaseHold` (so `expression.ops[i]` is the wrapper rather than what was evaluated), and it drops an argument whose evaluation yields nothing. The positional correspondence holds only for a non-associative operator with no `ReleaseHold` and no dropped argument. If your handler indexes into `expression.ops`, treat `expression.ops.length !== args.length` as "no provenance available" and fall back. **On a `lazy: true` operator the contrast does not exist**: held operands are passed through untouched, so `args` is raw and held as well. Such a handler must canonicalize each held operand it consumes — on the `ce.box()` and `ce.parse()` routes the held operands are not even canonical. ```js import { isFunction, isNumber } from "@cortex-js/compute-engine"; ce.declare("IsExactArgument", { signature: "(number) -> boolean", evaluate: ([x], { expression, engine }) => { // `x` may have been numericized; the raw operand never is. const raw = expression !== undefined && isFunction(expression) ? expression.op1 : x; return isNumber(raw) && raw.isExact ? engine.True : engine.False; }, }); ce.box(["IsExactArgument", ["Rational", 1, 3]]).N(); // → True ce.box(["IsExactArgument", 0.3333]).N(); // → False ``` In addition to the `evaluate` handler the function definition can include a `signature` type that describes the arguments and return value of the function. ```js ce.declare("double", { signature: "(number) -> number", description: "Multiply a number by two", keywords: ["twice", "doubling"], evaluate: ([x]) => x.mul(2), }, { extend: true }); ``` The optional `description` and `keywords` properties make a definition easier to discover with `ce.searchDefinitions()`. The search includes definition names, descriptions, synonyms, keywords, and associated LaTeX commands: ```js ce.searchDefinitions("average"); // ➔ [{ id: "Mean", kind: "function" }, ...] ce.searchDefinitions("doubling", { limit: 5 }); // ➔ [{ id: "double", kind: "function" }] ``` The optional `limit` setting limits the number of results and defaults to `10`. Pass a returned `id` to `ce.lookupDefinition(id)` to inspect the complete definition. A definition can give its own derivative with the `derivative` key. The value is an array with one function literal for each argument (each literal takes all the arguments as parameters), or a handler `(ops, { engine, argument }) => Expression | undefined` that returns the partial derivative with respect to argument number `argument` at `ops`. `D` applies the chain rule. A partial derivative that the key does not give stays symbolic. ```js ce.declare("Sq", { signature: "(number) -> number", evaluate: ([x]) => (isNumber(x) ? x.mul(x) : undefined), derivative: [["Function", ["Multiply", 2, "x"], "x"]], }); ce.box(["D", ["Sq", "x"], "x"]).evaluate(); // ➔ 2x ce.box(["D", ["Sq", ["Power", "x", 2]], "x"]).evaluate(); // ➔ 4x^3 ``` See `FunctionDefinition` for more details on the other handlers and properties that can be provided when defining a function. ### Validating custom canonical handlers A custom `canonical` handler participates in expression construction before an `evaluate` handler runs. It must not assume that signature validation has already produced every missing-argument or incompatible-type error needed by the function. For required operands, check arity first and return an expression containing the appropriate `engine.error("missing")` placeholders. For operands whose shape or type the handler consumes directly, validate them before reading specialized fields. Preserve the original head in the returned error expression so serialization, diagnostics, and later evaluation still identify the failed call. Keep validation at the narrowest layer that knows the contract: - Express ordinary arity and type requirements in the function signature. - Validate structural preconditions needed specifically by `canonical` inside that handler. - Validate value-dependent conditions, such as a positive evaluated integer, in `evaluate`. - Add tests for no arguments, each missing required argument, wrong types, and the valid boundary cases. This separation avoids both silently accepting malformed calls and evaluating operands merely to canonicalize their syntax. ### Deriving the Result Type of an Operator The `signature` of an operator states its result type once, for every call. When the result type depends on the operands — the sum of two integers is an integer, the norm of a point whose components are lists is a list — the definition can add a `type` handler. The handler receives one **operand descriptor** per operand, never the operand expression itself, and returns a type (a `Type`, a type string, or `undefined` to keep the signature's result). ```js ce.declare("Halve", { signature: "(number) -> number", type: ([x]) => { // The descriptor's `type` is the operand's type; a number literal's // type carries its value (`21`), so a literal is exact here. if (x.facts.finite !== true) return "number"; return x.facts.sgn === "zero" ? "0" : "real"; }, evaluate: ([x]) => x.div(2), }); ``` A descriptor has three parts: - `type` — the operand's type. - `facts` — a small set of three-valued facts (`true`, `false`, or `undefined` for "not known"): `finite`, `sgn`, `closed` (no free variables), `collection`, `finiteCollection`, `indexed`, a static `shape` and the `elementType` the operand's own collection handler proves. - `structureOf()` — an on-demand structural view: a `symbol` (with its name and whether its type was inferred), a `string`, a `number` (with its exact rational terms when it has them), an `application` (its head and child descriptors), a `function-literal` (parameters and body), a `tuple` or a `list-literal` (with element descriptors). The second argument is a context with a read-only view of the engine (`engine.type()`, `engine.lookupDefinition()`, `engine.tolerance`) and `derive(operator, operands)`, which returns the type of applying an operator to descriptors you build yourself — the way to type the body of a mapping literal over a collection's element type. Because a handler never holds an expression, it cannot declare, canonicalize, or evaluate anything while deriving a type, and the engine's type caches stay valid. Under test a handler that writes engine state throws. ### Declaring a Value Kind: Values with Their Own Arithmetic Some values are not numbers, but they take part in arithmetic with their own rules: an element of a finite field, a residue class, a measurement with an uncertainty. A **value kind** lets an operator that constructs such values give their arithmetic to the engine. Two declarations make a kind: - a **kind type**, the type of the values; - an operator, the **head** that constructs the values, declared with an `arithmetic` field and with the kind type as its result type. The number rules are wrong for most kinds. In GF(8), `x + x` is `0`, not `2x`; for a measurement, `x - x` has an uncertainty and is not `0`. So the engine keeps the number rules away from any expression that holds a value of a kind: canonicalization, `evaluate()` and `simplify()` do not apply `x - x = 0`, `x/x = 1` or like terms to it. Only the hooks of the kind combine its values. #### The kind type A kind type is one of: - a **kind primitive**, a type name that the library defines: `residue`, the type of a residue class, `measurement`, the type of a `Measurement(value, error)`, and `quantity`, the type of a `Quantity(magnitude, unit)` (see [Measurements and Quantities](#measurements-and-quantities)); - a **nominal type whose definition is exactly `value`**, declared with `{ mint: false }`. This is the form you use for a kind of your own: ```js ce.declareType("gf8", "value", { mint: false }); ``` A kind type is a subtype of `value` and of `expression`, so a parameter typed `value`, `expression` or `any` accepts its values: `List`, `Hold`, `Equal`, and a symbol declared `value` can hold one. A kind type is disjoint from every other type except `value`, `expression`, `any` and `unknown`: a value of type `gf8` is never a number, a string or a collection, and a symbol declared `gf8` refuses a number. By default, `ce.declareType()` also declares a constructor of the same name (see [Type Constructors](/compute-engine/guides/types/#type-constructors)). A kind has its own head as its constructor, so its type must be declared with `{ mint: false }`: a minted `gf8(…)` would be a second head of the type with no arithmetic. The engine refuses an `arithmetic` field whose result type was declared without `{ mint: false }`. #### A complete example: GF(8) GF(8) is the field with 8 elements. An element is a polynomial of degree less than 3 over GF(2), held here as a 3-bit number: the sum of two elements is the bitwise XOR of their numbers, and the product is the product of the polynomials modulo $x^3 + x + 1$. ```js // The product of two elements of GF(8), each held as a 3-bit number: // polynomials over GF(2), reduced modulo x³ + x + 1. function gfMul(a, b) { let r = 0; for (let i = 0; i < 3; i++) if (b & (1 << i)) r ^= a << i; for (let i = 4; i >= 3; i--) if (r & (1 << i)) r ^= 0b1011 << (i - 3); return r; } // The number of a `GF8(k)` value, or `undefined` for any other expression const gf = (x) => x.operator === "GF8" && Number.isInteger(x.op1.re) ? x.op1.re & 7 : undefined; // Combine every `GF8` operand of a sum or a product into one fold function fold(ops, engine, step, unit) { const operands = []; ops.evaluated.forEach((x, i) => { if (gf(x) !== undefined) operands.push(i); }); if (operands.length < 2) return undefined; const k = operands.reduce((acc, i) => step(acc, gf(ops.evaluated[i])), unit); return [{ value: engine.function("GF8", [k]), operands }]; } ce.declareType("gf8", "value", { mint: false }); ce.declare("GF8", { signature: "(integer) -> gf8", arithmetic: { add: (ops, { engine }) => fold(ops, engine, (a, b) => a ^ b, 0), multiply: (ops, { engine }) => fold(ops, engine, gfMul, 1), // In characteristic 2, -x is x negate: (x) => (gf(x.evaluated) !== undefined ? x.evaluated : undefined), divide: (a, b, { engine }) => { const [x, y] = [gf(a.evaluated), gf(b.evaluated)]; if (x === undefined || y === undefined || y === 0) return undefined; let inverse = 1; while (gfMul(y, inverse) !== 1) inverse += 1; return engine.function("GF8", [gfMul(x, inverse)]); }, power: (base, exponent, { engine }) => { const x = gf(base.evaluated); const n = exponent.exact(); if (x === undefined || n.isInteger !== true || n.isNonNegative !== true) return undefined; let r = 1; for (let i = 0; i < n.re; i++) r = gfMul(r, x); return engine.function("GF8", [r]); }, }, }); ``` With these declarations: ```js ce.box(["Add", ["GF8", 3], ["GF8", 5]]).evaluate(); // ➔ GF8(6) ce.box(["Multiply", ["GF8", 3], ["GF8", 5]]).evaluate(); // ➔ GF8(4) ce.box(["Subtract", ["GF8", 3], ["GF8", 3]]).evaluate(); // ➔ GF8(0) ce.box(["Divide", ["GF8", 3], ["GF8", 5]]).evaluate(); // ➔ GF8(6) ce.box(["Power", ["GF8", 3], 2]).evaluate(); // ➔ GF8(5) ce.box(["Add", "x", ["GF8", 1], ["GF8", 2]]).evaluate(); // ➔ x + GF8(3) // `divide` declines a zero divisor, and `power` an exponent of 1/2: // the expression stays as it is ce.box(["Divide", ["GF8", 3], ["GF8", 0]]).evaluate(); // ➔ GF8(3) / GF8(0) ce.box(["Sqrt", ["GF8", 3]]).evaluate(); // ➔ sqrt(GF8(3)) // A list of values is a list of the kind type ce.box(["List", ["GF8", 1], ["GF8", 2]]).type; // ➔ list // No other operator accepts a value of the kind ce.box(["Sin", ["GF8", 3]]).isValid; // ➔ false ``` A symbol declared with the kind type is protected before it has a value: ```js ce.declare("g", "gf8"); ce.box(["Subtract", "g", "g"]).simplify(); // ➔ g - g ce.box(["Divide", "g", "g"]).simplify(); // ➔ g / g ``` A symbol declared `value`, or not declared, can be a number, and the number rules apply to it: `w/w` is `1`. Declare a symbol that will hold a value of a kind with the kind type. Two values of a kind written next to each other are a product, as two numbers are: `\operatorname{GF8}(3)\operatorname{GF8}(5)` parses to `GF8(3) · GF8(5)`. #### The hooks The `arithmetic` field is an `ArithmeticDefinition`. Every hook is optional. A hook that answers `undefined` declines, and the expression stays as it is. | Hook | Called for | Answers | | :--- | :--- | :--- | | `add(ops, context)` | a sum (`Add`, `Subtract`) with a value of the kind | a list of folds | | `multiply(ops, context)` | a product with a value of the kind | a list of folds | | `negate(x, context)` | `-x` | an expression | | `divide(a, b, context)` | `a / b` | an expression | | `power(base, exponent, context)` | `base ^ exponent`; also `Sqrt(x)` (exponent `1/2`), `Root(x, n)` (exponent `1/n`), `Square(x)` (exponent `2`) | an expression | | `compare(a, b, context)` | the comparisons, `Max`, `Min`, `ElementMax`, `ElementMin`, `Sort`, `Clamp` | `-1`, `0`, `1`, `'<='`, `'>='`, `'unordered'` or `undefined` | | `extremum(which, ops, context)` | `Max`, `Min`, `ElementMax`, `ElementMin`, `Clamp`, before `compare` | an expression | | `apply(operator, ops, context)` | another operator that accepted the value as a number (see `admittedAs`) | an expression | Three optional fields describe a value of the kind: - `isExact(value)` answers whether `value` is exact. The engine reads it for the rule that an inexact operand changes an `Indeterminate` answer to `NaN`: `Max(x, Indeterminate)` is `NaN` when `x` is inexact, and `Indeterminate` when `x` is exact. Without `isExact`, every value of the kind is exact. The library `Measurement` answers `false` for every value, and `Quantity` answers whether the magnitude is exact. - `isNaN(value)` answers whether `value` stands for no value, as the number `NaN` does. Such a value follows the rule of `NaN`: `Max`, `Min`, `ElementMax`, `ElementMin` and `Clamp` give `NaN`, it has no order (`Less` and `Greater` are `False`), and it is equal to no value, itself included (`Equal` is `False`, `NotEqual` is `True`). The `compare` hook is not called for it. Without `isNaN`, no value of the kind is `NaN`. The library `Measurement` answers `true` for a `NaN` nominal, and `Quantity` for a `NaN` magnitude: `Max(Quantity(NaN, m), 2 m)` is `NaN`. - `numericValue(value)` gives the number that `value` stands for, for a kind whose values stand for a number. The numeric accessors of the value read it: `re`, `im`, `bignumRe`, `bignumIm` and `valueOf()`. The library `Measurement` gives its nominal, so `Measurement(2.5, 0.1).re` is `2.5`. Without `numericValue`, or when it answers `undefined`, `re` is `NaN`. The fact queries (`isPositive`, `sgn`, `isInteger`, `isFinite`, …) do not read it: they are `undefined` for every value of a kind, so that no canonical rule of the numbers applies to it. **The operands.** `add`, `multiply` and `apply` receive the operands as a `KindOperands` object: - `ops.evaluated[i]` is the evaluated operand `i`; - `ops.written[i]` is the operand as it was written before evaluation, or `undefined` for a value that the fold of another kind made. Under `simplify()`, it is the operand as it was written before the simplification of the operands, so it is the same on the three routes (`evaluate()`, `.N()`, `simplify()`). A kind whose rule depends on how a term was written reads it here: `a - b` is canonically `Add(a, Negate(b))`, and the position of the `Negate` term follows the canonical order, so search for it; - `ops.exact(i)` is the exact value of operand `i`. Outside `.N()` it is `ops.evaluated[i]`. Under `.N()` it is the operand evaluated without the numeric approximation, when this does not run a side effect again. A kind whose values are exact reads `exact(i)` so that `(c + 1/3).N()` stays exact; - `ops.elements(i)`, at an operator whose parameter is a collection (`Mean`), is the list of the elements of operand `i`, enumerated once by the engine. It is `undefined` for an operand that is not a collection, and for a collection that the engine did not enumerate: one larger than `engine.maxCollectionSize`, one of unknown or infinite size, or one whose element type cannot hold a value of a kind. `negate`, `divide`, `power` and `compare` receive each operand as a `KindOperand`, with the fields `evaluated`, `written` and `exact()`. **A payload that is costly to read.** The hooks of a kind receive the kind value as a call of its head, so a hook that reads a costly payload reads it again at each operation. `decodedPayload(expr, decode)` runs `decode` once for each boxed expression and returns the stored result after that; boxed expressions are immutable, so the result never needs to be refreshed. Define `decode` once (at the top level of a module), not as a closure in the call: `const { lo, hi } = decodedPayload(x, decodeEnclosure)`. The function is exported by the package: `import { decodedPayload } from "@cortex-js/compute-engine"`. The `context` has three fields: - `engine`; - `numericApproximation`, which is `true` under `.N()`. The hook makes its own result numeric under `.N()`: a kind whose values are exact computes the exact value anyway, and a kind whose values hold a float returns the numeric form. The engine does not apply `.N()` to the result of a hook; - `tolerance`, for the `compare` hook only. When the caller compares within a tolerance, it is that tolerance: `.is()` gives its `tolerance` argument, or `engine.tolerance` when the call has none. For every other comparison (the relational operators, `isEqual()`, `isLess()`, `Max`, `Min`, `Sort`, …) it is `undefined`, and the hook applies its own rule: usually, two exact values are compared exactly and a float within `engine.tolerance`. **Folds.** `add` and `multiply` answer a list of **folds**. A fold (`KindFold`) names the operands it combined, by their index in `ops.evaluated`, and the value they make: `{ value, operands: [0, 2] }`. A hook can answer several folds (one for each modulus, one for each dimension), and must not name an operand twice. A fold can name an operand of another kind only to make it part of its own value. The value of a fold can be a value of another kind or a number. An empty list folds nothing. **How a sum is evaluated.** For a sum (a product is the same): 1. The operands are evaluated. 2. The kinds of the operands are listed in the order of their first occurrence, by the head of each value. 3. Each kind, in that order, gets one call of its `add` hook, with the operands that are left: the operands that no fold took, and the values that the folds of the kinds before it made. Each fold replaces the operands it names. A kind is not called a second time: a hook that must repeat its rule until nothing changes does so before it answers. 4. The operands that hold no value of a kind, and the folds whose value is a number, are added as an ordinary sum. The other operands (the values of a kind, and the symbols declared with a kind type) are placed beside that sum, with no other change: like terms are not collected with them, and a zero or an infinity does not absorb them. So `x + x + GF8(1) + GF8(2)` is `2x + GF8(3)`. `negate`, `divide` and `power` are called for the first kind among the operands whose hook answers. When no hook answers, the expression stays as it is. **Rules for a hook.** - The operands of `add` and `multiply` are in canonical order: the canonical form sorts a sum and a product. So `multiply` must be commutative. A kind whose product is not commutative (quaternions, matrices) cannot be declared with this field. - A hook can make and evaluate expressions with `engine`, including expressions with values of another kind; the engine then calls the hooks of that kind. A hook must not call `.simplify()`: this can cause an infinite recursion. - An error thrown by a hook is not caught, as for an `evaluate` handler. - A fold that names an operand index that does not exist, or names an operand twice, is an error in the definition of the kind, and the engine throws. - A kind that gives `add` must also give `negate`, because `a - b` is `Add(a, Negate(b))`: without `negate`, a difference is never folded. The engine refuses the declaration otherwise. **The methods.** `.add()`, `.mul()`, `.div()` and `.pow()` call the same hooks: `ce.box(["GF8", 3]).mul(ce.box(["GF8", 5]))` is `GF8(4)`. These methods do not evaluate their operands, so a hook receives what the caller built. `.neg()` does not call `negate`: `GF8(3).neg()` is `-GF8(3)`, and `Negate(GF8(3))` calls `negate` when it is evaluated. So `a.sub(b)` gives `add` the operands `a` and `Negate(b)`: the `GF8` hooks above do not read a `Negate(…)` operand, and `GF8(3).sub(GF8(3))` stays `-GF8(3) + GF8(3)`, while `["Subtract", ["GF8", 3], ["GF8", 3]]` evaluates to `GF8(0)`. A kind whose `add` reads a `Negate(…)` operand folds `a.sub(b)` too (`ResidueClass` does). #### Where a value of a kind is accepted A kind type is not a number type, so a parameter typed with a number type does not accept a value of a kind by subtyping. A value of a kind is accepted: 1. **At the arithmetic operators**, when a head of the kind type gives the hook that the operator calls: `Add` and `Subtract` need `add`, `Multiply` needs `multiply`, `Negate` needs `negate`, `Divide` needs `divide`, and `Power`, `Sqrt`, `Root` and `Square` need `power`. A kind without `power` is refused by `Square(x)`. The rule also applies to the elements of a collection: with `L: list`, `L + L` adds the lists element by element. 2. **At a parameter that names the kind type or accepts `value`**: a parameter typed `gf8`, `value`, `expression`, `any` or `unknown`. The operator runs its own handler, which sees the value as it is: `List`, `Hold`, `Equal`, `Max`, the comparisons. 3. **As a number**, when the kind type says so with `admittedAs`: - `"none"` (the default): no other parameter accepts it; - `"real"`: every parameter that accepts a `real` accepts it (`real`, `number`, `complex`, `complex | infinity`). A parameter typed `integer` or a range of numbers does not; - `"number"`: every parameter that accepts a `number` or a `real` accepts it. Use it for a kind whose values can be complex. Admission as a number is not subtyping: `umeasurement` is not a subtype of `real`, and a symbol declared `real` refuses a value of the kind. **A function with a body** (a function literal, or a `ce.declare()` with a MathJSON or LaTeX body) accepts a value of a kind only when its parameter names the kind type, or is typed `value`, `any` or `unknown`: the third rule above does not apply to it. The body was canonicalized with the declared type of its parameter, and the canonical form of `x/x` is `1` for a parameter typed `real`, so the body cannot be used for a value of a kind. With `f: (real) -> real`, `f(UserMeasurement(2, 0.1))` is an `incompatible-type` error. Declare the parameter `real | umeasurement` instead. An untyped parameter (`unknown`) accepts the value, but the body may already have changed `x/x` to `1`. The element of a collection of values of a kind has the kind type, so the variable of `Map`, `Sum` and the other operators that bind a variable over such a collection has it too, and its body is protected: mapping `z ↦ z/z` over `[GF8(3), GF8(5)]` divides each element by itself through `divide` and gives `[GF8(1), GF8(1)]`. An expression is checked when it is boxed. An expression boxed before the head of the kind was declared is not checked again: with `r: gf8` declared before `GF8`, `r + 1` boxed before `GF8` is an `incompatible-type` error, and stays one. #### Comparisons A kind that has an order gives `compare`. It answers: - `-1`, `0` or `1`, the order of the two values; - `'unordered'` when the two values are known but have no order, as a complex number has none. The library `Measurement` answers it for a nominal that is complex or `NaN`. The four order operators (`Less`, `LessEqual`, `Greater`, `GreaterEqual`) then answer `False`, as they do for a complex number; `Equal` and `NotEqual` ask the `eq` handler of the operator definition of each head; `Max`, `Min` and `Sort` stay unevaluated; - `'<='` when the first value is less than or equal to the second, and the hook cannot say which: two intervals that touch, such as `Enclosure(0, 1)` and `Enclosure(1, 2)`, hold reals that are in this order, and they may hold the same real `1`. `LessEqual` then answers `True` and `Greater` answers `False`; `Less` and `GreaterEqual` stay unevaluated; `Equal` and `NotEqual` ask the `eq` handlers. `'>='` is the mirror answer. `Sort`, and `Max` and `Min` without an `extremum` hook, need a total order, and read these two answers as `undefined`; - `undefined` when the two values cannot be compared (a value of another kind, a symbol). A kind whose values can overlap gives `extremum(which, ops, context)` too. `Max`, `Min`, `ElementMax`, `ElementMin` and `Clamp` ask it BEFORE they order the operands with `compare`, for two operands at a time (`which` is `'max'` or `'min'`; `ops` is a `KindOperands` object). The hook can answer a value that is not one of the operands. When it answers `undefined`, or when the kind has no `extremum`, the engine orders the operands with `compare`. For example, the maximum of two intervals is the interval of the larger ends: ```js ce.declareType("ivl", "value", { mint: false }); // The endpoints of an `Ivl(lo, hi)`, or of a number read as a point const ends = (x) => x.operator === "Ivl" ? [x.op1.re, x.op2.re] : x.isNumberLiteral ? [x.re, x.re] : undefined; ce.declare("Ivl", { signature: "(real, real) -> ivl", arithmetic: { admittedAs: "real", compare: (a, b) => { const [x, y] = [ends(a.evaluated), ends(b.evaluated)]; if (!x || !y) return undefined; if (x[1] < y[0]) return -1; if (x[0] > y[1]) return 1; if (x[1] === y[0]) return "<="; if (x[0] === y[1]) return ">="; return undefined; }, extremum: (which, ops, { engine }) => { const xs = ops.evaluated.map(ends); if (xs.some((x) => !x)) return undefined; const pick = which === "max" ? Math.max : Math.min; return engine.function("Ivl", [ engine.number(pick(...xs.map((x) => x[0]))), engine.number(pick(...xs.map((x) => x[1]))), ]); }, }, }); ce.box(["LessEqual", ["Ivl", 0, 1], ["Ivl", 1, 2]]).evaluate(); // ➔ True ce.box(["Less", ["Ivl", 0, 1], ["Ivl", 1, 2]]).evaluate(); // ➔ unevaluated ce.box(["Max", ["Ivl", 0, 2], ["Ivl", 1, 3]]).evaluate(); // ➔ Ivl(1, 3) ``` One order is used for every comparison: the six relational operators, `Max`, `Min`, `ElementMax`, `ElementMin`, `Sort`, `Clamp`, and the methods `isEqual()`, `isLess()`, `isGreater()` and the others. `Max` and `Min` keep the first operand of a tie, and `Clamp(x, lo, hi)` returns `x` when `x` is equal to a bound. - A comparison of the same expression on both sides is decided first, for every kind: `a < a` is `False` and `a ≤ a` is `True`. - An order comparison (`Less`, …) that is not decided stays unevaluated. - `Equal` and `NotEqual` use `compare` when it answers. If not, they use the `eq` handler of the operator definition of each head, if there is one. If no head has an `eq` handler: two values whose types are disjoint are not equal (`GF8(1) == "abc"` is `False`), two identical values are equal, and any other pair stays unevaluated. So, for a kind without `compare`, `GF8(1) == GF8(2)` stays unevaluated: the engine does not know that the two values are different elements of the field, and an answer of `False` would be wrong for a kind whose values can be equal without being identical. - A kind without `compare` has no order: `GF8(1) < GF8(2)` stays unevaluated (typed `boolean`), and `Max(GF8(1), GF8(2))` is an `incompatible-type` error. - `.is()` uses the `Equal` rule for a value of a kind that has `compare`, and `.isSame()` otherwise. `compare` receives the tolerance of `.is()` in `context.tolerance`: the `tolerance` argument of the call, or `engine.tolerance`. #### Other operators: `apply` An operator is an **apply site** for a kind when a value of the kind is one of its operands only because of `admittedAs` (the third rule above), and the operator has an `evaluate` handler and is not lazy. At an apply site, the engine calls the `apply` hook of the kind in place of the `evaluate` handler of the operator. When `apply` answers `undefined`, the expression stays unevaluated. When the kind has no `apply`, the handler of the operator runs and sees the value as it is. An operator that accepts the kind by its type (its parameter names the kind type, or is `value` or `any`) is never an apply site: its handler runs. Nor are the arithmetic operators and the comparisons, which use the other hooks. A function applied to a collection is applied to each element, so `apply` is called for each element: `Sin([U1, U2])` is a list of two results of `apply`. At an operator whose parameter is a collection (`Mean`), `apply` receives the collection, and `ops.elements(i)` gives its elements. The numeric facts of a value of a kind (`isPositive`, `sgn`, `isInteger`, …) are `undefined`, and `re` is `NaN` unless the kind gives `numericValue`. So the canonical rules that read them do not apply, and only `apply` decides. The example below is a measurement: a nominal value and a standard uncertainty, propagated to first order. It is accepted as a number (`admittedAs: "number"`), has an order (`compare`, by the nominal value), and gives `apply` for the functions whose derivative it knows. ```js // A measurement `UserMeasurement(value, sigma)`: a nominal value and a // standard uncertainty. A number operand has an uncertainty of 0. const parts = (x) => x.operator === "UserMeasurement" ? { value: x.op1, sigma: x.op2 } : x.isNumberLiteral ? { value: x, sigma: x.engine.Zero } : undefined; const make = (engine, value, sigma, numericApproximation) => engine.function("UserMeasurement", [ value.evaluate({ numericApproximation }), sigma.evaluate({ numericApproximation }), ]); // √(a² + b²) const quadrature = (engine, a, b) => engine.function("Sqrt", [ engine.function("Add", [ engine.function("Square", [a]), engine.function("Square", [b]), ]), ]); // Combine every measurement and number operand of a sum or a product into // one fold, when at least one of them is a measurement function fold(ops, { engine, numericApproximation }, step) { const operands = []; ops.evaluated.forEach((x, i) => { if (parts(x)) operands.push(i); }); if (operands.length < 2) return undefined; if (!operands.some((i) => ops.evaluated[i].operator === "UserMeasurement")) return undefined; const p = operands .map((i) => parts(ops.evaluated[i])) .reduce((a, b) => step(a, b, engine)); return [ { value: make(engine, p.value, p.sigma, numericApproximation), operands }, ]; } // The derivatives of the functions that this kind propagates an error through const DERIVATIVE = { Sin: (x) => ["Cos", x], Cos: (x) => ["Negate", ["Sin", x]], Exp: (x) => ["Exp", x], }; ce.declareType("umeasurement", "value", { mint: false }); ce.declare("UserMeasurement", { signature: "(number, real) -> umeasurement", arithmetic: { admittedAs: "number", // A sum: the nominal values add, the uncertainties add in quadrature add: (ops, context) => fold(ops, context, (a, b, engine) => ({ value: engine.function("Add", [a.value, b.value]), sigma: quadrature(engine, a.sigma, b.sigma), })), // A product: the first-order propagation of two independent errors multiply: (ops, context) => fold(ops, context, (a, b, engine) => ({ value: engine.function("Multiply", [a.value, b.value]), sigma: quadrature( engine, engine.function("Multiply", [b.value, a.sigma]), engine.function("Multiply", [a.value, b.sigma]) ), })), negate: (x, { engine, numericApproximation }) => { const p = parts(x.evaluated); if (!p) return undefined; const value = engine.function("Negate", [p.value]); return make(engine, value, p.sigma, numericApproximation); }, // The order of the nominal values compare: (a, b) => { const [x, y] = [parts(a.exact())?.value, parts(b.exact())?.value]; if (!x?.isNumberLiteral || !y?.isNumberLiteral) return undefined; if (x.isEqual(y) === true) return 0; const less = x.isLess(y); return less === undefined ? undefined : less ? -1 : 1; }, // Every other operator that accepted the value as a number apply: (operator, ops, { engine, numericApproximation }) => { if (ops.evaluated.length !== 1) return undefined; const p = parts(ops.exact(0)); if (!p) return undefined; // A function whose value jumps uses the exact nominal value if (operator === "Floor" || operator === "Ceil" || operator === "Round") { const value = engine.function(operator, [p.value]); return make(engine, value, p.sigma, numericApproximation); } const d = DERIVATIVE[operator]; if (!d) return undefined; // The expression stays unevaluated const sigma = engine.function("Multiply", [ engine.function("Abs", [engine.box(d(p.value))]), p.sigma, ]); const value = engine.function(operator, [p.value]); return make(engine, value, sigma, numericApproximation); }, }, }); ``` With these declarations, and `U` for `["UserMeasurement", 2, 0.1]`: ```js ce.box(["Add", U, ["UserMeasurement", 3, 0.2]]).evaluate(); // ➔ UserMeasurement(5, 0.2236…) ce.box(["Subtract", U, U]).simplify(); // ➔ UserMeasurement(0, 0.1414…): the two errors are independent ce.box(["Multiply", 2, U]).evaluate(); // ➔ UserMeasurement(4, 0.2) ce.box(["Sin", U]).evaluate(); // ➔ UserMeasurement(sin(2), 0.0416…), through `apply` ce.box(["Floor", ["UserMeasurement", 2.5, 0.1]]).evaluate(); // ➔ UserMeasurement(2, 0.1) ce.box(["Sin", ["List", U, ["UserMeasurement", 1, 0.1]]]).evaluate(); // ➔ [UserMeasurement(sin(2), 0.0416…), UserMeasurement(sin(1), 0.0540…)] ce.box(["Tan", U]).evaluate(); // ➔ tan(UserMeasurement(2, 0.1)): `apply` does not know `Tan` ce.box(["Less", U, 3]).evaluate(); // ➔ True ce.box(["Equal", U, 2]).evaluate(); // ➔ True ce.box(["Max", U, ["UserMeasurement", 3, 0.5]]).evaluate(); // ➔ UserMeasurement(3, 0.5) ``` `Sqrt(U)` and `Square(U)` are `incompatible-type` errors, because this kind has no `power` hook (first rule above). An operator of your own declared over `real` with an `evaluate` handler is an apply site too. With `h: (real) -> real`, `h(U)` calls `apply`, which does not know `h`, so `h(U)` stays unevaluated. An operator that handles the kind itself names the kind type in its parameter: with `h2: (real | umeasurement) -> real | umeasurement`, the handler of `h2` runs. #### Result types The type of an expression with a value of a kind is found without evaluating it. For an arithmetic operator, an apply site, or an operator that names the kind type in a parameter, the default type is the kind type: `x + GF8(1)` is typed `gf8`, `Sin(U)` is typed `umeasurement`, and `GF8(3) · (1, 2)` is a `tuple`. An operand that may hold a number keeps the number arm: with `u: real | umeasurement`, `-u` is typed `real | umeasurement`. An operator whose result type is not a number type keeps it: `Less(U, 3)` is a `boolean`. `Max` and `Min` with a kind that has `compare` are typed with the union of the types of their operands: `Max(U, 3)` can be `3` or a `umeasurement`. A kind whose hooks can give another type gives `resultType`. It receives the name of the operator and the types of the operands (without their shapes: a `list` operand is seen as `uq`), and answers a type, a type string, or `undefined` for the default. The operator is `"Add"` for a sum or a difference, and `"Power"` for `Sqrt`, `Root` and `Square`, with the type of the exponent among the operand types. For example, a quantity whose quotient by a quantity of the same unit is a number: ```js ce.declareType("uq", "value", { mint: false }); ce.declare("Q", { signature: "(real, string) -> uq", arithmetic: { divide: (a, b, { engine }) => { const [x, y] = [a.evaluated, b.evaluated]; if (x.operator !== "Q" || y.operator !== "Q") return undefined; if (x.op2.string !== y.op2.string) return undefined; return engine.number(x.op1.re / y.op1.re); }, resultType: (operator) => (operator === "Divide" ? "number | uq" : undefined), }, }); ce.box(["Divide", ["Q", 6, "'m'"], ["Q", 3, "'m'"]]).type; // ➔ number | uq ce.box(["Divide", ["Q", 6, "'m'"], ["Q", 3, "'m'"]]).evaluate(); // ➔ 2 ``` #### Fields that belong to the kind type A symbol declared with a kind type has no head to ask, so three fields belong to the kind TYPE, not to the head: `admittedAs`, `resultType` and `compile.type`. Several heads can have the same kind type (the library `ResidueClass` and your own head that returns `residue`). The heads of one kind type that are declared at the same time must agree on these fields: a head gives the same value or leaves the field out, and then uses the value that another head gave. `admittedAs` is compared by value; `resultType` and `compile.type` are compared by identity, so two heads share one function or object. The engine refuses a declaration that does not agree. The other fields (`add`, `multiply`, `negate`, `divide`, `power`, `compare`, `apply` and `compile.value`) belong to the head. A value calls the hooks of its own head: `MyMod(2, 4) + ResidueClass(1, 4)` stays as it is, because each head folds only its own values. #### The declaration checks `ce.declare()` refuses an `arithmetic` field, with an error that names the rule, when: - the result type of an arm of the signature is not a kind type. It can be a kind type, a union of kind types, or one of these with the arms `nan`, `missing`, `nothing` or `error`. `-> residue | integer` is refused: the engine would keep the number rules away from every integer; - the result type is a nominal type that is defined as anything other than exactly `value`, that has type parameters (`gf<7>`), that is defined as another nominal type, or that was declared without `{ mint: false }`; - the field gives `add` but not `negate`; - the fields of the kind type do not agree with another head of the type; - the kind type is a library kind type (`residue`) and the field gives a `resultType` or a `compile.type` that the library left out, or another `admittedAs`. Such a field would change the arithmetic of every value and every symbol of the library type. After a kind is declared, its type can be declared again only as a nominal type defined as exactly `value`, with no type parameters and with `{ mint: false }` (a `DeclareType` statement that runs again); any other declaration of that type name is refused. #### Scopes The kind types are engine-wide, as all types are. The hooks belong to the operator definition: a head declared with a kind in a nested scope keeps its hooks for its values after the scope is popped, and a head of the same name declared in another scope with other hooks does not change the values of the first one. When a head is declared again without `arithmetic`, its values have no hooks any more; its kind type remains a kind type, so its values and symbols are still protected from the number rules. A checkpoint restore puts the kinds back as they were at the checkpoint. #### Compiling a value of a kind By default, `compile()` declines an expression that holds a value of a kind, on every target: the number rules must never compute `c - c` as `0`. An expression that may hold a number or a value of a kind (a parameter typed `number | gf8`) compiles as a number. A kind type can give a compiled representation with `compile` in its `arithmetic` field (an `ArithmeticCompile`). `compile.type` belongs to the kind type; `compile.value` gives the source of a literal value of the head. Each hook receives `context.language` and answers `undefined` for a target it does not support: ```js ce.declare("GF8", { signature: "(integer) -> gf8", arithmetic: { // … the hooks of the example above … compile: { // The representation of the type `gf8`: `{ gf8: k }` in JavaScript type: { lowered: ({ language }) => language === "javascript" ? "gf8" : undefined, add: (args, compile, { language }) => language === "javascript" ? `({ gf8: ${args.map((x) => `(${compile(x)}).gf8`).join(" ^ ")} })` : undefined, // The check of a value passed for a symbol of type `gf8` entry: (value) => Number.isInteger(value?.gf8), }, // A literal `GF8(k)` value: (expr) => `({ gf8: ${expr.op1.re & 7} })`, }, }, }); ce.declare("g", "gf8"); const f = compile(ce.parse("g + \\operatorname{GF8}(2)"), { to: "javascript" }); f.run({ g: { gf8: 1 } }); // ➔ { gf8: 3 } f.run({ g: 1 }); // ➔ throws: the entry check refused the value f.freeSymbolTypes.g; // ➔ { type: "gf8", lowered: "gf8", … } f.valueKinds; // ➔ ["gf8"] ``` The fields of `compile.type`: - `lowered(context)`: the name of the representation on the target, as `freeSymbolTypes[…].lowered` reports it. The name must not be one that the JavaScript runner reads as a number, a complex number or a list (`number`, `real`, `integer`, `complex`, `array`, `list`, `point`, `color`): a result of a kind is returned as the code makes it, and the host would read it wrongly; - `add(args, compile, context)` and `multiply(…)`: a sum and a product with an operand of the kind. A number operand beside a value of the kind is the hook's to convert; - `apply(operator, args, compile, context)`: the other arithmetic operators, the comparisons and the other operators with an operand of the kind. The structural operators (`If`, `Block`, `List`, `Tuple`, `Assign`, `Hold`, a call of a user function) do not call it: they pass the representation as it is; - `entry(value, context)`: the check of a host value given for a symbol of the kind type, when the compiled code runs; - `shippable` and `hostNames`: see below. An operation that the contract does not define declines with a message that names the kind type: with the contract above, `g · GF8(2)` declines because there is no `multiply`. Limits: - An expression that holds a collection of values of a kind (`L + L` with `L: list`, or `[GF8(1), GF8(2)]`) declines on every target: `entry` and `lowered` describe one value, not a collection. - Code compiled with a kind contract cannot be stored and loaded again with `createJavaScriptRuntime().load()`: the `entry` check is a function of the host that compiled the code, and stored code cannot carry it. Run it with the `run()` of the compilation result. `CompilationResult.valueKinds` names the kind types of the code, and `entryPlan.kinds` the symbols that need an `entry` check. - The result is `shippable` (it can be sent to a worker of the kernel pool as text) only when the contract of every kind in the code says `shippable: true`, lists no `hostNames`, and no input of the code is a value of a kind (its `entry` check could not run in the worker). - Of the library kinds, only `Enclosure` gives a `compile` contract, for the `interval-js` target (see [Enclosures](#enclosures)). An expression with a residue class, a measurement or a quantity declines on every target. #### Residue classes A residue class, an element of $\mathbb{Z}/n\mathbb{Z}$, is the library kind of the type `residue`: `ResidueClass(k, n)` declares its arithmetic with this field, with `admittedAs: "none"` and no `compare` (classes have no order: `ResidueClass(1, 5) < ResidueClass(2, 5)` stays unevaluated, and `Max` of two classes is an error). A head of your own can return `residue` too, and give its own arithmetic. For example, a head for the classes of $\mathbb{Z}/n\mathbb{Z}$ whose `add` and `multiply` give one fold for each modulus: ```js ce.declare("MyMod", { signature: "(integer, integer) -> residue", arithmetic: { add: (ops, { engine }) => { /* group the `MyMod` operands by modulus; one fold for each modulus */ }, multiply: (ops, { engine }) => { /* the same, for a product */ }, negate: (x, { engine }) => { /* the class of -k */ }, }, }); ``` `x + MyMod(2, 4) + MyMod(1, 4)` is then `x + MyMod(3, 4)`, and `MyMod(2, 4) - MyMod(2, 4)` is `MyMod(0, 4)`. The rules of the type `residue` apply to `MyMod`: `admittedAs` is `"none"`, and a head of your own cannot give `residue` a `resultType` or a `compile.type`. Do not extend `Add` or `Multiply` with an `evaluate` handler and `{ extend: true }` to give a kind its arithmetic: that replaces the library handler for the whole engine, and `x + x + 1 + 2` then stops combining to `2x + 3`. A `type` handler is not used for the protection from the number rules: an operator whose values are of a kind declares the kind type in its signature, even when it has a `type` handler. #### Measurements and Quantities `Measurement` and `Quantity` are two more library kinds. Use them as models for a kind of your own: each one uses the hooks above, and nothing else. **`Measurement(value, error)`** has the kind type `measurement`. It is a nominal value with a standard uncertainty. - It is accepted where a number is accepted (`admittedAs: "number"`), so `Sin`, `Floor`, `Hypot` and `Mean` accept it and call its `apply` hook. `apply` computes the value of the operator at the nominal, and propagates the uncertainty to first order: `f(a) ± |f′(a)|·σ`, and `√(Σ |∂f/∂xᵢ|²·σᵢ²)` for several uncertain operands. It uses the modulus of each partial derivative, so a complex nominal propagates by the same rule. - A propagated uncertainty of 0 gives the bare value: `Cos(0 ± 0.1)` is `1`, and `Floor(2.5 ± 0.1)` is `2`. So the kind declares the result type `number | measurement` (`resultType`) for the operators where this can occur. - `apply` answers `undefined`, and the call stays unevaluated, when the propagation has no answer: an infinite value or slope at the nominal (`Sqrt(0 ± 0.1)`, `Arcsin(1 ± 0.1)`), a jump of a step function within one σ of the nominal (`Floor(2.95 ± 0.1)`), an operator whose derivative the engine does not know (`Zeta`, a user operator `h: (real) -> real`), and an operator that uses an operand more than once in its formula (`Binomial`, `Determinant`). - A complex nominal at a parameter that is real (`Floor`, `Mod`, `Hypot`) is an `incompatible-type` error when the call is evaluated, not when it is boxed: the type `measurement` does not tell a real nominal from a complex one. - `compare` orders two measurements by their nominals, and answers `'unordered'` for a complex nominal. `isExact` answers `false`, and `numericValue` gives the nominal, so `Measurement(2.5, 0.1).re` is `2.5`. - The uncertainties of two occurrences of one measurement are independent: `M − M` is `0 ± 0.141` for `M = 1 ± 0.1`, and `Sin(M)^2 + Cos(M)^2` simplifies to `1 ± 0.13`. **`Quantity(magnitude, unit)`** has the kind type `quantity`. It is a value with a physical unit. - It is accepted only at the arithmetic operators, the comparisons, the operators that name `quantity` in their parameter type and the operators with a `value` parameter (`admittedAs: "none"`). The 24 trigonometric functions and `Abs` name `quantity`: `Sin(30 deg)` is `1/2`, and `Abs(-2 m)` is `2 m`. Every other operator refuses a quantity: `Floor(2 m)` and `Mean([2 m, 4 m])` are `incompatible-type` errors. - `add` makes one fold for each physical dimension, in the unit with the largest scale: `x + 2 m + 30 cm + 4 s` is `x + 2.3 m + 4 s`. `resultType` answers `number | quantity` for `Multiply`, `Divide` and `Power`, since a product whose unit has no dimension is a plain number (`6 m / 3 m` is `2`). - `compare` converts the two values to the finer of their units, and compares the magnitudes there; two values of different dimensions have no order. - The value of a physical constant (`SpeedOfLight`) is a quantity. **Reading how a term was written.** A temperature with an offset (`degC`, `degF`) shows why a hook reads `ops.written[i]`. A difference of two temperatures is a temperature interval: `20 degC − 5 degC` is `15 K`. A sum of two temperatures adds their absolute values: `20 degC + 5 degC` is `571.3 K`. The negation `−(5 degC)` is not the temperature `−5 degC`, so the `negate` hook of `Quantity` declines a temperature with an offset, and the term stays `Negate(5 degC)`. The `add` hook then reads the sign of each term from the operand as it was written. A kind of your own can do the same: ```js // A temperature in degrees Celsius. A difference of two temperatures is a // number of kelvins. const isCelsius = (x) => x?.operator === "Celsius"; ce.declareType("celsius", "value", { mint: false }); ce.declare("Celsius", { signature: "(real) -> celsius", arithmetic: { // The negation of a temperature is not a temperature: decline, so that // the term of a difference stays `Negate(Celsius(…))` on every route // (`evaluate()`, `simplify()`, `.N()`) negate: () => undefined, // Fold `a - b`: `Add(a, Negate(b))`, in canonical order add: (ops, { engine, numericApproximation }) => { const plus = []; const minus = []; ops.evaluated.forEach((x, i) => { const w = ops.written[i] ?? x; if (w.operator === "Negate" && isCelsius(w.op1)) minus.push({ i, t: w.op1 }); else if (isCelsius(w)) plus.push({ i, t: w }); }); if (plus.length !== 1 || minus.length !== 1) return undefined; const value = engine .function("Subtract", [plus[0].t.op1, minus[0].t.op1]) .evaluate({ numericApproximation }); return [{ value, operands: [plus[0].i, minus[0].i] }]; }, }, }); ce.box(["Subtract", ["Celsius", 20], ["Celsius", 5]]).evaluate(); // ➔ 15 ce.box(["Add", "x", ["Subtract", ["Celsius", 20], ["Celsius", 5]]]).evaluate(); // ➔ x + 15 ce.box(["Negate", ["Celsius", 5]]).evaluate(); // ➔ -Celsius(5) ce.box(["Add", ["Celsius", 20], ["Celsius", 5]]).evaluate(); // ➔ Celsius(5) + Celsius(20): this hook does not fold a sum ``` When `negate` gives a value for such a term, the sum receives that value in `ops.evaluated[i]`, and `ops.written[i]` still holds `Negate(…)`. This is the same under `evaluate()`, `.N()` and `simplify()`: `simplify()` gives the hook the term as it was written before the simplification of the operands. So a kind whose `negate` gives `Celsius(−t)` also folds `Celsius(20) − Celsius(5)` to `15` on the three routes. #### Enclosures `Enclosure(lo, hi)` is one more library kind, with the kind type `enclosure`. It is a closed interval that holds ONE real number: the value is somewhere in `[lo, hi]`. It is not the set `Interval(lo, hi)`: `Abs(Enclosure(-1, 2))` is `Enclosure(0, 2)`, while `Abs` of the set is its cardinality. Its LaTeX is `\mathrm{Enclosure}(a, b)`. ```js ce.box(["Add", ["Enclosure", 0, 1], ["Enclosure", ["Rational", 1, 3], 2]]).evaluate(); // ➔ Enclosure(1/3, 3) ce.box(["Sin", ["Enclosure", 0, "Pi"]]).evaluate(); // ➔ Enclosure(0, 1) ce.box(["Sin", ["Enclosure", 0, 1]]).evaluate(); // ➔ Enclosure(0, sin(1)) ce.box(["Sin", ["Enclosure", 0, 1]]).N(); // ➔ Enclosure(0, 0.8414709848078966) ce.box(["Divide", 1, ["Enclosure", 0, 1]]).evaluate(); // ➔ Enclosure(1, +oo) ce.box(["Ln", ["Enclosure", -2, -1]]).evaluate(); // ➔ Indeterminate: the image is empty ``` - **The endpoints.** The canonical form orders them (`Enclosure(2, 1)` is `Enclosure(1, 2)`), and reads one operand as a point (`Enclosure(3)` is `Enclosure(3, 3)`) or converts a set `Interval` (`Enclosure(Interval(0, 1))` is `Enclosure(0, 1)`). An endpoint can be a number, `+oo`, `-oo`, or an exact constant such as `Pi` or `Sin(1)`; an endpoint with a free variable (`Enclosure(a, 2)`) is kept, and the hooks leave such a value unevaluated until `a` has a value. A `NaN` endpoint, the same infinity twice and an empty set give the indeterminate value. An endpoint that is complex, or is a value of a kind, is an `incompatible-type` error. - **It is accepted where a real is accepted** (`admittedAs: "real"`): every numeric function with a `real` parameter is an apply site, and `apply` gives the image of the function over the interval, with the critical points of the function (`Sin`, `Cos`, `Tan`, `Abs`, `Gamma`, …) and its poles: a pole inside gives the whole line `Enclosure(-oo, +oo)`, a pole or a domain boundary at an endpoint a half-line, and a domain that the interval does not reach the indeterminate value. The trigonometric functions read the angle in `ce.angularUnit`. An operator that the hook does not know (`Factorial`) stays unevaluated. - **Exact and numeric endpoints.** Under `evaluate()`, when the endpoints are exact, the result has exact endpoints. Under `.N()`, or with a float endpoint, the endpoints are converted to machine doubles OUTWARD (the largest double below `lo`, the smallest double above `hi`), and the interval library of the engine computes the image: `Enclosure(0, Pi).N()` is `Enclosure(0, 3.1415926535897936)`. The endpoints are doubles at every `ce.precision`. They are rigorous for the rational operations (`+`, `−`, `×`, `/`, integer powers, `Abs`, the step functions); for the transcendental functions the outward step is the smallest move, and can miss the true value by one unit in the last place. - **Two operands are independent.** `Enclosure(1, 2) − Enclosure(1, 2)` is `Enclosure(-1, 1)`, not `0`: the two intervals may hold two different reals. - **The order.** `compare` answers `-1` or `1` for two intervals that do not overlap, `'<='` or `'>='` for two intervals that touch, and `undefined` for two intervals that overlap: `Enclosure(0, 1) < Enclosure(2, 3)` is `True`, `Enclosure(0, 1) <= Enclosure(1, 2)` is `True`, and `Enclosure(0, 2) < Enclosure(1, 3)` stays unevaluated. `Enclosure(1, 1) = 1` is `True`. The `extremum` hook gives the interval of the larger (or smaller) ends: `Max(Enclosure(0, 2), Enclosure(1, 3))` is `Enclosure(1, 3)`. - **The result types.** The image of `Sin`, `Cos`, `Abs` and the other functions whose image is never empty is typed `enclosure`; a sum or a product is `enclosure` when no operand can be infinite (`x + 1` with `x: enclosure`), and `enclosure | nan` otherwise; every other operator is `enclosure | nan`, because its image can be empty. **An example of a kind that compiles.** `Enclosure` gives a `compile` contract for the `interval-js` target, and none for the other targets, which decline it. An enclosure is the `{ lo, hi }` interval of that target, and the hooks emit, for each operation, the same function of the interval library that `.N()` calls, so the compiled code and `.N()` give the same doubles: ```js ce.declare("x", "enclosure"); const f = compile(ce.parse("\\sin(x) + 1"), { to: "interval-js" }); f.freeSymbolTypes.x; // ➔ { type: "enclosure", lowered: "interval", … } f.run({ x: { lo: 0, hi: 1 } }); // ➔ { kind: "interval", value: { lo: 1, hi: 1.841470984807897 } } f.run({ x: { lo: 1, hi: 0 } }); // ➔ throws: the entry check refused the value compile(ce.parse("x + 1"), { to: "javascript", fallback: false }); // ➔ throws: Could not compile an expression that holds a value of the kind // type 'enclosure' to target 'javascript' ``` The runner checks the value of each input whose type can hold an enclosure, before the code runs: a record `{ lo, hi }` with `lo <= hi`, no `NaN` and not the same infinity twice, or a finite number (a point). `broadcast()` and `broadcastAsync()` check every cell before the loop. A compiled comparison answers `'true'`, `'false'` or `'maybe'`; it compares doubles exactly (without the engine tolerance), and it has no information about identical operands (`c <= c` is `'maybe'` for an interval that is not a point, where the interpreter answers `True`). An operator that the interpreter leaves unevaluated declines, and with `fallback: true` the compilation throws instead of running the interpreter, because the fallback of this target would give a number to `x`. The code cannot be loaded again from its stored form, and an input typed `list` declines. ### Declaring the Effects of a Function If your function does something besides returning a value — draws a random number, writes a symbol, calls the network, prints — say so. The Compute Engine uses that information to decide what it may cache, share or re-evaluate, and a function that quietly lies about it will produce stale results. **To declare effects**, use the `effects` property, an array of labels (or the string `'any'` for "unknown effects"): ```js ce.assign("lastId", 0); ce.declare("nextId", { signature: "() -> integer", effects: ["scope"], evaluate: (_ops, { engine }) => { const n = engine.box("lastId").evaluate().re + 1; engine.assign("lastId", n); return engine.number(n); }, }); ce.lookupDefinition("nextId").operator.signature.toString(); // ➔ "() scope -> integer" ce.box(["nextId"]).evaluate(); // ➔ 1 ce.box(["nextId"]).evaluate(); // ➔ 2 ``` Equivalently, write the effects directly in the signature string, in the slot between the argument list and the arrow: ```js ce.declare("now", { signature: "() time -> number", evaluate: (_ops, { engine }) => engine.number(Date.now()), }); ce.box(["now"]).isPure; // ➔ false ``` The nine labels and the `any` and `pure` keywords are described in [Effect Specifiers](/compute-engine/guides/types/#effect-specifiers). The older `pure` and `drawsRandom` flags are still accepted as shorthand — `drawsRandom: true` is `effects: ["random"]`, and a bare `pure: false` means `effects: 'any'` — and both are now *derived* from the effect set rather than stored separately. Declarations that contradict themselves are rejected at registration rather than resolved silently: ```js ce.declare("bad", { signature: "(number) -> number", pure: true, drawsRandom: true }); // ➔ throws: the 'pure' and 'drawsRandom' flags are contradictory ce.declare("bad", { signature: "(number) random -> number", pure: true }); // ➔ throws: the declared effects and the 'pure'/'drawsRandom' flags disagree ``` For a function defined by a body rather than a JavaScript handler, effects are **inferred** from the body unless you state them. Stating them makes them a contract: every body later assigned to that symbol must stay within the declared set, or the assignment fails with an `incompatible-type` error. See [Inferred and Declared Effects](/compute-engine/guides/types/#inferred-and-declared-effects). **To require an effect-free argument**, give the parameter a function signature with a bare arrow — `signature: "((any) -> number, real, real) -> real"` says "the first argument must be a pure callback", and it is checked when the operator is applied. Alternatively, an `evaluate` handler can inspect its operands at run time and decline. Which property to read depends on what the handler will do with the operand — the difference between *invoking* a value and *evaluating* an expression: - **A callback the handler will invoke**: read `op.type.effects`, the latent set on the operand's arrow. It resolves through symbol bindings, so a symbol bound to a drawing function reports `["random"]`. The value is `undefined` (no effects), `[]` (declared pure), `'any'` (unknown), or the labels. ```js ce.declare("sampleWith", { signature: "(function, integer) -> number", evaluate: ([f, n], { engine }) => { const latent = f.type.effects; if (latent === "any" || (latent !== undefined && latent.length > 0)) return engine.error([ "incompatible-type", "a pure callback", `a callback with ${latent === "any" ? "unknown" : latent} effects`, ]); let sum = 0; for (let i = 1; i <= n.re; i++) sum += engine.function("Apply", [f, i]).N().re; return engine.number(sum); }, }); ce.box(["sampleWith", ["Function", ["Multiply", "x", 2], "x"], 3]).evaluate(); // ➔ 12 ce.box(["sampleWith", ["Function", ["Random"], "x"], 3]).evaluate(); // ➔ ["Error", ["ErrorCode", "'incompatible-type'", "'a pure callback'", // "'a callback with random effects'"]] ``` - **A held expression the handler will evaluate**: read `expr.effects`, the effects of evaluating it. (`expr.isPure` is the boolean summary of the same answer; `effects` says *which*, so the error message can name them.) ```js ce.declare("assertPure", { signature: "(any) -> any", lazy: true, evaluate: ([body], { engine }) => { const effects = body.canonical.effects; if (effects === undefined) return body.canonical.evaluate(); return engine.error([ "incompatible-type", "a pure operand", `an operand with ${effects === "any" ? "unknown" : effects} effects`, ]); }, }); ce.box(["assertPure", ["Add", 1, 2]]).evaluate(); // ➔ 3 ce.box(["assertPure", ["Assign", "q", 1]]).evaluate(); // ➔ ["Error", ["ErrorCode", "'incompatible-type'", "'a pure operand'", // "'an operand with scope effects'"]] ``` ### Host Capabilities: Redirecting and Denying Console I/O Some operators reach outside the engine. `Print` writes to the console and `Input` reads a line from the terminal or from the browser's `prompt()` dialog. They do not call the host directly: they use a **handler** that the engine holds in `ce.effects`, and your application can replace that handler or deny it. **To redirect console output and supply input**, assign a `console` handler: ```js const lines = []; ce.effects = { console: { log: (line) => lines.push(line), readLine: (prompt) => "Ada", }, }; ce.box(["Print", "'Hello'", ["Add", 1, 2]]).evaluate(); // lines ➔ ["Hello 3"] ce.box(["Input", "'Name? '"]).evaluate(); // ➔ "Ada" ``` `log(line)` receives each printed line, without a trailing newline. `readLine(prompt)` is synchronous and returns one of three things: - a string: the line that was read; - `null`: end of input, or the user canceled. `Input` evaluates to `Nothing`; - `undefined`: this host has no interactive input. `Input` stays unevaluated. The registry is per engine, and an assignment is a complete description: `ce.effects = {}` puts the default handler back. **To deny a capability**, set its handler to `null`. The operator then evaluates to an error value and does not reach the host. It does not throw. ```js ce.effects = { console: null }; ce.box(["Print", "'secret'"]).evaluate(); // ➔ ["Error", ["ErrorCode", "'capability-denied'", "'console'"]] ``` **To make a change for one block of code only**, use `ce.withEffects()`. The previous handlers come back when the callback returns or throws, and, when the callback returns a promise, when that promise settles. This is how to evaluate an expression you do not trust: ```js const result = ce.withEffects({ console: null }, () => expr.evaluate()); ``` Calls nest: a capability that an inner call does not mention keeps the handler of the outer call. Each evaluation uses the handlers that were installed **when it started**. Assigning `ce.effects` while an evaluation runs does not change that evaluation, and a `withEffects()` call made for one asynchronous evaluation does not change another one that is suspended on the same engine. One consequence to know: while the promise of an asynchronous `withEffects()` callback is pending, its handlers are the installed ones, so an unrelated evaluation that *starts* during that time also uses them. **To use a capability in your own operator**, declare the effect label in the signature and read the handler from the `options` argument of the `evaluate` handler — not from `ce.effects`, so that your operator follows the rule above: ```js ce.declare("Warn", { signature: "(string) console -> nothing", evaluate: ([message], { engine, effects }) => { if (effects.console === null) return engine.error(["capability-denied", "console"]); effects.console.log("warning: " + message.string); return engine.Nothing; }, }); ``` An `evaluateAsync` handler that starts nested evaluations must hand the handlers on: pass the `options` it received to `evaluateAsync()`. To change one option, copy the others — `op.evaluateAsync({ ...options, numericApproximation: true })` — and do not build the object from nothing. The `options` also carry the scopes of the evaluation, for example the scope of the index of an enclosing `Sum`. The engine installs them only while the evaluation's own synchronous code runs, because other code can run on the engine while a handler is suspended. Before the first `await` of an `evaluateAsync` handler they are in place. After it, synchronous work that reads the scope (canonicalizing a held operand with `x.canonical`, `ce.box()`, `ce.parse()`, `evaluate()`, `ce.assign()`) must run inside `ce.withEvaluationContext(options, fn)`: ```js evaluateAsync: async ([x], options) => { await somethingSlow(); return ce.withEvaluationContext(options, () => x.canonical).evaluateAsync(options); }, ``` Outside it, an index of an enclosing `Sum` stays symbolic, and a global symbol with its name is declared. A nested `evaluateAsync(options)` call does not need it. **The `entropy` handler** is the unseeded source of randomness: an object with one method, `random()`, that returns a uniform number in `[0, 1)` (`Math.random` by default). `RandomExpression` draws from it, and so does every random operator — `Random`, `RandomShuffle`, `RandomChoice`, `RandomPrime`, the Monte-Carlo estimators — when it is evaluated **outside** a `WithRandomSeed` frame. Inside a frame the draws come from the seeded stream and the handler is not consulted. A constant handler makes unframed random results reproducible in a test; `entropy: null` makes each unframed random operator evaluate to a `capability-denied` error value that names the operator: ```js ce.effects = { entropy: { random: () => 0.25 } }; ce.box(["Random"]).evaluate(); // ➔ 0.25 ce.effects = { entropy: null }; ce.box(["Random"]).evaluate(); // ➔ ["Error", ["ErrorCode", "'capability-denied'", "'entropy'"], "'Random'"] ce.box(["WithRandomSeed", 42, ["Random"]]).evaluate(); // ➔ 0.7367300395263549 (seeded: the handler is not used) ``` A compiled function draws from the same handler, but a denied handler makes it throw a `CapabilityDeniedError` at run time: compiled code has no error-value channel. Only `console` and `entropy` have a handler today. The other capability labels (`network`, `fs_read`, `fs_write`, `time`, `environment`) will get one together with the first library operator that needs it. ### Declaring an Operator that Binds a Variable Some operators own a **bound variable**: the `k` of a summation, the `x` of a derivative or an integral. A bound variable is not an ordinary argument — it must be a *new* variable belonging to the operator, shadowing any same-named symbol outside it, and it must stay symbolic even if a symbol of the same name has a value. **To declare a binder**, give the `scoped` property a **binding-site selector** instead of `true`. The selector tells the engine which operands are binding sites; the engine then declares those variables in the operator's own scope before your operands are canonicalized, and makes every occurrence in the other operands refer to that binding — consistently across the LaTeX, MathJSON, and `ce.function()` routes. The prebuilt selectors cover the common shapes: - `operandSites(...indices)` — the operands at these positions are bare bound-variable symbols (like `Series`' expansion variable). - `operandsFrom(first)` — every operand from position `first` on is a bound variable (like `D`'s variadic differentiation variables). - `indexingSetSites(first)` — the first element of each `Element`- or `Limits`-shaped operand from position `first` on is a bound variable (like `Sum`, `Product`, or a comprehension: `["Element", "k", collection]`). - `limitsIndexSites(op)` — the index inside the single `Limits` operand at position `op`. For example, an operator that computes the maximum of an expression over an indexing set: ```js import { ComputeEngine, indexingSetSites } from "@cortex-js/compute-engine"; const ce = new ComputeEngine(); ce.declare("MaxOver", { lazy: true, scoped: indexingSetSites(1), signature: "(expression, expression) -> number", evaluate: (ops, { engine }) => { // A lazy operator receives its operands unevaluated: canonicalize // the ones you consume. const body = ops[0].canonical; const elem = ops[1].canonical; // Element(k, collection) const k = elem.op1.symbol; const coll = elem.op2.evaluate(); let best; for (const v of coll.each()) { const val = body.subs({ [k]: v }).evaluate().re; if (best === undefined || val > best) best = val; } return best === undefined ? undefined : engine.number(best); }, }); ``` The engine guarantees the binding behavior without further work in the handler. Even with a same-named global that has a value, the bound variable is the operator's own: ```js ce.parse("k := 100").evaluate(); const e = ce.box([ "MaxOver", ["Subtract", ["Multiply", "k", 6], ["Power", "k", 2]], ["Element", "k", ["List", 1, 2, 3, 4, 5]], ]); console.log(e.evaluate()); // ➔ 9 (max of 6k - k² over {1…5}, at k = 3 — not affected by k := 100) console.log(ce.parse("k").evaluate()); // ➔ 100 (the global k is untouched) ``` A few notes: - `scoped: true` (without a selector) still means "this operator has a scope, but no syntactic bound variables" — appropriate for `Block`-like operators whose scope holds declarations. - With multiple indexing clauses, **later clauses see earlier bindings**: a collection expression in clause 2 may reference the index of clause 1, and a collection in clause 1 that mentions the *name* of clause 2's index refers to the enclosing scope, not the later clause. - A parameter or index named after a library constant (`Pi`, `e`, `i`) is bound like any other variable inside the operator; the constant is unaffected outside it. **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. ```js ce.declare("triple", "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. ```js ce.assign("double", ([x]) => x.mul(2)); ce.assign("double", ["Function", ["Multiply", "x", 2], "x"]); ce.assign("double",ce.parse("x \\mapsto 2x")); ``` If the function literal declares the types of its parameters (and, optionally, its return value), the assigned function is given that typed signature, and in strict mode its arguments are checked at each call: ```js ce.assign("double", ["Function", ["Typed", ["Multiply", "x", 2], "'integer'"], ["Typed", "x", "'integer'"]]); // double now has the signature (x: integer) -> integer ``` A function assignment may refer to the function being defined; a separate declaration is not required for recursion: ```js ce.parse("factorial(n) := n \\cdot factorial(n-1)").evaluate(); ``` Numeric evaluation also continues through user-defined functions. For example, after `f(x) := x/3`, `f(2).evaluate()` is the exact value `2/3`, while `f(2).N()` is its numeric approximation. The approximation is evaluated in the function's own lexical scope. Learn more about the standard operator to manipulate **functions**. ### Declaring a Sequence with Subscript Evaluation Mathematical sequences like Fibonacci numbers ($F_n$), indexed coefficients ($a_n$), or matrix elements ($M_{i,j}$) are commonly written using subscript notation. You can define custom evaluation logic for subscripted symbols using the `subscriptEvaluate` handler. ```js // Define a sequence of squares: S_n = n² ce.declare("S", { subscriptEvaluate: (subscript, { engine }) => { const n = subscript.re; if (!Number.isInteger(n) || n < 0) return undefined; return engine.number(n * n); }, }); ce.parse("S_{5}").evaluate(); // → 25 ce.parse("S_3").evaluate(); // → 9 ce.parse("S_n").evaluate(); // → stays as Subscript(S, n) ``` The `subscriptEvaluate` handler receives: - `subscript`: the subscript expression (already evaluated) - `options`: an object with `engine` (the ComputeEngine) and `numericApproximation` (true when called from `.N()`) **Return `undefined`** to keep the expression symbolic. This is useful when the subscript contains unknowns or is outside the valid range. ```js // Fibonacci sequence with memoization const fibMemo = new Map(); const fib = (n) => { if (n <= 1) return n; if (fibMemo.has(n)) return fibMemo.get(n); const result = fib(n - 1) + fib(n - 2); fibMemo.set(n, result); return result; }; ce.declare("F", { subscriptEvaluate: (subscript, { engine }) => { const n = subscript.re; if (!Number.isInteger(n) || n < 0) return undefined; return engine.number(fib(n)); }, }); ce.parse("F_{10}").evaluate(); // → 55 ce.parse("F_n").evaluate(); // → Subscript(F, n) - stays symbolic ``` **Multi-index subscripts** (like matrix elements) receive the subscript as a `Tuple` expression: ```js import { isFunction } from '@cortex-js/compute-engine'; const matrix = [[1,2,3], [4,5,6], [7,8,9]]; ce.declare("M", { subscriptEvaluate: (subscript, { engine }) => { if (isFunction(subscript, "Tuple")) { const [i, j] = subscript.ops; const row = matrix[i.re - 1]; // 1-indexed if (row && row[j.re - 1] !== undefined) { return engine.number(row[j.re - 1]); } } return undefined; }, }); ce.parse("M_{2,3}").evaluate(); // → 6 ``` Subscripted expressions with `subscriptEvaluate` have type `number` and can be used in arithmetic: ```js ce.parse("S_{5} + S_{3}").evaluate(); // → 34 (25 + 9) ce.parse("2 * F_{10}").evaluate(); // → 110 (2 × 55) ``` ### Declarative Sequence Definitions For common mathematical sequences defined by recurrence relations, the `declareSequence()` method provides a simpler declarative API: ```js // Fibonacci sequence: F_n = F_{n-1} + F_{n-2}, with F_0 = 0, F_1 = 1 ce.declareSequence('F', { base: { 0: 0, 1: 1 }, recurrence: 'F_{n-1} + F_{n-2}', }); ce.parse('F_{10}').evaluate(); // → 55 ce.parse('F_{20}').evaluate(); // → 6765 ``` The `SequenceDefinition` object accepts: | Property | Type | Description | |----------|------|-------------| | `base` | `Record` | **Required.** Base cases as index → value mapping | | `recurrence` | `string \| Expression` | **Required.** Recurrence relation (LaTeX string or expression) | | `variable` | `string` | Index variable name (default: `'n'`) | | `memoize` | `boolean` | Cache computed values (default: `true`) | | `domain` | `{ min?: number, max?: number }` | Valid index range | **Examples:** ```js // Arithmetic sequence: a_n = a_{n-1} + 2 ce.declareSequence('A', { base: { 0: 1 }, recurrence: 'A_{n-1} + 2', }); ce.parse('A_{5}').evaluate(); // → 11 // Factorial via recurrence: n! = n × (n-1)! ce.declareSequence('H', { base: { 0: 1 }, recurrence: 'n \\cdot H_{n-1}', }); ce.parse('H_{5}').evaluate(); // → 120 // Triangular numbers: T_n = T_{n-1} + n ce.declareSequence('T', { base: { 0: 0 }, recurrence: 'T_{n-1} + n', }); ce.parse('T_{5}').evaluate(); // → 15 // Using a custom index variable ce.declareSequence('W', { variable: 'k', base: { 0: 1 }, recurrence: 'W_{k-1} + k', }); ce.parse('W_{5}').evaluate(); // → 16 // With domain constraints (only valid for n ≥ 1) ce.declareSequence('X', { base: { 1: 1 }, recurrence: 'X_{n-1} + 1', domain: { min: 1 }, }); ce.parse('X_{5}').evaluate(); // → 5 ce.parse('X_{0}').evaluate(); // → stays symbolic (outside domain) ``` **Symbolic behavior:** When the subscript is symbolic or non-integer, the expression stays symbolic: ```js ce.parse('F_k').evaluate(); // → Subscript(F, k) - stays symbolic ce.parse('F_{1.5}').evaluate(); // → Subscript(F, 1.5) - stays symbolic ``` **Memoization:** By default, computed values are cached for efficiency. This is especially important for sequences like Fibonacci that have exponential complexity without memoization: ```js // Fast even for large indices thanks to memoization ce.parse('F_{30}').evaluate(); // → 832040 (computed quickly) ``` To disable memoization (e.g., for memory-constrained environments): ```js ce.declareSequence('O', { base: { 0: 1 }, recurrence: 'O_{n-1} + 1', memoize: false, }); ``` ### LaTeX-Based Sequence Definitions Sequences can also be defined using natural LaTeX assignment notation. This is especially useful in interactive environments or when working with mathematical notation directly: ```js // Arithmetic sequence via LaTeX ce.parse('L_0 := 1').evaluate(); ce.parse('L_n := L_{n-1} + 2').evaluate(); ce.parse('L_{5}').evaluate(); // → 11 // Fibonacci via LaTeX ce.parse('F_0 := 0').evaluate(); ce.parse('F_1 := 1').evaluate(); ce.parse('F_n := F_{n-1} + F_{n-2}').evaluate(); ce.parse('F_{10}').evaluate(); // → 55 // Factorial via LaTeX ce.parse('D_0 := 1').evaluate(); ce.parse('D_n := n \\cdot D_{n-1}').evaluate(); ce.parse('D_{5}').evaluate(); // → 120 ``` **Order independence:** Base cases and recurrence can be defined in any order. The sequence is finalized when both a base case and a recurrence relation are present: ```js // Recurrence first, then base case ce.parse('K_n := K_{n-1} + 1').evaluate(); ce.parse('K_0 := 0').evaluate(); // Sequence finalized here ce.parse('K_{5}').evaluate(); // → 5 ``` **How it works:** The system detects sequence definitions by checking if the right-hand side contains self-references (like `L_{n-1}` when defining `L_n`). Assignments without self-references are treated as function definitions instead: ```js // This defines a function f(x) = x², not a sequence ce.parse('f_x := x^2').evaluate(); ce.parse('f_{3}').evaluate(); // → 9 ``` ### Sequence Status and Introspection You can query the status of sequence definitions and inspect defined sequences: ```js // Check if a sequence is fully defined ce.parse('F_0 := 0').evaluate(); ce.getSequenceStatus('F'); // → { status: 'pending', hasBase: true, hasRecurrence: false, baseIndices: [0] } ce.parse('F_n := F_{n-1} + F_{n-2}').evaluate(); ce.getSequenceStatus('F'); // → { status: 'complete', hasBase: true, hasRecurrence: true, baseIndices: [0] } // For non-sequences: ce.getSequenceStatus('x'); // → { status: 'not-a-sequence', hasBase: false, hasRecurrence: false } ``` **Introspection methods** let you examine and manage defined sequences: ```js // Get detailed information about a sequence ce.getSequence('F'); // → { name: 'F', variable: 'n', baseIndices: [0, 1], memoize: true, cacheSize: 5 } // List all defined sequences ce.listSequences(); // → ['F', 'A', 'T'] // Check if a symbol is a sequence ce.isSequence('F'); // → true ce.isSequence('x'); // → false // Manage memoization cache ce.getSequenceCache('F'); // → Map { 2 => 1, 3 => 2, 5 => 5, ... } ce.clearSequenceCache('F'); // Clear cache for specific sequence ce.clearSequenceCache(); // Clear all sequence caches ``` `getSequenceCache()` holds the exact terms only, with the index as the key. The terms that `.N()` computes are kept apart, one set for each precision; `clearSequenceCache()` clears both. ### Generating Sequence Terms Generate a list of sequence terms with `getSequenceTerms()`: ```js ce.declareSequence('F', { base: { 0: 0, 1: 1 }, recurrence: 'F_{n-1} + F_{n-2}', }); // Get terms from index 0 to 10 (inclusive) ce.getSequenceTerms('F', 0, 10); // → [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55] // With a step parameter (every other term) ce.getSequenceTerms('F', 0, 10, 2); // → [0, 1, 3, 8, 21, 55] // Starting from a non-zero index ce.getSequenceTerms('F', 5, 10); // → [5, 8, 13, 21, 34, 55] ``` ### Sum and Product over Sequences `Sum` and `Product` work seamlessly with user-defined sequences: ```js ce.declareSequence('F', { base: { 0: 0, 1: 1 }, recurrence: 'F_{n-1} + F_{n-2}', }); // Sum of Fibonacci terms from k=0 to 10 ce.parse('\\sum_{k=0}^{10} F_k').evaluate(); // → 143 // Product over sequence terms ce.declareSequence('A', { base: { 1: 1 }, recurrence: 'A_{n-1} + 1', }); ce.parse('\\prod_{k=1}^{5} A_k').evaluate(); // → 120 (factorial) ``` ### OEIS Integration The [Online Encyclopedia of Integer Sequences (OEIS)](https://oeis.org) contains over 350,000 integer sequences. You can look up sequences and verify your definitions against known mathematical sequences: ```js // Look up a sequence by its terms const results = await ce.lookupOEIS([0, 1, 1, 2, 3, 5, 8, 13]); // → [{ id: 'A000045', name: 'Fibonacci numbers', terms: [...], url: '...' }] // Each result contains: // - id: OEIS sequence ID (e.g., 'A000045') // - name: Sequence description // - terms: First several terms // - formula: Formula if available // - url: Link to OEIS page ``` **Verify your sequences** against OEIS: ```js ce.declareSequence('F', { base: { 0: 0, 1: 1 }, recurrence: 'F_{n-1} + F_{n-2}', }); const result = await ce.checkSequenceOEIS('F', 10); // → { // matches: [{ id: 'A000045', name: 'Fibonacci numbers', ... }], // terms: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34] // } if (result.matches.length > 0) { console.log(`Your sequence matches ${result.matches[0].name}!`); } ``` **Options:** ```js // Limit number of results await ce.lookupOEIS([1, 2, 3, 4, 5], { maxResults: 3 }); // Set timeout (in milliseconds) await ce.lookupOEIS([1, 2, 3, 4, 5], { timeout: 5000 }); ``` > **Note:** OEIS lookups require network access to oeis.org. ## Overloading Functions **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: ```js const originalSqrtDefinition = ce.expr('Sqrt').operatorDefinition!; ce.declare('Sqrt', { ...originalSqrtDefinition, evaluate: (x, options) => { const y = originalSqrtDefinition.evaluate!(x, options); return y?.isExtendedReal === false ? ce.NaN : y; }, }); ce.parse('\\sqrt{-2.5}').evaluate(); // ➔ NaN ce.parse('\\sqrt{\\sin x}').evaluate(); // ➔ sqrt(sin(x)) ``` `isExtendedReal` is the test for "lies on the real line", and it accepts $\pm\infty$ along with the finite reals; only a value with a non-zero imaginary part, or `NaN`, fails it. For a symbolic result such as `sqrt(sin(x))` it is `undefined`, neither `true` nor `false`, so the guard compares with `false`: a plain truth test would turn every symbolic result into `NaN`. Use `isFinite` instead when the guard must also exclude the infinities — but note that `isFinite` accepts a finite *complex* value, so it is not a substitute here. An argument whose square root is reduced at canonicalization (`\sqrt{-4}` is `2i`) does not reach the `evaluate` handler. A redeclaration that replaces a handler, as `evaluate` above, is a user definition. `compile()` cannot lower it as the library `Sqrt`: it reports `success: false` and `run()` uses the interpreter. `D` differentiates it as an unknown function. A copy that keeps the library handlers is still the library operator for differentiation and compilation: after `ce.declare('Sqrt', { ...ce.expr('Sqrt').operatorDefinition, description: '…' })`, `compile(ce.parse('\\sqrt{\\sin x}'))` succeeds and `D(sqrt(sin(x)), x)` is `cos(x) / (2sqrt(sin(x)))`. The rule: a definition of a library name is the library operator when its `evaluate`, `canonical`, `compile`, `derivative` and `evaluateAsync` handlers are the same function objects as the library's, and its `lazy` and `broadcastable` flags have the same values. A copy declared under another name keeps that name. After `const { name, ...sin } = ce.lookupDefinition('Sin').operator; ce.declare('MySin', sin)`, `ce.box(['MySin', 'x']).evaluate()` is `MySin(x)`, and `MySin(π)` is `0`. **To change part of a definition**, pass `{ extend: true }` as the third argument of `ce.declare()`. The engine builds a new definition from the visible one and the fields of the patch. A field the patch does not name keeps its value. A field the patch names replaces the old value. This works for a name declared in the same scope, where a plain `ce.declare()` throws, and for a standard operator. ```js // Widen the signature and keep the evaluate handler ce.declare("SetMinus", { signature: "(value, value*) -> set" }, { extend: true }); ce.box(["SetMinus", ["Set", 1, 2, 3], ["Set", 2]]).evaluate(); // ➔ {1, 3} ce.box(["SetMinus", 5, 2]); // ➔ valid // Add a description and keep every handler: D still differentiates Sin ce.declare("Sin", { description: "The sine" }, { extend: true }); ce.box(["D", ["Sin", "x"], "x"]).evaluate(); // ➔ cos(x) ``` The patch can carry any field of the definition: `signature`, `evaluate`, `description`, `keywords`, `lazy`, … The field `addSignature` adds an overload instead of replacing the signature: the signature becomes `old & new`. The new signature must be a subtype of the old one, so that every call that was valid stays valid; otherwise the call throws. With `ce.declare("SetMinus", { addSignature: "(integer, integer) -> set" }, { extend: true })`, the signature is `((set, value*) -> set) & ((integer, integer) -> set)`. It is an error to extend a name with no operator definition, a name declared as a value, or a function defined by clauses. An expression boxed before the extension keeps the old definition. The third argument can also be `{ scope, extend }`; a scope alone as the third argument works as before. The standard functions are in a `system` scope, so a new declaration in the `global` scope or a child scope replaces the original declaration. ## Multi-Clause Function Definitions **To define a function by cases** — separate definitions for particular argument values, plus a general fallback — use the `DefineFunction` operator. Unlike `Assign`, which replaces a binding wholesale, `DefineFunction` **accumulates**: each statement adds a *clause*, and a call dispatches to the most specific clause admitting its arguments (declaration order only breaks ties between equally specific clauses). ```js ce.box(['DefineFunction', 'fib', ['Function', 0, ['Typed', 'z', { str: '0' }]]]).evaluate(); ce.box(['DefineFunction', 'fib', ['Function', 1, ['Typed', 'o', { str: '1' }]]]).evaluate(); ce.box(['DefineFunction', 'fib', ['Function', ['Add', ['fib', ['Subtract', 'n', 1]], ['fib', ['Subtract', 'n', 2]]], ['Typed', 'n', { str: 'integer' }]]]).evaluate(); console.log(ce.box(['fib', 10]).evaluate().toString()); // ➔ 55 ``` A parameter constrained to a single value (`{ str: '0' }` above) uses a **value type**: the clause admits exactly that value. In Epsil, literal parameters provide the same thing directly: `fib(0) = 0`. The clause rules: - A new clause with the **same parameter types** replaces the earlier clause in place (so re-running an edited definition behaves as expected); any other parameter list appends a clause. - A plain assignment (`Assign`, `ce.assign()`) still **replaces the whole binding**, clauses and all. - If a symbolic argument leaves dispatch undecided — a more specific clause *might* apply once the value is known — the call stays inert (symbolic) rather than committing to a fallback. - If the evaluated arguments match **no** clause, the call is a `no-matching-clause` error value. - Effects must be uniform across clauses: there is one effect row per function. An explicit specifier on one clause establishes the row; another clause's explicit specifier must agree, or the definition is rejected with `incompatible-clause-effects`. **To inspect the clause set**, use `About`: it lists one line per clause in declaration order, and annotates overlapping equal-specificity clauses and clauses made unreachable by more specific ones covering their whole (finite) domain. **To define a function whose arguments are not evaluated** — the user-function counterpart of an operator definition's `lazy` flag — pass the attribute `{hold: True}` as `DefineFunction`'s third operand (in Epsil, the `hold` prefix: `hold f(e) = Head(e)`). The arguments are bound to the parameters as written, canonical and bound in the caller's scope but unevaluated; reading a parameter in the body evaluates the argument there, and a structural operator (`Head`, `Tail`, `Hold`) sees the expression itself. A hold definition is installed as a `lazy` operator and is single-clause: a second clause, or a value-typed (literal) parameter, is refused. ```js ce.assign('a', 3); ce.box(['DefineFunction', 'f', ['Function', ['Head', 'e'], 'e'], { dict: { hold: 'True' } }]).evaluate(); console.log(ce.box(['f', ['Add', 'a', 1]]).evaluate().toString()); // ➔ Add (without the attribute: Integer — f receives 4) ``` The same attributes dictionary carries the other definition attributes: - `bind: ["i"]` — with `hold`, the named parameters are **bound variables**: the call must pass a symbol there, and the parameter is substituted by that symbol in the body (`hold mySum(body, bind i, n) = Sum(body, i, 1, n)`), the definition being installed as a binder (`scoped: operandSites(…)`), so the call declares the symbol in its own scope like `Sum` does. - `commutative`, `associative`, `idempotent`, `involution` — the operator flags of the same names, applied when a call is canonicalized. An associative function is binary; a flattened n-ary call is folded pairwise. Not with `hold`. - `description: "…"` — the definition's description (`About`, editor hovers); in Epsil this is the doc comment written before the definition. ## Defining Multiple Functions and Symbols **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` ::: ```js 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. ```js ce.assign({ "m": 10, "f": ce.parse("x \\mapsto x^2 + x + 41"), "g": ce.parse("t \\mapsto t^3 + t^2 + 17"), }); ``` ## Loading a Library A set of definitions can be packaged as a **library**: an object with a `name`, a `definitions` dictionary (the same entries as `ce.declare()` takes), and an optional `requires` list with the names of the libraries it depends on. **To load a library when the engine is created**, give it in the `libraries` constructor option. **To load a library on a running engine**, call `ce.loadLibrary(library)`. The definitions go in the global scope, as with `ce.declare()`, and an expression boxed before the call uses them. ```js ce.box(["Sq", 3]).evaluate(); // ➔ Sq(3) ce.loadLibrary({ name: "mine", definitions: { Sq: { signature: "(number) -> number", evaluate: ["Function", ["Square", "x"], "x"], }, }, }); ce.box(["Sq", 3]).evaluate(); // ➔ 9 ``` Each library in `requires` must already be loaded. The call throws, and declares nothing, for a library name that is already loaded, a missing required library, a standard library, or a definition that is invalid, repeated, or already declared in the global scope. A checkpoint taken before the call (`ce.checkpoint()`) undoes it. `ce.libraryOf(name)` returns the name of the library that defines `name`: `"trigonometry"` for `Sin`, the `name` of a loaded library for one of its definitions, and `undefined` for a name declared with `ce.declare()` or a name nothing defines. ```js ce.libraryOf("Sq"); // ➔ "mine" ce.libraryOf("Sin"); // ➔ "trigonometry" ``` --- # Parsing and Serializing LaTeX Source: https://mathlive.io/compute-engine/guides/latex-syntax/ The Compute Engine manipulates MathJSON expressions. It can also convert LaTeX strings to MathJSON expressions (**parsing**) and output MathJSON expressions as LaTeX string (**serializing**) :::info[Note] In this documentation, functions such as `ce.expr()` and `ce.parse()` require a `ComputeEngine` instance which is denoted by a `ce.` prefix.
Functions that apply to an expression, such as `expr.simplify()` are denoted with an `expr.` prefix. ::: **To create a new instance of the Compute Engine**, use the `new ComputeEngine()` constructor. When using the full package, LaTeX support is included automatically. ```javascript import { ComputeEngine } from '@cortex-js/compute-engine'; const ce = new ComputeEngine(); ``` If you are using only the `core` sub-path, inject a `LatexSyntax` instance to enable LaTeX parsing and serialization: ```javascript import { ComputeEngine } from '@cortex-js/compute-engine/core'; import { LatexSyntax } from '@cortex-js/compute-engine/latex-syntax'; const ce = new ComputeEngine({ latexSyntax: new LatexSyntax() }); ``` Without a `LatexSyntax`, calling `ce.parse()`, `.latex`, or `.toLatex()` will throw an error. MathJSON operations (`ce.expr()`, `.json`, `.evaluate()`, etc.) work without it.
**To input math using an interactive mathfield**, use the [Mathfield](/mathfield/) library. A `` DOM element works like a `