"""
Tests functions responsible for objects validation across FAT-Forensics.
"""
# Author: Kacper Sokol <k.sokol@bristol.ac.uk>
# License: new BSD

# pylint: disable=too-many-lines

import numpy as np
import pytest

import fatf.utils.array.validation as fuav
import fatf.utils.tools as fut

from fatf.utils.testing.arrays import (
    NUMERICAL_NP_ARRAY, NOT_NUMERICAL_NP_ARRAY, WIDE_NP_ARRAY,
    NUMERICAL_STRUCTURED_ARRAY, NOT_NUMERICAL_STRUCTURED_ARRAY,
    WIDE_STRUCTURED_ARRAY, BASE_NP_ARRAY, NOT_BASE_NP_ARRAY,
    BASE_STRUCTURED_ARRAY, NOT_BASE_STRUCTURED_ARRAY)

NUMERICAL_KINDS = [True, 1, -1, 1.0, 1 + 1j, np.nan, np.inf, -np.inf]
NOT_NUMERICAL_KINDS = [object(), 'string', u'unicode', None]
TEXTUAL_KINDS = ['string', u'unicode']
UNSUPPORTED_TEXTUAL_KINDS = [b'bytes']
UNSUPPORTED_TEXTUAL_DTYPES = [np.dtype('S'), np.dtype('a')]
BASE_KINDS = [True, 1, -1, 1.0, 1 + 1j, 'string', u'unicode', b'bytes', np.nan,
              np.inf, -np.inf]  # yapf: disable
NOT_BASE_KINDS = [None, object()]

NP_VER = [int(i) for i in np.version.version.split('.')]
NP_VER_TYPEERROR_MSG_14 = 'a bytes-like object is required, not \'int\''
NP_VER_TYPEERROR_MSG_12 = 'Empty data-type'

def test_is_numerical_dtype():
    """
    Tests :func:`fatf.utils.array.validation.is_numerical_dtype` function.
    """
    type_error = 'The input should be a numpy dtype object.'
    value_error = ('The numpy dtype object is structured. '
                   'Only base dtype are allowed.')
    # Test any object
    with pytest.raises(TypeError) as exin:
        fuav.is_numerical_dtype(None)
    assert str(exin.value) == type_error

    # Test simple numerical arrays
    for i in NUMERICAL_KINDS:
        for j in [i, [i], [i] * 2, [[i] * 2] * 2]:
            array = np.array(j)
            array_dtype = array.dtype
            assert fuav.is_numerical_dtype(array_dtype) is True

    # Test simple not numerical arrays
    for i in NOT_NUMERICAL_KINDS:
        for j in [i, [i], [i] * 2, [[i] * 2] * 2]:
            array = np.array(j)
            array_dtype = array.dtype
            assert fuav.is_numerical_dtype(array_dtype) is False

    # Test simple numerical array
    assert fuav.is_numerical_dtype(NUMERICAL_NP_ARRAY.dtype) is True
    # Test simple not numerical array
    assert fuav.is_numerical_dtype(NOT_NUMERICAL_NP_ARRAY.dtype) is False
    assert fuav.is_numerical_dtype(BASE_NP_ARRAY.dtype) is False
    assert fuav.is_numerical_dtype(NOT_BASE_NP_ARRAY.dtype) is False

    # Test structured numerical array
    with pytest.raises(ValueError) as exin:
        fuav.is_numerical_dtype(NUMERICAL_STRUCTURED_ARRAY.dtype)
    assert str(exin.value) == value_error
    # Test structured not numerical array
    with pytest.raises(ValueError) as exin:
        fuav.is_numerical_dtype(NOT_NUMERICAL_STRUCTURED_ARRAY.dtype)
    assert str(exin.value) == value_error
    with pytest.raises(ValueError) as exin:
        fuav.is_numerical_dtype(BASE_STRUCTURED_ARRAY.dtype)
    assert str(exin.value) == value_error
    with pytest.raises(ValueError) as exin:
        fuav.is_numerical_dtype(NOT_BASE_STRUCTURED_ARRAY.dtype)
    assert str(exin.value) == value_error

    # Test numpy types
    for kind, dtypes in np.sctypes.items():
        if kind == 'others':
            for dtype in dtypes:
                if dtype is bool:
                    assert fuav.is_numerical_dtype(np.dtype(dtype)) is True
                else:
                    assert fuav.is_numerical_dtype(np.dtype(dtype)) is False
        else:
            for dtype in dtypes:
                assert fuav.is_numerical_dtype(np.dtype(dtype)) is True

def test_is_textual_dtype():
    """
    Tests :func:`fatf.utils.array.validation.is_textual_dtype` function.
    """
    # pylint: disable=too-many-branches,too-many-statements
    type_error = 'The input should be a numpy dtype object.'
    value_error = ('The numpy dtype object is structured. '
                   'Only base dtype are allowed.')
    warning_message = ('Zero-terminated bytes type is not supported and is '
                       'not considered to be a textual type. Please use any '
                       'other textual type.')
    # Test any object
    with pytest.raises(TypeError) as exin:
        fuav.is_textual_dtype(None)
    assert str(exin.value) == type_error

    # Test simple numerical arrays
    for i in NUMERICAL_KINDS:
        for j in [i, [i], [i] * 2, [[i] * 2] * 2]:
            array = np.array(j)
            array_dtype = array.dtype
            assert fuav.is_textual_dtype(array_dtype) is False

    # Test simple textual arrays
    for i in TEXTUAL_KINDS:
        for j in [i, [i], [i] * 2, [[i] * 2] * 2]:
            array = np.array(j)
            array_dtype = array.dtype
            assert fuav.is_textual_dtype(array_dtype) is True

    # Test simple not numerical arrays
    for i in NOT_BASE_KINDS:
        for j in [i, [i], [i] * 2, [[i] * 2] * 2]:
            array = np.array(j)
            array_dtype = array.dtype
            assert fuav.is_textual_dtype(array_dtype) is False

    for i in UNSUPPORTED_TEXTUAL_KINDS:
        for j in [i, [i], [i] * 2, [[i] * 2] * 2]:
            array = np.array(j)
            array_dtype = array.dtype
            with pytest.warns(UserWarning) as warning:
                assert fuav.is_textual_dtype(array_dtype) is False
            assert warning_message == str(warning[0].message)

    for dtype in UNSUPPORTED_TEXTUAL_DTYPES:
        with pytest.warns(UserWarning) as warning:
            assert fuav.is_textual_dtype(dtype) is False
        assert warning_message == str(warning[0].message)

    # Test simple numerical array
    assert fuav.is_textual_dtype(NUMERICAL_NP_ARRAY.dtype) is False
    # Test simple not numerical array (with objects)
    assert fuav.is_textual_dtype(NOT_NUMERICAL_NP_ARRAY.dtype) is False
    assert fuav.is_textual_dtype(BASE_NP_ARRAY.dtype) is True
    assert fuav.is_textual_dtype(NOT_BASE_NP_ARRAY.dtype) is False

    # Test structured numerical array
    with pytest.raises(ValueError) as exin:
        fuav.is_textual_dtype(NUMERICAL_STRUCTURED_ARRAY.dtype)
    assert str(exin.value) == value_error
    # Test structured not numerical array
    with pytest.raises(ValueError) as exin:
        fuav.is_textual_dtype(NOT_NUMERICAL_STRUCTURED_ARRAY.dtype)
    assert str(exin.value) == value_error
    with pytest.raises(ValueError) as exin:
        fuav.is_textual_dtype(BASE_STRUCTURED_ARRAY.dtype)
    assert str(exin.value) == value_error
    with pytest.raises(ValueError) as exin:
        fuav.is_textual_dtype(NOT_BASE_STRUCTURED_ARRAY.dtype)
    assert str(exin.value) == value_error

    # Test numpy types
    for kind, dtypes in np.sctypes.items():
        if kind == 'others':
            for dtype in dtypes:
                if dtype is str:
                    assert fuav.is_textual_dtype(np.dtype(dtype)) is True
                elif dtype is bytes:  # pragma: no cover
                    with pytest.warns(UserWarning) as warning:
                        assert fuav.is_textual_dtype(np.dtype(dtype)) is False
                    assert warning_message == str(warning[0].message)
                else:
                    assert fuav.is_textual_dtype(np.dtype(dtype)) is False
        else:
            for dtype in dtypes:
                assert fuav.is_textual_dtype(np.dtype(dtype)) is False

def test_is_base_dtype():
    """
    Tests :func:`fatf.utils.array.validation.is_base_dtype` function.
    """
    type_error = 'The input should be a numpy dtype object.'
    value_error = ('The numpy dtype object is structured. '
                   'Only base dtype are allowed.')
    # Test any object
    with pytest.raises(TypeError) as exin:
        fuav.is_base_dtype(None)
    assert str(exin.value) == type_error

    # Test simple type arrays
    for i in BASE_KINDS:
        for j in [i, [i], [i] * 2, [[i] * 2] * 2]:
            array = np.array(j)
            array_dtype = array.dtype
            assert fuav.is_base_dtype(array_dtype) is True

    # Test simple not numerical arrays
    for i in NOT_BASE_KINDS:
        for j in [i, [i], [i] * 2, [[i] * 2] * 2]:
            array = np.array(j)
            array_dtype = array.dtype
            assert fuav.is_base_dtype(array_dtype) is False

    # Test simple array
    assert fuav.is_base_dtype(NUMERICAL_NP_ARRAY.dtype) is True
    assert fuav.is_base_dtype(NOT_NUMERICAL_NP_ARRAY.dtype) is False
    assert fuav.is_base_dtype(BASE_NP_ARRAY.dtype) is True
    assert fuav.is_base_dtype(NOT_BASE_NP_ARRAY.dtype) is False

    # Test structured array
    with pytest.raises(ValueError) as exin:
        fuav.is_base_dtype(NUMERICAL_STRUCTURED_ARRAY.dtype)
    assert str(exin.value) == value_error
    with pytest.raises(ValueError) as exin:
        fuav.is_base_dtype(NOT_NUMERICAL_STRUCTURED_ARRAY.dtype)
    assert str(exin.value) == value_error
    with pytest.raises(ValueError) as exin:
        fuav.is_base_dtype(BASE_STRUCTURED_ARRAY.dtype)
    assert str(exin.value) == value_error
    with pytest.raises(ValueError) as exin:
        fuav.is_base_dtype(NOT_BASE_STRUCTURED_ARRAY.dtype)
    assert str(exin.value) == value_error

    # Test numpy types
    for kind, dtypes in np.sctypes.items():
        if kind == 'others':
            for dtype in dtypes:
                if dtype is bool or dtype is str or dtype is bytes:
                    assert fuav.is_base_dtype(np.dtype(dtype)) is True
                else:
                    assert fuav.is_base_dtype(np.dtype(dtype)) is False
        else:
            for dtype in dtypes:
                assert fuav.is_base_dtype(np.dtype(dtype)) is True

def test_is_flat_dtype():
    """
    Tests :func:`fatf.utils.array.validation.is_flat_dtype` function.
    """

    def numpy_low():
        assert fuav.is_flat_dtype(NUMERICAL_NP_ARRAY.dtype)
        assert fuav.is_flat_dtype(NUMERICAL_STRUCTURED_ARRAY.dtype[0])
        assert fuav.is_flat_dtype(weird_array_1.dtype[0])
        assert fuav.is_flat_dtype(weird_array_1.dtype[1])
        assert not fuav.is_flat_dtype(weird_array_1.dtype[2])
        assert fuav.is_flat_dtype(weird_array_2.dtype)

    def numpy_high():  # pragma: no cover
        assert fuav.is_flat_dtype(NUMERICAL_NP_ARRAY.dtype)
        assert fuav.is_flat_dtype(NUMERICAL_STRUCTURED_ARRAY.dtype[0])
        assert fuav.is_flat_dtype(weird_array_1.dtype[0])
        assert fuav.is_flat_dtype(weird_array_1.dtype[1])
        assert not fuav.is_flat_dtype(weird_array_1.dtype[2])
        assert fuav.is_flat_dtype(weird_array_2.dtype)

    type_error = 'The input should be a numpy dtype object.'
    value_error = ('The numpy dtype object is structured. '
                   'Only base dtype are allowed.')
    # Test any object
    with pytest.raises(TypeError) as exin:
        fuav.is_flat_dtype(None)
    assert str(exin.value) == type_error

    # Test structured array
    with pytest.raises(ValueError) as exin:
        fuav.is_flat_dtype(NUMERICAL_STRUCTURED_ARRAY.dtype)
    assert str(exin.value) == value_error
    with pytest.raises(ValueError) as exin:
        fuav.is_flat_dtype(NOT_NUMERICAL_STRUCTURED_ARRAY.dtype)
    assert str(exin.value) == value_error
    with pytest.raises(ValueError) as exin:
        fuav.is_flat_dtype(BASE_STRUCTURED_ARRAY.dtype)
    assert str(exin.value) == value_error
    with pytest.raises(ValueError) as exin:
        fuav.is_flat_dtype(NOT_BASE_STRUCTURED_ARRAY.dtype)
    assert str(exin.value) == value_error

    weird_array_1 = np.zeros(
        3, dtype=[('x', 'f4'), ('y', np.float32), ('v', 'f4', (2, 2))])
    weird_array_2 = np.ones((2, 2), dtype=weird_array_1.dtype[2])

    if fuav._NUMPY_1_13:  # pragma: no cover # pylint: disable=protected-access
        numpy_low()
        numpy_high()
    else:  # pragma: no cover
        numpy_low()

