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
Original file line number Diff line number Diff line change
Expand Up @@ -4505,6 +4505,90 @@ Entity Integral(ERational power)
return expr * inTermsOfW.Substitute(w, wOfX) / inX;
}

/// <summary>
/// <c>sec(y) (a ± a sec(y))^m (c ∓ c sec(y))^n</c>, with <c>n</c> half-odd and <c>m</c> anything,
/// integrated in <c>u = a ± a sec(y)</c>: the two sums multiply to <c>-a c tan(y)^2</c>, so the
/// root of the second is <c>tan(y)</c> over the root of <c>u</c> times a constant,
/// <c>sec(y) tan(y) dy</c> is <c>du</c> over one, and what is left is
/// <c>u^(m - 1/2) (2 - u/a)^(n - 1/2)</c>. The cosecant's are the same with the cotangent.
/// </summary>
/// <remarks>
/// Rubi's <c>sec(e + f x) (a + a sec(e + f x))^m sqrt(c - c sec(e + f x))</c> and two more of
/// 4.5.2.3 were declined. The constants are not written: the answer is the integrand times
/// the antiderivative in <c>u</c> over what that differentiates back to, a quotient whose
/// square is one; and it is kept only where that square is one at the sampled points.
/// https://github.com/asc-community/AngouriMath/issues/718
/// </remarks>
internal static Entity? SolveASecantBesidePowersOfItsConjugateSums(Entity expr, Entity.Variable x, bool integrateByParts)
{
if (!Integration.AnsweringTheQuestionAskedOrOneBelow)
return null;
Entity? argument = null;
foreach (var node in expr.Nodes)
{
if (TrigonometricArgument(node) is not { } thisArgument || !thisArgument.ContainsNode(x))
continue;
if (argument is null)
argument = thisArgument;
else if (argument != thisArgument)
return null;
}
if (argument is null || !TreeAnalyzer.TryGetPolyLinear(argument, x, out var rate, out _)
|| rate.ContainsNode(x) || TreeAnalyzer.IsZero(rate))
return null;
var secant = MathS.Sec(argument);
var cosecant = new Cosecantf(argument);
Entity? function = null;
var sums = new List<(Entity Radicand, Entity Exponent, Entity A, bool Plus)>();
foreach (var (factor, underneath) in FactorsOfTheIntegrand(expr))
{
if (!factor.ContainsNode(x))
continue;
if ((factor == secant || factor == cosecant) && function is null && !underneath)
{
function = factor;
continue;
}
var (radicand, exponent) = factor is Powf(var @base, var power) ? (@base, power) : (factor, (Entity)Number.Integer.One);
if (exponent.ContainsNode(x) || ReadAsOnePlusMinusAFunction(radicand, secant, cosecant) is not var (sumConstant, plus, _))
return null;
sums.Add((radicand, underneath ? (-exponent).InnerSimplified : exponent, sumConstant, plus));
}
if (function is null || sums.Count != 2 || sums[0].Plus == sums[1].Plus
|| ReadAsOnePlusMinusAFunction(sums[0].Radicand, secant, cosecant) is not var (_, _, firstIsSecant)
|| ReadAsOnePlusMinusAFunction(sums[1].Radicand, secant, cosecant) is not var (_, _, secondIsSecant)
|| firstIsSecant != secondIsSecant || firstIsSecant != (function == secant))
return null;
// The half-odd one, which is written through the other; a power of it at least a half
// first, so that what is left of it is a whole power to expand.
static bool IsHalfOdd(Entity exponent) => exponent is Number.Rational rational and not Number.Integer
&& rational.ERational.Denominator.Equals(EInteger.FromInt32(2)) && rational.ERational.Numerator.CanFitInInt32();
var order = sums[0].Exponent is Number.Rational { ERational.Sign: > 0 } && IsHalfOdd(sums[0].Exponent) ? new[] { 0, 1 } : new[] { 1, 0 };
var (half, other) = (sums[order[0]], sums[order[1]]);
if (!IsHalfOdd(half.Exponent))
return null;
var n = ((Number.Rational)half.Exponent).ERational;
// With u the other sum, the half-odd one is c (2 - u/a), whatever the signs.
var u = Variable.CreateUnique(expr, "u_conjugate");
var c = half.A;
var a = other.A;
var leftOver = n.Subtract(ERational.FromInt32(1).Divide(ERational.FromInt32(2)));
var inU = (MathS.Pow(u, other.Exponent - Number.Rational.Create(1, 2))
* MathS.Pow(2 - u / a, Number.Integer.Create(leftOver.ToEInteger()))).InnerSimplified;
inU = Functions.PartialFractions.Bare(inU);
if (inU.ContainsNode(x) || inU.Nodes.Any(node => node == MathS.NaN))
return null;
var inX = inU.Substitute(u, other.Radicand) * other.Radicand.Differentiate(x);
// The square of the constant: -c^(2n)/(a rate^2).
var constantSquared = -MathS.Pow(c, Number.Integer.Create(n.Multiply(ERational.FromInt32(2)).ToEInteger())) / (a * MathS.Sqr(rate));
if (!Functions.PartialFractions.HoldsAtSampledPoints(constantSquared * MathS.Sqr(inX), MathS.Sqr(expr), x))
return null;
if (Integration.ComputeAsAQuestionOfItsOwn(inU, u, integrateByParts) is not { } inTermsOfU
|| inTermsOfU.Nodes.Any(node => node == MathS.NaN))
return null;
return expr * inTermsOfU.Substitute(u, other.Radicand) / inX;
}

/// <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>.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -908,6 +908,9 @@ private static Entity Normalized(Entity expr, Entity.Variable x) =>
// And beside a half-odd power of `c + d sec(y)`, which the half-angle tangent leaves as a
// second root: in sin(y/2), where the two powers of cos(y) they bring make a whole one.
if ((answer = IndefiniteIntegralSolver.SolveTwoHalfOddPowersOfSecantSumsByTheHalfAngleSine(expr, x, integrateByParts)) is { }) return answer;
// And the secant beside powers of a + a sec(y) and c - c sec(y), whose product is a
// square of the tangent: in u = a + a sec(y), a symbolic power of it too.
if ((answer = IndefiniteIntegralSolver.SolveASecantBesidePowersOfItsConjugateSums(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;
// The hyperbolic sine's, off the real line: 1 + i sinh(y) is (cosh(y/2) + i sinh(y/2))^2.
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>sec(y) (a ± a sec(y))^m (c ∓ c sec(y))^n</c>, with <c>n</c> half-odd and <c>m</c> a
/// symbol, in <c>u = a ± a sec(y)</c>, and the cosecant's. Rubi's 4.5.2.3.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class ASecantBesideConjugateSumsIntegralTest
{
[Theory]
[InlineData("sec(g + f*x)*(a + a*sec(g + f*x))^m*(c - c*sec(g + f*x))^(5/2)")]
[InlineData("sec(g + f*x)*(a + a*sec(g + f*x))^m*sqrt(c - c*sec(g + f*x))")]
[InlineData("sec(x)*(a - a*sec(x))^m*sqrt(c + c*sec(x))")]
[InlineData("csc(x)*(a + a*csc(x))^m*sqrt(c - c*csc(x))")]
public void InTheSum(string integrand)
{
var integral = integrand.ToEntity().Integrate("x");
var text = integral.Stringize();
Assert.DoesNotContain("integral(", text);
Assert.True(text.Length < 2000, $"{text.Length} characters of answer for {integrand}");
Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("c", 0.9).Substitute("m", 2.3).Substitute("g", 0.2).Substitute("f", 0.7);
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