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
19 changes: 19 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,25 @@ in Rubi's 1.1.3.4 come out shorter as well, and those that took two seconds take
| `"1/(x^8*(a + b*x^3)*(c + d*x^3))".ToEntity().Integrate("x")` | `integral(...)` | 1,217 characters |
| `"1/(x^6*(a + b*x^4)*(c + d*x^4))".ToEntity().Integrate("x")` | `integral(...)` | 795 characters |

### A power of the cosine beside a secant binomial is integrated in the cosine

**Answers where there were none.** `cos(c + d x)^2/(a + b sec(c + d x))` was declined, and so were the cosine's powers
beside `A + B sec + C sec^2` over the same binomial (Rubi's 4.5.1.2, 4.5.3.1 and 4.5.4.2). Wherever the secant is
defined, the integrand is one quotient in the cosine, `cos(c + d x)^3/(a cos(c + d x) + b)`, and it is integrated
in that spelling now. The same holds for a power of the sine beside a cosecant binomial. Only for the binomial to
the first power: for a higher power the secant's own rules answer
([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"cos(c + d*x)^2/(a + b*sec(c + d*x))".ToEntity().Integrate("x")` | `integral(...)` | 1,091 characters |
| `"cos(c + d*x)^2*(A + B*sec(c + d*x))/(a + b*sec(c + d*x))".ToEntity().Integrate("x")` | `integral(...)` | 4,347 characters |
| `"cos(c + d*x)^2*(A + C*sec(c + d*x)^2)/(a + b*sec(c + d*x))".ToEntity().Integrate("x")` | `integral(...)` | 3,999 characters |
| `"cos(c + d*x)^3*(B*sec(c + d*x) + C*sec(c + d*x)^2)/(a + b*sec(c + d*x))".ToEntity().Integrate("x")` | `integral(...)` | 4,349 characters |
| `"cos(c + d*x)^4*(B*sec(c + d*x) + C*sec(c + d*x)^2)/(a + b*sec(c + d*x))".ToEntity().Integrate("x")` | `integral(...)` | 5,579 characters |
| `"cos(c + d*x)^2*(A + B*sec(c + d*x) + C*sec(c + d*x)^2)/(a + b*sec(c + d*x))".ToEntity().Integrate("x")` | `integral(...)` | 12,065 characters |
| `"sin(x)^2/(a + b*csc(x))".ToEntity().Integrate("x")` | `integral(...)` | 898 characters |

### One linear twice is not two linear powers

**Answers that had no value.** `(a c + b c x)^(-3 - 2 p) (f + g x) (a^2 + 2 a b x + b^2 x^2)^p` was answered
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28951,12 +28951,14 @@ bool IsAPolynomialInBoth(Entity polynomial)
/// <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.
/// cosine's is answered in half of one. Only for two sums or more, all below the bar, or beside the
/// cosine itself. Rubi's 4.5.2.3 and 4.5.4.2.
/// https://github.com/asc-community/AngouriMath/issues/718
/// </remarks>
internal static Entity? SolveByWritingARationalFunctionOfTheSecantInTheCosine(Entity expr, Entity.Variable x, bool integrateByParts)
{
Entity? reciprocal = null;
var partners = new List<Entity>();
foreach (var node in expr.Nodes)
{
if (!node.ContainsNode(x) || node is Entity.Variable)
Expand All @@ -28969,6 +28971,9 @@ bool IsAPolynomialInBoth(Entity polynomial)
else if (reciprocal != node)
return null;
break;
case Cosf or Sinf:
partners.Add(node);
break;
case Sumf or Minusf or Mulf or Divf or Powf(_, Number.Integer):
break;
default:
Expand All @@ -28982,15 +28987,29 @@ bool IsAPolynomialInBoth(Entity polynomial)
}
if (reciprocal is null || !TreeAnalyzer.TryGetPolyLinear(reciprocal.DirectChildren.First(), x, out _, out _))
return null;
var argument = reciprocal.DirectChildren.First();
var function = reciprocal is Secantf ? MathS.Cos(argument) : MathS.Sin(argument);
// The cosine of the same argument beside the secant, or the sine beside the cosecant, is
// what the rewritten integrand is in already; any other is a second function.
if (partners.Any(partner => partner != function))
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.
// seconds where it is half of one. Beside the cosine itself, though, the secant's rules have
// two functions to read, and in the cosine alone it is one quotient: the half-angle
// substitution wrote `cos(x)^2/(a + b sec(x))` in 117,409 characters where
// `cos(x)^3/(a cos(x) + b)` is 873. Rubi's 4.5.4.2.
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)
if (partners.Count == 0 && (above.Nodes.Any(ASum) || below.Nodes.Where(ASum).Distinct().Count() < 2))
return null;
// Beside the cosine, a sum in the secant below, each to the first power only:
// `cos(x)/(a + b sec(x))^4` in the cosine is a partial fraction search past five seconds,
// which the secant's rules answer in a third of one, and with the sum in the sine instead,
// `csc(x)^3/(a + b sin(x)^3)` is a question for the rules of the sine's binomial.
if (partners.Count > 0
&& (!below.Nodes.Any(ASum) || Mulf.LinearChildren(below).Any(factor => factor is Powf(var @base, _) && ASum(@base))))
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);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//
// 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 cosine beside a rational function of the secant of the same argument, written
/// in the cosine alone, where it is one quotient. Rubi's 4.5.4.2.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class ACosineBesideASecantBinomialIntegralTest
{
[Theory]
[InlineData("cos(c + d*x)^2*(A + K*sec(c + d*x)^2)/(a + b*sec(c + d*x))", 6000)]
[InlineData("cos(c + d*x)^3*(B*sec(c + d*x) + K*sec(c + d*x)^2)/(a + b*sec(c + d*x))", 6000)]
[InlineData("cos(c + d*x)^2*(A + B*sec(c + d*x) + K*sec(c + d*x)^2)/(a + b*sec(c + d*x))", 15000)]
[InlineData("cos(x)^2/(a + b*sec(x))", 1500)]
[InlineData("sin(x)^2/(a + b*csc(x))", 1500)]
public void DifferentiatesBack(string integrand, int atMostCharacters)
{
var integral = integrand.ToEntity().Integrate("x");
Assert.DoesNotContain("integral(", integral.Stringize());
Assert.True(integral.Stringize().Length <= atMostCharacters, $"{integral.Stringize().Length} characters");
Entity Pinned(Entity e) => e.Substitute("a", 2).Substitute("b", 3).Substitute("c", "1/4".ToEntity()).Substitute("d", "3/2".ToEntity())
.Substitute("A", 5).Substitute("B", 7).Substitute("K", 11);
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
var original = Pinned(integrand.ToEntity());
foreach (var at in new[] { "0.35", "0.6", "1.1", "1.4" })
{
var want = original.Substitute("x", at.ToEntity()).EvalNumerical();
var got = derivative.Substitute("x", at.ToEntity()).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)),
$"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
}
}
}
}
Loading