github.com/gonum/lapack@v0.0.0-20181123203213-e4cdc5a0bff9/testlapack/dlahqr.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 "math/rand" 11 "testing" 12 13 "github.com/gonum/blas" 14 "github.com/gonum/blas/blas64" 15 ) 16 17 type Dlahqrer interface { 18 Dlahqr(wantt, wantz bool, n, ilo, ihi int, h []float64, ldh int, wr, wi []float64, iloz, ihiz int, z []float64, ldz int) int 19 } 20 21 type dlahqrTest struct { 22 h blas64.General 23 ilo, ihi int 24 iloz, ihiz int 25 wantt, wantz bool 26 27 evWant []complex128 // Optional slice holding known eigenvalues. 28 } 29 30 func DlahqrTest(t *testing.T, impl Dlahqrer) { 31 rnd := rand.New(rand.NewSource(1)) 32 33 // Tests that choose the [ilo:ihi+1,ilo:ihi+1] and 34 // [iloz:ihiz+1,ilo:ihi+1] blocks randomly. 35 for _, wantt := range []bool{true, false} { 36 for _, wantz := range []bool{true, false} { 37 for _, n := range []int{1, 2, 3, 4, 5, 6, 10, 18, 31, 53} { 38 for _, extra := range []int{0, 1, 11} { 39 for cas := 0; cas < 100; cas++ { 40 ilo := rnd.Intn(n) 41 ihi := rnd.Intn(n) 42 if ilo > ihi { 43 ilo, ihi = ihi, ilo 44 } 45 iloz := rnd.Intn(ilo + 1) 46 ihiz := ihi + rnd.Intn(n-ihi) 47 h := randomHessenberg(n, n+extra, rnd) 48 if ilo-1 >= 0 { 49 h.Data[ilo*h.Stride+ilo-1] = 0 50 } 51 if ihi+1 < n { 52 h.Data[(ihi+1)*h.Stride+ihi] = 0 53 } 54 test := dlahqrTest{ 55 h: h, 56 ilo: ilo, 57 ihi: ihi, 58 iloz: iloz, 59 ihiz: ihiz, 60 wantt: wantt, 61 wantz: wantz, 62 } 63 testDlahqr(t, impl, test) 64 } 65 } 66 } 67 } 68 } 69 // Tests that make sure that some potentially problematic corner cases, 70 // like zero-sized matrix, are covered. 71 for _, wantt := range []bool{true, false} { 72 for _, wantz := range []bool{true, false} { 73 for _, extra := range []int{0, 1, 11} { 74 for _, test := range []dlahqrTest{ 75 { 76 h: randomHessenberg(0, extra, rnd), 77 ilo: 0, 78 ihi: -1, 79 iloz: 0, 80 ihiz: -1, 81 }, 82 { 83 h: randomHessenberg(1, 1+extra, rnd), 84 ilo: 0, 85 ihi: 0, 86 iloz: 0, 87 ihiz: 0, 88 }, 89 { 90 h: randomHessenberg(2, 2+extra, rnd), 91 ilo: 1, 92 ihi: 1, 93 iloz: 1, 94 ihiz: 1, 95 }, 96 { 97 h: randomHessenberg(2, 2+extra, rnd), 98 ilo: 0, 99 ihi: 1, 100 iloz: 0, 101 ihiz: 1, 102 }, 103 { 104 h: randomHessenberg(10, 10+extra, rnd), 105 ilo: 0, 106 ihi: 0, 107 iloz: 0, 108 ihiz: 0, 109 }, 110 { 111 h: randomHessenberg(10, 10+extra, rnd), 112 ilo: 0, 113 ihi: 9, 114 iloz: 0, 115 ihiz: 9, 116 }, 117 { 118 h: randomHessenberg(10, 10+extra, rnd), 119 ilo: 0, 120 ihi: 1, 121 iloz: 0, 122 ihiz: 1, 123 }, 124 { 125 h: randomHessenberg(10, 10+extra, rnd), 126 ilo: 0, 127 ihi: 1, 128 iloz: 0, 129 ihiz: 9, 130 }, 131 { 132 h: randomHessenberg(10, 10+extra, rnd), 133 ilo: 9, 134 ihi: 9, 135 iloz: 0, 136 ihiz: 9, 137 }, 138 } { 139 if test.ilo-1 >= 0 { 140 test.h.Data[test.ilo*test.h.Stride+test.ilo-1] = 0 141 } 142 if test.ihi+1 < test.h.Rows { 143 test.h.Data[(test.ihi+1)*test.h.Stride+test.ihi] = 0 144 } 145 test.wantt = wantt 146 test.wantz = wantz 147 testDlahqr(t, impl, test) 148 } 149 } 150 } 151 } 152 153 // Tests with explicit eigenvalues computed by Octave. 154 for _, test := range []dlahqrTest{ 155 { 156 h: blas64.General{ 157 Rows: 1, 158 Cols: 1, 159 Stride: 1, 160 Data: []float64{7.09965484086874e-1}, 161 }, 162 ilo: 0, 163 ihi: 0, 164 iloz: 0, 165 ihiz: 0, 166 evWant: []complex128{7.09965484086874e-1}, 167 }, 168 { 169 h: blas64.General{ 170 Rows: 2, 171 Cols: 2, 172 Stride: 2, 173 Data: []float64{ 174 0, -1, 175 1, 0, 176 }, 177 }, 178 ilo: 0, 179 ihi: 1, 180 iloz: 0, 181 ihiz: 1, 182 evWant: []complex128{1i, -1i}, 183 }, 184 { 185 h: blas64.General{ 186 Rows: 2, 187 Cols: 2, 188 Stride: 2, 189 Data: []float64{ 190 6.25219991450918e-1, 8.17510791994361e-1, 191 3.31218891622294e-1, 1.24103744878131e-1, 192 }, 193 }, 194 ilo: 0, 195 ihi: 1, 196 iloz: 0, 197 ihiz: 1, 198 evWant: []complex128{9.52203547663447e-1, -2.02879811334398e-1}, 199 }, 200 { 201 h: blas64.General{ 202 Rows: 4, 203 Cols: 4, 204 Stride: 4, 205 Data: []float64{ 206 1, 0, 0, 0, 207 0, 6.25219991450918e-1, 8.17510791994361e-1, 0, 208 0, 3.31218891622294e-1, 1.24103744878131e-1, 0, 209 0, 0, 0, 1, 210 }, 211 }, 212 ilo: 1, 213 ihi: 2, 214 iloz: 0, 215 ihiz: 3, 216 evWant: []complex128{9.52203547663447e-1, -2.02879811334398e-1}, 217 }, 218 { 219 h: blas64.General{ 220 Rows: 2, 221 Cols: 2, 222 Stride: 2, 223 Data: []float64{ 224 -1.1219562276608, 6.85473513349362e-1, 225 -8.19951061145131e-1, 1.93728523178888e-1, 226 }, 227 }, 228 ilo: 0, 229 ihi: 1, 230 iloz: 0, 231 ihiz: 1, 232 evWant: []complex128{ 233 -4.64113852240958e-1 + 3.59580510817350e-1i, 234 -4.64113852240958e-1 - 3.59580510817350e-1i, 235 }, 236 }, 237 { 238 h: blas64.General{ 239 Rows: 5, 240 Cols: 5, 241 Stride: 5, 242 Data: []float64{ 243 9.57590178533658e-1, -5.10651295522708e-1, 9.24974510015869e-1, -1.30016306879522e-1, 2.92601986926954e-2, 244 -1.08084756637964, 1.77529701001213, -1.36480197632509, 2.23196371219601e-1, 1.12912853063308e-1, 245 0, -8.44075612174676e-1, 1.067867614486, -2.55782915176399e-1, -2.00598563137468e-1, 246 0, 0, -5.67097237165410e-1, 2.07205057427341e-1, 6.54998340743380e-1, 247 0, 0, 0, -1.89441413886041e-1, -4.18125416021786e-1, 248 }, 249 }, 250 ilo: 0, 251 ihi: 4, 252 iloz: 0, 253 ihiz: 4, 254 evWant: []complex128{ 255 2.94393309555622, 256 4.97029793606701e-1 + 3.63041654992384e-1i, 257 4.97029793606701e-1 - 3.63041654992384e-1i, 258 -1.74079119166145e-1 + 2.01570009462092e-1i, 259 -1.74079119166145e-1 - 2.01570009462092e-1i, 260 }, 261 }, 262 } { 263 test.wantt = true 264 test.wantz = true 265 testDlahqr(t, impl, test) 266 } 267 } 268 269 func testDlahqr(t *testing.T, impl Dlahqrer, test dlahqrTest) { 270 const tol = 1e-14 271 272 h := cloneGeneral(test.h) 273 n := h.Cols 274 extra := h.Stride - h.Cols 275 wantt := test.wantt 276 wantz := test.wantz 277 ilo := test.ilo 278 ihi := test.ihi 279 iloz := test.iloz 280 ihiz := test.ihiz 281 282 var z, zCopy blas64.General 283 if wantz { 284 z = eye(n, n+extra) 285 zCopy = cloneGeneral(z) 286 } 287 288 wr := nanSlice(ihi + 1) 289 wi := nanSlice(ihi + 1) 290 291 unconverged := impl.Dlahqr(wantt, wantz, n, ilo, ihi, h.Data, h.Stride, wr, wi, iloz, ihiz, z.Data, z.Stride) 292 293 prefix := fmt.Sprintf("Case wantt=%v, wantz=%v, n=%v, ilo=%v, ihi=%v, iloz=%v, ihiz=%v, extra=%v", 294 wantt, wantz, n, ilo, ihi, iloz, ihiz, extra) 295 296 if !generalOutsideAllNaN(h) { 297 t.Errorf("%v: out-of-range write to H\n%v", prefix, h.Data) 298 } 299 if !generalOutsideAllNaN(z) { 300 t.Errorf("%v: out-of-range write to Z\n%v", prefix, z.Data) 301 } 302 303 if !isUpperHessenberg(h) { 304 t.Logf("%v: H is not Hessenberg", prefix) 305 } 306 307 start := ilo // Index of the first computed eigenvalue. 308 if unconverged != 0 { 309 start = unconverged 310 if start == ihi+1 { 311 t.Logf("%v: no eigenvalue has converged", prefix) 312 } 313 } 314 315 // Check that wr and wi have not been modified in [:start]. 316 if !isAllNaN(wr[:start]) { 317 t.Errorf("%v: unexpected modification of wr", prefix) 318 } 319 if !isAllNaN(wi[:start]) { 320 t.Errorf("%v: unexpected modification of wi", prefix) 321 } 322 323 var hasReal bool 324 for i := start; i <= ihi; { 325 if wi[i] == 0 { // Real eigenvalue. 326 hasReal = true 327 // Check that the eigenvalue corresponds to a 1×1 block 328 // on the diagonal of H. 329 if wantt { 330 if wr[i] != h.Data[i*h.Stride+i] { 331 t.Errorf("%v: wr[%v] != H[%v,%v]", prefix, i, i, i) 332 } 333 for _, index := range []struct{ r, c int }{ 334 {i, i - 1}, // h h h 335 {i + 1, i - 1}, // 0 wr[i] h 336 {i + 1, i}, // 0 0 h 337 } { 338 if index.r >= n || index.c < 0 { 339 continue 340 } 341 if h.Data[index.r*h.Stride+index.c] != 0 { 342 t.Errorf("%v: H[%v,%v] != 0", prefix, index.r, index.c) 343 } 344 } 345 } 346 i++ 347 continue 348 } 349 350 // Complex eigenvalue. 351 352 // In the conjugate pair the real parts must be equal. 353 if wr[i] != wr[i+1] { 354 t.Errorf("%v: real part of conjugate pair not equal, i=%v", prefix, i) 355 } 356 // The first imaginary part must be positive. 357 if wi[i] < 0 { 358 t.Errorf("%v: wi[%v] not positive", prefix, i) 359 } 360 // The second imaginary part must be negative with the same 361 // magnitude. 362 if wi[i] != -wi[i+1] { 363 t.Errorf("%v: wi[%v] != -wi[%v]", prefix, i, i+1) 364 } 365 if wantt { 366 // Check that wi[i] has the correct value. 367 if wr[i] != h.Data[i*h.Stride+i] { 368 t.Errorf("%v: wr[%v] != H[%v,%v]", prefix, i, i, i) 369 } 370 if wr[i] != h.Data[(i+1)*h.Stride+i+1] { 371 t.Errorf("%v: wr[%v] != H[%v,%v]", prefix, i, i+1, i+1) 372 } 373 prod := math.Abs(h.Data[(i+1)*h.Stride+i] * h.Data[i*h.Stride+i+1]) 374 if math.Abs(math.Sqrt(prod)-wi[i]) > tol { 375 t.Errorf("%v: unexpected value of wi[%v]: want %v, got %v", prefix, i, math.Sqrt(prod), wi[i]) 376 } 377 378 // Check that the corresponding diagonal block is 2×2. 379 for _, index := range []struct{ r, c int }{ 380 {i, i - 1}, // i 381 {i + 1, i - 1}, // h h h h 382 {i + 2, i - 1}, // 0 wr[i] b h i 383 {i + 2, i}, // 0 c wr[i+1] h 384 {i + 2, i + 1}, // 0 0 0 h 385 } { 386 if index.r >= n || index.c < 0 { 387 continue 388 } 389 if h.Data[index.r*h.Stride+index.c] != 0 { 390 t.Errorf("%v: H[%v,%v] != 0", prefix, index.r, index.c) 391 } 392 } 393 } 394 i += 2 395 } 396 // If the number of found eigenvalues is odd, at least one must be real. 397 if (ihi+1-start)%2 != 0 && !hasReal { 398 t.Errorf("%v: expected at least one real eigenvalue", prefix) 399 } 400 401 // Compare found eigenvalues to the reference, if known. 402 if test.evWant != nil { 403 for i := start; i <= ihi; i++ { 404 ev := complex(wr[i], wi[i]) 405 found, _ := containsComplex(test.evWant, ev, tol) 406 if !found { 407 t.Errorf("%v: unexpected eigenvalue %v", prefix, ev) 408 } 409 } 410 } 411 412 if !wantz { 413 return 414 } 415 416 // Z should contain the orthogonal matrix U. 417 if !isOrthonormal(z) { 418 t.Errorf("%v: Z is not orthogonal", prefix) 419 } 420 // Z should have been modified only in the 421 // [iloz:ihiz+1,ilo:ihi+1] block. 422 for i := 0; i < n; i++ { 423 for j := 0; j < n; j++ { 424 if iloz <= i && i <= ihiz && ilo <= j && j <= ihi { 425 continue 426 } 427 if z.Data[i*z.Stride+j] != zCopy.Data[i*zCopy.Stride+j] { 428 t.Errorf("%v: Z modified outside of [iloz:ihiz+1,ilo:ihi+1] block", prefix) 429 } 430 } 431 } 432 if wantt { 433 hu := eye(n, n) 434 blas64.Gemm(blas.NoTrans, blas.NoTrans, 1, test.h, z, 0, hu) 435 uhu := eye(n, n) 436 blas64.Gemm(blas.Trans, blas.NoTrans, 1, z, hu, 0, uhu) 437 if !equalApproxGeneral(uhu, h, 10*tol) { 438 t.Errorf("%v: Z^T*(initial H)*Z and (final H) are not equal", prefix) 439 } 440 } 441 }