def test_are_similar_dtypes():
    """
    Tests :func:`fatf.utils.array.validation.are_similar_dtypes` function.
    """
    # pylint: disable=too-many-statements
    type_error_a = 'dtype_a should be a numpy dtype object.'
    type_error_b = 'dtype_b should be a numpy dtype object.'
    value_error_a = ('The dtype_a is a structured numpy dtype object. Only '
                     'base dtype are allowed.')
    value_error_b = ('The dtype_b is a structured numpy dtype object. Only '
                     'base dtype are allowed.')

    simple_dtype = NUMERICAL_NP_ARRAY.dtype
    structured_dtype = NUMERICAL_STRUCTURED_ARRAY.dtype

    # Test any object
    with pytest.raises(TypeError) as exin:
        fuav.are_similar_dtypes(None, None, False)
    assert str(exin.value) == type_error_a
    with pytest.raises(TypeError) as exin:
        fuav.are_similar_dtypes(None, simple_dtype, True)
    assert str(exin.value) == type_error_a
    with pytest.raises(TypeError) as exin:
        fuav.are_similar_dtypes(simple_dtype, None, False)
    assert str(exin.value) == type_error_b
    with pytest.raises(TypeError) as exin:
        fuav.are_similar_dtypes(structured_dtype, None, True)
    assert str(exin.value) == type_error_b

    # Test structured dtype
    with pytest.raises(ValueError) as exin:
        fuav.are_similar_dtypes(structured_dtype, structured_dtype, True)
    assert str(exin.value) == value_error_a
    with pytest.raises(ValueError) as exin:
        fuav.are_similar_dtypes(structured_dtype, simple_dtype, False)
    assert str(exin.value) == value_error_a
    with pytest.raises(ValueError) as exin:
        fuav.are_similar_dtypes(simple_dtype, structured_dtype, True)
    assert str(exin.value) == value_error_b

    f1_dtype = np.array([5, 1.222]).dtype
    f2_dtype = np.array([5, 1], dtype=float).dtype
    f3_dtype = np.array([5, 1]).dtype
    c1_dtype = np.array(['a', 'b']).dtype
    c2_dtype = np.array(['a']).dtype
    c3_dtype = np.array(['ab']).dtype
    c4_dtype = np.array(['a'], dtype=str).dtype
    c5_dtype = np.array([u'a']).dtype

    # Strict type comparison
    assert fuav.are_similar_dtypes(f1_dtype, f2_dtype, True) is True
    assert fuav.are_similar_dtypes(f2_dtype, f3_dtype, True) is False
    assert fuav.are_similar_dtypes(f3_dtype, c1_dtype, True) is False
    assert fuav.are_similar_dtypes(c1_dtype, c2_dtype, True) is True
    assert fuav.are_similar_dtypes(c2_dtype, c4_dtype, True) is True
    assert fuav.are_similar_dtypes(c2_dtype, c3_dtype, True) is False
    assert fuav.are_similar_dtypes(c3_dtype, c4_dtype, True) is False
    assert fuav.are_similar_dtypes(c1_dtype, c5_dtype, True) is True
    assert fuav.are_similar_dtypes(c2_dtype, c5_dtype, True) is True

    # Fuzzy type comparison
    assert fuav.are_similar_dtypes(f1_dtype, f2_dtype, False) is True
    assert fuav.are_similar_dtypes(f2_dtype, f3_dtype, False) is True
    assert fuav.are_similar_dtypes(f3_dtype, c1_dtype, False) is False
    assert fuav.are_similar_dtypes(c1_dtype, c2_dtype, False) is True
    assert fuav.are_similar_dtypes(c2_dtype, c4_dtype, False) is True
    assert fuav.are_similar_dtypes(c2_dtype, c3_dtype, False) is True
    assert fuav.are_similar_dtypes(c3_dtype, c4_dtype, False) is True
    assert fuav.are_similar_dtypes(c1_dtype, c5_dtype, False) is True
    assert fuav.are_similar_dtypes(c2_dtype, c5_dtype, False) is True

def test_are_similar_dtype_arrays():
    """
    Tests :func:`fatf.utils.array.validation.are_similar_dtype_arrays`.
    """
    type_error_a = 'array_a should be a numpy array-like object.'
    type_error_b = 'array_b should be a numpy array-like object.'

    # Test any object
    with pytest.raises(TypeError) as exin:
        fuav.are_similar_dtype_arrays(None, None, False)
    assert str(exin.value) == type_error_a
    with pytest.raises(TypeError) as exin:
        fuav.are_similar_dtype_arrays(None, NUMERICAL_NP_ARRAY, True)
    assert str(exin.value) == type_error_a
    with pytest.raises(TypeError) as exin:
        fuav.are_similar_dtype_arrays(NUMERICAL_NP_ARRAY, None, False)
    assert str(exin.value) == type_error_b

    # One structured the other one unstructured
    assert fuav.are_similar_dtype_arrays(
        NUMERICAL_NP_ARRAY, NUMERICAL_STRUCTURED_ARRAY, False) is False
    assert fuav.are_similar_dtype_arrays(NUMERICAL_STRUCTURED_ARRAY,
                                         NUMERICAL_NP_ARRAY, True) is False

    f1_array = np.array([5, 1.222])
    f2_array = np.array([5, 1], dtype=float)
    f3_array = np.array([5, 1])
    c1_array = np.array(['a', 'b'])
    c2_array = np.array(['a'])
    c3_array = np.array(['ab'])
    c4_array = np.array(['a'], dtype=str)

    # Both unstructured
    # Strict type comparison
    assert fuav.are_similar_dtype_arrays(f1_array, f2_array, True) is True
    assert fuav.are_similar_dtype_arrays(f2_array, f3_array, True) is False
    assert fuav.are_similar_dtype_arrays(f3_array, c1_array, True) is False
    assert fuav.are_similar_dtype_arrays(c1_array, c2_array, True) is True
    assert fuav.are_similar_dtype_arrays(c2_array, c4_array, True) is True
    assert fuav.are_similar_dtype_arrays(c2_array, c3_array, True) is False
    assert fuav.are_similar_dtype_arrays(c3_array, c4_array, True) is False
    # Fuzzy type comparison
    assert fuav.are_similar_dtype_arrays(f1_array, f2_array, False) is True
    assert fuav.are_similar_dtype_arrays(f2_array, f3_array, False) is True
    assert fuav.are_similar_dtype_arrays(f3_array, c1_array, False) is False
    assert fuav.are_similar_dtype_arrays(c1_array, c2_array, False) is True
    assert fuav.are_similar_dtype_arrays(c2_array, c4_array, False) is True
    assert fuav.are_similar_dtype_arrays(c2_array, c3_array, False) is True
    assert fuav.are_similar_dtype_arrays(c3_array, c4_array, False) is True

    s1_array = np.array([(1, 'abc', 3.14)],
                        dtype=[('a', int), ('b', str), ('c', float)])
    s2_array = np.array([(1, 'abc')], dtype=[('a', int), ('b', str)])
    s3_array = np.array([(1, 'abc')], dtype=[('a', int), ('c', str)])
    s4_array = np.array([(1, 'abc')], dtype=[('a', int), ('b', str)])
    s5_array = np.array([(1, 'abc')], dtype=[('a', float), ('c', str)])

    # Both structured
    # Strict type comparison
    assert fuav.are_similar_dtype_arrays(s1_array, s2_array, True) is False
    assert fuav.are_similar_dtype_arrays(s2_array, s3_array, True) is False
    assert fuav.are_similar_dtype_arrays(s2_array, s4_array, True) is True
    assert fuav.are_similar_dtype_arrays(s4_array, s5_array, True) is False
    # Fuzzy type comparison
    assert fuav.are_similar_dtype_arrays(s1_array, s3_array, False) is False
    assert fuav.are_similar_dtype_arrays(s2_array, s3_array, False) is False
    assert fuav.are_similar_dtype_arrays(s2_array, s4_array, False) is True
    assert fuav.are_similar_dtype_arrays(s4_array, s5_array, False) is False

def test_is_numerical_array():
    """
    Tests :func:`fatf.utils.array.validation.is_numerical_array` function.
    """
    # pylint: disable=too-many-branches,too-many-statements
    type_error = 'The input should be a numpy array-like object.'
    # Test any object
    with pytest.raises(TypeError) as exin:
        fuav.is_numerical_array(None)
    assert str(exin.value) == type_error

    # Test simple numerical arrays
    for i in NUMERICAL_KINDS:
        for j in [i, [i], [i] * 2, [[i] * 2] * 2]:
            array = np.array(j)
            assert fuav.is_numerical_array(array) is True

    # Test simple not numerical arrays
    for i in NOT_NUMERICAL_KINDS:
        for j in [i, [i], [i] * 2, [[i] * 2] * 2]:
            array = np.array(j)
            assert fuav.is_numerical_array(array) is False

    # Test simple numerical array
    assert fuav.is_numerical_array(NUMERICAL_NP_ARRAY) is True
    # Test structured numerical array
    assert fuav.is_numerical_array(NUMERICAL_STRUCTURED_ARRAY) is True
    #
    assert fuav.is_numerical_array(WIDE_NP_ARRAY) is True
    assert fuav.is_numerical_array(WIDE_STRUCTURED_ARRAY) is True

    # Test simple not numerical array
    assert fuav.is_numerical_array(NOT_NUMERICAL_NP_ARRAY) is False
    # Test structured not numerical array
    assert fuav.is_numerical_array(NOT_NUMERICAL_STRUCTURED_ARRAY) is False

    # Test base arrays
    assert fuav.is_numerical_array(BASE_NP_ARRAY) is False
    assert fuav.is_numerical_array(NOT_BASE_NP_ARRAY) is False
    assert fuav.is_numerical_array(BASE_STRUCTURED_ARRAY) is False
    assert fuav.is_numerical_array(NOT_BASE_STRUCTURED_ARRAY) is False

    # Test numpy types
    for kind, dtypes in np.sctypes.items():
        # yapf: disable
        if kind == 'others':
            for dtype in dtypes:
                if dtype is bool:
                    assert fuav.is_numerical_array(
                        np.empty((1, ), dtype=dtype)) is True
                    assert fuav.is_numerical_array(
                        np.ones((1, ), dtype=dtype)) is True
                    assert fuav.is_numerical_array(
                        np.zeros((1, ), dtype=dtype)) is True
                elif dtype is np.void:  # pragma: no cover
                    if not fut.at_least_verion([1, 12], NP_VER):
                        with pytest.raises(TypeError) as exin:
                            fuav.is_numerical_array(
                                np.ones((1, ), dtype=dtype))
                        assert str(exin.value) == NP_VER_TYPEERROR_MSG_12
                        with pytest.raises(TypeError) as exin:
                            fuav.is_numerical_array(
                                np.zeros((1, ), dtype=dtype))
                        assert str(exin.value) == NP_VER_TYPEERROR_MSG_12
                        with pytest.raises(TypeError) as exin:
                            fuav.is_numerical_array(
                                np.empty((1, ), dtype=dtype))
                        assert str(exin.value) == NP_VER_TYPEERROR_MSG_12
                    elif not fut.at_least_verion([1, 14], NP_VER):
                        with pytest.raises(TypeError) as exin:
                            fuav.is_numerical_array(
                                np.ones((1, ), dtype=dtype))
                        assert str(exin.value) == NP_VER_TYPEERROR_MSG_14
                        assert fuav.is_numerical_array(
                            np.zeros((1, ), dtype=dtype)) is False
                        assert fuav.is_numerical_array(
                            np.empty((1, ), dtype=dtype)) is False
                    else:
                        assert fuav.is_numerical_array(
                            np.ones((1, ), dtype=dtype)) is False
                        assert fuav.is_numerical_array(
                            np.zeros((1, ), dtype=dtype)) is False
                        assert fuav.is_numerical_array(
                            np.empty((1, ), dtype=dtype)) is False
                else:
                    assert fuav.is_numerical_array(
                        np.zeros((1, ), dtype=dtype)) is False
                    assert fuav.is_numerical_array(
                        np.empty((1, ), dtype=dtype)) is False
                    assert fuav.is_numerical_array(
                        np.ones((1, ), dtype=dtype)) is False
        else:
            for dtype in dtypes:
                assert fuav.is_numerical_array(
                    np.empty((1, ), dtype=dtype)) is True
                assert fuav.is_numerical_array(
                    np.ones((1, ), dtype=dtype)) is True
                assert fuav.is_numerical_array(
                    np.zeros((1, ), dtype=dtype)) is True
        # yapf: enable

