Search
Aloitus

Aloitus

Ennen uppoutumista materiaaleihin varmistetaan, että tekniikka toimii odotetusti. Haluamme, että python on asennettu oikein ja että myös qiskit, numpy ja matplotlib on asennettuina.

Tietokoneelta täytyy siis löytyä pythonin versio, joka täyttää kaikki kurssin kooditehtävien vaatimukset, jotta näitä koodeja on mahdollista ajaa.

Kun työskentelet pythonin kanssa, suosittelemme käyttämään esimerkiksi miniconda -ohjelmaa tarvittavien ohjelmistopakettien asentamiseen. Minicondalla voit luoda erillisiä python-ympäristöjä, joissa kaikissa voi olla eri versio pythonista ja erilaiset listat asennettuja paketteja. Näin voit välttää yleisen ongelman pakettien ristiriitaisista versioista, jos työstät samalla useampaa python-projektia.

Asentaaksesi minicondan, lataa asennusohjelma osoitteesta https://docs.conda.io/projects/miniconda/en/latest/ ja seuraa ohjeita sivun alareunasta. Linuxilla tai MacOS:lla voit suorittaa asennuksen suoraan komentotulkissa (terminal). Aja:

> sh Miniconda3-latest-Linux-x86_64.sh

ja seuraa asennusohjelman ohjeita.

Kun miniconda on asennettu, voit käynnistää komentotulkin uudelleen ja (base)n pitäisi ilmestyä komentotulkin vasemmalle puolelle. Tämä kertoo, että tällä hetkellä aktiivinen miniconda-ympäristö on (base)

Asentaaksesi kaikki kurssilla tarvittavat ohjelmat, olemme laatineet requirements.yml -tiedoston, jota voit käyttää ympäristön luomiseen. Luodaksesi uuden miniconda-ympäristön, joka täyttää kaikki vaatimukset, aja:

> conda env create -f requirements.yaml

Tämän pitäisi luoda uusi ympäristö nimeltä oqs-digiq. Aktivoidaksesi sen, aja:

> conda activate oqs-digiq

Tämä täytyy tehdä aina, kun käynnistät komentotulkin uudelleen ja haluat vaihtaa tähän ympäristöön. Jos näet nyt tekstin (oqs-digiq) komentotulkin vasemmassa reunassa, voit jatkaa eteenpäin!

Voit käyttää vapaasti mitä vain tekstieditoria, josta tykkäät (itse käytän Visual Studio Codea), mutta palauta ratkaisusi joko Jupyter notebook -muodossa tai python (.py) -tiedostona. Jätä koodiisi kommentteja tarpeen tullen, jos palautat sen python-tiedostona ja kuvaile myös joko kommenteissa tai erillisessä teksti- tai pdf-tiedostossa, miten koodi ajetaan ja mihin tulosten pitäisi ilmestyä.

Hyvä paikka tutustua avointen kvanttisysteemien ja kvanttilaskennan perusteisiin on Preliminaries-kappale kurssilta Open Quantum Systems with Qiskit.

Tutustutaan ensin numpyyn (API Reference). Se on erittäin hyödyllinen python-kirjasto, jota voidaan käyttää kaikenlaisiin numeerisiin laskuihin, erityisesti matriisien ja taulukoiden kanssa.

Python tukee listoja (lists), jotka ovat kooltaan dynaamisia, eli niihin voidaan lisätä tai niistä voidaan poistaa alkioita listan luomisen jälkeen. Toisaalta numpy-taulukot (arrays) ovat kiinteän kokoisia, mikä tarkoittaa, että jos halutaan muuttaa taulukon kokoa, on luotava uusi taulukko. Eräs syy numpyn nopeudelle on, että se pohjautuu C-kieleen.

Vertaillaanpa nopeasti näitä.

python_list = [0,0,0,0]

Kun lisäät python-listoja toisiinsa, ketjutat ne peräkkäin.

python_list + python_list
[0, 0, 0, 0, 0, 0, 0, 0]

