From 87e2c2b456b54530d7b858c805113e1e3b3239ae Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Wed, 21 Oct 2026 16:04:58 +0000 Subject: [PATCH] =?UTF-8?q?Integrate=20five=20shapes=20of=20the=20independ?= =?UTF-8?q?ent=20suites:=20a=20cube=20root's=20T'/(1=20-=20T^3),=20a=20roo?= =?UTF-8?q?t=20of=20a=20quartic's=20numerator=20square,=20an=20Ei=20deriva?= =?UTF-8?q?tive,=20roots=20of=20sec=20=C2=B1=201,=20and=20Euler's=20substi?= =?UTF-8?q?tution=20on=20a=20nested=20root?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - A quotient beside the cube root of a cubic with a linear factor, where it is T'/(1 - T^3) for T the root over the linear: Welz 94-96. - A linear over a linear beside the root of a quartic, where it is a function of the numerator's square over the root: Welz 179, 180, and Rubi's 1.2.2.3:557-560. - An exponential of a rational function beside the derivative of its exponential integral: Hebisch 12. - A function of sqrt(sec(u) + 1) and sqrt(sec(u) - 1), in their difference: Charlwood 140. - A half-odd power of a linear plus the root of a quadratic, in Euler's substitution where their squares differ by a square: Bondarenko 24, 25. Chain against master f7f2a7a8: pocket of 1,774 rows, 1,738 -> 1,742 solved, 0 wrong; family 0 whole 1,805 -> 1,814 of 1,814; other samples unchanged; the 13 rows that moved, alone, 0 -> 13 solved; no row's allocation grew past 1.3x. 2.5.0 declined all thirteen. CubeRootLogarithmAnsatzTest held `1/(x ((x - 1)(q - 2 q x + x^2))^(1/3))` as declined, the ansatz's field not holding q^(1/3). The first rule above answers it, with T = q^(1/3) (x - 1)/root, and the answer differentiates back for q = 0.37, 2.3, -0.7 and -3.1 at eleven points on both sides of the radicand's zeros; the row moves to a theory of its own that checks the derivative. Part of #233. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 67 +++ .../Integration/IndefiniteIntegralSolver.cs | 405 ++++++++++++++++++ .../Integration/Integration.Definition.cs | 22 + ...sOfTheSecantPlusAndMinusOneIntegralTest.cs | 49 +++ ...nearPlusTheRootOfAQuadraticIntegralTest.cs | 51 +++ ...icByTheSquareOfTheNumeratorIntegralTest.cs | 51 +++ ...eACubeRootWithALinearFactorIntegralTest.cs | 51 +++ ...iveOfItsExponentialIntegralIntegralTest.cs | 50 +++ .../Calculus/CubeRootLogarithmAnsatzTest.cs | 13 +- 9 files changed, 756 insertions(+), 3 deletions(-) create mode 100644 Sources/Tests/UnitTests/Calculus/AFunctionOfTheRootsOfTheSecantPlusAndMinusOneIntegralTest.cs create mode 100644 Sources/Tests/UnitTests/Calculus/AHalfOddPowerOfALinearPlusTheRootOfAQuadraticIntegralTest.cs create mode 100644 Sources/Tests/UnitTests/Calculus/ALinearOverALinearBesideTheRootOfAQuarticByTheSquareOfTheNumeratorIntegralTest.cs create mode 100644 Sources/Tests/UnitTests/Calculus/APseudoEllipticQuotientBesideACubeRootWithALinearFactorIntegralTest.cs create mode 100644 Sources/Tests/UnitTests/Calculus/AnExponentialBesideTheDerivativeOfItsExponentialIntegralIntegralTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 7b8cb4a18..2419e7cc9 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -1622,6 +1622,73 @@ an arctangent ([#718](https://github.com/asc-community/AngouriMath/issues/718)). | `"1/((d+k*x)*(c^2*d^2-b*c*d*k+b^2*k^2+3*b*c*k^2*x+3*c^2*k^2*x^2)^(1/3))".ToEntity().Integrate("x")` | `integral(...)` | 1,934 characters | | `"1/((d+k*x)*(-c^2*d^2+b*c*d*k+2*b^2*k^2+9*b*c*k^2*x+9*c^2*k^2*x^2)^(1/3))".ToEntity().Integrate("x")` | `integral(...)` | 1,274 characters | +### A quotient beside the cube root of a cubic with a linear factor is integrated where it is `T'/(1 - T^3)` + +**Answers where there were none.** Welz's `1/(x ((x - 1) (q - 2 q x + x^2))^(1/3))`, +`(2 - (1 + k) x)/((x (1 - x) (1 - k x))^(1/3) (1 - (1 + k) x))` and +`(1 - k x)/((1 - (2 - k) x) (x (1 - x) (1 - k x))^(2/3))` were declined. For `P = L Q` with `L` linear, a rational +function over `P^(1/3)` that is a constant times `W/(Q - mu L^2)`, or over `P^(2/3)` that is one times +`L W/(L^2 - mu Q)`, `W = 2 Q L' - L Q'`, is a constant times `T'/(1 - T^3)` for `T = m L/P^(1/3)` or `m P^(1/3)/L`, +`m^3 = mu`, and is answered by two logarithms and an arctangent of `T`. With numbers in `P` the ansatz over +logarithms of `L - y` answers these, and is asked first ([#233](https://github.com/asc-community/AngouriMath/issues/233)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"1/(x*((-1+x)*(q-2*q*x+x^2))^(1/3))".ToEntity().Integrate("x")` | `integral(...)` | 385 characters | +| `"(2-(1+k)*x)/(((1-x)*x*(1-k*x))^(1/3)*(1-(1+k)*x))".ToEntity().Integrate("x")` | `integral(...)` | 319 characters | +| `"(1-k*x)/((1+(-2+k)*x)*((1-x)*x*(1-k*x))^(2/3))".ToEntity().Integrate("x")` | `integral(...)` | 424 characters | + +### A linear over a linear beside the root of a quartic is integrated where it is a function of the numerator's square over the root + +**Answers where there were none.** `(1 + x - sqrt(3))/((1 + x + sqrt(3)) sqrt(x^4 + 4 sqrt(3) x^2 - 4))` and its +conjugate were declined, in Welz's suite and in Rubi's 1.2.2.3, and so were the two with `1 + 2 x` beside them. +Where `gamma P - mu M W = L^4` for `W = 4 L' P - L P'` and constants `gamma` and `mu`, `L/(M sqrt(P))` is the +derivative of `(2 mu/c) artanh(L^2/(c sqrt(P)))` with `c^2 = gamma`, an arctangent where `gamma` is negative. The +ansatz over logarithms of `A - B y` takes its places at rational poles, and these are at `-1 -+ sqrt(3)` ([#233](https://github.com/asc-community/AngouriMath/issues/233)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"(1+x-sqrt(3))/((1+x+sqrt(3))*sqrt(-4+x^4+4*x^2*sqrt(3)))".ToEntity().Integrate("x")` | `integral(...)` | 246 characters | +| `"(1+x+sqrt(3))/((1+x-sqrt(3))*sqrt(-4+x^4-4*x^2*sqrt(3)))".ToEntity().Integrate("x")` | `integral(...)` | 151 characters | +| `"(1+2*x-sqrt(3))/((1+2*x+sqrt(3))*sqrt(-1+4*x^4+4*x^2*sqrt(3)))".ToEntity().Integrate("x")` | `integral(...)` | 261 characters | +| `"(1+2*x+sqrt(3))/((1+2*x-sqrt(3))*sqrt(-1+4*x^4-4*x^2*sqrt(3)))".ToEntity().Integrate("x")` | `integral(...)` | 159 characters | + +### An exponential of a rational function beside the derivative of its exponential integral is integrated + +**Answers where there were none.** Hebisch's `(2 + 2x + 3x^2 - x^3 + 2x^4) e^(x/(2 + x^2))/(2x + x^3)` was declined. +It is `((2 + x^2) e^h)' + e^h h'/h` for `h = x/(2 + x^2)`, and the ansatz for `e^h N/D` alone has no solution. The +multiple of `h'/h` is the residue at a zero of `h`, the rest is asked of the ansatz, and the answer is +`e^h N/D + c Ei(h)` ([#233](https://github.com/asc-community/AngouriMath/issues/233)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"(2+2*x+3*x^2-x^3+2*x^4)*e^(x/(2+x^2))/(2*x+x^3)".ToEntity().Integrate("x")` | `integral(...)` | 61 characters | + +### A function of `sqrt(sec(u) + 1)` and `sqrt(sec(u) - 1)` is integrated in their difference + +**Answers where there were none.** Charlwood's `sqrt(sqrt(1 + sec(x)) - sqrt(sec(x) - 1))` was declined. Both roots +and the secant are rational functions of `w = sqrt(sec(u) + 1) - sqrt(sec(u) - 1)`, and `dx` is +`-8 K w dw/(b (w^4 + 4))` for `u = a + b x` and `K = cot(u) sqrt(sec(u) + 1) sqrt(sec(u) - 1)`, which is 1 or -1. The +integral in `w` is taken from `sqrt(2)`, where `K` changes sign, so that the answer is continuous wherever the +integrand is real ([#233](https://github.com/asc-community/AngouriMath/issues/233)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"sqrt(-sqrt(-1+sec(x))+sqrt(1+sec(x)))".ToEntity().Integrate("x")` | `integral(...)` | 2,635 characters | + +### A half-odd power of a linear plus the root of a quadratic is integrated in Euler's substitution where their squares differ by a square + +**Answers where there were none.** Bondarenko's `sqrt(1 + sqrt(x) + sqrt(1 + 2 x + 2 sqrt(x)))` and +`sqrt(sqrt(2) + sqrt(x) + sqrt(2 + 2 x + sqrt(8) sqrt(x)))` were declined. Under `x = t^2` each is a half-odd power of +`L + sqrt(Q)` with `Q - L^2 = c (t - r)^2`, and in `u = (sqrt(Q) - L)/(t - r)` both `t` and `sqrt(Q)` are rational and +`L + sqrt(Q)` is a constant over the quadratic `c - 2 b u - u^2`, `b` the slope of `L`: the integrand is rational +beside a root of a quadratic ([#233](https://github.com/asc-community/AngouriMath/issues/233)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"sqrt(1+sqrt(x)+sqrt(1+2*x+2*sqrt(x)))".ToEntity().Integrate("x")` | `integral(...)` | 2,261 characters | +| `"sqrt(sqrt(2)+sqrt(x)+sqrt(2+2*x+sqrt(8)*sqrt(x)))".ToEntity().Integrate("x")` | `integral(...)` | 2,715 characters | + ### A linear and a quadratic to one power are integrated as one binomial **Answers where there were none.** `1/((1 + x)^(1/3) (1 - x + x^2)^(1/3))` was declined. A linear and a quadratic to diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 5a953b156..9dd262a7f 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -6994,6 +6994,86 @@ bool IsAPolynomialInU(Entity part) return expr * inTermsOfW.Substitute(w, wOfX) / inX; } + /// + /// A function of p = sqrt(sec(u) + 1) and q = sqrt(sec(u) - 1), u = a + b x, + /// in their difference w = p - q: p + q = 2/w, so p = (w^2 + 2)/(2 w), + /// q = (2 - w^2)/(2 w) and sec(u) = (w^4 + 4)/(4 w^2), and + /// dx = -8 K w dw/(b (w^4 + 4)) for K = cot(u) p q, which is 1 or -1. + /// Charlwood's sqrt(sqrt(1 + sec(x)) - sqrt(sec(x) - 1)) is -8 w^(3/2)/(w^4 + 4) + /// in w. + /// + /// + /// w' = -b sec(u) tan(u) w/(2 p q) and K tan(u) = p q, so (K/b) w' is + /// -sec(u) w/2, which is -(w^4 + 4)/(8 w); K is constant wherever it is + /// continuous, its square being cot(u)^2 (sec(u)^2 - 1). Each step uses no more about + /// the roots than p^2 = sec(u) + 1 and q^2 = sec(u) - 1, so the answer holds on + /// every branch of either. Where the integrand is real, K changes sign only at the + /// multiples of 2 pi, where w = sqrt(2): the integral in w is taken from + /// there, so that the answer is continuous through them. Declined before, the roots of the two sums + /// being no substitution any rule tries. The answer is differentiated back at sampled points. + /// https://github.com/asc-community/AngouriMath/issues/233 + /// + internal static Entity? SolveAFunctionOfTheRootsOfTheSecantPlusAndMinusOneInTheirDifference(Entity expr, Entity.Variable x, bool integrateByParts) + { + Entity? argument = null; + foreach (var node in expr.Nodes) + { + if (TrigonometricArgument(node) is not { } thisArgument || !thisArgument.ContainsNode(x)) + continue; + if (argument is null) + argument = thisArgument; + else if (argument != thisArgument) + return null; + } + if (argument is null || !TreeAnalyzer.TryGetPolyLinear(argument, x, out var rate, out _) + || rate.ContainsNode(x) || VanishesIdentically(rate)) + return null; + var secant = MathS.Sec(argument); + static bool IsAHalfOddPower(Entity power) => power is Number.Rational rational and not Number.Integer + && rational.ERational.Denominator.Equals(EInteger.FromInt32(2)) && rational.ERational.Numerator.CanFitInInt32(); + // The side of a radicand that is sec(u) + 1 or sec(u) - 1, read as a linear in the secant. + var named = Variable.CreateUnique(expr, "s_secant"); + int? Side(Entity radicand) + => radicand.Replace(node => node == secant ? named : node) is var inS && !inS.ContainsNode(x) + && TreeAnalyzer.TryGetPolyLinear(inS, named, out var one, out var offset) && !one.ContainsNode(named) && !offset.ContainsNode(named) + && one.Evaled == Number.Integer.One && offset.Evaled is var side && (side == Number.Integer.One || side == Number.Integer.MinusOne) + ? (side == Number.Integer.One ? 1 : -1) : null; + Entity? plus = null, minus = null; + foreach (var node in expr.Nodes) + if (node is Powf(var @base, var power) && IsAHalfOddPower(power) && @base.ContainsNode(x)) + { + if (Side(@base) is 1) + plus ??= @base; + else if (Side(@base) is -1) + minus ??= @base; + } + if (plus is null || minus is null) + return null; + var w = Variable.CreateUnique(expr, "w_secant"); + var p = Variable.CreateUnique(expr + w, "p_secant"); + var q = Variable.CreateUnique(expr + w + p, "q_secant"); + // The roots first, since a replacement reaches the secant inside them before the roots. + var asRoots = expr.Replace(node => node is Powf(var @base, var power) && IsAHalfOddPower(power) && (@base == plus || @base == minus) + ? MathS.Pow(@base == plus ? p : q, Number.Integer.Create(((Number.Rational)power).ERational.Numerator)) + : node); + var square = MathS.Pow(w, 2); + var fourth = MathS.Pow(w, 4); + var inW = asRoots.Replace(node => node == secant ? (fourth + 4) / (4 * square) : node) + .Substitute(p, (square + 2) / (2 * w)).Substitute(q, (2 - square) / (2 * w)); + if (inW.ContainsNode(x)) + return null; + var integrand = Functions.PartialFractions.Bare((inW * -8 * w / (fourth + 4)).Simplify()); + if (integrand.ContainsNode(p) || integrand.ContainsNode(q) + || Integration.ComputeAsAQuestionOfItsOwn(integrand, w, integrateByParts) is not { } inTermsOfW + || inTermsOfW.Nodes.Any(node => node is Integralf || node == MathS.NaN)) + return null; + var (rootOfPlus, rootOfMinus) = (MathS.Sqrt(plus), MathS.Sqrt(minus)); + // From w = sqrt(2), where u is a multiple of 2 pi and K changes sign: continuous there. + var answer = (MathS.Cotan(argument) * rootOfPlus * rootOfMinus / rate + * (inTermsOfW.Substitute(w, rootOfPlus - rootOfMinus) - inTermsOfW.Substitute(w, MathS.Sqrt(2)))).InnerSimplified; + return Functions.PartialFractions.DerivativeHoldsAtSampledPoints(answer, expr, x) ? answer : null; + } + /// /// sec(y) (a ± a sec(y))^m (c ∓ c sec(y))^n, with n half-odd and m anything, /// integrated in u = a ± a sec(y): the two sums multiply to -a c tan(y)^2, so the @@ -8053,6 +8133,80 @@ Entity Symbolic(Entity side) => Mulf.LinearChildren(side).Where(factor => factor return Functions.PartialFractions.DerivativeHoldsAtSampledPoints(answer, expr, x) ? answer : null; } + /// + /// A linear over a linear beside the reciprocal of the square root of a cubic or a quartic, + /// L/(M y) with y = sqrt(P), where gamma P - mu M W = L^4 for + /// W = 4 L' P - L P' and constants gamma and mu: its integral is + /// (2 mu/c) artanh(L^2/(c y)) for c^2 = gamma, which is + /// -(2 mu/s) atan(L^2/(s y)) for s^2 = -gamma. Welz's + /// (1 + x - sqrt(3))/((1 + x + sqrt(3)) sqrt(x^4 + 4 sqrt(3) x^2 - 4)). + /// + /// + /// + /// u = L^2/(c y) has u' = L W/(2 c P y) and 1 - u^2 = (gamma P - L^4)/(gamma P), + /// so u'/(1 - u^2) = c L W/(2 y (gamma P - L^4)), which is c L/(2 mu M y) where + /// the coefficients agree. That uses no more about the root than y^2 = P, so it holds + /// on either branch, and for c = i s the inverse hyperbolic tangent is + /// -i atan(L^2/(s y)). The argument holds the square of L because the integrand + /// vanishes where L does, as the derivative of a function of L^2/y does. The + /// identity is five coefficients linear in gamma and mu. + /// + /// + /// The ansatz over logarithms of A - B y takes its places at rational poles, and + /// declined Welz's, whose pole is -1 - sqrt(3); the rule for a cubic binomial reads + /// no quartic, and is asked first. The answer is differentiated back at sampled points. + /// https://github.com/asc-community/AngouriMath/issues/233 + /// + /// + internal static Entity? SolveALinearOverALinearBesideTheRootOfAQuarticByTheSquareOfTheNumerator(Entity expr, Entity.Variable x) + { + var (numerator, denominator) = Functions.SingleQuotient.Of(expr); + Entity? radicand = null; + Entity below = Number.Integer.One; + foreach (var factor in Mulf.LinearChildren(denominator)) + if (factor is Powf(var @base, Number.Rational half) && half.ERational.Equals(ERational.Create(1, 2)) && @base.ContainsNode(x)) + { + if (radicand is not null) + return null; + radicand = @base; + } + else + below = below == Number.Integer.One ? factor : below * factor; + if (radicand is null + || !TreeAnalyzer.TryGetPolyLinear(numerator, x, out var slope, out var offset) || slope.ContainsNode(x) || offset.ContainsNode(x) + || VanishesIdentically(slope) + || !TreeAnalyzer.TryGetPolyLinear(below, x, out var belowSlope, out var belowOffset) || belowSlope.ContainsNode(x) || belowOffset.ContainsNode(x) + || VanishesIdentically(belowSlope) + || !TreeAnalyzer.TryGetPolynomial(radicand, x, out var p) || p.Any(pair => pair.Key.Sign < 0 || pair.Value.ContainsNode(x)) + || p.Keys.Max()!.ToInt32Unchecked() is not (3 or 4)) + return null; + var w = 4 * slope * radicand - numerator * radicand.Differentiate(x); + if (!TreeAnalyzer.TryGetPolynomial(-below * w, x, out var mw) || !TreeAnalyzer.TryGetPolynomial(MathS.Pow(numerator, 4), x, out var fourth)) + return null; + var powers = p.Keys.Concat(mw.Keys).Concat(fourth.Keys).Distinct().OrderBy(power => power).ToList(); + Entity At(Dictionary read, EInteger power) + => read.TryGetValue(power, out var value) ? value.Expand().InnerSimplified : Number.Integer.Zero; + var matrix = powers.Select(power => new[] { At(p, power), At(mw, power) }).ToArray(); + var rhs = powers.Select(power => At(fourth, power)).ToArray(); + if (!Functions.PartialFractions.TrySolveLinear(matrix, rhs, out var values)) + return null; + static Entity Tidy(Entity constant) => constant.Vars.Any() ? LowestOverTheSymbols(constant) : Functions.PartialFractions.Bare(constant.Simplify()); + var (gamma, mu) = (Tidy(values[0]), Tidy(values[1])); + if (VanishesIdentically(gamma) || VanishesIdentically(mu)) + return null; + var square = MathS.Pow(numerator, 2); + var y = MathS.Sqrt(radicand); + var c = MathS.Sqrt(gamma); + var s = MathS.Sqrt(Tidy(-gamma)); + // artanh(L^2/(c y)) as ln(((c y + L^2)/(c y - L^2))^2)/4: the same derivative, and real wherever c y is. + var positive = Tidy(mu / (2 * c)) * MathS.Ln(MathS.Pow((c * y + square) / (c * y - square), 2)); + var negative = Tidy(-2 * mu / s) * MathS.Arctan(square / (s * y)); + // A number's sign decided by its value, which `6 sqrt(3) - 9` written as a sum is not to the reader of signs. + var answer = (gamma.Vars.Any() ? BySign(gamma, positive, negative) + : gamma.Evaled is Number.Real { IsNegative: true } ? negative : positive).InnerSimplified; + return Functions.PartialFractions.DerivativeHoldsAtSampledPoints(answer, expr, x) ? answer : null; + } + /// /// The square root of , a positive rational times powers of /// symbols, each power a multiple of four, as the rational's root times their halves; @@ -18538,6 +18692,72 @@ private static Entity InTheBaseWritten(Entity answer, Entity integrand, Entity.V : answer.Replace(node => node is Powf(var e, var exponent) && e == MathS.e && written.TryGetValue(exponent, out var asWritten) ? asWritten : node); } + /// + /// An exponential of a rational function h times a rational function R that is + /// the derivative of e^h N/D and of a multiple of the exponential integral of h: + /// e^h R = (e^h N/D)' + c e^h h'/h, whose integral is e^h N/D + c Ei(h). + /// Hebisch's (2 + 2x + 3x^2 - x^3 + 2x^4) e^(x/(2 + x^2))/(2x + x^3) is + /// (2 + x^2) e^(x/(2 + x^2)) + Ei(x/(2 + x^2)). + /// + /// + /// At a zero r of h of order k where h has no pole, h'/h has + /// the residue k, and (N/D)' + h' N/D none: a pole of N/D there is one of + /// order two or more in it. So c is the residue of R at r over k, and + /// R - c h'/h is asked of 's ansatz, which + /// declines R whole: by Liouville's theorem e^h R has no elementary + /// antiderivative where c is not zero. Each linear factor of the numerator of h, + /// as written, is a candidate r. The answer is differentiated back at sampled points. + /// https://github.com/asc-community/AngouriMath/issues/233 + /// + internal static Entity? SolveAnExponentialBesideAMultipleOfTheDerivativeOfItsExponentialIntegral(Entity expr, Entity.Variable x) + { + Entity? exponent = null; + Entity rest = 0; + foreach (var term in Sumf.LinearChildren(expr)) + { + if (ReadOneExponentialTimesTheRest(term, x) is not var (h, r) || h is null) + return null; + if (exponent is null) + exponent = h; + else if (exponent != h) + return null; + rest += r; + } + if (exponent is null || !IsARationalFunction(exponent, x) || !IsARationalFunction(rest, x)) + return null; + var (p, q) = Functions.SingleQuotient.Of(exponent.InnerSimplified); + var (above, below) = Functions.SingleQuotient.Of(Functions.SingleQuotient.Combine(rest).InnerSimplified); + if (!below.ContainsNode(x)) + return null; + var logarithmicDerivative = (p.Differentiate(x) * q - p * q.Differentiate(x)) / (p * q); + var belowPrime = below.Differentiate(x); + foreach (var factor in Mulf.LinearChildren(p)) + { + var (linear, order) = factor is Powf(var @base, Number.Integer { EInteger.Sign: > 0 } power) && power.EInteger.CanFitInInt32() + ? (@base, power.EInteger.ToInt32Unchecked()) : (factor, 1); + if (!TreeAnalyzer.TryGetPolyLinear(linear, x, out var slope, out var offset) || slope.ContainsNode(x) || offset.ContainsNode(x) + || VanishesIdentically(slope)) + continue; + var root = LowestOverTheSymbols(-offset / slope); + Entity At(Entity polynomial) => LowestOverTheSymbols(polynomial.Substitute(x, root)); + // A simple pole of R at a zero of h that is not a pole of it. + if (VanishesIdentically(At(q)) || !VanishesIdentically(At(below)) || VanishesIdentically(At(belowPrime))) + continue; + var c = LowestOverTheSymbols(At(above) / (At(belowPrime) * order)); + if (VanishesIdentically(c)) + continue; + var (top, bottom) = Functions.SingleQuotient.Of(Functions.SingleQuotient.Combine(rest - c * logarithmicDerivative).InnerSimplified); + var remainder = Functions.PolynomialGcd.TryCancel(top, bottom, out var cancelled) && cancelled is not null + ? Functions.PartialFractions.Bare(cancelled) : top / bottom; + if (IntegrateByAnsatz(exponent, remainder, x) is not { } part) + continue; + var answer = (part + c * MathS.Ei(exponent)).InnerSimplified; + if (Functions.PartialFractions.DerivativeHoldsAtSampledPoints(answer, expr, x)) + return InTheBaseWritten(answer, expr, x); + } + return null; + } + /// /// e^(a x) times a rational function of sin(x) and cos(x), closed by /// an ansatz in the half-angle tangent: F = e^(a x) P(t)/Q(t) with t = tan(x/2). @@ -26484,6 +26704,105 @@ Entity ToThe(EInteger k) /// The largest odd degree of a binomial scales by, and the most terms it writes out. private const int MostOrderScaledToNumbers = 9, MostTermsScaledToNumbers = 6; + /// + /// A polynomial over a polynomial beside the reciprocal of the cube root of a quadratic or + /// a cubic P with a linear factor L, P = L Q, or of its square: R/y + /// with R a constant times W/(Q - mu L^2), or R/y^2 with R a + /// constant times L W/(L^2 - mu Q), for y = P^(1/3), W = 2 Q L' - L Q' and + /// a constant mu. With m^3 = mu and T = m L/y, or T = m y/L, it + /// is a constant times T'/(1 - T^3), whose integral is + /// -ln(1 - T)/3 + ln(1 + T + T^2)/6 + atan((1 + 2 T)/sqrt(3))/sqrt(3). Welz's + /// 1/(x ((x - 1) (q - 2 q x + x^2))^(1/3)) and + /// (1 - k x)/((1 - (2 - k) x) ((1 - x) x (1 - k x))^(2/3)). + /// + /// + /// + /// For P = L Q, 3 P L' - L P' is L W, so T = m L/y has + /// T' = m L W/(3 P y), and 1 - T^3 = L (Q - mu L^2)/P: the quotient is + /// m W/(3 (Q - mu L^2) y). And T = m y/L has T' = -m W/(3 L y^2) and + /// 1 - T^3 = (L^2 - mu Q)/L^2: the quotient is -m L W/(3 (L^2 - mu Q) y^2). Each + /// uses no more about the root than y^3 = P, and about m than m^3 = mu, + /// so it holds on every branch of either. 1 - T is the y - m L of Rubi's + /// answers over y, and the norm P - mu L^3 = L (Q - mu L^2) carries the poles of + /// the integrand. Matching R is a linear system in the constant's reciprocal and + /// mu times it, solved for each linear factor of P as written. + /// + /// + /// With numbers in P the ansatz over logarithms of L - y answers these, and is + /// asked first; with symbols in it, as in Welz's, it and every other rule declined them. The + /// answer is differentiated back at sampled points. + /// https://github.com/asc-community/AngouriMath/issues/233 + /// + /// + internal static Entity? SolveAPseudoEllipticQuotientBesideACubeRootWithALinearFactor(Entity expr, Entity.Variable x) + { + var (numerator, denominator) = Functions.SingleQuotient.Of(expr); + Entity? radicand = null; + var squared = false; + Entity below = Number.Integer.One; + foreach (var factor in Mulf.LinearChildren(denominator)) + if (factor is Powf(var @base, Number.Rational power) && power is not Number.Integer && @base.ContainsNode(x)) + { + var (top, bottom) = (power.ERational.Numerator, power.ERational.Denominator); + if (radicand is not null || !bottom.Equals(EInteger.FromInt32(3)) || !top.Equals(EInteger.One) && !top.Equals(EInteger.FromInt32(2))) + return null; + radicand = @base; + squared = top.Equals(EInteger.FromInt32(2)); + } + else + below = below == Number.Integer.One ? factor : below * factor; + // Coefficients by power, each free of x, of a polynomial in x. + Dictionary? Coefficients(Entity polynomial) + => TreeAnalyzer.TryGetPolynomial(polynomial, x, out var read) && read.All(pair => pair.Key.Sign >= 0 && !pair.Value.ContainsNode(x)) + ? read : null; + if (radicand is null || Coefficients(numerator) is null || Coefficients(below) is null + || Coefficients(radicand) is not { Count: > 0 } p || p.Keys.Max()!.ToInt32Unchecked() is not (2 or 3)) + return null; + var factors = Mulf.LinearChildren(radicand).ToList(); + var third = Number.Rational.Create(1, 3); + var y = MathS.Pow(radicand, third); + var rootOfThree = MathS.Sqrt(3); + for (var i = 0; i < factors.Count; i++) + { + if (!TreeAnalyzer.TryGetPolyLinear(factors[i], x, out var slope, out var offset) || slope.ContainsNode(x) || offset.ContainsNode(x) + || VanishesIdentically(slope)) + continue; + var linear = factors[i]; + Entity quotient = Number.Integer.One; + for (var k = 0; k < factors.Count; k++) + if (k != i) + quotient = quotient == Number.Integer.One ? factors[k] : quotient * factors[k]; + var w = 2 * quotient * slope - linear * quotient.Differentiate(x); + var linearSquared = MathS.Pow(linear, 2); + // W D = (a Q + b L^2) N, or L W D = (a L^2 + b Q) N, for R = N/D. + var (first, second, wanted) = squared + ? (linearSquared * numerator, quotient * numerator, linear * w * below) + : (quotient * numerator, linearSquared * numerator, w * below); + if (Coefficients(first) is not { } firstRead || Coefficients(second) is not { } secondRead || Coefficients(wanted) is not { } wantedRead) + continue; + var powers = firstRead.Keys.Concat(secondRead.Keys).Concat(wantedRead.Keys).Distinct().OrderBy(power => power).ToList(); + Entity At(Dictionary read, EInteger power) + => read.TryGetValue(power, out var value) ? value.Expand().InnerSimplified : Number.Integer.Zero; + var matrix = powers.Select(power => new[] { At(firstRead, power), At(secondRead, power) }).ToArray(); + var rhs = powers.Select(power => At(wantedRead, power)).ToArray(); + if (!Functions.PartialFractions.TrySolveLinear(matrix, rhs, out var values)) + continue; + var (a, b) = (LowestOverTheSymbols(values[0]), LowestOverTheSymbols(values[1])); + if (VanishesIdentically(a) || VanishesIdentically(b)) + continue; + var mu = LowestOverTheSymbols(-b / a); + // Any cube root; the real one of a negative number, so that the answer is real where y is. + var m = mu.Evaled is Number.Real { IsNegative: true } ? -MathS.Pow(-mu, third) : MathS.Pow(mu, third); + var t = squared ? m * y / linear : m * linear / y; + // ln((1 - T)^2)/2 for ln(1 - T): the same derivative, and real wherever T is. + var closed = (MathS.Ln(1 + t + MathS.Pow(t, 2)) - MathS.Ln(MathS.Pow(1 - t, 2))) / 6 + MathS.Arctan((1 + 2 * t) / rootOfThree) / rootOfThree; + var answer = (LowestOverTheSymbols((squared ? -3 : 3) / a) / m * closed).InnerSimplified; + if (Functions.PartialFractions.DerivativeHoldsAtSampledPoints(answer, expr, x)) + return answer; + } + return null; + } + /// /// Powers that are not whole of polynomials that are constant multiples of powers of one /// linear, gathered into one power of it: B^p = (B^p/L^(k p)) L^(k p) for @@ -37502,6 +37821,92 @@ private static bool NotRealAt(Entity expr, Entity.Variable x, double at) return table; } + /// + /// A rational function of x and R = sqrt(Q) beside a half-odd power of + /// L + R, for a quadratic Q and a linear L with Q - L^2 = c (x - r)^2, + /// in Euler's u = (R - L)/(x - r): x - r = 2 l u/D, R = L + u (x - r) and + /// L + R = 2 c l/D, for l = L(r), D = c - 2 b u - u^2 and b the slope + /// of L, so that the integrand is rational in u beside a half-odd power of the + /// quadratic D. Bondarenko's sqrt(1 + sqrt(x) + sqrt(1 + 2x + 2 sqrt(x))) is + /// 8 sqrt(2) u (1 + u^2)/D^(7/2) under x = t^2. + /// + /// + /// With s = x - r, R^2 = L^2 + c s^2 and R = L + u s give + /// s (c - 2 b u - u^2) = 2 l u, (L + R)(R - L) = c s^2 gives L + R = c s/u, + /// and dx = 2 l (c + u^2)/D^2 du. The power (L + R)^(k/2) is asked as + /// (2 c l)^(k/2)/D^(k/2), which it is up to a sign, and the answer in u is + /// multiplied by the quotient of the two, whose square is one: it holds on every branch. + /// Declined before, since no substitution any rule tries makes the nested root rational. + /// The answer is differentiated back at sampled points. + /// https://github.com/asc-community/AngouriMath/issues/233 + /// + internal static Entity? SolveAHalfOddPowerOfALinearPlusTheRootOfAQuadraticByEulersSubstitution(Entity expr, Entity.Variable x, bool integrateByParts) + { + static bool IsAHalfOddPower(Entity power) => power is Number.Rational rational and not Number.Integer + && rational.ERational.Denominator.Equals(EInteger.FromInt32(2)) && rational.ERational.Numerator.CanFitInInt32(); + var half = Number.Rational.Create(1, 2); + // The one half-odd power of a sum that holds a root. + Powf? outer = null; + foreach (var node in expr.Nodes) + if (node is Powf(var sum, var power) found && IsAHalfOddPower(power) && sum is Sumf or Minusf + && sum.Nodes.Any(inner => inner is Powf(var b, var p) && IsAHalfOddPower(p) && b.ContainsNode(x))) + { + if (outer is not null && outer != found) + return null; + outer = found; + } + if (outer is null) + return null; + Entity? quadratic = null; + Entity linear = Number.Integer.Zero; + foreach (var term in Sumf.LinearChildren(outer.Base)) + if (term is Powf(var b, var p) && p == half && b.ContainsNode(x) && quadratic is null) + quadratic = b; + else + linear += term; + Entity Coefficient(Dictionary read, int power) + => read.TryGetValue(EInteger.FromInt32(power), out var value) ? value : Number.Integer.Zero; + if (quadratic is null + || !TreeAnalyzer.TryGetPolynomial(linear, x, out var l) || l.Any(pair => pair.Key.Sign < 0 || pair.Key.CompareTo(EInteger.One) > 0 || pair.Value.ContainsNode(x)) + || !TreeAnalyzer.TryGetPolynomial(quadratic, x, out var q) || q.Any(pair => pair.Key.Sign < 0 || pair.Key.CompareTo(EInteger.FromInt32(2)) > 0 || pair.Value.ContainsNode(x))) + return null; + var (l0, l1) = (Coefficient(l, 0), Coefficient(l, 1)); + // Q - L^2 = c x^2 + e x + f, a square. + var c = LowestOverTheSymbols(Coefficient(q, 2) - l1 * l1); + var e = LowestOverTheSymbols(Coefficient(q, 1) - 2 * l0 * l1); + var f = LowestOverTheSymbols(Coefficient(q, 0) - l0 * l0); + if (VanishesIdentically(c) || !VanishesIdentically(e * e - 4 * c * f)) + return null; + var r = LowestOverTheSymbols(-e / (2 * c)); + var atR = LowestOverTheSymbols(l0 + l1 * r); + if (VanishesIdentically(atR)) + return null; + var u = Variable.CreateUnique(expr, "u_euler"); + var placeholder = Variable.CreateUnique(expr + u, "S_euler"); + var d = c - 2 * l1 * u - MathS.Pow(u, 2); + var s = 2 * atR * u / d; + var rootInU = atR + (l1 + u) * s; + // The power first, since a replacement reaches the root inside it before the power. + var named = expr.Replace(node => node == outer ? placeholder : node); + if (named.ContainsNode(outer.Base)) + return null; + var inU = named.Replace(node => node is Powf(var b, var p) && b == quadratic && IsAHalfOddPower(p) + ? MathS.Pow(rootInU, Number.Integer.Create(((Number.Rational)p).ERational.Numerator)) + : node) + .Substitute(x, r + s); + if (inU.ContainsNode(x)) + return null; + var exponent = outer.Exponent; + Entity Sigma(Entity at) => MathS.Pow(2 * c * atR, exponent) / MathS.Pow(d.Substitute(u, at), exponent); + var integrand = Functions.PartialFractions.Bare(inU.Substitute(placeholder, Sigma(u)) * 2 * atR * (c + MathS.Pow(u, 2)) / MathS.Pow(d, 2)); + if (Integration.ComputeAsAQuestionOfItsOwn(integrand, u, integrateByParts) is not { } inTermsOfU + || inTermsOfU.Nodes.Any(node => node is Integralf || node == MathS.NaN)) + return null; + var uOfX = (MathS.Sqrt(quadratic) - linear) / (x - r); + var answer = (outer / Sigma(uOfX) * inTermsOfU.Substitute(u, uOfX)).InnerSimplified; + return Functions.PartialFractions.DerivativeHoldsAtSampledPoints(answer, expr, x) ? answer : null; + } + /// /// A fractional power of a quotient whose denominator is positive for every real /// is written as the power of the numerator over the power of the diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 2e33e1ec9..7d4b943fa 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -1234,6 +1234,10 @@ internal static bool ASumOverRootsWouldAnswer // And the secant beside powers of a + a sec(y) and c - c sec(y), whose product is a // square of the tangent: in u = a + a sec(y), a symbolic power of it too. if ((answer = IndefiniteIntegralSolver.SolveASecantBesidePowersOfItsConjugateSums(expr, x, integrateByParts)) is { }) return answer; + // And a function of the roots of sec(y) + 1 and sec(y) - 1, in their difference w, of + // which both and the secant are rational functions: Charlwood's + // `sqrt(sqrt(1 + sec(x)) - sqrt(sec(x) - 1))`. + if ((answer = IndefiniteIntegralSolver.SolveAFunctionOfTheRootsOfTheSecantPlusAndMinusOneInTheirDifference(expr, x, integrateByParts)) is { }) return answer; // And an odd power of the tangent beside a function of the secant, in u = sec(y). if ((answer = IndefiniteIntegralSolver.SolveAnOddPowerOfTheTangentBesideAFunctionOfTheSecant(expr, x, integrateByParts)) is { }) return answer; // And `a ± a cosh(y)` under a fractional power: `2a cosh(y/2)^2`, `-2a sinh(y/2)^2`. @@ -1536,6 +1540,10 @@ internal static bool ASumOverRootsWouldAnswer // apart where its residue, the radicand at its root, is zero. if ((answer = IndefiniteIntegralSolver.SolveByWritingALinearBelowTheBarOverTheRadicand(expr, x, integrateByParts)) is { }) return answer; if ((answer = IndefiniteIntegralSolver.SolveByEulerSubstitution(expr, x)) is { }) return answer; + // And a half-odd power of L + sqrt(Q) where Q - L^2 is a square, in u = (sqrt(Q) - L)/(x - r), + // of which x, the inner root and the outer one's square are rational functions: + // Bondarenko's `sqrt(1 + sqrt(x) + sqrt(1 + 2x + 2 sqrt(x)))` under x = t^2. + if ((answer = IndefiniteIntegralSolver.SolveAHalfOddPowerOfALinearPlusTheRootOfAQuadraticByEulersSubstitution(expr, x, integrateByParts)) is { }) return answer; // And a root over the root of a quadratic, or over the roots of two linears with one // slope, in the variable that rationalises the roots inside: `sqrt(1 - x^2 + x sqrt(x^2 - 1))`. if ((answer = IndefiniteIntegralSolver.SolveANestedRootByRationalisingTheRootsInIt(expr, x, integrateByParts)) is { }) return answer; @@ -1558,6 +1566,11 @@ internal static bool ASumOverRootsWouldAnswer // rationalises it, and the elementary ones are logarithms of `L - y` for linear L // whose cube agrees with the polynomial at the poles, found by an ansatz. if ((answer = CubeRootLogarithmAnsatz.Integrate(expr, x)) is { }) return answer; + // And a quotient beside the cube root of a polynomial with a linear factor L, at the + // coefficients where it is a constant times T'/(1 - T^3) for T = m L/y or m y/L, in + // closed form: after the ansatz, which answers the numeric ones and declined Welz's, + // with symbols in the radicand. + if ((answer = IndefiniteIntegralSolver.SolveAPseudoEllipticQuotientBesideACubeRootWithALinearFactor(expr, x)) is { }) return answer; // And of one square root of a squarefree quadratic, cubic or quartic -- the latter // two elliptic, the first rational but with residues Euler's substitutions leave in // a field of degree four: the elementary ones are logarithms and arctangents of @@ -1571,6 +1584,11 @@ internal static bool ASumOverRootsWouldAnswer // And a sum with the root below the bar, over its conjugate: after every rule above, // which answer the numeric spellings, `1/(a c + b c x^2 + d sqrt(a + b x^2))` among them. if ((answer = IndefiniteIntegralSolver.SolveByTheConjugateOfASumWithARootOfAQuadraticBelowTheBar(expr, x, integrateByParts)) is { }) return answer; + // A linear over a linear beside the root of a cubic or a quartic, where the integral is a + // function of the numerator's square over the root: Welz's + // `(1 + x - sqrt(3))/((1 + x + sqrt(3)) sqrt(x^4 + 4 sqrt(3) x^2 - 4))`, whose pole is + // irrational. After the ansatz, which answers the ones with rational poles. + if ((answer = IndefiniteIntegralSolver.SolveALinearOverALinearBesideTheRootOfAQuarticByTheSquareOfTheNumerator(expr, x)) is { }) return answer; // And a power of a product with a factor under a whole power, that power taken out in // front of a constant: `(c sin(a + b x)^3)^(1/3)` is `K sin(a + b x)`. if ((answer = IndefiniteIntegralSolver.SolveByTakingTheWholePowersOutOfAPowerOfAProduct(expr, x, integrateByParts)) is { }) return answer; @@ -1600,6 +1618,10 @@ internal static bool ASumOverRootsWouldAnswer // an ansatz finds it or nothing does. After the substitutions, which answer the // linear-exponent cases in their own terms. if ((answer = IndefiniteIntegralSolver.SolveByExponentialAnsatz(expr, x)) is { }) return answer; + // And where it declines for a simple pole at a zero of the exponent, the derivative of a + // multiple of Ei(h) taken out first: Hebisch's + // `(2 + 2x + 3x^2 - x^3 + 2x^4) e^(x/(2 + x^2))/(2x + x^3)`. + if ((answer = IndefiniteIntegralSolver.SolveAnExponentialBesideAMultipleOfTheDerivativeOfItsExponentialIntegral(expr, x)) is { }) return answer; // An exponential of a polynomial in the reciprocal of a linear that is not a Gaussian, // under the Gaussian's substitution `u = 1/L`. After the ansatz, which answers some of // these in the base: `f^(a + b/x)/x^4` is `f^(a + b/x)` times a polynomial in `1/x`. diff --git a/Sources/Tests/UnitTests/Calculus/AFunctionOfTheRootsOfTheSecantPlusAndMinusOneIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/AFunctionOfTheRootsOfTheSecantPlusAndMinusOneIntegralTest.cs new file mode 100644 index 000000000..ffdaba188 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/AFunctionOfTheRootsOfTheSecantPlusAndMinusOneIntegralTest.cs @@ -0,0 +1,49 @@ +// +// 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 function of sqrt(sec(u) + 1) and sqrt(sec(u) - 1), integrated in their + /// difference, of which both roots and the secant are rational functions. Charlwood's was + /// declined. + /// #233 + /// + [Trait("Area", "Calculus")] + public sealed class AFunctionOfTheRootsOfTheSecantPlusAndMinusOneIntegralTest + { + [Theory] + [InlineData("sqrt(-sqrt(-1+sec(x))+sqrt(1+sec(x)))")] + [InlineData("1/sqrt(sqrt(1+sec(2*x))-sqrt(sec(2*x)-1))")] + public void InTheDifferenceOfTheRoots(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}"); + var derivative = integral.Substitute("C", 0).Differentiate("x"); + var original = integrand.ToEntity(); + var compared = 0; + foreach (var at in new[] { -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/AHalfOddPowerOfALinearPlusTheRootOfAQuadraticIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/AHalfOddPowerOfALinearPlusTheRootOfAQuadraticIntegralTest.cs new file mode 100644 index 000000000..85474e0b1 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/AHalfOddPowerOfALinearPlusTheRootOfAQuadraticIntegralTest.cs @@ -0,0 +1,51 @@ +// +// 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 half-odd power of L + sqrt(Q) where Q - L^2 is a square, in Euler's + /// u = (sqrt(Q) - L)/(x - r), under which the nested root is the root of a quadratic in + /// u. Bondarenko's two were declined. + /// #233 + /// + [Trait("Area", "Calculus")] + public sealed class AHalfOddPowerOfALinearPlusTheRootOfAQuadraticIntegralTest + { + [Theory] + [InlineData("sqrt(1+sqrt(x)+sqrt(1+2*x+2*sqrt(x)))")] + [InlineData("sqrt(sqrt(2)+sqrt(x)+sqrt(2+2*x+sqrt(8)*sqrt(x)))")] + [InlineData("x*sqrt(1+x+sqrt(1+2*x+2*x^2))")] + [InlineData("1/sqrt(1+x+sqrt(1+2*x+2*x^2))")] + public void InEulersSubstitution(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}"); + var derivative = integral.Substitute("C", 0).Differentiate("x"); + var original = integrand.ToEntity(); + var compared = 0; + foreach (var at in new[] { -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/ALinearOverALinearBesideTheRootOfAQuarticByTheSquareOfTheNumeratorIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/ALinearOverALinearBesideTheRootOfAQuarticByTheSquareOfTheNumeratorIntegralTest.cs new file mode 100644 index 000000000..418f73563 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/ALinearOverALinearBesideTheRootOfAQuarticByTheSquareOfTheNumeratorIntegralTest.cs @@ -0,0 +1,51 @@ +// +// 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 linear over a linear beside the root of a quartic whose integral is an inverse hyperbolic + /// tangent, or an arctangent, of the numerator's square over the root. Welz's two and Rubi's + /// four in 1.2.2.3, whose poles are irrational, were declined. + /// #233 + /// + [Trait("Area", "Calculus")] + public sealed class ALinearOverALinearBesideTheRootOfAQuarticByTheSquareOfTheNumeratorIntegralTest + { + [Theory] + [InlineData("(1+x-sqrt(3))/((1+x+sqrt(3))*sqrt(-4+x^4+4*x^2*sqrt(3)))")] + [InlineData("(1+x+sqrt(3))/((1+x-sqrt(3))*sqrt(-4+x^4-4*x^2*sqrt(3)))")] + [InlineData("(1+2*x-sqrt(3))/((1+2*x+sqrt(3))*sqrt(-1+4*x^4+4*x^2*sqrt(3)))")] + [InlineData("(1+2*x+sqrt(3))/((1+2*x-sqrt(3))*sqrt(-1+4*x^4-4*x^2*sqrt(3)))")] + public void AsAFunctionOfTheSquareOfTheNumeratorOverTheRoot(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}"); + var derivative = integral.Substitute("C", 0).Differentiate("x"); + var original = integrand.ToEntity(); + var compared = 0; + foreach (var at in new[] { -3.4, -1.2, -0.7, 0.3, 0.8, 1.3, 2.9, 3.6 }) + { + 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/APseudoEllipticQuotientBesideACubeRootWithALinearFactorIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/APseudoEllipticQuotientBesideACubeRootWithALinearFactorIntegralTest.cs new file mode 100644 index 000000000..cccc1af26 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/APseudoEllipticQuotientBesideACubeRootWithALinearFactorIntegralTest.cs @@ -0,0 +1,51 @@ +// +// 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 quotient beside the cube root of a polynomial with a linear factor, or of its square, + /// that is a constant times T'/(1 - T^3) for T = m L/y or m y/L. With + /// symbols in the radicand, Welz's three were declined. + /// #233 + /// + [Trait("Area", "Calculus")] + public sealed class APseudoEllipticQuotientBesideACubeRootWithALinearFactorIntegralTest + { + [Theory] + [InlineData("1/(x*((-1+x)*(q-2*q*x+x^2))^(1/3))")] + [InlineData("(2-(1+k)*x)/(((1-x)*x*(1-k*x))^(1/3)*(1-(1+k)*x))")] + [InlineData("(1-k*x)/((1+(-2+k)*x)*((1-x)*x*(1-k*x))^(2/3))")] + public void InTheCubeRootOverALinearFactor(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("q", 1.3).Substitute("k", 0.4); + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pinned(integrand.ToEntity()); + var compared = 0; + foreach (var at in new[] { -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/AnExponentialBesideTheDerivativeOfItsExponentialIntegralIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/AnExponentialBesideTheDerivativeOfItsExponentialIntegralIntegralTest.cs new file mode 100644 index 000000000..dfc6cf212 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/AnExponentialBesideTheDerivativeOfItsExponentialIntegralIntegralTest.cs @@ -0,0 +1,50 @@ +// +// 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 rational function beside a rational function that is the derivative of + /// e^h N/D and of a multiple of Ei(h). The ansatz for e^h N/D alone + /// declined Hebisch's, the residue at the zero of h being the exponential integral's. + /// #233 + /// + [Trait("Area", "Calculus")] + public sealed class AnExponentialBesideTheDerivativeOfItsExponentialIntegralIntegralTest + { + [Theory] + [InlineData("(2+2*x+3*x^2-x^3+2*x^4)*e^(x/(2+x^2))/(2*x+x^3)")] + [InlineData("(a+x+(2-a)*x^2-x^3+2*x^4)*e^(x/(1+x^2))/(x+x^3)")] + public void WithTheExponentialIntegralTakenOut(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("a", 1.3); + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pinned(integrand.ToEntity()); + var compared = 0; + foreach (var at in new[] { -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/CubeRootLogarithmAnsatzTest.cs b/Sources/Tests/UnitTests/Calculus/CubeRootLogarithmAnsatzTest.cs index 90bbca09a..3635c9234 100644 --- a/Sources/Tests/UnitTests/Calculus/CubeRootLogarithmAnsatzTest.cs +++ b/Sources/Tests/UnitTests/Calculus/CubeRootLogarithmAnsatzTest.cs @@ -106,12 +106,19 @@ private static void DifferentiatesBack(string integrand) public void WithASymbolInTheRadicand(string integrand) => DifferentiatesBack(integrand); /// - /// Declined, quickly, rather than answered wrongly: a constant q^(1/3) the field - /// does not hold, and a pole pair over Q(sqrt(3)) whose answer needs lines with - /// coefficients there. + /// A constant q^(1/3) the ansatz's field does not hold, which the ansatz declines and + /// the rule for T'/(1 - T^3) answers, T the root over the linear factor + /// -1 + x times q^(1/3). /// [Theory] [InlineData("1/(x*((-1 + x)*(q - 2*q*x + x^2))^(1/3))")] + public void WhereTheFieldIsNotHeldButTheRootOverALinearFactorIs(string integrand) => DifferentiatesBack(integrand); + + /// + /// Declined, quickly, rather than answered wrongly: a pole pair over Q(sqrt(3)) + /// whose answer needs lines with coefficients there. + /// + [Theory] [InlineData("1/((3 - x^2)*(2 + x^2)^(1/3))")] public void DeclinedWhereTheFieldIsNotHeld(string integrand) => Assert.Contains("integral(", integrand.ToEntity().Integrate("x").Stringize());