Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework assembler to allow third-party providers to integrate their own instructions #2162

Closed
chriseclectic opened this issue Apr 19, 2019 · 3 comments · Fixed by #2173
Closed
Labels
type: enhancement It's working, but needs polishing

Comments

@chriseclectic
Copy link
Member

What is the expected enhancement?

This is an idea for reworking the assemble_circuits and circuit.append functions to make it easy for third-party providers to define and implement custom instructions without having to modify any terra source code.

The idea requires two pieces:

Modify the base Instruction class to have an assemble method that returns the QasmQobjInstruction representation of the instruction for the assembler. The base class should include a default implementation of this that can be overriden by derived classes. Eg something like

class Instruction:
def assemble():
    return QasmQobjInstruction(<defaults>)

Then the assembler would just call this method for all instructions in a circuit (this might need to take arguments to pass in qubit positions or something from the qobj). This offloads having to define special cases in the assembler (like we currently do for unitary, snapshots, and conditionals), and would let these instructions define their own method of assembly.

The second part would be to add a to_instruction method to an object that must return an Instruction subclass object that allows it to be inserted into a circuit, and if you also define a custom instruction subclass you can override its assemble method to define how it is serialized. To make this useful you would need to change the circuit methods that take instructions to check if the input is an instruction or has to_instruction attribute:

class QuantumCircuit:
    # ...
   def _check_instruction(self, instruction):
        if issubclass(instruction.__class__, Instruction):
            return instruction
        if hasattr(instruction, 'to_instruction'):
            return instruction.to_instruction()
        raise QiskitError("Input is not a valid instruction.""")

   def append(self, instruction, qargs=None, cargs=None):
        instruction = self._check_instruction(instruction)
      # rest the same...

and then to define a custom objct that can be converted into a valid circuit instruction something like:

class MyClassInstruction(Instruction):
def assemble():
    """Return custom qobj instruction for my gate"""
    return QasmQobjInstruction(<kwargs>)

class MyClass:
    def to_instruction():
         return MyClassInstruction(args)

This would be very useful for defining instructions such as arbitrary unitaries, snapshots, kraus errors etc for Aer.

@taalexander
Copy link
Contributor

FYI see how Naoki went about solving this problem for pulse in #2115, specifically in the qobj.converters.pulse_instruction module.

@nkanazawa1989
Copy link
Contributor

I hope this could help:

from qiskit.qobj.converters.pulse_instruction import PulseQobjConverter, bind_instruction
from qiskit.pulse.commands import PulseCommand
from qiskit.pulse.commands.instruction import Instruction
from qiskit.pulse.common.timeslots import Interval, Timeslot, TimeslotCollection
from qiskit.qobj import PulseQobjInstruction

class CustomCommand(PulseCommand):
    """Custom command."""

    def __init__(self, my_parameter):
        super().__init__(duration=0)
        self.my_parameter = my_parameter

    def __call__(self, channel):
        return CustomInstruction(self, channel)

class CustomInstruction(Instruction):
    """Custom instruction."""

    def __init__(self, command, channel, start_time=0):
        slots = [Timeslot(Interval(start_time, start_time), channel)]
        super().__init__(command, start_time, TimeslotCollection(slots))
        self._channel = channel

    @property
    def command(self):
        return self._command

    @property
    def channel(self):
        return self._channel

class CustomConverter(PulseQobjConverter):
    @bind_instruction(CustomInstruction)
    def _convert_custom_command(self, instruction):
        command_dict = {
            'name': 'custom_command',
            't0': instruction.start_time,
            'extra_param': instruction.command.my_parameter
        }
        return self._qobj_model(**command_dict)


_command = CustomCommand(my_parameter='test_parameter')
_instruction = _command(device.q[0].drive) # need to define DeviceSpecification

custom_converter = CustomConverter(PulseQobjInstruction)
qobj_custom = custom_converter(_instruction)

To define some custom commands, you need to define PulseCommand and its Instruction to handle it in the pulse schedule. Then you can define custom converter (PulseQobjConverter) to create qobj. This object is passed to the assembler.

@1ucian0
Copy link
Member

1ucian0 commented May 8, 2019

#2173 "partially closes" this issue. What else is missing?

@1ucian0 1ucian0 reopened this May 8, 2019
@1ucian0 1ucian0 added the type: enhancement It's working, but needs polishing label Dec 20, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: enhancement It's working, but needs polishing
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants