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 @@ -975,6 +975,23 @@ the variable: `(a + i a tan(z))/(q - i q tan(z))^(3/2)`, from 4.3.2.1, ran past
| `"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 |

### Half-odd powers of two conjugate tangent sums are integrated as the exponential they make

**Answers in time where they were not.** `(a + i a tan(e + f x))^(7/2) (A + B tan(e + f x))/(c - i c tan(e + f x))^(9/2)`
ran past the budget, with the rest of Rubi's 4.3.2.1 and 4.3.3.1 that put half-odd powers on both sums: the
rule for one of the sums integrates in it beside a whole power of the other, and with both half-odd neither
is. `a + i a tan(z)` is `a sec(z) e^(i z)` on the real line and `c - i c tan(z)` is `c sec(z) e^(-i z)`, so
two powers of them adding up to a whole number `k` are a constant on every interval where they are
continuous times `sec(z)^k e^(i (p - q) z)`, rational in `w = e^(i z)` beside any function of the tangent,
the secant, the cosine or the sine of `z`. They are integrated in `w` now, and the answer is the integrand
times the antiderivative in `w` over what that differentiates back to
([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"(a+i*a*tan(pe+f*x))^(7/2)*(A+B*tan(pe+f*x))/(c-i*c*tan(pe+f*x))^(9/2)".ToEntity().Integrate("x")` | `integral(...)`; past 20 s on the unreleased master | in a tenth of a second |
| `"1/((a+i*a*tan(pe+f*x))^(7/2)*(c-i*c*tan(pe+f*x))^(3/2))".ToEntity().Integrate("x")` | `integral(...)`; past 20 s on the unreleased master | the same |

### A symbolic power of one of two conjugate tangent sums is integrated in that sum

**Answers where there were none.** `(a + i a tan(c + d x))^m (q - i q tan(c + d x))^4` and the rest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26706,6 +26706,107 @@ private static bool TryReadAnImaginaryTangent(Entity sum, Entity.Variable x, out
return true;
}

/// <summary>
/// Powers of the two conjugate sums <c>a + i a tan(z)</c> and <c>c - i c tan(z)</c>, not both
/// whole and adding up to a whole number <c>k</c>, beside a function of the tangent, the
/// secant or the cosine of the same argument, integrated as the exponential they are:
/// <c>a + i a tan(z)</c> is <c>a sec(z) e^(i z)</c> on the real line and
/// <c>c - i c tan(z)</c> is <c>c sec(z) e^(-i z)</c>, so the pair is a constant on every
/// interval where it is continuous times <c>sec(z)^k e^(i (p - q) z)</c>, and in
/// <c>w = e^(i z)</c> the whole is a rational function of <c>w</c> times a power of it.
/// </summary>
/// <remarks>
/// <para>
/// <c>(a + i a tan(x))^(7/2) (A + B tan(x))/(c - i c tan(x))^(9/2)</c> is
/// <c>e^(8 i x) (A cos(x) + B sin(x))</c> up to that constant, and ran past the budget with
/// the rest of Rubi's 4.3.2.1 and 4.3.3.1 that put half-odd powers on both sums: the rule for
/// one of them integrates in it beside a whole power of the other, and with both half-odd
/// neither is.
/// </para>
/// <para>
/// The constant is not written, as for one sum beside the secant: the answer is the integrand
/// times the antiderivative in <c>w</c> over what that differentiates back to, a quotient
/// constant wherever it is continuous. The antiderivative in <c>w</c> is checked there, where
/// it is the only thing computed; checked at sampled <c>x</c>, the quotient's constant need
/// not be the same on both sides of the points, and right answers were declined.
/// https://github.com/asc-community/AngouriMath/issues/718
/// </para>
/// </remarks>
internal static Entity? SolveAConjugatePairOfImaginaryTangentSums(Entity expr, Entity.Variable x, bool integrateByParts)
{
if (!expr.Nodes.Any(node => node is Tanf))
return null;
Entity? argument = null;
Entity? plusPower = null, minusPower = null;
Entity constant = Number.Integer.One;
Entity varying = Number.Integer.One;
Entity rest = Number.Integer.One;
foreach (var (factor, underneath) in FactorsOfTheIntegrand(expr))
{
if (!factor.ContainsNode(x))
{
constant = underneath ? constant / factor : constant * factor;
continue;
}
varying = underneath ? varying / factor : varying * factor;
var (@base, power) = factor is Powf(var b, var p) && !p.ContainsNode(x)
? (b, p.Evaled is Number.Rational r ? r : p)
: (factor, (Entity)Number.Integer.One);
if (underneath)
power = power is Number.Rational numeric ? -numeric : (-power).InnerSimplified;
if (TryReadAnImaginaryTangent(@base, x, out var tangentOf, out var isPlus))
{
if (argument is not null && argument != tangentOf || (isPlus ? plusPower : minusPower) is not null)
return null;
argument = tangentOf;
if (isPlus)
plusPower = power;
else
minusPower = power;
continue;
}
rest = underneath ? rest / factor : rest * factor;
}
if (plusPower is null || minusPower is null || argument is null
|| plusPower is Number.Integer && minusPower is Number.Integer
|| !TreeAnalyzer.TryGetPolyLinear(argument, x, out var slope, out _) || TreeAnalyzer.IsZero(slope))
return null;
var sum = (plusPower + minusPower).InnerSimplified;
if (sum is not Number && sum.Complexity <= 40)
sum = sum.Simplify();
if (sum is not Number.Integer { EInteger: var whole } || !whole.CanFitInInt32())
return null;
var k = whole.ToInt32Checked();
var phase = (plusPower - minusPower).InnerSimplified;
if (phase is not Number && phase.Complexity <= 40)
phase = phase.Simplify();
// The rest in w = e^(i z): a function of the tangent, the secant, the cosine and the sine
// of the argument alone, and rational in w once they are written so.
var w = Variable.CreateUnique(expr, "w_exp");
var secantInW = 2 * w / (MathS.Sqr(w) + 1);
var tangentInW = -MathS.i * (MathS.Sqr(w) - 1) / (MathS.Sqr(w) + 1);
var restInW = rest.Replace(node => node switch
{
Tanf(var inner) when inner == argument => tangentInW,
Secantf(var inner) when inner == argument => secantInW,
Cosf(var inner) when inner == argument => 1 / secantInW,
Sinf(var inner) when inner == argument => tangentInW / secantInW,
_ => node,
});
if (restInW.ContainsNode(x))
return null;
// sec(z)^k e^(i p z) R dz is (2 w/(w^2 + 1))^k w^(p - 1) R(w) dw/i, with dz = dw/(i w).
var inW = Functions.SingleQuotient.Combine(
(k == 0 ? Number.Integer.One : MathS.Pow(secantInW, k)) * MathS.Pow(w, (phase - 1).InnerSimplified) * restInW).InnerSimplified;
if (Integration.ComputeAsAQuestionOfItsOwn(inW, w, integrateByParts) is not { } inWAnswer
|| inWAnswer.Nodes.Any(node => node == MathS.NaN)
|| !Functions.PartialFractions.HoldsAtSampledPoints(inWAnswer.Differentiate(w), inW, w))
return null;
var exponential = MathS.Pow(MathS.e, MathS.i * argument);
var differentiatesBackTo = (k == 0 ? Number.Integer.One : MathS.Pow(MathS.Sec(argument), k)) * MathS.Pow(exponential, phase) * rest;
return constant * varying * inWAnswer.Substitute(w, exponential) / (MathS.i * slope * differentiatesBackTo);
}

