1
0
mirror of https://github.com/mainflux/mainflux.git synced 2025-04-24 13:48:49 +08:00
Dušan Borovčanin 3d3aa525a6
NOISSUE - Switch to Google Zanzibar Access control approach (#1919)
* Return Auth service

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

Update Compose to run with SpiceDB and Auth svc

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

Update auth gRPC API

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

Remove Users' policies

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

Move Groups to internal

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

Use shared groups in Users

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

Remove unused code

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

Use pkg Groups in Things

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

Remove Things groups

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

Make imports consistent

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

Update Groups networking

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

Remove things groups-specific API

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

Move Things Clients to the root

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

Move Clients to Users root

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

Temporarily remove tracing

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

Fix imports

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

Add buffer config for gRPC

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

Update auth type for Things

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

Use Auth for login

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

Add temporary solution for refresh token

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

Update Tokenizer interface

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

Updade tokens issuing

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

Fix token issuing

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

Update JWT validator and refactor Tokenizer

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

Rename access timeout

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

Rename login to authenticate

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

Update Identify to use SubjectID

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

Add Auth to Groups

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

Use the Auth service for Groups

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

Update auth schema

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

Fix Auth for Groups

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

Add auth for addons (#14)

Signed-off-by: Arvindh <arvindh91@gmail.com>

Speparate Login and Refresh tokens

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

Merge authN and authZ requests for things

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

Add connect and disconnect

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

Update sharing

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

Fix policies addition and removal

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

Update relation with roels

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

Add gRPC to Things

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

Assign and Unassign members to group and Listing of Group members (#15)

* add auth for addons

Signed-off-by: Arvindh <arvindh91@gmail.com>

* add assign and unassign to group

Signed-off-by: Arvindh <arvindh91@gmail.com>

* add group incomplete repo implementation

Signed-off-by: Arvindh <arvindh91@gmail.com>

* groups for users

Signed-off-by: Arvindh <arvindh91@gmail.com>

* groups for users

Signed-off-by: Arvindh <arvindh91@gmail.com>

* groups for users

Signed-off-by: Arvindh <arvindh91@gmail.com>

* groups for users

Signed-off-by: Arvindh <arvindh91@gmail.com>

---------

Signed-off-by: Arvindh <arvindh91@gmail.com>

Move coap mqtt and ws policies to spicedb (#16)

Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com>

Remove old policies

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

NOISSUE - Things authorize to return thingID (#18)

This commit modifies the authorize endpoint to the grpc endpoint to return thingID. The authorize endpoint allows adapters to get the publisher of the message.

Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com>

Add Groups to users service (#17)

* add assign and unassign to group

Signed-off-by: Arvindh <arvindh91@gmail.com>

* add group incomplete repo implementation

Signed-off-by: Arvindh <arvindh91@gmail.com>

* groups for users

Signed-off-by: Arvindh <arvindh91@gmail.com>

* groups for users

Signed-off-by: Arvindh <arvindh91@gmail.com>

* groups for users

Signed-off-by: Arvindh <arvindh91@gmail.com>

* groups for users

Signed-off-by: Arvindh <arvindh91@gmail.com>

* groups for users stable 1

Signed-off-by: Arvindh <arvindh91@gmail.com>

* groups for users stable 2

Signed-off-by: Arvindh <arvindh91@gmail.com>

* groups for users & things

Signed-off-by: Arvindh <arvindh91@gmail.com>

* Amend signature

Signed-off-by: Arvindh <arvindh91@gmail.com>

* fix merge error

Signed-off-by: Arvindh <arvindh91@gmail.com>

---------

Signed-off-by: Arvindh <arvindh91@gmail.com>
Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* NOISSUE - Fix es code (#21)

Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com>
Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* NOISSUE - Fix Bugs (#20)

* fix bugs

Signed-off-by: Arvindh <arvindh91@gmail.com>

* fix bugs

Signed-off-by: Arvindh <arvindh91@gmail.com>

---------

Signed-off-by: Arvindh <arvindh91@gmail.com>
Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* NOISSUE - Test e2e (#19)

* fix: connect method

Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com>

* fix: e2e

Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com>

* fix changes in sdk and e2e

Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com>

* feat(docker): remove unnecessary port mapping

Remove the port mapping for MQTT broker in the docker-compose.yml file.

Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com>

* Enable group listing

Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com>

* feat(responses): update ChannelsPage struct

The ChannelsPage struct in the responses.go file has been updated. The "Channels" field has been renamed to "Groups" to provide more accurate naming. This change ensures consistency and clarity in the codebase.

Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com>

* feat(things): add UpdateClientSecret method

Add the UpdateClientSecret method to the things service. This method allows updating the client secret for a specific client identified by the provided token, id, and key parameters.

Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com>

---------

Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com>
Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Use smaller buffers for gRPC

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Clean up tests (#22)

Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com>
Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Add Connect Disconnect endpoints (#23)

* fix bugs

Signed-off-by: Arvindh <arvindh91@gmail.com>

* fix bugs

Signed-off-by: Arvindh <arvindh91@gmail.com>

* fix list of things in a channel and Add connect disconnect endpoint

Signed-off-by: Arvindh <arvindh91@gmail.com>

* fix list of things in a channel and Add connect disconnect endpoint

Signed-off-by: Arvindh <arvindh91@gmail.com>

---------

Signed-off-by: Arvindh <arvindh91@gmail.com>
Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Add: Things share with users (#25)

* fix list of things in a channel and Add connect disconnect endpoint

Signed-off-by: Arvindh <arvindh91@gmail.com>

* add: things share with other users

Signed-off-by: Arvindh <arvindh91@gmail.com>

---------

Signed-off-by: Arvindh <arvindh91@gmail.com>
Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* NOISSUE - Rename gRPC Services (#24)

* Rename things and users auth service

Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com>

* docs: add authorization docs for gRPC services

Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com>

* Rename things and users grpc services

Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com>

* Remove mainflux.env package

Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com>

---------

Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com>
Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Add: Listing of things, channels, groups, users  (#26)

* add: listing of channels, users, groups, things

Signed-off-by: Arvindh <arvindh91@gmail.com>

* add: listing of channels, users, groups, things

Signed-off-by: Arvindh <arvindh91@gmail.com>

* add: listing of channels, users, groups, things

Signed-off-by: Arvindh <arvindh91@gmail.com>

* add: listing of channels, users, groups, things

Signed-off-by: Arvindh <arvindh91@gmail.com>

---------

Signed-off-by: Arvindh <arvindh91@gmail.com>
Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* NOISSUE - Clean Up Users (#27)

* feat(groups): rename redis package to events

- Renamed the `redis` package to `events` in the `internal/groups` directory.
- Updated the file paths and names accordingly.
- This change reflects the more accurate purpose of the package and improves code organization.

Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com>

* feat(auth): Modify identity method

Change request and response of identity method

Add accessToken and refreshToken to Token response

Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com>

* clean up users, remove dead code

Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com>

* feat(users): add unit tests for user service

This commit adds unit tests for the user service in the `users` package. The tests cover various scenarios and ensure the correct behavior of the service.

Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com>

---------

Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com>
Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Add: List of user groups & removed repeating code in groups (#29)

* removed repeating code in list groups

Signed-off-by: Arvindh <arvindh91@gmail.com>

* add: list of user group

Signed-off-by: Arvindh <arvindh91@gmail.com>

* fix: otel handler operator name for endpoints

Signed-off-by: Arvindh <arvindh91@gmail.com>

---------

Signed-off-by: Arvindh <arvindh91@gmail.com>
Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* NOISSUE - Clean Up Things Service (#28)

* Rework things service

Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com>

* add tests

Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com>

---------

Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com>
Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* NOISSUE - Clean Up Auth Service (#30)

* clean up auth service

Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com>

* feat(auth): remove unused import

Remove the unused import of `emptypb` in `auth.pb.go`. This import is not being used in the codebase and can be safely removed.

Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com>

---------

Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com>
Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* NOISSUE - Update API docs (#31)

Signed-off-by: rodneyosodo <blackd0t@protonmail.com>
Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Remove TODO comments and cleanup the code

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Update dependenices

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

---------

Signed-off-by: Arvindh <arvindh91@gmail.com>
Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>
Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com>
Signed-off-by: rodneyosodo <blackd0t@protonmail.com>
Co-authored-by: b1ackd0t <28790446+rodneyosodo@users.noreply.github.com>
Co-authored-by: Arvindh <30824765+arvindh123@users.noreply.github.com>
2023-10-15 22:02:13 +02:00

1143 lines
31 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright ©2013 The Gonum Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package mat
import (
"math"
"gonum.org/v1/gonum/blas"
"gonum.org/v1/gonum/blas/blas64"
"gonum.org/v1/gonum/lapack/lapack64"
)
const (
badTriangle = "mat: invalid triangle"
badCholesky = "mat: invalid Cholesky factorization"
)
var (
_ Matrix = (*Cholesky)(nil)
_ Symmetric = (*Cholesky)(nil)
_ Matrix = (*BandCholesky)(nil)
_ Symmetric = (*BandCholesky)(nil)
_ Banded = (*BandCholesky)(nil)
_ SymBanded = (*BandCholesky)(nil)
_ Matrix = (*PivotedCholesky)(nil)
_ Symmetric = (*PivotedCholesky)(nil)
)
// Cholesky is a symmetric positive definite matrix represented by its
// Cholesky decomposition.
//
// The decomposition can be constructed using the Factorize method. The
// factorization itself can be extracted using the UTo or LTo methods, and the
// original symmetric matrix can be recovered with ToSym.
//
// Note that this matrix representation is useful for certain operations, in
// particular finding solutions to linear equations. It is very inefficient
// at other operations, in particular At is slow.
//
// Cholesky methods may only be called on a value that has been successfully
// initialized by a call to Factorize that has returned true. Calls to methods
// of an unsuccessful Cholesky factorization will panic.
type Cholesky struct {
// The chol pointer must never be retained as a pointer outside the Cholesky
// struct, either by returning chol outside the struct or by setting it to
// a pointer coming from outside. The same prohibition applies to the data
// slice within chol.
chol *TriDense
cond float64
}
// updateCond updates the condition number of the Cholesky decomposition. If
// norm > 0, then that norm is used as the norm of the original matrix A, otherwise
// the norm is estimated from the decomposition.
func (c *Cholesky) updateCond(norm float64) {
n := c.chol.mat.N
work := getFloat64s(3*n, false)
defer putFloat64s(work)
if norm < 0 {
// This is an approximation. By the definition of a norm,
// |AB| <= |A| |B|.
// Since A = Uᵀ*U, we get for the condition number κ that
// κ(A) := |A| |A^-1| = |Uᵀ*U| |A^-1| <= |Uᵀ| |U| |A^-1|,
// so this will overestimate the condition number somewhat.
// The norm of the original factorized matrix cannot be stored
// because of update possibilities.
unorm := lapack64.Lantr(CondNorm, c.chol.mat, work)
lnorm := lapack64.Lantr(CondNormTrans, c.chol.mat, work)
norm = unorm * lnorm
}
sym := c.chol.asSymBlas()
iwork := getInts(n, false)
v := lapack64.Pocon(sym, norm, work, iwork)
putInts(iwork)
c.cond = 1 / v
}
// Dims returns the dimensions of the matrix.
func (ch *Cholesky) Dims() (r, c int) {
if !ch.valid() {
panic(badCholesky)
}
r, c = ch.chol.Dims()
return r, c
}
// At returns the element at row i, column j.
func (c *Cholesky) At(i, j int) float64 {
if !c.valid() {
panic(badCholesky)
}
n := c.SymmetricDim()
if uint(i) >= uint(n) {
panic(ErrRowAccess)
}
if uint(j) >= uint(n) {
panic(ErrColAccess)
}
var val float64
for k := 0; k <= min(i, j); k++ {
val += c.chol.at(k, i) * c.chol.at(k, j)
}
return val
}
// T returns the receiver, the transpose of a symmetric matrix.
func (c *Cholesky) T() Matrix {
return c
}
// SymmetricDim implements the Symmetric interface and returns the number of rows
// in the matrix (this is also the number of columns).
func (c *Cholesky) SymmetricDim() int {
r, _ := c.chol.Dims()
return r
}
// Cond returns the condition number of the factorized matrix.
func (c *Cholesky) Cond() float64 {
if !c.valid() {
panic(badCholesky)
}
return c.cond
}
// Factorize calculates the Cholesky decomposition of the matrix A and returns
// whether the matrix is positive definite. If Factorize returns false, the
// factorization must not be used.
func (c *Cholesky) Factorize(a Symmetric) (ok bool) {
n := a.SymmetricDim()
if c.chol == nil {
c.chol = NewTriDense(n, Upper, nil)
} else {
c.chol.Reset()
c.chol.reuseAsNonZeroed(n, Upper)
}
copySymIntoTriangle(c.chol, a)
sym := c.chol.asSymBlas()
work := getFloat64s(c.chol.mat.N, false)
norm := lapack64.Lansy(CondNorm, sym, work)
putFloat64s(work)
_, ok = lapack64.Potrf(sym)
if ok {
c.updateCond(norm)
} else {
c.Reset()
}
return ok
}
// Reset resets the factorization so that it can be reused as the receiver of a
// dimensionally restricted operation.
func (c *Cholesky) Reset() {
if c.chol != nil {
c.chol.Reset()
}
c.cond = math.Inf(1)
}
// IsEmpty returns whether the receiver is empty. Empty matrices can be the
// receiver for size-restricted operations. The receiver can be emptied using
// Reset.
func (c *Cholesky) IsEmpty() bool {
return c.chol == nil || c.chol.IsEmpty()
}
// SetFromU sets the Cholesky decomposition from the given triangular matrix.
// SetFromU panics if t is not upper triangular. If the receiver is empty it
// is resized to be n×n, the size of t. If dst is non-empty, SetFromU panics
// if c is not of size n×n. Note that t is copied into, not stored inside, the
// receiver.
func (c *Cholesky) SetFromU(t Triangular) {
n, kind := t.Triangle()
if kind != Upper {
panic("cholesky: matrix must be upper triangular")
}
if c.chol == nil {
c.chol = NewTriDense(n, Upper, nil)
} else {
c.chol.reuseAsNonZeroed(n, Upper)
}
c.chol.Copy(t)
c.updateCond(-1)
}
// Clone makes a copy of the input Cholesky into the receiver, overwriting the
// previous value of the receiver. Clone does not place any restrictions on receiver
// shape. Clone panics if the input Cholesky is not the result of a valid decomposition.
func (c *Cholesky) Clone(chol *Cholesky) {
if !chol.valid() {
panic(badCholesky)
}
n := chol.SymmetricDim()
if c.chol == nil {
c.chol = NewTriDense(n, Upper, nil)
} else {
c.chol = NewTriDense(n, Upper, use(c.chol.mat.Data, n*n))
}
c.chol.Copy(chol.chol)
c.cond = chol.cond
}
// Det returns the determinant of the matrix that has been factorized.
func (c *Cholesky) Det() float64 {
if !c.valid() {
panic(badCholesky)
}
return math.Exp(c.LogDet())
}
// LogDet returns the log of the determinant of the matrix that has been factorized.
func (c *Cholesky) LogDet() float64 {
if !c.valid() {
panic(badCholesky)
}
var det float64
for i := 0; i < c.chol.mat.N; i++ {
det += 2 * math.Log(c.chol.mat.Data[i*c.chol.mat.Stride+i])
}
return det
}
// SolveTo finds the matrix X that solves A * X = B where A is represented
// by the Cholesky decomposition. The result is stored in-place into dst.
// If the Cholesky decomposition is singular or near-singular a Condition error
// is returned. See the documentation for Condition for more information.
func (c *Cholesky) SolveTo(dst *Dense, b Matrix) error {
if !c.valid() {
panic(badCholesky)
}
n := c.chol.mat.N
bm, bn := b.Dims()
if n != bm {
panic(ErrShape)
}
dst.reuseAsNonZeroed(bm, bn)
if b != dst {
dst.Copy(b)
}
lapack64.Potrs(c.chol.mat, dst.mat)
if c.cond > ConditionTolerance {
return Condition(c.cond)
}
return nil
}
// SolveCholTo finds the matrix X that solves A * X = B where A and B are represented
// by their Cholesky decompositions a and b. The result is stored in-place into
// dst.
// If the Cholesky decomposition is singular or near-singular a Condition error
// is returned. See the documentation for Condition for more information.
func (a *Cholesky) SolveCholTo(dst *Dense, b *Cholesky) error {
if !a.valid() || !b.valid() {
panic(badCholesky)
}
bn := b.chol.mat.N
if a.chol.mat.N != bn {
panic(ErrShape)
}
dst.reuseAsZeroed(bn, bn)
dst.Copy(b.chol.T())
blas64.Trsm(blas.Left, blas.Trans, 1, a.chol.mat, dst.mat)
blas64.Trsm(blas.Left, blas.NoTrans, 1, a.chol.mat, dst.mat)
blas64.Trmm(blas.Right, blas.NoTrans, 1, b.chol.mat, dst.mat)
if a.cond > ConditionTolerance {
return Condition(a.cond)
}
return nil
}
// SolveVecTo finds the vector x that solves A * x = b where A is represented
// by the Cholesky decomposition. The result is stored in-place into
// dst.
// If the Cholesky decomposition is singular or near-singular a Condition error
// is returned. See the documentation for Condition for more information.
func (c *Cholesky) SolveVecTo(dst *VecDense, b Vector) error {
if !c.valid() {
panic(badCholesky)
}
n := c.chol.mat.N
if br, bc := b.Dims(); br != n || bc != 1 {
panic(ErrShape)
}
switch rv := b.(type) {
default:
dst.reuseAsNonZeroed(n)
return c.SolveTo(dst.asDense(), b)
case RawVectorer:
bmat := rv.RawVector()
if dst != b {
dst.checkOverlap(bmat)
}
dst.reuseAsNonZeroed(n)
if dst != b {
dst.CopyVec(b)
}
lapack64.Potrs(c.chol.mat, dst.asGeneral())
if c.cond > ConditionTolerance {
return Condition(c.cond)
}
return nil
}
}
// RawU returns the Triangular matrix used to store the Cholesky decomposition of
// the original matrix A. The returned matrix should not be modified. If it is
// modified, the decomposition is invalid and should not be used.
func (c *Cholesky) RawU() Triangular {
return c.chol
}
// UTo stores into dst the n×n upper triangular matrix U from a Cholesky
// decomposition
//
// A = Uᵀ * U.
//
// If dst is empty, it is resized to be an n×n upper triangular matrix. When dst
// is non-empty, UTo panics if dst is not n×n or not Upper. UTo will also panic
// if the receiver does not contain a successful factorization.
func (c *Cholesky) UTo(dst *TriDense) {
if !c.valid() {
panic(badCholesky)
}
n := c.chol.mat.N
if dst.IsEmpty() {
dst.ReuseAsTri(n, Upper)
} else {
n2, kind := dst.Triangle()
if n != n2 {
panic(ErrShape)
}
if kind != Upper {
panic(ErrTriangle)
}
}
dst.Copy(c.chol)
}
// LTo stores into dst the n×n lower triangular matrix L from a Cholesky
// decomposition
//
// A = L * Lᵀ.
//
// If dst is empty, it is resized to be an n×n lower triangular matrix. When dst
// is non-empty, LTo panics if dst is not n×n or not Lower. LTo will also panic
// if the receiver does not contain a successful factorization.
func (c *Cholesky) LTo(dst *TriDense) {
if !c.valid() {
panic(badCholesky)
}
n := c.chol.mat.N
if dst.IsEmpty() {
dst.ReuseAsTri(n, Lower)
} else {
n2, kind := dst.Triangle()
if n != n2 {
panic(ErrShape)
}
if kind != Lower {
panic(ErrTriangle)
}
}
dst.Copy(c.chol.TTri())
}
// ToSym reconstructs the original positive definite matrix from its
// Cholesky decomposition, storing the result into dst. If dst is
// empty it is resized to be n×n. If dst is non-empty, ToSym panics
// if dst is not of size n×n. ToSym will also panic if the receiver
// does not contain a successful factorization.
func (c *Cholesky) ToSym(dst *SymDense) {
if !c.valid() {
panic(badCholesky)
}
n := c.chol.mat.N
if dst.IsEmpty() {
dst.ReuseAsSym(n)
} else {
n2 := dst.SymmetricDim()
if n != n2 {
panic(ErrShape)
}
}
// Create a TriDense representing the Cholesky factor U with dst's
// backing slice.
// Operations on u are reflected in s.
u := &TriDense{
mat: blas64.Triangular{
Uplo: blas.Upper,
Diag: blas.NonUnit,
N: n,
Data: dst.mat.Data,
Stride: dst.mat.Stride,
},
cap: n,
}
u.Copy(c.chol)
// Compute the product Uᵀ*U using the algorithm from LAPACK/TESTING/LIN/dpot01.f
a := u.mat.Data
lda := u.mat.Stride
bi := blas64.Implementation()
for k := n - 1; k >= 0; k-- {
a[k*lda+k] = bi.Ddot(k+1, a[k:], lda, a[k:], lda)
if k > 0 {
bi.Dtrmv(blas.Upper, blas.Trans, blas.NonUnit, k, a, lda, a[k:], lda)
}
}
}
// InverseTo computes the inverse of the matrix represented by its Cholesky
// factorization and stores the result into s. If the factorized
// matrix is ill-conditioned, a Condition error will be returned.
// Note that matrix inversion is numerically unstable, and should generally be
// avoided where possible, for example by using the Solve routines.
func (c *Cholesky) InverseTo(dst *SymDense) error {
if !c.valid() {
panic(badCholesky)
}
dst.reuseAsNonZeroed(c.chol.mat.N)
// Create a TriDense representing the Cholesky factor U with the backing
// slice from dst.
// Operations on u are reflected in dst.
u := &TriDense{
mat: blas64.Triangular{
Uplo: blas.Upper,
Diag: blas.NonUnit,
N: dst.mat.N,
Data: dst.mat.Data,
Stride: dst.mat.Stride,
},
cap: dst.mat.N,
}
u.Copy(c.chol)
_, ok := lapack64.Potri(u.mat)
if !ok {
return Condition(math.Inf(1))
}
if c.cond > ConditionTolerance {
return Condition(c.cond)
}
return nil
}
// Scale multiplies the original matrix A by a positive constant using
// its Cholesky decomposition, storing the result in-place into the receiver.
// That is, if the original Cholesky factorization is
//
// Uᵀ * U = A
//
// the updated factorization is
//
// U'ᵀ * U' = f A = A'
//
// Scale panics if the constant is non-positive, or if the receiver is non-empty
// and is of a different size from the input.
func (c *Cholesky) Scale(f float64, orig *Cholesky) {
if !orig.valid() {
panic(badCholesky)
}
if f <= 0 {
panic("cholesky: scaling by a non-positive constant")
}
n := orig.SymmetricDim()
if c.chol == nil {
c.chol = NewTriDense(n, Upper, nil)
} else if c.chol.mat.N != n {
panic(ErrShape)
}
c.chol.ScaleTri(math.Sqrt(f), orig.chol)
c.cond = orig.cond // Scaling by a positive constant does not change the condition number.
}
// ExtendVecSym computes the Cholesky decomposition of the original matrix A,
// whose Cholesky decomposition is in a, extended by a the n×1 vector v according to
//
// [A w]
// [w' k]
//
// where k = v[n-1] and w = v[:n-1]. The result is stored into the receiver.
// In order for the updated matrix to be positive definite, it must be the case
// that k > w' A^-1 w. If this condition does not hold then ExtendVecSym will
// return false and the receiver will not be updated.
//
// ExtendVecSym will panic if v.Len() != a.SymmetricDim()+1 or if a does not contain
// a valid decomposition.
func (c *Cholesky) ExtendVecSym(a *Cholesky, v Vector) (ok bool) {
n := a.SymmetricDim()
if v.Len() != n+1 {
panic(badSliceLength)
}
if !a.valid() {
panic(badCholesky)
}
// The algorithm is commented here, but see also
// https://math.stackexchange.com/questions/955874/cholesky-factor-when-adding-a-row-and-column-to-already-factorized-matrix
// We have A and want to compute the Cholesky of
// [A w]
// [w' k]
// We want
// [U c]
// [0 d]
// to be the updated Cholesky, and so it must be that
// [A w] = [U' 0] [U c]
// [w' k] [c' d] [0 d]
// Thus, we need
// 1) A = U'U (true by the original decomposition being valid),
// 2) U' * c = w => c = U'^-1 w
// 3) c'*c + d'*d = k => d = sqrt(k-c'*c)
// First, compute c = U'^-1 a
w := NewVecDense(n, nil)
w.CopyVec(v)
k := v.At(n, 0)
var t VecDense
_ = t.SolveVec(a.chol.T(), w)
dot := Dot(&t, &t)
if dot >= k {
return false
}
d := math.Sqrt(k - dot)
newU := NewTriDense(n+1, Upper, nil)
newU.Copy(a.chol)
for i := 0; i < n; i++ {
newU.SetTri(i, n, t.At(i, 0))
}
newU.SetTri(n, n, d)
c.chol = newU
c.updateCond(-1)
return true
}
// SymRankOne performs a rank-1 update of the original matrix A and refactorizes
// its Cholesky factorization, storing the result into the receiver. That is, if
// in the original Cholesky factorization
//
// Uᵀ * U = A,
//
// in the updated factorization
//
// U'ᵀ * U' = A + alpha * x * xᵀ = 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. If the update fails
// the receiver is left unchanged.
//
// 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.SymmetricDim()
if r, c := x.Dims(); r != n || c != 1 {
panic(ErrShape)
}
if orig != c {
if c.chol == nil {
c.chol = NewTriDense(n, Upper, nil)
} else if c.chol.mat.N != n {
panic(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 := getFloat64s(n, false)
defer putFloat64s(work)
var xmat blas64.Vector
if rv, ok := x.(RawVectorer); ok {
xmat = rv.RawVector()
} else {
var tmp *VecDense
tmp.CopyVec(x)
xmat = tmp.RawVector()
}
blas64.Copy(xmat, blas64.Vector{N: n, Data: work, Inc: 1})
if alpha > 0 {
// Compute rank-1 update.
if alpha != 1 {
blas64.Scal(math.Sqrt(alpha), blas64.Vector{N: n, Data: work, Inc: 1})
}
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
// elements.
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(
blas64.Vector{N: n - i - 1, Data: umat.Data[i*stride+i+1 : i*stride+n], Inc: 1},
blas64.Vector{N: n - i - 1, Data: work[i+1 : n], Inc: 1},
c, s)
}
}
c.updateCond(-1)
return true
}
// Compute rank-1 downdate.
alpha = math.Sqrt(-alpha)
if alpha != 1 {
blas64.Scal(alpha, blas64.Vector{N: n, Data: work, Inc: 1})
}
// Solve Uᵀ * 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(blas64.Vector{N: n, Data: work, Inc: 1})
if norm >= 1 {
// The updated matrix is not positive definite.
return false
}
norm = math.Sqrt((1 + norm) * (1 - norm))
cos := getFloat64s(n, false)
defer putFloat64s(cos)
sin := getFloat64s(n, false)
defer putFloat64s(sin)
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
}
}
workMat := getTriDenseWorkspace(c.chol.mat.N, c.chol.triKind(), false)
defer putTriWorkspace(workMat)
workMat.Copy(c.chol)
umat := workMat.mat
stride := workMat.mat.Stride
for i := n - 1; i >= 0; i-- {
work[i] = 0
// Apply Givens matrices to U.
blas64.Rot(
blas64.Vector{N: n - i, Data: work[i:n], Inc: 1},
blas64.Vector{N: n - i, Data: umat.Data[i*stride+i : i*stride+n], Inc: 1},
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(-1, blas64.Vector{N: n - i, Data: umat.Data[i*stride+i : i*stride+n], Inc: 1})
}
}
if ok {
c.chol.Copy(workMat)
c.updateCond(-1)
}
return ok
}
func (c *Cholesky) valid() bool {
return c.chol != nil && !c.chol.IsEmpty()
}
// BandCholesky is a symmetric positive-definite band matrix represented by its
// Cholesky decomposition.
//
// Note that this matrix representation is useful for certain operations, in
// particular finding solutions to linear equations. It is very inefficient at
// other operations, in particular At is slow.
//
// BandCholesky methods may only be called on a value that has been successfully
// initialized by a call to Factorize that has returned true. Calls to methods
// of an unsuccessful Cholesky factorization will panic.
type BandCholesky struct {
// The chol pointer must never be retained as a pointer outside the Cholesky
// struct, either by returning chol outside the struct or by setting it to
// a pointer coming from outside. The same prohibition applies to the data
// slice within chol.
chol *TriBandDense
cond float64
}
// Factorize calculates the Cholesky decomposition of the matrix A and returns
// whether the matrix is positive definite. If Factorize returns false, the
// factorization must not be used.
func (ch *BandCholesky) Factorize(a SymBanded) (ok bool) {
n, k := a.SymBand()
if ch.chol == nil {
ch.chol = NewTriBandDense(n, k, Upper, nil)
} else {
ch.chol.Reset()
ch.chol.ReuseAsTriBand(n, k, Upper)
}
copySymBandIntoTriBand(ch.chol, a)
cSym := blas64.SymmetricBand{
Uplo: blas.Upper,
N: n,
K: k,
Data: ch.chol.RawTriBand().Data,
Stride: ch.chol.RawTriBand().Stride,
}
_, ok = lapack64.Pbtrf(cSym)
if !ok {
ch.Reset()
return false
}
work := getFloat64s(3*n, false)
iwork := getInts(n, false)
aNorm := lapack64.Lansb(CondNorm, cSym, work)
ch.cond = 1 / lapack64.Pbcon(cSym, aNorm, work, iwork)
putInts(iwork)
putFloat64s(work)
return true
}
// SolveTo finds the matrix X that solves A * X = B where A is represented by
// the Cholesky decomposition. The result is stored in-place into dst.
// If the Cholesky decomposition is singular or near-singular a Condition error
// is returned. See the documentation for Condition for more information.
func (ch *BandCholesky) SolveTo(dst *Dense, b Matrix) error {
if !ch.valid() {
panic(badCholesky)
}
br, bc := b.Dims()
if br != ch.chol.mat.N {
panic(ErrShape)
}
dst.reuseAsNonZeroed(br, bc)
if b != dst {
dst.Copy(b)
}
lapack64.Pbtrs(ch.chol.mat, dst.mat)
if ch.cond > ConditionTolerance {
return Condition(ch.cond)
}
return nil
}
// SolveVecTo finds the vector x that solves A * x = b where A is represented by
// the Cholesky decomposition. The result is stored in-place into dst.
// If the Cholesky decomposition is singular or near-singular a Condition error
// is returned. See the documentation for Condition for more information.
func (ch *BandCholesky) SolveVecTo(dst *VecDense, b Vector) error {
if !ch.valid() {
panic(badCholesky)
}
n := ch.chol.mat.N
if br, bc := b.Dims(); br != n || bc != 1 {
panic(ErrShape)
}
if b, ok := b.(RawVectorer); ok && dst != b {
dst.checkOverlap(b.RawVector())
}
dst.reuseAsNonZeroed(n)
if dst != b {
dst.CopyVec(b)
}
lapack64.Pbtrs(ch.chol.mat, dst.asGeneral())
if ch.cond > ConditionTolerance {
return Condition(ch.cond)
}
return nil
}
// Cond returns the condition number of the factorized matrix.
func (ch *BandCholesky) Cond() float64 {
if !ch.valid() {
panic(badCholesky)
}
return ch.cond
}
// Reset resets the factorization so that it can be reused as the receiver of
// a dimensionally restricted operation.
func (ch *BandCholesky) Reset() {
if ch.chol != nil {
ch.chol.Reset()
}
ch.cond = math.Inf(1)
}
// Dims returns the dimensions of the matrix.
func (ch *BandCholesky) Dims() (r, c int) {
if !ch.valid() {
panic(badCholesky)
}
r, c = ch.chol.Dims()
return r, c
}
// At returns the element at row i, column j.
func (ch *BandCholesky) At(i, j int) float64 {
if !ch.valid() {
panic(badCholesky)
}
n, k, _ := ch.chol.TriBand()
if uint(i) >= uint(n) {
panic(ErrRowAccess)
}
if uint(j) >= uint(n) {
panic(ErrColAccess)
}
if i > j {
i, j = j, i
}
if j-i > k {
return 0
}
var aij float64
for k := max(0, j-k); k <= i; k++ {
aij += ch.chol.at(k, i) * ch.chol.at(k, j)
}
return aij
}
// T returns the receiver, the transpose of a symmetric matrix.
func (ch *BandCholesky) T() Matrix {
return ch
}
// TBand returns the receiver, the transpose of a symmetric band matrix.
func (ch *BandCholesky) TBand() Banded {
return ch
}
// SymmetricDim implements the Symmetric interface and returns the number of rows
// in the matrix (this is also the number of columns).
func (ch *BandCholesky) SymmetricDim() int {
n, _ := ch.chol.Triangle()
return n
}
// Bandwidth returns the lower and upper bandwidth values for the matrix.
// The total bandwidth of the matrix is kl+ku+1.
func (ch *BandCholesky) Bandwidth() (kl, ku int) {
_, k, _ := ch.chol.TriBand()
return k, k
}
// SymBand returns the number of rows/columns in the matrix, and the size of the
// bandwidth. The total bandwidth of the matrix is 2*k+1.
func (ch *BandCholesky) SymBand() (n, k int) {
n, k, _ = ch.chol.TriBand()
return n, k
}
// IsEmpty returns whether the receiver is empty. Empty matrices can be the
// receiver for dimensionally restricted operations. The receiver can be emptied
// using Reset.
func (ch *BandCholesky) IsEmpty() bool {
return ch == nil || ch.chol.IsEmpty()
}
// Det returns the determinant of the matrix that has been factorized.
func (ch *BandCholesky) Det() float64 {
if !ch.valid() {
panic(badCholesky)
}
return math.Exp(ch.LogDet())
}
// LogDet returns the log of the determinant of the matrix that has been factorized.
func (ch *BandCholesky) LogDet() float64 {
if !ch.valid() {
panic(badCholesky)
}
var det float64
for i := 0; i < ch.chol.mat.N; i++ {
det += 2 * math.Log(ch.chol.mat.Data[i*ch.chol.mat.Stride])
}
return det
}
func (ch *BandCholesky) valid() bool {
return ch.chol != nil && !ch.chol.IsEmpty()
}
// PivotedCholesky is a symmetric positive semi-definite matrix represented by
// its Cholesky factorization with complete pivoting.
//
// The factorization has the form
//
// A = P * Uᵀ * U * Pᵀ
//
// where U is an upper triangular matrix and P is a permutation matrix.
//
// Cholesky methods may only be called on a receiver that has been successfully
// initialized by a call to Factorize. SolveTo and SolveVecTo methods may only
// called if Factorize has returned true.
//
// If the matrix A is certainly positive definite, then the unpivoted Cholesky
// could be more efficient, especially for smaller matrices.
type PivotedCholesky struct {
chol *TriDense // The factor U
piv, pivTrans []int // The permutation matrices P and Pᵀ
rank int // The computed rank of A
ok bool // Indicates whether and the factorization can be used for solving linear systems
cond float64 // The condition number when ok is true
}
// Factorize computes the Cholesky factorization of the symmetric positive
// semi-definite matrix A and returns whether the matrix is positive definite.
// If Factorize returns false, the SolveTo methods must not be used.
//
// tol is a tolerance used to determine the computed rank of A. If it is
// negative, a default value will be used.
func (c *PivotedCholesky) Factorize(a Symmetric, tol float64) (ok bool) {
n := a.SymmetricDim()
c.reset(n)
copySymIntoTriangle(c.chol, a)
work := getFloat64s(3*c.chol.mat.N, false)
defer putFloat64s(work)
sym := c.chol.asSymBlas()
aNorm := lapack64.Lansy(CondNorm, sym, work)
_, c.rank, c.ok = lapack64.Pstrf(sym, c.piv, tol, work)
if c.ok {
iwork := getInts(n, false)
defer putInts(iwork)
c.cond = 1 / lapack64.Pocon(sym, aNorm, work, iwork)
}
for i, p := range c.piv {
c.pivTrans[p] = i
}
return c.ok
}
// reset prepares the receiver for factorization of matrices of size n.
func (c *PivotedCholesky) reset(n int) {
if c.chol == nil {
c.chol = NewTriDense(n, Upper, nil)
} else {
c.chol.Reset()
c.chol.reuseAsNonZeroed(n, Upper)
}
c.piv = useInt(c.piv, n)
c.pivTrans = useInt(c.pivTrans, n)
c.rank = 0
c.ok = false
c.cond = math.Inf(1)
}
// Dims returns the dimensions of the matrix A.
func (ch *PivotedCholesky) Dims() (r, c int) {
if ch.chol == nil {
panic(badCholesky)
}
r, c = ch.chol.Dims()
return r, c
}
// At returns the element of A at row i, column j.
func (c *PivotedCholesky) At(i, j int) float64 {
if c.chol == nil {
panic(badCholesky)
}
n := c.SymmetricDim()
if uint(i) >= uint(n) {
panic(ErrRowAccess)
}
if uint(j) >= uint(n) {
panic(ErrColAccess)
}
i = c.pivTrans[i]
j = c.pivTrans[j]
minij := min(min(i+1, j+1), c.rank)
var val float64
for k := 0; k < minij; k++ {
val += c.chol.at(k, i) * c.chol.at(k, j)
}
return val
}
// T returns the receiver, the transpose of a symmetric matrix.
func (c *PivotedCholesky) T() Matrix {
return c
}
// SymmetricDim implements the Symmetric interface and returns the number of
// rows (or columns) in the matrix .
func (c *PivotedCholesky) SymmetricDim() int {
if c.chol == nil {
panic(badCholesky)
}
n, _ := c.chol.Dims()
return n
}
// Rank returns the computed rank of the matrix A.
func (c *PivotedCholesky) Rank() int {
if c.chol == nil {
panic(badCholesky)
}
return c.rank
}
// Cond returns the condition number of the factorized matrix.
func (c *PivotedCholesky) Cond() float64 {
if !c.ok {
panic(badCholesky)
}
return c.cond
}
// SolveTo finds the matrix X that solves A * X = B where A is represented by
// the Cholesky decomposition. The result is stored in-place into dst. If the
// Cholesky decomposition is singular or near-singular, a Condition error is
// returned. See the documentation for Condition for more information.
//
// If Factorize returned false, SolveTo will panic.
func (c *PivotedCholesky) SolveTo(dst *Dense, b Matrix) error {
if !c.ok {
panic(badCholesky)
}
n := c.chol.mat.N
bm, bn := b.Dims()
if n != bm {
panic(ErrShape)
}
dst.reuseAsNonZeroed(bm, bn)
if dst != b {
dst.Copy(b)
}
// Permute rows of B: D = Pᵀ * B.
lapack64.Lapmr(true, dst.mat, c.piv)
// Solve Uᵀ * U * Y = D.
lapack64.Potrs(c.chol.mat, dst.mat)
// Permute rows of Y to recover the solution: X = P * Y.
lapack64.Lapmr(false, dst.mat, c.piv)
if c.cond > ConditionTolerance {
return Condition(c.cond)
}
return nil
}
// SolveVecTo finds the vector x that solves A * x = b where A is represented by
// the Cholesky decomposition. The result is stored in-place into dst. If the
// Cholesky decomposition is singular or near-singular, a Condition error is
// returned. See the documentation for Condition for more information.
//
// If Factorize returned false, SolveVecTo will panic.
func (c *PivotedCholesky) SolveVecTo(dst *VecDense, b Vector) error {
if !c.ok {
panic(badCholesky)
}
n := c.chol.mat.N
if br, bc := b.Dims(); br != n || bc != 1 {
panic(ErrShape)
}
if b, ok := b.(RawVectorer); ok && dst != b {
dst.checkOverlap(b.RawVector())
}
dst.reuseAsNonZeroed(n)
if dst != b {
dst.CopyVec(b)
}
// Permute rows of B: D = Pᵀ * B.
lapack64.Lapmr(true, dst.asGeneral(), c.piv)
// Solve Uᵀ * U * Y = D.
lapack64.Potrs(c.chol.mat, dst.asGeneral())
// Permute rows of Y to recover the solution: X = P * Y.
lapack64.Lapmr(false, dst.asGeneral(), c.piv)
if c.cond > ConditionTolerance {
return Condition(c.cond)
}
return nil
}