def decompose_to_target_gateset(self, op: 'cirq.Operation', moment_idx: int) -> DecomposeResult: if not 1 <= protocols.num_qubits(op) <= 2: return self._decompose_multi_qubit_operation(op, moment_idx) if protocols.num_qubits(op) == 1: return self._decompose_single_qubit_operation(op, moment_idx) new_optree = self._decompose_two_qubit_operation(op, moment_idx) if new_optree is NotImplemented or new_optree is None: return new_optree new_optree = [*ops.flatten_to_ops_or_moments(new_optree)] op_untagged = op.untagged old_optree = ([*op_untagged.circuit] if isinstance(op_untagged, circuits.CircuitOperation) and self._intermediate_result_tag in op.tags else [op]) old_2q_gate_count = sum(1 for o in ops.flatten_to_ops(old_optree) if len(o.qubits) == 2) new_2q_gate_count = sum(1 for o in ops.flatten_to_ops(new_optree) if len(o.qubits) == 2) switch_to_new = (any( protocols.num_qubits(op) == 2 and op not in self for op in ops.flatten_to_ops(old_optree)) or new_2q_gate_count < old_2q_gate_count) if switch_to_new: return new_optree mapped_old_optree: List['cirq.OP_TREE'] = [] for old_op in ops.flatten_to_ops(old_optree): if old_op in self: mapped_old_optree.append(old_op) else: decomposed_op = self._decompose_single_qubit_operation( old_op, moment_idx) if decomposed_op is None or decomposed_op is NotImplemented: return NotImplemented mapped_old_optree.append(decomposed_op) return mapped_old_optree
def __init__( self, operations: 'cirq.OP_TREE', qubits: Tuple['cirq.Qid', ...], header: str = '', precision: int = 10, version: str = '2.0', ) -> None: """Representation of a circuit in QASM format. Args: operations: Tree of operations to insert. qubits: The qubits used in the operations. header: A multi-line string that is placed in a comment at the top of the QASM. precision: The number of digits after the decimal to show for numbers in the QASM code. version: The QASM version to target. Objects may return different QASM depending on version. """ self.operations = tuple(ops.flatten_to_ops(operations)) self.qubits = qubits self.header = header self.measurements = tuple(op for op in self.operations if isinstance(op.gate, ops.MeasurementGate)) meas_key_id_map, meas_comments = self._generate_measurement_ids() self.meas_comments = meas_comments qubit_id_map = self._generate_qubit_ids() self.args = protocols.QasmArgs( precision=precision, version=version, qubit_id_map=qubit_id_map, meas_key_id_map=meas_key_id_map, )
def decompose_arbitrary_into_syc_analytic(qubit_a: ops.Qid, qubit_b: ops.Qid, op: ops.GateOperation): """Synthesize an arbitrary 2 qubit operation to a sycamore operation using the given Tabulation. Args: qubit_a: first qubit of the operation qubit_b: second qubit of the operation op: operation to decompose tabulation: A tabulation for the Sycamore gate. Returns: New operations iterable object """ new_ops = optimizers.two_qubit_matrix_to_operations(op.qubits[0], op.qubits[1], op, allow_partial_czs=True) gate_ops = [] for new_op in new_ops: num_qubits = len(new_op.qubits) if num_qubits == 1: gate_ops.extend([ term.on(new_op.qubits[0]) for term in optimizers.single_qubit_matrix_to_gates( protocols.unitary(new_op)) ]) elif num_qubits == 2: gate_ops.extend( ops.flatten_to_ops( known_two_q_operations_to_sycamore_operations( new_op.qubits[0], new_op.qubits[1], cast(ops.GateOperation, new_op)))) return gate_ops
def _core_iterator( self, circuit: circuits.Circuit, sim_state: 'MPSState', ): """Iterator over MPSSimulatorStepResult from Moments of a Circuit Args: circuit: The circuit to simulate. sim_state: The initial state args for the simulation in the computational basis. Yields: MPSStepResult from simulating a Moment of the Circuit. """ if len(circuit) == 0: yield MPSSimulatorStepResult( measurements=sim_state.log_of_measurement_results, state=sim_state ) return noisy_moments = self.noise.noisy_moments(circuit, sorted(circuit.all_qubits())) for op_tree in noisy_moments: for op in flatten_to_ops(op_tree): if protocols.is_measurement(op) or protocols.has_mixture(op): sim_state.axes = tuple(sim_state.qubit_map[qubit] for qubit in op.qubits) protocols.act_on(op, sim_state) else: raise NotImplementedError(f"Unrecognized operation: {op!r}") yield MPSSimulatorStepResult( measurements=sim_state.log_of_measurement_results, state=sim_state ) sim_state.log_of_measurement_results.clear()
def decompose_arbitrary_into_syc_analytic(qubit_a: ops.Qid, qubit_b: ops.Qid, op: ops.Operation) -> ops.OP_TREE: """Synthesize an arbitrary 2 qubit operation to a sycamore operation using the given Tabulation. Args: qubit_a: first qubit of the operation qubit_b: second qubit of the operation op: operation to decompose tabulation: A tabulation for the Sycamore gate. Returns: New operations iterable object """ new_ops = optimizers.two_qubit_matrix_to_operations(qubit_a, qubit_b, op, allow_partial_czs=True) for new_op in new_ops: num_qubits = len(new_op.qubits) if num_qubits == 1: (a, ) = new_op.qubits yield from _phased_x_z_ops(protocols.unitary(new_op), a) elif num_qubits == 2: a, b = op.qubits yield from ops.flatten_to_ops( known_two_q_operations_to_sycamore_operations(a, b, new_op))
def _base_iterator( self, circuit: circuits.Circuit, qubit_order: ops.QubitOrderOrList, initial_state: int ) -> Iterator['cirq.contrib.quimb.mps_simulator.MPSSimulatorStepResult']: """Iterator over MPSSimulatorStepResult 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 in the computational basis. Represented as a big endian int. Yields: MPSStepResult 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 MPSSimulatorStepResult( measurements={}, state=MPSState( qubit_map, self.simulation_options, self.grouping, initial_state=initial_state, ), ) return state = MPSState( qubit_map, self.simulation_options, self.grouping, initial_state=initial_state, ) noisy_moments = self.noise.noisy_moments(circuit, sorted(circuit.all_qubits())) for op_tree in noisy_moments: measurements: Dict[str, List[int]] = collections.defaultdict(list) for op in flatten_to_ops(op_tree): if isinstance(op.gate, ops.MeasurementGate): key = str(protocols.measurement_key(op)) measurements[key].extend( state.perform_measurement(op.qubits, self.prng)) elif protocols.has_mixture(op): state.apply_op(op, self.prng) else: raise NotImplementedError( f"Unrecognized operation: {op!r}") yield MPSSimulatorStepResult(measurements=measurements, state=state)
def _core_iterator( self, circuit: circuits.AbstractCircuit, sim_state: OperationTarget[TActOnArgs], all_measurements_are_terminal: bool = False, ) -> Iterator[TStepResultBase]: """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. all_measurements_are_terminal: Whether all measurements in the given circuit are terminal. Yields: StepResults from simulating a Moment of the Circuit. Raises: TypeError: The simulator encounters an op it does not support. """ if len(circuit) == 0: yield self._create_step_result(sim_state) 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 # Preprocess measurements 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) # Simulate the operation protocols.act_on(op, sim_state) except TypeError: raise TypeError( f"{self.__class__.__name__} doesn't support {op!r}") step_result = self._create_step_result(sim_state) yield step_result sim_state = step_result._sim_state
def _base_iterator( self, circuit: circuits.Circuit, qubit_order: ops.QubitOrderOrList, initial_state: int) -> Iterator['MPSSimulatorStepResult']: """Iterator over MPSSimulatorStepResult 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 in the computational basis. Represented as a big endian int. Yields: MPSStepResult 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 MPSSimulatorStepResult( measurements={}, state=MPSState( qubit_map, self.prng, self.simulation_options, self.grouping, initial_state=initial_state, ), ) return state = MPSState( qubit_map, self.prng, self.simulation_options, self.grouping, initial_state=initial_state, ) noisy_moments = self.noise.noisy_moments(circuit, sorted(circuit.all_qubits())) for op_tree in noisy_moments: for op in flatten_to_ops(op_tree): if protocols.is_measurement(op) or protocols.has_mixture(op): state.axes = tuple(qubit_map[qubit] for qubit in op.qubits) protocols.act_on(op, state) else: raise NotImplementedError( f"Unrecognized operation: {op!r}") yield MPSSimulatorStepResult( measurements=state.log_of_measurement_results, state=state) state.log_of_measurement_results.clear()
def _core_iterator( self, circuit: circuits.Circuit, sim_state: act_on_density_matrix_args.ActOnDensityMatrixArgs, all_measurements_are_terminal: bool = False, ): """Iterator over DensityMatrixStepResult from Moments of a Circuit Args: circuit: The circuit to simulate. sim_state: The initial state args for the simulation in the computational basis. all_measurements_are_terminal: Indicator that all measurements are terminal, allowing optimization. Yields: DensityMatrixStepResult from simulating a Moment of the Circuit. """ if len(circuit) == 0: yield DensityMatrixStepResult( density_matrix=sim_state.target_tensor, measurements=dict(sim_state.log_of_measurement_results), qubit_map=sim_state.qubit_map, dtype=self._dtype, ) return noisy_moments = self.noise.noisy_moments(circuit, sorted(circuit.all_qubits())) measured = collections.defaultdict( bool) # type: Dict[Tuple[cirq.Qid, ...], bool] for moment in noisy_moments: for op in flatten_to_ops(moment): # 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 protocols.is_measurement(op): 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) yield DensityMatrixStepResult( density_matrix=sim_state.target_tensor, measurements=dict(sim_state.log_of_measurement_results), qubit_map=sim_state.qubit_map, dtype=self._dtype, ) sim_state.log_of_measurement_results.clear()
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 _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()
def __init__(self, operations: 'cirq.OP_TREE', qubits: Tuple['cirq.Qid', ...], header: str = '', precision: int = 10, version: str = '2.0') -> None: self.operations = tuple(ops.flatten_to_ops(operations)) self.qubits = qubits self.header = header self.measurements = tuple(op for op in self.operations if isinstance(op.gate, ops.MeasurementGate)) meas_key_id_map, meas_comments = self._generate_measurement_ids() self.meas_comments = meas_comments qubit_id_map = self._generate_qubit_ids() self.args = protocols.QasmArgs(precision=precision, version=version, qubit_id_map=qubit_id_map, meas_key_id_map=meas_key_id_map)
def _base_iterator( self, circuit: circuits.Circuit, qubit_order: ops.QubitOrderOrList, initial_state: 'cirq.STATE_VECTOR_LIKE', perform_measurements: bool = True, ) -> Iterator['SparseSimulatorStep']: qubits = ops.QubitOrder.as_qubit_order(qubit_order).order_for( circuit.all_qubits()) num_qubits = len(qubits) qid_shape = protocols.qid_shape(qubits) qubit_map = {q: i for i, q in enumerate(qubits)} state = qis.to_valid_state_vector(initial_state, num_qubits, qid_shape=qid_shape, dtype=self._dtype) if len(circuit) == 0: yield SparseSimulatorStep(state, {}, qubit_map, self._dtype) sim_state = act_on_state_vector_args.ActOnStateVectorArgs( target_tensor=np.reshape(state, qid_shape), available_buffer=np.empty(qid_shape, dtype=self._dtype), axes=[], prng=self._prng, log_of_measurement_results={}, ) noisy_moments = self.noise.noisy_moments(circuit, sorted(circuit.all_qubits())) for op_tree in noisy_moments: for op in flatten_to_ops(op_tree): if perform_measurements or not isinstance( op.gate, ops.MeasurementGate): sim_state.axes = tuple(qubit_map[qubit] for qubit in op.qubits) protocols.act_on(op, sim_state) yield SparseSimulatorStep( state_vector=sim_state.target_tensor, measurements=dict(sim_state.log_of_measurement_results), qubit_map=qubit_map, dtype=self._dtype, ) sim_state.log_of_measurement_results.clear()
def optimize_circuit(self, circuit: Circuit): frontier: Dict['Qid', int] = defaultdict(lambda: 0) i = 0 while i < len(circuit): # Note: circuit may mutate as we go. for op in circuit[i].operations: # Don't touch stuff inserted by previous optimizations. if any(frontier[q] > i for q in op.qubits): continue # Skip if an optimization removed the circuit underneath us. if i >= len(circuit): continue # Skip if an optimization removed the op we're considering. if op not in circuit[i].operations: continue opt = self.optimization_at(circuit, i, op) # Skip if the optimization did nothing. if opt is None: continue # Clear target area, and insert new operations. circuit.clear_operations_touching( opt.clear_qubits, [e for e in range(i, i + opt.clear_span)]) new_operations = self.post_clean_up( cast(Tuple[ops.Operation], opt.new_operations)) flat_new_operations = tuple(ops.flatten_to_ops(new_operations)) new_qubits = set() for flat_op in flat_new_operations: for q in flat_op.qubits: new_qubits.add(q) if not new_qubits.issubset(set(opt.clear_qubits)): raise ValueError( 'New operations in PointOptimizer should not act on new qubits.' ) circuit.insert_at_frontier(flat_new_operations, i, frontier) i += 1
def _core_iterator( self, circuit: circuits.Circuit, sim_state: act_on_state_vector_args.ActOnStateVectorArgs, ): """Iterator over SparseSimulatorStep from Moments of a Circuit Args: circuit: The circuit to simulate. sim_state: The initial state args for the simulation in the computational basis. Yields: SparseSimulatorStep from simulating a Moment of the Circuit. """ if len(circuit) == 0: yield SparseSimulatorStep( state_vector=sim_state.target_tensor, measurements=dict(sim_state.log_of_measurement_results), qubit_map=sim_state.qubit_map, dtype=self._dtype, ) return noisy_moments = self.noise.noisy_moments(circuit, sorted(circuit.all_qubits())) for op_tree in noisy_moments: for op in flatten_to_ops(op_tree): sim_state.axes = tuple(sim_state.qubit_map[qubit] for qubit in op.qubits) protocols.act_on(op, sim_state) yield SparseSimulatorStep( state_vector=sim_state.target_tensor, measurements=dict(sim_state.log_of_measurement_results), qubit_map=sim_state.qubit_map, dtype=self._dtype, ) sim_state.log_of_measurement_results.clear()
def optimize_circuit(self, circuit: Circuit): moment_new_ops = defaultdict( list) # type: Dict[int, List['cirq.Operation']] circuit_len = len(circuit) for i in range(circuit_len): moment_new_ops[i] = [] for op in circuit[i].operations: opt = self.optimization_at(circuit, i, op) if opt is None: # keep the old operation if the optimization did nothing. moment_new_ops[i].append(op) circuit.clear_operations_touching(op.qubits, [i]) else: # Clear target area, and insert new operations. circuit.clear_operations_touching( opt.clear_qubits, [e for e in range(i, i + opt.clear_span)]) new_operations = self.post_clean_up( cast(Tuple[ops.Operation], opt.new_operations)) flat_new_operations = tuple( ops.flatten_to_ops(new_operations)) new_qubits = set() for flat_op in flat_new_operations: for q in flat_op.qubits: new_qubits.add(q) if not new_qubits.issubset(set(opt.clear_qubits)): raise ValueError( 'New operations in PointOptimizer should not act on new' ' qubits.') moment_new_ops[i].extend(flat_new_operations) frontier = 0 for i in range(circuit_len): new_frontier = circuit.insert(frontier, moment_new_ops[i], InsertStrategy.EARLIEST_AFTER) if frontier < new_frontier: frontier = new_frontier
def known_two_q_operations_to_sycamore_operations( self, qubit_a: ops.Qid, qubit_b: ops.Qid, op: ops.GateOperation) -> ops.OP_TREE: """ Synthesize a known gate operation to a sycamore operation This function dispatches based on gate type Args: qubit_a: first qubit of GateOperation qubit_b: second qubit of GateOperation op: operation to decompose Returns: New operations iterable object """ gate = op.gate if isinstance(gate, ops.CNotPowGate): return [ ops.Y(qubit_b)**-0.5, cphase( cast(ops.CNotPowGate, gate).exponent * np.pi, qubit_a, qubit_b), ops.Y(qubit_b)**0.5, ] elif isinstance(gate, ops.CZPowGate): gate = cast(ops.CZPowGate, gate) if math.isclose(gate.exponent, 1.0): # check if CZ or CPHASE return decompose_cz_into_syc(qubit_a, qubit_b) else: # because CZPowGate == diag([1, 1, 1, e^{i pi phi}]) return cphase(gate.exponent * np.pi, qubit_a, qubit_b) elif isinstance(gate, ops.SwapPowGate) and math.isclose( cast(ops.SwapPowGate, gate).exponent, 1.0): return decompose_swap_into_syc(qubit_a, qubit_b) elif isinstance(gate, ops.ISwapPowGate) and math.isclose( cast(ops.ISwapPowGate, gate).exponent, 1.0): return decompose_iswap_into_syc(qubit_a, qubit_b) elif isinstance(gate, ops.ZZPowGate): return rzz( cast(ops.ZZPowGate, gate).exponent * np.pi / 2, *op.qubits) elif isinstance(gate, ops.MatrixGate) and len(op.qubits) == 2: new_ops = optimizers.two_qubit_matrix_to_operations( op.qubits[0], op.qubits[1], op, allow_partial_czs=True) gate_ops = [] for new_op in new_ops: num_qubits = len(new_op.qubits) if num_qubits == 1: gate_ops.extend([ term.on(new_op.qubits[0]) for term in optimizers.single_qubit_matrix_to_gates( protocols.unitary(new_op)) ]) elif num_qubits == 2: gate_ops.extend( ops.flatten_to_ops( self.known_two_q_operations_to_sycamore_operations( new_op.qubits[0], new_op.qubits[1], cast(ops.GateOperation, new_op)))) return gate_ops else: raise ValueError("Unrecognized gate: {!r}".format(op))
def _base_iterator( self, circuit: circuits.Circuit, qubit_order: ops.QubitOrderOrList, initial_state: Union[np.ndarray, 'cirq.STATE_VECTOR_LIKE'], all_measurements_are_terminal=False, is_raw_state=False, ) -> Iterator['DensityMatrixStepResult']: qubits = ops.QubitOrder.as_qubit_order(qubit_order).order_for( circuit.all_qubits()) qid_shape = protocols.qid_shape(qubits) qubit_map = {q: i for i, q in enumerate(qubits)} initial_matrix = (qis.to_valid_density_matrix(initial_state, len(qid_shape), qid_shape=qid_shape, dtype=self._dtype) if not is_raw_state else initial_state) if np.may_share_memory(initial_matrix, initial_state): initial_matrix = initial_matrix.copy() if len(circuit) == 0: yield DensityMatrixStepResult(initial_matrix, {}, qubit_map, self._dtype) return tensor = initial_matrix.reshape(qid_shape * 2) sim_state = act_on_density_matrix_args.ActOnDensityMatrixArgs( target_tensor=tensor, available_buffer=[np.empty_like(tensor) for _ in range(3)], axes=[], qid_shape=qid_shape, prng=self._prng, log_of_measurement_results={}, ) noisy_moments = self.noise.noisy_moments(circuit, sorted(circuit.all_qubits())) measured = collections.defaultdict( bool) # type: Dict[Tuple[cirq.Qid, ...], bool] for moment in noisy_moments: for op in flatten_to_ops(moment): # 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 protocols.is_measurement(op): 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(qubit_map[qubit] for qubit in op.qubits) protocols.act_on(op, sim_state) yield DensityMatrixStepResult( density_matrix=sim_state.target_tensor, measurements=dict(sim_state.log_of_measurement_results), qubit_map=qubit_map, dtype=self._dtype, ) sim_state.log_of_measurement_results.clear()
def decompose( val: TValue, *, intercepting_decomposer: Optional[OpDecomposer] = None, fallback_decomposer: Optional[OpDecomposer] = None, keep: Optional[Callable[['cirq.Operation'], bool]] = None, on_stuck_raise: Union[None, Exception, Callable[[ 'cirq.Operation' ], Union[None, Exception]]] = _value_error_describing_bad_operation ) -> List['cirq.Operation']: """Recursively decomposes a value into `cirq.Operation`s meeting a criteria. Args: val: The value to decompose into operations. intercepting_decomposer: An optional method that is called before the default decomposer (the value's `_decompose_` method). If `intercepting_decomposer` is specified and returns a result that isn't `NotImplemented` or `None`, that result is used. Otherwise the decomposition falls back to the default decomposer. Note that `val` will be passed into `intercepting_decomposer`, even if `val` isn't a `cirq.Operation`. fallback_decomposer: An optional decomposition that used after the `intercepting_decomposer` and the default decomposer (the value's `_decompose_` method) both fail. keep: A predicate that determines if the initial operation or intermediate decomposed operations should be kept or else need to be decomposed further. If `keep` isn't specified, it defaults to "value can't be decomposed anymore". on_stuck_raise: If there is an operation that can't be decomposed and also can't be kept, `on_stuck_raise` is used to determine what error to raise. `on_stuck_raise` can either directly be an `Exception`, or a method that takes the problematic operation and returns an `Exception`. If `on_stuck_raise` is set to `None` or a method that returns `None`, undecomposable operations are simply silently kept. `on_stuck_raise` defaults to a `ValueError` describing the unwanted undecomposable operation. Returns: A list of operations that the given value was decomposed into. If `on_stuck_raise` isn't set to None, all operations in the list will satisfy the predicate specified by `keep`. Raises: TypeError: `val` isn't a `cirq.Operation` and can't be decomposed even once. (So it's not possible to return a list of operations.) ValueError: Default type of error raised if there's an undecomposable operation that doesn't satisfy the given `keep` predicate. TError: Custom type of error raised if there's an undecomposable operation that doesn't satisfy the given `keep` predicate. """ if (on_stuck_raise is not _value_error_describing_bad_operation and keep is None): raise ValueError( "Must specify 'keep' if specifying 'on_stuck_raise', because it's " "not possible to get stuck if you don't have a criteria on what's " "acceptable to keep.") def try_op_decomposer(val: Any, decomposer: Optional[OpDecomposer] ) -> DecomposeResult: if decomposer is None or not isinstance(val, ops.Operation): return None return decomposer(val) output = [] queue: List[Any] = [val] while queue: item = queue.pop(0) if isinstance(item, ops.Operation) and keep is not None and keep(item): output.append(item) continue decomposed = try_op_decomposer(item, intercepting_decomposer) if decomposed is NotImplemented or decomposed is None: decomposed = decompose_once(item, default=None) if decomposed is NotImplemented or decomposed is None: decomposed = try_op_decomposer(item, fallback_decomposer) if decomposed is not NotImplemented and decomposed is not None: queue[:0] = ops.flatten_to_ops(decomposed) continue if not isinstance(item, ops.Operation) and isinstance(item, Iterable): queue[:0] = ops.flatten_to_ops(item) continue if keep is not None and on_stuck_raise is not None: if isinstance(on_stuck_raise, Exception): raise on_stuck_raise elif callable(on_stuck_raise): error = on_stuck_raise(item) if error is not None: raise error output.append(item) return output
def is_supported(self, op_tree: 'cirq.OP_TREE') -> bool: """Whether the given object contains only supported operations.""" return all(self.is_supported_operation(op) for op in ops.flatten_to_ops(op_tree))