Skip to content

[stdlib_linalg] matrix property checks #499

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 37 commits into from
Dec 31, 2021
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
3d86036
Add all single input chekcs
ghbrown Aug 21, 2021
6d9848f
Fix is_diagonal and add some tests
ghbrown Aug 24, 2021
4c5fdf1
Add is_symmetric and is_skew_symmetric tests
ghbrown Aug 25, 2021
f28bb47
Add tests for is_skew_symmetric and start is_triangular tests
ghbrown Aug 26, 2021
857a9bb
Start complex is_triangular tests
ghbrown Aug 27, 2021
a14af3b
Add final tests
ghbrown Aug 28, 2021
bdae9ae
Style changes
ghbrown Aug 28, 2021
e1f07e6
Separate calls to check in tests
ghbrown Aug 30, 2021
55e0dd0
Extend is_hamiltonian to real types and add is_hamiltonian tests
ghbrown Aug 31, 2021
fd8fcf1
Replace A_shape with size() calls
ghbrown Sep 2, 2021
3196fea
Add docs and examples
ghbrown Sep 6, 2021
c31200f
Add stdlib_error dependency to stdlib_linalg for GNU make
ghbrown Sep 6, 2021
ce2722d
Add missing slash to broken GNU makefile
ghbrown Sep 6, 2021
0da0d7d
Change (.not * .eq *) to (* .ne. *) for brevity
ghbrown Sep 27, 2021
1a9ddb3
Switch to modern relational operators
ghbrown Sep 28, 2021
ed42211
Change style of output comments in docs
ghbrown Sep 29, 2021
58346ff
Remove doubled check for squareness
ghbrown Oct 4, 2021
a759929
Make zero variables into parameters
ghbrown Oct 4, 2021
1915bbb
Clarify return value documentation
ghbrown Oct 4, 2021
677c577
Change to more specific documentation URLs
ghbrown Oct 4, 2021
080b552
update links for FORD
jvdp1 Oct 4, 2021
9412890
Merge pull request #1 from jvdp1/linalg_link
ghbrown Oct 4, 2021
6f6f5ac
Separate out matrix property checks tests
ghbrown Oct 11, 2021
c3d07a1
Merge branch 'matrix_property_checks' of github.com:ghbrown/stdlib in…
ghbrown Oct 11, 2021
8324fa7
Merge branch 'master' into matrix_property_checks
ghbrown Oct 11, 2021
12be97b
Add back optval dependencies accidentally removed in merge conflict r…
ghbrown Oct 11, 2021
d0a4a76
Remove redundant tests after separation
ghbrown Oct 16, 2021
0a0137b
After catch up merge
ghbrown Dec 27, 2021
74abe0f
Add fypp version of is_square
ghbrown Dec 27, 2021
ecc38d1
Settle on global style for fypp templating and add is_diagonal and is…
ghbrown Dec 27, 2021
009e22c
Implement all tests with testdrive and fypp
ghbrown Dec 27, 2021
03306f1
Add missing source file to manual makefile
ghbrown Dec 27, 2021
58fc9a2
Add missing separator for line break
ghbrown Dec 27, 2021
09e333f
Correct error in fypp templating
ghbrown Dec 28, 2021
c7c8bcd
Fix GNU makefiles and cleanup cmake and fypp fixes
ghbrown Dec 28, 2021
8d36da6
Blank line insertion and deletion
ghbrown Dec 28, 2021
60f0fa6
Remove hash files generated during testing
ghbrown Dec 31, 2021
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
286 changes: 286 additions & 0 deletions src/stdlib_linalg.fypp
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,21 @@ module stdlib_linalg
!! ([Specification](../page/specs/stdlib_linalg.html))
use stdlib_kinds, only: sp, dp, qp, &
int8, int16, int32, int64
use stdlib_error, only: error_stop
implicit none
private

public :: diag
public :: eye
public :: trace
public :: outer_product
public :: is_square
public :: is_diagonal
public :: is_symmetric
public :: is_skew_symmetric
public :: is_hermitian
public :: is_triangular
public :: is_hessenberg
Comment on lines +17 to +23
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be wise to move the implementations of all these functions fo submodules?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had considered that, but it seemed like it would further pollute the already cluttered src directory.
As long as this is fine with people, I can move them into their own submodules.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have time to find the thread, but if I recall correctly our plan was that once a module (say linalg) reaches a certain size, we would introduce a new folder like linalg/ in the src/ directory. We haven't done it yet for the stats routines which would be another candidate. @jvdp1, what do you think?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A separate folder is actually what I wanted to ask for, but didn't know the overall plan or want to complicate things.
I am very much on board with having separate folders for modules.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have time to find the thread, but if I recall correctly our plan was that once a module (say linalg) reaches a certain size, we would introduce a new folder like linalg/ in the src/ directory. We haven't done it yet for the stats routines which would be another candidate. @jvdp1, what do you think?

I don't recall this discussion/thread (but it doesn't mean I didn't read it or didn't participate to it ;) ). However, I think it would be a good idea of having separate folders for modules/a set of modules related to the same topic.

