diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md
index 7b8cb4a18..23af66a16 100644
--- a/BREAKING-CHANGES.md
+++ b/BREAKING-CHANGES.md
@@ -614,6 +614,23 @@ carried the discriminant's being real in its condition, and held nowhere when it
| `"piecewise(2 * x provided not x = 0, 3 * x ^ 2 provided i - 1 < 0)".ToEntity().DomainCondition` | `false` | `not x = 0` |
| `"1/(sqrt(a + i*a*x)*(c + d*x)^(3/2)*(a - i*a*x))".ToEntity().Integrate("x")` | `integral(...)`; on the unreleased master, an answer with no value at any point | 3,267 characters, defined |
+### A rational function over a binomial of degree three or four beside other factors is split over them
+
+**Answers where there were none.** Rubi's 4.3.7 `1/(a + b tan(c + d x)^3)` and its square, and 6.1.7
+`sech(c + d x)/(a + b sqrt(sinh(c + d x)))` and its square, were declined. Under the tangent and under the root of the
+sine they are rational functions over `(a + b u^3) (1 + u^2)` and `(a + b u) (1 + u^4)`, and the split over the
+factors as written read linear and quadratic ones only. It now reads one binomial of degree three or four, written
+once, beside factors of degree one or two, and the rules for a polynomial over a binomial answer each piece; not
+beside a power of the variable alone, which those rules read term by term, nor beside a second such binomial
+([#233](https://github.com/asc-community/AngouriMath/issues/233)).
+
+| Input | Was (2.5.0) | Now |
+|---|---|---|
+| `"1/(a+b*tan(c+d*x)^3)".ToEntity().Integrate("x")` | `integral(...)` | 1,853 characters |
+| `"1/(a+b*tan(c+d*x)^3)^2".ToEntity().Integrate("x")` | `integral(...)` | 3,026 characters |
+| `"sech(c+d*x)/(a+b*sqrt(sinh(c+d*x)))".ToEntity().Integrate("x")` | `integral(...)` | 3,325 characters |
+| `"sech(c+d*x)/(a+b*sqrt(sinh(c+d*x)))^2".ToEntity().Integrate("x")` | `integral(...)` | 4,614 characters |
+
### A rational function over the quadratic of an inverse tangent beside it is split
**Answers where there were none.** `x^4 atan(a x)/(c + a^2 c x^2)` ran out of time, and so did three more of Rubi's 5.3.4.
diff --git a/Sources/AngouriMath/Functions/Algebra/Polynomials/PartialFractions.cs b/Sources/AngouriMath/Functions/Algebra/Polynomials/PartialFractions.cs
index 1ff2f3dc2..a920387b4 100644
--- a/Sources/AngouriMath/Functions/Algebra/Polynomials/PartialFractions.cs
+++ b/Sources/AngouriMath/Functions/Algebra/Polynomials/PartialFractions.cs
@@ -438,6 +438,8 @@ internal static bool TrySplitOverWrittenFactors(
// coefficients free of x; a factor free of x is a constant and comes out in front.
var factors = new List<(Entity Factor, int Degree)>();
Entity constant = Integer.One;
+ var binomialsPastTheSecondDegree = 0;
+ var aPowerOfXAlone = false;
foreach (var part in Mulf.LinearChildren(denominator))
{
if (!part.ContainsNode(x))
@@ -464,8 +466,16 @@ internal static bool TrySplitOverWrittenFactors(
if (!TreeAnalyzer.TryGetPolynomial(toRead, x, out var read) || read.Count == 0)
return false;
var degree = read.Keys.Max()!;
- if (!degree.CanFitInInt32() || degree.ToInt32Unchecked() is not (1 or 2))
+ // And a binomial of degree three or four written once, `a + b x^3`: the system below
+ // reads any degree, and the rules for a polynomial over a binomial answer each
+ // piece -- `1/((a + b u^3) (1 + u^2))`, which the tangent makes of Rubi's
+ // `1/(a + b tan(c + d x)^3)`, was declined for want of a split.
+ var aBinomialOnce = read.Count == 2 && multiplicity == 1 && degree.CanFitInInt32() && degree.ToInt32Unchecked() is 3 or 4;
+ if (!degree.CanFitInInt32() || degree.ToInt32Unchecked() is not (1 or 2) && !aBinomialOnce)
return false;
+ if (aBinomialOnce)
+ binomialsPastTheSecondDegree++;
+ aPowerOfXAlone |= read.Count == 1;
// A repeated quadratic has a rule to land on only with a symbol somewhere,
// the split by residues below; over the rationals the system would hand it a
// cubic over the square, which no rule reads.
@@ -476,6 +486,15 @@ internal static bool TrySplitOverWrittenFactors(
return false;
factors.Add((part.InnerSimplified, degree.ToInt32Unchecked() * multiplicity));
}
+ // Not beside a power of x alone, which is the rules' for `x^m` over a binomial, term by
+ // term: the cube root's substitution makes Rubi's 1.1.3.4 `(a + b x^3)^(1/3)/(x^5 (c + d x^3))`
+ // a polynomial over `u^3 (c + (a d - b c) u^3)` with `a^9 c^36 d^9` in it, and split over
+ // the two, it came back in 12,377 characters where those rules write 1,964. Nor beside a
+ // second such binomial: `x^7 (a + b x^3)^(1/3)/(c + d x^3)` is one over `(b u^3 - 1)` and
+ // `(c + (a d - b c) u^3)`, whose system with symbols in it came back with `a^17 b^16 c^43`
+ // and a wrong answer, where the rules for the cube root answer it in 2,861 characters.
+ if (binomialsPastTheSecondDegree > 1 || binomialsPastTheSecondDegree == 1 && aPowerOfXAlone)
+ return false;
// Two linear factors with one root are one factor: `(a + b x)(a + x b)^2` is
// written so by the rules that make a factor monic and gather its powers, and
// as two distinct factors the decomposition has no answer -- its coefficients
diff --git a/Sources/Tests/UnitTests/Calculus/ARationalFunctionOverAWrittenBinomialOfDegreeThreeOrFourIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/ARationalFunctionOverAWrittenBinomialOfDegreeThreeOrFourIntegralTest.cs
new file mode 100644
index 000000000..b11b0cdea
--- /dev/null
+++ b/Sources/Tests/UnitTests/Calculus/ARationalFunctionOverAWrittenBinomialOfDegreeThreeOrFourIntegralTest.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 AngouriMath.Extensions;
+using Xunit;
+
+namespace AngouriMath.Tests.Calculus
+{
+ ///
+ /// A rational function with symbols in it over a product written with a binomial of degree three
+ /// or four in it, split over the factors as written by undetermined coefficients: each piece is a
+ /// polynomial over a binomial, or over a linear or a quadratic, which the rules for those answer.
+ /// The split read only linear and quadratic factors, and `1/((a + b u^3) (1 + u^2))`, which the
+ /// tangent makes of Rubi's 4.3.7 `1/(a + b tan(c + d x)^3)`, was declined.
+ /// #233
+ ///
+ [Trait("Area", "Calculus")]
+ public sealed class ARationalFunctionOverAWrittenBinomialOfDegreeThreeOrFourIntegralTest
+ {
+ [Theory]
+ [InlineData("1/((a+b*x^3)*(1+x^2))")]
+ [InlineData("1/((a+b*x^4)*(c+x^2))")]
+ [InlineData("1/(a+b*tan(c+d*x)^3)")]
+ [InlineData("1/(a+b*tan(c+d*x)^3)^2")]
+ public void DifferentiatesBack(string integrand)
+ {
+ var integral = integrand.ToEntity().Integrate("x");
+ Assert.DoesNotContain("integral(", integral.Stringize());
+ Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.7).Substitute("c", 0.4).Substitute("d", 0.9);
+ var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
+ var original = Pinned(integrand.ToEntity());
+ var compared = 0;
+ foreach (var at in new[] { -1.3, -0.6, -0.2, 0.3, 0.8, 1.4 })
+ {
+ var want = original.Substitute("x", at).EvalNumerical();
+ var got = derivative.Substitute("x", at).EvalNumerical();
+ if (want.IsNaN || got.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}");
+ }
+
+ ///
+ /// Not split beside a power of the variable alone, nor beside a second such binomial: the cube
+ /// root's substitution makes Rubi's 1.1.3.4 rows rational functions over u^3 (c + k u^3)
+ /// and over (b u^3 - 1)(c + k u^3), and split, they came back in 12,377 and 27,590
+ /// characters with c^36 and c^43 in them, the second wrong, where the rules for
+ /// the cube root write 1,964 and 2,861.
+ ///
+ [Theory]
+ [InlineData("(a+b*x^3)^(1/3)/(x^5*(c+d*x^3))")]
+ [InlineData("x^7*(a+b*x^3)^(1/3)/(c+d*x^3)")]
+ public void IsNotSplitBesideAPowerOfXOrASecondBinomial(string integrand)
+ {
+ var integral = integrand.ToEntity().Integrate("x");
+ var text = integral.Stringize();
+ Assert.DoesNotContain("integral(", text);
+ Assert.True(text.Length < 5000, $"the antiderivative of {integrand} is {text.Length} characters");
+ Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.7).Substitute("c", 0.4).Substitute("d", 0.9);
+ var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
+ var original = Pinned(integrand.ToEntity());
+ var compared = 0;
+ foreach (var at in new[] { 0.3, 0.6, 0.9, 1.2, 1.5, 1.8 })
+ {
+ var want = original.Substitute("x", at).EvalNumerical();
+ var got = derivative.Substitute("x", at).EvalNumerical();
+ if (want.IsNaN || got.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}");
+ }
+ }
+}