Skip to content

Commit e21932a

Browse files
authored
Creates a Composite gate helper and pipes extension to simulator.
Also fixes or adds documentation.
1 parent 21e4f07 commit e21932a

File tree

7 files changed

+171
-20
lines changed

7 files changed

+171
-20
lines changed

cirq/docs/gates.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,8 @@ One thing about ``CompositeGates`` is that sometimes you want
100100
to modify the decomposition. Algorithms that allow this can
101101
take an ``Extension`` which allows for overriding the
102102
``CompositeGate``. An example of this is for in
103-
``Simulators`` where an optional
103+
``Simulators`` where an optional extension can be supplied
104+
that can be used to override the CompositeGate.
104105

105106
#### TextDiagrammableGate
106107

@@ -135,7 +136,7 @@ out of the circuit (TODO: explain this in more detail)
135136

136137
**Exp11Gate** This is a two qubit gate and is a rotation
137138
about the ``|11><11|`` projector. It takes a single parameter
138-
``half_turns`` and is the gate ``exp(i pi 11 half_turns)``.
139+
``half_turns`` and is the gate ``exp(i pi |11><11| half_turns)``.
139140

140141
**XmonMeasurementGate** This is a single qubit measurement
141142
in the computational basis.

cirq/docs/simulation.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,8 @@ for getting it to work with the simulator:
138138
* Define it directly as an ``XmonGate``.
139139
* Provide a ``CompositeGate`` made up of ``XmonGates``.
140140
* Supply an ``Exentension`` to the simulator which converts
141-
the gate to an ``XmonGate`` (TODO(dabacon): not implemented yet).
141+
the gate to an ``XmonGate`` or to a ``CompositeGate`` which
142+
itself can be decomposed in ``XmonGates``.
142143

143144
### Parameterized Values and Studies
144145

cirq/google/sim/xmon_simulator.py

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@
3434

3535
from cirq.circuits import Circuit
3636
from cirq.circuits.drop_empty_moments import DropEmptyMoments
37+
from cirq.extension import Extensions
3738
from cirq.google import xmon_gates
39+
from cirq.google import xmon_gate_ext
3840
from cirq.google.convert_to_xmon_gates import ConvertToXmonGates
3941
from cirq.google.sim.xmon_stepper import Stepper
4042
from cirq.ops import raw_types
@@ -119,6 +121,7 @@ def run(
119121
options: Options = None,
120122
qubits: Sequence[raw_types.QubitId] = None,
121123
initial_state: Union[int, np.ndarray] = 0,
124+
extensions : Extensions = None,
122125
) -> SimulatorTrialResult:
123126
"""Simulates the entire supplied Circuit.
124127
@@ -135,12 +138,16 @@ def run(
135138
Otherwise if this is a np.ndarray it is the full initial
136139
state. In this case it must be the correct size, be normalized
137140
(an L2 norm of 1), and have a dtype of np.complex64.
141+
extensions: Extensions that will be applied while trying to
142+
decompose the circuit's gates into XmonGates. If None, this uses
143+
the default of xmon_gate_ext.
138144
139145
Returns:
140146
Results for this run.
141147
"""
142148
return self.run_sweep(circuit, [param_resolver], repetitions, options,
143-
qubits, initial_state)[0]
149+
qubits, initial_state,
150+
extensions or xmon_gate_ext)[0]
144151

145152
def run_sweep(
146153
self,
@@ -150,6 +157,7 @@ def run_sweep(
150157
options: Options = None,
151158
qubits: Sequence[raw_types.QubitId] = None,
152159
initial_state: Union[int, np.ndarray] = 0,
160+
extensions: Extensions = None
153161
) -> List[SimulatorTrialResult]:
154162
"""Simulates the entire supplied Circuit.
155163
@@ -166,6 +174,9 @@ def run_sweep(
166174
Otherwise if this is a np.ndarray it is the full initial state.
167175
In this case it must be the correct size, be normalized (an L2
168176
norm of 1), and have a dtype of np.complex64.
177+
extensions: Extensions that will be applied while trying to
178+
decompose the circuit's gates into XmonGates. If None, this uses
179+
the default of xmon_gate_ext.
169180
170181
Returns:
171182
List of trial results for this run, one for each possible parameter
@@ -175,18 +186,20 @@ def run_sweep(
175186
Circuit) else program.to_circuit()
176187
param_resolvers = self._to_resolvers(params or ParamResolver({}))
177188

178-
xmon_circuit, keys = self._to_xmon_circuit(circuit)
189+
xmon_circuit, keys = self._to_xmon_circuit(circuit,
190+
extensions or xmon_gate_ext)
179191
trial_results = [] # type: List[SimulatorTrialResult]
180192
for param_resolver in param_resolvers:
181193
measurements = {
182194
k: [] for k in keys} # type: Dict[str, List[np.ndarray]]
183195
final_states = [] # type: List[np.ndarray]
184196
for _ in range(repetitions):
185-
all_step_results = simulator_iterator(xmon_circuit,
186-
options or Options(),
187-
qubits,
188-
initial_state,
189-
param_resolver)
197+
all_step_results = simulator_iterator(
198+
xmon_circuit,
199+
options or Options(),
200+
qubits,
201+
initial_state,
202+
param_resolver)
190203
for step_result in all_step_results:
191204
for k, v in step_result.measurements.items():
192205
measurements[k].append(np.array(v, dtype=bool))
@@ -216,7 +229,8 @@ def moment_steps(
216229
options: 'Options' = None,
217230
qubits: Sequence[raw_types.QubitId] = None,
218231
initial_state: Union[int, np.ndarray]=0,
219-
param_resolver: ParamResolver = None) -> Iterator['StepResult']:
232+
param_resolver: ParamResolver = None,
233+
extensions: Extensions = None) -> Iterator['StepResult']:
220234
"""Returns an iterator of XmonStepResults for each moment simulated.
221235
222236
Args:
@@ -232,20 +246,25 @@ def moment_steps(
232246
norm of 1), and have a dtype of np.complex64.
233247
param_resolver: A ParamResolver for determining values of
234248
Symbols.
249+
extensions: Extensions that will be applied while trying to
250+
decompose the circuit's gates into XmonGates. If None, this
251+
uses the default of xmon_gate_ext.
235252
236253
Returns:
237254
SimulatorIterator that steps through the simulation, simulating
238255
each moment and returning a StepResult for each moment.
239256
"""
240257
param_resolver = param_resolver or ParamResolver({})
241-
xmon_circuit, _ = self._to_xmon_circuit(program)
258+
xmon_circuit, _ = self._to_xmon_circuit(program,
259+
extensions or xmon_gate_ext)
242260
return simulator_iterator(xmon_circuit, options or Options(), qubits,
243261
initial_state, param_resolver)
244262

245-
def _to_xmon_circuit(self, circuit: Circuit) -> Tuple[Circuit, Set[str]]:
263+
def _to_xmon_circuit(self, circuit: Circuit,
264+
extensions: Extensions = None) -> Tuple[Circuit, Set[str]]:
246265
# TODO: Use one optimization pass.
247266
xmon_circuit = Circuit(circuit.moments)
248-
ConvertToXmonGates().optimize_circuit(xmon_circuit)
267+
ConvertToXmonGates(extensions).optimize_circuit(xmon_circuit)
249268
DropEmptyMoments().optimize_circuit(xmon_circuit)
250269
keys = find_measurement_keys(xmon_circuit)
251270
return xmon_circuit, keys
@@ -256,7 +275,7 @@ def simulator_iterator(
256275
options: 'Options' = Options(),
257276
qubits: Sequence[raw_types.QubitId] = None,
258277
initial_state: Union[int, np.ndarray]=0,
259-
param_resolver: ParamResolver = ParamResolver({})
278+
param_resolver: ParamResolver = ParamResolver({}),
260279
) -> Iterator['StepResult']:
261280
"""Iterator over TrialResults from Moments of a Circuit.
262281

cirq/google/sim/xmon_simulator_test.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
from cirq.circuits import Circuit
2626
from cirq.devices import UnconstrainedDevice
27+
from cirq.extension import Extensions
2728
from cirq.google import (
2829
ExpWGate, ExpZGate, Exp11Gate, XmonMeasurementGate, XmonQubit,
2930
)
@@ -412,6 +413,27 @@ def test_unsupported_gate_composite(scheduler):
412413
_ = run(simulator, circuit, scheduler)
413414

414415

416+
@pytest.mark.parametrize('scheduler', SCHEDULERS)
417+
def test_extensions(scheduler):
418+
# We test that an extension is being applied, by created an incorrect
419+
# gate with an extension.
420+
421+
class WrongH(CompositeGate):
422+
def default_decompose(
423+
self, qubits: Sequence[raw_types.QubitId]) -> op_tree.OP_TREE:
424+
return X(Q1)
425+
426+
extensions = Extensions(desired_to_actual_to_wrapper=
427+
{CompositeGate: {H: lambda e: WrongH()}})
428+
429+
circuit = Circuit()
430+
circuit.append([WrongH()(Q1)])
431+
432+
simulator = xmon_simulator.Simulator()
433+
results = simulator.run(circuit, qubits=[Q1], extensions=extensions)
434+
np.testing.assert_almost_equal(results.final_states[0], np.array([0, -1j]))
435+
436+
415437
@pytest.mark.parametrize('scheduler', SCHEDULERS)
416438
def test_measurement_qubit_order(scheduler):
417439
circuit = Circuit()

cirq/google/xmon_gates.py

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,10 @@ def parameterized_value_to_proto(
8787

8888

8989
class XmonMeasurementGate(XmonGate, ops.MeasurementGate):
90-
"""Indicates that qubits should be measured, and where the result goes."""
90+
"""Indicates that qubits should be measured, and where the result goes.
91+
92+
This measurement is done in the computational basis.
93+
"""
9194

9295
def to_proto(self, *qubits):
9396
if len(qubits) == 0:
@@ -108,7 +111,17 @@ class Exp11Gate(XmonGate,
108111
ops.InterchangeableQubitsGate,
109112
ops.PhaseableGate,
110113
PotentialImplementation):
111-
"""A two-qubit interaction that phases the amplitude of the 11 state."""
114+
"""A two-qubit interaction that phases the amplitude of the 11 state.
115+
116+
This gate is exp(i * pi * |11><11| * half_turn).
117+
118+
Note that this half_turn parameter is such that a full turn is the
119+
identity matrix, in contrast to the single qubit gates, where a full
120+
turn is minus identity. The single qubit half-turn gates are defined
121+
so that a full turn corresponds to a rotation on the Bloch sphere of a
122+
360 degree rotation. For two qubit gates, there isn't a Bloch sphere,
123+
so the half_turn corresponds to half of a full rotation in U(4).
124+
"""
112125

113126
def __init__(self, *positional_args,
114127
half_turns: Union[Symbol, float]=1) -> None:
@@ -177,7 +190,23 @@ class ExpWGate(XmonGate,
177190
ops.PhaseableGate,
178191
ops.BoundedEffectGate,
179192
PotentialImplementation):
180-
"""A rotation around an axis in the XY plane of the Bloch sphere."""
193+
"""A rotation around an axis in the XY plane of the Bloch sphere.
194+
195+
This gate is exp(-i * pi * W(axis_half_turn) * half_turn / 2) where
196+
W(theta) = cos(pi * theta) X + sin(pi * theta) Y
197+
or in matrix form
198+
W(theta) = [[0, cos(pi * theta) - i sin(pi * theta)],
199+
[cos(pi * theta) + i sin(pi * theta), 0]]
200+
201+
Note the half_turn nomenclature here comes from viewing this as a rotation
202+
on the Bloch sphere. Two half_turns correspond to a rotation in the
203+
bloch sphere of 360 degrees. Note that this is minus identity, not
204+
just identity. Similarly the axis_half_turns refers thinking of rotating
205+
the Bloch operator, starting with the operator pointing along the X
206+
direction. An axis_half_turn of 1 corresponds to the operator pointing
207+
along the -X direction while an axis_half_turn of 0.5 correspond to
208+
an operator pointing along the Y direction.
209+
"""
181210

182211
def __init__(self, *positional_args,
183212
half_turns: Union[Symbol, float]=1,
@@ -294,7 +323,16 @@ class ExpZGate(XmonGate,
294323
ops.SingleQubitGate,
295324
ops.TextDiagrammableGate,
296325
PotentialImplementation):
297-
"""A rotation around the Z axis of the Bloch sphere."""
326+
"""A rotation around the Z axis of the Bloch sphere.
327+
328+
This gate is exp(-i * pi * Z * half_turns / 2) where Z is the Z matrix
329+
Z = [[1, 0],
330+
[0, -1]]
331+
332+
Note the half_turn nomenclature here comes from viewing this as a rotation
333+
on the Bloch sphere. Two half_turns correspond to a rotation in the
334+
bloch sphere of 360 degrees.
335+
"""
298336

299337
def __init__(self, *positional_args,
300338
half_turns: Union[Symbol, float]=1) -> None:

cirq/ops/gate_features.py

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
For example: some gates are reversible, some have known matrices, etc.
1818
"""
1919

20-
from typing import Sequence, Tuple, Optional
20+
from typing import Optional, Sequence, Tuple, Type, Union
2121

2222
import numpy as np
2323

@@ -96,6 +96,48 @@ def default_decompose(
9696
"""
9797

9898

99+
@classmethod
100+
def from_gates(cls: Type,
101+
gates: Union[Sequence[raw_types.Gate], Sequence[
102+
Tuple[raw_types.Gate, Tuple[int]]]]) -> 'CompositeGate':
103+
"""Returns a CompositeGate which decomposes into the given gates.
104+
105+
Args:
106+
gates: Either a sequence of gates or a sequences of (gate, indices)
107+
tuples, where indices is a tuple of qubit indices (ints). The
108+
first is used when decomposing a gate into a series of gates
109+
that all act on the same number of qubits. The second is used
110+
when decomposing a gate into a series of gates that may act on
111+
differing number of qubits. In this later case the indices
112+
is a tuple of qubit indices, describing which qubit the gate
113+
acts on.
114+
115+
Returns:
116+
A CompositeGate with a default_decompose that applies the
117+
given gates in sequence.
118+
"""
119+
return _CompositeGateImpl(gates)
120+
121+
122+
class _CompositeGateImpl(CompositeGate):
123+
"""Implementation of CompositeGate which uses specific sequence of gates."""
124+
125+
def __init__(self, gates: Union[Sequence[raw_types.Gate], Sequence[
126+
Tuple[raw_types.Gate, Tuple[int]]]]) -> None:
127+
self.gates = gates
128+
129+
def default_decompose(
130+
self, qubits: Sequence[raw_types.QubitId]) -> op_tree.OP_TREE:
131+
decomposition = []
132+
for x in self.gates:
133+
if isinstance(x, raw_types.Gate):
134+
decomposition.append(x(*qubits))
135+
else:
136+
gate, indices = x
137+
decomposition.append(gate(*map(qubits.__getitem__, indices)))
138+
return decomposition
139+
140+
99141
class KnownMatrixGate(raw_types.Gate, metaclass=abc.ABCMeta):
100142
"""A gate whose constant non-parameterized effect has a known matrix."""
101143

cirq/ops/gate_features_test.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,34 @@ def default_decompose(self, qubits):
105105
assert isinstance(Included(), gate_features.CompositeGate)
106106

107107

108+
def test_composite_gate_from_gates():
109+
class G1(raw_types.Gate):
110+
pass
111+
class G2(raw_types.Gate):
112+
pass
113+
114+
gates = [G1(), G2()]
115+
composite = gate_features.CompositeGate.from_gates(gates)
116+
117+
q1 = raw_types.QubitId()
118+
assert [gates[0](q1), gates[1](q1)] == composite.default_decompose([q1])
119+
120+
121+
def test_composite_gate_from_gate_tuples():
122+
class G1(raw_types.Gate):
123+
pass
124+
class G2(raw_types.Gate):
125+
pass
126+
127+
gates = [(G1(), (0,)), (G2(), (0, 1))]
128+
composite = gate_features.CompositeGate.from_gates(gates)
129+
130+
q1 = raw_types.QubitId()
131+
q2 = raw_types.QubitId()
132+
assert ([gates[0][0](q1), gates[1][0](q1, q2)]
133+
== composite.default_decompose([q1, q2]))
134+
135+
108136
def test_self_inverse_is_not_abstract():
109137
assert isinstance(gate_features.SelfInverseGate(),
110138
gate_features.ReversibleGate)

0 commit comments

Comments
 (0)