Compute Engine Changelog
0.66.0 2026-06-28
New Features
-
Multiplynow operates on vectors and matrices. Previously a product with any list/matrix operand was left unevaluated — even2 * [1, 2, 3].Multiply(i.e.*,\cdot,\times, and implicit products) now follows matrix-product / scalar-scaling semantics, matchingAdd's existing element-wise threading:- Scalar × tensor scales every element:
2 * [1, 2, 3]→[2, 4, 6],2 * \begin{pmatrix}1&2\\3&4\end{pmatrix}→\begin{pmatrix}2&4\\6&8\end{pmatrix}(exact values are preserved, e.g.\frac12 [2, 4, 6]→[1, 2, 3]). - Two or more matrices/vectors form the matrix product, folded
left-to-right in the written order:
\begin{pmatrix}1&2\\3&4\end{pmatrix}\begin{pmatrix}5&6\\7&8\end{pmatrix}→\begin{pmatrix}19&22\\43&50\end{pmatrix}. The product is not commutative — operand order is preserved (including formatrix·vectorvsvector·matrix), andvector·vectorreduces to the dot product. This reuses the existingMatrixMultiplyimplementation.
Element-wise (Hadamard) multiplication of two same-shape tensors is therefore not what
*does; tensors of incompatible dimensions are left unevaluated, and symbolic operands of unknown shape are unaffected. - Scalar × tensor scales every element:
-
Hadamard (element-wise) product
\odot. A newHadamardProductoperator, written\odot, multiplies two vectors or matrices of the same shape entry by entry:[1,2,3] \odot [4,5,6]→[4,10,18]and\begin{pmatrix}1&2\\3&4\end{pmatrix} \odot \begin{pmatrix}5&6\\7&8\end{pmatrix}→\begin{pmatrix}5&12\\21&32\end{pmatrix}(compare the matrix product*, which gives\begin{pmatrix}19&22\\43&50\end{pmatrix}). Operands of incompatible shape report anincompatible-dimensionserror. It binds like multiplication and round-trips through LaTeX as\odot.
Resolved Issues
-
Mixed chained inequalities keep their middle term. A chain combining different operators — e.g.
5 \le b \lt 7— canonicalized toAnd(5 \le 7, b \lt 7), droppingbfrom the first link (so3 \le 2 \lt 7wrongly evaluated toTrue). It now canonicalizes toAnd(5 \le b, b \lt 7). Uniform chains (5 \le b \le 7) and the already-correcta \lt b \le cform are unchanged. -
A transcendental of an exact constant expression stays symbolic. Per the exactness contract,
evaluate()of a transcendental of an exact argument returns a symbolic result and only.N()numericizes. This held for number literals (sin(2)→sin(2)) but not for exact constant expressions:sin(\pi^2)numericized to-0.4303…instead of stayingsin(π²)(and likewisecos(√2), etc.). These now stay symbolic underevaluate(); an inexact (float) argument such assin(2.5)still numericizes. -
An exact real added to the imaginary unit keeps its exact real part.
\frac12 + ievaluated to0.5 + i, and\frac34\sqrt3 + ito1.299… + i— the exact real part was floatified when folded withi. Exact reals (rationals, radicals) are now preserved alongside the imaginary unit (1/2 + i,3/4·√3 + i);.N()still numericizes, and inexact reals (1.5 + i) are unchanged. -
Matrix/vector arithmetic preserves exact entries. A tensor with exact rational or radical entries was stored with a
float64element type, so element-wise operations silently produced floats — e.g.\begin{pmatrix}½&⅓\end{pmatrix} + \begin{pmatrix}½&⅓\end{pmatrix}returned[1, 0.666…]instead of[1, ⅔], and a matrix of√2entries decayed to decimals. Exact entries now use theexpressionelement type and stay exact; inexact (machine/decimal) values continue to usefloat64. -
A^nis now the matrix power for an integer exponent. A power of a matrix was element-wise for non-negative exponents (A^2squared each entry,A^0gave a matrix of ones) yetA^{-1}already returned the inverse, and\begin{pmatrix}…\end{pmatrix}^2did not evaluate at all.A^nis now the matrix power — repeated matrix multiplication — consistent with*being the matrix product:A^2 = A·A,A^0is the identity,A^{-1}the inverse, andA^{-n} = (A^n)^{-1}. A non-square base reportsexpected-square-matrix. (Also fixesMatrixPower(A, n)forn < -1, which previously collapsed toA^{-1}.) -
Element-wise functions now distribute over matrix/vector-valued sub-expressions. A broadcastable unary function applied to an operand that only becomes a collection after evaluation — e.g.
\sqrt{AB},\sin(AB),|AB|whereABis a matrix product — was left unevaluated, because broadcasting was decided from the raw (un-evaluated) operand. It now also broadcasts over the evaluated operand, so these distribute element-wise like\sqrt{M}on a literal matrix already did. (Add/Multiplykeep their dedicated tensor handling.) -
Juxtaposed matrices now form the matrix product. Writing two matrices next to each other (
\begin{pmatrix}…\end{pmatrix}\begin{pmatrix}…\end{pmatrix}), or a scalar next to a matrix (2\begin{pmatrix}…\end{pmatrix}), previously produced aTupleinstead of a product, because theMatrix(…)wrapper is not reported as an indexed collection. The invisible (implicit) operator now treats matrix operands as multiplication, consistent with*/\cdot/\times. -
Negate(and henceSubtract) of a matrix-valued product is distributed correctly. A negation whose operand only became a vector/matrix after evaluation — e.g.Negate(Multiply(A, B))fromA B - A B— was left undistributed, so the followingAdd/Subtractmisclassified it as a scalar and broadcast it over the other matrix, yielding a bogus higher-rank result. Matrix subtraction (e.g. the commutatorAB - BA) now evaluates correctly. -
A
\textcolorwrapping a bare operator now parses as that operator. Input such asx \textcolor{red}{=} ypreviously failed — the=could not be parsed as a standalone group, producing aTuplearound anexpected-closing-delimitererror. The color command is now transparent in operator position, sox \textcolor{red}{=} yparses asEqual(x, y)(and likewise for+,<,\le,\times, …). Because MathJSON has no way to annotate a lone operator glyph, the operator's color is dropped; coloring an operand (\textcolor{red}{y},\textcolor{red}{x+1}) is unchanged and still yields anAnnotated. -
One-sided
\left( … \right.enclosures now parse.\right.(and the\bigr./\Bigr./… variants) is a TeX null delimiter: a fence with no visible closing glyph. Previously a one-sided group such as\sin\left(x\right.was rejected, leaking the\leftout as anunexpected-commanderror; it now parses the same as\sin\left(x\right)(→Sin(x)). The null open form (\left.…\right|, used byEvaluateAt) and ordinary two-sided delimiters are unchanged. -
Summation/product indices written as a
\lerange are now recognized. An index set of the form\sum_{1 \le i \le 10} i^2(and the one-sided\sum_{i \le 10}) is now turned into the expectedLimits, so the indexiis bound by the sum instead of falling through to the imaginary unit. The example above now evaluates to385rather than staying symbolic withi → Complex(0, 1). This mirrors the existing handling ofi \ge 1andi = 1; strict<chains are not yet treated as index sets.
0.65.0 2026-06-28
New Features
-
Differential equation solvers. (contributed by KingArth0r) Two new functions in the calculus library provide an initial slice of ordinary differential equation (ODE) support:
-
DSolve(eq, y, x)— symbolic solver for first-order linear scalar equations of the formy'(x) + p(x)·y(x) = q(x). It returns aListof solutions, each anEqualexpression fory(x), introducing an integration constantC(a fresh name is chosen ifCis already in use). For example,DSolve(y'(x) = y(x), y, x)→[y(x) = C·e^x]andDSolve(y'(x) + y(x) = x, y, x)→[y(x) = x - 1 + C·e^{-x}]. Nonlinear or higher-order equations are left unevaluated (inert). -
NDSolve(eq, y, limits, y0, steps?)— numerical solver for explicit scalar first-order initial value problemsy'(x) = f(x, y),y(x0) = y0, using a fixed-step fourth-order Runge–Kutta (RK4) method. It returns aListof[x, y]sample pairs over the interval given bylimits(aLimitsorTupleof(x, x0, x1)); the number of steps defaults to 100. It handles integrands with no elementary antiderivative (e.g. a Gaussian IVP whose solution is expressed withErf).
This slice is intentionally narrow so the API and result shape can get feedback before broader ODE support (adaptive RK45, systems, higher-order reductions, stiff and implicit solvers) is added.
-
-
\keyword{…}command for control-flow and logic keywords. Keyword constructs —if/then/else,for/from/to/do,where,such that,and,or,iff,for all,there exists,break,continue,return— can now be written with a dedicated\keyword{…}command, for example:\keyword{if} x > 0 \keyword{then} 1 \keyword{else} 0Unlike
\text{…},\keyword{…}keeps the input in math mode, and unlike\operatorname{…}it is rendered with symmetric keyword spacing. The existing\text{…}and\operatorname{…}spellings continue to work, and all three parse to the same expression. Multi-word keywords are written as a single token (e.g.\keyword{for all}).\keyword{otherwise}/\keyword{else}also serve as the default-branch marker inside acasesenvironment.A new
keywordStyleserialization option —"text"(default),"keyword", or"operatorname"— selects which spelling is emitted when serializingIf,Loop,Break,Continue, andReturnback to LaTeX. The default preserves the previous\text{…}output.
0.64.0 2026-06-27
New Features
-
Expanded number-theory library. A set of standard number-theoretic functions has been added to the
number-theorylibrary. Integer arguments use arbitrary-precision (bigint) arithmetic, and long-running cases honor the evaluation deadline.Factorization & divisors:
FactorInteger(n)— prime factorization as a list of[prime, exponent]tuples ordered by ascending prime:FactorInteger(360)→[(2, 3), (3, 2), (5, 1)]. Following Mathematica's conventions,FactorInteger(0)→[(0, 1)],FactorInteger(1)→[(1, 1)], and a negative integer carries its sign in a leading[-1, 1]tuple.PrimeFactors(n)— the sorted distinct prime factors:PrimeFactors(360)→[2, 3, 5].Divisors(n)— the sorted positive divisors:Divisors(12)→[1, 2, 3, 4, 6, 12].Divisors(0)is left unevaluated.Radical(n)— the square-free kernel (product of distinct primes):Radical(360)→30.PrimeNu(n)/PrimeOmega(n)— the number of prime factors without / with multiplicity (ω and Ω).MoebiusMu(n)— the Möbius function μ(n).DivisorSigma(k, n)— the divisor function σ_k(n) (generalizes the existingSigma0/Sigma1).IsSquareFree(n)— whethernis square-free.IsPerfectPower(n)— whethern = a^bfor integersa,b ≥ 2.
Primes:
-
NthPrime(n)— the nth prime (1-based):NthPrime(10)→ 29. (Mathematica names thisPrime, but in the Compute EnginePrimedenotes derivative notation, so the prime-number function isNthPrime.) -
NextPrime(n)/NextPrime(n, k)— the smallest prime greater thann; withk, the kth prime aftern(or the |k|th before it whenk < 0). -
PrimePi(n)— the prime-counting function π(n):PrimePi(10)→ 4. -
RandomPrime(n)/RandomPrime(m, n)— a random prime in the range.Primality for these uses exact 6k±1 trial division for small
nand switches to Miller–Rabin above 2³² (deterministic for the supported range), soNextPrimeandRandomPrimeare fast even for very large arguments.
Modular arithmetic & GCD:
PowerMod(a, b, m)— modular exponentiationa^b mod m; a negativebuses the modular inverse (undefined whenaandmare not coprime).ExtendedGCD(a, b)— the GCD with Bézout coefficients, as(g, x, y).ChineseRemainder(residues, moduli)— solves a system of simultaneous congruences (moduli need not be coprime).MultiplicativeOrder(a, n)— the order ofamodulon;PrimitiveRoot(n)— the smallest primitive root modn.JacobiSymbol(a, n)/LegendreSymbol(a, p)— the Jacobi and Legendre symbols.
Other primitives:
IntegerSqrt(n)— the integer (floor) square root.CarmichaelLambda(n)— the reduced totient λ(n).LucasL(n)— the nth Lucas number;CatalanNumber(n)— the nth Catalan number.BernoulliB(n)— the nth Bernoulli number as an exact rational, with the convention B₁ = -1/2.ContinuedFraction(x, n?)/FromContinuedFraction(list)— the continued-fraction expansion of a number (exact for rationals) and its inverse.IntegerDigits(n, base?, length?)/FromDigits(list, base?)— the digits ofnin a given base, and its inverse.DigitCount(n, base?, digit?)— digit-occurrence counts;DigitSum(n, base?)— the digit sum.
-
IsPrimeis now reliable for large integers. Primality was previously left unevaluated above ~10¹⁵ and could silently round integers beyond 2⁵³ to a wrong machine value.IsPrime(andIsComposite) now route through a single deterministic Miller–Rabin implementation shared with the number-theory library, so e.g.IsPrime(2^61 - 1)correctly returnsTrue. (The previous duplicate Miller–Rabin code, which used random bases and overflowed for large inputs, has been removed.) Relatedly, the internaltoIntegerhelper now returnsnullinstead of a precision-lost value for integers beyond the safe-integer range, so this class of silent-rounding bug cannot recur in the operators that use it for counts and indices. -
Factorial2,Subfactorial, andBellNumberno longer round a non-integer argument. These are defined only on integers; in non-strict mode they previously rounded a non-integer (e.g.Factorial2(5.5)returned6!!). They now stay symbolic for non-integer arguments. (In strict mode the(integer)signature already rejected such inputs.) -
N(expr, precision)evaluates to a requested number of significant digits. TheNfunction (and the["N", expr]MathJSON form) now accepts an optional precision argument:["N", "Pi", 50]returns π to 50 significant digits. When the requested precision exceeds the engine's working precision, the working precision is raised to match — and kept, since display precision is a global setting. When it is at or below the working precision, the result is rounded to that many significant digits without changing the global precision (N(1/3, 4)→0.3333). -
New linear-algebra operators.
Dot(a, b)— vector inner product / matrix product (Mathematica's.):Dot([1,2,3], [4,5,6])→32.Cross(a, b)— cross product of two 3-vectors.MatrixRank(m)— the rank (number of linearly independent rows/columns) via the rank–nullity theorem.MatrixPower(m, n)— a square matrix raised to an integer power (the repeated matrix productA·A·…, with negative powers using the inverse). Distinct from["Power", m, n], which threads element-wise.CharacteristicPolynomial(m, x?)— the monic characteristic polynomialdet(x·I − A)(variable defaults tox):[[1,2],[3,4]]→x² − 5x − 2.RowReduce(m)— the reduced row echelon form (RREF) of a matrix.IsSymmetric(m)/IsDiagonal(m)/IsSquareMatrix(m)— matrix-shape predicates returningTrue/False.
Resolved Issues
["N", expr]now numerically evaluates its operand. TheNoperator holds its argument unevaluated and previously called.N()on the still unbound operand — a no-op for symbolic constants — so["N", "Pi"]returnedPiunchanged (and["N", ["Sqrt", 2]]returnedSqrt(2)) instead of a numeric value. The operand is now bound before evaluation, making["N", expr]equivalent toexpr.N().
0.63.0 2026-06-26
New Features
-
LaTeX parse errors carry their source location. (contributed by zojize) The
Errorexpressions produced by the LaTeX parser now include asourceOffsets: [start, end]character range identifying where in the input the error occurred, so a consumer can map a parse error back to the offending span — e.g. to highlight an invalid token in a mathfield. Offsets are zero-based and end-exclusive into the serialized LaTeX (tokensToString); for input that round-trips through the tokenizer unchanged — editor-generated LaTeX, with no comments, Unicode normalization, or macro expansion — they match the original input string. Missing-operand errors (an empty\sqrt{}or\frac{}{}) use a zero-width range at the position where the token was expected. The newParser.sourceOffsets(startToken, endToken?)helper lets custom dictionary entries attach a range to errors they raise. The raw parser output (LatexSyntax().parse()) always carries these offsets, so anErrornode is now emitted in object form ({ fn: ["Error", …], sourceOffsets }) rather than the bare["Error", …]array whenever a range is available — a consumer matchingexpr[0] === "Error"should also handleexpr.fn?.[0] === "Error". Through the boxed path (ce.parse(latex).toMathJson()), source offsets are opt-in metadata likelatexandwikidata: included withmetadata: ['sourceOffsets']ormetadata: 'all', and omitted from the default serialization. -
Long numerators over a single power serialize with an inline solidus. When prettifying, a large numerator divided by a single power of a small base now serializes as
(3x^4+2x^3+x+5)/x^{23}instead of the tall, lopsided fraction\frac{3x^4+2x^3+x+5}{x^{23}}. This rounds out the existing prettify heuristics, which already factor a small denominator out of a large numerator (\frac{1}{x}(…)) and write a small numerator over a large denominator with a negative exponent ((a)(…)^{-1}). The new form applies when the numerator is large and the denominator is a single power of a small base —base^{k}with an integer exponentk ≥ 2(/x^{23}), a square (/x^2), or a square root (/\sqrt{x}). Lone powers (\frac{1}{x^{23}}), products in the denominator (a·x^n), compound bases ((x+1)^{23}), and all other shapes are unchanged. As with the other rewrites, it is disabled byprettify: false. -
Double-quoted string literals in LaTeX.
"hello"now parses to a string (previously"was anunexpected-token). Content is read verbatim up to the closing quote, with LaTeX commands normalized to Unicode like\text{…}("\alpha"→α); there is no escaping (use\text{…}for a string that must contain a"). Strings still serialize back to\text{…}. A"inside\unicode{…}/\charremains a hex prefix and is unaffected. -
Dictionary values can be read by key with
At.["At", dict, "key"](string key) now returns the value of that entry in a dictionary — e.g.["At", { dict: { height: 42 } }, "height"]→42. A missing key yieldsNothing. PreviouslyAtwas restricted to indexed (positional) collections and rejected dictionaries with anincompatible-typeerror; its value type is nowindexed_collection | dictionary. In LaTeX, the postfix bracket form accepts a string key, so\mathrm{data}["height"](or\mathrm{data}[\text{height}]) parses to["At", "data", "height"]. Dot-notation also works when the base is a symbol declared as a dictionary:\mathrm{data}.height→["At", "data", "height"](the key is an alphabetic, space-free name; for a dictionary base,.x/.realare key lookups, notFirst/Realcomponent access). Positional indexing of indexed collections is unchanged. -
BoxedExpression.referencedFunctionsandBoxedExpression.references. Two accessors aimed at dependency graphs (e.g. notebooks). The operator head of a function application — thefinf(x)org(x) := f(x) + 1— is not a symbol of the expression, so it appears in neithersymbolsnorfreeVariables;referencedFunctionsrecovers those applied user-function names (excluding built-in operators, constants, and names bound by an enclosing scope, using the same predicatefreeVariablesapplies to ordinary symbols).referencesis the complete in-edge set —freeVariables∪referencedFunctions, minusdefines— so it pairs withdefines(the out-edges) to build a use/def graph in one call. Subtractingdefinesdrops self-references, so a recursiveg(x) := g(x - 1)reports no dependency on itself. -
ce.declare()refines an auto-declared binding instead of throwing. Parsing auto-declares the names it encounters (a free variableaina + 1, a called functionfinf(x)), recording an inferred binding. Callingce.declare(name, …)for such a name now refines that inferred binding rather than throwing"… already declared in this scope"— which is exactly what theinferredflag is for. This lets a declare-first workflow parse cells to discover names and then declare them on the same engine. Re-declaring an explicit binding still throws, and a name bound to a value (e.g. a function argument) is still a genuine conflict.
Resolved Issues
canonicalandstructuraloptions are now honored byparse(),expr(), andfunction(). These methods only consulted theformoption when deciding how to box their result, so the documentedcanonical/structuralshortcuts were silently ignored:ce.parse(latex, { canonical: false })returned a canonical expression (and, as a side effect of canonicalization, auto-declared its symbols), andce.function('Power', ops, { structural: true })returned canonicalRootinstead of a structuralPower. The keys now resolve the same wayformdoes, with an explicitformtaking precedence. As part of this,ce.assume()now canonicalizes its predicate so the assumption machinery always sees a normalized form (e.g.Negate(ImaginaryUnit)folded to the complex literal-i) regardless of how the caller boxed it.
0.62.1 2026-06-22
New Features
indexStyleserialization option for collection indexing. TheAtoperator (e.g.["At", v, 1]) can now be serialized either as a subscript (v_1,M_{i,j}) or with programming-style brackets (v[1],M[i,j]). Like the other style options (fractionStyle,rootStyle, …) it is a callback(expr, level) => 'subscript' | 'bracket', settable engine-wide viace.latexOptions.indexStyleor per-call viaexpr.toLatex({ indexStyle }). The default is'subscript'.
Resolved Issues
-
Collection indexing (
At) now serializes to valid, round-tripping LaTeX.["At", v, 1]previously serialized to\lbrack v, 1\rbrack— i.e. the list[v, 1], which re-parsed as["List", v, 1], silently changing the meaning on a serialize→parse cycle. It now serializes asv_1(orv[1]withindexStyle: 'bracket'), both of which parse back toAt. -
Accents and decorations serialize with brace notation and round-trip.
OverHat,OverVector,OverTilde,OverBar,UnderBar, the over-arrows,OverBrace, etc. had no serializer and fell back to function-call notation —\hat{x}came back out as\hat(x), which re-parsed to["Multiply", x, ["OverHat"]]instead of["OverHat", x]. They now serialize as\hat{x},\vec{v},\overline{x}, … and round-trip correctly, including when subscripted (\hat{x}_0). -
Subscripted single-letter symbols serialize with an italic base instead of an upright one. When a symbol name carried a subscript (e.g.
a_1,x_n,S_t), the serializer chose its font style from the decorated string rather than the base: the subscript inflated the token count, so the multi-character rule wrapped the whole thing in\mathrm{…}and rendered the base letter upright (\mathrm{a_1}). A single-letter variable with a subscript is now rendered italic, as a variable should be —a_1serializes toa_1, not\mathrm{a_1}. The font style is now decided from the base alone: multi-letter bases are still upright with the wrapper enclosing the whole symbol, so descriptive subscripts stay roman (speed_max → \mathrm{speed_{max}}), and explicit style modifiers (\mathbf,\mathbb, …) are unchanged. Greek single-letter bases are likewise rendered with their default (italic) style.
0.62.0 2026-06-20
Resolved Issues
-
Arbitrary-precision sums of three or more terms no longer collapse to machine precision.
BigNumericValue.addhad a fast path that, when adding to a zero value, cloned the other operand through a constructor that reads its machine real part (decimal.toNumber()), silently truncating a full-precision bignum to ~16 significant digits. The exact (rational/radical) arithmetic path was unaffected, and two-term sums were unaffected, so this only surfaced when summing three or more inexact values at a precision above machine:ExactNumericValue.sumfolds those starting from a zero accumulator, and the very first0 + xᵢstep lost all extra precision. The degradation was invisible when the terms were of similar magnitude (the result was merely capped at ~16 digits), but became a wrong answer under cancellation — e.g. numerically evaluating a high-order symbolic derivative at a point (large factorial-scale terms cancelling to a small value) returned garbage at any working precision. The zero-accumulator path now reads the full-precision real part, matching the non-zero path. Coefficients were always computed exactly; only the final numeric summation was affected. -
High-order derivatives are reduced instead of blowing up. The
Derivativeoperator applies the differentiation rules iteratively, and the quotient and product rules square the denominator at each step, so the r-th derivative of a quotient carried anx^(2ʳ)-scale denominator — e.g. the 75th derivative ofsin(x)/xcame back overx^(2⁷⁵). The result was mathematically exact (the integer coefficients are computed exactly), but the enormous exponent made it unusable and overflowed toNaNwhen evaluated at a point.Derivativeof order ≥ 2 now runs a single simplification at the end, cancelling the common factors back to a linear-degree denominator (x^(2⁷⁵) → x⁷⁶). It is applied once, not per step, so it is cheap (~30 ms at order 75) and leaves first derivatives and the existing low-order results unchanged. -
interval-glslis now outward-rounded, making it a sound standalone exclusion oracle infloat32(preview). As shipped in 0.61.0 the_iv_*ops clamped to the sentinel range but rounded to nearest, so an operation — or the cell box itself — could come back slightly narrower than the true range. At a boundary that is enough to flip the exclusion verdict for a box the curve only grazes (e.g. the unit circle's tangent corner at(1, 0)), violating the containment contract that the GLSL interval must contain theinterval-js(float64) result — a spuriously narrow interval can exclude a box the curve actually passes through. Every inexact operation now widens its result outward (lotoward −∞,hitoward +∞) before the clamp: by ~1 ulp for the correctly-rounded ops (+ − ×,Square), and by a larger relative margin for the GLSL ES built-ins that are not correctly rounded — 8 ulp for/,Sqrt,Exp/Ln/Log, and inverse trigonometry, and 32 ulp forPower(x^nwithn ≥ 3, and fractional powers such as the astroidx^{2/3}). Crucially, the cell box thatcompileExclusionShader'smain()builds is itself outward-rounded (via the new_iv_widen_box): the float32mixthat constructs it rounds to nearest and is the actual source of the grazing miss, which per-op widening alone cannot fix (with exact endpoints the op chain is exact). That box pad is scaled to the domain extent, not the edge value, since that is what bounds themixerror — a value-relative pad would vanish for a box edge near 0 in a wide domain. Widening only ever moves a bound outward, so it cannot break soundness; theempty(lo > hi) /entire(±IV_INF) encodings, the finiteIV_INFsentinel, the per-op clamp, and exact empty-propagation are all preserved.Sin/Cosremain best-effort (see below). -
freeVariables/unknownsno longer report the bound variables ofFunctionliterals and integrals. A function literal leaked its own parameters, andIntegrate/Limitleaked their variable — e.g.freeVariablesoff(x) := x^2 + bwrongly included the parameterx, and a definite integral leaked its integration variable. They now return only genuinely free symbols ([b, f]for that definition,[]for∫ sin(x) dx), while a free coefficient is still reported (∫ a·sin(x) dx → [a]).Sum/Productwere already correct, andsymbolsis unchanged (it still includes bound variables). This is a behavior change for code that relied on the previous, over-inclusive result. -
Runaway user-function recursion now throws a catchable
CancellationErrorinstead of a nativeRangeError. A recursive definition with no reachable base case (e.g.f(x) := f(x-1) + 1) previously overflowed the JavaScript call stack with an uninformativeRangeError.recursionLimit— previously defined but never enforced — is now applied to user-function application: exceeding it throws aCancellationErrorwithcause: 'recursion-depth-exceeded', consistent with howtimeLimitanditerationLimitare surfaced. The defaultrecursionLimitis now 256 (was a nominal, unenforced 1024), chosen to fire below the native stack limit on typical engines; raisece.recursionLimitfor legitimately deep recursion. Iterating a user function (e.g.\sum f(i)) is not counted as recursion. (A sufficiently complex single call can still exceed the native stack before the limit is reached, so a robust caller catchesRangeErroras a backstop.) -
Integratebinds only the integration variable in its canonical integrand.∫ a·sin(x) dxpreviously canonicalized toIntegrate(Function(body, a, x), …), listing the free coefficientaas a spurious integrand parameter; it is nowIntegrate(Function(body, x), …). Introspecting the integrand (expr.op1) therefore reportsaas free, and the integrand is a proper single-variable function. Evaluation is unchanged. -
Nested (multivariate) integrals now parse and evaluate correctly.
\int_1^2\int_3^4 x y \, dx \, dypreviously attached all the trailing differentials to the innermost integral, leaving the outer integrals with aNothingintegration variable — so the expression could not evaluate. Each\intnow consumes only its own differential (the innermostdxpairs with the innermost\int, the nextdywith the next), producing a properly nestedIntegratewhere every level carries its own variable and limits (\iint/\iiintstill bind 2 / 3 variables at one level). Combined with the definite-integral evaluator now applying the limits to a parametric antiderivative (e.g.∫_3^4 k·x dx → 7/2·k; the symbolicf(b) - f(a)was previously left as an unevaluatedEvaluateAt), nested definite integrals evaluate to a value:∫_1^2∫_3^4 x·y dx dy → 21/4. -
Multiple-integral and contour-integral serialization round-trips.
\iint/\iiint(and\oiint/\oiiint) now serialize back to the compact sign with a single region subscript (\iint_{D}\!…) instead of a stack of\ints, so a flat multiple integral round-trips to the same structure. A separate long-standing bug that emitted the literal text\ointundefinedfor any\ointwith a region (its limit is a 3-elementTuple, serialized to MathJSON asTriple, which the serializer did not recognize) is also fixed:\oint_V f(s)\,dsnow serializes as\oint_{V}\!f(s)\, \mathrm{d}s. -
1^xsimplifies to1for any finite exponent. A symbolic or function exponent (e.g.1^{n+1},1^{\sin x}) previously leftPower(1, x)un-reduced because the canonicalizer bailed before its base-1 rule.1^x → 1now (matching SymPy / Mathematica); only a genuinely infinite or NaN exponent stays indeterminate (1^∞ → NaN, unchanged).
New Features
-
interval-glsl: public outward-rounding helpers and an opt-in absolute trig pad (preview). The widen helpers_iv_widen/_iv_widen_t/_iv_widen_pow/_iv_widen_sc/_iv_widen_box, and their epsilonsIV_EPS/IV_EPS_FN/IV_EPS_POW/IV_BOX_EPS, are a stable, public part of the emitted preamble: a renderer that builds its own cell box (instead of usingcompileExclusionShader) outward-rounds it by calling_iv_widen_box(vec2(lo, hi), extent)per axis, whereextentis the domain extent for that axis (the box pad is domain-scaled, not value-relative). The preamble is now emitted for any expression with free variables (not only ones that reference an_iv_*op), so those helpers are always available — e.g. for an axis linef = x. GLSL ESSin/Coscarry an absolute, implementation-defined error (≈2⁻¹¹ in the worst case; macOS ANGLE→Metal differs) that no relative pad can cover. A newtrigAbsPadoption (default0, off) oncompile(),IntervalGLSLTarget.compileExclusionShader(), and the newIntervalGLSLTarget.getPreamble()adds an absoluteSin/Cospad, so a trigonometric implicit curve can be a strictly-sound standalone oracle at the cost of fatter trig intervals. -
BoxedExpression.defines. A new accessor returning the symbols an expression defines: the target of a top-levelAssign/Declare(aina := 3,finf(x) := …), recursing throughBlock. It complementsfreeVariables(the symbols an expression references) — together they let tooling build a definition/use dependency graph, withreferences = freeVariablesminusdefines. -
ComputeEngine.appliedNonFunctions(latex). Returns the symbols written in function-application syntaxf(…)inlatexthat are not functions in the current scope, and so parse as implicit multiplication (f·x) or are left unresolved. The check is scope-aware (a symbol declared as a function is not reported) and has no side effects. Useful for flagging a likely call to an undefined function — e.g. warning thatf(x)was read asf·x.
0.61.0 2026-06-17
New Features
interval-glslcompilation target (preview). A GPU compilation target that evaluates an expression with interval arithmetic in GLSL — each value is avec2 (lo, hi)— so a robust implicit-curve renderer can run its per-cell exclusion test (lo > 0 || hi < 0) on the GPU instead of CPU-side viainterval-js. (Reinstates theinterval-glsltarget removed in 0.52, with a simplervec2-only representation — the GPU acts as an exclusion oracle and the CPU keeps curve extraction — instead of the former status-flag struct.)compile(expr, { to: 'interval-glsl' })emits_iv_*helper calls plus a preamble library. Coverage: arithmetic, integer and positive rational powers,Abs,Sqrt,Exp,Ln/Log/Lb, trigonometry / inverse trigonometry (Sin,Cos,Tan,Arcsin,Arccos,Arctan, with interval range reduction), and the step / rounding family (Floor,Ceil,Round,Truncate,Fract,Sign,Heaviside,Mod,Min,Max) — covering polynomial, rational, algebraic, trigonometric, and lattice/periodic implicit curves (conics, lemniscate, astroid, superellipse, trig lattices, floor/mod grids, …). Jump-discontinuity functions return a tight, sound value-range enclosure (so cells can still be excluded), with discontinuity classification left to the CPU; only genuine poles widen to the full range. A head that is not yet supported (e.g. hyperbolic functions) is reported in the result'sunsupportedfield, so a caller can fall back to another target per-expression. Values use a finite ±∞ sentinel and alo > hiencoding for the empty (domain-undefined) interval, propagated through every operation; domain-restricted functions (sqrt/ln/asin/rationalpowof an out-of-domain argument) yieldempty, and a pole (zero-spanning denominator,tanasymptote) yields the full range. Parity with theinterval-jstarget is verified against a shared corpus.IntervalGLSLTarget.compileExclusionShader()emits a complete, self-contained fragment shader (preamble + an_implicitinterval evaluator + a referencemainthat derives each fragment's cell box and applies the exclusion test) ready to drop into a WebGL2 renderer.
Resolved Issues
-
A function parameter now shadows a same-named constant. A parameter named like a constant (
i,e,Pi/\pi, …) was rewritten to the constant while the function body was canonicalized, so the binding was lost —λi. 2iapplied to5returned2i(the imaginary unit doubled) instead of10. Parameters now shadow whatever their name means in the enclosing scope — a constant, an assigned variable, or nothing — which is standard lexical scoping. A free symbol that is not a parameter is unchanged (ioutside a parameter is still the imaginary unit), and closure capture is preserved (λi. λz. (z + i)capturesicorrectly). -
compile()no longer emits a dangling reference to a symbol that has an assigned value (GLSL, WGSL, JavaScript, and interval-JS targets). When an expression referenced a symbol with an assigned value in the engine (ce.assign("a", 1.5)),compile()emitted a barea— an undeclared GLSL identifier (a shader that silently fails to compile) or a bare JS global (aReferenceErrorwhen the compiled function is called) — even though the symbol is omitted fromexpr.unknownsand folded byevaluate(). The value is now folded into the generated code (sin(a·x)→sin(1.5 * x)), makingcompile(),evaluate(), andunknownsconsistent. This also folds user-declared constants (ce.declare("c", { value: 3 })), and applies on the direct-targetcompile(expr, { target })path as well. A symbol supplied through thecompile()varsoption is never folded — the mapping always wins, so a per-frame GLSL uniform / JS argument keeps updating the result without recompiling — and a genuinely free symbol is unchanged. -
compile()folds a symbolic assigned value correctly, parenthesizing it and resolving the free symbols it references. When a symbol was assigned an expression rather than a number (ce.assign("b", ce.parse("c + 1"))), foldingbinto a larger expression had two bugs: the compound value was spliced in without parentheses, sob · xcompiled toc + 1 * x(i.e.c + x) instead of(c + 1) * x— a silently wrong result (2·b→2 * c + 1,b²→(c + 1 * c + 1)); and the inner free symbolc, hidden behindb's value and therefore absent fromexpr.unknowns, was emitted as a bare global (ReferenceErroron the JS target). The folded value is now parenthesized for its context, and a free symbol reachable only through a folded value routes through the normal free-symbol plumbing (_.con the JS / interval-JS targets; a uniform on GPU) and is reported in the result'sfreeSymbols. -
GPU compilation rejects non-finite numbers instead of emitting a non-compilable shader. GLSL and WGSL have no infinity or NaN literals, but
compile()emittedInfinity.0/NaN.0for a±∞orNaNvalue (e.g. from a literal\inftyor a constant-folded1/0) and reportedsuccess: true— a shader that silently fails to compile on the GPU. Such values now throw a clear error from the GLSL/WGSL targets (so the freecompile()falls back tosuccess: falsewith a diagnostic), consistent with how other GPU-unsupported constructs are handled. The JavaScript target is unchanged (Infinity/NaNare valid there). -
The JavaScript compilation target now lowers the exponential, trigonometric, and logarithmic integrals.
SinIntegral(Si),CosIntegral(Ci),ExpIntegralEi(Ei), andLogIntegral(li) compile to_SYSruntime helpers, matching the existing support forErf,FresnelS,Gamma,BesselJ, etc. These are the closed forms the antiderivative engine emits (e.g.∫ sin x / x dx = SinIntegral(x)), so an "evaluate then compile" pipeline — such as plotting∫ f dxfrom its closed form — no longer throwsUnknown operatorand falls back to numeric sampling. (GLSL/WGSL shader approximations of these are not yet provided.) -
The JavaScript compilation target now lowers the elliptic, AGM, and hypergeometric kernels.
AGM,EllipticK,EllipticE,EllipticF,EllipticPi,Hypergeometric2F1,Hypergeometric1F1,Erfi, andChoosecompile to_SYSruntime helpers. Like the integral functions above, these are closed formsevaluate()/.N()produces (e.g. a pendulum period or an arc length reduces to an elliptic integral), so they can now be plotted from the closed form rather than re-sampled numerically.EllipticEandEllipticPikeep their arity-overloaded complete/incomplete forms, andAGMaccepts the one-argumentAGM(z) = AGM(1, z)shorthand. (Real-valued like the other special functions on this target; GLSL/WGSL not provided.)
Improvements
-
compile()results now report their external references. ACompilationResultcarries two new fields so a caller can check that a result is self-contained declaratively, instead of executing or GPU-compiling the code to discover a dangling reference:freeSymbols— the identifiers the generated code references that the caller must supply at run time (JS vars-object keys / GLSL uniforms). These are the free symbols as codegen sees them: assigned values and constants are folded out, bound variables (lambda parameters,Sum/Product/Integrate/Loopindices,Blocklocals) are excluded, andvars-mapped symbols are always included. Unlikeexpr.unknowns, it also surfaces a free symbol reachable only through a folded value (e.g.bassignedc + 1exposesc). Use it to build a uniforms / vars mapping that is guaranteed consistent with the emitted code.unsupported— operator heads the target cannot lower (no operator/function mapping, not a structural form). On a failedcompile()this is populated alongside a human-readableerror, so an unlowerable operator (e.g.SinIntegralon the GLSL target) surfaces assuccess: falsewith a machine-readable list rather than only a thrown exception.
Built-in targets populate
freeSymbols(and an emptyunsupported) on every successful compile. The directgetCompilationTarget(name).compile(expr)path still throws on a genuinely unsupported operator (so the engine-levelcompile()can fall back to interpretation); theunsupported/errorfields are how the engine-levelcompile()reports that condition without a throw.
0.60.0 2026-06-16
Behavior Changes
-
isFiniteis now known for finite symbolic constants. Expressions such as√π,1/π, andπ^πreportexpr.isFinite === true(previouslyundefined), because finiteness is propagated throughSqrt,Root,Power, andDivideof finite operands. Cases that are genuinely indeterminate (e.g.1/xfor an unconstrainedx) still reportundefined. -
Exact transcendental expressions now remain symbolic under
evaluate(). For example,ln(2)remainsln(2)instead of becoming0.693…. Use.N()or{ numericApproximation: true }when a numeric approximation is wanted. Inexact inputs still evaluate numerically, and known exact values such ascos(π) = -1andarctan(1) = π/4still simplify. As a result, definite integrals also preserve exact results, such as∫₁² 1/x dx = ln(2)and∫₀¹ 1/(1+x²) dx = π/4. -
(aⁿ)ᵐno longer folds toaⁿᵐbased solely on an odd inner exponent. This combine was unsound on the principal branch: whena < 0andmis not an integer, the two sides differ by a phase. For example(x³)^{1/2}now stays√(x³)(which is8iatx = -4) instead of becoming the inequivalentx^{3/2}(-8i), and it is again confluent with the√(x³)form. The fold still applies when the base is non-negative or the outer exponent is an integer. (Roots are unaffected:(x³)^{1/3} = xstill holds, since odd-index roots use the real-root convention.) -
Logarithms are no longer combined across a branch cut.
ln(a) + ln(b) → ln(ab)(and thelogand subtraction variants) is only valid on the principal branch; for arguments on the negative real axis the two sides differ by a multiple of2πi. For exampleln(-2) + ln(-3)no longer simplifies to the inequivalentln(6)(its true value isln(6) + 2πi). The combine still applies to positive and unconstrained-symbolic arguments. The guard consults the analytic-property store's branch-cut records (see Special Functions). -
e^{iθ}stays in exponential form underevaluate()for a symbolic angle. Euler's formulae^{iθ} → cos θ + i·sin θis now applied only whenθis a constant that reduces to a closed form (e^{iπ/2} = i,e^{iπ} = -1,e^{ln y} = yare unchanged); for a symbolic angle,e^{ix}stayse^{ix}— a basis change is not an evaluation, and it no longer differs from the previous inconsistency where(e^{ix})²expanded whilee^{ix}did not. Convert to trigonometric form on demand with the new strategyexpr.simplify({ strategy: 'trig' }). -
N()at a known pole now returnsComplexInfinityinstead ofNaN. When a function is evaluated numerically at a pole recorded in the new analytic-property metadata store (see Special Functions), the result isComplexInfinityrather thanNaNor an unevaluated expression — for exampleDigamma(0).N()andDigamma(-2).N(). Functions whose kernels already returned an infinity at their poles (such asGamma) are unchanged.
Benchmarks
The numeric and symbolic gains in this release are summarized below against the
last release (0.59.0), SymPy, math.js, and Mathematica — the reference
baseline, since it is the broadest engine in the field. The tables are generated
by the harness in benchmarks/
(node benchmarks/report_changelog.mjs); every result is verified numerically
against an independent mpmath reference, never another tool. "CE 0.60.0" is
this release.
Numeric performance (200-digit precision)
Median time per call, in microseconds — lower is better. — means the tool
returned no usable result at that precision.
| Expression | CE 0.60.0 | CE 0.59.0 | SymPy | math.js | Mathematica |
|---|---|---|---|---|---|
\pi^2 | 15 | 20 | 174 | 107 | 3.9 |
\sin 1 | 25 | 61 | 220 | 429 | 5.2 |
\cos 1 | 24 | 60 | 222 | 455 | 7.1 |
\ln 2 | 87 | 302 | 339 | 4,374 | 3.7 |
e^{\pi} | 31 | 398 | 214 | 4,771 | 4.6 |
\zeta(3) | 3,419 | — | 264 | — | 49 |
\Gamma(\tfrac13) | 1,867 | 427,938 | 341 | — | 212 |
\psi(\tfrac13) | 1,689 | 404,300 | 2,831 | — | 169 |
Biggest gains over 0.59.0: \psi(\tfrac13) 239× faster,
\Gamma(\tfrac13) 229× faster, e^{\pi} 13× faster (it no longer
recomputes \ln e on every call), \ln 2 3.5× faster, \sin 1 / \cos 1
~2.5× faster. The elementary functions widen further at 1000+ digits (e.g.
\ln 2 ≈ 21× faster, where it now also leads SymPy and mpmath). 0.59.0 could
not reach 200 digits for \zeta(3) (it was capped near machine precision);
math.js has no arbitrary-precision ζ/Γ/ψ. Mathematica's native bignum kernel is
faster still on these constants.
Symbolic capability & performance
Each cell is how many times faster than Mathematica that engine is on the
case (Mathematica ÷ engine, so higher is better; Mathematica itself is
1×). — means the engine can't do the case. Compare the CE 0.60.0 and
CE 0.59.0 columns to see what is new this release (a — under 0.59.0
next to a number under CE 0.60.0). The CE + R/F column is CE 0.60.0 with
the opt-in Rubi integrator and Fungrim identities loaded (loadIntegrationRules
/ loadIdentities), on the same minified bundle: sometimes it improves
performance, sometimes it hurts it, but the overall effect is improved coverage.
| Operation | CE 0.60.0 | CE + R/F | CE 0.59.0 | SymPy | math.js | Mathematica |
|---|---|---|---|---|---|---|
| Antiderivatives | ||||||
\int\frac{1}{\sqrt x}\,dx | 1.5× | 3.7× | — | 0.5× | — | 1× |
\int\frac{x}{\sqrt{1-x^2}}\,dx | 2.5× | 2.6× | — | 0.09× | — | 1× |
\int\frac{1}{x^3+1}\,dx | 2.2× | 11× | — | 0.3× | — | 1× |
\int\frac{\sqrt x}{1+x}\,dx | — | 3.7× | — | 0.1× | — | 1× |
\int\frac{x}{(1+x)^{1/3}}\,dx | — | 3.9× | — | 0.01× | — | 1× |
\int\frac{x^2}{(1+x)^{1/3}}\,dx | — | 4.1× | — | 0.007× | — | 1× |
| Derivatives | ||||||
\tfrac{d}{dx}\sqrt{1-x^2} | 0.01× | 0.03× | 0.01× | 0.001× | 0.004× | 1× |
| Simplification | ||||||
\sqrt{3+2\sqrt2} | 11× | 20× | — | — | — | 1× |
\sqrt6\,x+\sqrt2\,x | 28× | 65× | 30× | 3.3× | 18× | 1× |
| Evaluation | ||||||
\lim_{x\to0}\tfrac{\sin x}{x} | 9.2× | 23× | — | 3.1× | — | 1× |
\lim_{x\to\infty}(1+\tfrac1x)^x | 1.6× | 1.6× | — | 2.1× | — | 1× |
\int_1^2\tfrac1x\,dx | 1996× | 1907× | — | 92× | — | 1× |
\int_{-\infty}^{\infty} e^{-x^2}\,dx | 106× | 428× | — | 2.5× | — | 1× |
| Solving | ||||||
x^4+x^2-1=0 | 0.07× | 0.08× | — | 0.06× | — | 1× |
x^3-x-1=0 | 0.08× | 0.1× | — | 0.04× | — | 1× |
Across the cases both solve, Compute Engine is a median 3.7× faster than
Mathematica (up to 1996×). The — entries under 0.59.0 show what is new
this release: limits, exact definite/improper integrals, and polynomial solving.
The bottom three antiderivative rows are integrals the base engine still leaves
unevaluated but the opt-in Rubi rules solve. Mathematica still leads on raw
derivative and root-finding latency (the <1× rows), where its native kernel is
hard to beat.
mpmath. Reproduce:
npm run build production && ./venv/bin/python3 benchmarks/gen_cases.py && node benchmarks/report.mjs && node benchmarks/report_changelog.mjs.Calculus
-
Limitcan now return exact symbolic results. This includes direct substitution, indeterminate quotients, rational functions at infinity, dominant-term analysis, and exponential forms. Examples includelim(x→0) sin(x)/x = 1,lim(x→∞) (1+1/x)^x = e, andlim(x→∞) arctan(x) = π/2. Limits that cannot be determined reliably fall back to numeric evaluation or remain unevaluated.NLimitremains numeric. -
Limits no longer return a wrong value at a special-function pole. A limit whose expression contains a special function (
Gamma,Digamma,PolyGamma,Zeta, …) evaluated at one of its poles — e.g.lim(x→-1) (x+1)·Digamma(x)— previously substituted the pole as a finite value and returned a confident wrong result (0). Such limits now stay unevaluated (or are recovered numerically where sampling allows) rather than reporting a false value. -
Symbolic integration supports many more integrands, including:
- Gaussian integrals and quadratic exponentials using
ErfandErfi - Fresnel integrals
- Sine, cosine, exponential, and logarithmic integrals
- Products of polynomials, exponentials, and trigonometric functions
- More radical and quadratic-root integrands
- Powers of secant, cosecant, tangent, and cotangent
- Reverse power-chain forms such as
∫ln(x)/x dx = ½ln²(x) - Products with symbolic exponents that previously failed or timed out
- Powers and radicals of a linear function, e.g.
∫√(1+x) dx,∫x√(1+2x) dx, and∫(a+bx)^p dx - Radical powers of a polynomial via the reverse chain rule, e.g.
∫x√(1−x²) dx = −⅓(1−x²)^{3/2} - Quotients by a sum of two square roots, e.g.
∫1/(√(a+bx)+√(c+bx)) dx, by conjugate rationalization - Absolute value of a linear argument, e.g.
∫|x| dx = x|x|/2and∫|ax+b| dx = (ax+b)|ax+b|/(2a)(valid for allx)
- Gaussian integrals and quadratic exponentials using
-
Rational-function integration is more exact and complete. Partial fractions now preserve rational and radical coefficients for a wider range of denominators, including
x³+1,x⁴+1,x⁴-1, and biquadratic polynomials. Several cases that previously returned incomplete results, floating-point coefficients, or no result now return exact antiderivatives. -
More improper integrals evaluate correctly. Exact results now include Gaussian, rational, and Fresnel integrals over infinite intervals. Numeric integration of convergent oscillatory integrals is also more reliable, while divergent or low-confidence cases remain unevaluated instead of returning a misleading finite value.
-
Fixed incorrect or missing antiderivatives for
sin²(ax+b),cos²(ax+b),√x,1/√x,1/√(1-x²), and related forms. -
New
Residue(f, x, a)operator computes the residue offatx = a(the coefficient of(x-a)⁻¹in its Laurent expansion). It detects the pole order and evaluates exactly via the symbolic limit engine, e.g.Residue(1/(x²-1), x, 1) → 1/2,Residue(eˣ/(x-1)², x, 1) → e, andResidue(cot(x), x, 0) → 1. Residues ofGamma,Digamma, andZetaat their poles use closed forms gated by the analytic-property store, e.g.Residue(Gamma(x), x, -2) → 1/2andResidue(Zeta(s), s, 1) → 1— including in a product or quotient with an analytic cofactor, such asResidue(Gamma(x)/(x-5), x, -2) → -1/14.
Algebra and Solving
-
solvehandles equations between two different inverse-trigonometric functions by applyingtanto both sides to clear them, then solving the resulting algebraic equation. For examplearcsin(x) = arctan(x) → 0andarccos(x) = arctan(x) → √((√5−1)/2). As part of this,√(f(x)) = g(x)with a non-linear right-hand side now solves too (e.g.√(1−x²) = x²). -
New
Solveoperator.Solve(equation, unknown)returns the list of solutions of an equation for an unknown, using the same solver as theexpr.solve()method — for example["Solve", ["Equal", "x^2", 1], "x"]returns["List", 1, -1]. The equation may be anEqualexpression or a bare expression read as= 0; the arguments are held, so the equation is no longer prematurely reduced to a boolean. -
solvenow handles general cubic, quartic, and higher-degree polynomials. Exact roots are still preferred; when no supported exact form is available, real roots are returned as numeric approximations. -
Absolute-value equations solve more reliably. This includes equations such as
|x| = 2,|x-1| = 2, non-linear arguments such as|x²-3| = 1, and equations with an absolute value on both sides. -
solvehandles more transcendental and substitution equations. Equations with equal exponential bases reduce by their exponents (e^{2-x²} = e^{-x} → -1, 2;2^x = 2^3 → 3);a·sin(x) + b·cos(x) = 0solves via the tangent (sin x = cos x → π/4); equations that are polynomials in a root of the unknown solve by substitution (2√x + 3·⁴√x = 2 → 1/16); and a single square root with a non-constant coefficient is eliminated by squaring (x = 1/√(x²+1)). -
Biquadratic and sparse-power equations return exact roots. Polynomials whose exponents share a common factor — such as
x⁴ + x² − 1— are solved by substitutingu = x²(orx³, …), so the roots are exact radicals (±√((√5−1)/2)) instead of numeric approximations. -
solvehandles equations that are polynomials in a single nonlinear generator, by substitutingu = g(x)for a logarithmic, exponential, trigonometric, or radical generatorg, solving foru, and inverting. For example(ln x)² = 4 → e², e⁻²,e^{2x} − 3eˣ + 2 = 0 → 0, ln 2, and√(ln x) = ln√x → 1, e⁴. -
solvefactors a zero product. When an equation is a product whose factors each involve the unknown — such asln(x)·(x − 1) = 0, or an already-factored(x + 1)·cos³(3x) = 0— its roots are the union of the roots of each factor. -
GCDnow finds common polynomial factors for univariate and multivariate polynomials. Integer operands retain their existing behavior; usePolynomialGCD()when an explicit polynomial result of1is needed for coprime inputs. -
New
Resultant(a, b, x)operator computes the resultant of two polynomials with respect to a variable (the Sylvester-matrix determinant). It is zero exactly when the polynomials share a common factor, e.g.Resultant(x² - 1, x - 1, x) → 0andResultant(x² + 1, x² - 1, x) → 4. Symbolic coefficients are supported:Resultant(x² + a, x + b, x) → a + b². -
Polynomial factorization is more complete and reliable. In particular,
Factor(xⁿ-1)now returns polynomial factors without introducing branch-dependent radicals, and the publicfactor()function once again factors expressions such asx²+5x+6. -
Nested radicals are simplified when possible, for example
√(3+2√2) = 1+√2.
Special Functions
-
Added numeric evaluation for:
- Complete and incomplete elliptic integrals:
EllipticK,EllipticE,EllipticF, andEllipticPi - The arithmetic-geometric mean
AGM Hypergeometric2F1,Hypergeometric1F1, andAppellF1- Jacobi theta functions and the Dedekind eta function
Erfi,SinIntegral,CosIntegral,ExpIntegralEi, andLogIntegral
- Complete and incomplete elliptic integrals:
-
Gammanow accepts a second argument, the upper incomplete gamma functionΓ(s, z) = ∫_z^∞ tˢ⁻¹ e⁻ᵗ dt(e.g.["Gamma", s, z]). It is evaluated numerically for real and complex arguments, including negative and fractional orderss(Gamma(-4, 2),Gamma(1/2, -1)), and honors the exactness contract: it stays symbolic underevaluate()and reducesΓ(s, 0)to the ordinaryΓ(s). Use.N()for a numeric value. The one-argumentΓ(z)is unchanged. -
Hypergeometric2F1now supports analytic continuation across most of the complex plane, rather than being limited to its defining power series. -
ZetaandGammanow honor the requested precision. At highce.precision, numeric evaluation ofZeta,Gamma,GammaLn,Beta,Digamma,Trigamma, andPolyGammapreviously stalled near machine precision (e.g.Zeta(3)was correct to only ~16 digits regardless of precision). They now return the full requested precision —Zetauses the Cohen–Villegas–Zagier acceleration, and all of these kernels compute with guard digits. -
EulerGamma(γ) now honors the requested precision. It was previously a fixed ~858-digit constant, so at higherce.precisionit silently stopped at ~858 correct digits (making identities such asDigamma(1) = -γappear wrong past that point). It is now computed on demand to the full working precision. -
Gammaand the polygamma family are dramatically faster at high precision (~340× at 300 digits —Gamma(1/3)≈1.9 s → ≈5 ms; ~130× at 1000 digits). The Stirling-series kernels (Gamma,GammaLn,Digamma,Trigamma,PolyGamma) were both shifting their argument just short of where the series converges (running far more terms than needed) and letting intermediate products grow in size without bound; the shift, term count, and per-step rounding are now chosen so the series converges quickly with bounded-size arithmetic. Results are unchanged to full precision. -
The Identities Library has been updated from 1,350 to 1,376 verified rules, including corrected Jacobi theta identities.
-
Modular and theta-function identities now discharge under
Im(τ) > 0. The upper-half-plane condition guarding these identities is expressed as the part inequalityIm(τ) > 0, so they apply once youassume(Im(τ) > 0)(previously an opaqueτ ∈ HHset membership was required). A new LaTeX shorthand,\mathbb{C}^+(also\C^+), denotes the open upper half-plane:z \in \mathbb{C}^+canonicalizes toIm(z) > 0. As a side effect three further identities became available — the derivative of the modular j-function and the θ₁/θ₂ logarithmic derivatives — recovered because the inequality form is verifiable where the opaque set was not. -
EisensteinE(s, τ)now evaluates numerically. The normalized Eisenstein series of even weights ≥ 2gets a numeric kernel (Lambert-series q-expansion in the upper half-plane), joiningJacobiTheta/DedekindEta. For exampleEisensteinE(4, i).N()is1.45576…,EisensteinE(2, i).N()is3/π, andEisensteinE(6, i).N()is0(an elliptic fixed point). Exact arguments stay symbolic underevaluate(); the kernel requiresIm(τ) > 0. -
New analytic-property metadata store.
ce.functionProperties(name)exposes per-operator analytic properties drawn from the Fungrim corpus — poles, zeros, branch points and cuts, residues, and holomorphic/meromorphic domains. For examplece.functionProperties('Gamma')?.polesis the setNonPositiveIntegers. Convenience accessors (poles,zeros,branchCuts,holomorphicDomain, …) return the unconditional record of each kind; parametric records (such as residues that depend on parameters) are available viaentries. This also powers pole-awareN()(see Behavior Changes).
Numeric Evaluation
-
Arbitrary-precision elementary and transcendental functions are substantially faster, especially at hundreds or thousands of digits. High-precision
πand trigonometric functions are no longer limited to about 2,350 digits. Square root is roughly twice as fast at 1,000+ digits (a giant-steps integer square root), the natural logarithm switches to the faster arithmetic–geometric-mean method from around 700 digits (previously ~1,250), and a power no longer recomputes the logarithm of its base on every call — at 1,000 digitsExp(x).N()is about three times faster, and a repeated base such as2^xor10^xabout 2.8 times faster. Results are unchanged. -
Odd roots of negative real numbers now use the real-root convention, so
Root(-8, 3)and(-8)^(1/3)evaluate to-2. -
N()of a non-unit rational power of a negative base no longer returnsNaN. Previously only unit fractions worked (they route throughSqrt/Root);(-4)^{3/2},(-8)^{2/3}, and similar fell through toMath.pow(negative, non-integer) = NaN. They now follow the same branch conventions as the roots above: an even denominator takes the principal complex value ((-4)^{3/2} = -8i, consistent withSqrt(-4) = 2i), and an odd denominator the real root ((-8)^{2/3} = 4,(-8)^{5/3} = -32, consistent with(-8)^{1/3} = -2). -
Exact
evaluate()of a non-unit rational power of a perfect power now reduces. Whenx^{p/q}has a real base and itsq-th root is an exact perfect power, it reduces to an exact value (8^{2/3} = 4,27^{2/3} = 9,(-8)^{5/3} = -32), extending the unit-fraction behavior (8^{1/3} = 2) to non-unit numerators and matchingN(). Non-perfect powers (2^{2/3}) and the negative even-root branch ((-4)^{3/2}, complex) stay symbolic underevaluate(). -
N()now fully evaluates applied functions and constants such ase,i, and expressions in Euler form. -
Complex equality and arbitrary-precision complex square roots are more robust in the presence of small rounding errors.
Collections and Matrices
-
Take,Drop,Slice, andCountnow operate on matrix rows consistently. For example,Count(matrix)returns the number of rows. -
Joinnow preserves list order, duplicates, and all elements when joining lists. Joining sets continues to produce a deduplicated set. -
Sums and products over ranges from
-∞to a finite bound, or from-∞to∞, now iterate over an appropriate finite approximation instead of an empty range.
Resolved Issues
-
Significant performance boost when many boxed expressions are involved in computations, due to improved handling of configuration changes and listener management.
-
Long-running evaluation is interruptible. Collection operations, number-theory functions, limits, differentiation, simplification, and integration now respect
ce.timeLimitmore consistently. Operations that cannot finish in time either throwCancellationErroror return the best numeric estimate available, as appropriate. -
Fractional powers and radicals now preserve the correct principal complex branch. This fixes several unsafe transformations involving negative or unknown-sign values, including
x/√(x²), negative factors under roots, products and quotients raised to fractional powers, and1/√u. -
Infinity arithmetic is more reliable for finite symbolic denominators, while indeterminate forms such as
∞/∞remain indeterminate. -
Numeric limits now reject overflow, catastrophic cancellation, oscillation, and other low-confidence results instead of returning spurious values.
-
Fixed hangs and crashes when factoring certain sums, simplifying expressions with radical coefficients, or mixing non-finite rational values with arbitrary-precision integers.
-
ce.number()now throws a helpful error when passed a MathJSON expression array; usece.expr()for expressions. -
Fixed incorrect simplification or evaluation of
2^i, division by a floating-point zero coefficient, and several exact expressions involving negative radicals. -
Fixed a rational function such as
1/(x(x²+x))wrongly simplifying (and integrating) to0when its factored denominator contained factors sharing a common root. The partial-fraction solver now detects the inconsistent system instead of returning a spurious all-zero decomposition. -
Factoris more complete: it now extracts a common monomial factor (e.g.x³+x² → x²(x+1),3x⁴+2x³ → x³(3x+2)) and fully factors already-factored products and powers, so partial-fraction decomposition sees irreducible factors with correct multiplicities. -
Partial-fraction decomposition now uses exact arbitrary-precision integer arithmetic, so decompositions of higher-degree denominators no longer lose precision (the previous machine-integer solver overflowed past 2⁵³ and could return wrong coefficients).
-
Rational functions with repeated linear or irreducible-quadratic factors now integrate to a closed form via full partial-fraction decomposition — e.g.
∫1/(x²(x+1)) dxand∫1/(x(1+x²)²) dx, which previously returned an unevaluated integral. -
Nested powers serialize to LaTeX and round-trip correctly. A
Powerwhose base is itself aPower— i.e.(aᵇ)ᶜ— was serialized asa^{bᶜ}, which re-parses asa^(bᶜ), a different expression. It now serializes as{aᵇ}^ᶜ, so e.g.(x³)^{2/5}round-trips instead of becomingx^{3^{2/5}}. -
GLSL/WGSL compilation no longer declares
int/i32for aBlock's local bindings. An integer-valued local (e.g.["Assign", "r", 3]) was declared asint r;while its value was emitted as a float literal (r = 3.0;), producing non-compilable shader code that also poisoned downstream float arithmetic. Scalar locals are now declared asfloat/f32— consistent with the always-float number literals and scalar shader math — and an explicit["Declare", "r", "complex"]type is honored. Complex locals still declare asvec2/vec2f. -
Loopnow compiles to JavaScript that returns its collected values. A value loop such asLoop(i², Element(i, Range(1, 5)))compiled to afor-loop IIFE with noreturn, so it evaluated toundefinedat runtime instead of the[1, 4, 9, 16, 25]the interpreter produces. The compiled loop now collects each iteration's value and returns the array. Imperative loops that mutate an outer accumulator or useBreak/Continue/Returnare unchanged. -
Integratenow compiles to JavaScript that returns a numeric estimate. For the common\int x^2 dxparse shape (where the integrand is aFunctionexpression), the integrand was wrapped in a double lambda ((x) => ((x) => x*x)), so the Monte-Carlo estimator never called the inner function and returnedNaN; it now compiles to a single lambda and returns the estimate (e.g.∫₀¹ x² dx ≈ 0.333). Integration bounds are also no longer floored, so non-integer limits such as∫₀^0.5integrate over the correct interval.
0.59.0 2026-06-10
This is a significant update to the Compute Engine.
The headline feature of this release is a large collection of curated mathematical identities, the Identities Library:
// When the Identities Library is loaded, CE can prove that...
console.log(parse("\\arctan(2-\\sqrt{3})").simplify().latex);
// ➔ "\frac{\pi}{12}"
// Declare that n is a positive integer...
ce.declare("n", "integer");
ce.assume(parse("n > 0"));
// ...and the parity identity applies:
console.log(parse("\\sin(\\pi n + \\frac{\\pi}{2})").simplify().latex);
// ➔ "(-1)^n"
Read more about the Identities Library in the dedicated guide.
This release also includes a large collection of performance improvements and bug fixes across the library.
This release includes some breaking changes.
Breaking Changes
-
replace()no longer eagerly canonicalizes the complete result. The requestedform, or the form produced by the rule, applies to replaced subexpressions. Call.canonicalon the result to restore the previous behavior. -
Fixed-size numeric collections now infer dimensioned types. For example,
[1, 2, 3]is nowvector<3>instead oflist<number>, and a 3×3 numeric collection ismatrix<3x3>.
Features
-
Curated mathematical identities: the new opt-in
loadIdentities()API loads over 1,300 guarded simplification rules and special values derived from Fungrim. Identities can be selected by topic, class, or purpose, and rules apply only when their side conditions can be proven.import { ComputeEngine } from '@cortex-js/compute-engine';import { loadIdentities } from '@cortex-js/compute-engine/identities';const ce = new ComputeEngine();loadIdentities(ce); // Or: loadIdentities(ce, { topics: ['gamma'] })ce.parse('\\Gamma(\\frac12)').simplify(); // → √πThe loader is synchronous and idempotent per engine. Importing the identities subpath is required, so applications that do not use it incur no bundle cost.
Simplifying with the full Identities Library loaded is now substantially faster:
simplify()runs at roughly 1.2–1.3× the unloaded baseline (previously ~1.6×). The many guarded rules that share a common arithmetic head —Multiply,Add,Divide, … — are dispatched together per head instead of one at a time, so the per-rule overhead on every arithmetic node is paid once per head rather than once per rule. Results are unchanged. -
More control over replacements:
ReplaceOptions.formcontrols the form of replacement expressions:'canonical','structural','raw', or a specific canonical transform. The previouscanonicaloption is deprecated and remains available as an alias for this release.ReplaceOptions.directionselects left-to-right or right-to-left traversal for order-sensitive rules.- Custom rules can now match user-defined function operators in
replace()andsimplify({ rules }).
-
Improved algebra:
solve()now handles quadratics with symbolic coefficients, includingx^2 - a x + 1 = 0and the generala x^2 + b x + c = 0. (#300)Factorinfers the variable of a univariate polynomial and preserves extracted numeric content:Factor(x^2 + 5x + 6)returns(x+2)(x+3), andFactor(6x + 9)returns3(2x + 3). (#309)
-
Parsing improvements:
- Two-argument
\arctan(y, x)and\tan^{-1}(y, x)now parse asArctan2. - LaTeX input is normalized to Unicode NFC, so decomposed identifiers parse like their precomposed equivalents.
- A trailing bare
\and trailing visual spacing commands are tolerated. - Multi-character subscripted identifiers such as
D_{etectsize}no longer collide with Euler derivative notation.
- Two-argument
Resolved Issues
-
Numeric evaluation and arithmetic:
- Corrected complex powers, reciprocals, roots, and logarithms, including
i^2,i^i, negative complex exponents, and even roots of negative reals. - Restored arbitrary-precision accuracy for roots,
exp(),ln(),mod(),gammaln(), and large integer conversion. Very small real results such asPower(10, -100).N()are no longer rounded to zero. - Exact
floor(),ceil(), andround()no longer lose digits beyond 2^53. Large decimal powers no longer report false overflow. - Division by zero,
NaN * 0, infinity comparisons, and signed infinities now behave consistently across numeric representations. - Corrected
Arctan2quadrants,ln(Root(a, b)), non-integer logarithm bases, exact radicals such assqrt(8), and division of Gaussian integers.
- Corrected complex powers, reciprocals, roots, and logarithms, including
-
Special functions and statistics:
- Added complex
GammaandGammaLnevaluation. Gamma and factorial poles at non-positive integers now returnComplexInfinity, while factorials of positive non-integers evaluate throughGamma(x + 1). - Improved
Erf/Erfcto machine precision and corrected small-argumentgammaln(). - Corrected
GCD,LCM,Congruent,Subfactorial, negative-indexFibonacci,IsOctahedral,Multinomial, andBellNumber. - Corrected skewness, kurtosis, interquartile range, histogram/bin endpoints, and exact combinatorial calculations.
- Added complex
-
Simplification, comparison, and assumptions:
- Indeterminate comparisons now remain unknown instead of becoming
false; this also improves sign inference,Boole, andKroneckerDelta. - Fixed equality handling for unordered expressions and multi-variable equation equivalence.
- Prevented invalid simplification of rational powers such as
(-x)^(3/4). - Set membership now remains undecided when a symbol's type is unknown, and
Subset,SubsetEqual,Superset, and empty-set relations use the correct direction. - Symbolic common factors are now recognized, and unresolved derivatives remain symbolic instead of recursing indefinitely.
- Indeterminate comparisons now remain unknown instead of becoming
-
Collections, matrices, and tensors:
- Corrected
Rest,Slice,Drop,Cycle,Position,SetFrom,TupleFrom,Filter,Zip, and compiledReducebehavior. - Determinants now work for matrices of any supported size, with exact integer results; inverses work beyond 2×2.
- Corrected matrix row access and the
isUpperTriangular,isDiagonal, andisTriangularpredicates. - Incompatible tensor broadcasts now throw instead of producing invalid data;
diagonal()respects its axis arguments, and mixed real/complex dtype joins preserve precision.
- Corrected
-
Types and serialization:
- Dimensioned list and matrix type strings now parse and round-trip, including
unknown dimensions, spaces, parenthesized element types, and single
^Ndimensions. - Corrected union reduction,
neversubtyping, narrowing of disjoint types, barematrixhandling, numeric literal subtyping, and invalid range validation. - String literals now remain strings after MathJSON round-trips, dictionary conversion retains every entry, and function literals can be applied directly.
- Plain symbols no longer report themselves as empty finite collections.
- Dimensioned list and matrix type strings now parse and round-trip, including
unknown dimensions, spaces, parenthesized element types, and single
-
LaTeX parsing and serialization:
- Corrected scaled/big delimiters, nested
\text{...}, repeating decimals beginning with., digit-like symbol names, prefixed-symbol errors, and unbalanced environment names. - Multiplication signs are now emitted where juxtaposition would merge numeric
factors, for example
3 \times 2^2instead of32^2. (#302) - Re-declaring a parser symbol with the same type no longer reports a conflict.
- Corrected scaled/big delimiters, nested
-
Compilation:
- Corrected JavaScript compilation of symbolic
Range, compound-bounded intervalSum/Product, and interpreted fallback for multi-argument lambdas. - Corrected Python parentheses for
(a^b)^c. - Corrected GLSL/WGSL output for
Degrees, complex multiplication,Gamma/Factorial/Beta/Erf, andIf/Which/When. - Corrected symbolic derivatives of
ArcsecandArccsc.
- Corrected JavaScript compilation of symbolic
-
Interval arithmetic:
- Restored conservative enclosures for multiplication involving zero and
infinity, negative-modulus
mod,clamp,binomial,gcd,lcm,gamma,gammaln,sinc, and Fresnel integrals.
- Restored conservative enclosures for multiplication involving zero and
infinity, negative-modulus
0.58.0 2026-05-12
Added
-
\operatorname{count}(L)lowercase alias — function-call form now parses to["Length", L], matching the existing dot-notation form (L.\operatorname{count}) and the other lowercase aliases (mod,var,shuffle,repeat,join). -
Repeat(value, count)2-arg form —Repeatnow accepts an optional integercountand evaluates to a finite list ofcountcopies ofvalue. The 1-argRepeat(value)keeps its existing infinite-sequence semantics. Materialization is gated byce.maxCollectionSize; larger values stay lazy (still accessible via.at()/ iterator). -
ce.maxCollectionSize— new configurable cap (default10_000) on the number of elements a collection may have when materialized into a concreteList. Assigning<= 0orInfinitydisables the cap (matchingiterationLimitandrecursionLimit). -
Sum(L)collection-reducer form —Sumnow accepts a single collection argument and reduces to the sum of its elements:["Sum", ["List", 1, 2, 3, 4, 5]] // ➔ 15. The big-op formSum(body, [i, a, b], …)is unchanged. TheSumhead is now preserved through canonicalization (previously rewritten toReduce(L, "Add", 0)), soL.\operatorname{total}round-trips cleanly withlatexOptions.dotNotation = true. The async path throwsCancellationErroron signal abort. -
Atextended with boolean-mask and integer-list indices —At(L, mask)wheremaskis a finite collection ofTrue/Falsereturns the elements ofLwhere the mask isTrue.At(L, indices)whereindicesis a finite collection of integers returns a sublist picked at those positions; out-of-range positions are filtered. Integer indices (At(L, 2)) and string keys (At(d, "key")) work as before. -
Function-application broadcasting for user-defined lambdas — when a user function with scalar-typed parameters is applied to a finite indexed collection, CE now broadcasts the call elementwise. For
ce.assign('f', ce.parse('x \\mapsto x^2 + 1')), the expression["f", ["List", 1, 2, 3]]evaluates to["List", 2, 5, 10]. Multi-arg functions broadcast with zip semantics, mixing scalars and lists naturally. The inferred default for\mapstolambdas is scalar parameters, so most user functions broadcast by default. To opt out, declare an explicit list parameter type viace.declare(name, '(list<X>) -> Y'). -
List type for mixed-kind and mixed-dimension elements —
widen()now builds a structural union when the common supertype would otherwise collapse to a lossy generic category (scalar,value,list,tuple,dictionary, …). Consumers can detect heterogeneous lists by inspectingexpr.type.toString():[1, 2, 3]→list<number>(precise)[1, "hello", 3]→list<finite_integer | string>(union)[(1,2), (1,2,3)]→list<tuple<finite_integer, finite_integer> | tuple<finite_integer, finite_integer, finite_integer>>(mixed dimension)[]→list<nothing>(empty)
-
ce.expr(true)/ce.expr(false)— JS boolean primitives now box to theTrue/Falsesymbols (previously fell through toUndefined). -
Lengthoperator definition —ce.operatorInfo('Length')now returns a valid entry. The evaluator returns an integer count for finite collections and leaves the expression unevaluated for non-collection or infinite inputs. -
Library entries for
Complex,Colon,Prime—ce.operatorInfo()now returns introspection data for these heads (previouslyundefined).Complexboxing is unchanged —["Complex", re, im]still produces aBoxedNumber. -
ce.symbolInfo(name)— new public API parallel toce.operatorInfo(), for introspecting constants and declared variables. Returns{ kind: 'constant' | 'variable', type: BoxedType }for symbols likePi,True,ExponentialE,ImaginaryUnit. Returnsundefinedfor unknown names and for operator heads. AddedSymbolInfotype to the public type surface.- Note:
Infinityis registered asPositiveInfinity/NegativeInfinity;Undefinedhas no value definition.
- Note:
-
ce.normalizeIdentifier(latex)— new public helper that converts a LaTeX identifier string to its canonical MathJSON name without side effects. Examples:R_{3}→R_3,f_{Bm}→f_Bm,\theta_x→theta_x. Inputs that aren't identifiers ('1 + 2', empty string) return''. Useful in importer pipelines that need to callce.declare()with normalized names before parsing referencing rows. -
First/Second/Thirdcompile entries — component access (p.x,p.y,p.z) now compiles cleanly. JS uses[0]/[1]/[2]index access; GLSL/WGSL use.x/.y/.zswizzles, assuming the argument compiles to avec2/vec3/vec4. 5+-element tuples (which compile tofloat[N]arrays) aren't supported. -
RangeGPU compile entry —Range(lo, hi[, step])with compile-time-constant bounds emits an inlinefloat[N](...)(GLSL) orarray<f32, N>(...)(WGSL) literal. Non-constant bounds throw a clear error directing the caller to materialize on the JS host and upload as a uniform. Sequence count is capped at 256 elements per call site. -
Variance/GCD/MedianGPU compile entries — GLSL+WGSL parity with their JS counterparts.Varianceis inlined (no size limit).GCDuses a preamble function implementing the Euclidean algorithm.Medianis supported for list sizes 2–8; lists with 9+ elements throw.
-
RandomGPU compile with deterministic seed —Random(seed)in GLSL/WGSL compiles to a hash-based pseudorandom.Random()(no args) in GLSL falls back to agl_FragCoord-derived seed (fragment-shader only); in WGSL it throws — callers must provide an explicit seed.- The fract-sin hash exhibits banding near
seed ≈ kπ. For high-quality shader random, use a more robust hash (e.g. PCG or xxHash). - JS-side
Randomis unchanged (stillMath.random, non-seeded). A seeded JS form will land in a future release.
- The fract-sin hash exhibits banding near
-
toSignedFunction()— new method onBoxedExpressionfor implicit-surface rendering and region classification:Equal(a, b)→a - b(zero on the surface)Less(a, b)/LessEqual(a, b)→a - b(negative when relation holds)Greater(a, b)/GreaterEqual(a, b)→b - a(negative when relation holds)NotEqual(a, b)→a - b- Non-relation expressions return
undefined.
Strictness and direction are encoded in
expr.operator. Note that CE canonical form normalizesGreaterEqualtoLessEqual(b, a)(and similarlyGreatertoLess), so callers will typically see theLess/LessEqualoperator on parsed expressions — the signed-function semantics are preserved. -
BoxedExpression.getInterval(symbol)— new method for extracting domain bounds from restriction expressions. ReturnsIntervalBoundswithlower/upper/lowerStrict/upperStrictforWhen(e, cond),And(c1, c2, …), and bare comparison expressions; returnsundefinedfor unsupported shapes. Useful for 2D-plot domain derivation (e.g. clippingy = f(x)\{0 < x < 5\}to[0, 5]). AddedIntervalBoundstype to the public type surface. -
Compact piecewise
\{cond_1 : val_1, …, default\}— now parses toWhich(c_1, v_1, …, True, default), the same head CE produces for\begin{cases}…\end{cases}. Disambiguated from set-builder\{x : type\}by inspecting the LHS of the top-levelColon: comparison/boolean heads (Less,Greater,Equal,And,Or,Not, …) → piecewise branch; otherwise → set-builder. Normal set literals (\{1, 2, 3\}) and set-builder via\midare unchanged.
Fixed
-
Linspaceendpoint inclusion —Linspace(a, b, n)now producesnpoints evenly spanning[a, b]inclusive of both endpoints (matching NumPy, Julia, and MATLAB). Previously the last sample fell short ofb(e.g.Linspace(0, 1, 5)yielded0, 0.2, 0.4, 0.6, 0.8instead of0, 0.25, 0.5, 0.75, 1).Linspace(a, b, 1)is the degenerate case and returns justa. Thecontainscheck is now tolerance-based (was an exact%test that failed for typical floating-point values). -
Heterogeneous-list type rendering — lists containing mixed kinds or mixed-dimension tuples previously rendered their type as
"[object Object]"in some paths (BoxedDictionary.type,collectionElementType). Types are now constructed programmatically. Lists containing tuples, sets, dictionaries, records, or strings are no longer misclassified as numericBoxedTensors.
Known issues
-
JS
Loopcompile producesundefined— the imperativefor-loop IIFE generated forLoop(body, Element(i, Range(lo, hi)))has noreturnstatement, so the compiled function returnsundefinedrather than the list of body values. Tracked for a future release. -
JS
Integratecompile producesNaN— whenargs[0]is aFunctionexpression (the common\int x^2 dxparse shape),compileIntegrateproduces a double-lambda, so_SYS.integratereceives a function-returning function. Tracked for a future release.
0.57.0 2026-05-10
Added
-
verbatimopt-in fortoLatex()—expr.toLatex({ verbatim: true })returns the original LaTeX source captured at parse time when the expression was parsed withpreserveLatex: true. Falls back to normal re-serialization if no verbatim is available (e.g. for synthetic or transformed expressions). The default behavior ofexpr.latexandexpr.toLatex()is unchanged — verbatim is strictly opt-in. Useful for round-tripping authored LaTeX (e.g.p.x,\sin(x)) without rewriting it to canonical form.- Verbatim is set only on the top-level boxed expression produced directly by
ce.parse(..., { preserveLatex: true }). Canonicalization,simplify(),evaluate(),subs(), andce._fn()produce fresh expressions withverbatimLatex === undefined. - Function expressions whose operator has a custom canonical handler (e.g.
Sin,Add) currently do not preserve top-level verbatim through canonicalization — the handler reconstructs the result without threading metadata. Atoms (symbols, numbers) and functions without custom canonical handlers (e.g.First) do preserve it. Useform: 'structural'to skip canonical handlers when verbatim preservation matters.
- Verbatim is set only on the top-level boxed expression produced directly by
-
dotNotationserialization option — when enabled (default off), member-access heads serialize to dot notation rather than function-call form:First(p)→p.x,Length(L)→L.\operatorname{count}, etc. Useful for round-tripping editor-authored dot-notation back to its source form. Set viace.latexOptions.dotNotation = trueor per-callexpr.toLatex({ dotNotation: true }). Only applies to arity-1 forms; multi-operand forms (e.g.Sumwith an index range) keep their standard serialization.- Serializer-only. The flag lives in
SerializeLatexOptionsand has no effect on parsing. All input forms continue to parse as before regardless of the flag:|L|,\operatorname{count}(L),L.\operatorname{count},\operatorname{length}(L)all still parse to["Length", L]whetherdotNotationis on or off. The flag only decides which form the serializer emits.
- Serializer-only. The flag lives in
-
Component access (
p.x,L.\operatorname{count},z.\operatorname{re}) — dot notation now parses to existing semantic heads at parse time. No generic accessor head was introduced.- Recognized members and their AST mapping:
x/y/z→First/Second/Third;real/re→Real;imag/im→Imaginary;count→Length;total→Sum;max→Max;min→Min. - Disambiguation: after a terminated integer or decimal,
.followed by a letter or\operatorname{...}is component access, not a decimal point. Examples:1.xparses as["First", 1](not a malformed decimal);1.5.xparses as["First", 1.5]. - Only
\operatorname{...}and bare-letter identifiers are recognized after..\mathrm{...}is not accepted (deliberately tight). Thirdis a new operator (parallelsFirst/Second) with signature(any) -> any.First/Secondwere widened from(collection) -> anyto(any) -> anyso component access on a non-collection (e.g.1.x) defers type-checking to evaluation; evaluation returns anErrorexpression for incompatible types.
- Recognized members and their AST mapping:
-
Restriction braces (
expr\{cond\}) — trailing brace predicates parse to a newWhenhead.f(x)\{0 < x < 2\}→["When", ["f", "x"], ["Less", 0, "x", 2]].- Stacked restrictions canonicalize:
expr\{c_1\}\{c_2\}→["When", expr, ["And", c_1, c_2]]. Downstream simplification, evaluation, interval intersection, and compilation see a single canonical shape regardless of source form. - Disambiguation from set literals is positional: standalone
\{1, 2, 3\}continues to parse as aSet;<expr>\{cond\}parses as aWhenrestriction. Allowed left operands include function calls, tuples, list/set literals, bare symbols, subscripted symbols, member access, power expressions, and chained restrictions. - Evaluator semantics:
When(e, True)evaluatese;When(e, False)returnsUndefined; indeterminatecondholds the form. - Serializer round-trips to the stacked-brace form (not
\wedgeinside one set of braces) so authored source and re-serialized output stay visually consistent. - JS and GLSL compilation: ternary
(cond ? e : NaN).
-
List-range ellipsis (
[1...9],[0, 0.1, ..., 1]) — ranges inside list literals parse to the existingRangehead.- Endpoint-only form:
[a...b]→["Range", a, b]. Triggers...,\ldots, and\dotsare all accepted. - Inferred-step form:
[a_0, a_1, ..., a_n]→["Range", a_0, a_n, step]wherestep = a_1 - a_0is inferred from the first sample pair. Intermediate samples are validated againsta_0 + k·stepwithince.tolerance; inconsistent samples produce a parse error. - The float idiom
[0, 0.1, 0.2, ..., 1]is supported (tolerance-aware comparison;0.1 + 0.1 ≠ 0.2exactly but is accepted within tolerance). - Outside
[...]brackets,\ldots/\dots/...continue to parse as theContinuationPlaceholdersymbol. The trigger is bracket context.
- Endpoint-only form:
-
For-comprehensions (
(x, y) \operatorname{for} x=L_1, y=L_2) — theLoophead now accepts multipleElementclauses, evaluated as nested loops with later bindings seeing earlier ones in scope.Loop(body, Element(x, L_1), Element(y, L_2), ...)produces anindexed_collection<T>of body evaluations, in row-major order.- For independent bindings this is the Cartesian product:
(x, y) \operatorname{for} x = [1...2], y = [1...2]→ 4 tuples. - For dependent bindings later clauses see earlier:
(x, y) \operatorname{for} x = [1...3], y = [1...x]→ 6 tuples (triangle, not Cartesian). - Precedence:
\operatorname{for}binds looser than,and=, tighter than;. So(x + y) \operatorname{for} x = L_1, y = L_2parses with bodyx + yand two bindings. - Bound names do not leak into the enclosing scope (uses
Scope.noAutoDeclare). - Legacy single-Element form continues to round-trip via the existing
\text{for } i \text{ from } a \text{ to } b \text{ do } bodysyntax. Multi-Element comprehensions serialize to the\operatorname{for}form.
-
Rangetype is now dynamic — element type narrows based on the step argument: integer step (or no step) yieldsindexed_collection<integer>; non-integer step yieldsindexed_collection<number>. Previously the type was alwaysindexed_collection<integer>, which was incorrect for float-step ranges. -
Whenhead — new conditional-value operator.When(expr, cond)returnsexprwhencondis true,Undefinedwhencondis false, and holds whencondis indeterminate. Used by restriction-brace parsing (see above) but also usable directly. -
ce.operatorInfo(head)— new method onComputeEnginefor introspecting registered operator heads. Returns{ kind: 'function' | 'opaque', signature?: BoxedType }orundefined.'function'— head has anevaluatehandler or acollectionhandler (lazy producers likeRange,Linspace,Tuplework via the latter).'opaque'— head is declared with a signature but has neither (e.g.,Triangle,Sphere,GeometricVector).undefined— no operator definition (constants likePiand unknown heads).- Lets external tooling classify heads by capability without maintaining a parallel list of supported operators.
-
toleranceinParseLatexOptions— populated automatically fromce.tolerancewhen parsing throughce.parse(). Used by list-range sample validation; available to other parse handlers that need tolerance-aware comparison.
Fixed
LoopwithElementclause — single-ElementLoop(body, Element(i, range))previously did not produce a list of body evaluations (the iteration path forElementform had a bug). The new variadic evaluator correctly yields aListof body values for each iteration.
0.56.0 2026-03-10
Added
-
First-class color values — colors are now typed values with a dedicated
colorprimitive type and per-colorspace constructor heads, rather than anonymous tuples.- Constructor heads:
Rgb,Hsv,Hsl,Oklab,Oklch. Each takes 3 components plus an optional alpha. Channels follow each colorspace's own conventions (RGB: 0–1 sRGB; HSV/HSL: hue in degrees, S/V/L 0–1; Oklab/Oklch: standard ranges). - LaTeX:
\operatorname{rgb}(...),\operatorname{hsv}(...),\operatorname{hsl}(...),\operatorname{oklab}(...),\operatorname{oklch}(...), parsing and serialization both directions. - Conversions:
AsRgb,AsHsv,AsHsl,AsOklab,AsOklchconvert any color to the named space (identity if already there). ColorDelta(a, b)— perceptual color difference (ΔE_OK, Euclidean distance in OKLab). Wide-gamut inputs are not clipped before measurement.
- Constructor heads:
-
JavaScript compile-target support for color values — all color constructors, the
As*converters,ColorDelta, andDistanceare supported. At runtime a color is a 3- or 4-element OKLCh array ([L, C, H]or[L, C, H, alpha]), matching the GPU target'svec3/vec4representation, so values move between JS, GLSL, and WGSL without conversion. -
Distance(p1, p2)— Euclidean distance between two points represented as tuples. Accepts any positive dimension; mismatched dimensions return a typed error. LaTeX trigger\operatorname{distance}(p1, p2). -
Geometric primitive heads —
Triangle,Sphere,Segment, andGeometricVectorare now recognized as typed function heads (no evaluator, preserved structurally for downstream consumers). LaTeX triggers\operatorname{triangle},\operatorname{sphere},\operatorname{segment},\operatorname{vector}(p1, p2).GeometricVectoris distinct from the existingVector(column-vector construction). -
Tohead registered —\toalready parsed to["To", a, b]but was classified asunsupported-operator; it is now a known typed head. -
Function-style aliases — lowercase
\operatorname{...}forms common in Desmos-style notation now parse to their existing capitalized operators:\operatorname{mod}→Mod,\operatorname{var}→Variance,\operatorname{shuffle}→Shuffle,\operatorname{random}→Random,\operatorname{repeat}→Repeat,\operatorname{join}→Join. -
ce.latexOptions— new mutable, engine-wide bag of LaTeX parse/serialize options (e.g.decimalSeparator,digitGroupSeparator). Available as a constructor option and as a read/write property:const ce = new ComputeEngine({ latexOptions: { decimalSeparator: '{,}' } });// or post-construction:ce.latexOptions = { decimalSeparator: '{,}' };These options are merged into every
ce.parse()andexpr.toLatex()call. Precedence (most-specific wins):LatexSyntaxinstance defaults <ce.latexOptions< per-call options. Previously, options likedecimalSeparatorcould only be changed per call post-construction (andexpr.latexcould not be customized at all).
Changed
Color('...')now returns anOklchhead instead of a 0–1 sRGBTuple. The string parser still accepts the same set of CSS-style inputs.ColorMixnow returns anOklchhead and mixes in OKLCh directly, preserving out-of-gamut chroma. Hue interpolation takes the shortest path around the wheel; mixing with an achromatic endpoint carries the other endpoint's hue (matches CSS Color 4color-mix).ContrastingColornow returns anRgbhead (was: 0–1 sRGBTuple).Colormapnow returnsOklchheads — either aList(Oklch, ...)or a singleOklchfor position-sampling.ColorToStringwith'oklch'format serializes typed color inputs without an sRGB round-trip; out-of-gamut chroma serializes losslessly.'hex'/'rgb'/'hsl'paths are unchanged.- Color-consuming signatures tightened —
(any, any)→(color | string | tuple, color | string | tuple)forColorDelta,ColorContrast,ColorMix,ContrastingColor,ColorToString,ColorToColorspace. TheAs*converters take(color) -> color.
Migration notes
Code that consumed the tuple output of Color('...'), ColorMix,
ContrastingColor, or Colormap now sees a typed color head. To get the
previous 0–1 sRGB shape, wrap with AsRgb:
// Before: const tuple = ce.expr(['Color', "'red'"]).evaluate(); // [r, g, b] in 0-1
// Now (equivalent 0-1 sRGB):
const rgb = ce.expr(['AsRgb', ['Color', "'red'"]]).evaluate();
// rgb is ['Rgb', r, g, b] with channels 0-1
Rgb head components are 0–1 sRGB across all layers (engine, JS compile,
GPU compile).
Fixed
-
Super-linear parse time on deeply-nested parametric expressions —
ce.parse()could exhibit exponential blowup on inputs like nested rotation matrices\left(\cos(\theta)\cdot S+\sin(\theta)\right)(depth 6 took ~44s). Two underlying causes were addressed: the type/sign cache onBoxedFunctionwas effectively disabled (causing every.typeaccess to recurse through all operands), andparseEnclosurewas speculatively trying matchfix definitions whose close-delimiter token wasn't even present in the input. Parse time on the affected inputs is now linear. -
ce.parse()ignored the injectedLatexSyntaxinstance'sdecimalSeparator—ce.parse()hardcodeddecimalSeparator: '.', silently overriding any value configured on aLatexSyntaxpassed via the constructor'slatexSyntaxoption. The injected instance's configured separator now takes effect end-to-end. -
expr.toMathJson({ metadata: ['latex'] })was silently dropped — passing a metadata array of specific fields (e.g.['latex']or['wikidata']) was ignored; onlymetadata: 'all'worked. The array form now correctly populates the requested fields. -
expr.toMathJson({ shorthands: ['all'] })disabled all shorthands — the['all']array form had the opposite of its intended effect. The string form'all'and explicit lists like['function']were unaffected.
0.55.6 2026-03-08
Resolved Issues
-
LaTeX parsing:
\limwith postfix operators —\lim_{x\to 0}\left(x\right)^xnow correctly parses asLimit(x^x)instead ofPower(Limit(x), x). The\limparser was usingparseArguments('implicit')which stripped the delimiters and left the^xunconsumed; it now usesparseExpressionso postfix operators are included in the limit body. -
LaTeX parsing: style, size, and color switch commands —
\displaystyle,\textstyle,\scriptstyle,\scriptscriptstyle,\tiny..\Huge(10 size commands), and\color{...}were silently discarded during parsing. They now produceAnnotatedexpressions that preserve the styling information and round-trip correctly through serialization. Added\scriptstyle/\scriptscriptstyleserialization support (previously only\displaystyleand\textstylewere handled). -
LaTeX parsing: set-builder notation —
\{x \in \R \mid x > 0\}now parses to["Set", expr, ["Condition", cond]]. Registered\midas an infix operator (Divides, precedence 160). The serializer round-trips set-builder notation correctly. -
LaTeX serialization:
Complement—["Complement", "A"]now serializes toA^\complementinstead of falling back to the generic function form. Removed stale@todocomments about a non-existent multi-argument case. -
LaTeX parsing: spacing commands —
\hspace{dim},\hspace*{dim},\hskip, and\kernare now consumed during parsing (previously caused "unexpected token" errors). These are treated as visual spacing and skipped. -
LaTeX serialization:
HorizontalSpacingmath classes — the 2-argument form["HorizontalSpacing", expr, "'bin'"]now serializes to\mathbin{expr}(and similarly forrel,op,ord,open,close,punct,inner). Previously the second argument was silently dropped. -
LaTeX serialization: redundant parens on matchfix operators —
wrap()no longer adds parentheses aroundAbs,Floor,Ceil,Norm, and other matchfix expressions that already have visible delimiters. -
LaTeX serialization: tabular environments — default environment serializer now renders matrix bodies (List of Lists) with
&column separators and\\row separators instead of nested function calls. -
LaTeX serialization: matchfix delimiter scaling — default matchfix serializer now respects
groupStyleto choose between bare delimiters,\left..\right, or\bigl..\bigrscaling. -
LaTeX parsing: Greek symbols in string groups —
\alpha,\beta, etc. inparseStringGroupContent()(used by\begin/\end, color arguments) are now interpreted as their Unicode equivalents instead of passing through as raw LaTeX commands.
0.55.5 2026-03-06
Resolved Issues
- Deep-zoom fractal precision — emulated-double (dp) and perturbation (pt)
shaders now compute per-pixel coordinates from
v_uvand viewport uniforms instead of the shader template's single-precisionmix(), which lost distinguishability at high zoom levels. - Perturbation theory: absolute vs delta coordinates — the perturbation
Mandelbrot/Julia handlers were passing absolute single-precision coordinates
to the shader instead of the small delta from the reference center. Fixed by
introducing
_pt_delta()which computes the per-pixel offset from viewport uniforms. compile()free function droppedhints— thehintsoption (viewport center/radius) was accepted but silently not forwarded to the language target. Fixed incompile-expression.ts.
New Features
BigDecimalexport — the arbitrary-precision decimal class is now exported from the public API for use by plot engines and other consumers that need precision beyond float64.HighPrecisionCoordtype — new union type (number | string | { hi: number; lo: number }) for passing extended-precision viewport coordinates through the compile API. Theviewport.centeroption now accepts this type instead of plain[number, number].
0.55.4 2026-03-06
Resolved Issues
- #254 LaTeX
parsing: interval notation with
\lbrack/\lparen— parsing\lbrack5,7)or\left\lbrack5,7\right)now correctly produces anIntervalexpression. Previously, when the open delimiter was a LaTeX command (e.g.,\lbrack), the parser incorrectly required the close delimiter to also be a LaTeX command (e.g.,\rpareninstead of)), causing mismatched-delimiter intervals to fail. - LaTeX parsing: invalid symbols in
\mathrm{}and related prefixes — invalid content inside\mathrm{},\operatorname{}, etc. (e.g.,\mathrm{=}or\mathrm{DavidBowie👨🏻🎤}) now produces the correctinvalid-symbolerror instead of cascading parse errors. Also fixedmatchPrefixedSymbolleaking parser state on failure, and emoji sequences are now properly recognized inside symbol prefixes (e.g.,\operatorname{😎🤏😳🕶🤏}).
New Features
- High-precision Mandelbrot/Julia compilation — the GPU compilation targets
(GLSL, WGSL) now support three precision tiers for fractal rendering, selected
automatically based on viewport hints:
- Single float (zoom < 10^6x): existing implementation, no overhead
- Emulated double (zoom 10^6x–10^14x): double-single (float-float) arithmetic using Dekker/Knuth algorithms, ~48-bit mantissa from two 32-bit floats
- Perturbation theory (zoom > 10^14x): reference orbit computed on CPU at
arbitrary precision via
BigDecimal, GPU iterates only the small delta from the reference, with glitch detection and single-float rebase fallback
- Viewport-aware compile API —
compile()accepts optionalhints: { viewport: { center, radius } }. The compiler auto-selects the precision strategy and returnsstaleWhenthresholds for cheap staleness checking by the plot engine. CompilationResultextensions — new optional fields:staleWhen(plain data staleness predicate),uniforms(scalar shader uniforms),textures(typed texture data with format/dimensions for GPU upload).
0.55.3 2026-03-05
Improved
- Compilation: constant folding —
Add,Multiply,Subtract,Negate,Divide,Power,Sqrt, andRoothandlers now fold numeric literals at compile time and eliminate identity values.x + yicompiles tovec2(x, y)instead ofvec2(x, 0.0) + (y * vec2(0.0, 1.0))2 + 3→5.0,x + 0→x,x * 1→x,x * 0→0.0Power(x, 2)→(x * x)for simple operands,pow(f(x), 2.0)for complex expressions to avoid duplicate computationPower(x, 0.5)→sqrt(x),Power(x, 0)→1.0,Power(x, -1)→(1.0 / x)Sqrt(4)→2.0,Root(x, 2)→sqrt(x)
isComplexValueduses expression type system instead of hard-coded operator list.- Integer arguments in GPU fractal functions emit as
200instead ofint(200.0). - Type-based optimizations — compilation handlers now use expression type
information for better code generation:
Floor/Ceil/Round/Truncateare no-ops when the operand is integer-typedAbsis a no-op when the operand is provably non-negativePower(x, 2)only expands to(x * x)for simple operands (symbols, literals) — function calls likePower(Sin(x), 2)usepow/Math.powto avoid duplicate evaluation- Integer
Modwith non-negative dividend uses plain%instead of the Euclidean double-mod formula - GPU variable declarations infer
i32/inttype for integer-typed locals
Resolved Issues
Abssignature: return type is nowrealinstead of propagating the input type (which incorrectly returnedcomplexfor complex inputs).- Compilation fallback: uses
pushScope/assignpattern instead of crashing when receiving a vars object.
New Features
MandelbrotandJuliaoperators in JavaScript and GPU compilation targets.
0.55.2 2026-03-04
Resolved Issues
\text{}flush bug:\text{a$x$b}now correctly produces["Text", "'a'", "x", "'b'"]. Previously the text before and after inline math were merged due to a missingflush()call inparseTextRun.#/*parsed as valid symbols: Bare#and*tokens were incorrectly accepted as valid symbol names because they match the UnicodeEmojiproperty (keycap base characters). They now produceunexpected-tokenerrors as expected. The fix excludes ASCII characters from the emoji regex in symbol validation.Textoperator type: TheTextoperator now has return typestringinstead ofexpression.\textcolorinside\text{}:\textcolor{red}{RED}inside\text{}now correctly parses the body as text ('RED') instead of switching to math mode and treating each letter as a separate symbol.parseSyntaxErrortoken consumption: Non-command tokens (like#,&) are now consumed when producing errors, preventing potential parser loops.parseSymbolTokenhardening: Raw tokens are pre-validated against\p{XIDC}before being consumed as symbols, providing defense-in-depth against futureisValidSymbolregressions.
New Features
- Text promotion: When
InvisibleOperatorcanonicalization encounters aTextexpression or a string operand, it now absorbs all operands into a singleTextexpression. For example,a\text{ in $x$ }bcanonicalizes to["Text", "a", " in ", "x", " ", "b"]instead of producing aTuple. - Text infix keywords:
\text{and},\text{or},\text{iff}, and\text{if and only if}are now recognized as infix operators that produceAnd,Or, andEquivalentexpressions respectively, following the existing\text{where}pattern. - Additional text keywords:
\text{such that}(maps toColon),\text{for all}(maps toForAll), and\text{there exists}(maps toExists) are now recognized as operators. Textserializer:Textexpressions now round-trip back to proper\text{...}LaTeX with inline$...$for math sub-expressions, instead of falling through to the default\mathrm{Text}(...)output.Textevaluate handler: Evaluating aTextexpression now concatenates all operands into a single string.
0.55.1 2026-03-04
Resolved Issues
- After
parse('f(x):=\\sin(x)'), the symbolfis now immediately recognized as having typefunction. Previously its type remainedunknownuntil theAssignexpression was explicitly evaluated. 2f(x)and2f \left(x\right)now both correctly parse as["Multiply", 2, ["f", "x"]]whenfis a known function symbol. Previously, a space before\leftcaused the parser to produce aTupleinstead ofMultiply, and expressions whose return type wasany(e.g., calls to generically-typed functions) were also misclassified asTuple.- Expressions involving operators that return
expressiontype (such asD,Simplify,Annotated) are now correctly treated as multiplicable in juxtaposition contexts. For example,2f'(x)now produces["Multiply", 2, ["D", ...]]instead ofTuple. - The
D(derivative) operator now returns a numeric type when its body is numeric, instead of always returning the genericexpressiontype. - Undeclared symbols followed by parenthesized multi-argument expressions (e.g.,
2g(x,y)) are now auto-declared as functions in all invisible operator paths, not just the two-operand path.
0.55.0 2026-03-04
Breaking Changes
ce.box()/box()renamed toce.expr()/expr()(ce.box()remains as a deprecated wrapper).- Removed
ce.latexDictionarygetter/setter; configure dictionaries throughnew LatexSyntax({ dictionary: [...] }). - Removed
ComputeEngine.getLatexDictionary(); import dictionary constants from package exports. - Removed deprecated type guard aliases:
isBoxedExpression,isBoxedNumber,isBoxedSymbol,isBoxedFunction,isBoxedString,isBoxedTensor(useisExpression,isNumber,isSymbol,isFunction,isString,isTensor). - Removed
LibraryDefinition.latexDictionary; LaTeX dictionaries now live in thelatex-syntaxmodule.
Resolved Issues
- #295 The
parse()free function now accepts the form options object, soparse("\\frac{10}{2}", { form: "raw" })return["Divide", "10", "2"]. - Undeclared symbols followed by parenthesized numeric expressions are now
interpreted as multiplication, not implicit function calls (for example,
q(2q)->2q^2). Function-call behavior remains for explicitly declared function symbols and non-numeric argument forms.
New Features
- Modular package exports for smaller bundles:
@cortex-js/compute-engine/core,@cortex-js/compute-engine/compile,@cortex-js/compute-engine/latex-syntax,@cortex-js/compute-engine/numerics, and@cortex-js/compute-engine/interval(with existing sub-paths still available, includingmath-json). - New standalone
LatexSyntaxAPI (class +parse()/serialize()helpers) for LaTeX ↔ MathJSON without aComputeEngineinstance. - New
ILatexSyntaxinterface exposed viaIComputeEngine.latexSyntaxto allow custom LaTeX parser/serializer implementations. - All 16 LaTeX domain dictionaries are now exported individually, plus the
combined
LATEX_DICTIONARY. Parsertype is now exported from the main package for typed customLatexDictionaryEntryparse handlers.
Changed
ComputeEnginenow accepts an injectablelatexSyntaxdependency.- Full package imports still auto-create a LaTeX syntax instance.
- Core-only imports do not bundle LaTeX support;
parse(),.latex, andtoLatex()require an injectedLatexSyntax. - MathJSON serialization omits optional LaTeX metadata when no LaTeX syntax is present.
decimal.jshas been replaced with a nativebigint-backedBigDecimalimplementation, reducing dependency surface and bundle size.BigDecimaladd(),sub(), andmul()are now exact; rounding is limited to operations that require it (div(), non-integerpow(), transcendentals).- Numeric string/LaTeX serialization now respects precision settings:
.latex/.toString()round toce.precision, while.json/toJSON()remain lossless. - High-precision special functions (
bigGamma,bigGammaln,bigDigamma,bigTrigamma,bigPolygamma,bigZeta) now scale withBigDecimal.precision; integer Gamma values are exact.
0.54.0 2026-02-26
-
New
expr.polynomialCoefficients()method: Returns the coefficients of a polynomial expression in descending order of degree, orundefinedif the expression is not a polynomial. Auto-detects the variable when the expression has exactly one unknown. SubsumesisPolynomial(check!== undefined) and degree computation (length - 1). -
polynomialCoefficients()now accepts an array of variables: Pass['x', 'y']to verify the expression is polynomial in all listed variables. Coefficients are decomposed by the first variable. -
New
expr.polynomialRoots()method: Returns the roots of a polynomial expression, orundefinedif not a polynomial. Handles degree 3+ polynomials with rational roots via the Rational Root Theorem. -
New
PolynomialCAS function: Constructs a polynomial from a coefficient list (descending order) and a variable. Inverse ofCoefficientList:Polynomial([1, 0, 2, 1], x)evaluates tox³ + 2x + 1. -
Improved
Factorfor degree 3+ polynomials:Factornow uses the Rational Root Theorem to factor polynomials with integer coefficients and rational roots. Previously only handled degree ≤ 2. -
Improved
Factorwith content extraction:Factornow extracts the GCD of integer coefficients before applying other strategies. For example,Factor(6x² + 12x + 6, x)now produces6(x+1)². -
New
PartialFractionCAS function: Decomposes rational expressions into partial fractions. Supports distinct and repeated linear factors, irreducible quadratic factors, and improper fractions (polynomial division performed first). Example:PartialFraction(1/((x+1)(x+2)), x)→1/(x+1) - 1/(x+2). -
New
ApartCAS function: Alias forPartialFraction. -
New
PolynomialRootsCAS function: Returns the roots of a polynomial as a set. Example:PolynomialRoots(x² - 5x + 6, x)→{2, 3}. -
New
DiscriminantCAS function: Returns the discriminant of a polynomial of degree 2, 3, or 4. Supports symbolic coefficients. Example:Discriminant(x² - 5x + 6, x)→1. -
simplify()auto-decomposes partial fractions: When aDivideexpression has a denominator already in factored form (product or power) and the decomposition is simpler,simplify()automatically applies partial fraction decomposition. -
Breaking:
CoefficientListnow returns descending order: The CAS functionCoefficientListnow returns coefficients from highest to lowest degree (e.g.,[1, 0, 2, 1]forx^3 + 2x + 1), matching the newpolynomialCoefficients()method and common external conventions. Previously it returned ascending order. -
expr.match()now accepts string patterns with auto-wildcarding: Pass a LaTeX string like'ax^2+bx+c'and single-character symbols are automatically treated as wildcards. Results use clean unprefixed keys ({a: 3, b: 2, c: 5}) with self-matches filtered out.useVariationsandmatchMissingTermsdefault totruefor string patterns. -
expr.match()now accepts MathJSON arrays directly: Pass a raw MathJSON pattern like['Add', '_a', '_b']without callingce.box()first. -
New
matchMissingTermsoption formatch(): When enabled, expressions with fewer operands than the pattern can still match by treating missing terms as identity elements (0 forAdd, 1 forMultiply). For example,3x^2+5matches the patternax^2+bx+cwithb = 0. Enabled by default for string patterns. -
Non-strict parsing: implicit superscript for letter+digit: In non-strict mode, a single letter immediately followed by a digit 2–9 is parsed as an exponent:
x2 + y2→x^2 + y^2. Handles common copy-paste from web pages. Only digits 2–9, only single ASCII letters, and only when adjacent (no space).
0.53.1 2026-02-25
-
timeLimitnow reliably interrupts long-running evaluations:Factorial,Sum,Product,Loop, andReduceall respect thetimeLimitproperty and throwCancellationErrorwhen the deadline is exceeded. Previously, generators yielded too infrequently (every 1,000–50,000 iterations), allowing a singlegen.next()call to block for longer than the timeout. All generators now yield every iteration. TheFactorialhandler no longer silently swallowsCancellationError, andwithDeadline/withDeadlineAsyncnow usetry/finallyto always reset the engine deadline. -
Fixed GPU compilation of
Sum,Product,Loop, andFunction: These constructs no longer leak JavaScript-specific syntax (IIFEs,let,while, arrow functions,{ re, im }objects) into GLSL/WGSL output.Sum/Productwith small constant bounds are unrolled inline; larger ranges emit nativeforloops.Loopemits a GPUforloop withint/i32index.Function(lambda) now throws a clear error for GPU targets. Block-levelDeclarestatements infervec2/vec2ftype from subsequent complex-valued assignments. -
Added GLSL/WGSL compilation for
Heaviside,Sinc,FresnelC,FresnelS,BesselJ: These five special functions now compile to GPU shader targets.FresnelC/FresnelSuse a three-region rational Chebyshev approximation (ported from Cephes/scipy) with a shared_gpu_polevlhelper.BesselJuses power series, Hankel asymptotic, and Miller's backward recurrence depending on the argument range. Both GLSL and WGSL preambles are emitted on demand. -
Fixed GLSL/WGSL block expression compilation: Block expressions (produced by
\coloneq/ semicolon blocks) now emit valid GPU shader code instead of JavaScript syntax. Variable declarations usefloat x(GLSL) orvar x: f32(WGSL) instead oflet x, and blocks are emitted as plain statements instead of JavaScript IIFEs.compileFunctioncorrectly formats multi-statement bodies. -
Fixed
\;in\text{where}clauses: Visual spacing commands like\;,\,,\quad, etc. between comma-separated bindings in where-clauses are now correctly skipped instead of being parsed asHorizontalSpacingexpressions wrapped inInvisibleOperator. -
Fixed
require()returning empty exports on Node 22+ (#292): Because the package sets"type": "module", Node treated the UMD.jsfiles as ESM, breaking the UMD factory pattern. The UMD builds now use a.cjsextension so Node always treats them as CommonJS.
0.53.0 2026-02-21
Runtime and Scoping
-
True lexical scoping for
Functionexpressions: Functions now capture their defining scope and resolve free variables from that scope chain (not the call site), with a fresh child scope on each call. -
BigOp scope pollution fixed:
Sum,Product, and other big operators now only declare their index variable locally. Other names are declared in the enclosing scope vianoAutoDeclare. -
Closure capture for nested functions: Returned functions now correctly capture outer parameters across multiple nesting levels.
-
EvalContext.valuesremoved: Symbol values now live only inBoxedValueDefinition.value. The per-frame shadow map andwithArgumentsoption were removed. -
forget()now resets values set byassume():forget('x')now clears values introduced byassume('x = ...')(value reset toundefined), in addition to clearing assumptions.
Expressions and Equality
-
expand()now returns the input expression instead ofnull: Both the free function and internalexpand()/expandAll()now return the original expression when no expansion is possible. -
New
.toRational()method: Returns[numerator, denominator]integers for rational expressions, ornullotherwise. -
New
.factors()method: Returns multiplicative factors as a flat array by decomposingMultiplyandNegatestructurally. -
.is()now tries expansion: After structural comparison,.is()expands both sides before numeric fallback, catching forms like(x+1)^2andx^2+2x+1. -
.is()is now symmetric:a.is(b) === b.is(a)now holds across all expression types.
LaTeX Parsing
-
Parse
\mleft/\mrightdelimiters: Alternative delimiters from themleftrightpackage are now treated like\left/\right. -
Parse
\colorin math mode:\color{...}is now recognized in math mode; the color argument is consumed so the following math parses normally. -
Parse
:and\colonas infix operators: Outside quantifier contexts, a bare:/\colonnow parses asColon(e.g.f:[a,b]\to\R), without affecting:=assignment or quantifier syntax. -
Parse
\dfrac,\tfrac, and\cfracas fractions: These variants now parse the same as\frac.
Fractals
- New
MandelbrotandJuliafunctions: Added built-in escape-time fractal operators.Mandelbrot(c, maxIter)andJulia(z, c, maxIter)return a smooth, normalized value in[0, 1](1for interior points, fractional for escaping points vialog₂(log₂(|z|²))smoothing). Both evaluate in JavaScript and compile to GLSL/WGSL.
0.52.1 2026-02-19
Expressions
-
Exact number literal check: Use
isNumber(expr) && expr.isExactto test for exact numeric literals. -
rawform preserves subtraction:x-1now parses as["Subtract", "x", "1"](instead of["Add", "x", -1]) when using raw form.
Parsing and Blocks
-
Fix
;\;parsing in semicolon blocks: Spacing commands after semicolons (\;,\,,\quad, etc.) no longer create spuriousNothingoperands. -
Fix
\text{if}parsing with\;spacing:\text{if}\;...\;\text{then}\;...\;\text{else}\;...now parses correctly asIf. -
Block serializer now uses
;: Serialization emits;(not;\;) to avoid reintroducing spacing-related parse issues on round-trip. -
Block compiler filters
Nothingoperands: The Block compiler now removesNothingsymbols and empty compile results before generating code. -
Subscripted variable names in blocks: Names like
r_1are treated as compound symbols (notSubscript) when the base is not a known collection. -
Non-strict parser supports exponents on bare functions: In
strict: falsemode, forms likesin^2(x)andcos^{10}(x)now parse correctly as powers. -
Unicode superscript/subscript digits supported: Superscript and subscript Unicode digits now normalize to
^{...}/_{...}in parsing.
Compilation
-
Selective GLSL interval preamble:
interval-glslnow emits only used helper functions (plus dependencies), typically reducing preamble size by 60-80%. -
Selective WGSL interval preamble:
interval-wgslnow applies the same used-only preamble strategy. -
Fix recursive GLSL gamma helper: Replaced recursive
_gpu_gamma()reflection logic (illegal in GLSL) with a non-recursive implementation.
Equality
-
.is()now works with assigned variables: Numeric fallback now applies to expressions with no free variables, including variables with assigned values. -
.is()now accepts an optionaltolerance: A per-call tolerance can overrideengine.tolerancefor numeric comparison.
0.52.0 2026-02-18
New Features
-
Smart
.is()/ exact.isSame()separation: The.is()and.isSame()methods on expressions now have distinct roles:-
.isSame(v)— Fast exact structural check. No evaluation, no tolerance. Now accepts primitives (number,bigint,boolean,string) in addition toExpression. This is the method used internally throughout the engine. -
.is(v)— Smart check with numeric evaluation fallback. Tries.isSame()first; if that fails and the expression is constant (no free variables), evaluates numerically and compares withinengine.tolerance. For literal numbers, behaves identically to.isSame()— tolerance only applies to expressions that require evaluation.
This resolves a common pain point where
ce.parse('\\cos(\\pi/2)').is(0)returnedfalsebecause.is()was purely structural. Now it returnstrue:ce.parse('\\sin(\\pi)').is(0); // true (evaluates, within tolerance)ce.parse('\\cos(\\frac{\\pi}{2})').is(0); // truece.number(1e-17).is(0); // false (literal number, no tolerance)ce.parse('x + 1').is(1); // false (not constant, no fallback) -
-
numericValue()convenience helper: New standalone function that combines theisNumber()guard with.numericValueaccess. Returns the numeric value if the expression is a number literal, orundefinedotherwise. Useful for safely extracting numeric values without verbose ternary patterns:import { numericValue } from '@cortex-js/compute-engine';// Beforeconst val = isNumber(expr) ? expr.numericValue : undefined;// Afterconst val = numericValue(expr); -
Stochastic equality check for expressions with unknowns:
expr.isEqual()now uses a stochastic fallback when symbolic methods (expand + simplify) can't prove equality. Both expressions are evaluated at 50 sample points (9 well-known values + 41 random) and compared with relative+absolute tolerance. This detects equivalences likesin²(x) + cos²(x) = 1,(x+y)² = x²+2xy+y², andsin(2x) = 2sin(x)cos(x)that were previously returned asundefined. Singularities (NaN at a sample point) are skipped rather than treated as disagreements. The check also works when the two expressions have different unknowns (e.g.x - x + yvsy). -
expr.freeVariablesproperty: New property onBoxedExpressionthat returns the free variables of an expression — symbols that are not constants, not operators, not bound to a value, and not locally scoped by constructs likeSumorProduct. Semantically identical toexpr.unknowns. -
New interval-js compilation functions: Added
Binomial,GCD,LCM,Chop,Erf,Erfc,Exp2,Arctan2, andHypotto the interval-js compilation target, with corresponding interval arithmetic implementations. -
GLSL/WGSL variable exponent support: The interval GLSL and WGSL targets now support
Powerwith variable exponents (e.g.(-1)^k,x^n). Previously these threw at compile time. Addedia_pow_interval()to both GPU library preambles using four-cornerexp(exp * ln(base))evaluation with special cases for point-integer exponents and(-1)^n. -
Factorial,Gamma,GammaLnfor GLSL/WGSL interval targets: Addedia_factorial(viaia_gamma(x+1)) to both GPU targets. Addedia_gamma(Lanczos approximation) andia_gammaln(Stirling asymptotic) to the WGSL target, matching existing GLSL implementations.
Resolved Issues
-
parse()withform: 'structural'ignored the structural flag: Thestructuraloption fromformToInternal()was dropped inparseLatexEntrypoint(), makingce.parse(s, { form: 'structural' })behave identically to{ form: 'raw' }(unbound, unsorted). Now correctly produces a bound, structural expression. -
Partial canonicalization with
'Flatten'form folded numerics: Usingce.parse(s, { form: ['Flatten', 'Order'] })unexpectedly evaluated numeric operands (e.g.3×2+1became7) becauseflattenForm()usedce.function()which defaults to full canonical mode. Now usesce._fn()to preserve operand structure. This enables structural comparison of expressions modulo commutativity and associativity without numeric evaluation — useful for checking the method used to solve a problem rather than just the numeric result:const a = ce.parse('3\\times2+1', { form: ['Flatten', 'Order'] });const b = ce.parse('1+2\\times3', { form: ['Flatten', 'Order'] });a.isSame(b); // ➔ true (same structure, different order)const c = ce.parse('7', { form: ['Flatten', 'Order'] });a.isSame(c); // ➔ false (different structure) -
Sum/Product with symbolic bounds compiled incorrectly: Expressions like
\sum_{k=0}^{n} f(k, x)where the upper bound is a variable produced loops that iterated 10001 times instead of using the variablen. The compilation extracted bounds vianormalizeIndexingSet()which converted symbolic bounds toNaNand fell back to a hardcoded limit. Now bounds are extracted as expressions and compiled to code (e.g.Math.floor(_.n)for JS,Math.floor((_.n).hi)for interval-js). This fixes Taylor series patterns like\sum_{k=0}^{n} \frac{(-1)^k x^{2k+1}}{(2k+1)!}for both JS and interval-js targets. -
Interval
(-1)^kreturnedemptyinstead of correct value: ThepowInterval()function required positive bases for variable exponents, causing(-1)^kpatterns in summations (e.g. Taylor series) to fail at runtime. Now correctly delegates tointPow()when the exponent is a point interval with an integer value, preserving even/odd parity. Also handles the case where base is-1and the exponent spans multiple integers by returning the conservative interval[-1, 1]. -
Factorialmissing from interval-js compilation target: Expressions containingn!(e.g.\frac{(-1)^k x^{2k+1}}{(2k+1)!}) failed interval-js compilation withsuccess: false. AddedFactorialandFactorial2interval functions and compilation handlers. -
expr.unknownsincluded bound variables: Scoped constructs likeSum,Product,Integrate, andBlockbind index variables in a local scope, butexpr.unknownswas reporting them as free unknowns. For example,\sum_{k=0}^{10} k \cdot xreturned["k", "x"]instead of["x"]. Now correctly excludes locally bound variables from the result. -
Symbolic upper bounds missing from
expr.unknowns: In expressions like\sum_{k=0}^{M} k \cdot x, the symbolic upper boundMwas incorrectly excluded fromunknownsbecause the scope's bindings map captured all symbols referenced during canonicalization. Now extracts bound variables structurally fromLimits/Element/Assign/Declareexpressions, so only true bound variables are excluded. This also fixesBlockexpressions where locally assigned variables (viaAssignorDeclare) were reported as unknowns. -
Integratewith symbolic bounds compiled incorrectly: Same issue as Sum/Product —compileIntegrate()usednormalizeIndexingSet()which converted symbolic bounds toNaN. Now usesextractLimits()and compiles bounds as expressions. -
Interval
piecewisetest fix: Fixed test that incorrectly accessedresult.lodirectly instead of unwrapping theIntervalResultenvelope (result.value.lo). Thepiecewise()function correctly returnsIntervalResultobjects.
0.51.1 2026-02-15
Features
- #172 Degrees-Minutes-Seconds (DMS) notation: Parse and serialize
geographic angle notation such as
9°30'15". The LaTeX parser now recognizes arc-minute (',\prime) and arc-second (",\doubleprime) symbols when they follow a degree symbol, producingAdd(Quantity(…, deg), Quantity(…, arcmin), …)expressions that evaluate and simplify through the existing unit system. Negative angles (e.g.-45°30') are fully supported for latitude/longitude coordinates. dmsFormatserialization option: SetdmsFormat: trueinSerializeLatexOptionsto serialize angle quantities as DMS notation (e.g.Quantity(9.5, deg)→9°30').angleNormalizationserialization option: Normalize angles during serialization with'0...360'(useful for bearings) or'-180...180'(useful for longitude). Default is'none'.realOnlycompilation option: Pass{ realOnly: true }tocompile()to automatically convert complex{ re, im }results to real numbers — returnsrewhenim === 0,NaNotherwise. Useful for plotting and other contexts that only need real-valued output.Sincfunction: Unnormalized cardinal sinesinc(x) = sin(x)/xwithsinc(0) = 1. Includes LaTeX parsing via\operatorname{sinc}, JavaScript and interval-arithmetic compilation targets.- Fresnel integrals (
FresnelS,FresnelC): Numeric evaluation using Cephes rational Chebyshev approximation, LaTeX parsing via\operatorname{FresnelS}/\operatorname{FresnelC}, JavaScript and interval-arithmetic compilation targets. Heavisidestep function:H(x) = 0forx < 0,1/2forx = 0,1forx > 0. LaTeX parsing via\operatorname{Heaviside}, JavaScript and interval-arithmetic compilation with singularity detection at zero.
LaTeX Syntax
Whichcompilation:\begin{cases}expressions now compile to JavaScript and interval-js targets as chained ternary operators withNaNfallback when no condition matches.Sum/Productcompilation:\sum_{k=a}^{b}and\prod_{k=a}^{b}expressions with numeric bounds now compile to JavaScript loops with accumulator variables, including complex number support.Loopcompilation:Loop,Break,Continue, andReturnoperators compile to JavaScriptforloops wrapped in IIFEs with standard control flow keywords.- Inline
Ifsyntax: Parse\text{if } C \text{ then } A \text{ else } B(or\operatorname{if}) to["If", C, A, B]expressions. wheresyntax: ParseE \text{ where } x \coloneq VtoBlockexpressions with implicit variable declarations.- Semicolon block syntax: Semicolons (
;,\;) act as statement separators, buildingBlockexpressions with auto-declared variables when assignments are present. forloop syntax: Parse\text{for } i \text{ from } a \text{ to } b \text{ do } bodyto["Loop", body, ["Element", "i", ["Range", a, b]]].
Resolved Issues
- Interval-JS compilation for Gamma functions: Added missing
gammaandgammalnexports and implementations in the interval-arithmetic library. - Interval-JS graceful fallback: The
interval-jstarget no longer throws when encountering unsupported functions. Unsupported operators now produce{ success: false }at compile time, and runtime errors return{ kind: "entire" }instead of propagating. CompilationResult.runtype signature: The TypeScript type forrunnow correctly reflects the actual calling convention ((...args: unknown[])) instead of the previous misleading(...args: (number | {re, im})[]).Loopcompilation for interval-js target: Loop counter now uses raw numbers (not_IA.point()) for theforstatement, with loop index references properly wrapped in the body. Conditions inif/break/continuestatements inside loops use scalar comparisons instead of interval comparison functions.
Other Changes
- Updated color palettes
- Deduplicated runtime helper object (
SYS_HELPERS) shared betweenComputeEngineFunctionandComputeEngineFunctionLiteralin compilation target - Centralized
sincimplementation innumerics/special-functions.ts(shared by library evaluation and JS compilation runtime) - Removed dead
args === nullchecks in compilation base class
0.51.0 2026-02-14
Colors
- New
colorslibrary: Four MathJSON operators for color manipulation and color space conversion, available as the"colors"library category. Color: Parse a color string (hex 3/6/8-digit,rgb(),hsl(), named CSS color,transparent) into a canonical sRGBTuplewith components normalized to 0-1. Alpha is included as a fourth component when not equal to 1.Colormap: Sample named visualization palettes. Three variants: no second argument returns the full palette as aList; integer n >= 2 resamples to n evenly spaced colors; real t in [0, 1] interpolates at position t using OKLCh color space with shorter-arc hue interpolation. Includes 8 sequential palettes (viridis, inferno, magma, plasma, cividis, turbo, rocket, mako), 6 categorical palettes (graph6, spectrum6, spectrum12, tableau10, tycho11, kelly22), and 12 diverging palettes (roma, vik, broc, rdbu, coolwarm, ocean-balance, plus reversed variants).ColorToColorspace: Convert an sRGB color (string orTuple) to components in"rgb","hsl","oklch", or"oklab"(alias"lab"). Preserves alpha when present.ColorFromColorspace: Convert color space components back to a canonical sRGBTuple. Accepts the same color space names asColorToColorspace.ColorToString: Convert a color (string or sRGBTuple) to a formatted string. Supports optional format argument:"hex"(default),"rgb","hsl", or"oklch"for CSS-style output. Alpha is included when not equal to 1.ColorMix: Blend two colors in OKLCh space with an optional ratio (default 0.5). Accepts color strings or sRGBTuplevalues. Interpolates lightness and chroma linearly, hue with shorter-arc interpolation.ColorContrast: Compute the APCA contrast ratio between a background and foreground color. Returns a positive value for dark-on-light and negative for light-on-dark.ContrastingColor: Choose the foreground color with better APCA contrast against a background. With one argument, picks between white and black. With three arguments, picks the better of two foreground candidates.- LaTeX color support:
\textcolor{color}{body},\colorbox{color}{body}, and\boxed{body}now roundtrip throughAnnotatedexpressions. Parsing and serialization are handled in the coreAnnotatedinfrastructure. - LaTeX font annotations:
\textbf,\textit,\texttt,\textsf,\textupnow serialize correctly fromAnnotatedexpressions viafontWeight,fontStyle, andfontFamilydict keys. - JavaScript compilation: All color operators (
Color,ColorToString,ColorMix,ColorContrast,ContrastingColor,ColorToColorspace,ColorFromColorspace,Colormap) now compile to JavaScript. oklab()CSS parsing:parseColor()now acceptsoklab(L a b)andoklab(L a b / alpha)syntax, matching the existingoklch()support.- GPU compilation:
ColorMix,ColorContrast,ContrastingColor,ColorToColorspace, andColorFromColorspacenow compile to GLSL and WGSL. Preamble functions provide sRGB ↔ OKLab ↔ OKLCh conversion, color mixing with shorter-arc hue interpolation, and APCA contrast on the GPU. - Added
rgbToHsl()conversion function. ExportedhslToRgb()(previously private).
Resolved Issues
- (#290) Derivatives of user-defined functions:
\frac{d}{dx} fandf'(x)now correctly evaluate whenfis a user-defined function (e.g.,f(x) := 2x). Previously\frac{d}{dx} freturned0andf'(x)returned a symbolicApply(Derivative(...)). - Cleaner
Dcanonical form:f'(x)now canonicalizes to["D", ["f", "x"], "x"]instead of the verbose["D", ["Function", ["Block", ["f", "x"]], "x"], "x"]. Function calls are no longer redundantly wrapped inFunction(Block(...)). Similarly,\frac{d}{dx} fwherefis a known function symbol canonicalizes to["D", ["f", "x"], "x"]by applying the function to the differentiation variable.
Free Functions
- Free functions (
simplify,evaluate,N,expand,expandAll,factor,solve,compile) now acceptExpressionInputin addition toLatexStringandExpression. This means you can pass numbers, MathJSON objects, or tuple arrays directly — e.g.,evaluate(["Add", 1, 2])orsimplify(["Power", "x", 2]). - Added
declare()free function to declare symbols without instantiating aComputeEngineexplicitly — e.g.,declare('x', 'integer')ordeclare({ x: 'integer', y: 'real' }).
Units and Quantities
- New
unitslibrary: A comprehensive unit system for physical quantities, available as the"units"library category. Supports SI base units, 18 named derived units, SI prefixes (quetta through quecto), and common non-SI units (imperial, angles, logarithmic). Quantityexpression: Pairs a numeric value with a unit:["Quantity", 9.8, ["Divide", "m", ["Power", "s", 2]]]. AccessorsQuantityMagnitudeandQuantityUnitextract the parts.- Quantity arithmetic:
Add,Subtract,Multiply,Divide, andPowerare unit-aware. Addition and subtraction automatically convert compatible units and express the result in the unit with the largest scale factor (e.g.,12 cm + 1 mevaluates to1.12 m). Incompatible dimensions remain unevaluated. - Unit conversion:
UnitConvertconverts between compatible units, including compound units likem/stokm/h. Supports affine temperature conversions (degC,degF,K). Returns an error for incompatible units.UnitSimplifyreduces compound units to named derived units when possible (e.g.,kg*m/s^2toN). - Dimensional analysis:
IsCompatibleUnittests dimensional compatibility.UnitDimensionreturns the 7-element SI dimension vector. Both support compound unit expressions. - LaTeX parsing:
\mathrm{...}and\text{...}containing recognized units produceQuantityexpressions when juxtaposed with numbers. Compound units with/,^, and\cdotare supported (e.g.,5\,\mathrm{m/s^{2}}). - siunitx commands:
\qty{value}{unit},\SI{value}{unit},\unit{unit}, and\si{unit}are parsed. - LaTeX serialization:
Quantityexpressions serialize tovalue\,\mathrm{unit}notation. - DSL string sugar: Compound units can be specified as strings in MathJSON:
["Quantity", 9.8, "m/s^2"]is canonicalized to the structured form. Parentheses are supported for grouping:"kg/(m*s^2)". - Temperature units:
degCanddegFwith affine offset conversions. - Angular unit unification: Trigonometric functions (
Sin,Cos,Tan, etc.) acceptQuantityarguments with angular units (deg,rad,grad,arcmin,arcsec) and convert to radians automatically. - Physics constants: 11 CODATA 2018 constants defined as
Quantityexpressions:SpeedOfLight,PlanckConstant,Mu0,StandardGravity,ElementaryCharge,BoltzmannConstant,AvogadroConstant,VacuumPermittivity,GravitationalConstant,StefanBoltzmannConstant, andGasConstant.
Compilation
- Tuple and Matrix compilation:
TupleandMatrixexpressions can now be compiled across all targets.compile('(\\sin(t), \\cos(t))')produces[Math.sin(t), Math.cos(t)]in JavaScript,vec2(sin(t), cos(t))in GLSL,vec2f(sin(t), cos(t))in WGSL, and(np.sin(t), np.cos(t))in Python. - GPU-native matrix types: Square matrices (2x2, 3x3, 4x4) compile to native
GPU matrix constructors (
mat2/mat3/mat4in GLSL,mat2x2f/mat3x3f/mat4x4fin WGSL) with proper column-major transposition. Column vectors are flattened tovecN/vecNfinstead of nested single-element arrays. - Complex number compilation: The JavaScript compilation target now supports
complex-valued expressions. The compiler performs static type analysis at
compile time to determine whether each subexpression is real or complex, and
emits the appropriate code path. Simple arithmetic (Add, Subtract, Multiply,
Divide, Negate) uses inline
{re, im}field math to avoid allocation. Transcendental functions (Sin, Cos, Exp, Ln, Sqrt, Power, and others) delegate to runtime helpers backed by thecomplex-esmlibrary. Mixed real/complex operands are promoted inline.ImaginaryUnitcompiles to{re: 0, im: 1}. Symbols with unknown type are assumed real. Complex-awareSumandProductloops emit{re, im}accumulators when the loop body is complex-valued. Reciprocal trig/hyperbolic functions (Cot, Sec, Csc, Coth, Sech, Csch) and their inverses dispatch to complex helpers when operands are complex. - Python complex compilation: The Python target now supports complex-valued
expressions using Python's native
complex()constructor and thecmathmodule for transcendental functions. Real-valued expressions continue to use NumPy. - Gamma function compilation:
GammaandGammaLncan now be compiled tointerval-js,glsl,wgsl, andinterval-glsltargets. The interval targets include pole detection at non-positive integers and correct monotonicity handling around the minimum at x ≈ 1.46. - Special function compilation: 27 additional functions can now be compiled
to JavaScript:
Erf,Erfc,ErfInv,Beta,Digamma,Trigamma,PolyGamma,Zeta,LambertW,BesselJ,BesselY,BesselI,BesselK,AiryAi,AiryBi,Factorial,Factorial2,Exp2,Log2,Log10,Lg,Arctan2,Hypot,Degrees,Haversine,InverseHaversine,Binomial, andFibonacci. - GPU special functions:
Erf,Erfc,ErfInv,Beta,Factorial,Arctan2,Hypot,Haversine,InverseHaversine,Log10, andLgcan now be compiled to GLSL and WGSL targets.Erf/ErfInvuse Abramowitz & Stegun polynomial approximations;BetaandFactorialleverage the existing GPU Gamma preamble.
Simplification
- Factorial quotient simplification:
n!/k!is now simplified to a partial product for both concrete integers (e.g.,10!/7!→720) and symbolic expressions with small constant difference (e.g.,n!/(n-2)!→n(n-1)). - Binomial detection: Expressions of the form
n!/(k!(n-k)!)are automatically recognized and simplified toBinomial(n, k). - Binomial identity simplification:
C(n,0)→1,C(n,1)→n,C(n,n)→1,C(n,n-1)→n. - Factorial sum factoring: Sums and differences of factorials with related
arguments are factored out, e.g.,
n! - (n-1)!→(n-1)! * (n-1),(n+1)! + n!→n! * (n+2).
0.50.2 2026-02-12
Numerics
- Centralized overflow protection: Improved robustness of
RationalandExactNumericValuearithmetic by centralizing overflow checks and automatic promotion toBigInt. - [#287](https://github.com/cortex-js/compute-engine/issues/287) Improved
precision for large integer products: Multiplications and additions of large
integers that would previously lose precision (exceeding
Number.MAX_SAFE_INTEGER) are now automatically promoted toBigIntto maintain exact results.
Symbols
- #288 Allow
reassigning a symbol from operator to value:
ce.assign()no longer throws when assigning a plain value to a symbol that was previously declared as a function. Existing expressions using the symbol as a function head will produce a type error at evaluation time if the new value is not callable.
Evaluation
- Fixed scope leaks: Ensured that evaluation contexts are correctly popped
even when an error or timeout occurs in
BoxedFunction.evaluate(),findUnivariateRoots(), and rule-boxing operations. - Improved numerical evaluation performance:
Sum,Product,Divide, and statistical operators (Mean,Variance, etc.) now correctly propagate thenumericApproximationoption, significantly speeding up large numerical calculations by avoiding expensive exact arithmetic.
0.50.1 2026-02-11
Compilation
CompilationResult.preamblefor shader targets:compile()withinterval-wgslandinterval-glsltargets now returns apreamblefield containing the interval arithmetic library (struct definitions, helper functions). Previously, the compiledcodereferenced functions likeia_divandia_sinthat were not included in the output. Usepreamble + codefor a self-contained shader, or callcompileShaderFunction()on the target directly.
0.50.0 2026-02-11
Breaking API Changes
This release includes several breaking changes to the public API.
The most significant is the restructuring of the Expression type hierarchy and
the introduction of type-guarded role interfaces, which improves type safety and
API ergonomics but requires updates to code that accessed role-specific
properties directly on expression instances.
See
MIGRATION_GUIDE_0.50.0.md
for details.
Naming Alignment: Expression, MathJsonExpression, and ExpressionInput
- The compute-engine runtime type is now
Expression(preferred name).BoxedExpressionis retained as a deprecated alias for migration. - The MathJSON type is now
MathJsonExpression(the old MathJSONExpressionname has been removed from themath-jsonentrypoint). SemiBoxedExpressionis nowExpressionInput(with a deprecated alias for migration).
Role-Specific Properties Moved to Type-Guarded Interfaces
Properties that were previously on all Expression instances (returning
undefined when not applicable) have been moved to role interfaces. They are
now only accessible after narrowing with a type guard.
Removed from Expression | Access via |
|---|---|
.symbol | isSymbol(expr) or isSymbol(expr, 'Pi') then expr.symbol |
.string | isString(expr) then expr.string |
.ops, .nops, .op1/.op2/.op3 | isFunction(expr) or isFunction(expr, 'Add') then expr.ops etc. |
.numericValue, .isNumberLiteral | isNumber(expr) then expr.numericValue |
.tensor | isTensor(expr) then expr.tensor |
// Before
if (expr.symbol !== null) console.log(expr.symbol);
// After
import { isSymbol, sym } from '@cortex-js/compute-engine';
if (isSymbol(expr)) console.log(expr.symbol);
// isSymbol() accepts an optional symbol name:
if (isSymbol(expr, 'Pi')) { /* expr is the Pi symbol */ }
// or use the convenience helper:
if (sym(expr) === 'Pi') { /* ... */ }
// isFunction() accepts an optional operator name:
if (isFunction(expr, 'Add')) {
// expr is narrowed to a function AND has operator 'Add'
console.log(expr.ops);
}
Properties that remain on Expression: .operator, .re/.im, .shape, all
arithmetic methods (.add(), .mul(), etc.), and all numeric predicates
(.isPositive, .isInteger, etc.).
Expression Creation: form Replaces canonical/structural
The canonical (boolean or array) and structural (boolean) options on
ce.box(), ce.function(), and ce.parse() have been unified into a single
form option.
ce.box(['Add', 1, 'x'], { form: 'canonical' }); // default
ce.box(['Add', 1, 'x'], { form: 'raw' }); // no canonicalization, no binding
ce.function('Add', [1, 'x'], { form: 'structural' }); // bound, not fully canonical
ce.box(['Add', 1, 'x'], { form: ['Number', 'Order'] }); // selective passes
New Free Functions
Top-level free functions are now available for common operations and use a
shared ComputeEngine instance created on first call.
| Function | Purpose |
|---|---|
getDefaultEngine() | Return the shared default ComputeEngine instance. |
parse(latex) | Parse a LaTeX string into an Expression. |
simplify(exprOrLatex) | Simplify an expression or LaTeX input. |
evaluate(exprOrLatex) | Evaluate an expression or LaTeX input symbolically. |
N(exprOrLatex) | Numerically evaluate an expression or LaTeX input. |
assign(id, value) / assign(record) | Assign one symbol value or many at once. |
expand(exprOrLatex) | Expand distributively at the top level (Expression | null). |
expandAll(exprOrLatex) | Expand distributively recursively (Expression | null). |
solve(exprOrLatex, vars?) | Solve equations/systems (returns solve result variants). |
factor(exprOrLatex) | Factor an expression. |
compile(exprOrLatex, options?) | Compile to a target language with CompilationResult. |
import {
getDefaultEngine,
parse,
simplify,
evaluate,
N,
assign,
expand,
expandAll,
solve,
factor,
compile,
} from '@cortex-js/compute-engine';
assign('x', 3);
const expr = parse('x^2 - 5x + 6');
solve(expr, 'x'); // [2, 3]
factor('(2x)(4y)'); // 8xy
compile('x^2 + 1').run({ x: 3 }); // 10
Except for parse(), assign(), and getDefaultEngine(), these free functions
accept either a LaTeX string or an existing Expression.
Free Function Notes
compile()is now a top-level entry point returningCompilationResult. Custom compilation targets are managed withce.registerCompilationTarget()andce.unregisterCompilationTarget().expand()andexpandAll()returnnullwhen an expression is not expandable.solve()is available as a top-level wrapper over equation/system solving.factor()is the top-level factoring entry point. Specialized helpers such asfactorPolynomial()andfactorQuadratic()remain expression-only APIs.
trigSimplify() Method Removed
Use simplify({ strategy: 'fu' }) instead, which is equivalent.
// Before
const result = expr.trigSimplify();
// After
const result = expr.simplify({ strategy: 'fu' });
Library System
The constructor now accepts a libraries option for controlling which libraries
are loaded. Libraries declare their dependencies and are loaded in topological
order.
// Load specific standard libraries
const ce = new ComputeEngine({
libraries: ['core', 'arithmetic', 'trigonometry'],
});
// Add a custom library
const ce = new ComputeEngine({
libraries: [
...ComputeEngine.getStandardLibrary(),
{ name: 'physics', requires: ['arithmetic'], definitions: { /* ... */ } },
],
});
User-Extensible Simplification Rules
ce.simplificationRules is now a public getter/setter. Users can push
additional rules or replace the entire rule set.
ce.simplificationRules.push({
match: ['Power', ['Sin', '_x'], 2],
replace: ['Subtract', 1, ['Power', ['Cos', '_x'], 2]],
});
Canonicalization
-
Exact numeric folding during canonicalization:
canonicalAddandcanonicalMultiplynow fold exact numeric operands at canonicalization time, making behavior consistent withcanonicalDividewhich already folded coefficients. This means expressions are reduced earlier in the pipeline without waiting for a.simplify()call.What gets folded (exact values):
- Integers:
Add(2, x, 5)→Add(x, 7) - Rationals:
Add(1/3, x, 2/3)→Add(x, 1) - Radicals:
Add(√2, x, √2)→Add(x, 2√2) - Mixed exact:
Multiply(2, x, 5)→Multiply(10, x) - Full reduction:
Add(2, 3)→5,Multiply(2, 3)→6 - Identity elimination:
Multiply(1/2, x, 2)→x - Complex promotion:
Add(1, Complex(0, -1))→Complex(1, -1)
What is NOT folded (non-exact values):
- Machine floats:
Add(1.5, x, 0.5)remainsAdd(x, 0.5, 1.5) - Infinity/NaN:
Multiply(0, ∞)correctly returnsNaN - Single numeric:
Multiply(5, Pi)is unchanged (nothing to fold)
The folding uses the existing
ExactNumericValuearithmetic, which automatically handles radical grouping (√2 + √2 = 2√2) and rational simplification (1/3 + 2/3 = 1). - Integers:
-
Exact numeric folding in
canonicalPower: Integer powers of numeric literals are now folded during canonicalization when the exponent is an integer with |e| ≤ 64. For machine-number bases, the result must be a safe integer; for exact numeric values (rationals, radicals),NumericValue.pow()is used.Power(2, 3)→8Power(3, 2)→9Power(1/2, 2)→1/4Power(-2, 3)→-8Power(2, 100)remains unevaluated (exponent exceeds limit)
-
Complex promotion handles non-adjacent operands:
canonicalAddnow combines a real float with imaginary terms even when they are not adjacent in the operand list. Previously, only a real immediately followed by an imaginary was promoted to a complex number.
Type Inference
- Type handlers for 25 operators: Added explicit
typehandlers to operators that were missing them, enabling the type system to return precise types instead of the broad signature return type.- Arithmetic:
Factorial,Factorial2,Signreturnfinite_integer;CeilandFloorreturnfinite_integerfor finite inputs,integerotherwise. - Trigonometry:
ArctanusesnumericTypeHandler(returnsfinite_realfor real inputs,finite_numberfor complex). - Complex:
Real,Imaginary,Argumentreturnfinite_real. - Number theory:
Totient,Sigma0,Sigma1,Eulerian,Stirling,NPartitionreturnfinite_integer;SigmaMinus1returnsfinite_rational. - Combinatorics:
Choose,Fibonacci,Binomial,Multinomial,Subfactorial,BellNumberreturnfinite_integer. Truncate,GCD,LCMtype handlers:Truncatereturnsfinite_integerfor finite inputs (matchingCeil/Floor);GCDandLCMalways returnfinite_integer.
- Arithmetic:
Solving
-
Andoperator support for systems of equations:solve()now acceptsAnd(Equal(...), Equal(...))in addition toList(Equal(...), Equal(...))for representing systems of equations. Both forms route through the same linear, polynomial, and inequality solvers. -
Parametric solution type filtering:
filterSolutionByTypesnow uses=== falseinstead of!== truefor type predicate checks. This allows underdetermined (parametric) solutions to pass through when type predicates returnundefined(unknown) rather than being incorrectly rejected. -
Oroperator support insolve(): SolvingOr(Equal(x,1), Equal(x,2))returns the union of solutions from each branch, with deduplication. Works for both univariate (returns array of values) and multivariate (returns array of records) cases. -
Mixed equality + inequality systems:
solve()now handles systems combiningEqualand inequality operators (Less,LessEqual,Greater,GreaterEqual). Equalities are solved first, then solutions are filtered against the inequalities. -
Parametric solutions omit free variables: Underdetermined linear systems no longer include free variables (self-referential entries) in the result record. Only dependent variables with non-trivial expressions are returned.
Special Functions
-
Numeric evaluation for Digamma, Trigamma, PolyGamma, Beta, Zeta, LambertW: These six functions now evaluate numerically when
.N()is called, at both machine precision and arbitrary precision (bignum). Returns unevaluated without numeric approximation.Digamma/Trigamma: recurrence + asymptotic with Bernoulli numbersPolyGamma: generalized recurrence for arbitrary order nBeta: via gamma, with log-gamma fallback for large argumentsZeta: Cohen-Villegas-Zagier acceleration, functional equation for\operatorname{Re}(s)<0LambertW: Halley's method with branch-point handling
-
Arbitrary-precision (bignum) variants for special functions: When
ce.precision > 15,Digamma,Trigamma,PolyGamma,Beta,Zeta, andLambertWnow compute results to the requested precision using bignum arithmetic. The asymptotic shift threshold scales with precision to maintain accuracy (e.g.,ce.precision = 50produces 50-digit results for Digamma and Zeta). -
Numeric evaluation for Bessel functions (
BesselJ,BesselY,BesselI,BesselK): Integer-order Bessel functions now evaluate numerically.BesselJ: power series for small|x|, Miller's backward recurrence for intermediate values, Hankel asymptotic expansion for large|x|BesselY: DLMF 10.8.3 series forY_0/Y_1, forward recurrence for higher orders, shared Hankel asymptotic withBesselJBesselI: power series + asymptotic expansionBesselK: series forK_0, Wronskian-derivedK_1, forward recurrence for higher orders, asymptotic for largex
-
Numeric evaluation for Airy functions (
AiryAi,AiryBi): Power series using Maclaurin coefficients for|x| \leq 5, asymptotic expansions (exponential decay for Ai, exponential growth for Bi at positivex, oscillatory for negativex) for large arguments.
Linear Algebra
(Fix #285)
-
\begin{vmatrix}now parses toDeterminant: ThevmatrixLaTeX environment now produces["Determinant", ["Matrix", ...]]instead of["Matrix", ..., "'||'"]. Serialization round-trips correctly back to\begin{vmatrix}...\end{vmatrix}when the argument is aMatrixexpression, and uses\det\left(...\right)for symbol arguments. -
\begin{Vmatrix}now parses toNorm: TheVmatrixLaTeX environment now produces["Norm", ["Matrix", ...]]instead of["Matrix", ..., "'‖‖'"]. Serialization round-trips to\begin{Vmatrix}...\end{Vmatrix}when the argument is aMatrix, and uses\left\Vert...\right\Vertfor symbol arguments. -
A^{-1}producesInversefor matrix-typed symbols and matrix expressions: When a symbol is declared with typematrix, parsingA^{-1}now returns["Inverse", "A"]instead of["Power", "A", -1]. This also works for inline matrix expressions, e.g.\begin{pmatrix}...\end{pmatrix}^{-1}. Undeclared symbols still fall through to the defaultPower/Dividehandling, and function symbols still produceInverseFunction(e.g.,\sin^{-1}→Arcsin). -
Inverseserializes as^{-1}:["Inverse", "A"]now serializes toA^{-1}instead of\mathrm{Inverse}(A). -
Power(A, -1)canonicalizes toInverse(A)for matrices: WhenAhas a matrix type,ce.box(["Power", "A", -1])now canonicalizes to["Inverse", "A"]instead of["Divide", 1, "A"]. -
\det(A)and\tr(A)now parse correctly: FixedDeterminantandTraceLaTeX dictionary entries to uselatexTrigger(\det,\tr) instead ofsymbolTrigger, which only matches plain identifiers. Both functions also accept plain text forms (det(A),tr(A)). -
\det Aand\tr Awork without parentheses:DeterminantandTracenow accept implicit arguments, so\det Aparses as["Determinant", "A"](like\cos xparses as["Cos", "x"]). Implicit arguments bind at multiplication precedence, so\det 2A + 1parses asdet(2A) + 1. -
Determinantserialization uses\det Afor simple arguments: Symbol arguments serialize as\det Ainstead of\det\left(A\right). Matrix arguments still serialize as\begin{vmatrix}...\end{vmatrix}. -
Added standard LaTeX operators
\ker,\dim,\deg,\hom: These commands are now in the MathJSON LaTeX dictionary as function entries with implicit arguments, so forms like\ker V,\dim V,\deg p, and\hom(V, W)parse correctly and serialize back to the corresponding standard operator notation. The corresponding function symbols (Kernel,Dimension,Degree,Hom) are also registered in the linear algebra library. -
Implemented runtime evaluation for
Kernel,Dimension,Degree, andHom:Kernelnow computes a numeric null-space basis (for scalar/vector/matrix real inputs) and returns it as a list of basis vectors.Dimensionnow evaluates finite dimensions for concrete tensors and collections, and computesdim(Hom(V, W)) = dim(V) * dim(W)when both dimensions are inferable.Degreenow evaluates polynomial degree for polynomial-form expressions while keeping ambiguous bare symbols (for exampleDegree(p)) unevaluated.Homnow evaluates/simplifies its arguments while preserving the symbolicHom(...)form.
LaTeX Parsing
arguments: 'implicit'option for function dictionary entries: Function entries in the LaTeX dictionary can now setarguments: 'implicit'to accept bare arguments without parentheses (e.g.,\det A), matching the behavior of trig functions. The default remains'enclosure'(parentheses required). Applied to\det,\tr,\Re,\Im,\arg,\max,\min,\sup,\inf.
Simplification
-
Infinity handling for 24+ functions:
arctan(∞),arccot(±∞),tanh/coth/sech/csch(±∞),arsinh(-∞),arcosh(-∞),arccoth(±∞),arcsch(±∞),π^∞,∞^n,(-∞)^{-n},log_∞(x),log_{0.5}(∞),√∞,∛∞now all return correct limits. -
Root edge cases:
Root(x, 0) → NaN,Root(0, n),Root(1, n),Root(+∞, n), andSqrt(+∞)now handled correctly. -
Division edge cases:
a/a → 1now works for compound expressions (e.g.,(π+1)/(π+1));2/0 → ComplexInfinityand1/(1/0) → 0propagate correctly. -
Logarithm edge cases: Fixed infinity detection in
simplify-log.ts(was usingsym()which fails onBoxedNumberinfinity values); addedlog_∞(∞) → NaN, base-awarelog_c(0), guards forlog_1(x)andlog_c(c^x)evaluation. -
Absolute value of odd functions:
|arcsin(x)|,|sinh(x)|,|arsinh(x)|,|artanh(x)|now simplify tof(|x|). -
Even function with abs argument:
cosh(|x+2|) → cosh(x+2). -
Trig period shifts:
cot(π+x) → cot(x),csc(π+x) → -csc(x). -
Ln simplification in Add/Multiply operands:
ln(x^3) − 3·ln(x) → 0andln(x^√2) → √2·ln(x)now work; cost function bypassed for log rules that are mathematically valid but structurally more expensive. -
Preserved function identity: Removed unconditional expansions of
sinh/cosh → exp,arsinh/arcosh/artanh → ln, andarcsin → arctan2that prevented abs/odd-function rules from firing.
Compilation
-
WGSL (WebGPU Shading Language) Compilation Target: New built-in WGSL target for compiling mathematical expressions to WebGPU shaders.
// Via the registryconst result = compile(expr, { to: 'wgsl' });WGSL-specific differences from GLSL:
inverseSqrt(camelCase) instead ofinversesqrt%operator for mod instead ofmod()functionvec2f/vec3f/vec4fconstructors instead ofvec2/vec3/vec4array<f32, n>()instead offloat[n]()fn name(x: f32) -> f32instead offloat name(float x)@vertex/@fragment/@computeentry points with struct-based I/O@group/@bindinguniform declarations and@workgroup_sizefor compute
-
Interval WGSL Compilation Target: New
interval-wgsltarget for interval arithmetic in WebGPU shaders, mirroring the existinginterval-glsltarget. Since WGSL does not support function overloading, the library uses_vsuffixes for internal vec2f-parameter implementations (e.g.,ia_add_v), while the public API (ia_add,ia_sin, etc.) takesIntervalResultvalues.
Resolved Issues
-
Sequencetype inference now returns a proper tuple type: Multi-argumentSequenceexpressions previously returned'any'as their inferred type, losing all type information. They now return atuple<...>type with each element's individual type preserved (e.g.,Sequence(1, "a")types astuple<integer, string>), consistent with theTupleoperator. -
Subscript parsing now checks for collection type: The LaTeX subscript (
_) parser now checks whether the LHS is a collection (symbol declared asindexed_collection, or a list literal) and producesAt()directly at parse time, consistent with bracket indexing (x[i]). Multi-index subscripts on collections (A_{k,j}) are now correctly unpacked into separateAtarguments instead of being wrapped in aTuple. -
NumericValue(0).mul(Infinity)now returns NaN: All threeNumericValuesubclasses (MachineNumericValue,BigNumericValue,ExactNumericValue) had an early-returnif (this.isZero) return thisinmul(), which returned0without checking if the other operand was infinity.0 × ±∞is now correctly indeterminate (NaN), and±∞ × 0is handled symmetrically. -
Power simplification
(a^n)^m -> a^{nm}now correctly guarded: The rule was applied unconditionally, which is mathematically incorrect when the base can be negative and exponents are non-integer. The classic counterexample:((-1)^2)^{1/2} = 1, but(-1)^{2·1/2} = -1. The rule is now only applied when: (1) the base is non-negative, (2) the outer exponent is an integer, or (3) the inner exponent is an odd integer. This fix applies to canonicalization (canonicalPower), thepow()helper, and simplification (simplifyPower). As a result,(x^2)^{1/2}now correctly simplifies to|x|instead ofx. -
Power distribution rules now guarded for non-integer exponents: Three additional power distribution rules in
pow()were applied unconditionally, producing wrong results when the exponent is non-integer and operands are negative. (1)(a/b)^c -> a^c / b^c— e.g.((-2)(-3))^{1/2} = sqrt(6)but distributing gives(-2)^{1/2} * (-3)^{1/2} = -sqrt(6). (2)(a*b)^c -> a^c * b^c— same class of bug. (3)(-x)^nusedn % 2 === 0to test parity, but for non-integern(e.g. 0.5),0.5 % 2 = 0.5falls to the odd branch, giving(-x)^{0.5} -> -(x^{0.5})which is wrong. All three rules, plus the correspondingcanonicalPower()Divide rule, now require integer exponents (or non-negative operands) before distributing. -
Sqrt/Root exponent rearrangement now guarded: Two more rules in
pow()unconditionally rearranged exponents. (1)(√a)^b -> √(a^b)rearranges(a^{1/2})^bto(a^b)^{1/2}, which is wrong for negativea(e.g.(√(-4))^3 = -8ibut√((-4)^3) = 8i). Now only applied whena >= 0. The even-integer branches ((√a)^2 -> a,(√a)^{2k} -> a^k) remain unconditional since integer outer exponents are always safe. (2)Root(a,b)^c -> a^{c/b}combined exponents unconditionally. Now guarded witha >= 0orcis integer. Audit ofsimplify-power.tsconfirmed all rules there are already properly guarded. -
Relational operators now evaluate: Seven relational operators (
TildeFullEqual,TildeEqual,Approx,ApproxEqual,ApproxNotEqual,Precedes,Succeeds) previously hadcanonicalhandlers but noevaluatehandlers, so expressions likeApprox(3.14, 3.14)returned unevaluated. The approximate-equality family (TildeFullEqual,TildeEqual,Approx,ApproxEqual) now checks whether|a - b| <= toleranceviace.chop(), with support for multi-argument chains.PrecedesandSucceedsevaluate as numeric<and>respectively. Negated variants (NotApprox,NotTildeFullEqual, etc.) work automatically through theNotoperator. -
BoxedNumber.operatornow returns specific numeric types: Theoperatorproperty onBoxedNumberinstances previously returned the generic'Number'for all numeric values. It now returns specific types that match the internal type system:'Integer'for integers,'Rational'for non-integer rationals,'Real'for floating-point numbers,'Complex'for complex numbers with non-zero imaginary part, and'NaN','PositiveInfinity','NegativeInfinity'for special values. This improves API consistency with thetypeproperty and enables more precise pattern matching and type discrimination in user code. Breaking change: Code that explicitly checks for.operator === 'Number'will need to be updated to check for specific numeric types or use theisNumber()type guard instead. -
Non-XIDC Unicode characters in symbol names now encoded correctly: When parsing LaTeX symbols containing non-identifier Unicode characters via
\unicode{...},\char, or^^XXescapes (e.g., figure dash U+2012 in\operatorname{speed\unicode{"2012}of\unicode{"2012}sound}), the characters are now encoded as____XXXXXX(4 underscores + 6 hex digits) in the symbol name. This encoding is valid perisValidSymbol()and round-trips correctly: the serializer decodes____XXXXXXback to\unicode{"XXXX"}in LaTeX output. Previously, these characters passed through raw and caused symbol validation to fail. -
Assign to compound symbol names no longer misinterpreted as sequence definitions (fixes #286):
ce.box(["Assign", "t_half", 10])previously failed because the Assign evaluate handler split any symbol containing_and treated it as a subscripted sequence definition. User-provided compound symbols liket_halforhalf_lifeare now assigned correctly. Sequence definitions via parsed LaTeX (e.g.,L_0 := 1) continue to work as before.
0.35.6 2026-02-07
Resolved Issues
- Monte Carlo improper integrals: Fixed two bugs in
monteCarloEstimate()that produced incorrect results (typicallyNaNorInfinity) for improper integrals. The change-of-variables estimator was inverted (f(x) / \mathrm{jacobian}instead off(x) * \mathrm{jacobian}), and the finite-interval scale factorb - awas applied to transformed domains where it is infinite. AffectsNIntegrateand compiledintegratefor any integral with infinite bounds.
Compilation
-
Truncate,Remainder, andModfor JS/GLSL targets: AddedTruncate(Math.trunc/trunc),Remainder, andModto the JavaScript and GLSL compilation targets, matching the Python target which already had them. -
Interval
truncandremainder: Addedtrunc()andremainder()to the interval arithmetic library.trunchas proper discontinuity detection (behaves likefloorfor positive,ceilfor negative, continuous at zero).remainder(a, b) = a - b * round(a/b)composes existing interval operations with discontinuity detection inherited fromround. Added corresponding mappings to both interval JavaScript and interval GLSL targets. -
Interval
Lb,Log, andRootfor GLSL: Addedia_log2,ia_log10, andRootto the interval GLSL target for consistency with the interval JavaScript target. -
Reverse cross-reference test: Added a test that verifies all core CE math functions have compilation support in every target. Currently all 5 targets have full coverage of the 47 compilable math functions.
0.35.5 2026-02-06
Resolved Issues
-
Compilation Target Function Name Mismatches: Fixed several function keys in compilation targets that did not match their canonical library operator names, causing silent compilation failures and runtime errors ("Unexpected value"). Affected mappings:
Ceiling→Ceil,Sgn→Sign,LogGamma→GammaLn,Arcsinh→Arsinh,Arccosh→Arcosh,Arctanh→Artanh,Re→Real,Im→Imaginary,Arg→Argumentacross all five compilation targets. -
Missing Library Operator Definitions: Added library definitions for
Exp2,Fract,Log10,Log2,Remainder, andTruncatewhich were referenced by compilation targets but had no corresponding library entries.Exp2canonicalizes toPower(2, x),Log10/Log2canonicalize toLogwith the appropriate base, andFract,Remainder,Truncatehave direct numeric evaluation. -
Derivative Rule for GammaLn: Fixed the derivative table entry that used the non-canonical name
LogGammainstead ofGammaLn, preventing the derivatived/dx GammaLn(x) = Digamma(x)from being computed.
0.35.4 2026-02-06
Interval Arithmetic
- Discontinuity Continuity Direction: Singular interval results now include
an optional
continuityfield ('left'or'right') indicating from which side the function is continuous at a jump discontinuity.Floor,Round,Fract, andModreport'right'(right-continuous),Ceilreports'left'(left-continuous). Pole-type singularities (e.g.,tan,1/x) leave the field undefined. This is reflected in both the JavaScript and GLSL interval arithmetic targets (newIA_SINGULAR_RIGHTandIA_SINGULAR_LEFTstatus constants in GLSL).
0.35.3 2026-02-06
Compilation
-
Expanded Function Support Across All Targets: Added comprehensive function mappings to all five compilation targets (JavaScript, GLSL, Interval GLSL, Interval JavaScript, Python): reciprocal trig (
Cot,Csc,Sec), inverse reciprocal trig (Arccot,Arccsc,Arcsec), hyperbolic (Sinh,Cosh,Tanh), reciprocal hyperbolic (Coth,Csch,Sech), inverse hyperbolic (Arcosh,Arsinh,Artanh,Arcoth,Arcsch,Arsech), and elementary functions (Sgn,Lb,Logwith base,Square,Root,Fract). -
Interval Discontinuity Detection:
Floor,Ceil,Round,Sign,Fract, andModnow correctly report singularities when an interval spans a discontinuity point, in both the JavaScript and GLSL interval arithmetic targets. Previously these functions returned normal interval bounds even across jump discontinuities, which could cause incorrect connecting lines in plotted curves. -
New Interval Functions: Added
Round,Fract, andModto the interval arithmetic targets (both JS and GLSL) with proper discontinuity detection.
0.35.2 2026-02-05
Resolved Issues
-
Decimal Number Representation: Numbers written with a decimal point (e.g.,
6.02e23) are now correctly treated as approximate decimal values (BigNumericValue) rather than exact integers. Previously,6.02e23was incorrectly converted to the exact bigint602000000000000000000000, which implied false precision and caused memory inefficiency for very large exponents. Numbers without a decimal point (e.g.,602e21) continue to be treated as exact integers when possible. This change aligns with the documented behavior of theparseNumbers: 'auto'option. -
Scientific Notation Serialization (#284): Fixed
toLatex()withscientificandadaptiveScientificnotation options to produce properly normalized output. Previously, numbers like6.02e23would serialize as602\cdot10^{21}instead of the expected6.02\cdot10^{23}. The output now depends only on the numeric value and formatting options, not on the internal representation. -
Numeric Sum Precision: Fixed precision loss when summing large integers with rational values (e.g.,
12345678^3 + 1/3). TheExactNumericValue.sum()method now usesbignumReinstead ofreto preserve full precision when handling large integer values fromBigNumericValue. -
Broadcastable Functions with Union/Any Types (#235): Broadcastable (threadable) functions like
MultiplyandAddno longer reject arguments whose type is a union of numeric and collection types (e.g.,number | list) orany. Previously, declaring a symbol asce.declare('a', 'number | list')and using it ince.box(['Multiply', 'a', 'b'])would produce anincompatible-typeerror. -
Division Canonicalization Over-Simplification (#227): Fixed
A/Abeing incorrectly simplified to1during canonicalization for constant expressions that evaluate to infinity or zero, such astan(π/2)/tan(π/2). This now correctly evaluates toNaN(since∞/∞is indeterminate) instead of1. Expressions with free variables (e.g.,x/x,sin(x)/sin(x)) continue to simplify to1per standard algebraic convention. Also fixed deferred constant divisions like0/(1-1)and(1-1)/(1-1)to properly evaluate toNaNinstead of remaining as unevaluated expressions.
0.35.1 2026-02-03
Resolved Issues
- Interval Arithmetic (JS/GLSL): Fixed interval evaluation of compound
arguments (e.g.
sin(2x),sin(x+x),sin(x^2),cos(2x)) by propagating interval results through trig, elementary, and comparison functions ininterval-js, and by addingIntervalResultoverloads to the GLSL interval library forinterval-glsl.
0.35.0 2026-02-02
Parsing
- Large Integer Precision: Fixed precision loss when parsing integers
exceeding
Number.MAX_SAFE_INTEGERwithparseNumbers: 'rational'. Large integers and rational numerators now use BigInt arithmetic to preserve exact values. Fixes #283.
Compilation
- Interval Arithmetic Targets: Added two new compilation targets for
reliable singularity detection:
interval-js- Compiles to JavaScript using interval arithmeticinterval-glsl- Compiles to GLSL for GPU-based interval evaluation
0.34.0 2026-02-01
Parsing
-
\mathopenand\mathclose: The LaTeX parser supports\mathopenand\mathclosedelimiter prefixes for matchfix operators (explicit delimiter spacing control), e.g.\mathopen(a, b\mathclose)and\mathopen{(}a, b\mathclose{)}. -
Interval Notation Parsing: Added support for parsing mathematical interval notation from LaTeX, including half-open intervals. Addresses #254.
// Half-open intervals (American notation)ce.parse('[3, 4)').json; // → ["Interval", 3, ["Open", 4]]ce.parse('(3, 4]').json; // → ["Interval", ["Open", 3], 4]// Open intervals (ISO/European notation)ce.parse(']3, 4[').json; // → ["Interval", ["Open", 3], ["Open", 4]]// LaTeX bracket commands and sizing prefixesce.parse('\\lbrack 3, 4\\rparen').json; // → ["Interval", 3, ["Open", 4]]ce.parse('\\left[ 3, 4 \\right)').json; // → ["Interval", 3, ["Open", 4]]ce.parse('\\bigl( 3, 4 \\bigr]').json; // → ["Interval", ["Open", 3], 4]Contextual Parsing: Lists and tuples are automatically converted to intervals when used in set contexts (Element, Union, Intersection, etc.):
ce.parse('x \\in [0, 1]').json;// → ["Element", "x", ["Interval", 0, 1]]ce.parse('[0, 1] \\cup [2, 3]').json;// → ["Union", ["Interval", 0, 1], ["Interval", 2, 3]]// Standalone notation remains backward compatiblece.parse('[0, 1]').json; // → ["List", 0, 1]ce.parse('(0, 1)').json; // → ["Tuple", 0, 1]
Compilation
-
Custom Operator Compilation: The
compile()method now supports overriding operators to use function calls instead of native operators. This enables compilation of vector/matrix operations and custom domain-specific languages. Addresses #240.// Override operators for vector operationsconst expr = ce.parse('v + w');const compiled = expr.compile({operators: {Add: ['add', 11], // Convert + to add() functionMultiply: ['mul', 12] // Convert * to mul() function},functions: {add: (a, b) => a.map((v, i) => v + b[i]),mul: (a, b) => a.map((v, i) => v * b[i])}});const result = compiled({ v: [1, 2, 3], w: [4, 5, 6] });// → [5, 7, 9]Highlights:
- Map operators via an object or a function
- Function-name operators compile to calls; symbol operators compile to infix
- Supports scalar/collection arguments and partial overrides
-
Exported Compilation Interfaces: Advanced users can now create custom compilation targets by using the exported
CompileTargetinterface,BaseCompilerclass, andJavaScriptTargetclass.import { BaseCompiler, JavaScriptTarget } from '@cortex-js/compute-engine';// Create a custom compilation targetconst customTarget = {language: 'my-dsl',operators: (op) => ({ Add: ['ADD', 11], Multiply: ['MUL', 12] }[op]),functions: (id) => id.toUpperCase(),var: (id) => `VAR("${id}")`,string: (s) => `"${s}"`,number: (n) => n.toString(),ws: () => ' ',preamble: '',indent: 0,};const expr = ce.parse('x + y * 2');const code = BaseCompiler.compile(expr, customTarget);// → "ADD(VAR("x"), MUL(VAR("y"), 2))"Exported building blocks include
CompileTarget,LanguageTarget,CompilationOptions,CompiledExecutable,BaseCompiler,JavaScriptTarget, andGLSLTarget(plus helper types likeCompiledOperatorsandCompiledFunctions). -
Compilation Plugin Architecture: The Compute Engine now supports registering custom compilation targets, allowing you to compile mathematical expressions to any target language beyond the built-in JavaScript and GLSL targets.
import { ComputeEngine, BaseCompiler } from '@cortex-js/compute-engine';const ce = new ComputeEngine();// Define a custom Python targetclass PythonTarget {// ... implementation (see documentation)}// Register the custom targetce.registerCompilationTarget('python', new PythonTarget());// Compile to Pythonconst expr = ce.parse('\\sin(x) + \\cos(y)');const pythonCode = expr.compile({ to: 'python' });console.log(pythonCode.toString());// → math.sin(x) + math.cos(y)// Switch between targetsconst jsFunc = expr.compile({ to: 'javascript' });const glslCode = expr.compile({ to: 'glsl' });Notes:
- Built-in targets:
javascript(executable) andglsl(shader code) - Add targets via
ce.registerCompilationTarget(name, target) - Switch targets with
compile({ to: ... })(or override once withtarget)
- Built-in targets:
-
Python/NumPy Compilation Target: Added a complete Python/NumPy compilation target for scientific computing workflows. The
PythonTargetclass compiles mathematical expressions to NumPy-compatible Python code.import { ComputeEngine, PythonTarget } from '@cortex-js/compute-engine';const ce = new ComputeEngine();const python = new PythonTarget({ includeImports: true });// Register the targetce.registerCompilationTarget('python', python);// Compile expressions to Pythonconst expr = ce.parse('\\sin(x) + \\cos(y)');const code = expr.compile({ to: 'python' });console.log(code.toString());// → import numpy as np//// np.sin(x) + np.cos(y)// Generate complete Python functionsconst func = python.compileFunction(ce.parse('\\sqrt{x^2 + y^2}'),'magnitude',['x', 'y'],'Calculate vector magnitude');// Generates:// import numpy as np//// def magnitude(x, y):// """Calculate vector magnitude"""// return np.sqrt(x ** 2 + y ** 2)Highlights:
- NumPy-compatible output (including arrays)
- Function mapping for common math + linear algebra
- Helpers for full functions, lambdas, and vectorized code
See the Python/NumPy Target Guide for complete documentation and examples.
-
GLSL Compilation Target: New built-in GLSL (OpenGL Shading Language) target for compiling mathematical expressions to WebGL shaders.
const expr = ce.parse('x^2 + y^2');const glslCode = expr.compile({ to: 'glsl' });console.log(glslCode.toString());// → pow(x, 2.0) + pow(y, 2.0)// Generate complete GLSL functionsimport { GLSLTarget } from '@cortex-js/compute-engine';const glsl = new GLSLTarget();const distExpr = ce.parse('\\sqrt{x^2 + y^2 + z^2}');const func = glsl.compileFunction(distExpr, 'distance3D', 'float', [['x', 'float'],['y', 'float'],['z', 'float'],]);console.log(func);// → float distance3D(float x, float y, float z) {// return sqrt(pow(x, 2.0) + pow(y, 2.0) + pow(z, 2.0));// }// Generate complete shadersconst shader = glsl.compileShader({type: 'fragment',version: '300 es',outputs: [{ name: 'fragColor', type: 'vec4' }],body: [{variable: 'fragColor',expression: ce.box(['List', 1, 0, 0, 1]),},],});Highlights:
- Native vector/matrix operators and constructors
- Float literal formatting (
2.0) - Helpers for functions and complete shaders
Algebra
-
Polynomial Factoring: The
Factorfunction now supports comprehensive polynomial factoring including perfect square trinomials, difference of squares, and quadratic factoring with rational roots. Addresses #180 and #33.// Perfect square trinomialsce.parse('x^2 + 2x + 1').factor().latex;// → "(x+1)^2"ce.parse('4x^2 + 12x + 9').factor().latex;// → "(2x+3)^2"// Difference of squaresce.parse('x^2 - 4').factor().latex;// → "(x-2)(x+2)"// Quadratic with rational rootsce.box(['Factor', ['Add', ['Power', 'x', 2], ['Multiply', 5, 'x'], 6], 'x']).evaluate().latex;// → "(x+2)(x+3)"Automatic Factoring in sqrt Simplification: Square roots now automatically factor their arguments before applying simplification rules, enabling expressions like
√(x²+2x+1)to simplify to|x+1|.// Issue #180 - Now works!ce.parse('\\sqrt{x^2 + 2x + 1}').simplify().latex;// → "\\vert x+1\\vert"ce.parse('\\sqrt{4x^2 + 12x + 9}').simplify().latex;// → "\\vert 2x+3\\vert"ce.parse('\\sqrt{a^2 + 2ab + b^2}').simplify().latex;// → "\\vert a+b\\vert"Includes perfect square trinomials, difference of squares, and quadratics with rational roots. Helper functions are exported for advanced usage (
factorPerfectSquare,factorDifferenceOfSquares,factorQuadratic,factorPolynomial).MathJSON API:
["Factor", expr] // Auto-detect variable["Factor", expr, variable] // Explicit variable specificationThe enhanced factoring system works seamlessly with existing polynomial functions like
Expand,Together,Cancel,PolynomialGCD, and others.
Simplification
-
Absolute Value Power Simplification: Fixed simplification of
|x^n|expressions with even and rational exponents. Previously, expressions like|x²|and|x^{2/3}|were not simplified. Now they correctly simplify based on the parity of the exponent's numerator. Addresses #181.ce.parse('|x^2|').simplify().latex; // → "x^2" (even exponent)ce.parse('|x^3|').simplify().latex; // → "|x|^3" (odd exponent)ce.parse('|x^{2/3}|').simplify().latex; // → "x^{2/3}" (even numerator)ce.parse('|x^{3/2}|').simplify().latex; // → "|x|^{3/2}" (odd numerator) -
Assumption-Based Simplification: Simplification rules use assumptions about symbol signs:
ce.assume(ce.parse('x > 0'));ce.parse('\\sqrt{x^2}').simplify().latex; // → "x" (was "|x|")ce.parse('|x|').simplify().latex; // → "x" (was "|x|")ce.assume(ce.parse('y < 0'));ce.parse('\\sqrt{y^2}').simplify().latex; // → "-y"ce.parse('|y|').simplify().latex; // → "-y" -
Nested Root Simplification: Nested roots simplify to a single root:
ce.box(['Sqrt', ['Sqrt', 'x']]).simplify() // → root(4)(x)ce.box(['Root', ['Root', 'x', 3], 2]).simplify() // → root(6)(x)ce.box(['Sqrt', ['Root', 'x', 3]]).simplify() // → root(6)(x)Applies to all combinations:
sqrt(sqrt(x)),root(sqrt(x), n),sqrt(root(x, n)), androot(root(x, m), n). -
Extended Coefficient Factoring in Power Combination: The power combination rule now handles additional coefficient forms when combining same-base powers in products:
- Multi-prime coefficients:
12·2ˣ·3ˣ→2^(x+2)·3^(x+1)(since 12 = 2²·3). All primes in the factorization must have a matching base. Non-matching multi-prime coefficients like6·2ˣare left unchanged. - Negative coefficients:
-4·2ˣ→-2^(x+2),-8·2ˣ→-2^(x+3). The absolute value is factored and the sign is preserved. - Rational-radical coefficients:
√2·2ˣ→2^(x+½),2√2·2ˣ→2^(x+3/2),(√2/2)·2ˣ→2^(x-½). Decomposes(num/den)·√radicalinto prime contributions from all three components (radical primes get half-integer exponents, numerator primes get positive exponents, denominator primes get negative exponents). - Rational coefficients:
2ˣ/4→2^(x-2),3ˣ/9→3^(x-2). Factors both numerator (positive exponents) and denominator (negative exponents).
- Multi-prime coefficients:
-
Improved Cost Function for Negated Powers:
Negate(Power(...))now costs3 + cost(exponent), consistent with the cost ofMultiply(-1, Power(...)). This makes the cost model more accurate when comparing negated power forms.
Assumptions & Types
-
Improved
ask()Queries:ce.ask()now matches patterns with wildcards correctly, can answer common "bound" queries such asask(["Greater", "x", "_k"])andask(["Greater", "_x", "_k"]), normalizes inequality patterns for matching (e.g.ask(["Greater", "_x", 0])), and falls back toverify()for closed predicates when the fact is known but not stored as an explicit assumption. -
Tri-state
verify(): Implementedce.verify()as a truth query that returnstrue,falseorundefinedwhen a predicate cannot be determined from the current assumptions and declarations.And/Or/Notuse 3-valued logic. -
Element/NotElementType Membership:Element(x, T)andNotElement(x, T)now support type-style RHS (e.g.real,finite_real,number,any) in addition to set collections (e.g.RealNumbers,Integers). -
Value Resolution from Equality Assumptions: After
ce.assume(['Equal', symbol, value]), the symbol now evaluates to the assumed value:ce.assume(ce.box(['Equal', 'one', 1]));ce.box('one').evaluate(); // → 1 (was: 'one')ce.box(['Equal', 'one', 1]).evaluate(); // → True (was: ['Equal', 'one', 1])ce.box(['Equal', 'one', 0]).evaluate(); // → Falsece.box('one').type.matches('integer'); // → trueThis also fixes comparison evaluation:
Equal(symbol, assumed_value)now correctly evaluates toTrueinstead of staying symbolic. -
Inequality Evaluation Using Assumptions: Inequality comparisons can use transitive bounds extracted from assumptions.
ce.assume(ce.box(['Greater', 'x', 4]));ce.box(['Greater', 'x', 0]).evaluate(); // → True (x > 4 > 0)ce.box(['Less', 'x', 0]).evaluate(); // → Falsece.box('x').isGreater(0); // → truece.box('x').isPositive; // → true -
Type Inference from Assumptions: Inequalities infer
real; equalities infer from the value.ce.assume(ce.box(['Greater', 'x', 4]));ce.box('x').type.toString(); // → 'real' (was: 'unknown')ce.assume(ce.box(['Equal', 'one', 1]));ce.box('one').type.toString(); // → 'integer' (was: 'unknown') -
Tautology and Contradiction Detection:
ce.assume()returns'tautology'for redundant assumptions and'contradiction'for conflicts.ce.assume(ce.box(['Greater', 'x', 4]));// Redundant assumption (x > 4 implies x > 0)ce.assume(ce.box(['Greater', 'x', 0])); // → 'tautology' (was: 'ok')// Conflicting assumption (x > 4 contradicts x < 0)ce.assume(ce.box(['Less', 'x', 0])); // → 'contradiction'// Same assumption repeatedce.assume(ce.box(['Equal', 'one', 1]));ce.assume(ce.box(['Equal', 'one', 1])); // → 'tautology'// Conflicting equalityce.assume(ce.box(['Less', 'one', 0])); // → 'contradiction'
Solving
-
Systems of Linear Equations: The
solve()method now handles systems of linear equations parsed from LaTeX\begin{cases}...\end{cases}environments. Returns an object mapping variable names to their solutions.const e = ce.parse('\\begin{cases}x+y=70\\\\2x-4y=80\\end{cases}');const result = e.solve(['x', 'y']);console.log(result.x.json); // 60console.log(result.y.json); // 10// 3x3 systems work tooconst e2 = ce.parse('\\begin{cases}x+y+z=6\\\\2x+y-z=1\\\\x-y+2z=5\\end{cases}');const result2 = e2.solve(['x', 'y', 'z']);// → { x: 1, y: 2, z: 3 }Non-linear systems that don't match known patterns and inconsistent systems return
null. -
Non-linear Polynomial Systems: The
solve()method now handles certain non-linear polynomial systems with 2 equations and 2 variables:-
Product + sum pattern: Systems like
xy = p, x + y = sare solved by recognizing that x and y are roots of the quadratict² - st + p = 0. -
Substitution method: When one equation is linear in one variable, it substitutes into the other equation and solves the resulting univariate equation.
Returns an array of solution objects (multiple solutions possible):
// Product + sum patternconst e = ce.parse('\\begin{cases}xy=6\\\\x+y=5\\end{cases}');const result = e.solve(['x', 'y']);// → [{ x: 2, y: 3 }, { x: 3, y: 2 }]// Substitution methodconst e2 = ce.parse('\\begin{cases}x+y=5\\\\x^2+y=7\\end{cases}');const result2 = e2.solve(['x', 'y']);// → [{ x: 2, y: 3 }, { x: -1, y: 6 }]Only real solutions are returned; complex solutions are filtered out.
-
-
Exact Rational Arithmetic in Linear Systems: The linear system solver now uses exact rational arithmetic throughout the Gaussian elimination process. Systems with fractional coefficients produce exact fractional results rather than floating-point approximations.
const e = ce.parse('\\begin{cases}x+y=1\\\\x-y=1/2\\end{cases}');const result = e.solve(['x', 'y']);console.log(result.x.json); // ["Rational", 3, 4] (exact 3/4)console.log(result.y.json); // ["Rational", 1, 4] (exact 1/4)// Fractional coefficientsconst e2 = ce.parse('\\begin{cases}x/3+y/2=1\\\\x/4+y/5=1\\end{cases}');const result2 = e2.solve(['x', 'y']);// → { x: 36/7, y: -10/7 } -
Linear Inequality Systems: The
solve()method now handles systems of linear inequalities in 2 variables, returning the vertices of the feasible region (convex polygon). Supports all inequality operators:<,<=,>,>=.// Triangle: x >= 0, y >= 0, x + y <= 10const e = ce.parse('\\begin{cases}x\\geq 0\\\\y\\geq 0\\\\x+y\\leq 10\\end{cases}');const result = e.solve(['x', 'y']);// → [{ x: 0, y: 0 }, { x: 10, y: 0 }, { x: 0, y: 10 }]// Square: 0 <= x <= 5, 0 <= y <= 5const square = ce.parse('\\begin{cases}x\\geq 0\\\\x\\leq 5\\\\y\\geq 0\\\\y\\leq 5\\end{cases}');square.solve(['x', 'y']);// → [{ x: 0, y: 0 }, { x: 5, y: 0 }, { x: 5, y: 5 }, { x: 0, y: 5 }]Vertices are returned in counterclockwise convex hull order. Returns
nullfor infeasible systems or non-linear constraints. -
Under-determined Systems (Parametric Solutions): The
solve()method now returns parametric solutions for under-determined linear systems (fewer equations than variables) instead of returningnull. Free variables appear as themselves in the solution, with other variables expressed in terms of them.// Single equation with two variablesconst e = ce.parse('\\begin{cases}x+y=5\\end{cases}');const result = e.solve(['x', 'y']);// → { x: -y + 5, y: y } (y is a free variable)// Two equations with three variablesconst e2 = ce.parse('\\begin{cases}x+y+z=6\\\\x-y=2\\end{cases}');const result2 = e2.solve(['x', 'y', 'z']);// → { x: -z/2 + 4, y: -z/2 + 2, z: z } (z is a free variable)Inconsistent systems still return
null. -
Extended Sqrt Equation Solving: The equation solver now handles sqrt equations of the form
√(f(x)) = g(x)by squaring both sides and solving the resulting polynomial. Extraneous roots are automatically filtered.ce.parse('\\sqrt{x+1} = x').solve('x'); // → [1.618...] (golden ratio)ce.parse('\\sqrt{2x+3} = x - 1').solve('x'); // → [4.449...]ce.parse('\\sqrt{3x-2} = x').solve('x'); // → [1, 2]ce.parse('\\sqrt{x} = x').solve('x'); // → [0, 1] -
Two Sqrt Equation Solving: The equation solver now handles equations with two sqrt terms of the form
√(f(x)) + √(g(x)) = eusing double squaring. Both addition and subtraction forms are supported, and extraneous roots are automatically filtered.ce.parse('\\sqrt{x+1} + \\sqrt{x+4} = 3').solve('x'); // → [0]ce.parse('\\sqrt{x} + \\sqrt{x+7} = 7').solve('x'); // → [9]ce.parse('\\sqrt{x+5} - \\sqrt{x-3} = 2').solve('x'); // → [4]ce.parse('\\sqrt{2x+1} + \\sqrt{x-1} = 4').solve('x'); // → [46 - 8√29] ≈ 2.919 -
Nested Sqrt Equation Solving: The equation solver now handles nested sqrt equations of the form
√(x + √x) = ausing substitution. These patterns have √x inside the argument of an outer sqrt. The solver uses u = √x substitution, solves the resulting quadratic, and filters negative u values.ce.parse('\\sqrt{x + 2\\sqrt{x}} = 3').solve('x'); // → [11 - 2√10] ≈ 4.675ce.parse('\\sqrt{x + \\sqrt{x}} = 2').solve('x'); // → [9/2 - √17/2] ≈ 2.438ce.parse('\\sqrt{x - \\sqrt{x}} = 1').solve('x'); // → [φ²] ≈ 2.618 -
Quadratic Equations Without Constant Term: Added support for solving quadratic equations of the form
ax² + bx = 0(missing constant term). These are solved by factoring:x(ax + b) = 0→x = 0orx = -b/a.ce.parse('x^2 + 3x = 0').solve('x'); // → [0, -3]ce.parse('2x^2 - 4x = 0').solve('x'); // → [0, 2]
Subscripts & Indexing
-
Subscript Evaluation Handler: Define custom evaluation functions for subscripted symbols like mathematical sequences using
subscriptEvaluate:// Define a Fibonacci sequencece.declare('F', {subscriptEvaluate: (subscript, { engine }) => {const n = subscript.re;if (!Number.isInteger(n) || n < 0) return undefined;// Calculate Fibonacci number...return engine.number(fibValue);},});ce.parse('F_{10}').evaluate(); // → 55ce.parse('F_5').evaluate(); // → 5ce.parse('F_n').evaluate(); // → stays symbolic (handler returns undefined)Both simple subscripts (
F_5) and complex subscripts (F_{5}) are supported. When the handler returnsundefined, the expression stays symbolic. Subscripted expressions withsubscriptEvaluatehave typenumberand can be used in arithmetic operations:ce.parse('F_{5} + F_{3}').evaluate()works correctly. -
Type-Aware Subscript Handling: Subscripts on symbols declared as collection types (list, tuple, matrix, etc.) now automatically convert to
At()indexing operations:ce.declare('v', 'list<number>');ce.parse('v_n'); // → At(v, n)ce.parse('v_{n+1}'); // → At(v, n+1)ce.parse('v_{i,j}'); // → At(v, Tuple(i, j))This works for both simple subscripts (
v_n) and complex subscripts (v_{n+1}). The type of theAt()expression is correctly inferred from the collection's element type, allowing subscripted collection elements to be used in arithmetic. -
Complex Subscripts in Arithmetic (Issue #273): Subscript expressions like
a_{n+1}can now be used in arithmetic operations without type errors:ce.parse('a_{n+1} + 1'); // → Add(Subscript(a, n+1), 1)ce.parse('2 * a_{n+1}'); // → Multiply(2, Subscript(a, n+1))ce.parse('a_{n+1}^2'); // → Power(Subscript(a, n+1), 2)Previously, complex subscripts would fail with "incompatible-type" errors when used in arithmetic contexts.
-
Multi-Index
At()Support: TheAtfunction now supports multiple indices for accessing nested collections (e.g., matrices):const matrix = ce.box(['List', ['List', 2, 3, 4], ['List', 6, 7, 9]]);ce.box(['At', matrix, 1, 2]).evaluate(); // → 3 (row 1, column 2)The signature was updated from single index to variadic:
(value: indexed_collection, index: (number|string)+) -> unknown -
Text Subscripts: Added support for
\text{}in subscripts, allowing descriptive subscript names:ce.parse('x_{\\text{max}}'); // → symbol "x_max"ce.parse('v_{\\text{initial}}'); // → symbol "v_initial"
Sequences
-
Declarative Sequence Definitions: Define mathematical sequences using recurrence relations with the new
declareSequence()method:// Fibonacci sequencece.declareSequence('F', {base: { 0: 0, 1: 1 },recurrence: 'F_{n-1} + F_{n-2}',});ce.parse('F_{10}').evaluate(); // → 55ce.parse('F_{20}').evaluate(); // → 6765// Arithmetic sequence: a_n = a_{n-1} + 2, a_0 = 1ce.declareSequence('A', {base: { 0: 1 },recurrence: 'A_{n-1} + 2',});ce.parse('A_{5}').evaluate(); // → 11// Factorial via recurrencece.declareSequence('H', {base: { 0: 1 },recurrence: 'n \\cdot H_{n-1}',});ce.parse('H_{5}').evaluate(); // → 120Features:
- Base cases as index → value mapping
- Recurrence relation as LaTeX string or BoxedExpression
- Automatic memoization for efficient evaluation (configurable)
- Custom index variable name (default:
n) - Domain constraints (min/max valid indices)
- Symbolic subscripts stay symbolic (e.g.,
F_kremains unevaluated)
Alternatively, sequences can be defined using natural LaTeX assignment notation:
// Arithmetic sequence via LaTeXce.parse('L_0 := 1').evaluate();ce.parse('L_n := L_{n-1} + 2').evaluate();ce.parse('L_{5}').evaluate(); // → 11// Fibonacci via LaTeXce.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(); // → 55Base cases and recurrence can be defined in any order. The sequence is finalized when both are present.
-
Sequence Status API: Query the status of sequence definitions with
getSequenceStatus():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] }ce.getSequenceStatus('x');// → { status: 'not-a-sequence', hasBase: false, hasRecurrence: false } -
Sequence Introspection API: Inspect and manage defined sequences:
// Get sequence informationce.getSequence('F');// → { name: 'F', variable: 'n', baseIndices: [0, 1], memoize: true, cacheSize: 5 }// List all defined sequencesce.listSequences(); // → ['F', 'A', 'H']// Check if a symbol is a sequencece.isSequence('F'); // → truece.isSequence('x'); // → false// Manage memoization cachece.getSequenceCache('F'); // → Map { 2 => 1, 3 => 2, ... }ce.clearSequenceCache('F'); // Clear cache for specific sequencece.clearSequenceCache(); // Clear all sequence caches -
Generate Sequence Terms: Generate a list of sequence terms with
getSequenceTerms():ce.declareSequence('F', {base: { 0: 0, 1: 1 },recurrence: 'F_{n-1} + F_{n-2}',});ce.getSequenceTerms('F', 0, 10);// → [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]// With step parameter (every other term)ce.getSequenceTerms('F', 0, 10, 2);// → [0, 1, 3, 8, 21, 55] -
Sum and Product over Sequences:
SumandProductnow work seamlessly with user-defined sequences:ce.declareSequence('F', {base: { 0: 0, 1: 1 },recurrence: 'F_{n-1} + F_{n-2}',});ce.parse('\\sum_{k=0}^{10} F_k').evaluate(); // → 143ce.parse('\\prod_{k=1}^{5} A_k').evaluate(); // Works with any defined sequence -
OEIS Integration: Look up sequences in the Online Encyclopedia of Integer Sequences (OEIS) and verify your sequences against known mathematical sequences:
// Look up a sequence by its termsconst results = await ce.lookupOEIS([0, 1, 1, 2, 3, 5, 8, 13]);// → [{ id: 'A000045', name: 'Fibonacci numbers', terms: [...], url: '...' }]// Check if your sequence matches a known OEIS sequencece.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: [...] }Note: OEIS lookups require network access to oeis.org.
-
Multi-Index Sequences: Define sequences with multiple indices like Pascal's triangle
P_{n,k}or grid-based recurrences:// Pascal's Triangle: P_{n,k} = P_{n-1,k-1} + P_{n-1,k}ce.declareSequence('P', {variables: ['n', 'k'],base: { 'n,0': 1, 'n,n': 1 }, // Pattern-based base casesrecurrence: 'P_{n-1,k-1} + P_{n-1,k}',domain: { n: { min: 0 }, k: { min: 0 } },constraints: 'k <= n', // k must not exceed n});ce.parse('P_{5,2}').evaluate(); // → 10ce.parse('P_{10,5}').evaluate(); // → 252Features:
- Multiple index variables with
variables: ['n', 'k'] - Pattern-based base cases:
'n,0'matches any (n, 0),'n,n'matches diagonal - Per-variable domain constraints
- Constraint expressions (e.g.,
'k <= n') - Composite key memoization (e.g.,
'5,2') - Full introspection support with
isMultiIndexflag
Pattern matching for base cases:
- Exact values:
'0,0'matches only (0, 0) - Wildcards:
'n,0'matches any value for n with k=0 - Equality:
'n,n'matches when both indices are equal - Priority: exact matches are checked before patterns
- Multiple index variables with
Special Functions
-
Special Function Definitions: Added type signatures for special mathematical functions, enabling them to be used in expressions without type errors:
Zeta- Riemann zeta function\zeta(s)Beta- Euler beta functionB(a,b) = \Gamma(a)\Gamma(b)/\Gamma(a+b)LambertW- Lambert W function (product logarithm)BesselJ,BesselY,BesselI,BesselK- Bessel functions of first/second kindAiryAi,AiryBi- Airy functions
These functions now have proper signatures and can be composed with other expressions:
ce.box(['Add', 1, ['LambertW', 'x']])works correctly. -
Special Function LaTeX Parsing: Added LaTeX parsing support for special functions:
\zeta(s),\Beta(a,b),\operatorname{W}(x), Bessel functions via\operatorname{J},\operatorname{Y}, etc., and Airy functions via\operatorname{Ai},\operatorname{Bi}.
Calculus
-
LambertW Derivative: Added derivative rule for the Lambert W function:
d/dx W(x) = W(x)/(x·(1+W(x))) -
Bessel Function Derivatives: Added derivative support for all four Bessel function types using order-dependent recurrence relations:
ce.box(['D', ['BesselJ', 'n', 'x'], 'x']).evaluate();// → 1/2 * BesselJ(n-1, x) - 1/2 * BesselJ(n+1, x)ce.box(['D', ['BesselI', 'n', 'x'], 'x']).evaluate();// → 1/2 * BesselI(n-1, x) + 1/2 * BesselI(n+1, x)ce.box(['D', ['BesselK', 'n', 'x'], 'x']).evaluate();// → -1/2 * BesselK(n-1, x) - 1/2 * BesselK(n+1, x)Chain rule is automatically applied for composite arguments.
-
Multi-Argument Function Derivatives: Added derivative support for:
-
Log(x, base) - Logarithm with custom base:
ce.box(['D', ['Log', 'x', 2], 'x']).evaluate(); // → 1/(x·ln(2))ce.box(['D', ['Log', 'x', 'a'], 'x']).evaluate(); // → 1/(x·ln(a))Also handles cases where both x and base depend on the variable by applying the quotient rule to ln(x)/ln(base).
-
Discrete functions (Mod, GCD, LCM) - Return 0 as these are step functions with derivative 0 almost everywhere:
ce.box(['D', ['Mod', 'x', 5], 'x']).evaluate(); // → 0ce.box(['D', ['GCD', 'x', 6], 'x']).evaluate(); // → 0
-
-
Integration of
1/(x·ln(x))Pattern: Added support for integrating expressions where the denominator is a product and one factor is the derivative of another:ce.parse('\\int \\frac{1}{x\\ln x} dx').evaluate(); // → ln(|ln(x)|)ce.parse('\\int \\frac{3}{x\\ln x} dx').evaluate(); // → 3·ln(|ln(x)|)This uses u-substitution: since
1/x = d/dx(ln(x)), the integral becomes∫ h'(x)/h(x) dx = ln|h(x)|. -
Cyclic Integration for e^x with Trigonometric Functions: Added support for integrating products of exponentials and trigonometric functions that require the "solve for the integral" technique:
ce.parse('\\int e^x \\sin x dx').evaluate();// → -1/2·cos(x)·e^x + 1/2·sin(x)·e^xce.parse('\\int e^x \\cos x dx').evaluate();// → 1/2·sin(x)·e^x + 1/2·cos(x)·e^x// Also works with linear arguments:ce.parse('\\int e^x \\sin(2x) dx').evaluate();// → -2/5·cos(2x)·e^x + 1/5·sin(2x)·e^xce.parse('\\int e^x \\cos(2x) dx').evaluate();// → 1/5·cos(2x)·e^x + 2/5·sin(2x)·e^xThese patterns cannot be solved by standard integration by parts (which would lead to infinite recursion) and instead use direct formulas:
∫ e^x·sin(ax+b) dx = (e^x/(a²+1))·(sin(ax+b) - a·cos(ax+b))∫ e^x·cos(ax+b) dx = (e^x/(a²+1))·(a·sin(ax+b) + cos(ax+b))
-
Derivative Recursion Safety: Added recursion protection to
differentiate()with a depth limit (MAX_DIFFERENTIATION_DEPTH), returningundefinedwhen the limit is exceeded. -
Equation Equivalence in
isEqual()(Issue #275): Two equations are now recognized as equivalent if they have the same solution set:ce.parse('2x+1=0').isEqual(ce.parse('x=-1/2')); // → truece.parse('3x+1=0').isEqual(ce.parse('6x+2=0')); // → trueUses sampling to check whether (LHS₁-RHS₁)/(LHS₂-RHS₂) is a non-zero constant.
Logic
-
Boolean Simplification Rules: Added absorption laws and improved boolean expression simplification:
- Absorption:
A ∧ (A ∨ B) → AandA ∨ (A ∧ B) → A - Idempotence:
A ∧ A → AandA ∨ A → A - Complementation:
A ∧ ¬A → FalseandA ∨ ¬A → True - Identity:
A ∧ True → AandA ∨ False → A - Domination:
A ∧ False → FalseandA ∨ True → True - Double negation:
¬¬A → A
These rules are applied automatically during simplification:
ce.box(['And', 'A', ['Or', 'A', 'B']]).simplify(); // → Ace.box(['Or', 'A', ['And', 'A', 'B']]).simplify(); // → A - Absorption:
-
Prime Implicants and Minimal Normal Forms: Added Quine-McCluskey algorithm for finding prime implicants/implicates and computing minimal CNF/DNF:
PrimeImplicants(expr)- Find all prime implicants (minimal product terms)PrimeImplicates(expr)- Find all prime implicates (minimal sum clauses)MinimalDNF(expr)- Convert to minimal DNF using prime implicant coverMinimalCNF(expr)- Convert to minimal CNF using prime implicate cover
// Find prime implicants (terms that can't be further simplified)ce.box(['PrimeImplicants', ['Or', ['And', 'A', 'B'], ['And', 'A', ['Not', 'B']]]]).evaluate();// → [A] (AB and A¬B combine to just A)// Compute minimal DNFce.box(['MinimalDNF', ['Or',['And', 'A', 'B'],['And', 'A', ['Not', 'B']],['And', ['Not', 'A'], 'B']]]).evaluate();// → A ∨ B (simplified from 3 terms to 2)Limited to 12 variables to prevent exponential blowup; larger expressions return unevaluated.
Linear Algebra
-
Matrix Decompositions: Added four matrix decomposition functions for numerical linear algebra:
LUDecomposition(A)→[P, L, U]- LU factorization with partial pivotingQRDecomposition(A)→[Q, R]- QR factorization using Householder reflectionsCholeskyDecomposition(A)→L- Cholesky factorization for positive definite matricesSVD(A)→[U, Σ, V]- Singular Value Decomposition
ce.box(['LUDecomposition', [[4, 3], [6, 3]]]).evaluate();// → [P, L, U] where PA = LUce.box(['QRDecomposition', [[1, 2], [3, 4]]]).evaluate();// → [Q, R] where A = QR, Q orthogonal, R upper triangularce.box(['CholeskyDecomposition', [[4, 2], [2, 2]]]).evaluate();// → L where A = LL^Tce.box(['SVD', [[1, 2], [3, 4]]]).evaluate();// → [U, Σ, V] where A = UΣV^T
Fixed
-
replace() Literal Matching in Object Rules:
.replace({ match: 'a', replace: 2 })no longer treats'a'as a wildcard (string rules like"a*x -> 2*x"still auto-wildcard).const expr = ce.box(['Add', ['Multiply', 'a', 'x'], 'b']);expr.replace({match: 'a', replace: 2}, {recursive: true});// → 2x + b (was: 2 - incorrectly matched entire expression) -
forget() Clears Assumed Values:
ce.forget()now clears values set by equality assumptions across all evaluation context frames.ce.assume(ce.box(['Equal', 'x', 5]));ce.box('x').evaluate(); // → 5ce.forget('x');ce.box('x').evaluate(); // → 'x' (was: 5) -
Scoped Assumptions Clean Up on popScope(): Assumptions made inside a scope no longer leak after
popScope().ce.pushScope();ce.assume(ce.box(['Equal', 'y', 10]));ce.box('y').evaluate(); // → 10ce.popScope();ce.box('y').evaluate(); // → 'y' (was: 10) -
Extraneous Root Filtering for Sqrt Equations: Candidate solutions are now validated against the original expression (before clearing denominators / harmonization) to filter extraneous roots.
Examples of equations that now correctly filter extraneous roots:
√x = x - 2→ returns[4](filters out x=1)√x + x - 2 = 0→ returns[1](filters out x=4)√x - x + 2 = 0→ returns[4](filters out x=1)x - 2√x - 3 = 0→ returns[9](filters out x=1)2x + 3√x - 2 = 0→ returns[1/4](filters out x=4)
-
Simplification (#178):
- Safer division canonicalization for denominators that may simplify to
0 - Implicit multiplication powers:
xx→x^2 - Targeted exp/log rewriting for
\exp(\log(x)±y)
- Safer division canonicalization for denominators that may simplify to
0.33.0 2026-01-30
Resolved Issues
Arithmetic and Infinity
-
Division by Zero: Improved handling of division by zero:
0/0returnsNaN(indeterminate form)a/0wherea ≠ 0returnsComplexInfinity(~∞) as a "better NaN" that indicates an infinite result with unknown sign- This applies to all forms including
1/0,x/0, and rational literals
-
Infinity Sign Propagation: Fixed infinity multiplication not propagating signs correctly. Now
∞ * (-2) = -∞and-∞ * 2 = -∞as expected. -
Infinity Division: Fixed
∞/∞incorrectly returning1. Now correctly returnsNaN(indeterminate form). Thea/a → 1simplification rule now excludes infinity values.
Trigonometry
-
Trigonometric Period Identities: Fixed incorrect sign handling for
csc(π+x)andcot(π+x):csc(π+x)now correctly simplifies to-csc(x)(was incorrectlycsc(x))cot(π+x)now correctly simplifies tocot(x)(was incorrectly-cot(x), cotangent has period π)
-
Trigonometric Co-function Identities: Fixed co-function identities not applying to canonical form expressions. Now correctly simplifies:
sin(π/2 - x)→cos(x)cos(π/2 - x)→sin(x)tan(π/2 - x)→cot(x)cot(π/2 - x)→tan(x)sec(π/2 - x)→csc(x)csc(π/2 - x)→sec(x)
-
Double Angle with Coefficient: Fixed
2sin(x)cos(x)not simplifying tosin(2x). The product-to-sum identity now handles coefficients:2sin(x)cos(x)→sin(2x)c·sin(x)cos(x)→c·sin(2x)/2for any coefficientc
-
Trigonometric Product Identities: Improved handling of trig products in simplification. The Multiply rule now correctly defers to trig-specific rules for patterns like
sin(x)*cos(x)andtan(x)*cot(x), ensuring these are simplified tosin(2x)/2and1respectively.
Logarithms and Exponentials
-
Logarithm-Exponential Composition: Fixed
log(exp(x))incorrectly simplifying tox. Now correctly returnsx/ln(10)≈0.434xsincelog₁₀(eˣ) = x·log₁₀(e) = x/ln(10). The identitylog(exp(x)) = xonly holds for natural logarithm. -
Logarithm of e: Added simplification for
log(e)→1/ln(10)≈0.434andlog_c(e)→1/ln(c)for any basec. -
Logarithm Combination Base Preservation: Fixed
log(x) + log(y)(base 10) incorrectly becomingln(xy). Now correctly produceslog(xy)preserving the original base. -
Logarithm Quotient Rule: Added expansion rule for logarithm of quotients.
ln(x/y)now simplifies toln(x) - ln(y)when x and y are known positive. Similarly for any base:log_c(x/y)→log_c(x) - log_c(y). -
Exponential-Logarithm Composition: Added simplification for
exp(log(x))where log has a different base than e. Nowe^log(x)→x^{1/ln(10)}and more generallye^log_c(x)→x^{1/ln(c)}for any base c.
Powers and Exponents
-
Zero Power with Symbolic Exponent: Fixed
0^πand similar expressions with positive symbolic exponents not simplifying. Now0^x→0whenxis known to be positive (includingπ,e, etc.). -
Exponent Evaluation in Products: Fixed
(x³)² · (y²)²not simplifying tox⁶y⁴. Numeric subexpressions in exponents (like2×3inx^{2×3}) are now evaluated when the expression is part of a product. -
Negative Exponents on Fractions: Fixed
(a/b)^{-n}not simplifying properly. Now(x³/y²)^{-2}correctly simplifies toy⁴/x⁶during canonicalization by distributing the negative exponent. -
Negative Base with Fractional Exponent: Fixed
(-ax)^{p/q}returning complex results whenpandqare both odd. Now correctly factors out the negative sign:(-2x)^{3/5}→-(2x)^{3/5}=-2^{3/5}·x^{3/5}, giving real results. This affects products like(-2x)^{3/5}·xwhich now correctly simplify to-2^{3/5}·x^{8/5}instead of returning an imaginary value.
Radicals
-
Radical Perfect Square Factoring: Fixed
√(x²y)not simplifying to|x|√y. Adjusted cost function to penalize radicals containing perfect squares, enabling the simplification rule to apply. -
Generalized Root Extraction: Added comprehensive root simplification rules:
√[n]{x^m}→x^{m/n}for odd roots (always valid)√[n]{x^m}→|x|^{m/n}for even roots with integer result√{x^{odd}}→|x|^n · √xfactoring (e.g.,√{x⁵}→|x|²√x)- Handles all combinations:
√[4]{x⁶}→|x|^{3/2},√[3]{x⁶}→x²
-
Symbolic Radicals Preservation: Fixed numeric radicals (
√2,∛5,2^{3/5}) being evaluated to floating-point approximations during multiplication. Nowx * √2stays as√2 · xinstead of1.414... · x, andx * 2^{1/3}stays asx · ∛2instead of1.259... · x. This preserves exact irrational values and allows proper algebraic manipulation. Use.N()to get numeric approximations when needed.
LaTeX Parsing
- LaTeX
\exp()Juxtaposition: Fixed adjacent\exp()calls not parsing as multiplication. Now\exp(x)\exp(2)correctly parses ase^x · e^2instead of producing a parse error. The expression then simplifies toe^{x+2}as expected.
Features
Trigonometry
-
Fu Algorithm for Trigonometric Simplification: Implemented the Fu algorithm based on Fu, Zhong, and Zeng's paper "Automated and readable simplification of trigonometric expressions" (2006). This provides systematic, high-quality trigonometric simplification through:
-
Transformation Rules (TR1-TR22): Comprehensive set of rewrite rules including reciprocal conversions (sec→1/cos), ratio forms (tan→sin/cos), Pythagorean substitutions (sin²+cos²=1), power reductions, product-to-sum, sum-to-product, angle expansion/contraction, and Morrie's law for cosine product chains.
-
Rule Lists (RL1, RL2): Organized application sequences for tan/cot expressions and sin/cos expressions respectively, with greedy selection of optimal results.
-
Cost Function: Minimizes trigonometric function count as primary metric, with leaf count as secondary, to find the most readable form.
Usage:
// Option 1: Use strategy option with simplify()const result = expr.simplify({ strategy: 'fu' });// Option 2: Dedicated trigSimplify() methodconst result = expr.trigSimplify();Examples:
sin(x)⁴ - cos(x)⁴→-cos(2x)tan(x)·cot(x)→1sin²(x) + cos²(x)→12sin(x)cos(x)→sin(2x)cos(x)·cos(2x)·cos(4x)→sin(8x)/(8sin(x))(Morrie's law)
Enhanced Transformations:
-
TRmorrie with Rational Coefficients: Morrie's law now handles angles that are rational multiples of π, such as
cos(π/9)·cos(2π/9)·cos(4π/9)→1/8. The algorithm detects maximal geometric sequences and handles cases where the sine terms cancel to produce pure fractions. -
TR12i Tangent Sum Identity: Recognizes the pattern
tan(A) + tan(B) - k·tan(A)·tan(B)and simplifies to-tan(C)whenA + B + C = πandk = tan(C). Works with standard angles (π/6, π/4, π/3, etc.) and handles sign variations. -
TRpythagorean for Compound Expressions: Detects
sin²(x) + cos²(x)pairs within larger Add expressions and simplifies them to 1, e.g.,sin²(x) + cos²(x) + 2→3. -
Early TR9 Sum-to-Product: Applies sum-to-product transformation before angle expansion to catch patterns like
sin(x+h) + sin(x-h)→2sin(x)cos(h)that would otherwise be expanded and lose their simplified form. -
Dual Strategy Approach: The Fu strategy now tries both "Fu first" and "simplify first" approaches and picks the best result. This handles both Morrie-like patterns (which need Fu before evaluation) and period reduction patterns (which need simplification first for angle contraction).
-
-
Trigonometric Periodicity Reduction: Trigonometric functions now simplify arguments containing integer multiples of π:
sin(5π + k)→-sin(k)(period 2π, with sign change for odd multiples)cos(4π + k)→cos(k)(period 2π)tan(3π + k)→tan(k)(period π)- Works for all six trig functions: sin, cos, tan, cot, sec, csc
- Handles both positive and negative multiples of π
-
Pythagorean Trigonometric Identities: Added simplification rules for all Pythagorean identities:
sin²(x) + cos²(x)→11 - sin²(x)→cos²(x)and1 - cos²(x)→sin²(x)sin²(x) - 1→-cos²(x)andcos²(x) - 1→-sin²(x)tan²(x) + 1→sec²(x)andsec²(x) - 1→tan²(x)1 + cot²(x)→csc²(x)andcsc²(x) - 1→cot²(x)a·sin²(x) + a·cos²(x)→a(with coefficient)
-
Trigonometric Equation Solving: The
solve()method now handles basic trigonometric equations:sin(x) = a→x = arcsin(a)andx = π - arcsin(a)(two solutions)cos(x) = a→x = arccos(a)andx = -arccos(a)(two solutions)tan(x) = a→x = arctan(a)(one solution per period)cot(x) = a→x = arccot(a)- Supports coefficient form:
a·sin(x) + b = 0 - Domain validation: returns no solutions when |a| > 1 for sin/cos
- Automatic deduplication of equivalent solutions (e.g.,
cos(x) = 1→ single solution0)
Calculus
-
(#163) Additional Derivative Notations: Added support for parsing multiple derivative notations beyond Leibniz notation:
-
Newton's dot notation for time derivatives:
\dot{x}→["D", "x", "t"],\ddot{x}for second derivative,\dddot{x}and\ddddot{x}for higher orders. The time variable is configurable via the newtimeDerivativeVariableparser option (default:"t"). -
Lagrange prime notation with arguments:
f'(x)now parses to["D", ["f", "x"], "x"], inferring the differentiation variable from the function argument. Works forf''(x),f'''(x), etc. for higher derivatives. -
Euler's subscript notation:
D_x f→["D", "f", "x"]andD^2_x forD_x^2 ffor second derivatives. -
Derivative serialization:
Dexpressions now serialize to Leibniz notation (\frac{\mathrm{d}}{\mathrm{d}x}f) for consistent round-trip parsing.
-
-
Derivative Rules for Special Functions: Added derivative formulas for:
d/dx Digamma(x) = Trigamma(x)d/dx Erf(x),d/dx Erfc(x),d/dx Erfi(x)d/dx FresnelS(x),d/dx FresnelC(x)d/dx LogGamma(x) = Digamma(x)
Special Functions
- Special Function Definitions: Added type signatures for Digamma, Trigamma,
and PolyGamma functions to the library:
Digamma(x)- The digamma function ψ(x), logarithmic derivative of GammaTrigamma(x)- The trigamma function ψ₁(x), derivative of digammaPolyGamma(n, x)- The polygamma function ψₙ(x), nth derivative of digamma
Logarithms and Exponentials
-
Logarithm Combination Rules: Added simplification rules that combine logarithms with the same base:
ln(x) + ln(y)→ln(xy)(addition combines via multiplication)ln(x) - ln(y)→ln(x/y)(subtraction combines via division)log_c(x) + log_c(y)→log_c(xy)(works with any base)log_c(x) - log_c(y)→log_c(x/y)- Handles multiple terms:
ln(a) + ln(b) - ln(c)→ln(ab/c)
-
Exponential e Simplification: Added rules for combining powers of e:
eˣ · eʸ→e^(x+y)(same-base multiplication)eˣ / eʸ→e^(x-y)(same-base division)eˣ · e→e^(x+1)andeˣ / e→e^(x-1)- Preserves symbolic form instead of evaluating e^n numerically
Powers and Exponents
-
Negative Base Power Simplification: Added rules to simplify powers with negated bases:
(-x)^n→x^nwhen n is even (e.g.,(-x)^4→x^4)(-x)^n→-x^nwhen n is odd (e.g.,(-x)^3→-x^3)(-x)^{n/m}→x^{n/m}when n is even and m is odd(-x)^{n/m}→-x^{n/m}when both n and m are odd(-1)^{p/q}→-1when both p and q are odd (real odd root)
-
Power Distribution: Added rule to distribute integer exponents over products:
(ab)^n→a^n · b^nwhen n is an integer- Example:
(x³y²)²→x⁶y⁴ - Example:
(-2x)²→4x²
-
Same-Base Power Combination: Improved power combination for products with 3+ terms:
a³ · a · a²→a⁶(combines all same-base terms)- Works with unknown symbols when sum of exponents is positive
- Handles mixed products:
b³c²dx⁷ya⁵gb²x⁵(3b)→3dgyx¹²b⁶a⁵c²
Sum and Product
- (#133)
Element-based Indexing Sets for Sum/Product: Added support for
\innotation in summation and product subscripts:-
Parsing:
\sum_{n \in \{1,2,3\}} nnow correctly parses to["Sum", "n", ["Element", "n", ["Set", 1, 2, 3]]]instead of silently dropping the constraint. -
Evaluation: Sums and products over finite sets, lists, and ranges are now evaluated correctly:
\sum_{n \in \{1,2,3\}} n→6\sum_{n \in \{1,2,3\}} n^2→14\prod_{k \in \{1,2,3,4\}} k→24
-
Serialization: Element-based indexing sets serialize back to LaTeX with proper
\innotation:\sum_{n\in \{1, 2, 3\}}n -
Range support: Works with
Rangeexpressions viace.box():["Sum", "n", ["Element", "n", ["Range", 1, 5]]]→15 -
Bracket notation as Range: Two-element integer lists in bracket notation
[a,b]are now treated as Range(a,b) when used in Element context:\sum_{n \in [1,5]} n→15(iterates 1, 2, 3, 4, 5)- Previously returned
6(treated as List with just elements 1 and 5)
-
Interval support:
Intervalexpressions work with Element-based indexing, including support forOpenandClosedboundary markers:["Interval", 1, 5]→ iterates integers 1, 2, 3, 4, 5 (closed bounds)["Interval", ["Open", 0], 5]→ iterates 1, 2, 3, 4, 5 (excludes 0)["Interval", 1, ["Open", 6]]→ iterates 1, 2, 3, 4, 5 (excludes 6)
-
Infinite series with Element notation: Known infinite integer sets are converted to their equivalent Limits form and iterated (capped at 1,000,000):
NonNegativeIntegers(ℕ₀) → iterates from 0, like\sum_{n=0}^{\infty}PositiveIntegers(ℤ⁺) → iterates from 1, like\sum_{n=1}^{\infty}- Convergent series produce numeric approximations:
\sum_{n \in \Z^+} \frac{1}{n^2}→≈1.6449(close to π²/6)
-
Non-enumerable domains stay symbolic: When the domain cannot be enumerated (unknown symbol, non-iterable infinite set, or symbolic bounds), the expression stays symbolic instead of returning NaN:
\sum_{n \in S} nwith unknownS→ stays as["Sum", "n", ["Element", "n", "S"]]\sum_{n \in \Z} n→ stays symbolic (bidirectional, can't forward iterate)\sum_{x \in \R} f(x)→ stays symbolic (non-countable)\sum_{n \in [1,a]} nwith symbolic bound → stays symbolic- Previously these would all return
NaNwith no explanation
-
Multiple Element indexing sets: Comma-separated Element expressions now parse and evaluate correctly:
\sum_{n \in A, m \in B} (n+m)→["Sum", ..., ["Element", "n", "A"], ["Element", "m", "B"]]- Nested sums like
\sum_{i \in A}\sum_{j \in B} i \cdot jevaluate correctly - Mixed indexing sets (Element + Limits) work together
-
Condition/filter support in Element expressions: Conditions can be attached to Element expressions to filter values from the set:
\sum_{n \in S, n > 0} n→ sums only positive values from S\sum_{n \in S, n \ge 2} n→ sums values ≥ 2 from S\prod_{k \in S, k < 0} k→ multiplies only negative values from S- Supported operators:
>,>=,<,<=,!= - Conditions are attached as the 4th operand of Element:
["Element", "n", "S", ["Greater", "n", 0]]
-
Linear Algebra
-
Matrix Multiplication: Added
MatrixMultiplyfunction supporting:- Matrix × Matrix:
A (m×n) × B (n×p) → result (m×p) - Matrix × Vector:
A (m×n) × v (n) → result (m) - Vector × Matrix:
v (m) × B (m×n) → result (n) - Vector × Vector (dot product):
v1 (n) · v2 (n) → scalar - Proper dimension validation with
incompatible-dimensionserrors - LaTeX serialization using
\cdotnotation
- Matrix × Matrix:
-
Matrix Addition and Scalar Broadcasting:
Addnow supports element-wise operations on tensors (matrices and vectors):- Matrix + Matrix: Element-wise addition (shapes must match)
- Scalar + Matrix: Broadcasts scalar to all elements
- Vector + Vector: Element-wise addition
- Scalar + Vector: Broadcasts scalar to all elements
- Symbolic support:
[[a,b],[c,d]] + [[1,2],[3,4]]evaluates correctly - Proper dimension validation with
incompatible-dimensionserrors
-
Matrix Construction Functions: Added convenience functions for creating common matrices:
IdentityMatrix(n): Creates an n×n identity matrixZeroMatrix(m, n?): Creates an m×n matrix of zeros (square if n omitted)OnesMatrix(m, n?): Creates an m×n matrix of ones (square if n omitted)
-
Matrix and Vector Norms: Added
Normfunction for computing various norms:- Vector norms: L1 (sum of absolute values), L2 (Euclidean, default), L-infinity (max absolute value), and general Lp norms
- Matrix norms: Frobenius (default, sqrt of sum of squared elements), L1 (max column sum), L-infinity (max row sum)
- Scalar norms return the absolute value
-
Eigenvalues and Eigenvectors: Added functions for eigenvalue decomposition:
Eigenvalues(matrix): Returns list of eigenvalues (2×2: symbolic via characteristic polynomial; 3×3: Cardano's formula; larger: numeric QR)Eigenvectors(matrix): Returns list of corresponding eigenvectors using null space computation via Gaussian eliminationEigen(matrix): Returns tuple of (eigenvalues, eigenvectors)
-
Diagonal Function: Now fully implemented with bidirectional behavior:
- Vector → Matrix: Creates a diagonal matrix from a vector
(
Diagonal([1,2,3])→ 3×3 diagonal matrix) - Matrix → Vector: Extracts the diagonal as a vector
(
Diagonal([[1,2],[3,4]])→[1,4])
- Vector → Matrix: Creates a diagonal matrix from a vector
(
-
Higher-Rank Tensor Operations: Extended
Transpose,ConjugateTranspose, andTraceto work with rank > 2 tensors:- Transpose: Swaps last two axes by default (batch transpose), or specify
explicit axes with
['Transpose', T, axis1, axis2] - ConjugateTranspose: Same axis behavior as Transpose, plus element-wise complex conjugation
- Trace (batch trace): Returns a tensor of traces over the last two axes.
For a
[2,2,2]tensor, returns[trace of T[0], trace of T[1]]. Optional axis parameters:['Trace', T, axis1, axis2]
- Transpose: Swaps last two axes by default (batch transpose), or specify
explicit axes with
-
Reshape Cycling: Implements APL-style ravel cycling. When reshaping to a larger shape, elements cycle from the beginning:
Reshape([1,2,3], (2,2))→[[1,2],[3,1]] -
Scalar Handling: Most linear algebra functions now handle scalar inputs:
Flatten(42)→[42](single-element list)Transpose(42)→42(identity)Determinant(42)→42(1×1 matrix determinant)Trace(42)→42(1×1 matrix trace)Inverse(42)→1/42(scalar reciprocal)ConjugateTranspose(42)→42(conjugate of real is itself)Reshape(42, (2,2))→[[42,42],[42,42]](scalar replication)
-
Improved Error Messages: Operations requiring square matrices (
Determinant,Trace,Inverse) now returnexpected-square-matrixerror for vectors and tensors (rank > 2).
Performance
- Pattern Matching Optimization: Significantly improved performance of
commutative pattern matching by adding early rejection guards:
- Arity Guard: Patterns without sequence wildcards (
__/___) now immediately reject expressions with mismatched operand counts instead of attempting factorial permutations - Anchor Fingerprint: Patterns with literal or symbolic anchors verify anchor presence before attempting permutation matching, eliminating impossible matches in O(n) time
- Universal Anchoring: Extended the efficient anchor-based backtracking algorithm to all patterns with anchors, not just those with sequence wildcards
- Hash Bucketing: For patterns with many anchors (4+) against large expressions (6+ operands), uses hash-based indexing to reduce anchor lookup from O(n×m) to O(n+m) average case
- Example: Matching
a + b + c + 1againstx + y + znow rejects immediately (arity mismatch: 4 vs 3) instead of trying 24 permutations
- Arity Guard: Patterns without sequence wildcards (
Resolved Issues
Arithmetic
-
Indeterminate Form Handling: Fixed incorrect results for mathematical indeterminate forms:
0 * ∞now correctly returnsNaN(previously returned∞)∞ / ∞now correctly returnsNaN(previously returned1)∞^0now correctly returnsNaN(was already correct)- All combinations (
0 * (-∞),(-∞) / ∞, etc.) are handled correctly
-
(#176) Power Combination Simplification: Fixed simplification failing to combine powers with the same base when one factor has an implicit exponent or when there are 3+ operands. Previously, expressions like
2 * 2^x,e * e^x * e^{-x}, andx^2 * xwould not simplify. Now correctly simplifies to2^(x+1),e, andx^3respectively. The fix includes:- Extended power combination rules to support numeric literal bases
- Added functional rule to handle n-ary Multiply expressions (3+ operands)
- Adjusted simplification cost threshold from 1.2 to 1.3 to accept
mathematically valid simplifications where exponents become slightly more
complex (e.g.,
2 * 2^x → 2^(x+1))
-
Symbolic Factorial: Fixed
(n-1)!incorrectly evaluating toNaNinstead of staying symbolic. The factorialevaluatefunction was attempting numeric computation on symbolic arguments. Now correctly returnsundefined(keeping the expression symbolic) when the argument is not a number literal.
Linear Algebra
- Matrix Operations Type Validation: Fixed matrix operations (
Shape,Rank,Flatten,Transpose,Determinant,Inverse,Trace, etc.) returning incorrect results or failing with type errors. The root cause was a type mismatch: function signatures expectedmatrixtype (a 2D list with dimensions), butBoxedTensor.typereturnedlist<number>without dimensions. NowBoxedTensor,BoxedFunction, andBoxedSymbolcorrectly deriveshapeandrankfrom their type's dimensions. Additionally, linear algebra functions now properly evaluate their operands before checking if they are tensors.
Calculus
- Numerical Integration: Fixed
\int_0^1 \sin(x) dxreturningNaNwhen evaluated numerically with.N(). The integrand was already wrapped in aFunctionexpression by the canonical form, but the numerical evaluation code was wrapping it again, creating a nested function that returned a function instead of a number. Now correctly checks if the integrand is already aFunctionbefore wrapping.
LaTeX Parsing and Serialization
-
Subscript Function Calls: Fixed parsing of function calls with subscripted names like
f_\text{a}(5). Previously, this was incorrectly parsed as aTupleinstead of a function call becauseSubscriptexpressions weren't being canonicalized before the function call check. Now correctly recognizes thatf_a(5)is a function call when the subscript canonicalizes to a symbol. -
(#130) Prefix/Postfix Operator LaTeX Serialization: Fixed incorrect LaTeX output for prefix operators (like
Negate) and postfix operators (likeFactorial) when applied to expressions with lower precedence. Previously,Negate(Add(a, b))incorrectly serialized as-a+binstead of-(a+b), causing round-trip failures where parsing the output produced a mathematically different expression. Similarly,Factorial(Add(a, b))now correctly serializes as(a+b)!instead ofa+b!. The fix ensures operands are wrapped in parentheses when their precedence is lower than the operator's precedence. -
(#156) Logical Operator Precedence: Fixed parsing of logical operators
\vee(Or) and\wedge(And) with relational operators. Previously, expressions like3=4\vee 7=8were incorrectly parsed with the wrong precedence. Now correctly parses as["Or", ["Equal", 3, 4], ["Equal", 7, 8]]. Logical operators have lower precedence (230-235) than comparison operators (245) and set relations (240), so compound propositions parse correctly without requiring parentheses. -
(#156) Logical Connective Arrows: Added support for additional arrow notation in logical expressions:
\rightarrownow parses asImplies(previously parsed asTofor set/function mapping)\leftrightarrownow parses asEquivalent(previously produced an "unexpected-command" error)- Long arrow variants now supported:
\Longrightarrow,\longrightarrow→Implies;\Longleftrightarrow,\longleftrightarrow→Equivalent - The existing variants
\Rightarrow,\Leftrightarrow,\implies,\iffcontinue to work \toremains available for function/set mapping notation (e.g.,f: A \to B)
Simplification
-
Rules Cache Isolation: Fixed rules cache building failing with "Invalid rule" errors when user expressions had previously polluted the global scope. For example, parsing
x(y+z)would addxas a symbol with function type to the global scope. Later, when the simplification rules cache was built, rule parsing would fail because wildcards like_xin rules would be type-checked against the polluted scope wherexhad incompatible type. The fix ensures rule parsing uses a clean scope that inherits only from the system scope (containing built-in definitions), not from user-polluted scopes. -
Simplification Rules: Added and fixed several simplification rules:
x + xnow correctly simplifies to2x(term combination)e^x * e^{-x}now correctly simplifies to1(exponential inverse)sin(∞)andcos(∞)now correctly evaluate toNaNtanh(∞)now correctly evaluates to1,tanh(-∞)to-1log_b(x^n)now correctly simplifies ton * log_b(x)(log power rule)- Improved cost function to prefer
n * ln(x)form overln(x^n) - Trigonometric functions now reduce arguments by their period (e.g.,
cos(5π + k)simplifies usingcos(π + k) = -cos(k))
-
(#178) Non-Canonical Expression Simplification: Fixed
.simplify()not working on expressions parsed with{ canonical: false }. Previously,ce.parse('x+x', { canonical: false }).simplify()would returnx+xinstead of2x. The bug was in the simplification loop detection: when canonicalizing before simplification, the non-canonical form was recorded in the "seen" set, and sinceisSame()considers non-canonical and canonical forms equivalent, the canonical form was incorrectly detected as already processed. Now the simplification correctly starts fresh when canonicalizing, allowing full simplification to proceed.
0.32.0 2026-01-28
Resolved Issues
Calculus
-
(#230) Root Derivatives: Fixed the
Doperator not differentiating expressions containing theRootoperator (n-th roots). Previously,D(Root(x, 3), x)(derivative of ∛x) would return an unevaluated derivative expression instead of computing the result. Now correctly returns1/(3x^(2/3)), equivalent to the expected(1/3)·x^(-2/3). The fix adds a special case in thedifferentiatefunction to handleRoot(base, n)by applying the power rule with exponent1/n. -
Abs Derivative: Fixed
d/dx |x|returning an error when evaluated with a variable that has an assigned value. The derivative formula now usesSign(x)instead of a complexWhichexpression that couldn't be evaluated symbolically. -
Step Function Derivatives: Fixed
D(floor(x), x),D(ceil(x), x), andD(round(x), x)causing infinite recursion. These step functions now correctly return 0 (the derivative is 0 almost everywhere). Also fixed a bug where derivative formulas that evaluate to 0 weren't recognized due to a falsy check. -
Inverse Trig Integrals: Fixed incorrect integration formulas for
arcsin,arccos, andarctan. The previous formulas were completely wrong. Correct:∫ arcsin(x) dx = x·arcsin(x) + √(1-x²)∫ arccos(x) dx = x·arccos(x) - √(1-x²)∫ arctan(x) dx = x·arctan(x) - (1/2)·ln(1+x²)
-
Erfc Derivative: Fixed incorrect derivative formula for
erfc(x). Now correctly returns-2/√π · e^(-x²)(the negative of theerfderivative). -
LogGamma Derivative: Added derivative rule for
LogGamma(x)which returnsDigamma(x)(the digamma/psi function). -
Special Function Derivatives: Fixed derivative formulas for several special functions and removed incorrect ones:
- Fixed
d/dx erfi(x) = (2/√π)·e^(x²)(imaginary error function) - Fixed
d/dx S(x) = sin(πx²/2)(Fresnel sine integral) - Fixed
d/dx C(x) = cos(πx²/2)(Fresnel cosine integral) - Removed incorrect derivative formulas for Zeta, Digamma, PolyGamma, Beta,
LambertW, Bessel functions, and Airy functions (these now return symbolic
derivatives like
Digamma'(x)instead of wrong numeric results)
- Fixed
-
Symbolic Derivative Evaluation: Fixed derivatives of unknown functions returning
0instead of symbolic derivatives. For example,D(Digamma(x), x)now correctly returnsDigamma'(x)(asApply(Derivative(Digamma, 1), x)) instead of incorrectly returning0.
LaTeX Parsing and Serialization
-
(#256) Subscript Symbol Parsing: Fixed parsing of single-letter symbols with subscripts. Previously,
i_Awas incorrectly parsed as["Subscript", ["Complex", 0, 1], "A"]becauseiwas recognized as the imaginary unit before the subscript was processed. Nowi_Acorrectly parses as the symboli_A. This applies to all single-letter symbols including constants likeeandi. Complex subscripts containing operators (n+1), commas (n,m), or parentheses ((n+1)) still produceSubscriptexpressions. -
LaTeX Serialization: Fixed TypeScript error in power serialization where
denom(anumber | null) was incorrectly passed where anExpressionwas expected. Now correctly usesoperand(exp, 2)to get the expression form. -
(#168) Absolute Value: Fixed parsing of nested absolute value expressions that start with a double bar (e.g.
||3-5|-4|), which previously produced an invalid structure instead of evaluating correctly. -
(#244) Serialization: Fixed LaTeX and ASCIIMath serialization ambiguity for negative bases and negated powers. Powers now render
(-2)^2(instead of-2^2) when the base is negative, and negated powers now render as-(2^2)rather than-2^2. -
(#243) LaTeX Parsing: Fixed logic operator precedence causing expressions like
x = 1 \vee x = 2to be parsed incorrectly asx = (1 ∨ x) = 2instead of(x = 1) ∨ (x = 2). Comparison operators (=,<,>, etc.) now correctly bind tighter than logic operators (\land,\lor,\veebar, etc.). -
(#264) Serialization: Fixed LaTeX serialization of quantified expressions (
ForAll,Exists,ExistsUnique,NotForAll,NotExists). Previously, only the quantifier symbol was output (e.g.,\forall xinstead of\forall x, x>y). The body of the quantified expression is now correctly serialized. -
(#257) LaTeX Parsing: Fixed
\gcdcommand not parsing function arguments correctly. Previously\gcd\left(24,37\right)would parse as["Tuple", "GCD", ["Tuple", 24, 37]]instead of the expected["GCD", 24, 37]. The\operatorname{gcd}form was unaffected. Also added support for\lcmas a LaTeX command (in addition to the existing\operatorname{lcm}). -
(#223) Serialization: Fixed scientific/engineering LaTeX serialization dropping the leading coefficient for exact powers of ten. For example,
1000now serializes to1\cdot10^{3}(or1\times10^{3}depending onexponentProduct) instead of10^{3}. -
LaTeX Parsing: Fixed
\coshincorrectly mapping toCschinstead ofCosh. -
(#255) LaTeX Parsing: Fixed multi-letter subscripts like
A_{CD}causing "incompatible-type" errors in arithmetic operations. Multi-letter subscripts without parentheses are now interpreted as compound symbol names (e.g.,A_{CD}→A_CD,x_{ij}→x_ij,T_{max}→T_max). Use parentheses for expression subscripts:A_{(CD)}creates aSubscriptexpression whereCDrepresents implicit multiplication. TheDelimiterwrapper is now stripped from subscript expressions for cleaner output.
First-Order Logic
- (#263) Quantifier
Scope: Fixed quantifier scope in First-Order Logic expressions. Previously,
\forall x.P(x)\rightarrow Q(x)was parsed with the implication inside the quantifier scope:["ForAll", "x", ["To", P(x), Q(x)]]. Now it correctly follows standard FOL conventions where the quantifier binds only the immediately following formula:["To", ["ForAll", "x", P(x)], Q(x)]. This applies to all quantifiers (ForAll,Exists,ExistsUnique,NotForAll,NotExists) and all logical connectives (\rightarrow,\to,\implies,\land,\lor,\iff). Use explicit parentheses for wider scope:\forall x.(P(x)\rightarrow Q(x)). Also fixed quantifier type signatures to properly returnboolean, enabling correct type checking when quantified expressions are used as arguments to logical operators.
Simplification
- Sign Simplification: Fixed
Sign(x).simplify()returning1instead of-1whenxis negative. The simplification rule incorrectly returnedce.Onefor both positive and negative cases.
Type System
- Ceil Type Signature: Fixed
Ceilfunction signature from(real) -> integerto(number) -> integerto matchFloor. This resolves "incompatible-type" errors when computing derivatives of ceiling expressions or usingCeilin contexts expecting a general number type.
Polynomials
- Polynomial Degree Detection: Fixed
polynomialDegree()returning 0 for expressions likee^xore^(-x^2)when it should return -1 (not a polynomial). When the base of a power is constant but the exponent depends on the variable, this is not a polynomial. This bug caused infinite recursion in simplification when simplifying expressions containing exponentials, such as the derivative oferf(x)which is(2/√π)·e^(-x²).
Pattern Matching
- (#258) Pattern
Matching: Fixed
BoxedExpression.match()returningnullwhen matching patterns against canonicalized expressions. Several cases are now handled:Rationalpatterns now match expressions like['Rational', 'x', 2]which are canonicalized to['Multiply', ['Rational', 1, 2], 'x']Powerpatterns now match['Power', 'x', -1]which is canonicalized to['Divide', 1, 'x'], returning{_base: x, _exp: -1}Powerpatterns now match['Root', 'x', 3](cube root), returning{_base: x, _exp: ['Divide', 1, 3]}
Sum and Product
- (#252)
Sum/Product: Fixed
SumandProductreturningNaNwhen the body contains free variables (variables not bound by the index). For example,\sum_{n=1}^{10}(x)now correctly evaluates to10xinstead ofNaN, and\prod_{n=1}^{5}(x)evaluates tox^5. Mixed expressions like\sum_{n=1}^{10}(n \cdot x)now return55x. Also fixedtoString()forSumandProductexpressions with non-trivial bodies (e.g.,Multiply) which were incorrectly displayed asint().
Equation Solving
-
(#242) Solve: Fixed
solve()returning an empty array for equations with variables in fractions. For example,F = 3g/hsolved forgnow correctly returnsFh/3instead of an empty array. The solver now clears denominators before applying solve rules, enabling it to handle expressions likea + bx/c = 0. Also added support for solving equations where the variable is in the denominator (e.g.,a/x = bnow returnsx = a/b). -
(#220) Solve: Fixed
solve()returning an empty array for equations involving square roots of the unknown, e.g.2x = \sqrt{5x}. The solver now handles equations of the formax + b√x + c = 0using quadratic substitution. Also added support for solving logarithmic equations likea·ln(x) + b = 0which returnsx = e^(-b/a).
Improvements
First-Order Logic
- (#263) First-Order
Logic: Added several improvements for working with First-Order Logic
expressions:
- Configurable quantifier scope: New
quantifierScopeparsing option controls how quantifier scope is determined. Use"tight"(default) for standard FOL conventions where quantifiers bind only the immediately following formula, or"loose"for scope extending to the end of the expression.ce.parse('\\forall x. P(x)', { quantifierScope: 'tight' }) // defaultce.parse('\\forall x. P(x)', { quantifierScope: 'loose' }) - Automatic predicate inference: Single uppercase letters followed by
parentheses (e.g.,
P(x),Q(a,b)) are now automatically recognized as predicate/function applications without requiring explicit declaration. This enables natural FOL syntax like\forall x. P(x) \rightarrow Q(x)to work out of the box. - Quantifier evaluation over finite domains: Quantifiers (
ForAll,Exists,ExistsUnique,NotForAll,NotExists) now evaluate to boolean values when the bound variable is constrained to a finite set. For example:Supportsce.box(['ForAll', ['Element', 'x', ['Set', 1, 2, 3]], ['Greater', 'x', 0]]).evaluate()// Returns True (all values in {1,2,3} are > 0)ce.box(['Exists', ['Element', 'x', ['Set', 1, 2, 3]], ['Greater', 'x', 2]]).evaluate()// Returns True (3 > 2)ce.box(['ExistsUnique', ['Element', 'x', ['Set', 1, 2, 3]], ['Equal', 'x', 2]]).evaluate()// Returns True (only one element equals 2)Set,List,Range, and integerIntervaldomains up to 1000 elements. Nested quantifiers are evaluated over the Cartesian product of their domains. - Symbolic simplification for quantifiers: Quantifiers now simplify
automatically in special cases:
∀x. True→True,∀x. False→False∃x. True→True,∃x. False→False∀x. P→P(when P doesn't contain x)∃x. P→P(when P doesn't contain x)
- CNF/DNF conversion: New
ToCNFandToDNFfunctions convert boolean expressions to Conjunctive Normal Form and Disjunctive Normal Form respectively:Handlesce.box(['ToCNF', ['Or', ['And', 'A', 'B'], 'C']]).evaluate()// Returns (A ∨ C) ∧ (B ∨ C)ce.box(['ToDNF', ['And', ['Or', 'A', 'B'], 'C']]).evaluate()// Returns (A ∧ C) ∨ (B ∧ C)And,Or,Not,Implies,Equivalent,Xor,Nand, andNoroperators using De Morgan's laws and distribution. - Boolean operator evaluation: Added evaluation support for
Xor,Nand, andNoroperators withTrue/Falsearguments:ce.box(['Xor', 'True', 'False']).evaluate() // Returns Truece.box(['Nand', 'True', 'True']).evaluate() // Returns Falsece.box(['Nor', 'False', 'False']).evaluate() // Returns True - N-ary boolean operators:
Xor,Nand, andNornow support any number of arguments:Xor(a, b, c, ...)returns true when an odd number of arguments are trueNand(a, b, c, ...)returns the negation ofAnd(a, b, c, ...)Nor(a, b, c, ...)returns the negation ofOr(a, b, c, ...)
- Satisfiability checking: New
IsSatisfiablefunction checks if a boolean expression can be made true with some assignment of variables:ce.box(['IsSatisfiable', ['And', 'A', ['Not', 'A']]]).evaluate() // Falsece.box(['IsSatisfiable', ['Or', 'A', 'B']]).evaluate() // True - Tautology checking: New
IsTautologyfunction checks if a boolean expression is true for all possible variable assignments:ce.box(['IsTautology', ['Or', 'A', ['Not', 'A']]]).evaluate() // Truece.box(['IsTautology', ['And', 'A', 'B']]).evaluate() // False - Truth table generation: New
TruthTablefunction generates a complete truth table for a boolean expression:ce.box(['TruthTable', ['And', 'A', 'B']]).evaluate()// Returns [["A","B","Result"],["False","False","False"],...] - Explicit
Predicatefunction: Added a newPredicatefunction to explicitly represent predicate applications in First-Order Logic. Inside quantifier scopes (\forall,\exists, etc.), single uppercase letters followed by parentheses are now parsed as["Predicate", "P", "x"]instead of["P", "x"]. This distinguishes predicates from regular function applications and avoids naming conflicts with library functions.Outside quantifier scopes,ce.parse('\\forall x. P(x)').json// Returns ["ForAll", "x", ["Predicate", "P", "x"]]P(x)is still parsed as["P", "x"]to maintain backward compatibility with function definitions likeQ(x) := .... D(f, x)no longer maps to derivative: The LaTeX notationD(f, x)is not standard mathematical notation for derivatives and previously caused confusion with theDderivative function in MathJSON. NowD(f, x)in LaTeX parses as["Predicate", "D", "f", "x"]instead of the derivative. Use Leibniz notation (\frac{d}{dx}f) for derivatives in LaTeX, or construct the derivative directly in MathJSON:["D", expr, "x"].N(x)no longer maps to numeric evaluation: Similarly,N(x)in LaTeX is CAS-specific notation, not standard math notation. NowN(x)parses as["Predicate", "N", "x"]instead of the numeric evaluation function. This allowsNto be used as a variable (e.g., "for all N in Naturals"). Use the.N()method for numeric evaluation, or construct it directly in MathJSON:["N", expr].
- Configurable quantifier scope: New
Polynomials
- Polynomial Simplification: The
simplify()function now automatically cancels common polynomial factors in univariate rational expressions. For example,(x² - 1)/(x - 1)simplifies tox + 1,(x³ - x)/(x² - 1)simplifies tox, and(x + 1)/(x² + 3x + 2)simplifies to1/(x + 2). Previously, this required explicitly calling theCancelfunction with a variable argument.
Sum and Product
- Sum/Product Simplification: Added simplification rules for
SumandProductexpressions with symbolic bounds:- Constant body:
\sum_{n=1}^{b}(x)simplifies tob * x - Triangular numbers (general bounds):
\sum_{n=a}^{b}(n)simplifies to(b(b+1) - a(a-1))/2 - Sum of squares:
\sum_{n=1}^{b}(n^2)simplifies tob(b+1)(2b+1)/6 - Sum of cubes:
\sum_{n=1}^{b}(n^3)simplifies to[b(b+1)/2]^2 - Geometric series:
\sum_{n=0}^{b}(r^n)simplifies to(1-r^(b+1))/(1-r) - Alternating unit series:
\sum_{n=0}^{b}((-1)^n)simplifies to(1+(-1)^b)/2 - Alternating linear series:
\sum_{n=0}^{b}((-1)^n * n)simplifies to(-1)^b * floor((b+1)/2) - Arithmetic progression:
\sum_{n=0}^{b}(a + d*n)simplifies to(b+1)(a + db/2) - Sum of binomial coefficients:
\sum_{k=0}^{n}C(n,k)simplifies to2^n - Alternating binomial sum:
\sum_{k=0}^{n}((-1)^k * C(n,k))simplifies to0 - Weighted binomial sum:
\sum_{k=0}^{n}(k * C(n,k))simplifies ton * 2^(n-1) - Partial fractions (telescoping):
\sum_{k=1}^{n}(1/(k(k+1)))simplifies ton/(n+1) - Partial fractions (telescoping):
\sum_{k=2}^{n}(1/(k(k-1)))simplifies to(n-1)/n - Weighted squared binomial sum:
\sum_{k=0}^{n}(k^2 * C(n,k))simplifies ton(n+1) * 2^(n-2) - Weighted cubed binomial sum:
\sum_{k=0}^{n}(k^3 * C(n,k))simplifies ton²(n+3) * 2^(n-3) - Alternating weighted binomial sum:
\sum_{k=0}^{n}((-1)^k * k * C(n,k))simplifies to0(n ≥ 2) - Sum of binomial squares:
\sum_{k=0}^{n}(C(n,k)^2)simplifies toC(2n, n) - Sum of consecutive products:
\sum_{k=1}^{n}(k(k+1))simplifies ton(n+1)(n+2)/3 - Arithmetic progression (general bounds):
\sum_{n=m}^{b}(a + d*n)simplifies to(b-m+1)(a + d(m+b)/2) - Product of constant:
\prod_{n=1}^{b}(x)simplifies tox^b - Factorial:
\prod_{n=1}^{b}(n)simplifies tob! - Shifted factorial:
\prod_{n=1}^{b}(n+c)simplifies to(b+c)!/c! - Odd double factorial:
\prod_{n=1}^{b}(2n-1)simplifies to(2b-1)!! - Even double factorial:
\prod_{n=1}^{b}(2n)simplifies to2^b * b! - Rising factorial (Pochhammer):
\prod_{k=0}^{n-1}(x+k)simplifies to(x)_n - Falling factorial:
\prod_{k=0}^{n-1}(x-k)simplifies tox!/(x-n)! - Telescoping product:
\prod_{k=1}^{n}((k+1)/k)simplifies ton+1 - Wallis-like product:
\prod_{k=2}^{n}(1 - 1/k^2)simplifies to(n+1)/(2n) - Factor out constants:
\sum_{n=1}^{b}(c \cdot f(n))simplifies toc \cdot \sum_{n=1}^{b}(f(n)), and similarly for products where the constant is raised to the power of the iteration count - Nested sums/products: inner sums/products are simplified first, enabling cascading simplification
- Edge cases: empty ranges (upper < lower) return identity elements (0 for Sum, 1 for Product), and single-iteration ranges substitute the bound value
- Constant body:
0.31.0 2026-01-27
Breaking Changes
- The
[Length]function has been renamed to[Count]. - The
xsizeproperty of collections has been renamed tocount. - The
xcontains()method of collections has been renamed tocontains(). - Handling of dictionaries (
["Dictionary"]expressions and\{dict:...\}shorthand) has been improved. - Inverse hyperbolic functions have been renamed to follow the ISO 80000-2
standard:
Arcsinh→Arsinh,Arccosh→Arcosh,Arctanh→Artanh,Arccoth→Arcoth,Arcsech→Arsech,Arccsch→Arcsch. The "ar" prefix (for "area") is mathematically correct since these functions relate to areas on a hyperbola, not arc lengths. Both LaTeX spellings (\arsinhand\arcsinh) are accepted as input (Postel's law).
Resolved Issues
LaTeX Parsing
-
Metadata Preservation: Fixed
verbatimLatexnot being preserved when parsing withpreserveLatex: true. The original LaTeX source is now correctly stored on parsed expressions (when using non-canonical mode). Also fixed metadata (latex,wikidata) being lost when boxing MathJSON objects that contain these attributes. -
String Parsing: Fixed parsing of
\text{...}withpreserveLatex: truewhich was incorrectly returning an "invalid-symbol" error instead of a string expression.
Calculus
-
Derivatives:
d/dx e^xnow correctly simplifies toe^xinstead ofln(e) * e^x. ThehasSymbolicTranscendental()function now recognizes that transcendentals which simplify to exact rational values (likeln(e) = 1) should not be preserved symbolically. -
Derivatives:
d/dx log(x)now returns1 / (x * ln(10))symbolically instead of evaluating to0.434... / x. Fixed by using substitution instead of function application when applying derivative formulas, which preserves symbolic transcendental constants.
Arithmetic
-
Rationals: Fixed
reducedRational()to properly normalize negative denominators before the early return check. Previously1/-2would not canonicalize to-1/2. -
Arithmetic: Fixed
.mul()to preserve logarithms symbolically. Previously multiplying expressions containingLnorLogwould evaluate the logarithm to its numeric value.
Serialization
-
Serialization: Fixed case inconsistency in
toString()output for trigonometric functions. Some functions likeCotwere being serialized with capital letters while others likecscwere lowercase. All trig functions now consistently serialize in lowercase (e.g.,cot(x)instead ofCot(x)). -
Serialization: Improved display of inverse trig derivatives and similar expressions:
- Negative exponents like
x^(-1/2)now display as1/sqrt(x)in both LaTeX and ASCII-math output - When a sum starts with a negative term and contains a positive constant, the
constant is moved to the front (e.g.,
-x^2 + 1displays as1 - x^2) while preserving polynomial ordering (e.g.,x^2 - x + 3stays unchanged) d/dx arcsin(x)now displays as1/sqrt(1-x^2)instead of(-x^2+1)^(-1/2)
- Negative exponents like
-
Scientific Notation: Fixed normalization of scientific notation for fractional values (e.g., numbers less than 1).
Sum and Product
-
Compilation: Fixed compilation of
SumandProductexpressions. -
Sum/Product: Fixed
sumandprodlibrary functions to correctly handle substitution of index variables.
New Features and Improvements
Serialization
- Number Serialization: Added
adaptiveScientificnotation mode. When serializing numbers to LaTeX, this mode uses scientific notation but avoids exponents within a configurable range (controlled byavoidExponentsInRange). This provides a balance between readability and precision for numbers across different orders of magnitude.
Type System
- Refactored the type parser to use a modular architecture. This allows for better extensibility and maintainability of the type system.
Pattern Matching
- Pattern Matching: The
validatePattern()function is now exported from the public API. Use it to check patterns for invalid combinations like consecutive sequence wildcards before using them.
Polynomials
- Polynomial Arithmetic: Added new library functions for polynomial
operations:
PolynomialDegree(expr, var)- Get the degree of a polynomialCoefficientList(expr, var)- Get the list of coefficientsPolynomialQuotient(dividend, divisor, var)- Polynomial division quotientPolynomialRemainder(dividend, divisor, var)- Polynomial division remainderPolynomialGCD(a, b, var)- Greatest common divisor of polynomialsCancel(expr, var)- Cancel common factors in rational expressions
Calculus
- Integration: Significantly expanded symbolic integration capabilities:
- Polynomial division: Integrals like
∫ x²/(x²+1) dxnow correctly divide first, yieldingx - arctan(x) - Repeated linear roots:
∫ 1/(x-1)² dx = -1/(x-1)and higher powers - Derivative pattern recognition:
∫ f'(x)/f(x) dx = ln|f(x)|is now recognized automatically - Completing the square: Irreducible quadratics like
∫ 1/(x²+2x+2) dxnow yieldarctan(x+1) - Reduction formulas:
∫ 1/(x²+1)² dxnow works using reduction formulas - Mixed partial fractions:
∫ 1/((x-1)(x²+1)) dxnow decomposes correctly - Factor cancellation:
∫ (x+1)/(x²+3x+2) dxsimplifies before integrating - Inverse hyperbolic: Added
∫ 1/√(x²+1) dx = arcsinh(x)and∫ 1/√(x²-1) dx = arccosh(x) - Arcsec pattern: Added
∫ 1/(x·√(x²-1)) dx = arcsec(x) - Trigonometric substitution: Added support for
∫√(a²-x²) dx,∫√(x²+a²) dx, and∫√(x²-a²) dxusing trig/hyperbolic substitution
- Polynomial division: Integrals like
0.30.2 2025-07-15
Breaking Changes
-
The
expr.valueproperty reflects the value of the expression if it is a number literal or a symbol with a literal value. If you previously used theexpr.valueproperty to get the value of an expression, you should now use theexpr.N().valueOf()method instead. ThevalueOf()method is suitable for interoperability with JavaScript, but it may result in a loss of precision for numbers with more than 15 digits. -
BoxedExpr.sgnnow returns undefined for complex numbers, or symbols with a complex-number value. -
The
ce.assign()method previously acceptedce.assign("f(x, y)", ce.parse("x+y")). This is now deprecated. Usece.assign("f", ce.parse("(x, y) \\mapsto x+y")instead. -
It was previously possible to invoke
expr.evaluate()orexpr.N()on a non-canonical expression. This will now return the expression itself.To evaluate a non-canonical expression, use
expr.canonical.evaluate()orexpr.canonical.N().That's also the case for the methods
numeratorDenominator(),numerator(), anddenominator().In addition, invoking the methods
inv(),abs(),add(),mul(),div(),pow(),root(),ln()will throw an error if the expression is not canonical.
New Features and Improvements
-
Collections now support lazy materialization. This means that the elements of some collection are not computed until they are needed. This can significantly improve performance when working with large collections, and allow working with infinite collections. For example:
ce.box(['Map', 'Integers', 'Square']).evaluate().print();// -> [0, 1, 4, 9, 16, ...]Materialization can be controlled with the
materializationoption of theevaluate()method. Lazy collections are materialized by default when converted to a string or LaTeX, or when assigned to a variable. -
The bindings of symbols and function expressions is now consistently done during canonicalization.
-
It was previously not possible to change the type of an identifier from a function to a value or vice versa. This is now possible.
-
Antiderivatives are now computed symbolically:
ce.parse(`\\int_0^1 \\sin(\\pi x) dx`).evaluate().print();
// -> 2 / pi
ce.parse(`\\int \\sin(\\pi x) dx`).evaluate().print();
// -> -cos(pi * x) / pi
Requesting a numeric approximation of the integral will use a Monte Carlo method:
ce.parse(`\\int_0^1 \\sin(\\pi x) dx`).N().print();
// -> 0.6366
-
Numeric approximations of integrals is several order of magnitude faster.
-
Added Number Theory functions:
Totient,Sigma0,Sigma1,SigmaMinus1,IsPerfect,Eulerian,Stirling,NPartition,IsTriangular,IsSquare,IsOctahedral,IsCenteredSquare,IsHappy,IsAbundant. -
Added Combinatorics functions:
Choose,Fibonacci,Binomial,CartesianProduct,PowerSet,Permutations,Combinations,Multinomial,SubfactorialandBellNumber. -
The
symboltype can be refined to match a specific symbol. For examplesymbol<True>. The typeexpressioncan be refined to match expressions with a specific operator, for exampleexpression<Add>is a type that matches expressions with theAddoperator. The numeric types can be refined with a lower and upper bound. For exampleinteger<0..10>is a type that matches integers between 0 and 10. The typereal<1..>matches real numbers greater than 1 andrational<..0>matches non-positive rational numbers. -
Numeric types can now be constrained with a lower and upper bound. For example,
real<0..10>is a type that matches real numbers between 0 and 10. The typeinteger<1..>matches integers greater than or equal to 1. -
Collections that can be indexed (
list,tuple) are now a subtype ofindexed_collection. -
The
maptype has been replaced withdictionaryfor collections of arbitrary key-value pairs andrecordfor collections of structured key-value pairs. -
Support for structural typing has been added. To define a structural type, use
ce.declareType()with thealiasflag, for example:ce.declareType("point", "tuple<x: integer, y: integer>",{ alias: true }); -
Recursive types are now supported by using the
typekeyword to forward reference types. For example, to define a type for a binary tree:ce.declareType("binary_tree","tuple<value: integer, left: type binary_tree?, right: type binary_tree?>",); -
The syntax for variadic arguments has changeed. To indicate a variadic argument, use a
+or*after the type, for example:ce.declare('f', '(number+) -> number');Use
+for a non-empty list of arguments and*for a possibly empty list. -
Added a rule to solve the equation
a^x + b = 0 -
The LaTeX parser now supports the
\placeholder[]{},\phantom{},\hphantom{},\vphantom{},\mathstrut,\strutand\smash{}commands. -
The range of recognized sign values, i.e. as returned from
BoxedExpression.sgnhas been simplified (e.g. '...-infinity' and 'nan' have been removed) -
The Power canonical-form is less aggressive - only carrying-out ops. as listed in doc. - is much more careful in its consideration of operand types & values... (for example, typically, exponents are required to be numbers: e.g.
x^1will simplify, butx^y(wherey===0), orx^{1+0}, will not)
Issues Resolved
-
Ensure expression LaTeX serialization is based on MathJSON generated with matching "pretty" formatting (or not), therefore resulting in LaTeX with less prettification, where
prettify === false(#daef87f) -
Symbols declare with a
constantflag are now not marked as "inferred" -
Some
BoxedSymbolsproperties now more consistently returnundefined, instead of aboolean(i.e. because the symbol is non-bound) -
Some
expr.root()computations -
Canonical-forms
- Fixes the
Numberform - Forms (at least,
Number,Power) do not mistakenly fully canonicalize operands - This (partial canonicalization) now substitutes symbols (constants) with a
holdUntilvalue of"never"during/prior-to canonicalization (i.e. just like for full canonicalization)
- Fixes the
0.29.1 2025-03-31
- #231 During evaluation, some numbers, for example
10e-15were incorrectly rounded to 0.
0.28.0 2025-02-06
Issues Resolved
-
#211 More consistent canonicalization and serialization of exact numeric values of the form
(a√b)/c. -
#219 The
invisibleOperatorcanonicalization previously also canonicalized some multiplication. -
#218 Improved performance of parsing invisible operators, including fixing some cases where the parsing was incorrect.
-
#216 Correctly parse subscripts with a single character, for example
x_1. -
#216 Parse some non-standard integral signs, for example
\int x \cdot \differentialD x(both the\cdotand the\differentialDare non-standard). -
#210 Numeric approximation of odd nth roots of negative numbers evaluate correctly.
-
#153 Correctly parse integrals with
\limits, e.g.\int\limits_0^1 x^2 \mathrm{d} x. -
Correctly serialize to ASCIIMath
Delimiterexpressions. -
When inferring the type of numeric values do not constrain them to be
real. As a result:ce.assign('a', ce.parse('i'));ce.parse('a+1').evaluate().print();now returns
1 + iinstead of throwing a type error. -
Correctly parse and evaluate unary and binary
\pmand\mpoperators.
New Features and Improvements
-
expr.isEqual()will now return true/false if the expressions include the same unknowns and are structurally equal after expansion and simplifications. For example:console.info(ce.parse('(x+1)^2').isEqual(ce.parse('x^2+2x+1')));// -> true
Asynchronous Operations
Some computations can be time-consuming, for example, computing a very large factorial. To prevent the browser from freezing, the Compute Engine can now perform some operations asynchronously.
To perform an asynchronous operation, use the expr.evaluateAsync method. For
example:
try {
const fact = ce.parse('(70!)!');
const factResult = await fact.evaluateAsync();
factResult.print();
} catch (e) {
console.error(e);
}
It is also possible to interrupt an operation, for example by providing a
pause/cancel button that the user can press. To do so, use an AbortController
object and a signal. For example:
const abort = new AbortController();
const signal = abort.signal;
setTimeout(() => abort.abort(), 500);
try {
const fact = ce.parse('(70!)!');
const factResult = await fact.evaluateAsync({ signal });
factResult.print();
} catch (e) {
console.error(e);
}
In the example above, we trigger an abort after 500ms.
It is also possible to control how long an operation can run by setting the
ce.timeLimit property with a value in milliseconds. For example:
ce.timeLimit = 1000;
try {
const fact = ce.parse('(70!)!');
fact.evaluate().print();
} catch (e) {
console.error(e);
}
The time limit applies to either the synchronous or asynchronous evaluation.
The default time limit is 2,000ms (2 seconds).
When an operation is canceled either because of a timeout or an abort, a
CancellationError is thrown.
0.27.0 2024-12-02
-
#217 Correctly parse LaTeX expressions that include a command followed by a
*such as\\pi*2. -
#217 Correctly calculate the angle of trigonometric expressions with an expression containing a reference to
Pi, for example\\sin(\\pi^2). -
The
Factorialfunction will now time out if the argument is too large. The timeout is signaled by throwing aCancellationError. -
When specifying
exp.toMathJSON({shorthands:[]}), i.e., not to use shorthands in the MathJSON, actually avoid using shorthands. -
Correctly use custom multiply, plus, etc. for LaTeX serialization.
-
When comparing two numeric values, the tolerance is now used to determine if the values are equal. The tolerance can be set with the
ce.toleranceproperty. -
When comparing two expressions with
isEqual()the values are compared structurally when necessary, or with a stochastic test when the expressions are too complex to compare structurally. -
Correctly serialize nested superscripts, e.g.
x^{y^z}. -
The result of evaluating a
Holdexpression is now the expression itself. -
To prevent evaluation of an expression temporarily, use the
Unevaluatedfunction. The result of evaluating anUnevaluatedexpression is its argument. -
The type of a
Holdexpression was incorrectly returned asstring. It now returns the type of its argument. -
The statistics function (
Mean,Median,Variance,StandardDeviation,Kurtosis,Skewness,Mode,QuartilesandInterQuartileRange) now accept as argument either a collection or a sequence of values.ce.parse("\\mathrm{Mean}([7, 2, 11])").evaluate().print();// -> 20/3ce.parse("\\mathrm{Mean}(7, 2, 11)").evaluate().print();// -> 20/3 -
The
VarianceandStandardDeviationfunctions now have variants for population statistics,PopulationVarianceandPopulationStandardDeviation. The default is to use sample statistics.ce.parse("\\mathrm{PopulationVariance}([7, 2, 11])").evaluate().print();// -> 13.555ce.parse("\\mathrm{Variance}([7, 2, 11])").evaluate().print();// -> 20.333 -
The statistics function can now be compiled to JavaScript:
const code = ce.parse("\\mathrm{Mean}(7, 2, 11)").compile();console.log(code());// -> 13.555 -
The statistics function calculate either using machine numbers or bignums depending on the precision. The precision can be set with the
precisionproperty of the Compute Engine. -
The argument of compiled function is now optional.
-
Compiled expressions can now reference external JavaScript functions. For example:
ce.defineFunction('Foo', {signature: 'number -> number',evaluate: ([x]) => ce.box(['Add', x, 1]),});const fn = ce.box(['Foo', 3]).compile({functions: { Foo: (x) => x + 1 },})!;console.info(fn());// -> 4ce.defineFunction('Foo', {signature: 'number -> number',evaluate: ([x]) => ce.box(['Add', x, 1]),});function foo(x) {return x + 1;}const fn = ce.box(['Foo', 3]).compile({functions: { Foo: foo },})!;console.info(fn());// -> 4Additionally, functions can be implicitly imported (in case they are needed by other JavaScript functions):
ce.defineFunction('Foo', {signature: 'number -> number',evaluate: ([x]) => ce.box(['Add', x, 1]),});function bar(x, y) {return x + y;}function foo(x) {return bar(x, 1);}const fn = ce.box(['Foo', 3]).compile({functions: { Foo: 'foo' },imports: [foo, bar],})!;console.info(fn());// -> 4 -
Compiled expression can now include an arbitrary preamble (JavaScript source) that is executed before the compiled function is executed. This can be used to define additional functions or constants.
ce.defineFunction('Foo', {signature: 'number -> number',evaluate: ([x]) => ce.box(['Add', x, 1]),});const code = ce.box(['Foo', 3]).compile({preamble: "function Foo(x) { return x + 1};",}); -
The
holdfunction definition flag has been renamed tolazy
0.26.4 2024-10-17
- #201 Identifiers of the form
A_\text{1}were not parsed correctly. - #202 Fixed serialization of integrals and bigops.
0.26.3 2024-10-17
- Correctly account for
fractionalDigitswhen formatting numbers. - #191 Correctly handle
\\lnot\\foralland\\lnot\\exists. - #206 The square root of 1000000 was canonicalized to 0.
- #207 When a square root with a literal base greater than 1e6 was preceded by a non-integer literal number, the literal number was ignored during canonicalization.
- #208 #204 Correctly evaluate numeric approximation of roots, e.g.
\\sqrt[3]{125}. - #205
1/ln(0)was incorrectly evaluated to1. It now returns0.
0.26.1 2024-10-04
Issues Resolved
- #194 Correctly handle the precedence of unary negate, for example in
-5^{\frac12}or-5!. - When using a function definition with
ce.declare(), do not generate a runtime error.
New Features and Improvements
- Added
.expand()method to boxed expression. This method expands the expression, for examplece.parse("(x+1)^2").expand()will returnx^2 + 2x + 1.
0.26.0 2024-10-01
Breaking Changes
-
The property
expr.headhas been deprecated. Useexpr.operatorinstead.expr.headis still supported in this version but will be removed in a future update. -
The MathJSON utility functions
head()andop()have been renamed tooperator()andoperand()respectively. -
The methods for algebraic operations (
add,div,mul, etc...) have been moved from the Compute Engine to the Boxed Expression class. Instead of callingce.add(a, b), calla.add(b).Those methods also behave more consistently: they apply some additional simplication rules over canonicalization. For example, while
ce.parse('1 + 2')return["Add", 1, 2],ce.box(1).add(2)will return3. -
The
ce.numericModeoption has been removed. Instead, set thece.precisionproperty to the desired precision. Set the precision to"machine"for machine precision calculations (about 15 digits). Set it to"auto"for a default of 21 digits. Set it to a number for a greater fixed precision. -
The MathJSON Dictionary element has been deprecated. Use a
Dictionaryexpression instead. -
The
ExtendedRealNumbers,ExtendedComplexNumbersdomains have been deprecated. Use theRealNumbersandComplexNumbersdomains instead. -
The "Domain" expression has been deprecated. Use types instead (see below).
-
Some
BoxedExpressionproperties have been removed:- Instead of
expr.isZero, useexpr.is(0). - Instead of
expr.isNotZero, use!expr.is(0). - Instead of
expr.isOne, useexpr.is(1). - Instead of
expr.isNegativeOne, useexpr.is(-1).
- Instead of
-
The signature of
ce.declare()has changed. In particular, theNhandler has been replaced withevaluate.
// Before
ce.declare('Mean', {
N: (ce: IComputeEngine): BoxedExpression => {
return ce.number(1);
},
});
// Now
ce.declare('Mean', { evaluate: (ops, { engine }) => ce.number(1) });
New Features and Improvements
-
New Simplification Engine
The way expressions are simplified has been completely rewritten. The new engine is more powerful and more flexible.
The core API remains the same: to simplify an expression, use
expr.simplify().To use a custom set of rules, pass the rules as an argument to
simplify():expr.simplify({rules: ["|x:<0| -> -x","|x:>=0| -> x",]});There are a few changes to the way rules are represented. The
priorityproperty has been removed. Instead, rules are applied in the order in which they are defined.A rule can also now be a function that takes an expression and returns a new expression. For example:
expr.simplify({rules: [(expr) => {if (expr.operator !== 'Abs') return undefined;const x = expr.args[0];return x.isNegative ? x.negate() : expr;}]});This can be used to perform more complex transformations at the cost of more verbose JavaScript code.
The algorithm for simplification has been simplified. It attempts to apply each rule in the rule set in turn, then restarts the process until no more rules can be applied or the result of applying a rule returns a previously seen expression.
Function definitions previously included a
simplifyhandler that could be used to perform simplifications specific to this function. This has been removed. Instead, use a rule that matches the function and returns the simplified expression. -
Types
Previously, an expression was associated with a domain such as
RealNumbersorComplexNumbers. This has been replaced with a more flexible system of types.A type is a set of values that an expression can take. For example, the type
realis the set of real numbers, the typeintegeris the set of integers,The type of an expression can be set with the
typeproperty. For example:const expr = ce.parse('\\sqrt{-1}');console.info(expr.type); // -> imaginaryThe type of a symbol can be set when declaring the symbol. For example:
ce.declare('x', 'imaginary');In addition to primitive types, the type system supports more complex types such union types, intersection types, and function types.
For example, the type
real|imaginaryis the union of the real and imaginary numbers.When declaring a function, the type of the arguments and the return value can be specified. For example, to declare a function
fthat takes two integers and returns a real number:ce.declare('f', '(integer, integer) -> real');The sets of numbers are defined as follows:
number- any number, real or complex, including NaN and infinitynon_finite_number- NaN or infinityrealfinite_real- finite real numbers (exclude NaN and infinity)imaginary- imaginary numbers (complex numbers with a real part of 0)finite_imaginarycomplex- complex numbers with a real and imaginary part not equal to 0finite_complexrationalfinite_rationalintegerfinite_integer
To check the type of an expression, use the
isSubtypeOf()method. For example:let expr = ce.parse('5');console.info(expr.type.isSubtypeOf('rational')); // -> trueconsole.info(expr.type.isSubtypeOf('integer')); // -> trueexpr = ce.parse('\\frac{1}{2}');console.info(expr.type.isSubtypeOf('rational')); // -> trueconsole.info(expr.type.isSubtypeOf('integer')); // -> falseAs a shortcut, the properties
isReal,isRational,isIntegerare available on boxed expressions. For example:let expr = ce.parse('5');console.info(expr.isInteger); // -> trueconsole.info(expr.isRational); // -> trueThey are equivalent to
expr.type.isSubtypeOf('integer')andexpr.type.isSubtypeOf('rational')respectively.To check if a number has a non-zero imaginary part, use:
let expr = ce.parse('5i');console.info(expr.isNumber && expr.isReal === false); // -> true -
Collections
Support for collections has been improved. Collections include
List,Set,Tuple,Range,Interval,LinspaceandDictionary.It is now possible to check if an element is contained in a collection using an
Elementexpression. For example:let expr = ce.parse('[1, 2, 3]');ce.box(['Element', 3, expr]).print(); // -> Truece.box(['Element', 5, expr]).print(); // -> FalseTo check if a collection is a subset of another collection, use the
Subsetexpression. For example:ce.box(['Subset', 'Integers', 'RealNumbers']).print(); // -> TrueCollections can also be compared for equality. For example:
let set1 = ce.parse('\\lbrace 1, 2, 3 \\rbrace');let set2 = ce.parse('\\lbrace 3, 2, 1 \\rbrace');console.info(set1.isEqual(set2)); // -> trueThere are also additional convenience methods on boxed expressions:
expr.isCollectionexpr.contains(element)expr.sizeexpr.isSubsetOf(other)expr.indexOf(element)expr.at(index)expr.each()expr.get(key)
-
Exact calculations
The Compute Engine has a new backed for numerical calculations. The new backed can handle arbitrary precision calculations, including real and complex numbers. It can also handle exact calculations, preserving calculations with rationals and radicals (square root of integers). For example
1/2 + 1/3is evaluated to5/6instead of0.8(3).To get an approximate result, use the
N()method, for examplece.parse("\\frac12 + \\frac13").N().Previously the result of calculations was not always an exact number but returned a numerical approximation instead.
This has now been improved by introducing a
NumericValuetype that encapsulates exact numbers and by doing all calculations in this type. Previously the calculations were handled manually in the various evaluation functions. This made the code complicated and error prone.A
NumericValueis made of:- an imaginary part, represented as a fixed-precision number
- a real part, represented either as a fixed or arbitrary precision number or as the product of a rational number and the square root of an integer.
For example:
- 234.567
- 1/2
- 3√5
- √7/3
- 4-3i
While this is a significant change internally, the external API remains the same. The result of calculations should be more predictable and more accurate.
One change to the public API is that the
expr.numericValueproperty is now either a machine precision number or aNumericValueobject. -
Rule Wildcards
When defining a rule as a LaTeX expression, single character identifiers are interpreted as wildcards. For example, the rule
x + x -> 2xwill match any expression with two identical terms. The wildcard corresponding toxis_x.It is now possible to define sequence wildcards and optional sequence wildcards. Sequence wildcards match 1 or more expressions, while optional sequence wildcards match 0 or more expressions.
They are indicated in LaTeX as
...xand...x?respectively. For example:expr.simplify("x + ...y -> 2x");If
exprisa + b + cthe rule will match and return2aexpr.simplify("x + ...y? -> 3x");If
exprisa + b + cthe rule will match and return3a. Ifexprisathe rule will match and return3a. -
Conditional Rules
Rules can now include conditions that are evaluated at runtime. If the condition is not satisfied, the rules does not apply.
For example, to simplify the expression
|x|:expr.simplify({rules: ["|x_{>=0}| -> x","|x_{<0}| -> -x",]});The condition is indicated as a subscript of the wildcard. The condition can be one of:
-
boolean- a boolean value, True or False -
string- a string of characters -
number- a number literal -
symbol -
expression -
numeric- an expression that has a numeric value, i.e. 2√3, 1/2, 3.14 -
integer- an integer value, -2, -1, 0, 1, 2, 3, ... -
natural- a natural number, 0, 1, 2, 3, ... -
real- real numbers, including integers -
imaginary- imaginary numbers, i.e. 2i, 3√-1 (not including real numbers) -
complex- complex numbers, including real and imaginary -
rational- rational numbers, 1/2, 3/4, 5/6, ... -
irrational- irrational numbers, √2, √3, π, ... -
algebraic- algebraic numbers, rational and irrational -
transcendental- transcendental numbers, π, e, ... -
positive- positive real numbers, > 0 -
negative- negative real numbers, < 0 -
nonnegative- nonnegative real numbers, >= 0 -
nonpositive- nonpositive real numbers, <= 0 -
even- even integers, 0, 2, 4, 6, ... -
odd- odd integers, 1, 3, 5, 7, ... -
prime:A000040 - prime numbers, 2, 3, 5, 7, 11, ... -
composite:A002808 - composite numbers, 4, 6, 8, 9, 10, ... -
notzero- a value that is not zero -
notone- a value that is not one -
finite- a finite value, not infinite -
infinite -
constant -
variable -
function -
operator -
relation- an equation or inequality -
equation -
inequality -
vector- a tensor of rank 1 -
matrix- a tensor of rank 2 -
list- a collection of values -
set- a collection of unique values -
tuple- a fixed length list -
single- a tuple of length 1 -
pair- a tuple of length 2 -
triple- a tuple of length 3 -
collection- a list, set, or tuple -
tensor- a nested list of values of the same type -
scalar- not a tensor or list
or one of the following expressions:
>0'->positive,\gt0'->positive,<0'->negative,\lt0'->negative,>=0'->nonnegative,\geq0'->nonnegative,<=0'->nonpositive,\leq0'->nonpositive,!=0'->notzero,\neq0'->notzero,!=1'->notone,\neq1'->notone,\in\Z'->integer,\in\mathbb{Z}'->integer,\in\N'->natural,\in\mathbb{N}'->natural,\in\R'->real,\in\mathbb{R}'->real,\in\C'->complex,\in\mathbb{C}'->complex,\in\Q'->rational,\in\mathbb{Q}'->rational,\in\Z^+'->integer,positive,\in\Z^-'->intger,negative,\in\Z^*'->nonzero,\in\R^+'->positive,\in\R^-'->negative,\in\R^*'->real,nonzero,\in\N^*'->integer,positive,\in\N_0'->integer,nonnegative,\in\R\backslash\Q'->irrational,
More complex conditions can be specified following a semi-colon, for example:
expr.simplify({x -> 2x; x < 10});Note that this syntax complements the existing rule syntax, and can be used together with the existing, more verbose, rule syntax.
expr.simplify({rules: [{match: "x + x", replace: "2x", condition: "x < 10"}]});This advanced syntax can specify more complex conditions, for example above the rule will only apply if
xis less than 10. -
-
Improved results for
Expand. In some cases the expression was not fully expanded. For example,4x(3x+2)-5(5x-4)now returns12x^2 - 17x + 20. Previously it returned4x(3x+2)+25x-20. -
AsciiMath serialization The
expr.toString()method now returns a serialization of the expression using the AsciiMath format.The serialization to AsciiMath can be customized using the
toAsciiMath()method. For example:console.log(ce.box(['Sigma', 2]).toAsciiMath({functions: {Sigma: 'sigma'}}));// -> sigma(2) -
The tolerance can now be specified with a value of
"auto"which will use the precision to determine a reasonable tolerance. The tolerance is used when comparing two numbers for equality. The tolerance can be specified with thece.toleranceproperty or in the Compute Engine constructor. -
Boxed expressions have some additional properties:
expr.isNumberLiteral- true if the expression is a number literal.This is equivalent to checking ifexpr.numericValueis notnull.expr.re- the real part of the expression, if it is a number literal,undefinedif not a number literal.expr.im- the imaginary part of the expression, if it is a number literal,undefinedif not a number literal.expr.bignumRe- the real part of the expression as a bignum, if it is a number literal,undefinedif not a number literal or a bignum representation is not available.expr.bignumIm- the imaginary part of the expression as a bignum, if it is a number literal,undefinedif not a number literal or if a bignum representation is not available.expr.root()to get the root of the expression. For example,expr.root(3)will return the cube root of the expression.- Additionally, the relational operators (
expr.isLess(), expr.isEqual(), etc...) now accept a number argument. For example,expr.isGreater(1)will return true if the expression is greater than 1.
-
Added LaTeX syntax to index collections. If
ais a collection:a[i]is parsed as["At", "a", "i"].a[i,j]is parsed as["At", "a", "i", "j"].a_iis parsed as["At", "a", "i"].a_{i,j}is parsed as["At", "a", "i", "j"].
-
Added support for Kronecker delta notation, i.e.
\delta_{ij}, which is parsed as["KroneckerDelta", "i", "j"]and is equal to 1 ifi = jand 0 otherwise.When a single index is provided the value of the function is 1 if the index is 0 and 0 otherwise
When multiple index are provided, the value of the function is 1 if all the indexes are equal and 0 otherwise.
-
Added support for Iverson Bracket notation, i.e.
[a = b], which is parsed as["Boole", ["Equal", "a", "b"]]and is equal to 1 if its argument is true and 0 otherwise. The argument is expected to be a relational expression. -
Implemented
UniqueandTallyon collections.Uniquereturns a collection with only the unique elements of the input collection, andTallyreturns a collection with the count of each unique element.console.log(ce.box(['Unique', ['List', 1, 2, 3, 1, 2, 3, 4, 5]]).value);// -> [1, 2, 3, 4, 5]console.log(ce.box(['Tally', ['List', 1, 2, 3, 1, 2, 3, 4, 5]]).value);// -> [['List', 1, 2, 3, 4, 5], ['List', 2, 2, 2, 1, 1]] -
Implemented the
Map,FilterandTabulatefunctions. These functions can be used to transform collections, for example:// Using LaTeXconsole.log(ce.parse('\\mathrm{Map}([3, 5, 7], x \\mapsto x^2)').toString());// -> [9, 25, 49]// Using boxed expressionsconsole.log(ce.box(['Map', ['List', 3, 5, 7], ['Square', '_']]).value);// -> [9, 25, 49]console.log(ce.box(['Tabulate',['Square', '_'], 5]).value);// -> [1, 4, 9, 16, 25]Tabulatecan be used with multiple indexes. For example, to generate a 4x4 unit matrix:console.log(ce.box(['Tabulate', ['If', ['Equal', '_1', '_2'], 1, 0]], 4, 4).value);// -> [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]// Using the Kronecker delta notation:console.log(ce.parse('\\mathrm{Tabulate}(i, j \\mapsto \\delta_{ij}, 4, 4)').value);// -> [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]] -
Added
Randomfunction.["Random"]returns a real pseudo-random number betwen 0 and 1.["Random", 10]returns an integer between 0 and 9,["Random", 5, 10]returns an integer between 5 and 10. -
Extended the definition of
expr.isConstant. Previously, it only applied to symbols, e.g.Pi. Now it apply to all expressions.expr.isConstantis true if the expression is a number literal, a symbol with a constant value, or a pure function with constant arguments. -
The boxed expression properties
isPositive,isNegative,isNonNegative,isNonPositive,isZero,isNotZeronow return a useful value for most function expressions. For example,ce.parse('|x + 1|').isPositiveis true.If the value cannot be determined, the property will return
undefined. For example,ce.parse('|x + 1|').isZeroisundefined.If the expression is not a real number, the property will return
NaN. For example,ce.parse('i').isPositiveisNaN. -
Added
Choosefunction to compute binomial coefficients, i.e.Choose(5, 2)is equal to 10. -
The fallback for non-constructible complex values of trigonometric functions is now implemented via rules.
-
The canonical order of the arguments has changed and should be more consistent and predictable. In particular, for polynomials, the monomial order is now degrevlex.
-
Canonical expressions can now include a
Rootexpression. For example, the canonical form of\\sqrt[3]{5}is["Root", 5, 3]. Previously, these were represented as["Power", 5, ["Divide", 1, 3]]. -
The function definitions no longer have a
Nhandler. Instead theevaluatehandler has an optional{numericApproximation}argument.
Issues Resolved
-
#188 Throw an error when invalid expressions are boxed, for example
ce.box(["Add", ["3"]]). -
Some LaTeX renderer can't render
\/, so use/instead. -
When definitions are added to the LaTeX dictionary, they now take precedence over the built-in definitions. This allows users to override the built-in definitions.
-
Improved parsing of functions, including when a mixture of named and positional arguments are used.
-
#175 Matching some patterns when the target had not enough operands would result in a runtime error.
0.25.1 2024-06-27
Issues Resolved
- #174 Fixed some simplifications, such as
\frac{a^n}{a^m} = a^{n-m)
New Features
-
Rules can be defined using a new shorthand syntax, where each rule is a string of LaTeX:
expr.simplify(["\\frac{x}{x} -> 1", "x + x -> 2x"]);
Single letter variables are assumed to be wildcards, so x is interpreted as
the wildcard _x.
Additionally, the expanded form can also include LaTeX strings. The previous syntax using expressions can still be used, and the new and old syntax can be mixed.
For example:
expr.simplify([
{
match: "\\frac{x}{x}",
replace: "1"
},
{
match: ["Add", "x", "x"],
replace: "2x"
}
]);
The condition function can also be expressed as a LaTeX string.
expr.simplify([ { match: "\\frac{x}{x}", replace: 1, condition: "x != 0" }, ]);
The shorthand syntax can be used any where a ruleset is expected, including with
the ce.rule() function.
- A new
ce.getRuleSet()method gives access to the built-in rules. - #171 The
SubtractandDividefunction can now accept an arbitrary number of arguments. For example,["Subtract", 1, 2, 3]is equivalent to["Subtract", ["Subtract", 1, 2], 3].
0.25.0 2024-06-25
Breaking Changes
-
The canonical form of expressions has changed. It is now more consistent and simpler and should produce more predictable results.
For example, previously
ce.parse("1-x^2")would produce["Subtract", 1, ["Square", "x"]].While this is a readable form, it introduces some complications when manipulating the expression: both the
SubtractandSquarefunctions have to be handled, in addition toAddandPower.The new canonical form of this expression is
["Add", 1, ["Negate", ["Power", "x", 2]]]. It is a bit more verbose, but it is simpler to manipulate. -
The
ce.serialize()method has been replaced withexpr.toLatex()andexpr.toMathJson(). Thece.latexOptionsandce.jsonSerializationOptionsproperties have been removed. Instead, pass the formating options directly to thetoLatex()andtoMathJson()methods. Thece.parse()method now takes an optional argument to specify the format of the input string. -
The default JSON serialization of an expression has changed.
Previously, the default JSON serialization, accessed via the
.jsonproperty, had some transformations applied to it (sugaring) to make the JSON more human readable.For example,
ce.parse("\frac12").jsonwould return the symbol"Half"instead of["Divide", 1, 2].However, this could lead to some confusion when manipulating the JSON directly. Since the JSON is intended to be used by machine more than humans, these additional transformations have been removed.
The
expr.jsonproperty now returns the JSON representing the expression, without any transformations.To get a version of JSON with some transformations applied use the
ce.toMathJson()function.expr = ce.box(["Subtract", 1, ["Square", "x"]]);console.log(expr.json);// -> ["Add", 1, ["Negate", ["Power", "x", 2]]]expr.toMathJson()// -> ["Subtract", 1, ["Square", "x"]]expr.toMathJson({exclude: "Square"})// -> ["Subtract", 1, ["Power", "x", 2]]In practice, the impact of both of these changes should be minimal. If you were manipulating expressions using
BoxedExpression, the new canonical form should make it easier to manipulate expressions. You can potentially simplify your code by removing special cases for functions such asSquareandSubtract.If you were using the JSON serialization directly, you may also be able to simplify you code since the default output from
expr.jsonis now more consistent and simpler. -
The name of some number formatting options has changed. The number formatting options are an optional argument of
ce.parse()andce.toLatex(). See theNumberFormatandNumberSerializationFormattypes. -
The values +infinity, -infinity and NaN are now represented preferably with the symbols
PositiveInfinity,NegativeInfinityandNaNrespectively. Previously they were represented with numeric values, i.e.{num: "+Infinity"},{num: "-Infinity"}and{num: "NaN"}. The numeric values are still supported, but the symbols are preferred. -
The method
expr.isNothinghas been removed. Instead, useexpr.symbol === "Nothing".
New Features
-
When serializing to LaTeX, the output can be "prettified". This involves modifying the LaTeX output to make it more pleasant to read, for example:
a+\\frac{-b}{c}->a-\\frac{b}{c}a\\times b^{-1}->\\frac{a}{b}\\frac{a}{b}\\frac{c}{d}->\\frac{a\\cdot c}{b\\cdot d}--2->2
This is on by default and can be turned off by setting the
prettifyoption tofalse. For example:ce.parse("a+\\frac{-b}{c}").toLatex({prettify: true})// -> "a-\\frac{b}{c}"ce.parse("a+\\frac{-b}{c}").toLatex({prettify: false})// -> "a+\\frac{-b}{c}" -
Numbers can have a different digit group length for the whole and fractional part of a number. For example,
ce.toLatex(ce.parse("1234.5678"), {digitGroup: [3, 0]})will return1\,234.5678. -
Numbers can now be formatted using South-East Asian Numbering System, i.e. lakh and crore. For example:
ce.toLatex(ce.parse("12345678"), {digitGroup: "lakh"})// -> "1,23,45,678" -
Expressions with Integrate functions can now be compiled to JavaScript. The compiled function can be used to evaluate the integral numerically. For example:
const f = ce.parse("\\int_0^1 x^2 dx");const compiled = f.compile();console.log(compiled()); // -> 0.33232945619482307 -
#82 Support for angular units. The default is radians, but degrees can be used by setting
ce.angularUnit = "deg". Other possible values are "grad" and "turn". This affects how unitless numbers with a trigonometric function are interpreted. For example,sin(90)will return 1 whence.angularUnitis "deg", 0.8939966636005579 whence.angularUnitis "grad" and 0 whence.angularUnitis "turn". -
Added
expr.map(fn)method to apply a function to each subexpression of an expression. This can be useful to apply custom canonical forms and compare two expressions. -
An optional canonical form can now be specified with the
ce.function().
Issues Resolved
- #173 Parsing
1++2would result in an expression with aPreIncrementfunction. It is now correctly parsed as["Add", 1, 2]. - #161 Power expressions would not be processed when their argument was a Divide expression.
- #165 More aggressive simplification of expressions with exponent greater than 3.
- #169 Calculating a constant integral (and integral that did not depend on the variable) would result in a runtime error.
- #164 Negative mixed fractions (e.g.
-1\frac23) are now parsed correctly. - #162 Numeric evaluation of expressions with large exponents could result in machine precision numbers instead of bignum numbers.
- #155 The expression
["Subtract", ["Multiply", 0.5, "x"], ["Divide", "x", 2]]will now evaluate to0. - #154 In some cases, parsing implicit argument of trig function return more
natural results, for example
\cos a \sin bis now parsed as(\cos a)(\sin b)and not\cos (a \sin b). - #147 The associativity of some operators, including
/was not applied correctly, resulting in unexpected results. For example,1/2/3would be parsed as["Divide", 1, ["Divide", 2, 3]]instead of["Divide", ["Divide", 1, 2], 3]. - #146 When parsing an expression like
x(x+1)wherexis an undeclared symbol, do not infer thatxis a function. Instead, infer thatxis a variable and that the expression is a product. - #145 The expression
["Or", "False", "False"], that is when all the arguments areFalse, is now evaluates toFalse. - Fixed canonical form of
e^x^2, and more generally apply power rule in more cases. - Added missing "Sech" and "Csch" functions.
- The digit grouping serializing would place the separator in the wrong place for some numbers.
- The
avoidExponentsInRangeformating option would not always avoid exponents in the specified range.
0.24.0 2024-02-23
Issues Resolved
- Fix parsing of very deeply nested expressions.
- Correctly apply rules to deeply nested expressions.
expr.print()now correctly prints the expression when using the minified version of the library.expr.isEqual()now correctly compares equalities and inequalities.expr.match()has been improved and works correctly in more cases. The signature of thematchfunction has been changed so that the pattern is the first argument, i.e. instead ofpattern.match(expr)useexpr.match(pattern).- Fix
expr.print()when using the minified version of the library. - #142 Accept complex expressions as the subcript of
\lnand\login LaTeX. - #139 Parse quantifiers
\foralland\existsin LaTeX.
0.23.1 2024-01-27
Issues Resolved
- Using a custom canonical order of
"Multiply"would not distribute theNegatefunction. - #141 The canonical form
"Order"was applied to non-commutative functions.
0.23.0 2024-01-01
New Features
- Added
ExpandAllfunction to expand an expression recursively. - Added
Factorfunction to factor an expression. - Added
Togetherfunction to combine rational expressions into a single fraction.
Issues Resolved
- The expression
\frac5 7is now parsed correctly as\frac{5}{7}instead of\frac{5}{}7. - Do not sugar non-canonical expression. Previously,
ce.parse('\\frac{1}{2}', {canonical: false})would returnHalfinstead of['Divide', '1', '2']. - #132 Attempting to set a value to 0 with
ce.defineSymbol("count", {value: 0})would fail: the symbol would be undefined. - Correctly evaluate power expressions in some cases, for example
(\sqrt2 + \sqrt2)^2. - Comparison of expressions containing non-exact numbers could fail. For
example:
2(13.1+3.1x)and26.2+6.2xwould not be considered equal.
Improvements
- Significant improvements to symbolic computation. Now, boxing, canonicalization and evaluation are more consistent and produce more predictable results.
- Adedd the
\negcommand, synonym for\lnot->Not. - Relational expressions (inequalities, etc...) are now properly factored.
- Integers are now factored when simplifying, i.e.
2x = 4x->x = 2x.
0.22.0 2023-11-13
Breaking Changes
-
Rule Syntax
The syntax to describe rules has changed. The syntax for a rule was previously a tuple
[lhs, rhs, {condition} ]. The new syntax is an object with the propertiesmatch,replaceandcondition. For example:- previous syntax:
[["Add", "_x", "_x"], ["Multiply", 2, "_x"]] - new syntax:
{match: ["Add", "_x", "_x"], replace: ["Multiply", 2, "_x"]}
The
conditionproperty is optional, and is either a boxed function or a JavaScript function. For example, to add a condition that checks that_xis a number literal:{match: ["Add", "_x", "_x"],replace: ["Multiply", 2, "_x"],condition: ({_x}) => _x.isNumberLiteral} - previous syntax:
-
CanonicalFormThe
CanonicalOrderfunction has been replaced by the more flexibleCanonicalFormfunction. TheCanonicalFormfunction takes an expression and a list of transformations to apply. To apply the same transformations asCanonicalOrder, use:['CanonicalForm', expr, 'Order']These canonical forms can also be specified with
box()andparse()options:ce.box(expr, { canonical: "Order" });ce.parse("x^2 + 2x + 1", { canonical: "Order" });
Work In Progress
- Linear algebra functions:
Rank,Shape,Reshape,Flatten,Determinant,Trace,Transpose,ConjugateTranspose,Inverse. See the Linear Algebra reference guide. Some of these function may not yet return correct result in all cases.
New Features
- Added a
expr.print()method as a synonym forconsole.log(expr.toString()). - Added an
exactoption (false by default) to theexpr.match()pattern matching method. Whentruesome additional patterns are automatically recognized, for example,xwill match["Multiply", '_a', 'x']whenexactisfalse, but not whenexactistrue.
Improvements
- The equation solver used by
expr.solve()has been improved and can now solve more equations. - The pattern matching engine has been improved and can now match more expressions, including sequences for commutative functions.
0.21.0 2023-11-02
New Features
-
#125 Parse and serialize environemnts, i.e.
\begin{matrix} 1 & 2 \\ 3 & 4 \end{matrix}will be parsed as["Matrix", ["List", ["List", 1, 2], ["List", 3, 4]]].A new section on Linear Algebra has some details on the supported formats.
The linear algebra operations are limited at the moment, but will be expanded in the future.
-
Added
IsSamefunction, which is the function expression corresponding toexpr.isSame(). -
AddedCanonicalOrderfunction, which sorts the arguments of commutative functions into canonical order. This is useful to compare two non-canonical expressions for equality.
ce.box(["CanonicalOrder", ["Add", 1, "x"]]).isSame(
ce.box(["CanonicalOrder", ["Add", "x", 1]])
);
// -> true
Issue Resolved
- When evaluating a sum (
\sum) with a bound that is not a number, return the sum expression instead of an error.
0.20.2 2023-10-31
Issues Resolved
- Fixed numerical evaluation of integrals and limits when parsed from LaTeX.
console.info(ce.parse("\\lim_{x \\to 0} \\frac{\\sin(x)}{x}").value);
// -> 1
console.info(ce.parse("\\int_{0}^{2} x^2 dx").value);
// -> 2.6666666666666665
0.20.1 2023-10-31
Issues Resolved
- Fixed evaluation of functions with multiple arguments
- Fixed compilation of some function assignments
- Improved serialization of function assignment
0.20.0 2023-10-30
Breaking Changes
-
Architectural changes: the invisible operator is used to represent the multiplication of two adjacent symbols, i.e.
2x. It was previously handled during parsing, but it is now handled during canonicalization. This allows more complex syntactic structures to be handled correctly, for examplef(x) := 2x: previously, the left-hand-side argument would have been parsed as a function application, while in this case it should be interpreted as a function definition.A new
InvisibleOperatorfunction has been added to support this.The
applyInvisibleOperatorparsing option has been removed. To support custom invisible operators, use theInvisibleOperatorfunction.
Issues Resolved
- #25 Correctly parse chained relational operators, i.e.
a < b <= c - #126 Logic operators only accepted up to two arguments.
- #127 Correctly compile
Logwith bases other than 10. - Correctly parse numbers with repeating patterns but no fractional digits, i.e.
0.(1234) - Correctly parse
|1+|a|+2|
New Features and Improvements
- Function assignment can now be done with this syntax:
f(x) := 2x+1. This syntax is equivalent tof := x -> 2x+1. - Implement the
ModandCongruentfunction. - Correctly parse
11 \bmod 5(Mod) and26\equiv 11 \pmod5(Congruent) - Better handle empty argument lists, i.e.
f() - When a function is used before being declared, infer that the symbol is a
function, e.g.
f(12)will infer thatfis a function (and not a variablefmultiplied by 12) - When a constant is followed by some parentheses, don't assume this is a
function application, e.g.
\pi(3+n)is now parsed as["Multiply", "Pi", ["Add", 3, "n"]]instead of["Pi", ["Add", 3, "n"]] - Improved parsing of nested lists, sequences and sets.
- Improved error messages when syntax errors are encountered during LaTeX parsing.
- When parsing with the canonical option set to false, preserve more closely the original LaTeX syntax.
- When parsing text strings, convert some LaTeX commands to Unicode, including
spacing commands. As a result,
ce.parse("\\text{dead\;beef}_{16}")correctly gets evaluated to 3,735,928,559.
0.19.1 2023-10-26
Issues Resolved
- Assigning a function to an indentifier works correctly now, i.e.
ce.parse("\\operatorname{f} := x \\mapsto 2x").evaluate();
0.19.0 2023-10-25
Breaking Changes
- The
domainproperty of the function definitionsignatureis deprecated and replaced with theparams,optParams,restParamandresultproperties instead. Thedomainproperty is still supported for backward compatibility, but will be removed in a future version.
Issues Resolved
- When invoking a declared function in a numeric operation, correctly infer the result type.
["Assign", "f", ["Add", "_", 1]]
["Add", ["f", 1], 1]
// -> 3
Previously a domain error was returned, now f is inferred to have a numeric
return type.
- Fixed a runtime error when inverting a fraction, i.e.
\frac{3}{4}^{-1} - The tangent of π/2 now correctly returns
ComplexInfinity. - The exact values of some constructible trigonometric operations (e.g.
\tan 18\degree = \frac{\sqrt{25-10\sqrt5}}{5}) returned incorrect results. The unit test case was incorrect and did not detect the problem. The unit test case has been fixed and the returned values are now correct.
New Features
- Implemented
UnionandIntersectionof collections, for example:
["Intersection", ["List", 3, 5, 7], ["List", 2, 5, 9]]
// -> ["Set", 5]
["Union", ["List", 3, 5, 7], ["List", 2, 5, 9]]
// -> ["Set", 3, 5, 7, 2, 9]
-
Parse ranges, for example
1..5or1, 3..10. Ranges are collections and can be used anywhere collections can be used. -
The functions
Sum,Product,Min,Max, and the statistics functions (Mean,Median,Variance, etc...) now handle collection arguments: collections:["Range"],["Interval"],["Linspace"]expressions["List"]or["Set"]expressions["Tuple"],["Pair"],["Pair"],["Triple"]expressions["Sequence"]expressions
-
Most mathematical functions are now threadable, that is their arguments can be collections, for example:
["Sin", ["List", 0, 1, 5]]
// -> ["List", 0, 0.8414709848078965, -0.9589242746631385]
["Add", ["List", 1, 2], ["List", 3, 4]]
// -> ["List", 4, 6]
- Added
GCDandLCMfunctions
["GCD", 10, 5, 15]
// -> 5
["LCM", 10, 5, 15]
// -> 30
-
Added
Numerator,Denominator,NumeratorDenominatorfunctions. These functions can be used on non-canonical expressions. -
Added
HeadandTailfunctions which can be used on non-canonical expressions. -
Added
display-quotientandinline-quotientstyle for formatting of division expressions in LaTeX.
Improvements
- Improved parsing of
\degreecommand
ce.parse("30\\degree)
// -> ["Divide", "Pi", 6]
- Improved interoperability with JavaScript:
expr.valuewill return a JavaScript primitive (number,boolean,string, etc...) when possible. This is a more succinct version ofexpr.N().valueOf().
0.18.1 2023-10-16
Issues Resolved
- Parsing of whole numbers while in
rationalmode would return incorrect results. - The
NDfunction to evaluate derivatives numerically now return correct values.
ce.parse("\\mathrm{ND}(x \\mapsto 3x^2+5x+7, 2)").N();
// -> 17.000000000001
Improvements
- Speed up
NIntegrateby temporarily switching the numeric mode tomachinewhile computing the Monte Carlo approximation.
0.18.0 2023-10-16
New Features
- Expanded LaTeX dictionary with
\max,\min,\sup,\infand\limfunctions - Added
SupremumandInfimumfunctions - Compilation of
Blockexpressions, local variables, return statements and conditionalsIf. - Added numerical evaluation of limits with
Limitfunctions andNLimitfunctions, using a Richardson Extrapolation.
console.info(ce.parse("\\lim_{x\\to0} \\frac{\\sin x}{x}").N().json);
// -> 1
console.info(
ce.box(["NLimit", ["Divide", ["Sin", "_"], "_"], 0]).evaluate().json
);
// -> 1
console.info(ce.parse("\\lim_{x\\to \\infty} \\cos \\frac{1}{x}").N().json);
// -> 1
-
Added
AssignandDeclarefunctions to assign values to symbols and declare symbols with a domain. -
Blockevaluations with local variables work now. For example:
ce.box(["Block", ["Assign", "c", 5], ["Multiply", "c", 2]]).evaluate().json;
// -> 10
-
When decimal numbers are parsed they are interpreted as inexact numbers by default, i.e. "1.2" ->
{num: "1.2"}. To force the number to be interpreted as a rational number, setce.latexOptions.parseNumbers = "rational". In that case, "1.2" ->["Rational", 12, 10], an exact number.While regular decimals are considered "inexact" numbers (i.e. they are assumed to be an approximation), rationals are assumed to be exact. In most cases, the safest thing to do is to consider decimal numbers as inexact to avoid introducing errors in calculations. If you know that the decimal numbers you parse are exact, you can use this option to consider them as exact numbers.
Improvements
- LaTeX parser: empty superscripts are now ignored, e.g.
4^{}is interpreted as4.
0.17.0 2023-10-12
Breaking Changes
- The
Nothingdomain has been renamed toNothingDomain - The
Functions,Maybe,Sequence,Dictionary,ListandTupledomain constructors have been renamed toFunctionOf,OptArg,VarArg,DictionaryOf,ListOfandTupleOf, respectively. - Domains no longer require a
["Domain"]expression wrapper, so for examplece.box("Pi").domainreturns"TranscendentalNumbers"instead of["Domain", "TranscendentalNumbers"]. - The
VarArgdomain constructor now indicates the presence of 0 or more arguments, instead of 1 or more arguments. - The
MaybeBooleansdomain has been dropped. Use["Union", "Booleans", "NothingDomain"]instead. - The
ce.defaultDomainhas been dropped. The domain of a symbol is now determined by the context in which it is used, or by thece.assume()method. In some circumstances, the domain of a symbol can beundefined.
New Features
- Symbolic derivatives of expressions can be calculated using the
Dfunction. For example,ce.box(["D", ce.parse("x^2 + 3x + 1"), "x"]).evaluate().latexreturns"2x + 3".
Improvements
- Some frequently used expressions are now available as predefined constants,
for example
ce.Pi,ce.Trueandce.Numbers. - Improved type checking and inference, especially for functions with complicated or non-numeric signatures.
Bugs Fixed
- Invoking a function repeatedly would invoke the function in the original scope rather than using a new scope for each invocation.
0.16.0 2023-09-29
Breaking Changes
- The methods
ce.let()andce.set()have been renamed toce.declare()andce.assign()respectively. - The method
ce.assume()requires a predicate. - The signatures of
ce.assume()andce.ask()have been simplified. - The signature of
ce.pushScope()has been simplified. - The
expr.freeVarsproperty has been renamed toexpr.unknowns. It returns the identifiers used in the expression that do not have a value associated with them. Theexpr.freeVariablesproperty now return the identifiers used in the expression that are defined outside of the local scope and are not arguments of the function, if a function.
New Features
-
Domain Inference when the domain of a symbol is not set explicitly (for example with
ce.declare()), the domain is inferred from the value of the symbol or from the context of its usage. -
Added
Assume,Identity,Which,Parse,N,Evaluate,Simplify,Domain. -
Assignments in LaTeX:
x \\coloneq 42produce["Assign", "x", 42] -
Added
ErfInv(inverse error function) -
Added
Factorial2(double factorial)
Functions
-
Functions can now be defined:
- using
ce.assign()orce.declare() - evaluating LaTeX:
(x, y) \mapsto x^2 + y^2 - evaluating MathJSON:
["Function", ["Add", ["Power", "x", 2], ["Power", "y", 2]]], "x", "y"]
- using
-
Function can be applied using
\operatorname{apply}or the operators\rhdand\lhd:\operatorname{apply}(f, x)f \rhd xx \lhd f
See Adding New Definitions and Functions.
Control Structures
- Added
FixedPoint,Block,If,Loop - Added
Break,ContinueandReturnstatements
Calculus
- Added numeric approximation of derivatives, using an 8-th order centered
difference approximation, with the
NDfunction. - Added numeric approximation of integrals, using a Monte Carlo method with
rebasing for improper integrals, with the
NIntegratefunction - Added symbolic calculation of derivatives with the
Dfunction.
Collections
Added support for collections such as lists, tuples, ranges, etc...
See Collections
Collections can be used to represent various data structures, such as lists, vectors, matrixes and more.
They can be iterated, sliced, filtered, mapped, etc...
["Length", ["List", 19, 23, 5]]
// -> 3
["IsEmpty", ["Range", 1, 10]]
// -> "False"
["Take", ["Linspace", 0, 100, 50], 4]
// -> ["List", 0, 2, 4, 6]
["Map", ["List", 1, 2, 3], ["Function", "x", ["Power", "x", 2]]]
// -> ["List", 1, 4, 9]
["Exclude", ["List", 33, 45, 12, 89, 65], -2, 2]
// -> ["List", 33, 12, 65]
["First", ["List", 33, 45, 12, 89, 65]]
// -> 33
Improvements
- The documentation has been significantly rewritten with help from an AI-powered writing assistant.
Issues Resolved
- The LaTeX string returned in
["Error"]expression was incorrectly tagged asLatexinstead ofLatexString.
0.15.0 2023-09-14
Improvements
- The
ce.serialize()function now takes an optionalcanonicalargument. Set it tofalseto prevent some transformations that are done to produce more readable LaTeX, but that may not match exactly the MathJSON. For example, by defaultce.serialize(["Power", "x", -1])returns\frac{1}{x}whilece.serialize(["Power", "x", -1], {canonical: false})returnsx^{-1}. - Improved parsing of delimiters, i.e.
\left(,\right], etc... - Added complex functions
Real,Imaginary,Arg,Conjugate,AbsArg. See Complex - Added parsing and evaluation of
\Re,\Im,\arg,^\star(Conjugate). - #104 Added the
["ComplexRoots", x, n]function which returns the nthroot ofx. - Added parsing and evaluation of statistics functions
Mean,Median,StandardDeviation,Variance,Skewness,Kurtosis,Quantile,Quartiles,InterquartileRange,Mode,Count,Erf,Erfc. See Statistics
0.14.0 2023-09-13
Breaking Changes
- The entries in the LaTeX syntax dictionary can now have LaTeX triggers
(
latexTrigger) or triggers based on identifiers (symbolTrigger). The former replaces thetriggerproperty. The latter is new. An entry with atriggerIdentifierofaveragewill match\operatorname{average},\mathrm{average}and other variants. - The
ce.latexOptionsandce.jsonSerializationOptionsproperties are more robust. They can be modified directly or one of their properties can be modified.
Improvements
-
Added more functions and symbols supported by
expr.compile():Factorialpostfix operator5!Gammafunction\Gamma(2)LogGammafunction\operatorname{LogGamma}(2)Gcdfunction\operatorname{gcd}(20, 5)Lcmfunction\operatorname{lcm}(20, 5)Chopfunction\operatorname{chop}(0.00000000001)Halfconstant\frac{1}{2}- 'MachineEpsilon' constant
GoldenRatioconstantCatalanConstantconstantEulerGammaconstant\gammaMaxfunction\operatorname{max}(1, 2, 3)Minfunction\operatorname{min}(13, 5, 7)- Relational operators:
Less,Greater,LessEqual,GreaterEqual, 'Equal', 'NotEqual' - Some logical operators and constants:
And,Or,Not,True,False
-
More complex identifiers syntax are recognized, including
\mathbin{},\mathord{}, etc...\operatorname{}is the recommended syntax, though: it will display the identifier in upright font and with the propert spacing, and is properly enclosing. Some commands, such as\mathrm{}are not properly enclosing: two adjacent\mathrm{}command could be merged into one. -
Environments are now parsed and serialized correctly.
-
When parsing LaTeX, function application is properly handled in more cases, including custom functions, e.g.
f(x) -
When parsing LaTeX, multiple arguments are properly handled, e.g.
f(x, y) -
Add LaTeX syntax for logical operators:
And:\land,\operatorname{and}(infix or function)Or:\lor,\operatorname{or}(infix or function)Not:\lnot,\operatorname{not}(prefix or function)Xor:\veebar(infix)Nand:\barwedge(infix)Nor:^^^^22BD(infix)Implies:\implies(infix)Equivalent:\iff(infix)
-
When a postfix operator is defined in the LaTeX syntax dictionary of the form
^plus a single token, a definition with braces is added automatically so that both forms will be recognized. -
Extended the LaTeX dictionary with:
floorceilroundsgnexpabsgcdlcmapply
-
Properly handle inverse and derivate notations, e.g.
\sin^{-1}(x),\sin'(x),\cos''(x),\cos^{(4)}(x)or even\sin^{-1}''(x)
0.13.0 2023-09-09
New Features
- Compilation Some expressions can be compiled to Javascript. This is useful
to evaluate an expression many times, for example in a loop. The compiled
expression is faster to evaluate than the original expression. To get the
compiled expression, use
expr.compile(). Read more at Compiling
Issues Resolved and Improvements
- Fixed parsing and serialization of extended LaTeX synonyms for
eandi. - Fixed serialization of
Half. - Fixed serialization of
Which - Improved serialization of
["Delimiter"]expressions.
0.12.7 2023-09-08
Improvements
- Made customization of the LaTeX dictionary simpler. The
ce.latexDictionaryproperty can be used to access and modify the dictionary. The documentation has been updated.
0.12.6 2023-09-08
Breaking Changes
- New API for the
Parserclass.
Improvements and Bux Fixes
- The
ComputeEnginenow exports thebignum()andcomplex()methods that can be used to create bignum and complex numbers from strings or numbers. The methodsisBigNum()andisComplex()have also been added to check if a value is a bignum (Decimal) or complex (Complex) number, for example as returned byexpr.numericValue. - #69
\leqwas incorrectly parsed asEqualsinstead ofLessEqual - #94 The
\expcommand was not parsed correctly. - Handle
PlusMinusin infix and prefix position, i.e.a\pm band\pm a. - Improved parsing, serialization
- Improved simplification
- Improved evaluation of
SumandProduct - Support complex identifiers (i.e. non-latin scripts, emojis).
- Fixed serialization of mixed numbers.
0.12.1 2022-12-01
Work around unpckg.com issue with libraries using BigInt.
0.12.0 2022-11-27
Breaking Changes
- The
expr.symbolsproperty return an array ofstring. Previously it returned an array ofBoxedExpression.
Improvements
- Rewrote the rational computation engine to use JavaScript
bigintinstead ofDecimalinstances. Performance improvements of up to 100x. expr.freeVarsprovides the free variables in an expression.- Improved performance of prime factorization of big num by x100.
- Added
["RandomExpression"] - Improved accuracy of some operations, for example
expr.parse("1e999 + 1").simplify()
Issues Resolved
- When
ce.numericMode === "auto", square roots of negative numbers would return an expression instead of a complex number. - The formatting of LaTeX numbers when using
ce.latexOptions.notation = "engineering"or"scientific"was incorrect. - The trig functions no longer "simplify" to the less simple exponential formulas.
- The canonical order of polynomials now orders non-lexicographic terms of degree 1 last, i.e. "ax^2+ bx+ c" instead of "x + ax^2 + bx".
- Fixed evaluation of inverse functions
- Fixed
expr.isLess,expr.isGreater,expr.isLessEqual,expr.isGreaterEqualand["Min"],["Max"]
0.11.0 2022-11-18
Breaking Changes
- The signature of
ce.defineSymbol(),ce.defineFunction()andce.pushScope()have changed
Improvements
- When a constant should be held or substituted with its value can now be more
precisely controlled. The
holdsymbol attribute is nowholdUntiland can specify at which stage the substitution should take place.
Issues Resolved
- Some constants would return a value as bignum or complex even when the
numericModedid not allow it. - Changing the value or domain of a symbol is now correctly taken into account.
Changes can be made with
ce.assume(),ce.set()orexpr.value. - When a symbol does not have a value associated with it, assumptions about it (e.g. "x > 0") are now correctly tracked and reflected.
0.10.0 2022-11-17
Breaking Changes
expr.isLiteralhas been removed. Useexpr.numericValue !== nullandexpr.string !== nullinstead.
Issues Resolved
- Calling
ce.forget()would not affect expressions that previously referenced the symbol.
Improvements
- More accurate calculations of some trig functions when using bignums.
- Improved performance when changing a value with
ce.set(). Up to 10x faster when evaluating a simple polynomial in a loop. ce.strictcan be set tofalseto bypass some domain and validity checks.
0.9.0 2022-11-15
Breaking Changes
- The head of a number expression is always
Number. Useexpr.domainto be get more specific info about what kind of number this is. - By default,
ce.box()andce.parse()return a canonical expression. A flag can be used if a non-canonical expression is desired. - The API surface of
BoxedExpressionhas been reduced. The propertiesmachineValue,bignumValue,asFloat,asSmallInteger,asRationaletc... have been replaced with a singlenumericValueproperty. parseUnknownSymbolis nowparseUnknownIdentifier
Improvements
-
Support angles in degrees with
30\degree,30^\circand\ang{30}. -
More accurate error expressions, for example if there is a missing closing delimiter an
["Error", ["ErrorCode", "'expected-closing-delimiter'", "')'"]]is produced. -
["Expand"]handles more cases -
The trig functions can now have a regular exponent, i.e.
\cos^2(x)in addition to-1for inverse, and a combination of\prime,\doubleprimeand'for derivatives. -
ce.assume()handle more expressions and can be used to define new symbols by domain or value. -
Better error message when parsing, e.g.
\sqrt(2)(instead of\sqrt{2}) -
Better simplification for square root expressions:
\sqrt{25x^2}->5x
-
Improved evaluation of
["Power"]expressions, including for negative arguments and non-integer exponents and complex arguments and exponents. -
Added
Arccot,Arcoth,Arcsch,Arcscc,ArsechandArccsc -
expr.solve()returns result for polynomials of order up to 2. -
The
pattern.match()function now work correctly for commutative functions, i.e.ce.pattern(['Add', '_a', 'x']).match(ce.parse('x+y')) -> {"_a": "y"} -
Added
ce.let()andce.set()to declare and assign values to identifiers. -
Preserve exact calculations involving rationals or square root of rationals.
\sqrt{\frac{49}{25}}->\frac{7}{5}
-
Addition and multiplication provide more consistent results for
evaluate()andN(). Evaluate returns an exact result when possible.- EXACT
- 2 + 5 -> 7
- 2 + 5/7 -> 19/7
- 2 + √2 -> 2 + √2
- 2 + √(5/7) -> 2 + √(5/7)
- 5/7 + 9/11 -> 118/77
- 5/7 + √2 -> 5/7 + √2
- 10/14 + √(18/9) -> 5/7 + √2
- √2 + √5 -> √2 + √5
- √2 + √2 -> 2√2
- sin(2) -> sin(2)
- sin(π/3) -> √3/2
- APPROXIMATE
- 2 + 2.1 -> 4.1
- 2 + √2.1 -> 3.44914
- 5/7 + √2.1 -> 2.16342
- sin(2) + √2.1 -> 2.35844
- EXACT
-
More consistent behavior of the
autonumeric mode: calculations are done withbignumandcomplexin most cases. -
JsonSerializationOptionshas a new option to specify the numeric precision in the MathJSON serialization. -
Shorthand numbers can now be strings if they do not fit in a float-64:
// Before
["Rational", { "num": "1234567890123456789"}, { "num": "2345678901234567889"}]
// Now
["Rational", "1234567890123456789", "2345678901234567889"]
\sumis now correctly parsed and evaluated. This includes creating a local scope with the index and expression value of the sum.
Bugs Fixed
- The parsing and evaluation of log functions could produce unexpected results
- The
\gammacommand now correctly maps to["Gamma"] - Fixed numeric evaluation of the
["Gamma"]function when using bignum - #57 Substituting
0(i.e. withexpr.subs({})) did not work. - #60 Correctly parse multi-char symbols with underscore, i.e.
\mathrm{V_a} - Parsing a number with repeating decimals and an exponent would drop the exponent.
- Correct calculation of complex square roots
\sqrt{-49}->7i
- Calculations were not always performed as bignum in
"auto"numeric mode if the precision was less than 15. Now, if the numeric mode is"auto", calculations are done as bignum or complex numbers. - If an identifier contained multiple strings of digits, it would not be
rendered to LaTeX correctly, e.g.
V20_20. - Correctly return
isRealfor real numbers
0.8.0 2022-10-02
Breaking Changes
-
Corrected the implementation of
expr.toJSON(),expr.valueOf()and added the esoteric[Symbol.toPrimitive]()method. These are used by JavaScript when interacting with other primitive types. A major change is thatexpr.toJSON()now returns anExpressionas an object literal, and not a string serialization of theExpression. -
Changed from "decimal" to "bignum". "Decimal" is a confusing name, since it is used to represent both integers and floating point numbers. Its key characteristic is that it is an arbitrary precision number, aka "bignum". This affects
ce.numericModewhich now usesbignuminstead ofdecimal,expr.decimalValue->expr.bignumValue,decimalValue()->bignumValue()
Bugs Fixed
- Numerical evaluation of expressions containing complex numbers when in
decimalorautomode produced incorrect results. Example:e^{i\\pi}
0.7.0 2022-09-30
Breaking Changes
- The
ce.latexOptions.preserveLatexdefault value is nowfalse - The first argument of the
["Error"]expression (default value) has been dropped. The first argument is now an error code, either as a string or an["ErrorCode"]expression.
Features
- Much improved LaTeX parser, in particular when parsing invalid LaTeX. The
parser now avoids throwing, but will return a partial expression with
["Error"]subexpressions indicating where the problems were. - Implemented new domain computation system (similar to type systems in programming languages)
- Added support for multiple signatures per function (ad-hoc polymorphism)
- Added
FixedPoint,Loop,Product,Sum,Break,Continue,Block,If,Let,Set,Function,Apply,Return - Added
Min,Max,Clamp - Parsing of
\sum,\prod,\int. - Added parsing of log functions,
\lb,\ln,\ln_{10},\ln_2, etc... - Added
expr.subexpressions,expr.getSubexpressions(),expr.errors,expr.symbols,expr.isValid. - Symbols can now be used to represent functions, i.e.
ce.box('Sin').domaincorrectly returns["Domain", "Function"]. - Correctly handle rational numbers with a numerator or denominator outside the range of a 64-bit float.
- Instead of a
Missingsymbol an["Error", "'missing'"]expression is used. - Name binding is now done lazily
- Correctly handle MathJSON numbers with repeating decimals, e.g.
1.(3). - Correctly evaluate inverse functions, e.g.
ce.parse('\\sin^{-1}(.5)).N() - Fixed some LaTeX serialization issues
Read more at Core Reference and [Arithmetic Reference] (https://cortexjs.io/compute-engine/reference/arithmetic/)
Bugs Fixed
- #43 If the input of
ce.parse()is an empty string, return an empty string forexpr.latexorexpr.json.latex: that is, ensure verbatim LaTeX round-tripping - Evaluating some functions, such as
\arccoswould result in a crash - Correctly handle parsing of multi-token decimal markers, e.g.
{,}
0.6.0 2022-04-18
Improvements
- Parse more cases of tabular environments
- Handle simplify and evaluate of inert functions by default
- Avoid unnecessary wrapping of functions when serializing LaTeX
- Parse arguments of LaTeX commands (e.g.
\vec{}) - #42 Export static
ComputeEngine.getLatexDictionary - Parse multi-character constants and variables, e.g.
\mathit{speed}and\mathrm{radius} - Parse/serialize some LaTeX styling commands:
\displaystyle,\tinyand more
0.5.0 2022-04-05
Improvements
- Correctly parse tabular content (for example in
\begin{pmatrix}...\end{pmatrix} - Correctly parse LaTeX groups, i.e.
{...} - Ensure constructible trigonometric values are canonical
- Correct and simplify evaluation loop for
simplify(),evaluate()andN(). - #41 Preserve the parsed LaTeX verbatim for top-level expressions
- #40 Correctly calculate the synthetic LaTeX metadata for numbers
- Only require Node LTS (16.14.2)
- Improved documentation, including Dark Mode support
0.4.4
Release Date: 2022-03-27
Improvements
- Added option to specify custom LaTeX dictionaries in
ComputeEngineconstructor expr.valueOfreturns rational numbers as[number, number]when applicable- The non-ESM builds (
compute-engine.min.js) now targets vintage JavaScript for improved compatibility with outdated toolchains (e.g. Webpack 4) and environments. The ESM build (compute-engine.min.esm.js) targets evergreen JavaScript (currently ECMAScript 2020).
0.4.3
Release Date: 2022-03-21
Transition Guide from 0.4.2
The API has changed substantially between 0.4.2 and 0.4.3, however adapting code to the new API is very straightforward.
The two major changes are the introduction of the BoxedExpression class and
the removal of top level functions.
Boxed Expression
The BoxedExpression class is a immutable box (wrapper) that encapsulates a
MathJSON Expression. It provides some member functions that can be used to
manipulate the expression, for example expr.simplify() or expr.evaluate().
The boxed expresson itself is immutable. For example, calling expr.simplify()
will return a new, simplified, expression, without modifying expr.
To create a "boxed" expression from a "raw" MathJSON expression, use ce.box().
To create a boxed expression from a LaTeX string, use ce.parse().
To access the "raw" MathJSON expression, use the expr.json property. To
serialize the expression to LaTeX, use the expr.latex property.
The top level functions such as parse() and evaluate() are now member
functions of the ComputeEngine class or the BoxedExpression class.
There are additional member functions to examine the content of a boxed
expression. For example, expr.symbol will return null if the expression is
not a MathJSON symbol, otherwise it will return the name of the symbol as a
string. Similarly, expr.ops return the arguments (operands) of a function,
expr.asFloat return null if the expression does not have a numeric value
that can be represented by a float, a number otherwise, etc...
Canonical Form
Use expr.canonical to obtain the canonical form of an expression rather than
the ce.format() method.
The canonical form is less aggressive in its attempt to simplify than what was
performed by ce.format().
The canonical form still accounts for distributive and associative functions,
and will collapse some integer constants. However, in some cases it may be
necessary to invoke expr.simplify() in order to get the same results as
ce.format(expr).
Rational and Division
In addition to machine floating points, arbitrary precision numbers and complex numbers, the Compute Engine now also recognize and process rational numbers.
This is mostly an implementation detail, although you may see
["Rational", 3, 4], for example, in the value of a expr.json property.
If you do not want rational numbers represented in the value of the .json
property, you can exclude the Rational function from the serialization of JSON
(see below) in which case Divide will be used instead.
Note also that internally (as a result of boxing), Divide is represented as a
product of a power with a negative exponent. This makes some pattern detection
and simplifications easier. However, when the .json property is accessed,
product of powers with a negative exponents are converted to a Divide, unless
you have included Divide as an excluded function for serialization.
Similarly, Subtract is converted internally to Add, but may be serialized
unless excluded.
Parsing and Serialization Customization
Rather than using a separate instance of the LatexSyntax class to customize
the parsing or serialization, use a ComputeEngine instance and its
ce.parse() method and the expr.latex property.
Custom dictionaries (to parse/serialize custom LaTeX syntax) can be passed as an
argument to the ComputeEngine constructor.
For more advanced customizations, use ce.latexOptions = {...}. For example, to
change the formatting options of numbers, how the invisible operator is
interpreted, how unknown commands and symbols are interpreted, etc...
Note that there are also now options available for the "serialization" to
MathJSON, i.e. when the expr.json property is used. It is possible to control
for example if metadata should be included, if shorthand forms are allowed, or
whether some functions should be avoided (Divide, Sqrt, Subtract, etc...).
These options can be set using ce.jsonSerializationOptions = {...}.
Comparing Expressions
There are more options to compare two expressions.
Previously, match() could be used to check if one expression matched another
as a pattern.
If match() returned null, the first expression could not be matched to the
second. If it returned an object literal, the two expressions matched.
The top-level match() function is replaced by the expr.match() method.
However, there are two other options that may offer better results:
expr.isSame(otherExpr)return true ifexprandotherExprare structurally identical. Structural identity is closely related to the concept of pattern matching, that is["Add", 1, "x"]and["Add", "x", 1]are not the same, since the order of the arguments is different. It is useful for example to compare some input to an answer that is expected to have a specific form.expr.isEqual(otherExpr)return true ifexprandotherExprare mathematically identical. For examplece.parse("1+1").isEqual(ce.parse("2"))will return true. This is useful if the specific structure of the expression is not important.
It is also possible to evaluate a boolean expression with a relational operator,
such as Equal:
console.log(ce.box(["Equal", expr, 2]).evaluate().symbol);
// -> "True"
console.log(expr.isEqual(ce.box(2)));
// -> true
Before / After
| Before | After |
|---|---|
expr = ["Add", 1, 2] | expr = ce.box(["Add", 1, 2]) |
expr = ce.evaluate(expr) | expr = expr.evaluate() |
console.log(expr) | console.log(expr.json) |
expr = new LatexSyntax().parse("x^2+1") | expr = ce.parse("x^2+1") |
new LatexSyntax().serialize(expr) | expr.latex |
ce.simplify(expr) | expr.simplify() |
await ce.evaluate(expr) | expr.evaluate() |
ce.N(expr) | expr.N() |
ce.domain(expr) | expr.domain |
ce.format(expr...) | expr.canonical expr.simplify() |
0.3.0
Release Date: 2021-06-18
Improvements
- In LaTeX, parse
\operatorname{foo}as the MathJSON symbol"foo".