Ejemplo n.º 1
0
def test_two_qubit_state_tomography():
    # Check that the density matrices of the four Bell states closely match
    # the ideal cases.
    simulator = sim.Simulator()
    q_0 = GridQubit(0, 0)
    q_1 = GridQubit(0, 1)

    circuit_00 = circuits.Circuit.from_ops(ops.H(q_0), ops.CNOT(q_0, q_1))
    circuit_01 = circuits.Circuit.from_ops(ops.X(q_1), ops.H(q_0),
                                           ops.CNOT(q_0, q_1))
    circuit_10 = circuits.Circuit.from_ops(ops.X(q_0), ops.H(q_0),
                                           ops.CNOT(q_0, q_1))
    circuit_11 = circuits.Circuit.from_ops(ops.X(q_0), ops.X(q_1), ops.H(q_0),
                                           ops.CNOT(q_0, q_1))

    act_rho_00 = two_qubit_state_tomography(simulator, q_0, q_1, circuit_00,
                                            100000).data
    act_rho_01 = two_qubit_state_tomography(simulator, q_0, q_1, circuit_01,
                                            100000).data
    act_rho_10 = two_qubit_state_tomography(simulator, q_0, q_1, circuit_10,
                                            100000).data
    act_rho_11 = two_qubit_state_tomography(simulator, q_0, q_1, circuit_11,
                                            100000).data

    tar_rho_00 = np.outer([1.0, 0, 0, 1.0], [1.0, 0, 0, 1.0]) / 2.0
    tar_rho_01 = np.outer([0, 1.0, 1.0, 0], [0, 1.0, 1.0, 0]) / 2.0
    tar_rho_10 = np.outer([1.0, 0, 0, -1.0], [1.0, 0, 0, -1.0]) / 2.0
    tar_rho_11 = np.outer([0, 1.0, -1.0, 0], [0, 1.0, -1.0, 0]) / 2.0

    np.testing.assert_almost_equal(act_rho_00, tar_rho_00, decimal=1)
    np.testing.assert_almost_equal(act_rho_01, tar_rho_01, decimal=1)
    np.testing.assert_almost_equal(act_rho_10, tar_rho_10, decimal=1)
    np.testing.assert_almost_equal(act_rho_11, tar_rho_11, decimal=1)
Ejemplo n.º 2
0
def _get_xeb_result(
    qubit_pair: GridQubitPair,
    circuits: List['cirq.Circuit'],
    measurement_results: Sequence[List[np.ndarray]],
    num_circuits: int,
    repetitions: int,
    cycles: List[int],
) -> CrossEntropyResult:
    # pytest-cov is unable to detect that this function is called by a
    # multiprocessing Pool
    # coverage: ignore
    simulator = sim.Simulator()
    # Simulate circuits to get bitstring probabilities
    all_and_observed_probabilities: Dict[int, List[Tuple[
        np.ndarray, np.ndarray]]] = collections.defaultdict(list)
    empirical_probabilities: Dict[
        int, List[np.ndarray]] = collections.defaultdict(list)
    for i, circuit in enumerate(circuits):
        step_results = simulator.simulate_moment_steps(circuit,
                                                       qubit_order=qubit_pair)
        moment_index = 0
        for depth, measurements in zip(cycles, measurement_results[i]):
            while moment_index < 2 * depth:
                step_result = next(step_results)
                moment_index += 1
            # copy=False is safe because state_vector_to_probabilities will copy anyways
            amplitudes = step_result.state_vector(copy=False)
            probabilities = value.state_vector_to_probabilities(amplitudes)
            _, counts = np.unique(measurements, return_counts=True)
            empirical_probs = counts / len(measurements)
            empirical_probs = np.pad(empirical_probs,
                                     (0, 4 - len(empirical_probs)),
                                     mode='constant')
            all_and_observed_probabilities[depth].append(
                (probabilities, probabilities[measurements]))
            empirical_probabilities[depth].append(empirical_probs)
    # Compute XEB result
    data = []
    purity_data = []
    for depth in cycles:
        all_probabilities, observed_probabilities = zip(
            *all_and_observed_probabilities[depth])
        empirical_probs = np.asarray(empirical_probabilities[depth]).flatten()
        fidelity, _ = least_squares_xeb_fidelity_from_probabilities(
            hilbert_space_dimension=4,
            observed_probabilities=observed_probabilities,
            all_probabilities=all_probabilities,
            observable_from_probability=None,
            normalize_probabilities=True,
        )
        purity = purity_from_probabilities(4, empirical_probs)
        data.append(CrossEntropyPair(depth, fidelity))
        purity_data.append(SpecklePurityPair(depth, purity))
    return CrossEntropyResult(  # type: ignore
        data=data,
        repetitions=repetitions,
        purity_data=purity_data)
