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 @@ -381,6 +381,25 @@ two more of Rubi's 4.5.2.3. The two sums multiply to `-a c tan^2`, so in `u = a
| `"sec(e + f*x)*(a + a*sec(e + f*x))^m*(c - c*sec(e + f*x))^(5/2)".ToEntity().Integrate("x")` | `integral(...)` | 469 characters |
| `"sec(e + f*x)*(a + a*sec(e + f*x))^m*sqrt(c - c*sec(e + f*x))".ToEntity().Integrate("x")` | `integral(...)` | 268 characters |

### `a cos + i a sin` beside powers of the secant is written over the cosine

**Answers where there were none, and shorter ones.** `sec(c + d x)^5/(a cos(c + d x) + i a sin(c + d x))^2` was declined,
and so were `sec^4` over the cube, and `sec^6` over the sum and over its cube, Rubi's 4.7.2. `A cos(y) ± i A sin(y)` below
the bar, with nothing but cosines and secants of `y` beside it, is written `A cos(y) (1 ± i tan(y))` now, where the
exponential it also is gave nothing the rules read: a power of the secant over a power of `1 ± i tan(y)` is what the
rules for `a + i a tan` take. 2.5.0 declined all six below; where the unreleased master answered, `sec^4` over the sum
and `sec^2` over its cube, the answers are a quarter and three-fifths the size
([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"sec(c + d*x)^6/(a*cos(c + d*x) + i*a*sin(c + d*x))".ToEntity().Integrate("x")` | `integral(...)` | 419 characters |
| `"sec(c + d*x)^5/(a*cos(c + d*x) + i*a*sin(c + d*x))^2".ToEntity().Integrate("x")` | `integral(...)` | 668 characters |
| `"sec(c + d*x)^4/(a*cos(c + d*x) + i*a*sin(c + d*x))^3".ToEntity().Integrate("x")` | `integral(...)` | 639 characters |
| `"sec(c + d*x)^6/(a*cos(c + d*x) + i*a*sin(c + d*x))^3".ToEntity().Integrate("x")` | `integral(...)` | 771 characters |
| `"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 |

### 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 @@ -28604,6 +28604,62 @@ node is Powf(Mulf(var left, var right), Number.Integer power)
return Integration.ComputeAsTheSameQuestion((above / rewritten).InnerSimplified, x, integrateByParts);
}

/// <summary>
/// <c>A cos(y) + i A sin(y)</c> below the bar, with nothing but cosines of <c>y</c> and their
/// reciprocals beside it, written as <c>A cos(y) (1 + i tan(y))</c>, and <c>A cos(y) - i A sin(y)</c>
/// as <c>A cos(y) (1 - i tan(y))</c>: a power of the secant over a power of <c>1 ± i tan(y)</c>,
/// which the rules for that sum answer.
/// </summary>
/// <remarks>
/// Where the exponential <see cref="SolveByWritingAnImaginarySumOfACosineAndASineAsAnExponential"/>
/// writes declines: an odd power of the secant beside it, `sec(y)^5/(a cos(y) + i a sin(y))^2`,
/// is a half-odd power of `1 + tan(y)^2` in the tangent, and that is `(1 - i t)^(5/2) (1 + i t)^(1/2)`
/// over the sum, which the rules for `a + i a tan` take. Rubi's 4.7.2.
/// https://github.com/asc-community/AngouriMath/issues/718
/// </remarks>
internal static Entity? SolveByWritingAnImaginarySumOfACosineAndASineOverTheCosine(Entity expr, Entity.Variable x, bool integrateByParts)
{
var (above, below) = Functions.SingleQuotient.Of(Functions.SingleQuotient.Combine(expr));
if (!below.ContainsNode(x))
return null;
Entity? argumentOfThePair = null;
var rewritten = below.Replace(node =>
{
if (node is not (Sumf or Minusf) || !node.ContainsNode(x)
|| !TryReadACosineAndASine(node, x, out var cosineCoefficient, out var sineCoefficient, out var argument)
|| argumentOfThePair is { } && argumentOfThePair != argument)
return node;
var ratio = Functions.PartialFractions.Bare((sineCoefficient / cosineCoefficient).InnerSimplified);
if (ratio.Evaled is not Number.Complex)
ratio = Functions.PartialFractions.Bare(ratio.Simplify());
var plus = ratio.Evaled == MathS.i.Evaled;
if (!plus && ratio.Evaled != (-MathS.i).Evaled)
return node;
argumentOfThePair = argument;
return cosineCoefficient * MathS.Cos(argument) * (1 + (plus ? MathS.i : -MathS.i) * MathS.Tan(argument));
});
if (rewritten == below || argumentOfThePair is not { } y)
return null;
// Nothing beside the pair but cosines and secants of its argument: a sine elsewhere is the
// exponential's.
bool OnlyCosines(Entity side, bool andTheTangent) => side.Nodes.All(node => node switch
{
Tanf(var a) => andTheTangent && a == y,
Sinf or Tanf or Cotanf or Cosecantf => false,
Cosf(var a) => a == y,
Secantf(var a) => a == y,
_ => true,
});
if (!OnlyCosines(above, andTheTangent: false) || !OnlyCosines(rewritten, andTheTangent: true))
return null;
// A whole power of the product split, so that the power of the sum stands on its own.
rewritten = rewritten.Replace(node =>
node is Powf(Mulf(var left, var right), Number.Integer power) && (left.Nodes.Any(n => n is Tanf) || right.Nodes.Any(n => n is Tanf))
? MathS.Pow(left, power) * MathS.Pow(right, power)
: node);
return Integration.ComputeAsTheSameQuestion((above / rewritten).InnerSimplified, x, integrateByParts);
}

/// <summary>
/// Bioche's first two rules for the hyperbolic functions: a rational function of
/// <c>sinh(y)</c> and <c>cosh(y)</c> that is odd in the hyperbolic sine is a rational
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,7 @@ private static Entity Normalized(Entity expr, Entity.Variable x) =>
if ((answer = IndefiniteIntegralSolver.SolveByWritingAnImaginaryTangentAsAnExponential(expr, x, integrateByParts)) is { }) return answer;
// And `A cos(z) + i A sin(z)`, which is `A e^(i z)`, where no rotation is real.
if ((answer = IndefiniteIntegralSolver.SolveByWritingAnImaginarySumOfACosineAndASineAsAnExponential(expr, x, integrateByParts)) is { }) return answer;
if ((answer = IndefiniteIntegralSolver.SolveByWritingAnImaginarySumOfACosineAndASineOverTheCosine(expr, x, integrateByParts)) is { }) return answer;
// Exponentials of quadratics, with sines, cosines and sums of them multiplied out:
// each term is the Gaussian's, where the search would take the product by parts.
if ((answer = IndefiniteIntegralSolver.SolveAProductOfExponentialsOfQuadratics(expr, x)) is { }) return answer;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//
// 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>
/// <c>a cos(y) + i a sin(y)</c> beside powers of the secant is written <c>a cos(y) (1 + i tan(y))</c>,
/// which the rules for <c>a + i a tan</c> take. Rubi's 4.7.2.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class AnImaginaryCosineAndSineSumOverTheCosineIntegralTest
{
[Theory]
[InlineData("sec(x)^5/(a*cos(x) + i*a*sin(x))^2")]
[InlineData("sec(x)^6/(a*cos(x) - i*a*sin(x))")]
public void DifferentiatesBack(string integrand)
{
var integral = integrand.ToEntity().Integrate("x");
Assert.DoesNotContain("integral(", integral.Stringize());
Entity Pinned(Entity e) => e.Substitute("a", 3);
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
var original = Pinned(integrand.ToEntity());
foreach (var at in new[] { -1.1, -0.4, 0.3, 1.2 })
{
var want = original.Substitute("x", at).EvalNumerical();
var got = derivative.Substitute("x", at).EvalNumerical();
var scale = Math.Max(1, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart));
Assert.True(Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart) < 1e-9 * scale,
$"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
}
}
}
}
Loading