From 2961a978e94e417016f5b54af0f053cf362c7039 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sun, 4 Oct 2026 13:54:49 +0000 Subject: [PATCH] A half-odd power of a constant over a polynomial of either sign is integrated with its sign Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 16 ++++++ .../Integration/IndefiniteIntegralSolver.cs | 38 +++++++++++++ .../Integration/Integration.Definition.cs | 3 + .../HalfOddPowerOfAReciprocalIntegralTest.cs | 55 +++++++++++++++++++ 4 files changed, 112 insertions(+) create mode 100644 Sources/Tests/UnitTests/Calculus/HalfOddPowerOfAReciprocalIntegralTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 4d47c7700..024ce1e77 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -249,6 +249,22 @@ Rubi's `x^m (a + b x^n)^p` and `(a + b x^n)^p (c + d x^n)^q` | `"1/(2+3/x^2)^3".Integrate("x")`, `1/(a + b/x^3)` | left unevaluated | an antiderivative | | `"(a+b*x^n)*(c+d*x^n)^3".Integrate("x")` | left unevaluated | written out, eight powers of `x` integrated: `a c^3 x + ... + b d^3 x^(4 n + 1)/(4 n + 1)` | +### A half-odd power of a constant over a polynomial of either sign is integrated with its sign + +**Answers where there were none.** `sqrt(1/(1 - x^2))`, the arcsine's derivative where it is real, +was declined, with every half-odd power of a constant over a polynomial whose sign changes: the +reading that writes a power of a quotient apart does so only where the denominator is positive. For +a real `Q` other than zero, `(A/Q)^r` is `(A/Q)^r Q^r` times `Q^(-r)`, the first factor's derivative +being zero wherever it has one, and for a positive number `A` that factor is `A^r sgn(Q)`. The power +is written so, after the substitutions, and the factor goes in front of the answer +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"sqrt(1/(1 - x^2))".ToEntity().Integrate("x")` | `integral(...)` | `sgn(1 - x^2) arcsin(x)` | +| `"(1/(1 - x^2))^(3/2)".ToEntity().Integrate("x")` | `integral(...)` | `sgn(1 - x^2) x/sqrt(1 - x^2)` | +| `"(c/(a + b*x^2))^(3/2)".ToEntity().Integrate("x")` | `integral(...)` | `(c/(a + b x^2))^(3/2) (a + b x^2)^(3/2) x/(a sqrt(a + b x^2))` | + ### A whole power of a quotient with a symbol in it is integrated as the quotient of the powers **Answers where there were none, and a slowdown since 2.5.0 undone.** `(c/(a + c x^2))^2` was diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index f072a2d88..f82114626 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -16454,6 +16454,44 @@ Entity Without(Entity side) return answer; } + /// + /// A half-odd power of a constant over a polynomial whose sign changes, among the factors + /// of the integrand, written apart with the factor that keeps it right on both sides of the + /// polynomial's zeros: for a real Q other than zero, (A/Q)^r is + /// K Q^(-r) with K = (A/Q)^r Q^r, whose derivative is zero wherever it has one, + /// and for a positive number A that is A^r times the sign of Q, the + /// root of a negative being i times the root of its modulus on both sides of the bar. + /// + /// + /// sqrt(1/(1 - x^2)) is sgn(1 - x^2)/sqrt(1 - x^2), the arcsine's derivative + /// where it is real, and was declined, with every such power over a polynomial that is not + /// positive: the reading that writes a power of a quotient apart does so only where it is. + /// Late in the chain, after the substitutions, which answer some of these without the + /// factor -- x sqrt(1/(4 - x^2)) is -(1/(4 - x^2))^(-1/2) -- and asked as the + /// same question, as the sign taken out above is. + /// https://github.com/asc-community/AngouriMath/issues/718 + /// + internal static Entity? SolveByWritingAHalfOddPowerOfAReciprocalWithItsSign(Entity expr, Entity.Variable x, bool integrateByParts) + { + foreach (var (factor, underneath) in FactorsOfTheIntegrand(expr)) + { + if (underneath || factor is not Powf(var @base, Number.Rational exponent) + || !exponent.ERational.Denominator.Equals(EInteger.FromInt32(2)) || !@base.ContainsNode(x)) + continue; + var (above, below) = Functions.SingleQuotient.Of(@base); + if (below == Number.Integer.One || above.ContainsNode(x) || !below.ContainsNode(x) + || !TreeAnalyzer.TryGetPolynomial(below, x, out var read) || read.Count == 0 + || read.Keys.Max()!.CompareTo(EInteger.FromInt32(4)) > 0 || IsPositiveForReal(below, x)) + continue; + var positive = above.Evaled is Number.Real { IsPositive: true }; + var inFront = positive ? MathS.Signum(below) : factor * MathS.Pow(below, exponent); + var replacement = positive ? MathS.Pow(above, exponent) * MathS.Pow(below, -exponent) : MathS.Pow(below, -exponent); + var written = expr.Replace(node => node == factor ? replacement : node); + return Integration.ComputeAsTheSameQuestion(written, x, integrateByParts) is { } answer ? inFront * answer : null; + } + return null; + } + /// /// x^(n - 1) g(x^n) with a symbolic n, which is g(u)/n under /// u = x^n: the power in front is the derivative of the power inside up to the diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 55108cc56..ad0f035a0 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -1113,6 +1113,9 @@ private static Entity Normalized(Entity expr, Entity.Variable x) => // A rational function with symbols in it beside a root of a linear, split into partial // fractions first, each term the question of one factor beside the root. if ((answer = IndefiniteIntegralSolver.SolveARationalFunctionBesideARootOfALinearSplitFirst(expr, x, integrateByParts)) is { }) return answer; + // A half-odd power of a constant over a polynomial of either sign, written apart with + // its sign in front. After the substitutions, which answer some of these without it. + if ((answer = IndefiniteIntegralSolver.SolveByWritingAHalfOddPowerOfAReciprocalWithItsSign(expr, x, integrateByParts)) is { }) return answer; // The sign of a real-valued factor is constant between its zeros, and goes in // front of the antiderivative of the rest. After every rule that reads the sign // where it stands: `cos(x) sgn(sin(x))` is `|sin(x)|` by the substitution, and diff --git a/Sources/Tests/UnitTests/Calculus/HalfOddPowerOfAReciprocalIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/HalfOddPowerOfAReciprocalIntegralTest.cs new file mode 100644 index 000000000..2c61d7e59 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/HalfOddPowerOfAReciprocalIntegralTest.cs @@ -0,0 +1,55 @@ +// +// 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 half-odd power of a constant over a polynomial whose sign changes, written apart with the + /// factor that keeps it right on both sides of the polynomial's zeros: (A/Q)^r is + /// (A/Q)^r Q^r times Q^(-r), and for a positive number A that factor is + /// A^r sgn(Q). sqrt(1/(1 - x^2)), the arcsine's derivative where it is real, was + /// declined. + /// #718 + /// + /// + /// Checked by differentiating back with a = 2.3, b = -0.7, c = 1.3, so that + /// a + b x^2 changes sign as well, at points on both sides of every denominator's zeros; + /// where the denominator is negative the integrand is imaginary, and the two are compared as + /// complex numbers. + /// + [Trait("Area", "Calculus")] + public sealed class HalfOddPowerOfAReciprocalIntegralTest + { + [Theory] + [InlineData("sqrt(1/(1 - x^2))")] + [InlineData("sqrt(1/(x^2 - 1))")] + [InlineData("(1 - x^2)*sqrt(1/(2 - x^2))")] + [InlineData("(1/(1 - x^2))^(3/2)")] + [InlineData("(c/(a + b*x^2))^(3/2)")] + [InlineData("x^2*(c/(a + b*x^2))^(3/2)")] + public void IsWrittenApartWithItsSign(string integrand) + { + var integral = integrand.ToEntity().Integrate("x"); + Assert.DoesNotContain("integral(", integral.Stringize()); + Entity Pinned(Entity e) => e.Substitute("a", 2.3).Substitute("b", -0.7).Substitute("c", 1.3); + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pinned(integrand.ToEntity()); + foreach (var at in new[] { -2.4, -1.6, -0.6, 0.6, 1.6, 2.4 }) + { + var want = original.Substitute("x", at).EvalNumerical(); + var got = derivative.Substitute("x", at).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) + Math.Abs((double)want.ImaginaryPart)), + $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}"); + } + } + } +}