def test_local_conjugate_request(benchmarker): config = PyquilConfig() if config.compiler_url is not None: cxn = BenchmarkConnection(endpoint=config.compiler_url) response = cxn.apply_clifford_to_pauli(Program("H 0"), PauliTerm("X", 0, 1.0)) assert isinstance(response, PauliTerm) assert str(response) == "(1+0j)*Z0"
def test_local_rb_sequence(benchmarker): config = PyquilConfig() if config.compiler_url is not None: cxn = BenchmarkConnection(endpoint=config.compiler_url) response = cxn.generate_rb_sequence(2, [PHASE(np.pi / 2, 0), H(0)], seed=52) assert [prog.out() for prog in response] == \ ["H 0\nPHASE(pi/2) 0\nH 0\nPHASE(pi/2) 0\nPHASE(pi/2) 0\n", "H 0\nPHASE(pi/2) 0\nH 0\nPHASE(pi/2) 0\nPHASE(pi/2) 0\n"]
def _exhaustive_dfe(program: Program, qubits: Sequence[int], in_states, benchmarker: BenchmarkConnection) -> ExperimentSetting: """Yield experiments over itertools.product(in_paulis). Used as a helper function for generate_exhaustive_xxx_dfe_experiment routines. :param program: A program comprised of clifford gates :param qubits: The qubits to perform DFE on. This can be a superset of the qubits used in ``program``. :param in_states: Use these single-qubit Pauli operators in every itertools.product() to generate an exhaustive list of DFE experiments. :return: experiment setting iterator :rtype: ``ExperimentSetting`` """ n_qubits = len(qubits) for i_states in itertools.product(in_states, repeat=n_qubits): i_st = functools.reduce(mul, (op(q) for op, q in zip(i_states, qubits) if op is not None), TensorProductState()) if len(i_st) == 0: continue yield ExperimentSetting( in_state=i_st, out_operator=benchmarker.apply_clifford_to_pauli(program, _state_to_pauli(i_st)), )
def _monte_carlo_dfe(program: Program, qubits: Sequence[int], in_states: list, n_terms: int, benchmarker: BenchmarkConnection) -> ExperimentSetting: """Yield experiments over itertools.product(in_paulis). Used as a helper function for generate_monte_carlo_xxx_dfe_experiment routines. :param program: A program comprised of clifford gates :param qubits: The qubits to perform DFE on. This can be a superset of the qubits used in ``program``. :param in_states: Use these single-qubit Pauli operators in every itertools.product() to generate an exhaustive list of DFE experiments. :param n_terms: Number of preparation and measurement settings to be chosen at random :return: experiment setting iterator :rtype: ``ExperimentSetting`` """ all_st_inds = np.random.randint(len(in_states), size=(n_terms, len(qubits))) for st_inds in all_st_inds: i_st = functools.reduce(mul, (in_states[si](qubits[i]) for i, si in enumerate(st_inds) if in_states[si] is not None), TensorProductState()) # TODO: we should not pick a new one, we should just return a trivial experiment while len(i_st) == 0: # pick a new one second_try_st_inds = np.random.randint(len(in_states), size=len(qubits)) i_st = functools.reduce(mul, (in_states[si](qubits[i]) for i, si in enumerate(second_try_st_inds) if in_states[si] is not None), TensorProductState()) yield ExperimentSetting( in_state=i_st, out_operator=benchmarker.apply_clifford_to_pauli(program, _state_to_pauli(i_st)), )
def generate_exhaustive_state_dfe_experiment( benchmarker: BenchmarkConnection, program: Program, qubits: list) -> ObservablesExperiment: """ Estimate state fidelity by exhaustive direct fidelity estimation. This leads to a quadratic reduction in overhead w.r.t. state tomography for fidelity estimation. The algorithm is due to [DFE1]_ and [DFE2]_. :param benchmarker: object returned from pyquil.api.get_benchmarker() used to conjugate each Pauli by the Clifford program :param program: A program comprised of Clifford group gates that constructs a state for which we estimate the fidelity. :param qubits: The qubits to perform DFE on. This can be a superset of the qubits used in ``program``, in which case it is assumed the identity acts on these qubits. Note that we assume qubits are initialized to the ``|0>`` state. :return: an ObservablesExperiment that constitutes a state DFE experiment. """ # measure all of the traceless combinations of I and Z on the qubits conjugated by the ideal # Clifford state preparation program. The in_state is all the all zero state since this is # the assumed initialization of the state preparation. settings = [ ExperimentSetting(in_state=zeros_state(qubits), observable=benchmarker.apply_clifford_to_pauli( program, iz_pauli)) for iz_pauli in all_traceless_pauli_z_terms(qubits) ] return ObservablesExperiment(settings, program=program)
def generate_monte_carlo_state_dfe_experiment(benchmarker: BenchmarkConnection, program: Program, qubits: List[int], n_terms=200) \ -> ObservablesExperiment: """ Estimate state fidelity by sampled direct fidelity estimation. This leads to constant overhead (w.r.t. number of qubits) fidelity estimation. The algorithm is due to [DFE1]_ and [DFE2]_. :param program: A program comprised of clifford gates that constructs a state for which we estimate the fidelity. :param qubits: The qubits to perform DFE on. This can be a superset of the qubits used in ``program``, in which case it is assumed the identity acts on these qubits. Note that we assume qubits are initialized to the ``|0>`` state. :param benchmarker: The `BenchmarkConnection` object used to design experiments :param n_terms: Number of randomly chosen observables to measure. This number should be a constant less than ``2**len(qubits)``, otherwise ``exhaustive_state_dfe`` is more efficient. :return: an ObservablesExperiment that constitutes a state DFE experiment. """ # pick n_terms different random combinations of I and Z on the qubits rand_iz_paulis = np.random.choice(['I', 'Z'], size=(n_terms, len(qubits))) settings = [] for iz_pauli in rand_iz_paulis: # sample a new state if this one is all identity while 'Z' not in iz_pauli: iz_pauli = np.random.choice(['I', 'Z'], size=len(qubits)) # conjugate the non-trivial iz Pauli by the ideal state prep program obs = benchmarker.apply_clifford_to_pauli( program, str_to_pauli_term(''.join(iz_pauli))) settings.append(ExperimentSetting(zeros_state(qubits), obs)) return ObservablesExperiment(settings, program=program)
def generate_simultaneous_rb_sequence( bm: BenchmarkConnection, subgraph: list, depth: int, random_seed: int = None, interleaved_gate: Program = None) -> list: """ Generates a Simultaneous RB Sequence -- a list of Programs where each Program performs a simultaneous Clifford on the given subgraph (single qubit or pair of qubits), and where the execution of all the Programs composes to the Identity on all edges. :param bm: A benchmark connection that will do the grunt work of generating the sequences :param subgraph: Iterable of tuples of integers specifying qubit singletons or pairs :param depth: The total number of Cliffords to perform on all edges (including inverse) :param random_seed: Base random seed used to seed compiler for sequence generation for each subgraph element :param interleaved_gate: Gate to interleave in between Cliffords; used for interleaved RB experiment :return: RB Sequence as a list of Programs """ if depth < 2: raise ValueError( "Sequence depth must be at least 2 for rb sequences, or at least 1 for unitarity sequences." ) size = len(subgraph[0]) assert all([len(x) == size for x in subgraph]) if size == 1: q_placeholders = QubitPlaceholder().register(n=1) gateset = list(oneq_rb_gateset(*q_placeholders)) elif size == 2: q_placeholders = QubitPlaceholder.register(n=2) gateset = list(twoq_rb_gateset(*q_placeholders)) else: raise ValueError("Subgraph elements must have length 1 or 2.") sequences = [] for j, qubits in enumerate(subgraph): if random_seed is not None: sequence = bm.generate_rb_sequence(depth=depth, gateset=gateset, seed=random_seed + j, interleaver=interleaved_gate) else: sequence = bm.generate_rb_sequence(depth=depth, gateset=gateset, interleaver=interleaved_gate) qubit_map = {qp: qid for (qp, qid) in zip(q_placeholders, qubits)} sequences.append( [address_qubits(prog, qubit_map) for prog in sequence]) return merge_sequences(sequences)
def generate_exhaustive_process_dfe_experiment( benchmarker: BenchmarkConnection, program: Program, qubits: list) -> ObservablesExperiment: """ Estimate process fidelity by exhaustive direct fidelity estimation (DFE). This leads to a quadratic reduction in overhead w.r.t. process tomography for fidelity estimation. The algorithm is due to: .. [DFE1] Practical Characterization of Quantum Devices without Tomography. Silva et al. PRL 107, 210404 (2011). https://doi.org/10.1103/PhysRevLett.107.210404 https://arxiv.org/abs/1104.3835 .. [DFE2] Direct Fidelity Estimation from Few Pauli Measurements. Flammia and Liu. PRL 106, 230501 (2011). https://doi.org/10.1103/PhysRevLett.106.230501 https://arxiv.org/abs/1104.4695 :param benchmarker: object returned from pyquil.api.get_benchmarker() used to conjugate each Pauli by the Clifford program :param program: A program comprised of Clifford group gates that defines the process for which we estimate the fidelity. :param qubits: The qubits to perform DFE on. This can be a superset of the qubits used in ``program``, in which case it is assumed the identity acts on these qubits. Note that we assume qubits are initialized to the ``|0>`` state. :return: an ObservablesExperiment that constitutes a process DFE experiment. """ settings = [] # generate all n-qubit pauli strings but skip the first all identity term for pauli_labels in [ ''.join(x) for x in itertools.product('IXYZ', repeat=len(qubits)) ][1:]: # calculate the appropriate output pauli from applying the ideal program to the Pauli observable = benchmarker.apply_clifford_to_pauli( program, str_to_pauli_term(pauli_labels, qubits)) # keep track of non-identity terms that may have a sign contribution non_identity_idx = [0 if label == 'I' else 1 for label in pauli_labels] # now replace the identities with Z terms, so they can be decomposed into Z eigenstates state_labels = [ 'Z' if label == 'I' else label for label in pauli_labels ] # loop over the ±1 eigenstates of each Pauli for eigenstate in itertools.product([0, 1], repeat=len(qubits)): in_state = TensorProductState( _OneQState(l, s, q) for l, s, q in zip(state_labels, eigenstate, qubits)) # make the observable negative if the in_state is a negative eigenstate sign_contribution = (-1)**np.dot(eigenstate, non_identity_idx) settings.append( ExperimentSetting(in_state=in_state, observable=observable * sign_contribution)) return ObservablesExperiment(settings, program=program)
def generate_monte_carlo_process_dfe_experiment(benchmarker: BenchmarkConnection, program: Program, qubits: List[int], n_terms: int = 200) \ -> ObservablesExperiment: """ Estimate process fidelity by randomly sampled direct fidelity estimation. This leads to constant overhead (w.r.t. number of qubits) fidelity estimation. The algorithm is due to [DFE1]_ and [DFE2]_. :param program: A program comprised of Clifford group gates that constructs a state for which we estimate the fidelity. :param qubits: The qubits to perform DFE on. This can be a superset of the qubits used in ``program``, in which case it is assumed the identity acts on these qubits. Note that we assume qubits are initialized to the ``|0>`` state. :param benchmarker: The `BenchmarkConnection` object used to design experiments :param n_terms: Number of randomly chosen observables to measure. This number should be a constant less than ``2**len(qubits)``, otherwise ``exhaustive_process_dfe`` is more efficient. :return: an ObservablesExperiment that constitutes a process DFE experiment. """ single_q_paulis = ['I', 'X', 'Y', 'Z'] # pick n_terms different random combinations of I, X, Y, Z on the qubits rand_paulis = np.random.randint(len(single_q_paulis), size=(n_terms, len(qubits))) settings = [] for pauli_idxs in rand_paulis: # sample a new state if this one is all identity while sum(pauli_idxs) == 0: pauli_idxs = np.random.randint(len(single_q_paulis), size=len(qubits)) # convert from indices to string pauli_str = ''.join([single_q_paulis[idx] for idx in pauli_idxs]) # convert from string to PauliTerm on appropriate qubits pauli = str_to_pauli_term(pauli_str, qubits) # calculate the appropriate output pauli from applying the ideal program to the Pauli observable = benchmarker.apply_clifford_to_pauli(program, pauli) # now replace the identities with Z terms, so they can be decomposed into Z eigenstates state_labels = ['Z' if label == 'I' else label for label in pauli_str] # randomly pick between ±1 eigenstates of each Pauli eigenstate = np.random.randint(2, size=len(qubits)) in_state = TensorProductState( _OneQState(l, s, q) for l, s, q in zip(state_labels, eigenstate, qubits)) # make the observable negative if the in_state is a negative eigenstate # only keep track of minus eigenstates associated to non-identity Paulis, i.e. idx >= 1 sign_contribution = (-1)**np.dot(eigenstate, [min(1, idx) for idx in pauli_idxs]) settings.append( ExperimentSetting(in_state=in_state, observable=observable * sign_contribution)) return ObservablesExperiment(settings, program=program)
def generate_rb_sequence(compiler: BenchmarkConnection, rb_type: str, depth: int, random_seed: int = None) -> (List[Program], List[QubitPlaceholder]): """ Generate a complete randomized benchmarking sequence. :param compiler: A compiler connection that will do the grunt work of generating the sequences :param rb_type: "1q" or "2q". :param depth: The total number of Cliffords in the sequence (including inverse) :param random_seed: Random seed passed to compiler to seed sequence generation. :return: A dictionary with keys "program", "qubits", and "bits". """ if depth < 2: raise ValueError("Sequence depth must be at least 2 for rb sequences, or at least 1 for unitarity sequences.") gateset, q_placeholders = get_rb_gateset(rb_type=rb_type) programs = compiler.generate_rb_sequence(depth=depth, gateset=gateset, seed=random_seed) return programs, q_placeholders
def generate_rb_sequence(benchmarker: BenchmarkConnection, qubits: Sequence[int], depth: int, interleaved_gate: Optional[Program] = None, random_seed: Optional[int] = None) \ -> List[Program]: """ Generate a complete randomized benchmarking sequence. :param benchmarker: object returned from get_benchmarker() used to generate clifford sequences :param qubits: qubits on which the sequence will act :param depth: The total number of Cliffords in the sequence (including inverse) :param random_seed: Random seed passed to the benchmarker to seed sequence generation. :param interleaved_gate: See [IRB]_; this gate will be interleaved into the sequence :return: A list of programs constituting Clifford gates in a self-inverting sequence. """ if depth < 2: raise ValueError("Sequence depth must be at least 2 for rb sequences, or at least 1 for " "unitarity sequences.") gateset = get_rb_gateset(qubits) programs = benchmarker.generate_rb_sequence(depth=depth, gateset=gateset, interleaver=interleaved_gate, seed=random_seed) # return a sequence composed of depth-many Cliffords. return programs
def generate_rb_program( benchmarker: BenchmarkConnection, qubits: Sequence[int], depth: int, interleaved_gate: Optional[Program] = None, random_seed: Optional[int] = None, ) -> Program: """ Generate a randomized benchmarking program. Args: benchmarker: Connection object to quilc for generating sequences. qubits: The qubits to generate and RB sequence for. depth: Total number of Cliffords in the sequence (with inverse). interleaved_gate: Gate to interleave into the sequence for IRB. random_seed: Random seed passed to the benchmarker. Returns: A pyQuil Program for a randomized benchmarking sequence. """ if depth < 2: raise ValueError("Sequence depth must be at least 2 for RB sequences.") if len(qubits) == 1: gateset = one_qubit_gateset(qubits[0]) elif len(qubits) == 2: gateset = two_qubit_gateset(*qubits) else: raise ValueError("We only support one- and two-qubit RB.") programs = benchmarker.generate_rb_sequence( depth=depth, gateset=gateset, interleaver=interleaved_gate, seed=random_seed, ) return Program(programs)
def benchmarker(client_configuration: QCSClientConfiguration): bm = BenchmarkConnection(timeout=2, client_configuration=client_configuration) bm.apply_clifford_to_pauli(Program(I(0)), sX(0)) return bm
def mock_rb_cxn(request, m_endpoints): return BenchmarkConnection(endpoint=m_endpoints[0])