Computing per-pair equivariant features

In a previous example, we computed the \(\lambda\)-SOAP equivariant descriptor. This tutorial focuses on the generalization of \(\lambda\)-SOAP to pairs of atoms rather than single centers. Per-pair equivariant features are useful as descriptors for two-center quantities such as the Hamiltonian matrix.

import chemfiles
from metatensor import Labels

from featomic import SphericalExpansion, SphericalExpansionByPair
from featomic.clebsch_gordan import EquivariantPowerSpectrumByPair

Let’s see how to compute the per-pair \(\lambda\)-SOAP descriptor using Featomic.

Read systems using Chemfiles. You can download the dataset for this example from our website.

with chemfiles.Trajectory("dataset.xyz") as trajectory:
    systems = [s for s in trajectory]

Featomic also handles systems read by ASE:

systems = ase.io.read("dataset.xyz", ":").

Next, define the hyperparameters for the spherical expansion:

HYPERPARAMETERS = {
    "cutoff": {
        "radius": 5.0,
        "smoothing": {"type": "ShiftedCosine", "width": 0.5},
    },
    "density": {
        "type": "Gaussian",
        "width": 0.3,
    },
    "basis": {
        "type": "TensorProduct",
        "max_angular": 2,
        "radial": {"type": "Gto", "max_radial": 2},
    },
}

Create a spherical expansion and a spherical expansion by pair calculator. The SphericalExpansion and SphericalExpansionByPair classes use the hyperparameters above. Then, wrap them with EquivariantPowerSpectrumByPair to compute the Clebsch-Gordan contraction for the per-pair \(\lambda\)-SOAP.

spex_calculator = SphericalExpansion(**HYPERPARAMETERS)
per_pair_spex_calculator = SphericalExpansionByPair(**HYPERPARAMETERS)
calculator = EquivariantPowerSpectrumByPair(spex_calculator, per_pair_spex_calculator)

Run the actual calculation

per_pair_power_spectrum = calculator.compute(systems, neighbors_to_properties=True)

The result is a TensorMap whose keys encode symmetry and the species of the atoms involved:

per_pair_power_spectrum.keys
metatensor.Labels

o3_lambdao3_sigmafirst_atom_typesecond_atom_type
0111
1111
2111
0116
1116
2116
0117
Show 121 more entries
1117
2117
0118
1118
2118
1-111
2-111
3111
1-116
2-116
3116
1-117
2-117
3117
1-118
2-118
3118
3-111
4111
3-116
4116
3-117
4117
3-118
4118
0161
1161
2161
0166
1166
2166
0167
1167
2167
0168
1168
2168
1-161
2-161
3161
1-166
2-166
3166
1-167
2-167
3167
1-168
2-168
3168
3-161
4161
3-166
4166
3-167
4167
3-168
4168
0171
1171
2171
0176
1176
2176
0177
1177
2177
0178
1178
2178
1-171
2-171
3171
1-176
2-176
3176
1-177
2-177
3177
1-178
2-178
3178
3-171
4171
3-176
4176
3-177
4177
3-178
4178
0181
1181
2181
0186
1186
2186
0187
1187
2187
0188
1188
2188
1-181
2-181
3181
1-186
2-186
3186
1-187
2-187
3187
1-188
2-188
3188
3-181
4181
3-186
4186
3-187
4187
3-188
4188


Often, you only need specific \(\lambda\) values. For example, if the target property is the Hamiltonian matrix on a minimal basis, you can restrict the output to \(\lambda\) values up to \(\lambda=2\) using the selected_keys parameter:

per_pair_power_spectrum_minimal_basis = calculator.compute(
    systems,
    neighbors_to_properties=True,
    selected_keys=Labels.range("o3_lambda", 3),
)
per_pair_power_spectrum_minimal_basis.keys
metatensor.Labels

o3_lambdao3_sigmafirst_atom_typesecond_atom_type
0111
1111
2111
0116
1116
2116
0117
Show 73 more entries
1117
2117
0118
1118
2118
1-111
2-111
1-116
2-116
1-117
2-117
1-118
2-118
0161
1161
2161
0166
1166
2166
0167
1167
2167
0168
1168
2168
1-161
2-161
1-166
2-166
1-167
2-167
1-168
2-168
0171
1171
2171
0176
1176
2176
0177
1177
2177
0178
1178
2178
1-171
2-171
1-176
2-176
1-177
2-177
1-178
2-178
0181
1181
2181
0186
1186
2186
0187
1187
2187
0188
1188
2188
1-181
2-181
1-186
2-186
1-187
2-187
1-188
2-188