Skip to main content

Extended Rules — The Identities Library

The Identities Library is an optional add-on that teaches the Compute Engine over 1,300 mathematical facts: special values and identities for the gamma function, the Riemann zeta function, inverse trigonometric functions, the Lambert W function, Jacobi theta functions, modular functions, and many other special functions.

It is an opt-in module: if you don't import it, it adds zero bytes to your bundle and has no effect on the engine.

import { ComputeEngine } from "@cortex-js/compute-engine";
import { loadIdentities } from "@cortex-js/compute-engine/identities";

const ce = new ComputeEngine();
loadIdentities(ce);

console.log(ce.parse("\\zeta(2)").simplify().latex);
// ➔ "\frac{\pi^2}{6}"

console.log(ce.parse("\\arctan(2-\\sqrt{3})").simplify().latex);
// ➔ "\frac{\pi}{12}"

The rules are derived from Fungrim, the Mathematical Functions Grimoire by Fredrik Johansson (MIT license). Every rule carries the identifier of its source entry — for example a simplification justified by rule fungrim:d4b0b6 can be looked up at fungrim.org/entry/d4b0b6 for its precise statement, conditions, and references.

What's Included

  • Specific values — exact closed forms such as $\Gamma(\tfrac12) = \sqrt{\pi}$, $\zeta(2) = \tfrac{\pi^2}{6}$, $\operatorname{W}(e) = 1$, values of $\arctan$, $\operatorname{sinc}$, the digamma function, and theta constants.
  • Identities — rewrite rules such as $n \cdot (n-1)! \to n!$, $\gcd(F_n, F_{n+1}) \to 1$, $\sin(\pi n + \tfrac{\pi}{2}) \to (-1)^n$, $\operatorname{W}(x e^x) \to x$, argument transformations for theta and modular functions, and more.
  • Symbol definitions for special functions the engine does not define natively (Jacobi theta, Carlson elliptic integrals, the Hurwitz zeta function, the arithmetic–geometric mean, and others), so expressions using them parse, canonicalize, and serialize correctly.

Guarded Rules and Assumptions

Many identities are only valid under conditions: an exponent must be an integer, an argument must be positive, a parameter must lie in the upper half-plane. The library enforces these conditions fail-closed: a rule only fires when the engine can prove its condition from the declared types and your assumptions. If a condition cannot be decided, the expression is left unchanged.

const ce = MathfieldElement.computeEngine; // Without any assumption, nothing happens: console.log(ce.parse("\\sin(\\pi t + \\frac{\\pi}{2})").simplify().latex); // ➔ "\sin(\pi t + \frac{\pi}{2})" // Declare that n is a positive integer... ce.declare("n", "integer"); ce.assume(ce.parse("n > 0")); // ...and the parity identity applies: console.log(ce.parse("\\sin(\\pi n + \\frac{\\pi}{2})").simplify().latex); // ➔ "(-1)^n"

Conditions over the complex domain work with the assumptions system as well (see Assumptions). For example, identities for theta and modular functions require their parameter to be in the upper half-plane:

ce.assume(ce.box(["Element", "tau", "HH"]));

// Jacobi's identity: θ₂(0,τ)⁴ + θ₄(0,τ)⁴ = θ₃(0,τ)⁴
ce.box(["Add",
["Power", ["JacobiTheta", 2, 0, "tau"], 4],
["Power", ["JacobiTheta", 4, 0, "tau"], 4],
]).simplify();
// ➔ JacobiTheta(3, 0, tau)^4

Simplification vs Transformation

Rules whose right-hand side is simpler than their left-hand side are applied by expr.simplify(). Some true identities go the other way — they rewrite an expression into a larger but more explicit closed form, such as $\operatorname{ln} i \to \tfrac{i\pi}{2}$ or the closed form of a Carlson integral. These are loaded with a purpose of "expand" and are deliberately not used by simplify(). To apply them, use expr.replace() with the full rule set:

const expr = ce.box(["CarlsonRF", 0, 1, 2]);
expr.replace(ce.rules(ce.simplificationRules), { recursive: true });
// ➔ Gamma(1/4)² / (4 · sqrt(2π))

Selective Loading and the Load Report

loadIdentities() accepts options to load a subset, and returns a report:

const report = loadIdentities(ce, {
topics: ["gamma", "zeta", "log"], // only these topics
classes: ["specific-value"], // and only value rules
});

console.log(report.loaded); // number of rules registered
console.log(report.declared); // special-function symbols declared
console.log(report.skipped); // anything that could not be loaded, with reasons

For debugging rules that don't seem to apply, the onGuardUndecided hook reports when a rule matched structurally but its condition could not be decided from the current assumptions:

loadIdentities(ce, {
onGuardUndecided: (ruleId, wildcards) =>
console.log(`${ruleId} did not fire: condition undecided`),
});

Performance

Loading the full library takes on the order of 100ms and registers the rules behind an operator-indexed dispatcher, so the impact on simplify() for expressions that don't involve special functions is small (typically 20–60%, depending on the expression mix). Loading is per-engine and idempotent: calling loadIdentities() twice does not duplicate rules.

Call loadIdentities() early — before declaring your own symbols — so the library's symbol declarations (e.g. JacobiTheta) do not conflict with yours. If you have already declared a symbol, the library skips it and reports it in report.skipped.

Attribution

The mathematical content is derived from Fungrim, the Mathematical Functions Grimoire, © 2019 Fredrik Johansson, used under the MIT license. Rule identifiers (fungrim:xxxxxx) refer to the corresponding Fungrim entries, which include formal statements, validity conditions, and literature references.