Ejemplo n.º 3
0
def test_single_qubit_randomized_benchmarking():
    # Check that the ground state population at the end of the Clifford
    # sequences is always unity.
    simulator = sim.Simulator()
    qubit = GridQubit(0, 0)
    num_cfds = range(5, 20, 5)
    results = single_qubit_randomized_benchmarking(simulator, qubit,
                                                   num_clifford_range=num_cfds)
    g_pops = np.asarray(results.data)[:, 1]
    assert np.isclose(np.mean(g_pops), 1.0)
Ejemplo n.º 4
0
def test_tomography_plot_raises_for_incorrect_number_of_axes():
    simulator = sim.Simulator()
    qubit = GridQubit(0, 0)
    circuit = circuits.Circuit(ops.X(qubit)**0.5)
    result = single_qubit_state_tomography(simulator, qubit, circuit, 1000)
    with pytest.raises(TypeError):  # ax is not a List[plt.Axes]
        ax = plt.subplot()
        result.plot(ax)
    with pytest.raises(ValueError):
        _, axes = plt.subplots(1, 3)
        result.plot(axes)
Ejemplo n.º 5
0
def test_rabi_oscillations():
    # Check that the excited state population matches the ideal case within a
    # small statistical error.
    simulator = sim.Simulator()
    qubit = GridQubit(0, 0)
    results = rabi_oscillations(simulator, qubit, np.pi, repetitions=1000)
    data = np.asarray(results.data)
    angles = data[:, 0]
    actual_pops = data[:, 1]
    target_pops = 0.5 - 0.5 * np.cos(angles)
    rms_err = np.sqrt(np.mean((target_pops - actual_pops)**2))
    assert rms_err < 0.1
Ejemplo n.º 6
0
def test_two_qubit_state_tomography():
    # Check that the density matrices of the four Bell states closely match
    # the ideal cases. In addition, check that the output states of
    # single-qubit rotations (H, H), (X/2, Y/2), (Y/2, X/2) have the correct
    # density matrices.

    simulator = sim.Simulator()
    q_0 = GridQubit(0, 0)
    q_1 = GridQubit(0, 1)

    circuit_00 = circuits.Circuit.from_ops(ops.H(q_0), ops.CNOT(q_0, q_1))
    circuit_01 = circuits.Circuit.from_ops(ops.X(q_1), ops.H(q_0),
                                           ops.CNOT(q_0, q_1))
    circuit_10 = circuits.Circuit.from_ops(ops.X(q_0), ops.H(q_0),
                                           ops.CNOT(q_0, q_1))
    circuit_11 = circuits.Circuit.from_ops(ops.X(q_0), ops.X(q_1), ops.H(q_0),
                                           ops.CNOT(q_0, q_1))
    circuit_hh = circuits.Circuit.from_ops(ops.H(q_0), ops.H(q_1))
    circuit_xy = circuits.Circuit.from_ops(ops.X(q_0)**0.5, ops.Y(q_1)**0.5)
    circuit_yx = circuits.Circuit.from_ops(ops.Y(q_0)**0.5, ops.X(q_1)**0.5)

    act_rho_00 = two_qubit_state_tomography(simulator, q_0, q_1, circuit_00,
                                            1000).data
    act_rho_01 = two_qubit_state_tomography(simulator, q_0, q_1, circuit_01,
                                            1000).data
    act_rho_10 = two_qubit_state_tomography(simulator, q_0, q_1, circuit_10,
                                            1000).data
    act_rho_11 = two_qubit_state_tomography(simulator, q_0, q_1, circuit_11,
                                            1000).data
    act_rho_hh = two_qubit_state_tomography(simulator, q_0, q_1, circuit_hh,
                                            1000).data
    act_rho_xy = two_qubit_state_tomography(simulator, q_0, q_1, circuit_xy,
                                            1000).data
    act_rho_yx = two_qubit_state_tomography(simulator, q_0, q_1, circuit_yx,
                                            1000).data

    tar_rho_00 = np.outer([1.0, 0, 0, 1.0], [1.0, 0, 0, 1.0]) * 0.5
    tar_rho_01 = np.outer([0, 1.0, 1.0, 0], [0, 1.0, 1.0, 0]) * 0.5
    tar_rho_10 = np.outer([1.0, 0, 0, -1.0], [1.0, 0, 0, -1.0]) * 0.5
    tar_rho_11 = np.outer([0, 1.0, -1.0, 0], [0, 1.0, -1.0, 0]) * 0.5
    tar_rho_hh = np.outer([0.5, 0.5, 0.5, 0.5], [0.5, 0.5, 0.5, 0.5])
    tar_rho_xy = np.outer([0.5, 0.5, -0.5j, -0.5j], [0.5, 0.5, 0.5j, 0.5j])
    tar_rho_yx = np.outer([0.5, -0.5j, 0.5, -0.5j], [0.5, 0.5j, 0.5, 0.5j])

    np.testing.assert_almost_equal(act_rho_00, tar_rho_00, decimal=1)
    np.testing.assert_almost_equal(act_rho_01, tar_rho_01, decimal=1)
    np.testing.assert_almost_equal(act_rho_10, tar_rho_10, decimal=1)
    np.testing.assert_almost_equal(act_rho_11, tar_rho_11, decimal=1)
    np.testing.assert_almost_equal(act_rho_hh, tar_rho_hh, decimal=1)
    np.testing.assert_almost_equal(act_rho_xy, tar_rho_xy, decimal=1)
    np.testing.assert_almost_equal(act_rho_yx, tar_rho_yx, decimal=1)
