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
19 changes: 19 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6593,6 +6593,25 @@ leaves are read as quotients of the sine and the cosine
| `"1/(a+b*asin(c*x))".Integrate("x")` | `integral(1 / (a + b * arcsin(c * x)), x)` | `Si` and `Ci` of `(a + b arcsin(c x))/b` |
| `"x/((c+a^2*c*x^2)^2*atan(a*x))".Integrate("x")` | `integral(x / ((c + a ^ 2 * c * x ^ 2) ^ 2 * arctan(a * x)), x)` | `1/2 * Si(2 * arctan(a * x)) / (a ^ 2 * c ^ 2) + C` |

### A power of x over a power of a quadratic times an inverse trigonometric function is integrated by parts against one term

**Answers where there were none.** `x^3 (a + b F(c x))/(d + e x^2)^3` for `F` the inverse sine, tangent, secant and
cosecant, Rubi's 5.1.4, 5.3.4, 5.5.1 and 5.6.1, was declined. By parts against the integrator's own antiderivative of
`x^3/(d + e x^2)^3`, a piecewise on `e = 0`, spent the budget on every arm. Where `m + 2p + 3 = 0`,
`x^m (d + e x^2)^p` has the one-term antiderivative `v = x^(m + 1) (d + e x^2)^(p + 1)/(d (m + 1))`, Rubi's, which
vanishes at 0, and the integral is now `v (a + b F(c x)) - b integral v F(c x)'` with `F(c x)'` algebraic.
`v' = x^m (d + e x^2)^p` is an identity of rational functions, so the parts hold wherever the integrand is defined,
on any branch of `F`. Only
for a whole `p` of `-3` or less, and not where `d + e x^2` is a multiple of the radicand `F` holds, which the
substitution under `F` answers ([#233](https://github.com/asc-community/AngouriMath/issues/233)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"x^3*(a + b*arcsin(c*x))/(d + k*x^2)^3".ToEntity().Integrate("x")` | `integral(...)` | 6,013 characters |
| `"x^3*(a + b*arctan(c*x))/(d + k*x^2)^3".ToEntity().Integrate("x")` | `integral(...)` | 4,066 characters |
| `"x^3*(a + b*arcsec(c*x))/(d + k*x^2)^3".ToEntity().Integrate("x")` | `integral(...)` | 1,772 characters |
| `"x^5*(a + b*arctan(c*x))/(d + k*x^2)^4".ToEntity().Integrate("x")` | `integral(...)` | 6,176 characters |

### The arcsecant beside a root of `x^2 - 1` is integrated right below `-1` too

**Answers where there were none.** Under `x = sec(u)`, the substitution that undoes the arcsecant,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32123,6 +32123,134 @@ Piecewise piecewise when piecewise.Cases.Any() => piecewise.Cases.First().Expres
return (alpha / (p0 * slope) * product).InnerSimplified;
}

/// <summary>
/// <c>x^m (d + e x^2)^p (a + b F(c x))</c>, <c>F</c> an inverse trigonometric function and
/// <c>m + 2p + 3 = 0</c>, by parts against the antiderivative of <c>x^m (d + e x^2)^p</c> that is
/// one term, <c>v = x^(m + 1) (d + e x^2)^(p + 1)/(d (m + 1))</c>:
/// <c>integral = v (a + b F(c x)) - b integral v F(c x)'</c>, and <c>F(c x)'</c> is algebraic.
/// </summary>
/// <remarks>
/// <para>
/// <c>v' = x^m (d + e x^2)^p ((m + 1)(d + e x^2) + 2 (p + 1) e x^2)/(d (m + 1))</c>, and the bracket
/// is <c>(m + 1) d + (m + 2p + 3) e x^2</c>, which is <c>(m + 1) d</c> for every <c>e</c>: an
/// identity of rational functions, so the parts are exact wherever the integrand is defined, on
/// any branch of <c>F</c>. It is Rubi's choice of <c>v</c>, and the one that vanishes at
/// <c>x = 0</c>.
/// </para>
/// <para>
/// The integrator's own antiderivative of <c>x^3/(d + e x^2)^3</c> is a piecewise on <c>e = 0</c>,
/// and by parts against it spent the budget on every arm: Rubi's 5.1.4, 5.3.4, 5.5.1 and 5.6.1
/// <c>x^3 (a + b F(c x))/(d + e x^2)^3</c>, for <c>F</c> the inverse sine, tangent, secant and
/// cosecant, ran out of time, where the remainder against this <c>v</c> is answered in under a
/// second.
/// </para>
/// <para>
/// Narrow on purpose. <c>p</c> whole and at most <c>-3</c>, so that <c>v</c> is rational and
/// <c>m = -2p - 3</c> at least 3: <c>m = 1</c>, <c>x/(d + e x^2)^2</c>, is answered by parts as it
/// was, and a half-odd <c>p</c> is answered as it was beside the inverse sine and tangent and is
/// elliptic beside the secant. And not for <c>e = +-c^2 d</c>, the quadratic a multiple of
/// <c>F</c>'s own radicand, which the substitution under <c>F</c> answers.
/// https://github.com/asc-community/AngouriMath/issues/233
/// </para>
/// </remarks>
internal static Entity? SolveAPowerOfXOverAPowerOfAQuadraticTimesAnInverseTrigonometricFunctionByParts(Entity expr, Entity.Variable x, bool integrateByParts)
{
if (!Integration.AnsweringTheQuestionAsked || integratingAPowerOverAQuadraticBesideAnInverse)
return null;
// The one inverse trigonometric function, of c x.
Entity? function = null;
foreach (var node in expr.Nodes)
if ((node is Arcsinf or Arccosf or Arctanf or Arccotanf or Arcsecantf or Arccosecantf) && node.ContainsNode(x))
{
if (function is not null && function != node)
return null;
function = node;
}
var argument = function switch
{
Arcsinf(var u) => u, Arccosf(var u) => u, Arctanf(var u) => u,
Arccotanf(var u) => u, Arcsecantf(var u) => u, Arccosecantf(var u) => u,
_ => null
};
if (function is null || argument is null || !TreeAnalyzer.TryGetPolyLinear(argument, x, out var rate, out var offset)
|| rate.ContainsNode(x) || !Functions.PartialFractions.IsZeroAsAValue(offset) || Functions.PartialFractions.IsZeroAsAValue(rate))
return null;
// Above the bar one factor linear in it, a + b F, and the rest; below, nothing with it.
var (numerator, denominator) = Functions.SingleQuotient.Of(expr);
if (denominator.ContainsNode(function))
return null;
Entity? linear = null;
Entity rest = Number.Integer.One;
foreach (var factor in Mulf.LinearChildren(numerator))
{
if (!factor.ContainsNode(function))
rest *= factor;
else if (linear is null)
linear = factor;
else
return null;
}
var t = Variable.CreateUnique(expr, "t_inv");
if (linear is null || !TreeAnalyzer.TryGetPolyLinear(linear.Substitute(function, t), t, out var slope, out var intercept)
|| slope.ContainsNode(x) || intercept.ContainsNode(x) || Functions.PartialFractions.IsZeroAsAValue(slope))
return null;
// The rest over the denominator as K x^m Q^p, Q = d + e x^2.
Entity constant = Number.Integer.One;
Entity? quadratic = null;
var m = 0;
var p = 0;
foreach (var (side, sign) in new[] { (rest, 1), (denominator, -1) })
foreach (var factor in Mulf.LinearChildren(side))
{
if (!factor.ContainsNode(x))
{
constant = sign > 0 ? constant * factor : constant / factor;
continue;
}
var (@base, power) = factor is Powf(var b0, Number.Integer { EInteger: var e0 }) && e0.CanFitInInt32()
? (b0, e0.ToInt32Unchecked()) : (factor, 1);
if (@base == x)
m += sign * power;
else if (quadratic is null || quadratic == @base)
{
quadratic = @base;
p += sign * power;
}
else
return null;
}
if (quadratic is null || p > -3 || m != -2 * p - 3
|| !TreeAnalyzer.TryGetPolynomial(quadratic, x, out var read)
|| read.Keys.Any(key => !key.IsZero && !key.Equals(EInteger.FromInt32(2)))
|| !read.TryGetValue(EInteger.Zero, out var d) || !read.TryGetValue(EInteger.FromInt32(2), out var e)
|| d.ContainsNode(x) || e.ContainsNode(x)
|| Functions.PartialFractions.IsZeroAsAValue(d) || Functions.PartialFractions.IsZeroAsAValue(e)
|| Functions.PartialFractions.IsZeroAsAValue((e - rate * rate * d).Expand())
|| Functions.PartialFractions.IsZeroAsAValue((e + rate * rate * d).Expand()))
return null;
var v = constant * MathS.Pow(x, Number.Integer.Create(m + 1)) * MathS.Pow(quadratic, Number.Integer.Create(p + 1))
/ (d * (m + 1));
// The remainder holds no inverse function, so it is not this integrand again; the flag is for
// whatever its own search writes.
var remainder = (slope * v * function.Differentiate(x)).InnerSimplified;
Entity? integral;
integratingAPowerOverAQuadraticBesideAnInverse = true;
try
{
integral = Integration.ComputeAsAQuestionOfItsOwn(remainder, x, integrateByParts);
}
finally
{
integratingAPowerOverAQuadraticBesideAnInverse = false;
}
if (integral is null)
return null;
var answer = v * linear - integral;
return Functions.PartialFractions.DerivativeDiffersWhereReal(answer, expr, x) ? null : answer;
}

[System.ThreadStatic] private static bool integratingAPowerOverAQuadraticBesideAnInverse;

/// <summary>
/// <c>x^m (a + b atan(c x))^p</c> over a power of <c>d (1 +- i c x)</c>, under <c>x = tan(u)/c</c>, where
/// <c>1 + i tan(u)</c> is <c>e^(i u)/cos(u)</c>: the integrand in <c>u</c> is
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,10 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr)
// A rational function times the logarithm of a nested product of powers of linears, by parts,
// its logarithm's derivative read off as written: `ln(e (f (a + b x)^p (c + d x)^q)^r)`.
if ((answer = IndefiniteIntegralSolver.SolveARationalFunctionTimesALogarithmOfPowersOfLinears(expr, x, integrateByParts)) is { }) return answer;
// x^m (d + e x^2)^p (a + b F(c x)) with m + 2p + 3 = 0 and F an inverse trigonometric function, by
// parts against v = x^(m + 1) (d + e x^2)^(p + 1)/(d (m + 1)), before by parts against the
// integrator's piecewise v timed out on `x^3 (a + b asin(c x))/(d + e x^2)^3`.
if ((answer = IndefiniteIntegralSolver.SolveAPowerOfXOverAPowerOfAQuadraticTimesAnInverseTrigonometricFunctionByParts(expr, x, integrateByParts)) is { }) return answer;
// A function of an inverse of something not linear times its derivative up to a factor
// constant though written with x: `atan(c x/sqrt(a - c^2 x^2))^2/sqrt(d - c^2 d x^2/a)`. Early,
// since it fires only where that factor holds x, and the rules before it spent 28 seconds on
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//
// 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 System.Globalization;
using System.Linq;
using AngouriMath.Extensions;
using Xunit;

