Skip to content

Improve O(3) transformations and Wigner-D evaluation - #293

Merged
Luthaf merged 9 commits into
metatensor:mainfrom
MichelangeloDomina:fix/o3-transformations
Jul 28, 2026
Merged

Improve O(3) transformations and Wigner-D evaluation#293
Luthaf merged 9 commits into
metatensor:mainfrom
MichelangeloDomina:fix/o3-transformations

Conversation

@MichelangeloDomina

@MichelangeloDomina MichelangeloDomina commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

This PR extracts and improves the O(3)-only part initially developed in #119.

What changes

  • Correct spherical parity for improper transformations: the additional factor is o3_sigma * (-1) ** o3_lambda and is applied only to the improper O(3) component.
  • Make real Wigner-D evaluation stable near both ZYZ Euler-angle poles and use the structured wigner_D_array API directly.
  • Store an independent copy of the matrix passed to O3Transformation, and construct the Wigner cache only when spherical data is first requested.
  • Build randomly sampled transformations through one batched validation and a trusted internal construction path.
  • Preserve TensorMap information and registered-neighbor autograd while transforming Systems, blocks, values, and gradients.
  • Validate angular-momentum arguments, spherical/Cartesian component metadata, system assignments, and transformation dtype/device consistently.
  • Support all ten component-axis positions already recognized by the O(3) naming convention.
  • Document the resulting public contract and require wigners >= 0.4.0 for the structured Wigner-D API.

Compatibility decisions

This PR proposes renaming O3Transformation.is_inverted property to is_improper, which describes the actual condition (det(matrix) < 0) more precisely.

The following observable behaviors also become explicit:

  • matrices passed to the public O3Transformation constructor are copied, while matrix and wigner_D_matrix retain their released no-copy return behavior;
  • Wigner-D construction moves from object construction to first spherical use;
  • random_transformations accepts the model-capability dtypes torch.float32 and torch.float64;
  • structurally ambiguous component metadata and system identifiers are rejected before numerical transformation.

Performance evidence

The retained optimizations were isolated from correctness changes:

  • Wigner-D matrices are now built only when a spherical transformation or a direct Wigner-D request requires them. For 32 float64 CUDA transformations with max_angular_momentum=4, this reduced creation time by 72.9% and creation plus Cartesian-only use by 71.9%. The Wigner-D cost is just deferred as shown by the fact that, when a spherical transformation was requested immediately, total time was unchanged within 0.2%.
  • When random_transformations generated 32 to 4096 transformations, replacing separate validation of every 3×3 matrix with one batched validation reduced creation time by 82.7–97.9% on CPU float64 and 90.8–99.6% on CUDA.

The compared matrices, proper/improper classifications, and representative Wigner-D results were identical.

Validation

  • canonical Ruff formatting and lint checks passed;
  • 121 focused O(3) cases passed, including CPU/CUDA and float32/float64 cases;
  • all 243 tests in the complete metatomic-torch Python suite passed;
  • the canonical documentation build passed with Sphinx warnings treated as errors.

Contributor (creator of pull-request) checklist

  • Tests updated (for new features and bugfixes)?
  • Documentation updated (for new features)?
  • Issue referenced (for PRs that solve an issue)?

Reviewer checklist

  • CHANGELOG updated with public API or any other important changes?

Recover ZYZ angles without amplifying roundoff at Euler poles and consume structured wigner_D_array results directly. Require wigners 0.4 or newer for the structured API.
Correct spherical parity, protect transformation state, defer Wigner caches, and batch trusted random construction. Preserve System and TensorMap metadata/autograd while validating component metadata, system assignments, and dtype/device contracts. Expand focused CPU/CUDA coverage for the released and corrected behavior.
Document component labels, parity, ownership, lazy Wigner construction, system assignment, and supported random dtypes. Record the observable API changes and correctness fixes in the unreleased changelog.

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

just a quick look at the API changes, I'll do a deeper pass for the implementation & tests after everything is converged

