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

import numpy as np
from scipy.integrate import solve_bvp, odeint, solve_ivp, simps, romb, romberg, quad
from scipy.interpolate import interp1d, CubicSpline
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from tqdm import trange

k = 100 #Reaction rate constant.
DG = 1.16 #Diffusion coefficient of Gallium.
DA = 0 #Diffusion coefficient of Arsenic.
F0 = 0.04 #Deposition flux of Arsenic.
F = 2 * F0 #Resdience time of Arsenic.
tau = 1/F0 #Resdience time of Arsenic.
GaAs_normalisation = 500
y1a = 1.0 #Surface adatom concentration of Gallium at r = rD.
y3a = 0.0 #Surface adatom concentration of Arsenic at r = rD.
CAs_c = F * tau #Surface adatom concentration of Arsenic for large r.
kappa_0 = k * y1a * CAs_c / 500 #Normalisation constant

rDi = 1.0#Initial droplet radius (first r-mesh point at t = 0).
rb = 10.0 #Last r-mesh point.
rdivs = 513 #484, 0.003, 20000, 165 #Number of r-mesh points.#300 when tol=0.01
tolerance = 1e-7 #Accuracy to which CGa and CAs are found
max_mesh_nodes = 4e7 #Number of mesh nodes algorithm for bvp solver uses to find concentration profiles

tf = 0.1473 #Final time
rD_cutoff = 0.003 #point at which radius stops decreasing to avoid small number errors
tdivs = 513 #Number of t-mesh points.
V_Ga = 1.0 #Ga primitive cell volume
V_GaAs = 1.0 #GaAs primitive cell volume
beta_theta = 1.0 #Contact angle constant
A = 2.57142857 #Constant coefficient of first term in drD/dt expression.
B = 1.5 #Constant coefficient of second term in drD/dt expression.
b = 1.0 * rDi #Constant coefficient of Gaussian expression.
w = 0.15 * rDi #Width of Gaussian.
hi = 0.0 #Initial height of inner/outer ring (at all points).

plt.rcParams['font.size'] = 30

def dydr(r, y):
    return np.vstack((y[1],
                      -y[1]/r + k * y[0] * F/(DG * (k * y[0] + 1/tau))))

def y_bc(ya, yb):
    return np.array([ya[0] - y1a, yb[0]])

rmeshi = np.linspace(rDi, rb, rdivs)
rmeshf = np.linspace(0, rb - rDi, rdivs)
y_at_ti_guess = np.zeros((2, rmeshi.size))
calc_y_at_ti_vs_r = solve_bvp(dydr, y_bc, rmeshi, y_at_ti_guess, tol = tolerance, max_nodes = max_mesh_nodes)
calc_y_at_ti_vs_r.status

def plot_y_at_ti_vs_r():
    plt.figure(1, figsize = (11,11))
    plt.plot(rmeshi, calc_y_at_ti_vs_r.sol(rmeshi)[0], 'r--', lw = '3', label = r'$C_{Ga}/C^0_{Ga}$')
    y3 = F / (k * calc_y_at_ti_vs_r.sol(rmeshi)[0] + 1/tau)
    plt.plot(rmeshi, y3, 'g', linestyle = (0, (1, 1)), lw = '3', label = r'$C_{As}/C^c_{As}$')
    plt.plot(rmeshi, k * calc_y_at_ti_vs_r.sol(rmeshi)[0] * y3 / kappa_0, color = 'k', linewidth = 5, label = r'$k_rC_{Ga}C_{As} / \kappa_0$')
    plt.plot([rmeshi[0], rmeshi[0]], [0, y1a], color = 'k', lw = '2', linestyle = (0, (1, 10)))
    plt.xlabel(r'$r/r_D(0)$')
    plt.legend()

plot_y_at_ti_vs_r() #This produces a graph of the concentration profiles.

"""
Droplet radius
"""

def y2_value_at_rD(rD):
    def dydr(r, y):
        return np.vstack((y[1],
                          -y[1]/r + k * y[0] * F/(DG * (k * y[0] + 1/tau))))

    def y_bc(ya, yb):
        return np.array([ya[0] - y1a, yb[0]])

    rmesh = np.linspace(rD, rb - (rDi - rD), rdivs)
    y_guess = np.zeros((2, rmesh.size))
    return solve_bvp(dydr, y_bc, rmesh, y_guess, tol = tolerance, max_nodes = max_mesh_nodes).sol(rD)[1]

def drDdt(t, rD):
    if rD > rD_cutoff:
        return (1 / rD) * (V_Ga / beta_theta) * (DG * A * y2_value_at_rD(rD[0]) - B * b * w / V_GaAs) #(Sould include DG dependence later because this is one of the parameters varied in the paper.)
    else:
        return 0.0

tmesh = np.linspace(0, tf, tdivs)
calc_rD_vs_t = solve_ivp(drDdt, [0.0, tf], [rDi], dense_output = True, atol = 1e-6)
rD_vs_t = calc_rD_vs_t.sol(tmesh)

y2a_vs_t = [y2_value_at_rD(i) for i in rD_vs_t[0]]

plt.figure(652, figsize = (11,11))
plt.xlabel(r'$t / t_f$')
plt.ylabel(r'$-\frac{dC_{Ga}(r_D, t)}{dr}$')
plt.yscale('log')
plt.plot(tmesh / tf, [-i for i in y2a_vs_t], color = 'r', lw = 4)

def plot_rD_vs_t():
        plt.figure(4, figsize = (11,11))
        plt.xlabel(r'$t / t_f$')
        plt.ylabel(r'$r_D(t) / r_D(0)$')
        x = np.linspace(0, tf, tdivs*500)
        x = tmesh
        plt.plot(x/tf, calc_rD_vs_t.sol(x)[0], color = 'r', lw = 4)

plot_rD_vs_t() #This produces a graph of droplet radius as a function of time.

def rD_value_at_t(t):
    if t <= tf:
        return calc_rD_vs_t.sol(t)[0]
    elif t > tf:
        return 0

"""
Inner ring
"""

def dhIdt(t, r):
    if rD_value_at_t(t) > rD_cutoff:
        return b*np.exp(-((r - rD_value_at_t(t))**2)/(2*w**2))
    else:
       return 0.0

hIf_vs_r = []
for r in rmeshf:
    hOf_at_r = romberg(dhIdt, 0, tf, args = (r,))
    hIf_vs_r.append(hOf_at_r)

hI1_vs_r = []
for r in rmeshf:
    hOf_at_r = romberg(dhIdt, 0, tf*(1/4), args = (r,))
    hI1_vs_r.append(hOf_at_r)

hI2_vs_r = []
for r in rmeshf:
    hOf_at_r = romberg(dhIdt, 0, tf*(2/4), args = (r,))
    hI2_vs_r.append(hOf_at_r)

hI3_vs_r = []
for r in rmeshf:
    hOf_at_r = romberg(dhIdt, 0, tf*(3/4), args = (r,))
    hI3_vs_r.append(hOf_at_r)

plt.figure(908, figsize = (11,11))
plt.plot(rmeshf, hI1_vs_r, color = 'r', label = r'$t = \frac{1}{4}t_f$')
plt.plot(rmeshf, hI2_vs_r, color = 'g', label = r'$t = \frac{1}{2}t_f$')
plt.plot(rmeshf, hI3_vs_r, color = 'b', label = r'$t = \frac{3}{4}t_f$')
plt.plot(rmeshf, hIf_vs_r, color = 'k', label = r'$t = t_f$')
plt.xlabel(r'$r/r_D(0)$')
plt.yticks([])
plt.legend()

"""
Outer ring
"""

y_vs_r_at_all_t = []

def Append_solution(rD):
    def dydr(r, y):
        return np.vstack((y[1],
                          -y[1]/r + k * y[0] * F/(DG * (k * y[0] + 1/tau))))

    def y_bc(ya, yb):
        return np.array([ya[0] - y1a, yb[0]])

    rmesh = np.linspace(rD, rb - (rDi - rD), rdivs)
    y_guess = np.zeros((2, rmesh.size))
    calc_y_vs_r_at_t = solve_bvp(dydr, y_bc, rmesh, y_guess, tol = tolerance, max_nodes = max_mesh_nodes).sol
    y_vs_r_at_all_t.append(calc_y_vs_r_at_t)
    return None

for i in trange(len(rD_vs_t[0])):
    Append_solution(rD_vs_t[0][i])

y1y3_values_mesh_points = []
for t in range(len(tmesh)):
    y1y3_over_rmesh_fixed_t = []
    for r in rmeshf:
        y1 = y_vs_r_at_all_t[t](r)[0]
        y3 = F / (k * y_vs_r_at_all_t[t](r)[0] + 1/tau)
        y1y3 = k * V_GaAs * y1 * y3
        if y1y3 > 1e-17 and rD_value_at_t(tmesh[t]) > rD_cutoff:
            y1y3_over_rmesh_fixed_t.append(y1y3)
        else:
            y1y3_over_rmesh_fixed_t.append(0.0)
    y1y3_values_mesh_points.append(y1y3_over_rmesh_fixed_t)

plt.figure(903)
for i in range(len(tmesh)):
    plt.plot(rmeshf, y1y3_values_mesh_points[i])

y1y3_values_mesh_points_r_vs_t = []
for r in rmeshf:
    y1y3_over_rmesh_fixed_r = []
    for t in range(len(tmesh)):
        y1 = y_vs_r_at_all_t[t](r)[0]
        y3 = F / (k * y_vs_r_at_all_t[t](r)[0] + 1/tau)
        y1y3 = k * V_GaAs * y1 * y3
        if y1y3 > 0 and rD_value_at_t(tmesh[t]) > rD_cutoff: #1e-17:
            y1y3_over_rmesh_fixed_r.append(y1y3)
        else:
            y1y3_over_rmesh_fixed_r.append(0.0)
    y1y3_values_mesh_points_r_vs_t.append(y1y3_over_rmesh_fixed_r)

plt.figure(904)
for i in range(len(rmeshf)):
    plt.plot(tmesh, y1y3_values_mesh_points_r_vs_t[i])

delta_t = tf/(tdivs - 1)

hOf_vs_r = []
for i in range(len(rmeshf)):
    hOf_at_r = simps(y1y3_values_mesh_points_r_vs_t[i], tmesh)
    hOf_vs_r.append(hOf_at_r)

hO1_vs_r = []
for i in range(len(rmeshf)):
    hOf_at_r = simps(y1y3_values_mesh_points_r_vs_t[i][:int((1/4) * tdivs)], tmesh[:int((1/4) * tdivs)])
    hO1_vs_r.append(hOf_at_r)

hO2_vs_r = []
for i in range(len(rmeshf)):
    hOf_at_r = simps(y1y3_values_mesh_points_r_vs_t[i][:int((2/4) * tdivs)], tmesh[:int((2/4) * tdivs)])
    hO2_vs_r.append(hOf_at_r)

hO3_vs_r = []
for i in range(len(rmeshf)):
    hOf_at_r = simps(y1y3_values_mesh_points_r_vs_t[i][:int((3/4) * tdivs)], tmesh[:int((3/4) * tdivs)])
    hO3_vs_r.append(hOf_at_r)

plt.figure(905)
plt.plot(rmeshf, hO1_vs_r, color = 'r', label = r'$t = \frac{1}{4}t_f$')
plt.plot(rmeshf, hO2_vs_r, color = 'g', label = r'$t = \frac{1}{2}t_f$')
plt.plot(rmeshf, hO3_vs_r, color = 'b', label = r'$t = \frac{3}{4}t_f$')
plt.plot(rmeshf, hOf_vs_r, color = 'k', label = r'$t = t_f$')
plt.xlabel(r'$r/r_D(0)$')
plt.yticks([])
plt.legend()

normalise = 5

h1_vs_r = np.array(hI1_vs_r) + normalise * np.array(hO1_vs_r)
h2_vs_r = np.array(hI2_vs_r) + normalise * np.array(hO2_vs_r)
h3_vs_r = np.array(hI3_vs_r) + normalise * np.array(hO3_vs_r)
hf_vs_r = np.array(hIf_vs_r) + normalise * np.array(hOf_vs_r)

h1_vs_r_T = np.insert(h1_vs_r, 0, h1_vs_r[::-1][:-1])
h2_vs_r_T = np.insert(h2_vs_r, 0, h2_vs_r[::-1][:-1])
h3_vs_r_T = np.insert(h3_vs_r, 0, h3_vs_r[::-1][:-1])
hf_vs_r_T = np.insert(hf_vs_r, 0, hf_vs_r[::-1][:-1])

rmeshf_T = np.linspace(-rmeshf[-1], rmeshf[-1], rdivs * 2 - 1)

plt.figure(906)
plt.plot(rmeshf_T, h1_vs_r_T, color = 'r', label = r'$t = \frac{1}{4}t_f$')
plt.plot(rmeshf_T, h2_vs_r_T, color = 'g', label = r'$t = \frac{1}{2}t_f$')
plt.plot(rmeshf_T, h3_vs_r_T, color = 'b', label = r'$t = \frac{3}{4}t_f$')
plt.plot(rmeshf_T, hf_vs_r_T, color = 'k', label = r'$t = t_f$')
plt.xlabel(r'$r/r_D(0)$')
plt.yticks([])
plt.legend()

