tred API documentation

# tred

The `tred` module implements tensor decomposition methods such as tensor PCA and tensor SVD. Most of the functionality in this module can be regarded as dimensionality reduction strategies for order-3 datasets of shape n, p, t, where p >> n > t.

View Source

     1"""
     2The `tred` module implements tensor decomposition methods such as tensor PCA
     3and tensor SVD. Most of the functionality in this module can be regarded as
     4dimensionality reduction strategies for order-3 datasets of shape n, p, t,
     5where p >> n > t.
     6"""
     7__version__ = "0.1.3"
     8
     9
    10from ._m_transforms import (
    11    generate_default_m_transform_pair,
    12    generate_transform_pair_from_matrix,
    13    generate_dstii_m_transform_pair,
    14    generate_dctii_m_transform_pair,
    15)
    16from ._tensor_ops import facewise_product, m_product, tsvdm
    17from ._tensor_pca import TPCA
    18from ._utils import display_tensor_facewise
    19
    20__all__ = [
    21    "tsvdm",
    22    "TPCA",
    23    "display_tensor_facewise",
    24    "generate_default_m_transform_pair",
    25    "generate_transform_pair_from_matrix",
    26    "generate_dctii_m_transform_pair",
    27    "generate_dstii_m_transform_pair",
    28    "facewise_product",
    29    "m_product",
    30    "datasets",
    31]
    32
    33# private - for testing
    34###############################################################################
    35from ._tensor_ops import _mode_1_unfold, _mode_2_unfold, _mode_3_unfold
    36from ._utils import _singular_vals_tensor_to_mat, _singular_vals_mat_to_tensor
    37
    38
    39# documentation configurations
    40###############################################################################
    41__docformat__ = "numpy"

def tsvdm( A, M=None, Minv=None, *, keep_hats=False, full_frontal_slices=True, svals_matrix_form=False): View Source

     90def tsvdm(
     91    A,
     92    M=None,
     93    Minv=None,
     94    *,
     95    keep_hats=False,
     96    full_frontal_slices=True,
     97    svals_matrix_form=False,
     98):
     99    """Return the t-SVDM decomposition from Kilmer et al. (2021).
    100
    101    This is a modified version, inspired by the implementation at
    102    https://github.com/UriaMorP/mprod_package
    103
    104    *NOTE: For now, unlike some other implementations (Numpy, Scipy), we
    105    will return the tensor $V$ NOT $V^T$.*
    106
    107    Parameters
    108    ----------
    109    A : ndarray of shape (n, p, t)
    110        Data tensor
    111
    112    M : Callable[[ndarray], ndarray] or None, default=None
    113        A function which expects an order-3 tensor as input, and returns the
    114        image under a m-transform. If unspecified TPCA will use the Discrete
    115        Cosine Transform (ii) from `scipy.fft`.
    116
    117    MInv : Callable[[ndarray], ndarray] or None, default=None
    118        A function implementing the inverse transform of `M`.
    119
    120    keep_hats : bool, default=False
    121        Setting to `True` will return the tSVDM factors in the m-transform
    122        space, under the specified `M`
    123
    124    full_frontal_slices : bool, default=True
    125        To reconstruct `A`, one only needs the first $k$ columns of
    126        $U$ and $V$.
    127
    128        Setting this to False will return the truncated tensors.
    129        See: https://numpy.org/doc/stable/reference/generated/numpy.linalg.svd.html
    130
    131    svals_matrix_form : bool, default=False
    132        Setting to `True` will return a compressed version of $S$, where the
    133        singular values of each f-diagonal frontal slice becomes the column of
    134        a matrix, with `t` columns total.
    135
    136    Returns
    137    -------
    138    U_tens : ndarray of shape (n, n, t)
    139        If `full_frontal_slices==False` shape is (n, k, t) instead
    140
    141    S_tens : ndarray of shape (n, p, t)
    142        If `full_frontal_slices==False` shape is (k, k, t) instead
    143        If `svals_matrix_form==True`, `S_mat` of shape (k, t) returned instead
    144
    145    V_tens : ndarray of shape (p, p, t)
    146        If `full_frontal_slices==False` shape is (p, k, t) instead
    147
    148    References
    149    ----------
    150    Kilmer, M.E., Horesh, L., Avron, H. and Newman, E., 2021. Tensor-tensor
    151    algebra for optimal representation and compression of multiway data.
    152    Proceedings of the National Academy of Sciences, 118(28), p.e2015851118.
    153    """
    154
    155    assert len(A.shape) == 3, "Ensure order-3 tensor input"
    156    assert not (
    157        callable(M) ^ callable(Minv)
    158    ), "If explicitly defined, both M and its inverse must be defined"
    159
    160    if not callable(M):  # and Minv is not defined - guaranteed by assertion
    161        M, Minv = generate_default_m_transform_pair(A.shape[-1])
    162
    163    # transform the tensor to new space via the mode-3 product
    164    hatA = M(A)
    165
    166    # an appropriate transposition allows Numpys array broadcasting to do
    167    # facewise svd's S_mat contains the singular values per matrix in the
    168    # input stack of matrices (the transpose tensor stacks top to bottom,
    169    # with t slices of size n by p)
    170    U_stack, S_mat, Vt_stack = np.linalg.svd(
    171        hatA.transpose(2, 0, 1), full_matrices=full_frontal_slices
    172    )
    173
    174    hatU = U_stack.transpose(1, 2, 0)
    175    S_mat = S_mat.transpose()
    176    # the following is a call to .transpose(1, 2, 0) followed by a facewise
    177    # transpose defined by .transpose(1, 0, 2)
    178    hatV = Vt_stack.transpose(2, 1, 0)
    179
    180    # if we are transforming scipy's singular values matrix back into tensor
    181    # form, make sure we use the correct dimensions corresponding to whether
    182    # or not the tensor faces were truncated during svd
    183    if not svals_matrix_form:
    184        if full_frontal_slices:
    185            desired_S_tens_shape = A.shape
    186        else:
    187            n, p, t = A.shape
    188            k = min(n, p)
    189            desired_S_tens_shape = (k, k, t)
    190
    191    if keep_hats:
    192        return (
    193            hatU,
    194            # by default return S as n,p,t f-diagonal tensor (or) convert into
    195            # compressed matrix of singular values of shape (k,t)
    196            S_mat
    197            if svals_matrix_form
    198            else _singular_vals_mat_to_tensor(S_mat, *desired_S_tens_shape),
    199            hatV,
    200        )
    201    else:
    202        return (
    203            Minv(hatU),
    204            # by default return S as n,p,t f-diagonal tensor (or) convert into
    205            # compressed matrix of singular values of shape (k,t)
    206            Minv(S_mat)
    207            if svals_matrix_form
    208            else _singular_vals_mat_to_tensor(Minv(S_mat), *desired_S_tens_shape),
    209            Minv(hatV),
    210        )

