From 7dbcdce6b5203f2d48042675e154e83ad1bf823c Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Thu, 24 Sep 2026 13:34:42 +0000 Subject: [PATCH] Two linear factors whose product is a multiple of the radicand arsinh holds are written as that radicand `(a + b arsinh(c x)) sqrt(d + i c d x) sqrt(f - i c f x)` ran out of time. The two linear factors multiply to `d f (1 + c^2 x^2)`, a constant multiple of the radicand `arsinh(c x) = ln(c x + sqrt(c^2 x^2 + 1))` holds -- the case #1487 answers, spelled as two factors. Rubi's 7.1.4 writes seventy-two of its 663 rows this way. `L1^p L2^q` with `p - q` whole is now `L1^(p - q) L1^q L2^q`, and `L1^q L2^q` is `lambda^q M^q` for a whole `q` and `K^k M^(k/2)` for `q = k/2`, with `K = sqrt(L1) sqrt(L2)/sqrt(M)` carried as a symbol and written in front of the answer: constant wherever it is defined, as #1487's is. Tried only where no single base is a multiple of the radicand, on the factors of both sides of the bar, with a nested power read through and a factor below the bar read with its exponent negated. Rubi's 7.1.4 and 7.1.5, all 376 problems that count: 308 to 345, no row lost, 56 timeouts to 27. Family 7 235 to 237 of 270; the 7.2 files, families 1, 4, 5 and 6 and the 1774-problem suite unchanged, row for row. The answers are complex-valued along the whole line and are exact at five points each compared as complex numbers, with a negative `d` among the pins. Part of #718. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 15 ++++ .../Integration/IndefiniteIntegralSolver.cs | 82 ++++++++++++++++++- .../InverseHyperbolicSubstitutionTest.cs | 36 ++++++++ 3 files changed, 129 insertions(+), 4 deletions(-) diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index e15106ce6..590cd4f7e 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -1410,6 +1410,21 @@ is real on `(-1/2, 1/2)`, where `arcosh(2x)` is `i arccos(2x)`. Rubi's 7.2.4 and 7.2.5, all 277 problems that count: 127 to 255, no row lost, 121 timeouts to 18. +### Two linear factors whose product is a multiple of the radicand `arsinh` holds are written as that radicand + +`(a + b arsinh(c x)) sqrt(d + i c d x) sqrt(f - i c f x)` ran out of time. The two linear factors +multiply to `d f (1 + c^2 x^2)`, a constant multiple of the radicand `arsinh(c x) = +ln(c x + sqrt(c^2 x^2 + 1))` holds -- the entry above's case, spelled as two factors. `L1^p L2^q` +with `p - q` whole is now `L1^(p - q) L1^q L2^q`, and `L1^q L2^q` is `K^k M^(k/2)` for `q = k/2`, +with `K = sqrt(L1) sqrt(L2)/sqrt(M)` in front of the answer, constant wherever it is defined. + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"(a+b*asinh(c*x))*sqrt(d+i*c*d*x)*sqrt(f-i*c*f*x)".Integrate("x")` | left unevaluated | in `arsinh(c x)` and `1 + c^2 x^2`, with `K` in front | +| `"(d+i*c*d*x)^(5/2)*(a+b*asinh(c*x))*sqrt(f-i*c*f*x)".Integrate("x")` | left unevaluated | the same, with `(d + i c d x)^2` beside it | + +Rubi's 7.1.4 and 7.1.5, all 376 problems that count: 308 to 345, no row lost, 56 timeouts to 27. + ### `binomial(n, k)` is a function **Addition, not silent.** The binomial coefficient is a node, `Entity.Binomialf`, spelled diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 42586d2c4..3a7bfbbec 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -15018,6 +15018,11 @@ static bool Through(Entity node, Entity.Variable x) } return node; }); + if (written is not null && reference is not null) + root = MathS.Sqrt(written) / MathS.Sqrt(reference); + // And the radicand written as two linear factors, when no single base was its multiple. + if (rewritten == expr && TryWriteAPairOfLinearsOverARadicand(expr, x, references, multiple, out var paired, out var pairRoot)) + (rewritten, root) = (paired, pairRoot); if (rewritten == expr) return null; var simplified = rewritten.InnerSimplified; @@ -15025,14 +15030,83 @@ static bool Through(Entity node, Entity.Variable x) return null; if (Integration.ComputeAsTheSameQuestion(simplified, x, integrateByParts) is not { } answer) return null; - if (written is not null && reference is not null) - { - root = MathS.Sqrt(written) / MathS.Sqrt(reference); + if (root is not null) answer = answer.Substitute(multiple, root); - } return answer.ContainsNode(multiple) ? null : answer; } + /// + /// Two linear factors whose product is a constant multiple of a radicand a logarithm holds, + /// written as that radicand: `d + i c d x` and `f - i c f x` beside `arsinh(c x)`, whose + /// product is `d f (1 + c^2 x^2)`. `L1^p L2^q` is `L1^(p - q) L1^q L2^q` where `p - q` is + /// whole, and `L1^q L2^q` is `lambda^q M^q` for a whole `q` and `K^k M^(k/2)` for + /// `q = k/2`, with `K = sqrt(L1) sqrt(L2)/sqrt(M)` constant wherever it is defined -- the + /// same factor the single base is written with. Rubi's 7.1.4 states seventy-two of its + /// rows this way; `(d + i c d x)^(5/2) sqrt(f - i c f x) (a + b arsinh(c x))` ran past + /// seventy seconds where the same thing over `1 + c^2 x^2` takes two thirds of one. + /// + private static bool TryWriteAPairOfLinearsOverARadicand(Entity expr, Entity.Variable x, List references, + Entity.Variable multiple, out Entity rewritten, out Entity? root) + { + rewritten = expr; + root = null; + // The factors on both sides of the bar, as powers: a nested power read through, and a + // factor below the bar with its exponent negated. + var (above, below) = Functions.SingleQuotient.Of(Functions.SingleQuotient.Combine(expr)); + var factors = new List<(Entity Base, Entity Exponent)>(); + foreach (var factor in Mulf.LinearChildren(above)) + factors.Add(AsAPower(factor)); + foreach (var factor in Mulf.LinearChildren(below)) + { + var (@base, exponent) = AsAPower(factor); + factors.Add((@base, (-exponent).InnerSimplified)); + } + for (var i = 0; i < factors.Count; i++) + for (var j = 0; j < factors.Count; j++) + { + if (i == j) + continue; + var (first, p) = factors[i]; + var (second, q) = factors[j]; + if (!first.ContainsNode(x) || !second.ContainsNode(x) + || !TreeAnalyzer.TryGetPolyLinear(first, x, out _, out _) || !TreeAnalyzer.TryGetPolyLinear(second, x, out _, out _) + || (p - q).Evaled is not Number.Integer || q.Evaled is not Number.Rational half) + continue; + var twice = (2 * half).Evaled; + if (half is not Number.Integer && twice is not Number.Integer) + continue; + var product = (first * second).Expand(); + foreach (var candidate in references) + { + if (TryReadAsAConstantMultiple(product, candidate, x) is not { } lambda) + continue; + Entity together; + if (half is Number.Integer) + together = MathS.Pow(lambda, half) * MathS.Pow(candidate, half); + else + { + together = MathS.Pow(multiple, twice) * MathS.Pow(candidate, half); + root = MathS.Sqrt(first) * MathS.Sqrt(second) / MathS.Sqrt(candidate); + } + Entity result = MathS.Pow(first, (p - q).InnerSimplified) * together; + for (var k = 0; k < factors.Count; k++) + if (k != i && k != j) + result = result * MathS.Pow(factors[k].Base, factors[k].Exponent); + rewritten = result; + return true; + } + } + return false; + + static (Entity Base, Entity Exponent) AsAPower(Entity factor) + => factor switch + { + Powf(Powf(var inner, var innerExponent), var outerExponent) => (inner, (innerExponent * outerExponent).InnerSimplified), + Powf(var @base, var exponent) => (@base, exponent), + _ => (factor, Number.Integer.One), + }; + } + /// /// Whether / takes the same value at two /// points, every other symbol pinned to a fixed value -- a necessary condition for a diff --git a/Sources/Tests/UnitTests/Calculus/InverseHyperbolicSubstitutionTest.cs b/Sources/Tests/UnitTests/Calculus/InverseHyperbolicSubstitutionTest.cs index 11091a0de..4755d8421 100644 --- a/Sources/Tests/UnitTests/Calculus/InverseHyperbolicSubstitutionTest.cs +++ b/Sources/Tests/UnitTests/Calculus/InverseHyperbolicSubstitutionTest.cs @@ -97,6 +97,42 @@ private static void DifferentiatesBack(string integrand, double[] points) public void AConstantMultipleOfTheRadicandIsWrittenOverIt(string integrand, double[] points) => DifferentiatesBack(integrand, points); + /// + /// The radicand written as two linear factors: (5 + 10 i x)(7 - 14 i x) is + /// 35 (1 + 4 x^2), the radicand of arsinh(2x), so L1^p L2^q is + /// L1^(p - q) K^k (1 + 4 x^2)^(k/2) with K = sqrt(L1) sqrt(L2)/sqrt(1 + 4 x^2) + /// in front, constant wherever it is defined. Rubi's 7.1.4 and 7.1.5, 39 rows; the + /// integrands are complex everywhere, and compared so. + /// + [Theory] + [InlineData("(3 + 2*arsinh(2*x))*sqrt(5 + 10*i*x)*sqrt(7 - 14*i*x)", new[] { -0.4, 0.1, 0.3, 0.7, 1.2 })] + [InlineData("(5 + 10*i*x)^(5/2)*(3 + 2*arsinh(2*x))*sqrt(7 - 14*i*x)", new[] { -0.4, 0.1, 0.3, 0.7, 1.2 })] + [InlineData("(5 + 10*i*x)^(3/2)*(7 - 14*i*x)^(3/2)*(3 + 2*arsinh(2*x))", new[] { -0.4, 0.1, 0.3, 0.7, 1.2 })] + [InlineData("x*(3 + 2*arsinh(2*x))*sqrt(5 + 10*i*x)*sqrt(7 - 14*i*x)", new[] { -0.4, 0.1, 0.3, 0.7, 1.2 })] + public void ARadicandWrittenAsTwoLinearFactorsIsWrittenAsOne(string integrand, double[] points) + => DifferentiatesBack(integrand, points); + + /// The same with every coefficient a symbol, pinned only after integrating. + [Theory] + [InlineData("(a+b*asinh(c*x))*sqrt(d+i*c*d*x)*sqrt(f-i*c*f*x)")] + [InlineData("(d+i*c*d*x)^(5/2)*(a+b*asinh(c*x))*sqrt(f-i*c*f*x)")] + public void TwoLinearFactorsWithSymbolicCoefficients(string integrand) + { + var integral = integrand.ToEntity().Integrate("x"); + Assert.DoesNotContain("integral(", integral.Stringize()); + Entity Pin(Entity e) => e.Substitute("a", 0.9).Substitute("b", 1.7).Substitute("c", 0.6).Substitute("d", -1.3).Substitute("f", 0.8); + var derivative = Pin(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pin(integrand.ToEntity()); + foreach (var at in new[] { -0.4, 0.1, 0.3, 0.7, 1.2 }) + { + var got = derivative.Substitute("x", at).EvalNumerical(); + var want = original.Substitute("x", at).EvalNumerical(); + var difference = Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart); + var scale = Math.Max(1.0, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart)); + Assert.True(difference / scale < 1e-9, $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}"); + } + } + /// /// A root of a *negative* multiple of the quadratic is not the multiple's root times /// the quadratic's: arcosh(a x)^2/sqrt(1 - a^2 x^2) came back with i a