From e964ea373591ef4b986933e701dda0e1f31d57f0 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sat, 17 Oct 2026 00:07:37 +0000 Subject: [PATCH 1/4] Integrate an exponential of an inverse hyperbolic tangent as powers of the two linears Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- .../Integration/IndefiniteIntegralSolver.cs | 205 ++++++++++++++++++ .../Integration/Integration.Definition.cs | 3 + ...fAnInverseHyperbolicTangentIntegralTest.cs | 60 +++++ 3 files changed, 268 insertions(+) create mode 100644 Sources/Tests/UnitTests/Calculus/AnExponentialOfAnInverseHyperbolicTangentIntegralTest.cs diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index ded68afa2..912443b74 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -26154,6 +26154,211 @@ when two.Equals(EInteger.FromInt32(2)) && e1 == MathS.e && e2 == MathS.e return Functions.PartialFractions.DerivativeDiffersWhereReal(answer, expr, x) ? null : answer; } + /// + /// e^(n artanh(u)) (K (1 - u^2))^p (1 + u)^q (1 - u)^r times the rest, written as + /// K^p (1 + u)^(n/2 + p + q) (1 - u)^(-n/2 + p + r) times the rest, as Rubi writes it, + /// both powers whole or both half an odd number, the generic case for the constant; else null. + /// e^(4 artanh(a x))/(c - a^2 c x^2)^2 is 1/(c^2 (1 - a x)^4). + /// + private static Entity? WithThePowersOfTheLinearsGathered(Entity expr, Entity.Variable x) + { + static IEnumerable<(Entity Factor, bool Below)> Factors(Entity e, bool below) => e switch + { + Mulf(var l, var r) => Factors(l, below).Concat(Factors(r, below)), + Divf(var l, var r) => Factors(l, below).Concat(Factors(r, !below)), + _ => new[] { (e, below) } + }; + Entity? u = null; + ERational plusPower = ERational.Zero, minusPower = ERational.Zero; + Entity constant = Number.Integer.One, rest = Number.Integer.One; + var factors = Factors(expr, false).ToList(); + // The exponentials first, which fix u. + foreach (var (factor, below) in factors) + { + if (factor is not Powf(var e0, var exponent) || e0 != MathS.e || !exponent.ContainsNode(x)) + continue; + var logarithms = exponent.Nodes.Where(inner => inner is Logf(var b0, var of) && b0 == MathS.e && of.ContainsNode(x)).Distinct().ToList(); + if (logarithms.Count != 1 || logarithms[0] is not Logf(_, Divf(var plus, var minus)) + || !Functions.PartialFractions.IsZeroAsAValue((plus + minus - 2).InnerSimplified)) + return null; + var placeholder = Variable.CreateUnique(exponent, "l_ah"); + if (!TreeAnalyzer.TryGetPolyLinear(exponent.Substitute(logarithms[0], placeholder), placeholder, out var multiple, out var free) + || !Functions.PartialFractions.IsZeroAsAValue(free) || multiple.InnerSimplified.Evaled is not Number.Rational half) + return null; + var thisU = ((plus - minus) / 2).InnerSimplified; + if (u is not null && !Functions.PartialFractions.IsZeroAsAValue((u - thisU).InnerSimplified)) + return null; + u = thisU; + var signed = below ? -half.ERational : half.ERational; + plusPower += signed; + minusPower -= signed; + } + if (u is null || !TreeAnalyzer.TryGetPolyLinear(u, x, out var slope, out var intercept)) + return null; + var quadratic = 1 - MathS.Sqr(u); + // K with candidate = K target as polynomials in x, else null. + Entity? MultipleOf(Entity candidate, Entity target, int degree) + { + Entity ca, cb, ta, tb; + Entity? cc = null, tc = null; + if (degree == 2) + { + if (!TreeAnalyzer.TryGetPolyQuadratic(candidate, x, out var a2, out var b2, out var c2) + || !TreeAnalyzer.TryGetPolyQuadratic(target, x, out var a3, out var b3, out var c3) + || a2 is null || b2 is null || c2 is null || a3 is null || b3 is null || c3 is null) + return null; + (ca, cb, cc, ta, tb, tc) = (a2, b2, c2, a3, b3, c3); + } + else + { + if (!TreeAnalyzer.TryGetPolyLinear(candidate, x, out var a2, out var b2) + || !TreeAnalyzer.TryGetPolyLinear(target, x, out var a3, out var b3) + || a2 is null || b2 is null || a3 is null || b3 is null) + return null; + (ca, cb, ta, tb) = (a2, b2, a3, b3); + } + if (Functions.PartialFractions.IsZeroAsAValue(ta)) + return null; + var k = (ca / ta).InnerSimplified; + if (!Functions.PartialFractions.IsZeroAsAValue((cb - k * tb).InnerSimplified) + || cc is not null && tc is not null && !Functions.PartialFractions.IsZeroAsAValue((cc - k * tc).InnerSimplified)) + return null; + return k; + } + foreach (var (factor, below) in factors) + { + if (factor is Powf(var e0, var exponent) && e0 == MathS.e && exponent.ContainsNode(x)) + continue; + if (!factor.ContainsNode(x)) + { + constant = below ? constant / factor : constant * factor; + continue; + } + var (@base, power) = factor is Powf(var b1, Number.Rational p1) ? (b1, p1.ERational) : (factor, ERational.One); + if (below) + power = -power; + if (MultipleOf(@base, quadratic, 2) is { } k2) + { + plusPower += power; minusPower += power; + constant *= MathS.Pow(k2, Number.Rational.Create(power.ToLowestTerms().Numerator, power.ToLowestTerms().Denominator)); + } + else if (MultipleOf(@base, 1 + u, 1) is { } kPlus) + { + plusPower += power; + constant *= MathS.Pow(kPlus, Number.Rational.Create(power.ToLowestTerms().Numerator, power.ToLowestTerms().Denominator)); + } + else if (MultipleOf(@base, 1 - u, 1) is { } kMinus) + { + minusPower += power; + constant *= MathS.Pow(kMinus, Number.Rational.Create(power.ToLowestTerms().Numerator, power.ToLowestTerms().Denominator)); + } + else + rest = below ? rest / factor : rest * factor; + } + plusPower = plusPower.ToLowestTerms(); + minusPower = minusPower.ToLowestTerms(); + static bool Whole(ERational r) => r.Denominator.Equals(EInteger.One); + static bool HalfOdd(ERational r) => r.Denominator.Equals(EInteger.FromInt32(2)); + // Both half an odd number: (1 + u)^A (1 - u)^B is (1 + u)^(A - B) (1 - u^2)^B, one power of + // the quadratic, `(1 + a x)/(1 - a^2 x^2)^(5/2)`, which the radical rules read. + ERational? quadraticPower = null; + if (HalfOdd(plusPower) && HalfOdd(minusPower)) + { + quadraticPower = minusPower; + plusPower = (plusPower - minusPower).ToLowestTerms(); + minusPower = ERational.Zero; + } + else if (!Whole(plusPower) || !Whole(minusPower)) + return null; + if (!plusPower.Numerator.CanFitInInt32() || !minusPower.Numerator.CanFitInInt32()) + return null; + // Powers above the bar and below it, as the rules read a quotient. + Entity above = constant * rest, under = Number.Integer.One; + void Put(Entity b2, int k) + { + if (k > 0) above *= k == 1 ? b2 : MathS.Pow(b2, Number.Integer.Create(k)); + else if (k < 0) under *= k == -1 ? b2 : MathS.Pow(b2, Number.Integer.Create(-k)); + } + Put(1 + u, plusPower.Numerator.ToInt32Unchecked()); + Put(1 - u, minusPower.Numerator.ToInt32Unchecked()); + if (quadraticPower is { } qp) + { + if (qp.Sign > 0) + above *= MathS.Pow(quadratic, Number.Rational.Create(qp.Numerator, qp.Denominator)); + else + under *= MathS.Pow(quadratic, Number.Rational.Create(-qp.Numerator, qp.Denominator)); + } + return (above / under).InnerSimplified; + } + + /// + /// An exponential of a whole multiple of an inverse hyperbolic tangent of a linear, written + /// algebraically: e^(n artanh(u)) arrives as e^((n/2) ln((1 + u)/(1 - u))), and + /// is (1 + u)^n (1 - u^2)^(-n/2) for |u| < 1, where the inverse is real. The + /// integrand so written is asked as a question of its own. + /// + /// + /// The rules after it timed out on Rubi's 7.3.6 e^(artanh(a x)) x^2/(c - a^2 c x^2)^2, + /// which written so is (1 + a x) x^2/(c^2 (1 - a^2 x^2)^(5/2)), answered in half a + /// second, and on e^(4 artanh(a x))/(c - a^2 c x^2)^2, a rational function. + /// https://github.com/asc-community/AngouriMath/issues/718 + /// + internal static Entity? SolveAnExponentialOfAnInverseHyperbolicTangentAlgebraically(Entity expr, Entity.Variable x) + { + if (!Integration.AnsweringTheQuestionAsked) + return null; + var rewrote = false; + var rewritten = expr.Replace(node => + { + // `e^((n/2) ln((1 + u)/(1 - u)))`, or the same written `((1 + u)/(1 - u))^(n/2)`. + Entity plus, minus; + ERational half; + if (node is Powf(var @base, var exponent) && @base == MathS.e && exponent.ContainsNode(x)) + { + var logarithms = exponent.Nodes.Where(inner => inner is Logf(var b0, var of) && b0 == MathS.e && of.ContainsNode(x)).Distinct().ToList(); + if (logarithms.Count != 1 || logarithms[0] is not Logf(_, Divf(var p0, var m0))) + return node; + // The multiple of the logarithm read as a linear coefficient: `(1/2 ln(q))/ln(q)` + // is not reduced by the simplification. + var placeholder = Variable.CreateUnique(exponent, "l_ah"); + if (!TreeAnalyzer.TryGetPolyLinear(exponent.Substitute(logarithms[0], placeholder), placeholder, out var multiple, out var rest) + || !Functions.PartialFractions.IsZeroAsAValue(rest) || multiple.InnerSimplified.Evaled is not Number.Rational r) + return node; + (plus, minus, half) = (p0, m0, r.ERational); + } + else if (node is Powf(Divf(var p1, var m1), Number.Rational r1) && node.ContainsNode(x)) + (plus, minus, half) = (p1, m1, r1.ERational); + else + return node; + // The quotient (1 + u)/(1 - u): its two sides add to 2. + if (!Functions.PartialFractions.IsZeroAsAValue((plus + minus - 2).InnerSimplified)) + return node; + var u = ((plus - minus) / 2).InnerSimplified; + if (!TreeAnalyzer.TryGetPolyLinear(u, x, out var slope, out _) || Functions.PartialFractions.IsZeroAsAValue(slope)) + return node; + var twice = (half * 2).ToLowestTerms(); + if (!twice.Denominator.Equals(EInteger.One) || !twice.Numerator.CanFitInInt32() || twice.IsZero) + return node; + var n = twice.Numerator.ToInt32Unchecked(); + if (System.Math.Abs(n) > 24) + return node; + rewrote = true; + // An even n as a quotient of powers of the linears, an odd one as a power of a linear + // over a power of the root of the quadratic: `e^(artanh(u))` is `(1 + u)/sqrt(1 - u^2)`. + // Quotients, as the rules read them: `(1 + a x)^2 (1 - a x)^(-2)/(c - a^2 c x^2)^2` + // timed out where the same over `(1 - a x)^2` is answered at once. + Entity Power(Entity b1, int k) => k == 1 ? b1 : MathS.Pow(b1, Number.Integer.Create(k)); + var (above, below) = n > 0 ? (1 + u, 1 - u) : (1 - u, 1 + u); + var m = System.Math.Abs(n); + if (m % 2 == 0) + return Power(above, m / 2) / Power(below, m / 2); + return Power(above, m) / MathS.Pow(1 - MathS.Sqr(u), Number.Rational.Create(m, 2)); + }); + if (!rewrote) + return null; + return Integration.ComputeAsAQuestionOfItsOwn((WithThePowersOfTheLinearsGathered(expr, x) ?? rewritten.InnerSimplified), x, integrateByParts: true); + } + /// /// A rational function of a sine, or of a cosine, over a quadratic in it with a symbol /// among the coefficients: sin(x)/(a + b sin(x) + c sin(x)^2), and the same with diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 39247aeed..4cea9fdbe 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -670,6 +670,9 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr) // A power of a sine, cosine or hyperbolic one over a binomial in a power of it, over the // binomial's roots, before the rules that spent the budget on `sinh(y)^4/(a + b sinh(y)^3)`. if ((answer = IndefiniteIntegralSolver.SolveAPowerOverABinomialInAPowerOfASineOverItsRoots(expr, x)) is { }) return answer; + // An exponential of a whole multiple of an inverse hyperbolic tangent, written algebraically, + // before the rules that timed out on `e^(artanh(a x)) x^2/(c - a^2 c x^2)^2`. + if ((answer = IndefiniteIntegralSolver.SolveAnExponentialOfAnInverseHyperbolicTangentAlgebraically(expr, x)) is { }) return answer; // A rational function times the logarithm of a nested product of powers of linears, by parts, // its logarithm's derivative read off as written: `ln(e (f (a + b x)^p (c + d x)^q)^r)`. if ((answer = IndefiniteIntegralSolver.SolveARationalFunctionTimesALogarithmOfPowersOfLinears(expr, x, integrateByParts)) is { }) return answer; diff --git a/Sources/Tests/UnitTests/Calculus/AnExponentialOfAnInverseHyperbolicTangentIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/AnExponentialOfAnInverseHyperbolicTangentIntegralTest.cs new file mode 100644 index 000000000..f3dc4a3ff --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/AnExponentialOfAnInverseHyperbolicTangentIntegralTest.cs @@ -0,0 +1,60 @@ +// +// 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 +{ + /// + /// An exponential of a whole multiple of an inverse hyperbolic tangent beside powers of its + /// quadratic, written as powers of the two linears: Rubi's 7.3.6 + /// e^(n artanh(a x)) x^m (c - a^2 c x^2)^p, which timed out. Pinned where |a x| < 1, + /// the only place the integrand is real. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class AnExponentialOfAnInverseHyperbolicTangentIntegralTest + { + private static void DifferentiatesBack(string integrand, params (string, double)[] pins) + { + var integral = integrand.ToEntity().Integrate("x"); + Assert.DoesNotContain("integral(", integral.Stringize()); + + var pinned = integral.Substitute("C", 0); + Entity original = integrand.ToEntity(); + foreach (var (name, value) in pins) + { + pinned = pinned.Substitute(name, value); + original = original.Substitute(name, value); + } + var derivative = pinned.Differentiate("x"); + foreach (var at in new[] { -2.3, -0.7, 0.4, 1.9 }) + { + var got = derivative.Substitute("x", at).EvalNumerical(); + var want = original.Substitute("x", at).EvalNumerical(); + var difference = Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart); + var scale = Math.Max(1.0, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart)); + Assert.True(difference / scale < 1e-8, + $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}"); + } + } + + [Theory] + [InlineData("e^atanh(a*x)*x^2/(q-a^2*q*x^2)^2")] + [InlineData("e^(4*atanh(a*x))/(q-a^2*q*x^2)^2")] + [InlineData("1/(e^(2*atanh(a*x))*(q-a^2*q*x^2)^3)")] + [InlineData("x^2/(e^(5*atanh(a*x))*(q-a^2*q*x^2)^(27/2))")] + [InlineData("e^atanh(a+b*x)/(x*(1-a^2-2*a*b*x-b^2*x^2))")] + public void IsAnsweredAndDifferentiatesBack(string integrand) + { + DifferentiatesBack(integrand, ("a", 0.2), ("b", 0.15), ("q", 1.3)); + DifferentiatesBack(integrand, ("a", -0.3), ("b", 0.1), ("q", 0.7)); + } + } +} From 8e7eec613efb5bd40f80bdc58d8766776b425a83 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sat, 17 Oct 2026 15:09:37 +0000 Subject: [PATCH 2/4] Leave the shapes the algebraic writing gets wrong or slow An odd multiple is written with sqrt(1 - u^2), which beyond u = 1 has the other sign from e^(artanh(u)); beside a power that is not whole the integrand is real there, and 191 of 7.3.6's answers came out wrong. A power of a product splits into its factors' powers only where it is whole. Beside a sum with a reciprocal of x in it the integrand as it was is answered in under half a second, and written so took a minute or more. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- .../Integration/IndefiniteIntegralSolver.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 912443b74..94d821a7d 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -26237,6 +26237,12 @@ when two.Equals(EInteger.FromInt32(2)) && e1 == MathS.e && e2 == MathS.e var (@base, power) = factor is Powf(var b1, Number.Rational p1) ? (b1, p1.ERational) : (factor, ERational.One); if (below) power = -power; + // A power of a product splits into the powers of its factors only where it is + // whole: (c - a c x)^(9/2) is not c^(9/2) (1 - a x)^(9/2) where both are negative, + // and beyond |u| = 1 that integrand is real while the split one changes its sign. + if (!power.ToLowestTerms().Denominator.Equals(EInteger.One) + && (MultipleOf(@base, quadratic, 2) is not null || MultipleOf(@base, 1 + u, 1) is not null || MultipleOf(@base, 1 - u, 1) is not null)) + return null; if (MultipleOf(@base, quadratic, 2) is { } k2) { plusPower += power; minusPower += power; @@ -26308,6 +26314,8 @@ void Put(Entity b2, int k) if (!Integration.AnsweringTheQuestionAsked) return null; var rewrote = false; + var odd = false; + var matched = new HashSet(); var rewritten = expr.Replace(node => { // `e^((n/2) ln((1 + u)/(1 - u)))`, or the same written `((1 + u)/(1 - u))^(n/2)`. @@ -26343,6 +26351,7 @@ void Put(Entity b2, int k) if (System.Math.Abs(n) > 24) return node; rewrote = true; + matched.Add(node); // An even n as a quotient of powers of the linears, an odd one as a power of a linear // over a power of the root of the quadratic: `e^(artanh(u))` is `(1 + u)/sqrt(1 - u^2)`. // Quotients, as the rules read them: `(1 + a x)^2 (1 - a x)^(-2)/(c - a^2 c x^2)^2` @@ -26352,10 +26361,23 @@ void Put(Entity b2, int k) var m = System.Math.Abs(n); if (m % 2 == 0) return Power(above, m / 2) / Power(below, m / 2); + odd = true; return Power(above, m) / MathS.Pow(1 - MathS.Sqr(u), Number.Rational.Create(m, 2)); }); if (!rewrote) return null; + // Beside a sum with a reciprocal of x in it, `(c - c/(a x))^3`, the rules after this + // answer the integrand as it was in under half a second, and written so it took a + // minute or more. + if (expr.Nodes.Any(node => node is Sumf or Minusf && node.DirectChildren.Any(term => term.Nodes.Any(inner => + inner is Divf(_, var under) && under.ContainsNode(x) || inner is Powf(var b5, Number.Integer { IsNegative: true }) && b5.ContainsNode(x))))) + return null; + // An odd n is written with sqrt(1 - u^2), which beyond u = 1 has the other sign from + // e^(artanh(u)) on its principal branch. The integrand is real there only beside a power + // of x that is not whole -- `e^(artanh(a x)) (c - a c x)^(9/2)` -- so there it is left. + if (odd && expr.Nodes.Any(node => node is Powf(var b2, var p2) && b2 != MathS.e && b2.ContainsNode(x) + && p2 is not Number.Integer && !matched.Contains(node))) + return null; return Integration.ComputeAsAQuestionOfItsOwn((WithThePowersOfTheLinearsGathered(expr, x) ?? rewritten.InnerSimplified), x, integrateByParts: true); } From c65c07e34f163375c19b7932aa6b03ebf4d1068b Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sat, 17 Oct 2026 18:48:39 +0000 Subject: [PATCH 3/4] Put the sign of 1 - u in front of an odd multiple written algebraically sqrt(1 - u^2) is e^(artanh(u)) times (1 + u)^(-1) for u < 1 and its negative beyond, where the integrand is not real: e^(artanh(x)) x (1+x)^2 differentiated back to the negative of the integrand at x = 1.9. The sign is constant on each side of the pole at u = 1 and goes in front. The test's x^2/(e^(5 artanh(a x)) (q - a^2 q x^2)^(27/2)) is left to the other rules now, with its odd multiple beside a half-odd power. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- .../Integration/IndefiniteIntegralSolver.cs | 11 ++++++++++- ...nentialOfAnInverseHyperbolicTangentIntegralTest.cs | 6 +++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 94d821a7d..3de1e0199 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -26315,6 +26315,7 @@ void Put(Entity b2, int k) return null; var rewrote = false; var odd = false; + Entity? oddU = null; var matched = new HashSet(); var rewritten = expr.Replace(node => { @@ -26361,7 +26362,11 @@ void Put(Entity b2, int k) var m = System.Math.Abs(n); if (m % 2 == 0) return Power(above, m / 2) / Power(below, m / 2); + // Two odd multiples of different inverses would need two signs. + if (odd && oddU is not null && oddU != u) + return node; odd = true; + oddU = u; return Power(above, m) / MathS.Pow(1 - MathS.Sqr(u), Number.Rational.Create(m, 2)); }); if (!rewrote) @@ -26378,7 +26383,11 @@ inner is Divf(_, var under) && under.ContainsNode(x) || inner is Powf(var b5, Nu if (odd && expr.Nodes.Any(node => node is Powf(var b2, var p2) && b2 != MathS.e && b2.ContainsNode(x) && p2 is not Number.Integer && !matched.Contains(node))) return null; - return Integration.ComputeAsAQuestionOfItsOwn((WithThePowersOfTheLinearsGathered(expr, x) ?? rewritten.InnerSimplified), x, integrateByParts: true); + var answer = Integration.ComputeAsAQuestionOfItsOwn((WithThePowersOfTheLinearsGathered(expr, x) ?? rewritten.InnerSimplified), x, integrateByParts: true); + // An odd n is written with sqrt(1 - u^2), which is e^(artanh(u)) on its principal + // branch for u < 1 and its negative beyond: the sign sgn(1 - u) is a constant on each + // side of u = 1, where the integrand has its pole, and goes in front of the integral. + return odd && oddU is not null && answer is not null ? MathS.Signum(1 - oddU) * answer : answer; } /// diff --git a/Sources/Tests/UnitTests/Calculus/AnExponentialOfAnInverseHyperbolicTangentIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/AnExponentialOfAnInverseHyperbolicTangentIntegralTest.cs index f3dc4a3ff..ad4c492ea 100644 --- a/Sources/Tests/UnitTests/Calculus/AnExponentialOfAnInverseHyperbolicTangentIntegralTest.cs +++ b/Sources/Tests/UnitTests/Calculus/AnExponentialOfAnInverseHyperbolicTangentIntegralTest.cs @@ -14,8 +14,8 @@ namespace AngouriMath.Tests.Calculus /// /// An exponential of a whole multiple of an inverse hyperbolic tangent beside powers of its /// quadratic, written as powers of the two linears: Rubi's 7.3.6 - /// e^(n artanh(a x)) x^m (c - a^2 c x^2)^p, which timed out. Pinned where |a x| < 1, - /// the only place the integrand is real. + /// e^(n artanh(a x)) x^m (c - a^2 c x^2)^p with p whole, which timed out. Pinned + /// where |a x| < 1, the only place the integrand is real for a whole p. /// #718 /// [Trait("Area", "Calculus")] @@ -49,7 +49,7 @@ private static void DifferentiatesBack(string integrand, params (string, double) [InlineData("e^atanh(a*x)*x^2/(q-a^2*q*x^2)^2")] [InlineData("e^(4*atanh(a*x))/(q-a^2*q*x^2)^2")] [InlineData("1/(e^(2*atanh(a*x))*(q-a^2*q*x^2)^3)")] - [InlineData("x^2/(e^(5*atanh(a*x))*(q-a^2*q*x^2)^(27/2))")] + [InlineData("e^atanh(x)*x*(1+x)^2")] [InlineData("e^atanh(a+b*x)/(x*(1-a^2-2*a*b*x-b^2*x^2))")] public void IsAnsweredAndDifferentiatesBack(string integrand) { From 2cf53d17a93e48fa1f6d4aaa867a7cd2d884290a Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sat, 17 Oct 2026 18:50:42 +0000 Subject: [PATCH 4/4] Record the exponentials of inverse hyperbolic tangents in BREAKING-CHANGES Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index ffcd098b4..a6ff741eb 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -521,6 +521,33 @@ roots, `1 - x^2`, is taken as its two ([#718](https://github.com/asc-community/A | `"1/((1 - x^2)*(a + b*x^3))".ToEntity().Integrate("x")` | `integral(...)` | 1,600 characters | | `"1/(x*(1 - x^2)*(a + b*x^3))".ToEntity().Integrate("x")` | `integral(...)` | 1,637 characters | +### An exponential of a whole multiple of an inverse hyperbolic tangent is written algebraically first + +**Answers where there were none.** `e^(n artanh(u))` with u linear is written as powers of `1 + u` and `1 - u`, and then +asked as a question of its own: +- For an even `n` it is `((1 + u)/(1 - u))^(n/2)`. +- For an odd `n` it is `(1 + u)^n/(1 - u^2)^(n/2)`, times `sgn(1 - u)`. That factor is constant on each side of the pole + at `u = 1`, and it keeps the principal branch where the integrand is not real. + +The integrand is left to the other rules in two cases: an odd `n` beside a power that is not whole, where the integrand +is real beyond `|u| = 1`, and a sum holding a reciprocal of x. Twelve of Rubi's 7.3.6 that ran out of time or were left +unevaluated are now answered ([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"e^atanh(a+b*x)/(x*(1-a^2-2*a*b*x-b^2*x^2))".ToEntity().Integrate("x")` | `integral(...)` | 5,868 characters | +| `"e^atanh(a+b*x)/(x^2*(1-a^2-2*a*b*x-b^2*x^2))".ToEntity().Integrate("x")` | `integral(...)` | 9,684 characters | +| `"e^atanh(a*x)*x^2/(k-a^2*k*x^2)^2".ToEntity().Integrate("x")` | `integral(...)` | 515 characters | +| `"e^atanh(a*x)/(x^2*(k-a^2*k*x^2)^2)".ToEntity().Integrate("x")` | `integral(...)` | 1,147 characters | +| `"e^(4*atanh(a*x))/(k-a^2*k*x^2)^2".ToEntity().Integrate("x")` | `integral(...)` | 79 characters | +| `"e^(4*atanh(a*x))/(k-a^2*k*x^2)^3".ToEntity().Integrate("x")` | `integral(...)` | 286 characters | +| `"e^(4*atanh(a*x))/(k-a^2*k*x^2)^4".ToEntity().Integrate("x")` | `integral(...)` | 384 characters | +| `"1/(e^(2*atanh(a*x))*(k-a^2*k*x^2)^3)".ToEntity().Integrate("x")` | `integral(...)` | 283 characters | +| `"e^(4*atanh(a*x))*x^2/(k-a^2*k*x^2)^9".ToEntity().Integrate("x")` | `integral(...)` | 787 characters | +| `"x^2/(e^(4*atanh(a*x))*(k-a^2*k*x^2)^9)".ToEntity().Integrate("x")` | `integral(...)` | 774 characters | +| `"e^(4*atanh(a*x))*(k-a*k*x)^p".ToEntity().Integrate("x")` | `integral(...)` | 434 characters | +| `"e^atanh(x)*x*(1+x)^2".ToEntity().Integrate("x")` | `integral(...)` | 1,110 characters | + ### A sine's binomial cube below the bar is split over the cube's two factors **Answers where there were none.** `csc(c + d x)/(a + b sin(c + d x)^3)` was declined, and so were the second to fifth