Skip to content

2.5.0

Latest

Choose a tag to compare

@Rafael-SOWNet Rafael-SOWNet released this 09 Sep 15:51
· 694 commits to master since this release
ab7a8bd

Tier 2 of the Math OS roadmap — the
rewrite graph
— is finished in this release. An e-graph now lives in the kernel rather than in a
measurement harness, every one of the thirty registered rule sets runs as data through a real
e-matcher, and each rule declares its own soundness tier and how much it grows what it matches. The
honest result of that work is the part worth reading: Simplify still does not run on the graph,
because the tier asked for equality saturation to be evaluated against memory cost on real
expressions and the evaluation came back negative. A measurement that says "not by default" is the
deliverable, not a substitute for it.

Every changed answer is in BREAKING-CHANGES.md
under 2.5.0 — since 2.4.0
, with the old value, the new one and why, each measured on a build of
both sides. Read that first if you have code depending on an answer. Seventy-six entries at a
glance and thirty-seven worked sections; twenty-nine are marked Silent — the call still succeeds
and quietly returns something else — and two are Loud, where input that parsed now raises.

AssemblyVersion stays 2.0.0.0. The recorded public surface has 136 additions and no
removals
, so this is source- and binary-compatible: nothing that compiled against 2.4.0 stops
compiling, and nothing bound against it stops binding.

