-
Notifications
You must be signed in to change notification settings - Fork 11
Add an O(3)-symmetrized atomistic model wrapper #294
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
Closed
MichelangeloDomina
wants to merge
18
commits into
metatensor:main
from
MichelangeloDomina:review/symmetrized-model-progressive
Closed
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
9a4bcdb
feat(torch): add reviewed SymmetrizedModel core
MichelangeloDomina 5ebf4d5
test(torch): cover SymmetrizedModel forward
MichelangeloDomina 0056d07
feat(torch): wrap symmetrized models for export
MichelangeloDomina 1daf33a
fix(torch): harden SymmetrizedModel contracts
MichelangeloDomina 5058bd4
Apply o3 review conventions to symmetrized model
ppegolo 6a3a4bb
Close coverage gaps and use physics terminology in docstrings
ppegolo 5ff2605
Address review
ppegolo 8f87505
Move quantity metadata to _quantities.py
ppegolo 17ebabc
Demote decompose shape validation to asserts
ppegolo 824277b
Relax angular momentum inference, rename max_o3_lambda parameters
ppegolo b97b2e6
Polish SymmetrizedModel API: group parameters, keyword-only init
ppegolo a8e5e11
Reuse and adapt O3 stuff + other review items
ppegolo 41c348d
Fix typo in _transformations.py file name
ppegolo 7c41bab
Address second-round review: drop wrapper-side name aliases, warn on …
ppegolo ce188e9
Keep the antisymmetric pseudovector sector in stress diagnostics
ppegolo 7542465
Further simplify code and tests
ppegolo 1092aaf
Recognize deprecated quantity names when decomposing outputs
ppegolo b310488
Remove the transform_system/block/tensor free functions
ppegolo File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,6 +11,7 @@ API reference | |
| units | ||
| wrappers | ||
| o3 | ||
| symmetrized-model | ||
| ase | ||
| misc | ||
|
|
||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,157 @@ | ||
| .. _symmetrized-model: | ||
|
|
||
| O(3)-symmetrized models | ||
| ======================= | ||
|
|
||
| The :py:class:`metatomic.torch.SymmetrizedModel` class wraps an exported | ||
| :py:class:`~metatomic.torch.AtomisticModel` with finite-quadrature O(3) | ||
| averaging and equivariance diagnostics. Pre-existing outputs of the model are | ||
| averaged over rotated and inverted copies of each input. | ||
| :py:class:`~metatomic.torch.SymmetrizedModel` also adds extra outputs to | ||
| compute the equivariance variance or squared character-projection | ||
| contributions of the model response. | ||
|
|
||
| Output requests | ||
| --------------- | ||
|
|
||
| The requested output name selects both the source output and the calculation: | ||
|
|
||
| .. list-table:: | ||
| :header-rows: 1 | ||
|
|
||
| * - Requested and returned name | ||
| - Result | ||
| * - ``<name>`` | ||
| - O(3) average of the underlying ``<name>`` output | ||
| * - ``o3::variance::<name>`` | ||
| - component-averaged equivariance variance of ``<name>`` | ||
| * - ``o3::character_projection::<name>`` | ||
| - unnormalized squared character-projection contributions of ``<name>`` | ||
|
|
||
| ``<name>`` is preserved verbatim. It can therefore be a standard quantity, a | ||
| variant such as ``energy/pbe``, or a custom name such as | ||
| ``mtt::feature::node``. For example, | ||
| ``o3::variance::energy/pbe`` evaluates the underlying ``energy/pbe`` output. | ||
|
|
||
| The meaning of the variance depends on the metadata of the underlying output. | ||
| When its blocks carry recognized component labels (``o3_mu``-style spherical | ||
| or ``xyz``-style Cartesian axes), each response is rotated back to the input | ||
| frame first, and the variance measures the breaking of *equivariance*. Outputs | ||
| without such labels cannot be rotated back: their responses are compared as-is | ||
| across the quadrature, so their variance measures the deviation from | ||
| *invariance* only. An equivariant but unlabelled output — for example an | ||
| internal feature vector — reports a large variance even when it transforms | ||
| correctly. | ||
|
|
||
| Average and variance | ||
| -------------------- | ||
|
|
||
| For an input :math:`x`, an O(3) operation :math:`g`, and the target | ||
| representation :math:`\rho_\alpha`, define the response transformed back to the | ||
| input frame as | ||
|
|
||
| .. math:: | ||
|
|
||
| z_\alpha(g;x) = \rho_\alpha(g^{-1}) f(gx). | ||
|
|
||
| The ordinary result is the normalized Haar average | ||
|
|
||
| .. math:: | ||
|
|
||
| \Pi_\alpha(f,x) | ||
| = \int_{\mathrm{O}(3)} z_\alpha(g;x)\,\mathrm{d}\mu(g). | ||
|
|
||
| For a TensorMap block with component multiplicity :math:`d`, the corresponding | ||
| variance output contains | ||
|
|
||
| .. math:: | ||
|
|
||
| v_\alpha(f,x) | ||
| = \frac{1}{d}\left[ | ||
| \int_{\mathrm{O}(3)} \lVert z_\alpha(g;x) \rVert_2^2\, | ||
| \mathrm{d}\mu(g) | ||
| - \lVert \Pi_\alpha(f,x) \rVert_2^2 | ||
| \right]. | ||
|
|
||
| This value is returned separately for every sample and property. It has no | ||
| component axes, and it is not reduced across samples or square-rooted. A | ||
| weighted mean of these values over a group of samples, followed by a square | ||
| root, gives a block-wise equivariance RMSE. | ||
|
|
||
| TensorMap representation | ||
| ------------------------ | ||
|
|
||
| An averaged output retains the physical schema declared by the source model. | ||
| For diagnostics, the standard quantities are represented as follows: | ||
|
|
||
| .. list-table:: | ||
| :header-rows: 1 | ||
|
|
||
| * - Source quantity | ||
| - Diagnostic target keys | ||
| * - ``energy``, ``energy_ensemble``, ``energy_uncertainty`` | ||
| - ``o3_lambda=0``, ``o3_sigma=1`` | ||
| * - ``non_conservative_force`` | ||
| - ``o3_lambda=1``, ``o3_sigma=1`` | ||
| * - ``non_conservative_stress`` | ||
| - ``(o3_lambda, o3_sigma)=(0,1)``, ``(1,-1)``, and ``(2,1)`` | ||
|
|
||
| Variants after ``/`` use the same representation as their base quantity. | ||
|
|
||
| Energy-like scalars acquire an ``o3_mu`` component of size one for diagnostics. | ||
| Cartesian force components are reordered into the real spherical | ||
| :math:`\ell=1` basis described in :ref:`o3-conventions`. Stress diagnostics | ||
| cover the full matrix: the scalar trace, the antisymmetric (axial pseudovector, | ||
| :math:`\ell=1` with ``o3_sigma=-1``) part, and the symmetric-traceless sector. | ||
| For a symmetric stress the pseudovector sector is exactly zero; a model | ||
| producing a non-symmetric stress (before any downstream symmetrization) sees | ||
| its antisymmetric response in this sector. | ||
|
|
||
| Already-spherical outputs retain their ``o3_lambda`` and ``o3_sigma`` keys and | ||
| ``o3_mu`` components, and other semantic source keys are preserved. The wrapper | ||
| does not infer the physical meaning of a custom output from its shape; in | ||
| particular, a custom Cartesian :math:`3\times3` output is not treated as a | ||
| symmetric stress. | ||
|
|
||
| Character projections | ||
| --------------------- | ||
|
|
||
| Character projections analyze the direct response :math:`u(g;x)=f(gx)`, rather | ||
| than the back-transformed response used for averaging. For the character sector | ||
| :math:`\beta=(\lambda,\sigma)` with :math:`d_\beta=2\lambda+1`, the squared | ||
| projection norm is | ||
|
|
||
| .. math:: | ||
|
|
||
| B_\beta(u,x) | ||
| = d_\beta \iint_{\mathrm{O}(3)} | ||
| u(g_1;x)^\dagger | ||
| \chi_\beta(g_1g_2^{-1})u(g_2;x)\, | ||
| \mathrm{d}\mu(g_1)\,\mathrm{d}\mu(g_2). | ||
|
|
||
| Character results append ``chi_lambda`` and ``chi_sigma`` to the TensorMap | ||
| keys. These labels describe the O(3) dependence of the response over the | ||
| rotation orbit. They are distinct from ``o3_lambda`` and ``o3_sigma``, which | ||
| describe the target representation of the output itself. Target component axes | ||
| are retained; summing over them gives the complete component norm in the | ||
| equation above. | ||
|
|
||
| Quadrature | ||
| ---------- | ||
|
|
||
| The deterministic grid combines a Lebedev rule on the sphere, uniformly spaced | ||
| in-plane rotations, and both O(3) cosets: O(3) splits into two cosets of SO(3), | ||
| the proper rotations, and the improper ones (a rotation composed with | ||
| inversion). Its weights are normalized to sum to one. A general | ||
| machine-learning model need not be band-limited, so a finite grid is not | ||
| automatically exact. ``max_angular_momentum_grid`` controls the quadrature | ||
| resolution, not the representation: increase it until the averages, variances, | ||
| and character projections of interest converge. | ||
|
|
||
| Reference | ||
| --------- | ||
|
|
||
| .. py:currentmodule:: metatomic.torch | ||
|
|
||
| .. autoclass:: SymmetrizedModel | ||
| :members: | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| """Cartesian layout and spherical character for standard quantities.""" | ||
|
|
||
| from typing import Dict | ||
|
|
||
|
|
||
| def standard_quantity_categories() -> Dict[str, str]: | ||
| """Return the Cartesian layout and spherical character for standard quantities. | ||
|
|
||
| This is the single source of truth for which outputs and inputs are | ||
| decomposed; it mirrors ``KNOWN_QUANTITIES`` in | ||
| ``metatomic-torch/src/quantities.cpp``, minus ``feature``. Only the current | ||
| (singular) spellings appear here: normalize deprecated aliases with | ||
| :py:func:`current_quantity_name` before looking them up. | ||
|
|
||
| TorchScript cannot read a module-level dictionary from a compiled function, | ||
| so the table is built by this function and bound to | ||
| :py:data:`STANDARD_QUANTITY_CATEGORIES` for Python callers. | ||
| """ | ||
| return { | ||
| # scalars: l = 0 | ||
| "charge": "scalar", | ||
| "energy": "scalar", | ||
| "energy_ensemble": "scalar", | ||
| "energy_uncertainty": "scalar", | ||
| "mass": "scalar", | ||
| "spin_multiplicity": "scalar", | ||
| # Cartesian vectors: l = 1 | ||
| "heat_flux": "cartesian_vector", | ||
| "momentum": "cartesian_vector", | ||
| "non_conservative_force": "cartesian_vector", | ||
| "position": "cartesian_vector", | ||
| "velocity": "cartesian_vector", | ||
| # symmetric 3x3 matrices: l = 0 and l = 2 | ||
| "non_conservative_stress": "symmetric_matrix", | ||
| } | ||
|
|
||
|
|
||
| STANDARD_QUANTITY_CATEGORIES: Dict[str, str] = standard_quantity_categories() | ||
|
|
||
| #: maximum angular momentum carried by each category above | ||
| MAX_ANGULAR_MOMENTUM_PER_CATEGORY: Dict[str, int] = { | ||
| "scalar": 0, | ||
| "cartesian_vector": 1, | ||
| "symmetric_matrix": 2, | ||
| } | ||
|
|
||
|
|
||
| def new_quantity_names() -> Dict[str, str]: | ||
| """Return the mapping from deprecated quantity names to their current name. | ||
|
|
||
| TorchScript cannot read a module-level dictionary from a compiled function, | ||
| so the table is built by this function. | ||
| """ | ||
| return { | ||
| "features": "feature", | ||
| "non_conservative_forces": "non_conservative_force", | ||
| "positions": "position", | ||
| "momenta": "momentum", | ||
| "masses": "mass", | ||
| "velocities": "velocity", | ||
| "charges": "charge", | ||
| } | ||
|
|
||
|
|
||
| def deprecated_quantity_names() -> Dict[str, str]: | ||
| """Return the mapping from current quantity names to their deprecated name.""" | ||
| result: Dict[str, str] = {} | ||
| for deprecated, new in new_quantity_names().items(): | ||
| result[new] = deprecated | ||
| return result | ||
|
|
||
|
|
||
| def current_quantity_name(name: str) -> str: | ||
| """Replace a deprecated base quantity in ``name`` with its current name.""" | ||
| base = name.split("/")[0] | ||
| names = new_quantity_names() | ||
| if base in names: | ||
| return name.replace(base, names[base], 1) | ||
| return name | ||
|
|
||
|
|
||
| def deprecated_quantity_name(name: str) -> str: | ||
| """Replace a current base quantity in ``name`` with its deprecated name.""" | ||
| base = name.split("/")[0] | ||
| names = deprecated_quantity_names() | ||
| if base in names: | ||
| return name.replace(base, names[base], 1) | ||
| return name |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
This is not very clear, but I can rephrase when everything else is done here =)