diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md
index 5ff95b8fd..0ecd17c01 100644
--- a/BREAKING-CHANGES.md
+++ b/BREAKING-CHANGES.md
@@ -350,6 +350,30 @@ cache is now keyed on the downcasting setting too
|---|---|---|
| `MathS.FromString("1/3").Evaled` under downcasting off, after the same parse with it on | `1/3` | `0.3333…` (one hundred digits) |
+### A symbolic power of the secant, a half-odd power of `a ± a sin` beside other linears, and an even power of the cosine over a sine's binomial are integrated
+
+**Answers where there were none.** `(b sec(g + f x))^m tan(g + f x)^3` and three more of Rubi's 4.3.0 were declined only
+because `m` was not a number: in `u = sec(y)` it is `(b u)^m (u^2 - 1)/u`, and the rule for an odd power of the tangent
+beside a function of the secant now reads a power free of x whatever its exponent.
+`1/(sqrt(a + a sin(y)) (c + d sin(y))^2)`, `1/((a + a sin(y))^2 sqrt(c - c sin(y)))` and fourteen more of 4.1.2.1 and
+4.1.3.1 are answered by the half angle at which `a + a sin(y)` is a square, which they never reached: the polynomial
+term wrote the reciprocal as a power and asked the substitution search, which spent the budget. A half-odd power of
+`a ± a sin(y)` beside at least one other linear in the same sine or cosine is asked it first now. And
+`cos(y)^4 sin(y)^3/(a + b sin(y))^2` and six more of 4.1.2.2 are read by the rule below, an even whole power of the
+cosine being a power of `1 - sin(y)^2` exactly, where a power of the sine stands above the bar beside it. That file's
+neighbours the unreleased master answered are respelled, some shorter and some longer ([#233](https://github.com/asc-community/AngouriMath/issues/233)).
+
+| Input | Was (2.5.0) | Now |
+|---|---|---|
+| `"(b*sec(g+f*x))^m*tan(g+f*x)^3".ToEntity().Integrate("x")` | `integral(...)` | 144 characters |
+| `"cot(g+f*x)^5*(b*csc(g+f*x))^m".ToEntity().Integrate("x")` | `integral(...)` | 221 characters |
+| `"1/(sqrt(a+a*sin(g+f*x))*(c+d*sin(g+f*x))^2)".ToEntity().Integrate("x")` | `integral(...)` | 2,679 characters |
+| `"1/((a+a*sin(g+f*x))^2*sqrt(c-c*sin(g+f*x)))".ToEntity().Integrate("x")` | `integral(...)` | 329 characters |
+| `"cos(c+d*x)^4*sin(c+d*x)^3/(a+b*sin(c+d*x))^2".ToEntity().Integrate("x")` | `integral(...)` | 4,174 characters |
+| `"cos(c+d*x)^6*sin(c+d*x)^3/(a+b*sin(c+d*x))^3".ToEntity().Integrate("x")` | `integral(...)` | 9,029 characters |
+| `"cos(c+d*x)^4*sin(c+d*x)^2/(a+b*sin(c+d*x))^2".ToEntity().Integrate("x")` | `integral(...)`; 7,068 characters, in 1.3 s, on the unreleased master | 3,677 characters, in 154 ms |
+| `"cos(c+d*x)^2*sin(c+d*x)^2/(a+b*sin(c+d*x))^3".ToEntity().Integrate("x")` | `integral(...)`; 4,621 characters, in 431 ms, on the unreleased master | 6,584 characters, in 570 ms |
+
### A polynomial in a sine over a power of a binomial in it is written in powers of the binomial
**Answers where there were none.** `(A + B cos(c + d x) + C cos(c + d x)^2)/(a + b cos(c + d x))^4` ran out of time, and
diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
index 7e8094ecb..8f3135b4e 100644
--- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
@@ -5364,7 +5364,8 @@ Entity Integral(ERational power)
///
/// https://github.com/asc-community/AngouriMath/issues/718
///
- internal static Entity? SolveByTheHalfAngleWhereOnePlusASineIsASquare(Entity expr, Entity.Variable x, bool integrateByParts)
+ internal static Entity? SolveByTheHalfAngleWhereOnePlusASineIsASquare(Entity expr, Entity.Variable x, bool integrateByParts,
+ bool collectTheLinears = false)
{
if (!Integration.AnsweringTheQuestionAsked)
return null;
@@ -5445,6 +5446,41 @@ Entity Integral(ERational power)
constant = MathS.Pow(a, exponent) * constant;
return constant * MathS.Pow(square, Number.Integer.Create(twoP));
});
+ // `c + d sin(y)` as `(c - d) + 2d sin(u)^2`, collected, where asked: written
+ // `c + d (2 sin(u)^2 - 1)`, `sin(u)^5/(c + d (2 sin(u)^2 - 1))^2` ran past the budget.
+ if (collectTheLinears)
+ {
+ var (function, square) = sineKind ? (sine, MathS.Sqr(sinU)) : (cosine, MathS.Sqr(cosU));
+ rewritten = rewritten.Replace(node =>
+ {
+ if (node is not Sumf || !node.ContainsNode(function))
+ return node;
+ Entity constant = Number.Integer.Zero, coefficient = Number.Integer.Zero;
+ foreach (var term in Sumf.LinearChildren(node))
+ {
+ if (!term.ContainsNode(x))
+ {
+ constant += term;
+ continue;
+ }
+ Entity factor = Number.Integer.One;
+ var seen = false;
+ foreach (var part in Mulf.LinearChildren(term))
+ if (part == function && !seen)
+ seen = true;
+ else if (part.ContainsNode(x))
+ return node;
+ else
+ factor *= part;
+ if (!seen)
+ return node;
+ coefficient += factor;
+ }
+ var shifted = (constant - coefficient).InnerSimplified;
+ var collected = (2 * coefficient).InnerSimplified * square;
+ return VanishesIdentically(shifted) ? collected : shifted + collected;
+ });
+ }
var inU = rewritten.Substitute(sine, sineInU).Substitute(cosine, cosineInU);
// What is left of the variable is the variable itself: the angle is `rate x + offset`
// and `u` is half of it (a quarter turn on for the sine), so `x` is
@@ -5468,6 +5504,82 @@ Entity Integral(ERational power)
return answer.Nodes.Any(node => node == MathS.NaN) ? null : answer;
}
+ ///
+ /// A half-odd power of a ± a sin(y) times whole or half-odd powers of other linears
+ /// in the same sine, c + d sin(y), and nothing else of x: by
+ /// , asked first. The cosine's
+ /// the same.
+ ///
+ ///
+ ///
+ /// Rubi's 1/(sqrt(a + a sin(e + f x)) (c + d sin(e + f x))^2),
+ /// 1/((a + a sin(e + f x))^2 sqrt(c - c sin(e + f x))) and their kin in 4.1.2.1
+ /// ran past the budget. The half angle answers each, its question in u taking
+ /// tens of milliseconds, but it was not reached: the polynomial term above it wrote the
+ /// reciprocal as a power, took the constant out of the root, and asked the substitution
+ /// search below that, whose simplification of what it found took the whole budget.
+ ///
+ ///
+ /// The answer is that rule's, so exact on the same terms. Read narrowly: a product of
+ /// powers of linears in one sine or one cosine, one of them half-odd and of
+ /// a ± a sin(y), and at least two of them, since one alone is
+ /// 's.
+ /// https://github.com/asc-community/AngouriMath/issues/233
+ ///
+ ///
+ internal static Entity? SolveAHalfOddPowerOfOnePlusMinusASineBesidePowersOfLinearsInIt(Entity expr, Entity.Variable x, bool integrateByParts)
+ {
+ if (!Integration.AnsweringTheQuestionAsked)
+ return null;
+ if (!TryReadAsQuotient(expr, out var above, out var below))
+ (above, below) = (expr, Number.Integer.One);
+ Entity? function = null;
+ var (factors, halfOdd) = (0, false);
+ foreach (var side in new[] { above, below })
+ foreach (var factor in Mulf.LinearChildren(side))
+ {
+ if (!factor.ContainsNode(x))
+ continue;
+ var (@base, exponent) = factor is Powf(var inner, Number.Rational power) ? (inner, power.ERational) : (factor, ERational.One);
+ var isHalfOdd = exponent.Denominator.Equals(EInteger.FromInt32(2));
+ if (!isHalfOdd && !exponent.Denominator.Equals(EInteger.One))
+ return null;
+ // c + d f(y), the one f throughout, and c there: a power of f itself is not this shape.
+ Entity? found = null;
+ var constant = false;
+ foreach (var term in Sumf.LinearChildren(@base))
+ {
+ if (!term.ContainsNode(x))
+ {
+ constant = true;
+ continue;
+ }
+ foreach (var part in Mulf.LinearChildren(term))
+ if (part.ContainsNode(x))
+ {
+ if (found is not null || part is not (Sinf or Cosf))
+ return null;
+ found = part;
+ }
+ }
+ if (found is null || !constant || function is not null && found != function)
+ return null;
+ function = found;
+ if (isHalfOdd)
+ {
+ if (ReadAsOnePlusMinusAFunction(@base, function is Sinf ? function : MathS.Sin(function.DirectChildren.First()),
+ function is Cosf ? function : MathS.Cos(function.DirectChildren.First())) is null)
+ return null;
+ halfOdd = true;
+ }
+ factors++;
+ }
+ if (!halfOdd || factors < 2 || function is null
+ || !TreeAnalyzer.TryGetPolyLinear(function.DirectChildren.First(), x, out var rate, out _) || rate.ContainsNode(x))
+ return null;
+ return SolveByTheHalfAngleWhereOnePlusASineIsASquare(expr, x, integrateByParts, collectTheLinears: true);
+ }
+
///
/// Powers of a ± a sin(y) with a symbolic exponent, or beside a fractional power of
/// g cos(y), and anything else in the sine alone, under u = sin(y):
@@ -6154,8 +6266,12 @@ static bool IsHalfOdd(Entity exponent) => exponent is Number.Rational rational a
///
/// Rubi's cot(c + d x)/(a + b sec(c + d x))^(3/2) was declined and
/// cot(c + d x)^3 sqrt(a + b sec(c + d x)) ran past thirty seconds, where in the
- /// secant each is a rational function beside the root of a linear.
+ /// secant each is a rational function beside the root of a linear. A power free of
+ /// x is read whatever its exponent: Rubi's (b sec(e + f x))^m tan(e + f x)^3,
+ /// 4.3.0, was declined only because m was not a number, where in u it is
+ /// (b u)^m (u^2 - 1)/u.
/// https://github.com/asc-community/AngouriMath/issues/718
+ /// https://github.com/asc-community/AngouriMath/issues/233
///
internal static Entity? SolveAnOddPowerOfTheTangentBesideAFunctionOfTheSecant(Entity expr, Entity.Variable x, bool integrateByParts)
{
@@ -6174,10 +6290,24 @@ static bool IsHalfOdd(Entity exponent) => exponent is Number.Rational rational a
if (argument is null || !TreeAnalyzer.TryGetPolyLinear(argument, x, out var rate, out _)
|| rate.ContainsNode(x) || TreeAnalyzer.IsZero(rate))
return null;
- if (!expr.Nodes.All(node => !node.ContainsNode(x)
- || node is Variable or Sumf or Minusf or Mulf or Divf or Sinf or Cosf or Secantf or Cosecantf or Tanf or Cotanf
- || node is Powf(_, Number.Rational)))
- return null;
+ // A symbolic power too, of a product of constants and secants or cosecants:
+ // `(b sec(y))^m` is `(b u)^m`. Of a sum it brings `(a + b u)^m/u`, not elementary.
+ var z = Variable.CreateUnique(expr, "z_secant");
+ bool IsAProductOfSecants(Entity @base)
+ {
+ var inZ = @base.Replace(node => node is Secantf(var a) && a == argument || node is Cosecantf(var b) && b == argument ? z : node);
+ return !inZ.ContainsNode(x) && inZ.Nodes.All(node => !node.ContainsNode(z) || node == z || node is Mulf or Divf or Powf(_, Number.Rational));
+ }
+ var symbolic = false;
+ foreach (var node in expr.Nodes)
+ {
+ if (!node.ContainsNode(x) || node is Variable or Sumf or Minusf or Mulf or Divf or Sinf or Cosf or Secantf or Cosecantf or Tanf or Cotanf
+ || node is Powf(_, Number.Rational))
+ continue;
+ if (node is not Powf(var @base, var exponent) || exponent.ContainsNode(x) || !IsAProductOfSecants(@base))
+ return null;
+ symbolic = true;
+ }
var t = Variable.CreateUnique(expr, "t_odd_tangent");
var u = Variable.CreateUnique(expr, "u_secant");
foreach (var ofTheSecant in new[] { true, false })
@@ -6193,6 +6323,9 @@ static bool IsHalfOdd(Entity exponent) => exponent is Number.Rational rational a
var n = exponents[0];
if (rest.ContainsNode(t) || !n.IsInteger() || n.ToEInteger().IsEven || !n.ToEInteger().CanFitInInt32())
continue;
+ // Beside a symbolic power a negative one leaves `u^m/(u (u^2 - 1)^k)`, hypergeometric.
+ if (symbolic && n.Sign < 0)
+ continue;
var inU = rest.Replace(node => node switch
{
Secantf(var a) when ofTheSecant && a == argument => u,
@@ -27949,22 +28082,57 @@ when two.Equals(EInteger.FromInt32(2)) && e1 == MathS.e && e2 == MathS.e
/// The rules after it took the whole quotient through the half-angle and wrote 30 million
/// characters for (A + B cos(x) + C cos(x)^2)/(a + b cos(x))^4 in eighteen seconds, where
/// 1/(a + b cos(x))^4 alone is 44 thousand. Rubi's 4.1.4 and 4.2.4
- /// (a + b cos)^m (A + B cos + C cos^2) at m = -4 timed out.
+ /// (a + b cos)^m (A + B cos + C cos^2) at m = -4 timed out. An even whole power
+ /// of the other function above the bar is a power of 1 - f^2, exactly, and read as
+ /// one: Rubi's 4.1.2.2 cos(c + d x)^6 sin(c + d x)^3/(a + b sin(c + d x))^2 and its
+ /// kin timed out too, where in the sine each is the polynomial over the power.
/// https://github.com/asc-community/AngouriMath/issues/718
+ /// https://github.com/asc-community/AngouriMath/issues/233
///
internal static Entity? SolveAPolynomialInASineOverAPowerOfABinomialInItInPowersOfTheBinomial(Entity expr, Entity.Variable x)
{
if (!Integration.AnsweringTheQuestionAsked)
return null;
var functions = expr.Nodes.Where(node => node is Sinf or Cosf && node.ContainsNode(x)).Distinct().ToList();
- if (functions.Count != 1)
+ if (functions.Count is not (1 or 2))
+ return null;
+ var s = Variable.CreateUnique(expr, "s_pow");
+ Entity? function = null, inS = null;
+ foreach (var candidate in functions)
+ {
+ // The other of the two only to even whole powers above the bar, each a power of
+ // 1 - f^2: `cos(y)^4 sin(y)^3/(a + b sin(y))^2` is a polynomial in the sine over the power.
+ var written = candidate;
+ if (functions.Count == 2)
+ {
+ var other = functions.Single(f => f != candidate);
+ if (candidate is Sinf && other is not Cosf || candidate is Cosf && other is not Sinf
+ || other.DirectChildren.First() != candidate.DirectChildren.First())
+ return null;
+ // And only beside the function itself above the bar: `cos(y)^4/(a + b sin(y))^8`, with
+ // no sine above it, is answered by the rules after it in 40 thousand characters, and
+ // was 106 thousand here, in 60% more time.
+ var (top, _) = Functions.SingleQuotient.Of(expr);
+ if (!top.ContainsNode(candidate))
+ continue;
+ written = expr.Replace(node => node is Powf(var @base, Number.Integer { EInteger: var power }) && @base == other
+ && power.Sign > 0 && power.IsEven && power.CompareTo(EInteger.FromInt32(12)) <= 0
+ ? MathS.Pow(1 - MathS.Sqr(candidate), Number.Integer.Create(power / 2))
+ : node);
+ if (written.ContainsNode(other))
+ continue;
+ }
+ else
+ written = expr;
+ function = candidate;
+ inS = written.Substitute(candidate, s);
+ break;
+ }
+ if (function is null || inS is null)
return null;
- var function = functions[0];
var argument = function switch { Sinf(var y) => y, Cosf(var y) => y, _ => null! };
if (!TreeAnalyzer.TryGetPolyLinear(argument, x, out var slope, out _) || Functions.PartialFractions.IsZeroAsAValue(slope))
return null;
- var s = Variable.CreateUnique(expr, "s_pow");
- var inS = expr.Substitute(function, s);
if (inS.ContainsNode(x))
return null;
var (above, below) = Functions.SingleQuotient.Of(inS);
diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs
index 66cfa421c..f23457c19 100644
--- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs
@@ -772,6 +772,9 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr)
// A polynomial beside error functions of one shifted linear, written in it: by parts in
// x leaves a Gaussian of the shifted linear beside a polynomial, which nothing reads.
if ((answer = IndefiniteIntegralSolver.SolveAPolynomialBesideErrorFunctionsOfAShiftedLinear(expr, x, integrateByParts)) is { }) return answer;
+ // A half-odd power of a ± a sin(y) beside powers of other linears in the sine, by the half
+ // angle, before the polynomial term: beneath it the substitution search ran out of time.
+ if ((answer = IndefiniteIntegralSolver.SolveAHalfOddPowerOfOnePlusMinusASineBesidePowersOfLinearsInIt(expr, x, integrateByParts)) is { }) return answer;
if ((answer = IndefiniteIntegralSolver.SolveAsPolynomialTerm(expr, x, integrateByParts)) is { }) return answer;
// A product of powers of the variable with a power of a constant multiple of it
// among them, `(c x)^m x^n`, by the power rule with the written power kept as
diff --git a/Sources/Tests/UnitTests/Calculus/AHalfOddPowerOfOnePlusMinusASineBesidePowersOfLinearsIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/AHalfOddPowerOfOnePlusMinusASineBesidePowersOfLinearsIntegralTest.cs
new file mode 100644
index 000000000..a02ecd30a
--- /dev/null
+++ b/Sources/Tests/UnitTests/Calculus/AHalfOddPowerOfOnePlusMinusASineBesidePowersOfLinearsIntegralTest.cs
@@ -0,0 +1,60 @@
+//
+// 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
+{
+ ///
+ /// A half-odd power of a ± a sin(y) beside whole or half-odd powers of other linears in
+ /// the sine, by the half angle at which 1 ± sin(y) is a square, asked before the
+ /// polynomial term: beneath it the substitution search ran past the budget on Rubi's 4.1.2.1
+ /// 1/(sqrt(a + a sin(e + f x)) (c + d sin(e + f x))^2) and its kin. The cosine's the same.
+ /// #233
+ ///
+ [Trait("Area", "Calculus")]
+ public sealed class AHalfOddPowerOfOnePlusMinusASineBesidePowersOfLinearsIntegralTest
+ {
+ [Theory]
+ [InlineData("1/(sqrt(a + a*sin(g + f*x))*(c + d*sin(g + f*x))^2)")]
+ [InlineData("1/((a + a*sin(g + f*x))^(3/2)*(c + d*sin(g + f*x))^3)")]
+ [InlineData("1/((a + a*sin(x))^(5/2)*(c + d*sin(x)))")]
+ [InlineData("(a + a*sin(g + f*x))^(5/2)/(c + d*sin(g + f*x))^2")]
+ [InlineData("1/((a + a*sin(g + f*x))^2*sqrt(c - c*sin(g + f*x)))")]
+ [InlineData("1/((a + a*sin(x))^3*(c - c*sin(x))^(5/2))")]
+ [InlineData("1/((a + a*sin(g + f*x))^(3/2)*(c - c*sin(g + f*x))^(3/2))")]
+ [InlineData("1/((a + a*cos(x))^2*sqrt(c - c*cos(x)))")]
+ [InlineData("(a + a*cos(x))^(5/2)/(c + d*cos(x))^2")]
+ [InlineData("1/((3 + 3*sin(x))^(1/2)*(2 + sin(x))^2)")]
+ public void ByTheHalfAngle(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("c", 0.9).Substitute("d", 0.4)
+ .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, 4.1 })
+ {
+ 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}");
+ }
+ }
+}
diff --git a/Sources/Tests/UnitTests/Calculus/APolynomialInASineOverAPowerOfABinomialInItIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/APolynomialInASineOverAPowerOfABinomialInItIntegralTest.cs
index 60fb55b54..3032c4cb7 100644
--- a/Sources/Tests/UnitTests/Calculus/APolynomialInASineOverAPowerOfABinomialInItIntegralTest.cs
+++ b/Sources/Tests/UnitTests/Calculus/APolynomialInASineOverAPowerOfABinomialInItIntegralTest.cs
@@ -55,5 +55,22 @@ public void IsAnsweredAndDifferentiatesBack(string integrand)
DifferentiatesBack(integrand, ("a", 1.3), ("b", 0.7), ("c", 0.7), ("d", 1.07), ("p", 0.43), ("q", -0.45), ("r", 0.22));
DifferentiatesBack(integrand, ("a", 2), ("b", -1.5), ("c", -0.3), ("d", 0.6), ("p", -1), ("q", 2), ("r", 0.5));
}
+
+ ///
+ /// An even power of the other function is a power of 1 - f^2, so the numerator is a
+ /// polynomial in the one: Rubi's 4.1.2.2 cos(c + d x)^6 sin(c + d x)^3/(a + b sin(c + d x))^2
+ /// and its kin, which timed out.
+ ///
+ [Theory]
+ [InlineData("cos(c+d*x)^4*sin(c+d*x)^3/(a+b*sin(c+d*x))^2")]
+ [InlineData("cos(c+d*x)^4*sin(c+d*x)^3/(a+b*sin(c+d*x))^3")]
+ [InlineData("cos(c+d*x)^6*sin(c+d*x)^2/(a+b*sin(c+d*x))^2")]
+ [InlineData("cos(c+d*x)^6*sin(c+d*x)^3/(a+b*sin(c+d*x))^3")]
+ [InlineData("sin(c+d*x)^2*cos(c+d*x)^2/(a+b*cos(c+d*x))^2")]
+ public void WithAnEvenPowerOfTheOther(string integrand)
+ {
+ DifferentiatesBack(integrand, ("a", 1.3), ("b", 0.7), ("c", 0.7), ("d", 1.07));
+ DifferentiatesBack(integrand, ("a", 2), ("b", -1.5), ("c", -0.3), ("d", 0.6));
+ }
}
}
diff --git a/Sources/Tests/UnitTests/Calculus/AnOddPowerOfTheTangentBesideTheSecantIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/AnOddPowerOfTheTangentBesideTheSecantIntegralTest.cs
index 33efff53b..a6a7e9471 100644
--- a/Sources/Tests/UnitTests/Calculus/AnOddPowerOfTheTangentBesideTheSecantIntegralTest.cs
+++ b/Sources/Tests/UnitTests/Calculus/AnOddPowerOfTheTangentBesideTheSecantIntegralTest.cs
@@ -46,5 +46,53 @@ public void InTheSecant(string integrand)
}
Assert.True(compared >= 5, $"only {compared} points could be compared for {integrand}");
}
+
+ ///
+ /// A symbolic power of b sec(y), which is (b u)^m in u: Rubi's 4.3.0,
+ /// declined only because m was not a number. Checked for a negative b as well,
+ /// where the integrand is complex.
+ ///
+ [Theory]
+ [InlineData("(b*sec(g + f*x))^m*tan(g + f*x)^3", 0.4)]
+ [InlineData("(b*sec(g + f*x))^m*tan(g + f*x)^5", 0.4)]
+ [InlineData("cot(g + f*x)^3*(b*csc(g + f*x))^m", 0.4)]
+ [InlineData("cot(g + f*x)^5*(b*csc(g + f*x))^m", 0.4)]
+ [InlineData("sec(x)^m*tan(x)^3", 0.4)]
+ [InlineData("(b*sec(g + f*x))^m*tan(g + f*x)^3", -0.4)]
+ [InlineData("cot(g + f*x)^5*(b*csc(g + f*x))^m", -0.4)]
+ public void ASymbolicPowerOfTheSecant(string integrand, double b)
+ {
+ 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("b", b).Substitute("m", 0.6).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}");
+ }
+
+ ///
+ /// A symbolic power of a sum in the secant leaves (a + b u)^m/u, which has no
+ /// elementary antiderivative, and so does a negative power of the tangent beside
+ /// (b sec(y))^m: declined.
+ ///
+ [Theory]
+ [InlineData("(a + b*sec(x))^m*tan(x)^3")]
+ [InlineData("(b*sec(x))^m*cot(x)")]
+ public void ASymbolicPowerThatIsNotElementary(string integrand)
+ => Assert.Contains("integral(", integrand.ToEntity().Integrate("x").Stringize());
}
}