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 @@ -3189,6 +3189,25 @@ were read as numbers only. In `w = e^(i z)` the integral is a power of `w` besid
| `"(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 |
| `"(a + i*a*tan(c + d*x))^n/(k*sec(c + d*x))^n".ToEntity().Integrate("x")` | `integral(...)` | the integrand over `i d n` |

### A rational function of the tangent beside roots of linears in it is integrated over the linears of `1 + tan^2`

**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
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
`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
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
the rational part split over them, so that each term is the roots over one power of a linear, which the rules for a
root of a linear over a linear answer; the sum is checked by its derivative in `t`. Only two roots of linears with
distinct roots, beside a polynomial or a linear below the bar, or one root below the bar beside a whole power of
`a ± i a tan(z)`. Their neighbours that the unreleased master answered come through it as well, shorter and sooner
([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"(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 |
| `"(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 |
| `"1/((a + i*a*tan(g + h*x))^3*(c + d*tan(g + h*x))^(3/2))".ToEntity().Integrate("x")` | `integral(...)` | 6,498 characters |
| `"(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 |

### A rational function of the tangent beside a power of `a + i a tan` is integrated in that sum

**Answers where there were none.** `(A + B tan(c + d x))/sqrt(a + i a tan(c + d x))` and the rest of
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33505,6 +33505,174 @@ static bool IsAWholeNumberOfSteps(ERational difference)
return Integration.ComputeAsAQuestionOfItsOwn(expanded.Expand().InnerSimplified, x, integrateByParts);
}

/// <summary>
/// A rational function of <c>tan(z)</c> beside roots of linears in it, integrated in
/// <c>t = tan(z)</c> term by term over the partial fractions of its rational part, with the
/// <c>1 + t^2</c> that <c>dz = dt/(f (1 + t^2))</c> puts below the bar written as the linears
/// of its roots, <c>(1 + i t)(1 - i t)</c>: each term is the roots over one power of a linear,
/// which the rules for a root of a linear over a linear answer.
/// </summary>
/// <remarks>
/// <para>
/// 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
/// 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
/// neighbours with smaller powers did not: in <c>t</c>, under the root of the quotient of the two
/// linears or the root of the one, <c>1 + t^2</c> is a quartic with symbols in every coefficient.
/// Over its linears the same integrands are three terms, or a few more, of a second or less each.
/// </para>
/// <para>
/// Only two roots of linears with distinct roots beside a polynomial or a linear below the
/// bar, or one root below the bar beside a whole power of <c>a +- i a tan(z)</c> below it, which
/// 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
/// the rules for a root of the tangent and for the imaginary sum read, and below the bar linears
/// only. Two roots alone, and a root above the bar beside the imaginary sum, the substitution in
/// the root answers in as few terms and as soon, and are left to it. The decomposition is exact,
/// its coefficients constants in the symbols, and the sum is checked by its derivative in
/// <c>t</c> before it is returned: each term's answer is its own rule's.
/// https://github.com/asc-community/AngouriMath/issues/718
/// </para>
/// </remarks>
internal static Entity? SolveARationalFunctionOfTheTangentBesideRootsOfLinearsInIt(Entity expr, Entity.Variable x, bool integrateByParts)
{
Entity? argument = null;
foreach (var node in expr.Nodes)
{
if (!node.ContainsNode(x))
continue;
if (node is Sinf or Cosf or Secantf or Cosecantf)
return null;
if (node is not (Tanf or Cotanf))
continue;
var inner = node.DirectChildren.First();
if (argument is null)
argument = inner;
else if (argument != inner)
return null;
}
if (argument is null || !TreeAnalyzer.TryGetPolyLinear(argument, x, out var rate, out _)
|| rate.ContainsNode(x) || VanishesIdentically(rate))
return null;
var t = Variable.CreateUnique(expr, "t_tangent");
var inT = expr.Replace(node => node switch
{
Tanf(var y) when y == argument => t,
Cotanf(var y) when y == argument => 1 / t,
_ => node,
});
if (inT.ContainsNode(x))
return null;

// `A + c t` with `c = +-i A`, which vanishes at a root of `1 + t^2`.
static bool IsAnImaginarySum(Entity slope, Entity intercept)
{
var ratio = Functions.PartialFractions.Bare((slope / intercept).InnerSimplified);
if (ratio.Evaled is not Number.Complex)
ratio = Functions.PartialFractions.Bare(ratio.Simplify());
return ratio.Evaled == MathS.i.Evaled || ratio.Evaled == (-MathS.i).Evaled;
}
// The integrand read as a constant, roots of linears, whole powers of polynomials above the
// bar and of linears below it.
Entity constant = Number.Integer.One;
var above = new List<Entity>();
var below = new List<Entity>();
var degreeBelow = 0;
var imaginarySumBelow = false;
var roots = new List<(Entity Linear, Entity Slope, Entity Intercept, Number.Rational Power)>();
bool Read(Entity node, EInteger times)
{
switch (node)
{
case Mulf(var left, var right):
return Read(left, times) && Read(right, times);
case Divf(var dividend, var divisor):
return Read(dividend, times) && Read(divisor, times.Negate());
case Powf(var @base, Number.Integer whole) when @base.ContainsNode(t):
return Read(@base, times.Multiply(whole.EInteger));
case Powf(var @base, Number.Rational power) when @base.ContainsNode(t):
{
var exponent = power.ERational.Multiply(ERational.FromEInteger(times));
if (exponent.Denominator.Equals(EInteger.One))
return Read(@base, exponent.Numerator);
if (!TreeAnalyzer.TryGetPolyLinear(@base, t, out var slope, out var intercept)
|| slope.ContainsNode(t) || intercept.ContainsNode(t)
|| VanishesIdentically(slope) || VanishesIdentically(intercept) || IsAnImaginarySum(slope, intercept))
return false;
roots.Add((@base, slope, intercept, Number.Rational.Create(exponent)));
return true;
}
case var free when !free.ContainsNode(t):
constant *= MathS.Pow(free, Number.Integer.Create(times));
return true;
default:
if (!TreeAnalyzer.TryGetPolynomial(node, t, out var monomials)
|| monomials.Values.Any(coefficient => coefficient.ContainsNode(t)) || monomials.Keys.Any(degree => degree.Sign < 0)
|| !times.CanFitInInt32())
return false;
if (times.Sign > 0)
{
above.Add(times.Equals(EInteger.One) ? node : MathS.Pow(node, Number.Integer.Create(times)));
return true;
}
if (!TreeAnalyzer.TryGetPolyLinear(node, t, out var linearSlope, out var linearIntercept)
|| linearSlope.ContainsNode(t) || linearIntercept.ContainsNode(t) || VanishesIdentically(linearSlope))
return false;
var multiplicity = times.Negate();
// `(A + i A t)^k` is `A^k (1 + i t)^k`, a whole power: with the constant out
// in front the split is over numbers, and its terms' answers a few lines each,
// where with `A` in the linear they came to ten times the length.
if (!VanishesIdentically(linearIntercept) && IsAnImaginarySum(linearSlope, linearIntercept))
{
imaginarySumBelow = true;
constant /= MathS.Pow(linearIntercept, Number.Integer.Create(multiplicity));
node = (1 + Functions.PartialFractions.Bare((linearSlope / linearIntercept).Simplify()) * t).InnerSimplified;
}
below.Add(multiplicity.Equals(EInteger.One) ? node : MathS.Pow(node, Number.Integer.Create(multiplicity)));
degreeBelow += multiplicity.ToInt32Checked();
return true;
}
}
if (!Read(inT, EInteger.One) || degreeBelow > 12)
return null;
// Two roots beside a polynomial or a linear below, or one root below the bar beside a power
// of the imaginary sum below it: two roots alone over `1 + t^2`, and a root above the bar,
// the root's substitution answers as soon, and in the same few terms.
if (roots.Count == 2
? above.Count == 0 && below.Count == 0
|| VanishesIdentically(roots[0].Slope * roots[1].Intercept - roots[1].Slope * roots[0].Intercept)
: roots.Count != 1 || !imaginarySumBelow || roots[0].Power.ERational.Sign > 0)
return null;

// Divided by `1 + t^2` and the linears as written, and the proper rest split over the
// linears with `1 + t^2` written as its own two.
static Entity Product(IEnumerable<Entity> factors) =>
factors.Aggregate((Entity?)null, (all, factor) => all is null ? factor : all * factor) ?? Number.Integer.One;
var numerator = Product(above);
var written = Product(below.Append(1 + MathS.Sqr(t)));
var overTheLinears = Product(below.Append(1 + MathS.i * t).Append(1 - MathS.i * t));
if (OverEachLinear(numerator, written, overTheLinears, degreeBelow + 2, t) is not { } terms)
return null;
var ofTheRoots = Product(roots.Select(root => MathS.Pow(root.Linear, root.Power)));
Entity total = Number.Integer.Zero;
foreach (var term in terms)
{
if (term.Evaled is Number.Complex { IsZero: true })
continue;
if (Integration.ComputeAsAQuestionOfItsOwn((term * ofTheRoots).InnerSimplified, t, integrateByParts) is not { } integrated
|| integrated.Nodes.Any(node => node == MathS.NaN))
return null;
total += integrated;
}
// Checked by its derivative, and where the intervals cannot tell -- the arctangent of a
// complex argument that the answer to `(c + d t)^(5/2)/((a + b t)^(5/2) (1 + i t))` holds,
// right to ninety digits at every point -- beside two roots refused only where it
// differs at a point where the integrand, then real, is.
var integrandInT = numerator / written * ofTheRoots;
if (!Functions.PartialFractions.DerivativeHoldsAtSampledPoints(total, integrandInT, t)
&& (roots.Count == 1 || Functions.PartialFractions.DerivativeDiffersWhereReal(total, integrandInT, t)))
return null;
return (constant / rate * total).Substitute(t, MathS.Tan(argument));
}

/// <summary>
/// A rational function of <c>tan(z)</c> beside a power of <c>S = a + c tan(z)</c> with
/// <c>c = ±i a</c>, below the bar or not whole, integrated in <c>S</c>: <c>tan(z)</c> is
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -817,6 +817,10 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr)
// And an exponential of a hyperbolic sine or cosine of a linear beside a function of it
// over its derivative, under u = twice that sine or cosine, which arrive as exponentials.
if ((answer = IndefiniteIntegralSolver.SolveAnExponentialOfAHyperbolicFunctionBesideItsDerivative(expr, x, integrateByParts)) is { }) return answer;
// A rational function of the tangent beside two roots of linears in it, or one beside a
// whole power of a + i a tan(z) below the bar, over the linears of 1 + tan^2: in the root
// of the linears' quotient 1 + tan^2 is a symbolic quartic, and a search past the budget.
if ((answer = IndefiniteIntegralSolver.SolveARationalFunctionOfTheTangentBesideRootsOfLinearsInIt(expr, x, integrateByParts)) is { }) return answer;
// `A + i A tan(z)` is `A e^(i z)/cos(z)`, which beside a polynomial is a shape the
// closed rules answer, where the imaginary unit in the coefficient is read by none.
// The same identity beside a power of the secant, where the powers are not whole and add
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//
// 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 rational function of the tangent beside two roots of linears in it, or one root below the
/// bar beside a whole power of <c>a + i a tan</c>, integrated in <c>t = tan(g + h x)</c> over the
/// partial fractions of its rational part with <c>1 + t^2</c> written as <c>(1 + i t)(1 - i t)</c>.
/// In the root of the linears' quotient <c>1 + t^2</c> is a symbolic quartic, and every one of
/// these ran past the budget. Rubi's 4.3.4.2 and 4.3.2.1.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class ARationalFunctionOfTheTangentBesideRootsOfLinearsInItIntegralTest
{
[Theory]
[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))")]
[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)")]
[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)")]
[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))")]
[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))")]
[InlineData("1/((a + i*a*tan(g + h*x))^3*(c + d*tan(g + h*x))^(3/2))")]
[InlineData("1/((a + i*a*tan(g + h*x))^2*(c + d*tan(g + h*x))^(5/2))")]
[InlineData("1/((a + i*a*tan(g + h*x))^3*(c + d*tan(g + h*x))^(5/2))")]
public void OverTheLinearsOfOnePlusTheTangentSquared(string integrand)
{
var integral = integrand.ToEntity().Integrate("x");
var text = integral.Stringize();
Assert.DoesNotContain("integral(", text);
Assert.True(text.Length < 30000, $"{text.Length} characters of answer for {integrand}");
Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.7).Substitute("c", 2.1).Substitute("d", 0.4)
.Substitute("A", 0.9).Substitute("B", 1.7).Substitute("C", 0.6).Substitute("g", 0.2).Substitute("h", 1.1);
var derivative = Pinned(integral).Differentiate("x");
var original = Pinned(integrand.ToEntity());
var compared = 0;
foreach (var at in new[] { -0.9, -0.5, 0.1, 0.4, 0.7, 1.0 })
{
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}, where the integrand is {want}");
}
Assert.True(compared >= 5, $"only {compared} points could be compared for {integrand}");
}
}
}
Loading