Skip to content

Commit 7ca8b88

Browse files
authored
Solving a linear system with a symmetric positive definite matrix (#1093)
2 parents eec591d + f429a98 commit 7ca8b88

10 files changed

Lines changed: 984 additions & 6 deletions

doc/specs/stdlib_linalg.md

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -736,6 +736,141 @@ If `err` is not present, exceptions trigger an `error stop`.
736736
{!example/linalg/example_solve3.f90!}
737737
```
738738

739+
## `solve_chol` - Solves a linear system using Cholesky factorization (one-shot interface).
740+
741+
### Status
742+
743+
Experimental
744+
745+
### Description
746+
747+
This subroutine computes the solution to a linear matrix equation \( A \cdot x = b \), where \( A \) is a symmetric (or Hermitian) positive definite matrix. It combines Cholesky factorization and the solve step in a single call.
748+
749+
Result vector or array `x` returns the exact solution to within numerical precision, provided that the matrix is positive definite.
750+
An error is returned if the matrix is not positive definite or has incompatible sizes with the right-hand-side.
751+
Use this routine for one-time solves. For repeated solves with the same matrix but different right-hand sides, use `cholesky` followed by `solve_lower_chol`/`solve_upper_chol` for better performance.
752+
The solver is based on LAPACK's `*POSV` backends.
753+
754+
### Syntax
755+
756+
Simple (`Pure`) interface:
757+
758+
`call ` [[stdlib_linalg(module):solve_chol(interface)]] `(a, b, x)`
759+
760+
Expert (`Pure`) interface:
761+
762+
`call ` [[stdlib_linalg(module):solve_chol(interface)]] `(a, b, x [, lower, overwrite_a, err])`
763+
764+
### Arguments
765+
766+
`a`: Shall be a rank-2 `real` or `complex` square array containing the coefficient matrix. It is an `intent(inout)` argument. By default (`overwrite_a=.false.`) its contents are preserved; if `overwrite_a=.true.`, it is used as temporary storage and its contents are destroyed by the call.
767+
768+
`b`: Shall be a rank-1 or rank-2 array of the same kind as `a`, containing the right-hand-side vector(s). It is an `intent(in)` argument.
769+
770+
`x`: Shall be a rank-1 or rank-2 array of the same kind and size as `b`, that returns the solution(s) to the system. It is an `intent(inout)` argument, and must have the `contiguous` property.
771+
772+
`lower` (optional): Shall be an input `logical` flag. If `.true.` (default), the lower triangular Cholesky factorization is computed. If `.false.`, the upper triangular factorization is computed. It is an `intent(in)` argument.
773+
774+
`overwrite_a` (optional): Shall be an input `logical` flag. If `.true.`, input matrix `a` will be used as temporary storage and overwritten. This avoids internal data allocation. This is an `intent(in)` argument.
775+
776+
`err` (optional): Shall be a `type(linalg_state_type)` value. This is an `intent(out)` argument.
777+
778+
### Return value
779+
780+
For a positive definite matrix, returns an array value that represents the solution to the linear system of equations.
781+
782+
Raises `LINALG_ERROR` if the matrix is not positive definite.
783+
Raises `LINALG_VALUE_ERROR` if the matrix and rhs vectors have invalid/incompatible sizes.
784+
If `err` is not present, exceptions trigger an `error stop`.
785+
786+
### Example
787+
788+
```fortran
789+
{!example/linalg/example_solve_chol.f90!}
790+
```
791+
792+
## `solve_lower_chol` - Solves a linear system using pre-computed lower Cholesky factor.
793+
794+
### Status
795+
796+
Experimental
797+
798+
### Description
799+
800+
This subroutine computes the solution to a linear matrix equation \( A \cdot x = b \), where \( A \) is a symmetric (or Hermitian) positive definite matrix that has been **previously factorized** using the Cholesky decomposition (via `cholesky` with `lower=.true.`).
801+
802+
Result vector or array `x` returns the exact solution to within numerical precision, provided that the factorization is correct.
803+
An error is returned if the matrix and right-hand-side have incompatible sizes.
804+
The solver is based on LAPACK's `*POTRS` backends.
805+
806+
### Syntax
807+
808+
`call ` [[stdlib_linalg(module):solve_lower_chol(interface)]] `(l, b, x [, err])`
809+
810+
### Arguments
811+
812+
`l`: Shall be a rank-2 `real` or `complex` square array containing the **lower** Cholesky factor `L` (output of `cholesky(..., lower=.true.)`). It is an `intent(in)` argument.
813+
814+
`b`: Shall be a rank-1 or rank-2 array of the same kind as `l`, containing the right-hand-side vector(s). It is an `intent(in)` argument.
815+
816+
`x`: Shall be a rank-1 or rank-2 array of the same kind and size as `b`, that returns the solution(s) to the system. It is an `intent(inout)` argument, and must have the `contiguous` property.
817+
818+
`err` (optional): Shall be a `type(linalg_state_type)` value. This is an `intent(out)` argument.
819+
820+
### Return value
821+
822+
For a correctly factorized matrix, returns an array value that represents the solution to the linear system of equations.
823+
824+
Raises `LINALG_VALUE_ERROR` if the matrix and rhs vectors have invalid/incompatible sizes.
825+
If `err` is not present, exceptions trigger an `error stop`.
826+
827+
### Example
828+
829+
```fortran
830+
{!example/linalg/example_solve_lower_chol.f90!}
831+
```
832+
833+
## `solve_upper_chol` - Solves a linear system using pre-computed upper Cholesky factor.
834+
835+
### Status
836+
837+
Experimental
838+
839+
### Description
840+
841+
This subroutine computes the solution to a linear matrix equation \( A \cdot x = b \), where \( A \) is a symmetric (or Hermitian) positive definite matrix that has been **previously factorized** using the Cholesky decomposition (via `cholesky` with `lower=.false.`).
842+
843+
Result vector or array `x` returns the exact solution to within numerical precision, provided that the factorization is correct.
844+
An error is returned if the matrix and right-hand-side have incompatible sizes.
845+
The solver is based on LAPACK's `*POTRS` backends.
846+
847+
### Syntax
848+
849+
`call ` [[stdlib_linalg(module):solve_upper_chol(interface)]] `(u, b, x [, err])`
850+
851+
### Arguments
852+
853+
`u`: Shall be a rank-2 `real` or `complex` square array containing the **upper** Cholesky factor `U` (output of `cholesky(..., lower=.false.)`). It is an `intent(in)` argument.
854+
855+
`b`: Shall be a rank-1 or rank-2 array of the same kind as `u`, containing the right-hand-side vector(s). It is an `intent(in)` argument.
856+
857+
`x`: Shall be a rank-1 or rank-2 array of the same kind and size as `b`, that returns the solution(s) to the system. It is an `intent(inout)` argument, and must have the `contiguous` property.
858+
859+
`err` (optional): Shall be a `type(linalg_state_type)` value. This is an `intent(out)` argument.
860+
861+
### Return value
862+
863+
For a correctly factorized matrix, returns an array value that represents the solution to the linear system of equations.
864+
865+
Raises `LINALG_VALUE_ERROR` if the matrix and rhs vectors have invalid/incompatible sizes.
866+
If `err` is not present, exceptions trigger an `error stop`.
867+
868+
### Example
869+
870+
```fortran
871+
{!example/linalg/example_solve_upper_chol.f90!}
872+
```
873+
739874
## `lstsq` - Computes the least squares solution to a linear matrix equation.
740875

741876
### Status

example/linalg/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,5 +63,8 @@ ADD_EXAMPLE(qr_space)
6363
ADD_EXAMPLE(pivoting_qr_space)
6464
ADD_EXAMPLE(cholesky)
6565
ADD_EXAMPLE(chol)
66+
ADD_EXAMPLE(solve_chol)
67+
ADD_EXAMPLE(solve_lower_chol)
68+
ADD_EXAMPLE(solve_upper_chol)
6669
ADD_EXAMPLE(expm)
6770
ADD_EXAMPLE(matrix_exp)
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
program example_solve_chol
2+
use stdlib_linalg_constants, only: dp
3+
use stdlib_linalg, only: solve_chol, linalg_state_type
4+
implicit none
5+
6+
real(dp) :: A(3,3), b(3), x(3)
7+
type(linalg_state_type) :: state
8+
9+
! Symmetric positive definite matrix
10+
A(1,:) = [4.0_dp, 2.0_dp, 2.0_dp]
11+
A(2,:) = [2.0_dp, 5.0_dp, 1.0_dp]
12+
A(3,:) = [2.0_dp, 1.0_dp, 6.0_dp]
13+
14+
! Right-hand side
15+
b = [1.0_dp, 2.0_dp, 3.0_dp]
16+
17+
! One-shot Cholesky factorization and solve (A is preserved by default)
18+
call solve_chol(A, b, x, lower=.true., err=state)
19+
if (state%error()) error stop state%print()
20+
21+
print '("Solution: ",*(f8.4,1x))', x
22+
23+
! For performance-critical code, use overwrite_a=.true.
24+
! to avoid internal allocation (but A will be destroyed)
25+
! call solve_chol(A, b, x, lower=.true., overwrite_a=.true., err=state)
26+
27+
end program example_solve_chol
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
program example_solve_lower_chol
2+
use stdlib_linalg_constants, only: dp
3+
use stdlib_linalg, only: cholesky, solve_lower_chol, linalg_state_type
4+
implicit none
5+
6+
real(dp) :: A(3,3), L(3,3), b1(3), b2(3), x(3)
7+
type(linalg_state_type) :: state
8+
9+
! Symmetric positive definite matrix
10+
A(1,:) = [4.0_dp, 2.0_dp, 2.0_dp]
11+
A(2,:) = [2.0_dp, 5.0_dp, 1.0_dp]
12+
A(3,:) = [2.0_dp, 1.0_dp, 6.0_dp]
13+
14+
! Compute lower Cholesky factorization once: A = L * L^T
15+
call cholesky(A, L, lower=.true., err=state)
16+
if (state%error()) error stop state%print()
17+
18+
! First right-hand side
19+
b1 = [1.0_dp, 2.0_dp, 3.0_dp]
20+
call solve_lower_chol(L, b1, x, err=state)
21+
if (state%error()) error stop state%print()
22+
print '("Solution 1: ",*(f8.4,1x))', x
23+
24+
! Second right-hand side (reusing the same factorization)
25+
b2 = [4.0_dp, 5.0_dp, 6.0_dp]
26+
call solve_lower_chol(L, b2, x, err=state)
27+
if (state%error()) error stop state%print()
28+
print '("Solution 2: ",*(f8.4,1x))', x
29+
30+
end program example_solve_lower_chol
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
program example_solve_upper_chol
2+
use stdlib_linalg_constants, only: dp
3+
use stdlib_linalg, only: cholesky, solve_upper_chol, linalg_state_type
4+
implicit none
5+
6+
real(dp) :: A(3,3), U(3,3), b1(3), b2(3), x(3)
7+
type(linalg_state_type) :: state
8+
9+
! Symmetric positive definite matrix
10+
A(1,:) = [4.0_dp, 2.0_dp, 2.0_dp]
11+
A(2,:) = [2.0_dp, 5.0_dp, 1.0_dp]
12+
A(3,:) = [2.0_dp, 1.0_dp, 6.0_dp]
13+
14+
! Compute upper Cholesky factorization once: A = U^T * U
15+
call cholesky(A, U, lower=.false., err=state)
16+
if (state%error()) error stop state%print()
17+
18+
! First right-hand side
19+
b1 = [1.0_dp, 2.0_dp, 3.0_dp]
20+
call solve_upper_chol(U, b1, x, err=state)
21+
if (state%error()) error stop state%print()
22+
print '("Solution 1: ",*(f8.4,1x))', x
23+
24+
! Second right-hand side (reusing the same factorization)
25+
b2 = [4.0_dp, 5.0_dp, 6.0_dp]
26+
call solve_upper_chol(U, b2, x, err=state)
27+
if (state%error()) error stop state%print()
28+
print '("Solution 2: ",*(f8.4,1x))', x
29+
30+
end program example_solve_upper_chol

src/lapack/stdlib_linalg_lapack_aux.fypp

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ module stdlib_linalg_lapack_aux
4141
public :: stdlib_selctg_${ri}$
4242
#:endfor
4343
public :: handle_potrf_info
44+
public :: handle_potrs_info
45+
public :: handle_posv_info
4446
public :: handle_getri_info
4547
public :: handle_gesdd_info
4648
public :: handle_gesv_info
@@ -1323,6 +1325,65 @@ module stdlib_linalg_lapack_aux
13231325

13241326
end subroutine handle_potrf_info
13251327

1328+
! Cholesky solve (triangular solve with pre-computed factors)
1329+
elemental subroutine handle_potrs_info(this,info,triangle,n,nrhs,lda,ldb,err)
1330+
character(len=*), intent(in) :: this
1331+
character, intent(in) :: triangle
1332+
integer(ilp), intent(in) :: info,n,nrhs,lda,ldb
1333+
type(linalg_state_type), intent(out) :: err
1334+
1335+
! Process output
1336+
select case (info)
1337+
case (0)
1338+
err%state = LINALG_SUCCESS
1339+
case (-1)
1340+
err = linalg_state_type(this,LINALG_INTERNAL_ERROR,'invalid triangle selection: ', &
1341+
triangle,'. should be U/L')
1342+
case (-2)
1343+
err = linalg_state_type(this,LINALG_VALUE_ERROR,'invalid matrix size n=',n)
1344+
case (-3)
1345+
err = linalg_state_type(this,LINALG_VALUE_ERROR,'invalid rhs size nrhs=',nrhs)
1346+
case (-5)
1347+
err = linalg_state_type(this,LINALG_VALUE_ERROR,'invalid lda=',lda,': should be >=',n)
1348+
case (-7)
1349+
err = linalg_state_type(this,LINALG_VALUE_ERROR,'invalid ldb=',ldb,': should be >=',n)
1350+
case default
1351+
err = linalg_state_type(this,LINALG_INTERNAL_ERROR,'catastrophic error')
1352+
end select
1353+
1354+
end subroutine handle_potrs_info
1355+
1356+
! Cholesky factorization and solve (combined)
1357+
elemental subroutine handle_posv_info(this,info,triangle,n,nrhs,lda,ldb,err)
1358+
character(len=*), intent(in) :: this
1359+
character, intent(in) :: triangle
1360+
integer(ilp), intent(in) :: info,n,nrhs,lda,ldb
1361+
type(linalg_state_type), intent(out) :: err
1362+
1363+
! Process output
1364+
select case (info)
1365+
case (0)
1366+
err%state = LINALG_SUCCESS
1367+
case (-1)
1368+
err = linalg_state_type(this,LINALG_INTERNAL_ERROR,'invalid triangle selection: ', &
1369+
triangle,'. should be U/L')
1370+
case (-2)
1371+
err = linalg_state_type(this,LINALG_VALUE_ERROR,'invalid matrix size n=',n)
1372+
case (-3)
1373+
err = linalg_state_type(this,LINALG_VALUE_ERROR,'invalid rhs size nrhs=',nrhs)
1374+
case (-5)
1375+
err = linalg_state_type(this,LINALG_VALUE_ERROR,'invalid lda=',lda,': should be >=',n)
1376+
case (-7)
1377+
err = linalg_state_type(this,LINALG_VALUE_ERROR,'invalid ldb=',ldb,': should be >=',n)
1378+
case (1:)
1379+
err = linalg_state_type(this,LINALG_ERROR,'matrix is not positive definite: ', &
1380+
'leading minor of order',info,' is not positive definite')
1381+
case default
1382+
err = linalg_state_type(this,LINALG_INTERNAL_ERROR,'catastrophic error')
1383+
end select
1384+
1385+
end subroutine handle_posv_info
1386+
13261387
elemental subroutine handle_getri_info(this,info,lda,n,err)
13271388
character(len=*), intent(in) :: this
13281389
integer(ilp), intent(in) :: info,lda,n

0 commit comments

Comments
 (0)