Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add uniq partition algorithm #17

Merged
merged 2 commits into from
Apr 6, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 160 additions & 2 deletions qdao/circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
This module provides methods to partition original circuit
into sub-circuits.
"""

import logging
from typing import Any, List

Expand Down Expand Up @@ -72,7 +73,6 @@ def run(self, circuit: Any) -> List[QdaoCircuit]:
sub_circ = self._circ_helper.gen_sub_circ([instr], self._nl, self._np)
sub_circs.append(sub_circ)
logging.info("Find sub-circuit: {}, qubits: {}".format(sub_circ.circ, qset))

return sub_circs


Expand Down Expand Up @@ -109,11 +109,169 @@ def run(self, circuit: Any) -> List[QdaoCircuit]:
if instrs:
sub_circ = self._circ_helper.gen_sub_circ(instrs, self._nl, self._np)
sub_circs.append(sub_circ)
logging.info("Find sub-circuit: {}, qubits: {}".format(sub_circ.circ, qset))
return sub_circs


class DependencyMatrix:
"""To assist the Uniq partitioning algorithm

A two-dimensional matrix is ​used to obtain the dependency between quantum
gates using a dynamic programming method, and based on this dependence,
a sub-circuit containing the most quantum gates that meets the requirements
is generated.We define op[i][j] as putting all operators that include qubit
j in the first i quantum gate and their dependencies into the same group.

Attributes:
gate_num: An integer holding the number of quantum gates contained in the quantum
circuit
qubit_num: An integer holding the number of qubits acted upon by the quantum circuit
result_bit: the set of all operators
result_op: the set of target a-bits
"""

def __init__(
self,
qubit_num: int,
gate_num: int,
) -> None:
"""Dependency matrix class constructor

Instantiate a dependency matrix class based on the given number of qubits and
quantum gates

Arg:
a:An integer representing the number of qubits
b:An integer representing the number of quantum gates
"""
self.gate_num = gate_num
self.qubit_num = qubit_num

def preprocessing_single_quantum_circuits(self, gate_index: int, gate_target: any):
"""Process a single quantum gate, adding it to the dependency matrix

Fill in the row of dependency matrix corresponding to the quantum gate.
For the qubit that the quantum gate acts on, combine the quantum gate sets
of all target bits in the previous row and add them to the current quantum
gate and put them into the dependency matrix. And record the qubits these q
uantum gates act on.

Arg:
gate_index: An integer representing the index of the quantum gate being processed
gate_target: An integer or list representing the qubits that the quantum gate acts on
"""
if isinstance(gate_target, int):
gate_target = [gate_target]
for j in range(self.qubit_num):
self.result_bit[gate_index + 1][j] += self.result_bit[gate_index][j]
self.result_op[gate_index + 1][j] += self.result_op[gate_index][j]
if j in gate_target:
self.result_op[gate_index + 1][j].append(gate_index + 1)
for k in gate_target:
self.result_bit[gate_index + 1][j] += self.result_bit[gate_index][k]
self.result_op[gate_index + 1][j] += self.result_op[gate_index][k]
self.result_bit[gate_index + 1][j] = list(
set(self.result_bit[gate_index + 1][j])
)
self.result_op[gate_index + 1][j] = list(
set(self.result_op[gate_index + 1][j])
)

def preprocessing_quantum_circuits(self, ops: list):
"""Process all quantum gates, adding it to the dependency matrix

Given a set of quantum gates, use the preprocessing_single_quantum_circuits function
to put them all into the dependency matrix

Arg:
ops:A list that stores the quantum gate sequence
"""
self.result_bit = [
[[] for _ in range(self.qubit_num)] for _ in range(self.gate_num + 1)
]
self.result_op = [
[[] for _ in range(self.qubit_num)] for _ in range(self.gate_num + 1)
]
for i in range(self.qubit_num):
self.result_bit[0][i].append(i)
for i in range(self.gate_num):
self.preprocessing_single_quantum_circuits(i, ops[i].pos)

def select_subcircuit(self, active_qubit_num: int) -> (List[int], int):
"""Find sub-lines that meet the requirements from the dependency matrix

Given an active qubit, which is the maximum number of qubits in the desired
subcircuit, find the set of quantum gates that is smaller than the active
qubit and contains the most quantum gates from the dependency matrix.
Returns a list containing the numbers of all quantum gates and the number
of qubits these quantum gates act on.

Arg:
active_qubit_num: An integer representing the required number of active qubits

Return:
list[int]:
int:
"""
qubit_num_subcircuit = 0
gate_num_subcircuit = 0
gate_list = []
for i in range(self.gate_num):
for j in range(self.qubit_num):
if (
len(self.result_op[i + 1][j]) >= gate_num_subcircuit
and len(self.result_bit[i + 1][j]) <= active_qubit_num
):
gate_list = self.result_op[i + 1][j]
qubit_num_subcircuit = len(self.result_bit[i + 1][j])
gate_num_subcircuit = len(self.result_op[i + 1][j])
return gate_list, qubit_num_subcircuit


class UniQPartitioner(BasePartitioner):
"""Partitioner in UniQ

References:
[1] https://ieeexplore.ieee.org/abstract/document/10045784/
"""

def run(self, circuit: Any) -> List[QdaoCircuit]:
self._circ_helper.circ = circuit
ops = []
ops += self._circ_helper.instructions
active = self._np
m = self._circ_helper.circ.num
sub_circs = []
while len(ops) > 0:
need_qubit = active
instrs = []
while need_qubit > 0 and len(ops) > 0:
task = DependencyMatrix(m, len(ops))
task.preprocessing_quantum_circuits(ops)
# Since there is a gap between the number of qubits
# selected in a single sub-circuit and the number of
# active qubits required, generating a sub-circuit
# requires multiple selections to minimize the number
# of sub-circuits.
gate_list, qubit_num_subcircuit = task.select_subcircuit(need_qubit)
if qubit_num_subcircuit == 0:
break
need_qubit -= qubit_num_subcircuit
for i in range(len(ops), -1, -1):
if i + 1 in gate_list:
instrs.append(ops[i])
ops.pop(i)
sub_circ = self._circ_helper.gen_sub_circ(instrs, self._nl, self._np)
sub_circs.append(sub_circ)
logging.info("Find sub-circuit: {}".format(sub_circ.circ))
return sub_circs


PARTITIONERS = {"baseline": BaselinePartitioner, "static": StaticPartitioner}
PARTITIONERS = {
"baseline": BaselinePartitioner,
"static": StaticPartitioner,
"uniq": UniQPartitioner,
}


INITIALIZERS = {"qiskit": QiskitCircuitWrapper, "quafu": QuafuCircuitHelper}
Expand Down
Loading