Skip to content

Commit

Permalink
Merge branch 'develop' into feature/updated_header
Browse files Browse the repository at this point in the history
  • Loading branch information
damiansteiger committed Sep 6, 2017
2 parents d59fd89 + 92c3911 commit 3b40534
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 5 deletions.
3 changes: 2 additions & 1 deletion projectq/ops/__init__.py
Expand Up @@ -19,7 +19,8 @@
BasicRotationGate,
ClassicalInstructionGate,
FastForwardingGate,
BasicMathGate)
BasicMathGate,
BasicPhaseGate)
from ._command import apply_command, Command
from ._metagates import (DaggeredGate,
get_inverse,
Expand Down
89 changes: 89 additions & 0 deletions projectq/ops/_basics.py
Expand Up @@ -312,6 +312,95 @@ def __ne__(self, other):
return not self.__eq__(other)


class BasicPhaseGate(BasicGate):
"""
Defines a base class of a phase gate.
A phase gate has a continuous parameter (the angle), labeled 'angle' /
self._angle. Its inverse is the same gate with the negated argument.
Phase gates of the same class can be merged by adding the angles.
The continuous parameter is modulo 2 * pi, self._angle is in the interval
[0, 2 * pi).
"""
def __init__(self, angle):
"""
Initialize a basic rotation gate.
Args:
angle (float): Angle of rotation (saved modulo 2 * pi)
"""
BasicGate.__init__(self)
self._angle = float(angle) % (2. * math.pi)

def __str__(self):
"""
Return the string representation of a BasicRotationGate.
Returns the class name and the angle as
.. code-block:: python
[CLASSNAME]([ANGLE])
"""
return str(self.__class__.__name__) + "(" + str(self._angle) + ")"

def tex_str(self):
"""
Return the Latex string representation of a BasicRotationGate.
Returns the class name and the angle as a subscript, i.e.
.. code-block:: latex
[CLASSNAME]$_[ANGLE]$
"""
return str(self.__class__.__name__) + "$_{" + str(self._angle) + "}$"

def get_inverse(self):
"""
Return the inverse of this rotation gate (negate the angle, return new
object).
"""
if self._angle == 0:
return self.__class__(0)
else:
return self.__class__(-self._angle + 2 * math.pi)

def get_merged(self, other):
"""
Return self merged with another gate.
Default implementation handles rotation gate of the same type, where
angles are simply added.
Args:
other: Rotation gate of same type.
Raises:
NotMergeable: For non-rotation gates or rotation gates of
different type.
Returns:
New object representing the merged gates.
"""
if isinstance(other, self.__class__):
return self.__class__(self._angle + other._angle)
raise NotMergeable("Can't merge different types of rotation gates.")

def __eq__(self, other):
""" Return True if same class and same rotation angle. """
tolerance = EQ_TOLERANCE
if isinstance(other, self.__class__):
difference = abs(self._angle - other._angle) % (2 * math.pi)
# Return True if angles are close to each other modulo 4 * pi
if difference < tolerance or difference > 2 * math.pi - tolerance:
return True
return False

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


# Classical instruction gates never have control qubits.
class ClassicalInstructionGate(BasicGate):
"""
Expand Down
58 changes: 58 additions & 0 deletions projectq/ops/_basics_test.py
Expand Up @@ -191,6 +191,64 @@ def test_basic_rotation_gate_comparison():
assert basic_rotation_gate2 != _basics.BasicRotationGate(0.5 + 2 * math.pi)


@pytest.mark.parametrize("input_angle, modulo_angle",
[(2.0, 2.0), (17., 4.4336293856408275),
(-0.5 * math.pi, 1.5 * math.pi), (2 * math.pi, 0)])
def test_basic_phase_gate_init(input_angle, modulo_angle):
# Test internal representation
gate = _basics.BasicPhaseGate(input_angle)
assert gate._angle == pytest.approx(modulo_angle)


def test_basic_phase_gate_str():
basic_phase_gate = _basics.BasicPhaseGate(0.5)
assert str(basic_phase_gate) == "BasicPhaseGate(0.5)"


def test_basic_phase_tex_str():
basic_phase_gate = _basics.BasicPhaseGate(0.5)
assert basic_phase_gate.tex_str() == "BasicPhaseGate$_{0.5}$"


@pytest.mark.parametrize("input_angle, inverse_angle",
[(2.0, -2.0 + 2 * math.pi), (-0.5, 0.5), (0.0, 0)])
def test_basic_phase_gate_get_inverse(input_angle, inverse_angle):
basic_phase_gate = _basics.BasicPhaseGate(input_angle)
inverse = basic_phase_gate.get_inverse()
assert isinstance(inverse, _basics.BasicPhaseGate)
assert inverse._angle == pytest.approx(inverse_angle)


def test_basic_phase_gate_get_merged():
basic_gate = _basics.BasicGate()
basic_phase_gate1 = _basics.BasicPhaseGate(0.5)
basic_phase_gate2 = _basics.BasicPhaseGate(1.0)
basic_phase_gate3 = _basics.BasicPhaseGate(1.5)
with pytest.raises(_basics.NotMergeable):
basic_phase_gate1.get_merged(basic_gate)
merged_gate = basic_phase_gate1.get_merged(basic_phase_gate2)
assert merged_gate == basic_phase_gate3


def test_basic_phase_gate_comparison():
basic_phase_gate1 = _basics.BasicPhaseGate(0.5)
basic_phase_gate2 = _basics.BasicPhaseGate(0.5)
basic_phase_gate3 = _basics.BasicPhaseGate(0.5 + 2 * math.pi)
assert basic_phase_gate1 == basic_phase_gate2
assert basic_phase_gate1 == basic_phase_gate3
basic_phase_gate4 = _basics.BasicPhaseGate(0.50000001)
# Test __ne__:
assert basic_phase_gate4 != basic_phase_gate1
# Test one gate close to 2*pi the other one close to 0
basic_phase_gate5 = _basics.BasicPhaseGate(1.e-13)
basic_phase_gate6 = _basics.BasicPhaseGate(2 * math.pi - 1.e-13)
assert basic_phase_gate5 == basic_phase_gate6
# Test different types of gates
basic_gate = _basics.BasicGate()
assert not basic_gate == basic_phase_gate6
assert basic_phase_gate2 != _basics.BasicPhaseGate(0.5 + math.pi)


def test_basic_math_gate():
def my_math_function(a, b, c):
return (a, b, c + a * b)
Expand Down
5 changes: 3 additions & 2 deletions projectq/ops/_gates.py
Expand Up @@ -37,6 +37,7 @@
from ._basics import (BasicGate,
SelfInverseGate,
BasicRotationGate,
BasicPhaseGate,
ClassicalInstructionGate,
FastForwardingGate,
BasicMathGate)
Expand Down Expand Up @@ -147,7 +148,7 @@ def __str__(self):
Entangle = EntangleGate()


class Ph(BasicRotationGate):
class Ph(BasicPhaseGate):
""" Phase gate (global phase) """
@property
def matrix(self):
Expand Down Expand Up @@ -183,7 +184,7 @@ def matrix(self):
[0, cmath.exp(.5 * 1j * self._angle)]])


class R(BasicRotationGate):
class R(BasicPhaseGate):
""" Phase-shift gate (equivalent to Rz up to a global phase) """
@property
def matrix(self):
Expand Down
25 changes: 25 additions & 0 deletions projectq/ops/_gates_test.py
Expand Up @@ -131,6 +131,31 @@ def test_rz(angle):
assert np.allclose(gate.matrix, expected_matrix)


@pytest.mark.parametrize("angle", [0, 0.2, 2.1, 4.1, 2 * math.pi])
def test_ph(angle):
gate = _gates.Ph(angle)
gate2 = _gates.Ph(angle + 2 * math.pi)
expected_matrix = np.matrix([[cmath.exp(1j * angle), 0],
[0, cmath.exp(1j * angle)]])
assert gate.matrix.shape == expected_matrix.shape
assert np.allclose(gate.matrix, expected_matrix)
assert gate2.matrix.shape == expected_matrix.shape
assert np.allclose(gate2.matrix, expected_matrix)
assert gate == gate2


@pytest.mark.parametrize("angle", [0, 0.2, 2.1, 4.1, 2 * math.pi])
def test_r(angle):
gate = _gates.R(angle)
gate2 = _gates.R(angle + 2 * math.pi)
expected_matrix = np.matrix([[1, 0], [0, cmath.exp(1j * angle)]])
assert gate.matrix.shape == expected_matrix.shape
assert np.allclose(gate.matrix, expected_matrix)
assert gate2.matrix.shape == expected_matrix.shape
assert np.allclose(gate2.matrix, expected_matrix)
assert gate == gate2


def test_flush_gate():
gate = _gates.FlushGate()
assert str(gate) == ""
Expand Down
4 changes: 2 additions & 2 deletions projectq/ops/_time_evolution.py
Expand Up @@ -37,7 +37,7 @@ class TimeEvolution(BasicGate):
Example:
.. code-block:: python
wavefuction = eng.allocate_qureg(5)
wavefunction = eng.allocate_qureg(5)
hamiltonian = 0.5 * QubitOperator("X0 Z1 Y5")
# Apply exp(-i * H * t) to the wavefunction:
TimeEvolution(time=2.0, hamiltonian=hamiltonian) | wavefunction
Expand Down Expand Up @@ -163,7 +163,7 @@ def __or__(self, qubits):
While in the above example the TimeEvolution gate is applied to 5
qubits, the hamiltonian of this TimeEvolution gate acts only
non-trivially on the two qubits wavefuction[1] and wavefunction[3].
non-trivially on the two qubits wavefunction[1] and wavefunction[3].
Therefore, the operator| will rescale the indices in the hamiltonian
and sends the equivalent of the following new gate to the MainEngine:
Expand Down

0 comments on commit 3b40534

Please sign in to comment.