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 @@ -365,6 +365,24 @@ quotient of two such linears the sum is `(b - d t^2)^2 + (c t^2 - a)^2`. Rubi's
| `"(c + d*tan(x))^(3/2)/(a + b*tan(x))^3".ToEntity().Integrate("x")` | `integral(...)` | the same |
| `"1/((a + b*tan(x))^(3/2)*(c + d*tan(x))^(3/2))".ToEntity().Integrate("x")` | `integral(...)`; past a minute on the unreleased master | in the root of the quotient of the two, in a second |

### A power of the secant beside a power of `a + i a tan` is integrated in the sum, whatever the powers

**Answers where there were none.** `sec(x)^3 sqrt(a + i a tan(x))` was declined, with the rest of
Rubi's 4.3.1.2 whose powers add up to no whole number. Under `u = a + i a tan(z)`,
`du = i a sec(z)^2 dz` and `sec(z)^2 = (u/a) ((2 a - u)/a)`, so a power of the secant, or of the
cosine, beside a power of the sum is a power of `u` beside a power of `2 a - u`. The antiderivative in
`u` is found for a real `u`, and its conditions -- a radicand at least zero -- hold nowhere on the
path, where `u` is not real; they are dropped, and the answer is kept only where its derivative is the
integrand at sampled points. `cos(x)^9 (a + i a tan(x))^(7/2)` was answered on the unreleased master
with the condition `1 + i tan(x) >= 0`, which no real `x` but the zeros of the tangent meets; it is
answered without one now ([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"sec(x)^3*sqrt(a + i*a*tan(x))".ToEntity().Integrate("x")` | `integral(...)` | powers of `sqrt(a - i a tan(x))` times a factor constant wherever it is continuous, 495 characters |
| `"sec(x)^5/(a + i*a*tan(x))^(3/2)".ToEntity().Integrate("x")` | `integral(...)` | the same, 399 characters |
| `"cos(x)^9*(a + i*a*tan(x))^(7/2)".ToEntity().Integrate("x")` | `integral(...)`; on the unreleased master, an answer provided `1 + i tan(x) >= 0` | 1,067 characters, unconditional |

### A quotient in `x^2` over a power of a linear in `x^2` and a biquadratic is split in `x^2`

**Shorter answers, sooner.** `sqrt(c + d tan(x)) (A + B tan(x) + C tan(x)^2)/(a + b tan(x))^3` was
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27007,6 +27007,124 @@ static Entity SumOfThePowers(Entity first, Entity second)
=> (first + second).InnerSimplified is var sum && (sum is Number || sum.Complexity > 40) ? sum : sum.Simplify();
}

/// <summary>
/// A power of the secant, or of the cosine, of <c>z</c> beside a power of <c>A + i A tan(z)</c>,
/// whatever the powers, integrated in that sum: under <c>u = A + i A tan(z)</c>,
/// <c>du = i A sec(z)^2 dz</c> and <c>sec(z)^2 = (u/A) ((2 A - u)/A)</c>, so
/// <c>sec(z)^s u^n dz</c> is <c>u^n ((u/A) ((2 A - u)/A))^r du/(i A)</c> with
/// <c>r = (s - 2)/2</c>, up to a factor constant wherever it is continuous; and
/// <c>(u/A)^r ((2 A - u)/A)^r</c> is <c>(sec(z)^2)^r</c> exactly, the two being
/// <c>1 + i tan(z)</c> and <c>1 - i tan(z)</c>, whose arguments are opposite and less than a
/// right angle.
/// </summary>
/// <remarks>
/// <para>
/// <c>sec(x)^3 sqrt(a + i a tan(x))</c> was declined, with the rest of Rubi's 4.3.1.2 whose
/// powers do not add up to a whole number, which
/// <see cref="SolveAPowerOfAnImaginaryTangentBesideAPowerOfTheSecant"/> integrates as an
/// exponential and this does not: <c>sec^5/(a + i a tan)^(3/2)</c>,
/// <c>(e cos)^(3/2) sqrt(a + i a tan)</c>. In <c>u</c> each is a power of <c>u</c> beside a
/// power of <c>2 A - u</c>.
/// </para>
/// <para>
/// The constant is not written, as there: the answer is the integrand times the
/// antiderivative in <c>u</c> over what that antiderivative differentiates back to, a
/// quotient constant wherever it is continuous.
/// https://github.com/asc-community/AngouriMath/issues/718
/// </para>
/// </remarks>
internal static Entity? SolveAPowerOfTheSecantBesideAPowerOfAnImaginaryTangentSumInTheSum(Entity expr, Entity.Variable x, bool integrateByParts)
{
if (!expr.Nodes.Any(node => node is Tanf) || !expr.Nodes.Any(node => node is Secantf or Cosf))
return null;
Entity? argument = null, sum = null;
var plus = false;
Entity? sumPower = null;
Entity secantPower = Number.Integer.Zero;
var sawSecant = false;
foreach (var (factor, underneath) in FactorsOfTheIntegrand(expr))
{
if (!factor.ContainsNode(x))
continue;
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 (sumPower is not null || argument is not null && argument != tangentOf)
return null;
(sumPower, argument, plus, sum) = (power, tangentOf, isPlus, @base);
continue;
}
var (trigonometric, sign) = @base switch
{
Secantf => (@base, 1),
Cosf => (@base, -1),
Mulf(var left, var right) when !left.ContainsNode(x) && right is Secantf => (right, 1),
Mulf(var left, var right) when !left.ContainsNode(x) && right is Cosf => (right, -1),
Mulf(var left, var right) when !right.ContainsNode(x) && left is Secantf => (left, 1),
Mulf(var left, var right) when !right.ContainsNode(x) && left is Cosf => (left, -1),
_ => ((Entity?)null, 0)
};
var of = trigonometric switch { Secantf(var inner) => inner, Cosf(var inner) => inner, _ => null };
if (of is null || argument is not null && argument != of)
return null;
argument = of;
secantPower = sign == 1 ? secantPower + power : secantPower - power;
sawSecant = true;
}
if (sumPower is null || sum is null || argument is null || !sawSecant
|| !TreeAnalyzer.TryGetPolyLinear(argument, x, out var slope, out _) || TreeAnalyzer.IsZero(slope))
return null;
secantPower = secantPower is Number ? secantPower : secantPower.InnerSimplified;
// Whole powers on both are a rational function of the sine and the cosine, which the
// rules for those answer in a fraction of a second, where in u they ran past five:
// `cos(x)^5/(a + i a tan(x))^3`.
if (sumPower is Number.Integer && secantPower is Number.Integer)
return null;
Entity constantTerm = Number.Integer.Zero;
foreach (var term in Sumf.LinearChildren(sum))
if (!term.ContainsNode(x))
constantTerm += term;
var a = constantTerm.InnerSimplified;
var u = Variable.CreateUnique(expr, "u_tan");
var half = ((secantPower - 2) / 2).InnerSimplified;
Entity squared = MathS.Pow(u / a, half) * MathS.Pow((2 * a - u) / a, half);
var inU = MathS.Pow(u, sumPower) * squared;
if (Integration.ComputeAsAQuestionOfItsOwn(inU, u, integrateByParts) is not { } inUAnswer
|| inUAnswer.Nodes.Any(node => node == MathS.NaN))
return null;
// du/dx is +-i A z' sec(z)^2.
var derivativeOfU = (plus ? MathS.i : -MathS.i) * a * slope * MathS.Pow(MathS.Sec(argument), 2);
var differentiatesBackTo = inU.Substitute(u, sum) * derivativeOfU;
// The antiderivative in u was found for a real u, and its conditions say so -- a radicand
// at least zero -- where u = A + i A tan(z) is not real: kept, they hold nowhere on the
// path and the answer has no value at all. The formula is an antiderivative wherever it is
// analytic, so the conditions go, each piecewise taken arm by arm, and an answer is kept
// only where its derivative is the integrand at the sampled points.
foreach (var arm in new[] { 0, -1 })
{
var formula = WithoutConditions(inUAnswer, arm);
var answer = expr * formula.Substitute(u, sum) / differentiatesBackTo;
if (Functions.PartialFractions.DerivativeHoldsAtSampledPoints(answer, expr, x))
return answer;
if (!inUAnswer.Nodes.Any(node => node is Piecewise))
break;
}
return null;

// Every condition dropped, and of every piecewise the first arm, or with -1 the last.
static Entity WithoutConditions(Entity e, int arm)
=> e.Replace(node => node switch
{
Providedf(var inner, _) => inner,
Piecewise piecewise when piecewise.Cases.Any() => arm == 0 ? piecewise.Cases.First().Expression : piecewise.Cases.Last().Expression,
_ => node
});
}

/// <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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,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 whatever the powers, in the sum, where they do not add up to a whole number.
if ((answer = IndefiniteIntegralSolver.SolveAPowerOfTheSecantBesideAPowerOfAnImaginaryTangentSumInTheSum(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.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//
// 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 the secant or the cosine beside a power of <c>a + i a tan(x)</c>, the powers adding
/// up to no whole number, integrated in that sum: under <c>u = a + i a tan(x)</c>,
/// <c>sec(x)^2 = (u/a) ((2 a - u)/a)</c>. Rubi's 4.3.1.2. The integrands are complex for a real
/// <c>x</c> and are compared as complex numbers.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class SecantBesideAnImaginaryTangentSumIntegralTest
{
[Theory]
[InlineData("sec(x)^3*sqrt(a + i*a*tan(x))")]
[InlineData("sec(x)^5/(a + i*a*tan(x))^(3/2)")]
[InlineData("(k*cos(x))^(3/2)*sqrt(a + i*a*tan(x))")]
[InlineData("cos(x)^9*(a + i*a*tan(x))^(7/2)")]
[InlineData("(m*sec(x))^(2/3)*(a + i*a*tan(x))^(5/3)")]
public void InTheSum(string integrand)
{
var integral = integrand.ToEntity().Integrate("x");
var text = integral.Stringize();
Assert.DoesNotContain("integral(", text);
Assert.True(text.Length < 5000, $"{text.Length} characters of answer for {integrand}");
Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("k", 0.7).Substitute("m", 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