github.com/gonum/lapack@v0.0.0-20181123203213-e4cdc5a0bff9/testlapack/dtrexc.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/cmplx" 10 "math/rand" 11 "testing" 12 13 "github.com/gonum/blas" 14 "github.com/gonum/blas/blas64" 15 "github.com/gonum/lapack" 16 ) 17 18 type Dtrexcer interface { 19 Dtrexc(compq lapack.EVComp, n int, t []float64, ldt int, q []float64, ldq int, ifst, ilst int, work []float64) (ifstOut, ilstOut int, ok bool) 20 } 21 22 func DtrexcTest(t *testing.T, impl Dtrexcer) { 23 rnd := rand.New(rand.NewSource(1)) 24 25 for _, compq := range []lapack.EVComp{lapack.None, lapack.UpdateSchur} { 26 for _, n := range []int{1, 2, 3, 4, 5, 6, 10, 18, 31, 53} { 27 for _, extra := range []int{0, 1, 11} { 28 for cas := 0; cas < 100; cas++ { 29 tmat := randomSchurCanonical(n, n+extra, rnd) 30 ifst := rnd.Intn(n) 31 ilst := rnd.Intn(n) 32 testDtrexc(t, impl, compq, tmat, ifst, ilst, extra, rnd) 33 } 34 } 35 } 36 } 37 38 for _, compq := range []lapack.EVComp{lapack.None, lapack.UpdateSchur} { 39 for _, extra := range []int{0, 1, 11} { 40 tmat := randomSchurCanonical(0, extra, rnd) 41 testDtrexc(t, impl, compq, tmat, 0, 0, extra, rnd) 42 } 43 } 44 } 45 46 func testDtrexc(t *testing.T, impl Dtrexcer, compq lapack.EVComp, tmat blas64.General, ifst, ilst, extra int, rnd *rand.Rand) { 47 const tol = 1e-13 48 49 n := tmat.Rows 50 fstSize, fstFirst := schurBlockSize(tmat, ifst) 51 lstSize, lstFirst := schurBlockSize(tmat, ilst) 52 53 tmatCopy := cloneGeneral(tmat) 54 55 var wantq bool 56 var q, qCopy blas64.General 57 if compq == lapack.UpdateSchur { 58 wantq = true 59 q = eye(n, n+extra) 60 qCopy = cloneGeneral(q) 61 } 62 63 work := nanSlice(n) 64 65 ifstGot, ilstGot, ok := impl.Dtrexc(compq, n, tmat.Data, tmat.Stride, q.Data, q.Stride, ifst, ilst, work) 66 67 prefix := fmt.Sprintf("Case compq=%v, n=%v, ifst=%v, nbf=%v, ilst=%v, nbl=%v, extra=%v", 68 compq, n, ifst, fstSize, ilst, lstSize, extra) 69 70 if !generalOutsideAllNaN(tmat) { 71 t.Errorf("%v: out-of-range write to T", prefix) 72 } 73 if wantq && !generalOutsideAllNaN(q) { 74 t.Errorf("%v: out-of-range write to Q", prefix) 75 } 76 77 if !ok { 78 t.Logf("%v: Dtrexc returned ok=false", prefix) 79 } 80 81 // Check that the index of the first block was correctly updated (if 82 // necessary). 83 ifstWant := ifst 84 if !fstFirst { 85 ifstWant = ifst - 1 86 } 87 if ifstWant != ifstGot { 88 t.Errorf("%v: unexpected ifst index. Want %v, got %v ", prefix, ifstWant, ifstGot) 89 } 90 91 // Check that the index of the last block is as expected when ok=true. 92 // When ok=false, we don't know at which block the algorithm failed, so 93 // we don't check. 94 ilstWant := ilst 95 if !lstFirst { 96 ilstWant-- 97 } 98 if ok { 99 if ifstWant < ilstWant { 100 // If the blocks are swapped backwards, these 101 // adjustments are not necessary, the first row of the 102 // last block will end up at ifst. 103 switch { 104 case fstSize == 2 && lstSize == 1: 105 ilstWant-- 106 case fstSize == 1 && lstSize == 2: 107 ilstWant++ 108 } 109 } 110 if ilstWant != ilstGot { 111 t.Errorf("%v: unexpected ilst index. Want %v, got %v", prefix, ilstWant, ilstGot) 112 } 113 } 114 115 if n <= 1 || ifstGot == ilstGot { 116 // Too small matrix or no swapping. 117 // Check that T was not modified. 118 for i := 0; i < n; i++ { 119 for j := 0; j < n; j++ { 120 if tmat.Data[i*tmat.Stride+j] != tmatCopy.Data[i*tmatCopy.Stride+j] { 121 t.Errorf("%v: unexpected modification at T[%v,%v]", prefix, i, j) 122 } 123 } 124 } 125 if !wantq { 126 return 127 } 128 // Check that Q was not modified. 129 for i := 0; i < n; i++ { 130 for j := 0; j < n; j++ { 131 if q.Data[i*q.Stride+j] != qCopy.Data[i*qCopy.Stride+j] { 132 t.Errorf("%v: unexpected modification at Q[%v,%v]", prefix, i, j) 133 } 134 } 135 } 136 return 137 } 138 139 if !isSchurCanonicalGeneral(tmat) { 140 t.Errorf("%v: T is not in Schur canonical form", prefix) 141 } 142 143 // Check that T was not modified except above the second subdiagonal in 144 // rows and columns [modMin,modMax]. 145 modMin := min(ifstGot, ilstGot) 146 modMax := max(ifstGot, ilstGot) + fstSize 147 for i := 0; i < n; i++ { 148 for j := 0; j < n; j++ { 149 if modMin <= i && i < modMax && j+1 >= i { 150 continue 151 } 152 if modMin <= j && j < modMax && j+1 >= i { 153 continue 154 } 155 diff := tmat.Data[i*tmat.Stride+j] - tmatCopy.Data[i*tmatCopy.Stride+j] 156 if diff != 0 { 157 t.Errorf("%v: unexpected modification at T[%v,%v]", prefix, i, j) 158 } 159 } 160 } 161 162 // Check that the block at ifstGot was delivered to ilstGot correctly. 163 if fstSize == 1 { 164 // 1×1 blocks are swapped exactly. 165 got := tmat.Data[ilstGot*tmat.Stride+ilstGot] 166 want := tmatCopy.Data[ifstGot*tmatCopy.Stride+ifstGot] 167 if want != got { 168 t.Errorf("%v: unexpected 1×1 block at T[%v,%v]. Want %v, got %v", 169 prefix, want, got, ilstGot, ilstGot) 170 } 171 } else { 172 // Check that the swapped 2×2 block is in Schur canonical form. 173 a, b, c, d := extract2x2Block(tmat.Data[ilstGot*tmat.Stride+ilstGot:], tmat.Stride) 174 if !isSchurCanonical(a, b, c, d) { 175 t.Errorf("%v: 2×2 block at T[%v,%v] not in Schur canonical form", prefix, ilstGot, ilstGot) 176 } 177 ev1Got, ev2Got := schurBlockEigenvalues(a, b, c, d) 178 179 // Check that the swapped 2×2 block has the same eigenvalues. 180 // The block was originally located at T[ifstGot,ifstGot]. 181 a, b, c, d = extract2x2Block(tmatCopy.Data[ifstGot*tmatCopy.Stride+ifstGot:], tmatCopy.Stride) 182 ev1Want, ev2Want := schurBlockEigenvalues(a, b, c, d) 183 if cmplx.Abs(ev1Got-ev1Want) > tol { 184 t.Errorf("%v: unexpected first eigenvalue of 2×2 block at T[%v,%v]. Want %v, got %v", 185 prefix, ilstGot, ilstGot, ev1Want, ev1Got) 186 } 187 if cmplx.Abs(ev2Got-ev2Want) > tol { 188 t.Errorf("%v: unexpected second eigenvalue of 2×2 block at T[%v,%v]. Want %v, got %v", 189 prefix, ilstGot, ilstGot, ev2Want, ev2Got) 190 } 191 } 192 193 if !wantq { 194 return 195 } 196 197 if !isOrthonormal(q) { 198 t.Errorf("%v: Q is not orthogonal", prefix) 199 } 200 // Check that Q is unchanged outside of columns [modMin,modMax]. 201 for i := 0; i < n; i++ { 202 for j := 0; j < n; j++ { 203 if modMin <= j && j < modMax { 204 continue 205 } 206 if q.Data[i*q.Stride+j]-qCopy.Data[i*qCopy.Stride+j] != 0 { 207 t.Errorf("%v: unexpected modification of Q[%v,%v]", prefix, i, j) 208 } 209 } 210 } 211 // Check that Q^T TOrig Q == T. 212 tq := eye(n, n) 213 blas64.Gemm(blas.NoTrans, blas.NoTrans, 1, tmatCopy, q, 0, tq) 214 qtq := eye(n, n) 215 blas64.Gemm(blas.Trans, blas.NoTrans, 1, q, tq, 0, qtq) 216 if !equalApproxGeneral(qtq, tmat, tol) { 217 t.Errorf("%v: Q^T (initial T) Q and (final T) are not equal", prefix) 218 } 219 }