Return the t-SVDM decomposition from Kilmer et al. (2021).

This is a modified version, inspired by the implementation at https://github.com/UriaMorP/mprod_package

NOTE: For now, unlike some other implementations (Numpy, Scipy), we will return the tensor $V$ NOT $V^T$.

###### Parameters

- A (ndarray of shape (n, p, t)): Data tensor
- M (Callable[[ndarray], ndarray] or None, default=None): A function which expects an order-3 tensor as input, and returns the image under a m-transform. If unspecified TPCA will use the Discrete Cosine Transform (ii) from `scipy.fft`.
- MInv (Callable[[ndarray], ndarray] or None, default=None): A function implementing the inverse transform of `M`.
- keep_hats (bool, default=False): Setting to `True` will return the tSVDM factors in the m-transform space, under the specified `M`

- full_frontal_slices (bool, default=True): To reconstruct `A`, one only needs the first $k$ columns of $U$ and $V$.

  Setting this to False will return the truncated tensors. See: https://numpy.org/doc/stable/reference/generated/numpy.linalg.svd.html

- svals_matrix_form (bool, default=False): Setting to `True` will return a compressed version of $S$, where the singular values of each f-diagonal frontal slice becomes the column of a matrix, with `t` columns total.

###### Returns

- U_tens (ndarray of shape (n, n, t)): If `full_frontal_slices==False` shape is (n, k, t) instead
- S_tens (ndarray of shape (n, p, t)): If `full_frontal_slices==False` shape is (k, k, t) instead If `svals_matrix_form==True`, `S_mat` of shape (k, t) returned instead
- V_tens (ndarray of shape (p, p, t)): If `full_frontal_slices==False` shape is (p, k, t) instead

###### References

Kilmer, M.E., Horesh, L., Avron, H. and Newman, E., 2021. Tensor-tensor algebra for optimal representation and compression of multiway data. Proceedings of the National Academy of Sciences, 118(28), p.e2015851118.

