Skip to content

Wolfram Language conformance gaps

Known places where Woxi's output differs from wolframscript's. Every entry was found by diffing the two engines and verified against the Wolfram Language; none of them is fixed yet.

Where an entry says not reproducible, the difference comes from a choice Wolfram makes internally (an algorithm's tie-break, a library's rounding, a front-end representation) that cannot be recovered from its output — those are recorded so they are not investigated twice.

Output form, ordering and rendering

Woxi and Wolfram usually agree on the value and disagree on how it is printed. This is by far the largest group.

InputForm does not normalize function spellings to operator syntax

ToString[…, InputForm] (and plain output) prints the function-call spelling where WL prints the operator, so the two spellings of the same expression render differently:

# In a result (an unevaluated ReplaceAll is what a failed replacement returns):
wolframscript -code 'ToString[ReplaceAll[x, 5], InputForm]'         # x /. 5
woxi eval 'ToString[ReplaceAll[x, 5], InputForm]'                   # ReplaceAll[x, 5]

# In a held expression:
wolframscript -code 'ToString[Unevaluated[Map[f, g]], InputForm]'   # f /@ g
woxi eval 'ToString[Unevaluated[Map[f, g]], InputForm]'             # Map[f, g]

The affected heads are the ones with operator spellings: ReplaceAll (/.), ReplaceRepeated (//.), Map (/@), Apply (@@), and Part ([[…]]). Part is the mildest of these — it renders correctly on its own and only diverges inside a hold (ToString[Unevaluated[Part[a, 2]], InputForm]).

InputForm brackets more than the printed form does

wolframscript's ToString[…, InputForm] brackets things its top-level output leaves bare, so the two disagree only when the InputForm string is compared:

wolframscript -code 'Hold[{1, 2}?f]'                          # Hold[{1, 2}?f]
wolframscript -code 'ToString[(Hold[{1, 2}?f]), InputForm]'   # Hold[({1, 2})?f]

Woxi prints Hold[{1, 2}?f] in both. A Graph's options are the same story: InputForm wraps them in a List (and reorders them into its own canonical order), the printed form does not.

A nested Plus/Times operand is not bracketed

Times and Plus only flatten once they evaluate, so a held expression can carry one inside another. wolframscript brackets the inner one so the printed form re-reads as the same tree; Woxi prints the flat spelling, which re-reads as the flattened expression:

wolframscript -code 'ToString[Hold[Times[Times[a, b], c]], InputForm]' # Hold[(a*b)*c]
woxi eval 'ToString[Hold[Times[Times[a, b], c]], InputForm]'          # Hold[a*b*c]
wolframscript -code 'ToString[Hold[Plus[Plus[a, b], c]], InputForm]'  # Hold[(a + b) + c]
woxi eval 'ToString[Hold[Plus[Plus[a, b], c]], InputForm]'            # Hold[a + b + c]

The values agree — the two trees are equal once evaluated — and Woxi's reader never builds a nested one, so this only shows on an expression written in function form under a hold, or produced by a head replacement on a held division (Hold[a/(b c) d] /. Power -> pp).

Unary minus inside a pure function round-trips as a subtraction

wolframscript -code 'ToString[Unevaluated[-# &], InputForm]'  # -#1 &
woxi eval 'ToString[Unevaluated[-# &], InputForm]'            # 0 - #1 &

Same family as the known Not[#2] vs !#2 rendering gap.

Association inside Unevaluated echoes with the wrong spelling

wolframscript -code 'ToString[Unevaluated[<|1 -> a|>], InputForm]'  # Association[1 -> a]
woxi eval 'ToString[Unevaluated[<|1 -> a|>], InputForm]'            # <|1 -> a|>

Cosmetic, and confined to held echoes: ToString[<|1 -> a|>, InputForm] agrees on both engines.

-a/b groups differently, and a held reciprocal is not canonicalized

Woxi groups -a/b as -(a/b) where wolframscript groups it as (-a)/b — a parse-precedence difference rather than a rendering one. Hold[-1/x] prints -1/x where wolframscript canonicalises to -x^(-1). - -x and !!a are parse errors in Woxi (the grammar forbids a second prefix operator).

ToString renders 1D where wolframscript renders 2D ASCII art

wolframscript -code 'ToString[a/b]'   # a over a bar over b
woxi eval 'ToString[a/b]'             # a/b

wolframscript -code 'ToString[x^2]'   # a raised 2 above x
woxi eval 'ToString[x^2]'             # x^2

The bare top-level echo is 1D in both engines — only ToString (and an explicit OutputForm wrapper) triggers the 2D layout. Sqrt[x] and matrices already agree. Switching the default ToString path to Woxi's existing 2D renderer would move a great many snapshots at once, so fractions and powers are still 1D.

Consequences elsewhere: ToString[-48/2033], ToString[-10/3], ToString[1.5*^10] and 2D rationals inside message text all print flat.

NumberForm does not switch to scientific notation

wolframscript -code 'ToString[NumberForm[123456789.]]'  # 1.23457 × 10^8
woxi eval 'ToString[NumberForm[123456789.]]'            # 123457000.

Also NumberForm[1000000.] (WL 1. × 10^6) and NumberForm[1.5*10^-8] (WL 1.5 × 10^-8). In-range reals — roughly 10^-5 ≤ |x| < 10^6 — agree.

NumberForm/ScientificForm round half-to-even, wolframscript rounds half-up

wolframscript -code 'ToString[ScientificForm[12345., 4]]'  # 1.235 × 10^4
woxi eval 'ToString[ScientificForm[12345., 4]]'            # 1.234 × 10^4

Only at an exact-half significant-figure boundary; anything with a non-zero tail agrees. A latent edge in the same area: a decimal that rounds up across 10^6 (999999.6) stays 1000000. in the box path instead of switching to 1.×10^6.

NumberFormat and BaseForm of a real are unimplemented

NumberForm[…, NumberFormat -> f] stays unevaluated where WL renders 1234.5e, and BaseForm[0.5, 2] stays unevaluated where WL gives 0.1 over a subscript 2.

TextString falls back to ToString for symbolic expressions

WL writes a FullForm-like linear form with numericized leaves — x + y"Plus[x, y]", Sqrt[x]"Power[x, 0.5]", a/b"Times[a, Power[b, -1]]", but f[1/2]"f[0.5]". Woxi keeps the ordinary 2D ToString form. (TextString[100!] is "∞" in wolframscript — a formatter overflow that is deliberately not reproduced.)

TeXForm orders the terms of a sum by monomial degree

wolframscript -code 'ToString[TeXForm[1 + x + x^2]]'      # x^2+x+1
woxi eval 'ToString[TeXForm[1 + x + x^2]]'                # x+x^2+1

wolframscript -code 'ToString[TeXForm[a x^2 + b x + c]]'  # a x^2+b x+c
woxi eval 'ToString[TeXForm[a x^2 + b x + c]]'            # c+b x+a x^2

wolframscript -code 'ToString[TeXForm[Sin[x] + Cos[y]]]'  # \sin (x)+\cos (y)
woxi eval 'ToString[TeXForm[Sin[x] + Cos[y]]]'            # \cos (y)+\sin (x)

Woxi keeps the canonical order and only moves numbers (and complex atoms) to the end; Wolfram sorts by the monomial, highest degree first. Neither "reverse the canonical order" nor "stable sort by total degree" reproduces all the samples — 3 + a + b stays a+b+3 (not reversed) while Cos[y] + Sin[x] does reverse, and x^3 + x^2 y^2 keeps x^3 first even though its total degree is lower — so this needs WL's actual lexicographic monomial comparison. Everything else in a 211-expression TeXForm sweep agrees.

TeXForm writes a rational exponent's quotient with a \left. bar

wolframscript -code 'ToString[TeXForm[x^(1/(2 y))]]'  # x^{\left.\frac{1}{2}\right/y}
woxi eval 'ToString[TeXForm[x^(1/(2 y))]]'            # x^{\frac{1}{2 y}}

(Whether a rational coefficient folds into its factor's fraction — \frac{f(x)}{2} against \frac{1}{2} \log \left(x^2+1\right) — now follows a box-count estimate of the factor that reproduces every probed case; see tex_box_weight in src/functions/string_ast.rs.)

TeXForm orders a product's factors by Woxi's own Times order

wolframscript -code 'ToString[TeXForm[Sin[x]^2 Star[a, b]]]'  # \sin ^2(x) (a*b)
woxi eval 'ToString[TeXForm[Sin[x]^2 Star[a, b]]]'            # (a*b) \sin ^2(x)

wolframscript -code 'ToString[TeXForm[(1 + x) y (a + b)]]'    # (x+1) y (a+b)
woxi eval 'ToString[TeXForm[(1 + x) y (a + b)]]'              # y (a+b) (x+1)

wolframscript -code 'ToString[x^2 Sqrt[a^2 - x^2], InputForm]'  # x^2*Sqrt[a^2 - x^2]
woxi eval 'ToString[x^2 Sqrt[a^2 - x^2], InputForm]'            # Sqrt[a^2 - x^2]*x^2

The renderer shows the factors in the order the evaluated Times holds them (moving symbols ahead of a constant-free sum, as WL does). The difference is upstream, in how Woxi sorts Star[a, b] against a power, one sum against another, and a power of a sum against a power of a symbol; see the Times ordering entries below.

A minus written in front of a held rational coefficient

wolframscript -code 'ToString[TeXForm[HoldForm[-(4/3) Subst[a, b]]]]'  # \frac{1}{3} (-4) \text{Subst}(a,b)
woxi eval 'ToString[TeXForm[HoldForm[-(4/3) Subst[a, b]]]]'            # -\frac{4}{3} \text{Subst}(a,b)

WL boxes the held Times[Times[-1, Times[4, Power[3, -1]]], Subst[a, b]] piecewise, leaving (-4) stranded behind the \frac{1}{3}; Woxi gathers the coefficient into one signed fraction, as both engines do for the evaluated Times[Rational[-4, 3], Subst[a, b]]. Not reproduced on purpose.

A held 1/x loses its explicit 1 in FullForm and Part

wolframscript -code 'ToString[FullForm[Hold[1/(2 y) z]]]'  # Hold[Times[Times[1, Power[Times[2, y], -1]], z]]
woxi eval 'ToString[FullForm[Hold[1/(2 y) z]]]'            # Hold[Times[Power[Times[2, y], -1], z]]

wolframscript -code 'Hold[1/(2 y)][[1, 1]]'  # 1
woxi eval 'Hold[1/(2 y)][[1, 1]]'            # 2*y

WL reads 1/(2 y) as Times[1, Power[Times[2, y], -1]] and keeps the 1 while the expression is held; Woxi's parse tree is the quotient itself, so Part sees the divisor and the exponent as its two parts. The evaluated expression is the same in both.

TraditionalForm boxes a multi-argument call's arguments as one RowBox

wolframscript -code 'ToString[Int[Cos[x]/x^2, x], TraditionalForm]'
# DisplayForm[FormBox[RowBox[{Int, (, RowBox[{FractionBox[…], ,, x}], )}], TraditionalForm]]
woxi eval 'ToString[Int[Cos[x]/x^2, x], TraditionalForm]'
# DisplayForm[FormBox[RowBox[{Int, (, FractionBox[…], ,, x, )}], TraditionalForm]]

Woxi lays the arguments out flat in the call's row; WL nests them in a row of their own. Same picture, different box tree.

TraditionalForm boxes are written out inline instead of as TemplateBoxes

ToBoxes[TraditionalForm[…]] differs in representation, not in picture: wolframscript hands every special function to a named front-end template — TemplateBox[{"n","x"}, "LegendreP"], {"x"}, "Gamma", {"s"}, "Zeta" — while Woxi writes the same layout out as SubscriptBox/SubsuperscriptBox rows, because it has no front end and its own box renderers have to draw it. Only the outer TagBox[FormBox[…, TraditionalForm], …] wrapper is common; n! and Subscript[a, b] do conform.

Row is a template in both ({"2","x","t"}, "RowDefault") — but only in the box escape an InputForm string carries, which nothing has to draw. The box builder that feeds Woxi's own renderers keeps flattening it: ToString[Row[{"a", 1}], TraditionalForm] is RowBox[{a, 1}] against WL's TemplateBox[{"a", 1}, RowDefault], and ToBoxes[Row[{"a", 1}]] writes the call's own source out as a RowBox. ToBoxes there also takes only one argument where WL takes a form as the second.

Relatedly, expr_to_boxes typesets TraditionalForm with StandardForm glyphs: Sin[x]/2 is FractionBox[RowBox[{Sin[, x, ]}], 2] against WL's RowBox[{sin, (, x, )}], and Pi stays Pi instead of π.

ToBoxes in StandardForm drops StyleBox and held TagBox

ToBoxes[Style[x, Red]] is "x" where wolframscript gives StyleBox["x", RGBColor[1, 0, 0], StripOnInput -> False]. The TraditionalForm builder learned both; the StandardForm one has not.

A box segment mid-string is not shown as DisplayForm

WL renders a \!\(\*…\) box segment anywhere in a string as DisplayForm[<box>] in terminal OutputForm; Woxi only does it when the string starts with the segment. Generalising it would break plot labels, whose SVG renderer typesets the raw markers — the real fix needs a "terminal text" vs "text handed to a typesetter" distinction the form system does not have.

\!\(…\) without \* is full 2D linear syntax; WL parses it into a RowBox, Woxi leaves it literal.

Machine reals in grid cells render at six significant figures

TableForm/Grid/MatrixForm cells and typeset labels round machine reals to 6 significant figures with a ×10^exp switch outside [-5, 5], matching WL's StandardForm. The CLI eval InputForm keeps full precision, as wolframscript -code does. This is a deliberate display rule, listed here because it makes the two layers disagree with each other.

Canonical ordering of Plus and Times

Woxi has three independent canonical-order comparators (one for Sort, one for Order, one for Plus terms) and they drift from each other as well as from Wolfram. Diagnose an ordering divergence by testing Sort[…], Order[a, b] and the bare a + b separately.

The Plus comparator follows Wolfram for every term led by a function call (plus_call_led_ordering in tests/interpreter_tests/arithmetic.rs): calls sort after the symbols, powers and products, by head name, then from the last factor of a product by that call's arguments, its exponent and the remaining factors. What is left are the entries below.

Sort puts a symbol before a sum, and a call before its curried form

wolframscript -code 'Sort[{c, a + b}]'          # {a + b, c}
woxi eval 'Sort[{c, a + b}]'                    # {c, a + b}

wolframscript -code 'Sort[{f[x][y], f[x]}]'     # {f[x], f[x][y]}
woxi eval 'Sort[{f[x][y], f[x]}]'               # {f[x][y], f[x]}

Wolfram compares a sum against a symbol from the sum's last term (b < c) and a curried call after the plain call with the same head. The Plus comparator has both rules (h[a + b] + h[c], f[x] + f[x][y]); the Sort comparator still orders by expression class first.

Two rational-function arguments of the same head

wolframscript -code 'ToString[Subst[f[(Sqrt[2] - 2 x)/(-1 + Sqrt[2] x - x^2)], x] + Subst[f[(Sqrt[2] + 2 x)/(-1 - Sqrt[2] x - x^2)], x], InputForm]'
# Subst[f[(Sqrt[2] + 2*x)/(-1 - Sqrt[2]*x - x^2)], x] + Subst[f[(Sqrt[2] - 2*x)/(-1 + Sqrt[2]*x - x^2)], x]
woxi eval 'ToString[Subst[f[(Sqrt[2] - 2 x)/(-1 + Sqrt[2] x - x^2)], x] + Subst[f[(Sqrt[2] + 2 x)/(-1 - Sqrt[2] x - x^2)], x], InputForm]'
# Subst[f[(Sqrt[2] - 2*x)/(-1 + Sqrt[2]*x - x^2)], x] + Subst[f[(Sqrt[2] + 2*x)/(-1 - Sqrt[2]*x - x^2)], x]

Wolfram decides this pair by the denominators (-1 - Sqrt[2] x - x^2 before -1 + Sqrt[2] x - x^2); Woxi's argument comparison reaches the numerators first. Surfaces in one intermediate sum of Rubi's Int[Sqrt[Tan[x]], x] step display.

A symbolic multiple of Infinity cancels against itself

wolframscript -code 'a Infinity - a Infinity'   # a*-Infinity + a*Infinity
woxi eval 'a Infinity - a Infinity'             # 0

Woxi collects the two terms as like terms; WL leaves the sum (its two DirectedInfinity products are not combined). Numeric multiples agree (Infinity - Infinity is Indeterminate in both), though the Infinity::indet message names -Infinity + Infinity where WL says ComplexInfinity + ComplexInfinity for ComplexInfinity - ComplexInfinity.

Held symbols compare by short name

wolframscript -code 'BeginPackage["Foo`"]; Int::usage = "x"; EndPackage[]; Hold[Foo`Int] === Hold[Global`Int]'  # False
woxi eval 'BeginPackage["Foo`"]; Int::usage = "x"; EndPackage[]; Hold[Foo`Int] === Hold[Global`Int]'            # True

Foox === Globalx is False in both, but inside a compound expression Woxi's structural comparison sees the two symbols under their short name.

Plus orders a reciprocal monomial before a power of a sum

wolframscript -code 'ToString[1/x + Sqrt[1 - x^(-2)], InputForm]'  # Sqrt[1 - x^(-2)] + x^(-1)
woxi eval 'ToString[1/x + Sqrt[1 - x^(-2)], InputForm]'            # x^(-1) + Sqrt[1 - x^(-2)]

Only the reciprocal monomial diverges — x + Sqrt[1 - x^2] agrees, and the Times counterpart of the rule is implemented.

A string atom sorts before a product in Plus

"m" + 2 a is m + 2*a in WL and 2*a + m in Woxi. For symbols Woxi orders atom-versus-product correctly; strings live in a different ordering class that skips the name comparison. Strings inside Plus never fully evaluate, so this is pathological input — recorded only so the comparator is not touched for it.

x^2 + (a + b) x keeps Woxi's order

WL prints (a + b) x + x^2. The products-compare-from-the-last-factor rule is implemented for Sort and Order but not for the Plus comparator, which is the delicate one (it is not strictly transitive and its sort is wrapped in a panic guard with a string-key fallback).

A power of a function call sorts before everything

wolframscript -code 'Sort[{g[x]^2, x}]'   # {x, g[x]^2}
woxi eval 'Sort[{g[x]^2, x}]'             # {g[x]^2, x}

WL gives a Power its base's place in the order, so g[x]^2 sorts where g[x] does — after f[x] and after every bare symbol. Woxi's Power[g[x], 2] falls through to the generic "a function call sorts after atoms" rule and so leads unconditionally. All three comparators are affected (Sort, Order and the Plus one), which is why the divergence also shows up in printed sums: g[x]^2 + x*f[x] is x*f[x] + g[x]^2 in WL. A symbol base is unaffected (Sort[{y^2, x}] agrees, via the string fallback).

Surfaces in Variance[GompertzMakehamDistribution[l, x]], whose closed form holds both an E^x ExpIntegralEi[-x]^2 and an x HypergeometricPFQ[…] term: WL puts the ExpIntegralEi one first (E < H), Woxi the other. Value identical, display only — the unit test pins the value by substituting parameters and rounding.

A scaled sum is ordered by its last term, not its first

FunctionExpand[Gamma[0, z]] is -ExpIntegralEi[-z] + (-Log[-z^(-1)] + Log[-z])/2 - Log[z] in WL and (-Log[-z^(-1)] + Log[-z])/2 - ExpIntegralEi[-z] - Log[z] in Woxi — same three terms, different order. WL compares a c (p + q) term against its neighbours using the sum's last (greatest) element: Aa[z] + (Log[w] + Log[y])/2 keeps Aa[z] first because Log[y] > Aa[z], while ExpIntegralEi[-z] + (b + c)/2 puts the sum first because c < ExpIntegralEi[-z]. Woxi's Plus comparator always puts the scaled sum first, so it happens to agree with WL on the second shape and not the first. Adopting the last-element key means reworking compare_plus_terms's hybrid has_transcendental_subexpr branch, which currently keys on the earliest variable — the same delicate comparator the x^2 + (a + b) x entry above describes. Value identical, display only.

The negative-order incomplete gammas carry that same group, and WL regroups around it as well: FunctionExpand[Gamma[-2, z]] collects the elementary part over the single denominator E^z z^3 (-((-z/2 + z^2/2)/(E^z z^3)) + …) where Woxi leaves the two reciprocal powers apart, and FunctionExpand[ExpIntegralE[2, z]] distributes the leading z^(n-1) inward (E^(-z) - z (…)) where Woxi keeps the product. The half-integer orders regroup the same way once past the two WL writes bare — Gamma[1/2, z] and Gamma[3/2, z] match exactly, Gamma[-1/2, z] differs only in whether the leading -2 is distributed. All are value-identical — the unit tests check the Gamma[a, z] = (Gamma[a + 1, z] - z^a E^-z)/a recurrence and compare numerically against ExpIntegralE.

Nested sum-versus-sum factor order

(-1 + x)*(1 + (-2 + x)/2) — WL emits the more-nested factor first. Value identical, display only. Surfaced through InterpolatingPolynomial's Newton form.

Mixed sharing/non-sharing numerators over a shared denominator

(5 + x)/(3 + x) + b/(3 + x)^2 — WL puts the b term first, Woxi keeps the sharing term first. Same for a*x/(1 - x) + b/(1 - x)^2. The all-sharing and all-non-sharing cases are decoded and implemented.

c/x^2 + x^(-1) (a monomial rather than sum base) also diverges: WL puts c/x^2 first.

Together does not hoist a numerator monomial for multi-factor denominators

wolframscript -code 'ToString[Together[x/(1-x) + x/(2-x)], InputForm]'
# -((x*(-3 + 2*x))/((-2 + x)*(-1 + x)))
woxi eval 'Together[x/(1-x) + x/(2-x)]'
# keeps the numerator expanded: (3*x - 2*x^2)/(...)

Related: after a cancellation Woxi returns an expanded numerator and denominator where WL reconstructs from the surviving factors — Together[(x^2 (1+x))/((1-x) x)] is -((x (1+x))/(-1+x)) in WL. And Together[(x^2+x)/((x-1)(x+1))] stays put in Woxi where WL gives x/(-1+x).

Radical quotient canonicalization

WL folds integer coefficients into radicals of rationals and combines radical quotients where Woxi keeps them split:

wolframscript -code 'ToString[Numerator[Sqrt[11]/Sqrt[7]], InputForm]'
woxi eval 'Numerator[Sqrt[11]/Sqrt[7]]'

Concretely 3*Sqrt[11/3]Sqrt[33], Sqrt[30]/Sqrt[6]Sqrt[5], and Numerator[-3 Sqrt[16]/(Sqrt[2] Sqrt[11])] is -6 Sqrt[2] in WL against Woxi's -12. Woxi's Simplify is also non-confluent here: given the already-distributed sum, Simplify[-6 Sqrt[399] + 6 Sqrt[2261]] returns -6*(Sqrt[399] - Sqrt[2261]), while the product form it came from is fixed.

Sqrt[1/(2 Pi)] and constants outside a radical

Sqrt[1/(2 Pi)] stays as written instead of 1/Sqrt[2 Pi], and a constant sitting outside the radical does not fold in: Pi/(2 Sqrt[2 Pi]) stays where WL gives Sqrt[Pi/2]/2.

(1/2)^k is not rewritten to 2^(-k)

WL rewrites a positive unit-fraction base with a non-numeric exponent: (1/2)^k2^(-k), (1/2)^Pi2^(-Pi), (1/2)^(k+1)2^(-1-k). Woxi keeps the unit fraction. Non-unit numerators ((2/3)^k) and negative bases ((-1/2)^k) agree.

Doing it eagerly regresses BinomialDistribution's PDF, which relies on (1/2)^x*(1/2)^(10-x) merging to (1/2)^10; the rewrite has to happen after same-base Times merging, or the integer-base merge has to combine 2^a*2^b2^(a+b) for symbolic exponents.

Woxi also normalizes (3/11)^(2/3) to (9/121)^(1/3), where WL keeps the primitive base and even rewrites the other direction.

A complex coefficient blocks the negative-exponent fold

wolframscript -code 'ToString[TrigToExp[Sin[x]], InputForm]'   # …I/2/E^(I*x)…
woxi eval 'ToString[TrigToExp[Sin[x]], InputForm]'             # …I/2*E^(-I*x)…

Real, integer and symbolic coefficients all fold correctly (1/2 E^(-I x)1/(2*E^(I*x))); only a complex or imaginary coefficient fails. Coth and Csch are left unevaluated by TrigToExp for the related reason that their denominators carry a negative real E-power, which Woxi prints as -(1/E^x). ArcSin/ArcCos/ArcCsch/ArcSech are omitted because the Log-argument Plus order diverges.

Expand leaves rational coefficients unfolded

wolframscript -code 'ToString[Expand[(x - 1/2)(x + 1/2)], InputForm]'  # -1/4 + x^2
woxi eval 'Expand[(x - 1/2)(x + 1/2)]'                                # -1/4 - x/2 + x/2 + x^2

Within a term the folding is fixed; across terms it is not, because the sum-level combiner keys Rational[1,2]*x and Rational[-1,2]*x differently. Two smaller residues: Expand[-x/3] prints x*-1/3 where WL gives -1/3*x, and Expand[(a-b)^2/12]'s middle term prints (2*a*b)/12 instead of (a*b)/6.

Trig powers are not canonicalized to reciprocal functions

Wolfram rewrites 1/Sin[x]Csc[x], Cos[x]/Sin[x]Cot[x] and Sin[x]^2/Cos[x]Sin[x] Tan[x]. Woxi ships only the self-contained reciprocal-pair subset (Sin[x] Csc[x] → 1 and friends); lone reciprocals and the cross-function rewrites stay as written.

Simplify factors where wolframscript keeps an expanded quotient

Simplify[(-2 + x + 5x^2 + 2x^3)/(-5)] gives (-1/5)(1+x)(2+x)(-1+2x) in Woxi against WL's (2 - x - 5x^2 - 2x^3)/5. Wolfram also factors numerators of combined fractions further than Woxi does (collecting in x with factored coefficients), so Woxi keeps a sum of quotients where WL prints one fraction — Together alone matches exactly on the same inputs.

Simplify can be pathologically slow

Simplify[(z+1)*(1/(z+1) + PolyGamma[0,2] + (z+1)*(PolyGamma[0,2]^2 + PolyGamma[1,2]))/(z+1)] takes tens of seconds where wolframscript is instant. The time is spent re-evaluating the result tree after simplification completes, not inside Simplify itself.

Three-factor sign flip

(-1/3)*(-2+x)*y diverges because Woxi evaluates BinaryOp pairs left-associatively and flips the inner pair, while WL sees a flat Times. Needs parser-level Times flattening.

Eliminate clears denominators differently

Eliminate[{x + y == 1, x - y == 2}, y] is x == 3/2 in Woxi and 2*x == 3 in WL, which keeps integer coefficients.

Numeric precision and floating point

Inexact zero loses WL's accuracy tracking

wolframscript -code 'ToString[PascalBinomial[6.0, -2], InputForm]'  # 0``15.954589770191005
woxi eval 'PascalBinomial[6.0, -2]'                                 # 0.

Binomial[6.0, -2] has the same reference value but drifts further — Woxi returns an exact 0 there, losing the inexactness too. WL returns an arbitrary-precision zero whose accuracy is $MachinePrecision — not a machine 0., whose accuracy is 323.6. Reproducing it needs precision/accuracy tracking through the Gamma-ratio path starting from a machine-real argument.

Machine floats overflow where wolframscript promotes to arbitrary precision

wolframscript -code 'Exp[1000] // N'   # 1.97007111401704699388887935224`12.95…*^434
woxi eval 'Exp[1000] // N'             # Infinity

wolframscript -code '1.0*^308 * 10'    # 1.00000000000000001097906362944`15.95…*^309
woxi eval '1.0*^308 * 10'              # Infinity

Anything that grows past ~1.8*^308 is affected, so Exp/Power of a few hundred silently loses the answer rather than approximating it.

Overflow[] is not absorbing, and its head is not Real

Woxi returns Overflow[]/Underflow[] for a power past $MaxNumber, but the object itself is inert rather than WL's absorbing one:

wolframscript -code 'ToString[Overflow[] + 1, InputForm]'   # Overflow[]
woxi eval 'ToString[Overflow[] + 1, InputForm]'             # 1 + Overflow[]

wolframscript -code 'Head[Overflow[]]'                      # Real
woxi eval 'Head[Overflow[]]'                                # Overflow

Between 10^5 and the $MaxNumber boundary Woxi also keeps a rational power symbolic (2^(10^10/3) stays 2^(10000000000/3)) where WL materialises the extracted integer factor.

Positive and Sign underflow on a rational below the double range

wolframscript -code 'Positive[10^-400]'   # True
woxi eval 'Positive[10^-400]'             # False

wolframscript -code 'Sign[10^-400]'       # 1
woxi eval 'Sign[10^-400]'                 # 0

Power and the comparison operators compare such values exactly, but Positive, Negative and Sign still route through f64, where the value underflows to 0.0.

An accuracy-form literal's precision differs in the last digit

1.5``20 stores its precision as 20 + log10(1.5). Woxi computes that with f64::log10 and gets 20.17609125905568; wolframscript computes it at arbitrary precision and gets 20.176091259055685. Visible whenever the precision tag is printed. Accuracy[1.5] differs in the last ULP for the same reason.

A symbolic constant raised to a BigFloat exponent stays symbolic

Pi^0.520` staysPi^0.520., so N[Gamma[1/2], 20] comes out malformed and N[Gamma[3/2], 20] gives 0.520.*Pi^0.520.. Routing it through N[base, p] produces numbers but with a different precision tag (20.30 against WL's 20.46, which accounts for the base magnitude) and a last mantissa digit off by one — an arbitrary-precision rounding problem rather than a dispatch one. N[Zeta[3], 25] is a separate gap: there is no arbitrary-precision Zeta.

N[I] stays exact

N[I] is I in Woxi and 0. + 1.*I in wolframscript, so N[Gamma[I]] and N[LogGamma[I]] echo instead of numericizing. Fixing it needs scalar * (0. + c*I) to fold first, and that non-fold is deliberate: a pure imaginary product is left as the c*I monomial so it still merges inside an enclosing Plus. Both changes were tried and reverted.

BesselJ/PolyGamma at complex numeric arguments are separately unimplemented.

Complex[Real, Real] has the wrong head

wolframscript -code 'Head[Complex[0., 0.]]'   # Complex
woxi eval 'Head[Complex[0., 0.]]'             # Plus

Woxi has no dedicated Complex expression variant, so a real-real complex is rewritten to Plus[Real, Times[Real, I]] during evaluation. The printed string matches; the head does not.

Code that has to tell a complex with a zero imaginary part from a real must therefore go by "extracts as complex but not as a real" rather than by the head — see reject_non_real_base in math_ast/elementary.rs. Such a value also renders with an explicit * where WL's messages use a space:

wolframscript -code 'CubeRoot[2. + 0. I]'   # …The parameter 2. + 0. I should be…
woxi eval 'CubeRoot[2. + 0. I]'             # …The parameter 2. + 0.*I should be…

Binomial[n, real] carries Gamma-error noise

Binomial[10, 3.] is 119.99999999999987 in Woxi and 120. in WL; Binomial[100, 3.] is 161699.999999994 against 161700.0000000032. WL's exactness is unpredictable — it depends on the magnitudes of the Gamma arguments, not of the result, so Binomial[26, 13.] is f64-exact there and Binomial[27, 13.] is not. Returning the exact BigInt fixes the small cases and introduces new divergences wherever WL is itself inexact. Not reproducible.

Variance and friends differ in the last ULP

Variance[{1., 2., 3.}] is 1. in Woxi and 1.0000000000000002 in WL, whose accumulation matches none of the textbook formulas (Σ(x-m)²/(n-1), (Σx² − n m²)/(n-1), (n Σx² − (Σx)²)/(n(n-1)), mean-of-squared-deviations scaled, or Welford). Affects Variance, Correlation, Kurtosis, Cumulant and Variance of an Audio. Not reproducible.

PageRankCentrality[CycleGraph[4]] is {0.25000000000000006, …} against WL's clean {0.25, …} — power-iteration noise in the same class.

FresnelF and FresnelG lose accuracy through composition

Woxi composes them from FresnelS/FresnelC; wolframscript uses dedicated rational approximations. Real outputs differ in the last 1–2 digits for small |x|, and the cancellation as S, C → 1/2 degrades relative accuracy to ~1e-13 by x = 10. Not reproducible without WL's coefficients.

BesselI and BesselJ differ by 1–2 ULP

BesselI[0, 3] is …5025 in Woxi and …50235 in WL. BesselK and BesselY are bit-exact. Affects e.g. PDF[VonMisesDistribution[2, 3], 2.5]'s last digit.

Also missing from Woxi's numeric folding: Gamma[rational], so 2. + Gamma[1/3] stays symbolic where WL gives 5.357877069415496.

Arbitrary-precision Root padding

N[Root[#^3 - # - 1 &, 1], 10] prints ~40 digits in both engines, but only the first ~17 are the ones that were asked for; past that the two continuations disagree. Woxi's is the correct one (checked against the plastic constant to 40 digits) — wolframscript pads with digits of its own. Same for N[Root[#^4 - # - 1 &, 1], 15].

MidDate on sub-second instants

Woxi carries an instant as one f64 count of seconds since the epoch, whose resolution near 1.7e9 is ~2.4e-7 s, so averaging instants that carry fractional seconds differs from wolframscript's exact mean in the 8th decimal. A midpoint that lands on a whole second is reported exactly, as an integer, matching wolframscript.

Image filters accumulate in a different width

RecurrenceFilter and Sharpen on an Image differ from wolframscript by one Real32 ULP in single taps (0.7749999761581421 against 0.7750000357627869): Woxi accumulates in f64 and snaps the result to the stored Real32 pixel, wolframscript accumulates in Real32 throughout. Not reproducible without matching its accumulation order.

N does not push into Cos[real*Pi]

N[Cos[Pi/28], 8] leaves Cos[0.0357*Pi] unevaluated.

NIntegrate, NSum and NProduct accuracy

NIntegrate never matches wolframscript digit for digit on any input — test against the exact value with a tolerance, not against WL. Specific gaps:

  • NIntegrate[x^(-0.99), {x, 0, 1}] gives 30.86 for 100. The tail decays like distance^0.01, past what double precision can represent; wolframscript does not converge either (it warns ::ncvb and reports 100.0036). Not reproducible.
  • NIntegrate[1/Sqrt[x], {x, 0, 1}] is off by 1.8e-5.
  • NProduct on a slowly convergent infinite product is ~2e-6 off where WL is ~3e-11 (NProduct[1 + 1/n^2, {n, 1, Infinity}] → 3.676076175562187 against 3.6760779100585657). Finite products are exact. NSum is more accurate than WL.

Numeric optimization lands on different points

Wolfram's interior-point method answers FindMinimum[{x^2, x >= 1}, x] with 1.000000013282579 where Woxi returns the exact optimum, and NArgMax[{x + y, x^2 + y^2 <= 1}, {x, y}] with 0.7071076183816036. A three-variable equality constraint lands within 1.5e-8 in Woxi rather than exactly. Neither engine is wrong; bit-equality is not achievable.

wolframscript -code 'ToString[FindMaximum[{-((x - 2)^2 + (y - 3)^2), 0 < x < 10 && 0 < y < 10}, {{x, 5}, {y, 5}}], InputForm]'
# {1.7763568394002505*^-15, {x -> 2.0000000012019776, y -> 3.0000000006105285}}
woxi eval 'FindMaximum[{-((x - 2)^2 + (y - 3)^2), 0 < x < 10 && 0 < y < 10}, {{x, 5}, {y, 5}}]'
# {0., {x -> 2., y -> 3.}}

An objective the optimizer can only sample (a distribution's CDF, say) diverges the same way: FindMaximum[{CDF[NormalDistribution[], x - 3], 0 < x < 5}, {x, 1}] stops at x -> 4.999998363890543 in WL against Woxi's x -> 5.. Assertions over either have to round.

StepMonitor never fires for an unconstrained FindMinimum

Both engines report noopmon and monitor nothing for the constrained {f, cons} form, but WL does fire the monitor once per step of the unconstrained one, where Woxi's Newton iteration ignores it entirely:

wolframscript -code 'Length[Reap[FindMinimum[(x - 2)^2, {x, 5}, StepMonitor :> Sow[x]]][[2]]]'  # 1
woxi eval 'Length[Reap[FindMinimum[(x - 2)^2, {x, 5}, StepMonitor :> Sow[x]]][[2]]]'            # 0

Matching it needs Woxi's iteration count to match WL's as well, not just the plumbing.

Erf's last ULP, and the far tail of a correlated normal CDF

Erf[0.7071067811865475] is 0.6826894921370859 in WL (correctly rounded) and 0.6826894921370857 here, so anything built on it — Erfc, a normal CDF, CDF[MultinormalDistribution[…]] — can differ in the last printed digit. Woxi's bivariate normal CDF (Genz's algorithm) otherwise agrees to about 15 digits, but its relative error grows in the far tail, where WL carries extra internal precision: CDF[MultinormalDistribution[{0, 0}, {{1., 0.2}, {0.2, 1.}}], {-3., 1.}] is 0.001288249693033831 in WL against 0.001288249693033744 here.

FindRoot options and damping schedule

WorkingPrecision, AccuracyGoal and PrecisionGoal are ignored. WL's answer under WorkingPrecision carries iteration residue past the requested precision, so matching it needs its exact iteration and rounding schedule. Its damping schedule for an oscillating Newton also differs (FindRoot[x^3 - 2x + 2, {x, 0}, MaxIterations -> 4] is 0.816037292480469 in WL, 0.8142908074786004 here), and the three-argument secant form differs in the last digit.

FindRoot with two starting points keeps WL's iteration residue

A two-point spec ({x, x0, x1}) makes WL build its Jacobian from the secant through the two points rather than from a derivative, and stop as soon as the step is under tolerance — so the answer carries a couple of ULP of that Jacobian's error even when the exact root is representable:

wolframscript -code 'FindRoot[{x + y - 1 == 0, x - y - 0.5 == 0}, {x, 0.1, 0.2}, {y, 0.1, 0.2}]'
# {x -> 0.7500000000000002, y -> 0.2500000000000001}
woxi eval 'FindRoot[{x + y - 1 == 0, x - y - 0.5 == 0}, {x, 0.1, 0.2}, {y, 0.1, 0.2}]'
# {x -> 0.75, y -> 0.25}

The secant slope of x + y - 1 between x = 0.1 and x = 0.2 is 1.0000000000000009, not 1, which is exactly where WL's two low bits come from; Woxi uses the symbolic Jacobian and lands on the exact root. The same shows up on FindRoot[5 == 50*x^0.6, {x, 0.001, 0.9}] (0.021544346900318832 in WL, 0.021544346900318825 here) and on an opaque _?NumericQ system, where Woxi's Broyden iterate is 1 ULP off WL's. Matching it means reproducing WL's Broyden update, line search and convergence test bit for bit — and being less accurate than the exact root.

Arbitrary-precision Tanh, Erf and Gamma carry a different precision tag

Woxi propagates a precision tag through a transcendental with the first-order relative condition number |x f'(x) / f(x)|, which is the rule WL's own Sin/Cos/Sinh/ArcTan markers match to ~15 digits. Four functions do not follow it in WL:

wolframscript -code 'ToString[Tanh[N[1/2, 20]], InputForm]'
# 0.46211715726000975850231848364367254873`19.881723602858166
woxi eval 'ToString[Tanh[N[1/2, 20]], InputForm]'
# 0.46211715726000975850231848364367254873`20.070112223892355

The values agree; only the tag differs. Two things cause it. The smaller one is arithmetic: Woxi evaluates the condition number and its logarithm in f64, so even where the rule is WL's the tag differs in the last digit or two (Cosh[N[1/2, 20]] is 20.636277902572626here against20.636277902572633), exactly as for the accuracy-form literal above. The larger one is the rule itself: WL's Tanh tag tracks whichever internal route it took (it matches the condition number at x = 1 and the Sinh/Cosh quotient's error sum at x = 1/2), and its Erf/Erfc tags report its own algorithm's error, not the conditioning — at x = 1/2 it claims 19.02 digits and its 21st digit onward is already wrong, where Woxi's value is correct to the last digit shown. Gamma/LogGamma differ in the third decimal of the tag for the same reason. Not reproducible.

Display digits past the claimed precision differ

An arbitrary-precision number prints more digits than its precision tag claims, and the surplus ones are not required to agree:

wolframscript -code 'ToString[N[Coth[1], 20], InputForm]'
# 1.31303528549933130363616124693084783292`20.
woxi eval 'ToString[N[Coth[1], 20], InputForm]'
# 1.31303528549933130363616124693084783291`20.

coth(1) is 1.313035285499331303636161246930847832912…, so Woxi's digit is the correctly rounded one. Cot at 40 digits is the same story.

Power of two arbitrary-precision numbers keeps too many digits

wolframscript -code 'ToString[SetPrecision[Sqrt[2], 5], InputForm]'
# 1.4142135623730950488`5.301029995663981
woxi eval 'ToString[SetPrecision[Sqrt[2], 5], InputForm]'
# 1.41421356237309504880168872420969807857`5.301029995663981

Power computes at the operands' bit budget and does not re-truncate the decimal string to the digit count the result's precision tier calls for, the way N[…, p] does. A machine-real exponent is also not contagious: N[2, 20]^0.5 is 1.4142135623730951 (machine) in WL and an arbitrary-precision number here.

Cross-platform libm differences

The last ULP of atanh, acos, asinh, tan, tanh and friends differs between macOS and Linux, so a full-precision string assertion is platform-dependent: Cot[0.3] is 3.232728143765828 on macOS and 3.2327281437658275 on the Linux of CI, and Coth[0.8] ends in 66 versus 63. The same 1-ULP FMA difference flips a single 8-bit colour channel in ComplexPlot domain-colouring output at an exact x.5 boundary.

A test that cares about the last bit therefore has to assert the property rather than the digits: compare the reciprocal against the reciprocal expression (Cot[x] vs 1/Tan[x], both evaluated by Woxi, so both carry the same platform rounding) and pin the magnitude with a scaled integer (Round[1000*Cot[x]]). See reciprocal_trig_last_bit in tests/interpreter_tests/math/numeric.rs.

Cot[-0.7] is the third argument of that group: -1.1872418321266796 on macOS against -1.1872418321266793 here. Neither library is the accurate one throughout — against the correctly rounded value glibc wins at Cot[0.3], macOS at Cot[-0.7] and Coth[0.8] — so a divergence here is the libm's last bit and never a formula to correct.

Algebra and calculus

Together ignores an inexact exponent's sign

wolframscript -code 'ToString[{Together[E^(-0.5*t) + 1], Together[x^(-1.5) + 1]}, InputForm]'
# {(1 + E^(0.5*t))/E^(0.5*t), (1 + x^1.5)/x^1.5}
woxi eval '{Together[E^(-0.5*t) + 1], Together[x^(-1.5) + 1]}'
# {1 + E^(-0.5*t), 1 + x^(-1.5)}

An exact negative exponent is a denominator on both sides (Together[E^(-t) + E^t] is (1 + E^(2*t))/E^t in each), and Woxi's common-denominator arithmetic keeps the LCM of exponents that are rational multiples of one symbol (E^(-t) + E^(-2*t) needs E^(2*t), never E^(3*t)). An inexact one is where they part: Woxi leaves it in the numerator, since its exponent arithmetic is exact-rational. wolframscript itself is not consistent about this — Denominator[x^(-1.5)] is 1 there even though Together[x^(-1.5) + 1] puts x^1.5 underneath.

A quotient with a symbolic negative exponent is split, not held

wolframscript -code 'ToString[{Cancel[(k/x)^(-a)], Simplify[(a*k^a*x^(-1 - a))/(k/x)^a]}, InputForm]'
# {(k/x)^(-a), (a*k^a*x^(-1 - a))/(k/x)^a}
woxi eval '{Cancel[(k/x)^(-a)], Simplify[(a*k^a*x^(-1 - a))/(k/x)^a]}'
# {x^a/k^a, a/x}

Woxi reads (p/q)^(-a) as q^a/p^a, which lets its cancellation see through the power — the reason HazardFunction[ParetoDistribution[k, a], x] reduces to a/x here and stays a ratio of powers there. Both forms are correct; Woxi's is the simplified one.

An antiderivative's common denominator is collected, not distributed

wolframscript -code 'ToString[Integrate[x^2/(2*x^2 + 1), x], InputForm]'
# x/2 - ArcTan[Sqrt[2]*x]/(2*Sqrt[2])
woxi eval 'Integrate[x^2/(2*x^2 + 1), x]'
# (x - ArcTan[Sqrt[2]*x]/Sqrt[2])/2

Same value, different common-denominator convention. wolframscript divides over the rationals from the start; Woxi pseudo-divides over the integers and takes the factor back out at the end, and integrate_ast's simplify pass re-collects it even when the rule distributes it. wolframscript is not consistent about this either — it collects Integrate[x^4/(3*x^2 + 1), x] into (-3*x + 3*x^3 + Sqrt[3]*ArcTan[Sqrt[3]*x])/27 and distributes the neighbouring cases — so there is no rule here to copy.

The same convention shows up whenever the result is a Plus with a shared numeric content: Integrate[(2 + 2*x)/(-3 + 3*x - 5*x^2), x] is -2*((13*ArcTan[…])/(5*Sqrt[51]) + Log[3 - 3*x + 5*x^2]/10) there and (-26*ArcTan[…])/(5*Sqrt[51]) - Log[3 - 3*x + 5*x^2]/5 here.

Integrate[f[a*x + b], x] is not angle-expanded for Sin and Cos

wolframscript -code 'ToString[Integrate[Sin[x + y], x], InputForm]'
# -(Cos[x]*Cos[y]) + Sin[x]*Sin[y]
woxi eval 'Integrate[Sin[x + y], x]'
# -Cos[x + y]

Woxi integrates a shifted argument by substitution, so the antiderivative keeps the argument it was given. wolframscript applies the angle-sum identity to the Sin/Cos results — but not to Tan, Cot or Sec, which agree, and not to Integrate[Sin[2*x + 1], x], which it leaves as -1/2*Cos[1 + 2*x] while expanding Integrate[Sin[x + 1], x]. The same applies to Integrate[Log[2*x + 3], x] and Integrate[ArcTan[2*x + 1], x], where wolframscript expands the product and drops the resulting constant.

Integrate[Log[Sin[x]], …] is unimplemented

wolframscript -code 'ToString[Integrate[Log[Sin[x]], {x, 0, 1}], InputForm]'
# (-1/12*I)*(6 + (-6 + Pi)*Pi - (12*I)*Log[2] - 6*PolyLog[2, E^(2*I)])
woxi eval 'Integrate[Log[Sin[x]], {x, 0, 1}]'
# Integrate[Log[Sin[x]], {x, 0, 1}]

The antiderivatives are known:

Integrate[Log[Sin[x]], x] ==
  -(x*Log[1 - E^((2*I)*x)]) + x*Log[Sin[x]] + (I/2)*(x^2 + PolyLog[2, E^((2*I)*x)])
Integrate[Log[Cos[x]], x] ==
  (I/2)*x^2 - x*Log[1 + E^((2*I)*x)] + x*Log[Cos[x]] + (I/2)*PolyLog[2, -E^((2*I)*x)]

The limit at x -> 0 contributes I Pi^2/12. The blocker is not the value but the printed form: WL's answer is Simplify-collected, and Woxi does not land on that grouping.

Integrate[Sqrt[a ± x^2], x] stays unevaluated

The antiderivative exists in Woxi already (the definite path uses it), and the values verify — but the printer reorders x*Sqrt[1-x^2] to Sqrt[1-x^2]*x whenever the radical contains the integration variable, and the Plus term order flips too. WL keeps (x*Sqrt[1-x^2] + ArcSin[x])/2.

Irreducible-quartic rational integrals

Integrate[1/(1+x^4), x] and its family stay unevaluated: the integrator does not partial-fraction over quadratics, and WL's output uses ArcTan-reflection forms (ArcTan[2/x] against ArcTan[x/2]) that would diverge anyway.

Also still unevaluated: Integrate[x Log[x]^2, x], and Integrate[Log[3 x], {x, 1, 2}] gives -1 - Log[3] + 2 Log[6] where WL combines to -1 + Log[12].

Integrate under assumptions

Integrate[Exp[-x t], {t, 0, Infinity}, Assumptions -> x > 0] gives x^(-1) - 1/(E^(Infinity*x)*x) instead of 1/x — the boundary is an infinite limit, not a refinement target. Without assumptions Integrate[x^n, {x, 0, 1}] is (1+n)^(-1) - 0^(1+n)/(1+n) where WL emits ConditionalExpression[(1+n)^-1, Re[n] > -1], a form Woxi does not build. Sin[a x]/x and one-sided 1/x^p improper integrals stay unevaluated. The Gaussian Integrate[Exp[-a x^2], {x, -Infinity, Infinity}] gives Sqrt[Pi/a] against WL's Sqrt[Pi]/Sqrt[a].

LaplaceTransform[t^n f(t), t, s] is unimplemented

The rule L[t^n f] = (-1)^n d^n/ds^n L[f] is straightforward and the values come out right, but WL canonicalizes an even power of a binomial by flipping the base sign — (s-a)^(-2) prints as (a-s)^(-2) — keeps (-1+s^2)^2 where Woxi factors, and leaves an un-combined sum after the derivative. Plain Sin/Cos/Exp/t^n transforms all match, as does the s-shifting theorem L[E^(c t) g(t)]; Cosh/Sinh integrands are excluded from the shift because WL canonicalizes the resulting difference-of-squares denominator inconsistently (factored for Cosh, expanded for Sinh).

InverseLaplaceTransform of a complex-pole rational function

A proper rational function with exact coefficients is inverted exactly, off its partial-fraction decomposition, and the residue sum is regrouped as E^(k_min t) times a polynomial in E^t — which reproduces WL for real poles (1/((s+1)(s+2)(s+3))(-1 + E^t)^2/(2 E^(3 t)), repeated poles included). A complex-conjugate pole pair diverges in form only: Woxi returns the real damped oscillation, WL a sum of complex exponentials.

wolframscript -code 'InverseLaplaceTransform[1/(s^3 + 2 s^2 + 5 s), s, t]'
# 1/5 + ((I/20)*((-1 + 2*I) + (1 + 2*I)*E^((4*I)*t)))/E^((1 + 2*I)*t)
woxi eval 'InverseLaplaceTransform[1/(s^3 + 2 s^2 + 5 s), s, t]'
# -1/10*(-2*E^t + 2*Cos[2*t] + Sin[2*t])/E^t

Both are the same function (1/5 - E^-t (Cos[2t]/5 + Sin[2t]/10)). WL's choice is its Simplify acting on complex residues, and it is not consistent — (s+2)/((s+1)(s^2+4)) comes back in the real Cos/Sin form, 1/(s^2+2 s+5) in the complex-exponential one. Simplify on the same input agrees with WL, so the divergence is in what each side feeds it. A real-pole numerator can also land on a different-but-tied form: s/((s+1)(s+2)(s+3)) factors the polynomial in E^t where WL keeps it expanded (both cost the same by WL's own SimplifyCount).

An improper fraction's DiracDelta derivative term also sorts differently inside the Plus: InverseLaplaceTransform[s^2/(s+1), s, t] is Derivative[1][DiracDelta][t] + E^(-t) - DiracDelta[t] where WL puts the Derivative term last. Same terms, canonical-order divergence for a curried head.

Fourier*Series are unimplemented

FourierSinSeries, FourierCosSeries, FourierTrigSeries and FourierSeries echo. The coefficients are easy; WL's output form is unpredictably factored — FourierTrigSeries[x, x, 3] is expanded, FourierTrigSeries[x^2, x, 2] is factored as Pi^2/3 + 4*(-Cos[x] + Cos[2*x]/4), and the same expression factors differently across the Sin/Cos/Trig variants.

Simplify expands a numerator WL keeps factored per coefficient

# Simplify[InterpolatingPolynomial[{{0,1},…,{5,q},…,{10,321}}, x]]
wolframscript   # (14400 + 5760*(-10831 + 126*q)*x - … - (-86 + q)*x^10)/14400
woxi eval       # (14400 - 62386560*x + 725760*q*x + … - q*x^10)/14400

Same value and same common denominator, but WL pulls the repeated linear factor (-86 + q) out of each coefficient where Woxi leaves the numerator expanded. Factor on the same input returns the expanded form too, so the missing step is recognizing a shared factor across coefficients rather than anything about Simplify itself.

Factor with GaussianIntegers, Extension or Trig

Recognized as valid options but the call stays unevaluated; WL factors x^2+1 into (-I + x)*(I + x). Same for FactorList and IrreduciblePolynomialQ. Composite-modulus LinearSolve, n-ary modular PolynomialLCM and composite-modulus GroebnerBasis are also left unevaluated (prime moduli work).

PolynomialGCD is wrong for multivariate input

wolframscript -code 'PolynomialGCD[x*y, y]'   # y
woxi eval 'PolynomialGCD[x*y, y]'             # 1

wolframscript -code 'PolynomialGCD[2 x y, x^2]'  # x
woxi eval 'PolynomialGCD[2 x y, x^2]'            # 2*x*y

The third argument of the 3-argument form is effectively ignored. Multivariate GCD needs a real algorithm (subresultant PRS over a polynomial ring, or a modular method); the current implementation is univariate only. This blocks multivariate SquareFreeQ, which stays unevaluated for the same reason.

Apart residues

Two shapes remain: a missing polynomial division when the leading denominator coefficient is negative (1/4 + (9+17x+23x^2)/(4*(3-5x-3x^2+4x^3))), and a 0 + expr artifact when the polynomial quotient is zero.

PartialFractions is unimplemented

WL's PartialFractions[expr, x] (unlike Apart) always splits to linear factors over the algebraic closure, so an irreducible quadratic produces (-1)^(1/3), Sqrt and three-argument Root coefficient forms. Only the rational-factorable case coincides with Apart. Matching the coefficient forms needs the roots-of-unity and Root display pipeline.

Series[x!, {x, 0, n}] coefficient form is order dependent

The x^2 coefficient is WL-canonical (6*EulerGamma^2 + Pi^2)/12 when it is the last one and re-folds to (EulerGamma^2 + Pi^2/6)/2 once it becomes interior; the x^3 coefficient diverges similarly. Value correct, form only.

Symbolic Sum is limited to monomials from 1

Non-unit lower bounds, linearity (Sum[2k, …], Sum[k^2+k, …]) and an explicit step with a symbolic bound are all unevaluated or refused. A general Faulhaber engine is mathematically straightforward and was written, but its output form does not match: Sum[k+1, {k, 1, n}] is (3*n + n^2)/2 in WL, while Woxi's Simplify gives (n*(3+n))/2, and factor ordering diverges ((1+Floor[n/2])*Floor[n/2]).

Clean finite-sum targets still unevaluated: Sum[k Binomial[n,k], {k,0,n}] = 2^(-1+n) n, Sum[k^2 Binomial[n,k], …] = 2^(-2+n) n (1+n), Sum[k 2^k, {k,1,n}] = 2(1-2^n+2^n n), and Sum[Cos[k x], {k,1,n}]. Sum[(-1)^(2n)/n^2] also fails, because (-1)^(2n) is not simplified to 1.

Infinite-sum gaps

  • Rational sums whose residues do not cancel per class have transcendental values Woxi cannot produce: Sum[1/(9n^2-1)] = (9 - Sqrt[3] Pi)/18, Sum[1/(n(2n-1))] = Log[4]. Needs PolyGamma at rational arguments.
  • PolyLog sums with n^k, k ≥ 2: Sum[1/(2^k k^2)] = Pi^2/12 - Log[2]^2/2.
  • Divergence messages: Sum[2^n/n] and Sum[1/(n Log[n])] are silent in Woxi where WL reports. WL's own boundary is inconsistent here (bare 2^n silent, 2^n/n messaged).
  • Product[R(n), {n, n0, Infinity}] handles integer roots only; rational roots (Product[1-1/(4n^2)] = 2/Pi) would need Gamma at half-integers.

Sum/Product unevaluated echoes keep the source order

Product[-n, …] echoes as 0 - n (a parse artifact) and Woxi keeps the source Plus order (n+2) where WL canonicalizes the held body (2+n).

Limit returns wrong values for factorial growth

wolframscript -code 'Limit[n!^(1/n), n -> Infinity]'      # Infinity
woxi eval 'Limit[n!^(1/n), n -> Infinity]'                # 1

wolframscript -code 'Limit[n!^(1/n)/n, n -> Infinity]'    # 1/E
woxi eval 'Limit[n!^(1/n)/n, n -> Infinity]'              # 0

wolframscript -code 'Limit[Log[n!]/n, n -> Infinity]'     # Infinity
woxi eval 'Limit[Log[n!]/n, n -> Infinity]'               # 0

Also Limit[LogGamma[n]/n] (WL Infinity, Woxi 0) and Limit[(2n)!/n!] (WL Infinity, Woxi 1). The engine applies a base^(1/n) → 1 / Log[g]/n → 0 shortcut that only holds for polynomial growth. This is the one place in Limit that returns a wrong value rather than an unevaluated call — everything else below only fails to evaluate.

Three Limit shapes at infinity stay unevaluated

wolframscript -code 'Limit[x^100/Exp[x], x -> Infinity]'        # 0
woxi eval 'Limit[x^100/Exp[x], x -> Infinity]'                  # Limit[x^100/E^x, x -> Infinity]

wolframscript -code 'Limit[Log[Log[x]]/Log[x], x -> Infinity]'  # 0
woxi eval 'Limit[Log[Log[x]]/Log[x], x -> Infinity]'            # Limit[…] unevaluated

wolframscript -code 'Limit[x/(x + Sqrt[x]), x -> -Infinity]'    # 1
woxi eval 'Limit[x/(x + Sqrt[x]), x -> -Infinity]'              # Limit[…] unevaluated

Each has its own cause. A polynomial over an exponential resolves up to about degree 45 and then hits the L'Hôpital depth guard. Nested logarithms need a u = Log[x] substitution — Limit[Log[u]/u, u -> Infinity] is already 0. The -Infinity case is excluded on purpose: the leading-order analysis is gated to +Infinity because x^p for non-integer p is not real to the left of zero, and extending it needs branch-cut care.

Consequences: AsymptoticGreater[Exp[x], x^100, …] and AsymptoticLess[Log[Log[x]], Log[x], …] stay unevaluated too.

Other open limits: Limit[Sin[1/x], x -> 0] should be Indeterminate, Limit[HarmonicNumber[n]/Log[n]] should be 1, and Limit[HarmonicNumber[2n] - HarmonicNumber[n]] should be Log[2]. Limit[Binomial[2n,n], n -> Infinity], Limit[1/Binomial[2n,n]] and Limit[Binomial[2n,n]^(1/n)] hang (the numeric probe evaluates Binomial at n = 10^6), as does the literal-Sum spelling of the harmonic-minus-log limit.

Residue and Simplify around Gamma poles

A Gamma pole model multiplied by another pole at a non-zero point — Residue[Gamma[z]/(z+1), {z, -1}], WL -1 + EulerGamma — is refused rather than risk the Simplify slowness above.

RSolve first-order linear inhomogeneous

RSolve[a[n] == c a[n-1] + d && a[k0] == v0, a[n], n] with constant c ≠ 1 returns unevaluated. The closed form is trivial; WL's display varies case by case through power folding, with no clean subset:

recurrence wolframscript
c=2, d=1, a[0]=2 -1 + 3*2^n
c=2, d=1, a[0]=3 -1 + 2^(2 + n) (the coefficient folded into the exponent)
c=2, d=1, a[0]=4 -1 + 5*2^n
c=2, d=5, a[0]=0 5*(-1 + 2^n) (common factor pulled out)
c=3, d=2, a[0]=5 -1 + 2*3^(1 + n) (partial fold)
c=5, d=3, a[0]=10 (-3 + 43*5^n)/4

RSolve of the logistic map at r=2 is deliberately left unevaluated for the same reason: WL displays an internally rewritten rational form ((-2^2^(1+n) + 5^2^n)/(2*5^2^n)) rather than the clean (1 - (1-2c)^2^n)/2.

Casoratian is unimplemented

The semantics are fully decoded, but every form needs exponential collapse Woxi's Simplify lacks ({2^n, 3^n}6^n, {n!, 2^n} → a Gamma form, {Sin[n], Cos[n]}-Sin[1]), and WL is internally inconsistent ({2^n,3^n,5^n}5^n*6^(1+n), not 6*30^n). Only the first-order-system form (Det[A]^n) is form-stable.

FindGeneratingFunction is unimplemented

Not a clean rational fit: {1,1,1,1,1} gives a Padé artifact, {1×7} and {1×10} give different families of answer, and the display switches between raw power and Together form. Not reproducible.

MultipleZeta reduction

WL reduces multiple zeta values through at least weight 8 using MultipleZeta[{5,3}] as a basis element. Depth 1, leading 1 and the empty argument are easy; matching the reduction extent needs full MZV datamine tables.

Higher-rank Curl

Scalar in 3D, vector in 4D and rank-2 tensor in 3D — the valid higher-rank antisymmetric forms — stay unevaluated. WL returns them as SymmetrizedArray (or a collapsed 0). Needs exterior derivative plus Hodge dual.

A coupled ODE system is solved numerically, so its constants are inexact

DSolve[{eq1, …}, {y1[t], …}, t] eliminates the system with an operator determinant and finds the characteristic roots in machine floating point, recovering exact-looking coefficients (1/2, Sqrt[3]) from them at the end. For a first-order homogeneous system the answer is re-parametrized to Y(t) = MatrixExp[A·t]·C — wolframscript's basis, in which C[k] is the initial value of the k-th variable — and matches it term for term, secular t·E^(λt) terms for defective systems included. Three shapes still differ:

  • Coefficients that are not a small rational or the square root of one stay as machine reals, and a radical that both a numerator and a denominator carry is not cancelled ((Sqrt[3]*E^(2*t) − …)/(3*Sqrt[3]*E^t) where WL writes (E^((3*t)/2) − …)/(3*E^(t/2))).
  • A system with inexact coefficients (a Foucault-pendulum-style rotational coupling with a 0.1 rate) gets a clean trigonometric answer here. WL's own is an unsimplified tangle of 4.4*^-17-scale complex noise, so there is no form to match.
  • A system with nonzero constant forcing gets its constant particular solution plus the eigenvector basis, where WL emits an unsimplified variation-of-parameters expression in its MatrixExp basis.

Second-order PDEs with a lower-order term

DSolve[u_xx + u_y == 0] (the heat equation) is unevaluated; WL falls back to a particular solution with C[1] … C[8] after DSolve::lpdeprtclr. With a == 0 or c == 0 WL also writes the characteristics unnormalised (C[1][x - y] + C[2][x]) rather than as λ x + y.

NDSolve's PDE branch is 1-D

This entry used to say "NDSolve covers ODEs only" — no longer accurate. 1-D parabolic PDEs (single or coupled reaction-diffusion-convection systems, Dirichlet or Neumann boundaries) solve via the method of lines:

NDSolve[{D[u[x,t],t] == D[u[x,t],x,x], u[x,0] == Sin[Pi x],
         u[0,t] == 0, u[1,t] == 0}, u, {x,0,1}, {t,0,1}]

returns a real InterpolatingFunction. A second-order-in-time (hyperbolic/wave) evolution equation — D[u[t,x],t,t] == …, needing an extra initial velocity condition D[u,t][t0,x] == g[x] alongside the ordinary initial value — is order-reduced to the first-order system D[u,t] == v, D[v,t] == w and solved the same way, even when the right-hand side itself contains an implicit/mixed derivative of the unknown (e.g. D[u[t,x],x,x,t,t]): the acceleration field w is eliminated by a tridiagonal solve each method-of-lines step rather than an explicit formula, so long as the right-hand side is linear in it. This is what a Wolfram Demonstration like A Passive Cochlear Model needs, and Woxi Studio can now render its Manipulate. What's still missing:

  • The hyperbolic branch only supports a single dependent function — no coupled hyperbolic systems (the parabolic branch supports coupled systems).
  • NeumannValue and DirichletCondition exist only as symbols; boundary conditions must be written as plain equalities.

On the symbolic side DSolve recognises three first-order two-variable PDE shapes; Laplace, which WL solves as C[1][I x + y] + C[2][-I x + y], is not among them.

NDSolve's DAE support handles an index-1 constraint that solves explicitly for one unknown; quadratic, coupled or index ≥ 2 constraints, and constraints whose unknown is also differentiated, are not covered.

NDSolve uses a fixed grid

Woxi integrates on 1000 nominal RK4 steps with bisection refinement; wolframscript adapts. Length[(y /. s[[1]])[[2]]] is 1001 against 13. Note that stock wolframscript's default tolerances can be less accurate than Woxi's here — get a converged reference before treating a difference as a bug.

Reduce does not eliminate quantifiers

wolframscript -code 'Reduce[Exists[y, x == y^2], x, Reals]'      # x >= 0
woxi eval 'Reduce[Exists[y, x == y^2], x, Reals]'                # Reduce[Exists[y, x == y^2], x]

wolframscript -code 'Reduce[ForAll[y, x + y^2 >= x], x, Reals]'  # True
woxi eval 'Reduce[ForAll[y, x + y^2 >= x], x, Reals]'            # Reduce[ForAll[y, x + y^2 >= x], x]

Exists and ForAll are parsed but never eliminated. Note the unevaluated form also drops the Reals domain argument, which is a separate bug in the echo path.

Other Reduce and FindInstance divergences

  • Complex-domain Abs is a wrong answer: Reduce[Abs[x] < 3, x] returns False although solutions exist. WL gives a Re/Im decomposition, since the default domain is Complexes.
  • Multi-symbol parametric coefficients: Reduce[a b x == c, x] gives an incomplete x == c/(a*b) where WL enumerates three sub-cases. The single-symbol case is implemented, and only for the default domain.
  • The two-argument x ∈ Integers membership form does not enumerate; the three-argument Reduce[…, x, Integers] form does.
  • Disjunct ordering: Reduce[x > 0 || x < -1, x] — WL sorts the regions, Woxi keeps input order.
  • Reduce[Sin[x] == 1, x] — WL puts the Element condition as a top-level And, Woxi wraps it in ConditionalExpression.
  • Reduce[…, Modulus -> n] ignores the modulus for a multivariate nonlinear system (univariate and linear systems work). Solve refuses that case rather than answer wrongly.
  • FindInstance picks a different, still-valid instance: x^2 == 2 gives Sqrt[2] against WL's -Sqrt[2]; x^2 + y^2 == 1 over the reals gives {x -> -1, y -> 0} against {x -> 1, y -> 0}; x^2 > 4 gives -100 against -4. Not reproducible.
  • FindInstance over the integers falls back to a bounded search when Solve cannot decide: every variable is walked outwards from zero (0, 1, −1, 2, −2, …) within a fixed evaluation budget. That reproduces wolframscript's answer for the small instances (x^5 + y^5 + z^5 == w^5 && x > 0{1, 0, 0, 1}) but cannot reach a large one — Euler's sum-of-powers counterexample 27^5 + 84^5 + 110^5 + 133^5 == 144^5 stays unevaluated where wolframscript finds it.

Reduce over Rationals omits the Element conjunct

Only a discrete solution set stands on its own over the rationals; an interval has to say that just its rational points are meant, and wolframscript writes that as a leading Element. Woxi drops it, and the surrounding rational surface diverges with it:

wolframscript -code 'Reduce[x >= 2, x, Rationals]'       # Element[x, Rationals] && x >= 2
woxi eval 'Reduce[x >= 2, x, Rationals]'                 # x >= 2

wolframscript -code 'Reduce[True, x, Rationals]'         # Element[x, Rationals]
woxi eval 'Reduce[True, x, Rationals]'                   # True

wolframscript -code 'Reduce[x != 2, x, Rationals]'       # Element[x, Rationals] && (x < 2 || x > 2)
woxi eval 'Reduce[x != 2, x, Rationals]'                 # x != 2

wolframscript -code 'Reduce[x >= 2 && x <= 2, x, Rationals]'  # x == 2
woxi eval 'Reduce[x >= 2 && x <= 2, x, Rationals]'            # 2 <= x <= 2

Several targets share one Element over an Alternatives, which the integer domain gets wrong the same way even though it does emit the conjunct:

wolframscript -code 'Reduce[x > 2 && y < 1, {x, y}, Integers]'
# Element[x | y, Integers] && x >= 3 && y <= 0
woxi eval 'Reduce[x > 2 && y < 1, {x, y}, Integers]'
# Element[x, Integers] && Element[y, Integers] && y <= 0 && x >= 3

The curated oracle case Reduce[1/3 < x && x <= 5/7, x, Rationals] in tests/reduce/oracle.rs is the recorded instance; it is the one failure make test-slow reports on a machine with z3 installed and network access.

Reduce leaves a joint multi-variable constraint unevaluated

Constraints that share a variable are reduced together, and the joint case is only implemented for what the linear engines cover. A shared-variable group that needs a cylindrical decomposition stays unevaluated:

wolframscript -code 'Reduce[a < b && b < 2 && x > 0, x]'
# a < 2 && Inequality[a, Less, b, Less, 2] && x > 0
woxi eval 'Reduce[a < b && b < 2 && x > 0, x]'
# Reduce[a < b && b < 2, {a, b}] && x > 0

wolframscript -code 'Reduce[a x > 0, x]'    # (a < 0 && x < 0) || (a > 0 && x > 0)
woxi eval 'Reduce[a x > 0, x]'              # Reduce[a*x + 0 > 0, x]

Independent constraints are separated and reduced one group at a time, so Reduce[x > 1 && a^2 < 4 && b^2 < 9, x] matches.

Solve over a system of two Abs equations gives no solutions

wolframscript -code 'Solve[{Abs[x] == 2, Abs[y] == 3}, {x, y}]'
# {{x -> -2, y -> -3}, {x -> 2, y -> -3}, {x -> -2, y -> 3}, {x -> 2, y -> 3}}
woxi eval 'Solve[{Abs[x] == 2, Abs[y] == 3}, {x, y}]'
# {}

One Abs equation is solved correctly, and a single Abs equation narrowed by an inequality is too. It is the multi-variable elimination that cannot take two of them apart and reports the system unsatisfiable, so ToRules turns False into an empty list.

Solve orders the negative root first in a two-variable system

wolframscript -code 'Solve[{x^2 + y^2 == 1, y == 0}, {x, y}]'
# {{x -> 1, y -> 0}, {x -> -1, y -> 0}}
woxi eval 'Solve[{x^2 + y^2 == 1, y == 0}, {x, y}]'
# {{x -> -1, y -> 0}, {x -> 1, y -> 0}}

The single-variable case agrees (Solve[x^2 == 4, x] is {{x -> -2}, {x -> 2}} in both), so the system path sorts where wolframscript does not.

Solve does not parametrize an unbounded integer congruence

wolframscript -code 'Solve[Mod[x, 3] == 1, x, Integers]'
# {{x -> ConditionalExpression[1 + 3*C[1], Element[C[1], Integers]]}}
woxi eval 'Solve[Mod[x, 3] == 1, x, Integers]'
# Solve[Mod[x, 3] == 1, x, Integers]

Reduce has the answer — it reports the class as Element[C[1], Integers] && x == 1 + 3*C[1] — so what is missing is Solve turning that into a rule with the parameter's membership as the ConditionalExpression condition.

The bounded case agrees: Solve[Mod[x, 3] == 1 && 0 <= x < 10, x, Integers] is {{x -> 1}, {x -> 4}, {x -> 7}} in both (rechecked 2026-09-10).

Roots root ordering

Roots[x^2 - 3x + 2 == 0, x] is x == 2 || x == 1 in Woxi and x == 1 || x == 2 in WL. But WL's order is not a clean sort — x^2-9 gives 3, -3, x^4-5x^2+4 gives -2,-1,1,2, x^2-2 gives Sqrt[2], -Sqrt[2], while x^2-2x-1 keeps Solve's order. It depends on which internal root-isolation path the degree takes. Woxi's Solve ordering does match exactly; only Roots diverges. Not reproducible.

Related and still open: Roots[x^n == c, x] uses ω^k generation order starting at k=1 when the radicand is not a perfect power, and Root of a reducible polynomial with cyclotomic factors prints a+bI (Root[x^6-1, 3] = (-1 - I Sqrt[3])/2) where Woxi keeps the Solve form -(-1)^(1/3).

Inexact polynomial solving

With any inexact coefficient WL's Solve is NSolve — purely numeric, no exact factoring, so Solve[x^3 - 1.5 x^2 + x - 1.5 == 0, x] gives 1.4999999999999996 rather than 3/2. Woxi agrees on that model, and its Durand–Kerner roots agree to 1–2 ULP — but never assert full digits. The one case WL still gets exactly right is a triple root (x^3 - 2.25 x^2 + 1.6875 x - 0.421875{0.75, 0.75, 0.75}), which it resolves at raised internal precision; f64 Durand–Kerner cannot.

Minimize does not detect an unattained infimum

Minimize[1/(1+x^2), x] returns a bogus large-x numeric instead of WL's natt message and 0.

Symbolic Fit and LinearModelFit

LinearModelFit[…]["BestFit"] drops the 0. + constant term wolframscript keeps, and Fit[{1,2,3,4},{1,x},x] gives a clean 0. + 1. x where WL keeps 5.07*^-16 + 0.9999999999999999 x — Woxi is the less noisy one.

For a rank-deficient design wolframscript is not minimum-norm: Fit[{{1,1},{2,2},{3,3}}, {1, x, x^2, x^3}, x] gives coefficients of norm 0.7546 where the minimum-norm solution has norm 0.6134. Both interpolate; WL switched to some other regularization there. Not reproducible.

Constrained and weighted fitting find different local minima

On a non-convex constrained problem the two engines land on different valid local minima — for a sine fit Wolfram lands on the constraint boundary while Woxi finds an interior point with a smaller residual. Compare the picture, not the parameters.

PowerMod[a, 1/n, m] with multiple roots

When gcd(a,m)=1 and gcd(n, λ(m)) ≠ 1 with n ≥ 3, WL returns a specific root whose choice depends on per-prime CRT lifting and follows no smallest/largest rule — n=4 gives the largest for two probes and the smallest for a third. Woxi leaves those unevaluated rather than return a possibly-wrong root. The unique-root and no-root regimes match exactly, as does n=2. Not reproducible.

Boolean minimization

BooleanConvert ships every wolframscript form except "ESOP" and "BFF", but four differences remain:

  • DNF needs term merging, not just redundancy removal: (a&&!b)||(!a&&b)||(a&&b) is a || b in WL; Woxi keeps three terms.
  • CNF clause strengthening: (!a||b||c)&&(a||!b)&&(b||!c) — WL shortens the first clause to (!a||b).
  • Clause order when clause lengths differ: Xor[a, Implies[b,c]] — neither variables-first nor length-first reproduces WL together with (a||b)&&c.
  • Equivalent-but-different CNF choices: a&&b&&c||!a&&!b&&!c — WL keys on (a,b)/(b,c) pairs, Woxi on (a,c)/(b,c). WL also sometimes produces a resolution-redundant CNF and a non-minimal DNF cover that Woxi does not.

BooleanConvert[Nand[a,b,c], "ANF"] prints Nand[a, b, c] in WL (it does not rewrite the head) against Woxi's !(a && b && c), and BooleanConvert[Equivalent[Xor[a,b],c], "IF"] branches on c first, so WL's BDD variable order is not always alphabetical.

BooleanFunction[k, n] and BooleanMinterms/BooleanMaxterms with an integer variable count normalise to the opaque BooleanFunction["BDD" -> …] object — implemented, encoding verified against WL for every function of 1, 3 and 4 variables and a sample of 5. The only remaining divergence is the too-few- arguments case: BooleanFunction[7, 2][True] and BooleanFunction[7, 2, {a}] both emit ::argr, but WL then leaks its internal BooleanConvert[…] wrapper into the returned expression where Woxi leaves the call as it stands.

SatisfiabilityInstances ordering

The default single instance (greedy True-first DFS) matches, and an unsatisfiable problem gives {}. The multi-instance and All ordering follows WL's internal BDD structure and differs per expression — a||b||c orders 7,3,1,5,2,6,4 while Majority[a,b,c] orders 7,6,5,3. Not reproducible.

Surd with a degree past i128

wolframscript -code 'ToString[Surd[8, 10^40], InputForm]'   # 8^(1/10000000000000000000000000000000000000000)
woxi eval 'Surd[8, 10^40]'                                  # Surd[8, 10000000000000000000000000000000000000000]

Surd builds the exponent with make_rational, which is i128-only, so a BigInteger degree leaves the call unevaluated. Degrees up to 10^38 work.

Convolve of two Gaussians leaves the constant as an unrationalized root

wolframscript -code 'ToString[Convolve[PDF[NormalDistribution[0, 1], x],
                     PDF[NormalDistribution[0, 2], x], x, y], InputForm]'
# 1/(E^(y^2/10)*Sqrt[10*Pi])
woxi eval 'Convolve[PDF[NormalDistribution[0, 1], x],
           PDF[NormalDistribution[0, 2], x], x, y]'
