# Path: cotede/fuzzy/fuzzy_core.py
# def fuzzy_uncertainty(data, features, output, require="all"):
#     """Estimate the Fuzzy uncertainty of the given data
# 
#     Parameters
#     ----------
#     data :
#     features :
#     output :
#     require : all or any, optional
#         Require all or any of the features to estimate the uncertainty
#     """
#     # It's not clear at Morello 2014 what is the operator K()
#     # Q is the uncertainty, hence Q_low is the low uncertainty
#     # Seems like K() is just a linear factor, which would give the level of uncertainty, like 0.1 for low, 0.5 for medium and 0.9 for high would define weights for each level?! I'm not sure. But the result would be a composite curve, so when the Qs are joinned it would give a curve with the possible values on Q (maybe multiple dimensions) and the y would be the composite result [0, 1].
#     # Q_low = 0.1 * u_low   # K_low(u_low)
#     # Q_medium = 0.5 * u_medium   # K_medium(u_medium)
#     # Q_high = 0.9 *u_high   # K_high(u_high)
# 
#     # Bisector
# 
#     # They refer to Q_i x_l, which I understand as the uncertainty for each value for each level
#     # It looks like the uncertainties of all tests of the three levels are groupped and ordered, and the bisector would be the value that would define the half of the area.
#     # Is it x the observed value of hypotetical values?
# 
#     # CQ = bisector(Qs, ...
# 
#     rules = fuzzyfy(data=data, features=features, output=output, require=require)
# 
#     N_out = 100
#     output_range = np.linspace(0, 1, N_out)
#     Q = {}
#     mfuncs = {"smf": smf, "trimf": trimf, "trapmf": trapmf, "zmf": zmf}
#     for m in output:
#         f = mfuncs[output[m]["type"]]
#         Q[m] = f(output_range, output[m]["params"])
# 
#     idx = np.isfinite([rules[r] for r in rules])
#     # This would be the regular fuzzy approach.
#     uncertainty = np.nan * np.ones(np.shape(idx)[1:])
#     valid = np.nonzero(idx.all(axis=0))[0]
#     for i in valid:
#         aggregated = np.zeros(N_out)
#         for m in rules:
#             aggregated = np.fmax(aggregated, np.fmin(rules[m][i], Q[m]))
#         if aggregated.sum() > 0:
#             uncertainty[i] = defuzz(output_range, aggregated, "bisector")
# 
#     return uncertainty
# 
# Path: tests/qctests/compare.py
# def compare_compound_feature_input_types(feature, *args, **kwargs):
#     compare_compound_feature_tuple(feature, *args, **kwargs)
#     compare_compound_feature_dataframe(feature, *args, **kwargs)
#     compare_compound_feature_dataset(feature, *args, **kwargs)


