Source code for geomstats.geometry.complex_matrices

```"""Module exposing the `ComplexMatrices` class.

"""

import geomstats.backend as gs
import geomstats.errors
from geomstats.geometry.base import ComplexVectorSpace
from geomstats.geometry.hermitian import HermitianMetric
from geomstats.geometry.matrices import Matrices
from geomstats.vectorization import repeat_out

[docs]
class ComplexMatrices(ComplexVectorSpace):
"""Class for the space of complex matrices (m, n).

Parameters
----------
m, n : int
Integers representing the shapes of the matrices: m x n.
"""

def __init__(self, m, n, equip=True):
geomstats.errors.check_integer(n, "n")
geomstats.errors.check_integer(m, "m")
super().__init__(dim=m * n * 2, shape=(m, n), equip=equip)
self.m = m
self.n = n

[docs]
@staticmethod
def default_metric():
"""Metric to equip the space with if equip is True."""
return ComplexMatricesMetric

def _create_basis(self):
"""Create the canonical basis."""
cdtype = gs.get_default_cdtype()
m, n = self.m, self.n
real_part = gs.reshape(gs.eye(n * m), (n * m, m, n))
complex_part = gs.reshape(gs.eye(n * m, dtype=cdtype), (n * m, m, n))
return gs.vstack([real_part, complex_part])

[docs]
def belongs(self, point, atol=gs.atol):
"""Check if point belongs to the Matrices space.

Parameters
----------
point : array-like, shape=[..., m, n]
Point to be checked.
atol : float
Unused here.

Returns
-------
belongs : array-like, shape=[...,]
Boolean evaluating if point belongs to the Matrices space.
"""
is_matrix = super().belongs(point, atol=atol)
belongs = gs.logical_and(is_matrix, gs.is_complex(point))
return belongs

[docs]
@staticmethod
def transconjugate(mat):
"""Return the transconjugate of matrices.

Parameters
----------
mat : array-like, shape=[..., n, n]
Matrix.

Returns
-------
transconjugate : array-like, shape=[..., n, n]
Transconjugated matrix.
"""
ndim = gs.ndim(mat)
axes = list(range(0, ndim))
axes[-1] = ndim - 2
axes[-2] = ndim - 1
return gs.transpose(gs.conj(mat), axes)

[docs]
@classmethod
def is_hermitian(cls, mat, atol=gs.atol):
"""Check if a square matrix is Hermitian.

Parameters
----------
mat : array-like, shape=[..., n, n]
Matrix.
atol : float
Absolute tolerance.
Optional, default: backend atol.

Returns
-------
is_herm : array-like, shape=[...,]
Boolean evaluating if the matrix is symmetric.
"""
return Matrices.equal(mat, cls.transconjugate(mat), atol)

[docs]
@classmethod
def is_hpd(cls, mat, atol=gs.atol):
"""Check if a square matrix is Hermitian positive definite.

Parameters
----------
mat : array-like, shape=[..., n, n]
Matrix.
atol : float
Absolute tolerance.
Optional, default: backend atol.

Returns
-------
is_hpd : array-like, shape=[...,]
Boolean evaluating if the matrix is Hermitian positive definite.
"""
return gs.logical_and(cls.is_hermitian(mat, atol), Matrices.is_pd(mat))

[docs]
@classmethod
def is_skew_hermitian(cls, mat, atol=gs.atol):
"""Check if a matrix is skew-Hermitian.

Parameters
----------
mat : array-like, shape=[..., n, n]
Matrix.
atol : float
Absolute tolerance.
Optional, default: backend atol.

Returns
-------
is_skew_herm : array-like, shape=[...,]
Boolean evaluating if the matrix is skew-Hermitian.
"""
is_square = Matrices.is_square(mat)
if not is_square:
is_vectorized = gs.ndim(gs.array(mat)) == 3
return gs.array([False] * len(mat)) if is_vectorized else False
return Matrices.equal(mat, -cls.transconjugate(mat), atol)

[docs]
@classmethod
def to_hermitian(cls, mat):
"""Make a matrix Hermitian.

Make a matrix Hermitian by averaging it
with its transconjugate.

Parameters
----------
mat : array-like, shape=[..., n, n]
Matrix.

Returns
-------
herm : array-like, shape=[..., n, n]
Hermitian matrix.
"""
return 1 / 2 * (mat + cls.transconjugate(mat))

[docs]
@classmethod
def to_skew_hermitian(cls, mat):
"""Make a matrix skew-Hermitian.

Make matrix skew-Hermitian by averaging it
with minus its transconjugate.

Parameters
----------
mat : array-like, shape=[..., n, n]
Matrix.

Returns
-------
skew_sym : array-like, shape=[..., n, n]
Skew-Hermitian matrix.
"""
return 1 / 2 * (mat - cls.transconjugate(mat))

[docs]
def random_point(self, n_samples=1, bound=1.0):
"""Sample from a uniform distribution in a complex cube.

Parameters
----------
n_samples : int
Number of samples.
Optional, default: 1.
bound : float
Bound of the interval in which to sample each entry.
Optional, default: 1.

Returns
-------
point : array-like, shape=[..., m, n]
Sample.
"""
cdtype = gs.get_default_cdtype()

size = (n_samples,) + self.shape if n_samples != 1 else self.shape
point = gs.cast(bound * (gs.random.rand(*size) - 0.5), dtype=cdtype)
point += 1j * gs.cast(bound * (gs.random.rand(*size) - 0.5), dtype=cdtype)
return point

[docs]
@classmethod
def congruent(cls, mat_1, mat_2):
r"""Compute the congruent action of mat_2 on mat_1.

This is :math:`mat\_2 \ mat\_1 \ mat\_2^T`.

Parameters
----------
mat_1 : array-like, shape=[..., n, n]
Matrix.
mat_2 : array-like, shape=[..., n, n]
Matrix.

Returns
-------
cong : array-like, shape=[..., n, n]
Result of the congruent action.
"""
return Matrices.mul(mat_2, mat_1, cls.transconjugate(mat_2))

[docs]
@staticmethod
def frobenius_product(mat_1, mat_2):
"""Compute Frobenius inner-product of two matrices.

The `einsum` function is used to avoid computing a matrix product. It
is also faster than using a sum an element-wise product.

Parameters
----------
mat_1 : array-like, shape=[..., m, n]
Matrix.
mat_2 : array-like, shape=[..., m, n]
Matrix.

Returns
-------
product : array-like, shape=[...,]
Frobenius inner-product of mat_1 and mat_2
"""
return gs.einsum("...ij,...ij->...", gs.conj(mat_1), mat_2)

[docs]
class ComplexMatricesMetric(HermitianMetric):
"""Hermitian metric on complex matrices given by Frobenius inner-product."""

[docs]
@staticmethod
def inner_product(tangent_vec_a, tangent_vec_b, base_point=None):
"""Compute Frobenius inner-product of two tangent vectors.

Parameters
----------
tangent_vec_a : array-like, shape=[..., m, n]
Tangent vector.
tangent_vec_b : array-like, shape=[..., m, n]
Tangent vector.
base_point : array-like, shape=[..., m, n]
Base point.
Optional, default: None.

Returns
-------
inner_prod : array-like, shape=[...,]
Frobenius inner-product of tangent_vec_a and tangent_vec_b.
"""
return ComplexMatrices.frobenius_product(tangent_vec_a, tangent_vec_b)

[docs]
def squared_norm(self, vector, base_point=None):
"""Compute the square of the norm of a vector.

Squared norm of a vector associated to the inner product
at the tangent space at a base point.

Parameters
----------
vector : array-like, shape=[..., dim]
Vector.
base_point : array-like, shape=[..., dim]
Base point.
Optional, default: None.

Returns
-------
sq_norm : array-like, shape=[...,]
Squared norm.
"""
sq_norm = self.inner_product(vector, vector, base_point)
sq_norm = gs.real(sq_norm)
return sq_norm

[docs]
def norm(self, vector, base_point=None):
"""Compute norm of a complex matrix.

Norm of a matrix associated to the Frobenius inner product.

Parameters
----------
vector : array-like, shape=[..., dim]
Vector.
base_point : array-like, shape=[..., dim]
Base point.
Optional, default: None.

Returns
-------
norm : array-like, shape=[...,]
Norm.
"""
norm = gs.linalg.norm(vector, axis=(-2, -1))
return repeat_out(self._space.point_ndim, norm, vector, base_point)

```