def parallel_xeb_workflow(
    sampler: cirq.Sampler,
    qubits: Sequence[cirq.GridQubit] | None = None,
    entangling_gate: cirq.Gate = ops.CZ,
    n_repetitions: int = 10**4,
    n_combinations: int = 10,
    n_circuits: int = 20,
    cycle_depths: Sequence[int] = (5, 25, 50, 100, 200, 300),
    random_state: cirq.RANDOM_STATE_OR_SEED_LIKE = None,
    ax: plt.Axes | None = None,
    pairs: Sequence[tuple[cirq.GridQubit, cirq.GridQubit]] | None = None,
    pool: multiprocessing.pool.Pool | None = None,
    batch_size: int = 9,
    tags: Sequence[Any] = (),
    **plot_kwargs,
) -> tuple[pd.DataFrame, Sequence[cirq.Circuit], pd.DataFrame]:
    """A utility method that runs the full XEB workflow.

    Args:
        sampler: The quantum engine or simulator to run the circuits.
        qubits: Qubits under test. If none, uses all qubits on the sampler's device.
        entangling_gate: The entangling gate to use.
        n_repetitions: The number of repetitions to use.
        n_combinations: The number of combinations to generate.
        n_circuits: The number of circuits to generate.
        cycle_depths: The cycle depths to use.
        random_state: The random state to use.
        ax: the plt.Axes to plot the device layout on. If not given,
            no plot is created.
        pairs: Pairs to use. If not specified, use all pairs between adjacent qubits.
        pool: An optional multiprocessing pool.
        batch_size: We call `run_batch` on the sampler, which can speed up execution in certain
            environments. The number of (circuit, cycle_depth) tasks to be run in each batch
            is given by this number.
        tags: Tags to add to two qubit operations.
        **plot_kwargs: Arguments to be passed to 'plt.Axes.plot'.

    Returns:
        - A DataFrame with columns 'cycle_depth' and 'fidelity'.
        - The circuits used to perform XEB.
        - A pandas dataframe with index given by ['circuit_i', 'cycle_depth'].
            Columns always include "sampled_probs". If `combinations_by_layer` is
            not `None` and you are doing parallel XEB, additional metadata columns
            will be attached to the returned DataFrame.

    Raises:
        ValueError: If qubits are not specified and the sampler has no device.
    """
    rs = value.parse_random_state(random_state)

    qubits, pairs = qubits_and_pairs(sampler, qubits, pairs)
    graph = nx.Graph(pairs)

    if ax is not None:
        nx.draw_networkx(graph, pos={q: (q.row, q.col) for q in qubits}, ax=ax)
        ax.set_title('device layout')
        ax.plot(**plot_kwargs)

    circuit_library = rqcg.generate_library_of_2q_circuits(
        n_library_circuits=n_circuits,
        two_qubit_gate=entangling_gate,
        random_state=rs,
        max_cycle_depth=max(cycle_depths),
        tags=tags,
    )

    combs_by_layer = rqcg.get_random_combinations_for_device(
        n_library_circuits=len(circuit_library),
        n_combinations=n_combinations,
        device_graph=graph,
        random_state=rs,
    )

    sampled_df = sample_2q_xeb_circuits(
        sampler=sampler,
        circuits=circuit_library,
        cycle_depths=cycle_depths,
        combinations_by_layer=combs_by_layer,
        shuffle=rs,
        repetitions=n_repetitions,
        batch_size=batch_size,
    )

    fids = benchmark_2q_xeb_fidelities(
        sampled_df=sampled_df, circuits=circuit_library, cycle_depths=cycle_depths, pool=pool
    )

    return fids, circuit_library, sampled_df
A utility method that runs the full XEB workflow.

    Args:
        sampler: The quantum engine or simulator to run the circuits.
        qubits: Qubits under test. If none, uses all qubits on the sampler's device.
        entangling_gate: The entangling gate to use.
        n_repetitions: The number of repetitions to use.
        n_combinations: The number of combinations to generate.
        n_circuits: The number of circuits to generate.
        cycle_depths: The cycle depths to use.
        random_state: The random state to use.
        ax: the plt.Axes to plot the device layout on. If not given,
            no plot is created.
        pairs: Pairs to use. If not specified, use all pairs between adjacent qubits.
        pool: An optional multiprocessing pool.
        batch_size: We call `run_batch` on the sampler, which can speed up execution in certain
            environments. The number of (circuit, cycle_depth) tasks to be run in each batch
            is given by this number.
        tags: Tags to add to two qubit operations.
        **plot_kwargs: Arguments to be passed to 'plt.Axes.plot'.

    Returns:
        - A DataFrame with columns 'cycle_depth' and 'fidelity'.
        - The circuits used to perform XEB.
        - A pandas dataframe with index given by ['circuit_i', 'cycle_depth'].
            Columns always include "sampled_probs". If `combinations_by_layer` is
            not `None` and you are doing parallel XEB, additional metadata columns
            will be attached to the returned DataFrame.

    Raises:
        ValueError: If qubits are not specified and the sampler has no device.
    
cirq-core/cirq/experiments/two_qubit_xeb.py