Files
gonum/lapack/testlapack/general.go
2024-04-06 20:24:32 +10:30

1406 lines
32 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright ©2015 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/cmplx"
"testing"
"golang.org/x/exp/rand"
"gonum.org/v1/gonum/blas"
"gonum.org/v1/gonum/blas/blas64"
"gonum.org/v1/gonum/lapack"
)
const (
// dlamchE is the machine epsilon. For IEEE this is 2^{-53}.
dlamchE = 0x1p-53
dlamchB = 2
dlamchP = dlamchB * dlamchE
// dlamchS is the smallest normal number. For IEEE this is 2^{-1022}.
dlamchS = 0x1p-1022
safmin = dlamchS
safmax = 1 / safmin
ulp = dlamchP
smlnum = safmin / ulp
bignum = safmax * ulp
)
// worklen describes how much workspace a test should use.
type worklen int
const (
minimumWork worklen = iota
mediumWork
optimumWork
)
func (wl worklen) String() string {
switch wl {
case minimumWork:
return "minimum"
case mediumWork:
return "medium"
case optimumWork:
return "optimum"
}
return ""
}
func normToString(norm lapack.MatrixNorm) string {
switch norm {
case lapack.MaxAbs:
return "MaxAbs"
case lapack.MaxRowSum:
return "MaxRowSum"
case lapack.MaxColumnSum:
return "MaxColSum"
case lapack.Frobenius:
return "Frobenius"
default:
panic("invalid norm")
}
}
func uploToString(uplo blas.Uplo) string {
switch uplo {
case blas.Lower:
return "Lower"
case blas.Upper:
return "Upper"
default:
panic("invalid uplo")
}
}
func diagToString(diag blas.Diag) string {
switch diag {
case blas.NonUnit:
return "NonUnit"
case blas.Unit:
return "Unit"
default:
panic("invalid diag")
}
}
func sideToString(side blas.Side) string {
switch side {
case blas.Left:
return "Left"
case blas.Right:
return "Right"
default:
panic("invalid side")
}
}
func transToString(trans blas.Transpose) string {
switch trans {
case blas.NoTrans:
return "NoTrans"
case blas.Trans:
return "Trans"
case blas.ConjTrans:
return "ConjTrans"
default:
panic("invalid trans")
}
}
// nanSlice allocates a new slice of length n filled with NaN.
func nanSlice(n int) []float64 {
s := make([]float64, n)
for i := range s {
s[i] = math.NaN()
}
return s
}
// randomSlice allocates a new slice of length n filled with random values.
func randomSlice(n int, rnd *rand.Rand) []float64 {
s := make([]float64, n)
for i := range s {
s[i] = rnd.NormFloat64()
}
return s
}
// nanGeneral allocates a new r×c general matrix filled with NaN values.
func nanGeneral(r, c, stride int) blas64.General {
if r < 0 || c < 0 {
panic("bad matrix size")
}
if r == 0 || c == 0 {
return blas64.General{Stride: max(1, stride)}
}
if stride < c {
panic("bad stride")
}
return blas64.General{
Rows: r,
Cols: c,
Stride: stride,
Data: nanSlice((r-1)*stride + c),
}
}
// randomGeneral allocates a new r×c general matrix filled with random
// numbers. Out-of-range elements are filled with NaN values.
func randomGeneral(r, c, stride int, rnd *rand.Rand) blas64.General {
ans := nanGeneral(r, c, stride)
for i := 0; i < r; i++ {
for j := 0; j < c; j++ {
ans.Data[i*ans.Stride+j] = rnd.NormFloat64()
}
}
return ans
}
// randomHessenberg allocates a new n×n Hessenberg matrix filled with zeros
// under the first subdiagonal and with random numbers elsewhere. Out-of-range
// elements are filled with NaN values.
func randomHessenberg(n, stride int, rnd *rand.Rand) blas64.General {
ans := nanGeneral(n, n, stride)
for i := 0; i < n; i++ {
for j := 0; j < i-1; j++ {
ans.Data[i*ans.Stride+j] = 0
}
for j := max(0, i-1); j < n; j++ {
ans.Data[i*ans.Stride+j] = rnd.NormFloat64()
}
}
return ans
}
// randomSchurCanonical returns a random, general matrix in Schur canonical
// form, that is, block upper triangular with 1×1 and 2×2 diagonal blocks where
// each 2×2 diagonal block has its diagonal elements equal and its off-diagonal
// elements of opposite sign. bad controls whether the returned matrix will have
// zero or tiny eigenvalues.
func randomSchurCanonical(n, stride int, bad bool, rnd *rand.Rand) (t blas64.General, wr, wi []float64) {
t = randomGeneral(n, n, stride, rnd)
// Zero out the lower triangle including the diagonal which will be set later.
for i := 0; i < t.Rows; i++ {
for j := 0; j <= i; j++ {
t.Data[i*t.Stride+j] = 0
}
}
// Randomly create 2×2 diagonal blocks.
for i := 0; i < t.Rows; {
a := rnd.NormFloat64()
if bad && rnd.Float64() < 0.5 {
if rnd.Float64() < 0.5 {
// A quarter of real parts of eigenvalues will be tiny.
a = dlamchS
} else {
// A quarter of them will be zero.
a = 0
}
}
// A half of eigenvalues will be real.
if rnd.Float64() < 0.5 || i == t.Rows-1 {
// Store 1×1 block at the diagonal of T.
t.Data[i*t.Stride+i] = a
wr = append(wr, a)
wi = append(wi, 0)
i++
continue
}
// Diagonal elements are equal.
d := a
// Element under the diagonal is "normal".
c := rnd.NormFloat64()
// Element above the diagonal cannot be zero.
var b float64
if bad && rnd.Float64() < 0.5 {
b = dlamchS
} else {
b = rnd.NormFloat64()
}
// Make sure off-diagonal elements are of opposite sign.
if math.Signbit(b) == math.Signbit(c) {
c *= -1
}
// Store 2×2 block at the diagonal of T.
t.Data[i*t.Stride+i], t.Data[i*t.Stride+i+1] = a, b
t.Data[(i+1)*t.Stride+i], t.Data[(i+1)*t.Stride+i+1] = c, d
wr = append(wr, a, a)
im := math.Sqrt(math.Abs(b)) * math.Sqrt(math.Abs(c))
wi = append(wi, im, -im)
i += 2
}
return t, wr, wi
}
// blockedUpperTriGeneral returns a normal random, general matrix in the form
//
// c-k-l k l
// A = k [ 0 A12 A13 ] if r-k-l >= 0;
// l [ 0 0 A23 ]
// r-k-l [ 0 0 0 ]
//
// c-k-l k l
// A = k [ 0 A12 A13 ] if r-k-l < 0;
// r-k [ 0 0 A23 ]
//
// where the k×k matrix A12 and l×l matrix is non-singular
// upper triangular. A23 is l×l upper triangular if r-k-l >= 0,
// otherwise A23 is (r-k)×l upper trapezoidal.
func blockedUpperTriGeneral(r, c, k, l, stride int, kblock bool, rnd *rand.Rand) blas64.General {
t := l
if kblock {
t += k
}
ans := zeros(r, c, stride)
for i := 0; i < min(r, t); i++ {
var v float64
for v == 0 {
v = rnd.NormFloat64()
}
ans.Data[i*ans.Stride+i+(c-t)] = v
}
for i := 0; i < min(r, t); i++ {
for j := i + (c - t) + 1; j < c; j++ {
ans.Data[i*ans.Stride+j] = rnd.NormFloat64()
}
}
return ans
}
// nanTriangular allocates a new r×c triangular matrix filled with NaN values.
func nanTriangular(uplo blas.Uplo, n, stride int) blas64.Triangular {
if n < 0 {
panic("bad matrix size")
}
if n == 0 {
return blas64.Triangular{
Stride: max(1, stride),
Uplo: uplo,
Diag: blas.NonUnit,
}
}
if stride < n {
panic("bad stride")
}
return blas64.Triangular{
N: n,
Stride: stride,
Data: nanSlice((n-1)*stride + n),
Uplo: uplo,
Diag: blas.NonUnit,
}
}
// generalOutsideAllNaN returns whether all out-of-range elements have NaN
// values.
func generalOutsideAllNaN(a blas64.General) bool {
// Check after last column.
for i := 0; i < a.Rows-1; i++ {
for _, v := range a.Data[i*a.Stride+a.Cols : i*a.Stride+a.Stride] {
if !math.IsNaN(v) {
return false
}
}
}
// Check after last element.
last := (a.Rows-1)*a.Stride + a.Cols
if a.Rows == 0 || a.Cols == 0 {
last = 0
}
for _, v := range a.Data[last:] {
if !math.IsNaN(v) {
return false
}
}
return true
}
// triangularOutsideAllNaN returns whether all out-of-triangle elements have NaN
// values.
func triangularOutsideAllNaN(a blas64.Triangular) bool {
if a.Uplo == blas.Upper {
// Check below diagonal.
for i := 0; i < a.N; i++ {
for _, v := range a.Data[i*a.Stride : i*a.Stride+i] {
if !math.IsNaN(v) {
return false
}
}
}
// Check after last column.
for i := 0; i < a.N-1; i++ {
for _, v := range a.Data[i*a.Stride+a.N : i*a.Stride+a.Stride] {
if !math.IsNaN(v) {
return false
}
}
}
} else {
// Check above diagonal.
for i := 0; i < a.N-1; i++ {
for _, v := range a.Data[i*a.Stride+i+1 : i*a.Stride+a.Stride] {
if !math.IsNaN(v) {
return false
}
}
}
}
// Check after last element.
for _, v := range a.Data[max(0, a.N-1)*a.Stride+a.N:] {
if !math.IsNaN(v) {
return false
}
}
return true
}
// transposeGeneral returns a new general matrix that is the transpose of the
// input. Nothing is done with data outside the {rows, cols} limit of the general.
func transposeGeneral(a blas64.General) blas64.General {
ans := blas64.General{
Rows: a.Cols,
Cols: a.Rows,
Stride: a.Rows,
Data: make([]float64, a.Cols*a.Rows),
}
for i := 0; i < a.Rows; i++ {
for j := 0; j < a.Cols; j++ {
ans.Data[j*ans.Stride+i] = a.Data[i*a.Stride+j]
}
}
return ans
}
// columnNorms returns the column norms of a.
func columnNorms(m, n int, a []float64, lda int) []float64 {
bi := blas64.Implementation()
norms := make([]float64, n)
for j := 0; j < n; j++ {
norms[j] = bi.Dnrm2(m, a[j:], lda)
}
return norms
}
// extractVMat collects the single reflectors from a into a matrix.
func extractVMat(m, n int, a []float64, lda int, direct lapack.Direct, store lapack.StoreV) blas64.General {
k := min(m, n)
switch {
default:
panic("not implemented")
case direct == lapack.Forward && store == lapack.ColumnWise:
v := blas64.General{
Rows: m,
Cols: k,
Stride: k,
Data: make([]float64, m*k),
}
for i := 0; i < k; i++ {
for j := 0; j < i; j++ {
v.Data[j*v.Stride+i] = 0
}
v.Data[i*v.Stride+i] = 1
for j := i + 1; j < m; j++ {
v.Data[j*v.Stride+i] = a[j*lda+i]
}
}
return v
case direct == lapack.Forward && store == lapack.RowWise:
v := blas64.General{
Rows: k,
Cols: n,
Stride: n,
Data: make([]float64, k*n),
}
for i := 0; i < k; i++ {
for j := 0; j < i; j++ {
v.Data[i*v.Stride+j] = 0
}
v.Data[i*v.Stride+i] = 1
for j := i + 1; j < n; j++ {
v.Data[i*v.Stride+j] = a[i*lda+j]
}
}
return v
}
}
// constructBidiagonal constructs a bidiagonal matrix with the given diagonal
// and off-diagonal elements.
func constructBidiagonal(uplo blas.Uplo, n int, d, e []float64) blas64.General {
bMat := blas64.General{
Rows: n,
Cols: n,
Stride: n,
Data: make([]float64, n*n),
}
for i := 0; i < n-1; i++ {
bMat.Data[i*bMat.Stride+i] = d[i]
if uplo == blas.Upper {
bMat.Data[i*bMat.Stride+i+1] = e[i]
} else {
bMat.Data[(i+1)*bMat.Stride+i] = e[i]
}
}
bMat.Data[(n-1)*bMat.Stride+n-1] = d[n-1]
return bMat
}
// constructVMat transforms the v matrix based on the storage.
func constructVMat(vMat blas64.General, store lapack.StoreV, direct lapack.Direct) blas64.General {
m := vMat.Rows
k := vMat.Cols
switch {
default:
panic("not implemented")
case store == lapack.ColumnWise && direct == lapack.Forward:
ldv := k
v := make([]float64, m*k)
for i := 0; i < m; i++ {
for j := 0; j < k; j++ {
if j > i {
v[i*ldv+j] = 0
} else if j == i {
v[i*ldv+i] = 1
} else {
v[i*ldv+j] = vMat.Data[i*vMat.Stride+j]
}
}
}
return blas64.General{
Rows: m,
Cols: k,
Stride: k,
Data: v,
}
case store == lapack.RowWise && direct == lapack.Forward:
ldv := m
v := make([]float64, m*k)
for i := 0; i < m; i++ {
for j := 0; j < k; j++ {
if j > i {
v[j*ldv+i] = 0
} else if j == i {
v[j*ldv+i] = 1
} else {
v[j*ldv+i] = vMat.Data[i*vMat.Stride+j]
}
}
}
return blas64.General{
Rows: k,
Cols: m,
Stride: m,
Data: v,
}
case store == lapack.ColumnWise && direct == lapack.Backward:
rowsv := m
ldv := k
v := make([]float64, m*k)
for i := 0; i < m; i++ {
for j := 0; j < k; j++ {
vrow := rowsv - i - 1
vcol := k - j - 1
if j > i {
v[vrow*ldv+vcol] = 0
} else if j == i {
v[vrow*ldv+vcol] = 1
} else {
v[vrow*ldv+vcol] = vMat.Data[i*vMat.Stride+j]
}
}
}
return blas64.General{
Rows: rowsv,
Cols: ldv,
Stride: ldv,
Data: v,
}
case store == lapack.RowWise && direct == lapack.Backward:
rowsv := k
ldv := m
v := make([]float64, m*k)
for i := 0; i < m; i++ {
for j := 0; j < k; j++ {
vcol := ldv - i - 1
vrow := k - j - 1
if j > i {
v[vrow*ldv+vcol] = 0
} else if j == i {
v[vrow*ldv+vcol] = 1
} else {
v[vrow*ldv+vcol] = vMat.Data[i*vMat.Stride+j]
}
}
}
return blas64.General{
Rows: rowsv,
Cols: ldv,
Stride: ldv,
Data: v,
}
}
}
func constructH(tau []float64, v blas64.General, store lapack.StoreV, direct lapack.Direct) blas64.General {
m := v.Rows
k := v.Cols
if store == lapack.RowWise {
m, k = k, m
}
h := blas64.General{
Rows: m,
Cols: m,
Stride: m,
Data: make([]float64, m*m),
}
for i := 0; i < m; i++ {
h.Data[i*m+i] = 1
}
for i := 0; i < k; i++ {
vecData := make([]float64, m)
if store == lapack.ColumnWise {
for j := 0; j < m; j++ {
vecData[j] = v.Data[j*v.Cols+i]
}
} else {
for j := 0; j < m; j++ {
vecData[j] = v.Data[i*v.Cols+j]
}
}
vec := blas64.Vector{
Inc: 1,
Data: vecData,
}
hi := blas64.General{
Rows: m,
Cols: m,
Stride: m,
Data: make([]float64, m*m),
}
for i := 0; i < m; i++ {
hi.Data[i*m+i] = 1
}
// hi = I - tau * v * vᵀ
blas64.Ger(-tau[i], vec, vec, hi)
hcopy := blas64.General{
Rows: m,
Cols: m,
Stride: m,
Data: make([]float64, m*m),
}
copy(hcopy.Data, h.Data)
if direct == lapack.Forward {
// H = H * H_I in forward mode
blas64.Gemm(blas.NoTrans, blas.NoTrans, 1, hcopy, hi, 0, h)
} else {
// H = H_I * H in backward mode
blas64.Gemm(blas.NoTrans, blas.NoTrans, 1, hi, hcopy, 0, h)
}
}
return h
}
// constructQ constructs the Q matrix from the result of dgeqrf and dgeqr2.
func constructQ(kind string, m, n int, a []float64, lda int, tau []float64) blas64.General {
k := min(m, n)
return constructQK(kind, m, n, k, a, lda, tau)
}
// constructQK constructs the Q matrix from the result of dgeqrf and dgeqr2 using
// the first k reflectors.
func constructQK(kind string, m, n, k int, a []float64, lda int, tau []float64) blas64.General {
var sz int
switch kind {
case "QR":
sz = m
case "LQ", "RQ":
sz = n
}
q := blas64.General{
Rows: sz,
Cols: sz,
Stride: max(1, sz),
Data: make([]float64, sz*sz),
}
for i := 0; i < sz; i++ {
q.Data[i*sz+i] = 1
}
qCopy := blas64.General{
Rows: q.Rows,
Cols: q.Cols,
Stride: q.Stride,
Data: make([]float64, len(q.Data)),
}
for i := 0; i < k; i++ {
h := blas64.General{
Rows: sz,
Cols: sz,
Stride: max(1, sz),
Data: make([]float64, sz*sz),
}
for j := 0; j < sz; j++ {
h.Data[j*sz+j] = 1
}
vVec := blas64.Vector{
Inc: 1,
Data: make([]float64, sz),
}
switch kind {
case "QR":
vVec.Data[i] = 1
for j := i + 1; j < sz; j++ {
vVec.Data[j] = a[lda*j+i]
}
case "LQ":
vVec.Data[i] = 1
for j := i + 1; j < sz; j++ {
vVec.Data[j] = a[i*lda+j]
}
case "RQ":
for j := 0; j < n-k+i; j++ {
vVec.Data[j] = a[(m-k+i)*lda+j]
}
vVec.Data[n-k+i] = 1
}
blas64.Ger(-tau[i], vVec, vVec, h)
copy(qCopy.Data, q.Data)
// Multiply q by the new h.
switch kind {
case "QR", "RQ":
blas64.Gemm(blas.NoTrans, blas.NoTrans, 1, qCopy, h, 0, q)
case "LQ":
blas64.Gemm(blas.NoTrans, blas.NoTrans, 1, h, qCopy, 0, q)
}
}
return q
}
// checkBidiagonal checks the bidiagonal decomposition from dlabrd and dgebd2.
// The input to this function is the answer returned from the routines, stored
// in a, d, e, tauP, and tauQ. The data of original A matrix (before
// decomposition) is input in aCopy.
//
// checkBidiagonal constructs the V and U matrices, and from them constructs Q
// and P. Using these constructions, it checks that Qᵀ * A * P and checks that
// the result is bidiagonal.
func checkBidiagonal(t *testing.T, m, n, nb int, a []float64, lda int, d, e, tauP, tauQ, aCopy []float64) {
// Check the answer.
// Construct V and U.
qMat := constructQPBidiagonal(lapack.ApplyQ, m, n, nb, a, lda, tauQ)
pMat := constructQPBidiagonal(lapack.ApplyP, m, n, nb, a, lda, tauP)
// Compute Qᵀ * A * P.
aMat := blas64.General{
Rows: m,
Cols: n,
Stride: lda,
Data: make([]float64, len(aCopy)),
}
copy(aMat.Data, aCopy)
tmp1 := blas64.General{
Rows: m,
Cols: n,
Stride: n,
Data: make([]float64, m*n),
}
blas64.Gemm(blas.Trans, blas.NoTrans, 1, qMat, aMat, 0, tmp1)
tmp2 := blas64.General{
Rows: m,
Cols: n,
Stride: n,
Data: make([]float64, m*n),
}
blas64.Gemm(blas.NoTrans, blas.NoTrans, 1, tmp1, pMat, 0, tmp2)
// Check that the first nb rows and cols of tm2 are upper bidiagonal
// if m >= n, and lower bidiagonal otherwise.
correctDiag := true
matchD := true
matchE := true
for i := 0; i < m; i++ {
for j := 0; j < n; j++ {
if i >= nb && j >= nb {
continue
}
v := tmp2.Data[i*tmp2.Stride+j]
if i == j {
if math.Abs(d[i]-v) > 1e-12 {
matchD = false
}
continue
}
if m >= n && i == j-1 {
if math.Abs(e[j-1]-v) > 1e-12 {
matchE = false
}
continue
}
if m < n && i-1 == j {
if math.Abs(e[i-1]-v) > 1e-12 {
matchE = false
}
continue
}
if math.Abs(v) > 1e-12 {
correctDiag = false
}
}
}
if !correctDiag {
t.Errorf("Updated A not bi-diagonal")
}
if !matchD {
fmt.Println("d = ", d)
t.Errorf("D Mismatch")
}
if !matchE {
t.Errorf("E mismatch")
}
}
// constructQPBidiagonal constructs Q or P from the Bidiagonal decomposition
// computed by dlabrd and bgebd2.
func constructQPBidiagonal(vect lapack.ApplyOrtho, m, n, nb int, a []float64, lda int, tau []float64) blas64.General {
sz := n
if vect == lapack.ApplyQ {
sz = m
}
var ldv int
var v blas64.General
if vect == lapack.ApplyQ {
ldv = nb
v = blas64.General{
Rows: m,
Cols: nb,
Stride: ldv,
Data: make([]float64, m*ldv),
}
} else {
ldv = n
v = blas64.General{
Rows: nb,
Cols: n,
Stride: ldv,
Data: make([]float64, m*ldv),
}
}
if vect == lapack.ApplyQ {
if m >= n {
for i := 0; i < m; i++ {
for j := 0; j <= min(nb-1, i); j++ {
if i == j {
v.Data[i*ldv+j] = 1
continue
}
v.Data[i*ldv+j] = a[i*lda+j]
}
}
} else {
for i := 1; i < m; i++ {
for j := 0; j <= min(nb-1, i-1); j++ {
if i-1 == j {
v.Data[i*ldv+j] = 1
continue
}
v.Data[i*ldv+j] = a[i*lda+j]
}
}
}
} else {
if m < n {
for i := 0; i < nb; i++ {
for j := i; j < n; j++ {
if i == j {
v.Data[i*ldv+j] = 1
continue
}
v.Data[i*ldv+j] = a[i*lda+j]
}
}
} else {
for i := 0; i < nb; i++ {
for j := i + 1; j < n; j++ {
if j-1 == i {
v.Data[i*ldv+j] = 1
continue
}
v.Data[i*ldv+j] = a[i*lda+j]
}
}
}
}
// The variable name is a computation of Q, but the algorithm is mostly the
// same for computing P (just with different data).
qMat := blas64.General{
Rows: sz,
Cols: sz,
Stride: sz,
Data: make([]float64, sz*sz),
}
hMat := blas64.General{
Rows: sz,
Cols: sz,
Stride: sz,
Data: make([]float64, sz*sz),
}
// set Q to I
for i := 0; i < sz; i++ {
qMat.Data[i*qMat.Stride+i] = 1
}
for i := 0; i < nb; i++ {
qCopy := blas64.General{Rows: qMat.Rows, Cols: qMat.Cols, Stride: qMat.Stride, Data: make([]float64, len(qMat.Data))}
copy(qCopy.Data, qMat.Data)
// Set g and h to I
for i := 0; i < sz; i++ {
for j := 0; j < sz; j++ {
if i == j {
hMat.Data[i*sz+j] = 1
} else {
hMat.Data[i*sz+j] = 0
}
}
}
var vi blas64.Vector
// H -= tauQ[i] * v[i] * v[i]^t
if vect == lapack.ApplyQ {
vi = blas64.Vector{
Inc: v.Stride,
Data: v.Data[i:],
}
} else {
vi = blas64.Vector{
Inc: 1,
Data: v.Data[i*v.Stride:],
}
}
blas64.Ger(-tau[i], vi, vi, hMat)
// Q = Q * G[1]
blas64.Gemm(blas.NoTrans, blas.NoTrans, 1, qCopy, hMat, 0, qMat)
}
return qMat
}
// printRowise prints the matrix with one row per line. This is useful for debugging.
// If beyond is true, it prints beyond the final column to lda. If false, only
// the columns are printed.
//
//lint:ignore U1000 This is useful for debugging.
func printRowise(a []float64, m, n, lda int, beyond bool) {
for i := 0; i < m; i++ {
end := n
if beyond {
end = lda
}
fmt.Println(a[i*lda : i*lda+end])
}
}
func copyGeneral(dst, src blas64.General) {
r := min(dst.Rows, src.Rows)
c := min(dst.Cols, src.Cols)
for i := 0; i < r; i++ {
copy(dst.Data[i*dst.Stride:i*dst.Stride+c], src.Data[i*src.Stride:i*src.Stride+c])
}
}
// cloneGeneral allocates and returns an exact copy of the given general matrix.
func cloneGeneral(a blas64.General) blas64.General {
c := a
c.Data = make([]float64, len(a.Data))
copy(c.Data, a.Data)
return c
}
// equalGeneral returns whether the general matrices a and b are equal.
func equalGeneral(a, b blas64.General) bool {
if a.Rows != b.Rows || a.Cols != b.Cols {
panic("bad input")
}
for i := 0; i < a.Rows; i++ {
for j := 0; j < a.Cols; j++ {
if a.Data[i*a.Stride+j] != b.Data[i*b.Stride+j] {
return false
}
}
}
return true
}
// equalApproxGeneral returns whether the general matrices a and b are
// approximately equal within given tolerance.
func equalApproxGeneral(a, b blas64.General, tol float64) bool {
if a.Rows != b.Rows || a.Cols != b.Cols {
panic("bad input")
}
for i := 0; i < a.Rows; i++ {
for j := 0; j < a.Cols; j++ {
diff := a.Data[i*a.Stride+j] - b.Data[i*b.Stride+j]
if math.IsNaN(diff) || math.Abs(diff) > tol {
return false
}
}
}
return true
}
func intsEqual(a, b []int) bool {
if len(a) != len(b) {
return false
}
for i, ai := range a {
if b[i] != ai {
return false
}
}
return true
}
// randSymBand returns an n×n random symmetric positive definite band matrix
// with kd diagonals.
func randSymBand(uplo blas.Uplo, n, kd, ldab int, rnd *rand.Rand) []float64 {
// Allocate a triangular band matrix U or L and fill it with random numbers.
var ab []float64
if n > 0 {
ab = make([]float64, (n-1)*ldab+kd+1)
}
for i := range ab {
ab[i] = rnd.NormFloat64()
}
// Make sure that the matrix U or L has a sufficiently positive diagonal.
switch uplo {
case blas.Upper:
for i := 0; i < n; i++ {
ab[i*ldab] = float64(n) + rnd.Float64()
}
case blas.Lower:
for i := 0; i < n; i++ {
ab[i*ldab+kd] = float64(n) + rnd.Float64()
}
}
// Compute Uᵀ*U or L*Lᵀ. The resulting (symmetric) matrix A will be
// positive definite and well-conditioned.
dsbmm(uplo, n, kd, ab, ldab)
return ab
}
// distSymBand returns the max-norm distance between the symmetric band matrices
// A and B.
func distSymBand(uplo blas.Uplo, n, kd int, a []float64, lda int, b []float64, ldb int) float64 {
var dist float64
switch uplo {
case blas.Upper:
for i := 0; i < n; i++ {
for j := 0; j < min(kd+1, n-i); j++ {
dist = math.Max(dist, math.Abs(a[i*lda+j]-b[i*ldb+j]))
}
}
case blas.Lower:
for i := 0; i < n; i++ {
for j := max(0, kd-i); j < kd+1; j++ {
dist = math.Max(dist, math.Abs(a[i*lda+j]-b[i*ldb+j]))
}
}
}
return dist
}
// eye returns an identity matrix of given order and stride.
func eye(n, stride int) blas64.General {
ans := nanGeneral(n, n, stride)
for i := 0; i < n; i++ {
for j := 0; j < n; j++ {
ans.Data[i*ans.Stride+j] = 0
}
ans.Data[i*ans.Stride+i] = 1
}
return ans
}
// zeros returns an m×n matrix with given stride filled with zeros.
func zeros(m, n, stride int) blas64.General {
a := nanGeneral(m, n, stride)
for i := 0; i < m; i++ {
for j := 0; j < n; j++ {
a.Data[i*a.Stride+j] = 0
}
}
return a
}
// extract2x2Block returns the elements of T at [0,0], [0,1], [1,0], and [1,1].
func extract2x2Block(t []float64, ldt int) (a, b, c, d float64) {
return t[0], t[1], t[ldt], t[ldt+1]
}
// isSchurCanonical returns whether the 2×2 matrix [a b; c d] is in Schur
// canonical form.
func isSchurCanonical(a, b, c, d float64) bool {
return c == 0 || (b != 0 && a == d && math.Signbit(b) != math.Signbit(c))
}
// isSchurCanonicalGeneral returns whether T is block upper triangular with 1×1
// and 2×2 diagonal blocks, each 2×2 block in Schur canonical form. The function
// checks only along the diagonal and the first subdiagonal, otherwise the lower
// triangle is not accessed.
func isSchurCanonicalGeneral(t blas64.General) bool {
n := t.Cols
if t.Rows != n {
panic("invalid matrix")
}
for j := 0; j < n-1; {
if t.Data[(j+1)*t.Stride+j] == 0 {
// 1×1 block.
for i := j + 1; i < n; i++ {
if t.Data[i*t.Stride+j] != 0 {
return false
}
}
j++
continue
}
// 2×2 block.
a, b, c, d := extract2x2Block(t.Data[j*t.Stride+j:], t.Stride)
if !isSchurCanonical(a, b, c, d) {
return false
}
for i := j + 2; i < n; i++ {
if t.Data[i*t.Stride+j] != 0 {
return false
}
}
for i := j + 2; i < n; i++ {
if t.Data[i*t.Stride+j+1] != 0 {
return false
}
}
j += 2
}
return true
}
// schurBlockEigenvalues returns the two eigenvalues of the 2×2 matrix [a b; c d]
// that must be in Schur canonical form.
//
//lint:ignore U1000 This is useful for debugging.
func schurBlockEigenvalues(a, b, c, d float64) (ev1, ev2 complex128) {
if !isSchurCanonical(a, b, c, d) {
panic("block not in Schur canonical form")
}
if c == 0 {
return complex(a, 0), complex(d, 0)
}
im := math.Sqrt(math.Abs(b)) * math.Sqrt(math.Abs(c))
return complex(a, im), complex(a, -im)
}
// schurBlockSize returns the size of the diagonal block at i-th row in the
// upper quasi-triangular matrix t in Schur canonical form, and whether i points
// to the first row of the block. For zero-sized matrices the function returns 0
// and true.
func schurBlockSize(t blas64.General, i int) (size int, first bool) {
if t.Rows != t.Cols {
panic("matrix not square")
}
if t.Rows == 0 {
return 0, true
}
if i < 0 || t.Rows <= i {
panic("index out of range")
}
first = true
if i > 0 && t.Data[i*t.Stride+i-1] != 0 {
// There is a non-zero element to the left, therefore i must
// point to the second row in a 2×2 diagonal block.
first = false
i--
}
size = 1
if i+1 < t.Rows && t.Data[(i+1)*t.Stride+i] != 0 {
// There is a non-zero element below, this must be a 2×2
// diagonal block.
size = 2
}
return size, first
}
// containsComplex returns whether z is approximately equal to one of the complex
// numbers in v. If z is found, its index in v will be also returned.
func containsComplex(v []complex128, z complex128, tol float64) (found bool, index int) {
for i := range v {
if cmplx.Abs(v[i]-z) < tol {
return true, i
}
}
return false, -1
}
// isAllNaN returns whether x contains only NaN values.
func isAllNaN(x []float64) bool {
for _, v := range x {
if !math.IsNaN(v) {
return false
}
}
return true
}
// isUpperHessenberg returns whether h contains only zeros below the
// subdiagonal.
func isUpperHessenberg(h blas64.General) bool {
if h.Rows != h.Cols {
panic("matrix not square")
}
n := h.Rows
for i := 0; i < n; i++ {
for j := 0; j < n; j++ {
if i > j+1 && h.Data[i*h.Stride+j] != 0 {
return false
}
}
}
return true
}
// isUpperTriangular returns whether a contains only zeros below the diagonal.
func isUpperTriangular(a blas64.General) bool {
if a.Rows != a.Cols {
panic("matrix not square")
}
n := a.Rows
for i := 1; i < n; i++ {
for j := 0; j < i; j++ {
if a.Data[i*a.Stride+j] != 0 {
return false
}
}
}
return true
}
// unbalancedSparseGeneral returns an m×n dense matrix with a random sparse
// structure consisting of nz nonzero elements. The matrix will be unbalanced by
// multiplying each element randomly by its row or column index.
func unbalancedSparseGeneral(m, n, stride int, nonzeros int, rnd *rand.Rand) blas64.General {
a := zeros(m, n, stride)
for k := 0; k < nonzeros; k++ {
i := rnd.Intn(n)
j := rnd.Intn(n)
if rnd.Float64() < 0.5 {
a.Data[i*stride+j] = float64(i+1) * rnd.NormFloat64()
} else {
a.Data[i*stride+j] = float64(j+1) * rnd.NormFloat64()
}
}
return a
}
// rootsOfUnity returns the n complex numbers whose n-th power is equal to 1.
func rootsOfUnity(n int) []complex128 {
w := make([]complex128, n)
for i := 0; i < n; i++ {
angle := math.Pi * float64(2*i) / float64(n)
w[i] = complex(math.Cos(angle), math.Sin(angle))
}
return w
}
// constructGSVDresults returns the matrices [ 0 R ], D1 and D2 described
// in the documentation of Dtgsja and Dggsvd3, and the result matrix in
// the documentation for Dggsvp3.
func constructGSVDresults(n, p, m, k, l int, a, b blas64.General, alpha, beta []float64) (zeroR, d1, d2 blas64.General) {
// [ 0 R ]
zeroR = zeros(k+l, n, n)
dst := zeroR
dst.Rows = min(m, k+l)
dst.Cols = k + l
dst.Data = zeroR.Data[n-k-l:]
src := a
src.Rows = min(m, k+l)
src.Cols = k + l
src.Data = a.Data[n-k-l:]
copyGeneral(dst, src)
if m < k+l {
// [ 0 R ]
dst.Rows = k + l - m
dst.Cols = k + l - m
dst.Data = zeroR.Data[m*zeroR.Stride+n-(k+l-m):]
src = b
src.Rows = k + l - m
src.Cols = k + l - m
src.Data = b.Data[(m-k)*b.Stride+n+m-k-l:]
copyGeneral(dst, src)
}
// D1
d1 = zeros(m, k+l, k+l)
for i := 0; i < k; i++ {
d1.Data[i*d1.Stride+i] = 1
}
for i := k; i < min(m, k+l); i++ {
d1.Data[i*d1.Stride+i] = alpha[i]
}
// D2
d2 = zeros(p, k+l, k+l)
for i := 0; i < min(l, m-k); i++ {
d2.Data[i*d2.Stride+i+k] = beta[k+i]
}
for i := m - k; i < l; i++ {
d2.Data[i*d2.Stride+i+k] = 1
}
return zeroR, d1, d2
}
func constructGSVPresults(n, p, m, k, l int, a, b blas64.General) (zeroA, zeroB blas64.General) {
zeroA = zeros(m, n, n)
dst := zeroA
dst.Rows = min(m, k+l)
dst.Cols = k + l
dst.Data = zeroA.Data[n-k-l:]
src := a
dst.Rows = min(m, k+l)
src.Cols = k + l
src.Data = a.Data[n-k-l:]
copyGeneral(dst, src)
zeroB = zeros(p, n, n)
dst = zeroB
dst.Rows = l
dst.Cols = l
dst.Data = zeroB.Data[n-l:]
src = b
dst.Rows = l
src.Cols = l
src.Data = b.Data[n-l:]
copyGeneral(dst, src)
return zeroA, zeroB
}
// distFromIdentity returns the L-infinity distance of an n×n matrix A from the
// identity. If A contains NaN elements, distFromIdentity will return +inf.
func distFromIdentity(n int, a []float64, lda int) float64 {
var dist float64
for i := 0; i < n; i++ {
for j := 0; j < n; j++ {
aij := a[i*lda+j]
if math.IsNaN(aij) {
return math.Inf(1)
}
if i == j {
dist = math.Max(dist, math.Abs(aij-1))
} else {
dist = math.Max(dist, math.Abs(aij))
}
}
}
return dist
}
func sameFloat64(a, b float64) bool {
return a == b || math.IsNaN(a) && math.IsNaN(b)
}
// sameLowerTri returns whether n×n matrices A and B are same under the diagonal.
func sameLowerTri(n int, a []float64, lda int, b []float64, ldb int) bool {
for i := 1; i < n; i++ {
for j := 0; j < i; j++ {
aij := a[i*lda+j]
bij := b[i*ldb+j]
if !sameFloat64(aij, bij) {
return false
}
}
}
return true
}
// sameUpperTri returns whether n×n matrices A and B are same above the diagonal.
func sameUpperTri(n int, a []float64, lda int, b []float64, ldb int) bool {
for i := 0; i < n-1; i++ {
for j := i + 1; j < n; j++ {
aij := a[i*lda+j]
bij := b[i*ldb+j]
if !sameFloat64(aij, bij) {
return false
}
}
}
return true
}
// svdJobString returns a string representation of job.
func svdJobString(job lapack.SVDJob) string {
switch job {
case lapack.SVDAll:
return "All"
case lapack.SVDStore:
return "Store"
case lapack.SVDOverwrite:
return "Overwrite"
case lapack.SVDNone:
return "None"
}
return "unknown SVD job"
}
// residualOrthogonal returns the residual
//
// |I - Q * Qᵀ| if m < n or (m == n and rowwise == true),
// |I - Qᵀ * Q| otherwise.
//
// It can be used to check that the matrix Q is orthogonal.
func residualOrthogonal(q blas64.General, rowwise bool) float64 {
m, n := q.Rows, q.Cols
if m == 0 || n == 0 {
return 0
}
var transq blas.Transpose
if m < n || (m == n && rowwise) {
transq = blas.NoTrans
} else {
transq = blas.Trans
}
minmn := min(m, n)
// Set work = I.
work := blas64.Symmetric{
Uplo: blas.Upper,
N: minmn,
Data: make([]float64, minmn*minmn),
Stride: minmn,
}
for i := 0; i < minmn; i++ {
work.Data[i*work.Stride+i] = 1
}
// Compute
// work = work - Q * Qᵀ = I - Q * Qᵀ
// or
// work = work - Qᵀ * Q = I - Qᵀ * Q
blas64.Syrk(transq, -1, q, 1, work)
return dlansy(lapack.MaxColumnSum, blas.Upper, work.N, work.Data, work.Stride)
}