Skip to content
33 changes: 33 additions & 0 deletions doc/specs/stdlib_linalg.md
Original file line number Diff line number Diff line change
Expand Up @@ -1884,3 +1884,36 @@ 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 computes 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 raises 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 @@ -57,3 +57,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
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ set(fppFiles
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
102 changes: 102 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,107 @@ 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 must be square (lda=', lda, ', n=', n, ')', &
' E must be square (lde=', lde, ', ne=', 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
2 changes: 2 additions & 0 deletions test/linalg/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ set(
"test_linalg_sparse.fypp"
"test_linalg_specialmatrices.fypp"
"test_linalg_cholesky.fypp"
"test_linalg_expm.fypp"
)

# Preprocessed files to contain preprocessor directives -> .F90
Expand All @@ -32,6 +33,7 @@ ADDTEST(linalg)
ADDTEST(linalg_cholesky)
ADDTEST(linalg_determinant)
ADDTEST(linalg_eigenvalues)
ADDTEST(linalg_expm)
ADDTEST(linalg_matrix_property_checks)
ADDTEST(linalg_inverse)
ADDTEST(linalg_pseudoinverse)
Expand Down
Loading
Loading