# Sqrt[(2*Pi)/5]/(2*E^(y^2/10)*Pi)

Same value — Sqrt[2 Pi/5]/(2 Pi) squared is 1/(10 Pi) — but Woxi does not pull the leftover Pi under the root and rationalize the result. This is the general Sqrt[q Pi]/Pi folding, not anything Convolve does; it only shows up here when the two widths do not divide out (equal widths, the common case, cancel exactly and agree). See also the Sqrt product-split note.

The sign of the squared shift used to diverge here too and no longer does: wolframscript prints whichever of (y - mu)^2 / (mu - y)^2 has the smaller LeafCount — its result has been through Simplify — with a tie going to the form that does not open with a minus. That reproduces all 11 measured shift shapes (3, s, s + t, 1 + s, Pi, 2 s, 2 s + 3 t, u v, s - t, -s - t, a + b).

Convolve of two Gaussians needs a literal width

wolframscript -code 'ToString[Convolve[PDF[NormalDistribution[m, s], x],
                     PDF[NormalDistribution[n, s], x], x, y], InputForm]'
# Sqrt[s^(-2)]/(2*E^((m + n - y)^2/(4*s^2))*Sqrt[Pi])
woxi eval 'Convolve[PDF[NormalDistribution[m, s], x],
           PDF[NormalDistribution[n, s], x], x, y]'
# Convolve[1/(E^((-m + x)^2/(2*s^2))*Sqrt[2*Pi]*s), …]

gaussian_shape reads the quadratic coefficient as a literal rational, so a symbolic standard deviation is not recognized and the call stays unevaluated. Symbolic means are recognized; only the width has to be a number.

Special functions

TrigToExp[Sin[x]^3] keeps the power unexpanded

wolframscript -code 'ToString[TrigToExp[Sin[x]^3], InputForm]'
# (-1/8*I)*(E^(-I*x) - E^(I*x))^3
woxi eval 'TrigToExp[Sin[x]^3]'
# (I/2/E^(I*x) - I/2*E^(I*x))^3

Same value. wolframscript pulls the (-I/2)^3 coefficient out of the cubed difference; Woxi rewrites each Sin in place and leaves the coefficient inside. Found by the differential fuzzer.

LegendreP distributes but LaguerreL collects

Resolved for both (see collect_or_distribute in src/functions/math_ast/orthogonal_polynomials.rs), noted here because the split is wolframscript's and not derivable: LegendreP[2, E] is -1/2 + (3*E^2)/2 there but LaguerreL[2, E] is (2 - 4*E + E^2)/2, and LegendreP[2, x] goes back to the collected (-1 + 3*x^2)/2. The rule Woxi implements is "collect when the argument mentions a symbol" for LegendreP and "always collect" for LaguerreL.

