diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 3547f0dd3..62e2ea2ab 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -302,6 +302,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 31ec42351..a5964c8bd 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)` @@ -14363,6 +14376,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}"); + } + } + } +}