Repository: microsoft/Qcodes

Issue:
module and package missing for AWG 5014C to use broadbean
If you encounter a bug, use the following template.
If you have a feature request feel free to freestyle or use ideas section in the discussions
[here](https://github.com/QCoDeS/Qcodes/discussions).
If you have a question or general opinion, open your query on a relevant discussion section.

Bug report template:

###
Hi all,
The trouble is that I'm missing a mysterious package called "lomenus". It's not anywhere in Qcodes github or anywhere in internet I know of. not on pip, not on conda. It's probably an internal module used? Without it, it's not possible to use convert broadbean sequence into .awg file that 5014C uses.
Would you be able to upload the module to the github? thanks. @jenshnielsen Send you a similar email but I figure it would be more official to have it here to be resolved.

### Steps to reproduce
1.
![image](https://github.com/QCoDeS/Qcodes/assets/73361099/1af4939a-ab8a-43bc-ac17-5ea6b769b4f7)

2.
3.

### Expected behaviour
What did you expect to happen?

### Actual behaviour
What did happen instead?

### System
It would be helpful to provide such information:

**operating system**

If you are using a released version of qcodes (recommended):
**qcodes version**

If you are using the development version of qcodes:
**qcodes branch**
**qcodes commit**



Discussion:
@gztony1227 Thanks for the report.  Lomentum was an old prototype for a rewrite of broadbean that was never released. The only thing it is used for is the function `make_send_and_load_awg_file_from_forged_sequence` for what you want to do I think you should be able to use `make_send_and_load_awg_file`. Could you check if that is the case?
@jenshnielsen Sorry it took a bit of time for me to look into this. I followed your lead and also this example link:
[https://qcodes.github.io/Qcodes/examples/driver_examples/Qcodes%20example%20with%20Tektronix%20AWG5014C.html](url)

Below funtion and comment within broadbean saved the day (not something obvious and I had to investigate enough to come to this conclusion):

```
def outputForAWGFile(self):
        """
        Returns a sliceable object with items matching the call
        signature of the 'make_*_awg_file' functions of the QCoDeS
        AWG5014 driver. One may then construct an awg file as follows
        (assuming that seq is the sequence object):

        .. code:: python

            package = seq.outputForAWGFile()
            make_awg_file(*package[:], **kwargs)


```
        """

Even with the example and helpful comment, I needed the 5014C driver, broadbean.py, and all to piece together how to use the function to make it work. It's now working that I believe I can send the broadbean-made sequence into the format (aka .awg file) 5014C wants and it shows up in its native win7 software. AWG Tek 5014C is also particular that if the sequence compose script doesn't set all the channel offsets , amplitudes, to be reasonable values, it would generate error and a relatively new user won't be able to tell whether the error is a problem of functions or missing to set offsets/amplitudes. *package[:] in an argument is also tricky to read for inexperienced python user. I learned it now and come to understand that 
*package[:] = (waveforms, m1s, m2s, nreps, trig_waits, goto_states, jump_tos, channels=[1, 3])

I definitely suggest removing anything mentioning Lomentum or include it as a function so that the function make_send_and_load_awg_file_from_forged_sequence can be useful. The example is not complete and Improving the example in the link will also be helpful. However, I also know that slowly 5014C is going out of fashion and will become more and more like a legendary instrument.
Let me know how you want to proceed?
@Dominik-Vogel What you say. Should we simply remove the lomentum specific code.

@gztony1227 I am not actively using the 5014c at the moment so I am unlikely to make improvements to the documentation. But we are very happy to review any improvements if you contribute them 

Yes, lets remove it.

Resolving patch:
diff --git a/qcodes/instrument_drivers/tektronix/AWG5014.py b/qcodes/instrument_drivers/tektronix/AWG5014.py
--- a/qcodes/instrument_drivers/tektronix/AWG5014.py
+++ b/qcodes/instrument_drivers/tektronix/AWG5014.py
@@ -2,21 +2,10 @@
 import logging
 import re
 import struct
-from collections import abc, defaultdict
+from collections import abc
 from io import BytesIO
 from time import localtime, sleep
-from typing import (
-    Any,
-    Dict,
-    List,
-    Literal,
-    NamedTuple,
-    Optional,
-    Sequence,
-    Tuple,
-    Union,
-    cast,
-)
+from typing import Any, Dict, Literal, NamedTuple, Optional, Sequence, Union, cast
 
 import numpy as np
 from pyvisa.errors import VisaIOError
@@ -24,17 +13,6 @@
 from qcodes import validators as vals
 from qcodes.instrument import VisaInstrument
 
-# conditionally import lomentum for support of lomentum type sequences
-try:
-    from lomentum.tools import (  # pyright: ignore[reportMissingImports]
-        get_element_channel_ids,
-        is_subsequence,
-    )
-    USE_LOMENTUM = True
-except ImportError:
-    USE_LOMENTUM = False
-
-
 log = logging.getLogger(__name__)
 
 
@@ -1077,140 +1055,6 @@ def parse_marker_channel_name(name: str) -> _MarkerDescriptor:
         return _MarkerDescriptor(marker=int(res.group('marker')),
                                  channel=int(res.group('channel')))
 
-    def make_send_and_load_awg_file_from_forged_sequence(
-            self,
-            seq: Dict[Any, Any],
-            filename: str = 'customawgfile.awg',
-            preservechannelsettings: bool = True) -> None:
-        """
-        Makes an awg file form a forged sequence as produced by
-        broadbean.sequence.Sequence.forge. The forged sequence is a dictionary
-        (see :attr:`fs_schmea <broadbean.sequence.fs_schmema>`) that does not
-        need to be created by broadbean.
-
-        Args:
-            seq: the sequence dictionary
-            filename: filename of the uploaded awg file.
-                See :meth:`~make_send_and_load_awg_file`
-            preservechannelsettings: see :meth:`~make_send_and_load_awg_file`
-
-        """
-        if not USE_LOMENTUM:
-            raise RuntimeError(
-                'The method "make_send_and_load_awg_file_from_forged_sequence" is '
-                ' only available with the `lomentum` module installed')
-        n_channels = 4
-        self.available_waveform_channels: List[Union[str, int]] = list(range(1, n_channels+1))
-        self.available_marker_channels: List[Union[str, int]] = [
-            f'{c}M{m}'
-            for c in self.available_waveform_channels
-            for m in [1, 2]]
-        self.available_channels = (self.available_waveform_channels +
-                                   self.available_marker_channels)
-
-        waveforms: List[List[Any]] = []
-        m1s: List[List[Any]] = []
-        m2s: List[List[Any]] = []
-        # unfortunately the definitions of the sequence elements in terms of
-        # channel and step in :meth:`make_and_send_awg_file` and the forged
-        # sequence definition are transposed. Start by filling out after schema
-        # and transpose at the end.
-        # make...: [[wfm1ch1, wfm2ch1, ...], [wfm1ch2, wfm2ch2], ...]
-        # dict efectively: {elementid: {channelid: wfm}}
-
-        nreps = []
-        trig_waits = []
-        goto_states = []
-        jump_tos = []
-
-        # obtain list of channels defined in the first step
-        if len(seq) < 1:
-            # TODO: better error
-            raise RuntimeError('Sequences need to have at least one element')
-
-        # assume that the channels are the same on every element
-        provided_channels = get_element_channel_ids(seq[0])
-        used_waveform_channels = list(set(provided_channels).intersection(
-            set(self.available_waveform_channels)))
-        used_marker_channels = list(set(provided_channels).intersection(
-                set(self.available_marker_channels)))
-        associated_marker_channels = [
-            self.parse_marker_channel_name(name).channel
-            for name in used_marker_channels]
-        used_channels = list(
-            set(used_waveform_channels).union(set(associated_marker_channels)))
-
-        for i_elem, elem in enumerate(seq):
-            # TODO: add support for subsequences
-            assert not is_subsequence(elem)
-            datadict = elem['data']
-
-            # Split up the dictionary into two, one for the markers the other
-            # for the waveforms
-            step_waveforms = {}
-            step_markers: Tuple[Dict[int, Any], Dict[int, Any]]
-            step_markers = defaultdict(None), defaultdict(None)
-            for channel, data in datadict.items():
-                if channel in self.available_marker_channels:
-                    t = self.parse_marker_channel_name(channel)
-                    step_markers[t.marker-1][t.channel] = data
-                elif channel in self.available_waveform_channels:
-                    step_waveforms[channel] = data
-                else:
-                    raise RuntimeError(
-                        f'The channel with name {channel} as defined in '
-                        f'the element with no. {i_elem} is not an available '
-                        f'marker channel or waveform channel.\n'
-                        f'Available channels are: '
-                        f'{self.available_marker_channels} and '
-                        f'{self.available_waveform_channels}')
-
-            # create empty trace as template for filling traces with markers
-            # only and traces without markers
-            n_samples = None
-            waveform_keys = list(step_waveforms.keys())
-            marker_keys = tuple(list(step_markers[i].keys()) for i in range(2))
-            if len(waveform_keys) != 0:
-                n_samples = len(step_waveforms[waveform_keys[0]])
-            else:
-                for i in range(2):
-                    if len(marker_keys[i]) != 0:
-                        n_samples = len(step_markers[i][marker_keys[i][0]])
-                        break
-            if n_samples is None:
-                raise RuntimeError('It is not allowed to upload an element '
-                                   'without markers nor waveforms')
-            blank_trace = np.zeros(n_samples)
-
-            # I think this does might add some traces dynamically if they are
-            # not the same in all elements. Add check in the beginning
-            step_waveforms_list = [step_waveforms.get(key, blank_trace)
-                                   for key in used_channels]
-            step_markers_list = tuple([step_markers[i].get(key, blank_trace)
-                                       for key in used_channels]
-                                      for i in range(2))
-
-            waveforms.append(step_waveforms_list)
-            m1s.append(step_markers_list[0])
-            m2s.append(step_markers_list[1])
-            # sequencing
-            seq_opts = elem['sequencing']
-            nreps.append(seq_opts.get('nrep', 1))
-            trig_waits.append(seq_opts.get('trig_wait', 0))
-            goto_states.append(seq_opts.get('goto_state', 0))
-            jump_tos.append(seq_opts.get('jump_to', 0))
-        # transpose list of lists
-        waveforms = [list(x) for x in zip(*waveforms)]
-        m1s = [list(x) for x in zip(*m1s)]
-        m2s = [list(x) for x in zip(*m2s)]
-
-        self.make_send_and_load_awg_file(waveforms, m1s, m2s,
-                                         nreps, trig_waits,
-                                         goto_states, jump_tos,
-                                         channels=used_channels,
-                                         filename=filename,
-                                         preservechannelsettings=preservechannelsettings)
-
     def _generate_awg_file(self,
                            packed_waveforms: Dict[str, np.ndarray],
                            wfname_l: np.ndarray,


Test patch: