def build_circuit(self, qubit_ops): program = Program() self.qubits = [program.alloc() for _ in range(len(qubit_ops))] program_readout_reg = program.declare("ro", memory_type="BIT", memory_size=len(self.qubits)) for qb, gatestr in zip(self.qubits, qubit_ops): if gatestr == "I": pass elif gatestr == "X": program.gate("X", qubits=[qb], params=[]) elif gatestr == "H": program.gate("H", qubits=[qb], params=[]) elif gatestr == "K": # our one char CX label program.gate( "CNOT", qubits=[self.qubits[self.qubits.index(qb) - 1], qb], params=[]) program.measure_all(*zip(self.qubits, program_readout_reg)) program = address_qubits(program) program.wrap_in_numshots_loop(shots=10) self.program = program
def generate_single_t1_experiment(qubits: Union[int, List[int]], time: float, n_shots: int = 1000) -> Program: """ Return a t1 program in native Quil for a single time point. :param qubits: Which qubits to measure. :param time: The decay time before measurement. :param n_shots: The number of shots to average over for the data point. :return: A T1 Program. """ program = Program() try: len(qubits) except TypeError: qubits = [qubits] ro = program.declare('ro', 'BIT', len(qubits)) for q in qubits: program += RX(np.pi, q) program += Pragma('DELAY', [q], str(time)) for i in range(len(qubits)): program += MEASURE(qubits[i], ro[i]) program.wrap_in_numshots_loop(n_shots) return program
def generate_single_rabi_experiment(qubits: Union[int, List[int]], theta: float, n_shots: int = 1000) -> Program: """ Return a Rabi program in native Quil rotated through the given angle. Rabi oscillations are observed by applying successively larger rotations to the same initial state. :param qubits: Which qubits to measure. :param theta: The angle of the Rabi RX rotation. :param n_shots: The number of shots to average over for the data point. :return: A Program that rotates through a given angle about the X axis. """ program = Program() try: len(qubits) except TypeError: qubits = [qubits] ro = program.declare('ro', 'BIT', len(qubits)) for q in qubits: program += RX(theta, q) for i in range(len(qubits)): program += MEASURE(qubits[i], ro[i]) program.wrap_in_numshots_loop(n_shots) return program
def run_and_benchmark_program(self, prog: Program, trials: int, is_correct: Callable[[List[int]], bool], separate: bool) -> int: n_qubits = len(prog.get_qubits()) qvm = pyquil.get_qc("{}q-qvm".format(n_qubits)) # The paramaters are 10x less noisy than the defaults. qvm.qam.noise_model = self.noise_model(qvm.device) if separate: elapsed = [] results = [] for i in range(trials): start_time = time.time() trial_results = qvm.run(prog) end_time = time.time() elapsed.append(end_time - start_time) results.extend(trial_results) else: prog.wrap_in_numshots_loop(trials) start_time = time.time() results = qvm.run(prog) end_time = time.time() elapsed = end_time - start_time correct = sum(is_correct(result) for result in results) return correct, elapsed
def generate_cz_phase_ramsey_program(qb: int, other_qb: int, n_shots: int = 1000) -> Program: """ Generate a single CZ phase Ramsey experiment at a given phase. :param qb: The qubit to move around the Bloch sphere and measure the incurred RZ on. :param other_qb: The other qubit that constitutes a two-qubit pair along with `qb`. :param n_shots: The number of shots to average over for each data point. :param phase: The phase kick to supply after playing the CZ pulse on the equator. :param num_shots: The number of shots to average over for the data point. :return: A parametric Program for performing a CZ Ramsey experiment. """ program = Program() # NOTE: only need readout register for `qb` not `other_qb` since `other_qb` is only # needed to identify which CZ gate we're using ro = program.declare('ro', 'BIT', 1) theta = program.declare('theta', 'REAL') # go to the equator program += Program(RX(np.pi / 2, qb)) # apply the CZ gate - note that CZ is symmetric, so the order of qubits doesn't matter program += Program(CZ(qb, other_qb)) # go to |1> after a phase kick program += Program(RZ(theta, qb), RX(np.pi / 2, qb)) program += MEASURE(qb, ro[0]) program.wrap_in_numshots_loop(n_shots) return program
def run_and_measure(self, program: Program, trials: int): """ Run the provided state preparation program and measure all qubits. .. note:: In contrast to :py:class:`QVMConnection.run_and_measure`, this method simulates noise correctly for noisy QVMs. However, this method is slower for ``trials > 1``. For faster noise-free simulation, consider :py:class:`WavefunctionSimulator.run_and_measure`. :param program: The state preparation program to run and then measure. :param trials: The number of times to run the program. :return: A numpy array of shape (trials, n_device_qubits) that contains 0s and 1s """ program = program.copy() for instr in program.instructions: if not isinstance(instr, Gate) and not isinstance(instr, Reset): raise ValueError( "run_and_measure programs must consist only of quantum gates." ) ro = program.declare('ro', 'BIT', max(self.device.qubit_topology().nodes) + 1) for q in sorted(self.device.qubit_topology().nodes): program.inst(MEASURE(q, ro[q])) program.wrap_in_numshots_loop(trials) executable = self.compile(program) return self.run(executable=executable)
def _operator_object_hook(obj: Mapping[str, Any]) -> Union[Mapping[str, Any], Experiment]: if "type" in obj and obj["type"] in ["Experiment", "TomographyExperiment"]: # I bet this doesn't work for grouped experiment settings settings = [[ExperimentSetting.from_str(s) for s in stt] for stt in obj["settings"]] p = Program(obj["program"]) p.wrap_in_numshots_loop(obj["shots"]) ex = Experiment(settings=settings, program=p, symmetrization=obj["symmetrization"]) ex.reset = obj["reset"] return ex return obj
def append_measure_register(program: Program, qubits: List = None, trials: int = 10, ham: PauliSum = None) -> Program: """Creates readout register, MEASURE instructions for register and wraps in trials trials. Parameters ---------- param qubits : list List of Qubits to measure. If None, program.get_qubits() is used param trials : int The number of trials to run. param ham : PauliSum Hamiltonian to whose basis we need to switch. All terms in it must trivially commute! Returns ------- Program : program with the gate change and measure instructions appended """ base_change_gates = {'X': lambda qubit: H(qubit), 'Y': lambda qubit: RX(np.pi / 2, qubit), 'Z': lambda qubit: I(qubit)} if qubits is None: qubits = program.get_qubits() def _get_correct_gate(qubit: Union[int, QubitPlaceholder]) -> Program(): """Correct base change gate on the qubit `qubit` given `ham`""" # this is an extra function, because `return` allows us to # easily break out of loops for term in ham: if term[qubit] != 'I': return base_change_gates[term[qubit]](qubit) raise ValueError(f"PauliSum {ham} doesn't act on qubit {qubit}") # append to correct base change gates if ham is specified. Otherwise # assume diagonal basis if ham is not None: for qubit in ham.get_qubits(): program += Program(_get_correct_gate(qubit)) # create a read out register ro = program.declare('ro', memory_type='BIT', memory_size=len(qubits)) # add measure instructions to the specified qubits for i, qubit in enumerate(qubits): program += MEASURE(qubit, ro[i]) program.wrap_in_numshots_loop(trials) return program
def run_and_measure(self, program: Program, trials: int) -> Dict[int, np.ndarray]: """ Run the provided state preparation program and measure all qubits. The returned data is a dictionary keyed by qubit index because qubits for a given QuantumComputer may be non-contiguous and non-zero-indexed. To turn this dictionary into a 2d numpy array of bitstrings, consider:: bitstrings = qc.run_and_measure(...) bitstring_array = np.vstack([bitstrings[q] for q in qc.qubits()]).T bitstring_array.shape # (trials, len(qc.qubits())) .. note:: If the target :py:class:`QuantumComputer` is a noiseless :py:class:`QVM` then only the qubits explicitly used in the program will be measured. Otherwise all qubits will be measured. In some circumstances this can exhaust the memory available to the simulator, and this may be manifested by the QVM failing to respond or timeout. .. note:: In contrast to :py:class:`QVMConnection.run_and_measure`, this method simulates noise correctly for noisy QVMs. However, this method is slower for ``trials > 1``. For faster noise-free simulation, consider :py:class:`WavefunctionSimulator.run_and_measure`. :param program: The state preparation program to run and then measure. :param trials: The number of times to run the program. :return: A dictionary keyed by qubit index where the corresponding value is a 1D array of measured bits. """ program = program.copy() validate_supported_quil(program) ro = program.declare("ro", "BIT", len(self.qubits())) measure_used = isinstance(self.qam, QVM) and self.qam.noise_model is None qubits_to_measure = set( map(qubit_index, program.get_qubits()) if measure_used else self. qubits()) for i, q in enumerate(qubits_to_measure): program.inst(MEASURE(q, ro[i])) program.wrap_in_numshots_loop(trials) executable = self.compile(program) bitstring_array = self.run(executable=executable) bitstring_dict = {} for i, q in enumerate(qubits_to_measure): bitstring_dict[q] = bitstring_array[:, i] for q in set(self.qubits()) - set(qubits_to_measure): bitstring_dict[q] = np.zeros(trials) return bitstring_dict
def compile_parametric_program(qc: QuantumComputer, parametric_prog: Program, num_shots: int = 1000) -> None: """ Compile the parametric program, and transfer the binary to the quantum device. :param qc: The QuantumComputer to run the experiment on. :param parametric_prog: The parametric program to compile and transfer to the quantum device. :param num_shots: The number of shots to average over for each data point. :return: The binary from the compiled parametric program. """ parametric_prog.wrap_in_numshots_loop(shots=num_shots) binary = qc.compiler.native_quil_to_executable(parametric_prog) return binary
def run_symmetrized_readout(self, program: Program, trials: int) -> np.ndarray: """ Run a quil program in such a way that the readout error is made collectively symmetric This means the probability of a bitstring ``b`` being mistaken for a bitstring ``c`` is the same as the probability of ``not(b)`` being mistaken for ``not(c)`` A more general symmetrization would guarantee that the probability of ``b`` being mistaken for ``c`` depends only on which bit of ``c`` are different from ``b``. This would require choosing random subsets of bits to flip. In a noisy device, the probability of accurately reading the 0 state might be higher than that of the 1 state. This makes correcting for readout more difficult. This function runs the program normally ``(trials//2)`` times. The other half of the time, it will insert an ``X`` gate prior to any ``MEASURE`` instruction and then flip the measured classical bit back. See :py:func:`run` for this function's parameter descriptions. """ flipped_program = _get_flipped_protoquil_program(program) if trials % 2 != 0: raise ValueError("Using symmetrized measurement functionality requires that you " "take an even number of trials.") half_trials = trials // 2 flipped_program = flipped_program.wrap_in_numshots_loop(shots=half_trials) flipped_executable = self.compile(flipped_program) executable = self.compile(program.wrap_in_numshots_loop(half_trials)) samples = self.run(executable) flipped_samples = self.run(flipped_executable) double_flipped_samples = np.logical_not(flipped_samples).astype(int) results = np.concatenate((samples, double_flipped_samples), axis=0) np.random.shuffle(results) return results
def generate_calibration_experiment(self) -> "Experiment": """ Generate another ``Experiment`` object that can be used to calibrate the various multi-qubit observables involved in this ``Experiment``. This is achieved by preparing the plus-one (minus-one) eigenstate of each ``out_operator``, and measuring the resulting expectation value of the same ``out_operator``. Ideally, this would always give +1 (-1), but when symmetric readout error is present the effect is to scale the resultant expectations by some constant factor. Determining this scale factor is what we call *readout calibration*, and then the readout error in subsequent measurements can then be mitigated by simply dividing by the scale factor. :return: A new ``Experiment`` that can calibrate the readout error of all the observables involved in this experiment. """ if self.calibration != CalibrationMethod.PLUS_EIGENSTATE: raise ValueError( 'We currently only support the "plus eigenstate" calibration method.' ) calibration_settings = [] for settings in self: assert len(settings) == 1 calibration_settings.append( ExperimentSetting( in_state=settings[0].out_operator, out_operator=settings[0].out_operator, additional_expectations=settings[0]. additional_expectations, )) calibration_program = Program() if self.reset: calibration_program += RESET() calibration_program.wrap_in_numshots_loop(self.shots) if self.symmetrization != SymmetrizationLevel.EXHAUSTIVE: raise ValueError( "We currently only support calibration for exhaustive symmetrization" ) return Experiment( settings=calibration_settings, program=calibration_program, symmetrization=SymmetrizationLevel.EXHAUSTIVE, calibration=CalibrationMethod.NONE, )
def run_and_measure(self, program: Program, trials: int) -> Dict[int, np.ndarray]: """ Run the provided state preparation program and measure all qubits. This will measure all the qubits on this QuantumComputer, not just qubits that are used in the program. The returned data is a dictionary keyed by qubit index because qubits for a given QuantumComputer may be non-contiguous and non-zero-indexed. To turn this dictionary into a 2d numpy array of bitstrings, consider:: bitstrings = qc.run_and_measure(...) bitstring_array = np.vstack(bitstrings[q] for q in sorted(qc.qubits())).T bitstring_array.shape # (trials, len(qc.qubits())) .. note:: In contrast to :py:class:`QVMConnection.run_and_measure`, this method simulates noise correctly for noisy QVMs. However, this method is slower for ``trials > 1``. For faster noise-free simulation, consider :py:class:`WavefunctionSimulator.run_and_measure`. :param program: The state preparation program to run and then measure. :param trials: The number of times to run the program. :return: A dictionary keyed by qubit index where the corresponding value is a 1D array of measured bits. """ program = program.copy() program = _validate_run_and_measure_program(program) ro = program.declare('ro', 'BIT', len(self.qubits())) for i, q in enumerate(self.qubits()): program.inst(MEASURE(q, ro[i])) program.wrap_in_numshots_loop(trials) executable = self.compile(program) bitstring_array = self.run(executable=executable) bitstring_dict = {} for i, q in enumerate(self.qubits()): bitstring_dict[q] = bitstring_array[:, i] return bitstring_dict
def _run_rpe_program(qc: QuantumComputer, program: Program, measure_qubits: Sequence[Sequence[int]], num_shots: int) -> np.ndarray: """ Simple helper to run a program with appropriate number of shots and return result. Note that the program is first compiled with basic_compile. :param qc: quantum computer to run program on :param program: program to run :param measure_qubits: all of the qubits to be measured after the program is run :param num_shots: number of shots of results to collect for the program :return: the results for all of the measure_qubits after running the program """ prog = Program() + program # make a copy of program meas_qubits = [qubit for qubits in measure_qubits for qubit in qubits] ro_bit = prog.declare("ro", "BIT", len(meas_qubits)) for idx, q in enumerate(meas_qubits): prog.measure(q, ro_bit[idx]) prog.wrap_in_numshots_loop(num_shots) executable = qc.compiler.native_quil_to_executable(basic_compile(prog)) return qc.run(executable)
def generate_single_t2_echo_experiment(qubits: Union[int, List[int]], time: float, detuning: float, n_shots: int = 1000) -> Program: """ Return a T2 echo program in native Quil for a single time point. :param qubits: Which qubits to measure. :param time: The decay time before measurement. :param detuning: The additional detuning frequency about the z axis. :param n_shots: The number of shots to average over for the data point. :return: A T2 Program. """ program = Program() try: len(qubits) except TypeError: qubits = [qubits] ro = program.declare('ro', 'BIT', len(qubits)) for q in qubits: # prepare plus state |+> program += RX(np.pi / 2, q) # wait half of the delay program += Pragma('DELAY', [q], str(time / 2)) # apply an X gate compiled out of RX(90) program += RX(np.pi / 2, q) program += RX(np.pi / 2, q) # wait the other half of the delay program += Pragma('DELAY', [q], str(time / 2)) program += RZ(2 * np.pi * time * detuning, q) program += RX(np.pi / 2, q) for i in range(len(qubits)): program += MEASURE(qubits[i], ro[i]) program.wrap_in_numshots_loop(n_shots) return program
def _compute_loss(self, parameters, history_list, dataset_type, indices=None): """ Computes mean loss for the given data subset (training or test). :param parameters: (list) Vector of training circuit parameters :param history_list: (list) List to store losses :param dataset_type: (bool) Indicator for training (0) or testing (1) set :param indices: (list) List of indices pointing to state preparation circuits (for training or testing) :returns: Average loss value for a given data set (training or test) :rtype: float """ losses = [] # Compute cost function value for each data point for i, index in enumerate(indices): # Apply compression and recovery maps qae_circuit = Program() qae_circuit.inst(self.construct_compression_circuit(parameters, index)) if not self.trash_training: qae_circuit.inst(self.construct_recovery_circuit(parameters, index)) # Apply measurement operations ro = qae_circuit.declare('ro', memory_type='BIT', memory_size=self.memory_size) for j in range(self.memory_size): qae_circuit.inst(MEASURE(self._physical_labels[j], ro[j])) # Prepare circuit execution qae_circuit = qae_circuit.wrap_in_numshots_loop(self.n_shots) if self.compile_program: qae_circuit = self.forest_cxn.compile(qae_circuit) # Compute loss for the data point and store single_loss = self._execute_circuit(parameters, qae_circuit, self.memory_size) losses.append(single_loss) mean_loss = -1. * numpy.mean(losses) history_list.append(mean_loss) if self.verbose: if (len(history_list) - 1) % self.print_interval == 0: print("Iter {0:4d} Mean Loss: {1:.7f}".format(self.n_iter, mean_loss)) self.n_iter += 1 return mean_loss
def compile_tomo_expts(self, pauli_list=None): """ This method compiles the tomography experiment circuits and prepares them for simulation. Every time the circuits are adjusted, re-compiling the tomography experiments is required to affect the outcome. """ # use Forest's sorting algo from the Tomography suite # to group Pauli measurements together settings = [] if pauli_list is None: pauli_list = self.pauli_list for term in pauli_list: # skip an identity operator, if len(term.operations_as_set()) > 0: # initial state and term pair. settings.append(ExperimentSetting( TensorProductState(), term)) # group_experiments cannot be directly run # because there may be experiment with multiple settings. # so here we just use group_experiments to sort out the settings, # then reset the experiments with additional measurements. experiments = Experiment(settings, Program()) suite = group_experiments(experiments) # we just need the grouped settings. grouped_pauil_terms = [] for setting in suite: group = [] for i, term in enumerate(setting): pauil_term = term.out_operator.copy() # Coefficients to be multiplied. pauil_term.coefficient = complex(1.) if i == 0: group.append(pauil_term) elif len(pauil_term) > len(group[0]): group.insert(0, pauil_term) else: group.append(pauil_term) # make sure the longest pauil_term contains all the small # pauil_terms. Otherwise, we prepare a bigger one. bigger_pauli = group[0] if len(group) > 1: for term in group[1:]: for iq, op in term.operations_as_set(): if op != group[0][iq]: assert(group[0][iq] == "I"), \ (f"{term} and {group[0]}" " not compatible!") if bigger_pauli is group[0]: bigger_pauli == group[0].copy() bigger_pauli *= PauliTerm(op, iq) if bigger_pauli is not group[0]: print(f"new pauli_term generated: {bigger_pauli}") group.insert(0, bigger_pauli) grouped_pauil_terms.append(group) # group settings with additional_expectations. grouped_settings = [] for pauil_terms in grouped_pauil_terms: additional = None if len(pauil_terms) > 1: additional = [] for term in pauil_terms[1:]: additional.append(term.get_qubits()) grouped_settings.append( ExperimentSetting(TensorProductState(), pauil_terms[0], additional_expectations=additional)) # get the uccsd program prog = Program() prog += RESET() prog += self.ref_state + self.ansatz prog.wrap_in_numshots_loop(shots=self.shotN) self.experiment_list = Experiment(grouped_settings, prog) print('Number of tomography experiments: ', len(self.experiment_list)) # calibration expreimental results. self.calibrations = self.qc.calibrate(self.experiment_list)
def generate_experiment_program(self) -> Program: """ Generate a parameterized program containing the main body program along with some additions to support the various state preparation, measurement, and symmetrization specifications of this ``Experiment``. State preparation and measurement are achieved via ZXZXZ-decomposed single-qubit gates, where the angles of each ``RZ`` rotation are declared parameters that can be assigned at runtime. Symmetrization is achieved by putting an ``RX`` gate (also parameterized by a declared value) before each ``MEASURE`` operation. In addition, a ``RESET`` operation is prepended to the ``Program`` if the experiment has active qubit reset enabled. Finally, each qubit specified in the settings is measured, and the number of shots is added. :return: Parameterized ``Program`` that is capable of collecting statistics for every ``ExperimentSetting`` in this ``Experiment``. """ meas_qubits = self.get_meas_qubits() p = Program() if self.reset: if any( isinstance(instr, (Reset, ResetQubit)) for instr in self.program): raise ValueError("RESET already added to program") p += RESET() for settings in self: assert len(settings) == 1 if ("X" in str(settings[0].in_state)) or ("Y" in str( settings[0].in_state)): if f"DECLARE preparation_alpha" in self.program.out(): raise ValueError( f'Memory "preparation_alpha" has been declared already.' ) if f"DECLARE preparation_beta" in self.program.out(): raise ValueError( f'Memory "preparation_beta" has been declared already.' ) if f"DECLARE preparation_gamma" in self.program.out(): raise ValueError( f'Memory "preparation_gamma" has been declared already.' ) p += parameterized_single_qubit_state_preparation(meas_qubits) break p += self.program for settings in self: assert len(settings) == 1 if ("X" in str(settings[0].out_operator)) or ("Y" in str( settings[0].out_operator)): if f"DECLARE measurement_alpha" in self.program.out(): raise ValueError( f'Memory "measurement_alpha" has been declared already.' ) if f"DECLARE measurement_beta" in self.program.out(): raise ValueError( f'Memory "measurement_beta" has been declared already.' ) if f"DECLARE measurement_gamma" in self.program.out(): raise ValueError( f'Memory "measurement_gamma" has been declared already.' ) p += parameterized_single_qubit_measurement_basis(meas_qubits) break if self.symmetrization != 0: if f"DECLARE symmetrization" in self.program.out(): raise ValueError( f'Memory "symmetrization" has been declared already.') p += parameterized_readout_symmetrization(meas_qubits) if "DECLARE ro" in self.program.out(): raise ValueError( 'Memory "ro" has already been declared for this program.') p += measure_qubits(meas_qubits) p.wrap_in_numshots_loop(self.shots) return p
def __init__(self, qc, file_prefix, num_bits, hamil, all_var_nums, fun_name_to_fun, do_resets=True, **kwargs): """ Constructor Do in constructor as much hamil indep stuff as possible so don't have to redo it with every call to cost fun. Also, when self.num_samples !=0, we store a dict called term_to_exec mapping an executable (output of Rigetti compile() function) to a term, for each term in the hamiltonian hamil. When num_samples=0, term_to_exec={} Parameters ---------- qc : QuantumComputer file_prefix : str num_bits : int hamil : QubitOperator all_var_nums : list[int] fun_name_to_fun : dict[str, function] do_resets : bool kwargs : dict key-words args of MeanHamilMinimizer constructor Returns ------- """ MeanHamil.__init__(self, file_prefix, num_bits, hamil, all_var_nums, fun_name_to_fun, **kwargs) self.qc = qc self.do_resets = do_resets # this creates a file with all PyQuil gates that # are independent of hamil. Gates may contain free parameters self.translator = Qubiter_to_RigettiPyQuil( self.file_prefix, self.num_bits, aqasm_name='RigPyQuil', prelude_str='', ending_str='') with open(self.translator.aqasm_path, 'r') as fi: self.translation_line_list = fi.readlines() pg = Program() self.pg = pg if self.num_samples: # pg prelude pg += Pragma('INITIAL_REWIRING', ['"PARTIAL"']) if self.do_resets: pg += RESET() ro = pg.declare('ro', 'BIT', self.num_bits) s = '' for var_num in self.all_var_nums: vname = self.translator.vprefix + str(var_num) s += vname s += ' = pg.declare("' s += vname s += '", memory_type="REAL")\n' exec(s) # add to pg the operations that are independent of hamil for line in self.translation_line_list: line = line.strip('\n') if line: exec(line) len_pg_in = len(pg) # hamil loop to store executables for each term in hamil self.term_to_exec = {} for term, coef in self.hamil.terms.items(): # reset pg to initial length. # Temporary work-around to bug # in PyQuil ver 2.5.0. # Slicing was changing # pg from type Program to type list pg = Program(pg[:len_pg_in]) self.pg = pg # add xy measurements coda to pg bit_pos_to_xy_str =\ {bit: action for bit, action in term if action != 'Z'} RigettiTools.add_xy_meas_coda_to_program( pg, bit_pos_to_xy_str) # request measurements for i in range(self.num_bits): pg += MEASURE(i, ro[i]) pg.wrap_in_numshots_loop(shots=self.num_samples) executable = self.qc.compile(pg) # print(",,,...", executable) self.term_to_exec[term] = executable
def simulate(self, amplitudes): """Perform the simulation for the molecule. Args: amplitudes (list): The initial amplitudes (float64). Returns: float64: The total energy (energy). Raise: ValueError: If the dimension of the amplitude list is incorrect. """ if len(amplitudes) != self.amplitude_dimension: raise ValueError("Incorrect dimension for amplitude list.") #Anti-hermitian operator and its qubit form generator = uccsd_singlet_generator(amplitudes, self.of_mole.n_qubits, self.of_mole.n_electrons) jw_generator = jordan_wigner(generator) pyquil_generator = qubitop_to_pyquilpauli(jw_generator) p = Program(Pragma('INITIAL_REWIRING', ['"GREEDY"'])) # Set initial wavefunction (Hartree-Fock) for i in range(self.of_mole.n_electrons): p.inst(X(i)) # Trotterization (unitary for UCCSD state preparation) for term in pyquil_generator.terms: term.coefficient = np.imag(term.coefficient) p += exponentiate(term) p.wrap_in_numshots_loop(self.backend_options["n_shots"]) # Do not simulate if no operator was passed if len(self.qubit_hamiltonian.terms) == 0: return 0. else: # Run computation using the right backend if isinstance(self.backend_options["backend"], WavefunctionSimulator): energy = self.backend_options["backend"].expectation( prep_prog=p, pauli_terms=self.forest_qubit_hamiltonian) else: # Set up experiment, each setting corresponds to a particular measurement basis settings = [ ExperimentSetting(in_state=TensorProductState(), out_operator=forest_term) for forest_term in self.forest_qubit_hamiltonian.terms ] experiment = TomographyExperiment(settings=settings, program=p) print(experiment, "\n") results = self.backend_options["backend"].experiment( experiment) energy = 0. coefficients = [ forest_term.coefficient for forest_term in self.forest_qubit_hamiltonian.terms ] for i in range(len(results)): energy += results[i].expectation * coefficients[i] energy = np.real(energy) # Save the amplitudes so we have the optimal ones for RDM calculation self.optimized_amplitudes = amplitudes return energy
def run(qc: QuantumComputer, exp: Program, n_trials: int) -> np.ndarray: exp.wrap_in_numshots_loop(n_trials) executable = qc.compiler.native_quil_to_executable(basic_compile(exp)) return qc.run(executable)
def __init__(self, qc, file_prefix, num_bits, hamil, all_var_nums, fun_name_to_fun, do_resets=True, **kwargs): """ Constructor Do in constructor as much hamil indep stuff as possible so don't have to redo it with every call to cost fun. Also, when self.num_samples !=0, we store a dict called term_to_exec mapping an executable (output of Rigetti compile() function) to a term, for each term in the hamiltonian hamil. When num_samples=0, term_to_exec={} Parameters ---------- qc : QuantumComputer file_prefix : str num_bits : int hamil : QubitOperator all_var_nums : list[int] fun_name_to_fun : dict[str, function] do_resets : bool kwargs : dict key-words args of MeanHamilMinimizer constructor Returns ------- """ MeanHamil.__init__(self, file_prefix, num_bits, hamil, all_var_nums, fun_name_to_fun, **kwargs) self.qc = qc self.do_resets = do_resets # this creates a file with all PyQuil gates that # are independent of hamil. Gates may contain free parameters self.translator = Qubiter_to_RigettiPyQuil(self.file_prefix, self.num_bits, aqasm_name='RigPyQuil', prelude_str='', ending_str='') with open(self.translator.aqasm_path, 'r') as fi: self.translation_line_list = fi.readlines() pg = Program() self.pg = pg if self.num_samples: # pg prelude pg += Pragma('INITIAL_REWIRING', ['"PARTIAL"']) if self.do_resets: pg += RESET() ro = pg.declare('ro', 'BIT', self.num_bits) s = '' for var_num in self.all_var_nums: vname = self.translator.vprefix + str(var_num) s += vname s += ' = pg.declare("' s += vname s += '", memory_type="REAL")\n' exec(s) # add to pg the operations that are independent of hamil for line in self.translation_line_list: line = line.strip('\n') if line: exec(line) len_pg_in = len(pg) # hamil loop to store executables for each term in hamil self.term_to_exec = {} for term, coef in self.hamil.terms.items(): # reset pg to initial length. # Temporary work-around to bug # in PyQuil ver 2.5.0. # Slicing was changing # pg from type Program to type list pg = Program(pg[:len_pg_in]) self.pg = pg # add xy measurements coda to pg bit_pos_to_xy_str =\ {bit: action for bit, action in term if action != 'Z'} RigettiTools.add_xy_meas_coda_to_program(pg, bit_pos_to_xy_str) # request measurements for i in range(self.num_bits): pg += MEASURE(i, ro[i]) pg.wrap_in_numshots_loop(shots=self.num_samples) executable = self.qc.compile(pg) # print(",,,...", executable) self.term_to_exec[term] = executable