Wrong answers fixed

  • Integrate could recurse without bound and take the process down. A stack overflow is not
    an exception a caller can handle — the process aborts. Caught by running Rubi's independent test
    suites against the release candidate, where the run died with SIGABRT; v2.4.0 on the same
    corpus and the same machine ran past that point, so this was introduced in this release rather
    than a standing limitation. Two changes: the descent is bounded (#1233), and an integral already
    being worked out is recognised as a cycle and declined at once (#1234). The memo added in #1157
    could not have caught it, and neither could a set of visited shapes — SolveBySubstitution
    names its variable with Variable.CreateUnique, so every level is alpha-equivalent to the one
    before rather than equal to it, and the cycle guard has to rename before it compares (#1232).
  • Matrix operations answered wrongly on more than one thread. MathS.Multithreading documents
    concurrent use as supported. Forty 4×4 symbolic matrices, computed once sequentially and then
    rebuilt and recomputed in parallel: 38 of 40 determinants, 18 of 40 inverses and 11 of 40
    adjugates came back carrying another computation's values, and the elementwise operators threw on
    a corrupted dictionary. Two of the three causes were AngouriMath's own caches, grown under a lock
    and read outside one (#1227, #1229). The third is not ours — GenericTensor 1.0.4 hands every
    caller the same scratch matrix
    for a given size and then writes into it, reported as
    GenericTensor#40 with a fix offered in
    #41, and guarded here meanwhile (#1230).
  • A negation was answered with the empty set. The empty set is a positive claim, so saying it for
    something never settled is a wrong answer rather than a missing one (#1127, #1131).
  • Domain.Any was treated as the universal set when it is a codomain, so membership questions
    got an answer that was not asked (#996, #1126).
  • A negative arccotan was not negative. This library's arccotan has range (-π/2, π/2], not
    the textbook (0, π), and a guard written from the textbook shipped wrong (#1113).
  • The limit of z / |z| lost the phase of z, coming back with a real-line answer for something
    that approaches a different value along every ray (#1208).
  • A factor of −1 was taken out as though it were a factor rather than read as a sign (#1172).
  • A saturation check compared two things that have no value, so it passed on inputs it could not
    have decided (#1166).

The rewrite graph

The e-graph moved out of the harness and into the kernel behind an explicit opt-in (#1101), and what
matches against it is a genuine e-matcher — a pattern against an e-class, not against a term (#1103).
A cost model is a record taken as a parameter rather than read from an ambient setting (#1102), so
SmallestTree, FewestDivisions and FewestRadicals are things you pass rather than things you
set. Canonicalisation runs over the graph (#1105), rule priorities are computed from the patterns
instead of typed by hand (#1106), and all thirty registered sets run as data (#1192).

The parts that make that safe are tests rather than assertions by an author: a rule's declared
growth is checked against a corpus (#1158), a set that rewrites back to where it started is caught
(#1165), and so is one that never settles over a whole tree (#1168). The runaway that used to be
blamed on a whole soundness tier turned out to be one inverse pair across two sets (#1193), and
the growth ceiling turned out to already be the scheduling policy the documents kept deferring
(#1194, #1203).

Speed

Five changes, each measured at the entry point rather than in a microbenchmark, and each
answer-identical:

SolveHard −98.6% allocation — the solver now tries the equation as written before asking for its alternatives (#1209)
SimplifyHard −64% — a candidate already registered is not registered again (#1207)
SimplifyHard −44% — a candidate is registered once and a subtree costed once (#1205)
SimplifyHard −35% — the sort key of a node is spelt once (#1210)
SimplifyHard −25% — a candidate is re-simplified at the default level whatever level it was offered at (#1211)

Extraction from the graph became a fixed point computed from the leaves up rather than a top-down
walk whose cycle guard defeated its own memo: 1,918 ms to 4 ms on the case that exposed it, and a
budget now honoured to the millisecond (#1202).

New notation

  • a divides b is a statement node, printing as a \mid b in LaTeX (#1220). divides is now a
    keyword, so a variable of that name no longer parses — one of the two Loud entries.
  • #S is the number of elements of a set, with card(S) still accepted on input (#1221).
  • The extremum of an expression over a set, as a binder (#1222).
  • A lambda is written with an arrow as well as a call: x => x + 1 beside lambda(x, x + 1).
    Nothing that parsed before parses differently — = followed by > was not a token (#1151).

Closed forms

A summation with a closed form now has one, and each carries the condition it holds under rather
than asserting it unconditionally:

sum(k, k, 1, n)              →  (n + n^2)/2, with the condition on n
sum(x^k, k, 0, +oo)          →  1 / (1 - x) provided abs(x) < 1
sum(2^k, k, 0, +oo)          →  +oo, by the nth-term test, where it used to be left as written

Polynomial summands (#1152), monomial products (#1153), a power over a factorial (#1213), binomial
sums with a power or trigonometric weight (#1214), geometric series (#1218) and divergent ones
(#1224).

Integration reached further too: a biquadratic denominator decomposed over the reals (#1145),
fractional powers and the tangent as substitutions (#1146), improper quotients divided out before
decomposition (#1147), repeated quadratic denominators (#1155), and an integral across a jump split
at the jumps rather than taken through an antiderivative that treats a floor as a constant
(#1215). There is also an analytical solver for first-order linear ODEs (#1137).

Integration speed, and a correction to an earlier version of these notes

An earlier version of this page said the integrator was left much slower than 2.4.0 on hard
integrands. That was wrong, and it was my measurement error: the figures behind it came from a
corpus run that had the full unit suite running beside it on the same machine, which inflated the
sections being compared. Measured properly — the seventeen hardest integrands from that part of the
corpus, one process, nothing else running, two runs of each arm — 2.5.0 is about 30% faster:
72,017 and 71,390 ms for 2.4.0 against 53,159 and 47,996 ms here. Several individual integrands
halve. The details are on
#1232, which is closed.

The honest caveat that survives the correction: partial fractions over repeated factors with high
powers is slow in both versions — 1/((2+x)^3*(3+x)^4) takes about half a minute either way.
That is a standing limitation rather than anything new.

No Rubi coverage figure is claimed for 2.5.0, because that run was never completed on a quiet
machine. The last complete one is 2.4.0's, taken the same day: 599 of 1774, 0 wrong.

Documentation that is checked

254 worked examples in the XML documentation say what they print, and nothing checked any of them;
67 were wrong — 56 printed something else and 11 did not compile or threw. They are compiled and run
against what they claim now (#1184), so a changed answer fails a test instead of leaving a wrong
promise standing in the docs a user reads.