def augment_program_with_memory_values(quil_program, memory_map): p = Program() # we stupidly allowed memory_map to be of type Dict[MemoryReference, Any], whereas qc.run # takes a memory initialization argument of type Dict[str, List[Union[int, float]]. until # we are in a position to remove this, we support both styles of input. if len(memory_map.keys()) == 0: return quil_program elif isinstance(list(memory_map.keys())[0], MemoryReference): warn( "Use of memory_map values of type Dict[MemoryReference, Any] have been " "deprecated. Please use Dict[str, List[Union[int, float]]], as with " "QuantumComputer.run .") for k, v in memory_map.items(): p += MOVE(k, v) elif isinstance(list(memory_map.keys())[0], str): for name, arr in memory_map.items(): for index, value in enumerate(arr): p += MOVE(MemoryReference(name, offset=index), value) else: raise TypeError( "Bad memory_map type; expected Dict[str, List[Union[int, float]]]." ) p += quil_program return percolate_declares(p)
def augment_program_with_memory_values(quil_program, memory_map): """ This function allocates the classical memory values (gate angles) to a parametric quil program in order to use it on a Numpy-based simulator :param Program quil_program: parametric quil program which would require classical memory allocation :param Dict memory_map: dictionary with as keys the MemoryReference or String descrbing the classical memory, and with items() an array of values for that classical memory :return: quil program with gate angles from memory_map allocated to the (originally parametric) program :rtype: Program """ p = Program() # this function allocates the memory values for a parametric program correctly... if len(memory_map.keys()) == 0: return quil_program elif isinstance(list(memory_map.keys())[0], MemoryReference): for k, v in memory_map.items(): p += MOVE(k, v) elif isinstance(list(memory_map.keys())[0], str): for name, arr in memory_map.items(): for index, value in enumerate(arr): p += MOVE(MemoryReference(name, offset=index), value) else: raise TypeError( "Bad memory_map type; expected Dict[str, List[Union[int, float]]]." ) p += quil_program return percolate_declares(p)
def augment_program_with_memory_values(self, quil_program): p = Program() for k, v in self._variables_shim.items(): p += MOVE(MemoryReference(name=k.name, offset=k.index), v) p += quil_program return percolate_declares(p)
def augment_program_with_memory_values(quil_program, memory_map): p = Program() for k, v in memory_map.items(): p += MOVE(k, v) p += quil_program return percolate_declares(p)
def __init__(self, list_gsuit_paulis: List[PauliTerm], qc: QuantumComputer, ref_state: Program, ansatz: Program, shotN: int, parametric_way: bool, n_qubits: int, active_reset: bool = True, cq=None, method='QC', verbose: bool = False): """ A tomography experiment class for use in VQE. In a real experiment, one only has access to measurements in the Z-basis, giving a result 0 or 1 for each qubit. The Hamiltonian may have terms like sigma_x or sigma_y, for which a basis rotation is required. As quantum mechanics prohibits measurements in multiple bases at once, the Hamiltonian needs to be grouped into commuting Pauli terms and for each set of terms the appropriate basis rotations is applied to each qubits. A parity_matrix is constructed to keep track of what consequence a qubit measurement of 0 or 1 has as a contribution to the Pauli estimation. The experiments are constructed as objects with class methods to run and adjust them. Instantiate using the following parameters: :param list() list_gsuit_paulis: list of Pauli terms which can be measured at the same time (they share a TPB!) :param QuantumComputer qc: QuantumComputer() object which will simulate the terms :param Program ref_state: Program() circuit object which produces the initial reference state (f.ex. Hartree-Fock) :param Program ansatz: Program() circuit object which produces the ansatz (f.ex. UCCSD) :param int shotN: number of shots to run this Setting for :param bool parametric_way: boolean whether to use parametric gates or hard-coded gates :param int n_qubits: total number of qubits used for the program :param bool active_reset: boolean whether or not to actively reset the qubits :param list() cq: list of qubit labels instead of the default [0,1,2,3,...,N-1] :param str method: string describing the computational method from {QC, linalg, WFS, Numpy} :param bool verbose: boolean, whether or not to give verbose output during execution """ self.parametric_way = parametric_way self.pauli_list = list_gsuit_paulis self.shotN = shotN self.method = method self.parity_matrix = self.construct_parity_matrix(list_gsuit_paulis, n_qubits) self.verbose = verbose self.n_qubits = n_qubits self.cq = cq if qc is not None: if qc.name[-4:] == 'yqvm' and self.cq is not None: raise NotImplementedError('manual qubit lattice feed for PyQVM not yet implemented. please set cq=None') else: if self.method == 'QC': raise ValueError('method is QC but no QuantumComputer object supplied.') # instantiate a new program and construct it for compilation prog = Program() if self.method == 'QC': ro = prog.declare('ro', memory_type='BIT', memory_size=self.n_qubits) if active_reset and self.method == 'QC': if not qc.name[-4:] == 'yqvm': # in case of PyQVM, can not contain reset statement prog += RESET() # circuit which produces reference state (f.ex. Hartree-Fock) prog += ref_state # produce which prepares an ansatz state starting from a reference state (f.ex. UCCSD or swap network UCCSD) prog += ansatz self.coefficients = [] already_done = [] for pauli in list_gsuit_paulis: # let's store the pauli term coefficients for later use self.coefficients.append(pauli.coefficient) # also, we perform the necessary rotations going from X or Y to Z basis for (i, st) in pauli.operations_as_set(): if st is not 'I' and i not in already_done: # note that 'i' is the *logical* index corresponding to the pauli. if cq is not None: # if the logical qubit should be remapped to physical qubits, access this cq prog += pauli_meas(cq[i], st) else: prog += pauli_meas(i, st) # if we already have rotated the basis due to another term, don't do it again! already_done.append(i) self.pure_pyquil_program = Program(prog) if self.method == 'QC': # measure the qubits and assign the result to classical register ro for i in range(self.n_qubits): if cq is not None: prog += MEASURE(cq[i], ro[i]) else: prog += MEASURE(i, ro[i]) prog2 = percolate_declares(prog) # wrap in shotN number of executions on the qc, to get operator measurement by sampling prog2.wrap_in_numshots_loop(shots=self.shotN) self.pyqvm_program = prog2 # compile to native quil if it's not a PYQVM if not qc.name[-4:] == 'yqvm': nq_program = qc.compiler.quil_to_native_quil(prog2) # if self.verbose: # debugging purposes # print('') # print(nq_program.native_quil_metadata) self.pyquil_executable = qc.compiler.native_quil_to_executable(nq_program)
def __init__( self, list_gsuit_paulis: List[PauliTerm], qc: QuantumComputer, ref_state: Program, ansatz: Program, shotN: int, parametric_way: bool, n_qubits: int, active_reset: bool = True, cq=None, method='QC', verbose: bool = False, calibration: bool = True, ): """ A tomography experiment class for use in VQE. In a real experiment, one only has access to measurements in the Z-basis, giving a result 0 or 1 for each qubit. The Hamiltonian may have terms like sigma_x or sigma_y, for which a basis rotation is required. As quantum mechanics prohibits measurements in multiple bases at once, the Hamiltonian needs to be grouped into commuting Pauli terms and for each set of terms the appropriate basis rotations is applied to each qubits. A parity_matrix is constructed to keep track of what consequence a qubit measurement of 0 or 1 has as a contribution to the Pauli estimation. The experiments are constructed as objects with class methods to run and adjust them. Instantiate using the following parameters: :param list() list_gsuit_paulis: list of Pauli terms which can be measured at the same time (they share a TPB!) :param QuantumComputer qc: QuantumComputer() object which will simulate the terms :param Program ref_state: Program() circuit object which produces the initial reference state (f.ex. Hartree-Fock) :param Program ansatz: Program() circuit object which produces the ansatz (f.ex. UCCSD) :param int shotN: number of shots to run this Setting for :param bool parametric_way: boolean whether to use parametric gates or hard-coded gates :param int n_qubits: total number of qubits used for the program :param bool active_reset: boolean whether or not to actively reset the qubits :param list() cq: list of qubit labels instead of the default [0,1,2,3,...,N-1] :param str method: string describing the computational method from {QC, linalg, WFS, Numpy} :param bool verbose: boolean, whether or not to give verbose output during execution """ self.parametric_way = parametric_way self.pauli_list = list_gsuit_paulis self.shotN = shotN self.method = method self.parity_matrix = self.construct_parity_matrix( list_gsuit_paulis, n_qubits) self.verbose = verbose self.n_qubits = n_qubits self.cq = cq self.calibration = calibration if qc is not None: if qc.name[-4:] == 'yqvm' and self.cq is not None: raise NotImplementedError( 'manual qubit lattice feed' ' for PyQVM not yet implemented. please set cq=None') else: if self.method == 'QC': raise ValueError('method is QC but no QuantumComputer' ' object supplied.') # instantiate a new program and construct it for compilation prog = Program() if self.method == 'QC': ro = prog.declare('ro', memory_type='BIT', memory_size=self.n_qubits) if active_reset and self.method == 'QC': if not qc.name[-4:] == 'yqvm': # in case of PyQVM, can not contain reset statement prog += RESET() # circuit which produces reference state (f.ex. Hartree-Fock) prog += ref_state # produce which prepares an ansatz state starting # from a reference state (f.ex. UCCSD or swap network UCCSD) prog += ansatz self.term_keys = [] already_done = [] for pauli in list_gsuit_paulis: # save the id for each term self.term_keys.append(pauli.operations_as_set()) # also, we perform the necessary rotations # going from X or Y to Z basis for (i, st) in pauli.operations_as_set(): if (st == 'X' or st == 'Y') and i not in already_done: # note that 'i' is the *logical* index # corresponding to the pauli. if cq is not None: # if the logical qubit should be remapped # to physical qubits, access this cq prog += pauli_meas(cq[i], st) else: prog += pauli_meas(i, st) # if we already have rotated the basis # due to another term, don't do it again! already_done.append(i) if self.method != 'QC': self.pure_pyquil_program = Program(prog) else: # measure the qubits and assign the result # to classical register ro for i in range(self.n_qubits): if cq is not None: prog += MEASURE(cq[i], ro[i]) else: prog += MEASURE(i, ro[i]) prog2 = percolate_declares(prog) # wrap in shotN number of executions on the qc, # to get operator measurement by sampling prog2.wrap_in_numshots_loop(shots=self.shotN) # print(self.term_keys) # print circuits: # from pyquil.latex import to_latex # print(to_latex(prog2)) # input() if qc.name[-4:] == 'yqvm': self.pyqvm_program = prog2 else: self.pyquil_executable = qc.compile(prog2) # print("compiled") # print(to_latex(qc.compiler.quil_to_native_quil(prog2))) # input() # now about calibration if self.calibration and self.method != 'QC': # turn off calibration if not QC. self.calibration = False if self.calibration: # prepare and run the calibration experiments prog = Program() ro = prog.declare('ro', memory_type='BIT', memory_size=self.n_qubits) if active_reset: if not qc.name[-4:] == 'yqvm': # in case of PyQVM, can not contain reset statement prog += RESET() # circuit which produces reference state, # which is in the case of calibration experiments # the same as the out_operators. already_done = [] for pauli in list_gsuit_paulis: # also, we perform the necessary rotations # going to X/Y/Z =1 state for (i, st) in pauli.operations_as_set(): if (st == 'X' or st == 'Y') and i not in already_done: # note that 'i' is the *logical* index # corresponding to the pauli. if cq is not None: # if the logical qubit should be remapped # to physical qubits, access this cq prog += pauli_meas(cq[i], st).dagger() else: prog += pauli_meas(i, st).dagger() # if we already have rotated the basis # due to another term, don't do it again! already_done.append(i) # measurement now already_done = [] for pauli in list_gsuit_paulis: # also, we perform the necessary rotations # going from X or Y to Z basis for (i, st) in pauli.operations_as_set(): if (st == 'X' or st == 'Y') and i not in already_done: # note that 'i' is the *logical* index # corresponding to the pauli. if cq is not None: # if the logical qubit should be remapped # to physical qubits, access this cq prog += pauli_meas(cq[i], st) else: prog += pauli_meas(i, st) # if we already have rotated the basis # due to another term, don't do it again! already_done.append(i) # measure the qubits and assign the result # to classical register ro for i in range(self.n_qubits): if cq is not None: prog += MEASURE(cq[i], ro[i]) else: prog += MEASURE(i, ro[i]) prog2 = percolate_declares(prog) # wrap in shotN number of executions on the qc, # to get operator measurement by sampling prog2.wrap_in_numshots_loop(shots=self.shotN) if qc.name[-4:] == 'yqvm': self.pyqvm_program = prog2 bitstrings = qc.run(self.pyqvm_program) # compile to native quil if it's not a PYQVM else: pyquil_executable = qc.compile(prog2) bitstrings = qc.run(pyquil_executable) # start data processing # this matrix computes the pauli string parity, # and stores that for each bitstring is_odd = np.mod(bitstrings.dot(self.parity_matrix), 2) # if the parity is odd, the bitstring gives a -1 eigenvalue, # and +1 vice versa. # sum over all bitstrings, average over shotN shots, # and weigh each pauli string by its coefficient e_array = 1 - 2 * np.sum(is_odd, axis=0) / self.shotN self.calibration_norms = e_array print(f"calibration_norms: {e_array}")