Source code for toqito.state_props.is_unextendible_product_basis

"""Check if a set of states form an unextendible product basis."""

from itertools import permutations

import numpy as np
from more_itertools import set_partitions
from scipy.linalg import null_space

from toqito.matrix_ops import tensor
from toqito.state_props import is_product


[docs] def is_unextendible_product_basis(vecs: list[np.ndarray], dims: list[int]) -> tuple[bool, np.ndarray | None]: r"""Check if a set of vectors form an unextendible product basis (UPB) [@Bennett_1999_UPB]. Consider a multipartite quantum system \(\mathcal{H} = \bigotimes_{i=1}^{m} \mathcal{H}_{i}\) with \(m\) parties with respective dimensions \(d_i, i = 1, 2, ..., m\). An (incomplete orthogonal) product basis (PB) is a set \(S\) of pure orthogonal product states spanning a proper subspace \(\mathcal{H}_S\) of \(\mathcal{H}\). An unextendible product basis (UPB) is a PB whose complementary subspace \(\mathcal{H}_S-\mathcal{H}\) contains no product state. This function is inspired from `IsUPB` in [@QETLAB_link]. Examples: See [tile()][toqito.states.tile.tile]. All the states together form a UPB: ```python exec="1" source="above" import numpy as np from toqito.states import tile from toqito.state_props import is_unextendible_product_basis upb_tiles = np.array([tile(i) for i in range(5)]) dims = np.array([3, 3]) print(is_unextendible_product_basis(upb_tiles, dims)) ``` However, the first 4 do not: ```python exec="1" source="above" import numpy as np from toqito.states import tile from toqito.state_props import is_unextendible_product_basis non_upb_tiles = np.array([tile(i) for i in range(4)]) dims = np.array([3, 3]) print(is_unextendible_product_basis(non_upb_tiles, dims)) ``` The orthogonal state is given by \[ \frac{1}{\sqrt{2}} |2\rangle \left( |1\rangle + |2\rangle \right) \] Raises: ValueError: If product of dimensions does not match the size of a vector. ValueError: If at least one vector is not a product state. Args: vecs: The list of states. dims: The list of dimensions. Returns: Returns a tuple. The first element is `True` if input is a UPB and `False` otherwise. The second element is a witness (a product state orthogonal to all the input vectors) if the input is a PB and `None` otherwise. """ vecs = np.array(vecs) dims = np.array(dims) if np.prod(dims) != vecs.shape[1]: raise ValueError("Product of dimensions does not equal the size of each vector") if not all(is_product(vec, dims)[0] for vec in vecs): raise ValueError("At least one vector is not a product state") # Number of parties (m). num_parties = dims.shape[0] # Number of vectors (n). num_vecs = vecs.shape[0] # If n < m vectors are provided, then we cannot generate set partitions, so it is not a UPB. We will extend the set # with m-n null vectors and run the same algorithm. if (num_vecs := vecs.shape[0]) < num_parties: vecs = np.append(vecs, np.zeros(shape=(num_parties - num_vecs, *vecs.shape[1:])), axis=0) num_vecs = vecs.shape[0] # Split products. vecs_split = np.array([is_product(vec, dims)[1] for vec in vecs]) # Acquire generator to m-partitions of [0, n-1]. parts_unordered = set_partitions(list(range(num_vecs)), num_parties) for part_unordered in parts_unordered: for part_ordered in permutations(part_unordered): # Witness vectors. wit = [] witness_found = True for i in range(num_parties): # For the i-th party, acquire the matrix. mat = np.stack([vecs_split[col, i, :] for col in part_ordered[i]]) # Find the basis of the null space. null_basis = null_space(mat) # If null space is empty then break. if null_basis.shape[1] == 0: witness_found = False break # If null space is non-empty, add a basis vector of null space to witness. wit.append(null_basis[:, 0]) # If witness was found, then it is not a UPB, return tensor product of witness vectors. if witness_found: # If wit is empty, tensor returns None. return False, tensor(wit) # If no witness was found, it is a UPB. return True, None