Skip to content

linalg: Moore-Penrose pseudo-inverse (pinv) #899

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Dec 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 136 additions & 0 deletions doc/specs/stdlib_linalg.md
Original file line number Diff line number Diff line change
Expand Up @@ -1459,6 +1459,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 @@ -16,6 +16,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 @@ -810,6 +813,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
Loading