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()