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

Provide state/error handling for linear algebra #774

Merged
merged 28 commits into from
Apr 11, 2024

Conversation

perazz
Copy link
Contributor

@perazz perazz commented Mar 16, 2024

With this PR I aim to introduce a state/error handler for expert flow control of linear algebra procedures:

  • linalg_state derived type with all pure interfaces
  • fast, fixed-size memory storage
  • expert control: on error, stop the program. Otherwise, do not stop and return state handler if user requested it
  • developer friendly: easy to set error messages thanks to generic interface
  • add tests for all stdlib kinds

cc: @fortran-lang/stdlib @jvdp1 @everythingfunctional @henilp105 @jalvesz @gnikit

This and #772 are prelminary to begin adding lapack-backed high-level algebra APIs, more context here.

@perazz perazz marked this pull request as draft March 16, 2024 10:29
@perazz perazz marked this pull request as ready for review March 16, 2024 10:53
Copy link
Member

@certik certik left a comment

Choose a reason for hiding this comment

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

I think we need a mechanism like this, thanks for creating it.

We just need to discuss the details of the implementation. I left some comments.

Another question to decide is whether this should be specialized to linear algebra, or made more general.


!> Error creation message, from N input variables (numeric or strings)
pure type(linalg_state) function new_state_nowhere(flag,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10, &
v1,v2,v3,v4,v5) result(new_state)
Copy link
Member

Choose a reason for hiding this comment

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

I am not sure about this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here's an example of how these functions allow easy coding of the error/state variable, for example for a complex number:

state = linalg_state(LINALG_SUCCESS,' 32-bit array: ',v1=[(1.0_sp,0.0_sp),(0.0_sp,1.0_sp)])

And the expected user error message:

' 32-bit array: [(1.00000000E+00,0.00000000E+00) (0.00000000E+00,1.00000000E+00)]', &

The real formats for 32/64/128 bit are taken from here:

character(*), parameter :: &
!> Format string for integers
FMT_INT = '(i0)', &
!> Format string for single precision real numbers
FMT_REAL_SP = '(es15.8e2)', &
!> Format string for souble precision real numbers
FMT_REAL_DP = '(es24.16e3)', &
!> Format string for extended double precision real numbers
FMT_REAL_XDP = '(es26.18e3)', &
!> Format string for quadruple precision real numbers
FMT_REAL_QP = '(es44.35e4)', &
!> Format string for single precision complex numbers
FMT_COMPLEX_SP = '(es15.8e2,1x,es15.8e2)', &
!> Format string for double precision complex numbers
FMT_COMPLEX_DP = '(es24.16e3,1x,es24.16e3)', &
!> Format string for extended double precision complex numbers
FMT_COMPLEX_XDP = '(es26.18e3,1x,es26.18e3)', &
!> Format string for quadruple precision complex numbers
FMT_COMPLEX_QP = '(es44.35e4,1x,es44.35e4)'

call appendv(new_state%message,v2)
call appendv(new_state%message,v3)
call appendv(new_state%message,v4)
call appendv(new_state%message,v5)
Copy link
Member

Choose a reason for hiding this comment

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

And this mechanism.

end function new_state_nowhere

! Append a generic value to the error flag
pure subroutine append(msg,a,prefix)
Copy link
Member

@certik certik Mar 25, 2024

Choose a reason for hiding this comment

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

Is this function appending a string representation of an integer, real or an array into the string message?

If so, I would keep it separate (the linalg library can append what it needs into the error message), not tie this into the implementation of the error state.

Copy link
Contributor Author

@perazz perazz Mar 25, 2024

Choose a reason for hiding this comment

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

Exactly @certik, it covers all real, integer, complex kinds covered by stdlib (according to the preprocessing) and strings. I designed it this way so that error handling is easy to be read by the user but also easy to implement. See for example in my current solve implementation:

https://github.com/perazz/fortran-lapack/blob/869e12f0aff09e3520a828e11e90898591872d47/src/stdlib_linalg_solve.f90#L178-L189

I find it very easy to read - and no need to write formats for each possible error string. Also, there are at least 5-6 possible error messages out of each LAPACK routine, it could be a format nightmare down the road.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

One option could be to move the numeric->string routines to stdlib_io?

