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
Vähennyslaskua listojen välillä ei tueta.
python_list - python_list
Voit liittää alkioita python-listaan.
python_list.append(1)
python_list
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}")
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
Kun kerrot taulukot keskenään, näiden alkiot kerrotaan keskenään.
np.array([1, 2]) * np.array([5, 10])
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
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())
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)
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)
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()
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)
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()
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()
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
all_zeros_state
# 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
psi
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()
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