diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 4ccb886a4..936fddfca 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -1173,6 +1173,22 @@ it in the answer, and what the loop asks is whether the degree has fallen to not | `"F^(c*(a+b*x))*sin(d+pe*x)^3".Integrate("x")` | left unevaluated | the antiderivative | | `"F^(c*(a+b*x))*sin(d+pe*x)*cos(d+pe*x)".Integrate("x")` | left unevaluated | the antiderivative | +### A polynomial may stand beside a root of `a ± a cos(y)` + +`x^3 sqrt(a + a cos(c + d x))` was left as written. The half angle at which `a ± a cos(y)` is a +square answers the root alone, and it substitutes the angle -- so anything else that mentioned +the variable stopped it, a polynomial among them. What is left of the variable is the variable +itself: with `u` half the angle (a quarter turn further for the sine's identity), `x` is +`(2u - offset)/rate`, so the polynomial is a polynomial in `u`, and what is handed on is one +times a cosine of `u`, which the closed rules answer. Rubi's 4.2.10 +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"x^3*sqrt(a+a*cos(c+d*x))".Integrate("x")` | left unevaluated | the antiderivative, with `sgn(cos((c + d x)/2))` in front | +| `"x^3*sqrt(a-a*cos(x))".Integrate("x")` | left unevaluated | the antiderivative | +| `"x^2*sqrt(a+a*sin(x))".Integrate("x")` | left unevaluated | the antiderivative | + ### `binomial(n, k)` is a function **Addition, not silent.** The binomial coefficient is a node, `Entity.Binomialf`, spelled diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 599e36c75..9a1414b12 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -2426,7 +2426,7 @@ Entity Step(ERational power) else if (argument != thisArgument) return null; } - if (argument is null || !TreeAnalyzer.TryGetPolyLinear(argument, x, out var rate, out _) + if (argument is null || !TreeAnalyzer.TryGetPolyLinear(argument, x, out var rate, out var offsetOfArgument) || rate.ContainsNode(x) || TreeAnalyzer.IsZero(rate)) return null; if (!expr.Nodes.All(node => !node.ContainsNode(x) @@ -2494,8 +2494,19 @@ Entity Step(ERational power) return constant * MathS.Pow(square, Number.Integer.Create(twoP)); }); var inU = rewritten.Substitute(sine, sineInU).Substitute(cosine, cosineInU); + // What is left of the variable is the variable itself: the angle is `rate x + offset` + // and `u` is half of it (a quarter turn on for the sine), so `x` is + // `(2u - pi/2 - offset)/rate` there and `(2u - offset)/rate` here -- and + // `x^3 sqrt(a + a cos(c + d x))` is a polynomial in `u` times a cosine of it, which + // the closed rules answer, where the rule used to decline anything beside the root. if (inU.ContainsNode(x)) - return null; + { + var argumentInU = sineKind ? (2 * u - MathS.pi / 2).InnerSimplified : (2 * u).InnerSimplified; + var xInU = ((argumentInU - offsetOfArgument) / rate).InnerSimplified; + inU = inU.Substitute(x, xInU); + if (inU.ContainsNode(x)) + return null; + } // dx = 2 du / rate. var integrand = (inU * 2 / rate).InnerSimplified; if (Integration.ComputeAsAQuestionOfItsOwn(integrand, u, integrateByParts) is not { } result) @@ -12416,7 +12427,7 @@ bool IsAConstantTimesAPowerOfOneMinusUSquared(Entity polynomial, out Entity cons else if (argument != thisArgument) return null; } - if (argument is null || !TreeAnalyzer.TryGetPolyLinear(argument, x, out var rate, out _) + if (argument is null || !TreeAnalyzer.TryGetPolyLinear(argument, x, out var rate, out var offsetOfArgument) || rate.ContainsNode(x) || TreeAnalyzer.IsZero(rate)) return null; if (!expr.Nodes.All(node => !node.ContainsNode(x) @@ -14846,7 +14857,7 @@ bool IsAPolynomialInBoth(Entity polynomial) else if (argument != thisArgument) return null; } - if (argument is null || !TreeAnalyzer.TryGetPolyLinear(argument, x, out var rate, out _) + if (argument is null || !TreeAnalyzer.TryGetPolyLinear(argument, x, out var rate, out var offsetOfArgument) || rate.ContainsNode(x) || TreeAnalyzer.IsZero(rate)) return null; var sine = MathS.Sin(argument); diff --git a/Sources/Tests/UnitTests/Calculus/HalfAngleSquareTest.cs b/Sources/Tests/UnitTests/Calculus/HalfAngleSquareTest.cs index f77dfe4fc..07af530cd 100644 --- a/Sources/Tests/UnitTests/Calculus/HalfAngleSquareTest.cs +++ b/Sources/Tests/UnitTests/Calculus/HalfAngleSquareTest.cs @@ -151,6 +151,26 @@ public void ThePairOfPowersTwoApartIsElementary(string integrand) Assert.True(compared >= 4, $"only {compared} of five points were comparable for {integrand}"); } + /// + /// A polynomial beside the root: the rule substitutes the half angle, and what is left + /// of the variable is the variable itself -- x is (2u - offset)/rate for + /// the cosine and a quarter turn further for the sine -- so + /// x^3 sqrt(a + a cos(c + d x)) is a polynomial in u times a cosine of it, + /// which the closed rules answer. It used to decline anything standing beside the root. + /// Rubi's 4.2.1. #718 + /// + [Theory] + [InlineData("x^3*sqrt(a + a*cos(c + d*x))")] + [InlineData("x*sqrt(a + a*cos(x))")] + [InlineData("x^3*sqrt(a - a*cos(x))")] + [InlineData("x^2*sqrt(a + a*sin(x))")] + [InlineData("x*(a + a*cos(x))^(3/2)")] + public void APolynomialMayStandBesideTheRoot(string integrand) + { + DifferentiatesBack(integrand, 2); + DifferentiatesBack(integrand, -3); + } + /// A combination that does not kill the residual is not this rule's, and is left alone. [Fact] public void AnotherCoefficientIsLeftAsWritten()