Skip to content

Commit 467d789

Browse files
Integrate a rational function over an inverse tangent's quadratic beside it by partial fractions (#1925)
* Integrate a rational function over an inverse tangent's quadratic beside it by partial fractions Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura * Record the rational functions over an inverse tangent's quadratic in BREAKING-CHANGES Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent 0d72aa4 commit 467d789

4 files changed

Lines changed: 171 additions & 0 deletions

File tree

‎BREAKING-CHANGES.md‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,20 @@ carried the discriminant's being real in its condition, and held nowhere when it
310310
| `"piecewise(2 * x provided not x = 0, 3 * x ^ 2 provided i - 1 < 0)".ToEntity().DomainCondition` | `false` | `not x = 0` |
311311
| `"1/(sqrt(a + i*a*x)*(c + d*x)^(3/2)*(a - i*a*x))".ToEntity().Integrate("x")` | `integral(...)`; on the unreleased master, an answer with no value at any point | 3,267 characters, defined |
312312

313+
### A rational function over the quadratic of an inverse tangent beside it is split
314+
315+
**Answers where there were none.** `x^4 atan(a x)/(c + a^2 c x^2)` ran out of time, and so did three more of Rubi's 5.3.4.
316+
The rational factor is now divided down and split over `c + a^2 c x^2` and the powers of x, and each part beside the
317+
inverse tangent is integrated on its own
318+
([#718](https://github.com/asc-community/AngouriMath/issues/718)).
319+
320+
| Input | Was (2.5.0) | Now |
321+
|---|---|---|
322+
| `"x^4*atan(a*x)/(c+a^2*c*x^2)".ToEntity().Integrate("x")` | `integral(...)` | 233 characters |
323+
| `"x^2*atan(a*x)/(c+a^2*c*x^2)".ToEntity().Integrate("x")` | `integral(...)` | 126 characters |
324+
| `"atan(a*x)/(x^2*(c+a^2*c*x^2))".ToEntity().Integrate("x")` | `integral(...)` | 612 characters |
325+
| `"atan(a*x)/(x^4*(c+a^2*c*x^2))".ToEntity().Integrate("x")` | `integral(...)` | 894 characters |
326+
313327
### A piecewise case whose condition is undefined is passed over
314328

315329
**A different answer.** A case whose predicate evaluates to NaN -- an order comparison of a number

‎Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs‎

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26445,6 +26445,101 @@ static void Add(Dictionary<int, ERational> into, int k, ERational value)
2644526445
return answer.Nodes.Any(node => node == MathS.NaN) ? null : answer;
2644626446
}
2644726447

26448+
/// <summary>
26449+
/// A rational function over the quadratic an inverse tangent's derivative is written over,
26450+
/// times a function of that inverse tangent, <c>x^4 atan(a x)/(c + a^2 c x^2)</c>, with the
26451+
/// rational function split into partial fractions over its written factors and each piece
26452+
/// beside the function its own question: <c>atan(a x)/(1 + a^2 x^2)</c> is
26453+
/// <c>atan(a x)^2/(2a)</c>, and the rest is a power of x beside the inverse tangent.
26454+
/// </summary>
26455+
/// <remarks>
26456+
/// The rules before it timed out on Rubi's 5.3.4 <c>x^4 atan(a x)/(c + a^2 c x^2)</c> and
26457+
/// <c>atan(a x)/(x^4 (c + a^2 c x^2))</c>, and declined <c>x^2 atan(a x)/(c + a^2 c x^2)</c>.
26458+
/// https://github.com/asc-community/AngouriMath/issues/718
26459+
/// </remarks>
26460+
internal static Entity? SolveARationalFunctionOverTheQuadraticOfAnArctangentBesideIt(Entity expr, Entity.Variable x)
26461+
{
26462+
if (!Integration.AnsweringTheQuestionAsked)
26463+
return null;
26464+
static IEnumerable<(Entity Factor, bool Below)> Factors(Entity e, bool below) => e switch
26465+
{
26466+
Mulf(var l, var r) => Factors(l, below).Concat(Factors(r, below)),
26467+
Divf(var l, var r) => Factors(l, below).Concat(Factors(r, !below)),
26468+
_ => new[] { (e, below) }
26469+
};
26470+
Entity? function = null;
26471+
Entity above = Number.Integer.One, below = Number.Integer.One;
26472+
foreach (var (factor, isBelow) in Factors(expr, false))
26473+
{
26474+
if (factor.Nodes.Any(node => node is Arctanf && node.ContainsNode(x)))
26475+
{
26476+
if (function is not null || isBelow)
26477+
return null;
26478+
function = factor;
26479+
}
26480+
else if (isBelow)
26481+
below *= factor;
26482+
else
26483+
above *= factor;
26484+
}
26485+
if (function is null || !below.ContainsNode(x))
26486+
return null;
26487+
var arctangents = function.Nodes.Where(node => node is Arctanf && node.ContainsNode(x)).Distinct().ToList();
26488+
if (arctangents.Count != 1 || arctangents[0] is not Arctanf(var argument)
26489+
|| !TreeAnalyzer.TryGetPolyLinear(argument, x, out var slope, out var intercept)
26490+
|| !Functions.PartialFractions.IsZeroAsAValue(intercept))
26491+
return null;
26492+
// The rest is a rational function in x, and one written factor below the bar is a
26493+
// multiple of 1 + (a x)^2.
26494+
var quadratic = 1 + MathS.Sqr(slope) * MathS.Sqr(x);
26495+
var (numerator, denominator) = Functions.SingleQuotient.Of(above / below);
26496+
if (!IsARationalFunction(numerator / denominator, x)
26497+
|| !Mulf.LinearChildren(denominator).Any(part => part.ContainsNode(x)
26498+
&& part.Substitute(x, Number.Integer.Zero).InnerSimplified is var atZero
26499+
&& !Functions.PartialFractions.IsZeroAsAValue(atZero)
26500+
&& Functions.PartialFractions.IsZeroAsAValue((part - atZero * quadratic).InnerSimplified)))
26501+
return null;
26502+
// An improper one divided first: `x^2/(c + a^2 c x^2)` is `1/(a^2 c) - 1/(a^2 (c + a^2 c x^2))`.
26503+
var pieces = new List<Entity>();
26504+
if (!IsProperByDegree(numerator, denominator, x))
26505+
{
26506+
if (TreeAnalyzer.PolynomialLongDivision(numerator, denominator, genericCase: true, inTermsOf: x) is not var (quotient, properPart))
26507+
return null;
26508+
pieces.Add(quotient);
26509+
// The generic case, as the integrator answers it: the remainder carries the
26510+
// condition that the leading coefficient is not zero.
26511+
properPart = properPart.Replace(node => node is Providedf(var expression, _) ? expression : node).InnerSimplified;
26512+
(numerator, denominator) = Functions.SingleQuotient.Of(properPart);
26513+
if (Functions.PartialFractions.IsZeroAsAValue(numerator))
26514+
numerator = Number.Integer.Zero;
26515+
}
26516+
Entity decomposition;
26517+
if (numerator == Number.Integer.Zero)
26518+
decomposition = Number.Integer.Zero;
26519+
else if (!Functions.PartialFractions.TrySplitOverWrittenFactors(numerator, denominator, x, out var split))
26520+
{
26521+
if (pieces.Count == 0)
26522+
return null;
26523+
decomposition = numerator / denominator;
26524+
}
26525+
else
26526+
decomposition = split;
26527+
// The split comes back as one sum over a factor free of x, `(0 + 1/(c x^2) + ...)/1`.
26528+
var (terms, divisor) = decomposition is Divf(var sum, var free) && !free.ContainsNode(x) ? (sum, free) : (decomposition, (Entity)Number.Integer.One);
26529+
pieces.AddRange(Sumf.LinearChildren(terms).Where(term => !Functions.PartialFractions.IsZeroAsAValue(term)).Select(term => term / divisor));
26530+
if (pieces.Count < 2)
26531+
return null;
26532+
Entity answer = Number.Integer.Zero;
26533+
foreach (var piece in pieces)
26534+
{
26535+
if (Integration.ComputeAsAQuestionOfItsOwn((piece * function).InnerSimplified, x, integrateByParts: true) is not { } integrated)
26536+
return null;
26537+
answer += integrated;
26538+
}
26539+
answer = answer.InnerSimplified;
26540+
return answer.Nodes.Any(node => node == MathS.NaN) ? null : answer;
26541+
}
26542+
2644826543
/// <summary>
2644926544
/// A rational function of a sine, or of a cosine, over a quadratic in it with a symbol
2645026545
/// among the coefficients: <c>sin(x)/(a + b sin(x) + c sin(x)^2)</c>, and the same with

‎Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -680,6 +680,9 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr)
680680
// A power of a sine, cosine or hyperbolic one over a binomial in a power of it, over the
681681
// binomial's roots, before the rules that spent the budget on `sinh(y)^4/(a + b sinh(y)^3)`.
682682
if ((answer = IndefiniteIntegralSolver.SolveAPowerOverABinomialInAPowerOfASineOverItsRoots(expr, x)) is { }) return answer;
683+
// A rational function over the quadratic of an inverse tangent beside it, split, before the
684+
// rules that timed out on `x^4 atan(a x)/(c + a^2 c x^2)`.
685+
if ((answer = IndefiniteIntegralSolver.SolveARationalFunctionOverTheQuadraticOfAnArctangentBesideIt(expr, x)) is { }) return answer;
683686
// A power of x times a power of a sum holding an inverse function of x^n, under t = x^n,
684687
// before the rules that timed out on `x^3 (a + b atanh(c x^2))^2`.
685688
if ((answer = IndefiniteIntegralSolver.SolveAPowerOfXTimesAFunctionOfAnotherUnderThatPower(expr, x)) is { }) return answer;
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
//
2+
// Copyright (c) 2019-2026 Angouri.
3+
// AngouriMath is licensed under MIT.
4+
// Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.
5+
// Website: https://am.angouri.org.
6+
//
7+
8+
using System;
9+
using AngouriMath.Extensions;
10+
using Xunit;
11+
12+
namespace AngouriMath.Tests.Calculus
13+
{
14+
/// <summary>
15+
/// A rational function over the quadratic an inverse tangent's derivative is written over, beside
16+
/// that inverse tangent, split into partial fractions: Rubi's 5.3.4 <c>x^4 atan(a x)/(c + a^2 c x^2)</c>
17+
/// and <c>atan(a x)/(x^4 (c + a^2 c x^2))</c>, which timed out.
18+
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
19+
/// </summary>
20+
[Trait("Area", "Calculus")]
21+
public sealed class ARationalFunctionOverTheQuadraticOfAnArctangentIntegralTest
22+
{
23+
private static void DifferentiatesBack(string integrand, params (string, double)[] pins)
24+
{
25+
var integral = integrand.ToEntity().Integrate("x");
26+
Assert.DoesNotContain("integral(", integral.Stringize());
27+
28+
var pinned = integral.Substitute("C", 0);
29+
Entity original = integrand.ToEntity();
30+
foreach (var (name, value) in pins)
31+
{
32+
pinned = pinned.Substitute(name, value);
33+
original = original.Substitute(name, value);
34+
}
35+
var derivative = pinned.Differentiate("x");
36+
foreach (var at in new[] { -2.3, -0.7, 0.4, 1.9 })
37+
{
38+
var got = derivative.Substitute("x", at).EvalNumerical();
39+
var want = original.Substitute("x", at).EvalNumerical();
40+
var difference = Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart);
41+
var scale = Math.Max(1.0, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart));
42+
Assert.True(difference / scale < 1e-8,
43+
$"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
44+
}
45+
}
46+
47+
// The quadratic's factor is q, not Rubi's c: the helper takes C, and c is too close.
48+
[Theory]
49+
[InlineData("x^4*atan(a*x)/(q+a^2*q*x^2)")]
50+
[InlineData("x^2*atan(a*x)/(q+a^2*q*x^2)")]
51+
[InlineData("atan(a*x)/(x^2*(q+a^2*q*x^2))")]
52+
[InlineData("atan(a*x)/(x^4*(q+a^2*q*x^2))")]
53+
public void IsAnsweredAndDifferentiatesBack(string integrand)
54+
{
55+
DifferentiatesBack(integrand, ("a", 1.3), ("q", 0.7));
56+
DifferentiatesBack(integrand, ("a", -0.4), ("q", -2));
57+
}
58+
}
59+
}

0 commit comments

Comments
 (0)