How to use Qiskit (the Python package) to define and run a quantum
circuit on an IBMQ computer, then plot the results in R – all in
RStudio. Here’s the repo. Or click the
button at the top right hand side of this page to download the Markdown
file.
I had already installed Anaconda and
run:
pip install qiskit[visualization]
R libraries:
library(reticulate)
library(tidyverse)
Registered S3 methods overwritten by 'dbplyr':
method from
print.tbl_lazy
print.tbl_sql
── Attaching packages ───────────────────────── tidyverse 1.3.2 ──✔ ggplot2 3.3.6 ✔ purrr 0.3.4
✔ tibble 3.1.8 ✔ dplyr 1.0.10
✔ tidyr 1.2.1 ✔ stringr 1.4.1
✔ readr 2.1.3 ✔ forcats 0.5.2 ── Conflicts ──────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag() masks stats::lag()
{reticulate} is used to get R to communicate with Python.
Now the Qiskit code to get going in a Python chunk:
from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit, IBMQ, assemble, transpile, execute
from numpy import pi
from qiskit.tools import job_monitor
Here’s a simple system with three qubits, two entangled.
\[|\psi\rangle = \frac{|000\rangle +
|011\rangle + |100\rangle + |111\rangle}{2}\]
Numbering the qubits right to left, the first two (call them \(q[0]\) and \(q[1]\)) are entangled, the third, \(q[2]\), is unentangled with the first two.
We are going to measure \(q[0]\) first,
which should cause \(q[1]\) to collapse
to the same state but leave \(q[2]\)
free to choose at random between 1 and 0. I’ve fiddled around with the
sums for this system over
here.
Back to Python, to define the circuit:
qreg_q = QuantumRegister(3, 'q')
creg_c = ClassicalRegister(3, 'c')
circuit = QuantumCircuit(qreg_q, creg_c)
circuit.reset(qreg_q[0])
circuit.reset(qreg_q[1])
circuit.reset(qreg_q[2])
circuit.h(qreg_q[0])
circuit.h(qreg_q[2])
circuit.cx(qreg_q[0], qreg_q[1])
circuit.measure(qreg_q[0], creg_c[0])
circuit.measure(qreg_q[1], creg_c[1])
circuit.measure(qreg_q[2], creg_c[2])
Draw the circuit:
circuit.draw('mpl').show()
This will pop up in a new window; I can’t work out how to get it to
appear in the Markdown other than by saving it and inserting a png…

Enter your TOP SECRET API token here:
IBMQ.save_account('1337', overwrite=True)
Load up the account:
provider = IBMQ.load_account()
See what backends I can access (I put this on one line as the output
appears to be printed before print is called otherwise):
for backend in provider.backends(): print(backend)
ibmq_qasm_simulator
ibmq_lima
ibmq_belem
ibmq_quito
simulator_statevector
simulator_mps
simulator_extended_stabilizer
simulator_stabilizer
ibmq_manila
ibm_nairobi
ibm_oslo
I chose ibmq_lima:
lima = provider.get_backend('ibmq_lima')
(And, note to self – look up how to query queue length.)
Send off the job!
job = execute(circuit,
backend = lima,
shots = 1024)
job.job_id()
If RStudio crashes for any reason (and it did), then the job can be
retrieved with the ID that would have been printed above before it
crashed (or log into your IBM Quantum web account and find the ID
there):
job = lima.retrieve_job('635aea27333aeb047289f3ba')
Use this command to query the status of the job:
job.status()
<JobStatus.DONE: 'job has successfully run'>
There’s another command that busy-waits until the job is done. Trying
to interrupt it is what crashed RStudio, so I wouldn’t recommend it!
Once the job is done, the results:
res = job.result()
Here are the frequencies of each outcome in Python:
vals = res.get_counts(circuit)
vals
{'000': 266, '001': 21, '010': 10, '011': 247, '100': 248, '101': 13, '110': 6, '111': 213}
We can also see them in R using py$
as a prefix on
variable names:
py$vals
{'000': 266, '001': 21, '010': 10, '011': 247, '100': 248, '101': 13, '110': 6, '111': 213}
Now how do I transform that (it’s a dictionary of some description)
into a data frame…? I couldn’t see an easy way in R (I didn’t think very
long). It was easier at the Python end:
import pandas
df = pandas.DataFrame(list(vals.items()),
columns = ['Outcome', 'Freq'])
df
Outcome Freq
0 000 266
1 001 21
2 010 10
3 011 247
4 100 248
5 101 13
6 110 6
7 111 213
Back in R:
py$df
And now we can ask ggplot to make a pic!
py$df |>
ggplot(aes(x = Outcome, y = Freq, fill = Outcome)) +
geom_col() +
theme(legend.position="none")

