from pylab import argmin, argmax, tile, unique, argwhere, array, mean, \
        newaxis, dot, pi, e, matrix
from svm import svm_model, svm_problem, svm_parameter, LINEAR

class NNb:
    """Nearest neighbour classifier."""
    def __init__(self, X, c):
        self.n, self.N = X.shape
        self.X, self.c = X, c

    def classify(self, x):
        d = self.X - tile(x.reshape(self.n, 1), self.N)
        dsq = sum(d*d, 0)
        return self.c[argmin(dsq)]

class kNNb:
    """k-Nearest neighbour classifier."""
    def __init__(self, X, c, k):
        self.n, self.N = X.shape
        self.X, self.c, self.k = X, c, k

    def classify(self, x):
        d = self.X - tile(x.reshape(self.n, 1), self.N)
        dsq = sum(d*d, 0)
        minindices = dsq.argsort()
        # Count class occurrences in k nearest neighbours
        hist = dict([(c, 1) for c in self.c[minindices[:self.k]]])
        for c in self.c[minindices[:self.k]]:
            hist[c] += 1
        # Return the majority class
        max_nbb = (0, None)
        for c, count in hist.iteritems():
            if count > max_nbb[0]:
                max_nnb = (count, c)
        return max_nnb[1]

class MEC:
    """Minimum error classifier."""
    def __init__(self, X, c):
        self.n, self.N = X.shape
        self.X, self.c = X, c
        self.classes = unique(c)
        self.estimate()

    def estimate(self):
        """Estimate the mean and covariance matrix for each class in the
        learning set."""
        self.class_data = []
        for c in self.classes:
            indices = argwhere(array(map(lambda x: x if x == c else 0,
                self.c))).T[0]
            X = self.X[:,indices]
            mu = mean(X, 1)
            Yzm = X - tile(mu[:,newaxis], X.shape[1])
            S = matrix(dot(Yzm, Yzm.T) / (self.n - 1))
            # Calculate the coefficient needed for the calculation in
            # classify(). This is just an optimization, because only the
            # covariance matrix is needed for the coefficient, and not the
            # vector that is being classified itself.
            coeff = 1 / (S.A**-.5 * (2 * pi)**(self.n / 2))
            self.class_data.append((mu, S, coeff))

    def classify(self, x):
        """Use the sum of all entries in the pdf matrix to determine the class
        with the greatest probability."""
        p = [coeff * e**(-.5 * dot(x - mu, dot(S.I, array([x -
            mu]).T)).tolist()[0][0]) for mu, S, coeff in self.class_data]
        return self.classes[argmax([i.sum() for i in p])]

class SVM:
    """Support vector machine classifier."""
    def __init__(self, X, c):
        self.model = svm_model(svm_problem(c, X.T),
                svm_parameter(kernel_type=LINEAR))

    def classify(self, x):
        return self.model.predict(x)