def test_is_textual_array():
    """
    Tests :func:`fatf.utils.array.validation.is_textual_array` function.
    """
    # pylint: disable=too-many-branches,too-many-statements
    type_error = 'The input should be a numpy array-like object.'
    warning_message = ('Zero-terminated bytes type is not supported and is '
                       'not considered to be a textual type. Please use any '
                       'other textual type.')
    # Test any object
    with pytest.raises(TypeError) as exin:
        fuav.is_textual_array(None)
    assert str(exin.value) == type_error

    # Test simple numerical arrays
    for i in NUMERICAL_KINDS:
        for j in [i, [i], [i] * 2, [[i] * 2] * 2]:
            array = np.array(j)
            assert fuav.is_textual_array(array) is False

    # Test simple not numerical arrays
    for i in TEXTUAL_KINDS:
        for j in [i, [i], [i] * 2, [[i] * 2] * 2]:
            array = np.array(j)
            assert fuav.is_textual_array(array) is True

    # Test simple not numerical arrays
    for i in NOT_BASE_KINDS:
        for j in [i, [i], [i] * 2, [[i] * 2] * 2]:
            array = np.array(j)
            assert fuav.is_textual_array(array) is False

    for i in UNSUPPORTED_TEXTUAL_KINDS:
        for j in [i, [i], [i] * 2, [[i] * 2] * 2]:
            array = np.array(j)
            with pytest.warns(UserWarning) as warning:
                assert fuav.is_textual_array(array) is False
            assert warning_message == str(warning[0].message)

    # Test simple numerical array
    assert fuav.is_textual_array(NUMERICAL_NP_ARRAY) is False
    # Test structured numerical array
    assert fuav.is_textual_array(NUMERICAL_STRUCTURED_ARRAY) is False
    #
    assert fuav.is_textual_array(WIDE_NP_ARRAY) is False
    assert fuav.is_textual_array(WIDE_STRUCTURED_ARRAY) is False

    # Test simple not numerical array
    assert fuav.is_textual_array(NOT_NUMERICAL_NP_ARRAY) is False
    # Test structured not numerical array
    assert fuav.is_textual_array(NOT_NUMERICAL_STRUCTURED_ARRAY) is False

    # Test base arrays
    assert fuav.is_textual_array(BASE_NP_ARRAY) is True
    assert fuav.is_textual_array(NOT_BASE_NP_ARRAY) is False
    assert fuav.is_textual_array(BASE_STRUCTURED_ARRAY) is False
    assert fuav.is_textual_array(NOT_BASE_STRUCTURED_ARRAY) is False

    # Test numpy types
    for kind, dtypes in np.sctypes.items():
        # yapf: disable
        if kind == 'others':
            for dtype in dtypes:
                if dtype is str:
                    assert fuav.is_textual_array(
                        np.empty((1, ), dtype=dtype)) is True
                    assert fuav.is_textual_array(
                        np.ones((1, ), dtype=dtype)) is True
                    assert fuav.is_textual_array(
                        np.zeros((1, ), dtype=dtype)) is True
                elif dtype is bytes:  # pragma: no cover
                    with pytest.warns(UserWarning) as warning:
                        assert fuav.is_textual_array(
                            np.zeros((1, ), dtype=dtype)) is False
                    assert warning_message == str(warning[0].message)
                    with pytest.warns(UserWarning) as warning:
                        assert fuav.is_textual_array(
                            np.empty((1, ), dtype=dtype)) is False
                    assert warning_message == str(warning[0].message)
                    with pytest.warns(UserWarning) as warning:
                        assert fuav.is_textual_array(
                            np.ones((1, ), dtype=dtype)) is False
                    assert warning_message == str(warning[0].message)
                elif dtype is np.void:  # pragma: no cover
                    if not fut.at_least_verion([1, 12], NP_VER):
                        with pytest.raises(TypeError) as exin:
                            fuav.is_textual_array(np.ones((1, ), dtype=dtype))
                        assert str(exin.value) == NP_VER_TYPEERROR_MSG_12
                        with pytest.raises(TypeError) as exin:
                            fuav.is_textual_array(
                                np.zeros((1, ), dtype=dtype))
                        assert str(exin.value) == NP_VER_TYPEERROR_MSG_12
                        with pytest.raises(TypeError) as exin:
                            fuav.is_textual_array(
                                np.empty((1, ), dtype=dtype))
                        assert str(exin.value) == NP_VER_TYPEERROR_MSG_12
                    elif not fut.at_least_verion([1, 14], NP_VER):
                        with pytest.raises(TypeError) as exin:
                            fuav.is_textual_array(np.ones((1, ), dtype=dtype))
                        assert str(exin.value) == NP_VER_TYPEERROR_MSG_14
                        assert fuav.is_textual_array(
                            np.zeros((1, ), dtype=dtype)) is False
                        assert fuav.is_textual_array(
                            np.empty((1, ), dtype=dtype)) is False
                    else:
                        assert fuav.is_textual_array(
                            np.ones((1, ), dtype=dtype)) is False
                        assert fuav.is_textual_array(
                            np.zeros((1, ), dtype=dtype)) is False
                        assert fuav.is_textual_array(
                            np.empty((1, ), dtype=dtype)) is False
                else:
                    assert fuav.is_textual_array(
                        np.zeros((1, ), dtype=dtype)) is False
                    assert fuav.is_textual_array(
                        np.empty((1, ), dtype=dtype)) is False
                    assert fuav.is_textual_array(
                        np.ones((1, ), dtype=dtype)) is False
        else:
            for dtype in dtypes:
                assert fuav.is_textual_array(
                    np.empty((1, ), dtype=dtype)) is False
                assert fuav.is_textual_array(
                    np.ones((1, ), dtype=dtype)) is False
                assert fuav.is_textual_array(
                    np.zeros((1, ), dtype=dtype)) is False
        # yapf: enable

def test_is_base_array():
    """
    Tests :func:`fatf.utils.array.validation.is_base_array` function.
    """
    # pylint: disable=too-many-branches,too-many-statements
    type_error = 'The input should be a numpy array-like object.'
    # Test any object
    with pytest.raises(TypeError) as exin:
        fuav.is_base_array(None)
    assert str(exin.value) == type_error

    # Test simple numerical arrays
    for i in BASE_KINDS:
        for j in [i, [i], [i] * 2, [[i] * 2] * 2]:
            array = np.array(j)
            assert fuav.is_base_array(array) is True

    # Test simple not numerical arrays
    for i in NOT_BASE_KINDS:
        for j in [i, [i], [i] * 2, [[i] * 2] * 2]:
            array = np.array(j)
            assert fuav.is_base_array(array) is False

    # Test simple array
    assert fuav.is_base_array(NUMERICAL_NP_ARRAY) is True
    assert fuav.is_base_array(WIDE_NP_ARRAY) is True
    assert fuav.is_base_array(NOT_NUMERICAL_NP_ARRAY) is False
    # Test structured array
    assert fuav.is_base_array(WIDE_STRUCTURED_ARRAY) is True
    assert fuav.is_base_array(NUMERICAL_STRUCTURED_ARRAY) is True
    assert fuav.is_base_array(NOT_NUMERICAL_STRUCTURED_ARRAY) is True
    # Test base arrays
    assert fuav.is_base_array(BASE_NP_ARRAY) is True
    assert fuav.is_base_array(NOT_BASE_NP_ARRAY) is False
    assert fuav.is_base_array(BASE_STRUCTURED_ARRAY) is True
    assert fuav.is_base_array(NOT_BASE_STRUCTURED_ARRAY) is False

    # Test numpy types
    for kind, dtypes in np.sctypes.items():
        # yapf: disable
        if kind == 'others':
            for dtype in dtypes:
                if dtype is bool or dtype is str or dtype is bytes:
                    assert fuav.is_base_array(
                        np.empty((1, ), dtype=dtype)) is True
                    assert fuav.is_base_array(
                        np.ones((1, ), dtype=dtype)) is True
                    assert fuav.is_base_array(
                        np.zeros((1, ), dtype=dtype)) is True
                elif dtype is np.void:  # pragma: no cover
                    if not fut.at_least_verion([1, 12], NP_VER):
                        with pytest.raises(TypeError) as exin:
                            fuav.is_base_array(np.ones((1, ), dtype=dtype))
                        assert str(exin.value) == NP_VER_TYPEERROR_MSG_12
                        with pytest.raises(TypeError) as exin:
                            fuav.is_base_array(
                                np.zeros((1, ), dtype=dtype))
                        assert str(exin.value) == NP_VER_TYPEERROR_MSG_12
                        with pytest.raises(TypeError) as exin:
                            fuav.is_base_array(
                                np.empty((1, ), dtype=dtype))
                        assert str(exin.value) == NP_VER_TYPEERROR_MSG_12
                    elif not fut.at_least_verion([1, 14], NP_VER):
                        with pytest.raises(TypeError) as exin:
                            fuav.is_base_array(np.ones((1, ), dtype=dtype))
                        assert str(exin.value) == NP_VER_TYPEERROR_MSG_14
                        assert fuav.is_base_array(
                            np.zeros((1, ), dtype=dtype)) is False
                        assert fuav.is_base_array(
                            np.empty((1, ), dtype=dtype)) is False
                    else:
                        assert fuav.is_base_array(
                            np.ones((1, ), dtype=dtype)) is False
                        assert fuav.is_base_array(
                            np.zeros((1, ), dtype=dtype)) is False
                        assert fuav.is_base_array(
                            np.empty((1, ), dtype=dtype)) is False
                else:
                    assert fuav.is_base_array(
                        np.zeros((1, ), dtype=dtype)) is False
                    assert fuav.is_base_array(
                        np.empty((1, ), dtype=dtype)) is False
                    assert fuav.is_base_array(
                        np.ones((1, ), dtype=dtype)) is False
        else:
            for dtype in dtypes:
                assert fuav.is_base_array(
                    np.empty((1, ), dtype=dtype)) is True
                assert fuav.is_base_array(
                    np.ones((1, ), dtype=dtype)) is True
                assert fuav.is_base_array(
                    np.zeros((1, ), dtype=dtype)) is True
        # yapf: enable

def test_is_2d_array():
    """
    Tests :func:`fatf.utils.array.validation.is_2d_array` function.
    """
    # pylint: disable=too-many-branches,too-many-locals,too-many-nested-blocks
    # pylint: disable=too-many-statements
    type_error = 'The input should be a numpy array-like.'
    warning_message = ('2-dimensional arrays with 1D structured elements are '
                       'not acceptable. Such a numpy array can be expressed '
                       'as a classic 2D numpy array with a desired type.')
    # Test any object
    with pytest.raises(TypeError) as exin:
        fuav.is_2d_array(None)
    assert str(exin.value) == type_error

    # Test simple numerical and not numerical arrays
    for i in NUMERICAL_KINDS + NOT_NUMERICAL_KINDS:
        for j in [[[i] * 2] * 2]:
            assert fuav.is_2d_array(np.array(j)) is True
        for j in [i, [i], [i] * 2, [[[i] * 2] * 2] * 2]:
            assert fuav.is_2d_array(np.array(j)) is False

    # Test simple and complex numerical and not numerical arrays
    assert fuav.is_2d_array(NUMERICAL_NP_ARRAY) is True
    assert fuav.is_2d_array(NOT_NUMERICAL_NP_ARRAY) is True
    assert fuav.is_2d_array(NUMERICAL_STRUCTURED_ARRAY) is True
    assert fuav.is_2d_array(NOT_NUMERICAL_STRUCTURED_ARRAY) is True
    assert fuav.is_2d_array(WIDE_NP_ARRAY) is True
    assert fuav.is_2d_array(WIDE_STRUCTURED_ARRAY) is True

    # Test simple types
    square_shapes = [(0, 0), (0, 1), (1, 0), (1, 1), (2, 1), (2, 2)]
    not_square_shapes = [(0, ), (1, ), (2, ), (0, 0, 0), (1, 0, 0), (0, 1, 0),
                         (0, 0, 1), (1, 1, 0), (0, 1, 1), (1, 0, 1), (1, 1, 1),
                         (2, 2, 2), (2, 1, 1), (2, 2, 1)]
    for _, dtypes in np.sctypes.items():
        for dtype in dtypes:
            for shape in square_shapes:
                if dtype is np.void:  # pragma: no cover
                    if not fut.at_least_verion([1, 12], NP_VER):
                        with pytest.raises(TypeError) as exin:
                            fuav.is_2d_array(np.ones(shape=shape, dtype=dtype))
                        assert str(exin.value) == NP_VER_TYPEERROR_MSG_12
                        with pytest.raises(TypeError) as exin:
                            fuav.is_2d_array(
                                np.zeros(shape=shape, dtype=dtype))
                        assert str(exin.value) == NP_VER_TYPEERROR_MSG_12
                        with pytest.raises(TypeError) as exin:
                            fuav.is_2d_array(
                                np.empty(shape=shape, dtype=dtype))
                        assert str(exin.value) == NP_VER_TYPEERROR_MSG_12
                    elif not fut.at_least_verion([1, 14], NP_VER):
                        if 0 not in shape:
                            with pytest.raises(TypeError) as exin:
                                fuav.is_2d_array(
                                    np.ones(shape=shape, dtype=dtype))
                            assert str(exin.value) == NP_VER_TYPEERROR_MSG_14
                        else:
                            ones = np.ones(shape=shape, dtype=dtype)
                            assert fuav.is_2d_array(ones) is True
                        zeros = np.zeros(shape=shape, dtype=dtype)
                        assert fuav.is_2d_array(zeros) is True
                        empty = np.empty(shape=shape, dtype=dtype)
                        assert fuav.is_2d_array(empty) is True
                    else:
                        ones = np.ones(shape=shape, dtype=dtype)
                        assert fuav.is_2d_array(ones) is True
                        zeros = np.zeros(shape=shape, dtype=dtype)
                        assert fuav.is_2d_array(zeros) is True
                        empty = np.empty(shape=shape, dtype=dtype)
                        assert fuav.is_2d_array(empty) is True
                else:
                    ones = np.ones(shape=shape, dtype=dtype)
                    assert fuav.is_2d_array(ones) is True
                    zeros = np.zeros(shape=shape, dtype=dtype)
                    assert fuav.is_2d_array(zeros) is True
                    empty = np.empty(shape=shape, dtype=dtype)
                    assert fuav.is_2d_array(empty) is True
            for shape in not_square_shapes:
                if dtype is np.void:  # pragma: no cover
                    if not fut.at_least_verion([1, 12], NP_VER):
                        with pytest.raises(TypeError) as exin:
                            fuav.is_2d_array(np.ones(shape=shape, dtype=dtype))
                        assert str(exin.value) == NP_VER_TYPEERROR_MSG_12
                        with pytest.raises(TypeError) as exin:
                            fuav.is_2d_array(
                                np.zeros(shape=shape, dtype=dtype))
                        assert str(exin.value) == NP_VER_TYPEERROR_MSG_12
                        with pytest.raises(TypeError) as exin:
                            fuav.is_2d_array(
                                np.empty(shape=shape, dtype=dtype))
                        assert str(exin.value) == NP_VER_TYPEERROR_MSG_12
                    elif not fut.at_least_verion([1, 14], NP_VER):
                        if 0 not in shape:
                            with pytest.raises(TypeError) as exin:
                                fuav.is_2d_array(
                                    np.ones(shape=shape, dtype=dtype))
                            assert str(exin.value) == NP_VER_TYPEERROR_MSG_14
                        else:
                            ones = np.ones(shape=shape, dtype=dtype)
                            assert fuav.is_2d_array(ones) is False
                        zeros = np.zeros(shape=shape, dtype=dtype)
                        assert fuav.is_2d_array(zeros) is False
                        empty = np.empty(shape=shape, dtype=dtype)
                        assert fuav.is_2d_array(empty) is False
                    else:
                        ones = np.ones(shape=shape, dtype=dtype)
                        assert fuav.is_2d_array(ones) is False
                        zeros = np.zeros(shape=shape, dtype=dtype)
                        assert fuav.is_2d_array(zeros) is False
                        empty = np.empty(shape=shape, dtype=dtype)
                        assert fuav.is_2d_array(empty) is False
                else:
                    ones = np.ones(shape=shape, dtype=dtype)
                    assert fuav.is_2d_array(ones) is False
                    zeros = np.zeros(shape=shape, dtype=dtype)
                    assert fuav.is_2d_array(zeros) is False
                    empty = np.empty(shape=shape, dtype=dtype)
                    assert fuav.is_2d_array(empty) is False

    # Complex types
    arr = np.zeros(
        3, dtype=[('x', 'f4'), ('y', np.float32), ('value', 'f4', (2, 2))])
    assert fuav.is_2d_array(arr) is False
    arr = np.ones((2, 2), dtype=arr.dtype[2])
    assert fuav.is_2d_array(arr) is False
    # yapf: disable
    not_flat_dtype = [
        NUMERICAL_STRUCTURED_ARRAY.dtype,
        NOT_NUMERICAL_STRUCTURED_ARRAY.dtype]
    flat_dtype = [
        NUMERICAL_NP_ARRAY.dtype,
        NOT_NUMERICAL_NP_ARRAY.dtype]
    flat_struct = [
        np.dtype([('n', NUMERICAL_STRUCTURED_ARRAY.dtype[0])]),
        np.dtype([('n', NUMERICAL_STRUCTURED_ARRAY.dtype[1])]),
        np.dtype([('n', NOT_NUMERICAL_STRUCTURED_ARRAY.dtype[0])]),
        np.dtype([('n', NOT_NUMERICAL_STRUCTURED_ARRAY.dtype[1])])]
    # yapf: enable
    complex_flat_shapes = [(0, ), (1, ), (2, )]
    complex_square_shapes = [(0, 0), (0, 1), (1, 0), (1, 1), (2, 1), (2, 2)]
    complex_not_square_shapes = [(0, 0, 0), (1, 0, 0), (0, 1, 0),
                                 (0, 0, 1), (1, 1, 0), (0, 1, 1),
                                 (1, 0, 1), (1, 1, 1), (2, 2, 2),
                                 (2, 1, 1), (2, 2, 1)]  # yapf: disable
    # Structured arrays flat with multi-demnsional tuples
    for shape in complex_not_square_shapes:
        for dtype in not_flat_dtype + flat_dtype + flat_struct:
            ones = np.ones(shape=shape, dtype=dtype)
            assert fuav.is_2d_array(ones) is False
            zeros = np.zeros(shape=shape, dtype=dtype)
            assert fuav.is_2d_array(zeros) is False
            empty = np.empty(shape=shape, dtype=dtype)
            assert fuav.is_2d_array(empty) is False
    for shape in complex_square_shapes:
        for dtype in flat_dtype:
            ones = np.ones(shape=shape, dtype=dtype)
            assert fuav.is_2d_array(ones) is True
            zeros = np.zeros(shape=shape, dtype=dtype)
            assert fuav.is_2d_array(zeros) is True
            empty = np.empty(shape=shape, dtype=dtype)
            assert fuav.is_2d_array(empty) is True
    for shape in complex_square_shapes:
        for dtype in flat_struct:
            ones = np.ones(shape=shape, dtype=dtype)
            with pytest.warns(UserWarning) as warning:
                assert fuav.is_2d_array(ones) is False
            assert warning_message == str(warning[0].message)
            zeros = np.zeros(shape=shape, dtype=dtype)
            with pytest.warns(UserWarning) as warning:
                assert fuav.is_2d_array(zeros) is False
            assert warning_message == str(warning[0].message)
            empty = np.empty(shape=shape, dtype=dtype)
            with pytest.warns(UserWarning) as warning:
                assert fuav.is_2d_array(empty) is False
            assert warning_message == str(warning[0].message)
    for shape in complex_square_shapes:
        for dtype in not_flat_dtype:
            ones = np.ones(shape=shape, dtype=dtype)
            assert fuav.is_2d_array(ones) is False
            zeros = np.zeros(shape=shape, dtype=dtype)
            assert fuav.is_2d_array(zeros) is False
            empty = np.empty(shape=shape, dtype=dtype)
            assert fuav.is_2d_array(empty) is False
    for shape in complex_flat_shapes:
        for dtype in flat_dtype:
            ones = np.ones(shape=shape, dtype=dtype)
            assert fuav.is_2d_array(ones) is False
            zeros = np.zeros(shape=shape, dtype=dtype)
            assert fuav.is_2d_array(zeros) is False
            empty = np.empty(shape=shape, dtype=dtype)
            assert fuav.is_2d_array(empty) is False
    for shape in complex_flat_shapes:
        for dtype in not_flat_dtype + flat_struct:
            ones = np.ones(shape=shape, dtype=dtype)
            assert fuav.is_2d_array(ones) is True
            zeros = np.zeros(shape=shape, dtype=dtype)
            assert fuav.is_2d_array(zeros) is True
            empty = np.empty(shape=shape, dtype=dtype)
            assert fuav.is_2d_array(empty) is True

