Skip to content

Use eigh for principal axes and fix moments near the periodic boundary - #440

Merged
harryswift01 merged 12 commits into
mainfrom
439-general-use-nplinalgeigh
Oct 9, 2026
Merged

harryswift01 merged 12 commits into
mainfrom
439-general-use-nplinalgeigh

Conversation

@harryswift01

@harryswift01 harryswift01 commented Oct 7, 2026 •

Copy link
Copy Markdown
Member

Summary

This PR addresses #439 and is a step towards #436. It switches the axes calculations from the general eigensolver (np.linalg.eig) to the symmetric one (np.linalg.eigh), makes the principal axes reproducible across LAPACK builds, and fixes a bug in how moments of inertia were computed for molecules near the periodic boundary.

Changes

Use np.linalg.eigh for symmetric matrices:

  • Replaces np.linalg.eig with np.linalg.eigh, since inertia tensors are always symmetric.
  • eig does not guarantee orthogonal eigenvectors for (near-)degenerate moments, while eigh always returns an orthonormal set.

Make the principal axes reproducible:

  • eigh fixes neither the sign of each eigenvector nor the basis within a degenerate eigenspace, and both can differ between LAPACK builds. The axes define the frame covariances are averaged in, so this changes the entropy.
  • get_principal_axes_from_tensor now returns canonical axes:
    • Moments are sorted by descending absolute value.
    • Within a degenerate eigenspace, the basis is chosen from a fixed reference vector. Axes with distinct moments are left unchanged.
    • The first two axes are signed against the same reference vector, and the third is their cross product, so the frame is always right-handed.
  • The degeneracy tolerance (degeneracy_rtol, default tied to float32 precision) and the reference vector are keyword arguments.
  • Adds get_principal_axes_from_group, which uses it in place of the built-in principal_axes() at all call sites in axes.py and covariance.py.
  • The axes depend on the lab-frame orientation, so they are reproducible but not rotation covariant.

Take the moment of inertia tensor after making the molecule whole:

  • get_molecule_axes took the tensor with unwrap=True before make_whole. This gives values of order M·L² when a molecule's centre of mass lies outside the primary cell.
  • It now calls make_whole first and uses the plain tensor, so the moments no longer depend on where the molecule sits relative to the box.

Clearer function names:

  • get_custom_principal_axes → get_principal_axes_from_tensor
  • get_principal_axes → get_principal_axes_from_group
  • get_vanilla_axes → get_molecule_axes
  • get_custom_axes → get_bonded_vector_axes

Tests:

  • Updates the unit tests for the new behaviour and names.
  • Adds tests that the axes are orthonormal and right-handed, unchanged under random eigenvector signs and bases, stable under rounding-level noise, and keep the unique axis of a linear tensor exact.
  • Adds a regression test that shifts a molecule by a box vector and checks its moments are unchanged.
  • Regenerates the regression baselines.

Impact

  • Axes are orthonormal and no longer depend on the LAPACK build, which removes a source of the intermittent CI failures in the regression tests.
  • Torques for molecules whose centre of mass lay outside the primary cell were down-weighted by roughly 100x, so they are now correct.
  • Entropy values change slightly because the axis signs and handedness convention changed, hence the new baselines.

@harryswift01 harryswift01 changed the title Use np.linalg.eigh for symmetric matrices and fix moment of inertia near the periodic boundary Use eigh for principal axes and fix moments near the periodic boundary Oct 8, 2026
@harryswift01
harryswift01 marked this pull request as ready for review October 8, 2026 13:06

@jimboid jimboid 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.

This looks like both a significant improvement in numerical reproducibility and stability as well as function naming in this area of the code. Baseline update is fairly self explanatory. Numbers don't look to change significantly but prefer Sarah F to have deciding vote on that. Otherwise can't see any reason why this can't merge

@skfegan skfegan 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.

The changes to the baselines look reasonable.

@harryswift01
harryswift01 merged commit b9f7fad into main Oct 9, 2026
23 checks passed
@harryswift01
harryswift01 deleted the 439-general-use-nplinalgeigh branch October 9, 2026 08:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants