From 613216e17eb69787fbddaea644ce082bc2f783e0 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sun, 4 Oct 2026 18:13:18 +0000 Subject: [PATCH] A constant plus an imaginary cosine and sine is an exponential a + b cos(y) + i b sin(y) is a + b e^(i y): the rule that writes the pair below the bar as the exponential reads it beside a constant, and there writes the cosine and sine of y elsewhere as exponentials too, so that the whole is rational in e^(i y). Without a constant the cosine above the bar stays, beside the exponential the closed rules answer. Rubi's (A + B cos(x))/(a + b cos(x) + i b sin(x)) and its kin were declined. Part of #718. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 13 +++++ .../Integration/IndefiniteIntegralSolver.cs | 47 +++++++++++++++++-- ...lusAnImaginaryCosineAndSineIntegralTest.cs | 46 ++++++++++++++++++ 3 files changed, 103 insertions(+), 3 deletions(-) create mode 100644 Sources/Tests/UnitTests/Calculus/ConstantPlusAnImaginaryCosineAndSineIntegralTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index dc39a1fe6..9002f67d0 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -705,6 +705,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 875a31f1c..ec90bc8d9 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -23990,19 +23990,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 @@ -24013,10 +24034,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}"); + } + } + } +}