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
17 changes: 17 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -793,6 +793,23 @@ wherever the cosine is negative; they are answered with the rest now
| `"sqrt(c*sec(x))*sqrt(a + i*a*tan(x))".ToEntity().Integrate("x")` | `integral(...)` | the integrand times an antiderivative in `e^(i x/2)` over its derivative |
| `"(c*sec(x))^(5/2)/(a + i*a*tan(x))^(5/2)".ToEntity().Integrate("x")` | `integral(...)` | the integrand times a power of `e^(i x)` |

### 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
Rubi's 4.3.3.1 with a whole power of the tangent or cotangent beside `A + B tan` over a power of
`a + i a tan` ran past the budget, whole powers and half-odd ones alike. With `S = a ± i a tan(z)`,
`tan(z)` is `(S - a)/(± i a)` and `dz = c dS/(S (S - 2a))`, so each is a rational function of `S`
beside a power of it, whose factors `S`, `S - 2a` and `S - a` have no imaginary root. Beside a
second such sum, `q - i q tan(z)`, which is linear in `S`, the sum under a power that is not whole is
the variable: `(a + i a tan(z))/(q - i q tan(z))^(3/2)`, from 4.3.2.1, ran past the budget as well
([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"tan(c + d*x)^2*(k + q*tan(c + d*x))/sqrt(a + i*a*tan(c + d*x))".ToEntity().Integrate("x")` | `integral(...)` | powers of `sqrt(a + i a tan(c + d x))` and, piecewise in the sign of `a`, an arctangent or a logarithm of it |
| `"cot(c + d*x)^2*(k + q*tan(c + d*x))/(a + i*a*tan(c + d*x))^4".ToEntity().Integrate("x")` | `integral(...)` | powers and logarithms of `a + i a tan(c + d x)`, of `a - i a tan(c + d x)` and of `tan(c + d x)` |
| `"(a + i*a*tan(c + d*x))/(q - i*q*tan(c + d*x))^(3/2)".ToEntity().Integrate("x")` | `integral(...)` | `-2 i a (q - i q tan(c + d x))^(-3/2)/(3d)`, written longer |

### A rational function with complex coefficients is integrated through its real and imaginary parts

**Answers where there were none.** `1/((1 + i x)^2 (1 + x^2))` was declined: the rational
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25289,6 +25289,145 @@ static bool IsAWholeNumberOfSteps(ERational difference)
return Integration.ComputeAsAQuestionOfItsOwn(expanded.Expand().InnerSimplified, x, integrateByParts);
}

/// <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
/// <c>(S - a)/c</c>, and since <c>c^2 = -a^2</c>, <c>dS/dz = c sec(z)^2 = S (S - 2a)/c</c>, so
/// <c>dz = c dS/(S (S - 2a))</c>.
/// </summary>
/// <remarks>
/// <para>
/// Rubi's <c>cot(c + d x)^3 (A + B tan(c + d x))/(a + i a tan(c + d x))^2</c> and the rest of
/// its 4.3.3.1 with a power of the tangent beside <c>(A + B tan)</c> over a power of
/// <c>a + i a tan</c> ran past the budget, whole powers and half-odd ones alike: under
/// <c>u = tan(z)</c> each is a rational function over <c>1 + i u</c> and <c>1 - i u</c> with
/// symbols in every coefficient. In <c>S</c> the factors are <c>S</c>, <c>S - 2a</c> and,
/// from a cotangent, <c>S - a</c>, with no imaginary root among them, and the same
/// integrands are a second or two; a half-odd power of <c>S</c> is a root of a linear.
/// </para>
/// <para>
/// Exact wherever the integrand is defined: the substitution is the identity
/// <c>sec(z)^2 = 1 + tan(z)^2</c> read in <c>S</c>, and the power of <c>S</c> is the
/// integrand's own. Only whole powers of the tangent and cotangent of <c>z</c> beside it,
/// and nothing else in x; a base standing only to positive whole powers is a polynomial in
/// the tangent, which the rules for those answer.
/// </para>
/// <para>
/// Beside a second such sum of the same argument, <c>q - i q tan(z)</c>, which is linear in
/// the first, the one under a power that is not whole is the variable and the other a whole
/// power of a linear in it: <c>(a + i a tan(z))/(q - i q tan(z))^(3/2)</c>, Rubi's 4.3.2.1,
/// ran past the budget too.
/// https://github.com/asc-community/AngouriMath/issues/718
/// </para>
/// </remarks>
internal static Entity? SolveInTheImaginarySumOfAConstantAndATangent(Entity expr, Entity.Variable x, bool integrateByParts)
{
var sums = new List<(Entity Base, Entity Constant, Entity Coefficient, Entity Argument)>();
foreach (var node in expr.Nodes)
{
if (node is not (Sumf or Minusf) || !node.ContainsNode(x) || sums.Any(found => found.Base == node))
continue;
Entity sum = Number.Integer.Zero;
Entity? factor = null, inner = null;
var read = true;
foreach (var term in Sumf.LinearChildren(node))
{
if (!term.ContainsNode(x))
{
sum = sum == Number.Integer.Zero ? term : sum + term;
continue;
}
if (factor is not null)
{
read = false;
break;
}
Entity product = Number.Integer.One;
foreach (var piece in Mulf.LinearChildren(term))
{
if (piece is Tanf(var y) && inner is null)
inner = y;
else if (!piece.ContainsNode(x))
product = product == Number.Integer.One ? piece : product * piece;
else
{
read = false;
break;
}
}
if (!read || inner is null)
{
read = false;
break;
}
factor = product;
}
if (!read || factor is null || inner is null || sum == Number.Integer.Zero)
continue;
var ratio = Functions.PartialFractions.Bare((factor / sum).InnerSimplified);
if (ratio.Evaled is not Number.Complex)
ratio = Functions.PartialFractions.Bare(ratio.Simplify());
if (ratio.Evaled != MathS.i.Evaled && ratio.Evaled != (-MathS.i).Evaled)
continue;
sums.Add((node, sum, factor, inner));
}
// Two such sums, `a + i a tan(z)` and `c - i c tan(z)`, are each linear in the other: in
// the one under a power that is not whole the other is a whole power of a linear, where
// in the other it would be the root of one. Where both or neither are, declined.
bool NotWhole(Entity sum) => expr.Nodes.Any(node => node is Powf(var b, Number.Rational p) && b == sum && p is not Number.Integer);
var chosen = sums.Count switch
{
1 => sums[0],
2 when NotWhole(sums[0].Base) != NotWhole(sums[1].Base) => NotWhole(sums[0].Base) ? sums[0] : sums[1],
_ => default,
};
var (@base, constant, coefficient, argument) = chosen;
if (@base is null || constant is null || coefficient is null || argument is null
|| !TreeAnalyzer.TryGetPolyLinear(argument, x, out var rate, out _) || rate.ContainsNode(x)
|| rate.Evaled is Number.Complex { IsZero: true })
return null;
// Below the bar, or to a power that is not whole: a positive whole power alone is a
// polynomial in the tangent.
var below = Functions.SingleQuotient.Of(Functions.SingleQuotient.Combine(expr)).Denominator;
if (!below.Nodes.Contains(@base)
&& !expr.Nodes.Any(node => node is Powf(var b, Number.Rational p) && b == @base && p is not Number.Integer))
return null;
var s = Variable.CreateUnique(expr, "s_imaginary_tangent");
var tangent = (s - constant) / coefficient;
// The other sum, `q + c' tan(z)`, written as the linear in `S` it is, `(c'/c) (S - r)`
// for `r = a - q c/c'`, its root spelled `2 a` where it is that: there it cancels the
// `S - 2a` of `dz` as written, where `q + c' (S - a)/c` cancelled nothing, and the pole
// it left made `(a + i a tan(z))^2/sqrt(q - i q tan(z))` fourteen thousand characters.
Entity? other = null, otherInS = null;
if (sums.Count == 2)
{
var (otherBase, otherConstant, otherCoefficient, _) = sums[0].Base == @base ? sums[1] : sums[0];
var ratio = Functions.PartialFractions.InLowestTermsOverTheSymbols(otherCoefficient / coefficient);
var root = Functions.PartialFractions.InLowestTermsOverTheSymbols(constant - otherConstant * coefficient / otherCoefficient);
if (Functions.PartialFractions.IsZeroAsAValue(root - 2 * constant))
root = 2 * constant;
(other, otherInS) = (otherBase, ratio * (s - root));
}
var rewritten = expr.Replace(node => node == @base ? s : other is not null && node == other ? otherInS! : node).Replace(node => node switch
{
Tanf(var y) when y == argument => tangent,
Cotanf(var y) when y == argument => 1 / tangent,
_ => node,
});
if (rewritten.ContainsNode(x))
return null;
// No power that is not whole but of S itself: a root of the tangent is a root of
// `(S - a)/c` with `c` imaginary, and taken apart over the constant it changed its
// branch -- `cot(z)^(3/2) (A + B tan(z))/(a + i a tan(z))^2` and `tan(z)^(8/3)/(a + i a tan(z))`
// came back wrong at every point.
if (rewritten.Nodes.Any(node => node is Powf(var radicand, var power) && power is not Number.Integer && radicand != s))
return null;
var inS = (rewritten * coefficient / (s * (s - 2 * constant)) / rate).InnerSimplified;
if (Integration.ComputeAsAQuestionOfItsOwn(inS, s, integrateByParts) is not { } inTermsOfS)
return null;
return inTermsOfS.Substitute(s, @base);
}

