From f105d8302c2b5d96f2c2fc400a0ff01b78a581a7 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Fri, 16 Oct 2026 22:13:28 +0000 Subject: [PATCH 1/2] Integrate a polynomial in a sine or cosine over a power of a binomial in it, in powers of the binomial Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- .../Integration/IndefiniteIntegralSolver.cs | 81 +++++++++++++++++++ .../Integration/Integration.Definition.cs | 3 + ...neOverAPowerOfABinomialInItIntegralTest.cs | 59 ++++++++++++++ 3 files changed, 143 insertions(+) create mode 100644 Sources/Tests/UnitTests/Calculus/APolynomialInASineOverAPowerOfABinomialInItIntegralTest.cs diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 176887f8a..12c5020b6 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -26087,6 +26087,87 @@ when two.Equals(EInteger.FromInt32(2)) && e1 == MathS.e && e2 == MathS.e return Functions.PartialFractions.DerivativeDiffersWhereReal(answer, expr, x) ? null : answer; } + /// + /// A polynomial in a sine or a cosine of a linear over a whole power of a binomial in it, + /// (A + B cos(y) + C cos(y)^2)/(a + b cos(y))^4, written in powers of the binomial: + /// with u = a + b f, f = (u - a)/b, and P(f)/u^n = sum c_j u^(j - n) with + /// c_j = sum_(i >= j) p_i C(i, j) (-a)^(i - j)/b^i, each power of the binomial its own + /// question. + /// + /// + /// The rules after it took the whole quotient through the half-angle and wrote 30 million + /// characters for (A + B cos(x) + C cos(x)^2)/(a + b cos(x))^4 in eighteen seconds, where + /// 1/(a + b cos(x))^4 alone is 44 thousand. Rubi's 4.1.4 and 4.2.4 + /// (a + b cos)^m (A + B cos + C cos^2) at m = -4 timed out. + /// https://github.com/asc-community/AngouriMath/issues/718 + /// + internal static Entity? SolveAPolynomialInASineOverAPowerOfABinomialInItInPowersOfTheBinomial(Entity expr, Entity.Variable x) + { + if (!Integration.AnsweringTheQuestionAsked) + return null; + var functions = expr.Nodes.Where(node => node is Sinf or Cosf && node.ContainsNode(x)).Distinct().ToList(); + if (functions.Count != 1) + return null; + var function = functions[0]; + var argument = function switch { Sinf(var y) => y, Cosf(var y) => y, _ => null! }; + if (!TreeAnalyzer.TryGetPolyLinear(argument, x, out var slope, out _) || Functions.PartialFractions.IsZeroAsAValue(slope)) + return null; + var s = Variable.CreateUnique(expr, "s_pow"); + var inS = expr.Substitute(function, s); + if (inS.ContainsNode(x)) + return null; + var (above, below) = Functions.SingleQuotient.Of(inS); + // Below the bar a factor free of s times one whole power of a binomial in s. + static IEnumerable Factors(Entity e) => e is Mulf(var l, var r) ? Factors(l).Concat(Factors(r)) : new[] { e }; + Entity constant = Number.Integer.One; + Entity? binomial = null; + var n = 0; + foreach (var factor in Factors(below)) + { + if (!factor.ContainsNode(s)) + { + constant *= factor; + continue; + } + if (binomial is not null) + return null; + (binomial, n) = factor is Powf(var b0, Number.Integer { EInteger: var power }) && power.CanFitInInt32() ? (b0, power.ToInt32Unchecked()) : (factor, 1); + } + if (binomial is null || n < 2 || n > 12 + || !TreeAnalyzer.TryGetPolyLinear(binomial, s, out var b, out var a) + || Functions.PartialFractions.IsZeroAsAValue(a) || Functions.PartialFractions.IsZeroAsAValue(b) + || !TreeAnalyzer.TryGetPolynomial(above, s, out var p) || p.Keys.Any(key => key.Sign < 0 || !key.CanFitInInt32())) + return null; + var degree = p.Keys.Max()!.ToInt32Unchecked(); + // The reciprocal of the power alone is what the rules after it answer, in a few milliseconds. + if (degree == 0 || degree > 12) + return null; + var u = a + b * function; + Entity answer = Number.Integer.Zero; + for (var j = 0; j <= degree; j++) + { + Entity c = Number.Integer.Zero; + var choose = EInteger.One; + for (var i = j; i <= degree; i++) + { + // C(i, j) from C(i - 1, j). + if (i > j) + choose = choose * i / (i - j); + if (p.TryGetValue(EInteger.FromInt32(i), out var pi)) + c += pi * Number.Integer.Create(choose) * (i == j ? Number.Integer.One : MathS.Pow(-a, Number.Integer.Create(i - j))) / (i == 0 ? Number.Integer.One : MathS.Pow(b, Number.Integer.Create(i))); + } + c = c.InnerSimplified; + if (Functions.PartialFractions.IsZeroAsAValue(c)) + continue; + var piece = j - n == 0 ? (Entity)Number.Integer.One : MathS.Pow(u, Number.Integer.Create(j - n)); + if (Integration.ComputeAsAQuestionOfItsOwn(piece, x, integrateByParts: true) is not { } integrated) + return null; + answer += c * integrated; + } + answer = (answer / constant).InnerSimplified; + return answer.Nodes.Any(node => node == MathS.NaN) ? null : answer; + } + /// /// A rational function of a sine, or of a cosine, over a quadratic in it with a symbol /// among the coefficients: sin(x)/(a + b sin(x) + c sin(x)^2), and the same with diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 39247aeed..4dcb2d3a9 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -670,6 +670,9 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr) // A power of a sine, cosine or hyperbolic one over a binomial in a power of it, over the // binomial's roots, before the rules that spent the budget on `sinh(y)^4/(a + b sinh(y)^3)`. if ((answer = IndefiniteIntegralSolver.SolveAPowerOverABinomialInAPowerOfASineOverItsRoots(expr, x)) is { }) return answer; + // A polynomial in a sine or a cosine over a whole power of a binomial in it, in powers of the + // binomial, before the half-angle wrote 30 million characters for `(A + B cos + C cos^2)/(a + b cos)^4`. + if ((answer = IndefiniteIntegralSolver.SolveAPolynomialInASineOverAPowerOfABinomialInItInPowersOfTheBinomial(expr, x)) is { }) return answer; // A rational function times the logarithm of a nested product of powers of linears, by parts, // its logarithm's derivative read off as written: `ln(e (f (a + b x)^p (c + d x)^q)^r)`. if ((answer = IndefiniteIntegralSolver.SolveARationalFunctionTimesALogarithmOfPowersOfLinears(expr, x, integrateByParts)) is { }) return answer; diff --git a/Sources/Tests/UnitTests/Calculus/APolynomialInASineOverAPowerOfABinomialInItIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/APolynomialInASineOverAPowerOfABinomialInItIntegralTest.cs new file mode 100644 index 000000000..60fb55b54 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/APolynomialInASineOverAPowerOfABinomialInItIntegralTest.cs @@ -0,0 +1,59 @@ +// +// 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 polynomial in a sine or a cosine over a whole power of a binomial in it, written in powers of + /// the binomial: Rubi's 4.1.4 and 4.2.4 (a + b cos)^m (A + B cos + C cos^2) at m = -4, + /// which timed out. Pinned on the real domain. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class APolynomialInASineOverAPowerOfABinomialInItIntegralTest + { + private static void DifferentiatesBack(string integrand, params (string, double)[] pins) + { + var integral = integrand.ToEntity().Integrate("x"); + Assert.DoesNotContain("integral(", integral.Stringize()); + + var pinned = integral.Substitute("C", 0); + Entity original = integrand.ToEntity(); + foreach (var (name, value) in pins) + { + pinned = pinned.Substitute(name, value); + original = original.Substitute(name, value); + } + var derivative = pinned.Differentiate("x"); + foreach (var at in new[] { -2.3, -0.7, 0.4, 1.9 }) + { + var got = derivative.Substitute("x", at).EvalNumerical(); + var want = original.Substitute("x", at).EvalNumerical(); + var difference = Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart); + var scale = Math.Max(1.0, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart)); + Assert.True(difference / scale < 1e-8, + $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}"); + } + } + + // The parameters are p, q, r rather than Rubi's A, B, C: the helper takes C for the constant. + [Theory] + [InlineData("(p+q*cos(c+d*x)+r*cos(c+d*x)^2)/(a+b*cos(c+d*x))^4")] + [InlineData("cos(c+d*x)^3*(p+q*cos(c+d*x)+r*cos(c+d*x)^2)/(a+b*cos(c+d*x))^4")] + [InlineData("cos(c+d*x)^2*(p+r*cos(c+d*x)^2)/(a+b*cos(c+d*x))^4")] + [InlineData("(p+q*sin(c+d*x)+r*sin(c+d*x)^2)/(a+b*sin(c+d*x))^3")] + public void IsAnsweredAndDifferentiatesBack(string integrand) + { + DifferentiatesBack(integrand, ("a", 1.3), ("b", 0.7), ("c", 0.7), ("d", 1.07), ("p", 0.43), ("q", -0.45), ("r", 0.22)); + DifferentiatesBack(integrand, ("a", 2), ("b", -1.5), ("c", -0.3), ("d", 0.6), ("p", -1), ("q", 2), ("r", 0.5)); + } + } +} From b535563b5203a97bedd4d741385023d7b9d27812 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sat, 17 Oct 2026 07:31:26 +0000 Subject: [PATCH 2/2] Record the polynomials in a sine over a power of a binomial in it in 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 5d01b9202..e89c879dd 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -244,6 +244,21 @@ cache is now keyed on the downcasting setting too |---|---|---| | `MathS.FromString("1/3").Evaled` under downcasting off, after the same parse with it on | `1/3` | `0.3333…` (one hundred digits) | +### A polynomial in a sine over a power of a binomial in it is written in powers of the binomial + +**Answers where there were none.** `(A + B cos(c + d x) + C cos(c + d x)^2)/(a + b cos(c + d x))^4` ran out of time, and +so did twelve more of Rubi's 4.1.3.1, 4.2.3.1 and 4.2.4.2. `P(f)/(a + b f)^n`, for `f` a sine or cosine, is now +written in powers of `u = a + b f`, with coefficients `c_j = sum_i p_i C(i, j) (-a)^(i - j)/b^i`, and each power +`u^(j - n)` is integrated on its own +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"(A+B*cos(c+d*x)+C*cos(c+d*x)^2)/(a+b*cos(c+d*x))^4".ToEntity().Integrate("x")` | `integral(...)` | 9,003 characters | +| `"(A+B*sin(e+f*x))*(c+d*sin(e+f*x))^3/(a+a*sin(e+f*x))^3".ToEntity().Integrate("x")` | `integral(...)` | 1,217 characters | +| `"(a+b*sin(e+f*x))^2*(A+B*sin(e+f*x))/(c+d*sin(e+f*x))^2".ToEntity().Integrate("x")` | `integral(...)` | 3,337 characters | +| `"cos(c+d*x)^4*(A+C*cos(c+d*x)^2)/(a+b*cos(c+d*x))^4".ToEntity().Integrate("x")` | `integral(...)` | 10,859 characters | + ### A difference of a negated term is printed as a sum **A printed form.** A solution and a difference were printed with the sign of a negative factor left inside: