diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 1880f261b..33e0d7dfa 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -249,6 +249,24 @@ roots, `1 - x^2`, is taken as its two ([#718](https://github.com/asc-community/A | `"1/((1 - x^2)*(a + b*x^3))".ToEntity().Integrate("x")` | `integral(...)` | 1,600 characters | | `"1/(x*(1 - x^2)*(a + b*x^3))".ToEntity().Integrate("x")` | `integral(...)` | 1,637 characters | +### A sine's binomial cube below the bar is split over the cube's two factors + +**Answers where there were none.** `csc(c + d x)/(a + b sin(c + d x)^3)` was declined, and so were the second to fifth +powers of the cosecant and the second and fourth powers of the secant over the same block (Rubi's 4.1.7). With `a` +and `b` symbols the block has no rational factor, so the split over a symbolic quadratic in the sine had nothing to +split. It is written `b (s + r)(s^2 - r s + r^2)` now, with `r = (a/b)^(1/3)`, which that split answers +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"csc(c + d*x)/(a + b*sin(c + d*x)^3)".ToEntity().Integrate("x")` | `integral(...)` | 5,058 characters | +| `"csc(c + d*x)^2/(a + b*sin(c + d*x)^3)".ToEntity().Integrate("x")` | `integral(...)` | 4,866 characters | +| `"csc(c + d*x)^3/(a + b*sin(c + d*x)^3)".ToEntity().Integrate("x")` | `integral(...)` | 5,382 characters | +| `"csc(c + d*x)^4/(a + b*sin(c + d*x)^3)".ToEntity().Integrate("x")` | `integral(...)` | 5,426 characters | +| `"csc(c + d*x)^5/(a + b*sin(c + d*x)^3)".ToEntity().Integrate("x")` | `integral(...)` | 5,958 characters | +| `"sec(c + d*x)^2/(a + b*sin(c + d*x)^3)".ToEntity().Integrate("x")` | `integral(...)` | 7,010 characters | +| `"sec(c + d*x)^4/(a + b*sin(c + d*x)^3)".ToEntity().Integrate("x")` | `integral(...)` | 11,646 characters | + ### A symbol shared by a numerator sum's coefficients is taken out before the partial fractions **Answers where there were none.** `(b d + 2c d x)^9/(a + b x + c x^2)^3` ran past a minute, where diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index ff16fe0f5..5df2d1209 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -23744,10 +23744,59 @@ static bool IsASumOfPowersOfX(Entity sum, Entity.Variable x) return null; if (OverASymbolicQuadraticInTheFunction(inS, s, w, function == sine, argument, rate, x, integrateByParts) is { } answer) return answer; + if (WithASymbolicBinomialCubeFactored(inS, s, w) is { } cube + && OverASymbolicQuadraticInTheFunction(cube.Factored, s, w, function == sine, argument, rate, x, integrateByParts) is { } inR) + return inR.Substitute(cube.R, cube.Root); } return null; } + /// + /// with one factor a + b s^3 below the bar, a symbol in it, + /// written b (s + r)(s^2 - r s + r^2) for a fresh r whose cube is a/b: + /// a linear and a quadratic with a symbol in it, which the rule above splits over. Null + /// where there is no such factor or more than one. + /// + /// + /// Rubi's 4.1.7: csc(x)/(a + b sin(x)^3) and its even powers of the cosecant and + /// the secant were declined. Under the half-angle the cube is a sextic in t with + /// symbols in every coefficient. The identity holds for any cube root of a/b. + /// https://github.com/asc-community/AngouriMath/issues/718 + /// + private static (Entity Factored, Variable R, Entity Root)? WithASymbolicBinomialCubeFactored(Entity inS, Variable s, Variable w) + { + var combined = Functions.SingleQuotient.Combine(inS).InnerSimplified; + if (combined is Providedf(var bare, _)) + combined = bare; + var (numerator, denominator) = Functions.SingleQuotient.Of(combined); + Entity? cube = null; + Entity rest = Number.Integer.One; + Entity free = Number.Integer.Zero, leading = Number.Integer.Zero; + foreach (var factor in Mulf.LinearChildren(denominator)) + { + if (factor is not (Sumf or Minusf) || factor.ContainsNode(w) + || !TreeAnalyzer.TryGetPolynomial(factor, s, out var read) + || read.Count != 2 || !read.ContainsKey(EInteger.Zero) || !read.ContainsKey(EInteger.FromInt32(3)) + || read.Values.Any(coefficient => coefficient.ContainsNode(s)) + || !factor.Vars.Any(v => v != s)) + { + rest = rest == Number.Integer.One ? factor : rest * factor; + continue; + } + if (cube is not null) + return null; + cube = factor; + free = read[EInteger.Zero]; + leading = read[EInteger.FromInt32(3)]; + } + if (cube is null || Functions.PartialFractions.IsZeroAsAValue(free) || Functions.PartialFractions.IsZeroAsAValue(leading)) + return null; + var r = Variable.CreateUnique(inS, "r_cube"); + var factored = leading * (s + r) * (MathS.Pow(s, 2) - r * s + MathS.Pow(r, 2)); + var below = rest == Number.Integer.One ? factored : rest * factored; + return (numerator / below, r, MathS.Pow(free / leading, Number.Rational.Create(1, 3))); + } + /// /// The rule above in , for the function that /// stands for and its complement: null where the integrand is diff --git a/Sources/Tests/UnitTests/Calculus/ABinomialCubeInTheSineIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/ABinomialCubeInTheSineIntegralTest.cs new file mode 100644 index 000000000..c6bfe2dd6 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/ABinomialCubeInTheSineIntegralTest.cs @@ -0,0 +1,44 @@ +// +// 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 rational function of the sine over a + b sin^3, the cube written + /// b (s + r)(s^2 - r s + r^2) with r^3 = a/b and split over its factors. Rubi's 4.1.7. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class ABinomialCubeInTheSineIntegralTest + { + [Theory] + [InlineData("csc(c + d*x)/(a + b*sin(c + d*x)^3)")] + [InlineData("csc(c + d*x)^2/(a + b*sin(c + d*x)^3)")] + [InlineData("csc(c + d*x)^3/(a + b*sin(c + d*x)^3)")] + [InlineData("sec(c + d*x)^2/(a + b*sin(c + d*x)^3)")] + public void DifferentiatesBack(string integrand) + { + var integral = integrand.ToEntity().Integrate("x"); + Assert.DoesNotContain("integral(", integral.Stringize()); + Entity Pinned(Entity e) => e.Substitute("a", 2).Substitute("b", 3).Substitute("c", "1/4".ToEntity()).Substitute("d", "3/2".ToEntity()); + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pinned(integrand.ToEntity()); + foreach (var at in new[] { "0.35", "0.6", "1.1", "1.4" }) + { + var want = original.Substitute("x", at.ToEntity()).EvalNumerical(); + var got = derivative.Substitute("x", at.ToEntity()).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)), + $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}"); + } + } + } +}