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}"); + } + } + } +}