Ejemplo n.º 7
0
def simulate_2q_xeb_circuits(
    circuits: Sequence['cirq.Circuit'],
    cycle_depths: Sequence[int],
    param_resolver: 'cirq.ParamResolverOrSimilarType' = None,
    pool: Optional['multiprocessing.pool.Pool'] = None,
    simulator: Optional['cirq.SimulatesIntermediateState'] = None,
):
    """Simulate two-qubit XEB circuits.

    These ideal probabilities can be benchmarked against potentially noisy
    results from `sample_2q_xeb_circuits`.

    Args:
        circuits: A library of two-qubit circuits generated from
            `random_rotations_between_two_qubit_circuit` of sufficient length for `cycle_depths`.
        cycle_depths: A sequence of cycle depths at which we will truncate each of the `circuits`
            to simulate.
        param_resolver: If circuits contain parameters, resolve according to this ParamResolver
            prior to simulation
        pool: If provided, execute the simulations in parallel.
        simulator: A noiseless simulator used to simulate the circuits. By default, this is
            `cirq.Simulator`. The simulator must support the `cirq.SimulatesIntermediateState`
            interface.

    Returns:
        A dataframe with index ['circuit_i', 'cycle_depth'] and column
        "pure_probs" containing the pure-state probabilities for each row.
    """
    if simulator is None:
        # Need an actual object; not np.random or else multiprocessing will
        # fail to pickle the closure object:
        # https://github.com/quantumlib/Cirq/issues/3717
        simulator = sim.Simulator(seed=np.random.RandomState())
    _simulate_2q_xeb_circuit = _Simulate_2q_XEB_Circuit(simulator=simulator)

    tasks = tuple(
        _Simulate2qXEBTask(
            circuit_i=circuit_i,
            cycle_depths=cycle_depths,
            circuit=circuit,
            param_resolver=param_resolver,
        ) for circuit_i, circuit in enumerate(circuits))

    if pool is not None:
        nested_records = pool.map(_simulate_2q_xeb_circuit, tasks)
    else:
        nested_records = [_simulate_2q_xeb_circuit(task) for task in tasks]

    records = [record for sublist in nested_records for record in sublist]
    return pd.DataFrame(records).set_index(['circuit_i', 'cycle_depth'])
Ejemplo n.º 8
0
def test_two_qubit_randomized_benchmarking():
    # Check that the ground state population at the end of the Clifford
    # sequences is always unity.
    simulator = sim.Simulator()
    q_0 = GridQubit(0, 0)
    q_1 = GridQubit(0, 1)
    num_cfds = [5, 10]
    results = two_qubit_randomized_benchmarking(simulator,
                                                q_0,
                                                q_1,
                                                num_clifford_range=num_cfds,
                                                num_circuits=10,
                                                repetitions=100)
    g_pops = np.asarray(results.data)[:, 1]
    assert np.isclose(np.mean(g_pops), 1.0)
