Decomposing gate-based circuits: qiskit and myQLM
In this notebook, we show how circuits from Qiskit and myQLM can be converted into Perceval circuits. To do so, we take the example of a simple gate-based circuit producing GHZ states. We then show the translation to a linear optical circuit. We also show the equivalence between the two circuits (gate-based and perceval).
As usual, we start by importing the needed libraries. Note that this notebook requires the installation of Qiskit and MyQLM (which can be easily done with pip install qiskit
and pip install myqlm
).
[1]:
import perceval as pcvl
from perceval.components import catalog
from perceval.converters import QiskitConverter, MyQLMConverter
from perceval.algorithm import Analyzer, Sampler
from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector
import qat.lang.AQASM as qataqasm
Conversion from Qiskit Circuit
GHZ State generation in Qiskit
We first define the circuit generating GHZ states of 3 qubits with Qiskit. To do so, we first act with a Hadamard gate on qubit 0 to put in superposition of state \(|0\rangle\) and \(|1\rangle\). Then we perform two CNOT gates using qubit 0 as control and qubits 1 and 2 as targets.
[2]:
# Create a Quantum Circuit acting on the q register
qiskit_circuit = QuantumCircuit(3)
# Add a H gate on qubit 0
qiskit_circuit.h(0)
# Add CX (CNOT) gates on control qubit 0 and target qubits 1 and 2
qiskit_circuit.cx(0, 1)
qiskit_circuit.cx(0, 2)
# Draw the circuit
qiskit_circuit.draw()
[2]:
┌───┐ q_0: ┤ H ├──■────■── └───┘┌─┴─┐ │ q_1: ─────┤ X ├──┼── └───┘┌─┴─┐ q_2: ──────────┤ X ├ └───┘
We display the final state when starting from the input state \(|000\rangle\).
[3]:
# Set the initial state of the simulator to the ground state using from_int
state = Statevector.from_int(0, 2**3)
# Evolve the state by the quantum circuit
state = state.evolve(qiskit_circuit)
#draw using latex
state.draw('latex')
[3]:
Conversion of Qiskit circuit to Perceval
With the use of QiskitConverter
, we can transform the Qiskit circuit into a Perceval circuit. It uses 2 modes per qubit and additional modes for ancillary photons to perform deterministically two-qubit gates. Below the first six modes correspond to the three logical qubits (see the ‘Spatial Modes encoding’ paragraph in the ‘Basics’ section of the documentation) of the gate-based circuit above.
The other modes are used to successfully implement two-qubit gates via heralding or post-selection. Heralding employs 4 ancillary modes while post-selection employs 2 ancillary modes. With the option use_postselection=True
in the method .convert
on a QiskitConverter
object, every CNOT but the last is implemented with a heralding scheme. Here it means that it would add
\(4+2\) ancillary modes. The option use_postselection=False
only implements heralded CNOTs. Here it would mean \(4+4\) ancillary modes. Note: the use_postselection
option is True
by default.
[4]:
qiskit_converter = QiskitConverter(catalog, backend_name="Naive")
quantum_processor = qiskit_converter.convert(qiskit_circuit, use_postselection=True)
pcvl.pdisplay(quantum_processor, recursive=True)
[4]:
With this converted circuit, we can now check that the resulting state is the same as before the conversion. By default, the input is the logical state \(|000\rangle_L\). Note that where Qiskit displays state in the order \(|q_2q_1q_0\rangle_L\), Perceval uses the reverse order \(|q_0q_1q_2\rangle_L\), but still shown as Fock states. Here, it doesn’t change anything since we end with only \(|000\rangle_L\) and \(|111\rangle_L\) states.
[5]:
# Not necessary here
quantum_processor.with_input(pcvl.LogicalState([0,0,0]))
sampler = Sampler(quantum_processor)
output_distribution = sampler.probs()["results"]
pcvl.pdisplay(output_distribution, precision=1e-2, max_v = 4)
state | probability |
---|---|
|1,0,1,0,1,0> | 1/2 |
|0,1,0,1,0,1> | 1/2 |
This circuit can now be converted using a general interferometer decomposition so it can be implemented on a generic photonic chip.
[6]:
# use either quantum_processor (after Qiskit above) or converted_processor (after myqlm above)
u = quantum_processor.linear_circuit().compute_unitary(use_symbolic=False)
[7]:
ub = (pcvl.Circuit(2)
// pcvl.BS(theta=pcvl.Parameter("theta"))
// (0, pcvl.PS(phi=pcvl.Parameter("φ_a"))))
pc_norm = pcvl.Circuit.decomposition(u, ub, shape=pcvl.InterferometerShape.TRIANGLE)
pcvl.pdisplay(pc_norm, compact=True, render_size=0.5)
[7]:
A cnot based on CZ
Another interesting example we can explore is how to build a cnot from a CZ gate using qiskit or directly the CZ gate of myqlm then convert it to Perceval. We will apply the following equivalence:
The code in Qiskit:
[8]:
qiskit_circuit = QuantumCircuit(2)
# Add (CNOT) built using equivalence with H-CZ-H
qiskit_circuit.h(1)
qiskit_circuit.cz(0, 1)
qiskit_circuit.h(1)
# Draw the circuit
qiskit_circuit.draw()
[8]:
q_0: ──────■────── ┌───┐ │ ┌───┐ q_1: ┤ H ├─■─┤ H ├ └───┘ └───┘
Then we call the converter like the previous example
[9]:
state = Statevector.from_int(0, 2**3)
state = state.evolve(qiskit_circuit)
qiskit_converter = QiskitConverter(catalog, backend_name="SLOS")
quantum_processor = qiskit_converter.convert(qiskit_circuit)
pcvl.pdisplay(quantum_processor, recursive=True) # the perceval processor can be displayed at this point if needed
[9]:
[10]:
input_states = [pcvl.BasicState([1, 0, 1, 0]), pcvl.BasicState([1, 0, 0, 1]), pcvl.BasicState([0, 1, 1, 0]), pcvl.BasicState([0, 1, 0, 1])]
analyzer = Analyzer(quantum_processor, input_states)
pcvl.pdisplay(analyzer)
|1,0,1,0> | |1,0,0,1> | |0,1,1,0> | |0,1,0,1> | |
---|---|---|---|---|
|1,0,1,0> | 1 | 0 | 0 | 0 |
|1,0,0,1> | 0 | 1 | 0 | 0 |
|0,1,1,0> | 0 | 0 | 0 | 1 |
|0,1,0,1> | 0 | 0 | 1 | 0 |
This is the truth table of a CNOT gate
Conversion from MyQLM Circuit
Analogous to QiskitConverter
, employ a MyQLMConverter
object to convert a MyQLM circuit to a Perceval processor with each qubit of the circuit represented by 2 modes and additional modes for ancillary photons to perform deterministically two-qubit gates. (Read above in section for qiskit converter for a discussion on how ancillary modes are set).
Circuit to generate GHZ State in MyQLM
[14]:
# Create a myqlm program
qprog = qataqasm.Program()
# Allocate qbits to the Program
qbits = qprog.qalloc(3)
# Add gates
qprog.apply(qataqasm.H, qbits[0])
qprog.apply(qataqasm.CNOT, qbits[0], qbits[1])
qprog.apply(qataqasm.CNOT, qbits[0], qbits[2])
# Convert program to myqlm circuit
myqlm_circuit = qprog.to_circ()
myqlm_circuit.display()
Conversion of Myqlm circuit to Perceval
[12]:
myqlm_converter = MyQLMConverter(catalog, backend_name="Naive")
converted_processor = myqlm_converter.convert(myqlm_circuit, use_postselection=True)
pcvl.pdisplay(converted_processor, recursive=True)
[12]:
[13]:
# computing results with converted processor from myqlm
converted_processor.with_input(pcvl.LogicalState([0,0,0]))
sampler = Sampler(converted_processor)
output_distribution_myqlm_pcvl = sampler.probs()["results"]
pcvl.pdisplay(output_distribution_myqlm_pcvl, precision=1e-2, max_v = 4)
state | probability |
---|---|
|1,0,1,0,1,0> | 1/2 |
|0,1,0,1,0,1> | 1/2 |
Note : The result is exactly the same as previously obtained from converter from Qiskit and is also the expected result.
Few remarks
Controlflow operations such as measurement operator in the qiskit ciruit or
qiskit.circuit.QuantumCircuit.if_test
are not supported.Custom gates from Qiskit are also not supported at the moment (see Issue#201).
Only 1-Qubit gates and the following 2-Qubits gates - CNOT, CSIGN(/CZ), and, SWAP from MyQLM are supported.