Package: olsq
OLSQ: Optimal Layout Synthesis for Quantum ComputingMany quantum computers have constraints on the connections between qubits.
However, a quantum program may not conform to these constraints.
Thus, it is necessary to perform 'layout synthesis for quantum computing', LSQC, which transforms quantum programs prior to execution so that the connectivity issues are resolved.OLSQ can solve LSQC optimally with respect to depth, number of SWAP gates, or fidelity.
There is also a transition-based mode (TB) to speed it up with little loss of optimality.
TB-OLSQ can reduce SWAP count by 70% and increase fidelity by 1.30x compared to leading previous works at the time of publication.For more details on the theory and the experiments, please refer tothe paper.
For more details on the software implementation, please refer to theAPI documentation.
Below is a brief tutorial on how to use the package.Installationpip install -U olsqPlease make sure that you havenetworkxversion>=2.5andz3-solverversion>=4.8.9.0in your Python environment.Initializationfrom olsq import OLSQ

# initiate olsq with depth as objective, in normal mode
lsqc_solver = OLSQ("depth", "normal")There are two argument in the constructor of OLSQ:objective_nameandmode.objective_name:"depth","swap", or"fidelity".mode:"normal"or"transition".
The latter stands for TB-OLSQ in the paper, which is usually much faster with little loss of optimality.Setting the deviceTo perform LSQC, we need to know the connections between the qubits, which is information about the physical device.
We are going to use thesetdevicemethod.
In general, there are three ways:Directly construct a device with some properties.Use one of the hard-coded devices (including all the devices appeared in the paper).Use device defined in other packages: refer to later parts of this tutorial onCirqandQiskit.from olsq.device import qcdevice

# directly construct a device from properties needed by olsq
lsqc_solver.setdevice( qcdevice(name="dev", nqubits=5, 
     connection=[(0, 1), (1, 2), (1, 3), (3, 4)], swap_duration=3) )We use a minimalist classqcdeviceto store the properties of the device that we need, which can be constructed with these arguments.
(The last three are only for fidelity optimization.)namenqubits: the number of physical qubitsconnection: a list of physical qubit pairs corresponding to edges in the coupling graphswap_duration: number of cycles a SWAP gate takes.
Usually it is either one, or three meaning three CX gates.fmeas: a list of measurement fidelityfsingle: a list of single-qubit gate fidelityftwo: a list of two-qubit gate fidelity, indices aligned withconnectionIfnamestarts with"default_", a hard-coded device stored inolsq/devices/would be loaded.
Other arguments can still be specified, in which case the original device properties would be replaced by the input.# use a hard-coded device in olsq/devices/ called ourense
# which actually has the same properties as the device we constructed above
lsqc_solver.setdevice( qcdevice("default_ourense") )Setting the Input ProgramApart from the device, we need the quantum program/circuit to execute, which can be set with thesetprogrammethod.To be safe, always set the device first and then the program.OLSQ has an intermediate representation (IR) of quantum programs. (For details, refer toa later partof this tutorial.)
In general, there are four ways to set the program:Use OLSQ IRUse a string in QASM formatUse an QASM file, e.g., one of programs used in the paper inolsq/benchmarks/.Use programs defined in other packages: refer to later parts of this tutorial onCirqandQiskit.circuit_str = "OPENQASM 2.0;\ninclude \"qelib1.inc\";\nqreg q[3];\nh q[2];\n" \
              "cx q[1], q[2];\ntdg q[2];\ncx q[0], q[2];\nt q[2];\n" \
              "cx q[1], q[2];\ntdg q[2];\ncx q[0], q[2];\nt q[1];\nt q[2];\n" \
              "cx q[0], q[1];\nh q[2];\nt q[0];\ntdg q[1];\ncx q[0], q[1];\n"

