Vai al contenuto principale

Implementazione con Qiskit

In questa sezione vedremo alcune implementazioni Qiskit dei concetti introdotti in questa lezione. Se vuoi eseguire queste implementazioni in autonomia, cosa fortemente consigliata, consulta la pagina Installa Qiskit nella documentazione IBM Quantum per i dettagli su come configurare Qiskit.

È bene tenere presente che Qiskit è in continua evoluzione e si concentra principalmente sul massimizzare le prestazioni dei computer quantistici con cui opera, i quali a loro volta continuano a evolversi. Di conseguenza, Qiskit è soggetto a modifiche che possono occasionalmente portare alla deprecazione di alcune parti del codice. Con questo in mente, eseguiremo sempre i seguenti comandi prima di presentare esempi di codice Qiskit in questo corso, in modo da rendere chiara la versione di Qiskit utilizzata. A partire da Qiskit v1.0, questo è un modo semplice per verificare quale versione di Qiskit è attualmente installata.

# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit
from qiskit import __version__

print(__version__)
2.1.1

Se stai eseguendo questo codice in un ambiente Python basato su cloud, potresti dover installare alcuni dei seguenti pacchetti:

#!pip install qiskit
#!pip install jupyter
#!pip install sympy
#!pip install matplotlib
#!pip install pylatexenc

Vettori e matrici in Python​

Qiskit utilizza il linguaggio di programmazione Python, quindi prima di parlare specificamente di Qiskit può essere utile fare una breve panoramica sui calcoli con matrici e vettori in Python.

In Python, i calcoli con matrici e vettori possono essere eseguiti usando la classe array della libreria NumPy, che offre funzionalità per molti calcoli numerici e scientifici. Il codice seguente carica questa libreria, definisce due vettori colonna, ket0 e ket1, corrispondenti ai vettori di stato del qubit ∣0⟩\vert 0\rangle e ∣1⟩,\vert 1\rangle, e ne stampa la media.

import numpy as np

ket0 = np.array([[1], [0]])
ket1 = np.array([[0], [1]])

print(ket0 / 2 + ket1 / 2)
[[0.5]
[0.5]]

Possiamo usare array anche per creare matrici che rappresentano operazioni.

M1 = np.array([[1, 1], [0, 0]])
M2 = np.array([[1, 0], [0, 1]])
M = M1 / 2 + M2 / 2
print(M)
[[1.  0.5]
[0. 0.5]]

Tieni presente che tutto il codice presente all'interno di una data lezione di questo corso deve essere eseguito in sequenza. Pertanto, non è necessario importare NumPy di nuovo qui, poiché è già stato importato in precedenza.

La moltiplicazione tra matrici, inclusa la moltiplicazione matrice-vettore come caso particolare, può essere eseguita usando la funzione matmul di NumPy.

print(np.matmul(M1, ket1))
print(np.matmul(M1, M2))
print(np.matmul(M, M))
[[1]
[0]]
[[1 1]
[0 0]]
[[1. 0.75]
[0. 0.25]]

Questa formattazione dell'output lascia un po' a desiderare dal punto di vista visivo. Una soluzione, per situazioni in cui è richiesta una presentazione più curata, è usare la funzione array_to_latex di Qiskit, dal modulo qiskit.visualization. Nota che nel codice che segue stiamo usando la funzione generica display di Python. Al contrario, il comportamento specifico di print può variare a seconda di cosa viene stampato, come accade per gli array definiti da NumPy.

from qiskit.visualization import array_to_latex

display(array_to_latex(np.matmul(M1, ket1)))
display(array_to_latex(np.matmul(M1, M2)))
display(array_to_latex(np.matmul(M, M)))
[10] \begin{bmatrix} 1 \\ 0 \\ \end{bmatrix} [1100] \begin{bmatrix} 1 & 1 \\ 0 & 0 \\ \end{bmatrix} [134014] \begin{bmatrix} 1 & \frac{3}{4} \\ 0 & \frac{1}{4} \\ \end{bmatrix}

Stati, misurazioni e operazioni​

Qiskit include diverse classi che permettono di creare e manipolare stati, misurazioni e operazioni — quindi non è necessario programmare da zero tutto il necessario per simulare stati quantistici, misurazioni e operazioni in Python. Di seguito sono inclusi alcuni esempi per aiutarti a iniziare.

Definire e visualizzare i vettori di stato​

