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