/// <summary>
/// <c>A + i A tan(z)</c> is <c>A e^(i z)/cos(z)</c>, and <c>A + i A cot(z)</c> is
/// <c>i A e^(-i z)/sin(z)</c> -- exactly, wherever the tangent is defined, since
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,8 @@ private static Entity Normalized(Entity expr, Entity.Variable x) =>
// product and takes it apart, and so answers (c sec)^(5/2)/(a + i a tan)^(5/2) with the
// wrong constant wherever the cosine is negative.
if ((answer = IndefiniteIntegralSolver.SolveAPowerOfAnImaginaryTangentBesideAPowerOfTheSecant(expr, x, integrateByParts)) is { }) return answer;
// A rational function of the tangent beside a power of a + i a tan(z), in that sum.
if ((answer = IndefiniteIntegralSolver.SolveInTheImaginarySumOfAConstantAndATangent(expr, x, integrateByParts)) is { }) return answer;
if ((answer = IndefiniteIntegralSolver.SolveByWritingAnImaginaryTangentAsAnExponential(expr, x, integrateByParts)) is { }) return answer;
// And `A cos(z) + i A sin(z)`, which is `A e^(i z)`, where no rotation is real.
if ((answer = IndefiniteIntegralSolver.SolveByWritingAnImaginarySumOfACosineAndASineAsAnExponential(expr, x, integrateByParts)) is { }) return answer;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//
// 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 <c>tan(z)</c> beside a power of <c>S = a ± i a tan(z)</c>, integrated
/// in <c>S</c>, where <c>dz = c dS/(S (S - 2a))</c>: the factors are <c>S</c>, <c>S - 2a</c> and
/// <c>S - a</c>, with no imaginary root among them. Beside <c>q - i q tan(z)</c>, a linear in
/// <c>S</c>, the sum under a power that is not whole is the variable. Rubi's 4.3.3.1 and 4.3.2.1.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
/// <remarks>Compared as complex numbers, the integrand being complex.</remarks>
[Trait("Area", "Calculus")]
public sealed class RationalInTheTangentBesideAnImaginarySumIntegralTest
{
[Theory]
[InlineData("cot(c + d*x)^3*(k + q*tan(c + d*x))/(a + i*a*tan(c + d*x))")]
[InlineData("cot(c + d*x)*(k + q*tan(c + d*x))/(a + i*a*tan(c + d*x))^2")]
[InlineData("tan(c + d*x)^2*(k + q*tan(c + d*x))/sqrt(a + i*a*tan(c + d*x))")]
[InlineData("(k + q*tan(c + d*x))/(a - i*a*tan(c + d*x))^(3/2)")]
[InlineData("cot(c + d*x)^2*(k + q*tan(c + d*x))/(a + i*a*tan(c + d*x))^4")]
[InlineData("(a + i*a*tan(c + d*x))/(q - i*q*tan(c + d*x))^(3/2)")]
[InlineData("sqrt(q - i*q*tan(c + d*x))/(a + i*a*tan(c + d*x))")]
[InlineData("(a + i*a*tan(c + d*x))^2/sqrt(q - i*q*tan(c + d*x))")]
public void IsIntegratedInTheSum(string integrand)
{
var integral = integrand.ToEntity().Integrate("x");
Assert.DoesNotContain("integral(", integral.Stringize());
Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("c", 0.4).Substitute("d", 1.1)
.Substitute("k", 1.1).Substitute("q", 0.6);
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
var original = Pinned(integrand.ToEntity());
foreach (var at in new[] { -1.1, -0.6, 0.3, 0.9 })
{
var want = original.Substitute("x", at).EvalNumerical();
var got = derivative.Substitute("x", at).EvalNumerical();
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}");
}
}
}
}
Loading