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