Skip to content

Commit 05695e6

Browse files
Integrate a rational function of the tangent beside roots of linears in it (#1970)
Under t = tan(z), dz = dt/(f (1 + t^2)), and a rational function of the tangent beside roots of linears in it is a rational function of t beside roots of linears in t. It is now integrated term by term over the partial fractions of its rational part, with the 1 + t^2 below the bar written as the linears of its roots, (1 + i t)(1 - i t). Each term is the roots over one power of a linear, which the rules for a root of a linear over a linear answer. Rubi's 4.3.4.2 (c + d tan(z))^(5/2) (A + B tan(z) + C tan(z)^2)/sqrt(a + b tan(z)) and six more of its rows, and three of 4.3.2.1, were past the budget. Part of #233. Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent 3d3a99b commit 05695e6

4 files changed

Lines changed: 250 additions & 0 deletions

File tree

‎BREAKING-CHANGES.md‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3189,6 +3189,25 @@ were read as numbers only. In `w = e^(i z)` the integral is a power of `w` besid
31893189
| `"(k*sec(c + d*x))^(-4 - n)*(a + i*a*tan(c + d*x))^n".ToEntity().Integrate("x")` | `integral(...)` | the integrand times powers of `e^(i (c + d x))` from `n - 4` to `n + 4`, each over its exponent |
31903190
| `"(a + i*a*tan(c + d*x))^n/(k*sec(c + d*x))^n".ToEntity().Integrate("x")` | `integral(...)` | the integrand over `i d n` |
31913191

3192+
### A rational function of the tangent beside roots of linears in it is integrated over the linears of `1 + tan^2`
3193+
3194+
**Answers where there were none.** `(c + d tan(z))^(5/2) (A + B tan(z) + C tan(z)^2)/sqrt(a + b tan(z))` and six more of
3195+
Rubi's 4.3.4.2, and `1/((a + i a tan(z))^3 (c + d tan(z))^(3/2))` and two more of 4.3.2.1, were declined. In
3196+
`t = tan(z)`, under the root of the quotient of the two linears or the root of the one, the `1 + t^2` that `dz` puts
3197+
below the bar is a quartic with symbols in every coefficient. It is now written as its linears `(1 + i t)(1 - i t)` and
3198+
the rational part split over them, so that each term is the roots over one power of a linear, which the rules for a
3199+
root of a linear over a linear answer; the sum is checked by its derivative in `t`. Only two roots of linears with
3200+
distinct roots, beside a polynomial or a linear below the bar, or one root below the bar beside a whole power of
3201+
`a ± i a tan(z)`. Their neighbours that the unreleased master answered come through it as well, shorter and sooner
3202+
([#718](https://github.com/asc-community/AngouriMath/issues/718)).
3203+
3204+
| Input | Was (2.5.0) | Now |
3205+
|---|---|---|
3206+
| `"(c + d*tan(g + h*x))^(5/2)*(A + B*tan(g + h*x) + C*tan(g + h*x)^2)/sqrt(a + b*tan(g + h*x))".ToEntity().Integrate("x")` | `integral(...)` | 4,315 characters |
3207+
| `"(A + B*tan(g + h*x) + C*tan(g + h*x)^2)/((a + b*tan(g + h*x))^(3/2)*(c + d*tan(g + h*x))^(3/2))".ToEntity().Integrate("x")` | `integral(...)` | 11,405 characters |
3208+
| `"1/((a + i*a*tan(g + h*x))^3*(c + d*tan(g + h*x))^(3/2))".ToEntity().Integrate("x")` | `integral(...)` | 6,498 characters |
3209+
| `"(c + d*tan(g + h*x))^(5/2)*(A + B*tan(g + h*x) + C*tan(g + h*x)^2)/(a + b*tan(g + h*x))^(5/2)".ToEntity().Integrate("x")` | `integral(...)`; 41,892 characters, in 10 s, on the unreleased master | 29,265 characters, in 1.4 s |
3210+
31923211
### A rational function of the tangent beside a power of `a + i a tan` is integrated in that sum
31933212

31943213
**Answers where there were none.** `(A + B tan(c + d x))/sqrt(a + i a tan(c + d x))` and the rest of

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

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33658,6 +33658,174 @@ static bool IsAWholeNumberOfSteps(ERational difference)
3365833658
return Integration.ComputeAsAQuestionOfItsOwn(expanded.Expand().InnerSimplified, x, integrateByParts);
3365933659
}
3366033660

33661+
/// <summary>
33662+
/// A rational function of <c>tan(z)</c> beside roots of linears in it, integrated in
33663+
/// <c>t = tan(z)</c> term by term over the partial fractions of its rational part, with the
33664+
/// <c>1 + t^2</c> that <c>dz = dt/(f (1 + t^2))</c> puts below the bar written as the linears
33665+
/// of its roots, <c>(1 + i t)(1 - i t)</c>: each term is the roots over one power of a linear,
33666+
/// which the rules for a root of a linear over a linear answer.
33667+
/// </summary>
33668+
/// <remarks>
33669+
/// <para>
33670+
/// Rubi's 4.3.4.2 <c>(c + d tan(z))^(5/2) (A + B tan(z) + C tan(z)^2)/sqrt(a + b tan(z))</c> and
33671+
/// 4.3.2.1 <c>1/((a + i a tan(z))^3 (c + d tan(z))^(3/2))</c> ran past the budget, as their
33672+
/// neighbours with smaller powers did not: in <c>t</c>, under the root of the quotient of the two
33673+
/// linears or the root of the one, <c>1 + t^2</c> is a quartic with symbols in every coefficient.
33674+
/// Over its linears the same integrands are three terms, or a few more, of a second or less each.
33675+
/// </para>
33676+
/// <para>
33677+
/// Only two roots of linears with distinct roots beside a polynomial or a linear below the
33678+
/// bar, or one root below the bar beside a whole power of <c>a +- i a tan(z)</c> below it, which
33679+
/// shares a root with <c>1 + t^2</c>; neither root a root of <c>t</c> or of <c>1 +- i t</c>, which
33680+
/// the rules for a root of the tangent and for the imaginary sum read, and below the bar linears
33681+
/// only. Two roots alone, and a root above the bar beside the imaginary sum, the substitution in
33682+
/// the root answers in as few terms and as soon, and are left to it. The decomposition is exact,
33683+
/// its coefficients constants in the symbols, and the sum is checked by its derivative in
33684+
/// <c>t</c> before it is returned: each term's answer is its own rule's.
33685+
/// https://github.com/asc-community/AngouriMath/issues/718
33686+
/// </para>
33687+
/// </remarks>
33688+
internal static Entity? SolveARationalFunctionOfTheTangentBesideRootsOfLinearsInIt(Entity expr, Entity.Variable x, bool integrateByParts)
33689+
{
33690+
Entity? argument = null;
33691+
foreach (var node in expr.Nodes)
33692+
{
33693+
if (!node.ContainsNode(x))
33694+
continue;
33695+
if (node is Sinf or Cosf or Secantf or Cosecantf)
33696+
return null;
33697+
if (node is not (Tanf or Cotanf))
33698+
continue;
33699+
var inner = node.DirectChildren.First();
33700+
if (argument is null)
33701+
argument = inner;
33702+
else if (argument != inner)
33703+
return null;
33704+
}
33705+
if (argument is null || !TreeAnalyzer.TryGetPolyLinear(argument, x, out var rate, out _)
33706+
|| rate.ContainsNode(x) || VanishesIdentically(rate))
33707+
return null;
33708+
var t = Variable.CreateUnique(expr, "t_tangent");
33709+
var inT = expr.Replace(node => node switch
33710+
{
33711+
Tanf(var y) when y == argument => t,
33712+
Cotanf(var y) when y == argument => 1 / t,
33713+
_ => node,
33714+
});
33715+
if (inT.ContainsNode(x))
33716+
return null;
33717+
33718+
// `A + c t` with `c = +-i A`, which vanishes at a root of `1 + t^2`.
33719+
static bool IsAnImaginarySum(Entity slope, Entity intercept)
33720+
{
33721+
var ratio = Functions.PartialFractions.Bare((slope / intercept).InnerSimplified);
33722+
if (ratio.Evaled is not Number.Complex)
33723+
ratio = Functions.PartialFractions.Bare(ratio.Simplify());
33724+
return ratio.Evaled == MathS.i.Evaled || ratio.Evaled == (-MathS.i).Evaled;
33725+
}
33726+
// The integrand read as a constant, roots of linears, whole powers of polynomials above the
33727+
// bar and of linears below it.
33728+
Entity constant = Number.Integer.One;
33729+
var above = new List<Entity>();
33730+
var below = new List<Entity>();
33731+
var degreeBelow = 0;
33732+
var imaginarySumBelow = false;
33733+
var roots = new List<(Entity Linear, Entity Slope, Entity Intercept, Number.Rational Power)>();
33734+
bool Read(Entity node, EInteger times)
33735+
{
33736+
switch (node)
33737+
{
33738+
case Mulf(var left, var right):
33739+
return Read(left, times) && Read(right, times);
33740+
case Divf(var dividend, var divisor):
33741+
return Read(dividend, times) && Read(divisor, times.Negate());
33742+
case Powf(var @base, Number.Integer whole) when @base.ContainsNode(t):
33743+
return Read(@base, times.Multiply(whole.EInteger));
33744+
case Powf(var @base, Number.Rational power) when @base.ContainsNode(t):
33745+
{
33746+
var exponent = power.ERational.Multiply(ERational.FromEInteger(times));
33747+
if (exponent.Denominator.Equals(EInteger.One))
33748+
return Read(@base, exponent.Numerator);
33749+
if (!TreeAnalyzer.TryGetPolyLinear(@base, t, out var slope, out var intercept)
33750+
|| slope.ContainsNode(t) || intercept.ContainsNode(t)
33751+
|| VanishesIdentically(slope) || VanishesIdentically(intercept) || IsAnImaginarySum(slope, intercept))
33752+
return false;
33753+
roots.Add((@base, slope, intercept, Number.Rational.Create(exponent)));
33754+
return true;
33755+
}
33756+
case var free when !free.ContainsNode(t):
33757+
constant *= MathS.Pow(free, Number.Integer.Create(times));
33758+
return true;
33759+
default:
33760+
if (!TreeAnalyzer.TryGetPolynomial(node, t, out var monomials)
33761+
|| monomials.Values.Any(coefficient => coefficient.ContainsNode(t)) || monomials.Keys.Any(degree => degree.Sign < 0)
33762+
|| !times.CanFitInInt32())
33763+
return false;
33764+
if (times.Sign > 0)
33765+
{
33766+
above.Add(times.Equals(EInteger.One) ? node : MathS.Pow(node, Number.Integer.Create(times)));
33767+
return true;
33768+
}
33769+
if (!TreeAnalyzer.TryGetPolyLinear(node, t, out var linearSlope, out var linearIntercept)
33770+
|| linearSlope.ContainsNode(t) || linearIntercept.ContainsNode(t) || VanishesIdentically(linearSlope))
33771+
return false;
33772+
var multiplicity = times.Negate();
33773+
// `(A + i A t)^k` is `A^k (1 + i t)^k`, a whole power: with the constant out
33774+
// in front the split is over numbers, and its terms' answers a few lines each,
33775+
// where with `A` in the linear they came to ten times the length.
33776+
if (!VanishesIdentically(linearIntercept) && IsAnImaginarySum(linearSlope, linearIntercept))
33777+
{
33778+
imaginarySumBelow = true;
33779+
constant /= MathS.Pow(linearIntercept, Number.Integer.Create(multiplicity));
33780+
node = (1 + Functions.PartialFractions.Bare((linearSlope / linearIntercept).Simplify()) * t).InnerSimplified;
33781+
}
33782+
below.Add(multiplicity.Equals(EInteger.One) ? node : MathS.Pow(node, Number.Integer.Create(multiplicity)));
33783+
degreeBelow += multiplicity.ToInt32Checked();
33784+
return true;
33785+
}
33786+
}
33787+
if (!Read(inT, EInteger.One) || degreeBelow > 12)
33788+
return null;
33789+
// Two roots beside a polynomial or a linear below, or one root below the bar beside a power
33790+
// of the imaginary sum below it: two roots alone over `1 + t^2`, and a root above the bar,
33791+
// the root's substitution answers as soon, and in the same few terms.
33792+
if (roots.Count == 2
33793+
? above.Count == 0 && below.Count == 0
33794+
|| VanishesIdentically(roots[0].Slope * roots[1].Intercept - roots[1].Slope * roots[0].Intercept)
33795+
: roots.Count != 1 || !imaginarySumBelow || roots[0].Power.ERational.Sign > 0)
33796+
return null;
33797+
33798+
// Divided by `1 + t^2` and the linears as written, and the proper rest split over the
33799+
// linears with `1 + t^2` written as its own two.
33800+
static Entity Product(IEnumerable<Entity> factors) =>
33801+
factors.Aggregate((Entity?)null, (all, factor) => all is null ? factor : all * factor) ?? Number.Integer.One;
33802+
var numerator = Product(above);
33803+
var written = Product(below.Append(1 + MathS.Sqr(t)));
33804+
var overTheLinears = Product(below.Append(1 + MathS.i * t).Append(1 - MathS.i * t));
33805+
if (OverEachLinear(numerator, written, overTheLinears, degreeBelow + 2, t) is not { } terms)
33806+
return null;
33807+
var ofTheRoots = Product(roots.Select(root => MathS.Pow(root.Linear, root.Power)));
33808+
Entity total = Number.Integer.Zero;
33809+
foreach (var term in terms)
33810+
{
33811+
if (term.Evaled is Number.Complex { IsZero: true })
33812+
continue;
33813+
if (Integration.ComputeAsAQuestionOfItsOwn((term * ofTheRoots).InnerSimplified, t, integrateByParts) is not { } integrated
33814+
|| integrated.Nodes.Any(node => node == MathS.NaN))
33815+
return null;
33816+
total += integrated;
33817+
}
33818+
// Checked by its derivative, and where the intervals cannot tell -- the arctangent of a
33819+
// complex argument that the answer to `(c + d t)^(5/2)/((a + b t)^(5/2) (1 + i t))` holds,
33820+
// right to ninety digits at every point -- beside two roots refused only where it
33821+
// differs at a point where the integrand, then real, is.
33822+
var integrandInT = numerator / written * ofTheRoots;
33823+
if (!Functions.PartialFractions.DerivativeHoldsAtSampledPoints(total, integrandInT, t)
33824+
&& (roots.Count == 1 || Functions.PartialFractions.DerivativeDiffersWhereReal(total, integrandInT, t)))
33825+
return null;
33826+
return (constant / rate * total).Substitute(t, MathS.Tan(argument));
33827+
}
33828+
3366133829
/// <summary>
3366233830
/// A rational function of <c>tan(z)</c> beside a power of <c>S = a + c tan(z)</c> with
3366333831
/// <c>c = ±i a</c>, below the bar or not whole, integrated in <c>S</c>: <c>tan(z)</c> is

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -819,6 +819,10 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr)
819819
// And an exponential of a hyperbolic sine or cosine of a linear beside a function of it
820820
// over its derivative, under u = twice that sine or cosine, which arrive as exponentials.
821821
if ((answer = IndefiniteIntegralSolver.SolveAnExponentialOfAHyperbolicFunctionBesideItsDerivative(expr, x, integrateByParts)) is { }) return answer;
822+
// A rational function of the tangent beside two roots of linears in it, or one beside a
823+
// whole power of a + i a tan(z) below the bar, over the linears of 1 + tan^2: in the root
824+
// of the linears' quotient 1 + tan^2 is a symbolic quartic, and a search past the budget.
825+
if ((answer = IndefiniteIntegralSolver.SolveARationalFunctionOfTheTangentBesideRootsOfLinearsInIt(expr, x, integrateByParts)) is { }) return answer;
822826
// `A + i A tan(z)` is `A e^(i z)/cos(z)`, which beside a polynomial is a shape the
823827
// closed rules answer, where the imaginary unit in the coefficient is read by none.
824828
// The same identity beside a power of the secant, where the powers are not whole and add
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 of the tangent beside two roots of linears in it, or one root below the
16+
/// bar beside a whole power of <c>a + i a tan</c>, integrated in <c>t = tan(g + h x)</c> over the
17+
/// partial fractions of its rational part with <c>1 + t^2</c> written as <c>(1 + i t)(1 - i t)</c>.
18+
/// In the root of the linears' quotient <c>1 + t^2</c> is a symbolic quartic, and every one of
19+
/// these ran past the budget. Rubi's 4.3.4.2 and 4.3.2.1.
20+
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
21+
/// </summary>
22+
[Trait("Area", "Calculus")]
23+
public sealed class ARationalFunctionOfTheTangentBesideRootsOfLinearsInItIntegralTest
24+
{
25+
[Theory]
26+
[InlineData("(c + d*tan(g + h*x))^(5/2)*(A + B*tan(g + h*x) + C*tan(g + h*x)^2)/sqrt(a + b*tan(g + h*x))")]
27+
[InlineData("(c + d*tan(g + h*x))^(5/2)*(A + B*tan(g + h*x) + C*tan(g + h*x)^2)/(a + b*tan(g + h*x))^(3/2)")]
28+
[InlineData("(a + b*tan(g + h*x))^(5/2)*(A + B*tan(g + h*x) + C*tan(g + h*x)^2)/(c + d*tan(g + h*x))^(5/2)")]
29+
[InlineData("(A + B*tan(g + h*x) + C*tan(g + h*x)^2)/((a + b*tan(g + h*x))^(3/2)*(c + d*tan(g + h*x))^(3/2))")]
30+
[InlineData("(A + B*tan(g + h*x) + C*tan(g + h*x)^2)/((a + b*tan(g + h*x))^(3/2)*(c + d*tan(g + h*x))^(5/2))")]
31+
[InlineData("1/((a + i*a*tan(g + h*x))^3*(c + d*tan(g + h*x))^(3/2))")]
32+
[InlineData("1/((a + i*a*tan(g + h*x))^2*(c + d*tan(g + h*x))^(5/2))")]
33+
[InlineData("1/((a + i*a*tan(g + h*x))^3*(c + d*tan(g + h*x))^(5/2))")]
34+
public void OverTheLinearsOfOnePlusTheTangentSquared(string integrand)
35+
{
36+
var integral = integrand.ToEntity().Integrate("x");
37+
var text = integral.Stringize();
38+
Assert.DoesNotContain("integral(", text);
39+
Assert.True(text.Length < 30000, $"{text.Length} characters of answer for {integrand}");
40+
Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.7).Substitute("c", 2.1).Substitute("d", 0.4)
41+
.Substitute("A", 0.9).Substitute("B", 1.7).Substitute("C", 0.6).Substitute("g", 0.2).Substitute("h", 1.1);
42+
var derivative = Pinned(integral).Differentiate("x");
43+
var original = Pinned(integrand.ToEntity());
44+
var compared = 0;
45+
foreach (var at in new[] { -0.9, -0.5, 0.1, 0.4, 0.7, 1.0 })
46+
{
47+
var want = original.Substitute("x", at).EvalNumerical();
48+
var got = derivative.Substitute("x", at).EvalNumerical();
49+
if (want.IsNaN)
50+
continue;
51+
compared++;
52+
Assert.True(Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart)
53+
< 1e-9 * Math.Max(1, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart)),
54+
$"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
55+
}
56+
Assert.True(compared >= 5, $"only {compared} points could be compared for {integrand}");
57+
}
58+
}
59+
}

0 commit comments

Comments
 (0)