Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,41 @@ power, and the integrand is answered in a fraction of a second
| `"csch(c+d*x)*(a+b*sinh(c+d*x)^4)^2".ToEntity().Integrate("x")` | past fifteen minutes | 426 characters |
| `"csch(c+d*x)*(a+b*sinh(c+d*x)^4)^3".ToEntity().Integrate("x")` | past fifteen minutes | 646 characters |

### Hyperbolic integrands are read through `cosh^2 = (1 + i sinh)(1 - i sinh)`, a symbolic power's own variable, and a phase

**Answers where there were none, and shorter ones.** Rows of these shapes in Rubi's 6.1.1 and 6.7.1 were declined or ran
out of time. Each is answered now through an identity of the hyperbolic functions that the substitutions did not use
([#233](https://github.com/asc-community/AngouriMath/issues/233)):
- `(g + f x)^m cosh(c + d x)^k/(a + i a sinh(c + d x))` and its `sinh^k` rows. `cosh^2 = (1 + i sinh)(1 - i sinh)`, so
the numerator divides by the denominator, leaving a polynomial and a constant over it.
- `1/(sech(x) ± i tanh(x))^4`. It is `cosh(x)^4/(1 ± i sinh(x))^4`, which the same identity cancels to
`(1 ∓ i sinh(x))^2/(1 ± i sinh(x))^2`. Only `cosh^(2m)` over `(1 ± i sinh)^(2m)` and the other way up: cancelling
where a polynomial or another power of the binomial is left made other answers longer.
- A symbolic power of `sinh`, `cosh`, `sech` or `csch` beside an odd power of the other function, with `tanh` and
`coth` read as quotients. `cosh(a + b x)^3 sinh(a + b x)^n` is integrated under `u = sinh`, and
`sech(a + b x)^n tanh(a + b x)^3` under `u = sech`. The power of `csch` stays a power of `u`, because it is not
`sinh^(-n)` where the sine is negative.
- `(cosh(x) coth(x))^(3/2)` and `(csch(x) + sinh(x))^(3/2)`. Both are `(cosh(x)^2/sinh(x))^(3/2)`, which is
`cosh(x)^3 sinh(x)^(-3/2)` times a constant on each side of zero, odd in the cosine.
- Half-odd powers of `S = b cosh(x) + c sinh(x) ± sqrt(b^2 - c^2)`. With `T = b sinh(x) + c cosh(x)` and `a` the
constant term, `d/dx (T S^(n - 1)) = n S^n - a (2n - 1) S^(n - 1)` steps the power to `±1/2`. The whole powers stay
the exponential substitution's, which writes them shorter.

2.5.0 declined or ran out of time on all of the inputs below. The unreleased master answered some of them, and
those answers are now two to twelve times shorter: `(sech(x) + i tanh(x))^2` and `^4` were 437 and 2,916
characters, and `1/(sech(x) + i tanh(x))^2` and `^4` were 372 and 551.

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"(g+f*x)*cosh(c+d*x)^2/(a+i*a*sinh(c+d*x))".ToEntity().Integrate("x")` | `integral(...)` | 366 characters |
| `"cosh(a+b*x)^3*sinh(a+b*x)^n".ToEntity().Integrate("x")` | `integral(...)` | 149 characters |
| `"sech(a+b*x)^n*tanh(a+b*x)^3".ToEntity().Integrate("x")` | past two minutes | 183 characters |
| `"coth(a+b*x)^3*csch(a+b*x)^n".ToEntity().Integrate("x")` | past a minute | 242 characters |
| `"(cosh(x)*coth(x))^(3/2)".ToEntity().Integrate("x")` | `integral(...)` | 247 characters |
| `"1/sqrt(b*cosh(x)+c*sinh(x)+sqrt(b^2-c^2))".ToEntity().Integrate("x")` | `integral(...)` | 409 characters |
| `"1/(sech(x)+i*tanh(x))^4".ToEntity().Integrate("x")` | `integral(...)` | 241 characters |
| `"(sech(x)+i*tanh(x))^4".ToEntity().Integrate("x")` | `integral(...)`, after 54 seconds | 251 characters, where the unreleased master gave 2,916 |

### A power of a polynomial whose leading coefficient is zero spelled otherwise is not divided by it

**Answers where there were none.** `1/((a (1 + x^2))^2 - (a (1 - x^2))^2)^3` was left unevaluated. That is
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -1134,6 +1134,8 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr)
// And a power of `a + b cos(y) + c sin(y)` with `a^2 = b^2 + c^2`, the same square
// turned by a phase, in closed form: the substitution search spent the budget on it.
if ((answer = IndefiniteIntegralSolver.SolveAPowerOfACosineAndASinePlusTheirAmplitude(expr, x)) is { }) return answer;
// And `a + b cosh(y) + c sinh(y)` with `a^2 = b^2 - c^2`, the hyperbolic one.
if ((answer = IndefiniteIntegralSolver.SolveAPowerOfAHyperbolicCosineAndSinePlusTheirAmplitude(expr, x)) is { }) return answer;
// And symbolic powers of `a ± a sin(y)` beside a power of `g cos(y)`, by u = sin(y), where
// `(1 + u)(1 - u)` is the cosine's square: the half angle wants numeric powers, and the
// substitution search spent the budget on these. Numeric ones too beside a fractional
Expand All @@ -1158,6 +1160,20 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr)
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;
// And a polynomial in sinh(y) and cosh(y) over a ± i a sinh(y) beside a power of x,
// divided out, cosh(y)^2 being (1 + i sinh(y))(1 - i sinh(y)).
if ((answer = IndefiniteIntegralSolver.SolveByDividingOutOnePlusAnImaginaryHyperbolicSine(expr, x, integrateByParts)) is { }) return answer;
// A symbolic power of one hyperbolic function beside an odd power of the other, under
// u = the first: Bioche's substitution reads whole and rational powers. The first may be
// sech(y) or csch(y), and tanh(y) and coth(y) beside it are read as quotients.
if ((answer = IndefiniteIntegralSolver.SolveASymbolicPowerOfAHyperbolicFunctionBesideAnOddPowerOfTheOther(expr, x, integrateByParts)) is { }) return answer;
// And cosh(y)^2 taken out of a fractional power of a quotient, `cosh(x) coth(x)` being
// `cosh(x)^2/sinh(x)`: Bioche's substitution reads the radicands that are polynomials.
if ((answer = IndefiniteIntegralSolver.SolveByTakingTheHyperbolicCosineSquaredOutOfAPower(expr, x, integrateByParts)) is { }) return answer;
// And cosh(y)^(2m) over (1 ± i sinh(y))^(2m), or the other way up, cancelled through
// cosh(y)^2 = (1 + i sinh(y))(1 - i sinh(y)): `1/(sech(x) + i tanh(x))^4` is
// `(1 - i sinh(x))^2/(1 + i sinh(x))^2`, where the substitutions spent the budget.
if ((answer = IndefiniteIntegralSolver.SolveByCancellingOnePlusAnImaginaryHyperbolicSineAgainstTheCosineSquared(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;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
//
// 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
{
/// <summary>
/// A half-odd power of <c>a + b cosh(y) + c sinh(y)</c> with <c>a^2 = b^2 - c^2</c>, a square of
/// the half angle turned by a phase, in closed form through <c>T = b sinh(y) + c cosh(y)</c>.
/// Rubi's 6.7.1.
/// <a href="https://github.com/asc-community/AngouriMath/issues/233">#233</a>
/// </summary>
/// <remarks>
/// Compared as complex numbers: with <c>b &lt; |c|</c> the constant is imaginary and so is the
/// base everywhere. Away from the zero of the base where <c>a</c> is negative.
/// </remarks>
[Trait("Area", "Calculus")]
public sealed class APowerOfAHyperbolicCosineAndSinePlusTheirAmplitudeIntegralTest
{
[Theory]
[InlineData("(b*cosh(x) + c*sinh(x) + sqrt(b^2 - c^2))^(5/2)", 2.0, 1.0)]
[InlineData("sqrt(b*cosh(x) + c*sinh(x) - sqrt(b^2 - c^2))", 2.0, 1.0)]
[InlineData("1/sqrt(b*cosh(x) + c*sinh(x) + sqrt(b^2 - c^2))", 2.0, 1.0)]
[InlineData("1/(b*cosh(x) + c*sinh(x) - sqrt(b^2 - c^2))^(5/2)", 2.0, -1.0)]
[InlineData("1/(b*cosh(g + f*x) + c*sinh(g + f*x) + sqrt(b^2 - c^2))^(3/2)", 0.7, 1.1)]
[InlineData("(b*cosh(g + f*x) + c*sinh(g + f*x) - sqrt(b^2 - c^2))^(3/2)", 0.7, 1.1)]
[InlineData("1/sqrt(5*cosh(2*x + 1) + 3*sinh(2*x + 1) + 4)", 0.0, 0.0)]
[InlineData("3*(2*cosh(x) - sinh(x) - sqrt(3))^(-3/2)", 0.0, 0.0)]
public void IsWrittenThroughTheDerivativeOfItsBase(string integrand, double b, double c)
=> DifferentiatesBack(integrand, b, c);

// A whole power is left to the exponential substitution, which writes `1/S^2`, `1/S^3` and
// `1/S^4` shorter than the same recurrence stepped up to `T/(a S)`: answered all the same.
[Theory]
[InlineData("1/(b*cosh(x) + c*sinh(x) + sqrt(b^2 - c^2))^2", 2.0, 1.0)]
[InlineData("1/(b*cosh(x) + c*sinh(x) + sqrt(b^2 - c^2))^3", 2.0, -1.0)]
[InlineData("1/(b*cosh(x) + c*sinh(x) + sqrt(b^2 - c^2))^4", 0.7, 1.1)]
[InlineData("1/(b*cosh(g + f*x) + c*sinh(g + f*x) - sqrt(b^2 - c^2))^3", 2.0, 1.0)]
[InlineData("3*(2*cosh(x) - sinh(x) - sqrt(3))^(-2)", 0.0, 0.0)]
[InlineData("1/(5*cosh(2*x + 1) + 3*sinh(2*x + 1) + 4)^3", 0.0, 0.0)]
public void AWholePowerIsAnsweredToo(string integrand, double b, double c)
=> DifferentiatesBack(integrand, b, c);

private static void DifferentiatesBack(string integrand, double b, double c)
{
var integral = integrand.ToEntity().Integrate("x");
Assert.DoesNotContain("integral(", integral.Stringize());
Entity Pinned(Entity e) => e.Substitute("b", b).Substitute("c", c).Substitute("g", 0.4).Substitute("f", 1.3);
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
var original = Pinned(integrand.ToEntity());
foreach (var at in new[] { -1.9, -1.1, -0.2, 0.4, 1.1, 1.9 })
{
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}");
}
}

/// <summary>
/// Real and continuous where the integrand is: across the point where <c>T</c> vanishes and
/// the base does not, and on either side of the base's zero where <c>a</c> is negative.
/// The difference of the answer against Simpson's rule.
/// </summary>
[Theory]
[InlineData(1.0, -2.0, 1.0)]
[InlineData(-1.0, 0.1, 2.0)]
[InlineData(-1.0, -3.0, -1.0)]
public void IsAContinuousAntiderivativeOnTheRealLine(double sign, double from, double to)
{
var root = sign > 0 ? "sqrt(3)" : "-sqrt(3)";
var integral = $"1/sqrt(2*cosh(x) + sinh(x) + {root})".ToEntity().Integrate("x").Substitute("C", 0);
Assert.DoesNotContain("integral(", integral.Stringize());
double Integrand(double t) => 1 / Math.Sqrt(2 * Math.Cosh(t) + Math.Sinh(t) + sign * Math.Sqrt(3));
const int steps = 400;
var h = (to - from) / steps;
var simpson = Integrand(from) + Integrand(to);
for (var k = 1; k < steps; k++)
simpson += (k % 2 == 1 ? 4 : 2) * Integrand(from + k * h);
simpson *= h / 3;
var difference = integral.Substitute("x", to).EvalNumerical() - integral.Substitute("x", from).EvalNumerical();
Assert.True(Math.Abs((double)difference.ImaginaryPart) < 1e-9, $"F({to}) - F({from}) is {difference}");
Assert.True(Math.Abs((double)difference.RealPart - simpson) < 1e-7, $"F({to}) - F({from}) is {difference}, Simpson's rule {simpson}");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//
// 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
{
/// <summary>
/// A symbolic power of <c>sinh(y)</c> beside an odd power of <c>cosh(y)</c>, under
/// <c>u = sinh(y)</c>, and the other way about under <c>u = cosh(y)</c>. Rubi's 6.7.1.
/// <a href="https://github.com/asc-community/AngouriMath/issues/233">#233</a>
/// </summary>
/// <remarks>Compared as complex numbers: a symbolic power of a negative sine is complex.</remarks>
[Trait("Area", "Calculus")]
public sealed class ASymbolicPowerOfAHyperbolicFunctionBesideAnOddPowerOfTheOtherIntegralTest
{
[Theory]
[InlineData("cosh(a + b*x)^3*sinh(a + b*x)^n")]
[InlineData("cosh(a + b*x)^5*sinh(a + b*x)^n")]
[InlineData("cosh(a + b*x)^m*sinh(a + b*x)^3")]
[InlineData("cosh(a + b*x)^m*sinh(a + b*x)^5")]
[InlineData("2*cosh(x)^3/sinh(x)^n")]
public void IsAPolynomialTimesAPowerInTheOther(string integrand)
{
var integral = integrand.ToEntity().Integrate("x");
Assert.DoesNotContain("integral(", integral.Stringize());
Entity Pinned(Entity e) => e.Substitute("a", 0.4).Substitute("b", 1.3).Substitute("n", 0.37).Substitute("m", -1.6);
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
var original = Pinned(integrand.ToEntity());
foreach (var at in new[] { -1.9, -1.1, -0.7, 0.4, 1.1, 1.9 })
{
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}");
}
}

/// <summary>
/// The reciprocals to the symbolic power and the tangent and cotangent beside them, read as
/// powers of the two: <c>sech(y)^n tanh(y)^3</c> under <c>u = sech(y)</c>, and
/// <c>coth(y)^3 csch(y)^n</c> under <c>u = csch(y)</c>, whose power is complex where the sine
/// is negative and not that of <c>sinh(y)^(-n)</c>. Rubi's 6.7.1. Both signs of the
/// argument at each slope.
/// </summary>
[Theory]
[InlineData("sech(a + b*x)^n*tanh(a + b*x)^3", 0.4, 1.3)]
[InlineData("coth(a + b*x)^3*csch(a + b*x)^n", 0.4, 1.3)]
[InlineData("coth(a + b*x)^3*csch(a + b*x)^n", 0.9, -0.8)]
[InlineData("sech(a + b*x)^m*tanh(a + b*x)^5", -0.4, 1.3)]
[InlineData("coth(a + b*x)^5*csch(a + b*x)^m", 0.9, -0.8)]
[InlineData("cosh(a + b*x)^n*tanh(a + b*x)^3", 0.4, 1.3)]
[InlineData("sinh(a + b*x)^m*coth(a + b*x)^3", -0.4, 1.3)]
[InlineData("csch(a + b*x)^n*cosh(a + b*x)^3", 0.4, 1.3)]
[InlineData("2*sech(x)^n*sinh(x)^3", 0.0, 1.0)]
public void ReadsTheReciprocalsAndTheQuotients(string integrand, double a, double b)
{
var integral = integrand.ToEntity().Integrate("x");
Assert.DoesNotContain("integral(", integral.Stringize());
Entity Pinned(Entity e) => e.Substitute("a", a).Substitute("b", b).Substitute("n", 0.37).Substitute("m", -1.6);
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
var original = Pinned(integrand.ToEntity());
foreach (var at in new[] { -1.9, -1.1, -0.7, 0.4, 1.1, 1.9 })
{
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}");
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//
// 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
{
/// <summary>
/// <c>cosh(y)^(2m)</c> over <c>(1 ± i sinh(y))^(2m)</c>, or the other way up, cancelled through
/// <c>cosh(y)^2 = (1 + i sinh(y))(1 - i sinh(y))</c>: <c>1/(sech(x) + i tanh(x))^4</c> is
/// <c>(1 - i sinh(x))^2/(1 + i sinh(x))^2</c>. Rubi's 6.7.1.
/// <a href="https://github.com/asc-community/AngouriMath/issues/233">#233</a>
/// </summary>
/// <remarks>Compared as complex numbers: the integrand is complex wherever it is defined.</remarks>
[Trait("Area", "Calculus")]
public sealed class CancellingOnePlusAnImaginaryHyperbolicSineAgainstTheCosineSquaredIntegralTest
{
[Theory]
[InlineData("1/(sech(x) + i*tanh(x))^4")]
[InlineData("1/(sech(x) - i*tanh(x))^4")]
[InlineData("1/(sech(x) + i*tanh(x))^6")]
[InlineData("1/(sech(a + b*x) - i*tanh(a + b*x))^4")]
[InlineData("cosh(x)^4/(1 + i*sinh(x))^4")]
[InlineData("3*cosh(a + b*x)^6/(a - i*a*sinh(a + b*x))^6")]
[InlineData("(sech(x) + i*tanh(x))^6")]
[InlineData("cosh(x)^2/(i + sinh(x))^2")]
public void IsTheQuotientTheCancellationLeaves(string integrand)
{
var integral = integrand.ToEntity().Integrate("x");
Assert.DoesNotContain("integral(", integral.Stringize());
Entity Pinned(Entity e) => e.Substitute("a", -0.4).Substitute("b", 1.3);
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
var original = Pinned(integrand.ToEntity());
foreach (var at in new[] { -1.9, -1.1, -0.2, 0.4, 1.1, 1.9 })
{
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}");
}
}
}
}
Loading
Loading