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