diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md
index 795771c4a..023f26be6 100644
--- a/BREAKING-CHANGES.md
+++ b/BREAKING-CHANGES.md
@@ -684,7 +684,8 @@ and below `-1/e` there is none ([#2182](https://github.com/asc-community/Angouri
al. (1996), as Mathematica's `ProductLog` and mpmath's `lambertw` have them: `W_0` is real on `[-1/e, +oo)` and `W_(-1)`
on `[-1/e, 0)`. It is exact at `0` and where `z` is `c e^c` for a real `c` the branch takes, so `lambertw(e)` is `1`
and `lambertw(-1/e)` is `-1`, and it is evaluated numerically elsewhere. Its derivative is `e^(-W(z))/(1 + W(z))`
-([#2182](https://github.com/asc-community/AngouriMath/issues/2182)).
+([#2182](https://github.com/asc-community/AngouriMath/issues/2182)). It compiles, in double precision on the same
+branches, in both compilers ([#2195](https://github.com/asc-community/AngouriMath/issues/2195)).
| Input | Was (2.6.0) | Now |
|---|---|---|
diff --git a/Sources/.editorconfig b/Sources/.editorconfig
index 7d45b2b7b..a24681bd6 100644
--- a/Sources/.editorconfig
+++ b/Sources/.editorconfig
@@ -363,6 +363,12 @@ file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed
[AngouriMath/Numerics/CombinatorialFunctions.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/Numerics/LambertWFunction.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/LambertWCompilationTest.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/CompileMatrixTest.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
diff --git a/Sources/AngouriMath/Core/Entity/Continuous/Number/LambertW.cs b/Sources/AngouriMath/Core/Entity/Continuous/Number/LambertW.cs
index 3e01c4bff..ea0cb488b 100644
--- a/Sources/AngouriMath/Core/Entity/Continuous/Number/LambertW.cs
+++ b/Sources/AngouriMath/Core/Entity/Continuous/Number/LambertW.cs
@@ -29,10 +29,11 @@ partial record Number
/// on [-1/e, 0), and a value on a branch cut is the one approached from above.
///
///
- /// The branch is found in double precision, where it is cheap: from the series in
- /// p = sqrt(2 (e z + 1)) about the branch point -1/e, from ln(1 + z) elsewhere on
- /// the principal branch short of |z| = 3, and from ln z + 2 pi i k - ln(ln z + 2 pi i k) otherwise,
- /// each then taken to a root by Halley's iteration. That root is refined by the same iteration
+ /// The branch is found in double precision, where it is cheap, by the kernel the compilers use,
+ /// : from the series in p = sqrt(2 (e z + 1))
+ /// about the branch point -1/e, from ln(1 + z) elsewhere on the principal branch short of
+ /// |z| = 3, and from ln z + 2 pi i k - ln(ln z + 2 pi i k) otherwise, each then taken to a
+ /// root by Halley's iteration. That root is refined by the same iteration
/// at the working precision, which triples the digits with each step and does not leave the
/// branch. Near the branch point, where double precision cannot resolve z + 1/e, the
/// series is evaluated at the working precision instead, and refined from there.
@@ -78,7 +79,7 @@ partial record Number
}
else
{
- var start = InDoublePrecision(k, new NumericsComplex(re.ToDouble(), im.ToDouble()));
+ var start = AngouriMath.Numerics.LambertWFunction.LambertW(new NumericsComplex(k, 0), new NumericsComplex(re.ToDouble(), im.ToDouble()));
if (double.IsNaN(start.Real) || double.IsNaN(start.Imaginary) || double.IsInfinity(start.Real) || double.IsInfinity(start.Imaginary))
return null;
w = (EDecimal.FromDouble(start.Real), EDecimal.FromDouble(start.Imaginary));
@@ -107,40 +108,6 @@ partial record Number
return Complex.Create(w.Re.RoundToPrecision(context), w.Im.RoundToPrecision(context));
}
- ///
- /// W_k(z) in double precision, from a start on the branch and Halley's iteration: the
- /// branch is chosen here, and the working precision only refines the root.
- ///
- internal static NumericsComplex InDoublePrecision(int k, NumericsComplex z)
- {
- NumericsComplex w;
- var p = NumericsComplex.Sqrt(2 * (Math.E * z + 1));
- // The series about the branch point, within 1/e of it, on the principal branch; ln(1 + z)
- // short of the asymptotic reach, which W_0 is close to there and the asymptotic start is
- // not: from that one, 0.5i went to another branch. Between -1 and -1/e on the axis
- // ln(1 + z) is real where W_0 is not, and only the series reaches the root.
- if (k == 0 && NumericsComplex.Abs(Math.E * z + 1) < 1)
- w = -1 + p - p * p / 3 + 11 * p * p * p / 72;
- else if (k == 0 && NumericsComplex.Abs(z) < 3)
- w = NumericsComplex.Log(1 + z);
- else
- {
- var l1 = NumericsComplex.Log(z) + new NumericsComplex(0, 2 * Math.PI * k);
- var l2 = NumericsComplex.Log(l1);
- w = l1 - l2 + l2 / l1;
- }
- for (var step = 0; step < 64; step++)
- {
- var ew = NumericsComplex.Exp(w);
- var f = w * ew - z;
- var change = f / (ew * (w + 1) - (w + 2) * f / (2 * w + 2));
- w -= change;
- if (NumericsComplex.Abs(change) <= 1e-15 * Math.Max(NumericsComplex.Abs(w), 1))
- break;
- }
- return w;
- }
-
private static (EDecimal Re, EDecimal Im) Exp((EDecimal Re, EDecimal Im) w, EContext work)
{
var modulus = w.Re.Exponential(work);
diff --git a/Sources/AngouriMath/Functions/Compilation/IntoFE/Compiler.cs b/Sources/AngouriMath/Functions/Compilation/IntoFE/Compiler.cs
index 8f9fb3c89..1f036fcc2 100644
--- a/Sources/AngouriMath/Functions/Compilation/IntoFE/Compiler.cs
+++ b/Sources/AngouriMath/Functions/Compilation/IntoFE/Compiler.cs
@@ -604,6 +604,19 @@ private protected override void CompileNode(Compiler compiler)
}
}
+ // The Lambert W function on its branch. https://github.com/asc-community/AngouriMath/issues/2195
+ public partial record LambertWf
+ {
+ private protected override bool CompilesItself => true;
+
+ private protected override void CompileNode(Compiler compiler)
+ {
+ Argument.InnerCompile(compiler);
+ Branch.InnerCompile(compiler);
+ compiler.Instructions.Add(new(InstructionType.CALL_LAMBERTW));
+ }
+ }
+
public partial record Maxf
{
private protected override bool CompilesItself => true;
diff --git a/Sources/AngouriMath/Functions/Compilation/IntoFE/FastExpression.cs b/Sources/AngouriMath/Functions/Compilation/IntoFE/FastExpression.cs
index a0801319e..a2cf38559 100644
--- a/Sources/AngouriMath/Functions/Compilation/IntoFE/FastExpression.cs
+++ b/Sources/AngouriMath/Functions/Compilation/IntoFE/FastExpression.cs
@@ -81,6 +81,7 @@ internal enum InstructionType
CALL_BINOMIAL,
CALL_FALLING_FACTORIAL,
CALL_RISING_FACTORIAL,
+ CALL_LAMBERTW,
// 3-arg: a value where its predicate holds, and the one below it elsewhere.
CALL_SELECT = 150,
@@ -355,6 +356,9 @@ public System.Numerics.Complex Substitute(params System.Numerics.Complex[] value
case InstructionType.CALL_RISING_FACTORIAL:
stack.Push(AngouriMath.Numerics.CombinatorialFunctions.RisingFactorial(stack.Pop(), stack.Pop()));
break;
+ case InstructionType.CALL_LAMBERTW:
+ stack.Push(AngouriMath.Numerics.LambertWFunction.LambertW(stack.Pop(), stack.Pop()));
+ break;
case InstructionType.CALL_DOUBLE_FACTORIAL:
stack.Push(AngouriMath.Numerics.CombinatorialFunctions.DoubleFactorial(stack.Pop()));
break;
diff --git a/Sources/AngouriMath/Functions/Compilation/IntoLinq/CompilationProtocol.cs b/Sources/AngouriMath/Functions/Compilation/IntoLinq/CompilationProtocol.cs
index 328345960..61cea63ea 100644
--- a/Sources/AngouriMath/Functions/Compilation/IntoLinq/CompilationProtocol.cs
+++ b/Sources/AngouriMath/Functions/Compilation/IntoLinq/CompilationProtocol.cs
@@ -242,6 +242,8 @@ FallingFactorialf when ShouldBeAtLeastDouble(left) is var newLeft && ShouldBeAtL
=> Expression.Call(GetDef("FallingFactorial", 2, newLeft.Type), newLeft, newRight),
RisingFactorialf when ShouldBeAtLeastDouble(left) is var newLeft && ShouldBeAtLeastDouble(right) is var newRight
=> Expression.Call(GetDef("RisingFactorial", 2, newLeft.Type), newLeft, newRight),
+ LambertWf when ShouldBeAtLeastDouble(left) is var newLeft && ShouldBeAtLeastDouble(right) is var newRight
+ => Expression.Call(GetDef("LambertW", 2, newLeft.Type), newLeft, newRight),
Maxf => Expression.Call(GetDef("Max", 2, left.Type), left, right),
Minf => Expression.Call(GetDef("Min", 2, left.Type), left, right),
diff --git a/Sources/AngouriMath/Functions/Compilation/IntoLinq/MathAllMethods.cs b/Sources/AngouriMath/Functions/Compilation/IntoLinq/MathAllMethods.cs
index fc03d498b..ef23af0b7 100644
--- a/Sources/AngouriMath/Functions/Compilation/IntoLinq/MathAllMethods.cs
+++ b/Sources/AngouriMath/Functions/Compilation/IntoLinq/MathAllMethods.cs
@@ -342,6 +342,11 @@ public static System.Numerics.Complex Round(System.Numerics.Complex a)
public static System.Numerics.Complex RisingFactorial(System.Numerics.Complex a, System.Numerics.Complex b) => AngouriMath.Numerics.CombinatorialFunctions.RisingFactorial(a, b);
public static double RisingFactorial(double a, double b) => AngouriMath.Numerics.CombinatorialFunctions.RisingFactorial(a, b);
+ // The Lambert W function on the branch the first argument names.
+ // https://github.com/asc-community/AngouriMath/issues/2195
+ public static System.Numerics.Complex LambertW(System.Numerics.Complex a, System.Numerics.Complex b) => AngouriMath.Numerics.LambertWFunction.LambertW(a, b);
+ public static double LambertW(double a, double b) => AngouriMath.Numerics.LambertWFunction.LambertW(a, b);
+
///
/// True where System.Numerics.Complex.IsNaN is, spelled out because that overload is
/// .NET 7 and later and this assembly also targets netstandard2.0. A complex number is NaN
@@ -534,6 +539,8 @@ public static bool IsNaN(System.Numerics.Complex a)
{ ("FallingFactorial", 2, typeof(double)), Def((a, b) => FallingFactorial(a, b)) },
{ ("RisingFactorial", 2, typeof(System.Numerics.Complex)), Def((a, b) => RisingFactorial(a, b)) },
{ ("RisingFactorial", 2, typeof(double)), Def((a, b) => RisingFactorial(a, b)) },
+ { ("LambertW", 2, typeof(System.Numerics.Complex)), Def((a, b) => LambertW(a, b)) },
+ { ("LambertW", 2, typeof(double)), Def((a, b) => LambertW(a, b)) },
};
// An expression tree is how C# spells "the MethodInfo of this method" without a string:
diff --git a/Sources/AngouriMath/Functions/Compilation/IntoLinq/MathAllMethods.tt b/Sources/AngouriMath/Functions/Compilation/IntoLinq/MathAllMethods.tt
index b39e74b0a..4443f45db 100644
--- a/Sources/AngouriMath/Functions/Compilation/IntoLinq/MathAllMethods.tt
+++ b/Sources/AngouriMath/Functions/Compilation/IntoLinq/MathAllMethods.tt
@@ -232,6 +232,11 @@ namespace AngouriMath.Core.Compilation.IntoLinq
public static double <#= name #>(double a, double b) => AngouriMath.Numerics.CombinatorialFunctions.<#= name #>(a, b);
<# } #>
+ // The Lambert W function on the branch the first argument names.
+ // https://github.com/asc-community/AngouriMath/issues/2195
+ public static System.Numerics.Complex LambertW(System.Numerics.Complex a, System.Numerics.Complex b) => AngouriMath.Numerics.LambertWFunction.LambertW(a, b);
+ public static double LambertW(double a, double b) => AngouriMath.Numerics.LambertWFunction.LambertW(a, b);
+
///
/// True where System.Numerics.Complex.IsNaN is, spelled out because that overload is
/// .NET 7 and later and this assembly also targets netstandard2.0. A complex number is NaN
@@ -285,6 +290,9 @@ namespace AngouriMath.Core.Compilation.IntoLinq
<# foreach (var type in new[]{ "System.Numerics.Complex", "double" }) { #>
{ ("<#= name #>", 2, typeof(<#= type #>)), Def<<#= type #>>((a, b) => <#= name #>(a, b)) },
<# } #>
+<# } #>
+<# foreach (var type in new[]{ "System.Numerics.Complex", "double" }) { #>
+ { ("LambertW", 2, typeof(<#= type #>)), Def<<#= type #>>((a, b) => LambertW(a, b)) },
<# } #>
};
diff --git a/Sources/AngouriMath/Numerics/LambertWFunction.cs b/Sources/AngouriMath/Numerics/LambertWFunction.cs
new file mode 100644
index 000000000..119a08036
--- /dev/null
+++ b/Sources/AngouriMath/Numerics/LambertWFunction.cs
@@ -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 NumericsComplex = System.Numerics.Complex;
+
+namespace AngouriMath.Numerics
+{
+ ///
+ /// The Lambert W function in double precision, for the compilers, on the branches the interpreter
+ /// takes: those of Corless et al. (1996), W_0 real on [-1/e, +oo) and W_(-1) on
+ /// [-1/e, 0), NaN on a branch that is not whole, and -oo at 0 off the principal one.
+ /// https://github.com/asc-community/AngouriMath/issues/2195
+ ///
+ internal static class LambertWFunction
+ {
+ [AngouriMath.Core.ConstantField] private static readonly NumericsComplex NaN = new(double.NaN, 0);
+
+ public static NumericsComplex LambertW(NumericsComplex branch, NumericsComplex z)
+ {
+ if (branch.Imaginary != 0 || double.IsNaN(branch.Real) || double.IsInfinity(branch.Real)
+ || Math.Floor(branch.Real) != branch.Real || Math.Abs(branch.Real) > int.MaxValue)
+ return NaN;
+ if (double.IsNaN(z.Real) || double.IsNaN(z.Imaginary) || double.IsInfinity(z.Real) || double.IsInfinity(z.Imaginary))
+ return NaN;
+ var k = (int)branch.Real;
+ if (z == 0)
+ return k == 0 ? 0 : double.NegativeInfinity;
+ // Without a signed zero, which a computation can leave behind: z on the axis is approached from above.
+ if (z.Imaginary == 0)
+ z = new NumericsComplex(z.Real, 0);
+
+ var nearTheBranchPoint = Math.E * z + 1;
+ // W_0 from either side, W_(-1) from above and on the axis, W_1 from below meet at -1/e.
+ var meetsTheBranchPoint = k == 0 || k == -1 && z.Imaginary >= 0 || k == 1 && z.Imaginary < 0;
+ // -1/e is not a double, and within its rounding the value is the branch point's.
+ if (meetsTheBranchPoint && NumericsComplex.Abs(nearTheBranchPoint) < 1e-15)
+ return -1;
+ var isReal = z.Imaginary == 0 && nearTheBranchPoint.Real >= 0 && (k == 0 || k == -1 && z.Real < 0);
+
+ // The start, by the regions SciPy's lambertw takes on the principal branch: the series about the
+ // branch point within 0.3 of it (and within a quarter of it on the other branches that meet
+ // there), ln(1 + z) in a box right of -1 about the axis, and the asymptotic start elsewhere. ln(1 + z)
+ // is no start near -1, where it is ln 0 and its iteration reaches another branch, nor on the axis
+ // from -1 to -1/e, where it is real and W_0 is not.
+ NumericsComplex w;
+ var branchPointSeries = k == 0 ? NumericsComplex.Abs(z + 1 / Math.E) < 0.3 : meetsTheBranchPoint && NumericsComplex.Abs(nearTheBranchPoint) < 0.25;
+ if (branchPointSeries)
+ {
+ // p = sqrt(2 (e z + 1)), and w = -1 + p - p^2/3 + 11 p^3/72 on W_0, -p on the other branch.
+ var p = NumericsComplex.Sqrt(2 * nearTheBranchPoint);
+ if (k != 0)
+ p = -p;
+ w = -1 + p - p * p / 3 + 11 * p * p * p / 72;
+ }
+ else if (k == 0 && z.Real > -1 && z.Real < 1.5 && Math.Abs(z.Imaginary) < 1 && z.Real > -2.5 * Math.Abs(z.Imaginary) - 0.2)
+ w = NumericsComplex.Log(1 + z);
+ else
+ {
+ var l1 = NumericsComplex.Log(z) + new NumericsComplex(0, 2 * Math.PI * k);
+ var l2 = NumericsComplex.Log(l1);
+ w = l1 - l2 + l2 / l1;
+ }
+ if (isReal)
+ w = new NumericsComplex(w.Real, 0);
+ for (var step = 0; step < 64; step++)
+ {
+ // Halley: w - f / (e^w (w + 1) - (w + 2) f / (2 w + 2)), with f = w e^w - z.
+ var ew = NumericsComplex.Exp(w);
+ var f = w * ew - z;
+ if (w + 1 == 0)
+ break;
+ var change = f / (ew * (w + 1) - (w + 2) * f / (2 * w + 2));
+ w -= change;
+ if (isReal)
+ w = new NumericsComplex(w.Real, 0);
+ if (NumericsComplex.Abs(change) <= 1e-15 * Math.Max(NumericsComplex.Abs(w), 1))
+ break;
+ }
+ return w;
+ }
+
+ // A real value where the branch is real there, and NaN where it is not.
+ public static double LambertW(double branch, double z)
+ {
+ var w = LambertW(new NumericsComplex(branch, 0), new NumericsComplex(z, 0));
+ return w.Imaginary == 0 ? w.Real : double.NaN;
+ }
+ }
+}
diff --git a/Sources/Tests/AotSmokeTest/Program.cs b/Sources/Tests/AotSmokeTest/Program.cs
index f856b05f4..23563ef3f 100644
--- a/Sources/Tests/AotSmokeTest/Program.cs
+++ b/Sources/Tests/AotSmokeTest/Program.cs
@@ -309,6 +309,8 @@ private static void Compilation()
var linqBinomial = "binomial(x, 2) + fallingfactorial(x, 3) + risingfactorial(x, 2) + x!!".Compile(x);
Check("compiled binomial(x, 2) + x(x-1)(x-2) + x(x+1) + x!! at x = 5", fastBinomial.Call(5) == 10 + 60 + 30 + 15 && linqBinomial(5) == 10 + 60 + 30 + 15,
$"{fastBinomial.Call(5)}, {linqBinomial(5)}");
+ var compiledW = "lambertw(-1, x)".Compile(x);
+ CheckClose("compiled lambertw(-1, x) at x = -0.3", compiledW(-0.3), -1.7813370234216276);
// FastExpression: a statement is its truth value, and a piecewise the case that holds.
var fastPiecewise = "piecewise(x provided x > 0, -x)".Compile(x);
var fastStatement = "x > 1 or x < 0".Compile(x);
diff --git a/Sources/Tests/UnitTests/Common/LambertWCompilationTest.cs b/Sources/Tests/UnitTests/Common/LambertWCompilationTest.cs
new file mode 100644
index 000000000..47dd6ed0b
--- /dev/null
+++ b/Sources/Tests/UnitTests/Common/LambertWCompilationTest.cs
@@ -0,0 +1,71 @@
+//
+// 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.Numerics;
+using AngouriMath;
+using AngouriMath.Extensions;
+using Xunit;
+
+namespace AngouriMath.Tests.Common
+{
+ ///
+ /// The Lambert W function compiles, in FastExpression and in the LINQ compiler, to what evaluation
+ /// gives on each branch: near the branch point -1/e, on either side of it, on the negative
+ /// axis where the branches are complex, and off the axis. It was refused.
+ /// #2195
+ ///
+ [Trait("Area", "Common")]
+ public sealed class LambertWCompilationTest
+ {
+ private static readonly string[] branches = { "-2", "-1", "0", "1", "3", "1/2" };
+
+ private static readonly string[] arguments =
+ {
+ "0", "1", "2", "10", "100000", "-1/10", "-3/10", "-9/25", "-1/e", "-37/100", "-1/2", "-2",
+ "i", "1 + i", "-1 - i", "i/2", "-3 + i/10", "-3/10 - i/100", "-3/10 + i/100",
+ };
+
+ private static Complex Numeric(string value) => ((Entity.Number.Complex)value.ToEntity().Evaled).ToNumerics();
+
+ // Both NaN, both the same infinity, or within a part in a billion.
+ private static bool Agree(Complex expected, Complex actual)
+ => double.IsNaN(expected.Real) || double.IsNaN(expected.Imaginary)
+ ? double.IsNaN(actual.Real) || double.IsNaN(actual.Imaginary)
+ : double.IsInfinity(expected.Real)
+ ? expected == actual
+ : Complex.Abs(expected - actual) <= 1e-9 * System.Math.Max(1, Complex.Abs(expected));
+
+ [Fact]
+ public void ItIsWhatEvaluationSaysOnEachBranch()
+ {
+ var fast = "lambertw(k, z)".ToEntity().Compile("k", "z");
+ var linq = "lambertw(k, z)".ToEntity().Compile("k", "z");
+ var real = "lambertw(k, z)".ToEntity().Compile("k", "z");
+ foreach (var branch in branches)
+ foreach (var argument in arguments)
+ {
+ var evaluated = Assert.IsAssignableFrom($"lambertw({branch}, {argument})".ToEntity().Evaled);
+ var expected = evaluated.ToNumerics();
+ var (k, z) = (Numeric(branch), Numeric(argument));
+ Assert.True(Agree(expected, fast.Call(k, z)), $"lambertw({branch}, {argument}): {expected} evaluated, {fast.Call(k, z)} by FastExpression");
+ Assert.True(Agree(expected, linq(k, z)), $"lambertw({branch}, {argument}): {expected} evaluated, {linq(k, z)} by LINQ");
+ if (z.Imaginary == 0)
+ {
+ // Real where the imaginary part is rounding's: evaluation rounds -1/e to a decimal
+ // that is across the branch point by a digit, and leaves an imaginary part of that size.
+ var overTheReals = System.Math.Abs(expected.Imaginary) <= 1e-12 * System.Math.Max(1, Complex.Abs(expected))
+ ? new Complex(expected.Real, 0) : new Complex(double.NaN, 0);
+ Assert.True(Agree(overTheReals, real(k.Real, z.Real)), $"lambertw({branch}, {argument}): {overTheReals} over the reals, {real(k.Real, z.Real)} over double");
+ }
+ }
+ }
+
+ [Fact]
+ public void ThePrincipalBranchCompilesInOneArgument()
+ => Assert.Equal(0.5671432904097838, "lambertw(x)".ToEntity().Compile("x")(1), 15);
+ }
+}
diff --git a/Sources/Tests/UnitTests/Convenience/LambertWTest.cs b/Sources/Tests/UnitTests/Convenience/LambertWTest.cs
index cd4c2acd4..6efa26053 100644
--- a/Sources/Tests/UnitTests/Convenience/LambertWTest.cs
+++ b/Sources/Tests/UnitTests/Convenience/LambertWTest.cs
@@ -24,7 +24,8 @@ public sealed class LambertWTest
///
/// mpmath's lambertw at 60 digits, at the decimals written: on the real axis where each branch is real, across the cut
/// below -1/e where W_0 and W_(-1) are conjugates, about the branch point from above and below,
- /// and far out.
+ /// and far out. W_0 on the axis from -1 to -2/e, about -1, and just left of -1 was real, or on
+ /// another branch: its start was ln(1 + z), which is real there and is ln 0 at -1.
///
public static readonly TheoryData Points = new()
{
@@ -50,6 +51,11 @@ public sealed class LambertWTest
{ 0, "0.5", "0.5", "0.40431612353121278901924224143642299", "0.24343775688425395570266928474288716" },
{ 0, "-0.2", "0.3", "-0.079775717127478451154646095753164503", "0.38226147065890346680498485830193446" },
{ 0, "2", "-1.5", "0.93210073009396256291394339557696040", "-0.31632723076474133978590653951403091" },
+ { 0, "-0.8", "0", "-0.47296437953376993647110619289081", "1.1934970731363015053999934286237" },
+ { 0, "-0.975", "0", "-0.33577732103000080461145009761745", "1.3220782205206208727604624558714" },
+ { 0, "-1", "0", "-0.31813150520476413531265425158766", "1.3372357014306894089011621431937" },
+ { 0, "-1.02", "0", "-0.30431558075585315366696810619974", "1.3489097881249077058064917902432" },
+ { 0, "-1.349", "-0.358", "0.039601766331215229042972440561754", "-1.3409186442644250944627990020702" },
{ -1, "-0.1", "0", "-3.5771520639572972184093919635119949", "0.0" },
{ -1, "-0.3", "0", "-1.7813370234216276119741702815127453", "0.0" },
{ -1, "-0.36", "0", "-1.2227701339785059531429380734238623", "0.0" },