diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md
index 409cf8bd6..466284748 100644
--- a/BREAKING-CHANGES.md
+++ b/BREAKING-CHANGES.md
@@ -221,6 +221,21 @@ factors written together, and is counted so ([#718](https://github.com/asc-commu
| `"sec(x)^2*sin(x)/(a + b*sin(x))^3".ToEntity().Integrate("x")` | `integral(...)`; past thirty seconds on the unreleased master | 5,017 characters |
| `"tan(x)^2/(a + b*sin(x))^3".ToEntity().Integrate("x")` | `integral(...)`; past thirty seconds on the unreleased master | 4,816 characters |
+### A product of sums in the symbols is multiplied out as a polynomial in the partial fractions
+
+**Answers where there were none.** `1/((a + b x^2)^3 (c + d x^2)^3)` ran past a minute, with twelve more of Rubi's
+that put a symbolic quadratic to a power beside other factors: the series at its roots multiplies coefficients
+that are products of powers of sums in the symbols, and multiplying those out term by term as expressions was the
+whole of the time. They are multiplied out as polynomials now
+([#718](https://github.com/asc-community/AngouriMath/issues/718), PR
+[#1833](https://github.com/asc-community/AngouriMath/pull/1833)).
+
+| Input | Was (2.5.0) | Now |
+|---|---|---|
+| `"1/((a + b*x^2)^3*(c + d*x^2)^3)".ToEntity().Integrate("x")` | `integral(...)`; past a minute on the unreleased master | 15,289 characters |
+| `"1/((d + e*x)^2*(a + b*x + c*x^2)^2)".ToEntity().Integrate("x")` | `integral(...)`; past a minute on the unreleased master | 11,274 characters |
+| `"1/((a + a*sec(e + f*x))^(5/2)*(c + d*sec(e + f*x))^3)".ToEntity().Integrate("x")` | `integral(...)`; past a minute on the unreleased master | 9,155 characters |
+
### A sine of a double argument beside the argument is written as a product
**Answers where there were none.** `csc(a + b x)^3 sin(2a + 2b x)^7` was declined: the rule that writes multiples
diff --git a/Sources/AngouriMath/Functions/Algebra/Polynomials/PartialFractions.cs b/Sources/AngouriMath/Functions/Algebra/Polynomials/PartialFractions.cs
index 326848ed9..d02fb4980 100644
--- a/Sources/AngouriMath/Functions/Algebra/Polynomials/PartialFractions.cs
+++ b/Sources/AngouriMath/Functions/Algebra/Polynomials/PartialFractions.cs
@@ -633,12 +633,12 @@ internal static bool TrySplitOverWrittenFactors(
internal static Entity InLowestTermsOverTheSymbols(Entity constant)
{
var (above, below) = SingleQuotient.Of(SingleQuotient.Combine(constant).InnerSimplified);
- var expandedAbove = Bare(above.Expand().InnerSimplified);
+ var expandedAbove = Bare(ExpandedOverTheSymbols(above).InnerSimplified);
// Zero as a value, decided at two sets of pinned symbols: `b (a + b (-a/b))` is
// zero and neither the expansion nor the evaluation of numbers says so.
if (expandedAbove == Integer.Zero || expandedAbove.Evaled is Complex { IsZero: true } || IsZeroAtPinnedSymbols(expandedAbove))
return Integer.Zero;
- var expandedBelow = Bare(below.Expand().InnerSimplified);
+ var expandedBelow = Bare(ExpandedOverTheSymbols(below).InnerSimplified);
// A number is folded already, and stays a number.
if ((expandedAbove.Vars.Any() || expandedBelow.Vars.Any())
&& (HoldsTheImaginaryUnit(expandedAbove) || HoldsTheImaginaryUnit(expandedBelow))
@@ -659,6 +659,34 @@ internal static Entity InLowestTermsOverTheSymbols(Entity constant)
return WithThePrimitiveDenominator(expandedBelow == Integer.One ? expandedAbove : expandedAbove / expandedBelow);
}
+ ///
+ /// multiplied out: as a polynomial over the rationals in its
+ /// symbols where it is one, and by where it is not.
+ ///
+ ///
+ /// multiplies a product of sums out by enumerating the
+ /// combinations of their terms as expressions. The coefficients the series at a repeated
+ /// quadratic makes are products of powers of sums in the symbols, and that enumeration was
+ /// where all of the eighteen seconds of 1/((1 + x^2)^2 (c + d + 2d x^2)^3 (1 + 2x^2))
+ /// went. A polynomial collects its terms as it multiplies.
+ /// https://github.com/asc-community/AngouriMath/issues/718
+ ///
+ private static Entity ExpandedOverTheSymbols(Entity expr)
+ {
+ // Only where there is a product to multiply out: a sum or a quotient of sums already
+ // written term by term keeps the spelling every other step reads it in.
+ if (!expr.Nodes.Any(node => node is Powf(Sumf or Minusf, Integer { EInteger: var power }) && power.CompareTo(EInteger.One) > 0
+ || node is Mulf(var left, var right) && left.Nodes.Any(n => n is Sumf or Minusf) && right.Nodes.Any(n => n is Sumf or Minusf)))
+ return expr.Expand();
+ var variables = expr.Vars.OrderBy(v => v.Name, System.StringComparer.Ordinal).ToList();
+ if (variables.Count == 0 || variables.Count > MultivariatePolynomial.MaxVariables)
+ return expr.Expand();
+ var indices = new Dictionary();
+ for (var i = 0; i < variables.Count; i++)
+ indices[variables[i]] = i;
+ return MultivariatePolynomial.TryParse(expr, indices) is { } polynomial ? polynomial.ToEntity(variables) : expr.Expand();
+ }
+
/// Whether a number off the real line is among the nodes of .
private static bool HoldsTheImaginaryUnit(Entity expr)
=> expr.Nodes.Any(node => node is Complex number && number is not Real && !number.ImaginaryPart.IsZero);
@@ -1882,6 +1910,7 @@ private static bool TrySolveOverPolynomials(Entity[][] matrix, Entity[] rhs, [No
indices[symbols[i]] = i;
var rows = rhs.Length;
var width = matrix[0].Length;
+ if (System.Environment.GetEnvironmentVariable("TSOP_DBG") is not null) System.Console.Error.WriteLine($"tsop rows={rows} width={width} symbols={symbols.Count} maxterms={matrix.SelectMany(r => r).Concat(rhs).Max(e => e.Complexity)}");
// The augmented matrix, the right-hand side its last column.
var augmented = new MultivariatePolynomial[rows][];
for (var row = 0; row < rows; row++)
diff --git a/Sources/Tests/UnitTests/Calculus/ARepeatedSymbolicQuadraticIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/ARepeatedSymbolicQuadraticIntegralTest.cs
new file mode 100644
index 000000000..f30fb119a
--- /dev/null
+++ b/Sources/Tests/UnitTests/Calculus/ARepeatedSymbolicQuadraticIntegralTest.cs
@@ -0,0 +1,48 @@
+//
+// 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
+{
+ ///
+ /// The partial fractions over a symbolic quadratic to a power, beside other quadratics: the
+ /// series at its roots, with its coefficients multiplied out as polynomials in the symbols.
+ /// #718
+ ///
+ [Trait("Area", "Calculus")]
+ public sealed class ARepeatedSymbolicQuadraticIntegralTest
+ {
+ [Theory]
+ [InlineData("1/((1 + x^2)^2*(c + d + 2*d*x^2)^3*(1 + 2*x^2))")]
+ public void OverItsPowers(string integrand)
+ {
+ var integral = integrand.ToEntity().Integrate("x");
+ var text = integral.Stringize();
+ Assert.DoesNotContain("integral(", text);
+ Assert.True(text.Length < 40000, $"{text.Length} characters of answer for {integrand}");
+ Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.4).Substitute("c", 0.9).Substitute("d", 0.6).Substitute("f", 0.7).Substitute("g", 0.2);
+ var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
+ var original = Pinned(integrand.ToEntity());
+ var compared = 0;
+ foreach (var at in new[] { -2.1, -0.4, 0.3, 0.6, 1.6, 2.5 })
+ {
+ 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}");
+ }
+ }
+}