andreasburger commited on
Commit
ebe6a47
·
verified ·
1 Parent(s): 5743861

Delete nets

Browse files
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