Skip to content

Fix compatibility with numpy 2.5#5404

Merged
RMeli merged 7 commits into
developfrom
fix-ci
Jun 23, 2026
Merged

Fix compatibility with numpy 2.5#5404
RMeli merged 7 commits into
developfrom
fix-ci

Conversation

@IAlibay

@IAlibay IAlibay commented Jun 22, 2026

Copy link
Copy Markdown
Member

Fixes #5403

Changes made in this Pull Request:

  • With numpy 2.5, compatibility in casting between inputs and outputs is more strongly verified.
  • This PR fixes an issue where np.degrees could be fed a complex number from principal_axes due to floating point noise.

LLM / AI generated code disclosure

LLMs or other AI-powered tools (beyond simple IDE use cases) were used in this contribution: no

PR Checklist

  • Issue raised/referenced?
  • Tests updated/added?
  • Documentation updated/added?
  • package/CHANGELOG file updated?
  • Is your name in package/AUTHORS? (If it is not, add it!)
  • LLM/AI disclosure was updated.

Developers Certificate of Origin

I certify that I can submit this code contribution as described in the Developer Certificate of Origin, under the MDAnalysis LICENSE.

"""
atomgroup = group.atoms
e_val, e_vec = np.linalg.eig(atomgroup.moment_of_inertia(wrap=wrap))
e_val, e_vec = np.linalg.eigh(atomgroup.moment_of_inertia(wrap=wrap))

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I think this should fix it?

Someone with better maths knowledge should double check this - my understanding is that the moment of intertia tensor is always a real symmetric matrix, so it should satisfy the requirements of np.linalg.eigh.

My understanding is that eigh doesn't have the stability issues of eig, so I tried swapping it out and it seems to be working fine.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Indeed, eigh is preferred for symmetric/Hermitian matrices.

[
[0.78787867, 0.26771575, -0.55459488],
[-0.40611024, -0.45112859, -0.7947059],
[-0.78787867, -0.26771575, 0.55459488],

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I believe these are fine since the eigenvector sign is arbitrary?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yes, the sign is algorithm-dependent and therefore can differ between eig and eigh.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks like some tests are still failing because of sign issues. Maybe to make them more robust we could check for all the eigenvector tests that they are numerically equivalent up to a sign change?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yeah I'm thinking we should do an abs check, will update the tests accordingly.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

good idea

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done - just to sanity check me, can I get a re-review please?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I thought about it again. abs is not the same as sign-check. Will comment on review.

@IAlibay IAlibay requested review from orbeckst and tylerjereddy June 22, 2026 10:37
"""
atomgroup = group.atoms
e_val, e_vec = np.linalg.eig(atomgroup.moment_of_inertia(wrap=wrap))
e_val, e_vec = np.linalg.eigh(atomgroup.moment_of_inertia(wrap=wrap))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Indeed, eigh is preferred for symmetric/Hermitian matrices.

[
[0.78787867, 0.26771575, -0.55459488],
[-0.40611024, -0.45112859, -0.7947059],
[-0.78787867, -0.26771575, 0.55459488],

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yes, the sign is algorithm-dependent and therefore can differ between eig and eigh.

@IAlibay IAlibay requested a review from RMeli June 22, 2026 18:22
@codecov

codecov Bot commented Jun 22, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.85%. Comparing base (d25f074) to head (2f46b14).
⚠️ Report is 3 commits behind head on develop.

Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #5404      +/-   ##
===========================================
+ Coverage    93.63%   93.85%   +0.21%     
===========================================
  Files          182      182              
  Lines        22509    22509              
  Branches      3202     3202              
===========================================
+ Hits         21076    21125      +49     
+ Misses         965      922      -43     
+ Partials       468      462       -6     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@orbeckst orbeckst left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Although testing for abs() equality works, it's not equivalent to testing that the two vectors are equal up to a sign change. The abs check can, in principle, lead to false positives.

I suggest to do this properly and use a helper function such as

def assert_vectors_no_sign_close(a, b):
   if not (np.allclose(a, b) or np.allclose(a, -b)):
      # raise assertion with the sign choice that looks closest
      x = b if np.linalg.norm(a - b) < np.linalg.norm(a + b) else -b
      assert_allclose(a, x)

allclose() may need proper rtol and atol if the defaults aren't appropriate.

Comment thread package/CHANGELOG Outdated
Comment on lines +25 to +28
* The `principal_axes` method in :class:`Masses` now uses
`np.linalg.eigh` instead of `np.linalg.eig`, improving numerical
stability. This may lead to slightly different results from previous
version of MDAnalysis.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should go under changes, not really a fix. Reference PR #5404

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Interesting, I see this as a a fix in the sense of "before we were using something that could yield a complex number because it was prone to floating point precision issues, now it's fixed" - what do you think?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

My view was that this was a robustness change that was surfaced by a newer version of numpy. However, your interpretation also makes sense so I am happy to leave it as a fix. I'd still add the issue/PR number, though.

assert_almost_equal(result[0], ref[method_name][0], self.prec)
assert_almost_equal(result[1], ref[method_name][1], self.prec)
elif method_name == "principal_axes":
assert_almost_equal(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Adding comment regarding sign-check would be useful + ref GH-5404

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

comment along the lines of

The direction (sign) of the principal axes is dependent on the specific algorithm
used in numpy.linear.eigh() but the direction itself is not physically relevant
so we have to test both directions.

[
[0.78787867, 0.26771575, -0.55459488],
[-0.40611024, -0.45112859, -0.7947059],
[-0.78787867, -0.26771575, 0.55459488],

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I thought about it again. abs is not the same as sign-check. Will comment on review.

@IAlibay IAlibay requested a review from orbeckst June 22, 2026 22:08
@IAlibay

IAlibay commented Jun 22, 2026

Copy link
Copy Markdown
Member Author

Thanks @orbeckst - here's a second attempt with something a little bit more fancy (einsum -> sign then use that to flip antiparallel vectors before the assert).

@orbeckst orbeckst left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

lgtm, thanks for the fix

Comment thread package/CHANGELOG Outdated
# specific algorithm used, but the direction itself is not physically
# relevant, so we get the signs to flip any anti-parallel vectors before
# comparing the two results arrays
signs = np.sign(np.einsum("ij,ij->i", result, ref[method_name]))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

fancy, I convinced myself that it does the right thing for the output of principal_axes().

@RMeli RMeli left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Agree with @orbeckst, abs() is not what we wanted here. The ensum implementation is a nice touch (and well commented for posterity). Thanks @IAlibay!

@RMeli RMeli merged commit f765ecd into develop Jun 23, 2026
23 of 25 checks passed
@RMeli RMeli deleted the fix-ci branch June 23, 2026 07:25
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.

MDAnalysis testsuite fails with numpy 2.5

3 participants