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}"); + } + } } }