From 0c9aac9c22c1d112dffafa672921783e900fec67 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sun, 13 Sep 2026 21:10:16 +0000 Subject: [PATCH] A fractional power of a quotient whose denominator is positive for every real x is written as the two powers it is, and the integrand so written is asked Timofeev's `((2 + x^2)/x^2)^(7/9)/(2 + x^2)^(3/2)` is nothing any rule reads as written, and `x^(-14/9) (2 + x^2)^(7/9 - 3/2)` once the quotient is written apart -- a binomial differential under `u = x^(1/9)`, which the linear radical substitution and the binomial rule answer between them in half a second. `(P/Q)^r = P^r/Q^r` whenever `Q` is a positive real, since then `arg(P/Q) = arg(P)`, and a polynomial in `x^2` with positive coefficients is one at every real `x` but zero, where the quotient was undefined anyway. The simplifier is right not to write it so for an `x` it knows nothing about; the integrator knows its variable is real. `(x^2)^(-7/9)` is `|x|^(-14/9)`, and is written as `x^(-14/9)`: what comes out is an antiderivative for `x > 0`, made one everywhere by parity -- exact, since an integrand with only even powers of `x` in it is even and its antiderivative odd -- or not at all. Both sides are checked. Asked at the top only, as every rule that lands on the open chain is, and beside the rule that combines radicals, which does the like for the roots it combines. Rubi sample: +1 (Timofeev 642), 0 wrong, 0 timeouts. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- .../Integration/IndefiniteIntegralSolver.cs | 75 +++++++++++++++++++ .../Integration/Integration.Definition.cs | 5 ++ .../Calculus/BinomialDifferentialTest.cs | 26 +++++++ 3 files changed, 106 insertions(+) diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index ec3f98581..6b848e3ad 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -6069,6 +6069,81 @@ private static bool OnEveryRealInterval(List bases, Entity.Variable x, S return true; } + /// + /// A fractional power of a quotient whose denominator is positive for every real + /// is written as the power of the numerator over the power of the + /// denominator, a power of an even power of x among them as a power of x + /// for x > 0, and the integrand so written is asked of the chain; where a + /// power of x was taken so, the answer is extended to x < 0 by parity. + /// + /// + /// + /// (P/Q)^r = P^r/Q^r whenever Q is a positive real, since then + /// arg(P/Q) = arg(P); a polynomial in x^2 with positive coefficients is one + /// at every real x but zero, where the quotient was undefined anyway. Timofeev's + /// ((2 + x^2)/x^2)^(7/9)/(2 + x^2)^(3/2) is nothing any rule reads as written, and + /// x^(-14/9) (2 + x^2)^(7/9 - 3/2) once the quotient is written apart, a binomial + /// differential under u = x^(1/9). The simplifier is right not to write it so + /// for an x it knows nothing about; the integrator knows its variable is real. + /// + /// + /// (x^2)^(-7/9) is |x|^(-14/9), and is written as x^(-14/9): what comes + /// out is an antiderivative for x > 0, made one everywhere by parity, which is + /// exact, or not at all. Asked at the top only, as every rule that lands on the open + /// chain is. + /// + /// https://github.com/asc-community/AngouriMath/issues/718 + /// + internal static Entity? SolveByWritingAPowerOfAQuotientApart(Entity expr, Entity.Variable x) + { + if (!Integration.AnsweringTheQuestionAsked) + return null; + var tookAPowerOfX = false; + var written = expr.Replace(node => + { + if (node is not Powf(Divf(var above, var below), Number.Rational exponent) || exponent is Number.Integer + || !below.ContainsNode(x) || !IsPositiveForReal(below, x)) + return node; + return MathS.Pow(above, exponent) * PowerOfAPositive(below, -exponent); + }); + if (written == expr) + return null; + var forPositive = Integration.ComputeIndefiniteIntegral(written.InnerSimplified, x, integrateByParts: true); + if (forPositive is null || forPositive.Nodes.Any(node => node == MathS.NaN)) + return null; + return tookAPowerOfX ? ExtendedByParity(expr, forPositive, x) : forPositive; + + // Q^r for a Q positive at every real x: a monomial `c x^(2k)` is `c^r x^(2kr)` for x > 0. + Entity PowerOfAPositive(Entity positive, Number.Rational exponent) + { + if (TreeAnalyzer.TryGetPolynomial(positive, x, out var monomials) && monomials.Count == 1) + { + var (degree, coefficient) = (monomials.Keys.First(), monomials.Values.First()); + if (degree.Sign > 0) + { + tookAPowerOfX = true; + var power = MathS.Pow(x, (Number.Integer.Create(degree) * exponent).InnerSimplified); + return coefficient.Evaled is Number.Integer { IsZero: false } one && one.EInteger.Equals(EInteger.One) + ? power + : MathS.Pow(coefficient, exponent) * power; + } + } + return MathS.Pow(positive, exponent); + } + } + + /// + /// Whether the polynomial in is positive at + /// every real but possibly zero, for the plain reason that every + /// monomial is an even power with a positive coefficient. + /// + private static bool IsPositiveForReal(Entity expr, Entity.Variable x) + { + if (!TreeAnalyzer.TryGetPolynomial(expr, x, out var monomials) || monomials.Count == 0) + return false; + return monomials.All(pair => pair.Key.IsEven && pair.Value.Evaled is Number.Real { IsPositive: true }); + } + /// /// The radicals of with every power of /// that a radical can hold taken out of it, for a that is not diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 669aa2ac7..f5277166c 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -463,6 +463,11 @@ private static Entity Normalized(Entity expr) => // `x/sqrt(1 - x^4)`, which the substitution answers, and it is what by parts leaves // from `arcsin(x)/(1 + x^2)^(3/2)`. if ((answer = IndefiniteIntegralSolver.SolveByCombiningRadicals(expr, x, integrateByParts)) is { }) return answer; + // And a fractional power of a quotient whose denominator is positive for every + // real x, written as the two powers it is: `((2 + x^2)/x^2)^(7/9)` is nothing any + // rule reads as written. Beside the rule above, which does the like for the roots + // it combines; at the top only, since it lands on the chain. + if ((answer = IndefiniteIntegralSolver.SolveByWritingAPowerOfAQuotientApart(expr, x)) is { }) return answer; if ((answer = IndefiniteIntegralSolver.SolveBySubstitution(expr, x, integrateByParts)) is { }) return answer; // After the general substitution rather than inside it, because the general one // divides by du/dx and asks what is left, and that question loses the shape here: diff --git a/Sources/Tests/UnitTests/Calculus/BinomialDifferentialTest.cs b/Sources/Tests/UnitTests/Calculus/BinomialDifferentialTest.cs index 9a8aa0780..c1bf91662 100644 --- a/Sources/Tests/UnitTests/Calculus/BinomialDifferentialTest.cs +++ b/Sources/Tests/UnitTests/Calculus/BinomialDifferentialTest.cs @@ -160,5 +160,31 @@ public void OutsideTheFirstCase(string integrand) [InlineData("x^3*sqrt(1 + x^2)")] [InlineData("1/(1 + x^2)^(3/2)")] public void TheNeighboursAreUntouched(string integrand) => DifferentiatesBack(integrand); + + /// + /// A fractional power of a quotient whose denominator is positive for every real + /// x is written as the two powers it is, and only then is a binomial seen: + /// Timofeev's ((2 + x^2)/x^2)^(7/9)/(2 + x^2)^(3/2) is + /// x^(-14/9) (2 + x^2)^(-13/18) so written, under u = x^(1/9). The power + /// of x^2 taken out is a power of |x|, so the answer for x > 0 is + /// extended by parity -- the integrand is even and the antiderivative odd -- and both + /// sides are checked. + /// + [Theory] + [InlineData("((2 + x^2)/x^2)^(7/9)/(2 + x^2)^(3/2)")] + [InlineData("((1 + x^2)/x^2)^(1/4)/(1 + x^2)^(3/2)")] + public void APowerOfAQuotientIsWrittenApart(string integrand) + { + DifferentiatesBack(integrand); + var derivative = integrand.ToEntity().Integrate("x").Substitute("C", 0).Differentiate("x"); + var original = integrand.ToEntity(); + foreach (var at in new[] { -0.83, -0.35 }) + { + var got = derivative.Substitute("x", at).EvalNumerical(); + var want = original.Substitute("x", at).EvalNumerical(); + Assert.True(Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart) < 1e-9, + $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}"); + } + } } }