hI1_vs_r_T = np.insert(np.array(hI1_vs_r), 0, np.array(hI1_vs_r)[::-1][:-1])
hI2_vs_r_T = np.insert(np.array(hI2_vs_r), 0, np.array(hI2_vs_r)[::-1][:-1])
hI3_vs_r_T = np.insert(np.array(hI3_vs_r), 0, np.array(hI3_vs_r)[::-1][:-1])
hIf_vs_r_T = np.insert(np.array(hIf_vs_r), 0, np.array(hIf_vs_r)[::-1][:-1])

hO1_vs_r_T = normalise * np.insert(np.array(hO1_vs_r), 0, np.array(hO1_vs_r)[::-1][:-1])
hO2_vs_r_T = normalise * np.insert(np.array(hO2_vs_r), 0, np.array(hO2_vs_r)[::-1][:-1])
hO3_vs_r_T = normalise * np.insert(np.array(hO3_vs_r), 0, np.array(hO3_vs_r)[::-1][:-1])
hOf_vs_r_T = normalise * np.insert(np.array(hOf_vs_r), 0, np.array(hOf_vs_r)[::-1][:-1])

plt.figure(888, figsize = (11,11))
plt.plot(rmeshf_T, hf_vs_r_T, color = 'k', lw = 8, label = r'$h(r/r_D(0), t = t_f)$')
plt.plot(rmeshf_T, hIf_vs_r_T, color = 'g', lw = 5, label = r'$h_I(r/r_D(0), t = t_f)$')
plt.plot(rmeshf_T, hOf_vs_r_T, color = 'r', lw = 5, label = r'$h_O(r/r_D(0), t = t_f)$')
plt.xlabel(r'$r/r_D(0)$')
plt.yticks([])
#plt.legend()

plt.figure(889, figsize = (11,11))
plt.plot(rmeshf_T, hf_vs_r_T, color = 'k', lw = 8, label = r'$h(r/r_D(0), t = t_f)$')
plt.ylim(0, 1.7 * max(hf_vs_r_T))
plt.xlabel(r'$r/r_D(0)$')
plt.ylabel(r'$h(r/r_D(0))$')
plt.yticks([])

#Following piece of code for 3D plot adapted from code written by ImportanceOfBeingErnest on https://stackoverflow.com/questions/47333811/how-do-i-create-a-surface-plot-with-matplotlib-of-a-closed-loop-revolve-about-an

x_values = rmeshf
# input xy coordinates
x_y_coordinates = [[x_values[i], hf_vs_r[i]] for i in range(len(x_values))]
xy = np.array(x_y_coordinates)
# radial component is x values of input
r = xy[:,0]
# angular component is one revolution of 60 steps
phi = np.linspace(0, 2*np.pi, 60)
# create grid
R,Phi = np.meshgrid(r,phi)
# transform to cartesian coordinates
X = R*np.cos(Phi)
Y = R*np.sin(Phi)
# Z values are y values, repeated 60 times
Z = np.tile(xy[:,1],len(Y)).reshape(Y.shape)

fig = plt.figure()
ax = fig.add_subplot(1, 1, 1, projection='3d')
ax2 = fig.add_axes([0.05,0.7,0.15,.2])
ax2.plot(xy[:,0],xy[:,1], color="k")

ax.plot_surface(X, Y, Z, alpha=0.5, color='gold', rstride=1, cstride=1)

plt.show()


# -*- coding: utf-8 -*-
"""
Created on Fri Jan 12 11:16:46 2018

@author: VidyaSagar
"""

import turtle

def draw_square():
    window = turtle.Screen()
    window.bgcolor("red")
    brad = turtle.Turtle()
    brad.shape("turtle")
    brad.color("green")
    brad.speed(2)
    for i in range(1,36):
        for i in range(1,5):
            brad.forward(100)
            brad.right(90)
        brad.right(10)
    window.exitonclick()
draw_square()


from pyramid.compat import json
import urllib

from plone.i18n.normalizer import urlnormalizer
from pyramid.i18n import get_locale_name
from pyramid.i18n import TranslationStringFactory
from pyramid.threadlocal import get_current_request
from pyramid.url import resource_url
from repoze.lru import LRUCache
from sqlalchemy.types import TypeDecorator, TEXT
from sqlalchemy.ext.mutable import Mutable

_ = TranslationStringFactory('Kotti')

def dump_default(obj):
    if isinstance(obj, MutationDict):
        return obj._d
    elif isinstance(obj, MutationList):
        return obj._d

class JsonType(TypeDecorator):
    """http://www.sqlalchemy.org/docs/core/types.html#marshal-json-strings
    """
    impl = TEXT

    def process_bind_param(self, value, dialect):
        if value is not None:
            value = json.dumps(value, default=dump_default)
        return value

    def process_result_value(self, value, dialect):
        if value is not None:
            value = json.loads(value)
        return value

class MutationDict(Mutable):
    """http://www.sqlalchemy.org/docs/orm/extensions/mutable.html
    """
    def __init__(self, data):
        self._d = data
        super(MutationDict, self).__init__()

    @classmethod
    def coerce(cls, key, value):
        if not isinstance(value, MutationDict):
            if isinstance(value, dict):
                return cls(value)
            return Mutable.coerce(key, value)
        else:
            return value

class MutationList(Mutable):
    def __init__(self, data):
        self._d = data
        super(MutationList, self).__init__()

    @classmethod
    def coerce(cls, key, value):
        if not isinstance(value, MutationList):
            if isinstance(value, list):
                return cls(value)
            return Mutable.coerce(key, value)
        else:
            return value

    def __radd__(self, other):
        return other + self._d

def _make_mutable_method_wrapper(wrapper_class, methodname, mutates):
    def replacer(self, *args, **kwargs):
        method = getattr(self._d, methodname)
        value = method(*args, **kwargs)
        if mutates:
            self.changed()
        return value
    replacer.__name__ = methodname
    return replacer

for wrapper_class in (MutationDict, MutationList):
    for methodname, mutates in (
        ('__iter__', False),
        ('__len__', False),
        ('__eq__', False),
        ('__add__', False),
        ('get', False),
        ('keys', False),

        ('__setitem__', True),
        ('__delitem__', True),
        ('append', True),
        ('insert', True),
        ('pop', True),
        ('setdefault', True),
        ):
        setattr(
            wrapper_class, methodname,
            _make_mutable_method_wrapper(
                wrapper_class, methodname, mutates),
            )

class NestedMixin(object):
    __parent__ = None

    def __init__(self, *args, **kwargs):
        self.__parent__ = kwargs.pop('__parent__', None)
        super(NestedMixin, self).__init__(*args, **kwargs)

    def __getitem__(self, key):
        value = self._d.__getitem__(key)
        return self.try_wrap(value)

    def changed(self):
        if self.__parent__ is not None:
            self.__parent__.changed()
        else:
            super(NestedMixin, self).changed()

    def try_wrap(self, value):
        for typ, wrapper in MUTATION_WRAPPERS.items():
            if isinstance(value, typ):
                value = wrapper(value, __parent__=self)
                break
        return value

class NestedMutationDict(NestedMixin, MutationDict):
    pass

class NestedMutationList(NestedMixin, MutationList):
    pass

MUTATION_WRAPPERS = {
    dict: NestedMutationDict,
    list: NestedMutationList,
    }

class ViewLink(object):
    def __init__(self, path, title=None):
        self.path = path
        if title is None:
            title = path.replace('-', ' ').replace('_', ' ').title()
        self.title = title

    def url(self, context, request):
        return resource_url(context, request) + '@@' + self.path

    def selected(self, context, request):
        return urllib.unquote(request.url).startswith(
            self.url(context, request))

    def permitted(self, context, request):
        from kotti.security import view_permitted
        return view_permitted(context, request, self.path)

    def __eq__(self, other):
        return isinstance(other, ViewLink) and repr(self) == repr(other)

    def __repr__(self):
        return "ViewLink(%r, %r)" % (self.path, self.title)

class DontCache(Exception):
    pass

_CACHE_ATTR = 'kotti_cache'

def request_container():
    request = get_current_request()
    if request is None:
        return None
    cache = getattr(request, _CACHE_ATTR, None)
    if cache is None:
        cache = {}
        setattr(request, _CACHE_ATTR, cache)
    return cache

def cache(compute_key, container_factory):
    marker = object()
    def decorator(func):
        def replacement(*args, **kwargs):
            cache = container_factory()
            if cache is None:
                return func(*args, **kwargs)
            try:
                key = compute_key(*args, **kwargs)
            except DontCache:
                return func(*args, **kwargs)
            key = '%s.%s:%s' % (func.__module__, func.__name__, key)
            cached_value = cache.get(key, marker)
            if cached_value is marker:
                #print "\n*** MISS %r ***" % key
                cached_value = cache[key] = func(*args, **kwargs)
            else:
                #print "\n*** HIT %r ***" % key
                pass
            return cached_value
        replacement.__doc__ = func.__doc__
        return replacement
    return decorator

def request_cache(compute_key):
    return cache(compute_key, request_container)

class LRUCacheSetItem(LRUCache):
    __setitem__ = LRUCache.put

_lru_cache = LRUCacheSetItem(1000)

def lru_cache(compute_key):
    return cache(compute_key, lambda: _lru_cache)

def clear_cache(): # only useful for tests really
    request = get_current_request()
    if request is not None:
        setattr(request, _CACHE_ATTR, None)
    _lru_cache.clear()

def extract_from_settings(prefix, settings=None):
    """
      >>> settings = {
      ...     'kotti_twitter.foo_bar': '1', 'kotti.spam_eggs': '2'}
      >>> print extract_from_settings('kotti_twitter.', settings)
      {'foo_bar': '1'}
    """
    from kotti import get_settings
    settings = settings if settings is not None else get_settings()
    extracted = {}
    for key, value in settings.items():
        if key.startswith(prefix):
            extracted[key[len(prefix):]] = value
    return extracted

def title_to_name(title):
    request = get_current_request()
    if request is not None:
        locale_name = get_locale_name(request)
    else:
        locale_name = 'en'
    return unicode(urlnormalizer.normalize(title, locale_name, max_length=40))

def camel_case_to_name(text):
    """
      >>> camel_case_to_name('FooBar')
      'foo_bar'
      >>> camel_case_to_name('TXTFile')
      'txt_file'
      >>> camel_case_to_name ('MyTXTFile')
      'my_txt_file'
      >>> camel_case_to_name('froBOZ')
      'fro_boz'
      >>> camel_case_to_name('f')
      'f'
    """
    value = text[0]
    for char in text[1:]:
        if char.isupper() and not value[-1].isupper():
            value += '_'
        elif (char.islower() and len(value) > 1
              and value[-1].isupper() and value[-2].isupper()):
            value = value[:-1] + '_' + value[-1]
        value += char
    return value.lower()


import pandas as pd
import numpy as np

def normalize_data(dataframe, mode):
    if mode == 'abs':
        from sklearn.preprocessing import MaxAbsScaler
        max_abs = MaxAbsScaler(copy=True)  #save for retransform later
        max_abs.fit(dataframe)
        data_norm = max_abs.transform(dataframe)

        return data_norm, max_abs

    if mode == 'robust':
        from sklearn.preprocessing import RobustScaler
        robust = RobustScaler(copy=True)  #save for retransform later
        robust.fit(dataframe)
        data_norm = robust.transform(dataframe)

        return data_norm, robust

    if mode == 'min_max':
        from sklearn.preprocessing import MinMaxScaler
        minmax = MinMaxScaler(feature_range=(0, 1), copy=True)  #save for retransform later
        minmax.fit(dataframe)
        data_norm = minmax.transform(dataframe)

        return data_norm, minmax
    if mode == 'std':
        from sklearn.preprocessing import StandardScaler
        stdscaler = StandardScaler(copy=True, with_mean=True, with_std=True)
        stdscaler.fit(dataframe)
        data_norm = stdscaler.transform(dataframe)

        return data_norm, stdscaler

def extract_data(dataframe, window_size=5, target_timstep=1, cols_x=[], cols_y=[], cols_gt=[],mode='std'):
    dataframe, scaler = normalize_data(dataframe, mode)

    xs = [] # return input data
    ys = [] # return output data
    ygt = [] # return groundtruth data

    if target_timstep != 1:
        for i in range(dataframe.shape[0] - window_size - target_timstep):
            xs.append(dataframe[i:i + window_size, cols_x])
            ys.append(dataframe[i + window_size:i + window_size + target_timstep,
                                cols_y].reshape(target_timstep, len(cols_y)))
            ygt.append(dataframe[i + window_size:i + window_size + target_timstep,
                       cols_gt].reshape(target_timstep, len(cols_gt)))
    else:
        for i in range(dataframe.shape[0] - window_size - target_timstep):
            xs.append(dataframe[i:i + window_size, cols_x])
            ys.append(dataframe[i + window_size:i + window_size + target_timstep, cols_y].reshape(len(cols_y)))
            ygt.append(dataframe[i + window_size:i + window_size + target_timstep, cols_gt].reshape(len(cols_gt)))
    return np.array(xs), np.array(ys), scaler, np.array(ygt)

def ed_extract_data(dataframe, window_size=5, target_timstep=1, cols_x=[], cols_y=[], mode='std'):
    dataframe, scaler = normalize_data(dataframe, mode)

    en_x = []
    de_x = []
    de_y = []

    for i in range(dataframe.shape[0] - window_size - target_timstep):
        en_x.append(dataframe[i:i + window_size, cols_x])

        #decoder input is q and h of 'window-size' days before
        de_x.append(dataframe[i + window_size - 1:i + window_size + target_timstep - 1,
                              cols_y].reshape(target_timstep, len(cols_y)))
        de_y.append(dataframe[i + window_size:i + window_size + target_timstep,
                              cols_y].reshape(target_timstep, len(cols_y)))

    en_x = np.array(en_x)
    de_x = np.array(de_x)
    de_y = np.array(de_y)
    de_x[:, 0, :] = 0

    return en_x, de_x, de_y, scaler

