Quick Start
In this tutorial, we will walk you through the fundamental procedures for simulating a quantum circuit using MIMIQ. Throughout the tutorial, we will provide links to detailed documentation and examples that can provide a deeper understanding of each topic.
Install and load MIMIQ
To install MIMIQ, please open Julia's interactive session (REPL), then press the ]
to start using the package manager mode, then type the following commands.
If it is the first time opening julia update the list of packages
update
Then add QPerfect's registry of Julia packages:
registry add https://github.com/qperfect-io/QPerfectRegistry.git
To install MimiqCircuits
, to its last stable release,
add MimiqCircuits
Check the installation page for more details.
In order to use MIMIQ, we simply need to load the MimiqCircuit
Julia module within your workspace like this:
using MimiqCircuits
Connect to remote service
To execute circuits you have to connect to MIMIQ's remote service, which can be achieved with a single instruction
conn = connect()
Connection:
├── url: https://mimiqfast.qperfect.io/api
├── executions: 4/1000
├── computing time: 0/600 minutes
├── Max time limit is: Infinite
├── Default time limit is: 30 minutes
└── status: open
For more details see cloud execution page
or see the documentation of connect
. If executed without supplemental arguments, connect()
will start a local webpage and will try to open it with your default browser. As an alternative, connect("john.smith@example.com", "jonspassword")
allows to insert directly the username and password of the user.
In order to complete this step you need an active subscription to MIMIQ. To obtain one, please contact us or, if your organization already has a subscription, contact the organization account holder.
Example: Simulate a GHZ circuit
Construct basic circuit
A circuit is basically a sequence of quantum operations (gates, measurements, etc...) that act on a set of qubits and potentially store information in a classical or "z" register (see circuit page). The classical register is a boolean vector to store the results of measurements, and the z register is a complex vector to store the result of mathematical calculations like expectation values.
The MIMIQ interface is similar to other software, but there are some important differences:
- There are no hardcoded quantum registers. Qubits are simply indicated by an integer index starting at 1 (Julia convention). The same for classical and z registers.
- A
Circuit
object doesn't have a fixed number of qubits. The number of qubits in a circuit is taken from looking at the qubits the gates are applied to. It is the maximum integer index used in a circuit. The same for the number of classical bits.
To construct a GHZ circuit, we start by defining an empty Circuit
circuit = Circuit()
empty circuit
The most important tool to build circuits is the push!
function. It is used like this: push!(circuit, quantum_operation, targets...)
. It accepts a circuit, a single quantum operation, and a series of targets, one for every qubit or bit the operation supports.
We apply a GateH
on the first qubit as
push!(circuit, GateH(), 1)
1-qubit circuit with 1 instructions:
└── H @ q[1]
The text representation H @ q[1]
informs us that there is an instruction which applies the Hadamard gate to the qubit with index 1
. Note that qubits start by default in the state 0
.
Multiple gates can be added at once through the same push!
syntax using iterables, see circuit and unitary gates page for more information. To prepare a 5-qubit GHZ state, we add 9 CX
or control-X
gates between the qubit 1 and all the qubits from 2 to 5.
push!(circuit, GateCX(), 1, 2:5)
5-qubit circuit with 5 instructions:
├── H @ q[1]
├── CX @ q[1], q[2]
├── CX @ q[1], q[3]
├── CX @ q[1], q[4]
└── CX @ q[1], q[5]
Measure, add noise, and extract information
We can extract information about the state of the system (without affecting the state) at any point in the circuit, see statistical operations page. For example, we can compute the expectation value of $| 11 \rangle\langle 11 |$ of qubits 1 and 5, and store it in the first z register as:
push!(circuit, ExpectationValue(Projector11()), 1, 5, 1)
5-qubit circuit with 6 instructions:
├── H @ q[1]
├── CX @ q[1], q[2]
├── CX @ q[1], q[3]
├── CX @ q[1], q[4]
├── CX @ q[1], q[5]
└── ⟨P₁₁(1)⟩ @ q[1,5], z[1]
We can measure the qubits and add other non-unitary operations at any point in the circuit, for example:
push!(circuit, Measure(), 1:5, 1:5)
5-qubit circuit with 11 instructions:
├── H @ q[1]
├── CX @ q[1], q[2]
├── CX @ q[1], q[3]
├── CX @ q[1], q[4]
├── CX @ q[1], q[5]
├── ⟨P₁₁(1)⟩ @ q[1,5], z[1]
├── M @ q[1], c[1]
├── M @ q[2], c[2]
├── M @ q[3], c[3]
├── M @ q[4], c[4]
└── M @ q[5], c[5]
Here, we measure qubits 1 to 5 and store the result in classical register 1 to 5. In general, the ordering of targets is always like push!(circ, op, quantum_targets..., classical_targets..., z_targets...)
.
Classical and z registers can be overwritten. If you do push!(circuit, Measure(), 1, 1)
followed by push!(circuit, Measure(), 2, 1)
, the second measurement will overwrite the first one since it will be stored on the same classical register 1. To avoid this in a circuit with many measurements you can, for example, keep track of the index of the last used register.
To simulate imperfect quantum computers we can add noise to the circuit. Noise operations can be added just like any other operations using push!
. However, noise can also be added after the circuit has been built to all gates of a certain type using add_noise!
. For example:
add_noise!(circuit, GateH(), AmplitudeDamping(0.01))
add_noise!(circuit, GateCX(), Depolarizing2(0.1); parallel=true)
add_noise!(circuit, Measure(), PauliX(0.05); before=true, parallel=true)
5-qubit circuit with 21 instructions:
├── H @ q[1]
├── AmplitudeDamping(0.01) @ q[1]
├── CX @ q[1], q[2]
├── Depolarizing(2,0.1) @ q[1:2]
├── CX @ q[1], q[3]
├── Depolarizing(2,0.1) @ q[1,3]
├── CX @ q[1], q[4]
├── Depolarizing(2,0.1) @ q[1,4]
├── CX @ q[1], q[5]
⋮ ⋮
├── PauliX(0.05) @ q[1]
├── PauliX(0.05) @ q[2]
├── PauliX(0.05) @ q[3]
├── PauliX(0.05) @ q[4]
├── PauliX(0.05) @ q[5]
├── M @ q[1], c[1]
├── M @ q[2], c[2]
├── M @ q[3], c[3]
├── M @ q[4], c[4]
└── M @ q[5], c[5]
See symbolic operations and special operations pages for other supported operations.
The number of qubits, classical bits, and complex z-values of a circuit can be obtained from:
numqubits(circuit), numbits(circuit), numzvars(circuit)
(5, 5, 1)
A circuit behaves in many ways like a vector (of instructions, i.e. operations + targets). You can get the length as length(circuit)
, access elements as circuit[2]
, insert elements with insert!
, append other circuits with append!
etc. You can also visualize circuits with draw
. See circuit page for more information.
Execute circuit
Executing a circuit on MIMIQ requires three steps:
- opening a connection to the MIMIQ Remote Services (which we did at the beginning of the tutorial),
- send a circuit for execution,
- retrieve the results of the execution.
After a connection has been established, an execution can be sent to the remote services using execute
.
job = execute(conn, circuit)
Execution
└── 674394363b2337cfea18efc1
This will execute a simulation of the given circuit with default parameters. The default choice of algorithm is "auto"
. Generally, there are three available options:
"auto"
for the automatically selecting the best algorithm according to circuit size and complexity,"statevector"
for a highly optimized state vector engine, and"mps"
for the large-scale Matrix Product States (MPS) method.
Check out the documentation of the execute
function for details.
Once the execution has finished, the results can be retrieved via the getresults
function, which returns a QCSResults
structure.
res = getresult(conn, job)
QCSRresults | |
Simulator | |
MIMIQ-MPS 0.16.0 | |
Timings | |
total time | 1.03187771s |
compression time | 0.2831493769999999s |
apply time | 0.7463708709999989s |
parse time | 0.000275512s |
Fidelity estimate | |
Min, Max | 0.99999999, 1.0 |
Mean | 1.0 |
Median | 1.0 |
Standard Deviation | 4.7126342e-17 |
Average multiqubit error estimate | |
Min, Max | 0.0,2.2204461e-16 |
Mean | 1.1657342e-18 |
Median | 0.0 |
Standard Deviation | 1.1723714e-17 |
Statistics | |
Number of executions | 1000 |
Number of samples | 1000 |
Number of amplitudes | 0 |
creg (most sampled) | |
bs"00000" | 289 |
bs"11111" | 273 |
bs"10111" | 54 |
bs"01000" | 42 |
bs"11110" | 29 |
bs"10000" | 27 |
bs"00010" | 27 |
bs"01100" | 25 |
bs"00001" | 25 |
bs"11011" | 24 |
zreg (most sampled) | |
0.49748743718592947 | 691 |
0.5025125628140706 | 75 |
0.0 | 58 |
0.5025125628140703 | 40 |
0.49748743718592964 | 29 |
0.4974874371859293 | 23 |
0.4974874371859292 | 14 |
0.5025125628140711 | 13 |
0.50251256281407 | 12 |
0.4974874371859298 | 12 |
To make a histogram out of the retrieved samples, it suffices to execute
histsamples(res)
Dict{BitString, Int64} with 32 entries:
bs"10111" => 54
bs"11001" => 4
bs"10110" => 6
bs"11000" => 5
bs"11011" => 24
bs"11010" => 2
bs"10001" => 24
bs"10000" => 27
bs"10011" => 23
bs"01101" => 5
bs"01100" => 25
bs"10010" => 2
bs"01111" => 22
bs"01110" => 14
bs"00101" => 4
bs"00100" => 20
bs"00111" => 4
bs"00110" => 3
bs"01001" => 3
⋮ => ⋮
To plot the results (works both with Plots.jl and Makie.jl) you can use
using Plots
plot(res)
Check the cloud execution
page for more details on job handling.
OpenQASM and Stim
OpenQASM and Stim files, defining quantum algorithms can be executed on MIMIQ in the same way native circuits can, simply use execute
and provide the path of the file to upload. See the import-export page for more details on how include files are handled.