/// <summary>
/// <c>A cos(y) + i A sin(y)</c> below the bar, written as the exponential it is:
/// <c>A e^(i y)</c>, and <c>A cos(y) - i A sin(y)</c> as <c>A e^(-i y)</c>. Beside a power
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;
// And powers of the two conjugate sums, both not whole, as the exponential they make.
if ((answer = IndefiniteIntegralSolver.SolveAConjugatePairOfImaginaryTangentSums(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;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//
// 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>
/// Powers of the conjugate sums <c>a + i a tan(x)</c> and <c>c - i c tan(x)</c>, both half-odd,
/// beside a function of the tangent: <c>a + i a tan(x)</c> is <c>a sec(x) e^(i x)</c> and
/// <c>c - i c tan(x)</c> is <c>c sec(x) e^(-i x)</c>, so the pair is a constant times
/// <c>sec(x)^k e^(i (p - q) x)</c>, rational in <c>e^(i x)</c>. Rubi's 4.3.2.1 and 4.3.3.1. The
/// integrands are complex for a real <c>x</c>, and compared as complex numbers.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class ConjugatePairOfImaginaryTangentSumsIntegralTest
{
[Theory]
[InlineData("(a + i*a*tan(x))^(7/2)*(A + B*tan(x))/(c - i*c*tan(x))^(9/2)")]
[InlineData("(a + i*a*tan(x))^(3/2)*(A + B*tan(x))/(c - i*c*tan(x))^(3/2)")]
[InlineData("(a + i*a*tan(x))^(3/2)/(c - i*c*tan(x))^(3/2)")]
[InlineData("1/((a + i*a*tan(x))^(7/2)*(c - i*c*tan(x))^(3/2))")]
[InlineData("(A + B*tan(x))/((a + i*a*tan(x))^(3/2)*(c - i*c*tan(x))^(3/2))")]
public void AsTheExponentialTheyMake(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.7).Substitute("A", 0.4).Substitute("B", 1.1);
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
var original = Pinned(integrand.ToEntity());
var compared = 0;
foreach (var at in new[] { -1.2, -0.7, 0.3, 0.8, 1.3, 2.9 })
{
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