From eaa3a00327e0cb9f596973ca061deadd35061b10 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Tue, 20 Oct 2026 12:43:19 +0000 Subject: [PATCH] =?UTF-8?q?Integrate=20half-odd=20powers=20of=20a=20=C2=B1?= =?UTF-8?q?=20a=20sin=20beside=20a=20fractional=20power=20of=20the=20cosin?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rubi's (e cos(c + d x))^p (a + a sin(c + d x))^m with half-odd p and m were past the budget: the substitution search spent it on the two radicals. Under u = sin(y) they are (1 + u)^A (1 - u)^B with A and B quarter-odd, which by Chebyshev's theorem is elementary only where A + B = m + p - 1 is whole. With every power a number, the rule now asks only where A, B or A + B is whole and the rest is a polynomial in u. (a + a sin(y)) sqrt(e cos(y)), which is elliptic, is declined before anything is asked. Numeric powers are read one level below the question too, where the polynomial term hands 1/(A B) on as (A B)^(-1), and a whole power of a product is read as the powers of its factors. Three of the file master answered in six to eleven seconds under a condition now come through in a twentieth of a second without one. Part of #233. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 21 +++++++ .../Integration/IndefiniteIntegralSolver.cs | 44 +++++++++++-- .../Integration/Integration.Definition.cs | 3 +- ...OfACosineBesideOnePlusASineIntegralTest.cs | 61 +++++++++++++++++++ 4 files changed, 123 insertions(+), 6 deletions(-) create mode 100644 Sources/Tests/UnitTests/Calculus/FractionalPowersOfACosineBesideOnePlusASineIntegralTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 706174fdb..a0a562864 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -3708,6 +3708,27 @@ Rubi's 1.1.4.2, `(c x)^m (a x^j + b x^n)^p` ([#718](https://github.com/asc-commu | `"x/sqrt(a*x + b*x^4)".Integrate("x")` | left unevaluated | `K` times `2 ln(2 b x^(3/2) + 2 sqrt(b) sqrt(a + b x^3))/(3 sqrt(b))` for `b > 0`, an arcsine for `b < 0`; real where `x < -(a/b)^(1/3)` as well, where `K` is `-1` | | `"x^(3/2*(n - 1))/(a*x^(n - 1) + b*x^n + c*x^(n + 1))^(3/2)".Integrate("x")` | left unevaluated | `K (2 b + 4 c x)/((4 a c - b^2) sqrt(a + b x + c x^2))`; `K` is 1 for a positive `x`, and a sign for a negative one, `-1` for `n = 2.3` | +### Half-odd powers of `a ± a sin` beside a fractional power of the cosine are integrated through the sine + +**Answers where there were none.** `(e cos(c + d x))^(5/2) (a + a sin(c + d x))^(3/2)`, +`sqrt(e cos(c + d x))/sqrt(a + a sin(c + d x))` and the 22 others of Rubi's 4.1.1.2 with a half-odd power of each were +declined. Under `u = sin(y)` they are `(1 + u)^A (1 - u)^B` with `A` and `B` quarter-odd, which by Chebyshev's theorem +is elementary only where `A + B` is whole. The rule below now takes numeric powers where `A`, `B` or `A + B` is whole +and the rest is a polynomial in `u`, also one level below the question, and declines +`(a + a sin(y)) sqrt(e cos(y))`, which is elliptic, before asking anything. Three of the file the unreleased master +answered in six to eleven seconds, under a condition, now come through it in a twentieth of a second and without one, +and `sqrt(e sin(c + d x))/sqrt(a - a cos(c + d x))`, which it answered by the half angle only for positive `a` and `e`, is +answered for either sign ([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"(k*cos(c + d*x))^(5/2)*(a + a*sin(c + d*x))^(3/2)".ToEntity().Integrate("x")` | `integral(...)` | 1,156 characters | +| `"sqrt(k*cos(c + d*x))/sqrt(a + a*sin(c + d*x))".ToEntity().Integrate("x")` | `integral(...)` | 859 characters | +| `"(a + a*sin(c + d*x))^(5/2)/(k*cos(c + d*x))^(13/2)".ToEntity().Integrate("x")` | `integral(...)` | 468 characters | +| `"(k*cos(c + d*x))^(9/2)/(a + a*sin(c + d*x))^(5/2)".ToEntity().Integrate("x")` | `integral(...)` | 1,073 characters | +| `"(a + a*sin(c + d*x))^(3/2)/(k*cos(c + d*x))^(9/2)".ToEntity().Integrate("x")` | `integral(...)`; 456 characters under a condition, in 10.6 s, on the unreleased master | 403 characters, in 61 ms | +| `"sqrt(k*sin(c + d*x))/sqrt(a - a*cos(c + d*x))".ToEntity().Integrate("x")` | `integral(...)`; on the unreleased master, an answer only where `a > 0` and `k > 0` | 825 characters, for either sign | + ### Symbolic powers of `a ± a sin` beside a power of the cosine are integrated through the sine **Answers where there were none.** `(a + a sin(h + f x))^m sqrt(c - c sin(h + f x))` and its kin, diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index b64fa6967..b3d615670 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -5469,8 +5469,8 @@ Entity Integral(ERational power) } /// - /// Powers of a ± a sin(y) with a symbolic exponent, beside a power of - /// g cos(y) and anything else in the sine alone, under u = sin(y): + /// Powers of a ± a sin(y) with a symbolic exponent, or beside a fractional power of + /// g cos(y), and anything else in the sine alone, under u = sin(y): /// (1 + sin(y))(1 - sin(y)) is cos(y)^2, so each factor is a power of /// 1 + u or of 1 - u up to a factor constant on each interval where it is /// defined, and dy = du/cos(y) is one more pair of half powers. The cosine's the @@ -5495,12 +5495,24 @@ Entity Integral(ERational power) /// (a (1 + u))^m is not a^m (1 + u)^m for every a, and the quotient /// of the two is constant on each interval either way. At the question asked only, and /// only where an exponent is symbolic: numeric half powers are the half angle's. + /// + /// + /// But for a fractional power of the cosine, which the half angle does not read: Rubi's + /// (e cos(c + d x))^p (a + a sin(c + d x))^m with half-odd p and m + /// were past the budget, the substitution search spending it on the two radicals. In + /// u they are (1 + u)^A (1 - u)^B with A and B quarter-odd, which + /// by Chebyshev's theorem is elementary only where A + B = m + p - 1 is whole, and + /// with every power a number the rule asks only where A, B or A + B is + /// whole and the rest is a polynomial in u: (a + a sin(y)) sqrt(e cos(y)), which + /// is elliptic, is declined before anything is asked. Numeric powers are read one level + /// below the question too, where the polynomial term hands 1/(A B) on as + /// (A B)^(-1), and a whole power of a product is read as the powers of its factors. /// https://github.com/asc-community/AngouriMath/issues/718 /// /// internal static Entity? SolveSymbolicPowersOfOnePlusMinusASineThroughTheSine(Entity expr, Entity.Variable x, bool integrateByParts) { - if (!Integration.AnsweringTheQuestionAsked) + if (!Integration.AnsweringTheQuestionAskedOrOneBelow) return null; Entity? argument = null; foreach (var node in expr.Nodes) @@ -5519,8 +5531,12 @@ Entity Integral(ERational power) var cosine = MathS.Cos(argument); // Each factor as a base and an exponent, a power of a power read as one power: the // quotient of the two spellings is constant on each interval, and K carries it. + // A whole power of a product is the product of the powers: `1/(A B)` is handed on as + // `(A B)^(-1)` by the polynomial term. var factors = new List<(Entity Factor, Entity Base, Entity Exponent)>(); - foreach (var factor in Mulf.LinearChildren(expr)) + foreach (var factor in Mulf.LinearChildren(expr).SelectMany(factor => factor is Powf(var product, Number.Integer power) && product is Mulf or Divf + ? Mulf.LinearChildren(product).Select(part => (Entity)MathS.Pow(part, power)) + : new[] { factor })) { Entity @base = factor, exponent = Number.Integer.One; while (@base is Powf(var inner, var power)) @@ -5580,12 +5596,22 @@ Entity Integral(ERational power) return null; rest *= inU; } - if (!symbolic) + // Symbolic powers at the question asked only. Numeric powers are the half angle's, but + // for a fractional power of the complement, which it does not read; and those one level + // down too, where the polynomial term hands on `1/(A B)` as `(A B)^(-1)`. + if (symbolic ? !Integration.AnsweringTheQuestionAsked + : !(ofTheComplement.InnerSimplified is Number.Rational complementPower && !IsWhole(complementPower.ERational))) return null; // dy = du/complement takes one more half power of each. var half = Number.Rational.Create(1, 2); var a = (plus + ofTheComplement * half - half).Simplify(); var b = (minus + ofTheComplement * half - half).Simplify(); + // With every power a number, only where Chebyshev's theorem says the question in u is + // elementary, and beside a polynomial in u: otherwise it is a search past the budget. + if (!symbolic && !(a is Number.Rational ra && b is Number.Rational rb + && (IsWhole(ra.ERational) || IsWhole(rb.ERational) || IsWhole(ra.ERational.Add(rb.ERational))) + && IsAPolynomialInU(rest.InnerSimplified))) + return null; // A zero exponent leaves its factor out: `(1 - u)^0` is one provided `u` is not one, and // the condition would stand between the question and the rules that answer it. Entity PowerOf(Entity @base, Entity exponent) => exponent == Number.Integer.Zero ? Number.Integer.One : MathS.Pow(@base, exponent); @@ -5619,6 +5645,14 @@ Entity Integral(ERational power) } return found; } + + static bool IsWhole(ERational value) => value.IsInteger(); + + // Free of u, or a polynomial in it with coefficients free of it. + bool IsAPolynomialInU(Entity part) + => !part.ContainsNode(u) + || TreeAnalyzer.TryGetPolynomial(part, u, out var terms) + && terms.Keys.All(power => power.Sign >= 0) && terms.Values.All(coefficient => !coefficient.ContainsNode(u)); } /// diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 53bb299b5..779ce6448 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -1045,7 +1045,8 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr) if ((answer = IndefiniteIntegralSolver.SolveAPowerOfACosineAndASinePlusTheirAmplitude(expr, x)) is { }) return answer; // And symbolic powers of `a ± a sin(y)` beside a power of `g cos(y)`, by u = sin(y), where // `(1 + u)(1 - u)` is the cosine's square: the half angle wants numeric powers, and the - // substitution search spent the budget on these. + // substitution search spent the budget on these. Numeric ones too beside a fractional + // power of `g cos(y)`, which the half angle does not read. if ((answer = IndefiniteIntegralSolver.SolveSymbolicPowersOfOnePlusMinusASineThroughTheSine(expr, x, integrateByParts)) is { }) return answer; // And a half-odd power of a +- a sec(y), which is that square over cos(y): by the half-angle // tangent, in which the whole is rational beside one root. diff --git a/Sources/Tests/UnitTests/Calculus/FractionalPowersOfACosineBesideOnePlusASineIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/FractionalPowersOfACosineBesideOnePlusASineIntegralTest.cs new file mode 100644 index 000000000..76125e6a6 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/FractionalPowersOfACosineBesideOnePlusASineIntegralTest.cs @@ -0,0 +1,61 @@ +// +// 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 +{ + /// + /// Numeric powers of a ± a sin(y) beside a fractional power of k cos(y), under + /// u = sin(y): (1 + u)^A (1 - u)^B with A + B whole, which the half angle + /// does not read and the substitution search spent the budget on. Checked where the cosine is + /// positive, and with k negative where it is negative, both real. Rubi's 4.1.1.2. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class FractionalPowersOfACosineBesideOnePlusASineIntegralTest + { + [Theory] + [InlineData("(k*cos(c + d*x))^(5/2)*(a + a*sin(c + d*x))^(3/2)")] + [InlineData("sqrt(k*cos(c + d*x))/sqrt(a + a*sin(c + d*x))")] + [InlineData("(a + a*sin(c + d*x))^(5/2)/(k*cos(c + d*x))^(13/2)")] + [InlineData("(k*cos(c + d*x))^(9/2)/(a + a*sin(c + d*x))^(5/2)")] + [InlineData("1/((k*cos(c + d*x))^(7/2)*(a + a*sin(c + d*x))^(3/2))")] + [InlineData("(k*cos(c + d*x))^(3/2)*sqrt(a - a*sin(c + d*x))")] + [InlineData("sin(c + d*x)*sqrt(k*cos(c + d*x))*(a + a*sin(c + d*x))^(3/2)")] + [InlineData("(k*sin(c + d*x))^(3/2)*(a + a*cos(c + d*x))^(3/2)")] + public void ThroughTheSine(string integrand) + { + var integral = integrand.ToEntity().Integrate("x"); + var text = integral.Stringize(); + Assert.DoesNotContain("integral(", text); + Assert.True(text.Length < 3000, $"{text.Length} characters of answer for {integrand}"); + // y = c + d x stays inside (0, pi/2) at the first pins and inside (pi/2, pi) at the second. + foreach (var (k, c) in new[] { (0.7, 0.4), (-0.7, 2.2) }) + { + Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("k", k).Substitute("c", c).Substitute("d", 0.3); + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pinned(integrand.ToEntity()); + var compared = 0; + foreach (var at in new[] { -1.2, -0.7, 0.3, 0.8, 1.3, 2.9 }) + { + var want = original.Substitute("x", at).EvalNumerical(); + var got = derivative.Substitute("x", at).EvalNumerical(); + if (want.IsNaN) + continue; + compared++; + 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}, k = {k}, where the integrand is {want}"); + } + Assert.True(compared >= 5, $"only {compared} points could be compared for {integrand}"); + } + } + } +}