diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md
index 0909ba42e..375222c37 100644
--- a/BREAKING-CHANGES.md
+++ b/BREAKING-CHANGES.md
@@ -129,6 +129,23 @@ since semantic versioning admits it.
Measured on the suite: **7 of 8,525 tests** referenced the old reading — five boolean-solver inputs
written `A | B`, and two that existed to record this spelling as at risk.
+### An inverse hyperbolic function over a half-odd power of an even quadratic is integrated by parts against it
+
+**Answers where there were none.** `atanh(a x)/(c + d x^2)^(3/2)` was declined, and so were its powers up to `^(9/2)`,
+`acosh(a x)` over the same powers, and `(a + b asinh(c x))` and `(a + b acosh(c x))` over `(d + k x^2)^(3/2)` to
+`^(7/2)`: Rubi's 7.1.4, 7.2.4, 7.2.5 and 7.3.4. By parts, the antiderivative of the power is `x` times powers of
+`(c + d x^2)^(-1/2)`, and the logarithm's derivative is even in `x`, so the remainder is `x` times a function of
+`x^2`; under `w = x^2` each of its terms is a power of `c + d w` over `1 - a^2 w` or over the root of a linear,
+answered at once. Not where the quadratic is the function's own, `1 - a^2 x^2` for the artanh, which the
+substitution under the hyperbolic function answers ([#233](https://github.com/asc-community/AngouriMath/issues/233)).
+
+| Input | Was (2.5.0) | Now |
+|---|---|---|
+| `"atanh(a*x)/(c+d*x^2)^(3/2)".ToEntity().Integrate("x")` | `integral(...)` | 1,034 characters |
+| `"atanh(a*x)/(c+d*x^2)^(9/2)".ToEntity().Integrate("x")` | `integral(...)` | 7,136 characters |
+| `"acosh(a*x)/(c+d*x^2)^(9/2)".ToEntity().Integrate("x")` | `integral(...)` | 2,409 characters |
+| `"(a+b*asinh(c*x))/(d+k*x^2)^(7/2)".ToEntity().Integrate("x")` | `integral(...)` | 1,843 characters |
+
### A power of x times `atanh(sqrt(e) x/sqrt(d + e x^2))` is integrated by parts against the root
**Answers where there were none.** `x atanh(sqrt(e) x/sqrt(d + e x^2))` was declined, and so were twelve more of Rubi's
@@ -142,6 +159,21 @@ quotient of two sums with a root in each, which the search never brought down to
| `"atanh(x*sqrt(k)/sqrt(d+k*x^2))".ToEntity().Integrate("x")` | `integral(...)` | `x atanh(...) - sqrt(d + k x^2)/sqrt(k)`, written in the logarithm |
| `"atanh(x*sqrt(k)/sqrt(d+k*x^2))/x^5".ToEntity().Integrate("x")` | `integral(...)` | by parts onto `1/(x^4 sqrt(d + k x^2))` |
+### A power of x times the arsech or arcsch of `sqrt(x)` is integrated by parts
+
+**Answers where there were none.** Every power from `x^0` to `x^3` times `asech(sqrt(x))` or `acsch(sqrt(x))`, Rubi's
+7.5.2 and 7.6.2, was declined or ran past thirty seconds. By parts the remainder is `K/(2 (m + 1))` times the
+integral of `x^m/sqrt(1 - x)`, or of `x^m/sqrt(1 + x)` for the arcsch, where `K = sqrt(1 - x)/(sqrt(x) sqrt(1/x - 1))` is `1` for `x > 0` and `-1` for `x < 0`, and the arcsch's
+`K = sqrt(1 + x)/(sqrt(x) sqrt(1/x + 1))` is `-1` between `-1` and `0` and `1` elsewhere. `K` is written in the answer
+as it is defined, so the answer holds on the negative half-line too
+([#233](https://github.com/asc-community/AngouriMath/issues/233)).
+
+| Input | Was (2.5.0) | Now |
+|---|---|---|
+| `"x^3*asech(sqrt(x))".ToEntity().Integrate("x")` | no answer in thirty seconds | 210 characters |
+| `"x*acsch(sqrt(x))".ToEntity().Integrate("x")` | `integral(...)` | 146 characters |
+| `"x^3*acsch(sqrt(x))".ToEntity().Integrate("x")` | no answer in thirty seconds | 205 characters |
+
### A power of x is taken out of a bracket to find the derivative of a product of powers
**Answers where there were none.** `x^(2n) (c + d x)^n (2 c x + 3 d x^2)`, Rubi's 1.3.1, was declined. A bracket that
@@ -283,6 +315,19 @@ the working digits, or `MathS.Settings.PrecisionErrorZeroRange` where a caller h
still that integer, so `e^(-123.456)` is 0 as it was.
[#1338](https://github.com/asc-community/AngouriMath/issues/1338).
+### A polynomial over a power of the linear of an artanh is written in powers of the linear
+
+**Answers where there were none.** `x (a + b atanh(c x))^2/(d + c d x)^3`, Rubi's 7.3.4, ran out of time. In powers of
+`d + c d x`, `x` is `((d + c d x) - d)/(c d)`, and the integrand is two terms, each a power of one of the logarithm's
+linears beside a function of it, which are answered under `t = (1 + c x)/(1 - c x)`. Where the polynomial's degree is
+at most the power less two, so that no term is over the linear alone, and the function is not the logarithm's first
+power, which by parts answers as before ([#233](https://github.com/asc-community/AngouriMath/issues/233)).
+
+| Input | Was (2.5.0) | Now |
+|---|---|---|
+| `"x*(a+b*atanh(c*x))^2/(d+c*d*x)^3".ToEntity().Integrate("x")` | `integral(...)` | 731 characters |
+| `"x*(a+b*acoth(c*x))^2/(d+c*d*x)^3".ToEntity().Integrate("x")` | `integral(...)` | 756 characters |
+
### An arctangent over a power of `d (1 + i c x)` is integrated under the tangent
**Answers where there were none.** `(a + b atan(c x))^2/(d + i c d x)^2` ran out of time, and so did seven more of
@@ -297,6 +342,20 @@ becomes a polynomial in `u` times exponentials and trigonometric functions, and
| `"(a+b*atan(c*x))^2/(1+i*c*x)^4".ToEntity().Integrate("x")` | `integral(...)` | 731 characters |
| `"(a+b*atan(c*x))^3/(d+i*c*d*x)^4".ToEntity().Integrate("x")` | `integral(...)` | 1,202 characters |
+### An arcoth of a linear is integrated as its artanh plus a constant
+
+**Answers where there were none.** `acoth(x)^2/(1 - x^2)^2`, Rubi's 7.4.1, was declined, where
+`(atanh(x) + k)^2/(1 - x^2)^2` is answered. `acoth(L) - atanh(L)` is `i pi/2` where `|L| < 1` and `-i pi/2` where `|L| > 1`:
+constant on each interval, and not the same constant on each. Such an integrand is now asked with a symbol `K` for it,
+as one in `atanh(L) + K`, and the answer is written back with `K = acoth(L) - atanh(L)`, so that it holds on every
+interval without a case for each. Only where nothing else answers the integrand as written
+([#233](https://github.com/asc-community/AngouriMath/issues/233)).
+
+| Input | Was (2.5.0) | Now |
+|---|---|---|
+| `"acoth(x)^2/(1-x^2)^2".ToEntity().Integrate("x")` | `integral(...)` | 1,304 characters, with `(ln((x + 1)/(x - 1)) - ln((1 + x)/(1 - x)))/2` for `K` |
+| `"acoth(c*x)^2/(1-c^2*x^2)^2".ToEntity().Integrate("x")` | `integral(...)` | 1,681 characters |
+
### A function of the hyperbolic tangent alone is integrated under `u = tanh`
**Answers where there were none.** `tanh(x)/sqrt(a + b tanh(x)^2 + c tanh(x)^4)` was declined, and so were 35 more of
diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
index 31dc3156e..a01e6cfe6 100644
--- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
@@ -810,6 +810,104 @@ private static bool IsADerivativeOfAFactorBelowTheBar(Entity expr, Entity.Variab
return (constant * (MathS.Pow(x, raised) * logarithm - 2 * slope * integral) / raised).InnerSimplified;
}
+ ///
+ /// A + B F(a x) for F the inverse hyperbolic sine, cosine or tangent, over a
+ /// half-odd power Q^(n + 1/2) of Q = d + e x^2, n >= 1, by parts against
+ /// F. Q^(-n - 1/2) is ((x Q^(1/2 - n))' + (2n - 2) Q^(1/2 - n))/((2n - 1) d), so
+ /// its antiderivative is S = x sum alpha_k Q^(1/2 - k), k = 1..n; and the logarithm
+ /// F is written as has the derivative 2a/(1 - a^2 x^2), a/sqrt(a^2 x^2 + 1) or
+ /// a/sqrt(a^2 x^2 - 1), even in x. So the remainder F' S is x times a
+ /// function of x^2, and under w = x^2 each of its terms is a power of the linear
+ /// d + e w over 1 - a^2 w or the root of another linear, closed by rationalising.
+ /// Rubi's 7.1.4, 7.2.4, 7.2.5 and 7.3.4, atanh(a x)/(c + d x^2)^(9/2) and the like.
+ /// https://github.com/asc-community/AngouriMath/issues/233
+ ///
+ ///
+ /// By parts in the search differentiates the logarithm as written, and the remainder's
+ /// numerator, a (1 - a x) + a (1 + a x) uncollected, was split into terms even in
+ /// x, each a harder question than the whole: from (c + d x^2)^(7/2) on they were
+ /// declined or ran out of time. Asked whole the remainder in w ran out of time too, and
+ /// each term of it is answered in half a second at most. Not where Q is the function's own quadratic, which the
+ /// substitution under the hyperbolic function answers; and only an argument a x, which
+ /// keeps the remainder odd. The identities hold for real x on every branch the
+ /// logarithms take, the cuts included, where each is a constant i pi off its modulus.
+ ///
+ internal static Entity? SolveByPartsAgainstAnInverseHyperbolicOverAHalfOddPowerOfAQuadratic(Entity expr, Entity.Variable x, bool integrateByParts)
+ {
+ if (!Integration.AnsweringTheQuestionAskedOrOneBelow)
+ return null;
+ var logarithms = expr.Nodes.Where(node => node is Logf(var logBase, var of) && logBase == MathS.e && of.ContainsNode(x)).Distinct().ToList();
+ if (logarithms.Count != 1 || logarithms[0] is not Logf(_, var antilogarithm)
+ || ReadAnInverseHyperbolic(antilogarithm, x) is not var (argument, kind) || kind > 3
+ || !TreeAnalyzer.TryGetPolyLinear(argument, x, out var a, out var offset) || a is null
+ || TreeAnalyzer.IsZero(a) || offset is { } && !TreeAnalyzer.IsZero(offset))
+ return null;
+ var logarithm = logarithms[0];
+ Entity constant = Number.Integer.One;
+ Entity? function = null, quadratic = null;
+ Number.Rational? power = null;
+ foreach (var (factor, underneath) in FactorsOfTheIntegrand(expr))
+ {
+ if (!factor.ContainsNode(x))
+ {
+ constant = underneath ? constant / factor : constant * factor;
+ continue;
+ }
+ if (!underneath && function is null && factor.ContainsNode(logarithm))
+ {
+ function = factor;
+ continue;
+ }
+ if (quadratic is not null || factor is not Powf(var @base, Number.Rational exponent) || exponent is Number.Integer)
+ return null;
+ (quadratic, power) = (@base, underneath ? exponent : (Number.Rational)(-exponent));
+ }
+ // A half-odd power n + 1/2 below the bar, n >= 1.
+ if (function is null || quadratic is null || power is null
+ || !power.ERational.Denominator.Equals(EInteger.FromInt32(2)) || power.ERational.Numerator.CompareTo(EInteger.FromInt32(3)) < 0
+ || !power.ERational.Numerator.CanFitInInt32())
+ return null;
+ var n = (power.ERational.Numerator.ToInt32Unchecked() - 1) / 2;
+ if (!TreeAnalyzer.TryGetPolyQuadratic(quadratic, x, out var e, out var linear, out var d)
+ || e is null || d is null || linear is { } && !TreeAnalyzer.IsZero(linear) || VanishesIdentically(e) || VanishesIdentically(d))
+ return null;
+ // The function linear in its logarithm: A' + B' ln(...).
+ var symbol = Variable.CreateUnique(expr, "w_inv");
+ if (!TreeAnalyzer.TryGetPolyLinear(function.Substitute(logarithm, symbol), symbol, out var slope, out var free)
+ || slope is null || slope.ContainsNode(x) || free is { } && free.ContainsNode(x) || VanishesIdentically(slope))
+ return null;
+ var w = Variable.CreateUnique(expr + symbol, "w_sq");
+ var squared = MathS.Sqr(a);
+ var (own, derivativeInW) = kind switch
+ {
+ 1 => (1 + squared * MathS.Sqr(x), a / MathS.Sqrt(squared * w + 1)),
+ 2 => (squared * MathS.Sqr(x) - 1, a / MathS.Sqrt(squared * w - 1)),
+ _ => (1 - squared * MathS.Sqr(x), 2 * a / (1 - squared * w)),
+ };
+ if (AreProportionalAtSampledPoints(quadratic, own, x))
+ return null;
+ // alpha_k of S_n by S_n = x Q^(1/2 - n)/((2n - 1) d) + (2n - 2)/((2n - 1) d) S_(n - 1).
+ var alphas = new Entity[n + 1];
+ for (var m = 1; m <= n; m++)
+ {
+ for (var k = 1; k < m; k++)
+ alphas[k] = (2 * m - 2) * alphas[k] / ((2 * m - 1) * d);
+ alphas[m] = Number.Integer.One / ((2 * m - 1) * d);
+ }
+ Entity antiderivative = Number.Integer.Zero, remainder = Number.Integer.Zero;
+ for (var k = 1; k <= n; k++)
+ {
+ var alpha = alphas[k].InnerSimplified;
+ antiderivative += alpha * x * MathS.Pow(quadratic, Number.Rational.Create(1 - 2 * k, 2));
+ var term = MathS.Pow(d + e * w, Number.Rational.Create(1 - 2 * k, 2)) * derivativeInW;
+ if (Integration.ComputeAsAQuestionOfItsOwn(term, w, integrateByParts) is not { } integrated
+ || integrated.Nodes.Any(node => node is Integralf || node == MathS.NaN))
+ return null;
+ remainder += alpha / 2 * integrated.Substitute(w, MathS.Sqr(x));
+ }
+ return constant * (function * antiderivative - slope * remainder);
+ }
+
///
/// A power of a quotient of two linears beside whole powers of the two, under the Moebius
/// substitution t = (p + q x)/(r + s x): then p + q x = t (r + s x),
@@ -11757,6 +11855,70 @@ node is Powf(var q, Number.Rational r) && r is not Number.Integer && q != first
return answer;
}
+ ///
+ /// x^m arsech(sqrt(x)) or x^m arcsch(sqrt(x)), m >= 0, by parts. The
+ /// logarithm ln(p + s) each is written as, p = 1/sqrt(x) and s = sqrt(1/x -+ 1),
+ /// has the derivative -p^3/(2 s), since p' = -p^3/2 and s^2 = p^2 -+ 1; so the
+ /// remainder is integral x^m p/s/(2 (m + 1)). And K = (p/s) sqrt(r), r = 1 -+ x,
+ /// has K^2 = 1: it is 1 or -1, constant where it is continuous, and the remainder is
+ /// K integral x^m/sqrt(r), a polynomial times the root, with K written as it is
+ /// defined. Rubi's 7.5.2 and 7.6.2.
+ /// https://github.com/asc-community/AngouriMath/issues/233
+ ///
+ ///
+ /// Under u = sqrt(x) the substitution asked 2 u^7 arsech(u) one level down, where
+ /// the rules that answer it at the top decline, and x^3 arsech(sqrt(x)) ran out of time;
+ /// x^2 arsech(sqrt(x)) took thirteen seconds and x^3 arcsch(sqrt(x)) twenty-four. For the
+ /// secant's K is -1 where x < 0, and for the cosecant's where -1 < x < 0: left
+ /// out, the answer is wrong there, as the one the substitution gave x arcsch(sqrt(x)) was.
+ ///
+ internal static Entity? SolveByPartsAgainstAnInverseHyperbolicSecantOfARoot(Entity expr, Entity.Variable x, bool integrateByParts)
+ {
+ Entity constant = Number.Integer.One;
+ Logf? logarithm = null;
+ var power = 0;
+ foreach (var (factor, underneath) in FactorsOfTheIntegrand(expr))
+ {
+ if (!factor.ContainsNode(x))
+ {
+ constant = underneath ? constant / factor : constant * factor;
+ continue;
+ }
+ if (!underneath && logarithm is null && factor is Logf ln && ln.Base == MathS.e)
+ {
+ logarithm = ln;
+ continue;
+ }
+ if (APowerOfTheVariable(factor, x) is not { } k)
+ return null;
+ power += underneath ? -k : k;
+ }
+ if (logarithm is null || power < 0 || power > 24 || logarithm.Antilogarithm is not Sumf(var left, var right))
+ return null;
+ // p = 1/sqrt(x) and s = sqrt(1/x -+ 1), compared by value, in whatever spelling the parser gave them.
+ var reciprocalRoot = 1 / MathS.Sqrt(x);
+ foreach (var (p, s) in new[] { (left, right), (right, left) })
+ {
+ if (!TryReadAHalfPower(s, out var radicand, out var numerator) || numerator != 1
+ || !Functions.PartialFractions.HoldsAtSampledPoints(p, reciprocalRoot, x))
+ continue;
+ int sign;
+ if (Functions.PartialFractions.HoldsAtSampledPoints(radicand, 1 / x - 1, x)) sign = -1;
+ else if (Functions.PartialFractions.HoldsAtSampledPoints(radicand, 1 / x + 1, x)) sign = 1;
+ else continue;
+ var r = 1 + sign * x;
+ var raised = power + 1;
+ var below = power == 0 ? 1 / MathS.Sqrt(r) : (power == 1 ? x : MathS.Pow(x, power)) / MathS.Sqrt(r);
+ if (Integration.ComputeAsAQuestionOfItsOwn(below, x, integrateByParts) is not { } integral
+ || integral.Nodes.Any(node => node is Integralf || node == MathS.NaN))
+ return null;
+ var k = p / s * MathS.Sqrt(r);
+ var answer = constant * (MathS.Pow(x, raised) * logarithm + k * integral / 2) / raised;
+ return Functions.PartialFractions.DerivativeHoldsAtSampledPoints(answer, expr, x) ? answer : null;
+ }
+ return null;
+ }
+
///
/// An integrand holding an inverse hyperbolic function of a linear in the variable,
/// integrated by the substitution that undoes it. The inverse hyperbolic functions
@@ -11979,6 +12141,82 @@ node is Powf(var @base, Number.Rational power) && power is not Number.Integer &&
return back;
}
+ ///
+ /// An inverse hyperbolic cotangent of a linear L as its tangent plus a constant:
+ /// arcoth(L) - artanh(L) is 1/2 (ln(-q) - ln(q)) for q = (1 + L)/(1 - L),
+ /// which is i pi/2 where |L| < 1 and -i pi/2 where |L| > 1 --
+ /// constant on each interval where both are continuous, and not the same constant on each.
+ /// With a symbol K for it the integrand is one in artanh(L) + K, which the
+ /// rules for the inverse hyperbolic tangent read; the answer is written back with
+ /// K = arcoth(L) - artanh(L), whose derivative is zero wherever it has one, so that
+ /// it is right on every interval without a case for each.
+ /// https://github.com/asc-community/AngouriMath/issues/233
+ ///
+ ///
+ /// The parser writes arcoth(L) as 1/2 ln((L + 1)/(L - 1)), which the
+ /// substitution under L = tanh(u) does not read as an inverse function: Rubi's 7.4.1
+ /// acoth(x)^2/(1 - x^2)^2 was declined in half a second where
+ /// (atanh(x) + k)^2/(1 - x^2)^2 is answered in a quarter of one. Last, after by parts,
+ /// so that every arcoth answered as written keeps its answer; at the top only, since it asks
+ /// the whole question again.
+ ///
+ internal static Entity? SolveByWritingAnArcothAsAnArtanhPlusAConstant(Entity expr, Entity.Variable x, bool integrateByParts)
+ {
+ if (!Integration.AnsweringTheQuestionAsked || writingAnArcothAsAnArtanh)
+ return null;
+ var back = new List<(Entity.Variable Constant, Entity Value)>();
+ var rewritten = expr;
+ foreach (var node in expr.Nodes.Where(node => node is Logf(var @base, _) && @base == MathS.e).Distinct().ToList())
+ {
+ if (node is not Logf(_, Divf(var above, var below)) || ArgumentOfAnArcoth(above, below, x) is not { } argument)
+ continue;
+ var constant = Variable.CreateUnique(rewritten, "k_coth");
+ var tangent = MathS.Ln((1 + argument) / (1 - argument));
+ // ln((L + 1)/(L - 1)) is 2 arcoth(L), and 2 artanh(L) + 2 K.
+ rewritten = rewritten.Replace(inside => inside == node ? tangent + 2 * constant : inside);
+ back.Add((constant, (node - tangent) / 2));
+ }
+ if (back.Count == 0)
+ return null;
+ Entity? answer;
+ writingAnArcothAsAnArtanh = true;
+ try
+ {
+ answer = Integration.ComputeAsAQuestionOfItsOwn(rewritten.InnerSimplified, x, integrateByParts);
+ }
+ finally
+ {
+ writingAnArcothAsAnArtanh = false;
+ }
+ if (answer is null || answer.Nodes.Any(node => node is Integralf))
+ return null;
+ foreach (var (constant, value) in back)
+ answer = answer.Substitute(constant, value);
+ // The identities the answer in K rests on were checked for a real K, and K is imaginary.
+ return Functions.PartialFractions.DerivativeHoldsAtSampledPoints(answer, expr, x) ? answer : null;
+ }
+
+ /// Whether is asking already.
+ [System.ThreadStatic] private static bool writingAnArcothAsAnArtanh;
+
+ ///
+ /// The L of ln((L + 1)/(L - 1)), the logarithm the parser writes
+ /// 2 arcoth(L) as, for a linear L: as written where it is written so, and
+ /// otherwise read off the two linears, which share their slope and whose offsets are two apart.
+ ///
+ private static Entity? ArgumentOfAnArcoth(Entity above, Entity below, Entity.Variable x)
+ {
+ if (above is Sumf(var argument, var one) && one == Number.Integer.One
+ && below is Minusf(var again, var minusOne) && minusOne == Number.Integer.One && again == argument)
+ return TreeAnalyzer.TryGetPolyLinear(argument, x, out var slope, out _) && slope is { } && !TreeAnalyzer.IsZero(slope) ? argument : null;
+ if (!TreeAnalyzer.TryGetPolyLinear(above, x, out var slopeAbove, out var offsetAbove) || slopeAbove is null || offsetAbove is null
+ || !TreeAnalyzer.TryGetPolyLinear(below, x, out var slopeBelow, out var offsetBelow) || slopeBelow is null || offsetBelow is null
+ || TreeAnalyzer.IsZero(slopeAbove) || !VanishesIdentically(slopeAbove - slopeBelow) || !VanishesIdentically(offsetAbove - offsetBelow - 2))
+ return null;
+ var offset = (offsetAbove - 1).InnerSimplified;
+ return TreeAnalyzer.IsZero(offset) ? slopeAbove * x : slopeAbove * x + offset;
+ }
+
///
/// The m for which is x^m times a function of
/// alone, else null: with the inverse written as a symbol, such an
@@ -29064,6 +29302,92 @@ static bool AreEqualAsPolynomials(Entity left, Entity right)
}
}
+ ///
+ /// A polynomial P of degree m times a function G of one logarithm of a
+ /// quotient of linears, over the power L^k of a linear proportional to one of them,
+ /// with m <= k - 2: P written in powers of L at its root,
+ /// P = p_0 + p_1 L + ... + p_m L^m, makes the integrand sum p_j G/L^(k - j)
+ /// exactly, and each term is a power of one of the logarithm's linears beside a function of
+ /// it, which answers under the
+ /// quotient. Rubi's 7.3.4 x (a + b atanh(c x))^2/(d + c d x)^3 is
+ /// ((a + b atanh(c x))^2/(d + c d x)^2 - d (a + b atanh(c x))^2/(d + c d x)^3)/(c d).
+ /// https://github.com/asc-community/AngouriMath/issues/233
+ ///
+ ///
+ /// The rule for a rational function times a power of a logarithm took it by parts, and the
+ /// remainder ran past the budget. Not where a term would be over L alone, a
+ /// dilogarithm nothing here answers, and not where G is the logarithm's first power,
+ /// which that rule answers in one step and keeps the answer it gives.
+ ///
+ internal static Entity? SolveByDividingAPolynomialByAPowerOfALinearOfTheLogarithm(Entity expr, Entity.Variable x, bool integrateByParts)
+ {
+ if (!Integration.AnsweringTheQuestionAskedOrOneBelow)
+ return null;
+ Entity polynomial = Number.Integer.One, rest = Number.Integer.One, constant = Number.Integer.One;
+ Entity? linear = null;
+ var power = 0;
+ foreach (var (factor, underneath) in FactorsOfTheIntegrand(expr))
+ {
+ if (!factor.ContainsNode(x))
+ {
+ constant = underneath ? constant / factor : constant * factor;
+ continue;
+ }
+ if (underneath)
+ {
+ // One power of one linear below the bar, and nothing else of x.
+ if (linear is not null || factor is not Powf(var @base, Number.Integer { EInteger: var whole })
+ || !whole.CanFitInInt32() || whole.ToInt32Unchecked() < 3
+ || !TreeAnalyzer.TryGetPolyLinear(@base, x, out var slope, out _) || slope is null || TreeAnalyzer.IsZero(slope))
+ return null;
+ (linear, power) = (@base, whole.ToInt32Unchecked());
+ continue;
+ }
+ if (TreeAnalyzer.TryGetPolynomial(factor, x, out var read) && read.Keys.All(degree => degree.Sign >= 0)
+ && read.Values.All(coefficient => !coefficient.ContainsNode(x)))
+ polynomial *= factor;
+ else
+ rest *= factor;
+ }
+ if (linear is null || !rest.ContainsNode(x)
+ || !TreeAnalyzer.TryGetPolynomial(polynomial, x, out var readPolynomial) || readPolynomial.Count == 0)
+ return null;
+ var degree = readPolynomial.Keys.Max()!;
+ if (degree.Sign <= 0 || !degree.CanFitInInt32() || degree.ToInt32Unchecked() > power - 2)
+ return null;
+ // The rest a function of one logarithm of a quotient of linears, one of them L up to a
+ // constant, and not its first power.
+ var logarithms = rest.Nodes.Where(node => node is Logf(var logBase, var of) && logBase == MathS.e && of.ContainsNode(x)).Distinct().ToList();
+ if (logarithms.Count != 1 || logarithms[0] is not Logf(_, Divf(var above, var below))
+ || !TreeAnalyzer.TryGetPolyLinear(above, x, out _, out _) || !TreeAnalyzer.TryGetPolyLinear(below, x, out _, out _)
+ || !AreProportionalAtSampledPoints(linear, above, x) && !AreProportionalAtSampledPoints(linear, below, x))
+ return null;
+ var w = Variable.CreateUnique(expr, "w_log");
+ var inW = rest.Substitute(logarithms[0], w);
+ if (inW.ContainsNode(x) || TreeAnalyzer.TryGetPolynomial(inW, w, out var inTheLogarithm)
+ && inTheLogarithm.Keys.All(key => key.CompareTo(EInteger.One) <= 0))
+ return null;
+ if (!TreeAnalyzer.TryGetPolyLinear(linear, x, out var beta, out var alpha) || beta is null)
+ return null;
+ var root = Functions.PartialFractions.Bare((-(alpha ?? Number.Integer.Zero) / beta).InnerSimplified);
+ var count = degree.ToInt32Unchecked() + 1;
+ if (Functions.PartialFractions.TaylorCoefficientsAtTheRoot(polynomial, root, count, x) is not { } coefficients)
+ return null;
+ Entity total = Number.Integer.Zero;
+ for (var order = 0; order < count; order++)
+ {
+ if (Functions.PartialFractions.IsZeroAsAValue(coefficients[order]))
+ continue;
+ var coefficient = Functions.PartialFractions.InLowestTermsOverTheSymbols(coefficients[order] / MathS.Pow(beta, order));
+ var piece = rest / MathS.Pow(linear, Number.Integer.Create(power - order));
+ if (Integration.ComputeAsAQuestionOfItsOwn(piece, x, integrateByParts) is not { } integrated
+ || integrated.Nodes.Any(node => node is Integralf))
+ return null;
+ total += coefficient * integrated;
+ }
+ return constant * total;
+ }
+
///
/// A rational function of times a whole power of a factor whose
/// derivative is rational -- A + B ln(R) or A + B arctan(R) with R
diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs
index b771c2129..0152781f4 100644
--- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs
@@ -1101,6 +1101,10 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr)
// where the rule below went step by step with the coefficients growing to
// b^205, and the search after it went past its budget.
if ((answer = IndefiniteIntegralSolver.SolveByTheQuotientOfTheLogarithmsLinears(expr, x, integrateByParts)) is { }) return answer;
+ // And a polynomial over a power of one of those linears, written in powers of it first:
+ // `x (a + b atanh(c x))^2/(d + c d x)^3` is two terms the rule above answers, where the
+ // rule after it took the whole by parts and the remainder ran past the budget.
+ if ((answer = IndefiniteIntegralSolver.SolveByDividingAPolynomialByAPowerOfALinearOfTheLogarithm(expr, x, integrateByParts)) is { }) return answer;
if ((answer = IndefiniteIntegralSolver.SolveARationalFunctionTimesAPowerOfALogarithm(expr, x, integrateByParts)) is { }) return answer;
// A rational function of the hyperbolic tangent with a symbol among its
// coefficients, by u = tanh(y): Rubi's `1/(a + b coth(c + d x)^2)^2` is a symbolic
@@ -1190,6 +1194,10 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr)
// before the substitution search, which reads `x^(2n)` as a power of `x^n` only for a
// number `n`.
if ((answer = IndefiniteIntegralSolver.SolveByAPowerOfXWithASymbolicExponent(expr, x, integrateByParts)) is { }) return answer;
+ // A power of x times the inverse hyperbolic secant or cosecant of `sqrt(x)`, by parts onto
+ // `x^m/sqrt(1 -+ x)`: before the substitution search, whose `u = sqrt(x)` asked the
+ // question in u one level down, where it ran out of time.
+ if ((answer = IndefiniteIntegralSolver.SolveByPartsAgainstAnInverseHyperbolicSecantOfARoot(expr, x, integrateByParts)) is { }) return answer;
if ((answer = IndefiniteIntegralSolver.SolveBySubstitution(expr, x, integrateByParts)) is { }) return answer;
// A constant times a whole power of something of x under a power that is not whole, the
// whole power taken out with the factor constant where it is not zero:
@@ -1542,6 +1550,10 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr)
// where it stands: `cos(x) sgn(sin(x))` is `|sin(x)|` by the substitution, and
// `sgn(sin(x)) sin(x)` with the sign taken out first.
if ((answer = IndefiniteIntegralSolver.SolveByTakingASignOut(expr, x, integrateByParts)) is { }) return answer;
+ // An inverse hyperbolic function of `a x` over a half-odd power of an even quadratic, by
+ // parts with the logarithm's derivative read off whole: the search's own parts split the
+ // remainder into terms harder than it, and past `(c + d x^2)^(7/2)` ran out of time.
+ if ((answer = IndefiniteIntegralSolver.SolveByPartsAgainstAnInverseHyperbolicOverAHalfOddPowerOfAQuadratic(expr, x, integrateByParts)) is { }) return answer;
if (integrateByParts && (answer = IndefiniteIntegralSolver.SolveIntegratingByParts(expr, x)) is { }) return answer;
// A hyperbolic integrand that is not rational in its exponential, asked as a trigonometric
// one: last, since the question in s can take long where a rule above answers in x at once.
@@ -1553,6 +1565,11 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr)
// A rational function of x and ln(x) over distinct linears in the logarithm, by partial
// fractions in it, where nothing has answered it whole: `(2 L^2 - L - x^2)/(L^3 - x^2 L)`.
if ((answer = IndefiniteIntegralSolver.SolveARationalFunctionOfTheLogarithmByPartialFractionsInIt(expr, x, integrateByParts)) is { }) return answer;
+ // An arcoth of a linear as its artanh plus a constant, last of all, so that every arcoth
+ // answered as written keeps that answer: `acoth(x)^2/(1 - x^2)^2` is
+ // `(atanh(x) + K)^2/(1 - x^2)^2` with `K = acoth(x) - atanh(x)`, constant where it is
+ // continuous, which the substitution under the hyperbolic tangent answers.
+ if ((answer = IndefiniteIntegralSolver.SolveByWritingAnArcothAsAnArtanhPlusAConstant(expr, x, integrateByParts)) is { }) return answer;
return null;
}
///
diff --git a/Sources/Tests/UnitTests/Calculus/APolynomialOverAPowerOfALinearOfTheLogarithmIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/APolynomialOverAPowerOfALinearOfTheLogarithmIntegralTest.cs
new file mode 100644
index 000000000..8e9e3b22b
--- /dev/null
+++ b/Sources/Tests/UnitTests/Calculus/APolynomialOverAPowerOfALinearOfTheLogarithmIntegralTest.cs
@@ -0,0 +1,55 @@
+//
+// 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
+{
+ ///
+ /// A polynomial times a function of atanh(c x) or acoth(c x) over a power of
+ /// d (1 +- c x), written in powers of the linear first: x (a + b atanh(c x))^2/(d + c d x)^3
+ /// is two terms answered under t = (1 + c x)/(1 - c x), where by parts on the whole ran out of
+ /// time. Rubi's 7.3.4.
+ /// #233
+ ///
+ [Trait("Area", "Calculus")]
+ public sealed class APolynomialOverAPowerOfALinearOfTheLogarithmIntegralTest
+ {
+ [Theory]
+ [InlineData("x*(a+b*atanh(c*x))^2/(d+c*d*x)^3")]
+ [InlineData("x*(a+b*atanh(c*x))^3/(1+c*x)^4")]
+ [InlineData("x^2*(a+b*atanh(c*x))^2/(1+c*x)^4")]
+ [InlineData("(2+x)*(a+b*atanh(c*x))^2/(1+c*x)^3")]
+ [InlineData("x*(a+b*atanh(c*x))^2/(d-c*d*x)^3")]
+ [InlineData("x*(a+b*acoth(c*x))^2/(d+c*d*x)^3")]
+ public void WrittenInPowersOfTheLinear(string integrand)
+ {
+ var integral = integrand.ToEntity().Integrate("x");
+ var text = integral.Stringize();
+ Assert.DoesNotContain("integral(", text);
+ Assert.True(text.Length < 20000, $"{text.Length} characters of answer for {integrand}");
+ Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.7).Substitute("c", 0.4).Substitute("d", 0.3);
+ var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
+ var original = Pinned(integrand.ToEntity());
+ var compared = 0;
+ foreach (var at in new[] { -2.9, -1.2, -0.7, 0.3, 0.8, 1.3, 2.9 })
+ {
+ var want = original.Substitute("x", at).EvalNumerical();
+ var got = derivative.Substitute("x", at).EvalNumerical();
+ if (want.IsNaN)
+ continue;
+ compared++;
+ Assert.True(Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart)
+ < 1e-9 * Math.Max(1, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart)),
+ $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
+ }
+ Assert.True(compared >= 5, $"only {compared} points could be compared for {integrand}");
+ }
+ }
+}
diff --git a/Sources/Tests/UnitTests/Calculus/AnArcothAsAnArtanhPlusAConstantIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/AnArcothAsAnArtanhPlusAConstantIntegralTest.cs
new file mode 100644
index 000000000..156022887
--- /dev/null
+++ b/Sources/Tests/UnitTests/Calculus/AnArcothAsAnArtanhPlusAConstantIntegralTest.cs
@@ -0,0 +1,53 @@
+//
+// 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 inverse hyperbolic cotangent of a linear as its tangent plus K = acoth(L) - atanh(L),
+ /// which is i pi/2 inside (-1, 1) and -i pi/2 outside it, constant on each side:
+ /// acoth(x)^2/(1 - x^2)^2 was declined where (atanh(x) + k)^2/(1 - x^2)^2 is answered.
+ /// Rubi's 7.4.1. The points are on both sides of c x = 1 and of c x = -1.
+ /// #233
+ ///
+ [Trait("Area", "Calculus")]
+ public sealed class AnArcothAsAnArtanhPlusAConstantIntegralTest
+ {
+ [Theory]
+ [InlineData("acoth(x)^2/(1-x^2)^2")]
+ [InlineData("acoth(c*x)^2/(1-c^2*x^2)^2")]
+ [InlineData("acoth(x)^3/(1-x^2)^2")]
+ [InlineData("acoth(x)^2/(1-x^2)^2+acoth(x)/(1-x^2)")]
+ public void OnBothSidesOfOne(string integrand)
+ {
+ var integral = integrand.ToEntity().Integrate("x");
+ var text = integral.Stringize();
+ Assert.DoesNotContain("integral(", text);
+ Assert.True(text.Length < 5000, $"{text.Length} characters of answer for {integrand}");
+ Entity Pinned(Entity e) => e.Substitute("c", 0.7);
+ var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
+ var original = Pinned(integrand.ToEntity());
+ var compared = 0;
+ foreach (var at in new[] { -2.9, -1.3, -0.7, 0.3, 0.8, 1.3, 2.9 })
+ {
+ var want = original.Substitute("x", at).EvalNumerical();
+ var got = derivative.Substitute("x", at).EvalNumerical();
+ if (want.IsNaN)
+ continue;
+ compared++;
+ Assert.True(Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart)
+ < 1e-9 * Math.Max(1, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart)),
+ $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
+ }
+ Assert.True(compared >= 5, $"only {compared} points could be compared for {integrand}");
+ }
+ }
+}
diff --git a/Sources/Tests/UnitTests/Calculus/AnInverseHyperbolicOverAHalfOddPowerOfAQuadraticIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/AnInverseHyperbolicOverAHalfOddPowerOfAQuadraticIntegralTest.cs
new file mode 100644
index 000000000..f4aca1e76
--- /dev/null
+++ b/Sources/Tests/UnitTests/Calculus/AnInverseHyperbolicOverAHalfOddPowerOfAQuadraticIntegralTest.cs
@@ -0,0 +1,55 @@
+//
+// 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
+{
+ ///
+ /// A + B F(a x), F an inverse hyperbolic sine, cosine or tangent, over a half-odd
+ /// power of c + d x^2, by parts against F, the remainder asked a term at a time under
+ /// w = x^2: from (c + d x^2)^(7/2) on, by parts in the search was declined or ran out of
+ /// time. Rubi's 7.1.4, 7.2.4, 7.2.5 and 7.3.4.
+ /// #233
+ ///
+ [Trait("Area", "Calculus")]
+ public sealed class AnInverseHyperbolicOverAHalfOddPowerOfAQuadraticIntegralTest
+ {
+ [Theory]
+ [InlineData("atanh(a*x)/(c+d*x^2)^(9/2)")]
+ [InlineData("acosh(a*x)/(c+d*x^2)^(9/2)")]
+ [InlineData("acosh(a*x)/(c+d*x^2)^(7/2)")]
+ [InlineData("(a+b*asinh(c*x))/(d+k*x^2)^(7/2)")]
+ [InlineData("(a+b*acosh(c*x))/(d+k*x^2)^(7/2)")]
+ [InlineData("atanh(a*x)/(c+d*x^2)^(3/2)")]
+ public void ByPartsOntoTermsInTheSquare(string integrand)
+ {
+ var integral = integrand.ToEntity().Integrate("x");
+ var text = integral.Stringize();
+ Assert.DoesNotContain("integral(", text);
+ Assert.True(text.Length < 20000, $"{text.Length} characters of answer for {integrand}");
+ Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.7).Substitute("c", 0.4).Substitute("d", 0.3).Substitute("k", 0.9);
+ var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
+ var original = Pinned(integrand.ToEntity());
+ var compared = 0;
+ foreach (var at in new[] { -2.9, -1.2, -0.7, 0.3, 0.8, 1.3, 2.9 })
+ {
+ var want = original.Substitute("x", at).EvalNumerical();
+ var got = derivative.Substitute("x", at).EvalNumerical();
+ if (want.IsNaN)
+ continue;
+ compared++;
+ Assert.True(Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart)
+ < 1e-9 * Math.Max(1, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart)),
+ $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
+ }
+ Assert.True(compared >= 5, $"only {compared} points could be compared for {integrand}");
+ }
+ }
+}
diff --git a/Sources/Tests/UnitTests/Calculus/AnInverseHyperbolicSecantOfARootByPartsIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/AnInverseHyperbolicSecantOfARootByPartsIntegralTest.cs
new file mode 100644
index 000000000..404eeb9b2
--- /dev/null
+++ b/Sources/Tests/UnitTests/Calculus/AnInverseHyperbolicSecantOfARootByPartsIntegralTest.cs
@@ -0,0 +1,55 @@
+//
+// 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
+{
+ ///
+ /// x^m arsech(sqrt(x)) and x^m arcsch(sqrt(x)) by parts onto x^m/sqrt(1 -+ x),
+ /// with K = sqrt(1 -+ x)/(sqrt(x) sqrt(1/x -+ 1)), which is 1 or -1, beside the remainder.
+ /// x^3 arsech(sqrt(x)) ran out of time, and the answer the substitution gave x arcsch(sqrt(x))
+ /// was wrong for negative x, which the points here include. Rubi's 7.5.2 and 7.6.2.
+ /// #233
+ ///
+ [Trait("Area", "Calculus")]
+ public sealed class AnInverseHyperbolicSecantOfARootByPartsIntegralTest
+ {
+ [Theory]
+ [InlineData("x^3*asech(sqrt(x))")]
+ [InlineData("x^2*asech(sqrt(x))")]
+ [InlineData("asech(sqrt(x))")]
+ [InlineData("x^3*acsch(sqrt(x))")]
+ [InlineData("x*acsch(sqrt(x))")]
+ [InlineData("acsch(sqrt(x))")]
+ public void OnEverySideOfZeroAndMinusOne(string integrand)
+ {
+ var integral = integrand.ToEntity().Integrate("x");
+ var text = integral.Stringize();
+ Assert.DoesNotContain("integral(", text);
+ Assert.True(text.Length < 5000, $"{text.Length} characters of answer for {integrand}");
+ Entity Pinned(Entity e) => e;
+ var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
+ var original = Pinned(integrand.ToEntity());
+ var compared = 0;
+ foreach (var at in new[] { -2.9, -1.3, -0.4, 0.3, 0.8, 1.3, 2.9 })
+ {
+ var want = original.Substitute("x", at).EvalNumerical();
+ var got = derivative.Substitute("x", at).EvalNumerical();
+ if (want.IsNaN)
+ continue;
+ compared++;
+ Assert.True(Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart)
+ < 1e-9 * Math.Max(1, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart)),
+ $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
+ }
+ Assert.True(compared >= 5, $"only {compared} points could be compared for {integrand}");
+ }
+ }
+}