Copy link
Member

Choose a reason for hiding this comment

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

Could you use the stdlib to_string?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Totally, although here I purposedly avoided allocatable components, because there is going to be one linalg_state_type variable in every function, and I wanted the compiler to be able to put it on the stack to minimize performance penalty. What do you think?

Copy link
Member

Choose a reason for hiding this comment

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

Is performance a real problem here? I mean, if it is at this stage, it means that there is an issue.
Anyway, I am ok with the code as it is now. I am just wondering what would be the impact performance-wise.

@perazz
Copy link
Contributor Author

perazz commented Mar 25, 2024

whether this should be specialized to linear algebra, or made more general.

Thanks a lot @certik for the review! We briefly touched this point during the last monthly call. It seems like the consensus was: let's try to use this error handling method for the linear algebra part first. If we see that we like it, don't find issues, etc., then it could be promoted to a global error handling paradigm for the whole stdlib. So far, there is really little/no error handling whatsoever, so we won't introduce breaking compatibility changes, as long as the contents of the linalg_state handler are agreed upon (i.e. state flag, message, and location):

!> The current exit state
integer(ilp) :: state = LINALG_SUCCESS
!> Message associated to the current state
character(len=MSG_LENGTH) :: message = repeat(' ',MSG_LENGTH)
!> Location of the state change
character(len=NAME_LENGTH) :: where_at = repeat(' ',NAME_LENGTH)

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 . Overall LGTM. Here are some suggestions.
It would be good to add some specs too.

src/stdlib_linalg_constants.fypp Outdated Show resolved Hide resolved
src/stdlib_linalg_state.fypp Outdated Show resolved Hide resolved
src/stdlib_linalg_state.fypp Outdated Show resolved Hide resolved
module stdlib_linalg_constants
use stdlib_kinds, only: sp, dp, qp, int32, int64, lk
use, intrinsic :: ieee_arithmetic, only: ieee_is_nan
!$ use omp_lib
Copy link
Member

Choose a reason for hiding this comment

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

Is omp_lib needed here?

Suggested change
!$ use omp_lib

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, it is used by the LAPACK functions. I put it in the constants module to avoid repeated use statements.

src/stdlib_linalg_constants.fypp Outdated Show resolved Hide resolved
src/stdlib_linalg_state.fypp Outdated Show resolved Hide resolved
@perazz
Copy link
Contributor Author

perazz commented Apr 2, 2024

@certik @jvdp1 (@gnikit @jalvesz) do you have further comments that could help get this merged soon? Thanks @jvdp1's review, IMHO everything looks in line with stdlib's style now.

After we have error/state handling merged, we can begin adding several high-level APIs in parallel, so people will have time to look and comment and we can iterate on them efficiently.

Copy link
Member

@gnikit gnikit left a comment

Choose a reason for hiding this comment

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

I just had a quick look. The majority of the contributions seem okay to me, but I feel I am missing something/misunderstanding the mechanism behind methods such as new_state_nowhere. I think I might need to spend some more time into this (probably not today) before I can leave a fully informed review. In the meantime, if others have the time to review please go ahead, don't wait for my comments.

@perazz
Copy link
Contributor Author

perazz commented Apr 2, 2024

the mechanism behind methods such as new_state_nowhere

2 state creation interfaces (with/without location info) use optional, rank-agnostic, unlimited polymorphic inputs for easy setup of the error messages, e.g.:

err = linalg_state_type(LINALG_VALUE_ERROR,'just an example with scalar ',&
'integer=',1,'real=',2.0,'complex=',(3.0,1.0),'and array ',[1,2,3],'inputs')

  • no clogging of the high-level APIs with format statements
  • no over-head issues (the variables may be resolved at runtime, but it's not a problem as this will only happen when an error is occurred)
  • up to 20 input tokens per message are supported

@jalvesz
Copy link
Contributor

jalvesz commented Apr 2, 2024

I tried to give a look and had a bit of trouble to fully grasp the mechanism. Also because I usually use very simple error handling. So my 1 cent at this stage would be that maybe a more complete example, (a kind of micro-tutorial) showing how it can be used together with an interface built locally within the example could help?

@perazz
Copy link
Contributor Author

perazz commented Apr 5, 2024

@certik @gnikit @jalvesz do you have indications that would make you happy of the PR, complete the review and approve for merge? For example, remove the helper functions altoghether, then manually set each error message.

@jalvesz
Copy link
Contributor

jalvesz commented Apr 6, 2024

I was thinking, for the example, say that you were to use this error catching mechanism to implement an interface for matrix inversion and you would like to decide wether to stop or just throw a warning if the determinant is zero? How would you structure the program? a small example showing this could be enlightening :)

