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
21 changes: 21 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/// <summary>
/// A power of x times a rational function of hyperbolic functions of a linear in a whole
/// power of x, <c>x^m F(sinh(c + d x^n), cosh(c + d x^n))</c> with <c>n &gt;= 2</c> dividing
/// <c>m + 1</c>, under <c>t = x^n</c>: <c>x^m dx</c> is <c>t^(k - 1) dt/n</c> for
/// <c>k = (m + 1)/n</c>, and the question in <c>t</c> is the same function of a linear in
/// <c>t</c> beside a whole power of <c>t</c>.
/// </summary>
/// <remarks>
/// The library writes the hyperbolic functions as exponentials, so <c>x sech(a + b x^2)^7</c>
/// arrives as x beside a seventh power of a quotient of exponentials of <c>a + b x^2</c>, and
/// the rules before this one spent the budget on it: Rubi's 6.5.2 and 6.6.2
/// <c>x sech(a + b x^2)^7</c>, <c>x/(a + b sech(c + d x^2))^2</c> and the two with the
/// cosecant, 6.1.3 and 6.2.3 <c>sinh(a + b x^2)^3/x^3</c> and its cosine, and 6.7.1
/// <c>x^5 cosh(a + b x^3)^7 sinh(a + b x^3)</c>, each of which in <c>t</c> is answered in
/// under a second. Exact wherever the integrand is defined, since the powers are whole:
/// <c>x^m</c> is <c>x^(n - 1) (x^n)^(k - 1)</c> at every <c>x</c> but 0. Exponentials of a
/// linear in <c>x^n</c> and whole powers of them only, with a whole power past the first of
/// a sum or a quotient of them among them: <c>x/(a + b sech(c + d x^2))</c> and
/// <c>x^3 sinh(a + b x^2)</c> are answered as written, in fewer characters than in
/// <c>t</c>, 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
/// <c>n</c> is <see cref="SolveByAPowerOfTheVariableTimesAFunctionOfItsPower"/>'s.
/// https://github.com/asc-community/AngouriMath/issues/233
/// </remarks>
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;
}

/// <summary>
/// A product of whole powers of a sine and a cosine of one linear times something else in x
/// that is no polynomial, <c>sin(u)^3 cos(u)^6/(a + b u)</c>, linearised: the product is a sum
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// A power of x times a whole power of hyperbolic functions of a linear in <c>x^n</c>, under
/// <c>t = x^n</c>, where <c>x^m dx</c> is <c>t^((m + 1)/n - 1) dt/n</c>. The hyperbolic functions
/// arrive as exponentials of <c>a + b x^n</c>, 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.
/// <a href="https://github.com/asc-community/AngouriMath/issues/233">#233</a>
/// </summary>
[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}");
}
}
}
Loading