Source code for toqito.matrix_props.commutant
"""Module for computing the commutant of a set of matrices."""
import numpy as np
from scipy.linalg import null_space
[docs]
def commutant(A: np.ndarray | list[np.ndarray]) -> list[np.ndarray]:
r"""Compute an orthonormal basis for the commutant algebra [@PlanetMathCommutant].
Given a matrix \(A\) or a set of matrices \(\mathcal{A} = \{A_1, A_2, \dots\}\),
this function determines an orthonormal basis (with respect to the Hilbert-Schmidt inner product)
for the algebra of matrices that commute with every matrix in \(\mathcal{A}\).
The commutant of a single matrix \(A \in \mathbb{C}^{n \times n}\) consists of all matrices
\(X \in \mathbb{C}^{n \times n}\) satisfying:
\[
A X = X A.
\]
More generally, for a set of matrices \(\mathcal{A} = \{A_1, A_2, \dots\}\), the commutant
consists of all matrices \(X\) satisfying:
\[
A_i X = X A_i \quad \forall A_i \in \mathcal{A}.
\]
This condition can be rewritten in vectorized form as:
\[
(A_i \otimes I - I \otimes A_i^T) \text{vec}(X) = 0, \quad \forall A_i \in \mathcal{A}.
\]
where \(\text{vec}(X)\) denotes the column-wise vectorization of \(X\).
The null space of this equation provides a basis for the commutant.
This implementation is based on [@QETLAB_link].
Examples:
Consider the following set of matrices:
\[
A_1 = \begin{pmatrix}
1 & 0 \\
0 & -1
\end{pmatrix}, \quad
A_2 = \begin{pmatrix}
0 & 1 \\
1 & 0
\end{pmatrix}
\]
The commutant consists of matrices that commute with both \(A_1\) and \(A_2\).
```python exec="1" source="above"
import numpy as np
from toqito.matrix_props import commutant
A1 = np.array([[1, 0], [0, -1]])
A2 = np.array([[0, 1], [1, 0]])
basis = commutant([A1, A2])
print(basis)
```
Now, consider a single matrix:
\[
A = \begin{pmatrix}
1 & 1 \\
0 & 1
\end{pmatrix}
\]
```python exec="1" source="above"
import numpy as np
from toqito.matrix_props import commutant
A = np.array([[1, 1], [0, 1]])
basis = commutant(A)
for i, basis_ in enumerate(basis):
print(f"basis{ i} :\n{basis_} \n")
```
Args:
A: A single matrix of the form np.ndarray or a list of square matrices of the same dimension.
Returns:
A list of matrices forming an orthonormal basis for the commutant.
"""
# Handle list of matrices.
if isinstance(A, list):
# Convert to 3D array.
A = np.stack(A, axis=0)
else:
# Ensure it's a 3D array.
A = np.expand_dims(A, axis=0)
# Extract number of operators and dimension.
num_ops, dim, _ = A.shape
# Construct the commutant condition (A ⊗ I - I ⊗ A^T) vec(X) = 0.
comm_matrices = [np.kron(A[i], np.eye(dim)) - np.kron(np.eye(dim), A[i].T) for i in range(num_ops)]
# Stack into a 2D matrix for null_space computation.
comm_matrix = np.vstack(comm_matrices) if len(comm_matrices) > 1 else comm_matrices[0]
# Compute null space.
null_basis = null_space(comm_matrix) # Basis vectors for commuting matrices
# Reshape each basis vector into a matrix of size (dim x dim).
return [null_basis[:, i].reshape((dim, dim)) for i in range(null_basis.shape[1])]