def atted_extract_data(dataframe, window_size=5, cols=[], mode='l2'):  #NOTE: unuse!!!
    dataframe, scaler = normalize_data(dataframe, mode)

    en_x = []
    de_x = []
    de_y = []

    for i in range(dataframe.shape[0] - 2 * window_size - 1):
        en_x.append(dataframe[i:i + window_size, cols])

        #decoder input is q and h of 'window-size' days before
        de_x.append(dataframe[(i + window_size - 1):(i + 2 * window_size - 1), [7, 5]])
        de_y.append(dataframe[(i + window_size):(i + 2 * window_size), [7, 5]])

    en_x = np.array(en_x)
    de_x = np.array(de_x)
    de_y = np.array(de_y)

    de_x[:, 0, :] = 0

    return en_x, de_x, de_y, scaler

def roll_data(dataframe, cols_x, cols_y, mode='min_max'):
    dataframe, scaler = normalize_data(dataframe, mode)
    #dataframe = dataframe.drop('time', axis=1)

    X = dataframe[:, cols_x]
    y = dataframe[:, cols_y]

    return X, y, scaler

def process_evaporation(full_dat, vapor_dat):
    full_dat['vapor'] = 0
    length = len(full_dat)

    full_dat.index = pd.to_datetime(full_dat['time'])
    full_dat = full_dat.drop('time', axis=1)

    vapor_dat.index = pd.to_datetime(vapor_dat['Time'])
    vapor_dat = vapor_dat.drop('Time', axis=1)

    vapor_dat['KonTum'] = vapor_dat['KonTum'] / 12

    print(full_dat.head())
    print(vapor_dat.head())
    print(length)
    #vapor_dat.to_csv('./RawData/KonTum.csv')
    import datetime
    for i in range(length):
        month = full_dat.index[i].month
        year = full_dat.index[i].year
        full_dat.iloc[i, 8] = vapor_dat.loc[datetime.datetime(year, month, 1), 'KonTum']
        if i < 10:
            print(vapor_dat.loc[datetime.datetime(year, month, 1), 'KonTum'])

    print(full_dat.head())
    full_dat.to_csv('./Kontum-daily.csv')

def plot_compare_model():
    dat = pd.read_csv('./Log/DataAnalysis/predict_val.csv', header=0)
    # dat_arima = pd.read_csv('./Log/Arima/arima.csv', header=0, index_col=0)

    # dat['arima_q'] = dat_arima['arima_q_hanoi']
    # dat['arima_h'] = dat_arima['arima_h_hanoi']

    # dat.to_csv('./Log/DataAnalysis/predict_val.csv', index=None)
    import matplotlib.pyplot as plt

    plt.plot(dat['real_q'], label='ground_truth')
    plt.plot(dat['ensemble_q'], label='ensemble')
    plt.plot(dat['rnn_cnn_q'], label='rnn_cnn')
    plt.plot(dat['en_de_q'], label='encoder_decoder')
    plt.legend(loc='best')
    plt.xlabel('Time')
    plt.ylabel('Q')
    plt.title('Các mô hình đề xuất')
    plt.savefig('./Log/DataAnalysis/our_model_q.png')
    plt.clf()

    plt.plot(dat['real_h'], label='ground_truth')
    plt.plot(dat['ensemble_h'], label='ensemble')
    plt.plot(dat['rnn_cnn_h'], label='rnn_cnn')
    plt.plot(dat['en_de_h'], label='encoder_decoder')
    plt.legend(loc='best')
    plt.xlabel('Time')
    plt.ylabel('H')
    plt.title('Các mô hình đề xuất')

    plt.savefig('./Log/DataAnalysis/our_model_h.png')
    plt.clf()

    plt.plot(dat['lstm_q'], label='lstm')
    plt.plot(dat['ann_q'], label='ann')
    plt.plot(dat['arima_q'], label='arima')
    plt.plot(dat['real_q'], label='ground_truth')

    plt.legend(loc='best')
    plt.xlabel('Time')
    plt.ylabel('Q')
    plt.title('Các mô hình hiện tại')
    plt.savefig('./Log/DataAnalysis/paper_model_q.png')
    plt.clf()

    plt.plot(dat['lstm_h'], label='lstm')
    plt.plot(dat['ann_h'], label='ann')
    plt.plot(dat['arima_h'], label='arima')
    plt.plot(dat['real_h'], label='ground_truth')

    plt.legend(loc='best')
    plt.xlabel('Time')
    plt.ylabel('H')
    plt.title('Các mô hình hiện tại')
    plt.savefig('./Log/DataAnalysis/paper_model_h.png')
    plt.clf()

    plt.plot(dat['real_q'], label='ground_truth')
    plt.plot(dat['ensemble_q'], label='ensemble')
    plt.plot(dat['rnn_cnn_q'], label='rnn_cnn')
    plt.plot(dat['en_de_q'], label='encoder_decoder')
    plt.plot(dat['lstm_q'], label='lstm')
    plt.plot(dat['ann_q'], label='ann')
    plt.plot(dat['arima_q'], label='arima')
    plt.legend(loc='best')
    plt.xlabel('Time')
    plt.ylabel('Q')
    plt.title('Mọi mô hình')
    plt.savefig('./Log/DataAnalysis/compare_q.png')
    plt.clf()

    plt.plot(dat['real_h'], label='ground_truth')
    plt.plot(dat['ensemble_h'], label='ensemble')
    plt.plot(dat['rnn_cnn_h'], label='rnn_cnn')
    plt.plot(dat['en_de_h'], label='encoder_decoder')
    plt.plot(dat['lstm_h'], label='lstm')
    plt.plot(dat['ann_h'], label='ann')
    plt.plot(dat['arima_h'], label='arima')
    plt.legend(loc='best')
    plt.xlabel('Time')
    plt.ylabel('H')
    plt.title('Mọi mô hình')
    plt.savefig('./Log/DataAnalysis/compare_H.png')
    plt.clf()

def plot_PM():
    import matplotlib.pyplot as plt

    gt = pd.read_csv('./RawData/PM/groundtruth.csv', header=None)
    pre = pd.read_csv('./RawData/PM/preds.csv', header=None)

    plt.plot(gt[1], label='ground_truth')
    plt.plot(pre[1], label='predict')

    plt.legend(loc='best')
    plt.xlabel('Thời gian')
    plt.ylabel('Lượng mưa')
    plt.title('Kết quả mô hình')
    plt.savefig('./Log/DataAnalysis/compare_pm.png')

if __name__ == '__main__':
    # full_dat = pd.read_csv('./RawData/Kontum-daily.csv', header=0, index_col=0)
    # vapor_dat = pd.read_csv('./RawData/KonTum.csv', header=0, index_col=None)

    # process_evaporation(full_dat, vapor_dat)

    # data = pd.read_csv('./RawData/Hanoi/Merge_HN.csv', header=0, index_col=0)
    # data = data.set_index('date')
    # data = data.drop(['date.1', 'date.2'], axis=1)
    # print(data.head())
    # data.to_csv('./RawData/Hanoi/Merge_HN.csv')
    # data = data.to_numpy()
    # print(data.shape)
    plot_PM()


from .intervention import *
from .user import *
from src.common.db import db

class CisVehicleModel(db.Model):

    __tablename__ = 'cis_vehicle'

    id = db.Column(db.Integer, primary_key=True)
    cis = db.Column(db.Integer)
    vehicle = db.relationship('InterventionEngineModel', backref="vehicle")
    duty = db.Column(db.Integer, db.ForeignKey('cis_engines.id'))
    name = db.Column(db.String)

    def json(self):
        return {
            'id': self.id,
            'name': self.name,
        }

    @classmethod
    def get_vehicle_by_name_and_cis(cls, cis, name):
        """ Get CIS from database """
        return cls.query.filter_by(cis=cis, name=name).first()

    @classmethod
    def get_vehicle_by_engine(cls, engine):
        """ Get CIS from database """
        return cls.query.filter_by(duty=engine).first()

class CisEngineModel(db.Model):

    __tablename__ = 'cis_engines'

    id = db.Column(db.Integer, primary_key=True)
    cis = db.Column(db.Integer, db.ForeignKey('cis.id'))
    name = db.Column(db.String)
    category = db.Column(db.String)
    duty = db.Column(db.String)
    members = db.Column(db.Integer)
    vehicle = db.relationship('CisVehicleModel')

    def json(self):
        return {
            'id': self.id,
            'name': self.name,
            'category': self.category,
            'duty': self.duty,
            'vehicle': list(map(lambda x: x.json(), self.vehicle))
        }

    @classmethod
    def get_engine_by_name_and_cis(cls, cis, name):
        """ Get CIS from database """
        return cls.query.filter_by(cis=cis, name=name).first()

class CisModel(db.Model):

    __tablename__ = 'cis'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String)
    location = db.Column(db.String)
    layout = db.Column(db.String)
    settings = db.Column(db.String)
    engines = db.relationship('CisEngineModel')
    vehicle = db.relationship('UserModel', backref="registered")

    def __init__(self, id, name, location):
        self.id = id
        self.name = name
        self.location = location

    def json(self):
        """ Return a directory from Model """
        return {
            'id': self.id,
            'name': self.name,
            'location': self.location,
            'layout': self.layout,
            'engines': list(map(lambda x: x.json(), self.engines))
        }

    @classmethod
    def get_cis_by_location(cls, location):
        """ Get CIS from database """
        return cls.query.filter_by(location=location).first()


""" Has action mask. """
import random

import numpy as np

from attackgraph import sample_strategy as ss

class Attacker(object):

    def __init__(self, G, oredges, andnodes, actionspace):
        self.num_nodes = G.number_of_nodes()
        self.observation = [0]*self.num_nodes
        self.attact = set()
        self.ORedges = oredges
        self.ANDnodes = andnodes
        self.actionspace = actionspace

    # When we're the opponent generate the action set.
    def att_greedy_action_builder(self, G):
        self.attact.clear()
        isDup = False
        mask = np.array([self.get_att_canAttack_mask(G)], dtype=np.float32)

        observations = []
        actions = []

        while not isDup:
            att_input = self.att_obs_constructor()
            observations += [att_input]
            x = self.nn_att(
                observation=att_input[None],
                stochastic=True,
                mask=mask,
                update_eps=-1,
                training_attacker=True)[0]
            actions += [x]
            action = self.actionspace[x]
            if action == 'pass':
                break
            isDup = (action in self.attact)
            if not isDup:
                self.attact.add(action)

    # Simulation.
    def att_greedy_action_builder_single(self, G, nn_att):
        self.attact.clear()
        isDup = False
        mask = np.array([self.get_att_canAttack_mask(G)], dtype=np.float32)
        while not isDup:
            att_input = self.att_obs_constructor()
            x = nn_att(
                observation=att_input[None],
                stochastic=False,
                mask=mask,
                update_eps=-1,
                training_attacker=True)[0]
            action = self.actionspace[x]
            if action == 'pass':
                break
            isDup = (action in self.attact)
            if not isDup:
                self.attact.add(action)

    # construct the input of the neural network
    def att_obs_constructor(self):
        inAttackSet = self.get_att_inAttackSet()
        att_input = self.observation + inAttackSet
        return np.array(att_input, dtype=np.float32)

    # This function can also be used as masking illegal actions.
    def get_att_canAttack(self, G):
        canAttack = []
        # TODO: recheck the logics
        for andnode in self.ANDnodes:
            if G.nodes[andnode]['root'] == 1 and G.nodes[andnode]['state'] == 0:
                canAttack.append(1)
                continue
            if G.nodes[andnode]['root'] == 1 and G.nodes[andnode]['state'] == 1:
                canAttack.append(0)
                continue
            precondflag = 1
            precond = G.predecessors(andnode)
            for prenode in precond:
                if G.nodes[prenode]['state'] == 0:
                    precondflag = 0
                    break
            if G.nodes[andnode]['state'] == 0 and precondflag:
                canAttack.append(1)
            else:
                canAttack.append(0)

        for (father, son) in self.ORedges:
            if G.nodes[father]['state'] == 1 and G.nodes[son]['state'] == 0:
                canAttack.append(1)
            else:
                canAttack.append(0)

        return canAttack

    def get_att_inAttackSet(self):
        inAttackSet = []
        for andnode in self.ANDnodes:
            if andnode in self.attact:
                inAttackSet.append(1)
            else:
                inAttackSet.append(0)

        for (father, son) in self.ORedges:
            if (father, son) in self.attact:
                inAttackSet.append(1)
            else:
                inAttackSet.append(0)

        return inAttackSet

    def uniform_strategy(self, G, rand_limit):
        # TODO: rand_limit should be less than number of available actions.
        actmask = self.get_att_canAttack(G)
        attSet = list(self.ANDnodes) + self.ORedges
        actset_masked = list(x for x, z in zip(attSet, actmask) if z)
        return set(random.sample(actset_masked, min(rand_limit, len(actset_masked))))

    def update_obs(self, obs):
        self.observation = obs

    def reset_att(self):
        self.observation = [0]*self.num_nodes
        self.attact.clear()

    # Designed for mask function
    def get_att_canAttack_mask(self, G):
        canAttack = []
        # TODO: recheck the logics
        for andnode in self.ANDnodes:
            if G.nodes[andnode]['root'] == 1 and G.nodes[andnode]['state'] == 0:
                canAttack.append(0)
                continue
            if G.nodes[andnode]['root'] == 1 and G.nodes[andnode]['state'] == 1:
                canAttack.append(-1000)
                continue
            precondflag = 1
            precond = G.predecessors(andnode)
            for prenode in precond:
                if G.nodes[prenode]['state'] == 0:
                    precondflag = 0
                    break
            if G.nodes[andnode]['state'] == 0 and precondflag:
                canAttack.append(0)
            else:
                canAttack.append(-1000)

        for (father, son) in self.ORedges:
            if G.nodes[father]['state'] == 1 and G.nodes[son]['state'] == 0:
                canAttack.append(0)
            else:
                canAttack.append(-1000)

        canAttack.append(0)  # mask pass with 0.
        return canAttack

    def get_att_isActive(self, G):
        isActive = []
        for id in G.nodes:
            if G.nodes[id]['state'] == 1:
                isActive.append(1)
            else:
                isActive.append(0)
        return isActive

    def set_env_belong_to(self, env):
        self.myenv = env

    def set_mix_strategy(self, mix):
        self.mix_str = mix

    def set_str_set(self, set):
        self.str_set = set

    def sample_and_set_str(self, str_dict=None):
        assert self.myenv.training_flag == 0  # Should be training the attacker when sampling defender meta-strategy.
        nn, nn_name = ss.sample_strategy_from_mixed(env=self.myenv, str_set=self.str_set, mix_str=self.mix_str, identity=1, str_dict=str_dict)
        self.nn_name = nn_name
        self.nn_att = nn


