From 50d7c4ddb4194649dc0a2c902ccb309b6444270c Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Tue, 20 Oct 2026 19:31:13 +0000 Subject: [PATCH] Write each linear once under a symbolic power A product of powers of quotients of linears whose exponents carry a symbol, e^atanh(a x) x^m sqrt(c - a^2 c x^2) or e^(2 p atanh(a x)) (c - a^2 c x^2)^p, was declined or ran out of time: the rule that writes each linear once reads rational exponents only. Each linear's exponent is now the symbolic sum of those it has in the bases, and K, the integrand over that product, has a logarithmic derivative of zero on every branch, so it stands in front. Only where the product is one power that is not whole beside whole ones; the answer is checked at sampled points. Rubi pocket (1,818 rows of 7.3.x and 7.4.x): 1726 -> 1748 solved, none lost; the moves re-run alone gain 22 rows of 7.3.6 and 7.4.2. Samples of every family unchanged. Part of #233. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 20 ++ .../Integration/IndefiniteIntegralSolver.cs | 284 ++++++++++++++++-- .../Integration/Integration.Definition.cs | 4 + ...ttenOnceUnderASymbolicPowerIntegralTest.cs | 99 ++++++ 4 files changed, 389 insertions(+), 18 deletions(-) create mode 100644 Sources/Tests/UnitTests/Calculus/EachLinearWrittenOnceUnderASymbolicPowerIntegralTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index acb549f9f..093c4ee52 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -4763,6 +4763,26 @@ and this is the same answer reached through the even root |---|---|---| | `"sqrt(x)/(sqrt(1/x + 1)*sqrt(1 - x^2))".Integrate("x")` | `integral(...)` | an antiderivative in `sqrt(x)`, `provided x >= 0` | +### A product of powers of quotients of linears with a symbolic exponent is written over each linear once + +**Answers where there were none.** `e^atanh(a x) x^m sqrt(c - a^2 c x^2)`, `e^(2 p atanh(a x)) (c - a^2 c x^2)^p` and +seventeen more of Rubi's 7.3.6 and 7.4.2 were declined. The first is `sqrt((1 + a x)/(1 - a x)) x^m sqrt(c - a^2 c x^2)`, +in which the root `1/a` stands in both bases with exponents `-1/2` and `1/2`, and the second is +`K (a x + 1)^(2 p)`; the rule below reads rational exponents only. Each exponent of a linear is now the sum, symbols +and all, of those it has in the bases, and `K`, the integrand over that product, has a logarithmic derivative of zero +on every branch, so it stands in front of the answer. Only where the product comes to one power that is not whole, of +x or of a linear, beside whole powers of the other linears above the bar: the antiderivative is then a sum of powers +of it on every interval between the roots, and is checked at sampled points. Two powers that are not whole are left +to the rules that answer them as a power of the quotient +([#233](https://github.com/asc-community/AngouriMath/issues/233)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"e^atanh(a*x)*x^m*sqrt(1-a^2*x^2)".ToEntity().Integrate("x")` | `integral(...)` | `K (x^(m + 1)/(m + 1) + a x^(m + 2)/(m + 2))`, 151 characters | +| `"e^atanh(a*x)*x^m*(c-a^2*c*x^2)^(5/2)".ToEntity().Integrate("x")` | `integral(...)` | 619 characters | +| `"e^(2*p*atanh(a*x))*(c-a^2*c*x^2)^p".ToEntity().Integrate("x")` | `integral(...)` | `K (a x + 1)^(2 p + 1)/(a (2 p + 1))`, 153 characters | +| `"e^(n*acoth(a*x))*(c-a*c*x)^(2+1/2*n)".ToEntity().Integrate("x")` | `integral(...)` | 585 characters | + ### A product of powers of quotients of linears is written over each linear once **Improvement, not silent.** `e^atanh(a x) sqrt(c - c/(a x))` is diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index d2db1ddd6..4729e1106 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -33014,30 +33014,278 @@ void Add(Entity root, Number.Rational times) } } } + } + + /// + /// The square root of a quotient of monomials in the symbols with a positive square + /// rational coefficient and even powers -- 1/a^2 is (1/a)^2 -- and null otherwise. + /// + private static Entity? AMonomialSquareRoot(Entity constant) + { + var (above, below) = Functions.SingleQuotient.Of(Functions.PartialFractions.InLowestTermsOverTheSymbols(constant)); + return RootOf(above) is { } top && RootOf(below) is { } bottom ? top / bottom : null; - // The square root of a quotient of monomials in the symbols with a positive square - // rational coefficient and even powers -- `1/a^2` is `(1/a)^2` -- and null otherwise. - static Entity? AMonomialSquareRoot(Entity constant) + static Entity? RootOf(Entity monomial) { - var (above, below) = Functions.SingleQuotient.Of(Functions.PartialFractions.InLowestTermsOverTheSymbols(constant)); - return RootOf(above) is { } top && RootOf(below) is { } bottom ? top / bottom : null; + Entity root = Number.Integer.One; + foreach (var factor in Mulf.LinearChildren(monomial)) + switch (factor) + { + case Number.Integer { EInteger: var whole } when whole.Sign > 0 && whole.Sqrt() is var floor && floor.Multiply(floor).Equals(whole): + root *= Number.Integer.Create(floor); + break; + case Powf(Variable symbol, Number.Integer { EInteger: var power }) when power.Sign > 0 && power.IsEven: + root *= MathS.Pow(symbol, Number.Integer.Create(power.Divide(EInteger.FromInt32(2)))); + break; + default: + return null; + } + return root; + } + } + + /// Set while asks its product. + [System.ThreadStatic] private static bool writingEachLinearOnceUnderASymbolicPower; - static Entity? RootOf(Entity monomial) + /// + /// A product of powers of quotients of polynomials in , a symbolic + /// exponent among them, where one root stands in two of the bases, written over each + /// linear once: K (x - r_1)^(e_1) ... (x - r_k)^(e_k), with e_i the sum of the + /// exponents r_i has in the bases, symbols and all. K is the integrand over + /// that product, and its logarithmic derivative is zero -- that of q^s is + /// s q'/q for any constant s and on every branch, and of e^(s ln(q)) + /// the same -- so it is constant wherever it is defined and stands in front of the answer, + /// the way Rubi writes sqrt(c - a^2 c x^2)/sqrt(1 - a^2 x^2) in front of its. + /// + /// + /// + /// e^atanh(a x) x^m sqrt(c - a^2 c x^2) is sqrt((1 + a x)/(1 - a x)) x^m sqrt(c - a^2 c x^2): + /// the root 1/a stands in both bases with exponents -1/2 and 1/2, and + /// -1/a with 1/2 twice, so the integrand is K x^m (a x + 1); and + /// e^(2 p atanh(a x)) (c - a^2 c x^2)^p is K (a x + 1)^(2 p). Each is a sum of + /// powers to the rules, answered in milliseconds. + /// reads rational exponents only, and declined both: the first ran out of time behind it + /// and the second was left unevaluated. Rubi's 7.3.6 and 7.4.2. + /// https://github.com/asc-community/AngouriMath/issues/233 + /// + /// + /// Only where it helps: a root stands in two sums to powers that are not positive whole + /// numbers and keeps a power once they are added, and the product is one power that is not + /// whole, of x or of a linear, beside whole powers of the other linears above the bar. Its + /// antiderivative is then a sum of powers of that one, which holds on every interval + /// between the roots whatever the exponent, and K is constant on each. Two powers + /// that are not whole -- e^(n atanh(a x))/(c - a^2 c x^2) is + /// K (a x + 1)^(n/2 - 1) (a x - 1)^(-n/2 - 1) -- are left to the rules behind this one, + /// which answer them as a power of the quotient, and one beside a whole power below the bar + /// is hypergeometric. Rational exponents alone are the other rule's. The answer is checked + /// at sampled points before it is given. + /// + /// + internal static Entity? SolveByWritingEachLinearOnceUnderASymbolicPower(Entity expr, Entity.Variable x, bool integrateByParts) + { + // Not while answering its own product, and only at the question asked, as the rule for + // rational exponents; and only where a power has a symbol for its exponent, or an + // exponential a symbol beside its logarithm, looked for before anything is read. + if (writingEachLinearOnceUnderASymbolicPower || !Integration.AnsweringTheQuestionAsked + || !expr.Nodes.Any(node => node is Powf(var @base, var exponent) + && (@base.ContainsNode(x) && !exponent.ContainsNode(x) && exponent.Vars.Any() + || @base == MathS.e && exponent.ContainsNode(x) + && exponent.Replace(inner => inner is Logf ? Number.Integer.One : inner).Vars.Any(symbol => symbol != x)))) + return null; + // Each root with the sum of its exponents and the sums it stands in to a power that is + // not a positive whole number; and of each base whether it is a sum. Only a root in two + // such is what writing each linear once is for: x in `x^m` and in `(a x)^2` is one power + // of x to the rules already, and `(a + b x)^m (a^2 - b^2 x^2)^2` a power of a linear + // times a polynomial, each answered as written. + var roots = new List<(Entity Root, Entity Exponent, HashSet Apart)>(); + var bases = new List(); + var symbolic = false; + if (!Read(expr, Number.Integer.One) || !symbolic || !roots.Any(root => root.Apart.Count >= 2)) + return null; + // Each linear with the root's denominator for its leading coefficient, `a x + 1` for the + // root `-1/a`, to the sum of its exponents simplified: `-p + p` is a zero power, and + // `-n/2 + 2 + n/2` a whole one, only so. + var linears = new List<(Entity Linear, Entity Exponent)>(); + var notWhole = 0; + var stillShared = false; + foreach (var (root, exponent, apart) in roots) + { + var total = exponent is Number ? exponent : exponent.Simplify(); + if (total.Evaled is Number.Integer whole) + total = whole; + else if (Functions.PartialFractions.IsZeroAsAValue(total)) + continue; + if (total == Number.Integer.Zero) + continue; + if (total is not Number.Integer) + notWhole++; + stillShared |= apart.Count >= 2; + var (above, below) = Functions.SingleQuotient.Of(root); + var linear = root == Number.Integer.Zero ? x + : (above is Number.Real { IsNegative: true } negative ? below * x + (-negative).InnerSimplified : below * x - above).InnerSimplified; + linears.Add((linear, total)); + } + // And only where a root shared so is left with a power: where every one of them is gone, + // `(a + b x)^n/(-a - b x)^n` and `e^(2 p atanh(a x)) (c - a c x)^p`, the rules behind + // answer the integrand as it is written, shorter. + if (!stillShared || notWhole > 1 || notWhole == 1 && linears.Any(written => written.Exponent is Number.Integer { IsNegative: true })) + return null; + // As a quotient, the negative whole powers below the bar, which is how the rules read it. + var pieces = linears.Select(written => written.Exponent is Number.Integer { IsNegative: true } negative + ? (Factor: negative == Number.Integer.MinusOne ? written.Linear : MathS.Pow(written.Linear, -negative), Below: true) + : (Factor: written.Exponent == Number.Integer.One ? written.Linear : MathS.Pow(written.Linear, written.Exponent), Below: false)).ToList(); + var product = Quotient(pieces).InnerSimplified; + // K, the integrand over the product, with a factor written in both taken out of both: + // `x^m` over `x^m` has no value at 0, where the integrand has one. + var unchanged = Factors(expr, false).ToList(); + var constant = new List<(Entity Factor, bool Below)>(); + foreach (var (factor, below) in pieces) + { + var same = unchanged.FindIndex(written => written.Below == below && written.Factor == factor); + if (same >= 0) + unchanged.RemoveAt(same); + else + constant.Add((factor, !below)); + } + constant.AddRange(unchanged); + Entity? integrated; + writingEachLinearOnceUnderASymbolicPower = true; + try + { + integrated = Integration.ComputeAsAQuestionOfItsOwn(product, x, integrateByParts); + } + finally + { + writingEachLinearOnceUnderASymbolicPower = false; + } + if (integrated is null || integrated.Nodes.Any(node => node == MathS.NaN)) + return null; + var answer = Quotient(constant) * integrated; + return Functions.PartialFractions.DerivativeHoldsAtSampledPoints(answer, expr, x) ? answer : null; + + static IEnumerable<(Entity Factor, bool Below)> Factors(Entity e, bool below) => e switch + { + Mulf(var left, var right) => Factors(left, below).Concat(Factors(right, below)), + Divf(var left, var right) => Factors(left, below).Concat(Factors(right, !below)), + _ => new[] { (e, below) }, + }; + + static Entity Quotient(List<(Entity Factor, bool Below)> factors) + { + Entity above = Number.Integer.One, below = Number.Integer.One; + foreach (var (factor, under) in factors) + if (under) + below = below == Number.Integer.One ? factor : below * factor; + else + above = above == Number.Integer.One ? factor : above * factor; + return below == Number.Integer.One ? above : above / below; + } + + // `factor` to the power `times`, read into the table through products, quotients and + // powers of them, the power of a product taken as the product of the powers and the + // exponential of a multiple of a logarithm as the power it is: `e^((n/2) ln(q))` is + // `q^(n/2)`, which is how the inverse hyperbolic functions under an exponential arrive. + // False where a factor of x is anything else. + bool Read(Entity factor, Entity times) + { + if (!factor.ContainsNode(x)) + return true; + switch (factor) { - Entity root = Number.Integer.One; - foreach (var factor in Mulf.LinearChildren(monomial)) - switch (factor) + case Mulf: + return Mulf.LinearChildren(factor).All(child => Read(child, times)); + case Divf(var above, var below): + return Read(above, times) && Read(below, Times(times, Number.Integer.MinusOne)); + case Powf(var e, var exponent) when e == MathS.e && AMultipleOfALogarithm(exponent) is var (multiple, argument): + return Read(argument, Times(times, multiple)); + case Powf(var @base, var exponent) when !exponent.ContainsNode(x): + return Read(@base, Times(times, exponent)); + case Variable or Sumf or Minusf: + var index = bases.Count; + bases.Add(factor is not Variable); + var (top, bottom) = Functions.SingleQuotient.Of(AsOneQuotient(factor)); + return WithItsRoots(top, times, index) && WithItsRoots(bottom, Times(times, Number.Integer.MinusOne), index); + default: + return false; + } + } + + // A product of exponents, exactly where both are rational, and noted as symbolic otherwise. + Entity Times(Entity left, Entity right) + { + if (left is Number.Rational leftRational && right is Number.Rational rightRational) + return (Number.Rational)(leftRational * rightRational); + symbolic = true; + return (left * right).InnerSimplified; + } + + // `c ln(q)` as `(c, q)` for a constant `c` and a natural logarithm of something of x, and + // null otherwise. + (Entity Multiple, Entity Argument)? AMultipleOfALogarithm(Entity exponent) + { + Entity multiple = Number.Integer.One; + Entity? argument = null; + foreach (var factor in Mulf.LinearChildren(exponent)) + if (factor is Logf(var logarithmBase, var antilogarithm) && logarithmBase == MathS.e && antilogarithm.ContainsNode(x) && argument is null) + argument = antilogarithm; + else if (factor.ContainsNode(x)) + return null; + else + multiple = Times(multiple, factor); + return argument is null ? null : (multiple, argument); + } + + // The roots of a polynomial in x, each its multiplicity times `exponent` into the table: + // a power of x, a linear, and a quadratic with no linear term whose roots are a monomial + // and its negative. False for anything else. + bool WithItsRoots(Entity polynomial, Entity exponent, int @base) + { + if (!polynomial.ContainsNode(x)) + return true; + if (!TreeAnalyzer.TryGetPolynomial(polynomial, x, out var read) || read.Count == 0 + || read.Values.Any(coefficient => coefficient.ContainsNode(x))) + return false; + var lowest = read.Keys.Min()!; + var highest = read.Keys.Max()!; + if (lowest.Sign < 0) + return false; + Entity CoefficientOf(EInteger power) => read.TryGetValue(power, out var coefficient) ? coefficient : Number.Integer.Zero; + if (Functions.PartialFractions.IsZeroAsAValue(CoefficientOf(highest)) || Functions.PartialFractions.IsZeroAsAValue(CoefficientOf(lowest))) + return false; + if (lowest.Sign > 0) + Add(Number.Integer.Zero, Times(exponent, Number.Integer.Create(lowest))); + var degree = highest.Subtract(lowest); + if (degree.IsZero) + return true; + if (degree.Equals(EInteger.One)) + { + Add(Functions.PartialFractions.InLowestTermsOverTheSymbols(-CoefficientOf(lowest) / CoefficientOf(highest)), exponent); + return true; + } + if (degree.Equals(EInteger.FromInt32(2)) + && Functions.PartialFractions.IsZeroAsAValue(CoefficientOf(lowest.Add(EInteger.One))) + && AMonomialSquareRoot(-CoefficientOf(lowest) / CoefficientOf(highest)) is { } root) + { + Add(root, exponent); + Add(Functions.PartialFractions.InLowestTermsOverTheSymbols(-root), exponent); + return true; + } + return false; + + void Add(Entity root, Entity times) + { + var apart = bases[@base] && times is not Number.Integer { IsPositive: true }; + for (var i = 0; i < roots.Count; i++) + if (roots[i].Root == root || Functions.PartialFractions.IsZeroAsAValue(roots[i].Root - root)) { - case Number.Integer { EInteger: var whole } when whole.Sign > 0 && whole.Sqrt() is var floor && floor.Multiply(floor).Equals(whole): - root *= Number.Integer.Create(floor); - break; - case Powf(Variable symbol, Number.Integer { EInteger: var power }) when power.Sign > 0 && power.IsEven: - root *= MathS.Pow(symbol, Number.Integer.Create(power.Divide(EInteger.FromInt32(2)))); - break; - default: - return null; + if (apart) + roots[i].Apart.Add(@base); + var sum = roots[i].Exponent is Number.Rational left && times is Number.Rational right + ? (Entity)(Number.Rational)(left + right) : (roots[i].Exponent + times).InnerSimplified; + roots[i] = (roots[i].Root, sum, roots[i].Apart); + return; } - return root; + roots.Add((root, times, apart ? new HashSet { @base } : new HashSet())); } } } diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 362e92079..c1f122cdc 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -729,6 +729,10 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr) // `1 + x^3`, which the rules for a power of a binomial answer. if ((answer = IndefiniteIntegralSolver.SolveALinearAndAQuadraticToOnePowerAsOneBinomial(expr, x, integrateByParts)) is { }) return answer; if ((answer = IndefiniteIntegralSolver.SolveByWritingEachLinearOnce(expr, x, integrateByParts)) is { }) return answer; + // The same with a symbol for an exponent, where the product is one power that is not + // whole beside whole ones: `e^atanh(a x) x^m sqrt(1 - a^2 x^2)` is `K x^m (a x + 1)`, and ran + // out of time behind it. + if ((answer = IndefiniteIntegralSolver.SolveByWritingEachLinearOnceUnderASymbolicPower(expr, x, integrateByParts)) is { }) return answer; // A rational function with numbers for coefficients over a denominator past the twelfth // degree, by Hermite's reduction before anything searches: nothing below answered one. if ((answer = IndefiniteIntegralSolver.SolveAHighDegreeRationalFunctionByHermite(expr, x)) is { }) return answer; diff --git a/Sources/Tests/UnitTests/Calculus/EachLinearWrittenOnceUnderASymbolicPowerIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/EachLinearWrittenOnceUnderASymbolicPowerIntegralTest.cs new file mode 100644 index 000000000..1b55549b1 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/EachLinearWrittenOnceUnderASymbolicPowerIntegralTest.cs @@ -0,0 +1,99 @@ +// +// 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 of powers of quotients of linears with a symbol for an exponent, in which one + /// linear stands in two of them, written over each linear once with a constant K in + /// front: e^atanh(a x) x^m sqrt(1 - a^2 x^2) is K x^m (a x + 1), and + /// e^(2 p atanh(a x)) (c - a^2 c x^2)^p is K (a x + 1)^(2 p). Rubi's 7.3.6 and + /// 7.4.2, which ran out of time or were left unevaluated. + /// #233 + /// + [Trait("Area", "Calculus")] + public sealed class EachLinearWrittenOnceUnderASymbolicPowerIntegralTest + { + /// + /// On both sides of every root, so that K is checked wherever it takes another + /// value, with the exponents pinned off the integers: the integrand is complex to the left + /// of 0 and beyond the roots, and compared there too. + /// + private static readonly double[] Points = { -2.9, -1.6, -0.95, -0.4, -0.15, 0.15, 0.4, 0.95, 1.6, 2.9 }; + + [Theory] + [InlineData("e^atanh(a*x)*x^m*sqrt(1-a^2*x^2)", "a=1.37,m=0.37")] + [InlineData("e^atanh(a*x)*x^m*(1-a^2*x^2)^(3/2)", "a=1.37,m=0.37")] + [InlineData("e^atanh(a*x)*x^m*(c-a^2*c*x^2)^(5/2)", "a=1.37,c=2.11,m=0.37")] + [InlineData("e^atanh(a*x)*x^m*(c-a^2*c*x^2)^(5/2)", "a=-1.37,c=-2.11,m=-0.37")] + [InlineData("e^atanh(a*x)*x^m*sqrt(c-c/(a^2*x^2))", "a=1.37,c=2.11,m=0.37")] + [InlineData("e^acoth(a*x)*x^m*sqrt(c-a^2*c*x^2)", "a=1.37,c=2.11,m=0.37")] + [InlineData("x^m*sqrt(c-a^2*c*x^2)/e^acoth(a*x)", "a=1.37,c=-2.11,m=0.37")] + [InlineData("e^acoth(a*x)*x^m*sqrt(c-c/(a^2*x^2))", "a=1.37,c=2.11,m=0.37")] + [InlineData("e^(2*(1+p)*atanh(a*x))/(1-a^2*x^2)^p", "a=1.37,p=0.61")] + [InlineData("e^(2*(1+p)*atanh(a*x))/(c-a^2*c*x^2)^p", "a=1.37,c=2.11,p=0.61")] + [InlineData("e^(2*p*atanh(a*x))*(c-a^2*c*x^2)^p", "a=1.37,c=2.11,p=0.61")] + [InlineData("e^(2*p*atanh(a*x))*(c-a^2*c*x^2)^p", "a=1.37,c=-2.11,p=-0.61")] + [InlineData("(c-a^2*c*x^2)^p/e^(2*p*atanh(a*x))", "a=1.37,c=2.11,p=0.61")] + [InlineData("e^(2*p*acoth(a*x))*(c-a^2*c*x^2)^p", "a=1.37,c=2.11,p=0.61")] + [InlineData("(c-a^2*c*x^2)^p/e^(2*p*acoth(a*x))", "a=1.37,c=2.11,p=0.61")] + [InlineData("e^(4*acoth(a*x))*(c-a*c*x)^p", "a=1.37,c=2.11,p=0.61")] + [InlineData("e^(n*acoth(a*x))*(c-a*c*x)^(2+1/2*n)", "a=1.37,c=2.11,n=1.3")] + [InlineData("e^(n*acoth(a*x))*(c-a*c*x)^(1+1/2*n)", "a=1.37,c=-2.11,n=1.3")] + public void IsAnsweredWithEachLinearOnce(string integrand, string pins) + => DifferentiatesBack(integrand, pins); + + /// + /// Two powers that are not whole once each linear is written once -- here + /// K (a x + 1)^(n/2 - 1) (a x - 1)^(-n/2 - 1) -- and a power of a linear beside a + /// polynomial that shares its root, are left to the rules behind, which answer them. + /// + [Theory] + [InlineData("e^(n*atanh(a*x))/(c-a^2*c*x^2)", "a=1.37,c=2.11,n=1.3")] + [InlineData("e^(n*acoth(a*x))/(c-a*c*x)^2", "a=1.37,c=2.11,n=1.3")] + [InlineData("(a+b*x)^m*(a^2-b^2*x^2)^2", "a=1.37,b=0.83,m=0.37")] + public void IsLeftToTheRulesBehind(string integrand, string pins) + => DifferentiatesBack(integrand, pins); + + private static void DifferentiatesBack(string integrand, string pins) + { + var integral = integrand.ToEntity().Integrate("x").Substitute("C", 0); + var text = integral.Stringize(); + Assert.DoesNotContain("integral(", text); + Assert.DoesNotContain("NaN", text); + Assert.True(text.Length < 5000, $"{text.Length} characters of answer for {integrand}"); + Entity Pin(Entity e) + { + foreach (var pin in pins.Split(',', StringSplitOptions.RemoveEmptyEntries)) + { + var parts = pin.Split('='); + e = e.Substitute(parts[0], double.Parse(parts[1], System.Globalization.CultureInfo.InvariantCulture)); + } + return e; + } + var derivative = Pin(integral).Differentiate("x"); + var original = Pin(integrand.ToEntity()); + var compared = 0; + foreach (var at in Points) + { + var want = original.Substitute("x", at).EvalNumerical(); + if (want.IsNaN) + continue; + var got = derivative.Substitute("x", at).EvalNumerical(); + compared++; + var difference = Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart); + Assert.True(difference < 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}"); + } + } +}