From 6688a0e3556461026a188ad6908dbb03f7a04eb9 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Mon, 5 Oct 2026 10:32:24 +0000 Subject: [PATCH] The arm for a quadratic off the real line excludes the arms on the sign of its discriminant An antiderivative over a quadratic with i among its coefficients has an arm off the real line, `not D in RR or D > 0`, beside `D = 0` and `D < 0`. A sum of two such answers drops a pair of arms whose conditions contradict each other, and that arm was read against neither: `D = 0` asks D to be real, and `D < 0` is NaN off the real line, so neither pairing is ever true. The contradiction now reads `not q in RR` as a test of q, and a disjunction as contradicted where each of its arms is. x^2/(x^2 + a + i b)^3 had 7 arms and has 3. Part of #1788. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 14 +++ .../Evaluation/Evaluation.Classes.cs | 95 +++++++++++++++++-- .../Core/ContradictoryConjunctionTest.cs | 27 ++++++ 3 files changed, 127 insertions(+), 9 deletions(-) diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 391832803..ef1a40ed0 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -433,6 +433,20 @@ whatever `a` is ([#718](https://github.com/asc-community/AngouriMath/issues/718) | `"sqrt(a + i*a*sinh(g + f*x))/x".ToEntity().Integrate("x")` | `integral(...)` | `Shi` and `Chi` of half of `f x` | | `"1/sqrt(a + i*a*sinh(g + f*x))".ToEntity().Integrate("x")` | `integral(...)` | arctangents and logarithms of `e^((g + f x)/2)` | +### The arm for a quadratic off the real line excludes the arms on the sign of its discriminant + +**Shorter answers.** An antiderivative over a quadratic with `i` among its coefficients has an arm +off the real line, `not D in RR or D > 0`, beside the arms `D = 0` and `D < 0`. A sum of two such +answers pairs their arms and drops a pair whose conditions contradict each other, and that arm was +read against neither, though `D = 0` asks `D` to be real and `D < 0` is NaN off the real line. Those +pairs are dropped now +([#1788](https://github.com/asc-community/AngouriMath/issues/1788)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"x^2/(x^2 + a + i*b)^3".ToEntity().Integrate("x")` | 10 arms, and no value at a real `x`; 7 arms and 2,722 characters on the unreleased master | 3 arms, 1,069 characters | +| `"1/(x^2 + i*a)^2 + 1/(x^2 + i*a)^3".ToEntity().Integrate("x")` | 9 arms, and no value at a real `x`; 7 arms and 1,994 characters on the unreleased master | 3 arms, 779 characters | + ### The hyperbolic functions have antiderivatives, and so does anything rational in `e^(k x)` An integrand rational in `e^(k x)` becomes a rational function of one variable under diff --git a/Sources/AngouriMath/Functions/Evaluation/Evaluation.Classes.cs b/Sources/AngouriMath/Functions/Evaluation/Evaluation.Classes.cs index 1f86edead..51671f9c2 100644 --- a/Sources/AngouriMath/Functions/Evaluation/Evaluation.Classes.cs +++ b/Sources/AngouriMath/Functions/Evaluation/Evaluation.Classes.cs @@ -241,28 +241,103 @@ private Entity CombinedCaseByCase(Piecewise a, Piecewise b, private static (bool Always, Entity? Undecided) Contradiction(Entity predicate) { var tests = new List<(Entity Quantity, int Relation)>(); + var choices = new List(); foreach (var conjunct in Conjuncts(predicate)) if (SignTest(conjunct) is { } test) tests.Add(test); + else if (conjunct is Orf) + choices.Add(conjunct); Entity? undecided = null; for (var i = 0; i < tests.Count; i++) for (var j = i + 1; j < tests.Count; j++) + switch (Against(tests[i], tests[j])) + { + case (true, _): + return (true, null); + case (false, { } quantity): + undecided = quantity; + break; + } + // A disjunction beside them contradicts them where each of its arms does: the arm a + // quadratic off the real line owes, `not D in RR or D > 0`, against `D = 0` is false + // for every value, and against `D < 0` false for a real one and NaN off the real + // line. Read as neither, the sum of the antiderivatives over three powers of one + // quadratic kept every pairing of their arms, 45 for + // `2 d^3 t^2/((t^2 - c - i d)^3 (t^2 - c + i d))` where 9 hold every case. + // https://github.com/asc-community/AngouriMath/issues/1788 + foreach (var choice in choices) + { + var (always, quantity, read) = (true, (Entity?)null, true); + foreach (var arm in Disjuncts(choice)) { - if (tests[i].Quantity != tests[j].Quantity) + if (SignTest(arm) is not { } armTest) + { + read = false; + break; + } + var (armAlways, armQuantity) = (false, (Entity?)null); + foreach (var test in tests) + switch (Against(armTest, test)) + { + case (true, _): + armAlways = true; + break; + case (false, { } q): + armQuantity ??= q; + break; + } + if (armAlways) continue; - var (r1, r2) = (tests[i].Relation, tests[j].Relation); - // 0 is "= 0", 2 is "not = 0", 1 and -1 the signs. - if (r1 == 0 && r2 != 0 || r2 == 0 && r1 != 0) - return (true, null); - if (r1 * r2 == -1) - undecided = tests[i].Quantity; + if (armQuantity is null || quantity is not null && quantity != armQuantity) + { + read = false; + break; + } + (always, quantity) = (false, armQuantity); } + if (!read) + continue; + if (always) + return (true, null); + undecided = quantity; + } return (false, undecided); } + /// + /// Two tests of one quantity q: in Always where no value satisfies both, + /// and otherwise the quantity where a real one satisfies neither together and one off the + /// real line makes a sign NaN; null where they do not test one quantity or agree. + /// + private static (bool Always, Entity? Undecided) Against((Entity Quantity, int Relation) first, (Entity Quantity, int Relation) second) + { + if (first.Quantity != second.Quantity) + return (false, null); + var (r1, r2) = (first.Relation, second.Relation); + // 0 is "= 0", 2 is "not = 0", 1 and -1 the signs, 3 "not in RR": zero is real. + if (r1 == 0 && r2 != 0 || r2 == 0 && r1 != 0) + return (true, null); + if (r1 * r2 == -1 || r1 == 3 && r2 is 1 or -1 || r2 == 3 && r1 is 1 or -1) + return (false, first.Quantity); + return (false, null); + } + + private static IEnumerable Disjuncts(Entity condition) + { + if (condition is Orf(var left, var right)) + { + foreach (var disjunct in Disjuncts(left)) + yield return disjunct; + foreach (var disjunct in Disjuncts(right)) + yield return disjunct; + } + else + yield return condition; + } + /// Whether has a conjunct testing against zero. private static bool TestsTheSignOf(Entity predicate, Entity quantity) - => Conjuncts(predicate).Any(conjunct => SignTest(conjunct) is var (q, relation) && q == quantity && relation != 2); + => Conjuncts(predicate).Any(conjunct => SignTest(conjunct) is var (q, relation) && q == quantity && relation is 0 or 1 or -1); /// /// A comparison of a quantity with zero as the quantity, with any constant factor taken @@ -281,11 +356,13 @@ private static (Entity Quantity, int Relation)? SignTest(Entity conjunct) Lessf(var zero, var q) when zero == Integer.Zero => (q, 1), Lessf(var q, var zero) when zero == Integer.Zero => (q, -1), Greaterf(var zero, var q) when zero == Integer.Zero => (q, -1), + Notf(Inf(var q, SpecialSet.Reals)) => (q, 3), _ => null, }; if (read is not var (quantity, relation)) return null; - // The constant factor out: `4 f (-d)` is `f (-d)`, and `2 f` is `f`. + // The constant factor out: `4 f (-d)` is `f (-d)`, and `2 f` is `f`. A real one, which + // leaves `not in RR` as it is. if (quantity is Mulf(Real factor, var rest) && !factor.IsZero) (quantity, relation) = (rest, relation is 1 or -1 && factor.IsNegative ? -relation : relation); return (quantity, relation); diff --git a/Sources/Tests/UnitTests/Core/ContradictoryConjunctionTest.cs b/Sources/Tests/UnitTests/Core/ContradictoryConjunctionTest.cs index 9a1480b9f..5184ccd71 100644 --- a/Sources/Tests/UnitTests/Core/ContradictoryConjunctionTest.cs +++ b/Sources/Tests/UnitTests/Core/ContradictoryConjunctionTest.cs @@ -99,6 +99,33 @@ public void TwoPiecewisesOnTheSameSplitCombineCaseByCase() Assert.Equal(Number.Integer.Create(11 + 6), chain.Substitute("q", 0).Evaled); } + /// + /// The arm an antiderivative owes a quadratic off the real line, not q in RR or q > 0, + /// against the arms on the sign of q: an equality asks q to be real, and a + /// strict sign is NaN off the real line, so neither pairing is ever true, and a sum of + /// such piecewises stays at three cases. The antiderivative of + /// 2 d^3 t^2/((t^2 - c - i d)^3 (t^2 - c + i d)) kept 45. + /// https://github.com/asc-community/AngouriMath/issues/1788 + /// + [Fact] + public void TheArmOffTheRealLineExcludesTheSignArms() + { + const string arms = "piecewise(1 provided not q in RR or q > 0, 2 provided q = 0, 3 provided q < 0)"; + var sum = (arms.ToEntity() + arms.ToEntity()).InnerSimplified; + Assert.Equal(3, CaseCount(sum)); + Assert.Equal(Number.Integer.Create(2), sum.Substitute("q", 1).Evaled); + Assert.Equal(Number.Integer.Create(4), sum.Substitute("q", 0).Evaled); + Assert.Equal(Number.Integer.Create(6), sum.Substitute("q", -1).Evaled); + Assert.Equal(Number.Integer.Create(2), sum.Substitute("q", "i").Evaled); + + var chain = sum; + for (var i = 0; i < 4; i++) + chain = (chain + arms.ToEntity()).InnerSimplified; + Assert.Equal(3, CaseCount(chain)); + Assert.Equal(Number.Integer.Create(6), chain.Substitute("q", "1 + i").Evaled); + Assert.Equal(Number.Integer.Create(18), chain.Substitute("q", -2).Evaled); + } + /// /// A quantity is read up to a constant factor: f = 0 and not 2 f = 0 /// contradict each other, which is how the integrator's case for a vanishing leading