# -*- coding: utf-8 -*-

# Copyright (c) 2013 Ole Krause-Sparmann

# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

import numpy
import scipy
import unittest
import time

from nearpy import Engine
from nearpy.distances import CosineDistance

from nearpy.hashes import RandomBinaryProjections, HashPermutations, HashPermutationMapper

def example2():

    # Dimension of feature space
    DIM = 100

    # Number of data points (dont do too much because of exact search)
    POINTS = 20000

    ##########################################################

    print('Performing indexing with HashPermutations...')
    t0 = time.time()

    # Create permutations meta-hash
    permutations = HashPermutations('permut')

    # Create binary hash as child hash
    rbp_perm = RandomBinaryProjections('rbp_perm', 14)
    rbp_conf = {'num_permutation':50,'beam_size':10,'num_neighbour':100}

    # Add rbp as child hash of permutations hash
    permutations.add_child_hash(rbp_perm, rbp_conf)

    # Create engine
    engine_perm = Engine(DIM, lshashes=[permutations], distance=CosineDistance())

    # First index some random vectors
    matrix = numpy.zeros((POINTS,DIM))
    for i in range(POINTS):
        v = numpy.random.randn(DIM)
        matrix[i] = v
        engine_perm.store_vector(v)

    # Then update permuted index
    permutations.build_permuted_index()

    t1 = time.time()
    print('Indexing took %f seconds' % (t1-t0))

    # Get random query vector
    query = numpy.random.randn(DIM)

    # Do random query on engine 3
    print('\nNeighbour distances with HashPermutations:')
    print('  -> Candidate count is %d' % engine_perm.candidate_count(query))
    results = engine_perm.neighbours(query)
    dists = [x[2] for x in results]
    print(dists)

    # Real neighbours
    print('\nReal neighbour distances:')
    query = query.reshape((DIM))
    dists = CosineDistance().distance(matrix, query)
    dists = dists.reshape((-1,))
    dists = sorted(dists)
    print(dists[:10])

    ##########################################################

    print('\nPerforming indexing with HashPermutationMapper...')
    t0 = time.time()

    # Create permutations meta-hash
    permutations2 = HashPermutationMapper('permut2')

    # Create binary hash as child hash
    rbp_perm2 = RandomBinaryProjections('rbp_perm2', 14)

    # Add rbp as child hash of permutations hash
    permutations2.add_child_hash(rbp_perm2)

    # Create engine
    engine_perm2 = Engine(DIM, lshashes=[permutations2], distance=CosineDistance())

    # First index some random vectors
    matrix = numpy.zeros((POINTS,DIM))
    for i in range(POINTS):
        v = numpy.random.randn(DIM)
        matrix[i] = v
        engine_perm2.store_vector(v)

    t1 = time.time()
    print('Indexing took %f seconds' % (t1-t0))

    # Get random query vector
    query = numpy.random.randn(DIM)

    # Do random query on engine 4
    print('\nNeighbour distances with HashPermutationMapper:')
    print('  -> Candidate count is %d' % engine_perm2.candidate_count(query))
    results = engine_perm2.neighbours(query)
    dists = [x[2] for x in results]
    print(dists)

    # Real neighbours
    print('\nReal neighbour distances:')
    query = query.reshape((DIM))
    dists = CosineDistance().distance(matrix,query)
    dists = dists.reshape((-1,))
    dists = sorted(dists)
    print(dists[:10])

    ##########################################################

    print('\nPerforming indexing with multiple binary hashes...')
    t0 = time.time()

    hashes = []
    for k in range(20):
        hashes.append(RandomBinaryProjections('rbp_%d' % k, 10))

    # Create engine
    engine_rbps = Engine(DIM, lshashes=hashes, distance=CosineDistance())

    # First index some random vectors
    matrix = numpy.zeros((POINTS,DIM))
    for i in range(POINTS):
        v = numpy.random.randn(DIM)
        matrix[i] = v
        engine_rbps.store_vector(v)

    t1 = time.time()
    print('Indexing took %f seconds' % (t1-t0))

    # Get random query vector
    query = numpy.random.randn(DIM)

    # Do random query on engine 4
    print('\nNeighbour distances with multiple binary hashes:')
    print('  -> Candidate count is %d' % engine_rbps.candidate_count(query))
    results = engine_rbps.neighbours(query)
    dists = [x[2] for x in results]
    print(dists)

    # Real neighbours
    print('\nReal neighbour distances:')
    query = query.reshape((DIM))
    dists = CosineDistance().distance(matrix,query)
    dists = dists.reshape((-1,))
    dists = sorted(dists)
    print(dists[:10])

    ##########################################################

[((44, 9, 44, 20), 'time.time', 'time.time', ({}, {}), '()', False, 'import time\n'), ((47, 19, 47, 45), 'nearpy.hashes.HashPermutations', 'HashPermutations', ({(47, 36, 47, 44): '"""permut"""'}, {}), "('permut')", False, 'from nearpy.hashes import RandomBinaryProjections, HashPermutations, HashPermutationMapper\n'), ((50, 15, 50, 54), 'nearpy.hashes.RandomBinaryProjections', 'RandomBinaryProjections', ({(50, 39, 50, 49): '"""rbp_perm"""', (50, 51, 50, 53): '14'}, {}), "('rbp_perm', 14)", False, 'from nearpy.hashes import RandomBinaryProjections, HashPermutations, HashPermutationMapper\n'), ((60, 13, 60, 38), 'numpy.zeros', 'numpy.zeros', ({(60, 25, 60, 37): '(POINTS, DIM)'}, {}), '((POINTS, DIM))', False, 'import numpy\n'), ((69, 9, 69, 20), 'time.time', 'time.time', ({}, {}), '()', False, 'import time\n'), ((73, 12, 73, 35), 'numpy.random.randn', 'numpy.random.randn', ({(73, 31, 73, 34): 'DIM'}, {}), '(DIM)', False, 'import numpy\n'), ((93, 9, 93, 20), 'time.time', 'time.time', ({}, {}), '()', False, 'import time\n'), ((96, 20, 96, 52), 'nearpy.hashes.HashPermutationMapper', 'HashPermutationMapper', ({(96, 42, 96, 51): '"""permut2"""'}, {}), "('permut2')", False, 'from nearpy.hashes import RandomBinaryProjections, HashPermutations, HashPermutationMapper\n'), ((99, 16, 99, 56), 'nearpy.hashes.RandomBinaryProjections', 'RandomBinaryProjections', ({(99, 40, 99, 51): '"""rbp_perm2"""', (99, 53, 99, 55): '14'}, {}), "('rbp_perm2', 14)", False, 'from nearpy.hashes import RandomBinaryProjections, HashPermutations, HashPermutationMapper\n'), ((108, 13, 108, 38), 'numpy.zeros', 'numpy.zeros', ({(108, 25, 108, 37): '(POINTS, DIM)'}, {}), '((POINTS, DIM))', False, 'import numpy\n'), ((114, 9, 114, 20), 'time.time', 'time.time', ({}, {}), '()', False, 'import time\n'), ((118, 12, 118, 35), 'numpy.random.randn', 'numpy.random.randn', ({(118, 31, 118, 34): 'DIM'}, {}), '(DIM)', False, 'import numpy\n'), ((138, 9, 138, 20), 'time.time', 'time.time', ({}, {}), '()', False, 'import time\n'), ((148, 13, 148, 38), 'numpy.zeros', 'numpy.zeros', ({(148, 25, 148, 37): '(POINTS, DIM)'}, {}), '((POINTS, DIM))', False, 'import numpy\n'), ((154, 9, 154, 20), 'time.time', 'time.time', ({}, {}), '()', False, 'import time\n'), ((158, 12, 158, 35), 'numpy.random.randn', 'numpy.random.randn', ({(158, 31, 158, 34): 'DIM'}, {}), '(DIM)', False, 'import numpy\n'), ((62, 12, 62, 35), 'numpy.random.randn', 'numpy.random.randn', ({(62, 31, 62, 34): 'DIM'}, {}), '(DIM)', False, 'import numpy\n'), ((110, 12, 110, 35), 'numpy.random.randn', 'numpy.random.randn', ({(110, 31, 110, 34): 'DIM'}, {}), '(DIM)', False, 'import numpy\n'), ((150, 12, 150, 35), 'numpy.random.randn', 'numpy.random.randn', ({(150, 31, 150, 34): 'DIM'}, {}), '(DIM)', False, 'import numpy\n'), ((57, 64, 57, 80), 'nearpy.distances.CosineDistance', 'CosineDistance', ({}, {}), '()', False, 'from nearpy.distances import CosineDistance\n'), ((85, 12, 85, 28), 'nearpy.distances.CosineDistance', 'CosineDistance', ({}, {}), '()', False, 'from nearpy.distances import CosineDistance\n'), ((105, 66, 105, 82), 'nearpy.distances.CosineDistance', 'CosineDistance', ({}, {}), '()', False, 'from nearpy.distances import CosineDistance\n'), ((130, 12, 130, 28), 'nearpy.distances.CosineDistance', 'CosineDistance', ({}, {}), '()', False, 'from nearpy.distances import CosineDistance\n'), ((142, 22, 142, 63), 'nearpy.hashes.RandomBinaryProjections', 'RandomBinaryProjections', ({(142, 46, 142, 58): "('rbp_%d' % k)", (142, 60, 142, 62): '(10)'}, {}), "('rbp_%d' % k, 10)", False, 'from nearpy.hashes import RandomBinaryProjections, HashPermutations, HashPermutationMapper\n'), ((145, 56, 145, 72), 'nearpy.distances.CosineDistance', 'CosineDistance', ({}, {}), '()', False, 'from nearpy.distances import CosineDistance\n'), ((170, 12, 170, 28), 'nearpy.distances.CosineDistance', 'CosineDistance', ({}, {}), '()', False, 'from nearpy.distances import CosineDistance\n')]