# %% imports
import numpy as np
import scipy
import scipy.io
import scipy.stats
import matplotlib
import matplotlib.pyplot as plt

from gaussparams import GaussParams
from mixturedata import MixtureParameters
import dynamicmodels
import measurementmodels
import imm
import ekf
import estimationstatistics as estats

# the given code gives warnings due to division by zero, which is handled.
# the below lines will mute these warnings
import warnings
warnings.filterwarnings("ignore")

# %% plot config check and style setup

# to see your plot config
print(f"matplotlib backend: {matplotlib.get_backend()}")
print(f"matplotlib config file: {matplotlib.matplotlib_fname()}")
print(f"matplotlib config dir: {matplotlib.get_configdir()}")
plt.close("all")

# set styles
try:
    # installed with "pip install SciencePLots" (https://github.com/garrettj403/SciencePlots.git)
    # gives quite nice plots
    plt_styles = ["science", "grid", "ieee", "bright", "no-latex"]
    plt.style.use(plt_styles)
    print(f"pyplot using style set {plt_styles}")
except Exception as e:
    print(e)
    print("setting grid and only grid and legend manually")
    plt.rcParams.update(
        {
            # setgrid
            "axes.grid": True,
            "grid.linestyle": ":",
            "grid.color": "k",
            "grid.alpha": 0.5,
            "grid.linewidth": 0.5,
            # Legend
            "legend.frameon": True,
            "legend.framealpha": 1.0,
            "legend.fancybox": True,
            "legend.numpoints": 1,
        }
    )

# %% load data
use_pregen = True
# you can generate your own data if set to false
if use_pregen:
    data_filename = "data_for_imm.mat"
    loaded_data = scipy.io.loadmat(data_filename)
    Z = loaded_data["Z"].T
    K = loaded_data["K"].item()
    Ts = loaded_data["Ts"].item()
    Xgt = loaded_data["Xgt"].T
else:
    K = 100
    Ts = 2.5
    sigma_z = 2.25
    sigma_a = 0.7  # effective all the time
    sigma_omega = 5e-4 * np.pi  # effective only in turns

    init_x = [0, 0, 2, 0, 0]
    init_P = np.diag([25, 25, 3, 3, 0.0005]) ** 2
    # [Xgt, Z] = simulate_atc(q, r, K, init, false);
    raise NotImplementedError

fig1, ax1 = plt.subplots(num=1, clear=True)
ax1.scatter(*Z.T[:2], marker='o', c='m', s=8, label="Measurement")
ax1.plot(*Xgt.T[:2], c='c', linewidth=1.5, alpha=1, label="Ground truth")
ax1.legend()

# %% tune single filters

# parameters
## for measure model
sigma_z = 3

## for CV model
sigma_a_CV = 0.17

## for CT model
sigma_a_CT = 0.17
sigma_omega = 0.0001 * np.pi

# initial values
init_mean = np.array([0, 0, 2, 0, 0])
init_cov = np.diag([25, 25, 3, 3, 0.0005]) ** 2

init_state_CV = GaussParams(init_mean[:4], init_cov[:4, :4])  # get rid of turn rate
init_state_CT = GaussParams(init_mean, init_cov)  # same init otherwise
init_states = [init_state_CV, init_state_CT]

# create models
measurement_model_CV = measurementmodels.CartesianPosition(sigma_z)
measurement_model_CT = measurementmodels.CartesianPosition(sigma_z, state_dim=5)
CV = dynamicmodels.WhitenoiseAccelleration(sigma_a_CV)
CT = dynamicmodels.ConstantTurnrate(sigma_a_CT, sigma_omega)

# create filters
filters = []
filters.append(ekf.EKF(CV, measurement_model_CV))
filters.append(ekf.EKF(CT, measurement_model_CT))

# allocate
pred = []
upd = []
NIS = np.empty((2, K))
NEES_pred = np.empty((2, K))
NEES_upd = np.empty((2, K))
err_pred = np.empty((2, 2, K))  # (filters, vel/pos, time)
err_upd = np.empty((2, 2, K))  # (filters, vel/pos, time)

# per filter
for i, (ekf_filter, init) in enumerate(zip(filters, init_states)):
    # setup per filter
    updated = init
    ekfpred_list = []
    ekfupd_list = []

    # over time steps
    for k, (zk, x_gt_k) in enumerate(zip(Z, Xgt)):  # bypass any EKF.sequence problems
        # filtering
        predicted = ekf_filter.predict(updated, Ts)
        updated = ekf_filter.update(zk, predicted)

        # store per time
        ekfpred_list.append(predicted)
        ekfupd_list.append(updated)

        # measurement metric
        NIS[i, k] = ekf_filter.NIS(zk, predicted)

    # stor per filter
    pred.append(ekfpred_list)
    upd.append(ekfupd_list)

    # extract means and covs for metric processing
    x_bar = np.array([p.mean for p in ekfpred_list])
    x_hat = np.array([u.mean for u in ekfupd_list])

    P_bar = np.array([p.cov for p in ekfpred_list])
    P_hat = np.array([u.cov for u in ekfupd_list])

    # calculate metrics
    NEES_pred[i] = estats.NEES_sequence_indexed(x_bar, P_bar, Xgt, idxs=np.arange(4))
    NEES_upd[i] = estats.NEES_sequence_indexed(x_hat, P_hat, Xgt, idxs=np.arange(4))

    err_pred[i, 0] = estats.distance_sequence_indexed(x_bar, Xgt, np.arange(2))
    err_pred[i, 1] = estats.distance_sequence_indexed(x_bar, Xgt, np.arange(2, 4))
    err_upd[i, 0] = estats.distance_sequence_indexed(x_hat, Xgt, np.arange(2))
    err_upd[i, 1] = estats.distance_sequence_indexed(x_hat, Xgt, np.arange(2, 4))

# errors
RMSE_pred = err_pred.mean(axis=2)
RMSE_upd = err_upd.mean(axis=2)

# measurement consistency
ANIS = NIS.mean(axis=1)
CINIS = np.array(scipy.stats.chi2.interval(0.9, 2))
CIANIS = np.array(scipy.stats.chi2.interval(0.9, K * 2)) / K
print(f"ANIS={ANIS} with CIANIS={CIANIS}")

# plot individual estimates
fig2, axs2 = plt.subplots(2, 2, num=2, clear=True)
for axu, axl, u_s, rmse_pred, rmse_upd in zip(
    axs2[0], axs2[1], upd, RMSE_pred, RMSE_upd
):
    # ax.scatter(*Z.T)
    x = np.array([data.mean for data in u_s])
    axu.plot(*x.T[:2])
    rmsestr = ", ".join(f"{num:.3f}" for num in (*rmse_upd, *rmse_pred))
    axu.set_title(f"RMSE(p_u, v_u, p_pr, v_pr)\n{rmsestr}", fontsize=8)
    #axu.axis("equal")
    axu.set_xlabel('X')
    axu.set_ylabel('Y', rotation=0)

    if x.shape[1] >= 5:
        axl.plot(np.arange(K) * Ts, x.T[4])
    axl.plot(np.arange(K) * Ts, Xgt[:, 4])
    axl.set_xlabel('time step')

axs2[1, 0].set_ylabel(r"$\omega$", rotation=0)
axs2[0, 0].legend(['CV'])
axs2[0, 1].legend(['CT'])
fig2.tight_layout(w_pad=0.5, h_pad=1.0)

# plot predicted vs ground truth
fig7, axs7 = plt.subplots(1, 2, num=7, clear=True)
fig7.suptitle("Model predictions vs ground truth")

axs7[0].plot(*Xgt.T[:2], label="Ground truth")
x_CV = np.array([data.mean for data in upd[0]])
axs7[0].plot(*x_CV.T[:2], '--r', label="CV model")
axs7[0].legend(fontsize=6)

axs7[1].plot(*Xgt.T[:2], label="Ground truth")
x_CT = np.array([data.mean for data in upd[1]])
axs7[1].plot(*x_CT.T[:2], '--r', label="CT model")
axs7[1].legend(fontsize=6)

# plot errors
fig3, axs3 = plt.subplots(1, 3, num=3, clear=True)
fig3.subplots_adjust(wspace=0.4)

axs3[0].plot(np.arange(K) * Ts, NIS[0], label='CV')
axs3[0].plot(np.arange(K) * Ts, NIS[1], label='CT')
for ci, anis, lab in zip(CIANIS, ANIS, ['CV', 'CT']):
    axs3[0].plot(np.arange(K) * Ts, ci * np.ones((K,)), '--r', label=f'CI_{lab}')
    axs3[0].plot(np.arange(K) * Ts, anis * np.ones((K,)), '--g', label=f"ANIS_{lab}")
axs3[0].set_title("NIS")
axs3[0].set_xlabel('Time step')
axs3[0].legend(fontsize=6, bbox_to_anchor=(-0.3,1))

axs3[1].plot(np.arange(K) * Ts, err_upd[:, 0].T)
# axs3[1].plot(np.arange(K) * Ts, err_upd[1, :, 0])
axs3[1].set_title("pos error (gt)")
axs3[1].set_xlabel('Time step')

axs3[2].plot(np.arange(K) * Ts, err_upd[:, 1].T)
# axs3[2].plot(np.arange(K) * Ts, err_upd[1, :, 1])
axs3[2].set_title("vel error (gt)")
axs3[2].set_xlabel('Time step')

# %% tune IMM by only looking at the measurements
sigma_z = 1.5
sigma_a_CV = 0.1
sigma_a_CT = 0.3
sigma_omega = 0.0001 * np.pi
PI = np.array([[0.95, 0.05], [0.05, 0.95]])
assert np.allclose(PI.sum(axis=1), 1), "rows of PI must sum to 1"

# make model
measurement_model = measurementmodels.CartesianPosition(sigma_z, state_dim=5)
CV = dynamicmodels.WhitenoiseAccelleration(sigma_a_CV, n=5)
CT = dynamicmodels.ConstantTurnrate(sigma_a_CT, sigma_omega)
ekf_filters = []
ekf_filters.append(ekf.EKF(CV, measurement_model))
ekf_filters.append(ekf.EKF(CT, measurement_model))
imm_filter = imm.IMM(ekf_filters, PI)

init_weights = np.array([0.5] * 2)
init_mean = [0] * 5
# HAVE TO BE DIFFERENT: use intuition, eg. diag guessed distance to true values squared.
init_cov = np.diag([1, 1, 1, 1, 1])*100
init_mode_states = [GaussParams(init_mean, init_cov)] * 2  # copy of the two modes
init_immstate = MixtureParameters(init_weights, init_mode_states)

imm_preds = []
imm_upds = []
imm_ests = []
updated_immstate = init_immstate
for zk in Z:
    predicted_immstate = imm_filter.predict(updated_immstate, Ts)
    updated_immstate = imm_filter.update(zk, predicted_immstate)
    estimate = imm_filter.estimate(updated_immstate)

    imm_preds.append(predicted_immstate)
    imm_upds.append(updated_immstate)
    imm_ests.append(estimate)

x_est = np.array([est.mean for est in imm_ests])
prob_est = np.array([upds.weights for upds in imm_upds])

# consistency
NISes_comb = [imm_filter.NISes(zk, pred_k) for zk, pred_k in zip(Z, imm_preds)]
NIS = np.array([n[0] for n in NISes_comb])
NISes = np.array([n[1] for n in NISes_comb])
ANIS = NIS.mean()
CINIS = np.array(scipy.stats.chi2.interval(0.9, 2))
CIANIS = np.array(scipy.stats.chi2.interval(0.9, 2 * K)) / K
print(f"ANIS={ANIS} with CIANIS={CIANIS}")

# plot imm
fig4, axs4 = plt.subplots(2, 2, num=4, clear=True)

axs4[0, 0].plot(*x_est.T[:2], label="est", color="C0")
axs4[0, 0].scatter(*Z.T, label="z", color="C1", s=4)
axs4[0, 0].legend(fontsize=8)

