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
16 changes: 16 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,22 @@ of the third to the sixth degree, with a symbol in it, that written in `y = x +
| `"1/(3*a*b + 3*b^2*x + 3*b*c*x^2 + c^2*x^3)".ToEntity().Integrate("x")`, and its square | `integral(...)` | two logarithms and an arctangent in `x + b/c` |
| `"x/(a + 8*x - 8*x^2 + 4*x^3 - x^4)".ToEntity().Integrate("x")`, and `1` over it | `integral(...)` | the antiderivative in `x - 1` |

### Half-odd powers of `a + i a sinh` are integrated by the half angle at which they are squares

**Answers where there were none.** `x^3 sqrt(a + i a sinh(g + f x))` was declined, with the rest of
Rubi's 6.1.1 and 6.1.5 with a half-odd power of `a ± i a sinh`, six of them after searches past the
budget. `1 + i sinh(y)` is `(cosh(y/2) + i sinh(y/2))^2`, so such a power is `a^p` times an odd
power of `cosh(y/2) ± i sinh(y/2)`, a sum of exponentials of the half angle, up to a constant on
every interval where both are continuous; it is integrated so now, and the answer is the
antiderivative times the power as written over that form, which holds on every such interval
whatever `a` is ([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"x^3*sqrt(a + i*a*sinh(g + f*x))".ToEntity().Integrate("x")` | `integral(...)` | powers of `x` times exponentials of the half angle |
| `"sqrt(a + i*a*sinh(g + f*x))/x".ToEntity().Integrate("x")` | `integral(...)` | `Shi` and `Chi` of half of `f x` |
| `"1/sqrt(a + i*a*sinh(g + f*x))".ToEntity().Integrate("x")` | `integral(...)` | arctangents and logarithms of `e^((g + f x)/2)` |

### The hyperbolic functions have antiderivatives, and so does anything rational in `e^(k x)`

An integrand rational in `e^(k x)` becomes a rational function of one variable under
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21936,6 +21936,113 @@ private static (Entity OverTheTwo, Entity Argument)? ReadTheHyperbolicFunctions(
return answer.Nodes.Any(node => node == MathS.NaN) ? null : answer;
}

/// <summary>
/// Half-odd powers of <c>a ± i a sinh(y)</c> beside anything in the hyperbolic functions of
/// <c>y</c>, by the half angle at which they are squares: <c>1 + i sinh(y)</c> is
/// <c>(cosh(y/2) + i sinh(y/2))^2</c>, so <c>sqrt(a + i a sinh(y))</c> is
/// <c>sqrt(a) (cosh(y/2) + i sinh(y/2))</c> times a constant on every interval where both
/// are continuous. The rest of the integrand is written in the half angle, and the question
/// asked again in <c>x</c>.
/// </summary>
/// <remarks>
/// <para>
/// Rubi's <c>x^3 sqrt(a + i a sinh(e + f x))</c> and the rest of the 47 of 6.1.1 and 6.1.5
/// were declined or searches past the budget: the half angle of
/// <see cref="SolveByTheHalfAngleWhereOnePlusAHyperbolicCosineIsASquare"/> reads
/// <c>a ± a cosh(y)</c>, and <c>1 + i sinh(y)</c> is the square of a complex function rather
/// than of a real one.
/// </para>
/// <para>
/// The constant is not written: the answer is the antiderivative of the rewritten integrand
/// times each power as written over the form it was rewritten to, a quotient whose
/// logarithmic derivative is zero, so it is constant wherever it is continuous and the
/// answer holds on every such interval, whatever <c>a</c> is.
/// https://github.com/asc-community/AngouriMath/issues/718
/// </para>
/// </remarks>
internal static Entity? SolveAHalfOddPowerOfOnePlusAnImaginaryHyperbolicSine(Entity expr, Entity.Variable x, bool integrateByParts)
{
var s = Variable.CreateUnique(expr, "s_hyp");
var c = Variable.CreateUnique(expr, "c_hyp");
if (ReadTheHyperbolicFunctions(expr, x, s, c) is not var (read, argument))
return null;
var half = (argument / 2).InnerSimplified;
var sinhHalf = MathS.Hyperbolic.Sinh(half);
var coshHalf = MathS.Hyperbolic.Cosh(half);
Entity constant = Number.Integer.One;
var found = false;
var rewritten = read.Replace(node =>
{
if (node is not Powf(var radicand, Number.Rational exponent) || exponent is Number.Integer
|| !exponent.ERational.Denominator.Equals(EInteger.FromInt32(2))
|| !radicand.ContainsNode(s) || radicand.ContainsNode(c)
|| ReadAsOnePlusMinusAnImaginarySine(radicand, s) is not var (a, plus))
return node;
found = true;
var root = plus ? coshHalf + MathS.i * sinhHalf : coshHalf - MathS.i * sinhHalf;
var written = MathS.Pow(a, exponent) * MathS.Pow(root, Number.Integer.Create(exponent.ERational.Numerator));
var asItIs = MathS.Pow(radicand.Substitute(s, MathS.Hyperbolic.Sinh(argument)), exponent);
constant = constant * asItIs / written;
return written;
});
if (!found)
return null;
var inX = rewritten.Substitute(c, 2 * MathS.Sqr(coshHalf) - 1).Substitute(s, 2 * sinhHalf * coshHalf);
if (inX.ContainsNode(s) || inX.ContainsNode(c))
return null;
if (Integration.ComputeAsTheSameQuestion(inX, x, integrateByParts) is not { } result
|| result.Nodes.Any(node => node == MathS.NaN))
return null;
return constant * result;
}

