Repository: quantumlib/Cirq

Issue:
Calling with_parameters_resolved_by on a  ParameterizableGate that is not parameterized should just return self, not produce an error
When all values are constants, resolving parameters is a no-op not an error.

My bad for missing this in the initial code review.


Discussion:

Resolving patch:
diff --git a/cirq/google/xmon_gates.py b/cirq/google/xmon_gates.py
--- a/cirq/google/xmon_gates.py
+++ b/cirq/google/xmon_gates.py
@@ -111,6 +111,7 @@ class Exp11Gate(XmonGate,
                 ops.TextDiagrammableGate,
                 ops.InterchangeableQubitsGate,
                 ops.PhaseableGate,
+                ops.ParameterizableGate,
                 PotentialImplementation):
     """A two-qubit interaction that phases the amplitude of the 11 state.
 
@@ -160,7 +161,7 @@ def text_diagram_wire_symbols(self,
                                   qubit_count=None,
                                   use_unicode_characters=True,
                                   precision=3):
-        return 'Z', 'Z'
+        return '@', 'Z'
 
     def text_diagram_exponent(self):
         return self.half_turns
@@ -183,7 +184,13 @@ def __ne__(self, other):
         return not self == other
 
     def __hash__(self):
-        return hash((ops.Rot11Gate, self.half_turns))
+        return hash((Exp11Gate, self.half_turns))
+
+    def is_parameterized(self) -> bool:
+        return isinstance(self.half_turns, Symbol)
+
+    def with_parameters_resolved_by(self, param_resolver) -> 'Exp11Gate':
+        return Exp11Gate(half_turns=param_resolver.value_of(self.half_turns))
 
 
 class ExpWGate(XmonGate,
@@ -191,6 +198,7 @@ class ExpWGate(XmonGate,
                ops.TextDiagrammableGate,
                ops.PhaseableGate,
                ops.BoundedEffectGate,
+               ops.ParameterizableGate,
                PotentialImplementation):
     """A rotation around an axis in the XY plane of the Bloch sphere.
 
@@ -325,10 +333,20 @@ def __hash__(self):
             return hash((ops.RotYGate, self.half_turns))
         return hash((ExpWGate, self.half_turns, self.axis_half_turns))
 
+    def is_parameterized(self) -> bool:
+        return (isinstance(self.half_turns, Symbol) or
+                isinstance(self.axis_half_turns, Symbol))
+
+    def with_parameters_resolved_by(self, param_resolver) -> 'ExpWGate':
+        return ExpWGate(
+                half_turns=param_resolver.value_of(self.half_turns),
+                axis_half_turns=param_resolver.value_of(self.axis_half_turns))
+
 
 class ExpZGate(XmonGate,
                ops.SingleQubitGate,
                ops.TextDiagrammableGate,
+               ops.ParameterizableGate,
                PotentialImplementation):
     """A rotation around the Z axis of the Bloch sphere.
 
@@ -384,7 +402,7 @@ def has_matrix(self):
     def matrix(self):
         if not self.has_matrix():
             raise ValueError("Don't have a known matrix.")
-        return ops.RotZGate(half_turns=self.half_turns).matrix()
+        return np.diag([(-1j)**self.half_turns, 1j**self.half_turns])
 
     def trace_distance_bound(self):
         if isinstance(self.half_turns, Symbol):
@@ -427,6 +445,12 @@ def __ne__(self, other):
     def __hash__(self):
         return hash((ExpZGate, self.half_turns))
 
+    def is_parameterized(self) -> bool:
+        return isinstance(self.half_turns, Symbol)
+
+    def with_parameters_resolved_by(self, param_resolver) -> 'ExpZGate':
+        return ExpZGate(half_turns=param_resolver.value_of(self.half_turns))
+
 
 def _canonicalize_half_turns(
         half_turns: Union[Symbol, float]
diff --git a/cirq/ops/gate_features.py b/cirq/ops/gate_features.py
--- a/cirq/ops/gate_features.py
+++ b/cirq/ops/gate_features.py
@@ -258,7 +258,4 @@ def with_parameters_resolved_by(self,
 
         Returns a gate of the same type, but with all Symbols replaced with
         floats according to the given ParamResolver.
-
-        Raises:
-            ValueError: The gate has no parameters to resolve.
         """
diff --git a/cirq/ops/partial_reflection_gate.py b/cirq/ops/partial_reflection_gate.py
--- a/cirq/ops/partial_reflection_gate.py
+++ b/cirq/ops/partial_reflection_gate.py
@@ -138,7 +138,5 @@ def is_parameterized(self) -> bool:
 
     def with_parameters_resolved_by(self,
                                     param_resolver) -> 'PartialReflectionGate':
-        if not self.is_parameterized():
-            raise ValueError("Gate does not have any parameters to resolve.")
         return self._with_half_turns(
                 half_turns=param_resolver.value_of(self.half_turns))


Test patch:
diff --git a/cirq/google/xmon_gates_test.py b/cirq/google/xmon_gates_test.py
--- a/cirq/google/xmon_gates_test.py
+++ b/cirq/google/xmon_gates_test.py
@@ -12,6 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 import pytest
+import numpy as np
 from google.protobuf import message, text_format
 
 from cirq.api.google.v1 import operations_pb2
@@ -20,6 +21,7 @@
     XmonGate, XmonQubit, XmonMeasurementGate, ExpZGate, Exp11Gate, ExpWGate,
 )
 from cirq.ops import KnownMatrixGate, ReversibleGate
+from cirq.study import ParamResolver
 from cirq.value import Symbol
 from cirq.testing import EqualsTester
 
@@ -127,6 +129,27 @@ def test_z_to_proto():
         """)
 
 
+def test_z_matrix():
+    assert np.allclose(ExpZGate(half_turns=1).matrix(),
+                       np.array([[-1j, 0], [0, 1j]]))
+    assert np.allclose(ExpZGate(half_turns=0.5).matrix(),
+                       np.array([[1 - 1j, 0], [0, 1 + 1j]]) / np.sqrt(2))
+    assert np.allclose(ExpZGate(half_turns=0).matrix(),
+                       np.array([[1, 0], [0, 1]]))
+    assert np.allclose(ExpZGate(half_turns=-0.5).matrix(),
+                       np.array([[1 + 1j, 0], [0, 1 - 1j]]) / np.sqrt(2))
+
+
+def test_z_parameterize():
+    parameterized_gate = ExpZGate(half_turns=Symbol('a'))
+    assert parameterized_gate.is_parameterized()
+    with pytest.raises(ValueError):
+        _ = parameterized_gate.matrix()
+    resolver = ParamResolver({'a': 0.1})
+    resolved_gate = parameterized_gate.with_parameters_resolved_by(resolver)
+    assert resolved_gate == ExpZGate(half_turns=0.1)
+
+
 def test_cz_eq():
     eq = EqualsTester()
     eq.make_equality_pair(lambda: Exp11Gate(half_turns=0))
@@ -179,6 +202,22 @@ def test_cz_to_proto():
         """)
 
 
+def test_cz_potential_implementation():
+    ex = Extensions()
+    assert not ex.can_cast(Exp11Gate(half_turns=Symbol('a')), KnownMatrixGate)
+    assert ex.can_cast(Exp11Gate(), KnownMatrixGate)
+
+
+def test_cz_parameterize():
+    parameterized_gate = Exp11Gate(half_turns=Symbol('a'))
+    assert parameterized_gate.is_parameterized()
+    with pytest.raises(ValueError):
+        _ = parameterized_gate.matrix()
+    resolver = ParamResolver({'a': 0.1})
+    resolved_gate = parameterized_gate.with_parameters_resolved_by(resolver)
+    assert resolved_gate == Exp11Gate(half_turns=0.1)
+
+
 def test_w_eq():
     eq = EqualsTester()
     eq.add_equality_group(ExpWGate(),
@@ -270,7 +309,12 @@ def test_w_potential_implementation():
     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)
+def test_w_parameterize():
+    parameterized_gate = ExpWGate(half_turns=Symbol('a'),
+                                  axis_half_turns=Symbol('b'))
+    assert parameterized_gate.is_parameterized()
+    with pytest.raises(ValueError):
+        _ = parameterized_gate.matrix()
+    resolver = ParamResolver({'a': 0.1, 'b': 0.2})
+    resolved_gate = parameterized_gate.with_parameters_resolved_by(resolver)
+    assert resolved_gate == ExpWGate(half_turns=0.1, axis_half_turns=0.2)
diff --git a/cirq/ops/partial_reflection_gate_test.py b/cirq/ops/partial_reflection_gate_test.py
--- a/cirq/ops/partial_reflection_gate_test.py
+++ b/cirq/ops/partial_reflection_gate_test.py
@@ -15,7 +15,6 @@
 from typing import Union
 
 import numpy as np
-import pytest
 
 import cirq
 from cirq.ops.partial_reflection_gate import PartialReflectionGate
@@ -84,5 +83,3 @@ def test_partial_reflection_gate_with_parameters_resolved_by():
     resolver = ParamResolver({'a': 0.1})
     resolved_gate = gate.with_parameters_resolved_by(resolver)
     assert resolved_gate.half_turns == 0.1
-    with pytest.raises(ValueError):
-        _ = resolved_gate.with_parameters_resolved_by(resolver)