def unroll_circuit_op_greedy_frontier( circuit: CIRCUIT_TYPE, *, tags_to_check=(MAPPED_CIRCUIT_OP_TAG, )) -> CIRCUIT_TYPE: """Unrolls (tagged) `cirq.CircuitOperation`s by inserting operations inline at qubit frontier. Each matching `cirq.CircuitOperation` is replaced by inserting underlying operations using the `circuit.insert_at_frontier` method. The greedy approach attempts to reuse any available space in existing moments on the right of circuit_op before inserting new moments. Args: circuit: Input circuit to apply the transformations on. The input circuit is not mutated. tags_to_check: If specified, only circuit operations tagged with one of the `tags_to_check` are unrolled. Returns: Copy of input circuit with (Tagged) CircuitOperation's expanded inline at qubit frontier. """ unrolled_circuit = circuit.unfreeze(copy=True) frontier: Dict['cirq.Qid', int] = defaultdict(lambda: 0) for idx, op in circuit.findall_operations( lambda op: _check_circuit_op(op, tags_to_check)): idx = max(idx, max(frontier[q] for q in op.qubits)) unrolled_circuit.clear_operations_touching(op.qubits, [idx]) frontier = unrolled_circuit.insert_at_frontier( protocols.decompose_once(op), idx, frontier) return _to_target_circuit_type(unrolled_circuit, circuit)
def unroll_circuit_op_greedy_earliest( circuit: CIRCUIT_TYPE, *, tags_to_check=(MAPPED_CIRCUIT_OP_TAG, )) -> CIRCUIT_TYPE: """Unrolls (tagged) `cirq.CircuitOperation`s by inserting operations using EARLIEST strategy. Each matching `cirq.CircuitOperation` is replaced by inserting underlying operations using the `cirq.InsertStrategy.EARLIEST` strategy. The greedy approach attempts to minimize circuit depth of the resulting circuit. Args: circuit: Input circuit to apply the transformations on. The input circuit is not mutated. tags_to_check: If specified, only circuit operations tagged with one of the `tags_to_check` are unrolled. Returns: Copy of input circuit with (Tagged) CircuitOperation's expanded using EARLIEST strategy. """ batch_removals = [ *circuit.findall_operations( lambda op: _check_circuit_op(op, tags_to_check)) ] batch_inserts = [(i, protocols.decompose_once(op)) for i, op in batch_removals] unrolled_circuit = circuit.unfreeze(copy=True) unrolled_circuit.batch_remove(batch_removals) unrolled_circuit.batch_insert(batch_inserts) return _to_target_circuit_type(unrolled_circuit, circuit)
def assert_decompose_is_consistent_with_unitary( val: Any, ignoring_global_phase: bool = False): """Uses `val._unitary_` to check `val._phase_by_`'s behavior.""" expected = protocols.unitary(val, None) if expected is None: # If there's no unitary, it's vacuously consistent. return qubit_count = len(expected).bit_length() - 1 if isinstance(val, ops.Operation): qubits = val.qubits dec = protocols.decompose_once(val, default=None) else: qubits = tuple(line.LineQubit.range(qubit_count)) dec = protocols.decompose_once_with_qubits(val, qubits, default=None) if dec is None: # If there's no decomposition, it's vacuously consistent. return actual = circuits.Circuit.from_ops(dec).unitary(qubit_order=qubits) if ignoring_global_phase: lin_alg_utils.assert_allclose_up_to_global_phase(actual, expected, atol=1e-8) else: # coverage: ignore np.testing.assert_allclose(actual, expected, atol=1e-8)
def defer(op: 'cirq.Operation', _) -> 'cirq.OP_TREE': if op in terminal_measurements: return op gate = op.gate if isinstance(gate, ops.MeasurementGate): key = value.MeasurementKey.parse_serialized(gate.key) targets = [_MeasurementQid(key, q) for q in op.qubits] measurement_qubits[key] = targets cxs = [ops.CX(q, target) for q, target in zip(op.qubits, targets)] xs = [ops.X(targets[i]) for i, b in enumerate(gate.full_invert_mask()) if b] return cxs + xs elif protocols.is_measurement(op): return [defer(op, None) for op in protocols.decompose_once(op)] elif op.classical_controls: controls = [] for c in op.classical_controls: if isinstance(c, value.KeyCondition): if c.key not in measurement_qubits: raise ValueError(f'Deferred measurement for key={c.key} not found.') qubits = measurement_qubits[c.key] if len(qubits) != 1: # TODO: Multi-qubit conditions require # https://github.com/quantumlib/Cirq/issues/4512 # Remember to update docstring above once this works. raise ValueError('Only single qubit conditions are allowed.') controls.extend(qubits) else: raise ValueError('Only KeyConditions are allowed.') return op.without_classical_controls().controlled_by( *controls, control_values=[tuple(range(1, q.dimension)) for q in controls] ) return op
def _decompose_into_cliffords(op: 'cirq.Operation') -> List['cirq.Operation']: # An operation that can be ignored? if isinstance(op, global_phase_op.GlobalPhaseOperation): return [] # Already a known Clifford? if isinstance(op.gate, (clifford_gate.SingleQubitCliffordGate, pauli_interaction_gate.PauliInteractionGate)): return [op] # Specifies a decomposition into Cliffords? v = getattr(op, '_decompose_into_clifford_', None) if v is not None: result = v() if result is not None and result is not NotImplemented: return list(op_tree.flatten_to_ops(result)) # Specifies a decomposition that happens to contain only Cliffords? decomposed = protocols.decompose_once(op, None) if decomposed is not None: return [ out for sub_op in decomposed for out in _decompose_into_cliffords(sub_op) ] raise TypeError(f'Operation is not a known Clifford and did not decompose ' f'into known Cliffords: {op!r}')
def _convert_one(self, op: ops.Operation) -> ops.OP_TREE: # Check if this is a CZ # Only keep partial CZ gates if allow_partial_czs if (isinstance(op, ops.GateOperation) and isinstance(op.gate, ops.CZPowGate) and (self.allow_partial_czs or op.gate.exponent == 1)): return op # Measurement? if ops.MeasurementGate.is_measurement(op): return op # Known matrix? mat = protocols.unitary(op, None) if mat is not None and len(op.qubits) == 1: return op if mat is not None and len(op.qubits) == 2: return decompositions.two_qubit_matrix_to_operations( op.qubits[0], op.qubits[1], mat, allow_partial_czs=False) # Provides a decomposition? decomposed = protocols.decompose_once(op, None) if decomposed is not None: return decomposed # Just let it be? if self.ignore_failures: return op raise TypeError("Don't know how to work with {!r}. " "It isn't composite or an operation with a " "known unitary effect on 1 or 2 qubits.".format(op))
def assert_decompose_is_consistent_with_unitary( val: Any, ignoring_global_phase: bool = False): """Uses `val._unitary_` to check `val._phase_by_`'s behavior.""" # pylint: disable=unused-variable __tracebackhide__ = True # pylint: enable=unused-variable expected = protocols.unitary(val, None) if expected is None: # If there's no unitary, it's vacuously consistent. return if isinstance(val, ops.Operation): qubits = val.qubits dec = protocols.decompose_once(val, default=None) else: qubits = tuple(devices.LineQid.for_gate(val)) dec = protocols.decompose_once_with_qubits(val, qubits, default=None) if dec is None: # If there's no decomposition, it's vacuously consistent. return actual = circuits.Circuit(dec).unitary(qubit_order=qubits) if ignoring_global_phase: lin_alg_utils.assert_allclose_up_to_global_phase(actual, expected, atol=1e-8) else: # coverage: ignore np.testing.assert_allclose(actual, expected, atol=1e-8)
def _convert_one(self, op: ops.Operation) -> ops.OP_TREE: # Don't change if it's already a SingleQubitCliffordGate if (isinstance(op, ops.GateOperation) and isinstance(op.gate, ops.SingleQubitCliffordGate)): return op # Single qubit gate with known matrix? if len(op.qubits) == 1: mat = protocols.unitary(op, None) if mat is not None: cliff_op = self._matrix_to_clifford_op(mat, op.qubits[0]) if cliff_op is not None: return cliff_op if self.ignore_failures: return op raise ValueError('Single qubit operation is not in the ' 'Clifford group: {!r}'.format(op)) # Provides a decomposition? decomposed = protocols.decompose_once(op, None) if decomposed is not None: return decomposed # Just let it be? if self.ignore_failures: return op raise TypeError("Don't know how to work with {!r}. " "It isn't composite or a 1-qubit operation " "with a known unitary effect.".format(op))
def _decompose_(self): result = protocols.decompose_once(self.sub_operation, NotImplemented) if result is NotImplemented: return NotImplemented return [ ControlledOperation(self.controls, op, self.control_values) for op in result ]
def _decompose_(self): result = protocols.decompose_once(self._sub_operation, NotImplemented) if result is NotImplemented: return NotImplemented return [ ClassicallyControlledOperation(op, self._conditions) for op in result ]
def _decompose(self, op: ops.Operation) -> ops.OP_TREE: """Recursively decompose composite gates into an OP_TREE of gates.""" skip = self.no_decomp(op) if skip and (skip is not NotImplemented): return op decomposed = protocols.decompose_once(op, None) if decomposed is None: return op return (self._decompose(op) for op in decomposed)
def _write_operations(self, op_tree: ops.OP_TREE, output: Callable[[str], None], output_line_gap: Callable[[int], None], top=True) -> None: for op in ops.flatten_op_tree(op_tree): out = protocols.qasm(op, args=self.args, default=None) if out is not None: output(out) continue if isinstance(op, ops.GateOperation): comment = 'Gate: {!s}'.format(op.gate) else: comment = 'Operation: {!s}'.format(op) decomp = protocols.decompose_once(op, None) if decomp is not None: if top: output_line_gap(1) output('// {}\n'.format(comment)) self._write_operations(decomp, output, output_line_gap, top=False) if top: output_line_gap(1) continue mat = protocols.unitary(op, None) if len(op.qubits) <= 2 else None if mat is not None and len(op.qubits) == 1: u_op = QasmUGate.from_matrix(mat).on(*op.qubits) if top: output_line_gap(1) output('// {}\n'.format(comment)) output(cast(str, protocols.qasm(u_op, args=self.args))) if top: output_line_gap(1) continue if mat is not None and len(op.qubits) == 2: u_op = QasmTwoQubitGate.from_matrix(mat).on(*op.qubits) if top: output_line_gap(1) output('// {}\n'.format(comment)) self._write_operations((u_op,), output, output_line_gap, top=False) if top: output_line_gap(1) continue raise ValueError('Cannot output operation as QASM: {!r}'.format(op))
def assert_decompose_ends_at_default_gateset(val: Any): """Asserts that cirq.decompose(val) ends at default cirq gateset or a known gate.""" if _known_gate_with_no_decomposition(val): return # coverage: ignore args = () if isinstance(val, ops.Operation) else (tuple( devices.LineQid.for_gate(val)), ) dec_once = protocols.decompose_once(val, [val(*args[0]) if args else val], *args) for op in [*ops.flatten_to_ops(protocols.decompose(d) for d in dec_once)]: assert _known_gate_with_no_decomposition(op.gate) or ( op in protocols.decompose_protocol.DECOMPOSE_TARGET_GATESET ), f'{val} decomposed to {op}, which is not part of default cirq target gateset.'
def assert_decompose_is_consistent_with_unitary(val: Any): """Uses `val._unitary_` to check `val._phase_by_`'s behavior.""" expected = protocols.unitary(val) qubit_count = len(expected).bit_length() - 1 if isinstance(val, ops.Operation): qubits = val.qubits dec = protocols.decompose_once(val) else: qubits = tuple(line.LineQubit.range(qubit_count)) dec = protocols.decompose_once_with_qubits(val, qubits) actual = circuits.Circuit.from_ops(dec).to_unitary_matrix( qubit_order=qubits) lin_alg_utils.assert_allclose_up_to_global_phase(actual, expected, atol=1e-8)
def _decompose_(self): result = protocols.decompose_once_with_qubits(self.gate, self.qubits, NotImplemented) if result is not NotImplemented: return result if isinstance(self.sub_operation.gate, matrix_gates.MatrixGate): # Default decompositions of 2/3 qubit `cirq.MatrixGate` ignores global phase, which is # local phase in the controlled variant and hence cannot be ignored. return NotImplemented result = protocols.decompose_once(self.sub_operation, NotImplemented) if result is NotImplemented: return NotImplemented return [ op.controlled_by(*self.controls, control_values=self.control_values) for op in result ]
def _decompose_(self) -> 'cirq.OP_TREE': return protocols.decompose_once(self.sub_operation, default=None)
def is_an_indecomposable_operation(operation: cirq.GateOperation) -> bool: if decompose_once(operation, default=NotImplemented) is not NotImplemented: return False # TODO: add new decomposers return True