diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 8c3f2c86f..a6ca8e773 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -381,6 +381,25 @@ two more of Rubi's 4.5.2.3. The two sums multiply to `-a c tan^2`, so in `u = a | `"sec(e + f*x)*(a + a*sec(e + f*x))^m*(c - c*sec(e + f*x))^(5/2)".ToEntity().Integrate("x")` | `integral(...)` | 469 characters | | `"sec(e + f*x)*(a + a*sec(e + f*x))^m*sqrt(c - c*sec(e + f*x))".ToEntity().Integrate("x")` | `integral(...)` | 268 characters | +### `a cos + i a sin` beside powers of the secant is written over the cosine + +**Answers where there were none, and shorter ones.** `sec(c + d x)^5/(a cos(c + d x) + i a sin(c + d x))^2` was declined, +and so were `sec^4` over the cube, and `sec^6` over the sum and over its cube, Rubi's 4.7.2. `A cos(y) ± i A sin(y)` below +the bar, with nothing but cosines and secants of `y` beside it, is written `A cos(y) (1 ± i tan(y))` now, where the +exponential it also is gave nothing the rules read: a power of the secant over a power of `1 ± i tan(y)` is what the +rules for `a + i a tan` take. 2.5.0 declined all six below; where the unreleased master answered, `sec^4` over the sum +and `sec^2` over its cube, the answers are a quarter and three-fifths the size +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"sec(c + d*x)^6/(a*cos(c + d*x) + i*a*sin(c + d*x))".ToEntity().Integrate("x")` | `integral(...)` | 419 characters | +| `"sec(c + d*x)^5/(a*cos(c + d*x) + i*a*sin(c + d*x))^2".ToEntity().Integrate("x")` | `integral(...)` | 668 characters | +| `"sec(c + d*x)^4/(a*cos(c + d*x) + i*a*sin(c + d*x))^3".ToEntity().Integrate("x")` | `integral(...)` | 639 characters | +| `"sec(c + d*x)^6/(a*cos(c + d*x) + i*a*sin(c + d*x))^3".ToEntity().Integrate("x")` | `integral(...)` | 771 characters | +| `"sec(c + d*x)^4/(a*cos(c + d*x) + i*a*sin(c + d*x))".ToEntity().Integrate("x")` | `integral(...)` | 209 characters, where the unreleased master gave 780 | +| `"sec(c + d*x)^2/(a*cos(c + d*x) + i*a*sin(c + d*x))^3".ToEntity().Integrate("x")` | `integral(...)` | 488 characters, where the unreleased master gave 810 | + ### An even rational function with symbols in it is not searched for a substitution **Answers where there were none.** `(d + e x^2 + f x^4)/(x^4 (a + b x^2 + c x^4)^2)` ran past a minute: the diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 37b5fe6ae..f45dd9882 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -28604,6 +28604,62 @@ node is Powf(Mulf(var left, var right), Number.Integer power) return Integration.ComputeAsTheSameQuestion((above / rewritten).InnerSimplified, x, integrateByParts); } + /// + /// A cos(y) + i A sin(y) below the bar, with nothing but cosines of y and their + /// reciprocals beside it, written as A cos(y) (1 + i tan(y)), and A cos(y) - i A sin(y) + /// as A cos(y) (1 - i tan(y)): a power of the secant over a power of 1 ± i tan(y), + /// which the rules for that sum answer. + /// + /// + /// Where the exponential + /// writes declines: an odd power of the secant beside it, `sec(y)^5/(a cos(y) + i a sin(y))^2`, + /// is a half-odd power of `1 + tan(y)^2` in the tangent, and that is `(1 - i t)^(5/2) (1 + i t)^(1/2)` + /// over the sum, which the rules for `a + i a tan` take. Rubi's 4.7.2. + /// https://github.com/asc-community/AngouriMath/issues/718 + /// + internal static Entity? SolveByWritingAnImaginarySumOfACosineAndASineOverTheCosine(Entity expr, Entity.Variable x, bool integrateByParts) + { + var (above, below) = Functions.SingleQuotient.Of(Functions.SingleQuotient.Combine(expr)); + if (!below.ContainsNode(x)) + return null; + Entity? argumentOfThePair = null; + var rewritten = below.Replace(node => + { + if (node is not (Sumf or Minusf) || !node.ContainsNode(x) + || !TryReadACosineAndASine(node, x, out var cosineCoefficient, out var sineCoefficient, out var argument) + || argumentOfThePair is { } && argumentOfThePair != argument) + return node; + var ratio = Functions.PartialFractions.Bare((sineCoefficient / cosineCoefficient).InnerSimplified); + if (ratio.Evaled is not Number.Complex) + ratio = Functions.PartialFractions.Bare(ratio.Simplify()); + var plus = ratio.Evaled == MathS.i.Evaled; + if (!plus && ratio.Evaled != (-MathS.i).Evaled) + return node; + argumentOfThePair = argument; + return cosineCoefficient * MathS.Cos(argument) * (1 + (plus ? MathS.i : -MathS.i) * MathS.Tan(argument)); + }); + if (rewritten == below || argumentOfThePair is not { } y) + return null; + // Nothing beside the pair but cosines and secants of its argument: a sine elsewhere is the + // exponential's. + bool OnlyCosines(Entity side, bool andTheTangent) => side.Nodes.All(node => node switch + { + Tanf(var a) => andTheTangent && a == y, + Sinf or Tanf or Cotanf or Cosecantf => false, + Cosf(var a) => a == y, + Secantf(var a) => a == y, + _ => true, + }); + if (!OnlyCosines(above, andTheTangent: false) || !OnlyCosines(rewritten, andTheTangent: true)) + return null; + // A whole power of the product split, so that the power of the sum stands on its own. + rewritten = rewritten.Replace(node => + node is Powf(Mulf(var left, var right), Number.Integer power) && (left.Nodes.Any(n => n is Tanf) || right.Nodes.Any(n => n is Tanf)) + ? MathS.Pow(left, power) * MathS.Pow(right, power) + : node); + return Integration.ComputeAsTheSameQuestion((above / rewritten).InnerSimplified, x, integrateByParts); + } + /// /// Bioche's first two rules for the hyperbolic functions: a rational function of /// sinh(y) and cosh(y) that is odd in the hyperbolic sine is a rational diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 8e98ddd36..924f34825 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -692,6 +692,7 @@ private static Entity Normalized(Entity expr, Entity.Variable x) => if ((answer = IndefiniteIntegralSolver.SolveByWritingAnImaginaryTangentAsAnExponential(expr, x, integrateByParts)) is { }) return answer; // And `A cos(z) + i A sin(z)`, which is `A e^(i z)`, where no rotation is real. if ((answer = IndefiniteIntegralSolver.SolveByWritingAnImaginarySumOfACosineAndASineAsAnExponential(expr, x, integrateByParts)) is { }) return answer; + if ((answer = IndefiniteIntegralSolver.SolveByWritingAnImaginarySumOfACosineAndASineOverTheCosine(expr, x, integrateByParts)) is { }) return answer; // Exponentials of quadratics, with sines, cosines and sums of them multiplied out: // each term is the Gaussian's, where the search would take the product by parts. if ((answer = IndefiniteIntegralSolver.SolveAProductOfExponentialsOfQuadratics(expr, x)) is { }) return answer; diff --git a/Sources/Tests/UnitTests/Calculus/AnImaginaryCosineAndSineSumOverTheCosineIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/AnImaginaryCosineAndSineSumOverTheCosineIntegralTest.cs new file mode 100644 index 000000000..9e313403d --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/AnImaginaryCosineAndSineSumOverTheCosineIntegralTest.cs @@ -0,0 +1,42 @@ +// +// 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 cos(y) + i a sin(y) beside powers of the secant is written a cos(y) (1 + i tan(y)), + /// which the rules for a + i a tan take. Rubi's 4.7.2. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class AnImaginaryCosineAndSineSumOverTheCosineIntegralTest + { + [Theory] + [InlineData("sec(x)^5/(a*cos(x) + i*a*sin(x))^2")] + [InlineData("sec(x)^6/(a*cos(x) - i*a*sin(x))")] + public void DifferentiatesBack(string integrand) + { + var integral = integrand.ToEntity().Integrate("x"); + Assert.DoesNotContain("integral(", integral.Stringize()); + Entity Pinned(Entity e) => e.Substitute("a", 3); + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pinned(integrand.ToEntity()); + foreach (var at in new[] { -1.1, -0.4, 0.3, 1.2 }) + { + var want = original.Substitute("x", at).EvalNumerical(); + var got = derivative.Substitute("x", at).EvalNumerical(); + var scale = Math.Max(1, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart)); + Assert.True(Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart) < 1e-9 * scale, + $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}"); + } + } + } +}