def test_is_1d_array():
    """
    Tests :func:`fatf.utils.array.validation.is_1d_array` function.
    """
    # pylint: disable=too-many-branches,too-many-locals,too-many-statements
    # pylint: disable=too-many-nested-blocks
    type_error = 'The input should be a numpy array-like.'
    warning_message = ('Structured (pseudo) 1-dimensional arrays are not '
                       'acceptable. A 1-dimensional structured numpy array '
                       'can be expressed as a classic numpy array with a '
                       'desired type.')
    # Test any object
    with pytest.raises(TypeError) as exin:
        fuav.is_1d_array(None)
    assert str(exin.value) == type_error
    # Test structured array row
    with pytest.raises(TypeError) as exin:
        fuav.is_1d_array(NUMERICAL_STRUCTURED_ARRAY[0])
    assert str(exin.value) == type_error

    # Test simple numerical and not numerical arrays
    for i in NUMERICAL_KINDS + NOT_NUMERICAL_KINDS:
        for j in [[i], [i] * 2]:
            assert fuav.is_1d_array(np.array(j)) is True
        for j in [i, [[i] * 2] * 2, [[[i] * 2] * 2] * 2]:
            assert fuav.is_1d_array(np.array(j)) is False

    # Test complex numerical and not numerical arrays
    assert fuav.is_1d_array(NUMERICAL_NP_ARRAY) is False
    assert fuav.is_1d_array(NOT_NUMERICAL_NP_ARRAY) is False
    #
    assert fuav.is_1d_array(NUMERICAL_STRUCTURED_ARRAY) is False
    assert fuav.is_1d_array(NOT_NUMERICAL_STRUCTURED_ARRAY) is False
    #
    assert fuav.is_1d_array(WIDE_NP_ARRAY) is False
    assert fuav.is_1d_array(WIDE_STRUCTURED_ARRAY) is False

    flat_shapes = [(0, ), (1, ), (2, )]
    not_flat_shapes = [(0, 0), (0, 1), (1, 0), (1, 1), (2, 1), (2, 2),
                       (0, 0, 0), (1, 0, 0), (0, 1, 0), (0, 0, 1), (1, 1, 0),
                       (0, 1, 1), (1, 0, 1), (1, 1, 1), (2, 2, 2), (2, 1, 1),
                       (2, 2, 1)]  # yapf: disable
    for _, dtypes in np.sctypes.items():
        for dtype in dtypes:
            for shape in flat_shapes:
                if dtype is np.void:  # pragma: no cover
                    if not fut.at_least_verion([1, 12], NP_VER):
                        with pytest.raises(TypeError) as exin:
                            fuav.is_1d_array(np.ones(shape=shape, dtype=dtype))
                        assert str(exin.value) == NP_VER_TYPEERROR_MSG_12
                        with pytest.raises(TypeError) as exin:
                            fuav.is_1d_array(
                                np.zeros(shape=shape, dtype=dtype))
                        assert str(exin.value) == NP_VER_TYPEERROR_MSG_12
                        with pytest.raises(TypeError) as exin:
                            fuav.is_1d_array(
                                np.empty(shape=shape, dtype=dtype))
                        assert str(exin.value) == NP_VER_TYPEERROR_MSG_12
                    elif not fut.at_least_verion([1, 14], NP_VER):
                        if 0 not in shape:
                            with pytest.raises(TypeError) as exin:
                                fuav.is_1d_array(
                                    np.ones(shape=shape, dtype=dtype))
                            assert str(exin.value) == NP_VER_TYPEERROR_MSG_14
                        else:
                            ones = np.ones(shape=shape, dtype=dtype)
                            assert fuav.is_1d_array(ones) is True
                        zeros = np.zeros(shape=shape, dtype=dtype)
                        assert fuav.is_1d_array(zeros) is True
                        empty = np.empty(shape=shape, dtype=dtype)
                        assert fuav.is_1d_array(empty) is True
                    else:
                        ones = np.ones(shape=shape, dtype=dtype)
                        assert fuav.is_1d_array(ones) is True
                        zeros = np.zeros(shape=shape, dtype=dtype)
                        assert fuav.is_1d_array(zeros) is True
                        empty = np.empty(shape=shape, dtype=dtype)
                        assert fuav.is_1d_array(empty) is True
                else:
                    ones = np.ones(shape=shape, dtype=dtype)
                    assert fuav.is_1d_array(ones) is True
                    zeros = np.zeros(shape=shape, dtype=dtype)
                    assert fuav.is_1d_array(zeros) is True
                    empty = np.empty(shape=shape, dtype=dtype)
                    assert fuav.is_1d_array(empty) is True
            for shape in not_flat_shapes:
                if dtype is np.void:  # pragma: no cover
                    if not fut.at_least_verion([1, 12], NP_VER):
                        with pytest.raises(TypeError) as exin:
                            fuav.is_1d_array(np.ones(shape=shape, dtype=dtype))
                        assert str(exin.value) == NP_VER_TYPEERROR_MSG_12
                        with pytest.raises(TypeError) as exin:
                            fuav.is_1d_array(
                                np.zeros(shape=shape, dtype=dtype))
                        assert str(exin.value) == NP_VER_TYPEERROR_MSG_12
                        with pytest.raises(TypeError) as exin:
                            fuav.is_1d_array(
                                np.empty(shape=shape, dtype=dtype))
                        assert str(exin.value) == NP_VER_TYPEERROR_MSG_12
                    elif not fut.at_least_verion([1, 14], NP_VER):
                        if 0 not in shape:
                            with pytest.raises(TypeError) as exin:
                                fuav.is_1d_array(
                                    np.ones(shape=shape, dtype=dtype))
                            assert str(exin.value) == NP_VER_TYPEERROR_MSG_14
                        else:
                            ones = np.ones(shape=shape, dtype=dtype)
                            assert fuav.is_1d_array(ones) is False
                        zeros = np.zeros(shape=shape, dtype=dtype)
                        assert fuav.is_1d_array(zeros) is False
                        empty = np.empty(shape=shape, dtype=dtype)
                        assert fuav.is_1d_array(empty) is False
                    else:
                        ones = np.ones(shape=shape, dtype=dtype)
                        assert fuav.is_1d_array(ones) is False
                        zeros = np.zeros(shape=shape, dtype=dtype)
                        assert fuav.is_1d_array(zeros) is False
                        empty = np.empty(shape=shape, dtype=dtype)
                        assert fuav.is_1d_array(empty) is False
                else:
                    ones = np.ones(shape=shape, dtype=dtype)
                    assert fuav.is_1d_array(ones) is False
                    zeros = np.zeros(shape=shape, dtype=dtype)
                    assert fuav.is_1d_array(zeros) is False
                    empty = np.empty(shape=shape, dtype=dtype)
                    assert fuav.is_1d_array(empty) is False

    # yapf: disable
    not_flat_dtype = [
        NUMERICAL_STRUCTURED_ARRAY.dtype,
        NOT_NUMERICAL_STRUCTURED_ARRAY.dtype]
    flat_dtype = [
        NUMERICAL_NP_ARRAY.dtype,
        NOT_NUMERICAL_NP_ARRAY.dtype]
    flat_struct = [
        np.dtype([('n', NUMERICAL_STRUCTURED_ARRAY.dtype[0])]),
        np.dtype([('n', NUMERICAL_STRUCTURED_ARRAY.dtype[1])]),
        np.dtype([('n', NOT_NUMERICAL_STRUCTURED_ARRAY.dtype[0])]),
        np.dtype([('n', NOT_NUMERICAL_STRUCTURED_ARRAY.dtype[1])])]
    # yapf: enable
    for shape in flat_shapes:
        for dtype in flat_dtype:
            ones = np.ones(shape=shape, dtype=dtype)
            assert fuav.is_1d_array(ones) is True
            zeros = np.zeros(shape=shape, dtype=dtype)
            assert fuav.is_1d_array(zeros) is True
            empty = np.empty(shape=shape, dtype=dtype)
            assert fuav.is_1d_array(empty) is True
    for shape in flat_shapes:
        for dtype in flat_struct:
            ones = np.ones(shape=shape, dtype=dtype)
            with pytest.warns(UserWarning) as warning:
                assert fuav.is_1d_array(ones) is False
            assert warning_message == str(warning[0].message)
            #
            zeros = np.zeros(shape=shape, dtype=dtype)
            with pytest.warns(UserWarning) as warning:
                assert fuav.is_1d_array(zeros) is False
            assert warning_message == str(warning[0].message)
            #
            empty = np.empty(shape=shape, dtype=dtype)
            with pytest.warns(UserWarning) as warning:
                assert fuav.is_1d_array(empty) is False
            assert warning_message == str(warning[0].message)
    for shape in flat_shapes:
        for dtype in not_flat_dtype:
            ones = np.ones(shape=shape, dtype=dtype)
            assert fuav.is_1d_array(ones) is False
            zeros = np.zeros(shape=shape, dtype=dtype)
            assert fuav.is_1d_array(zeros) is False
            empty = np.empty(shape=shape, dtype=dtype)
            assert fuav.is_1d_array(empty) is False
    for shape in not_flat_shapes:
        for dtype in not_flat_dtype + flat_dtype + flat_struct:
            ones = np.ones(shape=shape, dtype=dtype)
            assert fuav.is_1d_array(ones) is False
            zeros = np.zeros(shape=shape, dtype=dtype)
            assert fuav.is_1d_array(zeros) is False
            empty = np.empty(shape=shape, dtype=dtype)
            assert fuav.is_1d_array(empty) is False

def test_is_structured_row():
    """
    Tests :func:`fatf.utils.array.validation.is_structured_row` function.
    """
    type_error = ('The input should be a row of a structured numpy array '
                  '(numpy.void type).')
    # Wrong type
    with pytest.raises(TypeError) as exin:
        fuav.is_structured_row(None)
    assert str(exin.value) == type_error
    # Simple arrays
    with pytest.raises(TypeError) as exin:
        fuav.is_structured_row(np.ones((7, 15), dtype=float))
    assert str(exin.value) == type_error
    with pytest.raises(TypeError) as exin:
        fuav.is_structured_row(np.ones((4, ), dtype=float))
    assert str(exin.value) == type_error
    # Structured arrays
    with pytest.raises(TypeError) as exin:
        fuav.is_structured_row(NUMERICAL_STRUCTURED_ARRAY)
    assert str(exin.value) == type_error
    # Structured 0-dimensional arrays
    with pytest.raises(TypeError) as exin:
        fuav.is_structured_row(
            np.array((1., (1 + 1j)), dtype=[('n', '<f8'), ('c', '<c16')]))
    assert str(exin.value) == type_error
    # Structured 1-dimensional arrays
    with pytest.raises(TypeError) as exin:
        fuav.is_structured_row(
            np.array([(1., (1 + 1j))], dtype=[('n', '<f8'), ('c', '<c16')]))
    assert str(exin.value) == type_error
    with pytest.raises(TypeError) as exin:
        fuav.is_structured_row(
            np.array([(1., ), (2, ), (3, )], dtype=[('n', '<f8')]))
    assert str(exin.value) == type_error
    # Void types
    void_array = np.array([b'123'], np.void)
    with pytest.raises(TypeError) as exin:
        fuav.is_structured_row(void_array)
    assert str(exin.value) == type_error
    assert not fuav.is_structured_row(void_array[0])
    void_array = np.array([b'123', b'888'], np.void)
    with pytest.raises(TypeError) as exin:
        fuav.is_structured_row(void_array)
    assert str(exin.value) == type_error
    assert not fuav.is_structured_row(void_array[1])
    # Structured rows
    assert fuav.is_structured_row(NUMERICAL_STRUCTURED_ARRAY[0])
    assert fuav.is_structured_row(NOT_NUMERICAL_STRUCTURED_ARRAY[1])
    assert fuav.is_structured_row(BASE_STRUCTURED_ARRAY[2])
    assert fuav.is_structured_row(NOT_BASE_STRUCTURED_ARRAY[3])

def test_is_1d_like():
    """
    Tests :func:`fatf.utils.array.validation.is_1d_like` function.
    """
    type_error = ('The input should either be a numpy array-like object '
                  '(numpy.ndarray) or a row of a structured numpy array '
                  '(numpy.void).')
    # None type
    with pytest.raises(TypeError) as exin:
        fuav.is_1d_like(None)
    assert str(exin.value) == type_error
    # Array 2D
    assert not fuav.is_1d_like(np.ones((42, 24), dtype=float))
    # Array 1D
    assert fuav.is_1d_like(np.ones((42, ), dtype=float))
    # Structured 2D
    assert not fuav.is_1d_like(NUMERICAL_STRUCTURED_ARRAY)
    assert not fuav.is_1d_like(NOT_NUMERICAL_STRUCTURED_ARRAY)
    assert not fuav.is_1d_like(BASE_STRUCTURED_ARRAY)
    assert not fuav.is_1d_like(NOT_BASE_STRUCTURED_ARRAY)
    # Structured 1D
    assert not fuav.is_1d_like(
        np.array([(1., (1 + 1j))], dtype=[('n', '<f8'), ('c', '<c16')]))
    user_warning = ('Structured (pseudo) 1-dimensional arrays are not '
                    'acceptable. A 1-dimensional structured numpy array can '
                    'be expressed as a classic numpy array with a desired '
                    'type.')
    with pytest.warns(UserWarning) as warning:
        assert not fuav.is_1d_like(
            np.array([(1., ), (2, ), (3, )], dtype=[('n', '<f8')]))
    assert str(warning[0].message) == user_warning
    # Structured row
    assert fuav.is_1d_like(NUMERICAL_STRUCTURED_ARRAY[0])
    assert fuav.is_1d_like(NOT_NUMERICAL_STRUCTURED_ARRAY[1])
    assert fuav.is_1d_like(BASE_STRUCTURED_ARRAY[2])
    assert fuav.is_1d_like(NOT_BASE_STRUCTURED_ARRAY[3])
    # Numpy void
    void_array = np.array([b'123'], np.void)
    assert fuav.is_1d_like(void_array)
    assert not fuav.is_1d_like(void_array[0])
    void_array = np.array([b'123', b'888'], np.void)
    assert fuav.is_1d_like(void_array)
    assert not fuav.is_1d_like(void_array[1])

def test_is_structured_array():
    """
    Tests :func:`fatf.utils.array.validation.is_structured_array` function.
    """
    type_error = 'The input should be a numpy array-like.'
    # Test any object
    with pytest.raises(TypeError) as exin:
        fuav.is_structured_array(None)
    assert str(exin.value) == type_error

    assert fuav.is_structured_array(NUMERICAL_NP_ARRAY) is False
    assert fuav.is_structured_array(NOT_NUMERICAL_NP_ARRAY) is False
    assert fuav.is_structured_array(WIDE_NP_ARRAY) is False
    assert fuav.is_structured_array(NUMERICAL_STRUCTURED_ARRAY) is True
    assert fuav.is_structured_array(NOT_NUMERICAL_STRUCTURED_ARRAY) is True
    assert fuav.is_structured_array(WIDE_STRUCTURED_ARRAY) is True

    shapes = [(0, ), (1, ), (2, ), (0, 0), (0, 1), (1, 0), (1, 1), (2, 1),
              (2, 2), (0, 0, 0), (1, 0, 0), (0, 1, 0), (0, 0, 1), (1, 1, 0),
              (0, 1, 1), (1, 0, 1), (1, 1, 1), (2, 2, 2), (2, 1, 1), (2, 2, 1)]
    # yapf: disable
    basic_dtype = [
        NUMERICAL_NP_ARRAY.dtype,
        NOT_NUMERICAL_NP_ARRAY.dtype,
        NUMERICAL_STRUCTURED_ARRAY.dtype[0],
        NUMERICAL_STRUCTURED_ARRAY.dtype[1],
        NOT_NUMERICAL_STRUCTURED_ARRAY.dtype[0],
        NOT_NUMERICAL_STRUCTURED_ARRAY.dtype[1]]
    struct_dtype = [
        NUMERICAL_STRUCTURED_ARRAY.dtype,
        NOT_NUMERICAL_STRUCTURED_ARRAY.dtype,
        np.dtype([('n', NUMERICAL_STRUCTURED_ARRAY.dtype[0])]),
        np.dtype([('n', NUMERICAL_STRUCTURED_ARRAY.dtype[1])]),
        np.dtype([('n', NOT_NUMERICAL_STRUCTURED_ARRAY.dtype[0])]),
        np.dtype([('n', NOT_NUMERICAL_STRUCTURED_ARRAY.dtype[1])])]
    # yapf: enable
    for shape in shapes:
        for dtype in basic_dtype:
            ones = np.ones(shape=shape, dtype=dtype)
            assert fuav.is_structured_array(ones) is False
            zeros = np.zeros(shape=shape, dtype=dtype)
            assert fuav.is_structured_array(zeros) is False
            empty = np.empty(shape=shape, dtype=dtype)
            assert fuav.is_structured_array(empty) is False
        for dtype in struct_dtype:
            ones = np.ones(shape=shape, dtype=dtype)
            assert fuav.is_structured_array(ones) is True
            zeros = np.zeros(shape=shape, dtype=dtype)
            assert fuav.is_structured_array(zeros) is True
            empty = np.empty(shape=shape, dtype=dtype)
            assert fuav.is_structured_array(empty) is True


