From 90944d0af46ab872020354ac54f3283def4b7935 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sun, 11 Oct 2026 15:35:58 +0000 Subject: [PATCH 1/2] A power of the cosine beside a secant binomial is integrated in the cosine The route writing a rational function of the secant in the cosine declined whenever the cosine of the same argument stood beside it, so the half-angle substitution answered cos(x)^2/(a + b sec(x)) in 117,409 characters where cos(x)^3/(a cos(x) + b) is 873. The cosine beside the secant, or the sine beside the cosecant, is accepted now, with the binomial to the first power. Part of #718. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 19 ++++++++ .../Integration/IndefiniteIntegralSolver.cs | 27 +++++++++-- ...CosineBesideASecantBinomialIntegralTest.cs | 47 +++++++++++++++++++ 3 files changed, 88 insertions(+), 5 deletions(-) create mode 100644 Sources/Tests/UnitTests/Calculus/ACosineBesideASecantBinomialIntegralTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 1880f261b..a4cbb2ca8 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -329,6 +329,25 @@ in Rubi's 1.1.3.4 come out shorter as well, and those that took two seconds take | `"1/(x^8*(a + b*x^3)*(c + d*x^3))".ToEntity().Integrate("x")` | `integral(...)` | 1,217 characters | | `"1/(x^6*(a + b*x^4)*(c + d*x^4))".ToEntity().Integrate("x")` | `integral(...)` | 795 characters | +### A power of the cosine beside a secant binomial is integrated in the cosine + +**Answers where there were none.** `cos(c + d x)^2/(a + b sec(c + d x))` was declined, and so were the cosine's powers +beside `A + B sec + C sec^2` over the same binomial (Rubi's 4.5.1.2, 4.5.3.1 and 4.5.4.2). Wherever the secant is +defined, the integrand is one quotient in the cosine, `cos(c + d x)^3/(a cos(c + d x) + b)`, and it is integrated +in that spelling now. The same holds for a power of the sine beside a cosecant binomial. Only for the binomial to +the first power: for a higher power the secant's own rules answer +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"cos(c + d*x)^2/(a + b*sec(c + d*x))".ToEntity().Integrate("x")` | `integral(...)` | 1,091 characters | +| `"cos(c + d*x)^2*(A + B*sec(c + d*x))/(a + b*sec(c + d*x))".ToEntity().Integrate("x")` | `integral(...)` | 4,347 characters | +| `"cos(c + d*x)^2*(A + C*sec(c + d*x)^2)/(a + b*sec(c + d*x))".ToEntity().Integrate("x")` | `integral(...)` | 3,999 characters | +| `"cos(c + d*x)^3*(B*sec(c + d*x) + C*sec(c + d*x)^2)/(a + b*sec(c + d*x))".ToEntity().Integrate("x")` | `integral(...)` | 4,349 characters | +| `"cos(c + d*x)^4*(B*sec(c + d*x) + C*sec(c + d*x)^2)/(a + b*sec(c + d*x))".ToEntity().Integrate("x")` | `integral(...)` | 5,579 characters | +| `"cos(c + d*x)^2*(A + B*sec(c + d*x) + C*sec(c + d*x)^2)/(a + b*sec(c + d*x))".ToEntity().Integrate("x")` | `integral(...)` | 12,065 characters | +| `"sin(x)^2/(a + b*csc(x))".ToEntity().Integrate("x")` | `integral(...)` | 898 characters | + ### One linear twice is not two linear powers **Answers that had no value.** `(a c + b c x)^(-3 - 2 p) (f + g x) (a^2 + 2 a b x + b^2 x^2)^p` was answered diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index ff16fe0f5..576245375 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -28902,12 +28902,14 @@ bool IsAPolynomialInBoth(Entity polynomial) /// /// The half-angle substitution read the secant's spelling into a rational function of the /// half-angle tangent that a substitution search expanded past thirty seconds, where the - /// cosine's is answered in half of one. Only for two sums or more, all below the bar. Rubi's 4.5.2.3. + /// cosine's is answered in half of one. Only for two sums or more, all below the bar, or beside the + /// cosine itself. Rubi's 4.5.2.3 and 4.5.4.2. /// https://github.com/asc-community/AngouriMath/issues/718 /// internal static Entity? SolveByWritingARationalFunctionOfTheSecantInTheCosine(Entity expr, Entity.Variable x, bool integrateByParts) { Entity? reciprocal = null; + var partners = new List(); foreach (var node in expr.Nodes) { if (!node.ContainsNode(x) || node is Entity.Variable) @@ -28920,6 +28922,9 @@ bool IsAPolynomialInBoth(Entity polynomial) else if (reciprocal != node) return null; break; + case Cosf or Sinf: + partners.Add(node); + break; case Sumf or Minusf or Mulf or Divf or Powf(_, Number.Integer): break; default: @@ -28933,15 +28938,27 @@ bool IsAPolynomialInBoth(Entity polynomial) } if (reciprocal is null || !TreeAnalyzer.TryGetPolyLinear(reciprocal.DirectChildren.First(), x, out _, out _)) return null; + var argument = reciprocal.DirectChildren.First(); + var function = reciprocal is Secantf ? MathS.Cos(argument) : MathS.Sin(argument); + // The cosine of the same argument beside the secant, or the sine beside the cosecant, is + // what the rewritten integrand is in already; any other is a second function. + if (partners.Any(partner => partner != function)) + return null; // Only two sums in the secant or more, all below the bar: one, or one above, the rules for // the secant answer as written, and in the cosine `sec(x)^5/(a + b sec(x))^3` ran past five - // seconds where it is half of one. + // seconds where it is half of one. Beside the cosine itself, though, the secant's rules have + // two functions to read, and in the cosine alone it is one quotient: the half-angle + // substitution wrote `cos(x)^2/(a + b sec(x))` in 117,409 characters where + // `cos(x)^3/(a cos(x) + b)` is 873. Rubi's 4.5.4.2. var (above, below) = Functions.SingleQuotient.Of(expr); bool ASum(Entity node) => node is Sumf or Minusf && node.ContainsNode(reciprocal); - if (above.Nodes.Any(ASum) || below.Nodes.Where(ASum).Distinct().Count() < 2) + if (partners.Count == 0 && (above.Nodes.Any(ASum) || below.Nodes.Where(ASum).Distinct().Count() < 2)) + return null; + // Beside the cosine, each sum below to the first power only: `cos(x)/(a + b sec(x))^4` in the + // cosine is a partial fraction search past five seconds, which the secant's rules answer in + // a third of one. + if (partners.Count > 0 && Mulf.LinearChildren(below).Any(factor => factor is Powf(var @base, _) && ASum(@base))) return null; - var argument = reciprocal.DirectChildren.First(); - var function = reciprocal is Secantf ? MathS.Cos(argument) : MathS.Sin(argument); var rewritten = Functions.SingleQuotient.Combine(expr.Replace(node => node == reciprocal ? 1 / function : node)); return Integration.ComputeAsTheSameQuestion(rewritten, x, integrateByParts); } diff --git a/Sources/Tests/UnitTests/Calculus/ACosineBesideASecantBinomialIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/ACosineBesideASecantBinomialIntegralTest.cs new file mode 100644 index 000000000..e6aec0b6b --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/ACosineBesideASecantBinomialIntegralTest.cs @@ -0,0 +1,47 @@ +// +// 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 +{ + /// + /// A power of the cosine beside a rational function of the secant of the same argument, written + /// in the cosine alone, where it is one quotient. Rubi's 4.5.4.2. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class ACosineBesideASecantBinomialIntegralTest + { + [Theory] + [InlineData("cos(c + d*x)^2*(A + K*sec(c + d*x)^2)/(a + b*sec(c + d*x))", 6000)] + [InlineData("cos(c + d*x)^3*(B*sec(c + d*x) + K*sec(c + d*x)^2)/(a + b*sec(c + d*x))", 6000)] + [InlineData("cos(c + d*x)^2*(A + B*sec(c + d*x) + K*sec(c + d*x)^2)/(a + b*sec(c + d*x))", 15000)] + [InlineData("cos(x)^2/(a + b*sec(x))", 1500)] + [InlineData("sin(x)^2/(a + b*csc(x))", 1500)] + public void DifferentiatesBack(string integrand, int atMostCharacters) + { + var integral = integrand.ToEntity().Integrate("x"); + Assert.DoesNotContain("integral(", integral.Stringize()); + Assert.True(integral.Stringize().Length <= atMostCharacters, $"{integral.Stringize().Length} characters"); + Entity Pinned(Entity e) => e.Substitute("a", 2).Substitute("b", 3).Substitute("c", "1/4".ToEntity()).Substitute("d", "3/2".ToEntity()) + .Substitute("A", 5).Substitute("B", 7).Substitute("K", 11); + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pinned(integrand.ToEntity()); + foreach (var at in new[] { "0.35", "0.6", "1.1", "1.4" }) + { + var want = original.Substitute("x", at.ToEntity()).EvalNumerical(); + 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)), + $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}"); + } + } + } +} From 1982cc215af5b6e1f885a387fd00d106681d1e1e Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sun, 11 Oct 2026 17:30:47 +0000 Subject: [PATCH 2/2] Beside the cosine, only a sum in the secant below Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- .../Continuous/Integration/IndefiniteIntegralSolver.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 576245375..22d329c1c 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -28954,10 +28954,12 @@ bool IsAPolynomialInBoth(Entity polynomial) bool ASum(Entity node) => node is Sumf or Minusf && node.ContainsNode(reciprocal); if (partners.Count == 0 && (above.Nodes.Any(ASum) || below.Nodes.Where(ASum).Distinct().Count() < 2)) return null; - // Beside the cosine, each sum below to the first power only: `cos(x)/(a + b sec(x))^4` in the - // cosine is a partial fraction search past five seconds, which the secant's rules answer in - // a third of one. - if (partners.Count > 0 && Mulf.LinearChildren(below).Any(factor => factor is Powf(var @base, _) && ASum(@base))) + // Beside the cosine, a sum in the secant below, each to the first power only: + // `cos(x)/(a + b sec(x))^4` in the cosine is a partial fraction search past five seconds, + // which the secant's rules answer in a third of one, and with the sum in the sine instead, + // `csc(x)^3/(a + b sin(x)^3)` is a question for the rules of the sine's binomial. + if (partners.Count > 0 + && (!below.Nodes.Any(ASum) || Mulf.LinearChildren(below).Any(factor => factor is Powf(var @base, _) && ASum(@base)))) return null; var rewritten = Functions.SingleQuotient.Combine(expr.Replace(node => node == reciprocal ? 1 / function : node)); return Integration.ComputeAsTheSameQuestion(rewritten, x, integrateByParts);