Skip to content

Commit

Permalink
[pre-commit.ci] auto fixes from pre-commit.com hooks
Browse files Browse the repository at this point in the history
for more information, see https://pre-commit.ci
  • Loading branch information
pre-commit-ci[bot] committed Mar 2, 2024
1 parent 0dc4961 commit 93c7ca3
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 82 deletions.
150 changes: 76 additions & 74 deletions src/qibo/quantum_info/quantum_networks.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Module defining the `QuantumNetwork` class and adjacent functions."""

from functools import reduce
from logging import warning
from operator import mul
from typing import List, Optional, Tuple, Union

Expand All @@ -9,7 +10,6 @@
from qibo.backends import _check_backend
from qibo.config import raise_error

from logging import warning

class QuantumNetwork:
"""This class stores the Choi operator of the quantum network as a tensor,
Expand Down Expand Up @@ -60,38 +60,35 @@ def __init__(

self._set_parameters()

self.dims = reduce(mul, self.partition) # should be after `_set_parameters` to ensure `self.partition` is not `None`
self.dims = reduce(
mul, self.partition
) # should be after `_set_parameters` to ensure `self.partition` is not `None`

@staticmethod
def _order_tensor2operator(n:int, system_input: Union[List[bool], Tuple[bool]]):
order = list(range(0, n*2, 2)) + list(range(1, n*2, 2))
def _order_tensor2operator(n: int, system_input: Union[List[bool], Tuple[bool]]):
order = list(range(0, n * 2, 2)) + list(range(1, n * 2, 2))
for i, is_input in enumerate(system_input):
if is_input:
order[i] = order[i] + 1
order[i+n] = order[i+n] - 1
order[i + n] = order[i + n] - 1
return order

@staticmethod
def _order_operator2tensor(n:int, system_input: Union[List[bool], Tuple[bool]]):
order = list(sum(
zip(
list(range(0, n)), list(range(n, n*2))
),()))
def _order_operator2tensor(n: int, system_input: Union[List[bool], Tuple[bool]]):
order = list(sum(zip(list(range(0, n)), list(range(n, n * 2))), ()))
for i, is_input in enumerate(system_input):
if is_input:
temp = order[i*2]
order[i*2] = order[i*2+1]
order[i*2+1] = temp
temp = order[i * 2]
order[i * 2] = order[i * 2 + 1]
order[i * 2 + 1] = temp
return order

@classmethod
def _operator2tensor(cls,operator,partition:List[int], system_input:List[bool]):
def _operator2tensor(cls, operator, partition: List[int], system_input: List[bool]):
n = len(partition)
order = cls._order_operator2tensor(n, system_input)
try:
return operator.reshape(
list(partition) * 2
).transpose(order)
return operator.reshape(list(partition) * 2).transpose(order)
except:
raise_error(
ValueError,
Expand All @@ -100,13 +97,14 @@ def _operator2tensor(cls,operator,partition:List[int], system_input:List[bool]):
)

@classmethod
def from_nparray(cls,
arr:np.ndarray,
def from_nparray(
cls,
arr: np.ndarray,
partition: Optional[Union[List[int], Tuple[int]]] = None,
system_input: Optional[Union[List[bool], Tuple[bool]]] = None,
pure:bool=False,
pure: bool = False,
backend=None,
):
):

if pure:
if partition is None:
Expand All @@ -124,24 +122,28 @@ def from_nparray(cls,
else:
# check if arr is a valid choi operator
len_sys = len(arr.shape)
if (len_sys % 2 != 0) or (arr.shape[:len_sys//2] != arr.shape[len_sys//2:]):
if (len_sys % 2 != 0) or (
arr.shape[: len_sys // 2] != arr.shape[len_sys // 2 :]
):
raise_error(
ValueError,
'The opertor must be a square operator where the first half of the shape is the same as the second half of the shape. '+
f'However, the shape of the input is {arr.shape}. '+
'If the input is pure, set `pure=True`.'
"The opertor must be a square operator where the first half of the shape is the same as the second half of the shape. "
+ f"However, the shape of the input is {arr.shape}. "
+ "If the input is pure, set `pure=True`.",
)

if partition is None:
partition = arr.shape[:len_sys//2]
partition = arr.shape[: len_sys // 2]

tensor = cls._operator2tensor(arr, partition, system_input)

return cls(tensor,
partition=partition,
system_input=system_input,
pure=pure,
backend=backend)

return cls(
tensor,
partition=partition,
system_input=system_input,
pure=pure,
backend=backend,
)

def operator(self, backend=None, full=False):
"""Returns the Choi operator of the quantum network in matrix form.
Expand Down Expand Up @@ -169,9 +171,7 @@ def operator(self, backend=None, full=False):
n = len(self.partition)
order = self._order_tensor2operator(n, self.system_input)

operator = tensor.reshape(
np.repeat(self.partition, 2)
).transpose(order)
operator = tensor.reshape(np.repeat(self.partition, 2)).transpose(order)

return backend.cast(operator, dtype=self._tensor.dtype)

Expand Down Expand Up @@ -203,7 +203,7 @@ def is_hermitian(
Returns:
bool: Hermiticity condition.
"""
if self.is_pure(): # if the input is pure, it is always hermitian
if self.is_pure(): # if the input is pure, it is always hermitian
return True

if precision_tol < 0.0:
Expand All @@ -216,7 +216,8 @@ def is_hermitian(
order = "euclidean"

reshaped = self._backend.cast(
np.reshape(self.operator(), (self.dims, self.dims)), dtype=self._tensor.dtype
np.reshape(self.operator(), (self.dims, self.dims)),
dtype=self._tensor.dtype,
)
mat_diff = self._backend.cast(
np.transpose(np.conj(reshaped)) - reshaped, dtype=reshaped.dtype
Expand All @@ -238,11 +239,12 @@ def is_positive_semidefinite(self, precision_tol: float = 1e-8):
Returns:
bool: Positive-semidefinite condition.
"""
if self.is_pure(): # if the input is pure, it is always positive semidefinite
if self.is_pure(): # if the input is pure, it is always positive semidefinite
return True

reshaped = self._backend.cast(
np.reshape(self.operator(), (self.dims, self.dims)), dtype=self._tensor.dtype
np.reshape(self.operator(), (self.dims, self.dims)),
dtype=self._tensor.dtype,
)

if self.is_hermitian():
Expand Down Expand Up @@ -516,7 +518,7 @@ def _run_checks(self, partition, system_input, pure):
TypeError,
f"``pure`` must be type ``bool``, but it is type ``{type(pure)}``.",
)

@staticmethod
def _check_system_input(system_input, partition) -> Tuple[bool]:
"""
Expand All @@ -537,8 +539,7 @@ def _set_parameters(self):

self.partition = tuple(self.partition)

self.system_input = self._check_system_input(self.system_input,
self.partition)
self.system_input = self._check_system_input(self.system_input, self.partition)

try:
if self._pure:
Expand Down Expand Up @@ -567,16 +568,15 @@ def full(self, backend=None, update=False):
"""Reshapes input matrix based on purity."""
tensor.reshape([self.dims])
tensor = np.tensordot(tensor, np.conj(tensor), axes=0)
tensor = self._operator2tensor(tensor,
self.partition,
self.system_input)
tensor = self._operator2tensor(tensor, self.partition, self.system_input)

if update:
self._tensor = tensor
self._pure = False

return tensor


class QuantumComb(QuantumNetwork):

def __init__(
Expand All @@ -591,21 +591,19 @@ def __init__(
if pure:
partition = tensor.shape
else:
partition = (int(np.sqrt(d)) for d in tensor.shape)
partition = (int(np.sqrt(d)) for d in tensor.shape)
if len(partition) % 2 != 0:
raise_error(
ValueError,
"A quantum comb should only contain equal number of input and output systems. "
+ "For general quantum networks, one should use the ``QuantumNetwork`` class."
+ "For general quantum networks, one should use the ``QuantumNetwork`` class.",
)
if system_output is not None:
warning('system_output is ignored for QuantumComb')

super().__init__(tensor,
partition,
[False, True]*(len(partition)//2),
pure,
backend)
warning("system_output is ignored for QuantumComb")

super().__init__(
tensor, partition, [False, True] * (len(partition) // 2), pure, backend
)

def is_causal(
self, order: Optional[Union[int, str]] = None, precision_tol: float = 1e-8
Expand Down Expand Up @@ -655,6 +653,7 @@ def is_causal(

return float(norm) <= precision_tol


class QuantumChannel(QuantumNetwork):

def __init__(
Expand All @@ -669,19 +668,19 @@ def __init__(
raise_error(
ValueError,
"A quantum channel should only contain one input system and one output system. "
+ "For general quantum networks, one should use the ``QuantumNetwork`` class."
+ "For general quantum networks, one should use the ``QuantumNetwork`` class.",
)

if len(partition) == 1 or partition == None:
if system_output == None: # Assume the input is a quantum state
if system_output == None: # Assume the input is a quantum state
partition = (1, partition[0])
elif len(system_output) == 1:
if system_output:
partition = (1, partition[0])
else:
partition = (partition[0], 1)
super().__init__(tensor, partition, [False,True], pure, backend)

super().__init__(tensor, partition, [False, True], pure, backend)

def is_unital(
self, order: Optional[Union[int, str]] = None, precision_tol: float = 1e-8
Expand Down Expand Up @@ -776,10 +775,14 @@ def apply(self, state):

return np.einsum("jklm,km -> jl", matrix, state)


class StochQuantumNetwork:
pass

def link_product(subscripts:str = 'ij,jk -> ik' , *operands: QuantumNetwork, backend=None):

def link_product(
subscripts: str = "ij,jk -> ik", *operands: QuantumNetwork, backend=None
):
"""Link product between two quantum networks.
The link product is not commutative. Here, we assume that
Expand All @@ -804,23 +807,23 @@ def link_product(subscripts:str = 'ij,jk -> ik' , *operands: QuantumNetwork, bac
TypeError,
f"subscripts must be type str, but it is type {type(subscripts)}.",
)

for i, operand in enumerate(operands):
if not isinstance(operand, QuantumNetwork):
raise_error(
TypeError,
f"The {i}th operator is not a ``QuantumNetwork``."
)

tensors = (operand.full() if operand.is_pure() else operand._tensor for operand in operands)
raise_error(TypeError, f"The {i}th operator is not a ``QuantumNetwork``.")

tensors = (
operand.full() if operand.is_pure() else operand._tensor for operand in operands
)

# keep track of the `partition` and `system_input` of the network
_, contracrtion_list = np.einsum_path(subscripts, *tensors,
optimize=False, einsum_call=True)

_, contracrtion_list = np.einsum_path(
subscripts, *tensors, optimize=False, einsum_call=True
)

inds, idx_rm, einsum_str, remaining, blas = contracrtion_list[0]
input_str, results_index = einsum_str.split('->')
inputs = input_str.split(',')
input_str, results_index = einsum_str.split("->")
inputs = input_str.split(",")

partition = []
system_input = []
Expand All @@ -840,8 +843,7 @@ def link_product(subscripts:str = 'ij,jk -> ik' , *operands: QuantumNetwork, bac

except:
continue

new_tensor = np.einsum(subscripts, *tensors)

return QuantumNetwork(new_tensor, partition, system_input, backend=backend)

19 changes: 11 additions & 8 deletions tests/test_quantum_info_quantum_networks.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,9 @@ def test_with_unitaries(backend, subscript):
unitary_1 @ unitary_2, (dims, dims), pure=True, backend=backend
)

test = network_1.link_product(network_2, subscript).full(backend=backend,update=True)
test = network_1.link_product(network_2, subscript).full(
backend=backend, update=True
)

if subscript[1] == subscript[3]:
backend.assert_allclose(test, network_3.full(), atol=1e-8)
Expand Down Expand Up @@ -258,7 +260,7 @@ def test_with_comb(backend):
channel, channel_partition, system_input=channel_sys_out, backend=backend
)

test = comb_choi.link_product(channel_choi, subscript).full(backend,update=True)
test = comb_choi.link_product(channel_choi, subscript).full(backend, update=True)
channel_choi2 = comb_choi @ channel_choi

backend.assert_allclose(test, channel_choi2.full(backend), atol=1e-5)
Expand Down Expand Up @@ -292,21 +294,22 @@ def test_non_hermitian_and_prints(backend):

assert network.__str__() == "J[4 -> 4]"


def test_uility_func():
old_shape = (0,10,1,11,2,12,3,13)
old_shape = (0, 10, 1, 11, 2, 12, 3, 13)
test_ls = np.ones(old_shape)
n = len(test_ls.shape) // 2

system_input = (False, True, False, True)

order2op = QuantumNetwork._order_tensor2operator(n , system_input)
order2tensor = QuantumNetwork._order_operator2tensor(n , system_input)
order2op = QuantumNetwork._order_tensor2operator(n, system_input)
order2tensor = QuantumNetwork._order_operator2tensor(n, system_input)
new_shape = test_ls.transpose(order2op).shape

for i in range(n):
if system_input[i]:
assert (new_shape[i] - new_shape[i+n]) == 10
assert (new_shape[i] - new_shape[i + n]) == 10
else:
assert (new_shape[i] - new_shape[i+n]) == -10
assert (new_shape[i] - new_shape[i + n]) == -10

assert tuple(test_ls.transpose(order2op).transpose(order2tensor).shape) == old_shape

0 comments on commit 93c7ca3

Please sign in to comment.