def change_of_basis_matrix_to_quil(qc: QuantumComputer, qubits: Sequence[int], change_of_basis: np.ndarray) -> Program: """ Helper to return a native quil program for the given qc to implement the change_of_basis matrix. :param qc: Quantum Computer that will need to use the change of basis :param qubits: the qubits the program should act on :param change_of_basis: a unitary matrix acting on len(qubits) many qubits :return: a native quil program that implements change_of_basis on the qubits of qc. """ prog = Program() # ensure the program is compiled onto the proper qubits prog += Pragma('INITIAL_REWIRING', ['"NAIVE"']) g_definition = DefGate("COB", change_of_basis) # get the gate constructor COB = g_definition.get_constructor() # add definition to program prog += g_definition # add gate to program prog += COB(*qubits) # compile to native quil nquil = qc.compiler.quil_to_native_quil(prog) # strip the program to only what we need, i.e. the gates themselves. only_gates = Program([inst for inst in nquil if isinstance(inst, Gate)]) return only_gates
def __produce_u_f_gate(self): ''' Solve a linear system of equations using the mapping between the input and the output to get the U_f matrix. Produce the gate to be used in the circuit using the matrix. ''' bit_strings = self.__generate_bit_strings(self.n + 1) xs = list() bs = list() for bit_string in bit_strings: xs.append(self.__get_tensor(bit_string)) modified_bit_string = self.__modify_bit_string(bit_string) bs.append(self.__get_tensor(modified_bit_string)) X = np.hstack(tuple(xs)) B = np.hstack(tuple(bs)) A = np.linalg.solve(np.linalg.inv(B), np.linalg.inv(X)) self.__u_f = np.array(A) print(self.__u_f) self.__u_f_definition = DefGate("U_f", self.__u_f) self.__U_f = self.__u_f_definition.get_constructor()
def qubit_unitary(par, *wires): r"""Define a pyQuil custom unitary quantum operation. Args: par (array): a :math:`2^N\times 2^N` unitary matrix representing a custom quantum operation. wires (list): list of wires to prepare the basis state on Returns: list: list of PauliX matrix operators acting on each wire """ if par.shape[0] != par.shape[1]: raise ValueError("Qubit unitary must be a square matrix.") if not np.allclose(par @ par.conj().T, np.identity(par.shape[0])): raise ValueError("Qubit unitary matrix must be unitary.") if par.shape != tuple([2**len(wires)] * 2): raise ValueError( "Qubit unitary matrix must be 2^Nx2^N, where N is the number of wires." ) # Get the Quil definition for the new gate gate_definition = DefGate("U_{}".format(str(uuid.uuid4())[:8]), par) # Get the gate constructor gate_constructor = gate_definition.get_constructor() return [gate_definition, gate_constructor(*wires)]
def _apply_zf(self, qubits): """ Define Z_f gate (if not defined) and applies it to qubits. Parameters ---------- qubits : [int] Qubits to apply Z_f to. Returns ---------- Z_f : Gate Z_f gate applied to qubits """ if self.zf_definition is None: # Initializes Z_f as a 2^n by 2^n matrix of zeros Z_f = np.eye(2**self.n, dtype=int) # Apply definition of Z_f = (-1)^{f(x)} to construct matrix for x in range(2**self.n): Z_f[x][x] = (-1)**self.f(x) # Multiply by -1 to account for leading minus in G Z_f *= -1 self.zf_definition = DefGate("Z_f", Z_f) self.p += self.zf_definition Z_f = self.zf_definition.get_constructor() return Z_f(*qubits)
def _apply_uf(self, qubits): """ Define U_f gate (if not defined) that encodes oracle function f and applies it to qubits. Parameters ------- qubits : [int] Qubits to apply U_f to. Returns ------- U_f : Gate Z_f gate applied to qubits. """ if self.uf_definition is None: # Initializes U_f as a 2^(n+1) by 2^(n+1) matrix of zeros U_f = np.zeros((2 ** (self.n + 1),) * 2, dtype=int) # Apply definition of U_f = |x>|b + f(x)> to construct matrix for x in range(2 ** self.n): for b in [0, 1]: row = (x << 1) ^ b col = (x << 1) ^ (self.f(x) ^ b) U_f[row][col] = 1 self.uf_definition = DefGate("U_f", U_f) self.p += self.uf_definition U_f = self.uf_definition.get_constructor() return U_f(*qubits)
def _apply_z0(self, qubits): """ Defines Z_0 gate (if not defined) and applies it to qubits. Parameters ---------- qubits : [int] Qubits to apply Z_0 to. Returns ---------- Z_0 : Gate Z_0 gate applied to qubits """ if self.z0_definition is None: # Create Z_0 as identity, except with -1 in top-left corner Z_0 = np.eye(2**self.n) Z_0[0][0] = -1 self.z0_definition = DefGate("Z_0", Z_0) self.p += self.z0_definition Z_0 = self.z0_definition.get_constructor() return Z_0(*qubits)
def orig_encode(qubit): qubit_a = 4 qubit_b = 3 qubit_c = 1 qubit_d = 0 # qubit_a = QubitPlaceholder() # qubit_b = QubitPlaceholder() # qubit_c = QubitPlaceholder() # qubit_d = QubitPlaceholder() pi_rot = np.array([[-1.0, 0], [0, -1.0]]) pi_rot_def = DefGate("PI-ROT", pi_rot) PI_ROT = pi_rot_def.get_constructor() code_register = [qubit_a, qubit_b, qubit, qubit_c, qubit_d] pq = Program(H(qubit_a), H(qubit_b), H(qubit_c)) pq += pi_rot_def pq += PI_ROT(qubit_d).controlled(qubit_b).controlled(qubit).controlled( qubit_c) pq += [X(qubit_b), X(qubit_c)] pq += PI_ROT(qubit_d).controlled(qubit_b).controlled(qubit).controlled( qubit_c) pq += [X(qubit_b), X(qubit_c)] pq += CNOT(qubit, qubit_d) pq += CNOT(qubit_a, qubit) pq += CNOT(qubit_a, qubit_d) pq += CNOT(qubit_c, qubit) pq += CNOT(qubit_b, qubit_d) pq += PI_ROT(qubit).controlled(qubit_c).controlled(qubit_d) return pq, code_register
def _apply_uf(self, qubits): """ Creates a U_f gate that encodes oracle function f and applies it to qubits. Parameters ---------- qubits : [int] Qubits to apply U_f to. Returns ---------- [uf_definition, U_f] : [DefGate, Callable] Quil definition for U_f and the gate. """ # Initializes U_f as a 2^(2n) by 2^(2n) matrix of zeros U_f = np.zeros((2**(self.n * 2), ) * 2, dtype=int) # Apply definition of U_f = |x>|b + f(x)> to construct matrix for x in range(2**self.n): # Number of helper bits is equal to number of qubits for b in range(2**self.n): row = (x << self.n) ^ b col = (x << self.n) ^ (self.f(x) ^ b) U_f[row][col] = 1 uf_definition = DefGate("U_f", U_f) gate = uf_definition.get_constructor() return [uf_definition, gate(*qubits)]
def get_circuit(self, f, n_qubits): # construct oracle and define its gate constructor oracle = self.getOracle(f, n_qubits) oracle_definition = DefGate("ORACLE", oracle) ORACLE = oracle_definition.get_constructor() # make circuit p = Program() # apply the first hadamard to all qubits for i in range(n_qubits): p += H(i) # NOT ancilla qubit then apply hadamard p += X(n_qubits) p += H(n_qubits) # apply oracle to all qubits p += oracle_definition p += ORACLE(*range(n_qubits+1)) # apply hadamard to computational qubits for i in range(n_qubits): p += H(i) return p
def __produce_z_0_gate(self): ''' Produce matrix and gate for Z_0 ''' z_0 = np.identity(2**n) z_0[0][0] = -z_0[0][0] self.__z_0_definition = DefGate("Z_0", z_0) self.__Z_0 = self.__z_0_definition.get_constructor()
def __produce_negative_gate(self): ''' Produce matrix and gate for changing the coefficient of the set of qubits. ''' negative = -np.identity(2**n) self.__negative_definition = DefGate("NEGATIVE", negative) self.__NEGATIVE = self.__negative_definition.get_constructor()
def qc_program(n, m, reload, verbose): # Simon's algorithm uses (n - 1) * 4m iterations t = (n - 1) * (4 * m) SAVEDIR = 'uf/simon/' SPATH = 's_dict.npy' U_f_def = DefGate('U_f', getUf(n, reload)) U_f = U_f_def.get_constructor() if os.path.exists(SAVEDIR + SPATH): s = np.load(SAVEDIR + SPATH, allow_pickle=True).item()[n] else: s = None qc = get_qc(f'{str(2*n)}q-qvm') qc.compiler.client.timeout = 1000000 p = Program() p += U_f_def p += (H(i) for i in range(n)) p += U_f(*(tuple(range(2 * n)))) p += (H(i) for i in range(n)) result = qc.run_and_measure(p, trials=t) if verbose: print('====================================') print('Measured Qubit State Across Trials:') for i in range(n): print(f' {result[i]}') print('====================================\n') if verbose: print('====================================') print('Measured y values:') for i in range(4 * m): if verbose: print(f' Trial {i+1}:') ys = [] for j in range((n - 1) * i, (n - 1) * (i + 1)): measured = [result[q][j] for q in range(n)] y = "".join(str(b) for b in measured) ys.append(measured) if verbose: print(f' y_{j - (n-1)*i} = {y}') if check_lin_indep(ys): if verbose: print( f'Found linearly independent ys!\nChecking if they solve to s correctly...' ) print('====================================\n') return check_valid(ys, s) if verbose: print('====================================\n') return None
def Uf(uf_matrix): """ Returns the U_f gate for a given function f. :param f: matrix repr :return: the gate representation of f """ uf_definition = DefGate("UF-GATE", uf_matrix) UF_GATE = uf_definition.get_constructor() return uf_definition, UF_GATE
def simulate_controlled_y(): y = np.array([[0, -1j], [1j, 0]]) controlled_y_definition = DefGate("CONTROLLED-Y", controlled(y)) CONTROLLED_Y = controlled_y_definition.get_constructor() p = Program(controlled_y_definition) p += NOT(0) p += CONTROLLED_Y(0, 1) print(WavefunctionSimulator().wavefunction(p))
def makeControlGate(mat2D, gateName): u00 = mat2D[0][0] u01 = mat2D[0][1] u10 = mat2D[1][0] u11 = mat2D[1][1] cgatemat = array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, u00, u01], [0, 0, u10, u11]]) gatedef = DefGate(gateName, cgatemat) return gatedef, gatedef.get_constructor()
def __produce_z_f_gate(self): z_f = np.identity(2**n) bit_strings = self.__generate_bit_strings(self.n) for bit_string in bit_strings: output = f(bit_string) if output == 1: i = bit_strings.index(bit_string) #i = np.random.randint(2**n) z_f[i][i] = -z_f[i][i] self.__z_f_definition = DefGate("Z_f", z_f) self.__Z_f = self.__z_f_definition.get_constructor()
def _naive_program_generator(qc: QuantumComputer, qubits: Sequence[int], permutations: np.ndarray, gates: np.ndarray) -> Program: """ Naively generates a native quil program to implement the circuit which is comprised of the given permutations and gates. :param qc: the quantum resource that will implement the PyQuil program for each model circuit :param qubits: the qubits available for the implementation of the circuit. This naive implementation simply takes the first depth-many available qubits. :param permutations: array of depth-many arrays of size n_qubits indicating a qubit permutation :param gates: a depth by depth//2 array of matrices representing the 2q gates at each layer. The first row of matrices is the earliest-time layer of 2q gates applied. :return: a PyQuil program in native_quil instructions that implements the circuit represented by the input permutations and gates. Note that the qubits are measured in the proper order such that the results may be directly compared to the simulated heavy hitters from collect_heavy_outputs. """ num_measure_qubits = len(permutations[0]) # at present, naively select the minimum number of qubits to run on qubits = qubits[:num_measure_qubits] # create a simple program that uses the compiler to directly generate 2q gates from the matrices prog = Program() for layer_idx, (perm, layer) in enumerate(zip(permutations, gates)): for gate_idx, gate in enumerate(layer): # get the Quil definition for the new gate g_definition = DefGate( "LYR" + str(layer_idx) + "_RAND" + str(gate_idx), gate) # get the gate constructor G = g_definition.get_constructor() # add definition to program prog += g_definition # add gate to program, acting on properly permuted qubits prog += G(int(qubits[perm[gate_idx]]), int(qubits[perm[gate_idx + 1]])) ro = prog.declare("ro", "BIT", len(qubits)) for idx, qubit in enumerate(qubits): prog.measure(qubit, ro[idx]) native_quil = qc.compiler.quil_to_native_quil(prog) if not set(native_quil.get_qubits()).issubset(set(qubits)): raise ValueError( "naive_program_generator could not generate program using only the " "qubits supplied. Please provide your own program_generator if you wish " "to use only the qubits specified.") return native_quil
def __produce_z_f_gate(self): ''' Produce matrix and gate for Z_f using the mapping between the input and output. ''' z_f = np.identity(2**n) bit_strings = self.__generate_bit_strings(self.n) for bit_string in bit_strings: output = f(bit_string) if output == 1: i = bit_strings.index(bit_string) #i = np.random.randint(2**n) z_f[i][i] = -z_f[i][i] self.__z_f_definition = DefGate("Z_f", z_f) self.__Z_f = self.__z_f_definition.get_constructor()
def create_Np1_bit_CZ(N: int): my_mat = np.identity(2 ** (N + 1)) # middle = ((2 ** N) // 2) - 1 last = 2 ** (N + 1) - 1 # my_mat[middle, middle] = 0 #Middle row, middle column 11..0 # my_mat[middle, last] = 1 # Bottom row, middle column 11..0 -> 11..1 my_mat[last, last] = 0 my_mat[last - 1, last - 1] = 0 my_mat[last - 1, last] = 1 my_mat[last, last - 1] = 1 Np1_bit_CZ = DefGate("Nbit_CZ", my_mat) return Np1_bit_CZ, Np1_bit_CZ.get_constructor()
def qubit_unitary(par, *wires): r"""Define a pyQuil custom unitary quantum operation. Args: par (array): a :math:`2^N\times 2^N` unitary matrix representing a custom quantum operation. wires (list): list of wires to prepare the basis state on Returns: list: list of PauliX matrix operators acting on each wire """ # Get the Quil definition for the new gate gate_definition = DefGate("U_{}".format(str(uuid.uuid4())[:8]), par) # Get the gate constructor gate_constructor = gate_definition.get_constructor() return [gate_definition, gate_constructor(*wires)]
def makeTheCircuit(self, bitmap): #Make U_f from what we've been given simCircuit = Program() #Apply Hadamard's to the computational (e.g. first n) qubits for i in range(self.n_compQubs): simCircuit+=H(i) #Apply U_f to the circuit (all qubits) oracle = self.makeU_f(bitmap) oracle_definition = DefGate("ORACLE", oracle) ORACLE = oracle_definition.get_constructor() simCircuit += oracle_definition simCircuit += ORACLE(*reversed(range(self.n_compQubs*2))) #Apply a second Hadamard to the first n qubits (again) for i in range(self.n_compQubs): simCircuit+=H(i) return simCircuit
def define_extra_gates(self, qubit_program): ''' Defines quarter & eighth turn gates for the qubit program ''' # Gates to be used in calculate method global POS_SQRT_X, NEG_SQRT_X global POS_SQRT_Y, NEG_SQRT_Y global POS_SQRT_Z, NEG_SQRT_Z global POS_FTRT_X, NEG_FTRT_X global POS_FTRT_Y, NEG_FTRT_Y global POS_FTRT_Z, NEG_FTRT_Z # Definition of the unitary matrices for x, y & z pauli gates based on powers g = lambda p: cmath.exp(-1j*p*np.pi/-2) c = lambda p: math.cos(p*np.pi/2) s = lambda p: math.sin(p*np.pi/2) x_pow_gate = lambda p: np.array([[g(p)*c(p), -1j*g(p)*s(p)], [-1j*g(p)*s(p), g(p)*c(p)]]) y_pow_gate = lambda p: np.array([[g(p)*c(p), -g(p)*s(p)], [g(p)*s(p), g(p)*c(p)]]) z_pow_gate = lambda p: np.array([[1, 0],[0, g(2*p)]]) # Definition of the different dimensions and powers, all combinations dims = ['X', 'Y', 'Z'] powers = {"POS-SQRT": 0.5, "NEG-SQRT": -0.5, "POS-FTRT": 0.25, "NEG-FTRT": -0.25} constructors = {} for dim in dims: for power in powers: gate_name = power + "-" + dim # Get the unitary matrix for that dimension to the specific power if dim in 'X': matrix = x_pow_gate(powers[power]) elif dim in 'Y': matrix = y_pow_gate(powers[power]) else: matrix = z_pow_gate(powers[power]) # Get the Quil definition for the new gate gate_def = DefGate(gate_name, matrix) # Get the gate constructor constructors[gate_name] = gate_def.get_constructor() # Then we can use the new gate qubit_program += gate_def # Assign gate constructurs POS_SQRT_X, NEG_SQRT_X = constructors["POS-SQRT-X"], constructors["NEG-SQRT-X"] POS_SQRT_Y, NEG_SQRT_Y = constructors["POS-SQRT-Y"], constructors["NEG-SQRT-Y"] POS_SQRT_Z, NEG_SQRT_Z = constructors["POS-SQRT-Z"], constructors["NEG-SQRT-Z"] POS_FTRT_X, NEG_FTRT_X = constructors["POS-FTRT-X"], constructors["NEG-FTRT-X"] POS_FTRT_Y, NEG_FTRT_Y = constructors["POS-FTRT-Y"], constructors["NEG-FTRT-Y"] POS_FTRT_Z, NEG_FTRT_Z = constructors["POS-FTRT-Z"], constructors["NEG-FTRT-Z"] return qubit_program
def __produce_u_f_gate(self): bit_strings = self.__generate_bit_strings(self.n+1) xs = list() bs = list() for bit_string in bit_strings: xs.append(self.__get_tensor(bit_string)) modified_bit_string = self.__modify_bit_string(bit_string) bs.append(self.__get_tensor(modified_bit_string)) X = np.hstack(tuple(xs)) B = np.hstack(tuple(bs)) A = np.linalg.solve(np.linalg.inv(B), np.linalg.inv(X)) self.__u_f = np.array(A) print(self.__u_f) self.__u_f_definition = DefGate("U_f", self.__u_f) self.__U_f = self.__u_f_definition.get_constructor()
def prep_circuit_program(self, params): prog = Program() for i in range(len(gates)): q1 = gates[i][0] q2 = gates[i][1] index = i*16 unitary = self.two_qubit_unitary(params[index:index+16]) gate_definition = DefGate("GATE"+str(q1)+"-"+str(q2), unitary) gate = gate_definition.get_constructor() prog.inst(gate_definition, gate(q1, q2)) # index = 20*len(gates) # unitary = self.single_qubit_unitary(params[index:index+6], self.n - 1) # gate_definition = DefGate("GATE15", unitary) # gate = gate_definition.get_constructor() # prog.inst(gate_definition, gate(self.n-1)) return prog
def simon_program(U_f, n): p = Program() # apply Hadamard to n qubits for i in range(n): p += H(i) # define the U_f gate based on the unitary matrix returned by get_U_f U_f_def = DefGate("U_f", U_f) U_f_GATE = U_f_def.get_constructor() p += U_f_def p += U_f_GATE(*range(2 * n)) # apply Hadamard to all input qubits for k in range(n): p += H(k) # print(p) return p
def _compile_unitary(self, unitary): """Compiles the unitary to a program. Args: unitary : numpy.ndarray Unitary matrix to compile. Returns: A pyquil.Program with gates that build up the unitary. """ # Define the gate from the unitary gate = DefGate("U", unitary) # Get the constructor U_gate = gate.get_constructor() # Get the qubit indices qubit_indices = self.device_qubits # NOTE: Brian changed to work with chip indices not range(self.num_qubits) # Return the program return Program(gate, U_gate(*tuple(qubit_indices)))
def dj_program(U_f, n): p = Program() # invert the helper qubit to make it 1 p += X(n) # apply Hadamard to all input qubits and helper qubit for i in range(n + 1): p += H(i) # define the U_f gate based on the unitary matrix returned by get_U_f U_f_def = DefGate("U_f", U_f) U_f_GATE = U_f_def.get_constructor() p += U_f_def p += U_f_GATE(*range(n + 1)) # apply Hadamard to all input qubits for i in range(n): p += H(i) # print(p) return p
def get_circuit(self, f, n_qubits): """Creates the DJ circuit for this function f. Parameters ---------- f : f : {0,1}^n -> {0,1} Takes an n-bit array and outputs 1 bit. Either constant or balanced. Returns ------- 1 if f is constant 0 if f is balanced """ # construct oracle and define its gate constructor oracle = self.getOracle(f, n_qubits) oracle_definition = DefGate("ORACLE", oracle) ORACLE = oracle_definition.get_constructor() # make circuit p = Program() # apply the first hadamard to all qubits for i in range(n_qubits): p += H(i) # NOT ancilla qubit then apply hadamard p += X(n_qubits) p += H(n_qubits) # apply oracle to all qubits p += oracle_definition p += ORACLE(*range(n_qubits + 1)) # apply hadamard to computational qubits for i in range(n_qubits): p += H(i) return p
def single_shot_grovers(input_bits): n = round(math.log2(len(input_bits))) if 2**n != len(input_bits): raise Exception(f"could not logify ${input_bits}") # Construct gates for operating our function, and Grover diffusion bit_picker_matrix = np.diag([1 - 2 * bit for bit in input_bits]) bit_picker_definition = DefGate("BIT-PICKER", bit_picker_matrix) BIT_PICKER = bit_picker_definition.get_constructor() diffusion_matrix = diffusion(len(input_bits)) diffusion_definition = DefGate("DIFFUSION", diffusion_matrix) DIFFUSION = diffusion_definition.get_constructor() # Wire up the program qvm = get_qvm(n) p = Program() p += bit_picker_definition p += diffusion_definition # Superposition for i in range(n): p += H(i) p += BIT_PICKER(*range(n)) p += DIFFUSION(*range(n)) ro = p.declare("ro", "BIT", n) for i in range(n): p += MEASURE(i, ro[i]) result = qvm.run(p) return number_from_binary(result[0])
def get_circuit(self, f, n_qubits): # make oracle gate Z_f oracle = self.getOracle(f, n_qubits) oracle_definition = DefGate('ORACLE', oracle) ORACLE = oracle_definition.get_constructor() # make helper gate Z_0 helper = self.getHelper(n_qubits) helper_definition = DefGate('HELPER', helper) HELPER = helper_definition.get_constructor() # make flipper gate -I flipper = self.getFlipper(n_qubits) flipper_definition = DefGate('FLIPPER', flipper) FLIPPER = flipper_definition.get_constructor() # make circuit p = Program() # apply the first hadamard to all qubits for i in range(n_qubits): p += H(i) # add oracle and helper definitions p += oracle_definition p += helper_definition p += flipper_definition # apply oracle and diffuser designated amount of times NUM_ITERATIONS = int(math.sqrt(2**n_qubits) * math.pi / 4) for _ in range(NUM_ITERATIONS): # apply oracle p += ORACLE(*range(n_qubits)) # apply diffuser for i in range(n_qubits): p += H(i) p += HELPER(*range(n_qubits)) for i in range(n_qubits): p += H(i) p += FLIPPER(*range(n_qubits)) return p