Concepts in SilQ

This page describes the main concept in SilQ. The first section describes the Main classes that build up the layers of abstraction. The second section describes how all these classes interact with one another when Targeting a pulse sequence to a specific experimental setup. The final section describes the AcquisitionParameter, which is another main class, and the final layer of abstraction, and whose description requires knowledge of how pulse sequence targeting works.

Main classes

The classes described here are ordered by how they control one another. In general, classes later on control the classes described earlier. Every class described here is a QCoDeS ParameterNode, and their properties are Parameters, and so they benefit from all the features provided by these. See Parameter guide and ParameterNode guide for more information.

InstrumentInterface

Each instrument has a corresponding QCoDeS Instrument driver that facilitates communication with the user via Python. These drivers usually directly copy the commands described in the instrument manual, and occasionally add some features. As a result, each Instrument is controlled slightly differently. On top of this, different instruments that are meant to perform similar tasks, such as arbitrary waveform generators (AWGs), can have a completely different way of controlling.

To be able to start with a setup-independent pulse sequence, an interface is needed that can convert generic instructions, such as outputting a pulse, to instructions specific for the particular instrument. This is exactly what the InstrumentInterface does. When a pulse sequence is being setup (targeted), the InstrumentInterface receives a list of pulses that it should output when the experiment starts. This will usually be a subset of the whole pulse sequence, plus potentially ancillary pulses. The InstrumentInterface then converts these pulses into specific instrument instructions and sets up the instrument. An InstrumentInterface has a list of pulses that it can program, defined in its attribute pulse_implementations, and if it receives any pulse that is not defined here, it will raise an error.

Furthermore, the InstrumentInterface may request additional pulses, such as triggering pulses or modulation pulses. These get sent back to the Layout (described next), which will then direct those to the appropriate InstrumentInterface.

Note

While an InstrumentInterface is supposed to set all the parameters of the Instrument relevant to outputting a pulse, often there are simply too many parameters, and some are not included. However, these are usually parameters that are rarely modified. Additionally, the InstrumentInterface itself has parameters that can be set by the user, and will influence how it programs the Instrument.

Connection

In an experimental setup, instruments are physically connected to one another by cables. This physical connection is represented by the Connection, which has an input and output instrument and channel. It can additionally have flags, such as being a trigger connection, or having a scale (attenuation). Connections can also be combined into a CombinedConnection, which can be useful when you want a single pulse to be sent to multiple connections.

It is convenient to identify a connection by a label. This way, a pulse can be passed the same connection_label to ensure it is passed to that specific connection. Surprisingly, this helps keeping the pulse sequence setup-independent. For example, a pulse having the connection_label output can be directed to completely different connections in different setups, as the Connection having label output can differ.

Layout

The Layout is at the heart of the experimental setup. Its basis is being a layout of all the instruments and the connectivity between them. A PulseSequence is passed onto the Layout, which will then use its knowledge of the experimental setup to direct each of the pulses to the appropriate InstrumentInterface. If an InstrumentInterface requests additional pulses, the Layout can find the appropriate InstrumentInterface using its knowledge of the connectivity between instruments. The Layout also communicates with the data acquisition instrument (via its interface) to perform data acquisition.

Note

The Layout never directly communicates with an Instrument, but always via the corresponding InstrumentInterface.

Pulse

A Pulse is a representation of a physical pulse sent in an experiment. There are many different Pulse subclasses, common ones are DCPulse, SinePulse, TriggerPulse. These pulses usually have several attributes, such as a name, amplitude, duration, and frequency. In an experiment, a pulse is always attached to a particular connection (e.g. an AWG outputting a pulse from one of its channels to the input of a gate on your device sample). This is reflected in the Pulse, which is linked to a specific Connection. This means that when the pulse is targeted by the Layout, the Connection’s output InstrumentInterface will be instructed to program the Pulse to that particular connection. A Pulse also has ancillary properties, such as acquire, which signals the Layout that the signal during this pulse should be acquired by the data acquisition instrument.

Often, pulses with a specific name are reused, either in a PulseSequence, or in different PulseSequences. Instead of having to specify all the Pulse properties every time, properties belonging to a Pulse with a specific name can be stored in the config. This way, any time a new Pulse with that name is created, it will use those properties by default.

