From d6d6158ee68759d30f89f7231165122f4d45afa9 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Mon, 5 Oct 2026 11:29:44 +0000 Subject: [PATCH] A sum of two squares below the bar, beside a repeated factor, is split over its conjugates Under u = tan(x) and t = sqrt(c + d u), the 1 + u^2 of (c + d tan(x))^(3/2)/(a + b tan(x))^2 is (t^2 - c)^2 + d^2, a quartic beside the square of a d + b (t^2 - c). The split by residues reads factors of the first and second degree only, so the quotient went to the Hermite reduction, which solved for its numerators with the symbols in every entry and ran past two minutes. A written sum of two squares P^2 + Q^2, the greater of their degrees two, beside a repeated factor, is written (P - i Q)(P + i Q) now and the quotient asked again: four rows of 4.3.2.1 with one root over a power of a + b tan are answered in one to two seconds, and two with two roots, where the sum is (b - d t^2)^2 + (c t^2 - a)^2 under the root of their quotient. The constants in that split hold i, and it is short only with them in lowest terms (#1788's first part). Part of #718. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 18 ++++++ .../Integration/IndefiniteIntegralSolver.cs | 50 +++++++++++++++++ .../SumOfTwoSquaresBelowTheBarIntegralTest.cs | 55 +++++++++++++++++++ 3 files changed, 123 insertions(+) create mode 100644 Sources/Tests/UnitTests/Calculus/SumOfTwoSquaresBelowTheBarIntegralTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 391832803..d66073fe6 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -287,6 +287,24 @@ rules' already | `"asin(sqrt(1 + x) - sqrt(x))".ToEntity().Integrate("x")` | `integral(...)` | an antiderivative in `arcsin(sqrt(1 + x) - sqrt(x))` | | `"x^3*atan(-sqrt(x)+sqrt(1+x))".ToEntity().Integrate("x")` | `integral(...)` | an antiderivative in the arctangent | +### A sum of two squares below the bar, beside a repeated factor, is split over its conjugates + +**Answers where there were none.** `(c + d tan(x))^(3/2)/(a + b tan(x))^2` was declined in 2.5.0 and ran +past two minutes since. Under `u = tan(x)` and `t = sqrt(c + d u)` the `1 + u^2` the tangent leaves is +`(t^2 - c)^2 + d^2`, a quartic beside the square of `a d + b (t^2 - c)`, and the Hermite reduction +solved for the numerators over it with the symbols in every entry. A written sum of two squares +`P^2 + Q^2`, the greater of their degrees two, beside a repeated factor, is `(P - i Q)(P + i Q)` below +the bar now, and over those every factor is one the split by residues reads -- under a root of the +quotient of two such linears the sum is `(b - d t^2)^2 + (c t^2 - a)^2`. Rubi's 4.3.2.1 +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"(c + d*tan(x))^(3/2)/(a + b*tan(x))^2".ToEntity().Integrate("x")` | `integral(...)`; past two minutes on the unreleased master | arctangents and logarithms of `sqrt(c + d tan(x))`, in two seconds | +| `"(c + d*tan(x))^(5/2)/(a + b*tan(x))^2".ToEntity().Integrate("x")` | `integral(...)` | the same | +| `"(c + d*tan(x))^(3/2)/(a + b*tan(x))^3".ToEntity().Integrate("x")` | `integral(...)` | the same | +| `"1/((a + b*tan(x))^(3/2)*(c + d*tan(x))^(3/2))".ToEntity().Integrate("x")` | `integral(...)`; past a minute on the unreleased master | in the root of the quotient of the two, in a second | + ### A function of `x^n` for a symbolic `n` beside a power of `x` is integrated in a power of `x` **Answers where there were none.** `x^(-1 + 4n)/(a + b x^n + c x^(2n))` and the rest of Rubi's diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 838463566..ffb4748e8 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -847,6 +847,19 @@ is var (multiple, leftover) ?? Integration.ComputeIndefiniteIntegral(inY, y, integrateByParts)) is { } inTheShiftedVariable) return inTheShiftedVariable.Substitute(y, (x + shift).InnerSimplified); + // A written factor that is a sum of two squares, `P^2 + Q^2` with the greater of their + // degrees two, beside a repeated factor: it is `(P - i Q)(P + i Q)`, and over those two + // the split by residues above reads every factor. `1 + u^2` beside a root of a + // linear is `(t^2 - c)^2 + d^2` under `t = sqrt(c + d u)`, a quartic nothing splits, and + // the Hermite reduction below solved for its numerators with the symbols in every entry: + // `(c + d tan(x))^(3/2)/(a + b tan(x))^2` ran past two minutes. Once: what this writes + // has no such factor left. + if (Mulf.LinearChildren(denominator).Any(f => f.ContainsNode(x) && f is Powf(_, Number.Integer { EInteger.Sign: > 0 } e) && e != Number.Integer.One) + && OverTheConjugatesOfASumOfTwoSquares(denominator, x) is { } overTheConjugates + && (SolveByPartialFractions(numerator / overTheConjugates, x, integrateByParts) + ?? Integration.ComputeIndefiniteIntegral(numerator / overTheConjugates, x, integrateByParts)) is { } overConjugates) + return overConjugates; + // A denominator with a written repeated factor takes the Hermite reduction first: // the rational part of the answer in one linear solve, and what is left is a proper // fraction over a squarefree denominator for the splits below. `(1 + x^2)/(x (1 + x^3)^2)` @@ -14149,6 +14162,43 @@ private static bool IsASumOfMonomials(Entity expr, Entity.Variable x) return changed ? product : null; } + /// + /// with every written factor P^2 + Q^2, P and + /// Q polynomials in the greater of whose degrees is two, written + /// as (P - i Q)(P + i Q), with its power; where there is none, + /// or where nothing in the quotient is a symbol: with numbers alone the split over the reals + /// reads a biquadratic as it is. Not of the first degree: (a + b x)^2 + k^2 is a + /// quadratic every rule reads, by an arctangent. + /// + private static Entity? OverTheConjugatesOfASumOfTwoSquares(Entity denominator, Entity.Variable x) + { + static int? DegreeOfAPolynomial(Entity expr, Entity.Variable x) + => !expr.ContainsNode(x) ? 0 + : TreeAnalyzer.TryGetPolynomial(expr, x, out var read) && read.Count > 0 && read.Values.All(coefficient => !coefficient.ContainsNode(x)) + && read.Keys.All(power => power.Sign >= 0 && power.CanFitInInt32()) + ? read.Keys.Max()!.ToInt32Unchecked() : null; + if (!denominator.Vars.Any(symbol => symbol != x)) + return null; + var changed = false; + Entity product = Number.Integer.One; + foreach (var factor in Mulf.LinearChildren(denominator)) + { + var (@base, power) = factor is Powf(var b, Number.Integer { EInteger.Sign: > 0 } p) ? (b, p) : (factor, Number.Integer.One); + if (@base is Sumf(Powf(var first, Number.Integer(2)), Powf(var second, Number.Integer(2))) + && (first.ContainsNode(x) || second.ContainsNode(x)) + && DegreeOfAPolynomial(first, x) is { } firstDegree && DegreeOfAPolynomial(second, x) is { } secondDegree + && System.Math.Max(firstDegree, secondDegree) == 2) + { + var (minus, plus) = (first - MathS.i * second, first + MathS.i * second); + product *= power == Number.Integer.One ? minus * plus : MathS.Pow(minus, power) * MathS.Pow(plus, power); + changed = true; + continue; + } + product *= factor; + } + return changed ? product : null; + } + /// /// with every quadratic factor that has a root off the real line /// in common with a linear factor beside it written as its leading coefficient times the diff --git a/Sources/Tests/UnitTests/Calculus/SumOfTwoSquaresBelowTheBarIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/SumOfTwoSquaresBelowTheBarIntegralTest.cs new file mode 100644 index 000000000..ef3a1a731 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/SumOfTwoSquaresBelowTheBarIntegralTest.cs @@ -0,0 +1,55 @@ +// +// 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 +{ + /// + /// A sum of two squares below the bar beside a repeated factor, written over its two + /// conjugates. Under u = tan(x) and t = sqrt(c + d u), the 1 + u^2 of + /// Rubi's 4.3.2.1 (c + d tan(x))^(3/2)/(a + b tan(x))^2 is (t^2 - c)^2 + d^2, + /// a quartic beside the square of a d + b (t^2 - c); the Hermite reduction solved for + /// its numerators with the symbols in every entry and ran past two minutes. Written as + /// (t^2 - c - i d)(t^2 - c + i d), every factor is one the split by residues reads. + /// #718 + /// + /// + /// Under a root of the quotient of two such linears the sum is (b - d t^2)^2 + (c t^2 - a)^2, + /// both parts in t, and its conjugates are quadratics as well. The integrands are real + /// where a + b tan(x) and c + d tan(x) are positive, which the points are. + /// + [Trait("Area", "Calculus")] + public sealed class SumOfTwoSquaresBelowTheBarIntegralTest + { + [Theory] + [InlineData("(c + d*tan(x))^(3/2)/(a + b*tan(x))^2")] + [InlineData("(c + d*tan(x))^(5/2)/(a + b*tan(x))^2")] + [InlineData("(c + d*tan(x))^(3/2)/(a + b*tan(x))^3")] + [InlineData("x^4/((a*d + (x^2 - c)*b)^2*((x^2 - c)^2 + d^2))")] + [InlineData("1/((a + b*tan(x))^(3/2)*(c + d*tan(x))^(3/2))")] + [InlineData("1/((a + b*tan(x))^(5/2)*(c + d*tan(x))^(3/2))")] + public void OverItsConjugates(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.7).Substitute("c", 1.1).Substitute("d", 0.6); + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pinned(integrand.ToEntity()); + foreach (var at in new[] { 0.2, 0.5, 0.9, 1.2 }) + { + 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)), + $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}"); + } + } + } +}