#!/Users/bernardroesler/anaconda3/envs/insight/bin/python3
#==============================================================================
#     File: sorting_tests.py
#  Created: 07/05/2018, 17:32
#   Author: Bernie Roesler
#
"""
  Description: Test various sorting algorithms
"""
#==============================================================================

import numpy as np

from sorting_algos import bubble_sort, selection_sort, insertion_sort,\
                          merge_sort, quick_sort, heap_sort

def should_be(x):
    """Test a condition."""
    global tests, fails
    tests += 1
    if not x:
        fails += 1

# Define test cases
A = [8, 4, 3, 2, 1, 7, 6, 0, 5, 9]
sorted_A = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

# sort_funs = [bubble_sort, selection_sort, insertion_sort,\
#              merge_sort, quick_sort]
sort_funs = [heap_sort]

#------------------------------------------------------------------------------
#        Run general sorting algorithm tests
#------------------------------------------------------------------------------
tests = 0
fails = 0
for sort in sort_funs:
    should_be(sort([]) == [])                   # empty list
    should_be(sort([0]) == [0])                 # single element list
    should_be(sort([1, 1, 1]) == [1, 1, 1])     # all equal
    # Pass a copy so we don't sort the original
    should_be(sort(list(sorted_A)) == sorted_A) # sorted list
    should_be(sort(list(A[::-1])) == sorted_A)  # reverse sorted list
    should_be(sort(list(A)) == sorted_A)        # randomized A

if fails > 0:
    print("{}/{} tests failed!".format(fails, tests))
else:
    print("All {} test passed!".format(tests))

#==============================================================================
#==============================================================================


"""This file contains code for use with "Think Stats",
by Allen B. Downey, available from greenteapress.com

Copyright 2014 Allen B. Downey
License: GNU GPLv3 http://www.gnu.org/licenses/gpl.html
"""

from __future__ import print_function

import numpy as np
import sys

import nsfg
import thinkstats2

def main(script):
    """Tests the functions in this module.

    script: string script name
    """
    print('%s: All tests passed.' % script)

if __name__ == '__main__':
    main(*sys.argv)

DSC 530 - Data Exploration and Analysis/ThinkStats2/code/chap01ex.py


from collections import defaultdict
import re
import numpy as np
import string

MIN_WORD_OCCURRENCE = 3

class Tokenizer:
    SENTENCE_SPLIT_REGEX = re.compile(r'(\W+)')

    def __init__(self):
        self.clear()
        self.add_speical_words()

    def clear(self):
        self._vocab_size = 0
        self._idx2word = {}
        self._word2idx = {}
        self.unk_idx = 0

    def add_speical_words(self):
        for word in ["<PAD>", "<UNK>"]:
            self.add_word(word)

    def add_word(self, word):
        self._idx2word[self._vocab_size] = word
        self._word2idx[word] = self._vocab_size
        self._vocab_size += 1
        return self._vocab_size

    def tokenize(self, sent):
        toks = []
        for word in Tokenizer.SENTENCE_SPLIT_REGEX.split(sent.strip()):
            word = word.strip().lower()
            if len(word) > 0:
                if all(c in string.punctuation for c in word) and not all(c in '.' for c in word):
                    toks += list(word)
                else:
                    toks.append(word)
        return toks

    def build_vocab(self, sents, min_occur=MIN_WORD_OCCURRENCE):
        # Count
        self.occur = defaultdict(lambda: 0)
        for sent in sents:
            words = self.tokenize(sent)
            for word in words:
                self.occur[word.lower()] += 1

        wordXnum = sorted(self.occur.items(), key=lambda x:x[1], reverse=True)
        for word, num in wordXnum:
            if num >= min_occur:
                self.add_word(word)

    def word2idx(self, word, allow_unk=True):
        if word in self._word2idx:
            return self._word2idx[word]
        elif allow_unk:
            return self._word2idx['<UNK>']
        else:
            assert False, "No Word %s\n" % word

    def idx2word(self, idx):
        return self._idx2word[int(idx)]     # add int for handling tensors

    @property
    def vocab_size(self):
        return len(self._word2idx)

    @property
    def pad_id(self):
        return self.word2idx("<PAD>", allow_unk=False)

    @property
    def bos_id(self):
        return self.word2idx("<BOS>", allow_unk=False)

    @property
    def eos_id(self):
        return self.word2idx("<EOS>", allow_unk=False)

    @property
    def unk_id(self):
        return self.word2idx("<UNK>")

    def encode(self, sent):
        words = self.tokenize(sent)
        return list(map(lambda word: self.word2idx(word), words))

    def decode(self, idx):
        return " ".join(list(map(lambda i: self.idx2word(i), idx)))

    def dump(self, path):
        with open(path, 'w') as f:
            for i in range(len(self._idx2word)):
                f.write(self._idx2word[i] + "\n")

    def load(self, path):
        self.clear()
        with open(path, 'r') as f:
            for line in f:
                self.add_word(line.rstrip())


import json
import random
import os
from lm_eval.base import MultipleChoiceTask, rf
from ..metrics import mean
from tqdm import auto as tqdm_lib
from . common import simple_accuracy_metric
import numpy as np
from ..utils import sh

class SATAnalogies(MultipleChoiceTask):
    NEEDS_MANUAL_DL = True

    def __init__(self):
        super().__init__()

    def download(self):
        # We should be using a checksum here.
        # The canonical sha256 hash is below:
        # 9dece377d8d57253ef8c78370ff15de0bb1d9e90a82c815a67ba1e621e921bfc

        if not os.path.exists('data/sat/SAT-package-V3.txt'):
            raise NotImplementedError('SAT Analogies dataset is not provided. Follow instructions on https://aclweb.org/aclwiki/SAT_Analogy_Questions_(State_of_the_art) to locate.')

    def has_training_docs(self):
        return False

    def has_validation_docs(self):
        return True

    def has_test_docs(self):
        return False

    def training_docs(self):
        return []
    def test_docs(self):
        return []

    def validation_docs(self):
        data = []

        with open("data/sat/SAT-package-V3.txt", "r") as f:
            record = []
            for line in f:
                line = line.strip()
                if len(line) == 0 and record:
                    data.append(record)
                    record = []
                elif len(line) > 0 and line[0] == '#':
                    continue
                else:
                    record.append(line)
            data.append(record)

        for record in data:
            source = record[-8]
            query = record[-7]
            choices = record[-6:-1]
            answer_key = record[-1]

            doc = {
                'source': source,
                'query': query.split(' ')[:2],
                'choices': ["{} is to {}".format(*c.split(' ')[:2]) for c in choices],
                'gold': ['a','b','c','d','e'].index(answer_key.strip()),
            }
            yield doc

    def fewshot_description(self):
        # TODO: figure out actual description
        return ""

    def doc_to_text(self, doc):
        return "{} is to {} as".format(*doc['query'])


