Source code for pennylane.templates.state_preparations.superposition
# Copyright 2024 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.
r"""
Contains the Superposition template.
"""
import pennylane as qml
from pennylane.operation import AnyWires, Operation
def _assign_states(basis_list):
r"""
This function maps a given list of :math:`m` basis states to the first :math:`m` basis states in the
computational basis.
For instance, a given list of :math:`[s_0, s_1, ..., s_m]` where :math:`s` is a basis
state of length :math:`4` will be mapped as :math:`{s_0: |0000\rangle, s_1: |0001\rangle, s_2: |0010\rangle, \dots}`.
Note that if a state in ``basis_list`` is one of the first :math:`m` basis states,
this state will be mapped to itself.
Args:
basis_list (list): list of basis states to be mapped
Returns:
dict: dictionary mapping basis states to the first :math:`m` basis states
** Example **
.. code-block:: pycon
>>> basis_list = [[1, 1, 0, 0], [1, 0, 1, 0], [0, 1, 0, 1], [1, 0, 0, 1]]
>>> _assign_states(basis_list)
{
[1, 1, 0, 0]: [0, 0, 0, 0],
[1, 0, 1, 0]: [0, 0, 0, 1],
[0, 1, 0, 1]: [0, 0, 1, 0],
[1, 0, 0, 1]: [0, 0, 1, 1]
}
.. code-block:: pycon
>>>> basis_list = [[1, 1, 0, 0], [0, 1, 0, 1], [0, 0, 0, 1], [1, 0, 0, 1]]
>>> _assign_states(basis_list)
{
[1, 1, 0, 0]: [0, 0, 0, 0],
[0, 1, 0, 1]: [0, 0, 1, 0],
[0, 0, 0, 1]: [0, 0, 0, 1],
[1, 0, 0, 1]: [0, 0, 1, 1]
}
"""
length = len(basis_list[0])
smallest_basis_lists = [tuple(map(int, f"{i:0{length}b}")) for i in range(len(basis_list))]
binary_dict = {}
used_smallest = set()
# Assign keys that can map to themselves
for original in basis_list:
if original in smallest_basis_lists and tuple(original) not in used_smallest:
binary_dict[tuple(original)] = original
used_smallest.add(tuple(original))
# Assign remaining keys to unused binary lists
remaining_keys = [key for key in basis_list if tuple(key) not in binary_dict]
remaining_values = [
value for value in smallest_basis_lists if tuple(value) not in used_smallest
]
for key, value in zip(remaining_keys, remaining_values):
binary_dict[tuple(key)] = value
used_smallest.add(tuple(value))
return binary_dict
def _permutation_operator(basis1, basis2, wires, work_wire):
r"""
Creates operations that map an initial basis state to a target basis state using an auxiliary qubit.
Args:
basis1 (List): The initial basis state, represented as a list of binary digits.
basis2 (List): The target basis state, represented as a list of binary digits.
wires (Sequence[int]): The list of wires that the operator acts on.
work_wire (Union[Wires, int, str]): The auxiliary wire used for the permutation.
Returns:
list: A list of operators that map :math:`|\text{basis1}\rangle` to :math:`|\text{basis2}\rangle`.
"""
ops = []
ops.append(qml.ctrl(qml.PauliX(work_wire), control=wires, control_values=basis1))
for i, b in enumerate(basis1):
if b != basis2[i]:
ops.append(qml.CNOT(wires=work_wire + wires[i]))
ops.append(qml.ctrl(qml.PauliX(work_wire), control=wires, control_values=basis2))
return ops
[docs]class Superposition(Operation):
r"""
Prepare a superposition of computational basis states.
Given a list of :math:`m` coefficients :math:`c_i` and basic states :math:`|b_i\rangle`,
this operator prepares the state:
.. math::
|\phi\rangle = \sum_i^m c_i |b_i\rangle.
See the Details section for more information about the decomposition.
Args:
coeffs (tensor-like[float]): normalized coefficients of the superposition
bases (tensor-like[int]): basis states of the superposition
wires (Sequence[int]): wires that the operator acts on
work_wire (Union[Wires, int, str]): the auxiliary wire used for the permutation
**Example**
.. code-block::
import pennylane as qml
import numpy as np
coeffs = np.sqrt(np.array([1/3, 1/3, 1/3]))
bases = np.array([[1, 1, 1], [0, 1, 0], [0, 0, 0]])
wires = [0, 1, 2]
work_wire = 3
dev = qml.device('default.qubit')
@qml.qnode(dev)
def circuit():
qml.Superposition(coeffs, bases, wires, work_wire)
return qml.probs(wires)
.. code-block:: pycon
>>> print(circuit())
[0.33333333 0. 0.33333333 0. 0. 0.
0. 0.33333333]
.. details::
:title: Details
The input superposition state , :math:`|\phi\rangle = \sum_i^m c_i |b_i\rangle`, is implemented in two steps. First, the coefficients :math:`c_i` are used to prepares the state:
.. math::
|\phi\rangle = \sum_i^m c_i |i\rangle,
where :math:`|i\rangle` is a computational basis states and :math:`m` is the number of terms
in the superposition. This is done using the
:class:`~.StatePrep` template in the fisrt :math:`\lceil \log_2 m \rceil` qubits. Note that the number of qubits depends on the number of terms in the superposition, which helps to reduce the complexity of the operation.
The second step permutes the basis states prepared previously to
the target basis states:
.. math::
|i\rangle \rightarrow |b_i\rangle.
This block maps the elements one by one using an auxiliary qubit.
This can be done in three separate steps:
1. By using a multi-controlled NOT gate, check if the input state is :math:`|i\rangle` and
store the information in the auxiliary qubit. If the state is :math:`|i\rangle` the auxiliary
qubit will be in the :math:`|1\rangle` state.
2. If the auxiliary qubit is in the :math:`|1\rangle` state, the input state is modified by applying
``X`` gates to the bits that are different between :math:`|i\rangle` and :math:`|b_i\rangle`.
3. By using a multi-controlled ``NOT`` gate, check if the final state is :math:`|b_i\rangle` and
return the auxiliary qubit back to :math:`|0\rangle` state.
Applying all these together prepares the desired superposition:
.. math::
|\phi\rangle = \sum_i^m c_i |b_i\rangle.
The decomposition has a complexity that grows linearly with the number of terms in the superposition,
unlike other methods such as :class:`~.MottonenStatePreparation` that grows exponentially
with the number of qubits.
"""
num_wires = AnyWires
grad_method = None
ndim_params = (1,)
def __init__(
self, coeffs, bases, wires, work_wire, id=None
): # pylint: disable=too-many-positional-arguments, too-many-arguments
if not all(
all(qml.math.isclose(i, 0.0) or qml.math.isclose(i, 1.0) for i in b) for b in bases
):
raise ValueError("The elements of the basis states must be either 0 or 1.")
basis_lengths = {len(b) for b in bases}
if len(basis_lengths) > 1:
raise ValueError("All basis states must have the same length.")
if not qml.math.is_abstract(coeffs):
coeffs_norm = qml.math.linalg.norm(coeffs)
if not qml.math.allclose(coeffs_norm, qml.math.array(1.0)):
raise ValueError("The input superposition must be normalized.")
unique_basis = qml.math.unique(qml.math.array([tuple(b) for b in bases]), axis=0)
if len(unique_basis) != len(bases):
raise ValueError("The basis states must be unique.")
self.hyperparameters["bases"] = tuple(tuple(int(i) for i in b) for b in bases)
self.hyperparameters["target_wires"] = qml.wires.Wires(wires)
self.hyperparameters["work_wire"] = qml.wires.Wires(work_wire)
all_wires = self.hyperparameters["target_wires"] + self.hyperparameters["work_wire"]
super().__init__(coeffs, wires=all_wires, id=id)
@property
def num_params(self):
return 1
@classmethod
def _primitive_bind_call(cls, *args, **kwargs):
return cls._primitive.bind(*args, **kwargs)
def _flatten(self):
metadata = tuple(
(key, value) for key, value in self.hyperparameters.items() if key != "target_wires"
)
return tuple(self.parameters), (self.hyperparameters["target_wires"], metadata)
@classmethod
def _unflatten(cls, data, metadata):
hyperparams_dict = dict(metadata[1])
return cls(*data, wires=metadata[0], **hyperparams_dict)
[docs] def decomposition(self): # pylint: disable=arguments-differ
return self.compute_decomposition(
*self.parameters,
bases=self.hyperparameters["bases"],
wires=self.hyperparameters["target_wires"],
work_wire=self.hyperparameters["work_wire"],
)
[docs] @staticmethod
def compute_decomposition(coeffs, bases, wires, work_wire): # pylint: disable=arguments-differ
r"""Representation of the operator as a product of other operators.
Args:
coeffs (tensor-like[float]): normalized coefficients of the superposition
bases (tensor-like[int]): basis states of the superposition
wires (Sequence[int]): wires that the operator acts on
work_wire (Union[Wires, int, str]): the auxiliary wire used for the permutation
Returns:
list[.Operator]: Decomposition of the operator
**Example**
.. code-block:: pycon
>>> qml.Superposition(np.sqrt([1/2, 1/2]), [[1, 1], [0, 0]], [0, 1], 2).decomposition()
[StatePrep(array([0.70710678, 0.70710678]), wires=[1]),
MultiControlledX(wires=[0, 1, 2], control_values=[False, True]),
CNOT(wires=[2, 0]),
Toffoli(wires=[0, 1, 2])]
"""
dic_state = dict(zip(bases, coeffs))
perms = _assign_states(bases)
new_dic_state = {perms[key]: dic_state[key] for key in dic_state if key in perms}
sorted_coefficients = [
value
for key, value in sorted(
new_dic_state.items(), key=lambda item: int("".join(map(str, item[0])), 2)
)
]
op_list = []
op_list.append(
qml.StatePrep(
qml.math.stack(sorted_coefficients),
wires=wires[-int(qml.math.ceil(qml.math.log2(len(coeffs)))) :],
pad_with=0,
)
)
for basis2, basis1 in perms.items():
if not qml.math.allclose(basis1, basis2):
op_list += _permutation_operator(basis1, basis2, wires, work_wire)
return op_list
@property
def bases(self):
r"""List of basis states :math:`|b_i\rangle`."""
return self.hyperparameters["bases"]
@property
def work_wire(self):
r"""The auxiliary wire used for the permutation."""
return self.hyperparameters["work_wire"]
@property
def coeffs(self):
r"""List of coefficients :math:`c_i`."""
return self.parameters[0]
[docs] def map_wires(self, wire_map: dict):
new_dict = {
key: [wire_map.get(w, w) for w in self.hyperparameters[key]]
for key in ["target_wires", "work_wire"]
}
return Superposition(
self.coeffs,
bases=self.bases,
wires=new_dict["target_wires"],
work_wire=new_dict["work_wire"],
)
_modules/pennylane/templates/state_preparations/superposition
Download Python script
Download Notebook
View on GitHub