---
title: Quantum circuit optimization
description: This lesson will address several aspects of circuit optimization in quantum computing.
source: https://eu-de.quantum.cloud.ibm.com/learning/en/courses/utility-scale-quantum-computing/quantum-circuit-optimization
---

# Quantum circuit optimization

> **Note**
>
> Toshinari Itoko (21 June 2024)
>
> [Download the pdf](https://ibm.ent.box.com/public/static/0hvvgr1gnwx64x2ukgk04sss6sxc4zko.zip) of the original lecture. Note that some code snippets might become deprecated since these are static images.
>
> *Approximate QPU time to run this experiment is 15 s.*
>
> (Note: Some cells of part 2 are copied from the notebook "Qiskit Deep dive", written by Matthew Treinish (Qiskit maintainer))

```python
# !pip install 'qiskit[visualization]'
# !pip install qiskit_ibm_runtime qiskit_aer
# !pip install jupyter
# !pip install matplotlib pylatexenc pydot pillow
```

```python
import qiskit

qiskit.__version__
```

Output:

```
'2.0.2'
```

```python
import qiskit_ibm_runtime

qiskit_ibm_runtime.__version__
```

Output:

```
'0.40.1'
```

```python
import qiskit_aer

qiskit_aer.__version__
```

Output:

```
'0.17.1'
```

## 1. Introduction

This lesson will address several aspects of circuit optimization in quantum computing. Specifically, we will see the value of circuit optimization by using optimization settings built into Qiskit. Then we will go a bit deeper and see what you can do as an expert in your particular application area to build circuits in a smart way. Finally, we will take a close look at what goes on during transpilation that helps us optimize our circuits.

## 2. Circuit optimization matters

We first compare the results of running 5-qubit GHZ state ($\frac{1}{\sqrt{2}} \left( |00000\rangle + |11111\rangle \right)$) preparation circuits with and without optimization.

```python
from qiskit.circuit import QuantumCircuit
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.primitives import BackendSamplerV2 as Sampler
```

```python
from qiskit_ibm_runtime.fake_provider import FakeBrisbane

backend = FakeBrisbane()
```

We first use a GHZ circuit naively synthesized as follows.

```python
num_qubits = 5

ghz_circ = QuantumCircuit(num_qubits)
ghz_circ.h(0)
[ghz_circ.cx(0, i) for i in range(1, num_qubits)]
ghz_circ.measure_all()
ghz_circ.draw("mpl")
```

Output:

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/learning/images/courses/utility-scale-quantum-computing/quantum-circuit-optimization/extracted-outputs/262b97e5-0.avif)

### 2.1 Optimization level

`optimization_level` has four options, from 0 to 3. The higher the optimization level, the more computational effort is spent to optimize the circuit. Level 0 performs no optimization and just does the minimal amount of work to make the circuit runnable on the selected backend. Level 3 spends the most amount if effort (and typically runtime) to try to optimize the circuit. Level 1 is the default optimization level.

We transpile the circuit without optimization (`optimization_level=0`) and with optimization (`optimization_level=2`).
We see a big difference in the circuit length of transpiled circuits.

```python
pm0 = generate_preset_pass_manager(
    optimization_level=0, backend=backend, seed_transpiler=777
)
pm2 = generate_preset_pass_manager(
    optimization_level=2, backend=backend, seed_transpiler=777
)
circ0 = pm0.run(ghz_circ)
circ2 = pm2.run(ghz_circ)
print("optimization_level=0:")
display(circ0.draw("mpl", idle_wires=False, fold=-1))
print("optimization_level=2:")
display(circ2.draw("mpl", idle_wires=False, fold=-1))
```

Output:

```
optimization_level=0:
```

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/learning/images/courses/utility-scale-quantum-computing/quantum-circuit-optimization/extracted-outputs/042d2bbc-1.avif)

```
optimization_level=2:
```

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/learning/images/courses/utility-scale-quantum-computing/quantum-circuit-optimization/extracted-outputs/042d2bbc-3.avif)

### 2.2 Exercise

Try `optimization_level=1` as well and compare the resulting circuit with the above two. Try it by modifying the code above.

**Solution:**

