"""
Shortcuts for measurement patterns on circuit
"""
# circuit in, circuit out
# pylint: disable=invalid-name
from functools import wraps
from typing import Any, Callable, Optional, Sequence, Tuple
import numpy as np
from .graphs import Grid2DCoord
from .. import gates as G
from ..circuit import Circuit as Circ
from ..cons import backend
Circuit = Any # we don't use the real circuit class as too many mypy complains emerge
Tensor = Any
Graph = Any
[docs]def state_centric(f: Callable[..., Circuit]) -> Callable[..., Tensor]:
"""
Function decorator wraps the function with the first input and output in the format of circuit,
the wrapped function has the first input and the output as the state tensor.
:param f: Function with the fist input and the output as ``Circuit`` object.
:type f: Callable[..., Circuit]
:return: Wrapped function with the first input and the output as the state tensor correspondingly.
:rtype: Callable[..., Tensor]
"""
@wraps(f)
def wrapper(s: Tensor, *args: Any, **kws: Any) -> Tensor:
n = backend.sizen(s)
n = int(np.log2(n))
c = Circ(n, inputs=s)
c = f(c, *args, **kws)
s = c.state()
return s
return wrapper
[docs]def Bell_pair_block(
c: Circuit, links: Optional[Sequence[Tuple[int, int]]] = None
) -> Circuit:
"""
For each pair in links, the input product state |00> is transformed as (01>-|10>)
:param c: Circuit in
:type c: Circuit
:param links: pairs indices for Bell pairs, defaults to None, corresponds to neighbor links
:type links: Optional[Sequence[Tuple[int, int]]], optional
:return: Circuit out
:rtype: Circuit
"""
# from |00> return |01>-|10>
n = c._nqubits
if links is None:
links = [(i, i + 1) for i in range(0, n - 1, 2)]
for a, b in links:
c.X(a)
c.H(a)
c.cnot(a, b)
c.X(b)
return c
[docs]def Grid2D_entangling(
c: Circuit, coord: Grid2DCoord, unitary: Tensor, params: Tensor, **kws: Any
) -> Circuit:
i = 0
for a, b in coord.all_rows():
c.exp1(a, b, unitary=unitary, theta=params[i], **kws)
i += 1
for a, b in coord.all_cols():
c.exp1(a, b, unitary=unitary, theta=params[i], **kws)
i += 1
return c
[docs]def QAOA_block(
c: Circuit, g: Graph, paramzz: Tensor, paramx: Tensor, **kws: Any
) -> Circuit:
if backend.sizen(paramzz) == 1:
for e1, e2 in g.edges:
c.exp1(
e1,
e2,
unitary=G._zz_matrix,
theta=paramzz * g[e1][e2].get("weight", 1.0),
**kws
)
else:
i = 0
for e1, e2 in g.edges:
c.exp1(e1, e2, unitary=G._zz_matrix, theta=paramzz[i], **kws)
i += 1
if backend.sizen(paramx) == 1:
for n in g.nodes:
c.rx(n, theta=paramx)
else:
i = 0
for n in g.nodes:
c.rx(n, theta=paramx[i])
i += 1
return c
[docs]def example_block(
c: Circuit, param: Tensor, nlayers: int = 2, is_split: bool = False
) -> Circuit:
r"""
The circuit ansatz is firstly one layer of Hadamard gates and
then we have ``nlayers`` blocks of :math:`e^{i\theta Z_iZ_{i+1}}` two-qubit gate in ladder layout,
following rx gate.
:param c: The circuit
:type c: Circuit
:param param: paramter tensor with 2*nlayer*n elements
:type param: Tensor
:param nlayers: number of ZZ+RX blocks, defaults to 2
:type nlayers: int, optional
:param is_split: whether use SVD split to reduce ZZ gate bond dimension,
defaults to False
:type is_split: bool, optional
:return: The circuit with example ansatz attached
:rtype: Circuit
"""
# used for test and demonstrations
if is_split:
split_conf = {
"max_singular_values": 2,
"fixed_choice": 1,
}
else:
split_conf = None
n = c._nqubits
param = backend.reshape(param, [2 * nlayers, n])
for i in range(n):
c.H(i)
for j in range(nlayers):
for i in range(n - 1):
c.exp1(
i, i + 1, unitary=G._zz_matrix, theta=param[2 * j, i], split=split_conf
)
for i in range(n):
c.rx(i, theta=param[2 * j + 1, i])
return c
[docs]def qft(
c: Circuit,
*index: int,
do_swaps: bool = True,
inverse: bool = False,
insert_barriers: bool = False
) -> Circuit:
"""
This function applies quantum fourier transformation (QFT) to the selected circuit lines
:param c: Circuit in
:type c: Circuit
:param *index: the indices of the circuit lines to apply QFT
:type *index: List[int]
:param do_swaps: Whether to include the final swaps in the QFT
:type do_swaps: bool
:param inverse: If True, the inverse Fourier transform is constructed
:type inverse: bool
:param insert_barriers: If True, barriers are inserted as visualization improvement
:type insert_barriers: bool
:return: Circuit c
:rtype: Circuit
"""
assert len(set(index)) == len(index), "There should not be any repetitive qubits"
if inverse:
if do_swaps:
for i in range(len(index) // 2):
c.swap(index[i], index[len(index) - 1 - i])
for i in range(len(index) - 1, -1, -1):
rotation = -np.pi / 2
for j in range(len(index) - 1, i, -1):
c.cphase(index[j], index[i], theta=rotation)
rotation /= 2
c.H(index[i])
if insert_barriers:
c.barrier_instruction(range(min(index), max(index) + 1))
else:
for i in range(len(index)):
c.H(index[i])
rotation = np.pi / 2
for j in range(i + 1, len(index)):
c.cphase(index[j], index[i], theta=rotation)
rotation /= 2
if insert_barriers:
c.barrier_instruction(range(min(index), max(index) + 1))
if do_swaps:
for i in range(len(index) // 2):
c.swap(index[i], index[len(index) - 1 - i])
return c