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 @@ -508,6 +508,25 @@ antiderivative on each side of it, as Rubi's is
| `"1/((2 + 3*x^2)^(1/4)*(4 + 3*x^2))".ToEntity().Integrate("x")` | `integral(...)` | the same in `(2 + 3 x^2)^(1/4)` |
| `"1/((-2 + 3*x^2)*(-1 + 3*x^2)^(1/4))".ToEntity().Integrate("x")` | `integral(...)` | `-(arctan(u) + artanh(u))/(2 sqrt(6))`, `u = sqrt(3) x/(sqrt(2) (-1 + 3 x^2)^(1/4))` |

### A product of powers of the trigonometric functions, some not whole, is integrated as sine and cosine powers

**Answers where there were none.** `(a sin(x))^(5/2) sqrt(b sec(x))` was declined, and so were
`(d csc(x))^(3/2) sqrt(c sec(x))`, `cos(x)^(7/2)/sin(x)^(7/2)` and the rest of the products of
powers of the trigonometric functions of one argument, each of which is `sin^M cos^N` times a constant
on every interval where both are continuous. With the exponents adding up to an even number that is
`tan^M (1 + tan^2)^(-(M + N)/2)`, a function of the tangent, and with `M` or `N` an odd whole number
the plain product, which the substitution by the other function answers: Chebyshev's three cases.
It is integrated so now, and the answer is the integrand times the antiderivative of that form over
the form, which holds on every such interval, the constants inside the roots whatever their signs.
Rubi's 4.1.0 to 4.6.0 ([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"(a*sin(x))^(5/2)*sqrt(b*sec(x))".ToEntity().Integrate("x")` | `integral(...)` | the integrand times an antiderivative in `sqrt(tan(x))` over `tan(x)^(5/2) (1 + tan(x)^2)^(-1)` |
| `"cos(x)^(7/2)/sin(x)^(7/2)".ToEntity().Integrate("x")` | `integral(...)` | the same over `tan(x)^(-7/2)` |
| `"(b*tan(x)^3)^(3/2)".ToEntity().Integrate("x")` | `integral(...)` | the same over `tan(x)^(9/2)` |
| `"(d*sec(x))^(5/2)*sqrt(b*tan(x))".ToEntity().Integrate("x")` | `integral(...)` | the integrand times an antiderivative in `sqrt(sin(x))` over `sin(x)^(1/2) cos(x)^(-3)` |

### The third case of a binomial differential is right for a negative `x` too

**Answers where there were none.** Chebyshev's third case, `x^m (a + b x^n)^(p/q)` with
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8795,6 +8795,136 @@ private static bool TryReadAHomogeneousTrigonometricPolynomial(
return true;
}

