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