class TPCA(sklearn.base.ClassNamePrefixFeaturesOutMixin, sklearn.base.TransformerMixin, sklearn.base.BaseEstimator): View Source

     25class TPCA(ClassNamePrefixFeaturesOutMixin, TransformerMixin, BaseEstimator):
     26    """Tensor analogue of PCA, introduced by Mor et al. (2022).
     27
     28    t-SVDM tensor analogue of PCA using explicit rank truncation with explicit
     29    rank truncation from Mor et al. (2022), and underlying m-product framework
     30    from Kilmer et al. (2021). Takes in an $n \\times p \\times t$ input
     31    tensor, and transforms into a $n \\times$ `n_components` matrix of 2D
     32    transformed projections.
     33
     34    The input tensor is centred into Mean Deviation Form (by location), but
     35    not normalized (by scale).
     36
     37    Parameters
     38    ----------
     39    n_components : int, float, or None, default=None
     40        Control number of components to keep. If n_components is not set at
     41        all, or set to `None`, ``n_components == min(n, p) * t``
     42
     43        If `n_components` is an integer, the TPCA will return the number of
     44        loadings, provided that for the tensor data passed into `fit`,
     45        satisfies ``1 <= n_components <= min(n, p) * t``
     46
     47        If ``0 < n_components < 1``, TPCA will select the number of
     48        compononents such that the amount of variance that needs to be
     49        explained is greater than the percentage specified.
     50
     51    copy : bool, default=True
     52        If False, data passed to fit are overwritten and running
     53        `fit(X).transform(X)` will not yield the expected results. Use
     54        `fit_transform(X)` instead.
     55
     56    M : Callable[[ndarray], ndarray] or None, default=None
     57        A function which expects an order-3 tensor as input, and returns the
     58        image under a m-transform. If unspecified TPCA will use the Discrete
     59        Cosine Transform (ii) from `scipy.fft`.
     60
     61    Minv : Callable[[ndarray], ndarray] or None, default=None
     62        A function implementing the inverse transform of `M`.
     63
     64    centre : bool, default=True
     65        If False, the data tensor will not be centralized into Mean Deviation
     66        Form. By default, the mean horizontal slice of the tensor is
     67        subtracted, so that all of the horizontal slices sum to 0, analagous
     68        to centering the data in PCA.
     69
     70    Attributes
     71    ----------
     72    n_, p_, t_, k_ : int
     73        The dimensions of the training data. ``k_ == min(n_, p_)``
     74
     75    M_, MInv_ : Callable[[ndarray], ndarray]
     76        The m-transform pair (forward and inverse) used for the underlying
     77        tensor-tensor m-product.
     78
     79    n_components_ : int
     80        The estimated number of components. If `n_components` was explicitly
     81        set by an integer value, this will be the same as that. If
     82        `n_components` was a number between 0 and 1, this number is estimated
     83        from input data. Otherwise, if not set (defaults to None), it will
     84        default to $k \\times t$ in the training data.
     85
     86    explained_variance_ratio_ : ndarray of shape (n_components_,)
     87        Percentage of total variance explained by each of the selected
     88        components. The selected components are selected so that this is
     89        returned in descending order.
     90
     91        If `n_components` is not set then all components are stored and
     92        the sum of this ratios array is equal to 1.0.
     93
     94    singular_values_ : ndarray of shape (n_components,)
     95        The singular values corresponding to each of the selected components.
     96
     97    mean_ : ndarray of shape (p_, t_)
     98        Per-feature, per-timepoint empirical mean, estimated from the
     99        training set. This is used to normalize any new data passed to
    100        `transform(X)`, unless centre is explicitly turned off via
    101        ``centre==False`` during object instantiation.
    102
    103    rho_ : ndarray of shape (t,)
    104        The rho used in multi-rank truncation to achieve the desired explicit
    105        rank of ``n_components``. See Mor et al. (2022) for detail.
    106
    107    loadings_matrix_ : ndarray of shape (n_components_, p_)
    108        The i-th row corresponds to the column of $\\hat{V}$ which contains
    109        the feature weights applied to the data (in the hat-space) to get the
    110        i-th TPCA component.
    111
    112    References
    113    ----------
    114    Mor, U., Cohen, Y., Valdés-Mas, R., Kviatcovsky, D., Elinav, E. and Avron,
    115    H., 2022. Dimensionality reduction of longitudinal’omics data using modern
    116    tensor factorizations. PLoS Computational Biology, 18(7), p.e1010212.
    117    """
    118
    119    def __init__(self, n_components=None, *, copy=True, M=None, Minv=None, centre=True):
    120        """@private
    121        Hacky way (for now) to suppress pdoc documentation being generated
    122        for the instance variables and the constructor"""
    123        # as per sklearn conventions, we perform any and all parameter
    124        # validation inside fit, and none in __init__
    125        self.n_components = n_components
    126        """@private"""
    127        self.copy = copy
    128        """@private"""
    129        self.M = M
    130        """@private"""
    131        self.Minv = Minv
    132        """@private"""
    133        self.centre = centre
    134        """@private"""
    135
    136    def fit(self, X, y=None):
    137        """Fit the model with X.
    138
    139        Parameters
    140        ----------
    141        X : ndarray of shape (n, p, t)
    142            Training data, where `n` is the number of samples, `p` is the
    143            number of features, as `t` is the number of time points.
    144
    145        y : Ignored
    146            Ignored.
    147
    148        Returns
    149        -------
    150        self : object
    151            Returns the instance itself, after being fitted.
    152        """
    153        self._fit(X)
    154        return self
    155
    156    def transform(self, X):
    157        """Apply dimensionality reduction to X.
    158
    159        See the TCAM algorithm from Mor et al. (2022)
    160
    161        Parameters
    162        ----------
    163        X : ndarray of shape (n, p, t)
    164            Training data, where `n` is the number of samples, `p` is the
    165            number of features, as `t` is the number of time points.
    166
    167        y : Ignored
    168            Ignored.
    169
    170        Returns
    171        -------
    172        X_transformed : ndarray of shape (n, n_components)
    173            TCAM projections in 2D transformed space.
    174        """
    175
    176        check_is_fitted(self)
    177        assert len(X.shape) == 3, "Ensure order-3 tensor input"
    178        assert (
    179            X.shape[1] == self.p_ and X.shape[2] == self.t_
    180        ), "Ensure the number of features, and time points, matches the model fit data"
    181
    182        if self.centre:
    183            X = X - self.mean_
    184
    185        # in the interest of efficiency, V was returned in the m-transformed
    186        # space from tsvdm saving a pair of roundabout calls to M and Minv,
    187        # and pick out the top i_q and j_q indexes, as notated in
    188        # Mor et al. (2022)
    189        return facewise_product(self.M_(X), self._hatV)[
    190            :, self._k_t_flatten_sort[0], self._k_t_flatten_sort[1]
    191        ]
    192
    193    def fit_transform(self, X, y=None):
    194        """Fit the model with X and apply the dimensionality reduction on X.
    195
    196        The output here will not be identical to calling fix(X).transform(X).
    197        But, the two give the same results up to machine precision. We
    198        provide a brief discussion of this below.
    199
    200        Parameters
    201        ----------
    202        X : ndarray of shape (n, p, t)
    203            Training data, where `n` is the number of samples, `p` is the
    204            number of features, as `t` is the number of time points.
    205
    206        y : Ignored
    207            Ignored.
    208
    209        Returns
    210        -------
    211        X_transformed : ndarray of shape (n, n_components)
    212            TCAM projections in 2D transformed space.
    213
    214        Notes
    215        -----
    216        The tensor m-product from Kilmer et al. (2021) has a notion of tensor
    217        inverse, and tensor orthogonality.
    218
    219        We benchmarked an alternative approach as taken by sklearn in their
    220        PCA class. If we right multiply A's tSVDM by $V$ we note that it
    221        cancels the $V^T$ giving us:
    222            $$
    223                Z = A *_M V = U *_M S
    224            $$
    225        It appears that computing the final term is more computationally
    226        efficient, even if we have to convert $S$ into its full (sparse)
    227        tensor representation.
    228
    229        By contrast, transform(X) will simply compute
    230            $$
    231                Z = A *_M V
    232            $$
    233
    234        In both cases, $Z$ still needs to be converted by $\\times_3 M^{-1}$
    235        and 'compressed' before being returned.
    236        For these details see Mor et al. (2022).
    237        """
    238        # note that these tensors do NOT have full face-wise matrices
    239        hatU, hatS_mat, _ = self._fit(X)
    240        hatS = _singular_vals_mat_to_tensor(hatS_mat, self.k_, self.k_, self.t_)
    241        return facewise_product(hatU, hatS)[
    242            :, self._k_t_flatten_sort[0], self._k_t_flatten_sort[1]
    243        ]
    244
    245    def _fit(self, X):
    246        """Fit the model by computing the full SVD on X. Implementation
    247        loosely modelled around `_fit_full()` method from sklearn's PCA.
    248        In the future, we could potentially explore different svd solvers
    249        which lets one directly pass truncation specifications into the
    250        low-level solver...?
    251        """
    252        assert not (
    253            callable(self.M) ^ callable(self.Minv)
    254        ), "If explicitly defined, both M and its inverse must be defined"
    255
    256        assert len(X.shape) == 3, "Ensure order-3 tensor input"
    257        n, p, t = X.shape
    258        k = min(n, p)
    259
    260        # center the data into mean deviation form, see Mor et al. (2022)
    261        # similar to sklearns PCA, we choose to implement this within the
    262        # class and just store the mean slice for subsequent transform calls
    263        self.mean_ = np.mean(X, axis=0)
    264        if self.copy:
    265            X = X.copy()
    266        if self.centre:
    267            X -= self.mean_
    268
    269        # if there is no explicitly defined transform in __init__, assign
    270        # functions to perform a default transformation
    271        if not callable(self.M):
    272            self.M_, self.Minv_ = generate_default_m_transform_pair(X.shape[2])
    273        else:
    274            self.M_, self.Minv_ = self.M, self.Minv
    275
    276        # perform tensor decomposition via Kilmer's tSVDM
    277        hatU, hatS_mat, hatV = tsvdm(
    278            X,
    279            self.M_,
    280            self.Minv_,
    281            keep_hats=True,
    282            full_frontal_slices=False,
    283            svals_matrix_form=True,
    284        )
    285
    286        # we flatten out the compressed singular value matrix in Fortran memory
    287        # style (column-wise). tensor-wise, we can interpret this as stacking
    288        # the diagonals of each tensor face in S next to each other in the
    289        # flattened array, where the singular values are grouped by face
    290        singular_values_ = hatS_mat.flatten(order="F")
    291        self._k_t_flatten_sort = singular_values_.argsort()[::-1]
    292        singular_values_ = singular_values_[self._k_t_flatten_sort]
    293
    294        # get variance explained by singular values
    295        # note that we are not yet aware of any notion of 'variance' for random
    296        # tensors so we do not have sklearn PCA's self.explained_variance_
    297        # however we may find literature for this in the future to include it
    298        squared_singular_values = singular_values_**2
    299        total_var = np.sum(squared_singular_values)
    300        explained_variance_ratio_ = squared_singular_values / total_var
    301
    302        # process n_components input
    303        if self.n_components is None:
    304            n_components = k * t
    305        elif isinstance(self.n_components, RealNotInt):
    306            if 0 < self.n_components < 1.0:
    307                # retrieve the integer number of components required to explain
    308                # this proportion of the total squared sum of singular values
    309                ratio_cumsum = np.cumsum(explained_variance_ratio_)
    310                # np.searchsorted call returns the mininum i
    311                #   s.t. n_components < ratio_cumsum[i] (see numpy docs)
    312                # which means that the (i+1)th element in ratio_cumsum[i]
    313                # strictly exceeds the user's specified variance ratio
    314                n_components = (
    315                    np.searchsorted(ratio_cumsum, self.n_components, side="right") + 1
    316                )
    317            else:
    318                raise ValueError(
    319                    "For non-integer inputs, ensure that 0 < n_components < 1"
    320                )
    321        elif isinstance(self.n_components, Integral):
    322            if 1 <= self.n_components <= k * t:
    323                n_components = self.n_components
    324            else:
    325                raise ValueError(
    326                    f"Integer inputs must satisfy 1 <= n_components <= min(n, p)*t={k*t}"
    327                )
    328        else:
    329            raise TypeError(
    330                "n_components must be an integer, float, or None"
    331                f"Got {type(self.n_components)} instead"
    332            )
    333
    334        # convert the argsort indexes back into the two dimensional indexes.
    335        # in the same tensor semantics as hatS_mat, the rows (tuple[0]) in this
    336        # multindex correspond to the p-dimension location, and the
    337        # columns (tuple[1]) in the multindex correspond to the t-dimension
    338        # location. these are now the collection of i_h's and j_h's
    339        # in Mor et al. (2022)
    340        self._k_t_flatten_sort = np.unravel_index(
    341            self._k_t_flatten_sort[:n_components], shape=hatS_mat.shape, order="F"
    342        )
    343
    344        # perform truncation. this speeds up subsequent calls to transform,
    345        # but is not required to obtain the correct results. we include it
    346        # because it also computes rho, at basically no extra cost
    347        rho = _rank_q_truncation_zero_out(
    348            hatU, hatS_mat, hatV, sigma_q=singular_values_[n_components - 1]
    349        )
    350
    351        # we store the features tensor, in the tSVDM decomposition, in the
    352        # transforemd space, saving roundabout calls to M and Minv when
    353        # performing m-product with new data
    354        self._hatV = hatV
    355
    356        # store public attributes; as per sklearn conventions, we use trailing
    357        # underscores to indicate that they have been populated following a
    358        # call to fit()
    359        self.n_, self.p_, self.t_, self.k_ = n, p, t, k
    360        self.n_components_ = n_components
    361        self.explained_variance_ratio_ = explained_variance_ratio_[:n_components]
    362        self.singular_values_ = singular_values_[:n_components]
    363        self.rho_ = rho
    364        self.loadings_matrix_ = hatV[
    365            :, self._k_t_flatten_sort[0], self._k_t_flatten_sort[1]
    366        ].T
    367
    368        return hatU, hatS_mat, hatV
    369
    370    @property
    371    def _n_features_out(self):
    372        """Number of transformed output features.
    373
    374        [CAN IGNORE - NOT MATHEMATICALLY RELEVANT]:
    375        See sklearn/decompositions/_base.py
    376        """
    377        return self.n_components_

