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
18 changes: 18 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,24 @@ that `sec(x)^2/(a + b sin(x))` is rational in them rather than declined at once
| `"tan(x)^4/(a + a*cos(x))".ToEntity().Integrate("x")` | `integral(...)` | an antiderivative in `tan(x/2)` |
| `"csc(x)^2/(a + a*cos(x))".ToEntity().Integrate("x")` | `integral(...)` | `(tan(x/2)^3/12 + tan(x/2)/2 - 1/(4 tan(x/2)))/a` |

### A power of `a + i a tan` beside a power of the secant is integrated as an exponential

**Answers where there were none.** `sqrt(a + i a tan(x))/sqrt(c sec(x))` is `sqrt(a/c) e^(i x/2)`
wherever the cosine is positive, and was declined, with every other pair of such powers, one of them
not whole, whose exponents add up to a whole number: Rubi's 4.3.1.2 has 43 of them. `a + i a tan(z)` is
`a sec(z) e^(i z)` on the real line, so the integrand is a constant on every interval where it is
continuous times a power of the secant and an exponential, which in `w = e^(i z)` is rational in a root
of `w`. Three of the 43, `(c sec(x))^p/(a + i a tan(x))^p` for `p` a half, three halves and five, were
answered on the unreleased master through the power of a product taken apart, wrong by a constant
wherever the cosine is negative; they are answered with the rest now
([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"sqrt(a + i*a*tan(x))/sqrt(c*sec(x))".ToEntity().Integrate("x")` | `integral(...)` | the integrand times `2/i`, through `e^(i x)` |
| `"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 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 @@ -23594,6 +23594,136 @@ static bool IsAWholeNumberOfSteps(ERational difference)
return Integration.ComputeAsTheSameQuestion(inExponentials.InnerSimplified, x, integrateByParts);
}

/// <summary>
/// A power of <c>A + i A tan(z)</c> beside a power of the secant of the same argument, one
/// of them not whole and the two adding up to a whole number <c>k</c>, integrated as the
/// exponential it is: <c>A + i A tan(z)</c> is <c>A sec(z) e^(i z)</c> on the real line, so
/// <c>(A + i A tan(z))^n (c sec(z))^m</c> is a constant on every interval where it is
/// continuous times <c>sec(z)^k e^(i n z)</c>, which in <c>w = e^(i z)</c> is
/// <c>(2 w/(w^2 + 1))^k w^n</c>, and <c>dz</c> is <c>dw/(i w)</c>.
/// </summary>
/// <remarks>
/// <para>
/// <c>sqrt(a + i a tan(x))/sqrt(c sec(x))</c> is <c>sqrt(a/c) e^(i x/2)</c> wherever the
/// cosine is positive, and was declined, with every other power of the pair whose exponents
/// add up to a whole number: a root of a sum with the imaginary unit in it beside a root of the
/// secant is read by no rule, and <c>e^(i x/2)/cos(x)</c>, what the positive sums leave, is
/// declined as well. Rubi's 4.3.1.2.
/// </para>
/// <para>
/// The constant is not written: the answer is the integrand times the antiderivative in
/// <c>w</c> over what that antiderivative differentiates back to, <c>sec(z)^k (e^(i z))^n</c>
/// times <c>i</c> and the slope, a quotient whose logarithmic derivative is zero -- both
/// halves have <c>n (tan(z) + i) z'</c> -- so it is constant wherever it is continuous, and
/// the answer holds on every interval where the integrand is.
/// https://github.com/asc-community/AngouriMath/issues/718
/// </para>
/// </remarks>
internal static Entity? SolveAPowerOfAnImaginaryTangentBesideAPowerOfTheSecant(Entity expr, Entity.Variable x, bool integrateByParts)
{
if (!expr.Nodes.Any(node => node is Secantf) || !expr.Nodes.Any(node => node is Tanf))
return null;
Entity? argument = null;
var plus = false;
Number.Rational? tangentPower = null, secantPower = null;
Entity constant = Number.Integer.One;
Entity varying = 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.Evaled is Number.Rational r
? (b, r)
: (factor, (Number.Rational)Number.Integer.One);
if (underneath)
power = (Number.Rational)(-power);
if (TryReadAnImaginaryTangent(@base, x, out var tangentOf, out var isPlus))
{
if (tangentPower is not null || argument is not null && argument != tangentOf)
return null;
(tangentPower, argument, plus) = (power, tangentOf, isPlus);
continue;
}
var secant = @base switch
{
Secantf => @base,
Mulf(var left, Secantf right) when !left.ContainsNode(x) => right,
Mulf(Secantf left, var right) when !right.ContainsNode(x) => left,
_ => null
};
if (secant is not Secantf(var secantOf) || secantPower is not null || argument is not null && argument != secantOf)
return null;
(secantPower, argument) = (power, secantOf);
}
if (tangentPower is null || secantPower is null || argument is null
|| tangentPower is Number.Integer && secantPower is Number.Integer
|| (tangentPower + secantPower) is not Number.Integer { EInteger: var whole } || !whole.CanFitInInt32()
|| !TreeAnalyzer.TryGetPolyLinear(argument, x, out var slope, out _) || TreeAnalyzer.IsZero(slope))
return null;
var k = whole.ToInt32Checked();
var phase = plus ? tangentPower : (Number.Rational)(-tangentPower);
// In w = e^(i z): sec(z)^k e^(i n z) dz is (2 w)^k (w^2 + 1)^(-k) w^(n - 1) dw / i.
var w = Variable.CreateUnique(expr, "w_exp");
var inW = k >= 0
? MathS.Pow(2, k) * MathS.Pow(w, (phase + k - 1).InnerSimplified) / MathS.Pow(MathS.Sqr(w) + 1, k)
: MathS.Pow(2, k) * MathS.Pow(MathS.Sqr(w) + 1, -k) * MathS.Pow(w, (phase + k - 1).InnerSimplified);
if (Integration.ComputeAsAQuestionOfItsOwn(inW.InnerSimplified, w, integrateByParts) is not { } inWAnswer
|| inWAnswer.Nodes.Any(node => node == MathS.NaN))
return null;
var exponential = MathS.Pow(MathS.e, MathS.i * argument);
var differentiatesBackTo = MathS.Pow(MathS.Sec(argument), k) * MathS.Pow(exponential, phase);
return constant * varying * inWAnswer.Substitute(w, exponential) / (MathS.i * slope * differentiatesBackTo);
}