For more information on the Pulse, see Pulses and PulseSequences.

PulseSequence

An experiment usually consists of a sequence of pulses being output by different instruments at precise timings. In SilQ, this is represented by the PulseSequence, which contains Pulses that have specific start times, durations, and Connections. A PulseSequence can be passed onto the Layout, which then targets the PulseSequence to the particular experimental setup by passing its Pulses along to the InstrumentInterfaces, which then set up their instruments.

If the properties of the InstrumentInterfaces and Layout have been configured, passing a PulseSequence to the Layout is sufficient to execute the pulse sequence, and obtain the resulting traces from the data acquisition interface.

For more information on the PulseSequence, see Pulses and PulseSequences.

Note

Incorporating feedback routines into the pulse sequence is one of the future goals.

Targeting a pulse sequence

There are several steps happening when a PulseSequence is targeted by the Layout to a specific experimental setup. To understand the processes that happen behind the scenes, the most important piece of information is knowing which classes interact with each other, and if it’s a one-way interaction or two-way interaction. Below is a figure containing a very simple experimental setup (top), and the corresponding representation in SilQ.

Alternative text

The experiment shown above is a simplified version of a typical experimental setup. It only contains three instruments, and for simplicity we ignore any sample being experimented on. A trigger instrument (left) handles the timing of the system by sending periodic triggers to the other instruments to indicate an event. The waveform generator (middle) can output waveforms (pulses). It receives triggers from the trigger instrument to indicate that it should output the next pulse. Alternatively, a trigger can indicate that it should output the entire pulse sequence and wait for the next trigger (this is usually the case for experiments requiring nanoscale precision). The waveform generator emits the pulses to the acquisition card, which is programmed to record a fixed-duration digitized signal when it receives a trigger from the trigger instrument. By programming the instruments correctly, the acquisition card can be setup to record specific pulses from the waveform generator.

Even such a simple measurement as the one described above requires many commands to be sent to the different instruments. In SilQ, this is handled by the Layout targeting a PulseSequence to the particular experimental setup. The bottom of the figure shows how the different SilQ objects interact with one another when targeting a PulseSequence. The arrows indicate the direction of communication, a round dot indicates being a property of the class the line originates from. Blue lines indicate a Connection between the InstrumentInterfaces (there is also a connection between the left-most and right-most interface).

Targeting a PulseSequence is actually a two-stage process. However, stage zero is having preprogrammed all the Instruments, InstrumentInterfaces, and Layout. This does not mean manually sending all the commands to output the pulse sequence, but specifying the parameters that are freely configurable, such as the``sample rate``.

Stage 1 - Pulse distribution

The first stage is invoked by setting the Layout PulseSequence:

>>> layout.pulse_sequence = pulse_sequence

In the first step, no instruments are actually configured, but instead the Layout passes the Pulses around to the different InstrumentInterfaces. These then verify that they can program their instrument to output the pulse, and optionally request ancillary pulses from the Layout (such as trigger pulses). If any InstrumentInterface is not able to program its instrument to output all the required pulses, an error is raised.

If the first does not raise any errors, then each of the InstrumentInterfaces will have its InstrumentInterface.pulse_sequence filled with the pulses it should output. Additionally, InstrumentInterface.input_pulse_sequence contains a list of pulses that it receives. All Pulses in the PulseSequence that have Pulse.acquire = True are passed onto the acquisition InstrumentInterface.input_pulse_sequence. This is a good moment to see if the InstrumentInterfaces have pulse sequences that actually make sense.

Note

When Layout.pulse_sequence is set to a new PulseSequence, a copy of the PulseSequence can be stored on the computer as a python pickle with a timestamp. This can be useful as a logging feature, as the timestamp allows you to see what PulseSequence was targeted at a given time. See Storing PulseSequences for more information.

Stage 2 - Instrument setup

The second stage consists of programming the Instruments. This is invoked by calling

>>> layout.setup()

At this point the Layout signals all the InstrumentInterfaces to program their Instruments. Each InstrumentInterface will convert its PulseSequence into Instrument commands, and execute them. At this stage, errors may also be raised. This is often the case when an instrument command cannot be executed by the instrument.

