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
14 changes: 14 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,20 @@ and 7.3.2. `x^m F(x^n)^p`, with `F` a sum holding an inverse tangent, inverse hy
| `"(a+b*atanh(c*x^(3/2)))^2/x^4".ToEntity().Integrate("x")` | `integral(...)` | 330 characters |
| `"(a+b*ln(c*(d+k/x^(2/3))^n))^2/x^5".ToEntity().Integrate("x")` | `integral(...)` | 2,034 characters |

### A power of x times an inverse tangent and the logarithm of its quadratic is integrated by parts

**Answers where there were none.** `x^4 (a + b atan(c x)) (d + e ln(1 + c^2 x^2))` ran out of time, and so did three more
of Rubi's 5.3.4. It is now integrated by parts. For an odd power of x, the two remainders that have no elementary
antiderivative on their own are paired into the derivative of a product of the inverse tangent and the logarithm
([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"x*(a+b*atan(c*x))*(d+pe*ln(1+c^2*x^2))".ToEntity().Integrate("x")` | `integral(...)` | 1,742 characters |
| `"x^2*(a+b*atan(c*x))*(d+pe*ln(1+c^2*x^2))".ToEntity().Integrate("x")` | `integral(...)` | 1,710 characters |
| `"x^3*(a+b*atan(c*x))*(d+pe*ln(1+c^2*x^2))".ToEntity().Integrate("x")` | `integral(...)` | 3,470 characters |
| `"x^4*(a+b*atan(c*x))*(d+pe*ln(1+c^2*x^2))".ToEntity().Integrate("x")` | `integral(...)` | 5,027 characters |

### Zero times a power with a non-real exponent is zero where the power is defined

**A value where there was NaN.** `0 b^i` simplified to NaN, and so did `b^i - b^i`. That in turn made `x b^i`'s
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26540,6 +26540,113 @@ static void Add(Dictionary<int, ERational> into, int k, ERational value)
return answer.Nodes.Any(node => node == MathS.NaN) ? null : answer;
}

/// <summary>
/// A whole power of x times <c>F = a + b atan(c x)</c> times <c>G = d + e ln(1 + c^2 x^2)</c>,
/// by parts on the power: with <c>q = c^2</c> and <c>n = k + 1</c>, the remainder is
/// <c>(1/n) int x^n (b c G + 2 e q x F)/(1 + q x^2)</c>, and <c>x^n/(1 + q x^2)</c> is a
/// polynomial plus <c>(-1/q)^m x^(n - 2m)/(1 + q x^2)</c>, <c>m = floor(n/2)</c>.
/// </summary>
/// <remarks>
/// For an odd <c>k</c> the two remainders over the quadratic, <c>G/(1 + q x^2)</c> and
/// <c>x F/(1 + q x^2)</c>, have no elementary antiderivative apiece and come with the same
/// factor, and together they are <c>(F G)'/(b c)</c> -- <c>F' = b c/(1 + q x^2)</c>,
/// <c>G' = 2 e q x/(1 + q x^2)</c> -- so their integral is that factor times <c>F G</c>.
/// Integration by parts as the rules after it apply it never pairs them, and Rubi's 5.3.4
/// <c>x^k (a + b atan(c x))(d + e ln(1 + c^2 x^2))</c>, <c>k = 1</c> to <c>4</c>, timed out.
/// https://github.com/asc-community/AngouriMath/issues/718
/// </remarks>
internal static Entity? SolveAPowerOfXTimesAnArctangentAndTheLogarithmOfItsQuadratic(Entity expr, Entity.Variable x)
{
if (!Integration.AnsweringTheQuestionAsked)
return null;
var arctangents = expr.Nodes.Where(node => node is Arctanf && node.ContainsNode(x)).Distinct().ToList();
if (arctangents.Count != 1 || arctangents[0] is not Arctanf(var argument)
|| !TreeAnalyzer.TryGetPolyLinear(argument, x, out var c, out var intercept) || !Functions.PartialFractions.IsZeroAsAValue(intercept))
return null;
var arctangent = arctangents[0];
var q = MathS.Sqr(c).InnerSimplified;
var quadratic = 1 + q * MathS.Sqr(x);
// The logarithm of 1 + q x^2, as `ln` or `log` writes it.
var logarithms = expr.Nodes.Where(node => node is Logf(var @base, var of) && @base == MathS.e && of.ContainsNode(x)
&& Functions.PartialFractions.IsZeroAsAValue((of - quadratic).InnerSimplified)).Distinct().ToList();
if (logarithms.Count != 1)
return null;
var logarithm = logarithms[0];
static IEnumerable<Entity> Factors(Entity e) => e is Mulf(var l, var r) ? Factors(l).Concat(Factors(r)) : new[] { e };
Entity? f = null, g = null;
var k = 0;
Entity constant = Number.Integer.One;
foreach (var factor in Factors(expr))
{
if (!factor.ContainsNode(x))
constant *= factor;
else if (factor == x)
k += 1;
else if (factor is Powf(var b0, Number.Integer { EInteger: var power }) && b0 == x && power.Sign > 0 && power.CanFitInInt32())
k += power.ToInt32Unchecked();
else if (f is null && factor.ContainsNode(arctangent) && !factor.ContainsNode(logarithm))
f = factor;
else if (g is null && factor.ContainsNode(logarithm) && !factor.ContainsNode(arctangent))
g = factor;
else
return null;
}
if (f is null || g is null || k > 20)
return null;
// F = a + b atan(c x) and G = d + e ln(1 + q x^2), each linear in its function.
var s = Variable.CreateUnique(expr, "s_al");
if (f.Substitute(arctangent, s).ContainsNode(x) || g.Substitute(logarithm, s).ContainsNode(x)
|| !TreeAnalyzer.TryGetPolyLinear(f.Substitute(arctangent, s), s, out var b, out _)
|| !TreeAnalyzer.TryGetPolyLinear(g.Substitute(logarithm, s), s, out var e, out _)
|| Functions.PartialFractions.IsZeroAsAValue(b) || Functions.PartialFractions.IsZeroAsAValue(e))
return null;
// x^n/(1 + q x^2): the polynomial and the factor of x^(n - 2m)/(1 + q x^2).
(Entity Polynomial, Entity Remainder) Divided(int n)
{
Entity polynomial = Number.Integer.Zero;
Entity factor = Number.Integer.One;
for (var j = 0; j < n / 2; j++)
{
// x^0 written as 1: the power brings `provided not x = 0` with it.
var degree = n - 2 - 2 * j;
polynomial += factor / q * (degree == 0 ? Number.Integer.One : degree == 1 ? x : MathS.Pow(x, Number.Integer.Create(degree)));
factor = -factor / q;
}
return (polynomial, factor);
}
var n1 = k + 1;
var (p1, r1) = Divided(n1);
var (p2, r2) = Divided(n1 + 1);
// A monomial at a time: `(x^2/q - 1/q^2) atan(c x)` asked whole was declined.
Entity? Asked(Entity polynomial, Entity beside)
{
Entity sum = Number.Integer.Zero;
foreach (var term in Sumf.LinearChildren(polynomial).Select(monomial => (monomial * beside).InnerSimplified))
{
if (Functions.PartialFractions.IsZeroAsAValue(term))
continue;
if (Integration.ComputeAsAQuestionOfItsOwn(term, x, integrateByParts: true) is not { } integrated)
return null;
sum += integrated;
}
return sum;
}
if (Asked(p1, g) is not { } p1g || Asked(p2, f) is not { } p2f)
return null;
Entity remainders;
if (n1 % 2 == 0)
// r1 = r2: (r/n) int (b c G + 2 e q x F)/(1 + q x^2) = (r/n) F G.
remainders = r1 * f * g;
else
{
if (Asked(x, g / quadratic) is not { } xg || Asked(Number.Integer.One, f / quadratic) is not { } fq)
return null;
remainders = b * c * r1 * xg + 2 * e * q * r2 * fq;
}
var answer = (constant * (MathS.Pow(x, Number.Integer.Create(n1)) * f * g - b * c * p1g - 2 * e * q * p2f - remainders) / n1).InnerSimplified;
return answer.Nodes.Any(node => node == MathS.NaN) ? null : answer;
}

/// <summary>
/// A rational function of a sine, or of a cosine, over a quadratic in it with a symbol
/// among the coefficients: <c>sin(x)/(a + b sin(x) + c sin(x)^2)</c>, and the same with
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,9 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr)
// A power of a sine, cosine or hyperbolic one over a binomial in a power of it, over the
// binomial's roots, before the rules that spent the budget on `sinh(y)^4/(a + b sinh(y)^3)`.
if ((answer = IndefiniteIntegralSolver.SolveAPowerOverABinomialInAPowerOfASineOverItsRoots(expr, x)) is { }) return answer;
// A power of x times an inverse tangent and the logarithm of its quadratic, by parts with the
// two remainders that have no antiderivative apiece paired, before the rules that timed out.
if ((answer = IndefiniteIntegralSolver.SolveAPowerOfXTimesAnArctangentAndTheLogarithmOfItsQuadratic(expr, x)) is { }) return answer;
// A rational function over the quadratic of an inverse tangent beside it, split, before the
// rules that timed out on `x^4 atan(a x)/(c + a^2 c x^2)`.
if ((answer = IndefiniteIntegralSolver.SolveARationalFunctionOverTheQuadraticOfAnArctangentBesideIt(expr, x)) is { }) return answer;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//
// 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 power of x times an inverse tangent and the logarithm of its quadratic, by parts with the two
/// remainders that have no antiderivative apiece paired: Rubi's 5.3.4
/// <c>x^k (a + b atan(c x))(d + e ln(1 + c^2 x^2))</c>, which timed out.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class APowerOfXTimesAnArctangentAndTheLogarithmOfItsQuadraticIntegralTest
{
private static void DifferentiatesBack(string integrand, params (string, double)[] pins)
{
var integral = integrand.ToEntity().Integrate("x");
Assert.DoesNotContain("integral(", integral.Stringize());

var pinned = integral.Substitute("C", 0);
Entity original = integrand.ToEntity();
foreach (var (name, value) in pins)
{
pinned = pinned.Substitute(name, value);
original = original.Substitute(name, value);
}
var derivative = pinned.Differentiate("x");
foreach (var at in new[] { -2.3, -0.7, 0.4, 1.9 })
{
var got = derivative.Substitute("x", at).EvalNumerical();
var want = original.Substitute("x", at).EvalNumerical();
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}");
}
}

[Theory]
[InlineData("x^4*(a+b*atan(c*x))*(d+k*ln(1+c^2*x^2))")]
[InlineData("x^3*(a+b*atan(c*x))*(d+k*ln(1+c^2*x^2))")]
[InlineData("x*(a+b*atan(c*x))*(d+k*ln(1+c^2*x^2))")]
[InlineData("(a+b*atan(c*x))*(d+k*ln(1+c^2*x^2))")]
public void IsAnsweredAndDifferentiatesBack(string integrand)
{
DifferentiatesBack(integrand, ("a", 1.3), ("b", 0.7), ("c", 0.3), ("d", 2), ("k", -0.4));
DifferentiatesBack(integrand, ("a", -2), ("b", 1.5), ("c", -1.2), ("d", 0.5), ("k", 3));
}
}
}
Loading