QNLP  v1.0
circuit_printer.py
Go to the documentation of this file.
2  """
3  The CircuitPrinter class creates a quantum circuit .tex file for viewing the circuit output.
4  Assumes the availability of the quantikz LaTeX package when compiling. Preferably use lualatex
5  to ensure sufficient memory access.
6  """
7  def __init__(self, num_qubits):
8  self.num_qubits = num_qubits
9  self.ccts = []
10 
11  def ncu_cct_depth(self, num_ctrl):
12  """
13  Calculate the required depth for the circuit using the implemented nCU decomposition
14  """
15  if num_ctrl > 2:
16  return 2 + 3*self.ncu_cct_depth(num_ctrl-1)
17  else:
18  return 5
19 
20  def ctrl_line_column(self, gate_name, ctrl, tgt, column_length):
21  """
22  Creates a columnar slice of the circuit with the given gate name, control and target lines.
23  The number of qubits specifies the length of the column.
24  """
25 
26  column = [r"\qw & "]*column_length
27  column[tgt] = r"\gate{{{}}} & ".format(gate_name)
28  column[ctrl] = r"\ctrl{{{}}} & ".format(tgt - ctrl)
29  return column
30 
31  def single_qubit_line_column(self, gate_name, index, column_length):
32  """
33  Creates a columnar slice of the circuit with the given gate name, on index
34  The number of qubits specifies the length of the column.
35  """
36 
37  column = ["\qw & "]*column_length
38  column[index] = r"\gate{{{}}} & ".format(gate_name)
39  return column
40 
41  def slice_line_column(self, slice_name, column_length):
42  """
43  Creates a columnar slice of the circuit with a dashed line denoting the marked section
44  """
45 
46  column = ["\qw & "]*column_length
47  if "\\" in slice_name:
48  slice_name = "${}$".format(slice_name)
49  column[0] = r"\qw\slice{{{}}} & ".format(slice_name)
50  return column
51 
52  def load_data_csv(self, csv_file):
53  """
54  Loads the data from a CSV file. Assumes the following layout:
55  gate_name, control_qubit_number, target_qubit_number, gate_matrix_values
56  """
57  import csv
58  cct = []
59  with open(csv_file, 'r') as csvfile:
60 
61  filereader = csv.reader(csvfile, delimiter=',')
62  data = list(filereader)
63  cct_local = []
64  mergeable_row = 0
65  for idx,row in enumerate(data[1:]):
66  #Check for break in circuit runs, empty data, and single run datasets (ie currently empty output)
67  prev_was_ctrl = False
68  if (row != "\n") and (row != []) and (row != data[-1]):
69  if row[0][0:4] == "#!#{":
70  slice_label = row[0].rsplit("}")[0].rsplit("{")[-1]
71  slice_col = self.slice_line_column(slice_label, self.num_qubits)
72  cct_local.append(slice_col)
73 
74  elif int(row[1]) <= 2**32:
75  prev_was_ctrl = True
76  cct_local.append(self.ctrl_line_column(row[0], int(row[1]), int(row[2]), self.num_qubits) )
77 
78  else:
79  #Single qubit value; max value here indicates that the output value for the ctrl line was 2^64, hence non-existent
80  curr_col = self.single_qubit_line_column(row[0], int(row[2]), self.num_qubits)
81  #Attempt to merge non interfering gates to the same column to reduce visual circuit size
82  '''if (len(cct_local) > 0) and (prev_was_ctrl == False):
83  prev_col = cct_local[-1]
84 
85  icurr = [idx for (idx,val) in enumerate(curr_col) if val != "\\qw & "]
86  iprev = [idx for (idx,val) in enumerate(prev_col) if val != "\\qw & "]
87 
88  if icurr[0] != iprev[0] and len(icurr) == 1 and len(iprev) == 1:
89  curr_row[iprev[0]] = prev_row[iprev[0]]
90  del cct_local[-1]
91  '''
92  cct_local.append(curr_col)
93  prev_was_ctrl == False
94  else:
95  cct.append(cct_local)
96  cct_local = []
97  self.ccts = cct
98  return cct
99 
100  def row_merge():
101  """
102  WIP: Merges rows between independent qubit operations to reduce printed circuit size
103  e.g. [I, I, I, X], [I, Z, I, I] -> [I, Z, X, I]
104  """
105  data = self.ccts[0]
106  for i in range(1,len(data)):
107  prev_mod_vals = [(idx,val) for (idx,val) in enumerate(data[i-1]) if val != "\\qw & "]
108  curr_mod_vals = [(idx,val) for (idx,val) in enumerate(data[i]) if val != "\\qw & "]
109  for j in prev_mod_vals:
110  prev_has_ctrl = []
111  if "\\ctrl" in j[1]:
112  # Terrible, but it will do for now
113  range_len_prev = int(j[1].rsplit("}")[0].rsplit("{")[1])
114  prev_has_ctrl = [(j[0], j[0] + range_len_prev)]
115  for k in curr_mod_vals:
116  curr_has_ctrl = []
117  if "\\ctrl" in k[1]:
118  # Still terrible, but it will do for now
119  range_len_curr = int(k[1].rsplit("}")[0].rsplit("{")[1])
120  curr_has_ctrl = [(k[0], k[0] + range_len_curr)]
121  # Continue this later... seems incredibly inefficient
122 
123 
124  def latex_cct(self, data_file, file_name="cct", max_depth=16):
125  """
126  LaTeX file outputter for quantum circuit generation.
127  """
128  cct_array = self.load_data_csv(data_file)
129  num_cct = len(cct_array)
130  depth = len(cct_array[0])
131 
132  for cct_idx in range(num_cct):
133  with open(file_name + "_" + str(cct_idx) + ".tex", "w") as f:
134  f.write("\\documentclass{article} \n \\usepackage{amsmath} \\usepackage{adjustbox} \\usepackage{tikz} \\usetikzlibrary{quantikz} \\usepackage[margin=0.5cm]{geometry} \n \\begin{document} \centering\n")
135  # Split the circuit on a given depth boundary
136 
137  # Due to issues with latex variables ending with numeric indices, appending letters to the temporary savebox commands allows us to generate multiple of these.
138  # As depth is an issue with circuits, we currently expect the output not to exceed n-choose-k of 52-C-4 = 270725 combinations
139  # This will suffice until later.
140  import string, itertools
141  s_list = [i for i in string.ascii_letters]
142  box_str = r"\Box"
143  label_iterator = itertools.combinations(s_list,4)
144  box_labels = []
145 
146  for i in range(0, depth, max_depth):
147  local_label = box_str + "".join( next(label_iterator))
148  box_labels.append(local_label)
149  f.write(r"\newsavebox{{{}}}".format(local_label))
150  f.write(r"\savebox{{{}}}{{".format(local_label))
151 
152  f.write("\\begin{quantikz}[row sep={0.5cm,between origins}, column sep={0.75cm,between origins}, slice label style={inner sep=1pt,anchor=south west,rotate=40}]")
153  #Transposes the data so that q rows of length n exist, rather than n cols of length q
154  if(i + max_depth < depth):
155  cct_list_qubit= list(map(list, zip(*cct_array[cct_idx][i:i+max_depth])))
156  else:
157  cct_list_qubit= list(map(list, zip(*cct_array[cct_idx][i:])))
158 
159  for q in range(len(cct_list_qubit)):
160  out_str = "\\qw & ".join(cct_list_qubit[q]) + " \\qw "
161  if(q != len(cct_list_qubit)-1):
162  out_str += " \\\\ "
163  f.write(out_str)
164  f.write(" \\end{quantikz}\n}\n")
165  f.write("\n")
166 
167  for idx,l in enumerate(box_labels):
168  f.write(r"\usebox{{{}}} \\".format(l))
169  f.write("\n \\vspace{2em}")
170 
171  f.write(r"\end{document}")
172 
173 if __name__== "__main__":
174  import os
175  args = os.sys.argv
176  if len(args) < 4:
177  print("Please specify the file to load and number of qubits in sim, and output filename to save: python cct.py <CSV> <>")
178  exit()
179  CCT = CircuitPrinter(num_qubits=int(args[3]))
180  CCT.latex_cct(args[1], args[2], max_depth=int(args[4]))
def ctrl_line_column(self, gate_name, ctrl, tgt, column_length)
def latex_cct(self, data_file, file_name="cct", max_depth=16)
def single_qubit_line_column(self, gate_name, index, column_length)
def slice_line_column(self, slice_name, column_length)