# Path: core/kover/utils.py
# def _minimum_uint_size(max_value):
#     """
#     Find the minimum size unsigned integer type that can store values of at most max_value
#     """
#     if max_value <= np.iinfo(np.uint8).max:
#         return np.uint8
#     elif max_value <= np.iinfo(np.uint16).max:
#         return np.uint16
#     elif max_value <= np.iinfo(np.uint32).max:
#         return np.uint32
#     elif max_value <= np.iinfo(np.uint64).max:
#         return np.uint64
#     else:
#         return np.uint128
# 
# def _unpack_binary_bytes_from_ints(a):
#     """
#     Unpacks binary values stored in bytes into ints
#     """
#     type = a.dtype
# 
#     if type == np.uint32:
#         pack_size = 32
#     elif type == np.uint64:
#         pack_size = 64
#     else:
#         raise ValueError("Supported data types are 32-bit and 64-bit integers.")
# 
#     unpacked_n_rows = a.shape[0] * pack_size
#     unpacked_n_columns = a.shape[1] if len(a.shape) > 1 else 1
#     b = np.zeros((unpacked_n_rows, a.shape[1]) if len(a.shape) > 1 else unpacked_n_rows, dtype=np.uint8)
# 
#     packed_rows = 0
#     packing_row = 0
#     for i in xrange(b.shape[0]):
#         if packed_rows == pack_size:
#             packed_rows = 0
#             packing_row += 1
#         tmp = np.left_shift(np.ones(unpacked_n_columns, dtype=type), pack_size - (i - pack_size * packing_row)-1)
#         np.bitwise_and(a[packing_row], tmp, tmp)
#         b[i] = tmp > 0
#         packed_rows += 1
# 
#     return b


import numpy as np
from math import ceil
from .popcount import inplace_popcount_32, inplace_popcount_64
from ...utils import _minimum_uint_size, _unpack_binary_bytes_from_ints
        # TODO: This is inefficient!
        for row_idx in rows:
            current_idx = -1
            n_active_elements_seen = 0
            while n_active_elements_seen <= row_idx:
                current_idx += 1
                if not self.dataset_removed_rows_mask[current_idx]:
                    n_active_elements_seen += 1
            dataset_removed_rows.append(current_idx)
        # Update the dataset removed row lists
        # Update the start and stop indexes
        # Adjust the shape
        # Adjust the number of rows in each dataset
        # Store the sorted relative removed row indexes by dataset
        if len(dataset_removed_rows) > 0:
            self.dataset_removed_rows = sorted(set(self.dataset_removed_rows + dataset_removed_rows))
            self.dataset_removed_rows_mask = np.zeros(self.dataset_initial_n_rows, dtype=np.bool)
            self.dataset_removed_rows_mask[self.dataset_removed_rows] = True
            self.dataset_n_rows = self.dataset_initial_n_rows - len(self.dataset_removed_rows)

    @property
    def shape(self):
        return self.dataset_n_rows, self.dataset.shape[1] * 2

    # TODO: allow summing over multiple lists of rows at a time (saves i/o operations)
    def sum_rows(self, rows):
        """
        Note: Assumes that the rows argument does not contain duplicate elements. Rows will not be considered more than once.
        """
        rows = np.asarray(rows)

# Path: core/kover/utils.py
# def _minimum_uint_size(max_value):
#     """
#     Find the minimum size unsigned integer type that can store values of at most max_value
#     """
#     if max_value <= np.iinfo(np.uint8).max:
#         return np.uint8
#     elif max_value <= np.iinfo(np.uint16).max:
#         return np.uint16
#     elif max_value <= np.iinfo(np.uint32).max:
#         return np.uint32
#     elif max_value <= np.iinfo(np.uint64).max:
#         return np.uint64
#     else:
#         return np.uint128
# 
# def _unpack_binary_bytes_from_ints(a):
#     """
#     Unpacks binary values stored in bytes into ints
#     """
#     type = a.dtype
# 
#     if type == np.uint32:
#         pack_size = 32
#     elif type == np.uint64:
#         pack_size = 64
#     else:
#         raise ValueError("Supported data types are 32-bit and 64-bit integers.")
# 
#     unpacked_n_rows = a.shape[0] * pack_size
#     unpacked_n_columns = a.shape[1] if len(a.shape) > 1 else 1
#     b = np.zeros((unpacked_n_rows, a.shape[1]) if len(a.shape) > 1 else unpacked_n_rows, dtype=np.uint8)
# 
#     packed_rows = 0
#     packing_row = 0
#     for i in xrange(b.shape[0]):
#         if packed_rows == pack_size:
#             packed_rows = 0
#             packing_row += 1
#         tmp = np.left_shift(np.ones(unpacked_n_columns, dtype=type), pack_size - (i - pack_size * packing_row)-1)
#         np.bitwise_and(a[packing_row], tmp, tmp)
#         b[i] = tmp > 0
#         packed_rows += 1
# 
#     return b


# Path: core/kover/learning/common/rules.py
import numpy as np
from math import ceil
from .popcount import inplace_popcount_32, inplace_popcount_64
from ...utils import _minimum_uint_size, _unpack_binary_bytes_from_ints

        # TODO: This is inefficient!
        for row_idx in rows:
            current_idx = -1
            n_active_elements_seen = 0
            while n_active_elements_seen <= row_idx:
                current_idx += 1
                if not self.dataset_removed_rows_mask[current_idx]:
                    n_active_elements_seen += 1
            dataset_removed_rows.append(current_idx)
        # Update the dataset removed row lists
        # Update the start and stop indexes
        # Adjust the shape
        # Adjust the number of rows in each dataset
        # Store the sorted relative removed row indexes by dataset
        if len(dataset_removed_rows) > 0:
            self.dataset_removed_rows = sorted(set(self.dataset_removed_rows + dataset_removed_rows))
            self.dataset_removed_rows_mask = np.zeros(self.dataset_initial_n_rows, dtype=np.bool)
            self.dataset_removed_rows_mask[self.dataset_removed_rows] = True
            self.dataset_n_rows = self.dataset_initial_n_rows - len(self.dataset_removed_rows)

    @property
    def shape(self):
        return self.dataset_n_rows, self.dataset.shape[1] * 2

    # TODO: allow summing over multiple lists of rows at a time (saves i/o operations)
    def sum_rows(self, rows):
        """
        Note: Assumes that the rows argument does not contain duplicate elements. Rows will not be considered more than once.
        """
        rows = np.asarray(rows)

        result_dtype = _minimum_uint_size(rows.shape[0])