[PYTHON] Make a Blueqat backend ~ Part 2

Blueqat library for easy quantum programming

Is developing. https://github.com/Blueqat/Blueqat

Last time

In Making a Blueqat backend ~ Part 1, I explained how to make a simulator backend that can accept OpenQASM input. This time, I will explain how to make a backend in the general case where it is not.

How to make it all yourself

At a minimum, you can create a backend by inheriting Backend and implementing the run method. It also reimplements the copy method if the copy of the backend object itself is not convenient for copy.deepcopy.

The run method is in the formrun (self, gates, n_qubits, * args, ** kwargs). gates is the list of quantum gate objects, n_qubits is the number of qubits, * args, ** kwargs is when the user calls Circuit (). Run () The argument of run is passed.

If you implement this, you can do a backend, but since it is thrown too much to the developer, we have prepared the following flow. It sounds confusing, but if you follow the flow, it can be a little easier to implement. Also, when reimplementing the run method, you may use the one you have prepared.

How to get on the flow you have prepared

Overall flow

By default, calling the run method calls the following code.

    def _run(self, gates, n_qubits, args, kwargs):
        gates, ctx = self._preprocess_run(gates, n_qubits, args, kwargs)
        self._run_gates(gates, n_qubits, ctx)
        return self._postprocess_run(ctx)

    def _run_gates(self, gates, n_qubits, ctx):
        """Iterate gates and call backend's action for each gates"""
        for gate in gates:
            action = self._get_action(gate)
            if action is not None:
                ctx = action(gate, ctx)
            else:
                ctx = self._run_gates(gate.fallback(n_qubits), n_qubits, ctx)
        return ctx

What backend developers should implement

--_preprocess_run method --Action for each gate --_postprocess_run method

is.

About ctx

Notice the variable labeled ctx here. This variable is a variable to hold the state from the beginning to the end of run. (If you don't need it, you can use None, but I think there are few cases where you don't need it at all.) Since the backend itself is also an ordinary object, you can give it a state by setting self.foo = ... etc., but it will cause bugs etc., so please use ctx as much as possible.

As an example of what to save in ctx

--Number of qubits --State vector in the middle of calculation --The input option of the run method and its parsing ――Other things that take time to make or have a state

And so on. (Be careful about not knowing during execution unless you leave the number of qubits in ctx)

Also, if you look at the code above, paying attention to ctx

--Create a ctx object with _preprocess_run --When calling the action for each gate, a ctx object is passed. Action is also expected to be implemented to return a ctx object --_postprocess_run receives ctx and returns the result of run

It has become a flow.

definition of action

Quantum circuits are made by arranging gates. A typical backend implementation method is to apply the gates in a row in sequence.

The operation of applying a gate is called action here. To implement action, just add a method,

def gate_{Gate name}(self, gate, ctx):
    #Something implemented
    return ctx

To do.

As a feature of Blueqat, there was a slice notation such as Circuit (). H [:], but there is also a method to separate the slice notation, and for idx in gate.target_iter (qubit number) If you do like) , you can get the index of 1 qubit gate. Also, the index of a 2-qubit gate such as CNOT should be for c, t in gate.control_target_iter (number of qubits). The number of qubits is required here in order to know how many qubits to apply when doing something like Circuit (). H [:].

Action that needs to be defined

Not all gates need to be implemented. For example, T gates and S gates can be made if there is a rotating Z gate (RZ), so on the Blueqat side, if it is not implemented, take care to use another gate instead. I have.

Also, if there is no alternative gate, it will be an error if it is used, but it will not be an error if it is not used, so you can create a backend that supports only some gates. (For example, if you implement only X gate, CX gate, and CCX gate, you can create a backend specialized for classical logic circuits. You can implement it with just bit operations, so you can create something that operates at high speed.)

Gates that need to be implemented may be sorted out in the future, but at present Measurements are X, Y, Z, H, CZ, CX, RX, RY, RZ, CCZ, U3. (Measurement defines action in the same way as gate)

See implementation example

A slightly unusual backend is QasmOutputBackend. This is not a simulator, but a backend for converting Blueqat's quantum circuits to OpenQ ASM.

ctx holds a list of OpenQASM rows and the number of qubits. Also, each action adds a row to the list.

Click here for the entire code.

