Skip to content

Commit b2ef509

Browse files
Write an exponential of a whole multiple of an inverse hyperbolic tangent algebraically (#1931)
* Integrate an exponential of an inverse hyperbolic tangent as powers of the two linears Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura * 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) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura * 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) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura * Record the exponentials of inverse hyperbolic tangents in BREAKING-CHANGES Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent 081b56f commit b2ef509

4 files changed

Lines changed: 326 additions & 0 deletions

File tree

‎BREAKING-CHANGES.md‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,33 @@ roots, `1 - x^2`, is taken as its two ([#718](https://github.com/asc-community/A
521521
| `"1/((1 - x^2)*(a + b*x^3))".ToEntity().Integrate("x")` | `integral(...)` | 1,600 characters |
522522
| `"1/(x*(1 - x^2)*(a + b*x^3))".ToEntity().Integrate("x")` | `integral(...)` | 1,637 characters |
523523

524+
### An exponential of a whole multiple of an inverse hyperbolic tangent is written algebraically first
525+
526+
**Answers where there were none.** `e^(n artanh(u))` with u linear is written as powers of `1 + u` and `1 - u`, and then
527+
asked as a question of its own:
528+
- For an even `n` it is `((1 + u)/(1 - u))^(n/2)`.
529+
- 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
530+
at `u = 1`, and it keeps the principal branch where the integrand is not real.
531+
532+
The integrand is left to the other rules in two cases: an odd `n` beside a power that is not whole, where the integrand
533+
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
534+
unevaluated are now answered ([#718](https://github.com/asc-community/AngouriMath/issues/718)).
535+
536+
| Input | Was (2.5.0) | Now |
537+
|---|---|---|
538+
| `"e^atanh(a+b*x)/(x*(1-a^2-2*a*b*x-b^2*x^2))".ToEntity().Integrate("x")` | `integral(...)` | 5,868 characters |
539+
| `"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 |
540+
| `"e^atanh(a*x)*x^2/(k-a^2*k*x^2)^2".ToEntity().Integrate("x")` | `integral(...)` | 515 characters |
541+
| `"e^atanh(a*x)/(x^2*(k-a^2*k*x^2)^2)".ToEntity().Integrate("x")` | `integral(...)` | 1,147 characters |
542+
| `"e^(4*atanh(a*x))/(k-a^2*k*x^2)^2".ToEntity().Integrate("x")` | `integral(...)` | 79 characters |
543+
| `"e^(4*atanh(a*x))/(k-a^2*k*x^2)^3".ToEntity().Integrate("x")` | `integral(...)` | 286 characters |
544+
| `"e^(4*atanh(a*x))/(k-a^2*k*x^2)^4".ToEntity().Integrate("x")` | `integral(...)` | 384 characters |
545+
| `"1/(e^(2*atanh(a*x))*(k-a^2*k*x^2)^3)".ToEntity().Integrate("x")` | `integral(...)` | 283 characters |
546+
| `"e^(4*atanh(a*x))*x^2/(k-a^2*k*x^2)^9".ToEntity().Integrate("x")` | `integral(...)` | 787 characters |
547+
| `"x^2/(e^(4*atanh(a*x))*(k-a^2*k*x^2)^9)".ToEntity().Integrate("x")` | `integral(...)` | 774 characters |
548+
| `"e^(4*atanh(a*x))*(k-a*k*x)^p".ToEntity().Integrate("x")` | `integral(...)` | 434 characters |
549+
| `"e^atanh(x)*x*(1+x)^2".ToEntity().Integrate("x")` | `integral(...)` | 1,110 characters |
550+
524551
### A sine's binomial cube below the bar is split over the cube's two factors
525552

526553
**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

‎Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs‎

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26784,6 +26784,242 @@ static void Add(Dictionary<int, ERational> into, int k, ERational value)
2678426784
return null;
2678526785
}
2678626786

26787+
/// <summary>
26788+
/// <c>e^(n artanh(u)) (K (1 - u^2))^p (1 + u)^q (1 - u)^r</c> times the rest, written as
26789+
/// <c>K^p (1 + u)^(n/2 + p + q) (1 - u)^(-n/2 + p + r)</c> times the rest, as Rubi writes it,
26790+
/// both powers whole or both half an odd number, the generic case for the constant; else null.
26791+
/// <c>e^(4 artanh(a x))/(c - a^2 c x^2)^2</c> is <c>1/(c^2 (1 - a x)^4)</c>.
26792+
/// </summary>
26793+
private static Entity? WithThePowersOfTheLinearsGathered(Entity expr, Entity.Variable x)
26794+
{
26795+
static IEnumerable<(Entity Factor, bool Below)> Factors(Entity e, bool below) => e switch
26796+
{
26797+
Mulf(var l, var r) => Factors(l, below).Concat(Factors(r, below)),
26798+
Divf(var l, var r) => Factors(l, below).Concat(Factors(r, !below)),
26799+
_ => new[] { (e, below) }
26800+
};
26801+
Entity? u = null;
26802+
ERational plusPower = ERational.Zero, minusPower = ERational.Zero;
26803+
Entity constant = Number.Integer.One, rest = Number.Integer.One;
26804+
var factors = Factors(expr, false).ToList();
26805+
// The exponentials first, which fix u.
26806+
foreach (var (factor, below) in factors)
26807+
{
26808+
if (factor is not Powf(var e0, var exponent) || e0 != MathS.e || !exponent.ContainsNode(x))
26809+
continue;
26810+
var logarithms = exponent.Nodes.Where(inner => inner is Logf(var b0, var of) && b0 == MathS.e && of.ContainsNode(x)).Distinct().ToList();
26811+
if (logarithms.Count != 1 || logarithms[0] is not Logf(_, Divf(var plus, var minus))
26812+
|| !Functions.PartialFractions.IsZeroAsAValue((plus + minus - 2).InnerSimplified))
26813+
return null;
26814+
var placeholder = Variable.CreateUnique(exponent, "l_ah");
26815+
if (!TreeAnalyzer.TryGetPolyLinear(exponent.Substitute(logarithms[0], placeholder), placeholder, out var multiple, out var free)
26816+
|| !Functions.PartialFractions.IsZeroAsAValue(free) || multiple.InnerSimplified.Evaled is not Number.Rational half)
26817+
return null;
26818+
var thisU = ((plus - minus) / 2).InnerSimplified;
26819+
if (u is not null && !Functions.PartialFractions.IsZeroAsAValue((u - thisU).InnerSimplified))
26820+
return null;
26821+
u = thisU;
26822+
var signed = below ? -half.ERational : half.ERational;
26823+
plusPower += signed;
26824+
minusPower -= signed;
26825+
}
26826+
if (u is null || !TreeAnalyzer.TryGetPolyLinear(u, x, out var slope, out var intercept))
26827+
return null;
26828+
var quadratic = 1 - MathS.Sqr(u);
26829+
// K with candidate = K target as polynomials in x, else null.
26830+
Entity? MultipleOf(Entity candidate, Entity target, int degree)
26831+
{
26832+
Entity ca, cb, ta, tb;
26833+
Entity? cc = null, tc = null;
26834+
if (degree == 2)
26835+
{
26836+
if (!TreeAnalyzer.TryGetPolyQuadratic(candidate, x, out var a2, out var b2, out var c2)
26837+
|| !TreeAnalyzer.TryGetPolyQuadratic(target, x, out var a3, out var b3, out var c3)
26838+
|| a2 is null || b2 is null || c2 is null || a3 is null || b3 is null || c3 is null)
26839+
return null;
26840+
(ca, cb, cc, ta, tb, tc) = (a2, b2, c2, a3, b3, c3);
26841+
}
26842+
else
26843+
{
26844+
if (!TreeAnalyzer.TryGetPolyLinear(candidate, x, out var a2, out var b2)
26845+
|| !TreeAnalyzer.TryGetPolyLinear(target, x, out var a3, out var b3)
26846+
|| a2 is null || b2 is null || a3 is null || b3 is null)
26847+
return null;
26848+
(ca, cb, ta, tb) = (a2, b2, a3, b3);
26849+
}
26850+
if (Functions.PartialFractions.IsZeroAsAValue(ta))
26851+
return null;
26852+
var k = (ca / ta).InnerSimplified;
26853+
if (!Functions.PartialFractions.IsZeroAsAValue((cb - k * tb).InnerSimplified)
26854+
|| cc is not null && tc is not null && !Functions.PartialFractions.IsZeroAsAValue((cc - k * tc).InnerSimplified))
26855+
return null;
26856+
return k;
26857+
}
26858+
foreach (var (factor, below) in factors)
26859+
{
26860+
if (factor is Powf(var e0, var exponent) && e0 == MathS.e && exponent.ContainsNode(x))
26861+
continue;
26862+
if (!factor.ContainsNode(x))
26863+
{
26864+
constant = below ? constant / factor : constant * factor;
26865+
continue;
26866+
}
26867+
var (@base, power) = factor is Powf(var b1, Number.Rational p1) ? (b1, p1.ERational) : (factor, ERational.One);
26868+
if (below)
26869+
power = -power;
26870+
// A power of a product splits into the powers of its factors only where it is
26871+
// whole: (c - a c x)^(9/2) is not c^(9/2) (1 - a x)^(9/2) where both are negative,
26872+
// and beyond |u| = 1 that integrand is real while the split one changes its sign.
26873+
if (!power.ToLowestTerms().Denominator.Equals(EInteger.One)
26874+
&& (MultipleOf(@base, quadratic, 2) is not null || MultipleOf(@base, 1 + u, 1) is not null || MultipleOf(@base, 1 - u, 1) is not null))
26875+
return null;
26876+
if (MultipleOf(@base, quadratic, 2) is { } k2)
26877+
{
26878+
plusPower += power; minusPower += power;
26879+
constant *= MathS.Pow(k2, Number.Rational.Create(power.ToLowestTerms().Numerator, power.ToLowestTerms().Denominator));
26880+
}
26881+
else if (MultipleOf(@base, 1 + u, 1) is { } kPlus)
26882+
{
26883+
plusPower += power;
26884+
constant *= MathS.Pow(kPlus, Number.Rational.Create(power.ToLowestTerms().Numerator, power.ToLowestTerms().Denominator));
26885+
}
26886+
else if (MultipleOf(@base, 1 - u, 1) is { } kMinus)
26887+
{
26888+
minusPower += power;
26889+
constant *= MathS.Pow(kMinus, Number.Rational.Create(power.ToLowestTerms().Numerator, power.ToLowestTerms().Denominator));
26890+
}
26891+
else
26892+
rest = below ? rest / factor : rest * factor;
26893+
}
26894+
plusPower = plusPower.ToLowestTerms();
26895+
minusPower = minusPower.ToLowestTerms();
26896+
static bool Whole(ERational r) => r.Denominator.Equals(EInteger.One);
26897+
static bool HalfOdd(ERational r) => r.Denominator.Equals(EInteger.FromInt32(2));
26898+
// Both half an odd number: (1 + u)^A (1 - u)^B is (1 + u)^(A - B) (1 - u^2)^B, one power of
26899+
// the quadratic, `(1 + a x)/(1 - a^2 x^2)^(5/2)`, which the radical rules read.
26900+
ERational? quadraticPower = null;
26901+
if (HalfOdd(plusPower) && HalfOdd(minusPower))
26902+
{
26903+
quadraticPower = minusPower;
26904+
plusPower = (plusPower - minusPower).ToLowestTerms();
26905+
minusPower = ERational.Zero;
26906+
}
26907+
else if (!Whole(plusPower) || !Whole(minusPower))
26908+
return null;
26909+
if (!plusPower.Numerator.CanFitInInt32() || !minusPower.Numerator.CanFitInInt32())
26910+
return null;
26911+
// Powers above the bar and below it, as the rules read a quotient.
26912+
Entity above = constant * rest, under = Number.Integer.One;
26913+
void Put(Entity b2, int k)
26914+
{
26915+
if (k > 0) above *= k == 1 ? b2 : MathS.Pow(b2, Number.Integer.Create(k));
26916+
else if (k < 0) under *= k == -1 ? b2 : MathS.Pow(b2, Number.Integer.Create(-k));
26917+
}
26918+
Put(1 + u, plusPower.Numerator.ToInt32Unchecked());
26919+
Put(1 - u, minusPower.Numerator.ToInt32Unchecked());
26920+
if (quadraticPower is { } qp)
26921+
{
26922+
if (qp.Sign > 0)
26923+
above *= MathS.Pow(quadratic, Number.Rational.Create(qp.Numerator, qp.Denominator));
26924+
else
26925+
under *= MathS.Pow(quadratic, Number.Rational.Create(-qp.Numerator, qp.Denominator));
26926+
}
26927+
return (above / under).InnerSimplified;
26928+
}
26929+
26930+
/// <summary>
26931+
/// An exponential of a whole multiple of an inverse hyperbolic tangent of a linear, written
26932+
/// algebraically: <c>e^(n artanh(u))</c> arrives as <c>e^((n/2) ln((1 + u)/(1 - u)))</c>, and
26933+
/// is <c>(1 + u)^n (1 - u^2)^(-n/2)</c> for <c>|u| &lt; 1</c>, where the inverse is real. The
26934+
/// integrand so written is asked as a question of its own.
26935+
/// </summary>
26936+
/// <remarks>
26937+
/// The rules after it timed out on Rubi's 7.3.6 <c>e^(artanh(a x)) x^2/(c - a^2 c x^2)^2</c>,
26938+
/// which written so is <c>(1 + a x) x^2/(c^2 (1 - a^2 x^2)^(5/2))</c>, answered in half a
26939+
/// second, and on <c>e^(4 artanh(a x))/(c - a^2 c x^2)^2</c>, a rational function.
26940+
/// https://github.com/asc-community/AngouriMath/issues/718
26941+
/// </remarks>
26942+
internal static Entity? SolveAnExponentialOfAnInverseHyperbolicTangentAlgebraically(Entity expr, Entity.Variable x)
26943+
{
26944+
if (!Integration.AnsweringTheQuestionAsked)
26945+
return null;
26946+
var rewrote = false;
26947+
var odd = false;
26948+
Entity? oddU = null;
26949+
var matched = new HashSet<Entity>();
26950+
var rewritten = expr.Replace(node =>
26951+
{
26952+
// `e^((n/2) ln((1 + u)/(1 - u)))`, or the same written `((1 + u)/(1 - u))^(n/2)`.
26953+
Entity plus, minus;
26954+
ERational half;
26955+
if (node is Powf(var @base, var exponent) && @base == MathS.e && exponent.ContainsNode(x))
26956+
{
26957+
var logarithms = exponent.Nodes.Where(inner => inner is Logf(var b0, var of) && b0 == MathS.e && of.ContainsNode(x)).Distinct().ToList();
26958+
if (logarithms.Count != 1 || logarithms[0] is not Logf(_, Divf(var p0, var m0)))
26959+
return node;
26960+
// The multiple of the logarithm read as a linear coefficient: `(1/2 ln(q))/ln(q)`
26961+
// is not reduced by the simplification.
26962+
var placeholder = Variable.CreateUnique(exponent, "l_ah");
26963+
if (!TreeAnalyzer.TryGetPolyLinear(exponent.Substitute(logarithms[0], placeholder), placeholder, out var multiple, out var rest)
26964+
|| !Functions.PartialFractions.IsZeroAsAValue(rest) || multiple.InnerSimplified.Evaled is not Number.Rational r)
26965+
return node;
26966+
(plus, minus, half) = (p0, m0, r.ERational);
26967+
}
26968+
else if (node is Powf(Divf(var p1, var m1), Number.Rational r1) && node.ContainsNode(x))
26969+
(plus, minus, half) = (p1, m1, r1.ERational);
26970+
else
26971+
return node;
26972+
// The quotient (1 + u)/(1 - u): its two sides add to 2.
26973+
if (!Functions.PartialFractions.IsZeroAsAValue((plus + minus - 2).InnerSimplified))
26974+
return node;
26975+
var u = ((plus - minus) / 2).InnerSimplified;
26976+
if (!TreeAnalyzer.TryGetPolyLinear(u, x, out var slope, out _) || Functions.PartialFractions.IsZeroAsAValue(slope))
26977+
return node;
26978+
var twice = (half * 2).ToLowestTerms();
26979+
if (!twice.Denominator.Equals(EInteger.One) || !twice.Numerator.CanFitInInt32() || twice.IsZero)
26980+
return node;
26981+
var n = twice.Numerator.ToInt32Unchecked();
26982+
if (System.Math.Abs(n) > 24)
26983+
return node;
26984+
rewrote = true;
26985+
matched.Add(node);
26986+
// An even n as a quotient of powers of the linears, an odd one as a power of a linear
26987+
// over a power of the root of the quadratic: `e^(artanh(u))` is `(1 + u)/sqrt(1 - u^2)`.
26988+
// Quotients, as the rules read them: `(1 + a x)^2 (1 - a x)^(-2)/(c - a^2 c x^2)^2`
26989+
// timed out where the same over `(1 - a x)^2` is answered at once.
26990+
Entity Power(Entity b1, int k) => k == 1 ? b1 : MathS.Pow(b1, Number.Integer.Create(k));
26991+
var (above, below) = n > 0 ? (1 + u, 1 - u) : (1 - u, 1 + u);
26992+
var m = System.Math.Abs(n);
26993+
if (m % 2 == 0)
26994+
return Power(above, m / 2) / Power(below, m / 2);
26995+
// Two odd multiples of different inverses would need two signs.
26996+
if (odd && oddU is not null && oddU != u)
26997+
return node;
26998+
odd = true;
26999+
oddU = u;
27000+
return Power(above, m) / MathS.Pow(1 - MathS.Sqr(u), Number.Rational.Create(m, 2));
27001+
});
27002+
if (!rewrote)
27003+
return null;
27004+
// Beside a sum with a reciprocal of x in it, `(c - c/(a x))^3`, the rules after this
27005+
// answer the integrand as it was in under half a second, and written so it took a
27006+
// minute or more.
27007+
if (expr.Nodes.Any(node => node is Sumf or Minusf && node.DirectChildren.Any(term => term.Nodes.Any(inner =>
27008+
inner is Divf(_, var under) && under.ContainsNode(x) || inner is Powf(var b5, Number.Integer { IsNegative: true }) && b5.ContainsNode(x)))))
27009+
return null;
27010+
// An odd n is written with sqrt(1 - u^2), which beyond u = 1 has the other sign from
27011+
// e^(artanh(u)) on its principal branch. The integrand is real there only beside a power
27012+
// of x that is not whole -- `e^(artanh(a x)) (c - a c x)^(9/2)` -- so there it is left.
27013+
if (odd && expr.Nodes.Any(node => node is Powf(var b2, var p2) && b2 != MathS.e && b2.ContainsNode(x)
27014+
&& p2 is not Number.Integer && !matched.Contains(node)))
27015+
return null;
27016+
var answer = Integration.ComputeAsAQuestionOfItsOwn((WithThePowersOfTheLinearsGathered(expr, x) ?? rewritten.InnerSimplified), x, integrateByParts: true);
27017+
// An odd n is written with sqrt(1 - u^2), which is e^(artanh(u)) on its principal
27018+
// branch for u < 1 and its negative beyond: the sign sgn(1 - u) is a constant on each
27019+
// side of u = 1, where the integrand has its pole, and goes in front of the integral.
27020+
return odd && oddU is not null && answer is not null ? MathS.Signum(1 - oddU) * answer : answer;
27021+
}
27022+
2678727023
/// <summary>
2678827024
/// A rational function of a sine, or of a cosine, over a quadratic in it with a symbol
2678927025
/// among the coefficients: <c>sin(x)/(a + b sin(x) + c sin(x)^2)</c>, and the same with

‎Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -680,6 +680,9 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr)
680680
// A power of a sine, cosine or hyperbolic one over a binomial in a power of it, over the
681681
// binomial's roots, before the rules that spent the budget on `sinh(y)^4/(a + b sinh(y)^3)`.
682682
if ((answer = IndefiniteIntegralSolver.SolveAPowerOverABinomialInAPowerOfASineOverItsRoots(expr, x)) is { }) return answer;
683+
// An exponential of a whole multiple of an inverse hyperbolic tangent, written algebraically,
684+
// before the rules that timed out on `e^(artanh(a x)) x^2/(c - a^2 c x^2)^2`.
685+
if ((answer = IndefiniteIntegralSolver.SolveAnExponentialOfAnInverseHyperbolicTangentAlgebraically(expr, x)) is { }) return answer;
683686
// A power of a cosine over a power of k (1 +- sin), cancelled: `cos^2/((e + f x)(a + a sin))`.
684687
if ((answer = IndefiniteIntegralSolver.SolveACosinePowerOverABinomialInTheSineByCancelling(expr, x)) is { }) return answer;
685688
// A function of an inverse function of c + d x beside powers of multiples of it, under

0 commit comments

Comments
 (0)