diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md
index 35711e9ea..cee2871e0 100644
--- a/BREAKING-CHANGES.md
+++ b/BREAKING-CHANGES.md
@@ -412,6 +412,22 @@ shorter than the symbolic split's.
| `"1/(u*(u^2 + 1)^6*(a*(u^2 + 1) + 2*a*u))".Integrate("u")` | unevaluated | `(17/24 - 829/1536 u + … - 273/512 u^10)/(a (u^2 + 1)^5 (u + 1)) + ln(u)/a - 7/64 ln(u + 1)/a - 57/128 ln(u^2 + 1)/a - 329/512 arctan(u)/a` |
| `"u^5/((u^2 - 1)^5*(2*a*u + b*(u^2 + 1)))".Integrate("u")` | unevaluated | the same piecewise in `u`, its coefficients `1/256 (3a^3 - 7ab^2)/(a^4 - 2a^2 b^2 + b^4)` and the like rather than two thousand nodes each |
+### A logarithm of a nested quotient is written over one bar under the exponential substitution
+
+**Improvement, not silent.** `atanh(tanh(a + b x))` reaches the integrator as
+`1/2 ln((1 + (w - 1)/(w + 1))/(1 - (w - 1)/(w + 1)))` in `w = e^(2(a + b x))`, and under `u = w`
+that logarithm is `1/2 ln(u)`; but the combining on the way into the substitution reaches the
+quotient the integrand is and not one inside a function, the simplifier combines nothing (#1239),
+and the substitution search compared its candidates against the nested spelling and never saw
+the `u`. A logarithm whose argument is a rational function of `u` with a quotient nested in it is
+now written over one bar in lowest terms, where that is smaller, before the question in `u` is
+asked. Only under that substitution; `Simplify` still combines nothing.
+
+| | Was | Is |
+|---|---|---|
+| `"1/sqrt(atanh(tanh(a + b*x)))".Integrate("x")` | unevaluated after 49 s | `2 (1/2 ln(e^(2 b x + 2 a)))^(1/2)/(1/2)/(2 b)`, 0.3 s |
+| `"atanh(tanh(a + b*x))^(1/2)".Integrate("x")` | unevaluated | `2 (1/2 ln(e^(2 b x + 2 a)))^(3/2)/(3/2)/(2 b)` |
+
### A condition the answer states on its own is no longer repeated beside it
**Improvement, not silent.** `Simplify` returned `(1 - ln(x))/x^2 provided not x = 0` for the
diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
index a5be706b8..780c59f02 100644
--- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
@@ -9828,6 +9828,7 @@ node is Powf(Powf(var @base, var inner), var outer)
rewritten / (Number.Rational.Create(k) * u)).Simplify();
if (integrand is Providedf(var inner, _))
integrand = inner;
+ integrand = WithLogarithmArgumentsOverOneBar(integrand, u);
// A whole power of a product is written as the product of the powers: the
// simplifier writes `4a^2 u^2` as `(a u)^2 4`, and no rational reader sees the `u^2`
// inside -- Timofeev's `1/(a^2 + b^2 cosh(x)^2)` was declined in that spelling and
@@ -9879,6 +9880,40 @@ node is Powf(Powf(var @base, var inner), var outer)
}
}
+ ///
+ /// with every logarithm whose argument is a rational function
+ /// of with a quotient nested inside it written over one bar in
+ /// lowest terms, where that is smaller: ln((1 + (u - 1)/(u + 1))/(1 - (u - 1)/(u + 1)))
+ /// is ln(u).
+ ///
+ ///
+ /// The combining above reaches the quotient the integrand is and not one inside a
+ /// function, and the simplifier combines nothing
+ /// (https://github.com/asc-community/AngouriMath/issues/1239). atanh(tanh(a + b x))
+ /// arrives as half that logarithm of tanh written in e^(2(a + b x)), and
+ /// under u the substitution search compared its candidates against the nested
+ /// spelling and never saw the u it is: 1/sqrt(atanh(tanh(a + b x))) was
+ /// answered before the substitution took a symbolic slope and not after.
+ /// https://github.com/asc-community/AngouriMath/issues/718
+ ///
+ private static Entity WithLogarithmArgumentsOverOneBar(Entity expr, Entity.Variable x)
+ => expr.Replace(node =>
+ {
+ if (node is not Logf(var logBase, var argument) || logBase != MathS.e || !argument.ContainsNode(x) || !IsARationalFunction(argument, x)
+ || !argument.Nodes.Any(inner => inner is Divf && inner != argument))
+ return node;
+ var (above, below) = Functions.SingleQuotient.Of(argument);
+ var over = below == Number.Integer.One ? above.InnerSimplified
+ : Functions.PolynomialGcd.TryCancel(above.InnerSimplified, below.InnerSimplified, out var cancelled)
+ ? Functions.PartialFractions.Bare(cancelled).InnerSimplified
+ : (above / below).InnerSimplified;
+ if (over.Complexity >= argument.Complexity)
+ return node;
+ // Small by now, and `2u/2` is what the cancellation leaves of `(2u/(u + 1))/(2/(u + 1))`.
+ over = Functions.PartialFractions.Bare(over.Simplify());
+ return MathS.Ln(over);
+ });
+
///
/// A product with a whole negative power of a polynomial in of
/// two or more terms among its factors, with every such power written below the bar
diff --git a/Sources/Tests/UnitTests/Calculus/ExponentialSubstitutionIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/ExponentialSubstitutionIntegralTest.cs
index 7b569a8b3..7594c7b80 100644
--- a/Sources/Tests/UnitTests/Calculus/ExponentialSubstitutionIntegralTest.cs
+++ b/Sources/Tests/UnitTests/Calculus/ExponentialSubstitutionIntegralTest.cs
@@ -407,6 +407,22 @@ public void BasesThatArePowersOfOne(string integrand)
public void ASymbolicSlope(string integrand)
=> DifferentiatesBackWithParametersPinned(integrand, new[] { -0.5, 0.23, 0.61, 1.05, 1.7 });
+ ///
+ /// A logarithm's argument that is a rational function of u with a quotient
+ /// nested in it is written over one bar in lowest terms: atanh(tanh(a + b x))
+ /// arrives as 1/2 ln((1 + (u - 1)/(u + 1))/(1 - (u - 1)/(u + 1))) under
+ /// u = e^(2(a + b x)), which is 1/2 ln(u), and the substitution search
+ /// compared its candidates against the nested spelling and never saw it. The
+ /// combining on the way in reaches the quotient the integrand is and not one inside a
+ /// function, and the simplifier combines nothing.
+ ///
+ [Theory]
+ [InlineData("1/sqrt(atanh(tanh(a + b*x)))")]
+ [InlineData("atanh(tanh(a + b*x))^(1/2)")]
+ [InlineData("1/atanh(tanh(a + b*x))^2")]
+ public void ALogarithmOfANestedQuotientIsWrittenOverOneBar(string integrand)
+ => DifferentiatesBackWithParametersPinned(integrand, new[] { -0.5, 0.23, 0.61, 1.05, 1.7 });
+
///
/// A quotient the gathering on the chain's entry writes as a product with a negative
/// power -- u^3/((a u^2 + b)^3 u) as u^2 (a u^2 + b)^(-3) -- is written