Tensor analogue of PCA, introduced by Mor et al. (2022).

t-SVDM tensor analogue of PCA using explicit rank truncation with explicit rank truncation from Mor et al. (2022), and underlying m-product framework from Kilmer et al. (2021). Takes in an $n \times p \times t$ input tensor, and transforms into a $n \times$ `n_components` matrix of 2D transformed projections.

The input tensor is centred into Mean Deviation Form (by location), but not normalized (by scale).

###### Parameters

- n_components (int, float, or None, default=None): Control number of components to keep. If n_components is not set at all, or set to `None`, `n_components == min(n, p) * t`

  If `n_components` is an integer, the TPCA will return the number of loadings, provided that for the tensor data passed into `fit`, satisfies `1 <= n_components <= min(n, p) * t`

  If `0 < n_components < 1`, TPCA will select the number of compononents such that the amount of variance that needs to be explained is greater than the percentage specified.

- copy (bool, default=True): If False, data passed to fit are overwritten and running `fit(X).transform(X)` will not yield the expected results. Use `fit_transform(X)` instead.
- M (Callable[[ndarray], ndarray] or None, default=None): A function which expects an order-3 tensor as input, and returns the image under a m-transform. If unspecified TPCA will use the Discrete Cosine Transform (ii) from `scipy.fft`.
- Minv (Callable[[ndarray], ndarray] or None, default=None): A function implementing the inverse transform of `M`.
- centre (bool, default=True): If False, the data tensor will not be centralized into Mean Deviation Form. By default, the mean horizontal slice of the tensor is subtracted, so that all of the horizontal slices sum to 0, analagous to centering the data in PCA.

###### Attributes

- n_, p_, t_, k_ (int): The dimensions of the training data. `k_ == min(n_, p_)`
- M_, MInv_ (Callable[[ndarray], ndarray]): The m-transform pair (forward and inverse) used for the underlying tensor-tensor m-product.
- n_components_ (int): The estimated number of components. If `n_components` was explicitly set by an integer value, this will be the same as that. If `n_components` was a number between 0 and 1, this number is estimated from input data. Otherwise, if not set (defaults to None), it will default to $k \times t$ in the training data.

- explained_variance_ratio_ (ndarray of shape (n_components_,)): Percentage of total variance explained by each of the selected components. The selected components are selected so that this is returned in descending order.

  If `n_components` is not set then all components are stored and the sum of this ratios array is equal to 1.0.

- singular_values_ (ndarray of shape (n_components,)): The singular values corresponding to each of the selected components.
- mean_ (ndarray of shape (p_, t_)): Per-feature, per-timepoint empirical mean, estimated from the training set. This is used to normalize any new data passed to `transform(X)`, unless centre is explicitly turned off via `centre==False` during object instantiation.
- rho_ (ndarray of shape (t,)): The rho used in multi-rank truncation to achieve the desired explicit rank of `n_components`. See Mor et al. (2022) for detail.
- loadings_matrix_ (ndarray of shape (n_components_, p_)): The i-th row corresponds to the column of $\hat{V}$ which contains the feature weights applied to the data (in the hat-space) to get the i-th TPCA component.

###### References

Mor, U., Cohen, Y., Valdés-Mas, R., Kviatcovsky, D., Elinav, E. and Avron, H., 2022. Dimensionality reduction of longitudinal’omics data using modern tensor factorizations. PLoS Computational Biology, 18(7), p.e1010212.