/// <summary>
/// A product of powers of the trigonometric functions of one argument, some of them not
/// whole, which is <c>sin(z)^M cos(z)^N</c> times a constant on every interval where both
/// are continuous: with <c>M + N</c> even integrated as
/// <c>tan(z)^M (1 + tan(z)^2)^(-(M + N)/2)</c>, and with <c>M</c> or <c>N</c> an odd whole
/// number as the plain product, Chebyshev's other two cases.
/// </summary>
/// <remarks>
/// <para>
/// <c>sqrt(c sin(x))/sqrt(d cos(x))</c> is <c>sqrt(c/d) sqrt(tan(x))</c> wherever both
/// roots are real, and was declined, with the rest of such products the corpus has, a
/// constant inside a root among them: <c>(d csc(x))^(3/2) sqrt(c sec(x))</c>,
/// <c>(a sin(x))^(5/2) sqrt(b sec(x))</c>, <c>(d sec(x))^(5/2) sqrt(b tan(x))</c>. Each is
/// a power of the sine times a power of the cosine, and with the exponents adding up to an
/// even number it is a function of the tangent: <c>sin^M cos^N</c> is <c>tan^M cos^(M + N)</c>,
/// and <c>cos^(M + N)</c> is <c>(1 + tan^2)^(-(M + N)/2)</c> exactly. Rubi's 4.1.0 to
/// 4.6.0.
/// </para>
/// <para>
/// The constant is not written: the answer is the integrand times the antiderivative of the
/// tangent's form over that form, a quotient whose logarithmic derivative is zero -- both
/// halves have <c>(M cot(z) - N tan(z)) z'</c> -- so it is constant wherever it is
/// continuous, and the answer holds on every interval where the integrand is, the constants
/// inside the roots whatever their signs.
/// https://github.com/asc-community/AngouriMath/issues/718
/// </para>
/// </remarks>
internal static Entity? SolveAProductOfPowersOfTrigonometricFunctionsThroughTheTangent(Entity expr, Entity.Variable x, bool integrateByParts)
{
if (!expr.Nodes.Any(node => node is Powf(var radicand, Number.Rational fraction) && fraction is not Number.Integer && radicand.ContainsNode(x)))
return null;
Entity? argument = null;
Number.Rational sine = Number.Integer.Zero;
Number.Rational cosine = Number.Integer.Zero;
var notWhole = false;
Entity constant = Number.Integer.One;
Entity varying = Number.Integer.One;
foreach (var (factor, underneath) in ThroughWholePowersOfProducts(FactorsOfTheIntegrand(expr)))
{
if (!factor.ContainsNode(x))
{
constant = underneath ? constant / factor : constant * factor;
continue;
}
varying = underneath ? varying / factor : varying * factor;
var (@base, power) = factor is Powf(var b, var p) && p.Evaled is Number.Rational r
? (b, r)
: (factor, (Number.Rational)Number.Integer.One);
// A constant multiple of a function, and a whole power of one inside the power:
// `(b tan(z)^3)^(3/2)`.
if (@base is Mulf(var left, var right))
@base = !left.ContainsNode(x) ? right : !right.ContainsNode(x) ? left : @base;
if (@base is Powf(var inner, Number.Integer innerPower))
(@base, power) = (inner, (Number.Rational)(power * innerPower));
if (underneath)
power = (Number.Rational)(-power);
if (power is not Number.Integer)
notWhole = true;
// The powers of the sine and of the cosine each function is.
(Entity Of, int OnSine, int OnCosine)? read = @base switch
{
Sinf(var of) => (of, 1, 0),
Cosf(var of) => (of, 0, 1),
Tanf(var of) => (of, 1, -1),
Cotanf(var of) => (of, -1, 1),
Secantf(var of) => (of, 0, -1),
Cosecantf(var of) => (of, -1, 0),
_ => null
};
if (read is null || argument is not null && argument != read.Value.Of)
return null;
var (functionOf, onSine, onCosine) = read.Value;
argument = functionOf;
sine = (Number.Rational)(sine + power * onSine);
cosine = (Number.Rational)(cosine + power * onCosine);
}
if (argument is null || !notWhole
|| !TreeAnalyzer.TryGetPolyLinear(argument, x, out var slope, out _) || TreeAnalyzer.IsZero(slope))
return null;
Entity form;
if ((sine + cosine) is Number.Integer { EInteger: var total } && total.IsEven && total.CanFitInInt32())
{
// The exponents add up to an even number: a function of the tangent.
var tangent = MathS.Tan(argument);
var half = -total.ToInt32Checked() / 2;
form = half == 0
? MathS.Pow(tangent, sine)
: MathS.Pow(tangent, sine) * MathS.Pow(1 + MathS.Sqr(tangent), half);
}
else if (sine is Number.Integer { EInteger: var m } && !m.IsEven || cosine is Number.Integer { EInteger: var n } && !n.IsEven)
// An odd whole power of one of them: the plain product, which the substitution by
// the other answers, `(d sec(x))^(5/2) sqrt(b tan(x))` being `sin^(1/2) cos^(-3)`
// times a constant.
form = (sine is Number.Integer { IsZero: true } ? Number.Integer.One : MathS.Pow(MathS.Sin(argument), sine))
* (cosine is Number.Integer { IsZero: true } ? Number.Integer.One : MathS.Pow(MathS.Cos(argument), cosine));
else
return null;
form = form.InnerSimplified;
// Already the form, and then the substitution's to answer as it stands.
if (varying == form || varying.InnerSimplified == form)
return null;
if (Integration.ComputeAsTheSameQuestion(form, x, integrateByParts) is not { } answer
|| answer.Nodes.Any(node => node == MathS.NaN))
return null;
return constant * varying * answer / form;
}


