Skip to content
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

linalg: Matrix Inverse #828

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open

Conversation

perazz
Copy link
Contributor

@perazz perazz commented May 23, 2024

Compute the multiplicative inverse of a real or complex square matrix: $A \cdot A^{-1} = A^{-1} \cdot A = I_n$ .
Based on LAPACK General factorization (*GETRF) and inversion (*GETRI).

  • base implementation
  • tests
  • exclude unsupported xdp
  • documentation
  • submodule
  • examples
  • subroutine interface

Prior art

  • Numpy: B = inv(A)
  • Scipy: inv(A, overwrite_a=False, check_finite=True)
  • IMSL: .i.A

Proposed implementation

  • B = inv(A [, err]) function interface
  • call invert(A [, pivot] [, err]) in-place (no-allocation) subroutine interface (optional pivot array)
  • .inv.A operator interface

cc: @jalvesz @jvdp1 @loiseaujc @fortran-lang/stdlib

@perazz perazz marked this pull request as ready for review May 23, 2024 15:50
@loiseaujc
Copy link

As far as this is my first review for stdlib, it looks pretty good to me. I was jut wondering about the tests. Wouldn't including a test with a known singular matrix be useful to make sure the whole error handling is working correctly?

@perazz
Copy link
Contributor Author

perazz commented May 27, 2024

Great idea @loiseaujc, thanks!

  • test for singular matrix returning LINALG_ERROR
  • noticed that complex tests were not running -> add them

e10e4c4

@perazz
Copy link
Contributor Author

perazz commented Jun 4, 2024

From Fortran Monthly call:

  • in subroutine invert, add interface to not operate in-place but return inverse to another variable (i.e., call invert(a, am1, [, pivot] [, err])
  • in .inv.A operator, with singular matrix, do not return all zeros, but stop the program

Comment on lines +12 to +15
Am1 = A

! Invert matrix
call invert(Am1)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not use this example to show how to use the subroutine interface with the optional output matrix:

Suggested change
Am1 = A
! Invert matrix
call invert(Am1)
! Invert matrix without modifying the original matrix
Am1 = A
call invert(Am1)
! Which would be equivalent to: call invert(A,Am1)

@@ -0,0 +1,22 @@
! Matrix inversion example: operator interface
program example_inverse1
Copy link
Contributor

Choose a reason for hiding this comment

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

I would propose to give explicit names to the example files such as:
example_inverse_operator, example_inverse_function, example_inverse_subroutine

Copy link
Member

@jvdp1 jvdp1 left a comment

Choose a reason for hiding this comment

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

Thank you @perazz . On overall LGTM. Here are some suggestions.

@@ -777,7 +777,8 @@ Result vector `x` returns the approximate solution that minimizes the 2-norm \(

`cond` (optional): Shall be a scalar `real` value cut-off threshold for rank evaluation: `s_i >= cond*maxval(s), i=1:rank`. Shall be a scalar, `intent(in)` argument.

`singvals` (optional): Shall be a `real` rank-1 array of the same kind `a` and size at least `minval(shape(a))`, returning the list of singular values `s(i)>=cond*maxval(s)`, in descending order of magnitude. It is an `intent(out)` argument.
`singvals` (optional): Shall be a `real` rank-1 array of the same kind `a` and size at least `m
Copy link
Member

Choose a reason for hiding this comment

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

Is this a typo?

This operator returns the inverse of a `real` or `complex` square matrix \( A \).
The inverse \( A^{-1} \) is defined such that \( A \cdot A^{-1} = A^{-1} \cdot A = I_n \).

This interface is equivalent to the function version of [[stdlib_linalg(module):inv(interface)]].
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
This interface is equivalent to the function version of [[stdlib_linalg(module):inv(interface)]].
This interface is equivalent to the function [[stdlib_linalg(module):inv(interface)]].

{!example/linalg/example_inverse1.f90!}
```

## `invert` - Inversion of a square matrix.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
## `invert` - Inversion of a square matrix.
## `invert` - Inversion of a square matrix

`inva` (optional): Shall be a rank-2, square, `real` or `complex` array with the same size, and kind as `a`.
On output, it contains the inverse of `a`.

`pivot` (optional): Shall be a rank-1 array of the same kind and matrix dimension as `a`, providing storage for the diagonal pivot indices. It is an `intent(inout)` arguments, and returns the diagonal pivot indices.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
`pivot` (optional): Shall be a rank-1 array of the same kind and matrix dimension as `a`, providing storage for the diagonal pivot indices. It is an `intent(inout)` arguments, and returns the diagonal pivot indices.
`pivot` (optional): Shall be a rank-1 array of the same kind and matrix dimension as `a`, that contains the diagonal pivot indices on return. It is an `intent(inout)` argument.


### Return value

Replaces matrix \( A \) with its inverse, \(A^{-1}\).
Copy link
Member

Choose a reason for hiding this comment

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

That is not always the case

Suggested change
Replaces matrix \( A \) with its inverse, \(A^{-1}\).
Computes the inverse of the matrix \( A \), \(A^{-1}\, and returns it either in \( A \) or in another matrix.


`a`: Shall be a rank-2, square, `real` or `complex` array containing the coefficient matrix. It is an `intent(inout)` argument.

`err` (optional): Shall be a `type(linalg_state_type)` value. This is an `intent(out)` argument.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
`err` (optional): Shall be a `type(linalg_state_type)` value. This is an `intent(out)` argument.
`err` (optional): Shall be a `type(linalg_state_type)` value. It is an `intent(out)` argument.


! Has a pre-allocated pivots storage array been provided?
if (present(pivot)) then
ipiv => pivot
Copy link
Member

Choose a reason for hiding this comment

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

Maybe to allow larger vectors, could something like this be useful:

Suggested change
ipiv => pivot
ipiv => pivot(1:n)


#:for rk,rt,ri in RC_KINDS_TYPES
#:if rk!="xdp"
tests = [tests,new_unittest("${ri}$_eye_inverse",test_${ri}$_eye_inverse), &
Copy link
Member

Choose a reason for hiding this comment

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

I propose to add a test for random SPD matrices. In my own implementation I have something like that:

 do i = 2, ndim
  orimat = 0
  call random_number(mat(1:i, 1:i))
  orimat(1:i, 1:i) = 0.5_real64 * matmul(mat(1:i, 1:i), transpose(mat(1:i, 1:)))
  mat = orimat

  call inv(mat)

  !identity matrix
  identity(1:i, 1:i) = reshape([(merge(1._real64, 0._real64, j/i.eq.mod(j,i)), j = 0, i**2 - 1)], [i, i])

  mat(1:i, 1:i) =  matmul(mat(1:i, 1:i), orimat(1:i, 1:i))

  call check(error ,  all(abs(mat(1:i, 1:i) - identity(1:i, 1:i)) < tol_real64) &
              , 'inv_pd: wrong inverse for '//value2char(i))
  if(allocated(error))return
 enddo


! Get optimal worksize (returned in work(1)) (inflate by a 5% safety margin)
nb = stdlib_ilaenv(1,'${ri}$getri',' ',n,-1,-1,-1)
lwork = ceiling(1.05*n*nb,kind=ilp)
Copy link
Member

Choose a reason for hiding this comment

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

We should be careful with integer overflow in this case (I have been bitten several times ;). It might be good to have a check for lwork

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants