diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md
index 1a54a20b2..c83cb36be 100644
--- a/BREAKING-CHANGES.md
+++ b/BREAKING-CHANGES.md
@@ -417,6 +417,21 @@ taken through its decimal, which had already rounded it onto the integer beside
| `"round(10^120 + 3/2)".ToEntity().Evaled` | `10^120` | `10^120 + 2` |
| `"round(+oo)".ToEntity().Evaled` | throws `OverflowException` | `+oo` |
+### A factor a numerator's terms hold to a power cancels
+
+**Silent: a quotient whose numerator's terms each hold a factor of the denominator, as it is or to a whole power,
+is cancelled by it.** A term holding the factor only to a power was not read as holding it, so
+`(x (y + 1) + (y + 1)^2) / (y + 1)^4` was left as written while `(x (y + 1) + (y + 1)) / (y + 1)^2` cancelled. The
+quotient rule squares the denominator at each derivative, so a Taylor expansion of `e^x / (1 + x^2)` took its seventh
+derivative in 5,117 nodes, and takes it in 149: 9 terms took 8 s and take 1 s
+([#2301](https://github.com/asc-community/AngouriMath/issues/2301)).
+
+| Input | Was (2.6.0) | Now |
+|---|---|---|
+| `"(x * (y + 1) + (y + 1)^2) / (y + 1)^4".ToEntity().Simplify()` | `(x * (y + 1) + (y + 1) ^ 2) / (y + 1) ^ 4` | `(x + y + 1) / (y + 1) ^ 3` |
+| `"(x * (x^2 + 1) + (x^2 + 1)^2) / (x^2 + 1)^4".ToEntity().Simplify()` | `(x * (x ^ 2 + 1) + (x ^ 2 + 1) ^ 2) / (x ^ 2 + 1) ^ 4` | `(x ^ 2 + 1 + x) / (x ^ 2 + 1) ^ 3` |
+| `"(a * (x^2 + 1) + b * (x^2 + 1)^2) / (x^2 + 1)^3".ToEntity().Simplify()` | `(b * (x ^ 2 + 1) + a) / (x ^ 4 + 2 * x ^ 2 + 1) provided not x ^ 2 + 1 = 0` | `(a + b * (x ^ 2 + 1)) / (x ^ 2 + 1) ^ 2` |
+
### A squared sine and cosine with one coefficient are found anywhere in a sum
**Silent: an identity reaches its value however the sum is written.** `k sin(x)^2 + k cos(x)^2` was reduced to `k`
diff --git a/Sources/.editorconfig b/Sources/.editorconfig
index 70e9a7037..ddd6649cc 100644
--- a/Sources/.editorconfig
+++ b/Sources/.editorconfig
@@ -1019,6 +1019,9 @@ file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed
[Tests/UnitTests/Common/DivisionByZeroOverDoubleTest.cs]
file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n
+[Tests/UnitTests/Common/AFactorHeldToAPowerCancelsTest.cs]
+file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n
+
[Tests/UnitTests/Common/HyperbolicTangentPastTheRangeTest.cs]
file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n
diff --git a/Sources/AngouriMath/Functions/Simplification/Patterns/Patterns.Rational.cs b/Sources/AngouriMath/Functions/Simplification/Patterns/Patterns.Rational.cs
index c30a2dd68..05347ebcf 100644
--- a/Sources/AngouriMath/Functions/Simplification/Patterns/Patterns.Rational.cs
+++ b/Sources/AngouriMath/Functions/Simplification/Patterns/Patterns.Rational.cs
@@ -77,9 +77,75 @@ internal static Entity FractionCommonDenominatorRules(Entity expr, TreeAnalyzer.
///
internal static Entity PairwiseGroupedQuotient(
Entity whole, Entity num, Entity den, TreeAnalyzer.SortLevel level)
- => PairwiseGrouping(num, den, level).Select(PowerRules).MultiplyAll()
+ => PairwiseGrouping(WithAFactorOfTheOtherOut(num, den), WithAFactorOfTheOtherOut(den, num), level)
+ .Select(PowerRules).MultiplyAll()
.InnerSimplified.Replace(CollapseMultipleFractions);
+ ///
+ /// with a factor of taken out of it, where
+ /// every term of the sum holds that factor, as it is or to a whole power, so that the
+ /// pairing meets it: x (y + 1) + (y + 1)^2 beside (y + 1)^4 is
+ /// (y + 1) (x + y + 1). The sum as it is where no polynomial factor is in every term.
+ ///
+ ///
+ /// A term that held the factor only to a power was not read as holding it, so
+ /// (x (y + 1) + (y + 1)^2) / (y + 1)^4 was left as written while
+ /// (x (y + 1) + (y + 1)) / (y + 1)^2 cancelled. The quotient rule squares the
+ /// denominator at each derivative, so the Taylor expansion of e^x / (1 + x^2) took
+ /// its derivatives over (x^2 + 1)^2, ^4, ^8.
+ /// https://github.com/asc-community/AngouriMath/issues/2301
+ ///
+ private static Entity WithAFactorOfTheOtherOut(Entity sum, Entity other)
+ {
+ if (sum is not (Sumf or Minusf))
+ return sum;
+ var terms = Sumf.LinearChildren(sum);
+ foreach (var candidate in Mulf.LinearChildren(other))
+ {
+ var factor = candidate is Powf(var @base, Integer { IsPositive: true }) ? @base : candidate;
+ if (factor is Number || !factor.Vars.Any() || !IsAPolynomial(factor))
+ continue;
+ var lowest = terms.Min(term => PowerOf(factor, term));
+ if (lowest < 1)
+ continue;
+ return MathS.Pow(factor, lowest) * Sumf.Sum(terms.Select(term => Without(factor, lowest, term)).ToList());
+ }
+ return sum;
+
+ // A polynomial factor only. A power of a sine has a cancellation of its own, through the
+ // opened angles and a common denominator, and taking it out first left the reporter's
+ // second expression of https://github.com/asc-community/AngouriMath/issues/557 short of 0.
+ static bool IsAPolynomial(Entity factor)
+ => factor.Nodes.All(node => node is Number or Variable or Sumf or Minusf or Mulf
+ || node is Powf(_, Integer { IsPositive: true }));
+
+ // How many times the factor is in the product, counting a whole power of it as that many.
+ static int PowerOf(Entity factor, Entity term)
+ => Mulf.LinearChildren(term).Sum(child =>
+ child == factor ? 1
+ : child is Powf(var b, Integer { IsPositive: true } k) && b == factor && k.EInteger.CanFitInInt32() ? k.EInteger.ToInt32Checked()
+ : 0);
+
+ // The product with that many of the factor taken off it.
+ static Entity Without(Entity factor, int count, Entity term)
+ {
+ var kept = new List();
+ foreach (var child in Mulf.LinearChildren(term))
+ {
+ var here = child == factor ? 1
+ : child is Powf(var b, Integer { IsPositive: true } k) && b == factor && k.EInteger.CanFitInInt32() ? k.EInteger.ToInt32Checked()
+ : 0;
+ var taken = System.Math.Min(here, count);
+ count -= taken;
+ if (here - taken > 0)
+ kept.Add(here - taken == 1 ? factor : MathS.Pow(factor, here - taken));
+ else if (here == 0)
+ kept.Add(child);
+ }
+ return kept.Count == 0 ? Integer.One : kept.Aggregate((left, right) => left * right);
+ }
+ }
+
/// n^(p/q) for a q of 2 or more -- a root that is not a whole power.
private static bool IsSurd(Entity node)
=> node is Powf(_, Rational and not Integer);
diff --git a/Sources/Tests/UnitTests/Common/AFactorHeldToAPowerCancelsTest.cs b/Sources/Tests/UnitTests/Common/AFactorHeldToAPowerCancelsTest.cs
new file mode 100644
index 000000000..fe67b4858
--- /dev/null
+++ b/Sources/Tests/UnitTests/Common/AFactorHeldToAPowerCancelsTest.cs
@@ -0,0 +1,55 @@
+//
+// 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.Linq;
+using AngouriMath;
+using AngouriMath.Extensions;
+using Xunit;
+
+namespace AngouriMath.Tests.Common
+{
+ ///
+ /// A factor that every term of a quotient's numerator holds, as it is or to a whole power, is taken out of the
+ /// numerator and paired off against the denominator. A term holding it only to a power was not read as holding it,
+ /// so (x (y + 1) + (y + 1)^2) / (y + 1)^4 was left as written while (x (y + 1) + (y + 1)) / (y + 1)^2
+ /// cancelled. #2301
+ ///
+ [Trait("Area", "Common")]
+ public sealed class AFactorHeldToAPowerCancelsTest
+ {
+ [Theory]
+ [InlineData("(x * (y + 1) + (y + 1)^2) / (y + 1)^4", "(x + y + 1) / (y + 1) ^ 3")]
+ [InlineData("(x * (x^2 + 1) + (x^2 + 1)^2) / (x^2 + 1)^4", "(x ^ 2 + 1 + x) / (x ^ 2 + 1) ^ 3")]
+ [InlineData("(x * (y + 1) + (y + 1)) / (y + 1)^2", "(x + 1) / (y + 1)")]
+ public void TheFactorCancels(string quotient, string expected)
+ {
+ var simplified = quotient.ToEntity().Simplify();
+ // As printed: the answer's sums are grouped as Simplify builds them, not as the parser does.
+ Assert.Equal(expected, simplified.Stringize());
+ foreach (var (x, y) in new[] { (2.0, 0.5), (-1.5, 3.0), (0.25, -0.75) })
+ {
+ var before = quotient.ToEntity().Substitute("x", x).Substitute("y", y).EvalNumerical();
+ var after = simplified.Substitute("x", x).Substitute("y", y).EvalNumerical();
+ Assert.True((before - after).Abs() < 1e-9, $"{after} where the quotient is {before} at x = {x}, y = {y}");
+ }
+ }
+
+ ///
+ /// The quotient rule squares the denominator at each derivative, and what the new numerator shares with it was
+ /// not taken back: the seventh derivative of e^x / (1 + x^2), simplified at each step, had 5,117 nodes,
+ /// and has 149 once the factor cancels.
+ ///
+ [Fact]
+ public void TheDerivativesOfAQuotientStaySmall()
+ {
+ Entity derivative = "e^x / (1 + x^2)";
+ for (var i = 0; i < 7; i++)
+ derivative = derivative.Differentiate("x").Simplify();
+ Assert.True(derivative.Nodes.Count() < 500, $"{derivative.Nodes.Count()} nodes: {derivative}");
+ }
+ }
+}