github.com/gonum/matrix@v0.0.0-20181209220409-c518dec07be9/mat64/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 mat64
     6  
     7  import (
     8  	"math"
     9  
    10  	"github.com/gonum/blas"
    11  	"github.com/gonum/blas/blas64"
    12  	"github.com/gonum/lapack/lapack64"
    13  	"github.com/gonum/matrix"
    14  )
    15  
    16  // LQ is a type for creating and using the LQ factorization of a matrix.
    17  type LQ struct {
    18  	lq   *Dense
    19  	tau  []float64
    20  	cond float64
    21  }
    22  
    23  func (lq *LQ) updateCond() {
    24  	// A = LQ, where Q is orthonormal. Orthonormal multiplications do not change
    25  	// the condition number. Thus, ||A|| = ||L|| ||Q|| = ||Q||.
    26  	m := lq.lq.mat.Rows
    27  	work := make([]float64, 3*m)
    28  	iwork := make([]int, m)
    29  	l := lq.lq.asTriDense(m, blas.NonUnit, blas.Lower)
    30  	v := lapack64.Trcon(matrix.CondNorm, l.mat, work, iwork)
    31  	lq.cond = 1 / v
    32  }
    33  
    34  // Factorize computes the LQ factorization of an m×n matrix a where n <= m. The LQ
    35  // factorization always exists even if A is singular.
    36  //
    37  // The LQ decomposition is a factorization of the matrix A such that A = L * Q.
    38  // The matrix Q is an orthonormal n×n matrix, and L is an m×n upper triangular matrix.
    39  // L and Q can be extracted from the LFromLQ and QFromLQ methods on Dense.
    40  func (lq *LQ) Factorize(a Matrix) {
    41  	m, n := a.Dims()
    42  	if m > n {
    43  		panic(matrix.ErrShape)
    44  	}
    45  	k := min(m, n)
    46  	if lq.lq == nil {
    47  		lq.lq = &Dense{}
    48  	}
    49  	lq.lq.Clone(a)
    50  	work := make([]float64, 1)
    51  	lq.tau = make([]float64, k)
    52  	lapack64.Gelqf(lq.lq.mat, lq.tau, work, -1)
    53  	work = make([]float64, int(work[0]))
    54  	lapack64.Gelqf(lq.lq.mat, lq.tau, work, len(work))
    55  	lq.updateCond()
    56  }
    57  
    58  // TODO(btracey): Add in the "Reduced" forms for extracting the m×m orthogonal
    59  // and upper triangular matrices.
    60  
    61  // LFromLQ extracts the m×n lower trapezoidal matrix from a LQ decomposition.
    62  func (m *Dense) LFromLQ(lq *LQ) {
    63  	r, c := lq.lq.Dims()
    64  	m.reuseAs(r, c)
    65  
    66  	// Disguise the LQ as a lower triangular
    67  	t := &TriDense{
    68  		mat: blas64.Triangular{
    69  			N:      r,
    70  			Stride: lq.lq.mat.Stride,
    71  			Data:   lq.lq.mat.Data,
    72  			Uplo:   blas.Lower,
    73  			Diag:   blas.NonUnit,
    74  		},
    75  		cap: lq.lq.capCols,
    76  	}
    77  	m.Copy(t)
    78  
    79  	if r == c {
    80  		return
    81  	}
    82  	// Zero right of the triangular.
    83  	for i := 0; i < r; i++ {
    84  		zero(m.mat.Data[i*m.mat.Stride+r : i*m.mat.Stride+c])
    85  	}
    86  }
    87  
    88  // QFromLQ extracts the n×n orthonormal matrix Q from an LQ decomposition.
    89  func (m *Dense) QFromLQ(lq *LQ) {
    90  	r, c := lq.lq.Dims()
    91  	m.reuseAs(c, c)
    92  
    93  	// Set Q = I.
    94  	for i := 0; i < c; i++ {
    95  		v := m.mat.Data[i*m.mat.Stride : i*m.mat.Stride+c]
    96  		zero(v)
    97  		v[i] = 1
    98  	}
    99  
   100  	// Construct Q from the elementary reflectors.
   101  	h := blas64.General{
   102  		Rows:   c,
   103  		Cols:   c,
   104  		Stride: c,
   105  		Data:   make([]float64, c*c),
   106  	}
   107  	qCopy := getWorkspace(c, c, false)
   108  	v := blas64.Vector{
   109  		Inc:  1,
   110  		Data: make([]float64, c),
   111  	}
   112  	for i := 0; i < r; i++ {
   113  		// Set h = I.
   114  		zero(h.Data)
   115  		for j := 0; j < len(h.Data); j += c + 1 {
   116  			h.Data[j] = 1
   117  		}
   118  
   119  		// Set the vector data as the elementary reflector.
   120  		for j := 0; j < i; j++ {
   121  			v.Data[j] = 0
   122  		}
   123  		v.Data[i] = 1
   124  		for j := i + 1; j < c; j++ {
   125  			v.Data[j] = lq.lq.mat.Data[i*lq.lq.mat.Stride+j]
   126  		}
   127  
   128  		// Compute the multiplication matrix.
   129  		blas64.Ger(-lq.tau[i], v, v, h)
   130  		qCopy.Copy(m)
   131  		blas64.Gemm(blas.NoTrans, blas.NoTrans,
   132  			1, h, qCopy.mat,
   133  			0, m.mat)
   134  	}
   135  }
   136  
   137  // SolveLQ finds a minimum-norm solution to a system of linear equations defined
   138  // by the matrices A and b, where A is an m×n matrix represented in its LQ factorized
   139  // form. If A is singular or near-singular a Condition error is returned. Please
   140  // see the documentation for Condition for more information.
   141  //
   142  // The minimization problem solved depends on the input parameters.
   143  //  If trans == false, find the minimum norm solution of A * X = b.
   144  //  If trans == true, find X such that ||A*X - b||_2 is minimized.
   145  // The solution matrix, X, is stored in place into the receiver.
   146  func (m *Dense) SolveLQ(lq *LQ, trans bool, b Matrix) error {
   147  	r, c := lq.lq.Dims()
   148  	br, bc := b.Dims()
   149  
   150  	// The LQ solve algorithm stores the result in-place into the right hand side.
   151  	// The storage for the answer must be large enough to hold both b and x.
   152  	// However, this method's receiver must be the size of x. Copy b, and then
   153  	// copy the result into m at the end.
   154  	if trans {
   155  		if c != br {
   156  			panic(matrix.ErrShape)
   157  		}
   158  		m.reuseAs(r, bc)
   159  	} else {
   160  		if r != br {
   161  			panic(matrix.ErrShape)
   162  		}
   163  		m.reuseAs(c, bc)
   164  	}
   165  	// Do not need to worry about overlap between m and b because x has its own
   166  	// independent storage.
   167  	x := getWorkspace(max(r, c), bc, false)
   168  	x.Copy(b)
   169  	t := lq.lq.asTriDense(lq.lq.mat.Rows, blas.NonUnit, blas.Lower).mat
   170  	if trans {
   171  		work := make([]float64, 1)
   172  		lapack64.Ormlq(blas.Left, blas.NoTrans, lq.lq.mat, lq.tau, x.mat, work, -1)
   173  		work = make([]float64, int(work[0]))
   174  		lapack64.Ormlq(blas.Left, blas.NoTrans, lq.lq.mat, lq.tau, x.mat, work, len(work))
   175  
   176  		ok := lapack64.Trtrs(blas.Trans, t, x.mat)
   177  		if !ok {
   178  			return matrix.Condition(math.Inf(1))
   179  		}
   180  	} else {
   181  		ok := lapack64.Trtrs(blas.NoTrans, t, x.mat)
   182  		if !ok {
   183  			return matrix.Condition(math.Inf(1))
   184  		}
   185  		for i := r; i < c; i++ {
   186  			zero(x.mat.Data[i*x.mat.Stride : i*x.mat.Stride+bc])
   187  		}
   188  		work := make([]float64, 1)
   189  		lapack64.Ormlq(blas.Left, blas.Trans, lq.lq.mat, lq.tau, x.mat, work, -1)
   190  		work = make([]float64, int(work[0]))
   191  		lapack64.Ormlq(blas.Left, blas.Trans, lq.lq.mat, lq.tau, x.mat, work, len(work))
   192  	}
   193  	// M was set above to be the correct size for the result.
   194  	m.Copy(x)
   195  	putWorkspace(x)
   196  	if lq.cond > matrix.ConditionTolerance {
   197  		return matrix.Condition(lq.cond)
   198  	}
   199  	return nil
   200  }
   201  
   202  // SolveLQVec finds a minimum-norm solution to a system of linear equations.
   203  // Please see Dense.SolveLQ for the full documentation.
   204  func (v *Vector) SolveLQVec(lq *LQ, trans bool, b *Vector) error {
   205  	if v != b {
   206  		v.checkOverlap(b.mat)
   207  	}
   208  	r, c := lq.lq.Dims()
   209  	// The Solve implementation is non-trivial, so rather than duplicate the code,
   210  	// instead recast the Vectors as Dense and call the matrix code.
   211  	if trans {
   212  		v.reuseAs(r)
   213  	} else {
   214  		v.reuseAs(c)
   215  	}
   216  	return v.asDense().SolveLQ(lq, trans, b.asDense())
   217  }