mirror of
https://github.com/gonum/gonum.git
synced 2025-10-04 23:02:42 +08:00

Changes made in dsp/fourier/internal/fftpack break the formatting used there, so these are reverted. There will be complaints in CI. [git-generate] gofmt -w . go generate gonum.org/v1/gonum/blas go generate gonum.org/v1/gonum/blas/gonum go generate gonum.org/v1/gonum/unit go generate gonum.org/v1/gonum/unit/constant go generate gonum.org/v1/gonum/graph/formats/dot go generate gonum.org/v1/gonum/graph/formats/rdf go generate gonum.org/v1/gonum/stat/card git checkout -- dsp/fourier/internal/fftpack
228 lines
6.5 KiB
Go
228 lines
6.5 KiB
Go
// Copyright ©2021 The Gonum Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package testlapack
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"testing"
|
|
|
|
"golang.org/x/exp/rand"
|
|
|
|
"gonum.org/v1/gonum/blas/blas64"
|
|
"gonum.org/v1/gonum/floats"
|
|
)
|
|
|
|
type Dlag2er interface {
|
|
Dlag2(a []float64, lda int, b []float64, ldb int) (scale1, scale2, wr1, wr2, wi float64)
|
|
}
|
|
|
|
func Dlag2Test(t *testing.T, impl Dlag2er) {
|
|
rnd := rand.New(rand.NewSource(1))
|
|
for _, lda := range []int{2, 5} {
|
|
for _, ldb := range []int{2, 5} {
|
|
for aKind := 0; aKind <= 20; aKind++ {
|
|
for bKind := 0; bKind <= 20; bKind++ {
|
|
dlag2Test(t, impl, rnd, lda, ldb, aKind, bKind)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func dlag2Test(t *testing.T, impl Dlag2er, rnd *rand.Rand, lda, ldb int, aKind, bKind int) {
|
|
const tol = 1e-14
|
|
|
|
a := makeDlag2TestMatrix(rnd, lda, aKind)
|
|
b := makeDlag2TestMatrix(rnd, ldb, bKind)
|
|
|
|
aCopy := cloneGeneral(a)
|
|
bCopy := cloneGeneral(b)
|
|
|
|
scale1, scale2, wr1, wr2, wi := impl.Dlag2(a.Data, a.Stride, b.Data, b.Stride)
|
|
|
|
name := fmt.Sprintf("lda=%d,ldb=%d,aKind=%d,bKind=%d", lda, ldb, aKind, bKind)
|
|
aStr := fmt.Sprintf("A = [%g,%g]\n [%g,%g]", a.Data[0], a.Data[1], a.Data[a.Stride], a.Data[a.Stride+1])
|
|
bStr := fmt.Sprintf("B = [%g,%g]\n [%g,%g]", b.Data[0], b.Data[1], 0.0, b.Data[b.Stride+1])
|
|
|
|
if !floats.Same(a.Data, aCopy.Data) {
|
|
t.Errorf("%s: unexpected modification of a", name)
|
|
}
|
|
if !floats.Same(b.Data, bCopy.Data) {
|
|
t.Errorf("%s: unexpected modification of b", name)
|
|
}
|
|
|
|
if wi < 0 {
|
|
t.Fatalf("%s: wi is negative; wi=%g,\n%s\n%s", name, wi, aStr, bStr)
|
|
return
|
|
}
|
|
|
|
if wi > 0 {
|
|
if wr1 != wr2 {
|
|
t.Fatalf("%s: complex eigenvalue but wr1 != wr2; wr1=%g, wr2=%g,\n%s\n%s", name, wr1, wr2, aStr, bStr)
|
|
return
|
|
}
|
|
if scale1 != scale2 {
|
|
t.Fatalf("%s: complex eigenvalue but scale1 != scale2; scale1=%g, scale2=%g,\n%s\n%s", name, scale1, scale2, aStr, bStr)
|
|
return
|
|
}
|
|
}
|
|
|
|
resid, err := residualDlag2(a, b, scale1, complex(wr1, wi))
|
|
if err != nil {
|
|
t.Logf("%s: invalid input data: %v\n%s\n%s", name, err, aStr, bStr)
|
|
return
|
|
}
|
|
if resid > tol || math.IsNaN(resid) {
|
|
t.Errorf("%s: unexpected first eigenvalue %g with s=%g; resid=%g, want<=%g\n%s\n%s", name, complex(wr1, wi), scale1, resid, tol, aStr, bStr)
|
|
}
|
|
|
|
resid, err = residualDlag2(a, b, scale2, complex(wr2, -wi))
|
|
if err != nil {
|
|
t.Logf("%s: invalid input data: %s\n%s\n%s", name, err, aStr, bStr)
|
|
return
|
|
}
|
|
if resid > tol || math.IsNaN(resid) {
|
|
t.Errorf("%s: unexpected second eigenvalue %g with s=%g; resid=%g, want<=%g\n%s\n%s", name, complex(wr2, -wi), scale2, resid, tol, aStr, bStr)
|
|
}
|
|
}
|
|
|
|
func makeDlag2TestMatrix(rnd *rand.Rand, ld, kind int) blas64.General {
|
|
a := zeros(2, 2, ld)
|
|
switch kind {
|
|
case 0:
|
|
// Zero matrix.
|
|
case 1:
|
|
// Identity.
|
|
a.Data[0] = 1
|
|
a.Data[a.Stride+1] = 1
|
|
case 2:
|
|
// Large diagonal.
|
|
a.Data[0] = 2 * safmax
|
|
a.Data[a.Stride+1] = 2 * safmax
|
|
case 3:
|
|
// Tiny diagonal.
|
|
a.Data[0] = safmin
|
|
a.Data[a.Stride+1] = safmin
|
|
case 4:
|
|
// Tiny and large diagonal.
|
|
a.Data[0] = safmin
|
|
a.Data[a.Stride+1] = safmax
|
|
case 5:
|
|
// Large and tiny diagonal.
|
|
a.Data[0] = safmax
|
|
a.Data[a.Stride+1] = safmin
|
|
case 6:
|
|
// Large complex eigenvalue.
|
|
a.Data[0] = safmax
|
|
a.Data[1] = safmax
|
|
a.Data[a.Stride] = -safmax
|
|
a.Data[a.Stride+1] = safmax
|
|
case 7:
|
|
// Tiny complex eigenvalue.
|
|
a.Data[0] = safmin
|
|
a.Data[1] = safmin
|
|
a.Data[a.Stride] = -safmin
|
|
a.Data[a.Stride+1] = safmin
|
|
case 8:
|
|
// Random matrix with large elements.
|
|
a.Data[0] = safmax * (2*rnd.Float64() - 1)
|
|
a.Data[1] = safmax * (2*rnd.Float64() - 1)
|
|
a.Data[a.Stride] = safmax * (2*rnd.Float64() - 1)
|
|
a.Data[a.Stride+1] = safmax * (2*rnd.Float64() - 1)
|
|
case 9:
|
|
// Random matrix with tiny elements.
|
|
a.Data[0] = safmin * (2*rnd.Float64() - 1)
|
|
a.Data[1] = safmin * (2*rnd.Float64() - 1)
|
|
a.Data[a.Stride] = safmin * (2*rnd.Float64() - 1)
|
|
a.Data[a.Stride+1] = safmin * (2*rnd.Float64() - 1)
|
|
default:
|
|
// Random matrix.
|
|
a = randomGeneral(2, 2, ld, rnd)
|
|
}
|
|
return a
|
|
}
|
|
|
|
// residualDlag2 returns the value of
|
|
//
|
|
// | det( s*A - w*B ) |
|
|
// -------------------------------------------
|
|
// max(s*norm(A), |w|*norm(B))*norm(s*A - w*B)
|
|
//
|
|
// that can be used to check the generalized eigenvalues computed by Dlag2 and
|
|
// an error that indicates invalid input data.
|
|
func residualDlag2(a, b blas64.General, s float64, w complex128) (float64, error) {
|
|
const ulp = dlamchP
|
|
|
|
a11, a12 := a.Data[0], a.Data[1]
|
|
a21, a22 := a.Data[a.Stride], a.Data[a.Stride+1]
|
|
|
|
b11, b12 := b.Data[0], b.Data[1]
|
|
b22 := b.Data[b.Stride+1]
|
|
|
|
// Compute norms.
|
|
absw := zabs(w)
|
|
anorm := math.Max(math.Abs(a11)+math.Abs(a21), math.Abs(a12)+math.Abs(a22))
|
|
anorm = math.Max(anorm, safmin)
|
|
bnorm := math.Max(math.Abs(b11), math.Abs(b12)+math.Abs(b22))
|
|
bnorm = math.Max(bnorm, safmin)
|
|
|
|
// Check for possible overflow.
|
|
temp := (safmin*anorm)*s + (safmin*bnorm)*absw
|
|
if temp >= 1 {
|
|
// Scale down to avoid overflow.
|
|
s /= temp
|
|
w = scale(1/temp, w)
|
|
absw = zabs(w)
|
|
}
|
|
|
|
// Check for w and s essentially zero.
|
|
s1 := math.Max(ulp*math.Max(s*anorm, absw*bnorm), safmin*math.Max(s, absw))
|
|
if s1 < safmin {
|
|
if s < safmin && absw < safmin {
|
|
return 1 / ulp, fmt.Errorf("ulp*max(s*|A|,|w|*|B|) < safmin and s and w could not be scaled; s=%g, |w|=%g", s, absw)
|
|
}
|
|
// Scale up to avoid underflow.
|
|
temp = 1 / math.Max(s*anorm+absw*bnorm, safmin)
|
|
s *= temp
|
|
w = scale(temp, w)
|
|
absw = zabs(w)
|
|
s1 = math.Max(ulp*math.Max(s*anorm, absw*bnorm), safmin*math.Max(s, absw))
|
|
if s1 < safmin {
|
|
return 1 / ulp, fmt.Errorf("ulp*max(s*|A|,|w|*|B|) < safmin and s and w could not be scaled; s=%g, |w|=%g", s, absw)
|
|
}
|
|
}
|
|
|
|
// Compute C = s*A - w*B.
|
|
c11 := complex(s*a11, 0) - w*complex(b11, 0)
|
|
c12 := complex(s*a12, 0) - w*complex(b12, 0)
|
|
c21 := complex(s*a21, 0)
|
|
c22 := complex(s*a22, 0) - w*complex(b22, 0)
|
|
// Compute norm(s*A - w*B).
|
|
cnorm := math.Max(zabs(c11)+zabs(c21), zabs(c12)+zabs(c22))
|
|
// Compute det(s*A - w*B)/norm(s*A - w*B).
|
|
cs := 1 / math.Sqrt(math.Max(cnorm, safmin))
|
|
det := cmplxdet2x2(scale(cs, c11), scale(cs, c12), scale(cs, c21), scale(cs, c22))
|
|
// Compute |det(s*A - w*B)|/(norm(s*A - w*B)*max(s*norm(A), |w|*norm(B))).
|
|
return zabs(det) / s1 * ulp, nil
|
|
}
|
|
|
|
func zabs(z complex128) float64 {
|
|
return math.Abs(real(z)) + math.Abs(imag(z))
|
|
}
|
|
|
|
// scale scales the complex number c by f.
|
|
func scale(f float64, c complex128) complex128 {
|
|
return complex(f*real(c), f*imag(c))
|
|
}
|
|
|
|
// cmplxdet2x2 returns the determinant of
|
|
//
|
|
// |a11 a12|
|
|
// |a21 a22|
|
|
func cmplxdet2x2(a11, a12, a21, a22 complex128) complex128 {
|
|
return a11*a22 - a12*a21
|
|
}
|