Ejemplo n.º 9
0
def _simulate_2q_xeb_circuit(task: Dict[str, Any]):
    """Helper function for simulating a given (circuit, cycle_depth)."""
    circuit_i = task['circuit_i']
    cycle_depth = task['cycle_depth']
    circuit = task['circuit']
    param_resolver = task['param_resolver']

    circuit_depth = cycle_depth * 2 + 1
    assert circuit_depth <= len(circuit)
    tcircuit = circuit[:circuit_depth]
    tcircuit = protocols.resolve_parameters_once(tcircuit,
                                                 param_resolver=param_resolver)

    pure_sim = sim.Simulator()
    psi = cast(sim.StateVectorTrialResult, pure_sim.simulate(tcircuit))
    psi = psi.final_state_vector
    pure_probs = np.abs(psi)**2

    return {
        'circuit_i': circuit_i,
        'cycle_depth': cycle_depth,
        'pure_probs': pure_probs,
    }
Ejemplo n.º 10
0
def _get_xeb_result(qubit_pair: GridQubitPair, circuits: List['cirq.Circuit'],
                    measurement_results: Sequence[List[np.ndarray]],
                    num_circuits: int, repetitions: int,
                    cycles: List[int]) -> CrossEntropyResult:
    # pytest-cov is unable to detect that this function is called by a
    # multiprocessing Pool
    # coverage: ignore
    simulator = sim.Simulator()
    # Simulate circuits to get bitstring probabilities
    all_and_observed_probabilities = collections.defaultdict(
        list)  # type: Dict[int, List[Tuple[np.ndarray, np.ndarray]]]
    for i, circuit in enumerate(circuits):
        step_results = simulator.simulate_moment_steps(circuit,
                                                       qubit_order=qubit_pair)
        moment_index = 0
        for depth, measurements in zip(cycles, measurement_results[i]):
            while moment_index < 2 * depth:
                step_result = next(step_results)
                moment_index += 1
            amplitudes = step_result.state_vector()
            probabilities = np.abs(amplitudes)**2
            all_and_observed_probabilities[depth].append(
                (probabilities, probabilities[measurements]))
    # Compute XEB result
    data = []
    for depth in cycles:
        all_probabilities, observed_probabilities = zip(
            *all_and_observed_probabilities[depth])
        fidelity, _ = least_squares_xeb_fidelity_from_probabilities(
            hilbert_space_dimension=4,
            observed_probabilities=observed_probabilities,
            all_probabilities=all_probabilities,
            observable_from_probability=None,
            normalize_probabilities=True)
        data.append(CrossEntropyPair(depth, fidelity))
    return CrossEntropyResult(  # type: ignore
        data=data, repetitions=repetitions)
Ejemplo n.º 11
0
def test_single_qubit_state_tomography():
    # Check that the density matrices of the output states of X/2, Y/2 and
    # H + Y gates closely match the ideal cases.
    simulator = sim.Simulator()
    qubit = GridQubit(0, 0)

    circuit_1 = circuits.Circuit(ops.X(qubit)**0.5)
    circuit_2 = circuits.Circuit(ops.Y(qubit)**0.5)
    circuit_3 = circuits.Circuit(ops.H(qubit), ops.Y(qubit))

    act_rho_1 = single_qubit_state_tomography(simulator, qubit, circuit_1,
                                              1000).data
    act_rho_2 = single_qubit_state_tomography(simulator, qubit, circuit_2,
                                              1000).data
    act_rho_3 = single_qubit_state_tomography(simulator, qubit, circuit_3,
                                              1000).data

    tar_rho_1 = np.array([[0.5, 0.5j], [-0.5j, 0.5]])
    tar_rho_2 = np.array([[0.5, 0.5], [0.5, 0.5]])
    tar_rho_3 = np.array([[0.5, -0.5], [-0.5, 0.5]])

    np.testing.assert_almost_equal(act_rho_1, tar_rho_1, decimal=1)
    np.testing.assert_almost_equal(act_rho_2, tar_rho_2, decimal=1)
    np.testing.assert_almost_equal(act_rho_3, tar_rho_3, decimal=1)