axs4[0, 1].plot(np.arange(K) * Ts, x_est[:, 4], label=r"$\omega$")
axs4[0, 1].legend(fontsize=8)

axs4[1, 0].plot(np.arange(K) * Ts, prob_est, label=r"$Pr(s)$")
axs4[1, 0].legend([r"$Pr(CV)$", r"$Pr(CT)$"], fontsize=4)

axs4[1, 1].plot(np.arange(K) * Ts, NIS, label="NIS")
axs4[1, 1].plot(np.arange(K) * Ts, NISes)

ratio_in_CI = np.sum(np.less_equal(CINIS[0], NIS) * np.less_equal(NIS, CINIS[1])) / K
CI_LABELS = ["CI0", "CI1"]
for ci, cilbl in zip(CINIS, CI_LABELS):
    axs4[1, 1].plot([1, K * Ts], np.ones(2) * ci, "--r", label=cilbl)
axs4[1, 1].text(K * Ts * 1.1, 1, f"{ratio_in_CI} inside CI", rotation=90)
axs4[1, 1].legend(fontsize=4)

fig4.subplots_adjust(wspace=0.25, hspace=0.4)

# plot predicted vs ground truth
fig8, ax8 = plt.subplots(1, num=8, clear=True)
fig8.suptitle("IMM predictions vs ground truth")

ax8.plot(*Xgt.T[:2], label="Ground truth")
ax8.plot(*x_est.T[:2], '--r', label="IMM model")
ax8.legend(fontsize=6)

# %% tune IMM by looking at ground truth
sigma_z = 1.5
sigma_a_CV = 0.4
sigma_a_CT = 0.1
sigma_omega = 0.002 * np.pi
PI = np.array([[0.95, 0.05], [0.05, 0.95]])
assert np.allclose(PI.sum(axis=1), 1), "rows of PI must sum to 1"

# make model
measurement_model = measurementmodels.CartesianPosition(sigma_z, state_dim=5)
CV = dynamicmodels.WhitenoiseAccelleration(sigma_a_CV, n=5)
CT = dynamicmodels.ConstantTurnrate(sigma_a_CT, sigma_omega)
ekf_filters = []
ekf_filters.append(ekf.EKF(CV, measurement_model))
ekf_filters.append(ekf.EKF(CT, measurement_model))
imm_filter = imm.IMM(ekf_filters, PI)

init_weights = np.array([0.5] * 2)
init_mean = [0] * 5
# HAVE TO BE DIFFERENT: use intuition, eg. diag guessed distance to true values squared.
init_cov = np.diag([1, 1, 1, 1, 0.0001])*100**2
init_mode_states = [GaussParams(init_mean, init_cov)] * 2  # copy of the two modes
init_immstate = MixtureParameters(init_weights, init_mode_states)

imm_preds = []
imm_upds = []
imm_ests_pred = []
imm_ests_upd = []

updated_immstate = init_immstate
for zk in Z:
    predicted_immstate = imm_filter.predict(updated_immstate, Ts)
    updated_immstate = imm_filter.update(zk, predicted_immstate)

    estimate_pred = imm_filter.estimate(predicted_immstate)
    estimate_upd = imm_filter.estimate(updated_immstate)

    imm_preds.append(predicted_immstate)
    imm_upds.append(updated_immstate)

    imm_ests_pred.append(estimate_pred)
    imm_ests_upd.append(estimate_upd)

# extract all means and covs
x_bar = np.array([est.mean for est in imm_ests_pred])
P_bar = np.array([est.cov for est in imm_ests_pred])

x_hat = np.array([est.mean for est in imm_ests_upd])
P_hat = np.array([est.cov for est in imm_ests_upd])

x_bar_modes = np.array([[comp.mean for comp in pr.components] for pr in imm_preds])
P_bar_modes = np.array([[comp.cov for comp in pr.components] for pr in imm_preds])

x_hat_modes = np.array([[comp.mean for comp in pr.components] for pr in imm_upds])
P_hat_modes = np.array([[comp.cov for comp in pr.components] for pr in imm_upds])

mode_prob = np.array([upds.weights for upds in imm_upds])

# consistency: NIS
NISes_comb = (imm_filter.NISes(zk, pred_k) for zk, pred_k in zip(Z, imm_preds))
NIS, NISes = [np.array(n) for n in zip(*NISes_comb)]
ANIS = NIS.mean()
CINIS = np.array(scipy.stats.chi2.interval(0.9, 2))
CIANIS = np.array(scipy.stats.chi2.interval(0.9, 2 * K)) / K

# consistency: NEES
NEES_pred = estats.NEES_sequence_indexed(x_bar, P_bar, Xgt, idxs=np.arange(4))
NEESes_pred = np.array(
    [
        estats.NEES_sequence_indexed(x, P, Xgt, idxs=np.arange(4))
        for x, P in zip(x_bar_modes, P_bar_modes)
    ]
)
NEES_upd = estats.NEES_sequence_indexed(x_hat, P_hat, Xgt, idxs=np.arange(4))
NEESes_upd = np.array(
    [
        estats.NEES_sequence_indexed(x, P, Xgt, idxs=np.arange(4))
        for x, P in zip(x_hat_modes, P_hat_modes)
    ]
)

ANEES_pred = NEES_pred.mean()
ANEES_upd = NEES_upd.mean()

CINEES = np.array(scipy.stats.chi2.interval(0.9, 4))
CIANEES = np.array(scipy.stats.chi2.interval(0.9, 4 * K)) / K
print(f"ANIS={ANIS} and CIANIS={CIANIS}")
print(f"ANEES_upd={ANEES_upd}, ANEES_pred={ANEES_pred} and CIANEES={CIANEES}")

#  errors
pos_err = estats.distance_sequence_indexed(x_hat, Xgt, idxs=np.arange(2))
# np.sqrt(np.sum((x_est[:, :2] - Xgt[:, :2]) ** 2, axis=1))
vel_err = estats.distance_sequence_indexed(x_hat, Xgt, idxs=np.arange(2, 4))
# np.sqrt(np.sum((x_est[:, 2:4] - Xgt[:, 2:4]) ** 2, axis=1))
pos_RMSE = np.sqrt(
    np.mean(pos_err ** 2)
)  # not true RMSE (which is over monte carlo simulations)
vel_RMSE = np.sqrt(
    np.mean(vel_err ** 2)
)  # not true RMSE (which is over monte carlo simulations)
pos_peak_deviation = pos_err.max()
vel_peak_deviation = vel_err.max()

rmsestr = ", ".join(f"{num:.3f}" for num in (pos_RMSE, vel_RMSE))
devstr = ", ".join(f"{num:.3f}" for num in (pos_peak_deviation, vel_peak_deviation))
# plot
fig5, axs5 = plt.subplots(2, 2, num=5, clear=True)
axs5[0, 0].plot(*x_hat.T[:2], label="est", color="C0")
axs5[0, 0].scatter(*Z.T, label="z", color="C1")
axs5[0, 0].legend(fontsize=8)
axs5[0, 0].set_title(f"RMSE(p, v) = {rmsestr}\npeak_dev(p, v) = {devstr}.0")
axs5[0, 1].plot(np.arange(K) * Ts, x_hat[:, 4], label=r"$\hat{\omega}$")
axs5[0, 1].plot(np.arange(K) * Ts, Xgt[:, 4], label=r"$\omega_{gt}$")
axs5[0, 1].legend(fontsize=8)
for s in range(len(ekf_filters)):
    axs5[1, 0].plot(np.arange(K) * Ts, prob_est[:, s], label=rf"$Pr({['CV','CT'][s]})$")
axs5[1, 0].legend(fontsize=4)
axs5[1, 1].plot(np.arange(K) * Ts, NIS, label="NIS")
axs5[1, 1].plot(np.arange(K) * Ts, NISes)

ratio_in_CI = np.sum(np.less_equal(CINIS[0], NIS) * np.less_equal(NIS, CINIS[1])) / K
CI_LABELS = ["CI0", "CI1"]
for ci, cilbl in zip(CINIS, CI_LABELS):
    axs5[1, 1].plot([1, K * Ts], np.ones(2) * ci, "--r", label=cilbl)
axs5[1, 1].text(K * Ts * 1.1, 1, f"{ratio_in_CI} inside CI", rotation=90)
axs5[1, 1].legend(fontsize=6)

fig6, axs6 = plt.subplots(2, 2, sharex=True, num=6, clear=True)
axs6[0, 0].plot(np.arange(K) * Ts, pos_err)
axs6[0, 0].set_ylabel("position error")
axs6[0, 1].plot(np.arange(K) * Ts, vel_err)
axs6[0, 1].yaxis.set_label_position("right")
axs6[0, 1].set_ylabel("velocity error")
axs6[1, 0].plot(np.arange(K) * Ts, NIS)
axs6[1, 0].plot(np.arange(K) * Ts, NISes)
ratio_in_CI = np.mean(np.less_equal(CINIS[0], NIS) * np.less_equal(NIS, CINIS[1]))
axs6[1, 0].set_ylabel(f"NIS: {ratio_in_CI}% in CI")
axs6[1, 0].plot([0, Ts * (K - 1)], np.repeat(CINIS[None], 2, 0), "r--")
# axs6[1, 0].text(K * Ts * 1.1, -2, f"{ratio_in_CI}% inside CI", rotation=90)
axs6[1, 0].set_ylim([0, 2 * CINIS[1]])

axs6[1, 1].plot(np.arange(K) * Ts, NEES_pred)
axs6[1, 1].plot(np.arange(K) * Ts, NEES_upd)
# axs6[1, 1].plot(np.arange(K) * Ts, NISes)
ratio_in_CI_nees = np.mean(
    np.less_equal(CINEES[0], NEES_upd) * np.less_equal(NEES_upd, CINEES[1])
)
# axs6[1, 1].text(K * Ts * 1.1, -2, f"{ratio_in_CI_nees}% inside CI", rotation=90)
axs6[1, 1].yaxis.set_label_position("right")
axs6[1, 1].set_ylabel(f"NEES: {ratio_in_CI_nees}% in CI")
axs6[1, 1].plot([0, Ts * (K - 1)], np.repeat(CINEES[None], 2, 0), "r--")
axs6[1, 1].set_ylim([0, 2 * CINEES[1]])
# axs6[1, 1].text(K * Ts * 1.1, -2, f"{ratio_in_CI_nees}% inside CI", rotation=90)

# plot predicted vs ground truth
fig9, ax9 = plt.subplots(1, num=9, clear=True)
fig9.suptitle("IMM predictions vs ground truth")

ax9.plot(*Xgt.T[:2], label="Ground truth")
ax9.plot(*x_hat.T[:2], '--r', label="IMM model")
ax9.legend(fontsize=10)

# %%

# %%


import random

suits=["D", "C", "H", "S"]
ranks=["2","3","4","5","6","7","8","9","0","A","J","Q","K"]

#### Task 1
# read file
filename = input("Please type in the game name(shuffledDeck.txt): ")
#filename = "shuffledDeck.txt" # !!!!!!!!! No usful, for test only!!!!!!!!!

try:
    f = open(filename, "r")
except:
    print("file open error.")
    exit(0)

content = f.read()
f.close()
shuffled_cards = content.strip().split('\n')

# validation
if len(shuffled_cards) != 52:
    raise Exception("card number error: the number of cards not equal 52.")
for card in shuffled_cards:
    if len(card)!=2:
        # version 1
        # print("card format error: lenght of a card not equal to 2.")
        # exit(0)

        # version2
        raise Exception("card format error: lenght of a card not equal to 2.")
    if card[1] not in suits:
        raise Exception("card format error: invalid suit.")
    if card[0] not in ranks:
        raise Exception("card format error: invalid rank.")
    if shuffled_cards.count(card) != 1:
        raise Exception("card number error: dupilicate card found.")

#### Task 2
Player1 = []
Player2 = []
romdon_int = random.randint(0,1)

# version 1
# choice = ["Player1","Player2"]
# who_take_the_card = choice[romdon_int]
# for card in shuffled_cards:
#     if who_take_the_card == "Player1":
#         Player1.append(card)
#         who_take_the_card = "Player2"
#     else:
#         Player2.append(card)
#         who_take_the_card = "Player1"

# version 2
# choice = ["1", "2"]
# who_take_the_card = choice[romdon_int]
# appending_list = Player1
# for card in shuffled_cards:
#     appending_list.append(card)
#     if who_take_the_card=="1":
#         appending_list = Player2
#         who_take_the_card = "2"
#     else:
#         appending_list = Player1
#         who_take_the_card = "1"

# version 3
for i in range(len(shuffled_cards)):
    if i % 2 == romdon_int:
        Player1.append(shuffled_cards[i].upper())
    else:
        Player2.append(shuffled_cards[i].upper())

# print(shuffled_cards)
# print(Player1)
# print(Player2)

#### Task 3
while True:
    nbWarCards = input("Enter the number of cards face-down when war(from 1 to 3): ")
    if nbWarCards in '123' and len(nbWarCards)==1:#['1','2','3']:
        break
    else:
        print("Invalid input, please enter again.")

nbWarCards = int(nbWarCards)

#### Task 4
def compareCards(card1, card2):
    rank1 = ranks.index(card1[0])
    rank2 = ranks.index(card2[0])
    if rank1 > rank2:
        return 1
    elif rank1 < rank2:
        return -1
    else:
        return 0

# print(compareCards("KS", "KC"))
# print(compareCards("KS", "2C"))
# print(compareCards("2S", "KC"))

