def optimization_at(self, circuit, index, op): def find_removable_rz(): """Finds z rotations that can be removed. A z rotation is removable iff it is followed by a z-basis measurement. Returns: list[int]: moment indices of the z rotations to be removed """ remove_indices = [] for idx, moment in enumerate(circuit[index:], start=index): for x in moment.operations: if x.qubits == op.qubits: if isinstance( x.gate, cirq.ZPowGate ): # add idx to the list, keep looking for more remove_indices.append(idx) break if isinstance(x.gate, cirq.MeasurementGate ): # remove the accumulated indices return remove_indices return [] # other operations: do not remove anything return [] # circuit ends here: do not remove anything indices = find_removable_rz() if not indices: return None return circuits.PointOptimizationSummary(clear_span=max(indices) + 1 - index, clear_qubits=op.qubits, new_operations=[])
def optimization_at( self, circuit: circuits.Circuit, index: int, op: ops.Operation) -> Optional[circuits.PointOptimizationSummary]: if len(op.qubits) != 2: return None old_operations, indices, matrix = ( self._scan_two_qubit_ops_into_matrix(circuit, index, op.qubits)) # Find a max-3-cz construction. new_operations = decompositions.two_qubit_matrix_to_native_gates( op.qubits[0], op.qubits[1], matrix, self.allow_partial_czs, self.tolerance) old_interaction_count = len( [old_op for old_op in old_operations if len(old_op.qubits) == 2]) new_interaction_count = len( [new_op for new_op in new_operations if len(new_op.qubits) == 2]) switch_to_new = False switch_to_new |= new_interaction_count < old_interaction_count switch_to_new |= any(not xmon_gates.XmonGate.is_xmon_op(old_op) for old_op in old_operations) if not switch_to_new: return None converter = convert_to_xmon_gates.ConvertToXmonGates() new_xmon_operations = [ converter.convert(new_op) for new_op in new_operations ] return circuits.PointOptimizationSummary( clear_span=max(indices) + 1 - index, clear_qubits=op.qubits, new_operations=new_xmon_operations)
def optimization_at( self, circuit: cirq.Circuit, index: int, op: cirq.Operation, ) -> Optional[cirq.PointOptimizationSummary]: """Describes how to change operations near the given location. Args: circuit: The circuit to improve. index: The index of the moment with the operation to focus on. op: The operation to focus improvements upon. Returns: A description of the optimization to perform, or else None if no change should be made. """ if not isinstance(op.gate, self.ONE_PARAMETER_FAMILIES): return None def is_not_mergable(next_op: cirq.Operation) -> bool: """Predicate for finding gates that can be merged with op. A gate is mergable with op iff it (1) belongs to the same gate family, and (2) is acting on the same qubits. """ if not isinstance(next_op.gate, type(op.gate)): return True if isinstance(op.gate, ops.gate_features.InterchangeableQubitsGate): # same qubits in any order return set(op.qubits) != set(next_op.qubits) # same qubits in the same order return op.qubits != next_op.qubits # start searching from op onwards start_frontier = {q: index for q in op.qubits} op_list = circuit.findall_operations_until_blocked( start_frontier, is_blocker=is_not_mergable) if len(op_list) == 1: return None # just the one gate found, no changes indices, operations = zip(*op_list) # all the gates are in the same family so we may simply sum their parameters (mod periodicity) par = sum(o.gate.exponent for o in operations) # zero parameter (mod period) corresponds to identity # due to floating point errors we may be just a little below the period, which should also be # considered close to zero so let's shift away from the troublesome point before taking the modulo par = self._normalize_par(par) if abs(par) <= self.GATE_MERGING_TOLERANCE: rewritten = [] else: rewritten = op.gate.__class__(exponent=par).on(*op.qubits) return circuits.PointOptimizationSummary(clear_span=max(indices) + 1 - index, clear_qubits=op.qubits, new_operations=rewritten)
def optimization_at(self, circuit, index, op): if not isinstance(op.gate, circuit.device.DECOMPOSE_FINALLY): return None # no changes rewritten = circuit.device.operation_final_decomposer(op) return circuits.PointOptimizationSummary(clear_span=1, clear_qubits=op.qubits, new_operations=rewritten)
def optimization_at(self, circuit, index, op): converted = self.convert(op) if len(converted) == 1 and converted[0] is op: return None return circuits.PointOptimizationSummary(clear_span=1, new_operations=converted, clear_qubits=op.qubits)
def optimization_at( self, circuit: 'cirq.Circuit', index: int, op: 'cirq.Operation') -> Optional['cirq.PointOptimizationSummary']: if not isinstance(op, ops.GateOperation): return None gate = op.gate # Check for a SWAP and ZZPowGate together if isinstance(gate, ops.ZZPowGate) or gate == ops.SWAP: gate2 = None rads = None next_index = circuit.next_moment_operating_on(op.qubits, index + 1) if next_index is not None: ops_in_front = list( {circuit.operation_at(q, next_index) for q in op.qubits}) if len(ops_in_front) == 1 and isinstance( ops_in_front[0], ops.GateOperation): gate2 = ops_in_front[0].gate else: next_index = 0 if isinstance(gate, ops.SwapPowGate) and isinstance( gate2, ops.ZZPowGate): rads = gate2.exponent * np.pi / 2 if isinstance(gate, ops.ZZPowGate) and gate2 == ops.SWAP: rads = gate.exponent * np.pi / 2 if rads is not None: return circuits.PointOptimizationSummary( clear_span=next_index - index + 1, clear_qubits=op.qubits, new_operations=swap_rzz(rads, op.qubits[0], op.qubits[1]), ) converted = self.convert(op) if len(converted) == 1 and converted[0] is op: return None return circuits.PointOptimizationSummary(clear_span=1, new_operations=converted, clear_qubits=op.qubits)
def optimization_at(self, circuit, index, op): if isinstance(op.gate, ops.MatrixGate) and len(op.qubits) == 1: return None converted = self.convert(op) if len(converted) == 1 and converted[0] is op: return None return circuits.PointOptimizationSummary(clear_span=1, new_operations=converted, clear_qubits=op.qubits)
def test_clears_small(): m = circuits.DropNegligible(0.001) q = ops.QubitId() c = circuits.Circuit([circuits.Moment([ops.Z(q)**0.000001])]) assert (m.optimization_at(c, 0, c.operation_at(q, 0)) == circuits.PointOptimizationSummary(clear_span=1, clear_qubits=[q], new_operations=[])) m.optimize_circuit(c) assert c == circuits.Circuit([circuits.Moment()])
def optimization_at( self, circuit: cirq.Circuit, index: int, op: cirq.Operation, ) -> Optional[cirq.PointOptimizationSummary]: """Describes how to change operations near the given location. Args: circuit: The circuit to improve. index: The index of the moment with the operation to focus on. op: The operation to focus improvements upon. Returns: A description of the optimization to perform, or else None if no change should be made. """ def find_removable_rz() -> list[int]: """Finds z rotations that can be removed. A z rotation is removable iff it is followed by a z-basis measurement. Returns: moment indices of the z rotations to be removed """ # op is a ZPowGate remove_indices = [] for idx, moment in enumerate(circuit[index:], start=index): for x in moment.operations: if op.qubits[0] in x.qubits: # x acts on the same qubit as op if isinstance(x.gate, cirq.ZPowGate): # add idx to the list, keep looking for more remove_indices.append(idx) break # to next moment if isinstance(x.gate, cirq.MeasurementGate): # follows the ZPowGates, remove the accumulated indices return remove_indices return [] # other operations: do not remove anything # circuit ends here if self.drop_final: return remove_indices return [] if not isinstance(op.gate, cirq.ZPowGate): return None # shortcut indices = find_removable_rz() if not indices: return None return circuits.PointOptimizationSummary(clear_span=max(indices) + 1 - index, clear_qubits=op.qubits, new_operations=[])
def test_combines_sequence(): m = MergeRotations(0.000001) q = ops.QubitId() c = circuits.Circuit([ circuits.Moment([ops.X(q)**0.5]), circuits.Moment([ops.Z(q)**0.5]), circuits.Moment([ops.X(q)**-0.5]), ]) assert (m.optimization_at(c, 0, c.operation_at( q, 0)) == circuits.PointOptimizationSummary(clear_span=3, clear_qubits=[q], new_operations=ops.Y(q)**0.5))
def optimization_at(self, circuit: circuits.Circuit, index: int, op: ops.Operation ) -> Optional[circuits.PointOptimizationSummary]: if len(op.qubits) != 2: return None old_operations, indices, matrix = ( self._scan_two_qubit_ops_into_matrix(circuit, index, op.qubits)) old_interaction_count = len([old_op for old_op in old_operations if len(old_op.qubits) == 2]) switch_to_new = False switch_to_new |= any(len(old_op.qubits) == 2 and not (isinstance(old_op, ops.GateOperation) and isinstance(old_op.gate, ops.CZPowGate)) for old_op in old_operations) if not self.allow_partial_czs: switch_to_new |= any(isinstance(old_op, ops.GateOperation) and isinstance(old_op.gate, ops.CZPowGate) and old_op.gate.exponent != 1 for old_op in old_operations) # This point cannot be optimized using this method if not switch_to_new and old_interaction_count <= 1: return None # Find a max-3-cz construction. new_operations = ( two_qubit_decompositions.two_qubit_matrix_to_operations( op.qubits[0], op.qubits[1], matrix, self.allow_partial_czs, self.tolerance, False)) new_interaction_count = len([new_op for new_op in new_operations if len(new_op.qubits) == 2]) switch_to_new |= new_interaction_count < old_interaction_count if not switch_to_new: return None return circuits.PointOptimizationSummary( clear_span=max(indices) + 1 - index, clear_qubits=op.qubits, new_operations=new_operations)
def optimization_at( self, circuit: circuits.Circuit, index: int, op: ops.Operation) -> Optional[circuits.PointOptimizationSummary]: converted = protocols.decompose( op, intercepting_decomposer=self._decompose_two_qubit_unitaries, keep=self._keep, on_stuck_raise=(None if self.ignore_failures else self._on_stuck_raise)) if converted == [op]: return None return circuits.PointOptimizationSummary(clear_span=1, new_operations=converted, clear_qubits=op.qubits)
def test_stopped_at_2qubit(): m = MergeRotations(0.000001) q = ops.QubitId() q2 = ops.QubitId() c = circuits.Circuit([ circuits.Moment([ops.Z(q)]), circuits.Moment([ops.H(q)]), circuits.Moment([ops.X(q)]), circuits.Moment([ops.H(q)]), circuits.Moment([ops.CZ(q, q2)]), circuits.Moment([ops.H(q)]), ]) assert (m.optimization_at(c, 0, c.operation_at( q, 0)) == circuits.PointOptimizationSummary(clear_span=4, clear_qubits=[q], new_operations=[]))
def optimization_at(self, circuit: 'cirq.Circuit', index: int, op: 'cirq.Operation'): if isinstance(op.gate, AcquaintanceOpportunityGate): logical_indices = tuple(self.mapping[q] for q in op.qubits) logical_operations = self.execution_strategy.get_operations(logical_indices, op.qubits) clear_span = int(not self.execution_strategy.keep_acquaintance) return circuits.PointOptimizationSummary( clear_span=clear_span, clear_qubits=op.qubits, new_operations=logical_operations ) if isinstance(op, ops.GateOperation) and isinstance(op.gate, PermutationGate): op.gate.update_mapping(self.mapping, op.qubits) return raise TypeError( 'Can only execute a strategy consisting of gates that ' 'are instances of AcquaintanceOpportunityGate or ' 'PermutationGate.' )
def optimization_at(self, circuit, index, op): if not isinstance(op.gate, self.one_parameter_families): return None def is_not_mergable(next_op): """Predicate for finding gates that can be merged with op. A gate is mergable with op iff it (1) belongs to the same gate family, and (2) is acting on the same qubits. """ if not isinstance(next_op.gate, type(op.gate)): return True if isinstance(op.gate, ops.gate_features.InterchangeableQubitsGate): # same qubits in any order return set(op.qubits) != set(next_op.qubits) # same qubits in the same order return op.qubits != next_op.qubits # start searching from op onwards start_frontier = {q: index for q in op.qubits} op_list = circuit.findall_operations_until_blocked( start_frontier, is_blocker=is_not_mergable) if len(op_list) == 1: return None # just the one gate found, no changes indices, operations = zip(*op_list) # all the gates are in the same family so we may simply sum their parameters (mod periodicity) par = sum(o.gate.exponent for o in operations) # zero parameter (mod period) corresponds to identity # due to floating point errors we may be just a little below the period, which should also be # considered close to zero so let's shift away from the troublesome point before taking the modulo par = operator.mod(par + 1, 2) - 1 if abs(par) <= GATE_MERGING_TOLERANCE: rewritten = [] else: rewritten = op.gate.__class__(exponent=par).on(*op.qubits) return circuits.PointOptimizationSummary(clear_span=max(indices) + 1 - index, clear_qubits=op.qubits, new_operations=rewritten)
def optimization_at( self, circuit: circuits.Circuit, index: int, op: ops.Operation ) -> Optional[circuits.PointOptimizationSummary]: if len(op.qubits) != 2: return None old_operations, indices, matrix = self._scan_two_qubit_ops_into_matrix( circuit, index, op.qubits ) old_interaction_count = len( [old_op for old_op in old_operations if len(old_op.qubits) == 2] ) switch_to_new = False switch_to_new |= any( len(old_op.qubits) == 2 and not self._may_keep_old_op(old_op) for old_op in old_operations ) # This point cannot be optimized using this method if not switch_to_new and old_interaction_count <= 1: return None # Find a (possibly ideal) decomposition of the merged operations. new_operations = self._two_qubit_matrix_to_cz_operations(op.qubits[0], op.qubits[1], matrix) new_interaction_count = len( [new_op for new_op in new_operations if len(new_op.qubits) == 2] ) switch_to_new |= new_interaction_count < old_interaction_count if not switch_to_new: return None return circuits.PointOptimizationSummary( clear_span=max(indices) + 1 - index, clear_qubits=op.qubits, new_operations=new_operations, )
def optimization_at( self, circuit: circuits.Circuit, index: int, op: ops.Operation) -> Optional[circuits.PointOptimizationSummary]: if len(op.qubits) != 1: return None start = {op.qubits[0]: index} op_list = circuit.findall_operations_until_blocked( start, is_blocker=lambda next_op: len( next_op.qubits) != 1 or not protocols.has_unitary(next_op)) operations = [op for idx, op in op_list] indices = [idx for idx, op in op_list] rewritten = self._rewrite(operations) if rewritten is None: return None return circuits.PointOptimizationSummary(clear_span=max(indices) + 1 - index, clear_qubits=op.qubits, new_operations=rewritten)
def optimization_at( self, circuit: circuits.Circuit, index: int, op: ops.Operation) -> Optional[circuits.PointOptimizationSummary]: if len(op.qubits) != 1: return None start = {op.qubits[0]: index} end = circuit.reachable_frontier_from( start, is_blocker=lambda next_op: len( next_op.qubits) != 1 or not protocols.has_unitary(next_op)) operations_indexed = circuit.findall_operations_between(start, end) operations = [e for _, e in operations_indexed] indices = [e for e, _ in operations_indexed] rewritten = self._rewrite(operations) if rewritten is None: return None return circuits.PointOptimizationSummary(clear_span=max(indices) + 1 - index, clear_qubits=op.qubits, new_operations=rewritten)