From 0f523f057d0005a4e4af38769175dee559408dad Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Tue, 20 Oct 2026 18:09:16 +0000 Subject: [PATCH] Integrate a power of x times a hyperbolic function of a power under that power x^m F(sinh(c + d x^n), cosh(c + d x^n)), with n >= 2 dividing m + 1, is taken under t = x^n: x^m dx is t^(k - 1) dt/n for k = (m + 1)/n, and the question in t is the same function of a linear in t beside a whole power of t, which the rules for those answer. The parser writes the hyperbolic functions as exponentials of c + d x^n, which the substitution search did not read as a function of x^n. Rubi's 6.1.3, 6.2.3, 6.5.2 and 6.6.2, seven rows with x sech(a + b x^2)^7 among them, were past the budget. Part of #233. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 21 ++++ .../Integration/IndefiniteIntegralSolver.cs | 103 ++++++++++++++++++ .../Integration/Integration.Definition.cs | 4 + ...AHyperbolicFunctionOfAPowerIntegralTest.cs | 59 ++++++++++ 4 files changed, 187 insertions(+) create mode 100644 Sources/Tests/UnitTests/Calculus/APowerOfXTimesAHyperbolicFunctionOfAPowerIntegralTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 671eff440..dafb5f3be 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -5888,6 +5888,27 @@ exponential over the derivative of its sine or cosine is now asked as a rational | `"exp(n*sinh(a + b*x))*sinh(2*(a + b*x))".ToEntity().Integrate("x")` | `integral(...)` | elementary in `sinh(a + b x)` and its exponential | | `"exp(n*cosh(1/2*(a + b*x)))*sinh(a + b*x)".ToEntity().Integrate("x")` | `integral(...)` | the same in `cosh((a + b x)/2)` | +### A power of x times a hyperbolic function of a power of x is integrated under that power + +**Answers where there were none.** `x sech(a + b x^2)^7`, `x/(a + b sech(c + d x^2))^2` and their cosecant twins, Rubi's +6.5.2 and 6.6.2, `sinh(a + b x^2)^3/x^3` and its cosine, 6.1.3 and 6.2.3, and `x^5 cosh(a + b x^3)^7 sinh(a + b x^3)`, +6.7.1, were declined. The library writes the hyperbolic functions as exponentials, and the rules before this one spent +the budget on a power of a quotient of exponentials of `a + b x^2`. With `x^m` beside a rational function of hyperbolic +functions of `c + d x^n`, `n` at least 2 and dividing `m + 1`, the integrand is now asked in `t = x^n`, where +`x^m dx` is `t^(k - 1) dt/n` for `k = (m + 1)/n`, exactly wherever it is defined since the powers are whole. Only with +a whole power past the first of a sum or a quotient of the exponentials among them: `x/(a + b sech(c + d x^2))` and +`x^3 sinh(a + b x^2)` are answered as written, in fewer characters +([#233](https://github.com/asc-community/AngouriMath/issues/233)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"x*sech(a + b*x^2)^7".ToEntity().Integrate("x")` | `integral(...)` | 494 characters | +| `"x/(a + b*sech(c + d*x^2))^2".ToEntity().Integrate("x")` | `integral(...)` | 2,193 characters | +| `"sinh(a + b*x^2)^3/x^3".ToEntity().Integrate("x")` | `integral(...)` | 480 characters | +| `"x^5*cosh(a + b*x^3)^7*sinh(a + b*x^3)".ToEntity().Integrate("x")` | `integral(...)` | 2,824 characters | +| `"x*(a + b*sech(c + d*x^2))^2".ToEntity().Integrate("x")` | `integral(...)`; 205 characters, in 8.9 s, on the unreleased master | 185 characters, in 45 ms | +| `"x/(cosh(a + b*x^2)^2 + 1)".ToEntity().Integrate("x")` | `integral(...)`; 145 characters, in 8.3 s, on the unreleased master | 231 characters, in 76 ms | + ### A hyperbolic function of a logarithm is integrated, the exponent folded structurally `tanh(ln(x))` was left as written. The library spells `tanh(y)` with `e^(2y)`, so a hyperbolic diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index c91066d1e..f6d5a4e6b 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -27796,6 +27796,109 @@ when two.Equals(EInteger.FromInt32(2)) && e1 == MathS.e && e2 == MathS.e return answer.Nodes.Any(node => node == MathS.NaN) ? null : answer; } + /// + /// A power of x times a rational function of hyperbolic functions of a linear in a whole + /// power of x, x^m F(sinh(c + d x^n), cosh(c + d x^n)) with n >= 2 dividing + /// m + 1, under t = x^n: x^m dx is t^(k - 1) dt/n for + /// k = (m + 1)/n, and the question in t is the same function of a linear in + /// t beside a whole power of t. + /// + /// + /// The library writes the hyperbolic functions as exponentials, so x sech(a + b x^2)^7 + /// arrives as x beside a seventh power of a quotient of exponentials of a + b x^2, and + /// the rules before this one spent the budget on it: Rubi's 6.5.2 and 6.6.2 + /// x sech(a + b x^2)^7, x/(a + b sech(c + d x^2))^2 and the two with the + /// cosecant, 6.1.3 and 6.2.3 sinh(a + b x^2)^3/x^3 and its cosine, and 6.7.1 + /// x^5 cosh(a + b x^3)^7 sinh(a + b x^3), each of which in t is answered in + /// under a second. Exact wherever the integrand is defined, since the powers are whole: + /// x^m is x^(n - 1) (x^n)^(k - 1) at every x but 0. Exponentials of a + /// linear in x^n and whole powers of them only, with a whole power past the first of + /// a sum or a quotient of them among them: x/(a + b sech(c + d x^2)) and + /// x^3 sinh(a + b x^2) are answered as written, in fewer characters than in + /// t, and are left to that. After the rules that answer a polynomial beside + /// exponentials of a quadratic as written, which it leaves as they were; a symbolic + /// n is 's. + /// https://github.com/asc-community/AngouriMath/issues/233 + /// + internal static Entity? SolveAPowerOfXTimesAHyperbolicFunctionOfAPowerUnderThatPower(Entity expr, Entity.Variable x, bool integrateByParts) + { + if (!Integration.AnsweringTheQuestionAsked) + return null; + var m = 0; + Entity constant = Number.Integer.One; + Entity? above = null, below = null; + foreach (var (factor, underneath) in FactorsOfTheIntegrand(expr)) + { + if (!factor.ContainsNode(x)) + constant = underneath ? constant / factor : constant * factor; + else if (factor == x) + m += underneath ? -1 : 1; + else if (factor is Powf(var b0, Number.Integer { EInteger: var p0 }) && b0 == x && p0.CanFitInInt32()) + m += underneath ? -p0.ToInt32Unchecked() : p0.ToInt32Unchecked(); + else if (underneath) + below = below is null ? factor : below * factor; + else + above = above is null ? factor : above * factor; + } + if (above is null && below is null) + return null; + var rest = below is null ? above! : (above ?? Number.Integer.One) / below; + // The one whole power of x the rest is written in. + var powers = rest.Nodes.Where(node => node is Powf(var b1, Number.Integer) && b1 == x).Distinct().ToList(); + if (powers.Count != 1 || powers[0] is not Powf(_, Number.Integer { EInteger: var power }) || !power.CanFitInInt32()) + return null; + var n = power.ToInt32Unchecked(); + if (n < 2 || n > 12 || (m + 1) % n != 0) + return null; + var t = Variable.CreateUnique(expr, "t_hyp"); + var inT = rest.Substitute(powers[0], t); + if (inT.ContainsNode(x)) + return null; + // Exponentials of a linear in t with a constant base, and whole powers, sums, + // products and quotients of them: nothing else of t. With a whole power past the + // first of a sum of them, or of a quotient: a hyperbolic function to the first power + // is answered in x as written, by the rules before this one, and in fewer characters. + var anExponential = false; + var aWholePower = false; + foreach (var node in inT.Nodes) + { + if (!node.ContainsNode(t)) + continue; + switch (node) + { + case Variable or Sumf or Minusf or Mulf or Divf: + continue; + case Powf(var sum, Number.Integer { EInteger: var whole }): + aWholePower |= sum is Sumf or Minusf or Mulf or Divf && whole.Abs().CompareTo(EInteger.One) > 0; + continue; + case Powf(var @base, var exponent) when !@base.ContainsNode(t) + && TreeAnalyzer.TryGetPolyLinear(exponent, t, out var slope, out _) && !slope.ContainsNode(t) + && !Functions.PartialFractions.IsZeroAsAValue(slope): + anExponential = true; + continue; + default: + return null; + } + } + if (!anExponential || !aWholePower) + return null; + // A bare t outside every exponential is a power of x that n does not reach. + if (inT.Replace(node => node is Powf(var b2, var e2) && !b2.ContainsNode(t) && e2.ContainsNode(t) ? Number.Integer.One : node).ContainsNode(t)) + return null; + var k = (m + 1) / n; + var question = k switch + { + 1 => inT, + > 1 => MathS.Pow(t, Number.Integer.Create(k - 1)) * inT, + _ => inT / MathS.Pow(t, Number.Integer.Create(1 - k)), + }; + if (Integration.ComputeAsAQuestionOfItsOwn(question, t, integrateByParts) is not { } integrated + || integrated.Nodes.Any(node => node is Integralf)) + return null; + var answer = (constant / Number.Integer.Create(n) * integrated.Substitute(t, powers[0])).InnerSimplified; + return answer.Nodes.Any(node => node == MathS.NaN) ? null : answer; + } + /// /// A product of whole powers of a sine and a cosine of one linear times something else in x /// that is no polynomial, sin(u)^3 cos(u)^6/(a + b u), linearised: the product is a sum diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index d55feb5a3..11639d5c6 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -877,6 +877,10 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr) if ((answer = IndefiniteIntegralSolver.SolveByTheDerivativeOfAQuadraticAsTheVariable(expr, x, integrateByParts)) is { }) return answer; // x^(n - 1) g(x^n) with a symbolic n is g(u)/n under u = x^n. if ((answer = IndefiniteIntegralSolver.SolveByAPowerOfTheVariableTimesAFunctionOfItsPower(expr, x, integrateByParts)) is { }) return answer; + // And x^(k n - 1) g(x^n) with a whole n >= 2, for a whole power of hyperbolic functions of + // a linear in x^n, which arrive as exponentials: `x sech(a + b x^2)^7` is + // `sech(a + b t)^7/2` under t = x^2, where the rules before this spent the budget. + if ((answer = IndefiniteIntegralSolver.SolveAPowerOfXTimesAHyperbolicFunctionOfAPowerUnderThatPower(expr, x, integrateByParts)) is { }) return answer; // A whole power of a product of a constant and the variable, as the product of // the powers, which is how the inverse hyperbolic secant and cosecant arrive. if ((answer = IndefiniteIntegralSolver.SolveByDistributingWholePowersOfProducts(expr, x, integrateByParts)) is { }) return answer; diff --git a/Sources/Tests/UnitTests/Calculus/APowerOfXTimesAHyperbolicFunctionOfAPowerIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/APowerOfXTimesAHyperbolicFunctionOfAPowerIntegralTest.cs new file mode 100644 index 000000000..31823c223 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/APowerOfXTimesAHyperbolicFunctionOfAPowerIntegralTest.cs @@ -0,0 +1,59 @@ +// +// 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 x times a whole power of hyperbolic functions of a linear in x^n, under + /// t = x^n, where x^m dx is t^((m + 1)/n - 1) dt/n. The hyperbolic functions + /// arrive as exponentials of a + b x^n, and in x the rules spent the budget on each of + /// these. Rubi's 6.1.3, 6.2.3, 6.5.2, 6.6.2 and 6.7.1. + /// #233 + /// + [Trait("Area", "Calculus")] + public sealed class APowerOfXTimesAHyperbolicFunctionOfAPowerIntegralTest + { + [Theory] + [InlineData("sinh(a + b*x^2)^3/x^3")] + [InlineData("cosh(a + b*x^2)^3/x^3")] + [InlineData("x*sech(a + b*x^2)^7")] + [InlineData("x*csch(a + b*x^2)^7")] + [InlineData("x/(a + b*sech(c + d*x^2))^2")] + [InlineData("x/(a + b*csch(c + d*x^2))^2")] + [InlineData("x*(a + b*sech(c + d*x^2))^2")] + [InlineData("x^5*cosh(a + b*x^3)^7*sinh(a + b*x^3)")] + [InlineData("sinh(a + b*x^2)^4/x^5")] + [InlineData("x/(cosh(a + b*x^2)^2 + 1)")] + public void UnderThePower(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("b", 0.7).Substitute("c", 0.4).Substitute("d", 0.3); + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pinned(integrand.ToEntity()); + var compared = 0; + foreach (var at in new[] { -1.3, -0.8, -0.4, 0.35, 0.9, 1.4 }) + { + 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}"); + } + } +}