#### Task 5
class OnTable:
    def __init__(self):
        self.__cards = []
        self.__faceUp= []

    def place(self, player, card, hidden):
        if player == 1:
            self.__cards.insert(0, card)
            self.__faceUp.insert(0, not hidden)
        else:
            self.__cards.append(card)
            self.__faceUp.append(not hidden)

    def cleanTable(self):
        out = self.__cards
        self.__cards = []
        self.__faceUp= []
        return out

    def __str__(self):
        str_exp = '['
        for i in range(len(self.__cards)):
            if i>0:
                str_exp += ', '

            if self.__faceUp[i]:
                str_exp += self.__cards[i]
            else:
                str_exp += 'XX'
        str_exp += ']'
        return str_exp

# myTable = OnTable()
# print(myTable)
# myTable.place(1, "KC", False)
# print(myTable)
# myTable.place(2, "KS", False)
# print(myTable)
# myTable.place(1, "0C", True)
# myTable.place(2, "0S", True)
# print(myTable)

#Player2 = Player2[:3] # !!!!!!!!! No usful, for test only!!!!!!!!!
#### Task 6
end_game = False
cards_on_table = OnTable()
while not end_game:
    face_up_1 = Player1.pop(0)
    cards_on_table.place(1, face_up_1, False)
    face_up_2 = Player2.pop(0)
    cards_on_table.place(2, face_up_2, False)
    print(cards_on_table)
    print("Player1:", len(Player1))
    print("Player2:", len(Player2))
    input("Press return key to continue.")
    if compareCards(face_up_1, face_up_2)==1:
        Player1.extend(cards_on_table.cleanTable())
    elif compareCards(face_up_1, face_up_2)==-1:
        Player2.extend(cards_on_table.cleanTable())
    else:
        if len(Player1) < nbWarCards:
            Player2.extend(cards_on_table.cleanTable())
            Player2.extend(Player1)
            Player1 = []
            end_game = True
        else:
            for i in range(nbWarCards):
                war_card = Player1.pop(0)
                cards_on_table.place(1, war_card, True)
            if len(Player2) < nbWarCards:
                Player1.extend(cards_on_table.cleanTable())
                Player1.extend(Player2)
                Player2 = []
                end_game = True
            else:
                for i in range(nbWarCards):
                    war_card = Player2.pop(0)
                    cards_on_table.place(2, war_card, True)

    if len(Player1)==0:
        end_game = True
        print("player 1 win")
    if len(Player2)==0:
        end_game = True
        print("player 2 win")


#
# Plot for generating Figures 10.8, 10.9 and 10.10 of the Pathway Modeling book
#
import pylab as plt; import numpy as np
import lmfit; import tellurium as te
import time; import copy; import random; import sys
import emcee
import time

# Fix the data, not change in the errors in the data between runs
np.random.seed (126)
#np.random.seed (2)

nsims = 0

r = te.loada("""
   S1 -> S2; k1*S1;
   S2 -> S3; k2*S2;

   S1 = 1; S2 = 0; S3 = 0;
   k1 = 0.45; k2 = 0.15;
""")

toFit = ['k1', 'k2']; nParameters = len (toFit)

nDataPoints = 32
timeToSimulate = 20
# Create the experimental data
# First column is time, other columns are species
m = r.simulate (0, timeToSimulate, nDataPoints)

# Plot ground truth
plt.figure(figsize=(7,5))
plt.plot (m['time'], m['[S1]'], linewidth=3, label='S1')
plt.plot (m['time'], m['[S2]'], linewidth=3, label='S2')
plt.plot (m['time'], m['[S3]'], linewidth=3, label='S3')
plt.xlabel('Time', fontsize=18)
plt.ylabel('Concentation', fontsize=18)
plt.legend (fontsize=18)
plt.savefig  ('c:\\tmp\\groundtruth_simpleModel.pdf')
plt.show()

# Change this index to use a different variables
# These are the variables that will be used to fit the model
SIndexList = [3] # 1 = S1, 2 = S2, 3 = S3
x_data = m['time']; # Extract the time column

# Extract the SIndexList columns into y_data
y_data = []
for i in range (len(SIndexList)):
    y_data.append (m[:,SIndexList[i]])

# Create the 'experimental' data by adding noise
y_noise = np.empty([nDataPoints])
for k in range (len (SIndexList)):
   for i in range (0, len (y_data[k])):
       y_noise[i] = 0.1 # standard deviation of noise
       # Might be better to use lognormal here?
       ln = np.random.normal (0, y_noise[i]);
       while y_data[k][i] + ln < 0:
             ln = np.random.normal (0, y_noise[i]);
       y_data[k][i] = y_data[k][i] + ln # Add noise

# Plot ground truth
#plt.figure(figsize=(7,5))
index = 0
for i in SIndexList:
    plt.plot (m['time'], y_data[index], 'or', linewidth=3, label='S' + str (SIndexList));
    plt.xlabel('Time', fontsize=18)
    plt.ylabel('Concentation', fontsize=18)
    plt.legend (fontsize=18)
    plt.savefig  ('c:\\tmp\\groundtruthS' + str (i) + '.pdf')
    plt.show()
    index = index + 1

# Compute the simulation at the current parameter values
# Return the time series variable indicated by SIndex
# This is very inefficient since it has to be called for
# each variable we use to fit the model. Eg if SIndexList = [1,2,3]
# then we'll have to call this three times, ideally we should call
# it once and collect all SIndex variables at once.
def computeSimulationData(p, SIndex):
    global nsims
    r.reset()
    pp = p.valuesdict()
    for i in range(0, nParameters):
       r.model[toFit[i]] = pp[toFit[i]]
    m = r.simulate (0, timeToSimulate, nDataPoints)
    nsims = nsims + 1
    return m[:,SIndex]

# Compute the residuals between objective and experimental data
#def weightedResiduals(p):
#    return (y_data - my_ls_func (p))/y_weight

# Compute the residuals between objective and experimental data
def residuals(p):
    global y_data, SIndexList
    y1 = (y_data[0] - computeSimulationData (p, SIndexList[0]));
    y1 = np.concatenate ((y1, ))
    for k in range (1, len (SIndexList)):
        y1 = np.concatenate ((y1, (y_data[k] - computeSimulationData (p, SIndexList[k]))))
    return y1

def unWeightedResiduals(p):
    y1 = (y_data[0] - computeSimulationData (p, SIndexList[0]))
    return y1

# Randomize the work of the optimizer
rd = int (time.time())
print ('Random number = ', rd)
np.random.seed (rd)

# Set up the parameters that we will fit
params = lmfit.Parameters()
params.add('k1', value=1, min=0, max=10)
params.add('k2', value=1, min=0, max=10)

# Compute the fitted parameters
minimizer = lmfit.Minimizer(residuals, params)
result = minimizer.minimize(method='differential_evolution')#'leastsqr')
lmfit.report_fit(result.params, min_correl=0.5)
result = minimizer.minimize(method='leastsqr')
lmfit.report_fit(result.params, min_correl=0.5)

# Assign fitted parameters to the model
r.reset()
for i in range(0, nParameters):
   r.model[toFit[i]] = result.params[toFit[i]].value
m = r.simulate (0, timeToSimulate, 100)

# Set up some convenient font sizes
plt.rcParams.update({'axes.titlesize': 16})
plt.rcParams.update({'axes.labelsize': 14})
plt.rcParams.update({'xtick.labelsize': 13})
plt.rcParams.update({'ytick.labelsize': 13})

# Plot experimental data
plt.figure (figsize=(7,5))
ldata, = plt.plot (x_data, y_data[0], 'dm', markersize=8)
for k in range (1, len (SIndexList)):
    ldata2, = plt.plot (x_data, y_data[k], 'dm', markersize=8)

# Plot the fitted lines for S1, S2 and S3
# Retrieve lfit to use in the legend
lfit, = plt.plot (m[:,0], m[:,1], '-g', linewidth=2)
plt.plot (m[:,0], m[:,2], '-g', linewidth=2)
plt.plot (m[:,0], m[:,3], '-g', linewidth=2)

# Plot the residuals
resids = unWeightedResiduals(result.params)
lresids, = plt.plot (x_data, resids, 'bo', markersize=6)
plt.vlines(x_data, [0], resids, color='r', linewidth=2)

theResiduals = copy.deepcopy (resids)
#finalFittedData = copy.deepcopy (y_data)
originalYData = copy.deepcopy (y_data)

plt.tick_params(axis='both', which='major', labelsize=16)
plt.xlabel('Time')
plt.ylabel("Concentration", fontsize=16)
plt.legend([ldata, lfit, lresids],['Data', 'Best fit', 'Residuals'], loc=0, fontsize=10)
plt.axhline (y=0, color='k')
plt.savefig('fittedCurves.pdf')
plt.show()

to = time.time()
# Boostrapping analysis
if True:
    k1Sample = []; k2Sample = []; NSamples = 6000

    print ("\nStart Monte Carlo Estimation")

    chis = []
    to = time.time()
    nsims = 0
    # Start the Monte Carlo parameter confidence estimation
    for n in range (NSamples):
        if n % 100 == 0:
           print (n)

        for j in range (len (y_data[0])):
            for i in range (len (SIndexList)):
                y_data[i][j] = originalYData[i][j] + random.choice (theResiduals)

        #result = minimizer.minimize(method='differential_evolution')
        result = minimizer.minimize(method='leastsqr')
        result = minimizer.minimize(method='leastsqr')
        if result.success == False:
            print ('Failed')
        # Not all fits will work so we test against a threshold
        if result.redchi < 500:
           chis.append (result.redchi)
           pp = result.params.valuesdict()
           # Collect the fitted parameters
           k1Sample.append (pp['k1'])
           k2Sample.append (pp['k2'])
        else:
           print (result.redchi)

    print ("Finished Monte Carlo Estimation.....")
    if len (k1Sample) == 0:
        print ("Failed to compute Monto Carlo estimate, unable to generate fits, bad model or insufficient data")
        sys.exit("Unable to continue")

    print ('Time for Bootstrap = ', time.time() - to)
    print ('Number of simulations = ', nsims)

    # Compute the mean values of the k1 and k2 samples
    meank1 = np.mean (k1Sample); meank2 = np.mean (k2Sample)

    # Compute 95% percentiles
    plusk1 = np.percentile (k1Sample, 97.5) - meank1
    minusk1 = meank1 - np.percentile (k1Sample, 2.5)

    plusk2 = np.percentile (k2Sample, 97.5) - meank2
    minusk2 = meank2 - np.percentile (k2Sample, 2.5)

    # Note that the limits are not symmetric
    print ('Computed 95 percent percentiles from the Monte Carlo run:')
    print ('k1: ', meank1, "+/- ", plusk1, minusk1)
    print ('k2: ', meank2, "+/- ", plusk2, minusk2)

    plt.hist (k1Sample, 10, range=[0,1], color='peachpuff')
    plt.ylabel('Frequency');
    plt.xlabel ('k1'); plt.title ('k1 variation')
    plt.savefig('k1Distribution.pdf')
    plt.show()
    plt.hist (k2Sample, 10, range=[0,1], color='peachpuff')
    plt.ylabel('Frequency');
    plt.xlabel ('k2'); plt.title ('k2 variation');
    plt.savefig('k2Distribution.pdf')
    plt.show()

    # Scatter plot of k1 versus k2
    plt.xlim((0.2, 0.65)); plt.ylim ((0.0, 0.3))
    plt.plot (k1Sample, k2Sample, '.', color='cornflowerblue')
    plt.xlabel ('k1'); plt.ylabel ('k2')
    plt.title ('Scatter plot of k1 against k2')
    plt.savefig('k1_k2_scatter.pdf')
    plt.show()
print ('Time for monte carlo = ', time.time() - to)

# Chi Square Analysis
if False:
    to = time.time()
    ci = lmfit.conf_interval(minimizer, result)
    print ('Time for conf Interval = ', time.time() - to)
    lmfit.printfuncs.report_ci(ci)
    #
    cx, cy, grid = lmfit.conf_interval2d(minimizer, result, 'k1', 'k2', 100, 100)
    plt.contourf(cx, cy, grid, np.linspace(0, 1, 11))
    plt.xlabel('k1')
    plt.colorbar()
    plt.ylabel('k2')

to = time.time()
# MCMC Analysis
if False:
    nsims = 0
    np.set_printoptions(precision=4, linewidth=150)

    print (result.covar)

    from tqdm import tqdm
    to = time.time()
    res = minimizer.emcee(params=params,burn=5000, steps=15000, thin=20,is_weighted=False,progress=True)
    #res = lmfit.minimize(residuals, method='emcee', nan_policy='omit', burn=500, steps=5000, thin=20, progress=True,
    #                         params=params, is_weighted=False)
    import corner
    print('Plot results of emcee...')
    corner.corner(res.flatchain, labels=res.var_names, truths=list(res.params.valuesdict().values()), range=[(0,0.3),(0,1.2), 1])
    plt.savefig ('c:\\tmp\emcee.pdf')
    print ('Time for emcee = ', time.time() - to)
    print ('Number of simulations = ', nsims)


residents_ages = [[14,15,18,50,54], [9,9,39,42], [25,22,55,54], [25,28], [2,1,28,30]]
# A list of lists representing the ages of individuals in a household
residents_ages = [[14,15,18,50,54], [9,9,39,42], [25,22,55,54], [25,28], [2,1,28,30]]

# Example 1: Will return an entire sublist
print(residents_ages[0])

# Example 2: Will return the first age in the second sublist
print(residents_ages[1][0])
# Example 1 Output
[14,15,18,50,54]

