diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index fc7e23228..e00250237 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -18214,6 +18214,145 @@ static EInteger Choose(int whole, int chosen) } } + /// + /// A power of x or of a linear that is not a polynomial, beside a rational function of one exponential of a linear + /// e^B that is a Laurent polynomial in it: tanh(u) is (e^(2u) - 1)/(e^(2u) + 1) here, so + /// 1/(a + a tanh(u)) is (1 + e^(-2u))/(2a), and tan(u) and cot(u) are written in + /// e^(2 i u) first: 1/(1 + i tan(u)) is (1 + e^(-2 i u))/2. Each term is then the power beside + /// an exponential of a linear, a gamma function. And beside any power of x, or none, a Laurent polynomial in an + /// exponential of something in x that is not linear: x sinh(a + b x^n)^2. The exponentials are whole + /// multiples of one, and the quotient in w = e^B has a power of w below the bar, or this declines. Rubi's + /// 6.3.1, 6.4.1, 4.3.10, 4.4.10, 6.1.3 and 6.2.3. + /// https://github.com/asc-community/AngouriMath/issues/2075 + /// + internal static Entity? SolveAPowerBesideALaurentPolynomialInAnExponential(Entity expr, Entity.Variable x, bool integrateByParts) + { + if (!Integration.AnsweringTheQuestionAskedOrOneBelow || expr is not (Mulf or Divf or Powf)) + return null; + // The sine, cosine, tangent and cotangent of a linear A in E = e^(i A): sin(A) = (E - 1/E)/(2 i), cos(A) = + // (E + 1/E)/2, tan(A) = (E^2 - 1)/(i (E^2 + 1)) and cot(A) = i (E^2 + 1)/(E^2 - 1). + var written = expr.Replace(node => + { + if (node is not (Sinf or Cosf or Tanf or Cotanf)) + return node; + var argument = node.DirectChildren.First(); + if (!argument.ContainsNode(x) || !TreeAnalyzer.TryGetPolyLinear(argument, x, out _, out _)) + return node; + var exponential = MathS.Pow(MathS.e, MathS.i * argument); + return node switch + { + Sinf => (exponential - 1 / exponential) / (2 * MathS.i), + Cosf => (exponential + 1 / exponential) / 2, + Tanf => (MathS.Sqr(exponential) - 1) / (MathS.i * (MathS.Sqr(exponential) + 1)), + _ => MathS.i * (MathS.Sqr(exponential) + 1) / (MathS.Sqr(exponential) - 1), + }; + }); + if (written.Nodes.Any(node => node is TrigonometricFunction && node.ContainsNode(x))) + return null; + // A power of an exponential, (F^E)^n, is F^(n E) times K = (F^E)^n F^(-n E), which is constant between the + // cuts, its derivative being 0: carried through as a constant and written back in the answer. + var constantsBetweenTheCuts = new List<(Variable Name, Entity Value)>(); + written = written.Replace(node => + { + if (node is not Powf(Powf(var @base, var exponent) power, var outer) || @base.ContainsNode(x) || !exponent.ContainsNode(x) + || outer.ContainsNode(x) || outer is Number.Integer) + return node; + var name = Variable.CreateUnique(expr, "k_cut" + constantsBetweenTheCuts.Count); + constantsBetweenTheCuts.Add((name, node * MathS.Pow(@base, -(outer * exponent)))); + return name * MathS.Pow(@base, outer * exponent); + }); + // The factors holding an exponential of x, and the rest, which must be a power that is not a polynomial. + var (numerator, denominator) = written is Divf(var above, var below) ? (above, below) : (written, (Entity)Number.Integer.One); + Entity function = Number.Integer.One; + Entity beside = Number.Integer.One; + for (var side = 0; side < 2; side++) + foreach (var factor in Mulf.LinearChildren(side == 0 ? numerator : denominator)) + { + var holdsAnExponential = factor.Nodes.Any(node => node is Powf(var @base, var power) && !@base.ContainsNode(x) && power.ContainsNode(x)); + if (holdsAnExponential) + function = side == 0 ? function * factor : function / factor; + else + beside = side == 0 ? beside * factor : beside / factor; + } + if (!function.ContainsNode(x)) + return null; + // Every exponential a whole multiple of the first: e^(k B) is w^k. + Entity? unit = null; + foreach (var node in function.Nodes) + if (node is Powf(var @base, var power) && !@base.ContainsNode(x) && power.ContainsNode(x)) + { + unit = @base == MathS.e ? power : MathS.Ln(@base) * power; + break; + } + if (unit is null) + return null; + // Beside it only constants and powers of x or of one linear, whose terms the gamma rule reads. Beside an + // exponential of a linear, at least one power that is not whole: whole powers are elementary, or the sine and + // cosine integrals, sin(x)/x is Si(x), and are answered as such. Beside one of something else, powers of x + // alone: sinh(a + b x^n)^2 x^k is (e^(2 a + 2 b x^n) - 2 + e^(-2 a - 2 b x^n)) x^k/4, where sin(c + d/x)/(e + f x) + // and (a + b x^n)^p sin(c + d x) are not the gamma rule's, and asking it of each term was a slow decline. + Entity? linearBase = null; + var notWhole = false; + var (besideAbove, besideBelow) = beside is Divf(var besideNumerator, var besideDenominator) ? (besideNumerator, besideDenominator) : (beside, (Entity)Number.Integer.One); + for (var side = 0; side < 2; side++) + foreach (var factor in Mulf.LinearChildren(side == 0 ? besideAbove : besideBelow)) + { + if (!factor.ContainsNode(x)) + continue; + var (powered, exponentOfIt) = factor is Powf(var b, var e) && !e.ContainsNode(x) ? (b, e) : (factor, (Entity)Number.Integer.One); + if (!TreeAnalyzer.TryGetPolyLinear(powered, x, out _, out _) || linearBase is not null && powered != linearBase) + return null; + linearBase = powered; + notWhole |= exponentOfIt is not Number.Integer; + } + if (TreeAnalyzer.TryGetPolyLinear(unit, x, out _, out _) ? !notWhole : linearBase is not null && linearBase != x) + return null; + var w = Variable.CreateUnique(expr, "w_exp"); + var failed = false; + var inW = function.Replace(node => + { + if (node is not Powf(var @base, var power) || @base.ContainsNode(x) || !power.ContainsNode(x)) + return node; + var natural = @base == MathS.e ? power : MathS.Ln(@base) * power; + // The ratio comes with the condition that the unit is not 0, which it is not as a function of x. + var ratio = (natural / unit).Simplify(); + while (ratio is Providedf(var unconditional, _)) + ratio = unconditional; + if (ratio is Number.Integer multiple && multiple.EInteger.Abs().CompareTo(EInteger.FromInt32(12)) <= 0) + return MathS.Pow(w, multiple); + failed = true; + return node; + }); + if (failed || inW.ContainsNode(x)) + return null; + var (top, bottom) = Functions.SingleQuotient.Of(inW); + if (!TreeAnalyzer.TryGetPolynomial(bottom.Expand(), w, out var belowInW) || belowInW.Count != 1 + || !TreeAnalyzer.TryGetPolynomial(top.Expand(), w, out var aboveInW) || aboveInW.Keys.Any(power => power.Sign < 0 || power.CompareTo(EInteger.FromInt32(12)) > 0)) + return null; + // One term is the question again, respelled: x e^(sin(x)) is x w. Not where a power of an exponential was + // written as one. + if (constantsBetweenTheCuts.Count == 0 && aboveInW.Count(term => !TreeAnalyzer.IsZero(term.Value)) < 2) + return null; + var only = belowInW.Single(); + var (lowest, scale) = (only.Key, only.Value); + Entity? answer = null; + foreach (var entry in aboveInW) + { + var (power, coefficient) = (entry.Key, entry.Value); + if (TreeAnalyzer.IsZero(coefficient)) + continue; + var shift = power.Subtract(lowest); + var term = shift.IsZero ? coefficient / scale * beside + : coefficient / scale * beside * MathS.Pow(MathS.e, (Number.Integer.Create(shift) * unit).InnerSimplified); + if (Integration.ComputeAsAQuestionOfItsOwn(term.InnerSimplified, x, integrateByParts) is not { } integrated) + return null; + answer = answer is null ? integrated : answer + integrated; + } + foreach (var (name, value) in constantsBetweenTheCuts) + answer = answer?.Substitute(name, value); + return answer; + } + /// /// A polynomial beside a function of one shifted linear a + b x that parts cannot reach, written in /// u = a + b x: a power that is not whole of something holding a logarithm of it, x (a + b ln(c (d + e x)))^p, diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 5849f7600..9687c84b1 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -982,6 +982,7 @@ internal static bool ASumOverRootsWouldAnswer if ((answer = IndefiniteIntegralSolver.SolveByWritingAnExponentAsAPowerOfAShiftedLinear(expr, x, integrateByParts)) is { }) return answer; if ((answer = IndefiniteIntegralSolver.SolveAPolynomialBesideATranscendentalPowerOfAShiftedLinear(expr, x, integrateByParts)) is { }) return answer; if ((answer = IndefiniteIntegralSolver.SolveAPowerOfXBesideATranscendentalPowerOfABinomial(expr, x, integrateByParts)) is { }) return answer; + if ((answer = IndefiniteIntegralSolver.SolveAPowerBesideALaurentPolynomialInAnExponential(expr, x, integrateByParts)) is { }) return answer; // A half-odd power of a ± a sin(y) beside powers of other linears in the sine, by the half // angle, before the polynomial term: beneath it the substitution search ran out of time. if ((answer = IndefiniteIntegralSolver.SolveAHalfOddPowerOfOnePlusMinusASineBesidePowersOfLinearsInIt(expr, x, integrateByParts)) is { }) return answer; diff --git a/Sources/Tests/UnitTests/Calculus/APowerBesideAnExponentialOfAPowerIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/APowerBesideAnExponentialOfAPowerIntegralTest.cs index 1a7e0b76a..20453606d 100644 --- a/Sources/Tests/UnitTests/Calculus/APowerBesideAnExponentialOfAPowerIntegralTest.cs +++ b/Sources/Tests/UnitTests/Calculus/APowerBesideAnExponentialOfAPowerIntegralTest.cs @@ -218,6 +218,24 @@ public void APositiveRateIsTakenOutOfThePower() [InlineData("(c + a*asin(d - a*x))^n")] public void ASymbolicPowerOfAnInverseSineOrCosine(string integrand) => DifferentiatesBack(integrand); + /// + /// A power of a linear beside a rational function of a hyperbolic or circular tangent that is a Laurent polynomial + /// in an exponential: 1/(a + a tanh(u)) is (1 + e^(-2u))/(2a). And a power of x beside a square of a + /// hyperbolic function of a power of x, and powers of an exponential, (F^E)^n, which are F^(n E) times a + /// constant between the cuts. Rubi's 6.3.1, 6.4.1, 4.3.10, 4.4.10, 6.1.3, 6.2.3, 2.2 and 2.1. + /// + [Theory] + [InlineData("(d + f*x)^m/(a + a*tanh(c + b*x))")] + [InlineData("(d + f*x)^m/(a + a*coth(c + b*x))^2")] + [InlineData("(d + f*x)^m/(a + i*a*tan(c + b*x))")] + [InlineData("(d + f*x)^m/(a + i*a*cot(c + b*x))^3")] + [InlineData("x*sinh(a + b*x^n)^2")] + [InlineData("cosh(a + b*x^3)^2")] + [InlineData("sinh(a + b*x^n)^2/x^2")] + [InlineData("(a + b*(c^(d + f*x))^n)^2*(d + f*x)^m")] + [InlineData("(c^(a + b*x))^n*(d + f*x)^(4/3)")] + public void APowerBesideALaurentPolynomialInAnExponential(string integrand) => DifferentiatesBack(integrand); + /// /// Not where the integral is elementary, an exponential integral or an error function: those keep the form /// they had.