Example #1
0
    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
Example #2
0
    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
Example #3
0
    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
Example #4
0
 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
Example #5
0
    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)
Example #6
0
    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
Example #7
0
 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
Example #8
0
    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
Example #9
0
 def test_num_qudits_invalid(self) -> None:
     with pytest.raises(TypeError):
         MachineModel('a')  # type: ignore
     with pytest.raises(ValueError):
         MachineModel(0)
Example #10
0
 def test_alltoall_2(self) -> None:
     t = MachineModel(2)
     assert len(t.coupling_graph) == 1
     assert (0, 1) in t.coupling_graph
Example #11
0
 def test_num_qudits(self) -> None:
     for n in [1, 2, 3, 4]:
         t = MachineModel(n)
         assert t.num_qudits == n
Example #12
0
    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)
Example #13
0
    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))
Example #14
0
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
Example #15
0
    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')
Example #16
0
 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
Example #17
0
    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]))
Example #18
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)