Vähennyslaskua listojen välillä ei tueta.

python_list - python_list
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[3], line 1
----> 1 python_list - python_list

TypeError: unsupported operand type(s) for -: 'list' and 'list'

Voit liittää alkioita python-listaan.

python_list.append(1)
python_list
[0, 0, 0, 0, 1]

Listat voivat sisältää monen tyyppisiä objekteja, kuten sanakirjoja (dictionaries), minkä tahansa python-luokan alkioita tai vaikka toisia listoja.

Nämä seuraavat python-työkalut ja -objektit voivat myös olla hyödyllisiä.

# List comprehension
print([i for i in range(2,10)])

# Dictionary
result = {"a": 10, "b": 5, "c": 2}
print(result["a"])
result.update({"a": 0, "d": 5})
print(result)

# Enumerate (index and value)
for i, value in enumerate([10,11,12,13]):
    print(f"Index {i}, value {value}")
[2, 3, 4, 5, 6, 7, 8, 9]
10
{'a': 0, 'b': 5, 'c': 2, 'd': 5}
Index 0, value 10
Index 1, value 11
Index 2, value 12
Index 3, value 13
import numpy as np

numpy_array = np.array([1,0,0,1])

Yhteenlasku numpylla laskee taulukon alkiot yhteen. Vähennys, kertolasku ja jakolasku toimivat samaan tapaan.

numpy_array + numpy_array
array([2, 0, 0, 2])

Kun kerrot taulukot keskenään, näiden alkiot kerrotaan keskenään.

np.array([1, 2]) * np.array([5, 10])
array([ 5, 20])

Moniulotteisia listoja ja taulukoita voidaan luoda listaamalla listoja.

numpy_array = np.array([[1,0,0,1], [0,0,0,1], [0,1,0,1], [0,0,0,0]])
numpy_array
array([[1, 0, 0, 1],
       [0, 0, 0, 1],
       [0, 1, 0, 1],
       [0, 0, 0, 0]])

Moniulotteisilla taulukoilla voidaan tehdä helposti lineaarialgebraa.

# Matrix product
print(numpy_array @ numpy_array)
print(numpy_array * numpy_array)

# Norm of matrix
print(np.linalg.norm(numpy_array, ord=2))
print(np.trace(numpy_array))

# Conjugate transpose of matrix
complex_array = np.array([[1-1j, 0-1j], [0+0j, 1+1j]])
print(complex_array)
print(complex_array.T.conj())
[[1 0 0 1]
 [0 0 0 0]
 [0 0 0 1]
 [0 0 0 0]]
[[1 0 0 1]
 [0 0 0 1]
 [0 1 0 1]
 [0 0 0 0]]
1.9318516525781366
1
[[1.-1.j 0.-1.j]
 [0.+0.j 1.+1.j]]
[[1.+1.j 0.-0.j]
 [0.+1.j 1.-1.j]]

On paljon muitakin hyödyllisiä toimintoja, joita numpy tarjoaa.

# Identity matrix
I = np.eye(5)
print(I)

# Random 2x2 matrix
rand_matrix = np.random.choice([0,1,2], size=10, p=[0.1, 0.5, 0.4])
print(rand_matrix)

# Repeat 2x2 matrix
tiled_matrix = np.repeat([[0,1],[2,3]], repeats=5, axis=1)
print(tiled_matrix)
[[1. 0. 0. 0. 0.]
 [0. 1. 0. 0. 0.]
 [0. 0. 1. 0. 0.]
 [0. 0. 0. 1. 0.]
 [0. 0. 0. 0. 1.]]
[2 1 1 0 2 2 2 1 2 1]
[[0 0 0 0 0 1 1 1 1 1]
 [2 2 2 2 2 3 3 3 3 3]]

numpy:stä löytyy myös satunnaislukugeneraattori. Jos haluat poimia satunnaisen luvun 0:n ja 1:n väliltä tasaisella jakaumalla, voit toimia seuraavalla tavalla:

np.random.choice([0,1], size=20)
array([0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0])

