From 07dc9c8830842acc8112e8b09332fc8dc426027d Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Wed, 21 Oct 2026 20:20:49 +0000 Subject: [PATCH] Integrate a transcendental function of one shifted linear in it, the logarithms of a quotient onto the exponential integral, and derivatives over a power of a logarithm or an inverse - A function of one shifted linear c + d x alone with something other than sums, products and powers in it, written in u = c + d x where a power of the linear stands below the bar inside a sum or the linear is written two ways: F^(a + b/(c + d x)^3)/(c + d x)^19, ln(c (d + k/(f + g x)^3)^q). Only for an integrand as written, not one a substitution expands: the inverse sine's of (d + e x^2)^3 (a + b asinh(c x))^2 is a sum of exponentials of linears that cost 370 MB to read. - Powers of two linears beside a negative power of a logarithm of their quotient, onto the exponential integral in that quotient; the zero- discriminant case of SolveAnExponentialOverSeveralLinears no longer divides by the difference of its two equal roots. - The derivative of a function over a power of a logarithm, an inverse trigonometric function or a sum holding a logarithm, whose terms are not elementary apart. - A product of powers whose own logarithmic derivative is a constant, and a power with x in its base and its exponent as an exponential, its free terms raised: x^(-2 - 1/x) (1 - ln(x)) is -(x^(-1/x))'. - The Risch-Norman ansatz with li(x), and erfi beside one Gaussian with a factor of x alone below the bar, in a second pass: e^((a + x)^2)/x^2 - 2 a e^((a + x)^2)/x. Beside a polynomial, or two Gaussians, the pass declined at three times the allocation of the rules after it, and is not taken there. Chain against master 6e16aae0: pocket of 1,105 rows, 1,072 -> 1,085 solved, 0 wrong; family 3's sample 222 -> 224, the others unchanged; the 13 rows that moved, alone, 0 -> 13 solved; 6,603 rows compared for allocation, none grew past 1.3x. Part of #233. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 92 ++- .../Integration/IndefiniteIntegralSolver.cs | 627 +++++++++++++++++- .../Integration/Integration.Definition.cs | 12 + ...owerOfALogarithmOrAnInverseIntegralTest.cs | 67 ++ ...AnsatzBesideTheLogarithmOfXIntegralTest.cs | 68 ++ ...lFunctionOfOneShiftedLinearIntegralTest.cs | 67 ++ ...lsAloneAsTheirOwnDerivativeIntegralTest.cs | 68 ++ ...msLinearsOntoTheExponentialIntegralTest.cs | 75 +++ 8 files changed, 1062 insertions(+), 14 deletions(-) create mode 100644 Sources/Tests/UnitTests/Calculus/AFunctionOverAPowerOfALogarithmOrAnInverseIntegralTest.cs create mode 100644 Sources/Tests/UnitTests/Calculus/ARischNormanAnsatzBesideTheLogarithmOfXIntegralTest.cs create mode 100644 Sources/Tests/UnitTests/Calculus/ATranscendentalFunctionOfOneShiftedLinearIntegralTest.cs create mode 100644 Sources/Tests/UnitTests/Calculus/TheExponentialsAloneAsTheirOwnDerivativeIntegralTest.cs create mode 100644 Sources/Tests/UnitTests/Calculus/TheQuotientOfTheLogarithmsLinearsOntoTheExponentialIntegralTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 7b8cb4a18..0cb4c08b6 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -83,6 +83,24 @@ powers of `c + d x` itself were read before ([#233](https://github.com/asc-commu | `"e^(k/(c+d*x))/(a+b*x)".ToEntity().Integrate("x")` | `integral(...)` | two `Ei`, of `k/(c + d x)` and of `k/(c + d x) - k b/(a d - b c)` | | `"f^(c/(a+b*x)^2)*x^4".ToEntity().Integrate("x")` | `integral(...)` | 1,262 characters, the error function and powers of `1/(a + b x)` | +### A transcendental function of one shifted linear is integrated in the linear + +**Answers where there were none.** Rubi's 2.3 `F^(a + b/(c + d x)^3)/(c + d x)^19` and 3.4 `ln(c (d + e/(f + g x)^3)^q)` +ran past the budget, and 3.3's `(e g + f g x)^(p - 1)/ln(d (e + f x)^p)` was declined. Where every x stands in one linear +`c + d x`, up to constant multiples, and either a power of it stands below the bar inside a sum or it is the base of powers +written two ways, the integrand is now written in `u = c + d x` before the polynomial term takes the slope out of a power +of it: that wrote `(c/d + x)^19` beside the `(c + d x)^3` in the exponent, and the exponential ansatz on the two spellings +took longer the higher the power, 24 s at the 16th. In u it is 20 ms. Rows of these shapes that were answered are answered +with the slope in another place, `F^(a + b/(c + d x)^2)/(c + d x)^2` in 15 ms where it took 1.6 s +([#233](https://github.com/asc-community/AngouriMath/issues/233)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"F^(a+b/(c+d*x)^3)/(c+d*x)^19".ToEntity().Integrate("x")` | `integral(...)` | 386 characters, `F^(a + b/(c + d x)^3)` times a polynomial in `(c + d x)^3` | +| `"ln(c*(d+k/(f+g*x)^3)^q)".ToEntity().Integrate("x")` | `integral(...)` | 626 characters, by parts, the cube roots of `-k/d` | +| `"(k*g+f*g*x)^(p-1)/ln(d*(k+f*x)^p)".ToEntity().Integrate("x")` | `integral(...)` | 150 characters, `Ei(ln(d (k + f x)^p))` | +| `"F^(a+b/(c+d*x)^2)/(c+d*x)^2".ToEntity().Integrate("x")` | `integral(...)` | `-F^a sqrt(pi) erf(sqrt(-b ln F)/(c + d x))/(2 d sqrt(-b ln F))` | + ### A polynomial over a symbolic quartic that is two quadratics in a hidden variable is split over them **Answers where there were none.** `1/(a + b x + c x^2 + b x^3 + a x^4)` was declined, and so were Rubi's 1.3.1 @@ -216,6 +234,22 @@ it is still declined ([#233](https://github.com/asc-community/AngouriMath/issues | `"e^(n*acoth(a*x))*x/(c-a^2*c*x^2)^(3/2)".ToEntity().Integrate("x")` | `integral(...)` | 155 characters | | `"e^(n*acoth(a*x))*x^3/(c-a^2*c*x^2)^(5/2)".ToEntity().Integrate("x")` | `integral(...)` | 397 characters | +### A product of powers whose logarithmic derivative is a constant is its own derivative's multiple + +**Answers where there were none.** Rubi's 2.3 `(a F^(c + d x))^m (b F^(e + f x))^n` and `x^(-2 - 1/x) (1 - ln(x))`, and 6.7.1's +`cosh(x)^x (ln(cosh(x)) + x tanh(x))` and `F^(a + b x) (cosh(c + d x) - sinh(c + d x))^n`, were declined. The derivative of +a product of powers was read with some of them raised by one, and never with none: where the product's own logarithmic +derivative is a constant, the product over that constant is the answer. A power with x in its base and its exponent is an +exponential, `f^g = e^(g ln f)`, its terms free of x apart, and where the rest of the integrand is a constant times the +exponentials' logarithmic derivative the exponentials are the answer: `x^(-2 - 1/x) (1 - ln(x))` is `-(x^(-1/x))'` +([#233](https://github.com/asc-community/AngouriMath/issues/233)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"(a*F^(c+d*x))^m*(b*F^(k+f*x))^n".ToEntity().Integrate("x")` | `integral(...)` | `(a F^(c + d x))^m (b F^(k + f x))^n/((d m + f n) ln(F))` | +| `"x^(-2-1/x)*(1-ln(x))".ToEntity().Integrate("x")` | `integral(...)` | `-x^(-1/x)` | +| `"cosh(x)^x*(ln(cosh(x))+x*tanh(x))".ToEntity().Integrate("x")` | `integral(...)` | `((e^x + e^(-x))/2)^x`, which is `cosh(x)^x` | + ### A power of a quotient of linears beside whole powers of them is integrated under the quotient **Answers where there were none.** `e^(n acoth(a x))/(c - a^2 c x^2)^2` was declined, and so were its `^3` and `^4`, Rubi's @@ -245,6 +279,26 @@ found by one linear solve whose last equation is checked; where it fails the rul | `"e^(1/2*atanh(a*x))*x^2/(c-a^2*c*x^2)^(9/8)".ToEntity().Integrate("x")` | `integral(...)` | 121 characters | | `"e^(2*atanh(a*x))*x^2/(c-a^2*c*x^2)^3".ToEntity().Integrate("x")` | `integral(...)` | 109 characters | +### Powers of two linears beside a negative power of a logarithm of their quotient are integrated onto the exponential integral + +**Answers where there were none.** Rubi's 3.2.1 `1/((a g + b g x)^2 (A + B ln(e (a + b x)/(c + d x)))^2)` and three more +ran past the budget, and the rest of its rows and 3.2.2's with the powers of the linears summing to `-2` or `-3` took from +0.8 s to past 8 s: twenty of them past 8 s on a loaded machine, now under a quarter of a second. Under `t = (a + b x)/(c + d x)` each is a power of `t` and a polynomial beside a negative power of +`A + B ln(e t^n)`, which the rule for a power over a power of a logarithm answers onto `Ei`; that substitution was the +rule's already, far down the chain, and the polynomial term reached first took `b^2 g^2` out of `(a g + b g x)^2` and +asked again, past the budget. It is now tried first for these shapes. And `1/(t^2 (A + B ln(k t))^2)`, the question it +asks, had an answer with no value at any point: under `u = ln(k t)` the square `B^2 u^2 + 2 A B u + A^2` was split over +its two roots, which are one, and the partial fractions divided by their difference. A quadratic below the bar whose +discriminant is zero is now one linear twice ([#233](https://github.com/asc-community/AngouriMath/issues/233)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"1/((a*g+b*g*x)^2*(A+B*ln(k*(a+b*x)/(c+d*x)))^2)".ToEntity().Integrate("x")` | no answer in twenty seconds | 340 characters, `Ei(-(A + B ln(k (a + b x)/(c + d x)))/B)` and the elementary term | +| `"1/((a*g+b*g*x)^2*(A+B*ln(k*((a+b*x)/(c+d*x))^n))^2)".ToEntity().Integrate("x")` | `integral(...)` | 394 characters | +| `"1/((a*g+b*g*x)^2*(A+B*ln(k*(c+d*x)^2/(a+b*x)^2)))".ToEntity().Integrate("x")` | no answer in twenty seconds | 231 characters, one `Ei` | +| `"1/((a*g+b*g*x)^3*(A+B*ln(k*(a+b*x)/(c+d*x)))^2)".ToEntity().Integrate("x")` | no answer in twenty seconds | 644 characters, two powers of t | +| `"1/(x^2*(A+B*ln(k*x))^2)".ToEntity().Integrate("x")` | `integral(...)` | 133 characters, `Ei(-(A/B + ln(k x)))`, where before this change master gave an answer with no value at any point | + ### The last digits of an evaluated sine, cosine, tangent, arcsine or arctangent **Silent, and in the hundredth digit.** `EvalNumerical` at the default hundred digits gives the @@ -323,12 +377,29 @@ no order. Five integrals whose working squares such a sum were past the budget a | Input | Was (2.5.0) | Now | |---|---|---| -| `"1/((a*g+b*g*x)^2*(A+B*ln(k*((a+b*x)/(c+d*x))^n))^2)".ToEntity().Integrate("x")` | `integral(...)` | 412 characters | -| `"1/((a*g+b*g*x)^2*(A+B*ln(k*(a+b*x)/(c+d*x)))^2)".ToEntity().Integrate("x")` | past 60 s | 358 characters | +| `"1/((a*g+b*g*x)^2*(A+B*ln(k*((a+b*x)/(c+d*x))^n))^2)".ToEntity().Integrate("x")` | `integral(...)` | 394 characters | +| `"1/((a*g+b*g*x)^2*(A+B*ln(k*(a+b*x)/(c+d*x)))^2)".ToEntity().Integrate("x")` | past 60 s | 340 characters | | `"(a+a*sin(k+f*x))^(3/2)*(A+B*sin(k+f*x))/(c+d*sin(k+f*x))^2".ToEntity().Integrate("x")` | `integral(...)` | 5,373 characters | | `"sech(x)^2*tanh(x)^2/(2+tanh(x)^3)^2".ToEntity().Integrate("x")` | past 60 s | 117 characters | | `"coth(x)^3*csch(x)*sqrt(1+csch(x))".ToEntity().Integrate("x")` | `integral(...)` | 325 characters | +### The derivative of a function over a power of a logarithm, an inverse tangent or a sum with a logarithm in it is read as one + +**Answers where there were none.** Rubi's 3.2.3 `1/((c + d x)(c - a + (d - b) x) ln(Q)) + ln(1 - Q)/((a + b x)(c + d x) ln(Q)^2)` +for `Q = (a + b x)/(c + d x)`, its twin, 5.3.4's `x^3/((1 + a^2 x^2) atan(a x)^3) - 3 x^2/(2 a atan(a x)^2)` and 3.5's +`(a d n x^m - a d m x^m ln(c x^n) - b d n (q - 1) ln(c x^n)^q)/(x (a x^m + b ln(c x^n)^q)^2)` ran past the budget. Each is +`(U/V^k)' = U'/V^k - k U V'/V^(k + 1)`, whose terms are not elementary apart, so every split lost it and by parts on either +term asked the other's question again. For `V` a logarithm or an inverse trigonometric function, the terms over +`V^(k + 1)` give `U = -B/(k V')`, its rational part in lowest terms; for `V` a sum with a logarithm in it, `U` is a +monomial of the numerator times a constant read monomial by monomial. The answer is given only where its derivative is +the whole integrand at sampled points ([#233](https://github.com/asc-community/AngouriMath/issues/233)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"1/((c+d*x)*(-a+c+(-b+d)*x)*ln((a+b*x)/(c+d*x)))+ln(1+(-a-b*x)/(c+d*x))/((a+b*x)*(c+d*x)*ln((a+b*x)/(c+d*x))^2)".ToEntity().Integrate("x")` | no answer in twenty seconds | `ln(1 + (-a - b x)/(c + d x))/((a d - b c) ln((a + b x)/(c + d x)))` | +| `"x^3/((1+a^2*x^2)*atan(a*x)^3)-3/2*x^2/(a*atan(a*x)^2)".ToEntity().Integrate("x")` | `integral(...)` | `-x^3/(2 a atan(a x)^2)` | +| `"(a*d*n*x^m-a*d*m*x^m*ln(c*x^n)-b*d*n*(q-1)*ln(c*x^n)^q)/(x*(a*x^m+b*ln(c*x^n)^q)^2)".ToEntity().Integrate("x")` | `integral(...)` | `d ln(x^n c)/(a x^m + b ln(c x^n)^q)` | + ### A rational evaluated at a higher precision than before has all of its digits **More digits where there were a hundred.** A rational kept its decimal value in the precision current when it was @@ -373,6 +444,23 @@ parts what is left is a rational function and one over the root |---|---|---| | `"x^3*ln(-1+4*x+4*sqrt((x-1)*x))".ToEntity().Integrate("x")` | `integral(...)` | `x^4 ln(...)/4` minus a polynomial, `ln(8 x + 1)` and terms over `sqrt(x^2 - x)` | +### The Risch-Norman ansatz reads `ln(x)` beside x, with the logarithmic integral and the error function among its terms + +**Answers where there were none.** Rubi's 3.5 `(1 - ln(x))/(x (x + ln(x)))` and `(1 + x)/(ln(x) (x + ln(x)))`, and 2.3's +`e^((a + x)^2)/x^2 - 2 a e^((a + x)^2)/x`, were declined. The ansatz left a plain `ln(x)` to the logarithm tower's, a +polynomial in it, which has no logarithm of `x + ln(x)`; it now reads one where a sum below the bar holds x beside +`ln(x)` to two or more powers. Its answer may now hold `li(x)`, whose derivative is `1/ln(x)`, where `ln(x)` divides the +denominator, and for an exponential of a quadratic `erfi(sqrt(A) (x + B/(2 A)))`, whose derivative is a constant times +the exponential: `(1 + x)/(ln(x) (x + ln(x)))` is `(li(x) + ln(ln(x)) - ln(x + ln(x)))'`, and by parts the first term +of the third leaves the second's negative and `2 e^((a + x)^2)`. With either among the terms the elimination pins the +symbols first where it declines with them in ([#233](https://github.com/asc-community/AngouriMath/issues/233)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"(1-ln(x))/(x*(x+ln(x)))".ToEntity().Integrate("x")` | `integral(...)` | `ln(x + ln(x)) - ln(x)` | +| `"(1+x)/(ln(x)*(x+ln(x)))".ToEntity().Integrate("x")` | `integral(...)` | `li(x) + ln(ln(x)) - ln(x + ln(x))` | +| `"e^((a+x)^2)/x^2-2*e^((a+x)^2)*a/x".ToEntity().Integrate("x")` | `integral(...)` | `-e^((a + x)^2)/x + sqrt(pi) erfi(x + a)` | + ### The last digits of an evaluated logarithm, exponential, real power or complex power **Silent, and in the hundredth digit.** `ln`, `log`, `e^x`, a real base to a real power and the diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 5a953b156..2aabecbf1 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -17575,6 +17575,108 @@ node is Powf(var @base, var power) && @base.ContainsNode(u) _ => false, }; + /// The largest integrand reads; the shapes it answers are 17 to 25. + private const int MostComplexityWrittenInOneShiftedLinear = 80; + + /// + /// A function with something other than sums, products and powers in it, of one shifted + /// linear c + d x alone, written in u = c + d x: Rubi's + /// F^(a + b/(c + d x)^3)/(c + d x)^19 is F^(a + b/u^3)/(d u^19), + /// ln(c (d + e/(f + g x)^3)^q) is ln(c (d + e/u^3)^q)/g, and + /// (e g + f g x)^(p - 1)/ln(d (e + f x)^p) is (g u)^(p - 1)/(f ln(d u^p)). + /// + /// + /// In x, the polynomial term takes the slope out of a power of the linear and writes + /// (c/d + x)^19 beside the (c + d x)^3 in the exponent, two spellings of one + /// linear, and the exponential ansatz on that took longer the higher the power -- 24 s at + /// (c + d x)^16, past the budget at 19 -- for what is 20 ms in u. By parts on the + /// logarithm leaves a rational function in x of degree six with the symbols in it, where in u + /// it is 1/(d u^3 + e). Every x stands in a sum linear in it, each sum a constant + /// multiple of one of them, and either a power of the linear stands below the bar inside a + /// sum or the linear is written two ways: a function of the linear otherwise -- an + /// exponential of it, ln(c (d + e x)^n)^p -- is read as written by the rules for it, + /// and was left so. The algebraic integrands are the rule above's. At the question asked only: + /// a level down, the polynomial term's own monic spelling `(c/d + x)` beside `c + d x` is two + /// spellings, and the substitution rewrote answers the chain had given as written. In u the + /// linear is u itself, and this declines. + /// https://github.com/asc-community/AngouriMath/issues/233 + /// + internal static Entity? SolveByWritingATranscendentalFunctionOfOneShiftedLinear(Entity expr, Entity.Variable x, bool integrateByParts) + { + // A written integrand, not what a substitution expands one into: the inverse sine's of + // `(d + e x^2)^3 (a + b asinh(c x))^2` is a sum of exponentials of linears in u at the same + // level, and reading every sum in it for one was 1.4 s and 370 MB of a decline. + if (!Integration.AnsweringTheQuestionAsked || expr.Complexity > MostComplexityWrittenInOneShiftedLinear) + return null; + if (!expr.Nodes.Any(node => node.ContainsNode(x) && node is not (Variable or Sumf or Minusf or Mulf or Divf) + && !(node is Powf(_, var exponent) && !exponent.ContainsNode(x)))) + return null; + // The sums linear in x, outermost: every x must stand in one. + var linears = new List<(Entity Linear, Entity Slope, Entity Offset)>(); + var asBases = new List(); + var belowTheBarInASum = false; + bool IsLinear(Entity node) => node is Sumf or Minusf && TreeAnalyzer.TryGetPolyLinear(node, x, out var slope, out var offset) + && !slope.ContainsNode(x) && !offset.ContainsNode(x) && !TreeAnalyzer.IsZero(slope); + bool Read(Entity node, bool insideASum) + { + if (!node.ContainsNode(x)) + return true; + if (IsLinear(node)) + { + if (!linears.Any(entry => entry.Linear == node)) + { + TreeAnalyzer.TryGetPolyLinear(node, x, out var slope, out var offset); + linears.Add((node, slope!, offset!)); + } + return true; + } + if (node == x) + return false; + if (insideASum && (node is Divf(_, var under) && (IsLinear(under) || under is Powf(var raised, var p) && !p.ContainsNode(x) && IsLinear(raised)) + || node is Powf(var @base, var power) && power.Evaled is Number.Real { IsNegative: true } && IsLinear(@base))) + belowTheBarInASum = true; + if (node is Powf(var powered, var exponentOfIt) && !exponentOfIt.ContainsNode(x) && IsLinear(powered) && !asBases.Contains(powered)) + asBases.Add(powered); + var inside = insideASum || node is Sumf or Minusf; + return node.DirectChildren.All(child => Read(child, inside)); + } + // A power of the linear below the bar inside a sum -- `b/(c + d x)^3` in the exponent, or + // in the logarithm -- which in x is a rational function of a degree the power multiplies, + // or two spellings of one linear. A function of the linear alone otherwise, `ln(c (d + e x)^n)^p`, + // is read as written by the rules for the logarithm of a linear. + // Two spellings: two linears, neither monic, each the base of a power. A monic one beside + // its multiple is the polynomial term's own spelling, asked as the same question, and a + // linear in an exponent, `f^(c + d x)` beside `f^(2 c + 2 d x)`, is the exponential rules'. + if (!Read(expr, insideASum: false) || linears.Count == 0) + return null; + var twoSpellings = asBases.Count >= 2 && asBases.All(linear => + TreeAnalyzer.TryGetPolyLinear(linear, x, out var slope, out _) && !VanishesIdentically(slope - 1)); + if (!belowTheBarInASum && !twoSpellings) + return null; + // The one in the fewest symbols is the variable, so that the others are its constant + // multiples as written: `e g + f g x` is `g u` for `u = e + f x`. + var (linear, slope0, offset0) = linears.OrderBy(entry => entry.Linear.Complexity).First(); + if (VanishesIdentically(offset0)) + return null; + var u = Variable.CreateUnique(expr, "u_linear"); + var inU = new Dictionary(); + foreach (var (other, slope, offset) in linears) + { + if (!VanishesIdentically(slope * offset0 - slope0 * offset)) + return null; + var ratio = LowestOverTheSymbols((slope / slope0).InnerSimplified); + inU[other] = ratio == Number.Integer.One ? u : ratio * u; + } + var rewritten = expr.Replace(node => inU.TryGetValue(node, out var written) ? written : node); + if (rewritten.ContainsNode(x)) + return null; + // dx = du/d. + if (Integration.ComputeAsAQuestionOfItsOwn((rewritten / slope0).InnerSimplified, u, integrateByParts) is not { } answer) + return null; + var back = answer.Substitute(u, linear); + return back.Nodes.Any(node => node == MathS.NaN) ? null : back; + } + /// /// A polynomial times powers of error functions of one shifted linear, written in /// u = a + b x: x erf(a + b x)^2 is (u - a) erf(u)^2/b^2, which by parts @@ -19255,6 +19357,7 @@ static void Join(List<(Number.Rational Power, Entity Cofactor)> groups, Number.R var raisable = new List<(Entity Base, Entity Exponent)>(); var logarithmicDerivativesKept = new List(); Entity kept = Number.Integer.One; + var varyingInBoth = false; foreach (var (written, writtenUnderneath) in FactorsOfTheIntegrand(expr)) { // A secant is a power of the cosine, and a cosecant of the sine, below the bar, @@ -19294,18 +19397,63 @@ static void Join(List<(Number.Rational Power, Entity Cofactor)> groups, Number.R kept = underneath ? kept / factor : kept * factor; continue; } - // A power with x in its base and its exponent is the exponential of the exponent - // times the base's logarithm, kept as the exponentials are, with that derivative: - // `cosh(x)^x (ln(cosh(x)) + x tanh(x))` is `(cosh(x)^x)'`, and raised it was squared. - if (factor is Powf(var variableBase, var variableExponent) && variableBase.ContainsNode(x) && variableExponent.ContainsNode(x)) + // And a power with x in its base and its exponent, `f^g = e^(g ln f)`, its terms + // free of x apart as a power to raise: `x^(-2 - 1/x)` is `x^(-2) x^(-1/x)`. + if (factor is Powf(var varyingBase, var varyingExponent) && varyingBase.ContainsNode(x)) { - var derivative = Functions.PartialFractions.Bare((variableExponent * MathS.Ln(variableBase)).Differentiate(x).InnerSimplified); + varyingInBoth = true; + Entity free = Number.Integer.Zero, varying = Number.Integer.Zero; + foreach (var term in Sumf.LinearChildren(varyingExponent)) + if (term.ContainsNode(x)) + varying += term; + else + free += term; + varying = varying.InnerSimplified; + free = free.InnerSimplified; + if (free.Evaled is not Number.Complex { IsZero: true }) + raisable.Add((varyingBase, underneath ? (-free).InnerSimplified : free)); + var exponential = MathS.Pow(varyingBase, varying); + var derivative = Functions.PartialFractions.Bare((varying * MathS.Ln(varyingBase)).Differentiate(x).InnerSimplified); logarithmicDerivativesKept.Add(underneath ? -derivative : derivative); - kept = underneath ? kept / factor : kept * factor; + kept = underneath ? kept / exponential : kept * exponential; continue; } raisable.Add((factor, underneath ? Number.Integer.MinusOne : Number.Integer.One)); } + // The exponentials alone, the rest of the integrand a constant multiple of their + // logarithmic derivative, where one of them is a power with x in its base and its exponent: + // `x^(-2 - 1/x) (1 - ln(x))` is `-(x^(-1/x))'`, and `cosh(x)^x (ln(cosh(x)) + x tanh(x))` is + // `(cosh(x)^x)'`. No power is raised, the rest is the derivative's, and raising x by one + // could not reach the `x^2` that `(-ln(x)/x)'` lowers. With constant bases only it is the + // substitution's, which answers it as written. + if (varyingInBoth && kept != Number.Integer.One && logarithmicDerivativesKept.Count > 0 && raisable.Count <= 4) + { + Entity rest = bracket ?? Number.Integer.One; + foreach (var (@base, exponent) in raisable) + rest *= MathS.Pow(@base, exponent); + Entity sum = Number.Integer.Zero; + foreach (var derivative in logarithmicDerivativesKept) + sum += derivative; + sum = Functions.PartialFractions.Bare(sum.InnerSimplified); + if (AreProportionalAtSampledPoints(rest, sum, x)) + { + var ratio = Functions.PartialFractions.Bare(Functions.SingleQuotient.Combine(rest / sum).Simplify()); + if (ratio is Providedf(var inner, _)) + ratio = inner; + // With no symbol in it a number, read at a point where the simplification left + // `tanh(x)` beside `sinh(x)/cosh(x)`: a small fraction, and checked by the derivative. + if (ratio.ContainsNode(x) && !(rest / sum).Vars.Any(v => v != x) + && (rest / sum).Substitute(x, Number.Rational.Create(7, 5)).Evaled is Number.Complex { ImaginaryPart.IsZero: true } at + && SmallFractionNear(at.RealPart.EDecimal) is { } near) + ratio = near; + if (!ratio.ContainsNode(x) && !ratio.Nodes.Any(node => node == MathS.NaN)) + { + var answer = (constant * ratio * kept).InnerSimplified; + if (Functions.PartialFractions.DerivativeHoldsAtSampledPoints(answer, expr, x)) + return answer; + } + } + } // A function inside the bracket, raised from nothing: the bracket of `(K sin(u))'` is // `K'/K sin(u) + u' cos(u)`, so Rubi's // `F^(c (a + b x)) (f x)^m (e x cos(d + e x) + (m + b c x ln F) sin(d + e x))/x` is @@ -19325,9 +19473,20 @@ static void Join(List<(Number.Rational Power, Entity Cofactor)> groups, Number.R // `(a + b x^n + c x^(2 n))^p (a + b (1 + n + n p) x^n + c (1 + 2 n (1 + p)) x^(2 n))` // is `(x (a + b x^n + c x^(2 n))^(p + 1))'`. Whole powers only are a rational // function's, or a polynomial's beside a function, and the rules for those answer them. + // With no sum and no power raised, the product itself, where its logarithmic derivative is + // a constant: `(a F^(c + d x))^m (b F^(e + f x))^n` is `((a F^(c + d x))^m (b F^(e + f x))^n)'` + // over `(d m + f n) ln(F)`, and `F^(a + b x) (cosh(c + d x) + sinh(c + d x))^n` over + // `b ln(F) + d n`. Raising a power of either went nowhere, and the rules for one power of + // an exponential read one. + var itself = false; + var onlyItself = false; if (raisable.Any(pair => pair.Exponent is not Number.Integer)) { - if (raisable.Count >= 2) + // Beside an exponential, one power is the product itself only: raised, it was the + // substitution's question, answered as written. + if (bracket is null && (raisable.Count >= 2 || kept != Number.Integer.One)) + (itself, onlyItself) = (true, raisable.Count < 2); + if (raisable.Count >= 2 || kept != Number.Integer.One) bracket ??= Number.Integer.One; if (!raisable.Any(pair => pair.Base == x)) raisable.Insert(0, (x, Number.Integer.Zero)); @@ -19340,7 +19499,7 @@ static void Join(List<(Number.Rational Power, Entity Cofactor)> groups, Number.R var besideAFunction = new List<(Entity BracketOfTheDerivative, Entity Candidate)>(); // With a power of a variable base kept, the candidate may be the integrand's powers as // they are, nothing raised. - for (var subset = variablePowerKept ? 0 : 1; subset < 1 << raisable.Count; subset++) + for (var subset = variablePowerKept || itself ? 0 : 1; subset < (onlyItself ? 1 : 1 << raisable.Count); subset++) { var kernelsRaised = 0; var powersRaised = 0; @@ -19385,6 +19544,16 @@ static void Join(List<(Number.Rational Power, Entity Cofactor)> groups, Number.R candidate *= newExponent == Number.Integer.One ? @base : MathS.Pow(@base, newExponent); if (!raised) { + // In the product itself, a sum that is a constant multiple of its own derivative, + // `cosh(c + d x) + sinh(c + d x)`, has that constant for its logarithmic derivative, + // read monomial by monomial: as a quotient it was left as written, and nothing + // read a constant off it. + if (subset == 0 && @base is Sumf or Minusf && MonomialsOf(@base.Differentiate(x), x) is { } derived + && MonomialsOf(@base, x) is { } plain && TheRatioOfTheMonomials(derived, plain) is { } rate) + { + bracketOfTheDerivative += newExponent * rate * productOfRaisedBases; + continue; + } bracketOfTheDerivative += newExponent * @base.Differentiate(x) * productOfRaisedBases / @base; continue; } @@ -19604,6 +19773,178 @@ private static bool IsAFunctionToRaise(Entity factor, Entity.Variable x) return null; } + /// + /// The derivative of U/V^k for V a sum with a logarithm of x in it, standing to the + /// power k + 1 below the bar of a single quotient: Rubi's + /// (a d n x^m - a d m x^m ln(c x^n) - b d n (q - 1) ln(c x^n)^q)/(x (a x^m + b ln(c x^n)^q)^2) + /// is (d ln(c x^n)/(a x^m + b ln(c x^n)^q))'. U is a constant times one of the + /// monomials above the bar, tried in turn: the numerator W over the power must be + /// lambda (U' V - k U V'), decided at sampled points and then read monomial by monomial + /// with each power of x and of each function merged, so that ln(c x^n) ln(c x^n)^(q - 1) + /// is ln(c x^n)^q as the integrand writes it; or null. + /// + private static Entity? AsTheDerivativeOfAFunctionOverAPowerOfASum(Entity expr, Entity.Variable x) + { + if (expr is Sumf or Minusf) + return null; + var factors = FactorsOfTheIntegrand(expr); + foreach (var (factor, underneath) in factors) + { + // A sum with a logarithm of x in it: over a sum of exponentials or of trigonometric + // functions the rules for those answer as they did, the substitution among them. + if (!underneath || factor is not Powf(var sum, Number.Integer n) || sum is not (Sumf or Minusf) || !sum.ContainsNode(x) + || !sum.Nodes.Any(node => node is Logf(_, var argument) && argument.ContainsNode(x)) + || n.EInteger.CompareTo(EInteger.FromInt32(2)) < 0 || n.EInteger.CompareTo(EInteger.FromInt32(8)) > 0) + continue; + var k = n.EInteger.ToInt32Checked() - 1; + Entity above = Number.Integer.One, below = Number.Integer.One; + var skipped = false; + foreach (var (other, otherUnderneath) in factors) + { + if (!skipped && other == factor && otherUnderneath) + { + skipped = true; + continue; + } + if (otherUnderneath) below *= other; + else above *= other; + } + var w = above / below; + if (Monomials(w) is not { } read || read.Count == 0 || read.Count > 6) + continue; + // The candidates: each monomial above the bar with its constant dropped, and each of its + // functions of x on its own. Not 1: `V'/V^(k + 1)` is a power of a sum beside its + // derivative, which the rules for that answer as they always have. + var candidates = new List(); + foreach (var term in Sumf.LinearChildren(above).Concat(Sumf.LinearChildren(above.Expand()))) + { + Entity product = Number.Integer.One; + foreach (var part in Mulf.LinearChildren(term)) + if (part.ContainsNode(x)) + { + product = product == Number.Integer.One ? part : product * part; + if (!candidates.Contains(part)) + candidates.Add(part); + } + if (!candidates.Contains(product)) + candidates.Add(product); + } + var derivativeOfTheSum = sum.Differentiate(x); + foreach (var candidate in candidates.Take(12)) + { + if (candidate.ContainsNode(sum)) + continue; + var ofTheCandidate = candidate.Differentiate(x) * sum - k * candidate * derivativeOfTheSum; + if (!AreProportionalAtSampledPoints(w, ofTheCandidate, x) || Monomials(ofTheCandidate) is not { } theirs + || TheRatioOfTheMonomials(read, theirs) is not { } lambda) + continue; + var answer = lambda * candidate / (k == 1 ? sum : MathS.Pow(sum, k)); + if (Functions.PartialFractions.DerivativeHoldsAtSampledPoints(answer, expr, x)) + return answer; + } + } + return null; + + List<(Entity Coefficient, Entity Power, List<(Entity Base, Entity Power)> Atoms)>? Monomials(Entity e) => MonomialsOf(e, x); + } + + /// + /// Each term of the expansion of as a coefficient, a power of + /// and powers of functions of it, each base's powers merged and like terms + /// summed; null where a factor is a sum holding x. + /// + private static List<(Entity Coefficient, Entity Power, List<(Entity Base, Entity Power)> Atoms)>? MonomialsOf(Entity e, Entity.Variable x) + { + var terms = new List<(Entity Coefficient, Entity Power, List<(Entity Base, Entity Power)> Atoms)>(); + // The conditions a derivative carries, `provided not x = 0`, are the generic case's. + e = e.Replace(node => node is Providedf(var inner, _) ? inner : node); + foreach (var term in Sumf.LinearChildren(e.Expand())) + { + var (numerator, denominator) = Functions.SingleQuotient.Of(term); + Entity coefficient = Number.Integer.One, power = Number.Integer.Zero; + var atoms = new List<(Entity Base, Entity Power)>(); + foreach (var (side, sign) in new[] { (numerator, 1), (denominator, -1) }) + foreach (var part in Mulf.LinearChildren(side)) + { + if (!part.ContainsNode(x)) + { + coefficient = sign > 0 ? coefficient * part : coefficient / part; + continue; + } + var (@base, exponent) = part is Powf(var b, var p) && !p.ContainsNode(x) ? (b, p) : (part, (Entity)Number.Integer.One); + if (@base is Sumf or Minusf) + return null; + if (sign < 0) + exponent = -exponent; + if (@base == x) + { + power += exponent; + continue; + } + var at = atoms.FindIndex(atom => atom.Base == @base); + if (at >= 0) + atoms[at] = (@base, atoms[at].Power + exponent); + else + atoms.Add((@base, exponent)); + } + power = Functions.PartialFractions.Bare(power.Simplify()); + atoms = atoms.Select(atom => (atom.Base, Functions.PartialFractions.Bare(atom.Power.Simplify()))) + .Where(atom => atom.Item2.Evaled is not Number.Complex { IsZero: true }).ToList(); + var like = terms.FindIndex(t => TheSameValue(t.Power, power) && t.Atoms.Count == atoms.Count + && t.Atoms.All(mine => atoms.Any(other => other.Base == mine.Base && TheSameValue(other.Power, mine.Power)))); + if (like >= 0) + terms[like] = (terms[like].Coefficient + coefficient, terms[like].Power, terms[like].Atoms); + else + terms.Add((coefficient, power, atoms)); + } + return terms.Where(t => !VanishesIdentically(t.Coefficient)).ToList(); + } + + /// + /// The one constant every monomial of is of the same monomial of + /// , or null: the two are then proportional, read exactly. + /// + private static Entity? TheRatioOfTheMonomials(List<(Entity Coefficient, Entity Power, List<(Entity Base, Entity Power)> Atoms)> mine, + List<(Entity Coefficient, Entity Power, List<(Entity Base, Entity Power)> Atoms)> theirs) + { + if (mine.Count != theirs.Count) + return null; + Entity? ratio = null; + foreach (var term in mine) + { + var matching = theirs.Where(t => TheSameValue(t.Power, term.Power) && t.Atoms.Count == term.Atoms.Count + && t.Atoms.All(atom => term.Atoms.Any(other => other.Base == atom.Base && TheSameValue(other.Power, atom.Power)))).ToList(); + if (matching.Count != 1) + return null; + var here = Functions.PartialFractions.Bare((term.Coefficient / matching[0].Coefficient).Simplify()); + if (here.Nodes.Any(node => node == MathS.NaN)) + return null; + if (ratio is null) + ratio = here; + else if (!VanishesIdentically(ratio - here)) + return null; + } + return ratio is null ? null : LowestOverTheSymbols(ratio); + } + + private static bool TheSameValue(Entity left, Entity right) => left == right || VanishesIdentically(left - right); + + /// + /// The fraction with a denominator of at most 24 within 1e-20 of , + /// or null. + /// + private static Number.Rational? SmallFractionNear(EDecimal value) + { + for (var denominator = 1; denominator <= 24; denominator++) + { + var scaled = value.Multiply(EDecimal.FromInt32(denominator)); + var nearest = scaled.RoundToExponent(0, ERounding.HalfEven); + if (scaled.Subtract(nearest).Abs().CompareTo(EDecimal.FromString("1e-20")) < 0) + return Number.Rational.Create(nearest.ToEInteger(), EInteger.FromInt32(denominator)); + } + return null; + } + /// Set while asks its question. [System.ThreadStatic] private static bool derivativeBesideAFunction; @@ -19816,6 +20157,128 @@ private static bool IsAFunctionToRaise(Entity factor, Entity.Variable x) /// The most powers raises. private const int MostBinomialPowersReduced = 16; + /// + /// The derivative of a quotient U/V^k for V a logarithm or an inverse + /// trigonometric function: (U/V^k)' = U'/V^k - k U V'/V^(k + 1), so the terms of the + /// integrand with V^(k + 1) below the bar are -k U V' over it, and + /// U = -B/(k V') for their sum B. Rubi's + /// x^3/((1 + a^2 x^2) atan(a x)^3) - 3 x^2/(2 a atan(a x)^2) is + /// (-x^3/(2 a atan(a x)^2))', and + /// 1/((c + d x)(c - a + (d - b) x) ln(Q)) + ln(1 - Q)/((a + b x)(c + d x) ln(Q)^2), for + /// Q = (a + b x)/(c + d x), is (-ln(1 - Q)/((b c - a d) ln(Q)))'. + /// + /// + /// Neither term is elementary on its own, so every split loses it, and by parts on either + /// leaves the other's question again; both were searches past the budget. The rational part + /// of B/(k V') is brought to lowest terms, where it is a constant or a polynomial for + /// these, and the answer is U/V^k only where its derivative is the whole integrand at + /// sampled points, the terms with lower powers of V included: where it is not, the + /// integrand is no such derivative, and this declines. + /// https://github.com/asc-community/AngouriMath/issues/233 + /// + internal static Entity? SolveAsTheDerivativeOfAFunctionOverAPowerOfALogarithmOrAnInverse(Entity expr, Entity.Variable x) + { + if (!Integration.AnsweringTheQuestionAskedOrOneBelow || expr is not (Sumf or Minusf or Mulf or Divf)) + return null; + static bool AnAtom(Entity node, Entity.Variable x) + => node.ContainsNode(x) && node is Logf or Arctanf or Arcsinf or Arccosf or Arccotanf; + var terms = Sumf.LinearChildren(expr).Select(FactorsOfTheIntegrand).ToList(); + // The atom and the highest power of it below the bar in any one term. + var candidates = new List<(Entity Atom, int Power)>(); + foreach (var factors in terms) + foreach (var (factor, underneath) in factors) + if (underneath && factor is Powf(var atom, Number.Integer n) && AnAtom(atom, x) + && n.EInteger.CompareTo(EInteger.FromInt32(2)) >= 0 && n.EInteger.CompareTo(EInteger.FromInt32(12)) <= 0) + { + var power = n.EInteger.ToInt32Checked(); + var at = candidates.FindIndex(pair => pair.Atom == atom); + if (at < 0) + candidates.Add((atom, power)); + else if (candidates[at].Power < power) + candidates[at] = (atom, power); + } + foreach (var (atom, highest) in candidates) + { + var derivative = atom.Differentiate(x); + if (derivative.ContainsNode(atom)) + continue; + // B and its factors that are no rational function of x, kept as they are. + Entity rational = Number.Integer.Zero; + Entity? transcendental = null; + var read = true; + foreach (var factors in terms) + { + var power = 0; + Entity above = Number.Integer.One, below = Number.Integer.One, others = Number.Integer.One; + foreach (var (factor, underneath) in factors) + { + var (@base, times) = factor is Powf(var b, Number.Integer n) && n.EInteger.CanFitInInt32() ? (b, n.EInteger.ToInt32Checked()) : (factor, 1); + if (@base == atom) + power += underneath ? times : -times; + else if (!factor.ContainsNode(x) || IsARationalFunction(factor, x)) + { + if (underneath) below *= factor; + else above *= factor; + } + else if (underneath || factor.ContainsNode(atom)) + read = false; + else + others *= factor; + } + if (!read) + break; + if (power != highest) + continue; + // One transcendental factor among the terms of B, the same in each: with two + // different ones the quotient is no product of one and a rational function. + if (others != Number.Integer.One) + { + if (transcendental is not null && transcendental != others) + { + read = false; + break; + } + transcendental = others; + } + rational += above / below; + } + if (!read) + continue; + if (rational == Number.Integer.Zero) + continue; + var k = highest - 1; + var quotient = InLowestTerms((-rational / (k * derivative)).InnerSimplified); + // A constant U is a power of the function beside its derivative, which the rules for + // that answer as they did: `1/(x ln(c x)^2)` is `-1/ln(c x)` there. + if (transcendental is null && !quotient.ContainsNode(x)) + continue; + var answer = (transcendental is null ? quotient : quotient * transcendental) / MathS.Pow(atom, k); + if (answer.Nodes.Any(node => node == MathS.NaN)) + continue; + if (Functions.PartialFractions.DerivativeHoldsAtSampledPoints(answer, expr, x)) + return answer; + } + return AsTheDerivativeOfAFunctionOverAPowerOfASum(expr, x); + + // The rational part in lowest terms: a constant -- `-1/(b c - a d)` for the logarithm of a + // quotient of linears, whose derivative over one bar is a quotient of quadratics -- as its + // value at a point, simplified, and otherwise cancelled over one bar. + Entity InLowestTerms(Entity r) + { + if (AreProportionalAtSampledPoints(r, Number.Integer.One, x)) + foreach (var at in new[] { 0, 1, 2 }) + { + var value = Functions.PartialFractions.Bare(r.Substitute(x, at).Simplify()); + if (!value.ContainsNode(x) && !value.Nodes.Any(node => node == MathS.NaN) && value.Evaled is not Number.Complex { IsZero: true }) + return LowestOverTheSymbols(value); + } + var combined = Functions.SingleQuotient.Combine(r).InnerSimplified; + var (numerator, denominator) = Functions.SingleQuotient.Of(combined); + return Functions.PolynomialGcd.TryCancel(numerator.Expand().InnerSimplified, denominator.Expand().InnerSimplified, out var cancelled) && cancelled is not null + ? Functions.PartialFractions.Bare(cancelled) : combined; + } + } + /// /// A binomial differential x^m (a + b x^n)^p with a symbol in its exponents, in /// Chebyshev's third case at a whole (m + 1)/n + p + 1 = -k of at most 0: Rubi's @@ -20387,7 +20850,24 @@ private static bool AreProportionalAtSampledPoints(Entity left, Entity right, En static bool IsPlain(Entity tower, Entity.Variable x) => tower is Powf(_, var g) && TreeAnalyzer.TryGetPolyLinear(g, x, out _, out _) || tower is Logf(_, var argument) && argument == x; - if (towers.All(tower => IsPlain(tower, x))) + // Unless a sum below the bar holds x beside the logarithm, to two or more powers: + // `(1 - ln(x))/(x (x + ln(x)))` is `(ln(x + ln(x)) - ln(x))'`, a logarithm of that sum, which + // the tower's ansatz, a polynomial in `ln(x)`, does not write. Where one power of x is a + // factor of every term, `x + 4 x ln(x)^2`, the sum is that power times one in the logarithm, + // which the rules for a function of `ln(x)` answer as written. + static bool MixesTheVariableWithItsLogarithm(Entity expr, Entity.Variable x) + { + var logarithm = MathS.Ln(x); + var placeholder = Variable.CreateUnique(expr, "t_mixed"); + foreach (var term in Sumf.LinearChildren(expr)) + foreach (var factor in Mulf.LinearChildren(Functions.SingleQuotient.Of(term).Denominator)) + if ((factor is Powf(var b, Number.Integer) ? b : factor) is var @base && @base is Sumf or Minusf && @base.ContainsNode(logarithm) + && TreeAnalyzer.TryGetPolynomial(@base.Replace(node => node == logarithm ? placeholder : node), x, out var inX) + && inX.Count > 1) + return true; + return false; + } + if (towers.All(tower => IsPlain(tower, x)) && !MixesTheVariableWithItsLogarithm(expr, x)) return null; var monomials = new List<(Entity.Variable Indeterminate, Entity Written, Entity Derivative)>(); var map = new Dictionary(); @@ -20475,6 +20955,40 @@ Entity Differentiated(Entity generator) return result; } var exponentialIndeterminates = monomials.Where(m => m.Written is Powf).Select(m => m.Indeterminate).ToList(); + Entity.Variable? logarithmOfX = monomials.Where(m => m.Written == MathS.Ln(x)).Select(m => (Entity.Variable?)m.Indeterminate).FirstOrDefault(); + // Each exponential of a quadratic in x alone, with the error function its integral is. + var gaussians = new List<(Entity Generator, Entity Kappa, Entity.Variable Indeterminate)>(); + foreach (var (indeterminate, written, _) in monomials) + if (written is Powf(_, var exponent) && !monomials.Any(m => exponent.ContainsNode(m.Indeterminate)) + && TreeAnalyzer.TryGetPolyQuadratic(exponent, x, out var leading, out var middle, out var last) + && !leading.ContainsNode(x) && !middle.ContainsNode(x) && !last.ContainsNode(x) && !VanishesIdentically(leading)) + { + var rootOfTheLeading = MathS.Sqrt(leading); + var shift = Functions.PartialFractions.Bare((middle / (2 * leading)).Simplify()); + var offset = Functions.PartialFractions.Bare((MathS.Sqr(middle) / (4 * leading) - last).Simplify()); + var kappa = 2 * rootOfTheLeading / MathS.Sqrt(MathS.pi) * (VanishesIdentically(offset) ? Number.Integer.One : MathS.Pow(MathS.e, offset)); + gaussians.Add((MathS.Erfi(rootOfTheLeading * (x + shift)), kappa, indeterminate)); + } + // The denominator with one factor `t` taken out, where it has one as a factor. + static Entity? TheDenominatorWithoutOne(Entity below, Entity.Variable t) + { + var factors = Mulf.LinearChildren(below).ToList(); + for (var i = 0; i < factors.Count; i++) + { + Entity? rest = factors[i] == t ? Number.Integer.One + : factors[i] is Powf(var b, Number.Integer n) && b == t && n.EInteger.CompareTo(EInteger.One) > 0 + ? (n.EInteger.Equals(EInteger.FromInt32(2)) ? t : MathS.Pow(t, Number.Integer.Create(n.EInteger.Subtract(EInteger.One)))) + : null; + if (rest is null) + continue; + Entity product = rest; + for (var j = 0; j < factors.Count; j++) + if (j != i) + product = product * factors[j]; + return product; + } + return null; + } // Q: the factors with their powers lowered by one, and then as they are; each // exponential indeterminate to every power from zero up to the numerator's degree // in it plus one, since a power of an exponential above the bar is one below in @@ -20499,6 +21013,18 @@ Entity Differentiated(Entity generator) return System.Math.Min(MaximumTowerDegree, aboveDegree + 2); }).ToList(); var logarithmGenerators = factors.Select(f => f.Factor).Distinct().ToList(); + // The special functions in a second pass only, where the first found nothing: beside them + // every system has a column more, and Hearn's `e^(x^2)` integrands, answered without one, + // took five to ten times as long. The Gaussian's only beside a factor of x alone below the + // bar, as in `e^((a + x)^2)/x^2`: a polynomial times exponentials of a quadratic is the + // rules' for those, which answer it after this, and the pass declined `x^2 sinh(a + b x + c x^2)` + // and `x/(a + b csch(c + d x^2))` first at three times their allocation. And one Gaussian: a + // hyperbolic function of a quadratic is two, `sinh(a + b x^2)^2/x^2` among them, which the pass + // declined at three times the allocation of the rules after it. + var aGaussianBesideAPowerOfX = gaussians.Count == 1 + && Mulf.LinearChildren(below).Any(factor => factor.ContainsNode(x) && !monomials.Any(m => factor.ContainsNode(m.Indeterminate))); + var specialOnes = aGaussianBesideAPowerOfX || logarithmOfX is { } lnX && TheDenominatorWithoutOne(below, lnX) is not null; + foreach (var withTheSpecialFunctions in specialOnes ? new[] { false, true } : new[] { false }) foreach (var q in qCandidates) foreach (var exponentPowers in Combinations(exponentialBounds)) { @@ -20568,6 +21094,33 @@ Entity Differentiated(Entity generator) } columns.Add(column); } + // And the logarithmic integral, `li(x)' = 1/ln(x)`, where `ln(x)` is a factor of the + // integrand's denominator: `(1 + x)/(ln(x) (x + ln(x)))` is + // `(li(x) + ln(ln(x)) - ln(x + ln(x)))'`, and without it no rational part and no + // logarithms answer it. + if (withTheSpecialFunctions && !failed && logarithmOfX is { } t && TheDenominatorWithoutOne(below, t) is { } belowWithoutIt) + { + var numerator = MathS.Sqr(denominator) * belowWithoutIt * logArguments * towersBelow; + if (TryReadInAll(Functions.PartialFractions.Bare(numerator.Expand().InnerSimplified), variables, out var column)) + { + generators.Add(MathS.Li(x)); + columns.Add(column); + } + } + // And the Gaussian's, for an exponential `t = e^Q` of a quadratic: `erfi(sqrt(A) (x + B/(2 A)))'` + // is `kappa t`. `e^((a + x)^2)/x^2 - 2 a e^((a + x)^2)/x` is + // `(-e^((a + x)^2)/x + sqrt(pi) erfi(a + x))'`: by parts the first term leaves the second's + // negative and `2 e^((a + x)^2)`, and neither term is elementary on its own. + if (withTheSpecialFunctions && !failed && aGaussianBesideAPowerOfX) + foreach (var (generator, kappa, indeterminate) in gaussians) + { + var numerator = kappa * indeterminate * MathS.Sqr(denominator) * below * logArguments * towersBelow; + if (TryReadInAll(Functions.PartialFractions.Bare(numerator.Expand().InnerSimplified), variables, out var column)) + { + generators.Add(generator); + columns.Add(column); + } + } if (failed) continue; var targetNumerator = above * MathS.Sqr(denominator) * logArguments * towersBelow; @@ -20583,7 +21136,12 @@ Entity Differentiated(Entity generator) matrix[row][c] = columns[c].TryGetValue(keys[row], out var entry) ? entry : Number.Integer.Zero; rhs[row] = target.TryGetValue(keys[row], out var wanted) ? wanted : Number.Integer.Zero; } - if (!Functions.PartialFractions.TrySolveLinear(matrix, rhs, out var values) || values is null) + // With a special function among the generators, whose constant carries the symbols + // into the columns, an elimination that leaves `2 a - 2 a` as written declines; then + // with the symbols pinned first, and the columns the pinned solutions need. + var special = generators.Count > monomialGenerators.Count + logarithmGenerators.Count; + if ((!Functions.PartialFractions.TrySolveLinear(matrix, rhs, out var values) || values is null) + && (!special || !Functions.PartialFractions.TrySolveLinearWithSymbols(matrix, rhs, out values) || values is null)) continue; Entity answer = Number.Integer.Zero; for (var c = 0; c < values.Length; c++) @@ -20591,6 +21149,10 @@ Entity Differentiated(Entity generator) var value = values[c].InnerSimplified; if (value.Evaled is Number.Complex { IsZero: true }) continue; + // A special function's coefficient is a quotient of its constant, `1/(2/sqrt(pi))` + // as the elimination leaves it: small, and simplified. + if (c >= monomialGenerators.Count + logarithmGenerators.Count && value.Complexity < 80) + value = Functions.PartialFractions.Bare(value.Simplify()); answer += value * generators[c]; } if (answer == Number.Integer.Zero) @@ -31693,6 +32255,38 @@ private static Entity WithLogarithmArgumentsOverOneBar(Entity expr, Entity.Varia /// against the integrand at sampled points before it is given. /// internal static Entity? SolveByTheQuotientOfTheLogarithmsLinears(Entity expr, Entity.Variable x, bool integrateByParts) + => SolveByTheQuotientOfTheLogarithmsLinears(expr, x, integrateByParts, ontoTheExponentialIntegral: false); + + /// + /// where + /// the logarithm's factor is a negative whole power and the integrand in t is a power + /// of t and a polynomial beside it, t^q (b - d t)^j (A + B ln(K t^n))^(-k): the + /// powers of the linears sum to -2 - j, and + /// answers it onto the exponential integral, a power of t at a time. Rubi's + /// 1/((a g + b g x)^2 (A + B ln(e (a + b x)/(c + d x)))^2) is + /// t^(-2) (A + B ln(e t))^(-2)/(g^2 D). + /// + /// + /// Before the polynomial term, which takes b^2 g^2 out of (a g + b g x)^2 and asks + /// 1/((a/b + x)^2 (A + B ln(...))^2) again, and a level below that the substitution + /// search ran past the budget: the rule above, far down the chain, was not reached. Of Rubi's + /// 3.2.1 those rows were from 1.3 s to 23 s, and four past the budget. + /// https://github.com/asc-community/AngouriMath/issues/233 + /// + internal static Entity? SolveByTheQuotientOfTheLogarithmsLinearsOntoTheExponentialIntegral(Entity expr, Entity.Variable x) + { + // Read first, as cheaply as it can be: a factor with a logarithm in it below the bar, the + // logarithm of a quotient. Early in the chain, the rule's reading of the linears simplified + // a derivative for every logarithm it was shown. + static bool OfAQuotient(Entity node) => node is Logf(_, var argument) + && argument.Nodes.Any(inner => inner is Divf || inner is Powf(_, Number.Integer { IsNegative: true })); + if (!FactorsOfTheIntegrand(expr).Any(pair => pair.Factor.Nodes.Any(OfAQuotient) + && (pair.Underneath ? pair.Factor is not Powf(_, Number.Integer { IsNegative: true }) : pair.Factor is Powf(_, Number.Integer { IsNegative: true })))) + return null; + return SolveByTheQuotientOfTheLogarithmsLinears(expr, x, integrateByParts: false, ontoTheExponentialIntegral: true); + } + + private static Entity? SolveByTheQuotientOfTheLogarithmsLinears(Entity expr, Entity.Variable x, bool integrateByParts, bool ontoTheExponentialIntegral) { if (!Integration.AnsweringTheQuestionAskedOrOneBelow) return null; @@ -31785,6 +32379,7 @@ private static Entity WithLogarithmArgumentsOverOneBar(Entity expr, Entity.Varia Entity powerOfT = Number.Integer.Zero; Entity powerOfDenominator = Number.Integer.Create(-2); // dx = D dt/(b - d t)^2 Entity? logarithmsFactor = null; + Entity logarithmsPower = Number.Integer.One; // A polynomial beside the logarithm is by parts in x as it stands, one closed // step; in t it is a power of b - d t below the bar, which is the slower way. var aPolynomialBesideTheLogarithm = true; @@ -31808,6 +32403,7 @@ private static Entity WithLogarithmArgumentsOverOneBar(Entity expr, Entity.Varia if (inT.ContainsNode(x)) return null; logarithmsFactor = MathS.Pow(inT, power); + logarithmsPower = power; continue; } if (!TryReadLinear(@base, out var slope, out var offset)) @@ -31836,6 +32432,12 @@ private static Entity WithLogarithmArgumentsOverOneBar(Entity expr, Entity.Varia if (readFromTheReciprocal && powerOfDenominator.Evaled is Number.Complex { IsZero: true } && powerOfT.Evaled is not Number.Complex { IsZero: true }) return null; + // In t a power of t beside a polynomial and the logarithm's power: the linears' powers sum + // to -2 or less, and b - d t stands above the bar to a whole power. + if (ontoTheExponentialIntegral && (logarithmsPower.Evaled is not Number.Integer { IsNegative: true } + || Functions.PartialFractions.Bare(powerOfDenominator.Simplify()).Evaled is not Number.Integer { IsNegative: false } whole + || whole.EInteger.CompareTo(EInteger.FromInt32(12)) > 0)) + return null; Entity above = constantInFront * logarithmsFactor, below = Number.Integer.One; foreach (var (@base, power) in new[] { (t, Functions.PartialFractions.Bare(powerOfT.Simplify())), (denominatorInT, Functions.PartialFractions.Bare(powerOfDenominator.Simplify())) }) { @@ -31849,7 +32451,8 @@ private static Entity WithLogarithmArgumentsOverOneBar(Entity expr, Entity.Varia var rewritten = (below == Number.Integer.One ? above : above / below).InnerSimplified; if (rewritten.ContainsNode(x)) return null; - if (Integration.ComputeAsAQuestionOfItsOwn(rewritten, t, integrateByParts) is not { } integrated) + if ((ontoTheExponentialIntegral ? SolveAPowerOverAPowerOfALogarithm(rewritten, t) + : Integration.ComputeAsAQuestionOfItsOwn(rewritten, t, integrateByParts)) is not { } integrated) return null; var answer = integrated.Substitute(t, l1.Linear / l2.Linear); if (answer.Nodes.Any(node => node == MathS.NaN)) @@ -31951,7 +32554,7 @@ static bool AreEqualAsPolynomials(Entity left, Entity right) /// 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 + /// 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 diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 2e33e1ec9..6f03cb88e 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -766,6 +766,11 @@ internal static bool ASumOverRootsWouldAnswer // since it fires only where that factor holds x, and the rules before it spent 28 seconds on // `atan(e x/sqrt(-a e^2/b - e^2 x^2))/sqrt(a + b x^2)` declining. if ((answer = IndefiniteIntegralSolver.SolveAFunctionOfAnInverseTimesItsDerivativeUpToAConstant(expr, x, integrateByParts)) is { }) return answer; + // The derivative of a function over a power of a logarithm, an inverse trigonometric + // function, or a sum with a logarithm in it, whose terms are not elementary apart: early, + // since it reads only a power of one below the bar, and by parts on either term asked the + // other's question again. + if ((answer = IndefiniteIntegralSolver.SolveAsTheDerivativeOfAFunctionOverAPowerOfALogarithmOrAnInverse(expr, x)) is { }) return answer; // A whole power of a linear in the inverse sine, cosine or hyperbolic cosine of // `c + d x^2` with `c^2 = 1`, by its recurrence, which closes: by parts it went round. if ((answer = IndefiniteIntegralSolver.SolveAPowerOfAnInverseFunctionOfAQuadraticByTheRecurrence(expr, x)) is { }) return answer; @@ -849,6 +854,13 @@ internal static bool ASumOverRootsWouldAnswer // the sum and writes what is left out in powers of x, where the linear is gone; and // before the substitution search, which reads one function at a time. if ((answer = IndefiniteIntegralSolver.SolveByWritingAFunctionOfOneShiftedLinear(expr, x, integrateByParts)) is { }) return answer; + // And a transcendental function of one shifted linear alone, for the same reason: the + // polynomial term writes `(c/d + x)^19` beside `F^(a + b/(c + d x)^3)`, two spellings. + if ((answer = IndefiniteIntegralSolver.SolveByWritingATranscendentalFunctionOfOneShiftedLinear(expr, x, integrateByParts)) is { }) return answer; + // Powers of two linears beside a negative power of a logarithm of their quotient, which in + // the quotient are a power of it beside the logarithm alone, onto the exponential integral: + // the polynomial term takes the slope out of the linear and asks it again, past the budget. + if ((answer = IndefiniteIntegralSolver.SolveByTheQuotientOfTheLogarithmsLinearsOntoTheExponentialIntegral(expr, x)) is { }) return answer; // A polynomial beside error functions of one shifted linear, written in it: by parts in // x leaves a Gaussian of the shifted linear beside a polynomial, which nothing reads. if ((answer = IndefiniteIntegralSolver.SolveAPolynomialBesideErrorFunctionsOfAShiftedLinear(expr, x, integrateByParts)) is { }) return answer; diff --git a/Sources/Tests/UnitTests/Calculus/AFunctionOverAPowerOfALogarithmOrAnInverseIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/AFunctionOverAPowerOfALogarithmOrAnInverseIntegralTest.cs new file mode 100644 index 000000000..46cb5ab02 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/AFunctionOverAPowerOfALogarithmOrAnInverseIntegralTest.cs @@ -0,0 +1,67 @@ +// +// 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 +{ + /// + /// The derivative of U/V^k for V a logarithm or an inverse trigonometric function, whose + /// terms are not elementary apart: U = -B/(k V') for the terms B over V^(k + 1); and + /// for V a sum with a logarithm in it, U a monomial of the numerator, read monomial by + /// monomial. Rubi's 3.2.3, 3.5 and 5.3.4 rows ran past the budget. + /// #233 + /// + [Trait("Area", "Calculus")] + public sealed class AFunctionOverAPowerOfALogarithmOrAnInverseIntegralTest + { + private static readonly (string Symbol, double Value)[] Pins = + { + ("a", 1.3), ("b", 0.7), ("c", 0.4), ("d", 0.3), ("f", 0.9), ("g", 1.1), ("k", 0.6), ("h", 0.5), + ("m", 1.7), ("n", 2.3), ("p", 1.6), ("q", 2.2), ("A", 0.8), ("B", 1.2), ("F", 2.4), + }; + + [Theory] + [InlineData("1/((c + d*x)*(-a + c + (-b + d)*x)*ln((a + b*x)/(c + d*x))) + ln(1 + (-a - b*x)/(c + d*x))/((a + b*x)*(c + d*x)*ln((a + b*x)/(c + d*x))^2)")] + [InlineData("(-1)/((a + b*x)*(a - c + (b - d)*x)*ln((a + b*x)/(c + d*x))) + ln(1 + (-c - d*x)/(a + b*x))/((a + b*x)*(c + d*x)*ln((a + b*x)/(c + d*x))^2)")] + [InlineData("x^3/((1 + a^2*x^2)*atan(a*x)^3) - 3/2*x^2/(a*atan(a*x)^2)")] + [InlineData("(a*d*n*x^m - a*d*m*x^m*ln(c*x^n) - b*d*n*(q - 1)*ln(c*x^n)^q)/(x*(a*x^m + b*ln(c*x^n)^q)^2)")] + public void AsTheDerivativeOfAQuotient(string integrand) => AssertAnswered(integrand); + + private static void AssertAnswered(string integrand, string variable = "x") + { + var integral = integrand.ToEntity().Integrate(variable); + var text = integral.Stringize(); + Assert.DoesNotContain("integral(", text); + Assert.True(text.Length < 5000, $"{text.Length} characters of answer for {integrand}"); + Entity Pinned(Entity e) + { + foreach (var (symbol, value) in Pins) + if (symbol != variable) + e = e.Substitute(symbol, value); + return e; + } + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate(variable); + var original = Pinned(integrand.ToEntity()); + var compared = 0; + foreach (var at in new[] { 0.3, 0.8, 1.3, 1.9, 2.9, 3.7 }) + { + var want = original.Substitute(variable, at).EvalNumerical(); + var got = derivative.Substitute(variable, 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/d{variable} of the antiderivative of {integrand} is {got} at {variable} = {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/ARischNormanAnsatzBesideTheLogarithmOfXIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/ARischNormanAnsatzBesideTheLogarithmOfXIntegralTest.cs new file mode 100644 index 000000000..c9d76d22d --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/ARischNormanAnsatzBesideTheLogarithmOfXIntegralTest.cs @@ -0,0 +1,68 @@ +// +// 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 +{ + /// + /// The Risch-Norman ansatz on ln(x) alone where a sum below the bar holds x beside it to two + /// or more powers, with the logarithmic integral among its generators: (1 - ln(x))/(x (x + ln(x))) + /// is (ln(x + ln(x)) - ln(x))', and (1 + x)/(ln(x) (x + ln(x))) is + /// (li(x) + ln(ln(x)) - ln(x + ln(x)))'. Rubi's 3.5. And the error function among them for an + /// exponential of a quadratic: e^((a + x)^2)/x^2 - 2 a e^((a + x)^2)/x is + /// (-e^((a + x)^2)/x + sqrt(pi) erfi(a + x))', Rubi's 2.3, whose terms are not elementary apart. + /// #233 + /// + [Trait("Area", "Calculus")] + public sealed class ARischNormanAnsatzBesideTheLogarithmOfXIntegralTest + { + private static readonly (string Symbol, double Value)[] Pins = + { + ("a", 1.3), ("b", 0.7), ("c", 0.4), ("d", 0.3), ("f", 0.9), ("g", 1.1), ("k", 0.6), ("h", 0.5), + ("m", 1.7), ("n", 2.3), ("p", 1.6), ("q", 2.2), ("A", 0.8), ("B", 1.2), ("F", 2.4), + }; + + [Theory] + [InlineData("(1 - ln(x))/(x*(x + ln(x)))")] + [InlineData("(1 + x)/(ln(x)*(x + ln(x)))")] + [InlineData("e^((a + x)^2)/x^2 - 2*e^((a + x)^2)*a/x")] + public void BesideTheLogarithmOfX(string integrand) => AssertAnswered(integrand); + + private static void AssertAnswered(string integrand, string variable = "x") + { + var integral = integrand.ToEntity().Integrate(variable); + var text = integral.Stringize(); + Assert.DoesNotContain("integral(", text); + Assert.True(text.Length < 5000, $"{text.Length} characters of answer for {integrand}"); + Entity Pinned(Entity e) + { + foreach (var (symbol, value) in Pins) + if (symbol != variable) + e = e.Substitute(symbol, value); + return e; + } + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate(variable); + var original = Pinned(integrand.ToEntity()); + var compared = 0; + foreach (var at in new[] { 0.3, 0.8, 1.3, 1.9, 2.9, 3.7 }) + { + var want = original.Substitute(variable, at).EvalNumerical(); + var got = derivative.Substitute(variable, 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/d{variable} of the antiderivative of {integrand} is {got} at {variable} = {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/ATranscendentalFunctionOfOneShiftedLinearIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/ATranscendentalFunctionOfOneShiftedLinearIntegralTest.cs new file mode 100644 index 000000000..e507d9e32 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/ATranscendentalFunctionOfOneShiftedLinearIntegralTest.cs @@ -0,0 +1,67 @@ +// +// 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 transcendental function of one shifted linear alone, written in u = c + d x, where a + /// power of the linear stands below the bar inside a sum or the linear is written two ways. Rubi's + /// F^(a + b/(c + d x)^3)/(c + d x)^19 ran past the budget in x, and is 20 ms in u. + /// #233 + /// + [Trait("Area", "Calculus")] + public sealed class ATranscendentalFunctionOfOneShiftedLinearIntegralTest + { + private static readonly (string Symbol, double Value)[] Pins = + { + ("a", 1.3), ("b", 0.7), ("c", 0.4), ("d", 0.3), ("f", 0.9), ("g", 1.1), ("k", 0.6), ("h", 0.5), + ("m", 1.7), ("n", 2.3), ("p", 1.6), ("q", 2.2), ("A", 0.8), ("B", 1.2), ("F", 2.4), + }; + + [Theory] + [InlineData("F^(a + b/(c + d*x)^3)/(c + d*x)^19")] + [InlineData("F^(a + b/(c + d*x)^3)/(c + d*x)^16")] + [InlineData("F^(a + b/(c + d*x)^2)/(c + d*x)^10")] + [InlineData("ln(c*(d + k/(f + g*x)^3)^q)")] + [InlineData("(k*g + f*g*x)^(p - 1)/ln(d*(k + f*x)^p)")] + public void WrittenInTheShiftedLinear(string integrand) => AssertAnswered(integrand); + + private static void AssertAnswered(string integrand, string variable = "x") + { + var integral = integrand.ToEntity().Integrate(variable); + var text = integral.Stringize(); + Assert.DoesNotContain("integral(", text); + Assert.True(text.Length < 5000, $"{text.Length} characters of answer for {integrand}"); + Entity Pinned(Entity e) + { + foreach (var (symbol, value) in Pins) + if (symbol != variable) + e = e.Substitute(symbol, value); + return e; + } + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate(variable); + var original = Pinned(integrand.ToEntity()); + var compared = 0; + foreach (var at in new[] { 0.3, 0.8, 1.3, 1.9, 2.9, 3.7 }) + { + var want = original.Substitute(variable, at).EvalNumerical(); + var got = derivative.Substitute(variable, 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/d{variable} of the antiderivative of {integrand} is {got} at {variable} = {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/TheExponentialsAloneAsTheirOwnDerivativeIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/TheExponentialsAloneAsTheirOwnDerivativeIntegralTest.cs new file mode 100644 index 000000000..60b31e5e3 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/TheExponentialsAloneAsTheirOwnDerivativeIntegralTest.cs @@ -0,0 +1,68 @@ +// +// 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 product whose logarithmic derivative is a constant, or whose exponentials' logarithmic derivative + /// is the rest of it up to a constant -- with f^g = e^(g ln f) for x in the base and the exponent: + /// (a F^(c + d x))^m (b F^(k + f x))^n, x^(-2 - 1/x) (1 - ln(x)), cosh(x)^x (ln(cosh(x)) + x tanh(x)). + /// #233 + /// + [Trait("Area", "Calculus")] + public sealed class TheExponentialsAloneAsTheirOwnDerivativeIntegralTest + { + private static readonly (string Symbol, double Value)[] Pins = + { + ("a", 1.3), ("b", 0.7), ("c", 0.4), ("d", 0.3), ("f", 0.9), ("g", 1.1), ("k", 0.6), ("h", 0.5), + ("m", 1.7), ("n", 2.3), ("p", 1.6), ("q", 2.2), ("A", 0.8), ("B", 1.2), ("F", 2.4), + }; + + [Theory] + [InlineData("(a*F^(c + d*x))^m*(b*F^(k + f*x))^n")] + [InlineData("F^(a + b*x)*(cosh(c + d*x) + sinh(c + d*x))^n")] + [InlineData("F^(a + b*x)*(cosh(c + d*x) - sinh(c + d*x))^n")] + [InlineData("x^(-2 - 1/x)*(1 - ln(x))")] + [InlineData("cosh(x)^x*(ln(cosh(x)) + x*tanh(x))")] + [InlineData("x^x*(1 + ln(x))")] + public void TheirOwnDerivative(string integrand) => AssertAnswered(integrand); + + private static void AssertAnswered(string integrand, string variable = "x") + { + var integral = integrand.ToEntity().Integrate(variable); + var text = integral.Stringize(); + Assert.DoesNotContain("integral(", text); + Assert.True(text.Length < 5000, $"{text.Length} characters of answer for {integrand}"); + Entity Pinned(Entity e) + { + foreach (var (symbol, value) in Pins) + if (symbol != variable) + e = e.Substitute(symbol, value); + return e; + } + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate(variable); + var original = Pinned(integrand.ToEntity()); + var compared = 0; + foreach (var at in new[] { 0.3, 0.8, 1.3, 1.9, 2.9, 3.7 }) + { + var want = original.Substitute(variable, at).EvalNumerical(); + var got = derivative.Substitute(variable, 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/d{variable} of the antiderivative of {integrand} is {got} at {variable} = {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/TheQuotientOfTheLogarithmsLinearsOntoTheExponentialIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/TheQuotientOfTheLogarithmsLinearsOntoTheExponentialIntegralTest.cs new file mode 100644 index 000000000..279d2feb7 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/TheQuotientOfTheLogarithmsLinearsOntoTheExponentialIntegralTest.cs @@ -0,0 +1,75 @@ +// +// 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 +{ + /// + /// Powers of two linears beside a negative power of a logarithm of their quotient, which under + /// t = (a + b x)/(c + d x) are a power of t and a polynomial beside the logarithm alone, + /// onto the exponential integral: Rubi's 3.2.1 rows with the powers of the linears summing to + /// -2 or -3, which ran past the budget. And the square below the bar the substitution + /// u = ln(k t) writes, whose discriminant is zero: its two roots were one, and the answer had + /// no value at any point. + /// #233 + /// + [Trait("Area", "Calculus")] + public sealed class TheQuotientOfTheLogarithmsLinearsOntoTheExponentialIntegralTest + { + private static readonly (string Symbol, double Value)[] Pins = + { + ("a", 1.3), ("b", 0.7), ("c", 0.4), ("d", 0.3), ("f", 0.9), ("g", 1.1), ("k", 0.6), ("h", 0.5), + ("m", 1.7), ("n", 2.3), ("p", 1.6), ("q", 2.2), ("A", 0.8), ("B", 1.2), ("F", 2.4), + }; + + [Theory] + [InlineData("1/((a*g + b*g*x)^2*(A + B*ln(k*((a + b*x)/(c + d*x))^n))^2)")] + [InlineData("1/((a*g + b*g*x)^2*(A + B*ln(k*(a + b*x)/(c + d*x)))^2)")] + [InlineData("1/((a*g + b*g*x)^2*(A + B*ln(k*(a + b*x)^2/(c + d*x)^2))^2)")] + [InlineData("1/((a*g + b*g*x)^2*(A + B*ln(k*(c + d*x)^2/(a + b*x)^2)))")] + [InlineData("1/((a*g + b*g*x)^3*(A + B*ln(k*(a + b*x)/(c + d*x)))^2)")] + public void OntoTheExponentialIntegral(string integrand) => AssertAnswered(integrand); + + [Theory] + [InlineData("1/(x^2*(A + B*ln(k*x))^2)")] + [InlineData("1/(x^2*(A + B*ln(k*x^2))^2)")] + public void OverASquareWithAZeroDiscriminant(string integrand) => AssertAnswered(integrand); + + private static void AssertAnswered(string integrand, string variable = "x") + { + var integral = integrand.ToEntity().Integrate(variable); + var text = integral.Stringize(); + Assert.DoesNotContain("integral(", text); + Assert.True(text.Length < 5000, $"{text.Length} characters of answer for {integrand}"); + Entity Pinned(Entity e) + { + foreach (var (symbol, value) in Pins) + if (symbol != variable) + e = e.Substitute(symbol, value); + return e; + } + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate(variable); + var original = Pinned(integrand.ToEntity()); + var compared = 0; + foreach (var at in new[] { 0.3, 0.8, 1.3, 1.9, 2.9, 3.7 }) + { + var want = original.Substitute(variable, at).EvalNumerical(); + var got = derivative.Substitute(variable, 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/d{variable} of the antiderivative of {integrand} is {got} at {variable} = {at}, where the integrand is {want}"); + } + Assert.True(compared >= 5, $"only {compared} points could be compared for {integrand}"); + } + } +}