def test_pyquil_program(): """Tests if the Dynamic Decoupling Sequence gives rise to Identity operation in PyQuil """ _duration = 5e-6 _offsets = [0, 1e-6, 2.5e-6, 4e-6, 5e-6] _rabi_rotations = [np.pi / 2, np.pi / 2, np.pi, 0, np.pi / 2] _azimuthal_angles = [0, 0, np.pi / 2, 0, 0] _detuning_rotations = [0, 0, 0, np.pi, 0] sequence = DynamicDecouplingSequence( duration=_duration, offsets=_offsets, rabi_rotations=_rabi_rotations, azimuthal_angles=_azimuthal_angles, detuning_rotations=_detuning_rotations) program = convert_dds_to_pyquil_program(sequence, [0], gate_time=1e-6) assert len(program) == 13 assert program[0] == Pragma("PRESERVE_BLOCK") assert program[-1] == Pragma("END_PRESERVE_BLOCK") assert program[1] == RX(np.pi / 2, 0) assert program[2] == I(0) assert program[3] == RX(np.pi / 2, 0) assert program[4] == I(0) assert program[5] == RY(np.pi, 0) assert program[6] == I(0) assert program[7] == RZ(np.pi, 0) assert program[8] == I(0) assert program[9] == RX(np.pi / 2, 0)
def test_warn_on_pragma_with_trailing_measures(): "Check that to_latex warns when measurement alignment conflicts with gate group pragma." with pytest.warns(UserWarning): _ = to_latex(Program(Declare('ro', 'BIT'), Pragma("LATEX_GATE_GROUP"), MEASURE(0, MemoryReference('ro')), Pragma("END_LATEX_GATE_GROUP"), MEASURE(1, MemoryReference('ro'))))
def test_gate_group_pragma(): "Check that to_latex does not fail on LATEX_GATE_GROUP pragma." p = Program() p.inst(Pragma("LATEX_GATE_GROUP", [], 'foo'), X(0), X(0), Pragma("END_LATEX_GATE_GROUP"), X(1)) _ = to_latex(p)
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 _run_program(self, program): program = program.copy() if self.qpu: # time to go through the compiler. whee! pragma = Program( [Pragma("INITIAL_REWIRING", ['"PARTIAL"']), RESET()]) program = pragma + program program = self._wrap_program(program) nq_program = self._qc.compiler.quil_to_native_quil(program) gate_count = sum(1 for instr in nq_program if isinstance(instr, Gate)) executable = self._qc.compiler.native_quil_to_executable( nq_program) results = self._qc.run(executable=executable) else: program = self._wrap_program(program) gate_count = len(program) results = self._qc.run(program) info = { "gate_count": gate_count } # compiled length for qpu, uncompiled for qvm return results, info
def test_validate_protoquil_with_pragma(): prog = Program( RESET(), H(1), Pragma('DELAY'), MEASURE(1) ) assert prog.is_protoquil()
def test_validate_supported_quil_with_pragma(): prog = Program( RESET(), H(1), Pragma('DELAY'), MEASURE(1, None) ) assert prog.is_supported_on_qpu()
def test_noisy_instruction(): # Unaffected assert I(0) == _noisy_instruction(I(0)) assert RZ(.234)(0) == _noisy_instruction(RZ(.234)(0)) assert Pragma('lalala') == _noisy_instruction(Pragma('lalala')) # Noisified assert _noisy_instruction(CZ(0, 1)).out() == 'noisy-cz 0 1' assert _noisy_instruction(RX(np.pi / 2)(0)).out() == 'noisy-x-plus90 0' assert _noisy_instruction(RX(-np.pi / 2)(23)).out() == 'noisy-x-minus90 23' # Unsupported with pytest.raises(ValueError): _noisy_instruction(H(0)) with pytest.raises(ValueError): _noisy_instruction(RX(2 * np.pi / 3)(0))
def pre_expval(self): """Run the QVM""" # pylint: disable=attribute-defined-outside-init for e in self.expval_queue: wire = [e.wires[0]] if e.name == 'PauliX': # X = H.Z.H self.apply('Hadamard', wire, []) elif e.name == 'PauliY': # Y = (HS^)^.Z.(HS^) and S^=SZ self.apply('PauliZ', wire, []) self.apply('S', wire, []) self.apply('Hadamard', wire, []) elif e.name == 'Hadamard': # H = Ry(-pi/4)^.Z.Ry(-pi/4) self.apply('RY', wire, [-np.pi/4]) elif e.name == 'Hermitian': # For arbitrary Hermitian matrix H, let U be the unitary matrix # that diagonalises it, and w_i be the eigenvalues. H = e.parameters[0] Hkey = tuple(H.flatten().tolist()) if Hkey in self._eigs: # retrieve eigenvectors U = self._eigs[Hkey]['eigvec'] else: # store the eigenvalues corresponding to H # in a dictionary, so that they do not need to # be calculated later w, U = np.linalg.eigh(H) self._eigs[Hkey] = {'eigval': w, 'eigvec': U} # Perform a change of basis before measuring by applying U^ to the circuit self.apply('QubitUnitary', wire, [U.conj().T]) prag = Program(Pragma('INITIAL_REWIRING', ['"PARTIAL"'])) if self.active_reset: prag += RESET() self.prog = prag + self.prog qubits = list(self.prog.get_qubits()) ro = self.prog.declare('ro', 'BIT', len(qubits)) for i, q in enumerate(qubits): self.prog.inst(MEASURE(q, ro[i])) self.prog.wrap_in_numshots_loop(self.shots) executable = self.qc.compile(self.prog) bitstring_array = self.qc.run(executable=executable) self.state = {} for i, q in enumerate(qubits): self.state[q] = bitstring_array[:, i]
def test_validate_supported_quil_multiple_measures(): prog = Program( RESET(), H(1), Pragma('DELAY'), MEASURE(1, None), MEASURE(1, None) ) with pytest.raises(ValueError): validate_supported_quil(prog)
def hello_qmi(device_name: str = "9q-generic-qvm", shots: int = 5) -> None: """ Get acquainted with your quantum computer by asking it to perform a simple coin-toss experiment. Involve 3 qubits in this experiment, and ask each one to give `shots` many results. :param device_name: The name of a quantum computer which can be retrieved from `pyquil.api.get_qc()`. To find a list of all devices, you can use `pyquil.api.list_devices()`. """ # Initialize your Quil program program = Program() # Allow the compiler to re-index to use available qubits, if necessary. program += Pragma('INITIAL_REWIRING', ['"GREEDY"']) device = query_device(device_name) if device is not None: # device_name refers to a real (QPU) device, so let's construct # the program from the device's qubits. readout = program.declare('ro', 'BIT', len(device['qubits'])) for qubit in device['qubits'].values(): program += RX(math.pi / 2, qubit) for idx, qubit in enumerate(device['qubits'].values()): program += MEASURE(qubit, readout[idx]) else: # device_name refers to a non-real (QVM) device, so let's construct # the program from arbitrary qubits, e.g. 0, 1, and 2 # Declare 3 bits of memory space for the readout results of all three qubits readout = program.declare('ro', 'BIT', 3) # For each qubit, apply a pulse to move the qubit's state halfway between # the 0 state and the 1 state program += RX(math.pi / 2, 0) program += RX(math.pi / 2, 1) program += RX(math.pi / 2, 2) # Add measurement instructions to measure the qubits and record the result # into the respective bit in the readout register program += MEASURE(0, readout[0]) program += MEASURE(1, readout[1]) program += MEASURE(2, readout[2]) # This tells the program how many times to run the above sequence program.wrap_in_numshots_loop(shots) # Get the quantum computer we want to run our experiment on qc = get_qc(device_name) # Compile the program, specific to which quantum computer we are using compiled_program = qc.compile(program) # Run the program and get the shots x 3 array of results results = qc.run(compiled_program) # Print the results. We expect to see (shots x 3) random 0's and 1's print( f"Your{' virtual' if isinstance(qc.qam, QVM) else ''} quantum " f"computer, {device_name}, greets you with:\n", results)
def exponentiate_reward(pauli_sum, param): """ Calculates the exponential representation of the given QAOA problem hamiltonian term. This is separated out from the driver as it allows for manual "compilation" by setting the edges and their order to reflect the topology of any hardware the code is supposed to run on. :param pauli_sum: PauliSum of the problem hamiltonian :param param: parameter set (angles) for the problem hamiltonian :param edge_ordering: List of pairs of connected qubits in the desired order, or None for the default order. :return: pyQuil.Program for the exponential """ prog = Program() prog += Pragma('COMMUTING_BLOCKS') for term in pauli_sum.terms: prog += Pragma('BLOCK') prog += exponential_map(term)(param) prog += Pragma('END_BLOCK') prog += Pragma('END_COMMUTING_BLOCKS') return prog
def pre_measure(self): """Run the QVM""" # pylint: disable=attribute-defined-outside-init for e in self.obs_queue: wires = e.wires if e.name in [ "PauliX", "PauliY", "PauliZ", "Identity", "Hadamard" ]: self.pre_rotations(e.name, wires) elif e.name == "Hermitian": # For arbitrary Hermitian matrix H, let U be the unitary matrix # that diagonalises it, and w_i be the eigenvalues. H = e.parameters[0] Hkey = tuple(H.flatten().tolist()) if Hkey in self._eigs: # retrieve eigenvectors U = self._eigs[Hkey]["eigvec"] else: # store the eigenvalues corresponding to H # in a dictionary, so that they do not need to # be calculated later w, U = np.linalg.eigh(H) self._eigs[Hkey] = {"eigval": w, "eigvec": U} # Perform a change of basis before measuring by applying U^ to the circuit self.apply("QubitUnitary", wires, [U.conj().T]) prag = Program(Pragma("INITIAL_REWIRING", ['"PARTIAL"'])) if self.active_reset: prag += RESET() self.prog = prag + self.prog qubits = sorted(self.prog.get_qubits()) ro = self.prog.declare("ro", "BIT", len(qubits)) for i, q in enumerate(qubits): self.prog.inst(MEASURE(q, ro[i])) self.prog.wrap_in_numshots_loop(self.shots) if "pyqvm" in self.qc.name: bitstring_array = self.qc.run(self.prog) else: self.compiled = self.qc.compile(self.prog) bitstring_array = self.qc.run(executable=self.compiled) self.state = {} for i, q in enumerate(qubits): self.state[q] = bitstring_array[:, i]
def __init__(self, path: list, test_type: BellTestType, add_measurements, num_shots): super().__init__() assert len(path) >= 2, "qubit path has to have length at least 2" qubit_a = path[0] qubit_b = path[-1] self.qubit_a = qubit_a self.qubit_b = qubit_b self.path = path self.test_type = test_type self.add_measurements = add_measurements self.num_shots = num_shots # Build the circuit program = pq.Program() program += Pragma("INITIAL_REWIRING", ['"NAIVE"']) program += pq.gates.X(qubit_a) program += pq.gates.X(qubit_b) program += pq.gates.H(qubit_a) # CNOT along path for (x, y) in zip(path[:-2], path[1:-1]): program += pq.gates.CNOT(x, y) # CNOT the last pair program += pq.gates.CNOT(path[-2], qubit_b) # undo CNOT along path for (x, y) in reversed(list(zip(path[:-2], path[1:-1]))): program += pq.gates.CNOT(x, y) # measurement directions angle_a, angle_b = test_type.value if angle_a != 0: program += pq.gates.RZ(angle_a, qubit_a) if angle_b != 0: program += pq.gates.RZ(angle_b, qubit_b) # final hadamards program += pq.gates.H(qubit_a) program += pq.gates.H(qubit_b) # store the resulting circuit self.program = program
def test_pragma_with_placeholders(): q = QubitPlaceholder() q2 = QubitPlaceholder() p = Program() p.inst(Pragma('FENCE', [q, q2])) address_map = {q: 0, q2: 1} addressed_pragma = address_qubits(p, address_map)[0] parse_equals('PRAGMA FENCE 0 1\n', addressed_pragma) pq = Program(X(q)) pq.define_noisy_readout(q, .8, .9) pq.inst(X(q2)) pq.define_noisy_readout(q2, .9, .8) ret = address_qubits(pq, address_map).out() assert ret == """X 0
def apply(self, operations, **kwargs): """Run the QVM""" # pylint: disable=attribute-defined-outside-init super().apply(operations, **kwargs) prag = Program(Pragma("INITIAL_REWIRING", ['"PARTIAL"'])) if self.active_reset: prag += RESET() self.prog = prag + self.prog qubits = sorted(self.wiring.values()) ro = self.prog.declare("ro", "BIT", len(qubits)) for i, q in enumerate(qubits): self.prog.inst(MEASURE(q, ro[i])) self.prog.wrap_in_numshots_loop(self.shots)
def test_estimate_assignment_probs(): cxn = Mock(spec=QVMConnection) trials = 100 p00 = .8 p11 = .75 cxn.run.side_effect = [[[0]] * int(round(p00 * trials)) + [[1]] * int(round((1 - p00) * trials)), [[1]] * int(round(p11 * trials)) + [[0]] * int(round((1 - p11) * trials))] ap_target = np.array([[p00, 1 - p11], [1 - p00, p11]]) povm_pragma = Pragma("READOUT-POVM", (0, "({} {} {} {})".format(*ap_target.flatten()))) ap = estimate_assignment_probs(0, trials, cxn, Program(povm_pragma)) assert np.allclose(ap, ap_target) for call in cxn.run.call_args_list: args, kwargs = call prog = args[0] assert prog._instructions[0] == povm_pragma
def test_estimate_assignment_probs(mocker: MockerFixture): mock_qc = mocker.patch("pyquil.api.QuantumComputer").return_value mock_compiler = mocker.patch( "pyquil.api._abstract_compiler.AbstractCompiler").return_value trials = 100 p00 = 0.8 p11 = 0.75 mock_compiler.native_quil_to_executable.return_value = Program() mock_qc.compiler = mock_compiler mock_qc mock_qc.run.side_effect = [ QAMExecutionResult(executable=None, readout_data={ 'ro': np.array([[0]]) * int(round(p00 * trials)) + np.array([[1]]) * int(round((1 - p00) * trials)) }), # I gate results QAMExecutionResult(executable=None, readout_data={ 'ro': np.array([[1]]) * int(round(p11 * trials)) + np.array([[0]]) * int(round((1 - p11) * trials)) }), # X gate results ] ap_target = np.array([[p00, 1 - p11], [1 - p00, p11]]) povm_pragma = Pragma("READOUT-POVM", (0, "({} {} {} {})".format(*ap_target.flatten()))) ap = estimate_assignment_probs(0, trials, mock_qc, Program(povm_pragma)) assert mock_compiler.native_quil_to_executable.call_count == 2 assert mock_qc.run.call_count == 2 for call in mock_compiler.native_quil_to_executable.call_args_list: args, kwargs = call prog = args[0] assert prog._instructions[0] == povm_pragma assert np.allclose(ap, ap_target)
def test_pragma_with_placeholders(): q = QubitPlaceholder() q2 = QubitPlaceholder() p = Program() p.inst(Pragma("FENCE", [q, q2])) address_map = {q: 0, q2: 1} addressed_pragma = address_qubits(p, address_map)[0] parse_equals("PRAGMA FENCE 0 1\n", addressed_pragma) pq = Program(X(q)) pq.define_noisy_readout(q, 0.8, 0.9) pq.inst(X(q2)) pq.define_noisy_readout(q2, 0.9, 0.8) ret = address_qubits(pq, address_map).out() assert (ret == """X 0 PRAGMA READOUT-POVM 0 "(0.8 0.09999999999999998 0.19999999999999996 0.9)" X 1 PRAGMA READOUT-POVM 1 "(0.9 0.19999999999999996 0.09999999999999998 0.8)" """)
def test_fail_on_bad_pragmas(): "Check that to_latex raises an error when pragmas are imbalanced." # missing END_LATEX_GATE_GROUP with pytest.raises(ValueError): _ = to_latex(Program(Pragma("LATEX_GATE_GROUP", [], 'foo'), X(0))) # missing LATEX_GATE_GROUP with pytest.raises(ValueError): _ = to_latex(Program(X(0), Pragma("END_LATEX_GATE_GROUP"))) # nested groups are not currently supported with pytest.raises(ValueError): _ = to_latex( Program(Pragma("LATEX_GATE_GROUP"), X(0), Pragma("LATEX_GATE_GROUP"), X(1), Pragma("END_LATEX_GATE_GROUP"), Pragma("END_LATEX_GATE_GROUP")))
def adder(num_a: Sequence[int], num_b: Sequence[int], register_a: Sequence[int], register_b: Sequence[int], carry_ancilla: int, z_ancilla: int, in_x_basis: bool = False, use_param_program: bool = False) -> Program: """ Produces a program implementing reversible adding on a quantum computer to compute a + b. This implementation is based on [CDKM96]_, which is easy to implement, if not the most efficient. Each register of qubit labels should be provided such that the first qubit in each register is expected to carry the least significant bit of the respective number. This method also requires two extra ancilla, one initialized to 0 that acts as a dummy initial carry bit and another (which also probably ought be initialized to 0) that stores the most significant bit of the addition (should there be a final carry). The most straightforward ordering of the registers and two ancilla for adding n-bit numbers follows the pattern:: carry_ancilla b_0 a_0 ... b_j a_j ... b_n a_n z_ancilla With this layout, all gates in the circuit act on sets of three adjacent qubits. Such a layout is provided by calling get_qubit_registers_for_adder on the quantum resource. Note that even with this layout some of the gates used to implement the circuit may not be native. In particular there are CCNOT gates which must be decomposed and CNOT(q1, q3) gates acting on potentially non-adjacenct qubits (the layout only ensures q2 is adjacent to both q1 and q3). The output of the circuit falls on the qubits initially labeled by the b bits (and z_ancilla). The default option is to compute the addition in the computational (aka Z) basis. By setting in_x_basis true, the gates :func:`primitives.CNOT_X_basis` and :func:`primitives.CCNOT_X_basis` will replace CNOT and CCNOT so that the computation happens in the X basis. .. [CDKM96] "A new quantum ripple-carry addition circuit" S. Cuccaro, T. Draper, s. Kutin, D. Moulton https://arxiv.org/abs/quant-ph/0410184 :param num_a: the bitstring representation of the number a with least significant bit last :param num_b: the bitstring representation of the number b with least significant bit last :param register_a: list of qubit labels for register a, with least significant bit labeled first :param register_b: list of qubit labels for register b, with least significant bit labeled first :param carry_ancilla: qubit labeling a zero-initialized qubit, ideally adjacent to b_0 :param z_ancilla: qubit label, a zero-initialized qubit, ideally adjacent to register_a[-1] :param in_x_basis: if true, prepare the bitstring-representation of the numbers in the x basis and subsequently performs all addition logic in the x basis. :param use_param_program: if true, the input num_a and num_b should be arrays of the proper length, but their contents will be disregarded. Instead, the program returned will be parameterized and the input bitstrings to add must be specified at run time. :return: pyQuil program that implements the addition a+b, with output falling on the qubits formerly storing the input b. The output of a measurement will list the lsb as the last bit. """ if len(num_a) != len(num_b): raise ValueError("Numbers being added must be equal length bitstrings") # First, generate a set preparation program in the desired basis. prog = Program(Pragma('PRESERVE_BLOCK')) if use_param_program: input_register = register_a + register_b prog += parameterized_bitstring_prep(input_register[::-1], REG_NAME, in_x_basis=in_x_basis) else: prog += bitstring_prep(register_a, num_a[::-1], in_x_basis=in_x_basis) prog += bitstring_prep(register_b, num_b[::-1], in_x_basis=in_x_basis) if in_x_basis: prog += [H(carry_ancilla), H(z_ancilla)] # preparation complete; end the preserve block prog += Pragma("END_PRESERVE_BLOCK") prog_to_rev = Program() current_carry_label = carry_ancilla for (a, b) in zip(register_a, register_b): prog += majority_gate(a, b, current_carry_label, in_x_basis) prog_to_rev += unmajority_add_gate(a, b, current_carry_label, in_x_basis).dagger() current_carry_label = a undo_and_add_prog = prog_to_rev.dagger() if in_x_basis: prog += CNOT_X_basis(register_a[-1], z_ancilla) # need to switch back to computational (z) basis before measuring for qubit in register_b: # answer lays on the b qubit register undo_and_add_prog.inst(H(qubit)) undo_and_add_prog.inst(H(z_ancilla)) else: prog += CNOT(register_a[-1], z_ancilla) prog += undo_and_add_prog ro = prog.declare('ro', memory_type='BIT', memory_size=len(register_b) + 1) for idx, qubit in enumerate(register_b): prog += MEASURE(qubit, ro[len(register_b) - idx]) prog += MEASURE(z_ancilla, ro[0]) return prog
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
import numpy as np from pyquil import get_qc from pyquil.quil import Program, Pragma from pyquil.gates import RX, RZ, X import tqdm # Get our QuantumComputer instance, with a Quantum Virutal Machine (QVM) backend qpu_name = "8q-qvm" # 8q-qvm Aspen-8 asqvm = True QPU = get_qc(qpu_name, as_qvm=asqvm) calibration_trials = 2**10 measurement_trials = 2**10 batches = 2**2 program_initialization = Program(Pragma('INITIAL_REWIRING', ['"NAIVE"'])) """ defining ansatz """ def calibration_ansatz(state, prog_in=program_initialization): prog_out = prog_in.copy() if state == 1: prog_out += X(0) return prog_out def ansatz(angle, prog_in=program_initialization): prog_out = prog_in.copy() prog_out += RX(angle, 0)
def _naive_program_generator(qc: QuantumComputer, qubits: Sequence[int], permutations: Sequence[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. """ # artificially restrict the entire computation to num_measure_qubits num_measure_qubits = len(permutations[0]) # if these measure_qubits do not have a topology that supports the program, the compiler may # act on a different (potentially larger) subset of the input sequence of qubits. measure_qubits = qubits[:num_measure_qubits] # create a simple program that uses the compiler to directly generate 2q gates from the matrices prog = Program(Pragma('INITIAL_REWIRING', ['"PARTIAL"'])) 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(measure_qubits[perm[gate_idx]]), int(measure_qubits[perm[gate_idx + 1]])) ro = prog.declare("ro", "BIT", num_measure_qubits) for idx, qubit in enumerate(measure_qubits): prog.measure(qubit, ro[idx]) # restrict compilation to chosen qubits isa_dict = qc.device.get_isa().to_dict() single_qs = isa_dict['1Q'] two_qs = isa_dict['2Q'] new_1q = {} for key, val in single_qs.items(): if int(key) in qubits: new_1q[key] = val new_2q = {} for key, val in two_qs.items(): q1, q2 = key.split('-') if int(q1) in qubits and int(q2) in qubits: new_2q[key] = val new_isa = {'1Q': new_1q, '2Q': new_2q} new_compiler = copy(qc.compiler) new_compiler.target_device = TargetDevice( isa=new_isa, specs=qc.device.get_specs().to_dict()) # try to compile with the restricted qubit topology try: native_quil = new_compiler.quil_to_native_quil(prog) except RPCErrorError as e: if "Multiqubit instruction requested between disconnected components of the QPU graph:" \ in str(e): raise ValueError( "naive_program_generator could not generate a program using only the " "qubits supplied; expand the set of allowed qubits or supply " "a custom program_generator.") raise return native_quil
def convert_dds_to_pyquil_program(dynamic_decoupling_sequence, target_qubits=None, gate_time=0.1, add_measurement=True, algorithm=INSTANT_UNITARY): """Converts a Dynamic Decoupling Sequence into quantum program as defined in pyQuil. Parameters ---------- dynamic_decoupling_sequence : DynamicDecouplingSequence The dynamic decoupling sequence target_qubits : list, optional List of integers specifying target qubits for the sequence operation; defaults to None in which case 0-th Qubit is used gate_time : float, optional Time (in seconds) delay introduced by a gate; defaults to 0.1 add_measurement : bool, optional If True, the circuit contains a measurement operation for each of the target qubits and a set of ClassicalRegister objects created with length equal to `len(target_qubits)` algorithm : str, optional One of 'fixed duration unitary' or 'instant unitary'; In the case of 'fixed duration unitary', the sequence operations are assumed to be taking the amount of gate_time while 'instant unitary' assumes the sequence operations are instantaneous (and hence does not contribute to the delay between offsets). Defaults to 'instant unitary'. Returns ------- pyquil.Program The pyQuil program containing gates specified by the rotations of dynamic decoupling sequence Raises ------ ArgumentsValueError If any of the input parameters are invalid Notes ----- Dynamic Decoupling Sequences (DDS) consist of idealized pulse operation. Theoretically, these operations (pi-pulses in X,Y or Z) occur instantaneously. However, in practice, pulses require time. Therefore, this method of converting an idealized sequence results to a circuit that is only an approximate implementation of the idealized sequence. In idealized definition of DDS, `offsets` represents the instances within sequence `duration` where a pulse occurs instantaneously. A series of appropriate gatges is placed in order to represent these pulses. The `gaps` or idle time in between active pulses are filled up with `identity` gates. Each identity gate introduces a delay of `gate_time`. In this implementation, the number of identity gates is determined by :math:`np.int(np.floor(offset_distance / gate_time))`. As a consequence, the duration of the real-circuit is :math:`gate_time \\times number_of_identity_gates + pulse_gate_time \\times number_of_pulses`. Q-CTRL Open Controls support operation resulting in rotation around at most one axis at any offset. """ if dynamic_decoupling_sequence is None: raise ArgumentsValueError( 'No dynamic decoupling sequence provided.', {'dynamic_decoupling_sequence': dynamic_decoupling_sequence}) if not isinstance(dynamic_decoupling_sequence, DynamicDecouplingSequence): raise ArgumentsValueError( 'Dynamical decoupling sequence is not recognized.' 'Expected DynamicDecouplingSequence instance', { 'type(dynamic_decoupling_sequence)': type(dynamic_decoupling_sequence) }) target_qubits = target_qubits or [0] if gate_time <= 0: raise ArgumentsValueError( 'Time delay of identity gate must be greater than zero.', {'gate_time': gate_time}) if np.any(target_qubits) < 0: raise ArgumentsValueError( 'Every target qubits index must be non-negative.', {'target_qubits': target_qubits}) if algorithm not in [FIX_DURATION_UNITARY, INSTANT_UNITARY]: raise ArgumentsValueError( 'Algorithm must be one of {} or {}'.format(INSTANT_UNITARY, FIX_DURATION_UNITARY), {'algorithm': algorithm}) unitary_time = 0. if algorithm == FIX_DURATION_UNITARY: unitary_time = gate_time rabi_rotations = dynamic_decoupling_sequence.rabi_rotations azimuthal_angles = dynamic_decoupling_sequence.azimuthal_angles detuning_rotations = dynamic_decoupling_sequence.detuning_rotations offsets = dynamic_decoupling_sequence.offsets time_covered = 0 program = Program() program += Pragma('PRESERVE_BLOCK') for offset, rabi_rotation, azimuthal_angle, detuning_rotation in zip( list(offsets), list(rabi_rotations), list(azimuthal_angles), list(detuning_rotations)): offset_distance = offset - time_covered if np.isclose(offset_distance, 0.0): offset_distance = 0.0 if offset_distance < 0: raise ArgumentsValueError( "Offsets cannot be placed properly. Spacing between the rotations" "is smaller than the time required to perform the rotation. Provide" "a longer dynamic decoupling sequence or shorted gate time.", { 'dynamic_decoupling_sequence': dynamic_decoupling_sequence, 'gate_time': gate_time }) while (time_covered + gate_time) <= offset: for qubit in target_qubits: program += I(qubit) time_covered += gate_time x_rotation = rabi_rotation * np.cos(azimuthal_angle) y_rotation = rabi_rotation * np.sin(azimuthal_angle) z_rotation = detuning_rotation rotations = np.array([x_rotation, y_rotation, z_rotation]) zero_pulses = np.isclose(rotations, 0.0).astype(np.int) nonzero_pulse_counts = 3 - np.sum(zero_pulses) if nonzero_pulse_counts > 1: raise ArgumentsValueError( 'Open Controls support a sequence with one ' 'valid rotation at any offset. Found a sequence ' 'with multiple rotation operations at an offset.', {'dynamic_decoupling_sequence': dynamic_decoupling_sequence}, extras={ 'offset': offset, 'rabi_rotation': rabi_rotation, 'azimuthal_angle': azimuthal_angle, 'detuning_rotation': detuning_rotation }) for qubit in target_qubits: if nonzero_pulse_counts == 0: program += I(qubit) else: if not np.isclose(rotations[0], 0.0): program += RX(rotations[0], qubit) elif not np.isclose(rotations[1], 0.0): program += RY(rotations[1], qubit) elif not np.isclose(rotations[2], 0.): program += RZ(rotations[2], qubit) time_covered = offset + unitary_time if add_measurement: readout = program.declare('ro', 'BIT', len(target_qubits)) for idx, qubit in enumerate(target_qubits): program += MEASURE(qubit, readout[idx]) program += Pragma('END_PRESERVE_BLOCK') return program
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