Skip to content

linalg: determinant #798

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 29 commits into from
Apr 25, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
4a4b899
import files
perazz Apr 13, 2024
d54cfa3
add `pure` interface
perazz Apr 13, 2024
c987fa1
add operator interface
perazz Apr 13, 2024
b9a3b0e
add tests
perazz Apr 13, 2024
4a29219
make `pure`
perazz Apr 13, 2024
3d05cc3
remove `xdp`
perazz Apr 13, 2024
4beab1f
Documentation
perazz Apr 13, 2024
15023e1
`submodule version`
perazz Apr 14, 2024
5923a54
Update src/stdlib_linalg.fypp
perazz Apr 15, 2024
31cdc84
Update src/stdlib_linalg.fypp
perazz Apr 15, 2024
45a606f
add `det` example
perazz Apr 15, 2024
cacb585
Update src/stdlib_linalg_determinant.fypp
perazz Apr 15, 2024
5c16ff8
Update src/stdlib_linalg_determinant.fypp
perazz Apr 15, 2024
ab030c5
Update src/stdlib_linalg_determinant.fypp
perazz Apr 15, 2024
13bd98a
warn about `xdp`
perazz Apr 15, 2024
7bf7141
Merge branch 'determinant' of github.com:perazz/stdlib into determinant
perazz Apr 15, 2024
504d90d
cleanup xdp notes
perazz Apr 15, 2024
e80b508
spacing
perazz Apr 15, 2024
c6076ea
relax error thresholds
perazz Apr 15, 2024
eaebe5c
restore `pure` attribute
perazz Apr 15, 2024
5d52d48
Update test/linalg/test_linalg_determinant.fypp
perazz Apr 15, 2024
cff995d
add docs
perazz Apr 21, 2024
2fe2428
link to specs
perazz Apr 21, 2024
45447a8
Update doc/specs/stdlib_linalg.md
perazz Apr 21, 2024
3ee20ad
Update doc/specs/stdlib_linalg.md
perazz Apr 21, 2024
1e50115
Update doc/specs/stdlib_linalg.md
perazz Apr 21, 2024
fe933ad
Update doc/specs/stdlib_linalg.md
perazz Apr 21, 2024
59dae89
Update doc/specs/stdlib_linalg.md
perazz Apr 21, 2024
3c72b06
Update doc/specs/stdlib_linalg.md
perazz Apr 21, 2024
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
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ set(fppFiles
stdlib_linalg_outer_product.fypp
stdlib_linalg_kronecker.fypp
stdlib_linalg_cross_product.fypp
stdlib_linalg_determinant.fypp
stdlib_linalg_state.fypp
stdlib_optval.fypp
stdlib_selection.fypp
Expand Down
6 changes: 6 additions & 0 deletions src/stdlib_linalg.fypp
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@ module stdlib_linalg
int8, int16, int32, int64
use stdlib_error, only: error_stop
use stdlib_optval, only: optval
use stdlib_linalg_state, only: linalg_state_type, linalg_error_handling
use stdlib_linalg_determinant, only: det
implicit none
private

public :: det
public :: diag
public :: eye
public :: trace
Expand All @@ -23,6 +26,9 @@ module stdlib_linalg
public :: is_hermitian
public :: is_triangular
public :: is_hessenberg

! Export linalg error handling
public :: linalg_state_type, linalg_error_handling
Comment on lines +30 to +32
Copy link
Member

Choose a reason for hiding this comment

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

This let me think that we should review the structure of the specs. Currently we have 1 page per module. However, if these become available through stdlib_linalg, the specs should be modified accordingly (probably in another PR; I can start to work on a proposition when this is ready).


interface diag
!! version: experimental
Expand Down
302 changes: 302 additions & 0 deletions src/stdlib_linalg_determinant.fypp
Original file line number Diff line number Diff line change
@@ -0,0 +1,302 @@
#:include "common.fypp"
#:set RC_KINDS_TYPES = REAL_KINDS_TYPES + CMPLX_KINDS_TYPES
module stdlib_linalg_determinant
Copy link
Member

Choose a reason for hiding this comment

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

Could it be a submdodule of stdblib_linalg, such that users can use it as:

use stdlib_linalg, only: det

instead of:

use stdlib_linalg_determinant, only: det

This will avoid an enormous amount of stdlib_linalg modules.
Inconvenient: this approach will require the compilation of the a large module.

What do you think? What would be the best strategy for the users?

Copy link
Contributor

Choose a reason for hiding this comment

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

This seems like a good approach, everything would fit together smoothly. This might require indeed a large header module but for the end user and even a developer who would like to pick just one method and work on it, it would be easier... I think.

!! Determinant of a rectangular matrix
Copy link
Member

Choose a reason for hiding this comment

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

Is it for rectangular, or only square matrices (as mentioned earlier)?

Copy link
Member Author

Choose a reason for hiding this comment

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

Square matrix only. do any of the major packages (numpy, scipy) provide a result for rectangular matrix?

Copy link
Contributor

Choose a reason for hiding this comment

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

The determinant is defined for squared matrices only. Both numpy https://numpy.org/doc/stable/reference/generated/numpy.linalg.det.html and scipy https://docs.scipy.org/doc/scipy/reference/generated/scipy.linalg.det.html#scipy.linalg.det propose it for squared matrices. A "determinant" of a rectangular matrix could be computed with something like sqrt( det( A^T A ) ) but AFAIK this doesn't have a formal definition.

Copy link
Member Author

Choose a reason for hiding this comment

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

Right now we throw a LINALG_VALUE_ERROR if the matrix is singular (rank-deficient). Should we return 0.0 instead? I believe the former is more formally correct, but dunno.

Copy link
Contributor

Choose a reason for hiding this comment

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

Right now we throw a LINALG_VALUE_ERROR if the matrix is singular (rank-deficient)

Seems like the best solution. returning 0 might be misleading IMO.

use stdlib_linalg_constants
use stdlib_linalg_lapack, only: getrf
use stdlib_linalg_state, only: linalg_state_type, linalg_error_handling, LINALG_ERROR, &
LINALG_INTERNAL_ERROR, LINALG_VALUE_ERROR
implicit none(type,external)
private

! Function interface
public :: det
public :: operator(.det.)

character(*), parameter :: this = 'determinant'

interface det
!!### Summary
!! Interface for computing matrix determinant.
!!
!!### Description
!!
!! This interface provides methods for computing the determinant of a matrix.
!! Supported data types include real and complex.
!!
!!@note The provided functions are intended for square matrices.
!!
!!### Example
!!
!!```fortran
!!
!! real(sp) :: a(3,3), d
!! type(linalg_state_type) :: state
!! a = reshape([1, 2, 3, 4, 5, 6, 7, 8, 9], [3, 3])
!!
!! d = det(a,err=state)
!! if (state%ok()) then
!! print *, 'Success! det=',d
!! else
!! print *, state%print()
!! endif
!!
!!```
!!
#:for rk,rt in RC_KINDS_TYPES
#:if rk!="xdp"
module procedure stdlib_linalg_${rt[0]}$${rk}$determinant
module procedure stdlib_linalg_pure_${rt[0]}$${rk}$determinant
#:endif
#:endfor
end interface det

interface operator(.det.)
!!### Summary
!! Pure operator interface for computing matrix determinant.
!!
!!### Description
!!
!! This pure operator interface provides a convenient way to compute the determinant of a matrix.
!! Supported data types include real and complex.
!!
!!@note The provided functions are intended for square matrices.
!!
!!### Example
!!
!!```fortran
!!
!! real(sp) :: matrix(3,3), d
!! matrix = reshape([1, 2, 3, 4, 5, 6, 7, 8, 9], [3, 3])
!! d = .det.matrix
!!
!!```
!
#:for rk,rt in RC_KINDS_TYPES
#:if rk!="xdp"
module procedure stdlib_linalg_pure_${rt[0]}$${rk}$determinant
#:endif
#:endfor
end interface operator(.det.)

contains

#:for rk,rt in RC_KINDS_TYPES
#:if rk!="xdp"
pure function stdlib_linalg_pure_${rt[0]}$${rk}$determinant(a) result(det)
!!### Summary
!! Compute determinant of a real square matrix (pure interface).
!!
!!### Description
!!
!! This function computes the determinant of a real square matrix.
!!
!! param: a Input matrix of size [m,n].
!! return: det Matrix determinant.
!!
!!### Example
!!
!!```fortran
!!
!! ${rt}$ :: matrix(3,3)
!! ${rt}$ :: determinant
!! matrix = reshape([1, 2, 3, 4, 5, 6, 7, 8, 9], [3, 3])
!! determinant = det(matrix)
!!
!!```
!> Input matrix a[m,n]
${rt}$, intent(in) :: a(:,:)
!> Matrix determinant
${rt}$ :: det

!! Local variables
type(linalg_state_type) :: err0
integer(ilp) :: m,n,info,perm,k
integer(ilp), allocatable :: ipiv(:)
${rt}$, allocatable :: amat(:,:)

! Matrix determinant size
m = size(a,1,kind=ilp)
n = size(a,2,kind=ilp)

if (m/=n .or. .not.min(m,n)>=0) then
err0 = linalg_state_type(this,LINALG_VALUE_ERROR,'invalid or non-square matrix: a=[',m,',',n,']')
det = 0.0_${rk}$
goto 1
end if

select case (m)
case (0)

! Empty array has determinant 1 because math
det = 1.0_${rk}$

case (1)

! Scalar input
det = a(1,1)

case default

! Find determinant from LU decomposition

! Initialize a matrix temporary
allocate(amat(m,n),source=a)

! Pivot indices
allocate(ipiv(n))

! Compute determinant from LU factorization, then calculate the
! product of all diagonal entries of the U factor.
call getrf(m,n,amat,m,ipiv,info)

select case (info)
case (0)
! Success: compute determinant

! Start with real 1.0
det = 1.0_${rk}$
perm = 0
do k=1,n
if (ipiv(k)/=k) perm = perm+1
det = det*amat(k,k)
end do
if (mod(perm,2)/=0) det = -det

case (:-1)
err0 = linalg_state_type(this,LINALG_ERROR,'invalid matrix size a=[',m,',',n,']')
case (1:)
err0 = linalg_state_type(this,LINALG_ERROR,'singular matrix')
case default
err0 = linalg_state_type(this,LINALG_INTERNAL_ERROR,'catastrophic error')
end select

deallocate(amat)

end select

! Process output and return
1 call linalg_error_handling(err0)

end function stdlib_linalg_pure_${rt[0]}$${rk}$determinant

function stdlib_linalg_${rt[0]}$${rk}$determinant(a,overwrite_a,err) result(det)
!!### Summary
!! Compute determinant of a square matrix (with error control).
!!
!!### Description
!!
!! This function computes the determinant of a square matrix with error control.
!!
!! param: a Input matrix of size [m,n].
!! param: overwrite_a [optional] Flag indicating if the input matrix can be overwritten.
!! param: err State return flag.
!! return: det Matrix determinant.
!!
!!### Example
!!
!!```fortran
!!
!! ${rt}$ :: matrix(3,3)
!! ${rt}$ :: determinant
!! matrix = reshape([1, 2, 3, 4, 5, 6, 7, 8, 9], [3, 3])
!! determinant = det(matrix, err=err)
!!
!!```
!
!> Input matrix a[m,n]
${rt}$, intent(inout), target :: a(:,:)
!> [optional] Can A data be overwritten and destroyed?
logical(lk), optional, intent(in) :: overwrite_a
!> State return flag.
type(linalg_state_type), intent(out) :: err
!> Matrix determinant
${rt}$ :: det

!! Local variables
type(linalg_state_type) :: err0
integer(ilp) :: m,n,info,perm,k
integer(ilp), allocatable :: ipiv(:)
logical(lk) :: copy_a
${rt}$, pointer :: amat(:,:)

! Matrix determinant size
m = size(a,1,kind=ilp)
n = size(a,2,kind=ilp)

if (m/=n .or. .not.min(m,n)>=0) then
err0 = linalg_state_type(this,LINALG_VALUE_ERROR,'invalid or non-square matrix: a=[',m,',',n,']')
det = 0.0_${rk}$
goto 1
end if

! Can A be overwritten? By default, do not overwrite
if (present(overwrite_a)) then
copy_a = .not.overwrite_a
else
copy_a = .true._lk
endif

select case (m)
case (0)

! Empty array has determinant 1 because math
det = 1.0_${rk}$

case (1)

! Scalar input
det = a(1,1)

case default

! Find determinant from LU decomposition

! Initialize a matrix temporary
if (copy_a) then
allocate(amat(m,n),source=a)
else
amat => a
endif

! Pivot indices
allocate(ipiv(n))

! Compute determinant from LU factorization, then calculate the
! product of all diagonal entries of the U factor.
call getrf(m,n,amat,m,ipiv,info)

select case (info)
case (0)
! Success: compute determinant

! Start with real 1.0
det = 1.0_${rk}$
perm = 0
do k=1,n
if (ipiv(k)/=k) perm = perm+1
det = det*amat(k,k)
end do
if (mod(perm,2)/=0) det = -det

case (:-1)
err0 = linalg_state_type(this,LINALG_ERROR,'invalid matrix size a=[',m,',',n,']')
case (1:)
err0 = linalg_state_type(this,LINALG_ERROR,'singular matrix')
case default
err0 = linalg_state_type(this,LINALG_INTERNAL_ERROR,'catastrophic error')
end select

if (.not.copy_a) deallocate(amat)

end select

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

end function stdlib_linalg_${rt[0]}$${rk}$determinant

#:endif
#:endfor

end module stdlib_linalg_determinant
2 changes: 2 additions & 0 deletions test/linalg/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ set(
fppFiles
"test_linalg.fypp"
"test_blas_lapack.fypp"
"test_linalg_determinant.fypp"
"test_linalg_matrix_property_checks.fypp"
)
fypp_f90("${fyppFlags}" "${fppFiles}" outFiles)

ADDTEST(linalg)
ADDTEST(linalg_determinant)
ADDTEST(linalg_matrix_property_checks)
ADDTEST(blas_lapack)
Loading