matplotlib (käyttöohje) on pythonin suosituin kirjasto kuvaajien piirtämistä varten. Sillä voit tehdä monenlaisia asioita ja se on varsin joustava.

import matplotlib.pyplot as plt

# Generate 100 random values between 0 and 1.
x = range(100)
y = np.random.random(100)

plt.figure(figsize=(8,4))

# Plot the 100 random values
plt.plot(x, y, label="Random signal")

# Error bars to 1 standard deviation
error = np.std(y)
plt.fill_between(x, y - error, y + error, alpha=.3)

# Plot the average of the 100 values
plt.axhline(np.mean(y), linestyle="--", color="black", label="Average")
plt.xlabel("Sample index")
plt.ylabel("Value")
plt.legend()
<matplotlib.legend.Legend at 0x130f65a90>

Tutustutaan seuraavaksi qiskit:iin (Documentation). Se on pääasiassa IBM:n kehittämä kirjasto, josta löytyy monia työkaluja kvanttilaskentaan. Kirjastosta itsestään on tullut hyvin laaja, ja se sisältää monenlaisia ominaisuuksia. Sillä voi esimerkiksi luoda kvanttipiirin, laskea odotusarvoja kvanttimekaaniselle systeemille, simuloida meluisia kvanttilaitteita ja niin edelleen. Tässä johdannossa keskitymme pääasiassa kurssin kannalta tarpeellisiin asioihin, kuten kvanttipiirien luomiseen, niiden ajamiseen simulaattoreilla tai oikeilla laitteilla, sekä tulosten analysoimiseen.

Emme uppoudu syvälle kvanttilaskennan teoriaan, mutta käymme läpi välttämättömät perusteet.

Kvanttitietokone koostuu kubiteista, eli kaksitasoisista kvanttisysteemeistä. IBM:n laitteilla ne on toteutettu suprajohteilla, mutta moni muukin toteutustapa on mahdollinen. Kubitilla on kvanttisysteemin ominaisuudet, eli se voi olla superpositiossa, lomittunut toisten kubittien kanssa tai muuta vastaavaa.

Käytännössä kvanttitietokonetta on hyvin vaikeaa eristää täysin ympäristöstään, mikä tarkoittaa, että kubitti vuorovaikuttaa jotenkin myös ympäristönsä kanssa. Kvanttilaskennassa näitä vuorovaikutuksia käsitellään meluna tai virheinä, koska ne voivat saada kvanttitietokoneen toimimaan odottamattomalla ja ei-toivotulla tavalla.

Kvanttilaskennassa käytetään joukkoa operaattoreita tai portteja, joita voidaan soveltaa yhteen tai useampaan kubittiin. Yleisiä ovat esimerkiksi Paulin matriisit (X, Y, Z) ja CNOT-portti, joka vaikuttaa kubittiin kuten Paulin X-portti tai vaihtoehtoisesti ei tee mitään, riippuen toisen kubitin tilasta. CNOT-porttia voidaankin käyttää lomittamaan kaksi kubittia keskenään. Perehdymme näihin lisää myöhemmin tällä kurssilla.

Luodaan ensin kvanttipiiri, jossa käytämme Paulin X-porttia kaikkiin kubitteihin. Oletamme, että kaikki kvanttipiirin kubitit ovat aluksi tilassa $|0\rangle$. Paulin X-portti toimii tälle tilalle niin, että se kääntää kubitin tilaan $|1\rangle$.

from qiskit import QuantumCircuit

qc = QuantumCircuit(5)
qc.x([q for q in range(qc.num_qubits)])
qc.draw(output='mpl')

Voimme tarkastella piirien tilavektoreita suoraan. Tehdään tämä selvyyden vuoksi yhden kubitin piirille.

from qiskit.quantum_info import Statevector

qc = QuantumCircuit(1)
sv = Statevector(qc)
print(sv)

qc.x(0)
sv = Statevector(qc)
print(sv)

