// 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 ( "testing" "golang.org/x/exp/rand" "gonum.org/v1/gonum/blas" "gonum.org/v1/gonum/blas/blas64" "gonum.org/v1/gonum/floats" ) type Dgetf2er interface { Dgetf2(m, n int, a []float64, lda int, ipiv []int) bool } func Dgetf2Test(t *testing.T, impl Dgetf2er) { rnd := rand.New(rand.NewSource(1)) for _, test := range []struct { m, n, lda int }{ {10, 10, 0}, {10, 5, 0}, {10, 5, 0}, {10, 10, 20}, {5, 10, 20}, {10, 5, 20}, } { m := test.m n := test.n lda := test.lda if lda == 0 { lda = n } a := make([]float64, m*lda) for i := range a { a[i] = rnd.Float64() } aCopy := make([]float64, len(a)) copy(aCopy, a) mn := min(m, n) ipiv := make([]int, mn) for i := range ipiv { ipiv[i] = rnd.Int() } ok := impl.Dgetf2(m, n, a, lda, ipiv) checkPLU(t, ok, m, n, lda, ipiv, a, aCopy, 1e-14, true) } // Test with singular matrices (random matrices are almost surely non-singular). for _, test := range []struct { m, n, lda int a []float64 }{ { m: 2, n: 2, lda: 2, a: []float64{ 1, 0, 0, 0, }, }, { m: 2, n: 2, lda: 2, a: []float64{ 1, 5, 2, 10, }, }, { m: 3, n: 3, lda: 3, // row 3 = row1 + 2 * row2 a: []float64{ 1, 5, 7, 2, 10, -3, 5, 25, 1, }, }, { m: 3, n: 4, lda: 4, // row 3 = row1 + 2 * row2 a: []float64{ 1, 5, 7, 9, 2, 10, -3, 11, 5, 25, 1, 31, }, }, } { if impl.Dgetf2(test.m, test.n, test.a, test.lda, make([]int, min(test.m, test.n))) { t.Log("Returned ok with singular matrix.") } } } // checkPLU checks that the PLU factorization contained in factorize matches // the original matrix contained in original. func checkPLU(t *testing.T, ok bool, m, n, lda int, ipiv []int, factorized, original []float64, tol float64, print bool) { var hasZeroDiagonal bool for i := 0; i < min(m, n); i++ { if factorized[i*lda+i] == 0 { hasZeroDiagonal = true break } } if hasZeroDiagonal && ok { t.Error("Has a zero diagonal but returned ok") } if !hasZeroDiagonal && !ok { t.Error("Non-zero diagonal but returned !ok") } // Check that the LU decomposition is correct. mn := min(m, n) l := make([]float64, m*mn) ldl := mn u := make([]float64, mn*n) ldu := n for i := 0; i < m; i++ { for j := 0; j < n; j++ { v := factorized[i*lda+j] switch { case i == j: l[i*ldl+i] = 1 u[i*ldu+i] = v case i > j: l[i*ldl+j] = v case i < j: u[i*ldu+j] = v } } } LU := blas64.General{ Rows: m, Cols: n, Stride: n, Data: make([]float64, m*n), } U := blas64.General{ Rows: mn, Cols: n, Stride: ldu, Data: u, } L := blas64.General{ Rows: m, Cols: mn, Stride: ldl, Data: l, } blas64.Gemm(blas.NoTrans, blas.NoTrans, 1, L, U, 0, LU) p := make([]float64, m*m) ldp := m for i := 0; i < m; i++ { p[i*ldp+i] = 1 } for i := len(ipiv) - 1; i >= 0; i-- { v := ipiv[i] blas64.Swap(m, blas64.Vector{Inc: 1, Data: p[i*ldp:]}, blas64.Vector{Inc: 1, Data: p[v*ldp:]}) } P := blas64.General{ Rows: m, Cols: m, Stride: m, Data: p, } aComp := blas64.General{ Rows: m, Cols: n, Stride: lda, Data: make([]float64, m*lda), } copy(aComp.Data, factorized) blas64.Gemm(blas.NoTrans, blas.NoTrans, 1, P, LU, 0, aComp) if !floats.EqualApprox(aComp.Data, original, tol) { if print { t.Errorf("PLU multiplication does not match original matrix.\nWant: %v\nGot: %v", original, aComp.Data) return } t.Error("PLU multiplication does not match original matrix.") } }