mathext: add dilogarithm function Li2#2059
Merged
kortschak merged 20 commits intogonum:masterfrom Sep 24, 2025
Merged
Conversation
728eff0 to
feab458
Compare
…-values close to -1.
kortschak
reviewed
Sep 21, 2025
Member
kortschak
left a comment
There was a problem hiding this comment.
diff --git a/mathext/dilog.go b/mathext/dilog.go
index 0eedd87c..fc3f2ae9 100644
--- a/mathext/dilog.go
+++ b/mathext/dilog.go
@@ -42,13 +42,13 @@ func Li2(z complex128) complex128 {
// Arg(-z)= +/-Pi branch choice in log(-z) and can yield wrong imaginary signs
// if care is not taken.
if real(z) > 0.5 {
- return complex(math.Pi*math.Pi/6, 0) - cmplx.Log(z)*cmplx.Log(1-z) - Li2(1-z)
+ return complex(math.Pi*math.Pi/6, 0) - complex128(cmplx.Log(z)*cmplx.Log(1-z)) - Li2(1-z)
}
// Inversion: map |z| > 1 into unit disk
if cmplx.Abs(z) > 1 {
logmz := cmplx.Log(-z)
- return -complex(math.Pi*math.Pi/6, 0) - 0.5*logmz*logmz - Li2(1/z)
+ return -complex(math.Pi*math.Pi/6, 0) - complex128(0.5*logmz*logmz) - Li2(1/z)
}
// Direct series for |z| <= 1 and Re(z) <= 0.5
@@ -57,10 +57,10 @@ func Li2(z complex128) complex128 {
func li2Series(z complex128) complex128 {
const tol = 1e-15
- sum := complex(0, 0)
+ var sum complex128
zk := z // zk = z^k
- for k := 1; cmplx.Abs(zk)/float64(k*k) > tol; k++ {
- sum += zk / complex(float64(k*k), 0)
+ for k := 1.0; cmplx.Abs(zk)/(k*k) > tol; k++ {
+ sum += zk / complex(k*k, 0)
zk *= z
}
return sum
diff --git a/mathext/dilog_test.go b/mathext/dilog_test.go
index dfd59c33..a9676ccc 100644
--- a/mathext/dilog_test.go
+++ b/mathext/dilog_test.go
@@ -8,48 +8,53 @@ import (
"math"
"math/cmplx"
"testing"
+
+ "gonum.org/v1/gonum/cmplxs/cscalar"
)
func TestDiLogValues(t *testing.T) {
t.Parallel()
for _, test := range []struct {
- input, want complex128
+ in, want complex128
}{
// Reference values were generated using Python's scipy.special.spence
// (where Li2(z) = spence(1 - z)) and verified with a computer algebra system.
// well known values
- {0 + 0i, 0 + 0i},
- {1 + 0i, math.Pi*math.Pi/6 + 0i},
- {-1 + 0i, -math.Pi*math.Pi/12 + 0i},
- {-1.000010000000000 + 0i, -0.822473964886262 + 0i},
- {-0.999999000000000 + 0i, -0.822466340276836 + 0i},
- {0.5 + 0i, 0.582240526465012 + 0i},
- {2 + 0i, 2.467401100272340 - 2.177586090303602i},
+ {in: 0 + 0i, want: 0 + 0i},
+ {in: 1 + 0i, want: math.Pi*math.Pi/6 + 0i},
+ {in: -1 + 0i, want: -math.Pi*math.Pi/12 + 0i},
+ {in: -1.000010000000000 + 0i, want: -0.822473964886262 + 0i},
+ {in: -0.999999000000000 + 0i, want: -0.822466340276836 + 0i},
+ {in: 0.5 + 0i, want: 0.582240526465012 + 0i},
+ {in: 2 + 0i, want: 2.467401100272340 - 2.177586090303602i},
+
// abs(z) < 0.5
- {0.1 + 0.1i, 0.099751196798713 + 0.105220383905600i},
- {-0.3 + 0.39i, -0.306174046754339 + 0.337550668621259i},
- {0.001 - 0.49i, -0.055831393285929 - 0.478156430372353i},
+ {in: 0.1 + 0.1i, want: 0.099751196798713 + 0.105220383905600i},
+ {in: -0.3 + 0.39i, want: -0.306174046754339 + 0.337550668621259i},
+ {in: 0.001 - 0.49i, want: -0.055831393285929 - 0.478156430372353i},
+
// 0.5 < abs(z) < 1
- {0.5 + 0.7i, 0.359364350522653 + 0.856767712327590i},
- {complex(math.Pi/4, -math.Pi/7), 0.828086461155377 - 0.739345385309341i},
- {-0.8 - 0.0001i, -0.679781588954542 - 0.000073473333084i},
+ {in: 0.5 + 0.7i, want: 0.359364350522653 + 0.856767712327590i},
+ {in: complex(math.Pi/4, -math.Pi/7), want: 0.828086461155377 - 0.739345385309341i},
+ {in: -0.8 - 0.0001i, want: -0.679781588954542 - 0.000073473333084i},
+
// abs(z) > 1
- {5 + 0i, 1.783719161266631 - 5.056198322111862i},
- {-10 + 0i, -4.198277886858104 + 0i},
- {1000 + 10000i, -42.71073756884990 + 15.39396088869304i},
- {-1791.91931 + 0.5i, -29.70223568904652 + 0.00209038439188i},
+ {in: 5 + 0i, want: 1.783719161266631 - 5.056198322111862i},
+ {in: -10 + 0i, want: -4.198277886858104 + 0i},
+ {in: 1000 + 10000i, want: -42.71073756884990 + 15.39396088869304i},
+ {in: -1791.91931 + 0.5i, want: -29.70223568904652 + 0.00209038439188i},
} {
- got := Li2(test.input)
+ got := Li2(test.in)
const tol = 1e-10
diff := cmplx.Abs(got - test.want)
- if cmplx.Abs(test.want) > 0 {
- if diff/cmplx.Abs(test.want) > tol {
- t.Errorf("Li2(%g) relative error %g exceeds tol %g", test.input, diff/cmplx.Abs(test.want), tol)
+ if test.want != 0 {
+ if !cscalar.EqualWithinRel(got, test.want, tol) {
+ t.Errorf("Li2(%g) relative error %g exceeds tol %g", test.in, diff/cmplx.Abs(test.want), tol)
}
- } else if diff > tol {
- t.Errorf("Li2(%g) abs error %g exceeds tol %g", test.input, diff, tol)
+ } else if !cscalar.EqualWithinAbs(got, test.want, tol) {
+ t.Errorf("Li2(%g) abs error %g exceeds tol %g", test.in, diff, tol)
}
}
}
@@ -67,7 +72,7 @@ func TestDiLogProperties(t *testing.T) {
} {
lhs := Li2(z * z)
rhs := 2 * (Li2(z) + Li2(-z))
- if math.Abs(real(lhs)-real(rhs)) > tol || math.Abs(imag(lhs)-imag(rhs)) > tol {
+ if !cscalar.EqualWithinAbs(lhs, rhs, tol) {
t.Errorf("duplication formula failed for case %d, z=%v: got %v want %v", i, z, lhs, rhs)
}
}
@@ -81,7 +86,7 @@ func TestDiLogProperties(t *testing.T) {
} {
lhs := Li2(cmplx.Conj(z))
rhs := cmplx.Conj(Li2(z))
- if math.Abs(real(lhs)-real(rhs)) > tol || math.Abs(imag(lhs)-imag(rhs)) > tol {
+ if !cscalar.EqualWithinAbs(lhs, rhs, tol) {
t.Errorf("conjugation symmetry failed for case %d, z=%v: got %v want %v", i, z, lhs, rhs)
}
}
kortschak
reviewed
Sep 22, 2025
Co-authored-by: Dan Kortschak <[email protected]>
Co-authored-by: Dan Kortschak <[email protected]>
Co-authored-by: Dan Kortschak <[email protected]>
Co-authored-by: Dan Kortschak <[email protected]>
Co-authored-by: Dan Kortschak <[email protected]>
Co-authored-by: Dan Kortschak <[email protected]>
kortschak
reviewed
Sep 23, 2025
Co-authored-by: Dan Kortschak <[email protected]>
Co-authored-by: Dan Kortschak <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This change adds
Li2, the complex dilogarithm on the principal branch, tomathext.Results have been verified against
scipy.special.spence(1 - z)for a range of real and complex inputs.