class QasmOutputBackend(Backend):
    """Backend for OpenQASM output."""
    def _preprocess_run(self, gates, n_qubits, args, kwargs):
        def _parse_run_args(output_prologue=True, **_kwargs):
            return { 'output_prologue': output_prologue }

        args = _parse_run_args(*args, **kwargs)
        if args['output_prologue']:
            qasmlist = [
                "OPENQASM 2.0;",
                'include "qelib1.inc";',
                f"qreg q[{n_qubits}];",
                f"creg c[{n_qubits}];",
            ]
        else:
            qasmlist = []
        return gates, (qasmlist, n_qubits)

    def _postprocess_run(self, ctx):
        return "\n".join(ctx[0])

    def _one_qubit_gate_noargs(self, gate, ctx):
        for idx in gate.target_iter(ctx[1]):
            ctx[0].append(f"{gate.lowername} q[{idx}];")
        return ctx

    gate_x = _one_qubit_gate_noargs
    gate_y = _one_qubit_gate_noargs
    gate_z = _one_qubit_gate_noargs
    gate_h = _one_qubit_gate_noargs
    gate_t = _one_qubit_gate_noargs
    gate_s = _one_qubit_gate_noargs

    def _two_qubit_gate_noargs(self, gate, ctx):
        for control, target in gate.control_target_iter(ctx[1]):
            ctx[0].append(f"{gate.lowername} q[{control}],q[{target}];")
        return ctx

    gate_cz = _two_qubit_gate_noargs
    gate_cx = _two_qubit_gate_noargs
    gate_cy = _two_qubit_gate_noargs
    gate_ch = _two_qubit_gate_noargs
    gate_swap = _two_qubit_gate_noargs

    def _one_qubit_gate_args_theta(self, gate, ctx):
        for idx in gate.target_iter(ctx[1]):
            ctx[0].append(f"{gate.lowername}({gate.theta}) q[{idx}];")
        return ctx

    gate_rx = _one_qubit_gate_args_theta
    gate_ry = _one_qubit_gate_args_theta
    gate_rz = _one_qubit_gate_args_theta

    def gate_i(self, gate, ctx):
        for idx in gate.target_iter(ctx[1]):
            ctx[0].append(f"id q[{idx}];")
        return ctx

    def gate_u1(self, gate, ctx):
        for idx in gate.target_iter(ctx[1]):
            ctx[0].append(f"{gate.lowername}({gate.lambd}) q[{idx}];")
        return ctx

    def gate_u2(self, gate, ctx):
        for idx in gate.target_iter(ctx[1]):
            ctx[0].append(f"{gate.lowername}({gate.phi},{gate.lambd}) q[{idx}];")
        return ctx

    def gate_u3(self, gate, ctx):
        for idx in gate.target_iter(ctx[1]):
            ctx[0].append(f"{gate.lowername}({gate.theta},{gate.phi},{gate.lambd}) q[{idx}];")
        return ctx

    def gate_cu1(self, gate, ctx):
        for c, t in gate.control_target_iter(ctx[1]):
            ctx[0].append(f"{gate.lowername}({gate.lambd}) q[{c}],q[{t}];")
        return ctx

    def gate_cu2(self, gate, ctx):
        for c, t in gate.control_target_iter(ctx[1]):
            ctx[0].append(f"{gate.lowername}({gate.phi},{gate.lambd}) q[{c}],q[{t}];")
        return ctx

    def gate_cu3(self, gate, ctx):
        for c, t in gate.control_target_iter(ctx[1]):
            ctx[0].append(f"{gate.lowername}({gate.theta},{gate.phi},{gate.lambd}) q[{c}],q[{t}];")
        return ctx

    def _three_qubit_gate_noargs(self, gate, ctx):
        c0, c1, t = gate.targets
        ctx[0].append(f"{gate.lowername} q[{c0}],q[{c1}],q[{t}];")
        return ctx

    gate_ccx = _three_qubit_gate_noargs
    gate_cswap = _three_qubit_gate_noargs

    def gate_measure(self, gate, ctx):
        for idx in gate.target_iter(ctx[1]):
            ctx[0].append(f"measure q[{idx}] -> c[{idx}];")
        return ctx

See _preprocess_run

    def _preprocess_run(self, gates, n_qubits, args, kwargs):
        def _parse_run_args(output_prologue=True, **_kwargs):
            return { 'output_prologue': output_prologue }

        args = _parse_run_args(*args, **kwargs)
        if args['output_prologue']:
            qasmlist = [
                "OPENQASM 2.0;",
                'include "qelib1.inc";',
                f"qreg q[{n_qubits}];",
                f"creg c[{n_qubits}];",
            ]
        else:
            qasmlist = []
        return gates, (qasmlist, n_qubits)

The first thing we are doing is analyzing options.

