Skip to content

Commit

Permalink
Adds Uniformly controlled rotations (Ry and Rz) (#253)
Browse files Browse the repository at this point in the history
  • Loading branch information
damiansteiger committed Aug 9, 2018
1 parent 4674e11 commit d3631e2
Show file tree
Hide file tree
Showing 8 changed files with 497 additions and 4 deletions.
2 changes: 2 additions & 0 deletions projectq/ops/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,5 @@
from ._qubit_operator import QubitOperator
from ._shortcuts import *
from ._time_evolution import TimeEvolution
from ._uniformly_controlled_rotation import (UniformlyControlledRy,
UniformlyControlledRz)
4 changes: 2 additions & 2 deletions projectq/ops/_qubit_operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@ def __iadd__(self, addend):
if abs(addend.terms[term] + self.terms[term]) > 0.:
self.terms[term] += addend.terms[term]
else:
del self.terms[term]
self.terms.pop(term)
else:
self.terms[term] = addend.terms[term]
else:
Expand Down Expand Up @@ -425,7 +425,7 @@ def __isub__(self, subtrahend):
if abs(self.terms[term] - subtrahend.terms[term]) > 0.:
self.terms[term] -= subtrahend.terms[term]
else:
del self.terms[term]
self.terms.pop(term)
else:
self.terms[term] = -subtrahend.terms[term]
else:
Expand Down
3 changes: 3 additions & 0 deletions projectq/ops/_qubit_operator_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,9 @@ def test_isub_different_term():
assert len(a.terms) == 2
assert a.terms[term_a] == pytest.approx(1.0)
assert a.terms[term_b] == pytest.approx(-1.0)
b = qo.QubitOperator(term_a, 1.0)
b -= qo.QubitOperator(term_a, 1.0)
assert b.terms == {}


def test_isub_bad_addend():
Expand Down
147 changes: 147 additions & 0 deletions projectq/ops/_uniformly_controlled_rotation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
# Copyright 2018 ProjectQ-Framework (www.projectq.ch)
#
# 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.

import math

from ._basics import ANGLE_PRECISION, ANGLE_TOLERANCE, BasicGate, NotMergeable


class UniformlyControlledRy(BasicGate):
"""
Uniformly controlled Ry gate as introduced in arXiv:quant-ph/0312218.
This is an n-qubit gate. There are n-1 control qubits and one target qubit.
This gate applies Ry(angles(k)) to the target qubit if the n-1 control
qubits are in the classical state k. As there are 2^(n-1) classical
states for the control qubits, this gate requires 2^(n-1) (potentially
different) angle parameters.
Example:
.. code-block:: python
controls = eng.allocate_qureg(2)
target = eng.allocate_qubit()
UniformlyControlledRy(angles=[0.1, 0.2, 0.3, 0.4]) | (controls, target)
Note:
The first quantum register contains the control qubits. When converting
the classical state k of the control qubits to an integer, we define
controls[0] to be the least significant (qu)bit. controls can also
be an empty list in which case the gate corresponds to an Ry.
Args:
angles(list[float]): Rotation angles. Ry(angles[k]) is applied
conditioned on the control qubits being in state
k.
"""
def __init__(self, angles):
BasicGate.__init__(self)
rounded_angles = []
for angle in angles:
new_angle = round(float(angle) % (4. * math.pi), ANGLE_PRECISION)
if new_angle > 4 * math.pi - ANGLE_TOLERANCE:
new_angle = 0.
rounded_angles.append(new_angle)
self.angles = rounded_angles

def get_inverse(self):
return self.__class__([-1 * angle for angle in self.angles])

def get_merged(self, other):
if isinstance(other, self.__class__):
new_angles = [angle1 + angle2 for (angle1, angle2) in
zip(self.angles, other.angles)]
return self.__class__(new_angles)
raise NotMergeable()

def __str__(self):
return "UniformlyControlledRy(" + str(self.angles) + ")"

def __eq__(self, other):
""" Return True if same class, same rotation angles."""
if isinstance(other, self.__class__):
return self.angles == other.angles
else:
return False

def __ne__(self, other):
return not self.__eq__(other)

def __hash__(self):
return hash(str(self))


class UniformlyControlledRz(BasicGate):
"""
Uniformly controlled Rz gate as introduced in arXiv:quant-ph/0312218.
This is an n-qubit gate. There are n-1 control qubits and one target qubit.
This gate applies Rz(angles(k)) to the target qubit if the n-1 control
qubits are in the classical state k. As there are 2^(n-1) classical
states for the control qubits, this gate requires 2^(n-1) (potentially
different) angle parameters.
Example:
.. code-block:: python
controls = eng.allocate_qureg(2)
target = eng.allocate_qubit()
UniformlyControlledRz(angles=[0.1, 0.2, 0.3, 0.4]) | (controls, target)
Note:
The first quantum register are the contains qubits. When converting
the classical state k of the control qubits to an integer, we define
controls[0] to be the least significant (qu)bit. controls can also
be an empty list in which case the gate corresponds to an Rz.
Args:
angles(list[float]): Rotation angles. Rz(angles[k]) is applied
conditioned on the control qubits being in state
k.
"""
def __init__(self, angles):
BasicGate.__init__(self)
rounded_angles = []
for angle in angles:
new_angle = round(float(angle) % (4. * math.pi), ANGLE_PRECISION)
if new_angle > 4 * math.pi - ANGLE_TOLERANCE:
new_angle = 0.
rounded_angles.append(new_angle)
self.angles = rounded_angles

def get_inverse(self):
return self.__class__([-1 * angle for angle in self.angles])

def get_merged(self, other):
if isinstance(other, self.__class__):
new_angles = [angle1 + angle2 for (angle1, angle2) in
zip(self.angles, other.angles)]
return self.__class__(new_angles)
raise NotMergeable()

def __str__(self):
return "UniformlyControlledRz(" + str(self.angles) + ")"

def __eq__(self, other):
""" Return True if same class, same rotation angles."""
if isinstance(other, self.__class__):
return self.angles == other.angles
else:
return False

def __ne__(self, other):
return not self.__eq__(other)

def __hash__(self):
return hash(str(self))
73 changes: 73 additions & 0 deletions projectq/ops/_uniformly_controlled_rotation_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Copyright 2018 ProjectQ-Framework (www.projectq.ch)
#
# 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.


"""Tests for projectq.ops._uniformly_controlled_rotation."""
import math

import pytest

from projectq.ops import Rx
from ._basics import NotMergeable

from projectq.ops import _uniformly_controlled_rotation as ucr


@pytest.mark.parametrize("gate_class", [ucr.UniformlyControlledRy,
ucr.UniformlyControlledRz])
def test_init_rounding(gate_class):
gate = gate_class([0.1 + 4 * math.pi, -1e-14])
assert gate.angles == [0.1, 0.]


@pytest.mark.parametrize("gate_class", [ucr.UniformlyControlledRy,
ucr.UniformlyControlledRz])
def test_get_inverse(gate_class):
gate = gate_class([0.1, 0.2, 0.3, 0.4])
inverse = gate.get_inverse()
assert inverse == gate_class([-0.1, -0.2, -0.3, -0.4])


@pytest.mark.parametrize("gate_class", [ucr.UniformlyControlledRy,
ucr.UniformlyControlledRz])
def test_get_merged(gate_class):
gate1 = gate_class([0.1, 0.2, 0.3, 0.4])
gate2 = gate_class([0.1, 0.2, 0.3, 0.4])
merged_gate = gate1.get_merged(gate2)
assert merged_gate == gate_class([0.2, 0.4, 0.6, 0.8])
with pytest.raises(NotMergeable):
gate1.get_merged(Rx(0.1))


def test_str_and_hash():
gate1 = ucr.UniformlyControlledRy([0.1, 0.2, 0.3, 0.4])
gate2 = ucr.UniformlyControlledRz([0.1, 0.2, 0.3, 0.4])
assert str(gate1) == "UniformlyControlledRy([0.1, 0.2, 0.3, 0.4])"
assert str(gate2) == "UniformlyControlledRz([0.1, 0.2, 0.3, 0.4])"
assert hash(gate1) == hash("UniformlyControlledRy([0.1, 0.2, 0.3, 0.4])")
assert hash(gate2) == hash("UniformlyControlledRz([0.1, 0.2, 0.3, 0.4])")


@pytest.mark.parametrize("gate_class", [ucr.UniformlyControlledRy,
ucr.UniformlyControlledRz])
def test_equality(gate_class):
gate1 = gate_class([0.1, 0.2])
gate2 = gate_class([0.1, 0.2 + 1e-14])
assert gate1 == gate2
gate3 = gate_class([0.1, 0.2, 0.1, 0.2])
assert gate2 != gate3
gate4 = ucr.UniformlyControlledRz([0.1, 0.2])
gate5 = ucr.UniformlyControlledRy([0.1, 0.2])
assert gate4 != gate5
assert not gate5 == gate4
6 changes: 4 additions & 2 deletions projectq/setups/decompositions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
ry2rz,
swap2cnot,
toffoli2cnotandtgate,
time_evolution)
time_evolution,
uniformlycontrolledr2cnot)

all_defined_decomposition_rules = [
rule
Expand All @@ -46,6 +47,7 @@
ry2rz,
swap2cnot,
toffoli2cnotandtgate,
time_evolution]
time_evolution,
uniformlycontrolledr2cnot]
for rule in module.all_defined_decomposition_rules
]

0 comments on commit d3631e2

Please sign in to comment.