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

     1  // Copyright ©2019 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/floats"
    15  )
    16  
    17  type Dlassqer interface {
    18  	Dlassq(n int, x []float64, incx int, scale, ssq float64) (float64, float64)
    19  }
    20  
    21  func DlassqTest(t *testing.T, impl Dlassqer) {
    22  	values := []float64{
    23  		0,
    24  		2 * safmin,
    25  		smlnum,
    26  		ulp,
    27  		1,
    28  		1 / ulp,
    29  		bignum,
    30  		safmax,
    31  		math.Inf(1),
    32  		math.NaN(),
    33  	}
    34  
    35  	rnd := rand.New(rand.NewSource(1))
    36  	for _, n := range []int{0, 1, 2, 3, 4, 5, 10, 20, 30, 40} {
    37  		for _, incx := range []int{1, 3} {
    38  			for cas := 0; cas < 3; cas++ {
    39  				for _, v0 := range values {
    40  					if v0 > 1 {
    41  						v0 *= 0.5
    42  					}
    43  					for _, v1 := range values {
    44  						if v1 > 1 {
    45  							v1 = 0.5 * v1 / math.Sqrt(float64(n+1))
    46  						}
    47  						dlassqTest(t, impl, rnd, n, incx, cas, v0, v1)
    48  					}
    49  				}
    50  			}
    51  		}
    52  	}
    53  }
    54  
    55  func dlassqTest(t *testing.T, impl Dlassqer, rnd *rand.Rand, n, incx, cas int, v0, v1 float64) {
    56  	const (
    57  		rogue = 1234.5678
    58  		tol   = 1e-14
    59  	)
    60  
    61  	name := fmt.Sprintf("n=%v,incx=%v,cas=%v,v0=%v,v1=%v", n, incx, cas, v0, v1)
    62  
    63  	// Generate n random values in (-1,1].
    64  	work := make([]float64, n)
    65  	for i := range work {
    66  		work[i] = 1 - 2*rnd.Float64()
    67  	}
    68  
    69  	// Compute the sum of squares by an unscaled algorithm.
    70  	var workssq float64
    71  	for _, wi := range work {
    72  		workssq += wi * wi
    73  	}
    74  
    75  	// Construct a test (n+1)-vector z as
    76  	//  z[0] = known value
    77  	//  z[1:n+1] = scaling factor * work[:]
    78  	z := make([]float64, n+1)
    79  	z[0] = math.Sqrt(v0)
    80  	for i, wi := range work {
    81  		z[i+1] = v1 * wi
    82  	}
    83  	// Set initial scale and ssq corresponding to z[0].
    84  	var scale, ssq float64
    85  	switch cas {
    86  	case 0:
    87  		scale = 1
    88  		ssq = v0
    89  	case 1:
    90  		scale = math.Sqrt(v0)
    91  		ssq = 1
    92  	case 2:
    93  		if v0 < 1 {
    94  			scale = 1.0 / 3
    95  			ssq = 9 * v0
    96  		} else {
    97  			scale = 3
    98  			ssq = v0 / 9
    99  		}
   100  	default:
   101  		panic("bad cas")
   102  	}
   103  
   104  	const (
   105  		dtsml = 0x1p-511
   106  		dtbig = 0x1p486
   107  		dssml = 0x1p537
   108  		dsbig = 0x1p-538
   109  	)
   110  	if scale*math.Sqrt(ssq) > dtbig && scale < math.Sqrt(math.SmallestNonzeroFloat64/ulp)/dsbig {
   111  		// The scaled sum is big but the scale itself is small.
   112  		return
   113  	}
   114  	if scale*math.Sqrt(ssq) < dtsml && scale > math.Sqrt(math.MaxFloat64)/dssml {
   115  		// The scaled sum is small but the scale itself is big.
   116  		return
   117  	}
   118  
   119  	// Compute the expected value of the sum of squares using the (n+1)-vector z.
   120  	z0 := z[0]
   121  	var z1n float64
   122  	if n >= 1 {
   123  		z1n = v1 * math.Sqrt(workssq)
   124  	}
   125  	zmin := math.Min(z0, z1n)
   126  	zmax := math.Max(z0, z1n)
   127  	var nrmWant float64
   128  	switch {
   129  	case math.IsNaN(z0) || math.IsNaN(z1n):
   130  		nrmWant = math.NaN()
   131  	case zmin == zmax:
   132  		nrmWant = math.Sqrt2 * zmax
   133  	case zmax == 0:
   134  		nrmWant = 0
   135  	default:
   136  		nrmWant = zmax * math.Sqrt(1+(zmin/zmax)*(zmin/zmax))
   137  	}
   138  
   139  	// Allocate input slice for Dlassq and fill it with z[1:].
   140  	x := make([]float64, max(0, 1+(n-1)*incx))
   141  	for i := range x {
   142  		x[i] = rogue
   143  	}
   144  	for i, zi := range z[1:] {
   145  		x[i*incx] = zi
   146  	}
   147  	xCopy := make([]float64, len(x))
   148  	copy(xCopy, x)
   149  
   150  	scaleGot, ssqGot := impl.Dlassq(n, x, incx, scale, ssq)
   151  	nrmGot := scaleGot * math.Sqrt(ssqGot)
   152  
   153  	if !floats.Same(x, xCopy) {
   154  		t.Fatalf("%v: unexpected modification of x", name)
   155  	}
   156  
   157  	// Check the result.
   158  	switch {
   159  	case math.IsNaN(nrmGot) || math.IsNaN(nrmWant):
   160  		if !math.IsNaN(nrmGot) {
   161  			t.Errorf("%v: expected NaN; got %v", name, nrmGot)
   162  		}
   163  		if !math.IsNaN(nrmWant) {
   164  			t.Errorf("%v: unexpected NaN; want %v", name, nrmWant)
   165  		}
   166  	case nrmGot == nrmWant:
   167  	case nrmWant == 0:
   168  		if nrmGot > tol {
   169  			t.Errorf("%v: unexpected result; got %v, want 0", name, nrmGot)
   170  		}
   171  	default:
   172  		diff := math.Abs(nrmGot-nrmWant) / nrmWant / math.Max(1, float64(n))
   173  		if math.IsNaN(diff) || diff > tol {
   174  			t.Errorf("%v: unexpected result; got %v, want %v", name, nrmGot, nrmWant)
   175  		}
   176  	}
   177  }