Comment thread metatomic-torch/CHANGELOG.md Outdated
Comment thread metatomic-torch/CHANGELOG.md Outdated
Comment thread metatomic-torch/CHANGELOG.md
Comment thread python/metatomic_torch/metatomic/torch/o3/_tranformations.py Outdated
Comment on lines +175 to +189
@classmethod
def _create_from_internal_matrix(
cls,
matrix: torch.Tensor,
max_angular_momentum: int,
*,
is_improper: bool,
) -> "O3Transformation":
"""Create a transformation from an internally generated matrix."""
transformation = cls.__new__(cls)
transformation._matrix = matrix
transformation._max_angular_momentum = max_angular_momentum
transformation._is_improper = is_improper
transformation._wigner_D_cache = None
return transformation

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.

What's the point of this compared to the constructor above?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This private path is used only after random_transformations has generated and validated all matrices as one batch and already knows which one is proper/improper and It avoids repeating the public constructor’s per-matrix validation, copy, and determinant calculation.

Comment thread python/metatomic_torch/metatomic/torch/o3/_tranformations.py Outdated
Comment thread python/metatomic_torch/tests/o3.py Outdated
Comment thread python/metatomic_torch/tests/o3.py
Exported model wrappers can store Cartesian and Wigner-D matrices as registered tensor buffers, while the existing transform_tensor interface accepts Python O3Transformation objects and can not be compiled by TorchScript.

Add a private tensor-only transformation path that consumes precomputed matrix batches while reusing the O(3) component, parity, routing, gradient, and metadata conventions. Add a private query for the largest spherical component rank present in TensorMap values or gradients, allowing callers to validate their serialized Wigner-D coverage.

Keep the public O(3) API unchanged, and compare the new path directly with transform_tensor for proper and improper operations on CPU and CUDA.
@ppegolo
ppegolo marked this pull request as ready for review July 24, 2026 14:25

@Luthaf Luthaf 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 tests are very sloppy, checking minute details and drowning relevant information in a lot of lines and cases. This makes the test suite takes longer than needed for no reason, and makes it harder to understand the intent behind each test.

I would remove most of the tests that just check one error message in a very rare misuse of the API (i.e. sigma = SomeRandomClass()), and try to consolidate/clearly explain for each test why it is needed/what it is testing

Comment thread docs/src/torch/reference/o3.rst Outdated
Comment thread docs/src/torch/reference/o3.rst Outdated
Comment thread docs/src/torch/reference/o3.rst Outdated
Comment thread python/metatomic_torch/tests/o3.py
Comment thread python/metatomic_torch/tests/o3.py Outdated
Comment thread python/metatomic_torch/metatomic/torch/o3/_tranformations.py Outdated
Comment thread python/metatomic_torch/metatomic/torch/o3/_tranformations.py Outdated
Comment thread python/metatomic_torch/metatomic/torch/o3/_tranformations.py Outdated
Comment thread python/metatomic_torch/metatomic/torch/o3/_tranformations.py
Comment thread python/metatomic_torch/metatomic/torch/o3/_tranformations.py Outdated
@ppegolo
ppegolo force-pushed the fix/o3-transformations branch 8 times, most recently from 2b1a9fb to 4863f80 Compare July 27, 2026 14:12
@ppegolo
ppegolo force-pushed the fix/o3-transformations branch from 4863f80 to d8c324f Compare July 27, 2026 14:34

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

Getting there, some of the tests can be made more useful by checking the actual full message instead of a substring

Comment thread python/metatomic_torch/metatomic/torch/o3/_tranformations.py Outdated
Comment thread python/metatomic_torch/tests/o3.py
Comment thread python/metatomic_torch/tests/o3.py Outdated
Comment thread python/metatomic_torch/tests/o3.py Outdated
Comment thread python/metatomic_torch/tests/o3.py Outdated
Comment thread python/metatomic_torch/tests/o3.py Outdated
Comment thread python/metatomic_torch/tests/o3.py Outdated
Comment thread python/metatomic_torch/tests/o3.py Outdated
@Luthaf
Luthaf merged commit 851890c into metatensor:main Jul 28, 2026
63 checks passed
@Luthaf

Luthaf commented Jul 28, 2026

Copy link
Copy Markdown
Member

Thank @MichelangeloDomina and @ppegolo for your work here!

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.

3 participants