"""Bindings for the Barnes Hut TSNE algorithm with fast nearest neighbors

Refs:
References
[1] van der Maaten, L.J.P.; Hinton, G.E. Visualizing High-Dimensional Data
Using t-SNE. Journal of Machine Learning Research 9:2579-2605, 2008.
[2] van der Maaten, L.J.P. t-Distributed Stochastic Neighbor Embedding
http://homepage.tudelft.nl/19j49/t-SNE.html

"""

import numpy as N
import ctypes
import os
import pkg_resources

def ord_string(s):
    b = bytearray()
    arr = b.extend(map(ord, s))
    return N.array([x for x in b] + [0]).astype(N.uint8)

class TSNE(object):
    def __init__(self,
                 n_components=2,
                 perplexity=50.0,
                 early_exaggeration=2.0,
                 learning_rate=200.0,
                 num_neighbors=1023,
                 force_magnify_iters=250,
                 pre_momentum=0.5,
                 post_momentum=0.8,
                 theta=0.5,
                 epssq=0.0025,
                 n_iter=1000,
                 n_iter_without_progress=1000,
                 min_grad_norm=1e-7,
                 perplexity_epsilon=1e-3,
                 metric='euclidean',
                 init='random',
                 return_style='once',
                 num_snapshots=5,
                 verbose=0,
                 random_seed=None,
                 use_interactive=False,
                 viz_timeout=10000,
                 viz_server="tcp://localhost:5556",
                 dump_points=False,
                 dump_file="dump.txt",
                 dump_interval=1,
                 print_interval=10,
                 device=0,
            ):
        """Initialization method for barnes hut T-SNE class.
        """

        # Initialize the variables
        self.n_components = int(n_components)
        if self.n_components != 2:
            raise ValueError('The current barnes-hut implementation does not support projection into dimensions other than 2 for now.')
        self.perplexity = float(perplexity)
        self.early_exaggeration = float(early_exaggeration)
        self.learning_rate = float(learning_rate)
        self.n_iter = int(n_iter)
        self.n_iter_without_progress = int(n_iter_without_progress)
        self.min_grad_norm = float(min_grad_norm)
        if metric not in ['euclidean']:
            raise ValueError('Non-Euclidean metrics are not currently supported. Please use metric=\'euclidean\' for now.')
        else:
            self.metric = metric
        if init not in ['random']:
            raise ValueError('Non-Random initialization is not currently supported. Please use init=\'random\' for now.')
        else:
            self.init = init
        self.verbose = int(verbose)

        # Initialize non-sklearn variables
        self.num_neighbors = int(num_neighbors)
        self.force_magnify_iters = int(force_magnify_iters)
        self.perplexity_epsilon = float(perplexity_epsilon)
        self.pre_momentum = float(pre_momentum)
        self.post_momentum = float(post_momentum)
        self.theta = float(theta)
        self.epssq =float(epssq)
        self.device = int(device)
        self.print_interval = int(print_interval)

        # Point dumpoing
        self.dump_file = str(dump_file)
        self.dump_points = bool(dump_points)
        self.dump_interval = int(dump_interval)

        # Viz
        self.use_interactive = bool(use_interactive)
        self.viz_server = str(viz_server)
        self.viz_timeout = int(viz_timeout)

        # Return style
        if return_style not in ['once','snapshots']:
            raise ValueError('Invalid return style...')
        elif return_style == 'once':
            self.return_style = 0
        elif return_style == 'snapshots':
            self.return_style = 1
        self.num_snapshots = int(num_snapshots)

        # Build the hooks for the BH T-SNE library
        self._path = pkg_resources.resource_filename('tsnecuda','') # Load from current location
        # self._faiss_lib = N.ctypeslib.load_library('libfaiss', self._path) # Load the ctypes library
        # self._gpufaiss_lib = N.ctypeslib.load_library('libgpufaiss', self._path) # Load the ctypes library
        self._lib = N.ctypeslib.load_library('libtsnecuda', self._path) # Load the ctypes library

        # Hook the BH T-SNE function
        self._lib.pymodule_bh_tsne.restype = None
        self._lib.pymodule_bh_tsne.argtypes = [ 
                N.ctypeslib.ndpointer(N.float32, ndim=2, flags='ALIGNED, F_CONTIGUOUS, WRITEABLE'), # result
                N.ctypeslib.ndpointer(N.float32, ndim=2, flags='ALIGNED, CONTIGUOUS'), # points
                ctypes.POINTER(N.ctypeslib.c_intp), # dims
                ctypes.c_float, # Perplexity
                ctypes.c_float, # Learning Rate
                ctypes.c_float, # Magnitude Factor
                ctypes.c_int, # Num Neighbors
                ctypes.c_int, # Iterations
                ctypes.c_int, # Iterations no progress
                ctypes.c_int, # Force Magnify iterations
                ctypes.c_float, # Perplexity search epsilon
                ctypes.c_float, # pre-exaggeration momentum
                ctypes.c_float, # post-exaggeration momentum
                ctypes.c_float, # Theta
                ctypes.c_float, # epssq
                ctypes.c_float, # Minimum gradient norm
                ctypes.c_int, # Initialization types
                N.ctypeslib.ndpointer(N.float32, ndim=2, flags='ALIGNED, F_CONTIGUOUS'), # Initialization Data
                ctypes.c_bool, # Dump points
                N.ctypeslib.ndpointer(N.uint8, flags='ALIGNED, CONTIGUOUS'), # Dump File
                ctypes.c_int, # Dump interval
                ctypes.c_bool, # Use interactive
                N.ctypeslib.ndpointer(N.uint8, flags='ALIGNED, CONTIGUOUS'), # Viz Server
                ctypes.c_int, # Viz timeout
                ctypes.c_int, # Verbosity
                ctypes.c_int, # Print interval
                ctypes.c_int, # GPU Device
                ctypes.c_int, # Return style
                ctypes.c_int ] # Number of snapshots

    def fit_transform(self, X, y=None):
        """Fit X into an embedded space and return that transformed output.
        
        Arguments:
            X {array} -- Input array, shape: (n_points, n_dimensions) 
        
        Keyword Arguments:
            y {None} -- Ignored (default: {None})
        """

        # Setup points/embedding requirements
        self.points = N.require(X, N.float32, ['CONTIGUOUS', 'ALIGNED'])
        self.embedding = N.zeros(shape=(X.shape[0],self.n_components))
        self.embedding = N.require(self.embedding , N.float32, ['F_CONTIGUOUS', 'ALIGNED', 'WRITEABLE'])

        # Handle Initialization
        if y is None:
            self.initialization_type = 1
            self.init_data = N.require(N.zeros((1,1)),N.float32,['CONTIGUOUS','ALIGNED'])
        else:
            self.initialization_type = 3
            self.init_data = N.require(y, N.float32, ['F_CONTIGUOUS', 'ALIGNED'])

        # Handle dumping and viz strings
        self.dump_file_ = N.require(ord_string(self.dump_file), N.uint8, ['CONTIGUOUS', 'ALIGNED'])
        self.viz_server_ = N.require(ord_string(self.viz_server), N.uint8, ['CONTIGUOUS', 'ALIGNED'])


        self._lib.pymodule_bh_tsne(
                self.embedding, # result
                self.points, # points
                self.points.ctypes.shape, # dims
                ctypes.c_float(self.perplexity), # Perplexity
                ctypes.c_float(self.learning_rate), # Learning Rate
                ctypes.c_float(self.early_exaggeration), # Magnitude Factor
                ctypes.c_int(self.num_neighbors), # Num Neighbors
                ctypes.c_int(self.n_iter), # Iterations
                ctypes.c_int(self.n_iter_without_progress), # Iterations no progress
                ctypes.c_int(self.force_magnify_iters), # Force Magnify iterations
                ctypes.c_float(self.perplexity_epsilon), # Perplexity search epsilon
                ctypes.c_float(self.pre_momentum), # pre-exaggeration momentum
                ctypes.c_float(self.post_momentum), # post-exaggeration momentum
                ctypes.c_float(self.theta), # Theta
                ctypes.c_float(self.epssq), # epssq
                ctypes.c_float(self.min_grad_norm), # Minimum gradient norm
                ctypes.c_int(self.initialization_type), # Initialization types
                self.init_data, # Initialization Data
                ctypes.c_bool(self.dump_points), # Dump points
                self.dump_file_, # Dump File
                ctypes.c_int(self.dump_interval), # Dump interval
                ctypes.c_bool(self.use_interactive), # Use interactive
                self.viz_server_, # Viz Server
                ctypes.c_int(self.viz_timeout), # Viz timeout
                ctypes.c_int(self.verbose), # Verbosity
                ctypes.c_int(self.print_interval), # Print interval
                ctypes.c_int(self.device), # GPU Device
                ctypes.c_int(self.return_style), # Return style
                ctypes.c_int(self.num_snapshots) ) # Number of snapshots

        return self.embedding




        






