Source code for qubiter.latex_tools.AsciiPic_to_Latex

import numpy as np
import copy as cp
from qubiter.Controls import *


[docs]class AsciiPic_to_Latex: """ This class can translate a Qubiter Picture file (which is a text file that contains an ASCII picture of a quantum circuit) into Latex code that can be compiled into a scholarly, publication quality eps or pdf picture of the circuit. The Latex code generated by this class calls commands from the package QCircuit, which in turn calls commands from the package xypic. References ---------- 1. Bryan Eastin, S. Flammia, "Q-circuit Tutorial", https://arxiv.org/abs/quant-ph/0406003v2 2. K.H. Rose, "xypic users's guide" (There is also a much longer "xypic reference manual" You can use the Latex code produced by this class as a starting point. You can tweak the code by hand by adding annotations in Latex or by changing various parameters as explained in the xypic and Qcircuit documentation. Instead of going from Qubiter Picture file to Latex code directly, this class inserts an intermediate translation step that we call a Mosaic ascii picture. A Mosaic picture is a simplified, abridged version of a Qubiter "native" Picture file. In Qubiter native pictures, each gate is made up of multiple bonds, and each bond is 4 characters long. A Mosaic picture uses 1 character per bond instead of 4, usually retaining only the first character of each bond of the counterpart Qubiter native picture. Hence, a Mosaic picture is exactly 1/4 as long as its Qubiter native picture counterpart. Qubiter native pictures and Mosaic pictures can both be machine generated by Qubiter or written by hand. Because they are 1/4 as long, Mosaic pictures are easier to write by hand than Qubiter native pictures. Here is a quantum circuit with 7 qubits and 9 gates expressed in Qubiter native picture format and in Mosaic picture format. In both pictures, time points downwards. Each line (row) represents one gate, or one unit of time. Columns 0, 4, 8, ... represent a qubit in the Qubiter native pic. Qubiter native pic:: | X---+---+---@ | | | Y---+---O---@---O | | H | | | | | | <---+---+---> | | M | | | | | | @---X | | | | | @---+---+---+---+---X | @---+---+---O---+---X | Mosaic pic:: |X++@|| |Y+O@O| |H||||| |<++>|| M|||||| @X||||| @++++X| @++O+X| As you can see, in the Mosaic pic, the white spaces and hyphens are omitted. The following symbols are kept:: kept = [ 'H', # Hadamard matrix 'M', # measurement of type 2 'O', # Control of type False '@', # Control of type True 'X', # sigma_X Pauli matrix 'Y', # sigma_Y Pauli matrix 'Z', # sigma_Z Pauli matrix '<', # left edge of swap gate '>', # right edge of swap gate '|', # wire connecting two times '+', # '|' connecting 2 times crossed by a '-' connecting two qubits '$'] # place holder to be replaced in Latex by initial ket of a qubit In translating a Mosaic picture to Latex, this class will try to interpret each character of a Mosaic pic by using the "kept" list above. If you insert a character that it can't interpret, like for example an 'A', it will translate that character to a Latex one qubit box with an 'A' inside it. If a gate (=time=mosaic pic row) has at least one '?' in the mosaic pic, it will be drawn in the Latex with inter-qubit wiring. For example, a mosaic row ``|AA|?|`` will be drawn in the Latex with a box around each A and a box around the ? and a wire connecting all 3 boxes. In Qubiter native pics and Mosaic pics, time points down, the way we normally read English. In the Latex pictures generated by this class, you can choose for time to point either from left to right (>) or vice versa (<). The > convention is more popular than the < convention, but the < convention is preferred by the cognoscenti because it recognizes that quantum circuit diagrams are merely a graphical representation of Dirac notation, and Dirac notation uses the < convention. Reversing a convention espoused by the universally popular Dirac notation is a totally needless complication. Attributes ---------- gb_char_arr : np.ndarray This internal variable is a rectangular array of single characters, of shape (num_gates, num_qbits), hence its name starts with gb. Each gate is a unit of time, and, as usual in Qubiter, the words bit and qubit are used interchangeably. To build this array, we start by splitting a Mosaic picture into a character array, then we make some modifications like adding a row of '$' characters and another of '|' characters as placeholders. gb_ch_is_measured : np.ndarray[bool] Internal variable, a bool array of the same shape as gb_char_arr. For each ch (aka character, node, vertex) entry in gb_char_arr, the corresponding bool entry of this array answers the question of whether or not that node is already measured (i.e., whether it occurs at the same qubit as an M node but after it). init_states : list[str] A list of num_qbits many strings. Each string will be inserted inside a ket and displayed in the Latex picture as the starting state of a qubit. The strings in the list are ordered in top qubit to bottom qubit order. reverse_bits : bool Set to True iff you want qubit order in Mosaic pic to be reversed in Latex pic. reverse_gates : bool Set to True iff you want gate order in Mosaic pic to be reversed in Latex pic. time_dir : str time_dir = '<' if reverse_gates else '>' """
[docs] def __init__(self, reverse_bits=True, reverse_gates=True): """ Constructor Parameters ---------- reverse_bits : bool reverse_gates : bool Returns ------- """ self.reverse_bits = reverse_bits self.reverse_gates = reverse_gates self.time_dir = '<' if self.reverse_gates else '>' self.last_bit_on_top = self.reverse_bits # these change with each mosaic_word_list considered self.init_states = None self.gb_char_arr = None self.gb_ch_is_measured = None
[docs] @staticmethod def qubiter_pic_file_to_mosaic_word_list(file_path, num_qbits, ZL): """ Reads Qubiter native pic file and returns a mosaic word list, which is a list of words all of which have the same number ( =num_qbits) of characters. Parameters ---------- file_path : str Path to Qubiter native pic file. num_qbits : int Number of qubits. The name of every native pic file generated by Qubiter states the value of num_qbits. ZL : bool Set to True iff file is using Zero bit Last convention. Opposite of ZL is ZF (Zero First). The name of every native pic file generated by Qubiter states either ZL or ZF. Returns ------- list[str] """ mosaic_word_list = [] inside_if_m_block = False pic_file_in = open(file_path) while not pic_file_in.closed: line = pic_file_in.readline() if not line: pic_file_in.close() break line = line.replace('-', ' ') split_line = line.split() ch_list = [] # print("..", split_line) if not inside_if_m_block: m_block_controls = Controls(num_qbits) for vtx in split_line: # print('vtx', vtx) if vtx in ['@', '<', '>', '|', ':', '%', '+', 'H', 'M', 'O', 'Ph', 'Rx', 'Ry', 'Rz', 'R', 'X', 'Y', 'Z']: ch_list.append(vtx[0]) elif vtx in ['OP', '@P']: ch_list.append('?') elif vtx in ['M1', 'M2']: assert False, "unsupported measurement type" elif vtx in ['LOOP', 'NEXT']: assert False, "loops not supported by Mosaic" elif vtx in ['NOTA', 'PRINT']: break elif vtx == 'IF_M(': inside_if_m_block = True # m_block_controls should be empty at this point assert not m_block_controls.bit_pos_to_kind trols = split_line[1:-1] # print('trols', trols) trol_bits = [int(x[:-1]) for x in trols] trol_kinds = \ [True if x[-1] == 'T' else False for x in trols] for bit, kind in zip(trol_bits, trol_kinds): m_block_controls.bit_pos_to_kind[bit] = kind m_block_controls.refresh_lists() break elif vtx == '}IF_M': inside_if_m_block = False break else: assert False, "unexpected circuit node: '" + vtx + "'" # print("split_line", split_line) # ch_list may be empty in cases where broke out of vtx loop if ch_list: # give controls of if_m block to gates inside block assert len(ch_list) == num_qbits, str(ch_list) if inside_if_m_block: for bit, kind in m_block_controls.bit_pos_to_kind.items(): if not ZL: ch_list[bit] = '@' if kind else 'O' else: ch_list[num_qbits - bit - 1] = '@' if kind else 'O' # replace : by | ch_list = [ch if ch != ':' else '|' for ch in ch_list] # replace | by + when justified gate_has_inter_qubit_wires = False for ch in ch_list: if ch in ['@', 'O', '<', '>', '+']: gate_has_inter_qubit_wires = True break if gate_has_inter_qubit_wires: non_vertical_pos = [k for k in range(num_qbits) if ch_list[k] != '|'] min_non_vert = min(non_vertical_pos) max_non_vert = max(non_vertical_pos) for k in range(min_non_vert+1, max_non_vert): if ch_list[k] == '|': ch_list[k] = '+' mosaic_word_list.append(''.join(ch_list)) return mosaic_word_list
[docs] def process_mosaic_word_list(self, mosaic_word_list, init_states): """ Internal function that uses info in the input mosaic_word_list to fill certain attributes of the class. Parameters ---------- mosaic_word_list : list[str] init_states : list[str] Returns ------- None """ word_list = cp.copy(mosaic_word_list) self.init_states = init_states num_qbits = len(word_list[0]) for word in word_list: assert len(word) == num_qbits def repeated_ch(ch): return ''.join([ch]*num_qbits) # extend end if not self.reverse_gates: # print(word_list, [repeated_ch('|')] ) word_list = word_list + [repeated_ch('|')] else: word_list = [repeated_ch('|')] + word_list if init_states: word_list = [repeated_ch('$')] + word_list num_gates = len(word_list) char_list = [] for word in word_list: char_list.extend(list(word)) self.gb_char_arr = np.array(char_list).reshape((num_gates, num_qbits)) self.gb_ch_is_measured = np.zeros( shape=(num_gates, num_qbits), dtype=bool) for gate in range(num_gates): for bit in range(num_qbits): if self.gb_char_arr[gate, bit] == 'M': for gate1 in range(gate+1, num_gates): self.gb_ch_is_measured[gate1, bit] = True # print('..', self.gb_ch_is_measured) for gate in range(num_gates): for bit in range(num_qbits): if self.gb_ch_is_measured[gate, bit]: assert self.gb_char_arr[gate, bit] in \ ['@', 'O', '|', '+'],\ "Only admissible ops on a measured qubit are @,O,|,+"
# print("self.gb_char_arr=\n", self.gb_char_arr)
[docs] @staticmethod def get_preface_latex(): """ Returns preface code of Latex document. Returns ------- str """ preface = "\\documentclass[12pt]{article}\n" preface += "\\usepackage{Qcircuit_v2, Qcircuit_extension}\n" preface += "\\begin{document}\n" preface += "\n\n" return preface
[docs] @staticmethod def get_ending_latex(): """ Returns ending code of Latex document. Returns ------- str """ return "\\end{document}"
[docs] def get_ckt_latex(self, mosaic_word_list, init_states=None): """ Returns Latex code for quantum circuit described by the input mosaic_word_list. Parameters ---------- mosaic_word_list : list[str] init_states : list[str] Returns ------- str """ self.process_mosaic_word_list(mosaic_word_list, init_states) latex_str = "\\begin{equation}\n\\begin{array}{c}\n" latex_str += "\\Qcircuit @C=2em @R=.4em {\n" num_qbits = len(mosaic_word_list[0]) num_gates = len(mosaic_word_list) + 2 for bit in range(num_qbits): _bit = bit if self.reverse_bits: _bit = num_qbits - bit - 1 if bit > 0: latex_str += '\\\\ % bit ' + str(bit) + '\n' else: latex_str += '% bit ' + str(bit) + '\n' for gate_num in range(num_gates): _gate_num = gate_num if self.reverse_gates: _gate_num = num_gates - _gate_num - 1 gate_char = self.gb_char_arr[_gate_num, _bit] is_m = self.gb_ch_is_measured[_gate_num, _bit] if gate_char == 'M': if self.reverse_gates: latex_str += '&\\Rmeter' else: latex_str += '&\\meter' elif gate_char == 'O': latex_str += '&\\Cogate' if is_m else '&\\ogate' elif gate_char == 'X': latex_str += '&\\timesgate' elif gate_char == '<': if self.reverse_bits: latex_str += '&\\darrowgate' else: latex_str += '&\\uarrowgate' elif gate_char == '>': if self.reverse_bits: latex_str += '&\\uarrowgate' else: latex_str += '&\\darrowgate' elif gate_char == '@': latex_str += '&\\Cdotgate' if is_m else '&\\dotgate' elif gate_char == '|': latex_str += '&\\cw' if is_m else '&\\qw' elif gate_char == '+': latex_str += '&\\cw' if is_m else '&\\qw' elif gate_char == '$': stick = '\\lstick' if not self.reverse_gates else '' latex_str += '&' + stick + '{\\ket{' +\ self.init_states[bit] + '}}' else: latex_str += '&\\gate{' + gate_char + '}' # add \qwx if self.reverse_bits: bit_range = reversed(range(num_qbits)) else: bit_range = range(num_qbits) gate_ch_list = [self.gb_char_arr[_gate_num, k] for k in bit_range] gate_has_inter_qubit_wires = False for ch in gate_ch_list: # any gate-time with at least one '?' in mosaic pic # will be drawn in latex with inter-qubit wiring if ch in ['@', 'O', '<', '>', '+', '?']: gate_has_inter_qubit_wires = True break if gate_has_inter_qubit_wires: non_vertical_pos = [k for k in range(num_qbits) if gate_ch_list[k] != '|'] min_non_vert = min(non_vertical_pos) max_non_vert = max(non_vertical_pos) for k in range(min_non_vert, max_non_vert): if bit-1 == k: latex_str += '\\qwx' latex_str += '\t\t% gate ' + str(gate_num) + '\n' latex_str += '}\n' latex_str += '\\end{array}\n\\end{equation}\n\n' return latex_str
if __name__ == "__main__": def main(): def write_latex_file(file_out1): latex_str = AsciiPic_to_Latex.get_preface_latex() for rev_bits in [False, True]: last_bit_on_top = rev_bits for rev_gates in [False, True]: # print('case', rev_bits, rev_gates) time_dir = '<' if rev_gates else '>' latex_str += "\nlast bit on top=" + str(last_bit_on_top) latex_str += ", time dir= $" + time_dir + '$\n\n' trans = AsciiPic_to_Latex(rev_bits, rev_gates) latex_str += trans.get_ckt_latex(mosaic_word_list, init_states) latex_str += AsciiPic_to_Latex.get_ending_latex() with open(file_out1, "w") as fi: fi.write(latex_str) # test 1 --------------- print('test 1') num_qbits = 7 file_out = "latex_test1.tex" mosaic_word_list = [ "|X++@||", "|Y+O@O|", "HH|||||", "|<++>||", "M|M|M||", "@X|||||", "@++++X|", "@++O+X|", "|A|||B?"] init_states = [str(bit) for bit in range(num_qbits)] # init_states = None print(mosaic_word_list) write_latex_file(file_out) # test 2 --------------- print("test 2") num_qbits = 7 file_in = "latex_test_pic.txt" mosaic_word_list = \ AsciiPic_to_Latex.qubiter_pic_file_to_mosaic_word_list( file_in, num_qbits, ZL=True) print(mosaic_word_list) # test 3 --------------- print('test 3') num_qbits = 3 file_in = 'teleportation-with-ifs_3_ZLpic.txt' file_out = 'latex_test_telep.tex' mosaic_word_list = \ AsciiPic_to_Latex.qubiter_pic_file_to_mosaic_word_list( file_in, num_qbits, ZL=True) print(mosaic_word_list) init_states = ['0']*num_qbits write_latex_file(file_out) main()