Make your own plugin

In our framework, a Plugin class should inherit from qat.plugins.AbstractPlugin which defined the following methods:

  • Method compile() required, this method takes a Batch and a HardwareSpecs and returns a Batch. This compile method (way in) will pre-process any batch before executing it on the QPU

  • Method post_process() optional, this method takes a BatchResult and returns either a BatchResult or a Batch. This post_process method (way out) will post-process the result returned by the QPU. If this method returns a Batch, this new batch will be resubmitted to the QPU, otherwise the new result is returned to the user

Raising exception in the code

A Plugin can raise a PluginException. A plugin can be accessed remotely (using Qaptiva Access or by starting the Plugin in server mode). This exception can be serialized and re-raised on the client side

A PluginException can be raised using assert_plugin()

from qat.core.assertion import assert_plugin

# If my_condition() returns False: raises a PluginException
# If my_condition() returns True: do nothing
assert_plugin(my_condition(), "Error message")

Method compile

Method compile() is the only required method. This function takes two parameters:

  • An argument of type Batch defining what should be executed by the QPU

  • An argument of type HardwareSpecs giving the capabilities of the QPU. This argument is given by the get_specs() method of the QPU. This object contains some information on the QPU and can be used to adapt the compilation stage

This function should return a Batch that will be executed by the rest of the stack

from qat.core.plugins import AbstractPlugin

class MyPlugin(AbstractPlugin):
    def compile(self, batch, hardware_specs):
        # Do something with the batch of jobs `batch`, and, optionally, the specs
        return batch

Method post_process

Method post_process() is an optional method. This function takes a BatchResult (i.e. a list of Result) and returns either a:

  • A BatchResult: the result is then returned to the user (and post-processed by plugins placed before in the stack, if any)

  • A Batch: the result is resubmitted to the QPU (and pre-processed by plugins placed between the current plugin and the QPU)

This function is designed to repair the result if needed. For instance, the compile() method can return an equivalent batch (which is not equal), the result can then be repaired using post_process() to ensure that the result returned to the user correspond to expected result of the initial not compiled batch

from qat.core.plugins import AbstractPlugin

class MyPlugin(AbstractPlugin):
    def compile(self, batch, hardware_specs):
        # Do something with the batch of jobs `batch`, and, optionally, the specs
        return batch

    def post_process(self, batch_result):
        # Do something with the execution result
        return batch_result

Note

Any plugin has a do_post_processing() method. This method is used to check if the plugin must repair the result before returning it to the user (this function does not take any parameter and returns a boolean).

If a delocalized plugin (i.e. distant plugin) is used in a stack, this function will be used to check if the remote plugin needs to do post-processing. If the remote plugin does not need to do post-processing, the result is not send to this plugin, saving a lot of time

A concrete example

This section provides an example of plugin displaying debug information to the screen. This plugin will:

  • print all circuits submitted to the QPU. This function will loop of over the jobs composing the batch and print the circuit for each job

  • print the result of the computation. This function will loop over the results composing the batch-result and print the samples for each result

The plugin will look like:

from qat.core.plugins import AbstractPlugin

class MyPlugin(AbstractPlugin):
    def compile(self, batch, hardware_specs):
        # For each job
        for index, job in enumerate(batch):
            print(f">> Job #{index}")

            # Print the circuit (i.e. printing each gate)
            for op in job.circuit.iterate_simple():
                print(op)

        # Return compiled batch
        return batch

    def post_process(self, batch_result):
        # For each result
        for index, result in enumerate(batch_result):
            print(f">> Result #{index} - result of size {len(result)}")

            # Print the result (i.e. print each sample)
            for sample in result:
                print(sample.state, sample.probability)

        return batch_result

This plugin can be used with any QPU, by using the pipe operator. The following example creates a QPU using get_default_qpu() as execution engine but pre-processing and post-processing jobs using our plugin

from qat.qpus import get_default_qpu

my_qpu = MyPlugin() | get_default_qpu()

This QPU can be used to execute a GHZ circuit

from qat.lang import qrout, H, CNOT

@qrout
def ghz():
    H(0)
    CNOT(0, 1)
    CNOT(1, 2)

all_results = my_qpu.submit([ghz.to_job()] * 3)

print("\n===== Final result =====")

for result in all_results:
    print(result)
>> Job #0
('H', [], [0])
('CNOT', [], [0, 1])
('CNOT', [], [1, 2])
>> Job #1
('H', [], [0])
('CNOT', [], [0, 1])
('CNOT', [], [1, 2])
>> Job #2
('H', [], [0])
('CNOT', [], [0, 1])
('CNOT', [], [1, 2])
>> Result #0 - result of size 2
|000> 0.4999999999999999
|111> 0.4999999999999999
>> Result #1 - result of size 2
|000> 0.4999999999999999
|111> 0.4999999999999999
>> Result #2 - result of size 2
|000> 0.4999999999999999
|111> 0.4999999999999999

