From 095cad7ea94b6ddf791a2cc37490a024e1df7e51 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Mon, 12 Oct 2026 17:08:41 +0000 Subject: [PATCH 1/2] Conjugate secant sums whose powers add to a negative whole number are integrated sec(y) (a + a sec(y))^m (c - c sec(y))^n with m + n = -k: the integrand over u' times a polynomial in u = a + a sec(y) and its conjugate, for any m. Rubi's 4.5.2.3 #189-#191. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- .../Integration/IndefiniteIntegralSolver.cs | 41 +++++++++++++- ...tSumsWhosePowersAddToANegativeWholeTest.cs | 55 +++++++++++++++++++ 2 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 Sources/Tests/UnitTests/Calculus/ConjugateSecantSumsWhosePowersAddToANegativeWholeTest.cs diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 0a1498898..1256ace00 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -4951,7 +4951,7 @@ static bool IsHalfOdd(Entity exponent) => exponent is Number.Rational rational a var order = sums[0].Exponent is Number.Rational { ERational.Sign: > 0 } && IsHalfOdd(sums[0].Exponent) ? new[] { 0, 1 } : new[] { 1, 0 }; var (half, other) = (sums[order[0]], sums[order[1]]); if (!IsHalfOdd(half.Exponent)) - return null; + return WherePowersOfConjugateSumsAddToANegativeWhole(expr, x, function, sums[0], sums[1]); var n = ((Number.Rational)half.Exponent).ERational; // With u the other sum, the half-odd one is c (2 - u/a), whatever the signs. var u = Variable.CreateUnique(expr, "u_conjugate"); @@ -4974,6 +4974,45 @@ static bool IsHalfOdd(Entity exponent) => exponent is Number.Rational rational a return expr * inTermsOfU.Substitute(u, other.Radicand) / inX; } + /// + /// sec(y) (a ± a sec(y))^m (c ∓ c sec(y))^n with m + n a negative whole + /// number -k and neither half-odd: with u = a ± a sec(y), the antiderivative is + /// the integrand over u' times the polynomial + /// (2a)^(-k) Σ_j C(k - 1, j) u^(j + 1) (2a - u)^(k - j)/(m + j + 1/2), for any m. + /// + /// + /// Rubi's sec(e + f x) (a + a sec(e + f x))^m (c - c sec(e + f x))^(-1 - m) and its + /// -2 - m and -3 - m, 4.5.2.3, were declined. In u the integrand is + /// u^(m - 1/2) (2 - u/a)^(n - 1/2) up to a constant, which is v^(m - 1/2) (1 + v)^(k - 1) + /// in v = u/(2a - u), a finite sum; written back, the powers of u and + /// 2a - u the integrand has divide out and leave the polynomial. For k = 1 that is + /// -tan(y) (a + a sec(y))^m (c - c sec(y))^(-1 - m)/(2m + 1). Kept only where it + /// differentiates back to the integrand at the sampled points. + /// https://github.com/asc-community/AngouriMath/issues/718 + /// + private static Entity? WherePowersOfConjugateSumsAddToANegativeWhole(Entity expr, Entity.Variable x, Entity function, + (Entity Radicand, Entity Exponent, Entity A, bool Plus) first, (Entity Radicand, Entity Exponent, Entity A, bool Plus) second) + { + // Simplified, not InnerSimplified, which leaves `m + -1 - m` as it is. + if ((first.Exponent + second.Exponent).Simplify() is not Number.Integer { EInteger.Sign: < 0 } total + || total.EInteger.CompareTo(EInteger.FromInt32(-12)) < 0) + return null; + var k = -total.EInteger.ToInt32Checked(); + var (u, a) = (first.Radicand, first.A); + // `2a - u`, written as the conjugate sum: `a - a sec(y)` for `u = a + a sec(y)`. + var conjugate = first.Plus ? a - a * function : a + a * function; + Entity polynomial = Number.Integer.Zero; + var binomial = EInteger.One; + for (var j = 0; j < k; j++) + { + polynomial += Number.Integer.Create(binomial) * MathS.Pow(u, j + 1) * MathS.Pow(conjugate, k - j) + / (first.Exponent + j + Number.Rational.Create(1, 2)).Simplify(); + binomial = binomial.Multiply(EInteger.FromInt32(k - 1 - j)).Divide(EInteger.FromInt32(j + 1)); + } + var antiderivative = expr / u.Differentiate(x) * (MathS.Pow(2 * a, total) * polynomial).InnerSimplified; + return Functions.PartialFractions.HoldsAtSampledPoints(antiderivative.Differentiate(x), expr, x) ? antiderivative : null; + } + /// /// An odd whole power of tan(y) beside a function of sec(y), integrated in /// u = sec(y): du = sec(y) tan(y) dy and the even power of the tangent left over diff --git a/Sources/Tests/UnitTests/Calculus/ConjugateSecantSumsWhosePowersAddToANegativeWholeTest.cs b/Sources/Tests/UnitTests/Calculus/ConjugateSecantSumsWhosePowersAddToANegativeWholeTest.cs new file mode 100644 index 000000000..10ca4389f --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/ConjugateSecantSumsWhosePowersAddToANegativeWholeTest.cs @@ -0,0 +1,55 @@ +// +// 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 +{ + /// + /// sec(y) (a + a sec(y))^m (c - c sec(y))^n with m + n a negative whole number, + /// for any m: Rubi's 4.5.2.3. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class ConjugateSecantSumsWhosePowersAddToANegativeWholeTest + { + // The two sums multiply to -a c tan(y)^2, so they have one sign only where a and c do not: + // with a = 2 and c = -3 the first rows are real where sec(y) > 1. The rest are complex + // there, and are checked off the real line the same way. + [Theory] + [InlineData("sec(e+f*x)*(a+a*sec(e+f*x))^m*(c-c*sec(e+f*x))^(-1-m)", 300, true)] + [InlineData("sec(e+f*x)*(a+a*sec(e+f*x))^m*(c-c*sec(e+f*x))^(-2-m)", 400, true)] + [InlineData("sec(e+f*x)*(a+a*sec(e+f*x))^m*(c-c*sec(e+f*x))^(-3-m)", 500, true)] + [InlineData("sec(x)*(2+2*sec(x))^(5/3)*(3-3*sec(x))^(-8/3)", 200, false)] + [InlineData("sec(x)*(1-sec(x))^m*(1+sec(x))^(-1-m)", 200, false)] + [InlineData("csc(x)*(2+2*csc(x))^m*(3-3*csc(x))^(-2-m)", 300, false)] + public void DifferentiatesBack(string integrand, int atMost, bool real) + { + var integral = integrand.ToEntity().Integrate("x"); + var text = integral.Stringize(); + Assert.DoesNotContain("integral(", text); + Assert.True(text.Length < atMost, $"{text.Length} characters of answer for {integrand}"); + Entity At(Entity expression) => expression.Substitute("C", 0).Substitute("m", "1/3".ToEntity()) + .Substitute("a", 2).Substitute("c", -3).Substitute("e", "1/5".ToEntity()).Substitute("f", "3/2".ToEntity()); + var derivative = At(integral).Differentiate("x"); + var original = At(integrand.ToEntity()); + // Points where sec (or csc) of the argument is past 1: x and 1/5 + 3/2 x in (0, pi/2). + foreach (var at in integrand.StartsWith("csc") ? new[] { "1.1", "1.3", "1.5" } : new[] { "0.3", "0.6", "0.9" }) + { + var want = original.Substitute("x", at.ToEntity()).EvalNumerical(); + if (real) + Assert.True(Math.Abs((double)want.ImaginaryPart) < 1e-12, $"{integrand} is not real at x = {at}"); + 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) + Math.Abs((double)want.ImaginaryPart)), + $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}"); + } + } + } +} From f304c5c784fd2796070e5c05c259ab02393b7e47 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Mon, 12 Oct 2026 19:14:02 +0000 Subject: [PATCH 2/2] Record the conjugate secant sums in the breaking changes Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 422854f4c..eeb6537f8 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -505,6 +505,21 @@ two more of Rubi's 4.5.2.3. The two sums multiply to `-a c tan^2`, so in `u = a | `"sec(e + f*x)*(a + a*sec(e + f*x))^m*(c - c*sec(e + f*x))^(5/2)".ToEntity().Integrate("x")` | `integral(...)` | 469 characters | | `"sec(e + f*x)*(a + a*sec(e + f*x))^m*sqrt(c - c*sec(e + f*x))".ToEntity().Integrate("x")` | `integral(...)` | 268 characters | +### Powers of `a + a sec` and `c - c sec` that add to a negative whole number are integrated in the first sum + +**Answers where there were none.** `sec(e + f x) (a + a sec(e + f x))^m (c - c sec(e + f x))^(-1 - m)` was declined, +with two more of Rubi's 4.5.2.3. When the two powers add to `-k`, a whole number up to 12, the integral is the +integrand over the derivative of `u = a + a sec` times a polynomial in `u` and `a - a sec`, of degree `k + 1`, for a +symbolic `m` too, and the cosecant's is the same +([#718](https://github.com/asc-community/AngouriMath/issues/718), PR +[#1861](https://github.com/asc-community/AngouriMath/pull/1861)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"sec(e + f*x)*(a + a*sec(e + f*x))^m*(c - c*sec(e + f*x))^(-1 - m)".ToEntity().Integrate("x")` | `integral(...)` | 245 characters | +| `"sec(e + f*x)*(a + a*sec(e + f*x))^m*(c - c*sec(e + f*x))^(-3 - m)".ToEntity().Integrate("x")` | `integral(...)` | 402 characters | +| `"csc(x)*(2 + 2*csc(x))^m*(3 - 3*csc(x))^(-2 - m)".ToEntity().Integrate("x")` | `integral(...)` | 233 characters | + ### A secant and a tangent in one sum below the bar are integrated in the half angle **Answers where there were none.** `1/(a + c sec(x) + b tan(x))` was declined, and so was