# Sampling backends

In the previous section (Sampling estimation), we described how to estimate expectation value of operators using sampling measurements on a quantum circuit simulator. Since QURI Parts is designed to be platform independent, you can execute almost the same code on a real quantum computer.

In `QURI Parts`

, we use `SamplingBackend`

objects to submit jobs to the real devices. This tutorial is for explaining some common features shared between devices from different providers, e.g. `Qiskit`

and `Braket`

. For provider specific features, please refer to the corresponding tutorial pages.

## Prerequisite

This section requires topics described in the previous section (Sampling estimation), so you need to read it before this section. QURI Parts is capable of supporting backends provided by all providers. You may install any one depending on your preference. In this tutorial, we will be using backends provided by Amazon Braket as well as IBM Quantum as examples. Then, we will explain how to install and use both backends in their corresponding tutorials.

## Sampling Backend and Sampler

In order to use a real device, you need to create a `SamplingBackend`

object and then a `Sampler`

using the backend. The SamplingBackend provides a unified interface for handling various backend devices, computation jobs for the devices and results of the jobs.

You can create a sampler with a sampling backend. First, you can create sampling backends with the backend provider you prefer. For example:

`from quri_parts.qiskit.backend import QiskitSamplingBackend`

from quri_parts.braket.backend import BraketSamplingBackend

from qiskit_aer import AerSimulator

from braket.devices import LocalSimulator

# sampling_backend = QiskitSamplingBackend(backend=AerSimulator())

sampling_backend = BraketSamplingBackend(device=LocalSimulator())

### Using the sampling backend

It is possible to use these backends directly, though it is usually unnecessary as we will see below. The `SamplingBackend`

has `sample()`

method, which returns a `SamplingJob`

object, and you can extract a result of the sampling job:

`from math import pi`

from quri_parts.circuit import QuantumCircuit

circuit = QuantumCircuit(4)

circuit.add_X_gate(0)

circuit.add_H_gate(1)

circuit.add_Y_gate(2)

circuit.add_CNOT_gate(1, 2)

circuit.add_RX_gate(3, pi/4)

sampling_job = sampling_backend.sample(circuit, n_shots=1000)

sampling_result = sampling_job.result()

print(sampling_result.counts)

`#output`

Counter({3: 421, 5: 416, 13: 88, 11: 75})

### Create samplers with backend

Instead of using the backends directly, you can create a `Sampler`

from it with the `create_sampler_from_sampling_backend`

function:

`from quri_parts.core.sampling import create_sampler_from_sampling_backend`

sampler = create_sampler_from_sampling_backend(

sampling_backend # you may replace it with other sampling backends you prefer.

)

The sampler can then be used as usual:

`sampling_count = sampler(circuit, 1000)`

print(sampling_count)

`#output`

{11: 77, 13: 83, 3: 409, 5: 431}

### Sampling Estimate

Here we describe how to perform sampling estimate with the same code used in the previous Sampling estimation tutorials. To create a `SamplingEstimator`

, one needs to specify the concurrent sampler.

`from quri_parts.core.sampling import create_concurrent_sampler_from_sampling_backend`

from quri_parts.qiskit.backend import QiskitSamplingBackend

from quri_parts.braket.backend import BraketSamplingBackend

from qiskit_aer import AerSimulator

from braket.devices import LocalSimulator

# sampling_backend = QiskitSamplingBackend(backend=AerSimulator())

# concurrent_sampler = create_concurrent_sampler_from_sampling_backend(qiskit_sampling_backend)

sampling_backend = BraketSamplingBackend(device=LocalSimulator())

concurrent_sampler = create_concurrent_sampler_from_sampling_backend(sampling_backend)

Then you can use either concurrent sampler to perform sampling estimation.

`from quri_parts.core.estimator.sampling import create_sampling_estimator`

from quri_parts.core.state import quantum_state

from quri_parts.core.operator import Operator, pauli_label, PAULI_IDENTITY

from quri_parts.core.measurement import bitwise_commuting_pauli_measurement

from quri_parts.core.sampling.shots_allocator import create_weighted_random_shots_allocator

estimator = create_sampling_estimator(

5000,

concurrent_sampler,

bitwise_commuting_pauli_measurement,

create_weighted_random_shots_allocator(seed=777),

)

initial_state = quantum_state(4, bits=0b0101)

op = Operator({

pauli_label("Z0"): 0.25,

pauli_label("Z1 Z2"): 2.0,

pauli_label("X1 X2"): 0.5 + 0.25j,

pauli_label("Z1 Y3"): 1.0j,

pauli_label("Z2 Y3"): 1.5 + 0.5j,

pauli_label("X1 Y3"): 2.0j,

PAULI_IDENTITY: 3.0,

})

estimate = estimator(op, initial_state)

print(f'Estimated value: {estimate.value}')

print(f'Estimated error: {estimate.error}')

`#output`

Estimated value: (0.6730848581603831+0.042719018177966035j)

Estimated error: 0.07069176439271635

## Common Options and Features of Sampling Backends

### Shot Distribution

Usually the real device does not allow arbitrary large number of shots to be executed. However, `QURI Parts`

' `SamplingBackend.sample`

allows submitting shot count greater than the max shot count supported by the device. This is because `SamplingBackend`

performs shot distribution that group `n_shots`

into batches of `SamplingJob`

s where the shot count of each batch is equal to or smaller than the max shot supported by the device.

On the other hand, the device may restrict the minimal number of shots to be greater than some minimal shot number. In this case, if a shot count in a batch is smaller than the min shot supported by the device, you may use the `enable_shots_roundup`

argument in the backend to decide what to do with the remaining batch. If it is set to True, the backend will round the shot count of the remaining batch to the specified min shot. Otherwise, the backend will ignore the batch.

### Qubit Mapping

When you use a real quantum device, you may want to use specific device qubits selected by inspecting calibration data of the device. A `SamplingBackend`

supports such usage with `qubit_mapping`

argument. With `qubit_mapping`

you can specify an arbitrary one-to-one mapping between qubit indices in the input circuit and device qubits. For example, if you want to map qubits in the circuit into device qubits as 0 → 3, 1 → 2, 2 → 0 and 3 → 1, you can specify the mapping as follows:

`qubit_mapping = {0: 3, 1: 2, 2: 0, 3: 1}`

and pass it into the SamplingBackend. The result would look similar to one with no qubit mapping, since the measurement result from the device is mapped backward so that it is interpreted in terms of the original qubit indices.

### Circuit transpilation before execution

When the `SamplingBackend`

receives an input circuit, it performs circuit transpilation before sending the circuit to its backend since each device can have a different supported gate set. The transpilation performed by default depends on the backend.