diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 070dcb02e..cee0dcaa1 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -654,6 +654,20 @@ that `sec(x)^2/(a + b sin(x))` is rational in them rather than declined at once | `"tan(x)^4/(a + a*cos(x))".ToEntity().Integrate("x")` | `integral(...)` | an antiderivative in `tan(x/2)` | | `"csc(x)^2/(a + a*cos(x))".ToEntity().Integrate("x")` | `integral(...)` | `(tan(x/2)^3/12 + tan(x/2)/2 - 1/(4 tan(x/2)))/a` | +### A tangent times that of its double is a secant less one + +**Answers where there were none.** `sec(2(a + b x))^2 sqrt(c tan(a + b x) tan(2(a + b x)))` was +declined, with the rest of Rubi's 4.7.7 powers of `c tan(y) tan(2y)` beside the secant or cosine +of `2y`, some after searches past the budget. `tan(y) tan(2y)` is `sec(2y) - 1`, and written so the +integrand is in the one argument `2y`, which the half-angle tangent answers; the answer is exact +where the integrand is real ([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"sec(2*(a + b*x))^2*sqrt(c*tan(a + b*x)*tan(2*(a + b*x)))".ToEntity().Integrate("x")` | `integral(...)` | `sgn(tan(a + b x))` times powers of `sqrt(1 - tan(a + b x)^2)` | +| `"1/sqrt(c*tan(a + b*x)*tan(2*(a + b*x)))".ToEntity().Integrate("x")` | `integral(...)` | logarithms in `tan(a + b x)`, `sgn(tan(a + b x))` in front | +| `"tan(x)*tan(2*x)".ToEntity().Integrate("x")` | `integral(...)` | `ln((1 + sin(2x))/(1 - sin(2x)))/4 - x` | + ### A power of `a + i a tan` beside a power of the secant is integrated as an exponential **Answers where there were none.** `sqrt(a + i a tan(x))/sqrt(c sec(x))` is `sqrt(a/c) e^(i x/2)` diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 120c9f71b..db5f3fcf6 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -4152,6 +4152,56 @@ Entity Integral(ERational power) return roots < 2 ? answer : answer.Provided((secantKind ? MathS.Cos(argument) : MathS.Sin(argument)) >= Number.Integer.Zero); } + /// + /// tan(y) tan(2y) written sec(2y) - 1, and the integrand asked again in the + /// one argument 2y. + /// + /// + /// + /// tan(y) is (1 - cos(2y))/sin(2y), so tan(y) tan(2y) is + /// (1 - cos(2y))/cos(2y), which is sec(2y) - 1 wherever both sides are + /// defined; at the zeros of cos(y) the left is -2 as a limit, which the right + /// is. Rubi's 4.7.7 has sec(2(a + b x))^k sqrt(c tan(a + b x) tan(2(a + b x))) and + /// its kin, a half-odd power of c sec(2y) - c beside powers of the secant or cosine + /// of 2y, which + /// answers in that one argument; with two arguments nothing read them. + /// + /// + /// The same question in another spelling, so asked as the same question. The answer is + /// what that rule gives, exact where the integrand is real. + /// https://github.com/asc-community/AngouriMath/issues/718 + /// + /// + internal static Entity? SolveByWritingATangentTimesThatOfItsDoubleThroughTheSecant(Entity expr, Entity.Variable x, bool integrateByParts) + { + static bool IsTwice(Entity twice, Entity once) + => (twice - 2 * once).Expand().InnerSimplified.Evaled is Number.Complex { IsZero: true }; + var found = false; + var rewritten = expr.Replace(node => + { + if (node is not Mulf || !node.ContainsNode(x)) + return node; + var factors = Mulf.LinearChildren(node).ToList(); + var single = factors.FindIndex(factor => factor is Tanf(var y) && y.ContainsNode(x) + && factors.Any(other => other is Tanf(var z) && z != y && IsTwice(z, y))); + if (single < 0) + return node; + var once = ((Tanf)factors[single]).Argument; + var twice = factors.FindIndex(other => other is Tanf(var z) && z != once && IsTwice(z, once)); + var doubled = ((Tanf)factors[twice]).Argument; + Entity others = Number.Integer.One; + for (var i = 0; i < factors.Count; i++) + if (i != single && i != twice) + others = others == Number.Integer.One ? factors[i] : others * factors[i]; + found = true; + // Spread over the difference, so that a root of it reads as `c sec(2y) - c`. + return others == Number.Integer.One ? MathS.Sec(doubled) - 1 : others * MathS.Sec(doubled) - others; + }); + if (!found) + return null; + return Integration.ComputeAsTheSameQuestion(rewritten, x, integrateByParts); + } + /// /// as what it is a product of beside , and /// the sum of the exponents of each base in it, read through products, quotients and diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 9f1119d6b..494173d9e 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -867,6 +867,8 @@ private static Entity Normalized(Entity expr, Entity.Variable x) => if ((answer = IndefiniteIntegralSolver.SolveSymbolicPowersOfOnePlusMinusASineThroughTheSine(expr, x, integrateByParts)) is { }) return answer; // And a half-odd power of a +- a sec(y), which is that square over cos(y): by the half-angle // tangent, in which the whole is rational beside one root. + // And `tan(y) tan(2y)` written `sec(2y) - 1` first, so that the rule reads one argument. + if ((answer = IndefiniteIntegralSolver.SolveByWritingATangentTimesThatOfItsDoubleThroughTheSecant(expr, x, integrateByParts)) is { }) return answer; if ((answer = IndefiniteIntegralSolver.SolveByTheHalfAngleTangentBesideAHalfOddPowerOfOnePlusASecant(expr, x, integrateByParts)) is { }) return answer; // And `a ± a cosh(y)` under a fractional power: `2a cosh(y/2)^2`, `-2a sinh(y/2)^2`. if ((answer = IndefiniteIntegralSolver.SolveByTheHalfAngleWhereOnePlusAHyperbolicCosineIsASquare(expr, x, integrateByParts)) is { }) return answer; diff --git a/Sources/Tests/UnitTests/Calculus/TangentTimesThatOfItsDoubleIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/TangentTimesThatOfItsDoubleIntegralTest.cs new file mode 100644 index 000000000..1a3eb0a2b --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/TangentTimesThatOfItsDoubleIntegralTest.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 +{ + /// + /// tan(y) tan(2y) is sec(2y) - 1, and written so a power of + /// c tan(y) tan(2y) beside the secant or cosine of 2y is in one argument, which + /// the half-angle tangent answers. Rubi's 4.7.7. + /// #718 + /// + /// + /// Checked by differentiating back with a = 0.4, b = 1.1, c = 1.3 where the + /// integrand is real, cos(2(a + b x)) positive, on both sides of the zero of + /// tan(a + b x): the rule the rewriting reaches is exact there. + /// + [Trait("Area", "Calculus")] + public sealed class TangentTimesThatOfItsDoubleIntegralTest + { + [Theory] + [InlineData("sec(2*(a + b*x))^2*sqrt(c*tan(a + b*x)*tan(2*(a + b*x)))")] + [InlineData("cos(2*(a + b*x))*(c*tan(a + b*x)*tan(2*(a + b*x)))^(3/2)")] + [InlineData("1/sqrt(c*tan(a + b*x)*tan(2*(a + b*x)))")] + [InlineData("sec(2*(a + b*x))/(c*tan(a + b*x)*tan(2*(a + b*x)))^(3/2)")] + [InlineData("tan(x)*tan(2*x)")] + public void IsAPowerOfASecantLessOne(string integrand) + { + var integral = integrand.ToEntity().Integrate("x"); + Assert.DoesNotContain("integral(", integral.Stringize()); + Entity Pinned(Entity e) => e.Substitute("a", 0.4).Substitute("b", 1.1).Substitute("c", 1.3); + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pinned(integrand.ToEntity()); + foreach (var at in new[] { -0.9, -0.7, -0.5, 0.05, 0.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) + Math.Abs((double)want.ImaginaryPart)), + $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}"); + } + } + } +}