diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index b73c79b1d..0f440ae58 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -570,6 +570,20 @@ 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 power of `csc(x) - sin(x)` or `sec(x) - cos(x)` is integrated as sine and cosine powers + +**Answers where there were none.** `(csc(x) - sin(x))^(5/2)` was declined, with the rest of Rubi's +4.7.7 to a power that is not whole of either difference, either way up. `csc(z) - sin(z)` is +`cos(z)^2/sin(z)` and `sec(z) - cos(z)` is `sin(z)^2/cos(z)`; written so, the power is a product of +powers of the sine and cosine, which are integrated through the tangent, a quotient of whole powers +of the six functions read inside a power as it is +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"(csc(x) - sin(x))^(5/2)".ToEntity().Integrate("x")` | `integral(...)` | powers of `sin(x)` times `(cos(x)^2/sin(x))^(5/2)` | +| `"1/(sec(x) - cos(x))^(3/2)".ToEntity().Integrate("x")` | `integral(...)` | a root of `cos(x)` with a logarithm and an arctangent, times `(sin(x)^2/cos(x))^(-3/2)` | + ### 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 diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index fd17457b1..c88422e78 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -9090,33 +9090,38 @@ private static bool TryReadAHomogeneousTrigonometricPolynomial( 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) + // Inside the power, a constant multiple of a product and quotient of whole powers of + // the functions: `(b tan(z)^3)^(3/2)`, and `(cos(z)^2/sin(z))^(5/2)`, which is how + // `(csc(z) - sin(z))^(5/2)` is written. Not whole where some function's power is not: + // `(a csc(z)^2)^(-7/2)` is a whole power of the modulus of the cosecant, which the rule for + // an even power under a root answers with its sign. + if (!TryReadAMonomialInTheTrigonometricFunctions(@base, x, out var parts)) return null; - var (functionOf, onSine, onCosine) = read.Value; - argument = functionOf; - sine = (Number.Rational)(sine + power * onSine); - cosine = (Number.Rational)(cosine + power * onCosine); + foreach (var (function, times) in parts) + { + // The powers of the sine and of the cosine each function is. + (Entity Of, int OnSine, int OnCosine)? read = function 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; + var weight = (Number.Rational)(power * Number.Integer.Create(times)); + if (weight is not Number.Integer) + notWhole = true; + sine = (Number.Rational)(sine + weight * onSine); + cosine = (Number.Rational)(cosine + weight * onCosine); + } } if (argument is null || !notWhole || !TreeAnalyzer.TryGetPolyLinear(argument, x, out var slope, out _) || TreeAnalyzer.IsZero(slope)) @@ -9150,6 +9155,87 @@ private static bool TryReadAHomogeneousTrigonometricPolynomial( } + /// + /// A difference of a trigonometric function and its reciprocal's partner written as the + /// quotient it is: csc(z) - sin(z) is cos(z)^2/sin(z) and sec(z) - cos(z) + /// is sin(z)^2/cos(z), so that a power of either is a product of powers of the sine + /// and cosine; the integrand so written is asked as the same question. + /// + /// + /// (csc(x) - sin(x))^(5/2) was declined, with the rest of Rubi's 4.7.7 to a power that + /// is not whole of either difference, either way up. As the quotient it is a product of + /// powers of the sine and cosine, which the rule beside it reads through the tangent. + /// https://github.com/asc-community/AngouriMath/issues/718 + /// + internal static Entity? SolveByWritingADifferenceOfReciprocalFunctionsAsAQuotient(Entity expr, Entity.Variable x, bool integrateByParts) + { + var rewritten = expr.Replace(node => + { + if (!node.ContainsNode(x)) + return node; + // Each term a signed function: `csc(z)`, `-sin(z)`, `-1 * cos(z)`. + (Entity Function, int Sign) Signed(Entity term, int sign) => term switch + { + Mulf(Number.Integer { EInteger: var one }, var f) when one.Equals(EInteger.FromInt32(-1)) => (f, -sign), + Mulf(var f, Number.Integer { EInteger: var one }) when one.Equals(EInteger.FromInt32(-1)) => (f, -sign), + _ => (term, sign) + }; + List<(Entity Function, int Sign)>? signed = node switch + { + Minusf(var minuend, var subtrahend) => new() { Signed(minuend, 1), Signed(subtrahend, -1) }, + Sumf(var left, var right) when left is not (Sumf or Minusf) && right is not (Sumf or Minusf) => new() { Signed(left, 1), Signed(right, 1) }, + _ => null + }; + if (signed is null) + return node; + var (first, second) = (signed[0], signed[1]); + if (first.Sign == second.Sign) + return node; + var (positive, negative) = first.Sign > 0 ? (first.Function, second.Function) : (second.Function, first.Function); + return (positive, negative) switch + { + (Cosecantf(var a), Sinf(var b)) when a == b => MathS.Pow(MathS.Cos(a), 2) / MathS.Sin(a), + (Sinf(var a), Cosecantf(var b)) when a == b => -MathS.Pow(MathS.Cos(a), 2) / MathS.Sin(a), + (Secantf(var a), Cosf(var b)) when a == b => MathS.Pow(MathS.Sin(a), 2) / MathS.Cos(a), + (Cosf(var a), Secantf(var b)) when a == b => -MathS.Pow(MathS.Sin(a), 2) / MathS.Cos(a), + _ => node + }; + }); + return rewritten == expr ? null : Integration.ComputeAsTheSameQuestion(rewritten, x, integrateByParts); + } + + /// + /// read as a constant times a product of whole powers of the six + /// trigonometric functions, each with how many times it divides or multiplies; false where + /// anything else holds . + /// + private static bool TryReadAMonomialInTheTrigonometricFunctions(Entity expr, Entity.Variable x, out List<(Entity Function, int Times)> parts) + { + var read = new List<(Entity Function, int Times)>(); + parts = read; + return Read(expr, 1); + + bool Read(Entity e, int times) + { + switch (e) + { + case var constant when !constant.ContainsNode(x): + return true; + case Mulf(var left, var right): + return Read(left, times) && Read(right, times); + case Divf(var above, var below): + return Read(above, times) && Read(below, -times); + case Powf(var inner, Number.Integer { EInteger: var power }) when power.CanFitInInt32(): + return Read(inner, times * power.ToInt32Unchecked()); + case Sinf or Cosf or Tanf or Cotanf or Secantf or Cosecantf: + read.Add((e, times)); + return true; + default: + return false; + } + } + } + /// /// 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))) diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 08211ea4b..94ed6ccd9 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -917,6 +917,9 @@ 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 difference of a function and its reciprocal's partner, `csc(z) - sin(z)`, as the + // quotient of powers it is, for the rule after it. + if ((answer = IndefiniteIntegralSolver.SolveByWritingADifferenceOfReciprocalFunctionsAsAQuotient(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. diff --git a/Sources/Tests/UnitTests/Calculus/DifferenceOfReciprocalFunctionsIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/DifferenceOfReciprocalFunctionsIntegralTest.cs new file mode 100644 index 000000000..42d5ada2c --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/DifferenceOfReciprocalFunctionsIntegralTest.cs @@ -0,0 +1,51 @@ +// +// 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 power that is not whole of csc(x) - sin(x) or sec(x) - cos(x), which are + /// cos(x)^2/sin(x) and sin(x)^2/cos(x): written so, a product of powers of the + /// sine and cosine, read through the tangent. Rubi's 4.7.7. + /// #718 + /// + /// + /// Compared as complex numbers on both sides of the zeros of the sine and the cosine, where the + /// integrands are complex on some of the intervals. + /// + [Trait("Area", "Calculus")] + public sealed class DifferenceOfReciprocalFunctionsIntegralTest + { + [Theory] + [InlineData("(csc(x) - sin(x))^(5/2)")] + [InlineData("(csc(x) - sin(x))^(3/2)")] + [InlineData("1/(csc(x) - sin(x))^(1/2)")] + [InlineData("1/(csc(x) - sin(x))^(7/2)")] + [InlineData("(sec(x) - cos(x))^(5/2)")] + [InlineData("1/(sec(x) - cos(x))^(3/2)")] + [InlineData("(sin(x) - csc(x))^(3/2)")] + public void IsAProductOfPowersOfTheSineAndCosine(string integrand) + { + var integral = integrand.ToEntity().Integrate("x"); + Assert.DoesNotContain("integral(", integral.Stringize()); + var derivative = integral.Substitute("C", 0).Differentiate("x"); + var original = integrand.ToEntity(); + foreach (var at in new[] { -2.3, -1.1, -0.4, 0.4, 1.1, 1.9, 2.6 }) + { + 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}"); + } + } + } +}