Note

This page was generated from docs/tutorials/user-transpiled-circuits.ipynb.

# Submitting user-transpiled circuits using primitives¶

To get the best performance from your circuits, the Qiskit Runtime service will pass all circuits through Qiskit’s transpiler before running them. While this is usually a good thing, we might sometimes want to disable this by passing the argument `skip_transpilation=True`

to the primitive we’re using.

For example, we may know better than the transpiler in some cases, or want to target a specific subset of qubits on a specific device. In this tutorial, we’ll disable automatic transpilation to test the performance of different transpiler settings. This example will take you through the full process of creating, transpiling, and submitting circuits.

## Transpiling circuits for IBM Quantum devices¶

In the following code cell, we create a small circuit that our transpiler will try to optimize. In this example, we create a circuit that carries out Grover’s algorithm, with an oracle that marks the state `111`

. We then simulate the ideal distribution (what we’d expect to measure if we ran this on a perfect quantum computer, an infinite number of times) for comparison later.

```
[1]:
```

```
# Create circuit to test transpiler on
from qiskit import QuantumCircuit
from qiskit.circuit.library import GroverOperator, Diagonal
oracle = Diagonal([1] * 7 + [-1])
qc = QuantumCircuit(3)
qc.h([0, 1, 2])
qc = qc.compose(GroverOperator(oracle))
# Use Statevector object to calculate the ideal output
from qiskit.quantum_info import Statevector
ideal_distribution = Statevector.from_instruction(qc).probabilities_dict()
from qiskit.visualization import plot_histogram
plot_histogram(ideal_distribution)
```

```
[1]:
```

Next, we need to choose a backend to transpile for. In the following cell, we create a service instance, which we’ll use to start a session, and get the backend object, which contains information for the transpiler. Since the transpilation process depends on the device, we’ll ask the runtime service for a specific device by name. In this example, we’ll use `ibm_algiers`

, which is only available through IBM Cloud.

```
[2]:
```

```
from qiskit_ibm_runtime import QiskitRuntimeService
service = QiskitRuntimeService()
backend = service.backend("ibm_algiers")
```

Next, we transpile the circuits for our backend. We’re going to compare the performance of the transpiler with `optimization_level`

set to `0`

(lowest) against `3`

(highest). The lowest optimization level just does the bare minimum needed to get the circuit running on the device; it maps the circuit qubits to the device qubits, and adds swaps gates to allow all 2-qubit operations. The highest optimization level is much smarter and uses lots of tricks to reduce the overall gate count. Since
multi-qubit gates have high error rates, and qubits decohere over time, the shorter circuits should give better results.

In the following cell, we transpile `qc`

for both values of `optimization_level`

, print the number of CNOT gates, and add the transpiled circuits to a list. Some of the transpiler’s algorithms are randomized, so we set a seed for reproducibility.

```
[3]:
```

```
# Need to add measurements to the circuit
qc.measure_all()
from qiskit import transpile
circuits = []
for optimization_level in [0, 3]:
t_qc = transpile(qc, backend, optimization_level=optimization_level, seed_transpiler=0)
print(f"CNOTs (optimization_level={optimization_level}): ", t_qc.count_ops()["cx"])
circuits.append(t_qc)
```

```
CNOTs (optimization_level=0): 27
CNOTs (optimization_level=3): 12
```

Since CNOTs usually have a high error rate, the circuit transpiled with `optimization_level=3`

should perform much better.

Another way we can improve performance is through dynamic decoupling, where we apply a sequence of gates to idling qubits. This cancels out some unwanted interactions with the environment. In the following cell, we add dynamic decoupling to the circuit transpiled with `optimization_level=3`

, and add it to our list.

```
[4]:
```

```
from qiskit.transpiler import PassManager, InstructionDurations
from qiskit.transpiler.passes import ASAPSchedule, DynamicalDecoupling
from qiskit.circuit.library import XGate
# Get gate durations so the transpiler knows how long each operation takes
durations = InstructionDurations.from_backend(backend)
# This is the sequence we'll apply to idling qubits
dd_sequence = [XGate(), XGate()]
# Run scheduling and dynamic decoupling passes on circuit
pm = PassManager([ASAPSchedule(durations), DynamicalDecoupling(durations, dd_sequence)])
circ_dd = pm.run(circuits[1])
# Add this new circuit to our list
circuits.append(circ_dd)
```

## Run user-transpiled circuits using Qiskit Runtime¶

At this point, we have a list of circuits (named `circuits`

) transpiled for `ibm_algiers`

. In the following cell, we create an instance of the sampler primitive, and start a session using the context manager (`with ...:`

), which automatically opens and closes the session for us. This is where we pass the `skip_transpilation=True`

argument.

Within the context manager, we sample the circuits and store the results to `result`

.

```
[5]:
```

```
from qiskit_ibm_runtime import Sampler, Session
with Session(service=service, backend=backend):
sampler = Sampler()
job = sampler.run(
circuits=circuits, # sample all three circuits
skip_transpilation=True,
shots=8000,
)
result = job.result()
```

Finally, we can plot the results from the device runs against the ideal distribution. You can see the results with `optimization_level=3`

are closer to the ideal distribution due to the lower gate count, and `optimization_level=3 + dd`

is even closer due to the dynamic decoupling we applied.

```
[6]:
```

```
from qiskit.visualization import plot_histogram
binary_prob = [quasi_dist.binary_probabilities() for quasi_dist in result.quasi_dists]
plot_histogram(
binary_prob + [ideal_distribution],
bar_labels=False,
legend=[
"optimization_level=0",
"optimization_level=3",
"optimization_level=3 + dd",
"ideal distribution",
],
)
```

```
[6]:
```

We can confirm this by computing the Hellinger fidelity between each set of results and the ideal distribution (higher is better, and 1 is perfect fidelity).

```
[7]:
```

```
from qiskit.quantum_info import hellinger_fidelity
for counts in result.quasi_dists:
print(f"{hellinger_fidelity(counts.binary_probabilities(), ideal_distribution):.3f}")
```

```
0.927
0.938
0.951
```

```
[8]:
```

```
import qiskit_ibm_runtime
qiskit_ibm_runtime.version.get_version_info()
```

```
[8]:
```

```
'0.4.0'
```

```
[9]:
```

```
from qiskit.tools.jupyter import *
%qiskit_version_table
%qiskit_copyright
```

### Version Information

Qiskit Software | Version |
---|---|

`qiskit-terra` | 0.20.0 |

`qiskit-aer` | 0.9.1 |

`qiskit-ignis` | 0.7.0 |

`qiskit-ibmq-provider` | 0.18.3 |

`qiskit-aqua` | 0.9.5 |

`qiskit` | 0.34.0 |

`qiskit-nature` | 0.3.0 |

`qiskit-finance` | 0.2.1 |

`qiskit-optimization` | 0.2.3 |

`qiskit-machine-learning` | 0.2.1 |

System information | |

Python version | 3.9.10 |

Python compiler | Clang 13.0.0 (clang-1300.0.29.3) |

Python build | main, Jan 15 2022 11:48:00 |

OS | Darwin |

CPUs | 8 |

Memory (Gb) | 32.0 |

Wed Apr 13 19:59:49 2022 BST |

### This code is a part of Qiskit

© Copyright IBM 2017, 2022.

This code is licensed under the Apache License, Version 2.0. You may

obtain a copy of this license in the LICENSE.txt file in the root directory

of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.

Any modifications or derivative works of this code must retain this

copyright notice, and modified files need to carry a notice indicating

that they have been altered from the originals.