diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 014d7344f..621958520 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -41,7 +41,12 @@ is the primary spelling anyway and is what the library prints. --- -## Unreleased — since 2.4.0 +## 2.5.0 — since 2.4.0 + +Released 2026-09-09. This heading was renamed from “Unreleased” *before* the tag was cut, which is +the one thing the 2.4.0 section below records against itself: those entries sat under “Unreleased” +while 2.4.0 was tagged and published, so a reader on that version could not tell from this file what +they had. ### At a glance @@ -124,6 +129,7 @@ is the primary spelling anyway and is what the library prints. | **Silent** | `"integral((x - floor(x))^2, x, 0, n)".ToEntity().Simplify()`, and every such integral with a symbolic bound or a condition against a symbol | `(n - floor(n)) ^ 3 / 3` — wrong for every whole `n` but 0; `piecewise(x provided x < a, 0)` from 0 to 2 was `piecewise(2 provided x < a, 0)` | left as written | | **Silent** | `"sum(piecewise(k provided k < a, 0), k, 0, 5)".ToEntity().Simplify()`, and every piecewise carrying a case whose predicate entails an earlier one | 32 cases, most of them unreachable | 6 — the unreachable ones are dropped, and the value at every `a` is unchanged | | | `"integral((x - floor(x)) / floor(x)!, x, 1, +oo)".ToEntity().Simplify()`, and `integral(floor(x), x, 0, 5)` | left as written | `(e - 1) / 2`, `10` | +| **Silent** | `Determinant`, `Inverse` and `Adjugate` of a symbolic matrix, and the elementwise operators, called from more than one thread | a well-formed but **wrong** entity carrying another computation's values — 38 of 40 determinants disagreed with their single-threaded selves; the elementwise operators threw on a corrupted dictionary | the same answer a single thread gets. Unchanged on one thread; the determinant and inverse now serialise across threads where the polynomial elimination declines the matrix | ### A piecewise case that can never be reached is dropped @@ -1339,6 +1345,39 @@ depend on where the jumps fall. Offered only where every piece resolves. Questio | `"integral(floor(x), x, 1/2, 2)"`, `integral(x - floor(x), x, 0, 5/2)`, `integral(ceil(x) - x, x, 1/2, 2)` | left as written | `1`, `9/8`, `5/8` | | `"integral(floor(x^2), x, 0, 2)"` | left as written | the same — a floor of something other than the variable is not a step this reads. `integral(2^(-floor(x)), x, 0, +oo)` was left as written here for want of a geometric series, and is `2` as of the section above | +### Matrix operations called from more than one thread answer correctly + +`MathS.Multithreading` documents concurrent use as supported, and three caches did not honour it. +Each was a table grown under a lock and read outside one, so a reader could see a new count against +an old array and come back with **a different element** — a well-formed number, silently wrong, with +nothing thrown. The factorial cache +([#1227](https://github.com/asc-community/AngouriMath/pull/1227)) and the prime cache +([#1229](https://github.com/asc-community/AngouriMath/pull/1229)) are AngouriMath's own. The third +is not: GenericTensor 1.0.4 hands **every caller the same scratch matrix** for a given size and then +writes into it, reported as [GenericTensor#40](https://github.com/asc-community/GenericTensor/issues/40) +and guarded here in [#1230](https://github.com/asc-community/AngouriMath/pull/1230). + +**A single-threaded caller sees no change at all**, which is why there is no was/is table: the same +input produced these values before and produces them now. What changes is that a concurrent caller +now gets them too. Measured on `c2c8eb83`, forty 4x4 matrices with non-polynomial entries, each +computed once sequentially and then rebuilt and recomputed under `Parallel.For`: + +| | disagreements, before | +|---|---| +| `Determinant` | 38 of 40 | +| `Inverse` | 18 of 40 | +| `Adjugate` | 11 of 40 | +| `m1 + m2`, `m1 - m2`, `PointwiseMultiplication` | threw, on a corrupted `Dictionary` | + +**The one thing that is visible to a working program is throughput.** `Determinant`, `Inverse` and +`Adjugate` now serialise across threads where the polynomial elimination declines the matrix and the +Laplace fallback is taken — a matrix of polynomials never reaches it. The elementwise operators do +**not** serialise: the set of compiled loops this library can ask for is fixed at three, so they are +filled once and read without a lock thereafter. + +The guard is a stand-in and says so in its own doc comment. It should be deleted when a GenericTensor +release carrying [GenericTensor#41](https://github.com/asc-community/GenericTensor/pull/41) exists. + ## 2.4.0 — since 2.3.0 Released 2026-08-28. These entries sat under “Unreleased” while 2.4.0 was tagged and diff --git a/Sources/AngouriMath/Docs/WhatsNew/version_performance_control.md b/Sources/AngouriMath/Docs/WhatsNew/version_performance_control.md index 03f06d95e..88f860504 100644 --- a/Sources/AngouriMath/Docs/WhatsNew/version_performance_control.md +++ b/Sources/AngouriMath/Docs/WhatsNew/version_performance_control.md @@ -180,6 +180,82 @@ honest reason -- a compiled delegate over `Complex` has nothing to put on the he --- +## The 1845th and the 1954th, measured together on one machine — the pair for 2.5.0 + +The pair the release checklist owes: `v2.4.0` (`de7189b1`, the 1845th) re-measured beside the +release commit (`6a97c071`, the 1954th), both on 2026-09-09, on one machine, with nothing else on +it, one entry per run. The period is the whole of 2.5.0 — 108 commits. + +### Allocation + +| benchmark | v2.4.0 | 1954th | change | +|---|--:|--:|--:| +| `CompileEasy` | 11,042 | 11,003 | −0.35% | +| `CompileHard` | 20,776 | 20,473 | −1.46% | +| `Derivate` | 52,824 | 53,240 | **+0.79%** | +| `EvalTrig` | 1,341,377 | 1,341,457 | +0.01% | +| `EvalTrigPrecise` | 12,742,205 | 12,742,285 | +0.00% | +| `ParseEasy` | 18,112 | 18,264 | **+0.84%** | +| `ParseHard` | 3,522,481 | 3,584,841 | **+1.77%** | +| `SimplifyEasy` | 128,100 | 79,282 | **−38.11%** | +| `SimplifyHard` | 3,632,019,136 | 331,756,152 | **−90.87%** | +| `SolveEasy` | 8,853,735 | 8,865,872 | +0.14% | +| `SolveEasyMedium` | 97,195 | 65,648 | **−32.46%** | +| `SolveHard` | 1,446,794,112 | 11,883,024 | **−99.18%** | +| `SolveMedium` | 661,994 | 455,925 | **−31.13%** | +| `SolveMediumHard` | 164,549,808 | 1,449,266 | **−99.12%** | + +Bytes allocated. `EvalEasy`, `RunEasy`, `RunMedium` and `RunHard` allocate nothing in both columns +and are left out. + +### The individually-measured steps compose, and that was checked rather than assumed + +This file records a case where they did not: the rule-set exchange was measured step by step, each +step came back free or better, and the sum was **+13%**. So the five allocation changes in this +release are worth checking against where the release started rather than trusting their own +figures. + +`SimplifyHard` was measured at −44% ([#1205](https://github.com/asc-community/AngouriMath/pull/1205)), +−64% ([#1207](https://github.com/asc-community/AngouriMath/pull/1207)), +−35% ([#1210](https://github.com/asc-community/AngouriMath/pull/1210)) and +−25% ([#1211](https://github.com/asc-community/AngouriMath/pull/1211)), each against the commit in +front of it. Composed, those predict 9.8% of the original surviving, or −90.2%. **Measured +end-to-end against `v2.4.0`: −90.87%**, 0.7 percentage points from the prediction. `SolveHard` was +−98.6% for [#1209](https://github.com/asc-community/AngouriMath/pull/1209) alone and is −99.18% +across the release. They compose here. That is a measurement, not an expectation, and the +13% case +is why it was taken. + +### Three rows went up, and none is attributed + +`ParseHard` +1.77%, `ParseEasy` +0.84% and `Derivate` +0.79%. The parser gained three alternatives +this release — the `divides` keyword, `#` for cardinality and the lambda arrow — which is consistent +with the two parse rows, and `Derivate` has no such candidate. **Neither is bisected**, so both are +recorded as unattributed rather than explained. A move nobody has attributed is a finding, not a +footnote. + +### The determinism claim, held again — and its two exceptions confirmed + +`v2.4.0` was measured on 2026-09-07 for the 1930th and again today, two days and one run apart. +Eleven of the fourteen rows reproduce to within **0.021%**, four of them to the byte — +`EvalTrig`, `EvalTrigPrecise`, `ParseEasy`, `ParseHard`, `SolveMediumHard`. + +The only two rows that moved further are `CompileEasy` (+0.354%) and `CompileHard` (+1.674%), which +are exactly the two `performance-baseline.json` marks **ungated**, for the reason it records there: +their measured allocation includes the runtime building and JIT-compiling a delegate, which is not +reproducible, at a spread of 3.6% over three runs of one unchanged build. Today's spread sits inside +that. So this is an independent confirmation of a documented caveat rather than a new one — and it +is worth stating that the blanket sentence "the same commit measured twice gives the same bytes" +holds for every row this file gates and for neither of the two it does not. + +### Timings + +`SimplifyHard` −89.64%, `SolveHard` −87.29%, `SolveMediumHard` −83.83%, `SolveEasyMedium` −42.80%, +`SimplifyEasy` −57.15%. These are reproduced here, where the 1930th's deliberately were not, +because the 1844th section's objection is that this machine cannot resolve a move of a few per +cent — its measured run-to-run spread on mean time is up to 51.8%. A move of 84 to 90 per cent is +outside that band by a wide margin and agrees in sign and rough size with the allocation column +beside it. The small rows from the same run are still not worth reading, and are not quoted. + ## The 1930th, and every release beside it on one machine The second column measured by `Sources/Utils/benchmark_key_commits.sh` reaching all five entries in diff --git a/Sources/Tests/DotnetBenchmark/key-commits.txt b/Sources/Tests/DotnetBenchmark/key-commits.txt index aa0dcf99c..f5fa42135 100644 --- a/Sources/Tests/DotnetBenchmark/key-commits.txt +++ b/Sources/Tests/DotnetBenchmark/key-commits.txt @@ -39,4 +39,5 @@ v2.1.0 v2.2.0 v2.3.0 v2.4.0 +v2.5.0 master