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
15 changes: 15 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,21 @@ nested integrals cost twice as much per level
| `new Integralf("t + a".ToEntity(), "t", (0, 1)).Substitute("a", "t")` | `integral(t + t, t, 0, 1)` | `integral(t_1 + t, t_1, 0, 1)` |
| `new Summationf("k * a".ToEntity(), "k", 1, 3).Substitute("a", "k")` | `sum(k * k, k, 1, 3)`, 14 | `sum(k_1 * k, k_1, 1, 3)`, `6 k` |

### A power of `a + i a csch` is integrated in the exponential

**Answers where there were none.** `sqrt(a + i a csch(c + d x))` was declined, with the rest of Rubi's
6.6.3 that is a power of the sum not whole. In `w = e^(c + d x)` the sum is `a (w + i)^2/(w^2 - 1)`, the
square of a complex function over a real one, so its power is `a^p (w + i)^(2p) (w^2 - 1)^(-p)` times a
constant on every interval where both are continuous, and the integrand in `w` is answered at once. The
constant is not written: the answer is the integrand times the antiderivative in `w` over what that
differentiates back to ([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"sqrt(a + i*a*csch(c + d*x))".ToEntity().Integrate("x")` | `integral(...)` | 454 characters |
| `"(a + i*a*csch(c + d*x))^(3/2)".ToEntity().Integrate("x")` | `integral(...)` | 834 characters |
| `"1/sqrt(a - i*a*csch(c + d*x))".ToEntity().Integrate("x")` | `integral(...)` | 575 characters |

### A power of a constant below `1e-50` is no longer simplified to zero

**Answers that were wrong.** Evaluation rounds a value within `1e-50` of an integer onto it, and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24709,6 +24709,70 @@ private static (Entity OverTheTwo, Entity Argument)? ReadTheHyperbolicFunctions(
return constant * result;
}

/// <summary>
/// A power of <c>a ± i a csch(y)</c>, not whole, integrated in <c>w = e^y</c>: there
/// <c>csch(y)</c> is <c>2 w/(w^2 - 1)</c> and the sum is <c>a (w ± i)^2/(w^2 - 1)</c>, so its power is
/// <c>a^p (w ± i)^(2p) (w^2 - 1)^(-p)</c> times a constant on every interval where both are
/// continuous, and <c>dy = dw/w</c>.
/// </summary>
/// <remarks>
/// Rubi's <c>sqrt(a + i a csch(c + d x))</c> and the rest of 6.6.3's were declined: the sum is
/// the square of a complex function over a real one, which nothing read. The constant is not
/// written: the answer is the integrand times the antiderivative in <c>w</c> over what that
/// antiderivative differentiates back to, a quotient constant wherever it is continuous; and it
/// is kept only where its derivative is the integrand at the sampled points.
/// https://github.com/asc-community/AngouriMath/issues/718
/// </remarks>
internal static Entity? SolveAPowerOfAnImaginaryHyperbolicCosecantSumInTheExponential(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)
|| read.ContainsNode(c) || read.ContainsNode(x)
|| !TreeAnalyzer.TryGetPolyLinear(argument, x, out var slope, out _) || TreeAnalyzer.IsZero(slope))
return null;
Entity? radicand = null;
Number.Rational? power = null;
Entity constant = Number.Integer.One;
foreach (var (factor, underneath) in FactorsOfTheIntegrand(read))
{
if (!factor.ContainsNode(s))
{
constant = underneath ? constant / factor : constant * factor;
continue;
}
if (radicand is not null || factor is not Powf(var @base, Number.Rational exponent) || exponent is Number.Integer)
return null;
(radicand, power) = (@base, underneath ? Number.Rational.Create(exponent.ERational.Negate()) : exponent);
}
if (radicand is null || power is null || !radicand.Nodes.Any(node => node is Divf(_, var below) && below == s || node is Powf(var b, Number.Integer { EInteger.Sign: < 0 }) && b == s))
return null;
// In w: s = (w - 1/w)/2, and the sum as one quotient.
var w = Variable.CreateUnique(expr, "w_exp");
var (top, bottom) = Functions.SingleQuotient.Of(Functions.SingleQuotient.Combine(radicand.Substitute(s, (w - 1 / w) / 2)));
foreach (var sign in new[] { MathS.i, -MathS.i })
{
var square = MathS.Sqr(w + sign);
// Bare: the quotient simplifies to `a provided not w + i = 0`, a condition on w beside a constant.
var q = Functions.PartialFractions.Bare((top / square).Simplify());
if (q.ContainsNode(w))
continue;
var twice = Number.Rational.Create(power.ERational.Multiply(ERational.FromInt32(2)));
var inW = (constant * MathS.Pow(q, power) * MathS.Pow(w + sign, twice) * MathS.Pow(bottom, Number.Rational.Create(power.ERational.Negate())) / (slope * w)).InnerSimplified;
if (Integration.ComputeAsAQuestionOfItsOwn(inW, w, integrateByParts) is not { } inTermsOfW
|| inTermsOfW.Nodes.Any(node => node == MathS.NaN))
return null;
// Checked in w, where the antiderivative is of the integrand as written: in x, with the
// slope a symbol pinned at a sample, e^(c + d x) at the sampled points was past what the
// evaluation decides.
if (!Functions.PartialFractions.HoldsAtSampledPoints(inTermsOfW.Differentiate(w), inW, w))
return null;
var exponential = MathS.Pow(MathS.e, argument);
return expr * inTermsOfW.Substitute(w, exponential) / (inW.Substitute(w, exponential) * slope * exponential);
}
return null;
}

/// <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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -909,6 +909,8 @@ private static Entity Normalized(Entity expr, Entity.Variable x) =>
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;
// 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;
// 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,51 @@
//
// 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 <c>a ± i a csch(c + d x)</c>, not whole, integrated in <c>w = e^(c + d x)</c>, where the sum
/// is <c>a (w ± i)^2/(w^2 - 1)</c>. Rubi's 6.6.3. The integrands are complex for a real <c>x</c> and are
/// compared as complex numbers, on both signs of the argument.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class APowerOfAnImaginaryHyperbolicCosecantSumIntegralTest
{
[Theory]
[InlineData("sqrt(a + i*a*csch(c + d*x))")]
[InlineData("(a + i*a*csch(c + d*x))^(3/2)")]
[InlineData("1/sqrt(a - i*a*csch(c + d*x))")]
public void InTheExponential(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("c", 0.4).Substitute("d", 1.1);
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
var original = Pinned(integrand.ToEntity());
var compared = 0;
foreach (var at in new[] { -1.2, -0.7, 0.3, 0.8, 1.3, 2.9 })
{
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