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