docs: def assign_norrec_to_df(df):
      """Determine normal-reciprocal pairs for a given dataframe.
  
      Parameters
      ----------
      df: pandas.DataFrame
          The data
  
      Returns
      -------
      df_new: pandas.DataFrame
          The data with two new columns: "id" and "norrec"
  
      """
      if df.shape[0] == 0:
          # empty dataframe, just return a copy
          return df.copy()
  
      c = df[['a', 'b', 'm', 'n']].values.copy()
      # unique injections
      cu = np.unique(c, axis=0)
  
      print('generating ids')
      # now assign unique IDs to each config in normal and reciprocal
      running_index = 0
      normal_ids = {}
      reciprocal_ids = {}
      # loop through all configurations
      for i in range(0, cu.shape[0]):
          # print('testing', cu[i], i, cu.shape[0])
          # normalize configuration
          cu_norm = _normalize_abmn(cu[i, :]).squeeze()
          if tuple(cu_norm) in normal_ids:
              # print('already indexed')
              continue
  
          # find pairs
          indices = np.where((
              # current electrodes
              (
                  (
                      (cu[:, 0] == cu[i, 2]) & (cu[:, 1] == cu[i, 3])
                  ) |
                  (
                      (cu[:, 0] == cu[i, 3]) & (cu[:, 1] == cu[i, 2])
                  )
              ) &
              # voltage electrodes
              (
                  (
                      (cu[:, 2] == cu[i, 0]) & (cu[:, 3] == cu[i, 1])
                  ) |
                  (
                      (cu[:, 2] == cu[i, 1]) & (cu[:, 3] == cu[i, 0])
                  )
              )
          ))[0]
  
          # we found no pair
          if len(indices) == 0:
              # print('no reciprocals, continuing')
              if not tuple(cu_norm) in normal_ids:
                  if np.min(cu_norm[0:2]) < np.min(cu_norm[2:3]):
                      # treat as normal
                      normal_ids[tuple(cu_norm)] = running_index
                  else:
                      reciprocal_ids[tuple(cu_norm)] = running_index
                  running_index += 1
              continue
  
          # if len(indices) > 1:
          #     print('found more than one reciprocals')
  
          # normalize the first reciprocal
          cu_rec_norm = _normalize_abmn(cu[indices[0], :]).squeeze()
  
          # decide on normal or reciprocal
          # print('ABREC', cu_norm[0:2], cu_rec_norm[0:2])
          if np.min(cu_norm[0:2]) < np.min(cu_rec_norm[0:2]):
              # print('is normal')
              # normal
              normal_ids[tuple(cu_norm)] = running_index
              reciprocal_ids[tuple(cu_rec_norm)] = running_index
          else:
              normal_ids[tuple(cu_rec_norm)] = running_index
              reciprocal_ids[tuple(cu_norm)] = running_index
          running_index += 1
  
      print('assigning ids')
      # print(df.shape)
      # print(df.columns)
      # print('normal_ids', normal_ids)
      # print('reciprocal_ids', reciprocal_ids)
      # now convert the indices into a dataframe so we can use pd.merge
      # note that this code was previously written in another way, so the
      # conversion is quite cumbersome
      # at one point we need to rewrite everything here...
      df_nor = {item: key for key, item in normal_ids.items()}
      df_nor = pd.DataFrame(df_nor).T.reset_index().rename(
          {'index': 'id'}, axis=1)
      df_nor['norrec'] = 'nor'
  
      if len(normal_ids) > 0:
          df_nor.columns = ('id', 'a', 'b', 'm', 'n', 'norrec')
          df_nor2 = df_nor.copy()
          df_nor2.columns = ('id', 'b', 'a', 'm', 'n', 'norrec')
          df_nor3 = df_nor.copy()
          df_nor3.columns = ('id', 'b', 'a', 'n', 'm', 'norrec')
          df_nor4 = df_nor.copy()
          df_nor4.columns = ('id', 'a', 'b', 'n', 'm', 'norrec')
          df_ids = pd.concat(
              (
                  df_nor,
                  df_nor2,
                  df_nor3,
                  df_nor4,
              ),
              sort=True
          )
      else:
          df_ids = pd.DataFrame()
  
      if len(reciprocal_ids) > 0:
          df_rec = {item: key for key, item in reciprocal_ids.items()}
          df_rec = pd.DataFrame(df_rec).T.reset_index().rename(
              {'index': 'id'}, axis=1)
          df_rec['norrec'] = 'rec'
          df_rec.columns = ('id', 'a', 'b', 'm', 'n', 'norrec')
          df_rec2 = df_rec.copy()
          df_rec2.columns = ('id', 'b', 'a', 'm', 'n', 'norrec')
          df_rec3 = df_rec.copy()
          df_rec3.columns = ('id', 'b', 'a', 'n', 'm', 'norrec')
          df_rec4 = df_rec.copy()
          df_rec4.columns = ('id', 'a', 'b', 'n', 'm', 'norrec')
  
          df_ids = pd.concat(
              (
                  df_ids,
                  df_rec,
                  df_rec2,
                  df_rec3,
                  df_rec4,
              ),
              sort=True
          )
  
      df_new = pd.merge(df, df_ids, how='left', on=('a', 'b', 'm', 'n'))
      df_new.rename(
          {'id_y': 'id',
           'norrec_y': 'norrec'
           }, axis=1,
          inplace=True
      )
      return df_new
      import IPython
      IPython.embed()
      exit()
  
      df_new[['a', 'b', 'm', 'n', 'id_y', 'norrec_y']]
      # x.iloc[[0, 1978], :]
  
      # now assign to all measurements
      for key, item in normal_ids.items():
          df.loc[
              ((df.a == key[0]) & (df.b == key[1]) &
               (df.m == key[2]) & (df.n == key[3])) |
              ((df.a == key[1]) & (df.b == key[0]) &
               (df.m == key[2]) & (df.n == key[3])) |
              ((df.a == key[0]) & (df.b == key[1]) &
               (df.m == key[3]) & (df.n == key[2])) |
              ((df.a == key[1]) & (df.b == key[0]) &
               (df.m == key[3]) & (df.n == key[2])),
              ('id', 'norrec')
          ] = (item, 'nor')
      for key, item in reciprocal_ids.items():
          df.loc[
              ((df.a == key[0]) & (df.b == key[1]) &
               (df.m == key[2]) & (df.n == key[3])) |
              ((df.a == key[1]) & (df.b == key[0]) &
               (df.m == key[2]) & (df.n == key[3])) |
              ((df.a == key[0]) & (df.b == key[1]) &
               (df.m == key[3]) & (df.n == key[2])) |
              ((df.a == key[1]) & (df.b == key[0]) &
               (df.m == key[3]) & (df.n == key[2])),
              ('id', 'norrec')
          ] = [item, 'rec']
  
      # cast norrec-column to string
      df['norrec'] = df['norrec'].astype(str)
  
      return df