Running a pulse sequence

Once the Layout has successfully targeted a PulseSequence, the pulse sequence can be executed on the experimental setup. This generally happens in three steps.

Step 1 - Starting instruments

The first step consists of starting the instruments, and is called by

>>> layout.start()

The order of starting Instruments is based on their hierarchy: instruments that need to be triggered are started before the instrument that performs the triggering. At the top of the chain is the primary_instrument (in this case the triggering instrument), which is started last. This ensures that all other instruments are awaiting a trigger and thus are synchronized. When the primary_instrument is started, the pulse sequence is being output by the instruments.

Note

If the PulseSequence of any InstrumentInterface is empty, i.e. it does not need to output pulses, it won’t be started.

Step 2 - Acquiring data

Once the pulse sequence is running, the acquisition instrument, specified by layout.acquisition_interface, can be used to acquire a signal. Data acquisition can be performed by calling

>>> layout.acquisition()

At this point the acquisition instrument will acquire traces and pass them onto its InstrumentInterface. The InstrumentInterface will then segment the traces for each of the pulses. This way, each pulse in its input_pulse_sequence (which all have Pulse.acquire = True) has its corresponding measured traces. At this point, optional averaging of the traces, specified by Pulse.average, is also performed.

When traces are acquired, more than one channel can be measured. These channels are specified in Layout.acquisition_channels, and each channel is given a label. This allows the different acquisition channels to have meaningful labels (e.g. chip output) instead of channel indices (e.g. channel_A). The Layout attaches these labels once it receives the processed traces from the acquisition InstrumentInterface.

Note

  • The number of traces is specified by Layout.samples.

  • Layout.start() is called if the instruments have not yet been started.

Step 3 - Stopping instruments

The final step is to stop the instruments after the acquisition is finished, and can be called by

>>> layout.stop()

This will stop the instruments according to the same hierarchy used when starting the instruments. This step actually happens by default at the end of an acquisition (step 2). However, since there is overhead involved in stopping/starting instruments, it can be ignored by calling layout.acquisition(stop=False).

Note

layout.stop() is a useful command if you want to be absolutely sure that all instruments are not outputting anything. It will stop all instruments, even those that aren’t involved in the PulseSequence, and does not raise an error if instruments are already stopped.

Summary

Although there are many steps involved in setting up and running a PulseSequence, most of this happens under the hood. If the InstrumentInterfaces and Layout have been set up correctly, this whole process can be executed with just three commands:

>>> layout.pulse_sequence = pulse_sequence
>>> layout.setup()
>>> traces = layout.acquisition()

AcquisitionParameter

Performing a pulse sequence and acquiring its traces is usually only half the story; the traces often need to be analysed afterwards. While the complicated analysis should be done at a later point (potentially in an analysis notebook, initial postprocessing can usually be done straight away. Examples are measuring if a trace has a spike (blip) in current, or performing IQ demodulation of the output signal. This postprocessing is usually dependent on the type of measurement performed, and is therefore inherently linked to the pulse sequence.

The goal of the AcquisitionParameter is to combine the PulseSequence with the corresponding postprocessing analysis, such that the user performs a measurement, and gets the processed data straight away. Each AcquisitionParameter has a specific PulseSequence (AcquisitionParameter.pulse_sequence)and analysis (AcquisitionParameter.analysis()) attached to it. This does not mean that the PulseSequence is fixed; its properties can still be modified. However, the analysis usually analyses traces of specific Pulses in the PulseSequence, and so these pulses need to be present.

As its name suggests, an AcquisitionParameter is a Parameter and not a ParameterNode. The main reason is that you can use an AcquisitionParameter in a measurement Loop as you would any other Parameter. The AcquisitionParameter contains the attribute names, which is a list of things that the analysis returns. Each of these is saved in the DataSet during a Loop.

By default, calling AcquisitionParameter.get() performs the following tasks:

  1. Target its pulse sequence

  2. Setup instruments

  3. Start instruments

  4. Perform acquisition

  5. Stop instruments

  6. Perform its analysis and return results.

The AcquisitionParameter has a rich set of options/features, for more information see in-depth guides/AcquisitionParameter guide.

Note

The raw traces can also be saved during a measurement Loop, see Saving traces.