From 864693e3a8b639d01a3dc972150fdadf7e081d14 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sun, 4 Oct 2026 22:56:03 +0000 Subject: [PATCH] Two roots of linears over a linear off the real line are integrated 1/(sqrt(x) sqrt(a + b x) (1 - i x)) was declined: the closed forms for two roots of linears over a third chose between an arctangent and a logarithm by the sign of a quantity that has none off the real line, and the piecewise on it held at no point. Both forms use nothing about their root but sqrt(q)^2 = q, so the one for a positive quantity is an antiderivative whatever the quantity's phase, and with the imaginary unit in the quantity it is taken alone. Only for those two forms; the other callers of the same choice, an arcsine among them, are left as they were. Part of #718 Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 16 ++++++ .../Integration/IndefiniteIntegralSolver.cs | 23 ++++++--- ...ootsOfLinearsOffTheRealLineIntegralTest.cs | 49 +++++++++++++++++++ 3 files changed, 82 insertions(+), 6 deletions(-) create mode 100644 Sources/Tests/UnitTests/Calculus/RootsOfLinearsOffTheRealLineIntegralTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index dc39a1fe6..98957fb90 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -374,6 +374,22 @@ the linear at its root now, and each piece is the table's | `"t^9/(a + b*t)^8".ToEntity().Integrate("t")` | `integral(...)` | powers of `a/b + t` and a logarithm, in 0.03 s | | `"x^4/(a + b*sqrt(x))^8".ToEntity().Integrate("x")` | `integral(...)` | the same in `sqrt(x)`, in 0.5 s; a minute on the unreleased master | +### Two roots of linears over a linear off the real line are integrated + +**Answers where there were none.** `1/(sqrt(x) sqrt(a + b x) (1 - i x))` was declined. The closed forms +for two roots of linears over a third, an arctangent and a logarithm, were chosen by the sign of a +quantity that, over a linear whose coefficients are not real, has none -- `-i a - b < 0` is NaN, the +complex numbers not being ordered -- and on the unreleased master the answer was a piecewise on that +sign which held at no point. Both forms use nothing about their root but `sqrt(q)^2 = q`, so either +is an antiderivative wherever the quantity is not zero, whatever its phase, and with the imaginary +unit in the quantity the one for a positive quantity is taken alone +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"1/(sqrt(x)*sqrt(a + b*x)*(1 - i*x))".ToEntity().Integrate("x")` | `integral(...)` | `2 arctan(sqrt(-i a - b) sqrt(x)/sqrt(a + b x))/sqrt(-i a - b)` | +| `"sqrt(x)*sqrt(a + b*x)/(1 + i*x)".ToEntity().Integrate("x")` | `integral(...)` | a root, a logarithm and an arctangent, piecewise in the sign of `b` | + ### A polynomial over a power of a quadratic with a sum of symbols in it is reduced **Answers where there were none.** The reduction of `N(x)/Q(x)^n` divides `N` by the quadratic a diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 875a31f1c..6315bd592 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -9796,7 +9796,7 @@ is not var (algebraic, ofTheLogarithm, ofTheThirdKind)) var r = LowestOverTheSymbols(d / b); answer = answer + BySign(r, ofTheLogarithm * MathS.Ln((1 + MathS.Sqrt(r) * root) / (1 - MathS.Sqrt(r) * root)) / MathS.Sqrt(r), - 2 * ofTheLogarithm * MathS.Arctan(MathS.Sqrt(-r) * root) / MathS.Sqrt(-r)); + 2 * ofTheLogarithm * MathS.Arctan(MathS.Sqrt(-r) * root) / MathS.Sqrt(-r), alsoOffTheRealLine: true); } if (ofTheThirdKind != Number.Integer.Zero) { @@ -9806,7 +9806,7 @@ is not var (algebraic, ofTheLogarithm, ofTheThirdKind)) var rho = LowestOverTheSymbols(-atThePole[1] / atThePole[0]); answer = answer + BySign(rho, -2 * ofTheThirdKind * MathS.Arctan(MathS.Sqrt(rho) * root) / MathS.Sqrt(rho), - -ofTheThirdKind * MathS.Ln((1 + MathS.Sqrt(-rho) * root) / (1 - MathS.Sqrt(-rho) * root)) / MathS.Sqrt(-rho)); + -ofTheThirdKind * MathS.Ln((1 + MathS.Sqrt(-rho) * root) / (1 - MathS.Sqrt(-rho) * root)) / MathS.Sqrt(-rho), alsoOffTheRealLine: true); } return (constantBelow == Number.Integer.One ? answer : answer / constantBelow).InnerSimplified; } @@ -10414,10 +10414,21 @@ private sealed record ALinearBesideTheRoot(Entity Linear, Entity G, Entity H, in // where it holds, for a quantity with symbols in it, as `1/(a - x^2)` is answered. A // number times even powers of symbols has the number's sign wherever the symbols are // real and not zero, which is the generic case: `-b^2` in `a^2 - b^2 x^2` is negative. - private static Entity BySign(Entity quantity, Entity wherePositive, Entity whereNegative) - => SignOfANumberTimesEvenPowers(quantity) is { } sign - ? (sign < 0 ? whereNegative : wherePositive) - : MathS.Piecewise((wherePositive, new Greaterf(quantity, Number.Integer.Zero)), (whereNegative, new Lessf(quantity, Number.Integer.Zero))); + // Off the real line neither sign holds, the complex numbers not being ordered, and the + // piecewise had no arm there: `1/(sqrt(x) sqrt(a + b x) (1 - i x))` was answered with + // nothing at any point. Where the caller's form for a positive quantity uses nothing about + // its root but `sqrt(z)^2 = z`, as an arctangent or a logarithm does and an arcsine does + // not, it is an antiderivative wherever the quantity is not zero, whatever its phase -- the + // sign only chooses the form that is real on the real line -- and the caller says so with + // alsoOffTheRealLine: a quantity with the imaginary unit in it then takes that form alone. + private static Entity BySign(Entity quantity, Entity wherePositive, Entity whereNegative, bool alsoOffTheRealLine = false) + { + if (SignOfANumberTimesEvenPowers(quantity) is { } sign) + return sign < 0 ? whereNegative : wherePositive; + if (alsoOffTheRealLine && HoldsTheImaginaryUnit(quantity.InnerSimplified)) + return wherePositive; + return MathS.Piecewise((wherePositive, new Greaterf(quantity, Number.Integer.Zero)), (whereNegative, new Lessf(quantity, Number.Integer.Zero))); + } // The same where the form for a positive first quantity is chosen by the sign of a second, // as one piecewise rather than one inside another. diff --git a/Sources/Tests/UnitTests/Calculus/RootsOfLinearsOffTheRealLineIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/RootsOfLinearsOffTheRealLineIntegralTest.cs new file mode 100644 index 000000000..5efad5f17 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/RootsOfLinearsOffTheRealLineIntegralTest.cs @@ -0,0 +1,49 @@ +// +// Copyright (c) 2019-2026 Angouri. +// AngouriMath is licensed under MIT. +// Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md. +// Website: https://am.angouri.org. +// + +using System; +using AngouriMath.Extensions; +using Xunit; + +namespace AngouriMath.Tests.Calculus +{ + /// + /// The roots of two linears over a linear whose coefficients are not real, where the closed + /// forms chose between an arctangent and a logarithm by the sign of a quantity that has none, + /// and the answer held at no point. + /// #718 + /// + /// + /// Compared as complex numbers, the integrands being complex, and differentiated before the + /// symbols are pinned: a piecewise arm whose condition compares a number off the real line, pinned + /// first and then differentiated, made the whole derivative NaN. + /// + [Trait("Area", "Calculus")] + public sealed class RootsOfLinearsOffTheRealLineIntegralTest + { + [Theory] + [InlineData("1/(sqrt(x)*sqrt(a + b*x)*(1 - i*x))")] + [InlineData("sqrt(x)/(sqrt(a + b*x)*(1 + i*x))")] + [InlineData("sqrt(x)*sqrt(a + b*x)/(1 + i*x)")] + public void IsIntegratedOverTheLinear(string integrand) + { + var integral = integrand.ToEntity().Integrate("x"); + Assert.DoesNotContain("integral(", integral.Stringize()); + Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.6); + var derivative = Pinned(integral.Substitute("C", 0).Differentiate("x")); + var original = Pinned(integrand.ToEntity()); + foreach (var at in new[] { 0.3, 0.7, 1.2, 2.0 }) + { + var want = original.Substitute("x", at).EvalNumerical(); + var got = derivative.Substitute("x", at).EvalNumerical(); + Assert.True(Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart) + < 1e-9 * Math.Max(1, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart)), + $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}"); + } + } + } +}