def test_cross_entropy_benchmarking():
    # Check that the fidelities returned from a four-qubit XEB simulation are
    # close to 1 (deviations from 1 is expected due to finite number of
    # measurements).
    simulator = sim.Simulator()
    qubits = [
        devices.GridQubit(0, 0),
        devices.GridQubit(0, 1),
        devices.GridQubit(1, 0),
        devices.GridQubit(1, 1)
    ]

    # Build a sequence of CZ gates.
    interleaved_ops = build_entangling_layers(qubits, ops.CZ**0.91)

    # Specify a set of single-qubit rotations. Pick prime numbers for the
    # exponent to avoid evolving the system into a basis state.
    single_qubit_rots = [[ops.X**0.37], [ops.Y**0.73, ops.X**0.53],
                         [ops.Z**0.61, ops.X**0.43], [ops.Y**0.19]]

    # Simulate XEB using the default single-qubit gate set without two-qubit
    # gates, XEB using the specified single-qubit gate set without two-qubit
    # gates, and XEB using the specified single-qubit gate set with two-qubit
    # gate. Check that the fidelities are close to 1.0 in all cases. Also,
    # check that a single XEB fidelity is returned if a single cycle number
    # is specified.
    results_0 = cross_entropy_benchmarking(simulator,
                                           qubits,
                                           num_circuits=5,
                                           repetitions=5000,
                                           cycles=range(4, 30, 5))
    results_1 = cross_entropy_benchmarking(
        simulator,
        qubits,
        num_circuits=5,
        repetitions=5000,
        cycles=range(4, 30, 5),
        scrambling_gates_per_cycle=single_qubit_rots)
    results_2 = cross_entropy_benchmarking(
        simulator,
        qubits,
        benchmark_ops=interleaved_ops,
        num_circuits=5,
        repetitions=5000,
        cycles=range(4, 30, 5),
        scrambling_gates_per_cycle=single_qubit_rots)
    results_3 = cross_entropy_benchmarking(
        simulator,
        qubits,
        benchmark_ops=interleaved_ops,
        num_circuits=5,
        repetitions=5000,
        cycles=20,
        scrambling_gates_per_cycle=single_qubit_rots)
    fidelities_0 = [datum.xeb_fidelity for datum in results_0.data]
    fidelities_1 = [datum.xeb_fidelity for datum in results_1.data]
    fidelities_2 = [datum.xeb_fidelity for datum in results_2.data]
    fidelities_3 = [datum.xeb_fidelity for datum in results_3.data]
    assert np.isclose(np.mean(fidelities_0), 1.0, atol=0.1)
    assert np.isclose(np.mean(fidelities_1), 1.0, atol=0.1)
    assert np.isclose(np.mean(fidelities_2), 1.0, atol=0.1)
    assert len(fidelities_3) == 1

    # Sanity test that plot runs.
    results_1.plot()
