| |
|
|
| from ._tp_scatter_base import TensorProductScatter |
|
|
|
|
| def nequip_tp_desc( |
| irreps1, |
| irreps2, |
| irreps3, |
| ): |
| """Construct the NequIP version of channelwise tensor product descriptor. |
| |
| subscripts: ``weights[uv],lhs[iu],rhs[jv],output[ku]`` |
| |
| Args: |
| irreps1 (Irreps): Irreps of the first operand. |
| irreps2 (Irreps): Irreps of the second operand. |
| irreps3 (Irreps): Irreps of the output to consider. |
| """ |
| import cuequivariance as cue |
| from cuequivariance.group_theory.irreps_array.irrep_utils import into_list_of_irrep |
| import itertools |
|
|
| |
| |
|
|
| G = irreps1.irrep_class |
| irreps3_filter = into_list_of_irrep(G, irreps3) |
|
|
| d = cue.SegmentedTensorProduct.from_subscripts("uv,iu,jv,kuv+ijk") |
|
|
| for mul, ir in irreps1: |
| d.add_segment(1, (ir.dim, mul)) |
| for mul, ir in irreps2: |
| d.add_segment(2, (ir.dim, mul)) |
|
|
| irreps3 = [] |
| for (i1, (mul1, ir1)), (i2, (mul2, ir2)) in itertools.product( |
| enumerate(irreps1), enumerate(irreps2) |
| ): |
| for ir3 in ir1 * ir2: |
| if ir3 not in irreps3_filter: |
| continue |
|
|
| for cg in cue.clebsch_gordan(ir1, ir2, ir3): |
| d.add_path(None, i1, i2, None, c=cg, dims={"u": mul1, "v": mul2}) |
|
|
| irreps3.append((mul1 * mul2, ir3)) |
|
|
| irreps3 = cue.Irreps(G, irreps3) |
| irreps3, perm, inv = irreps3.sort() |
| d = d.permute_segments(3, inv) |
| d = d.normalize_paths_for_operand(-1) |
|
|
| return cue.EquivariantPolynomial( |
| [ |
| cue.IrrepsAndLayout(irreps1.new_scalars(d.operands[0].size), cue.ir_mul), |
| cue.IrrepsAndLayout(irreps1, cue.ir_mul), |
| cue.IrrepsAndLayout(irreps2, cue.ir_mul), |
| ], |
| [cue.IrrepsAndLayout(irreps3, cue.ir_mul)], |
| cue.SegmentedPolynomial.eval_last_operand(d), |
| ) |
|
|
|
|
| class CuEquivarianceTensorProductScatter(TensorProductScatter): |
| _nequip_custom_ops_libs = ("cuequivariance_torch",) |
|
|
| def __init__( |
| self, |
| feature_irreps_in, |
| irreps_edge_attr, |
| irreps_mid, |
| instructions, |
| ) -> None: |
| super().__init__( |
| feature_irreps_in=feature_irreps_in, |
| irreps_edge_attr=irreps_edge_attr, |
| irreps_mid=irreps_mid, |
| instructions=instructions, |
| ) |
| |
| |
|
|
| |
|
|
| |
| |
|
|
| import cuequivariance as cue |
| import cuequivariance_torch as cuet |
| from cuequivariance.group_theory.experimental.e3nn import O3_e3nn |
|
|
| self.tp_conv = cuet.SegmentedPolynomial( |
| nequip_tp_desc( |
| cue.Irreps(O3_e3nn, feature_irreps_in), |
| cue.Irreps(O3_e3nn, irreps_edge_attr), |
| cue.Irreps(O3_e3nn, irreps_mid), |
| ) |
| .flatten_coefficient_modes() |
| .squeeze_modes() |
| .polynomial, |
| method="fused_tp", |
| math_dtype=self.model_dtype, |
| ) |
|
|
| self.transpose_feat = cuet.TransposeIrrepsLayout( |
| feature_irreps_in, source=cue.mul_ir, target=cue.ir_mul |
| ) |
| self.transpose_out = cuet.TransposeIrrepsLayout( |
| irreps_mid, source=cue.ir_mul, target=cue.mul_ir |
| ) |
|
|
| def forward(self, x, edge_attr, edge_weight, edge_dst, edge_src): |
| return self.transpose_out( |
| self.tp_conv( |
| [edge_weight, self.transpose_feat(x), edge_attr], |
| {1: edge_src}, |
| {0: x}, |
| {0: edge_dst}, |
| )[0] |
| ) |
|
|