from datetime import timedelta
from hypothesis import given, settings, strategies as st
from hypothesis.extra.numpy import arrays, array_shapes
from numpy.testing import assert_allclose
from cotede.fuzzy import fuzzy_uncertainty
from ..qctests.compare import compare_compound_feature_input_types
import numpy as np
import pytest
    The default is to require all features, thus if anyone is NaN the
    outcome uncertainty will be NaN. If require is defined as any, one
    single valid feature will be enough to define an uncertainty. In both
    cases it returns NaN if all features are NaN.
    """
    features = {
        "f1": np.array([1.0, np.nan, 1.0, 1.0, np.nan]),
        "f2": np.array([5.2, 5.2, np.nan, 5.2, np.nan]),
        "f3": np.array([0.9, 0.9, 0.9, np.nan, np.nan]),
    }

    uncertainty = fuzzy_uncertainty(features, **CFG)
    answer = [0.47474747, np.nan, np.nan, np.nan, np.nan]
    assert_allclose(uncertainty, answer)

    uncertainty = fuzzy_uncertainty(features, **CFG, require="any")
    answer = [0.47474747, 0.44444444, 0.43434343, 0.51515152, np.nan]
    assert_allclose(uncertainty, answer)


@given(
    data=arrays(
        dtype=np.float,
        shape=array_shapes(min_dims=2, max_dims=2, min_side=3),
        elements=st.floats(allow_infinity=True, allow_nan=True),
    )
)
@settings(deadline=timedelta(milliseconds=500))
def test_feature_input_types(data):
    data = {"f1": data[:, 0], "f2": data[:, 1], "f3": data[:, 2]}

# Path: cotede/fuzzy/fuzzy_core.py
# def fuzzy_uncertainty(data, features, output, require="all"):
#     """Estimate the Fuzzy uncertainty of the given data
# 
#     Parameters
#     ----------
#     data :
#     features :
#     output :
#     require : all or any, optional
#         Require all or any of the features to estimate the uncertainty
#     """
#     # It's not clear at Morello 2014 what is the operator K()
#     # Q is the uncertainty, hence Q_low is the low uncertainty
#     # Seems like K() is just a linear factor, which would give the level of uncertainty, like 0.1 for low, 0.5 for medium and 0.9 for high would define weights for each level?! I'm not sure. But the result would be a composite curve, so when the Qs are joinned it would give a curve with the possible values on Q (maybe multiple dimensions) and the y would be the composite result [0, 1].
#     # Q_low = 0.1 * u_low   # K_low(u_low)
#     # Q_medium = 0.5 * u_medium   # K_medium(u_medium)
#     # Q_high = 0.9 *u_high   # K_high(u_high)
# 
#     # Bisector
# 
#     # They refer to Q_i x_l, which I understand as the uncertainty for each value for each level
#     # It looks like the uncertainties of all tests of the three levels are groupped and ordered, and the bisector would be the value that would define the half of the area.
#     # Is it x the observed value of hypotetical values?
# 
#     # CQ = bisector(Qs, ...
# 
#     rules = fuzzyfy(data=data, features=features, output=output, require=require)
# 
#     N_out = 100
#     output_range = np.linspace(0, 1, N_out)
#     Q = {}
#     mfuncs = {"smf": smf, "trimf": trimf, "trapmf": trapmf, "zmf": zmf}
#     for m in output:
#         f = mfuncs[output[m]["type"]]
#         Q[m] = f(output_range, output[m]["params"])
# 
#     idx = np.isfinite([rules[r] for r in rules])
#     # This would be the regular fuzzy approach.
#     uncertainty = np.nan * np.ones(np.shape(idx)[1:])
#     valid = np.nonzero(idx.all(axis=0))[0]
#     for i in valid:
#         aggregated = np.zeros(N_out)
#         for m in rules:
#             aggregated = np.fmax(aggregated, np.fmin(rules[m][i], Q[m]))
#         if aggregated.sum() > 0:
#             uncertainty[i] = defuzz(output_range, aggregated, "bisector")
# 
#     return uncertainty
# 
# Path: tests/qctests/compare.py
# def compare_compound_feature_input_types(feature, *args, **kwargs):
#     compare_compound_feature_tuple(feature, *args, **kwargs)
#     compare_compound_feature_dataframe(feature, *args, **kwargs)
#     compare_compound_feature_dataset(feature, *args, **kwargs)


# Path: tests/fuzzy/test_fuzzy_uncertainty.py
from datetime import timedelta
from hypothesis import given, settings, strategies as st
from hypothesis.extra.numpy import arrays, array_shapes
from numpy.testing import assert_allclose
from cotede.fuzzy import fuzzy_uncertainty
from ..qctests.compare import compare_compound_feature_input_types
import numpy as np
import pytest

    The default is to require all features, thus if anyone is NaN the
    outcome uncertainty will be NaN. If require is defined as any, one
    single valid feature will be enough to define an uncertainty. In both
    cases it returns NaN if all features are NaN.
    """
    features = {
        "f1": np.array([1.0, np.nan, 1.0, 1.0, np.nan]),
        "f2": np.array([5.2, 5.2, np.nan, 5.2, np.nan]),
        "f3": np.array([0.9, 0.9, 0.9, np.nan, np.nan]),
    }

    uncertainty = fuzzy_uncertainty(features, **CFG)
    answer = [0.47474747, np.nan, np.nan, np.nan, np.nan]
    assert_allclose(uncertainty, answer)

    uncertainty = fuzzy_uncertainty(features, **CFG, require="any")
    answer = [0.47474747, 0.44444444, 0.43434343, 0.51515152, np.nan]
    assert_allclose(uncertainty, answer)


@given(
    data=arrays(
        dtype=np.float,
        shape=array_shapes(min_dims=2, max_dims=2, min_side=3),
        elements=st.floats(allow_infinity=True, allow_nan=True),
    )
)
@settings(deadline=timedelta(milliseconds=500))
def test_feature_input_types(data):
    data = {"f1": data[:, 0], "f2": data[:, 1], "f3": data[:, 2]}

    compare_compound_feature_input_types(fuzzy_uncertainty, data=data, **CFG)