def drop_negligible_operations( circuit: 'cirq.AbstractCircuit', *, context: Optional['cirq.TransformerContext'] = None, atol: float = 1e-8, ) -> 'cirq.Circuit': """Removes operations with tiny effects. An operation `op` is considered to have a tiny effect if `cirq.trace_distance_bound(op) <= atol`. Args: circuit: Input circuit to transform. context: `cirq.TransformerContext` storing common configurable options for transformers. atol: Absolute tolerance to determine if an operation `op` is negligible -- i.e. if `cirq.trace_distance_bound(op) <= atol`. Returns: Copy of the transformed input circuit. """ if context is None: context = transformer_api.TransformerContext() def map_func(op: 'cirq.Operation', _: int) -> 'cirq.OP_TREE': return ( op if protocols.is_measurement(op) or protocols.trace_distance_bound(op) > atol else [] ) return transformer_primitives.map_operations( circuit, map_func, tags_to_ignore=context.tags_to_ignore, deep=context.deep ).unfreeze(copy=False)
def drop_terminal_measurements( circuit: 'cirq.AbstractCircuit', *, context: Optional['cirq.TransformerContext'] = transformer_api. TransformerContext(deep=True), ) -> 'cirq.Circuit': """Removes terminal measurements from a circuit. This transformer is helpful when trying to capture the final state vector of a circuit with many terminal measurements, as simulating the circuit with those measurements in place would otherwise collapse the final state. Args: circuit: The circuit to transform. It will not be modified. context: `cirq.TransformerContext` storing common configurable options for transformers. The default has `deep=True`, as "terminal measurements" is ill-defined without inspecting subcircuits; passing a context with `deep=False` will return an error. Returns: A copy of the circuit, with identity or X gates in place of terminal measurements. Raises: ValueError: if the circuit contains non-terminal measurements, or if the provided context has`deep=False`. """ if context is None or not context.deep: raise ValueError( 'Context has `deep=False`, but `deep=True` is required to drop terminal measurements.' ) if not circuit.are_all_measurements_terminal(): raise ValueError('Circuit contains a non-terminal measurement.') def flip_inversion(op: 'cirq.Operation', _) -> 'cirq.OP_TREE': if isinstance(op.gate, ops.MeasurementGate): return [ ops.X(q) if b else ops.I(q) for q, b in zip(op.qubits, op.gate.full_invert_mask()) ] return op ignored = () if context is None else context.tags_to_ignore return transformer_primitives.map_operations( circuit, flip_inversion, deep=context.deep if context else True, tags_to_ignore=ignored).unfreeze()
def dephase_measurements( circuit: 'cirq.AbstractCircuit', *, context: Optional['cirq.TransformerContext'] = transformer_api. TransformerContext(deep=True), ) -> 'cirq.Circuit': """Changes all measurements to a dephase operation. This transformer is useful when using a density matrix simulator, when wishing to calculate the final density matrix of a circuit and not simulate the measurements themselves. Args: circuit: The circuit to transform. It will not be modified. context: `cirq.TransformerContext` storing common configurable options for transformers. The default has `deep=True` to ensure measurements at all levels are dephased. Returns: A copy of the circuit, with dephase operations in place of all measurements. Raises: ValueError: If the circuit contains classical controls. In this case, it is required to change these to quantum controls via `cirq.defer_measurements` first. Since deferral adds ancilla qubits to the circuit, this is not done automatically, to prevent surprises. """ def dephase(op: 'cirq.Operation', _) -> 'cirq.OP_TREE': gate = op.gate if isinstance(gate, ops.MeasurementGate): key = value.MeasurementKey.parse_serialized(gate.key) return ops.KrausChannel.from_channel(ops.phase_damp(1), key=key).on_each(op.qubits) elif isinstance(op, ops.ClassicallyControlledOperation): raise ValueError( 'Use cirq.defer_measurements first to remove classical controls.' ) return op ignored = () if context is None else context.tags_to_ignore return transformer_primitives.map_operations( circuit, dephase, deep=context.deep if context else True, tags_to_ignore=ignored).unfreeze()
def eject_z( circuit: 'cirq.AbstractCircuit', *, context: Optional['cirq.TransformerContext'] = None, atol: float = 0.0, eject_parameterized: bool = False, ) -> 'cirq.Circuit': """Pushes Z gates towards the end of the circuit. As the Z gates get pushed they may absorb other Z gates, get absorbed into measurements, cross CZ gates, cross PhasedXPowGate (aka W) gates (by phasing them), etc. Args: circuit: Input circuit to transform. context: `cirq.TransformerContext` storing common configurable options for transformers. atol: Maximum absolute error tolerance. The optimization is permitted to simply drop negligible combinations of Z gates, with a threshold determined by this tolerance. eject_parameterized: If True, the optimization will attempt to eject parameterized Z gates as well. This may result in other gates parameterized by symbolic expressions. Returns: Copy of the transformed input circuit. """ # Tracks qubit phases (in half turns; multiply by pi to get radians). qubit_phase: Dict[ops.Qid, float] = defaultdict(lambda: 0) tags_to_ignore = set(context.tags_to_ignore) if context else set() phased_xz_replacements: Dict[Tuple[int, ops.Operation], ops.PhasedXZGate] = {} last_phased_xz_op: Dict[ops.Qid, Optional[Tuple[ int, ops.Operation]]] = defaultdict(lambda: None) def dump_tracked_phase(qubits: Iterable[ops.Qid]) -> 'cirq.OP_TREE': """Zeroes qubit_phase entries by emitting Z gates.""" for q in qubits: p, key = qubit_phase[q], last_phased_xz_op[q] qubit_phase[q] = 0 if not (key or single_qubit_decompositions.is_negligible_turn( p, atol)): yield ops.Z(q)**(p * 2) elif key: phased_xz_replacements[key] = phased_xz_replacements[ key].with_z_exponent(p * 2) def map_func(op: 'cirq.Operation', moment_index: int) -> 'cirq.OP_TREE': last_phased_xz_op.update({q: None for q in op.qubits}) if tags_to_ignore & set(op.tags): # Op marked with no-compile, dump phases and do not cross. return [dump_tracked_phase(op.qubits), op] gate = op.gate # Return if circuit operation. if gate is None: return [dump_tracked_phase(op.qubits), op] # Swap phases if `op` is a swap operation. if _is_swaplike(gate): a, b = op.qubits qubit_phase[a], qubit_phase[b] = qubit_phase[b], qubit_phase[a] return op # Z gate before measurement is a no-op. Drop tracked phase. if isinstance(gate, ops.MeasurementGate): for q in op.qubits: qubit_phase[q] = 0 return op # Move Z gates into tracked qubit phases. if isinstance(gate, ops.ZPowGate) and ( eject_parameterized or not protocols.is_parameterized(gate)): qubit_phase[op.qubits[0]] += gate.exponent / 2 return [] # Try to move the tracked phases over the operation via protocols.phase_by(op) phased_op = op for i, p in enumerate([qubit_phase[q] for q in op.qubits]): if not single_qubit_decompositions.is_negligible_turn(p, atol): phased_op = protocols.phase_by(phased_op, -p, i, default=None) if phased_op is None: return [dump_tracked_phase(op.qubits), op] gate = phased_op.gate if isinstance(gate, ops.PhasedXZGate) and ( eject_parameterized or not protocols.is_parameterized(gate.z_exponent)): qubit = phased_op.qubits[0] qubit_phase[qubit] += gate.z_exponent / 2 gate = gate.with_z_exponent(0) phased_op = gate.on(qubit) phased_xz_replacements[moment_index, phased_op] = gate last_phased_xz_op[qubit] = (moment_index, phased_op) return phased_op circuit = transformer_primitives.map_operations( circuit, map_func).unfreeze(copy=False) circuit.append(dump_tracked_phase(qubit_phase.keys())) circuit.batch_replace((m, op, g.on(*op.qubits)) for (m, op), g in phased_xz_replacements.items()) return transformer_primitives.unroll_circuit_op(circuit)