From dc9d37b31d9a81c9ca7e310de578ba8c23148176 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Thu, 22 Oct 2026 04:14:47 +0000 Subject: [PATCH] Read half-odd powers of a hyperbolic sine beside an exponential as a derivative Rubi's 6.7.1 e^(a + b x) sinh(c + d x)^(3/2) - 3/4 e^(a + b x) d^2/ ((b^2 - 9/4 d^2) sqrt(sinh(c + d x))) was declined: each term alone is not elementary, and the sum is the derivative of e^(a + b x) (4 b sinh^(3/2) - 6 d cosh sinh^(1/2))/(4 b^2 - 9 d^2). With E = e^(a + b x), S = sinh(u), C = cosh(u), u = c + d x, (E S^p C^q)' = E (b S^p C^q + d p S^(p - 1) C^(q + 1) + d q S^(p + 1) C^(q - 1)) with C^2 = 1 + S^2, so the answer is a sum of E S^p C^q over the integrand's monomials and the partners their derivative reaches, and its coefficients are a linear system: square, one row per monomial of the ansatz, with each compound coefficient of the integrand named while it is solved, since the elimination with symbols declined a row reducing to a zero it could not decide. Powers of one sine multiply on every branch, and no power of a power is taken. Last in the dispatch; checked by its derivative. Part of #233. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 13 ++ .../Integration/IndefiniteIntegralSolver.cs | 142 ++++++++++++++++++ .../Integration/Integration.Definition.cs | 3 + ...olicSineBesideAnExponentialIntegralTest.cs | 47 ++++++ 4 files changed, 205 insertions(+) create mode 100644 Sources/Tests/UnitTests/Calculus/HalfOddPowersOfAHyperbolicSineBesideAnExponentialIntegralTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 757f04982..f964211c6 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -618,6 +618,19 @@ interval without a case for each. Only where nothing else answers the integrand | `"acoth(x)^2/(1-x^2)^2".ToEntity().Integrate("x")` | `integral(...)` | 1,304 characters, with `(ln((x + 1)/(x - 1)) - ln((1 + x)/(1 - x)))/2` for `K` | | `"acoth(c*x)^2/(1-c^2*x^2)^2".ToEntity().Integrate("x")` | `integral(...)` | 1,681 characters | +### Half-odd powers of a hyperbolic sine beside an exponential are read as a derivative + +**An answer where there was none.** Rubi's 6.7.1 `e^(a + b x) sinh(c + d x)^(3/2) - 3/4 e^(a + b x) d^2/((b^2 - 9/4 d^2) sqrt(sinh(c + d x)))` +was declined. Each of its terms alone is not elementary, and the sum is the derivative of +`e^(a + b x) (4 b sinh^(3/2) - 6 d cosh sinh^(1/2))/(4 b^2 - 9 d^2)`. Such a sum is now read as the derivative of +`e^(a + b x)` times a sum of `sinh^p cosh^q`, over the integrand's monomials and the ones their derivative reaches, with +`cosh^2 = 1 + sinh^2`. Its coefficients are a linear system +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"e^(a+b*x)*sinh(c+d*x)^(3/2)-3/4*e^(a+b*x)*d^2/((b^2-9/4*d^2)*sqrt(sinh(c+d*x)))".ToEntity().Integrate("x")` | `integral(...)` | 249 characters | + ### A function of the hyperbolic tangent alone is integrated under `u = tanh` **Answers where there were none.** `tanh(x)/sqrt(a + b tanh(x)^2 + c tanh(x)^4)` was declined, and so were 35 more of diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index dc44a289e..2ad189101 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -29768,6 +29768,148 @@ node is Powf(var @base, var exponent) && @base == MathS.e && powers.TryGetValue( return Folded(written.Substitute(constant, named - s * slope * x)).Substitute(s, sign); } + /// + /// An exponential of a linear times half-odd powers of a hyperbolic sine of another linear, and the + /// cosine to the first power beside them, as the derivative of the same kind of sum: with + /// E = e^(a + b x), S = sinh(u), C = cosh(u) and u = c + d x, + /// (E S^p C^q)' = E (b S^p C^q + d p S^(p - 1) C^(q + 1) + d q S^(p + 1) C^(q - 1)), with + /// C^2 = 1 + S^2, so F is a sum of E S^p C^q over the integrand's monomials and their + /// partners, and its coefficients a linear system. Rubi's 6.7.1 + /// e^(a + b x) sinh(c + d x)^(3/2) - 3/4 e^(a + b x) d^2/((b^2 - 9/4 d^2) sqrt(sinh(c + d x))) + /// is (e^(a + b x) (b sinh^(3/2) - 3/2 d cosh sinh^(1/2))/(b^2 - 9/4 d^2))', where each term + /// alone is not elementary and the split declined both. Powers of one sine multiply on every + /// branch, and no power of a power is taken. Checked by its derivative. + /// https://github.com/asc-community/AngouriMath/issues/718 + /// + internal static Entity? SolveHalfOddPowersOfAHyperbolicSineBesideAnExponentialAsADerivative(Entity expr, Entity.Variable x) + { + if (!Integration.AnsweringTheQuestionAsked) + return null; + var terms = Sumf.LinearChildren(expr); + if (terms.Count < 2) + return null; + // u: the argument of the first e^u - e^(-u) or e^u + e^(-u) in the integrand. + Entity? u = null; + foreach (var node in expr.Nodes) + if (node is Minusf or Sumf && node.DirectChildren.ToList() is [Powf(var e1, var p1), Powf(var e2, var p2)] + && e1 == MathS.e && e2 == MathS.e && TreeAnalyzer.TryGetPolyLinear(p1, x, out var s1, out var c1) && !TreeAnalyzer.IsZero(s1) + && TreeAnalyzer.TryGetPolyLinear(p2, x, out var s2, out var c2) + && Functions.PartialFractions.InLowestTermsOverTheSymbols(s1 + s2) == Number.Integer.Zero + && Functions.PartialFractions.InLowestTermsOverTheSymbols(c1 + c2) == Number.Integer.Zero) + { + u = p1; + break; + } + if (u is null || !TreeAnalyzer.TryGetPolyLinear(u, x, out var d, out _)) + return null; + bool IsPlusMinus(Entity first, Entity second) => first is Powf(var e1, var p1) && e1 == MathS.e && p1 == u + && second is Powf(var e2, var p2) && e2 == MathS.e && Functions.PartialFractions.InLowestTermsOverTheSymbols(p2 + u) == Number.Integer.Zero; + Entity? exponentOfE = null; + // (constant, power of S, power of C), the exponential's exponent kept apart. + (Entity K, ERational P, int Q)? Read(Entity node) + { + if (!node.ContainsNode(x)) + return (node, ERational.Zero, 0); + switch (node) + { + case Minusf(var a, var b) when IsPlusMinus(a, b): + return (Number.Integer.Create(2), ERational.One, 0); + case Sumf(var a, var b) when IsPlusMinus(a, b): + return (Number.Integer.Create(2), ERational.Zero, 1); + case Powf(var e0, var exponent) when e0 == MathS.e && TreeAnalyzer.TryGetPolyLinear(exponent, x, out _, out _): + if (exponentOfE is null) + exponentOfE = exponent; + return exponentOfE == exponent ? (Number.Integer.One, ERational.Zero, 0) : null; + case Mulf(var a, var b): + return Read(a) is var (ka, pa, qa) && Read(b) is var (kb, pb, qb) ? (ka * kb, pa.Add(pb), qa + qb) : null; + case Divf(var a, var b): + return Read(a) is var (ka2, pa2, qa2) && Read(b) is var (kb2, pb2, qb2) ? (ka2 / kb2, pa2.Subtract(pb2), qa2 - qb2) : null; + // A power of the sine alone, or a whole power of anything read. + case Powf(var @base, Number.Integer { EInteger: var whole }) when whole.CanFitInInt32() && Read(@base) is var (k, pw, qw): + return (MathS.Pow(k, Number.Integer.Create(whole)), pw.Multiply(ERational.FromEInteger(whole)), qw * whole.ToInt32Unchecked()); + case Powf(var @base, Number.Rational { ERational: var r }) when Read(@base) is var (k2, ps, qs) && qs == 0 && ps.Equals(ERational.One): + return (MathS.Pow(k2, Number.Rational.Create(r)), r, 0); + default: + return null; + } + } + // The integrand's monomials, C^2 written 1 + S^2. + var target = new Dictionary<(ERational, int), Entity>(); + void Add(Dictionary<(ERational, int), Entity> into, ERational p, int q, Entity coefficient) + { + while (q >= 2) + { + Add(into, p.Add(ERational.FromInt32(2)), q - 2, coefficient); + q -= 2; + } + if (q < 0) + return; + into[(p, q)] = into.TryGetValue((p, q), out var already) ? already + coefficient : coefficient; + } + var halfOdd = false; + foreach (var term in terms) + { + if (Read(term) is not var (k, p, q) || q < 0 || q > 3) + return null; + halfOdd |= !p.Denominator.Equals(EInteger.One); + Add(target, p, q, k); + } + if (!halfOdd || exponentOfE is null || !TreeAnalyzer.TryGetPolyLinear(exponentOfE, x, out var b, out _)) + return null; + // The ansatz: every monomial and its partner, which the derivative reaches. + var basis = new List<(ERational P, int Q)>(); + foreach (var (p, q) in target.Keys) + foreach (var candidate in q == 0 ? new[] { (p, 0), (p.Subtract(ERational.One), 1) } : new[] { (p, 1), (p.Add(ERational.One), 0) }) + if (!basis.Contains(candidate)) + basis.Add(candidate); + var derivatives = basis.Select(m => + { + var into = new Dictionary<(ERational, int), Entity>(); + Add(into, m.P, m.Q, b); + if (!m.P.IsZero) + Add(into, m.P.Subtract(ERational.One), m.Q + 1, d * Number.Rational.Create(m.P)); + if (m.Q > 0) + Add(into, m.P.Add(ERational.One), m.Q - 1, d * m.Q); + return into; + }).ToList(); + // Square, one row for each monomial of the ansatz, which holds every one of the integrand's: the + // rows past it, the lowest power the derivative reaches, are zero for a solution, which an + // elimination with symbols does not decide; the derivative is checked at the end instead. + var keys = basis.Select(m => (m.P, m.Q)).ToList(); + var matrix = keys.Select(key => derivatives.Select(map => map.TryGetValue(key, out var v) ? v.InnerSimplified : Number.Integer.Zero).ToArray()).ToArray(); + // Each compound coefficient of the integrand a name while the system is solved: the elimination + // with symbols reduces a row to `0 = c` with c zero only as a value, `-3/4 d^2/(b^2 - 9/4 d^2)` + // less `d/2` times the second unknown, and declines. + var named = new List<(Variable Name, Entity Value)>(); + Entity Named(Entity value) + { + if (value is Number or Variable) + return value; + var name = Variable.CreateUnique(expr + named.Aggregate((Entity)Number.Integer.Zero, (all, pair) => all + pair.Name), "k_es"); + named.Add((name, value)); + return name; + } + var rhs = keys.Select(key => target.TryGetValue(key, out var v) ? Named(v.InnerSimplified) : Number.Integer.Zero).ToArray(); + if (!Functions.PartialFractions.TrySolveLinearWithSymbols(matrix, rhs, out var values)) + return null; + var sine = (MathS.Pow(MathS.e, u) - MathS.Pow(MathS.e, -u)) / 2; + var cosine = (MathS.Pow(MathS.e, u) + MathS.Pow(MathS.e, -u)) / 2; + Entity sum = Number.Integer.Zero; + for (var j = 0; j < basis.Count; j++) + { + var value = Functions.PartialFractions.InLowestTermsOverTheSymbols(named.Aggregate(values[j], (all, pair) => all.Substitute(pair.Name, pair.Value))); + if (value == Number.Integer.Zero) + continue; + var (p, q) = basis[j]; + Entity monomial = p.IsZero ? Number.Integer.One : MathS.Pow(sine, Number.Rational.Create(p)); + if (q == 1) + monomial *= cosine; + sum += value * monomial; + } + var answer = MathS.Pow(MathS.e, exponentOfE) * sum; + return Functions.PartialFractions.DerivativeHoldsAtSampledPoints(answer, expr, x) ? answer : null; + } + /// /// The sign of a real-valued function of among the factors of the /// integrand is constant on every interval between its zeros, and goes in front of the diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 92a33c32d..6d7dc76e0 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -1796,6 +1796,9 @@ internal static bool ASumOverRootsWouldAnswer // a sign on. Last, so that every one answered as written keeps that answer: parts answers a // polynomial beside it. if ((answer = IndefiniteIntegralSolver.SolveByNamingAnInverseSineOfASineLinearOnEachInterval(expr, x, integrateByParts)) is { }) return answer; + // And half-odd powers of a hyperbolic sine beside an exponential as the derivative of the same kind, + // last: each term alone is not elementary, and the split declined them. + if ((answer = IndefiniteIntegralSolver.SolveHalfOddPowersOfAHyperbolicSineBesideAnExponentialAsADerivative(expr, x)) is { }) return answer; return null; } /// diff --git a/Sources/Tests/UnitTests/Calculus/HalfOddPowersOfAHyperbolicSineBesideAnExponentialIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/HalfOddPowersOfAHyperbolicSineBesideAnExponentialIntegralTest.cs new file mode 100644 index 000000000..ef6a173d5 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/HalfOddPowersOfAHyperbolicSineBesideAnExponentialIntegralTest.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 hyperbolic sine beside an exponential of another linear, read as the + /// derivative of the same kind of sum: Rubi's 6.7.1 + /// e^(a + b x) sinh(c + d x)^(3/2) - 3/4 e^(a + b x) d^2/((b^2 - 9/4 d^2) sqrt(sinh(c + d x))) + /// is (e^(a + b x) (4 b sinh^(3/2) - 6 d cosh sinh^(1/2))/(4 b^2 - 9 d^2))', and each term alone + /// is not elementary, so the split declined both. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class HalfOddPowersOfAHyperbolicSineBesideAnExponentialIntegralTest + { + [Theory] + // Rubi 6.7.1:1141. + [InlineData("e^(a + b*x)*sinh(c + d*x)^(3/2) - 3/4*e^(a + b*x)*d^2/((b^2 - 9/4*d^2)*sqrt(sinh(c + d*x)))")] + public void IsAnsweredAsADerivative(string integrand) + { + var integral = integrand.ToEntity().Integrate("x"); + var text = integral.Stringize(); + Assert.DoesNotContain("integral(", text); + Entity Pinned(Entity e) => e.Substitute("a", 0.4).Substitute("b", 1.3).Substitute("c", 0.2).Substitute("d", 0.7); + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pinned(integrand.ToEntity()); + foreach (var at in new[] { 0.3, 0.9, 1.4, 2.1, 3.3 }) + { + var want = original.Substitute("x", at).EvalNumerical(); + var got = derivative.Substitute("x", at).EvalNumerical(); + Assert.False(got.IsNaN, $"d/dx of the antiderivative of {integrand} has no value at x = {at}, where the integrand is {want}"); + 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}"); + } + } + } +}