The decompose module provides functions for computing eigendecompositions of the mesh Laplacian and applying spectral filters to extract geometric features. These tools enable spectral geometry processing techniques like Heat Kernel Signatures (HKS) and other intrinsic shape descriptors.
Mathematical Background
Spectral decomposition solves the generalized eigenvalue problem:
where L is the cotangent Laplacian, M is the area matrix, and (λ, φ) are eigenvalue-eigenvector pairs. The eigenvectors form an orthonormal basis for functions on the mesh, analogous to the Fourier basis for functions on Euclidean space.
Core Functions
decompose_laplacian
def decompose_laplacian(
L,
M,
n_components: int = 100,
op_inv = None,
sigma: float = -1e-10,
tol: float = 1e-10,
ncv = None,
prefactor = None
) -> tuple[np.ndarray, np.ndarray]
Solve the generalized eigenvalue problem Lφ = λMφ.
Computes the smallest eigenvalues and corresponding eigenvectors of the Laplacian operator. Uses sparse eigensolvers (ARPACK) for efficiency on large meshes.
(n,n) sparse matrix of cotangent weights (Laplacian operator)
(n,n) sparse matrix of area weights (mass matrix)
Number of eigenvalues/eigenvectors to compute
op_inv
LinearOperator
default:"None"
Optional inverse operator for shift-invert mode. If None, will be computed based on prefactor.
Shift parameter for shift-invert mode. Eigenvalues near sigma are found most accurately.
Tolerance for eigenvalue convergence
Number of Lanczos vectors for ARPACK. Larger values may improve convergence but use more memory.
Preconditioner type. Options: "lu" for LU decomposition. Improves convergence for ill-conditioned problems.
Returns
(n_components,) array of eigenvalues sorted in ascending order
(n, n_components) array of corresponding eigenvectors
Example
from meshmash.laplacian import cotangent_laplacian
from meshmash.decompose import decompose_laplacian
L, M = cotangent_laplacian(mesh)
eigenvalues, eigenvectors = decompose_laplacian(L, M, n_components=50)
print(f"First 5 eigenvalues: {eigenvalues[:5]}")
print(f"Eigenvalue range: [{eigenvalues.min()}, {eigenvalues.max()}]")
decompose_mesh
def decompose_mesh(
mesh: Mesh,
n_components: int = 100,
op_inv = None,
sigma: float = -1e-10,
tol: float = 1e-10,
robust: bool = True,
mollify_factor: float = 1e-5,
prefactor = None
) -> tuple[np.ndarray, np.ndarray]
Compute eigendecomposition directly from a mesh.
This is a convenience function that combines Laplacian computation and eigendecomposition in one step.
Input mesh as tuple (vertices, faces) or object with those attributes
Number of eigenvalues/eigenvectors to compute
op_inv
LinearOperator
default:"None"
Optional inverse operator for shift-invert mode
Shift parameter for eigenvalue solver
Tolerance for convergence
Use robust Laplacian computation for non-manifold meshes
Mollification factor for robust Laplacian
Preconditioner type (e.g., "lu")
Returns
(n_components,) array of eigenvalues
(n, n_components) array of eigenvectors
Example
from meshmash.decompose import decompose_mesh
eigenvalues, eigenvectors = decompose_mesh(
mesh,
n_components=100,
robust=True
)
# Reconstruct a function using the spectral basis
coefficients = eigenvectors.T @ vertex_function
reconstructed = eigenvectors @ coefficients
decompose_laplacian_by_bands
def decompose_laplacian_by_bands(
L,
M,
max_eigenvalue: float = 1e-9,
band_size: int = 50,
truncate_extra: bool = True,
verbose: bool = False
) -> tuple[np.ndarray, np.ndarray]
Compute eigendecomposition up to a maximum eigenvalue using a band-by-band algorithm.
This method is more efficient than computing a fixed number of eigenpairs when you want all eigenvalues below a threshold. Based on the algorithm from Levy & Zhang (2009).
Compute all eigenvalues up to this threshold
Number of eigenvalues to compute in each band. Affects performance but not results.
If True, truncate to exactly max_eigenvalue. Otherwise may overshoot by up to one band.
Print progress information
Returns
Array of all eigenvalues up to max_eigenvalue
(n, k) array of corresponding eigenvectors where k is determined by max_eigenvalue
Example
from meshmash.decompose import decompose_laplacian_by_bands
# Compute all eigenpairs with eigenvalue < 1e-8
eigenvalues, eigenvectors = decompose_laplacian_by_bands(
L, M,
max_eigenvalue=1e-8,
band_size=50,
verbose=True
)
print(f"Computed {len(eigenvalues)} eigenpairs")
Spectral Filtering
spectral_geometry_filter
def spectral_geometry_filter(
mesh: Mesh,
filter: Optional[Callable] = None,
max_eigenvalue: float = 1e-8,
band_size: int = 50,
truncate_extra: bool = True,
drop_first: bool = True,
decomposition_dtype: Optional[np.dtype] = np.float64,
robust: bool = True,
mollify_factor: float = 1e-5,
verbose: int = False
) -> np.ndarray
Apply a spectral filter to the geometry of a mesh to extract features.
This is the core function for computing spectral geometric features. It decomposes the Laplacian and applies a user-defined filter function to the eigenvalues to produce per-vertex feature vectors.
Input mesh as tuple (vertices, faces) or object with those attributes. Can also be a pre-computed (L, M) tuple of Laplacian and area matrices.
Function that takes 1D array of eigenvalues and returns 2D array of filter coefficients with shape (n_filters, n_eigenvalues). If None, returns the eigenvectors directly.
Maximum eigenvalue to compute up to
Number of eigenvalues per band in the band-by-band algorithm
Truncate to exactly max_eigenvalue
Drop the first eigenpair (eigenvalue=0, constant eigenvector) which is often not useful
decomposition_dtype
np.dtype
default:"np.float64"
Numerical precision for computations. Use np.float32 for faster computation with slightly lower accuracy.
Use robust Laplacian computation
Mollification factor for robust Laplacian
Verbosity level. Higher values print more information.
Returns
(n_vertices, n_features) array of spectral features. If filter=None, returns tuple (eigenvalues, eigenvectors) instead.
Example
from meshmash.decompose import spectral_geometry_filter
import numpy as np
# Define a custom filter (e.g., low-pass filter)
def low_pass_filter(eigenvalues):
cutoff = 1e-9
weights = (eigenvalues < cutoff).astype(float)
return weights[np.newaxis, :] # shape: (1, n_eigenvalues)
features = spectral_geometry_filter(
mesh,
filter=low_pass_filter,
max_eigenvalue=1e-8,
robust=True
)
Notes
Numerical errors often indicate a malformed mesh. Using robust=True is recommended for non-manifold or poor quality meshes.
References
- [1] Vallet, B., & Levy, B. (2008). “Spectral Geometry Processing with Manifold Harmonics.”
- [2] Sharp, N., & Crane, K. (2020). “A Laplacian for Nonmanifold Triangle Meshes.”
compute_hks
def compute_hks(
mesh: Mesh,
max_eigenvalue: float = 1e-8,
t_max: Optional[float] = None,
t_min: Optional[float] = None,
n_components: int = 32,
band_size: int = 50,
truncate_extra: bool = False,
drop_first: bool = False,
robust: bool = True,
mollify_factor: float = 1e-5,
decomposition_dtype: Optional[np.dtype] = np.float64,
verbose: int = False
) -> np.ndarray
Compute Heat Kernel Signatures (HKS) for each vertex.
HKS is an intrinsic, multi-scale shape descriptor that captures local geometric information at different time scales. It is invariant to isometric deformations.
Maximum eigenvalue for decomposition
Maximum time scale for heat diffusion
Minimum time scale for heat diffusion
Number of time scales (features per vertex)
Band size for eigendecomposition
Truncate to max_eigenvalue exactly
decomposition_dtype
np.dtype
default:"np.float64"
Computation precision
Returns
(n_vertices, n_components) array of Heat Kernel Signatures
Example
from meshmash.decompose import compute_hks
hks_features = compute_hks(
mesh,
max_eigenvalue=1e-7,
n_components=64,
robust=True,
verbose=True
)
# Use for shape matching, segmentation, etc.
print(f"HKS shape: {hks_features.shape}")
Mathematical Background
The Heat Kernel Signature at time t is defined as:
HKS(x, t) = Σ_i exp(-λ_i * t) * φ_i(x)²
where λ_i and φ_i are eigenvalues and eigenvectors of the Laplacian.
compute_geometry_vectors
def compute_geometry_vectors(
mesh: Mesh,
max_eigenvalue: float = 1e-8,
n_components: int = 32,
band_size: int = 50,
truncate_extra: bool = False,
drop_first: bool = False,
robust: bool = True,
mollify_factor: float = 1e-5,
decomposition_dtype: Optional[np.dtype] = np.float64,
verbose: int = False
) -> np.ndarray
Compute spectral geometry feature vectors using B-spline filters.
This function uses B-spline basis functions distributed across the eigenvalue spectrum to create multi-scale geometric features.
Maximum eigenvalue for decomposition
Number of B-spline basis functions (features per vertex)
Band size for eigendecomposition
Truncate to max_eigenvalue exactly
decomposition_dtype
np.dtype
default:"np.float64"
Computation precision
Returns
(n_vertices, n_components) array of geometry feature vectors
Example
from meshmash.decompose import compute_geometry_vectors
geom_features = compute_geometry_vectors(
mesh,
max_eigenvalue=1e-7,
n_components=128,
robust=True
)
# Use for machine learning on mesh data
print(f"Feature vectors shape: {geom_features.shape}")
Utility Functions
get_hks_filter
def get_hks_filter(
t_max: Optional[float] = None,
t_min: Optional[float] = None,
n_scales: int = 32,
dtype: np.dtype = np.float64
) -> Callable
Create a Heat Kernel Signature filter function.
Returns a callable that computes HKS coefficients for given eigenvalues.
dtype
np.dtype
default:"np.float64"
Data type for computation
Returns
Function that takes eigenvalues and returns HKS filter coefficients
construct_bspline_basis
def construct_bspline_basis(
e_min: float,
e_max: float,
n_components: int
) -> list[BSpline]
Construct a set of B-spline basis functions for spectral filtering.
Creates cubic B-spline basis functions uniformly distributed in the eigenvalue range. This is a lower-level function used by construct_bspline_filter.
Number of B-spline basis functions to create
Returns
List of scipy.interpolate.BSpline objects representing the basis functions
Example
from meshmash.decompose import construct_bspline_basis
import numpy as np
# Create 32 basis functions over eigenvalue range [0, 1e-8]
bases = construct_bspline_basis(0.0, 1e-8, 32)
# Evaluate each basis at specific eigenvalues
eigenvalues = np.linspace(0, 1e-8, 100)
for i, basis in enumerate(bases):
values = basis(eigenvalues)
print(f"Basis {i}: max value = {values.max():.3f}")
construct_bspline_filter
def construct_bspline_filter(
e_min: float,
e_max: float,
n_components: int
) -> Callable
Construct a B-spline based spectral filter.
Creates a filter using cubic B-spline basis functions uniformly distributed in the eigenvalue range [e_min, e_max].
Number of B-spline basis functions
Returns
Function that takes eigenvalues and returns B-spline filter coefficients
Example
from meshmash.decompose import construct_bspline_filter, spectral_geometry_filter
filter_func = construct_bspline_filter(0.0, 1e-8, 64)
features = spectral_geometry_filter(
mesh,
filter=filter_func,
max_eigenvalue=1e-8
)