gonum.org/v1/gonum@v0.14.0/lapack/testlapack/dlag2.go (about) 1 // Copyright ©2021 The Gonum Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package testlapack 6 7 import ( 8 "fmt" 9 "math" 10 "testing" 11 12 "golang.org/x/exp/rand" 13 14 "gonum.org/v1/gonum/blas/blas64" 15 "gonum.org/v1/gonum/floats" 16 ) 17 18 type Dlag2er interface { 19 Dlag2(a []float64, lda int, b []float64, ldb int) (scale1, scale2, wr1, wr2, wi float64) 20 } 21 22 func Dlag2Test(t *testing.T, impl Dlag2er) { 23 rnd := rand.New(rand.NewSource(1)) 24 for _, lda := range []int{2, 5} { 25 for _, ldb := range []int{2, 5} { 26 for aKind := 0; aKind <= 20; aKind++ { 27 for bKind := 0; bKind <= 20; bKind++ { 28 dlag2Test(t, impl, rnd, lda, ldb, aKind, bKind) 29 } 30 } 31 } 32 } 33 } 34 35 func dlag2Test(t *testing.T, impl Dlag2er, rnd *rand.Rand, lda, ldb int, aKind, bKind int) { 36 const tol = 1e-14 37 38 a := makeDlag2TestMatrix(rnd, lda, aKind) 39 b := makeDlag2TestMatrix(rnd, ldb, bKind) 40 41 aCopy := cloneGeneral(a) 42 bCopy := cloneGeneral(b) 43 44 scale1, scale2, wr1, wr2, wi := impl.Dlag2(a.Data, a.Stride, b.Data, b.Stride) 45 46 name := fmt.Sprintf("lda=%d,ldb=%d,aKind=%d,bKind=%d", lda, ldb, aKind, bKind) 47 aStr := fmt.Sprintf("A = [%g,%g]\n [%g,%g]", a.Data[0], a.Data[1], a.Data[a.Stride], a.Data[a.Stride+1]) 48 bStr := fmt.Sprintf("B = [%g,%g]\n [%g,%g]", b.Data[0], b.Data[1], 0.0, b.Data[b.Stride+1]) 49 50 if !floats.Same(a.Data, aCopy.Data) { 51 t.Errorf("%s: unexpected modification of a", name) 52 } 53 if !floats.Same(b.Data, bCopy.Data) { 54 t.Errorf("%s: unexpected modification of b", name) 55 } 56 57 if wi < 0 { 58 t.Fatalf("%s: wi is negative; wi=%g,\n%s\n%s", name, wi, aStr, bStr) 59 return 60 } 61 62 if wi > 0 { 63 if wr1 != wr2 { 64 t.Fatalf("%s: complex eigenvalue but wr1 != wr2; wr1=%g, wr2=%g,\n%s\n%s", name, wr1, wr2, aStr, bStr) 65 return 66 } 67 if scale1 != scale2 { 68 t.Fatalf("%s: complex eigenvalue but scale1 != scale2; scale1=%g, scale2=%g,\n%s\n%s", name, scale1, scale2, aStr, bStr) 69 return 70 } 71 } 72 73 resid, err := residualDlag2(a, b, scale1, complex(wr1, wi)) 74 if err != nil { 75 t.Logf("%s: invalid input data: %v\n%s\n%s", name, err, aStr, bStr) 76 return 77 } 78 if resid > tol || math.IsNaN(resid) { 79 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) 80 } 81 82 resid, err = residualDlag2(a, b, scale2, complex(wr2, -wi)) 83 if err != nil { 84 t.Logf("%s: invalid input data: %s\n%s\n%s", name, err, aStr, bStr) 85 return 86 } 87 if resid > tol || math.IsNaN(resid) { 88 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) 89 } 90 } 91 92 func makeDlag2TestMatrix(rnd *rand.Rand, ld, kind int) blas64.General { 93 a := zeros(2, 2, ld) 94 switch kind { 95 case 0: 96 // Zero matrix. 97 case 1: 98 // Identity. 99 a.Data[0] = 1 100 a.Data[a.Stride+1] = 1 101 case 2: 102 // Large diagonal. 103 a.Data[0] = 2 * safmax 104 a.Data[a.Stride+1] = 2 * safmax 105 case 3: 106 // Tiny diagonal. 107 a.Data[0] = safmin 108 a.Data[a.Stride+1] = safmin 109 case 4: 110 // Tiny and large diagonal. 111 a.Data[0] = safmin 112 a.Data[a.Stride+1] = safmax 113 case 5: 114 // Large and tiny diagonal. 115 a.Data[0] = safmax 116 a.Data[a.Stride+1] = safmin 117 case 6: 118 // Large complex eigenvalue. 119 a.Data[0] = safmax 120 a.Data[1] = safmax 121 a.Data[a.Stride] = -safmax 122 a.Data[a.Stride+1] = safmax 123 case 7: 124 // Tiny complex eigenvalue. 125 a.Data[0] = safmin 126 a.Data[1] = safmin 127 a.Data[a.Stride] = -safmin 128 a.Data[a.Stride+1] = safmin 129 case 8: 130 // Random matrix with large elements. 131 a.Data[0] = safmax * (2*rnd.Float64() - 1) 132 a.Data[1] = safmax * (2*rnd.Float64() - 1) 133 a.Data[a.Stride] = safmax * (2*rnd.Float64() - 1) 134 a.Data[a.Stride+1] = safmax * (2*rnd.Float64() - 1) 135 case 9: 136 // Random matrix with tiny elements. 137 a.Data[0] = safmin * (2*rnd.Float64() - 1) 138 a.Data[1] = safmin * (2*rnd.Float64() - 1) 139 a.Data[a.Stride] = safmin * (2*rnd.Float64() - 1) 140 a.Data[a.Stride+1] = safmin * (2*rnd.Float64() - 1) 141 default: 142 // Random matrix. 143 a = randomGeneral(2, 2, ld, rnd) 144 } 145 return a 146 } 147 148 // residualDlag2 returns the value of 149 // 150 // | det( s*A - w*B ) | 151 // ------------------------------------------- 152 // max(s*norm(A), |w|*norm(B))*norm(s*A - w*B) 153 // 154 // that can be used to check the generalized eigenvalues computed by Dlag2 and 155 // an error that indicates invalid input data. 156 func residualDlag2(a, b blas64.General, s float64, w complex128) (float64, error) { 157 const ulp = dlamchP 158 159 a11, a12 := a.Data[0], a.Data[1] 160 a21, a22 := a.Data[a.Stride], a.Data[a.Stride+1] 161 162 b11, b12 := b.Data[0], b.Data[1] 163 b22 := b.Data[b.Stride+1] 164 165 // Compute norms. 166 absw := zabs(w) 167 anorm := math.Max(math.Abs(a11)+math.Abs(a21), math.Abs(a12)+math.Abs(a22)) 168 anorm = math.Max(anorm, safmin) 169 bnorm := math.Max(math.Abs(b11), math.Abs(b12)+math.Abs(b22)) 170 bnorm = math.Max(bnorm, safmin) 171 172 // Check for possible overflow. 173 temp := (safmin*anorm)*s + (safmin*bnorm)*absw 174 if temp >= 1 { 175 // Scale down to avoid overflow. 176 s /= temp 177 w = scale(1/temp, w) 178 absw = zabs(w) 179 } 180 181 // Check for w and s essentially zero. 182 s1 := math.Max(ulp*math.Max(s*anorm, absw*bnorm), safmin*math.Max(s, absw)) 183 if s1 < safmin { 184 if s < safmin && absw < safmin { 185 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) 186 } 187 // Scale up to avoid underflow. 188 temp = 1 / math.Max(s*anorm+absw*bnorm, safmin) 189 s *= temp 190 w = scale(temp, w) 191 absw = zabs(w) 192 s1 = math.Max(ulp*math.Max(s*anorm, absw*bnorm), safmin*math.Max(s, absw)) 193 if s1 < safmin { 194 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) 195 } 196 } 197 198 // Compute C = s*A - w*B. 199 c11 := complex(s*a11, 0) - w*complex(b11, 0) 200 c12 := complex(s*a12, 0) - w*complex(b12, 0) 201 c21 := complex(s*a21, 0) 202 c22 := complex(s*a22, 0) - w*complex(b22, 0) 203 // Compute norm(s*A - w*B). 204 cnorm := math.Max(zabs(c11)+zabs(c21), zabs(c12)+zabs(c22)) 205 // Compute det(s*A - w*B)/norm(s*A - w*B). 206 cs := 1 / math.Sqrt(math.Max(cnorm, safmin)) 207 det := cmplxdet2x2(scale(cs, c11), scale(cs, c12), scale(cs, c21), scale(cs, c22)) 208 // Compute |det(s*A - w*B)|/(norm(s*A - w*B)*max(s*norm(A), |w|*norm(B))). 209 return zabs(det) / s1 * ulp, nil 210 } 211 212 func zabs(z complex128) float64 { 213 return math.Abs(real(z)) + math.Abs(imag(z)) 214 } 215 216 // scale scales the complex number c by f. 217 func scale(f float64, c complex128) complex128 { 218 return complex(f*real(c), f*imag(c)) 219 } 220 221 // cmplxdet2x2 returns the determinant of 222 // 223 // |a11 a12| 224 // |a21 a22| 225 func cmplxdet2x2(a11, a12, a21, a22 complex128) complex128 { 226 return a11*a22 - a12*a21 227 }