#!/usr/bin/env python

import numpy as np
import matplotlib.pyplot as plt

from scipy.stats    import moyal
from scipy.optimize import minimize


def gaussian_kde(dataset, points):
    n = dataset.size
    m = points.size

    var = np.var(dataset) * np.power(n*3/4, -2/5) * 0.4
    result = np.zeros(m)

    if m >= n:
        for i in range(n):
            diff = dataset[i] - points
            result += np.exp(-diff**2/(2*var))/n
    else:
        for i in range(m):
            diff = dataset - points[i]
            result[i] = np.sum(np.exp(-diff**2 / (2*var)))

    return result / np.sqrt(2*np.pi*var) / n


def gaussian_kde_point(dataset, var, x):
    var *= np.power(dataset.size * 3/4, -2/5) * 0.4
    val = np.sum(np.exp(-(x - dataset)**2 / (2*var))) / dataset.size
    return val / np.sqrt(2*np.pi*var)


def hsm(x):
    n = len(x)
    while n > 3:
        k = n//2
        diffs = x[k:] - x[:-k]

        i = np.where(diffs == np.min(diffs))[0]
        i = int(np.mean(i))

        if diffs[i] == 0:
            return x[i]
        else:
            x = x[i:i+k]
        n = len(x)

    if n == 3:
        d = 2*x[1] - x[0] - x[2]
        return np.mean(x[1:] if d > 0 else x[:2])

    if n < 3:
        return np.mean(x)


def bootstrap(f, x, n):
    vals = np.empty(n)
    for i in range(n):
        s = np.random.choice(x, size=x.size)
        vals[i] = f(np.sort(s))
    return vals


np.random.seed(1)
x = np.linspace(-2, 25, 400)
sample = moyal.rvs(size=5000, loc=4)

s = np.argsort(sample)

f = gaussian_kde(sample, x)


def kde(sample):
    var = np.var(sample)
    f = lambda x: -gaussian_kde_point(sample, var, x)
    M = minimize(lambda x: f(x), hsm(sample)).x
    return M

m = bootstrap(hsm, sample, 500)
M = bootstrap(kde, sample, 500)

print(m.mean(), m.std())
print(M.mean(), M.std())

plt.figure()
plt.plot(x, moyal.pdf(x, loc=4), c='xkcd:gray', label='true PDF')
plt.plot(x, f, '#92182b', label='empirical PDF')
plt.plot(sample, np.zeros_like(sample),
         '|', c='xkcd:navy')
#plt.axvline(x=m.mean(), c='r')
plt.axvline(x=M.mean(), c='xkcd:camel', label='mode')
plt.legend()
plt.show()
