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 17 commits into
base: master
Choose a base branch
from
Open
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
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
48 changes: 48 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
public :: eye
public :: inv
public :: invert
Expand Down Expand Up @@ -1678,6 +1679,53 @@ 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_expm_${ri}$(A, order, err) 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
!> [optional] State return flag. On error, if not requested, the code will stop.
type(linalg_state_type), optional, intent(out) :: err
!> Exponential of the input matrix E = exp(A).
${rt}$, allocatable :: E(:, :)
end function stdlib_expm_${ri}$
#:endfor
end interface expm

contains


Expand Down
141 changes: 141 additions & 0 deletions src/stdlib_linalg_matrix_functions.fypp
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
#: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_state, only: linalg_state_type, linalg_error_handling, LINALG_ERROR, &
LINALG_INTERNAL_ERROR, LINALG_VALUE_ERROR
implicit none

contains

#:for rk,rt,ri in RC_KINDS_TYPES
module function stdlib_expm_${ri}$(A, order, err) result(E)
!> 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}$, allocatable :: E(:, :)

! 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
character(len=*), parameter :: this = "expm"
type(linalg_state_type) :: err0

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

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

if (m /= n) then
err0 = linalg_state_type(this,LINALG_VALUE_ERROR,'Invalid matrix size A=',[m, n])
call linalg_error_handling(err0, err)
return
else if (order_ < 0) then
err0 = linalg_state_type(this, LINALG_VALUE_ERROR, 'Order of Pade approximation &
needs to be positive, order=', order_)
call linalg_error_handling(err0, err)
return
endif

! Compute the L-infinity norm.
a_norm = mnorm(A, "inf")

! Determine scaling factor for the matrix.
ee = int(log(a_norm) / log(2.0_${rk}$)) + 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 (E, source=A2) ; allocate (Q, source=A2)
do concurrent(i=1:n, j=1:n)
E(i, j) = c*E(i, j) ; if (i == j) E(i, j) = 1.0_${rk}$ + E(i, j) ! E = I + c*A2
Q(i, j) = -c*Q(i, j) ; if (i == j) Q(i, j) = 1.0_${rk}$ + Q(i, j) ! Q = I - c*A2
enddo

! Iteratively compute the Pade approximation.
block
${rt}$ :: X_tmp(n, n)
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)
E(i, j) = E(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, E, n, info) ! E = inv(Q) @ E
call handle_gesv_info(info, n, n, n, err0)
call linalg_error_handling(err0, err)
end block

! Matrix squaring.
block
${rt}$ :: E_tmp(n, n)
do k = 1, s
E_tmp = E
#:if rt.startswith('complex')
call gemm("N", "N", n, n, n, one_c${rk}$, E_tmp, n, E_tmp, n, zero_c${rk}$, E, n)
#:else
call gemm("N", "N", n, n, n, one_${rk}$, E_tmp, n, E_tmp, n, zero_${rk}$, E, n)
#:endif
enddo
end block
return
contains
elemental subroutine handle_gesv_info(info,lda,n,nrhs,err)
integer(ilp), intent(in) :: info,lda,n,nrhs
type(linalg_state_type), intent(out) :: err
! Process output
select case (info)
case (0)
! Success
case (-1)
err = linalg_state_type(this,LINALG_VALUE_ERROR,'invalid problem size n=',n)
case (-2)
err = linalg_state_type(this,LINALG_VALUE_ERROR,'invalid rhs size n=',nrhs)
case (-4)
err = linalg_state_type(this,LINALG_VALUE_ERROR,'invalid matrix size a=',[lda,n])
case (-7)
err = linalg_state_type(this,LINALG_ERROR,'invalid matrix size a=',[lda,n])
case (1:)
err = linalg_state_type(this,LINALG_ERROR,'singular matrix')
case default
err = linalg_state_type(this,LINALG_INTERNAL_ERROR,'catastrophic error')
end select
end subroutine handle_gesv_info
end function stdlib_expm_${ri}$
#: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