mirror of
https://github.com/gonum/gonum.git
synced 2025-10-04 23:02:42 +08:00

Apply (with manual curation after the fact): * s/^T/U+1d40/g * s/^H/U+1d34/g * s/, {2,3}if / $1/g Some additional manual editing of odd formatting.
221 lines
6.3 KiB
Go
221 lines
6.3 KiB
Go
// Copyright ©2016 The Gonum Authors. All rights reserved.
|
||
// Use of this source code is governed by a BSD-style
|
||
// license that can be found in the LICENSE file.
|
||
|
||
package testlapack
|
||
|
||
import (
|
||
"fmt"
|
||
"math/cmplx"
|
||
"testing"
|
||
|
||
"golang.org/x/exp/rand"
|
||
|
||
"gonum.org/v1/gonum/blas"
|
||
"gonum.org/v1/gonum/blas/blas64"
|
||
"gonum.org/v1/gonum/lapack"
|
||
)
|
||
|
||
type Dtrexcer interface {
|
||
Dtrexc(compq lapack.UpdateSchurComp, n int, t []float64, ldt int, q []float64, ldq int, ifst, ilst int, work []float64) (ifstOut, ilstOut int, ok bool)
|
||
}
|
||
|
||
func DtrexcTest(t *testing.T, impl Dtrexcer) {
|
||
rnd := rand.New(rand.NewSource(1))
|
||
|
||
for _, compq := range []lapack.UpdateSchurComp{lapack.UpdateSchurNone, lapack.UpdateSchur} {
|
||
for _, n := range []int{1, 2, 3, 4, 5, 6, 10, 18, 31, 53} {
|
||
for _, extra := range []int{0, 1, 11} {
|
||
for cas := 0; cas < 100; cas++ {
|
||
tmat := randomSchurCanonical(n, n+extra, rnd)
|
||
ifst := rnd.Intn(n)
|
||
ilst := rnd.Intn(n)
|
||
testDtrexc(t, impl, compq, tmat, ifst, ilst, extra, rnd)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
for _, compq := range []lapack.UpdateSchurComp{lapack.UpdateSchurNone, lapack.UpdateSchur} {
|
||
for _, extra := range []int{0, 1, 11} {
|
||
tmat := randomSchurCanonical(0, extra, rnd)
|
||
testDtrexc(t, impl, compq, tmat, 0, 0, extra, rnd)
|
||
}
|
||
}
|
||
}
|
||
|
||
func testDtrexc(t *testing.T, impl Dtrexcer, compq lapack.UpdateSchurComp, tmat blas64.General, ifst, ilst, extra int, rnd *rand.Rand) {
|
||
const tol = 1e-13
|
||
|
||
n := tmat.Rows
|
||
fstSize, fstFirst := schurBlockSize(tmat, ifst)
|
||
lstSize, lstFirst := schurBlockSize(tmat, ilst)
|
||
|
||
tmatCopy := cloneGeneral(tmat)
|
||
|
||
var wantq bool
|
||
var q, qCopy blas64.General
|
||
if compq == lapack.UpdateSchur {
|
||
wantq = true
|
||
q = eye(n, n+extra)
|
||
qCopy = cloneGeneral(q)
|
||
}
|
||
|
||
work := nanSlice(n)
|
||
|
||
ifstGot, ilstGot, ok := impl.Dtrexc(compq, n, tmat.Data, tmat.Stride, q.Data, max(1, q.Stride), ifst, ilst, work)
|
||
|
||
prefix := fmt.Sprintf("Case compq=%v, n=%v, ifst=%v, nbf=%v, ilst=%v, nbl=%v, extra=%v",
|
||
compq, n, ifst, fstSize, ilst, lstSize, extra)
|
||
|
||
if !generalOutsideAllNaN(tmat) {
|
||
t.Errorf("%v: out-of-range write to T", prefix)
|
||
}
|
||
if wantq && !generalOutsideAllNaN(q) {
|
||
t.Errorf("%v: out-of-range write to Q", prefix)
|
||
}
|
||
|
||
if !ok {
|
||
t.Logf("%v: Dtrexc returned ok=false", prefix)
|
||
}
|
||
|
||
// Check that the index of the first block was correctly updated (if
|
||
// necessary).
|
||
ifstWant := ifst
|
||
if !fstFirst {
|
||
ifstWant = ifst - 1
|
||
}
|
||
if ifstWant != ifstGot {
|
||
t.Errorf("%v: unexpected ifst index. Want %v, got %v ", prefix, ifstWant, ifstGot)
|
||
}
|
||
|
||
// Check that the index of the last block is as expected when ok=true.
|
||
// When ok=false, we don't know at which block the algorithm failed, so
|
||
// we don't check.
|
||
ilstWant := ilst
|
||
if !lstFirst {
|
||
ilstWant--
|
||
}
|
||
if ok {
|
||
if ifstWant < ilstWant {
|
||
// If the blocks are swapped backwards, these
|
||
// adjustments are not necessary, the first row of the
|
||
// last block will end up at ifst.
|
||
switch {
|
||
case fstSize == 2 && lstSize == 1:
|
||
ilstWant--
|
||
case fstSize == 1 && lstSize == 2:
|
||
ilstWant++
|
||
}
|
||
}
|
||
if ilstWant != ilstGot {
|
||
t.Errorf("%v: unexpected ilst index. Want %v, got %v", prefix, ilstWant, ilstGot)
|
||
}
|
||
}
|
||
|
||
if n <= 1 || ifstGot == ilstGot {
|
||
// Too small matrix or no swapping.
|
||
// Check that T was not modified.
|
||
for i := 0; i < n; i++ {
|
||
for j := 0; j < n; j++ {
|
||
if tmat.Data[i*tmat.Stride+j] != tmatCopy.Data[i*tmatCopy.Stride+j] {
|
||
t.Errorf("%v: unexpected modification at T[%v,%v]", prefix, i, j)
|
||
}
|
||
}
|
||
}
|
||
if !wantq {
|
||
return
|
||
}
|
||
// Check that Q was not modified.
|
||
for i := 0; i < n; i++ {
|
||
for j := 0; j < n; j++ {
|
||
if q.Data[i*q.Stride+j] != qCopy.Data[i*qCopy.Stride+j] {
|
||
t.Errorf("%v: unexpected modification at Q[%v,%v]", prefix, i, j)
|
||
}
|
||
}
|
||
}
|
||
return
|
||
}
|
||
|
||
if !isSchurCanonicalGeneral(tmat) {
|
||
t.Errorf("%v: T is not in Schur canonical form", prefix)
|
||
}
|
||
|
||
// Check that T was not modified except above the second subdiagonal in
|
||
// rows and columns [modMin,modMax].
|
||
modMin := min(ifstGot, ilstGot)
|
||
modMax := max(ifstGot, ilstGot) + fstSize
|
||
for i := 0; i < n; i++ {
|
||
for j := 0; j < n; j++ {
|
||
if modMin <= i && i < modMax && j+1 >= i {
|
||
continue
|
||
}
|
||
if modMin <= j && j < modMax && j+1 >= i {
|
||
continue
|
||
}
|
||
diff := tmat.Data[i*tmat.Stride+j] - tmatCopy.Data[i*tmatCopy.Stride+j]
|
||
if diff != 0 {
|
||
t.Errorf("%v: unexpected modification at T[%v,%v]", prefix, i, j)
|
||
}
|
||
}
|
||
}
|
||
|
||
// Check that the block at ifstGot was delivered to ilstGot correctly.
|
||
if fstSize == 1 {
|
||
// 1×1 blocks are swapped exactly.
|
||
got := tmat.Data[ilstGot*tmat.Stride+ilstGot]
|
||
want := tmatCopy.Data[ifstGot*tmatCopy.Stride+ifstGot]
|
||
if want != got {
|
||
t.Errorf("%v: unexpected 1×1 block at T[%v,%v]. Want %v, got %v",
|
||
prefix, want, got, ilstGot, ilstGot)
|
||
}
|
||
} else {
|
||
// Check that the swapped 2×2 block is in Schur canonical form.
|
||
a, b, c, d := extract2x2Block(tmat.Data[ilstGot*tmat.Stride+ilstGot:], tmat.Stride)
|
||
if !isSchurCanonical(a, b, c, d) {
|
||
t.Errorf("%v: 2×2 block at T[%v,%v] not in Schur canonical form", prefix, ilstGot, ilstGot)
|
||
}
|
||
ev1Got, ev2Got := schurBlockEigenvalues(a, b, c, d)
|
||
|
||
// Check that the swapped 2×2 block has the same eigenvalues.
|
||
// The block was originally located at T[ifstGot,ifstGot].
|
||
a, b, c, d = extract2x2Block(tmatCopy.Data[ifstGot*tmatCopy.Stride+ifstGot:], tmatCopy.Stride)
|
||
ev1Want, ev2Want := schurBlockEigenvalues(a, b, c, d)
|
||
if cmplx.Abs(ev1Got-ev1Want) > tol {
|
||
t.Errorf("%v: unexpected first eigenvalue of 2×2 block at T[%v,%v]. Want %v, got %v",
|
||
prefix, ilstGot, ilstGot, ev1Want, ev1Got)
|
||
}
|
||
if cmplx.Abs(ev2Got-ev2Want) > tol {
|
||
t.Errorf("%v: unexpected second eigenvalue of 2×2 block at T[%v,%v]. Want %v, got %v",
|
||
prefix, ilstGot, ilstGot, ev2Want, ev2Got)
|
||
}
|
||
}
|
||
|
||
if !wantq {
|
||
return
|
||
}
|
||
|
||
if !isOrthogonal(q) {
|
||
t.Errorf("%v: Q is not orthogonal", prefix)
|
||
}
|
||
// Check that Q is unchanged outside of columns [modMin,modMax].
|
||
for i := 0; i < n; i++ {
|
||
for j := 0; j < n; j++ {
|
||
if modMin <= j && j < modMax {
|
||
continue
|
||
}
|
||
if q.Data[i*q.Stride+j]-qCopy.Data[i*qCopy.Stride+j] != 0 {
|
||
t.Errorf("%v: unexpected modification of Q[%v,%v]", prefix, i, j)
|
||
}
|
||
}
|
||
}
|
||
// Check that Qᵀ TOrig Q == T.
|
||
tq := eye(n, n)
|
||
blas64.Gemm(blas.NoTrans, blas.NoTrans, 1, tmatCopy, q, 0, tq)
|
||
qtq := eye(n, n)
|
||
blas64.Gemm(blas.Trans, blas.NoTrans, 1, q, tq, 0, qtq)
|
||
if !equalApproxGeneral(qtq, tmat, tol) {
|
||
t.Errorf("%v: Qᵀ (initial T) Q and (final T) are not equal", prefix)
|
||
}
|
||
}
|