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"

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