qc.h(0)
sv = Statevector(qc)
print(sv)
Statevector([1.+0.j, 0.+0.j],
            dims=(2,))
Statevector([0.+0.j, 1.+0.j],
            dims=(2,))
Statevector([ 0.70710678+0.j, -0.70710678+0.j],
            dims=(2,))

Ajetaan nyt piiri simulaattorilla. Simulaattoreita on muutamia mistä valita, jopa sellaisia, joiden melumallit pohjautuvat IBM:n todellisten laitteiden meluun. Myös oman melumallin tekeminen on mahdollista. Mutta oletetaan toistaiseksi, että kvanttitietokoneessa ei ole melua ja tehdään kvanttipiirin ideaalinen simulaatio.

from qiskit_aer import AerProvider

AerProvider().backends()
[AerSimulator('aer_simulator'),
 AerSimulator('aer_simulator_statevector'),
 AerSimulator('aer_simulator_density_matrix'),
 AerSimulator('aer_simulator_stabilizer'),
 AerSimulator('aer_simulator_matrix_product_state'),
 AerSimulator('aer_simulator_extended_stabilizer'),
 AerSimulator('aer_simulator_unitary'),
 AerSimulator('aer_simulator_superop'),
 QasmSimulator('qasm_simulator'),
 StatevectorSimulator('statevector_simulator'),
 UnitarySimulator('unitary_simulator')]

Valitaan käyttöön aer_simulator, joka tekee meluttoman simulaation tilavektorille. Mukana on kuitenkin niin kutsuttu “shot noise”, joka seuraa siitä, että

backend = AerProvider().get_backend('aer_simulator')

Simulaattori voidaan myös luoda suoraan seuraavalla tavalla.

from qiskit_aer import AerSimulator

backend = AerSimulator()

Piirejä luodessa pitäisi yleensä ottaa huomioon käytettävä laitteisto ja kubittien yhdistettävyys laitteella. Simulaattorilla on kuitenkin täysi yhdistettävyys (full connectivity), eli CNOT-porttia voidaan käyttää minkä tahansa kahden kubitin välillä, ja kubittien määrää rajoittaa lähinnä vain oman tietokoneesi laskentateho. Luodaan seuraavaksi yksinkertainen viiden kubitin piiri, jossa on mukana hieman lomittumista.

qc = QuantumCircuit(5)

qc.rx(np.pi/4, 0)

for target_qubit in range(1,5):
    qc.cx(target_qubit - 1, target_qubit)

# Let us store the circuit without the measurement for later use
qc_tmp = qc.copy()
qc.measure_all()
qc.draw(output='mpl')

Simuloidaan nyt piiriä 1000:lla laukauksella (shots). Tämä tarkoittaa, että tuotamme saman alkutilan samalle piirille tuhat kertaa ja mittaamme sen joka kierroksella. Jokaisella mittauksella voimme periaatteessa saada eri tuloksen. Tämä koe antaa meille histogrammin eri lopputuloksia. Siispä mitä useampi mittauskierros tehdään, sitä täsmällisemmin histogrammi alkaa näyttää kvanttitilan odotetulta todennäköisyysjakaumalta.

job = backend.run(qc, shots=10000)
result = job.result()
result.get_counts()
{'11111': 1463, '00000': 8537}

Voimme laskea odotetut todennäköisyydet seuraavalla tavalla:

# Explicitly construct the |00000> statevector
zero = np.array([1,0])
all_zeros_state = zero.copy()
for qubit in range(qc.num_qubits - 1):
    all_zeros_state = np.kron(all_zeros_state, zero)

psi = Statevector(qc_tmp)
psi_zero = Statevector(all_zeros_state)

