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
14 changes: 14 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,20 @@ 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 tangent times that of its double is a secant less one

**Answers where there were none.** `sec(2(a + b x))^2 sqrt(c tan(a + b x) tan(2(a + b x)))` was
declined, with the rest of Rubi's 4.7.7 powers of `c tan(y) tan(2y)` beside the secant or cosine
of `2y`, some after searches past the budget. `tan(y) tan(2y)` is `sec(2y) - 1`, and written so the
integrand is in the one argument `2y`, which the half-angle tangent answers; the answer is exact
where the integrand is real ([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"sec(2*(a + b*x))^2*sqrt(c*tan(a + b*x)*tan(2*(a + b*x)))".ToEntity().Integrate("x")` | `integral(...)` | `sgn(tan(a + b x))` times powers of `sqrt(1 - tan(a + b x)^2)` |
| `"1/sqrt(c*tan(a + b*x)*tan(2*(a + b*x)))".ToEntity().Integrate("x")` | `integral(...)` | logarithms in `tan(a + b x)`, `sgn(tan(a + b x))` in front |
| `"tan(x)*tan(2*x)".ToEntity().Integrate("x")` | `integral(...)` | `ln((1 + sin(2x))/(1 - sin(2x)))/4 - x` |

### 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)`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4152,6 +4152,56 @@ Entity Integral(ERational power)
return roots < 2 ? answer : answer.Provided((secantKind ? MathS.Cos(argument) : MathS.Sin(argument)) >= Number.Integer.Zero);
}

/// <summary>
/// <c>tan(y) tan(2y)</c> written <c>sec(2y) - 1</c>, and the integrand asked again in the
/// one argument <c>2y</c>.
/// </summary>
/// <remarks>
/// <para>
/// <c>tan(y)</c> is <c>(1 - cos(2y))/sin(2y)</c>, so <c>tan(y) tan(2y)</c> is
/// <c>(1 - cos(2y))/cos(2y)</c>, which is <c>sec(2y) - 1</c> wherever both sides are
/// defined; at the zeros of <c>cos(y)</c> the left is <c>-2</c> as a limit, which the right
/// is. Rubi's 4.7.7 has <c>sec(2(a + b x))^k sqrt(c tan(a + b x) tan(2(a + b x)))</c> and
/// its kin, a half-odd power of <c>c sec(2y) - c</c> beside powers of the secant or cosine
/// of <c>2y</c>, which <see cref="SolveByTheHalfAngleTangentBesideAHalfOddPowerOfOnePlusASecant"/>
/// answers in that one argument; with two arguments nothing read them.
/// </para>
/// <para>
/// The same question in another spelling, so asked as the same question. The answer is
/// what that rule gives, exact where the integrand is real.
/// https://github.com/asc-community/AngouriMath/issues/718
/// </para>
/// </remarks>
internal static Entity? SolveByWritingATangentTimesThatOfItsDoubleThroughTheSecant(Entity expr, Entity.Variable x, bool integrateByParts)
{
static bool IsTwice(Entity twice, Entity once)
=> (twice - 2 * once).Expand().InnerSimplified.Evaled is Number.Complex { IsZero: true };
var found = false;
var rewritten = expr.Replace(node =>
{
if (node is not Mulf || !node.ContainsNode(x))
return node;
var factors = Mulf.LinearChildren(node).ToList();
var single = factors.FindIndex(factor => factor is Tanf(var y) && y.ContainsNode(x)
&& factors.Any(other => other is Tanf(var z) && z != y && IsTwice(z, y)));
if (single < 0)
return node;
var once = ((Tanf)factors[single]).Argument;
var twice = factors.FindIndex(other => other is Tanf(var z) && z != once && IsTwice(z, once));
var doubled = ((Tanf)factors[twice]).Argument;
Entity others = Number.Integer.One;
for (var i = 0; i < factors.Count; i++)
if (i != single && i != twice)
others = others == Number.Integer.One ? factors[i] : others * factors[i];
found = true;
// Spread over the difference, so that a root of it reads as `c sec(2y) - c`.
return others == Number.Integer.One ? MathS.Sec(doubled) - 1 : others * MathS.Sec(doubled) - others;
});
if (!found)
return null;
return Integration.ComputeAsTheSameQuestion(rewritten, x, integrateByParts);
}

/// <summary>
/// <paramref name="expr"/> as what it is a product of beside <paramref name="bases"/>, and
/// the sum of the exponents of each base in it, read through products, quotients and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -867,6 +867,8 @@ private static Entity Normalized(Entity expr, Entity.Variable x) =>
if ((answer = IndefiniteIntegralSolver.SolveSymbolicPowersOfOnePlusMinusASineThroughTheSine(expr, x, integrateByParts)) is { }) return answer;
// And a half-odd power of a +- a sec(y), which is that square over cos(y): by the half-angle
// tangent, in which the whole is rational beside one root.
// And `tan(y) tan(2y)` written `sec(2y) - 1` first, so that the rule reads one argument.
if ((answer = IndefiniteIntegralSolver.SolveByWritingATangentTimesThatOfItsDoubleThroughTheSecant(expr, x, integrateByParts)) is { }) return answer;
if ((answer = IndefiniteIntegralSolver.SolveByTheHalfAngleTangentBesideAHalfOddPowerOfOnePlusASecant(expr, x, integrateByParts)) is { }) return answer;
// And `a ± a cosh(y)` under a fractional power: `2a cosh(y/2)^2`, `-2a sinh(y/2)^2`.
if ((answer = IndefiniteIntegralSolver.SolveByTheHalfAngleWhereOnePlusAHyperbolicCosineIsASquare(expr, x, integrateByParts)) is { }) return answer;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//
// 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>
/// <c>tan(y) tan(2y)</c> is <c>sec(2y) - 1</c>, and written so a power of
/// <c>c tan(y) tan(2y)</c> beside the secant or cosine of <c>2y</c> is in one argument, which
/// the half-angle tangent answers. Rubi's 4.7.7.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
/// <remarks>
/// Checked by differentiating back with <c>a = 0.4</c>, <c>b = 1.1</c>, <c>c = 1.3</c> where the
/// integrand is real, <c>cos(2(a + b x))</c> positive, on both sides of the zero of
/// <c>tan(a + b x)</c>: the rule the rewriting reaches is exact there.
/// </remarks>
[Trait("Area", "Calculus")]
public sealed class TangentTimesThatOfItsDoubleIntegralTest
{
[Theory]
[InlineData("sec(2*(a + b*x))^2*sqrt(c*tan(a + b*x)*tan(2*(a + b*x)))")]
[InlineData("cos(2*(a + b*x))*(c*tan(a + b*x)*tan(2*(a + b*x)))^(3/2)")]
[InlineData("1/sqrt(c*tan(a + b*x)*tan(2*(a + b*x)))")]
[InlineData("sec(2*(a + b*x))/(c*tan(a + b*x)*tan(2*(a + b*x)))^(3/2)")]
[InlineData("tan(x)*tan(2*x)")]
public void IsAPowerOfASecantLessOne(string integrand)
{
var integral = integrand.ToEntity().Integrate("x");
Assert.DoesNotContain("integral(", integral.Stringize());
Entity Pinned(Entity e) => e.Substitute("a", 0.4).Substitute("b", 1.1).Substitute("c", 1.3);
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
var original = Pinned(integrand.ToEntity());
foreach (var at in new[] { -0.9, -0.7, -0.5, 0.05, 0.2 })
{
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