def find_terminal_measurements( circuit: 'cirq.AbstractCircuit', ) -> List[Tuple[int, 'cirq.Operation']]: """Finds all terminal measurements in the given circuit. A measurement is terminal if there are no other operations acting on the measured qubits after the measurement operation occurs in the circuit. Args: circuit: The circuit to find terminal measurements in. Returns: List of terminal measurements, each specified as (moment_index, measurement_operation). """ open_qubits: Set['cirq.Qid'] = set(circuit.all_qubits()) seen_control_keys: Set['cirq.MeasurementKey'] = set() terminal_measurements: List[Tuple[int, 'cirq.Operation']] = [] for i in range(len(circuit) - 1, -1, -1): moment = circuit[i] for q in open_qubits: op = moment.operation_at(q) if ( op is not None and open_qubits.issuperset(op.qubits) and protocols.is_measurement(op) and not (seen_control_keys & protocols.measurement_key_objs(op)) ): terminal_measurements.append((i, op)) open_qubits -= moment.qubits seen_control_keys |= protocols.control_keys(moment) if not open_qubits: break return terminal_measurements
def _assert_meets_standards_helper( val: Any, *, ignoring_global_phase: bool, setup_code: str, global_vals: Optional[Dict[str, Any]], local_vals: Optional[Dict[str, Any]], ) -> None: __tracebackhide__ = True # pylint: disable=unused-variable assert_consistent_resolve_parameters(val) assert_specifies_has_unitary_if_unitary(val) assert_has_consistent_qid_shape(val) assert_has_consistent_apply_unitary(val) assert_all_implemented_act_on_effects_match_unitary(val) assert_qasm_is_consistent_with_unitary(val) assert_has_consistent_trace_distance_bound(val) assert_decompose_is_consistent_with_unitary(val, ignoring_global_phase=ignoring_global_phase) assert_phase_by_is_consistent_with_unitary(val) assert_pauli_expansion_is_consistent_with_unitary(val) assert_equivalent_repr( val, setup_code=setup_code, global_vals=global_vals, local_vals=local_vals ) assert protocols.measurement_key_objs(val) == protocols.measurement_key_names(val) if isinstance(val, ops.EigenGate): assert_eigen_shifts_is_consistent_with_eigen_components(val) if isinstance(val, ops.Gate): assert_controlled_and_controlled_by_identical(val)
def _measurement_key_objs_(self) -> AbstractSet['cirq.MeasurementKey']: if self._cached_measurement_key_objs is None: circuit_keys = protocols.measurement_key_objs(self.circuit) if circuit_keys and self.use_repetition_ids: self._ensure_deterministic_loop_count() if self.repetition_ids is not None: circuit_keys = { key.with_key_path_prefix(repetition_id) for repetition_id in self.repetition_ids for key in circuit_keys } circuit_keys = { key.with_key_path_prefix(*self.parent_path) for key in circuit_keys } object.__setattr__( self, '_cached_measurement_key_objs', { protocols.with_measurement_key_mapping( key, self.measurement_key_map) for key in circuit_keys }, ) return self._cached_measurement_key_objs # type: ignore
def with_operation(self, operation: 'cirq.Operation') -> 'cirq.Moment': """Returns an equal moment, but with the given op added. Args: operation: The operation to append. Returns: The new moment. Raises: ValueError: If the operation given overlaps a current operation in the moment. """ if any(q in self._qubits for q in operation.qubits): raise ValueError(f'Overlapping operations: {operation}') # Use private variables to facilitate a quick copy. m = Moment() m._operations = self._operations + (operation, ) m._sorted_operations = None m._qubits = self._qubits.union(operation.qubits) m._qubit_to_op = { **self._qubit_to_op, **{q: operation for q in operation.qubits} } m._measurement_key_objs = self._measurement_key_objs_().union( protocols.measurement_key_objs(operation)) m._control_keys = self._control_keys_().union( protocols.control_keys(operation)) return m
def _op_info_with_fallback( op: 'cirq.Operation', args: 'cirq.CircuitDiagramInfoArgs' ) -> 'cirq.CircuitDiagramInfo': info = protocols.circuit_diagram_info(op, args, None) rows: List[LabelEntity] = list(op.qubits) if args.label_map is not None: rows += protocols.measurement_key_objs(op) & args.label_map.keys() rows += protocols.control_keys(op) & args.label_map.keys() if info is not None: if max(1, len(rows)) != len(info.wire_symbols): raise ValueError(f'Wanted diagram info from {op!r} for {rows!r}) but got {info!r}') return info # Use the untagged operation's __str__. name = str(op.untagged) # Representation usually looks like 'gate(qubit1, qubit2, etc)'. # Try to cut off the qubit part, since that would be redundant. redundant_tail = f"({', '.join(str(e) for e in op.qubits)})" if name.endswith(redundant_tail): name = name[: -len(redundant_tail)] # Add tags onto the representation, if they exist if op.tags: name += f'{list(op.tags)}' # Include ordering in the qubit labels. symbols = (name,) + tuple(f'#{i + 1}' for i in range(1, len(op.qubits))) return protocols.CircuitDiagramInfo(wire_symbols=symbols)
def _measurement_key_objs_(self) -> AbstractSet['cirq.MeasurementKey']: if self._measurement_key_objs is None: self._measurement_key_objs = { key for op in self.operations for key in protocols.measurement_key_objs(op) } return self._measurement_key_objs
def _commutes_( self, other: Any, *, atol: Union[int, float] = 1e-8) -> Union[bool, NotImplementedType, None]: """Determine if this Operation commutes with the object""" if not isinstance(other, Operation): return NotImplemented self_keys = protocols.measurement_key_objs(self) other_keys = protocols.measurement_key_objs(other) if (not self_keys.isdisjoint(other_keys) or not protocols.control_keys(self).isdisjoint(other_keys) or not protocols.control_keys(other).isdisjoint(self_keys)): return False if hasattr(other, 'qubits') and set(self.qubits).isdisjoint( other.qubits): return True from cirq import circuits # Remove the classical controls to validate the quantum commutativity. This can be done # because during execution, the two operations will either both be run, in which case they # behave like the suboperations, so if the suboperations commute then these commute. Or # one of them is cold in which case it behaves like the identity, which always commutes. self_raw = self.without_classical_controls() other_raw = other.without_classical_controls() circuit12 = circuits.Circuit(self_raw, other_raw) circuit21 = circuits.Circuit(other_raw, self_raw) # Don't create gigantic matrices. shape = protocols.qid_shape_protocol.qid_shape(circuit12) if np.prod(shape, dtype=np.int64) > 2**10: return NotImplemented # coverage: ignore m12 = protocols.unitary_protocol.unitary(circuit12, default=None) m21 = protocols.unitary_protocol.unitary(circuit21, default=None) if m12 is None or m21 is None: return NotImplemented return np.allclose(m12, m21, atol=atol)
def add_op_to_moment(self, moment_index: int, op: 'cirq.Operation') -> None: self.ops_by_index[moment_index][op] = 0 for q in op.qubits: if moment_index > self.qubit_indexes[q][-1]: self.qubit_indexes[q].append(moment_index) else: bisect.insort(self.qubit_indexes[q], moment_index) for mkey in protocols.measurement_key_objs(op): bisect.insort(self.mkey_indexes[mkey], moment_index) for ckey in protocols.control_keys(op): bisect.insort(self.ckey_indexes[ckey], moment_index)
def remove_op_from_moment(self, moment_index: int, op: 'cirq.Operation') -> None: self.ops_by_index[moment_index].pop(op) for q in op.qubits: if self.qubit_indexes[q][-1] == moment_index: self.qubit_indexes[q].pop() else: self.qubit_indexes[q].remove(moment_index) for mkey in protocols.measurement_key_objs(op): self.mkey_indexes[mkey].remove(moment_index) for ckey in protocols.control_keys(op): self.ckey_indexes[ckey].remove(moment_index)
def __post_init__(self): if not isinstance(self.circuit, circuits.FrozenCircuit): raise TypeError( f'Expected circuit of type FrozenCircuit, got: {type(self.circuit)!r}' ) # Ensure that the circuit is invertible if the repetitions are negative. if self.repetitions < 0: try: protocols.inverse(self.circuit.unfreeze()) except TypeError: raise ValueError( 'repetitions are negative but the circuit is not invertible' ) # Initialize repetition_ids to default, if unspecified. Else, validate their length. loop_size = abs(self.repetitions) if not self.repetition_ids: object.__setattr__(self, 'repetition_ids', self._default_repetition_ids()) elif len(self.repetition_ids) != loop_size: raise ValueError( f'Expected repetition_ids to be a list of length {loop_size}, ' f'got: {self.repetition_ids}') # Disallow mapping to keys containing the `MEASUREMENT_KEY_SEPARATOR` for mapped_key in self.measurement_key_map.values(): if value.MEASUREMENT_KEY_SEPARATOR in mapped_key: raise ValueError( f'Mapping to invalid key: {mapped_key}. "{value.MEASUREMENT_KEY_SEPARATOR}" ' 'is not allowed for measurement keys in a CircuitOperation' ) # Disallow qid mapping dimension conflicts. for q, q_new in self.qubit_map.items(): if q_new.dimension != q.dimension: raise ValueError( f'Qid dimension conflict.\nFrom qid: {q}\nTo qid: {q_new}') if self.repeat_until: if self.use_repetition_ids or self.repetitions != 1: raise ValueError('Cannot use repetitions with repeat_until') if protocols.measurement_key_objs( self._mapped_single_loop()).isdisjoint( self.repeat_until.keys): raise ValueError( 'Infinite loop: condition is not modified in subcircuit.') # Ensure that param_resolver is converted to an actual ParamResolver. object.__setattr__(self, 'param_resolver', study.ParamResolver(self.param_resolver))
def _measurement_key_objs(self) -> FrozenSet['cirq.MeasurementKey']: circuit_keys = protocols.measurement_key_objs(self.circuit) if circuit_keys and self.use_repetition_ids: self._ensure_deterministic_loop_count() if self.repetition_ids is not None: circuit_keys = frozenset( key.with_key_path_prefix(repetition_id) for repetition_id in self.repetition_ids for key in circuit_keys) circuit_keys = frozenset( key.with_key_path_prefix(*self.parent_path) for key in circuit_keys) return frozenset( protocols.with_measurement_key_mapping( key, self.measurement_key_map) for key in circuit_keys)
def get_mergeable_ops( self, op: 'cirq.Operation', op_qs: Set['cirq.Qid']) -> Tuple[int, List['cirq.Operation']]: # Find the index of previous moment which can be merged with `op`. idx = max([self.qubit_indexes[q][-1] for q in op_qs], default=-1) idx = max([idx] + [ self.mkey_indexes[ckey][-1] for ckey in protocols.control_keys(op) ]) idx = max([idx] + [ self.ckey_indexes[mkey][-1] for mkey in protocols.measurement_key_objs(op) ]) # Return the set of overlapping ops in moment with index `idx`. if idx == -1: return idx, [] return idx, [ left_op for left_op in self.ops_by_index[idx] if not op_qs.isdisjoint(left_op.qubits) ]
def __init__( self, sub_operation: 'cirq.Operation', conditions: Sequence[Union[str, 'cirq.MeasurementKey', 'cirq.Condition', sympy.Basic]], ): """Initializes a `ClassicallyControlledOperation`. Multiple consecutive `ClassicallyControlledOperation` layers are squashed when possible, so one should not depend on a specific number of layers. Args: sub_operation: The operation to gate with a classical control condition. conditions: A sequence of measurement keys, or strings that can be parsed into measurement keys. Raises: ValueError: If an unsupported gate is being classically controlled. """ if protocols.measurement_key_objs(sub_operation): raise ValueError( f'Cannot conditionally run operations with measurements: {sub_operation}' ) conditions = tuple(conditions) if isinstance(sub_operation, ClassicallyControlledOperation): conditions += sub_operation._conditions sub_operation = sub_operation._sub_operation conds: List['cirq.Condition'] = [] for c in conditions: if isinstance(c, str): c = value.MeasurementKey.parse_serialized(c) if isinstance(c, value.MeasurementKey): c = value.KeyCondition(c) if isinstance(c, sympy.Basic): c = value.SympyCondition(c) conds.append(cast('cirq.Condition', c)) self._conditions: Tuple['cirq.Condition', ...] = tuple(conds) self._sub_operation: 'cirq.Operation' = sub_operation
def with_operations(self, *contents: 'cirq.OP_TREE') -> 'cirq.Moment': """Returns a new moment with the given contents added. Args: *contents: New operations to add to this moment. Returns: The new moment. Raises: ValueError: If the contents given overlaps a current operation in the moment. """ flattened_contents = tuple(op_tree.flatten_to_ops(contents)) m = Moment() # Use private variables to facilitate a quick copy. m._qubit_to_op = self._qubit_to_op.copy() qubits = set(self._qubits) for op in flattened_contents: if any(q in qubits for q in op.qubits): raise ValueError(f'Overlapping operations: {op}') qubits.update(op.qubits) for q in op.qubits: m._qubit_to_op[q] = op m._qubits = frozenset(qubits) m._operations = self._operations + flattened_contents m._sorted_operations = None m._measurement_key_objs = self._measurement_key_objs_().union( set( itertools.chain(*(protocols.measurement_key_objs(op) for op in flattened_contents)))) m._control_keys = self._control_keys_().union( set( itertools.chain(*(protocols.control_keys(op) for op in flattened_contents)))) return m
def _measurement_key_objs_(self) -> AbstractSet[value.MeasurementKey]: if self._cached_measurement_key_objs is None: circuit_keys = protocols.measurement_key_objs(self.circuit) if self.repetition_ids is not None: circuit_keys = { key.with_key_path_prefix(repetition_id) for repetition_id in self.repetition_ids for key in circuit_keys } circuit_keys = { protocols.with_key_path_prefix(key, self.parent_path) for key in circuit_keys } object.__setattr__( self, '_cached_measurement_key_objs', { protocols.with_measurement_key_mapping( key, self.measurement_key_map) for key in circuit_keys }, ) return self._cached_measurement_key_objs # type: ignore
def _measurement_key_objs_(self) -> AbstractSet['cirq.MeasurementKey']: return protocols.measurement_key_objs(self.sub_operation)
def __init__( self, circuit: 'cirq.FrozenCircuit', repetitions: int = 1, qubit_map: Optional[Dict['cirq.Qid', 'cirq.Qid']] = None, measurement_key_map: Optional[Dict[str, str]] = None, param_resolver: Optional[study.ParamResolverOrSimilarType] = None, repetition_ids: Optional[Sequence[str]] = None, parent_path: Tuple[str, ...] = (), extern_keys: FrozenSet['cirq.MeasurementKey'] = frozenset(), use_repetition_ids: bool = True, repeat_until: Optional['cirq.Condition'] = None, ): """Initializes a CircuitOperation. Args: circuit: The FrozenCircuit wrapped by this operation. repetitions: How many times the circuit should be repeated. This can be integer, or a sympy expression. If sympy, the expression must resolve to an integer, or float within 0.001 of integer, at runtime. qubit_map: Remappings for qubits in the circuit. measurement_key_map: Remappings for measurement keys in the circuit. The keys and values should be unindexed (i.e. without repetition_ids). The values cannot contain the `MEASUREMENT_KEY_SEPARATOR`. param_resolver: Resolved values for parameters in the circuit. repetition_ids: List of identifiers for each repetition of the CircuitOperation. If populated, the length should be equal to the repetitions. If not populated and abs(`repetitions`) > 1, it is initialized to strings for numbers in `range(repetitions)`. parent_path: A tuple of identifiers for any parent CircuitOperations containing this one. extern_keys: The set of measurement keys defined at extern scope. The values here are used by decomposition and simulation routines to cache which external measurement keys exist as possible binding targets for unbound `ClassicallyControlledOperation` keys. This field is not intended to be set or changed manually, and should be empty in circuits that aren't in the middle of decomposition. use_repetition_ids: When True, any measurement key in the subcircuit will have its path prepended with the repetition id for each repetition. When False, this will not happen and the measurement key will be repeated. repeat_until: A condition that will be tested after each iteration of the subcircuit. The subcircuit will repeat until condition returns True, but will always run at least once, and the measurement key need not be defined prior to the subcircuit (but must be defined in a measurement within the subcircuit). This field is incompatible with repetitions or repetition_ids. Raises: TypeError: if repetitions is not an integer or sympy expression, or if the provided circuit is not a FrozenCircuit. ValueError: if any of the following conditions is met. - Negative repetitions on non-invertible circuit - Number of repetition IDs does not match repetitions - Repetition IDs used with parameterized repetitions - Conflicting qubit dimensions in qubit_map - Measurement key map has invalid key names - repeat_until used with other repetition controls - Key(s) in repeat_until are not modified by circuit """ # This fields is exclusively for use in decomposition. It should not be # referenced outside this class. self._extern_keys = extern_keys # All other fields are pseudo-private: read access is allowed via the # @property methods, but mutation is prohibited. self._param_resolver = study.ParamResolver(param_resolver) self._parent_path = parent_path self._circuit = circuit if not isinstance(self._circuit, circuits.FrozenCircuit): raise TypeError( f'Expected circuit of type FrozenCircuit, got: {type(self._circuit)!r}' ) # Ensure that the circuit is invertible if the repetitions are negative. self._repetitions = repetitions self._repetition_ids = None if repetition_ids is None else list( repetition_ids) self._use_repetition_ids = use_repetition_ids if isinstance(self._repetitions, float): if math.isclose(self._repetitions, round(self._repetitions)): self._repetitions = round(self._repetitions) if isinstance(self._repetitions, INT_CLASSES): if self._repetitions < 0: try: protocols.inverse(self._circuit.unfreeze()) except TypeError: raise ValueError( 'repetitions are negative but the circuit is not invertible' ) # Initialize repetition_ids to default, if unspecified. Else, validate their length. loop_size = abs(self._repetitions) if not self._repetition_ids: self._repetition_ids = self._default_repetition_ids() elif len(self._repetition_ids) != loop_size: raise ValueError( f'Expected repetition_ids to be a list of length {loop_size}, ' f'got: {self._repetition_ids}') elif isinstance(self._repetitions, sympy.Expr): if self._repetition_ids is not None: raise ValueError( 'Cannot use repetition ids with parameterized repetitions') else: raise TypeError(f'Only integer or sympy repetitions are allowed.\n' f'User provided: {self._repetitions}') # Disallow qid mapping dimension conflicts. self._qubit_map = dict(qubit_map or {}) for q, q_new in self._qubit_map.items(): if q_new.dimension != q.dimension: raise ValueError( f'Qid dimension conflict.\nFrom qid: {q}\nTo qid: {q_new}') self._measurement_key_map = dict(measurement_key_map or {}) # Disallow mapping to keys containing the `MEASUREMENT_KEY_SEPARATOR` for mapped_key in self._measurement_key_map.values(): if value.MEASUREMENT_KEY_SEPARATOR in mapped_key: raise ValueError( f'Mapping to invalid key: {mapped_key}. "{value.MEASUREMENT_KEY_SEPARATOR}" ' 'is not allowed for measurement keys in a CircuitOperation' ) self._repeat_until = repeat_until if self._repeat_until: if self._use_repetition_ids or self._repetitions != 1: raise ValueError('Cannot use repetitions with repeat_until') if protocols.measurement_key_objs( self._mapped_single_loop()).isdisjoint( self._repeat_until.keys): raise ValueError( 'Infinite loop: condition is not modified in subcircuit.')
def _measurement_key_objs_(self) -> FrozenSet['cirq.MeasurementKey']: if self._measurement_key_objs is None: self._measurement_key_objs = frozenset( key for op in self.operations for key in protocols.measurement_key_objs(op)) return self._measurement_key_objs