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 partitioning algorithm #16

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
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
48 changes: 48 additions & 0 deletions examples/partitionor_test_randomcircuits.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from qiskit import transpile
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we do not need to commit this file into this project. If necessary, it would be better to appear in our test directory

from qiskit.circuit.random import random_circuit
from qiskit_aer import Aer
from qiskit.qasm2 import dumps
from qdao import Engine
from quafu import QuantumCircuit
from qdao.circuit import (
BasePartitioner,
CircuitHelperProvider,
QdaoCircuit,
StaticPartitioner,
UniQPartitioner,
BaselinePartitioner,
)
import pandas as pd

data = {"qubit": [], "static-partitionor": [], "Uniq-partitioner": []}
for i in range(8, 26):
for j in range(10):
num_qubits = i
num_primary = i - 4
num_local = 0
circ = random_circuit(num_qubits, i, measure=False, max_operands=2)
backend = Aer.get_backend("aer_simulator")
circ = transpile(circ, backend=backend)
quafu_circ = QuantumCircuit(1)
quafu_circ.from_openqasm(dumps(circ))
eng = Engine(
partitioner=StaticPartitioner(
np=num_primary, nl=num_local, backend="quafu"
),
circuit=quafu_circ,
num_primary=num_primary,
num_local=num_local,
backend="quafu",
)
data["qubit"].append(i)
data["static-partitionor"].append(eng.run())
eng = Engine(
partitioner=UniQPartitioner(np=num_primary, nl=num_local, backend="quafu"),
circuit=quafu_circ,
num_primary=num_primary,
num_local=num_local,
backend="quafu",
)
data["Uniq-partitioner"].append(eng.run())
df = pd.DataFrame(data)
df.to_csv("test_randomcircuit.csv", index=False)
97 changes: 96 additions & 1 deletion qdao/circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
This module provides methods to partition original circuit
into sub-circuits.
"""

import logging
import copy
from typing import Any, List

from qdao.qiskit.circuit import QiskitCircuitWrapper
Expand Down Expand Up @@ -72,7 +74,8 @@ 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))

print("----------BaselinePartitioner-----------")
print("num of sub-circuits:" + str(len(sub_circs)))
return sub_circs


Expand Down Expand Up @@ -109,7 +112,99 @@ 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)
print("----------StaticPartitioner-----------")
print("num of sub-circuits:" + str(len(sub_circs)))
return sub_circs


class dptask:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

naming convention please follow google style guide

"""To assist the Uniq partitioning algorithm"""

def __init__(self, qubitNum: int, gateNum: int):
self.gateNum = gateNum
self.qubitNum = qubitNum

def addGate(self, gateIndex: int, gatePos):
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, please refer to the style guide.

In short

  • For classes, use CamelCase convention.
  • For methods, variables, use lower_case_with_underscore convention.

if isinstance(gatePos, int):
gatePos = [gatePos]
for j in range(self.qubitNum):
self.result_bit[gateIndex + 1][j] += self.result_bit[gateIndex][j]
self.result_op[gateIndex + 1][j] += self.result_op[gateIndex][j]
if j in gatePos:
self.result_op[gateIndex + 1][j].append(gateIndex + 1)
if len(gatePos) == 2:
a = [x for x in gatePos if x != j][0]
self.result_bit[gateIndex + 1][j] += self.result_bit[gateIndex][a]
self.result_bit[gateIndex + 1][j] = list(
set(self.result_bit[gateIndex + 1][j])
)
self.result_op[gateIndex + 1][j] += self.result_op[gateIndex][a]
self.result_op[gateIndex + 1][j] = list(
set(self.result_op[gateIndex + 1][j])
)

def createTask(self, ops):
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function name will confuse users and developers. Basically, we already defined DpTask class, and one might think that DpTask() will create a task object. So I suggest to consider better naming to distinguish this function from vanilla DpTask instantiation.

self.result_bit = [
[[] for _ in range(self.qubitNum)] for _ in range(self.gateNum + 1)
]
self.result_op = [
[[] for _ in range(self.qubitNum)] for _ in range(self.gateNum + 1)
]
for i in range(self.qubitNum):
self.result_bit[0][i].append(i)
for i in range(self.gateNum):
self.addGate(i, ops[i].pos)

def selectSubCircuit(self, numSubcircuit: int) -> (list[int], int):
"""Find sub-lines that meet the requirements from the array in
preprocessing. The return value is the number of bits representing
the sub-circuit. When a sub-circuit that fails to obtain enough
qubits is selected at one time, the return value can be used for
the next selection."""
numQubit = 0
numOp = 0
opList = []
bitList = []
for i in range(self.gateNum):
for j in range(self.qubitNum):
if (
len(self.result_op[i + 1][j]) >= numOp
and len(self.result_bit[i + 1][j]) <= numSubcircuit
):
opList = self.result_op[i + 1][j]
bitList = self.result_bit[i + 1][j]
numQubit = len(self.result_bit[i + 1][j])
numOp = len(self.result_op[i + 1][j])
return (opList, numQubit)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not necessary to explicitly use tuple. return op_list, num_qubits would be enough



class UniQPartitioner(BasePartitioner):
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should consider registering UniQPartitioner into PARTITIONERS. This is somehow a naive factory design pattern offering better maintainablility. We could discuss more elegant practice.

"""Partitioner in UniQ"""
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest to add a reference here. Refer to sphinx docstring sections to see commonly used keywords.

Currently, we're not creating a document website. But trying to write standard docstrings will reduce lots of effort once we need to have a website holding documentation of this project.

"""
References:
    [1] <the-paper-link>