# A list of lists representing the ages of individuals in a household
residents_ages = [[14,15,18,50,54], [9,9,39,42], [25,22,55,54], [25,28], [2,1,28,30]]

for household in residents_ages:
    print(household)

# A list of lists representing the ages of individuals in a household
residents_ages = [[14,15,18,50,54], [9,9,39,42], [25,22,55,54], [25,28], [2,1,28,30]]

for household in residents_ages:
    for age in household: # iterates over the ages in each sublist
        print(age)
for household in residents_ages:
    for household in residents_ages:
        for age in household:
            print(age)

num_list = [1, 2, 3, 4, 5]
alpha_list = ['a', 'b', 'c']

# A list of quiz grades grouped by student
quiz_grades = [[56, 74, 82], [97, 65, 87], [88, 92, 93], [82, 74, 79]]

for student in quiz_grades:
    for grade in student:
        print(grade)

quiz_grades = [[56, 74, 82], [97, 65, 87], [88, 92, 93], [82, 74, 79]]

# Empty list to hold letter grades for all students
letter_grades = []

for student in quiz_grades:
    # An empty list to translate the grades for each student
    student_grade = []
    for grade in student:
        if grade >= 90:
            student_grade.append("A")
        elif grade >= 80:
            student_grade.append("B")
        else:
            student_grade.append("<80")
    letter_grades.append(student_grade)

residents_ages = [[14,15,18,50,54], [9,9,39,42], [25,22,55,54], [25,28], [2,1,28,30]]

residents_ages = [[14,15,18,50,54], [9,9,39,42], [25,22,55,54], [25,28], [2,1,28,30]]

# Calculates the number of eligible voters per household
def number_of_voters(neighborhood):
    # A list to hold the number of eligible voters for each household.
    eligible_voters = []

    for house in neighborhood:
        can_vote = 0
        for age in house:
           if age >= 18:
               can_vote +=1
        eligible_voters.append(can_vote)

    return eligible_voters

print(number_of_voters(residents_ages))

patients = [[60, 72, 65, 78], [75, 80, 90, 87], [60, 62, 60, 65], [70, 75, 72, 68]]
for record in patients:
    sum_hr = 0
    for heart_rate in record:
        sum_hr += heart_rate
    average_hr = sum_hr/len(record)
    print(average_hr)

for record in patients: # outer loop to iterate over each patient
  sum_hr = 0 # counter to add each hr to
  for heart_rate in record: # inner loop to iterate over each heart rate
    sum_hr += heart_rate
  average_hr = sum_hr/len(record) # calculate average when inner loop completes
  print(average_hr)

numbers = [3, 6, 9, 12, 15]
over_ten = []

for num in numbers:
    if num > 10:
       over_ten.append(num)

print(over_ten)

numbers = [3, 6, 9, 12, 15]

over_ten = list(filter(lambda x: x>10, numbers))
print(over_ten)

numbers = [3, 6, 9, 12, 15]

over_ten = [num for num in numbers if num > 10]
print(over_ten)

# List comprehension with a string
my_string = "Python"
string_chars = [char for char in my_string]

# Output
['P', 'y','t', 'h', 'o', 'n']

# List comprehension with range()
triple_range = [num * 3 for num in range(0,10)]

# Output
[0, 3, 6, 9, 12, 15, 18, 21, 24, 27]

numbers = [2,4,6,8,10]

m = list(map(lambda num: num ** 2, numbers))

numbers = [2,4,6,8,10]

m = [num ** 2 for num in numbers]

numbers = list(range(0,30))
plus_one = [num + 1 for num in numbers]

string_numbers = [str(num) for num in numbers]
print(string_numbers)

customer_id = [1023.0, 1024.0, 1025.0, 1026.0, 1027.0, 1028.0, 1029.0, 1030.0, 1031.0, 1032.0, 1033.0, 1034.0, 1035.0, 1036.0, 1037.0, 1038.0, 1039.0, 1040.0, 1041.0, 1042.0, 1043.0, 1044.0, 1045.0, 1046.0, 1047.0, 1048.0, 1049.0, 1050.0, 1051.0, 1052.0, 1053.0, 1054.0, 1055.0]

def clean_ids(id_list):
    return [int(cust_id) for cust_id in id_list]

print(clean_ids(customer_id))
print([(num-5)**2 for num in numbers])

temp_celsius = [23, 27, 32, 36, 41]
temperature_fahr = []

for temperature in temp_celsius:
    fahr = (temperature * 9/5) + 32
    temperature_fahr.append(fahr)

[(c*9/5) + 32 for c in temp_celsius]
pathstream = [num for num in range(100) if num % 5 == 0 if num % 3 == 0]

path = [num for num in range(100) if num % 5 == 0]
pathstream = ["pathstream" if num % 15 == 0 else num for num in range(100)]

pathstream = ["pathstream" if num % 15 == 0 else "path" if num % 3 == 0 else "stream" if num % 5 == 0 else num for num in range(100)]

def pathstream(num):
    if num % 15 == 0:
        return "pathstream"
    elif num % 3 == 0:
        return "path"
    elif num % 5 == 0:
        return "stream"
    else:
        return num

pathstream_list = [pathstream(num) for num in range(100)]
def negatives_only(prices):
    return [p for p in prices if p < 0]

def increase_price(prices):
    return [round(p*1.10,2) for p in prices if p > 0]

def clean_pricelist(prices):
    return [0 if p < 0 else p for p in prices]
nested_list = [[0,1,2,3],[4,5,6,7], [8,9,10,11]]
flat_list = []

for sublist in nested_list: # Iterate over the sublists
    for item in sublist:  # Iterate over the items in each sublist
        flat_list.append(item)

print(flat_list)
nested_list = [[0,1,2,3],[4,5,6,7], [8,9,10,11]]

flat_list = [item for sublist in nested_list for item in sublist]

nested_list = [["a","b"], ["c","d"]]

new_list = [item*2 for sublist in nested_list for item in sublist]

matrix = [[0, 1, 2, 3, 4],
          [0, 1, 2, 3, 4],
          [0, 1, 2, 3, 4],
          [0, 1, 2, 3, 4],
          [0, 1, 2, 3, 4]]

matrix = []

for row in range(5):
    new_row = [] # creating the sublist
    for num in range(5):
        new_row.append(num) # adding numbers to sublist
    matrix.append(new_row) # add sublist to matrix
print(matrix)

matrix = [[col for col in range(5)] for row in range(5)]

box_measurements_in = [[18, 18, 16],
                       [18, 18, 24],
                       [16, 12, 12],
                       [24, 20, 46]]

box_measurements_cm = [[item * 2.54 for item in sublist] for sublist in box_measurements_in]
print(box_measurements_cm)

matrix = [[0,1], [2,3], [4,5]]
transposed_matrix = []

for i in range(len(matrix)-1):
  transposed_row = []
  for row in matrix:
    transposed_row.append(row[i])
  transposed_matrix.append(transposed_row)

# Output
[[0, 2, 4], [1, 3, 5]]

matrix = [[0,1],[2,3],[4,5]]

transposed_matrix = [[row[i] for row in matrix] for i in range(len(matrix)-1)]

# Output
[[0, 2, 4], [1, 3, 5]]

population_by_country = [
    ["Year", 2017, 2018, 2019],
    ["Canada", 36.54, 37.06, 37.59],
    ["US", 325.1, 327.2, 328.2],
    ["UK", 65.82, 66.27, 66.65]
]

transposed_population = [['Year', 'Canada', 'US', 'UK'],
                         [2017, 36.54, 325.1, 65.82],
                         [2018, 37.06, 327.2, 66.27],
                         [2019, 37.59, 328.2, 66.65]]
new_list = [[col for col in range(3)] for row in range(3)]
print(new_list)
new_list = [[row[i] for row in nested_list] for i in range(len(nested_list))]

nested_list = [[0,1,2,3],[4,5,6,7], [8,9,10,11]]
flat_list = []

for sublist in nested_list: # Iterate over the sublists
    for item in sublist:  # Iterate over the items in each sublist
        flat_list.append(item)

print(flat_list)

# Output
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

planets = [["Mercury", 57.91], ["Venus", 108.2], ["Earth", 149.6],["Mars", 227.9],["Jupiter", 778.5]]

distant_planets = [planet for sublist in planets for planet in sublist if sublist[1] > 149.6]

distant_planets = []
for sublist in planets:
    distance = sublist[1]
    if distance > 149.6:
        for details in sublist:
            distant_planets.append(details)


import json
import request_response_utils

#primecore_status check.process.status 3 0 kb # primecore status is wrong on {host.name},last()>2,4,1
def create_trigger(authID,trigger_name,expression,severity,status):
    data = json.dumps({
        "jsonrpc": "2.0",
        "method": "trigger.create",
        "params": {
            "description": trigger_name,
            "expression": expression,
            "priority": severity,
            "status":status
        },
        "auth": authID,
        "id": 1
    })

    response = request_response_utils.request_and_response(data)
    return response['result']['triggerids'][0]


from collections import defaultdict

class Solution:
    """
    @param source : A string
    @param target: A string
    @return: A string denote the minimum window, return "" if there is no such a string
    """
    def minWindow(self, source , target):
        target_map = defaultdict(int)
        for c in target:
            target_map[c] += 1

        n = len(source)

        source_map = defaultdict(int)
        matched = 0
        goal = len(target_map)
        ans = ""
        min_len = n + 1
        j = 0
        for i in range(n):
            while j < n and matched < goal:
                source_map[source[j]] += 1
                if source_map[source[j]] == target_map[source[j]]:
                    matched += 1
                j += 1
            if j - i < min_len and matched == goal:
                ans = source[i: j]
                min_len = len(ans)
            if source[i] in target_map:
                if source_map[source[i]] == target_map[source[i]]:
                    matched -= 1
                source_map[source[i]] -= 1

        return ans


gasses = [
    "acetone",
    "acetylene",
    "acidgas",
    "ammonia",
    "carbon-dioxide",
    "carbon-sulfide",
    "chlorine",
    "chloromethane",
    "coal-gas",
    "coalbed-gas",
    "coke-oven-gas",
    "cold-air",
    "cold-clean-air",
    "combustion-mixture1",
    "critical-steam",
    "diborane",
    "dirty-syngas",
    "dms",
    "dry-gas-stream",
    "ethylene",
    "flue-gas",
    "gas-stream",
    "helium",
    "helium-rich-gas",
    "helium3",
    "high-purified-gas",
    "hot-air",
    "hot-reaction-gas",
    "hot-syngas",
    "hydrogen",
    "methane",
    "natural-gas",
    "nexelit-slurry",
    "nitrogen",
    "outlet-gas-01",
    "outlet-gas-02",
    "outlet-gas-03",
    "outlet-gas-04",
    "oxygen",
    "phosphine-gas",
    "pressured-air",
    "pressured-hydrogen",
    "pressured-steam",
    "propene",
    "proto-tholins",
    "proton",
    "pure-natural-gas",
    "purest-nitrogen-gas",
    "purier-helium",
    "purified-air",
    "purified-natural-gas",
    "purified-syngas",
    "raw-gas",
    "refined-natural-gas",
    "refsyngas",
    "residual-gas",
    "rich-gas",
    "syngas",
    "tholins",
    "tritium",
    "vacuum"
]

