Skip to content

Commit

Permalink
Add SELECT template (#4431)
Browse files Browse the repository at this point in the history
* initial commit

* simplify and remove extra ops

* improve docs, remove extra comment

* add tests, update error message

* autograd test, black, pylint

* add changelog, use generator

* Fix changelog

Co-authored-by: Utkarsh <utkarshazad98@gmail.com>

* Fix copy-paste mistake

Co-authored-by: Utkarsh <utkarshazad98@gmail.com>

* Apply suggestions from code review

Co-authored-by: Utkarsh <utkarshazad98@gmail.com>

* SELECT -> Select

* sum -> qml.all_wires

* test identity and custom wire labels

* black -l 100

* itertools -> bitstring processing

* address review comments

* add template tile image

* changelog fix attempt

* Update pennylane/templates/subroutines/select.py

Co-authored-by: Tom Bromley <49409390+trbromley@users.noreply.github.com>

* add _flatten, _unflatten

* fix _flatten, _unflatten

* update hyperparameters, flattening, add decomp tests

* new_op.control_wires

* add .tex for template image

* Update pennylane/templates/subroutines/select.py

Co-authored-by: Utkarsh <utkarshazad98@gmail.com>

* Code review - np to qml.math

Co-authored-by: Utkarsh <utkarshazad98@gmail.com>

* add image in docstring

* add decomposition examples

* bitstring processing -> itertools

* black -l 100

---------

Co-authored-by: Utkarsh <utkarshazad98@gmail.com>
Co-authored-by: Tom Bromley <49409390+trbromley@users.noreply.github.com>
Co-authored-by: Jay Soni <jbsoni@uwaterloo.ca>
  • Loading branch information
4 people authored Aug 16, 2023
1 parent 9ac4539 commit af1eba4
Show file tree
Hide file tree
Showing 7 changed files with 604 additions and 0 deletions.
Binary file added doc/_static/templates/subroutines/select.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 15 additions & 0 deletions doc/_static/templates/subroutines/select.tex
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
\documentclass{standalone}
\usepackage{graphicx} % Required for inserting images
\usepackage{quantikz}


\begin{document}

\begin{quantikz}
& \ctrl[open]{3} & \ctrl[open]{3} & \ \ldots\ & \ctrl{3} & \qw \\
& \ctrl[open]{2} & \ctrl[open]{2} & \ \ldots\ & \ctrl{2} & \qw \\
& \ctrl[open]{1} & \ctrl{1} & \ \ldots\ & \ctrl{1} & \qw\\
& \gate{U_0}& \gate{U_1} & \ \ldots\ & \gate{U_7} & \qw
\end{quantikz}

\end{document}
4 changes: 4 additions & 0 deletions doc/introduction/templates.rst
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,10 @@ Other useful templates which do not belong to the previous categories can be fou
:description: :doc:`QSVT<../code/api/pennylane.QSVT>`
:figure: _static/templates/subroutines/qsvt.png

.. gallery-item::
:description: :doc:`Select<../code/api/pennylane.Select>`
:figure: _static/templates/subroutines/select.png

.. raw:: html

<div style='clear:both'></div>
Expand Down
20 changes: 20 additions & 0 deletions doc/releases/changelog-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,26 @@ def circuit():
>>> circuit(shots=1)
array([False, False])


* A new `qml.Select` operation is available. It applies specific input operations depending on the
state of the designated control qubits
[(#4431)](https://github.com/PennyLaneAI/pennylane/pull/4431)

```pycon
>>> dev = qml.device('default.qubit',wires=4)
>>> ops = [qml.PauliX(wires=2),qml.PauliX(wires=3),qml.PauliY(wires=2),qml.SWAP([2,3])]
>>> @qml.qnode(dev)
>>> def circuit():
>>> qml.Select(ops,control_wires=[0,1])
>>> return qml.state()
...
>>> print(qml.draw(circuit,expansion_strategy='device')())
0: ─╭○─╭○─╭●─╭●────┤ State
1: ─├○─├●─├○─├●────┤ State
2: ─╰X─│──╰Y─├SWAP─┤ State
3: ────╰X────╰SWAP─┤ State
```

* Functions are available to obtain a state vector from `PySCF` solver objects.
[(#4427)](https://github.com/PennyLaneAI/pennylane/pull/4427)
[(#4433)](https://github.com/PennyLaneAI/pennylane/pull/4433)
Expand Down
1 change: 1 addition & 0 deletions pennylane/templates/subroutines/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@
from .flip_sign import FlipSign
from .basis_rotation import BasisRotation
from .qsvt import QSVT, qsvt
from .select import Select
182 changes: 182 additions & 0 deletions pennylane/templates/subroutines/select.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
# Copyright 2018-2023 Xanadu Quantum Technologies Inc.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

# http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Contains the Select template.
"""
# pylint: disable=too-many-arguments

import itertools
import pennylane as qml
from pennylane.operation import Operation
from pennylane import math


class Select(Operation):
r"""Applies specific input operations depending on the state of
the designated control qubits.
.. math:: Select|i\rangle \otimes |\psi\rangle = |i\rangle \otimes U_i |\psi\rangle
.. figure:: ../../../doc/_static/templates/subroutines/select.png
:align: center
:width: 60%
:target: javascript:void(0);
Args:
ops (list[Operator]): operations to apply
control_wires (Sequence[int]): the wires controlling which operation is applied
id (str or None): String representing the operation (optional)
.. note::
The position of the operation in the list determines which qubit state implements that operation.
For example, when the qubit register is in the state :math:`|00\rangle`, we will apply ``ops[0]``.
When the qubit register is in the state :math:`|10\rangle`, we will apply ``ops[2]``. To obtain the
binary bitstring representing the state for list position ``index`` we can use the following relationship:
``index = int(state_string, 2)``. For example, ``2 = int('10', 2)``.
**Example**
>>> dev = qml.device('default.qubit', wires=4)
>>> ops = [qml.PauliX(wires=2), qml.PauliX(wires=3), qml.PauliY(wires=2), qml.SWAP([2,3])]
>>> @qml.qnode(dev)
>>> def circuit():
>>> qml.Select(ops, control_wires=[0,1])
>>> return qml.state()
...
>>> print(qml.draw(circuit, expansion_strategy='device')())
0: ─╭○─╭○─╭●─╭●────┤ State
1: ─├○─├●─├○─├●────┤ State
2: ─╰X─│──╰Y─├SWAP─┤ State
3: ────╰X────╰SWAP─┤ State
"""

num_wires = qml.operation.AnyWires

def _flatten(self):
return (self.ops), (self.control_wires)

@classmethod
def _unflatten(cls, data, metadata) -> "Select":
return cls(data, metadata)

def __init__(self, ops, control_wires, id=None):
control_wires = qml.wires.Wires(control_wires)
self.hyperparameters["ops"] = ops
self.hyperparameters["control_wires"] = control_wires

if 2 ** len(control_wires) < len(ops):
raise ValueError(
f"Not enough control wires ({len(control_wires)}) for the desired number of "
+ f"operations ({len(ops)}). At least {int(math.ceil(math.log2(len(ops))))} control "
+ "wires required."
)

if any(
control_wire in qml.wires.Wires.all_wires([op.wires for op in ops])
for control_wire in control_wires
):
raise ValueError("Control wires should be different from operation wires.")

for op in ops:
qml.QueuingManager.remove(op)

target_wires = qml.wires.Wires.all_wires([op.wires for op in ops])
self.hyperparameters["target_wires"] = target_wires

all_wires = target_wires + control_wires
super().__init__(ops, wires=all_wires, id=id)

def decomposition(self):
r"""Representation of the operator as a product of other operators.
.. math:: O = O_1 O_2 \dots O_n
A ``DecompositionUndefinedError`` is raised if no representation by decomposition is defined.
.. seealso:: :meth:`~.Operator.compute_decomposition`.
Returns:
list[Operator]: decomposition of the operator
**Example**
>>> ops = [qml.PauliX(wires=2), qml.PauliX(wires=3), qml.PauliY(wires=2), qml.SWAP([2,3])]
>>> op = qml.Select(ops, control_wires=[0,1])
>>> op.decomposition()
[MultiControlledX(wires=[0, 1, 2], control_values="00"),
MultiControlledX(wires=[0, 1, 3], control_values="01"),
Controlled(PauliY(wires=[2]), control_wires=[0, 1], control_values=[True, False]),
Controlled(SWAP(wires=[2, 3]), control_wires=[0, 1])]
"""
return self.compute_decomposition(self.ops, control_wires=self.control_wires)

@staticmethod
def compute_decomposition(
ops,
control_wires,
): # pylint: disable=arguments-differ, unused-argument
r"""Representation of the operator as a product of other operators (static method).
.. math:: O = O_1 O_2 \dots O_n.
.. note::
Operations making up the decomposition should be queued within the
``compute_decomposition`` method.
.. seealso:: :meth:`~.Operator.decomposition`.
Args:
ops (list[Operator]): operations to apply
control_wires (Sequence[int]): the wires controlling which operation is applied
Returns:
list[Operator]: decomposition of the operator
**Example**
>>> ops = [qml.PauliX(wires=2), qml.PauliX(wires=3), qml.PauliY(wires=2), qml.SWAP([2,3])]
>>> qml.Select.compute_decomposition(ops, control_wires=[0,1])
[MultiControlledX(wires=[0, 1, 2], control_values="00"),
MultiControlledX(wires=[0, 1, 3], control_values="01"),
Controlled(PauliY(wires=[2]), control_wires=[0, 1], control_values=[True, False]),
Controlled(SWAP(wires=[2, 3]), control_wires=[0, 1])]
"""
states = list(itertools.product([0, 1], repeat=len(control_wires)))
decomp_ops = [
qml.ctrl(op, control_wires, control_values=states[index])
for index, op in enumerate(ops)
]
return decomp_ops

@property
def ops(self):
"""Operations to be applied."""
return self.hyperparameters["ops"]

@property
def control_wires(self):
"""The control wires."""
return self.hyperparameters["control_wires"]

@property
def target_wires(self):
"""The wires of the input operators."""
return self.hyperparameters["target_wires"]

@property
def wires(self):
"""All wires involved in the operation."""
return self.hyperparameters["control_wires"] + self.hyperparameters["target_wires"]
Loading

0 comments on commit af1eba4

Please sign in to comment.