Skip to content

Commit

Permalink
Merge pull request #107 from qutech/feature/trigger_mapping_overhaul
Browse files Browse the repository at this point in the history
Feature/trigger mapping overhaul
  • Loading branch information
THuckemann authored Oct 31, 2024
2 parents f4e413a + 35a336c commit c49b60d
Show file tree
Hide file tree
Showing 17 changed files with 2,809 additions and 94 deletions.
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
rev: v5.0.0
hooks:
- id: check-docstring-first
- id: check-yaml
Expand All @@ -15,7 +15,7 @@ repos:
- id: trailing-whitespace
- id: end-of-file-fixer
- repo: https://github.com/asottile/pyupgrade
rev: v3.17.0
rev: v3.19.0
hooks:
- id: pyupgrade
args: [--py39-plus]
Expand All @@ -25,7 +25,7 @@ repos:
- id: isort
args: ["--profile", "black"]
- repo: https://github.com/psf/black
rev: 24.8.0
rev: 24.10.0
hooks:
- id: black
args: ["--line-length", "120"]
Expand Down
16 changes: 9 additions & 7 deletions docs/examples.rst
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
Examples
Examples
==============

Measurement Scripts
Expand Down Expand Up @@ -376,7 +377,7 @@ to the used instruments. This requires a few changes to the way the measurement
station.add_component(dac)
mfli = MFLI("mfli", "DEV4121", "169.254.40.160")
add_mapping_to_instrument(mfli, path = MFLI_MAPPING)
add_mapping_to_instrument(mfli, mappint = MFLI_MAPPING)
station.add_component(mfli)
(This tutorial expects you to do the basic qcodes and QuMADA imports on your own)
Expand All @@ -386,13 +387,10 @@ The QuMADA buffer has methods to setup the buffer and triggers as well as to sta
to the QuMADA ones. Currently, QuMADA supports the MFLI and the SR830 (more to come), how to add additional instruments by yourself will be covered in a different section.

The DecaDac's is required to do a smooth ramp, which requires usage of the built in ramp method. As this cannot be mapped by using the normal QuMADA mapping.json file, we use the DecadacMapping class and pass it as the mapping-kwarg
(instead of "path") to "add_mapping_to_instrument". This does not only add the normal mapping but includes the _qumada_ramp() method which is used in QuMADA' buffered measurement scripts for ramping channels. This method makes use of the
to "add_mapping_to_instrument". This does not only add the normal mapping but includes the _qumada_ramp() method which is used in QuMADA' buffered measurement scripts for ramping channels. This method makes use of the
built-in ramp method, but standardizes the input parameters so that different instruments can be used with the same measurement script. Note that instruments without built-in ramps can be used for the buffered measurements as well, but then require communication at
each setpoint, which slows down the measurement and can lead to asynchronicity. It is strongly adviced to use this feature only for debugging.

.. note::

In some cases it is possible to add trigger channels to the _qumada_ramp method. Those are triggered as soon as the ramp starts. However, this feature is still WIP and can lead to significat offsets due to time delays.

Setting up the buffer in QuMADA is done via a settings dict (which can also be serialized into a yaml or json file). The parameters are:

Expand Down Expand Up @@ -478,7 +476,7 @@ The measurement script is then setup in almost the same way as for normal, unbuf
sync_trigger = dac.channels[19].volt)
map_gates_to_instruments(station.components, script.gate_parameters)
map_buffers(station.components, script.properties, script.gate_parameters)
map_triggers(station.components)
Instead of the Generic_1D_Sweep we are now using the buffed version. It requires the buffer_settings as input argument as well as the trigger_type.
The trigger type defines, how the measurement is started, it can be either "manual", meaning the script does not care about triggers and just starts the sweep once the script.run is executed,
Expand All @@ -496,7 +494,7 @@ The latter tells the measurement script how to start the measurement.
You can specify a sync_trigger in the script.setup() which is then passed on to the ramp method (if supported by the instrument) and will automatically raise the trigger once the measurement is started in "manual" mode.
In this example the Dac's last channel will be used to trigger the measurement.

In addition to the familiar map_gates_to_instruments, we have to execute map_buffers() as well.
In addition to the familiar map_gates_to_instruments, we have to execute map_triggers() as well.
It is used to specify the trigger inputs used to trigger the available buffers.

.. code-block::
Expand All @@ -514,6 +512,10 @@ It is used to specify the trigger inputs used to trigger the available buffers.
If required the buffer settings are changed to allow usage of the chosen trigger input. In our example, choosing the trigger_in_1 for the MFLI will change the trigger_mode from "edge" to "digital",
as the MFLI's trigger inputs require this setting and would raise an exception during the measurement.

It is also possible to save and load trigger mappings to/from json files. You can simply use "save_trigger_mapping" and "load_trigger_mapping" from qumada.instrument.buffers.buffer and provide
station.components and a file path. Alternatively, you can call map_triggers with the "path" argument and provide the path to your mapping-json. The mapping prompt will only open up, if not all instruments can be
mapped from the file.

