From 20353a81858c9903215ddbd7c8628b82ab7dfe63 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Wed, 21 Oct 2026 22:50:51 +0000 Subject: [PATCH] Integrate a power of x beside a binomial in an odd negative power of x on both sides of 0 SolveByTakingAPowerOfXOutOfAFractionalPower took x^(j p) out of a sum of whole powers of x under a fractional power p: for sqrt(a + b/x^3)/x it wrote x^(-3/2) sqrt(a x^3 + b), which has no value at a negative x, where a + b/x^3 is real, and fourteen of Rubi's 1.1.3.2 x^m (a + b/x^3)^(k/2) were answered for a positive x only. Past an odd j other than 1 and -1, where j p is not whole, the rule now declines, and the substitution in the root answers them on both sides: sqrt(a + b/x^3)/x is a function of u = sqrt(a + b/x^3). Where j p is whole, x^(j p) is real on both sides and the rule is kept: (a x^3 + b x^6)^(5/3), Rubi's 1.2.3.2, is x^5 times a power of a + b x^3. Chain against master 2a98be40: the 14 partial answers of 1.1.3.2 are solved on both sides, and the first version's guard, on the odd power alone, lost 1.2.3.2:8; keyed on j p it is answered again, 15 of 15 alone in the probe. Part of #233. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 15 ++++ .../Integration/IndefiniteIntegralSolver.cs | 15 +++- ...inomialInAnOddNegativePowerIntegralTest.cs | 76 +++++++++++++++++++ 3 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 Sources/Tests/UnitTests/Calculus/APowerOfXOutOfABinomialInAnOddNegativePowerIntegralTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 76e155998..d03426544 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -299,6 +299,21 @@ discriminant is zero is now one linear twice ([#233](https://github.com/asc-comm | `"1/((a*g+b*g*x)^3*(A+B*ln(k*(a+b*x)/(c+d*x)))^2)".ToEntity().Integrate("x")` | no answer in twenty seconds | 644 characters, two powers of t | | `"1/(x^2*(A+B*ln(k*x))^2)".ToEntity().Integrate("x")` | `integral(...)` | 133 characters, `Ei(-(A/B + ln(k x)))`, where before this change master gave an answer with no value at any point | +### A power of x beside a binomial in an odd negative power of x is integrated on both sides of 0 + +**Answers where there were none.** `sqrt(a + b/x^3)/x` and thirteen more of Rubi's 1.1.3.2, +`x^m (a + b/x^3)^(k/2)`, were declined. `a + b/x^3` is real on both sides of 0, and they are answered +there in the variable under the root, where taking `x^(-3/2)` out of it would leave an answer for a +positive x only ([#233](https://github.com/asc-community/AngouriMath/issues/233)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"sqrt(a+b/x^3)/x".ToEntity().Integrate("x")` | `integral(...)` | 455 characters | +| `"sqrt(a+b/x^3)/x^7".ToEntity().Integrate("x")` | `integral(...)` | 101 characters | +| `"(a+b/x^3)^(3/2)*x^2".ToEntity().Integrate("x")` | `integral(...)` | 842 characters | +| `"x^5/sqrt(a+b/x^3)".ToEntity().Integrate("x")` | `integral(...)` | 831 characters | +| `"x^2/(a+b/x^3)^(3/2)".ToEntity().Integrate("x")` | `integral(...)` | 911 characters | + ### The last digits of an evaluated sine, cosine, tangent, arcsine or arctangent **Silent, and in the hundredth digit.** `EvalNumerical` at the default hundred digits gives the diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 1ad367eb0..275227b65 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -31152,12 +31152,25 @@ bool MayBeARootOfASquare(Entity node, Entity.Variable x) // minute of search to be declined, where as written it is five seconds. Past those // nothing reads the sum as written, and the factor below answers both sides: // `x/sqrt(a x + b x^4)`. An even power comes out as its sign, and a fractional one - // is real for a positive x only, as the sum already was. + // beside a power of x that is not whole is real for a positive x only, as the sum + // already was. if (lowest is Number.Integer { EInteger.IsEven: false } odd && odd.EInteger.Abs().Equals(EInteger.One) && !sum.Nodes.Any(node => node is Powf(var @base, var r) && @base == x && r is not Number.Integer) && (Mulf.LinearChildren(rest).Where(part => part.ContainsNode(x)).ToList() is not [Sumf or Minusf] || Sumf.LinearChildren(rest).All(term => PowerOfX(term, x) is (_, Number.Integer { EInteger: var degree }) && degree.CompareTo(1) <= 0 && degree.Sign >= 0))) continue; + // And x to any other odd power out of a sum of whole powers under a power p that + // leaves `x^(j p)` fractional: the sum is real on both sides of 0 and `x^(j p)` is + // not, and the answer came out for a positive x only. The substitution answers it + // on both: `sqrt(a + b/x^3)/x` is a function of `u = sqrt(a + b/x^3)`, where taking + // out `x^(-3/2)` left Rubi's 1.1.3.2 with no value at a negative x where the + // integrand is real. A whole `j p` is real on both sides, and the factor in front + // is constant on each: `(a x^3 + b x^6)^(5/3)` is `x^5` times a power of + // `a + b x^3`, Rubi's 1.2.3.2. + if (lowest is Number.Integer { EInteger.IsEven: false } oddPast && !oddPast.EInteger.Abs().Equals(EInteger.One) + && (oddPast * power).Evaled is not Number.Integer + && !sum.Nodes.Any(node => node is Powf(var @base, var r) && @base == x && r is not Number.Integer)) + continue; // The rest of the integrand a constant times the sum's derivative is the plain // substitution's, u = the sum, which answers it as a power of u, where the sum // with x taken out is a search: `(x - 3 sqrt(x))^(2/3) (2 sqrt(x) - 3)/sqrt(x)` diff --git a/Sources/Tests/UnitTests/Calculus/APowerOfXOutOfABinomialInAnOddNegativePowerIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/APowerOfXOutOfABinomialInAnOddNegativePowerIntegralTest.cs new file mode 100644 index 000000000..3a2934ed9 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/APowerOfXOutOfABinomialInAnOddNegativePowerIntegralTest.cs @@ -0,0 +1,76 @@ +// +// 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 + b/x^3 is real on both sides of 0, and taking x^(-3/2) out of its root, as + /// sqrt(a + b/x^3) = K x^(-3/2) sqrt(a x^3 + b), left an answer with no value at a negative + /// x where the integrand is real: Rubi's 1.1.3.2 x^m (a + b/x^3)^(k/2), fourteen of them. The + /// substitution under the root answers on both sides. + /// #233 + /// + [Trait("Area", "Calculus")] + public sealed class APowerOfXOutOfABinomialInAnOddNegativePowerIntegralTest + { + [Theory] + [InlineData("sqrt(a+b/x^3)/x")] + [InlineData("sqrt(a+b/x^3)/x^7")] + [InlineData("(a+b/x^3)^(3/2)*x^2")] + [InlineData("(a+b/x^3)^(3/2)/x^10")] + [InlineData("x^5/sqrt(a+b/x^3)")] + [InlineData("x^2/(a+b/x^3)^(3/2)")] + public void DifferentiatesBackOnBothSidesOfZero(string integrand) + { + var integral = integrand.ToEntity().Integrate("x"); + Assert.DoesNotContain("integral(", integral.Stringize()); + Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.7); + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pinned(integrand.ToEntity()); + // a + b/x^3 is positive below the root of -b/a, about -0.81, and above 0. + foreach (var at in new[] { -2.3, -1.7, -1.1, 0.4, 1.3, 2.2 }) + { + var want = original.Substitute("x", at).EvalNumerical(); + var got = derivative.Substitute("x", at).EvalNumerical(); + Assert.False(got.IsNaN, $"d/dx of the antiderivative of {integrand} has no value at x = {at}, where the integrand is {want}"); + 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}"); + } + } + + /// + /// And an odd power taken out where j p is whole, which is real on both sides: Rubi's + /// 1.2.3.2 (a x^3 + b x^6)^(5/3) is x^5 times a power of a + b x^3, and a + /// guard on the odd power alone declined it. + /// + [Fact] + public void TakesAnOddPowerOutWhereItsPowerIsWhole() + { + const string integrand = "(a*x^3+b*x^6)^(5/3)"; + var integral = integrand.ToEntity().Integrate("x"); + Assert.DoesNotContain("integral(", integral.Stringize()); + Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.7); + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pinned(integrand.ToEntity()); + // a x^3 + b x^6 is positive below the root of -a/b, about -1.23, and above 0. + foreach (var at in new[] { -2.3, -1.7, -1.4, 0.4, 1.3, 2.2 }) + { + var want = original.Substitute("x", at).EvalNumerical(); + var got = derivative.Substitute("x", at).EvalNumerical(); + Assert.False(got.IsNaN, $"d/dx of the antiderivative of {integrand} has no value at x = {at}, where the integrand is {want}"); + 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}"); + } + } + } +}