Source code for spharapy.spharatransform

"""SPHARA transform

This module provides a class to perform the SPHARA transform. The
class is derived from :class:`spharapy.spharabasis.SpharaBasis`. It
provides methodes the SPHARA anaylsis and synthesis of spatially
irregularly sampled data.

"""

from typing import cast

import numpy as np
from numpy.typing import ArrayLike, NDArray

from spharapy.spharabasis import SpharaBasis

FloatArray = NDArray[np.floating]


[docs] class SpharaTransform(SpharaBasis): """SPHARA transform class This class is used to perform the SPHARA forward (analysis) and inverse (synthesis) transformation. Parameters ---------- triangsamples : trimesh object A trimesh object from the package spharapy in which the triangulation of the spatial arrangement of the sampling points is stored. The SPHARA basic functions are determined for this triangulation of the sample points. mode : {'unit', 'inv_euclidean', 'fem'}, optional The discretisation method used to estimate the Laplace-Beltrami operator. Using the option 'unit' all edges of the mesh are weighted by unit weighting function. The option 'inv_euclidean' results in edge weights corresponding to the inverse Euclidean distance of the edge lengths. The option 'fem' uses a FEM discretisation. The default weighting function is 'fem'. """ def __init__(self, triangsamples=None, mode="fem"): SpharaBasis.__init__(self, triangsamples, mode) self._basis: FloatArray | None = None self._frequencies: FloatArray | None = None self._massmatrix: FloatArray | None = None def _ensure_basis(self) -> None: """Ensure basis (and mass matrix for FEM) are initialized.""" if self._basis is None or self._frequencies is None: self.basis() if self._basis is None: raise RuntimeError("SPHARA basis not initialized; call basis() or fit().") if self._mode == "fem" and self._massmatrix is None: raise RuntimeError("Mass matrix not initialized for FEM mode.")
[docs] def analysis(self, data: ArrayLike) -> FloatArray: r"""Perform the SPHARA transform (analysis) This method performs the SPHARA transform (analysis) of data defined at spatially distributed sampling points described by a triangular mesh. The forward transformation is performed by matrix multiplication of the data matrix and the matrix with SPHARA basis functions :math:`\tilde{\boldsymbol{X}} = \boldsymbol{X} \cdot \boldsymbol{S}`, with the SPHARA basis :math:`\boldsymbol{S}`, the data matrix :math:`\boldsymbol{X}` and the SPHARA coefficients matix :math:`\tilde{\boldsymbol{X}}`. In the forward transformation using SPHARA basic functions determined by discretization with FEM approach, the modified scalar product including the mass matrix is used :math:`\tilde{\boldsymbol{X}} = \boldsymbol{X} \cdot \boldsymbol{B} \cdot \boldsymbol{S}`, with the mass matrix :math:`\boldsymbol{B}`. Parameters ---------- data : array, shape(m, n_points) A matrix with data to be transformed (analyzed) by SPHARA. The number of vertices of the triangular mesh is n_points. The order of the spatial sample points must correspond to that in the vertex list used to determine the SPHARA basis functions. Returns ------- coefficients : array, shape (m, n_points) A matrix containing the SPHARA coefficients. The coefficients are sorted column by column with increasing spatial frequency, starting with DC in the first column. Examples -------- Import the necessary packages >>> import numpy as np >>> from spharapy import trimesh as tm >>> from spharapy import spharatransform as st >>> testtrimesh = tm.TriMesh([[0, 1, 2]], [[1., 0., 0.], [0., 2., 0.], ... [0., 0., 3.]]) >>> st_fem_simple = st.SpharaTransform(testtrimesh, mode='fem') >>> data = np.concatenate([[[0., 0., 0.], [1., 1., 1.]], ... np.transpose(st_fem_simple.basis()[0])]) >>> data array([[ 0. , 0. , 0. ], [ 1. , 1. , 1. ], [ 0.53452248, 0.53452248, 0.53452248], [-0.49487166, -0.98974332, 1.48461498], [ 1.42857143, -1.14285714, -0.28571429]]) >>> coef_fem_simple = st_fem_simple.analysis(data) >>> coef_fem_simple array([[ 0.00000000e+00, 0.00000000e+00, 0.00000000e+00], [ 1.87082869e+00, 1.09883582e-16, -4.18977022e-16], [ 1.00000000e+00, -2.75573800e-16, -8.86630311e-18], [ -1.14766454e-16, 1.00000000e+00, 2.30648330e-16], [ 6.52367763e-17, 1.68383874e-16, 1.00000000e+00]]) """ # lazy evaluation, compute the basis at the first request and store # it until the triangular mesh or the discretization method is changed self._ensure_basis() basis: FloatArray = cast(FloatArray, np.asarray(self._basis, dtype=float)) M: FloatArray | None = ( None if self._massmatrix is None else cast(FloatArray, np.asarray(self._massmatrix, dtype=float)) ) data = np.asarray(data, dtype=float) # Does the number of spatial samples of the data correspond to # that of the basis functions? if basis.shape[0] != data.shape[1]: raise ValueError("Dimension mismatch") # compute the SPHARA coefficients if self._mode == "fem": assert M is not None coefficients: FloatArray = data @ M @ basis else: coefficients = data @ basis return coefficients
[docs] def synthesis(self, coefficients: ArrayLike) -> FloatArray: r"""Perform the inverse SPHARA transform (synthesis) This method performs the inverse SPHARA transform (synthesis) for data defined at spatially distributed sampling points described by a triangular mesh. The forward transformation is performed by matrix multiplication of the data matrix and the matrix with SPHARA basis functions :math:`\tilde{\boldsymbol{X}} = \boldsymbol{X} \cdot \boldsymbol{S}`, with the SPHARA basis :math:`\boldsymbol{S}`, the data matrix :math:`\boldsymbol{X}` and the SPHARA coefficients matix :math:`\tilde{\boldsymbol{X}}`. In the forward transformation using SPHARA basic functions determined by discretization with FEM approach, the modified scalar product including the mass matrix is used :math:`\tilde{\boldsymbol{X}} = \boldsymbol{X} \cdot \boldsymbol{B} \cdot \boldsymbol{S}`, with the mass matrix :math:`\boldsymbol{B}`. Parameters ---------- coefficients : array, shape (m, n_points) A matrix containing the SPHARA coefficients. The coefficients are sorted column by column with increasing spatial frequency, starting with DC in the first column. Returns ------- data : array, shape(m, n_points) A matrix with data to be forward transformed (analyzed) by SPHARA. The number of vertices of the triangular mesh is n_points. The order of the spatial sample points must correspond to that in the vertex list used to determine the SPHARA basis functions. Examples -------- >>> import numpy as np >>> from spharapy import trimesh as tm >>> from spharapy import spharatransform as st >>> testtrimesh = tm.TriMesh([[0, 1, 2]], [[1., 0., 0.], [0., 2., 0.], ... [0., 0., 3.]]) >>> st_fem_simple = st.SpharaTransform(testtrimesh, mode='fem') >>> data = np.concatenate([[[0., 0., 0.], [1., 1., 1.]], ... np.transpose(st_fem_simple.basis()[0])]) >>> data array([[ 0. , 0. , 0. ], [ 1. , 1. , 1. ], [ 0.53452248, 0.53452248, 0.53452248], [-0.49487166, -0.98974332, 1.48461498], [ 1.42857143, -1.14285714, -0.28571429]]) >>> coef_fem_simple = st_fem_simple.analysis(data) >>> coef_fem_simple array([[ 0.00000000e+00, 0.00000000e+00, 0.00000000e+00], [ 1.87082869e+00, 1.09883582e-16, -4.18977022e-16], [ 1.00000000e+00, -2.75573800e-16, -8.86630311e-18], [ -1.14766454e-16, 1.00000000e+00, 2.30648330e-16], [ 6.52367763e-17, 1.68383874e-16, 1.00000000e+00]]) >>> recon_fem_simple = st_fem_simple.synthesis(coef_fem_simple) >>> recon_fem_simple array([[ 0. , 0. , 0. ], [ 1. , 1. , 1. ], [ 0.53452248, 0.53452248, 0.53452248], [-0.49487166, -0.98974332, 1.48461498], [ 1.42857143, -1.14285714, -0.28571429]]) """ # lazy evaluation, compute the basis at the first request and store # it until the triangular mesh or the discretization method is changed self._ensure_basis() basis: FloatArray = cast(FloatArray, np.asarray(self._basis, dtype=float)) coefficients = np.asarray(coefficients, dtype=float) # Does the number of SPHARA coefficients correspond to that of # the basis functions? if basis.shape[0] != coefficients.shape[1]: raise ValueError("Dimension mismatch") # compute the data from SPHARA coefficients data: FloatArray = coefficients @ basis.T return data