Since it may be executed with the ʻoutput_prologue option like Circuit (). Run (backend ='qasm_output', output_prologue = False) `, the option is analyzed. This option is True by default, but if False is specified, it will be added to the beginning of OpenQ ASM.

OPENQASM 2.0;
include "qelib1.inc";
qreg q[Bit number];
creg c[Bit number];

Is omitted.

Next, ctx is a list of OpenQASM rows and a tuple of qubits.

It returns gates and ctx, but gates just returns what was passed as an argument. I decided to return gates with _preprocess_run because I wanted to operate the gate column for optimization etc., but if there is no particular need to do it, the received argument is returned as it is.

See action

    def _one_qubit_gate_noargs(self, gate, ctx):
        for idx in gate.target_iter(ctx[1]):
            ctx[0].append(f"{gate.lowername} q[{idx}];")
        return ctx

    gate_x = _one_qubit_gate_noargs
    gate_y = _one_qubit_gate_noargs
    gate_z = _one_qubit_gate_noargs

    #Omitted because there are many

    def gate_cu3(self, gate, ctx):
        for c, t in gate.control_target_iter(ctx[1]):
            ctx[0].append(f"{gate.lowername}({gate.theta},{gate.phi},{gate.lambd}) q[{c}],q[{t}];")
        return ctx

I will do it like this. Since it is troublesome to make x, y, z gates one by one, I wear them sideways. If you can't wear it sideways, implement it carefully like gate_cu3. What we are doing is that ctx was([list of rows], number of quantum bits), so add a new row to the list of rows withctx [0] .append (...) I'm just doing it.

See _postprocess_run

    def _postprocess_run(self, ctx):
        return "\n".join(ctx[0])

It simply returns a list of lines as a line break delimited string. This result is the result of run.

Summary

Last time, we looked at the backend implementation method for processing systems that can read OpenQASM, but this time, we looked at the more general-purpose backend implementation method.

At Blueqat, we aim to take care of as much as possible on the library side and allow developers and users to do what they want, and it is also possible to easily create a backend by riding on the mechanism provided by Blueqat. You can, and you can even implement the full run method without getting on board.

Anyone can create the backend itself, and you can even use your own simulator with the Blueqat interface. Everyone, please try to implement the backend.

Recommended Posts

Make a Blueqat backend ~ Part 1
Make a Blueqat backend ~ Part 2
Let's make a Backend plugin for Errbot
Make a squash game
Make a function decorator
Make a distance matrix
I'll make a password!
Make a Nyan button
Make a Tetris-style game!
Make a Base64 decoder
How to make a shooting game with toio (Part 1)
Let's make a Discord Bot.
Make a tky2jgd plugin with no practicality in QGIS Part 2
How to make a hacking lab-Kali Linux (2020.1) VirtualBox 64-bit Part 2-
Make a tky2jgd plugin with no practicality in QGIS Part 1
[Django] Make a pull-down menu
Make a LINE BOT (chat)
Let's make Splatoon AI! part.1
Make a bookmarklet in Python
Make a fortune with Python
Make Responder a daemon (service)
Let's make a rock-paper-scissors game
Make a fire with kdeplot
Make a math drill print
Let's make a WEB application for phone book with flask Part 1
Make a cat detector with Google Colabratory (Part 2) [Python] ~ Use OpenCV ~
Let's make a WEB application for phone book with flask Part 2
Make a thermometer with Raspberry Pi and make it viewable with a browser Part 4
How to make a unit test Part.1 Design pattern for introduction
Let's make a WEB application for phone book with flask Part 3
Let's make a WEB application for phone book with flask Part 4
Let's make a remote rumba [Hardware]
How to make a Japanese-English translation
Make a Santa classifier from a Santa image
Play with a turtle with turtle graphics (Part 1)
Let's make a remote rumba [Software]
Make a Tweet box for Pepper
Let's make a GUI with python.
Make a sound with Jupyter notebook
Let's make a spot sale service 2
Make a face recognizer using TensorFlow
How to make a slack bot
Let's make a breakout with wxPython
Let's make a spot sale service 1
How to make a crawler --Advanced
How to make a recursive function
Make C compilation a little easier
python / Make a dict from a list.
[Python] Make the function a lambda function
Make a recommender system with python
How to make a deadman's switch
[Blender] How to make a Blender plugin
Make Flask a Cloud Native application
Make a filter with a django template
[LINE bot] I'm a ranger! Part 2
Let's make a graph with python! !!
Let's make a supercomputer with xCAT
How to make a crawler --Basic
Make a model iterator with PySide
Make a nice graph with plotly
Make a curtain generator in Blender