def fit(self, X, y=None): View Source

    136    def fit(self, X, y=None):
    137        """Fit the model with X.
    138
    139        Parameters
    140        ----------
    141        X : ndarray of shape (n, p, t)
    142            Training data, where `n` is the number of samples, `p` is the
    143            number of features, as `t` is the number of time points.
    144
    145        y : Ignored
    146            Ignored.
    147
    148        Returns
    149        -------
    150        self : object
    151            Returns the instance itself, after being fitted.
    152        """
    153        self._fit(X)
    154        return self

Fit the model with X.

###### Parameters

- X (ndarray of shape (n, p, t)): Training data, where `n` is the number of samples, `p` is the number of features, as `t` is the number of time points.
- y (Ignored): Ignored.

###### Returns

- self (object): Returns the instance itself, after being fitted.

def transform(self, X): View Source

    156    def transform(self, X):
    157        """Apply dimensionality reduction to X.
    158
    159        See the TCAM algorithm from Mor et al. (2022)
    160
    161        Parameters
    162        ----------
    163        X : ndarray of shape (n, p, t)
    164            Training data, where `n` is the number of samples, `p` is the
    165            number of features, as `t` is the number of time points.
    166
    167        y : Ignored
    168            Ignored.
    169
    170        Returns
    171        -------
    172        X_transformed : ndarray of shape (n, n_components)
    173            TCAM projections in 2D transformed space.
    174        """
    175
    176        check_is_fitted(self)
    177        assert len(X.shape) == 3, "Ensure order-3 tensor input"
    178        assert (
    179            X.shape[1] == self.p_ and X.shape[2] == self.t_
    180        ), "Ensure the number of features, and time points, matches the model fit data"
    181
    182        if self.centre:
    183            X = X - self.mean_
    184
    185        # in the interest of efficiency, V was returned in the m-transformed
    186        # space from tsvdm saving a pair of roundabout calls to M and Minv,
    187        # and pick out the top i_q and j_q indexes, as notated in
    188        # Mor et al. (2022)
    189        return facewise_product(self.M_(X), self._hatV)[
    190            :, self._k_t_flatten_sort[0], self._k_t_flatten_sort[1]
    191        ]

Apply dimensionality reduction to X.

See the TCAM algorithm from Mor et al. (2022)

###### Parameters

- X (ndarray of shape (n, p, t)): Training data, where `n` is the number of samples, `p` is the number of features, as `t` is the number of time points.
- y (Ignored): Ignored.

###### Returns

- X_transformed (ndarray of shape (n, n_components)): TCAM projections in 2D transformed space.

def fit_transform(self, X, y=None): View Source

    193    def fit_transform(self, X, y=None):
    194        """Fit the model with X and apply the dimensionality reduction on X.
    195
    196        The output here will not be identical to calling fix(X).transform(X).
    197        But, the two give the same results up to machine precision. We
    198        provide a brief discussion of this below.
    199
    200        Parameters
    201        ----------
    202        X : ndarray of shape (n, p, t)
    203            Training data, where `n` is the number of samples, `p` is the
    204            number of features, as `t` is the number of time points.
    205
    206        y : Ignored
    207            Ignored.
    208
    209        Returns
    210        -------
    211        X_transformed : ndarray of shape (n, n_components)
    212            TCAM projections in 2D transformed space.
    213
    214        Notes
    215        -----
    216        The tensor m-product from Kilmer et al. (2021) has a notion of tensor
    217        inverse, and tensor orthogonality.
    218
    219        We benchmarked an alternative approach as taken by sklearn in their
    220        PCA class. If we right multiply A's tSVDM by $V$ we note that it
    221        cancels the $V^T$ giving us:
    222            $$
    223                Z = A *_M V = U *_M S
    224            $$
    225        It appears that computing the final term is more computationally
    226        efficient, even if we have to convert $S$ into its full (sparse)
    227        tensor representation.
    228
    229        By contrast, transform(X) will simply compute
    230            $$
    231                Z = A *_M V
    232            $$
    233
    234        In both cases, $Z$ still needs to be converted by $\\times_3 M^{-1}$
    235        and 'compressed' before being returned.
    236        For these details see Mor et al. (2022).
    237        """
    238        # note that these tensors do NOT have full face-wise matrices
    239        hatU, hatS_mat, _ = self._fit(X)
    240        hatS = _singular_vals_mat_to_tensor(hatS_mat, self.k_, self.k_, self.t_)
    241        return facewise_product(hatU, hatS)[
    242            :, self._k_t_flatten_sort[0], self._k_t_flatten_sort[1]
    243        ]

Fit the model with X and apply the dimensionality reduction on X.

The output here will not be identical to calling fix(X).transform(X). But, the two give the same results up to machine precision. We provide a brief discussion of this below.

###### Parameters

- X (ndarray of shape (n, p, t)): Training data, where `n` is the number of samples, `p` is the number of features, as `t` is the number of time points.
- y (Ignored): Ignored.

###### Returns

- X_transformed (ndarray of shape (n, n_components)): TCAM projections in 2D transformed space.

###### Notes

The tensor m-product from Kilmer et al. (2021) has a notion of tensor inverse, and tensor orthogonality.

We benchmarked an alternative approach as taken by sklearn in their PCA class. If we right multiply A's tSVDM by $V$ we note that it cancels the $V^T$ giving us: $$ Z = A *_M V = U *_M S $$ It appears that computing the final term is more computationally efficient, even if we have to convert $S$ into its full (sparse) tensor representation.

By contrast, transform(X) will simply compute $$ Z = A *_M V $$

In both cases, $Z$ still needs to be converted by $\times_3 M^{-1}$ and 'compressed' before being returned. For these details see Mor et al. (2022).

##### Inherited Members

sklearn.base.ClassNamePrefixFeaturesOutMixin
  get_feature_names_out
sklearn.utils._set_output._SetOutputMixin
  set_output
sklearn.base.BaseEstimator
  get_params
  set_params
sklearn.utils._metadata_requests._MetadataRequester
  get_metadata_routing

