Source code for qubiter.adv_applications.StairsDerivCkt_writer

from qubiter.SEO_writer import *
from qubiter.adv_applications.StairsCkt_writer import *


[docs]class StairsDerivCkt_writer(SEO_writer): """ This class is a subclass of `SEO_writer`. It writes several intermediary stairs derivative circuits that will be used in class `StairsDeriv_native` for calculating the gradients of a quantum cost function (mean hamiltonian). Suppose U = exp[i*(t_0 + t_1*sigx + t_2*sigy + t_3*sigz)], where sigx, sigy, sigz are the Pauli matrices and t_r for r in range(4) are 4 real parameters. To take the derivative wrt t_r of a given multi-controlled gate U in a stairs circuit, we need to evaluate several circuits (we call them dparts, which stands for derivative parts). Say, for instance, that ``GATE= @---O---+---U``. To calculate d/dt_r GATE(t_0, t_1, t_2, t_3), for r=0,1, 2, 3, we need to calculate a new circuit wherein the GATE in the parent circuit is replaced by:: sum_k c_k @---@---O---+---U_k (which is said to have `has_neg_polarity`=False) and:: sum_k c_k @---@---O---+---U_k Z---@---O | | (which is said to have `has_neg_polarity`=True) Also, some extra stuff (a coda) must be appended to the end of the parent stairs circuit. Note that an extra "ancilla" qbit has been added (as the new last qubit) to the parent stairs circuit being differentiated. So if the parent stairs circuit has a number `parent_num_qbits` of qubits, then the one written by this class has that many qubits plus one. The index r which is in range(4) is called the derivative direction ( `deriv_direc`) `gate_str_to_rads_list` is the same as for the parent stairs circuit. `deriv_gate_str` is a well formed gate_str that specifies which U is being differentiated The index k is given as a string called `dpart_name` ("dpart" stands for derivative part). The coefficients c_k can be obtained via the method get_coef_of_dpart() Each U_k is a U(2) matrix itself, and its 4 parameters are defined in terms of the parameters tlist=[t_0, t_1, t_2, t_3] of the U(tlist) being differentiated, via 4 functions of tlist. These functions can be obtained via the method get_fun_name_to_fun(). Attributes ---------- deriv_direc : int in range(4) deriv_gate_str : str dpart_name : str gate_str_to_rads_list : dict[str, list[float]] has_neg_polarity : bool """
[docs] def __init__(self, deriv_gate_str, has_neg_polarity, deriv_direc, dpart_name, gate_str_to_rads_list, file_prefix, emb, **kwargs): """ Constructor This constructor writes English and Picture files but it doesn't close those files after writing them. You must do that yourself using close_files(). Parameters ---------- deriv_gate_str : str has_neg_polarity : bool deriv_direc : int in range(4) dpart_name : str gate_str_to_rads_list : dict[int, list[float]] file_prefix : str emb : CktEmbedder kwargs : dict key-word arguments of SEO_writer Returns ------- """ SEO_writer.__init__(self, file_prefix, emb, **kwargs) self.deriv_gate_str = deriv_gate_str self.has_neg_polarity = has_neg_polarity self.deriv_direc = deriv_direc self.dpart_name = dpart_name self.gate_str_to_rads_list = gate_str_to_rads_list assert deriv_gate_str in gate_str_to_rads_list.keys() assert deriv_direc in range(4) if deriv_gate_str == 'prior': assert has_neg_polarity is None self.write()
[docs] def write(self): """ This method writes English and Picture files for a quantum circuit Der. Der is used in calculating the derivative of a parent stairs circuit Pa with respect to one of 4 parameters for each of the multi-controlled U's of Pa. Returns ------- """ num_qbits = self.emb.num_qbits_bef anc_bit_pos = num_qbits-1 for gate_str, rads_list in self.gate_str_to_rads_list.items(): u2_pos = self.get_u2_pos(gate_str) trols = StairsCkt_writer.get_controls_from_gate_str( num_qbits, gate_str) if gate_str == self.deriv_gate_str: # add control on ancilla qubit trols.bit_pos_to_kind[anc_bit_pos] = True trols.refresh_lists() deriv_rads_list = \ self.gate_str_to_rads_list[self.deriv_gate_str] if not isinstance(deriv_rads_list[0], str): t_list = self.gate_str_to_rads_list[self.deriv_gate_str] rads_list = StairsDerivCkt_writer.\ get_rads_list_of_dpart(t_list, self.deriv_direc, self.dpart_name) else: rads_list = ['rads' + deriv_rads_list[k][1:] + ''.join(deriv_rads_list) for k in range(4)] self.write_H(anc_bit_pos) else: rads_list = self.gate_str_to_rads_list[gate_str] self.write_controlled_one_qbit_gate(u2_pos, trols, OneQubitGate.u2, rads_list) if gate_str == self.deriv_gate_str and gate_str != 'prior' and \ self.has_neg_polarity: # remove ancilla qubit control that was added previously del trols.bit_pos_to_kind[anc_bit_pos] trols.refresh_lists() # add controlled sigz for neg X term self.write_controlled_one_qbit_gate(anc_bit_pos, trols, OneQubitGate.sigz)
[docs] def get_u2_pos(self, gate_str): """ Given a well formed gate_str (one of the keys of gate_str_to_rads_list), this method returns the bit position of the U(2) matrix. Class StairsCkt_writer has a method of same name but which returns a different value for the same gate_str input. This is due to the fact that the circuit generated by this class has an extra ancilla qubit compared with its parent circuit. Parameters ---------- gate_str : str Returns ------- int """ # last qubit position, num_qbits-1, is reserved for ancilla qubit # "prior" gate_str has U2 at bit pos num_qbits-2 num_qbits = self.emb.num_qbits_bef if gate_str == 'prior': return num_qbits - 2 else: return num_qbits - 2 - len(gate_str) // 2
[docs] @staticmethod def float_t_list(t_list, var_num_to_rads): """ Internal method that checks whether t_list is of form list[str] or list[float]. In first case, it uses var_num_to_rads to replace t_list by a list[float]. Parameters ---------- t_list : list[float|str] var_num_to_rads : list[int, float] Returns ------- list[float] """ if any([isinstance(t, str) for t in t_list]): assert var_num_to_rads is not None,\ 't_list has #int strings, items not all floats,' \ ' so must provide a var_num_to_rads' t_list = [var_num_to_rads[int(t[1:])] for t in t_list] return t_list
[docs] @staticmethod def get_rads_list_of_dpart(t_list, deriv_direc, dpart_name, var_num_to_rads=None): """ This method returns the rads list (list of 4 floats) for a given given: 1. the rads list `t_list` of the U that is being differentiated, 2. the dpart (derivative part), and 3. which of 4 directions deriv_direc the derivative is wrt. var_num_to_rads is used if self wrote the English file with #int string symbols for placeholder variables. var_num_to_rads is used to float those strings. Parameters ---------- t_list : list[float | str] deriv_direc : int int in range(4) dpart_name : str var_num_to_rads : dict[int, float] | None Returns ------- list[float] """ t_list = StairsDerivCkt_writer.\ float_t_list(t_list, var_num_to_rads) t_vec = t_list[1:] t = np.linalg.norm(np.array(t_vec)) if deriv_direc == 0: assert dpart_name == 'single' rads_array = np.array(t_list) rads_array[0] += np.pi/2 else: # deriv_direc in [1, 2, 3] rads_array = np.zeros((4,), dtype=float) rads_array[0] = t_list[0] if dpart_name == '1': if t > 1e-6: for k in range(1, 4): rads_array[k] += (np.pi/2 + t)*t_list[k]/t elif dpart_name == 's': rads_array[deriv_direc] += np.pi/2 elif dpart_name == '1s': if t > 1e-6: for k in range(1, 4): rads_array[k] += (np.pi/2)*t_list[k]/t else: assert False, 'unsupported deriv part name' return list(rads_array)
[docs] @staticmethod def get_coef_of_dpart(t_list, deriv_direc, dpart_name, var_num_to_rads=None): """ This method returns coefficient of dpart (derivative part), either p1, ps or -p1*ps var_num_to_rads is used if self wrote the English file with #int string symbols for placeholder variables. var_num_to_rads is used to float those strings. Parameters ---------- t_list : list[float | str] deriv_direc : int int in range(4) dpart_name : str var_num_to_rads : dict[int, float] | None Returns ------- float """ # t stands for theta not time t_list = StairsDerivCkt_writer.\ float_t_list(t_list, var_num_to_rads) # space components t_vec = t_list[1:] t = np.linalg.norm(np.array(t_vec)) if t < 1e-6: ps = 1. p1 = 0. else: ps = np.sin(t)/t p1 = t_list[deriv_direc]/t if deriv_direc == 0: assert dpart_name == 'single' coef = 1. else: # deriv_direc in [1, 2, 3] if dpart_name == '1': coef = p1 elif dpart_name == 's': coef = ps elif dpart_name == '1s': coef = -p1*ps else: assert False, 'unsupported deriv part name' return coef
[docs] @staticmethod def get_fun_name_to_fun(t_list, deriv_direc, dpart_name): """ This method returns a dictionary fun_name_to_fun mapping the function name to function, for all functions defined by this class. Parameters ---------- t_list : list[float | str] deriv_direc : int dpart_name : str Returns ------- dict[str, function] """ fun_name_to_fun = {} if isinstance(t_list[0], str): for k in range(4): fun_name = 'rads' + t_list[k][1:] fun_name_to_fun[fun_name] =\ (lambda t0, t1, t2, t3, k1=k, x2=deriv_direc, x3=dpart_name: StairsDerivCkt_writer. get_rads_list_of_dpart([t0, t1, t2, t3], x2, x3)[k1]) return fun_name_to_fun
if __name__ == "__main__": def main(): num_qbits = 4 parent_num_qbits = num_qbits - 1 # one bit for ancilla gate_str_to_rads_list = StairsCkt_writer.\ get_gate_str_to_rads_list( parent_num_qbits, '#int', rads_const=np.pi/2) file_prefix = 'stairs_deriv_writer_test' emb = CktEmbedder(num_qbits, num_qbits) deriv_gate_str = list(gate_str_to_rads_list.keys())[2] for deriv_direc, dpart_name, has_neg_polarity in \ [(0, 'single', None), (3, 's', True)]: wr = StairsDerivCkt_writer(deriv_gate_str, has_neg_polarity, deriv_direc, dpart_name, gate_str_to_rads_list, file_prefix, emb) wr.close_files() print("%%%%%%%%%%%%%%%%%%%%%%%%%%") wr.print_eng_file() wr.print_pic_file() main()