diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md
index 698516842..c28d1a612 100644
--- a/BREAKING-CHANGES.md
+++ b/BREAKING-CHANGES.md
@@ -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
diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
index f5c4c2674..b91ac8539 100644
--- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
@@ -25102,19 +25102,40 @@ private static bool TryReadAnImaginaryTangent(Entity sum, Entity.Variable x, out
/// cos(x)^2/(a cos(x) + i a sin(x))^3 was declined after twelve seconds.
///
///
+ ///
/// The sibling of , which reads
/// A + i A tan(y); below the bar only, for the same reason.
+ ///
+ ///
+ /// With a constant beside the pair, a + b cos(y) + i b sin(y) is a + b e^(i y),
+ /// and there the cosine and sine of y elsewhere are written as exponentials too, so
+ /// that the whole is rational in e^(i y): Rubi's
+ /// (A + B cos(x))/(a + b cos(x) + i b sin(x)) 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
+ ///
///
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
@@ -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,
diff --git a/Sources/Tests/UnitTests/Calculus/ConstantPlusAnImaginaryCosineAndSineIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/ConstantPlusAnImaginaryCosineAndSineIntegralTest.cs
new file mode 100644
index 000000000..f677240e9
--- /dev/null
+++ b/Sources/Tests/UnitTests/Calculus/ConstantPlusAnImaginaryCosineAndSineIntegralTest.cs
@@ -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
+{
+ ///
+ /// a + b cos(x) ± i b sin(x) 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). Rubi's 4.7.7.
+ /// #718
+ ///
+ /// Compared as complex numbers, the integrand being complex.
+ [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}");
+ }
+ }
+ }
+}