-
Notifications
You must be signed in to change notification settings - Fork 173
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
Adding matrix norms to the stdlib_linalg
module.
#820
Comments
Great idea @loiseaujc. Rn I'm trying to tackle the decompositions (pseudo-inverse, Cholesky, QR) and I was planning to address norms and condition number next. So, your contribution would be very welcome! As a way to separate between the two approaches to norm, would there be a way to use the same |
We probably could. Julia is more exhaustive than SciPy for this. Having The more I think about it, the more it feels like the Julia way may actually cover more use-cases than are actually used in practice. I still like the difference between |
I took some time to think about all the different norms (both for vectors and matrices) I've ever needed in my scientific computing adventure over the past 15 years or so. Here is a pretty standard list (+ whatever Vector norms
Among these four, the Euclidean 2-norm is by far the most popular and, more often than not, is what people mean when discussing the norm of a vector I've used the other three quite regularly as well but more from a convex optimization perspective. I hardly ever had to actually compute them but they're simple enough to implement and I believe are something everybody expects from any standard linear algebra module. Matrix norms
Like everyone, I use the Frobenius norm all the time. Similarly, the 2-norm and the nuclear norm are quite extensively used in model order reduction. As before, the 1-norm and the There are a bunch of other matrix-related norms that I have used or seen being used over the years but they are more of a niche thing. These include:
Among these two, the Here are my thoughts (in no particular order) on the matter:
I tend to think that being strict about the rank-1 array argument for @perazz : On a side note, do you have a publicly available roadmap for the development of the |
@loiseaujc I basically agree with everything you said! I just have one question regarding the naming Regarding your query about the type of matrix, I think stdlib has in the linalg module https://stdlib.fortran-lang.org/page/specs/stdlib_linalg.html there are several checks such https://stdlib.fortran-lang.org/page/specs/stdlib_linalg.html#is_hermitian-checks-if-a-matrix-is-hermitian, https://stdlib.fortran-lang.org/page/specs/stdlib_linalg.html#is_hessenberg-checks-if-a-matrix-is-hessenberg among others.
Agree, and I don't think that in Fortran you would have a performance loss (that would be true in python). If the implementations and interfaces are well designed the compilers might even be able to properly vectorize nicely. One thing to consider though, would be to have at least dimensions 2, 3 and 4 with explicit unrolled implementations and then a generic one for d>4. |
I've used |
Thanks @loiseaujc for the detailed comments. I very mostly agree with your ideas and I would summarize the consensus so far:
Here is some further thoughts I'm having about the design.
character(*), optional, intent(in) :: norm_type
select case (norm_type)
case ('2','Euclidean')
...
case ('inf','Inf','Infinity')
...
end select
|
I shall be able to start working on this by the end of next week. I'll start with the baseline implementation and will see from there how we can improve and make sure it is not confounded with
I like this.
Agreed. I prefer the |
Absolutely agree, and it would be more in line with the |
Here is a draft/hacky implementation of #:for rk, rt, ri in RC_KINDS_TYPES
#:if rk !="xdp"
module function stdlib_linalg_norm_${ri}$(x, which) result(out)
!> Input vector x[n]
${rt}$, intent(in) :: x(:)
!> [optional] Which vector norm is being computed.
character(len=*), optional, intent(in) :: which
!> Norm of the vector.
real(${rk}$) :: out
!> Internal variables.
integer(ilp) :: n, idx
character(len=:), allocatable :: which_
!> Dimension of the vector.
n = size(x, kind=ilp)
!> Default: Euclidean 2-norm.
which_ = optval(which, "2") ; which_ = to_lower(which_)
!> Dispatch computation to the appropriate driver.
select case (which_)
case ("2", "euclidean", "l2")
out = nrm2(n, x, 1_ilp)
case ("1", "l1")
#:if rt[0] == "r"
out = stdlib_${rk[0]}$asum(n, x, 1_ilp)
#:elif rt[0] == "c" and rk[0] == "s"
out = stdlib_scsum1(n, x, 1_ilp)
#:elif rt[0] == "c" and rk[0] == "d"
out = stdlib_dzsum1(n, x, 1_ilp)
#:elif rt[0] == "c" and rk[0] == "q"
out = stdlib_qzsum1(n, x, 1_ilp)
#:endif
case ("inf")
#:if rt[0] == "r"
idx = stdlib_i${rk[0]}$amax(n, x, 1_ilp)
#:else
idx = stdlib_i${ri[0]}$max1(n, x, 1_ilp)
#:endif
out = abs(x(idx))
case default
stop
end select
end function stdlib_linalg_norm_${ri}$
#:endif
#:endfor After having extended the interface for
interface asum
module function rsasum
module function csasum
...
end interface and interface sum1
module function rsasum
module function csum1
...
end interface
I'll wait for your feedback at the moment and will start implementing the tests and documentation in the mean time. |
This sounds good! another option, if you prefere to keep a single interface: use stdlib_str2num, only: to_num
use stdlib_error, only: error_stop
...
case default
p = to_num( which_ , p )
if( p > 2 .and. p < p_limit ) then !> define a limit p value
!> compute the p-norm
else
call error_stop( "Invalid p-norm",code )
end if If you prefere to have an interface that takes an integer |
looks good so far!
What I like the most is to have a separate function to do the option parsing, so the input could be either pure subroutine parse_norm(which, norm_type, err)
character(*), intent(in) :: which
integer(ilp), intent(out) :: norm_type
type(linalg_state_type), intent(out) :: err
select case (to_lower(which))
case ("euclidean","2","l2")
norm_type = NORM_TYPE_2
case ("inf","infinity")
norm_type = NORM_TYPE_INF
case default
p = to_num(which, p)
if (p>2 .and. p<=NORM_TYPE_MAX) then
norm_type = p
else
norm_type = NORM_TYPE_ERROR
err = linalg_state_type("norm",LINALG_VALUE_ERROR,"invalid norm type: ",which)
endif
end select
end subroutine The rule could be to define parameters that are >1 for p-norms and <=0 for ther norms, i.e.: integer(ilp), parameter :: NORM_TYPE_ERROR = -huge(0_ilp)
integer(ilp), parameter :: NORM_TYPE_FROB = -1
integer(ilp), parameter :: NORM_TYPE_INF = -2
integer(ilp), parameter :: NORM_TYPE_L1 = 1
integer(ilp), parameter :: NORM_TYPE_L2 = 2
integer(ilp), parameter :: NORM_TYPE_MAX = 100
For the BLAS/LAPACK functions that have different names for real/complex types, I've been using inline fypp declarations, i.e.: stdlib/src/stdlib_linalg_svd.fypp Line 266 in d996e43
You can look at the stdlib/src/stdlib_linalg_svd.fypp Line 287 in d996e43
This forces the code to either return an error code (hopefully |
Motivation
I'm currently updating my package
LightKrylov
to make use as much as possible of the new linalg features offered in thestdlib_linalg
module. Among the things I'll need eventually are functions to compute matrix norms. Is there any on-going work at the moment from @perazz, @jvdp1, or @jalvesz on this subject or could I make this my first task withstdlib
?Prior Art
scipy.linalg.norm(A, ord=None, axis=None, keepdims=False, check_finite=True)
. It handles both standard vector norms as well as a variety of vector-induced and non-induced matrix norms.LinearAlgebra.norm(x, p)
wherex
is ann
-vector returns theLinearAlgebra.norm(A, p)
whereA
is anm x n
matrix returns the "entry-wise"A
.LinearAlgebra.opnorm(A, p)
whereA
is anm x n
matrix returns the vector-inducedA
.Additional Information
While most people might be accustomed to the SciPy standard, my personal preference would still go to the Julia principle of separating the true vector-induced norms (i.e. 1-norm, 2-norm,$\infty$ -norm) from the entrywise ones (e.g. Frobenius) although I'm obviously open to discussion.
The text was updated successfully, but these errors were encountered: