Beispiel #1
0
    def _scan_two_qubit_ops_into_matrix(
            self,
            circuit: circuits.Circuit,
            index: Optional[int],
            qubits: Tuple[ops.QubitId, ...]
    ) -> Tuple[List[ops.Operation], List[int], np.ndarray]:
        """Accumulates operations affecting the given pair of qubits.

        The scan terminates when it hits the end of the circuit, finds an
        operation without a known matrix, or finds an operation that interacts
        the given qubits with other qubits.

        Args:
            circuit: The circuit to scan for operations.
            index: The index to start scanning forward from.
            qubits: The pair of qubits we care about.

        Returns:
            A tuple containing:
                0. The operations.
                1. The moment indices those operations were on.
                2. A matrix equivalent to the effect of the scanned operations.
        """

        product = np.eye(4, dtype=np.complex128)
        all_operations = []
        touched_indices = []

        while index is not None:
            operations = list({circuit.operation_at(q, index) for q in qubits})
            op_data = [
                self._op_to_matrix(op, qubits)
                for op in operations
                if op is not None
            ]

            # Stop at any non-constant or non-local interaction.
            if any(e is None for e in op_data):
                break
            present_ops = [op for op in operations if op]
            present_op_data = cast(List[np.ndarray], op_data)

            for op_mat in present_op_data:
                product = np.dot(op_mat, product)
            all_operations.extend(present_ops)

            touched_indices.append(index)
            index = circuit.next_moment_operating_on(qubits, index + 1)

        return all_operations, touched_indices, product
Beispiel #2
0
def _sample_measurements(circuit: Circuit,
                         step_result: 'XmonStepResult',
                         repetitions: int) -> Dict[str, List]:
    """Sample from measurements in the given circuit.

    This should only be called if the circuit has only terminal measurements.

    Args:
        circuit: The circuit to sample from.
        step_result: The XmonStepResult from which to sample. This should be
            the step at the end of the circuit. Can be None if no steps were
            taken.
        repetitions: The number of time to sample.

    Returns:
        A dictionary from the measurement keys to the measurement results.
        These results are lists of lists, with the outer list corresponding to
        the repetition, and the inner list corresponding to the qubits as
        ordered in the measurement gate.
    """
    if step_result is None:
        return {}
    bounds = {}
    all_qubits = []  # type: List[raw_types.QubitId]
    current_index = 0
    for _, op, gate in circuit.findall_operations_with_gate_type(
            xmon_gates.XmonMeasurementGate):
        key = gate.key
        bounds[key] = (current_index, current_index + len(op.qubits))
        all_qubits.extend(op.qubits)
        current_index += len(op.qubits)
    sample = step_result.sample(all_qubits, repetitions)
    return {k: [x[s:e] for x in sample] for k,(s, e) in bounds.items()}
Beispiel #3
0
def optimized_for_xmon(
        circuit: circuits.Circuit,
        new_device: Optional[xmon_device.XmonDevice] = None,
        qubit_map: Callable[[ops.QubitId], devices.GridQubit] =
            lambda e: cast(devices.GridQubit, e),
        allow_partial_czs: bool = False,
) -> circuits.Circuit:
    """Optimizes a circuit with XmonDevice in mind.

    Starts by converting the circuit's operations to the xmon gate set, then
    begins merging interactions and rotations, ejecting pi-rotations and phasing
    operations, dropping unnecessary operations, and pushing operations earlier.

    Args:
        circuit: The circuit to optimize.
        new_device: The device the optimized circuit should be targeted at. If
            set to None, the circuit's current device is used.
        qubit_map: Transforms the qubits (e.g. so that they are GridQubits).
        allow_partial_czs: If true, the optimized circuit may contain partial CZ
            gates.  Otherwise all partial CZ gates will be converted to full CZ
            gates.  At worst, two CZ gates will be put in place of each partial
            CZ from the input.

    Returns:
        The optimized circuit.
    """
    copy = circuit.copy()
    opts = _OPTIMIZERS_PART_CZ if allow_partial_czs else _OPTIMIZERS
    for optimizer in opts:
        optimizer.optimize_circuit(copy)

    return circuits.Circuit.from_ops(
        (op.transform_qubits(qubit_map) for op in copy.all_operations()),
        strategy=circuits.InsertStrategy.EARLIEST,
        device=new_device or copy.device)
Beispiel #4
0
def move_non_clifford_into_clifford(circuit_left: Union[circuits.Circuit,
                                                        circuits.CircuitDag],
                                    circuit_right: circuits.Circuit
                                    ) -> circuits.Circuit:
    if isinstance(circuit_left, circuits.CircuitDag):
        string_dag = cast(circuits.CircuitDag, circuit_left)
    else:
        string_dag = pauli_string_dag_from_circuit(
                        cast(circuits.Circuit, circuit_left))
    output_ops = list(circuit_right.all_operations())

    rightmost_nodes = (set(string_dag.nodes)
                       - set(before for before, _ in string_dag.edges))

    def possible_string_placements(
            ) -> Iterator[Tuple[PauliStringGateOperation, int,
                                circuits.Unique[PauliStringGateOperation]]]:
        for right_node in rightmost_nodes:
            string_op = right_node.val
            # Try moving the Pauli string through, stop at measurements
            yield string_op, 0, right_node
            for i, out_op in enumerate(output_ops):
                if not set(out_op.qubits) & set(string_op.qubits):
                    # Skip if operations don't share qubits
                    continue
                if not (isinstance(out_op, ops.GateOperation) and
                        isinstance(out_op.gate, (ops.CliffordGate,
                                                 ops.PauliInteractionGate))):
                    # This is as far through as this Pauli string can move
                    break
                string_op = string_op.pass_operations_over([out_op],
                                                           after_to_before=True)
                yield string_op, i+1, right_node

    while rightmost_nodes:
        # Pick the Pauli string that can be moved furthest through the Clifford
        # circuit
        best_string_op, best_index, best_node = max(
            possible_string_placements(),
            key=lambda placement: (-len(placement[0].pauli_string),
                                   placement[1]))

        # Place the best one into the output circuit
        output_ops.insert(best_index, best_string_op)
        # Remove the best one from the dag and update rightmost_nodes
        rightmost_nodes.remove(best_node)
        rightmost_nodes.update(
            pred_node
            for pred_node in string_dag.predecessors(best_node)
            if len(string_dag.succ[pred_node]) <= 1)
        string_dag.remove_node(best_node)

    assert not string_dag.nodes, 'There was a cycle in the CircuitDag'

    return circuits.Circuit.from_ops(
            output_ops,
            strategy=circuits.InsertStrategy.EARLIEST,
            device=circuit_right.device)
Beispiel #5
0
    def to_circuit(self) -> Circuit:
        """Convert the schedule to a circuit.

        This discards most timing information from the schedule, but does place
        operations that are scheduled at the same time in the same Moment.
        """
        circuit = Circuit()
        ops = []  # type: List[Operation]
        time = None  # type: Optional[Timestamp]
        for so in self.scheduled_operations:
            if so.time != time:
                circuit.append(ops)
                ops = [so.operation]
                time = so.time
            else:
                ops.append(so.operation)
        circuit.append(ops)
        return circuit
Beispiel #6
0
def linearize_circuit_qubits(
        circuit: circuits.Circuit,
        qubit_order: ops.QubitOrderOrList = ops.QubitOrder.DEFAULT
        ) -> None:
    qubits = ops.QubitOrder.as_qubit_order(qubit_order).order_for(
        circuit.all_qubits())
    qubit_map = {q: line.LineQubit(i)
                 for i, q in enumerate(qubits)}
    QubitMapper(qubit_map.__getitem__).optimize_circuit(circuit)
Beispiel #7
0
def find_measurement_keys(circuit: Circuit) -> Set[str]:
    keys = set()  # type: Set[str]
    for _, _, gate in circuit.findall_operations_with_gate_type(
            xmon_gates.XmonMeasurementGate):
        key = gate.key
        if key in keys:
            raise ValueError('Repeated Measurement key {}'.format(key))
        keys.add(key)
    return keys
Beispiel #8
0
def circuit_to_quirk_url(circuit: circuits.Circuit,
                         prefer_unknown_gate_to_failure: bool=False,
                         escape_url=True) -> str:
    """Returns a Quirk URL for the given circuit.

    Args:
        circuit: The circuit to open in Quirk.
        prefer_unknown_gate_to_failure: If not set, gates that fail to convert
            will cause this function to raise an error. If set, a URL
            containing bad gates will be generated. (Quirk will open the URL,
            and replace the bad gates with parse errors, but still get the rest
            of the circuit.)
        escape_url: If set, the generated URL will have special characters such
            as quotes escaped using %. This makes it possible to paste the URL
            into forums and the command line and etc and have it properly
            parse. If not set, the generated URL will be more compact and human
            readable (and can still be pasted directly into a browser's address
            bar).

    Returns:

    """
    circuit = circuit.copy()
    linearize_circuit_qubits(circuit)

    cols = []  # Type: List[List[Any]]
    for moment in circuit:
        can_merges = []
        for op in moment.operations:
            for col, can_merge in _to_quirk_cols(
                    op,
                    prefer_unknown_gate_to_failure):
                if can_merge:
                    can_merges.append(col)
                else:
                    cols.append(col)
        if can_merges:
            merged_col = [1] * max(len(e) for e in can_merges)
            for col in can_merges:
                for i in range(len(col)):
                    if col[i] != 1:
                        merged_col[i] = col[i]
            cols.append(merged_col)

    circuit_json = json.JSONEncoder(ensure_ascii=False,
                                    separators=(',', ':'),
                                    sort_keys=True).encode({'cols': cols})
    if escape_url:
        suffix = urllib.parse.quote(circuit_json)
    else:
        suffix = circuit_json
    return 'http://algassert.com/quirk#circuit={}'.format(suffix)
Beispiel #9
0
    def _to_xmon_circuit(self, circuit: Circuit,
                         param_resolver: ParamResolver,
                         extensions: Extensions = None
                         ) -> Tuple[Circuit, Set[str]]:
        converter = ConvertToXmonGates(extensions)
        extensions = converter.extensions

        # TODO: Use one optimization pass.
        xmon_circuit = circuit.with_parameters_resolved_by(
                param_resolver, extensions)
        converter.optimize_circuit(xmon_circuit)
        DropEmptyMoments().optimize_circuit(xmon_circuit)
        keys = find_measurement_keys(xmon_circuit)
        return xmon_circuit, keys
Beispiel #10
0
    def _to_xmon_circuit(
            self,
            circuit: Circuit,
            param_resolver: ParamResolver,
            extensions: Extensions = None) -> Tuple[Circuit, Set[str]]:
        converter = ConvertToXmonGates(extensions)
        extensions = converter.extensions

        # TODO: Use one optimization pass.
        xmon_circuit = circuit.with_parameters_resolved_by(
            param_resolver, extensions)
        converter.optimize_circuit(xmon_circuit)
        DropEmptyMoments().optimize_circuit(xmon_circuit)
        keys = find_measurement_keys(xmon_circuit)
        return xmon_circuit, keys
Beispiel #11
0
 def _run_sweep_sample(self, circuit: circuits.Circuit,
                       repetitions: int) -> Dict[str, List[np.ndarray]]:
     for step_result in self._base_iterator(
             circuit=circuit,
             qubit_order=ops.QubitOrder.DEFAULT,
             initial_state=0,
             perform_measurements=False):
         pass
     # We can ignore the mixtures since this is a run method which
     # does not return the state.
     measurement_ops = [
         op for _, op, _ in circuit.findall_operations_with_gate_type(
             ops.MeasurementGate)
     ]
     return step_result.sample_measurement_ops(measurement_ops, repetitions)
Beispiel #12
0
def merge_single_qubit_gates_into_phxz(circuit: circuits.Circuit,
                                       atol: float = 1e-8) -> None:
    """Canonicalizes runs of single-qubit rotations in a circuit.

    Specifically, any run of non-parameterized single-qubit gates will be
    replaced by an optional PhasedXZ operation.

    Args:
        circuit: The circuit to rewrite. This value is mutated in-place.
        atol: Absolute tolerance to angle error. Larger values allow more
            negligible gates to be dropped, smaller values increase accuracy.
    """
    circuit._moments = [
        *transformers.merge_single_qubit_gates_to_phxz(circuit, atol=atol)
    ]
Beispiel #13
0
    def _run(self, circuit: circuits.Circuit,
             repetitions: int) -> Dict[str, np.ndarray]:

        measurements: Dict[str, List[int]] = {
            key: []
            for key in protocols.measurement_keys(circuit)
        }
        axes_map = {q: i for i, q in enumerate(circuit.all_qubits())}

        for _ in range(repetitions):
            state = ActOnCliffordTableauArgs(
                CliffordTableau(num_qubits=len(axes_map)),
                axes=(),
                prng=self._prng,
                log_of_measurement_results={},
            )
            for op in circuit.all_operations():
                state.axes = tuple(axes_map[q] for q in op.qubits)
                protocols.act_on(op, state)

            for k, v in state.log_of_measurement_results.items():
                measurements[k].append(v)

        return {k: np.array(v) for k, v in measurements.items()}
Beispiel #14
0
 def test_u3_decomposition(self):
     q_0 = cirq.NamedQubit("q_0")
     before = cirq.Circuit([QasmUGate(3 / 2, 1 / 2, 1).on(q_0)])
     after = cirq.Circuit([
         cirq.rz(np.pi).on(q_0),
         cirq.H.on(q_0),
         cirq.rz((3 * np.pi) / 2).on(q_0),
         cirq.H.on(q_0),
         cirq.rz(np.pi / 2).on(q_0)
     ])
     circuit = Circuit(
         decompose(before,
                   intercepting_decomposer=decompose_library,
                   keep=need_to_keep))
     self.assertEqual(circuit, after)
Beispiel #15
0
 def to_calibration_layer(self) -> CalibrationLayer:
     circuit = Circuit([self.gate.on(*pair) for pair in self.pairs])
     return CalibrationLayer(
         calibration_type=_FLOQUET_PHASED_FSIM_HANDLER_NAME,
         program=circuit,
         args={
             'est_theta': self.options.characterize_theta,
             'est_zeta': self.options.characterize_zeta,
             'est_chi': self.options.characterize_chi,
             'est_gamma': self.options.characterize_gamma,
             'est_phi': self.options.characterize_phi,
             # Experimental option that should always be set to True.
             'readout_corrections': True,
         },
     )
Beispiel #16
0
 def package_results(self, circuit: CirqCircuit,
                     q_bits: Sequence[Qubit]) -> List[BackendResult]:
     moments = self._simulator.simulate_moment_steps(
         circuit,
         qubit_order=ops.QubitOrder.as_qubit_order(
             ops.QubitOrder.DEFAULT).order_for(circuit.all_qubits()),
     )
     all_backres = [
         BackendResult(
             state=cast(CliffordSimulatorStepResult,
                        run).state.state_vector(),
             q_bits=q_bits,
         ) for run in moments
     ]
     return all_backres
Beispiel #17
0
 def _run_sweep_sample(self, circuit: circuits.Circuit,
                       repetitions: int) -> Dict[str, np.ndarray]:
     for step_result in self._base_iterator(
             circuit=circuit,
             qubit_order=ops.QubitOrder.DEFAULT,
             initial_state=0,
             all_measurements_are_terminal=True):
         pass
     measurement_ops = [
         op for _, op, _ in circuit.findall_operations_with_gate_type(
             ops.MeasurementGate)
     ]
     return step_result.sample_measurement_ops(measurement_ops,
                                               repetitions,
                                               seed=self._prng)
Beispiel #18
0
def parameterize_circuit(
    circuit: 'cirq.Circuit',
    options: XEBCharacterizationOptions,
) -> 'cirq.Circuit':
    """Parameterize PhasedFSim-like gates in a given circuit according to
    `phased_fsim_options`.
    """
    gate = options.get_parameterized_gate()
    return Circuit(
        ops.Moment(
            gate.on(*op.qubits) if options.should_parameterize(op) else op
            for op in moment.operations
        )
        for moment in circuit.moments
    )
Beispiel #19
0
    def _run(self, circuit: circuits.Circuit,
             param_resolver: study.ParamResolver,
             repetitions: int) -> Dict[str, List[np.ndarray]]:
        """See definition in `cirq.SimulatesSamples`."""
        param_resolver = param_resolver or study.ParamResolver({})
        resolved_circuit = protocols.resolve_parameters(
            circuit, param_resolver)
        self._check_all_resolved(resolved_circuit)

        def measure_or_mixture(op):
            return protocols.is_measurement(op) or protocols.has_mixture(op)

        if circuit.are_all_matches_terminal(measure_or_mixture):
            return self._run_sweep_sample(resolved_circuit, repetitions)
        return self._run_sweep_repeat(resolved_circuit, repetitions)
    def _base_iterator(
            self, circuit: circuits.Circuit, qubit_order: ops.QubitOrderOrList,
            initial_state: int
    ) -> Iterator['cirq.CliffordSimulatorStepResult']:
        """Iterator over CliffordSimulatorStepResult from Moments of a Circuit

        Args:
            circuit: The circuit to simulate.
            qubit_order: Determines the canonical ordering of the qubits. This
                is often used in specifying the initial state, i.e. the
                ordering of the computational basis states.
            initial_state: The initial state for the simulation.


        Yields:
            CliffordStepResult from simulating a Moment of the Circuit.
        """
        qubits = ops.QubitOrder.as_qubit_order(qubit_order).order_for(
            circuit.all_qubits())

        qubit_map = {q: i for i, q in enumerate(qubits)}

        if len(circuit) == 0:
            yield CliffordSimulatorStepResult(measurements={},
                                              state=CliffordState(
                                                  qubit_map,
                                                  initial_state=initial_state))
            return

        state = CliffordState(qubit_map, initial_state=initial_state)

        for moment in circuit:
            measurements: Dict[
                str, List[np.ndarray]] = collections.defaultdict(list)

            for op in moment:
                if isinstance(op.gate, ops.MeasurementGate):
                    key = protocols.measurement_key(op)
                    measurements[key].extend(
                        state.perform_measurement(op.qubits, self._prng))
                elif protocols.has_unitary(op):
                    state.apply_unitary(op)
                else:
                    raise NotImplementedError(
                        f"Unrecognized operation: {op!r}")

            yield CliffordSimulatorStepResult(measurements=measurements,
                                              state=state)
Beispiel #21
0
    def _run(
        self,
        circuit: circuits.Circuit,
        param_resolver: study.ParamResolver,
        repetitions: int,
    ) -> Dict[str, List[np.ndarray]]:
        """See definition in `cirq.SimulatesSamples`."""

        circuit = protocols.resolve_parameters(circuit, param_resolver)
        _verify_xmon_circuit(circuit)

        # Delegate to appropriate method based on contents.
        if circuit.are_all_measurements_terminal():
            return self._run_sweep_sample(circuit, repetitions)

        return self._run_sweep_repeat(circuit, repetitions)
Beispiel #22
0
 def _run_sweep_sample(self, circuit: circuits.Circuit,
                       repetitions: int) -> Dict[str, List[np.ndarray]]:
     step_result = None
     for step_result in self._base_iterator(
             circuit=circuit,
             qubit_order=ops.QubitOrder.DEFAULT,
             initial_state=0,
             perform_measurements=False):
         pass
     if step_result is None:
         return {}
     measurement_ops = [
         op for _, op, _ in circuit.findall_operations_with_gate_type(
             ops.MeasurementGate)
     ]
     return step_result.sample_measurement_ops(measurement_ops, repetitions)
Beispiel #23
0
def test_moment_by_moment_schedule_empty_moment_ignored():
    device = _TestDevice()
    qubits = device.qubits

    circuit = Circuit(
        [Moment([ops.H(qubits[0])]),
         Moment([]),
         Moment([ops.H(qubits[0])])])
    schedule = moment_by_moment_schedule(device, circuit)

    zero_ns = cirq.Timestamp()
    twenty_ns = cirq.Timestamp(nanos=20)
    assert set(schedule.scheduled_operations) == {
        ScheduledOperation.op_at_on(ops.H(qubits[0]), zero_ns, device),
        ScheduledOperation.op_at_on(ops.H(qubits[0]), twenty_ns, device),
    }
Beispiel #24
0
    def optimize_circuit(self, circuit: circuits.Circuit):
        state = _OptimizerState()

        for moment_index, moment in enumerate(circuit):
            for op in moment.operations:
                affected = [q for q in op.qubits
                            if state.held_w_phases.get(q) is not None]

                # Collect, phase, and merge Ws.
                w = _try_get_known_w(op)
                if w is not None:
                    if decompositions.is_negligible_turn(
                            cast(float, w.half_turns) - 1,
                            self.tolerance):
                        _potential_cross_whole_w(moment_index,
                                                 op,
                                                 self.tolerance,
                                                 state)
                    else:
                        _potential_cross_partial_w(moment_index, op, state)
                    continue

                if not affected:
                    continue

                # Absorb Z rotations.
                t = _try_get_known_z_half_turns(op)
                if t is not None:
                    _absorb_z_into_w(moment_index, op, state)
                    continue

                # Dump coherent flips into measurement bit flips.
                if ops.MeasurementGate.is_measurement(op):
                    _dump_into_measurement(moment_index, op, state)

                # Cross CZs using kickback.
                if _try_get_known_cz_half_turns(op) is not None:
                    if len(affected) == 1:
                        _single_cross_over_cz(moment_index,
                                              op,
                                              affected[0],
                                              state)
                    else:
                        _double_cross_over_cz(op, state)
                    continue

                # Don't know how to handle this situation. Dump the gates.
                _dump_held(op.qubits, moment_index, state)

        # Put anything that's still held at the end of the circuit.
        _dump_held(state.held_w_phases.keys(), len(circuit), state)

        circuit.batch_remove(state.deletions)
        circuit.batch_insert_into(state.inline_intos)
        circuit.batch_insert(state.insertions)
Beispiel #25
0
    def optimize_circuit(self, circuit: circuits.Circuit):
        state = _OptimizerState()

        for moment_index, moment in enumerate(circuit):
            for op in moment.operations:
                affected = [q for q in op.qubits
                            if state.held_w_phases.get(q) is not None]

                # Collect, phase, and merge Ws.
                w = _try_get_known_w(op)
                if w is not None:
                    if decompositions.is_negligible_turn(
                            cast(float, w.exponent) - 1,
                            self.tolerance):
                        _potential_cross_whole_w(moment_index,
                                                 op,
                                                 self.tolerance,
                                                 state)
                    else:
                        _potential_cross_partial_w(moment_index, op, state)
                    continue

                if not affected:
                    continue

                # Absorb Z rotations.
                t = _try_get_known_z_half_turns(op)
                if t is not None:
                    _absorb_z_into_w(moment_index, op, state)
                    continue

                # Dump coherent flips into measurement bit flips.
                if ops.MeasurementGate.is_measurement(op):
                    _dump_into_measurement(moment_index, op, state)

                # Cross CZs using kickback.
                if _try_get_known_cz_half_turns(op) is not None:
                    if len(affected) == 1:
                        _single_cross_over_cz(moment_index,
                                              op,
                                              affected[0],
                                              state)
                    else:
                        _double_cross_over_cz(op, state)
                    continue

                # Don't know how to handle this situation. Dump the gates.
                _dump_held(op.qubits, moment_index, state)

        # Put anything that's still held at the end of the circuit.
        _dump_held(state.held_w_phases.keys(), len(circuit), state)

        circuit.batch_remove(state.deletions)
        circuit.batch_insert_into(state.inline_intos)
        circuit.batch_insert(state.insertions)
Beispiel #26
0
    def optimize_circuit(self, circuit: circuits.Circuit):
        # Tracks qubit phases (in half turns; multiply by pi to get radians).
        qubit_phase = defaultdict(lambda: 0)  # type: Dict[ops.QubitId, float]

        def dump_tracked_phase(qubits: Iterable[ops.QubitId],
                               index: int) -> None:
            """Zeroes qubit_phase entries by emitting Z gates."""
            for q in qubits:
                p = qubit_phase[q]
                if not decompositions.is_negligible_turn(p, self.tolerance):
                    dump_op = ops.Z(q)**(p * 2)
                    insertions.append((index, dump_op))
                qubit_phase[q] = 0

        deletions = []  # type: List[Tuple[int, ops.Operation]]
        inline_intos = []  # type: List[Tuple[int, ops.Operation]]
        insertions = []  # type: List[Tuple[int, ops.Operation]]
        for moment_index, moment in enumerate(circuit):
            for op in moment.operations:
                # Move Z gates into tracked qubit phases.
                h = _try_get_known_z_half_turns(op)
                if h is not None:
                    q = op.qubits[0]
                    qubit_phase[q] += h / 2
                    deletions.append((moment_index, op))
                    continue

                # Z gate before measurement is a no-op. Drop tracked phase.
                if ops.MeasurementGate.is_measurement(op):
                    for q in op.qubits:
                        qubit_phase[q] = 0

                # If there's no tracked phase, we can move on.
                phases = [qubit_phase[q] for q in op.qubits]
                if all(decompositions.is_negligible_turn(p, self.tolerance)
                       for p in phases):
                    continue

                # Try to move the tracked phasing over the operation.
                phased_op = op
                for i, p in enumerate(phases):
                    if not decompositions.is_negligible_turn(p, self.tolerance):
                        phased_op = protocols.phase_by(phased_op, -p, i,
                                                       default=None)
                if phased_op is not None:
                    deletions.append((moment_index, op))
                    inline_intos.append((moment_index,
                                     cast(ops.Operation, phased_op)))
                else:
                    dump_tracked_phase(op.qubits, moment_index)

        dump_tracked_phase(qubit_phase.keys(), len(circuit))
        circuit.batch_remove(deletions)
        circuit.batch_insert_into(inline_intos)
        circuit.batch_insert(insertions)
Beispiel #27
0
    def _core_iterator(
        self,
        circuit: circuits.Circuit,
        sim_state: TActOnArgs,
        all_measurements_are_terminal: bool = False,
    ) -> Iterator[TStepResult]:
        """Standard iterator over StepResult from Moments of a Circuit.

        Args:
            circuit: The circuit to simulate.
            sim_state: The initial args for the simulation. The form of
                this state depends on the simulation implementation. See
                documentation of the implementing class for details.

        Yields:
            StepResults from simulating a Moment of the Circuit.
        """
        if len(circuit) == 0:
            yield self._create_step_result(sim_state, sim_state.qubit_map)
            return

        noisy_moments = self.noise.noisy_moments(circuit,
                                                 sorted(circuit.all_qubits()))
        measured: Dict[Tuple[cirq.Qid, ...],
                       bool] = collections.defaultdict(bool)
        for moment in noisy_moments:
            for op in ops.flatten_to_ops(moment):
                try:
                    # TODO: support more general measurements.
                    # Github issue: https://github.com/quantumlib/Cirq/issues/3566
                    if all_measurements_are_terminal and measured[op.qubits]:
                        continue
                    if isinstance(op.gate, ops.MeasurementGate):
                        measured[op.qubits] = True
                        if all_measurements_are_terminal:
                            continue
                        if self._ignore_measurement_results:
                            op = ops.phase_damp(1).on(*op.qubits)
                    sim_state.axes = tuple(sim_state.qubit_map[qubit]
                                           for qubit in op.qubits)
                    protocols.act_on(op, sim_state)
                except TypeError:
                    raise TypeError(
                        f"{self.__class__.__name__} doesn't support {op!r}")

            yield self._create_step_result(sim_state, sim_state.qubit_map)
            sim_state.log_of_measurement_results.clear()
Beispiel #28
0
    def _generate_json(
        self,
        circuit: circuits.Circuit,
        param_resolver: study.ParamResolverOrSimilarType,
    ) -> str:
        """Generates the JSON string from a Circuit

        The json format is defined as follows:

        [[op_string,gate_exponent,qubits]]

        which is a list of sequential quantum operations,
        each operation defined by:

        op_string: str that specifies the operation type: "X","Y","Z","MS"
        gate_exponent: float that specifies the gate_exponent of the operation
        qubits: list of qubits where the operation acts on.

        Args:
            circuit: Circuit to be run
            param_resolver: Param resolver for the

        Returns:
            json formatted string of the sequence
        """

        seq_list: List[Union[Tuple[str, float, List[int]],
                             Tuple[str, float, float, List[int]]]] = []
        circuit = resolve_parameters(circuit, param_resolver)
        for op in circuit.all_operations():
            line_qubit = cast(Tuple[LineQubit], op.qubits)
            op = cast(ops.GateOperation, op)
            qubit_idx = [obj.x for obj in line_qubit]
            op_str = get_op_string(op)
            gate: Union[ops.EigenGate, ops.PhasedXPowGate]
            if op_str == 'R':
                gate = cast(ops.PhasedXPowGate, op.gate)
                seq_list.append((op_str, float(gate.exponent),
                                 float(gate.phase_exponent), qubit_idx))
            else:
                gate = cast(ops.EigenGate, op.gate)
                seq_list.append((op_str, float(gate.exponent), qubit_idx))
        if len(seq_list) == 0:
            raise RuntimeError('Cannot send an empty circuit')
        json_str = json.dumps(seq_list)
        return json_str
Beispiel #29
0
def circuit_to_latex_using_qcircuit(
        circuit: circuits.Circuit,
        qubit_order: ops.QubitOrderOrList = ops.QubitOrder.DEFAULT) -> str:
    """Returns a QCircuit-based latex diagram of the given circuit.

    Args:
        circuit: The circuit to represent in latex.
        qubit_order: Determines the order of qubit wires in the diagram.

    Returns:
        Latex code for the diagram.
    """
    diagram = circuit.to_text_diagram_drawer(
        qubit_namer=qcircuit_qubit_namer,
        qubit_order=qubit_order,
        get_circuit_diagram_info=get_qcircuit_diagram_info)
    return _render(diagram)
Beispiel #30
0
def replace_acquaintance_with_swap_network(
        circuit: circuits.Circuit,
        qubit_order: Sequence[ops.Qid],
        acquaintance_size: Optional[int] = 0,
        swap_gate: ops.Gate = ops.SWAP
        ) -> bool:
    """
    Replace every moment containing acquaintance gates (after
    rectification) with a generalized swap network, with the partition
    given by the acquaintance gates in that moment (and singletons for the
    free qubits). Accounts for reversing effect of swap networks.

    Args:
        circuit: The acquaintance strategy.
        qubit_order: The qubits, in order, on which the replacing swap network
            gate acts on.
        acquaintance_size: The acquaintance size of the new swap network gate.
        swap_gate: The gate used to swap logical indices.

    Returns: Whether or not the overall effect of the inserted swap network
        gates is to reverse the order of the qubits, i.e. the parity of the
        number of swap network gates inserted.

    Raises:
        TypeError: circuit is not an acquaintance strategy.
    """

    if not is_acquaintance_strategy(circuit):
        raise TypeError('not is_acquaintance_strategy(circuit)')

    rectify_acquaintance_strategy(circuit)
    reflected = False
    reverse_map = {q: r for q, r in zip(qubit_order, reversed(qubit_order))}
    for moment_index, moment in enumerate(circuit):
        if reflected:
            moment = moment.transform_qubits(reverse_map.__getitem__)
        if all(isinstance(op.gate, AcquaintanceOpportunityGate)
                for op in moment.operations):
            swap_network_gate = SwapNetworkGate.from_operations(
                    qubit_order, moment.operations,
                    acquaintance_size, swap_gate)
            swap_network_op = swap_network_gate(*qubit_order)
            moment = ops.Moment([swap_network_op])
            reflected = not reflected
        circuit._moments[moment_index] = moment
    return reflected
Beispiel #31
0
    def _run_sweep_repeat(self, circuit: circuits.Circuit,
                          repetitions: int) -> Dict[str, np.ndarray]:
        measurements = {}  # type: Dict[str, List[np.ndarray]]
        if repetitions == 0:
            for _, op, _ in circuit.findall_operations_with_gate_type(
                    ops.MeasurementGate):
                measurements[protocols.measurement_key(op)] = np.empty([0, 1])

        for _ in range(repetitions):
            all_step_results = self._base_iterator(
                circuit, qubit_order=ops.QubitOrder.DEFAULT, initial_state=0)
            for step_result in all_step_results:
                for k, v in step_result.measurements.items():
                    if not k in measurements:
                        measurements[k] = []
                    measurements[k].append(np.array(v, dtype=np.uint8))
        return {k: np.array(v) for k, v in measurements.items()}
Beispiel #32
0
def create_corrected_circuit(target_unitary: np.ndarray,
                             program: circuits.Circuit, q0: ops.Qid,
                             q1: ops.Qid):
    # Get the local equivalents
    b_0, b_1, a_0, a_1 = find_local_equivalents(
        target_unitary,
        program.unitary(qubit_order=ops.QubitOrder.explicit([q0, q1])))

    # Apply initial corrections
    yield (gate(q0) for gate in optimizers.single_qubit_matrix_to_gates(b_0))
    yield (gate(q1) for gate in optimizers.single_qubit_matrix_to_gates(b_1))

    # Apply interaction part
    yield program

    # Apply final corrections
    yield (gate(q0) for gate in optimizers.single_qubit_matrix_to_gates(a_0))
    yield (gate(q1) for gate in optimizers.single_qubit_matrix_to_gates(a_1))
def create_corrected_circuit(target_unitary: np.ndarray,
                             program: circuits.Circuit, q0: ops.Qid,
                             q1: ops.Qid) -> ops.OP_TREE:
    # Get the local equivalents
    b_0, b_1, a_0, a_1 = find_local_equivalents(
        target_unitary,
        program.unitary(qubit_order=ops.QubitOrder.explicit([q0, q1])))

    # Apply initial corrections
    yield from _phased_x_z_ops(b_0, q0)
    yield from _phased_x_z_ops(b_1, q1)

    # Apply interaction part
    yield program

    # Apply final corrections
    yield from _phased_x_z_ops(a_0, q0)
    yield from _phased_x_z_ops(a_1, q1)
Beispiel #34
0
    def program_as_schedule(self,
                            program: Union[Circuit, Schedule],
                            device: Device = None) -> Schedule:
        if isinstance(program, Circuit):
            device = device or UnconstrainedDevice
            circuit_copy = Circuit(program.moments)
            ConvertToXmonGates().optimize_circuit(circuit_copy)
            DropEmptyMoments().optimize_circuit(circuit_copy)
            device.validate_circuit(circuit_copy)
            return moment_by_moment_schedule(device, circuit_copy)

        elif isinstance(program, Schedule):
            if device:
                raise ValueError(
                    'Device can not be provided when running a schedule.')
            return program
        else:
            raise TypeError('Unexpected program type.')
Beispiel #35
0
def move_pauli_strings_into_circuit(
        circuit_left: Union[circuits.Circuit, circuits.CircuitDag],
        circuit_right: circuits.Circuit) -> circuits.Circuit:
    if isinstance(circuit_left, circuits.CircuitDag):
        string_dag = circuits.CircuitDag(pauli_string_reorder_pred,
                                         circuit_left)
    else:
        string_dag = pauli_string_dag_from_circuit(
            cast(circuits.Circuit, circuit_left))
    output_ops = list(circuit_right.all_operations())

    rightmost_nodes = (set(string_dag.nodes()) -
                       set(before for before, _ in string_dag.edges()))

    while rightmost_nodes:
        # Sort the pauli string placements based on paulistring length and
        # furthest possible distance in circuit_right
        placements = _sorted_best_string_placements(rightmost_nodes,
                                                    output_ops)
        last_index = len(output_ops)

        # Pick the Pauli string that can be moved furthest through
        # the Clifford circuit
        for best_string_op, best_index, best_node in placements:

            assert best_index <= last_index, (
                "Unexpected insertion index order,"
                " {} >= {}, len: {}".format(best_index, last_index,
                                            len(output_ops)))

            last_index = best_index
            output_ops.insert(best_index, best_string_op)
            # Remove the best one from the dag and update rightmost_nodes
            rightmost_nodes.remove(best_node)
            rightmost_nodes.update(
                pred_node for pred_node in string_dag.predecessors(best_node)
                if len(string_dag.succ[pred_node]) <= 1)
            string_dag.remove_node(best_node)

    assert not string_dag.nodes(), 'There was a cycle in the CircuitDag'

    return circuits.Circuit(output_ops,
                            strategy=circuits.InsertStrategy.EARLIEST,
                            device=circuit_right.device)
Beispiel #36
0
def stratify_circuit(classifiers: Iterable[Classifier],
                     circuit: circuits.Circuit):
    """Performs the stratification by iterating through the operations in the
    circuit and using the given classifiers to align them.

    Args:
        classifiers: A list of rules to align the circuit. Must be exhaustive,
            i.e. all operations will be caught by one of the processors.
        circuit: The circuit to break out into homogeneous moments. Will not be
            edited.

    Returns:
        The stratified circuit.
    """
    solution = circuits.Circuit()
    circuit_copy = circuit.copy()
    while len(circuit_copy.all_qubits()) > 0:
        for classifier in classifiers:
            current_moment = ops.Moment()
            blocked_qubits: Set[ops.Qid] = set()
            for moment_idx, moment in enumerate(circuit_copy.moments):
                for op in moment.operations:
                    can_insert = classifier(op)
                    if not can_insert:
                        blocked_qubits.update(op.qubits)
                    else:
                        # Ensure that all the qubits for this operation are
                        # still available.
                        if not any(qubit in blocked_qubits
                                   for qubit in op.qubits):
                            # Add the operation to the current moment and
                            # remove it from the circuit.
                            current_moment = current_moment.with_operation(op)
                            blocked_qubits.update(op.qubits)
                            circuit_copy.batch_remove([(moment_idx, op)])

                # Short-circuit: If all the qubits are blocked, go on to the
                # next moment.
                if blocked_qubits.issuperset(circuit_copy.all_qubits()):
                    break

            if len(current_moment) > 0:
                solution.append(current_moment)
    return solution
Beispiel #37
0
def test_moment_by_moment_schedule_max_duration():
    device = _TestDevice()
    qubits = device.qubits

    circuit = Circuit([
        Moment([ops.H(qubits[0]),
                ops.CZ(qubits[1], qubits[2])]),
        Moment([ops.H(qubits[0])])
    ])
    schedule = moment_by_moment_schedule(device, circuit)

    zero_ns = cirq.Timestamp()
    fourty_ns = cirq.Timestamp(nanos=40)
    assert set(schedule.scheduled_operations) == {
        ScheduledOperation.op_at_on(ops.H(qubits[0]), zero_ns, device),
        ScheduledOperation.op_at_on(ops.CZ(qubits[1], qubits[2]), zero_ns,
                                    device),
        ScheduledOperation.op_at_on(ops.H(qubits[0]), fourty_ns, device),
    }
Beispiel #38
0
def test_moment_by_moment_schedule_two_moments():
    device = _TestDevice()
    qubits = device.qubits

    circuit = Circuit([
        Moment(ops.H(q) for q in qubits),
        Moment((ops.CZ(qubits[i], qubits[i + 1]) for i in range(0, 9, 3)))
    ])
    schedule = moment_by_moment_schedule(device, circuit)

    zero_ns = cirq.Timestamp()
    twenty_ns = cirq.Timestamp(nanos=20)
    expected_one_qubit = set(
        ScheduledOperation.op_at_on(ops.H(q), zero_ns, device) for q in qubits)
    expected_two_qubit = set(
        ScheduledOperation.op_at_on(ops.CZ(qubits[i], qubits[i + 1]),
                                    twenty_ns, device) for i in range(0, 9, 3))
    expected = expected_one_qubit.union(expected_two_qubit)
    assert set(schedule.scheduled_operations) == expected
 def _run_sweep_sample(
     self,
     circuit: circuits.Circuit,
     repetitions: int,
     act_on_args: act_on_density_matrix_args.ActOnDensityMatrixArgs,
 ) -> Dict[str, np.ndarray]:
     for step_result in self._core_iterator(
             circuit=circuit,
             sim_state=act_on_args,
             all_measurements_are_terminal=True,
     ):
         pass
     measurement_ops = [
         op for _, op, _ in circuit.findall_operations_with_gate_type(
             ops.MeasurementGate)
     ]
     return step_result.sample_measurement_ops(measurement_ops,
                                               repetitions,
                                               seed=self._prng)
Beispiel #40
0
    def optimize_circuit(self, circuit: circuits.Circuit):
        turns_state = defaultdict(lambda: 0)  # type: Dict[ops.QubitId, float]

        def dump_phases(qubits, index):
            for q in qubits:
                p = turns_state[q]
                if not is_negligible_turn(p, self.tolerance):
                    dump_op = ops.Z(q)**(p * 2)
                    insertions.append((index, dump_op))
                turns_state[q] = 0

        deletions = []  # type: List[Tuple[int, ops.Operation]]
        inline_intos = []  # type: List[Tuple[int, ops.Operation]]
        insertions = []  # type: List[Tuple[int, ops.Operation]]
        for moment_index, moment in enumerate(circuit):
            for op in moment.operations:
                h = _try_get_known_z_half_turns(op)
                if h is not None:
                    q = op.qubits[0]
                    turns_state[q] += h / 2
                    deletions.append((moment_index, op))
                    continue

                if ops.MeasurementGate.is_measurement(op):
                    for q in op.qubits:
                        turns_state[q] = 0

                phases = [turns_state[q] for q in op.qubits]
                if all(is_negligible_turn(p, self.tolerance) for p in phases):
                    continue

                phased_op = op
                for i, p in enumerate(phases):
                    if p and phased_op is not None:
                        phased_op = protocols.phase_by(phased_op,
                                                       -p,
                                                       i,
                                                       default=None)

                if phased_op is not None:
                    deletions.append((moment_index, op))
                    inline_intos.append(
                        (moment_index, cast(ops.Operation, phased_op)))
                else:
                    dump_phases(op.qubits, moment_index)

        dump_phases(turns_state.keys(), len(circuit))
        circuit.batch_remove(deletions)
        circuit.batch_insert_into(inline_intos)
        circuit.batch_insert(insertions)
Beispiel #41
0
def _canonicalize_up_to_terminal_measurement_phase(
        circuit1: circuits.Circuit,
        circuit2: circuits.Circuit) -> Tuple[np.ndarray, np.ndarray]:
    qubits = circuit1.all_qubits().union(circuit2.all_qubits())
    order = ops.QubitOrder.DEFAULT.order_for(qubits)
    assert circuit1.are_all_measurements_terminal()
    assert circuit2.are_all_measurements_terminal()

    measured_1 = {q
                  for op in circuit1.all_operations()
                  if ops.MeasurementGate.is_measurement(op)
                  for q in op.qubits}
    measured_2 = {q
                  for op in circuit2.all_operations()
                  if ops.MeasurementGate.is_measurement(op)
                  for q in op.qubits}
    assert measured_1 == measured_2

    matrix1 = circuit1.to_unitary_matrix(qubits_that_should_be_present=qubits)
    matrix2 = circuit2.to_unitary_matrix(qubits_that_should_be_present=qubits)
    for q in measured_1:
        _cancel_qubit_phase(matrix1, matrix2, order.index(q))
    return matrix1, matrix2
Beispiel #42
0
 def validate_circuit(self, circuit: circuits.Circuit):
     super().validate_circuit(circuit)
     _verify_unique_measurement_keys(circuit.all_operations())
Beispiel #43
0
def _simulator_iterator(
        circuit: Circuit,
        options: 'XmonOptions' = XmonOptions(),
        qubit_order: ops.QubitOrderOrList = ops.QubitOrder.DEFAULT,
        initial_state: Union[int, np.ndarray]=0,
        perform_measurements: bool=True,
) -> Iterator['XmonStepResult']:
    """Iterator over XmonStepResult from Moments of a Circuit.

    This should rarely be instantiated directly, instead prefer to create an
    XmonSimulator and use methods on that object to get an iterator.

    Args:
        circuit: The circuit to simulate. Must contain only xmon gates with no
            unresolved parameters.
        options: XmonOptions configuring the simulation.
        qubit_order: Determines the canonical ordering of the qubits used to
            define the order of amplitudes in the wave function.
        initial_state: If this is an int, the state is set to the computational
            basis state corresponding to the integer. Note that
            the low bit of the integer corresponds to the value of the first
            qubit as determined by the basis argument.

            If this is a np.ndarray it is the full initial state.
            In this case it must be the correct size, be normalized (an L2
            norm of 1), and be safely castable to a np.complex64.
        perform_measurements: Whether or not to perform the measurements in
            the circuit. Should only be set to False when optimizing for
            sampling over the measurements.

    Yields:
        XmonStepResults from simulating a Moment of the Circuit.

    Raises:
        TypeError: if the circuit contains gates that are not XmonGates or
            composite gates made of XmonGates.
    """
    qubits = ops.QubitOrder.as_qubit_order(qubit_order).order_for(
        circuit.all_qubits())
    qubit_map = {q: i for i, q in enumerate(reversed(qubits))}
    if isinstance(initial_state, np.ndarray):
        initial_state = initial_state.astype(dtype=np.complex64,
                                             casting='safe')

    with xmon_stepper.Stepper(
            num_qubits=len(qubits),
            num_prefix_qubits=options.num_prefix_qubits,
            initial_state=initial_state,
            min_qubits_before_shard=options.min_qubits_before_shard,
            use_processes=options.use_processes
    ) as stepper:
        for moment in circuit:
            measurements = collections.defaultdict(
                list)  # type: Dict[str, List[bool]]
            phase_map = {}  # type: Dict[Tuple[int, ...], float]
            for op in moment.operations:
                gate = cast(ops.GateOperation, op).gate
                if isinstance(gate, xmon_gates.ExpZGate):
                    index = qubit_map[op.qubits[0]]
                    phase_map[(index,)] = cast(float, gate.half_turns)
                elif isinstance(gate, xmon_gates.Exp11Gate):
                    index0 = qubit_map[op.qubits[0]]
                    index1 = qubit_map[op.qubits[1]]
                    phase_map[(index0, index1)] = cast(float, gate.half_turns)
                elif isinstance(gate, xmon_gates.ExpWGate):
                    index = qubit_map[op.qubits[0]]
                    stepper.simulate_w(
                        index=index,
                        half_turns=gate.half_turns,
                        axis_half_turns=gate.axis_half_turns)
                elif isinstance(gate, xmon_gates.XmonMeasurementGate):
                    if perform_measurements:
                        invert_mask = (
                                gate.invert_mask or len(op.qubits) * (False,))
                        for qubit, invert in zip(op.qubits, invert_mask):
                            index = qubit_map[qubit]
                            result = stepper.simulate_measurement(index)
                            if invert:
                                result = not result
                            measurements[cast(str, gate.key)].append(result)
                else:
                    raise TypeError('{!r} is not supported by the '
                                    'xmon simulator.'.format(gate))
            stepper.simulate_phases(phase_map)
            yield XmonStepResult(stepper, qubit_map, measurements)
Beispiel #44
0
def converted_gate_set(circuit: circuits.Circuit, atol: float = 1e-7
                       ) -> circuits.Circuit:
    """Returns a new, equivalent circuit using the gate set {CliffordGate,
    PauliInteractionGate, PauliStringPhasor}.

    The circuit structure may differ because it is optimized during conversion.
    """
    xmon_circuit = google.optimized_for_xmon(circuit, allow_partial_czs=False)

    qubits = circuit.all_qubits()
    tol = linalg.Tolerance(atol=atol)

    def is_clifford_rotation(half_turns):
        return tol.all_near_zero_mod(half_turns, 0.5)

    def to_quarter_turns(half_turns):
        return round(2 * half_turns) % 4

    def is_quarter_turn(half_turns):
        return (is_clifford_rotation(half_turns) and
                to_quarter_turns(half_turns) % 2 == 1)

    def is_half_turn(half_turns):
        return (is_clifford_rotation(half_turns) and
                to_quarter_turns(half_turns) == 2)

    def is_no_turn(half_turns):
        return (is_clifford_rotation(half_turns) and
                to_quarter_turns(half_turns) == 0)

    def rotation_to_clifford_op(pauli, qubit, half_turns):
        quarter_turns = to_quarter_turns(half_turns)
        if quarter_turns == 0:
            return ops.CliffordGate.I(qubit)
        elif quarter_turns == 2:
            return ops.CliffordGate.from_pauli(pauli)(qubit)
        else:
            gate = ops.CliffordGate.from_pauli(pauli, True)(qubit)
            if quarter_turns == 3:
                gate = gate.inverse()
            return gate

    def rotation_to_non_clifford_op(pauli, qubit, half_turns):
        return PauliStringPhasor(ops.PauliString.from_single(qubit, pauli),
                                 half_turns=half_turns)

    def single_qubit_matrix_to_ops(mat, qubit):
        # Decompose matrix
        z_rad_before, y_rad, z_rad_after = (
            linalg.deconstruct_single_qubit_matrix_into_angles(mat))
        z_ht_before = z_rad_before / np.pi - 0.5
        m_ht = y_rad / np.pi
        m_pauli = ops.Pauli.X
        z_ht_after = z_rad_after / np.pi + 0.5

        # Clean up angles
        if is_clifford_rotation(z_ht_before):
            if is_quarter_turn(z_ht_before) or is_quarter_turn(z_ht_after):
                z_ht_before += 0.5
                z_ht_after -= 0.5
                m_pauli = ops.Pauli.Y
            if is_half_turn(z_ht_before) or is_half_turn(z_ht_after):
                z_ht_before -= 1
                z_ht_after += 1
                m_ht = -m_ht
        if is_no_turn(m_ht):
            z_ht_before += z_ht_after
            z_ht_after = 0
        elif is_half_turn(m_ht):
            z_ht_after -= z_ht_before
            z_ht_before = 0

        # Generate operations
        rotation_list = [
            (ops.Pauli.Z, z_ht_before),
            (m_pauli, m_ht),
            (ops.Pauli.Z, z_ht_after)]
        is_clifford_list = [is_clifford_rotation(ht)
                            for pauli, ht in rotation_list]
        op_list = [rotation_to_clifford_op(pauli, qubit, ht) if is_clifford
                   else rotation_to_non_clifford_op(pauli, qubit, ht)
                   for is_clifford, (pauli, ht) in
                        zip(is_clifford_list, rotation_list)]

        # Merge adjacent Clifford gates
        for i in reversed(range(len(op_list) - 1)):
            if is_clifford_list[i] and is_clifford_list[i+1]:
                op_list[i] = op_list[i].gate.merged_with(op_list[i+1].gate
                                                         )(qubit)
                is_clifford_list.pop(i+1)
                op_list.pop(i+1)

        # Yield non-identity ops
        for is_clifford, op in zip(is_clifford_list, op_list):
            if is_clifford and op.gate == ops.CliffordGate.I:
                continue
            yield op

    def generate_ops():
        conv_cz = ops.PauliInteractionGate(ops.Pauli.Z, False,
                                           ops.Pauli.Z, False)
        matrices = {qubit: np.eye(2) for qubit in qubits}

        def clear_matrices(qubits):
            for qubit in qubits:
                yield single_qubit_matrix_to_ops(matrices[qubit], qubit)
                matrices[qubit] = np.eye(2)

        for op in xmon_circuit.all_operations():
            # Assumes all Xmon operations are GateOperation
            gate = op.gate
            if isinstance(gate, google.XmonMeasurementGate):
                yield clear_matrices(op.qubits)
                yield ops.MeasurementGate(gate.key, gate.invert_mask
                                          )(*op.qubits)
            elif len(op.qubits) == 1:
                # Collect all one qubit rotations
                # Assumes all Xmon gates implement KnownMatrix
                qubit, = op.qubits
                matrices[qubit] = op.matrix().dot(matrices[qubit])
            elif isinstance(gate, google.Exp11Gate):
                yield clear_matrices(op.qubits)
                if gate.half_turns != 1:
                    # coverage: ignore
                    raise ValueError('Unexpected partial CZ: {}'.format(op))
                yield conv_cz(*op.qubits)
            else:
                # coverage: ignore
                raise TypeError('Unknown Xmon operation: {}'.format(op))

        yield clear_matrices(qubits)

    return circuits.Circuit.from_ops(
                generate_ops(),
                strategy=circuits.InsertStrategy.EARLIEST)