La classe Statevector in Qiskit offre funzionalità per definire e manipolare i vettori di stato quantistici. Nel codice che segue, la classe Statevector viene importata e vengono definiti alcuni vettori. (Importiamo anche la funzione sqrt dalla libreria NumPy per calcolare la radice quadrata. Questa funzione potrebbe in alternativa essere chiamata come np.sqrt a condizione che NumPy sia già stato importato, come avvenuto in precedenza; questo è semplicemente un modo diverso di importare e usare questa specifica funzione.)

from qiskit.quantum_info import Statevector
from numpy import sqrt

u = Statevector([1 / sqrt(2), 1 / sqrt(2)])
v = Statevector([(1 + 2.0j) / 3, -2 / 3])
w = Statevector([1 / 3, 2 / 3])

La classe Statevector include un metodo draw per visualizzare i vettori di stato in vari modi, tra cui text per testo semplice, latex per LaTeX renderizzato, e latex_source per il codice LaTeX, utile per copiare e incollare nei documenti. (Usa print invece di display per mostrare il codice LaTeX con i migliori risultati.)

display(u.draw("text"))
display(u.draw("latex"))
print(u.draw("latex_source"))
[0.70710678+0.j,0.70710678+0.j]

22∣0⟩+22∣1⟩\frac{\sqrt{2}}{2} |0\rangle+\frac{\sqrt{2}}{2} |1\rangle

\frac{\sqrt{2}}{2} |0\rangle+\frac{\sqrt{2}}{2} |1\rangle

La classe Statevector include anche il metodo is_valid, che verifica se un dato vettore è un vettore di stato quantistico valido (ovvero che abbia norma euclidea uguale a 1):

display(u.is_valid())
display(w.is_valid())
True
False

Simulare misurazioni con Statevector​

Vedremo ora un modo per simulare le misurazioni degli stati quantistici in Qiskit, usando il metodo measure della classe Statevector. Usiamo lo stesso vettore di stato del qubit v definito in precedenza.

display(v.draw("latex"))

(13+2i3)∣0⟩−23∣1⟩(\frac{1}{3} + \frac{2 i}{3}) |0\rangle- \frac{2}{3} |1\rangle

Eseguendo il metodo measure si simula una misurazione nella base standard. Restituisce il risultato della misurazione, più il nuovo vettore di stato quantistico del sistema dopo la misurazione. (Qui stiamo usando la funzione print di Python con il prefisso f per la stampa formattata con espressioni incorporate.)

outcome, state = v.measure()
print(f"Measured: {outcome}\nPost-measurement state:")
display(state.draw("latex"))
Measured: 1
Post-measurement state:

−∣1⟩- |1\rangle

I risultati delle misurazioni sono probabilistici, quindi questo metodo può restituire risultati diversi se eseguito più volte. Per il particolare esempio del vettore v definito sopra, il metodo measure definisce il vettore di stato quantistico dopo che la misurazione ha avuto luogo come

(1+2i5)∣0⟩\biggl(\frac{1 + 2i}{\sqrt{5}}\biggr) \vert 0\rangle

(anziché ∣0⟩\vert 0\rangle) oppure

−∣1⟩- \vert 1\rangle

(anziché ∣1⟩\vert 1\rangle), a seconda del risultato della misurazione. In entrambi i casi, le alternative a ∣0⟩\vert 0\rangle e ∣1⟩\vert 1\rangle sono, di fatto, equivalenti a questi vettori di stato; si dice che sono equivalenti a meno di una fase globale perché l'uno è uguale all'altro moltiplicato per un numero complesso sul cerchio unitario. Questa questione è trattata in modo più approfondito nella lezione Quantum circuits e può essere tranquillamente ignorata per ora.

Statevector genererà un errore se il metodo measure viene applicato a un vettore di stato quantistico non valido.

Statevector include anche un metodo sample_counts che consente di simulare un numero qualsiasi di misurazioni sul sistema, partendo ogni volta da una copia fresca dello stato. Ad esempio, il codice seguente mostra il risultato della misurazione del vettore v per 10001000 volte, il che (con alta probabilità) produce il risultato 00 circa 55 volte ogni 99 (ossia circa 556556 delle 10001000 prove) e il risultato 11 circa 44 volte ogni 99 (ossia circa 444444 delle 10001000 prove). Il codice che segue mostra anche la funzione plot_histogram dal modulo qiskit.visualization per visualizzare i risultati.

from qiskit.visualization import plot_histogram

statistics = v.sample_counts(1000)
plot_histogram(statistics)

Output of the previous code cell

Eseguire questo codice più volte in autonomia, con diversi numeri di campioni al posto di 1000,1000, può essere utile per sviluppare un'intuizione su come il numero di prove influenza il numero di volte in cui appare ciascun risultato. Con un numero sempre maggiore di campioni, la frazione di campioni per ogni possibilità tende ad avvicinarsi sempre di più alla probabilità corrispondente. Questo fenomeno, più in generale, è noto come la legge dei grandi numeri nella teoria della probabilità.

Eseguire operazioni con Operator e Statevector​

Le operazioni unitarie possono essere definite in Qiskit usando la classe Operator, come nell'esempio che segue. Questa classe include un metodo draw con argomenti simili a quelli di Statevector. Nota che l'opzione latex produce risultati equivalenti ad array_to_latex.

from qiskit.quantum_info import Operator

Y = Operator([[0, -1.0j], [1.0j, 0]])
H = Operator([[1 / sqrt(2), 1 / sqrt(2)], [1 / sqrt(2), -1 / sqrt(2)]])
S = Operator([[1, 0], [0, 1.0j]])
T = Operator([[1, 0], [0, (1 + 1.0j) / sqrt(2)]])

display(T.draw("latex"))
[10022+2i2] \begin{bmatrix} 1 & 0 \\ 0 & \frac{\sqrt{2}}{2} + \frac{\sqrt{2} i}{2} \\ \end{bmatrix}

Possiamo applicare un'operazione unitaria a un vettore di stato usando il metodo evolve.

v = Statevector([1, 0])

v = v.evolve(H)
v = v.evolve(T)
v = v.evolve(H)
v = v.evolve(S)
v = v.evolve(Y)

display(v.draw("latex"))

(0.1464466094−0.3535533906i)∣0⟩+(−0.3535533906+0.8535533906i)∣1⟩(0.1464466094 - 0.3535533906 i) |0\rangle+(-0.3535533906 + 0.8535533906 i) |1\rangle

Un'anteprima dei circuiti quantistici​

I circuiti quantistici non verranno introdotti formalmente fino alla lezione Quantum circuits, che è la terza lezione di questo corso, ma possiamo comunque sperimentare la composizione di operazioni unitarie su qubit usando la classe QuantumCircuit in Qiskit. In particolare, possiamo definire un circuito quantistico (che, in questo caso, sarà semplicemente una sequenza di operazioni unitarie eseguite su un singolo qubit) come segue.

from qiskit import QuantumCircuit

circuit = QuantumCircuit(1)

circuit.h(0)
circuit.t(0)
circuit.h(0)
circuit.s(0)
circuit.y(0)

display(circuit.draw(output="mpl"))

Output of the previous code cell

Qui stiamo usando il metodo draw della classe QuantumCircuit con il renderer mpl (abbreviazione di Matplotlib, una libreria di visualizzazione Python). Questo è l'unico renderer che useremo per i circuiti quantistici in questo corso, ma esistono altre opzioni, tra cui un renderer testuale e uno basato su LaTeX.

Le operazioni vengono applicate in sequenza, partendo da sinistra e terminando a destra nel diagramma. Un modo pratico per ottenere la matrice unitaria corrispondente a questo circuito è usare il metodo from_circuit della classe Operator.

display(Operator.from_circuit(circuit).draw("latex"))
[0.1464466094−0.3535533906i0.8535533906+0.3535533906i−0.3535533906+0.8535533906i0.3535533906+0.1464466094i] \begin{bmatrix} 0.1464466094 - 0.3535533906 i & 0.8535533906 + 0.3535533906 i \\ -0.3535533906 + 0.8535533906 i & 0.3535533906 + 0.1464466094 i \\ \end{bmatrix}

Possiamo anche inizializzare un vettore di stato quantistico di partenza e poi far evolvere quello stato secondo la sequenza di operazioni descritta dal circuito.

ket0 = Statevector([1, 0])
v = ket0.evolve(circuit)
display(v.draw("latex"))

(0.1464466094−0.3535533906i)∣0⟩+(−0.3535533906+0.8535533906i)∣1⟩(0.1464466094 - 0.3535533906 i) |0\rangle+(-0.3535533906 + 0.8535533906 i) |1\rangle

Il codice seguente simula un esperimento in cui lo stato ottenuto dal circuito di cui sopra viene misurato con una misurazione nella base standard 4000 volte (usando una copia fresca dello stato ogni volta).

statistics = v.sample_counts(4000)
display(plot_histogram(statistics))

Output of the previous code cell