Source code for qubiter.SEO_reader

from qubiter.Controls import *
from qubiter.SEO_pre_reader import *
from qubiter.PlaceholderManager import *
from qubiter.LoopyPlaceholderManager import *
import qubiter.utilities_gen as utg
import os

import sys
if 'autograd.numpy' not in sys.modules:
    import numpy as np


[docs]class SEO_reader(SEO_pre_reader): """ This class inherits from the class SEO_pre_reader. It's an abstract class because it has a bunch of use_ methods that must be overridden by a child class. This class reads each line of an English file, parses it, and sends the info obtained to a use_ method for further processing. One very important child of this class is SEO_simulator which uses each line of the English file to evolve by one further step a quantum state vector. See the docstring for the class SEO_writer for more info about English files. The main use of this "abstract" class is as an intermediate class that is a parent class to a child class. However, this class is not 100% abstract. An object of it can be created without getting an error message iff the write_log input parameter, which is False by default, is set to True instead. In that case, creating an object of the class will produce a log file about the English file that it reads. That log file will contain useful information about the English file, like its number of lines, its number of elementary ops, its number of CNOT operations (SIGX with one control), etc. Attributes ---------- english_in : _io.TextIOWrapper file object for input text file that stores English description of circuit line_count : int loop_to_cur_rep : dict[int, int] a dictionary mapping loop number TO current repetition mcase_trols if outside if_m block, else a control specifying the current if_m case measured_bits : list(int) list of bits that have been measured with type 2 measurement and haven't been reset to ``|0>`` or ``|1>`` num_cnots : int num_ops : int split_line : list[str] vars_manager : PlaceholderManager handles variables indicated by #int in the English file being read verbose : bool write_log : bool xfile_num : int You can ignore this if the English file has no loops. This number is -1 by default. If you change it to something non-negative, the class looks in the io_folder for a "Loop xfile" with a very specific name that mentions the xfile_num: file_prefix + '_' + str( num_qbits) + '_loop' + str(xfile_num) + ".py". If the class can't find that xfile , it will abort. Otherwise, it tries to exec() that xfile. A Loop xfile is a file that you write yourself from a template file called "Loop File" that is generated by the classes LoopFileGenerator and LoopyPlaceholder. The Loop File has the same name as the Loop xfile, except that the Loop xfile has that additional xfile_num before the ".py". Read docstrings and main() of classes LoopFileGenerator and LoopyPlaceholder for more info and examples illustrating how to use Loop Files and Loop xfiles. """
[docs] def __init__(self, file_prefix, num_qbits, vars_manager=None, verbose=False, write_log=False, xfile_num=-1): """ Constructor Parameters ---------- file_prefix : str num_qbits : int vars_manager : PlaceholderManager verbose : bool write_log : bool xfile_num : int Returns ------- """ SEO_pre_reader.__init__(self, file_prefix, num_qbits) self.split_line = None self.vars_manager = vars_manager if vars_manager is None: self.vars_manager = PlaceholderManager() self.verbose = verbose self.write_log = write_log if write_log: self.vars_manager.eval_all_vars = False self.xfile_num = xfile_num if xfile_num >= 0: assert not self.vars_manager.var_num_to_rads, "we don't "\ "allow a var_num_to_rads and a loop xfile simultaneously" assert not self.vars_manager.fun_name_to_fun, "we don't "\ "allow a fun_name_to_fun and a loop xfile simultaneously" self.fill_history_lists_by_executing_loop_xfile() self.measured_bits = [] self.mcase_trols = None self.english_in = open(utg.preface( file_prefix + '_' + str(num_qbits) + '_eng.txt'), 'rt') self.loop_to_cur_rep = {loop_num: 0 for loop_num in self.loop_to_nreps.keys()} self.num_ops = 0 self.num_cnots = 0 self.line_count = 0 while not self.english_in.closed: self.next_line() if write_log: self.do_log()
[docs] @staticmethod def xed_file_prefix(file_prefix): """ Xed file_prefix. Returns file_prefix + '_X1', assuming that '_X' + str(k) for some integer k is not already the ending of file_prefix. If it is, then the ending is changed to '_X' + str( k+1). Classes that use this are called "expanders" throughout Qubiter. The X stands for expanded. Parameters ---------- file_prefix : str Returns ------- str """ k = file_prefix.rfind('_X') if k == -1: out_file_prefix = file_prefix + '_X1' else: out_file_prefix = file_prefix[:k+2] + \ str(int(file_prefix[k+2:])+1) return out_file_prefix
[docs] def do_log(self): """ Write a log file and print info on console too. Returns ------- None """ log = open(utg.preface( self.file_prefix + '_' + str(self.num_qbits) + '_log.txt'), 'wt') s = '' s += "Number of lines in file = " + str(self.tot_num_lines) + '\n' s += "Number of Elem. Ops = " + str(self.num_ops) + '\n' s += "Number of CNOTS (SIGX with single control) = " + \ str(self.num_cnots) + '\n' s += "List of distinct variable numbers encountered " s += "(length=" + str(len(self.vars_manager.all_var_nums)) + ')=\n' s += str(self.vars_manager.all_var_nums) + "\n" s += "List of distinct function names encountered " s += "(length=" + str(len(self.vars_manager.all_fun_names)) + ')=\n' s += str(self.vars_manager.all_fun_names) + "\n" log.write(s) if self.verbose: print(s) log.close()
[docs] def get_log_file_path(self, rel=False): """ Returns path (relative if rel is True, absolute if rel is False) of log file. Parameters ---------- rel : bool Returns ------- str """ rel_path = self.file_prefix + '_' + str(self.num_qbits) + '_log.txt' return rel_path if rel else utg.preface(rel_path)
[docs] def print_log_file(self): """ Prints log file. Returns ------- None """ path = self.get_log_file_path(rel=True) with open(utg.preface(path)) as f: print(f.read())
[docs] def degs_str_to_rads(self, degs_str): """ Wrapper for function of same name in PlaceholderManager. Parameters ---------- degs_str : str Returns ------- float | str """ return self.vars_manager.degs_str_to_rads(degs_str, self.line_count)
[docs] def fill_history_lists_by_executing_loop_xfile(self): """ This method is called by the constructor of this class, iff the user enters a valid (non-negative) xfile number. Before using this method, user is expected to have generated a Loop File from an English file via classes LoopFileGenerator and LoopyPlaceholderManager, and created a Loop xfile by editing that Loop File. This class executes the Loop xfile to fill its history dictionaries (the ones that end in _hist). Returns ------- None """ assert self.xfile_num >= 0, \ "user entered xfile number must be a non-negative int" all_var_nums = [] all_fun_names = [] var_num_to_hist = defaultdict(list) fun_name_to_hist = defaultdict(list) xfile_name = self.file_prefix + '_' + str(self.num_qbits) +\ '_loop' + str(self.xfile_num) + '.py' try: loopx_in = open(utg.preface(xfile_name), 'rt') except IOError: print("Expected to find but didn't find a file named\n" + xfile_name) exit() # var_dict = \ # { # 'all_var_nums': all_var_nums, # 'all_fun_names': all_fun_names, # 'var_num_to_hist': var_num_to_hist, # 'fun_name_to_hist': fun_name_to_hist # } exec(loopx_in.read()) self.vars_manager.all_var_nums = all_var_nums self.vars_manager.all_fun_names = all_fun_names self.vars_manager.var_num_to_hist = var_num_to_hist self.vars_manager.fun_name_to_hist = fun_name_to_hist # print('--------before resolving') # print(all_var_nums, all_fun_names) # print(var_num_to_hist) # print(fun_name_to_hist) self.vars_manager.resolve_all_histories()
# print('--------after resolving') # print(var_num_to_hist) # print(fun_name_to_hist)
[docs] def next_line(self): """ Analyze the inputted line. Send info to use_ methods labelled by first four letters of line) for further use. Parameters ---------- Returns ------- None """ line = self.english_in.readline() if not line or not line.strip(): self.english_in.close() return self.split_line = line.split() line_name = self.split_line[0] self.num_ops += 1 self.line_count += 1 if line_name == "DIAG": # example: # DIAG IF 2:1 1:0 0T BY 30.0 10.5 11.0 83.1 BY_pos = -1 for k in range(len(self.split_line)): if self.split_line[k] == 'BY': BY_pos = k break trol_tokens = self.split_line[2: BY_pos] ang_tokens = self.split_line[BY_pos + 1: len(self.split_line)] trols = self.read_multi_controls(trol_tokens) rad_angles = [self.degs_str_to_rads(ang_tokens[k]) for k in range(len(ang_tokens))] self.use_DIAG(trols, rad_angles) elif line_name == "HAD2": # example: # HAD2 AT 1 IF 3F 2T tar_bit_pos = int(self.split_line[2]) controls = self.read_TF_controls(self.split_line[4:]) self.use_HAD2(tar_bit_pos, controls) elif line_name == "IF_M(": # don't count IF_M(<controls>){ as operation self.num_ops -= 1 # example: # IF_M( 3F 2T ){ self.mcase_trols = self.read_TF_controls( self.split_line[1:-1]) for bit in self.mcase_trols.bit_pos: assert bit in self.measured_bits, \ "IF_M() argument mentions a qubit that" \ " hasn't been measured yet" self.use_IF_M_beg(self.mcase_trols) elif line_name == "}IF_M": # don't count }IF_M as operation self.num_ops -= 1 self.mcase_trols = None self.use_IF_M_end() elif line_name == "LOOP": # don't count LOOP as operation self.num_ops -= 1 # example: # LOOP 5 NREPS= 2 loop_num = int(self.split_line[1]) nreps = int(self.split_line[3]) self.use_LOOP(loop_num, nreps) elif line_name == "MEAS": # example: # MEAS 0 AT 5 # MEAS 1 AT 5 # MEAS 2 AT 5 kind = int(self.split_line[1]) tar_bit_pos = int(self.split_line[3]) if kind == 2: # don't measure same bit twice assert tar_bit_pos not in self.measured_bits,\ "attempting to measure (kind=2) same qubit twice" self.measured_bits.append(tar_bit_pos) self.use_MEAS(tar_bit_pos, kind) elif line_name == "MP_Y": # example: # MP_Y AT 3 IF 2:1 1:0 0T BY 30.0 10.5 11.0 83.1 tar_bit_pos = int(self.split_line[2]) BY_pos = -1 for k in range(len(self.split_line)): if self.split_line[k] == 'BY': BY_pos = k break trol_tokens = self.split_line[4: BY_pos] ang_tokens = self.split_line[BY_pos + 1: len(self.split_line)] trols = self.read_multi_controls(trol_tokens) rad_angles = [self.degs_str_to_rads(ang_tokens[k]) for k in range(len(ang_tokens))] self.use_MP_Y(tar_bit_pos, trols, rad_angles) elif line_name == "NEXT": # don't count NEXT as operation self.num_ops -= 1 # example: # NEXT 5 loop_num = int(self.split_line[1]) self.use_NEXT(loop_num) elif line_name == 'NOTA': # don't count NOTA as operation self.num_ops -= 1 # example: # NOTA "I love you Mary." self.use_NOTA(line[4:].strip()) elif line_name == "PHAS": # example: # PHAS 42.7 AT 1 IF 3F 2T angle_rads = self.degs_str_to_rads(self.split_line[1]) tar_bit_pos = int(self.split_line[3]) controls = self.read_TF_controls(self.split_line[5:]) self.use_PHAS(angle_rads, tar_bit_pos, controls) elif line_name == "P0PH": self.read_P_phase_factor(0) elif line_name == "P1PH": self.read_P_phase_factor(1) elif line_name == "PRINT": # don't count PRINT as operation self.num_ops -= 1 # example: # PRINT V1 assert len(self.split_line) == 2, \ "PRINT line must contain style str" self.use_PRINT(self.split_line[1], self.line_count) elif line_name == "ROTX": self.read_ROT(1) elif line_name == "ROTY": self.read_ROT(2) elif line_name == "ROTZ": self.read_ROT(3) elif line_name == "ROTN": # example: # ROTN 42.7 30.2 78.5 AT 1 IF 3F 2T angle_x_rads = self.degs_str_to_rads(self.split_line[1]) angle_y_rads = self.degs_str_to_rads(self.split_line[2]) angle_z_rads = self.degs_str_to_rads(self.split_line[3]) tar_bit_pos = int(self.split_line[5]) controls = self.read_TF_controls(self.split_line[7:]) self.use_ROTN(angle_x_rads, angle_y_rads, angle_z_rads, tar_bit_pos, controls) elif line_name == "SIGX": self.read_SIG(1) elif line_name == "SIGY": self.read_SIG(2) elif line_name == "SIGZ": self.read_SIG(3) elif line_name == "SWAP": # example: # SWAP 1 0 IF 3F 2T bit1 = int(self.split_line[1]) bit2 = int(self.split_line[2]) controls = self.read_TF_controls(self.split_line[4:]) self.use_SWAP(bit1, bit2, controls) elif line_name == "SWAY": # example: # SWAY 0 1 BY 25.1 42.7 IF 3F 2T bit1 = int(self.split_line[1]) bit2 = int(self.split_line[2]) rads_list = [self.degs_str_to_rads(self.split_line[k]) for k in range(4, 6)] controls = self.read_TF_controls(self.split_line[7:]) self.use_SWAY(bit1, bit2, controls, rads_list) elif line_name == "U_2_": # example: # U_2_ 25.1 42.7 30.2 78.5 AT 1 IF 3F 2T rads0 = self.degs_str_to_rads(self.split_line[1]) rads1 = self.degs_str_to_rads(self.split_line[2]) rads2 = self.degs_str_to_rads(self.split_line[3]) rads3 = self.degs_str_to_rads(self.split_line[4]) tar_bit_pos = int(self.split_line[6]) controls = self.read_TF_controls(self.split_line[8:]) self.use_U_2_(rads0, rads1, rads2, rads3, tar_bit_pos, controls) else: assert False, \ "reading an unsupported line kind: " + line_name self.finalize_next_line()
[docs] def finalize_next_line(self): """ Useful for intercepting the end of each call to next_line(). Returns ------- """ if self.verbose: print('line_num, operation =', self.line_count, self.num_ops)
[docs] def read_multi_controls(self, tokens, allow_only_TF=False): """ Given a list of tokens of the form: * an int followed by either T or F, * int, colon, int, construct a control out of it. Parameters ---------- tokens : list[str] allow_only_TF : bool Returns ------- Controls """ # safe to use when no "IF" # when no "IF", will return controls with _numControls=0 controls = Controls(self.num_qbits) if tokens: for t in tokens: t_end = t[-1] if allow_only_TF: assert t_end in ['T', 'F'] if t_end == 'T': controls.set_control(int(t[:-1]), True) elif t_end == 'F': controls.set_control(int(t[:-1]), False) else: k1, k2 = t.split(':') controls.set_control(int(k1), int(k2)) controls.refresh_lists() return controls
[docs] def read_TF_controls(self, tokens): """ Same as read_multi_controls() but only allows T/F kind controls. Parameters ---------- tokens : list[str] Returns ------- Controls """ return self.read_multi_controls(tokens, allow_only_TF=True)
[docs] def read_P_phase_factor(self, projection_bit): """ Collect useful info from P0PH or P1PH split_line and forward it to use_ method. Parameters ---------- projection_bit : int Returns ------- None """ # example: # P0PH 42.7 AT 1 IF 3F 2T # P1PH 42.7 AT 1 IF 3F 2T angle_rads = self.degs_str_to_rads(self.split_line[1]) tar_bit_pos = int(self.split_line[3]) controls = self.read_TF_controls(self.split_line[5:]) assert projection_bit in [0, 1] self.use_P_PH(projection_bit, angle_rads, tar_bit_pos, controls)
[docs] def read_ROT(self, axis): """ Collect useful info from ROTX, ROTY, or ROTZ split_line and forward it to use_ method. Parameters ---------- axis : int Returns ------- None """ # example: # ROTX 42.7 AT 1 IF 3F 2T # ROTY 42.7 AT 1 IF 3F 2T # ROTZ 42.7 AT 1 IF 3F 2T angle_rads = self.degs_str_to_rads(self.split_line[1]) tar_bit_pos = int(self.split_line[3]) controls = self.read_TF_controls(self.split_line[5:]) self.use_ROTA(axis, angle_rads, tar_bit_pos, controls)
[docs] def read_SIG(self, axis): """ Collect useful info from SIGX, SIGY, or SIGZ split_line and forward it to use_ method. Parameters ---------- axis : int Returns ------- None """ # example: # SIGX AT 1 IF 3F 2T # SIGY AT 1 IF 3F 2T # SIGZ AT 1 IF 3F 2T tar_bit_pos = int(self.split_line[2]) controls = self.read_TF_controls(self.split_line[4:]) assert axis in [1, 2, 3] if axis == 1 and len(controls.bit_pos) == 1: self.num_cnots += 1 self.use_SIG(axis, tar_bit_pos, controls)
[docs] def use_DIAG(self, trols, rad_angles): """ Abstract use_ method that must be overridden by child class. Parameters ---------- trols : Controls rad_angles : list[float] Returns ------- None """ if self.write_log: return assert False, 'DIAG not used'
[docs] def use_HAD2(self, tar_bit_pos, controls): """ Abstract use_ method that must be overridden by child class. Parameters ---------- tar_bit_pos : int controls : Controls Returns ------- None """ if self.write_log: return assert False, 'HAD2 not used'
[docs] def use_IF_M_beg(self, controls): """ Abstract use_ method that must be overridden by child class. Parameters ---------- controls : Controls Returns ------- None """ if self.write_log: return assert False, 'IF_M(){ not used'
[docs] def use_IF_M_end(self): """ Abstract use_ method that must be overridden by child class. Parameters ---------- Returns ------- None """ if self.write_log: return assert False, '}IF_M not used'
[docs] def use_LOOP(self, loop_num, nreps): """ Don't override this unless you know what you are doing and have very good reasons. It has been carefully set up to deal properly with embedded loops. Parameters ---------- loop_num : int nreps : int Returns ------- None """ pass
[docs] def use_MEAS(self, tar_bit_pos, kind): """ Abstract use_ method that must be overridden by child class. Parameters ---------- kind : int tar_bit_pos : int Returns ------- None """ if self.write_log: return assert False, 'MEAS not used'
[docs] def use_MP_Y(self, tar_bit_pos, trols, rad_angles): """ Abstract use_ method that must be overridden by child class. Parameters ---------- tar_bit_pos : int trols : Controls rad_angles : list[float] Returns ------- None """ if self.write_log: return assert False, 'MP_Y not used'
[docs] def use_NEXT(self, loop_num): """ Don't override this unless you know what you are doing and have very good reasons. It has been carefully set up to deal properly with embedded loops. Parameters ---------- loop_num : int Returns ------- None """ cur_rep = self.loop_to_cur_rep[loop_num] if cur_rep < self.loop_to_nreps[loop_num]-1: self.english_in.seek(self.loop_to_start_offset[loop_num]) self.line_count = self.loop_to_start_line[loop_num] - 1 self.loop_to_cur_rep[loop_num] += 1 else: # cur_rep = self.loop_to_nreps[loop_num]-1 self.loop_to_cur_rep[loop_num] = 0
[docs] def use_NOTA(self, bla_str): """ Abstract use_ method that must be overridden by child class. Parameters ---------- bla_str : str Returns ------- None """ if self.write_log: return assert False, 'NOTA not used'
[docs] def use_PHAS(self, angle_rads, tar_bit_pos, controls): """ Abstract use_ method that must be overridden by child class. Parameters ---------- angle_rads : float tar_bit_pos : int controls : Controls Returns ------- None """ if self.write_log: return assert False, 'PHAS not used'
[docs] def use_P_PH(self, projection_bit, angle_rads, tar_bit_pos, controls): """ Abstract use_ method that must be overridden by child class. Parameters ---------- projection_bit : int angle_rads : float tar_bit_pos : int controls : Controls Returns ------- None """ if self.write_log: return assert False, 'P0PH or P1PH not used'
[docs] def use_PRINT(self, style, line_num): """ Abstract use_ method that must be overridden by child class. Parameters ---------- style : str line_num : int Returns ------- None """ if self.write_log: return assert False, 'PRINT not used'
[docs] def use_ROTA(self, axis, angle_rads, tar_bit_pos, controls): """ Abstract use_ method that must be overridden by child class. Parameters ---------- axis : int angle_rads : float tar_bit_pos : int controls : Controls Returns ------- None """ if self.write_log: return assert False, 'ROTX, ROTY or ROTZ not used'
[docs] def use_ROTN(self, angle_x_rads, angle_y_rads, angle_z_rads, tar_bit_pos, controls): """ Abstract use_ method that must be overridden by child class. Parameters ---------- angle_x_rads : float angle_y_rads : float angle_z_rads : float tar_bit_pos : int controls : Controls Returns ------- None """ if self.write_log: return assert False, 'ROTN not used'
[docs] def use_SIG(self, axis, tar_bit_pos, controls): """ Abstract use_ method that must be overridden by child class. Parameters ---------- axis : int tar_bit_pos : int controls : Controls Returns ------- None """ if self.write_log: return assert False, 'SIGX, SIGY or SIGZ not used'
[docs] def use_SWAP(self, bit1, bit2, controls): """ Abstract use_ method that must be overridden by child class. Parameters ---------- bit1 : int bit2 : int controls : Controls Returns ------- None """ if self.write_log: return assert False, 'SWAP not used'
[docs] def use_SWAY(self, bit1, bit2, controls, rads_list): """ Abstract use_ method that must be overridden by child class. Parameters ---------- bit1 : int bit2 : int controls : Controls rads_list: list[float | str] Returns ------- None """ if self.write_log: return assert False, 'SWAY not used'
[docs] def use_U_2_(self, rads0, rads1, rads2, rads3, tar_bit_pos, controls): """ Abstract use_ method that must be overridden by child class. Parameters ---------- rads0 : float rads1 : float rads2 : float rads3 : float tar_bit_pos : int controls : Controls Returns ------- None """ if self.write_log: return assert False, 'U_2_ not used'
if __name__ == "__main__": def main(): file_prefix = 'expansions_examples_X1' num_qbits = 3 SEO_reader(file_prefix, num_qbits, write_log=True) main()