```python
pm1 = generate_preset_pass_manager(
    optimization_level=1, backend=backend, seed_transpiler=777
)
circ1 = pm1.run(ghz_circ)
print("optimization_level=1:")
display(circ1.draw("mpl", idle_wires=False, fold=-1))
```

Output:

```
optimization_level=1:
```

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/learning/images/courses/utility-scale-quantum-computing/quantum-circuit-optimization/extracted-outputs/6e8389e1-1.avif)

Run on a fake backend (noisy simulation). See Appendix 1 for how to run on a real backend.

```python
# run the circuits on the fake backend (noisy simulator)
sampler = Sampler(backend=backend)
job = sampler.run([circ0, circ2], shots=10000)
print(f"Job ID: {job.job_id()}")
```

Output:

```
Job ID: 93a4ac70-e3ea-44ad-aea9-5045840c9076
```

```python
# get results
result = job.result()
unoptimized_result = result[0].data.meas.get_counts()
optimized_result = result[1].data.meas.get_counts()
```

```python
from qiskit.visualization import plot_histogram

# plot
sim_result = {"0" * 5: 0.5, "1" * 5: 0.5}
plot_histogram(
    [result for result in [sim_result, unoptimized_result, optimized_result]],
    bar_labels=False,
    legend=[
        "ideal",
        "no optimization",
        "with optimization",
    ],
)
```

Output:

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/learning/images/courses/utility-scale-quantum-computing/quantum-circuit-optimization/extracted-outputs/5d344bb9-0.avif)

## 3. Circuit synthesis matters

We next compare the results of running two differently synthesized 5-qubit GHZ state ($\frac{1}{\sqrt{2}} \left( |00000\rangle + |11111\rangle \right)$) preparation circuits.

```python
# Original GHZ circuit (naive synthesis)
ghz_circ.draw("mpl")
```

Output:

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/learning/images/courses/utility-scale-quantum-computing/quantum-circuit-optimization/extracted-outputs/896dc520-0.avif)

```python
# A cleverly-synthesized GHZ circuit
ghz_circ2 = QuantumCircuit(5)
ghz_circ2.h(2)
ghz_circ2.cx(2, 1)
ghz_circ2.cx(2, 3)
ghz_circ2.cx(1, 0)
ghz_circ2.cx(3, 4)
ghz_circ2.measure_all()
ghz_circ2.draw("mpl")
```

Output:

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/learning/images/courses/utility-scale-quantum-computing/quantum-circuit-optimization/extracted-outputs/d27a9d9b-0.avif)

```python
# transpile both with the same optimization level 2
circ_org = pm2.run(ghz_circ)
circ_new = pm2.run(ghz_circ2)
print("original synthesis:")
display(circ_org.draw("mpl", idle_wires=False, fold=-1))
print("new synthesis:")
display(circ_new.draw("mpl", idle_wires=False, fold=-1))
```

Output:

```
original synthesis:
```

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/learning/images/courses/utility-scale-quantum-computing/quantum-circuit-optimization/extracted-outputs/d4e16053-1.avif)

```
new synthesis:
```

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/learning/images/courses/utility-scale-quantum-computing/quantum-circuit-optimization/extracted-outputs/d4e16053-3.avif)

The new synthesis produces a shallower circuit. Why?

This is because the new circuit can be laid out on linearly connected qubits, so on IBM® Brisbane's heavy-hexagon coupling graph as well, while the original circuit requires star-shaped connectivity (a degree-4 node) and hence cannot be laid out on the heavy-hex coupling graph, which has nodes at most degree 3. As a result, the original circuit requires qubit routing that adds SWAP gates, increasing the gate count.

What we have done in the new circuit can be seen as a manual "coupling constraint-aware" circuit synthesis. In other words: manually solving circuit synthesis and circuit mapping at the same time.

```python
# run the circuits
sampler = Sampler(backend=backend)
job = sampler.run([circ_org, circ_new], shots=10000)
print(f"Job ID: {job.job_id()}")
```

Output:

```
Job ID: 19d635b0-4d8b-44c2-a76e-49e4b9078b1b
```

