# Path: hep_ml/commonutils.py
# def check_sample_weight(y_true, sample_weight):
#     """
#     Checks the weights, returns normalized version
#     :param y_true: numpy.array of shape [n_samples]
#     :param sample_weight: array-like of shape [n_samples] or None
#     :returns: numpy.array with weights of shape [n_samples]"""
#     if sample_weight is None:
#         return numpy.ones(len(y_true), dtype=numpy.float)
#     else:
#         sample_weight = numpy.array(sample_weight, dtype=numpy.float)
#         assert len(y_true) == len(sample_weight), \
#             "The length of weights is different: not {0}, but {1}".format(len(y_true), len(sample_weight))
#         return sample_weight
# 
# def sigmoid_function(x, width):
#     """ Sigmoid function is smoothing of Heaviside function,
#     the less width, the closer we are to Heaviside function
#     :type x: array-like with floats, arbitrary shape
#     :type width: float, if width == 0, this is simply Heaviside function
#     """
#     assert width >= 0, 'the width should be non-negative'
#     if abs(width) > 0.0001:
#         return expit(x / width)
#     else:
#         return (x > 0) * 1.0
# 
# def compute_cut_for_efficiency(efficiency, mask, y_pred, sample_weight=None):
#     """ Computes such cut(s), that provide given signal efficiency.
#     :type efficiency: float or numpy.array with target efficiencies, shape = [n_effs]
#     :type mask: array-like, shape = [n_samples], True for needed classes
#     :type y_pred: array-like, shape = [n_samples], predictions or scores (float)
#     :type sample_weight: None | array-like, shape = [n_samples]
#     :return: float or numpy.array, shape = [n_effs]
#     """
#     sample_weight = check_sample_weight(mask, sample_weight)
#     assert len(mask) == len(y_pred), 'lengths are different'
#     efficiency = numpy.array(efficiency)
#     is_signal = mask > 0.5
#     y_pred, sample_weight = y_pred[is_signal], sample_weight[is_signal]
#     return weighted_percentile(y_pred, 1. - efficiency, sample_weight=sample_weight)


import numpy
from .commonutils import check_sample_weight, sigmoid_function, compute_cut_for_efficiency
from sklearn.utils.validation import column_or_1d
def compute_divided_weight(group_indices, sample_weight):
    """Divided weight takes into account that different events
    are met different number of times """
    indices = numpy.concatenate(group_indices)
    occurences = numpy.bincount(indices, minlength=len(sample_weight))
    return sample_weight / numpy.maximum(occurences, 1)


def compute_group_weights(group_indices, sample_weight):
    """
    Group weight = sum of divided weights of indices inside that group.
    """
    divided_weight = compute_divided_weight(group_indices, sample_weight=sample_weight)
    result = numpy.zeros(len(group_indices))
    for i, group in enumerate(group_indices):
        result[i] = numpy.sum(divided_weight[group])
    return result / numpy.sum(result)


def compute_bin_efficiencies(y_score, bin_indices, cut, sample_weight, minlength=None):
    """Efficiency of bin = total weight of (signal) events that passed the cut
    in the bin / total weight of signal events in the bin.
    Returns small negative number for empty bins"""
    y_score = column_or_1d(y_score)
    assert len(y_score) == len(sample_weight) == len(bin_indices), "different size"
    if minlength is None:
        minlength = numpy.max(bin_indices) + 1

    bin_total = numpy.bincount(bin_indices, weights=sample_weight, minlength=minlength)
    passed_cut = y_score > cut

# Path: hep_ml/commonutils.py
# def check_sample_weight(y_true, sample_weight):
#     """
#     Checks the weights, returns normalized version
#     :param y_true: numpy.array of shape [n_samples]
#     :param sample_weight: array-like of shape [n_samples] or None
#     :returns: numpy.array with weights of shape [n_samples]"""
#     if sample_weight is None:
#         return numpy.ones(len(y_true), dtype=numpy.float)
#     else:
#         sample_weight = numpy.array(sample_weight, dtype=numpy.float)
#         assert len(y_true) == len(sample_weight), \
#             "The length of weights is different: not {0}, but {1}".format(len(y_true), len(sample_weight))
#         return sample_weight
# 
# def sigmoid_function(x, width):
#     """ Sigmoid function is smoothing of Heaviside function,
#     the less width, the closer we are to Heaviside function
#     :type x: array-like with floats, arbitrary shape
#     :type width: float, if width == 0, this is simply Heaviside function
#     """
#     assert width >= 0, 'the width should be non-negative'
#     if abs(width) > 0.0001:
#         return expit(x / width)
#     else:
#         return (x > 0) * 1.0
# 
# def compute_cut_for_efficiency(efficiency, mask, y_pred, sample_weight=None):
#     """ Computes such cut(s), that provide given signal efficiency.
#     :type efficiency: float or numpy.array with target efficiencies, shape = [n_effs]
#     :type mask: array-like, shape = [n_samples], True for needed classes
#     :type y_pred: array-like, shape = [n_samples], predictions or scores (float)
#     :type sample_weight: None | array-like, shape = [n_samples]
#     :return: float or numpy.array, shape = [n_effs]
#     """
#     sample_weight = check_sample_weight(mask, sample_weight)
#     assert len(mask) == len(y_pred), 'lengths are different'
#     efficiency = numpy.array(efficiency)
#     is_signal = mask > 0.5
#     y_pred, sample_weight = y_pred[is_signal], sample_weight[is_signal]
#     return weighted_percentile(y_pred, 1. - efficiency, sample_weight=sample_weight)


# Path: hep_ml/metrics_utils.py
import numpy
from .commonutils import check_sample_weight, sigmoid_function, compute_cut_for_efficiency
from sklearn.utils.validation import column_or_1d

def compute_divided_weight(group_indices, sample_weight):
    """Divided weight takes into account that different events
    are met different number of times """
    indices = numpy.concatenate(group_indices)
    occurences = numpy.bincount(indices, minlength=len(sample_weight))
    return sample_weight / numpy.maximum(occurences, 1)


def compute_group_weights(group_indices, sample_weight):
    """
    Group weight = sum of divided weights of indices inside that group.
    """
    divided_weight = compute_divided_weight(group_indices, sample_weight=sample_weight)
    result = numpy.zeros(len(group_indices))
    for i, group in enumerate(group_indices):
        result[i] = numpy.sum(divided_weight[group])
    return result / numpy.sum(result)


def compute_bin_efficiencies(y_score, bin_indices, cut, sample_weight, minlength=None):
    """Efficiency of bin = total weight of (signal) events that passed the cut
    in the bin / total weight of signal events in the bin.
    Returns small negative number for empty bins"""
    y_score = column_or_1d(y_score)
    assert len(y_score) == len(sample_weight) == len(bin_indices), "different size"
    if minlength is None:
        minlength = numpy.max(bin_indices) + 1

    bin_total = numpy.bincount(bin_indices, weights=sample_weight, minlength=minlength)
    passed_cut = y_score > cut

    bin_passed_cut = numpy.bincount(bin_indices[passed_cut],