import torch

def compute_laplacian(aff_matrix, lap_type="normalized"):
        r"""Compute a graph Laplacian.

        For undirected graphs, the combinatorial Laplacian is defined as

        .. math:: L = D - W,

        where :math:`W` is the weighted adjacency matrix and :math:`D` the
        weighted degree matrix. The normalized Laplacian is defined as

        .. math:: L = I - D^{-1/2} W D^{-1/2},

        where :math:`I` is the identity matrix.
        """
        aff_matrix = aff_matrix.to(torch.float)
        n_samples = aff_matrix.shape[0]
        degrees = torch.ravel(aff_matrix.sum(axis=0))

        if lap_type == "combinatorial":
                D = torch.diag(degrees)
                L = D - aff_matrix
        elif lap_type == "normalized":
                disconnected = (degrees == 0)
                mask = disconnected.to(torch.bool)
                d = torch.zeros(n_samples, dtype=torch.float)
                d[~mask] = torch.pow(degrees[~mask], -0.5)
                D = torch.diag(d)
                L = torch.eye(n_samples, dtype=torch.float) - (D @ aff_matrix @ D)
        else:
                raise

        return L