```python
# get results
result = job.result()
synthesis_org_result = result[0].data.meas.get_counts()
synthesis_new_result = result[1].data.meas.get_counts()
```

```python
# plot
sim_result = {"0" * 5: 0.5, "1" * 5: 0.5}
plot_histogram(
    [
        result
        for result in [
            sim_result,
            unoptimized_result,
            synthesis_org_result,
            synthesis_new_result,
        ]
    ],
    bar_labels=False,
    legend=[
        "ideal",
        "no optimization",
        "synthesis_org",
        "synthesis_new",
    ],
)
```

Output:

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/learning/images/courses/utility-scale-quantum-computing/quantum-circuit-optimization/extracted-outputs/80f8ef81-0.avif)

In general, circuit synthesis depends on application and it's too difficult for a software to cover all possible applications. Qiskit transpiler happens to have no functions of synthesizing GHZ state preparation circuit. In such a case, manual circuit synthesis as shown above is worth considering.

In this section, we look into the details of how Qiskit transpiler works using the following toy example circuit.

```python
# Build a toy example circuit
from math import pi
import itertools
from qiskit.circuit import QuantumCircuit
from qiskit.circuit.library import excitation_preserving

circuit = QuantumCircuit(4, name="Example circuit")
circuit.append(excitation_preserving(4, reps=1, flatten=True), range(4))
circuit.measure_all()

value_cycle = itertools.cycle([0, pi / 4, pi / 2, 3 * pi / 4, pi, 2 * pi])
circuit.assign_parameters(
    [x[1] for x in zip(range(len(circuit.parameters)), value_cycle)], inplace=True
)
circuit.draw("mpl")
```

Output:

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/learning/images/courses/utility-scale-quantum-computing/quantum-circuit-optimization/extracted-outputs/f2228937-0.avif)

### 3.1 Draw the entire Qiskit transpilation flow

We look into the transpiler passes (tasks) for `optimization_level=1`.

```python
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager

# There is no need to read this entire image, but this outputs all the steps in the transpile() call
# for optimization level 1
pm = generate_preset_pass_manager(1, backend, seed_transpiler=42)
pm.draw()
```

Output:

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/learning/images/courses/utility-scale-quantum-computing/quantum-circuit-optimization/extracted-outputs/74bd20af-0.avif)

The flow consists of six stages:

```python
print(pm.stages)
```

Output:

```
('init', 'layout', 'routing', 'translation', 'optimization', 'scheduling')
```

### 3.2 Draw an individual stage

First, let's draw all the tasks (transpiler passes) done in the `init` stage.

```python
pm.init.draw()
```

Output:

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/learning/images/courses/utility-scale-quantum-computing/quantum-circuit-optimization/extracted-outputs/09b4ffbe-0.avif)

We can run each individual stage. Let's run `init` stage for our circuit. By enabling logger, we can see the details of the run.

```python
import logging

logger = logging.getLogger()
logger.setLevel("INFO")

init_out = pm.init.run(circuit)
init_out.draw("mpl", fold=-1)
```

Output:

```
INFO:qiskit.passmanager.base_tasks:Pass: UnitarySynthesis - 0.03576 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: HighLevelSynthesis - 0.16618 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: BasisTranslator - 0.07176 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: InverseCancellation - 0.27299 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: ContractIdleWiresInControlFlow - 0.00811 (ms)
```

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/learning/images/courses/utility-scale-quantum-computing/quantum-circuit-optimization/extracted-outputs/a139da85-e5b4-4c7c-900f-da8a0b8a5989-1.avif)

### 3.3 Exercise

Draw `layout` stage passes and run the stage for the output circuit of the `init` stage (`init_out`), by modifying cells used above.

**Solution:**

```python
display(pm.layout.draw())
layout_out = pm.layout.run(init_out)
layout_out.draw("mpl", idle_wires=False, fold=-1)
```

Output:

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/learning/images/courses/utility-scale-quantum-computing/quantum-circuit-optimization/extracted-outputs/56024db6-0.avif)

