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
13 changes: 13 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,19 @@ after it
|---|---|---|
| `"(a*c + b*c*x)^(-3-2*p)*(f + g*x)*(a^2 + 2*a*b*x + b^2*x^2)^p".ToEntity().Integrate("x")` | `integral(...)`; an answer with no value on the unreleased master | the antiderivative |

### A rational function of the secant with two sums below the bar is written in the cosine

**Answers where there were none.** `sec(x)/((a + b sec(x)) (c + d sec(x))^2)` ran past thirty seconds: the
half-angle substitution read the secant's spelling into a rational function of the half-angle tangent that a
substitution search expanded without end. As one quotient in the cosine it is
`cos(x)^2/((a cos(x) + b) (c cos(x) + d)^2)`, answered in a second, and it is written so where two sums in the
secant stand below the bar and none above ([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"sec(x)/((a + b*sec(x))*(c + d*sec(x))^2)".ToEntity().Integrate("x")` | `integral(...)`; past thirty seconds on the unreleased master | 9,439 characters, a case for each sign of two discriminants |
| `"sec(x)/((a + b*sec(x))^2*(c + d*sec(x)))".ToEntity().Integrate("x")` | `integral(...)`; past thirty seconds on the unreleased master | 9,430 characters |

### `a + i a tan` of a shifted linear below the bar is integrated in the linear

**Answers where there were none.** `sqrt(a + i a tan(g + f x)) (A + B tan(g + f x))/sqrt(c - i c tan(g + f x))`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27741,6 +27741,58 @@ bool IsAPolynomialInBoth(Entity polynomial)
return null;
}

/// <summary>
/// A rational function of the secant of one argument, or of the cosecant, written in the
/// cosine, or the sine, as one quotient: <c>sec(z)/((a + b sec(z)) (c + d sec(z))^2)</c> is
/// <c>cos(z)^2/((a cos(z) + b) (c cos(z) + d)^2)</c> wherever the secant is defined, exactly.
/// </summary>
/// <remarks>
/// The half-angle substitution read the secant's spelling into a rational function of the
/// half-angle tangent that a substitution search expanded past thirty seconds, where the
/// cosine's is answered in half of one. Only for two sums or more, all below the bar. Rubi's 4.5.2.3.
/// https://github.com/asc-community/AngouriMath/issues/718
/// </remarks>
internal static Entity? SolveByWritingARationalFunctionOfTheSecantInTheCosine(Entity expr, Entity.Variable x, bool integrateByParts)
{
Entity? reciprocal = null;
foreach (var node in expr.Nodes)
{
if (!node.ContainsNode(x) || node is Entity.Variable)
continue;
switch (node)
{
case Secantf or Cosecantf:
if (reciprocal is null)
reciprocal = node;
else if (reciprocal != node)
return null;
break;
case Sumf or Minusf or Mulf or Divf or Powf(_, Number.Integer):
break;
default:
// Inside the reciprocal's own argument, a linear in x.
if (reciprocal is not null && reciprocal.DirectChildren.First().Nodes.Contains(node))
break;
if (expr.Nodes.Any(other => other is Secantf or Cosecantf && other.DirectChildren.First().Nodes.Contains(node)))
break;
return null;
}
}
if (reciprocal is null || !TreeAnalyzer.TryGetPolyLinear(reciprocal.DirectChildren.First(), x, out _, out _))
return null;
// Only two sums in the secant or more, all below the bar: one, or one above, the rules for
// the secant answer as written, and in the cosine `sec(x)^5/(a + b sec(x))^3` ran past five
// seconds where it is half of one.
var (above, below) = Functions.SingleQuotient.Of(expr);
bool ASum(Entity node) => node is Sumf or Minusf && node.ContainsNode(reciprocal);
if (above.Nodes.Any(ASum) || below.Nodes.Where(ASum).Distinct().Count() < 2)
return null;
var argument = reciprocal.DirectChildren.First();
var function = reciprocal is Secantf ? MathS.Cos(argument) : MathS.Sin(argument);
var rewritten = Functions.SingleQuotient.Combine(expr.Replace(node => node == reciprocal ? 1 / function : node));
return Integration.ComputeAsTheSameQuestion(rewritten, x, integrateByParts);
}

/// <summary>
/// A half-odd power of the secant or the cosecant, in an integrand with the cosine or the
/// sine of the same argument in it, written as a power of the cosine or the sine:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1101,6 +1101,9 @@ private static Entity Normalized(Entity expr, Entity.Variable x) =>
// Bioche's first two rules before the third: a rational function of the two that
// is odd in one of them is a rational function of the other, smaller than the
// half-angle tangent's and answered more shortly.
// A rational function of the secant alone in the cosine first, as one quotient: the
// half-angle tangent of the secant's spelling was a search past thirty seconds.
if ((answer = IndefiniteIntegralSolver.SolveByWritingARationalFunctionOfTheSecantInTheCosine(expr, x, integrateByParts)) is { }) return answer;
if ((answer = IndefiniteIntegralSolver.SolveByBiochesOddSubstitution(expr, x, integrateByParts)) is { }) return answer;
if ((answer = IndefiniteIntegralSolver.SolveByHalfAngleSubstitution(expr, x, integrateByParts)) is { }) return answer;
// The substitution by a sine or a cosine where an odd power of the complement is
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//
// 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 rational function of the secant with two sums in it below the bar, written in the cosine as
/// one quotient: <c>sec(x)/((a + b sec(x)) (c + d sec(x))^2)</c> is
/// <c>cos(x)^2/((a cos(x) + b) (c cos(x) + d)^2)</c>. 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 ARationalFunctionOfTheSecantInTheCosineIntegralTest
{
[Theory]
[InlineData("sec(x)/((a + b*sec(x))*(c + d*sec(x))^2)")]
[InlineData("sec(x)/((a + b*sec(x))^2*(c + d*sec(x)))")]
public void AsOneQuotient(string integrand)
{
var integral = integrand.ToEntity().Integrate("x");
var text = integral.Stringize();
Assert.DoesNotContain("integral(", text);
Assert.True(text.Length < 20000, $"{text.Length} characters of answer for {integrand}");
Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.7).Substitute("c", 2.1).Substitute("d", 0.6);
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