LS0tDQp0aXRsZTogIlB5dGhvbiBRaXNraXQgYW5kIFIgaW4gUlN0dWRpbyAtLSBmaXJzdCBleHBlcmltZW50Ig0KYXV0aG9yOiBBbmRpIEZ1Z2FyZA0KZGF0ZTogMjggT2N0IDIwMjINCm91dHB1dDogDQogIGh0bWxfbm90ZWJvb2s6IA0KICAgIGNvZGVfZm9sZGluZzogbm9uZQ0KLS0tDQoNCkhvdyB0byB1c2UgUWlza2l0ICh0aGUgUHl0aG9uIHBhY2thZ2UpIHRvIGRlZmluZSBhbmQgcnVuIGEgcXVhbnR1bSBjaXJjdWl0IG9uIGFuIElCTVEgY29tcHV0ZXIsIHRoZW4gcGxvdCB0aGUgcmVzdWx0cyBpbiBSIC0tIGFsbCBpbiBSU3R1ZGlvLiBIZXJlJ3MgdGhlIFtyZXBvXShodHRwczovL2dpdGh1Yi5jb20vSW5kdWN0aXZlU3RlcC9xaXNraXQtcikuIE9yIGNsaWNrIHRoZSBidXR0b24gYXQgdGhlIHRvcCByaWdodCBoYW5kIHNpZGUgb2YgdGhpcyBwYWdlIHRvIGRvd25sb2FkIHRoZSBNYXJrZG93biBmaWxlLg0KDQpJIGhhZCBhbHJlYWR5IGluc3RhbGxlZCBbQW5hY29uZGFdKGh0dHBzOi8vd3d3LmFuYWNvbmRhLmNvbS9wcm9kdWN0cy9kaXN0cmlidXRpb24pIGFuZCBydW46DQoNCmBgYA0KcGlwIGluc3RhbGwgcWlza2l0W3Zpc3VhbGl6YXRpb25dDQpgYGANCg0KUiBsaWJyYXJpZXM6DQoNCmBgYHtyfQ0KbGlicmFyeShyZXRpY3VsYXRlKQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpgYGANCg0Ke3JldGljdWxhdGV9IGlzIHVzZWQgdG8gZ2V0IFIgdG8gY29tbXVuaWNhdGUgd2l0aCBQeXRob24uDQoNCk5vdyB0aGUgUWlza2l0IGNvZGUgdG8gZ2V0IGdvaW5nIGluIGEgUHl0aG9uIGNodW5rOg0KDQpgYGB7cHl0aG9ufQ0KZnJvbSBxaXNraXQgaW1wb3J0IFF1YW50dW1SZWdpc3RlciwgQ2xhc3NpY2FsUmVnaXN0ZXIsIFF1YW50dW1DaXJjdWl0LCBJQk1RLCBhc3NlbWJsZSwgdHJhbnNwaWxlLCBleGVjdXRlDQpmcm9tIG51bXB5IGltcG9ydCBwaQ0KZnJvbSBxaXNraXQudG9vbHMgaW1wb3J0IGpvYl9tb25pdG9yDQpgYGANCg0KSGVyZSdzIGEgc2ltcGxlIHN5c3RlbSB3aXRoIHRocmVlIHF1Yml0cywgdHdvIGVudGFuZ2xlZC4NCg0KJCR8XHBzaVxyYW5nbGUgPSBcZnJhY3t8MDAwXHJhbmdsZSArIHwwMTFccmFuZ2xlICsgfDEwMFxyYW5nbGUgKyB8MTExXHJhbmdsZX17Mn0kJA0KDQpOdW1iZXJpbmcgdGhlIHF1Yml0cyByaWdodCB0byBsZWZ0LCB0aGUgZmlyc3QgdHdvIChjYWxsIHRoZW0gJHFbMF0kIGFuZCAkcVsxXSQpIGFyZSBlbnRhbmdsZWQsIHRoZSB0aGlyZCwgJHFbMl0kLCBpcyB1bmVudGFuZ2xlZCB3aXRoIHRoZSBmaXJzdCB0d28uIFdlIGFyZSBnb2luZyB0byBtZWFzdXJlICRxWzBdJCBmaXJzdCwgd2hpY2ggc2hvdWxkIGNhdXNlICRxWzFdJCB0byBjb2xsYXBzZSB0byB0aGUgc2FtZSBzdGF0ZSBidXQgbGVhdmUgJHFbMl0kIGZyZWUgdG8gY2hvb3NlIGF0IHJhbmRvbSBiZXR3ZWVuIDEgYW5kIDAuIEkndmUgZmlkZGxlZCBhcm91bmQgd2l0aCB0aGUgc3VtcyBmb3IgdGhpcyBzeXN0ZW0gW292ZXIgaGVyZV0oaHR0cHM6Ly93d3cuYW5kaWZ1Z2FyZC5pbmZvL21lYXN1cmVtZW50LWluLXF1YW50dW0tY29tcHV0aW5nLykuDQoNCkJhY2sgdG8gUHl0aG9uLCB0byBkZWZpbmUgdGhlIGNpcmN1aXQ6DQoNCmBgYHtweXRob259DQpxcmVnX3EgPSBRdWFudHVtUmVnaXN0ZXIoMywgJ3EnKQ0KY3JlZ19jID0gQ2xhc3NpY2FsUmVnaXN0ZXIoMywgJ2MnKQ0KY2lyY3VpdCA9IFF1YW50dW1DaXJjdWl0KHFyZWdfcSwgY3JlZ19jKQ0KDQpjaXJjdWl0LnJlc2V0KHFyZWdfcVswXSkNCmNpcmN1aXQucmVzZXQocXJlZ19xWzFdKQ0KY2lyY3VpdC5yZXNldChxcmVnX3FbMl0pDQpjaXJjdWl0LmgocXJlZ19xWzBdKQ0KY2lyY3VpdC5oKHFyZWdfcVsyXSkNCmNpcmN1aXQuY3gocXJlZ19xWzBdLCBxcmVnX3FbMV0pDQpjaXJjdWl0Lm1lYXN1cmUocXJlZ19xWzBdLCBjcmVnX2NbMF0pDQpjaXJjdWl0Lm1lYXN1cmUocXJlZ19xWzFdLCBjcmVnX2NbMV0pDQpjaXJjdWl0Lm1lYXN1cmUocXJlZ19xWzJdLCBjcmVnX2NbMl0pDQpgYGANCg0KRHJhdyB0aGUgY2lyY3VpdDoNCg0KYGBge3B5dGhvbn0NCmNpcmN1aXQuZHJhdygnbXBsJykuc2hvdygpDQpgYGANCg0KVGhpcyB3aWxsIHBvcCB1cCBpbiBhIG5ldyB3aW5kb3c7IEkgY2FuJ3Qgd29yayBvdXQgaG93IHRvIGdldCBpdCB0byBhcHBlYXIgaW4gdGhlIE1hcmtkb3duIG90aGVyIHRoYW4gYnkgc2F2aW5nIGl0IGFuZCBpbnNlcnRpbmcgYSBwbmcuLi4NCg0KIVtdKGNpcmN1aXQucG5nKQ0KDQpFbnRlciB5b3VyIFRPUCBTRUNSRVQgQVBJIHRva2VuIGhlcmU6DQoNCmBgYHtweXRob259DQpJQk1RLnNhdmVfYWNjb3VudCgnMTMzNycsIG92ZXJ3cml0ZT1UcnVlKQ0KYGBgDQoNCkxvYWQgdXAgdGhlIGFjY291bnQ6DQoNCmBgYHtweXRob259DQpwcm92aWRlciA9IElCTVEubG9hZF9hY2NvdW50KCkNCmBgYA0KDQpTZWUgd2hhdCBiYWNrZW5kcyBJIGNhbiBhY2Nlc3MgKEkgcHV0IHRoaXMgb24gb25lIGxpbmUgYXMgdGhlIG91dHB1dCBhcHBlYXJzIHRvIGJlIHByaW50ZWQgYmVmb3JlIHByaW50IGlzIGNhbGxlZCBvdGhlcndpc2UpOg0KDQpgYGB7cHl0aG9ufQ0KZm9yIGJhY2tlbmQgaW4gcHJvdmlkZXIuYmFja2VuZHMoKTogcHJpbnQoYmFja2VuZCkNCmBgYA0KDQpJIGNob3NlICppYm1xX2xpbWEqOg0KDQpgYGB7cHl0aG9ufQ0KbGltYSA9IHByb3ZpZGVyLmdldF9iYWNrZW5kKCdpYm1xX2xpbWEnKQ0KYGBgDQoNCihBbmQsIG5vdGUgdG8gc2VsZiAtLSBsb29rIHVwIGhvdyB0byBxdWVyeSBxdWV1ZSBsZW5ndGguKQ0KDQpTZW5kIG9mZiB0aGUgam9iIQ0KDQpgYGB7cHl0aG9ufQ0Kam9iID0gZXhlY3V0ZShjaXJjdWl0LA0KICAgICAgICAgICAgICBiYWNrZW5kID0gbGltYSwNCiAgICAgICAgICAgICAgc2hvdHMgPSAxMDI0KQ0Kam9iLmpvYl9pZCgpDQpgYGANCg0KSWYgUlN0dWRpbyBjcmFzaGVzIGZvciBhbnkgcmVhc29uIChhbmQgaXQgZGlkKSwgdGhlbiB0aGUgam9iIGNhbiBiZSByZXRyaWV2ZWQgd2l0aCB0aGUgSUQgdGhhdCB3b3VsZCBoYXZlIGJlZW4gcHJpbnRlZCBhYm92ZSBiZWZvcmUgaXQgY3Jhc2hlZCAob3IgbG9nIGludG8geW91ciBJQk0gUXVhbnR1bSB3ZWIgYWNjb3VudCBhbmQgZmluZCB0aGUgSUQgdGhlcmUpOg0KDQpgYGB7cHl0aG9ufQ0Kam9iID0gbGltYS5yZXRyaWV2ZV9qb2IoJzYzNWFlYTI3MzMzYWViMDQ3Mjg5ZjNiYScpDQpgYGANCg0KVXNlIHRoaXMgY29tbWFuZCB0byBxdWVyeSB0aGUgc3RhdHVzIG9mIHRoZSBqb2I6DQoNCmBgYHtweXRob259DQpqb2Iuc3RhdHVzKCkNCmBgYA0KDQpUaGVyZSdzIGFub3RoZXIgY29tbWFuZCB0aGF0IGJ1c3ktd2FpdHMgdW50aWwgdGhlIGpvYiBpcyBkb25lLiBUcnlpbmcgdG8gaW50ZXJydXB0IGl0IGlzIHdoYXQgY3Jhc2hlZCBSU3R1ZGlvLCBzbyBJIHdvdWxkbid0IHJlY29tbWVuZCBpdCENCg0KT25jZSB0aGUgam9iIGlzIGRvbmUsIHRoZSByZXN1bHRzOg0KDQpgYGB7cHl0aG9ufQ0KcmVzID0gam9iLnJlc3VsdCgpDQpgYGANCg0KSGVyZSBhcmUgdGhlIGZyZXF1ZW5jaWVzIG9mIGVhY2ggb3V0Y29tZSBpbiBQeXRob246DQoNCmBgYHtweXRob259DQp2YWxzID0gcmVzLmdldF9jb3VudHMoY2lyY3VpdCkNCnZhbHMNCmBgYA0KDQpXZSBjYW4gYWxzbyBzZWUgdGhlbSBpbiBSIHVzaW5nIGBweSRgIGFzIGEgcHJlZml4IG9uIHZhcmlhYmxlIG5hbWVzOg0KDQpgYGB7cn0NCnB5JHZhbHMNCmBgYA0KDQpOb3cgaG93IGRvIEkgdHJhbnNmb3JtIHRoYXQgKGl0J3MgYSBkaWN0aW9uYXJ5IG9mIHNvbWUgZGVzY3JpcHRpb24pIGludG8gYSBkYXRhIGZyYW1lLi4uPyBJIGNvdWxkbid0IHNlZSBhbiBlYXN5IHdheSBpbiBSIChJIGRpZG4ndCB0aGluayB2ZXJ5IGxvbmcpLiBJdCB3YXMgZWFzaWVyIGF0IHRoZSBQeXRob24gZW5kOg0KDQpgYGB7cHl0aG9ufQ0KaW1wb3J0IHBhbmRhcw0KZGYgPSBwYW5kYXMuRGF0YUZyYW1lKGxpc3QodmFscy5pdGVtcygpKSwNCiAgICAgICAgICAgICAgICAgICAgICBjb2x1bW5zID0gWydPdXRjb21lJywgJ0ZyZXEnXSkNCmRmDQpgYGANCg0KQmFjayBpbiBSOg0KDQpgYGB7cn0NCnB5JGRmDQpgYGANCg0KQW5kIG5vdyB3ZSBjYW4gYXNrIGdncGxvdCB0byBtYWtlIGEgcGljIQ0KDQpgYGB7cn0NCnB5JGRmIHw+IA0KICBnZ3Bsb3QoYWVzKHggPSBPdXRjb21lLCB5ID0gRnJlcSwgZmlsbCA9IE91dGNvbWUpKSArDQogIGdlb21fY29sKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKQ0KYGBgDQoNCg0K