diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index dc39a1fe6..72ed18c4a 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -282,6 +282,22 @@ of the third to the sixth degree, with a symbol in it, that written in `y = x + | `"1/(3*a*b + 3*b^2*x + 3*b*c*x^2 + c^2*x^3)".ToEntity().Integrate("x")`, and its square | `integral(...)` | two logarithms and an arctangent in `x + b/c` | | `"x/(a + 8*x - 8*x^2 + 4*x^3 - x^4)".ToEntity().Integrate("x")`, and `1` over it | `integral(...)` | the antiderivative in `x - 1` | +### Half-odd powers of `a + i a sinh` are integrated by the half angle at which they are squares + +**Answers where there were none.** `x^3 sqrt(a + i a sinh(g + f x))` was declined, with the rest of +Rubi's 6.1.1 and 6.1.5 with a half-odd power of `a ± i a sinh`, six of them after searches past the +budget. `1 + i sinh(y)` is `(cosh(y/2) + i sinh(y/2))^2`, so such a power is `a^p` times an odd +power of `cosh(y/2) ± i sinh(y/2)`, a sum of exponentials of the half angle, up to a constant on +every interval where both are continuous; it is integrated so now, and the answer is the +antiderivative times the power as written over that form, which holds on every such interval +whatever `a` is ([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"x^3*sqrt(a + i*a*sinh(g + f*x))".ToEntity().Integrate("x")` | `integral(...)` | powers of `x` times exponentials of the half angle | +| `"sqrt(a + i*a*sinh(g + f*x))/x".ToEntity().Integrate("x")` | `integral(...)` | `Shi` and `Chi` of half of `f x` | +| `"1/sqrt(a + i*a*sinh(g + f*x))".ToEntity().Integrate("x")` | `integral(...)` | arctangents and logarithms of `e^((g + f x)/2)` | + ### The hyperbolic functions have antiderivatives, and so does anything rational in `e^(k x)` An integrand rational in `e^(k x)` becomes a rational function of one variable under diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 875a31f1c..4a22d1cdd 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -21936,6 +21936,113 @@ private static (Entity OverTheTwo, Entity Argument)? ReadTheHyperbolicFunctions( return answer.Nodes.Any(node => node == MathS.NaN) ? null : answer; } + /// + /// Half-odd powers of a ± i a sinh(y) beside anything in the hyperbolic functions of + /// y, by the half angle at which they are squares: 1 + i sinh(y) is + /// (cosh(y/2) + i sinh(y/2))^2, so sqrt(a + i a sinh(y)) is + /// sqrt(a) (cosh(y/2) + i sinh(y/2)) times a constant on every interval where both + /// are continuous. The rest of the integrand is written in the half angle, and the question + /// asked again in x. + /// + /// + /// + /// Rubi's x^3 sqrt(a + i a sinh(e + f x)) and the rest of the 47 of 6.1.1 and 6.1.5 + /// were declined or searches past the budget: the half angle of + /// reads + /// a ± a cosh(y), and 1 + i sinh(y) is the square of a complex function rather + /// than of a real one. + /// + /// + /// The constant is not written: the answer is the antiderivative of the rewritten integrand + /// times each power as written over the form it was rewritten to, a quotient whose + /// logarithmic derivative is zero, so it is constant wherever it is continuous and the + /// answer holds on every such interval, whatever a is. + /// https://github.com/asc-community/AngouriMath/issues/718 + /// + /// + internal static Entity? SolveAHalfOddPowerOfOnePlusAnImaginaryHyperbolicSine(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)) + return null; + var half = (argument / 2).InnerSimplified; + var sinhHalf = MathS.Hyperbolic.Sinh(half); + var coshHalf = MathS.Hyperbolic.Cosh(half); + Entity constant = Number.Integer.One; + var found = false; + var rewritten = read.Replace(node => + { + if (node is not Powf(var radicand, Number.Rational exponent) || exponent is Number.Integer + || !exponent.ERational.Denominator.Equals(EInteger.FromInt32(2)) + || !radicand.ContainsNode(s) || radicand.ContainsNode(c) + || ReadAsOnePlusMinusAnImaginarySine(radicand, s) is not var (a, plus)) + return node; + found = true; + var root = plus ? coshHalf + MathS.i * sinhHalf : coshHalf - MathS.i * sinhHalf; + var written = MathS.Pow(a, exponent) * MathS.Pow(root, Number.Integer.Create(exponent.ERational.Numerator)); + var asItIs = MathS.Pow(radicand.Substitute(s, MathS.Hyperbolic.Sinh(argument)), exponent); + constant = constant * asItIs / written; + return written; + }); + if (!found) + return null; + var inX = rewritten.Substitute(c, 2 * MathS.Sqr(coshHalf) - 1).Substitute(s, 2 * sinhHalf * coshHalf); + if (inX.ContainsNode(s) || inX.ContainsNode(c)) + return null; + if (Integration.ComputeAsTheSameQuestion(inX, x, integrateByParts) is not { } result + || result.Nodes.Any(node => node == MathS.NaN)) + return null; + return constant * result; + } + + /// + /// read as a (1 ± i s) for the hyperbolic sine + /// : the constant a, and whether the imaginary unit comes + /// with a plus. Null for anything else. + /// + private static (Entity Constant, bool Plus)? ReadAsOnePlusMinusAnImaginarySine(Entity radicand, Entity sine) + { + Entity a = Number.Integer.Zero; + Entity? b = null; + foreach (var term in Sumf.LinearChildren(radicand)) + { + if (!term.ContainsNode(sine)) + { + a += term; + continue; + } + if (b is not null) + return null; + Entity coefficient = Number.Integer.One; + var seen = false; + foreach (var factor in Mulf.LinearChildren(term)) + { + if (factor == sine && !seen) + seen = true; + else if (!factor.ContainsNode(sine)) + coefficient *= factor; + else + return null; + } + if (!seen) + return null; + b = coefficient; + } + if (b is null) + return null; + a = a.InnerSimplified; + if (a.Evaled is Number.Complex { IsZero: true }) + return null; + static bool IsZero(Entity value) => value.InnerSimplified.Evaled is Number.Complex { IsZero: true } + || value.Simplify().Evaled is Number.Complex { IsZero: true }; + if (IsZero(b - MathS.i * a)) + return (a, true); + if (IsZero(b + MathS.i * a)) + return (a, false); + return null; + } + /// /// A sum every term of which carries the same power of one linear form in /// , written as that power times the sum of the rest: diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 15f973f6b..909915fb2 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -853,6 +853,8 @@ private static Entity Normalized(Entity expr, Entity.Variable x) => if ((answer = IndefiniteIntegralSolver.SolveByTheHalfAngleTangentBesideAHalfOddPowerOfOnePlusASecant(expr, x, integrateByParts)) is { }) return answer; // And `a ± a cosh(y)` under a fractional power: `2a cosh(y/2)^2`, `-2a sinh(y/2)^2`. 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; // Several trigonometric arguments that are multiples of one linear with an offset or a // symbolic slope, written in that linear: before the substitution search, which reads // each function on its own. diff --git a/Sources/Tests/UnitTests/Calculus/OnePlusAnImaginaryHyperbolicSineIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/OnePlusAnImaginaryHyperbolicSineIntegralTest.cs new file mode 100644 index 000000000..c727ae764 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/OnePlusAnImaginaryHyperbolicSineIntegralTest.cs @@ -0,0 +1,47 @@ +// +// 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 +{ + /// + /// Half-odd powers of a + i a sinh(y), which is a (cosh(y/2) + i sinh(y/2))^2: + /// beside a power of x each is a sum of exponentials of the half angle times that power, + /// up to a constant on every interval where both are continuous. Rubi's 6.1.1 and 6.1.5. The + /// integrands are complex, and compared as complex numbers on both sides of zero. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class OnePlusAnImaginaryHyperbolicSineIntegralTest + { + [Theory] + [InlineData("x^3*sqrt(a + i*a*sinh(g + f*x))")] + [InlineData("x*sqrt(a + i*a*sinh(g + f*x))")] + [InlineData("sqrt(a + i*a*sinh(g + f*x))/x")] + [InlineData("x^3*(a + i*a*sinh(g + f*x))^(3/2)")] + [InlineData("1/sqrt(a + i*a*sinh(g + f*x))")] + public void ByTheHalfAngle(string integrand) + { + var integral = integrand.ToEntity().Integrate("x"); + Assert.DoesNotContain("integral(", integral.Stringize()); + Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("g", 0.4).Substitute("f", 1.1); + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pinned(integrand.ToEntity()); + foreach (var at in new[] { -1.2, -0.4, 0.3, 0.8, 1.5 }) + { + 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}"); + } + } + } +}