//  [python]
def calc_prob(self, data, fast=False, check='no', save='no', verbose=False, silence=False):
        x, edge_index = data.x, data.edge_index
        x = x.to(self.device)
        ori_adj = get_adj_tensor(edge_index.cpu()).to(self.device)
        # ori_adj = to_dense_adj(edge_index)
        
        nnodes = ori_adj.shape[0]
        adj_changes = Parameter(torch.FloatTensor(int(nnodes*(nnodes-1)/2)), requires_grad=True).to(self.device)
        torch.nn.init.uniform_(adj_changes, 1e-5, 1./nnodes)
        
        ori_adj_norm = get_normalize_adj_tensor(ori_adj, device=self.device)
        
        if fast: # only obtain k largest and/or smallest eigenvalues with faster eigendecomposition alg
            ori_e = torch.lobpcg(ori_adj_norm, k=10, largest=True)
        else:
            ori_e = torch.linalg.eigvalsh(ori_adj_norm)
        eigen_norm = torch.norm(ori_e)
        
        n_perturbations = int(self.ratio * (ori_adj.sum()/2))
        with tqdm(total=self.iteration, desc='Spectral Augment-'+self.dis_type, disable=silence) as pbar:
            verb = max(1, int(self.iteration/10))
            for t in range(1, self.iteration+1):
                modified_adj = self.get_modified_adj(ori_adj, self.reshape_m(nnodes, adj_changes))
                
                # add noise to make the graph asymmetric
                modified_adj_noise = modified_adj
                # modified_adj_noise = self.add_random_noise(modified_adj)
                adj_norm_noise = get_normalize_adj_tensor(modified_adj_noise, device=self.device)
                
                if fast: # only obtain k largest and/or smallest eigenvalues with faster eigendecomposition alg
                    e = torch.lobpcg(adj_norm_noise, k=10, largest=True)
                else: 
                    e = torch.linalg.eigvalsh(adj_norm_noise)
                eigen_self = torch.norm(e)
                
                # spectral distance
                eigen_mse = torch.norm(ori_e-e)
                
                if self.dis_type == 'max-l2':
                    reg_loss = eigen_mse / eigen_norm
                elif self.dis_type == 'min-l2':
                    reg_loss = -eigen_mse / eigen_norm
                elif self.dis_type == 'max':
                    reg_loss = eigen_self / eigen_norm
                    
                    # n = 100
                    # idx = torch.argsort(e)[:n]
                    # mask = torch.zeros_like(e).bool()
                    # mask[idx] = True
                    # eigen_low = torch.norm(e*mask, p=2)
                    # # eigen_gf = torch.pow(torch.norm(e*mask, p=2), 2) * torch.pow(torch.norm(torch.matmul(v.detach()*mask, x), p=2), 2)
                    
                    # idx2 = torch.argsort(e, descending=True)[:n]
                    # mask2 = torch.zeros_like(e).bool()
                    # mask2[idx2] = True
                    # eigen_high = torch.norm(e*mask2, p=2)
                    # # eigen_gf = torch.pow(torch.norm(e*mask, p=2), 2) * torch.pow(torch.norm(torch.matmul(v.detach()*mask, x), p=2), 2)
                    
                    # reg_loss = eigen_low - eigen_high
                    
                elif self.dis_type == 'min':
                    reg_loss = -eigen_self / eigen_norm
                    
                    # n = 100
                    # idx = torch.argsort(e)[:n]
                    # mask = torch.zeros_like(e).bool()
                    # mask[idx] = True
                    # eigen_low = torch.norm(e*mask, p=2)
                    # # eigen_gf = torch.pow(torch.norm(e*mask, p=2), 2) * torch.pow(torch.norm(torch.matmul(v.detach()*mask, x), p=2), 2)
                    
                    # idx2 = torch.argsort(e, descending=True)[:n]
                    # mask2 = torch.zeros_like(e).bool()
                    # mask2[idx2] = True
                    # eigen_high = torch.norm(e*mask2, p=2)
                    # # eigen_gf = torch.pow(torch.norm(e*mask, p=2), 2) * torch.pow(torch.norm(torch.matmul(v.detach()*mask, x), p=2), 2)
                    
                    # reg_loss = - eigen_low + eigen_high
                    
                elif self.dis_type.startswith('max-low'):
                    # low-rank loss in GF-attack
                    n = int(self.dis_type.replace('max-low',''))
                    idx = torch.argsort(e)[:n]
                    mask = torch.zeros_like(e).bool()
                    mask[idx] = True
                    eigen_gf = torch.norm(e*mask, p=2)
                    # eigen_gf = torch.pow(torch.norm(e*mask, p=2), 2) * torch.pow(torch.norm(torch.matmul(v.detach()*mask, x), p=2), 2)
                    reg_loss = eigen_gf
                elif self.dis_type.startswith('min-low'):
                    # low-rank loss in GF-attack
                    n = int(self.dis_type.replace('min-low',''))
                    idx = torch.argsort(e)[:n]
                    mask = torch.zeros_like(e).bool()
                    mask[idx] = True
                    eigen_gf = torch.norm(e*mask, p=2)
                    # eigen_gf = torch.pow(torch.norm(e*mask, p=2), 2) * torch.pow(torch.norm(torch.matmul(v.detach()*mask, x), p=2), 2)
                    reg_loss = -eigen_gf
                elif self.dis_type.startswith('max-high'):
                    # high-rank loss in GF-attack
                    n = int(self.dis_type.replace('max-high',''))
                    idx = torch.argsort(e, descending=True)[:n]
                    mask = torch.zeros_like(e).bool()
                    mask[idx] = True
                    eigen_gf = torch.norm(e*mask, p=2)
                    # eigen_gf = torch.pow(torch.norm(e*mask, p=2), 2) * torch.pow(torch.norm(torch.matmul(v.detach()*mask, x), p=2), 2)
                    reg_loss = eigen_gf
                elif self.dis_type.startswith('min-high'):
                    # high-rank loss in GF-attack
                    n = int(self.dis_type.replace('min-high',''))
                    idx = torch.argsort(e, descending=True)[:n]
                    mask = torch.zeros_like(e).bool()
                    mask[idx] = True
                    eigen_gf = torch.norm(e*mask, p=2)
                    # eigen_gf = torch.pow(torch.norm(e*mask, p=2), 2) * torch.pow(torch.norm(torch.matmul(v.detach()*mask, x), p=2), 2)
                    reg_loss = -eigen_gf
                elif self.dis_type.startswith('sep'):
                    
                    # n = int(self.dis_type.replace('sep',''))
                    # mask = torch.zeros_like(e).bool()
                    # mask[-n:] = True
                    # eigen_high = torch.masked_select(e, mask)
                    # reg_loss = eigen_high @ eigen_high.t() / (e @ e.t())
                    
                    # mask = e.ge(0.0)  # [-1, 1]
                    # eigen_high = torch.masked_select(e, mask)
                    # reg_loss = eigen_high @ eigen_high.t() / (e @ e.t())
                    
                    mask = e.le(0.0)  # [-1, 1]
                    ori_high = torch.masked_select(ori_e, mask)
                    high = torch.masked_select(e, mask)
                    mask2 = e.ge(0.0)
                    ori_low = torch.masked_select(ori_e, mask2)
                    low = torch.masked_select(e, mask2)
                    cos = torch.nn.CosineSimilarity(dim=0, eps=1e-6)
                    # reg_loss = cos(ori_high, high) + 1 - cos(ori_low, low)
                    reg_loss = cos(ori_high, high)
                    # reg_loss = torch.norm(ori_high - high, p=2)**2 / torch.norm(ori_e - e, p=2)**2
                else:
                    exit(f'unknown distance metric: {self.dis_type}')
                
                self.loss = reg_loss
                
                adj_grad = torch.autograd.grad(self.loss, adj_changes)[0]

                lr = self.lr / np.sqrt(t+1)
                adj_changes.data.add_(lr * adj_grad)
                
                before_p = torch.clamp(adj_changes, 0, 1).sum()
                before_l = adj_changes.min()
                before_r = adj_changes.max()
                before_m = torch.clamp(adj_changes, 0, 1).sum()/torch.count_nonzero(adj_changes)
                self.projection(n_perturbations, adj_changes)
                after_p = adj_changes.sum()
                after_l = adj_changes.min()
                after_r = adj_changes.max()
                after_m = adj_changes.sum()/torch.count_nonzero(adj_changes)
                
                if verbose and t%verb == 0:
                    print (
                        '-- Epoch {}, '.format(t), 
                        'reg loss = {:.4f} | '.format(reg_loss),
                        'ptb budget/b/a = {:.1f}/{:.1f}/{:.1f}'.format(n_perturbations, before_p, after_p),
                        'min b/a = {:.4f}/{:.4f}'.format(before_l, after_l),
                        'max b/a = {:.4f}/{:.4f}'.format(before_r, after_r),
                        'mean b/a = {:.4f}/{:.4f}'.format(before_m, after_m))
                    self.check_hist()

                pbar.set_postfix({'reg_loss': reg_loss.item(), 'budget': n_perturbations, 'b_proj': before_p.item(), 'a_proj': after_p.item()})
                pbar.update()
    
        data[self.dis_type] = SparseTensor.from_dense(self.reshape_m(nnodes, adj_changes))
    
#         if check == 'yes':
#             self.check_changes(ori_adj, adj_changes, y)
            
#         if save == 'yes':
#             out_dir = '../check'
#             os.makedirs(out_dir, exist_ok=True)
            
#             output_path = os.path.join(out_dir, self.dis_type+'_'+str(self.ratio)+'_'+str(self.lr)+'_'+str(self.iteration)+'.bin')
#             res = {'ori_e': ori_e, 'e': e, 'adj_change': adj_changes.detach().cpu(), 'ori_adj': ori_adj.detach().cpu()}
#             with open(output_path, 'wb') as file:
#                 pkl.dump(res, file)
        
        return data