def create_graph_state(graph: nx.Graph, use_pragmas=True): """Write a program to create a graph state according to the specified graph A graph state involves Hadamarding all your qubits and then applying a CZ for each edge in the graph. A graph state and the ability to measure it however you want gives you universal quantum computation. Some good references are [MBQC] A One-Way Quantum Computer Raussendorf et al., Phys. Rev. Lett. 86, 5188 (2001) https://doi.org/10.1103/PhysRevLett.86.5188 https://arxiv.org/abs/quant-ph/0010033 [MBCS] Measurement-based quantum computation with cluster states Raussendorf et al., Phys. Rev. A 68, 022312 (2003) https://dx.doi.org/10.1103/PhysRevA.68.022312 https://arxiv.org/abs/quant-ph/0301052 Similar to a Bell state / GHZ state, we can try to prepare a graph state and measure how well we've done according to expected parities. :param graph: The graph. Nodes are used as arguments to gates, so they should be qubit-like. :param use_pragmas: Use COMMUTING_BLOCKS pragmas to hint at the compiler :return: A program that constructs a graph state. """ program = Program() for q in graph.nodes: program += H(q) if use_pragmas: program += Pragma('COMMUTING_BLOCKS') for a, b in graph.edges: if use_pragmas: program += Pragma('BLOCK') program += CZ(a, b) if use_pragmas: program += Pragma('END_BLOCK') if use_pragmas: program += Pragma('END_COMMUTING_BLOCKS') return program
def pragma_freeform_string(self, name, *pragma_names_and_string): if len(pragma_names_and_string) == 1: freeform_string = pragma_names_and_string[0] args = () else: *args_identifiers, freeform_string = pragma_names_and_string args = list(map(str, args_identifiers)) # Strip the quotes from start/end of string which are matched # by the Lark grammar freeform_string = freeform_string[1:-1] p = Pragma(str(name), args=args, freeform_string=freeform_string) return p
def basis_state_preps(*qubits): """ Generate a sequence of programs that prepares the measurement basis states of some set of qubits in the order such that the qubit with highest index is iterated over the most quickly: E.g., for ``qubits=(0, 1)``, it returns the circuits:: I_0 I_1 I_0 X_1 X_0 I_1 X_0 X_1 :param list qubits: Each qubit to include in the basis state preparation. :return: Yields programs for each basis state preparation. :rtype: Program """ for prep in cartesian_product([I, X], repeat=len(qubits)): basis_prep = Program(Pragma("PRESERVE_BLOCK")) for gate, qubit in zip(prep, qubits): basis_prep.inst(gate(qubit)) basis_prep.inst(Pragma("END_PRESERVE_BLOCK")) yield basis_prep
def _do_tomography(target_program, nsamples, cxn, qubits, max_num_qubits, tomography_class, program_generator, settings): """ :param Program target_program: The program to run to generate the state or process. :param int nsamples: The number of samples to take per measurement. :param QPUConnection|QVMConnection cxn: The connection to Forest. :param lists qubits: The list of qubits to perform tomography on. :param int max_num_qubits: The maximum allowed number of qubits. :param type tomography_class: The type of tomography to perform. :param function program_generator: The function that yields the tomography experiments. :param TomographySettings settings: The settings for running the optimizer. :return: The tomography result, the assignment probabilities, and the histograms of counts measured. :rtype: tuple """ if qubits is None: qubits = sorted(target_program.get_qubits()) num_qubits = len(qubits) dimension = 2**num_qubits if num_qubits > max_num_qubits: raise ValueError("Too many qubits!") assignment_probs = sample_assignment_probs(qubits, nsamples, cxn) tomo_seq = list(program_generator(target_program, qubits)) histograms = np.zeros((len(tomo_seq), dimension)) for i, tomo_prog in izip(ut.TRANGE(len(tomo_seq)), tomo_seq): tomo_prog = Program(Pragma("PRESERVE_BLOCK")) + tomo_prog results = cxn.run_and_measure(tomo_prog, qubits, nsamples) idxs = list(map(bitlist_to_int, results)) histograms[i] = ut.make_histogram(idxs, dimension) povm = o_ut.make_diagonal_povm( grove.tomography.operator_utils.POVM_PI_BASIS**num_qubits, assignment_probs) channel_ops = list(default_channel_ops(num_qubits)) # Currently the analysis pathways are slightly different, so we branch on which type of # tomography is being done. if tomography_class.__tomography_type__ == "PROCESS": histograms = histograms.reshape( (len(channel_ops), len(channel_ops), dimension)) tomo_result = tomography_class.estimate_from_ssr( histograms, povm, channel_ops, channel_ops, settings) else: tomo_result = tomography_class.estimate_from_ssr( histograms, povm, channel_ops, settings) return tomo_result, assignment_probs, histograms
def test_transform_with_post_transformation_hooks( bell_circuit_with_qids: Tuple[cirq.Circuit, List[cirq.Qid]], ) -> None: """test that a user can transform a `cirq.Circuit` to a `pyquil.Program` functionally with explicit physical qubit address mapping. """ bell_circuit, qubits = bell_circuit_with_qids def reset_hook(program, measurement_id_map): program._instructions.insert(0, Reset()) return program, measurement_id_map reset_hook_spec = create_autospec( reset_hook, side_effect=reset_hook, ) pragma = Pragma('INTIAL_REWIRING', freeform_string='GREEDY') def rewire_hook(program, measurement_id_map): program._instructions.insert(0, pragma) return program, measurement_id_map rewire_hook_spec = create_autospec( rewire_hook, side_effect=rewire_hook, ) transformer = transformers.build( qubits=tuple(qubits), post_transformation_hooks=[reset_hook_spec, rewire_hook_spec], ) program, _ = transformer(circuit=bell_circuit) assert 1 == reset_hook_spec.call_count assert Reset() in program.instructions, "hook should add reset" assert 1 == rewire_hook_spec.call_count assert pragma in program.instructions, "hook should add pragma" assert H(0) in program.instructions, "bell circuit should include Hadamard" assert CNOT(0, 1) in program.instructions, "bell circuit should include CNOT" assert ( DECLARE("m0", memory_size=2) in program.instructions ), "executable should declare a read out bit" assert ( MEASURE(0, ("m0", 0)) in program.instructions ), "executable should measure the first qubit to the first read out bit" assert ( MEASURE(1, ("m0", 1)) in program.instructions ), "executable should measure the second qubit to the second read out bit"
def state_tomography_programs(state_prep, qubits=None, rotation_generator=tomography.default_rotations): """ Yield tomographic sequences that prepare a state with Quil program `state_prep` and then append tomographic rotations on the specified `qubits`. If `qubits is None`, it assumes all qubits in the program should be tomographically rotated. :param Program state_prep: The program to prepare the state to be tomographed. :param list|NoneType qubits: A list of Qubits or Numbers, to perform the tomography on. If `None`, performs it on all in state_prep. :param generator rotation_generator: A generator that yields tomography rotations to perform. :return: Program for state tomography. :rtype: Program """ if qubits is None: qubits = state_prep.get_qubits() for tomography_program in rotation_generator(*qubits): state_tomography_program = Program(Pragma("PRESERVE_BLOCK")) state_tomography_program.inst(state_prep) state_tomography_program.inst(tomography_program) state_tomography_program.inst(Pragma("END_PRESERVE_BLOCK")) yield state_tomography_program
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 _get_flipped_protoquil_program(program: Program) -> Program: """For symmetrization, generate a program where X gates are added before measurement. Forest is picky about where the measure instructions happen. It has to be at the end! """ program = program.copy() to_measure = [] while len(program) > 0: inst = program.instructions[-1] if isinstance(inst, Measurement): program.pop() to_measure.append((inst.qubit, inst.classical_reg)) else: break program += Pragma('PRESERVE_BLOCK') for qu, addr in to_measure[::-1]: program += RX(pi, qu) program += Pragma('END_PRESERVE_BLOCK') for qu, addr in to_measure[::-1]: program += Measurement(qubit=qu, classical_reg=addr) return program
def _create_kraus_pragmas(name, qubit_indices, kraus_ops): """ Generate the pragmas to define a Kraus map for a specific gate on some qubits. :param str name: The name of the gate. :param list|tuple qubit_indices: The qubits :param list|tuple kraus_ops: The Kraus operators as matrices. :return: A QUIL string with PRAGMA ADD-KRAUS ... statements. :rtype: str """ pragmas = [Pragma("ADD-KRAUS", [name] + list(qubit_indices), "({})".format(" ".join(map(format_parameter, np.ravel(k))))) for k in kraus_ops] return pragmas
def generate_t2_echo_experiments( qubits: Sequence[int], times: Sequence[float], detuning: float = 1e6) -> List[ObservablesExperiment]: """ Return ObservablesExperiments containing programs which constitute a T2 echo experiment to measure the T2 echo coherence decay time. For each delay time in times a single program will be generated in which all qubits are initialized to the minusY state and later simultaneously measured along the Y axis. Unlike in the t2_star experiment above there is a 'echo' applied in the middle of the delay in which the qubit is rotated by pi radians around the Y axis. Similarly to t2_star, if the qubit frequency is perfectly calibrated then the Y expectation will oscillate at the given detuning frequency as the qubit is rotated about the Z axis (with respect to the lab frame, which by hypothesis matches the natural qubit frame). Unlike in a t2_star experiment, even if the qubit frequency is off such that there is some spurious rotation about the Z axis during the DELAY, the effect of an ideal echo is to cancel the effect of this rotation so that the qubit returns to the initial state minusY before the detuning rotation is applied. :param qubits: list of qubits to measure. :param times: the times at which to measure, given in seconds. Each time is rounded to the nearest .1 microseconds. :param detuning: The additional detuning frequency about the z axis. :return: ObservablesExperiments which can be run to acquire an estimate of T2 for each qubit. """ expts = [] for t in times: half_time = round(t / 2, 7) # enforce 100ns boundaries t = round(t, 7) program = Program() settings = [] for q in qubits: half_delay = Pragma('DELAY', [q], str(half_time)) # echo program += [half_delay, RY(pi, q), half_delay] # apply detuning program += RZ(2 * pi * t * detuning, q) settings.append(ExperimentSetting(minusY(q), PauliTerm('Y', q))) expts.append(ObservablesExperiment(settings, program)) return expts
def _create_kraus_pragmas(name: str, qubit_indices: Sequence[int], kraus_ops: Sequence[np.ndarray]) -> List[Pragma]: """ Generate the pragmas to define a Kraus map for a specific gate on some qubits. :param name: The name of the gate. :param qubit_indices: The qubits :param kraus_ops: The Kraus operators as matrices. :return: A QUIL string with PRAGMA ADD-KRAUS ... statements. """ pragmas = [ Pragma( "ADD-KRAUS", (name, ) + tuple(qubit_indices), "({})".format(" ".join(map(format_parameter, np.ravel(k)))), ) for k in kraus_ops ] return pragmas
def test_pragma(): _test('PRAGMA gate_time H "10 ns"', Pragma('gate_time', ['H'], '10 ns')) _test('PRAGMA qubit 0', Pragma('qubit', [0])) _test('PRAGMA NO-NOISE', Pragma('NO-NOISE'))
def pragma(self, name, *pragma_names_and_string): args = list(map(str, pragma_names_and_string)) p = Pragma(str(name), args=args) return p
def test_pragma(): parse_equals('PRAGMA gate_time H "10 ns"', Pragma('gate_time', ['H'], '10 ns')) parse_equals('PRAGMA qubit 0', Pragma('qubit', [0])) parse_equals('PRAGMA NO-NOISE', Pragma('NO-NOISE'))
from unittest.mock import patch from pyquil.api import QVMConnection, QPUConnection, CompilerConnection from pyquil.api._base_connection import validate_noise_probabilities, validate_run_items from pyquil.api.qpu import append_measures_to_program from pyquil.quil import Program from pyquil.paulis import PauliTerm from pyquil.gates import CNOT, H, MEASURE, PHASE, Z, RZ, RX, CZ from pyquil.device import ISA from pyquil.quilbase import Pragma EMPTY_PROGRAM = Program() BELL_STATE = Program(H(0), CNOT(0, 1)) BELL_STATE_MEASURE = Program(H(0), CNOT(0, 1), MEASURE(0, 0), MEASURE(1, 1)) COMPILED_BELL_STATE = Program([ Pragma("EXPECTED_REWIRING", ('"#(0 1 2 3)"',)), RZ(pi / 2, 0), RX(pi / 2, 0), RZ(-pi / 2, 1), RX(pi / 2, 1), CZ(1, 0), RZ(-pi / 2, 0), RX(-pi / 2, 1), RZ(pi / 2, 1), Pragma("CURRENT_REWIRING", ('"#(0 1 2 3)"',)), Pragma("EXPECTED_REWIRING", ('"#(0 1 2 3)"',)), Pragma("CURRENT_REWIRING", ('"#(0 1 2 3)"',)), ]) DUMMY_ISA_DICT = {"1Q": {"0": {}, "1": {}}, "2Q": {"0-1": {}}} DUMMY_ISA = ISA.from_dict(DUMMY_ISA_DICT)
def address_qubits(program, qubit_mapping=None): """ Takes a program which contains placeholders and assigns them all defined values. Either all qubits must be defined or all undefined. If qubits are undefined, you may provide a qubit mapping to specify how placeholders get mapped to actual qubits. If a mapping is not provided, integers 0 through N are used. This function will also instantiate any label placeholders. :param program: The program. :param qubit_mapping: A dictionary-like object that maps from :py:class:`QubitPlaceholder` to :py:class:`Qubit` or ``int`` (but not both). :return: A new Program with all qubit and label placeholders assigned to real qubits and labels. """ fake_qubits, real_qubits, qubits = _what_type_of_qubit_does_it_use(program) if real_qubits: if qubit_mapping is not None: warnings.warn( "A qubit mapping was provided but the program does not " "contain any placeholders to map!") return program if qubit_mapping is None: qubit_mapping = {qp: Qubit(i) for i, qp in enumerate(qubits)} else: if all(isinstance(v, Qubit) for v in qubit_mapping.values()): pass # we good elif all(isinstance(v, int) for v in qubit_mapping.values()): qubit_mapping = {k: Qubit(v) for k, v in qubit_mapping.items()} else: raise ValueError( "Qubit mapping must map to type Qubit or int (but not both)") result = [] for instr in program: # Remap qubits on Gate, Measurement, and ResetQubit instructions if isinstance(instr, Gate): remapped_qubits = [qubit_mapping[q] for q in instr.qubits] gate = Gate(instr.name, instr.params, remapped_qubits) gate.modifiers = instr.modifiers result.append(gate) elif isinstance(instr, Measurement): result.append( Measurement(qubit_mapping[instr.qubit], instr.classical_reg)) elif isinstance(instr, ResetQubit): result.append(ResetQubit(qubit_mapping[instr.qubit])) elif isinstance(instr, Pragma): new_args = [] for arg in instr.args: # Pragmas can have arguments that represent things besides qubits, so here we # make sure to just look up the QubitPlaceholders. if isinstance(arg, QubitPlaceholder): new_args.append(qubit_mapping[arg]) else: new_args.append(arg) result.append( Pragma(instr.command, new_args, instr.freeform_string)) # Otherwise simply add it to the result else: result.append(instr) new_program = program.copy() new_program._instructions = result return new_program
def test_pragma(): parse_equals('PRAGMA gate_time H "10 ns"', Pragma("gate_time", ["H"], "10 ns")) parse_equals("PRAGMA qubit 0", Pragma("qubit", [0])) parse_equals("PRAGMA NO-NOISE", Pragma("NO-NOISE"))
def fill_placeholders(obj, placeholder_values: Dict[Union[FormalArgument, Parameter], Any]): """Update Parameter and FormalArgument references in objects with their corresponding definitions. It is an error if the object has a Parameter or FormalArgument reference without a corresponding definition in placeholder_values. :param obj: A Quil AST object. :param placeholder_values: A dictionary mapping placeholders to their values. :returns: The updated AST object. """ try: if obj is None or isinstance( obj, (int, float, complex, Qubit, MemoryReference)): return obj elif isinstance(obj, Expression): # defer to the usual PyQuil substitution return substitute( obj, { k: v for k, v in placeholder_values.items() if isinstance(k, Parameter) }) elif isinstance(obj, FormalArgument): return placeholder_values[obj] elif isinstance(obj, Frame): return Frame(fill_placeholders(obj.qubits, placeholder_values), obj.name) elif isinstance(obj, WaveformReference): return obj elif isinstance(obj, TemplateWaveform): return obj.__class__( **fill_placeholders(obj.__dict__, placeholder_values)) elif isinstance(obj, list): return [fill_placeholders(elt, placeholder_values) for elt in obj] elif isinstance(obj, dict): return { k: fill_placeholders(v, placeholder_values) for (k, v) in obj.items() } elif isinstance(obj, tuple): return tuple( [fill_placeholders(item, placeholder_values) for item in obj]) elif isinstance(obj, Pragma) and obj.command == "LOAD-MEMORY": (source, ) = obj.args arg = FormalArgument(obj.freeform_string) if arg in placeholder_values: return Pragma("LOAD-MEMORY", [source], str(placeholder_values[arg])) else: return obj else: specs = { Gate: ["params", "qubits"], Measurement: ["qubit", "classical_reg"], ResetQubit: ["qubit"], Pulse: ["frame", "waveform"], SetFrequency: ["frame", "freq"], ShiftFrequency: ["frame", "freq"], SetPhase: ["frame", "phase"], ShiftPhase: ["frame", "phase"], SwapPhase: ["frameA", "frameB"], SetScale: ["frame", "scale"], Capture: ["frame", "kernel", "memory_region"], RawCapture: ["frame", "duration", "memory_region"], DelayQubits: ["qubits", "duration"], DelayFrames: ["frames", "duration"], Fence: ["qubits"], FenceAll: [], Declare: [], Pragma: [], } if type(obj) in specs: attrs = specs[type(obj)] updated = copy(obj) for attr in attrs: setattr( updated, attr, fill_placeholders(getattr(updated, attr), placeholder_values)) return updated else: raise CalibrationError( f"Unable to fill placeholders in object {obj}.") except Exception as e: raise e