Sequential Quantum Gate Decomposer  v1.9.6
Powerful decomposition of general unitarias into one- and two-qubit gates gates
split.py
Go to the documentation of this file.
1 from squander.partitioning.tools import build_dependency
2 import heapq
3 
4 def _get_topo_order(g, rg, gate_to_qubit):
5  g = { x: set(y) for x, y in g.items() }
6  rg = { x: set(y) for x, y in rg.items() }
7  S = [(tuple(sorted(gate_to_qubit[m])), m) for m in rg if len(rg[m]) == 0]
8  heapq.heapify(S)
9  L = []
10  while S:
11  n = heapq.heappop(S)[1]
12  L.append(n)
13  assert not rg[n]
14  for m in set(g[n]):
15  g[n].remove(m)
16  rg[m].remove(n)
17  if not rg[m]:
18  heapq.heappush(S, (tuple(sorted(gate_to_qubit[m])), m))
19  return L
20 
21 def _get_balanced_initial_partitions(g, rg, gate_to_qubit):
22  topo = _get_topo_order(g, rg, gate_to_qubit)
23  mid = len(topo)//2
24  return set(topo[:mid]), set(topo[mid:])
25 
26 def _check_qubits(X, gate_to_qubit, max_qubit):
27  print(X, set.union(*(gate_to_qubit[x] for x in X)))
28  return len(set.union(*(gate_to_qubit[x] for x in X))) <= max_qubit
29 
30 #https://en.wikipedia.org/wiki/Kernighan%E2%80%93Lin_algorithm
31 #here we must always ensure A is topologically orderable before B
32 def _kernighan_lin(g, rg, A, B):
33  gv, av, bv, Anew, Bnew = [], [], [], set(A), set(B)
34  Da = {x: sum(1 if y in A else -1 for y in g[x]|rg[x]) for x in A}
35  Db = {x: sum(1 if y in B else -1 for y in g[x]|rg[x]) for x in B}
36  while True:
37  #a node in A can only move if it doesnt have any children in A
38  #a node in B can only move if it doesnt have any parents in B
39  Da = {x: sum(1 if y in A else -1 for y in g[x]|rg[x]) for x in A}
40  Db = {x: sum(1 if y in B else -1 for y in g[x]|rg[x]) for x in B}
41  #assert Da == {x: sum(1 if y in A else -1 for y in g[x]|rg[x]) for x in A}, (set(Da.items()) ^ set({x: sum(1 if y in A else -1 for y in g[x]|rg[x]) for x in A}.items()))
42  #assert Db == {x: sum(1 if y in B else -1 for y in g[x]|rg[x]) for x in B}, (set(Db.items()) ^ set({x: sum(1 if y in B else -1 for y in g[x]|rg[x]) for x in B}.items()))
43  gv.clear(); av.clear(); bv.clear()
44  for _ in range(0, len(g)//2):
45  apos = {x for x in A if not (g[x] & Anew)}
46  bpos = {x for x in B if not (rg[x] & Bnew)}
47  if len(apos) == 0 or len(bpos) == 0: break
48  a, b = max(apos, key=Da.__getitem__), max(bpos, key=Db.__getitem__)
49  if b in g[a]:
50  aalt = max((x for x in apos if x not in rg[b]), key=Da.__getitem__, default=None)
51  balt = max((x for x in bpos if x not in g[a]), key=Db.__getitem__, default=None)
52  candidates = [(a, b, Da[a]+Db[b]-2)]
53  if not aalt is None: candidates.append((aalt, b, Da[aalt]+Db[b]))
54  if not balt is None: candidates.append((a, balt, Da[a]+Db[balt]))
55  a, b, gc = max(candidates, key=lambda x: x[2])
56  else: gc = Da[a] + Db[b]
57  if gc == 0: break
58  gv.append(gc+(gv[-1] if gv else 0)); av.append(a); bv.append(b)
59  A.remove(a); B.remove(b)
60  Anew.remove(a); Bnew.remove(b); Anew.add(b); Bnew.add(a)
61  for x in g[a]: Db[x] += 2
62  for x in rg[a]: Da[x] -= 2
63  for x in g[b]: Db[x] -= 2
64  for x in rg[b]: Da[x] += 2
65  Db[a] = Da[a] + 2*(len(g[a]) - len(rg[a])); del Da[a]
66  Da[b] = Db[b] + 2*(len(rg[b]) - len(g[b])); del Db[b]
67  k = max(range(len(gv)), key=gv.__getitem__, default=None)
68  if k is None or gv[k] <= 0: break
69  A.update(bv[:k+1], av[k+1:])
70  Anew.difference_update(bv[k+1:]); Anew.update(av[k+1:])
71  B.update(av[:k+1], bv[k+1:])
72  Bnew.difference_update(av[k+1:]); Bnew.update(bv[k+1:])
73  A.update(av)
74  B.update(bv)
75  return A, B
76 
77 def _do_split_partitions(g, rg, gate_to_qubit, splitfunc, max_qubit):
78  worklist, output = [set(g)], []
79  while worklist:
80  part = worklist.pop()
81  if _check_qubits(part, gate_to_qubit, max_qubit): output.append(part)
82  else:
83  gpart, rgpart = {x: g[x] & part for x in part}, {x: rg[x] & part for x in part}
84  A, B = splitfunc(gpart, rgpart, *_get_balanced_initial_partitions(gpart, rgpart, gate_to_qubit))
85  worklist.append(B)
86  worklist.append(A)
87  return output
88 
89 def split_partitions(c, max_qubit):
90  gate_dict, g, rg, gate_to_qubit, S = build_dependency(c)
91  from squander.partitioning.kahn import kahn_partition_preparts
92  L = _do_split_partitions(g, rg, gate_to_qubit, _kernighan_lin, max_qubit)
93  return kahn_partition_preparts(c, max_qubit, preparts=L)
94 
96  K = 3
97  #filename = "examples/partitioning/qasm_samples/heisenberg-16-20.qasm"
98  filename = "benchmarks/partitioning/test_circuit/0410184_169.qasm"
99  from squander import utils
100 
101  circ, parameters = utils.qasm_to_squander_circuit(filename)
102  partition = split_partitions(circ, K)
103 
104  print(partition[2], len(partition[2]))
105 
106 if __name__ == "__main__":
def _check_qubits(X, gate_to_qubit, max_qubit)
Definition: split.py:26
def _get_balanced_initial_partitions(g, rg, gate_to_qubit)
Definition: split.py:21
def kahn_partition_preparts(c, max_qubit, preparts)
Definition: kahn.py:80
def _get_topo_order(g, rg, gate_to_qubit)
Definition: split.py:4
def split_partitions(c, max_qubit)
Definition: split.py:89
def build_dependency
Definition: tools.py:136
def _kernighan_lin(g, rg, A, B)
Definition: split.py:32
def _test_split_qasm()
Definition: split.py:95
def _do_split_partitions(g, rg, gate_to_qubit, splitfunc, max_qubit)
Definition: split.py:77