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
13 changes: 13 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,19 @@ number; the zero is a power `1/x`, whose integral is a logarithm. A symbolic `b`
| `"sin(a + ln(c*x^2)*sqrt(-1/4))".ToEntity().Integrate("x")` | `integral(...)` | powers of `x` and `c x^2` and a logarithm, through `e^(i a)` |
| `"1/cos(a - 2*i*ln(c*x))^(3/2)".ToEntity().Integrate("x")` | `integral(...)` | an arctangent and a logarithm of a root, through `e^(i a)` |

### A constant plus an imaginary cosine and sine is an exponential

**Answers where there were none.** `(A + B cos(x))/(a + b cos(x) + i b sin(x))` was declined, with the
rest of Rubi's 4.7.7 over `a + b cos(x) ± i b sin(x)`. That denominator is `a + b e^(±i x)`, and with
the cosine and sine above it written as exponentials too the integrand is rational in `e^(i x)`;
`1/(a + b cos(x) + i b sin(x))`, answered on the unreleased master by the half-angle tangent, is
answered in the exponential now ([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"(k + q*cos(x))/(a + b*cos(x) + i*b*sin(x))".ToEntity().Integrate("x")` | `integral(...)` | logarithms in `e^(i x)` and a term `e^(-i x)/a`, piecewise in the symbols |
| `"1/(a + b*cos(x) + i*b*sin(x))".ToEntity().Integrate("x")` | `integral(...)` | a logarithm in `e^(i x)`, piecewise in `b = 0` |

### `e^(n i arctan(a x))` to a power that is not whole is integrated

**Answers where there were none.** `e^(n i arctan(L))` is written algebraically, as
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25102,19 +25102,40 @@ private static bool TryReadAnImaginaryTangent(Entity sum, Entity.Variable x, out
/// <c>cos(x)^2/(a cos(x) + i a sin(x))^3</c> was declined after twelve seconds.
/// </summary>
/// <remarks>
/// <para>
/// The sibling of <see cref="SolveByWritingAnImaginaryTangentAsAnExponential"/>, which reads
/// <c>A + i A tan(y)</c>; below the bar only, for the same reason.
/// </para>
/// <para>
/// With a constant beside the pair, <c>a + b cos(y) + i b sin(y)</c> is <c>a + b e^(i y)</c>,
/// and there the cosine and sine of <c>y</c> elsewhere are written as exponentials too, so
/// that the whole is rational in <c>e^(i y)</c>: Rubi's
/// <c>(A + B cos(x))/(a + b cos(x) + i b sin(x))</c> and its kin were declined, a cosine
/// beside the exponential being nothing the rules for either read. Without the constant the
/// cosine above the bar stays, beside the exponential the closed rules answer.
/// https://github.com/asc-community/AngouriMath/issues/718
/// </para>
/// </remarks>
internal static Entity? SolveByWritingAnImaginarySumOfACosineAndASineAsAnExponential(Entity expr, Entity.Variable x, bool integrateByParts)
{
var (above, below) = Functions.SingleQuotient.Of(Functions.SingleQuotient.Combine(expr));
if (!below.ContainsNode(x))
return null;
Entity? argumentBesideAConstant = 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))
if (node is not (Sumf or Minusf) || !node.ContainsNode(x))
return node;
// A constant beside the pair: the pair alone is read, and the constant added back.
Entity? constant = null;
var pair = node;
var terms = Sumf.LinearChildren(node);
if (terms.Count == 3 && terms.Count(term => !term.ContainsNode(x)) == 1)
{
constant = terms.First(term => !term.ContainsNode(x));
pair = terms.Where(term => term.ContainsNode(x)).Aggregate((left, right) => left + right);
}
if (!TryReadACosineAndASine(pair, x, out var cosineCoefficient, out var sineCoefficient, out var argument))
return node;
// The ratio is the imaginary unit one way or the other, and nothing else. Bare:
// `i a/a` simplifies to `i provided not a = 0`, and a condition is not a number
Expand All @@ -25125,10 +25146,30 @@ private static bool TryReadAnImaginaryTangent(Entity sum, Entity.Variable x, out
var plus = ratio.Evaled == MathS.i.Evaled;
if (!plus && ratio.Evaled != (-MathS.i).Evaled)
return node;
return cosineCoefficient * MathS.Pow(MathS.e, ((plus ? MathS.i : -MathS.i) * argument).InnerSimplified);
var exponential = cosineCoefficient * MathS.Pow(MathS.e, ((plus ? MathS.i : -MathS.i) * argument).InnerSimplified);
if (constant is null)
return exponential;
if (argumentBesideAConstant is not null && argumentBesideAConstant != argument)
return node;
argumentBesideAConstant = argument;
return constant + exponential;
});
if (rewritten == below)
return null;
// Beside a constant, the cosine and sine of that argument everywhere as exponentials.
if (argumentBesideAConstant is { } inExponentials)
{
var rising = MathS.Pow(MathS.e, (MathS.i * inExponentials).InnerSimplified);
var falling = MathS.Pow(MathS.e, (-MathS.i * inExponentials).InnerSimplified);
Entity InExponentials(Entity side) => side.Replace(node => node switch
{
Cosf(var y) when y == inExponentials => (rising + falling) / 2,
Sinf(var y) when y == inExponentials => (rising - falling) / (2 * MathS.i),
_ => node,
});
above = InExponentials(above);
rewritten = InExponentials(rewritten);
}
// And a whole power of what was written split, `(A e^(i y))^n` as `A^n e^(i n y)`, so
// that the exponential stands on its own for the rules that read one: the rule that
// distributes such powers takes a constant with a symbol in it and leaves a number,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//
// 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 + b cos(x) ± i b sin(x)</c> is <c>a + b e^(±i x)</c>, and with the cosine and sine above
/// it written as exponentials too the integrand is rational in <c>e^(i x)</c>. Rubi's 4.7.7.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
/// <remarks>Compared as complex numbers, the integrand being complex.</remarks>
[Trait("Area", "Calculus")]
public sealed class ConstantPlusAnImaginaryCosineAndSineIntegralTest
{
[Theory]
[InlineData("(k + q*cos(x))/(a + b*cos(x) + i*b*sin(x))")]
[InlineData("(k + q*sin(x))/(a + b*cos(x) - i*b*sin(x))")]
[InlineData("(k + p*cos(x) + q*sin(x))/(a + b*cos(x) + i*b*sin(x))")]
[InlineData("cos(x)/(a + b*cos(x) + i*b*sin(x))")]
public void IsRationalInTheExponential(string integrand)
{
var integral = integrand.ToEntity().Integrate("x");
Assert.DoesNotContain("integral(", integral.Stringize());
Entity Pinned(Entity e) => e.Substitute("a", 2.3).Substitute("b", 0.9)
.Substitute("k", 1.1).Substitute("p", 0.4).Substitute("q", 0.6);
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
var original = Pinned(integrand.ToEntity());
foreach (var at in new[] { -2.3, -1.1, -0.4, 0.4, 1.1, 2.3 })
{
var want = original.Substitute("x", at).EvalNumerical();
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}");
}
}
}
}
Loading