""" Active Learning by QUerying Informative and Representative Examples (QUIRE)

This module contains a class that implements an active learning algorithm
(query strategy): QUIRE

"""

import bisect

import numpy as np
from sklearn.metrics.pairwise import *

from libact.base.interfaces import QueryStrategy


class QUIRE(QueryStrategy):

    """Querying Informative and Representative Examples (QUIRE)

    Query the most informative and representative examples where the metrics
    measuring and combining are done using min-max approach.

    Parameters
    ----------
    lambda: float, optional (default=1.0)
        A regularization parameter used in the regularization learning framework.

    kernel : {'linear', 'poly', 'rbf', callable}, optional (default='rbf')
        Specifies the kernel type to be used in the algorithm.
        It must be one of 'linear', 'poly', 'rbf', or a callable.
        If a callable is given it is used to pre-compute the kernel matrix 
        from data matrices; that matrix should be an array of shape 
        ``(n_samples, n_samples)``.

    degree : int, optional (default=3)
        Degree of the polynomial kernel function ('poly').
        Ignored by all other kernels.

    gamma : float, optional (default=1.)
        Kernel coefficient for 'rbf', 'poly'.

    coef0 : float, optional (default=1.)
        Independent term in kernel function.
        It is only significant in 'poly'.


    Attributes
    ----------

    Examples
    --------
    Here is an example of declaring a QUIRE query_strategy object:

    .. code-block:: python

       from libact.query_strategies import QUIRE

       qs = QUIRE(
                dataset, # Dataset object
            )

    References
    ----------
    .. [1] S.-J. Huang, R. Jin, and Z.-H. Zhou. Active learning by querying
           informative and representative examples.
    """

    def __init__(self, *args, **kwargs):
        super(QUIRE, self).__init__(*args, **kwargs)
        self.Uindex = [
            idx for idx, _ in self.dataset.get_unlabeled_entries()
        ]
        self.Lindex = [
            idx for idx in range(len(self.dataset)) if idx not in self.Uindex
        ]
        self.lmbda = kwargs.pop('lambda', 1.)
        X, self.y = zip(*self.dataset.get_entries())
        self.y = list(self.y)
        self.kernel = kwargs.pop('kernel', 'rbf')
        if self.kernel == 'rbf':
            self.K = rbf_kernel(X=X, Y=X, gamma=kwargs.pop('gamma', 1.))
        elif self.kernel == 'poly':
            self.K = polynomial_kernel(X=X, Y=X, coef0=kwargs.pop('coef0', 1),
                                       degree=kwargs.pop('degree', 3), gamma=kwargs.pop('gamma', 1.))
        elif self.kernel == 'linear':
            self.K = linear_kernel(X=X, Y=X)
        elif hasattr(self.kernel, '__call__'):
            self.K = self.kernel(X=np.array(X), Y=np.array(X))
        else:
            raise NotImplementedError

        if not isinstance(self.K, np.ndarray):
            raise TypeError('K should be an ndarray')
        if self.K.shape != (len(X), len(X)):
            raise ValueError(
                'kernel should have size (%d, %d)' % (len(X), len(X)))
        self.L = np.linalg.inv(self.K + self.lmbda * np.eye(len(X)))

    def update(self, entry_id, label):
        bisect.insort(a=self.Lindex, x=entry_id)
        self.Uindex.remove(entry_id)
        self.y[entry_id] = label

    def make_query(self):
        L = self.L
        Lindex = self.Lindex
        Uindex = self.Uindex
        query_index = -1
        min_eva = np.inf
        y_labeled = np.array([label for label in self.y if label is not None])
        det_Laa = np.linalg.det(L[np.ix_(Uindex, Uindex)])
        # efficient computation of inv(Laa)
        M3 = np.dot(self.K[np.ix_(Uindex, Lindex)],
                    np.linalg.inv(self.lmbda * np.eye(len(Lindex))))
        M2 = np.dot(M3, self.K[np.ix_(Lindex, Uindex)])
        M1 = self.lmbda * np.eye(len(Uindex)) + self.K[np.ix_(Uindex, Uindex)]
        inv_Laa = M1 - M2
        iList = list(range(len(Uindex)))
        if len(iList) == 1:
            return Uindex[0]
        for i, each_index in enumerate(Uindex):
            # go through all unlabeled instances and compute their evaluation
            # values one by one
            Uindex_r = Uindex[:]
            Uindex_r.remove(each_index)
            iList_r = iList[:]
            iList_r.remove(i)
            inv_Luu = inv_Laa[np.ix_(iList_r, iList_r)] - 1 / inv_Laa[i, i] * \
                np.dot(inv_Laa[iList_r, i], inv_Laa[iList_r, i].T)
            tmp = np.dot(
                L[each_index][Lindex] -
                np.dot(
                    np.dot(
                        L[each_index][Uindex_r],
                        inv_Luu
                    ),
                    L[np.ix_(Uindex_r, Lindex)]
                ),
                y_labeled,
            )
            eva = L[each_index][each_index] - \
                det_Laa / L[each_index][each_index] + 2 * np.abs(tmp)

            if eva < min_eva:
                query_index = each_index
                min_eva = eva
        return query_index

@@ -249,17 +249,65 @@
  import 
-*
+linear_kernel, polynomial_kernel,%5C%0A    rbf_kernel
 %0A%0Afrom l
@@ -726,16 +726,24 @@
 learning
+%0A       
  framewo
@@ -1025,17 +1025,16 @@
 l matrix
- 
 %0A       
@@ -1093,17 +1093,16 @@
 of shape
- 
 %0A       
@@ -2603,21 +2603,99 @@
 nel(X=X,
- Y=X,
+%0A                                       Y=X,%0A                                      
  coef0=k
@@ -2787,16 +2787,55 @@
 ee', 3),
+%0A                                      
  gamma=k

update quire coding style