Quickstart¶
The central interface for working with quantum circuits in the Munich Quantum Toolkit is the QuantumComputation
class.
It represents quantum circuits as a sequential list of operations.
Operations can be directly applied to the QuantumComputation
:
1from mqt.core import QuantumComputation
2
3# Build a `QuantumComputation` representing a Bell-state preparation circuit.
4nqubits = 2
5qc = QuantumComputation(nqubits)
6
7qc.h(0) # Apply Hadamard gate on qubit 0
8qc.cx(0, 1) # Apply a CNOT (controlled X-Gate) with control on qubit 0 and target on qubit 1
9
10# Get Circuit in OpenQASM 3.0 format
11print(qc.qasm3_str())
// i 0 1
// o 0 1
OPENQASM 3.0;
include "stdgates.inc";
qubit[2] q;
h q[0];
cx q[0], q[1];
The circuit class provides a lot of flexibility as every unitary gate can be declared as a controlled gate:
1from mqt.core.operations import Control
2
3nqubits = 2
4qc = QuantumComputation(nqubits)
5
6# Controlled Hadamard Gate
7qc.ch(0, 1)
8
9# Negatively controlled S-gate: S-Gate on target is performed if control is in |0> state.
10qc.cs(Control(0, Control.Type.Neg), 1)
11
12print(qc.qasm3_str())
// i 0 1
// o 0 1
OPENQASM 3.0;
include "stdgates.inc";
qubit[2] q;
ch q[0], q[1];
negctrl @ p(pi/2) q[0], q[1];
Providing a set of Control
objects allows declaring any (unitary) gate as a multi-controlled gate:
1nqubits = 3
2qc = QuantumComputation(nqubits)
3
4# Toffoli gate in mqt-core:
5qc.mcx({0, 1}, 2)
6
7# Control type can be individually declared
8qc.mcs({Control(0, Control.Type.Neg), Control(1, Control.Type.Pos)}, 2)
9
10print(qc.qasm3_str())
// i 0 1 2
// o 0 1 2
OPENQASM 3.0;
include "stdgates.inc";
qubit[3] q;
ccx q[0], q[1], q[2];
negctrl @ ctrl @ p(pi/2) q[0], q[1], q[2];
Layout Information¶
A QuantumComputation
also contains information about the mapping of algorithmic (or logical/virtual/circuit) qubits to and from device (or physical) qubits.
These are contained in the initial_layout
and output_permutation
members which are instances of the Permutation
class. If no layout is given the trivial layout is assumed.
When printing the OpenQASM representation of the QuantumComputation
the input and output permutations are given as comments in the first two lines of the QASM string. The format is:
// i Q_0, Q_1, ..., Q_n
… algorithmic qubit \(i\) is mapped to device qubit \(Q_i\).
// o Q_0, Q_1, ..., Q_n
… the value of algorithmic qubit \(i\) (assumed to be stored in classical bit \(c[i]\)) is measured at device qubit \(Q_i\).
1nqubits = 3
2qc = QuantumComputation(nqubits)
3qc.initial_layout[2] = 0
4qc.initial_layout[0] = 1
5qc.initial_layout[1] = 2
6
7qc.output_permutation[2] = 0
8qc.output_permutation[0] = 1
9qc.output_permutation[1] = 2
10
11
12print(qc.qasm3_str())
// i 2 0 1
// o 2 0 1
OPENQASM 3.0;
include "stdgates.inc";
qubit[3] q;
The layout information can also be automatically determined from measurements
using the initialize_io_mapping()
method:
1nqubits = 3
2qc = QuantumComputation(nqubits, nqubits) # 3 qubits, 3 classical bits
3
4qc.h(0)
5qc.x(1)
6qc.s(2)
7qc.measure(1, 0) # c[0] is measured at qubit 1
8qc.measure(2, 1) # c[1] is measured at qubit 2
9qc.measure(0, 2) # c[2] is measured at qubit 0
10qc.initialize_io_mapping() # determine permutation from measurement
11
12print(qc.qasm3_str())
// i 0 1 2
// o 1 2 0
OPENQASM 3.0;
include "stdgates.inc";
qubit[3] q;
bit[3] c;
h q[0];
x q[1];
s q[2];
c[0] = measure q[1];
c[1] = measure q[2];
c[2] = measure q[0];
Visualizing Circuits¶
Circuits can be printed in a human-readable format:
1from mqt.core import QuantumComputation
2
3nqubits = 2
4qc = QuantumComputation(nqubits, 1)
5
6qc.h(0)
7qc.cx(0, 1)
8qc.measure(1, 0)
9
10print(qc)
i: 0 1
1: h |
2: c x
3: | 0
o: 0 1
Operations¶
The operations in a QuantumComputation
object are of type Operation
.
Every type of operation in mqt-core
is derived from this class.
Operations can also be explicitly constructed.
Each Operation
has a type in the form of an OpType
.
StandardOperation
¶
A StandardOperation
is used to represent basic unitary gates. These can also be declared with arbitrary targets and controls.
1from math import pi
2
3from mqt.core.operations import OpType, StandardOperation
4
5nqubits = 3
6
7# u3 gate on qubit 0 in a 3-qubit circuit
8u_gate = StandardOperation(target=0, params=[pi / 4, pi, -pi / 2], op_type=OpType.u)
9
10# controlled x-rotation
11crx = StandardOperation(control=Control(0), target=1, params=[pi], op_type=OpType.rx)
12
13# multi-controlled x-gate
14mcx = StandardOperation(controls={Control(0), Control(1)}, target=2, op_type=OpType.x)
15
16# add operations to a quantum computation
17qc = QuantumComputation(nqubits)
18qc.append(u_gate)
19qc.append(crx)
20qc.append(mcx)
21
22print(qc)
i: 0 1 2
1: u | | p: (0.785398) (3.14159) (-1.5708)
2: c rx | p: (3.14159)
3: c c x
o: 0 1 2
NonUnitaryOperation
¶
A NonUnitaryOperation
is used to represent operations involving measurements or resets.
1from mqt.core.operations import NonUnitaryOperation
2
3nqubits = 2
4qc = QuantumComputation(nqubits, nqubits)
5qc.h(0)
6
7# measure qubit 0 on classical bit 0
8meas_0 = NonUnitaryOperation(target=0, classic=0)
9
10# reset all qubits
11reset = NonUnitaryOperation(targets=[0, 1], op_type=OpType.reset)
12
13qc.append(meas_0)
14qc.append(reset)
15
16print(qc.qasm3_str())
// i 0 1
// o 0 1
OPENQASM 3.0;
include "stdgates.inc";
qubit[2] q;
bit[2] c;
h q[0];
c[0] = measure q[0];
reset q;
SymbolicOperation
¶
A SymbolicOperation
can represent all gates of a StandardOperation
but the gate parameters can be symbolic.
Symbolic expressions are represented in MQT using the Expression
type, which represent linear combinations of symbolic Term
objects over some set of Variable
objects.
1from mqt.core.operations import SymbolicOperation
2from mqt.core.symbolic import Expression, Term, Variable
3
4nqubits = 1
5
6x = Variable("x")
7y = Variable("y")
8sym = Expression([Term(x, 2), Term(y, 3)])
9print(sym)
10
11sym += 1
12print(sym)
13
14# Create symbolic gate
15u1_symb = SymbolicOperation(target=0, params=[sym], op_type=OpType.p)
16
17# Mixed symbolic and instantiated parameters
18u2_symb = SymbolicOperation(target=0, params=[sym, 2.0], op_type=OpType.u2)
2*x + 3*y + 0
2*x + 3*y + 1
CompoundOperation
¶
A CompoundOperation
bundles multiple Operation
objects together.
1from mqt.core.operations import CompoundOperation
2
3nqubits = 2
4comp_op = CompoundOperation()
5
6# create bell pair circuit
7comp_op.append(StandardOperation(0, op_type=OpType.h))
8comp_op.append(StandardOperation(target=0, control=Control(1), op_type=OpType.x))
9
10qc = QuantumComputation(nqubits)
11qc.append(comp_op)
12
13print(qc)
i: 0 1
1:--------
: h |
: x c
---------
o: 0 1
Circuits can be conveniently turned into operations which allows to create nested circuits:
1from mqt.core import QuantumComputation
2
3nqubits = 2
4comp = QuantumComputation(nqubits)
5comp.h(0)
6comp.cx(0, 1)
7
8qc = QuantumComputation(nqubits)
9qc.append(comp.to_operation())
10
11print(qc)
i: 0 1
1:--------
: h |
: c x
---------
o: 0 1
Interfacing with other SDKs¶
Since a QuantumComputation
can be imported from and exported to an OpenQASM 3.0 (or OpenQASM 2.0) string, any library that can work with OpenQASM is easy to use in conjunction with the QuantumComputation
class.
In addition, mqt-core
can import Qiskit QuantumCircuit
objects directly.
1from qiskit import QuantumCircuit
2
3from mqt.core.plugins.qiskit import qiskit_to_mqt
4
5# GHZ circuit in qiskit
6qiskit_qc = QuantumCircuit(3)
7qiskit_qc.h(0)
8qiskit_qc.cx(0, 1)
9qiskit_qc.cx(0, 2)
10
11qiskit_qc.draw(output="mpl", style="iqp")
1mqt_qc = qiskit_to_mqt(qiskit_qc)
2print(mqt_qc)
circuit-158
i: 0 1 2
1: h | |
2: c x |
3: c | x
o: 0 1 2