-
-
Notifications
You must be signed in to change notification settings - Fork 10.8k
ENH: np.linalg.inv: Allow disabling error when one matrix is singular in a stack #28782
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
base: main
Are you sure you want to change the base?
Conversation
The PyPy failure can be ignored. |
We should probably discuss how to do this (i.e. how to name the I thought I recalled some discussion on this in SciPy (@tylerjereddy?) if they had a plan on how (if) to do this, then we need to align. For the implementation/code: You also need to test that the result for the invalid entries is as expected, you are testing only the valid ones! |
I’ve improved the test cases as suggested. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought I recalled some discussion on this in SciPy (@tylerjereddy?) if they had a plan on how (if) to do this, then we need to align.
Yup, I was pretty vocal about wanting to have the default behavior remain erroring out if something is "wrong" (fail fast and early, Pythonic, etc.). Here is the main discussion that comes to mind: scipy/scipy#22476
@ilayn @mdhaber and @ev-br had some back and forth there. I think Ralf had a similar view to mine.
A conceptually related thing is "lazy" backends (i.e., JAX) where you can't introspect and so your only real choice is to return NaNs for busted axes/slices. One other thing I remember being discussed was wanting to avoid certain synchronization scenarios on GPUs, even for eager backends, so avoiding the error checks in those cases was also discussed.
Anyway, that's a bit wider than what NumPy needs to care about, but those are the cases where recent discussions came up I think. Perhaps NumPy only wants to be involved in the "batching" part of the discussion, though some loose awareness of the other scenarios where NaNs get returned instead of errors may be helpful context for the broader ecosystem.
numpy/linalg/tests/test_linalg.py
Outdated
|
||
assert_almost_equal(result[0], np.array([[1.0, 0.0], [0.0, 1.0]])) | ||
assert_(np.isnan(result[1]).all()) | ||
assert_almost_equal(result[2], np.array([[2 / 3, -1 / 3], [-1 / 3, 2 / 3]])) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor points, but assert_allclose()
should probably be preferred as noted in the docs for assert_almost_equal
(in newly-written code, even if you see the old way in some source files).
Plain assert
probably slightly preferred to the old assert_
as well, now that we use pytest
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, also assert_almost_equal
should deal with NaNs, so there is no need to do this in 3 asserts, creating the full expected result and compariging is a bit nicer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've updated the tests to replace test functions, following the feedback provided.
Thank you for your suggestions !!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It’s strange—tests are failing because the expected error isn’t being raised correctly, but this issue appears only in a single environment.
Yeah, even if we were to change the defeault, it would maybe make sense to try and allow the old behavior or go to a warning.
Those are arguments for making it available and slightly normalizing non-erroring behavior. It's a bit hard for SciPy, I guess, but I am not sure I would worry too much about behavior changing here if you pass a cupy/torch/... array rather than a NumPy one to a library function. Anyway, right now I lean towards That is slightly harder to implement, and we need a name of for that option of course. "Linalg" related would make sense. Something more general could actually also make sense to me, but maybe it's best to stay specific (one can always add a "group" if we end up with multiple such errstates). N.B.: The fast-math discussion is slightly similar, as it could also be tagged on to |
Returning error is fine if there is nothing else to be done. But this is not just "there is an error I'm quitting". There is still work to do in a batch array. That is the crux of the issue. Not all jobs are meant to be "if broken, requires to be fixed" if one slice is singular and the rest is OK. We are definitely not trying to hide or silence any error. Also we are not forgiving any bad LAPACK argument errors etc. Those are fundamental errors that relate to the code not being able to carry on prescribed algorithm and raises an Exception and work stops. There is no change there and those are hard errors. Having @ev-br also mentioned the idea of talking to np.errorstate in scipy/scipy#22838 but I don't know ow to do that in non-NumPy C code yet. That would also fix this issue without any extra keywords and still maintain the explicit style of |
+1 for Ideally, there's a context manager to allow user control of at least three scenarios:
I don't know if |
This one should be out of scope in my opinion. That's a hard error independent from the data and it should never happen unless there is an issue somewhere else so better to quit. |
So I think this is very clearly leaning towards @ilayn currently there is basically no useful C-API for But, I don't think that matters. We can clearly expand this, but we should focus on user API first, IMO. We may not even want to include it into
These are the exact options we do for all other warning classes of course (plus print and call, but I would be happy to omit them for new things, TBH). "Fail early" would require the loop to know about the state. That shouldn't be too hard, but I would consider a later step. (I am not convinced fail-fast is a very important feature here and it's orthogonal to the user API again.) EDIT: Ah, I missed the additional info to "something went wrong". That may be depend on the infrastructure we create, but not sure if it is vital.
The "always"/"once" is a Python warning option. I think it's best to just keep it that way and not overcomplicate things here. (It's also too difficult anyway, since Python "once" takes into account the call site.)
I mean it's just a context from Python that you can query that context and we can trivially make it a bit faster/easier to access with new C-API. |
I agree with this. So probably I couldn't elaborate properly earlier. From the UX, SciPy does try to be helpful for Now we want to extend this to nD-arrays. Let's keep example simple; A is of shape (10, 5, 5). Slice 3 (0-indexed) is ill-conditioned and Slice 7 is exactly singular. Currently this is happening in a Python loop for SciPy and C loop in NumPy, that is solving each 5x5 case and pushing it to a suitable output array. And if any singularity occurs, it's done. A = np.zeros([10, 5, 5])
A += np.eye(5)
A[7, :, :] = np.arange(25).reshape(5,5)
A[3, 4, 4] = 1e-16 # Currently no effect, since both NumPy and SciPy does not have ill-conditioning check on inv
np.linalg.inv(A)
---------------------------------------------------------------------------
LinAlgError Traceback (most recent call last)
Cell In[43], line 1
----> 1 np.linalg.inv(A)
File ~\AppData\Local\Programs\Python\Python312\Lib\site-packages\numpy\linalg\_linalg.py:609, in inv(a)
606 signature = 'D->D' if isComplexType(t) else 'd->d'
607 with errstate(call=_raise_linalgerror_singular, invalid='call',
608 over='ignore', divide='ignore', under='ignore'):
--> 609 ainv = _umath_linalg.inv(a, signature=signature)
610 return wrap(ainv.astype(result_t, copy=False))
File ~\AppData\Local\Programs\Python\Python312\Lib\site-packages\numpy\linalg\_linalg.py:104, in _raise_linalgerror_singular(err, flag)
103 def _raise_linalgerror_singular(err, flag):
--> 104 raise LinAlgError("Singular matrix")
LinAlgError: Singular matrix So the argument is then making it such that if we do with np.errstate(<some setting tbd>='ignore'):
np.linalg.inv(A)
LinAlgWarning: input [3, ..., ...] is ill-conditioned. The results may be inaccurate (rcond=....) returns a partially NaN filled array. Am I seeing the picture correctly? |
Well, clearly, |
That's a very juicy decision discussion to have. I guess one detail for us to understand, since we will probably follow your lead; Can we get the context before we start the operations meaning that if If we can get the context beforehand then we can pass this information to the actual algorithm for an early exit. I think this would affect the final decision. |
I still think this is wrong.
Yes it is. My point is if we're designing a context manager, it better include the warning control, so that a user can use a single context manager instead of two, one for linalg state and the other for |
Let's separate this out:
Yes, implementation-wise for linalg functions a fail-fast may be the right thing to do though (a difference only for in-place
Well, |
Yes if Since we don't implement these as ufuncs, this detail is important for us and not so much for NumPy |
OK item 2 is also not so relevant for linalg batches anyways then we are good to go in that regard. |
OK, let me just argue for these 2-3 things to start with:
This is should all be pretty straight forward and 3. is only to make things a bit nicer especially for SciPy. The trickiest part should really be dancing around the call/print option (and deciding how to perform that dance). |
Description
This PR introduces a new optional parameter noerr (default: False) to the numpy.linalg.inv function. The purpose of this parameter is to allow users to compute inverses of multiple matrices simultaneously (stacked matrices with shape (m, n, n)), without raising a LinAlgError if at least one matrix is singular. #27035
Example
close #27035