2.2.0
Infrastructure release. 2.1.0 was mostly wrong answers becoming right ones; this one is mostly the
layer underneath them — a real polynomial layer, and a written specification of what canonical
form means here with both halves implemented. The corrected answers in it are largely consequences
of those two rather than separate fixes.
Every changed answer is in BREAKING-CHANGES.md under 2.2.0 — since 2.1.0,
with the old value, the new one, and why, measured on a build of each version. Read that first if you
have code depending on an answer.
AssemblyVersion stays 2.0.0.0. See the rename note at the bottom before dropping the DLL in
without recompiling.
A polynomial layer
The single item that #746 names as
unblocking a large fraction of the tracker. Multivariate GCD, resultants, square-free decomposition,
and factorisation over ℚ and finite fields. (#918, #920/#923, #921/#927)
It is not shipped for its own sake — three things in this release are consequences of having it:
- A polynomial equation that factors is solved through its factors.
x^5 + 2x^3 - 2x^2 - 4
returned three of its five roots, one of them a float; it returns all five, exact.x^4 + x^2 + 1
loses a nested radical. (#918) - A rational function is decomposed over the factors of its denominator, not only its roots, so
1/(x^4 + 3x^2 + 2)and1/(x^4 + 4)now integrate instead of coming back unevaluated. (#926) - A resultant is bounded by measured work rather than by a reasoned size limit, which raised the
ceiling from a Sylvester matrix of 24 to one of 40 without risking the blow-up the old bound was
guessing at. (#921/#927)
Canonical form: specified, measured, and offered
Docs/Contributing/CanonicalForm.md states the position rather than leaving it to be inferred:
canonical is about identity, simplest is about presentation, and there is no canonical form for
the whole language — zero-equivalence is undecidable once pi, exp, the trigonometric functions and
abs are in play (Richardson 1968). So the specification is a canonical form on a decidable
sublanguage, a normalisation elsewhere that must not be mistaken for one, and a search that is not
required to be canonical at all. (#928)
Both halves are now reachable from Entity, beside Simplify and Factorize:
Entity Canonicalize() // the commutative structure: 0 idempotence and
// 0 order-independence failures over 834 expressions
Entity? CanonicalizeAsRationalFunction() // rational functions over Q -- or null, which is the
// library saying it has no answer rather than guessingx/x canonicalises to 1 provided not x = 0 and is deliberately not equal to the canonical form of
1: the quotient is undefined where the polynomial is not. (#933, #935, #940)
Nothing applies either by default. Simplify and InnerSimplified return exactly what they
returned before. Turning canonical ordering on by default would change every commutative operand order
in every printed answer, and that is a decision for a release that says so.
Wrong answers fixed
sin(-x) + sin(x)was left as written and is0— the parity identities were not being applied.
cos(-x)andabs(-x)fold too. (#929/#931)InnerSimplifiedis idempotent again. An exact trigonometric value reached through a half turn
came back as-(-1)where it should have been1. The value was never wrong, but applying
InnerSimplifiedtwice gave a different tree from applying it once, and much of the library treats
what it returns as settled. (#930/#932)- A negative pair keeps its sign. (#936/#937)
- The logarithm's domain follows the reading, as every other node's already did, so
log(-3, -3)
is no longer declared undefined while evaluating to1. (#721/#890, #916) log(x, x)was1 provided x > 0—NaNat every negativex, and1atx = 1where the
logarithm isNaN. (#916)d/dx x^ncarriedprovided x > 0, a condition it never needed, making the derivative undefined
at every negativex. (#916)- A sum of logarithms is no longer gathered unless that is exact —
ln(a) + ln(b) -> ln(a*b)is
wrong by2*pi*ioff the positive reals. Two limits lost to that guard in 2.1.0 are back, via an
ambient scope rather than a new pass. (#922, #925)
Under the hood
- A rewrite rule's left-hand side can be data.
MatchPatternmatches by enumerating solutions, so
commutative operands backtrack properly, and three rule sets are expressed as data and proved
equivalent to theswitchthey mirror. Internal for now — it is a prerequisite for
#746's rewrite graph, and for a rule being
able to carry its own justification. (#248, #938) - A measured performance pair is published in
Docs/WhatsNew/version_performance_control.md, both
columns re-measured on one machine. It caught four solver benchmarks 7–21% slower with allocation up
10–20%, isolated to the polynomial layer by bisecting on allocation. That is the price of the
factoring solver, recorded rather than quietly absorbed — and the same document notes that none of
the ten solver benchmarks factors, so the suite measures that change's cost and none of its benefit.
One rename
Five members were spelled -ise on a surface that is otherwise Factorize, Latexize,
Normalization. Three had not shipped; two had:
| Was (2.1.0) | Is |
|---|---|
Transformation.Rationalisation |
Transformation.Rationalization |
RewriteRules.RationaliseDenominator |
RewriteRules.RationalizeDenominator |
Recompiling turns a stale reference into a compile error. Swapping the DLL without recompiling does
not — AssemblyVersion is pinned at 2.0.0.0 so the assembly still binds, and the call throws
MissingMethodException when it is reached. Documentation prose keeps British spelling; the convention
is about identifiers. (#940)