HypergeometricPFQ residues

HypergeometricPFQ[{}, {b}, z] with a negative half-integer b below -5/2 diverges in shape: WL stops collecting and prints a nested Bessel form. Two smaller divergences live in the core Power normalizer and reproduce without any hypergeometric: (64/729)^(1/4) stays put where WL gives (2 Sqrt[2/3])/3, and Woxi's prefactor prints 4^(1/2 - b/2) where WL prints 2^(1 - b).

Hypergeometric1F1[a, b, z] with symbolic z and 1 ≤ a < b: HypergeometricPFQ[{1},{2},x] is (-1+E^z)/z in WL and unevaluated in Woxi. The a=1 family has a clean formula, but Woxi orders E^z after the z-power terms where WL puts it right after the constant.

Carlson elliptic integrals

CarlsonRF[0, y, z] with distinct non-zero arguments needs CarlsonRK, and symbolic CarlsonRC[y, y] needs a Piecewise — both unimplemented, so Woxi echoes. CarlsonRK and CarlsonRM are unimplemented outright.

InverseWeierstrassP is broken: InverseWeierstrassP[0.5, {1,2}] returns a garbage real pair {-109.5, -39.3} where WL returns a scalar complex 1.308 + 0.469 I. The correct route is ℘⁻¹(p) = CarlsonRF[p-e1, p-e2, p-e3], but WL's representative selection is not a simple sign or conjugate of it — for one probe it is the conjugate, for another it differs by a lattice period. Picking it needs the period lattice.

Weierstrass and elliptic gaps

WeierstrassHalfPeriods covers only the real positive-discriminant case; the rhombic case needs a complex EllipticK. WeierstrassInvariants[{1,I}] and WeierstrassHalfPeriods[{4,0}] with exact arguments give Gamma closed forms in WL and stay symbolic in Woxi. WeierstrassZeta, WeierstrassSigma, SiegelTheta and QPolyGamma are unimplemented.

Neville theta values are 1–2 ULP off wolframscript on some points.

Mathieu functions

MathieuC[1, 2, 0.5] is 0.26 + 1.17 I in WL — the Mathieu functions are complex for a general characteristic value. Woxi's MathieuS returns a wrong real value, having confused the order-versus-characteristic convention. A correct fix needs a full Floquet implementation (continued fractions for the characteristic exponent plus Fourier coefficients).

SphericalHarmonicY numericizes exact angles

SphericalHarmonicY[l, m, Pi/2, 0] returns a machine float (e.g. 2.99…*^-17 instead of an exact 0) where wolframscript returns the exact symbolic form. Two residual form divergences remain even on the symbolic path: Sqrt[Pi^(-1)] against 1/Sqrt[Pi], and E^(I k Pi) against (-1)^k.

LaguerreL with a fractional second argument

Value correct, form divergent: (105/8 - (105*x)/4 + (21*x^2)/2 - x^3)/6 in Woxi against (105 - 210*x + 84*x^2 - 8*x^3)/48 in WL. WL's evaluator does not hoist content out of Times[1/6, Plus[…]] when typed directly, so LaguerreL must build the common denominator internally — but LaguerreL[1, 1/2, x] stays 3/2 - x rather than (3 - 2*x)/2, so it is not a plain Together either. Integer second arguments match.

ArcCot[Interval[…]] is unevaluated

WL converts endpoints to ArcTan form inside the interval (ArcCot[2]ArcTan[1/2]), which differs from its own scalar ArcCot[2].

WignerD[{1,0,0}, Pi/2]

Gives 2.22e-16 instead of an exact 0.

LerchPhi outside the unit disc

The numeric path sums Σ z^k/(k+a)^s and converges only for |z| < 1 (plus a tail for z = 1, s > 1); outside that region the call is unevaluated. wolframscript continues analytically — LerchPhi[2, 3, -1.5]51.981861922538684 - 2.1345964981239467*I. A correct implementation needs Crandall's algorithm or a contour formula.

SyntaxLength is unimplemented

wolframscript -code 'SyntaxLength["1+"]'   # 4
woxi eval 'SyntaxLength["1+"]'             # SyntaxLength[1+]  (+ "not yet implemented" warning)

SyntaxLength[s] is the length of the longest prefix of s that could still begin a complete expression — 4 for "1+" (WL counts past the end of the string, since more input could complete it).

Capitalize[str, "TitleCase"] is unimplemented

The algorithm is verified (tokenize on whitespace, first and last words capitalized, middle "small words" lowercased), but the small-word set is WL's complete internal preposition/conjunction/article list, keyed on the literal word. 130+ candidates were tested and it kept growing — common (a, the, of), participial (considering, barring, notwithstanding, but not excluding/following), and archaic (betwixt, qua, ere, sans, cum, nigh, outwith, thru, contra, vice). Any finite list diverges on untested words. Not reproducible. SpokenString and TextCases entity extraction are the same class.

TextSentences grouping

WL's segmenter groups short sentences oddly ("One. Two. Three."{"One.", "Two. Three."}) while handling "Dr. Smith went home." correctly. Woxi's straightforward splitting is the sensible answer. Not reproducible.

Hash[expr] for non-strings

wolframscript hashes an internal serialisation: Hash[1, "CRC32"] is 3017272578 and Hash[{1,2}, "CRC32"] is 1789268987, matching CRC32 of neither the WXF, the InputForm text, the FullForm text, nor an integer encoding. Strings and ByteArrays are special-cased to raw bytes and do match, as do all the named algorithms on them. Hash[expr] with no type is WL's proprietary 64-bit expression hash. Compress output length differs too (their container format). Not reproducible.

Distributions and statistics

