from qiskit import QuantumCircuit, transpile
from qiskit.providers.basic_provider import BasicProvider
from qiskit_aer import UnitarySimulator
from qiskit.visualization import plot_histogram
import qiskit.quantum_info as qi
from math import sqrt
import numpy as np
# Un simulateur de circuit complet
= BasicProvider().get_backend('basic_simulator')
sim
# Un simulateur calculant la matrice de transformation
= UnitarySimulator()
usim
# Afficher un circuit
def draw_circ(circ): display(circ.draw(output='mpl'))
# Calculer la matrice d'un circuit
def mat_circ(circ): return usim.run(circ).result().get_unitary()
def show(circ):
draw_circ(circ)'latex')) display(mat_circ(circ).draw(
Programmation Quantique (suite)
Nicolas Ollinger et Ioan Todinca
M2 info, S9 2024/2025
Circuits quantiques ?
Les ordinateurs quantiques d’aujourd’hui (ou plutôt de demain ?) s’utilisent comme des coprocesseurs : on y fait subir une transformation unitaire à un registre de qubits initialement tous à zéro avant d’effectuer des mesures sur son état.
Les transformations unitaires sont codées sous forme de circuits et la programmation quantique ressemble à de la programmation assembleur.
La programmation quantique propose une nouvelle forme de parallélisme.
Hello qiskit
Circuits quantiques
Circuits quantiques
Un circuit quantique est composé d’un nombre constant de fils qui représentent des états quantiques d’un système discret fini. Les fils sont les qubits.
Des portes quantiques permettent d’effectuer des opérations sur ces états jusqu’à obtenir un état final.
Seules des mesures de ces états permettent d’obtenir une information (partielle) en sortie.
Les qubits sont tous initialisés à la valeur \(\left|0\right>\).
Représentations et calculs
Pour raisonner sur ces objets circuits et les transformations unitaires qu’ils réalisent, plusieurs outils sont à notre disposition : - la représentation matricielle des circuits (de dimension \(2^n\) pour \(n\) qubits !) ; - le calcul avec la notation de Dirac \(\left<\phi\middle|\psi\right>\) ; - les outils graphiques du ZX-calcul !
Un qubit
Un registre de mémoire quantique est un système quantique discret fini constitué de qubits.
Un bit quantique est une superposition de \(0\) et de \(1\) : \[ \alpha \left|0\right> + \beta \left|1\right> \quad\mbox{avec}\quad \alpha, \beta \in \mathbb{C} \quad\mbox{tels que}\quad |\alpha|^2+|\beta|^2 = 1 \]
Par exemple \(\left|0\right>\) ou encore \(\frac{1}{\sqrt{2}}\left(\left|0\right>+\left|1\right>\right)\)
Des qubits
Un registre de \(n\) qubits est une superposition de chacun des vecteurs de bits possibles, chacun muni d’une amplitude.
\[ \left|\varphi\right> = \sum_{x\in\{0,1\}^n} \alpha_x\left|x\right> \quad\mbox{tels que}\quad \sqrt{\sum_{x\in\{0,1\}^n} |\alpha_x|^2} = 1 \]
Par exemple \(\frac12\left|1000\right>+\frac12\left|0100\right>+\frac12\left|0010\right>+\frac12\left|0001\right>\)
Produit tensoriel
La composition de deux registres quantiques s’obtient par l’opérateur bilinéaire de produit tensoriel \(\otimes\) qui satisfait \(\left|x\right> \otimes \left|y\right> = \left|xy\right>\).
\[ \left|1\right> \otimes \frac{1}{\sqrt{2}}\left(\left|0\right>+\left|1\right>\right) = \frac{1}{\sqrt{2}}\left(\left|10\right>+\left|11\right>\right) \]
Contrairement au cas classique, tout registre n’est pas décomposable en sous-registres !
Exemple d’état intriqué : \(\frac{1}{\sqrt{2}}\left(\left|00\right>+\left|11\right>\right)\).
Mesure quantique (observation)
Mesure quantique d’un qubit particulier
Partant d’un état \(\left|\varphi\right> = \sum_x \alpha_x\left|x\right>\), si on mesure le qubit numéro \(i\), on observe la valeur \(r\) avec la probabilité \(p_r\) et le système change d’état :
\[ \sum_{x\in\left\{0,1\right\}^n} \alpha_x\left|x\right> \underset{\phantom{blop}p_r\phantom{blop}}{\longrightarrow} \frac{1}{\sqrt{p_r}} \sum_{x\in\left\{0,1\right\}^n | x_i = r} \alpha_x\left|x\right> \]
\[ \mbox{avec}\quad p_r = \sum_{x\in\left\{0,1\right\}^n | x_i = r}|\alpha_x|^2 \]
Évolution unitaire
En l’absence de mesure, lorsqu’il est isolé, l’état d’un système quantique évolue de manière unitaire.
\[ U : \mathbb{C}^{\{0,1\}^n} \rightarrow \mathbb{C}^{\{0,1\}^n} \]
- \(U\) est linéaire : \[ U\left(\alpha\left|\varphi\right>+\beta\left|\psi\right>\right) = \alpha U\left|\varphi\right>+\beta U\left|\psi\right>\quad; \]
- \(U\) préserve la norme : \(\left\| U\left|\varphi\right> \right\| = \left\| \left|\varphi\right> \right\|\).
Porte quantique
Un circuit quantique code une transformation unitaire obtenue par composition de portes quantiques elles aussi unitaires, donc réversibles, avec autant de qubits en entrée qu’en sortie. - composition séquentielle : produit de matrices ; - composition parallèle : produit tensoriel.
Portes sur 1 qubit
\[ \begin{array}{clclcl} X :& \left|0\right> \mapsto \left|1\right> & H :& \left|0\right> \mapsto \frac{\left|0\right>+\left|1\right>}{\sqrt2} & R_\theta :& \left|0\right> \mapsto \left|0\right>\\ & \left|1\right> \mapsto \left|0\right> & & \left|1\right> \mapsto \frac{\left|0\right>-\left|1\right>}{\sqrt2} & & \left|1\right> \mapsto e^{i\theta}\left|1\right> \\ \end{array} \]
from math import pi
= QuantumCircuit(1)
circuit # remplacer ici le x par h ou t (=r(pi/4)) ou sx (sqrt(x))
0)
circuit.h( show(circuit)
$$
\[\begin{bmatrix} \frac{\sqrt{2}}{2} & \frac{\sqrt{2}}{2} \\ \frac{\sqrt{2}}{2} & - \frac{\sqrt{2}}{2} \\ \end{bmatrix}\]$$
Portes contrôlées
\[ \begin{array}{cl} CX :& \left|00\right> \mapsto \left|00\right> \\ & \left|01\right> \mapsto \left|01\right> \\ & \left|10\right> \mapsto \left|11\right> \\ & \left|11\right> \mapsto \left|10\right> \\ \end{array} \]
= QuantumCircuit(2)
circuit 0,1)
circuit.cx( show(circuit)
$$
\[\begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 0 & 0 & 1 \\ 0 & 0 & 1 & 0 \\ 0 & 1 & 0 & 0 \\ \end{bmatrix}\]$$
Échauffement
Que calcule ce circuit ?
= QuantumCircuit(2)
circuit 0,1)
circuit.cx(1,0)
circuit.cx(0,1)
circuit.cx( draw_circ(circuit)
Routage
Le processeur Quito peut effectuer des portes sur 1 qubit et des portes CX sur 2 qubits. Cette famille de portes est universelle.
Comment modifier un circuit complexe qui a besoin d’effectuer un CX entre les qubits 0 et 4 ? Quel en est le coût ?
Porte de Hadamard
Porte de Hadamard
La porte de Hadamard (H), sur 1 qubit, est une porte de changement de base.
\[ \begin{array}{clllll} H :& \left|0\right> &\mapsto &\left|+\right> & = &\frac{\left|0\right>+\left|1\right>}{\sqrt2} \\ & \left|1\right> &\mapsto &\left|-\right> & = &\frac{\left|0\right>-\left|1\right>}{\sqrt2} \\ \end{array} \]
En théorie, il suffit d’ajouter cette porte à la porte de Toffoli pour simuler polynomialement toute transformation unitaire (donc tout circuit quantique).
En pratique ce n’est pas très efficace, on procède autrement.
Exercice
Étudier ce circuit sur l’entrée \(\left|00\right>\).
= QuantumCircuit(2)
circuit 0)
circuit.h(0,1)
circuit.cx( draw_circ(circuit)
Propriétés
\[ \begin{array}{rl} H \cdot H &= \text{Id}\\ H : \quad\left|x\right> &\mapsto \frac1{\sqrt 2}\sum_{y\in\{0,1\}} (-1)^{xy} \left|y\right>\\ H^{\otimes n} : \quad\left|x\right> &\mapsto \frac1{\sqrt {2^n}}\sum_{y\in\{0,1\}^n} (-1)^{x\bullet y} \left|y\right> \qquad\text{avec}\; x\bullet y = \bigoplus_i x_i y_i \end{array} \]
Exercice
Calculer \(H^{\otimes n} \left( \left|0\right>^{\otimes n}\right)\)
Étudier ce circuit
= QuantumCircuit(2)
circuit 0)
circuit.h(1)
circuit.h(0,1)
circuit.cx(0)
circuit.h(1)
circuit.h( draw_circ(circuit)
Vérification assistée par ordinateur
= QuantumCircuit(2)
circuit 0)
circuit.h(1)
circuit.h(0,1)
circuit.cx(0)
circuit.h(1)
circuit.h( show(circuit)
$$
\[\begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 1 \\ 0 & 0 & 1 & 0 \\ \end{bmatrix}\]$$
= QuantumCircuit(2)
circuit 1,0)
circuit.cx( show(circuit)
$$
\[\begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 1 \\ 0 & 0 & 1 & 0 \\ \end{bmatrix}\]$$
Oracle
Pour toute fonction \(f : \{0,1\}^n\rightarrow \{0,1\}\), nous avons vu qu’il existe un circuit quantique à \(n+1\) qubits utiles (+ des qubits auxiliares) qui sur l’entrée \(\left|x\right>\otimes\left|y\right>\) calcule \(\left|x\right>\otimes\left|y \oplus f(x)\right>\).
Montrer qu’en ajoutant une porte \(X\) et une porte \(H\) de part et d’autre du circuit sur le fil de \(\left|y\right>\), on obtient un circuit qui sur l’entrée \(\left|x\right>\) calcule \((-1)^{f(x)} \left|x\right>\) à l’aide d’un qubit auxiliaire à \(\left|0\right>\).
Algorithme de Bernstein-Vazirani
Faire mieux en quantique ?
Les algorithmes quantiques dépassent les algorithmes classiques non pas sur ce qu’ils peuvent calculer mais par leur capacité à effectuer certains calculs asymptotiquement plus vite que les meilleurs algorithmes classiques connus : - factorisation d’entiers (Shor) ; - recherche d’un élément dans une liste non triée (Grover) ; - …
Regardons un exemple simple ensemble.
Bernstein-Vazirani
Cet algorithme est de type boîte noire.
On suppose disposer d’un circuit classique qui, sur l’entrée \(x \in \{0,1\}^{n}\), calcule la valeur \(f(x) = x\odot s\) où \(s\) est un vecteur caché de \(n\) bits et \(x\odot s = x_1 s_1 \oplus x_2 s_2 \oplus \cdots \oplus x_n s_n\).
L’objectif est de retrouver \(s\) en le moins d’appels possible à \(f\).
Un rapide raisonnement de théorie de l’information montre qu’un algorithme classique a besoin d’appeler \(f\) au moins \(n\) fois pour retrouver \(s\) (et y arrive toujours).
Bernstein-Vazirani (suite)
L’algorithme quantique proposé par Bernstein et Vazirani retrouve \(s\) en un unique appel à \(f\) !
On suppose disposer d’un circuit quantique qui, sur l’entrée \((x\otimes y)\in \{\left|0\right>,\left|1\right>\}^{n+1}\), calcule la valeur \(x \otimes (y \oplus x\odot s)\).
Construisons un circuit qui illustre cet algorithme !
= '0110'
s = len(s)
k
= QuantumCircuit(2*k+1,k)
circuit
# superposition
circuit.x(k)for i in range(k+1):
circuit.h(i)
circuit.barrier()# calcul parallèle
for i in range(k):
if s[i] == '1':
+1+i)
circuit.x(k
for i in range(k):
+1+i,k)
circuit.ccx(i,k
for i in range(k):
if s[i] == '1':
+1+i)
circuit.x(k
circuit.barrier()# transformation quantique
for i in range(k):
circuit.h(i)# mesure
list(range(k)), list(range(k)))
circuit.measure(
='mpl',scale=1))
display(circuit.draw(output= sim.run(circuit, shots=1).result()
res print(res.get_counts(circuit))
{'0110': 1}
Exécuter à distance
Exécuter le code sur un processeur quantique
Quelle différence entre notre simulateur et la vraie vie ?
Modifions l’exemple en un circuit équivalent pour le faire tenir sur moins de bits et le rendre un peu plus résistant au bruit.
Observons le résultat obtenu et le circuit exécuté.
= '0110'
s = len(s)
k
= QuantumCircuit(k+1,k)
circuit
circuit.x(k)for i in range(k+1):
circuit.h(i)
circuit.barrier()
for i in range(k):
if s[i] == '1':
circuit.cx(i,k)
circuit.barrier()
for i in range(k):
circuit.h(i)
list(range(k)), list(range(k)))
circuit.measure(
='mpl',scale=1))
display(circuit.draw(output= sim.run(circuit, shots=1).result()
res print(res.get_counts(circuit))
{'0110': 1}
from qiskit_ibm_runtime import QiskitRuntimeService
= QiskitRuntimeService()
service = service.least_busy(operational=True, simulator=False, min_num_qubits=5)
backend
# Get the least busy real quantum system
print(backend, backend.status().pending_jobs)
<IBMBackend('ibm_sherbrooke')> 20
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
= backend.target
target = generate_preset_pass_manager(target=target, optimization_level=3)
pm
= pm.run(circuit)
circuit_isa ="mpl", idle_wires=False, style="iqp") circuit_isa.draw(output
from qiskit_ibm_runtime import SamplerV2 as Sampler
= Sampler(backend)
sampler = 10_000
sampler.options.default_shots = sampler.run([circuit_isa]).result() result
0].data.c.get_counts()) plot_histogram(result[