"""


def run(self, circuit: Any) -> List[QdaoCircuit]:
self._circ_helper.circ = circuit
ops = copy.deepcopy(self._circ_helper.instructions)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need to make a deep copy of instructions? If necessary, we need to measure the performance overhead when circuit becomes very large with a large number of instructions.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because after each selection of a subcircuit, the selected gate must be removed and preprocessed again. In order not to affect the original data, a deep copy operation is selected. This really isn't necessary and if it would cause a performance hit, I would build an array to record this kind of property.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

based on my understanding, all operations you performed on ops is pop() and get(). As long as we do not need to modify, i.e., write to, the value of ops[i]. There's no need to use deepcopy.

active = self._np
m = self._circ_helper.circ.num
sub_circs = []
while len(ops) != 0:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

> 0 is enough.

needQubit = active
instrs = []
while needQubit != 0 and len(ops) != 0:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe better to use need_qubit > 0 and len(ops) > 0

task = dptask(m, len(ops))
task.createTask(ops)
opList, numQubit = task.selectSubCircuit(needQubit)
if numQubit == 0:
break
needQubit -= numQubit
for i in range(len(ops), -1, -1):
if i + 1 in opList:
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)
print("----------UniqPartitioner-----------")
print("num of sub-circuits:" + str(len(sub_circs)))
Comment on lines +206 to +207
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it would be better to use logging, if this is just used for testing, it is not necessary to commit these lines.

return sub_circs


Expand Down
6 changes: 2 additions & 4 deletions qdao/quafu/circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,19 +72,17 @@ def gen_sub_circ(self, instrs: List[QuantumGate], num_local: int, num_primary: i

# 1. Get the set of qubits
qset = set(range(num_local))

# instrs: a series of gates
# [XGate, XGate, CXGate, RYGate, RXGate, RZGate, CZGate]

for instr in instrs:
for q in self.get_instr_qubits(instr):
qset.add(q)

# Fix qbit
sub_circ = QuantumCircuit(num_primary)
# Sorting []
real_qubits = sorted(list(qset))

# print("len(real_qubits)=" + str(len(real_qubits)))
# print("num_primary=" + str(num_primary))
assert len(real_qubits) <= num_primary

qubit_map = {q: i for i, q in enumerate(real_qubits)}
Expand Down
Loading