diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index f9b3137d1..a5b6dcbe9 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -2849,6 +2849,22 @@ quadratic is. `w` is real for a positive `x`, and the answer says so, `provided | `"(a^2+b^2/x^(2/5)+2*a*b/x^(1/5))^(5/2)".Integrate("x")`, Rubi 1.2.3.2 #659 | left unevaluated | an antiderivative in `x^(-1/5)`, under the same condition | | `"(1+2*x^(1/2)+x)^(3/2)".Integrate("x")` | left unevaluated | `2 (x/2 + x^(3/2) + 3 x^2/4 + x^(5/2)/5) provided x > 0` | +### A sine or cosine of the reciprocal of a linear beside a power of it is integrated under `u = 1/L` + +**Answers where there were none.** `sin(a + b/x)/x^3` was declined, while under `u = 1/x` it is +`-u sin(a + b u)`, one step by parts. A trigonometric function of `a + b/L^k`, `L` a linear, beside +`L^m` with `m <= -3` is a polynomial in `u = 1/L` times the function, and is integrated so and written +back. Rubi's 4.1.12 and 4.2.12, `(e x)^m (a + b sin(c + d x^n))^p` for a negative `n`. The other +powers were answered already and are answered as before +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"sin(a + b/x)/x^3".ToEntity().Integrate("x")` | `integral(...)` | a sine and a cosine of `a + b/x` | +| `"cos(a + b/x)/x^4".ToEntity().Integrate("x")` | `integral(...)` | the same | +| `"sin(a + b/x^2)/x^5".ToEntity().Integrate("x")` | `integral(...)` | the same, of `a + b/x^2` | +| `"sin(a + b/(c + d*x))/(c + d*x)^3".ToEntity().Integrate("x")` | `integral(...)` | the same, of `a + b/(c + d x)` | + ### A power of the variable times a sine or cosine of a logarithm, and a power of a monomial `x^2 sin(a + b ln(c x^n))` and `(c x^n)^b` were both left as written. Two rules, each exact: diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 780107b11..6886a554a 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -15972,6 +15972,84 @@ private static bool TryReadAPowerTimesARadicalQuadratic(Entity expr, Entity.Vari return (-constant / slope * answerInU.Substitute(u, 1 / linear)).InnerSimplified; } + /// + /// Sines, cosines and the rest of a function of the reciprocal of a linear, beside a whole + /// power of the linear: L^m f(a + b/L^k) with L = c + d x. Under u = 1/L, + /// dx = -du/(d u^2), and it is -(1/d) u^(-m - 2) f(a + b u^k): a polynomial times + /// the function for m <= -2, which by parts is elementary for a sine or cosine of a + /// linear. sin(a + b/x)/x^3 is -u sin(a + b u). Rubi's 4.1.12 and 4.2.12, + /// (e x)^m (a + b sin(c + d x^n))^p for a negative n; the exponential's case is + /// . + /// https://github.com/asc-community/AngouriMath/issues/718 + /// + /// + /// Every factor with the variable in it is read: a trigonometric function of something of + /// L with 1/L in it, or a whole power of L; anything else and the rule + /// declines, rather than write a polynomial of x in u. Only for m <= -3: + /// the other powers are answered as written, and asked here would come back respelled. + /// + internal static Entity? SolveAFunctionOfTheReciprocalOfALinear(Entity expr, Entity.Variable x, bool integrateByParts) + { + static bool IsATrigonometric(Entity node) => node is Sinf or Cosf or Tanf or Cotanf or Secantf or Cosecantf; + // The linear a trigonometric argument divides by. + Entity? linear = null; + foreach (var node in expr.Nodes) + if (IsATrigonometric(node) && node.DirectChildren.First() is var argument && argument.ContainsNode(x) + && ALinearBelowABar(argument, x) is { } below) + { + linear = below; + break; + } + if (linear is null || !TreeAnalyzer.TryGetPolyLinear(linear, x, out var slope, out _) || TreeAnalyzer.IsZero(slope)) + return null; + var u = Variable.CreateUnique(expr, "u_rec"); + Entity constant = Number.Integer.One; + var power = EInteger.Zero; + Entity inU = Number.Integer.One; + var sawAFunction = false; + foreach (var (factor, underneath) in FactorsOfTheIntegrand(expr)) + { + if (!factor.ContainsNode(x)) + { + constant = underneath ? constant / factor : constant * factor; + continue; + } + var (candidate, k) = factor is Powf(var raised, Number.Integer n) ? (raised, n.EInteger) : (factor, EInteger.One); + if (candidate == linear) + { + power = underneath ? power.Subtract(k) : power.Add(k); + continue; + } + // A function of the reciprocal of the linear, read in u; a whole power of one as well. + // Bottom-up, so that each quotient by the linear is met whole; the linear anywhere + // else is left, and declines. + if (!factor.Nodes.Any(IsATrigonometric)) + return null; + var rewritten = factor.Replace(node => node switch + { + Divf(var numerator, Powf(var below, Number.Integer k)) when below == linear => numerator * MathS.Pow(u, k), + Divf(var numerator, var below) when below == linear => numerator * u, + Powf(var below, Number.Integer { EInteger.Sign: < 0 } k) when below == linear => MathS.Pow(u, -k), + _ => node + }).InnerSimplified; + if (rewritten.ContainsNode(x)) + return null; + sawAFunction = true; + inU = underneath ? inU / rewritten : inU * rewritten; + } + // L^m dx = -u^(-m - 2) du/d. Only where that is a positive power of u: at m = -2 the + // integrand is the derivative of its argument times a function of it, and above, a power + // of u below the bar is the sine and cosine integrals', both answered as written. + var inUPower = power.Negate().Subtract(EInteger.FromInt32(2)); + if (!sawAFunction || inUPower.Sign <= 0 || inUPower.CompareTo(EInteger.FromInt32(24)) > 0) + return null; + var question = -constant / slope * MathS.Pow(u, Number.Integer.Create(inUPower)) * inU; + if (Integration.ComputeAsAQuestionOfItsOwn(question, u, integrateByParts) is not { } answer + || answer.Nodes.Any(node => node is Integralf || node == MathS.NaN)) + return null; + return answer.Substitute(u, 1 / linear); + } + /// /// The linear a node of divides by, b/L^k or L^(-k), /// or . diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 29c0bcde8..a49d97785 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -638,6 +638,8 @@ private static Entity Normalized(Entity expr, Entity.Variable x) => if ((answer = IndefiniteIntegralSolver.SolveByWritingAPowerOfAnExponentialAsAMultipleOfOne(expr, x, integrateByParts)) is { }) return answer; // An exponential of a quadratic in 1/L beside a power of L, onto the Gaussian under u = 1/L. if ((answer = IndefiniteIntegralSolver.SolveAGaussianInAReciprocal(expr, x)) is { }) return answer; + // And a trigonometric function of the reciprocal of a linear beside a power of it, under u = 1/L. + if ((answer = IndefiniteIntegralSolver.SolveAFunctionOfTheReciprocalOfALinear(expr, x, integrateByParts)) is { }) return answer; // An exponential of a multiple of a logarithm is a power of the argument, which is // how every inverse hyperbolic function under an exponential arrives. if ((answer = IndefiniteIntegralSolver.SolveByFoldingAnExponentialOfALogarithm(expr, x, integrateByParts)) is { }) return answer; diff --git a/Sources/Tests/UnitTests/Calculus/FunctionOfTheReciprocalIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/FunctionOfTheReciprocalIntegralTest.cs new file mode 100644 index 000000000..f1019143b --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/FunctionOfTheReciprocalIntegralTest.cs @@ -0,0 +1,53 @@ +// +// 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 sine or cosine of a + b/L^k beside L^m, m <= -3, under u = 1/L: + /// sin(a + b/x)/x^3 is -u sin(a + b u), elementary by parts, and was declined. + /// Rubi's 4.1.12 and 4.2.12, (e x)^m (a + b sin(c + d x^n))^p for a negative n. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class FunctionOfTheReciprocalIntegralTest + { + [Theory] + [InlineData("sin(a + b/x)/x^3")] + [InlineData("cos(a + b/x)/x^4")] + [InlineData("sin(a + b/x)^2/x^3")] + [InlineData("cos(a + b/x)^3/x^4")] + [InlineData("sin(a + b/x^2)/x^5")] + [InlineData("sin(a + b/(c + d*x))/(c + d*x)^3")] + public void UnderTheReciprocal(string integrand) + { + var integral = integrand.ToEntity().Integrate("x"); + Assert.DoesNotContain("integral(", integral.Stringize()); + Assert.DoesNotContain("NaN", integral.Stringize()); + Entity Pinned(Entity e) => e.Substitute("a", 2.3).Substitute("b", 0.7).Substitute("c", 1.3).Substitute("d", 1.7); + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pinned(integrand.ToEntity()); + var compared = 0; + foreach (var at in new[] { -1.7, -0.9, 0.3, 0.8, 1.6, 2.9 }) + { + var want = original.Substitute("x", at).EvalNumerical(); + if (want.IsNaN || Math.Abs((double)want.ImaginaryPart) > 1e-12) + continue; + compared++; + var got = derivative.Substitute("x", at).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}"); + } + Assert.True(compared >= 5, $"only {compared} points could be compared for {integrand}"); + } + } +}