From 4875c1fd2f025e2131b14a89ff852db20a2e22db Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Wed, 23 Sep 2026 04:28:35 +0000 Subject: [PATCH] A power of the variable below the bar beside a root of a quadratic `1/(x^2 sqrt(4 + 3x + 2x^2))` was left as written while `1/(x sqrt(...))` came out: the single factor was read and the repeated one was not, and the repeated one is what a round of parts against `arccos(a + b x)/x^4` leaves. Under `x = 1/t` the integrand is `-sgn(t) t^(n - 1)/sqrt(q0 t^2 + q1 t + q2)`, a polynomial over the root of the quadratic read the other way round, which the rules for those answer. The radicand is assembled rather than substituted into -- `Q(1/t)` is `R(t)/t^2`, and writing the quotient inside the root leaves a nesting nothing downstream reduces -- and the modulus that leaves is a `sgn(t)` in front, which at `t = 1/x` is `sgn(x)`. The reciprocal's own `t != 0` is stripped before the integrand is handed on: it says what the integrand already says, and a condition is not an integrand to the rules below, which declined it. `SolveByReciprocalSubstitution` is this substitution for a different shape, a palindromic quartic under the root, and reads nothing here. Family 5 of the Rubi suite: 211 -> 212 of 257, family 1 155 -> 156 of 228, 0 wrong; the 1774-problem suite 1707 unchanged. Suite 12662 passed; allocation gate passed on all 19 gated benchmarks. Six forms differentiate back at five points, with and without symbols among the coefficients. Part of #718. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 20 ++++ .../Integration/IndefiniteIntegralSolver.cs | 106 ++++++++++++++++++ .../Integration/Integration.Definition.cs | 4 + .../Calculus/ReciprocalSubstitutionTest.cs | 28 +++++ 4 files changed, 158 insertions(+) diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 94869eaf4..bb79325a8 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -1212,6 +1212,26 @@ keeps its decline ([#718](https://github.com/asc-community/AngouriMath/issues/71 | `"tan(x)/sqrt(2+tan(x)+tan(x)^2)".Integrate("x")` | left unevaluated | the antiderivative | | `"tan(x)^2/(2+tan(x)+tan(x)^2)^(3/2)".Integrate("x")` | left unevaluated | the antiderivative | +### A power of the variable below the bar beside a root of a quadratic + +`1/(x^2 sqrt(4 + 3x + 2x^2))` was left as written, while `1/(x sqrt(...))` came out: the single +factor was read and the repeated one was not. Under `x = 1/t` the integrand is +`-sgn(t) t^(n - 1)/sqrt(q0 t^2 + q1 t + q2)` -- a polynomial over the root of the quadratic read +the other way round, which the rules for those answer. That is what one round of parts against +`arccos(a + b x)/x^4` leaves, so the inverse trigonometric rows over a power of `x` come with it. + +The radicand is assembled rather than substituted into, as `Q(1/t)` is `R(t)/t^2` and writing the +quotient inside the root leaves a nesting nothing reduces; the modulus that leaves is a `sgn(t)` +in front, which at `t = 1/x` is `sgn(x)`. `SolveByReciprocalSubstitution` is the same substitution +for a different shape -- a palindromic quartic, where the reciprocal maps the quartic to itself -- +and reads nothing here ([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"1/(x^3*sqrt(4+3*x+2*x^2))".Integrate("x")` | left unevaluated | the antiderivative | +| `"1/(x^2*sqrt(1-(a+b*x)^2))".Integrate("x")` | left unevaluated | the antiderivative | +| `"acos(a+b*x)/x^4".Integrate("x")` | left unevaluated | the antiderivative | + ### `binomial(n, k)` is a function **Addition, not silent.** The binomial coefficient is a node, `Entity.Binomialf`, spelled diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index d3eff5ea0..dae7a7793 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -14431,6 +14431,112 @@ private static (Entity Factor, Entity Inside)? TryTakeACommonLinearFactorOfASum( return shifted.Nodes.Any(node => node == MathS.NaN) ? null : shifted; } + /// + /// A power of the variable below the bar beside a root of a quadratic, by + /// x = 1/t: 1/(x^n sqrt(q0 + q1 x + q2 x^2)) is + /// -sgn(t) t^(n - 1)/sqrt(q0 t^2 + q1 t + q2), a polynomial over the root of the + /// quadratic with its coefficients reversed, which the rules for those answer. + /// + /// + /// + /// 1/(x sqrt(1 - (a + b x)^2)) was answered and 1/(x^2 sqrt(...)) was not: + /// the repeated factor is what nothing reads, and it is what a round of parts against + /// acos(a + b x)/x^4 leaves. is the + /// same substitution for a different shape -- a palindromic quartic under the root, where + /// the reciprocal maps the quartic to itself -- and reads nothing here. + /// + /// + /// The radicand is assembled rather than substituted into: Q(1/t) is + /// R(t)/t^2 with R the reversed quadratic, so the root is + /// R^(p/2) |t|^(-p), and writing the quotient inside the root instead leaves a + /// nesting nothing downstream reduces. The modulus is a sgn(t) in front for an odd + /// p, constant between its zeros; with t = 1/x it is sgn(x), which is + /// what the answers of this family carry anyway. + /// https://github.com/asc-community/AngouriMath/issues/718 + /// + /// + internal static Entity? SolveByTheReciprocalBesideARootOfAQuadratic(Entity expr, Entity.Variable x, bool integrateByParts) + { + if (!Integration.AnsweringTheQuestionAskedOrOneBelow) + return null; + var (numerator, denominator) = Functions.SingleQuotient.Of(Functions.SingleQuotient.Combine(expr)); + // A power of x, two or more, below the bar; one root of a quadratic; polynomials else. + var powerOfX = 0; + Entity? radicand = null; + Number.Rational? rootPower = null; + Entity rest = Number.Integer.One; + foreach (var (side, isBelow) in new[] { (numerator, false), (denominator, true) }) + foreach (var factor in Mulf.LinearChildren(side)) + { + if (!factor.ContainsNode(x)) + { + rest = isBelow ? rest / factor : rest * factor; + continue; + } + switch (factor) + { + case Variable bare when bare == x && isBelow: + powerOfX += 1; + continue; + case Powf(Variable bare, Number.Integer whole) when bare == x && isBelow && whole.EInteger.Sign > 0 && whole.EInteger.CanFitInInt32(): + powerOfX += whole.EInteger.ToInt32Unchecked(); + continue; + case Powf(var inner, Number.Rational power) when power is not Number.Integer && radicand is null: + if (!power.ERational.Denominator.Equals(EInteger.FromInt32(2))) + return null; + radicand = inner; + rootPower = isBelow ? Number.Rational.Create(power.ERational.Negate()) : power; + continue; + default: + if (!TreeAnalyzer.TryGetPolynomial(factor, x, out _)) + return null; + rest = isBelow ? rest / factor : rest * factor; + continue; + } + } + if (powerOfX < 2 || radicand is null || rootPower is null) + return null; + if (!TreeAnalyzer.TryGetPolynomial(radicand, x, out var monomials) || monomials.Count == 0 + || monomials.Keys.Any(k => k.Sign < 0 || k.CompareTo(EInteger.FromInt32(2)) > 0) + || !monomials.ContainsKey(EInteger.FromInt32(2))) + return null; + if (!TreeAnalyzer.TryGetPolynomial(rest, x, out var restMonomials) || restMonomials.Keys.Any(k => k.Sign < 0)) + return null; + + var t = Variable.CreateUnique(expr, "u_recip"); + // Q(1/t) is R(t)/t^2 with R the quadratic read the other way round. + var q0 = monomials.TryGetValue(EInteger.Zero, out var m0) ? m0 : Number.Integer.Zero; + var q1 = monomials.TryGetValue(EInteger.One, out var m1) ? m1 : Number.Integer.Zero; + var q2 = monomials[EInteger.FromInt32(2)]; + var reversed = (q0 * MathS.Sqr(t) + q1 * t + q2).InnerSimplified; + if (q0.Evaled is Number.Complex { IsZero: true }) + return null; // the reversed quadratic is linear, which is the substitution's own shape + // The rest at 1/t, term by term, so that no quotient is left nested. + Entity restInT = Number.Integer.Zero; + foreach (var pair in restMonomials) + { + if (!pair.Key.CanFitInInt32()) + return null; + var degree = pair.Key.ToInt32Unchecked(); + restInT += degree == 0 ? pair.Value : pair.Value / MathS.Pow(t, Number.Integer.Create(degree)); + } + // dx = -dt/t^2, x^(-n) = t^n, and the root is R^(p/2) |t|^(-p). + var numeratorPower = Number.Integer.Create(rootPower.ERational.Numerator); + Entity integrand = -restInT * MathS.Pow(t, Number.Integer.Create(powerOfX - 2)) + * MathS.Pow(reversed, rootPower) * MathS.Pow(t, (-numeratorPower).InnerSimplified); + Entity signs = numeratorPower.EInteger.IsEven ? Number.Integer.One : MathS.Signum(t); + // Bare: the reciprocal's own `t != 0` rides along as a condition -- it is `1/x`, so + // it says what the integrand already says -- and a condition is not an integrand to + // the rules below, which decline it. + integrand = Functions.PartialFractions.Bare(integrand.InnerSimplified); + if (integrand.ContainsNode(x) || integrand.Nodes.Any(node => node == MathS.NaN)) + return null; + if (Integration.ComputeAsAQuestionOfItsOwn(integrand, t, integrateByParts) is not { } answer) + return null; + var back = (signs == Number.Integer.One ? answer : signs * answer).Substitute(t, (Number.Integer.One / x).InnerSimplified); + return back.Nodes.Any(node => node == MathS.NaN) ? null : back; + } + /// /// A pair of hyperbolic powers two apart whose coefficients kill the reduction's /// residual: cosh(y)^p - (p - 1)/p cosh(y)^(p - 2) is diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index d50b5b83e..2f7c62191 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -799,6 +799,10 @@ private static Entity Normalized(Entity expr, Entity.Variable x) => // And a root of a palindromic quartic, which is a root of a quadratic in x -+ 1/x: // Charlwood's `(1 + x^2)/((1 - x^2) sqrt(1 + x^4))` is `-du/(u sqrt(u^2 + 2))`. if ((answer = IndefiniteIntegralSolver.SolveByReciprocalSubstitution(expr, x)) is { }) return answer; + // And a power of the variable below the bar beside a root of a quadratic, by the + // same reciprocal: `1/(x^2 sqrt(Q))` is a polynomial over the root of the reversed + // quadratic, which the rules for those answer. + if ((answer = IndefiniteIntegralSolver.SolveByTheReciprocalBesideARootOfAQuadratic(expr, x, integrateByParts)) is { }) return answer; // A rational function of x and one cube root of a polynomial: no substitution // rationalises it, and the elementary ones are logarithms of `L - y` for linear L // whose cube agrees with the polynomial at the poles, found by an ansatz. diff --git a/Sources/Tests/UnitTests/Calculus/ReciprocalSubstitutionTest.cs b/Sources/Tests/UnitTests/Calculus/ReciprocalSubstitutionTest.cs index 8b2c3bbf1..43abf634c 100644 --- a/Sources/Tests/UnitTests/Calculus/ReciprocalSubstitutionTest.cs +++ b/Sources/Tests/UnitTests/Calculus/ReciprocalSubstitutionTest.cs @@ -142,5 +142,33 @@ public void WhatIsNotAFunctionOfTheReciprocalVariable(string integrand) return; // unanswered is a legitimate verdict; a wrong answer is not DifferentiatesBack(integrand); } + + /// + /// A power of the variable below the bar beside a root of a quadratic, by the same + /// reciprocal: 1/(x^n sqrt(q0 + q1 x + q2 x^2)) is + /// -sgn(t) t^(n - 1)/sqrt(q0 t^2 + q1 t + q2), a polynomial over the root of the + /// quadratic read the other way round. 1/(x sqrt(1 - (a + b x)^2)) was answered + /// and the repeated factor was not, which is what a round of parts against + /// acos(a + b x)/x^4 leaves. + /// #718 + /// + [Theory] + [InlineData("1/(x^2*sqrt(1 + x^2))")] + [InlineData("1/(x^3*sqrt(4 + 3*x + 2*x^2))")] + [InlineData("1/(x^2*sqrt(1 + x + x^2))")] + [InlineData("1/(x^4*sqrt(2 - x + x^2))")] + public void APowerOfTheVariableBesideARootOfAQuadratic(string integrand) => DifferentiatesBack(integrand); + + /// + /// And with symbols among the coefficients, which is the shape Rubi's 5.2 rows leave: + /// acos(a + b x)/x^4 by parts is 1/(x^3 sqrt(1 - (a + b x)^2)) up to the + /// constants. + /// + [Theory] + [InlineData("1/(x^2*sqrt(1 - (a + b*x)^2))")] + [InlineData("1/(x^3*sqrt(1 - (a + b*x)^2))")] + [InlineData("acos(a + b*x)/x^4")] + public void TheSameWithSymbolicCoefficients(string integrand) + => DifferentiatesBack(integrand, ("a", 0.3), ("b", 0.4)); } }