qiboteam / qibolab

Quantum hardware module and drivers for Qibo.
https://qibo.science
Apache License 2.0
40 stars 10 forks source link

Refactoring abstract platform API #249

Closed scarrazza closed 1 year ago

scarrazza commented 1 year ago

The following steps are needed in order to upgrade the platform API:

stavros11 commented 1 year ago

Is there a plan about task assignment (who is doing what) and an expected timeline? I can make proposal based on the document and a Qubit object but I am open if others would like to contribute.

Soon I would like to test calibrations using the QM platform and I guess they are already doing so for ZHInst and RFSoC. Now everyone is writing his own calibration methods branching out from qibolab (for example here). This is not elegant and will duplicate work as these branches will be removed eventually if we aim for platform agnostic qibocal. I believe it makes more sense to prioritize the abstraction.

scarrazza commented 1 year ago

I agree and suggest the following:

andrea-pasquale commented 1 year ago

Sounds good to me @scarrazza. It should be pretty straightforward to modify qibocal once the AbstractPlatform is more or less ready.

Jacfomg commented 1 year ago

Is there a plan about task assignment (who is doing what) and an expected timeline? I can make proposal based on the document and a Qubit object but I am open if others would like to contribute.

Soon I would like to test calibrations using the QM platform and I guess they are already doing so for ZHInst and RFSoC. Now everyone is writing his own calibration methods branching out from qibolab (for example here). This is not elegant and will duplicate work as these branches will be removed eventually if we aim for platform agnostic qibocal. I believe it makes more sense to prioritize the abstraction.

In my case I needed to find a way of making use of Zurich capabilities and the one I found was generating a great number of pulses and send them all at once to so its gets compiled and acts exactly like a sweeper but with greater flexibility. I don't know about the other platforms or if I should start getting these routines in files so QM abd FPGA can see if they also work for them or look for other approach.

scarrazza commented 1 year ago

Let me try to summarize a discussing we had this afternoon with @andrea-pasquale and @Edoardo-Pedicillo about this topic.

We assume there are a 2 steps refactoring: a minor refactoring for qibocal and qibo, involving the AbstractPlatform and a major refactoring involving instruments. However, I believe we should agree and discuss about that now, and maybe go for the major refactoring directly.

Refactor 1

The first requirement is to cleanup AbstractPlatform by removing instrument specifics and runcard parsing. We should create separate inheritance for each platform specialization, e.g.:

image

The AbstractPlatform must contain the minimalist set of setters and getters (as suggested by @stavros11) required by qibocal and qibo, everything else goes inside the platform specialization. The aim is to avoid a strong dependency between changes in qibolab and dependent projects.

Refactor 2

The aim here is to fix the major limitations of the design above, in particular:

The following 3 steps should provide a practical recipe to solve those issues:

  1. We propose to define a single Platform class containing the setters and getters discussed above, plus the mechanism to read and write runcards containing only the minimal set of information for calibration. Furthermore, this class should allocate and modify qubits, through Qubit objects. This class must take as input the runcard and an InstrumentDesign (see below).

image

  1. We encapsulate each instrument in a Instrument interface class. This interface should expose getters and setters for initialization, execution of pulse sequences and termination.

  2. Platforms should operate with a new object, called InstrumentsDesign which takes care of orchestrating one or more instruments in order to execute a pulse sequence. This class and/or its inheritance must provide the specific steps required to operate the experiment, e.g. a design based on a single instrument should send a pulse sequence to the proper instrument object, while and multi-instrument layout (qblox+lo) should performa all steps required to operate the platform.

This will make the Platform instrument independent, so the new Platform allocator in platform.py must be constructed as follows:

# single instrument setup
instrument = QBloxInstrument()
design = SingleInstrumentDesign(instrument)
platform = Platform(design, runcard)

# multi instrument setup
design = QBloxRSDesign()
platform = Platform(design, runcard)

This layout considers something simple as:

class Platform:
   def __init__(self, design, runcard):
      self.instrument_design = design
      params = load_runcard(runcard)
      self.qubits = [ Qubit(p, ...) for p in range(params.nqubits)]

   def execute_pulse_sequence(self, sequence):
      self.instrument_design.play(sequence)

class SingleInstrumentDesign(InstrumentsDesign):
  def play(self, sequence):
     return self.instrument.play(sequence)

class QBloxDesign(InstrumentsDesign):
  def play(self, sequence):
     # perform custom operations with instruments
     self.instrument[0].connect()      
     ...
     return self.instrument[2].play(sequence)

If this works, then any developer can operate with qibolab without making a new release by:

This is just a draft idea, let me know your opinion.

alecandido commented 1 year ago

I want to clarify one point, especially to address some concerns that recently arose.

There is always a trade-off between flexibility and generality, that here we are touching critically. This trade-off manifests in the first place at the level of Instruments, since there are many parallel instruments that are capable to perform a similar task, but they are not the exact same. One simple advantage of this proposal is to isolate the two sides of the trade-off, by decoupling different parts. In Refactor 2, the Platform will be fully general, since the overall orchestration has not to depend on the details of the setup, still you need the flexibility to be somewhere, and this is the role of the InstrumentDesign.

So, there will be a place (Platform) where as many operations as possible will be implemented, but only as long as they are fully general, while flexibility is kept in the user-provided component. Nevertheless, even in the flexible layer (InstrumentDesign) you do not want to leave the user alone, so as many primitives as possible will be implemented in the Instrument layer, and they will be available as a library to the user, to compose their own design: with ease, in case no special tuning is need, and with control, if the peculiar features of the specific instruments are required. Moreover, we are not bound to have a single abstract specification for instruments: we can also provide more specialized abstractions (e.g. we could provide a Reader and Controller subclasses), or factorize some code to be common to all the instruments that implement a certain features (e.g. using mix-ins).

