gonum.org/v1/gonum@v0.14.0/lapack/testlapack/dlag2.go (about)

     1  // Copyright ©2021 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/blas64"
    15  	"gonum.org/v1/gonum/floats"
    16  )
    17  
    18  type Dlag2er interface {
    19  	Dlag2(a []float64, lda int, b []float64, ldb int) (scale1, scale2, wr1, wr2, wi float64)
    20  }
    21  
    22  func Dlag2Test(t *testing.T, impl Dlag2er) {
    23  	rnd := rand.New(rand.NewSource(1))
    24  	for _, lda := range []int{2, 5} {
    25  		for _, ldb := range []int{2, 5} {
    26  			for aKind := 0; aKind <= 20; aKind++ {
    27  				for bKind := 0; bKind <= 20; bKind++ {
    28  					dlag2Test(t, impl, rnd, lda, ldb, aKind, bKind)
    29  				}
    30  			}
    31  		}
    32  	}
    33  }
    34  
    35  func dlag2Test(t *testing.T, impl Dlag2er, rnd *rand.Rand, lda, ldb int, aKind, bKind int) {
    36  	const tol = 1e-14
    37  
    38  	a := makeDlag2TestMatrix(rnd, lda, aKind)
    39  	b := makeDlag2TestMatrix(rnd, ldb, bKind)
    40  
    41  	aCopy := cloneGeneral(a)
    42  	bCopy := cloneGeneral(b)
    43  
    44  	scale1, scale2, wr1, wr2, wi := impl.Dlag2(a.Data, a.Stride, b.Data, b.Stride)
    45  
    46  	name := fmt.Sprintf("lda=%d,ldb=%d,aKind=%d,bKind=%d", lda, ldb, aKind, bKind)
    47  	aStr := fmt.Sprintf("A = [%g,%g]\n    [%g,%g]", a.Data[0], a.Data[1], a.Data[a.Stride], a.Data[a.Stride+1])
    48  	bStr := fmt.Sprintf("B = [%g,%g]\n    [%g,%g]", b.Data[0], b.Data[1], 0.0, b.Data[b.Stride+1])
    49  
    50  	if !floats.Same(a.Data, aCopy.Data) {
    51  		t.Errorf("%s: unexpected modification of a", name)
    52  	}
    53  	if !floats.Same(b.Data, bCopy.Data) {
    54  		t.Errorf("%s: unexpected modification of b", name)
    55  	}
    56  
    57  	if wi < 0 {
    58  		t.Fatalf("%s: wi is negative; wi=%g,\n%s\n%s", name, wi, aStr, bStr)
    59  		return
    60  	}
    61  
    62  	if wi > 0 {
    63  		if wr1 != wr2 {
    64  			t.Fatalf("%s: complex eigenvalue but wr1 != wr2; wr1=%g, wr2=%g,\n%s\n%s", name, wr1, wr2, aStr, bStr)
    65  			return
    66  		}
    67  		if scale1 != scale2 {
    68  			t.Fatalf("%s: complex eigenvalue but scale1 != scale2; scale1=%g, scale2=%g,\n%s\n%s", name, scale1, scale2, aStr, bStr)
    69  			return
    70  		}
    71  	}
    72  
    73  	resid, err := residualDlag2(a, b, scale1, complex(wr1, wi))
    74  	if err != nil {
    75  		t.Logf("%s: invalid input data: %v\n%s\n%s", name, err, aStr, bStr)
    76  		return
    77  	}
    78  	if resid > tol || math.IsNaN(resid) {
    79  		t.Errorf("%s: unexpected first eigenvalue %g with s=%g; resid=%g, want<=%g\n%s\n%s", name, complex(wr1, wi), scale1, resid, tol, aStr, bStr)
    80  	}
    81  
    82  	resid, err = residualDlag2(a, b, scale2, complex(wr2, -wi))
    83  	if err != nil {
    84  		t.Logf("%s: invalid input data: %s\n%s\n%s", name, err, aStr, bStr)
    85  		return
    86  	}
    87  	if resid > tol || math.IsNaN(resid) {
    88  		t.Errorf("%s: unexpected second eigenvalue %g with s=%g; resid=%g, want<=%g\n%s\n%s", name, complex(wr2, -wi), scale2, resid, tol, aStr, bStr)
    89  	}
    90  }
    91  
    92  func makeDlag2TestMatrix(rnd *rand.Rand, ld, kind int) blas64.General {
    93  	a := zeros(2, 2, ld)
    94  	switch kind {
    95  	case 0:
    96  		// Zero matrix.
    97  	case 1:
    98  		// Identity.
    99  		a.Data[0] = 1
   100  		a.Data[a.Stride+1] = 1
   101  	case 2:
   102  		// Large diagonal.
   103  		a.Data[0] = 2 * safmax
   104  		a.Data[a.Stride+1] = 2 * safmax
   105  	case 3:
   106  		// Tiny diagonal.
   107  		a.Data[0] = safmin
   108  		a.Data[a.Stride+1] = safmin
   109  	case 4:
   110  		// Tiny and large diagonal.
   111  		a.Data[0] = safmin
   112  		a.Data[a.Stride+1] = safmax
   113  	case 5:
   114  		// Large and tiny diagonal.
   115  		a.Data[0] = safmax
   116  		a.Data[a.Stride+1] = safmin
   117  	case 6:
   118  		// Large complex eigenvalue.
   119  		a.Data[0] = safmax
   120  		a.Data[1] = safmax
   121  		a.Data[a.Stride] = -safmax
   122  		a.Data[a.Stride+1] = safmax
   123  	case 7:
   124  		// Tiny complex eigenvalue.
   125  		a.Data[0] = safmin
   126  		a.Data[1] = safmin
   127  		a.Data[a.Stride] = -safmin
   128  		a.Data[a.Stride+1] = safmin
   129  	case 8:
   130  		// Random matrix with large elements.
   131  		a.Data[0] = safmax * (2*rnd.Float64() - 1)
   132  		a.Data[1] = safmax * (2*rnd.Float64() - 1)
   133  		a.Data[a.Stride] = safmax * (2*rnd.Float64() - 1)
   134  		a.Data[a.Stride+1] = safmax * (2*rnd.Float64() - 1)
   135  	case 9:
   136  		// Random matrix with tiny elements.
   137  		a.Data[0] = safmin * (2*rnd.Float64() - 1)
   138  		a.Data[1] = safmin * (2*rnd.Float64() - 1)
   139  		a.Data[a.Stride] = safmin * (2*rnd.Float64() - 1)
   140  		a.Data[a.Stride+1] = safmin * (2*rnd.Float64() - 1)
   141  	default:
   142  		// Random matrix.
   143  		a = randomGeneral(2, 2, ld, rnd)
   144  	}
   145  	return a
   146  }
   147  
   148  // residualDlag2 returns the value of
   149  //
   150  //	           | det( s*A - w*B ) |
   151  //	-------------------------------------------
   152  //	max(s*norm(A), |w|*norm(B))*norm(s*A - w*B)
   153  //
   154  // that can be used to check the generalized eigenvalues computed by Dlag2 and
   155  // an error that indicates invalid input data.
   156  func residualDlag2(a, b blas64.General, s float64, w complex128) (float64, error) {
   157  	const ulp = dlamchP
   158  
   159  	a11, a12 := a.Data[0], a.Data[1]
   160  	a21, a22 := a.Data[a.Stride], a.Data[a.Stride+1]
   161  
   162  	b11, b12 := b.Data[0], b.Data[1]
   163  	b22 := b.Data[b.Stride+1]
   164  
   165  	// Compute norms.
   166  	absw := zabs(w)
   167  	anorm := math.Max(math.Abs(a11)+math.Abs(a21), math.Abs(a12)+math.Abs(a22))
   168  	anorm = math.Max(anorm, safmin)
   169  	bnorm := math.Max(math.Abs(b11), math.Abs(b12)+math.Abs(b22))
   170  	bnorm = math.Max(bnorm, safmin)
   171  
   172  	// Check for possible overflow.
   173  	temp := (safmin*anorm)*s + (safmin*bnorm)*absw
   174  	if temp >= 1 {
   175  		// Scale down to avoid overflow.
   176  		s /= temp
   177  		w = scale(1/temp, w)
   178  		absw = zabs(w)
   179  	}
   180  
   181  	// Check for w and s essentially zero.
   182  	s1 := math.Max(ulp*math.Max(s*anorm, absw*bnorm), safmin*math.Max(s, absw))
   183  	if s1 < safmin {
   184  		if s < safmin && absw < safmin {
   185  			return 1 / ulp, fmt.Errorf("ulp*max(s*|A|,|w|*|B|) < safmin and s and w could not be scaled; s=%g, |w|=%g", s, absw)
   186  		}
   187  		// Scale up to avoid underflow.
   188  		temp = 1 / math.Max(s*anorm+absw*bnorm, safmin)
   189  		s *= temp
   190  		w = scale(temp, w)
   191  		absw = zabs(w)
   192  		s1 = math.Max(ulp*math.Max(s*anorm, absw*bnorm), safmin*math.Max(s, absw))
   193  		if s1 < safmin {
   194  			return 1 / ulp, fmt.Errorf("ulp*max(s*|A|,|w|*|B|) < safmin and s and w could not be scaled; s=%g, |w|=%g", s, absw)
   195  		}
   196  	}
   197  
   198  	// Compute C = s*A - w*B.
   199  	c11 := complex(s*a11, 0) - w*complex(b11, 0)
   200  	c12 := complex(s*a12, 0) - w*complex(b12, 0)
   201  	c21 := complex(s*a21, 0)
   202  	c22 := complex(s*a22, 0) - w*complex(b22, 0)
   203  	// Compute norm(s*A - w*B).
   204  	cnorm := math.Max(zabs(c11)+zabs(c21), zabs(c12)+zabs(c22))
   205  	// Compute det(s*A - w*B)/norm(s*A - w*B).
   206  	cs := 1 / math.Sqrt(math.Max(cnorm, safmin))
   207  	det := cmplxdet2x2(scale(cs, c11), scale(cs, c12), scale(cs, c21), scale(cs, c22))
   208  	// Compute |det(s*A - w*B)|/(norm(s*A - w*B)*max(s*norm(A), |w|*norm(B))).
   209  	return zabs(det) / s1 * ulp, nil
   210  }
   211  
   212  func zabs(z complex128) float64 {
   213  	return math.Abs(real(z)) + math.Abs(imag(z))
   214  }
   215  
   216  // scale scales the complex number c by f.
   217  func scale(f float64, c complex128) complex128 {
   218  	return complex(f*real(c), f*imag(c))
   219  }
   220  
   221  // cmplxdet2x2 returns the determinant of
   222  //
   223  //	|a11 a12|
   224  //	|a21 a22|
   225  func cmplxdet2x2(a11, a12, a21, a22 complex128) complex128 {
   226  	return a11*a22 - a12*a21
   227  }