Skip to content

Exponential of a matrix #968

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

Open
wants to merge 31 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
b0a74b1
Working implementation of expm.
loiseaujc Mar 27, 2025
fa36f33
Improved implementation + error handling.
loiseaujc Mar 27, 2025
d8b1857
Added docstring for the interface.
loiseaujc Mar 27, 2025
4089d18
Specs + example.
loiseaujc Mar 27, 2025
c6857bc
Update doc/specs/stdlib_linalg.md
loiseaujc Mar 30, 2025
4310db5
Replace matmul with gemm.
loiseaujc Mar 31, 2025
59ffb20
Error handling tests.
loiseaujc Mar 31, 2025
56474e1
Merge branch 'matrix_exponential' into master
loiseaujc Jul 3, 2025
75e0892
Merge pull request #2 from loiseaujc/master
loiseaujc Jul 3, 2025
8d6a3f9
Remove tests for failure to pinpoint seg fault.
loiseaujc Jul 3, 2025
65ad5f2
Pinpointing why the expm test fails.
loiseaujc Jul 3, 2025
cc3f1f2
Revert "Pinpointing why the expm test fails."
loiseaujc Jul 3, 2025
28bb69b
Remove print statement.
loiseaujc Jul 3, 2025
f479582
Change operator norm to standard norm for error checking.
loiseaujc Jul 3, 2025
b092515
Fix import
loiseaujc Jul 3, 2025
1bb1e01
Make use of stdlib_constants to avoid redefining some variables.
loiseaujc Jul 4, 2025
3ebdf9e
Replaced matmul with gemm.
loiseaujc Jul 4, 2025
f99e804
merge trick replacing if i == j.
loiseaujc Jul 8, 2025
8acc8de
Merge branch 'master'
loiseaujc Jul 8, 2025
34745cb
Make use of the new handle_gesv_info function.
loiseaujc Jul 8, 2025
534a88d
Define log(2.0) as a constant.
loiseaujc Jul 8, 2025
d97043d
Specify integer kind in size function.
loiseaujc Jul 8, 2025
a381d0b
subroutine driver and interface (in-place and out-of-place)
loiseaujc Jul 9, 2025
1bc6427
Fixed error handling.
loiseaujc Jul 9, 2025
531261a
Looking for the msys2-build error
loiseaujc Jul 9, 2025
3941673
Revert "Looking for the msys2-build error"
loiseaujc Jul 9, 2025
3b9c77d
Print computed matrix for reference.
loiseaujc Jul 9, 2025
2258de3
Revert "Print computed matrix for reference."
loiseaujc Jul 9, 2025
ae14bb5
Looking for the mysys2-build error.
loiseaujc Jul 9, 2025
1fb76b2
Revert "Looking for the mysys2-build error."
loiseaujc Jul 9, 2025
5eb6b47
Looking for mysys2-build error.
loiseaujc Jul 9, 2025
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
35 changes: 35 additions & 0 deletions doc/specs/stdlib_linalg.md
Original file line number Diff line number Diff line change
Expand Up @@ -1884,3 +1884,38 @@ If `err` is not present, exceptions trigger an `error stop`.
{!example/linalg/example_mnorm.f90!}
```

## `expm` - Computes the matrix exponential {#expm}

### Status

Experimental

### Description

Given a matrix \(A\), this function compute its matrix exponential \(E = \exp(A)\) using a Pade approximation.

### Syntax

`E = ` [[stdlib_linalg(module):expm(interface)]] `(a [, order, err])`

### Arguments

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

`order` (optional): Shall be a non-negative `integer` value specifying the order of the Pade approximation. By default `order=10`. It is an `intent(in)` argument.

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

### Return value

The returned array `E` contains the Pade approximation of \(\exp(A)\).

If `A` is non-square or `order` is negative, it raise a `LINALG_VALUE_ERROR`.
If `err` is not present, exceptions trigger an `error stop`.

### Example

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

1 change: 1 addition & 0 deletions example/linalg/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,4 @@ ADD_EXAMPLE(qr)
ADD_EXAMPLE(qr_space)
ADD_EXAMPLE(cholesky)
ADD_EXAMPLE(chol)
ADD_EXAMPLE(expm)
7 changes: 7 additions & 0 deletions example/linalg/example_expm.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
program example_expm
use stdlib_linalg, only: expm
implicit none
real :: A(3, 3), E(3, 3)
A = reshape([1, 2, 3, 4, 5, 6, 7, 8, 9], [3, 3])
E = expm(A)
end program example_expm
5 changes: 3 additions & 2 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,17 @@ set(fppFiles
stdlib_linalg_kronecker.fypp
stdlib_linalg_cross_product.fypp
stdlib_linalg_eigenvalues.fypp
stdlib_linalg_solve.fypp
stdlib_linalg_solve.fypp
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
stdlib_linalg_svd.fypp
stdlib_linalg_cholesky.fypp
stdlib_linalg_schur.fypp
stdlib_linalg_matrix_functions.fypp
stdlib_optval.fypp
stdlib_selection.fypp
stdlib_sorting.fypp
Expand Down
1 change: 1 addition & 0 deletions src/stdlib_constants.fypp
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ module stdlib_constants
#:for k, t, s in R_KINDS_TYPES
${t}$, parameter, public :: zero_${s}$ = 0._${k}$
${t}$, parameter, public :: one_${s}$ = 1._${k}$
${t}$, parameter, public :: log2_${s}$ = log(2.0_${k}$)
#:endfor
#:for k, t, s in C_KINDS_TYPES
${t}$, parameter, public :: zero_${s}$ = (0._${k}$,0._${k}$)
Expand Down
103 changes: 103 additions & 0 deletions src/stdlib_linalg.fypp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ module stdlib_linalg
public :: eigh
public :: eigvals
public :: eigvalsh
public :: expm, matrix_exp
public :: eye
public :: inv
public :: invert
Expand Down Expand Up @@ -1678,6 +1679,108 @@ module stdlib_linalg
#:endfor
end interface mnorm

!> Matrix exponential: function interface
interface expm
!! version : experimental
!!
!! Computes the exponential of a matrix using a rational Pade approximation.
!! ([Specification](../page/specs/stdlib_linalg.html#expm))
!!
!! ### Description
!!
!! This interface provides methods for computing the exponential of a matrix
!! represented as a standard Fortran rank-2 array. Supported data types include
!! `real` and `complex`.
!!
!! By default, the order of the Pade approximation is set to 10. It can be changed
!! via the `order` argument which must be non-negative.
!!
!! If the input matrix is non-square or the order of the Pade approximation is
!! negative, the function returns an error state.
!!
!! ### Example
!!
!! ```fortran
!! real(dp) :: A(3, 3), E(3, 3)
!!
!! A = reshape([1, 2, 3, 4, 5, 6, 7, 8, 9], [3, 3])
!!
!! ! Default Pade approximation of the matrix exponential.
!! E = expm(A)
!!
!! ! Pade approximation with specified order.
!! E = expm(A, order=12)
!! ```
!!
#:for rk,rt,ri in RC_KINDS_TYPES
module function stdlib_linalg_${ri}$_expm_fun(A, order) result(E)
!> Input matrix a(n, n).
${rt}$, intent(in) :: A(:, :)
!> [optional] Order of the Pade approximation (default `order=10`)
integer(ilp), optional, intent(in) :: order
!> Exponential of the input matrix E = exp(A).
${rt}$, allocatable :: E(:, :)
end function stdlib_linalg_${ri}$_expm_fun
#:endfor
end interface expm

!> Matrix exponential: subroutine interface
interface matrix_exp
!! version : experimental
!!
!! Computes the exponential of a matrix using a rational Pade approximation.
!! ([Specification](../page/specs/stdlib_linalg.html#matrix_exp))
!!
!! ### Description
!!
!! This interface provides methods for computing the exponential of a matrix
!! represented as a standard Fortran rank-2 array. Supported data types include
!! `real` and `complex`.
!!
!! By default, the order of the Pade approximation is set to 10. It can be changed
!! via the `order` argument which must be non-negative.
!!
!! If the input matrix is non-square or the order of the Pade approximation is
!! negative, the function returns an error state.
!!
!! ### Example
!!
!! ```fortran
!! real(dp) :: A(3, 3), E(3, 3)
!!
!! A = reshape([1, 2, 3, 4, 5, 6, 7, 8, 9], [3, 3])
!!
!! ! Default Pade approximation of the matrix exponential.
!! call matrix_exp(A, E) ! Out-of-place
!! ! call matrix_exp(A) for in-place computation.
!!
!! ! Pade approximation with specified order.
!! call matrix_exp(A, E, order=12)
!! ```
!!
#:for rk,rt,ri in RC_KINDS_TYPES
module subroutine stdlib_linalg_${ri}$_expm_inplace(A, order, err)
!> Input matrix A(n, n) / Output matrix E = exp(A)
${rt}$, intent(inout) :: A(:, :)
!> [optional] Order of the Pade approximation (default `order=10`)
integer(ilp), optional, intent(in) :: order
!> [optional] Error handling.
type(linalg_state_type), optional, intent(out) :: err
end subroutine stdlib_linalg_${ri}$_expm_inplace

module subroutine stdlib_linalg_${ri}$_expm(A, E, order, err)
!> Input matrix A(n, n)
${rt}$, intent(in) :: A(:, :)
!> Output matrix exponential E = exp(A)
${rt}$, intent(out) :: E(:, :)
!> [optional] Order of the Pade approximation (default `order=10`)
integer(ilp), optional, intent(in) :: order
!> [optional] Error handling.
type(linalg_state_type), optional, intent(out) :: err
end subroutine stdlib_linalg_${ri}$_expm
#:endfor
end interface matrix_exp

contains


Expand Down
159 changes: 159 additions & 0 deletions src/stdlib_linalg_matrix_functions.fypp
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
#:include "common.fypp"
#:set RC_KINDS_TYPES = REAL_KINDS_TYPES + CMPLX_KINDS_TYPES
submodule (stdlib_linalg) stdlib_linalg_matrix_functions
use stdlib_constants
use stdlib_linalg_constants
use stdlib_linalg_blas, only: gemm
use stdlib_linalg_lapack, only: gesv
use stdlib_linalg_lapack_aux, only: handle_gesv_info
use stdlib_linalg_state, only: linalg_state_type, linalg_error_handling, LINALG_ERROR, &
LINALG_INTERNAL_ERROR, LINALG_VALUE_ERROR
implicit none

character(len=*), parameter :: this = "matrix_exponential"

contains

#:for rk,rt,ri in RC_KINDS_TYPES
module function stdlib_linalg_${ri}$_expm_fun(A, order) result(E)
!> Input matrix A(n, n).
${rt}$, intent(in) :: A(:, :)
!> [optional] Order of the Pade approximation.
integer(ilp), optional, intent(in) :: order
!> Exponential of the input matrix E = exp(A).
${rt}$, allocatable :: E(:, :)

E = A ; call stdlib_linalg_${ri}$_expm_inplace(E, order)
end function

module subroutine stdlib_linalg_${ri}$_expm(A, E, order, err)
!> Input matrix A(n, n).
${rt}$, intent(in) :: A(:, :)
!> [optional] Order of the Pade approximation.
integer(ilp), optional, intent(in) :: order
!> [optional] State return flag.
type(linalg_state_type), optional, intent(out) :: err
!> Exponential of the input matrix E = exp(A).
${rt}$, intent(out) :: E(:, :)

type(linalg_state_type) :: err0
integer(ilp) :: lda, n, lde, ne

! Check E sizes
lda = size(A, 1, kind=ilp) ; n = size(A, 2, kind=ilp)
lde = size(E, 1, kind=ilp) ; ne = size(E, 2, kind=ilp)

if (lda<1 .or. n<1 .or. lda<n .or. lde<n .or. ne<n) then
err0 = linalg_state_type(this,LINALG_VALUE_ERROR, &
'invalid matrix sizes: A=',[lda,n], &
' E=',[lde,ne])
else
E(:n, :n) = A(:n, :n) ; call stdlib_linalg_${ri}$_expm_inplace(E, order, err0)
endif

! Process output and return
call linalg_error_handling(err0,err)

return
end subroutine stdlib_linalg_${ri}$_expm

module subroutine stdlib_linalg_${ri}$_expm_inplace(A, order, err)
!> Input matrix A(n, n) / Output matrix exponential.
${rt}$, intent(inout) :: A(:, :)
!> [optional] Order of the Pade approximation.
integer(ilp), optional, intent(in) :: order
!> [optional] State return flag.
type(linalg_state_type), optional, intent(out) :: err

! Internal variables.
${rt}$, allocatable :: A2(:, :), Q(:, :), X(:, :)
real(${rk}$) :: a_norm, c
integer(ilp) :: m, n, ee, k, s, order_, i, j
logical(lk) :: p
type(linalg_state_type) :: err0

! Deal with optional args.
order_ = 10 ; if (present(order)) order_ = order

! Problem's dimension.
m = size(A, dim=1, kind=ilp) ; n = size(A, dim=2, kind=ilp)

if (m /= n) then
err0 = linalg_state_type(this,LINALG_VALUE_ERROR,'Invalid matrix size A=',[m, n])
else if (order_ < 0) then
err0 = linalg_state_type(this, LINALG_VALUE_ERROR, 'Order of Pade approximation &
needs to be positive, order=', order_)
else
! Compute the L-infinity norm.
a_norm = mnorm(A, "inf")

! Determine scaling factor for the matrix.
ee = int(log(a_norm) / log2_${rk}$, kind=ilp) + 1
s = max(0, ee+1)

! Scale the input matrix & initialize polynomial.
A2 = A/2.0_${rk}$**s ; X = A2

! First step of the Pade approximation.
c = 0.5_${rk}$
allocate (Q, source=A2) ; A = A2
do concurrent(i=1:n, j=1:n)
A(i, j) = merge(1.0_${rk}$ + c*A(i, j), c*A(i, j), i == j)
Q(i, j) = merge(1.0_${rk}$ - c*Q(i, j), -c*Q(i, j), i == j)
enddo

! Iteratively compute the Pade approximation.
block
${rt}$, allocatable :: X_tmp(:, :)
p = .true.
do k = 2, order_
c = c * (order_ - k + 1) / (k * (2*order_ - k + 1))
X_tmp = X
#:if rt.startswith('complex')
call gemm("N", "N", n, n, n, one_c${rk}$, A2, n, X_tmp, n, zero_c${rk}$, X, n)
#:else
call gemm("N", "N", n, n, n, one_${rk}$, A2, n, X_tmp, n, zero_${rk}$, X, n)
#:endif
do concurrent(i=1:n, j=1:n)
A(i, j) = A(i, j) + c*X(i, j) ! E = E + c*X
enddo
if (p) then
do concurrent(i=1:n, j=1:n)
Q(i, j) = Q(i, j) + c*X(i, j) ! Q = Q + c*X
enddo
else
do concurrent(i=1:n, j=1:n)
Q(i, j) = Q(i, j) - c*X(i, j) ! Q = Q - c*X
enddo
endif
p = .not. p
enddo
end block

block
integer(ilp) :: ipiv(n), info
call gesv(n, n, Q, n, ipiv, A, n, info) ! E = inv(Q) @ E
call handle_gesv_info(this, info, n, n, n, err0)
end block

! Matrix squaring.
block
${rt}$, allocatable :: E_tmp(:, :)
do k = 1, s
E_tmp = A
#:if rt.startswith('complex')
call gemm("N", "N", n, n, n, one_c${rk}$, E_tmp, n, E_tmp, n, zero_c${rk}$, A, n)
#:else
call gemm("N", "N", n, n, n, one_${rk}$, E_tmp, n, E_tmp, n, zero_${rk}$, A, n)
#:endif
enddo
end block
endif

call linalg_error_handling(err0, err)

return
end subroutine stdlib_linalg_${ri}$_expm_inplace
#:endfor

end submodule stdlib_linalg_matrix_functions
4 changes: 3 additions & 1 deletion test/linalg/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ set(
"test_linalg_matrix_property_checks.fypp"
"test_linalg_sparse.fypp"
"test_linalg_cholesky.fypp"
"test_linalg_expm.fypp"
)

# Preprocessed files to contain preprocessor directives -> .F90
Expand All @@ -41,4 +42,5 @@ ADDTEST(linalg_qr)
ADDTEST(linalg_schur)
ADDTEST(linalg_svd)
ADDTEST(linalg_sparse)
ADDTESTPP(blas_lapack)
ADDTEST(linalg_expm)
ADDTESTPP(blas_lapack)
Loading
Loading