diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index c43b8d83e..bb61a59b6 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -192,6 +192,18 @@ quotient of two sums with a root in each, which the search never brought down to | `"atanh(x*sqrt(k)/sqrt(d+k*x^2))".ToEntity().Integrate("x")` | `integral(...)` | `x atanh(...) - sqrt(d + k x^2)/sqrt(k)`, written in the logarithm | | `"atanh(x*sqrt(k)/sqrt(d+k*x^2))/x^5".ToEntity().Integrate("x")` | `integral(...)` | by parts onto `1/(x^4 sqrt(d + k x^2))` | +### A sine over x and a power of a binomial has the power lowered first + +**Answers where there were none.** Rubi's 4.1.11 `sin(c + d x)/(x (a + b x^3)^3)` was declined. The split over x and the +cube's three roots, each to the third power, came out past the size it hands on. Since `a + b x^3 - b x^3` is `a`, +`1/(x Q^k)` is `1/(a x Q^(k - 1)) - (b/a) x^2/Q^k`, and each of the two is answered as it stands +([#1501](https://github.com/asc-community/AngouriMath/issues/1501)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"sin(c+d*x)/(x*(a+b*x^3)^3)".ToEntity().Integrate("x")` | `integral(...)` | 13,110 characters | +| `"cos(c+d*x)/(x*(a+b*x^3)^3)".ToEntity().Integrate("x")` | `integral(...)` | 13,132 characters | + ### A power of x times the arsech or arcsch of `sqrt(x)` is integrated by parts **Answers where there were none.** Every power from `x^0` to `x^3` times `asech(sqrt(x))` or `acsch(sqrt(x))`, Rubi's diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 9041fb2f7..284b36a5a 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -11150,10 +11150,53 @@ void Add(int k, bool isSine, Entity coefficient) if (expr is not (Divf or Mulf or Powf or Sinf or Cosf) || ASineOrCosineArgument(expr, x) is not { } argument) return null; if (TreeAnalyzer.TryGetPolyLinear(argument, x, out var rate, out _) && !TreeAnalyzer.IsZero(rate)) - return OverAPowerOfALinear(expr, x, argument); + return OverAPowerOfALinear(expr, x, argument) ?? ByLoweringThePowerOfABinomialBesideX(expr, x, argument); return UnderAPowerOfTheVariable(expr, x, argument); } + /// + /// T/(x (a + b x^n)^k), for T a polynomial in the sine and cosine of a linear, n three or + /// four and k two or more, as T/(a x (a + b x^n)^(k - 1)) - (b/a) x^(n - 1) T/(a + b x^n)^k, + /// since a + b x^n - b x^n is a: Rubi's 4.1.11 sin(c + d x)/(x (a + b x^3)^3) was + /// declined, the split over x and the binomial's three roots, each to the cube, past the size it + /// hands on, where each of the two is a question answered at once. Null where either is not. + /// + private static Entity? ByLoweringThePowerOfABinomialBesideX(Entity expr, Entity.Variable x, Entity argument) + { + if (!TryReadAsQuotient(expr, out var numerator, out var denominator)) + return null; + // x in the numerator only in the sine and cosine of the argument. + var (sine, cosine) = (MathS.Sin(argument), MathS.Cos(argument)); + if (numerator.Replace(node => node == sine || node == cosine ? Number.Integer.One : node).ContainsNode(x)) + return null; + Entity constant = Number.Integer.One; + Entity? binomial = null; + var power = 0; + var xOnce = false; + foreach (var factor in Mulf.LinearChildren(denominator)) + { + if (!factor.ContainsNode(x)) + constant *= factor; + else if (factor == x && !xOnce) + xOnce = true; + else if (binomial is null && factor is Powf(var @base, Number.Integer { EInteger: var k }) && k.CompareTo(EInteger.FromInt32(2)) >= 0 + && k.CanFitInInt32() && TheRootsOfABinomial(@base, x) is not null) + (binomial, power) = (@base, k.ToInt32Unchecked()); + else + return null; + } + if (!xOnce || binomial is null || !TreeAnalyzer.TryGetPolynomial(binomial, x, out var terms)) + return null; + var free = terms[EInteger.Zero]; + var top = terms.First(term => !term.Key.IsZero); + var lower = numerator / (constant * free * x * (power == 2 ? binomial : MathS.Pow(binomial, power - 1))); + var beside = -(top.Value / free) * MathS.Pow(x, Number.Integer.Create(top.Key - 1)) * numerator / (constant * MathS.Pow(binomial, power)); + if (Integration.ComputeAsAQuestionOfItsOwn(lower, x, integrateByParts: true) is not { } first + || Integration.ComputeAsAQuestionOfItsOwn(beside, x, integrateByParts: true) is not { } second) + return null; + return first + second; + } + // x - r, written x + |r| where the root is a negative number: x + 1 for the root -1. private static Entity LessTheRoot(Entity root, Entity.Variable x) => root is Number.Complex { RealPart.IsNegative: true } or Number.Complex { RealPart.EDecimal.IsZero: true, ImaginaryPart.IsNegative: true } diff --git a/Sources/Tests/UnitTests/Calculus/APowerOfABinomialBesideXBelowASineIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/APowerOfABinomialBesideXBelowASineIntegralTest.cs new file mode 100644 index 000000000..a1785d25e --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/APowerOfABinomialBesideXBelowASineIntegralTest.cs @@ -0,0 +1,53 @@ +// +// 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 System.Threading; +using System.Threading.Tasks; +using AngouriMath.Extensions; +using Xunit; + +namespace AngouriMath.Tests.Calculus +{ + /// + /// A sine or cosine of a linear over x times a power of a binomial, with the power lowered by + /// 1/(x Q^k) = 1/(a x Q^(k - 1)) - (b/a) x^(n - 1)/Q^k for Q = a + b x^n: Rubi's + /// 4.1.11 sin(c + d x)/(x (a + b x^3)^3) was declined, the split over x and the cube's three + /// roots, each to the third power, past the size it hands on, where each of the two is answered. + /// #1501 + /// + [Trait("Area", "Calculus")] + public sealed class APowerOfABinomialBesideXBelowASineIntegralTest + { + [Theory] + // Rubi 4.1.11:136. + [InlineData("sin(c + d*x)/(x*(a + b*x^3)^3)")] + [InlineData("cos(c + d*x)/(x*(a + b*x^3)^3)")] + public void IsAnsweredWithinTheGuard(string integrand) + { + Entity? integral = null; + // A thread of its own, so that the guard is not spent waiting behind other tests' work. + var task = Task.Factory.StartNew(() => integral = integrand.ToEntity().Integrate("x"), + CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default); + Assert.True(task.Wait(LimitTermination.Guard), $"{integrand} did not finish within {LimitTermination.Guard}"); + var text = integral!.Stringize(); + Assert.DoesNotContain("integral(", text); + Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.7).Substitute("c", 0.4).Substitute("d", 0.9); + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pinned(integrand.ToEntity()); + foreach (var at in new[] { -2.2, -0.9, 0.3, 1.4, 2.1 }) + { + var want = original.Substitute("x", at).EvalNumerical(); + var got = derivative.Substitute("x", at).EvalNumerical(); + Assert.False(got.IsNaN, $"d/dx of the antiderivative of {integrand} has no value at x = {at}, where the integrand is {want}"); + 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}"); + } + } + } +}