It is true that at each level you will have to choose if you want to be flexible or general, but having different parts responsible for the two options you can get both. Moreover, if the user should be able access the full power of the components, a library layout is more successful than an application one. If you had to cook N fully fledged applications, you would end up working for each of them, with users that have to ask every time they need something. While with a library, the user can exploit programmatically all the primitives implemented, following existing examples to implement their own application, and getting in touch with the library development only if they need more primitives, or asking for help with examples. This would also simplify the lifecycle of the package.

Think about a web framework: you get the components and some patterns, but in the end your website is not implemented inside the framework, you are creating it by yourself. And Qibo is a framework.

I'm not advocating not to engage with the users, but they should be free to consume qibolab on their own, if they are able to. Like you would not be happy to have to interact with Python development/developers every time you need to implement a new program.

I apologize in advance if I some of the details are not completely accurate, I hope the main concept to be clear anyhow.

stavros11 commented 1 year ago

Thank you @scarrazza and @AleCandido for the summary and useful remarks. I fully agree with the two refactoring steps. For me the first is mandatory, otherwise qibocal will not be able to operate in a clean way with all the different instruments. The second is not absolutely required for things to work but it could improve qibolab code a lot. I also like the framework idea and making extensions (new instruments) possible without having to release. I am not sure how easy it will be to do both refactorings together but we can give a try.

Now in practice, I am trying to make the QM driver follow this picture. The main challenge is to decide what belongs to the Platform and what in the InstrumentDesign and how exactly the two communicate with each other. Clearly platform has a reference to the design but what does the design know about the platform? I guess it will require some access to the qubit objects in order to play pulses. We can take as an example the latest qw5q_gold runcard, which has the following main sections:

  1. nqubits
  2. settings
  3. topology
  4. qubit_channel_map
  5. instruments
  6. native_gates
  7. characterization

From these, nqubits, topology, native_gates, characterization clearly belong to the Platform as they provide physics information about the qubits, while settings and instruments clearly belong to the InstrumentDesign as they are specific to the insturment setup. qubit_channel_map is somewhere in between but code-wise it may be more convenient to be in the Platform. More specifically the design I have in mind is the following:

class Platform:
   def __init__(self, design, runcard):
      self.instrument_design = design
      params = load_runcard(runcard)
      self.qubits = [ Qubit(p, ...) for p in range(params.nqubits)]

class Qubit:
    """Representation of a physical qubit.""""
   def __init__(self, name, characterization, readout, feedback, drive, flux=None):
        # name is usually the int index but we could also accept other types, eg str
        self.name = name
        # characterization parameters (frequencies, T1, T2, etc. loaded from the runcard)
        self.characterization = SimpleNamespace(**characterization)
        # Channel objects that interact with this qubit
        self.drive = drive
        self.readout = readout
        self.feedback = feedback
        self.flux = flux

class Channel:
    """Representation of physical wire connection (channel)."""
    qubits = [] # list of Qubit objects connected to this channel
    instruments/ports = [] # references to instruments connected to this channel
    # this list may be populated by the `InstrumentDesign`?

I think this closely resembles the real situation in the lab where qubits are connected via cables (channels) to the instruments. Platform and Qubit will be fully exposed to qibocal so the interface should be fixed. Parts of Channel may also be exposed, for example if one wants to set something like platform.qubits[0].readout.gain = 10 however I am not sure if this is required as I suspect that it is possible to do all calibrations by changing the pulses only and not touching the qubit and channel parameters.

The main questions are:

scarrazza commented 1 year ago

Thanks @stavros11 for the suggestion and prototypes.

Here Channel acts as the middle object between Platform and instruments and is modified by both. Do you like this idea?

Yes, a Channel object is interesting and follows Alessandro's comment. If we are motivated, a practical implementation layout could look similar to keras (or qibo Circuit) where a Platform object provide the add method for Qubits, Channels(Qubits) and Design.

On the other hand, I still believe that InstrumentsDesign provides the practical recipe to operate with instruments, therefore the platform is responsible for sending qubits/channels/pulses to the design.

How do you imagine the simplified runcard?

Yes, absolutely, only physics with variable parameters for qubits and channels. I am not particularly worried about hardcoding instrument specifics, such as IP, etc. We should provide predefined platform allocators in platform.py but at the same time allow users to build a new custom platform with his own qubits, channels, instrumentdesign and instruments.

alecandido commented 1 year ago

@stavros11 if you start having a set of entities and connections, this structure looks like a graph.

I do not want to force a graph anywhere, and it is not trivial which are entities and which are connections. E.g. a channel would look like a connection, but not necessarily it is, since a connection is only related to two entities. But then the channel might be a node, and qubits and instruments can be connected to the channel through edges.

This would partially address the problem of references: the graph object (that can be the Design or whatever) holds references to all the objects, and the list of connections between them. The individual objects, might be queried from the outside, and their outputs propagated from the graph. My goal is to simplify the flow, because when you are making objects communicating directly and holding references, you might end up in very deep stacks of calls and possible loops, that might be better detected and visualized when there is a central object managing the flow. For sure, this will have some other drawbacks, we should try to understand what we are interested in for this use case.

In case, Python is not missing graphs libraries, that might also help for actual visualization (i.e. plotting). One renown example is NetworkX. Here a brief tutorial.