namespace AngouriMath.Tests.Calculus
{
/// <summary>
/// <c>x^m (d + k x^2)^p (a + b F(c x))</c> with <c>m + 2p + 3 = 0</c> and <c>F</c> an inverse
/// trigonometric function, by parts against <c>v = x^(m + 1) (d + k x^2)^(p + 1)/(d (m + 1))</c>.
/// By parts against the integrator's own antiderivative, a piecewise on <c>k = 0</c>, ran out of
/// time. Rubi's 5.1.4, 5.3.4, 5.5.1 and 5.6.1.
/// <a href="https://github.com/asc-community/AngouriMath/issues/233">#233</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class APowerOfXOverAPowerOfAQuadraticTimesAnInverseTrigonometricFunctionByPartsIntegralTest
{
private const string Inside = "-1.3 -0.9 -0.4 0.3 0.8 1.2";
private const string Outside = "-4.1 -2.9 -2.1 2.1 2.9 4.1";

[Theory]
[InlineData("x^3*(a + b*arcsin(c*x))/(d + k*x^2)^3", Inside)]
[InlineData("x^3*(a + b*arctan(c*x))/(d + k*x^2)^3", Inside)]
[InlineData("x^3*(a + b*arcsec(c*x))/(d + k*x^2)^3", Outside)]
[InlineData("x^3*(a + b*arccsc(c*x))/(d + k*x^2)^3", Outside)]
[InlineData("x^3*(a + b*arccos(c*x))/(d + k*x^2)^3", Inside)]
[InlineData("x^3*(a + b*arccot(c*x))/(d + k*x^2)^3", Inside)]
[InlineData("x^3*arcsin(c*x)/(d + k*x^2)^3", Inside)]
[InlineData("x^5*(a + b*arctan(c*x))/(d + k*x^2)^4", Inside)]
public void ByPartsAgainstTheOneTermAntiderivative(string integrand, string points)
{
var integral = integrand.ToEntity().Integrate("x");
var text = integral.Stringize();
Assert.DoesNotContain("integral(", text);
Assert.True(text.Length < 20000, $"{text.Length} characters of answer for {integrand}");
foreach (var (d, k) in new[] { (2.1, 0.9), (-1.7, 1.3), (1.9, -0.8) })
{
Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.7).Substitute("c", 0.6)
.Substitute("d", d).Substitute("k", k);
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
var original = Pinned(integrand.ToEntity());
var compared = 0;
foreach (var at in points.Split(' ').Select(point => double.Parse(point, CultureInfo.InvariantCulture)))
{
var want = original.Substitute("x", at).EvalNumerical();
var got = derivative.Substitute("x", at).EvalNumerical();
if (want.IsNaN)
continue;
compared++;
Assert.True(Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart)
< 1e-9 * Math.Max(1, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart)),
$"d/dx of the antiderivative of {integrand} is {got} at x = {at}, d = {d}, k = {k}, where the integrand is {want}");
}
Assert.True(compared >= 5, $"only {compared} points could be compared for {integrand} at d = {d}, k = {k}");
}
}
}
}
Loading