func newSimilarityCircuit(uVal, uGrad, vVal, vGrad []float64) *similarityCircuit { s := similarityCircuit{ UVal: uVal, UGrad: uGrad, VVal: vVal, VGrad: vGrad, } u := blas64.Vector{Inc: 1, Data: uVal} v := blas64.Vector{Inc: 1, Data: vVal} s.UV = blas64.Dot(len(uVal), u, v) s.Unorm = blas64.Nrm2(len(uVal), u) s.Vnorm = blas64.Nrm2(len(vVal), v) s.TopVal = s.UV / (s.Unorm * s.Vnorm) return &s }
// LQ computes an LQ Decomposition for an m-by-n matrix a with m <= n by Householder // reflections. The LQ decomposition is an m-by-n orthogonal matrix q and an m-by-m // lower triangular matrix l so that a = l.q. LQ will panic with ErrShape if m > n. // // The LQ decomposition always exists, even if the matrix does not have full rank, // so LQ will never fail unless m > n. The primary use of the LQ decomposition is // in the least squares solution of non-square systems of simultaneous linear equations. // This will fail if LQIsFullRank() returns false. The matrix a is overwritten by the // decomposition. func LQ(a *Dense) LQFactor { // Initialize. m, n := a.Dims() if m > n { panic(ErrShape) } lq := *a lDiag := make([]float64, m) projs := NewVector(m, nil) // Main loop. for k := 0; k < m; k++ { hh := lq.RawRowView(k)[k:] norm := blas64.Nrm2(len(hh), blas64.Vector{Inc: 1, Data: hh}) lDiag[k] = norm if norm != 0 { hhNorm := (norm * math.Sqrt(1-hh[0]/norm)) if hhNorm == 0 { hh[0] = 0 } else { // Form k-th Householder vector. s := 1 / hhNorm hh[0] -= norm blas64.Scal(len(hh), s, blas64.Vector{Inc: 1, Data: hh}) // Apply transformation to remaining columns. if k < m-1 { a = lq.View(k+1, k, m-k-1, n-k).(*Dense) projs = projs.ViewVec(0, m-k-1) projs.MulVec(a, false, NewVector(len(hh), hh)) for j := 0; j < m-k-1; j++ { dst := a.RawRowView(j) blas64.Axpy(len(dst), -projs.at(j), blas64.Vector{Inc: 1, Data: hh}, blas64.Vector{Inc: 1, Data: dst}, ) } } } } } *a = lq return LQFactor{a, lDiag} }
// Norm returns the specified (induced) norm of the matrix a. See // https://en.wikipedia.org/wiki/Matrix_norm for the definition of an induced norm. // // Valid norms are: // 1 - The maximum absolute column sum // 2 - Frobenius norm, the square root of the sum of the squares of the elements. // Inf - The maximum absolute row sum. // Norm will panic with ErrNormOrder if an illegal norm order is specified and // with matrix.ErrShape if the matrix has zero size. func Norm(a Matrix, norm float64) float64 { r, c := a.Dims() if r == 0 || c == 0 { panic(matrix.ErrShape) } aU, aTrans := untranspose(a) var work []float64 switch rma := aU.(type) { case RawMatrixer: rm := rma.RawMatrix() n := normLapack(norm, aTrans) if n == lapack.MaxColumnSum { work = make([]float64, rm.Cols) } return lapack64.Lange(n, rm, work) case RawTriangular: rm := rma.RawTriangular() n := normLapack(norm, aTrans) if n == lapack.MaxRowSum || n == lapack.MaxColumnSum { work = make([]float64, rm.N) } return lapack64.Lantr(n, rm, work) case RawSymmetricer: rm := rma.RawSymmetric() n := normLapack(norm, aTrans) if n == lapack.MaxRowSum || n == lapack.MaxColumnSum { work = make([]float64, rm.N) } return lapack64.Lansy(n, rm, work) case *Vector: rv := rma.RawVector() switch norm { default: panic("unreachable") case 1: if aTrans { imax := blas64.Iamax(rma.n, rv) return math.Abs(rma.At(imax, 0)) } return blas64.Asum(rma.n, rv) case 2: return blas64.Nrm2(rma.n, rv) case math.Inf(1): if aTrans { return blas64.Asum(rma.n, rv) } imax := blas64.Iamax(rma.n, rv) return math.Abs(rma.At(imax, 0)) } } switch norm { default: panic("unreachable") case 1: var max float64 for j := 0; j < c; j++ { var sum float64 for i := 0; i < r; i++ { sum += math.Abs(a.At(i, j)) } if sum > max { max = sum } } return max case 2: var sum float64 for i := 0; i < r; i++ { for j := 0; j < c; j++ { v := a.At(i, j) sum += v * v } } return math.Sqrt(sum) case math.Inf(1): var max float64 for i := 0; i < r; i++ { var sum float64 for j := 0; j < c; j++ { sum += math.Abs(a.At(i, j)) } if sum > max { max = sum } } return max } }
func Dgelq2Test(t *testing.T, impl Dgelq2er) { for c, test := range []struct { m, n, lda int }{ {1, 1, 0}, {2, 2, 0}, {3, 2, 0}, {2, 3, 0}, {1, 12, 0}, {2, 6, 0}, {3, 4, 0}, {4, 3, 0}, {6, 2, 0}, {1, 12, 0}, {1, 1, 20}, {2, 2, 20}, {3, 2, 20}, {2, 3, 20}, {1, 12, 20}, {2, 6, 20}, {3, 4, 20}, {4, 3, 20}, {6, 2, 20}, {1, 12, 20}, } { n := test.n m := test.m lda := test.lda if lda == 0 { lda = test.n } k := min(m, n) tau := make([]float64, k) for i := range tau { tau[i] = rand.Float64() } work := make([]float64, m) for i := range work { work[i] = rand.Float64() } a := make([]float64, m*lda) for i := 0; i < m*lda; i++ { a[i] = rand.Float64() } aCopy := make([]float64, len(a)) copy(aCopy, a) impl.Dgelq2(m, n, a, lda, tau, work) Q := constructQ("LQ", m, n, a, lda, tau) // Check that Q is orthonormal for i := 0; i < Q.Rows; i++ { nrm := blas64.Nrm2(Q.Cols, blas64.Vector{Inc: 1, Data: Q.Data[i*Q.Stride:]}) if math.Abs(nrm-1) > 1e-14 { t.Errorf("Q not normal. Norm is %v", nrm) } for j := 0; j < i; j++ { dot := blas64.Dot(Q.Rows, blas64.Vector{Inc: 1, Data: Q.Data[i*Q.Stride:]}, blas64.Vector{Inc: 1, Data: Q.Data[j*Q.Stride:]}, ) if math.Abs(dot) > 1e-14 { t.Errorf("Q not orthogonal. Dot is %v", dot) } } } L := blas64.General{ Rows: m, Cols: n, Stride: n, Data: make([]float64, m*n), } for i := 0; i < m; i++ { for j := 0; j <= min(i, n-1); j++ { L.Data[i*L.Stride+j] = a[i*lda+j] } } ans := blas64.General{ Rows: m, Cols: n, Stride: lda, Data: make([]float64, m*lda), } copy(ans.Data, aCopy) blas64.Gemm(blas.NoTrans, blas.NoTrans, 1, L, Q, 0, ans) if !floats.EqualApprox(aCopy, ans.Data, 1e-14) { t.Errorf("Case %v, LQ mismatch. Want %v, got %v.", c, aCopy, ans.Data) } } }
func Dgeqr2Test(t *testing.T, impl Dgeqr2er) { for c, test := range []struct { m, n, lda int }{ {1, 1, 0}, {2, 2, 0}, {3, 2, 0}, {2, 3, 0}, {1, 12, 0}, {2, 6, 0}, {3, 4, 0}, {4, 3, 0}, {6, 2, 0}, {12, 1, 0}, {1, 1, 20}, {2, 2, 20}, {3, 2, 20}, {2, 3, 20}, {1, 12, 20}, {2, 6, 20}, {3, 4, 20}, {4, 3, 20}, {6, 2, 20}, {12, 1, 20}, } { n := test.n m := test.m lda := test.lda if lda == 0 { lda = test.n } a := make([]float64, m*lda) for i := range a { a[i] = rand.Float64() } aCopy := make([]float64, len(a)) k := min(m, n) tau := make([]float64, k) for i := range tau { tau[i] = rand.Float64() } work := make([]float64, n) for i := range work { work[i] = rand.Float64() } copy(aCopy, a) impl.Dgeqr2(m, n, a, lda, tau, work) // Test that the QR factorization has completed successfully. Compute // Q based on the vectors. q := constructQ("QR", m, n, a, lda, tau) // Check that q is orthonormal for i := 0; i < m; i++ { nrm := blas64.Nrm2(m, blas64.Vector{1, q.Data[i*m:]}) if math.Abs(nrm-1) > 1e-14 { t.Errorf("Case %v, q not normal", c) } for j := 0; j < i; j++ { dot := blas64.Dot(m, blas64.Vector{1, q.Data[i*m:]}, blas64.Vector{1, q.Data[j*m:]}) if math.Abs(dot) > 1e-14 { t.Errorf("Case %v, q not orthogonal", i) } } } // Check that A = Q * R r := blas64.General{ Rows: m, Cols: n, Stride: n, Data: make([]float64, m*n), } for i := 0; i < m; i++ { for j := i; j < n; j++ { r.Data[i*n+j] = a[i*lda+j] } } atmp := blas64.General{ Rows: m, Cols: n, Stride: lda, Data: make([]float64, m*lda), } copy(atmp.Data, a) blas64.Gemm(blas.NoTrans, blas.NoTrans, 1, q, r, 0, atmp) if !floats.EqualApprox(atmp.Data, aCopy, 1e-14) { t.Errorf("Q*R != a") } } }
func DlangeTest(t *testing.T, impl Dlanger) { for _, test := range []struct { m, n, lda int }{ {4, 3, 0}, {3, 4, 0}, {4, 3, 100}, {3, 4, 100}, } { 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] = (rand.Float64() - 0.5) } work := make([]float64, n) for i := range work { work[i] = rand.Float64() } aCopy := make([]float64, len(a)) copy(aCopy, a) // Test MaxAbs norm. norm := impl.Dlange(lapack.MaxAbs, m, n, a, lda, work) var ans float64 for i := 0; i < m; i++ { idx := blas64.Iamax(n, blas64.Vector{1, aCopy[i*lda:]}) ans = math.Max(ans, math.Abs(a[i*lda+idx])) } // Should be strictly equal because there is no floating point summation error. if ans != norm { t.Errorf("MaxAbs mismatch. Want %v, got %v.", ans, norm) } // Test MaxColumnSum norm. norm = impl.Dlange(lapack.MaxColumnSum, m, n, a, lda, work) ans = 0 for i := 0; i < n; i++ { sum := blas64.Asum(m, blas64.Vector{lda, aCopy[i:]}) ans = math.Max(ans, sum) } if math.Abs(norm-ans) > 1e-14 { t.Errorf("MaxColumnSum mismatch. Want %v, got %v.", ans, norm) } // Test MaxRowSum norm. norm = impl.Dlange(lapack.MaxRowSum, m, n, a, lda, work) ans = 0 for i := 0; i < m; i++ { sum := blas64.Asum(n, blas64.Vector{1, aCopy[i*lda:]}) ans = math.Max(ans, sum) } if math.Abs(norm-ans) > 1e-14 { t.Errorf("MaxRowSum mismatch. Want %v, got %v.", ans, norm) } // Test Frobenius norm norm = impl.Dlange(lapack.NormFrob, m, n, a, lda, work) ans = 0 for i := 0; i < m; i++ { sum := blas64.Nrm2(n, blas64.Vector{1, aCopy[i*lda:]}) ans += sum * sum } ans = math.Sqrt(ans) if math.Abs(norm-ans) > 1e-14 { t.Errorf("NormFrob mismatch. Want %v, got %v.", ans, norm) } } }
// SymRankOne performs a rank-1 update of the original matrix A and refactorizes // its Cholesky factorization, storing the result into the reciever. That is, if // in the original Cholesky factorization // U^T * U = A, // in the updated factorization // U'^T * U' = A + alpha * x * x^T = A'. // // Note that when alpha is negative, the updating problem may be ill-conditioned // and the results may be inaccurate, or the updated matrix A' may not be // positive definite and not have a Cholesky factorization. SymRankOne returns // whether the updated matrix A' is positive definite. // // SymRankOne updates a Cholesky factorization in O(n²) time. The Cholesky // factorization computation from scratch is O(n³). func (c *Cholesky) SymRankOne(orig *Cholesky, alpha float64, x *Vector) (ok bool) { if !orig.valid() { panic(badCholesky) } n := orig.Size() if x.Len() != n { panic(matrix.ErrShape) } if orig != c { if c.isZero() { c.chol = NewTriDense(n, matrix.Upper, nil) } else if c.chol.mat.N != n { panic(matrix.ErrShape) } c.chol.Copy(orig.chol) } if alpha == 0 { return true } // Algorithms for updating and downdating the Cholesky factorization are // described, for example, in // - J. J. Dongarra, J. R. Bunch, C. B. Moler, G. W. Stewart: LINPACK // Users' Guide. SIAM (1979), pages 10.10--10.14 // or // - P. E. Gill, G. H. Golub, W. Murray, and M. A. Saunders: Methods for // modifying matrix factorizations. Mathematics of Computation 28(126) // (1974), Method C3 on page 521 // // The implementation is based on LINPACK code // http://www.netlib.org/linpack/dchud.f // http://www.netlib.org/linpack/dchdd.f // and // https://icl.cs.utk.edu/lapack-forum/viewtopic.php?f=2&t=2646 // // According to http://icl.cs.utk.edu/lapack-forum/archives/lapack/msg00301.html // LINPACK is released under BSD license. // // See also: // - M. A. Saunders: Large-scale Linear Programming Using the Cholesky // Factorization. Technical Report Stanford University (1972) // http://i.stanford.edu/pub/cstr/reports/cs/tr/72/252/CS-TR-72-252.pdf // - Matthias Seeger: Low rank updates for the Cholesky decomposition. // EPFL Technical Report 161468 (2004) // http://infoscience.epfl.ch/record/161468 work := make([]float64, n) blas64.Copy(n, x.RawVector(), blas64.Vector{1, work}) if alpha > 0 { // Compute rank-1 update. if alpha != 1 { blas64.Scal(n, math.Sqrt(alpha), blas64.Vector{1, work}) } umat := c.chol.mat stride := umat.Stride for i := 0; i < n; i++ { // Compute parameters of the Givens matrix that zeroes // the i-th element of x. c, s, r, _ := blas64.Rotg(umat.Data[i*stride+i], work[i]) if r < 0 { // Multiply by -1 to have positive diagonal // elemnts. r *= -1 c *= -1 s *= -1 } umat.Data[i*stride+i] = r if i < n-1 { // Multiply the extended factorization matrix by // the Givens matrix from the left. Only // the i-th row and x are modified. blas64.Rot(n-i-1, blas64.Vector{1, umat.Data[i*stride+i+1 : i*stride+n]}, blas64.Vector{1, work[i+1 : n]}, c, s) } } c.updateCond(-1) return true } // Compute rank-1 downdate. alpha = math.Sqrt(-alpha) if alpha != 1 { blas64.Scal(n, alpha, blas64.Vector{1, work}) } // Solve U^T * p = x storing the result into work. ok = lapack64.Trtrs(blas.Trans, c.chol.RawTriangular(), blas64.General{ Rows: n, Cols: 1, Stride: 1, Data: work, }) if !ok { // The original matrix is singular. Should not happen, because // the factorization is valid. panic(badCholesky) } norm := blas64.Nrm2(n, blas64.Vector{1, work}) if norm >= 1 { // The updated matrix is not positive definite. return false } norm = math.Sqrt((1 + norm) * (1 - norm)) cos := make([]float64, n) sin := make([]float64, n) for i := n - 1; i >= 0; i-- { // Compute parameters of Givens matrices that zero elements of p // backwards. cos[i], sin[i], norm, _ = blas64.Rotg(norm, work[i]) if norm < 0 { norm *= -1 cos[i] *= -1 sin[i] *= -1 } } umat := c.chol.mat stride := umat.Stride for i := n - 1; i >= 0; i-- { // Apply Givens matrices to U. // TODO(vladimir-ch): Use workspace to avoid modifying the // receiver in case an invalid factorization is created. blas64.Rot(n-i, blas64.Vector{1, work[i:n]}, blas64.Vector{1, umat.Data[i*stride+i : i*stride+n]}, cos[i], sin[i]) if umat.Data[i*stride+i] == 0 { // The matrix is singular (may rarely happen due to // floating-point effects?). ok = false } else if umat.Data[i*stride+i] < 0 { // Diagonal elements should be positive. If it happens // that on the i-th row the diagonal is negative, // multiply U from the left by an identity matrix that // has -1 on the i-th row. blas64.Scal(n-i, -1, blas64.Vector{1, umat.Data[i*stride+i : i*stride+n]}) } } if ok { c.updateCond(-1) } else { c.Reset() } return ok }