liquids = [
    "acetic-acid",
    "acid-solvent",
    "acid-strip-solution",
    "acrolein",
    "aerofloat-15",
    "al-pulp-01",
    "al-pulp-02",
    "al-pulp-03",
    "al-pulp-04",
    "al-tailings",
    "alamac",
    "anthraquinone",
    "armac-12",
    "aromatics",
    "bacteria-1",
    "bacteria-2",
    "benzene",
    "bitumen-froth",
    "bitumen",
    "black-liquor",
    "blood",
    "blue-liquor",
    "boric-acid",
    "btx",
    "chloroethanol",
    "chromite-concentrate",
    "chromite-fines",
    "chromite-mix",
    "chromite-pulp-01",
    "chromite-pulp-02",
    "chromite-pulp-03",
    "chromite-pulp-04",
    "chromite-pulp-05",
    "chromite-pulp-06",
    "chromite-pulp-07",
    "chromite-solution",
    "chromium-rejects",
    "coal-fines",
    "coal-pulp-01",
    "coal-pulp-02",
    "coal-pulp-03",
    "coal-pulp-04",
    "coal-pulp-05",
    "coal-slime-overflow",
    "coal-slurry",
    "coal-under-pulp",
    "condensates",
    "condensed-distillate",
    "conditioned-fines",
    "copper-pregnant-solution",
    "copper-pulp-01",
    "copper-pulp-02",
    "copper-pulp-03",
    "copper-pulp-04",
    "copper-solution",
    "creosote",
    "cresylic-acid",
    "crude-oil",
    "cumene",
    "cyanic-acid",
    "deuterium",
    "diamond-concentrate",
    "diamond-tailings",
    "diesel",
    "dimethyldichlorosilane",
    "dirty-water",
    "drilling-fluid-0",
    "drilling-fluid-1",
    "drilling-fluid-2",
    "drilling-fluid-3",
    "drilling-waste",
    "ech",
    "etching",
    "ethanolamine",
    "ethylene-chlorohydrin",
    "ethylene-glycol",
    "fines-pulp",
    "fluid-unknown",
    "fuel-oil",
    "gasoline",
    "glycerol",
    "gold-solution",
    "grease",
    "heavy-oil",
    "heavy-water",
    "high-ash-fines",
    "high-distillate",
    "high-grade-quartz-pulp",
    "hot-residual-mixture",
    "hydrogen-chloride",
    "hydrogen-peroxide",
    "industrial-solvent",
    "iron-pulp-01",
    "iron-pulp-02",
    "iron-pulp-03",
    "iron-pulp-04",
    "iron-pulp-05",
    "iron-pulp-06",
    "iron-pulp-06-thick",
    "iron-pulp-07",
    "iron-slime",
    "jig-concentrate",
    "jig-grade1",
    "jig-grade2",
    "jig-grade3",
    "kerosene",
    "kimberlite-pulp",
    "light-oil",
    "liquid-helium",
    "liquid-nitrogen",
    "liquid-petgas",
    "liquid-pure-air",
    "liquid-rich-gas",
    "low-distillate",
    "ltn-cargo-alert",
    "ltn-cargo-warning",
    "ltn-depot-empty",
    "ltn-depot-warning",
    "lubricant",
    "medium-distillate",
    "methanal",
    "methanol",
    "mibc",
    "molten-aluminium",
    "molten-chromium",
    "molten-copper",
    "molten-glass",
    "molten-iron",
    "molten-lead",
    "molten-nexelit",
    "molten-nickel",
    "molten-silver",
    "molten-solder",
    "molten-stainless-steel-p1",
    "molten-stainless-steel-p2",
    "molten-stainless-steel-p3",
    "molten-stainless-steel",
    "molten-steel",
    "molten-super-steel-p1",
    "molten-super-steel-p2",
    "molten-super-steel",
    "molten-tin",
    "molten-titanium",
    "molten-zinc",
    "molybdenite-pulp",
    "molybdenum-pulp",
    "msa",
    "naphtha",
    "nexelit-pulp-01",
    "nexelit-pulp-02",
    "nexelit-pulp-03",
    "nexelit-pulp-04",
    "nexelit-refined-pulp",
    "nickel-overflow",
    "nickel-prepared-solution",
    "nickel-pulp-01",
    "nickel-pulp-02",
    "nickel-pulp-03",
    "nickel-pulp-04",
    "nickel-slime",
    "nickel-tailings",
    "niobium-complex",
    "nitrobenzene",
    "oil-sand-slurry",
    "olefin",
    "oleochemicals",
    "organic-solvent",
    "organic-vanadate",
    "petroleum-sulfonates",
    "phosphoric-acid",
    "phosphorous-acid",
    "phytoplankton",
    "polybutadiene",
    "pregnant-solution",
    "prepared-chromium",
    "prepared-nickel-pulp",
    "prepared-quartz",
    "pressured-vpulp",
    "pressured-water",
    "processed-light-oil",
    "purified-ti-pulp",
    "quartz-pulp-01",
    "quartz-pulp-02",
    "ralesia-extract",
    "rare-earth-mud",
    "raw-ralesia-extract",
    "ree-slurry",
    "ree-solution",
    "residual-mixture",
    "residual-oil",
    "scrude",
    "sl-01",
    "sl-02",
    "sl-03",
    "slacked-lime",
    "slz-pulp-01",
    "slz-pulp-02",
    "soaked-gel",
    "soda-ash",
    "stripped-distillate",
    "styrene",
    "sulfuric-acid",
    "tall-oil",
    "tar",
    "thickened-coal-fines",
    "ti-overflow-waste",
    "ti-pulp-01",
    "ti-pulp-02",
    "ti-pulp-03",
    "ti-pulp-04",
    "ti-pulp-05",
    "ti-pulp-06",
    "ti-solution-02",
    "ti-solution",
    "tin-bottom-pulp",
    "tin-middle-pulp",
    "tin-pulp-01",
    "tin-pulp-02",
    "tin-pulp-03",
    "tin-slime-overflow",
    "tin-slime",
    "tin-solution",
    "tpa",
    "u-15",
    "u-20",
    "u-23",
    "u-25",
    "u-30",
    "u-33",
    "u-35",
    "u-40",
    "u-43",
    "u-45",
    "u-50",
    "u-65",
    "u-70",
    "u-73",
    "u-75",
    "u-79",
    "u-81",
    "u-83",
    "u-pulp-01",
    "u-pulp-02",
    "u-pulp-03",
    "u-rich-pulp",
    "u-waste",
    "used-solvent",
    "vanabins",
    "vanadates",
    "vanadium-concentrate",
    "vanadium-mixture",
    "vanadium-solution",
    "vpulp-precip",
    "vpulp1",
    "vpulp2",
    "vpulp3",
    "vpulp4",
    "vpulp5",
    "waste-water",
    "water",
    "water-saline",
    "xyhiphoe-blood",
    "xylenol",
    "z3-reagent",
    "zinc-overflow",
    "zinc-pulp-01",
    "zinc-pulp-02",
    "zinc-pulp-03",
    "zinc-pulp-04",
    "zinc-waste"
]


from pathlib import Path
import numpy as np
from sklearn.metrics.pairwise import pairwise_distances_argmin
import requests

from utils import *

_ARTIFACTS_PATH = Path('artifacts')

class ThreadRanker(object):
    def __init__(self, paths):
        self.word_embeddings, self.embeddings_dim = load_embeddings(str(_ARTIFACTS_PATH / paths['WORD_EMBEDDINGS']))
        self.thread_embeddings_folder = _ARTIFACTS_PATH / paths['THREAD_EMBEDDINGS_FOLDER']

    def __load_embeddings_by_tag(self, tag_name):
        thread_ids, thread_embeddings = unpickle_file(str(self.thread_embeddings_folder / "{}.pkl".format(tag_name)))
        return thread_ids, thread_embeddings

    def get_best_thread(self, question, tag_name):
        """ Returns id of the most similar thread for the question.
            The search is performed across the threads with a given tag.
        """
        thread_ids, thread_embeddings = self.__load_embeddings_by_tag(tag_name)

        # HINT: you have already implemented a similar routine in the 3rd assignment.

        question_vec = np.expand_dims(question_to_vec(question, self.word_embeddings, self.embeddings_dim), axis=0)
        best_thread = pairwise_distances_argmin(X=question_vec, Y=thread_embeddings, axis=1)[0]

        return thread_ids[best_thread]

class QARanker(object):
    def __init__(self, paths):
        self.embeddings_path = _ARTIFACTS_PATH / paths['QA_EMBEDDINGS']

    def set_endpoint(self, ip: str, port: str):
        self.endpoint = f'http://{ip}:{port}/sentence-transformers'
        self.ping = f'http://{ip}:{port}/ping'
        self._warmup_service()

    def _warmup_service(self):
        r = requests.get(url=self.ping)
        status = r.json()['status']
        if status != 200:
            raise Exception(f'Service Unavailable: status {status}')

    def _post_service(self, text: str):
        r = requests.post(url=self.endpoint, json={'text': text})
        return np.array(r.json()['embedding'])

    def get_best_answer(self, question: str):
        # Load sentence embeddings into memory
        answers, q_embeddings = unpickle_file(str(self.embeddings_path))
        # Get question vector
        question_vec = np.expand_dims(self._post_service(text=question), axis=0)
        # Get best answer ID
        best_answer_id = pairwise_distances_argmin(X=question_vec, Y=q_embeddings, axis=1)[0]
        # Get the answer
        return answers[best_answer_id]

class DialogueManager(object):
    def __init__(self, paths):
        print("Loading resources...")

        # Intent recognition:
        self.intent_recognizer = unpickle_file(str(_ARTIFACTS_PATH / paths['INTENT_RECOGNIZER']))
        self.tfidf_vectorizer = unpickle_file(str(_ARTIFACTS_PATH / paths['TFIDF_VECTORIZER']))

        self.ANSWER_TEMPLATE = 'I think its about %s\nThis thread might help you: https://stackoverflow.com/questions/%s'

        # Goal-oriented part:
        self.tag_classifier = unpickle_file(str(_ARTIFACTS_PATH / paths['TAG_CLASSIFIER']))
        self.thread_ranker = ThreadRanker(paths)

        # Chit-chat
        self.qa_ranker = QARanker(paths)

    def create_chitchat_bot(self, ip: str, port: str):
        self.qa_ranker.set_endpoint(ip=ip, port=port)

    def generate_answer(self, question):
        """Combines stackoverflow and chitchat parts using intent recognition."""

        # Recognize intent of the question using `intent_recognizer`.
        # Don't forget to prepare question and calculate features for the question.
        prepared_question = text_prepare(question)
        features = self.tfidf_vectorizer.transform([prepared_question])
        intent = self.intent_recognizer.predict(features)

        # Chit-chat part:
        if intent == 'dialogue':
            # Launch dialogue model and sentence embeddings to get best answer. Then release the memory used.
            return self.qa_ranker.get_best_answer(question=prepared_question)

        # Goal-oriented part:
        else:
            # Pass features to tag_classifier to get predictions.
            tag = self.tag_classifier.predict(features)[0]

            # Pass prepared_question to thread_ranker to get predictions.
            thread_id = self.thread_ranker.get_best_thread(question=prepared_question, tag_name=tag)

            return self.ANSWER_TEMPLATE % (tag, thread_id)


#import modules
from textblob import TextBlob
import numpy as np
from textblob.classifiers import NaiveBayesClassifier
from random import shuffle
import os

# Functions:
def get_data(file_name):
    #gets data from csv
    lines = open(file_name,'r').readlines()

    if lines == None: return -1
    out = [tuple(line.lower().strip().split(',')) for line in lines]      # create keyword and answer tuples
    shuffle(out)
    return out

def test_keywords(keyword, classifier, answer):
    # Prints whether keyword classifier is correct or not
    classification = classifier.classify(keyword)
    if classification == answer:
        is_correct = 'Correct'
    else:
        is_correct = 'Incorrect'
    print("{0: <50} --> {1: <15} ans: {3: <15} {2: <15}".format(keyword, classifier.classify(keyword), is_correct, answer))

def test_dataset(data, classifier):
    correct = 0
    for line in data:
        test_keywords(line[0], classifier, line[1])
        if classifier.classify(line[0]) == line[1]:
            correct += 1
    print('{0:} of {1:} correct ({2:}%)'.format(correct, len(data),round((correct/len(data)*100.0))))

def main():
    #Constants
    training_samples = 20                          # Number of samples to include in testing
    data_source = 'training_data_user.csv'         # Training data source file
    # data_source = 'training_data_category.csv'

    # get data from file
    data = get_data(data_source)
    if data == -1 or data == None:
        print('ERROR: Data source not valid.')
        quit()

    # split into training and test samples
    train_data = data[:-training_samples]
    test_data = data[-training_samples:]

    #train classifier
    cl = NaiveBayesClassifier(train_data)

    # test classifier on 10 last lines of training_data.csv
    # [test_keywords(line[0], cl, line[1]) for line in test_data]
    test_dataset(test_data, cl)

    # test_keywords('asthma attack child',cl)
    # test_keywords('asthma classification children',cl)
    # tb = TextBlob('I am great')
    # print(tb.sentiment)
    # print('Subjectivity: {}, Polarity: {}'.format(tb.sentiment.subjectivity, tb.sentiment.polarity))

if __name__ =="__main__":
    main()


# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4
import cocotb
from cocotb.triggers import Timer, RisingEdge, FallingEdge
from cocotb.clock import Clock
from cocotb.drivers.amba import AXI4SlaveRead
from cocotb.result import TestFailure

@cocotb.test()
def test_fetcher(dut):
    cocotb.fork(Clock(dut.clk, 5, 'ns').start())

    dut.rst = 1
    dut.btn_in <= 0
    yield Timer(12, 'ns')
    dut.rst = 0

    for _ in range(5):
        yield RisingEdge(dut.clk)

    # start bouncing and then settle
    for i in range(30):
        dut.btn_in <= (i & 0x01)
        if dut.btn_out == 1:
            raise TestFailure("output should not change during bouncing")
        if dut.btn_rising_edge == 1 or dut.btn_falling_edge == 1:
            raise TestFailure("no edges should be detected during bouncing")

        yield FallingEdge(dut.clk)

    for _ in range(17):
        yield FallingEdge(dut.clk)

    if dut.btn_out != 1:
        raise TestFailure("output should have changed")
    if dut.btn_rising_edge != 1:
        raise TestFailure("output edge should have changed")

    for _ in range(5):
        yield RisingEdge(dut.clk)

    # start bounce again
    for i in range(9):
        dut.btn_in <= ((i & 0x02) >> 1)
        if dut.btn_out == 0:
            raise TestFailure("output should not change during bouncing")
        if dut.btn_rising_edge == 1 or dut.btn_falling_edge == 1:
            raise TestFailure("no edges should be detected during bouncing")

        yield FallingEdge(dut.clk)

    for _ in range(17):
        yield FallingEdge(dut.clk)

    if dut.btn_out != 0:
        raise TestFailure("output should have changed")
    if dut.btn_falling_edge != 1:
        raise TestFailure("output edge should have changed")

    yield RisingEdge(dut.clk)
    yield RisingEdge(dut.clk)

    # bounce but return to previous value
    for i in range(25):
        dut.btn_in <= ((i >> 2) & 0x01)
        if dut.btn_out == 1:
            raise TestFailure("output should not change during bouncing")
        if dut.btn_rising_edge == 1 or dut.btn_falling_edge == 1:
            raise TestFailure("no edges should be detected during bouncing")

        yield FallingEdge(dut.clk)

    for _ in range(20):
        if dut.btn_out == 1:
            raise TestFailure("output should not have changed")

    for _ in range(10):
        yield RisingEdge(dut.clk)