```
INFO:qiskit.passmanager.base_tasks:Pass: SetLayout - 0.01001 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: TrivialLayout - 0.07129 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: CheckMap - 0.08917 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: VF2Layout - 1.24431 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: BarrierBeforeFinalMeasurements - 0.02599 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: SabreLayout - 5.11169 (ms)
```

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/learning/images/courses/utility-scale-quantum-computing/quantum-circuit-optimization/extracted-outputs/56024db6-2.avif)

Do the same thing for `translation` stage.

**Solution:**

```python
display(pm.translation.draw())
basis_out = pm.translation.run(layout_out)
basis_out.draw("mpl", idle_wires=False, fold=-1)
```

Output:

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/learning/images/courses/utility-scale-quantum-computing/quantum-circuit-optimization/extracted-outputs/fd7cec6b-0.avif)

```
INFO:qiskit.passmanager.base_tasks:Pass: UnitarySynthesis - 0.03386 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: HighLevelSynthesis - 0.02718 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: BasisTranslator - 2.64192 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: CheckGateDirection - 0.02217 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: GateDirection - 0.36502 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: BasisTranslator - 0.64778 (ms)
```

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/learning/images/courses/utility-scale-quantum-computing/quantum-circuit-optimization/extracted-outputs/fd7cec6b-2.avif)

Note: Any individual stage cannot always be run independently (as some of them need to carry over information from one previous stage).

### 3.4 Optimization Stage

The last default stage in the pipeline is optimization. After we've embedded the circuit for the target the circuit has expanded quite a bit. Most of this is due to inefficiencies in the equivalence relationships from basis translation and swap insertion. The optimization stage is used to try and minimize the size and depth of the circuit. It runs a series of passes in a `do while` loop until it reaches a steady output.

```python
# pm.pre_optimization.draw()
pm.optimization.draw()
```

Output:

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/learning/images/courses/utility-scale-quantum-computing/quantum-circuit-optimization/extracted-outputs/f86b9045-0.avif)

```python
logger = logging.getLogger()
logger.setLevel("INFO")
opt_out = pm.optimization.run(basis_out)
```

Output:

```
INFO:qiskit.passmanager.base_tasks:Pass: Depth - 0.30112 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: FixedPoint - 0.03195 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: Size - 0.01216 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: FixedPoint - 0.01001 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: Optimize1qGatesDecomposition - 0.63729 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: InverseCancellation - 0.41723 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: ContractIdleWiresInControlFlow - 0.01192 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: GatesInBasis - 0.05484 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: Depth - 0.08583 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: FixedPoint - 0.20599 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: Size - 0.00787 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: FixedPoint - 0.00715 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: Optimize1qGatesDecomposition - 0.16809 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: InverseCancellation - 0.17190 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: ContractIdleWiresInControlFlow - 0.00691 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: GatesInBasis - 0.02408 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: Depth - 0.04935 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: FixedPoint - 0.00525 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: Size - 0.00620 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: FixedPoint - 0.00286 (ms)
```

```python
opt_out.draw("mpl", idle_wires=False, fold=-1)
```

Output:

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/learning/images/courses/utility-scale-quantum-computing/quantum-circuit-optimization/extracted-outputs/65d650b0-ec27-4b1b-a121-f1bb958b18e2-0.avif)

## 4. In-depth examples

### 4.1 Two-qubit block optimization using two-qubit unitary synthesis

For level 2 and 3, we have more passes (`Collect2qBlocks`, `ConsolidateBlocks`, `UnitarySynthesis`) for more optimization, namely two-qubit block optimization. (Compare the optimization stage flow for level 2 with that above for level 1)

The two-qubit block optimization is composed of two steps: Collecting and consolidating 2-qubit blocks and synthesizing the 2-qubit unitary matrices.

```python
pm2 = generate_preset_pass_manager(2, backend, seed_transpiler=42)
pm2.optimization.draw()
```

Output:

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/learning/images/courses/utility-scale-quantum-computing/quantum-circuit-optimization/extracted-outputs/179b1440-0.avif)

```python
from qiskit.transpiler import PassManager
from qiskit.transpiler.passes import (
    Collect2qBlocks,
    ConsolidateBlocks,
    UnitarySynthesis,
)

# Collect 2q blocks and consolidate to unitary when we expect that we can reduce the 2q gate count
# for that unitary
consolidate_pm = PassManager(
    [
        Collect2qBlocks(),
        ConsolidateBlocks(target=backend.target),
    ]
)
```

