def _potential_cross_whole_w( op: ops.Operation, atol: float, held_w_phases: Dict[ops.Qid, value.TParamVal]) -> 'cirq.OP_TREE': """Grabs or cancels a held W gate against an existing W gate. [Where W(a) is shorthand for PhasedX(phase_exponent=a).] Uses the following identity: ───W(a)───W(b)─── ≡ ───Z^-a───X───Z^a───Z^-b───X───Z^b─── ≡ ───Z^-a───Z^-a───Z^b───X───X───Z^b─── ≡ ───Z^-a───Z^-a───Z^b───Z^b─── ≡ ───Z^2(b-a)─── """ _, phase_exponent = cast(Tuple[value.TParamVal, value.TParamVal], _try_get_known_phased_pauli(op)) q = op.qubits[0] a = held_w_phases.get(q, None) b = phase_exponent if a is None: # Collect the gate. held_w_phases[q] = b else: # Cancel the gate. del held_w_phases[q] t = 2 * (b - a) if not single_qubit_decompositions.is_negligible_turn(t / 2, atol): return ops.Z(q)**t return []
def _potential_cross_whole_w(moment_index: int, op: ops.Operation, tolerance: float, state: _OptimizerState) -> None: """Grabs or cancels a held W gate against an existing W gate. [Where W(a) is shorthand for PhasedX(phase_exponent=a).] Uses the following identity: ───W(a)───W(b)─── ≡ ───Z^-a───X───Z^a───Z^-b───X───Z^b─── ≡ ───Z^-a───Z^-a───Z^b───X───X───Z^b─── ≡ ───Z^-a───Z^-a───Z^b───Z^b─── ≡ ───Z^2(b-a)─── """ state.deletions.append((moment_index, op)) _, phase_exponent = cast(Tuple[value.TParamVal, value.TParamVal], _try_get_known_phased_pauli(op)) q = op.qubits[0] a = state.held_w_phases.get(q, None) b = phase_exponent if a is None: # Collect the gate. state.held_w_phases[q] = b else: # Cancel the gate. del state.held_w_phases[q] t = 2 * (b - a) if not single_qubit_decompositions.is_negligible_turn( t / 2, tolerance): leftover_phase = ops.Z(q)**t state.inline_intos.append((moment_index, leftover_phase))
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 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 q in state.held_w_phases] # Collect, phase, and merge Ws. w = _try_get_known_phased_pauli( op, no_symbolic=not self.eject_parameterized) if w is not None: if single_qubit_decompositions.is_negligible_turn( (w[0] - 1) / 2, 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, no_symbolic=not self.eject_parameterized) if t is not None: _absorb_z_into_w(moment_index, op, state) continue # Dump coherent flips into measurement bit flips. if isinstance(op.gate, ops.MeasurementGate): _dump_into_measurement(moment_index, op, state) # Cross CZs using kickback. if (_try_get_known_cz_half_turns( op, no_symbolic=not self.eject_parameterized) 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)
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
def dump_tracked_phase(qubits: Iterable[ops.Qid], index: int) -> None: """Zeroes qubit_phase entries by emitting Z gates.""" for q in qubits: p = qubit_phase[q] qubit_phase[q] = 0 if single_qubit_decompositions.is_negligible_turn(p, self.tolerance): continue dumped = False moment_index = circuit.prev_moment_operating_on([q], index) if moment_index is not None: op = circuit.moments[moment_index][q] if op and isinstance(op.gate, ops.PhasedXZGate): # Attach z-rotation to replacing PhasedXZ gate. idx = phased_xz_replacements[moment_index, q] _, _, repl_op = replacements[idx] gate = cast(ops.PhasedXZGate, repl_op.gate) repl_op = gate.with_z_exponent(p * 2).on(q) replacements[idx] = (moment_index, op, repl_op) dumped = True if not dumped: # Add a new Z gate dump_op = ops.Z(q) ** (p * 2) insertions.append((index, dump_op))
def map_func(op: 'cirq.Operation', _: int) -> 'cirq.OP_TREE': # Dump if `op` marked with a no compile tag. if set(op.tags) & tags_to_ignore: return [_dump_held(op.qubits, held_w_phases), op] # Collect, phase, and merge Ws. w = _try_get_known_phased_pauli(op, no_symbolic=not eject_parameterized) if w is not None: return ( _potential_cross_whole_w(op, atol, held_w_phases) if single_qubit_decompositions.is_negligible_turn((w[0] - 1) / 2, atol) else _potential_cross_partial_w(op, held_w_phases) ) affected = [q for q in op.qubits if q in held_w_phases] if not affected: return op # Absorb Z rotations. t = _try_get_known_z_half_turns(op, no_symbolic=not eject_parameterized) if t is not None: return _absorb_z_into_w(op, held_w_phases) # Dump coherent flips into measurement bit flips. if isinstance(op.gate, ops.MeasurementGate): return _dump_into_measurement(op, held_w_phases) # Cross CZs using kickback. if _try_get_known_cz_half_turns(op, no_symbolic=not eject_parameterized) is not None: return ( _single_cross_over_cz(op, affected[0]) if len(affected) == 1 else _double_cross_over_cz(op, held_w_phases) ) # Don't know how to handle this situation. Dump the gates. return [_dump_held(op.qubits, held_w_phases), op]
def optimize_circuit(self, circuit: circuits.Circuit): # Tracks qubit phases (in half turns; multiply by pi to get radians). qubit_phase: Dict[ops.Qid, float] = defaultdict(lambda: 0) deletions: List[Tuple[int, ops.Operation]] = [] replacements: List[Tuple[int, ops.Operation, ops.Operation]] = [] insertions: List[Tuple[int, ops.Operation]] = [] phased_xz_replacements: Dict[Tuple[int, ops.Qid], int] = {} def dump_tracked_phase(qubits: Iterable[ops.Qid], index: int) -> None: """Zeroes qubit_phase entries by emitting Z gates.""" for q in qubits: p = qubit_phase[q] qubit_phase[q] = 0 if single_qubit_decompositions.is_negligible_turn(p, self.tolerance): continue dumped = False moment_index = circuit.prev_moment_operating_on([q], index) if moment_index is not None: op = circuit.moments[moment_index][q] if op and isinstance(op.gate, ops.PhasedXZGate): # Attach z-rotation to replacing PhasedXZ gate. idx = phased_xz_replacements[moment_index, q] _, _, repl_op = replacements[idx] gate = cast(ops.PhasedXZGate, repl_op.gate) repl_op = gate.with_z_exponent(p * 2).on(q) replacements[idx] = (moment_index, op, repl_op) dumped = True if not dumped: # Add a new Z gate dump_op = ops.Z(q) ** (p * 2) insertions.append((index, dump_op)) 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, self.eject_parameterized) 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 isinstance(op.gate, ops.MeasurementGate): 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 not isinstance(op.gate, ops.PhasedXZGate) and all( single_qubit_decompositions.is_negligible_turn(p, self.tolerance) for p in phases ): continue if _is_swaplike(op): a, b = op.qubits qubit_phase[a], qubit_phase[b] = qubit_phase[b], qubit_phase[a] continue # Try to move the tracked phasing over the operation. phased_op = op for i, p in enumerate(phases): if not single_qubit_decompositions.is_negligible_turn(p, self.tolerance): phased_op = protocols.phase_by(phased_op, -p, i, default=None) if phased_op is not None: gate = phased_op.gate if isinstance(gate, ops.PhasedXZGate) and ( self.eject_parameterized or not protocols.is_parameterized(gate.z_exponent) ): qubit = phased_op.qubits[0] qubit_phase[qubit] += gate.z_exponent / 2 phased_op = gate.with_z_exponent(0).on(qubit) repl_idx = len(replacements) phased_xz_replacements[moment_index, qubit] = repl_idx replacements.append((moment_index, op, phased_op)) else: dump_tracked_phase(op.qubits, moment_index) dump_tracked_phase(qubit_phase.keys(), len(circuit)) circuit.batch_remove(deletions) circuit.batch_replace(replacements) circuit.batch_insert(insertions)