/// <summary>
/// <paramref name="radicand"/> read as <c>a (1 ± i s)</c> for the hyperbolic sine
/// <paramref name="sine"/>: the constant <c>a</c>, and whether the imaginary unit comes
/// with a plus. Null for anything else.
/// </summary>
private static (Entity Constant, bool Plus)? ReadAsOnePlusMinusAnImaginarySine(Entity radicand, Entity sine)
{
Entity a = Number.Integer.Zero;
Entity? b = null;
foreach (var term in Sumf.LinearChildren(radicand))
{
if (!term.ContainsNode(sine))
{
a += term;
continue;
}
if (b is not null)
return null;
Entity coefficient = Number.Integer.One;
var seen = false;
foreach (var factor in Mulf.LinearChildren(term))
{
if (factor == sine && !seen)
seen = true;
else if (!factor.ContainsNode(sine))
coefficient *= factor;
else
return null;
}
if (!seen)
return null;
b = coefficient;
}
if (b is null)
return null;
a = a.InnerSimplified;
if (a.Evaled is Number.Complex { IsZero: true })
return null;
static bool IsZero(Entity value) => value.InnerSimplified.Evaled is Number.Complex { IsZero: true }
|| value.Simplify().Evaled is Number.Complex { IsZero: true };
if (IsZero(b - MathS.i * a))
return (a, true);
if (IsZero(b + MathS.i * a))
return (a, false);
return null;
}

/// <summary>
/// A sum every term of which carries the same power of one linear form in
/// <paramref name="x"/>, written as that power times the sum of the rest:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -853,6 +853,8 @@ private static Entity Normalized(Entity expr, Entity.Variable x) =>
if ((answer = IndefiniteIntegralSolver.SolveByTheHalfAngleTangentBesideAHalfOddPowerOfOnePlusASecant(expr, x, integrateByParts)) is { }) return answer;
// And `a ± a cosh(y)` under a fractional power: `2a cosh(y/2)^2`, `-2a sinh(y/2)^2`.
if ((answer = IndefiniteIntegralSolver.SolveByTheHalfAngleWhereOnePlusAHyperbolicCosineIsASquare(expr, x, integrateByParts)) is { }) return answer;
// The hyperbolic sine's, off the real line: 1 + i sinh(y) is (cosh(y/2) + i sinh(y/2))^2.
if ((answer = IndefiniteIntegralSolver.SolveAHalfOddPowerOfOnePlusAnImaginaryHyperbolicSine(expr, x, integrateByParts)) is { }) return answer;
// Several trigonometric arguments that are multiples of one linear with an offset or a
// symbolic slope, written in that linear: before the substitution search, which reads
// each function on its own.
Expand Down
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Half-odd powers of <c>a + i a sinh(y)</c>, which is <c>a (cosh(y/2) + i sinh(y/2))^2</c>:
/// beside a power of <c>x</c> each is a sum of exponentials of the half angle times that power,
/// up to a constant on every interval where both are continuous. Rubi's 6.1.1 and 6.1.5. The
/// integrands are complex, and compared as complex numbers on both sides of zero.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class OnePlusAnImaginaryHyperbolicSineIntegralTest
{
[Theory]
[InlineData("x^3*sqrt(a + i*a*sinh(g + f*x))")]
[InlineData("x*sqrt(a + i*a*sinh(g + f*x))")]
[InlineData("sqrt(a + i*a*sinh(g + f*x))/x")]
[InlineData("x^3*(a + i*a*sinh(g + f*x))^(3/2)")]
[InlineData("1/sqrt(a + i*a*sinh(g + f*x))")]
public void ByTheHalfAngle(string integrand)
{
var integral = integrand.ToEntity().Integrate("x");
Assert.DoesNotContain("integral(", integral.Stringize());
Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("g", 0.4).Substitute("f", 1.1);
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
var original = Pinned(integrand.ToEntity());
foreach (var at in new[] { -1.2, -0.4, 0.3, 0.8, 1.5 })
{
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