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
20 changes: 20 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,26 @@ in Rubi's 1.1.3.4 come out shorter as well, and those that took two seconds take
| `"1/(x^8*(a + b*x^3)*(c + d*x^3))".ToEntity().Integrate("x")` | `integral(...)` | 1,217 characters |
| `"1/(x^6*(a + b*x^4)*(c + d*x^4))".ToEntity().Integrate("x")` | `integral(...)` | 795 characters |

### A trigonometric function of `a + b/x` beside a rational function is integrated in `1/x`

**Answers where there were none.** `sin(c + d/x)/(e + f x)` was declined: the substitution search reads a candidate's
derivative but never solves the candidate for `x`. Under `x = 1/u` it is `-sin(c + d u)/(u (e u + f)) du`, which the
partial fractions and the sine and cosine integrals answer, and `x = 1/u` is one-to-one on either side of 0. Below a
root of a linear, the linear radical substitution gets there first
([#718](https://github.com/asc-community/AngouriMath/issues/718)).

The answers below a root are large because the sine over the linears that come out of the substitution carries
expanded coefficients ([#1877](https://github.com/asc-community/AngouriMath/issues/1877)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"sin(c + d/x)/(e + f*x)".ToEntity().Integrate("x")` | `integral(...)` | 196 characters |
| `"(a + b*sin(c + d/x))/(e + f*x)^2".ToEntity().Integrate("x")` | `integral(...)` | 470 characters |
| `"(a + b*sin(c + d/x))^2/(e + f*x)^3".ToEntity().Integrate("x")` | `integral(...)` | 2,516 characters |
| `"(c*e + d*e*x)^(1/3)*sin(a + b/(c + d*x)^(1/3))".ToEntity().Integrate("x")` | `integral(...)` | 1,453 characters |
| `"sin(a + b/sqrt(c + d*x))/(e + f*x)".ToEntity().Integrate("x")` | `integral(...)` | 12,010 characters |
| `"sin(a + b/sqrt(c + d*x))/(e + f*x)^2".ToEntity().Integrate("x")` | `integral(...)` | 32,780 characters |

### A power of the cosine beside a secant binomial is integrated in the cosine

**Answers where there were none.** `cos(c + d x)^2/(a + b sec(c + d x))` was declined, and so were the cosine's powers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19120,6 +19120,77 @@ Entity TakeOut(Entity node)
/// <summary>Set while <see cref="SolveByTakingTheWholePowersOutOfAPowerOfAProduct"/> asks its question.</summary>
[System.ThreadStatic] private static bool takingTheWholePowersOut;

/// <summary>
/// A rational function of <c>x</c> and of trigonometric functions of <c>a + b/x</c>, under
/// <c>x = 1/u</c>: <c>sin(c + d/x)/(e + f x)</c> is
/// <c>-sin(c + d u)/(u (e u + f)) du</c>, which the partial fractions and the sine and
/// cosine integrals answer.
/// </summary>
/// <remarks>
/// <c>x = 1/u</c> is one-to-one on either side of 0, so the answer in <c>u</c> written back
/// in <c>1/x</c> holds on both. Every argument of a function holding <c>x</c> has to be
/// linear in <c>1/x</c> and not in <c>x</c>, so the integrand in <c>u</c> is one this rule
/// does not read again. Rubi's 4.1.12 <c>(a + b sin(c + d/x))^k/(e + f x)^m</c> were
/// declined: the substitution search reads a candidate's derivative but never solves
/// the candidate for <c>x</c>. https://github.com/asc-community/AngouriMath/issues/718
/// </remarks>
internal static Entity? SolveByTheReciprocalOfAFunctionOfTheReciprocal(Entity expr, Entity.Variable x, bool integrateByParts)
{
var u = Variable.CreateUnique(expr, "u_rec");
// Each function of x, named, and written in u: its argument the linear in u it is.
var inU = new Dictionary<Entity, Entity>();
var names = new Dictionary<Entity, Variable>();
foreach (var node in expr.Nodes)
{
Entity argument;
switch (node)
{
case Sinf(var a): argument = a; break;
case Cosf(var a): argument = a; break;
case Tanf(var a): argument = a; break;
case Cotanf(var a): argument = a; break;
case Secantf(var a): argument = a; break;
case Cosecantf(var a): argument = a; break;
case Powf(_, Number.Integer):
case Sumf or Minusf or Mulf or Divf or Variable or Number:
continue;
default:
if (node.ContainsNode(x))
return null;
continue;
}
if (!argument.ContainsNode(x) || inU.ContainsKey(node))
continue;
if (!TreeAnalyzer.TryGetPolynomial(argument.Substitute(x, Number.Integer.One / u).InnerSimplified, u, out var read)
|| read.Keys.Any(k => k.Sign < 0 || k.CompareTo(EInteger.One) > 0) || !read.TryGetValue(EInteger.One, out var slope))
return null;
Entity linear = read.TryGetValue(EInteger.Zero, out var constant)
? constant.InnerSimplified + slope.InnerSimplified * u
: slope.InnerSimplified * u;
inU[node] = node switch
{
Sinf => MathS.Sin(linear),
Cosf => MathS.Cos(linear),
Tanf => MathS.Tan(linear),
Cotanf => MathS.Cotan(linear),
Secantf => MathS.Sec(linear),
Cosecantf => MathS.Cosec(linear),
_ => node,
};
names[node] = Variable.CreateUnique(expr, "f_rec" + names.Count);
}
if (inU.Count == 0)
return null;
// The rest one quotient, the way the rules read it: not `e + f/u` but `(e u + f)/u`.
var named = expr.Replace(node => names.TryGetValue(node, out var name) ? name : node);
var integrand = Functions.SingleQuotient.Combine(-named.Substitute(x, Number.Integer.One / u) / MathS.Sqr(u)).InnerSimplified;
foreach (var pair in names)
integrand = integrand.Substitute(pair.Value, inU[pair.Key]);
if (Integration.ComputeIndefiniteIntegral(integrand, u, integrateByParts) is not { } inTermsOfU)
return null;
return inTermsOfU.Substitute(u, Number.Integer.One / x);
}

/// <summary>
/// A root of a product beside a root of one of its factors, the product split:
/// <c>(P Q)^r</c> is <c>K P^r Q^r</c>, where <c>K = (P Q)^r/(P^r Q^r)</c> is constant wherever
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,9 @@ private static Entity Normalized(Entity expr, Entity.Variable x) =>
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;
// And one of `a + b/x` beside any rational function, under `x = 1/u`:
// `sin(c + d/x)/(e + f x)` is `-sin(c + d u)/(u (e u + f)) du`.
if ((answer = IndefiniteIntegralSolver.SolveByTheReciprocalOfAFunctionOfTheReciprocal(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,79 @@
//
// 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 trigonometric function of <c>a + b/x</c> beside a rational function, under <c>x = 1/u</c>:
/// <c>sin(c + d/x)/(e + f x)</c> is <c>-sin(c + d u)/(u (e u + f)) du</c>.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
/// <remarks>
/// Rubi's 4.1.12 <c>(a + b sin(c + d/x))^k/(e + f x)^m</c> were declined. Each answer is
/// differentiated back on both sides of 0.
/// </remarks>
[Trait("Area", "Calculus")]
public sealed class AFunctionOfTheReciprocalIntegralTest
{
private static void DifferentiatesBack(string integrand, double[] points, params (string, double)[] pins)
{
var integral = integrand.ToEntity().Integrate("x");
Assert.DoesNotContain("integral(", integral.Stringize());
Assert.DoesNotContain("NaN", integral.Stringize());

var derivative = integral.Substitute("C", 0).Differentiate("x");
Entity original = integrand.ToEntity();
foreach (var (name, value) in pins)
{
derivative = derivative.Substitute(name, value);
original = original.Substitute(name, value);
}

var compared = 0;
foreach (var at in points)
{
var got = derivative.Substitute("x", at).EvalNumerical();
var want = original.Substitute("x", at).EvalNumerical();
if (got.IsNaN || want.IsNaN)
continue;
compared++;
var difference = Math.Abs((double)(got - want).RealPart)
+ Math.Abs((double)(got - want).ImaginaryPart);
var scale = Math.Max(1.0, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart));
Assert.True(difference / scale < 1e-8,
$"d/dx of the antiderivative of {integrand} is {got} at x = {at}, "
+ $"where the integrand is {want}");
}
Assert.True(compared >= 3,
$"only {compared} of {points.Length} points were comparable for {integrand}");
}

[Theory]
[InlineData("sin(c+d/x)/(e+f*x)")]
[InlineData("cos(c+d/x)/(e+f*x)^2")]
[InlineData("(a+b*sin(c+d/x))/(e+f*x)^3")]
[InlineData("(a+b*sin(c+d/x))^2/(e+f*x)")]
public void AFunctionOfAPlusBOverX(string integrand)
{
var points = new[] { -1.4, -0.3, 0.8, 2.25 };
DifferentiatesBack(integrand, points, ("a", 1.0 / 3), ("b", 2), ("c", 1.5), ("d", 1.25), ("e", 2), ("f", 3));
}

[Theory]
[InlineData("(c*e+d*e*x)^(1/3)*sin(a+b/(c+d*x)^(1/3))")]
[InlineData("sin(a+b/sqrt(c+d*x))/(e+f*x)")]
public void AFunctionOfAPlusBOverARootOfALinear(string integrand)
{
var points = new[] { -0.3, 0.8, 2.25 };
DifferentiatesBack(integrand, points, ("a", 1.0 / 3), ("b", 2), ("c", 1.5), ("d", 1.25), ("e", 2), ("f", 3));
}
}
}
Loading