# Calculate |<00000|psi>|^2
np.abs(psi.inner(psi_zero))**2
0.8535533905932737
all_zeros_state
array([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
# Explicitly construct the |11111> statevector
one = np.array([0,1])
all_ones_state = one.copy()
for qubit in range(qc.num_qubits - 1):
    all_ones_state = np.kron(all_ones_state, one)

psi = Statevector(qc_tmp)
psi_one = Statevector(all_ones_state)

# Calculate |<11111|psi>|^2
np.abs(psi.inner(psi_one))**2
0.14644660940672624
psi
Statevector([0.92387953+0.j        , 0.        +0.j        ,
             0.        +0.j        , 0.        +0.j        ,
             0.        +0.j        , 0.        +0.j        ,
             0.        +0.j        , 0.        +0.j        ,
             0.        +0.j        , 0.        +0.j        ,
             0.        +0.j        , 0.        +0.j        ,
             0.        +0.j        , 0.        +0.j        ,
             0.        +0.j        , 0.        +0.j        ,
             0.        +0.j        , 0.        +0.j        ,
             0.        +0.j        , 0.        +0.j        ,
             0.        +0.j        , 0.        +0.j        ,
             0.        +0.j        , 0.        +0.j        ,
             0.        +0.j        , 0.        +0.j        ,
             0.        +0.j        , 0.        +0.j        ,
             0.        +0.j        , 0.        +0.j        ,
             0.        +0.j        , 0.        -0.38268343j],
            dims=(2, 2, 2, 2, 2))

Kokeillaan nyt samaa koetta meluisalla simulaattorilla. Tällaiset simulaattorit käyttävät melumalleja, jotka pohjautuvat IBM:n todellisten kvanttilaitteiden kalibrointidataan. Ne eivät ole täydellisen realistisia, mutta toimivat yleensä tarpeeksi hyvin.

from qiskit_ibm_runtime.fake_provider import FakeHanoi

backend = FakeHanoi()
job = backend.run(qc, shots=1000)
result = job.result()
result.get_counts()
{'01111': 2,
 '11110': 2,
 '00011': 1,
 '00001': 6,
 '00100': 16,
 '11011': 3,
 '11111': 128,
 '11100': 3,
 '10111': 3,
 '01000': 8,
 '00010': 9,
 '11101': 3,
 '10000': 11,
 '00000': 805}

Kuten voit huomata, saamme monia erialisia lopputuloksia, koska jotkut melukanavat vaikuttivat kubitteihin!

Voimme myös yrittää ajaa nämä piirit todellisella laitteella. Tehdäksesi niin mene osoitteeseen https://quantum-computing.ibm.com ja luo käyttäjätili. Käytä “tokenia”, jonka saat sivulta tallentaaksesi käyttäjätilisi.

from qiskit_ibm_runtime import QiskitRuntimeService

QiskitRuntimeService.save_account(token="TOKEN")

Piirejä ajaessa seuraamme samanlaisia askelia kuin aiemmin, mutta muutamia eroja on. Huomaa myös, että yhdellä käyttäjätilillä on pääsy IBM Quantumin kvanttitietokoneille 10 minuutin ajan kuukaudessa. Emme pakota ajamaan kokeita näillä laitteilla, mutta rohkaisemme tekemään niin, jos haluat!

from qiskit_ibm_runtime import Sampler

service = QiskitRuntimeService()
service.backends()
backend = service.get_backend("ibmq_qasm_simulator")
sampler = Sampler(backend)

job = sampler.run(qc, shots=1000)
job_id = job.job_id()
print(job_id)

Jos haluat sulkea notebookin, kun työsi jonottaa pääsyä kvanttilaiteelle, voit tallentaa työsi ID:n. Näin voit helposti noutaa tuloksesi jälkikäteen.

from qiskit_ibm_runtime import QiskitRuntimeService

service = QiskitRuntimeService()
job2 = service.job(job_id=job_id)
job2.result()

Tulokset eivät ole tässä lukumäärien muodossa, vaan kvasitodennäköisyysjakaumina. Tämä johtuu siitä, näihin tuloksiin voidaan käyttää joitakin Qiskitin melunvaimennustekniikoista. Nämä tekniikat voivat palauttaa pieniä negatiivisia todennäköisyyksiä, ja siksi lukumäärien palauttamisessa ei olisi järkeä.

job2.result().quasi_dists