# input the quantum program as a QASM string
lsqc_solver.setprogram(circuit_str)The example above is a Toffoli gate.
We can also load an QASM file of it.# load one of the QASM files from olsq/benchmarks
lsqc_solver.setprogram("toffoli", input_mode="benchmark")

# load your own QASM file
# circuit_file = open("my-qasm-file", "r").read()

lsqc_solver.setprogram(circuit_file)

# Toffoli Gate:
#                                                        ┌───┐      
# q_0: ───────────────────■─────────────────────■────■───┤ T ├───■──
#                         │             ┌───┐   │  ┌─┴─┐┌┴───┴┐┌─┴─┐
# q_1: ───────■───────────┼─────────■───┤ T ├───┼──┤ X ├┤ TDG ├┤ X ├
#      ┌───┐┌─┴─┐┌─────┐┌─┴─┐┌───┐┌─┴─┐┌┴───┴┐┌─┴─┐├───┤└┬───┬┘└───┘
# q_2: ┤ H ├┤ X ├┤ TDG ├┤ X ├┤ T ├┤ X ├┤ TDG ├┤ X ├┤ T ├─┤ H ├──────
#      └───┘└───┘└─────┘└───┘└───┘└───┘└─────┘└───┘└───┘ └───┘      
"""Solving and OutputIt can be seen that in the Toffoli gate above, there are two-qubit gates on pair(q_0,q_1),(q_1,q_2), and(q_2,q_0).
However, there are no such triangles on deviceourense.
This means that no matter how the qubits in the program are mapped to physical qubits, we need to insert SWAP gates.# solve LSQC
result = lsqc_solver.solve()Thesolvemethod can take two optional arguemntsoutput_mode: can be"IR". Referhereon what would be returned in this case.output_file_nameIfoutput_modeis default, the return is a tuple of three things:A string representing the output quantum program in QASM format.
Ifoutput_file_nameis provided, then the QASM string would be written to that file.final_mapping: from each program qubit to the corresponding physical qubit at the end of execution.objective_valueThe result of the Toffoli example is shown below.
Note that a SWAP gate, decomposed into three CX gates, has been inserted.# a LSQC solution to the Toffoli gate on device 'ourense'
#                                                  ┌───┐     ┌───┐┌───┐ ┌───┐      ┌─┐      
# q_0: ───────────────────■─────────────────────■──┤ X ├──■──┤ X ├┤ T ├─┤ H ├──────┤M├──────
#      ┌───┐┌───┐┌─────┐┌─┴─┐┌───┐┌───┐┌─────┐┌─┴─┐└─┬─┘┌─┴─┐└─┬─┘└───┘ ├───┤      └╥┘┌─┐   
# q_1: ┤ H ├┤ X ├┤ TDG ├┤ X ├┤ T ├┤ X ├┤ TDG ├┤ X ├──■──┤ X ├──■────■───┤ T ├───■───╫─┤M├───
#      └───┘└─┬─┘└─────┘└───┘└───┘└─┬─┘└┬───┬┘└───┘     └───┘     ┌─┴─┐┌┴───┴┐┌─┴─┐ ║ └╥┘┌─┐
# q_2: ───────■─────────────────────■───┤ T ├─────────────────────┤ X ├┤ TDG ├┤ X ├─╫──╫─┤M├
#                                       └───┘                     └───┘└─────┘└───┘ ║  ║ └╥┘
# q_3: ─────────────────────────────────────────────────────────────────────────────╫──╫──╫─
#                                                                                   ║  ║  ║
# q_4: ─────────────────────────────────────────────────────────────────────────────╫──╫──╫─
#                                                                                   ║  ║  ║
# c: 5/═════════════════════════════════════════════════════════════════════════════╩══╩══╩═
#                                                                                   2  0  1Cirq InterfaceWe can input anetworkx.Graphobject representing the devie tosetdevicegraph.
Note that the method name is different fromsetdevice.
(Such a representation is used in some components in Cirq, e.g.,device_graphonthis line.)We can input acirq.Circuitobject as program insetprogram.from olsq.olsq_cirq import OLSQ_cirq

lsqc_solver = OLSQ_cirq("depth", "normal")

# use a cirq.Circuit object as program
lsqc_solver.setprogram(circuit)

# use a networkx.Graph object representing the device
lsqc_solver.setdevicegraph(device_graph)

# result_circuit is a cirq.Circuit object
result_circuit, final_mapping, objective_value = lsqc_solver.solve()Qiskit InterfaceAbackendfromIBMQcan be input to thesetdevicemethod with the second argument set to"ibm".There are two arguments for thesetprogrammethod ofOLSQ_qiskit: if the second is"qasm", input a QASM string representing the quantum program as the first argument; if the second is none, then input aQuantumCircuitobject in Qiskit as the first argument.from qiskit import IBMQ
from olsq.olsq_qiskit import OLSQ_qiskit

lsqc_solver = OLSQ_qiskit("depth", "normal")

# use a qiskit.QuantumCircuit object as program
lsqc_solver.setprogram(circuit)

provider = IBMQ.load_account()
backend = provider.get_backend("ibmq_lima") # change to your backend of choice
# use an IBMQ backend as the device
lsqc_solver.setdevice(backend, "ibm")

# result_circuit is a qiskit.QuantumCircuit object
result_circuit, final_mapping, objective_value = lsqc_solver.solve()TB-OLSQThe transition-based mode is enabled if chosen at the initiation ofOLSQ.
Roughly speaking, we only use a kind of coarse-grain time in this mode, so the runtime is much shorter.
For theoretical details, please refer tothe paper.
The returned QASM string andfinal_mappingshould be similar to what they were before.
Only if the objective is"depth", the objective value would be very different from the normal mode.
There is only one SWAP inserted, so there are only two coarse-grain time steps, separated by the SWAP, whereas there are 14 time steps if using exact time.OLSQ IROLSQ IR contains three things:count_program_qubit: the number of qubits in the program.gates: a list of tuples representing qubit(s) acted on by a gate, each tuple has one index if it is a single-qubit gate, two indices if it is a two-qubit gate.gate_spec: list of type/name of each gate, which is not important to OLSQ, and only needed when generating output.# For the following circuit
# q_0: ───────────────────■───
#                         │  
# q_1: ───────■───────────┼───
#      ┌───┐┌─┴─┐┌─────┐┌─┴─┐
# q_2: ┤ H ├┤ X ├┤ TDG ├┤ X ├─
#      └───┘└───┘└─────┘└───┘ 

# count_program_qubit = 3
# gates = ((2,), (1,2), (2,), (0,1))
# gate_spec = ("h", "cx", "tdg", "cx")If in thesolvemethod,output_modeis set to"IR", the return is a tuple of five thingsresult_depth: depth of the resulting quantum programlist_scheduled_gate_name: similar togate_specin the IRlist_scheduled_gate_qubits: similar togatesin the IRfinal_mappingobjective_valueBibTeX Citation@InProceedings{iccad20-tan-cong-optimal-layout-synthesis,
  author          = {Tan, Bochen and Cong, Jason},
  booktitle       = {Proceedings of the 39th International Conference on Computer-Aided Design},
  title           = {Optimal Layout Synthesis for Quantum Computing},
  year            = {2020},
  address         = {New York, NY, USA},
  publisher       = {Association for Computing Machinery},
  series          = {ICCAD '20},
  archiveprefix   = {arXiv},
  eprint          = {2007.15671},
  primaryclass    = {quant-ph},
  articleno       = {137},
  doi             = {10.1145/3400302.3415620},
  isbn            = {9781450380263},
  keywords        = {quantum computing, scheduling, allocation, mapping, placement, layout synthesis},
  location        = {Virtual Event, USA},
  numpages        = {9},
  url             = {https://doi.org/10.1145/3400302.3415620},
}