===== Final result =====
Result(raw_data=None, _value=None, error=None, value_data=None, error_data=None, meta_data={'simulation_time': '0.000321'}, in_memory=True, data=ResData(mem_ptr=94876961580992, data_type=1, data_size=8, _serialized=None, qregs=[QRegister(scope=<qat.lang.AQASM.program.Program object at 0x1499e4395c70>, length=3, start=0, msb=None, _subtype_metadata=None, qbits=[<qat.lang.AQASM.bits.Qbit object at 0x1499e43ea7c0>, <qat.lang.AQASM.bits.Qbit object at 0x1499e43ea790>, <qat.lang.AQASM.bits.Qbit object at 0x1499e43ea5e0>])]), qregs=[QRegister(scope=<qat.lang.AQASM.program.Program object at 0x1499e4395c70>, length=3, start=0, msb=None, _subtype_metadata=None, qbits=[<qat.lang.AQASM.bits.Qbit object at 0x1499e43ea7c0>, <qat.lang.AQASM.bits.Qbit object at 0x1499e43ea790>, <qat.lang.AQASM.bits.Qbit object at 0x1499e43ea5e0>])], _parameter_map=None, _values=None, values_data=None, need_flip=False, nbqbits=None, lsb_first=False, has_statevector=True, statevector=array([0.70710678+0.j, 0.        +0.j, 0.        +0.j, 0.        +0.j,
       0.        +0.j, 0.        +0.j, 0.        +0.j, 0.70710678+0.j]))
Result(raw_data=None, _value=None, error=None, value_data=None, error_data=None, meta_data={'simulation_time': '0.000120'}, in_memory=True, data=ResData(mem_ptr=94876953801328, data_type=1, data_size=8, _serialized=None, qregs=[QRegister(scope=<qat.lang.AQASM.program.Program object at 0x1499e4395c70>, length=3, start=0, msb=None, _subtype_metadata=None, qbits=[<qat.lang.AQASM.bits.Qbit object at 0x1499e43ea7c0>, <qat.lang.AQASM.bits.Qbit object at 0x1499e43ea790>, <qat.lang.AQASM.bits.Qbit object at 0x1499e43ea5e0>])]), qregs=[QRegister(scope=<qat.lang.AQASM.program.Program object at 0x1499e4395c70>, length=3, start=0, msb=None, _subtype_metadata=None, qbits=[<qat.lang.AQASM.bits.Qbit object at 0x1499e43ea7c0>, <qat.lang.AQASM.bits.Qbit object at 0x1499e43ea790>, <qat.lang.AQASM.bits.Qbit object at 0x1499e43ea5e0>])], _parameter_map=None, _values=None, values_data=None, need_flip=False, nbqbits=None, lsb_first=False, has_statevector=True, statevector=array([0.70710678+0.j, 0.        +0.j, 0.        +0.j, 0.        +0.j,
       0.        +0.j, 0.        +0.j, 0.        +0.j, 0.70710678+0.j]))
Result(raw_data=None, _value=None, error=None, value_data=None, error_data=None, meta_data={'simulation_time': '0.000285'}, in_memory=True, data=ResData(mem_ptr=94876966430976, data_type=1, data_size=8, _serialized=None, qregs=[QRegister(scope=<qat.lang.AQASM.program.Program object at 0x1499e4395c70>, length=3, start=0, msb=None, _subtype_metadata=None, qbits=[<qat.lang.AQASM.bits.Qbit object at 0x1499e43ea7c0>, <qat.lang.AQASM.bits.Qbit object at 0x1499e43ea790>, <qat.lang.AQASM.bits.Qbit object at 0x1499e43ea5e0>])]), qregs=[QRegister(scope=<qat.lang.AQASM.program.Program object at 0x1499e4395c70>, length=3, start=0, msb=None, _subtype_metadata=None, qbits=[<qat.lang.AQASM.bits.Qbit object at 0x1499e43ea7c0>, <qat.lang.AQASM.bits.Qbit object at 0x1499e43ea790>, <qat.lang.AQASM.bits.Qbit object at 0x1499e43ea5e0>])], _parameter_map=None, _values=None, values_data=None, need_flip=False, nbqbits=None, lsb_first=False, has_statevector=True, statevector=array([0.70710678+0.j, 0.        +0.j, 0.        +0.j, 0.        +0.j,
       0.        +0.j, 0.        +0.j, 0.        +0.j, 0.70710678+0.j]))