Source code for toqito.matrix_props.positive_semidefinite_rank
"""Calculates the positive semidefinite rank of a nonnegative matrix."""
import cvxpy as cp
import numpy as np
from toqito.matrix_props import is_nonnegative, is_square
[docs]
def positive_semidefinite_rank(mat: np.ndarray, max_rank: int = 10) -> int | None:
r"""Compute the positive semidefinite rank (PSD rank) of a nonnegative matrix.
The definition of PSD rank is defined in [@Fawzi_2015_Positive].
Finds the PSD rank of an input matrix by checking feasibility for increasing rank.
Examples:
As an example (Equation 21 from [@Heinosaari_2024_Can]), the PSD rank of the following matrix
\[
A = \frac{1}{2}
\begin{pmatrix}
0 & 1 & 1 \\
1 & 0 & 1 \\
1 & 1 & 0
\end{pmatrix}
\]
is known to be \(\text{rank}_{\text{PSD}}(A) = 2\).
```python exec="1" source="above"
import numpy as np
from toqito.matrix_props import positive_semidefinite_rank
print(positive_semidefinite_rank(1/2 * np.array([[0, 1, 1], [1,0,1], [1,1,0]])))
```
The PSD rank of the identity matrix is the dimension of the matrix [@Fawzi_2015_Positive].
```python exec="1" source="above"
import numpy as np
from toqito.matrix_props import positive_semidefinite_rank
print(positive_semidefinite_rank(np.identity(3)))
```
"""
if not is_nonnegative(mat):
raise ValueError("Matrix must be nonnegative.")
if not is_square(mat):
raise ValueError("Matrix must be square.")
for k in range(1, max_rank + 1):
if _check_psd_rank(mat, k):
return k
return None
def _check_psd_rank(mat: np.ndarray, max_rank: int) -> bool:
"""Check if the given PSD rank k is feasible for matrix M.
Args:
mat: 2D numpy ndarray.
max_rank: The maximum rank to check.
mat: 2D numpy ndarray
max_rank: The maximum rank to check.
Returns:
True if `max_rank` is a feasible PSD rank, False otherwise.
"""
m, n = mat.shape
# Define variables:
x_var = cp.Variable((m, n))
# Define constraints:
constraints = []
for i in range(m):
for j in range(n):
constraints.append(cp.bmat([[x_var[i, j], mat[i, j]], [mat[i, j], x_var[j, i]]]) >> 0)
constraints.append(cp.norm(x_var, "nuc") <= max_rank)
# Define objective.
obj = cp.sum(cp.square(x_var - mat))
# Solve problem.
prob = cp.Problem(cp.Minimize(obj), constraints)
# Use CVXOPT solver (project default) which handles nuclear norm and avoids SCS compatibility issues
prob.solve(solver=cp.CVXOPT)
# Check if the problem is feasible and the objective is close to zero.
return prob.status == cp.OPTIMAL and prob.value < 1e-6