Delete nets
Browse files- nets/__init__.py +0 -1
- nets/equiformer_v2/Jd.pt +0 -3
- nets/equiformer_v2/activation.py +0 -193
- nets/equiformer_v2/drop.py +0 -142
- nets/equiformer_v2/edge_rot_mat.py +0 -51
- nets/equiformer_v2/equiformer_v2_oc20.py +0 -702
- nets/equiformer_v2/gaussian_rbf.py +0 -44
- nets/equiformer_v2/input_block.py +0 -126
- nets/equiformer_v2/layer_norm.py +0 -424
- nets/equiformer_v2/module_list.py +0 -10
- nets/equiformer_v2/radial_function.py +0 -30
- nets/equiformer_v2/so2_ops.py +0 -348
- nets/equiformer_v2/so3.py +0 -682
- nets/equiformer_v2/transformer_block.py +0 -689
- nets/equiformer_v2/wigner.py +0 -38
- nets/prediction_utils.py +0 -34
- nets/scatter_utils.py +0 -85
nets/__init__.py
DELETED
|
@@ -1 +0,0 @@
|
|
| 1 |
-
from .equiformer_v2.equiformer_v2_oc20 import EquiformerV2_OC20
|
|
|
|
|
|
nets/equiformer_v2/Jd.pt
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:b4059c45be246dcb6c49c545670b65c56550eb0c2e7a9c92b4b50a92d370dbe2
|
| 3 |
-
size 21697
|
|
|
|
|
|
|
|
|
|
|
|
nets/equiformer_v2/activation.py
DELETED
|
@@ -1,193 +0,0 @@
|
|
| 1 |
-
import torch
|
| 2 |
-
import torch.nn as nn
|
| 3 |
-
import torch.nn.functional as F
|
| 4 |
-
|
| 5 |
-
# from .linear import Linear_gaussian_init
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
class ScaledSiLU(nn.Module):
|
| 9 |
-
def __init__(self, inplace=False):
|
| 10 |
-
super(ScaledSiLU, self).__init__()
|
| 11 |
-
self.inplace = inplace
|
| 12 |
-
self.scale_factor = 1.6791767923989418
|
| 13 |
-
|
| 14 |
-
def forward(self, inputs):
|
| 15 |
-
return F.silu(inputs, inplace=self.inplace) * self.scale_factor
|
| 16 |
-
|
| 17 |
-
def extra_repr(self):
|
| 18 |
-
str = "scale_factor={}".format(self.scale_factor)
|
| 19 |
-
if self.inplace:
|
| 20 |
-
str = str + ", inplace=True"
|
| 21 |
-
return str
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
# Reference: https://github.com/facebookresearch/llama/blob/main/llama/model.py#L175
|
| 25 |
-
class ScaledSwiGLU(nn.Module):
|
| 26 |
-
def __init__(self, in_channels, out_channels, bias=True):
|
| 27 |
-
super(ScaledSwiGLU, self).__init__()
|
| 28 |
-
self.in_channels = in_channels
|
| 29 |
-
self.out_channels = out_channels
|
| 30 |
-
self.w = torch.nn.Linear(in_channels, 2 * out_channels, bias=bias)
|
| 31 |
-
self.act = ScaledSiLU()
|
| 32 |
-
|
| 33 |
-
def forward(self, inputs):
|
| 34 |
-
w = self.w(inputs)
|
| 35 |
-
w_1 = w.narrow(-1, 0, self.out_channels)
|
| 36 |
-
w_1 = self.act(w_1)
|
| 37 |
-
w_2 = w.narrow(-1, self.out_channels, self.out_channels)
|
| 38 |
-
out = w_1 * w_2
|
| 39 |
-
return out
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
# Reference: https://github.com/facebookresearch/llama/blob/main/llama/model.py#L175
|
| 43 |
-
class SwiGLU(nn.Module):
|
| 44 |
-
def __init__(self, in_channels, out_channels, bias=True):
|
| 45 |
-
super(SwiGLU, self).__init__()
|
| 46 |
-
self.in_channels = in_channels
|
| 47 |
-
self.out_channels = out_channels
|
| 48 |
-
self.w = torch.nn.Linear(in_channels, 2 * out_channels, bias=bias)
|
| 49 |
-
self.act = torch.nn.SiLU()
|
| 50 |
-
|
| 51 |
-
def forward(self, inputs):
|
| 52 |
-
w = self.w(inputs)
|
| 53 |
-
w_1 = w.narrow(-1, 0, self.out_channels)
|
| 54 |
-
w_1 = self.act(w_1)
|
| 55 |
-
w_2 = w.narrow(-1, self.out_channels, self.out_channels)
|
| 56 |
-
out = w_1 * w_2
|
| 57 |
-
return out
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
class SmoothLeakyReLU(torch.nn.Module):
|
| 61 |
-
def __init__(self, negative_slope=0.2):
|
| 62 |
-
super().__init__()
|
| 63 |
-
self.alpha = negative_slope
|
| 64 |
-
|
| 65 |
-
def forward(self, x):
|
| 66 |
-
x1 = ((1 + self.alpha) / 2) * x
|
| 67 |
-
x2 = ((1 - self.alpha) / 2) * x * (2 * torch.sigmoid(x) - 1)
|
| 68 |
-
return x1 + x2
|
| 69 |
-
|
| 70 |
-
def extra_repr(self):
|
| 71 |
-
return "negative_slope={}".format(self.alpha)
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
class ScaledSmoothLeakyReLU(torch.nn.Module):
|
| 75 |
-
def __init__(self):
|
| 76 |
-
super().__init__()
|
| 77 |
-
self.act = SmoothLeakyReLU(0.2)
|
| 78 |
-
self.scale_factor = 1.531320475574866
|
| 79 |
-
|
| 80 |
-
def forward(self, x):
|
| 81 |
-
return self.act(x) * self.scale_factor
|
| 82 |
-
|
| 83 |
-
def extra_repr(self):
|
| 84 |
-
return "negative_slope={}, scale_factor={}".format(
|
| 85 |
-
self.act.alpha, self.scale_factor
|
| 86 |
-
)
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
class ScaledSigmoid(torch.nn.Module):
|
| 90 |
-
def __init__(self):
|
| 91 |
-
super().__init__()
|
| 92 |
-
self.scale_factor = 1.8467055342154763
|
| 93 |
-
|
| 94 |
-
def forward(self, x):
|
| 95 |
-
return torch.sigmoid(x) * self.scale_factor
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
class GateActivation(torch.nn.Module):
|
| 99 |
-
def __init__(self, lmax, mmax, num_channels):
|
| 100 |
-
super().__init__()
|
| 101 |
-
|
| 102 |
-
self.lmax = lmax
|
| 103 |
-
self.mmax = mmax
|
| 104 |
-
self.num_channels = num_channels
|
| 105 |
-
|
| 106 |
-
# compute `expand_index` based on `lmax` and `mmax`
|
| 107 |
-
num_components = 0
|
| 108 |
-
for l in range(1, self.lmax + 1):
|
| 109 |
-
num_m_components = min((2 * l + 1), (2 * self.mmax + 1))
|
| 110 |
-
num_components = num_components + num_m_components
|
| 111 |
-
expand_index = torch.zeros([num_components]).long()
|
| 112 |
-
start_idx = 0
|
| 113 |
-
for l in range(1, self.lmax + 1):
|
| 114 |
-
length = min((2 * l + 1), (2 * self.mmax + 1))
|
| 115 |
-
expand_index[start_idx : (start_idx + length)] = l - 1
|
| 116 |
-
start_idx = start_idx + length
|
| 117 |
-
self.register_buffer("expand_index", expand_index)
|
| 118 |
-
|
| 119 |
-
self.scalar_act = (
|
| 120 |
-
torch.nn.SiLU()
|
| 121 |
-
) # SwiGLU(self.num_channels, self.num_channels) # #
|
| 122 |
-
self.gate_act = torch.nn.Sigmoid() # torch.nn.SiLU() # #
|
| 123 |
-
|
| 124 |
-
def forward(self, gating_scalars, input_tensors):
|
| 125 |
-
"""
|
| 126 |
-
`gating_scalars`: shape [N, lmax * num_channels]
|
| 127 |
-
`input_tensors`: shape [N, (lmax + 1) ** 2, num_channels]
|
| 128 |
-
"""
|
| 129 |
-
|
| 130 |
-
gating_scalars = self.gate_act(gating_scalars)
|
| 131 |
-
gating_scalars = gating_scalars.reshape(
|
| 132 |
-
gating_scalars.shape[0], self.lmax, self.num_channels
|
| 133 |
-
)
|
| 134 |
-
gating_scalars = torch.index_select(
|
| 135 |
-
gating_scalars, dim=1, index=self.expand_index
|
| 136 |
-
)
|
| 137 |
-
|
| 138 |
-
input_tensors_scalars = input_tensors.narrow(1, 0, 1)
|
| 139 |
-
input_tensors_scalars = self.scalar_act(input_tensors_scalars)
|
| 140 |
-
|
| 141 |
-
input_tensors_vectors = input_tensors.narrow(1, 1, input_tensors.shape[1] - 1)
|
| 142 |
-
input_tensors_vectors = input_tensors_vectors * gating_scalars
|
| 143 |
-
|
| 144 |
-
output_tensors = torch.cat(
|
| 145 |
-
(input_tensors_scalars, input_tensors_vectors), dim=1
|
| 146 |
-
)
|
| 147 |
-
|
| 148 |
-
return output_tensors
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
class S2Activation(torch.nn.Module):
|
| 152 |
-
"""
|
| 153 |
-
Assume we only have one resolution
|
| 154 |
-
"""
|
| 155 |
-
|
| 156 |
-
def __init__(self, lmax, mmax):
|
| 157 |
-
super().__init__()
|
| 158 |
-
self.lmax = lmax
|
| 159 |
-
self.mmax = mmax
|
| 160 |
-
self.act = torch.nn.SiLU()
|
| 161 |
-
|
| 162 |
-
def forward(self, inputs, SO3_grid):
|
| 163 |
-
to_grid_mat = SO3_grid[self.lmax][self.mmax].get_to_grid_mat(
|
| 164 |
-
device=None
|
| 165 |
-
) # `device` is not used
|
| 166 |
-
from_grid_mat = SO3_grid[self.lmax][self.mmax].get_from_grid_mat(device=None)
|
| 167 |
-
x_grid = torch.einsum("bai, zic -> zbac", to_grid_mat, inputs)
|
| 168 |
-
x_grid = self.act(x_grid)
|
| 169 |
-
outputs = torch.einsum("bai, zbac -> zic", from_grid_mat, x_grid)
|
| 170 |
-
return outputs
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
class SeparableS2Activation(torch.nn.Module):
|
| 174 |
-
def __init__(self, lmax, mmax):
|
| 175 |
-
super().__init__()
|
| 176 |
-
|
| 177 |
-
self.lmax = lmax
|
| 178 |
-
self.mmax = mmax
|
| 179 |
-
|
| 180 |
-
self.scalar_act = torch.nn.SiLU()
|
| 181 |
-
self.s2_act = S2Activation(self.lmax, self.mmax)
|
| 182 |
-
|
| 183 |
-
def forward(self, input_scalars, input_tensors, SO3_grid):
|
| 184 |
-
output_scalars = self.scalar_act(input_scalars)
|
| 185 |
-
output_scalars = output_scalars.reshape(
|
| 186 |
-
output_scalars.shape[0], 1, output_scalars.shape[-1]
|
| 187 |
-
)
|
| 188 |
-
output_tensors = self.s2_act(input_tensors, SO3_grid)
|
| 189 |
-
outputs = torch.cat(
|
| 190 |
-
(output_scalars, output_tensors.narrow(1, 1, output_tensors.shape[1] - 1)),
|
| 191 |
-
dim=1,
|
| 192 |
-
)
|
| 193 |
-
return outputs
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
nets/equiformer_v2/drop.py
DELETED
|
@@ -1,142 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
Add `extra_repr` into DropPath implemented by timm
|
| 3 |
-
for displaying more info.
|
| 4 |
-
"""
|
| 5 |
-
|
| 6 |
-
import torch
|
| 7 |
-
import torch.nn as nn
|
| 8 |
-
from e3nn import o3
|
| 9 |
-
import torch.nn.functional as F
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
def drop_path(x, drop_prob: float = 0.0, training: bool = False):
|
| 13 |
-
"""Drop paths (Stochastic Depth) per sample (when applied in main path of residual blocks).
|
| 14 |
-
This is the same as the DropConnect impl I created for EfficientNet, etc networks, however,
|
| 15 |
-
the original name is misleading as 'Drop Connect' is a different form of dropout in a separate paper...
|
| 16 |
-
See discussion: https://github.com/tensorflow/tpu/issues/494#issuecomment-532968956 ... I've opted for
|
| 17 |
-
changing the layer and argument names to 'drop path' rather than mix DropConnect as a layer name and use
|
| 18 |
-
'survival rate' as the argument.
|
| 19 |
-
"""
|
| 20 |
-
if drop_prob == 0.0 or not training:
|
| 21 |
-
return x
|
| 22 |
-
keep_prob = 1 - drop_prob
|
| 23 |
-
shape = (x.shape[0],) + (1,) * (
|
| 24 |
-
x.ndim - 1
|
| 25 |
-
) # work with diff dim tensors, not just 2D ConvNets
|
| 26 |
-
random_tensor = keep_prob + torch.rand(shape, dtype=x.dtype, device=x.device)
|
| 27 |
-
random_tensor.floor_() # binarize
|
| 28 |
-
output = x.div(keep_prob) * random_tensor
|
| 29 |
-
return output
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
class DropPath(nn.Module):
|
| 33 |
-
"""Drop paths (Stochastic Depth) per sample (when applied in main path of residual blocks)."""
|
| 34 |
-
|
| 35 |
-
def __init__(self, drop_prob=None):
|
| 36 |
-
super(DropPath, self).__init__()
|
| 37 |
-
self.drop_prob = drop_prob
|
| 38 |
-
|
| 39 |
-
def forward(self, x):
|
| 40 |
-
return drop_path(x, self.drop_prob, self.training)
|
| 41 |
-
|
| 42 |
-
def extra_repr(self):
|
| 43 |
-
return "drop_prob={}".format(self.drop_prob)
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
class GraphDropPath(nn.Module):
|
| 47 |
-
"""
|
| 48 |
-
Consider batch for graph data when dropping paths.
|
| 49 |
-
"""
|
| 50 |
-
|
| 51 |
-
def __init__(self, drop_prob=None):
|
| 52 |
-
super(GraphDropPath, self).__init__()
|
| 53 |
-
self.drop_prob = drop_prob
|
| 54 |
-
|
| 55 |
-
def forward(self, x, batch):
|
| 56 |
-
batch_size = batch.max() + 1
|
| 57 |
-
shape = (batch_size,) + (1,) * (
|
| 58 |
-
x.ndim - 1
|
| 59 |
-
) # work with diff dim tensors, not just 2D ConvNets
|
| 60 |
-
ones = torch.ones(shape, dtype=x.dtype, device=x.device)
|
| 61 |
-
drop = drop_path(ones, self.drop_prob, self.training)
|
| 62 |
-
out = x * drop[batch]
|
| 63 |
-
return out
|
| 64 |
-
|
| 65 |
-
def extra_repr(self):
|
| 66 |
-
return "drop_prob={}".format(self.drop_prob)
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
class EquivariantDropout(nn.Module):
|
| 70 |
-
def __init__(self, irreps, drop_prob):
|
| 71 |
-
super(EquivariantDropout, self).__init__()
|
| 72 |
-
self.irreps = irreps
|
| 73 |
-
self.num_irreps = irreps.num_irreps
|
| 74 |
-
self.drop_prob = drop_prob
|
| 75 |
-
self.drop = torch.nn.Dropout(drop_prob, True)
|
| 76 |
-
self.mul = o3.ElementwiseTensorProduct(
|
| 77 |
-
irreps, o3.Irreps("{}x0e".format(self.num_irreps))
|
| 78 |
-
)
|
| 79 |
-
|
| 80 |
-
def forward(self, x):
|
| 81 |
-
if not self.training or self.drop_prob == 0.0:
|
| 82 |
-
return x
|
| 83 |
-
shape = (x.shape[0], self.num_irreps)
|
| 84 |
-
mask = torch.ones(shape, dtype=x.dtype, device=x.device)
|
| 85 |
-
mask = self.drop(mask)
|
| 86 |
-
out = self.mul(x, mask)
|
| 87 |
-
return out
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
class EquivariantScalarsDropout(nn.Module):
|
| 91 |
-
def __init__(self, irreps, drop_prob):
|
| 92 |
-
super(EquivariantScalarsDropout, self).__init__()
|
| 93 |
-
self.irreps = irreps
|
| 94 |
-
self.drop_prob = drop_prob
|
| 95 |
-
|
| 96 |
-
def forward(self, x):
|
| 97 |
-
if not self.training or self.drop_prob == 0.0:
|
| 98 |
-
return x
|
| 99 |
-
out = []
|
| 100 |
-
start_idx = 0
|
| 101 |
-
for mul, ir in self.irreps:
|
| 102 |
-
temp = x.narrow(-1, start_idx, mul * ir.dim)
|
| 103 |
-
start_idx += mul * ir.dim
|
| 104 |
-
if ir.is_scalar():
|
| 105 |
-
temp = F.dropout(temp, p=self.drop_prob, training=self.training)
|
| 106 |
-
out.append(temp)
|
| 107 |
-
out = torch.cat(out, dim=-1)
|
| 108 |
-
return out
|
| 109 |
-
|
| 110 |
-
def extra_repr(self):
|
| 111 |
-
return "irreps={}, drop_prob={}".format(self.irreps, self.drop_prob)
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
class EquivariantDropoutArraySphericalHarmonics(nn.Module):
|
| 115 |
-
def __init__(self, drop_prob, drop_graph=False):
|
| 116 |
-
super(EquivariantDropoutArraySphericalHarmonics, self).__init__()
|
| 117 |
-
self.drop_prob = drop_prob
|
| 118 |
-
self.drop = torch.nn.Dropout(drop_prob, True)
|
| 119 |
-
self.drop_graph = drop_graph
|
| 120 |
-
|
| 121 |
-
def forward(self, x, batch=None):
|
| 122 |
-
if not self.training or self.drop_prob == 0.0:
|
| 123 |
-
return x
|
| 124 |
-
assert len(x.shape) == 3
|
| 125 |
-
|
| 126 |
-
if self.drop_graph:
|
| 127 |
-
assert batch is not None
|
| 128 |
-
batch_size = batch.max() + 1
|
| 129 |
-
shape = (batch_size, 1, x.shape[2])
|
| 130 |
-
mask = torch.ones(shape, dtype=x.dtype, device=x.device)
|
| 131 |
-
mask = self.drop(mask)
|
| 132 |
-
out = x * mask[batch]
|
| 133 |
-
else:
|
| 134 |
-
shape = (x.shape[0], 1, x.shape[2])
|
| 135 |
-
mask = torch.ones(shape, dtype=x.dtype, device=x.device)
|
| 136 |
-
mask = self.drop(mask)
|
| 137 |
-
out = x * mask
|
| 138 |
-
|
| 139 |
-
return out
|
| 140 |
-
|
| 141 |
-
def extra_repr(self):
|
| 142 |
-
return "drop_prob={}, drop_graph={}".format(self.drop_prob, self.drop_graph)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
nets/equiformer_v2/edge_rot_mat.py
DELETED
|
@@ -1,51 +0,0 @@
|
|
| 1 |
-
import torch
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
def init_edge_rot_mat(edge_distance_vec):
|
| 5 |
-
edge_vec_0 = edge_distance_vec
|
| 6 |
-
edge_vec_0_distance = torch.sqrt(torch.sum(edge_vec_0**2, dim=1))
|
| 7 |
-
|
| 8 |
-
# Make sure the atoms are far enough apart
|
| 9 |
-
# assert torch.min(edge_vec_0_distance) < 0.0001
|
| 10 |
-
if torch.min(edge_vec_0_distance) < 0.0001:
|
| 11 |
-
print("Error edge_vec_0_distance: {}".format(torch.min(edge_vec_0_distance)))
|
| 12 |
-
|
| 13 |
-
norm_x = edge_vec_0 / (edge_vec_0_distance.view(-1, 1))
|
| 14 |
-
|
| 15 |
-
edge_vec_2 = torch.rand_like(edge_vec_0) - 0.5
|
| 16 |
-
edge_vec_2 = edge_vec_2 / (torch.sqrt(torch.sum(edge_vec_2**2, dim=1)).view(-1, 1))
|
| 17 |
-
# Create two rotated copys of the random vectors in case the random vector is aligned with norm_x
|
| 18 |
-
# With two 90 degree rotated vectors, at least one should not be aligned with norm_x
|
| 19 |
-
edge_vec_2b = edge_vec_2.clone()
|
| 20 |
-
edge_vec_2b[:, 0] = -edge_vec_2[:, 1]
|
| 21 |
-
edge_vec_2b[:, 1] = edge_vec_2[:, 0]
|
| 22 |
-
edge_vec_2c = edge_vec_2.clone()
|
| 23 |
-
edge_vec_2c[:, 1] = -edge_vec_2[:, 2]
|
| 24 |
-
edge_vec_2c[:, 2] = edge_vec_2[:, 1]
|
| 25 |
-
vec_dot_b = torch.abs(torch.sum(edge_vec_2b * norm_x, dim=1)).view(-1, 1)
|
| 26 |
-
vec_dot_c = torch.abs(torch.sum(edge_vec_2c * norm_x, dim=1)).view(-1, 1)
|
| 27 |
-
|
| 28 |
-
vec_dot = torch.abs(torch.sum(edge_vec_2 * norm_x, dim=1)).view(-1, 1)
|
| 29 |
-
edge_vec_2 = torch.where(torch.gt(vec_dot, vec_dot_b), edge_vec_2b, edge_vec_2)
|
| 30 |
-
vec_dot = torch.abs(torch.sum(edge_vec_2 * norm_x, dim=1)).view(-1, 1)
|
| 31 |
-
edge_vec_2 = torch.where(torch.gt(vec_dot, vec_dot_c), edge_vec_2c, edge_vec_2)
|
| 32 |
-
|
| 33 |
-
vec_dot = torch.abs(torch.sum(edge_vec_2 * norm_x, dim=1))
|
| 34 |
-
# Check the vectors aren't aligned
|
| 35 |
-
assert torch.max(vec_dot) < 0.99
|
| 36 |
-
|
| 37 |
-
norm_z = torch.cross(norm_x, edge_vec_2, dim=1)
|
| 38 |
-
norm_z = norm_z / (torch.sqrt(torch.sum(norm_z**2, dim=1, keepdim=True)))
|
| 39 |
-
norm_z = norm_z / (torch.sqrt(torch.sum(norm_z**2, dim=1)).view(-1, 1))
|
| 40 |
-
norm_y = torch.cross(norm_x, norm_z, dim=1)
|
| 41 |
-
norm_y = norm_y / (torch.sqrt(torch.sum(norm_y**2, dim=1, keepdim=True)))
|
| 42 |
-
|
| 43 |
-
# Construct the 3D rotation matrix
|
| 44 |
-
norm_x = norm_x.view(-1, 3, 1)
|
| 45 |
-
norm_y = -norm_y.view(-1, 3, 1)
|
| 46 |
-
norm_z = norm_z.view(-1, 3, 1)
|
| 47 |
-
|
| 48 |
-
edge_rot_mat_inv = torch.cat([norm_z, norm_x, norm_y], dim=2)
|
| 49 |
-
edge_rot_mat = torch.transpose(edge_rot_mat_inv, 1, 2)
|
| 50 |
-
|
| 51 |
-
return edge_rot_mat.detach()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
nets/equiformer_v2/equiformer_v2_oc20.py
DELETED
|
@@ -1,702 +0,0 @@
|
|
| 1 |
-
import logging
|
| 2 |
-
import time
|
| 3 |
-
import math
|
| 4 |
-
import numpy as np
|
| 5 |
-
import torch
|
| 6 |
-
import torch.nn as nn
|
| 7 |
-
from pyexpat.model import XML_CQUANT_OPT
|
| 8 |
-
from torch.autograd import grad
|
| 9 |
-
from ocpmodels.common.registry import registry
|
| 10 |
-
from ocpmodels.common.utils import conditional_grad
|
| 11 |
-
from ocpmodels.models.base import BaseModel
|
| 12 |
-
from ocpmodels.models.scn.sampling import CalcSpherePoints
|
| 13 |
-
from ocpmodels.models.scn.smearing import (
|
| 14 |
-
GaussianSmearing,
|
| 15 |
-
LinearSigmoidSmearing,
|
| 16 |
-
SigmoidSmearing,
|
| 17 |
-
SiLUSmearing,
|
| 18 |
-
)
|
| 19 |
-
from torch_geometric.nn import radius_graph
|
| 20 |
-
|
| 21 |
-
try:
|
| 22 |
-
from e3nn import o3
|
| 23 |
-
except ImportError:
|
| 24 |
-
pass
|
| 25 |
-
|
| 26 |
-
from .gaussian_rbf import GaussianRadialBasisLayer
|
| 27 |
-
from torch.nn import Linear
|
| 28 |
-
from .edge_rot_mat import init_edge_rot_mat
|
| 29 |
-
from .so3 import (
|
| 30 |
-
CoefficientMappingModule,
|
| 31 |
-
SO3_Embedding,
|
| 32 |
-
SO3_Grid,
|
| 33 |
-
SO3_Rotation,
|
| 34 |
-
SO3_LinearV2,
|
| 35 |
-
)
|
| 36 |
-
from .module_list import ModuleListInfo
|
| 37 |
-
from .so2_ops import SO2_Convolution
|
| 38 |
-
from .radial_function import RadialFunction
|
| 39 |
-
from .layer_norm import (
|
| 40 |
-
EquivariantLayerNormArray,
|
| 41 |
-
EquivariantLayerNormArraySphericalHarmonics,
|
| 42 |
-
EquivariantRMSNormArraySphericalHarmonics,
|
| 43 |
-
EquivariantRMSNormArraySphericalHarmonicsV2,
|
| 44 |
-
get_normalization_layer,
|
| 45 |
-
)
|
| 46 |
-
from .transformer_block import (
|
| 47 |
-
SO2EquivariantGraphAttention,
|
| 48 |
-
FeedForwardNetwork,
|
| 49 |
-
TransBlockV2,
|
| 50 |
-
)
|
| 51 |
-
from .input_block import EdgeDegreeEmbedding
|
| 52 |
-
from torch import tensor, float32
|
| 53 |
-
|
| 54 |
-
# Statistics of IS2RE 100K
|
| 55 |
-
_AVG_NUM_NODES = 77.81317
|
| 56 |
-
_AVG_DEGREE = 23.395238876342773 # IS2RE: 100k, max_radius = 5, max_neighbors = 100
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
def get_scalar_from_embedding(embedding, data):
|
| 60 |
-
embedding = embedding.embedding.narrow(1, 0, 1)
|
| 61 |
-
scalars = torch.zeros(
|
| 62 |
-
len(data.natoms), device=embedding.device, dtype=embedding.dtype
|
| 63 |
-
)
|
| 64 |
-
scalars.index_add_(0, data.batch, embedding.view(-1))
|
| 65 |
-
return scalars / _AVG_NUM_NODES
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
@registry.register_model("equiformer_v2")
|
| 69 |
-
class EquiformerV2_OC20(BaseModel):
|
| 70 |
-
"""
|
| 71 |
-
Equiformer with graph attention built upon SO(2) convolution and feedforward network built upon S2 activation
|
| 72 |
-
|
| 73 |
-
Args:
|
| 74 |
-
use_pbc (bool): Use periodic boundary conditions
|
| 75 |
-
regress_forces (bool): Compute forces
|
| 76 |
-
otf_graph (bool): Compute graph On The Fly (OTF)
|
| 77 |
-
max_neighbors (int): Maximum number of neighbors per atom
|
| 78 |
-
max_radius (float): Maximum distance between nieghboring atoms in Angstroms
|
| 79 |
-
max_num_elements (int): Maximum atomic number
|
| 80 |
-
|
| 81 |
-
num_layers (int): Number of layers in the GNN
|
| 82 |
-
sphere_channels (int): Number of spherical channels (one set per resolution)
|
| 83 |
-
attn_hidden_channels (int): Number of hidden channels used during SO(2) graph attention
|
| 84 |
-
num_heads (int): Number of attention heads
|
| 85 |
-
attn_alpha_head (int): Number of channels for alpha vector in each attention head
|
| 86 |
-
attn_value_head (int): Number of channels for value vector in each attention head
|
| 87 |
-
ffn_hidden_channels (int): Number of hidden channels used during feedforward network
|
| 88 |
-
norm_type (str): Type of normalization layer (['layer_norm', 'layer_norm_sh', 'rms_norm_sh'])
|
| 89 |
-
|
| 90 |
-
lmax_list (int): List of maximum degree of the spherical harmonics (1 to 10)
|
| 91 |
-
mmax_list (int): List of maximum order of the spherical harmonics (0 to lmax)
|
| 92 |
-
grid_resolution (int): Resolution of SO3_Grid
|
| 93 |
-
|
| 94 |
-
num_sphere_samples (int): Number of samples used to approximate the integration of the sphere in the output blocks
|
| 95 |
-
|
| 96 |
-
edge_channels (int): Number of channels for the edge invariant features
|
| 97 |
-
use_atom_edge_embedding (bool): Whether to use atomic embedding along with relative distance for edge scalar features
|
| 98 |
-
share_atom_edge_embedding (bool): Whether to share `atom_edge_embedding` across all blocks
|
| 99 |
-
use_m_share_rad (bool): Whether all m components within a type-L vector of one channel share radial function weights
|
| 100 |
-
distance_function ("gaussian", "sigmoid", "linearsigmoid", "silu"): Basis function used for distances
|
| 101 |
-
|
| 102 |
-
attn_activation (str): Type of activation function for SO(2) graph attention
|
| 103 |
-
use_s2_act_attn (bool): Whether to use attention after S2 activation. Otherwise, use the same attention as Equiformer
|
| 104 |
-
use_attn_renorm (bool): Whether to re-normalize attention weights
|
| 105 |
-
ffn_activation (str): Type of activation function for feedforward network
|
| 106 |
-
use_gate_act (bool): If `True`, use gate activation. Otherwise, use S2 activation
|
| 107 |
-
use_grid_mlp (bool): If `True`, use projecting to grids and performing MLPs for FFNs.
|
| 108 |
-
use_sep_s2_act (bool): If `True`, use separable S2 activation when `use_gate_act` is False.
|
| 109 |
-
|
| 110 |
-
alpha_drop (float): Dropout rate for attention weights
|
| 111 |
-
drop_path_rate (float): Drop path rate
|
| 112 |
-
proj_drop (float): Dropout rate for outputs of attention and FFN in Transformer blocks
|
| 113 |
-
|
| 114 |
-
weight_init (str): ['normal', 'uniform'] initialization of weights of linear layers except those in radial functions
|
| 115 |
-
"""
|
| 116 |
-
|
| 117 |
-
def __init__(
|
| 118 |
-
self,
|
| 119 |
-
# num_atoms, # not used
|
| 120 |
-
# bond_feat_dim, # not used
|
| 121 |
-
# num_targets, # not used
|
| 122 |
-
use_pbc=True,
|
| 123 |
-
regress_forces=True,
|
| 124 |
-
otf_graph=True,
|
| 125 |
-
max_neighbors=500,
|
| 126 |
-
max_radius=5.0,
|
| 127 |
-
max_num_elements=90,
|
| 128 |
-
num_layers=12,
|
| 129 |
-
sphere_channels=128,
|
| 130 |
-
attn_hidden_channels=128,
|
| 131 |
-
num_heads=8,
|
| 132 |
-
attn_alpha_channels=32,
|
| 133 |
-
attn_value_channels=16,
|
| 134 |
-
ffn_hidden_channels=512,
|
| 135 |
-
norm_type="rms_norm_sh",
|
| 136 |
-
lmax_list=[6],
|
| 137 |
-
mmax_list=[2],
|
| 138 |
-
grid_resolution=None,
|
| 139 |
-
num_sphere_samples=128,
|
| 140 |
-
edge_channels=128,
|
| 141 |
-
use_atom_edge_embedding=True,
|
| 142 |
-
share_atom_edge_embedding=False,
|
| 143 |
-
use_m_share_rad=False,
|
| 144 |
-
distance_function="gaussian",
|
| 145 |
-
num_distance_basis=512,
|
| 146 |
-
attn_activation="scaled_silu",
|
| 147 |
-
use_s2_act_attn=False,
|
| 148 |
-
use_attn_renorm=True,
|
| 149 |
-
ffn_activation="scaled_silu",
|
| 150 |
-
use_gate_act=False,
|
| 151 |
-
use_grid_mlp=False,
|
| 152 |
-
use_sep_s2_act=True,
|
| 153 |
-
alpha_drop=0.1,
|
| 154 |
-
drop_path_rate=0.05,
|
| 155 |
-
proj_drop=0.0,
|
| 156 |
-
weight_init="normal",
|
| 157 |
-
# added for eigenvalue/eigenvector prediction
|
| 158 |
-
do_eigvec_1=False,
|
| 159 |
-
do_eigvec_2=False,
|
| 160 |
-
do_eigval_1=False,
|
| 161 |
-
do_eigval_2=False,
|
| 162 |
-
**kwargs,
|
| 163 |
-
):
|
| 164 |
-
super().__init__()
|
| 165 |
-
print(f"EquiformerV2_OC20: ignoring kwargs: {kwargs}")
|
| 166 |
-
|
| 167 |
-
self.use_pbc = use_pbc
|
| 168 |
-
self.regress_forces = regress_forces
|
| 169 |
-
self.otf_graph = otf_graph
|
| 170 |
-
self.max_neighbors = max_neighbors
|
| 171 |
-
self.max_radius = max_radius
|
| 172 |
-
self.cutoff = max_radius
|
| 173 |
-
self.max_num_elements = max_num_elements
|
| 174 |
-
|
| 175 |
-
self.num_layers = num_layers
|
| 176 |
-
self.sphere_channels = sphere_channels
|
| 177 |
-
self.attn_hidden_channels = attn_hidden_channels
|
| 178 |
-
self.num_heads = num_heads
|
| 179 |
-
self.attn_alpha_channels = attn_alpha_channels
|
| 180 |
-
self.attn_value_channels = attn_value_channels
|
| 181 |
-
self.ffn_hidden_channels = ffn_hidden_channels
|
| 182 |
-
self.norm_type = norm_type
|
| 183 |
-
|
| 184 |
-
self.lmax_list = lmax_list
|
| 185 |
-
self.mmax_list = mmax_list
|
| 186 |
-
self.grid_resolution = grid_resolution
|
| 187 |
-
|
| 188 |
-
self.num_sphere_samples = num_sphere_samples
|
| 189 |
-
|
| 190 |
-
self.edge_channels = edge_channels
|
| 191 |
-
self.use_atom_edge_embedding = use_atom_edge_embedding
|
| 192 |
-
self.share_atom_edge_embedding = share_atom_edge_embedding
|
| 193 |
-
if self.share_atom_edge_embedding:
|
| 194 |
-
assert self.use_atom_edge_embedding
|
| 195 |
-
self.block_use_atom_edge_embedding = False
|
| 196 |
-
else:
|
| 197 |
-
self.block_use_atom_edge_embedding = self.use_atom_edge_embedding
|
| 198 |
-
self.use_m_share_rad = use_m_share_rad
|
| 199 |
-
self.distance_function = distance_function
|
| 200 |
-
self.num_distance_basis = num_distance_basis
|
| 201 |
-
|
| 202 |
-
self.attn_activation = attn_activation
|
| 203 |
-
self.use_s2_act_attn = use_s2_act_attn
|
| 204 |
-
self.use_attn_renorm = use_attn_renorm
|
| 205 |
-
self.ffn_activation = ffn_activation
|
| 206 |
-
self.use_gate_act = use_gate_act
|
| 207 |
-
self.use_grid_mlp = use_grid_mlp
|
| 208 |
-
self.use_sep_s2_act = use_sep_s2_act
|
| 209 |
-
|
| 210 |
-
self.alpha_drop = alpha_drop
|
| 211 |
-
self.drop_path_rate = drop_path_rate
|
| 212 |
-
self.proj_drop = proj_drop
|
| 213 |
-
|
| 214 |
-
self.weight_init = weight_init
|
| 215 |
-
assert self.weight_init in ["normal", "uniform"]
|
| 216 |
-
|
| 217 |
-
self.device = torch.cuda.current_device()
|
| 218 |
-
|
| 219 |
-
self.grad_forces = False
|
| 220 |
-
self.num_resolutions = len(self.lmax_list)
|
| 221 |
-
self.sphere_channels_all = self.num_resolutions * self.sphere_channels
|
| 222 |
-
|
| 223 |
-
# Weights for message initialization
|
| 224 |
-
self.sphere_embedding = nn.Embedding(
|
| 225 |
-
self.max_num_elements, self.sphere_channels_all
|
| 226 |
-
)
|
| 227 |
-
|
| 228 |
-
# Initialize the function used to measure the distances between atoms
|
| 229 |
-
assert self.distance_function in [
|
| 230 |
-
"gaussian",
|
| 231 |
-
]
|
| 232 |
-
if self.distance_function == "gaussian":
|
| 233 |
-
self.distance_expansion = GaussianSmearing(
|
| 234 |
-
0.0,
|
| 235 |
-
self.cutoff,
|
| 236 |
-
600,
|
| 237 |
-
2.0,
|
| 238 |
-
)
|
| 239 |
-
# self.distance_expansion = GaussianRadialBasisLayer(num_basis=self.num_distance_basis, cutoff=self.max_radius)
|
| 240 |
-
else:
|
| 241 |
-
raise ValueError
|
| 242 |
-
|
| 243 |
-
# Initialize the sizes of radial functions (input channels and 2 hidden channels)
|
| 244 |
-
self.edge_channels_list = [int(self.distance_expansion.num_output)] + [
|
| 245 |
-
self.edge_channels
|
| 246 |
-
] * 2
|
| 247 |
-
|
| 248 |
-
# Initialize atom edge embedding
|
| 249 |
-
if self.share_atom_edge_embedding and self.use_atom_edge_embedding:
|
| 250 |
-
self.source_embedding = nn.Embedding(
|
| 251 |
-
self.max_num_elements, self.edge_channels_list[-1]
|
| 252 |
-
)
|
| 253 |
-
self.target_embedding = nn.Embedding(
|
| 254 |
-
self.max_num_elements, self.edge_channels_list[-1]
|
| 255 |
-
)
|
| 256 |
-
self.edge_channels_list[0] = (
|
| 257 |
-
self.edge_channels_list[0] + 2 * self.edge_channels_list[-1]
|
| 258 |
-
)
|
| 259 |
-
else:
|
| 260 |
-
self.source_embedding, self.target_embedding = None, None
|
| 261 |
-
|
| 262 |
-
# Initialize the module that compute WignerD matrices and other values for spherical harmonic calculations
|
| 263 |
-
self.SO3_rotation = nn.ModuleList()
|
| 264 |
-
for i in range(self.num_resolutions):
|
| 265 |
-
self.SO3_rotation.append(SO3_Rotation(self.lmax_list[i]))
|
| 266 |
-
|
| 267 |
-
# Initialize conversion between degree l and order m layouts
|
| 268 |
-
self.mappingReduced = CoefficientMappingModule(self.lmax_list, self.mmax_list)
|
| 269 |
-
|
| 270 |
-
# Initialize the transformations between spherical and grid representations
|
| 271 |
-
self.SO3_grid = ModuleListInfo(
|
| 272 |
-
"({}, {})".format(max(self.lmax_list), max(self.lmax_list))
|
| 273 |
-
)
|
| 274 |
-
for l in range(max(self.lmax_list) + 1):
|
| 275 |
-
SO3_m_grid = nn.ModuleList()
|
| 276 |
-
for m in range(max(self.lmax_list) + 1):
|
| 277 |
-
SO3_m_grid.append(
|
| 278 |
-
SO3_Grid(
|
| 279 |
-
l, m, resolution=self.grid_resolution, normalization="component"
|
| 280 |
-
)
|
| 281 |
-
)
|
| 282 |
-
self.SO3_grid.append(SO3_m_grid)
|
| 283 |
-
|
| 284 |
-
# Edge-degree embedding
|
| 285 |
-
self.edge_degree_embedding = EdgeDegreeEmbedding(
|
| 286 |
-
self.sphere_channels,
|
| 287 |
-
self.lmax_list,
|
| 288 |
-
self.mmax_list,
|
| 289 |
-
self.SO3_rotation,
|
| 290 |
-
self.mappingReduced,
|
| 291 |
-
self.max_num_elements,
|
| 292 |
-
self.edge_channels_list,
|
| 293 |
-
self.block_use_atom_edge_embedding,
|
| 294 |
-
rescale_factor=_AVG_DEGREE,
|
| 295 |
-
)
|
| 296 |
-
|
| 297 |
-
# Initialize the blocks for each layer of EquiformerV2
|
| 298 |
-
self.blocks = nn.ModuleList()
|
| 299 |
-
for i in range(self.num_layers):
|
| 300 |
-
block = TransBlockV2(
|
| 301 |
-
self.sphere_channels,
|
| 302 |
-
self.attn_hidden_channels,
|
| 303 |
-
self.num_heads,
|
| 304 |
-
self.attn_alpha_channels,
|
| 305 |
-
self.attn_value_channels,
|
| 306 |
-
self.ffn_hidden_channels,
|
| 307 |
-
self.sphere_channels,
|
| 308 |
-
self.lmax_list,
|
| 309 |
-
self.mmax_list,
|
| 310 |
-
self.SO3_rotation,
|
| 311 |
-
self.mappingReduced,
|
| 312 |
-
self.SO3_grid,
|
| 313 |
-
self.max_num_elements,
|
| 314 |
-
self.edge_channels_list,
|
| 315 |
-
self.block_use_atom_edge_embedding,
|
| 316 |
-
self.use_m_share_rad,
|
| 317 |
-
self.attn_activation,
|
| 318 |
-
self.use_s2_act_attn,
|
| 319 |
-
self.use_attn_renorm,
|
| 320 |
-
self.ffn_activation,
|
| 321 |
-
self.use_gate_act,
|
| 322 |
-
self.use_grid_mlp,
|
| 323 |
-
self.use_sep_s2_act,
|
| 324 |
-
self.norm_type,
|
| 325 |
-
self.alpha_drop,
|
| 326 |
-
self.drop_path_rate,
|
| 327 |
-
self.proj_drop,
|
| 328 |
-
)
|
| 329 |
-
self.blocks.append(block)
|
| 330 |
-
|
| 331 |
-
# Output blocks for energy and forces
|
| 332 |
-
self.norm = get_normalization_layer(
|
| 333 |
-
self.norm_type, lmax=max(self.lmax_list), num_channels=self.sphere_channels
|
| 334 |
-
)
|
| 335 |
-
self.energy_block = FeedForwardNetwork(
|
| 336 |
-
self.sphere_channels,
|
| 337 |
-
self.ffn_hidden_channels,
|
| 338 |
-
1,
|
| 339 |
-
self.lmax_list,
|
| 340 |
-
self.mmax_list,
|
| 341 |
-
self.SO3_grid,
|
| 342 |
-
self.ffn_activation,
|
| 343 |
-
self.use_gate_act,
|
| 344 |
-
self.use_grid_mlp,
|
| 345 |
-
self.use_sep_s2_act,
|
| 346 |
-
)
|
| 347 |
-
if self.regress_forces:
|
| 348 |
-
self.force_block = SO2EquivariantGraphAttention(
|
| 349 |
-
self.sphere_channels,
|
| 350 |
-
self.attn_hidden_channels,
|
| 351 |
-
self.num_heads,
|
| 352 |
-
self.attn_alpha_channels,
|
| 353 |
-
self.attn_value_channels,
|
| 354 |
-
1,
|
| 355 |
-
self.lmax_list,
|
| 356 |
-
self.mmax_list,
|
| 357 |
-
self.SO3_rotation,
|
| 358 |
-
self.mappingReduced,
|
| 359 |
-
self.SO3_grid,
|
| 360 |
-
self.max_num_elements,
|
| 361 |
-
self.edge_channels_list,
|
| 362 |
-
self.block_use_atom_edge_embedding,
|
| 363 |
-
self.use_m_share_rad,
|
| 364 |
-
self.attn_activation,
|
| 365 |
-
self.use_s2_act_attn,
|
| 366 |
-
self.use_attn_renorm,
|
| 367 |
-
self.use_gate_act,
|
| 368 |
-
self.use_sep_s2_act,
|
| 369 |
-
alpha_drop=0.0,
|
| 370 |
-
)
|
| 371 |
-
|
| 372 |
-
################################################################
|
| 373 |
-
# Add extra heads for eigenvalue/eigenvector prediction
|
| 374 |
-
################################################################
|
| 375 |
-
# Eigenvectors are vectors (degree 1) like forces
|
| 376 |
-
# we will just use the same architecture as the force head
|
| 377 |
-
if do_eigvec_1:
|
| 378 |
-
print(f"{self.__class__.__name__}: Adding eigvec_1_head")
|
| 379 |
-
self.eigvec_1_head = SO2EquivariantGraphAttention(
|
| 380 |
-
self.sphere_channels,
|
| 381 |
-
self.attn_hidden_channels,
|
| 382 |
-
self.num_heads,
|
| 383 |
-
self.attn_alpha_channels,
|
| 384 |
-
self.attn_value_channels,
|
| 385 |
-
1,
|
| 386 |
-
self.lmax_list,
|
| 387 |
-
self.mmax_list,
|
| 388 |
-
self.SO3_rotation,
|
| 389 |
-
self.mappingReduced,
|
| 390 |
-
self.SO3_grid,
|
| 391 |
-
self.max_num_elements,
|
| 392 |
-
self.edge_channels_list,
|
| 393 |
-
self.block_use_atom_edge_embedding,
|
| 394 |
-
self.use_m_share_rad,
|
| 395 |
-
self.attn_activation,
|
| 396 |
-
self.use_s2_act_attn,
|
| 397 |
-
self.use_attn_renorm,
|
| 398 |
-
self.use_gate_act,
|
| 399 |
-
self.use_sep_s2_act,
|
| 400 |
-
alpha_drop=0.0,
|
| 401 |
-
)
|
| 402 |
-
else:
|
| 403 |
-
self.eigvec_1_head = None
|
| 404 |
-
if do_eigvec_2:
|
| 405 |
-
print(f"{self.__class__.__name__}: Adding eigvec_2_head")
|
| 406 |
-
self.eigvec_2_head = SO2EquivariantGraphAttention(
|
| 407 |
-
self.sphere_channels,
|
| 408 |
-
self.attn_hidden_channels,
|
| 409 |
-
self.num_heads,
|
| 410 |
-
self.attn_alpha_channels,
|
| 411 |
-
self.attn_value_channels,
|
| 412 |
-
1,
|
| 413 |
-
self.lmax_list,
|
| 414 |
-
self.mmax_list,
|
| 415 |
-
self.SO3_rotation,
|
| 416 |
-
self.mappingReduced,
|
| 417 |
-
self.SO3_grid,
|
| 418 |
-
self.max_num_elements,
|
| 419 |
-
self.edge_channels_list,
|
| 420 |
-
self.block_use_atom_edge_embedding,
|
| 421 |
-
self.use_m_share_rad,
|
| 422 |
-
self.attn_activation,
|
| 423 |
-
self.use_s2_act_attn,
|
| 424 |
-
self.use_attn_renorm,
|
| 425 |
-
self.use_gate_act,
|
| 426 |
-
self.use_sep_s2_act,
|
| 427 |
-
alpha_drop=0.0,
|
| 428 |
-
)
|
| 429 |
-
else:
|
| 430 |
-
self.eigvec_2_head = None
|
| 431 |
-
|
| 432 |
-
# eigenvalues are scalars (degree 0) like energy
|
| 433 |
-
# we will just use the same architecture as the energy head
|
| 434 |
-
if do_eigval_1:
|
| 435 |
-
print(f"{self.__class__.__name__}: Adding eigval_1_head")
|
| 436 |
-
self.eigval_1_head = FeedForwardNetwork(
|
| 437 |
-
self.sphere_channels,
|
| 438 |
-
self.ffn_hidden_channels,
|
| 439 |
-
1,
|
| 440 |
-
self.lmax_list,
|
| 441 |
-
self.mmax_list,
|
| 442 |
-
self.SO3_grid,
|
| 443 |
-
self.ffn_activation,
|
| 444 |
-
self.use_gate_act,
|
| 445 |
-
self.use_grid_mlp,
|
| 446 |
-
self.use_sep_s2_act,
|
| 447 |
-
)
|
| 448 |
-
else:
|
| 449 |
-
self.eigval_1_head = None
|
| 450 |
-
if do_eigval_2:
|
| 451 |
-
print(f"{self.__class__.__name__}: Adding eigval_2_head")
|
| 452 |
-
self.eigval_2_head = FeedForwardNetwork(
|
| 453 |
-
self.sphere_channels,
|
| 454 |
-
self.ffn_hidden_channels,
|
| 455 |
-
1,
|
| 456 |
-
self.lmax_list,
|
| 457 |
-
self.mmax_list,
|
| 458 |
-
self.SO3_grid,
|
| 459 |
-
self.ffn_activation,
|
| 460 |
-
self.use_gate_act,
|
| 461 |
-
self.use_grid_mlp,
|
| 462 |
-
self.use_sep_s2_act,
|
| 463 |
-
)
|
| 464 |
-
else:
|
| 465 |
-
self.eigval_2_head = None
|
| 466 |
-
|
| 467 |
-
self.apply(self._init_weights)
|
| 468 |
-
self.apply(self._uniform_init_rad_func_linear_weights)
|
| 469 |
-
|
| 470 |
-
def grad_hess_ij(self, energy, posj, posi, create_graph=True):
|
| 471 |
-
"""Calculating the inter-atomic part of hessian matrices.Find the cross-derivative for the coordinates
|
| 472 |
-
of atom i and atom j that interact on the interaction layer.
|
| 473 |
-
require out_type='scalar' and grad_type='Hij' and sclar_outsize=1 and irreps_out=None
|
| 474 |
-
"""
|
| 475 |
-
fj = -grad([torch.sum(energy)], [posj], create_graph=create_graph)[0]
|
| 476 |
-
Hji = torch.zeros((fj.shape[0], 3, 3), device=fj.device)
|
| 477 |
-
for i in range(3):
|
| 478 |
-
gji = -grad(
|
| 479 |
-
[fj[:, i].sum()], [posi], create_graph=create_graph, retain_graph=True
|
| 480 |
-
)[0]
|
| 481 |
-
Hji[:, i] = gji
|
| 482 |
-
return Hji
|
| 483 |
-
|
| 484 |
-
@conditional_grad(torch.enable_grad())
|
| 485 |
-
def forward(self, data, eigen=False):
|
| 486 |
-
"""
|
| 487 |
-
If eigen=True, return predictions for eigenvalues and eigenvectors of the Hessian in outputs dict.
|
| 488 |
-
|
| 489 |
-
Returns:
|
| 490 |
-
energy: (N*B,)
|
| 491 |
-
forces: (N*B, 3)
|
| 492 |
-
outputs (Optional): dict of eigenvalues and eigenvectors of the Hessian
|
| 493 |
-
eigval_1: (N*B,)
|
| 494 |
-
eigval_2: (N*B,)
|
| 495 |
-
eigvec_1: (N*B, 3)
|
| 496 |
-
eigvec_2: (N*B, 3)
|
| 497 |
-
"""
|
| 498 |
-
self.batch_size = len(data.natoms)
|
| 499 |
-
self.dtype = data.pos.dtype
|
| 500 |
-
self.device = data.pos.device
|
| 501 |
-
|
| 502 |
-
atomic_numbers = data.z.long()
|
| 503 |
-
num_atoms = len(atomic_numbers)
|
| 504 |
-
pos = data.pos
|
| 505 |
-
|
| 506 |
-
# (
|
| 507 |
-
# edge_index,
|
| 508 |
-
# edge_distance,
|
| 509 |
-
# edge_distance_vec,
|
| 510 |
-
# cell_offsets,
|
| 511 |
-
# _, # cell offset distances
|
| 512 |
-
# neighbors,
|
| 513 |
-
# ) = self.generate_graph(data)
|
| 514 |
-
|
| 515 |
-
edge_index = radius_graph(pos, r=self.cutoff, batch=data.batch)
|
| 516 |
-
j, i = edge_index
|
| 517 |
-
posj = pos[j]
|
| 518 |
-
posi = pos[i]
|
| 519 |
-
vecs = posj - posi
|
| 520 |
-
edge_distance_vec = vecs
|
| 521 |
-
edge_distance = (vecs).norm(dim=-1)
|
| 522 |
-
###############################################################
|
| 523 |
-
# Initialize data structures
|
| 524 |
-
###############################################################
|
| 525 |
-
|
| 526 |
-
# Compute 3x3 rotation matrix per edge
|
| 527 |
-
edge_rot_mat = self._init_edge_rot_mat(data, edge_index, edge_distance_vec)
|
| 528 |
-
|
| 529 |
-
# Initialize the WignerD matrices and other values for spherical harmonic calculations
|
| 530 |
-
for i in range(self.num_resolutions):
|
| 531 |
-
self.SO3_rotation[i].set_wigner(edge_rot_mat)
|
| 532 |
-
|
| 533 |
-
###############################################################
|
| 534 |
-
# Initialize node embeddings
|
| 535 |
-
###############################################################
|
| 536 |
-
|
| 537 |
-
# Init per node representations using an atomic number based embedding
|
| 538 |
-
offset = 0
|
| 539 |
-
x = SO3_Embedding(
|
| 540 |
-
num_atoms,
|
| 541 |
-
self.lmax_list,
|
| 542 |
-
self.sphere_channels,
|
| 543 |
-
self.device,
|
| 544 |
-
self.dtype,
|
| 545 |
-
)
|
| 546 |
-
|
| 547 |
-
offset_res = 0
|
| 548 |
-
offset = 0
|
| 549 |
-
# Initialize the l = 0, m = 0 coefficients for each resolution
|
| 550 |
-
for i in range(self.num_resolutions):
|
| 551 |
-
if self.num_resolutions == 1:
|
| 552 |
-
x.embedding[:, offset_res, :] = self.sphere_embedding(atomic_numbers)
|
| 553 |
-
else:
|
| 554 |
-
x.embedding[:, offset_res, :] = self.sphere_embedding(atomic_numbers)[
|
| 555 |
-
:, offset : offset + self.sphere_channels
|
| 556 |
-
]
|
| 557 |
-
offset = offset + self.sphere_channels
|
| 558 |
-
offset_res = offset_res + int((self.lmax_list[i] + 1) ** 2)
|
| 559 |
-
|
| 560 |
-
# Edge encoding (distance and atom edge)
|
| 561 |
-
edge_distance = self.distance_expansion(edge_distance)
|
| 562 |
-
if self.share_atom_edge_embedding and self.use_atom_edge_embedding:
|
| 563 |
-
source_element = atomic_numbers[edge_index[0]] # Source atom atomic number
|
| 564 |
-
target_element = atomic_numbers[edge_index[1]] # Target atom atomic number
|
| 565 |
-
source_embedding = self.source_embedding(source_element)
|
| 566 |
-
target_embedding = self.target_embedding(target_element)
|
| 567 |
-
edge_distance = torch.cat(
|
| 568 |
-
(edge_distance, source_embedding, target_embedding), dim=1
|
| 569 |
-
)
|
| 570 |
-
|
| 571 |
-
# Edge-degree embedding
|
| 572 |
-
edge_degree = self.edge_degree_embedding(
|
| 573 |
-
atomic_numbers, edge_distance, edge_index
|
| 574 |
-
)
|
| 575 |
-
x.embedding = x.embedding + edge_degree.embedding
|
| 576 |
-
|
| 577 |
-
###############################################################
|
| 578 |
-
# Update spherical node embeddings
|
| 579 |
-
###############################################################
|
| 580 |
-
|
| 581 |
-
for i in range(self.num_layers):
|
| 582 |
-
x = self.blocks[i](
|
| 583 |
-
x, # SO3_Embedding
|
| 584 |
-
atomic_numbers,
|
| 585 |
-
edge_distance,
|
| 586 |
-
edge_index,
|
| 587 |
-
batch=data.batch, # for GraphDropPath
|
| 588 |
-
)
|
| 589 |
-
|
| 590 |
-
# Final layer norm
|
| 591 |
-
x.embedding = self.norm(x.embedding)
|
| 592 |
-
|
| 593 |
-
###############################################################
|
| 594 |
-
# Energy estimation
|
| 595 |
-
###############################################################
|
| 596 |
-
node_energy = self.energy_block(x)
|
| 597 |
-
# node_energy = node_energy.embedding.narrow(1, 0, 1)
|
| 598 |
-
# energy = torch.zeros(
|
| 599 |
-
# len(data.natoms), device=node_energy.device, dtype=node_energy.dtype
|
| 600 |
-
# )
|
| 601 |
-
# energy.index_add_(0, data.batch, node_energy.view(-1))
|
| 602 |
-
# energy = energy / _AVG_NUM_NODES
|
| 603 |
-
energy = get_scalar_from_embedding(node_energy, data)
|
| 604 |
-
|
| 605 |
-
# hessian_ij = self.grad_hess_ij(energy=energy, posj=posj, posi=posi)
|
| 606 |
-
|
| 607 |
-
###############################################################
|
| 608 |
-
# Force estimation
|
| 609 |
-
###############################################################
|
| 610 |
-
|
| 611 |
-
forces = self.force_block(x, atomic_numbers, edge_distance, edge_index)
|
| 612 |
-
forces = forces.embedding.narrow(1, 1, 3)
|
| 613 |
-
forces = forces.view(-1, 3)
|
| 614 |
-
|
| 615 |
-
###############################################################
|
| 616 |
-
# Eigenvalue/eigenvector estimation
|
| 617 |
-
###############################################################
|
| 618 |
-
if eigen:
|
| 619 |
-
outputs = {}
|
| 620 |
-
if self.eigval_1_head is not None:
|
| 621 |
-
node_eigval_1 = self.eigval_1_head(x)
|
| 622 |
-
eigval_1 = get_scalar_from_embedding(node_eigval_1, data)
|
| 623 |
-
outputs["eigval_1"] = eigval_1
|
| 624 |
-
if self.eigval_2_head is not None:
|
| 625 |
-
node_eigval_2 = self.eigval_2_head(x)
|
| 626 |
-
eigval_2 = get_scalar_from_embedding(node_eigval_2, data)
|
| 627 |
-
outputs["eigval_2"] = eigval_2
|
| 628 |
-
if self.eigvec_1_head is not None:
|
| 629 |
-
eigvec_1 = self.eigvec_1_head(
|
| 630 |
-
x, atomic_numbers, edge_distance, edge_index
|
| 631 |
-
)
|
| 632 |
-
eigvec_1 = eigvec_1.embedding.narrow(1, 1, 3)
|
| 633 |
-
eigvec_1 = eigvec_1.view(-1, 3)
|
| 634 |
-
outputs["eigvec_1"] = eigvec_1
|
| 635 |
-
if self.eigvec_2_head is not None:
|
| 636 |
-
eigvec_2 = self.eigvec_2_head(
|
| 637 |
-
x, atomic_numbers, edge_distance, edge_index
|
| 638 |
-
)
|
| 639 |
-
eigvec_2 = eigvec_2.embedding.narrow(1, 1, 3)
|
| 640 |
-
eigvec_2 = eigvec_2.view(-1, 3)
|
| 641 |
-
outputs["eigvec_2"] = eigvec_2
|
| 642 |
-
|
| 643 |
-
return energy.reshape(data.ae.shape), forces, outputs
|
| 644 |
-
|
| 645 |
-
return energy.reshape(data.ae.shape), forces
|
| 646 |
-
|
| 647 |
-
# Initialize the edge rotation matrics
|
| 648 |
-
def _init_edge_rot_mat(self, data, edge_index, edge_distance_vec):
|
| 649 |
-
return init_edge_rot_mat(edge_distance_vec)
|
| 650 |
-
|
| 651 |
-
@property
|
| 652 |
-
def num_params(self):
|
| 653 |
-
return sum(p.numel() for p in self.parameters())
|
| 654 |
-
|
| 655 |
-
def _init_weights(self, m):
|
| 656 |
-
if isinstance(m, torch.nn.Linear) or isinstance(m, SO3_LinearV2):
|
| 657 |
-
if m.bias is not None:
|
| 658 |
-
torch.nn.init.constant_(m.bias, 0)
|
| 659 |
-
if self.weight_init == "normal":
|
| 660 |
-
std = 1 / math.sqrt(m.in_features)
|
| 661 |
-
torch.nn.init.normal_(m.weight, 0, std)
|
| 662 |
-
|
| 663 |
-
elif isinstance(m, torch.nn.LayerNorm):
|
| 664 |
-
torch.nn.init.constant_(m.bias, 0)
|
| 665 |
-
torch.nn.init.constant_(m.weight, 1.0)
|
| 666 |
-
|
| 667 |
-
def _uniform_init_rad_func_linear_weights(self, m):
|
| 668 |
-
if isinstance(m, RadialFunction):
|
| 669 |
-
m.apply(self._uniform_init_linear_weights)
|
| 670 |
-
|
| 671 |
-
def _uniform_init_linear_weights(self, m):
|
| 672 |
-
if isinstance(m, torch.nn.Linear):
|
| 673 |
-
if m.bias is not None:
|
| 674 |
-
torch.nn.init.constant_(m.bias, 0)
|
| 675 |
-
std = 1 / math.sqrt(m.in_features)
|
| 676 |
-
torch.nn.init.uniform_(m.weight, -std, std)
|
| 677 |
-
|
| 678 |
-
@torch.jit.ignore
|
| 679 |
-
def no_weight_decay(self):
|
| 680 |
-
no_wd_list = []
|
| 681 |
-
named_parameters_list = [name for name, _ in self.named_parameters()]
|
| 682 |
-
for module_name, module in self.named_modules():
|
| 683 |
-
if (
|
| 684 |
-
isinstance(module, torch.nn.Linear)
|
| 685 |
-
or isinstance(module, SO3_LinearV2)
|
| 686 |
-
or isinstance(module, torch.nn.LayerNorm)
|
| 687 |
-
or isinstance(module, EquivariantLayerNormArray)
|
| 688 |
-
or isinstance(module, EquivariantLayerNormArraySphericalHarmonics)
|
| 689 |
-
or isinstance(module, EquivariantRMSNormArraySphericalHarmonics)
|
| 690 |
-
or isinstance(module, EquivariantRMSNormArraySphericalHarmonicsV2)
|
| 691 |
-
or isinstance(module, GaussianRadialBasisLayer)
|
| 692 |
-
):
|
| 693 |
-
for parameter_name, _ in module.named_parameters():
|
| 694 |
-
if isinstance(module, torch.nn.Linear) or isinstance(
|
| 695 |
-
module, SO3_LinearV2
|
| 696 |
-
):
|
| 697 |
-
if "weight" in parameter_name:
|
| 698 |
-
continue
|
| 699 |
-
global_parameter_name = module_name + "." + parameter_name
|
| 700 |
-
assert global_parameter_name in named_parameters_list
|
| 701 |
-
no_wd_list.append(global_parameter_name)
|
| 702 |
-
return set(no_wd_list)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
nets/equiformer_v2/gaussian_rbf.py
DELETED
|
@@ -1,44 +0,0 @@
|
|
| 1 |
-
import torch
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
@torch.jit.script
|
| 5 |
-
def gaussian(x, mean, std):
|
| 6 |
-
pi = 3.14159
|
| 7 |
-
a = (2 * pi) ** 0.5
|
| 8 |
-
return torch.exp(-0.5 * (((x - mean) / std) ** 2)) / (a * std)
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
# From Graphormer
|
| 12 |
-
class GaussianRadialBasisLayer(torch.nn.Module):
|
| 13 |
-
def __init__(self, num_basis, cutoff):
|
| 14 |
-
super().__init__()
|
| 15 |
-
self.num_basis = num_basis
|
| 16 |
-
self.cutoff = cutoff + 0.0
|
| 17 |
-
self.mean = torch.nn.Parameter(torch.zeros(1, self.num_basis))
|
| 18 |
-
self.std = torch.nn.Parameter(torch.zeros(1, self.num_basis))
|
| 19 |
-
self.weight = torch.nn.Parameter(torch.ones(1, 1))
|
| 20 |
-
self.bias = torch.nn.Parameter(torch.zeros(1, 1))
|
| 21 |
-
|
| 22 |
-
self.std_init_max = 1.0
|
| 23 |
-
self.std_init_min = 1.0 / self.num_basis
|
| 24 |
-
self.mean_init_max = 1.0
|
| 25 |
-
self.mean_init_min = 0
|
| 26 |
-
torch.nn.init.uniform_(self.mean, self.mean_init_min, self.mean_init_max)
|
| 27 |
-
torch.nn.init.uniform_(self.std, self.std_init_min, self.std_init_max)
|
| 28 |
-
torch.nn.init.constant_(self.weight, 1)
|
| 29 |
-
torch.nn.init.constant_(self.bias, 0)
|
| 30 |
-
|
| 31 |
-
def forward(self, dist, node_atom=None, edge_src=None, edge_dst=None):
|
| 32 |
-
x = dist / self.cutoff
|
| 33 |
-
x = x.unsqueeze(-1)
|
| 34 |
-
x = self.weight * x + self.bias
|
| 35 |
-
x = x.expand(-1, self.num_basis)
|
| 36 |
-
mean = self.mean
|
| 37 |
-
std = self.std.abs() + 1e-5
|
| 38 |
-
x = gaussian(x, mean, std)
|
| 39 |
-
return x
|
| 40 |
-
|
| 41 |
-
def extra_repr(self):
|
| 42 |
-
return "mean_init_max={}, mean_init_min={}, std_init_max={}, std_init_min={}".format(
|
| 43 |
-
self.mean_init_max, self.mean_init_min, self.std_init_max, self.std_init_min
|
| 44 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
nets/equiformer_v2/input_block.py
DELETED
|
@@ -1,126 +0,0 @@
|
|
| 1 |
-
import torch
|
| 2 |
-
import torch.nn as nn
|
| 3 |
-
import copy
|
| 4 |
-
|
| 5 |
-
from .so3 import SO3_Embedding
|
| 6 |
-
from .radial_function import RadialFunction
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
class EdgeDegreeEmbedding(torch.nn.Module):
|
| 10 |
-
"""
|
| 11 |
-
|
| 12 |
-
Args:
|
| 13 |
-
sphere_channels (int): Number of spherical channels
|
| 14 |
-
|
| 15 |
-
lmax_list (list:int): List of degrees (l) for each resolution
|
| 16 |
-
mmax_list (list:int): List of orders (m) for each resolution
|
| 17 |
-
|
| 18 |
-
SO3_rotation (list:SO3_Rotation): Class to calculate Wigner-D matrices and rotate embeddings
|
| 19 |
-
mappingReduced (CoefficientMappingModule): Class to convert l and m indices once node embedding is rotated
|
| 20 |
-
|
| 21 |
-
max_num_elements (int): Maximum number of atomic numbers
|
| 22 |
-
edge_channels_list (list:int): List of sizes of invariant edge embedding. For example, [input_channels, hidden_channels, hidden_channels].
|
| 23 |
-
The last one will be used as hidden size when `use_atom_edge_embedding` is `True`.
|
| 24 |
-
use_atom_edge_embedding (bool): Whether to use atomic embedding along with relative distance for edge scalar features
|
| 25 |
-
|
| 26 |
-
rescale_factor (float): Rescale the sum aggregation
|
| 27 |
-
"""
|
| 28 |
-
|
| 29 |
-
def __init__(
|
| 30 |
-
self,
|
| 31 |
-
sphere_channels,
|
| 32 |
-
lmax_list,
|
| 33 |
-
mmax_list,
|
| 34 |
-
SO3_rotation,
|
| 35 |
-
mappingReduced,
|
| 36 |
-
max_num_elements,
|
| 37 |
-
edge_channels_list,
|
| 38 |
-
use_atom_edge_embedding,
|
| 39 |
-
rescale_factor,
|
| 40 |
-
):
|
| 41 |
-
super(EdgeDegreeEmbedding, self).__init__()
|
| 42 |
-
self.sphere_channels = sphere_channels
|
| 43 |
-
self.lmax_list = lmax_list
|
| 44 |
-
self.mmax_list = mmax_list
|
| 45 |
-
self.num_resolutions = len(self.lmax_list)
|
| 46 |
-
self.SO3_rotation = SO3_rotation
|
| 47 |
-
self.mappingReduced = mappingReduced
|
| 48 |
-
|
| 49 |
-
self.m_0_num_coefficients = self.mappingReduced.m_size[0]
|
| 50 |
-
self.m_all_num_coefficents = len(self.mappingReduced.l_harmonic)
|
| 51 |
-
|
| 52 |
-
# Create edge scalar (invariant to rotations) features
|
| 53 |
-
# Embedding function of the atomic numbers
|
| 54 |
-
self.max_num_elements = max_num_elements
|
| 55 |
-
self.edge_channels_list = copy.deepcopy(edge_channels_list)
|
| 56 |
-
self.use_atom_edge_embedding = use_atom_edge_embedding
|
| 57 |
-
|
| 58 |
-
if self.use_atom_edge_embedding:
|
| 59 |
-
self.source_embedding = nn.Embedding(
|
| 60 |
-
self.max_num_elements, self.edge_channels_list[-1]
|
| 61 |
-
)
|
| 62 |
-
self.target_embedding = nn.Embedding(
|
| 63 |
-
self.max_num_elements, self.edge_channels_list[-1]
|
| 64 |
-
)
|
| 65 |
-
nn.init.uniform_(self.source_embedding.weight.data, -0.001, 0.001)
|
| 66 |
-
nn.init.uniform_(self.target_embedding.weight.data, -0.001, 0.001)
|
| 67 |
-
self.edge_channels_list[0] = (
|
| 68 |
-
self.edge_channels_list[0] + 2 * self.edge_channels_list[-1]
|
| 69 |
-
)
|
| 70 |
-
else:
|
| 71 |
-
self.source_embedding, self.target_embedding = None, None
|
| 72 |
-
|
| 73 |
-
# Embedding function of distance
|
| 74 |
-
self.edge_channels_list.append(self.m_0_num_coefficients * self.sphere_channels)
|
| 75 |
-
self.rad_func = RadialFunction(self.edge_channels_list)
|
| 76 |
-
|
| 77 |
-
self.rescale_factor = rescale_factor
|
| 78 |
-
|
| 79 |
-
def forward(self, atomic_numbers, edge_distance, edge_index):
|
| 80 |
-
|
| 81 |
-
if self.use_atom_edge_embedding:
|
| 82 |
-
source_element = atomic_numbers[edge_index[0]] # Source atom atomic number
|
| 83 |
-
target_element = atomic_numbers[edge_index[1]] # Target atom atomic number
|
| 84 |
-
source_embedding = self.source_embedding(source_element)
|
| 85 |
-
target_embedding = self.target_embedding(target_element)
|
| 86 |
-
x_edge = torch.cat(
|
| 87 |
-
(edge_distance, source_embedding, target_embedding), dim=1
|
| 88 |
-
)
|
| 89 |
-
else:
|
| 90 |
-
x_edge = edge_distance
|
| 91 |
-
|
| 92 |
-
x_edge_m_0 = self.rad_func(x_edge)
|
| 93 |
-
x_edge_m_0 = x_edge_m_0.reshape(
|
| 94 |
-
-1, self.m_0_num_coefficients, self.sphere_channels
|
| 95 |
-
)
|
| 96 |
-
x_edge_m_pad = torch.zeros(
|
| 97 |
-
(
|
| 98 |
-
x_edge_m_0.shape[0],
|
| 99 |
-
(self.m_all_num_coefficents - self.m_0_num_coefficients),
|
| 100 |
-
self.sphere_channels,
|
| 101 |
-
),
|
| 102 |
-
device=x_edge_m_0.device,
|
| 103 |
-
)
|
| 104 |
-
x_edge_m_all = torch.cat((x_edge_m_0, x_edge_m_pad), dim=1)
|
| 105 |
-
|
| 106 |
-
x_edge_embedding = SO3_Embedding(
|
| 107 |
-
0,
|
| 108 |
-
self.lmax_list.copy(),
|
| 109 |
-
self.sphere_channels,
|
| 110 |
-
device=x_edge_m_all.device,
|
| 111 |
-
dtype=x_edge_m_all.dtype,
|
| 112 |
-
)
|
| 113 |
-
x_edge_embedding.set_embedding(x_edge_m_all)
|
| 114 |
-
x_edge_embedding.set_lmax_mmax(self.lmax_list.copy(), self.mmax_list.copy())
|
| 115 |
-
|
| 116 |
-
# Reshape the spherical harmonics based on l (degree)
|
| 117 |
-
x_edge_embedding._l_primary(self.mappingReduced)
|
| 118 |
-
|
| 119 |
-
# Rotate back the irreps
|
| 120 |
-
x_edge_embedding._rotate_inv(self.SO3_rotation, self.mappingReduced)
|
| 121 |
-
|
| 122 |
-
# Compute the sum of the incoming neighboring messages for each target node
|
| 123 |
-
x_edge_embedding._reduce_edge(edge_index[1], atomic_numbers.shape[0])
|
| 124 |
-
x_edge_embedding.embedding = x_edge_embedding.embedding / self.rescale_factor
|
| 125 |
-
|
| 126 |
-
return x_edge_embedding
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
nets/equiformer_v2/layer_norm.py
DELETED
|
@@ -1,424 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
1. Normalize features of shape (N, sphere_basis, C),
|
| 3 |
-
with sphere_basis = (lmax + 1) ** 2.
|
| 4 |
-
|
| 5 |
-
2. The difference from `layer_norm.py` is that all type-L vectors have
|
| 6 |
-
the same number of channels and input features are of shape (N, sphere_basis, C).
|
| 7 |
-
"""
|
| 8 |
-
|
| 9 |
-
import torch
|
| 10 |
-
import torch.nn as nn
|
| 11 |
-
import math
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
def get_normalization_layer(
|
| 15 |
-
norm_type, lmax, num_channels, eps=1e-5, affine=True, normalization="component"
|
| 16 |
-
):
|
| 17 |
-
assert norm_type in ["layer_norm", "layer_norm_sh", "rms_norm_sh"]
|
| 18 |
-
if norm_type == "layer_norm":
|
| 19 |
-
norm_class = EquivariantLayerNormArray
|
| 20 |
-
elif norm_type == "layer_norm_sh":
|
| 21 |
-
norm_class = EquivariantLayerNormArraySphericalHarmonics
|
| 22 |
-
elif norm_type == "rms_norm_sh":
|
| 23 |
-
norm_class = EquivariantRMSNormArraySphericalHarmonicsV2
|
| 24 |
-
else:
|
| 25 |
-
raise ValueError
|
| 26 |
-
return norm_class(lmax, num_channels, eps, affine, normalization)
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
def get_l_to_all_m_expand_index(lmax):
|
| 30 |
-
expand_index = torch.zeros([(lmax + 1) ** 2]).long()
|
| 31 |
-
for l in range(lmax + 1):
|
| 32 |
-
start_idx = l**2
|
| 33 |
-
length = 2 * l + 1
|
| 34 |
-
expand_index[start_idx : (start_idx + length)] = l
|
| 35 |
-
return expand_index
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
class EquivariantLayerNormArray(nn.Module):
|
| 39 |
-
|
| 40 |
-
def __init__(
|
| 41 |
-
self, lmax, num_channels, eps=1e-5, affine=True, normalization="component"
|
| 42 |
-
):
|
| 43 |
-
super().__init__()
|
| 44 |
-
|
| 45 |
-
self.lmax = lmax
|
| 46 |
-
self.num_channels = num_channels
|
| 47 |
-
self.eps = eps
|
| 48 |
-
self.affine = affine
|
| 49 |
-
|
| 50 |
-
if affine:
|
| 51 |
-
self.affine_weight = nn.Parameter(torch.ones(lmax + 1, num_channels))
|
| 52 |
-
self.affine_bias = nn.Parameter(torch.zeros(num_channels))
|
| 53 |
-
else:
|
| 54 |
-
self.register_parameter("affine_weight", None)
|
| 55 |
-
self.register_parameter("affine_bias", None)
|
| 56 |
-
|
| 57 |
-
assert normalization in ["norm", "component"]
|
| 58 |
-
self.normalization = normalization
|
| 59 |
-
|
| 60 |
-
def __repr__(self):
|
| 61 |
-
return f"{self.__class__.__name__}(lmax={self.lmax}, num_channels={self.num_channels}, eps={self.eps})"
|
| 62 |
-
|
| 63 |
-
@torch.amp.autocast("cuda", enabled=False)
|
| 64 |
-
def forward(self, node_input):
|
| 65 |
-
"""
|
| 66 |
-
Assume input is of shape [N, sphere_basis, C]
|
| 67 |
-
"""
|
| 68 |
-
|
| 69 |
-
out = []
|
| 70 |
-
|
| 71 |
-
for l in range(self.lmax + 1):
|
| 72 |
-
start_idx = l**2
|
| 73 |
-
length = 2 * l + 1
|
| 74 |
-
|
| 75 |
-
feature = node_input.narrow(1, start_idx, length)
|
| 76 |
-
|
| 77 |
-
# For scalars, first compute and subtract the mean
|
| 78 |
-
if l == 0:
|
| 79 |
-
feature_mean = torch.mean(feature, dim=2, keepdim=True)
|
| 80 |
-
feature = feature - feature_mean
|
| 81 |
-
|
| 82 |
-
# Then compute the rescaling factor (norm of each feature vector)
|
| 83 |
-
# Rescaling of the norms themselves based on the option "normalization"
|
| 84 |
-
if self.normalization == "norm":
|
| 85 |
-
feature_norm = feature.pow(2).sum(dim=1, keepdim=True) # [N, 1, C]
|
| 86 |
-
elif self.normalization == "component":
|
| 87 |
-
feature_norm = feature.pow(2).mean(dim=1, keepdim=True) # [N, 1, C]
|
| 88 |
-
|
| 89 |
-
feature_norm = torch.mean(feature_norm, dim=2, keepdim=True) # [N, 1, 1]
|
| 90 |
-
feature_norm = (feature_norm + self.eps).pow(-0.5)
|
| 91 |
-
|
| 92 |
-
if self.affine:
|
| 93 |
-
weight = self.affine_weight.narrow(0, l, 1) # [1, C]
|
| 94 |
-
weight = weight.view(1, 1, -1) # [1, 1, C]
|
| 95 |
-
feature_norm = feature_norm * weight # [N, 1, C]
|
| 96 |
-
|
| 97 |
-
feature = feature * feature_norm
|
| 98 |
-
|
| 99 |
-
if self.affine and l == 0:
|
| 100 |
-
bias = self.affine_bias
|
| 101 |
-
bias = bias.view(1, 1, -1)
|
| 102 |
-
feature = feature + bias
|
| 103 |
-
|
| 104 |
-
out.append(feature)
|
| 105 |
-
|
| 106 |
-
out = torch.cat(out, dim=1)
|
| 107 |
-
|
| 108 |
-
return out
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
class EquivariantLayerNormArraySphericalHarmonics(nn.Module):
|
| 112 |
-
"""
|
| 113 |
-
1. Normalize over L = 0.
|
| 114 |
-
2. Normalize across all m components from degrees L > 0.
|
| 115 |
-
3. Do not normalize separately for different L (L > 0).
|
| 116 |
-
"""
|
| 117 |
-
|
| 118 |
-
def __init__(
|
| 119 |
-
self,
|
| 120 |
-
lmax,
|
| 121 |
-
num_channels,
|
| 122 |
-
eps=1e-5,
|
| 123 |
-
affine=True,
|
| 124 |
-
normalization="component",
|
| 125 |
-
std_balance_degrees=True,
|
| 126 |
-
):
|
| 127 |
-
super().__init__()
|
| 128 |
-
|
| 129 |
-
self.lmax = lmax
|
| 130 |
-
self.num_channels = num_channels
|
| 131 |
-
self.eps = eps
|
| 132 |
-
self.affine = affine
|
| 133 |
-
self.std_balance_degrees = std_balance_degrees
|
| 134 |
-
|
| 135 |
-
# for L = 0
|
| 136 |
-
self.norm_l0 = torch.nn.LayerNorm(
|
| 137 |
-
self.num_channels, eps=self.eps, elementwise_affine=self.affine
|
| 138 |
-
)
|
| 139 |
-
|
| 140 |
-
# for L > 0
|
| 141 |
-
if self.affine:
|
| 142 |
-
self.affine_weight = nn.Parameter(torch.ones(self.lmax, self.num_channels))
|
| 143 |
-
else:
|
| 144 |
-
self.register_parameter("affine_weight", None)
|
| 145 |
-
|
| 146 |
-
assert normalization in ["norm", "component"]
|
| 147 |
-
self.normalization = normalization
|
| 148 |
-
|
| 149 |
-
if self.std_balance_degrees:
|
| 150 |
-
balance_degree_weight = torch.zeros((self.lmax + 1) ** 2 - 1, 1)
|
| 151 |
-
for l in range(1, self.lmax + 1):
|
| 152 |
-
start_idx = l**2 - 1
|
| 153 |
-
length = 2 * l + 1
|
| 154 |
-
balance_degree_weight[start_idx : (start_idx + length), :] = (
|
| 155 |
-
1.0 / length
|
| 156 |
-
)
|
| 157 |
-
balance_degree_weight = balance_degree_weight / self.lmax
|
| 158 |
-
self.register_buffer("balance_degree_weight", balance_degree_weight)
|
| 159 |
-
else:
|
| 160 |
-
self.balance_degree_weight = None
|
| 161 |
-
|
| 162 |
-
def __repr__(self):
|
| 163 |
-
return f"{self.__class__.__name__}(lmax={self.lmax}, num_channels={self.num_channels}, eps={self.eps}, std_balance_degrees={self.std_balance_degrees})"
|
| 164 |
-
|
| 165 |
-
@torch.amp.autocast("cuda", enabled=False)
|
| 166 |
-
def forward(self, node_input):
|
| 167 |
-
"""
|
| 168 |
-
Assume input is of shape [N, sphere_basis, C]
|
| 169 |
-
"""
|
| 170 |
-
|
| 171 |
-
out = []
|
| 172 |
-
|
| 173 |
-
# for L = 0
|
| 174 |
-
feature = node_input.narrow(1, 0, 1)
|
| 175 |
-
feature = self.norm_l0(feature)
|
| 176 |
-
out.append(feature)
|
| 177 |
-
|
| 178 |
-
# for L > 0
|
| 179 |
-
if self.lmax > 0:
|
| 180 |
-
num_m_components = (self.lmax + 1) ** 2
|
| 181 |
-
feature = node_input.narrow(1, 1, num_m_components - 1)
|
| 182 |
-
|
| 183 |
-
# Then compute the rescaling factor (norm of each feature vector)
|
| 184 |
-
# Rescaling of the norms themselves based on the option "normalization"
|
| 185 |
-
if self.normalization == "norm":
|
| 186 |
-
feature_norm = feature.pow(2).sum(dim=1, keepdim=True) # [N, 1, C]
|
| 187 |
-
elif self.normalization == "component":
|
| 188 |
-
if self.std_balance_degrees:
|
| 189 |
-
feature_norm = feature.pow(
|
| 190 |
-
2
|
| 191 |
-
) # [N, (L_max + 1)**2 - 1, C], without L = 0
|
| 192 |
-
feature_norm = torch.einsum(
|
| 193 |
-
"nic, ia -> nac", feature_norm, self.balance_degree_weight
|
| 194 |
-
) # [N, 1, C]
|
| 195 |
-
else:
|
| 196 |
-
feature_norm = feature.pow(2).mean(dim=1, keepdim=True) # [N, 1, C]
|
| 197 |
-
|
| 198 |
-
feature_norm = torch.mean(feature_norm, dim=2, keepdim=True) # [N, 1, 1]
|
| 199 |
-
feature_norm = (feature_norm + self.eps).pow(-0.5)
|
| 200 |
-
|
| 201 |
-
for l in range(1, self.lmax + 1):
|
| 202 |
-
start_idx = l**2
|
| 203 |
-
length = 2 * l + 1
|
| 204 |
-
feature = node_input.narrow(1, start_idx, length) # [N, (2L + 1), C]
|
| 205 |
-
if self.affine:
|
| 206 |
-
weight = self.affine_weight.narrow(0, (l - 1), 1) # [1, C]
|
| 207 |
-
weight = weight.view(1, 1, -1) # [1, 1, C]
|
| 208 |
-
feature_scale = feature_norm * weight # [N, 1, C]
|
| 209 |
-
else:
|
| 210 |
-
feature_scale = feature_norm
|
| 211 |
-
feature = feature * feature_scale
|
| 212 |
-
out.append(feature)
|
| 213 |
-
|
| 214 |
-
out = torch.cat(out, dim=1)
|
| 215 |
-
return out
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
class EquivariantRMSNormArraySphericalHarmonics(nn.Module):
|
| 219 |
-
"""
|
| 220 |
-
1. Normalize across all m components from degrees L >= 0.
|
| 221 |
-
"""
|
| 222 |
-
|
| 223 |
-
def __init__(
|
| 224 |
-
self, lmax, num_channels, eps=1e-5, affine=True, normalization="component"
|
| 225 |
-
):
|
| 226 |
-
super().__init__()
|
| 227 |
-
|
| 228 |
-
self.lmax = lmax
|
| 229 |
-
self.num_channels = num_channels
|
| 230 |
-
self.eps = eps
|
| 231 |
-
self.affine = affine
|
| 232 |
-
|
| 233 |
-
# for L >= 0
|
| 234 |
-
if self.affine:
|
| 235 |
-
self.affine_weight = nn.Parameter(
|
| 236 |
-
torch.ones((self.lmax + 1), self.num_channels)
|
| 237 |
-
)
|
| 238 |
-
else:
|
| 239 |
-
self.register_parameter("affine_weight", None)
|
| 240 |
-
|
| 241 |
-
assert normalization in ["norm", "component"]
|
| 242 |
-
self.normalization = normalization
|
| 243 |
-
|
| 244 |
-
def __repr__(self):
|
| 245 |
-
return f"{self.__class__.__name__}(lmax={self.lmax}, num_channels={self.num_channels}, eps={self.eps})"
|
| 246 |
-
|
| 247 |
-
@torch.amp.autocast("cuda", enabled=False)
|
| 248 |
-
def forward(self, node_input):
|
| 249 |
-
"""
|
| 250 |
-
Assume input is of shape [N, sphere_basis, C]
|
| 251 |
-
"""
|
| 252 |
-
|
| 253 |
-
out = []
|
| 254 |
-
|
| 255 |
-
# for L >= 0
|
| 256 |
-
feature = node_input
|
| 257 |
-
if self.normalization == "norm":
|
| 258 |
-
feature_norm = feature.pow(2).sum(dim=1, keepdim=True) # [N, 1, C]
|
| 259 |
-
elif self.normalization == "component":
|
| 260 |
-
feature_norm = feature.pow(2).mean(dim=1, keepdim=True) # [N, 1, C]
|
| 261 |
-
|
| 262 |
-
feature_norm = torch.mean(feature_norm, dim=2, keepdim=True) # [N, 1, 1]
|
| 263 |
-
feature_norm = (feature_norm + self.eps).pow(-0.5)
|
| 264 |
-
|
| 265 |
-
for l in range(0, self.lmax + 1):
|
| 266 |
-
start_idx = l**2
|
| 267 |
-
length = 2 * l + 1
|
| 268 |
-
feature = node_input.narrow(1, start_idx, length) # [N, (2L + 1), C]
|
| 269 |
-
if self.affine:
|
| 270 |
-
weight = self.affine_weight.narrow(0, l, 1) # [1, C]
|
| 271 |
-
weight = weight.view(1, 1, -1) # [1, 1, C]
|
| 272 |
-
feature_scale = feature_norm * weight # [N, 1, C]
|
| 273 |
-
else:
|
| 274 |
-
feature_scale = feature_norm
|
| 275 |
-
feature = feature * feature_scale
|
| 276 |
-
out.append(feature)
|
| 277 |
-
|
| 278 |
-
out = torch.cat(out, dim=1)
|
| 279 |
-
return out
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
class EquivariantRMSNormArraySphericalHarmonicsV2(nn.Module):
|
| 283 |
-
"""
|
| 284 |
-
1. Normalize across all m components from degrees L >= 0.
|
| 285 |
-
2. Expand weights and multiply with normalized feature to prevent slicing and concatenation.
|
| 286 |
-
"""
|
| 287 |
-
|
| 288 |
-
def __init__(
|
| 289 |
-
self,
|
| 290 |
-
lmax,
|
| 291 |
-
num_channels,
|
| 292 |
-
eps=1e-5,
|
| 293 |
-
affine=True,
|
| 294 |
-
normalization="component",
|
| 295 |
-
centering=True,
|
| 296 |
-
std_balance_degrees=True,
|
| 297 |
-
):
|
| 298 |
-
super().__init__()
|
| 299 |
-
|
| 300 |
-
self.lmax = lmax
|
| 301 |
-
self.num_channels = num_channels
|
| 302 |
-
self.eps = eps
|
| 303 |
-
self.affine = affine
|
| 304 |
-
self.centering = centering
|
| 305 |
-
self.std_balance_degrees = std_balance_degrees
|
| 306 |
-
|
| 307 |
-
# for L >= 0
|
| 308 |
-
if self.affine:
|
| 309 |
-
self.affine_weight = nn.Parameter(
|
| 310 |
-
torch.ones((self.lmax + 1), self.num_channels)
|
| 311 |
-
)
|
| 312 |
-
if self.centering:
|
| 313 |
-
self.affine_bias = nn.Parameter(torch.zeros(self.num_channels))
|
| 314 |
-
else:
|
| 315 |
-
self.register_parameter("affine_bias", None)
|
| 316 |
-
else:
|
| 317 |
-
self.register_parameter("affine_weight", None)
|
| 318 |
-
self.register_parameter("affine_bias", None)
|
| 319 |
-
|
| 320 |
-
assert normalization in ["norm", "component"]
|
| 321 |
-
self.normalization = normalization
|
| 322 |
-
|
| 323 |
-
expand_index = get_l_to_all_m_expand_index(self.lmax)
|
| 324 |
-
self.register_buffer("expand_index", expand_index)
|
| 325 |
-
|
| 326 |
-
if self.std_balance_degrees:
|
| 327 |
-
balance_degree_weight = torch.zeros((self.lmax + 1) ** 2, 1)
|
| 328 |
-
for l in range(self.lmax + 1):
|
| 329 |
-
start_idx = l**2
|
| 330 |
-
length = 2 * l + 1
|
| 331 |
-
balance_degree_weight[start_idx : (start_idx + length), :] = (
|
| 332 |
-
1.0 / length
|
| 333 |
-
)
|
| 334 |
-
balance_degree_weight = balance_degree_weight / (self.lmax + 1)
|
| 335 |
-
self.register_buffer("balance_degree_weight", balance_degree_weight)
|
| 336 |
-
else:
|
| 337 |
-
self.balance_degree_weight = None
|
| 338 |
-
|
| 339 |
-
def __repr__(self):
|
| 340 |
-
return f"{self.__class__.__name__}(lmax={self.lmax}, num_channels={self.num_channels}, eps={self.eps}, centering={self.centering}, std_balance_degrees={self.std_balance_degrees})"
|
| 341 |
-
|
| 342 |
-
@torch.amp.autocast("cuda", enabled=False)
|
| 343 |
-
def forward(self, node_input):
|
| 344 |
-
"""
|
| 345 |
-
Assume input is of shape [N, sphere_basis, C]
|
| 346 |
-
"""
|
| 347 |
-
|
| 348 |
-
feature = node_input
|
| 349 |
-
|
| 350 |
-
if self.centering:
|
| 351 |
-
feature_l0 = feature.narrow(1, 0, 1)
|
| 352 |
-
feature_l0_mean = feature_l0.mean(dim=2, keepdim=True) # [N, 1, 1]
|
| 353 |
-
feature_l0 = feature_l0 - feature_l0_mean
|
| 354 |
-
feature = torch.cat(
|
| 355 |
-
(feature_l0, feature.narrow(1, 1, feature.shape[1] - 1)), dim=1
|
| 356 |
-
)
|
| 357 |
-
|
| 358 |
-
# for L >= 0
|
| 359 |
-
if self.normalization == "norm":
|
| 360 |
-
assert not self.std_balance_degrees
|
| 361 |
-
feature_norm = feature.pow(2).sum(dim=1, keepdim=True) # [N, 1, C]
|
| 362 |
-
elif self.normalization == "component":
|
| 363 |
-
if self.std_balance_degrees:
|
| 364 |
-
feature_norm = feature.pow(2) # [N, (L_max + 1)**2, C]
|
| 365 |
-
feature_norm = torch.einsum(
|
| 366 |
-
"nic, ia -> nac", feature_norm, self.balance_degree_weight
|
| 367 |
-
) # [N, 1, C]
|
| 368 |
-
else:
|
| 369 |
-
feature_norm = feature.pow(2).mean(dim=1, keepdim=True) # [N, 1, C]
|
| 370 |
-
|
| 371 |
-
feature_norm = torch.mean(feature_norm, dim=2, keepdim=True) # [N, 1, 1]
|
| 372 |
-
feature_norm = (feature_norm + self.eps).pow(-0.5)
|
| 373 |
-
|
| 374 |
-
if self.affine:
|
| 375 |
-
weight = self.affine_weight.view(
|
| 376 |
-
1, (self.lmax + 1), self.num_channels
|
| 377 |
-
) # [1, L_max + 1, C]
|
| 378 |
-
weight = torch.index_select(
|
| 379 |
-
weight, dim=1, index=self.expand_index
|
| 380 |
-
) # [1, (L_max + 1)**2, C]
|
| 381 |
-
feature_norm = feature_norm * weight # [N, (L_max + 1)**2, C]
|
| 382 |
-
|
| 383 |
-
out = feature * feature_norm
|
| 384 |
-
|
| 385 |
-
if self.affine and self.centering:
|
| 386 |
-
out[:, 0:1, :] = out.narrow(1, 0, 1) + self.affine_bias.view(
|
| 387 |
-
1, 1, self.num_channels
|
| 388 |
-
)
|
| 389 |
-
|
| 390 |
-
return out
|
| 391 |
-
|
| 392 |
-
|
| 393 |
-
class EquivariantDegreeLayerScale(nn.Module):
|
| 394 |
-
"""
|
| 395 |
-
1. Similar to Layer Scale used in CaiT (Going Deeper With Image Transformers (ICCV'21)), we scale the output of both attention and FFN.
|
| 396 |
-
2. For degree L > 0, we scale down the square root of 2 * L, which is to emulate halving the number of channels when using higher L.
|
| 397 |
-
"""
|
| 398 |
-
|
| 399 |
-
def __init__(self, lmax, num_channels, scale_factor=2.0):
|
| 400 |
-
super().__init__()
|
| 401 |
-
|
| 402 |
-
self.lmax = lmax
|
| 403 |
-
self.num_channels = num_channels
|
| 404 |
-
self.scale_factor = scale_factor
|
| 405 |
-
|
| 406 |
-
self.affine_weight = nn.Parameter(
|
| 407 |
-
torch.ones(1, (self.lmax + 1), self.num_channels)
|
| 408 |
-
)
|
| 409 |
-
for l in range(1, self.lmax + 1):
|
| 410 |
-
self.affine_weight.data[0, l, :].mul_(
|
| 411 |
-
1.0 / math.sqrt(self.scale_factor * l)
|
| 412 |
-
)
|
| 413 |
-
expand_index = get_l_to_all_m_expand_index(self.lmax)
|
| 414 |
-
self.register_buffer("expand_index", expand_index)
|
| 415 |
-
|
| 416 |
-
def __repr__(self):
|
| 417 |
-
return f"{self.__class__.__name__}(lmax={self.lmax}, num_channels={self.num_channels}, scale_factor={self.scale_factor})"
|
| 418 |
-
|
| 419 |
-
def forward(self, node_input):
|
| 420 |
-
weight = torch.index_select(
|
| 421 |
-
self.affine_weight, dim=1, index=self.expand_index
|
| 422 |
-
) # [1, (L_max + 1)**2, C]
|
| 423 |
-
node_input = node_input * weight # [N, (L_max + 1)**2, C]
|
| 424 |
-
return node_input
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
nets/equiformer_v2/module_list.py
DELETED
|
@@ -1,10 +0,0 @@
|
|
| 1 |
-
import torch
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
class ModuleListInfo(torch.nn.ModuleList):
|
| 5 |
-
def __init__(self, info_str, modules=None):
|
| 6 |
-
super().__init__(modules)
|
| 7 |
-
self.info_str = str(info_str)
|
| 8 |
-
|
| 9 |
-
def __repr__(self):
|
| 10 |
-
return self.info_str
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
nets/equiformer_v2/radial_function.py
DELETED
|
@@ -1,30 +0,0 @@
|
|
| 1 |
-
import torch
|
| 2 |
-
import torch.nn as nn
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
class RadialFunction(nn.Module):
|
| 6 |
-
"""
|
| 7 |
-
Contruct a radial function (linear layers + layer normalization + SiLU) given a list of channels
|
| 8 |
-
"""
|
| 9 |
-
|
| 10 |
-
def __init__(self, channels_list):
|
| 11 |
-
super().__init__()
|
| 12 |
-
modules = []
|
| 13 |
-
input_channels = channels_list[0]
|
| 14 |
-
for i in range(len(channels_list)):
|
| 15 |
-
if i == 0:
|
| 16 |
-
continue
|
| 17 |
-
|
| 18 |
-
modules.append(nn.Linear(input_channels, channels_list[i], bias=True))
|
| 19 |
-
input_channels = channels_list[i]
|
| 20 |
-
|
| 21 |
-
if i == len(channels_list) - 1:
|
| 22 |
-
break
|
| 23 |
-
|
| 24 |
-
modules.append(nn.LayerNorm(channels_list[i]))
|
| 25 |
-
modules.append(torch.nn.SiLU())
|
| 26 |
-
|
| 27 |
-
self.net = nn.Sequential(*modules)
|
| 28 |
-
|
| 29 |
-
def forward(self, inputs):
|
| 30 |
-
return self.net(inputs)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
nets/equiformer_v2/so2_ops.py
DELETED
|
@@ -1,348 +0,0 @@
|
|
| 1 |
-
import torch
|
| 2 |
-
import torch.nn as nn
|
| 3 |
-
import math
|
| 4 |
-
import copy
|
| 5 |
-
|
| 6 |
-
from torch.nn import Linear
|
| 7 |
-
from .so3 import SO3_Embedding
|
| 8 |
-
from .radial_function import RadialFunction
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
class SO2_m_Convolution(torch.nn.Module):
|
| 12 |
-
"""
|
| 13 |
-
SO(2) Conv: Perform an SO(2) convolution on features corresponding to +- m
|
| 14 |
-
|
| 15 |
-
Args:
|
| 16 |
-
m (int): Order of the spherical harmonic coefficients
|
| 17 |
-
sphere_channels (int): Number of spherical channels
|
| 18 |
-
m_output_channels (int): Number of output channels used during the SO(2) conv
|
| 19 |
-
lmax_list (list:int): List of degrees (l) for each resolution
|
| 20 |
-
mmax_list (list:int): List of orders (m) for each resolution
|
| 21 |
-
"""
|
| 22 |
-
|
| 23 |
-
def __init__(self, m, sphere_channels, m_output_channels, lmax_list, mmax_list):
|
| 24 |
-
super(SO2_m_Convolution, self).__init__()
|
| 25 |
-
|
| 26 |
-
self.m = m
|
| 27 |
-
self.sphere_channels = sphere_channels
|
| 28 |
-
self.m_output_channels = m_output_channels
|
| 29 |
-
self.lmax_list = lmax_list
|
| 30 |
-
self.mmax_list = mmax_list
|
| 31 |
-
self.num_resolutions = len(self.lmax_list)
|
| 32 |
-
|
| 33 |
-
num_channels = 0
|
| 34 |
-
for i in range(self.num_resolutions):
|
| 35 |
-
num_coefficents = 0
|
| 36 |
-
if self.mmax_list[i] >= self.m:
|
| 37 |
-
num_coefficents = self.lmax_list[i] - self.m + 1
|
| 38 |
-
num_channels = num_channels + num_coefficents * self.sphere_channels
|
| 39 |
-
assert num_channels > 0
|
| 40 |
-
|
| 41 |
-
self.fc = Linear(
|
| 42 |
-
num_channels,
|
| 43 |
-
2 * self.m_output_channels * (num_channels // self.sphere_channels),
|
| 44 |
-
bias=False,
|
| 45 |
-
)
|
| 46 |
-
self.fc.weight.data.mul_(1 / math.sqrt(2))
|
| 47 |
-
|
| 48 |
-
def forward(self, x_m):
|
| 49 |
-
x_m = self.fc(x_m)
|
| 50 |
-
x_r = x_m.narrow(2, 0, self.fc.out_features // 2)
|
| 51 |
-
x_i = x_m.narrow(2, self.fc.out_features // 2, self.fc.out_features // 2)
|
| 52 |
-
x_m_r = x_r.narrow(1, 0, 1) - x_i.narrow(1, 1, 1) # x_r[:, 0] - x_i[:, 1]
|
| 53 |
-
x_m_i = x_r.narrow(1, 1, 1) + x_i.narrow(1, 0, 1) # x_r[:, 1] + x_i[:, 0]
|
| 54 |
-
x_out = torch.cat((x_m_r, x_m_i), dim=1)
|
| 55 |
-
|
| 56 |
-
return x_out
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
class SO2_Convolution(torch.nn.Module):
|
| 60 |
-
"""
|
| 61 |
-
SO(2) Block: Perform SO(2) convolutions for all m (orders)
|
| 62 |
-
|
| 63 |
-
Args:
|
| 64 |
-
sphere_channels (int): Number of spherical channels
|
| 65 |
-
m_output_channels (int): Number of output channels used during the SO(2) conv
|
| 66 |
-
lmax_list (list:int): List of degrees (l) for each resolution
|
| 67 |
-
mmax_list (list:int): List of orders (m) for each resolution
|
| 68 |
-
mappingReduced (CoefficientMappingModule): Used to extract a subset of m components
|
| 69 |
-
internal_weights (bool): If True, not using radial function to multiply inputs features
|
| 70 |
-
edge_channels_list (list:int): List of sizes of invariant edge embedding. For example, [input_channels, hidden_channels, hidden_channels].
|
| 71 |
-
extra_m0_output_channels (int): If not None, return `out_embedding` (SO3_Embedding) and `extra_m0_features` (Tensor).
|
| 72 |
-
"""
|
| 73 |
-
|
| 74 |
-
def __init__(
|
| 75 |
-
self,
|
| 76 |
-
sphere_channels,
|
| 77 |
-
m_output_channels,
|
| 78 |
-
lmax_list,
|
| 79 |
-
mmax_list,
|
| 80 |
-
mappingReduced,
|
| 81 |
-
internal_weights=True,
|
| 82 |
-
edge_channels_list=None,
|
| 83 |
-
extra_m0_output_channels=None,
|
| 84 |
-
):
|
| 85 |
-
super(SO2_Convolution, self).__init__()
|
| 86 |
-
self.sphere_channels = sphere_channels
|
| 87 |
-
self.m_output_channels = m_output_channels
|
| 88 |
-
self.lmax_list = lmax_list
|
| 89 |
-
self.mmax_list = mmax_list
|
| 90 |
-
self.mappingReduced = mappingReduced
|
| 91 |
-
self.num_resolutions = len(lmax_list)
|
| 92 |
-
self.internal_weights = internal_weights
|
| 93 |
-
self.edge_channels_list = copy.deepcopy(edge_channels_list)
|
| 94 |
-
self.extra_m0_output_channels = extra_m0_output_channels
|
| 95 |
-
|
| 96 |
-
num_channels_rad = 0 # for radial function
|
| 97 |
-
|
| 98 |
-
num_channels_m0 = 0
|
| 99 |
-
for i in range(self.num_resolutions):
|
| 100 |
-
num_coefficients = self.lmax_list[i] + 1
|
| 101 |
-
num_channels_m0 = num_channels_m0 + num_coefficients * self.sphere_channels
|
| 102 |
-
|
| 103 |
-
# SO(2) convolution for m = 0
|
| 104 |
-
m0_output_channels = self.m_output_channels * (
|
| 105 |
-
num_channels_m0 // self.sphere_channels
|
| 106 |
-
)
|
| 107 |
-
if self.extra_m0_output_channels is not None:
|
| 108 |
-
m0_output_channels = m0_output_channels + self.extra_m0_output_channels
|
| 109 |
-
self.fc_m0 = Linear(num_channels_m0, m0_output_channels)
|
| 110 |
-
num_channels_rad = num_channels_rad + self.fc_m0.in_features
|
| 111 |
-
|
| 112 |
-
# SO(2) convolution for non-zero m
|
| 113 |
-
self.so2_m_conv = nn.ModuleList()
|
| 114 |
-
for m in range(1, max(self.mmax_list) + 1):
|
| 115 |
-
self.so2_m_conv.append(
|
| 116 |
-
SO2_m_Convolution(
|
| 117 |
-
m,
|
| 118 |
-
self.sphere_channels,
|
| 119 |
-
self.m_output_channels,
|
| 120 |
-
self.lmax_list,
|
| 121 |
-
self.mmax_list,
|
| 122 |
-
)
|
| 123 |
-
)
|
| 124 |
-
num_channels_rad = num_channels_rad + self.so2_m_conv[-1].fc.in_features
|
| 125 |
-
|
| 126 |
-
# Embedding function of distance
|
| 127 |
-
self.rad_func = None
|
| 128 |
-
if not self.internal_weights:
|
| 129 |
-
assert self.edge_channels_list is not None
|
| 130 |
-
self.edge_channels_list.append(int(num_channels_rad))
|
| 131 |
-
self.rad_func = RadialFunction(self.edge_channels_list)
|
| 132 |
-
|
| 133 |
-
def forward(self, x, x_edge):
|
| 134 |
-
|
| 135 |
-
num_edges = len(x_edge)
|
| 136 |
-
out = []
|
| 137 |
-
|
| 138 |
-
# Reshape the spherical harmonics based on m (order)
|
| 139 |
-
x._m_primary(self.mappingReduced)
|
| 140 |
-
|
| 141 |
-
# radial function
|
| 142 |
-
if self.rad_func is not None:
|
| 143 |
-
x_edge = self.rad_func(x_edge)
|
| 144 |
-
offset_rad = 0
|
| 145 |
-
|
| 146 |
-
# Compute m=0 coefficients separately since they only have real values (no imaginary)
|
| 147 |
-
x_0 = x.embedding.narrow(1, 0, self.mappingReduced.m_size[0])
|
| 148 |
-
x_0 = x_0.reshape(num_edges, -1)
|
| 149 |
-
if self.rad_func is not None:
|
| 150 |
-
x_edge_0 = x_edge.narrow(1, 0, self.fc_m0.in_features)
|
| 151 |
-
x_0 = x_0 * x_edge_0
|
| 152 |
-
x_0 = self.fc_m0(x_0)
|
| 153 |
-
|
| 154 |
-
x_0_extra = None
|
| 155 |
-
# extract extra m0 features
|
| 156 |
-
if self.extra_m0_output_channels is not None:
|
| 157 |
-
x_0_extra = x_0.narrow(-1, 0, self.extra_m0_output_channels)
|
| 158 |
-
x_0 = x_0.narrow(
|
| 159 |
-
-1,
|
| 160 |
-
self.extra_m0_output_channels,
|
| 161 |
-
(self.fc_m0.out_features - self.extra_m0_output_channels),
|
| 162 |
-
)
|
| 163 |
-
|
| 164 |
-
x_0 = x_0.view(num_edges, -1, self.m_output_channels)
|
| 165 |
-
# x.embedding[:, 0 : self.mappingReduced.m_size[0]] = x_0
|
| 166 |
-
out.append(x_0)
|
| 167 |
-
offset_rad = offset_rad + self.fc_m0.in_features
|
| 168 |
-
|
| 169 |
-
# Compute the values for the m > 0 coefficients
|
| 170 |
-
offset = self.mappingReduced.m_size[0]
|
| 171 |
-
for m in range(1, max(self.mmax_list) + 1):
|
| 172 |
-
# Get the m order coefficients
|
| 173 |
-
x_m = x.embedding.narrow(1, offset, 2 * self.mappingReduced.m_size[m])
|
| 174 |
-
x_m = x_m.reshape(num_edges, 2, -1)
|
| 175 |
-
|
| 176 |
-
# Perform SO(2) convolution
|
| 177 |
-
if self.rad_func is not None:
|
| 178 |
-
x_edge_m = x_edge.narrow(
|
| 179 |
-
1, offset_rad, self.so2_m_conv[m - 1].fc.in_features
|
| 180 |
-
)
|
| 181 |
-
x_edge_m = x_edge_m.reshape(
|
| 182 |
-
num_edges, 1, self.so2_m_conv[m - 1].fc.in_features
|
| 183 |
-
)
|
| 184 |
-
x_m = x_m * x_edge_m
|
| 185 |
-
x_m = self.so2_m_conv[m - 1](x_m)
|
| 186 |
-
x_m = x_m.view(num_edges, -1, self.m_output_channels)
|
| 187 |
-
# x.embedding[:, offset : offset + 2 * self.mappingReduced.m_size[m]] = x_m
|
| 188 |
-
out.append(x_m)
|
| 189 |
-
offset = offset + 2 * self.mappingReduced.m_size[m]
|
| 190 |
-
offset_rad = offset_rad + self.so2_m_conv[m - 1].fc.in_features
|
| 191 |
-
|
| 192 |
-
out = torch.cat(out, dim=1)
|
| 193 |
-
out_embedding = SO3_Embedding(
|
| 194 |
-
0,
|
| 195 |
-
x.lmax_list.copy(),
|
| 196 |
-
self.m_output_channels,
|
| 197 |
-
device=x.device,
|
| 198 |
-
dtype=x.dtype,
|
| 199 |
-
)
|
| 200 |
-
out_embedding.set_embedding(out)
|
| 201 |
-
out_embedding.set_lmax_mmax(self.lmax_list.copy(), self.mmax_list.copy())
|
| 202 |
-
|
| 203 |
-
# Reshape the spherical harmonics based on l (degree)
|
| 204 |
-
out_embedding._l_primary(self.mappingReduced)
|
| 205 |
-
|
| 206 |
-
if self.extra_m0_output_channels is not None:
|
| 207 |
-
return out_embedding, x_0_extra
|
| 208 |
-
else:
|
| 209 |
-
return out_embedding
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
class SO2_Linear(torch.nn.Module):
|
| 213 |
-
"""
|
| 214 |
-
SO(2) Linear: Perform SO(2) linear for all m (orders).
|
| 215 |
-
|
| 216 |
-
Args:
|
| 217 |
-
sphere_channels (int): Number of spherical channels
|
| 218 |
-
m_output_channels (int): Number of output channels used during the SO(2) conv
|
| 219 |
-
lmax_list (list:int): List of degrees (l) for each resolution
|
| 220 |
-
mmax_list (list:int): List of orders (m) for each resolution
|
| 221 |
-
mappingReduced (CoefficientMappingModule): Used to extract a subset of m components
|
| 222 |
-
internal_weights (bool): If True, not using radial function to multiply inputs features
|
| 223 |
-
edge_channels_list (list:int): List of sizes of invariant edge embedding. For example, [input_channels, hidden_channels, hidden_channels].
|
| 224 |
-
"""
|
| 225 |
-
|
| 226 |
-
def __init__(
|
| 227 |
-
self,
|
| 228 |
-
sphere_channels,
|
| 229 |
-
m_output_channels,
|
| 230 |
-
lmax_list,
|
| 231 |
-
mmax_list,
|
| 232 |
-
mappingReduced,
|
| 233 |
-
internal_weights=False,
|
| 234 |
-
edge_channels_list=None,
|
| 235 |
-
):
|
| 236 |
-
super(SO2_Linear, self).__init__()
|
| 237 |
-
self.sphere_channels = sphere_channels
|
| 238 |
-
self.m_output_channels = m_output_channels
|
| 239 |
-
self.lmax_list = lmax_list
|
| 240 |
-
self.mmax_list = mmax_list
|
| 241 |
-
self.mappingReduced = mappingReduced
|
| 242 |
-
self.internal_weights = internal_weights
|
| 243 |
-
self.edge_channels_list = copy.deepcopy(edge_channels_list)
|
| 244 |
-
self.num_resolutions = len(lmax_list)
|
| 245 |
-
|
| 246 |
-
num_channels_rad = 0
|
| 247 |
-
|
| 248 |
-
num_channels_m0 = 0
|
| 249 |
-
for i in range(self.num_resolutions):
|
| 250 |
-
num_coefficients = self.lmax_list[i] + 1
|
| 251 |
-
num_channels_m0 = num_channels_m0 + num_coefficients * self.sphere_channels
|
| 252 |
-
|
| 253 |
-
# SO(2) linear for m = 0
|
| 254 |
-
self.fc_m0 = Linear(
|
| 255 |
-
num_channels_m0,
|
| 256 |
-
self.m_output_channels * (num_channels_m0 // self.sphere_channels),
|
| 257 |
-
)
|
| 258 |
-
num_channels_rad = num_channels_rad + self.fc_m0.in_features
|
| 259 |
-
|
| 260 |
-
# SO(2) linear for non-zero m
|
| 261 |
-
self.so2_m_fc = nn.ModuleList()
|
| 262 |
-
for m in range(1, max(self.mmax_list) + 1):
|
| 263 |
-
num_in_channels = 0
|
| 264 |
-
for i in range(self.num_resolutions):
|
| 265 |
-
num_coefficents = 0
|
| 266 |
-
if self.mmax_list[i] >= m:
|
| 267 |
-
num_coefficents = self.lmax_list[i] - m + 1
|
| 268 |
-
num_in_channels = (
|
| 269 |
-
num_in_channels + num_coefficents * self.sphere_channels
|
| 270 |
-
)
|
| 271 |
-
assert num_in_channels > 0
|
| 272 |
-
fc = Linear(
|
| 273 |
-
num_in_channels,
|
| 274 |
-
self.m_output_channels * (num_in_channels // self.sphere_channels),
|
| 275 |
-
bias=False,
|
| 276 |
-
)
|
| 277 |
-
num_channels_rad = num_channels_rad + fc.in_features
|
| 278 |
-
self.so2_m_fc.append(fc)
|
| 279 |
-
|
| 280 |
-
# Embedding function of distance
|
| 281 |
-
self.rad_func = None
|
| 282 |
-
if not self.internal_weights:
|
| 283 |
-
assert self.edge_channels_list is not None
|
| 284 |
-
self.edge_channels_list.append(int(num_channels_rad))
|
| 285 |
-
self.rad_func = RadialFunction(self.edge_channels_list)
|
| 286 |
-
|
| 287 |
-
def forward(self, x, x_edge):
|
| 288 |
-
|
| 289 |
-
batch_size = x.embedding.shape[0]
|
| 290 |
-
out = []
|
| 291 |
-
|
| 292 |
-
# Reshape the spherical harmonics based on m (order)
|
| 293 |
-
x._m_primary(self.mappingReduced)
|
| 294 |
-
|
| 295 |
-
# radial function
|
| 296 |
-
if self.rad_func is not None:
|
| 297 |
-
x_edge = self.rad_func(x_edge)
|
| 298 |
-
offset_rad = 0
|
| 299 |
-
|
| 300 |
-
# Compute m=0 coefficients separately since they only have real values (no imaginary)
|
| 301 |
-
x_0 = x.embedding.narrow(1, 0, self.mappingReduced.m_size[0])
|
| 302 |
-
x_0 = x_0.reshape(batch_size, -1)
|
| 303 |
-
if self.rad_func is not None:
|
| 304 |
-
x_edge_0 = x_edge.narrow(1, 0, self.fc_m0.in_features)
|
| 305 |
-
x_0 = x_0 * x_edge_0
|
| 306 |
-
x_0 = self.fc_m0(x_0)
|
| 307 |
-
x_0 = x_0.view(batch_size, -1, self.m_output_channels)
|
| 308 |
-
out.append(x_0)
|
| 309 |
-
offset_rad = offset_rad + self.fc_m0.in_features
|
| 310 |
-
|
| 311 |
-
# Compute the values for the m > 0 coefficients
|
| 312 |
-
offset = self.mappingReduced.m_size[0]
|
| 313 |
-
for m in range(1, max(self.mmax_list) + 1):
|
| 314 |
-
# Get the m order coefficients
|
| 315 |
-
x_m = x.embedding.narrow(1, offset, 2 * self.mappingReduced.m_size[m])
|
| 316 |
-
x_m = x_m.reshape(batch_size, 2, -1)
|
| 317 |
-
if self.rad_func is not None:
|
| 318 |
-
x_edge_m = x_edge.narrow(
|
| 319 |
-
1, offset_rad, self.so2_m_fc[m - 1].in_features
|
| 320 |
-
)
|
| 321 |
-
x_edge_m = x_edge_m.reshape(
|
| 322 |
-
batch_size, 1, self.so2_m_fc[m - 1].in_features
|
| 323 |
-
)
|
| 324 |
-
x_m = x_m * x_edge_m
|
| 325 |
-
|
| 326 |
-
# Perform SO(2) linear
|
| 327 |
-
x_m = self.so2_m_fc[m - 1](x_m)
|
| 328 |
-
x_m = x_m.view(batch_size, -1, self.m_output_channels)
|
| 329 |
-
out.append(x_m)
|
| 330 |
-
|
| 331 |
-
offset = offset + 2 * self.mappingReduced.m_size[m]
|
| 332 |
-
offset_rad = offset_rad + self.so2_m_fc[m - 1].in_features
|
| 333 |
-
|
| 334 |
-
out = torch.cat(out, dim=1)
|
| 335 |
-
out_embedding = SO3_Embedding(
|
| 336 |
-
0,
|
| 337 |
-
x.lmax_list.copy(),
|
| 338 |
-
self.m_output_channels,
|
| 339 |
-
device=x.device,
|
| 340 |
-
dtype=x.dtype,
|
| 341 |
-
)
|
| 342 |
-
out_embedding.set_embedding(out)
|
| 343 |
-
out_embedding.set_lmax_mmax(self.lmax_list.copy(), self.mmax_list.copy())
|
| 344 |
-
|
| 345 |
-
# Reshape the spherical harmonics based on l (degree)
|
| 346 |
-
out_embedding._l_primary(self.mappingReduced)
|
| 347 |
-
|
| 348 |
-
return out_embedding
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
nets/equiformer_v2/so3.py
DELETED
|
@@ -1,682 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
Copyright (c) Facebook, Inc. and its affiliates.
|
| 3 |
-
|
| 4 |
-
This source code is licensed under the MIT license found in the
|
| 5 |
-
LICENSE file in the root directory of this source tree.
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
TODO:
|
| 9 |
-
1. Simplify the case when `num_resolutions` == 1.
|
| 10 |
-
2. Remove indexing when the shape is the same.
|
| 11 |
-
3. Move some functions outside classes and to separate files.
|
| 12 |
-
"""
|
| 13 |
-
|
| 14 |
-
import os
|
| 15 |
-
import math
|
| 16 |
-
import torch
|
| 17 |
-
import torch.nn as nn
|
| 18 |
-
|
| 19 |
-
try:
|
| 20 |
-
from e3nn import o3
|
| 21 |
-
from e3nn.o3 import FromS2Grid, ToS2Grid
|
| 22 |
-
except ImportError:
|
| 23 |
-
pass
|
| 24 |
-
|
| 25 |
-
from .wigner import wigner_D
|
| 26 |
-
from torch.nn import Linear
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
class CoefficientMappingModule(torch.nn.Module):
|
| 30 |
-
"""
|
| 31 |
-
Helper module for coefficients used to reshape l <--> m and to get coefficients of specific degree or order
|
| 32 |
-
|
| 33 |
-
Args:
|
| 34 |
-
lmax_list (list:int): List of maximum degree of the spherical harmonics
|
| 35 |
-
mmax_list (list:int): List of maximum order of the spherical harmonics
|
| 36 |
-
"""
|
| 37 |
-
|
| 38 |
-
def __init__(
|
| 39 |
-
self,
|
| 40 |
-
lmax_list,
|
| 41 |
-
mmax_list,
|
| 42 |
-
):
|
| 43 |
-
super().__init__()
|
| 44 |
-
|
| 45 |
-
self.lmax_list = lmax_list
|
| 46 |
-
self.mmax_list = mmax_list
|
| 47 |
-
self.num_resolutions = len(lmax_list)
|
| 48 |
-
|
| 49 |
-
# Temporarily use `cpu` as device and this will be overwritten.
|
| 50 |
-
self.device = "cpu"
|
| 51 |
-
|
| 52 |
-
# Compute the degree (l) and order (m) for each entry of the embedding
|
| 53 |
-
l_harmonic = torch.tensor([], device=self.device).long()
|
| 54 |
-
m_harmonic = torch.tensor([], device=self.device).long()
|
| 55 |
-
m_complex = torch.tensor([], device=self.device).long()
|
| 56 |
-
|
| 57 |
-
res_size = torch.zeros([self.num_resolutions], device=self.device).long()
|
| 58 |
-
|
| 59 |
-
offset = 0
|
| 60 |
-
for i in range(self.num_resolutions):
|
| 61 |
-
for l in range(0, self.lmax_list[i] + 1):
|
| 62 |
-
mmax = min(self.mmax_list[i], l)
|
| 63 |
-
m = torch.arange(-mmax, mmax + 1, device=self.device).long()
|
| 64 |
-
m_complex = torch.cat([m_complex, m], dim=0)
|
| 65 |
-
m_harmonic = torch.cat([m_harmonic, torch.abs(m).long()], dim=0)
|
| 66 |
-
l_harmonic = torch.cat([l_harmonic, m.fill_(l).long()], dim=0)
|
| 67 |
-
res_size[i] = len(l_harmonic) - offset
|
| 68 |
-
offset = len(l_harmonic)
|
| 69 |
-
|
| 70 |
-
num_coefficients = len(l_harmonic)
|
| 71 |
-
# `self.to_m` moves m components from different L to contiguous index
|
| 72 |
-
to_m = torch.zeros([num_coefficients, num_coefficients], device=self.device)
|
| 73 |
-
m_size = torch.zeros([max(self.mmax_list) + 1], device=self.device).long()
|
| 74 |
-
|
| 75 |
-
# The following is implemented poorly - very slow. It only gets called
|
| 76 |
-
# a few times so haven't optimized.
|
| 77 |
-
offset = 0
|
| 78 |
-
for m in range(max(self.mmax_list) + 1):
|
| 79 |
-
idx_r, idx_i = self.complex_idx(m, -1, m_complex, l_harmonic)
|
| 80 |
-
|
| 81 |
-
for idx_out, idx_in in enumerate(idx_r):
|
| 82 |
-
to_m[idx_out + offset, idx_in] = 1.0
|
| 83 |
-
offset = offset + len(idx_r)
|
| 84 |
-
|
| 85 |
-
m_size[m] = int(len(idx_r))
|
| 86 |
-
|
| 87 |
-
for idx_out, idx_in in enumerate(idx_i):
|
| 88 |
-
to_m[idx_out + offset, idx_in] = 1.0
|
| 89 |
-
offset = offset + len(idx_i)
|
| 90 |
-
|
| 91 |
-
to_m = to_m.detach()
|
| 92 |
-
|
| 93 |
-
# save tensors and they will be moved to GPU
|
| 94 |
-
self.register_buffer("l_harmonic", l_harmonic)
|
| 95 |
-
self.register_buffer("m_harmonic", m_harmonic)
|
| 96 |
-
self.register_buffer("m_complex", m_complex)
|
| 97 |
-
self.register_buffer("res_size", res_size)
|
| 98 |
-
self.register_buffer("to_m", to_m)
|
| 99 |
-
self.register_buffer("m_size", m_size)
|
| 100 |
-
|
| 101 |
-
# for caching the output of `coefficient_idx`
|
| 102 |
-
self.lmax_cache, self.mmax_cache = None, None
|
| 103 |
-
self.mask_indices_cache = None
|
| 104 |
-
self.rotate_inv_rescale_cache = None
|
| 105 |
-
|
| 106 |
-
# Return mask containing coefficients of order m (real and imaginary parts)
|
| 107 |
-
def complex_idx(self, m, lmax, m_complex, l_harmonic):
|
| 108 |
-
"""
|
| 109 |
-
Add `m_complex` and `l_harmonic` to the input arguments
|
| 110 |
-
since we cannot use `self.m_complex`.
|
| 111 |
-
"""
|
| 112 |
-
if lmax == -1:
|
| 113 |
-
lmax = max(self.lmax_list)
|
| 114 |
-
|
| 115 |
-
indices = torch.arange(len(l_harmonic), device=self.device)
|
| 116 |
-
# Real part
|
| 117 |
-
mask_r = torch.bitwise_and(l_harmonic.le(lmax), m_complex.eq(m))
|
| 118 |
-
mask_idx_r = torch.masked_select(indices, mask_r)
|
| 119 |
-
|
| 120 |
-
mask_idx_i = torch.tensor([], device=self.device).long()
|
| 121 |
-
# Imaginary part
|
| 122 |
-
if m != 0:
|
| 123 |
-
mask_i = torch.bitwise_and(l_harmonic.le(lmax), m_complex.eq(-m))
|
| 124 |
-
mask_idx_i = torch.masked_select(indices, mask_i)
|
| 125 |
-
|
| 126 |
-
return mask_idx_r, mask_idx_i
|
| 127 |
-
|
| 128 |
-
# Return mask containing coefficients less than or equal to degree (l) and order (m)
|
| 129 |
-
def coefficient_idx(self, lmax, mmax):
|
| 130 |
-
|
| 131 |
-
if (self.lmax_cache is not None) and (self.mmax_cache is not None):
|
| 132 |
-
if (self.lmax_cache == lmax) and (self.mmax_cache == mmax):
|
| 133 |
-
if self.mask_indices_cache is not None:
|
| 134 |
-
return self.mask_indices_cache
|
| 135 |
-
|
| 136 |
-
mask = torch.bitwise_and(self.l_harmonic.le(lmax), self.m_harmonic.le(mmax))
|
| 137 |
-
self.device = mask.device
|
| 138 |
-
indices = torch.arange(len(mask), device=self.device)
|
| 139 |
-
mask_indices = torch.masked_select(indices, mask)
|
| 140 |
-
self.lmax_cache, self.mmax_cache = lmax, mmax
|
| 141 |
-
self.mask_indices_cache = mask_indices
|
| 142 |
-
return self.mask_indices_cache
|
| 143 |
-
|
| 144 |
-
# Return the re-scaling for rotating back to original frame
|
| 145 |
-
# this is required since we only use a subset of m components for SO(2) convolution
|
| 146 |
-
def get_rotate_inv_rescale(self, lmax, mmax):
|
| 147 |
-
|
| 148 |
-
if (self.lmax_cache is not None) and (self.mmax_cache is not None):
|
| 149 |
-
if (self.lmax_cache == lmax) and (self.mmax_cache == mmax):
|
| 150 |
-
if self.rotate_inv_rescale_cache is not None:
|
| 151 |
-
return self.rotate_inv_rescale_cache
|
| 152 |
-
|
| 153 |
-
if self.mask_indices_cache is None:
|
| 154 |
-
self.coefficient_idx(lmax, mmax)
|
| 155 |
-
|
| 156 |
-
rotate_inv_rescale = torch.ones(
|
| 157 |
-
(1, (lmax + 1) ** 2, (lmax + 1) ** 2), device=self.device
|
| 158 |
-
)
|
| 159 |
-
for l in range(lmax + 1):
|
| 160 |
-
if l <= mmax:
|
| 161 |
-
continue
|
| 162 |
-
start_idx = l**2
|
| 163 |
-
length = 2 * l + 1
|
| 164 |
-
rescale_factor = math.sqrt(length / (2 * mmax + 1))
|
| 165 |
-
rotate_inv_rescale[
|
| 166 |
-
:, start_idx : (start_idx + length), start_idx : (start_idx + length)
|
| 167 |
-
] = rescale_factor
|
| 168 |
-
rotate_inv_rescale = rotate_inv_rescale[:, :, self.mask_indices_cache]
|
| 169 |
-
self.rotate_inv_rescale_cache = rotate_inv_rescale
|
| 170 |
-
return self.rotate_inv_rescale_cache
|
| 171 |
-
|
| 172 |
-
def __repr__(self):
|
| 173 |
-
return f"{self.__class__.__name__}(lmax_list={self.lmax_list}, mmax_list={self.mmax_list})"
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
class SO3_Embedding:
|
| 177 |
-
"""
|
| 178 |
-
Helper functions for performing operations on irreps embedding
|
| 179 |
-
|
| 180 |
-
Args:
|
| 181 |
-
length (int): Batch size
|
| 182 |
-
lmax_list (list:int): List of maximum degree of the spherical harmonics
|
| 183 |
-
num_channels (int): Number of channels
|
| 184 |
-
device: Device of the output
|
| 185 |
-
dtype: type of the output tensors
|
| 186 |
-
"""
|
| 187 |
-
|
| 188 |
-
def __init__(
|
| 189 |
-
self,
|
| 190 |
-
length,
|
| 191 |
-
lmax_list,
|
| 192 |
-
num_channels,
|
| 193 |
-
device,
|
| 194 |
-
dtype,
|
| 195 |
-
):
|
| 196 |
-
super().__init__()
|
| 197 |
-
self.num_channels = num_channels
|
| 198 |
-
self.device = device
|
| 199 |
-
self.dtype = dtype
|
| 200 |
-
self.num_resolutions = len(lmax_list)
|
| 201 |
-
|
| 202 |
-
self.num_coefficients = 0
|
| 203 |
-
for i in range(self.num_resolutions):
|
| 204 |
-
self.num_coefficients = self.num_coefficients + int((lmax_list[i] + 1) ** 2)
|
| 205 |
-
|
| 206 |
-
embedding = torch.zeros(
|
| 207 |
-
length,
|
| 208 |
-
self.num_coefficients,
|
| 209 |
-
self.num_channels,
|
| 210 |
-
device=self.device,
|
| 211 |
-
dtype=self.dtype,
|
| 212 |
-
)
|
| 213 |
-
|
| 214 |
-
self.set_embedding(embedding)
|
| 215 |
-
self.set_lmax_mmax(lmax_list, lmax_list.copy())
|
| 216 |
-
|
| 217 |
-
# Clone an embedding of irreps
|
| 218 |
-
def clone(self):
|
| 219 |
-
clone = SO3_Embedding(
|
| 220 |
-
0,
|
| 221 |
-
self.lmax_list.copy(),
|
| 222 |
-
self.num_channels,
|
| 223 |
-
self.device,
|
| 224 |
-
self.dtype,
|
| 225 |
-
)
|
| 226 |
-
clone.set_embedding(self.embedding.clone())
|
| 227 |
-
return clone
|
| 228 |
-
|
| 229 |
-
# Initialize an embedding of irreps
|
| 230 |
-
def set_embedding(self, embedding):
|
| 231 |
-
self.length = len(embedding)
|
| 232 |
-
self.embedding = embedding
|
| 233 |
-
|
| 234 |
-
# Set the maximum order to be the maximum degree
|
| 235 |
-
def set_lmax_mmax(self, lmax_list, mmax_list):
|
| 236 |
-
self.lmax_list = lmax_list
|
| 237 |
-
self.mmax_list = mmax_list
|
| 238 |
-
|
| 239 |
-
# Expand the node embeddings to the number of edges
|
| 240 |
-
def _expand_edge(self, edge_index):
|
| 241 |
-
embedding = self.embedding[edge_index]
|
| 242 |
-
self.set_embedding(embedding)
|
| 243 |
-
|
| 244 |
-
# Initialize an embedding of irreps of a neighborhood
|
| 245 |
-
def expand_edge(self, edge_index):
|
| 246 |
-
x_expand = SO3_Embedding(
|
| 247 |
-
0,
|
| 248 |
-
self.lmax_list.copy(),
|
| 249 |
-
self.num_channels,
|
| 250 |
-
self.device,
|
| 251 |
-
self.dtype,
|
| 252 |
-
)
|
| 253 |
-
x_expand.set_embedding(self.embedding[edge_index])
|
| 254 |
-
return x_expand
|
| 255 |
-
|
| 256 |
-
# Compute the sum of the embeddings of the neighborhood
|
| 257 |
-
def _reduce_edge(self, edge_index, num_nodes):
|
| 258 |
-
new_embedding = torch.zeros(
|
| 259 |
-
num_nodes,
|
| 260 |
-
self.num_coefficients,
|
| 261 |
-
self.num_channels,
|
| 262 |
-
device=self.embedding.device,
|
| 263 |
-
dtype=self.embedding.dtype,
|
| 264 |
-
)
|
| 265 |
-
new_embedding.index_add_(0, edge_index, self.embedding)
|
| 266 |
-
self.set_embedding(new_embedding)
|
| 267 |
-
|
| 268 |
-
# Reshape the embedding l -> m
|
| 269 |
-
def _m_primary(self, mapping):
|
| 270 |
-
self.embedding = torch.einsum("nac, ba -> nbc", self.embedding, mapping.to_m)
|
| 271 |
-
|
| 272 |
-
# Reshape the embedding m -> l
|
| 273 |
-
def _l_primary(self, mapping):
|
| 274 |
-
self.embedding = torch.einsum("nac, ab -> nbc", self.embedding, mapping.to_m)
|
| 275 |
-
|
| 276 |
-
# Rotate the embedding
|
| 277 |
-
def _rotate(self, SO3_rotation, lmax_list, mmax_list):
|
| 278 |
-
|
| 279 |
-
if self.num_resolutions == 1:
|
| 280 |
-
embedding_rotate = SO3_rotation[0].rotate(
|
| 281 |
-
self.embedding, lmax_list[0], mmax_list[0]
|
| 282 |
-
)
|
| 283 |
-
else:
|
| 284 |
-
offset = 0
|
| 285 |
-
embedding_rotate = torch.tensor([], device=self.device, dtype=self.dtype)
|
| 286 |
-
for i in range(self.num_resolutions):
|
| 287 |
-
num_coefficients = int((self.lmax_list[i] + 1) ** 2)
|
| 288 |
-
embedding_i = self.embedding[:, offset : offset + num_coefficients]
|
| 289 |
-
embedding_rotate = torch.cat(
|
| 290 |
-
[
|
| 291 |
-
embedding_rotate,
|
| 292 |
-
SO3_rotation[i].rotate(embedding_i, lmax_list[i], mmax_list[i]),
|
| 293 |
-
],
|
| 294 |
-
dim=1,
|
| 295 |
-
)
|
| 296 |
-
offset = offset + num_coefficients
|
| 297 |
-
|
| 298 |
-
self.embedding = embedding_rotate
|
| 299 |
-
self.set_lmax_mmax(lmax_list.copy(), mmax_list.copy())
|
| 300 |
-
|
| 301 |
-
# Rotate the embedding by the inverse of the rotation matrix
|
| 302 |
-
def _rotate_inv(self, SO3_rotation, mappingReduced):
|
| 303 |
-
|
| 304 |
-
if self.num_resolutions == 1:
|
| 305 |
-
embedding_rotate = SO3_rotation[0].rotate_inv(
|
| 306 |
-
self.embedding, self.lmax_list[0], self.mmax_list[0]
|
| 307 |
-
)
|
| 308 |
-
else:
|
| 309 |
-
offset = 0
|
| 310 |
-
embedding_rotate = torch.tensor([], device=self.device, dtype=self.dtype)
|
| 311 |
-
for i in range(self.num_resolutions):
|
| 312 |
-
num_coefficients = mappingReduced.res_size[i]
|
| 313 |
-
embedding_i = self.embedding[:, offset : offset + num_coefficients]
|
| 314 |
-
embedding_rotate = torch.cat(
|
| 315 |
-
[
|
| 316 |
-
embedding_rotate,
|
| 317 |
-
SO3_rotation[i].rotate_inv(
|
| 318 |
-
embedding_i, self.lmax_list[i], self.mmax_list[i]
|
| 319 |
-
),
|
| 320 |
-
],
|
| 321 |
-
dim=1,
|
| 322 |
-
)
|
| 323 |
-
offset = offset + num_coefficients
|
| 324 |
-
self.embedding = embedding_rotate
|
| 325 |
-
|
| 326 |
-
# Assume mmax = lmax when rotating back
|
| 327 |
-
for i in range(self.num_resolutions):
|
| 328 |
-
self.mmax_list[i] = int(self.lmax_list[i])
|
| 329 |
-
self.set_lmax_mmax(self.lmax_list, self.mmax_list)
|
| 330 |
-
|
| 331 |
-
# Compute point-wise spherical non-linearity
|
| 332 |
-
def _grid_act(self, SO3_grid, act, mappingReduced):
|
| 333 |
-
offset = 0
|
| 334 |
-
for i in range(self.num_resolutions):
|
| 335 |
-
|
| 336 |
-
num_coefficients = mappingReduced.res_size[i]
|
| 337 |
-
|
| 338 |
-
if self.num_resolutions == 1:
|
| 339 |
-
x_res = self.embedding
|
| 340 |
-
else:
|
| 341 |
-
x_res = self.embedding[
|
| 342 |
-
:, offset : offset + num_coefficients
|
| 343 |
-
].contiguous()
|
| 344 |
-
to_grid_mat = SO3_grid[self.lmax_list[i]][
|
| 345 |
-
self.mmax_list[i]
|
| 346 |
-
].get_to_grid_mat(self.device)
|
| 347 |
-
from_grid_mat = SO3_grid[self.lmax_list[i]][
|
| 348 |
-
self.mmax_list[i]
|
| 349 |
-
].get_from_grid_mat(self.device)
|
| 350 |
-
|
| 351 |
-
x_grid = torch.einsum("bai, zic -> zbac", to_grid_mat, x_res)
|
| 352 |
-
x_grid = act(x_grid)
|
| 353 |
-
x_res = torch.einsum("bai, zbac -> zic", from_grid_mat, x_grid)
|
| 354 |
-
if self.num_resolutions == 1:
|
| 355 |
-
self.embedding = x_res
|
| 356 |
-
else:
|
| 357 |
-
self.embedding[:, offset : offset + num_coefficients] = x_res
|
| 358 |
-
offset = offset + num_coefficients
|
| 359 |
-
|
| 360 |
-
# Compute a sample of the grid
|
| 361 |
-
def to_grid(self, SO3_grid, lmax=-1):
|
| 362 |
-
if lmax == -1:
|
| 363 |
-
lmax = max(self.lmax_list)
|
| 364 |
-
|
| 365 |
-
to_grid_mat_lmax = SO3_grid[lmax][lmax].get_to_grid_mat(self.device)
|
| 366 |
-
grid_mapping = SO3_grid[lmax][lmax].mapping
|
| 367 |
-
|
| 368 |
-
offset = 0
|
| 369 |
-
x_grid = torch.tensor([], device=self.device)
|
| 370 |
-
|
| 371 |
-
for i in range(self.num_resolutions):
|
| 372 |
-
num_coefficients = int((self.lmax_list[i] + 1) ** 2)
|
| 373 |
-
if self.num_resolutions == 1:
|
| 374 |
-
x_res = self.embedding
|
| 375 |
-
else:
|
| 376 |
-
x_res = self.embedding[
|
| 377 |
-
:, offset : offset + num_coefficients
|
| 378 |
-
].contiguous()
|
| 379 |
-
to_grid_mat = to_grid_mat_lmax[
|
| 380 |
-
:, :, grid_mapping.coefficient_idx(self.lmax_list[i], self.lmax_list[i])
|
| 381 |
-
]
|
| 382 |
-
x_grid = torch.cat(
|
| 383 |
-
[x_grid, torch.einsum("bai, zic -> zbac", to_grid_mat, x_res)], dim=3
|
| 384 |
-
)
|
| 385 |
-
offset = offset + num_coefficients
|
| 386 |
-
|
| 387 |
-
return x_grid
|
| 388 |
-
|
| 389 |
-
# Compute irreps from grid representation
|
| 390 |
-
def _from_grid(self, x_grid, SO3_grid, lmax=-1):
|
| 391 |
-
if lmax == -1:
|
| 392 |
-
lmax = max(self.lmax_list)
|
| 393 |
-
|
| 394 |
-
from_grid_mat_lmax = SO3_grid[lmax][lmax].get_from_grid_mat(self.device)
|
| 395 |
-
grid_mapping = SO3_grid[lmax][lmax].mapping
|
| 396 |
-
|
| 397 |
-
offset = 0
|
| 398 |
-
offset_channel = 0
|
| 399 |
-
for i in range(self.num_resolutions):
|
| 400 |
-
from_grid_mat = from_grid_mat_lmax[
|
| 401 |
-
:, :, grid_mapping.coefficient_idx(self.lmax_list[i], self.lmax_list[i])
|
| 402 |
-
]
|
| 403 |
-
if self.num_resolutions == 1:
|
| 404 |
-
temp = x_grid
|
| 405 |
-
else:
|
| 406 |
-
temp = x_grid[
|
| 407 |
-
:, :, :, offset_channel : offset_channel + self.num_channels
|
| 408 |
-
]
|
| 409 |
-
x_res = torch.einsum("bai, zbac -> zic", from_grid_mat, temp)
|
| 410 |
-
num_coefficients = int((self.lmax_list[i] + 1) ** 2)
|
| 411 |
-
|
| 412 |
-
if self.num_resolutions == 1:
|
| 413 |
-
self.embedding = x_res
|
| 414 |
-
else:
|
| 415 |
-
self.embedding[:, offset : offset + num_coefficients] = x_res
|
| 416 |
-
|
| 417 |
-
offset = offset + num_coefficients
|
| 418 |
-
offset_channel = offset_channel + self.num_channels
|
| 419 |
-
|
| 420 |
-
|
| 421 |
-
class SO3_Rotation(torch.nn.Module):
|
| 422 |
-
"""
|
| 423 |
-
Helper functions for Wigner-D rotations
|
| 424 |
-
|
| 425 |
-
Args:
|
| 426 |
-
lmax_list (list:int): List of maximum degree of the spherical harmonics
|
| 427 |
-
"""
|
| 428 |
-
|
| 429 |
-
def __init__(
|
| 430 |
-
self,
|
| 431 |
-
lmax,
|
| 432 |
-
):
|
| 433 |
-
super().__init__()
|
| 434 |
-
self.lmax = lmax
|
| 435 |
-
self.mapping = CoefficientMappingModule([self.lmax], [self.lmax])
|
| 436 |
-
|
| 437 |
-
def set_wigner(self, rot_mat3x3):
|
| 438 |
-
self.device, self.dtype = rot_mat3x3.device, rot_mat3x3.dtype
|
| 439 |
-
length = len(rot_mat3x3)
|
| 440 |
-
self.wigner = self.RotationToWignerDMatrix(rot_mat3x3, 0, self.lmax)
|
| 441 |
-
self.wigner_inv = torch.transpose(self.wigner, 1, 2).contiguous()
|
| 442 |
-
self.wigner = self.wigner.detach()
|
| 443 |
-
self.wigner_inv = self.wigner_inv.detach()
|
| 444 |
-
|
| 445 |
-
# Rotate the embedding
|
| 446 |
-
def rotate(self, embedding, out_lmax, out_mmax):
|
| 447 |
-
out_mask = self.mapping.coefficient_idx(out_lmax, out_mmax)
|
| 448 |
-
wigner = self.wigner[:, out_mask, :]
|
| 449 |
-
return torch.bmm(wigner, embedding)
|
| 450 |
-
|
| 451 |
-
# Rotate the embedding by the inverse of the rotation matrix
|
| 452 |
-
def rotate_inv(self, embedding, in_lmax, in_mmax):
|
| 453 |
-
in_mask = self.mapping.coefficient_idx(in_lmax, in_mmax)
|
| 454 |
-
wigner_inv = self.wigner_inv[:, :, in_mask]
|
| 455 |
-
wigner_inv_rescale = self.mapping.get_rotate_inv_rescale(in_lmax, in_mmax)
|
| 456 |
-
wigner_inv = wigner_inv * wigner_inv_rescale
|
| 457 |
-
return torch.bmm(wigner_inv, embedding)
|
| 458 |
-
|
| 459 |
-
# Compute Wigner matrices from rotation matrix
|
| 460 |
-
def RotationToWignerDMatrix(self, edge_rot_mat, start_lmax, end_lmax):
|
| 461 |
-
x = edge_rot_mat @ edge_rot_mat.new_tensor([0.0, 1.0, 0.0])
|
| 462 |
-
alpha, beta = o3.xyz_to_angles(x)
|
| 463 |
-
R = (
|
| 464 |
-
o3.angles_to_matrix(alpha, beta, torch.zeros_like(alpha)).transpose(-1, -2)
|
| 465 |
-
@ edge_rot_mat
|
| 466 |
-
)
|
| 467 |
-
gamma = torch.atan2(R[..., 0, 2], R[..., 0, 0])
|
| 468 |
-
|
| 469 |
-
size = (end_lmax + 1) ** 2 - (start_lmax) ** 2
|
| 470 |
-
wigner = torch.zeros(len(alpha), size, size, device=self.device)
|
| 471 |
-
start = 0
|
| 472 |
-
for lmax in range(start_lmax, end_lmax + 1):
|
| 473 |
-
block = wigner_D(lmax, alpha, beta, gamma)
|
| 474 |
-
end = start + block.size()[1]
|
| 475 |
-
wigner[:, start:end, start:end] = block
|
| 476 |
-
start = end
|
| 477 |
-
|
| 478 |
-
return wigner.detach()
|
| 479 |
-
|
| 480 |
-
|
| 481 |
-
class SO3_Grid(torch.nn.Module):
|
| 482 |
-
"""
|
| 483 |
-
Helper functions for grid representation of the irreps
|
| 484 |
-
|
| 485 |
-
Args:
|
| 486 |
-
lmax (int): Maximum degree of the spherical harmonics
|
| 487 |
-
mmax (int): Maximum order of the spherical harmonics
|
| 488 |
-
"""
|
| 489 |
-
|
| 490 |
-
def __init__(
|
| 491 |
-
self,
|
| 492 |
-
lmax,
|
| 493 |
-
mmax,
|
| 494 |
-
normalization="integral",
|
| 495 |
-
resolution=None,
|
| 496 |
-
):
|
| 497 |
-
super().__init__()
|
| 498 |
-
self.lmax = lmax
|
| 499 |
-
self.mmax = mmax
|
| 500 |
-
self.lat_resolution = 2 * (self.lmax + 1)
|
| 501 |
-
if lmax == mmax:
|
| 502 |
-
self.long_resolution = 2 * (self.mmax + 1) + 1
|
| 503 |
-
else:
|
| 504 |
-
self.long_resolution = 2 * (self.mmax) + 1
|
| 505 |
-
if resolution is not None:
|
| 506 |
-
self.lat_resolution = resolution
|
| 507 |
-
self.long_resolution = resolution
|
| 508 |
-
|
| 509 |
-
self.mapping = CoefficientMappingModule([self.lmax], [self.lmax])
|
| 510 |
-
|
| 511 |
-
device = "cpu"
|
| 512 |
-
|
| 513 |
-
to_grid = ToS2Grid(
|
| 514 |
-
self.lmax,
|
| 515 |
-
(self.lat_resolution, self.long_resolution),
|
| 516 |
-
normalization=normalization, # normalization="integral",
|
| 517 |
-
device=device,
|
| 518 |
-
)
|
| 519 |
-
to_grid_mat = torch.einsum("mbi, am -> bai", to_grid.shb, to_grid.sha).detach()
|
| 520 |
-
# rescale based on mmax
|
| 521 |
-
if lmax != mmax:
|
| 522 |
-
for l in range(lmax + 1):
|
| 523 |
-
if l <= mmax:
|
| 524 |
-
continue
|
| 525 |
-
start_idx = l**2
|
| 526 |
-
length = 2 * l + 1
|
| 527 |
-
rescale_factor = math.sqrt(length / (2 * mmax + 1))
|
| 528 |
-
to_grid_mat[:, :, start_idx : (start_idx + length)] = (
|
| 529 |
-
to_grid_mat[:, :, start_idx : (start_idx + length)] * rescale_factor
|
| 530 |
-
)
|
| 531 |
-
to_grid_mat = to_grid_mat[
|
| 532 |
-
:, :, self.mapping.coefficient_idx(self.lmax, self.mmax)
|
| 533 |
-
]
|
| 534 |
-
|
| 535 |
-
from_grid = FromS2Grid(
|
| 536 |
-
(self.lat_resolution, self.long_resolution),
|
| 537 |
-
self.lmax,
|
| 538 |
-
normalization=normalization, # normalization="integral",
|
| 539 |
-
device=device,
|
| 540 |
-
)
|
| 541 |
-
from_grid_mat = torch.einsum(
|
| 542 |
-
"am, mbi -> bai", from_grid.sha, from_grid.shb
|
| 543 |
-
).detach()
|
| 544 |
-
# rescale based on mmax
|
| 545 |
-
if lmax != mmax:
|
| 546 |
-
for l in range(lmax + 1):
|
| 547 |
-
if l <= mmax:
|
| 548 |
-
continue
|
| 549 |
-
start_idx = l**2
|
| 550 |
-
length = 2 * l + 1
|
| 551 |
-
rescale_factor = math.sqrt(length / (2 * mmax + 1))
|
| 552 |
-
from_grid_mat[:, :, start_idx : (start_idx + length)] = (
|
| 553 |
-
from_grid_mat[:, :, start_idx : (start_idx + length)]
|
| 554 |
-
* rescale_factor
|
| 555 |
-
)
|
| 556 |
-
from_grid_mat = from_grid_mat[
|
| 557 |
-
:, :, self.mapping.coefficient_idx(self.lmax, self.mmax)
|
| 558 |
-
]
|
| 559 |
-
|
| 560 |
-
# save tensors and they will be moved to GPU
|
| 561 |
-
self.register_buffer("to_grid_mat", to_grid_mat)
|
| 562 |
-
self.register_buffer("from_grid_mat", from_grid_mat)
|
| 563 |
-
|
| 564 |
-
# Compute matrices to transform irreps to grid
|
| 565 |
-
def get_to_grid_mat(self, device):
|
| 566 |
-
return self.to_grid_mat
|
| 567 |
-
|
| 568 |
-
# Compute matrices to transform grid to irreps
|
| 569 |
-
def get_from_grid_mat(self, device):
|
| 570 |
-
return self.from_grid_mat
|
| 571 |
-
|
| 572 |
-
# Compute grid from irreps representation
|
| 573 |
-
def to_grid(self, embedding, lmax, mmax):
|
| 574 |
-
to_grid_mat = self.to_grid_mat[:, :, self.mapping.coefficient_idx(lmax, mmax)]
|
| 575 |
-
grid = torch.einsum("bai, zic -> zbac", to_grid_mat, embedding)
|
| 576 |
-
return grid
|
| 577 |
-
|
| 578 |
-
# Compute irreps from grid representation
|
| 579 |
-
def from_grid(self, grid, lmax, mmax):
|
| 580 |
-
from_grid_mat = self.from_grid_mat[
|
| 581 |
-
:, :, self.mapping.coefficient_idx(lmax, mmax)
|
| 582 |
-
]
|
| 583 |
-
embedding = torch.einsum("bai, zbac -> zic", from_grid_mat, grid)
|
| 584 |
-
return embedding
|
| 585 |
-
|
| 586 |
-
|
| 587 |
-
class SO3_Linear(torch.nn.Module):
|
| 588 |
-
def __init__(self, in_features, out_features, lmax, bias=True):
|
| 589 |
-
super().__init__()
|
| 590 |
-
self.in_features = in_features
|
| 591 |
-
self.out_features = out_features
|
| 592 |
-
self.lmax = lmax
|
| 593 |
-
self.linear_list = torch.nn.ModuleList()
|
| 594 |
-
for l in range(lmax + 1):
|
| 595 |
-
if l == 0:
|
| 596 |
-
self.linear_list.append(Linear(in_features, out_features, bias=bias))
|
| 597 |
-
else:
|
| 598 |
-
self.linear_list.append(Linear(in_features, out_features, bias=False))
|
| 599 |
-
|
| 600 |
-
def forward(self, input_embedding, output_scale=None):
|
| 601 |
-
out = []
|
| 602 |
-
for l in range(self.lmax + 1):
|
| 603 |
-
start_idx = l**2
|
| 604 |
-
length = 2 * l + 1
|
| 605 |
-
features = input_embedding.embedding.narrow(1, start_idx, length)
|
| 606 |
-
features = self.linear_list[l](features)
|
| 607 |
-
if output_scale is not None:
|
| 608 |
-
scale = output_scale.narrow(1, l, 1)
|
| 609 |
-
features = features * scale
|
| 610 |
-
out.append(features)
|
| 611 |
-
out = torch.cat(out, dim=1)
|
| 612 |
-
|
| 613 |
-
out_embedding = SO3_Embedding(
|
| 614 |
-
0,
|
| 615 |
-
input_embedding.lmax_list.copy(),
|
| 616 |
-
self.out_features,
|
| 617 |
-
device=input_embedding.device,
|
| 618 |
-
dtype=input_embedding.dtype,
|
| 619 |
-
)
|
| 620 |
-
out_embedding.set_embedding(out)
|
| 621 |
-
out_embedding.set_lmax_mmax(
|
| 622 |
-
input_embedding.lmax_list.copy(), input_embedding.lmax_list.copy()
|
| 623 |
-
)
|
| 624 |
-
|
| 625 |
-
return out_embedding
|
| 626 |
-
|
| 627 |
-
def __repr__(self):
|
| 628 |
-
return f"{self.__class__.__name__}(in_features={self.in_features}, out_features={self.out_features}, lmax={self.lmax})"
|
| 629 |
-
|
| 630 |
-
|
| 631 |
-
class SO3_LinearV2(torch.nn.Module):
|
| 632 |
-
def __init__(self, in_features, out_features, lmax, bias=True):
|
| 633 |
-
"""
|
| 634 |
-
1. Use `torch.einsum` to prevent slicing and concatenation
|
| 635 |
-
2. Need to specify some behaviors in `no_weight_decay` and weight initialization.
|
| 636 |
-
"""
|
| 637 |
-
super().__init__()
|
| 638 |
-
self.in_features = in_features
|
| 639 |
-
self.out_features = out_features
|
| 640 |
-
self.lmax = lmax
|
| 641 |
-
|
| 642 |
-
self.weight = torch.nn.Parameter(
|
| 643 |
-
torch.randn((self.lmax + 1), out_features, in_features)
|
| 644 |
-
)
|
| 645 |
-
bound = 1 / math.sqrt(self.in_features)
|
| 646 |
-
torch.nn.init.uniform_(self.weight, -bound, bound)
|
| 647 |
-
self.bias = torch.nn.Parameter(torch.zeros(out_features))
|
| 648 |
-
|
| 649 |
-
expand_index = torch.zeros([(lmax + 1) ** 2]).long()
|
| 650 |
-
for l in range(lmax + 1):
|
| 651 |
-
start_idx = l**2
|
| 652 |
-
length = 2 * l + 1
|
| 653 |
-
expand_index[start_idx : (start_idx + length)] = l
|
| 654 |
-
self.register_buffer("expand_index", expand_index)
|
| 655 |
-
|
| 656 |
-
def forward(self, input_embedding):
|
| 657 |
-
|
| 658 |
-
weight = torch.index_select(
|
| 659 |
-
self.weight, dim=0, index=self.expand_index
|
| 660 |
-
) # [(L_max + 1) ** 2, C_out, C_in]
|
| 661 |
-
out = torch.einsum(
|
| 662 |
-
"bmi, moi -> bmo", input_embedding.embedding, weight
|
| 663 |
-
) # [N, (L_max + 1) ** 2, C_out]
|
| 664 |
-
bias = self.bias.view(1, 1, self.out_features)
|
| 665 |
-
out[:, 0:1, :] = out.narrow(1, 0, 1) + bias
|
| 666 |
-
|
| 667 |
-
out_embedding = SO3_Embedding(
|
| 668 |
-
0,
|
| 669 |
-
input_embedding.lmax_list.copy(),
|
| 670 |
-
self.out_features,
|
| 671 |
-
device=input_embedding.device,
|
| 672 |
-
dtype=input_embedding.dtype,
|
| 673 |
-
)
|
| 674 |
-
out_embedding.set_embedding(out)
|
| 675 |
-
out_embedding.set_lmax_mmax(
|
| 676 |
-
input_embedding.lmax_list.copy(), input_embedding.lmax_list.copy()
|
| 677 |
-
)
|
| 678 |
-
|
| 679 |
-
return out_embedding
|
| 680 |
-
|
| 681 |
-
def __repr__(self):
|
| 682 |
-
return f"{self.__class__.__name__}(in_features={self.in_features}, out_features={self.out_features}, lmax={self.lmax})"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
nets/equiformer_v2/transformer_block.py
DELETED
|
@@ -1,689 +0,0 @@
|
|
| 1 |
-
import torch
|
| 2 |
-
import torch.nn as nn
|
| 3 |
-
import torch.nn.functional as F
|
| 4 |
-
import math
|
| 5 |
-
import torch_geometric
|
| 6 |
-
import copy
|
| 7 |
-
|
| 8 |
-
from .activation import (
|
| 9 |
-
ScaledSiLU,
|
| 10 |
-
ScaledSwiGLU,
|
| 11 |
-
SwiGLU,
|
| 12 |
-
ScaledSmoothLeakyReLU,
|
| 13 |
-
SmoothLeakyReLU,
|
| 14 |
-
GateActivation,
|
| 15 |
-
SeparableS2Activation,
|
| 16 |
-
S2Activation,
|
| 17 |
-
)
|
| 18 |
-
from .layer_norm import (
|
| 19 |
-
EquivariantLayerNormArray,
|
| 20 |
-
EquivariantLayerNormArraySphericalHarmonics,
|
| 21 |
-
EquivariantRMSNormArraySphericalHarmonics,
|
| 22 |
-
get_normalization_layer,
|
| 23 |
-
)
|
| 24 |
-
from .so2_ops import SO2_Convolution, SO2_Linear
|
| 25 |
-
from .so3 import SO3_Embedding, SO3_Linear, SO3_LinearV2
|
| 26 |
-
from .radial_function import RadialFunction
|
| 27 |
-
from .drop import GraphDropPath, EquivariantDropoutArraySphericalHarmonics
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
class SO2EquivariantGraphAttention(torch.nn.Module):
|
| 31 |
-
"""
|
| 32 |
-
SO2EquivariantGraphAttention: Perform MLP attention + non-linear message passing
|
| 33 |
-
SO(2) Convolution with radial function -> S2 Activation -> SO(2) Convolution -> attention weights and non-linear messages
|
| 34 |
-
attention weights * non-linear messages -> Linear
|
| 35 |
-
|
| 36 |
-
Args:
|
| 37 |
-
sphere_channels (int): Number of spherical channels
|
| 38 |
-
hidden_channels (int): Number of hidden channels used during the SO(2) conv
|
| 39 |
-
num_heads (int): Number of attention heads
|
| 40 |
-
attn_alpha_head (int): Number of channels for alpha vector in each attention head
|
| 41 |
-
attn_value_head (int): Number of channels for value vector in each attention head
|
| 42 |
-
output_channels (int): Number of output channels
|
| 43 |
-
lmax_list (list:int): List of degrees (l) for each resolution
|
| 44 |
-
mmax_list (list:int): List of orders (m) for each resolution
|
| 45 |
-
|
| 46 |
-
SO3_rotation (list:SO3_Rotation): Class to calculate Wigner-D matrices and rotate embeddings
|
| 47 |
-
mappingReduced (CoefficientMappingModule): Class to convert l and m indices once node embedding is rotated
|
| 48 |
-
SO3_grid (SO3_grid): Class used to convert from grid the spherical harmonic representations
|
| 49 |
-
|
| 50 |
-
max_num_elements (int): Maximum number of atomic numbers
|
| 51 |
-
edge_channels_list (list:int): List of sizes of invariant edge embedding. For example, [input_channels, hidden_channels, hidden_channels].
|
| 52 |
-
The last one will be used as hidden size when `use_atom_edge_embedding` is `True`.
|
| 53 |
-
use_atom_edge_embedding (bool): Whether to use atomic embedding along with relative distance for edge scalar features
|
| 54 |
-
use_m_share_rad (bool): Whether all m components within a type-L vector of one channel share radial function weights
|
| 55 |
-
|
| 56 |
-
activation (str): Type of activation function
|
| 57 |
-
use_s2_act_attn (bool): Whether to use attention after S2 activation. Otherwise, use the same attention as Equiformer
|
| 58 |
-
use_attn_renorm (bool): Whether to re-normalize attention weights
|
| 59 |
-
use_gate_act (bool): If `True`, use gate activation. Otherwise, use S2 activation.
|
| 60 |
-
use_sep_s2_act (bool): If `True`, use separable S2 activation when `use_gate_act` is False.
|
| 61 |
-
|
| 62 |
-
alpha_drop (float): Dropout rate for attention weights
|
| 63 |
-
"""
|
| 64 |
-
|
| 65 |
-
def __init__(
|
| 66 |
-
self,
|
| 67 |
-
sphere_channels,
|
| 68 |
-
hidden_channels,
|
| 69 |
-
num_heads,
|
| 70 |
-
attn_alpha_channels,
|
| 71 |
-
attn_value_channels,
|
| 72 |
-
output_channels,
|
| 73 |
-
lmax_list,
|
| 74 |
-
mmax_list,
|
| 75 |
-
SO3_rotation,
|
| 76 |
-
mappingReduced,
|
| 77 |
-
SO3_grid,
|
| 78 |
-
max_num_elements,
|
| 79 |
-
edge_channels_list,
|
| 80 |
-
use_atom_edge_embedding=True,
|
| 81 |
-
use_m_share_rad=False,
|
| 82 |
-
activation="scaled_silu",
|
| 83 |
-
use_s2_act_attn=False,
|
| 84 |
-
use_attn_renorm=True,
|
| 85 |
-
use_gate_act=False,
|
| 86 |
-
use_sep_s2_act=True,
|
| 87 |
-
alpha_drop=0.0,
|
| 88 |
-
):
|
| 89 |
-
super(SO2EquivariantGraphAttention, self).__init__()
|
| 90 |
-
|
| 91 |
-
self.sphere_channels = sphere_channels
|
| 92 |
-
self.hidden_channels = hidden_channels
|
| 93 |
-
self.num_heads = num_heads
|
| 94 |
-
self.attn_alpha_channels = attn_alpha_channels
|
| 95 |
-
self.attn_value_channels = attn_value_channels
|
| 96 |
-
self.output_channels = output_channels
|
| 97 |
-
self.lmax_list = lmax_list
|
| 98 |
-
self.mmax_list = mmax_list
|
| 99 |
-
self.num_resolutions = len(self.lmax_list)
|
| 100 |
-
|
| 101 |
-
self.SO3_rotation = SO3_rotation
|
| 102 |
-
self.mappingReduced = mappingReduced
|
| 103 |
-
self.SO3_grid = SO3_grid
|
| 104 |
-
|
| 105 |
-
# Create edge scalar (invariant to rotations) features
|
| 106 |
-
# Embedding function of the atomic numbers
|
| 107 |
-
self.max_num_elements = max_num_elements
|
| 108 |
-
self.edge_channels_list = copy.deepcopy(edge_channels_list)
|
| 109 |
-
self.use_atom_edge_embedding = use_atom_edge_embedding
|
| 110 |
-
self.use_m_share_rad = use_m_share_rad
|
| 111 |
-
|
| 112 |
-
if self.use_atom_edge_embedding:
|
| 113 |
-
self.source_embedding = nn.Embedding(
|
| 114 |
-
self.max_num_elements, self.edge_channels_list[-1]
|
| 115 |
-
)
|
| 116 |
-
self.target_embedding = nn.Embedding(
|
| 117 |
-
self.max_num_elements, self.edge_channels_list[-1]
|
| 118 |
-
)
|
| 119 |
-
nn.init.uniform_(self.source_embedding.weight.data, -0.001, 0.001)
|
| 120 |
-
nn.init.uniform_(self.target_embedding.weight.data, -0.001, 0.001)
|
| 121 |
-
self.edge_channels_list[0] = (
|
| 122 |
-
self.edge_channels_list[0] + 2 * self.edge_channels_list[-1]
|
| 123 |
-
)
|
| 124 |
-
else:
|
| 125 |
-
self.source_embedding, self.target_embedding = None, None
|
| 126 |
-
|
| 127 |
-
self.use_s2_act_attn = use_s2_act_attn
|
| 128 |
-
self.use_attn_renorm = use_attn_renorm
|
| 129 |
-
self.use_gate_act = use_gate_act
|
| 130 |
-
self.use_sep_s2_act = use_sep_s2_act
|
| 131 |
-
|
| 132 |
-
assert not self.use_s2_act_attn # since this is not used
|
| 133 |
-
|
| 134 |
-
# Create SO(2) convolution blocks
|
| 135 |
-
extra_m0_output_channels = None
|
| 136 |
-
if not self.use_s2_act_attn:
|
| 137 |
-
extra_m0_output_channels = self.num_heads * self.attn_alpha_channels
|
| 138 |
-
if self.use_gate_act:
|
| 139 |
-
extra_m0_output_channels = (
|
| 140 |
-
extra_m0_output_channels
|
| 141 |
-
+ max(self.lmax_list) * self.hidden_channels
|
| 142 |
-
)
|
| 143 |
-
else:
|
| 144 |
-
if self.use_sep_s2_act:
|
| 145 |
-
extra_m0_output_channels = (
|
| 146 |
-
extra_m0_output_channels + self.hidden_channels
|
| 147 |
-
)
|
| 148 |
-
|
| 149 |
-
if self.use_m_share_rad:
|
| 150 |
-
self.edge_channels_list = self.edge_channels_list + [
|
| 151 |
-
2 * self.sphere_channels * (max(self.lmax_list) + 1)
|
| 152 |
-
]
|
| 153 |
-
self.rad_func = RadialFunction(self.edge_channels_list)
|
| 154 |
-
expand_index = torch.zeros([(max(self.lmax_list) + 1) ** 2]).long()
|
| 155 |
-
for l in range(max(self.lmax_list) + 1):
|
| 156 |
-
start_idx = l**2
|
| 157 |
-
length = 2 * l + 1
|
| 158 |
-
expand_index[start_idx : (start_idx + length)] = l
|
| 159 |
-
self.register_buffer("expand_index", expand_index)
|
| 160 |
-
|
| 161 |
-
self.so2_conv_1 = SO2_Convolution(
|
| 162 |
-
2 * self.sphere_channels,
|
| 163 |
-
self.hidden_channels,
|
| 164 |
-
self.lmax_list,
|
| 165 |
-
self.mmax_list,
|
| 166 |
-
self.mappingReduced,
|
| 167 |
-
internal_weights=(False if not self.use_m_share_rad else True),
|
| 168 |
-
edge_channels_list=(
|
| 169 |
-
self.edge_channels_list if not self.use_m_share_rad else None
|
| 170 |
-
),
|
| 171 |
-
extra_m0_output_channels=extra_m0_output_channels, # for attention weights and/or gate activation
|
| 172 |
-
)
|
| 173 |
-
|
| 174 |
-
if self.use_s2_act_attn:
|
| 175 |
-
self.alpha_norm = None
|
| 176 |
-
self.alpha_act = None
|
| 177 |
-
self.alpha_dot = None
|
| 178 |
-
else:
|
| 179 |
-
if self.use_attn_renorm:
|
| 180 |
-
self.alpha_norm = torch.nn.LayerNorm(self.attn_alpha_channels)
|
| 181 |
-
else:
|
| 182 |
-
self.alpha_norm = torch.nn.Identity()
|
| 183 |
-
self.alpha_act = SmoothLeakyReLU()
|
| 184 |
-
self.alpha_dot = torch.nn.Parameter(
|
| 185 |
-
torch.randn(self.num_heads, self.attn_alpha_channels)
|
| 186 |
-
)
|
| 187 |
-
# torch_geometric.nn.inits.glorot(self.alpha_dot) # Following GATv2
|
| 188 |
-
std = 1.0 / math.sqrt(self.attn_alpha_channels)
|
| 189 |
-
torch.nn.init.uniform_(self.alpha_dot, -std, std)
|
| 190 |
-
|
| 191 |
-
self.alpha_dropout = None
|
| 192 |
-
if alpha_drop != 0.0:
|
| 193 |
-
self.alpha_dropout = torch.nn.Dropout(alpha_drop)
|
| 194 |
-
|
| 195 |
-
if self.use_gate_act:
|
| 196 |
-
self.gate_act = GateActivation(
|
| 197 |
-
lmax=max(self.lmax_list),
|
| 198 |
-
mmax=max(self.mmax_list),
|
| 199 |
-
num_channels=self.hidden_channels,
|
| 200 |
-
)
|
| 201 |
-
else:
|
| 202 |
-
if self.use_sep_s2_act:
|
| 203 |
-
# separable S2 activation
|
| 204 |
-
self.s2_act = SeparableS2Activation(
|
| 205 |
-
lmax=max(self.lmax_list), mmax=max(self.mmax_list)
|
| 206 |
-
)
|
| 207 |
-
else:
|
| 208 |
-
# S2 activation
|
| 209 |
-
self.s2_act = S2Activation(
|
| 210 |
-
lmax=max(self.lmax_list), mmax=max(self.mmax_list)
|
| 211 |
-
)
|
| 212 |
-
|
| 213 |
-
self.so2_conv_2 = SO2_Convolution(
|
| 214 |
-
self.hidden_channels,
|
| 215 |
-
self.num_heads * self.attn_value_channels,
|
| 216 |
-
self.lmax_list,
|
| 217 |
-
self.mmax_list,
|
| 218 |
-
self.mappingReduced,
|
| 219 |
-
internal_weights=True,
|
| 220 |
-
edge_channels_list=None,
|
| 221 |
-
extra_m0_output_channels=(
|
| 222 |
-
self.num_heads if self.use_s2_act_attn else None
|
| 223 |
-
), # for attention weights
|
| 224 |
-
)
|
| 225 |
-
|
| 226 |
-
self.proj = SO3_LinearV2(
|
| 227 |
-
self.num_heads * self.attn_value_channels,
|
| 228 |
-
self.output_channels,
|
| 229 |
-
lmax=self.lmax_list[0],
|
| 230 |
-
)
|
| 231 |
-
|
| 232 |
-
def forward(self, x, atomic_numbers, edge_distance, edge_index):
|
| 233 |
-
|
| 234 |
-
# Compute edge scalar features (invariant to rotations)
|
| 235 |
-
# Uses atomic numbers and edge distance as inputs
|
| 236 |
-
if self.use_atom_edge_embedding:
|
| 237 |
-
source_element = atomic_numbers[edge_index[0]] # Source atom atomic number
|
| 238 |
-
target_element = atomic_numbers[edge_index[1]] # Target atom atomic number
|
| 239 |
-
source_embedding = self.source_embedding(source_element)
|
| 240 |
-
target_embedding = self.target_embedding(target_element)
|
| 241 |
-
x_edge = torch.cat(
|
| 242 |
-
(edge_distance, source_embedding, target_embedding), dim=1
|
| 243 |
-
)
|
| 244 |
-
else:
|
| 245 |
-
x_edge = edge_distance
|
| 246 |
-
|
| 247 |
-
x_source = x.clone()
|
| 248 |
-
x_target = x.clone()
|
| 249 |
-
x_source._expand_edge(edge_index[0, :])
|
| 250 |
-
x_target._expand_edge(edge_index[1, :])
|
| 251 |
-
|
| 252 |
-
x_message_data = torch.cat((x_source.embedding, x_target.embedding), dim=2)
|
| 253 |
-
x_message = SO3_Embedding(
|
| 254 |
-
0,
|
| 255 |
-
x_target.lmax_list.copy(),
|
| 256 |
-
x_target.num_channels * 2,
|
| 257 |
-
device=x_target.device,
|
| 258 |
-
dtype=x_target.dtype,
|
| 259 |
-
)
|
| 260 |
-
x_message.set_embedding(x_message_data)
|
| 261 |
-
x_message.set_lmax_mmax(self.lmax_list.copy(), self.mmax_list.copy())
|
| 262 |
-
|
| 263 |
-
# radial function (scale all m components within a type-L vector of one channel with the same weight)
|
| 264 |
-
if self.use_m_share_rad:
|
| 265 |
-
x_edge_weight = self.rad_func(x_edge)
|
| 266 |
-
x_edge_weight = x_edge_weight.reshape(
|
| 267 |
-
-1, (max(self.lmax_list) + 1), 2 * self.sphere_channels
|
| 268 |
-
)
|
| 269 |
-
x_edge_weight = torch.index_select(
|
| 270 |
-
x_edge_weight, dim=1, index=self.expand_index
|
| 271 |
-
) # [E, (L_max + 1) ** 2, C]
|
| 272 |
-
x_message.embedding = x_message.embedding * x_edge_weight
|
| 273 |
-
|
| 274 |
-
# Rotate the irreps to align with the edge
|
| 275 |
-
x_message._rotate(self.SO3_rotation, self.lmax_list, self.mmax_list)
|
| 276 |
-
|
| 277 |
-
# First SO(2)-convolution
|
| 278 |
-
if self.use_s2_act_attn:
|
| 279 |
-
x_message = self.so2_conv_1(x_message, x_edge)
|
| 280 |
-
else:
|
| 281 |
-
x_message, x_0_extra = self.so2_conv_1(x_message, x_edge)
|
| 282 |
-
|
| 283 |
-
# Activation
|
| 284 |
-
x_alpha_num_channels = self.num_heads * self.attn_alpha_channels
|
| 285 |
-
if self.use_gate_act:
|
| 286 |
-
# Gate activation
|
| 287 |
-
x_0_gating = x_0_extra.narrow(
|
| 288 |
-
1, x_alpha_num_channels, x_0_extra.shape[1] - x_alpha_num_channels
|
| 289 |
-
) # for activation
|
| 290 |
-
x_0_alpha = x_0_extra.narrow(
|
| 291 |
-
1, 0, x_alpha_num_channels
|
| 292 |
-
) # for attention weights
|
| 293 |
-
x_message.embedding = self.gate_act(x_0_gating, x_message.embedding)
|
| 294 |
-
else:
|
| 295 |
-
if self.use_sep_s2_act:
|
| 296 |
-
x_0_gating = x_0_extra.narrow(
|
| 297 |
-
1, x_alpha_num_channels, x_0_extra.shape[1] - x_alpha_num_channels
|
| 298 |
-
) # for activation
|
| 299 |
-
x_0_alpha = x_0_extra.narrow(
|
| 300 |
-
1, 0, x_alpha_num_channels
|
| 301 |
-
) # for attention weights
|
| 302 |
-
x_message.embedding = self.s2_act(
|
| 303 |
-
x_0_gating, x_message.embedding, self.SO3_grid
|
| 304 |
-
)
|
| 305 |
-
else:
|
| 306 |
-
x_0_alpha = x_0_extra
|
| 307 |
-
x_message.embedding = self.s2_act(x_message.embedding, self.SO3_grid)
|
| 308 |
-
##x_message._grid_act(self.SO3_grid, self.value_act, self.mappingReduced)
|
| 309 |
-
|
| 310 |
-
# Second SO(2)-convolution
|
| 311 |
-
if self.use_s2_act_attn:
|
| 312 |
-
x_message, x_0_extra = self.so2_conv_2(x_message, x_edge)
|
| 313 |
-
else:
|
| 314 |
-
x_message = self.so2_conv_2(x_message, x_edge)
|
| 315 |
-
|
| 316 |
-
# Attention weights
|
| 317 |
-
if self.use_s2_act_attn:
|
| 318 |
-
alpha = x_0_extra
|
| 319 |
-
else:
|
| 320 |
-
x_0_alpha = x_0_alpha.reshape(-1, self.num_heads, self.attn_alpha_channels)
|
| 321 |
-
x_0_alpha = self.alpha_norm(x_0_alpha)
|
| 322 |
-
x_0_alpha = self.alpha_act(x_0_alpha)
|
| 323 |
-
alpha = torch.einsum("bik, ik -> bi", x_0_alpha, self.alpha_dot)
|
| 324 |
-
alpha = torch_geometric.utils.softmax(alpha, edge_index[1])
|
| 325 |
-
alpha = alpha.reshape(alpha.shape[0], 1, self.num_heads, 1)
|
| 326 |
-
if self.alpha_dropout is not None:
|
| 327 |
-
alpha = self.alpha_dropout(alpha)
|
| 328 |
-
|
| 329 |
-
# Attention weights * non-linear messages
|
| 330 |
-
attn = x_message.embedding
|
| 331 |
-
attn = attn.reshape(
|
| 332 |
-
attn.shape[0], attn.shape[1], self.num_heads, self.attn_value_channels
|
| 333 |
-
)
|
| 334 |
-
attn = attn * alpha
|
| 335 |
-
attn = attn.reshape(
|
| 336 |
-
attn.shape[0], attn.shape[1], self.num_heads * self.attn_value_channels
|
| 337 |
-
)
|
| 338 |
-
x_message.embedding = attn
|
| 339 |
-
|
| 340 |
-
# Rotate back the irreps
|
| 341 |
-
x_message._rotate_inv(self.SO3_rotation, self.mappingReduced)
|
| 342 |
-
|
| 343 |
-
# Compute the sum of the incoming neighboring messages for each target node
|
| 344 |
-
x_message._reduce_edge(edge_index[1], len(x.embedding))
|
| 345 |
-
|
| 346 |
-
# Project
|
| 347 |
-
out_embedding = self.proj(x_message)
|
| 348 |
-
|
| 349 |
-
return out_embedding
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
class FeedForwardNetwork(torch.nn.Module):
|
| 353 |
-
"""
|
| 354 |
-
FeedForwardNetwork: Perform feedforward network with S2 activation or gate activation
|
| 355 |
-
|
| 356 |
-
Args:
|
| 357 |
-
sphere_channels (int): Number of spherical channels
|
| 358 |
-
hidden_channels (int): Number of hidden channels used during feedforward network
|
| 359 |
-
output_channels (int): Number of output channels
|
| 360 |
-
|
| 361 |
-
lmax_list (list:int): List of degrees (l) for each resolution
|
| 362 |
-
mmax_list (list:int): List of orders (m) for each resolution
|
| 363 |
-
|
| 364 |
-
SO3_grid (SO3_grid): Class used to convert from grid the spherical harmonic representations
|
| 365 |
-
|
| 366 |
-
activation (str): Type of activation function
|
| 367 |
-
use_gate_act (bool): If `True`, use gate activation. Otherwise, use S2 activation
|
| 368 |
-
use_grid_mlp (bool): If `True`, use projecting to grids and performing MLPs.
|
| 369 |
-
use_sep_s2_act (bool): If `True`, use separable grid MLP when `use_grid_mlp` is True.
|
| 370 |
-
"""
|
| 371 |
-
|
| 372 |
-
def __init__(
|
| 373 |
-
self,
|
| 374 |
-
sphere_channels,
|
| 375 |
-
hidden_channels,
|
| 376 |
-
output_channels,
|
| 377 |
-
lmax_list,
|
| 378 |
-
mmax_list,
|
| 379 |
-
SO3_grid,
|
| 380 |
-
activation="scaled_silu",
|
| 381 |
-
use_gate_act=False,
|
| 382 |
-
use_grid_mlp=False,
|
| 383 |
-
use_sep_s2_act=True,
|
| 384 |
-
):
|
| 385 |
-
super(FeedForwardNetwork, self).__init__()
|
| 386 |
-
self.sphere_channels = sphere_channels
|
| 387 |
-
self.hidden_channels = hidden_channels
|
| 388 |
-
self.output_channels = output_channels
|
| 389 |
-
self.lmax_list = lmax_list
|
| 390 |
-
self.mmax_list = mmax_list
|
| 391 |
-
self.num_resolutions = len(lmax_list)
|
| 392 |
-
self.sphere_channels_all = self.num_resolutions * self.sphere_channels
|
| 393 |
-
self.SO3_grid = SO3_grid
|
| 394 |
-
self.use_gate_act = use_gate_act
|
| 395 |
-
self.use_grid_mlp = use_grid_mlp
|
| 396 |
-
self.use_sep_s2_act = use_sep_s2_act
|
| 397 |
-
|
| 398 |
-
self.max_lmax = max(self.lmax_list)
|
| 399 |
-
|
| 400 |
-
self.so3_linear_1 = SO3_LinearV2(
|
| 401 |
-
self.sphere_channels_all, self.hidden_channels, lmax=self.max_lmax
|
| 402 |
-
)
|
| 403 |
-
if self.use_grid_mlp:
|
| 404 |
-
if self.use_sep_s2_act:
|
| 405 |
-
self.scalar_mlp = nn.Sequential(
|
| 406 |
-
nn.Linear(
|
| 407 |
-
self.sphere_channels_all, self.hidden_channels, bias=True
|
| 408 |
-
),
|
| 409 |
-
nn.SiLU(),
|
| 410 |
-
)
|
| 411 |
-
else:
|
| 412 |
-
self.scalar_mlp = None
|
| 413 |
-
self.grid_mlp = nn.Sequential(
|
| 414 |
-
nn.Linear(self.hidden_channels, self.hidden_channels, bias=False),
|
| 415 |
-
nn.SiLU(),
|
| 416 |
-
nn.Linear(self.hidden_channels, self.hidden_channels, bias=False),
|
| 417 |
-
nn.SiLU(),
|
| 418 |
-
nn.Linear(self.hidden_channels, self.hidden_channels, bias=False),
|
| 419 |
-
)
|
| 420 |
-
else:
|
| 421 |
-
if self.use_gate_act:
|
| 422 |
-
self.gating_linear = torch.nn.Linear(
|
| 423 |
-
self.sphere_channels_all, self.max_lmax * self.hidden_channels
|
| 424 |
-
)
|
| 425 |
-
self.gate_act = GateActivation(
|
| 426 |
-
self.max_lmax, self.max_lmax, self.hidden_channels
|
| 427 |
-
)
|
| 428 |
-
else:
|
| 429 |
-
if self.use_sep_s2_act:
|
| 430 |
-
self.gating_linear = torch.nn.Linear(
|
| 431 |
-
self.sphere_channels_all, self.hidden_channels
|
| 432 |
-
)
|
| 433 |
-
self.s2_act = SeparableS2Activation(self.max_lmax, self.max_lmax)
|
| 434 |
-
else:
|
| 435 |
-
self.gating_linear = None
|
| 436 |
-
self.s2_act = S2Activation(self.max_lmax, self.max_lmax)
|
| 437 |
-
self.so3_linear_2 = SO3_LinearV2(
|
| 438 |
-
self.hidden_channels, self.output_channels, lmax=self.max_lmax
|
| 439 |
-
)
|
| 440 |
-
|
| 441 |
-
def forward(self, input_embedding):
|
| 442 |
-
|
| 443 |
-
gating_scalars = None
|
| 444 |
-
if self.use_grid_mlp:
|
| 445 |
-
if self.use_sep_s2_act:
|
| 446 |
-
gating_scalars = self.scalar_mlp(
|
| 447 |
-
input_embedding.embedding.narrow(1, 0, 1)
|
| 448 |
-
)
|
| 449 |
-
else:
|
| 450 |
-
if self.gating_linear is not None:
|
| 451 |
-
gating_scalars = self.gating_linear(
|
| 452 |
-
input_embedding.embedding.narrow(1, 0, 1)
|
| 453 |
-
)
|
| 454 |
-
|
| 455 |
-
input_embedding = self.so3_linear_1(input_embedding)
|
| 456 |
-
|
| 457 |
-
if self.use_grid_mlp:
|
| 458 |
-
# Project to grid
|
| 459 |
-
input_embedding_grid = input_embedding.to_grid(
|
| 460 |
-
self.SO3_grid, lmax=self.max_lmax
|
| 461 |
-
)
|
| 462 |
-
# Perform point-wise operations
|
| 463 |
-
input_embedding_grid = self.grid_mlp(input_embedding_grid)
|
| 464 |
-
# Project back to spherical harmonic coefficients
|
| 465 |
-
input_embedding._from_grid(
|
| 466 |
-
input_embedding_grid, self.SO3_grid, lmax=self.max_lmax
|
| 467 |
-
)
|
| 468 |
-
|
| 469 |
-
if self.use_sep_s2_act:
|
| 470 |
-
input_embedding.embedding = torch.cat(
|
| 471 |
-
(
|
| 472 |
-
gating_scalars,
|
| 473 |
-
input_embedding.embedding.narrow(
|
| 474 |
-
1, 1, input_embedding.embedding.shape[1] - 1
|
| 475 |
-
),
|
| 476 |
-
),
|
| 477 |
-
dim=1,
|
| 478 |
-
)
|
| 479 |
-
else:
|
| 480 |
-
if self.use_gate_act:
|
| 481 |
-
input_embedding.embedding = self.gate_act(
|
| 482 |
-
gating_scalars, input_embedding.embedding
|
| 483 |
-
)
|
| 484 |
-
else:
|
| 485 |
-
if self.use_sep_s2_act:
|
| 486 |
-
input_embedding.embedding = self.s2_act(
|
| 487 |
-
gating_scalars, input_embedding.embedding, self.SO3_grid
|
| 488 |
-
)
|
| 489 |
-
else:
|
| 490 |
-
input_embedding.embedding = self.s2_act(
|
| 491 |
-
input_embedding.embedding, self.SO3_grid
|
| 492 |
-
)
|
| 493 |
-
|
| 494 |
-
input_embedding = self.so3_linear_2(input_embedding)
|
| 495 |
-
|
| 496 |
-
return input_embedding
|
| 497 |
-
|
| 498 |
-
|
| 499 |
-
class TransBlockV2(torch.nn.Module):
|
| 500 |
-
"""
|
| 501 |
-
|
| 502 |
-
Args:
|
| 503 |
-
sphere_channels (int): Number of spherical channels
|
| 504 |
-
attn_hidden_channels (int): Number of hidden channels used during SO(2) graph attention
|
| 505 |
-
num_heads (int): Number of attention heads
|
| 506 |
-
attn_alpha_head (int): Number of channels for alpha vector in each attention head
|
| 507 |
-
attn_value_head (int): Number of channels for value vector in each attention head
|
| 508 |
-
ffn_hidden_channels (int): Number of hidden channels used during feedforward network
|
| 509 |
-
output_channels (int): Number of output channels
|
| 510 |
-
|
| 511 |
-
lmax_list (list:int): List of degrees (l) for each resolution
|
| 512 |
-
mmax_list (list:int): List of orders (m) for each resolution
|
| 513 |
-
|
| 514 |
-
SO3_rotation (list:SO3_Rotation): Class to calculate Wigner-D matrices and rotate embeddings
|
| 515 |
-
mappingReduced (CoefficientMappingModule): Class to convert l and m indices once node embedding is rotated
|
| 516 |
-
SO3_grid (SO3_grid): Class used to convert from grid the spherical harmonic representations
|
| 517 |
-
|
| 518 |
-
max_num_elements (int): Maximum number of atomic numbers
|
| 519 |
-
edge_channels_list (list:int): List of sizes of invariant edge embedding. For example, [input_channels, hidden_channels, hidden_channels].
|
| 520 |
-
The last one will be used as hidden size when `use_atom_edge_embedding` is `True`.
|
| 521 |
-
use_atom_edge_embedding (bool): Whether to use atomic embedding along with relative distance for edge scalar features
|
| 522 |
-
use_m_share_rad (bool): Whether all m components within a type-L vector of one channel share radial function weights
|
| 523 |
-
|
| 524 |
-
attn_activation (str): Type of activation function for SO(2) graph attention
|
| 525 |
-
use_s2_act_attn (bool): Whether to use attention after S2 activation. Otherwise, use the same attention as Equiformer
|
| 526 |
-
use_attn_renorm (bool): Whether to re-normalize attention weights
|
| 527 |
-
ffn_activation (str): Type of activation function for feedforward network
|
| 528 |
-
use_gate_act (bool): If `True`, use gate activation. Otherwise, use S2 activation
|
| 529 |
-
use_grid_mlp (bool): If `True`, use projecting to grids and performing MLPs for FFN.
|
| 530 |
-
use_sep_s2_act (bool): If `True`, use separable S2 activation when `use_gate_act` is False.
|
| 531 |
-
|
| 532 |
-
norm_type (str): Type of normalization layer (['layer_norm', 'layer_norm_sh'])
|
| 533 |
-
|
| 534 |
-
alpha_drop (float): Dropout rate for attention weights
|
| 535 |
-
drop_path_rate (float): Drop path rate
|
| 536 |
-
proj_drop (float): Dropout rate for outputs of attention and FFN
|
| 537 |
-
"""
|
| 538 |
-
|
| 539 |
-
def __init__(
|
| 540 |
-
self,
|
| 541 |
-
sphere_channels,
|
| 542 |
-
attn_hidden_channels,
|
| 543 |
-
num_heads,
|
| 544 |
-
attn_alpha_channels,
|
| 545 |
-
attn_value_channels,
|
| 546 |
-
ffn_hidden_channels,
|
| 547 |
-
output_channels,
|
| 548 |
-
lmax_list,
|
| 549 |
-
mmax_list,
|
| 550 |
-
SO3_rotation,
|
| 551 |
-
mappingReduced,
|
| 552 |
-
SO3_grid,
|
| 553 |
-
max_num_elements,
|
| 554 |
-
edge_channels_list,
|
| 555 |
-
use_atom_edge_embedding=True,
|
| 556 |
-
use_m_share_rad=False,
|
| 557 |
-
attn_activation="silu",
|
| 558 |
-
use_s2_act_attn=False,
|
| 559 |
-
use_attn_renorm=True,
|
| 560 |
-
ffn_activation="silu",
|
| 561 |
-
use_gate_act=False,
|
| 562 |
-
use_grid_mlp=False,
|
| 563 |
-
use_sep_s2_act=True,
|
| 564 |
-
norm_type="rms_norm_sh",
|
| 565 |
-
alpha_drop=0.0,
|
| 566 |
-
drop_path_rate=0.0,
|
| 567 |
-
proj_drop=0.0,
|
| 568 |
-
):
|
| 569 |
-
super(TransBlockV2, self).__init__()
|
| 570 |
-
|
| 571 |
-
max_lmax = max(lmax_list)
|
| 572 |
-
self.norm_1 = get_normalization_layer(
|
| 573 |
-
norm_type, lmax=max_lmax, num_channels=sphere_channels
|
| 574 |
-
)
|
| 575 |
-
|
| 576 |
-
self.ga = SO2EquivariantGraphAttention(
|
| 577 |
-
sphere_channels=sphere_channels,
|
| 578 |
-
hidden_channels=attn_hidden_channels,
|
| 579 |
-
num_heads=num_heads,
|
| 580 |
-
attn_alpha_channels=attn_alpha_channels,
|
| 581 |
-
attn_value_channels=attn_value_channels,
|
| 582 |
-
output_channels=sphere_channels,
|
| 583 |
-
lmax_list=lmax_list,
|
| 584 |
-
mmax_list=mmax_list,
|
| 585 |
-
SO3_rotation=SO3_rotation,
|
| 586 |
-
mappingReduced=mappingReduced,
|
| 587 |
-
SO3_grid=SO3_grid,
|
| 588 |
-
max_num_elements=max_num_elements,
|
| 589 |
-
edge_channels_list=edge_channels_list,
|
| 590 |
-
use_atom_edge_embedding=use_atom_edge_embedding,
|
| 591 |
-
use_m_share_rad=use_m_share_rad,
|
| 592 |
-
activation=attn_activation,
|
| 593 |
-
use_s2_act_attn=use_s2_act_attn,
|
| 594 |
-
use_attn_renorm=use_attn_renorm,
|
| 595 |
-
use_gate_act=use_gate_act,
|
| 596 |
-
use_sep_s2_act=use_sep_s2_act,
|
| 597 |
-
alpha_drop=alpha_drop,
|
| 598 |
-
)
|
| 599 |
-
|
| 600 |
-
self.drop_path = GraphDropPath(drop_path_rate) if drop_path_rate > 0.0 else None
|
| 601 |
-
self.proj_drop = (
|
| 602 |
-
EquivariantDropoutArraySphericalHarmonics(proj_drop, drop_graph=False)
|
| 603 |
-
if proj_drop > 0.0
|
| 604 |
-
else None
|
| 605 |
-
)
|
| 606 |
-
|
| 607 |
-
self.norm_2 = get_normalization_layer(
|
| 608 |
-
norm_type, lmax=max_lmax, num_channels=sphere_channels
|
| 609 |
-
)
|
| 610 |
-
|
| 611 |
-
self.ffn = FeedForwardNetwork(
|
| 612 |
-
sphere_channels=sphere_channels,
|
| 613 |
-
hidden_channels=ffn_hidden_channels,
|
| 614 |
-
output_channels=output_channels,
|
| 615 |
-
lmax_list=lmax_list,
|
| 616 |
-
mmax_list=mmax_list,
|
| 617 |
-
SO3_grid=SO3_grid,
|
| 618 |
-
activation=ffn_activation,
|
| 619 |
-
use_gate_act=use_gate_act,
|
| 620 |
-
use_grid_mlp=use_grid_mlp,
|
| 621 |
-
use_sep_s2_act=use_sep_s2_act,
|
| 622 |
-
)
|
| 623 |
-
|
| 624 |
-
if sphere_channels != output_channels:
|
| 625 |
-
self.ffn_shortcut = SO3_LinearV2(
|
| 626 |
-
sphere_channels, output_channels, lmax=max_lmax
|
| 627 |
-
)
|
| 628 |
-
else:
|
| 629 |
-
self.ffn_shortcut = None
|
| 630 |
-
|
| 631 |
-
def forward(
|
| 632 |
-
self,
|
| 633 |
-
x, # SO3_Embedding
|
| 634 |
-
atomic_numbers,
|
| 635 |
-
edge_distance,
|
| 636 |
-
edge_index,
|
| 637 |
-
batch, # for GraphDropPath
|
| 638 |
-
):
|
| 639 |
-
|
| 640 |
-
output_embedding = x
|
| 641 |
-
|
| 642 |
-
x_res = output_embedding.embedding
|
| 643 |
-
output_embedding.embedding = self.norm_1(output_embedding.embedding)
|
| 644 |
-
output_embedding = self.ga(
|
| 645 |
-
output_embedding, atomic_numbers, edge_distance, edge_index
|
| 646 |
-
)
|
| 647 |
-
|
| 648 |
-
if self.drop_path is not None:
|
| 649 |
-
output_embedding.embedding = self.drop_path(
|
| 650 |
-
output_embedding.embedding, batch
|
| 651 |
-
)
|
| 652 |
-
if self.proj_drop is not None:
|
| 653 |
-
output_embedding.embedding = self.proj_drop(
|
| 654 |
-
output_embedding.embedding, batch
|
| 655 |
-
)
|
| 656 |
-
|
| 657 |
-
output_embedding.embedding = output_embedding.embedding + x_res
|
| 658 |
-
|
| 659 |
-
x_res = output_embedding.embedding
|
| 660 |
-
output_embedding.embedding = self.norm_2(output_embedding.embedding)
|
| 661 |
-
output_embedding = self.ffn(output_embedding)
|
| 662 |
-
|
| 663 |
-
if self.drop_path is not None:
|
| 664 |
-
output_embedding.embedding = self.drop_path(
|
| 665 |
-
output_embedding.embedding, batch
|
| 666 |
-
)
|
| 667 |
-
if self.proj_drop is not None:
|
| 668 |
-
output_embedding.embedding = self.proj_drop(
|
| 669 |
-
output_embedding.embedding, batch
|
| 670 |
-
)
|
| 671 |
-
|
| 672 |
-
if self.ffn_shortcut is not None:
|
| 673 |
-
shortcut_embedding = SO3_Embedding(
|
| 674 |
-
0,
|
| 675 |
-
output_embedding.lmax_list.copy(),
|
| 676 |
-
self.ffn_shortcut.in_features,
|
| 677 |
-
device=output_embedding.device,
|
| 678 |
-
dtype=output_embedding.dtype,
|
| 679 |
-
)
|
| 680 |
-
shortcut_embedding.set_embedding(x_res)
|
| 681 |
-
shortcut_embedding.set_lmax_mmax(
|
| 682 |
-
output_embedding.lmax_list.copy(), output_embedding.lmax_list.copy()
|
| 683 |
-
)
|
| 684 |
-
shortcut_embedding = self.ffn_shortcut(shortcut_embedding)
|
| 685 |
-
x_res = shortcut_embedding.embedding
|
| 686 |
-
|
| 687 |
-
output_embedding.embedding = output_embedding.embedding + x_res
|
| 688 |
-
|
| 689 |
-
return output_embedding
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
nets/equiformer_v2/wigner.py
DELETED
|
@@ -1,38 +0,0 @@
|
|
| 1 |
-
import os
|
| 2 |
-
import torch
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
# Borrowed from e3nn @ 0.4.0:
|
| 6 |
-
# https://github.com/e3nn/e3nn/blob/0.4.0/e3nn/o3/_wigner.py#L10
|
| 7 |
-
# _Jd is a list of tensors of shape (2l+1, 2l+1)
|
| 8 |
-
_Jd = torch.load(os.path.join(os.path.dirname(__file__), "Jd.pt"), weights_only=True)
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
# Borrowed from e3nn @ 0.4.0:
|
| 12 |
-
# https://github.com/e3nn/e3nn/blob/0.4.0/e3nn/o3/_wigner.py#L37
|
| 13 |
-
#
|
| 14 |
-
# In 0.5.0, e3nn shifted to torch.matrix_exp which is significantly slower:
|
| 15 |
-
# https://github.com/e3nn/e3nn/blob/0.5.0/e3nn/o3/_wigner.py#L92
|
| 16 |
-
def wigner_D(l, alpha, beta, gamma):
|
| 17 |
-
if not l < len(_Jd):
|
| 18 |
-
raise NotImplementedError(
|
| 19 |
-
f"wigner D maximum l implemented is {len(_Jd) - 1}, send us an email to ask for more"
|
| 20 |
-
)
|
| 21 |
-
|
| 22 |
-
alpha, beta, gamma = torch.broadcast_tensors(alpha, beta, gamma)
|
| 23 |
-
J = _Jd[l].to(dtype=alpha.dtype, device=alpha.device)
|
| 24 |
-
Xa = _z_rot_mat(alpha, l)
|
| 25 |
-
Xb = _z_rot_mat(beta, l)
|
| 26 |
-
Xc = _z_rot_mat(gamma, l)
|
| 27 |
-
return Xa @ J @ Xb @ J @ Xc
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
def _z_rot_mat(angle, l):
|
| 31 |
-
shape, device, dtype = angle.shape, angle.device, angle.dtype
|
| 32 |
-
M = angle.new_zeros((*shape, 2 * l + 1, 2 * l + 1))
|
| 33 |
-
inds = torch.arange(0, 2 * l + 1, 1, device=device)
|
| 34 |
-
reversed_inds = torch.arange(2 * l, -1, -1, device=device)
|
| 35 |
-
frequencies = torch.arange(l, -l - 1, -1, dtype=dtype, device=device)
|
| 36 |
-
M[..., inds, reversed_inds] = torch.sin(frequencies * angle[..., None])
|
| 37 |
-
M[..., inds, inds] = torch.cos(frequencies * angle[..., None])
|
| 38 |
-
return M
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
nets/prediction_utils.py
DELETED
|
@@ -1,34 +0,0 @@
|
|
| 1 |
-
import torch
|
| 2 |
-
from nets.scatter_utils import scatter_mean
|
| 3 |
-
|
| 4 |
-
GLOBAL_ATOM_NUMBERS = torch.tensor([1, 6, 7, 8])
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
def remove_mean_batch(x, indices):
|
| 8 |
-
mean = scatter_mean(x, indices, dim=0)
|
| 9 |
-
x = x - mean[indices]
|
| 10 |
-
return x
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
def compute_extra_props(batch, pos_require_grad=True):
|
| 14 |
-
"""Adds device, z, and removes mean batch"""
|
| 15 |
-
device = batch.pos.device
|
| 16 |
-
if hasattr(batch, "one_hot"):
|
| 17 |
-
# this is only for the HORM dataset
|
| 18 |
-
# it uses a weird convention
|
| 19 |
-
# atom types are encoded as one-hot vectors of shape (N, 5)
|
| 20 |
-
# where the fifth is unused, likely a padding or None class
|
| 21 |
-
# corresponds to H, C, N, O, None
|
| 22 |
-
indices = batch.one_hot.long().argmax(dim=1)
|
| 23 |
-
batch.z = GLOBAL_ATOM_NUMBERS.to(device)[indices.to(device)]
|
| 24 |
-
elif hasattr(batch, "z"):
|
| 25 |
-
batch.z = batch.z.to(device)
|
| 26 |
-
else:
|
| 27 |
-
raise ValueError("batch has no one_hot or z attribute")
|
| 28 |
-
batch.pos = remove_mean_batch(batch.pos, batch.batch)
|
| 29 |
-
# atomization energy. shape used by equiformerv2
|
| 30 |
-
if not hasattr(batch, "ae"):
|
| 31 |
-
batch.ae = torch.tensor(0.0, device=device, dtype=torch.float64)
|
| 32 |
-
if pos_require_grad:
|
| 33 |
-
batch.pos.requires_grad_(True)
|
| 34 |
-
return batch
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
nets/scatter_utils.py
DELETED
|
@@ -1,85 +0,0 @@
|
|
| 1 |
-
import torch
|
| 2 |
-
from typing import Optional
|
| 3 |
-
|
| 4 |
-
# copied from torch_scatter.scatter.py
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
def broadcast(src: torch.Tensor, other: torch.Tensor, dim: int):
|
| 8 |
-
if dim < 0:
|
| 9 |
-
dim = other.dim() + dim
|
| 10 |
-
if src.dim() == 1:
|
| 11 |
-
for _ in range(0, dim):
|
| 12 |
-
src = src.unsqueeze(0)
|
| 13 |
-
for _ in range(src.dim(), other.dim()):
|
| 14 |
-
src = src.unsqueeze(-1)
|
| 15 |
-
src = src.expand(other.size())
|
| 16 |
-
return src
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
def scatter_sum(
|
| 20 |
-
src: torch.Tensor,
|
| 21 |
-
index: torch.Tensor,
|
| 22 |
-
dim: int = -1,
|
| 23 |
-
out: Optional[torch.Tensor] = None,
|
| 24 |
-
dim_size: Optional[int] = None,
|
| 25 |
-
) -> torch.Tensor:
|
| 26 |
-
index = broadcast(index, src, dim)
|
| 27 |
-
if out is None:
|
| 28 |
-
size = list(src.size())
|
| 29 |
-
if dim_size is not None:
|
| 30 |
-
size[dim] = dim_size
|
| 31 |
-
elif index.numel() == 0:
|
| 32 |
-
size[dim] = 0
|
| 33 |
-
else:
|
| 34 |
-
size[dim] = int(index.max()) + 1
|
| 35 |
-
out = torch.zeros(size, dtype=src.dtype, device=src.device)
|
| 36 |
-
return out.scatter_add_(dim, index, src)
|
| 37 |
-
else:
|
| 38 |
-
return out.scatter_add_(dim, index, src)
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
def scatter_add(
|
| 42 |
-
src: torch.Tensor,
|
| 43 |
-
index: torch.Tensor,
|
| 44 |
-
dim: int = -1,
|
| 45 |
-
out: Optional[torch.Tensor] = None,
|
| 46 |
-
dim_size: Optional[int] = None,
|
| 47 |
-
) -> torch.Tensor:
|
| 48 |
-
return scatter_sum(src, index, dim, out, dim_size)
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
def scatter_mul(
|
| 52 |
-
src: torch.Tensor,
|
| 53 |
-
index: torch.Tensor,
|
| 54 |
-
dim: int = -1,
|
| 55 |
-
out: Optional[torch.Tensor] = None,
|
| 56 |
-
dim_size: Optional[int] = None,
|
| 57 |
-
) -> torch.Tensor:
|
| 58 |
-
return torch.ops.torch_scatter.scatter_mul(src, index, dim, out, dim_size)
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
def scatter_mean(
|
| 62 |
-
src: torch.Tensor,
|
| 63 |
-
index: torch.Tensor,
|
| 64 |
-
dim: int = -1,
|
| 65 |
-
out: Optional[torch.Tensor] = None,
|
| 66 |
-
dim_size: Optional[int] = None,
|
| 67 |
-
) -> torch.Tensor:
|
| 68 |
-
out = scatter_sum(src, index, dim, out, dim_size)
|
| 69 |
-
dim_size = out.size(dim)
|
| 70 |
-
|
| 71 |
-
index_dim = dim
|
| 72 |
-
if index_dim < 0:
|
| 73 |
-
index_dim = index_dim + src.dim()
|
| 74 |
-
if index.dim() <= index_dim:
|
| 75 |
-
index_dim = index.dim() - 1
|
| 76 |
-
|
| 77 |
-
ones = torch.ones(index.size(), dtype=src.dtype, device=src.device)
|
| 78 |
-
count = scatter_sum(ones, index, index_dim, None, dim_size)
|
| 79 |
-
count[count < 1] = 1
|
| 80 |
-
count = broadcast(count, out, dim)
|
| 81 |
-
if out.is_floating_point():
|
| 82 |
-
out.true_divide_(count)
|
| 83 |
-
else:
|
| 84 |
-
out.div_(count, rounding_mode="floor")
|
| 85 |
-
return out
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|