```python
display(basis_out.draw("mpl", idle_wires=False, fold=-1))

consolidated = consolidate_pm.run(basis_out)
consolidated.draw("mpl", idle_wires=False, fold=-1)
```

Output:

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/learning/images/courses/utility-scale-quantum-computing/quantum-circuit-optimization/extracted-outputs/bbf4fa9a-6b49-4833-82fd-b3821f6bcb78-0.avif)

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/learning/images/courses/utility-scale-quantum-computing/quantum-circuit-optimization/extracted-outputs/bbf4fa9a-6b49-4833-82fd-b3821f6bcb78-1.avif)

```python
# Synthesize unitaries
UnitarySynthesis(target=backend.target)(consolidated).draw(
    "mpl", idle_wires=False, fold=-1
)
```

Output:

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/learning/images/courses/utility-scale-quantum-computing/quantum-circuit-optimization/extracted-outputs/7e7e0d3b-d267-4b1c-b207-42556d1ff3f2-0.avif)

```python
logger.setLevel("WARNING")
```

We saw in Part 2 that the real quantum compiler flow is not that simple and is composed of many passes (tasks). This is mainly due to the software engineering required to ensure performance for a wide range of application circuits and maintainability of the software. Qiskit transpiler would work well in most cases but if you happen to see your circuit is not well optimized by Qiskit transpiler, it would be a good opportunity to research your own application-specific circuit optimization as shown in Part 1. Transpiler technology is evolving, your R\&D contribution is welcome.

```python
from qiskit.circuit import QuantumCircuit
from qiskit_ibm_runtime import QiskitRuntimeService, Sampler
```

```python
service = QiskitRuntimeService()
backend = service.backend("ibm_brisbane")
sampler = Sampler(backend)
```

```python
circ = QuantumCircuit(3)
circ.ccx(0, 1, 2)
circ.measure_all()
circ.draw("mpl")
```

Output:

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/learning/images/courses/utility-scale-quantum-computing/quantum-circuit-optimization/extracted-outputs/30a84ca1-0.avif)

```python
sampler.run([circ])  # IBMInputValueError will be raised
```

Output:

```
---------------------------------------------------------------------------
IBMInputValueError                        Traceback (most recent call last)
Cell In[44], line 1
----> 1 sampler.run([circ])  # IBMInputValueError will be raised

File /opt/homebrew/Caskroom/miniforge/base/envs/doc/lib/python3.11/site-packages/qiskit_ibm_runtime/sampler.py:111, in SamplerV2.run(self, pubs, shots)
    107 coerced_pubs = [SamplerPub.coerce(pub, shots) for pub in pubs]
    109 validate_classical_registers(coerced_pubs)
--> 111 return self._run(coerced_pubs)

File /opt/homebrew/Caskroom/miniforge/base/envs/doc/lib/python3.11/site-packages/qiskit_ibm_runtime/base_primitive.py:158, in BasePrimitiveV2._run(self, pubs)
    156 for pub in pubs:
    157     if getattr(self._backend, "target", None) and not is_simulator(self._backend):
--> 158         validate_isa_circuits([pub.circuit], self._backend.target)
    160     if isinstance(self._backend, IBMBackend):
    161         self._backend.check_faulty(pub.circuit)

File /opt/homebrew/Caskroom/miniforge/base/envs/doc/lib/python3.11/site-packages/qiskit_ibm_runtime/utils/validations.py:96, in validate_isa_circuits(circuits, target)
     94 message = is_isa_circuit(circuit, target)
     95 if message:
---> 96     raise IBMInputValueError(
     97         message
     98         + " Circuits that do not match the target hardware definition are no longer "
     99         "supported after March 4, 2024. See the transpilation documentation "
    100         "(/docs/guides/transpile) for instructions "
    101         "to transform circuits and the primitive examples "
    102         "(/docs/guides/primitives-examples) to see "
    103         "this coupled with operator transformations."
    104     )

IBMInputValueError: 'The instruction ccx on qubits (0, 1, 2) is not supported by the target system. Circuits that do not match the target hardware definition are no longer supported after March 4, 2024. See the transpilation documentation (/docs/guides/transpile) for instructions to transform circuits and the primitive examples (/docs/guides/primitives-examples) to see this coupled with operator transformations.'
```

