//  [python]
def forward(
            self,
            pos,
            x,
            label,
            matrices={},
            oper_dict=[],
            return_predicted_feat=False
        ):
        bz, N, x_k = pos.size(0), pos.size(1), x.size(-1)

        ''' ESTIMATE normals '''
        if 'normals' not in matrices:
            if 'flow' in matrices:
                normals = matrices['flow']
            else:
                normals = self.estimate_normals(pos)
            # normals = self.calculate_part_offset(pos, label)
        else:
            normals = matrices['normals']
        ''' ESTIMATE normals '''

        ''' INITIALIZE loss '''
        inter_gt_l = torch.zeros((1,), dtype=torch.float32, device=x.device)
        ''' INITIALIZE loss '''

        if oper_dict is None:
            return inter_gt_l

        predicted_feat = []

        for i, cur_oper_dict in enumerate(oper_dict):
            # GET part-level operator index

            # A valid operator index
            if self.module_index_to_selected_oper_dict[i] is not None:

                # cur_oper_dict_list = self.from_oper_dict_to_oper_list(cur_oper_dict)
                # module_index = self.selected_oper_dict_to_module_index[cur_oper_dict_list]
                ''' GENERATE part-level features for each point '''
                with torch.no_grad(): # label.size = bz x N x k
                    if self.args.part_aware:
                        pp_label_sim = torch.matmul(label, label.transpose(1, 2)) / \
                                       torch.clamp(torch.norm(label, dim=2, keepdim=True) * torch.norm(label.transpose(1, 2), dim=1,
                                                                                                       keepdim=True), min=1e-9)
                    else:
                        pp_label_sim = torch.matmul(label, label.transpose(1, 2)) / \
                                       torch.clamp(
                                           torch.norm(label, dim=2, keepdim=True) * torch.norm(label.transpose(1, 2),
                                                                                               dim=1,
                                                                                               keepdim=True), min=1e-9)
                        pp_label_sim[:, :, :] = 1.

                    if self.args.restrict_nei:
                        # r = 0.5
                        r = 0.5
                        pp_pos_dist = torch.sqrt(torch.sum((pos.unsqueeze(2) - pos.unsqueeze(1)) ** 2, dim=-1))
                        pp_label_sim[pp_pos_dist > r] = 0.0

                    # pp_label_sim = pp_label_sim.cuda()

                    permidx = np.random.permutation(N)
                    permidx = torch.from_numpy(permidx).to(pos.device)
                    pp_label_sim = pp_label_sim[:, :, permidx]
                    top_k_label_sim, top_k_label_sim_idx = torch.topk(pp_label_sim, k=self.pp_sim_k, dim=-1, largest=True)
                    perm_pos = pos[:, permidx]
                    perm_normals = normals[:, permidx]
                    if not self.args.geometric_aware:
                        perm_labels = label[:, permidx].float()


                # permidx = np.random.permutation(N)
                # permidx = torch.from_numpy(permidx).to(pos.device)
                # pp_label_sim = pp_label_sim[:, :, permidx]
                # top_k_label_sim, top_k_label_sim_idx = torch.topk(pp_label_sim, k=self.pp_sim_k, dim=-1, largest=True)
                # perm_pos = pos[:, permidx]
                # perm_normals = normals[:, permidx]
                pp_part_pos = batched_index_select(values=perm_pos, indices=top_k_label_sim_idx, dim=1)
                pp_part_normals = batched_index_select(values=perm_normals, indices=top_k_label_sim_idx, dim=1)

                if not self.args.geometric_aware:
                    pp_part_labels = batched_index_select(values=perm_labels, indices=top_k_label_sim_idx, dim=1)

                # ''' GENERATE part-level features for each point '''
                # # get samled neis
                # sampled_neis = self.get_near_sim_part_neighbours(label, pos)
                # pp_part_pos = batched_index_select(values=pos, indices=sampled_neis, dim=1)
                # pp_part_normals = batched_index_select(values=normals, indices=sampled_neis, dim=1)
                #
                # ''' GENERATE part-level features for each point '''

                if not self.args.geometric_aware:
                    to_pred_feat, to_pred_feat_dim = self.generate_gt_fats_by_oper_dict(cur_oper_dict,
                                                                                        [pp_part_labels])
                else:
                    to_pred_feat, to_pred_feat_dim = self.generate_gt_fats_by_oper_dict(cur_oper_dict, [pp_part_pos, pp_part_normals])

                if to_pred_feat is None: # when an invalid operation is sampled...
                    continue

                if len(to_pred_feat_dim) + 2 < len(to_pred_feat.size()):
                    to_pred_feat = to_pred_feat.contiguous().squeeze(2).contiguous()

                if len(to_pred_feat_dim) > 1:
                    to_pred_feat = to_pred_feat.contiguous().view(bz, N, -1).contiguous()

                cur_head_idx = self.module_index_to_head_idx_dict[i]

                if to_pred_feat is not None and not (torch.any(torch.isnan(to_pred_feat))):
                    ''' CALCULATE predicted features & mse loss '''
                    pred_feat = CorrFlowPredNet.apply_module_with_conv1d_bn(
                        x,
                        self.pred_feat_dim_to_head_list[i][cur_head_idx]
                    )
                    # print(to_pred_feat.size(), pred_feat.size())
                    # GET l2-loss between predicted features and ground-truth prediction features
                    cur_loss = torch.mean(torch.sum((pred_feat - to_pred_feat) ** 2, dim=-1))
                    ''' CALCULATE predicted features & mse loss '''
                    inter_gt_l += cur_loss
                    predicted_feat.append(pred_feat)

        if return_predicted_feat:
            if len(predicted_feat) > 0:
                predicted_feat = torch.cat(predicted_feat, dim=-1)
            return inter_gt_l, predicted_feat

        return inter_gt_l