Skip to content

Implement ControlledGate #430

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

Merged
merged 4 commits into from
May 29, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 2 additions & 0 deletions cirq/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
CNOT,
CNotGate,
CompositeGate,
ControlledGate,
CZ,
EigenGate,
ExtrapolatableGate,
Expand All @@ -103,6 +104,7 @@
QubitOrderOrList,
ReversibleCompositeGate,
ReversibleGate,
ParameterizableGate,
PhaseableGate,
QubitId,
Rot11Gate,
Expand Down
2 changes: 1 addition & 1 deletion cirq/extension/extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ def try_cast(self,
return actual_value

if isinstance(actual_value, PotentialImplementation):
return actual_value.try_cast_to(desired_type)
return actual_value.try_cast_to(desired_type, self)

return None

Expand Down
2 changes: 1 addition & 1 deletion cirq/extension/extensions_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ class PotentialOther(PotentialImplementation):
def __init__(self, is_other):
self.is_other = is_other

def try_cast_to(self, desired_type):
def try_cast_to(self, desired_type, ext):
if desired_type is OtherType and self.is_other:
return OtherType()
return None
Expand Down
14 changes: 12 additions & 2 deletions cirq/extension/potential_implementation.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@
# limitations under the License.


from typing import Optional, Type, TypeVar
from typing import Optional, Type, TypeVar, TYPE_CHECKING

if TYPE_CHECKING:
# pylint: disable=unused-import
import cirq.extension.extensions

T_DESIRED = TypeVar('T_DESIRED')

Expand All @@ -25,7 +29,9 @@ class PotentialImplementation:
Extensions uses this as a fallback when trying to cast to a desired type.
"""

def try_cast_to(self, desired_type: Type[T_DESIRED]
def try_cast_to(self,
desired_type: Type[T_DESIRED],
extensions: 'cirq.extension.extensions.Extensions'
) -> Optional[T_DESIRED]:
"""Turns this value into the desired type, if possible.

Expand All @@ -34,6 +40,10 @@ def try_cast_to(self, desired_type: Type[T_DESIRED]

Args:
desired_type: The type of thing that the caller wants to use.
extensions: The extensions instance that is asking us to try to
cast ourselves into something as part of its try_cast method.
If we need to recursively cast some of our fields in order to
cast ourselves, this is the extensions instance we should use.

Returns:
None if the receiving instance doesn't recognize or can't implement
Expand Down
12 changes: 6 additions & 6 deletions cirq/google/xmon_gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,10 +143,10 @@ def to_proto(self, *qubits):
self.parameterized_value_to_proto(self.half_turns, op.exp_11.half_turns)
return op

def try_cast_to(self, desired_type):
def try_cast_to(self, desired_type, ext):
if desired_type is ops.KnownMatrixGate and self.has_matrix():
return self
return super().try_cast_to(desired_type)
return super().try_cast_to(desired_type, ext)

def has_matrix(self):
return not isinstance(self.half_turns, Symbol)
Expand Down Expand Up @@ -237,12 +237,12 @@ def to_proto(self, *qubits):
self.parameterized_value_to_proto(self.half_turns, op.exp_w.half_turns)
return op

def try_cast_to(self, desired_type):
def try_cast_to(self, desired_type, ext):
if desired_type is ops.KnownMatrixGate and self.has_matrix():
return self
if desired_type is ops.ReversibleGate and self.has_inverse():
return self
return super().try_cast_to(desired_type)
return super().try_cast_to(desired_type, ext)

def has_inverse(self):
return not isinstance(self.half_turns, Symbol)
Expand Down Expand Up @@ -363,12 +363,12 @@ def text_diagram_exponent(self):
return -1
return self.half_turns

def try_cast_to(self, desired_type):
def try_cast_to(self, desired_type, ext):
if desired_type is ops.KnownMatrixGate and self.has_matrix():
return self
if desired_type is ops.ReversibleGate and self.has_inverse():
return self
return super().try_cast_to(desired_type)
return super().try_cast_to(desired_type, ext)

def has_inverse(self):
return not isinstance(self.half_turns, Symbol)
Expand Down
12 changes: 8 additions & 4 deletions cirq/google/xmon_gates_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,9 +264,13 @@ def test_w_to_proto():

def test_w_potential_implementation():
ex = Extensions()
assert not ex.can_cast(ExpWGate(half_turns=Symbol('a')),
KnownMatrixGate)
assert not ex.can_cast(ExpWGate(half_turns=Symbol('a')),
ReversibleGate)
assert not ex.can_cast(ExpWGate(half_turns=Symbol('a')), KnownMatrixGate)
assert not ex.can_cast(ExpWGate(half_turns=Symbol('a')), ReversibleGate)
assert ex.can_cast(ExpWGate(), KnownMatrixGate)
assert ex.can_cast(ExpWGate(), ReversibleGate)


def test_cz_potential_implementation():
ex = Extensions()
assert not ex.can_cast(Exp11Gate(half_turns=Symbol('a')), KnownMatrixGate)
assert ex.can_cast(Exp11Gate(), KnownMatrixGate)
4 changes: 4 additions & 0 deletions cirq/ops/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@
Y,
Z,
)
from cirq.ops.controlled_gate import (
ControlledGate,
)
from cirq.ops.eigen_gate import (
EigenGate,
)
Expand All @@ -42,6 +45,7 @@
CompositeGate,
ExtrapolatableGate,
KnownMatrixGate,
ParameterizableGate,
PhaseableGate,
ReversibleGate,
SelfInverseGate,
Expand Down
22 changes: 12 additions & 10 deletions cirq/ops/common_gates_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,12 +148,14 @@ def test_x_matrix():

def test_runtime_types_of_rot_gates():
for gate_type in [ops.Rot11Gate, ops.RotXGate, ops.RotYGate, ops.RotZGate]:
ext = cirq.Extensions()

p = gate_type(half_turns=Symbol('a'))
assert p.try_cast_to(ops.KnownMatrixGate) is None
assert p.try_cast_to(ops.ExtrapolatableGate) is None
assert p.try_cast_to(ops.ReversibleGate) is None
assert p.try_cast_to(ops.SelfInverseGate) is None
assert p.try_cast_to(ops.BoundedEffectGate) is p
assert p.try_cast_to(ops.KnownMatrixGate, ext) is None
assert p.try_cast_to(ops.ExtrapolatableGate, ext) is None
assert p.try_cast_to(ops.ReversibleGate, ext) is None
assert p.try_cast_to(ops.SelfInverseGate, ext) is None
assert p.try_cast_to(ops.BoundedEffectGate, ext) is p
with pytest.raises(ValueError):
_ = p.matrix()
with pytest.raises(ValueError):
Expand All @@ -162,16 +164,16 @@ def test_runtime_types_of_rot_gates():
_ = p.inverse()

c = gate_type(half_turns=0.5)
assert c.try_cast_to(ops.KnownMatrixGate) is c
assert c.try_cast_to(ops.ExtrapolatableGate) is c
assert c.try_cast_to(ops.ReversibleGate) is c
assert c.try_cast_to(ops.BoundedEffectGate) is c
assert c.try_cast_to(ops.KnownMatrixGate, ext) is c
assert c.try_cast_to(ops.ExtrapolatableGate, ext) is c
assert c.try_cast_to(ops.ReversibleGate, ext) is c
assert c.try_cast_to(ops.BoundedEffectGate, ext) is c
assert c.matrix() is not None
assert c.extrapolate_effect(2) is not None
assert c.inverse() is not None

c = gate_type(half_turns=1)
assert c.try_cast_to(ops.SelfInverseGate) is c
assert c.try_cast_to(ops.SelfInverseGate, ext) is c


def test_measurement_eq():
Expand Down
137 changes: 137 additions & 0 deletions cirq/ops/controlled_gate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# Copyright 2018 The Cirq Developers
#
# 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
#
# https://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.

from typing import Optional, TypeVar, Type

import numpy as np

from cirq import linalg, extension
from cirq.ops import gate_features
from cirq.ops import raw_types

T_DESIRED = TypeVar('T_DESIRED')

POTENTIALLY_EXPOSED_SUB_TYPES = (
gate_features.BoundedEffectGate,
gate_features.ExtrapolatableGate,
gate_features.KnownMatrixGate,
gate_features.ParameterizableGate,
gate_features.ReversibleGate,
gate_features.TextDiagrammableGate,
)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are these particular subclasses in this list?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm I think I see ... these are the ones that you implemented methods for.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct



class ControlledGate(raw_types.Gate, extension.PotentialImplementation):
"""Augments existing gates with a control qubit."""

def __init__(self,
sub_gate: raw_types.Gate,
default_extensions: Optional[extension.Extensions] = None
) -> None:
"""Initializes the controlled gate.

Args:
sub_gate: The gate to add a control qubit to.
default_extensions: The extensions method that should be used when
determining if the controlled gate supports certain gate
features. For example, if this extensions instance is able to
cast sub_gate to a KnownMatrixGate then the controlled gate
can also be cast to a KnownMatrixGate. When this value is None,
an empty extensions instance is used instead.
"""
self.sub_gate = sub_gate
self.default_extensions = default_extensions

def validate_args(self, qubits) -> None:
if len(qubits) < 1:
raise ValueError('No control qubit specified.')
self.sub_gate.validate_args(qubits[1:])

def __eq__(self, other):
if not isinstance(other, type(self)):
return NotImplemented
return self.sub_gate == other.sub_gate

def __ne__(self, other):
return not self == other

def __hash__(self):
return hash((ControlledGate, self.sub_gate))

def _cast_sub_gate(self, desired_type: Type[T_DESIRED]) -> T_DESIRED:
ext = self.default_extensions or extension.Extensions()
cast_sub_gate = ext.try_cast(self.sub_gate, desired_type)
if cast_sub_gate is None:
raise TypeError('sub_gate is not a {}', desired_type)
return cast_sub_gate

def try_cast_to(self, desired_type, ext):
if desired_type in POTENTIALLY_EXPOSED_SUB_TYPES:
cast_sub_gate = ext.try_cast(self.sub_gate, desired_type)
if cast_sub_gate is None:
return None
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not raise a TypeError, as you do in _cast_sub_gate?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because this is a try_cast method, not a cast method.

This method is inherited from PotentialImplementation and so must satisfy that contract as opposed to one that may be more convenient to this specific class.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I guess this is just following the docstring of try_cast_to.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I see.

return ControlledGate(cast_sub_gate, ext)
return super().try_cast_to(desired_type, ext)

def matrix(self) -> np.ndarray:
cast_sub_gate = self._cast_sub_gate(gate_features.KnownMatrixGate)
sub_matrix = cast_sub_gate.matrix()
return linalg.block_diag(np.eye(sub_matrix.shape[0]), sub_matrix)

def extrapolate_effect(self, factor) -> 'ControlledGate':
cast_sub_gate = self._cast_sub_gate(gate_features.ExtrapolatableGate)
new_sub_gate = cast_sub_gate.extrapolate_effect(factor)
return ControlledGate(new_sub_gate, self.default_extensions)

def __pow__(self, power: float) -> 'ControlledGate':
return self.extrapolate_effect(power)

def inverse(self) -> 'ControlledGate':
cast_sub_gate = self._cast_sub_gate(gate_features.ReversibleGate)
return ControlledGate(cast_sub_gate.inverse(), self.default_extensions)

def is_parameterized(self) -> bool:
cast_sub_gate = self._cast_sub_gate(gate_features.ParameterizableGate)
return cast_sub_gate.is_parameterized()

def with_parameters_resolved_by(self, param_resolver) -> 'ControlledGate':
cast_sub_gate = self._cast_sub_gate(gate_features.ParameterizableGate)
new_sub_gate = cast_sub_gate.with_parameters_resolved_by(
param_resolver)
return ControlledGate(new_sub_gate, self.default_extensions)

def text_diagram_exponent(self):
cast_sub_gate = self._cast_sub_gate(gate_features.TextDiagrammableGate)
return cast_sub_gate.text_diagram_exponent()

def trace_distance_bound(self):
cast_sub_gate = self._cast_sub_gate(gate_features.BoundedEffectGate)
return cast_sub_gate.trace_distance_bound()

def text_diagram_wire_symbols(self,
qubit_count=None,
use_unicode_characters=True,
precision=3):
cast_sub_gate = self._cast_sub_gate(gate_features.TextDiagrammableGate)
sub_symbols = cast_sub_gate.text_diagram_wire_symbols(
qubit_count,
use_unicode_characters,
precision)
return ('@',) + sub_symbols

def __str__(self):
return 'C' + str(self.sub_gate)

def __repr__(self):
return 'ControlledGate(sub_gate={!r})'.format(self.sub_gate)
Loading