[((3877, 3924), 'pkg_resources.resource_filename', 'pkg_resources.resource_filename', (['"""tsnecuda"""', '""""""'], {}), "('tsnecuda', '')\n", (3908, 3924), False, 'import pkg_resources\n'), ((4185, 4236), 'numpy.ctypeslib.load_library', 'N.ctypeslib.load_library', (['"""libtsnecuda"""', 'self._path'], {}), "('libtsnecuda', self._path)\n", (4209, 4236), True, 'import numpy as N\n'), ((6466, 6516), 'numpy.require', 'N.require', (['X', 'N.float32', "['CONTIGUOUS', 'ALIGNED']"], {}), "(X, N.float32, ['CONTIGUOUS', 'ALIGNED'])\n", (6475, 6516), True, 'import numpy as N\n'), ((6542, 6588), 'numpy.zeros', 'N.zeros', ([], {'shape': '(X.shape[0], self.n_components)'}), '(shape=(X.shape[0], self.n_components))\n', (6549, 6588), True, 'import numpy as N\n'), ((6613, 6691), 'numpy.require', 'N.require', (['self.embedding', 'N.float32', "['F_CONTIGUOUS', 'ALIGNED', 'WRITEABLE']"], {}), "(self.embedding, N.float32, ['F_CONTIGUOUS', 'ALIGNED', 'WRITEABLE'])\n", (6622, 6691), True, 'import numpy as N\n'), ((503, 532), 'numpy.array', 'N.array', (['([x for x in b] + [0])'], {}), '([x for x in b] + [0])\n', (510, 532), True, 'import numpy as N\n'), ((4416, 4503), 'numpy.ctypeslib.ndpointer', 'N.ctypeslib.ndpointer', (['N.float32'], {'ndim': '(2)', 'flags': '"""ALIGNED, F_CONTIGUOUS, WRITEABLE"""'}), "(N.float32, ndim=2, flags=\n    'ALIGNED, F_CONTIGUOUS, WRITEABLE')\n", (4437, 4503), True, 'import numpy as N\n'), ((4525, 4594), 'numpy.ctypeslib.ndpointer', 'N.ctypeslib.ndpointer', (['N.float32'], {'ndim': '(2)', 'flags': '"""ALIGNED, CONTIGUOUS"""'}), "(N.float32, ndim=2, flags='ALIGNED, CONTIGUOUS')\n", (4546, 4594), True, 'import numpy as N\n'), ((4621, 4655), 'ctypes.POINTER', 'ctypes.POINTER', (['N.ctypeslib.c_intp'], {}), '(N.ctypeslib.c_intp)\n', (4635, 4655), False, 'import ctypes\n'), ((5395, 5466), 'numpy.ctypeslib.ndpointer', 'N.ctypeslib.ndpointer', (['N.float32'], {'ndim': '(2)', 'flags': '"""ALIGNED, F_CONTIGUOUS"""'}), "(N.float32, ndim=2, flags='ALIGNED, F_CONTIGUOUS')\n", (5416, 5466), True, 'import numpy as N\n'), ((5551, 5610), 'numpy.ctypeslib.ndpointer', 'N.ctypeslib.ndpointer', (['N.uint8'], {'flags': '"""ALIGNED, CONTIGUOUS"""'}), "(N.uint8, flags='ALIGNED, CONTIGUOUS')\n", (5572, 5610), True, 'import numpy as N\n'), ((5735, 5794), 'numpy.ctypeslib.ndpointer', 'N.ctypeslib.ndpointer', (['N.uint8'], {'flags': '"""ALIGNED, CONTIGUOUS"""'}), "(N.uint8, flags='ALIGNED, CONTIGUOUS')\n", (5756, 5794), True, 'import numpy as N\n'), ((6963, 7015), 'numpy.require', 'N.require', (['y', 'N.float32', "['F_CONTIGUOUS', 'ALIGNED']"], {}), "(y, N.float32, ['F_CONTIGUOUS', 'ALIGNED'])\n", (6972, 7015), True, 'import numpy as N\n'), ((7442, 7473), 'ctypes.c_float', 'ctypes.c_float', (['self.perplexity'], {}), '(self.perplexity)\n', (7456, 7473), False, 'import ctypes\n'), ((7504, 7538), 'ctypes.c_float', 'ctypes.c_float', (['self.learning_rate'], {}), '(self.learning_rate)\n', (7518, 7538), False, 'import ctypes\n'), ((7572, 7611), 'ctypes.c_float', 'ctypes.c_float', (['self.early_exaggeration'], {}), '(self.early_exaggeration)\n', (7586, 7611), False, 'import ctypes\n'), ((7648, 7680), 'ctypes.c_int', 'ctypes.c_int', (['self.num_neighbors'], {}), '(self.num_neighbors)\n', (7660, 7680), False, 'import ctypes\n'), ((7714, 7739), 'ctypes.c_int', 'ctypes.c_int', (['self.n_iter'], {}), '(self.n_iter)\n', (7726, 7739), False, 'import ctypes\n'), ((7770, 7812), 'ctypes.c_int', 'ctypes.c_int', (['self.n_iter_without_progress'], {}), '(self.n_iter_without_progress)\n', (7782, 7812), False, 'import ctypes\n'), ((7855, 7893), 'ctypes.c_int', 'ctypes.c_int', (['self.force_magnify_iters'], {}), '(self.force_magnify_iters)\n', (7867, 7893), False, 'import ctypes\n'), ((7938, 7977), 'ctypes.c_float', 'ctypes.c_float', (['self.perplexity_epsilon'], {}), '(self.perplexity_epsilon)\n', (7952, 7977), False, 'import ctypes\n'), ((8023, 8056), 'ctypes.c_float', 'ctypes.c_float', (['self.pre_momentum'], {}), '(self.pre_momentum)\n', (8037, 8056), False, 'import ctypes\n'), ((8102, 8136), 'ctypes.c_float', 'ctypes.c_float', (['self.post_momentum'], {}), '(self.post_momentum)\n', (8116, 8136), False, 'import ctypes\n'), ((8183, 8209), 'ctypes.c_float', 'ctypes.c_float', (['self.theta'], {}), '(self.theta)\n', (8197, 8209), False, 'import ctypes\n'), ((8235, 8261), 'ctypes.c_float', 'ctypes.c_float', (['self.epssq'], {}), '(self.epssq)\n', (8249, 8261), False, 'import ctypes\n'), ((8287, 8321), 'ctypes.c_float', 'ctypes.c_float', (['self.min_grad_norm'], {}), '(self.min_grad_norm)\n', (8301, 8321), False, 'import ctypes\n'), ((8363, 8401), 'ctypes.c_int', 'ctypes.c_int', (['self.initialization_type'], {}), '(self.initialization_type)\n', (8375, 8401), False, 'import ctypes\n'), ((8496, 8527), 'ctypes.c_bool', 'ctypes.c_bool', (['self.dump_points'], {}), '(self.dump_points)\n', (8509, 8527), False, 'import ctypes\n'), ((8604, 8636), 'ctypes.c_int', 'ctypes.c_int', (['self.dump_interval'], {}), '(self.dump_interval)\n', (8616, 8636), False, 'import ctypes\n'), ((8670, 8705), 'ctypes.c_bool', 'ctypes.c_bool', (['self.use_interactive'], {}), '(self.use_interactive)\n', (8683, 8705), False, 'import ctypes\n'), ((8788, 8818), 'ctypes.c_int', 'ctypes.c_int', (['self.viz_timeout'], {}), '(self.viz_timeout)\n', (8800, 8818), False, 'import ctypes\n'), ((8850, 8876), 'ctypes.c_int', 'ctypes.c_int', (['self.verbose'], {}), '(self.verbose)\n', (8862, 8876), False, 'import ctypes\n'), ((8906, 8939), 'ctypes.c_int', 'ctypes.c_int', (['self.print_interval'], {}), '(self.print_interval)\n', (8918, 8939), False, 'import ctypes\n'), ((8974, 8999), 'ctypes.c_int', 'ctypes.c_int', (['self.device'], {}), '(self.device)\n', (8986, 8999), False, 'import ctypes\n'), ((9030, 9061), 'ctypes.c_int', 'ctypes.c_int', (['self.return_style'], {}), '(self.return_style)\n', (9042, 9061), False, 'import ctypes\n'), ((9094, 9126), 'ctypes.c_int', 'ctypes.c_int', (['self.num_snapshots'], {}), '(self.num_snapshots)\n', (9106, 9126), False, 'import ctypes\n'), ((6828, 6843), 'numpy.zeros', 'N.zeros', (['(1, 1)'], {}), '((1, 1))\n', (6835, 6843), True, 'import numpy as N\n')]