/// <summary>
/// <c>A + i A tan(z)</c> or <c>A - i A tan(z)</c>, with a constant <c>A</c>: the argument,
/// and whether the imaginary unit comes with a plus.
/// </summary>
private static bool TryReadAnImaginaryTangent(Entity sum, Entity.Variable x, out Entity argument, out bool plus)
{
(argument, plus) = (Number.Integer.Zero, false);
if (sum is not Sumf and not Minusf || !sum.ContainsNode(x))
return false;
Entity free = Number.Integer.Zero;
Entity? coefficient = null;
Entity? tangentOf = null;
foreach (var term in Sumf.LinearChildren(sum))
{
if (!term.ContainsNode(x))
{
free += term;
continue;
}
if (coefficient is not null)
return false;
Entity factors = Number.Integer.One;
foreach (var factor in Mulf.LinearChildren(term))
if (factor is Tanf(var inner) && tangentOf is null)
tangentOf = inner;
else if (factor.ContainsNode(x))
return false;
else
factors *= factor;
if (tangentOf is null)
return false;
coefficient = factors;
}
if (coefficient is null || tangentOf is null || free == Number.Integer.Zero)
return false;
var ratio = Functions.PartialFractions.Bare((coefficient / free).InnerSimplified);
if (ratio.Evaled is not Number.Complex)
ratio = Functions.PartialFractions.Bare(ratio.Simplify());
plus = ratio.Evaled == MathS.i.Evaled;
if (!plus && ratio.Evaled != (-MathS.i).Evaled)
return false;
argument = tangentOf;
return true;
}

/// <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 @@ -669,6 +669,12 @@ private static Entity Normalized(Entity expr, Entity.Variable x) =>
if ((answer = IndefiniteIntegralSolver.SolveAnExponentialOfAHyperbolicFunctionBesideItsDerivative(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
// up to a whole number: an exponential over a power of the cosine, in e^(i z). Before the
// rule below, which writes a power of the identity that is not whole as the power of a
// 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;
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,55 @@
//
// 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 power of <c>a + i a tan(x)</c> beside a power of the secant, one of them not whole and
/// the two adding up to a whole number: <c>a + i a tan(x)</c> is <c>a sec(x) e^(i x)</c> on
/// the real line, so the integrand is a constant on every interval where it is continuous times
/// a power of the secant and an exponential. Rubi's 4.3.1.2. The integrands are complex along
/// the real line and are compared as complex numbers, where the cosine is negative as well,
/// which is where the fourth row's answer on master was off by a constant factor.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class ImaginaryTangentBesideTheSecantIntegralTest
{
[Theory]
[InlineData("sqrt(a + i*a*tan(x))/sqrt(k*sec(x))")]
[InlineData("sqrt(k*sec(x))*sqrt(a + i*a*tan(x))")]
[InlineData("(a + i*a*tan(x))^(3/2)/(k*sec(x))^(7/2)")]
[InlineData("(k*sec(x))^(5/2)/(a + i*a*tan(x))^(5/2)")]
[InlineData("(k*sec(c + d*x))^(3/2)*(a - i*a*tan(c + d*x))^(3/2)")]
public void AsTheExponentialItIs(string integrand)
{
var integral = integrand.ToEntity().Integrate("x");
Assert.DoesNotContain("integral(", integral.Stringize());
Assert.DoesNotContain("NaN", integral.Stringize());
Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("k", 0.8).Substitute("c", 0.4).Substitute("d", 1.1);
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
var original = Pinned(integrand.ToEntity());
var compared = 0;
foreach (var at in new[] { -2.6, -1.1, -0.4, 0.3, 0.8, 1.2, 2.0 })
{
var want = original.Substitute("x", at).EvalNumerical();
if (want.IsNaN)
continue;
compared++;
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}");
}
Assert.True(compared >= 6, $"only {compared} points could be compared for {integrand}");
}
}
}
Loading