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.
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.