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.

Contents#

Install and load MIMIQ#

To install MIMIQ use the following command:

pip install "mimiqcircuits @ git+https://github.com/qperfect-io/mimiqcircuits-python.git"

Check the installation page for more details.

In order to use MIMIQ, we simply need to import the mimiqcircuits Python module within your workspace like this:

from mimiqcircuits import *

Connect to remote service#

To execute circuits you have to connect to MIMIQ’s remote service, which can be achieved with the following instructions

conn = MimiqConnection()
conn.connect()

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.

Note

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 0 (Python 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()

The most important tool to build circuits is the push() function. It is used like this: circuit.push(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

>>> circuit.push(GateH(), 0)
1-qubit circuit with 1 instructions:
└── H @ q[0]

The text representation H @ q[0] informs us that there is an instruction which applies the Hadamard gate to the qubit with index 0. 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 5 CX gates or control-X gates between the qubit 0 and all the qubits from 1 to 4.

>>> circuit.push(GateCX(), 0, range(1, 5))
4-qubit circuit with 4 instructions:
├── H @ q[0]
├── CX @ q[0], q[1]
├── CX @ q[0], q[2]
└── CX @ q[0], q[3]

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 0 and 3, and store it in the first z register as:

>>> circuit.push(ExpectationValue(Projector11()), 0, 4, 0)
6-qubit circuit with 5 instructions:
├── H @ q[0]
├── CX @ q[0], q[1]
├── CX @ q[0], q[2]
├── CX @ q[0], q[3]
└── ⟨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:

>>> circuit.push(Measure(), range(0, 5), range(0, 5))
6-qubit circuit with 9 instructions:
├── H @ q[0]
├── CX @ q[0], q[1]
├── CX @ q[0], q[2]
├── CX @ q[0], q[3]
├── ⟨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]

Here, we measure qubits 0 to 4 and store the result in classical register 0 to 4. In general, the ordering of targets is always like circuit.push(op, quantum_targets..., classical_targets..., z_targets...).

Warning

Classical and z registers can be overwritten. If you do circuit.push(Measure(), 0, 0) followed by circuit.push(Measure(), 1, 0), the second measurement will overwrite the first one since it will be stored on the same classical register 0. 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:

>>> circuit.add_noise(GateH(), AmplitudeDamping(0.01))
6-qubit circuit with 10 instructions:
├── H @ q[0]
├── AmplitudeDamping(0.01) @ q[0]
├── CX @ q[0], q[1]
├── CX @ q[0], q[2]
├── CX @ q[0], q[3]
├── ⟨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]

>>> circuit.add_noise(GateCX(), Depolarizing2(0.1), parallel=True)
6-qubit circuit with 13 instructions:
├── H @ q[0]
├── AmplitudeDamping(0.01) @ q[0]
├── CX @ q[0], q[1]
├── Depolarizing(0.1) @ q[0,1]
├── CX @ q[0], q[2]
├── Depolarizing(0.1) @ q[0,2]
├── CX @ q[0], q[3]
├── Depolarizing(0.1) @ q[0,3]
├── ⟨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]

>>> circuit.add_noise(Measure(), PauliX(0.05), before=True, parallel=True)
6-qubit circuit with 17 instructions:
├── H @ q[0]
├── AmplitudeDamping(0.01) @ q[0]
├── CX @ q[0], q[1]
├── Depolarizing(0.1) @ q[0,1]
├── CX @ q[0], q[2]
├── Depolarizing(0.1) @ q[0,2]
├── CX @ q[0], q[3]
├── Depolarizing(0.1) @ q[0,3]
├── ⟨P₁₁(1)⟩ @ q[1,5], z[1]
├── PauliX(0.05) @ q[1]
├── PauliX(0.05) @ q[1]
├── PauliX(0.05) @ q[1]
├── PauliX(0.05) @ q[1]
├── M @ q[1], c[1]
├── M @ q[2], c[2]
├── M @ q[3], c[3]
└── M @ q[4], c[4]

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:

>>> circuit.num_qubits(), circuit.num_bits(), circuit.num_zvars()
(6, 5, 2)

A circuit behaves in many ways like a vector (of instructions, i.e. operations + targets). You can get the length as len(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:

  1. opening a connection to the MIMIQ Remote Services (which we did at the beginning of the tutorial),

  2. send a circuit for execution,

  3. retrieve the results of the execution.

After a connection has been established, an execution can be sent to the remote services using execute().

>>> job = conn.execute(circuit)

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 mimiqcircuitsQCSResults structure.

>>> res = conn.get_result(job)

To make a histogram out of the retrieved samples, it suffices to execute

>>> res.histogram()
{frozenbitarray('01110'): 1000}

To plot the results use the following:

>>> from mimiqcircuits.visualization import plothistogram
>>> plothistogram(res)
<Figure size 960x720 with 1 Axes>

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.