diff --git a/Sources/AngouriMath/Functions/Algebra/Polynomials/PartialFractions.cs b/Sources/AngouriMath/Functions/Algebra/Polynomials/PartialFractions.cs index 2d837b7eb..074dc0af6 100644 --- a/Sources/AngouriMath/Functions/Algebra/Polynomials/PartialFractions.cs +++ b/Sources/AngouriMath/Functions/Algebra/Polynomials/PartialFractions.cs @@ -866,7 +866,7 @@ Entity PolynomialFromCoefficients(Entity[] coefficients) /// in at , /// each in lowest terms over the symbols; null where it does not read as one. /// - private static Entity[]? TaylorCoefficientsAtTheRoot(Entity polynomial, Entity root, int count, Variable x) + internal static Entity[]? TaylorCoefficientsAtTheRoot(Entity polynomial, Entity root, int count, Variable x) { var offset = Variable.CreateUnique(polynomial + root, "t"); var shifted = WithWholePowersOfPowersMultiplied(Bare(polynomial.Substitute(x, root + offset).Expand().InnerSimplified)); diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index e5bf8b06f..0b63dd996 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -1145,6 +1145,108 @@ over is Entity.Powf(var @base, var power) ? _ => null }; + /// + /// A polynomial numerator over a denominator with a power of a linear in it beside a + /// factor that is not a polynomial -- P(x)/((a + b x)^k S(x)) -- with the + /// polynomial written in powers of the linear at its root: what the power divides goes + /// over S alone, and each lower power over its own power of the linear. + /// + /// + /// + /// P(x) = p_0 + p_1 (a + b x) + ... + (a + b x)^k Q(x) is a polynomial identity, + /// its coefficients the Taylor coefficients of P at -a/b over the powers + /// of b, so the integrand is Q/S + sum p_j /((a + b x)^(k-j) S) exactly and + /// everywhere. (A + B x + C x^2 + D x^3)/((a + b x) sqrt(c + d x)) was answered + /// through the substitution u = sqrt(c + d x) term by term, a cubic in + /// u^2 - c over a symbolic quadratic in u each time, in a hundred kilobytes + /// of piecewise that did not evaluate within the corpus's budget; reduced, it is a + /// quadratic over the root, three powers, and one p_0/((a + b x) sqrt(c + d x)). + /// + /// + /// Only beside a factor that is not a polynomial in x: a quotient of polynomials + /// is the partial fractions' and they do this and more. + /// + /// https://github.com/asc-community/AngouriMath/issues/718 + /// + internal static Entity? SolveByReducingThePolynomialOverALinearFactor(Entity expr, Entity.Variable x, bool integrateByParts) + { + if (!TryReadAsQuotient(expr, out var numerator, out var denominator)) + return null; + if (!TreeAnalyzer.TryGetPolynomial(numerator, x, out var polynomial) || polynomial.Count == 0 + || polynomial.Keys.Any(power => power.Sign < 0) || polynomial.Values.Any(coefficient => coefficient.ContainsNode(x))) + return null; + var degree = polynomial.Keys.Max()!; + if (!degree.CanFitInInt32() || degree.ToInt32Unchecked() < 1) + return null; + + Entity? linear = null; + var multiplicity = 0; + Entity rest = Number.Integer.One; + var somethingElse = false; + foreach (var factor in Mulf.LinearChildren(denominator)) + { + if (!factor.ContainsNode(x)) + { + rest *= factor; + continue; + } + var (@base, power) = factor is Powf(var repeated, Number.Integer { EInteger.Sign: > 0 } times) && times.EInteger.CanFitInInt32() + ? (repeated, times.EInteger.ToInt32Unchecked()) : (factor, 1); + if (linear is null && TreeAnalyzer.TryGetPolynomial(@base, x, out var read) && read.Count > 0 + && read.Keys.Max()!.Equals(EInteger.One) && read.Values.All(coefficient => !coefficient.ContainsNode(x))) + { + linear = @base; + multiplicity = power; + continue; + } + if (!TreeAnalyzer.TryGetPolynomial(factor, x, out _)) + somethingElse = true; + rest *= factor; + } + if (linear is null || !somethingElse) + return null; + + if (!TreeAnalyzer.TryGetPolynomial(linear, x, out var line)) + return null; + var beta = line[EInteger.One]; + var alpha = line.TryGetValue(EInteger.Zero, out var constantTerm) ? constantTerm : Number.Integer.Zero; + var root = Functions.PartialFractions.Bare((-alpha / beta).InnerSimplified); + var count = degree.ToInt32Unchecked() + 1; + if (Functions.PartialFractions.TaylorCoefficientsAtTheRoot(numerator, root, count, x) is not { } coefficients) + return null; + + Entity total = Number.Integer.Zero; + for (var order = 0; order < multiplicity && order < count; order++) + { + if (coefficients[order] == Number.Integer.Zero) + continue; + var over = multiplicity - order; + var coefficient = Functions.PartialFractions.InLowestTermsOverTheSymbols(coefficients[order] / MathS.Pow(beta, order)); + var piece = coefficient / ((over == 1 ? linear : MathS.Pow(linear, over)) * rest); + if (Integration.ComputeIndefiniteIntegral(piece, x, integrateByParts) is not { } integrated) + return null; + total += integrated; + } + Entity quotient = Number.Integer.Zero; + var offset = x - root; + for (var order = multiplicity; order < count; order++) + { + if (coefficients[order] == Number.Integer.Zero) + continue; + var coefficient = Functions.PartialFractions.InLowestTermsOverTheSymbols(coefficients[order] / MathS.Pow(beta, multiplicity)); + var shift = order - multiplicity; + quotient += shift == 0 ? coefficient : shift == 1 ? coefficient * offset : coefficient * MathS.Pow(offset, shift); + } + if (quotient != Number.Integer.Zero) + { + var expanded = Functions.PartialFractions.Bare(quotient.Expand().InnerSimplified); + if (Integration.ComputeIndefiniteIntegral(expanded / rest, x, integrateByParts) is not { } integrated) + return null; + total += integrated; + } + return total; + } + /// /// A polynomial in x^2 over a + b x^2 + c x^4 with a symbol in it, by the /// two roots in x^2: with q = sqrt(b^2 - 4 a c) and diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 32605084d..c1fa6d18a 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -487,6 +487,11 @@ private static Entity Normalized(Entity expr, Entity.Variable x) => // split, since Hearn's `e^(x^2)/x + 2x e^(x^2) ln(x) + ...` is a sum whose terms are // not elementary apart; at the top only, and only for a tower no closed rule reads. if ((answer = IndefiniteIntegralSolver.SolveByRischNormanAnsatz(expr, x)) is { }) return answer; + // A polynomial over a power of a linear and something that is not a polynomial, + // the polynomial written in powers of the linear at its root: the part the power + // divides goes over the rest alone. Before the split, so that the whole polynomial + // is reduced once rather than each of its terms. + if ((answer = IndefiniteIntegralSolver.SolveByReducingThePolynomialOverALinearFactor(expr, x, integrateByParts)) is { }) return answer; if (expr is Entity.Sumf or Entity.Minusf && (answer = IndefiniteIntegralSolver.SolveBySplittingSum(expr, x, integrateByParts)) is { }) return answer; diff --git a/Sources/Tests/UnitTests/Calculus/SymbolicFactorsPartialFractionsTest.cs b/Sources/Tests/UnitTests/Calculus/SymbolicFactorsPartialFractionsTest.cs index 529a1d059..4ddb897fa 100644 --- a/Sources/Tests/UnitTests/Calculus/SymbolicFactorsPartialFractionsTest.cs +++ b/Sources/Tests/UnitTests/Calculus/SymbolicFactorsPartialFractionsTest.cs @@ -192,6 +192,26 @@ public void SymbolicLinearBlocksBesideAQuadratic(string integrand) Assert.True(integrand.ToEntity().Integrate("x").Stringize().Length < 4000, "the answer is a page"); } + /// + /// A polynomial over a power of a linear beside a radical is written in powers of the + /// linear at its root, so that what the power divides goes over the radical alone: + /// (A + B x + C x^2 + D x^3)/((a + b x) sqrt(c + d x)) went through the + /// substitution u = sqrt(c + d x) term by term, a cubic in u^2 - c over a + /// symbolic quadratic each time, in a hundred kilobytes of piecewise that did not + /// evaluate within the corpus's budget; reduced it is a quadratic over the root and + /// one p_0/((a + b x) sqrt(c + d x)), under four kilobytes. + /// + [Theory] + [InlineData("(A + B*x + F*x^2 + G*x^3)/((a + b*x)*sqrt(c + d*x))")] + [InlineData("x^3/((a + b*x)*sqrt(c + d*x))")] + [InlineData("(A + B*x + F*x^2)/((a + b*x)*(c + d*x)^(1/3))")] + public void APolynomialOverALinearBesideARadicalIsReduced(string integrand) + { + DifferentiatesBack(integrand, "x", ("a", 1.7), ("b", 2.3), ("c", 0.4), ("d", 1.1), ("A", 0.5), ("B", 1.3), ("F", 0.7), ("G", 2.1)); + var length = integrand.ToEntity().Integrate("x").Stringize().Length; + Assert.True(length < 4000, $"{length} characters of answer for {integrand}"); + } + /// /// A repeated symbolic quadratic beside the linears: the numerators over its powers are /// the digits of the numerator over the other factors written in powers of the