.. code-block:: python
script.run()
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ dynamic = ["version"]

requires-python = ">=3.9"
dependencies = [
"qcodes >= 0.40.0",
"qcodes >= 0.46.0",
"qcodes_contrib_drivers >= 0.18.0",
"matplotlib",
"jsonschema",
Expand Down
18 changes: 11 additions & 7 deletions src/examples/buffered_dummy_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,11 @@
from qcodes.station import Station

from qumada.instrument.buffered_instruments import BufferedDummyDMM as DummyDmm
from qumada.instrument.buffers.buffer import map_buffers
from qumada.instrument.buffers.buffer import (
load_trigger_mapping,
map_triggers,
save_trigger_mapping,
)
from qumada.instrument.custom_drivers.Dummies.dummy_dac import DummyDac
from qumada.instrument.mapping import (
DUMMY_DMM_MAPPING,
Expand Down Expand Up @@ -87,20 +91,20 @@
buffer_settings = {
# "trigger_threshold": 0.005,
# "trigger_mode": "digital",
"sampling_rate": 10,
"sampling_rate": 20,
"duration": 5,
"burst_duration": 5,
"delay": 0,
}

# %% Measurement Setup
parameters = {
"dmm": {
"ohmic": {
"voltage": {"type": "gettable"},
"current": {"type": "gettable"},
},
"dac": {"voltage": {"type": "dynamic", "setpoints": np.linspace(0, np.pi, 100), "value": 0}},
"dac2": {"voltage": {"type": "dynamic", "setpoints": np.linspace(0, np.pi, 100), "value": 0}},
"gate1": {"voltage": {"type": "dynamic", "setpoints": np.linspace(0, np.pi, 100), "value": 0}},
"gate2": {"voltage": {"type": "dynamic", "setpoints": np.linspace(0, np.pi, 100), "value": 0}},
}
# %%
script = Generic_1D_Sweep_buffered()
Expand All @@ -114,7 +118,7 @@
)

map_terminals_gui(station.components, script.gate_parameters)
map_buffers(station.components, script.properties, script.gate_parameters)
map_triggers(station.components)

# %% Run measurement
script.run(insert_metadata_into_db=False)
script.run()
4 changes: 2 additions & 2 deletions src/examples/buffered_dummy_example_compensation.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
from qcodes.station import Station

from qumada.instrument.buffered_instruments import BufferedDummyDMM as DummyDmm
from qumada.instrument.buffers.buffer import map_buffers
from qumada.instrument.buffers.buffer import map_triggers
from qumada.instrument.custom_drivers.Dummies.dummy_dac import DummyDac
from qumada.instrument.mapping import (
DUMMY_DMM_MAPPING,
Expand Down Expand Up @@ -139,7 +139,7 @@
)

map_terminals_gui(station.components, script.gate_parameters)
map_buffers(station.components, script.properties, script.gate_parameters)
map_triggers(station.components, script.properties, script.gate_parameters)

# %% Run measurement
script.run(insert_metadata_into_db=False)
Expand Down
121 changes: 75 additions & 46 deletions src/qumada/instrument/buffers/buffer.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

from __future__ import annotations

import json
import logging
from abc import ABC, abstractmethod
from collections.abc import Mapping
from typing import Any
Expand All @@ -28,6 +30,8 @@
from qcodes.metadatable import Metadatable
from qcodes.parameters import Parameter

logger = logging.getLogger(__name__)


def is_bufferable(object: Instrument | Parameter):
"""Checks if the instrument or parameter is bufferable using the qumada Buffer definition."""
Expand All @@ -54,26 +58,15 @@ class BufferException(Exception):

def map_buffers(
components: Mapping[Any, Metadatable],
properties: dict,
gate_parameters: Mapping[Any, Mapping[Any, Parameter] | Parameter],
overwrite_trigger=None,
skip_mapped=True,
**kwargs,
) -> None:
"""
Maps the bufferable instruments of gate parameters.
Args:
components (Mapping[Any, Metadatable]): Instruments/Components in QCoDeS
gate_parameters (Mapping[Any, Union[Mapping[Any, Parameter], Parameter]]):
Gates, as defined in the measurement script
"""
# subscribe to gettable parameters with buffer
for gate, parameters in gate_parameters.items():
for parameter, channel in parameters.items():
if properties[gate][parameter]["type"] == "gettable":
if is_bufferable(channel):
buffer: Buffer = channel.root_instrument._qumada_buffer
buffer.subscribe([channel])

buffered_instruments = filter(is_bufferable, components.values())
for instrument in buffered_instruments:
Expand All @@ -85,14 +78,7 @@ def map_buffers(
print("[0]: None")
for idx, trigger in enumerate(buffer.AVAILABLE_TRIGGERS, 1):
print(f"[{idx}]: {trigger}")
# TODO: Just a workaround, fix this!
if overwrite_trigger is not None:
try:
chosen = int(overwrite_trigger)
except Exception:
chosen = int(input(f"Choose the trigger input for {instrument.name}: "))
else:
chosen = int(input(f"Choose the trigger input for {instrument.name}: "))
chosen = int(input(f"Choose the trigger input for {instrument.name}: "))
if chosen == 0:
trigger = None
else:
Expand All @@ -103,18 +89,14 @@ def map_buffers(

def _map_triggers(
components: Mapping[Any, Metadatable],
properties: dict,
gate_parameters: Mapping[Any, Mapping[Any, Parameter] | Parameter],
overwrite_trigger=None,
skip_mapped=True,
**kwargs,
) -> None:
"""
Maps the bufferable instruments of gate parameters.
Args:
components (Mapping[Any, Metadatable]): Instruments/Components in QCoDeS
gate_parameters (Mapping[Any, Union[Mapping[Any, Parameter], Parameter]]):
Gates, as defined in the measurement script
"""
triggered_instruments = filter(is_triggerable, components.values())
for instrument in triggered_instruments:
Expand All @@ -125,14 +107,7 @@ def _map_triggers(
print("[0]: None")
for idx, trigger in enumerate(instrument._qumada_mapping.AVAILABLE_TRIGGERS, 1):
print(f"[{idx}]: {trigger}")
# TODO: Just a workaround, fix this!
if overwrite_trigger is not None:
try:
chosen = int(overwrite_trigger)
except Exception:
chosen = int(input(f"Choose the trigger input for {instrument.name}: "))
else:
chosen = int(input(f"Choose the trigger input for {instrument.name}: "))
chosen = int(input(f"Choose the trigger input for {instrument.name}: "))
if chosen == 0:
trigger = None
else:
Expand All @@ -143,25 +118,79 @@ def _map_triggers(

def map_triggers(
components: Mapping[Any, Metadatable],
properties: dict,
gate_parameters: Mapping[Any, Mapping[Any, Parameter] | Parameter],
overwrite_trigger=None,
skip_mapped=True,
path: None | str = None,
**kwargs,
) -> None:
"""
Maps the triggers of triggerable or bufferable components.
Ignores already mapped triggers by default.
Parameters
----------
components : Mapping[Any, Metadatable]
Components of QCoDeS station (containing instruments to be mapped).
properties : dict
Properties of measurement script/device. Currently only required for
buffered instruments.
TODO: Remove!
gate_parameters : Mapping[Any, Mapping[Any, Parameter] | Parameter]
Parameters of measurement script/device. Currently only required for
buffered instruments.
TODO: Remove!
skip_mapped : Bool, optional
If true already mapped parameters are skipped
Set to false if you want to remap something. The default is True.
path : None|str, optional
Provide path to a json file with trigger mapping. If not all instruments
are covered in the file, you will be asked to map those. Works only if
names in file match instrument.full_name of your current instruments.
The default is None.
"""
if path is not None:
try:
load_trigger_mapping(components, path)
except Exception as e:
logger.warning(f"Exception when loadig trigger mapping from file: {e}")
map_buffers(
components,
properties,
gate_parameters,
overwrite_trigger,
skip_mapped,
)
_map_triggers(
components,
properties,
gate_parameters,
overwrite_trigger,
skip_mapped,
**kwargs,
)
_map_triggers(components, skip_mapped, **kwargs)


def save_trigger_mapping(components: Mapping[Any, Metadatable], path: str):
"""
Saves mapped triggers from components to json file.
Components should be station.components, path is the path to the file.
"""
trigger_dict = {}
triggered_instruments = filter(lambda x: any((is_triggerable(x), is_bufferable(x))), components.values())
for instrument in triggered_instruments:
try:
trigger_dict[instrument.full_name] = instrument._qumada_mapping.trigger_in
except AttributeError:
trigger_dict[instrument.full_name] = instrument._qumada_buffer.trigger
with open(path, mode="w") as file:
json.dump(trigger_dict, file)


def load_trigger_mapping(components: Mapping[Any, Metadatable], path: str):
"""
Loads json file with trigger mappings and tries to apply them to instruments
in the components. Works only if the instruments have the same full_name
as in the saved file!
"""
with open(path) as file:
trigger_dict: Mapping[str, Mapping[str, str] | str] = json.load(file)
for instrument_name, trigger in trigger_dict.items():
for instrument in components.values():
if instrument.full_name == instrument_name:
if is_bufferable(instrument) is True:
instrument._qumada_buffer.trigger = trigger
elif is_triggerable(instrument) is True:
instrument._qumada_mapping.trigger_in = trigger


class Buffer(ABC):
Expand Down
2 changes: 1 addition & 1 deletion src/qumada/instrument/buffers/mfli_buffer.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ def num_points(self) -> int | None:

@num_points.setter
def num_points(self, num_points) -> None:
if num_points > 8388608:
if num_points > 8_388_608:
raise BufferException(
"Buffer is to small for this measurement. \
Please reduce the number of data points"
Expand Down
Loading

0 comments on commit c49b60d

Please sign in to comment.