diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 148ef6384..6cd9da22e 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -445,6 +445,23 @@ then had no truth value for. A product or a quotient of two real numbers is now | `"-x".ToEntity().Compile("x").Call(double.PositiveInfinity)` | `(-Infinity, NaN)` | `(-Infinity, 0)` | | `"x / 2".ToEntity().Compile("x")(double.PositiveInfinity)` | `(Infinity, NaN)` | `(Infinity, 0)` | +### A compiled function takes the side of its branch cut evaluation takes + +**Silent: a compiled logarithm, root or inverse function of a number on its cut is evaluation's value, where it was +the conjugate.** `System.Numerics.Complex` computes `-0` as a part of many real values, `cos(2)` being `(-0.416, -0)`, +and reads it as the lower side of a cut. The library has no signed zero, so a real number is on the upper side. Every +compiled function with a cut now reads a zero part as `+0`: the logarithm, a power that is not whole, the inverse +trigonometric functions, `arg`, the exponential and logarithmic integrals and `lambertw`, in FastExpression and in the +LINQ compiler. A `conj` of a real number, new to compile since 2.6.0, took the same wrong side, as `ln(conj(x))` at +`-2` did ([#2236](https://github.com/asc-community/AngouriMath/issues/2236)). FastExpression's logarithm also kept only +the real part of a complex base, and takes `ln(value) / ln(base)` now +([#2239](https://github.com/asc-community/AngouriMath/issues/2239)). + +| Input | Was (2.6.0) | Now | +|---|---|---| +| `"ln(cos(x))".ToEntity().Compile("x").Call(2)` | `(-0.8767171085319084, -3.141592653589793)` | `(-0.8767171085319084, 3.141592653589793)` | +| `"log(x, 2)".ToEntity().Compile("x").Call(new Complex(1, 1))` | `(NaN, NaN)` | `(0.32596797216345985, -0.7387021222729507)` | + ### A compiled power of real numbers is the real power, at an infinity and at zero **Silent: a compiled power over `Complex` is what evaluation gives where it had no value or the wrong one.** In diff --git a/Sources/.editorconfig b/Sources/.editorconfig index eae3ab026..88fd02ade 100644 --- a/Sources/.editorconfig +++ b/Sources/.editorconfig @@ -931,3 +931,6 @@ file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed [Tests/UnitTests/Common/RealPowerCompilationTest.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/BranchCutCompilationTest.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/Functions/Compilation/ComplexBranches.cs b/Sources/AngouriMath/Functions/Compilation/ComplexBranches.cs index 1f0783342..6b7f6e5c0 100644 --- a/Sources/AngouriMath/Functions/Compilation/ComplexBranches.cs +++ b/Sources/AngouriMath/Functions/Compilation/ComplexBranches.cs @@ -39,10 +39,36 @@ internal static class ComplexBranches /// see https://github.com/asc-community/AngouriMath/issues/115. /// public static NumericsComplex Arcsin(NumericsComplex a) - => a.Imaginary == 0 && Math.Abs(a.Real) > 1 + => (a = Unsigned(a)).Imaginary == 0 && Math.Abs(a.Real) > 1 ? NumericsComplex.Conjugate(NumericsComplex.Asin(a)) : NumericsComplex.Asin(a); + /// + /// with each zero part read as +0. The library has no signed zero, so a real + /// number is on the upper side of every branch cut it lies on; + /// computes -0 as a part of many real values, cos(2) and conj(-2) among them, and reads + /// it as the lower side, which made ln(cos(x)) at 2 the conjugate of the logarithm. Adding +0 + /// turns -0 into +0 and changes no other number. Taken at the entry of every function with a cut. + /// https://github.com/asc-community/AngouriMath/issues/2236 + /// + public static NumericsComplex Unsigned(NumericsComplex z) => new(z.Real + 0.0, z.Imaginary + 0.0); + + /// + /// The logarithm of to , ln(value) / ln(base) for any + /// base, where FastExpression's Complex.Log(value, base.Real) dropped a complex base's imaginary part. + /// https://github.com/asc-community/AngouriMath/issues/2239 + /// + public static NumericsComplex Log(NumericsComplex @base, NumericsComplex value) + => RealProducts.Quotient(NumericsComplex.Log(Unsigned(value)), NumericsComplex.Log(Unsigned(@base))); + + public static NumericsComplex Arccos(NumericsComplex a) => NumericsComplex.Acos(Unsigned(a)); + + public static NumericsComplex Arctan(NumericsComplex a) => NumericsComplex.Atan(Unsigned(a)); + + public static NumericsComplex Arccotan(NumericsComplex a) => NumericsComplex.Atan(Unsigned(1 / a)); + + public static NumericsComplex Arcsecant(NumericsComplex a) => NumericsComplex.Acos(Unsigned(1 / a)); + /// /// The arccosecant the library evaluates to, which is of the /// reciprocal and so lands on the same cuts -- for arccosecant, at the real arguments diff --git a/Sources/AngouriMath/Functions/Compilation/IntoFE/FastExpression.cs b/Sources/AngouriMath/Functions/Compilation/IntoFE/FastExpression.cs index 878d96c78..2f5fb9f93 100644 --- a/Sources/AngouriMath/Functions/Compilation/IntoFE/FastExpression.cs +++ b/Sources/AngouriMath/Functions/Compilation/IntoFE/FastExpression.cs @@ -250,22 +250,25 @@ public System.Numerics.Complex Substitute(params System.Numerics.Complex[] value stack.Push(1 / System.Numerics.Complex.Tan(stack.Pop())); break; case InstructionType.CALL_LOG: - stack.Push(System.Numerics.Complex.Log(stack.Pop(), stack.Pop().Real)); + { + var value = stack.Pop(); + stack.Push(Core.Compilation.ComplexBranches.Log(stack.Pop(), value)); + } break; case InstructionType.CALL_ARCSIN: stack.Push(Core.Compilation.ComplexBranches.Arcsin(stack.Pop())); break; case InstructionType.CALL_ARCCOS: - stack.Push(System.Numerics.Complex.Acos(stack.Pop())); + stack.Push(Core.Compilation.ComplexBranches.Arccos(stack.Pop())); break; case InstructionType.CALL_ARCTAN: - stack.Push(System.Numerics.Complex.Atan(stack.Pop())); + stack.Push(Core.Compilation.ComplexBranches.Arctan(stack.Pop())); break; case InstructionType.CALL_ARCCOTAN: - stack.Push(System.Numerics.Complex.Atan(1 / stack.Pop())); + stack.Push(Core.Compilation.ComplexBranches.Arccotan(stack.Pop())); break; case InstructionType.CALL_ARCSECANT: - stack.Push(System.Numerics.Complex.Acos(1 / stack.Pop())); + stack.Push(Core.Compilation.ComplexBranches.Arcsecant(stack.Pop())); break; case InstructionType.CALL_ARCCOSECANT: stack.Push(Core.Compilation.ComplexBranches.Arccosecant(stack.Pop())); @@ -282,22 +285,22 @@ public System.Numerics.Complex Substitute(params System.Numerics.Complex[] value stack.Push(AngouriMath.Numerics.SpecialFunctions.Erfi(stack.Pop())); break; case InstructionType.CALL_EI: - stack.Push(AngouriMath.Numerics.SpecialFunctions.Ei(stack.Pop())); + stack.Push(AngouriMath.Numerics.SpecialFunctions.Ei(Core.Compilation.ComplexBranches.Unsigned(stack.Pop()))); break; case InstructionType.CALL_LI: - stack.Push(AngouriMath.Numerics.SpecialFunctions.Li(stack.Pop())); + stack.Push(AngouriMath.Numerics.SpecialFunctions.Li(Core.Compilation.ComplexBranches.Unsigned(stack.Pop()))); break; case InstructionType.CALL_SI: stack.Push(AngouriMath.Numerics.SpecialFunctions.Si(stack.Pop())); break; case InstructionType.CALL_CI: - stack.Push(AngouriMath.Numerics.SpecialFunctions.Ci(stack.Pop())); + stack.Push(AngouriMath.Numerics.SpecialFunctions.Ci(Core.Compilation.ComplexBranches.Unsigned(stack.Pop()))); break; case InstructionType.CALL_SHI: stack.Push(AngouriMath.Numerics.SpecialFunctions.Shi(stack.Pop())); break; case InstructionType.CALL_CHI: - stack.Push(AngouriMath.Numerics.SpecialFunctions.Chi(stack.Pop())); + stack.Push(AngouriMath.Numerics.SpecialFunctions.Chi(Core.Compilation.ComplexBranches.Unsigned(stack.Pop()))); break; case InstructionType.CALL_FACTORIAL: stack.Push(AngouriMath.Numerics.SpecialFunctions.Factorial(stack.Pop())); @@ -315,7 +318,7 @@ public System.Numerics.Complex Substitute(params System.Numerics.Complex[] value stack.Push(AngouriMath.Numerics.ComplexParts.Conj(stack.Pop())); break; case InstructionType.CALL_ARG: - stack.Push(AngouriMath.Numerics.ComplexParts.Arg(stack.Pop())); + stack.Push(AngouriMath.Numerics.ComplexParts.Arg(Core.Compilation.ComplexBranches.Unsigned(stack.Pop()))); break; case InstructionType.CALL_RE: stack.Push(AngouriMath.Numerics.ComplexParts.Re(stack.Pop())); @@ -374,7 +377,7 @@ public System.Numerics.Complex Substitute(params System.Numerics.Complex[] value 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())); + stack.Push(AngouriMath.Numerics.LambertWFunction.LambertW(stack.Pop(), Core.Compilation.ComplexBranches.Unsigned(stack.Pop()))); break; case InstructionType.CALL_POLYGAMMA: stack.Push(AngouriMath.Numerics.PolygammaFunction.Polygamma(stack.Pop(), stack.Pop())); diff --git a/Sources/AngouriMath/Functions/Compilation/IntoLinq/MathAllMethods.cs b/Sources/AngouriMath/Functions/Compilation/IntoLinq/MathAllMethods.cs index 18f2c70e5..2f81f9eba 100644 --- a/Sources/AngouriMath/Functions/Compilation/IntoLinq/MathAllMethods.cs +++ b/Sources/AngouriMath/Functions/Compilation/IntoLinq/MathAllMethods.cs @@ -23,7 +23,7 @@ internal static class MathAllMethods /* POWERS */ public static System.Numerics.Complex Log(System.Numerics.Complex a, System.Numerics.Complex b) - => System.Numerics.Complex.Log(b) / System.Numerics.Complex.Log(a); + => AngouriMath.Core.Compilation.ComplexBranches.Log(a, b); public static double Log(double a, double b) => Math.Log(b, a); public static float Log(float a, float b) @@ -109,7 +109,7 @@ public static BigInteger Asin(BigInteger a) public static System.Numerics.Complex Acos(System.Numerics.Complex a) - => System.Numerics.Complex.Acos(a); + => AngouriMath.Core.Compilation.ComplexBranches.Arccos(a); public static double Acos(double a) => Math.Acos(a); public static float Acos(float a) @@ -123,7 +123,7 @@ public static BigInteger Acos(BigInteger a) public static System.Numerics.Complex Atan(System.Numerics.Complex a) - => System.Numerics.Complex.Atan(a); + => AngouriMath.Core.Compilation.ComplexBranches.Arctan(a); public static double Atan(double a) => Math.Atan(a); public static float Atan(float a) @@ -185,7 +185,7 @@ public static BigInteger Csc(BigInteger a) public static System.Numerics.Complex Acot(System.Numerics.Complex a) - => System.Numerics.Complex.Atan(1 / a); + => AngouriMath.Core.Compilation.ComplexBranches.Arccotan(a); public static double Acot(double a) => Math.Atan(1 / a); public static float Acot(float a) @@ -199,7 +199,7 @@ public static BigInteger Acot(BigInteger a) public static System.Numerics.Complex Asec(System.Numerics.Complex a) - => System.Numerics.Complex.Acos(1 / a); + => AngouriMath.Core.Compilation.ComplexBranches.Arcsecant(a); public static double Asec(double a) => Math.Acos(1 / a); public static float Asec(float a) @@ -299,36 +299,36 @@ public static System.Numerics.Complex Round(System.Numerics.Complex a) // The special functions, in double precision as the kernels define them, and the factorial, // the gamma function one along. https://github.com/asc-community/AngouriMath/issues/1607 - public static System.Numerics.Complex Erf(System.Numerics.Complex a) => AngouriMath.Numerics.SpecialFunctions.Erf(a); + public static System.Numerics.Complex Erf(System.Numerics.Complex a) => AngouriMath.Numerics.SpecialFunctions.Erf(AngouriMath.Core.Compilation.ComplexBranches.Unsigned(a)); public static double Erf(double a) => AngouriMath.Numerics.SpecialFunctions.Erf(a); - public static System.Numerics.Complex Erfc(System.Numerics.Complex a) => AngouriMath.Numerics.SpecialFunctions.Erfc(a); + public static System.Numerics.Complex Erfc(System.Numerics.Complex a) => AngouriMath.Numerics.SpecialFunctions.Erfc(AngouriMath.Core.Compilation.ComplexBranches.Unsigned(a)); public static double Erfc(double a) => AngouriMath.Numerics.SpecialFunctions.Erfc(a); - public static System.Numerics.Complex Erfi(System.Numerics.Complex a) => AngouriMath.Numerics.SpecialFunctions.Erfi(a); + public static System.Numerics.Complex Erfi(System.Numerics.Complex a) => AngouriMath.Numerics.SpecialFunctions.Erfi(AngouriMath.Core.Compilation.ComplexBranches.Unsigned(a)); public static double Erfi(double a) => AngouriMath.Numerics.SpecialFunctions.Erfi(a); - public static System.Numerics.Complex Ei(System.Numerics.Complex a) => AngouriMath.Numerics.SpecialFunctions.Ei(a); + public static System.Numerics.Complex Ei(System.Numerics.Complex a) => AngouriMath.Numerics.SpecialFunctions.Ei(AngouriMath.Core.Compilation.ComplexBranches.Unsigned(a)); public static double Ei(double a) => AngouriMath.Numerics.SpecialFunctions.Ei(a); - public static System.Numerics.Complex Li(System.Numerics.Complex a) => AngouriMath.Numerics.SpecialFunctions.Li(a); + public static System.Numerics.Complex Li(System.Numerics.Complex a) => AngouriMath.Numerics.SpecialFunctions.Li(AngouriMath.Core.Compilation.ComplexBranches.Unsigned(a)); public static double Li(double a) => AngouriMath.Numerics.SpecialFunctions.Li(a); - public static System.Numerics.Complex Si(System.Numerics.Complex a) => AngouriMath.Numerics.SpecialFunctions.Si(a); + public static System.Numerics.Complex Si(System.Numerics.Complex a) => AngouriMath.Numerics.SpecialFunctions.Si(AngouriMath.Core.Compilation.ComplexBranches.Unsigned(a)); public static double Si(double a) => AngouriMath.Numerics.SpecialFunctions.Si(a); - public static System.Numerics.Complex Ci(System.Numerics.Complex a) => AngouriMath.Numerics.SpecialFunctions.Ci(a); + public static System.Numerics.Complex Ci(System.Numerics.Complex a) => AngouriMath.Numerics.SpecialFunctions.Ci(AngouriMath.Core.Compilation.ComplexBranches.Unsigned(a)); public static double Ci(double a) => AngouriMath.Numerics.SpecialFunctions.Ci(a); - public static System.Numerics.Complex Shi(System.Numerics.Complex a) => AngouriMath.Numerics.SpecialFunctions.Shi(a); + public static System.Numerics.Complex Shi(System.Numerics.Complex a) => AngouriMath.Numerics.SpecialFunctions.Shi(AngouriMath.Core.Compilation.ComplexBranches.Unsigned(a)); public static double Shi(double a) => AngouriMath.Numerics.SpecialFunctions.Shi(a); - public static System.Numerics.Complex Chi(System.Numerics.Complex a) => AngouriMath.Numerics.SpecialFunctions.Chi(a); + public static System.Numerics.Complex Chi(System.Numerics.Complex a) => AngouriMath.Numerics.SpecialFunctions.Chi(AngouriMath.Core.Compilation.ComplexBranches.Unsigned(a)); public static double Chi(double a) => AngouriMath.Numerics.SpecialFunctions.Chi(a); - public static System.Numerics.Complex Factorial(System.Numerics.Complex a) => AngouriMath.Numerics.SpecialFunctions.Factorial(a); + public static System.Numerics.Complex Factorial(System.Numerics.Complex a) => AngouriMath.Numerics.SpecialFunctions.Factorial(AngouriMath.Core.Compilation.ComplexBranches.Unsigned(a)); public static double Factorial(double a) => AngouriMath.Numerics.SpecialFunctions.Factorial(a); // The conjugate, the principal argument and the real and the imaginary parts. // https://github.com/asc-community/AngouriMath/issues/2195 - public static System.Numerics.Complex Conj(System.Numerics.Complex a) => AngouriMath.Numerics.ComplexParts.Conj(a); + public static System.Numerics.Complex Conj(System.Numerics.Complex a) => AngouriMath.Numerics.ComplexParts.Conj(AngouriMath.Core.Compilation.ComplexBranches.Unsigned(a)); public static double Conj(double a) => AngouriMath.Numerics.ComplexParts.Conj(a); - public static System.Numerics.Complex Arg(System.Numerics.Complex a) => AngouriMath.Numerics.ComplexParts.Arg(a); + public static System.Numerics.Complex Arg(System.Numerics.Complex a) => AngouriMath.Numerics.ComplexParts.Arg(AngouriMath.Core.Compilation.ComplexBranches.Unsigned(a)); public static double Arg(double a) => AngouriMath.Numerics.ComplexParts.Arg(a); - public static System.Numerics.Complex Re(System.Numerics.Complex a) => AngouriMath.Numerics.ComplexParts.Re(a); + public static System.Numerics.Complex Re(System.Numerics.Complex a) => AngouriMath.Numerics.ComplexParts.Re(AngouriMath.Core.Compilation.ComplexBranches.Unsigned(a)); public static double Re(double a) => AngouriMath.Numerics.ComplexParts.Re(a); - public static System.Numerics.Complex Im(System.Numerics.Complex a) => AngouriMath.Numerics.ComplexParts.Im(a); + public static System.Numerics.Complex Im(System.Numerics.Complex a) => AngouriMath.Numerics.ComplexParts.Im(AngouriMath.Core.Compilation.ComplexBranches.Unsigned(a)); public static double Im(double a) => AngouriMath.Numerics.ComplexParts.Im(a); // The binomial coefficient, the factorial powers and the double factorial, on the interpreter's @@ -346,7 +346,7 @@ public static System.Numerics.Complex Round(System.Numerics.Complex a) // 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 System.Numerics.Complex LambertW(System.Numerics.Complex a, System.Numerics.Complex b) => AngouriMath.Numerics.LambertWFunction.LambertW(a, AngouriMath.Core.Compilation.ComplexBranches.Unsigned(b)); public static double LambertW(double a, double b) => AngouriMath.Numerics.LambertWFunction.LambertW(a, b); // The polygamma functions of a whole order, and the harmonic numbers. diff --git a/Sources/AngouriMath/Functions/Compilation/IntoLinq/MathAllMethods.tt b/Sources/AngouriMath/Functions/Compilation/IntoLinq/MathAllMethods.tt index de6c9d098..efd34c6a2 100644 --- a/Sources/AngouriMath/Functions/Compilation/IntoLinq/MathAllMethods.tt +++ b/Sources/AngouriMath/Functions/Compilation/IntoLinq/MathAllMethods.tt @@ -48,7 +48,7 @@ namespace AngouriMath.Core.Compilation.IntoLinq /* POWERS */ public static System.Numerics.Complex Log(System.Numerics.Complex a, System.Numerics.Complex b) - => System.Numerics.Complex.Log(b) / System.Numerics.Complex.Log(a); + => AngouriMath.Core.Compilation.ComplexBranches.Log(a, b); public static double Log(double a, double b) => Math.Log(b, a); public static float Log(float a, float b) @@ -81,6 +81,10 @@ namespace AngouriMath.Core.Compilation.IntoLinq public static System.Numerics.Complex <#= name #>(System.Numerics.Complex a) <# if (name == "Asin") { #> => AngouriMath.Core.Compilation.ComplexBranches.Arcsin(a); +<# } else if (name == "Acos") { #> + => AngouriMath.Core.Compilation.ComplexBranches.Arccos(a); +<# } else if (name == "Atan") { #> + => AngouriMath.Core.Compilation.ComplexBranches.Arctan(a); <# } else { #> => System.Numerics.Complex.<#= name #>(a); <# } #> @@ -123,8 +127,10 @@ namespace AngouriMath.Core.Compilation.IntoLinq public static System.Numerics.Complex <#= name.Item1 #>(System.Numerics.Complex a) <# if (name.Item1 == "Acsc") { #> => AngouriMath.Core.Compilation.ComplexBranches.Arccosecant(a); +<# } else if (name.Item1 == "Acot") { #> + => AngouriMath.Core.Compilation.ComplexBranches.Arccotan(a); <# } else { #> - => System.Numerics.Complex.<#= name.Item2 #>(1 / a); + => AngouriMath.Core.Compilation.ComplexBranches.Arcsecant(a); <# } #> public static double <#= name.Item1 #>(double a) => Math.<#= name.Item2 #>(1 / a); @@ -213,14 +219,14 @@ namespace AngouriMath.Core.Compilation.IntoLinq // The special functions, in double precision as the kernels define them, and the factorial, // the gamma function one along. https://github.com/asc-community/AngouriMath/issues/1607 <# foreach (var name in special) { #> - public static System.Numerics.Complex <#= name #>(System.Numerics.Complex a) => AngouriMath.Numerics.SpecialFunctions.<#= name #>(a); + public static System.Numerics.Complex <#= name #>(System.Numerics.Complex a) => AngouriMath.Numerics.SpecialFunctions.<#= name #>(AngouriMath.Core.Compilation.ComplexBranches.Unsigned(a)); public static double <#= name #>(double a) => AngouriMath.Numerics.SpecialFunctions.<#= name #>(a); <# } #> // The conjugate, the principal argument and the real and the imaginary parts. // https://github.com/asc-community/AngouriMath/issues/2195 <# foreach (var name in parts) { #> - public static System.Numerics.Complex <#= name #>(System.Numerics.Complex a) => AngouriMath.Numerics.ComplexParts.<#= name #>(a); + public static System.Numerics.Complex <#= name #>(System.Numerics.Complex a) => AngouriMath.Numerics.ComplexParts.<#= name #>(AngouriMath.Core.Compilation.ComplexBranches.Unsigned(a)); public static double <#= name #>(double a) => AngouriMath.Numerics.ComplexParts.<#= name #>(a); <# } #> @@ -237,7 +243,7 @@ namespace AngouriMath.Core.Compilation.IntoLinq // 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 System.Numerics.Complex LambertW(System.Numerics.Complex a, System.Numerics.Complex b) => AngouriMath.Numerics.LambertWFunction.LambertW(a, AngouriMath.Core.Compilation.ComplexBranches.Unsigned(b)); public static double LambertW(double a, double b) => AngouriMath.Numerics.LambertWFunction.LambertW(a, b); // The polygamma functions of a whole order, and the harmonic numbers. diff --git a/Sources/AngouriMath/Functions/Compilation/RealProducts.cs b/Sources/AngouriMath/Functions/Compilation/RealProducts.cs index bf1342d0d..245d210fc 100644 --- a/Sources/AngouriMath/Functions/Compilation/RealProducts.cs +++ b/Sources/AngouriMath/Functions/Compilation/RealProducts.cs @@ -39,7 +39,7 @@ public static NumericsComplex Quotient(NumericsComplex a, NumericsComplex b) public static NumericsComplex Power(NumericsComplex a, NumericsComplex b) { if (a.Imaginary != 0 || b.Imaginary != 0) - return NumericsComplex.Pow(a, b); + return NumericsComplex.Pow(ComplexBranches.Unsigned(a), ComplexBranches.Unsigned(b)); var (x, y) = (a.Real, b.Real); if (double.IsNaN(x) || double.IsNaN(y)) return new NumericsComplex(double.NaN, 0); @@ -50,7 +50,7 @@ public static NumericsComplex Power(NumericsComplex a, NumericsComplex b) return new NumericsComplex(y == 0.5 ? Math.Sqrt(x) : Math.Pow(x, y), 0); if (!double.IsInfinity(y) && Math.Floor(y) == y) return new NumericsComplex(Math.Pow(x, y), 0); - return NumericsComplex.Pow(a, b); + return NumericsComplex.Pow(ComplexBranches.Unsigned(a), ComplexBranches.Unsigned(b)); } } } diff --git a/Sources/Tests/UnitTests/Common/BranchCutCompilationTest.cs b/Sources/Tests/UnitTests/Common/BranchCutCompilationTest.cs new file mode 100644 index 000000000..c0c92c573 --- /dev/null +++ b/Sources/Tests/UnitTests/Common/BranchCutCompilationTest.cs @@ -0,0 +1,92 @@ +// +// 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 +{ + /// + /// A compiled function with a branch cut takes the side evaluation takes, where its argument is a number on the cut + /// that computes with -0 as a part: cos(2) is (-0.416, -0) and + /// conj(-2) is (-2, -0), and ln(cos(x)) at 2 was the conjugate of the logarithm. + /// #2236. And FastExpression's logarithm + /// keeps a complex base's imaginary part. #2239 + /// + [Trait("Area", "Common")] + public sealed class BranchCutCompilationTest + { + private static readonly double[] points = { -4, -2.5, -2, -1, -0.5, 0.5, 1, 2, 2.5, 3, 4 }; + + 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) || double.IsInfinity(expected.Imaginary) + ? expected == actual + : Complex.Abs(expected - actual) <= 1e-9 * System.Math.Max(1, Complex.Abs(expected)); + + [Theory] + [InlineData("ln({0})")] + [InlineData("sqrt({0})")] + // An exponent whose denominator is even: evaluation takes the real root for an odd one, which a double + // exponent cannot show, and the principal value here, as the compilers do. + [InlineData("({0})^(1/4)")] + [InlineData("({0})^(1/2 + i)")] + [InlineData("log(2, {0})")] + [InlineData("log({0}, 2)")] + [InlineData("arcsin({0})")] + [InlineData("arccos({0})")] + [InlineData("arctan({0})")] + [InlineData("arccotan({0})")] + [InlineData("arcsec({0})")] + [InlineData("arccosec({0})")] + [InlineData("arg({0})")] + [InlineData("Ei({0})")] + [InlineData("li({0})")] + [InlineData("Ci({0})")] + [InlineData("Chi({0})")] + [InlineData("lambertw(0, {0})")] + [InlineData("lambertw(-1, {0})")] + public void OnACutTheSideIsEvaluations(string function) + { + var compared = 0; + foreach (var inner in new[] { "conj(x)", "cos(x)", "i * x" }) + { + var expression = string.Format(function, inner).ToEntity(); + var fast = expression.Compile("x"); + var linq = expression.Compile("x"); + foreach (var x in points) + { + if (expression.Substitute("x", x).Evaled is not Entity.Number.Complex evaluated) + continue; + var expected = evaluated.ToNumerics(); + // Not at a pole, arctan at -i among them, which is not what this is about. + if (double.IsInfinity(expected.Real) || double.IsInfinity(expected.Imaginary)) + continue; + Assert.True(Agree(expected, fast.Call(x)), $"{expression} at {x}: {expected} evaluated, {fast.Call(x)} by FastExpression"); + Assert.True(Agree(expected, linq(x)), $"{expression} at {x}: {expected} evaluated, {linq(x)} by LINQ"); + compared++; + } + } + Assert.True(compared > points.Length, $"only {compared} of {function} evaluated"); + } + + [Theory] + [InlineData("log(x, 2)", 1, 1)] + [InlineData("log(x, x + 1)", 0.5, 1.2)] + [InlineData("log(x, 3 - i)", -2, 0.5)] + public void ALogarithmToAComplexBaseIsEvaluations(string expression, double re, double im) + { + var x = new Complex(re, im); + var expected = ((Entity.Number.Complex)expression.ToEntity().Substitute("x", (Entity.Number.Complex)x).Evaled).ToNumerics(); + Assert.True(Agree(expected, expression.ToEntity().Compile("x").Call(x)), $"{expression} at {x}: {expected} evaluated, {expression.ToEntity().Compile("x").Call(x)} by FastExpression"); + Assert.True(Agree(expected, expression.ToEntity().Compile("x")(x)), $"{expression} at {x} by LINQ"); + } + } +}