diff --git a/src/content/quickstart/index.html b/src/content/quickstart/index.html index 20319332f..918083dd1 100644 --- a/src/content/quickstart/index.html +++ b/src/content/quickstart/index.html @@ -39,12 +39,14 @@
dotnet add package AngouriMath.FSharp
- The current release is 2.1.0, and both it and 2.0.0 change answers that earlier versions
+ The current release is 2.5.0, and every 2.x release changes answers that earlier versions
got wrong. Whichever version you are coming from, read
BREAKING-CHANGES.md
first: it lists every input whose result is now different, with the old value and the new one,
- under a heading per release. 2.1.0 is a drop-in replacement for 2.0.0 — the assembly version is
- pinned at 2.0.0.0 for the whole of 2.x.
+ under a heading per release. The assembly version is pinned at 2.0.0.0 for the whole
+ of 2.x, so 2.5.0 binds wherever a 2.x release did. One rename needs a recompile: 2.2.0 spelled
+ Transformation.Rationalisation and RewriteRules.RationaliseDenominator
+ with a z, and code calling either throws MissingMethodException until it is rebuilt.
The library targets netstandard2.0, net8.0 and net10.0.
diff --git a/src/content/whatsnew/index.html b/src/content/whatsnew/index.html
index 5ef8f61fe..59632be91 100644
--- a/src/content/whatsnew/index.html
+++ b/src/content/whatsnew/index.html
@@ -34,7 +34,214 @@
+ 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).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).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).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).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).−1 was taken out as though it were a factor rather than read as a sign (#1172).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).
+ 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) |
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).x => x + 1 beside lambda(x, x + 1). Nothing that parsed before parses differently — = followed by > was not a token (#1151).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).
+ 1/((2+x)^3*(3+x)^4) takes about half a minute either way. That is a standing limitation rather than anything new.
+
+ Tier 1 of the Math OS roadmap is finished, and the thing that finished it kept finding wrong answers. Writing a rewrite rule out as data — a pattern and a replacement, rather than an arm of a switch — makes the correspondence between the two something you have to state. Four times this cycle, a rule did not survive stating it.
+
+ Every changed answer is in BREAKING-CHANGES.md under 2.4.0 — since 2.3.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. Twenty-seven entries, and more than half are marked Silent — the call still succeeds and quietly returns something else.
+
+ AssemblyVersion stays 2.0.0.0. The recorded public surface shows 68 additions and 130 removals, and none of the removals is one a caller can hit: Stringize() and Latexize() stopped being abstract on Entity and each node's override became a private helper, so the member is still public on Entity and inherited by every node. Source- and binary-compatible.
+
+ Wrong answers fixed:
+
Factor("4x² − 4y²", "x") returned (x + y)(x − y) — the 4 simply gone, the difference from the input −3x² + 3y². Every candidate in the polynomial layer is checked by exact division, but that check is on the individual factors; nothing compared the assembled product against the input, so a constant lost during assembly was lost silently. (#1092)(y < x) or (x = y) simplified to x <= y — its own negation off the diagonal. Four of the eight or-with-equality rules carried their neighbour's comparison. Only reachable with both operands symbolic: with a number on one side, 2 < x is rewritten to x > 2 earlier in the same pass and one of the four correct rules matches. A test written with a numeric operand would have passed on the defect. (#1077)x! = 0 was answered False everywhere, including at the negative integers where the factorial has a pole and the statement is NaN. The rule read a property pattern on the factorial's argument rather than on the factorial. It took x! / x! → 1 with it, and three recorded test verdicts had been recording that. (#1081)ln(1/b) = −ln(b) was applied unconditionally, which is false on the negative reals — at b = −0.63 the two differ by the full turn of the argument the principal branch discards. Three rules gained the guard their neighbours ten lines below already had. (#1062)Factorize was built entirely out of rewrite rules, so it factored what someone had written a rule for and handed everything else back whole — while square-free decomposition, Zassenhaus over ℚ and the multivariate GCD sat in the tree unused by it.
+ "x3 - 1".Factorize() x ^ 3 - 1 → (x - 1) * (x ^ 2 + x + 1)
+"x4 - 5x2 + 4".Factorize() unchanged → (x + 1) * (x + 2) * (x - 2) * (x - 1)
+"x2 + 2x + 1".Factorize() unchanged → (x + 1) ^ 2
+ The layer speaks only where the rules said nothing, so every answer they already gave is unchanged — the order two factors come out in is arbitrary and theirs is the one on record. (#1018)
+ x⁷ − y⁷ maps to t⁷(1 − t⁴⁹), whose factors are cyclotomic. An evaluation image inflates nothing.
+ Factor("x12 - y12", "x") null → six factors, the full cyclotomic split
+Factor("x7 - y7", "x") null → (x - y)(x⁶ + x⁵y + ⋯ + y⁶)
+ Where the substitution gives up entirely, an evaluation image can still prove a polynomial irreducible — which since #1059 is an answer rather than a refusal, so Factor("x2 + y2 + z2 + w2 + 1", "x") returns the polynomial instead of null. (#1087)
+ switch it replaces over thousands of generated expressions. Twenty-seven run it. The three that do not are the canonical orders, and that is a measurement rather than an omission.
+ Sound, 141 SoundUnderAssumptions), and 26 rules that can be read backwards.
+ SimplifyEasy is about 13% slower than before the exchange — 82,676 ns to 93,732 ns on one desktop, both arms, standard deviation under half a per cent. Every conversion was measured against the commit in front of it and every one came back free or better; nothing was measured against the start, and the sum of a run of free steps is +13%.
+ SolveMediumHard on its own.
+ version_performance_control.md records where it goes, what three attempts to remove it measured, and the roughly 40% still unattributed.
+
+ Correctness release, and the first one whose claim to that is measured on an outside corpus rather than only on our own tests. Against Rubi's integration suite — 1774 problems that each carry an antiderivative known to exist — this version answers 604 where 2.2.0-era master answered 536, and gets 0 wrong where that answered 7. Six of those seven were NaN: a definite claim that no value exists, made about integrals that have one.
+
+ Every changed answer is in BREAKING-CHANGES.md under 2.3.0 — since 2.2.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.
+
+ AssemblyVersion stays 2.0.0.0. This release removes and renames nothing — 103 additions and 0 removals against the 2.2.0 public API baseline — so it is a drop-in replacement in both the binding and the source sense, which 2.2.0 was not.
+
+ Wrong answers fixed:
+
NaN for ordinary matrices. Gaussian elimination left the pivots as literal divisions, so the expression was undefined wherever a pivot vanishes. Two of four ordinary 3×3 matrices came back NaN, one of them the singular example every linear-algebra course opens with. It is Laplace expansion now, which never divides — and is faster, dramatically so on numeric matrices. (#992)BB, ZZ, QQ, RR, CC and their intersections — because membership was answered with a guard that is deliberately permissive about what it has not ruled out. Asking "might this be a member" and reporting it as "is a member" are different questions. (#995)pi or e behaved as though they varied. sin(pi) differentiated by MathS.pi was -1, which is cos(pi): the chain rule run over a symbol that cannot change. It is 0. (#993)Differentiate(x, n) returned raw chain-rule output for n >= 1, and because each pass differentiated the unsimplified result of the last, the expression compounded — x^4 three times was a screenful where differentiating three times by hand is 2 * x * 3 * 4. Same value, and only one of them is an answer. (#1002)pi or e still carried the constant's value, so a binder could not bind them and derivative(e^2, e) was 0. A name a binder declares is a variable, whatever it is spelled. (#984)ToSympyCode emitted Python that does not run, for every set, every lambda, every piecewise and every non-vector matrix: unqualified FiniteSet, Interval, Union and S against a preamble of only import sympy; a lambda with no body; a set builder that threw out of the exporter; and Interval, Piecewise and Matrix writing their children as this library spells them rather than as SymPy does. Measured by executing the generated program rather than reading it: 24 of 45 ran before, 45 of 45 run now. (#985)"{ k : k > 0 }".FreeVariables was { %1 } — a name in no expression, not typeable, and different for a different predicate. It also broke Vars' own promise in both directions at once: the name that occurs was missing and the one that does not was there. (#989)Stringize of a Rational read back as a Divf, so the round trip was not an identity. Fixed by the parser change below rather than by anything aimed at it. (#873)Rational it denotes rather than as a division.Core.Binding makes binder resolution happen at construction, which is what let i, pi and e become bindable names.
+ SolveEasy is 21.3 ms to 8.5 ms — a 2.5× speedup with a 2.3× allocation drop beside it, which is how you tell work from machine noise.
+
+ 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:
+
+
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)1/(x^4 + 3x^2 + 2) and 1/(x^4 + 4) now integrate instead of coming back unevaluated. (#926)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)
+ 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 guessing
+ x/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)
+ 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.
+ sin(-x) + sin(x) was left as written and is 0 — the parity identities were not being applied. cos(-x) and abs(-x) fold too. (#929/#931)InnerSimplified is idempotent again. An exact trigonometric value reached through a half turn came back as -(-1) where it should have been 1. The value was never wrong, but applying InnerSimplified twice gave a different tree from applying it once, and much of the library treats what it returns as settled. (#930/#932)log(-3, -3) is no longer declared undefined while evaluating to 1. (#721/#890, #916)log(x, x) was 1 provided x > 0 — NaN at every negative x, and 1 at x = 1 where the logarithm is NaN. (#916)d/dx x^n carried provided x > 0, a condition it never needed, making the derivative undefined at every negative x. (#916)ln(a) + ln(b) -> ln(a*b) is wrong by 2*pi*i off 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)MatchPattern matches by enumerating solutions, so commutative operands backtrack properly, and three rule sets are expressed as data and proved equivalent to the switch they 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)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.-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 |
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)
+
+A correctness release. Almost everything below is a wrong answer becoming a right one, and most of it was found by harnesses rather than reported — boundary points where a rule's assumption fails,