Source code for geomfum.laplacian

"""Laplacian-related algorithms."""

import abc

import geomstats.backend as gs

import geomfum.backend as xgs
import geomfum.wrap as _wrap  # noqa (for register)
from geomfum._registry import LaplacianFinderRegistry, MeshWhichRegistryMixins
from geomfum.basis import LaplaceEigenBasis
from geomfum.numerics.eig import ScipyEigsh


[docs] class BaseLaplacianFinder(abc.ABC): """Algorithm to find the Laplacian.""" @abc.abstractmethod def __call__(self, shape): """Apply algorithm. Parameters ---------- shape : Shape Shape. Returns ------- stiffness_matrix : array-like, shape=[n_vertices, n_vertices] Stiffness matrix. mass_matrix : array-like, shape=[n_vertices, n_vertices] Mass matrix. """
[docs] class LaplacianFinder(MeshWhichRegistryMixins, BaseLaplacianFinder): """Algorithm to find the Laplacian.""" _Registry = LaplacianFinderRegistry def __call__(self, shape): """Apply algorithm. Laplace Beltrami operator with cotangent weights formulation. Parameters ---------- shape : TriangleMesh Mesh. Returns ------- stiffness_matrix : sparse.csc_matrix, shape=[n_vertices, n_vertices] Stiffness matrix. mass_matrix : scipy.sparse.dia_matrix or sparse.csc_matrix, shape=[n_vertices, n_vertices] Diagonal lumped mass matrix. """ face_vertex_coords = shape.face_vertex_coords edges21 = face_vertex_coords[:, 2] - face_vertex_coords[:, 1] edges02 = face_vertex_coords[:, 0] - face_vertex_coords[:, 2] edges10 = face_vertex_coords[:, 1] - face_vertex_coords[:, 0] elen21 = gs.linalg.norm(edges21, axis=1) elen02 = gs.linalg.norm(edges02, axis=1) elen10 = gs.linalg.norm(edges10, axis=1) cos_angle12 = gs.einsum("ij,ij->i", -edges02, edges10) / (elen02 * elen10) cos_angle20 = gs.einsum("ij,ij->i", edges21, -edges10) / (elen21 * elen10) cos_angle01 = gs.einsum("ij,ij->i", -edges21, edges02) / (elen21 * elen02) vind012 = gs.concatenate( [shape.faces[:, 0], shape.faces[:, 1], shape.faces[:, 2]] ) vind120 = gs.concatenate( [shape.faces[:, 1], shape.faces[:, 2], shape.faces[:, 0]] ) cos_angles = gs.concatenate([cos_angle01, cos_angle12, cos_angle20]) cot_angles = 0.5 * cos_angles / gs.sqrt(1 - cos_angles**2) row = gs.concatenate([vind012, vind120, vind012, vind120]) col = gs.concatenate([vind120, vind012, vind012, vind120]) data = gs.concatenate([-cot_angles, -cot_angles, cot_angles, cot_angles]) stiffness_matrix = xgs.sparse.csc_matrix( gs.stack([row, col]), data, shape=(shape.n_vertices, shape.n_vertices), coalesce=True, ) mass_matrix = xgs.sparse.dia_matrix(shape.vertex_areas) return stiffness_matrix, mass_matrix
[docs] class LaplacianSpectrumFinder: """Algorithm to find Laplacian spectrum. Parameters ---------- spectrum_size : int Spectrum size. Ignored if ``eig_solver`` is not None. nonzero : bool Remove zero zero eigenvalue. fix_sign : bool Wheather to have all the first components with positive sign. laplacian_finder : BaseLaplacianFinder Algorithm to find the Laplacian. Ignored if Laplace and mass matrices were already computed. eig_solver : EigSolver Eigen solver. """ def __init__( self, spectrum_size=100, nonzero=False, fix_sign=False, laplacian_finder=None, eig_solver=None, ): if eig_solver is None: eig_solver = ScipyEigsh(spectrum_size=spectrum_size, sigma=-0.01) self.nonzero = nonzero self.fix_sign = fix_sign self.laplacian_finder = laplacian_finder self.eig_solver = eig_solver @property def spectrum_size(self): """Spectrum size. Returns ------- spectrum_size : int Spectrum size. """ return self.eig_solver.spectrum_size @spectrum_size.setter def spectrum_size(self, spectrum_size): """Set spectrum size. Parameters ---------- spectrum_size : int Spectrum size. """ self.eig_solver.spectrum_size = spectrum_size def __call__(self, shape, as_basis=True, recompute=False): """Apply algorithm. Parameters ---------- shape : Shape Shape. as_basis : bool Whether return basis or eigenvals/vecs. recompute : bool Whether to recompute Laplacian if information is cached. Returns ------- eigenvals : array-like, shape=[spectrum_size] Eigenvalues. (If ``basis is False``.) eigenvecs : array-like, shape=[n_vertices, spectrum_size] Eigenvectors. (If ``basis is False``.) basis : LaplaceEigenBasis A basis. (If ``basis is True``.) """ stiffness_matrix, mass_matrix = shape.laplacian.find( self.laplacian_finder, recompute=recompute ) eigenvals, eigenvecs = self.eig_solver(stiffness_matrix, M=mass_matrix) if self.nonzero: eigenvals = eigenvals[1:] eigenvecs = eigenvecs[:, 1:] if self.fix_sign: indices = eigenvecs[0, :] < 0 eigenvals[indices] *= -1 eigenvecs[:, indices] *= -1 if as_basis: return LaplaceEigenBasis(shape, eigenvals, eigenvecs) return eigenvals, eigenvecs