Most distribution divergences are the Plus/Times ordering and Sqrt-split classes above, reached through a moment formula. The recurring shapes:

  • Sqrt product splitSqrt[2 nu] against WL's Sqrt[2] Sqrt[nu], reached through StandardDeviation[ChiSquareDistribution[nu]], Nakagami's numeric Mean, Hoyt's numeric Mean (4*Sqrt[1/(5*Pi)]*EllipticE[3/4] against (4*EllipticE[3/4])/Sqrt[5*Pi]), SkewNormal's Mean[0,1,1] (Sqrt[Pi^(-1)] against 1/Sqrt[Pi]) and VarianceGamma's integer-λ branches.
  • Plus term order in a two-term variance — most distribution variances are a two-term difference and hit it. Named instances: LogLogistic, LogSeries, Coxian, Hyperexponential, TukeyLambda, Wakeby, Weibull (3-arg), Suzuki, PERT, Benini, Dirichlet.
  • Times factor orderPi sorts ahead of a function-containing sum in Woxi (4 Pi (…) against WL's 4 (…) Pi), and p^(-1+k)*(1-p) against WL's (1-p)*p^(-1+k).

Distributions compute with out-of-range parameters

wolframscript -code 'PDF[NormalDistribution[0, -1], 0.3]'
# NormalDistribution::posprm: Parameter -1 at position 2 in
# NormalDistribution[0, -1] is expected to be positive.
woxi eval 'PDF[NormalDistribution[0, -1], 0.3]'
# -0.3813878…

A negative probability density. Constructing the distribution is fine in both (BetaDistribution[4, -1] echoes silently); the divergence is in using one. The same applies to PDF, Quantile and Mean. Woxi has a validator, but it is wired only to DistributionParameterQ and returns a bare boolean — the message needs the offending parameter's position and value.

Raw moments are missing for most distributions

Moment[dist, n] for n ≥ 3 returns unevaluated except for Bernoulli, Exponential, Uniform, Normal (symbolic n ≤ 4), Gamma, Poisson and ChiSquare, which cascades into Skewness/Kurtosis not reducing. Two structural notes: the order-2 raw moment is computed by a separate variance-plus-mean² path, so Moment[dist, 2] prints expanded (2k+k^2) not WL's factored k(2+k); and raw Moment[dist, n] diverges anyway for the distributions whose WL answer is an incomplete-Gamma form.

Beta raw moments are deliberately not added: its central moments are rational functions in a,b that Woxi's simplifier cannot collapse, so Skewness/Kurtosis[Beta] would explode into 100+ term expressions against WL's compact form.

Skewness of a scale-parameter distribution is a positivity rabbit hole — Skewness[ExponentialDistribution[l]] is 2/(l^(-2))^(3/2)*l^3, value correct but not simplified to 2 without knowing l > 0.

Central moments of a distribution factor differently

wolframscript -code 'ToString[CentralMoment[BernoulliDistribution[p], 3], InputForm]'
# (1 - p)*p*((1 - p)^2 - p^2)
woxi eval 'CentralMoment[BernoulliDistribution[p], 3]'
# p - 3*p^2 + 2*p^3

Even constructing the factored form does not help: Woxi reorders the inner Plus to (1-p)*p*(-p^2 + (1-p)^2). Skewness and Kurtosis of such distributions diverge for the same reason.

Probability of a normal tail uses a different Erf form

wolframscript -code 'ToString[Probability[x > 1, x \[Distributed] NormalDistribution[0,1]], InputForm]'
# (1 - Erf[1/Sqrt[2]])/2
woxi eval 'Probability[x > 1, x \[Distributed] NormalDistribution[0,1]]'
# (2 - Erfc[-(1/Sqrt[2])])/2

CDF and SurvivalFunction of the same distribution do match. WL is entangled with itself here — its SurvivalFunction[Normal, 1] is the unsimplified 1 - Erfc[-(1/Sqrt[2])]/2 while Probability[x > 1] is the Erf form, so there is no single canonical target for numeric arguments.

Per-distribution scope gaps

Left unevaluated because WL's own output is an unreproducible form:

distribution what is missing
Coxian mixed repeated/distinct rates, float and symbolic-rate PDF/CDF
Hypoexponential repeated rates (Erlang-style terms)
TsallisQGaussian CDF for numeric q ≠ 1 (WL collapses 2F1 per q), float params
TukeyLambda any λ outside {0, ±1, 1/2, 2}; λ=3 is a Root form in WL
Wakeby symbolic Mean/Variance and symbolic-q Quantile ordering
VarianceGamma CDF (WL's forms are partially unexpanded)
Hoyt CDF (needs MarcumQ)
SkewNormal Skewness/Kurtosis; CDF at a=1 has a special squared form in WL
NegativeMultinomial CDF with symbolic parameters; symbolic StandardDeviation
HotellingTSquare 1-ULP coefficient gap for non-integer float parameters
Wishart PDF (needs MatrixGammaFunction), RandomVariate
FailureDistribution duplicated events, Mean (inclusion-exclusion sums)
DiscreteMarkovProcess bare StationaryDistribution, MarkovProcessProperties, CDF, graph constructor
FirstPassageTime symbolic-t PDF, probability-vector initial states
PERT Median (WL gives InverseBetaRegularized/Root forms)
LogSeries RandomVariate, Median, Quantile
PolyaAeppli PDF, CDF
Benford, WaringYule, 4-arg Gamma, Davis unimplemented

Also: DiscreteUniformDistribution's StandardDeviation is Sqrt[X/12] against WL's Sqrt[X]/(2 Sqrt[3]), and ExpGammaDistribution's PDF is unimplemented (exponent Plus order).

MovingMap[Mean, …] stays exact

wolframscript -code 'MovingMap[Mean, {1,2,3,4,5}, 2]'  # {2., 3., 4.}
woxi eval 'MovingMap[Mean, {1,2,3,4,5}, 2]'            # {2, 3, 4}

The trigger is the applied function, not the window: MovingMap[Total, …] stays exact in both. Mean[{1,2,3}] is exact on its own, yet WL floats it inside MovingMap. Which functions WL numericizes is not determinable without a large probe; Woxi's exact behaviour is arguably the better one. Not reproducible.

Robust statistics that iterate

  • BiweightLocation is iterative in machine floats in WL and returns unconverged trailing digits ({1,2}1.4999988566906757) even for exact input. BiweightMidvariance is closed-form and matches.
  • SpatialMedian uses an unconverged Weiszfeld iteration (0.5773502623362886 against the true 1/Sqrt[3]). Only 1-D inputs, which reduce to Median, are reproducible.

Not reproducible.

EstimatedDistribution is unimplemented

Mean, Quantile and RootMeanSquare of an EventSeries or TemporalData

Unevaluated.

Linear algebra

Float matrices with complex eigenvalues

Wolfram complexifies the whole result and orders each conjugate pair with +I first: {0. + 1.*I, 0. - 1.*I, 1. + 0.*I}. Woxi gives the value-correct but form-divergent {0. - 1.*I, 0. + 1.*I, 1.}, and non-block complex cases stay unevaluated. Complex Eigenvectors for n ≥ 3 are unevaluated too, and radical eigenvector components order differently ((-Sqrt[5] + I)/3 against (I - Sqrt[5])/3).

Generic dense float matrices also differ in the last 1–2 digits from WL/LAPACK.

SchurDecomposition and SmithDecomposition factors are non-unique

Wolfram's LAPACK eigenvalue ordering along t and the column signs of q are not reproducible independently ({{1,2},{3,4}} — WL puts −0.372 first, Woxi 5.372). Test the defining properties instead. RealBlockDiagonalForm -> False (the complex Schur form) is unimplemented.

SmithDecomposition's u and v differ the same way, and WL sometimes keeps a negative diagonal entry in s ({{1,0},{0,-2}} stays) while Woxi always normalises to the canonical non-negative SNF — six probe rounds produced no rule. Woxi's answer is the mathematically canonical one. Not reproducible.

JordanReduce scope

Left unevaluated: eigenvalues containing Root objects (WL's diagonal ordering there is neither Eigenvalues order nor ascending), and inexact matrices with n ≥ 3 and repeated eigenvalues (WL clusters with a tolerance).

LDLDecomposition and FrobeniusReduce on inexact or symbolic input

Left unevaluated. WL's float LDL shows LAPACK-internal rounding (D = {4., 2.0000000000000004} where plain f64 gives exactly 2.0), and its FrobeniusReduce returns precision-tagged arbitrary-precision reals whose precision comes from internal error tracking. Symbolic matrices come back with unsimplified algorithm-internal quotients.

Also found: FrobeniusReduce[m, Modulus -> -5] reproducibly crashes the wolframscript kernel (exit 139); Woxi returns unevaluated.

BunchKaufmanDecomposition and PopovDecomposition

Both deferred. Bunch-Kaufman's BlockDiagonalMatrix payload has encoder quirks (a whole-matrix antidiagonal 2×2 pivot is stored as two permuted 1×1 blocks) plus LAPACK pivot-order replication in exact arithmetic. Popov normalizes over ℤ[x] rather than ℚ[x] — constant matrices get integer Hermite normal form, pivots are not made monic, and rational entries get content cleared.

BlockUpperTriangularMatrix / BlockLowerTriangularMatrix

Not constructors: they find a matrix's finest block-triangular permutation and return a structured array. The component ordering is a Dulmage–Mendelsohn decomposition needing a perfect bipartite matching first, and WL's tie-breaking would be high-risk to match.

MatrixFunction and MatrixExp on irrational results

Both produce value-correct but unsimplified, form-divergent results for non-diagonal matrices with irrational entries — (3E-E^3)/2-E+E^3 against WL's E/2+E^3/2, (1+Sqrt[3])/2 against WL's split 1/2+Sqrt[3]/2. Diagonal and integer-result cases match. MatrixExp on complex/rotation matrices is value-correct but Woxi simplifies to Cos/Sin where WL 15 keeps E^±I combinations.

FourierDCTMatrix for n ≥ 5

Values agree to ~15 digits; the radical display diverges — WL canonicalizes Sqrt[(5/8+Sqrt[5]/8)/5] where Woxi gives Sqrt[5/8+Sqrt[5]/8]/Sqrt[5], and WL folds 2/(2 Sqrt[3]) to 1/Sqrt[3].

SymmetricReduction two-argument form

Value correct always, but the display order diverges for some higher-degree two-variable cases: x^3+y^3 gives (x+y)^3-3*x*y*(x+y) in Woxi against WL's -3*x*y*(x+y)+(x+y)^3. The three-argument form is fully conformant.

Symmetrize of a symbolic tensor

Symmetrize[T, Symmetric[{1,2,3}]] for a bare symbol T should give the explicit (T + TensorTranspose[T, {2,1}] + …)/6. Woxi leaves it unevaluated: TensorTranspose on a symbol errors, and wolframscript's term order truncates trailing fixed points.

ArrayFlatten[a, r]

The two-argument form is unimplemented (Woxi reports argx). Ragged block-tensor assembly, rarely used.

RotationMatrix / RotationTransform about a symbolic axis

Woxi leaves a 3D rotation about a non-numeric axis unevaluated (RotationTransform[Pi, {0, 0, x}, {1, 0, 0}] echoes). Wolfram does return something, but only as an unsimplified artifact of its internal Gram–Schmidt:

wolframscript -code 'RotationMatrix[Pi, {0, 0, x}]'
# {{-((x Conjugate[x])/Abs[x]^2), 0, 0}, {0, -1, 0}, {0, 0, (x Conjugate[x])/Abs[x]^2}}

Note the asymmetry — entry (1,1) keeps the un-cancelled x Conjugate[x]/Abs[x]^2 (which is just 1) while entry (2,2) is a plain -1, so the two diagonal entries of the same rotation print differently. A fully symbolic axis {a, b, c} produces roughly 8 KB of nested Conjugate[1/Sqrt[a Conjugate[a] + b Conjugate[b]]] terms, with Sqrt[(a Conjugate[a] + b Conjugate[b])/(a Conjugate[a])] denominators that are singular exactly on the coordinate axes. The output is not a canonical form (and is not a real rotation matrix for complex components), so there is nothing stable to conform to. Not reproducible; the affected unit test is listed in EXACT_EXPR_SKIP in tests/wolframscript/verify_unit_tests.ts.

NumberFieldDiscriminant

Non-maximal orders are unevaluated (Sqrt[2]+Sqrt[3] → WL 2304 via a full conductor/Round-2 computation), as are non-monic minimal polynomials of degree ≥ 3.

Expression structure and evaluation

A package symbol answers before its package has been loaded

wolframscript -code 'ToString[InterpolatingFunctionDomain[
                     Interpolation[{1, 4, 9, 16}]], InputForm]'
# InterpolatingFunctionDomain[InterpolatingFunction[{{1, 4}}, …]]
woxi eval 'InterpolatingFunctionDomain[Interpolation[{1, 4, 9, 16}]]'
# {{1, 4}}

InterpolatingFunctionDomain and its InterpolatingFunctionAnatomy siblings live in a package a fresh kernel does not autoload, so without Needs["DifferentialEquationsInterpolatingFunctionAnatomy"] wolframscript reads the name into Global`` and echoes the call back. Woxi keeps every built-in in one namespace —Needson a context that ships with the language is a no-op — so the name works either way, which makes Woxi a superset here rather than a mismatch. Writing theNeeds` makes the two agree; there is no way to make Woxi withhold the symbol without giving up the flat namespace.

Unevaluated comes off for some heads and not others

The wrapper is stripped where it was verified to matter — the arithmetic and structural operators, SameQ/UnsameQ, Print, and the built-ins that already took it (Length, Head, Sqrt, Options, Information) — but not as the general rule Wolfram applies, which takes every Unevaluated[…] off just before the head's own rules run and puts it back if nothing was consumed:

wolframscript -code 'Print[Unevaluated[1] < 2]'
# True
woxi eval 'Unevaluated[1] < 2'
# Unevaluated[1] < 2

The arithmetic comparisons are the ones this reaches. Wolfram compares the unevaluated content and answers Unevaluated[1 + 1] == 2 with the comparison itself; Woxi would have to evaluate the content to compare it at all, so stripping there would answer True where Wolfram does not. The wrapper is therefore left in place for ==, <, > and their relatives.

Rubi loads and integrates, but not everything it computes agrees

The Rubi rule base is the densest available exercise of the pattern matcher and the definition store: 7000 rules, all read back out of DownValues and rewritten before use. It loads unmodified, integrates, and its step display ($LoadShowSteps = True, Steps) works. On a 30-integral sample 27 answers are character-for-character wolframscript's and the other 3 are the same function written another way; the IntWithStepsOfTeXForm notebook reproduces three of its four examples exactly. What is left, all of it ordinary Woxi behaviour rather than anything about the package:

  • Loading takes about a minute against roughly twenty seconds under wolframscript, three with the step display.
  • Int[Sin[x]^3*Cos[x]^2, x] and Int[Sin[x]*Cos[x]^3, x] run for minutes and exhaust memory. wolframscript answers both instantly.
  • Int[E^x*x, x] is -Gamma[2, -x] where Rubi gives -E^x + E^x*x, and Int[1/(a + b*Cos[x]), x] reaches its arctangent through another substitution: a different rule fires first. Equivalent antiderivatives.
  • x^2*Sqrt[a^2 - x^2] prints as Sqrt[a^2 - x^2]*x^2 (the Times order of a power of a sum against a power of a symbol), and one intermediate sum of Int[Sqrt[Tan[x]], x] lists two Subst terms the other way round (see the ordering entries above).
  • The rule text recorded beside each step keeps its FreeQ conditions and is written in linear box syntax rather than DisplayForm, and the rule numbers differ (7391 rules against 7300).

A held ;-sequence is not bracketed inside a tighter operator

wolframscript -code 'Hold[(a; b) + c]'
# Hold[(a; b) + c]
woxi eval 'Hold[(a; b) + c]'
# Hold[a; b + c]

CompoundExpression has the lowest precedence of any operator, so the printed form re-parses as CompoundExpression[a, Plus[b, c]] — a different expression. Inside a call it is right: Hold[f[(a; b), c]] prints as Hold[f[a; b, c]], as wolframscript does.

Newline-separated statements become one CompoundExpression

wolframscript -code 'ToExpression["2\n3", InputForm, Hold]'
# Hold[2, 3]
woxi eval 'ToExpression["2\n3", InputForm, Hold]'
# Hold[2; 3]

The reader turns every statement separator into a ; before parsing, so it cannot tell a newline from one. A ;-separated program agrees (ToExpression["2; 3", InputForm, Hold] is Hold[2; 3] in both), and so does a trailing ; (Hold[a := 1; ]).

Derivative[n][f][x] is stored flat, so structural functions see three parts

wolframscript -code 'Head[Derivative[1][g][x]]'      # Derivative[1][g]
woxi eval 'Head[Derivative[1][g][x]]'                # Derivative

wolframscript -code 'Length[Derivative[1][g][x]]'    # 1
woxi eval 'Length[Derivative[1][g][x]]'              # 3

wolframscript -code 'Apply[f, Derivative[1][g][x]]'  # f[x]
woxi eval 'Apply[f, Derivative[1][g][x]]'            # f[1, g, x]

Woxi stores it flat rather than as nested curried calls. The renderer prints it correctly and D returns the right thing, so only structural introspection diverges — Head, Length, Part, Level, Map and Apply all leak the internal shape.

Unevaluated[…] is not transparent to structural functions

wolframscript -code 'Unevaluated[1 + 1] // Head'   # Plus
woxi eval 'Unevaluated[1 + 1] // Head'             # Unevaluated

wolframscript -code 'Depth[Unevaluated[1 + 1]]'    # 2
woxi eval 'Depth[Unevaluated[1 + 1]]'              # 3

Length, AtomQ and Head now agree; Depth, First and Part still see the wrapper (Part[Unevaluated[1+2], 1] gives 3 where WL gives 1). The arithmetic consumers are also open: Unevaluated[1+1] + 1 is 1 + Unevaluated[1 + 1] rather than 3, and Total/Identity behave the same way. Woxi's model is per-consumer stripping because the real rule is not the published "strip unless HoldAllComplete".

The same per-consumer stripping is why a wrapper a pure function's body produced is stripped one level too eagerly. WL strips only wrappers written literally in an argument list, so a produced one survives into any consumer:

wolframscript -code 'Head[(Unevaluated[Sequence[#, #^2]] &)[3]]'   # Unevaluated
woxi eval 'Head[(Unevaluated[Sequence[#, #^2]] &)[3]]'             # Sequence

Structural positions agree — {0, (Unevaluated[Sequence[#, #^2]] &)[3], 9} is {0, Unevaluated[Sequence[3, 3^2]], 9} and f[…] keeps the wrapper in both — and so do the strippers whose wrapper was literal. Only an argument-consuming built-in (ToString, Length, Head) fed a produced wrapper diverges; telling the two apart needs the literal-ness of each argument threaded through to the built-in dispatch.

A loop body is an argument-consuming position of the same kind, so a wrapper written there survives where WL strips it and evaluates what was inside:

wolframscript -code 'Table[Unevaluated[1 + 1], {i, 2}]'   # {2, 2}
woxi eval 'Table[Unevaluated[1 + 1], {i, 2}]'             # {Unevaluated[1 + 1], Unevaluated[1 + 1]}

wolframscript -code 'Do[Print[Unevaluated[1 + 1]], {i, 1}]'   # 2
woxi eval 'Do[Print[Unevaluated[1 + 1]], {i, 1}]'             # Unevaluated[1 + 1]

A definition's bare symbol argument becomes a blank pattern

SetDelayed turns a plain symbol in the left-hand side's argument list into a named pattern, so f[x] := … defines what f[x_] := … defines. WL takes the symbol literally: the definition is stored for the single argument x and matches nothing else.

wolframscript -code 'f[x] := x^2; f[5]'               # f[5]
woxi eval 'f[x] := x^2; f[5]'                         # 25

wolframscript -code 'f[x] := x^2; DownValues[f]'      # {HoldPattern[f[x]] :> x^2}
woxi eval 'f[x] := x^2; DownValues[f]'                # {HoldPattern[f[x_]] :> x^2}

WL also evaluates the arguments of the left-hand side before storing the definition, which Woxi's pattern reading hides:

wolframscript -code 'i = 3; g[i] := i; DownValues[g]'  # {HoldPattern[g[3]] :> i}
woxi eval 'i = 3; g[i] := i; DownValues[g]'            # {HoldPattern[g[i_]] :> i}

Set (=) stores the literal argument as WL does. A symbol nested inside a structured argument takes a third path and leaks an internal name into the stored right-hand side:

wolframscript -code 'f[{x}] := x; DownValues[f]'      # {HoldPattern[f[{x}]] :> x}
woxi eval 'f[{x}] := x; DownValues[f]'                # {HoldPattern[f[{x}]] :> _lp0[[1]]}

Sum and Product substitute their iterator instead of localizing it

Table and Do localize the iterator the way Block does — the symbol is given a value for one iteration rather than replaced throughout the body — so held positions keep it symbolic. Sum and Product still substitute, which both burns the counter into held subexpressions and stops WL's own recognition of a summand that does not depend on the index:

wolframscript -code 'Sum[Hold[i], {i, 2}]'      # 2*Hold[i]
woxi eval 'Sum[Hold[i], {i, 2}]'                # Hold[1] + Hold[2]

wolframscript -code 'Product[Hold[i], {i, 2}]'  # Hold[i]^2
woxi eval 'Product[Hold[i], {i, 2}]'            # Hold[1]*Hold[2]

A non-terminating NestWhile returns a wrong answer instead of not terminating

wolframscript -code 'TimeConstrained[NestWhile[#/2 &, 16, UnsameQ, 2], 5, timeout]'   # timeout
woxi eval 'NestWhile[#/2 &, 16, UnsameQ, 2]'                                          # 1/2^10000

Successive halvings of 16 are never SameQ, so this iterates forever in wolframscript. Woxi silently stops after about 10 000 iterations and returns the value it had reached, which is neither wolframscript's behaviour nor an error.

Return unwinds through ordinary argument evaluation

Wolfram treats Return[x] as an ordinary expression that only three things consume (a definition body, Do, Scan), with CompoundExpression, While, For, Module, Block and With passing it upward intact. Everything else keeps it as a value: Table[Return[1], {2}] is {Return[1], Return[1]}, Catch[Return[1]] is Return[1], {Return[1], 2} and f[Return[1]] and Return[1] + 2 all stand.

Woxi raises Return as a signal, so each boundary must be explicit. Table, Map, Select and With are wired; a Return reached by ordinary argument evaluation still unwinds — {Return[1], 2} gives 1, Catch[Return[1]] gives 1, Function[Return[3]][] gives 3.

x = 2 x does not recurse

wolframscript evaluates the right-hand side, assigns the own-value x → 2*x, and then re-evaluates it on every lookup until $RecursionLimit:

$RecursionLimit::reclim: Recursion depth of 30 exceeded.
TerminatedEvaluation[RecursionLimit]

Woxi returns 2*x and treats the own-value as a fixed-point substitution.

Format upvalues are stored as UpValues rather than FormatValues

wolframscript -code 'c /: Format[c] := "see"; ToString[UpValues[c], InputForm]'  # {}
woxi eval 'ClearAll[c]; c /: Format[c] := "see"; UpValues[c]'
# {HoldPattern[Format[c]] :> see}

Wolfram files a Format definition made through /: under FormatValues, not UpValues, so UpValues[c] comes back empty. Woxi has no separate FormatValues table and reports the rule as an upvalue. The rest of the UpValues / TagSet / TagUnset family agrees with wolframscript.

Format[…] is neither held nor form-aware

wolframscript -code 'Head[Format[x + y]]'          # Format
woxi eval 'Head[Format[x + y]]'                    # Plus

wolframscript -code 'Head[Format[x + y, OutputForm]]'  # Format
woxi eval 'Head[Format[x + y, OutputForm]]'            # Symbol

Wolfram keeps Format[expr] and Format[expr, form] unevaluated — Length is 1 or 2, part 1 is the expression — and only applies a display rule, the same model Definition follows.

The second argument is also ignored, so every form but OutputForm renders the plain expression:

wolframscript -code 'Format[Sqrt[x], TeXForm]'      # \sqrt{x}
woxi eval 'Format[Sqrt[x], TeXForm]'                # Sqrt[x]

wolframscript -code 'Format[x/y, TeXForm]'          # \frac{x}{y}
woxi eval 'Format[x/y, TeXForm]'                    # x/y

wolframscript -code 'Format[x + y, StandardForm]'   # RowBox[{x, +, y}]
woxi eval 'Format[x + y, StandardForm]'             # x + y

wolframscript -code 'Format["ab", InputForm]'       # "ab"
woxi eval 'Format["ab", InputForm]'                 # ab

StandardForm and TraditionalForm want boxes, which ToBoxes already produces correctly, and TeXForm / InputForm want the corresponding renderer. An unsupported form (Format[x^2, FullForm]) stays unevaluated in WL and prints as Format[x^2, FullForm].

CellularAutomaton refuses an explicit window it would have to allocate

wolframscript -code 'CellularAutomaton[90, {1, 0, 1}, {{{1}}, {0, 1000000000}}]'
# a billion-cell list
woxi eval 'CellularAutomaton[90, {1, 0, 1}, {{{1}}, {0, 1000000000}}]'
# CellularAutomaton[90, {1, 0, 1}, {{{1}}, {0, 1000000000}}]

A cyclic initial condition never grows past its own size, so nothing bounds an explicit cell window against it; Woxi checks the window's length arithmetically and declines rather than collecting a billion indices. The tspec grammar and every window that fits in memory agree with WL exactly.

DownValues drops a /; guard on a compound left-hand side

f[x_] := x^2 /; x > 0 round-trips, but k[(a_. + b_.*x_)^m_., x_Symbol] := … /; FreeQ[…] reports {HoldPattern[k[…]] :> a + b + m} with no /;. The guard is enforced at call time — it lives in a per-parameter condition slot the reconstruction never re-attaches. This is the single remaining blocker for running Rubi's FixIntRules[], which reads DownValues[Int], sees unguarded rules and writes them back, destroying the rule base.

A related display-only residue: a trailing-sequence list rule's DownValues shows {a_, __} rather than {a_, b__}, because the trailing element's name is not recoverable.

ToExpression of a definition

ToExpression["h[x__] := {x}; h[1, 2]"] gives h[1, 2] while ToExpression["zz = 7; zz + 1"] gives 8 — definitions made through ToExpression do not take effect for the rest of the same string.

Context of an unknown context

Context["zzzabc"]returns"zzz" in Woxi; wolframscript reports Context::notfound and stays unevaluated.

Module rename artefacts and InformationData

Module locals are named loc$1 here and loc$ in wolframscript (and the counter value is a session number that can never be pinned), and InformationData prints its keys unquoted. wolframscript also carries its own WolframScript`` context, which Woxi has no equivalent for — so a bare$ContextPath` can never match.

Legacy package names

Any Combinatorica,PolyhedronOperations or HypothesisTestingsymbol Woxi implements (e.g.UnrankPermutation,Truncate,Stellate,MeanTest,MeanDifferenceTest) evaluates where wolframscript leaves it unevaluated, because the package is not loaded. Woxi has no package system forNeeds` to load into, so the qualified names are always live. With the package loaded the results agree.

Needs also does not put such a context on $ContextPath, so where wolframscript prints OneSidedPValue -> … after Needs["HypothesisTesting"], Woxi prints the qualifiedHypothesisTestingOneSidedPValue -> … — the same expression, spelled in full. For the same reason a bare MeanTest never resolves to the package's symbol here. Compare through property /. …, not on the printed rule.

HypothesisTestingMeanTest[data, mu0]with a symbolicmu0(includingAutomatic, which the package does *not* read as 0) or symbolicdatastays unevaluated in Woxi; wolframscript builds a symbolicPiecewiseofBetaRegularizedbranches out of it.MeanDifferenceTestwith matrix samples stays unevaluated too — the package's own matrix handling there is broken, producing aStudentTDistribution` over a list of degrees of freedom.

$VersionNumber is a string

Woxi returns its version string (v0.3.0-46-g…) rather than a number, so ToString[$VersionNumber] and any $VersionNumber >= 9 test break.

Manipulate's Initialization is not scoped

Manipulate[…, Initialization :> (f[x_] := …)] leaves f defined in the global scope, so a later cell sees it. wolframscript keeps it inside the DynamicModule the Manipulate wraps itself in and f[3] echoes back. Woxi has no such module, and its controls re-resolve the body on every frame, so the definitions have to outlive the call.

Running Initialization at evaluation time also resolves control bounds that reference what it defines. A text front end never displays the DynamicModule, so wolframscript never runs Initialization at all and evaluates such a bound against an undefined symbol:

wolframscript -code 'Manipulate[func[[n]], {{n, 1, "which"}, 1, Length[func], 1}, Initialization :> (func = {a, b, c, d})]'
# Manipulate[func[[n]], {{n, 1, which}, 1, 0, 1}, Initialization :> (func = {a, b, c, d})]
woxi eval 'Manipulate[func[[n]], {{n, 1, "which"}, 1, Length[func], 1}, Initialization :> (func = {a, b, c, d})]'
# Manipulate[func[[n]], {{n, 1, which}, 1, 4, 1}, Initialization :> (func = {a, b, c, d})]

Woxi matches the notebook here rather than the CLI: 4 is the slider maximum a front end shows (this is the shape of the "Function Explorer 3D" Demonstration), and Woxi Studio reads its controls out of exactly this expression. A 0 conformant with wolframscript would leave that Demonstration with an empty slider.

Get returns raw text

Get returns an Expr::Raw, so List @@ Get["PacletInfo.m"] fails, and SystemPrivate$InputFileName is never set, so a package cannot locate its own directory. DumpSave is unimplemented, so a paclet's MX fast-load path can never work.

Lists, associations and structured objects

ListCorrelate / ListConvolve have no multi-dimensional overhang

wolframscript -code 'ToString[ListCorrelate[{{1, 1}, {1, 1}}, {{a, b, c}, {d, e, f}, {g, h, i}}, 1], InputForm]'
# {{a + b + d + e, b + c + e + f, a + c + d + f},
#  {d + e + g + h, e + f + h + i, d + f + g + i},
#  {a + b + g + h, b + c + h + i, a + c + g + i}}
woxi eval 'ListCorrelate[{{1, 1}, {1, 1}}, {{a, b, c}, {d, e, f}, {g, h, i}}, 1]'
# ListCorrelate[{{1, 1}, {1, 1}}, {{a, b, c}, {d, e, f}, {g, h, i}}, 1]

The two-argument multi-dimensional form is correct; only the overhang path (k / {kL, kR}, padding, generalized g/h) is one-dimensional, and it stays unevaluated for a rank-2 kernel rather than answering.

The 7th argument, a level specification, is unimplemented for every rank:

wolframscript -code 'ToString[ListCorrelate[{x, y}, {a, b, c}, 1, p, Times, Plus, 1], InputForm]'
# {a*x + b*y, b*x + c*y, c*x + p*y}
woxi eval 'ListCorrelate[{x, y}, {a, b, c}, 1, p, Times, Plus, 1]'
# ListCorrelate::argb: called with 7 arguments; between 2 and 6 arguments are expected.

Total groups negative levels globally rather than per parent

wolframscript -code 'Total[{{1, 2}, {3, {4, 5}}}, {-1}]'   # {3, {3, 9}}
woxi eval 'Total[{{1, 2}, {3, {4, 5}}}, {-1}]'             # {{1, 2}, {3, 9}}

Woxi reads {-1} as the deepest level of the whole expression — level 3 here, so it answers Total[…, {3}]. wolframscript sums the depth-1 parts grouped by their immediate parent, which leaves the depth-1 atom 3 alone and reduces only {4, 5}.

Every other head measures a negative level as the part's own depth; Total is the exception because its traversal also carries AllowedHeads and the head-preservation rules.

AssociationThread does not take a scalar key

wolframscript -code 'AssociationThread[3, {1, 2}]'      # <|3 -> 2|>
wolframscript -code 'AssociationThread["ab", {1, 2}]'   # <|ab -> 2|>
woxi eval 'AssociationThread[3, {1, 2}]'                # AssociationThread[3, {1, 2}]

A scalar value is shared across the keys, but a scalar key against a list of values keeps only the last value, which is peculiar enough that generalising from two examples would be guessing. Left unevaluated on purpose; it echoes rather than aborting.

Mixed association/list set operations

Union, Intersection and Complement mixing an association with a list stay unevaluated in both engines, but WL emits ::heads where Woxi emits ::normal.

Take with a zero start index

Take[{1,2,3}, {0, -1}] and {0, 3} are {} in wolframscript and an error in Woxi. Every other {0, n} errors in WL too, so the rule there is obscure.

SparseArray slices and Select

  • sa[[1]] and sa[[All, 2]] return a dense list (or, for [[All, 2]], garbage) where WL returns a sparse sub-array.
  • Select[sa, pred] returns a broken SparseArray[]. WL returns a dense list when the background value fails the predicate and stays sparse when it passes — a predicate-dependent choice.
  • SparseArray op list and SparseArray + SparseArray return a dense list in Woxi where WL keeps the result sparse. The values match through Normal.
  • Map[f, sa] at rank ≥ 2 densifies; WL keeps the sub-rows sparse.
  • AdjacencyMatrix[g] returns a dense matrix rather than a SparseArray, so AdjacencyMatrix[g] // MatrixForm prints differently.

Threaded[scalar] gives garbage

wolframscript -code 'Threaded[1] + {1,2,3}'
# Threaded::rarray … and the operation unevaluated as {1, 2, 3} + Threaded[1]
woxi eval 'Threaded[1] + {1,2,3}'
# {Threaded[2], Threaded[3], Threaded[4]}

The Threaded argument must be rank ≥ 1; a scalar inner is invalid. A two-argument special case was tried and reverted because it turned the three-argument case from garbage into a crash.

ReplaceAll head replacement in two exotic corners

  • Association under a symbol blank: Woxi descends into keys and values keeping the association form (<|f[k] -> f[v]|>); WL gives f[Association][k -> f[v]], leaving keys and the inner Rule head alone.
  • Held infix: Hold[2+a] /. x_Symbol :> f[x] leaves the inner held Plus head unrewritten (f[Hold][2 + f[a]] against WL's f[Hold][f[Plus][2, f[a]]]).

A failed nested Part rebuilds the whole chain

wolframscript leaves Part[m, 3] unevaluated and applies the next index to that expression, so m[[3]][[1]] returns m and m[[1]][[1]][[1]] reports Part::partd: Part specification 1[[1]]. Woxi rebuilds the chain against the base and prints {{1, 2}, {3, 4}}[[1,1,1]]. Both agree on every in-range read; this shows only on the error path.

DateInterval canonical form

Woxi canonicalizes to a 4-argument form with "Day" granularity; current wolframscript produces a 6-argument form and derives granularity from the input (Year for a pair of year DateObjects). Both the arity and the granularity diverge. The 2- and 3-argument constructor calls fall through unevaluated.

Duration of a Video, DateInterval or Play

Valid objects with no message, but unmeasured — silently unevaluated. An interval-form SoundNote has absolute-placement semantics that are also not modelled.

InterpolatingFunction's raw InputForm

WL prints its internal representation (InterpolatingFunction[{{1, 3}}, {5, 3, 0, {3}, {3}, 0, …}, …]) while Woxi prints InterpolatingFunction[domain, data, order]. The property queries ("Domain", "Grid", "ValuesOnGrid", …) all match.

Compile produces a different object

Woxi's two-part CompiledFunction[specs, body] is nothing like WL's eight-part bytecode object, so the printed form can never match. The binding rules (untyped defaults to _Real, element-by-element conversion of rank-n real arguments, result type following the signature) do conform.

TimeSeries and TemporalData

The installed wolframscript (Tabular-backed internals) disagrees with Woxi's verified tests on several points — 0-based "Times", Length counting points, Part giving a {time, value} pair, "Values" as a TabularColumn. These are version drift, not Woxi bugs. Likewise Mean/Variance/Median/ StandardDeviation of a whole-valued series return machine reals in wolframscript (its packed Integer64 column being promoted) and stay exact in Woxi.

Still genuinely open: WL keeps a TemporalData[Automatic, {…}, False, version] internal form that Woxi normalizes to TimeSeries, and MovingMap[f, x, 0] should report MovingMap::wsizen (WL then returns a mangled DisplayForm value that is not worth reproducing, so Woxi's width check is silent).

Dataset variance

Variance[Dataset[{1., 2., 3.}]] is 1 against WL's 1.0000000000000002 — the float-noise class above.

Patterns, strings and parsing

A tagged assignment cannot have a curried left-hand side

wolframscript -code 'f /: g[a_][f[_]] := 1; UpValues[f]'
# {HoldPattern[g[a_][f[_]]] :> 1}
woxi eval 'f /: g[a_][f[_]] := 1; UpValues[f]'
# {HoldPattern[g[a_, f[_]]] :> 1}

tag /: lhs := rhs reads its left-hand side as a single call, so the two bracket groups of g[a_][f[_]] are merged into one argument list and the rule that gets stored fires on g[1, f[2]] instead. The upvalue store keys a rule by one head and a flat parameter list, which a subvalue-shaped rule has nowhere to go in; g[a_][f[_]] := 1 without a tag is fine (it becomes a SubValues entry). Reached through Hold/ReleaseHold the same definition reports TagSetDelayed::tagpos and defines nothing.

x_ y_ is mis-parsed

wolframscript -code 'Hold[x_ y_] // FullForm'
# Hold[Times[Pattern[x, Blank[]], Pattern[y, Blank[]]]]
woxi eval 'Hold[x_ y_] // FullForm'
# Hold[Pattern[x, BlankSequence[y_]]]

The parser merges the two _ across the space into a BlankSequence whose head becomes y_. x_ + y_ parses correctly; only the implicit-times juxtaposition is broken, so any implicit-times product of patterns is affected (ReplaceList[a b c, x_ y_ :> {x,y}] gives {} where WL gives six splits). Writing Times[x_, y_] explicitly works.

A bare Span cannot follow a Rule

a -> 2 ;; 3 fails to parse ("expected SpanSep"); WL reads it as Rule[a, Span[2,3]] because ;; binds tighter than ->. Parenthesizing works. This blocks the documented StringExtract[s, sep -> i ;; j] form. X::y on the right of ->/:> fails the same way.

f[{1, 2} // Total -> 3] does not parse

WL reads it as {1,2} // (Total -> 3), i.e. the postfix function is the rule. Making the rule's left side a postfix application parses it with the wrong precedence, which is worse than the parse error.

(a|b)|c loses its grouping

Explicit parentheses should give Length 2 (nested) in WL; Woxi's parser produces the same flat 3-argument form as a|b|c.

Parse-time messages do not exist

{a,,b} and f[a,] parse to Null as they should, but wolframscript also prints Syntax::com: Warning: comma encountered with no adjacent expression. once per omitted comma, at read time. Woxi has no parse-time message channel at all — wiring one through the AST conversion would duplicate the warning, because a cell is parsed more than once per evaluation. The same absence means a \!\(…\) escape with a bad form tag is left literal rather than reported as MakeExpression::boxfmt.

Empty matches are dropped

wolframscript -code 'StringCases["abcd", ___]'                    # {abcd, }
woxi eval 'StringCases["abcd", ___]'                              # {abcd}

wolframscript -code 'StringCases["abcd", ___, Overlaps -> True]'  # {abcd, bcd, cd, d, }

StringPosition["abcd", ___] likewise drops WL's trailing {5, 4}. The default and Overlaps -> True paths exclude empty regex matches and never visit the position one past the last character; the Overlaps -> All path handles them correctly.

Overlaps -> All with zero-width assertions

Any zero-width assertion other than a leading StartOfString or trailing EndOfString is evaluated against the probed slice rather than the whole string, so WordBoundary ~~ __ under Overlaps -> All diverges.

String pattern back-references need backtracking

A repeated pattern name is a back-reference (x_ ~~ x_ matches two equal characters). The regex engine has none, so Woxi emits a duplicate capture and compares the two after the match — which cannot backtrack to find an alternate assignment. Fine for fixed-width single-character blanks; variable-width (x__ ~~ x__) greedy assignment can miss a valid split.

Look-around and back-references in RegularExpression

RegularExpression["(?=[A-Z])"] cannot compile (Woxi now reports RegularExpression::badregex and leaves the call unevaluated rather than aborting). WL supports look-around and \1.

DatePattern element coverage

AMPM, Quarter, YearShort and other element names are unimplemented, as is DateDelimiters-style probing beyond the four verified separators [/-.:].

Longest/Shortest in a definition

The rule path (/., Cases, MatchQ) honours them; the definition path strips them, so f[Longest[x__], y__] := … splits shortest-first.

Unimplemented argument forms found by diffing against SyntaxInformation

Woxi's arity table can be diffed mechanically against wolframscript's own declared signatures:

(* for each implemented function name *)
"ArgumentsPattern" /. SyntaxInformation[Symbol["System`" <> name]]

Counting the required and optional slots (skipping anything with OptionsPattern or a BlankSequence, which are unbounded) and comparing against Woxi's declared maximum turns up every documented argument form that is missing. The sweep originally found 80; the 67 below are what is left.

Each one is a form wolframscript accepts and Woxi rejects on arity — so the failure mode is an ::argt / ::argb / ::argx message rather than a wrong answer, which makes them safe but individually invisible.

Function Woxi WL up to ArgumentsPattern
AbsoluteCorrelation 1–2 3 {_, _., _.}
AiryAiZero 1–1 2 {_, _.}
AiryBiZero 1–1 2 {_, _.}
AngerJ 2–2 3 {_, _, _.}
AngleBisector 1–1 2 {{_, _, _}, _.}
ArcCurvature 2–2 3 {{__}, _, _.}
BesselYZero 2–2 3 {_, _, _.}
BooleanMaxterms 2–2 3 {_, _., _.}
BooleanMinterms 2–2 3 {_, _., _.}
CellularAutomaton 1–3 4 {_, _., _., _.}
CenterArray 1–3 4 {_, _., _., _.}
CharacterName 1–1 2 {_, _.}
CircleThrough 1–1 3 {{__}, _., _.}
ConstantArray 2–2 3 {_, _, _.}
CoordinateBoundingBoxArray 1–3 4 {{_, _}, _., _., _.}
CoordinateBoundsArray 1–3 4 {{__}, _., _., _.}
Correlation 1–2 3 {_, _., _.}
Counts 1–1 2 {_, _.}
Covariance 1–2 3 {_, _., _.}
DigitCount 1–3 4 {_, _., _., _.}
DigitSum 1–2 3 {_, _., _.}
EulerAngles 1–1 2 {_, Optional[{_, _, _}]}
FindLinearRecurrence 1–1 2 {_, _.}
FrenetSerretSystem 2–2 3 {{__}, _, _.}
GammaDistribution 2–2 4 {_, _, _., _.}
GompertzMakehamDistribution 2–2 4 {_, _, _., _.}
GroupOrbits 2–2 3 {_, Optional[{__}], _.}
GroupStabilizer 2–2 3 {_, {__}, _.}
Groupings 2–2 3 {_, _, _.}
HarmonicNumber 1–2 3 {_, _., _.}
Head 1–1 2 {_, _.}
ImageAdjust 1–2 4 {_, _., Optional[{_, _}], Optional[{_, _}]}
Inner 3–4 5 {_, _, _, _., _.}
InverseChiSquareDistribution 1–1 2 {_, _.}
InverseErf 1–1 2 {_, _.}
InverseGammaDistribution 2–2 4 {_, _, _., _.}
InverseGaussianDistribution 2–2 3 {_, _, _.}
KendallTau 2–2 3 {_, _., _.}
Latitude 1–1 2 {_., _.}
LatitudeLongitude 1–1 2 {_., _.}
LegendreP 2–3 4 {_, _, _., _.}
LegendreQ 2–3 4 {_, _, _., _.}
ListConvolve 2–6 7 {_, _, _., _., _., _., _.}
ListCorrelate 2–6 7 {_, _, _., _., _., _., _.}
Longitude 1–1 2 {_., _.}
MaximalBy 1–3 4 {_, _., _., _.}
MeijerG 3–3 4 {{{___}, {___}}, {{___}, {___}}, _, _.}
MinimalBy 1–3 4 {_, _., _., _.}
MultipleHarmonicNumber 1–2 3 {_, Optional[{__}], Optional[{__, _}]}
NotebookDirectory 0–0 1 {_.}
ParentDirectory 0–1 2 {_., _.}
PerfectNumber 1–1 2 {_, _.}
PolyLog 2–2 3 {_, _, _.}
PositionLargest 1–2 3 {_, _., _.}
PositionSmallest 1–2 3 {_, _., _.}
Precedence 1–1 2 {_, _.}
QuantityQ 1–1 2 {_, _.}
RiceDistribution 2–2 3 {_, _, _.}
SpearmanRho 2–2 3 {_, _., _.}
Subsequences 1–2 3 {_, _., _.}
SubsetReplace 1–2 3 {_, _., _.}
Symmetrize 1–1 2 {_, _.}
SyntaxQ 1–1 2 {_, _.}
ToBoxes 1–1 2 {_, _.}
Uncompress 1–1 2 {_, _.}
Unique 0–1 2 {_., Optional[{__}]}
WeberE 2–2 3 {_, _, _.}

Notes on the ones already looked at:

  • MinimalBy / MaximalBy / DigitCount: wolframscript appears to ignore the extra trailing argument (MinimalBy[{{1,2},{3,1},{2,5}}, Last, 2, x] gives the same answer as without x), so there may be nothing to match.
  • Counts[list, n]: wolframscript rejects a non-list second argument with Counts::invl, so the slot is not a useful form either.
  • PositionLargest / PositionSmallest: the remaining slot is an orderfun, but wolframscript rejects Greater and Less there with ::nord3 (it wants an Order-style comparison returning ±1/0), and Order itself is the default — so there is little behaviour to match.
  • The generalized distribution constructors (GammaDistribution 2→4, InverseGammaDistribution 2→4, GompertzMakehamDistribution 2→4, RiceDistribution 2→3, InverseGaussianDistribution 2→3, InverseChiSquareDistribution 1→2) have real closed forms, but their output is Piecewise wrapping GammaRegularized, LaguerreL and nested Gamma quotients. Matching wolframscript there means matching its exact symbolic canonicalization.
wolframscript -code 'ToString[PDF[GammaDistribution[a, b, g, m], x], InputForm]'
# Piecewise[{{(g*((-m + x)/b)^(-1 + a*g))/(b*E^((-m + x)/b)^g*Gamma[a]), x > m}}, 0]
woxi eval 'PDF[GammaDistribution[a, b, g, m], x]'
# GammaDistribution::argrx: GammaDistribution called with 4 arguments; 2 arguments are expected.

A second sweep, comparing declared arities against the arities the dispatch actually handles, found these still open: Transliterate[s, scheme], SumConvergence[f, n, Assumptions -> …], LexicographicSort[list, ord], SavitzkyGolayMatrix[r, order, deriv], and the 3-argument forms of ClusteringComponents, ComponentMeasurements, ImageValue, WarpingCorrespondence, plus EventSeriesLookup ¾-argument.

Declared but dead options

For each option a head declares in Options[f], setting it should change the answer. These do not:

  • NIntegrate's MaxRecursion, MinRecursion, WorkingPrecision, MaxPoints, PrecisionGoal, AccuracyGoal.
  • NSum and NProduct accept WorkingPrecision and ignore it.
  • Root[…, ExactRootIsolation -> True] stays unevaluated.
  • D[f[x] g[x], x, NonConstants -> {f}] differentiates f anyway; WL holds it as D[f[x], x, NonConstants -> {f}]. (The plain D[x y, x, NonConstants -> {y}] case works.)
  • SetOptions[Plot, PlotStyle -> …] maintains the option list but does not reach the renderer.
  • Options[ListPlot] is {} against WL's 70 entries; Plot3D 85, Histogram 61, BarChart 64, Graphics 39. Only Plot has a table.
  • OptionValue[Plot, Axes] gives Axes instead of True, and $DisplayFunction/$PerformanceGoal are undefined so OptionValue[Plot, DisplayFunction] returns the unresolved symbol.
  • OptionValue's ::rep message for a non-rule element of the explicit option list is not emitted, and WL's own rule for when it fires is unclear (OptionValue[Plot, Axes, Hold] reports it, OptionValue[Plot, 5, Frame] does not).

N[head[patt_]] = body installs a DownValue on N, not an NValue

N[F[x_]] = x^2 should evaluate its LHS immediately (with F[x_] resolved through any existing F[x_] = … rule already in effect) and install the result as an NValues entry on the resolved head — a subsequent N[<that head>[n]] call then runs through N's own numericization, so an exact result still comes back as a machine real. Woxi resolves the head through the existing rule correctly, but stores the rule as an ordinary DownValue on N itself, so the body's result is returned exactly as written rather than coerced to a real:

wolframscript -code 'F[x_]=G[x]; N[F[x_]]=x^2; ClearAll[F]; {N[F[2]], N[G[2]]}'
# {F[2.], 4.}
woxi eval 'F[x_]=G[x]; N[F[x_]]=x^2; ClearAll[F]; {N[F[2]], N[G[2]]}'
# {F[2.], 4}

Messages and error handling

Partition reports ilsmp where the padded forms allow a zero block

With three or more arguments a block size of 0 is legal — the offset says how many empty blocks come out — so wolframscript rejects a bad size there with ilsmn ("non-negative"), keeping ilsmp ("positive") for the two-argument form. Woxi says ilsmp for every arity, and rejects a literal 0 outright once an alignment/padding argument follows:

wolframscript -code 'Partition[{a,b,c}, n, n, {1,1}, {}]'
# Partition::ilsmn: Single or list of non-negative machine-sized integers …
woxi eval 'Partition[{a,b,c}, n, n, {1,1}, {}]'
# Partition::ilsmp: Single or list of positive machine-sized integers …

wolframscript -code 'Partition[{a,b,c}, 0, 1, {1,1}, {}]'  # {{}, {}, {}}
woxi eval 'Partition[{a,b,c}, 0, 1, {1,1}, {}]'            # Partition::ilsmp

The unpadded Partition[{a,b,c}, 0, 1] agrees ({{}, {}, {}, {}}) — note that the padded form yields one block fewer, so the two count differently.

Expressions inside message text print in InputForm

WL formats a message's substituted expressions the way the front end shows them (Times as a space), Woxi formats them like InputForm (*):

wolframscript -code '2 x = 5'   # Set::write: Tag Times in 2 x is Protected.
woxi eval '2 x = 5'             # Set::write: Tag Times in 2*x is Protected.

The message tag, its arguments and the returned value all agree; only the rendering of an embedded expression differs, and it does so for every message that embeds one (Set::write, ImageAdjust::arg2, …). Each message site formats with expr_to_string; matching WL means routing them through the OutputForm renderer instead.

A refused base literal does not also report a syntax error

wolframscript treats base^^digits with a base outside 2–36, or with a digit the base cannot spell, as text it could not read: it reports the reason and then the syntax error that reading stopped at. Woxi reports the reason and returns $Failed without the second message.

wolframscript -code '1^^111'
# General::base: Requested base 1 in 1^^111 should be between 2 and 36.
# ToExpression::sntx: Invalid syntax in or before "1^^111 ".
# $Failed
woxi eval '1^^111'
# General::base: Requested base 1 in 1^^111 should be between 2 and 36.
# $Failed

Same for General::digit (2^^1012). This is the general shape of Woxi's read-time errors: input the grammar rejects reports a parser error of its own rather than ToExpression::sntx/::sntxi, and in a script the lines before it do not run.

Read does not report unreadable input

wolframscript -code 's = StringToStream["1+"]; Read[s]'
# Read::readt: Invalid input found when reading 1+ from StringToStream[1+].
# $Failed
woxi eval 's = StringToStream["1+"]; Read[s]'
# $Failed

The value agrees; only the message is missing. The reader that produces it sees the text and the requested type but not the stream expression the message names, so emitting it means plumbing that through.

Outer does not report mismatched heads

wolframscript -code 'Outer[f, h[1, 2], {a, b}]'
# Outer::heads: Heads List and h at positions 3 and 2 are expected to be the same.
# Outer[f, h[1, 2], {a, b}]
woxi eval 'Outer[f, h[1, 2], {a, b}]'
# Outer[f, h[1, 2], {a, b}]

The result is right — the call stays unevaluated either way — but the Outer::heads message is missing. Every other Outer form checked (per-list levels, general heads, operator form) agrees.

Options does not report an unknown option name

wolframscript -code 'Options[Graphics[{Disk[]}], Frobnicate]'
# Options::optnf: Frobnicate is not a known option for Graphics.
# {}
woxi eval 'Options[Graphics[{Disk[]}], Frobnicate]'
# {}

The value agrees. On a head with no options at all the second argument is dropped rather than answered, which does not:

wolframscript -code 'Options[f[1, 2], Axes]'   # Options::optnf: … ; {}
woxi eval 'Options[f[1, 2], Axes]'             # Options[f[1, 2]]

A list of option names is not accepted either — Options[g, {Axes, PlotRange}] gives {} where WL answers both.

Rasterize with an unknown element aborts the whole script

wolframscript -code 'Print[ToString[Rasterize[x, "Text"], InputForm]]; Print["after"]'
# Rasterize::elmntavl: "Text" is not an available element. Possible elements
# include "BoundingBox", "Data", "Graphics", "RasterSize", "Regions", and "Image".
# $Failed
# after
woxi eval 'Print[Rasterize[x, "Text"]]; Print["after"]'
# Error: Evaluation error: Rasterize: unsupported expression type

The hard error kills the run, so nothing after it evaluates. Rasterize should emit Rasterize::elmntavl and return $Failed.

Twenty-two heads raise a hard error for a bad first argument

wolframscript -code 'ImageCrop[{1, 2, 3}, -1]'
# ImageCrop::imgvinv: Expecting an image, graphics or video instead of {1, 2, 3}.
# ImageCrop[{1, 2, 3}, -1]
woxi eval 'ImageCrop[{1, 2, 3}, -1]'
# Error: Evaluation error: ImageCrop: first argument is not an Image

A sweep of the 855 heads whose arity admits two arguments, against nine degenerate argument pairs, found 227 calls that abort the enclosing evaluation instead of reporting. What remains is this one shape, spread over While, TuringMachine, Find, ReadList, Piecewise, ImageApply, ImageAssemble, ImageCollage, ImageCompose, ImageCrop, NMinimize, NMaximize, FindSequenceFunction, RealDigits, Dt, FrenetSerretSystem, ArcCurvature, MinimalPolynomial, Root, NumberFieldSignature, WordCounts and DivisorSigma.

The argument is nonsense in every case, so no correct result is lost — but a hard error takes the whole script down where a message does not.

An operator form that fails names the flattened call, not what was written

wolframscript -code 'Select[EvenQ][5]'
# Select::normal: Nonatomic expression expected at position 1 in Select[EvenQ][5].
# Select[EvenQ][5]
woxi eval 'Select[EvenQ][5]'
# Select::normal: Nonatomic expression expected at position 1 in Select[5, EvenQ].
# Select[5, EvenQ]

f[spec][data] is rewritten to f[data, spec] before dispatch, and when the call then fails both the message and the echo describe the rewrite. The same happens for SortBy, KeyTake and Nearest, so this is one issue in the operator-form dispatch rather than a per-function one.

Iteration constructs substitute their variable instead of binding it

Table, Sum and Product rewrite the iterator symbol into the body rather than binding it dynamically the way Wolfram does, so a held occurrence is replaced instead of left standing: Table[Hold[k], {k, 1, 2}] gives {Hold[1], Hold[2]} where Wolfram gives {Hold[k], Hold[k]}, and Sum[Hold[k], {k, 1, 2}] gives Hold[1] + Hold[2] instead of 2 Hold[k]. The substitution is capture-avoiding, so a nested Module/With/Function binder of the same name is left alone; only unbound held positions diverge.

$GeoLocation and location-defaulting messages

Woxi returns Missing["NotAvailable"] — matching an offline wolframscript — but does not emit $GeoLocation::dloff or the per-function Fn::geoloc.

Message repetition and 2D layouts

  • DensityPlot's degenerate-range message is reported under its internal context name (Visualization`Core`DensityPlot::plld) and emitted twice; Woxi cannot produce that symbol name and leaves DensityPlot out of the check entirely.
  • PolarPlot, ContourPlot3D and two-parameter ParametricPlot emit the message twice (a second internal pass); Woxi emits it once.
  • Hash::invhash is printed twice by wolframscript for an unknown algorithm.
  • Quantity::compat is emitted twice by wolframscript, without quotes, and its two unit names appear in an order that is not input order (Kilograms + Meters → "Meters and Kilograms"). Woxi quotes them and emits once.
  • Messages that embed a fraction are rendered as 2D layouts by wolframscript and 1D by Woxi. This is systemic across the distribution and Select::normal message families.
  • Message multiplicity in general is not comparable: wolframscript re-evaluates a failing specification, so it prints some messages twice, and applies General::stop after three identical ones.

A too-deep Part on a packed array is Part::partd1

Which of the two messages wolframscript picks depends on whether the object is a packed array, a storage detail Woxi has no equivalent of. Woxi always emits Part::partd, which is the unpacked answer:

# Unpacked — both engines agree:
wolframscript -code '{{1, 2}, {3, 4}}[[1, 1, 1]]'
# Part::partd: Part specification …[[1,1,1]] is longer than depth of object.

# Packed (anything Range, Table or Permutations built) — WL switches message:
wolframscript -code 'Range[5][[1, 1]]'
# Part::partd1: Depth of object {1, 2, 3, 4, 5} is not sufficient for the
# given part specification.
woxi eval 'Range[5][[1, 1]]'
# Part::partd: Part specification {1, 2, 3, 4, 5}[[1,1]] is longer than depth …

Packing the literal with Developer`ToPackedArray and unpacking the Range with Developer`FromPackedArray swap the two messages over, which is how the rule was confirmed. The values agree — both engines return the Part expression unevaluated.

ArgMax with an invalid constraint form

ArgMax[{list}, fn] — WL emits ArgMax::consf, Woxi silently echoes.

Failure prints its formatted message

In script mode wolframscript does not print a Failure[…] expression — it prints the failure's formatted message, wrapping every substituted parameter in DisplayForm[TagBox[…, Short[#1, 3] &]]. That is typeset internals leaking into script mode; Woxi prints the expression and renders ["Message"] cleanly. Deliberately not reproduced — test a Failure's keys, not its printed form.

Graphics and plotting

Woxi renders its own SVG; wolframscript prints -Graphics- and exports cairo-rendered SVG, so pixel or markup equality is never the goal. What follows is where the drawn result differs.

The default plot style is not Wolfram's

Every Woxi plot draws its first curve in #5E81B5 at 1.5 px. Wolfram draws it in RGBColor[0.24, 0.6, 0.8] = (61, 153, 204) at AbsoluteThickness[2]. Woxi is using the ColorData[97] palette where Plot has its own DefaultPlotStyle, readable from the kernel:

"DefaultPlotStyle" /. (Method /. Charting`ResolvePlotTheme[Automatic, Plot])

Woxi already writes the right values in its .nb GraphicsBox export; only the SVG renderer disagrees. Changing it moves 153 snapshots and about 40 inline colour assertions at once.

Graphics with Axes clips its edge tick labels

Graphics[…, Axes -> True, ImageSize -> {400, 400}] lays its plotting area out from x = 0 with only a right-hand margin, so the tick label at the range minimum is centred on x = 0 and half of it falls outside the canvas; the topmost y label is clipped the same way. Wolfram insets the area far enough for both. This is the Graphics renderer, not the one Plot uses.

Tick step from the padded range

Woxi derives the tick step from the padded display range, so a ListPlot whose data ends at 6e15 can pick a coarser step than Wolfram, which uses the data range.

ParametricPlot samples uniformly

max(PlotPoints, 500) points, no adaptive refinement (Plot has it). On a 51-term trig sum with PlotPoints -> 100 wolframscript emits 6052 line points against Woxi's 500. The values agree exactly and the shape is the same; only small-scale wiggles are under-resolved.

Plot parts nest differently

wolframscript wraps curves in GraphicsComplex / Annotation where Woxi emits plain Line/Arrow lists; SphericalPlot3D samples a different grid; Graph[…, opts] echoes its options unwrapped where wolframscript wraps them in a list.

Chart functions have no symbolic primitive list at all

PieChart, BarChart and friends render straight to SVG, so unlike Plot they expose no Graphics[{…}] to index into: Head[PieChart[{0.3, 0.7}][[1]]] is Part in Woxi (the Part stays unapplied on the opaque graphic) against List in wolframscript, and a /. rule that matches nothing leaves the whole expression alone rather than rewriting inside it. Making the charts symbolic means routing them through the Graphics layer the plots use.

PointSize in PlotStyle and ListPlot's PlotLabel

PointSize inside PlotStyle is dropped — the three scatter renderers hardcode the marker size. ListPlot[…, PlotLabel -> "x"] parses the option but never draws it (Show[…, PlotLabel -> …] does work).

An AxesLabel carrying a reciprocal power

A label's Power[x, -1] renders as x⁻¹ in SVG markup where Wolfram stacks it as the fraction 1/x.

Graphics3D lighting and projection

With the same scene both engines place every primitive identically, but:

  • Lighting. Woxi applies a single diffuse term; Wolfram adds specular highlights and coloured light sources, so faces of one polygon come out at noticeably different brightness (its default face colour reads cream where Woxi's reads blue-grey). Woxi also clamps intensity to 1, so a face can never be brighter than its base colour, while Wolfram's ambient plus three directional lights routinely saturate a channel.
  • Projection. ViewAngle drives a perspective camera in Wolfram; Woxi's projection stays effectively orthographic, so a lattice looks uniform instead of splaying toward the viewer. A wide angle (≳30°) leaves the silhouette a few percent narrow.

Compare geometry — vertex positions, which primitives appear, occlusion order — not pixels.

Specularity is accepted and ignored

Measured against wolframscript on a sphere under an explicit Lighting spec matching Woxi's model, Specularity[0.7], Specularity[1] and no Specularity at all give pixel-identical output. The highlight only appears under WL's default light set. So a specular term added to Woxi's single-light model would be invented behaviour; it needs the default lighting first.

SphericalRegion -> True shrinks about 17 % more than Wolfram

On a cylinder from {0,0,0} to {10,0,0} at ImageSize -> {550, 350} the trimmed figure width is 324 px in Wolfram and 268 px in Woxi; without the option both are 532. Working backwards, Wolfram behaves as if it fit the enclosing sphere into 399 px of a 550×350 frame — neither dimension, nor their mean, diagonal or geometric mean. What radius it normalizes by was not decoded.

Texture and VertexTextureCoordinates

A textured solid renders as flat-shaded default-blue polygons. Implementing it means carrying uv pairs per triangle and emitting the image once into <defs> with per-triangle clip paths and an affine matrix — a renderer feature, and one that cannot be checked against wolframscript by diffing SVG (it rasterises 3D output).

Complex* plot functions are unimplemented

ComplexPlot, ComplexPlot3D, ComplexListPlot, ComplexArrayPlot, ComplexContourPlot, ComplexRegionPlot, ComplexVectorPlot and ComplexStreamPlot are registered as graphics-producing heads (so Head[…] behaves) but have no implementation; wolframscript renders all of them.

GeoGraphics view and output form

The zoom level and single-point default span are Woxi heuristics and will not match wolframscript's view. GeoGraphics text output is -Graphics- in Woxi where wolframscript dumps the full GeoGraphics[-Graphics-, GeoBackground -> …, GeoCenter -> …] options form, and GeoRegionValuePlot returns a bare Graphics where WL wraps it in Legended.

Geodesic values differ in the last 2–4 digits

GeoDistance and friends agree with WL to ~12 significant figures. Wolfram displays the shortest round-trip of its own geodesic f64, which differs from geographiclib (both the Rust crate and the canonical Python reference) by a few to tens of ULP. Not reproducible with this library.

Entity["City", …] resolves to the region centroid, because the offline country database has no city-level data — Munich resolves to Bavaria's centre rather than WL's actual Munich coordinates. GeoNearest returns a human name where WL returns its canonical entity id.

DiskSegment and other region primitives are not drawn

DiskSegment has its measures but no graphics rendering.

Buttons inside a picture are not clickable

Inset[Button[label, action], pos] inside a Manipulate body draws correctly and the action is held, but pressing it does nothing: the renderer does not report each inset button's pixel rectangle, and Woxi Studio has no hit-testing against the rendered picture.

Manipulate control gaps

  • A TabView of controls is flattened — every tab's controls are shown at once, since Woxi's control panel is one flat list.
  • One unrecognised control spec makes the entire Manipulate give up, so a single unsupported control takes every other control with it.
  • Manipulate[…, Initialization :> …] leaks its definitions into the session.

Woxi's own SVG cannot be compared

wolframscript's ExportString[…, "SVG"] is always cairo output — pt units, text as glyph outlines, rgb(%) colours — for every graphics type. A test that prints raw SVG can never match. Assert the meaning instead (Area[Polygon[…]] rather than counting <polygon> tags).

Cairo also stamps per-run ids into the output, so even comparing wolframscript with itself fails:

wolframscript -code 'ExportString[Graphics[Line[{{0, 0}, {1, 1}}]], "SVG"] ===
                     ExportString[Graphics[Line[{{0, 0}, {1, 1}}]], "SVG"]'
# False

Two pictures that are the same drawing therefore cannot be compared through ExportString in a doc test either; pin those in tests/interpreter_tests/graphics.rs instead.

Play prints as -Sound-

wolframscript -code 'Play[Sin[2 Pi 440 t], {t, 0, 1}]'
# Sound[SampledSoundFunction[CompiledFunction[{11, 15., 5446}, …], 8000, 8000]]
woxi eval 'Play[Sin[2 Pi 440 t], {t, 0, 1}]'
# -Sound-

wolframscript compiles the amplitude function and prints the whole compiled object, internal register layout and all. Woxi wraps the inert Play call in a Sound, which reports the same Head and renders the same playable widget, but prints as the short form. Test with Head, AudioSampleRate or AudioLength, never against the printed form.

GraphPlot accepts a DirectedEdge-keyed edge shape rule

wolframscript -code 'Head[GraphPlot[{1 -> 2, 2 -> 3},
                       EdgeShapeFunction -> {DirectedEdge[1, 2] -> (Line[#1] &)}]]'
# GraphPlot   (unevaluated, no message)
woxi eval 'Head[GraphPlot[{1 -> 2, 2 -> 3},
             EdgeShapeFunction -> {DirectedEdge[1, 2] -> (Line[#1] &)}]]'
# Graphics

wolframscript's GraphPlot builds an undirected graph out of a rule list, so only an UndirectedEdge-keyed rule matches a part of it; a DirectedEdge key matches nothing and the whole call is abandoned without a message. Woxi accepts either key. Not reproduced: silently abandoning the plot looks like a defect, and the same call with an UndirectedEdge key conforms on both sides.

Images

HistogramTransform equalizes on the bands, not on a spline

Woxi maps each pixel to the midpoint of the band its value occupies in the cumulative distribution, rescaled from the 256 display levels to k/255. That reproduces wolframscript exactly whenever the distinct values are few or evenly spread — a four-value ramp, a two-value image, a constant channel, and the 256-level identity all agree to the last bit — but wolframscript actually interpolates the cumulative distribution (its Interpolation::inhr message leaks out for a constant channel), so for other value distributions the two differ by up to about 3·10⁻³:

wolframscript -code 'ImageData[HistogramTransform[Image[{{0., 0.25, 0.5, 0.75, 1.}}]]]'
# {{0.1, 0.301961, 0.501961, 0.701961, 0.901961}}
woxi eval 'ImageData[HistogramTransform[Image[{{0., 0.25, 0.5, 0.75, 1.}}]]]'
# {{0.0984314, 0.299216, 0.5, 0.700784, 0.901569}}

Reproducing the rest needs wolframscript's exact spline through its 256-bin histogram. Test rounded (Round[100 …]) or on the exact cases above.

ColorBalance matches the Bradford model to ~3·10⁻⁴

Both engines run a von Kries adaptation in Bradford cone space over the same D50 working matrix, dividing away the chromaticity (XYZ normalized to Y = 1) of the reference. Woxi lands within about 3·10⁻⁴ of wolframscript, which is enough for Round[100 …] to agree everywhere but not for a bare ImageData comparison:

wolframscript -code 'ImageData[ColorBalance[Image[{{{0., 1., 0.}}}], Green]]'
# {{{0.863451, 0.863391, 0.863085}}}
woxi eval 'ImageData[ColorBalance[Image[{{{0., 1., 0.}}}], Green]]'
# {{{0.863393, 0.863393, 0.863393}}}

wolframscript's answer is not even neutral — the three channels differ in the fourth decimal — so its matrix product is not the textbook one. Ruled out: D65 primaries (an order of magnitude worse), the rounded Bradford inverse, and CAT02/plain-von-Kries cone matrices.

A two-channel image (gray plus alpha) diverges outright: Woxi balances the gray channel and drops the alpha, wolframscript mixes the two channels into something that is not the balance of either. Same family as the four-channel ColorConvert entry below — an unusual channel count with no colour space to go by.

Pruning[image] picks the other pixel of a final pair

The counted form Pruning[image, n] conforms exactly. The bare form, which wears every thin arc down to a single pixel, agrees with wolframscript everywhere except which of the last two pixels is kept:

wolframscript -code 'ImageData[Pruning[Image[{{0, 0, 1, 0, 0}, {0, 0, 1, 0, 0},
                                              {0, 0, 1, 0, 0}, {1, 1, 1, 1, 1}}]]]'
# the surviving pixel is in the bottom row
woxi eval '…'
# the surviving pixel is one row up

Woxi keeps the first pixel in raster order of a component that a pass would empty. That matches wolframscript for horizontal, vertical and diagonal pairs alike in every other case tried, so the rule wolframscript really uses is something else again — it is not raster order, reverse raster order, or distance from the component's centroid.

ColorConvert of a four-channel image with no colour space

wolframscript -code 'ImageData[ColorConvert[Image[{{{1., 0., 0., 0.25}}}], "RGB"]]'
# {{{1., 0., 0.25}}}
woxi eval 'ImageData[ColorConvert[Image[{{{1., 0., 0., 0.25}}}], "RGB"]]'
# {{{1., 0., 0.}}}

With ImageColorSpace -> Automatic and four channels, wolframscript builds its three output channels out of channels 1, 2 and 4 — it keeps the alpha and drops the blue. Given ColorSpace -> "RGB" explicitly it agrees with Woxi (alpha rides along untouched). Not reproduced: it looks like an off-by-one in wolframscript's channel handling, and the explicit spelling conforms.

ImageAdd and friends refuse mismatched dimensions

wolframscript -code 'ImageData[ImageAdd[Image[{{0.1, 0.2}}], Image[{{0.3}}]]]'
# {{0.4, 0.2}}
wolframscript -code 'ImageData[ImageAdd[Image[{{0.3}}], Image[{{0.1, 0.2}}]]]'
# {{0.5}}
woxi eval 'ImageData[ImageAdd[Image[{{0.1, 0.2}}], Image[{{0.3}}]]]'
# Error: Evaluation error: ImageAdd: images must have the same dimensions and channels

The result always takes the first image's dimensions, but the second image is read differently depending on which is larger: a smaller second image is applied at the top-left and the rest of the first is passed through, while a larger one contributes its last element (0.3 + 0.2 = 0.5), not its first. ImageMultiply and ImageSubtract behave the same way.

Refusing is at least honest, but it refuses with a hard error, which aborts the enclosing evaluation.

Four image heads accept a bad specification silently

wolframscript -code 'ImageCrop[Image[{{0.1, 0.2}}], x]'
# ImageCrop::arg2: x is not a positive integer, pair of integers, Full or Automatic.
woxi eval 'ImageCrop[Image[{{0.1, 0.2}}], x]'
# ImageCrop[-Image-, x]     (no message)

wolframscript -code 'ImagePad[Image[{{0.1, 0.2}}], x]'
# ImagePad::imgpadn: Expecting a number or a 2 by 2 matrix of numbers instead of x.
woxi eval 'ImagePad[Image[{{0.1, 0.2}}], x]'
# ImagePad[-Image-, x]      (no message)

wolframscript -code 'TotalVariationFilter[{1, 2, 3, 4, 100}, -1]'
# TotalVariationFilter::arg2: Expecting a non-negative real number, a vector of
# such numbers (for multi-channel images) or Automatic instead of -1.
woxi eval 'TotalVariationFilter[{1, 2, 3, 4, 100}, -1]'
# TotalVariationFilter[{1, 2, 3, 4, 100}, -1]   (no message)

These return the right expression and only the message is missing, so they are conformance gaps rather than wrong answers. Blur and ImageMultiply["x"] are missing ::bdrad and ::bdarg for the same reason. Note that TotalVariationFilter's second argument is a regularisation parameter, not a neighbourhood range, so it does not share the other filters' validation.

More than the documented number of arguments gives ::argt in Woxi where wolframscript routes through its options machinery and gives nonopt — systemic across ImageTake, ImagePartition and DistanceTransform.

FindThreshold is unimplemented and its return convention is unclear

wolframscript -code 'FindThreshold[Image[{{0., 1.}}]]'   # 0.498046875
woxi eval 'FindThreshold[Image[{{0., 1.}}]]'
# FindThreshold[-Image-] is a built-in Wolfram Language function not yet implemented in Woxi.

The Otsu machinery already exists — EdgeDetect uses it, and for the gradient images it is applied to it reproduces wolframscript exactly: bin into 256 bins spanning [min, max], maximise the between-cluster variance, and return min + k (max - min)/256 for the winning bin index k.

What blocks exposing it as FindThreshold is that the reported value is not always on that grid. FindThreshold[Image[{{0., 1.}}]] is 0.498046875, which is 127.5/256 — a bin centre, where the gradient cases return a bin edge. Other probes give 0.4988281324331183 for {{0.2, 0.8}} but an exact 0.25 for {{0.25, 0.5, 0.75}}, so the parity is inconsistent across inputs. This also blocks the one-argument (Otsu-default) form of MorphologicalBinarize.

EdgeDetect keeps a whole flat gradient plateau

wolframscript -code 'ImageData[EdgeDetect[Image[{{0, 0, 1, 1, 0, 0, 1, 1, 0, 0}}]]]'
# {{0, 1, 0, 1, 0, 1, 0, 0, 1, 0}}
woxi eval 'ImageData[EdgeDetect[Image[{{0, 0, 1, 1, 0, 0, 1, 1, 0, 0}}]]]'
# {{0, 1, 0, 1, 1, 1, 1, 0, 1, 0}}

A period-4 square wave is resonant with the default radius-2 kernel and produces a gradient magnitude that is exactly constant across six pixels. wolframscript keeps alternating ones there; Woxi's non-maximum suppression keeps the whole plateau.

No rule over the magnitudes of the two neighbours can tell index 3 from index 4 in that run — both have equal magnitudes on both sides — so the tie-break uses information that could not be identified from the outputs. Every non-degenerate case matches, over 15 reference cases spanning 1D and 2D shapes, colour and Byte images, and explicit thresholds. Not reproducible.

LaplacianGaussianFilter and DerivativeFilter are unimplemented

wolframscript -code 'First[ImageData[LaplacianGaussianFilter[Image[{{0., 0., 0., 1., 0., 0., 0.}}], 1]]]'
# {0., 0., 1.8418419230004717, -3.683683846000945, 1.8418419230004717, 0., 0.}
wolframscript -code 'First[ImageData[DerivativeFilter[Image[{{0., 0., 0., 1., 0., 0., 0.}}], {0, 1}]]]'
# {0.05771365940052183, -0.21539030917347252, 0.803847577293368, 0., …}

Both are feature gaps rather than divergences. Neither can reuse GradientFilter directly: DerivativeFilter normalises its kernel differently (the impulse responses do not agree up to a scale factor), and LaplacianGaussianFilter's three-tap response at radius 1 is a scaled second difference rather than the second-derivative-of-Gaussian kernel the name suggests.

Colour-space tags do not propagate

ImageTake and ImagePartition propagate an image's colour-space tag; Blur, ImageAdjust, blends, morphological operations and ImageResize all construct an untagged image. Wolfram probably propagates — each needs its own probe.

LABColor and its family

LABColor is deliberately out of scope; XYZColor, LUVColor and LCHColor belong to the same family, whose ColorConvert round-trips go through D50/D65 white points and WL-specific matrices that would all need to match bit for bit.

ColorData named gradients

ColorData["Rainbow", 0.5] and ColorData["Rainbow"] (a ColorDataFunction) are unimplemented. ColorData[name, "ColorList"] is Missing["NotApplicable"] in WL, so the control points are not exposed — they would have to be recovered by sampling and the interpolation scheme guessed. The indexed schemes (1, 2, 3) are implemented but scheme 1 agrees to ~15 digits rather than bit for bit.

ColorQuantize is a stub

Echoes unevaluated; a median-cut palette choice would likely be a selection-artifact problem.

ImageMeasurements intensity on colour images

WL takes the luma and carries its Real32 rounding (MinIntensity of pure red is 0.29899999499320984); those measures are left unevaluated in Woxi.

GaussianFilter on an image

Differs in the last digits because wolframscript rounds its intermediates to f32 while Woxi rounds only at the boundaries. wolframscript is also inconsistent with itself here, giving 0.80123907 and 0.80123901 for the same kernel tap across evaluation orders.

AudioTrim on tiny objects

wolframscript gives {{1., 0.}} for {0., 1., 0.} but {{1.}} for {1., 0., 1.}, dropping a non-silent sample — minimum-window artifacts at 44.1 kHz, not a rule worth matching. FourierDCT/FourierDST differ in the last ULP.

Export of a multi-segment Sound

Single Play and Audio exports are byte-identical to wolframscript. For Sound[{Play, Play}] segment 1 matches byte for byte, but WL applies an unexplained affine requantization to segment 2 (residual ≈ 0.54·s − 0.54 LSB, uncorrelated with the time shift; not float32 rounding, not floor-versus-round, not global normalization). Maximum error ≤ 1 LSB of int16. Not reproducible.

Graphs

Wolfram's graph layer is igraph-backed, and two classes of its output are not recoverable from outside.

GraphData[] carries a slice of the atlas, not all 12474 entries

Woxi bundles the named entities it can answer every property for, plus the parametrized families ({"Complete", n}, {"Cycle", n}, {"Path", n}, {"Star", n}, {"Wheel", n}, {"CompleteBipartite", {m, k}}). Names, vertex labelling and edge order follow Wolfram's, and an unknown entity gets the same GraphData::notent message and unevaluated result — but Length[GraphData[]] is 14 against WL's 12474, and any entity outside the bundled set is a notent where WL answers. Only the ~760-entry property list is likewise abridged: GraphData["Properties"] returns the nine properties Woxi answers.

Selection among equal optima

FindVertexCover, FindEdgeCover, FindVertexCut and FindEdgeCut each pick one minimum solution via igraph internals. Three candidate models (lexicographically smallest, complement of the lexicographically first or last maximum independent set, greedy minimum-degree) are each contradicted by probes: CompleteGraph[4]{1,2,3}, CycleGraph[6]{2,4,6} (not {1,3,5}), GridGraph[{2,3}]{2,3,6}. FindVertexCut[PathGraph[{1..5}]] is {4} where {2} and {3} are equally small. Not reproducible.

KnapsackSolve is the same shape: value-optimal solutions are easy, but WL's choice among ties follows its LinearProgramming simplex/branch-and-bound. Reverse-index DFS fits some probes and max-count others, and the two contradict.

Vertex order within a component

ConnectedComponents on an undirected graph: Graph[{1<->2, 3<->4}] gives {{1,2},{3,4}} in Woxi and {{2,1},{4,3}} in WL. WeaklyConnectedComponents already ships a divergent order for the same reason — {1→2} lists as {2, 1} but the structurally identical {7→8} lists as {7, 8}, and {a→b} lists as {a, b}. Every hypothesis tried (original order, DFS pre/post, BFS from the target, degree-based start) is contradicted. WeaklyConnectedGraphComponents is unimplemented for this reason. Not reproducible.

TopologicalSort vertex order

WL's vertex ordering is not reproducible by DFS reverse-postorder or by Kahn with a min/max heap: {1->3, 2->3}{1,2,3} needs Kahn-min, but {1->2, 1->3, 2->4, 3->4}{1,3,2,4} needs DFS, and the two contradict. Also {1->2, 3->4}{3,4,1,2} and Graph[{1,2,3}, {1->3}]{1,3,2}. A sweep of 115 random DAGs against 42 candidate algorithms (Kahn with a min/max heap, FIFO and LIFO keyed on either VertexList index or vertex value; DFS reverse-postorder over four start orders × four successor orders; each of those on the transposed graph) tops out at 67/115 — no candidate fits.

The order in fact does not depend on the graph alone. Graph[{4,1,2,7,3,6,5}, {6->7}] and Graph[{1,2,3,4,5,6,7}, {6->7}] both give {3,6,7,1,2,5,4}, so VertexList order is mostly ignored; but Graph[{7,6,5,4,3,2,1}, {6->7}] gives {3,6,7,1,5,2,4}, and relabelling the same graph to the matching strings gives {c,d,e,f,g,b,a}. That is WL's internal vertex hashing showing through, and it is deterministic per run but not derivable. Woxi always returns the lexicographically smallest topological order (Kahn's algorithm, ties broken by VertexList index) — a valid ordering, just not always WL's. Not reproducible.

FindHamiltonianPath choice

Graph[{1 <-> 2, 1 <-> 3}] answers {3, 1, 2} while the structurally identical Graph[{2 <-> 1, 1 <-> 3}] gives {2, 1, 3}. Woxi does a greedy DFS from the first vertex. Not reproducible.

Provenance-dependent edge order

Generator graphs (CycleGraph, CompleteGraph, StarGraph, PetersenGraph) and explicitly constructed Graph[{…},{…}] objects produce different edge orderings from the same operations (Subgraph, LineGraph, NeighborhoodGraph). Woxi implements the generator convention. When a neighborhood or subgraph covers the whole graph, WL emits igraph's BFS traversal order.

GroupCentralizer generator order

The returned generators are almost the structural centralizer generators, but the ordering contradicts itself across cases — S4/(12)(34) gives rotations ascending while S6/(12)(34)(56) reverses the first pair, and fixed points use descending adjacent transpositions unlike the 2-cycle classes' star swaps. It is Schreier–Sims discovery order. Not reproducible.

VertexCoordinates annotations

An item-level VertexCoordinates read returns the laid-out coordinate, so Woxi answers from its own GraphEmbedding — the values diverge because the layout differs.

Multi-entry rule lists inside a graph option

WL scrambles the order of a multi-entry rule list inside an option (VertexLabels -> {2 -> "b", 1 -> "a"} for the input {1 -> "a", 2 -> "b"}) — internal hash order. Woxi keeps input order.

Chemistry data

IsotopeData carries NIST's isotope table, not the full ~3000-nuclide chart

Like GraphData's atlas slice above, Woxi bundles the isotopes from NIST's "Atomic Weights and Isotopic Compositions for All Elements" (the same table ElementData["…", "AtomicWeight"] is built from): one or two naturally occurring isotopes for every element that has any, plus the longest-lived known isotope for every element that has none — 354 isotopes total, against Wolfram's own curated chart of roughly 3000. An isotope outside that set is unrecognized where WL answers it.

ElementData[…, "StableIsotopes"] draws from the same table and applies the same coarser cut: an isotope counts as "stable" when NIST reports a measured natural abundance for it, not by true nuclear-decay stability. Long-lived primordial radionuclides that still occur naturally (potassium-40, rubidium-87, thorium-232, the natural uranium isotopes, …) are included, matching how these elements are conventionally described in chemistry references, but diverging from a strict "does it decay" reading.

IsotopeData[…, "BindingEnergy"] is not looked up — it is computed from NIST's atomic mass for the isotope via the standard mass-excess formula BE = (Z·m(¹H) + N·m(n) − M(A,Z))·c², using CODATA 2018 constants for the neutron mass and the u→MeV conversion factor, then divided by the mass number (wolframscript reports the binding energy per nucleon). This reproduces textbook values (carbon-12's 92.16 MeV total, 7.68 MeV per nucleon) but agrees with wolframscript's own tabulated value only to about seven significant figures — 7.6801447 against WL's 7.6801446 for carbon-12.

IsotopeData[…, "AtomicMass"] and …, "IsotopeAbundance" carry the same units as wolframscript ("AtomicMassUnit", "Percent") but as machine reals. WL tags its own values with the precision its measurement uncertainty justifies, which is not reconstructible from NIST's published digits:

Quantity[12.`9., "AtomicMassUnit"]
Quantity[98.9399999999999946176`3.2172206556445184, "Percent"]

The abundances themselves are NIST's, which are not always Wolfram's: NIST puts carbon-12 at 98.93 %, WL at 98.94 % (the difference between the tabulated representative composition and the best-measurement range it sits in).

IsotopeData["Properties"] lists the EntityProperty["Isotope", …] objects Woxi answers — seven, against the roughly sixty of Wolfram's curated chart (decay modes, half-lives, magnetic moments, medical applications, …). Same story as ElementData["Properties"].

Geometry and regions

Three region measures are missing for heads whose siblings have them

wolframscript -code 'ToString[{RegionDimension[Tube[{{0,0,0},{0,0,2}}]],
                     Area[Torus[{0,0,0},{1,3}]], SurfaceArea[Torus[{0,0,0},{1,3}]],
                     Volume[Hexahedron[]]}, InputForm]'
# {2, 8*Pi^2, Undefined, 1}
woxi eval '{RegionDimension[Tube[{{0,0,0},{0,0,2}}]], …}'
# each one echoed back unevaluated

Tube is absent from the intrinsic-dimension table (Cylinder and Cone are there). Torus has RegionMeasureRegionMeasure[Torus[{0,0,0},{1,3}]] is 8*Pi^2, correct — but Area/SurfaceArea do not route a 2-dimensional region to it the way they do for Sphere. Hexahedron's volume is only computed from the centre-and-edge Platonic form, not from the eight-vertex form its default expands to, which needs a general decomposition rather than a table entry. Independent of how the region was written: the zero-argument default forms diverge for the same reason the explicit ones do.

TriangleCenter accepts a triangle embedded in 3D

TriangleCenter[Triangle[{{0,0,0},{4,0,0},{0,3,0}}], "Circumcenter"] is {2, 3/2, 0} in Woxi; wolframscript handles only the planar case and leaves the call unevaluated. Deliberate — the 3D centre is well defined.

ConvexHullMesh is unevaluated for 3D point sets

wolframscript -code 'ToString[ConvexHullMesh[{{0,0,0},{1,0,0},{0,1,0},{0,0,1}}], InputForm]'
# BoundaryMeshRegion[{{0, 0, 0}, {1, 0, 0}, {0, 1, 0}, {0, 0, 1}},
#   {Polygon[{{3, 2, 1}, {2, 4, 1}, {4, 3, 1}, {3, 4, 2}}]},
#   Method -> {"SeparateBoundaries" -> False}, WorkingPrecision -> Infinity]
woxi eval 'ConvexHullMesh[{{0,0,0},{1,0,0},{0,1,0},{0,0,1}}]'
# ConvexHullMesh[{{0, 0, 0}, {1, 0, 0}, {0, 1, 0}, {0, 0, 1}}]

Computing the hull is the easy part; WL hands qhull's internal facet bookkeeping straight through, and three things would have to be replicated to match the printed Polygon:

  1. Facet order. No sort explains all three samples below.
  2. Vertex rotation within a face. Faces are outward-oriented, but the starting vertex varies.
  3. Coplanar merging. Triangles that share a plane come back as one polygon, so the cube's six faces are quads, not twelve triangles.

Reference outputs (all carry Method -> {"SeparateBoundaries" -> False}, WorkingPrecision -> Infinity):

points faces
{{0,0,0},{1,0,0},{0,1,0},{0,0,1}} {{3,2,1},{2,4,1},{4,3,1},{3,4,2}}
the same plus {1,1,1} {{3,2,1},{2,4,1},{4,3,1},{3,5,2},{5,4,2},{4,5,3}}
the eight unit-cube corners {{3,2,1,4},{1,2,6,5},{4,1,5,8},{2,3,7,6},{3,4,8,7},{5,6,7,8}}

Mesh cell order is qhull's

Which vertex a triangle starts at, and the order of the cells, follow qhull — {{1,2,3},{3,2,4}} against Woxi's {{1,2,3},{2,4,3}}, the same two triangles with the same orientation. Compare with Sort[Sort /@ MeshCells[m, 2][[All,1]]].

Wolfram is also inconsistent with itself here: Perimeter[DelaunayMesh[…]] of an exact square is 8. while Woxi gives the exact 8. RegionMeasure[Polygon, spec] in WL is a machine-precision quadrature value (3.366666189468263 for the exact 101/30) while its MomentOfInertia[Polygon] is exact — so Woxi leaves polygon RegionMoment unevaluated on purpose.

Symbolic region membership

RegionMember with a symbolic point is unevaluated for HalfSpace, StadiumShape and others. WL emits a Reduce-normalized condition Element[x | y, Reals] && <inequality> with non-trivial canonicalization: rational bounds cleared to integers, division by the coefficient gcd, negative-coefficient terms moved across, and an all-negative normal flipped to a >= 0 shape. Reproducing it is a mini-Reduce.

Region scope gaps

  • RegionDisjoint leaves ellipse Disk[c, {a, b}] (a quartic distance problem), ImplicitRegion, Annulus and Line unevaluated.
  • RegionMoment for an ellipse Disk is open, as is MomentOfInertia beyond what is implemented.
  • Simplices in a higher embedding dimension than their intrinsic dimension (a triangle given as three points in 3-space) need the Gram determinant and are unevaluated. The no-argument regular Tetrahedron[]/Simplex[n] defaults are only partly handled.
  • RegionBounds and RegionMember for Annulus are unimplemented.
  • SphericalShell and CapsuleShape SurfaceArea with symbolic radii is left unevaluated, because wolframscript itself hangs on it.
  • RegionMeasure[ImplicitRegion[…]] is hard in general.

PolyhedronData form divergences

Two entries in the hand-written Dodecahedron record print differently: Sqrt[5/8 + (11*Sqrt[5])/40] against WL's Sqrt[5/8 + 11/(8*Sqrt[5])], and a Sqrt[…] where WL gives Root[1 - 20*#1^2 + 80*#1^4 &, 3, 0]. Re-evaluating the first flips it to Wolfram's, so === against the stored value can be False for the same value depending on how many evaluation passes each side saw.

Molecule residues

[13C]-style isotope atoms get a WL UnpairedElectronCount property Woxi omits, and the no-carbon Hill ordering diverges — WL writes ammonium as NH4 (N before H) where Woxi keeps strict alphabetical order.

SpherePoints and ShiftRegisterSequence

SpherePoints[n] returns specific polyhedral configurations for small n, but the machine values leak WL's construction pipeline (SpherePoints[4] starts 0.9999999999999999), and large n switch to an undocumented regularized spiral. ShiftRegisterSequence's explicit-taps form contradicts its own default-form model: {4, {3,4}}, {5, {2,5}} and {5, {3,5}} all return {0} even though those polynomials are primitive, yet the n=4 default satisfies exactly the {3,4} recurrence. Not reproducible.

IsolatingInterval

No decodable convention: Sqrt[2]{45/32, 91/64} (width 2^-6), Sqrt[3] → width 2^-5, but 2^(1/3) and Root[#^5-#-1&,1]{0, 2} (unrefined), and Sqrt[2]+Sqrt[3]{1, 4} (width 3, not from bisection at all). The refinement depth comes from WL's internal interval-arithmetic convergence loop. Not reproducible.

JuliaSetIterationCount

Escaping orbits fit R = 2 + |c|² over four probes, and an undecided orbit gives cap+1. But bounded orbits return small counts via an attractor/cycle detection with internal tolerances — [0, 1/2] → 3, [0, 0.9] → 5, [0, 0.31] → 2, yet the parabolic [0.25, 0.1] → 1001. A fixed inner threshold needs ε ∈ (0.0343, 0.0625]; successive-difference and Brent-checkpoint models both fail. MandelbrotSetIterationCount was decodable and is implemented. Not reproducible.

FindClusters without an explicit cluster count

FindClusters[{1,2,10,11,20,21}] gives {{1,2,10,11},{20,21}} in Woxi and {{1,2},{20,21},{10,11}} in WL — a different k and a non-sorted emission order, both from an internal silhouette/gap heuristic. The explicit-k and DistanceFunction forms are correct. Not reproducible.

FindPeaks with a non-zero smoothing scale

FindPeaks[list, σ] is not FindPeaks[GaussianFilter[list, σ]]: at σ=2.2 the blurred list still has two peaks but FindPeaks reports one, and no blur radius reconciles the counts across σ. It is a scale-space tracking pass. A bare sharpness inherits σ as its scale and is not the plain second difference of the blurred data either. Woxi leaves any non-zero scale unevaluated.

Astronomy residues

SunPosition, MoonPosition and SiderealTime now agree closely, but:

  • The Sun's near-meridian azimuth differs by ~20″, because Meeus's short solar series is only good to 0.01°.
  • Sunrise/Sunset stay 13–30 s away: the half-day length implies wolframscript uses an effective horizon altitude near −0.885°, not the standard −50′ that Meeus documents. The offset is systematic across latitudes and seasons but has no physical derivation. Do not tune the constant to fit it.
  • SiderealTime is 0.28 s off (a different nutation/precession model).

Import, export, units and system

A plain read after a pattern read still sees the rest of the stream

ReadString[stream, patt] returns the text before the first match of patt and consumes the match. wolframscript implements that by pulling a 10 000-character chunk into a buffer: later pattern reads consume from the buffer and continue correctly, but a later plain ReadString[stream] reads from the underlying file position instead, which the chunk already moved past. On anything under 10 000 characters that means the rest of the stream is simply lost:

wolframscript -code 's = StringToStream["aaa%HEAD%rest"]; {ReadString[s, ___ ~~ "%HEAD%"], ReadString[s]}'
# {, EndOfFile}
woxi eval 's = StringToStream["aaa%HEAD%rest"]; {ReadString[s, ___ ~~ "%HEAD%"], ReadString[s]}'
# {, rest}

StreamPosition shows the same thing from the other side: after a pattern read wolframscript reports the chunk's file offset (10000, or the file size for a shorter file) where Woxi reports the logical position just past the match. Every value a pattern read returns agrees, including a whole sequence of them — WLJS Notebook's .wln reader is four pattern reads on one stream and gives identical text on both. Only mixing a pattern read with a plain read on the same stream diverges, which is why Woxi keeps the self-consistent behaviour rather than emulating the buffer.

ExampleData bundles its own data and properties

Woxi lists Wolfram's whole 228-entry "NetworkGraph" catalogue (so ExampleData["NetworkGraph"] and ExampleData::notent agree), but ships the data for only the handful of classic networks it assembled from the original sources, and exposes the properties that data supports — "VertexList", "EdgeRules", "AdjacencyMatrix" — none of which wolframscript knows (it answers ExampleData::notpropx). Its own list is ByteCount, Description, EdgeCount, EdgeProperty, FullGraph, Graph, LongDescription, Name, Source, StandardName, VertexCount, VertexProperty. ExampleData[] likewise names only the two collections Woxi serves, against wolframscript's nineteen, and the vertex names of a bundled network follow the original publication ("MlleBaptistine") rather than Wolfram's spelling ("Mlle Baptistine"). Deliberate: the catalogue is Wolfram's. Write tests against shape and presence, never against either side's catalogue.

A catalogued name whose data Woxi does not bundle stays quietly unevaluated (no ExampleData::notent — the name is known), where wolframscript returns the network:

wolframscript -code 'ExampleData[{"NetworkGraph", "WorldWideWeb"}]'  # Graph[…] — 325 729 vertices
woxi eval 'ExampleData[{"NetworkGraph", "WorldWideWeb"}]'            # ExampleData[{NetworkGraph, WorldWideWeb}]

That one is worth naming because its InputForm is a single 51 MB line: any harness that captures wolframscript's stdout has to cap what it prints.

ExampleData[{"TestImage", name}] ships no pixels

wolframscript -code 'ExampleData[{"TestImage", "Couple"}]'  # Image[NumericArray[…]]
woxi eval 'ExampleData[{"TestImage", "Couple"}]'            # ExampleData[{TestImage, Couple}]

Deliberate: the photographs behind that catalogue are not Woxi's to redistribute. The name catalogue is bundled, so ExampleData["TestImage"] returns the same {"TestImage", name} pairs and a script that builds an image picker from it (the "Histogram Equalization" Demonstration's popup, say) still gets the real entries. Asking for the data itself stays unevaluated rather than returning invented pixels.

ShortTimeFourier partitions differently

Woxi's default partition offset is half the window; wolframscript's is 1, so the same signal yields 3 frames against its 8. The property names differ too: Woxi answers "WindowSize", "Offset" and "NumberOfFrames" where wolframscript's ShortTimeFourierData has "PartitionSize", "PartitionOffset", "SmoothingWindow", "PartitionPadding", "FourierParameters", "SampleRate" and "DataType".

UnitSimplify is unimplemented

Two parts. The clean one: a compound dimension matching a named SI derived unit becomes that unit (N*m → Joules, kg*m/s^2 → Newtons, J/s → Watts), while metric units and no-named-unit compounds stay (1/s stays 1/s, not Hertz). The rabbit hole: a single imperial unit converts to a magnitude-dependent metric unit — Feet → 762/25 Centimeters, Miles → 25146/15625 Kilometers — and the metric-prefix choice (cm vs m vs km) is WL-specific and unpredictable. Implementing only the clean part would still diverge on the common cases.

Temperature arithmetic is not affine

expression wolframscript Woxi
Quantity[5,"Celsius"] + Quantity[3,"Celsius"] Quantity[5543/10, Kelvins] Quantity[8, DegreesCelsius]
Quantity[10,"Celsius"] - Quantity[3,"Celsius"] Quantity[7, DegreesCelsiusDifference] Quantity[7, DegreesCelsius]
Quantity[5,"Celsius"] + Quantity[3,"Kelvins"] Quantity[8, DegreesCelsius] a compat error
2 Quantity[5,"Celsius"] Quantity[5563/10, Kelvins] Quantity[10, DegreesCelsius]

Needs a full absolute-versus-difference temperature model with the DegreesCelsiusDifference / DegreesFahrenheitDifference units.

MixedRadixQuantity is unimplemented

Needs MixedMagnitude and MixedUnit first.

WXF serialization bails

BinarySerialize deliberately leaves these unevaluated rather than write bytes that would differ: a Plus/Times containing a numeric-complex term (WL sorts Complex first in sums and folds numeric factors into it in products), Pattern/Function/Slot expression kinds, BigFloat, and multi-operator comparison chains. The deserializer treats the NumericArray token as corrupt, since Woxi has no such object. BinarySerialize[Range[3]] is packed in WL — only literal lists are comparable.

CSV ColumnTypes

wolframscript types the last column of a labelled table as "String" when the text does not end in a newline, contradicting its own import of the same field as an integer. It also changes its CSV behaviour mid-session once any "Table" import has run — HeaderLines is ignored in a cold kernel and honoured after, and booleans are coerced in a cold kernel and left as strings after. Woxi implements the cold-kernel behaviour and stays newline-agnostic. Not reproducible.

Its ColumnTypes values are self-contradictory in the labelled two-column case as well: "a,b\n1,2" types the last column as String although it holds numbers, while "a,b,c\n1,2,3" types all three Integer64.

Import element lists

WL's element list for CSV names EventSeries and TimeSeries, which Woxi does not implement.

Named characters and Unicode drift

wolframscript's Unicode database is older than Rust's. A sweep of CharacterNormalize over the BMP and SMP found zero cases where wolframscript normalizes and Woxi disagrees — every difference is the reverse, Woxi normalizing a character wolframscript does not know yet (Latin Extended-D additions, Vithkuqi, Latin Extended-F, Todhri, ). Woxi's output is the standards-correct one. Never write a conformance test on a code point added after Unicode ~13.

Note also that wolframscript stores most named characters at private-use code points (\[Rule] is U+F522, \[WarningSign] U+F725), and mangles non-ASCII input in both -code and -file — several apparent divergences in this area are artifacts of that.

SeedRandom streams differ

Wolfram uses a proprietary generator, Woxi uses ChaCha8; no seed produces the same sequence. Verify determinism and distribution instead. Not reproducible.

Performance

Woxi's list operations are bound by expression-allocation throughput, not by algorithmic complexity: Expr::List holds an owned vector with no structural sharing, so building a large result deep-copies its elements. Tuples[{Range[4900], Range[70]}] (343k pairs) takes ~1.5 s in release — roughly 100× slower than wolframscript, which shares structure. A real fix needs Rc/Arc-backed lists or a different allocator.

woxi eval exits 0 on an evaluation error

The error goes to stderr but the exit code stays 0, so a shell check like woxi eval '…' >/dev/null 2>&1 && echo OK reports OK even when the expression failed. (wolframscript -file has the same property.)

woxi run does not echo the value an aborted script ended on

wolframscript -file prints the terminal value of a script that stopped early — $Aborted for Abort[], TerminatedEvaluation[RecursionLimit] for a recursion past $RecursionLimit — even though a script otherwise prints only what Print sends:

printf 'Print["a"]\nAbort[]\nPrint["b"]\n' > /tmp/ab.wls
wolframscript -file /tmp/ab.wls  # a  + $Aborted
woxi run /tmp/ab.wls             # a

Both stop before Print["b"], so only the echo is missing. woxi eval and wolframscript -code agree (both print $Aborted / TerminatedEvaluation[RecursionLimit]).

woxi repl does not tag the output prompt with the result's form

When a REPL line evaluates to an output-form wrapper, wolframscript moves the wrapper into the prompt and prints the wrapped expression:

In[1]:= 7//FullForm
Out[1]//FullForm= 7      (* wolframscript *)
Out[1]= FullForm[7]      (* woxi repl     *)

Same for MatrixForm, TableForm, InputForm and the other form wrappers. woxi eval is unaffected — script mode prints FullForm[7] on both sides. The REPL gets only the formatted string back from interpret, so tagging the prompt needs the result expression (and a per-wrapper rendering) plumbed through to repl.rs.

balanced_ternary computes a wrong value

btmultiply["+-0++0+", btsubtract[tobt@-436, "+-++-"]] gives +--0 in Woxi where wolframscript gives ----0+--0++0. The divergence is inside btadd's Fold over a transposed, padded character matrix combined with a multi-clause StringReplace over alternative patterns — a pattern-matching or accumulator bug, not the (already fixed) #0@ self-reference.

Sockets

The TCP socket functions (SocketOpen, SocketConnect, SocketListen, SocketReadMessage, SocketReadyQ, SocketWaitNext, SocketWaitAll, Sockets) were implemented against behaviour recorded from wolframscript in advance. The entries below are the parts of that surface no wolframscript run has confirmed: the machine Woxi is developed on has no Wolfram Engine, so these are Woxi's choices, not verified matches. Check each one against wolframscript before writing a conformance test on it.

Property values are unverified

The property names are the recorded ones — sock["Properties"] gives ConnectedClients, DestinationHostname, DestinationIPAddress, DestinationPort, DirectionType, InprocQ, Protocol, Scheme, SocketListener, Type, UUID, and lis["Properties"] gives CharacterEncoding, HandlerFunctions, HandlerFunctionsKeys, RecordSeparators, Socket — but only DestinationPort was ever seen with a value. Woxi answers "TCP" for Protocol, "tcp" for Scheme, "ZMQ_STREAM" for Type, "Server" / "Client" for DirectionType, False for InprocQ, "UTF-8" for CharacterEncoding and {} for RecordSeparators. Wolfram's socket layer is built on ZeroMQ, so the last three in particular may well read differently.

c["SourcePort"] returning {} was recorded, so Woxi answers {} for every property it does not keep. Whether wolframscript does the same for an arbitrary unknown property, or only for the ones it knows about but leaves empty, is untested.

Handler associations for events other than "DataReceived"

A "DataReceived" handler is called with the recorded seven keys — TimeStamp, SourceSocket, Socket, Data, DataBytes, DataByteArray, MultipartComplete — in that order. "DataReceived" is the only event name wolframscript accepts: any other key makes lis["HandlerFunctions"] read Missing["NotAvailable", "HandlerFunctions"]. Woxi keeps "Accepted", "Closed" and "Error" as extensions, and what wolframscript would pass such a handler was never recorded; Woxi passes TimeStamp, SourceSocket and Socket, plus a Message key for "Error".

A closed socket is closed again without complaint

wolframscript answers a second Close with a failure object naming the socket:

wolframscript -code 'srv = SocketOpen[0]; Close[srv]; Close[srv]'
# Failure["SocketsLink", <|"MessageTemplate" :> SocketObject::invalidSock,
#                          "MessageParameters" -> {SocketObject["TCPSERVER-…"]}|>]
woxi eval 'srv = SocketOpen[0]; Close[srv]; Close[srv]'
# "127.0.0.1:…"

The rest of Woxi's socket layer reports an unusable socket as $Failed plus a free-text line rather than as a Failure[…], so this is one instance of a wider difference in how the two report a bad socket.

Data and DataBytes are delayed association entries (key :> value), so a handler that only reads DataByteArray pays neither the text decoding nor one Expr per byte. Which of the keys wolframscript delays is unverified; the laziness itself is what the recorded behaviour called for.

SocketWaitNext and SocketWaitAll on a timeout

The one recorded data point is that both stayed unevaluated for a client socket with nothing pending and a one-second timeout. Woxi generalizes that: each waits for activity on the listed sockets — an event a listener handled, including one on a connection it accepted, or data waiting on the wire — and leaves the call unevaluated when the timeout runs out. SocketWaitNext returns the socket, SocketWaitAll the list it was given. Whether wolframscript's rule is the same, or whether "unevaluated" was reporting something narrower (a client socket being an invalid argument, say), is unknown.

SocketReadMessage[sock, n]

SocketReadMessage[c, 2] was recorded returning unevaluated with nothing pending, which is equally consistent with wolframscript having no two-argument form at all. Woxi reads up to n bytes of what has already arrived and stays unevaluated when nothing has — agreeing with the recorded case either way, but a superset if the form does not exist.

An unusable socket is $Failed, not a Failure[…] object

Verified against wolframscript on 2026-09-10, and the root of most of the entries in this section. Every operation on a socket wolframscript considers unusable returns the same object:

Failure["SocketsLink",
  <|"MessageTemplate" :> SocketObject::invalidSock,
    "MessageParameters" -> {SocketObject["baef81c0-…"]}|>]

and prints nothing — reads, Close on an already closed socket, and every property query, "UUID" and "Properties" included. (A read that fails for another reason carries a different template, e.g. SocketReadMessage::zmqerror with {"Interrupted system call"}.) Woxi prints the free-text line and returns $Failed instead.

Displayed at top level the Failure renders as its message text with the socket spelled as a front-end summary box (a DisplayForm[TagBox[…]] of several kilobytes of GraphicsBox), so no testcase that displays one can conform — which is why the closed-socket read lives in tests/interpreter_tests/sockets.rs rather than in the doc tests.

Resolved 2026-09-10: Woxi used to keep answering property queries on a closed socket (Close[srv]; srv["DestinationPort"] gave the port back where wolframscript failed). It now rejects them all with $Failed, silently — wolframscript prints nothing there either, since its failure is the returned object. Only the shape of the failure value still differs.

A refused connection blocks SocketReadMessage forever

Verified against wolframscript on 2026-09-10. Connecting to a port nothing listens on still yields a socket, because Wolfram connects lazily:

wolframscript -code 'c = SocketConnect["127.0.0.1:1"]; Head[c]'   # SocketObject

SocketReadMessage[c] then waits for a first byte that never arrives and never returns — there is no default timeout. Woxi detects the unusable socket instead and answers The socket object … is invalid or not open. plus $Failed.

tests/cli/sockets/SocketReadMessage.md used to run exactly that as a testcase; it now shows it as an untested example and leaves the behaviour to the unit tests, because no bound on the call makes it conform. Never run it against wolframscript: a stuck wolframscript holds the single license slot, so every later invocation fails with the misleading Your Wolfram Engine installation is not activated, which turns one hanging testcase into dozens of unrelated failed documents. scrut's per-document timeout does not prevent this — it reaps its own shell but not the wolframscript below it. The wo wrapper caps each attempt at WO_TIMEOUT seconds (300 by default) and kills the kernel with it for exactly this reason.

Error text

The socket object … is invalid or not open. and Failed socket operation: … are free text, not tagged messages: they leave $MessageList empty, which is what was recorded. The exact wording of the second was recorded only for a privileged-port bind (Permission denied); Woxi uses the operating system's message for every bind failure, and reports $Failed as the value.

A socket read races its own listener

Reading a connection directly (SocketReadMessage, ReadString, …) while a SocketListen handler is attached to it means two readers on one socket, and which of them gets a given chunk is a matter of timing. wolframscript polls the same socket from both places and presumably has the same problem, but this was not tested. Use one or the other, not both.

Timing of handler calls

Handlers run on the thread that evaluates Wolfram code — the only thread that may touch an Expr — so they fire where evaluation waits: Pause, a blocking socket read, SocketWaitNext/SocketWaitAll, SocketReadyQ with a timeout, and between statements. A long computation with no such point defers every handler until it ends. The recorded wolframscript behaviour agrees (a handler observably fired during a later blocking call rather than at write time), but only for that one case.

No transport but TCP

SocketOpen[port, "UDP"], ZeroMQ schemes ("inproc://…", "ZMQ_*" socket types) and SocketOpen's "ZMQ_STREAM"-family options are not implemented: anything that is not TCP leaves the call unevaluated. Sockets["TCP"] is accepted and is the same as Sockets[].