gonum.org/v1/gonum@v0.14.0/lapack/testlapack/dsterf.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  	"sort"
    11  	"testing"
    12  
    13  	"golang.org/x/exp/rand"
    14  
    15  	"gonum.org/v1/gonum/blas"
    16  	"gonum.org/v1/gonum/blas/blas64"
    17  	"gonum.org/v1/gonum/floats"
    18  	"gonum.org/v1/gonum/lapack"
    19  )
    20  
    21  type Dsterfer interface {
    22  	Dsteqrer
    23  	Dlansyer
    24  	Dsterf(n int, d, e []float64) (ok bool)
    25  }
    26  
    27  func DsterfTest(t *testing.T, impl Dsterfer) {
    28  	const tol = 1e-14
    29  
    30  	// Tests with precomputed eigenvalues.
    31  	for cas, test := range []struct {
    32  		d []float64
    33  		e []float64
    34  		n int
    35  
    36  		want []float64
    37  	}{
    38  		{
    39  			d: []float64{1, 3, 4, 6},
    40  			e: []float64{2, 4, 5},
    41  			n: 4,
    42  			// Computed from original Fortran code.
    43  			want: []float64{11.046227528488854, 4.795922173417400, -2.546379458290125, 0.704229756383872},
    44  		},
    45  	} {
    46  		n := test.n
    47  		got := make([]float64, len(test.d))
    48  		copy(got, test.d)
    49  		e := make([]float64, len(test.e))
    50  		copy(e, test.e)
    51  		ok := impl.Dsterf(n, got, e)
    52  		if !ok {
    53  			t.Errorf("Case %d, n=%v: Dsterf failed", cas, n)
    54  			continue
    55  		}
    56  		want := make([]float64, len(test.want))
    57  		copy(want, test.want)
    58  		sort.Float64s(want)
    59  		diff := floats.Distance(got, want, math.Inf(1))
    60  		if diff > tol {
    61  			t.Errorf("Case %d, n=%v: unexpected result, |dGot-dWant|=%v", cas, n, diff)
    62  		}
    63  	}
    64  
    65  	rnd := rand.New(rand.NewSource(1))
    66  	// Probabilistic tests.
    67  	for _, n := range []int{0, 1, 2, 3, 4, 5, 6, 10, 50} {
    68  		for typ := 0; typ <= 8; typ++ {
    69  			d := make([]float64, n)
    70  			var e []float64
    71  			if n > 1 {
    72  				e = make([]float64, n-1)
    73  			}
    74  			// Generate a tridiagonal matrix A.
    75  			switch typ {
    76  			case 0:
    77  				// The zero matrix.
    78  			case 1:
    79  				// The identity matrix.
    80  				for i := range d {
    81  					d[i] = 1
    82  				}
    83  			case 2:
    84  				// A diagonal matrix with evenly spaced entries
    85  				// 1, ..., eps  and random signs.
    86  				for i := 0; i < n; i++ {
    87  					if i == 0 {
    88  						d[i] = 1
    89  					} else {
    90  						d[i] = 1 - (1-dlamchE)*float64(i)/float64(n-1)
    91  					}
    92  					if rnd.Float64() < 0.5 {
    93  						d[i] *= -1
    94  					}
    95  				}
    96  			case 3, 4, 5:
    97  				// A diagonal matrix with geometrically spaced entries
    98  				// 1, ..., eps  and random signs.
    99  				for i := 0; i < n; i++ {
   100  					if i == 0 {
   101  						d[i] = 1
   102  					} else {
   103  						d[i] = math.Pow(dlamchE, float64(i)/float64(n-1))
   104  					}
   105  					if rnd.Float64() < 0.5 {
   106  						d[i] *= -1
   107  					}
   108  				}
   109  				switch typ {
   110  				case 4:
   111  					// Multiply by SQRT(overflow threshold).
   112  					floats.Scale(math.Sqrt(1/dlamchS), d)
   113  				case 5:
   114  					// Multiply by SQRT(underflow threshold).
   115  					floats.Scale(math.Sqrt(dlamchS), d)
   116  				}
   117  			case 6:
   118  				// A diagonal matrix with "clustered" entries 1, eps, ..., eps
   119  				// and random signs.
   120  				for i := range d {
   121  					if i == 0 {
   122  						d[i] = 1
   123  					} else {
   124  						d[i] = dlamchE
   125  					}
   126  				}
   127  				for i := range d {
   128  					if rnd.Float64() < 0.5 {
   129  						d[i] *= -1
   130  					}
   131  				}
   132  			case 7:
   133  				// Diagonal matrix with random entries.
   134  				for i := range d {
   135  					d[i] = rnd.NormFloat64()
   136  				}
   137  			case 8:
   138  				// Random symmetric tridiagonal matrix.
   139  				for i := range d {
   140  					d[i] = rnd.NormFloat64()
   141  				}
   142  				for i := range e {
   143  					e[i] = rnd.NormFloat64()
   144  				}
   145  			}
   146  			eCopy := make([]float64, len(e))
   147  			copy(eCopy, e)
   148  
   149  			name := fmt.Sprintf("n=%d,type=%d", n, typ)
   150  
   151  			// Compute the eigenvalues of A using Dsterf.
   152  			dGot := make([]float64, len(d))
   153  			copy(dGot, d)
   154  			ok := impl.Dsterf(n, dGot, e)
   155  			if !ok {
   156  				t.Errorf("%v: Dsterf failed", name)
   157  				continue
   158  			}
   159  
   160  			if n == 0 {
   161  				continue
   162  			}
   163  
   164  			// Test that the eigenvalues are sorted.
   165  			if !sort.Float64sAreSorted(dGot) {
   166  				t.Errorf("%v: eigenvalues are not sorted", name)
   167  				continue
   168  			}
   169  
   170  			// Compute the expected eigenvalues of A using Dsteqr.
   171  			dWant := make([]float64, len(d))
   172  			copy(dWant, d)
   173  			copy(e, eCopy)
   174  			z := nanGeneral(n, n, n)
   175  			ok = impl.Dsteqr(lapack.EVTridiag, n, dWant, e, z.Data, z.Stride, make([]float64, 2*n))
   176  			if !ok {
   177  				t.Errorf("%v: computing reference solution using Dsteqr failed", name)
   178  				continue
   179  			}
   180  
   181  			if resid := residualOrthogonal(z, false); resid > tol*float64(n) {
   182  				t.Errorf("%v: Z is not orthogonal; resid=%v, want<=%v", name, resid, tol*float64(n))
   183  			}
   184  
   185  			// Check whether eigenvalues from Dsteqr and Dsterf (which use
   186  			// different algorithms) are equal.
   187  			var diff, dMax float64
   188  			for i, di := range dGot {
   189  				diffAbs := math.Abs(di - dWant[i])
   190  				diff = math.Max(diff, diffAbs)
   191  				dAbs := math.Max(math.Abs(di), math.Abs(dWant[i]))
   192  				dMax = math.Max(dMax, dAbs)
   193  			}
   194  			dMax = math.Max(dlamchS, dMax)
   195  			if diff > tol*dMax {
   196  				t.Errorf("%v: unexpected result; |dGot-dWant|=%v", name, diff)
   197  			}
   198  
   199  			// Construct A as a symmetric dense matrix and compute its 1-norm.
   200  			copy(e, eCopy)
   201  			lda := n
   202  			a := make([]float64, n*lda)
   203  			var anorm, tmp float64
   204  			for i := 0; i < n-1; i++ {
   205  				a[i*lda+i] = d[i]
   206  				a[i*lda+i+1] = e[i]
   207  				tmp2 := math.Abs(e[i])
   208  				anorm = math.Max(anorm, math.Abs(d[i])+tmp+tmp2)
   209  				tmp = tmp2
   210  			}
   211  			a[(n-1)*lda+n-1] = d[n-1]
   212  			anorm = math.Max(anorm, math.Abs(d[n-1])+tmp)
   213  
   214  			// Compute A - Z D Zᵀ. The result should be the zero matrix.
   215  			bi := blas64.Implementation()
   216  			for i := 0; i < n; i++ {
   217  				bi.Dsyr(blas.Upper, n, -dGot[i], z.Data[i:], z.Stride, a, lda)
   218  			}
   219  
   220  			// Compute |A - Z D Zᵀ|.
   221  			wnorm := impl.Dlansy(lapack.MaxColumnSum, blas.Upper, n, a, lda, make([]float64, n))
   222  
   223  			// Compute diff := |A - Z D Zᵀ| / (|A| N).
   224  			if anorm > wnorm {
   225  				diff = wnorm / anorm / float64(n)
   226  			} else {
   227  				if anorm < 1 {
   228  					diff = math.Min(wnorm, float64(n)*anorm) / anorm / float64(n)
   229  				} else {
   230  					diff = math.Min(wnorm/anorm, float64(n)) / float64(n)
   231  				}
   232  			}
   233  
   234  			// Check whether diff is small.
   235  			if diff > tol {
   236  				t.Errorf("%v: unexpected result; |A - Z D Zᵀ|/(|A| n)=%v", name, diff)
   237  			}
   238  		}
   239  	}
   240  }