Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] Projector blocks #208

Merged
merged 31 commits into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
d3f5df7
Functionality to get tensor from projector blocks.
RolandMacDoland Nov 21, 2023
e9f0053
New projector block.
RolandMacDoland Nov 21, 2023
a531723
Better imports.
RolandMacDoland Nov 21, 2023
8115322
New OpName for projectors.
RolandMacDoland Nov 21, 2023
70d689e
The projector as an operation.
RolandMacDoland Nov 21, 2023
321c588
Test for the new projector op.
RolandMacDoland Nov 21, 2023
b332112
Integrate projector with number and toffoli.
RolandMacDoland Nov 21, 2023
44413ca
Test projector integration with both number and toffoli.
RolandMacDoland Nov 21, 2023
dfab496
Use number operator consistently in controlled blocks.
RolandMacDoland Nov 21, 2023
43bb33e
Test number operator in controlled blocks.
RolandMacDoland Nov 21, 2023
0f52930
Address comments about parameter ordering.
RolandMacDoland Nov 21, 2023
ae9b43e
Merge branch 'main' into rg/projector-blocks
RolandMacDoland Nov 21, 2023
d8c376f
More use of the number operator.
RolandMacDoland Nov 22, 2023
d646893
Better type annotation.
RolandMacDoland Nov 22, 2023
5d3c2bb
Check that the returned gate list is always populated.
RolandMacDoland Nov 22, 2023
99f48eb
Correct type annotation.
RolandMacDoland Nov 22, 2023
900b472
Better docstring.
RolandMacDoland Nov 22, 2023
a111517
Fix imports.
RolandMacDoland Nov 22, 2023
cfba9e2
Mark test as flaky.
RolandMacDoland Nov 22, 2023
b93b0e0
Correct flaky decorator.
RolandMacDoland Nov 22, 2023
2eb02d5
Add projectors as non-unitary and revert hack in circuit generation.
RolandMacDoland Nov 22, 2023
3120839
Better docstring.
RolandMacDoland Nov 22, 2023
238fa38
Convert ProjectorBlock to corresponding PyQTorch type.
RolandMacDoland Nov 23, 2023
5c873c4
Test integration with PyQTorch.
RolandMacDoland Nov 23, 2023
3271da1
Correct interface with PyQTorch.
RolandMacDoland Nov 23, 2023
86ace17
More tests with PyQTorch in two qubit case.
RolandMacDoland Nov 23, 2023
9027d04
Update to latest pyqtorch.
RolandMacDoland Nov 27, 2023
84f71de
Correct argument.
RolandMacDoland Nov 27, 2023
f51bd07
Remove debugging line.
RolandMacDoland Nov 27, 2023
49ab22b
Correct handking of the N op as a projector.
RolandMacDoland Nov 27, 2023
e45f1ae
Move testing the projector matrices to the appropriate test file.
RolandMacDoland Nov 27, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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