@@ -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| < 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
0 commit comments