For now, I would suggest to keep it as it is, or to only create submodules. We can discuss in an issue how to create folders, and if it is appropriate (also by keeping in mind that it must be compatible with fpm).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is one issue to keep in mind with FORD when using subdirectories for the Fortran files, because only the basename will be visible in the documentation, like here: https://toml-f.github.io/toml-f/lists/files.html

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking back, it was a single comment from Zaak Beekman in #7 (comment):

As stuff gets added if src gets too cluttered you can break
out more nesting and structure. E.g. src/strings/ascii.f90 etc. where
other string stuff gets gets grouped there.

I agree we postpone the folder changes for a new PR, which will be linked with a newly created issue.


interface diag
!! version: experimental
Expand Down Expand Up @@ -80,8 +88,93 @@ module stdlib_linalg
#:endfor
end interface outer_product


! Check for squareness
interface is_square
!! version: experimental
!!
!! Checks if a matrix (rank-2 array) is square
!! ([Specification](../page/specs/stdlib_linalg.html#description_4))
#:for k1, t1 in RCI_KINDS_TYPES
module procedure is_square_${t1[0]}$${k1}$
#:endfor
end interface is_square


! Check for diagonality
interface is_diagonal
!! version: experimental
!!
!! Checks if a matrix (rank-2 array) is diagonal
!! ([Specification](../page/specs/stdlib_linalg.html#description_5))
#:for k1, t1 in RCI_KINDS_TYPES
module procedure is_diagonal_${t1[0]}$${k1}$
#:endfor
end interface is_diagonal


! Check for symmetry
interface is_symmetric
!! version: experimental
!!
!! Checks if a matrix (rank-2 array) is symmetric
!! ([Specification](../page/specs/stdlib_linalg.html#description_6))
#:for k1, t1 in RCI_KINDS_TYPES
module procedure is_symmetric_${t1[0]}$${k1}$
#:endfor
end interface is_symmetric


! Check for skew-symmetry
interface is_skew_symmetric
!! version: experimental
!!
!! Checks if a matrix (rank-2 array) is skew-symmetric
!! ([Specification](../page/specs/stdlib_linalg.html#description_7))
#:for k1, t1 in RCI_KINDS_TYPES
module procedure is_skew_symmetric_${t1[0]}$${k1}$
#:endfor
end interface is_skew_symmetric


! Check for Hermiticity
interface is_hermitian
!! version: experimental
!!
!! Checks if a matrix (rank-2 array) is Hermitian
!! ([Specification](../page/specs/stdlib_linalg.html#description_8))
#:for k1, t1 in CMPLX_KINDS_TYPES
module procedure is_hermitian_${t1[0]}$${k1}$
#:endfor
end interface is_hermitian


! Check for triangularity
interface is_triangular
!! version: experimental
!!
!! Checks if a matrix (rank-2 array) is triangular
!! ([Specification](../page/specs/stdlib_linalg.html#description_9))
#:for k1, t1 in RCI_KINDS_TYPES
module procedure is_triangular_${t1[0]}$${k1}$
#:endfor
end interface is_triangular


! Check for matrix being Hessenberg
interface is_hessenberg
!! version: experimental
!!
!! Checks if a matrix (rank-2 array) is Hessenberg
!! ([Specification](../page/specs/stdlib_linalg.html#description_10))
#:for k1, t1 in RCI_KINDS_TYPES
module procedure is_Hessenberg_${t1[0]}$${k1}$
#:endfor
end interface is_hessenberg

contains


function eye(n) result(res)
!! version: experimental
!!
Expand All @@ -108,4 +201,197 @@ contains
end do
end function trace_${t1[0]}$${k1}$
#:endfor


#:for k1, t1 in RCI_KINDS_TYPES
pure function is_square_${t1[0]}$${k1}$(A) result(res)
${t1}$, intent(in) :: A(:,:)
logical :: res
integer :: A_shape(2)
A_shape = shape(A)
res = (A_shape(1) .eq. A_shape(2))
end function is_square_${t1[0]}$${k1}$
#:endfor


#:for k1, t1 in RCI_KINDS_TYPES
pure function is_diagonal_${t1[0]}$${k1}$(A) result(res)
${t1}$, intent(in) :: A(:,:)
logical :: res
${t1}$ :: zero
integer :: A_shape(2), m, n, o, i, j
zero = 0 !zero of relevant type
A_shape = shape(A)
m = A_shape(1)
n = A_shape(2)
do j = 1, n !loop over all columns
o = min(j-1,m) !index of row above diagonal (or last row)
do i = 1, o !loop over rows above diagonal
if (.not. (A(i,j) .eq. zero)) then
res = .false.
return
end if
end do
do i = o+2, m !loop over rows below diagonal
if (.not. (A(i,j) .eq. zero)) then
res = .false.
return
end if
end do
end do
res = .true. !otherwise A is diagonal
end function is_diagonal_${t1[0]}$${k1}$
#:endfor


#:for k1, t1 in RCI_KINDS_TYPES
pure function is_symmetric_${t1[0]}$${k1}$(A) result(res)
${t1}$, intent(in) :: A(:,:)
logical :: res
integer :: A_shape(2), n, i, j
if (.not. is_square(A)) then
res = .false.
return !nonsquare matrices cannot be symmetric
end if
A_shape = shape(A)
n = A_shape(1) !symmetric dimension of A
do j = 1, n !loop over all columns
do i = 1, j-1 !loop over all rows above diagonal
if (.not. (A(i,j) .eq. A(j,i))) then
res = .false.
return
end if
end do
end do
res = .true. !otherwise A is symmetric
end function is_symmetric_${t1[0]}$${k1}$
#:endfor


#:for k1, t1 in RCI_KINDS_TYPES
pure function is_skew_symmetric_${t1[0]}$${k1}$(A) result(res)
${t1}$, intent(in) :: A(:,:)
logical :: res
integer :: A_shape(2), n, i, j
if (.not. is_square(A)) then
res = .false.
return !nonsquare matrices cannot be skew-symmetric
end if
A_shape = shape(A)
n = A_shape(1) !symmetric dimension of A
do j = 1, n !loop over all columns
do i = 1, j !loop over all rows above diagonal (and diagonal)
if (.not. (A(i,j) .eq. -A(j,i))) then
res = .false.
return
end if
end do
end do
res = .true. !otherwise A is skew-symmetric
end function is_skew_symmetric_${t1[0]}$${k1}$
#:endfor


#:for k1, t1 in CMPLX_KINDS_TYPES
pure function is_hermitian_${t1[0]}$${k1}$(A) result(res)
${t1}$, intent(in) :: A(:,:)
logical :: res
integer :: A_shape(2), n, i, j
if (.not. is_square(A)) then
res = .false.
return !nonsquare matrices cannot be Hermitian
end if
A_shape = shape(A)
n = A_shape(1) !symmetric dimension of A
do j = 1, n !loop over all columns
do i = 1, j !loop over all rows above diagonal (and diagonal)
if (.not. (A(i,j) .eq. conjg(A(j,i)))) then
res = .false.
return
end if
end do
end do
res = .true. !otherwise A is Hermitian
end function is_hermitian_${t1[0]}$${k1}$
#:endfor


#:for k1, t1 in RCI_KINDS_TYPES
function is_triangular_${t1[0]}$${k1}$(A,uplo) result(res)
${t1}$, intent(in) :: A(:,:)
character, intent(in) :: uplo
logical :: res
${t1}$ :: zero
integer :: A_shape(2), m, n, o, i, j
zero = 0 !zero of relevant type
A_shape = shape(A)
m = A_shape(1)
n = A_shape(2)
if ((uplo .eq. 'u') .or. (uplo .eq. 'U')) then !check for upper triangularity
do j = 1, n !loop over all columns
o = min(j-1,m) !index of row above diagonal (or last row)
do i = o+2, m !loop over rows below diagonal
if (.not. (A(i,j) .eq. zero)) then
res = .false.
return
end if
end do
end do
else if ((uplo .eq. 'l') .or. (uplo .eq. 'L')) then !check for lower triangularity
do j=1,n !loop over all columns
o = min(j-1,m) !index of row above diagonal (or last row)
do i=1,o !loop over rows above diagonal
if (.not. (A(i,j) .eq. zero)) then
res = .false.
return
end if
end do
end do
else
call error_stop("ERROR (is_triangular): second argument must be one of {'u','U','l','L'}")
end if

res = .true. !otherwise A is triangular of the requested type
end function is_triangular_${t1[0]}$${k1}$
#:endfor


#:for k1, t1 in RCI_KINDS_TYPES
function is_hessenberg_${t1[0]}$${k1}$(A,uplo) result(res)
${t1}$, intent(in) :: A(:,:)
character, intent(in) :: uplo
logical :: res
${t1}$ :: zero
integer :: A_shape(2), m, n, o, i, j
zero = 0 !zero of relevant type
A_shape = shape(A)
m = A_shape(1)
n = A_shape(2)
if ((uplo .eq. 'u') .or. (uplo .eq. 'U')) then !check for upper Hessenberg
do j = 1, n !loop over all columns
o = min(j-2,m) !index of row two above diagonal (or last row)
do i = o+4, m !loop over rows two or more below main diagonal
if (.not. (A(i,j) .eq. zero)) then
res = .false.
return
end if
end do
end do
else if ((uplo .eq. 'l') .or. (uplo .eq. 'L')) then !check for lower Hessenberg
do j = 1, n !loop over all columns
o = min(j-2,m) !index of row two above diagonal (or last row)
do i = 1, o !loop over rows one or more above main diagonal
if (.not. (A(i,j) .eq. zero)) then
res = .false.
return
end if
end do
end do
else
call error_stop("ERROR (is_hessenberg): second argument must be one of {'u','U','l','L'}")
end if
res = .true. !otherwise A is Hessenberg of the requested type
end function is_hessenberg_${t1[0]}$${k1}$
#:endfor

end module
Loading