Main entry function to filtering configuration types

    Parameters
    ----------
    configs: Nx4 array
        array containing A-B-M-N configurations
    settings: dict
        'only_types': ['dd', 'other'],  # filter only for those types

    Returns
    -------
    dict
        results dict containing filter results (indices) for all registered
        filter functions.  All remaining configs are stored under the keywords
        'remaining'

def filter(configs, settings):
    """Main entry function to filtering configuration types

    Parameters
    ----------
    configs: Nx4 array
        array containing A-B-M-N configurations
    settings: dict
        'only_types': ['dd', 'other'],  # filter only for those types

    Returns
    -------
    dict
        results dict containing filter results (indices) for all registered
        filter functions.  All remaining configs are stored under the keywords
        'remaining'

    """
    if isinstance(configs, pd.DataFrame):
        configs = configs[['a', 'b', 'm', 'n']].values

    # assign short labels to Python functions
    filter_funcs = {
        'dd': _filter_dipole_dipole,
        'schlumberger': _filter_schlumberger,
    }

    # we need a list to fix the call order of filter functions
    keys = ['dd', 'schlumberger', ]

    allowed_keys = settings.get('only_types', filter_funcs.keys())

    results = {}
    # we operate iteratively on the configs, set the first round here
    # rows are iteratively set to nan when filters remove them!
    configs_filtered = configs.copy().astype(float)

    for key in keys:
        if key in allowed_keys:
            configs_filtered, indices_filtered = filter_funcs[key](
                configs_filtered,
            )
            if len(indices_filtered) > 0:
                results[key] = indices_filtered

    # add all remaining indices to the results dict
    results['not_sorted'] = np.where(
        ~np.all(np.isnan(configs_filtered), axis=1)
    )[0]
    return results