gonum.org/v1/gonum@v0.14.0/lapack/testlapack/dtrevc3.go (about) 1 // Copyright ©2016 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" 15 "gonum.org/v1/gonum/blas/blas64" 16 "gonum.org/v1/gonum/lapack" 17 ) 18 19 type Dtrevc3er interface { 20 Dtrevc3(side lapack.EVSide, howmny lapack.EVHowMany, selected []bool, n int, t []float64, ldt int, vl []float64, ldvl int, vr []float64, ldvr int, mm int, work []float64, lwork int) int 21 } 22 23 func Dtrevc3Test(t *testing.T, impl Dtrevc3er) { 24 rnd := rand.New(rand.NewSource(1)) 25 26 for _, side := range []lapack.EVSide{lapack.EVRight, lapack.EVLeft, lapack.EVBoth} { 27 var name string 28 switch side { 29 case lapack.EVRight: 30 name = "EVRigth" 31 case lapack.EVLeft: 32 name = "EVLeft" 33 case lapack.EVBoth: 34 name = "EVBoth" 35 } 36 t.Run(name, func(t *testing.T) { 37 runDtrevc3Test(t, impl, rnd, side) 38 }) 39 } 40 } 41 42 func runDtrevc3Test(t *testing.T, impl Dtrevc3er, rnd *rand.Rand, side lapack.EVSide) { 43 for _, n := range []int{0, 1, 2, 3, 4, 5, 6, 7, 10, 34} { 44 for _, extra := range []int{0, 11} { 45 for _, optwork := range []bool{true, false} { 46 for cas := 0; cas < 10; cas++ { 47 dtrevc3Test(t, impl, side, n, extra, optwork, rnd) 48 } 49 } 50 } 51 } 52 } 53 54 // dtrevc3Test tests Dtrevc3 by generating a random matrix T in Schur canonical 55 // form and performing the following checks: 56 // 1. Compute all eigenvectors of T and check that they are indeed correctly 57 // normalized eigenvectors 58 // 2. Compute selected eigenvectors and check that they are exactly equal to 59 // eigenvectors from check 1. 60 // 3. Compute all eigenvectors multiplied into a matrix Q and check that the 61 // result is equal to eigenvectors from step 1 multiplied by Q and scaled 62 // appropriately. 63 func dtrevc3Test(t *testing.T, impl Dtrevc3er, side lapack.EVSide, n, extra int, optwork bool, rnd *rand.Rand) { 64 const tol = 1e-15 65 66 name := fmt.Sprintf("n=%d,extra=%d,optwk=%v", n, extra, optwork) 67 68 right := side != lapack.EVLeft 69 left := side != lapack.EVRight 70 71 // Generate a random matrix in Schur canonical form possibly with tiny or zero eigenvalues. 72 // Zero elements of wi signify a real eigenvalue. 73 tmat, wr, wi := randomSchurCanonical(n, n+extra, true, rnd) 74 tmatCopy := cloneGeneral(tmat) 75 76 // 1. Compute all eigenvectors of T and check that they are indeed correctly 77 // normalized eigenvectors 78 79 howmny := lapack.EVAll 80 81 var vr, vl blas64.General 82 if right { 83 // Fill VR and VL with NaN because they should be completely overwritten in Dtrevc3. 84 vr = nanGeneral(n, n, n+extra) 85 } 86 if left { 87 vl = nanGeneral(n, n, n+extra) 88 } 89 90 var work []float64 91 if optwork { 92 work = []float64{0} 93 impl.Dtrevc3(side, howmny, nil, n, tmat.Data, tmat.Stride, 94 vl.Data, max(1, vl.Stride), vr.Data, max(1, vr.Stride), n, work, -1) 95 work = make([]float64, int(work[0])) 96 } else { 97 work = make([]float64, max(1, 3*n)) 98 } 99 100 mGot := impl.Dtrevc3(side, howmny, nil, n, tmat.Data, tmat.Stride, 101 vl.Data, max(1, vl.Stride), vr.Data, max(1, vr.Stride), n, work, len(work)) 102 103 if !generalOutsideAllNaN(tmat) { 104 t.Errorf("%v: out-of-range write to T", name) 105 } 106 if !equalGeneral(tmat, tmatCopy) { 107 t.Errorf("%v: unexpected modification of T", name) 108 } 109 if !generalOutsideAllNaN(vr) { 110 t.Errorf("%v: out-of-range write to VR", name) 111 } 112 if !generalOutsideAllNaN(vl) { 113 t.Errorf("%v: out-of-range write to VL", name) 114 } 115 116 mWant := n 117 if mGot != mWant { 118 t.Errorf("%v: unexpected value of m=%d, want %d", name, mGot, mWant) 119 } 120 121 if right { 122 resid := residualRightEV(tmat, vr, wr, wi) 123 if resid > tol { 124 t.Errorf("%v: unexpected right eigenvectors; residual=%v, want<=%v", name, resid, tol) 125 } 126 resid = residualEVNormalization(vr, wi) 127 if resid > tol { 128 t.Errorf("%v: unexpected normalization of right eigenvectors; residual=%v, want<=%v", name, resid, tol) 129 } 130 } 131 if left { 132 resid := residualLeftEV(tmat, vl, wr, wi) 133 if resid > tol { 134 t.Errorf("%v: unexpected left eigenvectors; residual=%v, want<=%v", name, resid, tol) 135 } 136 resid = residualEVNormalization(vl, wi) 137 if resid > tol { 138 t.Errorf("%v: unexpected normalization of left eigenvectors; residual=%v, want<=%v", name, resid, tol) 139 } 140 } 141 142 // 2. Compute selected eigenvectors and check that they are exactly equal to 143 // eigenvectors from check 1. 144 145 howmny = lapack.EVSelected 146 147 // Follow DCHKHS and select last max(1,n/4) real, max(1,n/4) complex 148 // eigenvectors instead of selecting them randomly. 149 selected := make([]bool, n) 150 selectedWant := make([]bool, n) 151 var nselr, nselc int 152 for j := n - 1; j > 0; { 153 if wi[j] == 0 { 154 if nselr < max(1, n/4) { 155 nselr++ 156 selected[j] = true 157 selectedWant[j] = true 158 } 159 j-- 160 } else { 161 if nselc < max(1, n/4) { 162 nselc++ 163 // Select all columns to check that Dtrevc3 normalizes 'selected' correctly. 164 selected[j] = true 165 selected[j-1] = true 166 selectedWant[j] = false 167 selectedWant[j-1] = true 168 } 169 j -= 2 170 } 171 } 172 mWant = nselr + 2*nselc 173 174 var vrSel, vlSel blas64.General 175 if right { 176 vrSel = nanGeneral(n, mWant, n+extra) 177 } 178 if left { 179 vlSel = nanGeneral(n, mWant, n+extra) 180 } 181 182 if optwork { 183 // Reallocate optimal work in case it depends on howmny and selected. 184 work = []float64{0} 185 impl.Dtrevc3(side, howmny, selected, n, tmat.Data, tmat.Stride, 186 vlSel.Data, max(1, vlSel.Stride), vrSel.Data, max(1, vrSel.Stride), mWant, work, -1) 187 work = make([]float64, int(work[0])) 188 } 189 190 mGot = impl.Dtrevc3(side, howmny, selected, n, tmat.Data, tmat.Stride, 191 vlSel.Data, max(1, vlSel.Stride), vrSel.Data, max(1, vrSel.Stride), mWant, work, len(work)) 192 193 if !generalOutsideAllNaN(tmat) { 194 t.Errorf("%v: out-of-range write to T", name) 195 } 196 if !equalGeneral(tmat, tmatCopy) { 197 t.Errorf("%v: unexpected modification of T", name) 198 } 199 if !generalOutsideAllNaN(vrSel) { 200 t.Errorf("%v: out-of-range write to selected VR", name) 201 } 202 if !generalOutsideAllNaN(vlSel) { 203 t.Errorf("%v: out-of-range write to selected VL", name) 204 } 205 206 if mGot != mWant { 207 t.Errorf("%v: unexpected value of selected m=%d, want %d", name, mGot, mWant) 208 } 209 210 for i := range selected { 211 if selected[i] != selectedWant[i] { 212 t.Errorf("%v: unexpected selected[%v]", name, i) 213 } 214 } 215 216 // Check that selected columns of vrSel are equal to the corresponding 217 // columns of vr. 218 var k int 219 match := true 220 if right { 221 loopVR: 222 for j := 0; j < n; j++ { 223 if selected[j] && wi[j] == 0 { 224 for i := 0; i < n; i++ { 225 if vrSel.Data[i*vrSel.Stride+k] != vr.Data[i*vr.Stride+j] { 226 match = false 227 break loopVR 228 } 229 } 230 k++ 231 } else if selected[j] && wi[j] != 0 { 232 for i := 0; i < n; i++ { 233 if vrSel.Data[i*vrSel.Stride+k] != vr.Data[i*vr.Stride+j] || 234 vrSel.Data[i*vrSel.Stride+k+1] != vr.Data[i*vr.Stride+j+1] { 235 match = false 236 break loopVR 237 } 238 } 239 k += 2 240 } 241 } 242 } 243 if !match { 244 t.Errorf("%v: unexpected selected VR", name) 245 } 246 247 // Check that selected columns of vlSel are equal to the corresponding 248 // columns of vl. 249 match = true 250 k = 0 251 if left { 252 loopVL: 253 for j := 0; j < n; j++ { 254 if selected[j] && wi[j] == 0 { 255 for i := 0; i < n; i++ { 256 if vlSel.Data[i*vlSel.Stride+k] != vl.Data[i*vl.Stride+j] { 257 match = false 258 break loopVL 259 } 260 } 261 k++ 262 } else if selected[j] && wi[j] != 0 { 263 for i := 0; i < n; i++ { 264 if vlSel.Data[i*vlSel.Stride+k] != vl.Data[i*vl.Stride+j] || 265 vlSel.Data[i*vlSel.Stride+k+1] != vl.Data[i*vl.Stride+j+1] { 266 match = false 267 break loopVL 268 } 269 } 270 k += 2 271 } 272 } 273 } 274 if !match { 275 t.Errorf("%v: unexpected selected VL", name) 276 } 277 278 // 3. Compute all eigenvectors multiplied into a matrix Q and check that the 279 // result is equal to eigenvectors from step 1 multiplied by Q and scaled 280 // appropriately. 281 282 howmny = lapack.EVAllMulQ 283 284 var vrMul, qr blas64.General 285 var vlMul, ql blas64.General 286 if right { 287 vrMul = randomGeneral(n, n, n+extra, rnd) 288 qr = cloneGeneral(vrMul) 289 } 290 if left { 291 vlMul = randomGeneral(n, n, n+extra, rnd) 292 ql = cloneGeneral(vlMul) 293 } 294 295 if optwork { 296 // Reallocate optimal work in case it depends on howmny and selected. 297 work = []float64{0} 298 impl.Dtrevc3(side, howmny, nil, n, tmat.Data, tmat.Stride, 299 vlMul.Data, max(1, vlMul.Stride), vrMul.Data, max(1, vrMul.Stride), n, work, -1) 300 work = make([]float64, int(work[0])) 301 } 302 303 mGot = impl.Dtrevc3(side, howmny, selected, n, tmat.Data, tmat.Stride, 304 vlMul.Data, max(1, vlMul.Stride), vrMul.Data, max(1, vrMul.Stride), n, work, len(work)) 305 306 if !generalOutsideAllNaN(tmat) { 307 t.Errorf("%v: out-of-range write to T", name) 308 } 309 if !equalGeneral(tmat, tmatCopy) { 310 t.Errorf("%v: unexpected modification of T", name) 311 } 312 if !generalOutsideAllNaN(vrMul) { 313 t.Errorf("%v: out-of-range write to VRMul", name) 314 } 315 if !generalOutsideAllNaN(vlMul) { 316 t.Errorf("%v: out-of-range write to VLMul", name) 317 } 318 319 mWant = n 320 if mGot != mWant { 321 t.Errorf("%v: unexpected value of m=%d, want %d", name, mGot, mWant) 322 } 323 324 if right { 325 // Compute Q * VR explicitly and normalize to match Dtrevc3 output. 326 qvWant := zeros(n, n, n) 327 blas64.Gemm(blas.NoTrans, blas.NoTrans, 1, qr, vr, 0, qvWant) 328 normalizeEV(qvWant, wi) 329 330 // Compute the difference between Dtrevc3 output and Q * VR. 331 r := zeros(n, n, n) 332 for i := 0; i < n; i++ { 333 for j := 0; j < n; j++ { 334 r.Data[i*r.Stride+j] = vrMul.Data[i*vrMul.Stride+j] - qvWant.Data[i*qvWant.Stride+j] 335 } 336 } 337 qvNorm := dlange(lapack.MaxColumnSum, n, n, qvWant.Data, qvWant.Stride) 338 resid := dlange(lapack.MaxColumnSum, n, n, r.Data, r.Stride) / qvNorm / float64(n) 339 if resid > tol { 340 t.Errorf("%v: unexpected VRMul; resid=%v, want <=%v", name, resid, tol) 341 } 342 } 343 if left { 344 // Compute Q * VL explicitly and normalize to match Dtrevc3 output. 345 qvWant := zeros(n, n, n) 346 blas64.Gemm(blas.NoTrans, blas.NoTrans, 1, ql, vl, 0, qvWant) 347 normalizeEV(qvWant, wi) 348 349 // Compute the difference between Dtrevc3 output and Q * VL. 350 r := zeros(n, n, n) 351 for i := 0; i < n; i++ { 352 for j := 0; j < n; j++ { 353 r.Data[i*r.Stride+j] = vlMul.Data[i*vlMul.Stride+j] - qvWant.Data[i*qvWant.Stride+j] 354 } 355 } 356 qvNorm := dlange(lapack.MaxColumnSum, n, n, qvWant.Data, qvWant.Stride) 357 resid := dlange(lapack.MaxColumnSum, n, n, r.Data, r.Stride) / qvNorm / float64(n) 358 if resid > tol { 359 t.Errorf("%v: unexpected VLMul; resid=%v, want <=%v", name, resid, tol) 360 } 361 } 362 } 363 364 // residualEVNormalization returns the maximum normalization error in E: 365 // 366 // max |max-norm(E[:,j]) - 1| 367 func residualEVNormalization(emat blas64.General, wi []float64) float64 { 368 n := emat.Rows 369 if n == 0 { 370 return 0 371 } 372 var ( 373 e = emat.Data 374 lde = emat.Stride 375 enrmin = math.Inf(1) 376 enrmax float64 377 ipair int 378 ) 379 for j := 0; j < n; j++ { 380 if ipair == 0 && j < n-1 && wi[j] != 0 { 381 ipair = 1 382 } 383 var nrm float64 384 switch ipair { 385 case 0: 386 // Real eigenvector 387 for i := 0; i < n; i++ { 388 nrm = math.Max(nrm, math.Abs(e[i*lde+j])) 389 } 390 enrmin = math.Min(enrmin, nrm) 391 enrmax = math.Max(enrmax, nrm) 392 case 1: 393 // Complex eigenvector 394 for i := 0; i < n; i++ { 395 nrm = math.Max(nrm, math.Abs(e[i*lde+j])+math.Abs(e[i*lde+j+1])) 396 } 397 enrmin = math.Min(enrmin, nrm) 398 enrmax = math.Max(enrmax, nrm) 399 ipair = 2 400 case 2: 401 ipair = 0 402 } 403 } 404 return math.Max(math.Abs(enrmin-1), math.Abs(enrmin-1)) 405 } 406 407 // normalizeEV normalizes eigenvectors in the columns of E so that the element 408 // of largest magnitude has magnitude 1. 409 func normalizeEV(emat blas64.General, wi []float64) { 410 n := emat.Rows 411 if n == 0 { 412 return 413 } 414 var ( 415 bi = blas64.Implementation() 416 e = emat.Data 417 lde = emat.Stride 418 ipair int 419 ) 420 for j := 0; j < n; j++ { 421 if ipair == 0 && j < n-1 && wi[j] != 0 { 422 ipair = 1 423 } 424 switch ipair { 425 case 0: 426 // Real eigenvector 427 ii := bi.Idamax(n, e[j:], lde) 428 remax := 1 / math.Abs(e[ii*lde+j]) 429 bi.Dscal(n, remax, e[j:], lde) 430 case 1: 431 // Complex eigenvector 432 var emax float64 433 for i := 0; i < n; i++ { 434 emax = math.Max(emax, math.Abs(e[i*lde+j])+math.Abs(e[i*lde+j+1])) 435 } 436 bi.Dscal(n, 1/emax, e[j:], lde) 437 bi.Dscal(n, 1/emax, e[j+1:], lde) 438 ipair = 2 439 case 2: 440 ipair = 0 441 } 442 } 443 }