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