Metadata-Version: 2.1
Name: olsq
Version: 0.0.2
Summary: Optimal Layout Synthesis for Quantum Computing (OLSQ) for mapping and scheduling quantum circuits
Home-page: https://github.com/tbcdebug/OLSQ
Author: Daniel Bochen Tan
Author-email: bctan@cs.ucla.edu
License: BSD
Project-URL: Bug Tracker, https://github.com/tbcdebug/OLSQ/issues
Description: [![iccad](https://img.shields.io/badge/Published-ICCAD'20-brightgreen.svg?style=for-the-badge)](https://ieeexplore.ieee.org/document/9256696)
        [![arXiv](https://img.shields.io/badge/arXiv-2007.15671-brightgreen.svg?style=for-the-badge)](https://arxiv.org/abs/2007.15671)
        [![Unitary Fund](https://img.shields.io/badge/Supported%20By-UNITARY%20FUND-brightgreen.svg?style=for-the-badge)](http://unitary.fund)
        
        # OLSQ: Optimal Layout Synthesis for Quantum Computing
        
        Many 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 to [the paper](https://doi.org/10.1145/3400302.3415620).
        For more details on the software implementation, please refer to the [API documentation](https://olsq.readthedocs.io/en/latest/).
        Below is a brief tutorial on how to use the package.
        
        ## Installation
        
        ```
        python3 -m pip install olsq
        ```
        Please make sure that you have `networkx` version `>=2.5` and `z3-solver` version `>=4.8.9.0` in your Python environment.
        
        ## Initialization
        
        ```
        from 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_name` and `mode`.
        - `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 device
        
        To perform LSQC, we need to know the connections between the qubits, which is information about the physical device.
        We are going to use the `setdevice` method.
        In general, there are three ways: 
        1. Directly construct a device with some properties.
        2. Use one of the hard-coded devices (including all the devices appeared in the paper).
        3. Use device defined in other packages: refer to later parts of this tutorial on [Cirq](#cirq-interface) and [Qiskit](#qiskit-interface).
        
        ```
        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 class `qcdevice` to store the properties of the device that we need, which can be constructed with these arguments.
        (The last three are only for fidelity optimization.)
        - `name`
        - `nqubits`: the number of physical qubits
        - `connection`: a list of physical qubit pairs corresponding to edges in the coupling graph
        - `swap_duration`: number of cycles a SWAP gate takes.
           Usually it is either one, or three meaning three CX gates.
        - `fmeas`: a list of measurement fidelity
        - `fsingle`: a list of single-qubit gate fidelity
        - `ftwo`: a list of two-qubit gate fidelity, indices aligned with `connection`
        
        If `name` starts with `"default_"`, a hard-coded device stored in `olsq/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 Program
        
        Apart from the device, we need the quantum program/circuit to execute, which can be set with the `setprogram` method.
        _To be safe, always set the device first and then the program._
        
        OLSQ has an intermediate representation (IR) of quantum programs. (For details, refer to [a later part](#olsq-ir) of this tutorial.)
        In general, there are four ways to set the program: 
        1. Use OLSQ IR
        2. Use a string in QASM format
        3. Use an QASM file, e.g., one of programs used in the paper in `olsq/benchmarks/`.
        4. Use programs defined in other packages: refer to later parts of this tutorial on [Cirq](#cirq-interface) and [Qiskit](#qiskit-interface).
        
        ```
        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 a QASM file from olsq/benchmarks
        circuit_file = open("olsq/benchmarks/toffoli.qasm", "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 Output
        
        It 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 device `ourense`.
        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()
        ```
        
        The `solve` method can take two optional arguemnts
        - `output_mode`: can be `"IR"`. Refer [here](#olsq-ir) on what would be returned in this case.
        - `output_file_name`
        
        If `output_mode` is default, the return is a tuple of three things:
        - A string representing the output quantum program in QASM format.
        If `output_file_name` is 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_value
        
        The 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  1
        ```
        
        ## Cirq Interface
        
        We can input a `networkx.Graph` object representing the devie to `setdevicegraph`.
        Note that the method name is different from `setdevice`.
        (Such a representation is used in some components in Cirq, e.g.,`device_graph` on [this line](https://github.com/quantumlib/Cirq/blob/8f9d8597364b8bd0d29833cbbd014ebf1c62f3db/cirq/contrib/quantum_volume/quantum_volume.py#L215).)
        
        We can input a `cirq.Circuit` object as program in `setprogram`.
        ```
        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 Interface
        
        A `backend` from `IBMQ` can be input to the `setdevice` method with the second argument set to `"ibm"`.
        
        There are two arguments for the `setprogram` method of `OLSQ_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 a `QuantumCircuit` object 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.setdevicegraph(backend, "ibm")
        
        # result_circuit is a qiskit.QuantumCircuit object
        result_circuit, final_mapping, objective_value = lsqc_solver.solve()
        ```
        
        ## TB-OLSQ
        
        The transition-based mode is enabled if chosen at the initiation of `OLSQ`.
        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 to [the paper](https://doi.org/10.1145/3400302.3415620).
        The returned QASM string and `final_mapping` should 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 IR
        
        OLSQ IR contains three things:
        1. `count_program_qubit`: the number of qubits in the program.
        2. `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.
        3. `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 the `solve` method, `output_mode` is set to `"IR"`, the return is a tuple of five things
        1. `result_depth`: depth of the resulting quantum program
        2. `list_scheduled_gate_name`: similar to `gate_spec` in the IR
        3. `list_scheduled_gate_qubits`: similar to `gates` in the IR
        4. `final_mapping`
        5. `objective_value`
        
        ## BibTeX 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},
        }
        ```
        
Platform: UNKNOWN
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
Requires-Python: >=3.6
Description-Content-Type: text/markdown
