def test_2(self) -> None: coupling_graph = {(0, 1), (1, 2), (2, 3)} model = MachineModel(4, coupling_graph) l = model.get_locations(3) assert len(l) == 2 assert (0, 1, 2) in l assert (1, 2, 3) in l
def test_2(self) -> None: coupling_graph = {(0, 1), (1, 2), (0, 3), (2, 3)} model = MachineModel(4, coupling_graph) l = model.get_subgraph((0, 1, 3)) assert len(l) == 2 assert (0, 1) in l assert (0, 3) in l
def test_3(self) -> None: model = MachineModel(4) l = model.get_locations(3) assert len(l) == 4 assert (0, 1, 2) in l assert (0, 1, 3) in l assert (0, 2, 3) in l assert (1, 2, 3) in l
def test_coupling_graph_invalid(self) -> None: coupling_graph = {(0, 1), (1, 2), (2, 3)} with pytest.raises(TypeError): MachineModel(2, coupling_graph) with pytest.raises(TypeError): MachineModel(2, (0, 1)) # type: ignore with pytest.raises(TypeError): MachineModel(2, 0) # type: ignore with pytest.raises(TypeError): MachineModel(2, 'a') # type: ignore
def run(self, circuit: Circuit, data: dict[str, Any]) -> None: """Perform the pass's operation, see BasePass for more info.""" # Collect CircuitGate blocks blocks: list[tuple[CircuitPoint, Operation]] = [] for cycle, op in circuit.operations_with_cycles(): if isinstance(op.gate, CircuitGate): blocks.append((cycle, op)) # 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()) subdata = data.copy() # Perform work points: list[CircuitPoint] = [] ops: list[Operation] = [] for cycle, op in blocks: gate: CircuitGate = op.gate subcircuit = gate._circuit.copy() subcircuit.set_params(op.params) subnumbering = {op.location[i]: i for i in range(len(op.location))} subdata['machine_model'] = MachineModel( len(op.location), model.get_subgraph(op.location, subnumbering), ) for loop_pass in self.loop_body: loop_pass.run(circuit, subdata) if self.replace_filter(subcircuit, op): points.append(CircuitPoint(cycle, op.location[0])) ops.append( Operation( CircuitGate(subcircuit, True), op.location, subcircuit.get_params(), ), ) # TODO: Load freshly written data from subdata into data circuit.batch_replace(points, ops)
def test_coupling_graph(self) -> None: coupling_graph = {(0, 1), (1, 2), (2, 3)} t = MachineModel(4, coupling_graph) assert len(t.coupling_graph) == 3 for link in coupling_graph: assert link in t.coupling_graph
def test_alltoall_4(self) -> None: t = MachineModel(4) assert len(t.coupling_graph) == 6 assert (0, 1) in t.coupling_graph assert (0, 2) in t.coupling_graph assert (0, 3) in t.coupling_graph assert (1, 2) in t.coupling_graph assert (1, 3) in t.coupling_graph assert (2, 3) in t.coupling_graph
def gen_successors( self, circuit: Circuit, data: dict[str, Any], ) -> list[Circuit]: """ Generate the successors of a circuit node. Raises: ValueError: If circuit is a single-qudit circuit. """ if not isinstance(circuit, Circuit): raise TypeError('Expected circuit, got %s.' % type(circuit)) if circuit.get_size() < 2: raise ValueError('Cannot expand a single-qudit circuit.') # 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()) # TODO: Reconsider linear topology default successors = [] for edge in model.coupling_graph: successor = circuit.copy() successor.append_gate(self.two_qudit_gate, [edge[0], edge[1]]) successor.append_gate(self.single_qudit_gate_1, edge[0]) successor.append_gate(self.single_qudit_gate_2, edge[1]) successors.append(successor) return successors
def test_num_qudits_invalid(self) -> None: with pytest.raises(TypeError): MachineModel('a') # type: ignore with pytest.raises(ValueError): MachineModel(0)
def test_alltoall_2(self) -> None: t = MachineModel(2) assert len(t.coupling_graph) == 1 assert (0, 1) in t.coupling_graph
def test_num_qudits(self) -> None: for n in [1, 2, 3, 4]: t = MachineModel(n) assert t.num_qudits == n
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 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) num_cycles = circuit.get_num_cycles() num_qudits_groups = len(qudit_groups) op_cycles = [[[0] * self.block_size for q_group in qudit_groups] for cycle in range(num_cycles)] for cycle, op in circuit.operations_with_cycles(): if len(op.location) > 1: for q_group_index, q_group in enumerate(qudit_groups): if all([qudit in q_group for qudit in op.location]): for qudit in op.location: op_cycles[cycle][q_group_index][q_group.index( qudit)] = self.multi_gate_score else: for qudit in op.location: if qudit in q_group: op_cycles[cycle][q_group_index][q_group.index( qudit)] = -1 else: qudit = op.location[0] for q_group_index, q_group in enumerate(qudit_groups): if qudit in q_group: op_cycles[cycle][q_group_index][q_group.index( qudit)] = self.single_gate_score max_blocks = [] for q_group_index in range(num_qudits_groups): block_start = 0 block_ends = [0] * self.block_size score = 0 for cycle in range(num_cycles): if cycle: for qudit in range(self.block_size): if (op_cycles[cycle - 1][q_group_index][qudit] == -1 and op_cycles[cycle][q_group_index][qudit] != -1): max_blocks.append([ score, block_start, block_ends, q_group_index ], ) score = 0 block_start = cycle block_ends = [cycle + 1] * self.block_size break for qudit in range(self.block_size): if op_cycles[cycle][q_group_index][qudit] != -1: block_ends[qudit] = cycle + 1 score += op_cycles[cycle][q_group_index][qudit] max_blocks.append([score, block_start, block_ends, q_group_index]) block_id = -1 max_blocks.sort() remaining_assignments = circuit.get_size() * num_cycles block_map = [[-1] * circuit.get_size() for cycle in range(num_cycles)] while remaining_assignments: perform_assign = False if len(max_blocks) == 1: perform_assign = True else: block_start = max_blocks[-1][1] block_ends = max_blocks[-1][2] q_group_index = max_blocks[-1][3] score = 0 for cycle in range(block_start, max(block_ends)): for qudit in range(self.block_size): q = qudit_groups[q_group_index][qudit] if (cycle < block_ends[qudit] and block_map[cycle][q] == -1): score += 1 if score < max_blocks[-2][0]: max_blocks[-1][0] = score max_blocks.sort() else: perform_assign = True if perform_assign: block_id += 1 block_start = max_blocks[-1][1] block_ends = max_blocks[-1][2] q_group_index = max_blocks[-1][3] prev_status = None for cycle in range(block_start, max(block_ends)): status = [ block_map[cycle][qudit_groups[q_group_index][qudit]] for qudit in range(self.block_size) ] if prev_status and len(prev_status) <= len( status, ) and status != prev_status: block_id += 1 for qudit in range(self.block_size): if (cycle < block_ends[qudit] and block_map[cycle][ qudit_groups[q_group_index][qudit]] == -1): block_map[cycle][qudit_groups[q_group_index] [qudit]] = block_id remaining_assignments -= 1 prev_status = status del max_blocks[-1] for cycle in range(num_cycles): if not cycle or block_map[cycle] == block_map[cycle - 1]: continue indices = [{}, {}] for i in range(2): for qudit in range(circuit.get_size()): block = block_map[cycle - i][qudit] if block not in indices[i]: indices[i][block] = [] indices[i][block].append(qudit) for prev_blocks, prev_qudits in indices[1].items(): for current_qudits in indices[0].values(): if all([qudit in prev_qudits for qudit in current_qudits]): for qudit in current_qudits: block_map[cycle][qudit] = prev_blocks blocks = {} for cycle in range(num_cycles): for qudit in range(circuit.get_size()): if block_map[cycle][qudit] not in blocks: blocks[block_map[cycle][qudit]] = {} blocks[block_map[cycle][qudit]][-1] = cycle blocks[block_map[cycle][qudit]][qudit] = cycle block_order = [] for block in blocks.values(): block_order.append([block, block[-1]]) block_order.sort(reverse=True, key=lambda x: x[1]) for block, start_cycle in block_order: points_in_block = [] for cycle, op in circuit.operations_with_cycles(): qudit = op.location[0] if (qudit in block and cycle >= start_cycle and cycle <= block[qudit]): points_in_block.append((cycle, qudit)) circuit.fold(circuit.get_region(points_in_block))
circ.append_gate(CNOTGate(), [0, 1]) circ.append_gate(CNOTGate(), [1, 2]) circ.append_gate(U3Gate(), [2], [0, 0, pi / 8]) circ.append_gate(CNOTGate(), [1, 2]) circ.append_gate(U3Gate(), [2], [0, 0, 7 * pi / 4]) circ.append_gate(CNOTGate(), [1, 2]) circ.append_gate(U3Gate(), [2], [pi / 2, 0, 5 * pi / 4]) circ.append_gate(CNOTGate(), [2, 3]) circ.append_gate(CNOTGate(), [3, 2]) circ.append_gate(CNOTGate(), [2, 3]) circ.append_gate(CNOTGate(), [2, 1]) circ.append_gate(CNOTGate(), [1, 2]) circ.append_gate(CNOTGate(), [2, 1]) circ.append_gate(CNOTGate(), [4, 3]) circ.append_gate(CNOTGate(), [3, 4]) circ.append_gate(CNOTGate(), [4, 3]) # Do partitioning mach = MachineModel(num_q, coup_map) part = ScanPartitioner(mach, 3) data = {} # type: ignore part.run(circ, data) circ_iter = CircuitIterator(circ) count = 0 for op in circ_iter: print('unitary %d' % (count)) print(op.get_unitary().get_numpy()) # type: ignore count += 1
def test_invalid(self) -> None: coupling_graph = {(0, 1), (1, 2), (2, 3)} model = MachineModel(4, coupling_graph) with pytest.raises(TypeError): model.get_locations('a')
def test_alltoall_3(self) -> None: t = MachineModel(3) assert len(t.coupling_graph) == 3 assert (0, 1) in t.coupling_graph assert (0, 2) in t.coupling_graph assert (1, 2) in t.coupling_graph
def synthesize(self, utry: UnitaryMatrix, data: dict[str, Any]) -> Circuit: """Synthesize `utry` into a circuit, see SynthesisPass for more info.""" # 0. Skip any unitaries too small for the configured block. if self.block_size_start > utry.get_size(): _logger.warning( 'Skipping synthesis: block size is larger than input unitary.', ) return Circuit.from_unitary(utry) # 1. Create empty circuit with same size and radixes as `utry`. circuit = Circuit(utry.get_size(), utry.get_radixes()) # 2. Calculate block sizes block_size_end = utry.get_size() - 1 if self.block_size_limit is not None: block_size_end = self.block_size_limit # 3. Calculate relevant coupling_graphs # TODO: Look for topology info in `data`, use all-to-all otherwise. model = MachineModel(utry.get_size()) locations = [ model.get_locations(i) for i in range(self.block_size_start, block_size_end + 1) ] # 3. Bottom-up synthesis: build circuit up one gate at a time layer = 1 last_cost = 1.0 last_loc = None while True: remainder = utry.get_dagger() @ circuit.get_unitary() sorted_locations = self.analyze_remainder(remainder, locations) for loc in sorted_locations: # Never predict the previous location if loc == last_loc: continue _logger.info(f'Trying next predicted location {loc}.') circuit.append_gate(VariableUnitaryGate(len(loc)), loc) circuit.instantiate( utry, **self.instantiate_options, # type: ignore ) cost = self.cost.calc_cost(circuit, utry) _logger.info(f'Instantiated; layers: {layer}, cost: {cost:e}.') if cost < self.success_threshold: _logger.info(f'Circuit found with cost: {cost:e}.') _logger.info('Successful synthesis.') return circuit progress_threshold = self.progress_threshold_a progress_threshold += self.progress_threshold_r * np.abs(cost) if last_cost - cost >= progress_threshold: _logger.info('Progress has been made, depth increasing.') last_loc = loc last_cost = cost layer += 1 break _logger.info('Progress has not been made.') circuit.pop((-1, loc[0]))
def run(self, circuit: Circuit, data: dict[str, Any]) -> None: """Perform the pass's operation, see BasePass for more info.""" # Collect synthesizable operations ops_to_syn: list[tuple[int, Operation]] = [] for cycle, op in circuit.operations_with_cycles(): if self.collection_filter(op): ops_to_syn.append((cycle, op)) # 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()) sub_data = data.copy() structure_list: list[Sequence[int]] = [] # Synthesize operations errors: list[float] = [] points: list[CircuitPoint] = [] new_ops: list[Operation] = [] num_blocks = len(ops_to_syn) for block_num, (cycle, op) in enumerate(ops_to_syn): sub_numbering = {op.location[i]: i for i in range(op.size)} sub_data['machine_model'] = MachineModel( len(op.location), model.get_subgraph(op.location, sub_numbering), ) structure_list.append([op.location[i] for i in range(op.size)]) syn_circuit = self.synthesize(op.get_unitary(), sub_data) if self.checkpoint_dir is not None: save_checkpoint(syn_circuit, self.checkpoint_dir, block_num) if self.replace_filter(syn_circuit, op): # Calculate errors new_utry = syn_circuit.get_unitary() old_utry = op.get_unitary() error = new_utry.get_distance_from(old_utry) errors.append(error) points.append(CircuitPoint(cycle, op.location[0])) new_ops.append( Operation( CircuitGate(syn_circuit, True), op.location, list(syn_circuit.get_params()), # TODO: RealVector ), ) _logger.info( f'Error in synthesized CircuitGate {block_num+1} of ' f'{num_blocks}: {error}', ) data['synthesispass_error_sum'] = sum(errors) # TODO: Might be replaced _logger.info( 'Synthesis pass completed. Upper bound on ' f"circuit error is {data['synthesispass_error_sum']}", ) if self.checkpoint_dir is not None: with open(f'{self.checkpoint_dir}/structure.pickle', 'wb') as f: dump(structure_list, f) circuit.batch_replace(points, new_ops)