| |
| import torch |
|
|
| from e3nn.o3._irreps import Irreps |
| from e3nn.nn._gate import Gate |
| from e3nn.nn._normact import NormActivation |
|
|
| from onescience.datapipes.materials.nequip import AtomicDataDict |
| from ._graph_mixin import GraphModuleMixin |
| from .interaction_block import InteractionBlock |
| from .nonlinearities import shifted_softplus |
| from .utils import tp_path_exists |
|
|
|
|
| from typing import Any, Dict, Optional, Callable |
|
|
|
|
| acts = { |
| "abs": torch.abs, |
| "tanh": torch.tanh, |
| "ssp": shifted_softplus, |
| "silu": torch.nn.functional.silu, |
| } |
|
|
|
|
| class ConvNetLayer(GraphModuleMixin, torch.nn.Module): |
| """ |
| Args: |
| |
| """ |
|
|
| resnet: bool |
|
|
| def __init__( |
| self, |
| irreps_in, |
| feature_irreps_hidden, |
| convolution=InteractionBlock, |
| convolution_kwargs: Optional[Dict[str, Any]] = None, |
| resnet: bool = False, |
| nonlinearity_type: str = "gate", |
| nonlinearity_scalars: Dict[int, Callable] = {"e": "silu", "o": "tanh"}, |
| nonlinearity_gates: Dict[int, Callable] = {"e": "silu", "o": "tanh"}, |
| ): |
| super().__init__() |
| |
| assert nonlinearity_type in ("gate", "norm") |
| |
| nonlinearity_scalars = { |
| 1: nonlinearity_scalars["e"], |
| -1: nonlinearity_scalars["o"], |
| } |
| nonlinearity_gates = { |
| 1: nonlinearity_gates["e"], |
| -1: nonlinearity_gates["o"], |
| } |
| |
| convolution_kwargs = ( |
| {} if convolution_kwargs is None else dict(convolution_kwargs) |
| ) |
|
|
| self.feature_irreps_hidden = Irreps(feature_irreps_hidden) |
| self.resnet = resnet |
|
|
| |
| self._init_irreps( |
| irreps_in=irreps_in, |
| required_irreps_in=[AtomicDataDict.NODE_FEATURES_KEY], |
| ) |
|
|
| edge_attr_irreps = self.irreps_in[AtomicDataDict.EDGE_ATTRS_KEY] |
| irreps_layer_out_prev = self.irreps_in[AtomicDataDict.NODE_FEATURES_KEY] |
|
|
| irreps_scalars = Irreps( |
| [ |
| (mul, ir) |
| for mul, ir in self.feature_irreps_hidden |
| if ir.l == 0 |
| and tp_path_exists(irreps_layer_out_prev, edge_attr_irreps, ir) |
| ] |
| ) |
|
|
| irreps_gated = Irreps( |
| [ |
| (mul, ir) |
| for mul, ir in self.feature_irreps_hidden |
| if ir.l > 0 |
| and tp_path_exists(irreps_layer_out_prev, edge_attr_irreps, ir) |
| ] |
| ) |
|
|
| irreps_layer_out = (irreps_scalars + irreps_gated).simplify() |
|
|
| if nonlinearity_type == "gate": |
| ir = ( |
| "0e" |
| if tp_path_exists(irreps_layer_out_prev, edge_attr_irreps, "0e") |
| else "0o" |
| ) |
| irreps_gates = Irreps([(mul, ir) for mul, _ in irreps_gated]) |
|
|
| |
| |
| equivariant_nonlin = Gate( |
| irreps_scalars=irreps_scalars, |
| act_scalars=[ |
| acts[nonlinearity_scalars[ir.p]] for _, ir in irreps_scalars |
| ], |
| irreps_gates=irreps_gates, |
| act_gates=[acts[nonlinearity_gates[ir.p]] for _, ir in irreps_gates], |
| irreps_gated=irreps_gated, |
| ) |
|
|
| conv_irreps_out = equivariant_nonlin.irreps_in.simplify() |
|
|
| else: |
| conv_irreps_out = irreps_layer_out.simplify() |
|
|
| equivariant_nonlin = NormActivation( |
| irreps_in=conv_irreps_out, |
| |
| scalar_nonlinearity=acts[nonlinearity_scalars[1]], |
| normalize=True, |
| epsilon=1e-8, |
| bias=False, |
| ) |
|
|
| self.equivariant_nonlin = equivariant_nonlin |
|
|
| |
| if irreps_layer_out == irreps_layer_out_prev and resnet: |
| |
| self.resnet = True |
| else: |
| self.resnet = False |
|
|
| |
|
|
| |
| convolution_kwargs.pop("irreps_in", None) |
| convolution_kwargs.pop("irreps_out", None) |
| self.conv = convolution( |
| irreps_in=self.irreps_in, |
| irreps_out=conv_irreps_out, |
| **convolution_kwargs, |
| ) |
|
|
| |
| |
| self.irreps_out.update(self.conv.irreps_out) |
| |
| self.irreps_out[AtomicDataDict.NODE_FEATURES_KEY] = ( |
| self.equivariant_nonlin.irreps_out |
| ) |
|
|
| def forward(self, data: AtomicDataDict.Type) -> AtomicDataDict.Type: |
| |
| old_x = data[AtomicDataDict.NODE_FEATURES_KEY] |
| |
| data = self.conv(data) |
| |
| data[AtomicDataDict.NODE_FEATURES_KEY] = self.equivariant_nonlin( |
| data[AtomicDataDict.NODE_FEATURES_KEY] |
| ) |
| |
| if self.resnet: |
| data[AtomicDataDict.NODE_FEATURES_KEY] = ( |
| old_x + data[AtomicDataDict.NODE_FEATURES_KEY] |
| ) |
| return data |
|
|