# Copyright 2018-2021 Xanadu Quantum Technologies Inc.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#     http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Tests for the Permute template.
"""
import pytest
import numpy as np
import pennylane as qml

class TestDecomposition:
    """Tests that the template defines the correct decomposition."""

    def test_identity_permutation_qnode(self):
        """ Test that identity permutations have no effect on QNodes. """

        dev = qml.device("default.qubit", wires=4)

        @qml.qnode(dev)
        def identity_permutation():
            qml.templates.Permute([0, 1, 2, 3], wires=dev.wires)
            return qml.expval(qml.PauliZ(0))

        identity_permutation()

        # expand the Permute operation
        tape = identity_permutation.qtape.expand()

        assert len(tape.operations) == 0

    def test_identity_permutation_tape(self):
        """ Test that identity permutations have no effect on tapes. """

        with qml.tape.QuantumTape() as tape:
            qml.templates.Permute([0, "a", "c", "d"], wires=[0, "a", "c", "d"])

        # expand the Permute operation
        tape = tape.expand()

        assert len(tape.operations) == 0

    @pytest.mark.parametrize(
        "permutation_order,expected_wires",
        [
            ([1, 0], [(0, 1)]),
            ([1, 0, 2], [(0, 1)]),
            ([1, 0, 2, 3], [(0, 1)]),
            ([0, 2, 1, 3], [(1, 2)]),
            ([2, 3, 0, 1], [(0, 2), (1, 3)]),
        ],
    )
    def test_two_cycle_permutations_qnode(self, permutation_order, expected_wires):
        """ Test some two-cycles on QNodes. """

        dev = qml.device("default.qubit", wires=len(permutation_order))

        @qml.qnode(dev)
        def two_cycle():
            qml.templates.Permute(permutation_order, wires=dev.wires)
            return qml.expval(qml.PauliZ(0))

        two_cycle()

        # expand the Permute operation
        tape = two_cycle.qtape.expand()

        # Ensure all operations are SWAPs, and that the wires are the same
        assert all(op.name == "SWAP" for op in tape.operations)
        assert [op.wires.labels for op in two_cycle.qtape.operations] == expected_wires

    @pytest.mark.parametrize(
        # For tape need to specify the wire labels
        "permutation_order,wire_order,expected_wires",
        [
            ([1, 0], [0, 1], [(0, 1)]),
            ([1, 0, 2], [0, 1, 2], [(0, 1)]),
            ([1, 0, 2, 3], [0, 1, 2, 3], [(0, 1)]),
            ([0, 2, 1, 3], [0, 1, 2, 3], [(1, 2)]),
            ([2, 3, 0, 1], [0, 1, 2, 3], [(0, 2), (1, 3)]),
            (["a", "b", 0, 1], [0, 1, "a", "b"], [(0, "a"), (1, "b")]),
        ],
    )
    def test_two_cycle_permutations_tape(self, permutation_order, wire_order, expected_wires):
        """ Test some two-cycles on tapes. """

        with qml.tape.QuantumTape() as tape:
            qml.templates.Permute(permutation_order, wire_order)

        # expand the Permute operation
        tape = tape.expand()

        # Ensure all operations are SWAPs, and that the wires are the same
        assert all(op.name == "SWAP" for op in tape.operations)
        assert [op.wires.labels for op in tape.operations] == expected_wires

    @pytest.mark.parametrize(
        "permutation_order,expected_wires",
        [
            ([1, 2, 0], [(0, 1), (1, 2)]),
            ([3, 0, 1, 2], [(0, 3), (1, 3), (2, 3)]),
            ([1, 2, 3, 0], [(0, 1), (1, 2), (2, 3)]),
        ],
    )
    def test_cyclic_permutations_qnode(self, permutation_order, expected_wires):
        """ Test more general cycles on QNodes. """

        dev = qml.device("default.qubit", wires=len(permutation_order))

        @qml.qnode(dev)
        def cycle():
            qml.templates.Permute(permutation_order, wires=dev.wires)
            return qml.expval(qml.PauliZ(0))

        cycle()

        # expand the Permute operation
        tape = cycle.qtape.expand()

        # Ensure all operations are SWAPs, and that the wires are the same
        assert all(op.name == "SWAP" for op in tape.operations)
        assert [op.wires.labels for op in cycle.qtape.operations] == expected_wires

    @pytest.mark.parametrize(
        "permutation_order,wire_order,expected_wires",
        [
            ([1, 2, 0], [0, 1, 2], [(0, 1), (1, 2)]),
            (["d", "a", "b", "c"], ["a", "b", "c", "d"], [("a", "d"), ("b", "d"), ("c", "d")]),
            (["b", 0, "d", "a"], ["a", "b", 0, "d"], [("a", "b"), ("b", 0), (0, "d")]),
        ],
    )
    def test_cyclic_permutations_tape(self, permutation_order, wire_order, expected_wires):
        """ Test more general cycles on tapes. """

        with qml.tape.QuantumTape() as tape:
            qml.templates.Permute(permutation_order, wire_order)

        # expand the Permute operation
        tape = tape.expand()

        # Ensure all operations are SWAPs, and that the wires are the same
        assert all(op.name == "SWAP" for op in tape.operations)
        assert [op.wires.labels for op in tape.operations] == expected_wires

    @pytest.mark.parametrize(
        "permutation_order,expected_wires",
        [
            ([3, 0, 2, 1], [(0, 3), (1, 3)]),
            ([1, 3, 0, 4, 2], [(0, 1), (1, 3), (2, 3), (3, 4)]),
            ([5, 1, 4, 2, 3, 0], [(0, 5), (2, 4), (3, 4)]),
        ],
    )
    def test_arbitrary_permutations_qnode(self, permutation_order, expected_wires):
        """ Test arbitrarily generated permutations on QNodes. """

        dev = qml.device("default.qubit", wires=len(permutation_order))

        @qml.qnode(dev)
        def arbitrary_perm():
            qml.templates.Permute(permutation_order, wires=dev.wires)
            return qml.expval(qml.PauliZ(0))

        arbitrary_perm()

        # expand the Permute operation
        tape = arbitrary_perm.qtape.expand()

        # Ensure all operations are SWAPs, and that the wires are the same
        assert all(op.name == "SWAP" for op in tape.operations)
        assert [op.wires.labels for op in arbitrary_perm.qtape.operations] == expected_wires

    @pytest.mark.parametrize(
        "permutation_order,wire_order,expected_wires",
        [
            ([1, 3, 0, 2], [0, 1, 2, 3], [(0, 1), (1, 3), (2, 3)]),
            (
                ["d", "a", "e", "b", "c"],
                ["a", "b", "c", "d", "e"],
                [("a", "d"), ("b", "d"), ("c", "e")],
            ),
            (
                ["p", "f", 4, "q", "z", 0, "c", "d"],
                ["z", 0, "d", "c", 4, "f", "q", "p"],
                [("z", "p"), (0, "f"), ("d", 4), ("c", "q"), (4, "p")],
            ),
        ],
    )
    def test_arbitrary_permutations_tape(self, permutation_order, wire_order, expected_wires):
        """ Test arbitrarily generated permutations on tapes. """

        with qml.tape.QuantumTape() as tape:
            qml.templates.Permute(permutation_order, wire_order)

        # expand the Permute operation
        tape = tape.expand()

        # Ensure all operations are SWAPs, and that the wires are the same
        assert all(op.name == "SWAP" for op in tape.operations)
        assert [op.wires.labels for op in tape.operations] == expected_wires

    @pytest.mark.parametrize(
        "num_wires,permutation_order,wire_subset,expected_wires",
        [
            (3, [1, 0], [0, 1], [(0, 1)]),
            (4, [3, 0, 2], [0, 2, 3], [(0, 3), (2, 3)]),
            (6, [4, 2, 1, 3], [1, 2, 3, 4], [(1, 4), (3, 4)]),
        ],
    )
    def test_subset_permutations_qnode(
        self, num_wires, permutation_order, wire_subset, expected_wires
    ):
        """ Test permutation of wire subsets on QNodes. """

        dev = qml.device("default.qubit", wires=num_wires)

        @qml.qnode(dev)
        def subset_perm():
            qml.templates.Permute(permutation_order, wires=wire_subset)
            return qml.expval(qml.PauliZ(0))

        subset_perm()

        # expand the Permute operation
        tape = subset_perm.qtape.expand()

        # Ensure all operations are SWAPs, and that the wires are the same
        assert all(op.name == "SWAP" for op in tape.operations)
        assert [op.wires.labels for op in subset_perm.qtape.operations] == expected_wires

    @pytest.mark.parametrize(
        "wire_labels,permutation_order,wire_subset,expected_wires",
        [
            ([0, 1, 2], [1, 0], [0, 1], [(0, 1)]),
            ([0, 1, 2, 3], [3, 0, 2], [0, 2, 3], [(0, 3), (2, 3)]),
            (
                [0, 2, "a", "c", 1, 4],
                [4, "c", 2, "a"],
                [2, "a", "c", 4],
                [(2, 4), ("a", "c"), ("c", 4)],
            ),
        ],
    )
    def test_subset_permutations_tape(
        self, wire_labels, permutation_order, wire_subset, expected_wires
    ):
        """ Test permutation of wire subsets on tapes. """

        with qml.tape.QuantumTape() as tape:
            # Make sure all the wires are actually there
            for wire in wire_labels:
                qml.RZ(0, wires=wire)
            qml.templates.Permute(permutation_order, wire_subset)

        # expand the Permute operation
        tape = tape.expand()

        # Make sure to start comparison after the set of RZs have been applied
        assert all(op.name == "SWAP" for op in tape.operations[len(wire_labels) :])
        assert [op.wires.labels for op in tape.operations[len(wire_labels) :]] == expected_wires

    def test_custom_wire_labels(self, tol):
        """Test that template can deal with non-numeric, nonconsecutive wire labels."""

        permutation = [3, 0, 2, 1]
        permutation2 = ["o", "z", "k", "a"]
        dev = qml.device("default.qubit", wires=4)
        dev2 = qml.device("default.qubit", wires=["z", "a", "k", "o"])

        @qml.qnode(dev)
        def circuit():
            qml.templates.Permute(permutation, wires=range(4))
            return qml.expval(qml.Identity(0))

        @qml.qnode(dev2)
        def circuit2():
            qml.templates.Permute(permutation2, wires=["z", "a", "k", "o"])
            return qml.expval(qml.Identity("z"))

        circuit()
        circuit2()

        assert np.allclose(dev.state, dev2.state, atol=tol, rtol=0)

class TestInputs:
    """Test inputs and pre-processing."""

    @pytest.mark.parametrize(
        "permutation_order,expected_error_message",
        [
            ([0], "Permutations must involve at least 2 qubits."),
            ([0, 1, 2], "Permutation must specify outcome of all wires."),
            ([0, 1, 1, 3], "Values in a permutation must all be unique"),
            ([4, 3, 2, 1], "not present in wire set"),
        ],
    )
    def test_invalid_inputs_qnodes(self, permutation_order, expected_error_message):
        """Tests if errors are thrown for invalid permutations with QNodes."""

        dev = qml.device("default.qubit", wires=4)

        @qml.qnode(dev)
        def permute_qubits():
            qml.templates.Permute(permutation_order, wires=dev.wires)
            return qml.expval(qml.PauliZ(0))

        with pytest.raises(ValueError, match=expected_error_message):
            permute_qubits()

    @pytest.mark.parametrize(
        "permutation_order,expected_error_message",
        [
            ([0], "Permutations must involve at least 2 qubits."),
            ([2, "c", "a", 0], "Permutation must specify outcome of all wires."),
            ([2, "a", "c", "c", 1], "Values in a permutation must all be unique"),
            ([2, "a", "d", "c", 1], r"not present in wire set"),
        ],
    )
    def test_invalid_inputs_tape(self, permutation_order, expected_error_message):
        """Tests if errors are thrown for invalid permutations with tapes."""

        wire_labels = [0, 2, "a", "c", 1]

        with qml.tape.QuantumTape() as tape:
            with pytest.raises(ValueError, match=expected_error_message):
                qml.templates.Permute(permutation_order, wires=wire_labels)


"""
Unit tests of Path types.
"""
import numpy as np
from holoviews import Dataset, Ellipse, Box, Polygons, Path
from holoviews.core.data.interface import DataError
from holoviews.element.comparison import ComparisonTestCase

class PathTests(ComparisonTestCase):

    def test_multi_path_list_constructor(self):
        path = Path([[(0, 1), (1, 2)], [(2, 3), (3, 4)]])
        self.assertTrue(path.interface.multi)
        self.assertEqual(path.dimension_values(0), np.array([
            0, 1, np.nan, 2, 3]))
        self.assertEqual(path.dimension_values(1), np.array([
            1, 2, np.nan, 3, 4]))

    def test_multi_path_cast_path(self):
        path = Path([[(0, 1), (1, 2)], [(2, 3), (3, 4)]])
        path2 = Path(path)
        self.assertTrue(path2.interface.multi)
        self.assertEqual(path2.dimension_values(0), np.array([
            0, 1, np.nan, 2, 3]))
        self.assertEqual(path2.dimension_values(1), np.array([
            1, 2, np.nan, 3, 4]))

    def test_multi_path_tuple(self):
        path = Path(([0, 1], [[1, 3], [2, 4]]))
        self.assertTrue(path.interface.multi)
        self.assertEqual(path.dimension_values(0), np.array([
            0, 1, np.nan, 0, 1]))
        self.assertEqual(path.dimension_values(1), np.array([
            1, 2, np.nan, 3, 4]))

    def test_multi_path_unpack_single_paths(self):
        path = Path([Path([(0, 1), (1, 2)]), Path([(2, 3), (3, 4)])])
        self.assertTrue(path.interface.multi)
        self.assertEqual(path.dimension_values(0), np.array([
            0, 1, np.nan, 2, 3]))
        self.assertEqual(path.dimension_values(1), np.array([
            1, 2, np.nan, 3, 4]))

    def test_multi_path_unpack_multi_paths(self):
        path = Path([Path([[(0, 1), (1, 2)]]),
                     Path([[(2, 3), (3, 4)], [(4, 5), (5, 6)]])])
        self.assertTrue(path.interface.multi)
        self.assertEqual(path.dimension_values(0), np.array([
            0, 1, np.nan, 2, 3, np.nan, 4, 5]))
        self.assertEqual(path.dimension_values(1), np.array([
            1, 2, np.nan, 3, 4, np.nan, 5, 6]))

    def test_single_path_list_constructor(self):
        path = Path([(0, 1), (1, 2), (2, 3), (3, 4)])
        self.assertEqual(path.dimension_values(0), np.array([
            0, 1, 2, 3]))
        self.assertEqual(path.dimension_values(1), np.array([
            1, 2, 3, 4]))

    def test_single_path_tuple_constructor(self):
        path = Path(([0, 1, 2, 3], [1, 2, 3, 4]))
        self.assertEqual(path.dimension_values(0), np.array([
            0, 1, 2, 3]))
        self.assertEqual(path.dimension_values(1), np.array([
            1, 2, 3, 4]))

    def test_multi_path_list_split(self):
        path = Path([[(0, 1), (1, 2)], [(2, 3), (3, 4)]])
        subpaths = path.split()
        self.assertEqual(len(subpaths), 2)
        self.assertEqual(subpaths[0], Path([(0, 1), (1, 2)]))
        self.assertEqual(subpaths[1], Path([(2, 3), (3, 4)]))

    def test_single_path_split(self):
        path = Path(([0, 1, 2, 3], [1, 2, 3, 4]))
        self.assertEqual(path, path.split()[0])

    def test_dataset_groupby_path(self):
        ds = Dataset([(0, 0, 1), (0, 1, 2), (1, 2, 3), (1, 3, 4)], ['group', 'x', 'y'])
        subpaths = ds.groupby('group', group_type=Path)
        self.assertEqual(len(subpaths), 2)
        self.assertEqual(subpaths[0], Path([(0, 1), (1, 2)]))
        self.assertEqual(subpaths[1], Path([(2, 3), (3, 4)]))

class PolygonsTests(ComparisonTestCase):

    def setUp(self):
        xs = [1, 2, 3]
        ys = [2, 0, 7]
        holes = [[[(1.5, 2), (2, 3), (1.6, 1.6)], [(2.1, 4.5), (2.5, 5), (2.3, 3.5)]]]
        self.single_poly = Polygons([{'x': xs, 'y': ys, 'holes': holes}])

        xs = [1, 2, 3, np.nan, 6, 7, 3]
        ys = [2, 0, 7, np.nan, 7, 5, 2]
        holes = [
            [[(1.5, 2), (2, 3), (1.6, 1.6)], [(2.1, 4.5), (2.5, 5), (2.3, 3.5)]],
            []
        ]
        self.multi_poly = Polygons([{'x': xs, 'y': ys, 'holes': holes}])
        self.multi_poly_no_hole = Polygons([{'x': xs, 'y': ys}])

        self.distinct_polys = Polygons([
            {'x': xs, 'y': ys, 'holes': holes, 'value': 0},
            {'x': [4, 6, 6], 'y': [0, 2, 1], 'value': 1}], vdims='value')

    def test_single_poly_holes_match(self):
        self.assertTrue(self.single_poly.interface.has_holes(self.single_poly))
        paths = self.single_poly.split(datatype='array')
        holes = self.single_poly.interface.holes(self.single_poly)
        self.assertEqual(len(paths), len(holes))
        self.assertEqual(len(holes), 1)
        self.assertEqual(len(holes[0]), 1)
        self.assertEqual(len(holes[0][0]), 2)

    def test_multi_poly_holes_match(self):
        self.assertTrue(self.multi_poly.interface.has_holes(self.multi_poly))
        paths = self.multi_poly.split(datatype='array')
        holes = self.multi_poly.interface.holes(self.multi_poly)
        self.assertEqual(len(paths), len(holes))
        self.assertEqual(len(holes), 1)
        self.assertEqual(len(holes[0]), 2)
        self.assertEqual(len(holes[0][0]), 2)
        self.assertEqual(len(holes[0][1]), 0)

    def test_multi_poly_empty_holes(self):
        poly = Polygons([])
        self.assertFalse(poly.interface.has_holes(poly))
        self.assertEqual(poly.interface.holes(poly), [])

    def test_multi_poly_no_holes_match(self):
        self.assertFalse(self.multi_poly_no_hole.interface.has_holes(self.multi_poly_no_hole))
        paths = self.multi_poly_no_hole.split(datatype='array')
        holes = self.multi_poly_no_hole.interface.holes(self.multi_poly_no_hole)
        self.assertEqual(len(paths), len(holes))
        self.assertEqual(len(holes), 1)
        self.assertEqual(len(holes[0]), 2)
        self.assertEqual(len(holes[0][0]), 0)
        self.assertEqual(len(holes[0][1]), 0)

    def test_distinct_multi_poly_holes_match(self):
        self.assertTrue(self.distinct_polys.interface.has_holes(self.distinct_polys))
        paths = self.distinct_polys.split(datatype='array')
        holes = self.distinct_polys.interface.holes(self.distinct_polys)
        self.assertEqual(len(paths), len(holes))
        self.assertEqual(len(holes), 2)
        self.assertEqual(len(holes[0]), 2)
        self.assertEqual(len(holes[0][0]), 2)
        self.assertEqual(len(holes[0][1]), 0)
        self.assertEqual(len(holes[1]), 1)
        self.assertEqual(len(holes[1][0]), 0)

    def test_single_poly_hole_validation(self):
        xs = [1, 2, 3]
        ys = [2, 0, 7]
        with self.assertRaises(DataError):
            Polygons([{'x': xs, 'y': ys, 'holes': [[], []]}])

    def test_multi_poly_hole_validation(self):
        xs = [1, 2, 3, np.nan, 6, 7, 3]
        ys = [2, 0, 7, np.nan, 7, 5, 2]
        with self.assertRaises(DataError):
            Polygons([{'x': xs, 'y': ys, 'holes': [[]]}])

class EllipseTests(ComparisonTestCase):

    def setUp(self):
        self.pentagon = np.array([[  0.00000000e+00,   5.00000000e-01],
                                  [  4.75528258e-01,   1.54508497e-01],
                                  [  2.93892626e-01,  -4.04508497e-01],
                                  [ -2.93892626e-01,  -4.04508497e-01],
                                  [ -4.75528258e-01,   1.54508497e-01],
                                  [ -1.22464680e-16,   5.00000000e-01]])

        self.squashed = np.array([[  0.00000000e+00,   1.00000000e+00],
                                  [  4.75528258e-01,   3.09016994e-01],
                                  [  2.93892626e-01,  -8.09016994e-01],
                                  [ -2.93892626e-01,  -8.09016994e-01],
                                  [ -4.75528258e-01,   3.09016994e-01],
                                  [ -1.22464680e-16,   1.00000000e+00]])

    def test_ellipse_simple_constructor(self):
        ellipse = Ellipse(0,0,1, samples=100)
        self.assertEqual(len(ellipse.data[0]), 100)

    def test_ellipse_simple_constructor_pentagon(self):
        ellipse = Ellipse(0,0,1, samples=6)
        self.assertEqual(np.allclose(ellipse.data[0], self.pentagon), True)

    def test_ellipse_tuple_constructor_squashed(self):
        ellipse = Ellipse(0,0,(1,2), samples=6)
        self.assertEqual(np.allclose(ellipse.data[0], self.squashed), True)

    def test_ellipse_simple_constructor_squashed_aspect(self):
        ellipse = Ellipse(0,0,2, aspect=0.5, samples=6)
        self.assertEqual(np.allclose(ellipse.data[0], self.squashed), True)

class BoxTests(ComparisonTestCase):

    def setUp(self):
        self.rotated_square = np.array([[-0.27059805, -0.65328148],
                                        [-0.65328148,  0.27059805],
                                        [ 0.27059805,  0.65328148],
                                        [ 0.65328148, -0.27059805],
                                        [-0.27059805, -0.65328148]])

        self.rotated_rect = np.array([[-0.73253782, -0.8446232 ],
                                      [-1.11522125,  0.07925633],
                                      [ 0.73253782,  0.8446232 ],
                                      [ 1.11522125, -0.07925633],
                                      [-0.73253782, -0.8446232 ]])

    def test_box_simple_constructor_rotated(self):
        box = Box(0,0,1, orientation=np.pi/8)
        self.assertEqual(np.allclose(box.data[0], self.rotated_square), True)

    def test_box_tuple_constructor_rotated(self):
        box = Box(0,0,(2,1), orientation=np.pi/8)
        self.assertEqual(np.allclose(box.data[0], self.rotated_rect), True)

    def test_box_aspect_constructor_rotated(self):
        box = Box(0,0,1, aspect=2, orientation=np.pi/8)
        self.assertEqual(np.allclose(box.data[0], self.rotated_rect), True)


# AUTOGENERATED! DO NOT EDIT! File to edit: notebooks/custering_and_neighbours.ipynb (unless otherwise specified).

__all__ = []

# Cell
from nbdev.showdoc import *
import jovsatools
import fastcore
from sklearn.datasets import make_blobs
import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
from collections import defaultdict


import numpy as np

def train_test_split(
        X: np.ndarray,
        y: np.ndarray,
        train_size: float = 0.8
):
    """Split dataset into training and test.

    :param X: training data.
    :param y: labels.
    :param train_size: Float, should be between 0.0 and 1.0 and
        represent the proportion of the dataset to include in the
        train split. The rest proportion will be considered as test.
    """
    indices = np.random.permutation(X.shape[0])
    num_train_samples = int(train_size * len(indices))
    indices_train = indices[: num_train_samples]
    indices_test = indices[num_train_samples:]
    return (X[indices_train, :], y[indices_train, :]), (
        X[indices_test, :], y[indices_test, :])


"""
=========================================
A custom pipeline with more possibilities
=========================================

