From 2309b7472c36fb47ab89859681c998ca7f2299b5 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Wed, 21 Oct 2026 04:30:41 +0000 Subject: [PATCH] Integrate six seams of the independent suites - A logarithmic part past the twelfth degree, up to the sixteenth, where every residue lies in a field of degree at most two (Hearn 365), with a mod-p screen that leaves before factoring a resultant whose factor of degree three or more shows modulo a prime. - e^(x + e^x) kept whole as a substitution's candidate (Hebisch 15). - The root of a real square beside a constant off the real line over a power of a real linear, its sign kept on the real line (Welz 44). - A rational function times sqrt(c x^2 + d sqrt(P))/sqrt(P), P = a + b x^4, c^2 = b d^2, as two roots of quadratics (Welz 47, 48). - A symbolic power inside a reciprocal of a root (Timofeev 448). - A rational function of x and ln(x) over distinct linears in ln(x), split into partial fractions in ln(x) (Bronstein 22). Rubi's 3.5:364 is answered too. The moves re-run alone go from 3 to 9 answered; 3.2.1:292 and 3.5:37, answered on both builds in 21-34 s, time out under load and allocate the same on both at a 60 s budget. Part of #233. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 32 ++ .../Integration/IndefiniteIntegralSolver.cs | 280 +++++++++++++++++- .../Integration/Integration.Definition.cs | 6 + .../Continuous/Integration/RothsteinTrager.cs | 55 +++- ...SeamsOfTheIndependentSuitesIntegralTest.cs | 86 ++++++ 5 files changed, 448 insertions(+), 11 deletions(-) create mode 100644 Sources/Tests/UnitTests/Calculus/SeamsOfTheIndependentSuitesIntegralTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 70d3eb7b5..7a30f892e 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -1386,6 +1386,38 @@ with a 107-bit leading coefficient, which needs 2^535. Up to degree six the boun | `"x^5/(216+108*x^2+324*x^3+18*x^4+x^6)^2".ToEntity().Integrate("x")` | `integral(...)` | 377 characters | | `"x^2/(216+108*x^2+324*x^3+18*x^4+x^6)^2".ToEntity().Integrate("x")` | `integral(...)` | 394 characters | +### Logarithms past the twelfth degree with residues in a quadratic field, and five more shapes from the independent suites, are integrated + +**Answers where there were none.** Seven problems of the Bronstein, Hearn, Hebisch, Timofeev and Welz suites were +declined or ran out of time ([#233](https://github.com/asc-community/AngouriMath/issues/233)): +- A logarithmic part past the twelfth degree, up to the sixteenth, where every residue lies in a field of degree at most + two. Hearn's `(3 + 3 x - ... + 7 x^13)/(1 + 2 x - ... + x^14)` is over `P^2 - 2 Q^2` for `P = x^7 - x - 1` and + `Q = x + x^2`, irreducible over the rationals, and its residues are the roots of `4 t^2 - 4 t - 1`: two logarithms of + `P ± sqrt(2) Q`. Where a factor of the resultant of degree three or more shows modulo a prime, the path is left + before the factorization. +- `e^(x + e^x)` is kept whole as a substitution's candidate: Hebisch's `(1 + e^x) e^(x + e^x)/(x + e^x)` is `e^u/u` + under `u = x + e^x`, which is `Ei(x + e^x)`. +- The root of a real square beside a constant off the real line, over a power of a real linear. The quadratic keeps the + sign of its imaginary part on the real line, so the root and the logarithm of the artanh's argument are continuous + there: Welz's `1/((1 + x)^2 sqrt(2) sqrt(x^2 - i)) + 1/((1 + x)^2 sqrt(2) sqrt(x^2 + i))`. +- A rational function times `sqrt(c x^2 + d sqrt(P))/sqrt(P)`, `P = a + b x^4` with `c^2 = b d^2`. On the real line that + is `(d/sqrt(2)) (1/sqrt(c x^2 - i d sqrt(a)) + 1/sqrt(c x^2 + i d sqrt(a)))`, two roots of quadratics: Welz's + `sqrt(x^2 + sqrt(1 + x^4))/((1 + x) sqrt(1 + x^4))` and its twin over `(1 + x)^2`. +- A symbolic power inside a reciprocal of a root: Timofeev's `1/((1 + x^(2n)) sqrt((1 + x^(2n))^(1/n) - x^2))` is + `arctan(x/sqrt((1 + x^(2n))^(1/n) - x^2))`. +- A rational function of `x` and `L = ln(x)` over distinct linears in `L`, split into partial fractions in `L`: + Bronstein's `(2 L^2 - L - x^2)/(L^3 - x^2 L)` is `1/L + ((x - 1)/(2 x))/(L - x) + ((x + 1)/(2 x))/(L + x)`, the + logarithmic integral and two logarithms. Only for two linears or more, and only where nothing answers it whole. + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"(3+3*x-4*x^2-4*x^3-7*x^6+4*x^7+10*x^8+7*x^13)/(1+2*x-x^2-4*x^3-2*x^4-2*x^7-2*x^8+x^14)".ToEntity().Integrate("x")` | no answer in twenty seconds | 189 characters | +| `"(1+e^x)*e^(x+e^x)/(x+e^x)".ToEntity().Integrate("x")` | `integral(...)` | `Ei(x + e^x)` | +| `"1/((1+x)^2*sqrt(2)*sqrt(-i+x^2))+1/((1+x)^2*sqrt(2)*sqrt(i+x^2))".ToEntity().Integrate("x")` | `integral(...)` | 494 characters | +| `"sqrt(x^2+sqrt(1+x^4))/((1+x)*sqrt(1+x^4))".ToEntity().Integrate("x")` | `integral(...)` | 333 characters | +| `"1/((1+x^(2*n))*(-x^2+(1+x^(2*n))^(1/n))^(1/2))".ToEntity().Integrate("x")` | `integral(...)` | 78 characters | +| `"(-x^2-ln(x)+2*ln(x)^2)/(-x^2*ln(x)+ln(x)^3)".ToEntity().Integrate("x")` | `integral(...)` | 55 characters | + ### A rational function past the twelfth degree is reduced by Hermite's method **Answers where there were none.** The integrator read a denominator of degree twelve at most, so a derivative over diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 993932131..7473aeaf3 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -14621,11 +14621,17 @@ is not var (algebraic, ofTheLogarithm, ofTheThirdKind)) // A square below the root is the modulus of its root, which the rules for it take; and // each closed form is chosen by the sign of a quantity, which a number off the real // line does not have. Without the linear, and with a first power of it and numbers - // only, the rules further down answer: see the remarks. - if (quadratic is null || HoldsTheImaginaryUnit(expr) || CoefficientsBesideARoot(quadratic, x) is not { Length: 3 } q + // only, the rules further down answer: see the remarks. Except a real square beside a + // constant off the real line, and a real linear: see IsARealSquareBesideAConstantOffTheRealLine. + if (quadratic is null || CoefficientsBesideARoot(quadratic, x) is not { Length: 3 } q + || HoldsTheImaginaryUnit(expr) && !IsARealSquareBesideAConstantOffTheRealLine(expr, q, x) || IsZeroOverTheSymbols(q[2]) || IsZeroOverTheSymbols(q[1] * q[1] - 4 * q[0] * q[2]) || ReadBesideSquareRoots(expr, x, quadratic) is not var (halves, above, constantBelow, third, order) - || order == 0 || order == 1 && !quadratic.Vars.Concat(third!.Vars).Any(symbol => symbol != x)) + || order == 0) + return null; + var offTheRealLine = HoldsTheImaginaryUnit(expr); + if (offTheRealLine ? HoldsTheImaginaryUnit(third!) || HoldsTheImaginaryUnit(constantBelow) + : order == 1 && !quadratic.Vars.Concat(third!.Vars).Any(symbol => symbol != x)) return null; // Q^(m/2) is Q^((m + 1)/2) over its root, and below -1 a power of Q below the bar @@ -14639,7 +14645,9 @@ is not var (algebraic, ofTheLogarithm, ofTheThirdKind)) ALinearBesideTheRoot? pole = null; if (third is { }) { - if (!TreeAnalyzer.TryGetPolyLinear(third, x, out var h, out var g) || IsZeroOverTheSymbols(h)) + // Off the real line, a power of x below is the substitutions' in x^2, which answer it. + if (!TreeAnalyzer.TryGetPolyLinear(third, x, out var h, out var g) || IsZeroOverTheSymbols(h) + || offTheRealLine && IsZeroOverTheSymbols(g)) return null; // h^2 Q at the linear's root; where that is zero, h Q' there, which is not, since // the root is a simple one. @@ -14669,14 +14677,39 @@ is not var (algebraic, ofTheFirstKind, ofTheThirdKind)) // size where that is negative, and the logarithm of (1 + it)/(1 - it) is real, and // more where it is positive, and the logarithm of (it + 1)/(it - 1) is. Entity Logarithm(Entity ratio) => -ofTheThirdKind * MathS.Ln(ratio) / (2 * MathS.Sqrt(atThePole[0])); - answer = answer + BySigns(atThePole[0], q1 * q1 - 4 * q0 * q2, + answer = answer + (offTheRealLine ? Logarithm((twiceTheRoot + z) / (twiceTheRoot - z)) : BySigns(atThePole[0], q1 * q1 - 4 * q0 * q2, Logarithm((z + twiceTheRoot) / (z - twiceTheRoot)), Logarithm((twiceTheRoot + z) / (twiceTheRoot - z)), - ofTheThirdKind * MathS.Arctan(z / (2 * MathS.Sqrt(-atThePole[0]) * root)) / MathS.Sqrt(-atThePole[0])); + ofTheThirdKind * MathS.Arctan(z / (2 * MathS.Sqrt(-atThePole[0]) * root)) / MathS.Sqrt(-atThePole[0]))); } return (constantBelow == Number.Integer.One ? answer : answer / constantBelow).InnerSimplified; } + /// + /// Whether the quadratic with coefficients is a real square beside a + /// constant off the real line, q2 x^2 + q1 x + q0 with q2 > 0 and q1 + /// real numbers and q0 a number that is not real, in with no + /// symbol but . + /// + /// + /// There, with s = x + q1/(2 q2) and c = q0/q2 - q1^2/(4 q2^2), which is not + /// real, Q = q2 (s^2 + c) keeps the sign of its imaginary part on the whole real line, + /// so its root and ln(2 sqrt(q2) S + 2 q2 x + q1) are continuous there. And beside a real + /// linear with its root at s0, 1 - w^2 for the artanh's argument w is + /// c/((s0^2 + c)(s^2 + c)) times a square: for c = r e^(i theta) each factor + /// below has its argument between 0 and theta, so that of the quotient lies in + /// [-theta, theta), and w never reaches the artanh's cuts. The logarithm of + /// (1 + w)/(1 - w) is then continuous on either side of the linear's root, whatever + /// the phase of H, and its derivative needs only sqrt(z)^2 = z. Welz's + /// 1/((1 + x)^2 sqrt(x^2 + i)) was declined for the sign of H = 1 + i. + /// https://github.com/asc-community/AngouriMath/issues/233 + /// + private static bool IsARealSquareBesideAConstantOffTheRealLine(Entity expr, Entity[] q, Entity.Variable x) + => expr.Vars.All(symbol => symbol == x) + && q[2].Evaled is Number.Real { IsPositive: true } + && q[1].Evaled is Number.Real + && q[0].Evaled is Number.Complex constant && !constant.ImaginaryPart.EDecimal.IsZero; + /// /// as one quotient beside the square roots of /// : the power of each root counted in halves, above the bar @@ -16764,6 +16797,77 @@ static bool IsRationalInBoth(Entity e, Entity.Variable x, Entity.Variable L) return null; } + /// + /// A rational function of x and L = ln(x) whose denominator is a product of + /// distinct linears in L over the rational functions of x, by partial fractions + /// in L: N/D = sum N(r)/(D'(r) (L - r)) over the roots r in L, and + /// each term a question of its own. + /// + /// + /// + /// Bronstein's (2 L^2 - L - x^2)/(L^3 - x^2 L) is 1/L + ((x - 1)/(2x))/(L - x) + + /// ((x + 1)/(2x))/(L + x), the logarithmic integral and the logarithmic derivatives of + /// L - x and L + x, each of which is answered; written whole it was declined, + /// the tower ansatz writing no logarithmic integral. The identity is one in the field of + /// rational functions of x, since x and ln(x) are algebraically independent, and the + /// answer is differentiated back at sampled points all the same. + /// + /// + /// Last of all, at the question asked only, and only for two roots or more: one linear in + /// L is the substitutions' and the parts', and nothing they answered is taken from them. + /// https://github.com/asc-community/AngouriMath/issues/233 + /// + /// + internal static Entity? SolveARationalFunctionOfTheLogarithmByPartialFractionsInIt(Entity expr, Entity.Variable x, bool integrateByParts) + { + if (!Integration.AnsweringTheQuestionAsked) + return null; + var logarithm = MathS.Ln(x); + if (!expr.ContainsNode(logarithm)) + return null; + var L = Variable.CreateUnique(expr, "L_fractions"); + var inL = expr.Substitute(logarithm, L); + if (!IsARationalFunction(inL, x) || !IsARationalFunction(inL, L)) + return null; + var (numerator, denominator) = Functions.SingleQuotient.Of(Functions.SingleQuotient.Combine(inL)); + if (!TreeAnalyzer.TryGetPolynomial(numerator, L, out var above) || !TreeAnalyzer.TryGetPolynomial(denominator, L, out var below) + || below.Keys.Max() is not { } degree || degree.CompareTo(EInteger.FromInt32(2)) < 0 + || above.Keys.Max() is not { } degreeAbove || degreeAbove.CompareTo(degree) >= 0 || above.Keys.Concat(below.Keys).Any(power => power.Sign < 0)) + return null; + // Its roots in L, one linear factor each. + if (MathS.Polynomials.Factor(denominator, L) is not { } factored) + return null; + var roots = new List(); + foreach (var factor in Mulf.LinearChildren(factored)) + { + if (!factor.ContainsNode(L)) + continue; + if (!TreeAnalyzer.TryGetPolyLinear(factor, L, out var slope, out var offset) || slope.ContainsNode(L) || offset.ContainsNode(L) + || VanishesIdentically(slope)) + return null; + var root = (-offset / slope).InnerSimplified; + if (roots.Any(other => VanishesIdentically(other - root))) + return null; + roots.Add(root); + } + if (!degree.Equals(EInteger.FromInt32(roots.Count))) + return null; + var derivative = denominator.Differentiate(L); + Entity answer = Number.Integer.Zero; + foreach (var root in roots) + { + var top = numerator.Substitute(L, root).InnerSimplified; + var bottom = derivative.Substitute(L, root).InnerSimplified; + var residue = Functions.PolynomialGcd.TryCancel(top, bottom, out var cancelled) + ? Functions.PartialFractions.Bare(cancelled) : top / bottom; + if (Integration.ComputeAsAQuestionOfItsOwn(residue / (logarithm - root).InnerSimplified, x, integrateByParts) is not { } part + || part.Nodes.Any(node => node is Integralf)) + return null; + answer += Functions.PartialFractions.Bare(part); + } + return Functions.PartialFractions.DerivativeHoldsAtSampledPoints(answer, expr, x) ? answer.InnerSimplified : null; + } + /// The largest power of the logarithm the tower ansatz reads or tries. private const int MaximumTowerDegree = 4; @@ -25792,13 +25896,53 @@ internal static bool IsAPerfectSquareDiscriminant(Entity a, Entity b, Entity c) return null; var a = terms.FirstOrDefault(t => !t.ContainsNode(x)); var power = terms.FirstOrDefault(t => t.ContainsNode(x)) is { } t2 && ConstantTimesAWholePowerOfX(t2) is var (_, n) ? n : null; - if (a is null || power is null) - return null; + if (a is null) + return null; + // A symbolic power n, beside the binomial to the power its root is written with, + // which is 2/n by value: Timofeev's `1/((1 + x^(2n)) sqrt((1 + x^(2n))^(1/n) - x^2))`. + if (power is null) + return ASymbolicPowerOfX(terms.First(t => t.ContainsNode(x))) is { } symbolic + && SquareOfXPlusAPowerOf(sum, outer) is var (cSymbolic, rootPower) + && Functions.PartialFractions.IsZeroAsAValue(rootPower * symbolic - 2) + ? InW(1 / a, cSymbolic, x / MathS.Pow(sum, half)) + : null; if (SquareOfXPlusMultipleOfRoot(sum, outer, Number.Rational.Create(2, power.EInteger)) is not var (c, _)) return null; return InW(1 / a, c, x / MathS.Pow(sum, half)); } + // k x^n with n free of x and no number: n. + Entity? ASymbolicPowerOfX(Entity term) + { + Entity? n = null; + foreach (var part in Mulf.LinearChildren(term)) + { + if (!part.ContainsNode(x)) + continue; + if (n is { } || part is not Powf(var bx, var e) || bx != x || e.ContainsNode(x) || e is Number) + return null; + n = e; + } + return n; + } + + // c x^2 + d R^e, with R as given and e free of x: (c, e). + (Entity C, Entity Power)? SquareOfXPlusAPowerOf(Entity sum, Entity root) + { + Entity? c = null, e = null; + foreach (var term in Sumf.LinearChildren(sum)) + { + var withX = Mulf.LinearChildren(term).Where(part => part.ContainsNode(x)).ToList(); + if (ConstantTimesAWholePowerOfX(term) is var (k, two) && two.EInteger.Equals(EInteger.FromInt32(2))) + c = c is null ? k : null; + else if (withX is [Powf(var b, var p)] && b == root && !p.ContainsNode(x)) + e = e is null ? p : null; + else + return null; + } + return c is null || e is null ? null : (c, e); + } + // sqrt(a x^2 + b x sqrt(Q))/(x sqrt(Q)), Q = c + d x^2, a^2 = b^2 d, b^2 c + a = 0: // sqrt(2) b/a K(a x + b sqrt(Q)), K' = 1/sqrt(1 + t^2/a). Entity? AProductRootOverTheRootInsideIt(List three) @@ -25952,6 +26096,118 @@ static Entity Lowest(Entity constant) } } + /// + /// A rational function times sqrt(c x^2 + d sqrt(P))/sqrt(P), P = a + b x^4 with + /// c^2 = b d^2, written over two conjugate roots: on the real line it is + /// (d/sqrt(2)) (1/sqrt(c x^2 - i d sqrt(a)) + 1/sqrt(c x^2 + i d sqrt(a))), and each of + /// the two is a question the rules for a root of a quadratic answer. + /// + /// + /// + /// With alpha = (c x^2 + i d sqrt(a))/2, d^2 P = c^2 x^4 + d^2 a = 4 |alpha|^2, so + /// d sqrt(P) = 2 |alpha| for d > 0, and (sqrt(alpha) + sqrt(conj alpha))^2 = + /// c x^2 + 2 |alpha|: the sum is twice the real part of a principal root, not negative, and + /// is the outer root. And sqrt(P) = 2 sqrt(alpha) sqrt(conj alpha)/d. Both for real x + /// and real c, with a and d positive, which is where this is asked, and the + /// answer is differentiated back at real points before it is returned. + /// + /// + /// After , which answers a constant beside the + /// roots in closed form. Welz's sqrt(x^2 + sqrt(1 + x^4))/((1 + x)^2 sqrt(1 + x^4)) was + /// declined, with numbers only, as here. + /// https://github.com/asc-community/AngouriMath/issues/233 + /// + /// + internal static Entity? SolveANestedRootOverTheRootInsideItAsTwoConjugateRoots(Entity expr, Entity.Variable x, bool integrateByParts) + { + if (expr.Vars.Any(symbol => symbol != x) || HoldsTheImaginaryUnit(expr)) + return null; + var half = Number.Rational.Create(1, 2); + // The outer root, the one square root with a square root of x inside it. + Powf? outer = null; + foreach (var node in expr.Nodes) + if (node is Powf(var sum, var exponent) root && exponent == half && sum.Nodes.Any(inside => inside is Powf(var b, var p) && p == half && b.ContainsNode(x))) + { + if (outer is { } && outer != root) + return null; + outer = root; + } + if (outer is null) + return null; + // c x^2 + d sqrt(P), P = a + b x^4. + Entity? c = null, d = null, inner = null; + foreach (var term in Sumf.LinearChildren(outer.Base)) + { + var root = term.Nodes.OfType().FirstOrDefault(p => p.Exponent == half && p.Base.ContainsNode(x)); + if (root is null) + { + if (c is { } || !TreeAnalyzer.TryGetPolynomial(term, x, out var square) || square.Count != 1 + || !square.TryGetValue(EInteger.FromInt32(2), out var coefficient) || coefficient.ContainsNode(x)) + return null; + c = coefficient; + continue; + } + var others = Mulf.LinearChildren(term).ToList(); + if (!others.Remove(root)) + return null; + Entity multiple = Number.Integer.One; + foreach (var factor in others) + multiple = multiple == Number.Integer.One ? factor : multiple * factor; + if (d is { } || multiple.ContainsNode(x)) + return null; + d = multiple; + inner = root.Base; + } + if (c is null || d is null || inner is null || !TreeAnalyzer.TryGetPolynomial(inner, x, out var quartic) || quartic.Count != 2 + || !quartic.TryGetValue(EInteger.Zero, out var a) || !quartic.TryGetValue(EInteger.FromInt32(4), out var b) + || a.Evaled is not Number.Real { IsPositive: true } || d.Evaled is not Number.Real { IsPositive: true } || c.Evaled is not Number.Real + || !Functions.PartialFractions.IsZeroAsAValue(c * c - b * d * d)) + return null; + // The rest a rational function: the integrand with the two roots named. + var nestedName = Variable.CreateUnique(expr, "n_nested"); + var innerName = Variable.CreateUnique(expr + nestedName, "r_nested"); + // The outer root first, since a replacement reaches the leaves before the nodes above them. + var named = expr.Replace(node => node == outer ? nestedName : node).Replace(node => + node is Powf(var radicand, var p) && radicand == inner && (p == half || p == -half) ? p == half ? innerName : MathS.Pow(innerName, -1) : node); + // One of each taken off its side of the bar, since a symbol over itself survives the + // inner simplification. + var (above, below) = Functions.SingleQuotient.Of(named); + var aboveFactors = Mulf.LinearChildren(above).ToList(); + var belowFactors = Mulf.LinearChildren(below).ToList(); + var nestedAt = aboveFactors.IndexOf(nestedName); + if (nestedAt < 0) + return null; + aboveFactors.RemoveAt(nestedAt); + var innerAt = belowFactors.IndexOf(innerName); + if (innerAt >= 0) + belowFactors.RemoveAt(innerAt); + else if (aboveFactors.IndexOf(MathS.Pow(innerName, -1)) is var reciprocalAt and >= 0) + aboveFactors.RemoveAt(reciprocalAt); + else + return null; + Entity rest = Number.Integer.One; + foreach (var factor in aboveFactors) + rest = rest == Number.Integer.One ? factor : rest * factor; + foreach (var factor in belowFactors) + rest = rest / factor; + // With x below the bar: a polynomial beside the roots is the substitutions', in x^2 for + // `x sqrt(x^2 + sqrt(1 + x^4))/sqrt(1 + x^4)`, which answer it as it is written. + if (rest.ContainsNode(nestedName) || rest.ContainsNode(innerName) || !IsARationalFunction(rest, x) + || !Functions.SingleQuotient.Of(rest).Denominator.ContainsNode(x)) + return null; + var imaginary = (Number.Complex.ImaginaryOne * d * MathS.Sqrt(a)).InnerSimplified; + Entity answer = Number.Integer.Zero; + foreach (var conjugate in new[] { c * MathS.Pow(x, 2) - imaginary, c * MathS.Pow(x, 2) + imaginary }) + { + if (Integration.ComputeAsAQuestionOfItsOwn(rest / MathS.Sqrt(conjugate), x, integrateByParts) is not { } part + || part.Nodes.Any(node => node is Integralf)) + return null; + answer += Functions.PartialFractions.Bare(part); + } + answer = (d / MathS.Sqrt(2) * answer).InnerSimplified; + return Functions.PartialFractions.DerivativeHoldsAtSampledPoints(answer, expr, x) ? answer : null; + } + /// /// A square root of a perfect square in is the modulus: /// sqrt(x^2) is |x|, which for a real x is sgn(x) x, and @@ -39233,11 +39489,19 @@ private static bool IsAFunctionOfTheTangentAlone(Entity expr, Entity.Variable u) // only: `e^(e^x) e^x` arrives as `e^(e^x + x)`, in which `e^x` is a candidate whose // derivative divides nothing, and written apart it is `e^u du`. Where two or more // terms of the exponent hold x; a constant term is left where it is. + // And not where the exponent is written outside the exponential too, which makes it the + // candidate: Hebisch's `(1 + e^x) e^(x + e^x)/(x + e^x)` is `e^u/u du` under `u = x + e^x`, + // and apart, `e^x e^(e^x)` is nothing in u. https://github.com/asc-community/AngouriMath/issues/233 + var asWritten = expr; + Entity? besideTheExponentials = null; expr = expr.Replace(node => { if (node is not Powf(var @base, var exponent) || exponent is not (Sumf or Minusf) || @base.ContainsNode(x) || Sumf.LinearChildren(exponent).Count(term => term.ContainsNode(x)) < 2) return node; + besideTheExponentials ??= asWritten.Replace(written => written is Powf(var constant, Sumf or Minusf) && !constant.ContainsNode(x) ? Number.Integer.One : written); + if (besideTheExponentials.ContainsNode(exponent) && !IsOnlyAPartialSum(besideTheExponentials, exponent)) + return node; Entity product = Number.Integer.One; foreach (var term in Sumf.LinearChildren(exponent)) product = product == Number.Integer.One ? MathS.Pow(@base, term) : product * MathS.Pow(@base, term); diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 1f6e0233e..4a42981b0 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -905,6 +905,9 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr) // Four nested roots with a closed form: `sqrt(1 + sqrt(1 - x^2))`, // `sqrt(x^2 + sqrt(1 + x^4))/sqrt(1 + x^4)` and the two beside them in Rubi's 1.3.3. if ((answer = IndefiniteIntegralSolver.SolveANestedRootByItsClosedForm(expr, x, integrateByParts)) is { }) return answer; + // And the second of them beside a rational function, over the two conjugate roots it is + // on the real line: `sqrt(x^2 + sqrt(1 + x^4))/((1 + x) sqrt(1 + x^4))`. + if ((answer = IndefiniteIntegralSolver.SolveANestedRootOverTheRootInsideItAsTwoConjugateRoots(expr, x, integrateByParts)) is { }) return answer; // And any power of a square in any power of x, as the power of its root times a factor // constant where the root is not zero: `x^2 (a^2 + 2 a b x^3 + b^2 x^6)^p`. if ((answer = IndefiniteIntegralSolver.SolveByWritingAPowerOfASquareAsAPowerOfItsRoot(expr, x, integrateByParts)) is { }) return answer; @@ -1520,6 +1523,9 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr) // asked without the division's condition that the power is not zero: last, where nothing // has answered it with the condition, `x^2/((1 + x^2) (a + b x + c x^2)^(3/2))`. if ((answer = IndefiniteIntegralSolver.SolveByDividingWithoutTheConditionsOfTheDenominator(expr, x, integrateByParts)) is { }) return answer; + // A rational function of x and ln(x) over distinct linears in the logarithm, by partial + // fractions in it, where nothing has answered it whole: `(2 L^2 - L - x^2)/(L^3 - x^2 L)`. + if ((answer = IndefiniteIntegralSolver.SolveARationalFunctionOfTheLogarithmByPartialFractionsInIt(expr, x, integrateByParts)) is { }) return answer; return null; } /// diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/RothsteinTrager.cs b/Sources/AngouriMath/Functions/Continuous/Integration/RothsteinTrager.cs index 5c2894e32..9e68cec5a 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/RothsteinTrager.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/RothsteinTrager.cs @@ -66,12 +66,24 @@ internal static class RothsteinTrager /// /// The largest denominator degree read at all: Hermite's reduction is a few Diophantine /// solves over the rationals, and what it leaves for the resultant is the squarefree part, - /// held to . Rubi's 1.3.1 3 (19 x^3 + 120 x^2 + 228 x - 47)/(x^4 + x + 3)^4 + ... + /// held to , or to where no + /// sum over roots is written for it. Rubi's 1.3.1 3 (19 x^3 + 120 x^2 + 228 x - 47)/(x^4 + x + 3)^4 + ... /// is a derivative over a sixteenth-degree denominator with nothing left for the logarithms, /// and was declined after a minute and a half of searching elsewhere. /// private const int MaxReducedDegree = 24; + /// + /// The largest squarefree part past whose logarithms are taken, on + /// the path for a high degree only, and there only where every residue lies in a field of + /// degree at most two, so that no sum over roots is written for it. Hearn's + /// (3 + 3x - 4x^2 - ... + 7x^13)/(1 + 2x - ... + x^14) is over P^2 - 2Q^2, + /// P = x^7 - x - 1 and Q = x + x^2, irreducible over the rationals and the + /// product of P -+ sqrt(2) Q, and its residues are the roots of 4t^2 - 4t - 1: + /// it was declined for its degree alone. + /// + private const int MaxReducedLogarithmicDegree = 16; + /// /// The integral of over , /// both polynomials in with rational coefficients and the fraction @@ -115,7 +127,8 @@ internal static class RothsteinTrager var answer = rationalPart; if (!logAbove.IsZero) { - if (logBelow.Degree > MaxDegree || LogarithmicPart(logAbove, logBelow, x) is not { } logarithmic) + if (logBelow.Degree > MaxReducedLogarithmicDegree + || LogarithmicPart(logAbove, logBelow, x, sumsOverRoots: logBelow.Degree <= MaxDegree) is not { } logarithmic) return null; answer = answer + logarithmic; } @@ -163,8 +176,10 @@ private static (Entity Rational, RationalPolynomial Above, RationalPolynomial Be /// /// The logarithmic part of the integral of over /// , the latter squarefree and monic and the fraction proper. + /// Without , where a residue lies in a + /// field of degree above two. /// - private static Entity? LogarithmicPart(RationalPolynomial above, RationalPolynomial below, Variable x) + private static Entity? LogarithmicPart(RationalPolynomial above, RationalPolynomial below, Variable x, bool sumsOverRoots = true) { var derivative = Derivative(below); // The resultant in t of D and A - t D' has degree at most that of D in t, and is @@ -175,6 +190,8 @@ private static (Entity Rational, RationalPolynomial Above, RationalPolynomial Be // under Euler's substitution, asked several times over. if (ResultantInT(below, above, derivative) is not { } resultantPolynomial || resultantPolynomial.IsZero) return null; + if (!sumsOverRoots && HasAFactorPastQuadraticModuloAPrime(resultantPolynomial)) + return null; if (SquareFree(resultantPolynomial) is not { } residueParts) return null; @@ -191,6 +208,8 @@ private static (Entity Rational, RationalPolynomial Above, RationalPolynomial Be factors.Add((irreducibles, part.Multiplicity)); } var overRoots = factors.Any(part => part.Factors.Any(irreducible => irreducible.Factor.Degree > 2)); + if (overRoots && !sumsOverRoots) + return null; // Only once nothing else has answered the question: see // Integration.SumsOverRootsAllowed. Until then this declines, as it always did. if (overRoots && !Integration.SumsOverRootsAllowed) @@ -242,6 +261,36 @@ private static (Entity Rational, RationalPolynomial Above, RationalPolynomial Be return total; } + /// + /// Whether the resultant has an irreducible factor of degree above two over the rationals, + /// as seen modulo a prime: a factor over the rationals reduces to factors whose degrees add + /// up to its own, so one of degree three or more modulo a prime that keeps the degree is one + /// over the rationals too. Seen, not decided: a factor that splits into linears and + /// quadratics modulo both primes is left to the factorization. The squarefree decomposition + /// over the integers is the cost it spares -- a minute for the resultant of + /// (x^3 + 1)/(x^15 + 2x^2 + 7), of the fifteenth degree and irreducible. + /// + private static bool HasAFactorPastQuadraticModuloAPrime(RationalPolynomial resultant) + { + var integer = ToInteger(resultant); + var tried = 0; + // Each above every degree read, so that no derivative vanishes for its characteristic. + foreach (var prime in new long[] { 10007, 10009, 10037, 10039 }) + { + if (integer.ToPrimeField(prime) is not { } reduced) + continue; + var monic = reduced.MakeMonic(); + var radical = monic.Quotient(monic.Gcd(monic.Derivative())); + // Every irreducible factor of degree one or two divides t^(p^2) - t, and no other. + var t = PrimeFieldPolynomial.Monomial(1, prime); + if (!t.PowMod(EInteger.FromInt64(prime * prime), radical).SameAs(t.Remainder(radical))) + return true; + if (++tried == 2) + break; + } + return false; + } + /// /// The logarithms at the poles whose residues are roots of , /// as a sum over those poles: sum(A(r)/D'(r) ln(x - r), r in { r : E(r) = 0 }), diff --git a/Sources/Tests/UnitTests/Calculus/SeamsOfTheIndependentSuitesIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/SeamsOfTheIndependentSuitesIntegralTest.cs new file mode 100644 index 000000000..384625bfa --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/SeamsOfTheIndependentSuitesIntegralTest.cs @@ -0,0 +1,86 @@ +// +// 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 System.Globalization; +using System.Linq; +using AngouriMath.Extensions; +using Xunit; + +namespace AngouriMath.Tests.Calculus +{ + /// + /// Rows of Rubi's independent suites that were declined where the pieces they come apart into + /// were answered: Hearn's rational function over P^2 - 2Q^2 of the fourteenth degree, + /// Hebisch's e^u/u du under u = x + e^x, Welz's linears beside the root of + /// x^2 +- i and the nested root that is two of them, Timofeev's symbolic power below a + /// nested root, and Bronstein's partial fractions in ln(x). + /// #233 + /// + [Trait("Area", "Calculus")] + public sealed class SeamsOfTheIndependentSuitesIntegralTest + { + [Theory] + // Hearn 365: the logarithms over Q(sqrt(2)), past the twelfth degree. + [InlineData("(3+3*x-4*x^2-4*x^3-7*x^6+4*x^7+10*x^8+7*x^13)/(1+2*x-x^2-4*x^3-2*x^4-2*x^7-2*x^8+x^14)", "-2.7 -0.8 -0.3 0.2 0.5 1.1 2.3")] + // Hebisch 15: Ei(x + e^x). + [InlineData("(1+e^x)*e^(x+e^x)/(x+e^x)", "-2.3 -1.1 -0.2 0.4 1.3 2.1")] + // Welz 44, 47 and 48, and a linear beside a root of a quadratic off the real line. + [InlineData("1/((1+x)^2*sqrt(2)*sqrt(-i+x^2))+1/((1+x)^2*sqrt(2)*sqrt(i+x^2))", "-3.1 -1.6 -0.7 0.3 1.2 2.8")] + [InlineData("sqrt(x^2+sqrt(1+x^4))/((1+x)^2*sqrt(1+x^4))", "-3.1 -1.6 -0.7 0.3 1.2 2.8")] + [InlineData("sqrt(x^2+sqrt(1+x^4))/((1+x)*sqrt(1+x^4))", "-3.1 -1.6 -0.7 0.3 1.2 2.8")] + [InlineData("x/((2+x)^2*sqrt(x^2+3*i))", "-3.1 -1.6 -0.7 0.3 1.2 2.8")] + // Timofeev 448, with n pinned to a whole number so that both sides of zero are real. + [InlineData("1/((1+x^(2*n))*(-x^2+(1+x^(2*n))^(1/n))^(1/2))", "-2.1 -0.9 -0.4 0.3 0.8 1.7")] + // Bronstein 22: li(x) and two logarithmic derivatives. + [InlineData("(-x^2-ln(x)+2*ln(x)^2)/(-x^2*ln(x)+ln(x)^3)", "0.2 0.4 0.8 1.3 2.1 3.7")] + public void DifferentiatesBackToTheIntegrand(string integrand, string points) + { + var integral = integrand.ToEntity().Integrate("x"); + var text = integral.Stringize(); + Assert.DoesNotContain("integral(", text); + Entity Pinned(Entity e) => e.Substitute("n", 2); + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pinned(integrand.ToEntity()); + var compared = 0; + foreach (var at in points.Split(' ').Select(point => double.Parse(point, CultureInfo.InvariantCulture))) + { + var want = original.Substitute("x", at).EvalNumerical(); + var got = derivative.Substitute("x", at).EvalNumerical(); + if (want.IsNaN) + continue; + compared++; + 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}"); + } + Assert.True(compared >= 5, $"only {compared} points could be compared for {integrand}"); + } + + [Theory] + // Past the twelfth degree with residues in a field of degree past two, still declined, as + // the residues' field is seen modulo a prime: the squarefree decomposition of their + // resultant over the integers was a minute. + [InlineData("x/(x^14+3*x+1)")] + [InlineData("(x^3+1)/(x^15+2*x^2+7)")] + [InlineData("1/((x^7-x-1)^2-2*(x+x^2)^2)")] + public void AHighDegreeWithResiduesPastAQuadraticFieldIsDeclined(string integrand) + => Assert.Contains("integral(", integrand.ToEntity().Integrate("x").Stringize()); + + [Theory] + // Answered before as they are now, by the routes that answered them: a power of x below + // the root off the real line, and a polynomial beside the nested root. + [InlineData("1/(x*sqrt(x^2+i))", "arctan")] + [InlineData("sqrt(x^2+sqrt(1+x^4))*x/sqrt(1+x^4)", "x ^ 2) ^ 2")] + public void ASolvedNeighbourKeepsItsForm(string integrand, string written) + { + var text = integrand.ToEntity().Integrate("x").Stringize(); + Assert.DoesNotContain("integral(", text); + Assert.Contains(written, text); + } + } +}