Skip to content

Commit 8d5f693

Browse files
Read the derivative of a sum of exponentials over another off the integrand over the other's square (#2040)
Rubi's 6.7.1 x (b - a sinh(x))/(a + b sinh(x))^2 ran past the budget: the split took the bracket's terms apart, the substitution in e^x answered each over the square of a quadratic with a piecewise on its discriminant, and parts beside x integrated that piecewise again. For a sum D of exponentials of x, the integrand over D^2 is now read as (N/D)', N a constant plus a constant times each exponential in D. The exponentials' span is closed under the derivative, so N' D - N D' is linear in N's constants, and with each exponential a whole power of the first, w, the identity read at six values of w is a linear system. N and N + c D share a derivative over D; the solution with no constant is tried first, which is -cosh(x)/(a + b sinh(x)). Beside x, by parts, the integral of N/D is written as a constant times ln(D) where N is a constant times D', and asked otherwise. The trigonometric twin is SolveAsTheDerivativeOfASineRaisedFromNothingBesidePowersOfTheCosine's; there are no hyperbolic nodes, since sinh is parsed into exponentials. Part of #233. Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent dd4ebcb commit 8d5f693

4 files changed

Lines changed: 230 additions & 0 deletions

File tree

‎BREAKING-CHANGES.md‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,24 @@ tanh keep the answers the exponential substitution gives them ([#233](https://gi
570570
| `"sin(tanh(a+b*x))".ToEntity().Integrate("x")` | `integral(...)` | `Si` and `Ci` of `1 - tanh(a + b x)` and `1 + tanh(a + b x)` |
571571
| `"tanh(x)/sqrt(a+b*tanh(x)^2+c*tanh(x)^4)".ToEntity().Integrate("x")` | `integral(...)` | a logarithm of the root, piecewise on the sign of `a + b + c` |
572572

573+
### The derivative of a sum of exponentials over another is read off the integrand over the other's square
574+
575+
**Answers where there were none.** Rubi's 6.7.1 `x (b - a sinh(x))/(a + b sinh(x))^2` and `x (b + a cosh(x))/(a + b cosh(x))^2`
576+
were declined. The bracket over the square is the derivative of `-cosh(x)/(a + b sinh(x))`, and of
577+
`sinh(x)/(a + b cosh(x))`. So by parts the integral is `x` times that quotient, plus `ln(a + b sinh(x))/b` or minus
578+
`ln(a + b cosh(x))/b`. The integrals without x were answered with a piecewise on a discriminant, from the
579+
substitution in `e^x`; they are now the quotient itself. For a sum `D` of exponentials, the rule reads the
580+
integrand over `D^2` as `(N/D)'`, with `N` a constant times each exponential in `D`
581+
([#233](https://github.com/asc-community/AngouriMath/issues/233)).
582+
583+
| Input | Was (2.5.0) | Now |
584+
|---|---|---|
585+
| `"x*(b-a*sinh(x))/(a+b*sinh(x))^2".ToEntity().Integrate("x")` | `integral(...)` | 123 characters |
586+
| `"x*(b+a*cosh(x))/(a+b*cosh(x))^2".ToEntity().Integrate("x")` | `integral(...)` | 119 characters |
587+
| `"(b-a*sinh(x))/(a+b*sinh(x))^2".ToEntity().Integrate("x")` | `integral(...)` | 71 characters, where before this change master gave 7,861 |
588+
| `"(b+a*cosh(x))/(a+b*cosh(x))^2".ToEntity().Integrate("x")` | `integral(...)` | 70 characters, where before this change master gave 7,631 |
589+
| `"sech(x)^2*tanh(x)^2/(2+tanh(x)^3)^2".ToEntity().Integrate("x")` | no answer in thirty seconds | 60 characters, `-1/3/(2 + tanh(x)^3)`, in 55 ms; master gave 117 in 16 s and 9.7 GB |
590+
573591
### A hyperbolic function of `a + 2 ln(x)` is integrated with `e^a` as a symbol too
574592

575593
**Answers where there were none.** `tanh(a + 2 ln(x))^2/x^3` was declined, and so were nineteen more of Rubi's 6.3.2,

‎Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs‎

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43120,6 +43120,162 @@ Entity Pinned(Entity e)
4312043120
}
4312143121
}
4312243122

43123+
/// <summary>
43124+
/// The derivative of <c>N/D</c>, for <c>D</c> a sum of exponentials of x and constants and
43125+
/// <c>N</c> a constant plus a constant times each exponential in <c>D</c>, read off the
43126+
/// integrand over <c>D^2</c>: alone, or beside x by parts. Rubi's
43127+
/// <c>x (b - a sinh(x))/(a + b sinh(x))^2</c> is <c>x G'</c> for <c>G = -cosh(x)/(a + b sinh(x))</c>,
43128+
/// so <c>x G - integral G</c>, and the integral of <c>G</c> is <c>-ln(a + b sinh(x))/b</c>.
43129+
/// </summary>
43130+
/// <remarks>
43131+
/// <para>
43132+
/// The exponentials' span is closed under the derivative, so <c>(N/D)' = (N' D - N D')/D^2</c>
43133+
/// with <c>N' D - N D'</c> linear in N's constants, and matching it to the integrand's
43134+
/// numerator is a linear system: each exponential a whole power of the first one, <c>w</c>,
43135+
/// and the identity read at six values of w. The answer is differentiated back at sampled
43136+
/// points.
43137+
/// </para>
43138+
/// <para>
43139+
/// The split took the numerator's terms apart, and the exponential substitution answered each
43140+
/// over the square of a quadratic in <c>e^x</c> with a piecewise on its discriminant; beside x,
43141+
/// parts integrated that piecewise again and ran past the budget, where the trigonometric
43142+
/// twin is <see cref="SolveAsTheDerivativeOfASineRaisedFromNothingBesidePowersOfTheCosine"/>'s.
43143+
/// https://github.com/asc-community/AngouriMath/issues/233
43144+
/// </para>
43145+
/// </remarks>
43146+
internal static Entity? SolveAsTheDerivativeOfAQuotientOverASumOfExponentials(Entity expr, Entity.Variable x, bool integrateByParts)
43147+
{
43148+
if (!Integration.AnsweringTheQuestionAskedOrOneBelow || expr is not (Mulf or Divf))
43149+
return null;
43150+
// A constant, x at most once, a numerator and the square of one sum below the bar.
43151+
Entity constant = Number.Integer.One;
43152+
Entity numerator = Number.Integer.One;
43153+
Entity? sum = null;
43154+
var besideX = false;
43155+
foreach (var (factor, underneath) in FactorsOfTheIntegrand(expr))
43156+
{
43157+
if (!factor.ContainsNode(x))
43158+
constant = underneath ? constant / factor : constant * factor;
43159+
else if (factor == x && !underneath && !besideX)
43160+
besideX = true;
43161+
else if (underneath && sum is null && factor is Powf(Sumf or Minusf, Number.Integer { EInteger: var two } ) squared && two.Equals(EInteger.FromInt32(2)))
43162+
sum = squared.Base;
43163+
else if (!underneath)
43164+
numerator = numerator == Number.Integer.One ? factor : numerator * factor;
43165+
else
43166+
return null;
43167+
}
43168+
bool OnlyThroughTheExponentials(Entity e)
43169+
=> !e.Replace(node => node is Powf(var @base, var exponent) && @base == MathS.e && exponent.ContainsNode(x) ? Number.Integer.One : node).ContainsNode(x);
43170+
if (sum is null || !OnlyThroughTheExponentials(sum) || !OnlyThroughTheExponentials(numerator))
43171+
return null;
43172+
// Each exponential a whole power w^n of the first one, w = e^(m + k x): `e^(-(c + d x))` is
43173+
// `w^(-1)` beside `e^(c + d x)`, with no `e^c` left to the system.
43174+
var exponentials = new List<(Entity Node, Entity Exponent, Entity Rate, int Power)>();
43175+
foreach (var node in expr.Nodes)
43176+
if (node is Powf(var @base, var exponent) && @base == MathS.e && exponent.ContainsNode(x)
43177+
&& !exponentials.Any(known => known.Node == node))
43178+
{
43179+
if (!TreeAnalyzer.TryGetPolyLinear(exponent, x, out var rate, out var offset) || rate.ContainsNode(x) || offset.ContainsNode(x)
43180+
|| Functions.PartialFractions.IsZeroAsAValue(rate))
43181+
return null;
43182+
var multiple = exponentials.Count == 0 ? Number.Integer.One : Functions.PartialFractions.Bare((exponent / exponentials[0].Exponent).Simplify());
43183+
if (multiple is not Number.Integer { EInteger: var power } || !power.CanFitInInt32())
43184+
return null;
43185+
exponentials.Add((node, exponent, rate, power.ToInt32Unchecked()));
43186+
}
43187+
var inTheSum = exponentials.Where(exponential => sum.ContainsNode(exponential.Node)).ToList();
43188+
if (inTheSum.Count is 0 or > 4)
43189+
return null;
43190+
// N = c_0 + sum c_i E_i: N' D - N D' is sum c_i (k_i E_i D - E_i D') - c_0 D'. N and N + c D
43191+
// have one derivative over D, so the system has a line of solutions; without c_0 first,
43192+
// which is the one of them with no constant where D has one: `-cosh(x)/(a + b sinh(x))`,
43193+
// whose integral is a logarithm, and not `-(a/b + e^x)/(a + b sinh(x))`, a constant away.
43194+
var derivativeOfTheSum = sum.Differentiate(x);
43195+
var columns = new List<Entity> { -derivativeOfTheSum };
43196+
foreach (var (node, _, rate, _) in inTheSum)
43197+
columns.Add(rate * node * sum - node * derivativeOfTheSum);
43198+
var target = constant * numerator;
43199+
if ((Above(withoutAConstant: true) ?? Above(withoutAConstant: false)) is not { } above)
43200+
return null;
43201+
Entity? At(Entity e, Number.Rational w)
43202+
{
43203+
var written = e.Replace(node => node is Powf(var @base, var exponent) && @base == MathS.e && exponent.ContainsNode(x)
43204+
&& exponentials.FirstOrDefault(known => known.Node == node) is { Node: not null } known
43205+
? MathS.Pow(w, Number.Integer.Create(known.Power))
43206+
: node).InnerSimplified;
43207+
return written.ContainsNode(x) ? null : written;
43208+
}
43209+
var g = above / sum;
43210+
if (!Functions.PartialFractions.DerivativeHoldsAtSampledPoints(g, target / MathS.Pow(sum, 2), x))
43211+
return null;
43212+
if (!besideX)
43213+
return g;
43214+
// Where N is a constant times D', the integral of G is that constant times ln(D): the
43215+
// constant read off the first exponential's coefficients, D being linear in them, and
43216+
// the answer checked. Asked as a question, `-cosh(x)/(a + b sinh(x))` spelled in
43217+
// exponentials went through the substitution in e^x and came back a piecewise.
43218+
var (firstNode, _, firstRate, _) = inTheSum[0];
43219+
Entity WithTheExponentials(Entity e, Entity? one)
43220+
=> e.Replace(node => node is Powf(var @base, var exponent) && @base == MathS.e && exponent.ContainsNode(x)
43221+
? (node == one ? Number.Integer.One : Number.Integer.Zero) : node);
43222+
var inTheSumOfTheFirst = LowestOverTheSymbols((WithTheExponentials(sum, firstNode) - WithTheExponentials(sum, null)).InnerSimplified);
43223+
var inTheNumeratorOfTheFirst = LowestOverTheSymbols((WithTheExponentials(above, firstNode) - WithTheExponentials(above, null)).InnerSimplified);
43224+
Entity? answer = null;
43225+
if (!Functions.PartialFractions.IsZeroAsAValue(inTheSumOfTheFirst))
43226+
{
43227+
var logarithmic = LowestOverTheSymbols(inTheNumeratorOfTheFirst / (firstRate * inTheSumOfTheFirst));
43228+
var closed = (x * g - logarithmic * MathS.Ln(sum)).InnerSimplified;
43229+
if (Functions.PartialFractions.DerivativeHoldsAtSampledPoints(closed, expr, x))
43230+
answer = closed;
43231+
}
43232+
if (answer is null)
43233+
{
43234+
if (Integration.ComputeAsAQuestionOfItsOwn(g, x, integrateByParts) is not { } integralOfG
43235+
|| integralOfG.Nodes.Any(node => node == MathS.NaN || node is Integralf))
43236+
return null;
43237+
var asked = (x * g - integralOfG).InnerSimplified;
43238+
if (Functions.PartialFractions.DerivativeHoldsAtSampledPoints(asked, expr, x))
43239+
answer = asked;
43240+
}
43241+
return answer;
43242+
43243+
// N, the system read at six values of w, or null where it has no solution.
43244+
Entity? Above(bool withoutAConstant)
43245+
{
43246+
var first = withoutAConstant ? 1 : 0;
43247+
var samples = new[] { Number.Rational.Create(2), Number.Rational.Create(3), Number.Rational.Create(5),
43248+
Number.Rational.Create(1, 2), Number.Rational.Create(1, 3), Number.Rational.Create(7) };
43249+
var matrix = new Entity[samples.Length][];
43250+
var rhs = new Entity[samples.Length];
43251+
for (var j = 0; j < samples.Length; j++)
43252+
{
43253+
matrix[j] = new Entity[columns.Count - first];
43254+
for (var k = first; k < columns.Count; k++)
43255+
if (At(columns[k], samples[j]) is { } entry)
43256+
matrix[j][k - first] = entry;
43257+
else
43258+
return null;
43259+
if (At(target, samples[j]) is not { } value)
43260+
return null;
43261+
rhs[j] = value;
43262+
}
43263+
if (!Functions.PartialFractions.TrySolveLinearWithSymbols(matrix, rhs, out var coefficients))
43264+
return null;
43265+
Entity read = Number.Integer.Zero;
43266+
for (var k = 0; k < coefficients.Length; k++)
43267+
{
43268+
var coefficient = LowestOverTheSymbols(coefficients[k]);
43269+
if (Functions.PartialFractions.IsZeroAsAValue(coefficient))
43270+
continue;
43271+
var column = k + first;
43272+
var term = column == 0 ? coefficient : coefficient == Number.Integer.One ? inTheSum[column - 1].Node : coefficient * inTheSum[column - 1].Node;
43273+
read = read == Number.Integer.Zero ? term : read + term;
43274+
}
43275+
return read == Number.Integer.Zero ? null : read;
43276+
}
43277+
}
43278+
4312343279
/// <summary>
4312443280
/// <c>x^m (a + b atan(c x))^p</c> over a power of <c>d (1 +- i c x)</c>, under <c>x = tan(u)/c</c>, where
4312543281
/// <c>1 + i tan(u)</c> is <c>e^(i u)/cos(u)</c>: the integrand in <c>u</c> is

‎Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,11 @@ internal static bool ASumOverRootsWouldAnswer
720720
// `(a + a cos(u))^(3/2)/cos(u)^(5/4)`, and parts integrated `x (b + a sin(x))/(a + b sin(x))^2`'s
721721
// derivative into a function of tan(x/2) and ran out of time.
722722
if ((answer = IndefiniteIntegralSolver.SolveAsTheDerivativeOfASineRaisedFromNothingBesidePowersOfTheCosine(expr, x, integrateByParts)) is { }) return answer;
723+
// And its hyperbolic twin, where the functions are exponentials: the derivative of a sum of
724+
// them over another, read off the integrand over the other's square, alone or beside x by
725+
// parts. The split and the exponential substitution answered `(b - a sinh(x))/(a + b sinh(x))^2`
726+
// with a piecewise on a discriminant, and parts beside x ran past the budget integrating it.
727+
if ((answer = IndefiniteIntegralSolver.SolveAsTheDerivativeOfAQuotientOverASumOfExponentials(expr, x, integrateByParts)) is { }) return answer;
723728
// A cosine beside a function of the sine with a power off the rationals in it, in the sine:
724729
// the substitution search declined `cos(x)/(sin(x) + sin(x)^sqrt(2))`.
725730
if ((answer = IndefiniteIntegralSolver.SolveACosineBesideAFunctionOfTheSineWithAnIrrationalPowerInIt(expr, x, integrateByParts)) is { }) return answer;
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//
2+
// Copyright (c) 2019-2026 Angouri.
3+
// AngouriMath is licensed under MIT.
4+
// Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.
5+
// Website: https://am.angouri.org.
6+
//
7+
8+
using System;
9+
using AngouriMath.Extensions;
10+
using Xunit;
11+
12+
namespace AngouriMath.Tests.Calculus
13+
{
14+
/// <summary>
15+
/// The derivative of a sum of exponentials over another, read off the integrand over the other's
16+
/// square: <c>(b - a sinh(x))/(a + b sinh(x))^2</c> is the derivative of <c>-cosh(x)/(a + b sinh(x))</c>,
17+
/// and beside x, by parts, Rubi's 6.7.1 <c>x (b - a sinh(x))/(a + b sinh(x))^2</c> is
18+
/// <c>-x cosh(x)/(a + b sinh(x)) + ln(a + b sinh(x))/b</c>. The split and the substitution in
19+
/// <c>e^x</c> answered the first with a piecewise on a discriminant, and the second ran past the budget.
20+
/// <a href="https://github.com/asc-community/AngouriMath/issues/233">#233</a>
21+
/// </summary>
22+
[Trait("Area", "Calculus")]
23+
public sealed class TheDerivativeOfAQuotientOverASumOfExponentialsIntegralTest
24+
{
25+
[Theory]
26+
[InlineData("x*(b - a*sinh(x))/(a + b*sinh(x))^2")]
27+
[InlineData("x*(b + a*cosh(x))/(a + b*cosh(x))^2")]
28+
[InlineData("x*(b - a*sinh(c + d*x))/(a + b*sinh(c + d*x))^2")]
29+
[InlineData("(b - a*sinh(x))/(a + b*sinh(x))^2")]
30+
[InlineData("(b + a*cosh(c + d*x))/(a + b*cosh(c + d*x))^2")]
31+
public void IsAnsweredWithoutCases(string integrand)
32+
{
33+
var integral = integrand.ToEntity().Integrate("x");
34+
var text = integral.Stringize();
35+
Assert.DoesNotContain("integral(", text);
36+
Assert.DoesNotContain("piecewise", text);
37+
Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.7).Substitute("c", 0.4).Substitute("d", 0.9);
38+
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
39+
var original = Pinned(integrand.ToEntity());
40+
foreach (var at in new[] { -1.7, -0.6, 0.4, 1.3, 2.2 })
41+
{
42+
var want = original.Substitute("x", at).EvalNumerical();
43+
var got = derivative.Substitute("x", at).EvalNumerical();
44+
Assert.False(got.IsNaN, $"d/dx of the antiderivative of {integrand} has no value at x = {at}, where the integrand is {want}");
45+
Assert.True(Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart)
46+
< 1e-9 * Math.Max(1, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart)),
47+
$"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
48+
}
49+
}
50+
}
51+
}

0 commit comments

Comments
 (0)