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))")]