github.com/gopherd/gonum@v0.0.4/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  	"math/rand"
    13  
    14  	"github.com/gopherd/gonum/blas/blas64"
    15  	"github.com/gopherd/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  //             | det( s*A - w*B ) |
   150  //  -------------------------------------------
   151  //  max(s*norm(A), |w|*norm(B))*norm(s*A - w*B)
   152  // that can be used to check the generalized eigenvalues computed by Dlag2 and
   153  // an error that indicates invalid input data.
   154  func residualDlag2(a, b blas64.General, s float64, w complex128) (float64, error) {
   155  	const ulp = dlamchP
   156  
   157  	a11, a12 := a.Data[0], a.Data[1]
   158  	a21, a22 := a.Data[a.Stride], a.Data[a.Stride+1]
   159  
   160  	b11, b12 := b.Data[0], b.Data[1]
   161  	b22 := b.Data[b.Stride+1]
   162  
   163  	// Compute norms.
   164  	absw := zabs(w)
   165  	anorm := math.Max(math.Abs(a11)+math.Abs(a21), math.Abs(a12)+math.Abs(a22))
   166  	anorm = math.Max(anorm, safmin)
   167  	bnorm := math.Max(math.Abs(b11), math.Abs(b12)+math.Abs(b22))
   168  	bnorm = math.Max(bnorm, safmin)
   169  
   170  	// Check for possible overflow.
   171  	temp := (safmin*anorm)*s + (safmin*bnorm)*absw
   172  	if temp >= 1 {
   173  		// Scale down to avoid overflow.
   174  		s /= temp
   175  		w = scale(1/temp, w)
   176  		absw = zabs(w)
   177  	}
   178  
   179  	// Check for w and s essentially zero.
   180  	s1 := math.Max(ulp*math.Max(s*anorm, absw*bnorm), safmin*math.Max(s, absw))
   181  	if s1 < safmin {
   182  		if s < safmin && absw < safmin {
   183  			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)
   184  		}
   185  		// Scale up to avoid underflow.
   186  		temp = 1 / math.Max(s*anorm+absw*bnorm, safmin)
   187  		s *= temp
   188  		w = scale(temp, w)
   189  		absw = zabs(w)
   190  		s1 = math.Max(ulp*math.Max(s*anorm, absw*bnorm), safmin*math.Max(s, absw))
   191  		if s1 < safmin {
   192  			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)
   193  		}
   194  	}
   195  
   196  	// Compute C = s*A - w*B.
   197  	c11 := complex(s*a11, 0) - w*complex(b11, 0)
   198  	c12 := complex(s*a12, 0) - w*complex(b12, 0)
   199  	c21 := complex(s*a21, 0)
   200  	c22 := complex(s*a22, 0) - w*complex(b22, 0)
   201  	// Compute norm(s*A - w*B).
   202  	cnorm := math.Max(zabs(c11)+zabs(c21), zabs(c12)+zabs(c22))
   203  	// Compute det(s*A - w*B)/norm(s*A - w*B).
   204  	cs := 1 / math.Sqrt(math.Max(cnorm, safmin))
   205  	det := cmplxdet2x2(scale(cs, c11), scale(cs, c12), scale(cs, c21), scale(cs, c22))
   206  	// Compute |det(s*A - w*B)|/(norm(s*A - w*B)*max(s*norm(A), |w|*norm(B))).
   207  	return zabs(det) / s1 * ulp, nil
   208  }
   209  
   210  func zabs(z complex128) float64 {
   211  	return math.Abs(real(z)) + math.Abs(imag(z))
   212  }
   213  
   214  // scale scales the complex number c by f.
   215  func scale(f float64, c complex128) complex128 {
   216  	return complex(f*real(c), f*imag(c))
   217  }
   218  
   219  // cmplxdet2x2 returns the determinant of
   220  //  |a11 a12|
   221  //  |a21 a22|
   222  func cmplxdet2x2(a11, a12, a21, a22 complex128) complex128 {
   223  	return a11*a22 - a12*a21
   224  }