Skip to main content

Extended Rules — Symbolic Integration

The Integration Rules library is an optional add-on that extends the Compute Engine's symbolic integration with a large rule set ported from Rubi, the Rule-Based Integration project. It currently covers Chapter 1 — algebraic functions (rational functions, radicals, and binomial forms (a + b x^n)^p), some 2,600 rules.

It is an opt-in module: if you don't import it, it adds zero bytes to your bundle and the engine's integration behavior is unchanged.

import { ComputeEngine } from "@cortex-js/compute-engine";
import { loadIntegrationRules } from "@cortex-js/compute-engine/integration-rules";

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

console.log(ce.parse("\\int \\frac{1}{\\sqrt{x}(1+x)}\\,dx").evaluate().latex);
// ➔ "2\arctan(\sqrt{x})"

Without the library, the same integral is returned unevaluated. The built-in antiderivative handles many common integrands on its own; the rule set broadens coverage to the harder algebraic cases.

What's Included​

The rules close algebraic integrands that the built-in antiderivative leaves unevaluated, including ones with symbolic parameters:

ce.parse("\\int \\frac{x^2}{\\sqrt{a+b x^3}}\\,dx").evaluate().latex;
// ➔ "\frac{2\sqrt{bx^3+a}}{3b}"

ce.parse("\\int \\frac{\\sqrt{1+x}}{x}\\,dx").evaluate().latex;
// ➔ "2\sqrt{x+1}-2\mathrm{artanh}(\sqrt{x+1})"

The current corpus is Rubi's Chapter 1 (algebraic functions): polynomial and rational integrands, integrands involving \sqrt{a+bx} and other radicals, and binomial/trinomial powers. Transcendental and special-function chapters are not yet ported.

How It Works​

loadIntegrationRules(ce) registers the rule set as the engine's symbolic integration provider. When you evaluate an Integrate expression, the provider is consulted first; if it cannot close the integrand (it returns an unevaluated integral) the engine falls back to its built-in antiderivative. With no provider registered — the default — behavior is exactly as before.

This applies to both indefinite and definite integrals, since a definite integral is evaluated from its antiderivative.

ce.parse("\\int_0^1 \\frac{x^2}{\\sqrt{1+x^3}}\\,dx").evaluate();

Step-by-Step Explanations​

With the rules loaded, expr.explain('Integrate') traces the integration as textbook steps — term-by-term splits, constant factors moved out, and each corpus rule application — each step a whole-expression state with a stable machine id and an English description:

const expr = ce.parse("\\int x\\sqrt{1+x}\\,dx");
for (const step of expr.explain("Integrate").steps)
console.log(step.value.latex, "—", step.description);
// ➔ \int\!\sqrt{x+1}^{3}-\sqrt{x+1}\,\mathrm{d}x — Apply integration rule 1.1.1.2#19 (Rubi)
// ➔ … — Integrate term by term: ∫(u+v) dx = ∫u dx + ∫v dx
// ➔ … — Move the constant factor out of the integral
// ➔ \frac{1}{5}(2\sqrt{x+1}^{5})-\frac{1}{3}(2\sqrt{x+1}^{3}) — Apply integration rule 1.1.1.1#17 (Rubi)

The explanation's result is the same value evaluating the integral returns. A definite integral is presented via the Fundamental Theorem of Calculus (antiderivative, then the bracket F\big|_a^b and the bounds). Without the rules loaded, or when the rules cannot close the integral, explain('Integrate') throws a precise error.

Options and the Load Report​

loadIntegrationRules() returns a report and accepts two limits for each integral: a step budget and a wall-clock limit.

const report = loadIntegrationRules(ce, {
stepBudget: 300000, // steps of the rule driver per integral (default 300000)
timeLimitMs: 120000, // wall-clock guard per integral (default 120000)
});

console.log(report.ruleCount); // number of compiled rules registered (~2600)
console.log(report.skipped); // corpus rules skipped at compile time

The step budget decides when the rule driver gives up on an integrand. Because it counts steps and not time, the same integral gives the same answer on a fast machine, a slow one and a loaded one: an integral that needs more steps stays unevaluated everywhere, and the built-in antiderivative is used instead.

The wall-clock limit is only a guard against a hang in code that does not count steps. The default (120 s) is about 8 times what the step budget allows on an idle machine, so on a machine up to about 8 times slower the step budget, not the clock, decides the result. On a slower or more loaded machine, raise timeLimitMs: when the clock stops a search that the step budget allows, the integral can stay unevaluated on that machine and close on a faster one.

Performance​

Compiling the rule set costs roughly 300ms on the first call; the compiled rules are cached per engine, so loadIntegrationRules() is idempotent — calling it again re-registers the provider without recompiling and does not duplicate rules. The library only participates in Integrate; it has no effect on simplify(), evaluate() of non-integral expressions, or parsing.

Attribution​

The rules are derived from Rubi, the Rule-Based Integration project created by Albert D. Rich, used under the MIT license (© 2018 Rule-based Integration Organization). The corpus is ported from Rubi release 4.17.3.0.