Earlier, we demonstrated how :class:`~pyampute.ampute.MultivariateAmputation` can be integrated in a scikit-learn pipeline (see `A quick example`_ and `Evaluating missing values with grid search and a pipeline`_).

It may be valuable to understand the impact of missing values in more detail. Therefore, we demonstrate how a ``CustomTransformer`` and ``CustomEstimator`` can be used to do a more thorough analysis. Not only will such analysis gain insights in the statistical problems of missing data (and some imputation methods), but it will also help you to create real-world and realistic missingness scenarios.

Another example, of a more systematic approach, can be found in `Schouten and Vink (2021)`_.

.. _`A quick example`: https://rianneschouten.github.io/pyampute/build/html/auto_examples/plot_easy_example.html
.. _`Evaluating missing values with grid search and a pipeline`: https://rianneschouten.github.io/pyampute/build/html/auto_examples/plot_simulation_pipeline.html
.. _`Schouten and Vink (2021)`: https://journals.sagepub.com/doi/full/10.1177/0049124118799376

"""

# Author: Rianne Schouten <https://rianneschouten.github.io/>

# %%
# Recap
#######
#
# Given is the following setting (from `Evaluating missing values with grid search and a pipeline`_):
#
# .. _`Evaluating missing values with grid search and a pipeline`: https://rianneschouten.github.io/pyampute/build/html/auto_examples/plot_simulation_pipeline.html

import numpy as np

m = 5
n = 10000

mean = np.repeat(5, m)
cor = 0.5
cov = np.identity(m)
cov[cov == 0] = cor
compl_dataset = np.random.multivariate_normal(mean, cov, n)

# %%
# As amputation parameter settings, we will vary the proportion, the mechanism and the ``score_to_probability_func``. Since in  the latter have to be specified within the same dictionary, we define the parameters for the grid search as follows.
#

import itertools as it

mechs = ["MCAR", "MAR", "MNAR"]
funcs = ["sigmoid-right", "sigmoid-mid"]

parameters = {
    "amputation__prop": [0.1, 0.5, 0.9],
    "amputation__patterns": [
        [{"incomplete_vars": [0,1], "mechanism": mechanism, "score_to_probability_func": func}]
        for mechanism, func in list(it.product(mechs, funcs))]
}

# %%
# A transformer that drops incomplete rows
##########################################
#
# Previously, we evaluated the ``SimpleImputer`` class from scikit-learn. Another good way to evaluate the effect of missing values, is by analyzing the incomplete dataset directly. Since most prediction and analysis models do not accept missing values, we apply the `dropna` or `listwise deletion` or `complete case analysis` method (all names refer to the same strategy). To allow for integration in a pipeline, we set up a custom ``TransformerMixin``.
#

from sklearn.base import TransformerMixin

class DropTransformer(TransformerMixin):

    def __init__(self):
        super().__init__()

    def fit(self, X, y=None):
        self.X = X

        return self

    def transform(self, X, y=None):

        # drop incomplete rows
        Xp = pd.DataFrame(X)
        Xdrop = Xp.dropna().to_numpy()

        return Xdrop

# %%
# A custom estimator
####################
#
# Almost all, if not all, estimators and evaluation metrics in scikit-learn are aimed at prediction or classification. That is what most people want to do.
#
# However, for evaluating the effect of missing values on your model, it may be good to look further than just the prediction or classification accuracy. In this example, we will focus on the center of the distribution of one feature and evaluate the bias in that distribution.
#
# That could work as follows.
#

from sklearn.base import BaseEstimator

class CustomEstimator(BaseEstimator):

    def __init__(self):
        super().__init__()

    def fit(self, X, y=None):
        self.X = X

        return self

    def predict(self, X):

        # return values of first feature
        values_used_for_score = X[:,0]

        return values_used_for_score

def my_evaluation_metric(y_true, y_pred):

    m1 = np.mean(y_true)
    m2 = np.mean(y_pred)

    bias = np.abs(m1 - m2)

    return bias

# %%
# An evaluation pipeline
########################
#
# As can be seen, the ``predict`` function returns the first feature of the transformed dataset. The evaluation metric then calculated the mean difference between that feature, and the truth.
#
# In our experiment, the complete dataset is the ground truth and we evaluate the impact of several missing data models (and imputation models) on that truth.
#
# We then run the pipeline twice.
#

import pandas as pd
from sklearn.pipeline import Pipeline
from sklearn.model_selection import GridSearchCV
from sklearn.impute import SimpleImputer
from pyampute.ampute import MultivariateAmputation
from sklearn.metrics import make_scorer

# %%
# Once with the DropTransformer

steps = [('amputation', MultivariateAmputation()), ('imputation', DropTransformer()), ('estimator', CustomEstimator())]
pipe = Pipeline(steps)
grid = GridSearchCV(
    estimator=pipe,
    param_grid=parameters,
    scoring=make_scorer(my_evaluation_metric),
)

grid.fit(compl_dataset, np.zeros(len(compl_dataset)))
grid.score(compl_dataset, compl_dataset[:,0])
results_drop = pd.DataFrame(grid.cv_results_)

# %%
# Once with the SimpleImputer

steps = [('amputation', MultivariateAmputation()), ('imputation', SimpleImputer()), ('estimator', CustomEstimator())]
pipe = Pipeline(steps)
grid = GridSearchCV(
    estimator=pipe,
    param_grid=parameters,
    scoring=make_scorer(my_evaluation_metric),
)

grid.fit(compl_dataset, np.zeros(len(compl_dataset)))
grid.score(compl_dataset, compl_dataset[:,0])
results_mean = pd.DataFrame(grid.cv_results_)

# %%
# Comparison
############
#

res_drop = results_drop[['param_amputation__patterns', 'param_amputation__prop', 'mean_test_score']]
res_mean = results_mean[['param_amputation__patterns', 'param_amputation__prop', 'mean_test_score']]

res_drop.columns = ['mechanism, func', 'prop', 'score']
res_mean.columns = ['mechanism, func', 'prop', 'score']

res_drop

# %%

res_mean

# %%
#
# What you find here, is that a MCAR mechanism will not affect the center of the distribution of the first feature much, independent of the proportion of incomplete rows.
#
# A MAR mechanism with a sigmoid-right probability function will, on average, remove the right-hand side of the distribution (also, because there is a positive correlation between the observed data and the first feature). Therefore, the larger the proportion, the more bias. However, with a sigmoid-mid probability function, values in the center of the distribution of the first feature are removed, and there is therefore not much effect on the bias.
#
# The same logic applies to MNAR missingness, but since MNAR missingness does not depend on the size of the correlation between observed data and incomplete data, the bias will be stronger.
#
# `Schouten and Vink (2021)`_ further discuss this topic and the effect of multiple imputation (which can be performed using scikit-learn's IterativeImputer).
#
# SimpleImputer will use the mean of the observed data in the first feature. Therefore, in case there is any bias, that bias will remain. In case there is no bias, mean imputation will distort the correlation structure with other features. But that is another story...
#
# .. _`Schouten and Vink (2021)`: https://journals.sagepub.com/doi/full/10.1177/0049124118799376