Ejemplo n.º 1
0
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"
Ejemplo n.º 2
0
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)
Ejemplo n.º 7
0
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
Ejemplo n.º 12
0
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)
Ejemplo n.º 13
0
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
Ejemplo n.º 14
0
def mock_rb_cxn(request, m_endpoints):
    return BenchmarkConnection(endpoint=m_endpoints[0])