"""This file defines  one class : sort_vectors_mc
   and a test function to this class.
   A demonstration file is available.
"""

# Copyright (c) 2001 LOGILAB S.A. (Paris, FRANCE).
# http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

class sort_vectors_mc:
    """
    This class sorts input vectors to output indice tree.
    Sorting uses mobils centrals method.
    """
    def __init__(self, all_vect, nb_class, debug=0):
        self.debug = debug
        self.all_vect = all_vect      # vector set
        self.nb_class = nb_class      # number of class
        self.classes = []             # new vectors classes
        if self.nb_class > len(self.all_vect) or self.nb_class <= 1:
            self.nb_class = int(len(self.all_vect)/float(2) + 1)
            if self.debug:
                print "nb_class don't correspond to number of vectors"
                print " => nb_class reinit to ", self.nb_class
    
    
    def do(self) :
        """
        Do the class, after initialize
        This fct returns the indice tree
        This fct uses mobils centrals method
        """
        
        if len(self.all_vect) <= 1:
            if len(self.all_vect) == 1:
                output = [0]
            else:
                output = []
            return []
        
        # Init nb centrals points
        # (Searching  self.nb_class  vectors  in  self.all_vect)
        centrals_points = []
        if self.nb_class == 1:
            unit = 1
        else:
            unit = (len(self.all_vect)-1)/(float(self.nb_class-1))
        if self.debug:
            print '\nStarting central point: '
        for i in range(self.nb_class):
            if self.debug:
                print i, ' ', int(i*unit), ':', self.all_vect[int(i*unit)][0]
            centrals_points.append(self.all_vect[int(i*unit)][1])
        
        # Iteration while classements are different
        oldclasses = None
        while oldclasses != self.classes:
            if self.debug:
                print '--'*35
                print '\noldclasses != self.classes'
            oldclasses = self.classes
            centrals_points = self._search_class(centrals_points)
        return self.classes



    def _search_class(self, cent_pts):
        """
        This fct searches for each vector is nearest of class
        Algo:
             init classes
             init gravite centres
             foreach vector in all_vector:
                 Search what central point is nearest with this vector
                 add this vector in central point class
             foreach point in central point
                 foreach vector in central point class:
                     foreach word in vector
                         perform gravity centre
        """
        import random
        # init classes
        self.classes = []
        for i in range(len(cent_pts)):
            self.classes.append([])
        
        # init gravite centres
        grav_cent = []
        for i in range(len(cent_pts)):
            grav_cent.append({})
        
        for i in range(len(self.all_vect)):
            # Search what central point is nearest with this vector
            new_sim = None
            for j in range(len(cent_pts)):
                temp_sim = self._calc_sim_vect(self.all_vect[i][1],
                                               cent_pts[j])
                if self.debug:
                    print 'Sum', i, j, temp_sim
                    ## print cent_pts[j]
                    ## print self.all_vect[i][1]
                if new_sim == None or new_sim < temp_sim:
                    new_sim = temp_sim
                    nearest_pt_ref = j
            if self.debug:
                print 'Vecteur', i
                print 'cent_pts', nearest_pt_ref, '\n'
            # add this vector in central point class
            self.classes[nearest_pt_ref].append(i)
        
        # perform a gravity centre
        for i in range(len(cent_pts)):
            for ref in self.classes[i]:
                for word in self.all_vect[ref][1].keys():
                    try:
                        grav_cent[i][word] = \
                         grav_cent[i][word] + \
                      (self.all_vect[ref][1][word]/float(len(self.classes[i])))
                    except:
                         # first time
                         grav_cent[i][word] = \
                        self.all_vect[ref][1][word]/float(len(self.classes[i]))
            if len(self.classes[i]) == 0:
                max = None
                for j in range(len(cent_pts)):
                    if max == None or max < len(self.classes[i]):
                        max = len(self.classes[i])
                if len(self.classes[max]) != 0:
                    ref = random.randint(0, len(self.classes[max])-1)
                    grav_cent[i] = self.all_vect[self.classes[max][ref]][1]
                
        if self.debug:
            print 'self.classes', self.classes

        return grav_cent


    def _calc_sim_vect(self, vect1, vect2):
        """
        Calculate the dot product between two vectors.
        """
        sum = 0
        for key in vect1.keys():
            vect2_key = vect2.get(key, 0)
            if vect2_key:
                sum = sum + vect2_key*vect1[key]
                if self.debug:
                    print key, '   ', vect1[key], '           ', vect2[key]
        return sum
