diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index e69635cad..85189f790 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -400,6 +400,27 @@ whose denominator in the tangent is a product of linears | `"sec(c + d*x)^3/(a*cos(c + d*x) + b*sin(c + d*x))^4".ToEntity().Integrate("x")` | `integral(...)` | answered, in 9,322 characters | | `"1/(a*sec(x) + b*tan(x))^4".ToEntity().Integrate("x")` | `integral(...)` | answered, in 6,548 characters | +### An odd power of a sine or a cosine of a logarithm is integrated, and a resonance on a sign is declined + +**Answers where there were none.** `x^m sin(a + b ln(k x^n))^3` and the cosine's cube were declined, though the first +and second powers were answered (Rubi's 4.7.5). Under `t = ln(k x^n)` each one is an exponential times a power of a +sine, `e^((m + 1) t/n) sin(a + b t)^3`. The rule that closes that answers only the question asked, and the +substitution asked it one level down. A logarithm's substitution now asks that integrand as the question +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +**No answer where there was a wrong one.** `e^(x/n) sin(x sqrt(-1/n^2))` is resonant: `1/n + i sqrt(-1/n^2)` is zero +for a positive `n`, and `1/n - i sqrt(-1/n^2)` is zero for a negative one. The answer divided by one of them, and had +no value at any point. It is declined now ([#1852](https://github.com/asc-community/AngouriMath/issues/1852)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"sin(ln(x))^3".ToEntity().Integrate("x")` | `integral(...)` | 130 characters | +| `"sin(a + b*ln(x))^3".ToEntity().Integrate("x")` | `integral(...)` | 909 characters | +| `"x^2*sin(a + b*ln(k*x^n))^3".ToEntity().Integrate("x")` | `integral(...)` | 1,605 characters | +| `"x^m*cos(a + b*ln(k*x^n))^3".ToEntity().Integrate("x")` | `integral(...)` | 1,907 characters | +| `"sin(a + b*ln(k*x^n))^4/x^2".ToEntity().Integrate("x")` | `integral(...)` | 2,083 characters | +| `"exp(x/n)*sin(x*sqrt((-1)/n^2))".ToEntity().Integrate("x")` | an answer with no value anywhere, divided by `1/n^2 + (-1/n^2)` | `integral(...)` | + ### An odd power of a quadratic's derivative over a power of the quadratic is integrated in the quadratic **Shorter answers, and answers where there were none.** `(b + 2c x)^9/(a + b x + c x^2)^3` was answered through the diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 3fcac5325..5f5414950 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -8146,12 +8146,24 @@ private static (Entity Elementary, Entity Si, Entity Ci) APowerTimesASineOrACosi // `e^(i arctan(a x))/sqrt(c + a^2 c x^2)` under `x = tan(u)/a` is `e^(i u) cos(u)`. var plus = ((onTheCosine - MathS.i * onTheSine) / 2).InnerSimplified; var minus = ((onTheCosine + MathS.i * onTheSine) / 2).InnerSimplified; + var towardsPlus = (rate + MathS.i * frequency).InnerSimplified; + var towardsMinus = (rate - MathS.i * frequency).InnerSimplified; + static bool Vanishes(Entity exponent) => exponent.Evaled is Number.Complex { IsZero: true } + || exponent.Vars.Any() && Functions.PartialFractions.Bare(exponent.Simplify()).Evaled is Number.Complex { IsZero: true }; + var plusVanishes = Vanishes(towardsPlus); + var minusVanishes = Vanishes(towardsMinus); + // One of the two vanishes, since their product is the scale, and which one can rest on + // a sign: with `a = 1/n` and `b = sqrt(-1/n^2)`, `a + i b` is zero for a positive `n` + // and `a - i b` for a negative one. Neither seen to, the term over it divides by zero + // on one side of the sign, and the answer had no value anywhere. + if (!plusVanishes && !minusVanishes) + return null; Entity resonant = Number.Integer.Zero; - foreach (var (coefficient, exponent) in new[] { (plus, (rate + MathS.i * frequency).InnerSimplified), (minus, (rate - MathS.i * frequency).InnerSimplified) }) + foreach (var (coefficient, exponent, vanishes) in new[] { (plus, towardsPlus, plusVanishes), (minus, towardsMinus, minusVanishes) }) { if (coefficient.Evaled is Number.Complex { IsZero: true }) continue; - var term = exponent.Evaled is Number.Complex { IsZero: true } ? polynomial : polynomial * MathS.Pow(MathS.e, exponent * x); + var term = vanishes ? polynomial : polynomial * MathS.Pow(MathS.e, exponent * x); if (Integration.ComputeIndefiniteIntegral(term.InnerSimplified, x, integrateByParts: false) is not { } termIntegral || termIntegral.Nodes.Any(node => node == MathS.NaN)) return null; resonant += coefficient * termIntegral; @@ -9820,12 +9832,83 @@ node is Logf(var @base, var argument) && !@base.ContainsNode(x) && @base != Math if (integrand is Providedf(var inner, _)) integrand = inner; - if (Integration.ComputeIndefiniteIntegral(integrand, u, integrateByParts) is not { } result) + if (TheIntegrandInTheLogarithm(integrand, u, integrateByParts) is not { } result) return null; var back = result.Substitute(u, MathS.Ln(x)); return IsARadicalFunctionOfWholePowersOfTheExponential(inU, u) ? back.Provided(x > Number.Integer.Zero) : back; } + /// + /// The integral of what a logarithm's substitution makes of the integrand, in the logarithm + /// ; an exponential times whole powers of sines and cosines asked at the top. + /// + /// + /// x^m sin(a + b ln(c x^n))^3 is e^((m + 1) t/n) sin(a + b t)^3 in t, which the + /// closed rule for an exponential times a trigonometric answers only when asked, and as the + /// substitution's integrand it is one level down: the cube was declined where the first and + /// second powers, which other rules answer, were not. The substitution is exact, so its integrand + /// is the question asked in another variable. Once: an integrand asked so does not lift its own + /// substitution again. Rubi's 4.7.5. + /// https://github.com/asc-community/AngouriMath/issues/718 + /// + private static Entity? TheIntegrandInTheLogarithm(Entity integrand, Entity.Variable t, bool integrateByParts) + { + if (!Integration.AnsweringTheQuestionAsked || !IsAnExponentialTimesPowersOfSinesAndCosines(integrand, t)) + return Integration.ComputeIndefiniteIntegral(integrand, t, integrateByParts); + if (askingTheIntegrandInTheLogarithm) + { + var answer = Integration.ComputeIndefiniteIntegral(integrand, t, integrateByParts); + if (answer is null) + Integration.DeclinedForItsScope(); + return answer; + } + askingTheIntegrandInTheLogarithm = true; + try + { + return Integration.ComputeAsAQuestionOfItsOwn(integrand, t, integrateByParts); + } + finally + { + askingTheIntegrandInTheLogarithm = false; + } + } + + /// Whether is asking at the top already. + [System.ThreadStatic] private static bool askingTheIntegrandInTheLogarithm; + + /// + /// Whether is a constant times an exponential of a linear in + /// times whole positive powers of sines and cosines of linears in it. + /// + private static bool IsAnExponentialTimesPowersOfSinesAndCosines(Entity expr, Entity.Variable t) + { + if (expr is Divf(var above, var below)) + { + if (below.ContainsNode(t)) + return false; + expr = above; + } + var exponential = false; + var trigonometric = false; + foreach (var factor in Mulf.LinearChildren(expr)) + { + if (!factor.ContainsNode(t)) + continue; + if (factor is Powf(var @base, var exponent) && !@base.ContainsNode(t)) + { + if (!TreeAnalyzer.TryGetPolyLinear(exponent, t, out _, out _)) + return false; + exponential = true; + continue; + } + var function = factor is Powf(var raised, Number.Integer power) && power.EInteger.Sign > 0 ? raised : factor; + if (function is not (Sinf or Cosf) || !TreeAnalyzer.TryGetPolyLinear(function.DirectChildren.First(), t, out _, out _)) + return false; + trigonometric = true; + } + return exponential && trigonometric; + } + /// /// Whether holds only in exponentials /// e^(k t + a) with a whole k, some of them under a power that is not @@ -9910,7 +9993,7 @@ node is Powf(var @base, var exponent) && @base == MathS.e && exponent.ContainsNo var integrand = Functions.SingleQuotient.Combine(inT * MathS.Pow(MathS.e, (mPlusOne / n).InnerSimplified * t)).InnerSimplified; if (integrand is Providedf(var bare, _)) integrand = bare; - if (Integration.ComputeIndefiniteIntegral(integrand, t, integrateByParts) is not { } inTIntegral) + if (TheIntegrandInTheLogarithm(integrand, t, integrateByParts) is not { } inTIntegral) return null; // Of ln(x) itself, K is 1, and written as x^(m + 1) x^(-(m + 1)) it is not seen to be. var k = logarithms[0].Antilogarithm == x ? Number.Integer.One diff --git a/Sources/Tests/UnitTests/Calculus/AnOddPowerOfATrigonometricOfALogarithmIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/AnOddPowerOfATrigonometricOfALogarithmIntegralTest.cs new file mode 100644 index 000000000..76f4a83ce --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/AnOddPowerOfATrigonometricOfALogarithmIntegralTest.cs @@ -0,0 +1,60 @@ +// +// 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 power of x times a whole power of a sine or a cosine of a + b ln(k x^n), which + /// in the logarithm is an exponential times a power of a sine or a cosine. Rubi's 4.7.5. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class AnOddPowerOfATrigonometricOfALogarithmIntegralTest + { + [Theory] + [InlineData("sin(ln(x))^3", 500)] + [InlineData("sin(a + b*ln(x))^3", 2000)] + [InlineData("x^2*sin(a + b*ln(k*x^n))^3", 3000)] + [InlineData("sin(a + b*ln(k*x^n))^3/x^3", 3000)] + [InlineData("x^m*cos(a + b*ln(k*x^n))^3", 3000)] + [InlineData("sin(a + b*ln(k*x^n))^4/x^2", 4000)] + public void DifferentiatesBack(string integrand, int atMostCharacters) + { + var integral = integrand.ToEntity().Integrate("x"); + Assert.DoesNotContain("integral(", integral.Stringize()); + Assert.True(integral.Stringize().Length <= atMostCharacters, $"{integral.Stringize().Length} characters"); + Entity Pinned(Entity e) => e.Substitute("a", "1/4".ToEntity()).Substitute("b", "3/2".ToEntity()) + .Substitute("k", 2).Substitute("n", 3).Substitute("m", "5/2".ToEntity()); + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pinned(integrand.ToEntity()); + foreach (var at in new[] { "0.35", "0.6", "1.1", "1.4" }) + { + var want = original.Substitute("x", at.ToEntity()).EvalNumerical(); + var got = derivative.Substitute("x", at.ToEntity()).EvalNumerical(); + Assert.True(Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart) + < 1e-9 * Math.Max(1, Math.Abs((double)want.RealPart)), + $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}"); + } + } + + /// + /// a + i b or a - i b is zero, which one by the sign of n, and an answer + /// dividing by either had no value anywhere. + /// #1852 + /// + [Theory] + [InlineData("exp(x/n)*sin(x*sqrt((-1)/n^2))")] + [InlineData("exp(x/n)*sin(a + x*sqrt((-1)/n^2))")] + [InlineData("sin(a + ln(k*x^n)*sqrt((-1)/n^2))")] + public void AResonanceOnASignIsDeclined(string integrand) => + Assert.Contains("integral(", integrand.ToEntity().Integrate("x").Stringize()); + } +}