def display_tensor_facewise(tens): View Source

    23def display_tensor_facewise(tens):
    24    """Display an order-3 tensor represented by a Numpy ndarray nicely.
    25
    26    By default Numpy prints order-3 arrays as vertical stacks of order-2
    27    arrays in line with their broadcasting rules. This function prints a
    28    transpose view so the print output is a more intuitive sequence of frontal
    29    slices. It also prints the rounded values.
    30
    31    We often use this in notebooks.
    32
    33    UPDATE (v0.1.1): Also works for matrices and vectors now.
    34
    35    Parameters
    36    ----------
    37    tens : ndarray of shape (n, p, t)
    38        Order-3 tensor.
    39
    40    Examples
    41    --------
    42    >>> import numpy as np
    43    >>> from tred import display_tensor_facewise as disp
    44    >>> test = np.eye(3)[:, :, None] # a 3x3x1 tensor
    45    >>> print(test) # Numpy ndarrays default __str__ method is not intuitive
    46    [[[1.]
    47    [0.]
    48    [0.]]
    49    [[0.]
    50    [1.]
    51    [0.]]
    52    [[0.]
    53    [0.]
    54    [1.]]]
    55    >>> disp(test)
    56    Tensor with dimensions (3, 3, 1)
    57    [[[1. 0. 0.]
    58    [0. 1. 0.]
    59    [0. 0. 1.]]]
    60    """
    61    assert len(tens.shape) in (
    62        1,  # vector
    63        2,  # matrix
    64        3,  # tensor
    65    ), "Expecting 1D (vector), 2D (matrix) or 3D (tensor) input"
    66
    67    if len(tens.shape) == 3:
    68        print(f"Tensor with shape {tens.shape}")
    69        print(tens.transpose(2, 0, 1).round(6))
    70    else:
    71        if len(tens.shape) == 2:
    72            print(f"Matrix with shape {tens.shape}")
    73        else:
    74            print(f"Vector with length {tens.shape}")
    75        print(tens.round(6))

Display an order-3 tensor represented by a Numpy ndarray nicely.

By default Numpy prints order-3 arrays as vertical stacks of order-2 arrays in line with their broadcasting rules. This function prints a transpose view so the print output is a more intuitive sequence of frontal slices. It also prints the rounded values.

We often use this in notebooks.

UPDATE (v0.1.1): Also works for matrices and vectors now.

###### Parameters

- tens (ndarray of shape (n, p, t)): Order-3 tensor.

###### Examples

    >>> import numpy as np
    >>> from tred import display_tensor_facewise as disp
    >>> test = np.eye(3)[:, :, None] # a 3x3x1 tensor
    >>> print(test) # Numpy ndarrays default __str__ method is not intuitive
    [[[1.]
    [0.]
    [0.]]
    [[0.]
    [1.]
    [0.]]
    [[0.]
    [0.]
    [1.]]]
    >>> disp(test)
    Tensor with dimensions (3, 3, 1)
    [[[1. 0. 0.]
    [0. 1. 0.]
    [0. 0. 1.]]]

def generate_default_m_transform_pair(t): View Source

    29def generate_default_m_transform_pair(t):
    30    """Return the default `M, Minv` used by `tred` algorithms.
    31
    32    We do not guarantee that the default m-transform used in `tred` will remain
    33    consistent between versions, as this may change depending on recent
    34    research and literature. We highly encourage `tred` users to explicitly
    35    define M and Minv in the calling state to 'future-proof' your code.
    36
    37    Parameters
    38    ----------
    39    t : int
    40        The length of the tubal fibers of the target tensors, i.e. the size of
    41        its third dimension.
    42
    43    Returns
    44    -------
    45    M : Callable[[ndarray], ndarray]
    46        A function which expects an order-3 tensor as input, and returns the
    47        image under the default m-transform being used in `tred`
    48
    49    Minv : Callable[[ndarray], ndarray]
    50        A function implementing the inverse transform of `M`.
    51
    52    References
    53    ----------
    54    Kilmer, M.E., Horesh, L., Avron, H. and Newman, E., 2021. Tensor-tensor
    55    algebra for optimal representation and compression of multiway data.
    56    Proceedings of the National Academy of Sciences, 118(28), p.e2015851118.
    57
    58    Mor, U., Cohen, Y., Valdés-Mas, R., Kviatcovsky, D., Elinav, E. and Avron,
    59    H., 2022. Dimensionality reduction of longitudinal’omics data using modern
    60    tensor factorizations. PLoS Computational Biology, 18(7), p.e1010212.
    61    """
    62    return generate_dctii_m_transform_pair(t)

Return the default `M, Minv` used by `tred` algorithms.

We do not guarantee that the default m-transform used in `tred` will remain consistent between versions, as this may change depending on recent research and literature. We highly encourage `tred` users to explicitly define M and Minv in the calling state to 'future-proof' your code.

###### Parameters

- t (int): The length of the tubal fibers of the target tensors, i.e. the size of its third dimension.

###### Returns

- M (Callable[[ndarray], ndarray]): A function which expects an order-3 tensor as input, and returns the image under the default m-transform being used in `tred`
- Minv (Callable[[ndarray], ndarray]): A function implementing the inverse transform of `M`.

###### References

Kilmer, M.E., Horesh, L., Avron, H. and Newman, E., 2021. Tensor-tensor algebra for optimal representation and compression of multiway data. Proceedings of the National Academy of Sciences, 118(28), p.e2015851118.

Mor, U., Cohen, Y., Valdés-Mas, R., Kviatcovsky, D., Elinav, E. and Avron, H., 2022. Dimensionality reduction of longitudinal’omics data using modern tensor factorizations. PLoS Computational Biology, 18(7), p.e1010212.

def generate_transform_pair_from_matrix(M_mat, Minv_mat=None, *, inplace=False): View Source

     65def generate_transform_pair_from_matrix(M_mat, Minv_mat=None, *, inplace=False):
     66    """Return `M, Minv` as defined by an orthogonal matrix.
     67
     68    Allows the user to specify any orthogonal matrix, and this function will
     69    infer `t`, and numerically compute the inverse, returning functions `M`
     70    and `Minv` which can be used for `tred` algorithms.
     71
     72    Optionally, the user can also choose to specify the inverse explicitly.
     73
     74    Parameters
     75    ----------
     76    M_mat : ndarray
     77        Orthogonal square matrix.
     78
     79    Minv_mat : ndarray or None, default=None
     80        Square matrix, the inverse of M_mat. If not specified, this function
     81        will numerically evaluate the inverse of `M_mat`.
     82
     83    inplace : bool, default=False
     84        *Placeholder for future development*
     85
     86    Returns
     87    -------
     88    M : Callable[[ndarray], ndarray]
     89        A function which expects an order-3 tensor as input, and applies
     90        `M_mat` to each of the tubal fibres. This preserves the dimensions of
     91        the tensor.
     92
     93    Minv : Callable[[ndarray], ndarray]
     94        A function implementing the inverse transform of `M`.
     95
     96    References
     97    ----------
     98    Kilmer, M.E., Horesh, L., Avron, H. and Newman, E., 2021. Tensor-tensor
     99    algebra for optimal representation and compression of multiway data.
    100    Proceedings of the National Academy of Sciences, 118(28), p.e2015851118.
    101    """
    102    assert len(M_mat.shape) == 2, "Expecting matrix (order-2 array) input"
    103    assert M_mat.shape[0] == M_mat.shape[1], "Expecting square matrix input"
    104    t_ = M_mat.shape[0]
    105
    106    # numerically evaluate inverse matrix if not explicitly specified
    107    if Minv_mat is None:
    108        # be pedantic and check for invertibility
    109        # https://stackoverflow.com/questions/13249108/efficient-pythonic-check-for-singular-matrix
    110        # NOTE: revisit later, is the following more robust?:
    111        # https://stackoverflow.com/questions/17931613/how-to-decide-a-whether-a-matrix-is-singular-in-python-numpy
    112        if linalg.cond(M_mat) < 1 / sys.float_info.epsilon:
    113            Minv_mat = linalg.inv(M_mat)
    114        else:
    115            raise ValueError(
    116                "Input matrix must be invertible, but appears singular, or close to singular"
    117            )
    118    else:
    119        assert (
    120            Minv_mat.shape == M_mat.shape
    121        ), "Ensure the shapes of matrix M and its inverse are the same"
    122
    123    # a quick way of applying M along the tubal fibres of X, using numpy
    124    # broadcasting: transpose X into a (n x t x p) vertical stack of (t x p)
    125    # matrices. matrix multiplication is broadcast vertically, whereby M left
    126    # multiplies each of the t x p matrices, effectively applying the
    127    # transform to the columns, which were the original tubal fibres.
    128    # then just perform the reverse transposition to get the original
    129    # orientation of the tensor
    130    def M(X):
    131        _assert_t_and_order(X, t_)
    132        if len(X.shape) == 3:
    133            return (M_mat @ X.transpose(0, 2, 1)).transpose(0, 2, 1)
    134        elif len(X.shape) == 2:
    135            return (M_mat @ X.T).T
    136        else:  # len(X.shape == 1)
    137            return M_mat @ X
    138
    139    def Minv(X):
    140        _assert_t_and_order(X, t_)
    141        if len(X.shape) == 3:
    142            return (Minv_mat @ X.transpose(0, 2, 1)).transpose(0, 2, 1)
    143        elif len(X.shape) == 2:
    144            return (Minv_mat @ X.T).T
    145        else:  # len(X.shape == 1)
    146            return Minv_mat @ X
    147
    148    return M, Minv

Return `M, Minv` as defined by an orthogonal matrix.

Allows the user to specify any orthogonal matrix, and this function will infer `t`, and numerically compute the inverse, returning functions `M` and `Minv` which can be used for `tred` algorithms.

Optionally, the user can also choose to specify the inverse explicitly.

###### Parameters

- M_mat (ndarray): Orthogonal square matrix.
- Minv_mat (ndarray or None, default=None): Square matrix, the inverse of M_mat. If not specified, this function will numerically evaluate the inverse of `M_mat`.
- inplace (bool, default=False): Placeholder for future development

###### Returns

- M (Callable[[ndarray], ndarray]): A function which expects an order-3 tensor as input, and applies `M_mat` to each of the tubal fibres. This preserves the dimensions of the tensor.
- Minv (Callable[[ndarray], ndarray]): A function implementing the inverse transform of `M`.

###### References

Kilmer, M.E., Horesh, L., Avron, H. and Newman, E., 2021. Tensor-tensor algebra for optimal representation and compression of multiway data. Proceedings of the National Academy of Sciences, 118(28), p.e2015851118.

def generate_dctii_m_transform_pair(t, *, inplace=False, norm='ortho'): View Source

    151def generate_dctii_m_transform_pair(t, *, inplace=False, norm="ortho"):
    152    """Return `M, Minv` as defined by the Discrete Cosine Transform of
    153    length `t`.
    154
    155    Bascially a wrapper around scipy.fft to generate functions to perform
    156    fourier transform based $\\times_3 M$ operations, as used by
    157    Mor et al. (2022)
    158
    159    Parameters
    160    ----------
    161    t : int
    162        The length of the transform
    163
    164    inplace : bool, default=False
    165        Control whether or not the generated functions modify the input tensor
    166        in-place, or return a copy with the m-transform applied
    167
    168    norm : {“backward”, “ortho”, “forward”}, default="ortho"
    169        See https://docs.scipy.org/doc/scipy/reference/generated/scipy.fft.dct.html#scipy.fft.dct
    170
    171    Returns
    172    -------
    173    M : Callable[[ndarray], ndarray]
    174        A function which expects an order-3 tensor as input, and applies the
    175        DCT-II transorm to each of the tubal fibres. This preserves the
    176        dimensions of the tensor.
    177
    178    Minv : Callable[[ndarray], ndarray]
    179        A function implementing the inverse transform of `M`.
    180
    181    References
    182    ----------
    183    Mor, U., Cohen, Y., Valdés-Mas, R., Kviatcovsky, D., Elinav, E. and Avron,
    184    H., 2022. Dimensionality reduction of longitudinal’omics data using modern
    185    tensor factorizations. PLoS Computational Biology, 18(7), p.e1010212.
    186    """
    187
    188    def M(X):
    189        _assert_t_and_order(X, t)
    190        return dct(
    191            X,
    192            type=2,
    193            n=t,
    194            axis=-1,
    195            norm=norm,
    196            workers=2 * os.cpu_count(),
    197            overwrite_x=inplace,
    198        )
    199
    200    def Minv(X):
    201        _assert_t_and_order(X, t)
    202        return idct(
    203            X,
    204            type=2,
    205            n=t,
    206            axis=-1,
    207            norm=norm,
    208            workers=2 * os.cpu_count(),
    209            overwrite_x=inplace,
    210        )
    211
    212    return M, Minv

Return `M, Minv` as defined by the Discrete Cosine Transform of length `t`.

Bascially a wrapper around scipy.fft to generate functions to perform fourier transform based $\times_3 M$ operations, as used by Mor et al. (2022)

###### Parameters

- t (int): The length of the transform
- inplace (bool, default=False): Control whether or not the generated functions modify the input tensor in-place, or return a copy with the m-transform applied
- norm ({“backward”, “ortho”, “forward”}, default="ortho"): See https://docs.scipy.org/doc/scipy/reference/generated/scipy.fft.dct.html#scipy.fft.dct

###### Returns

- M (Callable[[ndarray], ndarray]): A function which expects an order-3 tensor as input, and applies the DCT-II transorm to each of the tubal fibres. This preserves the dimensions of the tensor.
- Minv (Callable[[ndarray], ndarray]): A function implementing the inverse transform of `M`.

###### References

Mor, U., Cohen, Y., Valdés-Mas, R., Kviatcovsky, D., Elinav, E. and Avron, H., 2022. Dimensionality reduction of longitudinal’omics data using modern tensor factorizations. PLoS Computational Biology, 18(7), p.e1010212.

