Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15972,6 +15972,84 @@ private static bool TryReadAPowerTimesARadicalQuadratic(Entity expr, Entity.Vari
return (-constant / slope * answerInU.Substitute(u, 1 / linear)).InnerSimplified;
}

/// <summary>
/// Sines, cosines and the rest of a function of the reciprocal of a linear, beside a whole
/// power of the linear: <c>L^m f(a + b/L^k)</c> with <c>L = c + d x</c>. Under <c>u = 1/L</c>,
/// <c>dx = -du/(d u^2)</c>, and it is <c>-(1/d) u^(-m - 2) f(a + b u^k)</c>: a polynomial times
/// the function for <c>m &lt;= -2</c>, which by parts is elementary for a sine or cosine of a
/// linear. <c>sin(a + b/x)/x^3</c> is <c>-u sin(a + b u)</c>. Rubi's 4.1.12 and 4.2.12,
/// <c>(e x)^m (a + b sin(c + d x^n))^p</c> for a negative <c>n</c>; the exponential's case is
/// <see cref="SolveAGaussianInAReciprocal"/>.
/// https://github.com/asc-community/AngouriMath/issues/718
/// </summary>
/// <remarks>
/// Every factor with the variable in it is read: a trigonometric function of something of
/// <c>L</c> with <c>1/L</c> in it, or a whole power of <c>L</c>; anything else and the rule
/// declines, rather than write a polynomial of <c>x</c> in <c>u</c>. Only for <c>m &lt;= -3</c>:
/// the other powers are answered as written, and asked here would come back respelled.
/// </remarks>
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);
}

/// <summary>
/// The linear a node of <paramref name="exponent"/> divides by, <c>b/L^k</c> or <c>L^(-k)</c>,
/// or <see langword="null"/>.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// A sine or cosine of <c>a + b/L^k</c> beside <c>L^m</c>, <c>m &lt;= -3</c>, under <c>u = 1/L</c>:
/// <c>sin(a + b/x)/x^3</c> is <c>-u sin(a + b u)</c>, elementary by parts, and was declined.
/// Rubi's 4.1.12 and 4.2.12, <c>(e x)^m (a + b sin(c + d x^n))^p</c> for a negative <c>n</c>.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
[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}");
}
}
}
Loading