diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index afb191c17..ffcd098b4 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -486,6 +486,20 @@ of a linear over a linear, which is answered in a second | `"1/(sqrt(a + i*a*tan(x))*(c + d*tan(x))^(3/2))".ToEntity().Integrate("x")` | `integral(...)`; past thirty seconds on the unreleased master | 2,217 characters | | `"1/((a + i*a*tan(x))^(3/2)*(c + d*tan(x))^(5/2))".ToEntity().Integrate("x")` | `integral(...)`; past thirty seconds on the unreleased master | 3,333 characters | +### A power of a cosine over `a + a sin` is cancelled to a polynomial in the sine + +**Answers where there were none.** `cos(c + d x)^2/((e + f x) (a + a sin(c + d x)))` was left unevaluated, and so were three +more of Rubi's 4.1.10. `cos^2/(1 + sin)` is `1 - sin` wherever it is defined, so the integrand becomes a sine or cosine +over a power of a linear. That is answered with `Si` and `Ci` +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"cos(c+d*x)^2/((pe+f*x)*(a+a*sin(c+d*x)))".ToEntity().Integrate("x")` | `integral(...)` | 132 characters | +| `"cos(c+d*x)^2/((pe+f*x)^2*(a+a*sin(c+d*x)))".ToEntity().Integrate("x")` | `integral(...)` | 413 characters | +| `"cos(c+d*x)^3/((pe+f*x)*(a+a*sin(c+d*x)))".ToEntity().Integrate("x")` | `integral(...)` | 246 characters | +| `"cos(c+d*x)^3/((pe+f*x)^2*(a+a*sin(c+d*x)))".ToEntity().Integrate("x")` | `integral(...)` | 790 characters | + ### Every linear beside a block is split off, a quadratic with rational roots among them **Answers where there were none.** `sec(c + d x)/(a + b sin(c + d x)^3)` was declined, and so were diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 6a2d91d1f..6a28ff4d3 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -26713,6 +26713,77 @@ static void Add(Dictionary into, int k, ERational value) return answer.Nodes.Any(node => node == MathS.NaN) ? null : answer; } + /// + /// A power of a cosine over a power of k (1 +- sin(y)), or of a sine over one of + /// k (1 +- cos(y)), cancelled where the power above is at least twice the one below: + /// cos^2 = (1 - sin)(1 + sin), so cos(y)^m/(k (1 + sin(y)))^n is + /// cos(y)^(m - 2n) (1 - sin(y))^n/k^n. What else is beside it stays as it is. + /// + /// + /// Rubi's 4.1.10 cos(c + d x)^2/((e + f x)(a + a sin(c + d x))) was declined, and + /// (1 - sin(c + d x))/(e + f x) is answered in sine and cosine integrals. + /// https://github.com/asc-community/AngouriMath/issues/718 + /// + internal static Entity? SolveACosinePowerOverABinomialInTheSineByCancelling(Entity expr, Entity.Variable x) + { + if (!Integration.AnsweringTheQuestionAsked) + return null; + static IEnumerable<(Entity Factor, bool Below)> Factors(Entity e, bool below) => e switch + { + Mulf(var l, var r) => Factors(l, below).Concat(Factors(r, below)), + Divf(var l, var r) => Factors(l, below).Concat(Factors(r, !below)), + _ => new[] { (e, below) } + }; + var factors = Factors(expr, false).ToList(); + foreach (var (candidate, isBelow) in factors) + { + if (!isBelow) + continue; + var (binomial, n) = candidate is Powf(var b0, Number.Integer { EInteger: var p0 }) && p0.Sign > 0 && p0.CanFitInInt32() + ? (b0, p0.ToInt32Unchecked()) : (candidate, 1); + // k (1 + sigma f(y)), f a sine or a cosine. + var functions = binomial.Nodes.Where(node => node is Sinf or Cosf && node.ContainsNode(x)).Distinct().ToList(); + if (functions.Count != 1) + continue; + var function = functions[0]; + var s0 = Variable.CreateUnique(expr, "s_cs"); + if (!TreeAnalyzer.TryGetPolyLinear(binomial.Substitute(function, s0), s0, out var slope, out var k) + || slope is null || k is null || slope.ContainsNode(x) || k.ContainsNode(x) + || Functions.PartialFractions.IsZeroAsAValue(k)) + continue; + var sigma = (slope / k).InnerSimplified; + int sign; + if (Functions.PartialFractions.IsZeroAsAValue((sigma - 1).InnerSimplified)) sign = 1; + else if (Functions.PartialFractions.IsZeroAsAValue((sigma + 1).InnerSimplified)) sign = -1; + else continue; + var argument = function is Sinf(var ys) ? ys : ((Cosf)function).Argument; + Entity complement = function is Sinf ? MathS.Cos(argument) : MathS.Sin(argument); + // The complement's power above the bar. + var m = 0; + foreach (var (factor, below) in factors) + if (!below && (factor == complement ? 1 : factor is Powf(var b1, Number.Integer { EInteger: var p1 }) && b1 == complement && p1.CanFitInInt32() ? p1.ToInt32Unchecked() : 0) is var power and > 0) + m += power; + if (m < 2 * n) + continue; + // Everything but the binomial and the complement's powers, times what the cancelling leaves. + Entity rest = Number.Integer.One; + foreach (var (factor, below) in factors) + { + if (below && factor == candidate) + continue; + if (!below && (factor == complement || factor is Powf(var b2, Number.Integer) && b2 == complement)) + continue; + rest = below ? rest / factor : rest * factor; + } + var left = m - 2 * n; + Entity cancelled = (left == 0 ? Number.Integer.One : left == 1 ? complement : MathS.Pow(complement, Number.Integer.Create(left))) + * (n == 1 ? 1 - sign * function : MathS.Pow(1 - sign * function, Number.Integer.Create(n))) + / (n == 1 ? k : MathS.Pow(k, Number.Integer.Create(n))); + return Integration.ComputeAsAQuestionOfItsOwn((rest * cancelled).InnerSimplified, x, integrateByParts: true); + } + return null; + } + /// /// A rational function of a sine, or of a cosine, over a quadratic in it with a symbol /// among the coefficients: sin(x)/(a + b sin(x) + c sin(x)^2), and the same with diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index d320fdfda..8117fbac2 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -680,6 +680,8 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr) // A power of a sine, cosine or hyperbolic one over a binomial in a power of it, over the // binomial's roots, before the rules that spent the budget on `sinh(y)^4/(a + b sinh(y)^3)`. if ((answer = IndefiniteIntegralSolver.SolveAPowerOverABinomialInAPowerOfASineOverItsRoots(expr, x)) is { }) return answer; + // A power of a cosine over a power of k (1 +- sin), cancelled: `cos^2/((e + f x)(a + a sin))`. + if ((answer = IndefiniteIntegralSolver.SolveACosinePowerOverABinomialInTheSineByCancelling(expr, x)) is { }) return answer; // A function of an inverse function of c + d x beside powers of multiples of it, under // u = c + d x, before the rules that timed out on `(c e + d e x)^3 (a + b arsinh(c + d x))^2`. if ((answer = IndefiniteIntegralSolver.SolveAFunctionOfAnInverseOfALinearBesideItsPowersUnderTheLinear(expr, x)) is { }) return answer; diff --git a/Sources/Tests/UnitTests/Calculus/ACosinePowerOverABinomialInTheSineIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/ACosinePowerOverABinomialInTheSineIntegralTest.cs new file mode 100644 index 000000000..cdce0d737 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/ACosinePowerOverABinomialInTheSineIntegralTest.cs @@ -0,0 +1,57 @@ +// +// 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 of a cosine over a power of k (1 +- sin), cancelled: Rubi's 4.1.10 + /// cos(c + d x)^2/((e + f x)(a + a sin(c + d x))), which was declined. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class ACosinePowerOverABinomialInTheSineIntegralTest + { + private static void DifferentiatesBack(string integrand, params (string, double)[] pins) + { + var integral = integrand.ToEntity().Integrate("x"); + Assert.DoesNotContain("integral(", integral.Stringize()); + + var pinned = integral.Substitute("C", 0); + Entity original = integrand.ToEntity(); + foreach (var (name, value) in pins) + { + pinned = pinned.Substitute(name, value); + original = original.Substitute(name, value); + } + var derivative = pinned.Differentiate("x"); + foreach (var at in new[] { -2.3, -0.7, 0.4, 1.9 }) + { + var got = derivative.Substitute("x", at).EvalNumerical(); + var want = original.Substitute("x", at).EvalNumerical(); + var difference = Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart); + var scale = Math.Max(1.0, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart)); + Assert.True(difference / scale < 1e-8, + $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}"); + } + } + + // The linear's root kept off the sampled points. + [Theory] + [InlineData("cos(c+d*x)^2/((k+f*x)*(a+a*sin(c+d*x)))")] + [InlineData("cos(c+d*x)^3/((k+f*x)^2*(a+a*sin(c+d*x)))")] + [InlineData("sin(c+d*x)^2/((k+f*x)*(a-a*cos(c+d*x)))")] + public void IsAnsweredAndDifferentiatesBack(string integrand) + { + DifferentiatesBack(integrand, ("a", 1.3), ("c", 0.7), ("d", 1.07), ("k", 5), ("f", 0.3)); + DifferentiatesBack(integrand, ("a", -0.5), ("c", -0.25), ("d", 1.5), ("k", -6), ("f", 0.4)); + } + } +}