/// <summary>
/// <paramref name="factors"/> with a whole power of a product read as the product of the
/// powers, a negative one on the other side of the bar: <c>1/(cos(x)^(7/2) sqrt(sin(x)))</c>
/// comes back from the polynomial term's <c>c/g</c> as <c>(cos(x)^(7/2) sqrt(sin(x)))^(-1)</c>,
/// one factor that is no function's power, and the half-angle substitution searched it for
/// twenty seconds before the tangent's form was reached.
/// </summary>
private static IEnumerable<(Entity Factor, bool Underneath)> ThroughWholePowersOfProducts(IEnumerable<(Entity Factor, bool Underneath)> factors)
{
foreach (var (factor, underneath) in factors)
{
if (factor is not Powf(Mulf product, Number.Integer { EInteger: var power }) || power.IsZero)
{
yield return (factor, underneath);
continue;
}
var magnitude = Number.Integer.Create(power.Abs());
foreach (var child in Mulf.LinearChildren(product))
yield return (magnitude == Number.Integer.One ? child : MathS.Pow(child, magnitude), power.Sign < 0 ? !underneath : underneath);
}
}

/// <summary>
/// A power of the cotangent that is not whole below the bar of an integrand with the
/// tangent of the same argument in it, written as a power of the tangent above it:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -901,6 +901,10 @@ private static Entity Normalized(Entity expr, Entity.Variable x) =>
// A power of the cotangent that is not whole beside the tangent, written as the
// tangent's with the constant that takes in front, which the substitution below reads.
if ((answer = IndefiniteIntegralSolver.SolveByWritingAPowerOfTheCotangentInTheTangent(expr, x, integrateByParts)) is { }) return answer;
// A product of powers of the trigonometric functions of one argument, some not whole,
// whose powers of the sine and cosine add up to an even number: the tangent's form
// times a constant, which the substitution below reads.
if ((answer = IndefiniteIntegralSolver.SolveAProductOfPowersOfTrigonometricFunctionsThroughTheTangent(expr, x, integrateByParts)) is { }) return answer;
if ((answer = IndefiniteIntegralSolver.SolveByTangentSubstitution(expr, x, integrateByParts)) is { }) return answer;
// A root of a quadratic in the tangent with a linear term, rotated until it has
// none: `1/sqrt(a + b tan(x) + c tan(x)^2)` is `1/sqrt(A + C tan(y)^2)` under
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//
// 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 product of powers of the trigonometric functions of one argument, some not whole, which
/// is <c>sin^M cos^N</c> times a constant on every interval where both are continuous, with
/// <c>M + N</c> even, the tangent's form <c>tan^M (1 + tan^2)^(-(M + N)/2)</c>, or with
/// <c>M</c> or <c>N</c> an odd whole number, the plain product. Compared as complex numbers
/// on both sides of the zeros, where the integrands leave the reals and the constant is what
/// keeps the answer right. Rubi's 4.1.0 to 4.6.0.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class ProductOfTrigonometricPowersIntegralTest
{
[Theory]
[InlineData("sqrt(c*sin(x))/sqrt(d*cos(x))")]
[InlineData("(d*csc(x))^(3/2)*sqrt(c*sec(x))")]
[InlineData("(a*sin(x))^(5/2)*sqrt(b*sec(x))")]
[InlineData("(b*tan(x)^3)^(3/2)")]
[InlineData("sqrt(c*sin(a + b*x))/(d*cos(a + b*x))^(9/2)")]
[InlineData("cos(x)^(7/2)/sin(x)^(7/2)")]
[InlineData("(d*sec(x))^(5/2)*sqrt(b*tan(x))")]
[InlineData("sqrt(b*tan(x))/(a*sin(x))^(3/2)")]
public void AsPowersOfTheSineAndTheCosine(string integrand)
{
var integral = integrand.ToEntity().Integrate("x");
Assert.DoesNotContain("integral(", integral.Stringize());
Assert.DoesNotContain("NaN", integral.Stringize());
Entity Pinned(Entity e) => e.Substitute("a", 0.4).Substitute("b", 1.1).Substitute("c", 1.3).Substitute("d", 0.8);
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
var original = Pinned(integrand.ToEntity());
var compared = 0;
foreach (var at in new[] { -2.6, -1.1, -0.4, 0.3, 0.8, 1.2, 2.0 })
{
var want = original.Substitute("x", at).EvalNumerical();
if (want.IsNaN)
continue;
compared++;
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}");
}
Assert.True(compared >= 6, $"only {compared} points could be compared for {integrand}");
}
}
}
Loading