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
17 changes: 17 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,23 @@ made `0^(-1)` an infinity and `0^0` 1 ([#2286](https://github.com/asc-community/
| `"x^(-1)".Compile<double, double>("x")(0)` | `+Infinity` | `NaN` |
| `"x^x".Compile<double, double>("x")(0)` | `1` | `NaN` |

### `tanh` and `coth` of a large number are their limits

**Silent: `tanh` and `coth` of a large positive number evaluate and compile to `1`, where they were `NaN`, and at
`+oo` they are `1`, as at `-oo` they were already `-1`.** Both are spelled `(e^(2x) - 1) / (e^(2x) + 1)` and its
reciprocal, and past the range of the decimals, or of a double, both sides were infinite. A quotient of one power plus
two constants, `(b^v + c1) / (b^v + c2)`, is now evaluated as `1 + (c1 - c2) / (b^v + c2)` where both sides are
infinite, and always compiled so, since a double cannot tell an overflow from an infinity
([#2254](https://github.com/asc-community/AngouriMath/issues/2254)).

| Input | Was (2.6.0) | Now |
|---|---|---|
| `"tanh(5568)".ToEntity().Evaled` | `NaN` | `1` |
| `"coth(332640)".ToEntity().EvalNumerical()` | `NaN` | `1` |
| `"tanh(+oo)".ToEntity().Evaled` | `NaN` | `1` |
| `"tanh(x)".Compile<double, double>("x")(400)` | `NaN` | `1` |
| `"coth(x)".ToEntity().Compile("x").Call(400)` | `(NaN, NaN)` | `(1, 0)` |

### A lambda applied to a vector multiplies by it as a vector

**Silent: a matrix of vectors becomes the product.** Applying a lambda simplified its body first, and a simplified
Expand Down
6 changes: 6 additions & 0 deletions Sources/.editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,9 @@ file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed
[AngouriMath/Numerics/NumberStatements.cs]
file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n

[AngouriMath/Functions/ExponentialQuotients.cs]
file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n

[AngouriMath/Functions/Compilation/CompiledMembership.cs]
file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n

Expand Down Expand Up @@ -1013,6 +1016,9 @@ file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed
[Tests/UnitTests/Common/DivisionByZeroOverDoubleTest.cs]
file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n

[Tests/UnitTests/Common/HyperbolicTangentPastTheRangeTest.cs]
file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n

[Tests/UnitTests/Common/RefuseNonRealCompilationTest.cs]
file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1016,6 +1016,7 @@ internal static FastExpression Compile(Entity func, IEnumerable<Variable> variab
foreach (var constant in Variable.NamedConstants.Values)
func = func.Substitute(constant, constant.Value);
func = Compilation.CompiledMembership.Rewritten(func);
func = Functions.ExponentialQuotients.Rewritten(func);
Compilation.StatementPositions.Check(func);
var visited = new HashSet<Entity>();
var cache = new Dictionary<Entity, int>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ internal static TDelegate Compile<TDelegate>(
if (expr.Nodes.Any(node => node is Entity.Matrix))
expr = expr.InnerSimplified;
expr = CompiledMembership.Rewritten(expr);
expr = Functions.ExponentialQuotients.Rewritten(expr);
StatementPositions.Check(expr);
if (MathS.Settings.RefuseNonRealCompilation && returnType is { } real && IsRealType(real)
&& typesAndNames.All(c => IsRealType(c.type)) && !IsRealWhereverDefined(expr, typesAndNames))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,11 @@ n0.Evaled is Complex c
? c.IsZero ? MathS.NaN : 0
: new Providedf(0, new Notf(new Equalsf(n0, 0))),
(_, Integer(0)) => Real.NaN,
// tanh's spelling past the decimals' range, both sides infinite: the same
// quotient without them. https://github.com/asc-community/AngouriMath/issues/2254
(Complex { IsFinite: false }, Complex { IsFinite: false }) when !isExact
&& Functions.ExponentialQuotients.Rescaled(Dividend, Divisor) is { } rescaled
=> rescaled.InnerSimplified(isExact),
(Complex n1, Complex n2) when !isExact => n1 / n2,
(var n1, Integer(1)) => n1,
// Sizes are not divided: aleph(0) / 2 has no value.
Expand Down
57 changes: 57 additions & 0 deletions Sources/AngouriMath/Functions/ExponentialQuotients.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//
// 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 static AngouriMath.Entity;

namespace AngouriMath.Functions
{
/// <summary>
/// A quotient of one power plus two constants, <c>(b^v + c1) / (b^v + c2)</c>, as
/// <c>1 + (c1 - c2) / (b^v + c2)</c>. The two are the same value wherever the quotient has one,
/// and the second does not divide an overflow by an overflow.
/// </summary>
/// <remarks>
/// It is how <c>tanh</c> and <c>coth</c> are spelled: <see cref="MathS.Hyperbolic.Tanh"/> is
/// <c>(e^(2x) - 1) / (e^(2x) + 1)</c>. Past the range of the decimals, or of a double, both sides
/// of it are infinite, so <c>tanh(5568)</c> evaluated to NaN and <c>tanh(400)</c> compiled to NaN,
/// where both are 1. At a real infinity the second form is the limit, as <c>tanh(-oo)</c> already
/// was <c>-1</c> and <c>arctan(+oo)</c> is <c>pi / 2</c>: <c>tanh(+oo)</c> is 1.
/// https://github.com/asc-community/AngouriMath/issues/2254
/// </remarks>
internal static class ExponentialQuotients
{
/// <summary>
/// <c>1 + (c1 - c2) / divisor</c> where <paramref name="dividend"/> and
/// <paramref name="divisor"/> are one power plus a constant each, and <see langword="null"/>
/// otherwise.
/// </summary>
internal static Entity? Rescaled(Entity dividend, Entity divisor)
=> Split(dividend) is var (power, c1) && Split(divisor) is var (same, c2) && power == same
? 1 + (c1 - c2) / divisor
: null;

/// <summary>
/// Every such quotient in <paramref name="expression"/> rewritten, for the compilers: a double
/// cannot tell an overflow from an infinity, so the rewrite is made wherever the shape is.
/// </summary>
internal static Entity Rewritten(Entity expression)
=> expression.Nodes.Any(node => node is Divf(Sumf or Minusf, Sumf or Minusf))
? expression.Replace(node => node is Divf(var dividend, var divisor) && Rescaled(dividend, divisor) is { } rescaled ? rescaled : node)
: expression;

/// <summary>The power and the constant of <c>b^v + c</c>, <c>c + b^v</c> or <c>b^v - c</c>, with <c>b</c> and <c>c</c> constants.</summary>
private static (Powf Power, Entity Constant)? Split(Entity sum) => sum switch
{
Sumf(Powf power, var constant) when IsConstant(power.Base) && IsConstant(constant) => (power, constant),
Sumf(var constant, Powf power) when IsConstant(power.Base) && IsConstant(constant) => (power, constant),
Minusf(Powf power, var constant) when IsConstant(power.Base) && IsConstant(constant) => (power, -constant),
_ => null,
};

private static bool IsConstant(Entity expression) => !expression.Vars.Any();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//
// 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 System.Numerics;
using AngouriMath;
using AngouriMath.Extensions;
using Xunit;

namespace AngouriMath.Tests.Common
{
/// <summary>
/// <c>tanh</c> and <c>coth</c> are spelled <c>(e^(2x) - 1) / (e^(2x) + 1)</c> and its reciprocal, and past the range
/// of the decimals, or of a double, both sides are infinite: <c>tanh(5568)</c> evaluated to NaN and <c>tanh(400)</c>
/// compiled to NaN, where both are 1. The negative side was right, since <c>e^(-11136)</c> is 0.
/// <a href="https://github.com/asc-community/AngouriMath/issues/2254">#2254</a>
/// </summary>
[Trait("Area", "Common")]
public sealed class HyperbolicTangentPastTheRangeTest
{
[Theory]
[InlineData("tanh(5568)", 1)]
[InlineData("tanh(-5568)", -1)]
[InlineData("coth(332640)", 1)]
[InlineData("coth(-332640)", -1)]
[InlineData("tanh(+oo)", 1)]
[InlineData("tanh(-oo)", -1)]
[InlineData("coth(+oo)", 1)]
public void EvaluatedPastTheRangeItIsOne(string expression, int expected)
{
Assert.Equal(Entity.Number.Integer.Create(expected), expression.ToEntity().Evaled);
Assert.Equal(Entity.Number.Integer.Create(expected), expression.ToEntity().EvalNumerical());
}

[Theory]
[InlineData("tanh(x)", 400, 1)]
[InlineData("tanh(x)", -400, -1)]
[InlineData("coth(x)", 400, 1)]
[InlineData("coth(x)", -400, -1)]
[InlineData("tanh(x)", double.PositiveInfinity, 1)]
[InlineData("tanh(x)", double.NegativeInfinity, -1)]
public void CompiledPastTheRangeItIsOne(string expression, double at, double expected)
{
Assert.Equal(expected, expression.Compile<double, double>("x")(at));
Assert.Equal(new Complex(expected, 0), expression.Compile<Complex, Complex>("x")(at));
Assert.Equal(new Complex(expected, 0), expression.Compile("x").Call(at));
}

/// <summary>Inside the range the compiled values are still the hyperbolic tangent's, near 0 as well.</summary>
[Theory]
[InlineData(1e-6)]
[InlineData(-0.3)]
[InlineData(2)]
[InlineData(19)]
public void CompiledInsideTheRangeItIsTheHyperbolicTangent(double at)
{
var compiled = "tanh(x)".Compile<double, double>("x")(at);
Assert.True(Math.Abs(compiled - Math.Tanh(at)) <= 1e-9 * Math.Abs(Math.Tanh(at)), $"{compiled} against {Math.Tanh(at)}");
var coth = "coth(x)".Compile<double, double>("x")(at);
Assert.True(Math.Abs(coth - 1 / Math.Tanh(at)) <= 1e-9 * Math.Abs(1 / Math.Tanh(at)), $"{coth} against {1 / Math.Tanh(at)}");
}
}
}
Loading