Sequential Quantum Gate Decomposer  v1.9.6
Powerful decomposition of general unitarias into one- and two-qubit gates gates
kahn.py
Go to the documentation of this file.
1 from itertools import dropwhile
2 
3 from squander.gates.qgd_Circuit import qgd_Circuit as Circuit
4 from squander.partitioning.tools import build_dependency
5 import heapq
6 
7 def kahn_partition(c, max_qubit, preparts=None):
8  """
9  Partition a circuit greedily using Kahn's algorithm
10 
11  Args:
12 
13  c: SQUANDER Circuit to partition
14 
15  max_qubit: Max qubits per partition
16 
17  preparts: Optional predefined partitions
18 
19  Returns:
20 
21  Partitioned circuit, parameter order (source_idx, dest_idx, param_count), partition assignments
22  """
23  # Build dependency graphs
24  gate_dict, g, rg, gate_to_qubit, S = build_dependency(c)
25  S = [(tuple(sorted(gate_to_qubit[x])), x) for x in S]
26  heapq.heapify(S)
27 
28  L = []
29 
30  if preparts is None:
31  def partition_condition(idx):
32  return len(gate_to_qubit[S[idx][1]] | curr_partition) > max_qubit
33  else:
34  def partition_condition(idx):
35  return not S[idx][1] in preparts[len(parts)-1]
36 
37  curr_partition = set()
38  curr_idx = 0
39  total = 0
40  parts = [[]]
41 
42  while S:
43  idx = next(dropwhile(partition_condition, range(len(S))), None)
44  n = S[idx][1] if idx is not None else None
45  if preparts is not None:
46  assert (n is None) == (len(preparts[len(parts)-1]) == len(parts[-1])) #sanity check valid partitioning
47 
48  if n is None: # partition cannot be expanded
49  # Add partition to circuit
50  total += len(parts[-1])
51  # Reset for next partition
52  curr_partition = set()
53  parts.append([])
54  idx = next(dropwhile(partition_condition, range(len(S))), None)
55  n = S[idx][1] if idx is not None else None
56 
57 
58  # Add gate to current partition
59  parts[-1].append(n)
60  curr_partition |= gate_to_qubit[n]
61  curr_idx += gate_dict[n].get_Parameter_Num()
62 
63  # Update dependencies
64  L.append(n)
65  del S[idx]
66  assert len(rg[n]) == 0
67  for child in set(g[n]):
68  g[n].remove(child)
69  rg[child].remove(n)
70  if not rg[child]:
71  heapq.heappush(S, (tuple(sorted(gate_to_qubit[child])), child))
72 
73  # Add the last partition
74  total += len(parts[-1])
75  assert total == len(gate_dict)
76  # print(parts)
77  return kahn_partition_preparts(c, max_qubit, preparts=parts)
78 
79 
80 def kahn_partition_preparts(c, max_qubit, preparts):
81  """
82  Build partitioned circuit from predefined partitions
83 
84  Args:
85 
86  c: SQUANDER Circuit to partition
87 
88  max_qubit: Max qubits per partition
89 
90  preparts: Predefined partition assignments
91 
92  Returns:
93 
94  Partitioned circuit, parameter order (source_idx, dest_idx, param_count), partition assignments
95  """
96  top_circuit = Circuit(c.get_Qbit_Num())
97 
98  # Build dependency graphs
99  gate_dict, g, rg, gate_to_qubit, S = build_dependency(c)
100  S = [(tuple(sorted(gate_to_qubit[x])), x) for x in S]
101  heapq.heapify(S)
102 
103  preparts = split_partition(preparts, g, rg)
104 
105  L = []
106  param_order = []
107 
108  if preparts is None:
109  def partition_condition(idx):
110  return len(gate_to_qubit[S[idx][1]] | curr_partition) > max_qubit
111  else:
112  def partition_condition(idx):
113  return not S[idx][1] in preparts[len(parts)-1]
114 
115  c = Circuit(c.get_Qbit_Num())
116  curr_partition = set()
117  curr_idx = 0
118  total = 0
119  parts = [[]]
120 
121  while S:
122  idx = next(dropwhile(partition_condition, range(len(S))), None)
123  n = S[idx][1] if idx is not None else None
124  if preparts is not None:
125  assert (n is None) == (len(preparts[len(parts)-1]) == len(parts[-1])) #sanity check valid partitioning
126 
127  if n is None: # partition cannot be expanded
128  # Add partition to circuit
129  top_circuit.add_Circuit(c)
130  total += len(c.get_Gates())
131  # Reset for next partition
132  curr_partition = set()
133  c = Circuit(c.get_Qbit_Num())
134  parts.append([])
135  idx = next(dropwhile(partition_condition, range(len(S))), None)
136  n = S[idx][1] if idx is not None else None
137 
138 
139  # Add gate to current partition
140  parts[-1].append(n)
141  curr_partition |= gate_to_qubit[n]
142  c.add_Gate(gate_dict[n])
143  param_order.append((
144  gate_dict[n].get_Parameter_Start_Index(),
145  curr_idx,
146  gate_dict[n].get_Parameter_Num()
147  ))
148  curr_idx += gate_dict[n].get_Parameter_Num()
149 
150  # Update dependencies
151  L.append(n)
152  del S[idx]
153  assert len(rg[n]) == 0
154  for child in set(g[n]):
155  g[n].remove(child)
156  rg[child].remove(n)
157  if not rg[child]:
158  heapq.heappush(S, (tuple(sorted(gate_to_qubit[child])), child))
159 
160  # Add the last partition
161  top_circuit.add_Circuit(c)
162  total += len(c.get_Gates())
163  assert total == len(gate_dict)
164  # print(parts)
165  return top_circuit, param_order, parts
166 
167 
168 def split_partition(preparts, g, rg):
169  """
170  Split partitions into connected components
171 
172  Args:
173 
174  preparts: Initial partitions
175 
176  g: Forward dependency graph
177 
178  rg: Reverse dependency graph
179 
180  Returns:
181 
182  List of split partitions
183  """
184  L = []
185  for part in preparts:
186  V = set(part)
187  while V:
188  S = [next(iter(V))]
189  L.append(set())
190  while S:
191  node = S.pop()
192  if not node in V:
193  continue
194  V.remove(node)
195  L[-1].add(node)
196  S.extend(rg[node])
197  S.extend(g[node])
198  ord_dict = {x: i for i, part in enumerate(preparts) for x in part}
199  return [list(sorted(x, key=ord_dict.get)) for x in L]
200 
201 
203  """
204  Test split_partition function for correctness.
205  """
206  q = 3
207  c = Circuit(q)
208 
209  c.add_H(0)
210  c.add_H(1)
211  c.add_CNOT(0, 1)
212  c.add_Z(2)
213  c.add_H(2)
214 
215  expected = [[0, 1, 2], [3, 4]]
216 
217  _, g, rg, _, _ = build_dependency(c)
218 
219  split = split_partition([[0, 1, 2, 3, 4]], g, rg)
220 
221  print(split)
222 
223  assert { frozenset(x) for x in expected } == { frozenset(x) for x in split }
224 
225 
226 if __name__ == "__main__":
def kahn_partition(c, max_qubit, preparts=None)
Definition: kahn.py:7
def kahn_partition_preparts(c, max_qubit, preparts)
Definition: kahn.py:80
def test_split_partition()
Definition: kahn.py:202
def split_partition(preparts, g, rg)
Definition: kahn.py:168
def get_Parameter_Num(self)
Call to get the number of free parameters in the gate structure used for the decomposition.
def build_dependency
Definition: tools.py:136