MQT Qudits Tutorial¶
Discover a New Dimension in Quantum Computing. Embark on a journey with MQT Qudits, a framework for mixed-dimensional quantum computing. The following tutorial will guide you through the initial tools and contributions we have made to advance quantum information processing for science and technology.
User Inputs¶
1import numpy as np
2
3from mqt.qudits.quantum_circuit import QuantumCircuit
New QASM Extension:¶
Dive into a language meticulously designed to express quantum algorithms and circuits. MQT Qudits extends the OpenQASM 2.0 grammar, effortlessly adapting to mixed-dimensional registers. In the following, a DITQASM program is explicitly written, although several methods for importing programs from files are present in the library.
1qasm = """
2 DITQASM 2.0;
3
4 qreg field [7][5,5,5,5,5,5,5];
5 qreg matter [2];
6
7 creg meas_matter[7];
8 creg meas_fields[3];
9
10 h matter[0] ctl field[0] field[1] [0,0];
11 cx field[2], matter[0];
12 cx field[2], matter[1];
13 rxy (0, 1, pi, pi/2) field[3];
14
15 measure q[0] -> meas[0];
16 measure q[1] -> meas[1];
17 measure q[2] -> meas[2];
18 """
A new feature is the control syntax:
_operation_ __ctl__ _quditline_ [list of qudit control levels]
We can import the DITQASM program and construct a quantum circuit.
1circuit = QuantumCircuit()
2circuit.from_qasm(qasm)
3
4print(f"Number of operations: {len(circuit.instructions)}")
5print(f"Number of qudits in the circuit: {circuit.num_qudits}")
6print(f"Dimensions: {circuit.dimensions}")
Number of operations: 4
Number of qudits in the circuit: 9
Dimensions: [5, 5, 5, 5, 5, 5, 5, 2, 2]
Python Interface¶
Constructing and manipulating quantum programs becomes a breeze with Python. You have the flexibility to:
Initialize Quantum Circuits: Start by creating your quantum circuits effortlessly.
Create Quantum Registers: Build dedicated quantum registers tailored to your needs.
Compose Circuits: Seamlessly bring together your quantum registers, forming a unified and powerful circuit.
Apply Operations: Easily apply a variety of qudit operations, without worrying about the right representation.
Let’s construct a quantum circuit from scratch, with the python interface.
1from mqt.qudits.quantum_circuit import QuantumRegister
2
3circuit = QuantumCircuit()
4
5field_reg = QuantumRegister("fields", 1, [7])
6matter_reg = QuantumRegister("matter", 1, [2])
7
8circuit.append(field_reg)
9circuit.append(matter_reg)
10
11print(f"Number of operations: {len(circuit.instructions)}")
12print(f"Number of qudits in the circuit: {circuit.num_qudits}")
13print(f"Gate set: {circuit.gate_set}")
Number of operations: 0
Number of qudits in the circuit: 2
Gate set: csum
cu_one
cu_two
cu_multi
cx
gellmann
h
ls
ms
pm
r
rh
randu
rz
virtrz
s
x
z
No operations were inserted yet, let’s take a look at how operations can be applied!
The size of every line is detected automatically and the right operations are applied to the right qudits
1circuit.h(field_reg[0])
2circuit.csum([field_reg[0], matter_reg[0]])
3
4print(f"Number of operations: {len(circuit.instructions)}")
5print(f"Number of qudits in the circuit: {circuit.num_qudits}")
Number of operations: 2
Number of qudits in the circuit: 2
It is possible to export the code as well and share your program in a QASM file.
1print(circuit.to_qasm())
DITQASM 2.0;
qreg fields [1][7];
qreg matter [1][2];
creg meas[2];
h fields[0];
csum fields[0], matter[0];
measure fields[0] -> meas[0];
measure matter[0] -> meas[1];
Let’s save the circuit to a file
1file = circuit.save_to_file("my_circuit", ".")
and load it back
1circuit.load_from_file(file)
2
3print("Program:", circuit.to_qasm(), sep="\n")
4print("Dimensions: ", circuit.dimensions)
Program:
DITQASM 2.0;
qreg fields [1][7];
qreg matter [1][2];
creg meas[2];
h fields[0];
csum fields[0], matter[0];
measure fields[0] -> meas[0];
measure matter[0] -> meas[1];
Dimensions: [7, 2]
Custom gates can be added to the circuit as well.
1n = 5
2random_matrix = np.random.randn(n, n) + 1j * np.random.randn(n, n)
3
4Q, R = np.linalg.qr(random_matrix)
5
6unitary_matrix = Q
7cu = circuit.cu_one(field_reg[0], unitary_matrix)
Gates follow the order:
target qudit/s : list or single number
parameters list with order lower level, upper level, control level, theta, phi
control data
A simple qudit gate can be added as follows:
1r = circuit.r(field_reg[0], [0, 1, np.pi / 5, np.pi / 7])
Operations can also be controlled by other qudits, as shown below:
1from mqt.qudits.quantum_circuit.gate import ControlData
2
3r_c1 = circuit.r(field_reg[0], [0, 1, np.pi / 5, np.pi / 7], ControlData([matter_reg[0]], [1]))
or as
1r_c2 = circuit.r(field_reg[0], [0, 1, np.pi / 5, np.pi / 7]).control([matter_reg[0]], [1])
The representation of the matrix corresponding to a gate is dynamic:
0: no identities
1: identities in between long-range gates are introduced
2: full circuit unitary
1print(f"Gate matrix for {r._name}:", r.to_matrix(0), sep="\n")
Gate matrix for R7:
[[ 0.95105652+0.j -0.13407745-0.27841469j 0. +0.j
0. +0.j 0. +0.j 0. +0.j
0. +0.j ]
[ 0.13407745-0.27841469j 0.95105652+0.j 0. +0.j
0. +0.j 0. +0.j 0. +0.j
0. +0.j ]
[ 0. +0.j 0. +0.j 1. +0.j
0. +0.j 0. +0.j 0. +0.j
0. +0.j ]
[ 0. +0.j 0. +0.j 0. +0.j
1. +0.j 0. +0.j 0. +0.j
0. +0.j ]
[ 0. +0.j 0. +0.j 0. +0.j
0. +0.j 1. +0.j 0. +0.j
0. +0.j ]
[ 0. +0.j 0. +0.j 0. +0.j
0. +0.j 0. +0.j 1. +0.j
0. +0.j ]
[ 0. +0.j 0. +0.j 0. +0.j
0. +0.j 0. +0.j 0. +0.j
1. +0.j ]]
The inverse of any gate can easily be obtained.
1rd = r.dag()
2print(f"Inverse gate matrix for {r._name}:", rd.to_matrix(0), sep="\n")
Inverse gate matrix for R7_dag:
[[ 0.95105652-0.j 0.13407745+0.27841469j 0. -0.j
0. -0.j 0. -0.j 0. -0.j
0. -0.j ]
[-0.13407745+0.27841469j 0.95105652-0.j 0. -0.j
0. -0.j 0. -0.j 0. -0.j
0. -0.j ]
[ 0. -0.j 0. -0.j 1. -0.j
0. -0.j 0. -0.j 0. -0.j
0. -0.j ]
[ 0. -0.j 0. -0.j 0. -0.j
1. -0.j 0. -0.j 0. -0.j
0. -0.j ]
[ 0. -0.j 0. -0.j 0. -0.j
0. -0.j 1. -0.j 0. -0.j
0. -0.j ]
[ 0. -0.j 0. -0.j 0. -0.j
0. -0.j 0. -0.j 1. -0.j
0. -0.j ]
[ 0. -0.j 0. -0.j 0. -0.j
0. -0.j 0. -0.j 0. -0.j
1. -0.j ]]
The control information can be accessed as well.
1r_c1.control_info
{'target': 0,
'dimensions_slice': 7,
'params': [0, 1, 0.6283185307179586, 0.4487989505128276],
'controls': ControlData(indices=[1], ctrl_states=[1])}
Two- and multi-qudit gates follow the rule:
two : target_qudits first is control, second is target
multi: all are controls, except last one is target
1r_c1.reference_lines
[1, 0]
Simulation¶
After crafting your quantum circuit with precision, take it for a spin using two distinct engines, each flaunting its unique set of data structures.
External Tensor-Network Simulator: Delve into the quantum realm with a robust external tensor-network simulator. Can simulate all the gate-set.
MiSiM (C++-Powered): Unleash the power of decision-diagram-based simulation with MiSiM, seamlessly interfaced with Python for a fluid and efficient experience. Can only simulate the machine following machine gate set:
csum
cx
h
rxy
rz
rh
virtrz
s
x
z
Basic Simulation¶
1circuit = QuantumCircuit()
2
3field_reg = QuantumRegister("fields", 1, [3])
4matter_reg = QuantumRegister("matter", 1, [3])
5
6circuit.append(field_reg)
7circuit.append(matter_reg)
8
9h = circuit.h(field_reg[0])
10csum = circuit.csum([field_reg[0], matter_reg[0]])
11
12print(f"Number of operations: {len(circuit.instructions)}")
13print(f"Number of qudits in the circuit: {circuit.num_qudits}")
Number of operations: 2
Number of qudits in the circuit: 2
1from mqt.qudits.simulation import MQTQuditProvider
2
3provider = MQTQuditProvider()
4provider.backends("sim")
['tnsim', 'misim']
1from mqt.qudits.visualisation import plot_counts, plot_state
2
3backend = provider.get_backend("tnsim")
4
5job = backend.run(circuit)
6result = job.result()
7
8state_vector = result.get_state_vector()
9
10plot_state(state_vector, circuit)
array([0.57735027, 0. , 0. , 0. , 0.57735027,
0. , 0. , 0. , 0.57735027])
1backend = provider.get_backend("misim")
2
3job = backend.run(circuit)
4result = job.result()
5
6state_vector = result.get_state_vector()
7
8plot_state(state_vector, circuit)
array([0.57735027, 0. , 0. , 0. , 0.57735027,
0. , 0. , 0. , 0.57735027])
Extending Engines with Noise Model and Properties for FakeBackend¶
Enhance your quantum simulation experience by extending the engines with a noise model and incorporating various properties. By combining a noise model and carefully tuned properties, you can craft a FakeBackend that closely emulates the performance of the best quantum machines in experimental laboratories. This allows for more realistic and insightful quantum simulations.
Experiment, iterate, and simulate quantum circuits with the sophistication of real-world conditions, all within the controlled environment of your simulation.
1from mqt.qudits.simulation.noise_tools.noise import Noise, NoiseModel
2
3# Depolarizing quantum errors
4local_error = Noise(probability_depolarizing=0.001, probability_dephasing=0.001)
5local_error_rz = Noise(probability_depolarizing=0.03, probability_dephasing=0.03)
6
7entangling_error = Noise(probability_depolarizing=0.1, probability_dephasing=0.001)
8entangling_error_extra = Noise(probability_depolarizing=0.1, probability_dephasing=0.1)
9
10entangling_error_on_target = Noise(probability_depolarizing=0.1, probability_dephasing=0.0)
11entangling_error_on_control = Noise(probability_depolarizing=0.01, probability_dephasing=0.0)
12
13# Add errors to noise_tools model
14
15noise_model = NoiseModel() # We know that the architecture is only two qudits
16# Very noisy gate
17noise_model.add_all_qudit_quantum_error(local_error, ["csum"])
18noise_model.add_recurrent_quantum_error_locally(local_error, ["csum"], [0])
19# Entangling gates
20noise_model.add_nonlocal_quantum_error(entangling_error, ["cx", "ls", "ms"])
21noise_model.add_nonlocal_quantum_error_on_target(entangling_error_on_target, ["cx", "ls", "ms"])
22noise_model.add_nonlocal_quantum_error_on_control(entangling_error_on_control, ["csum", "cx", "ls", "ms"])
23# Super noisy Entangling gates
24noise_model.add_nonlocal_quantum_error(entangling_error_extra, ["csum"])
25# Local Gates
26noise_model.add_quantum_error_locally(local_error, ["h", "rxy", "s", "x", "z"])
27noise_model.add_quantum_error_locally(local_error_rz, ["rz", "virtrz"])
28
29print(noise_model.quantum_errors)
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[22], line 18
16 # Very noisy gate
17 noise_model.add_all_qudit_quantum_error(local_error, ["csum"])
---> 18 noise_model.add_recurrent_quantum_error_locally(local_error, ["csum"], [0])
19 # Entangling gates
20 noise_model.add_nonlocal_quantum_error(entangling_error, ["cx", "ls", "ms"])
AttributeError: 'NoiseModel' object has no attribute 'add_recurrent_quantum_error_locally'
We can set the noise model for the simulation, but also set several other flags:
shots
: number of shots for the stochastic simulationmemory
: flag for saving shots (True/False)full_state_memory
: save the full noisy statesfile_path
: file path of the h5 database storing the datafile_name
: name of the file
1backend = provider.get_backend("tnsim")
2
3job = backend.run(circuit, noise_model=noise_model)
4
5result = job.result()
6counts = result.get_counts()
7
8plot_counts(counts, circuit)
You can also invoke a fake backend and retrieve a few relevant properties, that are already embedded in them
1provider = MQTQuditProvider()
2provider.backends("fake")
1backend_ion = provider.get_backend("faketraps2trits", shots=1000)
1import matplotlib.pyplot as plt
2import networkx as nx
3
4mapping = backend_ion.energy_level_graphs
5
6pos = nx.circular_layout(mapping[0])
7nx.draw(mapping[0], pos, with_labels=True, node_size=2000, node_color="lightblue", font_size=12, font_weight="bold")
8plt.show()
1job = backend_ion.run(circuit)
2result = job.result()
3counts = result.get_counts()
4
5plot_counts(counts, circuit)
Compilation¶
Tailor your quantum compilation process to achieve optimal performance and emulate the intricacies of experimental setups.
Compiler Customization with Modern Passes¶
Optimization Strategies: Implement specific optimization strategies based on your quantum algorithm’s characteristics. Fine-tune compilation for better resource utilization and reduced gate counts.
Gate Decomposition: Customize gate decomposition techniques to match the capabilities of experimental quantum hardware. Aligning with the native gate set enhances the efficiency of your compiled circuits.
Experimental-Inspired Compilation¶
Emulate the features of the best experimental laboratories in your compilation process. Leverage modern compiler passes to customize optimization, gate decomposition, and noise-aware strategies, creating compiled circuits that closely resemble the challenges and advantages of cutting-edge quantum hardware.
Customize, compile, and push the boundaries of quantum algorithms with a tailored approach to quantum compilation.
1from mqt.qudits.compiler import QuditCompiler
1qudit_compiler = QuditCompiler()
2
3passes = ["PhyLocQRPass"]
1compiled_circuit_qr = qudit_compiler.compile(backend_ion, circuit, passes)
2
3print(f"Number of operations: {len(compiled_circuit_qr.instructions)}")
4print(f"Number of qudits in the circuit: {compiled_circuit_qr.num_qudits}")
1job = backend_ion.run(compiled_circuit_qr)
2
3result = job.result()
4counts = result.get_counts()
5
6plot_counts(counts, compiled_circuit_qr)
1passes = ["PhyLocAdaPass", "ZPropagationPass", "ZRemovalPass"]
2
3compiled_circuit_ada = qudit_compiler.compile(backend_ion, circuit, passes)
4
5print(f"Number of operations: {len(compiled_circuit_ada.instructions)}")
6print(f"Number of qudits in the circuit: {compiled_circuit_ada.num_qudits}")
1job = backend_ion.run(compiled_circuit_ada)
2
3result = job.result()
4counts = result.get_counts()
5
6plot_counts(counts, compiled_circuit_ada)
1from mqt.qudits.visualisation import draw_qudit_local
2
3draw_qudit_local(compiled_circuit_ada)