### 4.2 Circuit optimization matters

We first compare the results of running 5-qubit GHZ state ($\frac{1}{\sqrt{2}} \left( |00000\rangle + |11111\rangle \right)$) preparation circuits with and without optimization.

```python
from qiskit.circuit import QuantumCircuit
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService, Sampler
```

```python
service = QiskitRuntimeService()
```

```python
# backend = service.backend('ibm_brisbane')
backend = service.least_busy(
    operational=True, simulator=False, min_num_qubits=127
)  # Eagle
backend
```

We first use a GHZ circuit naively synthesized as follows.

```python
num_qubits = 5

ghz_circ = QuantumCircuit(num_qubits)
ghz_circ.h(0)
[ghz_circ.cx(0, i) for i in range(1, num_qubits)]
ghz_circ.measure_all()
ghz_circ.draw("mpl")
```

Output:

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/learning/images/courses/utility-scale-quantum-computing/quantum-circuit-optimization/extracted-outputs/485b8ce6-0.avif)

We transpile the circuit without optimization (`optimization_level=0`) and with optimization (`optimization_level=2`).
As you can see, there is a big difference in the circuit length of transpiled circuits.

```python
pm0 = generate_preset_pass_manager(
    optimization_level=0, backend=backend, seed_transpiler=777
)
pm2 = generate_preset_pass_manager(
    optimization_level=2, backend=backend, seed_transpiler=777
)
circ0 = pm0.run(ghz_circ)
circ2 = pm2.run(ghz_circ)
print("optimization_level=0:")
display(circ0.draw("mpl", idle_wires=False, fold=-1))
print("optimization_level=2:")
display(circ2.draw("mpl", idle_wires=False, fold=-1))
```

Output:

```
optimization_level=0:
```

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/learning/images/courses/utility-scale-quantum-computing/quantum-circuit-optimization/extracted-outputs/87b861e2-1.avif)

```
optimization_level=2:
```

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/learning/images/courses/utility-scale-quantum-computing/quantum-circuit-optimization/extracted-outputs/87b861e2-3.avif)

```python
# run the circuits
sampler = Sampler(backend)
job = sampler.run([circ0, circ2], shots=10000)
job_id = job.job_id()
print(f"Job ID: {job_id}")
```

Output:

```
Job ID: d13rnnemya70008ek1zg
```

```python
# REPLACE WITH YOUR OWN JOB IDS
job = service.job(job_id)
```

```python
# get results
result = job.result()
unoptimized_result = result[0].data.meas.get_counts()
optimized_result = result[1].data.meas.get_counts()
```

```python
from qiskit.visualization import plot_histogram

# plot
sim_result = {"0" * 5: 0.5, "1" * 5: 0.5}
plot_histogram(
    [result for result in [sim_result, unoptimized_result, optimized_result]],
    bar_labels=False,
    legend=[
        "ideal",
        "no optimization",
        "with optimization",
    ],
)
```

Output:

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/learning/images/courses/utility-scale-quantum-computing/quantum-circuit-optimization/extracted-outputs/7527976e-0.avif)

### 4.3 Circuit synthesis matters

We next compare the results of running two differently synthesized 5-qubit GHZ state ($\frac{1}{\sqrt{2}} \left( |00000\rangle + |11111\rangle \right)$) preparation circuits.

```python
# Original GHZ circuit (naive synthesis)
ghz_circ.draw("mpl")
```

Output:

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/learning/images/courses/utility-scale-quantum-computing/quantum-circuit-optimization/extracted-outputs/886d9b45-0.avif)

```python
# A better GHZ circuit (smarter synthesis), you learned in a previous lecture
ghz_circ2 = QuantumCircuit(5)
ghz_circ2.h(2)
ghz_circ2.cx(2, 1)
ghz_circ2.cx(2, 3)
ghz_circ2.cx(1, 0)
ghz_circ2.cx(3, 4)
ghz_circ2.measure_all()
ghz_circ2.draw("mpl")
```

