Source code for qubiter.PlaceholderManager

import sys
if 'autograd.numpy' not in sys.modules:
    import numpy as np
else:
    import autograd.numpy as np
# from SEO_writer import *
import qubiter.utilities_gen as utg
from collections import defaultdict


[docs]class PlaceholderManager: """ The word "placeholder" (as used here and in Tensorflow) refers to a variable whose evaluation is postponed until a later time. In this class, we consider gate angle variables-placeholders. Legal variable names are described in the docstring of the function is_legal_var_name() in this class. An object of this class is owned by SEO_reader and subclasses thereof. When a circuit is read, the methods owned by this class are called to try to replace the placeholders by values. This class owns a dictionary `var_num_to_rads` that is used to store values of all angle variables used in all placeholders. The class also owns a dictionary `fun_name_to_fun` that is used to store functions used in "functional" type placeholders. Together, these 2 dictionaries are used to find a float value ("resolve") each placeholder. Attributes ---------- all_fun_names : list[str] a list of all the distinct function names encountered in circuit all_var_nums : list[int] a list of all distinct variable numbers encountered in circuit eval_all_vars : bool will abort if this is True and a variable can't be evaluated fun_name_to_fun : dict[str, function] dictionary mapping a function name to a function fun_name_to_hist : dict[str, list[function|None]] dictionary mapping a function name to its history (a list of function or None). This is only used when using loops in the English file. See LoopFileGenerator and LoopyPlaceholder classes for more info about this variable. fun_name_to_use_count: dict[str, int] dictionary mapping a function name to its current use count (the number of times it has been used. Repeat appearances inside a loop or nested loops are considered as different uses.) no_vars : bool will abort if this is True and a variable is detected var_num_to_hist : dict[int, list[float|None]] dictionary mapping a variable number to its history (a list of float or None). This is only used when using loops in the English file. See LoopFileGenerator and LoopyPlaceholder classes for more info about this variable. var_num_to_rads : dict[int, float] dictionary mapping a variable number to its value in radians var_num_to_use_count : dict[int, int] dictionary mapping a variable number to its current use count (the number of times it has been used. Repeat appearances inside a loop or nested loops are considered as different uses.) """
[docs] def __init__(self, no_vars=False, eval_all_vars=True, var_num_to_rads=None, fun_name_to_fun=None): """ Constructor Parameters ---------- no_vars : bool eval_all_vars : bool var_num_to_rads : dict[int, float] a dict mapping variable numbers to a float for radians. Used by a SEO_reader or its children to evaluate hash prefixed angle variables in a placeholder. fun_name_to_fun : dict[str, function] a dictionary mapping each function name to a function that returns a float that stands for radians. Used by a SEO_reader or its children to evaluate function names in a functional placeholder. Returns ------- """ self.no_vars = no_vars self.eval_all_vars = eval_all_vars self.var_num_to_rads = var_num_to_rads self.fun_name_to_fun = fun_name_to_fun # will be filled by this class self.all_var_nums = [] self.all_fun_names = [] self.var_num_to_hist = defaultdict(list) self.fun_name_to_hist = defaultdict(list) self.var_num_to_use_count = {} self.fun_name_to_use_count = {}
[docs] @staticmethod def is_functional_var(var_name): """ Assumes var_name is a legal variable name (doesn't check). Returns True iff var_name is a functional variable name Parameters ---------- var_name : str Returns ------- bool """ assert isinstance(var_name, str) and len(var_name) >= 2 if var_name[0] == '-': if not var_name[1].islower(): return False else: if not var_name[0].islower(): return False return True
[docs] @staticmethod def get_leg_var_sign(var_name): """ Assumes var_name is a legal variable name (doesn't check). Returns sign of variable, either +1 or -1 Parameters ---------- var_name : str Returns ------- int """ return -1 if var_name[0] == '-' else 1
[docs] @staticmethod def get_leg_var_var_nums(var_name): """ Assumes var_name is a legal variable name (doesn't check). Returns a list of the variable numbers Parameters ---------- var_name : str Returns ------- list[int] """ is_fun_var = PlaceholderManager.is_functional_var(var_name) sign = PlaceholderManager.get_leg_var_sign(var_name) # this gives -1 if "*" not found star_ind = var_name.find("*") if star_ind < 0: star_ind = len(var_name) # this excludes sign char and scale factor if there is one tokens = var_name[(0 if sign == 1 else 1): star_ind].split("#") # if var_name starts with # or -#, first token will be # empty string so remove it if tokens[0] == '': tokens = tokens[1:] # create list of variable number strings # this excludes function name if there is one var_num_tokens = tokens if is_fun_var: var_num_tokens = tokens[1:] # print("--", var_num_tokens) return [int(t) for t in var_num_tokens]
[docs] @staticmethod def get_leg_var_fun_name(var_name): """ Assumes var_name is a legal variable name (doesn't check). Returns the function name if there is one or None otherwise. Parameters ---------- var_name : str Returns ------- str | None """ is_fun_var = PlaceholderManager.is_functional_var(var_name) if not is_fun_var: return None sign = PlaceholderManager.get_leg_var_sign(var_name) # this finds index of first "#" hash_ind = var_name.find("#") return var_name[0 if sign == 1 else 1: hash_ind]
[docs] @staticmethod def get_leg_var_scale_fac(var_name): """ Assumes var_name is a legal variable name (doesn't check). If it's a functional variable, it returns None. If it's not a functional variable, it returns the scale factor, the float after the *, if there is a * or a 1 if there is no * Parameters ---------- var_name : str Returns ------- float | None """ is_fun_var = PlaceholderManager.is_functional_var(var_name) if is_fun_var: return None # this finds index of first "*", returns -1 if there is none star_ind = var_name.find("*") if star_ind < 0: return 1 return float(var_name[star_ind+1:])
[docs] def degs_str_to_rads(self, degs_str, line_count): """ This method takes in a string degs_str which might represent either a str() of a float expressing degrees or a legal var name. If degs_str is not a legal_var_name, the method assumes (This assumption is okay because the file was written by SEO_writer so it is always well formed) it's a str() of a float and returns float( deg_str)*pi/180. If degs_str is a legal variable name, the method tries to resolve it into a rads_value using the 2 dictionaries: self.var_num_to_rads and self.fun_name_to_fun. If it finds a rads_value, the method returns rads_value. If no rads_value can be found, the method outputs the input string unchanged (unless eval_all_vars=True in which case the method aborts). Parameters ---------- degs_str : str line_count : int this is the line_count in the English file being read. This is not used by this function but is used by overrides of it like the one in LoopyPlaceholderManager Returns ------- float | str """ if not PlaceholderManager.is_legal_var_name(degs_str): try: y = float(degs_str)*np.pi/180 except: assert False, 'tried to convert ' + str(degs_str)\ + ' to a float' return y else: # is a legal variable name assert not self.no_vars, 'no circuit variables allowed' is_fun_var = PlaceholderManager.is_functional_var(degs_str) sign = PlaceholderManager.get_leg_var_sign(degs_str) scale_fac = PlaceholderManager.get_leg_var_scale_fac(degs_str) fun_name = PlaceholderManager.get_leg_var_fun_name(degs_str) token_var_nums = PlaceholderManager.get_leg_var_var_nums(degs_str) # store var numbers and update their use_count for var_num in token_var_nums: if var_num not in self.all_var_nums: self.all_var_nums.append(var_num) if var_num not in self.var_num_to_use_count: self.var_num_to_use_count[var_num] = 1 else: self.var_num_to_use_count[var_num] += 1 # store function name and update its use_count if fun_name: if fun_name not in self.all_fun_names: self.all_fun_names.append(fun_name) if fun_name not in self.fun_name_to_use_count.keys(): self.fun_name_to_use_count[fun_name] = 1 else: self.fun_name_to_use_count[fun_name] += 1 if self.var_num_to_rads is None: self.var_num_to_rads = {} if self.fun_name_to_fun is None: self.fun_name_to_fun = {} var_num_to_rads = self.var_num_to_rads fun_name_to_fun = self.fun_name_to_fun # if there's a var_num_to_hist, ignore input value of # self.var_num_to_rads. Refill it using the history if self.var_num_to_hist: for var_num in token_var_nums: var_num_to_rads[var_num] = \ self.var_num_to_hist[var_num][ self.var_num_to_use_count[var_num] - 1] # if there's a fun_name_to_hist, ignore input value of # self.fun_name_to_rads. Refill it using the history if self.fun_name_to_hist: if fun_name: fun_name_to_fun[fun_name] = \ self.fun_name_to_hist[fun_name][ self.fun_name_to_use_count[fun_name] - 1] if var_num_to_rads: if not is_fun_var: # if its not a functional placeholder, # there is only one fun var var_num = token_var_nums[0] key_exists = var_num in var_num_to_rads.keys() if key_exists: return sign*var_num_to_rads[var_num]*scale_fac else: # key doesn't exist if self.eval_all_vars: assert False, "eval_all_vars is True and " +\ 'var_num_to_rads has no value for ' + \ 'variable #' + str(var_num) else: return degs_str else: # is_fun_var if fun_name_to_fun: all_var_keys_exist = \ all([var_num in var_num_to_rads.keys() for var_num in token_var_nums]) fun_key_exists = \ fun_name in fun_name_to_fun.keys() if all_var_keys_exist and fun_key_exists: arg_float_list = [var_num_to_rads[var_num] for var_num in token_var_nums] fun = fun_name_to_fun[fun_name] return sign*fun(*arg_float_list) else: if self.eval_all_vars: assert False, "eval_all_vars is True and " +\ "can't resolve the function " +\ fun_name + \ " acting on variables " + \ str(token_var_nums) else: return degs_str else: # no fun_name_to_fun if self.eval_all_vars: assert False, 'eval_all_vars is True and ' \ 'there is no fun_name_to_fun dictionary' + \ " so can't evaluate " + degs_str else: return degs_str else: # no var_num_to_rads if self.eval_all_vars: assert False, 'eval_all_vars is True and ' \ 'there is no var_num_to_rads dictionary' + \ " so can't evaluate " + degs_str else: return degs_str
[docs] @staticmethod def have_resolved_history(hist): """ This function tries to resolve a history. It returns True iff it succeeds. A history of a variable is a list of all the values it will assume all the times it is used in the circuit (each repetition in a loop or nested loops is counted as a different use.) For a hash placeholder, a history is a list of floats or None's. For a functional placeholder, a history is a list of functions or None's. Resolving a history successfully means replacing all None's by a value that is not a None. This is accomplished by moving in order of ascending index on the list and replacing each None by its predecessor. The very first use cannot be a None. Parameters ---------- hist : list[float | function | None] Returns ------- bool """ for time, event in enumerate(hist): if event is None: if time == 0: return False else: hist[time] = hist[time-1] return True
[docs] def resolve_all_histories(self): """ This function tries to resolve the histories of all hash and functional placeholder variables. It aborts if it fails to resolve any of those histories. Returns ------- None """ for val_num in self.all_var_nums: assert PlaceholderManager.have_resolved_history( self.var_num_to_hist[val_num]) for fun_name in self.all_fun_names: assert PlaceholderManager.have_resolved_history( self.fun_name_to_hist[fun_name])
if __name__ == "__main__": from qubiter.SEO_writer import * from qubiter.SEO_reader import * from qubiter.EchoingSEO_reader import * from qubiter.SEO_simulator import * def main(): # We begin by writing a simple circuit with 4 qubits. As usual, # the following code will write an English and a Picture file in the # io_folder directory. Note that some rotation angles have been # entered into the write() Python functions as legal variable names # instead of floats. In the English file, you will see those legal # names where the numerical values of those angles would have been. num_qbits = 4 file_prefix = 'placeholder_test' emb = CktEmbedder(num_qbits, num_qbits) wr = SEO_writer(file_prefix, emb) wr.write_Rx(2, rads=np.pi/7) wr.write_Rx(1, rads='#2*.5') wr.write_Rx(1, rads='my_fun1#2') wr.write_Rn(3, rads_list=['#1', '-#1*3', '#3']) wr.write_Rx(1, rads='-my_fun2#2#1') wr.write_cnot(2, 3) wr.close_files() # Simply by creating an object of the class SEO_reader with the flag # `write_log` set equal to True, you can create a log file which # contains # (1) a list of distinct variable numbers # (2) a list of distinct function names # encountered in the English file SEO_reader(file_prefix, num_qbits, write_log=True) def my_fun1(x): return x*.5 def my_fun2(x, y): return x + y # partial substitution, this creates new files # with #1=30, #2=60, 'my_fun1'->my_fun1, # but #3 and 'my_fun2' still undecided vman = PlaceholderManager(eval_all_vars=False, var_num_to_rads={1: np.pi/6, 2: np.pi/3}, fun_name_to_fun={'my_fun1': my_fun1}) wr = SEO_writer(file_prefix + '_eval01', emb) EchoingSEO_reader(file_prefix, num_qbits, wr, vars_manager=vman) # this runs the simulator after substituting # #1=30, #2=60, #3=90, 'my_fun1'->my_fun1, 'my_fun2'->my_fun2 vman = PlaceholderManager( var_num_to_rads={1: np.pi/6, 2: np.pi/3, 3: np.pi/2}, fun_name_to_fun={'my_fun1': my_fun1, 'my_fun2': my_fun2} ) sim = SEO_simulator(file_prefix, num_qbits, verbose=True, vars_manager=vman) print("\n----------------------------------------") StateVec.describe_st_vec_dict(sim.cur_st_vec_dict) main()