def test_through_middle_of_outside(self) -> None: circuit = Circuit(3) circuit.append_gate(CNOTGate(), (0, 1)) circuit.append_gate(HGate(), 0) circuit.append_gate(HGate(), 1) circuit.append_gate(CNOTGate(), (0, 2)) circuit.append_gate(CNOTGate(), (0, 1)) region = circuit.surround((1, 0), 2) assert region == CircuitRegion({0: (0, 1), 1: (0, 1)})
def test_small_circuit_1(self) -> None: circuit = Circuit(2) circuit.append_gate(HGate(), 0) circuit.append_gate(HGate(), 1) circuit.append_gate(CNOTGate(), (0, 1)) circuit.append_gate(HGate(), 0) circuit.append_gate(HGate(), 1) region = circuit.surround((0, 1), 2) assert region == CircuitRegion({0: (0, 2), 1: (0, 2)})
def run(self, circuit: Circuit, data: dict[str, Any]) -> None: """ Partition gates in a circuit into a series of CircuitGates. Args: circuit (Circuit): Circuit to be partitioned. data (dict[str,Any]): Optional data unique to specific run. """ if self.block_size > circuit.get_size(): _logger.warning( 'Configured block size is greater than circuit size; ' 'blocking entire circuit.', ) circuit.fold({ qudit_index: (0, circuit.get_num_cycles()) for qudit_index in range(circuit.get_size()) }) return # If a MachineModel is provided in the data dict, it will be used. # Otherwise all-to-all connectivity is assumed. model = None if 'machine_model' in data: model = data['machine_model'] if (not isinstance(model, MachineModel) or model.num_qudits < circuit.get_size()): _logger.warning( 'MachineModel not specified or invalid;' ' defaulting to all-to-all.', ) model = MachineModel(circuit.get_size()) # Find all connected, `block_size`-sized groups of qudits # NOTE: This assumes circuit and topology qudit numbers are equal qudit_groups = model.get_locations(self.block_size) # Prune unused qudit groups used_qudits = [ q for q in range(circuit.get_size()) if not circuit.is_qudit_idle(q) ] for qudit_group in qudit_groups: if all([q not in used_qudits for q in qudit_group]): qudit_groups.remove(qudit_group) # divider splits the circuit into partitioned and unpartitioned spaces. active_qudits = circuit.get_active_qudits() num_cycles = circuit.get_num_cycles() divider = [ 0 if q in active_qudits else num_cycles for q in range(circuit.get_size()) ] # Form regions until there are no more gates to partition regions: list[CircuitRegion] = [] while any(cycle < num_cycles for cycle in divider): # Move past/skip any gates that are larger than block size qudits_to_increment: list[int] = [] for qudit, cycle in enumerate(divider): if qudit in qudits_to_increment or cycle >= num_cycles: continue if not circuit.is_point_idle((cycle, qudit)): op = circuit[cycle, qudit] if len(op.location) > self.block_size: if all(divider[q] == cycle for q in op.location): qudits_to_increment.extend(op.location) _logger.warning( 'Skipping gate larger than block size.', ) if len(qudits_to_increment) > 0: regions.append( CircuitRegion( { qudit: (divider[qudit], divider[qudit]) for qudit in qudits_to_increment }, ), ) for qudit in qudits_to_increment: divider[qudit] += 1 # Skip any idle qudit-cycles amount_to_add_to_each_qudit = [ 0 for _ in range(circuit.get_size()) ] for qudit, cycle in enumerate(divider): while (cycle < num_cycles and circuit.is_point_idle( (cycle, qudit))): amount_to_add_to_each_qudit[qudit] += 1 cycle += 1 for qudit, amount in enumerate(amount_to_add_to_each_qudit): divider[qudit] += amount # Find the scores of the qudit groups. best_score = None best_region = None for qudit_group in qudit_groups: ops_and_cycles = circuit.operations_with_cycles( qudits_or_region=CircuitRegion({ qudit_index: (divider[qudit_index], num_cycles) for qudit_index in qudit_group }), ) in_qudits = list(q for q in qudit_group) stopped_cycles = {q: num_cycles for q in qudit_group} score = 0 for cycle, op in ops_and_cycles: if len(op.location.union(in_qudits)) != len(in_qudits): for qudit_index in op.location.intersection(in_qudits): stopped_cycles[qudit_index] = cycle in_qudits.remove(qudit_index) else: if len(op.location) > 1: score += self.multi_gate_score else: score += self.single_gate_score if len(in_qudits) == 0: break if best_score is None or score > best_score: best_score = score best_region = CircuitRegion({ qudit: ( divider[qudit], # Might have errors if below is removed stopped_cycles[qudit] - 1, ) for qudit in qudit_group # This statement # if stopped_cycles[qudit] - 1 >= divider[qudit] }) if best_score is None or best_region is None: raise RuntimeError('No valid block found.') _logger.info('Found block with score: %d.' % (best_score)) regions.append(best_region) # Update divider for qudit_index in best_region: divider[qudit_index] = best_region[qudit_index].upper + 1 # Fold the circuit folded_circuit = Circuit(circuit.get_size(), circuit.get_radixes()) # Option to keep a block's idle qudits as part of the CircuitGate if 'keep_idle_qudits' in data and data['keep_idle_qudits'] is True: for region in regions: small_region = circuit.downsize_region(region) cgc = circuit.get_slice(small_region.points) if len(region.location) > len(small_region.location): for i in range(len(region.location)): if region.location[i] not in small_region.location: cgc.insert_qudit(i) folded_circuit.append_gate( CircuitGate(cgc, True), sorted(list(region.keys())), list(cgc.get_params()), ) else: for region in regions: region = circuit.downsize_region(region) if 0 < len(region) <= self.block_size: cgc = circuit.get_slice(region.points) folded_circuit.append_gate( CircuitGate(cgc, True), sorted(list(region.keys())), list(cgc.get_params()), ) else: folded_circuit.extend(circuit[region]) circuit.become(folded_circuit)
def __init__( self, circuit: Circuit, start: CircuitPointLike = CircuitPoint(0, 0), end: CircuitPointLike | None = None, qudits_or_region: CircuitRegionLike | Sequence[int] | None = None, exclude: bool = False, reverse: bool = False, and_cycles: bool = False, ) -> None: """ Construct a CircuitIterator. Args: circuit (Circuit): The circuit to iterate through. start (CircuitPointLike): Only iterate through points greater than or equal to `start`. Defaults to start at the beginning of the circuit. (Default: (0, 0)) end (CircuitPointLike | None): Only iterate through points less than or equal to this. If left as None, iterates until the end of the circuit. (Default: None) qudits_or_region (CircuitRegionLike | Sequence[int] | None): Determines the way the circuit is iterated. If a region is given, then iterate through operations in the region. If a sequence of qudit indices is given, then only iterate the operations touching those qudits. If left as None, then iterate through the entire circuit in simulation order. (Default: None) exclude (bool): If iterating through a region or only some qudits and `exclude` is true, then do not yield operations that are only partially in the region or on the desired qudits. This may result in a sequence of operations that does not occur in simulation order in the circuit. (Default: False) reverse (bool): Reverse the ordering. If true, then end acts as start and vice versa. (Default: False) and_cycles (bool): If true, in addition to the operation, return the cycle index where it was found. (Default: False) """ if not CircuitPoint.is_point(start): raise TypeError(f'Expected point for start, got {type(start)}.') if end is not None and not CircuitPoint.is_point(end): raise TypeError(f'Expected point for end, got {type(end)}.') if end is None: end = CircuitPoint( circuit.get_num_cycles() - 1, circuit.get_size() - 1, ) self.circuit = circuit self.start = CircuitPoint(*start) self.end = CircuitPoint(*end) self.exclude = exclude self.reverse = reverse self.and_cycles = and_cycles # Set mode of iteration: if qudits_or_region is None: # iterate through the entire circuit normally self.qudits = list(range(self.circuit.get_size())) self.region = CircuitRegion({ qudit: (0, self.circuit.get_num_cycles()) for qudit in self.qudits }) elif CircuitRegion.is_region(qudits_or_region): # TODO: Typeguard # iterate through the region in the circuit self.qudits = list(qudits_or_region.keys()) # type: ignore self.region = CircuitRegion(qudits_or_region) # type: ignore elif is_sequence(qudits_or_region): # iterate through the circuit but only on the specified qudits if not all(is_integer(qudit) for qudit in qudits_or_region): raise TypeError('Expected region or sequence of indices.') if not all(0 <= qudit < self.circuit.get_size() for qudit in qudits_or_region): raise ValueError('Invalid sequence of qudit indices.') self.qudits = list(qudits_or_region) self.region = CircuitRegion({ qudit: (0, self.circuit.get_num_cycles()) for qudit in self.qudits }) self.max_qudit = max(self.qudits) self.min_qudit = min(self.qudits) self.min_cycle = self.region.min_cycle self.max_cycle = self.region.max_cycle if start < (self.min_cycle, self.min_qudit): start = CircuitPoint(self.min_cycle, self.min_qudit) if end > (self.max_cycle, self.max_qudit): end = CircuitPoint(self.max_cycle, self.max_qudit) assert isinstance(start, CircuitPoint) # TODO: Typeguard assert isinstance(end, CircuitPoint) # TODO: Typeguard # Pointer into the circuit structure self.cycle = start.cycle if not self.reverse else end.cycle self.qudit = start.qudit if not self.reverse else end.qudit # Used to track changes to circuit structure self.num_ops = self.circuit.get_num_operations() self.num_cycles = self.circuit.get_num_cycles() self.num_qudits = self.circuit.get_size() # Ensure operations are only returned once self.qudits_to_skip: set[int] = set()
class CircuitIterator( Iterator[Union[Operation, Tuple[int, Operation], # if and_cycles == True ]], ): """ The CircuitIterator Class. A CircuitIterator can iterate through a circuit in a few different ways. By default it can iterate through all operations in the circuit in simulation order. Additionally, it can iterate all the operations on a qudit or set of qudits or iterate through a specified CircuitRegion. If iterating all operations in a region or on some qudits, you can choose to exclude operations that only are partially in the specified area. """ def __init__( self, circuit: Circuit, start: CircuitPointLike = CircuitPoint(0, 0), end: CircuitPointLike | None = None, qudits_or_region: CircuitRegionLike | Sequence[int] | None = None, exclude: bool = False, reverse: bool = False, and_cycles: bool = False, ) -> None: """ Construct a CircuitIterator. Args: circuit (Circuit): The circuit to iterate through. start (CircuitPointLike): Only iterate through points greater than or equal to `start`. Defaults to start at the beginning of the circuit. (Default: (0, 0)) end (CircuitPointLike | None): Only iterate through points less than or equal to this. If left as None, iterates until the end of the circuit. (Default: None) qudits_or_region (CircuitRegionLike | Sequence[int] | None): Determines the way the circuit is iterated. If a region is given, then iterate through operations in the region. If a sequence of qudit indices is given, then only iterate the operations touching those qudits. If left as None, then iterate through the entire circuit in simulation order. (Default: None) exclude (bool): If iterating through a region or only some qudits and `exclude` is true, then do not yield operations that are only partially in the region or on the desired qudits. This may result in a sequence of operations that does not occur in simulation order in the circuit. (Default: False) reverse (bool): Reverse the ordering. If true, then end acts as start and vice versa. (Default: False) and_cycles (bool): If true, in addition to the operation, return the cycle index where it was found. (Default: False) """ if not CircuitPoint.is_point(start): raise TypeError(f'Expected point for start, got {type(start)}.') if end is not None and not CircuitPoint.is_point(end): raise TypeError(f'Expected point for end, got {type(end)}.') if end is None: end = CircuitPoint( circuit.get_num_cycles() - 1, circuit.get_size() - 1, ) self.circuit = circuit self.start = CircuitPoint(*start) self.end = CircuitPoint(*end) self.exclude = exclude self.reverse = reverse self.and_cycles = and_cycles # Set mode of iteration: if qudits_or_region is None: # iterate through the entire circuit normally self.qudits = list(range(self.circuit.get_size())) self.region = CircuitRegion({ qudit: (0, self.circuit.get_num_cycles()) for qudit in self.qudits }) elif CircuitRegion.is_region(qudits_or_region): # TODO: Typeguard # iterate through the region in the circuit self.qudits = list(qudits_or_region.keys()) # type: ignore self.region = CircuitRegion(qudits_or_region) # type: ignore elif is_sequence(qudits_or_region): # iterate through the circuit but only on the specified qudits if not all(is_integer(qudit) for qudit in qudits_or_region): raise TypeError('Expected region or sequence of indices.') if not all(0 <= qudit < self.circuit.get_size() for qudit in qudits_or_region): raise ValueError('Invalid sequence of qudit indices.') self.qudits = list(qudits_or_region) self.region = CircuitRegion({ qudit: (0, self.circuit.get_num_cycles()) for qudit in self.qudits }) self.max_qudit = max(self.qudits) self.min_qudit = min(self.qudits) self.min_cycle = self.region.min_cycle self.max_cycle = self.region.max_cycle if start < (self.min_cycle, self.min_qudit): start = CircuitPoint(self.min_cycle, self.min_qudit) if end > (self.max_cycle, self.max_qudit): end = CircuitPoint(self.max_cycle, self.max_qudit) assert isinstance(start, CircuitPoint) # TODO: Typeguard assert isinstance(end, CircuitPoint) # TODO: Typeguard # Pointer into the circuit structure self.cycle = start.cycle if not self.reverse else end.cycle self.qudit = start.qudit if not self.reverse else end.qudit # Used to track changes to circuit structure self.num_ops = self.circuit.get_num_operations() self.num_cycles = self.circuit.get_num_cycles() self.num_qudits = self.circuit.get_size() # Ensure operations are only returned once self.qudits_to_skip: set[int] = set() def increment_iter(self) -> None: """Increment the iterator to the next valid circuit point.""" while (self.qudit in self.qudits_to_skip or self.qudit not in self.qudits or (self.cycle not in self.region[self.qudit] and self.cycle <= self.max_cycle)): self.qudit += 1 if self.qudit > self.max_qudit: self.qudit = self.min_qudit self.cycle += 1 self.qudits_to_skip.clear() def decrement_iter(self) -> None: """Decrement the iterator to the next valid circuit point.""" while (self.qudit in self.qudits_to_skip or self.qudit not in self.qudits or (self.cycle not in self.region[self.qudit] and self.cycle >= self.min_cycle)): self.qudit -= 1 if self.qudit < self.min_qudit: self.qudit = self.max_qudit self.cycle -= 1 self.qudits_to_skip.clear() def step(self) -> None: """Move the iterator one step.""" if (self.num_ops != self.circuit.get_num_operations() or self.num_cycles != self.circuit.get_num_cycles() or self.num_qudits != self.circuit.get_size()): raise RuntimeError('Circuit changed under iteration.') if not self.reverse: self.increment_iter() else: self.decrement_iter() point = (self.cycle, self.qudit) if point < self.start or point > self.end: raise StopIteration def __next__(self) -> Operation | tuple[int, Operation]: while True: self.step() op = self.circuit._circuit[self.cycle][self.qudit] if op is None: self.qudits_to_skip.add(self.qudit) continue if self.exclude: if not all(qudit in self.qudits for qudit in op.location): continue if not all( self.region.overlaps((self.cycle, qudit)) for qudit in op.location): continue self.qudits_to_skip.update(op.location) if self.and_cycles: return self.cycle, op return op def __iter__(self) -> CircuitIterator: return self