Output:

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/learning/images/courses/utility-scale-quantum-computing/quantum-circuit-optimization/extracted-outputs/3b559186-0.avif)

```python
circ_org = pm2.run(ghz_circ)
circ_new = pm2.run(ghz_circ2)
print("original synthesis:")
display(circ_org.draw("mpl", idle_wires=False, fold=-1))
print("new synthesis:")
display(circ_new.draw("mpl", idle_wires=False, fold=-1))
```

Output:

```
original synthesis:
```

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/learning/images/courses/utility-scale-quantum-computing/quantum-circuit-optimization/extracted-outputs/054890b6-1.avif)

```
new synthesis:
```

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/learning/images/courses/utility-scale-quantum-computing/quantum-circuit-optimization/extracted-outputs/054890b6-3.avif)

```python
# run the circuits
sampler = Sampler(backend)
job = sampler.run([circ_org, circ_new], shots=10000)
job_id = job.job_id()
print(f"Job ID: {job_id}")
```

Output:

```
Job ID: d13rp283grvg008j12fg
```

```python
# REPLACE WITH YOUR OWN JOB IDS
job = service.job(job_id)
```

```python
# get results
result = job.result()
synthesis_org_result = result[0].data.meas.get_counts()
synthesis_new_result = result[1].data.meas.get_counts()
```

```python
# plot
sim_result = {"0" * 5: 0.5, "1" * 5: 0.5}
plot_histogram(
    [result for result in [sim_result, synthesis_org_result, synthesis_new_result]],
    bar_labels=False,
    legend=[
        "ideal",
        "synthesis_org",
        "synthesis_new",
    ],
)
```

Output:

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/learning/images/courses/utility-scale-quantum-computing/quantum-circuit-optimization/extracted-outputs/b9021da5-0.avif)

### 4.4 General 1-qubit gate decomposition

```python
from qiskit import QuantumCircuit, transpile
from qiskit.circuit import Parameter
from qiskit.circuit.library.standard_gates import UGate

phi, theta, lam = Parameter("φ"), Parameter("θ"), Parameter("λ")
```

```python
qc = QuantumCircuit(1)
qc.append(UGate(theta, phi, lam), [0])
qc.draw(output="mpl")
```

Output:

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/learning/images/courses/utility-scale-quantum-computing/quantum-circuit-optimization/extracted-outputs/ed93f69a-0.avif)

```python
transpile(qc, basis_gates=["rz", "sx"]).draw(output="mpl")
```

Output:

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/learning/images/courses/utility-scale-quantum-computing/quantum-circuit-optimization/extracted-outputs/2fa17bd2-0.avif)

### 4.5 One-qubit block optimization

```python
from qiskit import QuantumCircuit

qc = QuantumCircuit(1)
qc.x(0)
qc.y(0)
qc.z(0)
qc.rx(1.23, 0)
qc.ry(1.23, 0)
qc.rz(1.23, 0)
qc.h(0)
qc.s(0)
qc.t(0)
qc.sx(0)
qc.sdg(0)
qc.tdg(0)
qc.draw(output="mpl")
```

Output:

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/learning/images/courses/utility-scale-quantum-computing/quantum-circuit-optimization/extracted-outputs/6f64d07d-0.avif)

```python
from qiskit.quantum_info import Operator

Operator(qc)
```

Output:

```
Operator([[ 0.45292511-0.57266982j, -0.66852684-0.14135058j],
          [ 0.14135058+0.66852684j, -0.57266982+0.45292511j]],
         input_dims=(2,), output_dims=(2,))
```

```python
from qiskit import transpile

qc_opt = transpile(qc, basis_gates=["rz", "sx"])
qc_opt.draw(output="mpl")
```

Output:

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/learning/images/courses/utility-scale-quantum-computing/quantum-circuit-optimization/extracted-outputs/c06f5e75-0.avif)

```python
Operator(qc_opt)
```

Output:

```
Operator([[ 0.45292511-0.57266982j, -0.66852684-0.14135058j],
          [ 0.14135058+0.66852684j, -0.57266982+0.45292511j]],
         input_dims=(2,), output_dims=(2,))
```

