Skip to content

Commit

Permalink
[Feature] Projector blocks (#208)
Browse files Browse the repository at this point in the history
  • Loading branch information
RolandMacDoland authored Nov 27, 2023
1 parent 2695e67 commit beae601
Show file tree
Hide file tree
Showing 9 changed files with 563 additions and 25 deletions.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ dependencies = [
"jsonschema",
"nevergrad",
"scipy",
"pyqtorch==1.0.1",
"pyqtorch==1.0.3",
"matplotlib"
]

Expand Down
10 changes: 9 additions & 1 deletion qadence/backends/pyqtorch/convert_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
block_to_diagonal,
block_to_tensor,
)
from qadence.blocks.primitive import ProjectorBlock
from qadence.operations import (
OpName,
U,
Expand Down Expand Up @@ -127,7 +128,14 @@ def convert_block(
# which would be wrong.
return [pyq.QuantumCircuit(n_qubits, ops)]
elif isinstance(block, tuple(non_unitary_gateset)):
return [getattr(pyq, block.name)(qubit_support[0])]
if isinstance(block, ProjectorBlock):
projector = getattr(pyq, block.name)
if block.name == OpName.N:
return [projector(target=qubit_support)]
else:
return [projector(qubit_support=qubit_support, ket=block.ket, bra=block.bra)]
else:
return [getattr(pyq, block.name)(qubit_support[0])]
elif isinstance(block, tuple(single_qubit_gateset)):
pyq_cls = getattr(pyq, block.name)
if isinstance(block, ParametricBlock):
Expand Down
11 changes: 11 additions & 0 deletions qadence/blocks/block_to_tensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@
PrimitiveBlock,
ScaleBlock,
)
from qadence.blocks.primitive import ProjectorBlock
from qadence.blocks.utils import chain, kron, uuid_to_expression
from qadence.parameters import evaluate, stringify

# from qadence.states import product_state
from qadence.types import Endianness, TensorType, TNumber

J = torch.tensor(1j)
Expand Down Expand Up @@ -463,6 +466,14 @@ def _block_to_tensor_embedded(
# add missing identities on unused qubits
mat = _fill_identities(block_mat, block.qubit_support, qubit_support, endianness=endianness)

elif isinstance(block, ProjectorBlock):
from qadence.states import product_state

bra = product_state(block.bra)
ket = product_state(block.ket)

mat = torch.kron(ket, bra.T)

else:
raise TypeError(f"Conversion for block type {type(block)} not supported.")

Expand Down
38 changes: 38 additions & 0 deletions qadence/blocks/primitive.py
Original file line number Diff line number Diff line change
Expand Up @@ -454,3 +454,41 @@ def _block_title(self) -> str:

s += rf" \[params: {params_str}]"
return s if self.tag is None else (s + rf" \[tag: {self.tag}]")


class ProjectorBlock(PrimitiveBlock):
"""The abstract ProjectorBlock."""

name = "ProjectorBlock"

def __init__(
self,
ket: str,
bra: str,
qubit_support: int | tuple[int, ...],
) -> None:
"""
Arguments:
ket (str): The ket given as a bitstring.
bra (str): The bra given as a bitstring.
qubit_support (int | tuple[int]): The qubit_support of the block.
"""
if isinstance(qubit_support, int):
qubit_support = (qubit_support,)
if len(bra) != len(ket):
raise ValueError(
"Bra and ket must be bitstrings of same length in the 'Projector' definition."
)
elif len(bra) != len(qubit_support):
raise ValueError("Bra or ket must be of same length as the 'qubit_support'")
for wf in [bra, ket]:
if not all(int(item) == 0 or int(item) == 1 for item in wf):
raise ValueError(
"All qubits must be either in the '0' or '1' state"
" in the 'ProjectorBlock' definition."
)

self.ket = ket
self.bra = bra
super().__init__(qubit_support)
59 changes: 38 additions & 21 deletions qadence/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
WaitBlock,
)
from qadence.blocks.block_to_tensor import block_to_tensor
from qadence.blocks.primitive import ProjectorBlock
from qadence.blocks.utils import (
add, # noqa
block_is_commuting_hamiltonian,
Expand Down Expand Up @@ -169,13 +170,35 @@ def dagger(self) -> Z:
return self


class N(PrimitiveBlock):
class Projector(ProjectorBlock):
"""The projector operator."""

name = OpName.PROJ

def __init__(
self,
ket: str,
bra: str,
qubit_support: int | tuple[int, ...],
):
super().__init__(ket=ket, bra=bra, qubit_support=qubit_support)

@property
def generator(self) -> None:
raise ValueError("Property `generator` not available for non-unitary operator.")

@property
def eigenvalues_generator(self) -> None:
raise ValueError("Property `eigenvalues_generator` not available for non-unitary operator.")


class N(Projector):
"""The N = (1/2)(I-Z) operator."""

name = OpName.N

def __init__(self, target: int):
super().__init__((target,))
def __init__(self, target: int, state: str = "1"):
super().__init__(ket=state, bra=state, qubit_support=(target,))

@property
def generator(self) -> None:
Expand Down Expand Up @@ -668,7 +691,7 @@ class CNOT(ControlBlock):
name = OpName.CNOT

def __init__(self, control: int, target: int) -> None:
self.generator = kron((I(control) - Z(control)) * 0.5, X(target) - I(target))
self.generator = kron(N(control), X(target) - I(target))
super().__init__((control,), X(target))

@property
Expand Down Expand Up @@ -699,9 +722,7 @@ class MCZ(ControlBlock):
name = OpName.MCZ

def __init__(self, control: tuple[int, ...], target: int) -> None:
self.generator = kron(
*[(I(qubit) - Z(qubit)) * 0.5 for qubit in control], Z(target) - I(target)
)
self.generator = kron(*[N(qubit) for qubit in control], Z(target) - I(target))
super().__init__(control, Z(target))

@property
Expand Down Expand Up @@ -749,7 +770,7 @@ def __init__(
target: int,
parameter: Parameter | TNumber | sympy.Expr | str,
) -> None:
self.generator = kron(*[(I(qubit) - Z(qubit)) * 0.5 for qubit in control], X(target))
self.generator = kron(*[N(qubit) for qubit in control], X(target))
super().__init__(control, RX(target, parameter))

@classmethod
Expand Down Expand Up @@ -792,7 +813,7 @@ def __init__(
target: int,
parameter: Parameter | TNumber | sympy.Expr | str,
) -> None:
self.generator = kron(*[(I(qubit) - Z(qubit)) * 0.5 for qubit in control], Y(target))
self.generator = kron(*[N(qubit) for qubit in control], Y(target))
super().__init__(control, RY(target, parameter))

@classmethod
Expand Down Expand Up @@ -835,7 +856,7 @@ def __init__(
target: int,
parameter: Parameter | TNumber | sympy.Expr | str,
) -> None:
self.generator = kron(*[(I(qubit) - Z(qubit)) * 0.5 for qubit in control], Z(target))
self.generator = kron(*[N(qubit) for qubit in control], Z(target))
super().__init__(control, RZ(target, parameter))

@classmethod
Expand Down Expand Up @@ -878,10 +899,10 @@ def __init__(self, control: int | tuple[int, ...], target1: int, target2: int) -
if isinstance(control, tuple):
control = control[0]

a00m = 0.5 * (Z(control) - I(control))
a00p = -0.5 * (Z(control) + I(control))
a11 = 0.5 * (Z(target1) - I(target1))
a22 = -0.5 * (Z(target2) + I(target2))
a00m = -N(target=control)
a00p = -N(target=control, state="0")
a11 = -N(target=target1)
a22 = -N(target=target2, state="0")
a12 = 0.5 * (chain(X(target1), Z(target1)) + X(target1))
a21 = 0.5 * (chain(Z(target2), X(target2)) + X(target2))
no_effect = kron(a00m, I(target1), I(target2))
Expand Down Expand Up @@ -1031,9 +1052,7 @@ def __init__(
target: int,
parameter: Parameter | TNumber | sympy.Expr | str,
) -> None:
self.generator = kron(
*[(I(qubit) - Z(qubit)) * 0.5 for qubit in control], Z(target) - I(target)
)
self.generator = kron(*[N(qubit) for qubit in control], Z(target) - I(target))
super().__init__(control, PHASE(target, parameter))

@classmethod
Expand Down Expand Up @@ -1082,9 +1101,7 @@ class Toffoli(ControlBlock):
name = OpName.TOFFOLI

def __init__(self, control: tuple[int, ...], target: int) -> None:
self.generator = kron(
*[(I(qubit) - Z(qubit)) * 0.5 for qubit in control], X(target) - I(target)
)
self.generator = kron(*[N(qubit) for qubit in control], X(target) - I(target))
super().__init__(control, X(target))

def dagger(self) -> Toffoli:
Expand Down Expand Up @@ -1288,4 +1305,4 @@ def AnalogRZ(
entangle,
wait,
]
non_unitary_gateset = [Zero, N]
non_unitary_gateset = [Zero, N, Projector]
2 changes: 2 additions & 0 deletions qadence/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -368,3 +368,5 @@ class OpName(StrEnum):
"""The entanglement operation."""
WAIT = "wait"
"""The wait operation."""
PROJ = "Projector"
"""The projector operation."""
Loading

0 comments on commit beae601

Please sign in to comment.