Skip to content

Commit

Permalink
linalg: Moore-Penrose pseudo-inverse (pinv) (#899)
Browse files Browse the repository at this point in the history
  • Loading branch information
perazz authored Dec 26, 2024
2 parents 095219e + 8297763 commit 195d4a5
Show file tree
Hide file tree
Showing 9 changed files with 686 additions and 6 deletions.
136 changes: 136 additions & 0 deletions doc/specs/stdlib_linalg.md
Original file line number Diff line number Diff line change
Expand Up @@ -1487,6 +1487,142 @@ If `err` is not present, exceptions trigger an `error stop`.
{!example/linalg/example_inverse_function.f90!}
```

## `pinv` - Moore-Penrose pseudo-inverse of a matrix

### Status

Experimental

### Description

This function computes the Moore-Penrose pseudo-inverse of a `real` or `complex` matrix.
The pseudo-inverse, \( A^{+} \), generalizes the matrix inverse and satisfies the conditions:
- \( A \cdot A^{+} \cdot A = A \)
- \( A^{+} \cdot A \cdot A^{+} = A^{+} \)
- \( (A \cdot A^{+})^T = A \cdot A^{+} \)
- \( (A^{+} \cdot A)^T = A^{+} \cdot A \)

The computation is based on singular value decomposition (SVD). Singular values below a relative
tolerance threshold \( \text{rtol} \cdot \sigma_{\max} \), where \( \sigma_{\max} \) is the largest
singular value, are treated as zero.

### Syntax

`b =` [[stdlib_linalg(module):pinv(interface)]] `(a, [, rtol, err])`

### Arguments

`a`: Shall be a rank-2, `real` or `complex` array of shape `[m, n]` containing the coefficient matrix.
It is an `intent(in)` argument.

`rtol` (optional): Shall be a scalar `real` value specifying the relative tolerance for singular value cutoff.
If `rtol` is not provided, the default relative tolerance is \( \text{rtol} = \text{max}(m, n) \cdot \epsilon \),
where \( \epsilon \) is the machine precision for the element type of `a`. It is an `intent(in)` argument.

`err` (optional): Shall be a `type(linalg_state_type)` value. It is an `intent(out)` argument.

### Return value

Returns an array value of the same type, kind, and rank as `a` with shape `[n, m]`, that contains the pseudo-inverse matrix \( A^{+} \).

Raises `LINALG_ERROR` if the underlying SVD did not converge.
Raises `LINALG_VALUE_ERROR` if `a` has invalid size.
If `err` is not present, exceptions trigger an `error stop`.

### Example

```fortran
{!example/linalg/example_pseudoinverse.f90!}
```

## `pseudoinvert` - Moore-Penrose pseudo-inverse of a matrix

### Status

Experimental

### Description

This subroutine computes the Moore-Penrose pseudo-inverse of a `real` or `complex` matrix.
The pseudo-inverse \( A^{+} \) is a generalization of the matrix inverse and satisfies the following properties:
- \( A \cdot A^{+} \cdot A = A \)
- \( A^{+} \cdot A \cdot A^{+} = A^{+} \)
- \( (A \cdot A^{+})^T = A \cdot A^{+} \)
- \( (A^{+} \cdot A)^T = A^{+} \cdot A \)

The computation is based on singular value decomposition (SVD). Singular values below a relative
tolerance threshold \( \text{rtol} \cdot \sigma_{\max} \), where \( \sigma_{\max} \) is the largest
singular value, are treated as zero.

On return, matrix `pinva` `[n, m]` will store the pseudo-inverse of `a` `[m, n]`.

### Syntax

`call ` [[stdlib_linalg(module):pseudoinvert(interface)]] `(a, pinva [, rtol] [, err])`

### Arguments

`a`: Shall be a rank-2, `real` or `complex` array containing the coefficient matrix.
It is an `intent(in)` argument.

`pinva`: Shall be a rank-2 array of the same kind as `a`, and size equal to that of `transpose(a)`.
On output, it contains the Moore-Penrose pseudo-inverse of `a`.

`rtol` (optional): Shall be a scalar `real` value specifying the relative tolerance for singular value cutoff.
If not provided, the default threshold is \( \text{max}(m, n) \cdot \epsilon \), where \( \epsilon \) is the
machine precision for the element type of `a`.

`err` (optional): Shall be a `type(linalg_state_type)` value. It is an `intent(out)` argument.

### Return value

Computes the Moore-Penrose pseudo-inverse of the matrix \( A \), \( A^{+} \), and returns it in matrix `pinva`.

Raises `LINALG_ERROR` if the underlying SVD did not converge.
Raises `LINALG_VALUE_ERROR` if `pinva` and `a` have degenerate or incompatible sizes.
If `err` is not present, exceptions trigger an `error stop`.

### Example

```fortran
{!example/linalg/example_pseudoinverse.f90!}
```

## `.pinv.` - Moore-Penrose Pseudo-Inverse operator

### Status

Experimental

### Description

This operator returns the Moore-Penrose pseudo-inverse of a `real` or `complex` matrix \( A \).
The pseudo-inverse \( A^{+} \) is computed using Singular Value Decomposition (SVD), and singular values
below a given threshold are treated as zero.

This interface is equivalent to the function [[stdlib_linalg(module):pinv(interface)]].

### Syntax

`b = ` [[stdlib_linalg(module):operator(.pinv.)(interface)]] `a`

### Arguments

`a`: Shall be a rank-2 array of any `real` or `complex` kinds, with arbitrary dimensions \( m \times n \). It is an `intent(in)` argument.

### Return value

Returns a rank-2 array with the same type, kind, and rank as `a`, that contains the Moore-Penrose pseudo-inverse of `a`.

If an exception occurs, or if the input matrix is degenerate (e.g., rank-deficient), the returned matrix will contain `NaN`s.
For more detailed error handling, it is recommended to use the `subroutine` or `function` interfaces.

### Example

```fortran
{!example/linalg/example_pseudoinverse.f90!}
```

## `get_norm` - Computes the vector norm of a generic-rank array.

### Status
Expand Down
1 change: 1 addition & 0 deletions example/linalg/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ ADD_EXAMPLE(inverse_operator)
ADD_EXAMPLE(inverse_function)
ADD_EXAMPLE(inverse_inplace)
ADD_EXAMPLE(inverse_subroutine)
ADD_EXAMPLE(pseudoinverse)
ADD_EXAMPLE(outer_product)
ADD_EXAMPLE(eig)
ADD_EXAMPLE(eigh)
Expand Down
32 changes: 32 additions & 0 deletions example/linalg/example_pseudoinverse.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
! Matrix pseudo-inversion example: function, subroutine, and operator interfaces
program example_pseudoinverse
use stdlib_linalg, only: pinv, pseudoinvert, operator(.pinv.), linalg_state_type
implicit none(type,external)

real :: A(15,5), Am1(5,15)
type(linalg_state_type) :: state
integer :: i, j
real, parameter :: tol = sqrt(epsilon(0.0))

! Generate random matrix A (15x15)
call random_number(A)

! Pseudo-inverse: Function interfcae
Am1 = pinv(A, err=state)
print *, 'Max error (function) : ', maxval(abs(A-matmul(A, matmul(Am1,A))))

! User threshold
Am1 = pinv(A, rtol=0.001, err=state)
print *, 'Max error (rtol=0.001): ', maxval(abs(A-matmul(A, matmul(Am1,A))))

! Pseudo-inverse: Subroutine interface
call pseudoinvert(A, Am1, err=state)

print *, 'Max error (subroutine): ', maxval(abs(A-matmul(A, matmul(Am1,A))))

! Operator interface
Am1 = .pinv.A

print *, 'Max error (operator) : ', maxval(abs(A-matmul(A, matmul(Am1,A))))

end program example_pseudoinverse
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ set(fppFiles
stdlib_linalg_determinant.fypp
stdlib_linalg_qr.fypp
stdlib_linalg_inverse.fypp
stdlib_linalg_pinv.fypp
stdlib_linalg_norms.fypp
stdlib_linalg_state.fypp
stdlib_linalg_svd.fypp
Expand Down
10 changes: 5 additions & 5 deletions src/stdlib_io.fypp
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ contains
read (s,*,iostat=ios,iomsg=iomsg) d(i, :)

if (ios/=0) then
write(msgout,1) trim(iomsg),i,trim(filename)
write(msgout,1) trim(iomsg),size(d,2),i,trim(filename)
call error_stop(msg=trim(msgout))
end if

Expand All @@ -178,7 +178,7 @@ contains
read (s,fmt_,iostat=ios,iomsg=iomsg) d(i, :)

if (ios/=0) then
write(msgout,1) trim(iomsg),i,trim(filename)
write(msgout,1) trim(iomsg),size(d,2),i,trim(filename)
call error_stop(msg=trim(msgout))
end if

Expand All @@ -187,7 +187,7 @@ contains

close(s)

1 format('loadtxt: error <',a,'> reading line ',i0,' of ',a,'.')
1 format('loadtxt: error <',a,'> reading ',i0,' values from line ',i0,' of ',a,'.')

end subroutine loadtxt_${t1[0]}$${k1}$
#:endfor
Expand Down Expand Up @@ -230,14 +230,14 @@ contains
iostat=ios,iomsg=iomsg) d(i, :)

if (ios/=0) then
write(msgout,1) trim(iomsg),i,trim(filename)
write(msgout,1) trim(iomsg),size(d,2),i,trim(filename)
call error_stop(msg=trim(msgout))
end if

end do
close(s)

1 format('savetxt: error <',a,'> writing line ',i0,' of ',a,'.')
1 format('savetxt: error <',a,'> writing ',i0,' values to line ',i0,' of ',a,'.')

end subroutine savetxt_${t1[0]}$${k1}$
#:endfor
Expand Down
128 changes: 128 additions & 0 deletions src/stdlib_linalg.fypp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ module stdlib_linalg
public :: inv
public :: invert
public :: operator(.inv.)
public :: pinv
public :: pseudoinvert
public :: operator(.pinv.)
public :: lstsq
public :: lstsq_space
public :: norm
Expand Down Expand Up @@ -846,6 +849,131 @@ module stdlib_linalg
end interface operator(.inv.)
! Moore-Penrose Pseudo-Inverse: Function interface
interface pinv
!! version: experimental
!!
!! Pseudo-inverse of a matrix
!! ([Specification](../page/specs/stdlib_linalg.html#pinv-moore-penrose-pseudo-inverse-of-a-matrix))
!!
!!### Summary
!! This interface provides methods for computing the Moore-Penrose pseudo-inverse of a matrix.
!! The pseudo-inverse \( A^{+} \) is a generalization of the matrix inverse, computed for square, singular,
!! or rectangular matrices. It is defined such that it satisfies the conditions:
!! - \( A \cdot A^{+} \cdot A = A \)
!! - \( A^{+} \cdot A \cdot A^{+} = A^{+} \)
!! - \( (A \cdot A^{+})^T = A \cdot A^{+} \)
!! - \( (A^{+} \cdot A)^T = A^{+} \cdot A \)
!!
!!### Description
!!
!! This function interface provides methods that return the Moore-Penrose pseudo-inverse of a matrix.
!! Supported data types include `real` and `complex`.
!! The pseudo-inverse \( A^{+} \) is returned as a function result. The computation is based on the
!! singular value decomposition (SVD). An optional relative tolerance `rtol` is provided to control the
!! inclusion of singular values during inversion. Singular values below \( \text{rtol} \cdot \sigma_{\max} \)
!! are treated as zero, where \( \sigma_{\max} \) is the largest singular value. If `rtol` is not provided,
!! a default threshold is applied.
!!
!! Exceptions are raised in case of computational errors or invalid input, and trigger an `error stop`
!! if the state flag `err` is not provided.
!!
!!@note The provided functions are intended for both rectangular and square matrices.
!!
#:for rk,rt,ri in RC_KINDS_TYPES
module function stdlib_linalg_pseudoinverse_${ri}$(a,rtol,err) result(pinva)
!> Input matrix a[m,n]
${rt}$, intent(in), target :: a(:,:)
!> [optional] Relative tolerance for singular value cutoff
real(${rk}$), optional, intent(in) :: rtol
!> [optional] State return flag. On error if not requested, the code will stop
type(linalg_state_type), optional, intent(out) :: err
!> Output matrix pseudo-inverse [n,m]
${rt}$ :: pinva(size(a,2,kind=ilp),size(a,1,kind=ilp))
end function stdlib_linalg_pseudoinverse_${ri}$
#:endfor
end interface pinv
! Moore-Penrose Pseudo-Inverse: Subroutine interface
interface pseudoinvert
!! version: experimental
!!
!! Computation of the Moore-Penrose pseudo-inverse
!! ([Specification](../page/specs/stdlib_linalg.html#pseudoinvert-moore-penrose-pseudo-inverse-of-a-matrix))
!!
!!### Summary
!! This interface provides methods for computing the Moore-Penrose pseudo-inverse of a rectangular
!! or square `real` or `complex` matrix.
!! The pseudo-inverse \( A^{+} \) generalizes the matrix inverse and satisfies the properties:
!! - \( A \cdot A^{+} \cdot A = A \)
!! - \( A^{+} \cdot A \cdot A^{+} = A^{+} \)
!! - \( (A \cdot A^{+})^T = A \cdot A^{+} \)
!! - \( (A^{+} \cdot A)^T = A^{+} \cdot A \)
!!
!!### Description
!!
!! This subroutine interface provides a way to compute the Moore-Penrose pseudo-inverse of a matrix.
!! Supported data types include `real` and `complex`.
!! Users must provide two matrices: the input matrix `a` [m,n] and the output pseudo-inverse `pinva` [n,m].
!! The input matrix `a` is used to compute the pseudo-inverse and is not modified. The computed
!! pseudo-inverse is stored in `pinva`. The computation is based on the singular value decomposition (SVD).
!!
!! An optional relative tolerance `rtol` is used to control the inclusion of singular values in the
!! computation. Singular values below \( \text{rtol} \cdot \sigma_{\max} \) are treated as zero,
!! where \( \sigma_{\max} \) is the largest singular value. If `rtol` is not provided, a default
!! threshold is applied.
!!
!! Exceptions are raised in case of computational errors or invalid input, and trigger an `error stop`
!! if the state flag `err` is not provided.
!!
!!@note The provided subroutines are intended for both rectangular and square matrices.
!!
#:for rk,rt,ri in RC_KINDS_TYPES
module subroutine stdlib_linalg_pseudoinvert_${ri}$(a,pinva,rtol,err)
!> Input matrix a[m,n]
${rt}$, intent(inout) :: a(:,:)
!> Output pseudo-inverse matrix [n,m]
${rt}$, intent(out) :: pinva(:,:)
!> [optional] Relative tolerance for singular value cutoff
real(${rk}$), optional, intent(in) :: rtol
!> [optional] State return flag. On error if not requested, the code will stop
type(linalg_state_type), optional, intent(out) :: err
end subroutine stdlib_linalg_pseudoinvert_${ri}$
#:endfor
end interface pseudoinvert
! Moore-Penrose Pseudo-Inverse: Operator interface
interface operator(.pinv.)
!! version: experimental
!!
!! Pseudo-inverse operator of a matrix
!! ([Specification](../page/specs/stdlib_linalg.html#pinv-moore-penrose-pseudo-inverse-operator))
!!
!!### Summary
!! Operator interface for computing the Moore-Penrose pseudo-inverse of a `real` or `complex` matrix.
!!
!!### Description
!!
!! This operator interface provides a convenient way to compute the Moore-Penrose pseudo-inverse
!! of a matrix. Supported data types include `real` and `complex`. The pseudo-inverse \( A^{+} \)
!! is computed using singular value decomposition (SVD), with singular values below an internal
!! threshold treated as zero.
!!
!! For computational errors or invalid input, the function may return a matrix filled with NaNs.
!!
!!@note The provided functions are intended for both rectangular and square matrices.
!!
#:for rk,rt,ri in RC_KINDS_TYPES
module function stdlib_linalg_pinv_${ri}$_operator(a) result(pinva)
!> Input matrix a[m,n]
${rt}$, intent(in), target :: a(:,:)
!> Result pseudo-inverse matrix
${rt}$ :: pinva(size(a,2,kind=ilp),size(a,1,kind=ilp))
end function stdlib_linalg_pinv_${ri}$_operator
#:endfor
end interface operator(.pinv.)
! Eigendecomposition of a square matrix: eigenvalues, and optionally eigenvectors
interface eig
!! version: experimental
Expand Down
Loading

0 comments on commit 195d4a5

Please sign in to comment.