```python
Operator(qc).equiv(Operator(qc_opt))
```

Output:

```
True
```

### 4.6 Toffoli decomposition

```python
qc = QuantumCircuit(3)
qc.ccx(0, 1, 2)
qc.draw(output="mpl")
```

Output:

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/learning/images/courses/utility-scale-quantum-computing/quantum-circuit-optimization/extracted-outputs/f802c5df-0.avif)

```python
from qiskit import QuantumCircuit, transpile

qc = QuantumCircuit(3)
qc.ccx(0, 1, 2)
qc = transpile(qc, basis_gates=["rz", "sx", "cx"])
qc.draw(output="mpl")
```

Output:

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/learning/images/courses/utility-scale-quantum-computing/quantum-circuit-optimization/extracted-outputs/330cea7e-0.avif)

### 4.7 CU gate decomposition

```python
from qiskit.circuit.library.standard_gates import CUGate

phi, theta, lam, gamma = Parameter("φ"), Parameter("θ"), Parameter("λ"), Parameter("γ")
qc = QuantumCircuit(2)
# qc.cu(theta, phi, lam, gamma, 0, 1)
qc.append(CUGate(theta, phi, lam, gamma), [0, 1])
qc.draw(output="mpl")
```

Output:

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/learning/images/courses/utility-scale-quantum-computing/quantum-circuit-optimization/extracted-outputs/1df5876d-0.avif)

```python
from qiskit.circuit.library.standard_gates import CUGate

phi, theta, lam, gamma = Parameter("φ"), Parameter("θ"), Parameter("λ"), Parameter("γ")
qc = QuantumCircuit(2)
qc.append(CUGate(theta, phi, lam, gamma), [0, 1])
qc = transpile(qc, basis_gates=["rz", "sx", "cx"])
qc.draw(output="mpl")
```

Output:

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/learning/images/courses/utility-scale-quantum-computing/quantum-circuit-optimization/extracted-outputs/64f7e5f3-0.avif)

### 4.8 CX, ECR, CZ equal up to local Cliffords

Note that $H$(Hadamard), $S$($\pi/2$ Z-rotation), $S^\dagger$($-\pi/2$ Z-rotation), $X$(Pauli X) are all Clifford gates.

```python
qc = QuantumCircuit(2)
qc.cx(0, 1)
qc.draw(output="mpl", style="bw")
```

Output:

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/learning/images/courses/utility-scale-quantum-computing/quantum-circuit-optimization/extracted-outputs/f5b362b6-0.avif)

```python
qc = QuantumCircuit(2)
qc.cx(0, 1)
transpile(qc, basis_gates=["x", "s", "h", "sdg", "ecr"]).draw(output="mpl", style="bw")
```

Output:

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/learning/images/courses/utility-scale-quantum-computing/quantum-circuit-optimization/extracted-outputs/8740d07b-0.avif)

```python
qc = QuantumCircuit(2)
qc.cx(0, 1)
transpile(qc, basis_gates=["h", "cz"]).draw(output="mpl", style="bw")
```

Output:

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/learning/images/courses/utility-scale-quantum-computing/quantum-circuit-optimization/extracted-outputs/5113a7c5-0.avif)

Using IBM backend 1q basis gates "rz", "sx" and "x".

```python
qc = QuantumCircuit(2)
qc.cx(0, 1)
transpile(qc, basis_gates=["rz", "sx", "x", "ecr"]).draw(output="mpl", style="bw")
```

Output:

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/learning/images/courses/utility-scale-quantum-computing/quantum-circuit-optimization/extracted-outputs/9d9b54d4-0.avif)

```python
qc = QuantumCircuit(2)
qc.cx(0, 1)
transpile(qc, basis_gates=["rz", "sx", "x", "cz"]).draw(output="mpl", style="bw")
```

Output:

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/learning/images/courses/utility-scale-quantum-computing/quantum-circuit-optimization/extracted-outputs/c395cd24-0.avif)

```python
# Check Qiskit version
import qiskit

qiskit.__version__
```

Output:

```
'2.0.2'
```
