diff --git a/AGENTS.md b/AGENTS.md index 9bd1ff7..8e10baa 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -23,7 +23,8 @@ every documented defect. ## Which AngouriMath you built against The build uses a **sibling AngouriMath checkout at `../AngouriMath` when one exists, and the -released 2.1.0 NuGet package when it does not**. It prints which; read the line. +released NuGet package (2.5.0, set once as `AngouriMathPackageVersion`) when it does not**. +It prints which, and the version; read the line. This used to be the first trap in the repo — the fallback was 1.4.0, which behaves differently enough that two assertions in `test/smoke.sh` failed against it legitimately diff --git a/README.md b/README.md index c77fc42..542d3eb 100644 --- a/README.md +++ b/README.md @@ -264,14 +264,13 @@ Limits worth knowing: there are no eigenvectors, no SVD, and no matrix exponenti and that is Abel–Ruffini rather than a defect — no general radical solution exists. Ten qubits would be a 1024×1024 symbolic matrix; this dies long before that. -**A defect it works around.** `Entity.Matrix.Determinant` calls GenericTensor's -`DeterminantGaussianSafeDivision`, which divides by pivots and leaves a `provided` guard for -each. Those guards are artefacts, not mathematics: the raw output claims -`det([[a,b],[c,d]]) = a*d - b*c provided not a = 0`, and eigenvalues of `[[0,J],[J,0]]` come -back as `J provided not J = 0` — excluding a perfectly valid case. This server simplifies -*first* (the guard is what licenses cancelling `a/a`), then strips the guard and reports it -under `dropped_guards`. The real fix belongs upstream: GenericTensor already ships a -division-free `DeterminantLaplace`, which emits none of this. +**Pivot guards.** Before AngouriMath 2.5.0 the determinant divided by pivots and left a +`provided` guard for each — `det([[a,b],[c,d]]) = a*d - b*c provided not a = 0`, and +eigenvalues of `[[0,J],[J,0]]` came back as `J provided not J = 0` — artefacts rather than +mathematics. 2.5.0's determinant leaves none: `a*d - b*c`, and `lambda^2 - J^2` for that +characteristic polynomial. This server still simplifies *first* and strips any guard that +remains, reporting it under `dropped_guards`, so a guard from any other pivoting routine is +handled the same way. ## Step-by-step, and knowing what to distrust diff --git a/UPSTREAM.md b/UPSTREAM.md index ce4cb00..4dca690 100644 --- a/UPSTREAM.md +++ b/UPSTREAM.md @@ -42,27 +42,32 @@ reports them under `dropped_guards` in the meantime. ## Defects worth reporting upstream -Re-verified against AngouriMath 2.1.0 — a claim measured on an older build is not worth +Re-verified against AngouriMath 2.5.0 — a claim measured on an older build is not worth reporting. `--selftest` re-checks each row on every run; three entries were dropped at the 2.0.0 upgrade because the release fixed them, which is the whole reason that check exists. -**Nothing was dropped at 2.1.0**, and that is a measurement rather than an assumption: the -build was made against the *published* 2.1.0 package rather than the sibling checkout, which -still sat at 2.0.0 while this was written. All eleven identities hold and all five rows below -still reproduce. 2.1.0 is a correctness release, and none of what it fixed is on this list. +**Two were dropped at 2.5.0**, measured against the *published* 2.5.0 package rather than the +sibling checkout: the integral of `x^4*(1-x)^4/(1+x^2)` and the determinant's pivot guards, +both under "Fixed upstream" below. Nothing was dropped at 2.1.0. All identities hold, the +integral is now one of them, and the four rows below still reproduce. | Observed | Note | |---|---| | `Simplify(sqrt(x^2))` is left as written, not reduced to `abs(x)` | No longer the soundness bug it was — 2.0.0 stopped answering `x`, which was wrong for every negative. What remains is a gap: writing `abs` needs to know the expression is real, which the codomain of [#719](https://github.com/asc-community/AngouriMath/issues/719) can now say and the simplifier does not yet read. | | `MathS.Equations(...)` throws on an equality | It wants each equation in `= 0` form; passing an `Equalsf` raises `NotSufficientlySupportedException` rather than normalising `a = b` to `a - b`, which is a rewrite it could do itself. This server does it instead. Re-checked on 2.0.0 by `--selftest`; the exception type changed with the release, the behaviour did not. | -| `Integrate` declines `x^4*(1-x)^4/(1+x^2)` | It handles the same function once the polynomial division is done by hand, so the gap is dividing a rational function whose numerator outranks its denominator. | | `Entity.DefiniteIntegral` is a first-order rule | New in 2.0.0, and it is a rectangle rule: the error halves per doubling of the step count, so 4000 steps buy about four digits of `∫[0,1] e^(x^2)` at ~150 ms. Simpson's rule is the same amount of code and would give roughly eight. It also samples both endpoints, so a convergent integral with a singular endpoint — `∫[0,1] sin(x)/x`, `∫[0,1] ln(x)` — returns `NaN` rather than a value. Both are worth raising; this server runs it twice and reports only the agreed digits in the meantime. | | An unknown identifier still becomes implicit multiplication silently | 2.0.0 closed most of this: `exp`, `log10`, `log2`, `pow`, `floor`, `ceil`, `round`, `min`, `max` and `gcd` became real functions, and eleven names the library does not have are now refused by name. The general case remains — `im(z)` is `im * z` — and cannot be closed without refusing `a(b + c)`, so a warning or a strict default is still the only answer. This server warns. | ## Fixed upstream, kept here as a record -Each of these was on the list above and reproduced no longer at the 2.0.0 upgrade. Listed -so that nobody re-reports them, and so the cost of not re-measuring is visible. +Each of these was on the list above and reproduced no longer at an upgrade. Listed so that +nobody re-reports them, and so the cost of not re-measuring is visible. + +- **`Integrate` declining `x^4*(1-x)^4/(1+x^2)`** (at 2.5.0). It divides the rational + function out itself now, and the integral over `[0, 1]` is exactly `22/7 - pi`, which + `--selftest` checks as an identity. +- **The determinant's pivot guards** (at 2.5.0). `det([[a,b],[c,d]])` was + `a*d - b*c provided not a = 0`; it is `a*d - b*c`. - **`Factorize(x^2 - 1)` emitting `sqrt(1)`.** Dropped before 2.0.0: true of 1.4.0, false of the branch, and it went stale unnoticed. This is why `--selftest` exists. diff --git a/src/AngouriMath.Mcp/AngouriMath.Mcp.csproj b/src/AngouriMath.Mcp/AngouriMath.Mcp.csproj index fe13d64..b0f81ae 100644 --- a/src/AngouriMath.Mcp/AngouriMath.Mcp.csproj +++ b/src/AngouriMath.Mcp/AngouriMath.Mcp.csproj @@ -31,6 +31,9 @@ $(MSBuildThisFileDirectory)../../../AngouriMath/Sources/AngouriMath/AngouriMath.csproj true + + 2.5.0 @@ -40,13 +43,13 @@ - + + Text="AngouriMathMCP: building against the released $(AngouriMathPackageVersion) package." /> diff --git a/src/AngouriMath.Mcp/Matrices.cs b/src/AngouriMath.Mcp/Matrices.cs index 6382167..3e3ce64 100644 --- a/src/AngouriMath.Mcp/Matrices.cs +++ b/src/AngouriMath.Mcp/Matrices.cs @@ -134,15 +134,14 @@ public static (Matrix Value, List Guards) Clean(Matrix m) /// /// Eigenvalues via the characteristic polynomial. /// - /// The guard-dropping is not cosmetic. Entity.Matrix.Determinant calls GenericTensor's - /// DeterminantGaussianSafeDivision, which divides by pivots and leaves a `provided` guard - /// for each one. On a symbolic matrix those guards are artefacts of the algorithm, not - /// mathematics: the characteristic polynomial of the Pauli X matrix comes back as - /// `lambda^2 - 1 provided not lambda = 0`, and [[0,J],[J,0]] yields eigenvalues + /// The guard-dropping is not cosmetic. Until AngouriMath 2.5.0, Entity.Matrix.Determinant + /// called GenericTensor's DeterminantGaussianSafeDivision, which divides by pivots and left + /// a `provided` guard for each one. On a symbolic matrix those guards are artefacts of the + /// algorithm, not mathematics: the characteristic polynomial of the Pauli X matrix came + /// back as `lambda^2 - 1 provided not lambda = 0`, and [[0,J],[J,0]] yielded eigenvalues /// `{J provided not J = 0, -J provided not J = 0}` — both wrong, since those values are - /// perfectly valid. GenericTensor also ships the division-free DeterminantLaplace, which - /// would emit none of this; until AngouriMath uses it for symbolic entries, we strip the - /// guards here and report what was dropped. + /// perfectly valid. 2.5.0's determinant leaves none (`lambda^2 - 1`, `lambda^2 - J^2`); the + /// stripping stays, and still reports what it dropped, for any routine that pivots. /// public static Eigen Compute(Matrix a, Variable lambda) { diff --git a/src/AngouriMath.Mcp/Parsing.cs b/src/AngouriMath.Mcp/Parsing.cs index 270d9e9..3699606 100644 --- a/src/AngouriMath.Mcp/Parsing.cs +++ b/src/AngouriMath.Mcp/Parsing.cs @@ -58,6 +58,14 @@ public sealed record Outcome(Entity? Entity, List Warnings, string? Erro "floor", "ceil", "ceiling", "round", "min", "max", "gcd", "factorial", // `pow(x, y)` parses to x^y as of 2.0.0. "pow", + // Short and alternative spellings the grammar has accepted at least since 2.1.0, and + // which this list missed until the 2.5.0 probe read every name back: `asin(x)` was + // being warned about as an unknown function. + "asin", "acos", "atan", "asec", "acsc", "acosec", "acot", "acotan", + "sh", "ch", "th", "cth", "sch", + "acoth", "acsch", "arcoth", "arcsch", "arsch", + // New by 2.5.0: a set's cardinality, sums and products, and arg-extrema. + "card", "sum", "product", "argmax", "argmin", "gamma", "phi", "derivative", "integral", "limit", "limitleft", "limitright", "piecewise", "provided", "apply", "lambda", "domain", "intersect", "and", "or", "not", "xor", diff --git a/src/AngouriMath.Mcp/Resources.cs b/src/AngouriMath.Mcp/Resources.cs index 3a5f4c4..e43c5fc 100644 --- a/src/AngouriMath.Mcp/Resources.cs +++ b/src/AngouriMath.Mcp/Resources.cs @@ -131,12 +131,11 @@ exactly what to run — so this doubles as a self-test corpus. If one of these s ## A machine-checked proof that 22/7 > π The integrand is positive on (0,1), so the integral is positive — which proves the - inequality. It needs the polynomial division done by hand first, because the - integrator declines the unexpanded rational form: + inequality: - 1. `am_verify_equal x^4*(1-x)^4/(1+x^2) vs x^6 - 4*x^5 + 5*x^4 - 4*x^2 + 4 - 4/(1+x^2)` - 2. `am_integrate x^6 - 4*x^5 + 5*x^4 - 4*x^2 + 4 - 4/(1+x^2) dx` (verifies) - 3. `am_evaluate 1/7 - 4/6 + 1 - 4/3 + 4 - 4*arctan(1)` → **exactly `22/7 - pi`** + 1. `am_integrate x^4*(1-x)^4/(1+x^2) dx` (verifies; the integrator divides the + rational function out itself as of AngouriMath 2.5.0) + 2. `am_evaluate 1/7 - 4/6 + 1 - 4/3 + 4 - 4*arctan(1)` → **exactly `22/7 - pi`** ## Near misses — where floating point would lie to you diff --git a/src/AngouriMath.Mcp/SelfTest.cs b/src/AngouriMath.Mcp/SelfTest.cs index 954d79a..b1b0666 100644 --- a/src/AngouriMath.Mcp/SelfTest.cs +++ b/src/AngouriMath.Mcp/SelfTest.cs @@ -45,6 +45,17 @@ private static readonly (string Name, Func Holds)[] Identities = ("42 is 101010 in binary", () => MathS.ToBaseN(42, 2) == "101010"), ("42 is the 5th Catalan number", () => Value("10! / (6! * 5!)") == "42"), ("Pythagoras: sin^2 + cos^2 = 1", () => IsZero("sin(x)^2 + cos(x)^2 - 1")), + ("22/7 - pi is the integral of x^4*(1-x)^4/(1+x^2) over [0, 1]", () => + { + // Documented as declined until 2.5.0 integrated it whole; kept as an identity so + // the proof the reliability resource walks through cannot silently stop working. + var outcome = Parsing.Parse("x^4*(1-x)^4/(1+x^2)"); + if (outcome.Entity is null) return false; + var x = MathS.Var("x"); + var antiderivative = outcome.Entity.Integrate(x); + var value = antiderivative.Substitute(x, 1) - antiderivative.Substitute(x, 0); + return Numeric.IsZero((value - (MathS.FromString("22/7") - MathS.pi)).Simplify()); + }), ("d/dx integral of x*ln(x) returns the integrand", () => { var outcome = Parsing.Parse("x*ln(x)"); @@ -85,22 +96,6 @@ private static readonly (string Name, Func StillBroken, string Documented) var ratio = Math.Abs(fine - coarse) / Math.Abs(finer - fine); return ratio is > 1.7 and < 2.3; }, "error halves per doubling, so ~4 digits at 4000 steps; Simpson would give more"), - ("integral of x^4*(1-x)^4/(1+x^2)", () => - { - var outcome = Parsing.Parse("x^4*(1-x)^4/(1+x^2)"); - if (outcome.Entity is null) return false; - var result = outcome.Entity.Integrate(MathS.Var("x")).Stringize(); - return Guard.IsDeclined(result); - }, "declined, though it succeeds once divided out by hand"), - ("determinant leaves a pivot guard", () => - { - var m = MathS.Matrix(new Entity[,] - { - { MathS.Var("a"), MathS.Var("b") }, - { MathS.Var("c"), MathS.Var("d") }, - }); - return m.Determinant is { } det && Analysis.Conditions(det.Simplify()).Count > 0; - }, "det([[a,b],[c,d]]) carries `provided not a = 0`"), ]; public static int Run(TextWriter output)