def generate_dstii_m_transform_pair(t, *, inplace=False, norm='ortho'): View Source

    215def generate_dstii_m_transform_pair(t, *, inplace=False, norm="ortho"):
    216    """Return `M, Minv` as defined by the Discrete Sine Transform of
    217    length `t`.
    218
    219    Bascially a wrapper around scipy.fft to generate functions to perform
    220    fourier transform based $\\times_3 M$ operations.
    221
    222    Parameters
    223    ----------
    224    t : int
    225        The length of the transform
    226
    227    inplace : bool, default=False
    228        Control whether or not the generated functions modify the input tensor
    229        in-place, or return a copy with the m-transform applied
    230
    231    norm : {“backward”, “ortho”, “forward”}, default="ortho"
    232        See https://docs.scipy.org/doc/scipy/reference/generated/scipy.fft.dst.html#scipy.fft.dst
    233
    234    Returns
    235    -------
    236    M : Callable[[ndarray], ndarray]
    237        A function which expects an order-3 tensor as input, and applies the
    238        DST-II transorm to each of the tubal fibres. This preserves the
    239        dimensions of the tensor.
    240
    241    Minv : Callable[[ndarray], ndarray]
    242        A function implementing the inverse transform of `M`.
    243    """
    244
    245    def M(X):
    246        _assert_t_and_order(X, t)
    247        return dst(
    248            X,
    249            type=2,
    250            n=t,
    251            axis=-1,
    252            norm=norm,
    253            workers=2 * os.cpu_count(),
    254            overwrite_x=inplace,
    255        )
    256
    257    def Minv(X):
    258        _assert_t_and_order(X, t)
    259        return idst(
    260            X,
    261            type=2,
    262            n=t,
    263            axis=-1,
    264            norm=norm,
    265            workers=2 * os.cpu_count(),
    266            overwrite_x=inplace,
    267        )
    268
    269    return M, Minv

Return `M, Minv` as defined by the Discrete Sine Transform of length `t`.

Bascially a wrapper around scipy.fft to generate functions to perform fourier transform based $\times_3 M$ operations.

###### Parameters

- t (int): The length of the transform
- inplace (bool, default=False): Control whether or not the generated functions modify the input tensor in-place, or return a copy with the m-transform applied
- norm ({“backward”, “ortho”, “forward”}, default="ortho"): See https://docs.scipy.org/doc/scipy/reference/generated/scipy.fft.dst.html#scipy.fft.dst

###### Returns

- M (Callable[[ndarray], ndarray]): A function which expects an order-3 tensor as input, and applies the DST-II transorm to each of the tubal fibres. This preserves the dimensions of the tensor.
- Minv (Callable[[ndarray], ndarray]): A function implementing the inverse transform of `M`.

def facewise_product(*tensors): View Source

    19def facewise_product(*tensors):
    20    """Compute cumulative facewise product.
    21
    22    Definition:
    23    $$
    24        C_{:,:,i} = A_{:,:,i} B_{:,:,i}
    25    $$
    26
    27    Parameters
    28    ----------
    29    *tensors : ndarray
    30        Variable number of tensors, such that all adjacent input tensors have
    31        shape (a, b, d) and shape (b, c, d) respectively
    32
    33    Returns
    34    -------
    35    C : ndarray of shape (a, c, d)
    36        Facewise tensor product
    37    """
    38    # apply the lambda function cumulatively over the tensor inputs
    39    return reduce(_BINARY_FACEWISE, tensors)

Compute cumulative facewise product.

Definition: $$ C_{:,:,i} = A_{:,:,i} B_{:,:,i} $$

###### Parameters

- *tensors (ndarray): Variable number of tensors, such that all adjacent input tensors have shape (a, b, d) and shape (b, c, d) respectively

###### Returns

- C (ndarray of shape (a, c, d)): Facewise tensor product

def m_product(*tensors, **transforms): View Source

    42def m_product(*tensors, **transforms):
    43    """Kilmer et al. (2021) tensor m-product for order-3 tensors.
    44
    45    See paper for definition.
    46
    47    Parameters
    48    ----------
    49    *tensors : ndarray
    50        Variable number of tensors, such that all adjacent input tensors have
    51        shape (a, b, d) and shape (b, c, d) respectively
    52
    53    M : Callable[[ndarray], ndarray] or None, default=None
    54        A function which, given some order-3 tensor, returns it under an
    55        orthogonal tubal transformation
    56
    57    MInv : Callable[[ndarray], ndarray] or None, default=None
    58        A function implementing the inverse tubal transformation of M
    59
    60    Returns
    61    -------
    62    m_product : ndarray of shape (a, c, d)
    63        Tensor-tensor m-product as found in Kilmer et al. (2021)
    64
    65    References
    66    ----------
    67    Kilmer, M.E., Horesh, L., Avron, H. and Newman, E., 2021. Tensor-tensor
    68    algebra for optimal representation and compression of multiway data.
    69    Proceedings of the National Academy of Sciences, 118(28), p.e2015851118.
    70    """
    71    # process the m-transform keyword inputs, applying defaults if needed
    72    default_transforms = {"M": None, "Minv": None}
    73    transforms = {**default_transforms, **transforms}
    74    assert not (
    75        callable(transforms["M"]) ^ callable(transforms["Minv"])
    76    ), "If explicitly defined, both M and its inverse must be defined"
    77
    78    if not callable(transforms["M"]):
    79        M, Minv = generate_default_m_transform_pair(tensors[0].shape[-1])
    80    else:
    81        M, Minv = transforms["M"], transforms["Minv"]
    82
    83    # use a generator representing all the tensors under the m-transform
    84    # ('hat space'), allowing it to be lazily reduced over using the binary
    85    # facewise product anonymous function. prevents storing all of the
    86    # transformed tensors in memory at the same time
    87    return Minv(reduce(_BINARY_FACEWISE, (M(tens) for tens in tensors)))

Kilmer et al. (2021) tensor m-product for order-3 tensors.

See paper for definition.

###### Parameters

- *tensors (ndarray): Variable number of tensors, such that all adjacent input tensors have shape (a, b, d) and shape (b, c, d) respectively
- M (Callable[[ndarray], ndarray] or None, default=None): A function which, given some order-3 tensor, returns it under an orthogonal tubal transformation
- MInv (Callable[[ndarray], ndarray] or None, default=None): A function implementing the inverse tubal transformation of M

###### Returns

- m_product (ndarray of shape (a, c, d)): Tensor-tensor m-product as found in Kilmer et al. (2021)

###### References

Kilmer, M.E., Horesh, L., Avron, H. and Newman, E., 2021. Tensor-tensor algebra for optimal representation and compression of multiway data. Proceedings of the National Academy of Sciences, 118(28), p.e2015851118.