mirror of
https://github.com/gonum/gonum.git
synced 2025-10-28 17:31:56 +08:00
277 lines
6.6 KiB
Go
277 lines
6.6 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"
|
||
"math/rand"
|
||
"testing"
|
||
|
||
"github.com/gonum/blas/blas64"
|
||
)
|
||
|
||
type Dlahqrer interface {
|
||
Dlahqr(wantt, wantz bool, n, ilo, ihi int, h []float64, ldh int, wr, wi []float64, iloz, ihiz int, z []float64, ldz int) int
|
||
}
|
||
|
||
func DlahqrTest(t *testing.T, impl Dlahqrer) {
|
||
rnd := rand.New(rand.NewSource(1))
|
||
|
||
// Tests that choose the [ilo:ihi+1,ilo:ihi+1] and
|
||
// [iloz:ihiz+1,ilo:ihi+1] blocks randomly.
|
||
for _, wantt := range []bool{true, false} {
|
||
for _, wantz := range []bool{true, false} {
|
||
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++ {
|
||
ilo := rnd.Intn(n)
|
||
ihi := rnd.Intn(n)
|
||
if ilo > ihi {
|
||
ilo, ihi = ihi, ilo
|
||
}
|
||
iloz := rnd.Intn(ilo + 1)
|
||
ihiz := ihi + rnd.Intn(n-ihi)
|
||
testDlahqr(t, impl, wantt, wantz, n, ilo, ihi, iloz, ihiz, extra, rnd)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
// Tests that make sure that some potentially problematic corner cases,
|
||
// like zero-sized matrix, are covered.
|
||
for _, wantt := range []bool{true, false} {
|
||
for _, wantz := range []bool{true, false} {
|
||
for _, extra := range []int{0, 1, 11} {
|
||
for _, test := range []struct {
|
||
n, ilo, ihi, iloz, ihiz int
|
||
}{
|
||
{
|
||
n: 0,
|
||
ilo: 0,
|
||
ihi: -1,
|
||
iloz: 0,
|
||
ihiz: -1,
|
||
},
|
||
{
|
||
n: 1,
|
||
ilo: 0,
|
||
ihi: 0,
|
||
iloz: 0,
|
||
ihiz: 0,
|
||
},
|
||
{
|
||
n: 2,
|
||
ilo: 1,
|
||
ihi: 1,
|
||
iloz: 1,
|
||
ihiz: 1,
|
||
},
|
||
{
|
||
n: 2,
|
||
ilo: 0,
|
||
ihi: 1,
|
||
iloz: 0,
|
||
ihiz: 1,
|
||
},
|
||
{
|
||
n: 10,
|
||
ilo: 0,
|
||
ihi: 0,
|
||
iloz: 0,
|
||
ihiz: 0,
|
||
},
|
||
{
|
||
n: 10,
|
||
ilo: 0,
|
||
ihi: 9,
|
||
iloz: 0,
|
||
ihiz: 9,
|
||
},
|
||
{
|
||
n: 10,
|
||
ilo: 0,
|
||
ihi: 1,
|
||
iloz: 0,
|
||
ihiz: 1,
|
||
},
|
||
{
|
||
n: 10,
|
||
ilo: 0,
|
||
ihi: 1,
|
||
iloz: 0,
|
||
ihiz: 9,
|
||
},
|
||
{
|
||
n: 10,
|
||
ilo: 9,
|
||
ihi: 9,
|
||
iloz: 0,
|
||
ihiz: 9,
|
||
},
|
||
} {
|
||
testDlahqr(t, impl, wantt, wantz, test.n, test.ilo, test.ihi, test.iloz, test.ihiz, extra, rnd)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
func testDlahqr(t *testing.T, impl Dlahqrer, wantt, wantz bool, n, ilo, ihi, iloz, ihiz, extra int, rnd *rand.Rand) {
|
||
const tol = 1e-14
|
||
|
||
var z, zCopy blas64.General
|
||
if wantz {
|
||
z = eye(n, n+extra)
|
||
zCopy = cloneGeneral(z)
|
||
}
|
||
h := randomHessenberg(n, n+extra, rnd)
|
||
if ilo > 0 {
|
||
h.Data[ilo*h.Stride+ilo-1] = 0
|
||
}
|
||
if ihi < n-1 {
|
||
h.Data[(ihi+1)*h.Stride+ihi] = 0
|
||
}
|
||
wr := nanSlice(n)
|
||
wi := nanSlice(n)
|
||
|
||
unconverged := impl.Dlahqr(wantt, wantz, n, ilo, ihi, h.Data, h.Stride, wr, wi, iloz, ihiz, z.Data, z.Stride)
|
||
|
||
prefix := fmt.Sprintf("Case n=%v, ilo=%v, ihi=%v, iloz=%v, ihiz=%v, wantt=%v, wantz=%v, extra=%v", n, ilo, ihi, iloz, ihiz, wantt, wantz, extra)
|
||
|
||
if !generalOutsideAllNaN(h) {
|
||
t.Errorf("%v: out-of-range write to H\n%v", prefix, h.Data)
|
||
}
|
||
if !generalOutsideAllNaN(z) {
|
||
t.Errorf("%v: out-of-range write to Z\n%v", prefix, z.Data)
|
||
}
|
||
|
||
if wantz {
|
||
// Z should contain the orthogonal matrix U.
|
||
if !isOrthonormal(z) {
|
||
t.Errorf("%v: Z is not orthogonal", prefix)
|
||
}
|
||
// Z should have been modified only in the
|
||
// [iloz:ihiz+1,ilo:ihi+1] block.
|
||
for i := 0; i < n; i++ {
|
||
for j := 0; j < n; j++ {
|
||
if iloz <= i && i <= ihiz && ilo <= j && j <= ihi {
|
||
continue
|
||
}
|
||
if z.Data[i*z.Stride+j] != zCopy.Data[i*zCopy.Stride+j] {
|
||
t.Errorf("%v: Z modified outside of [iloz:ihiz+1,ilo:ihi+1] block", prefix)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
start := ilo // Index of the first computed eigenvalue.
|
||
if unconverged != 0 {
|
||
start = unconverged
|
||
if start == ihi+1 {
|
||
t.Logf("%v: no eigenvalue has converged", prefix)
|
||
}
|
||
}
|
||
|
||
// Check that wr and wi have not been modified outside [start:ihi+1].
|
||
for i := 0; i < start; i++ {
|
||
if !math.IsNaN(wr[i]) {
|
||
t.Errorf("%v: wr modified before [ilo:ihi+1] block", prefix)
|
||
}
|
||
if !math.IsNaN(wi[i]) {
|
||
t.Errorf("%v: wi modified before [ilo:ihi+1] block", prefix)
|
||
}
|
||
}
|
||
for i := ihi + 1; i < n; i++ {
|
||
if !math.IsNaN(wr[i]) {
|
||
t.Errorf("%v: wr modified after [ilo:ihi+1] block", prefix)
|
||
}
|
||
if !math.IsNaN(wi[i]) {
|
||
t.Errorf("%v: wi modified after [ilo:ihi+1] block", prefix)
|
||
}
|
||
}
|
||
|
||
var hasReal bool
|
||
for i := start; i <= ihi; {
|
||
if wi[i] == 0 { // Real eigenvalue.
|
||
hasReal = true
|
||
|
||
// Check that the eigenvalue corresponds to a 1×1 block
|
||
// on the diagonal of H.
|
||
if wantt {
|
||
if wr[i] != h.Data[i*h.Stride+i] {
|
||
t.Errorf("%v: wr[%v] != H[%v,%v]", prefix, i, i, i)
|
||
}
|
||
for _, index := range []struct{ r, c int }{
|
||
{i, i - 1}, // h h h
|
||
{i + 1, i - 1}, // 0 wr[i] h
|
||
{i + 1, i}, // 0 0 h
|
||
} {
|
||
if index.r >= n || index.c < 0 {
|
||
continue
|
||
}
|
||
if h.Data[index.r*h.Stride+index.c] != 0 {
|
||
t.Errorf("%v: H[%v,%v] != 0", prefix, index.r, index.c)
|
||
}
|
||
}
|
||
}
|
||
|
||
i++
|
||
continue
|
||
}
|
||
|
||
// Complex eigenvalue.
|
||
|
||
// In the conjugate pair the real parts must be equal.
|
||
if wr[i] != wr[i+1] {
|
||
t.Errorf("%v: real part of conjugate pair not equal, i=%v", prefix, i)
|
||
}
|
||
// The first imaginary part must be positive.
|
||
if wi[i] < 0 {
|
||
t.Errorf("%v: wi[%v] not positive", prefix, i)
|
||
}
|
||
// The second imaginary part must be negative with the same
|
||
// magnitude.
|
||
if wi[i] != -wi[i+1] {
|
||
t.Errorf("%v: wi[%v] != wi[%v]", prefix, i, i+1)
|
||
}
|
||
if wantt {
|
||
// Check that wi[i] has the correct value.
|
||
if wr[i] != h.Data[i*h.Stride+i] {
|
||
t.Errorf("%v: wr[%v] != H[%v,%v]", prefix, i, i, i)
|
||
}
|
||
if wr[i] != h.Data[(i+1)*h.Stride+i+1] {
|
||
t.Errorf("%v: wr[%v] != H[%v,%v]", prefix, i, i+1, i+1)
|
||
}
|
||
prod := math.Abs(h.Data[(i+1)*h.Stride+i] * h.Data[i*h.Stride+i+1])
|
||
if math.Abs(math.Sqrt(prod)-wi[i]) > tol {
|
||
t.Errorf("%v: unexpected value of wi[%v]: want %v, got %v", prefix, i, math.Sqrt(prod), wi[i])
|
||
}
|
||
|
||
// Check that the corresponding diagonal block is 2×2.
|
||
for _, index := range []struct{ r, c int }{
|
||
{i, i - 1}, // i
|
||
{i + 1, i - 1}, // h h h h
|
||
{i + 2, i - 1}, // 0 wr[i] b h i
|
||
{i + 2, i}, // 0 c wr[i+1] h
|
||
{i + 2, i + 1}, // 0 0 0 h
|
||
} {
|
||
if index.r >= n || index.c < 0 {
|
||
continue
|
||
}
|
||
if h.Data[index.r*h.Stride+index.c] != 0 {
|
||
t.Errorf("%v: H[%v,%v] != 0", prefix, index.r, index.c)
|
||
}
|
||
}
|
||
}
|
||
i += 2
|
||
}
|
||
|
||
// If the number of found eigenvalues is odd, at least one must be real.
|
||
if (ihi+1-start)%2 != 0 && !hasReal {
|
||
t.Errorf("%v: expected at least one real eigenvalue", prefix)
|
||
}
|
||
}
|