diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md
index 7d8b0e0ac..b51a69e46 100644
--- a/BREAKING-CHANGES.md
+++ b/BREAKING-CHANGES.md
@@ -4236,8 +4236,8 @@ where `cos(y)` is positive; beyond it each turns its sign by its constant's. Two
integrand real on both sides at once, for a positive product of their constants, and then the turns
are as many as the roots of the cosine: an answer through two holds where the cosine is negative too,
with `sgn(cos(y))` in front for an odd number of roots of the cosine, and says
-`provided a > 0 or cos(y) >= 0` where the product's sign is not known. Three say
-`provided cos(y) >= 0` ([#718](https://github.com/asc-community/AngouriMath/issues/718)).
+`provided a > 0 or cos(y) >= 0` where the product's sign is not known. Three are in the entry
+after this one ([#718](https://github.com/asc-community/AngouriMath/issues/718)).
| Input | Was (2.5.0) | Now |
|---|---|---|
@@ -4249,6 +4249,26 @@ with `sgn(cos(y))` in front for an odd number of roots of the cosine, and says
| `"cos(x)^(3/2)/sqrt(1 + sec(x))".ToEntity().Integrate("x")` | `integral(...)` | a logarithm and a power in `tan(x/2)`, times `sgn(cos(x))` |
| `"cos(c+d*x)^(3/2)*(A+C*sec(c+d*x)^2)/sqrt(a+a*sec(c+d*x))".ToEntity().Integrate("x")` | `integral(...)` | the same, times `sgn(cos(c + d x))`, `provided a > 0 or cos(c + d x) >= 0` |
+### An odd number of roots of `a ± a sec` and the secant or cosine holds where the cosine is negative
+
+**Answers where there were none.** Three half-odd powers, as in
+`sqrt(a + a sec(x)) sqrt(sec(x)) sqrt(cos(x))`, leave a root of `1 - tan(x/2)^2` in the answer, and
+the answer said `provided cos(x) >= 0`. An odd number of roots makes the integrand real on one side
+of the cosine's sign change at most, and on the negative one just where the product of the roots'
+constants is negative, so `sqrt(-1 - sec(x)) sqrt(sec(x)) sqrt(cos(x))` had no value anywhere it is
+real. There each root turns its sign by its constant's, as two do, and the turns are as many as the
+roots of the cosine. The answer is kept beyond the condition where the antiderivative in the
+half-angle tangent differentiates back there, and says `provided a < 0 or cos(y) >= 0` where the
+product's sign is not known. The cosecant's the same way through the complement
+([#718](https://github.com/asc-community/AngouriMath/issues/718)).
+
+| Input | Was (2.5.0) | Now |
+|---|---|---|
+| `"sqrt(-1 - sec(x))*sqrt(sec(x))*sqrt(cos(x))".ToEntity().Integrate("x")` | `integral(...)` | an arctangent in `tan(x/2)`, times `sgn(cos(x))` |
+| `"sqrt(1 - sec(x))*sqrt(sec(x))*sqrt(cos(x))".ToEntity().Integrate("x")` | `integral(...)` | a logarithm in `tan(x/2)`, times `sgn(cos(x)) sgn(tan(x/2))` |
+| `"sqrt(a + a*sec(x))*sqrt(sec(x))*sqrt(cos(x))".ToEntity().Integrate("x")` | `integral(...)` | an arctangent in `tan(x/2)`, times `sgn(cos(x))`, `provided a < 0 or cos(x) >= 0` |
+| `"sqrt(a + a*csc(x))*sqrt(csc(x))*sqrt(sin(x))".ToEntity().Integrate("x")` | `integral(...)` | an arctangent in `tan(pi/4 - x/2)`, times `sgn(sin(x))`, `provided a < 0 or sin(x) >= 0` |
+
### An even power of the secant or the cosecant under a root is written in the tangent
**Answers where there were none.** `sqrt(a + b csc(x)^2)`, `(a + b sec(x)^2)^(3/2)` and their kin,
diff --git a/Sources/AngouriMath/Functions/Algebra/Polynomials/PartialFractions.cs b/Sources/AngouriMath/Functions/Algebra/Polynomials/PartialFractions.cs
index c0877cbc6..e8ad086d8 100644
--- a/Sources/AngouriMath/Functions/Algebra/Polynomials/PartialFractions.cs
+++ b/Sources/AngouriMath/Functions/Algebra/Polynomials/PartialFractions.cs
@@ -2030,9 +2030,10 @@ internal static Entity Bare(Entity e)
/// f is shown real-valued, which a symbol that might be complex prevents and a
/// number does not: an answer with sgn(tanh(c + d x)) in it, right, was
/// unevaluable differentiated with c and d in it and threw out of the
- /// integrator.
+ /// integrator. The points may be the caller's, as
+ /// takes them.
///
- internal static bool DerivativeHoldsAtSampledPoints(Entity antiderivative, Entity integrand, Variable x)
+ internal static bool DerivativeHoldsAtSampledPoints(Entity antiderivative, Entity integrand, Variable x, string[]? points = null)
{
// Off the downcasting for the simplification of the pinned answer, which folds its
// numbers: downcast, a pinned 1.37 is 137/100 and every constant of the answer is
@@ -2040,7 +2041,7 @@ internal static bool DerivativeHoldsAtSampledPoints(Entity antiderivative, Entit
// where in decimals it is 3 ms. The comparison itself reads no setting.
using var _ = MathS.Settings.DowncastingEnabled.Set(false);
var (pinnedAntiderivative, pinnedIntegrand) = Pinned(antiderivative, integrand, x);
- return HoldsAtSampledPoints(Bare(pinnedAntiderivative).Differentiate(x), pinnedIntegrand, x);
+ return HoldsAtSampledPoints(Bare(pinnedAntiderivative).Differentiate(x), pinnedIntegrand, x, points);
}
///
diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
index f289ef6fc..1eaad30e8 100644
--- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
@@ -5687,7 +5687,11 @@ bool IsAPolynomialInU(Entity part)
/// cosine is negative too, and there each written apart may have turned its sign: the answer
/// said provided cos(y) >= 0, half of the integrand's domain, for every one of 165
/// rows of Rubi's 4.5.4.2. Which turn is known from the constants, and the answer carries
- /// the cosine's sign where they leave an odd number turned.
+ /// the cosine's sign where they leave an odd number turned. Three, as in
+ /// sqrt(a + a sec(y)) sqrt(sec(y)) sqrt(cos(y)), leave a root of 1 - t^2 in
+ /// the answer in t, which said provided cos(y) >= 0 where for a negative a the
+ /// integrand is real only beyond it; the answer in t is kept beyond where it differentiates
+ /// back there.
/// https://github.com/asc-community/AngouriMath/issues/233
///
///
@@ -5841,19 +5845,24 @@ bool IsAPolynomialInU(Entity part)
// differing by e^(2 i pi p) = -1: where cos(y) < 0 a root of a ± a sec(y) or d sec(y)
// turns its sign if its constant is positive, and one of d cos(y) if its constant is
// negative. One root alone turns only where the integrand is complex, which is nothing
- // to be wrong about. For an even number the turns multiply to (-1)^n s, for n roots of
- // the cosine and s the sign of the product of the constants, and the integrand is real,
- // on both sides at once, just where s is positive: `sqrt(sec(x)) sqrt(1 + sec(x))` holds
- // as written where the cosine is negative, and `cos(x)^(3/2)/sqrt(1 + sec(x))` with the
- // cosine's sign in front. The root of 1 - t^2 has cancelled then, so that the answer in
- // t holds beyond t^2 < 1 too; for an odd number above one it has not, and the answer
- // says where it holds, as it does where the product's sign is not known.
+ // to be wrong about. A root is imaginary there just where its constant is positive, so
+ // for N roots, n of them of the cosine, and s the sign of the product of the constants,
+ // the turns multiply to (-1)^(N + n) s, and the integrand is real there just where
+ // (-1)^N s is positive, and then the turns are (-1)^n: `sqrt(sec(x)) sqrt(1 + sec(x))`
+ // holds as written where the cosine is negative, and `cos(x)^(3/2)/sqrt(1 + sec(x))` with
+ // the cosine's sign in front. For an even N the root of 1 - t^2 has cancelled, so that
+ // the answer in t holds beyond t^2 < 1 too. For an odd one it has not, and the answer in
+ // t is kept beyond only where it differentiates back there. It said
+ // `provided cos(y) >= 0`, and `sqrt(a + a sec(x)) sqrt(sec(x)) sqrt(cos(x))` is real, for
+ // a negative a, only beyond that.
if (roots < 2)
return answer;
var withinTheHalfTurn = answer.Provided(companion >= Number.Integer.Zero);
- if (!exponents[0].Numerator.Remainder(exponents[0].Denominator).IsZero)
+ var odd = roots % 2 == 1;
+ if (odd == exponents[0].Numerator.Remainder(exponents[0].Denominator).IsZero
+ || odd && !Functions.PartialFractions.DerivativeHoldsAtSampledPoints(result, integrand, t, BeyondTheHalfTurn))
return withinTheHalfTurn;
- var turns = rootsOfTheCompanion % 2 == 1;
+ var turns = (roots + rootsOfTheCompanion) % 2 == 1;
switch (constants.Evaled)
{
case Number.Real { IsFinite: true } product when !product.IsZero:
@@ -5863,11 +5872,20 @@ bool IsAPolynomialInU(Entity part)
case Number:
return withinTheHalfTurn;
default:
- return (turns ? MathS.Signum(companion) * answer : answer)
- .Provided(constants > Number.Integer.Zero | companion >= Number.Integer.Zero);
+ // Where s is not known, the answer for the s that makes the integrand real.
+ var real = odd ? constants < Number.Integer.Zero : constants > Number.Integer.Zero;
+ return (rootsOfTheCompanion % 2 == 1 ? MathS.Signum(companion) * answer : answer)
+ .Provided(real | companion >= Number.Integer.Zero);
}
}
+ ///
+ /// Values of the half-angle tangent past one on either side, where the cosine is negative,
+ /// or for the cosecant the sine.
+ ///
+ [ConstantField]
+ private static readonly string[] BeyondTheHalfTurn = { "1.19", "1.43", "3.17", "7.3", "-1.61", "-2.9", "-6.1" };
+
///
/// A half-odd power of a ± a sec(y) beside one of c + d sec(y), integrated in
/// the half angle's sine, or its cosine for the minus: a + a sec(y) is
diff --git a/Sources/Tests/UnitTests/Calculus/OnePlusASecantHalfPowerIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/OnePlusASecantHalfPowerIntegralTest.cs
index 54396cbdb..165408a75 100644
--- a/Sources/Tests/UnitTests/Calculus/OnePlusASecantHalfPowerIntegralTest.cs
+++ b/Sources/Tests/UnitTests/Calculus/OnePlusASecantHalfPowerIntegralTest.cs
@@ -178,17 +178,64 @@ public void AProductOfConstantsOfUnknownSignSaysWhereItHolds()
}
///
- /// Three roots leave one of 1 - t^2 in the answer in t, which is not shown to hold
- /// beyond t^2 < 1, and the answer says so.
+ /// Three roots leave one of 1 - t^2 in the answer in t, which is kept beyond
+ /// t^2 < 1 where it differentiates back there. An odd number of roots makes the
+ /// integrand real on one side of the cosine's sign change at most, the negative one for a
+ /// negative product of the constants, and there the answer holds as written or with the
+ /// cosine's sign in front. It said provided cos(x) >= 0, and
+ /// sqrt(-1 - sec(x)) sqrt(sec(x)) sqrt(cos(x)), real only where the cosine is
+ /// negative, had no value anywhere it is real.
///
- [Fact]
- public void ThreeRootsSayWhereTheyHold()
+ [Theory]
+ [InlineData("sqrt(cos(x))*sqrt(sec(x))*sqrt(1 + sec(x))", 3)]
+ [InlineData("(1 + sec(x))^(3/2)*sqrt(2*sec(x))*sqrt(3*cos(x))", 3)]
+ [InlineData("sqrt(-1 - sec(x))*sqrt(sec(x))*sqrt(cos(x))", 6)]
+ [InlineData("sqrt(1 - sec(x))*sqrt(sec(x))*sqrt(cos(x))", 6)]
+ [InlineData("sqrt(sin(x))*sqrt(csc(x))*sqrt(1 + csc(x))", 4)]
+ [InlineData("sqrt(-1 - csc(x))*sqrt(csc(x))*sqrt(sin(x))", 5)]
+ public void ThreeRootsHoldWhereTheIntegrandIsReal(string integrand, int compare)
+ {
+ Assert.DoesNotContain("provided", integrand.ToEntity().Integrate("x").Stringize());
+ HoldsWhereverTheIntegrandIsReal(integrand, BothSides, compare, Pins);
+ }
+
+ ///
+ /// Three roots whose product of constants is of unknown sign: the integrand is real where
+ /// the cosine is negative for a negative product, and the answer holds there, and where it
+ /// is positive for a positive one; the answer says provided a < 0 or cos(x) >= 0, or
+ /// -a < 0 for a - a sec(x), the integrand complex beyond it. Each at both
+ /// signs of the constant, and the cosecant's through the complement.
+ ///
+ [Theory]
+ [InlineData("sqrt(a + a*sec(x))*sqrt(sec(x))*sqrt(cos(x))", 1.7, 3)]
+ [InlineData("sqrt(a + a*sec(x))*sqrt(sec(x))*sqrt(cos(x))", -1.7, 6)]
+ [InlineData("sqrt(a + a*sec(x))*sec(x)^(3/2)*sqrt(cos(x))*(A + B*sec(x))", 1.7, 3)]
+ [InlineData("sqrt(a + a*sec(x))*sec(x)^(3/2)*sqrt(cos(x))*(A + B*sec(x))", -1.7, 6)]
+ [InlineData("sqrt(a - a*sec(x))*sqrt(sec(x))*sqrt(cos(x))*cos(x)", 1.7, 6)]
+ [InlineData("sqrt(a - a*sec(x))*sqrt(sec(x))*sqrt(cos(x))*cos(x)", -1.7, 3)]
+ [InlineData("sqrt(a + a*csc(x))*sqrt(csc(x))*sqrt(sin(x))", 1.7, 4)]
+ [InlineData("sqrt(a + a*csc(x))*sqrt(csc(x))*sqrt(sin(x))", -1.7, 5)]
+ public void ThreeRootsOfAConstantOfUnknownSignHoldWhereTheIntegrandIsReal(string integrand, double a, int compare)
{
- const string integrand = "sqrt(cos(x))*sqrt(sec(x))*sqrt(1 + sec(x))";
- Assert.Contains("provided cos(x) >= 0", integrand.ToEntity().Integrate("x").Stringize());
- DifferentiatesBackPinned(integrand, new[] { 0.3, 0.7, 1.1, 1.4 });
+ Assert.Contains(" < 0 or ", integrand.ToEntity().Integrate("x").Stringize());
+ HoldsWhereverTheIntegrandIsReal(integrand, BothSides, compare, new[] { ("a", a), ("A", 0.6), ("B", 1.3) });
}
+ ///
+ /// The same at y = 0.9 + 1.3 x, where the points fall four where the cosine is
+ /// positive and five where it is negative, and with three binomials, of which the
+ /// integrand is real where the cosine is negative for a c k > 0 and where it is
+ /// positive for a c k < 0.
+ ///
+ [Theory]
+ [InlineData("sqrt(a + a*sec(c + d*x))*sqrt(sec(c + d*x))*sqrt(cos(c + d*x))", 1.7, 0.9, 4)]
+ [InlineData("sqrt(a + a*sec(c + d*x))*sqrt(sec(c + d*x))*sqrt(cos(c + d*x))", -1.7, 0.9, 5)]
+ [InlineData("sqrt(a + a*sec(x))*sqrt(c + c*sec(x))*sqrt(k - k*sec(x))", 1.7, 0.9, 6)]
+ [InlineData("sqrt(a + a*sec(x))*sqrt(c + c*sec(x))*sqrt(k - k*sec(x))", -1.7, 0.9, 3)]
+ public void ThreeRootsOfSymbolsHoldWhereTheIntegrandIsReal(string integrand, double a, double c, int compare)
+ => HoldsWhereverTheIntegrandIsReal(integrand, BothSides, compare,
+ new[] { ("a", a), ("c", c), ("d", 1.3), ("k", 1.3) });
+
/// The cosecant's, through the complement: csc(y) = sec(pi/2 - y).
[Theory]
[InlineData("sqrt(1 + csc(x))")]