Skip to content
18 changes: 18 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,24 @@ and `sec^2` over its cube, the answers are a quarter and three-fifths the size
| `"sec(c + d*x)^4/(a*cos(c + d*x) + i*a*sin(c + d*x))".ToEntity().Integrate("x")` | `integral(...)` | 209 characters, where the unreleased master gave 780 |
| `"sec(c + d*x)^2/(a*cos(c + d*x) + i*a*sin(c + d*x))^3".ToEntity().Integrate("x")` | `integral(...)` | 488 characters, where the unreleased master gave 810 |

### A root of a sum of the cosine and the sine beside a half power of one of them is read in the tangent

**Answers where there were none.** `csc(x) sqrt(cos(x) + sin(x))/cos(x)^(3/2)` was declined, Rubi's 4.7.7, and so
were its respellings below. `sqrt(a cos + b sin)` beside `cos^(n/2)` is `sqrt(a + b tan) cos^((n + 1)/2)` wherever
both sides are real, since the root of a product of two non-negative numbers is the product of their roots, and
when the sum and the cosine are both negative the two signs cancel the same way; the sine reads as `a/tan + b`. What
is left is a function of the tangent times a whole power of the cosine. Where the integrand is not real the answer
takes the branches of that respelling
([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"csc(x)*sqrt(cos(x) + sin(x))/cos(x)^(3/2)".ToEntity().Integrate("x")` | `integral(...)` | 94 characters |
| `"sqrt(cos(x) + sin(x))/cos(x)^(5/2)".ToEntity().Integrate("x")` | `integral(...)` | `(1 + tan(x)) ^ (3/2) / (3/2)` |
| `"sqrt(2*cos(x) + 3*sin(x))/(sin(x)*cos(x)^(3/2))".ToEntity().Integrate("x")` | `integral(...)` | 138 characters |
| `"sec(x)*sqrt(cos(x) - sin(x))/sin(x)^(3/2)".ToEntity().Integrate("x")` | `integral(...)` | 74 characters |
| `"1/(cos(x)^(3/2)*sqrt(3*cos(x) + sin(x)))".ToEntity().Integrate("x")` | `integral(...)` | `2 * sqrt(tan(x) + 3)` |

### An even rational function with symbols in it is not searched for a substitution

**Answers where there were none.** `(d + e x^2 + f x^4)/(x^4 (a + b x^2 + c x^4)^2)` ran past a minute: the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10997,6 +10997,101 @@ bool Read(Entity e, int times)
return Integration.ComputeAsTheSameQuestion(rewritten, x, integrateByParts)?.Pipe(answer => answer / constant);
}

/// <summary>
/// <c>(a cos(x) + b sin(x))^(m/2) cos(x)^(n/2)</c>, <c>m</c> and <c>n</c> odd, written as
/// <c>(a + b tan(x))^(m/2) cos(x)^((m + n)/2)</c>, and the sine's the same way with
/// <c>a cot(x) + b</c>; <see langword="null"/> where the integrand is not that shape.
/// </summary>
/// <remarks>
/// <para>
/// <c>A^(m/2) c^(n/2)</c> is <c>(sqrt(A)/sqrt(c))^m c^((m + n)/2)</c> for principal roots,
/// and <c>sqrt(A)/sqrt(c)</c> is <c>sqrt(A/c)</c> where <c>A</c> and <c>c</c> have one
/// sign: both roots real, or both imaginary. Where the signs differ the product is
/// imaginary, so with no other root of <c>x</c> in the integrand to make it real again,
/// the two agree wherever the integrand is real. Rubi's 4.7.7
/// <c>csc(x) sqrt(cos(x) + sin(x))/cos(x)^(3/2)</c> is
/// <c>sqrt(1 + tan(x)) csc(x) sec(x)</c> so, and that is the tangent substitution's.
/// </para>
/// <para>
/// Only a linear <c>A</c>: <c>sqrt(c^3)</c> and <c>c^(3/2)</c> differ in sign where
/// <c>c</c> is negative, so a cubic over the cube of the cosine is not the same quotient.
/// https://github.com/asc-community/AngouriMath/issues/718
/// </para>
/// </remarks>
private static Entity? WithARootOverTheCosineOrTheSine(Entity expr, Entity.Variable x)
{
var factors = FactorsOfTheIntegrand(expr);
int trigonometric = -1, root = -1;
for (var i = 0; i < factors.Count; i++)
{
var factor = factors[i].Factor;
if (factor is Powf(var @base, Number.Rational fraction) && fraction is not Number.Integer && @base.ContainsNode(x))
{
if (!fraction.ERational.Denominator.Equals(EInteger.FromInt32(2)))
return null;
if (@base is Cosf or Sinf && @base.DirectChildren[0] == x)
{
if (trigonometric >= 0)
return null;
trigonometric = i;
}
else
{
if (root >= 0)
return null;
root = i;
}
}
else if (factor.Nodes.Any(node => node is Powf(var b, var p) && b.ContainsNode(x) && p is not Number.Integer))
return null;
}
if (trigonometric < 0 || root < 0)
return null;
var (trigonometricPower, trigonometricUnderneath) = factors[trigonometric];
var (rootPower, rootUnderneath) = factors[root];
if (trigonometricPower is not Powf(var function, Number.Rational trigonometricExponent)
|| rootPower is not Powf(var sum, Number.Rational rootExponent))
return null;

var cosine = MathS.Cos(x);
var sine = MathS.Sin(x);
var c = Variable.CreateUnique(expr, "c_cos");
var s = Variable.CreateUnique(expr * c, "s_sin");
var inCs = sum.Substitute(cosine, c).Substitute(sine, s);
if (inCs.ContainsNode(x)
|| !TreeAnalyzer.TryGetPolyLinear(inCs, c, out var a, out var rest)
|| !TreeAnalyzer.TryGetPolyLinear(rest, s, out var b, out var free)
|| a.ContainsNode(c) || a.ContainsNode(s) || b.ContainsNode(c) || b.ContainsNode(s)
|| free.Evaled != Number.Integer.Zero || a.Evaled == Number.Integer.Zero || b.Evaled == Number.Integer.Zero)
return null;

var m = rootUnderneath ? rootExponent.ERational.Numerator.Negate() : rootExponent.ERational.Numerator;
var n = trigonometricUnderneath ? trigonometricExponent.ERational.Numerator.Negate() : trigonometricExponent.ERational.Numerator;
var tangent = MathS.Tan(x);
var quotient = function is Cosf ? a + b * tangent : a / tangent + b;
var whole = m.Add(n).Divide(EInteger.FromInt32(2));

Entity above = Number.Integer.One, below = Number.Integer.One;
for (var i = 0; i < factors.Count; i++)
if (i != trigonometric && i != root)
{
if (factors[i].Underneath)
below *= factors[i].Factor;
else
above *= factors[i].Factor;
}
var halfOfM = Number.Rational.Create(m.Abs(), EInteger.FromInt32(2));
if (m.Sign > 0)
above *= MathS.Pow(quotient, halfOfM);
else
below *= MathS.Pow(quotient, halfOfM);
if (whole.Sign > 0)
above *= MathS.Pow(function, Number.Integer.Create(whole));
else if (whole.Sign < 0)
below *= MathS.Pow(function, Number.Integer.Create(whole.Abs()));
return above / below;
}

/// <summary>
/// An integrand that is a function of <c>tan(x)</c> and of nothing else, integrated by
/// the substitution <c>u = tan(x)</c>, under which <c>dx</c> is <c>du/(1 + u^2)</c>.
Expand Down Expand Up @@ -11061,6 +11156,11 @@ node is Powf(var product, Number.Rational fraction) && fraction is not Number.In
? apart
: node);

// A half-odd power of `a cos(x) + b sin(x)` beside a half-odd power of the cosine is
// a root of their quotient, a function of the tangent; the sine the same way.
if (WithARootOverTheCosineOrTheSine(expr, x) is { } combined)
expr = combined;

var tangent = MathS.Tan(x);
// Or a sine or a cosine under a root, for the writing by the sign below, or an even
// power of the secant or the cosecant under one, which the next step writes in the
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//
// 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 root of <c>a cos(x) + b sin(x)</c> beside a half-odd power of the cosine, read as a
/// root of <c>a + b tan(x)</c>: <c>csc(x) sqrt(cos(x) + sin(x))/cos(x)^(3/2)</c> is
/// <c>sqrt(1 + tan(x)) csc(x) sec(x)</c>, and the sine's the same way. Checked where both
/// roots are imaginary too, since the integrand is real there. Rubi's 4.7.7.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class ARootBesideAHalfPowerOfTheCosineIntegralTest
{
[Theory]
[InlineData("csc(x)*sqrt(cos(x) + sin(x))/cos(x)^(3/2)", 200)]
[InlineData("sqrt(cos(x) + sin(x))/cos(x)^(5/2)", 100)]
[InlineData("sqrt(2*cos(x) + 3*sin(x))/(sin(x)*cos(x)^(3/2))", 300)]
[InlineData("sec(x)*sqrt(cos(x) - sin(x))/sin(x)^(3/2)", 200)]
public void DifferentiatesBack(string integrand, int atMost)
{
var integral = integrand.ToEntity().Integrate("x");
var text = integral.Stringize();
Assert.DoesNotContain("integral(", text);
Assert.True(text.Length < atMost, $"{text.Length} characters of answer for {integrand}");
var derivative = integral.Substitute("C", 0).Differentiate("x");
var original = integrand.ToEntity();
foreach (var at in new[] { "0.35", "0.6", "3.8", "3.9" })
{
var want = original.Substitute("x", at.ToEntity()).EvalNumerical();
var got = derivative.Substitute("x", at.ToEntity()).EvalNumerical();
Assert.True(Math.Abs((double)want.ImaginaryPart) < 1e-9, $"{integrand} is not real at x = {at}");
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