def cross_entropy_benchmarking(
        sampler: work.Sampler,
        qubits: Sequence[ops.Qid],
        *,
        benchmark_ops: Sequence[ops.Moment] = None,
        num_circuits: int = 20,
        repetitions: int = 1000,
        cycles: Union[int, Iterable[int]] = range(2, 103, 10),
        scrambling_gates_per_cycle: List[List[ops.SingleQubitGate]] = None,
        simulator: sim.Simulator = None,
) -> CrossEntropyResult:
    r"""Cross-entropy benchmarking (XEB) of multiple qubits.

    A total of M random circuits are generated, each of which comprises N
    layers where N = max('cycles') or 'cycles' if a single value is specified
    for the 'cycles' parameter. Every layer contains randomly generated
    single-qubit gates applied to each qubit, followed by a set of
    user-defined benchmarking operations (e.g. a set of two-qubit gates).

    Each circuit (circuit_m) from the M random circuits is further used to
    generate a set of circuits {circuit_mn}, where circuit_mn is built from the
    first n cycles of circuit_m. n spans all the values in 'cycles'.

    For each fixed value n, the experiment performs the following:

    1) Experimentally collect a number of bit-strings for each circuit_mn via
    projective measurements in the z-basis.

    2) Theoretically compute the expected bit-string probabilities
    $P^{th, mn}_|...00>$,  $P^{th, mn}_|...01>$, $P^{th, mn}_|...10>$,
    $P^{th, mn}_|...11>$ ... at the end of circuit_mn for all m and for all
    possible bit-strings in the Hilbert space.

    3) Compute an experimental XEB function for each circuit_mn:

    $f_{mn}^{meas} = \langle D * P^{th, mn}_q - 1 \rangle$

    where D is the number of states in the Hilbert space, $P^{th, mn}_q$ is the
    theoretical probability of a bit-string q at the end of circuit_mn, and
    $\langle \rangle$ corresponds to the ensemble average over all measured
    bit-strings.

    Then, take the average of $f_{mn}^{meas}$ over all circuit_mn with fixed
    n to obtain:

    $f_{n} ^ {meas} = (\sum_m f_{mn}^{meas}) / M$

    4) Compute a theoretical XEB function for each circuit_mn:

    $f_{mn}^{th} = D \sum_q (P^{th, mn}_q) ** 2 - 1$

    where the summation goes over all possible bit-strings q in the Hilbert
    space.

    Similarly, we then average $f_m^{th}$ over all circuit_mn with fixed n to
    obtain:

    $f_{n} ^ {th} = (\sum_m f_{mn}^{th}) / M$

    5) Calculate the XEB fidelity $\alpha_n$ at fixed n:

    $\alpha_n = f_{n} ^ {meas} / f_{n} ^ {th}$

    Args:
        sampler: The quantum engine or simulator to run the circuits.
        qubits: The qubits included in the XEB experiment.
        benchmark_ops: A sequence of ops.Moment containing gate operations
            between specific qubits which are to be benchmarked for fidelity.
            If more than one ops.Moment is specified, the random circuits
            will rotate between the ops.Moment's. As an example,
            if benchmark_ops = [Moment([ops.CZ(q0, q1), ops.CZ(q2, q3)]),
            Moment([ops.CZ(q1, q2)]) where q0, q1, q2 and q3 are instances of
            Qid (such as GridQubits), each random circuit will apply CZ gate
            between q0 and q1 plus CZ between q2 and q3 for the first cycle,
            CZ gate between q1 and q2 for the second cycle, CZ between q0 and
            q1 and CZ between q2 and q3 for the third cycle and so on. If
            None, the circuits will consist only of single-qubit gates.
        num_circuits: The total number of random circuits to be used.
        repetitions: The number of measurements for each circuit to estimate
            the bit-string probabilities.
        cycles: The different numbers of circuit layers in the XEB study.
            Could be a single or a collection of values.
        scrambling_gates_per_cycle: If None (by default), the single-qubit
            gates are chosen from X/2 ($\pi/2$ rotation around the X axis),
            Y/2 ($\pi/2$ rotation around the Y axis) and (X + Y)/2 ($\pi/2$
            rotation around an axis $\pi/4$ away from the X on the equator of
            the Bloch sphere). Otherwise the single-qubit gates for each layer
            are chosen from a list of possible choices (each choice is a list
            of one or more single-qubit gates).
        simulator: A simulator that calculates the bit-string probabilities
            of the ideal circuit. By default, this is set to sim.Simulator().

    Returns:
        A CrossEntropyResult object that stores and plots the result.
    """
    simulator = sim.Simulator() if simulator is None else simulator
    num_qubits = len(qubits)

    if isinstance(cycles, int):
        cycle_range = [cycles]
    else:
        cycle_range = list(cycles)

    # These store the measured and simulated bit-string probabilities from
    # all trials in two dictionaries. The keys of the dictionaries are the
    # numbers of cycles. The values are 2D arrays with each row being the
    # probabilities obtained from a single trial.
    probs_meas = {
        n: np.zeros((num_circuits, 2**num_qubits)) for n in cycle_range
    }
    probs_exp = {
        n: np.zeros((num_circuits, 2**num_qubits)) for n in cycle_range
    }

    for k in range(num_circuits):

        # Generates one random XEB circuit with max(num_cycle_range) cycles.
        # Then the first n cycles of the circuit are taken to generate
        # shorter circuits with n cycles (n taken from cycles). All of these
        # circuits are stored in circuits_k.
        circuits_k = _build_xeb_circuits(qubits, cycle_range,
                                         scrambling_gates_per_cycle,
                                         benchmark_ops)

        # Run each circuit with the sampler to obtain a collection of
        # bit-strings, from which the bit-string probabilities are estimated.
        probs_meas_k = _measure_prob_distribution(sampler, repetitions, qubits,
                                                  circuits_k)

        # Simulate each circuit with the Cirq simulator to obtain the
        # wavefunction at the end of each circuit, from which the
        # theoretically expected bit-string probabilities are obtained.
        probs_exp_k = []  # type: List[np.ndarray]
        for circ_k in circuits_k:
            res = simulator.simulate(circ_k, qubit_order=qubits)
            state_probs = np.abs(np.asarray(res.final_state)  # type: ignore
                                )**2
            probs_exp_k.append(state_probs)

        for i, num_cycle in enumerate(cycle_range):
            probs_exp[num_cycle][k, :] = probs_exp_k[i]
            probs_meas[num_cycle][k, :] = probs_meas_k[i]

    fidelity_vals = _xeb_fidelities(probs_exp, probs_meas)
    xeb_data = [
        CrossEntropyPair(c, k) for (c, k) in zip(cycle_range, fidelity_vals)
    ]
    return CrossEntropyResult(xeb_data)