@perazz
Copy link
Contributor Author

perazz commented Apr 6, 2024

a small example showing this could be enlightening :)

Great idea @jalvesz, I added one more example, I hope shows it better (later when we start merging linear algebra functions, there will be plenty more):

if (b==0.0) then
! Division by zero
err0 = linalg_state_type(this,LINALG_VALUE_ERROR,'Division by zero trying ',a,'/',b)
elseif (.not.abs(b)<MAXABS) then
! B is out of bounds
err0 = linalg_state_type(this,LINALG_VALUE_ERROR,'B is infinity in a/b: ',[a,b]) ! use an array
elseif (.not.abs(a)<MAXABS) then
! A is out of bounds
err0 = linalg_state_type(this,LINALG_VALUE_ERROR,'A is infinity in a/b: a=',a,' b=',b)
else
a_div_b = a/b
if (.not.abs(a_div_b)<MAXABS) then
! Result is out of bounds
err0 = linalg_state_type(this,LINALG_VALUE_ERROR,'A/B is infinity in a/b: a=',a,' b=',b)
else
err0%state = LINALG_SUCCESS
end if
end if

@jalvesz
Copy link
Contributor

jalvesz commented Apr 8, 2024

Thanks @perazz LGTM!!

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.

LGTM. Thank you @perazz for this PR. I just have a few minor suggestions. Feel free to ignore them.

example/linalg/example_state1.f90 Outdated Show resolved Hide resolved
example/linalg/example_state2.f90 Outdated Show resolved Hide resolved
example/linalg/example_state2.f90 Outdated Show resolved Hide resolved
src/stdlib_linalg_state.fypp Outdated Show resolved Hide resolved
src/stdlib_linalg_state.fypp Outdated Show resolved Hide resolved
src/stdlib_linalg_state.fypp Outdated Show resolved Hide resolved
perazz and others added 6 commits April 8, 2024 14:17
Co-authored-by: Jeremie Vandenplas <jeremie.vandenplas@gmail.com>
Co-authored-by: Jeremie Vandenplas <jeremie.vandenplas@gmail.com>
Co-authored-by: Jeremie Vandenplas <jeremie.vandenplas@gmail.com>
Co-authored-by: Jeremie Vandenplas <jeremie.vandenplas@gmail.com>
Co-authored-by: Jeremie Vandenplas <jeremie.vandenplas@gmail.com>
Co-authored-by: Jeremie Vandenplas <jeremie.vandenplas@gmail.com>
@perazz
Copy link
Contributor Author

perazz commented Apr 8, 2024

Thanks for the reviews @jvdp1 @jalvesz: let's see if there are more comments. If I see that no outstanding issues in the next few days, I think it's good enough to merge.

@perazz perazz merged commit 407798c into fortran-lang:master Apr 11, 2024
17 checks passed
@jvdp1
Copy link
Member

jvdp1 commented Apr 11, 2024

@perazz Should I generate a new release of stdlib (v0.5.0)?

@perazz
Copy link
Contributor Author

perazz commented Apr 11, 2024

Yes @jvdp1, what do you think? I believe it's a good idea because that sets the starting point for the linear algebra procedures. Another option would be to wait until some of them are also implemented, but that could also come with 0.5.1, or another minor tick.

@jvdp1
Copy link
Member

jvdp1 commented Apr 11, 2024

I think it would set the starting point of the integration of BLAS/LAPACK into stdlib. This will help users to test it and provide some feedbacks.
Additional APIs and related changes could be indeed part of revision of this version.
@gnikit what do you think?

@gnikit
Copy link
Member

gnikit commented Apr 11, 2024

Yes, that sounds great @jvdp1! (If I am unresponsive here you can always reach me on Discourse).

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.

5 participants