Import and export circuits

from mimiqcircuits import *

In this section we introduce different options to import and export circuits. In particular, MIMIQ allows to import circuits in well-known languages such as OpenQASM and Stim, as well as save and import circuits using its own Protobuf structure.

Protobuf

Export Protobuf files from MIMIQ

After building a circuit in MIMIQ you can export it into a Protobuf format using the saveproto() function. You need to give it two arguments, the name of the file to create (.pb format) and the circuit to save.

results.saveproto("my_circuit.pb")

The same method allows you to save your simulation results in a Protobuf file.

# get the results
results = conn.get_results(job)
# save the results
results.saveproto("my_results.pb")

Note

Protobuf is a serialized file format developed by google. It is very lightweight and efficient to parse. Check the Protobuf repository <https://github.com/protocolbuffers/protobuf>`_ for more information.

Note

You can only export a circuit into Protobuf format and cannot export an OpenQASM or Stim file in the current version of MIMIQ.

Import Protobuf file to MIMIQ

MIMIQ allows you to import Protobuf circuit files using the loadproto() function and import Protobuf results file using loadproto(). With this function you can get previously saved circuit or get previous simulation results. You need to give this function the name of the file to parse and the type of object to parse.

# Import circuit from Protobuf to MIMIQ
circuit = Circuit.loadproto("my_circuit.pb")

# Import results from Protobuf to MIMIQ
results = QCSResults.loadproto("my_results.pb")

The circuit imported with loadproto() can be manipulated like any other circuit on MIMIQ to add or insert gates, see circuit page.

OpenQASM

Open Quantum Assembly Language is a programming language designed for describing quantum circuits and algorithms for execution on quantum computers. It is a very convenient middle ground for different quantum computer architectures to interpret and execute circuits.

Execute OpenQASM file in MIMIQ

The remote MIMIQ services can readily process and execute OpenQASM files, thanks to fast and feature-complete Julia and C++ parsers and interpreters.

Here is a simple comprehensive example of executing a QASM file on MIMIQ.

>>> qasm = """
... // Implementation of Deutsch algorithm with two qubits for f(x)=x
... // taken from https://github.com/pnnl/QASMBench/blob/master/small/deutsch_n2/deutsch_n2.qasm
... OPENQASM 2.0;
... include "qelib1.inc";
... qreg q[2];
... creg c[2];
... x q[1];
... h q[0];
... h q[1];
... cx q[0],q[1];
... h q[0];
... measure q[0] -> c[0];
... measure q[1] -> c[1];
... """

# Write the OPENQASM as a file
>>> with open("/tmp/deutsch_n2.qasm", "w") as file:
...     file.write(qasm)
308

# actual execution of the QASM file
>>> job = conn.submit("/tmp/deutsch_n2.qasm", algorithm="statevector")
>>> res = conn.get_result(job)
>>> res.histogram()
{frozenbitarray('10'): 530, frozenbitarray('11'): 470}
>>> from mimiqcircuits.visualization import *

# Visualizing the result
>>> plothistogram(res)
<Figure size 960x720 with 1 Axes>

The result will be plotted as follows:

Alternative Text

For more informations, read the documentation of submit().

Behaviour of include files

A common file used by many QASM files is the qelib1.inc file. This file is not defined as being part of OpenQASM 2.0, but its usage is so widespread that it might be considered as de-facto part of the specifications.

details

We remind the careful reader that OpenQASM 2.0 specifications only define 6 operations: U, CX, measure, reset, barrier and if.

If we were to parse every file together with qelib1.inc, we would have at the end just a list of simple U and CX gates, leaving behind any speed improvement that we would gain by using more complex gates as blocks. For this reason, if you don’t explicitly provide the include files, MIMIQ will not parse the usual qelib1.inc file but will instead use a simplified version of it, where almost all gate definitions are replaced by opaque definitions. These opaque definitions will be converted to the corresponding MIMIQ gates listed in `GATES`().

Another alternative is to use the mimiqlib.inc directly in your file. For now it’s almost a copy of the modified qelib1.inc but in the future it will be extended to contain more gates and operations, diverging from qelib1.inc.

Parse OpenQASM locally

You can parse OpenQASM 2.0 code directly into a MIMIQ Circuit object using the mimiqcircuits.qasm module. This is useful for importing circuits from other tools, analyzing them, or modifying them before execution.

The parsing process involves two steps:

  1. Parsing: Converts the QASM source (string) into an Abstract Syntax Tree (AST) using loads() or load().

  2. Interpretation: Converts the AST into a Circuit.

>>> from mimiqcircuits.qasm import loads
>>> qasm_source = """
... OPENQASM 2.0;
... include "qelib1.inc";
... qreg q[2];
... h q[0];
... cx q[0], q[1];
... """
>>> circuit = loads(qasm_source)
>>> print(f"Circuit with {circuit.num_qubits()} qubits") # compact print for doctest
Circuit with 2 qubits

You can also load directly from a file:

from mimiqcircuits.qasm import load
circuit = load("my_circuit.qasm")

Export to OpenQASM

Any MIMIQ Circuit can be exported back to OpenQASM 2.0 format. This supports standard gates as well as custom gate definitions.

  • dumps(): Returns the QASM representation of the circuit as a string.

  • dump(): Writes the QASM representation directly to a file.

>>> from mimiqcircuits.qasm import dumps
>>> qasm_out = dumps(circuit)
>>> print(qasm_out)
OPENQASM 2.0;
include "qelib1.inc";
qreg q[2];
creg c[0];
h q[0];
cx q[0],q[1];

Relations between OpenQASM registers and MIMIQ indices

During the parsing of the QASM file, we will assign a unique index to each qubit and classical bit. This index will be used to identify the qubit or bit in the MIMIQ-CIRC service.

The indices are assigned in the following way:

  • The first qubit is assigned index 0 (Python), the second 1, and so on.

  • All registers retain the same ordering as in the QASM file.

  • Qubits and classical bits behave similarly but have they have each other its own sequence from indices, starting from 0.

A simple example will clarify this behaviour:

OPENQASM 2.0;
qreg q[2];
creg m[10];
qreg a[10];
creg g[2];

Will be parsed as:

QASM name

MIMIQ Qubit index

MIMIQ Bit index

q[0]

0

q[1]

1

a[0]

2

a[1]

3

...

...

...

a[9]

11

m[0]

0

m[1]

1

...

...

...

m[9]

0

g[0]

10

g[1]

11

Stim

Execute Stim file on MIMIQ

Stim is a fast stabilizer circuit simulator commonly used for Clifford circuit simulation. Stim allows users to export their circuit to a text format usually with the .stim extention. The remote MIMIQ services can readily process and execute Stim files as follows:

job = conn.submit("my_stim_circuit.stim")

Warning

The support of Stim is still in progress and some of the most specific Stim features are not supported. For instance, detectors will be completely ignored by MIMIQ at exectution time.

The results of the simulation can be accessed as usual on MIMIQ, see remote execution page.

Reference

mimiqcircuits.Circuit.saveproto(self, file)

Saves the circuit as a protobuf (binary) file.

Parameters:

filename (str) – The name of the file to save the circuit to.

Returns:

The number of bytes written to the file.

Return type:

int

Examples

>>> from mimiqcircuits import *
>>> from symengine import *
>>> import tempfile
>>> x, y = symbols("x y")
>>> c = Circuit()
>>> c.push(GateH(), 0)
1-qubit circuit with 1 instruction:
└── H @ q[0]

>>> c.push(GateXXplusYY(x**2, y),0,1)
2-qubit circuit with 2 instructions:
├── H @ q[0]
└── XXplusYY(x**2, y) @ q[0:1]

>>> c.push(Measure(),0,0)
2-qubit, 1-bit circuit with 3 instructions:
├── H @ q[0]
├── XXplusYY(x**2, y) @ q[0:1]
└── M @ q[0], c[0]

>>> tmpfile = tempfile.NamedTemporaryFile(suffix=".pb", delete=True)
>>> c.saveproto(tmpfile.name)
64
>>> c.loadproto(tmpfile.name)
2-qubit, 1-bit circuit with 3 instructions:
├── H @ q[0]
├── XXplusYY(x**2, y) @ q[0:1]
└── M @ q[0], c[0]
Note:

This example uses a temporary file to demonstrate the save and load functionality. You can save your file with any name at any location using:

c.saveproto("example.pb")
c.loadproto("example.pb")
mimiqcircuits.Circuit.loadproto(file)

Loads a circuit from a protobuf (binary) file.

Parameters:

filename (str) – The name of the file to load the circuit from.

Returns:

The circuit loaded from the file.

Return type:

Circuit

Note

Look for example in Circuit.saveproto()

mimiqcircuits.QCSResults.saveproto(self, file)

Save QCSResults object to a Protocol Buffers file.

Examples

>>> from mimiqcircuits import *
>>> from symengine import *
>>> import os
>>> import tempfile
>>> x, y = symbols("x y")
>>> c = Circuit()
>>> c.push(GateH(), 0)
1-qubit circuit with 1 instructions:
└── H @ q[0]

>>> conn = MimiqConnection()
>>> conn.connect(os.getenv("MIMIQUSER"), os.getenv("MIMIQPASS"))
MimiqConnection:
├── url: https://mimiq.qperfect.io
├── Max time limit per request: 360 minutes
├── Default time limit is equal to max time limit: 360 minutes
└── status: open
>>> job = conn.execute(c)
>>> res = conn.get_result(job)
>>> res
QCSResults:
├── simulator: MIMIQ-MPS 0.18.3
├── timings:
│    ├── amplitudes time: 1.65e-07s
│    ├── sample time: 0.000204869s
│    ├── parse time: 0.000296368s
│    ├── total time: 0.001337133s
│    ├── compression time: 0.000166837s
│    └── apply time: 0.0003098s
├── fidelity estimate: 1
├── average multi-qubit gate error estimate: 0
├── most sampled:
│    ├── bs"1" => 537
│    └── bs"0" => 463
├── 1 executions
├── 0 amplitudes
└── 1000 samples
>>> tmpfile = tempfile.NamedTemporaryFile(suffix=".pb", delete=True)
>>> res.saveproto(tmpfile.name)
7161
>>> res.loadproto(tmpfile.name)
QCSResults:
├── simulator: MIMIQ-MPS 0.18.3
├── timings:
│    ├── parse time: 0.000296368s
│    ├── sample time: 0.000204869s
│    ├── amplitudes time: 1.65e-07s
│    ├── total time: 0.001337133s
│    ├── compression time: 0.000166837s
│    └── apply time: 0.0003098s
├── fidelity estimate: 1
├── average multi-qubit gate error estimate: 0
├── most sampled:
│    ├── bs"1" => 537
│    └── bs"0" => 463
├── 1 executions
├── 0 amplitudes
└── 1000 samples
Note:

This example uses a temporary file to demonstrate the save and load functionality. You can save your file with any name at any location using:

res.saveproto("example.pb")
res.loadproto("example.pb")
mimiqcircuits.QCSResults.loadproto(file)

Load QCSResults object from a Protocol Buffers file.

The loadproto() method is a static method and should be called on the class, not on an instance of the class.

Note

Look for example in QCSResults.saveproto()

mimiqcircuits.qasm.load(filename, includedirs=None)[source]

Parse and interpret an OpenQASM 2.0 file.

Parameters:
  • filename (str) – Path to the OpenQASM 2.0 file.

  • includedirs (List[str], optional) – List of directories to search for included files.

Returns:

The interpreted circuit.

Return type:

mc.Circuit

mimiqcircuits.qasm.loads(s, includedirs=None)[source]

Parse and interpret an OpenQASM 2.0 string.

Parameters:
  • s (str) – The OpenQASM 2.0 string.

  • includedirs (List[str], optional) – List of directories to search for included files.

Returns:

The interpreted circuit.

Return type:

mc.Circuit

mimiqcircuits.qasm.dump(c, filename, decompose_wrappers=False, sanitize_names=True)[source]

Serialize a circuit to an OpenQASM 2.0 file.

Parameters:
  • c (mc.Circuit) – The circuit to serialize.

  • filename (str) – The file path to write to.

mimiqcircuits.qasm.dumps(c, decompose_wrappers=False, sanitize_names=True)[source]

Serialize a circuit to an OpenQASM 2.0 string.

Parameters:

c (mc.Circuit) – The circuit to serialize.

Returns:

The OpenQASM 2.0 string representation.

Return type:

str