gonum.org/v1/gonum@v0.14.0/lapack/testlapack/dlaqr23.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  	"testing"
    10  
    11  	"golang.org/x/exp/rand"
    12  
    13  	"gonum.org/v1/gonum/blas"
    14  	"gonum.org/v1/gonum/blas/blas64"
    15  	"gonum.org/v1/gonum/lapack"
    16  )
    17  
    18  type Dlaqr23er interface {
    19  	Dlaqr23(wantt, wantz bool, n, ktop, kbot, nw int, h []float64, ldh int, iloz, ihiz int, z []float64, ldz int, sr, si []float64, v []float64, ldv int, nh int, t []float64, ldt int, nv int, wv []float64, ldwv int, work []float64, lwork int, recur int) (ns, nd int)
    20  }
    21  
    22  type dlaqr23Test struct {
    23  	wantt, wantz bool
    24  	ktop, kbot   int
    25  	nw           int
    26  	h            blas64.General
    27  	iloz, ihiz   int
    28  
    29  	evWant []complex128 // Optional slice with known eigenvalues.
    30  }
    31  
    32  func newDlaqr23TestCase(wantt, wantz bool, n, ldh int, rnd *rand.Rand) dlaqr23Test {
    33  	// Generate the deflation window size.
    34  	var nw int
    35  	if n <= 75 {
    36  		// For small matrices any window size works because they will
    37  		// always use Dlahrq inside Dlaqr23.
    38  		nw = rnd.Intn(n) + 1
    39  	} else {
    40  		// For sufficiently large matrices generate a large enough
    41  		// window to assure that the Dlaqr4 path is taken.
    42  		nw = 76 + rnd.Intn(n-75)
    43  	}
    44  	// Generate a random Hessenberg matrix.
    45  	h := randomHessenberg(n, ldh, rnd)
    46  	// Generate the block limits of H on which Dlaqr23 will operate so that
    47  	// the restriction
    48  	//  0 <= nw <= kbot-ktop+1
    49  	// is satisfied.
    50  	ktop := rnd.Intn(n - nw + 1)
    51  	kbot := ktop + nw - 1
    52  	kbot += rnd.Intn(n - kbot)
    53  	// Make the block isolated by zeroing out the sub-diagonal elements.
    54  	if ktop-1 >= 0 {
    55  		h.Data[ktop*h.Stride+ktop-1] = 0
    56  	}
    57  	if kbot+1 < n {
    58  		h.Data[(kbot+1)*h.Stride+kbot] = 0
    59  	}
    60  	// Generate the rows of Z to which transformations will be applied if
    61  	// wantz is true.
    62  	iloz := rnd.Intn(ktop + 1)
    63  	ihiz := kbot + rnd.Intn(n-kbot)
    64  	return dlaqr23Test{
    65  		wantt: wantt,
    66  		wantz: wantz,
    67  		ktop:  ktop,
    68  		kbot:  kbot,
    69  		nw:    nw,
    70  		h:     h,
    71  		iloz:  iloz,
    72  		ihiz:  ihiz,
    73  	}
    74  }
    75  
    76  func Dlaqr23Test(t *testing.T, impl Dlaqr23er) {
    77  	rnd := rand.New(rand.NewSource(1))
    78  
    79  	// Randomized tests.
    80  	for _, wantt := range []bool{true, false} {
    81  		for _, wantz := range []bool{true, false} {
    82  			for _, n := range []int{1, 2, 3, 4, 5, 6, 10, 18, 31, 100} {
    83  				for _, extra := range []int{0, 11} {
    84  					for cas := 0; cas < 10; cas++ {
    85  						test := newDlaqr23TestCase(wantt, wantz, n, n+extra, rnd)
    86  						testDlaqr23(t, impl, test, false, 1, rnd)
    87  						testDlaqr23(t, impl, test, true, 1, rnd)
    88  						testDlaqr23(t, impl, test, false, 0, rnd)
    89  						testDlaqr23(t, impl, test, true, 0, rnd)
    90  					}
    91  				}
    92  			}
    93  		}
    94  	}
    95  
    96  	// Tests with n=0.
    97  	for _, wantt := range []bool{true, false} {
    98  		for _, wantz := range []bool{true, false} {
    99  			for _, extra := range []int{0, 1, 11} {
   100  				test := dlaqr23Test{
   101  					wantt: wantt,
   102  					wantz: wantz,
   103  					h:     randomHessenberg(0, extra, rnd),
   104  					ktop:  0,
   105  					kbot:  -1,
   106  					iloz:  0,
   107  					ihiz:  -1,
   108  					nw:    0,
   109  				}
   110  				testDlaqr23(t, impl, test, true, 1, rnd)
   111  				testDlaqr23(t, impl, test, false, 1, rnd)
   112  				testDlaqr23(t, impl, test, true, 0, rnd)
   113  				testDlaqr23(t, impl, test, false, 0, rnd)
   114  			}
   115  		}
   116  	}
   117  
   118  	// Tests with explicit eigenvalues computed by Octave.
   119  	for _, test := range []dlaqr23Test{
   120  		{
   121  			h: blas64.General{
   122  				Rows:   1,
   123  				Cols:   1,
   124  				Stride: 1,
   125  				Data:   []float64{7.09965484086874e-1},
   126  			},
   127  			ktop:   0,
   128  			kbot:   0,
   129  			iloz:   0,
   130  			ihiz:   0,
   131  			evWant: []complex128{7.09965484086874e-1},
   132  		},
   133  		{
   134  			h: blas64.General{
   135  				Rows:   2,
   136  				Cols:   2,
   137  				Stride: 2,
   138  				Data: []float64{
   139  					0, -1,
   140  					1, 0,
   141  				},
   142  			},
   143  			ktop:   0,
   144  			kbot:   1,
   145  			iloz:   0,
   146  			ihiz:   1,
   147  			evWant: []complex128{1i, -1i},
   148  		},
   149  		{
   150  			h: blas64.General{
   151  				Rows:   2,
   152  				Cols:   2,
   153  				Stride: 2,
   154  				Data: []float64{
   155  					6.25219991450918e-1, 8.17510791994361e-1,
   156  					3.31218891622294e-1, 1.24103744878131e-1,
   157  				},
   158  			},
   159  			ktop:   0,
   160  			kbot:   1,
   161  			iloz:   0,
   162  			ihiz:   1,
   163  			evWant: []complex128{9.52203547663447e-1, -2.02879811334398e-1},
   164  		},
   165  		{
   166  			h: blas64.General{
   167  				Rows:   4,
   168  				Cols:   4,
   169  				Stride: 4,
   170  				Data: []float64{
   171  					1, 0, 0, 0,
   172  					0, 6.25219991450918e-1, 8.17510791994361e-1, 0,
   173  					0, 3.31218891622294e-1, 1.24103744878131e-1, 0,
   174  					0, 0, 0, 1,
   175  				},
   176  			},
   177  			ktop:   1,
   178  			kbot:   2,
   179  			iloz:   0,
   180  			ihiz:   3,
   181  			evWant: []complex128{9.52203547663447e-1, -2.02879811334398e-1},
   182  		},
   183  		{
   184  			h: blas64.General{
   185  				Rows:   2,
   186  				Cols:   2,
   187  				Stride: 2,
   188  				Data: []float64{
   189  					-1.1219562276608, 6.85473513349362e-1,
   190  					-8.19951061145131e-1, 1.93728523178888e-1,
   191  				},
   192  			},
   193  			ktop: 0,
   194  			kbot: 1,
   195  			iloz: 0,
   196  			ihiz: 1,
   197  			evWant: []complex128{
   198  				-4.64113852240958e-1 + 3.59580510817350e-1i,
   199  				-4.64113852240958e-1 - 3.59580510817350e-1i,
   200  			},
   201  		},
   202  		{
   203  			h: blas64.General{
   204  				Rows:   5,
   205  				Cols:   5,
   206  				Stride: 5,
   207  				Data: []float64{
   208  					9.57590178533658e-1, -5.10651295522708e-1, 9.24974510015869e-1, -1.30016306879522e-1, 2.92601986926954e-2,
   209  					-1.08084756637964, 1.77529701001213, -1.36480197632509, 2.23196371219601e-1, 1.12912853063308e-1,
   210  					0, -8.44075612174676e-1, 1.067867614486, -2.55782915176399e-1, -2.00598563137468e-1,
   211  					0, 0, -5.67097237165410e-1, 2.07205057427341e-1, 6.54998340743380e-1,
   212  					0, 0, 0, -1.89441413886041e-1, -4.18125416021786e-1,
   213  				},
   214  			},
   215  			ktop: 0,
   216  			kbot: 4,
   217  			iloz: 0,
   218  			ihiz: 4,
   219  			evWant: []complex128{
   220  				2.94393309555622,
   221  				4.97029793606701e-1 + 3.63041654992384e-1i,
   222  				4.97029793606701e-1 - 3.63041654992384e-1i,
   223  				-1.74079119166145e-1 + 2.01570009462092e-1i,
   224  				-1.74079119166145e-1 - 2.01570009462092e-1i,
   225  			},
   226  		},
   227  	} {
   228  		test.wantt = true
   229  		test.wantz = true
   230  		test.nw = test.kbot - test.ktop + 1
   231  		testDlaqr23(t, impl, test, true, 1, rnd)
   232  		testDlaqr23(t, impl, test, false, 1, rnd)
   233  		testDlaqr23(t, impl, test, true, 0, rnd)
   234  		testDlaqr23(t, impl, test, false, 0, rnd)
   235  	}
   236  }
   237  
   238  func testDlaqr23(t *testing.T, impl Dlaqr23er, test dlaqr23Test, opt bool, recur int, rnd *rand.Rand) {
   239  	const tol = 1e-14
   240  
   241  	// Clone the test matrix to avoid modifying test data.
   242  	h := cloneGeneral(test.h)
   243  	// Extract test values to simplify notation.
   244  	n := h.Cols
   245  	extra := h.Stride - h.Cols
   246  	wantt := test.wantt
   247  	wantz := test.wantz
   248  	ktop := test.ktop
   249  	kbot := test.kbot
   250  	nw := test.nw
   251  	iloz := test.iloz
   252  	ihiz := test.ihiz
   253  
   254  	var z, zCopy blas64.General
   255  	if wantz {
   256  		// Using the identity matrix for Z is the easiest way to check
   257  		// that the transformation accumulated into it by Dlaqr23 is orthogonal.
   258  		z = eye(n, n+extra)
   259  		zCopy = cloneGeneral(z)
   260  	}
   261  
   262  	// Allocate slices for storing the converged eigenvalues, initially
   263  	// filled with NaN.
   264  	sr := nanSlice(kbot + 1)
   265  	si := nanSlice(kbot + 1)
   266  
   267  	// Allocate work matrices.
   268  	v := randomGeneral(nw, nw, nw+extra, rnd)
   269  	var nh int
   270  	if nw > 0 {
   271  		nh = nw + rnd.Intn(nw) // nh must be at least nw.
   272  	}
   273  	tmat := randomGeneral(nw, nh, nh+extra, rnd)
   274  	var nv int
   275  	if nw > 0 {
   276  		nv = rnd.Intn(nw) + 1
   277  	}
   278  	wv := randomGeneral(nv, nw, nw+extra, rnd)
   279  
   280  	var work []float64
   281  	if opt {
   282  		// Allocate work slice with optimal length.
   283  		work = nanSlice(1)
   284  		impl.Dlaqr23(wantt, wantz, n, ktop, kbot, nw, h.Data, h.Stride, iloz, ihiz, z.Data, max(1, z.Stride),
   285  			sr, si, v.Data, v.Stride, tmat.Cols, tmat.Data, tmat.Stride, wv.Rows, wv.Data, wv.Stride, work, -1, recur)
   286  		work = nanSlice(int(work[0]))
   287  	} else {
   288  		// Allocate work slice with minimum length.
   289  		work = nanSlice(max(1, 2*nw))
   290  	}
   291  
   292  	ns, nd := impl.Dlaqr23(wantt, wantz, n, ktop, kbot, nw, h.Data, h.Stride, iloz, ihiz, z.Data, max(1, z.Stride),
   293  		sr, si, v.Data, v.Stride, tmat.Cols, tmat.Data, tmat.Stride, wv.Rows, wv.Data, wv.Stride, work, len(work), recur)
   294  
   295  	prefix := fmt.Sprintf("Case wantt=%v, wantz=%v, n=%v, ktop=%v, kbot=%v, nw=%v, iloz=%v, ihiz=%v, extra=%v",
   296  		wantt, wantz, n, ktop, kbot, nw, iloz, ihiz, extra)
   297  
   298  	if !generalOutsideAllNaN(h) {
   299  		t.Errorf("%v: out-of-range write to H\n%v", prefix, h.Data)
   300  	}
   301  	if !generalOutsideAllNaN(z) {
   302  		t.Errorf("%v: out-of-range write to Z\n%v", prefix, z.Data)
   303  	}
   304  	if !generalOutsideAllNaN(v) {
   305  		t.Errorf("%v: out-of-range write to V\n%v", prefix, v.Data)
   306  	}
   307  	if !generalOutsideAllNaN(tmat) {
   308  		t.Errorf("%v: out-of-range write to T\n%v", prefix, tmat.Data)
   309  	}
   310  	if !generalOutsideAllNaN(wv) {
   311  		t.Errorf("%v: out-of-range write to WV\n%v", prefix, wv.Data)
   312  	}
   313  	if !isAllNaN(sr[:kbot-nd-ns+1]) || !isAllNaN(sr[kbot+1:]) {
   314  		t.Errorf("%v: out-of-range write to sr", prefix)
   315  	}
   316  	if !isAllNaN(si[:kbot-nd-ns+1]) || !isAllNaN(si[kbot+1:]) {
   317  		t.Errorf("%v: out-of-range write to si", prefix)
   318  	}
   319  
   320  	if !isUpperHessenberg(h) {
   321  		t.Errorf("%v: H is not upper Hessenberg", prefix)
   322  	}
   323  
   324  	if test.evWant != nil {
   325  		// Check all converged eigenvalues against known eigenvalues.
   326  		for i := kbot - nd + 1; i <= kbot; i++ {
   327  			ev := complex(sr[i], si[i])
   328  			found, _ := containsComplex(test.evWant, ev, tol)
   329  			if !found {
   330  				t.Errorf("%v: unexpected eigenvalue %v", prefix, ev)
   331  			}
   332  		}
   333  	}
   334  
   335  	// Checks below need the matrix Z.
   336  	if !wantz {
   337  		return
   338  	}
   339  
   340  	// Test whether the matrix Z was modified outside the given block.
   341  	var zmod bool
   342  	for i := 0; i < n; i++ {
   343  		for j := 0; j < n; j++ {
   344  			if z.Data[i*z.Stride+j] == zCopy.Data[i*zCopy.Stride+j] {
   345  				continue
   346  			}
   347  			if i < iloz || ihiz < i || j < kbot-nw+1 || kbot < j {
   348  				zmod = true
   349  			}
   350  		}
   351  	}
   352  	if zmod {
   353  		t.Errorf("%v: unexpected modification of Z", prefix)
   354  	}
   355  	// Check that Z is orthogonal.
   356  	if resid := residualOrthogonal(z, false); resid > tol*float64(n) {
   357  		t.Errorf("Case %v: Z is not orthogonal; resid=%v, want<=%v", prefix, resid, tol*float64(n))
   358  	}
   359  	if wantt {
   360  		// Check that |Zᵀ*HOrig*Z - H| is small where H is the result from Dlaqr23.
   361  		hz := zeros(n, n, n)
   362  		blas64.Gemm(blas.NoTrans, blas.NoTrans, 1, test.h, z, 0, hz)
   363  		r := cloneGeneral(h)
   364  		blas64.Gemm(blas.Trans, blas.NoTrans, 1, z, hz, -1, r)
   365  		resid := dlange(lapack.MaxColumnSum, r.Rows, r.Cols, r.Data, r.Stride)
   366  		if resid > tol*float64(n) {
   367  			t.Errorf("%v: |Zᵀ*(initial H)*Z - (final H)|=%v, want<=%v", prefix, resid, tol*float64(n))
   368  		}
   369  	}
   370  }