gonum.org/v1/gonum@v0.14.0/mat/lq.go (about)

     1  // Copyright ©2013 The Gonum Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package mat
     6  
     7  import (
     8  	"math"
     9  
    10  	"gonum.org/v1/gonum/blas"
    11  	"gonum.org/v1/gonum/blas/blas64"
    12  	"gonum.org/v1/gonum/lapack"
    13  	"gonum.org/v1/gonum/lapack/lapack64"
    14  )
    15  
    16  const badLQ = "mat: invalid LQ factorization"
    17  
    18  // LQ is a type for creating and using the LQ factorization of a matrix.
    19  type LQ struct {
    20  	lq   *Dense
    21  	tau  []float64
    22  	cond float64
    23  }
    24  
    25  func (lq *LQ) updateCond(norm lapack.MatrixNorm) {
    26  	// Since A = L*Q, and Q is orthogonal, we get for the condition number κ
    27  	//  κ(A) := |A| |A^-1| = |L*Q| |(L*Q)^-1| = |L| |Qᵀ * L^-1|
    28  	//        = |L| |L^-1| = κ(L),
    29  	// where we used that fact that Q^-1 = Qᵀ. However, this assumes that
    30  	// the matrix norm is invariant under orthogonal transformations which
    31  	// is not the case for CondNorm. Hopefully the error is negligible: κ
    32  	// is only a qualitative measure anyway.
    33  	m := lq.lq.mat.Rows
    34  	work := getFloat64s(3*m, false)
    35  	iwork := getInts(m, false)
    36  	l := lq.lq.asTriDense(m, blas.NonUnit, blas.Lower)
    37  	v := lapack64.Trcon(norm, l.mat, work, iwork)
    38  	lq.cond = 1 / v
    39  	putFloat64s(work)
    40  	putInts(iwork)
    41  }
    42  
    43  // Factorize computes the LQ factorization of an m×n matrix a where m <= n. The LQ
    44  // factorization always exists even if A is singular.
    45  //
    46  // The LQ decomposition is a factorization of the matrix A such that A = L * Q.
    47  // The matrix Q is an orthonormal n×n matrix, and L is an m×n lower triangular matrix.
    48  // L and Q can be extracted using the LTo and QTo methods.
    49  func (lq *LQ) Factorize(a Matrix) {
    50  	lq.factorize(a, CondNorm)
    51  }
    52  
    53  func (lq *LQ) factorize(a Matrix, norm lapack.MatrixNorm) {
    54  	m, n := a.Dims()
    55  	if m > n {
    56  		panic(ErrShape)
    57  	}
    58  	k := min(m, n)
    59  	if lq.lq == nil {
    60  		lq.lq = &Dense{}
    61  	}
    62  	lq.lq.CloneFrom(a)
    63  	work := []float64{0}
    64  	lq.tau = make([]float64, k)
    65  	lapack64.Gelqf(lq.lq.mat, lq.tau, work, -1)
    66  	work = getFloat64s(int(work[0]), false)
    67  	lapack64.Gelqf(lq.lq.mat, lq.tau, work, len(work))
    68  	putFloat64s(work)
    69  	lq.updateCond(norm)
    70  }
    71  
    72  // isValid returns whether the receiver contains a factorization.
    73  func (lq *LQ) isValid() bool {
    74  	return lq.lq != nil && !lq.lq.IsEmpty()
    75  }
    76  
    77  // Cond returns the condition number for the factorized matrix.
    78  // Cond will panic if the receiver does not contain a factorization.
    79  func (lq *LQ) Cond() float64 {
    80  	if !lq.isValid() {
    81  		panic(badLQ)
    82  	}
    83  	return lq.cond
    84  }
    85  
    86  // TODO(btracey): Add in the "Reduced" forms for extracting the m×m orthogonal
    87  // and upper triangular matrices.
    88  
    89  // LTo extracts the m×n lower trapezoidal matrix from a LQ decomposition.
    90  //
    91  // If dst is empty, LTo will resize dst to be r×c. When dst is
    92  // non-empty, LTo will panic if dst is not r×c. LTo will also panic
    93  // if the receiver does not contain a successful factorization.
    94  func (lq *LQ) LTo(dst *Dense) {
    95  	if !lq.isValid() {
    96  		panic(badLQ)
    97  	}
    98  
    99  	r, c := lq.lq.Dims()
   100  	if dst.IsEmpty() {
   101  		dst.ReuseAs(r, c)
   102  	} else {
   103  		r2, c2 := dst.Dims()
   104  		if r != r2 || c != c2 {
   105  			panic(ErrShape)
   106  		}
   107  	}
   108  
   109  	// Disguise the LQ as a lower triangular.
   110  	t := &TriDense{
   111  		mat: blas64.Triangular{
   112  			N:      r,
   113  			Stride: lq.lq.mat.Stride,
   114  			Data:   lq.lq.mat.Data,
   115  			Uplo:   blas.Lower,
   116  			Diag:   blas.NonUnit,
   117  		},
   118  		cap: lq.lq.capCols,
   119  	}
   120  	dst.Copy(t)
   121  
   122  	if r == c {
   123  		return
   124  	}
   125  	// Zero right of the triangular.
   126  	for i := 0; i < r; i++ {
   127  		zero(dst.mat.Data[i*dst.mat.Stride+r : i*dst.mat.Stride+c])
   128  	}
   129  }
   130  
   131  // QTo extracts the n×n orthonormal matrix Q from an LQ decomposition.
   132  //
   133  // If dst is empty, QTo will resize dst to be c×c. When dst is
   134  // non-empty, QTo will panic if dst is not c×c. QTo will also panic
   135  // if the receiver does not contain a successful factorization.
   136  func (lq *LQ) QTo(dst *Dense) {
   137  	if !lq.isValid() {
   138  		panic(badLQ)
   139  	}
   140  
   141  	_, c := lq.lq.Dims()
   142  	if dst.IsEmpty() {
   143  		dst.ReuseAs(c, c)
   144  	} else {
   145  		r2, c2 := dst.Dims()
   146  		if c != r2 || c != c2 {
   147  			panic(ErrShape)
   148  		}
   149  		dst.Zero()
   150  	}
   151  	q := dst.mat
   152  
   153  	// Set Q = I.
   154  	ldq := q.Stride
   155  	for i := 0; i < c; i++ {
   156  		q.Data[i*ldq+i] = 1
   157  	}
   158  
   159  	// Construct Q from the elementary reflectors.
   160  	work := []float64{0}
   161  	lapack64.Ormlq(blas.Left, blas.NoTrans, lq.lq.mat, lq.tau, q, work, -1)
   162  	work = getFloat64s(int(work[0]), false)
   163  	lapack64.Ormlq(blas.Left, blas.NoTrans, lq.lq.mat, lq.tau, q, work, len(work))
   164  	putFloat64s(work)
   165  }
   166  
   167  // SolveTo finds a minimum-norm solution to a system of linear equations defined
   168  // by the matrices A and b, where A is an m×n matrix represented in its LQ factorized
   169  // form. If A is singular or near-singular a Condition error is returned.
   170  // See the documentation for Condition for more information.
   171  //
   172  // The minimization problem solved depends on the input parameters.
   173  //
   174  //	If trans == false, find the minimum norm solution of A * X = B.
   175  //	If trans == true, find X such that ||A*X - B||_2 is minimized.
   176  //
   177  // The solution matrix, X, is stored in place into dst.
   178  // SolveTo will panic if the receiver does not contain a factorization.
   179  func (lq *LQ) SolveTo(dst *Dense, trans bool, b Matrix) error {
   180  	if !lq.isValid() {
   181  		panic(badLQ)
   182  	}
   183  
   184  	r, c := lq.lq.Dims()
   185  	br, bc := b.Dims()
   186  
   187  	// The LQ solve algorithm stores the result in-place into the right hand side.
   188  	// The storage for the answer must be large enough to hold both b and x.
   189  	// However, this method's receiver must be the size of x. Copy b, and then
   190  	// copy the result into x at the end.
   191  	if trans {
   192  		if c != br {
   193  			panic(ErrShape)
   194  		}
   195  		dst.reuseAsNonZeroed(r, bc)
   196  	} else {
   197  		if r != br {
   198  			panic(ErrShape)
   199  		}
   200  		dst.reuseAsNonZeroed(c, bc)
   201  	}
   202  	// Do not need to worry about overlap between x and b because w has its own
   203  	// independent storage.
   204  	w := getDenseWorkspace(max(r, c), bc, false)
   205  	w.Copy(b)
   206  	t := lq.lq.asTriDense(lq.lq.mat.Rows, blas.NonUnit, blas.Lower).mat
   207  	if trans {
   208  		work := []float64{0}
   209  		lapack64.Ormlq(blas.Left, blas.NoTrans, lq.lq.mat, lq.tau, w.mat, work, -1)
   210  		work = getFloat64s(int(work[0]), false)
   211  		lapack64.Ormlq(blas.Left, blas.NoTrans, lq.lq.mat, lq.tau, w.mat, work, len(work))
   212  		putFloat64s(work)
   213  
   214  		ok := lapack64.Trtrs(blas.Trans, t, w.mat)
   215  		if !ok {
   216  			return Condition(math.Inf(1))
   217  		}
   218  	} else {
   219  		ok := lapack64.Trtrs(blas.NoTrans, t, w.mat)
   220  		if !ok {
   221  			return Condition(math.Inf(1))
   222  		}
   223  		for i := r; i < c; i++ {
   224  			zero(w.mat.Data[i*w.mat.Stride : i*w.mat.Stride+bc])
   225  		}
   226  		work := []float64{0}
   227  		lapack64.Ormlq(blas.Left, blas.Trans, lq.lq.mat, lq.tau, w.mat, work, -1)
   228  		work = getFloat64s(int(work[0]), false)
   229  		lapack64.Ormlq(blas.Left, blas.Trans, lq.lq.mat, lq.tau, w.mat, work, len(work))
   230  		putFloat64s(work)
   231  	}
   232  	// x was set above to be the correct size for the result.
   233  	dst.Copy(w)
   234  	putDenseWorkspace(w)
   235  	if lq.cond > ConditionTolerance {
   236  		return Condition(lq.cond)
   237  	}
   238  	return nil
   239  }
   240  
   241  // SolveVecTo finds a minimum-norm solution to a system of linear equations.
   242  // See LQ.SolveTo for the full documentation.
   243  // SolveToVec will panic if the receiver does not contain a factorization.
   244  func (lq *LQ) SolveVecTo(dst *VecDense, trans bool, b Vector) error {
   245  	if !lq.isValid() {
   246  		panic(badLQ)
   247  	}
   248  
   249  	r, c := lq.lq.Dims()
   250  	if _, bc := b.Dims(); bc != 1 {
   251  		panic(ErrShape)
   252  	}
   253  
   254  	// The Solve implementation is non-trivial, so rather than duplicate the code,
   255  	// instead recast the VecDenses as Dense and call the matrix code.
   256  	bm := Matrix(b)
   257  	if rv, ok := b.(RawVectorer); ok {
   258  		bmat := rv.RawVector()
   259  		if dst != b {
   260  			dst.checkOverlap(bmat)
   261  		}
   262  		b := VecDense{mat: bmat}
   263  		bm = b.asDense()
   264  	}
   265  	if trans {
   266  		dst.reuseAsNonZeroed(r)
   267  	} else {
   268  		dst.reuseAsNonZeroed(c)
   269  	}
   270  	return lq.SolveTo(dst.asDense(), trans, bm)
   271  }