Skip to content

Commit b0a74b1

Browse files
committed
Working implementation of expm.
1 parent 4ebe266 commit b0a74b1

File tree

5 files changed

+177
-2
lines changed

5 files changed

+177
-2
lines changed

src/CMakeLists.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,17 @@ set(fppFiles
3232
stdlib_linalg_kronecker.fypp
3333
stdlib_linalg_cross_product.fypp
3434
stdlib_linalg_eigenvalues.fypp
35-
stdlib_linalg_solve.fypp
35+
stdlib_linalg_solve.fypp
3636
stdlib_linalg_determinant.fypp
3737
stdlib_linalg_qr.fypp
3838
stdlib_linalg_inverse.fypp
3939
stdlib_linalg_pinv.fypp
4040
stdlib_linalg_norms.fypp
4141
stdlib_linalg_state.fypp
42-
stdlib_linalg_svd.fypp
42+
stdlib_linalg_svd.fypp
4343
stdlib_linalg_cholesky.fypp
4444
stdlib_linalg_schur.fypp
45+
stdlib_linalg_matrix_functions.fypp
4546
stdlib_optval.fypp
4647
stdlib_selection.fypp
4748
stdlib_sorting.fypp

src/stdlib_linalg.fypp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ module stdlib_linalg
2828
public :: eigh
2929
public :: eigvals
3030
public :: eigvalsh
31+
public :: expm
3132
public :: eye
3233
public :: inv
3334
public :: invert
@@ -1678,6 +1679,19 @@ module stdlib_linalg
16781679
#:endfor
16791680
end interface mnorm
16801681

1682+
!> Matrix exponential: function interface
1683+
interface expm
1684+
#:for rk,rt,ri in RC_KINDS_TYPES
1685+
module function expm_${ri}$(A, order) result(E)
1686+
${rt}$, intent(in) :: A(:, :)
1687+
!! On entry, the original matrix. On exit, its exponential.
1688+
integer(ilp), optional, intent(in) :: order
1689+
!! Order of the rational approximation.
1690+
${rt}$, allocatable :: E(:, :)
1691+
end function expm_${ri}$
1692+
#:endfor
1693+
end interface expm
1694+
16811695
contains
16821696

16831697

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#:include "common.fypp"
2+
#:set RC_KINDS_TYPES = REAL_KINDS_TYPES + CMPLX_KINDS_TYPES
3+
submodule (stdlib_linalg) stdlib_linalg_matrix_functions
4+
use stdlib_linalg_constants
5+
use stdlib_linalg_state, only: linalg_state_type, linalg_error_handling, LINALG_ERROR, &
6+
LINALG_INTERNAL_ERROR, LINALG_VALUE_ERROR
7+
implicit none
8+
9+
contains
10+
11+
#:for rk,rt,ri in RC_KINDS_TYPES
12+
module function expm_${ri}$(A, order) result(E)
13+
${rt}$, intent(in) :: A(:, :)
14+
integer(ilp), optional, intent(in) :: order
15+
${rt}$, allocatable :: E(:, :)
16+
17+
${rt}$, allocatable :: A2(:, :), Q(:, :), X(:, :)
18+
real(${rk}$) :: a_norm, c
19+
integer(ilp) :: n, ee, k, s
20+
logical(lk) :: p
21+
integer(ilp) :: p_order
22+
23+
! Deal with optional args.
24+
p_order = 10 ; if (present(order)) p_order = order
25+
26+
n = size(A, 1)
27+
28+
! Compute the L-infinity norm.
29+
a_norm = mnorm(A, "inf")
30+
31+
! Determine scaling factor for the matrix.
32+
ee = int(log(a_norm) / log(2.0_${rk}$)) + 1
33+
s = max(0, ee+1)
34+
35+
! Scale the input matrix & initialize polynomial.
36+
A2 = A / 2.0_${rk}$**s
37+
X = A2
38+
39+
! Initialize P & Q and add first step.
40+
c = 0.5_${rk}$
41+
E = eye(n, mold=1.0_${rk}$) ; E = E + c*A2
42+
43+
Q = eye(n, mold=1.0_${rk}$) ; Q = Q - c*A2
44+
45+
! Iteratively compute the Pade approximation.
46+
p = .true.
47+
do k = 2, p_order
48+
c = c*(p_order - k + 1) / (k * (2*p_order - k + 1))
49+
X = matmul(A2, X)
50+
E = E + c*X
51+
if (p) then
52+
Q = Q + c*X
53+
else
54+
Q = Q - c*X
55+
endif
56+
p = .not. p
57+
enddo
58+
59+
E = matmul(inv(Q), E)
60+
do k = 1, s
61+
E = matmul(E, E)
62+
enddo
63+
64+
return
65+
end function
66+
#:endfor
67+
68+
end submodule stdlib_linalg_matrix_functions

test/linalg/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ set(
1616
"test_linalg_svd.fypp"
1717
"test_linalg_matrix_property_checks.fypp"
1818
"test_linalg_sparse.fypp"
19+
"test_linalg_expm.fypp"
1920
)
2021
fypp_f90("${fyppFlags}" "${fppFiles}" outFiles)
2122

@@ -35,3 +36,4 @@ ADDTEST(linalg_schur)
3536
ADDTEST(linalg_svd)
3637
ADDTEST(blas_lapack)
3738
ADDTEST(linalg_sparse)
39+
ADDTEST(linalg_expm)

test/linalg/test_linalg_expm.fypp

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
#:include "common.fypp"
2+
#:set RC_KINDS_TYPES = REAL_KINDS_TYPES + CMPLX_KINDS_TYPES
3+
! Test Schur decomposition
4+
module test_linalg_expm
5+
use testdrive, only: error_type, check, new_unittest, unittest_type
6+
use stdlib_linalg_constants
7+
use stdlib_linalg, only: expm, eye, mnorm
8+
9+
implicit none (type,external)
10+
11+
public :: test_expm_computation
12+
13+
contains
14+
15+
!> schur decomposition tests
16+
subroutine test_expm_computation(tests)
17+
!> Collection of tests
18+
type(unittest_type), allocatable, intent(out) :: tests(:)
19+
20+
allocate(tests(0))
21+
22+
#:for rk,rt,ri in RC_KINDS_TYPES
23+
tests = [tests, new_unittest("expm_${ri}$",test_expm_${ri}$)]
24+
#:endfor
25+
26+
end subroutine test_expm_computation
27+
28+
!> Matrix exponential with analytic expression.
29+
#:for rk,rt,ri in RC_KINDS_TYPES
30+
subroutine test_expm_${ri}$(error)
31+
type(error_type), allocatable, intent(out) :: error
32+
! Problem dimension.
33+
integer(ilp), parameter :: n = 5, m = 6
34+
! Test matrix.
35+
${rt}$ :: A(n, n), E(n, n), Eref(n, n)
36+
real(${rk}$) :: err
37+
integer(ilp) :: i, j
38+
39+
! Initialize matrix.
40+
A = 0.0_${rk}$
41+
do i = 1, n-1
42+
A(i, i+1) = m*1.0_${rk}$
43+
enddo
44+
45+
! Reference with analytical exponential.
46+
Eref = eye(n, mold=1.0_${rk}$)
47+
do i = 1, n-1
48+
do j = 1, n-i
49+
Eref(i, i+j) = Eref(i, i+j-1)*m/j
50+
enddo
51+
enddo
52+
53+
! Compute matrix exponential.
54+
E = expm(A)
55+
56+
! Check result.
57+
err = mnorm(Eref - E, "inf")
58+
call check(error, err < (n**2)*epsilon(1.0_${rk}$), "Analytical matrix exponential.")
59+
if (allocated(error)) return
60+
return
61+
end subroutine test_expm_${ri}$
62+
#:endfor
63+
64+
end module test_linalg_expm
65+
66+
program test_expm
67+
use, intrinsic :: iso_fortran_env, only : error_unit
68+
use testdrive, only : run_testsuite, new_testsuite, testsuite_type
69+
use test_linalg_expm, only : test_expm_computation
70+
implicit none
71+
integer :: stat, is
72+
type(testsuite_type), allocatable :: testsuites(:)
73+
character(len=*), parameter :: fmt = '("#", *(1x, a))'
74+
75+
stat = 0
76+
77+
testsuites = [ &
78+
new_testsuite("linalg_expm", test_expm_computation) &
79+
]
80+
81+
do is = 1, size(testsuites)
82+
write(error_unit, fmt) "Testing:", testsuites(is)%name
83+
call run_testsuite(testsuites(is)%collect, error_unit, stat)
84+
end do
85+
86+
if (stat > 0) then
87+
write(error_unit, '(i0, 1x, a)') stat, "test(s) failed!"
88+
error stop
89+
end if
90+
end program test_expm

0 commit comments

Comments
 (0)