-
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
Open
MichelangeloDomina
wants to merge
11
commits into
metatensor:main
Choose a base branch
from
MichelangeloDomina:review/symmetrized-model-progressive
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
11 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 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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,145 @@ | ||
| .. _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. | ||
|
|
||
| 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)`` 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`. Models should provide | ||
| symmetric ``non_conservative_stress`` tensors. Stress diagnostics retain only | ||
| the scalar trace and symmetric-traceless sectors, silently discarding any | ||
| antisymmetric part. | ||
|
|
||
| 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,79 @@ | ||
| """ | ||
| Python-side mirror of ``metatomic-torch/src/quantities.cpp`` (``KNOWN_QUANTITIES`` | ||
| and the per-quantity checks), holding the metadata for standard quantities: their | ||
| category (Cartesian layout and spherical character) and their deprecated-name | ||
| aliases. | ||
|
|
||
| This module must not import anything from ``metatomic``, so that any other module | ||
| can import it without creating an import cycle. | ||
| """ | ||
|
|
||
| from typing import Dict | ||
|
|
||
|
|
||
| def standard_quantity_categories() -> Dict[str, str]: | ||
| """Return the Cartesian layout of every decomposable standard quantity. | ||
|
|
||
| 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: deprecated names are normalized before | ||
| they reach the code using this table. | ||
|
|
||
| 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 map 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 and bound to | ||
| :py:data:`NEW_QUANTITY_NAMES` for Python callers. | ||
| """ | ||
| return { | ||
| "features": "feature", | ||
| "non_conservative_forces": "non_conservative_force", | ||
| "positions": "position", | ||
| "momenta": "momentum", | ||
| "masses": "mass", | ||
| "velocities": "velocity", | ||
| "charges": "charge", | ||
| } | ||
|
|
||
|
|
||
| NEW_QUANTITY_NAMES: Dict[str, str] = _new_quantity_names() | ||
|
|
||
| #: mapping from current quantity names to the corresponding deprecated name | ||
| DEPRECATED_QUANTITY_NAMES: Dict[str, str] = { | ||
| new: deprecated for deprecated, new in NEW_QUANTITY_NAMES.items() | ||
| } |
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 =)