Source code for toqito.matrix_props.is_block_positive

"""Checks if the matrix is block positive."""

import numpy as np

from toqito.matrix_props.is_hermitian import is_hermitian
from toqito.matrix_props.is_positive_semidefinite import is_positive_semidefinite
from toqito.matrix_props.sk_norm import sk_operator_norm


[docs] def is_block_positive( mat: np.ndarray, k: int = 1, dim: int | list[int] | None = None, effort: int = 2, rtol: float = 1e-5, ) -> bool | RuntimeError: r"""Check if matrix is block positive [@Johnston_2012_Norms]. Examples: The swap operator is always block positive, since it is the Choi matrix of the transpose map. ```python exec="1" source="above" from toqito.perms.swap_operator import swap_operator from toqito.matrix_props.is_block_positive import is_block_positive mat = swap_operator(3) print(is_block_positive(mat=mat)) ``` However, it's not 2 - block positive. ```python exec="1" source="above" from toqito.perms.swap_operator import swap_operator from toqito.matrix_props.is_block_positive import is_block_positive mat = swap_operator(3) print(is_block_positive(mat=mat, k=2)) ``` Raises: RuntimeError: Unable to determine k-block positivity. Please consider increasing the relative tolerance or the effort level. Args: mat: A bipartite Hermitian operator. k: A positive integer indicating that the function should determine whether or not the input operator is k-block positive, i.e., whether or not it remains nonnegative under left and right multiplication by vectors with Schmidt rank <= k (default 1). dim: The dimension of the two sub-systems. By default it's assumed to be equal. effort: An integer value indicating the amount of computation you want to devote to determine block positivity before giving up. rtol: The relative tolerance parameter (default 1e-05). Returns: Return `True` if matrix is k-block positive definite, `False` if not, or raise a runtime error if we are unable to determine whether or not the operator is block positive. """ if not is_hermitian(mat): return False dim_xy = mat.shape[0] # Set default dimension if none was provided. if dim is None: dim_val = int(np.round(np.sqrt(dim_xy))) elif isinstance(dim, int): dim_val = dim else: dim_val = None # Allow the user to enter in a single integer for dimension. if dim_val is not None: dim_arr = np.array([dim_val, dim_xy / dim_val]) dim_arr[1] = int(np.round(dim_arr[1])) else: dim_arr = np.array(dim) dim_arr = np.array(dim_arr, dtype=int) # When a local dimension is small, block positivity is trivial. if min(dim_arr) <= k: return is_positive_semidefinite(mat) op_norm = np.linalg.norm(mat, ord=2) # We compute the S(k)-norm of this operator since # X k-block positive iff: # c >= S(k)-norm of(c*I - X) # See Corollary 4.2.9. of `[@Johnston_2012_Norms]. c_mat = op_norm * np.eye(dim_xy) - mat lower_bound, upper_bound = sk_operator_norm(c_mat, k, dim_arr, op_norm, effort) # block positive # Note that QETLAB is more conservative here and multiplies # by (1 - rtol). After some experiments though, I found out # that probably due to numerical inaccuracies of CVXPY the check # upper_bound <= op_norm * (1 - rtol) # would fail even for k - block positive matrices. So, we choose to # relax this inequality by increasing RHS. Additionally, the check # upper_bound <= op_norm * (1 - rtol) # has the "undesired" property that increasing tolerance makes the # inequality more difficult to satisfy but usually the reverse holds, # i.e increased tolerance parameter relaxes the problem. if upper_bound <= op_norm * (1 + rtol): return True # not block positive if lower_bound >= op_norm * (1 - rtol): return False return RuntimeError( "Unable to determine k-block positivity. Please consider increasing the relative tolerance or the effort level." )