From fc6627f40a2122ae5e5cb672e979dcdef9592c20 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Thu, 8 Oct 2026 09:03:16 +0000 Subject: [PATCH] A power of a + i a csch is integrated in the exponential In w = e^y the sum is a (w + i)^2/(w^2 - 1), so a power of it not whole is a^p (w + i)^(2p) (w^2 - 1)^(-p) times a constant on every interval where both are continuous. Part of #718. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 15 +++++ .../Integration/IndefiniteIntegralSolver.cs | 64 +++++++++++++++++++ .../Integration/Integration.Definition.cs | 2 + ...ginaryHyperbolicCosecantSumIntegralTest.cs | 51 +++++++++++++++ 4 files changed, 132 insertions(+) create mode 100644 Sources/Tests/UnitTests/Calculus/APowerOfAnImaginaryHyperbolicCosecantSumIntegralTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 3c6f937bd..5e09bede5 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -237,6 +237,21 @@ nested integrals cost twice as much per level | `new Integralf("t + a".ToEntity(), "t", (0, 1)).Substitute("a", "t")` | `integral(t + t, t, 0, 1)` | `integral(t_1 + t, t_1, 0, 1)` | | `new Summationf("k * a".ToEntity(), "k", 1, 3).Substitute("a", "k")` | `sum(k * k, k, 1, 3)`, 14 | `sum(k_1 * k, k_1, 1, 3)`, `6 k` | +### A power of `a + i a csch` is integrated in the exponential + +**Answers where there were none.** `sqrt(a + i a csch(c + d x))` was declined, with the rest of Rubi's +6.6.3 that is a power of the sum not whole. In `w = e^(c + d x)` the sum is `a (w + i)^2/(w^2 - 1)`, the +square of a complex function over a real one, so its power is `a^p (w + i)^(2p) (w^2 - 1)^(-p)` times a +constant on every interval where both are continuous, and the integrand in `w` is answered at once. The +constant is not written: the answer is the integrand times the antiderivative in `w` over what that +differentiates back to ([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"sqrt(a + i*a*csch(c + d*x))".ToEntity().Integrate("x")` | `integral(...)` | 454 characters | +| `"(a + i*a*csch(c + d*x))^(3/2)".ToEntity().Integrate("x")` | `integral(...)` | 834 characters | +| `"1/sqrt(a - i*a*csch(c + d*x))".ToEntity().Integrate("x")` | `integral(...)` | 575 characters | + ### A power of a constant below `1e-50` is no longer simplified to zero **Answers that were wrong.** Evaluation rounds a value within `1e-50` of an integer onto it, and diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 6eeb69950..800082a70 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -24709,6 +24709,70 @@ private static (Entity OverTheTwo, Entity Argument)? ReadTheHyperbolicFunctions( return constant * result; } + /// + /// A power of a ± i a csch(y), not whole, integrated in w = e^y: there + /// csch(y) is 2 w/(w^2 - 1) and the sum is a (w ± i)^2/(w^2 - 1), so its power is + /// a^p (w ± i)^(2p) (w^2 - 1)^(-p) times a constant on every interval where both are + /// continuous, and dy = dw/w. + /// + /// + /// Rubi's sqrt(a + i a csch(c + d x)) and the rest of 6.6.3's were declined: the sum is + /// the square of a complex function over a real one, which nothing read. The constant is not + /// written: the answer is the integrand times the antiderivative in w over what that + /// antiderivative differentiates back to, a quotient constant wherever it is continuous; and it + /// is kept only where its derivative is the integrand at the sampled points. + /// https://github.com/asc-community/AngouriMath/issues/718 + /// + internal static Entity? SolveAPowerOfAnImaginaryHyperbolicCosecantSumInTheExponential(Entity expr, Entity.Variable x, bool integrateByParts) + { + var s = Variable.CreateUnique(expr, "s_hyp"); + var c = Variable.CreateUnique(expr, "c_hyp"); + if (ReadTheHyperbolicFunctions(expr, x, s, c) is not var (read, argument) + || read.ContainsNode(c) || read.ContainsNode(x) + || !TreeAnalyzer.TryGetPolyLinear(argument, x, out var slope, out _) || TreeAnalyzer.IsZero(slope)) + return null; + Entity? radicand = null; + Number.Rational? power = null; + Entity constant = Number.Integer.One; + foreach (var (factor, underneath) in FactorsOfTheIntegrand(read)) + { + if (!factor.ContainsNode(s)) + { + constant = underneath ? constant / factor : constant * factor; + continue; + } + if (radicand is not null || factor is not Powf(var @base, Number.Rational exponent) || exponent is Number.Integer) + return null; + (radicand, power) = (@base, underneath ? Number.Rational.Create(exponent.ERational.Negate()) : exponent); + } + if (radicand is null || power is null || !radicand.Nodes.Any(node => node is Divf(_, var below) && below == s || node is Powf(var b, Number.Integer { EInteger.Sign: < 0 }) && b == s)) + return null; + // In w: s = (w - 1/w)/2, and the sum as one quotient. + var w = Variable.CreateUnique(expr, "w_exp"); + var (top, bottom) = Functions.SingleQuotient.Of(Functions.SingleQuotient.Combine(radicand.Substitute(s, (w - 1 / w) / 2))); + foreach (var sign in new[] { MathS.i, -MathS.i }) + { + var square = MathS.Sqr(w + sign); + // Bare: the quotient simplifies to `a provided not w + i = 0`, a condition on w beside a constant. + var q = Functions.PartialFractions.Bare((top / square).Simplify()); + if (q.ContainsNode(w)) + continue; + var twice = Number.Rational.Create(power.ERational.Multiply(ERational.FromInt32(2))); + var inW = (constant * MathS.Pow(q, power) * MathS.Pow(w + sign, twice) * MathS.Pow(bottom, Number.Rational.Create(power.ERational.Negate())) / (slope * w)).InnerSimplified; + if (Integration.ComputeAsAQuestionOfItsOwn(inW, w, integrateByParts) is not { } inTermsOfW + || inTermsOfW.Nodes.Any(node => node == MathS.NaN)) + return null; + // Checked in w, where the antiderivative is of the integrand as written: in x, with the + // slope a symbol pinned at a sample, e^(c + d x) at the sampled points was past what the + // evaluation decides. + if (!Functions.PartialFractions.HoldsAtSampledPoints(inTermsOfW.Differentiate(w), inW, w)) + return null; + var exponential = MathS.Pow(MathS.e, argument); + return expr * inTermsOfW.Substitute(w, exponential) / (inW.Substitute(w, exponential) * slope * exponential); + } + return null; + } + /// /// read as a (1 ± i s) for the hyperbolic sine /// : the constant a, and whether the imaginary unit comes diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index cd915e3f9..8192a0c89 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -909,6 +909,8 @@ private static Entity Normalized(Entity expr, Entity.Variable x) => if ((answer = IndefiniteIntegralSolver.SolveByTheHalfAngleWhereOnePlusAHyperbolicCosineIsASquare(expr, x, integrateByParts)) is { }) return answer; // The hyperbolic sine's, off the real line: 1 + i sinh(y) is (cosh(y/2) + i sinh(y/2))^2. if ((answer = IndefiniteIntegralSolver.SolveAHalfOddPowerOfOnePlusAnImaginaryHyperbolicSine(expr, x, integrateByParts)) is { }) return answer; + // And the cosecant's: a ± i a csch(y) is a (w ± i)^2/(w^2 - 1) in w = e^y. + if ((answer = IndefiniteIntegralSolver.SolveAPowerOfAnImaginaryHyperbolicCosecantSumInTheExponential(expr, x, integrateByParts)) is { }) return answer; // A quotient of linears as a function's argument, written over its denominator: a // constant plus a multiple of the reciprocal of a linear, which the rules read. if ((answer = IndefiniteIntegralSolver.SolveByWritingAQuotientOfLinearsOverItsDenominator(expr, x, integrateByParts)) is { }) return answer; diff --git a/Sources/Tests/UnitTests/Calculus/APowerOfAnImaginaryHyperbolicCosecantSumIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/APowerOfAnImaginaryHyperbolicCosecantSumIntegralTest.cs new file mode 100644 index 000000000..ff82b8179 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/APowerOfAnImaginaryHyperbolicCosecantSumIntegralTest.cs @@ -0,0 +1,51 @@ +// +// 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 power of a ± i a csch(c + d x), not whole, integrated in w = e^(c + d x), where the sum + /// is a (w ± i)^2/(w^2 - 1). Rubi's 6.6.3. The integrands are complex for a real x and are + /// compared as complex numbers, on both signs of the argument. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class APowerOfAnImaginaryHyperbolicCosecantSumIntegralTest + { + [Theory] + [InlineData("sqrt(a + i*a*csch(c + d*x))")] + [InlineData("(a + i*a*csch(c + d*x))^(3/2)")] + [InlineData("1/sqrt(a - i*a*csch(c + d*x))")] + public void InTheExponential(string integrand) + { + var integral = integrand.ToEntity().Integrate("x"); + var text = integral.Stringize(); + Assert.DoesNotContain("integral(", text); + Assert.True(text.Length < 5000, $"{text.Length} characters of answer for {integrand}"); + Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("c", 0.4).Substitute("d", 1.1); + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pinned(integrand.ToEntity()); + var compared = 0; + foreach (var at in new[] { -1.2, -0.7, 0.3, 0.8, 1.3, 2.9 }) + { + var want = original.Substitute("x", at).EvalNumerical(); + var got = derivative.Substitute("x", at).EvalNumerical(); + if (want.IsNaN) + continue; + compared++; + 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}"); + } + Assert.True(compared >= 5, $"only {compared} points could be compared for {integrand}"); + } + } +}