mirror of
				https://github.com/gonum/gonum.git
				synced 2025-10-31 18:42:45 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			443 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			443 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright ©2016 The Gonum Authors. All rights reserved.
 | |
| // Use of this source code is governed by a BSD-style
 | |
| // license that can be found in the LICENSE file.
 | |
| 
 | |
| package testlapack
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"math"
 | |
| 	"testing"
 | |
| 
 | |
| 	"golang.org/x/exp/rand"
 | |
| 
 | |
| 	"gonum.org/v1/gonum/blas"
 | |
| 	"gonum.org/v1/gonum/blas/blas64"
 | |
| 	"gonum.org/v1/gonum/lapack"
 | |
| )
 | |
| 
 | |
| type Dtrevc3er interface {
 | |
| 	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
 | |
| }
 | |
| 
 | |
| func Dtrevc3Test(t *testing.T, impl Dtrevc3er) {
 | |
| 	rnd := rand.New(rand.NewSource(1))
 | |
| 
 | |
| 	for _, side := range []lapack.EVSide{lapack.EVRight, lapack.EVLeft, lapack.EVBoth} {
 | |
| 		var name string
 | |
| 		switch side {
 | |
| 		case lapack.EVRight:
 | |
| 			name = "EVRigth"
 | |
| 		case lapack.EVLeft:
 | |
| 			name = "EVLeft"
 | |
| 		case lapack.EVBoth:
 | |
| 			name = "EVBoth"
 | |
| 		}
 | |
| 		t.Run(name, func(t *testing.T) {
 | |
| 			runDtrevc3Test(t, impl, rnd, side)
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func runDtrevc3Test(t *testing.T, impl Dtrevc3er, rnd *rand.Rand, side lapack.EVSide) {
 | |
| 	for _, n := range []int{0, 1, 2, 3, 4, 5, 6, 7, 10, 34} {
 | |
| 		for _, extra := range []int{0, 11} {
 | |
| 			for _, optwork := range []bool{true, false} {
 | |
| 				for cas := 0; cas < 10; cas++ {
 | |
| 					dtrevc3Test(t, impl, side, n, extra, optwork, rnd)
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // dtrevc3Test tests Dtrevc3 by generating a random matrix T in Schur canonical
 | |
| // form and performing the following checks:
 | |
| //  1. Compute all eigenvectors of T and check that they are indeed correctly
 | |
| //     normalized eigenvectors
 | |
| //  2. Compute selected eigenvectors and check that they are exactly equal to
 | |
| //     eigenvectors from check 1.
 | |
| //  3. Compute all eigenvectors multiplied into a matrix Q and check that the
 | |
| //     result is equal to eigenvectors from step 1 multiplied by Q and scaled
 | |
| //     appropriately.
 | |
| func dtrevc3Test(t *testing.T, impl Dtrevc3er, side lapack.EVSide, n, extra int, optwork bool, rnd *rand.Rand) {
 | |
| 	const tol = 1e-15
 | |
| 
 | |
| 	name := fmt.Sprintf("n=%d,extra=%d,optwk=%v", n, extra, optwork)
 | |
| 
 | |
| 	right := side != lapack.EVLeft
 | |
| 	left := side != lapack.EVRight
 | |
| 
 | |
| 	// Generate a random matrix in Schur canonical form possibly with tiny or zero eigenvalues.
 | |
| 	// Zero elements of wi signify a real eigenvalue.
 | |
| 	tmat, wr, wi := randomSchurCanonical(n, n+extra, true, rnd)
 | |
| 	tmatCopy := cloneGeneral(tmat)
 | |
| 
 | |
| 	//  1. Compute all eigenvectors of T and check that they are indeed correctly
 | |
| 	//     normalized eigenvectors
 | |
| 
 | |
| 	howmny := lapack.EVAll
 | |
| 
 | |
| 	var vr, vl blas64.General
 | |
| 	if right {
 | |
| 		// Fill VR and VL with NaN because they should be completely overwritten in Dtrevc3.
 | |
| 		vr = nanGeneral(n, n, n+extra)
 | |
| 	}
 | |
| 	if left {
 | |
| 		vl = nanGeneral(n, n, n+extra)
 | |
| 	}
 | |
| 
 | |
| 	var work []float64
 | |
| 	if optwork {
 | |
| 		work = []float64{0}
 | |
| 		impl.Dtrevc3(side, howmny, nil, n, tmat.Data, tmat.Stride,
 | |
| 			vl.Data, max(1, vl.Stride), vr.Data, max(1, vr.Stride), n, work, -1)
 | |
| 		work = make([]float64, int(work[0]))
 | |
| 	} else {
 | |
| 		work = make([]float64, max(1, 3*n))
 | |
| 	}
 | |
| 
 | |
| 	mGot := impl.Dtrevc3(side, howmny, nil, n, tmat.Data, tmat.Stride,
 | |
| 		vl.Data, max(1, vl.Stride), vr.Data, max(1, vr.Stride), n, work, len(work))
 | |
| 
 | |
| 	if !generalOutsideAllNaN(tmat) {
 | |
| 		t.Errorf("%v: out-of-range write to T", name)
 | |
| 	}
 | |
| 	if !equalGeneral(tmat, tmatCopy) {
 | |
| 		t.Errorf("%v: unexpected modification of T", name)
 | |
| 	}
 | |
| 	if !generalOutsideAllNaN(vr) {
 | |
| 		t.Errorf("%v: out-of-range write to VR", name)
 | |
| 	}
 | |
| 	if !generalOutsideAllNaN(vl) {
 | |
| 		t.Errorf("%v: out-of-range write to VL", name)
 | |
| 	}
 | |
| 
 | |
| 	mWant := n
 | |
| 	if mGot != mWant {
 | |
| 		t.Errorf("%v: unexpected value of m=%d, want %d", name, mGot, mWant)
 | |
| 	}
 | |
| 
 | |
| 	if right {
 | |
| 		resid := residualRightEV(tmat, vr, wr, wi)
 | |
| 		if resid > tol {
 | |
| 			t.Errorf("%v: unexpected right eigenvectors; residual=%v, want<=%v", name, resid, tol)
 | |
| 		}
 | |
| 		resid = residualEVNormalization(vr, wi)
 | |
| 		if resid > tol {
 | |
| 			t.Errorf("%v: unexpected normalization of right eigenvectors; residual=%v, want<=%v", name, resid, tol)
 | |
| 		}
 | |
| 	}
 | |
| 	if left {
 | |
| 		resid := residualLeftEV(tmat, vl, wr, wi)
 | |
| 		if resid > tol {
 | |
| 			t.Errorf("%v: unexpected left eigenvectors; residual=%v, want<=%v", name, resid, tol)
 | |
| 		}
 | |
| 		resid = residualEVNormalization(vl, wi)
 | |
| 		if resid > tol {
 | |
| 			t.Errorf("%v: unexpected normalization of left eigenvectors; residual=%v, want<=%v", name, resid, tol)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	//  2. Compute selected eigenvectors and check that they are exactly equal to
 | |
| 	//     eigenvectors from check 1.
 | |
| 
 | |
| 	howmny = lapack.EVSelected
 | |
| 
 | |
| 	// Follow DCHKHS and select last max(1,n/4) real, max(1,n/4) complex
 | |
| 	// eigenvectors instead of selecting them randomly.
 | |
| 	selected := make([]bool, n)
 | |
| 	selectedWant := make([]bool, n)
 | |
| 	var nselr, nselc int
 | |
| 	for j := n - 1; j > 0; {
 | |
| 		if wi[j] == 0 {
 | |
| 			if nselr < max(1, n/4) {
 | |
| 				nselr++
 | |
| 				selected[j] = true
 | |
| 				selectedWant[j] = true
 | |
| 			}
 | |
| 			j--
 | |
| 		} else {
 | |
| 			if nselc < max(1, n/4) {
 | |
| 				nselc++
 | |
| 				// Select all columns to check that Dtrevc3 normalizes 'selected' correctly.
 | |
| 				selected[j] = true
 | |
| 				selected[j-1] = true
 | |
| 				selectedWant[j] = false
 | |
| 				selectedWant[j-1] = true
 | |
| 			}
 | |
| 			j -= 2
 | |
| 		}
 | |
| 	}
 | |
| 	mWant = nselr + 2*nselc
 | |
| 
 | |
| 	var vrSel, vlSel blas64.General
 | |
| 	if right {
 | |
| 		vrSel = nanGeneral(n, mWant, n+extra)
 | |
| 	}
 | |
| 	if left {
 | |
| 		vlSel = nanGeneral(n, mWant, n+extra)
 | |
| 	}
 | |
| 
 | |
| 	if optwork {
 | |
| 		// Reallocate optimal work in case it depends on howmny and selected.
 | |
| 		work = []float64{0}
 | |
| 		impl.Dtrevc3(side, howmny, selected, n, tmat.Data, tmat.Stride,
 | |
| 			vlSel.Data, max(1, vlSel.Stride), vrSel.Data, max(1, vrSel.Stride), mWant, work, -1)
 | |
| 		work = make([]float64, int(work[0]))
 | |
| 	}
 | |
| 
 | |
| 	mGot = impl.Dtrevc3(side, howmny, selected, n, tmat.Data, tmat.Stride,
 | |
| 		vlSel.Data, max(1, vlSel.Stride), vrSel.Data, max(1, vrSel.Stride), mWant, work, len(work))
 | |
| 
 | |
| 	if !generalOutsideAllNaN(tmat) {
 | |
| 		t.Errorf("%v: out-of-range write to T", name)
 | |
| 	}
 | |
| 	if !equalGeneral(tmat, tmatCopy) {
 | |
| 		t.Errorf("%v: unexpected modification of T", name)
 | |
| 	}
 | |
| 	if !generalOutsideAllNaN(vrSel) {
 | |
| 		t.Errorf("%v: out-of-range write to selected VR", name)
 | |
| 	}
 | |
| 	if !generalOutsideAllNaN(vlSel) {
 | |
| 		t.Errorf("%v: out-of-range write to selected VL", name)
 | |
| 	}
 | |
| 
 | |
| 	if mGot != mWant {
 | |
| 		t.Errorf("%v: unexpected value of selected m=%d, want %d", name, mGot, mWant)
 | |
| 	}
 | |
| 
 | |
| 	for i := range selected {
 | |
| 		if selected[i] != selectedWant[i] {
 | |
| 			t.Errorf("%v: unexpected selected[%v]", name, i)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Check that selected columns of vrSel are equal to the corresponding
 | |
| 	// columns of vr.
 | |
| 	var k int
 | |
| 	match := true
 | |
| 	if right {
 | |
| 	loopVR:
 | |
| 		for j := 0; j < n; j++ {
 | |
| 			if selected[j] && wi[j] == 0 {
 | |
| 				for i := 0; i < n; i++ {
 | |
| 					if vrSel.Data[i*vrSel.Stride+k] != vr.Data[i*vr.Stride+j] {
 | |
| 						match = false
 | |
| 						break loopVR
 | |
| 					}
 | |
| 				}
 | |
| 				k++
 | |
| 			} else if selected[j] && wi[j] != 0 {
 | |
| 				for i := 0; i < n; i++ {
 | |
| 					if vrSel.Data[i*vrSel.Stride+k] != vr.Data[i*vr.Stride+j] ||
 | |
| 						vrSel.Data[i*vrSel.Stride+k+1] != vr.Data[i*vr.Stride+j+1] {
 | |
| 						match = false
 | |
| 						break loopVR
 | |
| 					}
 | |
| 				}
 | |
| 				k += 2
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	if !match {
 | |
| 		t.Errorf("%v: unexpected selected VR", name)
 | |
| 	}
 | |
| 
 | |
| 	// Check that selected columns of vlSel are equal to the corresponding
 | |
| 	// columns of vl.
 | |
| 	match = true
 | |
| 	k = 0
 | |
| 	if left {
 | |
| 	loopVL:
 | |
| 		for j := 0; j < n; j++ {
 | |
| 			if selected[j] && wi[j] == 0 {
 | |
| 				for i := 0; i < n; i++ {
 | |
| 					if vlSel.Data[i*vlSel.Stride+k] != vl.Data[i*vl.Stride+j] {
 | |
| 						match = false
 | |
| 						break loopVL
 | |
| 					}
 | |
| 				}
 | |
| 				k++
 | |
| 			} else if selected[j] && wi[j] != 0 {
 | |
| 				for i := 0; i < n; i++ {
 | |
| 					if vlSel.Data[i*vlSel.Stride+k] != vl.Data[i*vl.Stride+j] ||
 | |
| 						vlSel.Data[i*vlSel.Stride+k+1] != vl.Data[i*vl.Stride+j+1] {
 | |
| 						match = false
 | |
| 						break loopVL
 | |
| 					}
 | |
| 				}
 | |
| 				k += 2
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	if !match {
 | |
| 		t.Errorf("%v: unexpected selected VL", name)
 | |
| 	}
 | |
| 
 | |
| 	//  3. Compute all eigenvectors multiplied into a matrix Q and check that the
 | |
| 	//     result is equal to eigenvectors from step 1 multiplied by Q and scaled
 | |
| 	//     appropriately.
 | |
| 
 | |
| 	howmny = lapack.EVAllMulQ
 | |
| 
 | |
| 	var vrMul, qr blas64.General
 | |
| 	var vlMul, ql blas64.General
 | |
| 	if right {
 | |
| 		vrMul = randomGeneral(n, n, n+extra, rnd)
 | |
| 		qr = cloneGeneral(vrMul)
 | |
| 	}
 | |
| 	if left {
 | |
| 		vlMul = randomGeneral(n, n, n+extra, rnd)
 | |
| 		ql = cloneGeneral(vlMul)
 | |
| 	}
 | |
| 
 | |
| 	if optwork {
 | |
| 		// Reallocate optimal work in case it depends on howmny and selected.
 | |
| 		work = []float64{0}
 | |
| 		impl.Dtrevc3(side, howmny, nil, n, tmat.Data, tmat.Stride,
 | |
| 			vlMul.Data, max(1, vlMul.Stride), vrMul.Data, max(1, vrMul.Stride), n, work, -1)
 | |
| 		work = make([]float64, int(work[0]))
 | |
| 	}
 | |
| 
 | |
| 	mGot = impl.Dtrevc3(side, howmny, selected, n, tmat.Data, tmat.Stride,
 | |
| 		vlMul.Data, max(1, vlMul.Stride), vrMul.Data, max(1, vrMul.Stride), n, work, len(work))
 | |
| 
 | |
| 	if !generalOutsideAllNaN(tmat) {
 | |
| 		t.Errorf("%v: out-of-range write to T", name)
 | |
| 	}
 | |
| 	if !equalGeneral(tmat, tmatCopy) {
 | |
| 		t.Errorf("%v: unexpected modification of T", name)
 | |
| 	}
 | |
| 	if !generalOutsideAllNaN(vrMul) {
 | |
| 		t.Errorf("%v: out-of-range write to VRMul", name)
 | |
| 	}
 | |
| 	if !generalOutsideAllNaN(vlMul) {
 | |
| 		t.Errorf("%v: out-of-range write to VLMul", name)
 | |
| 	}
 | |
| 
 | |
| 	mWant = n
 | |
| 	if mGot != mWant {
 | |
| 		t.Errorf("%v: unexpected value of m=%d, want %d", name, mGot, mWant)
 | |
| 	}
 | |
| 
 | |
| 	if right {
 | |
| 		// Compute Q * VR explicitly and normalize to match Dtrevc3 output.
 | |
| 		qvWant := zeros(n, n, n)
 | |
| 		blas64.Gemm(blas.NoTrans, blas.NoTrans, 1, qr, vr, 0, qvWant)
 | |
| 		normalizeEV(qvWant, wi)
 | |
| 
 | |
| 		// Compute the difference between Dtrevc3 output and Q * VR.
 | |
| 		r := zeros(n, n, n)
 | |
| 		for i := 0; i < n; i++ {
 | |
| 			for j := 0; j < n; j++ {
 | |
| 				r.Data[i*r.Stride+j] = vrMul.Data[i*vrMul.Stride+j] - qvWant.Data[i*qvWant.Stride+j]
 | |
| 			}
 | |
| 		}
 | |
| 		qvNorm := dlange(lapack.MaxColumnSum, n, n, qvWant.Data, qvWant.Stride)
 | |
| 		resid := dlange(lapack.MaxColumnSum, n, n, r.Data, r.Stride) / qvNorm / float64(n)
 | |
| 		if resid > tol {
 | |
| 			t.Errorf("%v: unexpected VRMul; resid=%v, want <=%v", name, resid, tol)
 | |
| 		}
 | |
| 	}
 | |
| 	if left {
 | |
| 		// Compute Q * VL explicitly and normalize to match Dtrevc3 output.
 | |
| 		qvWant := zeros(n, n, n)
 | |
| 		blas64.Gemm(blas.NoTrans, blas.NoTrans, 1, ql, vl, 0, qvWant)
 | |
| 		normalizeEV(qvWant, wi)
 | |
| 
 | |
| 		// Compute the difference between Dtrevc3 output and Q * VL.
 | |
| 		r := zeros(n, n, n)
 | |
| 		for i := 0; i < n; i++ {
 | |
| 			for j := 0; j < n; j++ {
 | |
| 				r.Data[i*r.Stride+j] = vlMul.Data[i*vlMul.Stride+j] - qvWant.Data[i*qvWant.Stride+j]
 | |
| 			}
 | |
| 		}
 | |
| 		qvNorm := dlange(lapack.MaxColumnSum, n, n, qvWant.Data, qvWant.Stride)
 | |
| 		resid := dlange(lapack.MaxColumnSum, n, n, r.Data, r.Stride) / qvNorm / float64(n)
 | |
| 		if resid > tol {
 | |
| 			t.Errorf("%v: unexpected VLMul; resid=%v, want <=%v", name, resid, tol)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // residualEVNormalization returns the maximum normalization error in E:
 | |
| //  max |max-norm(E[:,j]) - 1|
 | |
| func residualEVNormalization(emat blas64.General, wi []float64) float64 {
 | |
| 	n := emat.Rows
 | |
| 	if n == 0 {
 | |
| 		return 0
 | |
| 	}
 | |
| 	var (
 | |
| 		e      = emat.Data
 | |
| 		lde    = emat.Stride
 | |
| 		enrmin = math.Inf(1)
 | |
| 		enrmax float64
 | |
| 		ipair  int
 | |
| 	)
 | |
| 	for j := 0; j < n; j++ {
 | |
| 		if ipair == 0 && j < n-1 && wi[j] != 0 {
 | |
| 			ipair = 1
 | |
| 		}
 | |
| 		var nrm float64
 | |
| 		switch ipair {
 | |
| 		case 0:
 | |
| 			// Real eigenvector
 | |
| 			for i := 0; i < n; i++ {
 | |
| 				nrm = math.Max(nrm, math.Abs(e[i*lde+j]))
 | |
| 			}
 | |
| 			enrmin = math.Min(enrmin, nrm)
 | |
| 			enrmax = math.Max(enrmax, nrm)
 | |
| 		case 1:
 | |
| 			// Complex eigenvector
 | |
| 			for i := 0; i < n; i++ {
 | |
| 				nrm = math.Max(nrm, math.Abs(e[i*lde+j])+math.Abs(e[i*lde+j+1]))
 | |
| 			}
 | |
| 			enrmin = math.Min(enrmin, nrm)
 | |
| 			enrmax = math.Max(enrmax, nrm)
 | |
| 			ipair = 2
 | |
| 		case 2:
 | |
| 			ipair = 0
 | |
| 		}
 | |
| 	}
 | |
| 	return math.Max(math.Abs(enrmin-1), math.Abs(enrmin-1))
 | |
| }
 | |
| 
 | |
| // normalizeEV normalizes eigenvectors in the columns of E so that the element
 | |
| // of largest magnitude has magnitude 1.
 | |
| func normalizeEV(emat blas64.General, wi []float64) {
 | |
| 	n := emat.Rows
 | |
| 	if n == 0 {
 | |
| 		return
 | |
| 	}
 | |
| 	var (
 | |
| 		bi    = blas64.Implementation()
 | |
| 		e     = emat.Data
 | |
| 		lde   = emat.Stride
 | |
| 		ipair int
 | |
| 	)
 | |
| 	for j := 0; j < n; j++ {
 | |
| 		if ipair == 0 && j < n-1 && wi[j] != 0 {
 | |
| 			ipair = 1
 | |
| 		}
 | |
| 		switch ipair {
 | |
| 		case 0:
 | |
| 			// Real eigenvector
 | |
| 			ii := bi.Idamax(n, e[j:], lde)
 | |
| 			remax := 1 / math.Abs(e[ii*lde+j])
 | |
| 			bi.Dscal(n, remax, e[j:], lde)
 | |
| 		case 1:
 | |
| 			// Complex eigenvector
 | |
| 			var emax float64
 | |
| 			for i := 0; i < n; i++ {
 | |
| 				emax = math.Max(emax, math.Abs(e[i*lde+j])+math.Abs(e[i*lde+j+1]))
 | |
| 			}
 | |
| 			bi.Dscal(n, 1/emax, e[j:], lde)
 | |
| 			bi.Dscal(n, 1/emax, e[j+1:], lde)
 | |
| 			ipair = 2
 | |
| 		case 2:
 | |
| 			ipair = 0
 | |
| 		}
 | |
| 	}
 | |
| }
 | 
