Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 40 additions & 1 deletion BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
76 changes: 76 additions & 0 deletions Sources/AngouriMath/Docs/WhatsNew/version_performance_control.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions Sources/Tests/DotnetBenchmark/key-commits.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,5 @@ v2.1.0
v2.2.0
v2.3.0
v2.4.0
v2.5.0
master
Loading