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