diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 5d77674d5..31abe3799 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -508,6 +508,25 @@ antiderivative on each side of it, as Rubi's is | `"1/((2 + 3*x^2)^(1/4)*(4 + 3*x^2))".ToEntity().Integrate("x")` | `integral(...)` | the same in `(2 + 3 x^2)^(1/4)` | | `"1/((-2 + 3*x^2)*(-1 + 3*x^2)^(1/4))".ToEntity().Integrate("x")` | `integral(...)` | `-(arctan(u) + artanh(u))/(2 sqrt(6))`, `u = sqrt(3) x/(sqrt(2) (-1 + 3 x^2)^(1/4))` | +### A product of powers of the trigonometric functions, some not whole, is integrated as sine and cosine powers + +**Answers where there were none.** `(a sin(x))^(5/2) sqrt(b sec(x))` was declined, and so were +`(d csc(x))^(3/2) sqrt(c sec(x))`, `cos(x)^(7/2)/sin(x)^(7/2)` and the rest of the products of +powers of the trigonometric functions of one argument, each of which is `sin^M cos^N` times a constant +on every interval where both are continuous. With the exponents adding up to an even number that is +`tan^M (1 + tan^2)^(-(M + N)/2)`, a function of the tangent, and with `M` or `N` an odd whole number +the plain product, which the substitution by the other function answers: Chebyshev's three cases. +It is integrated so now, and the answer is the integrand times the antiderivative of that form over +the form, which holds on every such interval, the constants inside the roots whatever their signs. +Rubi's 4.1.0 to 4.6.0 ([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"(a*sin(x))^(5/2)*sqrt(b*sec(x))".ToEntity().Integrate("x")` | `integral(...)` | the integrand times an antiderivative in `sqrt(tan(x))` over `tan(x)^(5/2) (1 + tan(x)^2)^(-1)` | +| `"cos(x)^(7/2)/sin(x)^(7/2)".ToEntity().Integrate("x")` | `integral(...)` | the same over `tan(x)^(-7/2)` | +| `"(b*tan(x)^3)^(3/2)".ToEntity().Integrate("x")` | `integral(...)` | the same over `tan(x)^(9/2)` | +| `"(d*sec(x))^(5/2)*sqrt(b*tan(x))".ToEntity().Integrate("x")` | `integral(...)` | the integrand times an antiderivative in `sqrt(sin(x))` over `sin(x)^(1/2) cos(x)^(-3)` | + ### The third case of a binomial differential is right for a negative `x` too **Answers where there were none.** Chebyshev's third case, `x^m (a + b x^n)^(p/q)` with diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index d7b12536b..17007530f 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -8795,6 +8795,136 @@ private static bool TryReadAHomogeneousTrigonometricPolynomial( return true; } + /// + /// A product of powers of the trigonometric functions of one argument, some of them not + /// whole, which is sin(z)^M cos(z)^N times a constant on every interval where both + /// are continuous: with M + N even integrated as + /// tan(z)^M (1 + tan(z)^2)^(-(M + N)/2), and with M or N an odd whole + /// number as the plain product, Chebyshev's other two cases. + /// + /// + /// + /// sqrt(c sin(x))/sqrt(d cos(x)) is sqrt(c/d) sqrt(tan(x)) wherever both + /// roots are real, and was declined, with the rest of such products the corpus has, a + /// constant inside a root among them: (d csc(x))^(3/2) sqrt(c sec(x)), + /// (a sin(x))^(5/2) sqrt(b sec(x)), (d sec(x))^(5/2) sqrt(b tan(x)). Each is + /// a power of the sine times a power of the cosine, and with the exponents adding up to an + /// even number it is a function of the tangent: sin^M cos^N is tan^M cos^(M + N), + /// and cos^(M + N) is (1 + tan^2)^(-(M + N)/2) exactly. Rubi's 4.1.0 to + /// 4.6.0. + /// + /// + /// The constant is not written: the answer is the integrand times the antiderivative of the + /// tangent's form over that form, a quotient whose logarithmic derivative is zero -- both + /// halves have (M cot(z) - N tan(z)) z' -- so it is constant wherever it is + /// continuous, and the answer holds on every interval where the integrand is, the constants + /// inside the roots whatever their signs. + /// https://github.com/asc-community/AngouriMath/issues/718 + /// + /// + internal static Entity? SolveAProductOfPowersOfTrigonometricFunctionsThroughTheTangent(Entity expr, Entity.Variable x, bool integrateByParts) + { + if (!expr.Nodes.Any(node => node is Powf(var radicand, Number.Rational fraction) && fraction is not Number.Integer && radicand.ContainsNode(x))) + return null; + Entity? argument = null; + Number.Rational sine = Number.Integer.Zero; + Number.Rational cosine = Number.Integer.Zero; + var notWhole = false; + Entity constant = Number.Integer.One; + Entity varying = Number.Integer.One; + foreach (var (factor, underneath) in ThroughWholePowersOfProducts(FactorsOfTheIntegrand(expr))) + { + if (!factor.ContainsNode(x)) + { + constant = underneath ? constant / factor : constant * factor; + continue; + } + varying = underneath ? varying / factor : varying * factor; + var (@base, power) = factor is Powf(var b, var p) && p.Evaled is Number.Rational r + ? (b, r) + : (factor, (Number.Rational)Number.Integer.One); + // A constant multiple of a function, and a whole power of one inside the power: + // `(b tan(z)^3)^(3/2)`. + if (@base is Mulf(var left, var right)) + @base = !left.ContainsNode(x) ? right : !right.ContainsNode(x) ? left : @base; + if (@base is Powf(var inner, Number.Integer innerPower)) + (@base, power) = (inner, (Number.Rational)(power * innerPower)); + if (underneath) + power = (Number.Rational)(-power); + if (power is not Number.Integer) + notWhole = true; + // The powers of the sine and of the cosine each function is. + (Entity Of, int OnSine, int OnCosine)? read = @base switch + { + Sinf(var of) => (of, 1, 0), + Cosf(var of) => (of, 0, 1), + Tanf(var of) => (of, 1, -1), + Cotanf(var of) => (of, -1, 1), + Secantf(var of) => (of, 0, -1), + Cosecantf(var of) => (of, -1, 0), + _ => null + }; + if (read is null || argument is not null && argument != read.Value.Of) + return null; + var (functionOf, onSine, onCosine) = read.Value; + argument = functionOf; + sine = (Number.Rational)(sine + power * onSine); + cosine = (Number.Rational)(cosine + power * onCosine); + } + if (argument is null || !notWhole + || !TreeAnalyzer.TryGetPolyLinear(argument, x, out var slope, out _) || TreeAnalyzer.IsZero(slope)) + return null; + Entity form; + if ((sine + cosine) is Number.Integer { EInteger: var total } && total.IsEven && total.CanFitInInt32()) + { + // The exponents add up to an even number: a function of the tangent. + var tangent = MathS.Tan(argument); + var half = -total.ToInt32Checked() / 2; + form = half == 0 + ? MathS.Pow(tangent, sine) + : MathS.Pow(tangent, sine) * MathS.Pow(1 + MathS.Sqr(tangent), half); + } + else if (sine is Number.Integer { EInteger: var m } && !m.IsEven || cosine is Number.Integer { EInteger: var n } && !n.IsEven) + // An odd whole power of one of them: the plain product, which the substitution by + // the other answers, `(d sec(x))^(5/2) sqrt(b tan(x))` being `sin^(1/2) cos^(-3)` + // times a constant. + form = (sine is Number.Integer { IsZero: true } ? Number.Integer.One : MathS.Pow(MathS.Sin(argument), sine)) + * (cosine is Number.Integer { IsZero: true } ? Number.Integer.One : MathS.Pow(MathS.Cos(argument), cosine)); + else + return null; + form = form.InnerSimplified; + // Already the form, and then the substitution's to answer as it stands. + if (varying == form || varying.InnerSimplified == form) + return null; + if (Integration.ComputeAsTheSameQuestion(form, x, integrateByParts) is not { } answer + || answer.Nodes.Any(node => node == MathS.NaN)) + return null; + return constant * varying * answer / form; + } + + + /// + /// with a whole power of a product read as the product of the + /// powers, a negative one on the other side of the bar: 1/(cos(x)^(7/2) sqrt(sin(x))) + /// comes back from the polynomial term's c/g as (cos(x)^(7/2) sqrt(sin(x)))^(-1), + /// one factor that is no function's power, and the half-angle substitution searched it for + /// twenty seconds before the tangent's form was reached. + /// + private static IEnumerable<(Entity Factor, bool Underneath)> ThroughWholePowersOfProducts(IEnumerable<(Entity Factor, bool Underneath)> factors) + { + foreach (var (factor, underneath) in factors) + { + if (factor is not Powf(Mulf product, Number.Integer { EInteger: var power }) || power.IsZero) + { + yield return (factor, underneath); + continue; + } + var magnitude = Number.Integer.Create(power.Abs()); + foreach (var child in Mulf.LinearChildren(product)) + yield return (magnitude == Number.Integer.One ? child : MathS.Pow(child, magnitude), power.Sign < 0 ? !underneath : underneath); + } + } + /// /// A power of the cotangent that is not whole below the bar of an integrand with the /// tangent of the same argument in it, written as a power of the tangent above it: diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 2f968045c..68d0c3a12 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -901,6 +901,10 @@ private static Entity Normalized(Entity expr, Entity.Variable x) => // A power of the cotangent that is not whole beside the tangent, written as the // tangent's with the constant that takes in front, which the substitution below reads. if ((answer = IndefiniteIntegralSolver.SolveByWritingAPowerOfTheCotangentInTheTangent(expr, x, integrateByParts)) is { }) return answer; + // A product of powers of the trigonometric functions of one argument, some not whole, + // whose powers of the sine and cosine add up to an even number: the tangent's form + // times a constant, which the substitution below reads. + if ((answer = IndefiniteIntegralSolver.SolveAProductOfPowersOfTrigonometricFunctionsThroughTheTangent(expr, x, integrateByParts)) is { }) return answer; if ((answer = IndefiniteIntegralSolver.SolveByTangentSubstitution(expr, x, integrateByParts)) is { }) return answer; // A root of a quadratic in the tangent with a linear term, rotated until it has // none: `1/sqrt(a + b tan(x) + c tan(x)^2)` is `1/sqrt(A + C tan(y)^2)` under diff --git a/Sources/Tests/UnitTests/Calculus/ProductOfTrigonometricPowersIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/ProductOfTrigonometricPowersIntegralTest.cs new file mode 100644 index 000000000..844c7e5ad --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/ProductOfTrigonometricPowersIntegralTest.cs @@ -0,0 +1,58 @@ +// +// 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 product of powers of the trigonometric functions of one argument, some not whole, which + /// is sin^M cos^N times a constant on every interval where both are continuous, with + /// M + N even, the tangent's form tan^M (1 + tan^2)^(-(M + N)/2), or with + /// M or N an odd whole number, the plain product. Compared as complex numbers + /// on both sides of the zeros, where the integrands leave the reals and the constant is what + /// keeps the answer right. Rubi's 4.1.0 to 4.6.0. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class ProductOfTrigonometricPowersIntegralTest + { + [Theory] + [InlineData("sqrt(c*sin(x))/sqrt(d*cos(x))")] + [InlineData("(d*csc(x))^(3/2)*sqrt(c*sec(x))")] + [InlineData("(a*sin(x))^(5/2)*sqrt(b*sec(x))")] + [InlineData("(b*tan(x)^3)^(3/2)")] + [InlineData("sqrt(c*sin(a + b*x))/(d*cos(a + b*x))^(9/2)")] + [InlineData("cos(x)^(7/2)/sin(x)^(7/2)")] + [InlineData("(d*sec(x))^(5/2)*sqrt(b*tan(x))")] + [InlineData("sqrt(b*tan(x))/(a*sin(x))^(3/2)")] + public void AsPowersOfTheSineAndTheCosine(string integrand) + { + var integral = integrand.ToEntity().Integrate("x"); + Assert.DoesNotContain("integral(", integral.Stringize()); + Assert.DoesNotContain("NaN", integral.Stringize()); + Entity Pinned(Entity e) => e.Substitute("a", 0.4).Substitute("b", 1.1).Substitute("c", 1.3).Substitute("d", 0.8); + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pinned(integrand.ToEntity()); + var compared = 0; + foreach (var at in new[] { -2.6, -1.1, -0.4, 0.3, 0.8, 1.2, 2.0 }) + { + var want = original.Substitute("x", at).EvalNumerical(); + if (want.IsNaN) + continue; + compared++; + 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}"); + } + Assert.True(compared >= 6, $"only {compared} points could be compared for {integrand}"); + } + } +}