"""Functional operators. This module defines various class to support implementation of functional operators, like Gradient and Laplacian."""
import abc
import gsops.backend as gs
import geomfum.linalg as la
from geomfum._registry import (
FaceDivergenceOperatorRegistry,
FaceOrientationOperatorRegistry,
FaceValuedGradientRegistry,
WhichRegistryMixins,
)
from geomfum.laplacian import LaplacianFinder, LaplacianSpectrumFinder
# TODO: remove functional; simply use operator
[docs]
class FunctionalOperator(abc.ABC):
"""Abstract class fot Functional operator."""
def __init__(self, shape):
self._shape = shape
@abc.abstractmethod
def __call__(self, point):
"""Apply operator.
Parameters
----------
point : array-like, shape=[..., n_vertices]
"""
[docs]
class VectorFieldOperator(abc.ABC):
"""Vector field operator."""
def __init__(self, shape):
self._shape = shape
@abc.abstractmethod
def __call__(self, vector):
"""Apply operator.
Parameters
----------
point : array-like, shape=[..., n_faces, 3]
"""
[docs]
class Laplacian(FunctionalOperator):
"""Laplacian operator on a shape.
Check [P2016]_ for representation choice.
Parameters
----------
stiffness_matrix : array-like, shape=[n_vertices, n_vertices]
Stiffness matrix.
mass_matrix : array-like, shape=[n_vertices, n_vertices]
Diagonal lumped mass matrix.
References
----------
.. [P2016] Giuseppe Patané. “STAR - Laplacian Spectral Kernels and Distances
for Geometry Processing and Shape Analysis.” Computer Graphics Forum 35,
no. 2 (2016): 599–624. https://doi.org/10.1111/cgf.12866.
"""
def __init__(self, shape, stiffness_matrix=None, mass_matrix=None):
super().__init__(shape)
self._stiffness_matrix = stiffness_matrix
self._mass_matrix = mass_matrix
self._basis = None
@property
def stiffness_matrix(self):
"""Stiffness matrix.
Returns
-------
stiffness_matrix : array-like, shape=[n_vertices, n_vertices]
Stiffness matrix.
"""
if self._stiffness_matrix is None:
self.find()
return self._stiffness_matrix
@property
def mass_matrix(self):
"""Mass matrix.
Returns
-------
mass_matrix : array-like, shape=[n_vertices, n_vertices]
Mass matrix.
"""
if self._mass_matrix is None:
self.find()
return self._mass_matrix
@property
def basis(self):
"""Laplace eigenbasis.
Returns
-------
basis : LaplaceEigenBasis
Laplace eigenbasis.
"""
if self._basis is None:
self.find_spectrum()
return self._basis
[docs]
def find(self, laplacian_finder=None, recompute=False):
"""Compute the laplacian matrices using an indicated algorithm.
Parameters
----------
laplacian_finder : BaseLaplacianFinder
Algorithm to find the Laplacian.
recompute : bool
Whether to recompute Laplacian if information is cached.
Returns
-------
stiffness_matrix : array-like, shape=[n_vertices, n_vertices]
Stiffness matrix.
mass_matrix : array-like, shape=[n_vertices, n_vertices]
Diagonal lumped mass matrix.
"""
if (
not recompute
and self._stiffness_matrix is not None
and self._mass_matrix is not None
):
return self._stiffness_matrix, self._mass_matrix
if laplacian_finder is None:
laplacian_finder = LaplacianFinder.from_registry(
mesh=self._shape.is_mesh, which="robust"
)
self._stiffness_matrix, self._mass_matrix = laplacian_finder(self._shape)
return self._stiffness_matrix, self._mass_matrix
[docs]
def find_spectrum(
self,
spectrum_size=100,
laplacian_spectrum_finder=None,
set_as_basis=True,
recompute=False,
):
"""Compute the spectrum of the Laplacian operator.
Parameters
----------
spectrum_size : int
Spectrum size. Ignored if ``laplacian_spectrum_finder`` is not None.
laplacian_spectrum_finder : LaplacianSpectrumFinder
Algorithm to find Laplacian spectrum.
set_as_basis : bool
Whether to set spectrum as basis.
recompute : bool
Whether to recompute spectrum if information is cached in basis.
Returns
-------
eigenvals : array-like, shape=[spectrum_size]
Eigenvalues.
eigenvecs : array-like, shape=[n_vertices, spectrum_size]
Eigenvectors.
"""
if not recompute and self._basis is not None:
if set_as_basis:
self._shape.set_basis(self.basis)
return self.basis.full_vals, self.basis.full_vecs
if laplacian_spectrum_finder is None:
laplacian_spectrum_finder = LaplacianSpectrumFinder(
spectrum_size=spectrum_size,
nonzero=False,
fix_sign=False,
)
self._basis = laplacian_spectrum_finder(self._shape, as_basis=True)
if set_as_basis:
self._shape.set_basis(self.basis)
return self.basis.full_vals, self.basis.full_vecs
def __call__(self, function):
"""Apply operator.
Parameters
----------
function : array-like, shape=[..., n_vertices]
Returns
-------
result : array-like, shape=[..., n_vertices]
The Laplacian applied to the input function.
"""
Sf = la.matvecmul(self.stiffness_matrix, function)
mass_vec = gs.sparse.to_dense(self.mass_matrix).diagonal()
return la.matvecmul(gs.sparse.dia_matrix(1 / mass_vec), Sf)
[docs]
class FaceValuedGradient(WhichRegistryMixins, FunctionalOperator):
"""Gradient of a function on a mesh.
Computes the gradient of a function on f using linear
interpolation between vertices.
"""
_Registry = FaceValuedGradientRegistry
[docs]
class Gradient(FunctionalOperator):
"""Gradient of a function f on a shape defined on the points.
Parameters
----------
gradient_matrix : array-like, shape=[n_vertices, n_vertices]
Gradient matrix.
"""
def __init__(self, shape, gradient_matrix=None):
super().__init__(shape)
self._gradient_matrix = gradient_matrix
@property
def gradient_matrix(self):
"""Compute the gradient operator as a complex sparse matrix.
This code locally fits a linear function to the scalar values at each vertex and its neighbors, extracts the gradient in the tangent plane, and assembles the global sparse matrix that acts as the discrete gradient operator on the mesh.
Returns
-------
grad_op : complex gs.sparse.csc_matrix, shape=[n_vertices, n_vertices]
Complex sparse matrix representing the gradient operator.
The real part corresponds to the X component in the local tangent frame,
and the imaginary part corresponds to the Y component.
"""
if self._gradient_matrix is None:
from geomfum.shape.shape_utils import compute_gradient_matrix_fem
self._gradient_matrix = compute_gradient_matrix_fem(
self._shape.vertices,
self._shape.edges,
self._shape.edge_tangent_vectors,
)
return self._gradient_matrix
def __call__(self, function):
"""Apply operator.
Parameters
----------
function : array-like, shape=[..., n_vertices]
Returns
-------
feat_grad : array-like, shape=[..., n_vertices, 2]
Gradient of the function at the vertices, where the last dimension
contains the X and Y components in the local tangent frame.
"""
feat_gradX = self.gradient_matrix.real @ function
feat_gradY = self.gradient_matrix.imag @ function
return gs.stack((feat_gradX, feat_gradY), -1)
[docs]
class FaceDivergenceOperator(WhichRegistryMixins, VectorFieldOperator):
"""Divergence of a function on a mesh."""
_Registry = FaceDivergenceOperatorRegistry
[docs]
class FaceOrientationOperator(WhichRegistryMixins, VectorFieldOperator):
r"""Orientation operator associated to a gradient field.
For a given function :math:`g` on the vertices, this operator linearly computes
:math:`< \grad(f) x \grad(g)`, n> for each vertex by averaging along the adjacent
faces.
In practice, we compute :math:`< n x \grad(f), \grad(g) >` for simpler computation.
"""
_Registry = FaceOrientationOperatorRegistry