github.com/jingcheng-WU/gonum@v0.9.1-0.20210323123734-f1a2a11a8f7b/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  	"github.com/jingcheng-WU/gonum/blas"
    11  	"github.com/jingcheng-WU/gonum/blas/blas64"
    12  	"github.com/jingcheng-WU/gonum/lapack"
    13  	"github.com/jingcheng-WU/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 := getFloats(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  	putFloats(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 = getFloats(int(work[0]), false)
    67  	lapack64.Gelqf(lq.lq.mat, lq.tau, work, len(work))
    68  	putFloats(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 = getFloats(int(work[0]), false)
   163  	lapack64.Ormlq(blas.Left, blas.NoTrans, lq.lq.mat, lq.tau, q, work, len(work))
   164  	putFloats(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  //  If trans == false, find the minimum norm solution of A * X = B.
   174  //  If trans == true, find X such that ||A*X - B||_2 is minimized.
   175  // The solution matrix, X, is stored in place into dst.
   176  // SolveTo will panic if the receiver does not contain a factorization.
   177  func (lq *LQ) SolveTo(dst *Dense, trans bool, b Matrix) error {
   178  	if !lq.isValid() {
   179  		panic(badLQ)
   180  	}
   181  
   182  	r, c := lq.lq.Dims()
   183  	br, bc := b.Dims()
   184  
   185  	// The LQ solve algorithm stores the result in-place into the right hand side.
   186  	// The storage for the answer must be large enough to hold both b and x.
   187  	// However, this method's receiver must be the size of x. Copy b, and then
   188  	// copy the result into x at the end.
   189  	if trans {
   190  		if c != br {
   191  			panic(ErrShape)
   192  		}
   193  		dst.reuseAsNonZeroed(r, bc)
   194  	} else {
   195  		if r != br {
   196  			panic(ErrShape)
   197  		}
   198  		dst.reuseAsNonZeroed(c, bc)
   199  	}
   200  	// Do not need to worry about overlap between x and b because w has its own
   201  	// independent storage.
   202  	w := getWorkspace(max(r, c), bc, false)
   203  	w.Copy(b)
   204  	t := lq.lq.asTriDense(lq.lq.mat.Rows, blas.NonUnit, blas.Lower).mat
   205  	if trans {
   206  		work := []float64{0}
   207  		lapack64.Ormlq(blas.Left, blas.NoTrans, lq.lq.mat, lq.tau, w.mat, work, -1)
   208  		work = getFloats(int(work[0]), false)
   209  		lapack64.Ormlq(blas.Left, blas.NoTrans, lq.lq.mat, lq.tau, w.mat, work, len(work))
   210  		putFloats(work)
   211  
   212  		ok := lapack64.Trtrs(blas.Trans, t, w.mat)
   213  		if !ok {
   214  			return Condition(math.Inf(1))
   215  		}
   216  	} else {
   217  		ok := lapack64.Trtrs(blas.NoTrans, t, w.mat)
   218  		if !ok {
   219  			return Condition(math.Inf(1))
   220  		}
   221  		for i := r; i < c; i++ {
   222  			zero(w.mat.Data[i*w.mat.Stride : i*w.mat.Stride+bc])
   223  		}
   224  		work := []float64{0}
   225  		lapack64.Ormlq(blas.Left, blas.Trans, lq.lq.mat, lq.tau, w.mat, work, -1)
   226  		work = getFloats(int(work[0]), false)
   227  		lapack64.Ormlq(blas.Left, blas.Trans, lq.lq.mat, lq.tau, w.mat, work, len(work))
   228  		putFloats(work)
   229  	}
   230  	// x was set above to be the correct size for the result.
   231  	dst.Copy(w)
   232  	putWorkspace(w)
   233  	if lq.cond > ConditionTolerance {
   234  		return Condition(lq.cond)
   235  	}
   236  	return nil
   237  }
   238  
   239  // SolveVecTo finds a minimum-norm solution to a system of linear equations.
   240  // See LQ.SolveTo for the full documentation.
   241  // SolveToVec will panic if the receiver does not contain a factorization.
   242  func (lq *LQ) SolveVecTo(dst *VecDense, trans bool, b Vector) error {
   243  	if !lq.isValid() {
   244  		panic(badLQ)
   245  	}
   246  
   247  	r, c := lq.lq.Dims()
   248  	if _, bc := b.Dims(); bc != 1 {
   249  		panic(ErrShape)
   250  	}
   251  
   252  	// The Solve implementation is non-trivial, so rather than duplicate the code,
   253  	// instead recast the VecDenses as Dense and call the matrix code.
   254  	bm := Matrix(b)
   255  	if rv, ok := b.(RawVectorer); ok {
   256  		bmat := rv.RawVector()
   257  		if dst != b {
   258  			dst.checkOverlap(bmat)
   259  		}
   260  		b := VecDense{mat: bmat}
   261  		bm = b.asDense()
   262  	}
   263  	if trans {
   264  		dst.reuseAsNonZeroed(r)
   265  	} else {
   266  		dst.reuseAsNonZeroed(c)
   267  	}
   268  	return lq.SolveTo(dst.asDense(), trans, bm)
   269  }