'''Functions for drawing contours of Dirichlet distributions.'''

# Author: Thomas Boggs

from __future__ import division, print_function

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.tri as tri

_corners = np.array([[0, 0], [1, 0], [0.5, 0.75**0.5]])
_AREA = 0.5 * 1 * 0.75**0.5
_triangle = tri.Triangulation(_corners[:, 0], _corners[:, 1])

# For each corner of the triangle, the pair of other corners
_pairs = [_corners[np.roll(range(3), -i)[1:]] for i in range(3)]
# The area of the triangle formed by point xy and another pair or points
tri_area = lambda xy, pair: 0.5 * np.linalg.norm(np.cross(*(pair - xy)))

def xy2bc(xy, tol=1.e-4):
    '''Converts 2D Cartesian coordinates to barycentric.
    Arguments:
        `xy`: A length-2 sequence containing the x and y value.
    '''
    coords = np.array([tri_area(xy, p) for p in _pairs]) / _AREA
    return np.clip(coords, tol, 1.0 - tol)

class Dirichlet(object):
    def __init__(self, alpha):
        '''Creates Dirichlet distribution with parameter `alpha`.'''
        from math import gamma
        from operator import mul
        self._alpha = np.array(alpha)
        self._coef = gamma(np.sum(self._alpha)) / \
                     np.multiply.reduce([gamma(a) for a in self._alpha])
    def pdf(self, x):
        '''Returns pdf value for `x`.'''
        from operator import mul
        return self._coef * np.multiply.reduce([xx ** (aa - 1)
                                                for (xx, aa)in zip(x, self._alpha)])
    def sample(self, N):
        '''Generates a random sample of size `N`.'''
        return np.random.dirichlet(self._alpha, N)

def draw_pdf_contours(dist, border=False, nlevels=200, subdiv=8, **kwargs):
    '''Draws pdf contours over an equilateral triangle (2-simplex).
    Arguments:
        `dist`: A distribution instance with a `pdf` method.
        `border` (bool): If True, the simplex border is drawn.
        `nlevels` (int): Number of contours to draw.
        `subdiv` (int): Number of recursive mesh subdivisions to create.
        kwargs: Keyword args passed on to `plt.triplot`.
    '''
    from matplotlib import ticker, cm
    import math

    refiner = tri.UniformTriRefiner(_triangle)
    trimesh = refiner.refine_triangulation(subdiv=subdiv)
    pvals = [dist.pdf(xy2bc(xy)) for xy in zip(trimesh.x, trimesh.y)]

    plt.figure(figsize=(25, 15))
    plt.tricontourf(trimesh, pvals, nlevels, cmap='jet', **kwargs)
    plt.axis('equal')
    plt.xlim(0, 1)
    plt.ylim(0, 0.75**0.5)
    plt.axis('off')
    if border is True:
        plt.triplot(_triangle, linewidth=1)

def plot_points(X, barycentric=True, border=True, **kwargs):
    '''Plots a set of points in the simplex.
    Arguments:
        `X` (ndarray): A 2xN array (if in Cartesian coords) or 3xN array
                       (if in barycentric coords) of points to plot.
        `barycentric` (bool): Indicates if `X` is in barycentric coords.
        `border` (bool): If True, the simplex border is drawn.
        kwargs: Keyword args passed on to `plt.plot`.
    '''
    if barycentric is True:
        X = X.dot(_corners)
    plt.plot(X[:, 0], X[:, 1], 'k.', ms=1, **kwargs)
    plt.axis('equal')
    plt.xlim(0, 1)
    plt.ylim(0, 0.75**0.5)
    plt.axis('off')
    if border is True:
        plt.triplot(_triangle, linewidth=1)

#if __name__ == '__main__':
#    f = plt.figure(figsize=(8, 6))
#    alphas = [[0.999] * 3,
#              [5] * 3,
#              [2, 5, 15]]
#    for (i, alpha) in enumerate(alphas):
#        plt.subplot(2, len(alphas), i + 1)
#        dist = Dirichlet(alpha)
#        draw_pdf_contours(dist)
#        title = r'$\alpha$ = (%.3f, %.3f, %.3f)' % tuple(alpha)
#        plt.title(title, fontdict={'fontsize': 8})
#        plt.subplot(2, len(alphas), i + 1 + len(alphas))
#        plot_points(dist.sample(5000))
#    plt.savefig('dirichlet_plots.png')
#    print('Wrote plots to "dirichlet_plots.png".')


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Thu Apr  8 14:21:29 2021

@author: ariandovald
"""

import numpy as np
#from numba import jit

#MIDPOINT METHOD
def mid_one_d(steps, a, delta_x, f):
    '''uses midpoint method to calculate an integral given
    the starting point a, the number of steps, delta_x, and f'''
    i = 0
    for n in range(0, steps):
        x = a + (n * delta_x) #UPDATE X BY ADDING N * DELTA-X
        i += f(x + (delta_x * 0.5)) * delta_x #UPDATE INTEGRAL SUM USING MIDPOINT METHOD
    return i

#TRAPEZOIDAL ORDINAL SUM
#@jit(nopython = True)
def trapezoidal_ordsum(steps, a, b, a_new, b_new, delta_x, f) -> float:
    '''calculates a trapezoidal ordinate sum for a given function f and
    endpoints a and b'''
    ordsum = np.float64(0)
    x = a_new #SET STARTING POINT FOR X

    if a == a_new and b == b_new: #CHECK IF THIS IS THE FIRST ITERATION OR NOT
        ordsum += 0.5 * f(x) #ADD HALF OF THE STARTING ENDPOINT
        for n in range(1, steps-1):
            #REPEAT LOOP FROM AFTER STARTING ENDPOINT TO BEFORE ENDING ENDPOINT
            x = a + (n * delta_x) #UPDATE X BY ADDING N * DELTA_X
            ordsum += f(x) #ADD TO ORDINATE SUM
        ordsum += 0.5 * f(x + delta_x) #ADD HALF OF THE ENDING ENDPOINT
    else:
        for n in range(0, steps):
            #REPEAT LOOP FROM AFTER STARTING ENDPOINT TO BEFORE ENDING ENDPOINT
            if (x + (delta_x/2)) <= b:
                x = a_new + (n * delta_x) #UPDATE X BY ADDING N * DELTA_X
                ordsum += f(x) #ADD TO ORDINATE SUM

    return ordsum


import numpy as np
from Activation_function import sigmoid
from FeatureMapping import features_mapping

# TODO Cost function for linear regression for classification prob
def cost_function_linear(theta, X, y):
    # X: (m, n);  theta: (n, 1);  y: (m, 1)
    (m, n) = X.shape

    # Cost function
    J = 0
    h = np.matmul(X, theta)  # h: (m, 1)
    square = (h-y)**2
    J = 1/(2*m) * sum(square)

    return J

# TODO Cost function for logistic regression for classification prob
def cost_function_logistic(theta, X, y):
    # m: Number of training examples; n: Number of features
    # X: (m, n);  theta: (n, 1);  y: (m, 1)
    J = 0
    m, n = X.shape
    alpha = 0.0001
    grad = np.zeros(theta.shape)

    # (NOTE) Theta DIM must be (n, 1) (2D array))
    theta = np.reshape(theta, (n, 1))

    # Cost function
    h = sigmoid(np.dot(X, theta))  # h: (m, 1)
    J = (-1/m) * np.sum(np.dot(y.T, np.log(h)) + np.dot((1-y).T, np.log(1-h)))

    # Derivative of J (gradient descent)
    grad = (1/m) * (np.dot(X.T, (h-y)))

    return J, grad

# TODO Cost applied Regularization for classification logistic regression
# PURPOSE: Apply regularization for solving "Overfitting" problem
def cost_logistic_regularization(theta, X, y, lambdas):
    # X: (m, n+1);  theta: (n+1,)_1D array;  y: (m, 1), h: (m, 1)
    # Initial values
    theta = theta[:, np.newaxis]  # Increase 1D to 2D theta
    J = 0
    m = X.shape[0]
    grad = np.zeros((X.shape[1], 1))

    # Cost function apply regularization
    h = sigmoid(np.dot(X, theta))
    term = np.dot(y.T, np.log(h)) + np.dot((1-y).T, np.log(1-h))
    J = ((-1/m) * term) + (lambdas/(2*m)) * np.sum(theta[1:]**2)

    # Derivative of cost function (grad: (n+1, 1))
    grad = (1/m) * (np.dot(X.T, (h-y))) + (lambdas/m) * theta
    grad[0, 0] = (1/m) * np.dot(X.T[0, :], (h-y))

    return J, grad

# (NOTE) np.hstack(), np.vstack(): concatenate 2D array (horizontal & vertical)
# (NOTE) np.matmul() OR np.dot() return 2D array (If input 2D array)
# (NOTE) np.multiply() return 1D array (Even if input 2D array)


# ------------- Machine Learning - Topic 2: Logistic Regression

# You will need to complete the following functions
# in this exercise:
#     sigmoid
#     costFunction
#     predict
#     plotData
#     plotDecisionBoundary

## Initialization
import numpy as np
from scipy.optimize import fmin, fmin_bfgs
import os, sys
sys.path.append(os.getcwd() + os.path.dirname('/ml/ex2/'))
from helpers import sigmoid, costFunction, predict, plotData, plotDecisionBoundary

# Load Data
#  The first two columns contains the exam scores and the third column
#  contains the label.
data = np.loadtxt('ml/ex2/ex2data1.txt', delimiter=",")
X = data[:, :2]
y = data[:, 2]

## ==================== Part 1: Plotting ====================
#  We start the exercise by first plotting the data to understand the
#  the problem we are working with.
print('Plotting data with + indicating (y = 1) examples and o indicating (y = 0) examples.')

plt, p1, p2 = plotData(X, y)

# # Labels and Legend
plt.xlabel('Exam 1 score')
plt.ylabel('Exam 2 score')
plt.legend((p1, p2), ('Admitted', 'Not Admitted'), numpoints=1, handlelength=0)

plt.show(block=False) # prevents having to close the graph to move forward with ex2.py

input('Program paused. Press enter to continue.\n')
# plt.close()

## ============ Part 2: Compute Cost and Gradient ============
#  In this part of the exercise, you will implement the cost and gradient
#  for logistic regression. You neeed to complete the code in
#  costFunction.m

#  Setup the data matrix appropriately, and add ones for the intercept term
m, n = X.shape
X_padded = np.column_stack((np.ones((m,1)), X))

# Initialize fitting parameters
initial_theta = np.zeros((n + 1, 1))

# Compute and display initial cost and gradient
cost, grad = costFunction(initial_theta, X_padded, y, return_grad=True)

print('Cost at initial theta (zeros): {:f}'.format(cost))
print('Gradient at initial theta (zeros):')
print(grad)

input('Program paused. Press enter to continue.\n')

## ============= Part 3: Optimizing using fmin (and fmin_bfgs)  =============
#  In this exercise, you will use a built-in function (fmin) to find the
#  optimal parameters theta.

#  Run fmin and fmin_bfgs to obtain the optimal theta
#  This function will return theta and the cost
#  fmin followed by fmin_bfgs inspired by stackoverflow.com/a/23089696/583834
#  overkill... but wanted to use fmin_bfgs, and got error if used first
myargs=(X_padded, y)
theta = fmin(costFunction, x0=initial_theta, args=myargs)
theta, cost_at_theta, _, _, _, _, _ = fmin_bfgs(costFunction, x0=theta, args=myargs, full_output=True)

# Print theta to screen
print('Cost at theta found by fmin: {:f}'.format(cost_at_theta))
print('theta:'),
print(theta)

# Plot Boundary
plotDecisionBoundary(theta, X_padded, y)

plt.hold(False) # prevents further drawing on plot
plt.show(block=False)

input('Program paused. Press enter to continue.\n')

## ============== Part 4: Predict and Accuracies ==============
#  After learning the parameters, you'll like to use it to predict the outcomes
#  on unseen data. In this part, you will use the logistic regression model
#  to predict the probability that a student with score 45 on exam 1 and
#  score 85 on exam 2 will be admitted.
#
#  Furthermore, you will compute the training and test set accuracies of
#  our model.
#
#  Predict probability for a student with score 45 on exam 1
#  and score 85 on exam 2

prob = sigmoid(np.dot(np.array([1,45,85]),theta))
print('For a student with scores 45 and 85, we predict an admission probability of {:f}'.format(prob))

# Compute accuracy on our training set
p = predict(theta, X_padded)

print('Train Accuracy: {:f}'.format(np.mean(p == y) * 100))

input('Program paused. Press enter to continue.\n')


import sympy as sym
x, L, C, D, c_0, c_1, = sym.symbols('x L C D c_0 c_1')

class TwoPtBoundaryValueProblem(object):
    """
    Solve -(a*u')' = f(x) with boundary conditions
    specified in subclasses (method get_bc).
    a and f must be sympy expressions of x.
    """
    def __init__(self, f, a=1, L=L, C=C, D=D):
        """Default values for L, C, D are symbols."""
        self.f = f
        self.a = a
        self.L = L
        self.C = C
        self.D = D

        # Integrate twice
        u_x = - sym.integrate(f, (x, 0, x)) + c_0
        u = sym.integrate(u_x/a, (x, 0, x)) + c_1
        # Set up 2 equations from the 2 boundary conditions and solve
        # with respect to the integration constants c_0, c_1
        eq = self.get_bc(u)
        eq = [sym.simplify(eq_) for eq_ in eq]
        print(('BC eq:', eq))
        self.u = self.apply_bc(eq, u)

    def apply_bc(self, eq, u):
        # Solve BC eqs respect to the integration constants
        r = sym.solve(eq, [c_0, c_1])
        # Substitute the integration constants in the solution
        u = u.subs(c_0, r[c_0]).subs(c_1, r[c_1])
        u = sym.simplify(sym.expand(u))
        return u

    def get_solution(self, latex=False):
        return sym.latex(self.u, mode='plain') if latex else self.u

    def get_residuals(self):
        """Return the residuals in the equation and BCs."""
        R_eq = sym.diff(sym.diff(self.u, x)*self.a, x) + self.f
        R_0, R_L = self.get_bc(self.u)
        residuals = [sym.simplify(R) for R in (R_eq, R_0, R_L)]
        return residuals

    def get_bc(self, u):
        raise NotImplementedError(
            'class %s has not implemented get_bc' %
            self.__class__.__name__)

class Model1(TwoPtBoundaryValueProblem):
    """u(0)=0, u(L)=D."""
    def get_bc(self, u):
        return [u.subs(x, 0)-0,               # x=0 condition
                u.subs(x, self.L) - self.D]   # x=L condition

class Model2(TwoPtBoundaryValueProblem):
    """u'(0)=C, u(L)=D."""
    def get_bc(self, u):
        return [sym.diff(u,x).subs(x, 0) - self.C, # x=0 cond.
                u.subs(x, self.L) - self.D]        # x=L cond.

class Model3(TwoPtBoundaryValueProblem):
    """u(0)=C, u(L)=D."""
    def get_bc(self, u):
        return [u.subs(x, 0) - self.C,
                u.subs(x, self.L) - self.D]

class Model4(TwoPtBoundaryValueProblem):
    """u(0)=0, -u'(L)=C*(u-D)."""
    def get_bc(self, u):
        return [u.subs(x, 0) - 0,
                -sym.diff(u, x).subs(x, self.L) -
                self.C*(u.subs(x, self.L) - self.D)]

def test_TwoPtBoundaryValueProblem():
    f = 2
    model = Model1(f)
    print(('Model 1, u:', model.get_solution()))
    for R in model.get_residuals():
        assert R == 0

    f = x
    model = Model2(f)
    print(('Model 2, u:', model.get_solution()))
    for R in model.get_residuals():
        assert R == 0

    f = 0
    a = 1 + x**2
    model = Model3(f, a=a)
    print(('Model 3, u:', model.get_solution()))
    for R in model.get_residuals():
        assert R == 0

def demo_Model4():
    f = 0
    model = Model4(f, a=sym.sqrt(1+x))
    print(('Model 4, u:', model.get_solution()))

if __name__ == '__main__':
    test_TwoPtBoundaryValueProblem()
    demo_Model4()


import numpy as np
import matplotlib.pyplot as plt
import os
#figdir = os.path.join(os.environ["PYPROBML"], "figures")
figdir = "../figures";
def save_fig(fname): plt.savefig(os.path.join(figdir, fname))

from scipy.stats import nbinom

xs = np.arange(50);
fig, ax = plt.subplots(1,1)
p = 0.5; r = 1;
probabilities = nbinom.pmf(xs, r, p)
ax.bar(xs, probabilities)
ax.set_title('NB(r={:.2f}, p={:.2f})'.format(r, p))
save_fig('negbinomPlot_r{}_p{}.png'.format(int(r*10), int(p*10)))
plt.show()

fig, ax = plt.subplots(1,1)
p = 0.5; r = 10;
probabilities = nbinom.pmf(xs, r, p)
ax.bar(xs, probabilities)
ax.set_title('NB(r={:.2f}, p={:.2f})'.format(r, p))
save_fig('negbinomPlot_r{}_p{}.png'.format(int(r*10), int(p*10)))
plt.show()


'''arange'''
# an arange returns evenly spaced values (where you give the space size) within a given interval and is part of the numpy package

#numpy.arange(start,stop,step)

#where start is a number that you want to start with, the interval includes this value
#stop is the end of the interval the interval does not include this number
#step is the spacing between values

#example
import numpy as numpy

a=numpy.arange(1,11,1)
print a

#this gives [1,2,3,4,5,6,7,8,9,10]

'''linspace'''

#linspace returns evenly spaced values (where you give the number of spaces) within a given interval and is part of the numpy package

#numpy.linspace(start,stop,num=x)

#where start is a number that you want to start with, the interval includes this value
#stop is the end of the interval the interval does include this number (unless you add endpoint=False,)
#num=x is the number of spaces you want and the interval will be split evenly amoung these spaces

b=numpy.linspace(0,10,num=5)
print b


import numpy as np
import time
import matplotlib.pyplot as plt
import sympy as sym

pts = [[0.1, 10.0], [0.2, 5.0], [0.30000000000000004, 3.333333333333333], [0.4, 2.5], [0.5, 2.0], [0.6, 1.6666666666666667], [0.7000000000000001, 1.4285714285714284], [0.8, 1.25], [0.9, 1.1111111111111112], [1.0, 1.0], [1.1, 0.9090909090909091], [1.2000000000000002, 0.8333333333333333], [1.3000000000000003, 0.769230769230769], [1.4000000000000001, 0.7142857142857142], [1.5000000000000002, 0.6666666666666665], [1.6, 0.625], [1.7000000000000002, 0.588235294117647], [1.8000000000000003, 0.5555555555555555], [1.9000000000000001, 0.5263157894736842], [2.0, 0.5], [2.1, 0.47619047619047616], [2.2, 0.45454545454545453], [2.3000000000000003, 0.4347826086956521], [2.4000000000000004, 0.41666666666666663], [2.5000000000000004, 0.3999999999999999], [2.6, 0.3846153846153846], [2.7, 0.37037037037037035], [2.8000000000000003, 0.3571428571428571], [2.9000000000000004, 0.3448275862068965], [3.0000000000000004, 0.33333333333333326], [3.1, 0.3225806451612903], [3.2, 0.3125], [3.3000000000000003, 0.303030303030303], [3.4000000000000004, 0.2941176470588235], [3.5000000000000004, 0.2857142857142857], [3.6, 0.2777777777777778], [3.7, 0.27027027027027023], [3.8000000000000003, 0.2631578947368421], [3.9000000000000004, 0.2564102564102564], [4.0, 0.25], [4.1, 0.24390243902439027], [4.2, 0.23809523809523808], [4.3, 0.23255813953488372], [4.3999999999999995, 0.2272727272727273], [4.5, 0.2222222222222222], [4.6, 0.2173913043478261], [4.7, 0.2127659574468085], [4.8, 0.20833333333333334], [4.9, 0.2040816326530612], [5.0, 0.2]]

def lagrange_eq(pts):
    n_equations = len(pts)
    crr_eq = list()

    for k in range(n_equations):
        crr_factors = list()
        for i in range(n_equations):
            if i != k:
                crr_factors.append(f'(x-{pts[i][0]})/({pts[k][0]}-{pts[i][0]})')
        crr_eq.append('*'.join(crr_factors) + '*' + str(pts[k][1]))

    eq_txt = '+'.join(crr_eq)
    eq = lambda x: eval(eq_txt)
    return eq

def lagrange_eq_lambdas(pts):
    n_equations = len(pts)
    crr_eq = list()
    x = sym.symbols('x')

    for k in range(n_equations):
        crr_factors = list()

        for i in range(n_equations):
            if pts[k][0] != pts[i][0]:
                kx1 = pts[k][0]
                ix1 = pts[i][0]
                crr_factors.append(((x-ix1) / (kx1-ix1)))

        yk = pts[k][1]
        eq = np.prod([subeq for subeq in crr_factors])
        eqq = eq * yk

        eqq = sym.lambdify(x, eqq)
        crr_eq.append(eqq)

    final_eq = lambda x: sum([e(x) for e in crr_eq])
    return final_eq

titxt = time.time()
P1 = lagrange_eq(pts)
print(P1(4))
print(P1(2.5))
print(f'Finished. Time necessary: {time.time()-titxt}')

tilambda = time.time()
P2 = lagrange_eq_lambdas(pts)
print(P2(4))
print(P2(2.5))
print(f'Finished. Time necessary: {time.time()-tilambda}')

# rango = np.linspace(1, 5, 1000)
# domain = [P2(x) for x in rango]
# print(len(rango))
# print(len(domain))
# correct = lambda x: 0.05*x**2 - 0.425*x + 1.15
#
#
# plt.plot(rango, domain)
# plt.plot(rango, correct(rango))
# plt.grid()
# plt.show()
#


import numpy as np
from scipy import linalg

def get_spectral_radius(W):
    """Given a squarred weight matrix (e.g. the recurrent reservoir matrix),
    returns the spectral radius"""
    return max(abs(linalg.eig(W)[0]))

def compute_error_NRMSE(teacher_signal, predicted_signal, verbose=False):
    """ Computes Normalized Root-Mean-Squarred Error between a teacher signal and a predicted signal
    Return the errors in this order: nmrse mean, nmrse max-min, rmse, mse.
    By default, only NMRSE mean should be considered as a general measure to be
    compared for different datasets.

    For more information, see:
    - Mean Squared Error https://en.wikipedia.org/wiki/Mean_squared_error
    - Root Mean Squared Error https://en.wikipedia.org/wiki/Root-mean-square_deviation for more info
    """
    mse = np.mean((teacher_signal - predicted_signal)**2)
    rmse = np.sqrt(mse)
    nmrse_mean = abs(rmse / np.mean(teacher_signal[:])) # Normalised RMSE (based on mean)
    nmrse_maxmin = rmse / abs(np.max(teacher_signal[:]) - np.min(teacher_signal[:])) # Normalised RMSE (based on max - min)
    return nmrse_mean, nmrse_maxmin, rmse, mse


import numpy as np
from numpy.linalg import eigh

# The probability of a Gaussian.
def px(x, mu, cov_inv, cov_det):
  N     = x.shape[0]
  d     = x.shape[1]
  coeff = 1.0/((2.0*np.pi)**(d/2.0) * cov_det**(0.5))

  result = np.zeros(N)
  for n in range(N):
    x_shifted = x[n,:] - mu
    result[n] = coeff * np.exp(-0.5 * x_shifted @ cov_inv @ x_shifted)

  return result

# Perform the Expectation-Maximization (EM) algorithm to
# find the maximum likelihood estimation of the parameters
# of a Gaussian-mixture model.
# Based on these lecture notes:
#  https://see.stanford.edu/materials/aimlcs229/cs229-notes8.pdf
#  https://www.ics.uci.edu/~smyth/courses/cs274/notes/EMnotes.pdf
# Also this video lecture is helpful:
#  https://www.youtube.com/watch?v=rVfZHWTwXSA
def em_gmm(data, num_gaussians, max_iter=100, rtol=1e-6, atol=1e-10, return_iterations=False):
  # The data matrix
  X = data

  # Number of samples
  N = data.shape[0]
  # Dimension of data
  d = data.shape[1]

  # The number of Gaussian in the Gaussian mixture model
  K    = num_gaussians
  # The probability of being in Gaussian l in {1,...,K} (initialized to uniform)
  phis = np.ones(K) / K
  # The mean vector of Gaussian l (initialized to first data points)
  mus = np.zeros((K, d))
  for k in range(K):
    mus[k,:] = X[k,:]
  # The covariance matrix of Gaussian l (initialized to data covariance)
  covs        = np.zeros((K, d, d))
  covs[0,:,:] = np.cov(X.T)
  for k in range(1,K):
    covs[k,:,:] = covs[0,:,:]

  if return_iterations:
    iteration_data = {
        "L"       : [],
        "mus"     : [],
        "covs"    : [],
        "phis"    : [],
        "weights" : []
      }

  log_likelihood_prev = np.inf
  weights             = np.zeros((N, K))
  for t in range(max_iter):
    for k in range(K):
      (evals, evecs) = eigh(covs[k,:,:])
      assert(np.all(evals > 1e-15))
      cov_det        = np.prod(evals)
      evals_inv      = np.array([(1.0/eval if np.abs(eval) > 1e-15 else 0.0) for eval in evals])
      cov_inv        = evecs @ np.diag(evals_inv) @ (evecs.conj().T)

      weights[:, k] = px(X, mus[k], cov_inv, cov_det) * phis[k]

    # Compute the log-likelihood.
    log_likelihood = np.sum(np.log(np.sum(weights, axis=1)))

    rel_change = np.abs(log_likelihood - log_likelihood_prev)/np.abs(log_likelihood)
    abs_change = np.abs(log_likelihood - log_likelihood_prev)

    print(f"=== Step {t+1} ===")
    print(f' L          = {log_likelihood}')
    print(f" rel_change = {rel_change}")
    print(f" abs_change = {abs_change}")

    if rel_change < rtol or abs_change < atol:
      break

    # Normalize the weights correctly.
    for n in range(N):
      weights[n, :] /= np.sum(weights[n, :])

    # Update the parameters.
    Nk   = np.sum(weights, axis=0)
    phis = Nk / N

    for i in range(d):
      mus[:, i] = (X[:,i] @ weights) / Nk

    for k in range(K):
      for i in range(d):
        x_shifted_i = X[:,i] - mus[k,i]
        for j in range(d):
          x_shifted_j   = X[:,j] - mus[k,j]
          covs[k, i, j] = ((x_shifted_i * x_shifted_j) @ weights[:,k]) / Nk[k]

    if return_iterations:
      iteration_data['L'].append(log_likelihood)
      iteration_data['mus'].append(np.copy(mus))
      iteration_data['covs'].append(np.copy(covs))
      iteration_data['phis'].append(np.copy(phis))
      iteration_data['weights'].append(np.copy(weights))

    log_likelihood_prev = log_likelihood

  if not return_iterations:
    return (log_likelihood, phis, mus, covs, weights)
  else:
    return (log_likelihood, phis, mus, covs, weights, iteration_data)


"""
Example of a gradient descent (ascent) implementation in tensorflow to find the discrete
probability distribution that solves the most simple one-step free energy optimization problem
of information-theoretic bounded rationality
Author: Sebastian Gottwald
Project: https://github.com/sgttwld/gradientdescent
"""

import numpy as np
import tensorflow as tf

# parameters
numEp = 50000
lr = .1
precision = 1e-8
N = 50
beta = 5.0

# constants
t = tf.constant(np.linspace(0,2,N))
U = -(t**2-2)**2/4 + 1
q = tf.constant(np.ones(N)/N)

# variables
theta = tf.Variable(np.ones(N),dtype=tf.float64)

# parametrization of p(t)
p = tf.exp(theta)/tf.reduce_sum(tf.exp(theta))

# objective
EU = tf.reduce_sum(p*U)    # expected utility E[U] = sum_x p(x) U(x)
DKL = tf.reduce_sum(p*tf.log(p/q))
obj = EU - DKL/beta

# optimizer and training operator
optimizer = tf.train.AdamOptimizer(learning_rate=lr,beta1=.9, beta2=.999,epsilon=1e-08,name='Adam')
#optimizer = tf.train.GradientDescentOptimizer(learning_rate=lr)
train_op = optimizer.minimize(-obj)

# initializer
init = tf.global_variables_initializer()

# running the TF session
with tf.Session() as sess:

    ## initializing
    sess.run(init)
    obj_curr = obj.eval(session=sess)
    obj0 = obj_curr

    print('f(t) = ', obj_curr)

    for n in range(0,numEp):

        ## run gradient descent
        sess.run(train_op)

        ## evaluation
        obj_curr = obj.eval(session=sess)
        p_curr = t.eval(session=sess)
        if n % 50 == 0:
            print('ep:',n,'F[p] = ', obj_curr)
        if abs(obj_curr-obj0) < precision:
            break
        else:
            obj0 = obj_curr

    prob = p.eval(session=sess)

# for command line visualization:
import hipsterplot as hplt

def show(v,stretch=2):
    hplt.plot(np.concatenate([[val]*stretch for val in v]), num_x_chars=stretch*len(v),num_y_chars=10)

print('utility')
show(-(np.linspace(0,2,N)**2-2)**2 / 4 + 1,stretch=1)

print('probability distribution')
show(prob,stretch=1)


import pytool
import numpy as np
import matplotlib.pyplot as plt

# =====================generate Ellipse=====================
a = 6  # major axis
b = 2  # minor axis
x0 = 10  # center x0
y0 = 10  # center y0
N = 1000  # number of points

# angle for rotating ellipse data
theta = np.pi * 30 / 180

x, y = pytool.ellipse_surface(a, b, x0, y0, N, 'rand')
# x, y = pytool.ellipse_surface(a, b, x0, y0, N, 'order')

x = x - np.mean(x)
y = y - np.mean(y)

xse = [min(x), max(x)]
yse = [min(y), max(y)]

xy = np.array([x, y])
print(xy.shape)

A = xy

# ========================rotate============================
# ------------rotating matrix(Anti-clockwise)---------------
M = [[np.cos(theta), np.sin(theta)],
     [-np.sin(theta), np.cos(theta)]]
M = [[np.cos(theta), -np.sin(theta)],
     [np.sin(theta), np.cos(theta)]]
print("rotating matrix M: ", M)

# -----------------------rotating---------------------------
A = np.dot(M, A)

x = A[0]
y = A[1]

# ========================show data=========================
plt.figure()
plt.scatter(x, y, c='g', marker='o')
plt.grid()

# ==================Eigenvalue decomposition================

AAT = np.dot(A, A.transpose())
ATA = np.dot(A.transpose(), A)

evalues, evectors = np.linalg.eig(AAT)
# evalues, evectors = np.linalg.eig(ATA)
print(A.shape, AAT.shape, evalues.shape, evectors.shape)
print(AAT)
print("evalues: ", evalues, "evectors: ", evectors)
angle1 = np.arctan(evectors[1, 0] / evectors[0, 0]) * 180 / np.pi
angle2 = np.arctan(evectors[1, 1] / evectors[0, 1]) * 180 / np.pi
print(angle1, angle2)

# =====================plot eigen vector=====================
colorlines = ['-r', '-b']
pytool.plot_vectors2d(evectors.transpose(), xse=xse, yse=yse,
                      nPoints=N, title='eigen vectors', colorlines=colorlines)
plt.legend(['eigen vector1', 'eigen vector2'])
plt.show()

plt.figure()
plt.imshow(AAT)
plt.show()


#%%
import numpy as np
from kdg.utils import generate_gaussian_parity, generate_ellipse, generate_spirals, generate_sinewave, generate_polynomial
from kdg.utils import plot_2dsim
from kdg import kdf
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
from scipy.io import savemat, loadmat
# %%
n_samples = 1e4
X, y = {}, {}
#%%
X['gxor'], y['gxor'] = generate_gaussian_parity(n_samples)
X['spiral'], y['spiral'] = generate_spirals(n_samples)
X['circle'], y['circle'] = generate_ellipse(n_samples)
X['sine'], y['sine'] = generate_sinewave(n_samples)
X['poly'], y['poly'] = generate_polynomial(n_samples, a=[1,3])
#%%
sns.set_context('talk')
fig, ax = plt.subplots(6,5, figsize=(40,48), sharex=True)
title_size = 45
ticksize = 30

plot_2dsim(X['gxor'], y['gxor'], ax=ax[0][0])
ax[0][0].set_ylabel('Simulation Data', fontsize=title_size-5)
ax[0][0].set_xlim([-2,2])
ax[0][0].set_ylim([-2,2])
ax[0][0].set_xticks([])
ax[0][0].set_yticks([-2,-1,0,1,2])
ax[0][0].tick_params(labelsize=ticksize)
ax[0][0].set_title('Gaussian XOR', fontsize=title_size)

plot_2dsim(X['spiral'], y['spiral'], ax=ax[0][1])
ax[0][1].set_xlim([-2,2])
ax[0][1].set_ylim([-2,2])
ax[0][1].set_xticks([])
ax[0][1].set_yticks([])
ax[0][1].tick_params(labelsize=ticksize)
ax[0][1].set_title('Spiral', fontsize=title_size)

plot_2dsim(X['circle'], y['circle'], ax=ax[0][2])
ax[0][2].set_xlim([-2,2])
ax[0][2].set_ylim([-2,2])
ax[0][2].set_xticks([])
ax[0][2].set_yticks([])
ax[0][2].tick_params(labelsize=ticksize)
ax[0][2].set_title('Circle', fontsize=title_size)

plot_2dsim(X['sine'], y['sine'], ax=ax[0][3])
ax[0][3].set_xlim([-2,2])
ax[0][3].set_ylim([-2,2])
ax[0][3].set_xticks([])
ax[0][3].set_yticks([])
ax[0][3].tick_params(labelsize=ticksize)
ax[0][3].set_title('Sinewave', fontsize=title_size)

plot_2dsim(X['poly'], y['poly'], ax=ax[0][4])
ax[0][4].set_xlim([-2,2])
ax[0][4].set_ylim([-2,2])
ax[0][4].set_xticks([])
ax[0][4].set_yticks([])
ax[0][4].tick_params(labelsize=ticksize)
ax[0][4].set_title('Polynomial', fontsize=title_size)

################################################
#define grids
p = np.arange(-2, 2, step=0.01)
q = np.arange(-2, 2, step=0.01)
xx, yy = np.meshgrid(p, q)

# get true posterior
tp_df = pd.read_csv("true_posterior/Gaussian_xor_pdf.csv")
proba_true = 0.5*np.ones((400, 400))
tmp = np.array([tp_df["posterior"][x] for x in range(40000)])
tmp = tmp.reshape(200, 200)
proba_true[100:300, 100:300] = tmp

ax0 = ax[1][0].imshow(
    proba_true,
    extent=[xx.min(), xx.max(), yy.min(), yy.max()],
    cmap="bwr",
    vmin=0,
    vmax=1,
    interpolation="nearest",
    aspect="auto",
)
#ax[1][0].set_title("True Class Posteriors", fontsize=24)
ax[1][0].set_aspect("equal")
ax[1][0].tick_params(labelsize=ticksize)
ax[1][0].set_yticks([-2,-1,0,1,2])
ax[1][0].set_xticks([])
ax[1][0].set_ylabel('True Posteriors',fontsize=title_size-5)

tp_df = pd.read_csv("true_posterior/spiral_pdf.csv")
proba_true = 0.5*np.ones((400, 400))
tmp = np.array([tp_df["posterior"][x] for x in range(40000)])
tmp = tmp.reshape(200, 200)
proba_true[100:300, 100:300] = 1 - tmp

ax0 = ax[1][1].imshow(
    np.flip(proba_true, axis=0),
    extent=[xx.min(), xx.max(), yy.min(), yy.max()],
    cmap="bwr",
    vmin=0,
    vmax=1,
    interpolation="nearest",
    aspect="auto",
)
#ax[1][1].set_title("True Class Posteriors", fontsize=24)
ax[1][1].set_aspect("equal")
ax[1][1].tick_params(labelsize=ticksize)
ax[1][1].set_yticks([])
ax[1][1].set_xticks([])

tp_df = pd.read_csv("true_posterior/ellipse_pdf.csv")
proba_true = 0.5*np.ones((400, 400))
tmp = np.array([tp_df["posterior"][x] for x in range(40000)])
tmp = tmp.reshape(200, 200)
proba_true[100:300, 100:300] = tmp

ax0 = ax[1][2].imshow(
    proba_true,
    extent=[xx.min(), xx.max(), yy.min(), yy.max()],
    cmap="bwr",
    vmin=0,
    vmax=1,
    interpolation="nearest",
    aspect="auto",
)
#ax[1][2].set_title("True Class Posteriors", fontsize=24)
ax[1][2].set_aspect("equal")
ax[1][2].tick_params(labelsize=ticksize)
ax[1][2].set_yticks([])
ax[1][2].set_xticks([])

tp_df = pd.read_csv("true_posterior/sinewave_pdf.csv")
proba_true = 0.5*np.ones((400, 400))
tmp = np.array([tp_df["posterior"][x] for x in range(40000)])
tmp = np.flip(tmp.reshape(200, 200),axis=0)
proba_true[100:300, 100:300] = tmp

ax0 = ax[1][3].imshow(
    proba_true,
    extent=[xx.min(), xx.max(), yy.min(), yy.max()],
    cmap="bwr",
    vmin=0,
    vmax=1,
    interpolation="nearest",
    aspect="auto",
)
#ax[1][3].set_title("True Class Posteriors", fontsize=24)
ax[1][3].set_aspect("equal")
ax[1][3].tick_params(labelsize=ticksize)
ax[1][3].set_yticks([])
ax[1][3].set_xticks([])

tp_df = pd.read_csv("true_posterior/polynomial_pdf.csv")
proba_true = 0.5*np.ones((400, 400))
tmp = np.array([tp_df["posterior"][x] for x in range(40000)])
tmp = np.flip(tmp.reshape(200, 200),axis=0)
proba_true[100:300, 100:300] = tmp

ax0 = ax[1][4].imshow(
    proba_true,
    extent=[xx.min(), xx.max(), yy.min(), yy.max()],
    cmap="bwr",
    vmin=0,
    vmax=1,
    interpolation="nearest",
    aspect="auto",
)
#ax[1][4].set_title("True Class Posteriors", fontsize=24)
ax[1][4].set_aspect("equal")
ax[1][4].tick_params(labelsize=ticksize)
ax[1][4].set_yticks([])
ax[1][4].set_xticks([])

#########################################################
df = loadmat('kdf_experiments/results/gxor_plot_data.mat')
ax1 = ax[2][0].imshow(
    df['posterior_rf'],
    extent=[xx.min(), xx.max(), yy.min(), yy.max()],
    cmap="bwr",
    vmin=0,
    vmax=1,
    interpolation="nearest",
    aspect="auto",
)
ax[2][0].set_ylabel("RF Posteriors", fontsize=title_size-5)
ax[2][0].set_aspect("equal")
ax[2][0].tick_params(labelsize=ticksize)
ax[2][0].set_yticks([-2,-1,0,1,2])
ax[2][0].set_xticks([])

ax1 = ax[3][0].imshow(
    df['posterior_kdf'],
    extent=[xx.min(), xx.max(), yy.min(), yy.max()],
    cmap="bwr",
    vmin=0,
    vmax=1,
    interpolation="nearest",
    aspect="auto",
)
ax[3][0].set_ylabel('KDF Posteriors', fontsize=title_size-5)
ax[3][0].set_aspect("equal")
ax[3][0].tick_params(labelsize=ticksize)
ax[3][0].set_yticks([-2,-1,0,1,2])
ax[3][0].set_xticks([])

############################################
df = loadmat('kdf_experiments/results/spiral_plot_data.mat')
ax1 = ax[2][1].imshow(
    1-np.flip(df['posterior_rf'],axis=0),
    extent=[xx.min(), xx.max(), yy.min(), yy.max()],
    cmap="bwr",
    vmin=0,
    vmax=1,
    interpolation="nearest",
    aspect="auto",
)
ax[2][1].set_aspect("equal")
ax[2][1].tick_params(labelsize=ticksize)
ax[2][1].set_yticks([])
ax[2][1].set_xticks([])

ax1 = ax[3][1].imshow(
    1-np.flip(df['posterior_kdf'],axis=0),
    extent=[xx.min(), xx.max(), yy.min(), yy.max()],
    cmap="bwr",
    vmin=0,
    vmax=1,
    interpolation="nearest",
    aspect="auto",
)
ax[3][1].set_aspect("equal")
ax[3][1].tick_params(labelsize=ticksize)
ax[3][1].set_yticks([])
ax[3][1].set_xticks([])

#############################################
df = loadmat('kdf_experiments/results/circle_plot_data.mat')
ax1 = ax[2][2].imshow(
    df['posterior_rf'],
    extent=[xx.min(), xx.max(), yy.min(), yy.max()],
    cmap="bwr",
    vmin=0,
    vmax=1,
    interpolation="nearest",
    aspect="auto",
)
ax[2][2].set_aspect("equal")
ax[2][2].tick_params(labelsize=ticksize)
ax[2][2].set_yticks([])
ax[2][2].set_xticks([])

ax1 = ax[3][2].imshow(
    df['posterior_kdf'],
    extent=[xx.min(), xx.max(), yy.min(), yy.max()],
    cmap="bwr",
    vmin=0,
    vmax=1,
    interpolation="nearest",
    aspect="auto",
)
ax[3][2].set_aspect("equal")
ax[3][2].tick_params(labelsize=ticksize)
ax[3][2].set_yticks([])
ax[3][2].set_xticks([])

##################################################
df = loadmat('kdf_experiments/results/sinewave_plot_data.mat')
ax1 = ax[2][3].imshow(
    np.flip(df['posterior_rf'],axis=0),
    extent=[xx.min(), xx.max(), yy.min(), yy.max()],
    cmap="bwr",
    vmin=0,
    vmax=1,
    interpolation="nearest",
    aspect="auto",
)
ax[2][3].set_aspect("equal")
ax[2][3].tick_params(labelsize=ticksize)
ax[2][3].set_yticks([])
ax[2][3].set_xticks([])

ax1 = ax[3][3].imshow(
    np.flip(df['posterior_kdf'], axis=0),
    extent=[xx.min(), xx.max(), yy.min(), yy.max()],
    cmap="bwr",
    vmin=0,
    vmax=1,
    interpolation="nearest",
    aspect="auto",
)
ax[3][3].set_aspect("equal")
ax[3][3].tick_params(labelsize=ticksize)
ax[3][3].set_yticks([])
ax[3][3].set_xticks([])

###################################################
df = loadmat('kdf_experiments/results/polynomial_plot_data.mat')
ax1 = ax[2][4].imshow(
    np.flip(df['posterior_rf'],axis=0),
    extent=[xx.min(), xx.max(), yy.min(), yy.max()],
    cmap="bwr",
    vmin=0,
    vmax=1,
    interpolation="nearest",
    aspect="auto",
)
ax[2][4].set_aspect("equal")
ax[2][4].tick_params(labelsize=ticksize)
ax[2][4].set_yticks([])
ax[2][4].set_xticks([])

ax1 = ax[3][4].imshow(
    np.flip(df['posterior_kdf'],axis=0),
    extent=[xx.min(), xx.max(), yy.min(), yy.max()],
    cmap="bwr",
    vmin=0,
    vmax=1,
    interpolation="nearest",
    aspect="auto",
)
ax[3][4].set_aspect("equal")
ax[3][4].tick_params(labelsize=ticksize)
ax[3][4].set_yticks([])
ax[3][4].set_xticks([])

##############################################
##############################################
df = loadmat('kdn_experiments/results/gxor_plot_data.mat')
proba_nn = 1-np.flip(df["nn_proba"][:, 0].reshape(400, 400), axis=1)
proba_kdn = 1-np.flip(df["kdn_proba"][:, 0].reshape(400, 400), axis=1)

ax1 = ax[4][0].imshow(
    proba_nn,
    extent=[xx.min(), xx.max(), yy.min(), yy.max()],
    cmap="bwr",
    vmin=0,
    vmax=1,
    interpolation="nearest",
    aspect="auto",
)
ax[4][0].set_aspect("equal")
ax[4][0].tick_params(labelsize=ticksize)
ax[4][0].set_ylabel('NN Posteriors',fontsize=title_size-5)
ax[4][0].set_yticks([-2,-1,0,1,2])
ax[4][0].set_xticks([])

ax1 = ax[5][0].imshow(
    proba_kdn,
    extent=[xx.min(), xx.max(), yy.min(), yy.max()],
    cmap="bwr",
    vmin=0,
    vmax=1,
    interpolation="nearest",
    aspect="auto",
)
ax[5][0].set_aspect("equal")
ax[5][0].set_ylabel('KDN Posteriors',fontsize=title_size-5)
ax[5][0].tick_params(labelsize=ticksize)
ax[5][0].set_yticks([-2,-1,0,1,2])
ax[5][0].set_xticks([-2,-1,0,1,2])

########################################
df = loadmat('kdn_experiments/results/spiral_plot_data.mat')
proba_nn = np.flip(df["nn_proba"][:, 0].reshape(400, 400), axis=1)
proba_kdn = np.flip(df["kdn_proba"][:, 0].reshape(400, 400), axis=1)

ax1 = ax[4][1].imshow(
    proba_nn,
    extent=[xx.min(), xx.max(), yy.min(), yy.max()],
    cmap="bwr",
    vmin=0,
    vmax=1,
    interpolation="nearest",
    aspect="auto",
)
ax[4][1].set_aspect("equal")
ax[4][1].tick_params(labelsize=ticksize)
ax[4][1].set_yticks([])
ax[4][1].set_xticks([])

ax1 = ax[5][1].imshow(
    proba_kdn,
    extent=[xx.min(), xx.max(), yy.min(), yy.max()],
    cmap="bwr",
    vmin=0,
    vmax=1,
    interpolation="nearest",
    aspect="auto",
)
ax[5][1].set_aspect("equal")
ax[5][1].tick_params(labelsize=ticksize)
ax[5][1].set_yticks([])
ax[5][1].set_xticks([-2,-1,0,1,2])

########################################################
df = loadmat('kdn_experiments/results/circle_plot_data.mat')
proba_nn = np.flip(df["nn_proba"][:, 0].reshape(400, 400), axis=1)
proba_kdn = np.flip(df["kdn_proba"][:, 0].reshape(400, 400), axis=1)

ax1 = ax[4][2].imshow(
    proba_nn,
    extent=[xx.min(), xx.max(), yy.min(), yy.max()],
    cmap="bwr",
    vmin=0,
    vmax=1,
    interpolation="nearest",
    aspect="auto",
)
ax[4][2].set_aspect("equal")
ax[4][2].tick_params(labelsize=ticksize)
ax[4][2].set_yticks([])
ax[4][2].set_xticks([])

ax1 = ax[5][2].imshow(
    proba_kdn,
    extent=[xx.min(), xx.max(), yy.min(), yy.max()],
    cmap="bwr",
    vmin=0,
    vmax=1,
    interpolation="nearest",
    aspect="auto",
)
ax[5][2].set_aspect("equal")
ax[5][2].tick_params(labelsize=ticksize)
ax[5][2].set_yticks([])
ax[5][2].set_xticks([-2,-1,0,1,2])

####################################################
df = loadmat('kdn_experiments/results/sinewave_plot_data.mat')
proba_nn = np.flip(df["nn_proba"][:, 0].reshape(400, 400), axis=0)
proba_kdn = np.flip(df["kdn_proba"][:, 0].reshape(400, 400), axis=0)

ax1 = ax[4][3].imshow(
    proba_nn,
    extent=[xx.min(), xx.max(), yy.min(), yy.max()],
    cmap="bwr",
    vmin=0,
    vmax=1,
    interpolation="nearest",
    aspect="auto",
)
ax[4][3].set_aspect("equal")
ax[4][3].tick_params(labelsize=ticksize)
ax[4][3].set_yticks([])
ax[4][3].set_xticks([])

ax1 = ax[5][3].imshow(
    proba_kdn,
    extent=[xx.min(), xx.max(), yy.min(), yy.max()],
    cmap="bwr",
    vmin=0,
    vmax=1,
    interpolation="nearest",
    aspect="auto",
)
ax[5][3].set_aspect("equal")
ax[5][3].tick_params(labelsize=ticksize)
ax[5][3].set_yticks([])
ax[5][3].set_xticks([-2,-1,0,1,2])

#######################################################
df = loadmat('kdn_experiments/results/polynomial_plot_data.mat')
proba_nn = 1-np.flip(df["nn_proba"][:, 0].reshape(400, 400), axis=1)
proba_kdn = np.flip(df["kdn_proba"][:, 0].reshape(400, 400), axis=1)

ax1 = ax[4][4].imshow(
    proba_nn,
    extent=[xx.min(), xx.max(), yy.min(), yy.max()],
    cmap="bwr",
    vmin=0,
    vmax=1,
    interpolation="nearest",
    aspect="auto",
)
#fig.colorbar(ax1, ax=ax[4][4], anchor=(0, 0.3), shrink=0.85)
ax[4][4].set_aspect("equal")
ax[4][4].tick_params(labelsize=ticksize)
ax[4][4].set_yticks([])
ax[4][4].set_xticks([])

ax1 = ax[5][4].imshow(
    proba_kdn,
    extent=[xx.min(), xx.max(), yy.min(), yy.max()],
    cmap="bwr",
    vmin=0,
    vmax=1,
    interpolation="nearest",
    aspect="auto",
)
#fig.colorbar(ax1, anchor=(0, 0.3), shrink=0.85)

ax[5][4].set_aspect("equal")
ax[5][4].tick_params(labelsize=ticksize)
ax[5][4].set_yticks([])
ax[5][4].set_xticks([-2,-1,0,1,2])

#plt.savefig('plots/simulations.pdf')
# %%
def calc_stat(a, reps=45):
    a_med = []
    a_25 = []
    a_75 = []
    a = a.reshape(-1,reps)
    return np.median(a,axis=1), np.quantile(a,[.25], axis=1)[0], np.quantile(a,[.75], axis=1)[0]
# %%
sns.set_context('talk')
sample_size = [50, 100, 500, 1000, 5000, 10000]

fig, ax = plt.subplots(5,4, figsize=(45,40))
title_size = 45
ticksize = 30

for ax_ in ax:
    for ax__ in ax_:
        ax__.tick_params(labelsize=ticksize)

df = loadmat('kdn_experiments/results/graphs/gxor.mat')

med, a_25, a_75 = calc_stat(1-df['kdn_acc'])
med_nn, nn_25, nn_75 = calc_stat(1-df['nn_acc'])

ax[0][0].plot(sample_size, med[1:], c="b", label='KDN')
ax[0][0].plot(sample_size, med_nn[1:], c="c", label='NN')

ax[0][0].fill_between(sample_size, a_25[1:], a_75[1:], facecolor='b', alpha=.3)
ax[0][0].fill_between(sample_size, nn_25[1:], nn_75[1:], facecolor='c', alpha=.3)

ax[0][0].set_xscale('log')
#ax[0][0].set_xlabel('Sample size')
ax[0][0].set_xticks([])
ax[0][0].set_ylabel('Generalization Error', fontsize=ticksize)

right_side = ax[0][0].spines["right"]
right_side.set_visible(False)
top_side = ax[0][0].spines["top"]
top_side.set_visible(False)

df_ = loadmat('kdf_experiments/results/gxor_plot_data.mat')
ax[0][0].plot(sample_size, df_['error_kdf_med'].ravel(), c="r", label='KDF')
ax[0][0].plot(sample_size, df_['error_rf_med'].ravel(), c="k", label='RF')

ax[0][0].fill_between(sample_size, df_["error_kdf_25"].ravel(), df_["error_kdf_75"].ravel(), facecolor='r', alpha=.3)
ax[0][0].fill_between(sample_size, df_["error_rf_25"].ravel(), df_["error_rf_75"].ravel(), facecolor='k', alpha=.3)

ax[0][0].legend(fontsize=ticksize, frameon=False)

##################################################################################################
med, a_25, a_75 = calc_stat(df['kdn_hd'])
med_nn, nn_25, nn_75 = calc_stat(df['nn_hd'])

ax[0][1].plot(sample_size, med[1:], c="b", label='KDN')
ax[0][1].plot(sample_size, med_nn[1:], c="c", label='NN')

ax[0][1].fill_between(sample_size, a_25[1:], a_75[1:], facecolor='b', alpha=.3)
ax[0][1].fill_between(sample_size, nn_25[1:], nn_75[1:], facecolor='c', alpha=.3)

ax[0][1].set_xscale('log')
ax[0][1].set_xticks([])
ax[0][1].set_ylabel('Hellinger Distance', fontsize=ticksize)

right_side = ax[0][1].spines["right"]
right_side.set_visible(False)
top_side = ax[0][1].spines["top"]
top_side.set_visible(False)

ax[0][1].plot(sample_size, df_['hellinger_kdf_med'].ravel(), c="r", label='KDF')
ax[0][1].plot(sample_size, df_['hellinger_rf_med'].ravel(), c="k", label='RF')

ax[0][1].fill_between(sample_size, df_["hellinger_kdf_25"].ravel(), df_["hellinger_kdf_75"].ravel(), facecolor='r', alpha=.3)
ax[0][1].fill_between(sample_size, df_["hellinger_rf_25"].ravel(), df_["hellinger_rf_75"].ravel(), facecolor='k', alpha=.3)
ax[0][1].set_title('Gaussian XOR', fontsize=title_size)

##################################################################################################
med, a_25, a_75 = calc_stat(df['kdn_mmcIn'])
med_nn, nn_25, nn_75 = calc_stat(df['nn_mmcIn'])

ax[0][2].plot(sample_size, med[1:], c="b", label='KDN')
ax[0][2].plot(sample_size, med_nn[1:], c="c", label='NN')

ax[0][2].fill_between(sample_size, a_25[1:], a_75[1:], facecolor='b', alpha=.3)
ax[0][2].fill_between(sample_size, nn_25[1:], nn_75[1:], facecolor='c', alpha=.3)

ax[0][2].set_xscale('log')
ax[0][2].set_xticks([])
ax[0][2].set_ylabel('Mean Max Confidence\n (In Distribution)', fontsize=ticksize)

right_side = ax[0][2].spines["right"]
right_side.set_visible(False)
top_side = ax[0][2].spines["top"]
top_side.set_visible(False)

ax[0][2].plot(sample_size, df_['mmcIn_kdf_med'].ravel(), c="r", label='KDF')
ax[0][2].plot(sample_size, df_['mmcIn_rf_med'].ravel(), c="k", label='RF')

ax[0][2].fill_between(sample_size, df_["mmcIn_kdf_25"].ravel(), df_["mmcIn_kdf_75"].ravel(), facecolor='r', alpha=.3)
ax[0][2].fill_between(sample_size, df_["mmcIn_rf_25"].ravel(), df_["mmcIn_rf_75"].ravel(), facecolor='k', alpha=.3)

##################################################################################################
med, a_25, a_75 = calc_stat(df['kdn_mmcOut'])
med_nn, nn_25, nn_75 = calc_stat(df['nn_mmcOut'])

ax[0][3].plot(sample_size, med[1:], c="b", label='KDN')
ax[0][3].plot(sample_size, med_nn[1:], c="c", label='NN')

ax[0][3].fill_between(sample_size, a_25[1:], a_75[1:], facecolor='b', alpha=.3)
ax[0][3].fill_between(sample_size, nn_25[1:], nn_75[1:], facecolor='c', alpha=.3)

ax[0][3].set_xscale('log')
ax[0][3].set_xticks([])
ax[0][3].set_ylabel('Mean Max Confidence\n (Out Distribution)', fontsize=ticksize)

right_side = ax[0][3].spines["right"]
right_side.set_visible(False)
top_side = ax[0][3].spines["top"]
top_side.set_visible(False)

ax[0][3].plot(sample_size, df_['mmcOut_kdf_med'].ravel(), c="r", label='KDF')
ax[0][3].plot(sample_size, df_['mmcOut_rf_med'].ravel(), c="k", label='RF')

ax[0][3].fill_between(sample_size, df_["mmcOut_kdf_25"].ravel(), df_["mmcOut_kdf_75"].ravel(), facecolor='r', alpha=.3)
ax[0][3].fill_between(sample_size, df_["mmcOut_rf_25"].ravel(), df_["mmcOut_rf_75"].ravel(), facecolor='k', alpha=.3)

#########################################################
#########################################################
df = loadmat('kdn_experiments/results/graphs/spiral.mat')

med, a_25, a_75 = calc_stat(1-df['kdn_acc'])
med_nn, nn_25, nn_75 = calc_stat(1-df['nn_acc'])

ax[1][0].plot(sample_size, med[1:], c="b", label='KDN')
ax[1][0].plot(sample_size, med_nn[1:], c="c", label='NN')

ax[1][0].fill_between(sample_size, a_25[1:], a_75[1:], facecolor='b', alpha=.3)
ax[1][0].fill_between(sample_size, nn_25[1:], nn_75[1:], facecolor='c', alpha=.3)

ax[1][0].set_xscale('log')
ax[1][0].set_xticks([])
ax[1][0].set_ylabel('Generalization Error', fontsize=ticksize)

right_side = ax[1][0].spines["right"]
right_side.set_visible(False)
top_side = ax[1][0].spines["top"]
top_side.set_visible(False)

df_ = loadmat('kdf_experiments/results/spiral_plot_data.mat')
ax[1][0].plot(sample_size, df_['error_kdf_med'].ravel(), c="r", label='KDF')
ax[1][0].plot(sample_size, df_['error_rf_med'].ravel(), c="k", label='RF')

ax[1][0].fill_between(sample_size, df_["error_kdf_25"].ravel(), df_["error_kdf_75"].ravel(), facecolor='r', alpha=.3)
ax[1][0].fill_between(sample_size, df_["error_rf_25"].ravel(), df_["error_rf_75"].ravel(), facecolor='k', alpha=.3)

##################################################################################################
med, a_25, a_75 = calc_stat(df['kdn_hd'])
med_nn, nn_25, nn_75 = calc_stat(df['nn_hd'])

ax[1][1].plot(sample_size, med[1:], c="b", label='KDN')
ax[1][1].plot(sample_size, med_nn[1:], c="c", label='NN')

ax[1][1].fill_between(sample_size, a_25[1:], a_75[1:], facecolor='b', alpha=.3)
ax[1][1].fill_between(sample_size, nn_25[1:], nn_75[1:], facecolor='c', alpha=.3)

ax[1][1].set_xscale('log')
ax[1][1].set_xticks([])
ax[1][1].set_ylabel('Hellinger Distance', fontsize=ticksize)

right_side = ax[1][1].spines["right"]
right_side.set_visible(False)
top_side = ax[1][1].spines["top"]
top_side.set_visible(False)

ax[1][1].plot(sample_size, df_['hellinger_kdf_med'].ravel(), c="r", label='KDF')
ax[1][1].plot(sample_size, df_['hellinger_rf_med'].ravel(), c="k", label='RF')

ax[1][1].fill_between(sample_size, df_["hellinger_kdf_25"].ravel(), df_["hellinger_kdf_75"].ravel(), facecolor='r', alpha=.3)
ax[1][1].fill_between(sample_size, df_["hellinger_rf_25"].ravel(), df_["hellinger_rf_75"].ravel(), facecolor='k', alpha=.3)
ax[1][1].set_title('Spiral', fontsize=title_size)
##################################################################################################
med, a_25, a_75 = calc_stat(df['kdn_mmcIn'])
med_nn, nn_25, nn_75 = calc_stat(df['nn_mmcIn'])

ax[1][2].plot(sample_size, med[1:], c="b", label='KDN')
ax[1][2].plot(sample_size, med_nn[1:], c="c", label='NN')

ax[1][2].fill_between(sample_size, a_25[1:], a_75[1:], facecolor='b', alpha=.3)
ax[1][2].fill_between(sample_size, nn_25[1:], nn_75[1:], facecolor='c', alpha=.3)

ax[1][2].set_xscale('log')
ax[1][2].set_xticks([])
ax[1][2].set_ylabel('Mean Max Confidence\n (In Distribution)', fontsize=ticksize)

right_side = ax[1][2].spines["right"]
right_side.set_visible(False)
top_side = ax[1][2].spines["top"]
top_side.set_visible(False)

ax[1][2].plot(sample_size, df_['mmcIn_kdf_med'].ravel(), c="r", label='KDF')
ax[1][2].plot(sample_size, df_['mmcIn_rf_med'].ravel(), c="k", label='RF')

ax[1][2].fill_between(sample_size, df_["mmcIn_kdf_25"].ravel(), df_["mmcIn_kdf_75"].ravel(), facecolor='r', alpha=.3)
ax[1][2].fill_between(sample_size, df_["mmcIn_rf_25"].ravel(), df_["mmcIn_rf_75"].ravel(), facecolor='k', alpha=.3)

##################################################################################################
med, a_25, a_75 = calc_stat(df['kdn_mmcOut'])
med_nn, nn_25, nn_75 = calc_stat(df['nn_mmcOut'])

ax[1][3].plot(sample_size, med[1:], c="b", label='KDN')
ax[1][3].plot(sample_size, med_nn[1:], c="c", label='NN')

ax[1][3].fill_between(sample_size, a_25[1:], a_75[1:], facecolor='b', alpha=.3)
ax[1][3].fill_between(sample_size, nn_25[1:], nn_75[1:], facecolor='c', alpha=.3)

ax[1][3].set_xscale('log')
ax[1][3].set_xticks([])
ax[1][3].set_ylabel('Mean Max Confidence\n (Out Distribution)', fontsize=ticksize)

right_side = ax[1][3].spines["right"]
right_side.set_visible(False)
top_side = ax[1][3].spines["top"]
top_side.set_visible(False)

ax[1][3].plot(sample_size, df_['mmcOut_kdf_med'].ravel(), c="r", label='KDF')
ax[1][3].plot(sample_size, df_['mmcOut_rf_med'].ravel(), c="k", label='RF')

ax[1][3].fill_between(sample_size, df_["mmcOut_kdf_25"].ravel(), df_["mmcOut_kdf_75"].ravel(), facecolor='r', alpha=.3)
ax[1][3].fill_between(sample_size, df_["mmcOut_rf_25"].ravel(), df_["mmcOut_rf_75"].ravel(), facecolor='k', alpha=.3)

#########################################################
#########################################################
df = loadmat('kdn_experiments/results/graphs/circle.mat')

med, a_25, a_75 = calc_stat(1-df['kdn_acc'])
med_nn, nn_25, nn_75 = calc_stat(1-df['nn_acc'])

ax[2][0].plot(sample_size, med[1:], c="b", label='KDN')
ax[2][0].plot(sample_size, med_nn[1:], c="c", label='NN')

ax[2][0].fill_between(sample_size, a_25[1:], a_75[1:], facecolor='b', alpha=.3)
ax[2][0].fill_between(sample_size, nn_25[1:], nn_75[1:], facecolor='c', alpha=.3)

ax[2][0].set_xscale('log')
ax[2][0].set_xticks([])
ax[2][0].set_ylabel('Generalization Error', fontsize=ticksize)

right_side = ax[2][0].spines["right"]
right_side.set_visible(False)
top_side = ax[2][0].spines["top"]
top_side.set_visible(False)

df_ = loadmat('kdf_experiments/results/circle_plot_data.mat')
ax[2][0].plot(sample_size, df_['error_kdf_med'].ravel(), c="r", label='KDF')
ax[2][0].plot(sample_size, df_['error_rf_med'].ravel(), c="k", label='RF')

ax[2][0].fill_between(sample_size, df_["error_kdf_25"].ravel(), df_["error_kdf_75"].ravel(), facecolor='r', alpha=.3)
ax[2][0].fill_between(sample_size, df_["error_rf_25"].ravel(), df_["error_rf_75"].ravel(), facecolor='k', alpha=.3)

##################################################################################################
med, a_25, a_75 = calc_stat(df['kdn_hd'])
med_nn, nn_25, nn_75 = calc_stat(df['nn_hd'])

ax[2][1].plot(sample_size, med[1:], c="b", label='KDN')
ax[2][1].plot(sample_size, med_nn[1:], c="c", label='NN')

ax[2][1].fill_between(sample_size, a_25[1:], a_75[1:], facecolor='b', alpha=.3)
ax[2][1].fill_between(sample_size, nn_25[1:], nn_75[1:], facecolor='c', alpha=.3)

ax[2][1].set_xscale('log')
ax[2][1].set_xticks([])
ax[2][1].set_ylabel('Hellinger Distance', fontsize=ticksize)

right_side = ax[2][1].spines["right"]
right_side.set_visible(False)
top_side = ax[2][1].spines["top"]
top_side.set_visible(False)

ax[2][1].plot(sample_size, df_['hellinger_kdf_med'].ravel(), c="r", label='KDF')
ax[2][1].plot(sample_size, df_['hellinger_rf_med'].ravel(), c="k", label='RF')

ax[2][1].fill_between(sample_size, df_["hellinger_kdf_25"].ravel(), df_["hellinger_kdf_75"].ravel(), facecolor='r', alpha=.3)
ax[2][1].fill_between(sample_size, df_["hellinger_rf_25"].ravel(), df_["hellinger_rf_75"].ravel(), facecolor='k', alpha=.3)
ax[2][1].set_title('Circle', fontsize=title_size)
##################################################################################################
med, a_25, a_75 = calc_stat(df['kdn_mmcIn'])
med_nn, nn_25, nn_75 = calc_stat(df['nn_mmcIn'])

ax[2][2].plot(sample_size, med[1:], c="b", label='KDN')
ax[2][2].plot(sample_size, med_nn[1:], c="c", label='NN')

ax[2][2].fill_between(sample_size, a_25[1:], a_75[1:], facecolor='b', alpha=.3)
ax[2][2].fill_between(sample_size, nn_25[1:], nn_75[1:], facecolor='c', alpha=.3)

ax[2][2].set_xscale('log')
ax[2][2].set_xticks([])
ax[2][2].set_ylabel('Mean Max Confidence\n (In Distribution)', fontsize=ticksize)

right_side = ax[2][2].spines["right"]
right_side.set_visible(False)
top_side = ax[2][2].spines["top"]
top_side.set_visible(False)

ax[2][2].plot(sample_size, df_['mmcIn_kdf_med'].ravel(), c="r", label='KDF')
ax[2][2].plot(sample_size, df_['mmcIn_rf_med'].ravel(), c="k", label='RF')

ax[2][2].fill_between(sample_size, df_["mmcIn_kdf_25"].ravel(), df_["mmcIn_kdf_75"].ravel(), facecolor='r', alpha=.3)
ax[2][2].fill_between(sample_size, df_["mmcIn_rf_25"].ravel(), df_["mmcIn_rf_75"].ravel(), facecolor='k', alpha=.3)

##################################################################################################
med, a_25, a_75 = calc_stat(df['kdn_mmcOut'])
med_nn, nn_25, nn_75 = calc_stat(df['nn_mmcOut'])

ax[2][3].plot(sample_size, med[1:], c="b", label='KDN')
ax[2][3].plot(sample_size, med_nn[1:], c="c", label='NN')

ax[2][3].fill_between(sample_size, a_25[1:], a_75[1:], facecolor='b', alpha=.3)
ax[2][3].fill_between(sample_size, nn_25[1:], nn_75[1:], facecolor='c', alpha=.3)

ax[2][3].set_xscale('log')
ax[2][3].set_xticks([])
ax[2][3].set_ylabel('Mean Max Confidence\n (Out Distribution)', fontsize=ticksize)

right_side = ax[2][3].spines["right"]
right_side.set_visible(False)
top_side = ax[2][3].spines["top"]
top_side.set_visible(False)

ax[2][3].plot(sample_size, df_['mmcOut_kdf_med'].ravel(), c="r", label='KDF')
ax[2][3].plot(sample_size, df_['mmcOut_rf_med'].ravel(), c="k", label='RF')

ax[2][3].fill_between(sample_size, df_["mmcOut_kdf_25"].ravel(), df_["mmcOut_kdf_75"].ravel(), facecolor='r', alpha=.3)
ax[2][3].fill_between(sample_size, df_["mmcOut_rf_25"].ravel(), df_["mmcOut_rf_75"].ravel(), facecolor='k', alpha=.3)

#########################################################
#########################################################
df = loadmat('kdn_experiments/results/graphs/sinewave.mat')

med, a_25, a_75 = calc_stat(1-df['kdn_acc'])
med_nn, nn_25, nn_75 = calc_stat(1-df['nn_acc'])

ax[3][0].plot(sample_size, med[1:], c="b", label='KDN')
ax[3][0].plot(sample_size, med_nn[1:], c="c", label='NN')

ax[3][0].fill_between(sample_size, a_25[1:], a_75[1:], facecolor='b', alpha=.3)
ax[3][0].fill_between(sample_size, nn_25[1:], nn_75[1:], facecolor='c', alpha=.3)

ax[3][0].set_xscale('log')
ax[3][0].set_xticks([])
ax[3][0].set_ylabel('Generalization Error', fontsize=ticksize)

right_side = ax[3][0].spines["right"]
right_side.set_visible(False)
top_side = ax[3][0].spines["top"]
top_side.set_visible(False)

df_ = loadmat('kdf_experiments/results/sinewave_plot_data.mat')
ax[3][0].plot(sample_size, df_['error_kdf_med'].ravel(), c="r", label='KDF')
ax[3][0].plot(sample_size, df_['error_rf_med'].ravel(), c="k", label='RF')

ax[3][0].fill_between(sample_size, df_["error_kdf_25"].ravel(), df_["error_kdf_75"].ravel(), facecolor='r', alpha=.3)
ax[3][0].fill_between(sample_size, df_["error_rf_25"].ravel(), df_["error_rf_75"].ravel(), facecolor='k', alpha=.3)

##################################################################################################
med, a_25, a_75 = calc_stat(df['kdn_hd'])
med_nn, nn_25, nn_75 = calc_stat(df['nn_hd'])

ax[3][1].plot(sample_size, med[1:], c="b", label='KDN')
ax[3][1].plot(sample_size, med_nn[1:], c="c", label='NN')

ax[3][1].fill_between(sample_size, a_25[1:], a_75[1:], facecolor='b', alpha=.3)
ax[3][1].fill_between(sample_size, nn_25[1:], nn_75[1:], facecolor='c', alpha=.3)

ax[3][1].set_xscale('log')
ax[3][1].set_xticks([])
ax[3][1].set_ylabel('Hellinger Distance', fontsize=ticksize)

right_side = ax[3][1].spines["right"]
right_side.set_visible(False)
top_side = ax[3][1].spines["top"]
top_side.set_visible(False)

ax[3][1].plot(sample_size, df_['hellinger_kdf_med'].ravel(), c="r", label='KDF')
ax[3][1].plot(sample_size, df_['hellinger_rf_med'].ravel(), c="k", label='RF')

ax[3][1].fill_between(sample_size, df_["hellinger_kdf_25"].ravel(), df_["hellinger_kdf_75"].ravel(), facecolor='r', alpha=.3)
ax[3][1].fill_between(sample_size, df_["hellinger_rf_25"].ravel(), df_["hellinger_rf_75"].ravel(), facecolor='k', alpha=.3)
ax[3][1].set_title('Sinewave', fontsize=title_size)
##################################################################################################
med, a_25, a_75 = calc_stat(df['kdn_mmcIn'])
med_nn, nn_25, nn_75 = calc_stat(df['nn_mmcIn'])

ax[3][2].plot(sample_size, med[1:], c="b", label='KDN')
ax[3][2].plot(sample_size, med_nn[1:], c="c", label='NN')

ax[3][2].fill_between(sample_size, a_25[1:], a_75[1:], facecolor='b', alpha=.3)
ax[3][2].fill_between(sample_size, nn_25[1:], nn_75[1:], facecolor='c', alpha=.3)

ax[3][2].set_xscale('log')
ax[3][2].set_xticks([])
ax[3][2].set_ylabel('Mean Max Confidence\n (In Distribution)', fontsize=ticksize)

right_side = ax[3][2].spines["right"]
right_side.set_visible(False)
top_side = ax[3][2].spines["top"]
top_side.set_visible(False)

ax[3][2].plot(sample_size, df_['mmcIn_kdf_med'].ravel(), c="r", label='KDF')
ax[3][2].plot(sample_size, df_['mmcIn_rf_med'].ravel(), c="k", label='RF')

ax[3][2].fill_between(sample_size, df_["mmcIn_kdf_25"].ravel(), df_["mmcIn_kdf_75"].ravel(), facecolor='r', alpha=.3)
ax[3][2].fill_between(sample_size, df_["mmcIn_rf_25"].ravel(), df_["mmcIn_rf_75"].ravel(), facecolor='k', alpha=.3)

##################################################################################################
med, a_25, a_75 = calc_stat(df['kdn_mmcOut'])
med_nn, nn_25, nn_75 = calc_stat(df['nn_mmcOut'])

ax[3][3].plot(sample_size, med[1:], c="b", label='KDN')
ax[3][3].plot(sample_size, med_nn[1:], c="c", label='NN')

ax[3][3].fill_between(sample_size, a_25[1:], a_75[1:], facecolor='b', alpha=.3)
ax[3][3].fill_between(sample_size, nn_25[1:], nn_75[1:], facecolor='c', alpha=.3)

ax[3][3].set_xscale('log')
ax[3][3].set_xticks([])
ax[3][3].set_ylabel('Mean Max Confidence\n (Out Distribution)', fontsize=ticksize)

right_side = ax[3][3].spines["right"]
right_side.set_visible(False)
top_side = ax[3][3].spines["top"]
top_side.set_visible(False)

ax[3][3].plot(sample_size, df_['mmcOut_kdf_med'].ravel(), c="r", label='KDF')
ax[3][3].plot(sample_size, df_['mmcOut_rf_med'].ravel(), c="k", label='RF')

ax[3][3].fill_between(sample_size, df_["mmcOut_kdf_25"].ravel(), df_["mmcOut_kdf_75"].ravel(), facecolor='r', alpha=.3)
ax[3][3].fill_between(sample_size, df_["mmcOut_rf_25"].ravel(), df_["mmcOut_rf_75"].ravel(), facecolor='k', alpha=.3)

#########################################################
#########################################################
df = loadmat('kdn_experiments/results/graphs/polynomial.mat')

med, a_25, a_75 = calc_stat(1-df['kdn_acc'])
med_nn, nn_25, nn_75 = calc_stat(1-df['nn_acc'])

ax[4][0].plot(sample_size, med[1:], c="b", label='KDN')
ax[4][0].plot(sample_size, med_nn[1:], c="c", label='NN')

ax[4][0].fill_between(sample_size, a_25[1:], a_75[1:], facecolor='b', alpha=.3)
ax[4][0].fill_between(sample_size, nn_25[1:], nn_75[1:], facecolor='c', alpha=.3)

ax[4][0].set_xscale('log')
ax[4][0].set_xlabel('Sample size', fontsize=ticksize)
ax[4][0].set_ylabel('Generalization Error', fontsize=ticksize)

right_side = ax[4][0].spines["right"]
right_side.set_visible(False)
top_side = ax[4][0].spines["top"]
top_side.set_visible(False)

df_ = loadmat('kdf_experiments/results/polynomial_plot_data.mat')
ax[4][0].plot(sample_size, df_['error_kdf_med'].ravel(), c="r", label='KDF')
ax[4][0].plot(sample_size, df_['error_rf_med'].ravel(), c="k", label='RF')

ax[4][0].fill_between(sample_size, df_["error_kdf_25"].ravel(), df_["error_kdf_75"].ravel(), facecolor='r', alpha=.3)
ax[4][0].fill_between(sample_size, df_["error_rf_25"].ravel(), df_["error_rf_75"].ravel(), facecolor='k', alpha=.3)

##################################################################################################
med, a_25, a_75 = calc_stat(df['kdn_hd'])
med_nn, nn_25, nn_75 = calc_stat(df['nn_hd'])

ax[4][1].plot(sample_size, med[1:], c="b", label='KDN')
ax[4][1].plot(sample_size, med_nn[1:], c="c", label='NN')

ax[4][1].fill_between(sample_size, a_25[1:], a_75[1:], facecolor='b', alpha=.3)
ax[4][1].fill_between(sample_size, nn_25[1:], nn_75[1:], facecolor='c', alpha=.3)

ax[4][1].set_xscale('log')
ax[4][1].set_xlabel('Sample size', fontsize=ticksize)
ax[4][1].set_ylabel('Hellinger Distance', fontsize=ticksize)

right_side = ax[4][1].spines["right"]
right_side.set_visible(False)
top_side = ax[4][1].spines["top"]
top_side.set_visible(False)

ax[4][1].plot(sample_size, df_['hellinger_kdf_med'].ravel(), c="r", label='KDF')
ax[4][1].plot(sample_size, df_['hellinger_rf_med'].ravel(), c="k", label='RF')

ax[4][1].fill_between(sample_size, df_["hellinger_kdf_25"].ravel(), df_["hellinger_kdf_75"].ravel(), facecolor='r', alpha=.3)
ax[4][1].fill_between(sample_size, df_["hellinger_rf_25"].ravel(), df_["hellinger_rf_75"].ravel(), facecolor='k', alpha=.3)
ax[4][1].set_title('Polynomial', fontsize=title_size)
##################################################################################################
med, a_25, a_75 = calc_stat(df['kdn_mmcIn'])
med_nn, nn_25, nn_75 = calc_stat(df['nn_mmcIn'])

ax[4][2].plot(sample_size, med[1:], c="b", label='KDN')
ax[4][2].plot(sample_size, med_nn[1:], c="c", label='NN')

ax[4][2].fill_between(sample_size, a_25[1:], a_75[1:], facecolor='b', alpha=.3)
ax[4][2].fill_between(sample_size, nn_25[1:], nn_75[1:], facecolor='c', alpha=.3)

ax[4][2].set_xscale('log')
ax[4][2].set_xlabel('Sample size', fontsize=ticksize)
ax[4][2].set_ylabel('Mean Max Confidence\n (In Distribution)', fontsize=ticksize)

right_side = ax[4][2].spines["right"]
right_side.set_visible(False)
top_side = ax[4][2].spines["top"]
top_side.set_visible(False)

ax[4][2].plot(sample_size, df_['mmcIn_kdf_med'].ravel(), c="r", label='KDF')
ax[4][2].plot(sample_size, df_['mmcIn_rf_med'].ravel(), c="k", label='RF')

ax[4][2].fill_between(sample_size, df_["mmcIn_kdf_25"].ravel(), df_["mmcIn_kdf_75"].ravel(), facecolor='r', alpha=.3)
ax[4][2].fill_between(sample_size, df_["mmcIn_rf_25"].ravel(), df_["mmcIn_rf_75"].ravel(), facecolor='k', alpha=.3)

##################################################################################################
med, a_25, a_75 = calc_stat(df['kdn_mmcOut'])
med_nn, nn_25, nn_75 = calc_stat(df['nn_mmcOut'])

ax[4][3].plot(sample_size, med[1:], c="b", label='KDN')
ax[4][3].plot(sample_size, med_nn[1:], c="c", label='NN')

ax[4][3].fill_between(sample_size, a_25[1:], a_75[1:], facecolor='b', alpha=.3)
ax[4][3].fill_between(sample_size, nn_25[1:], nn_75[1:], facecolor='c', alpha=.3)

ax[4][3].set_xscale('log')
ax[4][3].set_xlabel('Sample size', fontsize=ticksize)
ax[4][3].set_ylabel('Mean Max Confidence\n (Out Distribution)', fontsize=ticksize)

right_side = ax[4][3].spines["right"]
right_side.set_visible(False)
top_side = ax[4][3].spines["top"]
top_side.set_visible(False)

ax[4][3].plot(sample_size, df_['mmcOut_kdf_med'].ravel(), c="r", label='KDF')
ax[4][3].plot(sample_size, df_['mmcOut_rf_med'].ravel(), c="k", label='RF')

ax[4][3].fill_between(sample_size, df_["mmcOut_kdf_25"].ravel(), df_["mmcOut_kdf_75"].ravel(), facecolor='r', alpha=.3)
ax[4][3].fill_between(sample_size, df_["mmcOut_rf_25"].ravel(), df_["mmcOut_rf_75"].ravel(), facecolor='k', alpha=.3)

plt.savefig('plots/simulation_res.pdf')
# %%


#%%
import sympy as sp
import numpy as np

# Setting up the symbolic variables
x, nu, t = sp.symbols('x nu t')
phi = sp.exp(-(x-4*t)**2/(4*nu*(t+1))) + sp.exp(-(x-4*t-2*np.pi)**2/(4*nu*(t+1)))

# Evaluate the partial derivative dphi/dx using sympy
phiprime = phi.diff(x)
#print phiprime

# Create the initial conditions function
u = -2*nu*(phiprime/phi)+4
#print u

# Transform the symbolic equation into a function using lambdify
from sympy.utilities.lambdify import lambdify
ufunc = lambdify ((t, x, nu), u)

nu = 0.01/np.pi
vxn = 256
vtn = 100
x = np.linspace(-1.0, 1, vxn)
t = np.linspace(0, 1, vtn)
u = np.zeros((vxn, vtn))
for i, x_i in enumerate(x):
    for j, t_j in enumerate(t):
        u[i, j] = ufunc(t_j, x_i, nu)

print(u)

np.save("1d-burgers/data/burgers_x", x)
np.save("1d-burgers/data/burgers_t", t)
np.save("1d-burgers/data/burgers_u", u)

import matplotlib.pyplot as plt
from scipy.interpolate import griddata
import scipy.io
import matplotlib.gridspec as gridspec

X, T = np.meshgrid(x,t)

# Preparing the inputs x and t (meshed as X, T) for predictions in one single array, as X_star
X_star = np.hstack((X.flatten()[:,None], T.flatten()[:,None]))
U_pred = griddata(X_star, u.T.flatten(), (X, T), method='cubic')

####### Row 0: u(t,x) ##################
gs0 = gridspec.GridSpec(1, 2)
gs0.update(top=1-0.06, bottom=1-1.0/3.0+0.06, left=0.15, right=0.85, wspace=0)
ax = plt.subplot(gs0[:, :])

h = ax.imshow(U_pred.T, interpolation='nearest', cmap='rainbow',
            extent=[t.min(), t.max(), x.min(), x.max()],
                  origin='lower', aspect='auto')
plt.show()

import numpy,import scipy,from scipy,import sympy,from sympy


import numpy as np
import matplotlib.pyplot as plt
from time import time

def createKDTree(dataSet, depth):
    n = np.shape(dataSet)[0]
    treeNode = {}
    if n == 0:
        return None
    else:
        n, m = np.shape(dataSet)
        split_axis = depth % m
        depth += 1
        treeNode['split'] = split_axis
        dataSet = sorted(dataSet, key=lambda a: a[split_axis])
        num = n // 2
        treeNode['median'] = dataSet[num]
        treeNode['left'] = createKDTree(dataSet[:num], depth)
        treeNode['right'] = createKDTree(dataSet[num + 1:], depth)
        return treeNode

def searchTree(tree, data):
    k = len(data)
    if tree is None:
        return [0] * k, float('inf')
    split_axis = tree['split']
    median_point = tree['median']
    if data[split_axis] <= median_point[split_axis]:
        nearestPoint, nearestDistance = searchTree(tree['left'], data)
    else:
        nearestPoint, nearestDistance = searchTree(tree['right'], data)
    nowDistance = np.linalg.norm(data - median_point)  # the distance between data to current point
    if 0 < nowDistance < nearestDistance:
        nearestDistance = nowDistance
        nearestPoint = median_point.copy()
    splitDistance = abs(data[split_axis] - median_point[split_axis])  # the distance between hyperplane
    if splitDistance > nearestDistance > 0:
        return nearestPoint, nearestDistance
    else:
        if data[split_axis] <= median_point[split_axis]:
            nextTree = tree['right']
        else:
            nextTree = tree['left']
        nearPoint, nearDistance = searchTree(nextTree, data)
        if nearestDistance > nearDistance > 0:
            nearestDistance = nearDistance
            nearestPoint = nearPoint.copy()
        return nearestPoint, nearestDistance


from functools import lru_cache
import inspect
import math

import numpy as np

def sigmoid(x):
    return 1.0 / (1 + np.exp(-x))

@lru_cache(maxsize=10)
def choose(n, k):
    return math.comb(n, k)

def gen_choose(n, r):
    return np.prod(np.arange(n, n - r, -1)) / math.factorial(r)

def get_num_args(function):
    return len(get_parameters(function))

def get_parameters(function):
    return inspect.signature(function).parameters

# Just to have a less heavyweight name for this extremely common operation
#
# We may wish to have more fine-grained control over division by zero behavior
# in the future (separate specifiable values for 0/0 and x/0 with x != 0),
# but for now, we just allow the option to handle indeterminate 0/0.

def clip(a, min_a, max_a):
    if a < min_a:
        return min_a
    elif a > max_a:
        return max_a
    return a

def fdiv(a, b, zero_over_zero_value=None):
    if zero_over_zero_value is not None:
        out = np.full_like(a, zero_over_zero_value)
        where = np.logical_or(a != 0, b != 0)
    else:
        out = None
        where = True

    return np.true_divide(a, b, out=out, where=where)

def binary_search(function,
                  target,
                  lower_bound,
                  upper_bound,
                  tolerance=1e-4):
    lh = lower_bound
    rh = upper_bound
    while abs(rh - lh) > tolerance:
        mh = np.mean([lh, rh])
        lx, mx, rx = [function(h) for h in (lh, mh, rh)]
        if lx == target:
            return lx
        if rx == target:
            return rx

        if lx <= target and rx >= target:
            if mx > target:
                rh = mh
            else:
                lh = mh
        elif lx > target and rx < target:
            lh, rh = rh, lh
        else:
            return None
    return mh


# -*- coding: utf-8 -*-
"""
Created on Mon Feb 24 11:37:13 2020

@author: Sumanshu
"""

import numpy as np
import random as rnd
import matplotlib.pyplot as plt

r=3
fig_sz=(3,3)
fig1=plt.figure(figsize=fig_sz)
ax1=plt.axes([0.125,0.125,0.75,0.75])
d_tot=1000
d_circ=0
area_square=(2*r)**2
pi=np.pi
plt.xlim([-r,r])
plt.ylim([-r,r])
plt.axis('square')
k=0
theta=np.arange(0,2*pi,0.01)

for jj in range(d_tot):
    k +=1
    x=rnd.uniform(-r,r)
    y=rnd.uniform(-r,r)
    dist=np.sqrt(x**2+y**2)

    if dist<=r:
        d_circ += 1
        ax1.plot(x,y,'r.')
    else:
        ax1.plot(x,y,'b.')
    pr=d_circ/k
    area=pr*area_square
    # print(area)

plt.xticks(np.linspace(-r, r,2*r+1))
plt.yticks(np.linspace(-r, r,2*r+1))
ax1.plot(r*np.cos(theta),r*np.sin(theta),'k-',linewidth=2.0)

print('Calculated area of circle from simulation:', area)
area_circ=pi*r**2
print('Calculated area of circle using equation:',area_circ)
fig1.savefig('monte-carlo.png',dpi=300)


import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt
import matplotlib.cm as cm

DIMENSIONS = 2
CLUSTERS = 10
DATA_POINTS = 10000

TRAINING_STEPS = 1000
TOLERANCE = 0

def generate_gmm_data(points, components, dimensions):
    """Generates synthetic data of a given size from a random GMM"""
    np.random.seed(10)

    c_means = np.random.normal(size=[components, dimensions]) * 10
    c_variances = np.abs(np.random.normal(size=[components, dimensions]))
    c_weights = np.abs(np.random.normal(size=[components]))
    c_weights /= np.sum(c_weights)

    result = np.zeros((points, dimensions), dtype=np.float32)

    for i in range(points):
        comp = np.random.choice(np.array(range(10)), p=c_weights)
        result[i] = np.random.multivariate_normal(
            c_means[comp], np.diag(c_variances[comp])
        )

    np.random.seed()

    return result, c_means, c_variances, c_weights

def plot_clustered_data(points, c_means, c_assignments):
    """Plots the cluster-colored data and the cluster means"""
    colors = cm.rainbow(np.linspace(0, 1, CLUSTERS))

    for cluster, color in zip(range(CLUSTERS), colors):
        c_points = points[c_assignments == cluster]
        plt.plot(c_points[:, 0], c_points[:, 1], ".", color=color, zorder=0)
        plt.plot(c_means[cluster, 0], c_means[cluster, 1], ".", color="black", zorder=1)

    plt.show()

# PREPARING DATA

# generating DATA_POINTS points from a GMM with CLUSTERS components
data, true_means, true_variances, true_weights = generate_gmm_data(DATA_POINTS, CLUSTERS, DIMENSIONS)

# BUILDING COMPUTATIONAL GRAPH

# model inputs: generated data points
input = tf.placeholder(tf.float32, [None, DIMENSIONS])

# trainable variables: clusters means
random_point_ids = tf.squeeze(tf.multinomial(tf.ones([1, tf.shape(input)[0]]), CLUSTERS))
means = tf.Variable(tf.gather(input, random_point_ids), dtype=tf.float32)

# E-step: recomputing cluster assignments according to the current means
inputs_ex, means_ex = tf.expand_dims(input, 0), tf.expand_dims(means, 1)
distances = tf.reduce_sum(tf.squared_difference(inputs_ex, means_ex), 2)
assignments = tf.argmin(distances, 0)

# M-step: relocating cluster means according to the computed assignments
sums = tf.unsorted_segment_sum(input, assignments, CLUSTERS)
counts = tf.reduce_sum(tf.one_hot(assignments, CLUSTERS), 0)
means_ = tf.divide(sums, tf.expand_dims(counts, 1))

# distortion measure: sum of squared distances
# from each point to the closest cluster mean
distortion = tf.reduce_sum(tf.reduce_min(distances, 0))

# updating the means by new values
train_step = means.assign(means_)

# RUNNING COMPUTATIONAL GRAPH

# creating session
sess = tf.InteractiveSession()

# initializing trainable variables
sess.run(tf.global_variables_initializer(), feed_dict={input: data})

previous_assignments = None

# training loop
for step in range(TRAINING_STEPS):
    # executing a training step and
    # fetching evaluation information
    distortion_measure, current_means, current_assignments, _ = sess.run(
        [distortion, means_, assignments, train_step],
        feed_dict={input: data}
    )

    if step > 0:
        # computing the number of re-assignments during the step
        re_assignments = (current_assignments != previous_assignments).sum()
        print("{0}:\tdistortion {1:.2f}\tre-assignments {2}".format(
            step, distortion_measure, re_assignments))

        # stopping if no re-assignments occurred
        if re_assignments <= TOLERANCE:
            break
    else:
        print("{0}:\tdistortion {1:.2f}".format(
            step, distortion_measure))

    previous_assignments = current_assignments

plot_clustered_data(data, current_means, current_assignments)


import numpy as np
import scipy as sp
from scipy import integrate, special

from .function_fast import function_1d, function_2d

## One-dimensional functions
def linear(x, *p):
    """[summary]

    Arguments:
        x {[type]} -- [description]

    Returns:
        [type] -- [description]
    """
    return p[0] * x + p[1]

def quad_1d(x, *p):
    """[summary]

    Arguments:
        x {[type]} -- [description]

    Returns:
        [type] -- [description]
    """
    A, x0, C = p
    xc = x - x0

    return A * xc**2 + C

def gaussian_1d(x, *p):
    """[summary]

    Arguments:
        x {[type]} -- [description]

    Returns:
        [type] -- [description]
    """

    return function_1d.gaussian(x, *p)

def ThomasFermi_1d(x, *p):
    """[summary]

    Arguments:
        x {[type]} -- [description]

    Returns:
        [type] -- [description]
    """
    A, x0, r0, C = p
    xc = x - x0

    y = 1 - (xc / r0)**2
    y[y<0] = 0

    return A * y + C

def bimodal_1d(x, *p):
    """[summary]

    Arguments:
        x {[type]} -- [description]

    Returns:
        [type] -- [description]
    """
    ATF, xTF, rTF, Ag, x0, sigma, C = p

    # Thomas Fermi distribution
    pTF = [ATF, xTF, rTF, 0]
    yTF = ThomasFermi_1d(x, *pTF)

    # Gaussian distribution
    pg = [Ag, x0, sigma, 0]
    yg = gaussian_1d(x, *pg)

    return yTF + yg + C

def double_gauss_1d(x, *p):
    """[summary]

    Arguments:
        x {[type]} -- [description]
    # Parameters
    # 0: amplitude - 1, 1: center - 1, 2: 1/e width - 1,
    # 3: amplitude - 2, 4: center - 2, 5: 1/e width - 2,
    # 6: offset

    Returns:
        [type] -- [description]
    """
    A1, x01, sigma1, A2, x02, sigma2, C = p

    # Gaussian - 1
    xc1 = x - x01
    y1 = A1 * np.exp(-xc1**2 / sigma1**2)

    # Gaussian - 2
    xc2 = x - x02
    y2 = A2 * np.exp(-xc2**2 / sigma2**2)

    return y1 + y2 + C

def lorentzian_1d(x, *p):
    """[summary]

    Arguments:
        x {[type]} -- [description]

    Returns:
        [type] -- [description]
    """
    A, x0, sigma, C = p

    x_shift = x - x0

    return A * sigma**2 / (x_shift**2 + sigma**2) + C

def psf_1d(x, *p):
    """[summary]

    Arguments:
        x {[type]} -- [description]

    Returns:
        [type] -- [description]
    """
    A, x0, alpha, C = p

    r = (x - x0) * alpha
    y = np.zeros(r.shape)

    y[r!=0] = A * (2 * special.j1(r[r!=0]) / r[r!=0])**2
    y[r==0] = A

    return y + C

## Two-dimensional functions
def gaussian_2d(xy_mesh, *p):
    """[summary]

    Arguments:
        xy_mesh {[type]} -- [description]

    Returns:
        [type] -- [description]
    """
    # unpack 1D list into 2D x and y coords
    (x, y) = xy_mesh

    return function_2d.gaussian(x, y, *p)

def gaussian_2d_tmp(xy_mesh, *p):
    """[summary]

    Arguments:
        xy_mesh {[type]} -- [description]

    Returns:
        [type] -- [description]
    """
    # unpack 1D list into 2D x and y coords
    (x, y) = xy_mesh

    # # Parameters
    A, x0, sigmax, y0, sigmay, C = p

    xc = x - x0
    yc = y - y0

    return A * np.exp(-xc**2/sigmax**2 - yc**2/sigmay**2) + C

def gaussian_2dr(xy_mesh, *p):
    """[summary]

    Arguments:
        x {[type]} -- [description]

    Returns:
        [type] -- [description]
    """
    # unpack 1D list into 2D x and y coords
    (x, y) = xy_mesh

    # Parameters
    A, x0, sigmax, y0, sigmay, theta, C = p

    xc = x - x0
    yc = y - y0

    xr = xc * np.cos(theta) - yc * np.sin(theta)
    yr = xc * np.sin(theta) + yc * np.cos(theta)

    return A * np.exp(-xr**2/sigmax**2 - yr**2/sigmay**2) + C

def psf_2d(xy_mesh, *p):
    """[summary]

    Arguments:
        xy_mesh {[type]} -- [description]

    Returns:
        [type] -- [description]
    """
    # unpack 1D list into 2D x and y coords
    (x, y) = xy_mesh

    # Parameters
    A, x0, y0, alpha, C = p

    xc = x - x0
    yc = y - y0

    r = np.sqrt(xc**2 + yc**2) * alpha

    z = np.zeros(r.shape)

    z[r!=0] = A * (2 * special.j1(r[r!=0]) / r[r!=0])**2
    z[r==0] = A

    return z + C

def psf_2d_tmp(xy_mesh, *p):
    """[summary]

    Arguments:
        xy_mesh {[type]} -- [description]

    Returns:
        [type] -- [description]
    """
    # unpack 1D list into 2D x and y coords
    (x, y) = xy_mesh

    z = function_2d.psf(x, y, *p)
    return z.reshape(x.shape)

def psf_2d_int(xy_mesh, *p):
    """[summary]

    Arguments:
        xy_mesh {[type]} -- [description]

    Returns:
        [type] -- [description]
    """
    # unpack 1D list into 2D x and y coords
    (x, y) = xy_mesh

    A, x0, y0, alpha, C = p
    xc = x - x0
    yc = y - y0

    s_eff = x[1, 0] - x[0, 0]
    z = np.zeros(x0.shape)
    z_err = np.zeros(x0.shape)

    def f(x, y):
        if x == 0:
            val = 1
        else:
            r = alpha * np.sqrt(xc**2 + yc**2)
            val = (2 * special.j1(r) / r)**2

        return alpha**2 / (4 * np.pi) * val

    for i in range(x.shape[0]):
        for j in range(x.shape[1]):
            z[i, j], z_err[i, j] = integrate.dblquad(f, xc[i, j], xc[i, j] - s_eff,
                                                                            lambda x: yc[i, j] + s_eff, lambda x: yc[i, j])

    z = C + A * z

    return z

def psf_2d_gaussian(xy_mesh, *p):
    """[summary]

    Arguments:
        xy_mesh {[type]} -- [description]

    Returns:
        [type] -- [description]
    """
    # unpack 1D list into 2D x and y coords
    (x, y) = xy_mesh

    # # Parameters
    # A, x0, y0, sigma, C = p

    # r2 = (x - x0)**2 + (y - y0)**2

    # return A * np.exp(-r2/sigma**2) + C

    return function_2d.gaussian_iso(x, y, *p)


import numpy as np

#reproducibility
np.random.seed(0)

class OneHiddenLayerNetwork:

    #
    # activation functions
    #

    @staticmethod
    def tang(y):
        return np.tanh(y)

    @staticmethod
    def derivative_tang(y):
        return 1.0 - y ** 2

    @staticmethod
    def sigmoid(y):
        return 1 / (1 + np.exp(-y))

    @staticmethod
    def derivative_sigmoid(y):
        return y * (1 - y)

    def __init__(self, learning_rate=0.1):

        """
         neural network architecture
         simple 2 x 2 x 1 that is enough for XOR example
         input x hidden x output
        """
        self.learning_rate = learning_rate
        self.output = None

        # weights with random values
        self.weights = [
            np.random.uniform(low=-0.2, high=0.2, size=(2, 2)),  # input layer
            np.random.uniform(low=-2, high=2, size=(2, 1))  # hidden layer
        ]

    def activation(self, activation_type, y):
        if activation_type == 'sigmoid':
            return self.sigmoid(y)
        if activation_type == 'tang':
            return self.tang(y)

        raise ValueError('Undefined activation function: {}'.format(activation_type))

    def derivative_activation(self, activation_type, y):
        if activation_type == 'sigmoid':
            return self.derivative_sigmoid(y)
        if activation_type == 'tang':
            return self.derivative_tang(y)

        raise ValueError('Undefined derivative activation function: {}'.format(activation_type))

    #
    # forward pass
    # layer by layer
    #
    def feed_forward_pass(self, x_values):

        # forward pass
        input_layer = x_values
        hidden_layer = self.activation('tang', np.dot(input_layer, self.weights[0]))
        output_layer = self.activation('tang', np.dot(hidden_layer, self.weights[1]))

        self.layers = [
            input_layer,
            hidden_layer,
            output_layer
        ]

        # last layer is an output
        return self.layers[2]

    #
    # back propagation error through the network layers
    #
    def backward_pass(self, target_output, actual_output):

        # divergence of network output
        err = (target_output - actual_output)

        # backward from output to input layer
        # propagate gradients using chain rule
        for backward in range(2, 0, -1):
            err_delta = err * self.derivative_activation('tang', self.layers[backward])

            # update weights using computed gradient
            self.weights[backward - 1] += self.learning_rate * np.dot(self.layers[backward - 1].T, err_delta)

            # propagate error using updated weights of previous layer
            err = np.dot(err_delta, self.weights[backward - 1].T)

    def train(self, x_values, target):
        self.output = self.feed_forward_pass(x_values)
        self.backward_pass(target, self.output)

    def predict(self, x_values):
        return self.feed_forward_pass(x_values)

#XOR gate i/o, simple small dataset ;)
X = np.array(([0, 0], [0, 1], [1, 0], [1, 1]), dtype=float)
y = np.array(([0], [1], [1], [0]), dtype=float)

network = OneHiddenLayerNetwork(learning_rate=0.1)
iterations = 5000

# training loop
for i in range(iterations):
    network.train(X, y)

    ten = iterations // 10
    if i % ten == 0:
        print('-' * 10)
        print("Iteration number: " + str(i) + ' / ' +
              "Squared loss: " + str(np.mean(np.square(y - network.output))))

# predict
for i in range(len(X)):
    print('-' * 10)
    print('Input value: ' + str(X[i]))
    print('Predicted target: ' + str(network.predict(X[i])))
    print('Actual target: ' + str(y[i]))


from __future__ import annotations
import logging
import numpy as np

"""
PICARD Visual inspection of the Picard condition.

 eta = picard(U,s,b,d)
 eta = picard(U,sm,b,d)  ,  sm = [sigma,mu]

 Plots the singular values, s(i), the abs. value of the Fourier
 coefficients, |U(:,i)'*b|, and a (possibly smoothed) curve of
 the solution coefficients eta(i) = |U(:,i)'*b|/s(i).

 If s = [sigma,mu], where gamma = sigma./mu are the generalized
 singular values, then this routine plots gamma(i), |U(:,i)'*b|,
 and (smoothed) eta(i) = |U(:,i)'*b|/gamma(i).

 The smoothing is a geometric mean over 2*d+1 points, centered
 at point # i. If nargin = 3, then d = 0 (i.e, no smothing).

 Reference: P. C. Hansen, "The discrete Picard condition for
 discrete ill-posed problems", BIT 30 (1990), 658-672.

 Per Christian Hansen, IMM, April 14, 2001.
 ported to Python by Michael Hirsch
"""

def picard(U, s, b, d=0) -> tuple[np.ndarray, int, np.ndarray, np.ndarray, np.ndarray, int]:

    n, ps = np.atleast_2d(s).T.shape

    beta = np.abs(np.asfortranarray(U[:, :n]).T.dot(b))
    eta = np.zeros(n, order="F")

    if ps == 2:
        s = s[:, 0] / s[:, 1]

    d21 = 2 * d + 1
    keta = np.arange(d, n - d)

    if (s == 0).any():  # 10**-14 is OK?
        logging.warning("** picard: Division by zero: singular values")

    for i in keta:
        es = np.s_[i - d : i + d + 1]
        eta[i] = (beta[es].prod() ** (1 / d21)) / s[i]

    return eta, n, s, beta, keta, ps

def plotpicard(
    n: int, s: np.ndarray, beta: np.ndarray, eta: np.ndarray, keta: np.ndarray, ps: int
):
    from matplotlib.pyplot import figure

    ni = np.arange(n)
    ax = figure().gca()
    ax.semilogy(ni, s, ".-")  # breaks for inf
    ax.semilogy(ni, beta, "x")  # breaks for inf
    ax.semilogy(keta, eta[keta], "o")
    ax.set_xlabel("i")
    ax.set_title("Picard plot")
    # ax.autoscale(True,tight=True)
    if ps == 1:
        ax.legend((r"$\sigma_i$", r"$|u_i^T b|$", r"$|u_i^T b|/\sigma_i$"), loc="lower left")
    else:
        ax.legend(
            (r"$\sigma_i/\mu_i$", r"$|u_i^T b|$", r"$|u_i^T b|/ (\sigma_i/\mu_i)$"),
            loc="lower left",
        )


import numpy as np
from sympy import Matrix, Transpose, expand, latex

def expandir_matriz(matriz):
    fila = matriz.shape[0]
    for i in range(fila):
        for j in range(fila):
            matriz[i, j] = expand(matriz[i, j])
    return matriz

def lagrangiana_1D(x, n):
    puntos = np.linspace(-1, 1, n)
    resultado = []
    for i in range(np.size(puntos)):
        b = puntos[puntos != puntos[i]]
        polinomio = 1.0
        for j in range(np.size(b)):
            polinomio *= (x - b[j]) / (puntos[i] - b[j])
        resultado.append(expand(polinomio))
    return resultado

def lagrangiana_2D(x, y, n):
    Lx = Matrix(lagrangiana_1D(x, n))
    Ly = Transpose(Matrix(lagrangiana_1D(y, n)))
    Lxy = expandir_matriz((Lx*Ly))
    return Lxy

def lagrangiana_3D(x, y, z, n):
    Lx = Matrix(lagrangiana_1D(x, n))
    Ly = Transpose(Matrix(lagrangiana_1D(y, n)))
    Lz = Transpose(Matrix(lagrangiana_1D(z, n)))
    Lxy = expandir_matriz((Lx*Ly))
    filas = Lxy.shape[0]
    Lxyz = []
    for i in range(filas):
        resultado = expandir_matriz((Lxy[:, i]*Lz))
        Lxyz = np.append(Lxyz, resultado)
    return Lxyz.reshape(filas, filas, filas)

def recorrer_matriz_espiral(M):
    filas_M = M.shape[0]
    iteraciones = filas_M - (filas_M/2)
    for i in range(iteraciones):
        m = M[i:filas_M-i, i:filas_M-i]
        filas_m = m.shape[0]
        n = filas_m - 1
        if n == 0:
            print m[0,0]
        else:
            for j in range(n):
                print m[j,0]
            for j in range(n):
                print m[n,j]
            for j in range(n,0,-1):
                print m[j,n]
            for j in range(n,0,-1):
                print m[0,j]

def guardar_matriz_espiral(M):
    resultado = []
    filas_M = M.shape[0]
    iteraciones = filas_M - (filas_M/2)
    for i in range(iteraciones):
        m = M[i:filas_M-i, i:filas_M-i]
        filas_m = m.shape[0]
        n = filas_m - 1
        if n == 0:
            resultado.append(m[0,0])
        else:
            for j in range(n):
                resultado.append(m[j,0])
            for j in range(n):
                resultado.append(m[n,j])
            for j in range(n,0,-1):
                resultado.append(m[j,n])
            for j in range(n,0,-1):
                resultado.append(m[0,j])
    return resultado

def recorrer_matriz_3d_espiral(N):
    filas = N.shape[0]
    for i in range(filas):
        recorrer_matriz_espiral(N[i])

def guardar_matriz_3d_espiral(N):
    resultado1 = []
    filas = N.shape[0]
    for i in range(filas):
        resultado1.append(guardar_matriz_espiral(N[i]))
    return reduce(lambda x,y: x+y,resultado1)

def hermitiana_1D(x, n):
    resultado1 = lagrangiana_1D(x, n)
    resultado2 = []
    puntos2 = np.linspace(-1, 1, n)
    for i in range(np.size(puntos2)):
        A = x - puntos2[i]
        B = (resultado1[i].diff(x)).subs(x, puntos2[i])
        C = resultado1[i]**2
        D = (1 - (A*B))*C
        resultado2.append(expand(D))
        resultado2.append(expand(A*C))
    return resultado2


# -*- coding: utf-8 -*-
"""
Created on Mon May 24 06:47:31 2021

@author: Pramesh Kumar
"""

import numpy as np
from gurobipy import *
import time, math

def GenerateCuttingStockData():
    n=12;
    d=[20,13,17,18,20,45,13,50,20,35,20,30];
    w=[29,75,58,32,18,47,24,62,70,33,21,41];
    W=100;

    u=np.zeros(n) # no. of rolls of type i from one big roll
    for i in range(n):
        u[i]=math.floor(W/w[i]);

    nraw=0; # total big rolls needed
    for i in range(n):
        nraw+=math.ceil(d[i]/math.floor(W/w[i]))

    return n,d,w,u,W,nraw;

def SolveCuttingStockModel(n,d,w,u,W,nraw,types):

    val=-float("inf");
    # Step 0: start clock
    ts = time.time();

    # Step 1: Create the model
    m = Model()
    if (types=="IP"):
        x = {(i,j): m.addVar(lb = 0.0, vtype = GRB.INTEGER, name = str(i) + '_' + str(j)) for i in range(n) for j in range(nraw)}
        y = {j: m.addVar(lb = 0.0, vtype = GRB.BINARY, name = str(j)) for j in range(nraw)}
    elif (types=="LP"):
        x = {(i,j): m.addVar(lb = 0.0, vtype = GRB.CONTINUOUS, name = str(i) + '_' + str(j)) for i in range(n) for j in range(nraw)}
        y = {j: m.addVar(lb = 0.0, ub = 1.0, vtype = GRB.CONTINUOUS, name = str(j)) for j in range(nraw)}
    else:
        print("\nModel type ({:s}) is not recognized \n".format(types));
        val=-float("inf");
        return val

    obj = sum([y[k] for k in y])

    for i in range(n):
        m.addConstr(sum([x[i, j] for j in range(nraw)]) == d[i])

    for j in range(nraw):
        m.addConstr(sum([w[i] * x[i, j] for i in range(n)]) <= W*y[j])

    m.setObjective(obj, sense=GRB.MINIMIZE); m.update(); m.Params.OutputFlag = 1; m.Params.InfUnbdInfo = 1; m.Params.DualReductions = 0

    # Step 2: Solve the model
    m.optimize()

    # Step 3: Display solution if required
    if m.status == 2:
        print("---------------------------------------\n")
        print("Natural Formulation:",types)
        print(" {:6.2f} \n".format(m.objVal))
        print("---------------------------------------\n")

        if (type=="IP"):
            for j in range(nraw):
                valy=y[j].x
                if (valy>1e-4):
                    print("* Pattern : [")
                    for i in range(n):
                        print("{:3d}, ".format(round(x[i,j].x)));
                    print("   .......   used   {:3d} times\n".format(round(y[j].x)))

        else:
            for j in range(nraw):
                valy=y[j].x
                if (valy>1e-4):
                    print("* Pattern : [")
                    for i in range(n):
                        print("{:3.2f}, ".format(x[i,j].x));
                    print("   .......   used   {:3f} times\n".format(round(y[j].x)))

    # Step 4: Calculate solution time
    te = time.time();
    Deltat = (te-ts);

    print("-----> Run time:      {:6.4f}\n\n".format(Deltat))

    # Step 5: output optimal value;
    return m.objVal

def SolveCuttingStockModelCG(n,d,w,u,W,nraw,type, verbsol):

    # Step 0: start clock
    ts = time.time();

    # Step 1: Initialize variables
    t=1;

    # Step 2: Display solution
    print("---------------------------------------\n")
    print("Column Generation Algorithm: \n")
    print("---------------------------------------\n")

    # Step 3: Generate a first collection of simple patterns
    A=np.zeros((n,n)) # pttern of type i rom pattern j
    for i in range(n):
        A[i,i]=u[i]

    # Step 4: Create and solve initial master problem
    m = Model()
    x = {i: m.addVar(lb = 0.0, vtype = GRB.CONTINUOUS, name = str(i)) for i in range(n)}
    m.update()
    demConstrs = {i: m.addConstr(sum([A[i,j]*x[j] for j in range(n)]) >= d[i]) for i in range(n)}
    obj = sum([x[j] for j in range(n)])
    m.setObjective(obj, sense=GRB.MINIMIZE); m.update(); m.Params.OutputFlag = verbsol; m.Params.InfUnbdInfo = 1; m.Params.DualReductions = 0
    m.optimize()
    if m.status == 2:
        val = m.objVal
        print("* Iteration {:3.1f}: {:7.3f}       ".format(t,val))

    # Step 5: Create and solve initial subproblem
    subm = Model()
    y = {i: subm.addVar(lb = 0.0, vtype = GRB.INTEGER, name = str(i)) for i in range(n)}
    for j in range(n):
        subm.addConstr(sum([w[i]*y[i] for i in range(n)]) <= W)

    subObj = sum([demConstrs[i].pi * y[i] for i in range(n)])
    subm.setObjective(subObj, sense=GRB.MAXIMIZE); m.update(); m.Params.OutputFlag = verbsol; m.Params.InfUnbdInfo = 1; m.Params.DualReductions = 0

    subm.optimize()
    if subm.status == 2:
        rc=subm.objVal;
        print("// |rc| = {:7.3f} //       Pattern = [".format(rc-1))
        for i in range(n):
            print("{:3.0f},".format(round(y[i].x)))

    # Step 6: Create names and matrices for new columns generated
    newcols = [];
    newpatterns =np.zeros((0,n), int)

    # Step 7: Perform column generation
    while (rc>1+1e-5):
        # Step 7.1: Increase number of patterns added
        t+=1;

        # Step 7.2: Add variable to master and record generated pattern
        # Column is used for the added varaibles that are used to modify the constraints
        c = Column()
        for i in demConstrs:
            c.addTerms(y[i].x, demConstrs[i])

        z = m.addVar(lb = 0.0, vtype = GRB.CONTINUOUS, name = str(len(x)), column = c)
        m.update()

        newcols.append(z)
        newpatterns = np.append(newpatterns, np.array([[y[k].x for k in y]]), axis=0)

        # Step 7.3: Solve master model
        m.optimize()
        if m.status == 2:
            val=m.objVal
        print("* Iteration {:3.1f}: {:7.3f}       ".format(t,val))

        # Step 7.4: Set the objective and solve new subproblem
        subObj = sum([demConstrs[i].pi * y[i] for i in range(n)])
        subm.setObjective(subObj, sense=GRB.MAXIMIZE); m.update()

        subm.optimize()
        if subm.status == 2:
            rc=subm.objVal;
            print("// |rc| = {:7.3f} //       Pattern = [".format(rc-1))
            for i in range(n):
                print("{:3.0f},".format(round(y[i].x)))

    val=m.objVal;

    # Step 8: Write lp model to a file
    m.write("CGmodel.lp")

    # Step 9: Display solution obtained at the end of the CG algorithm
    print("---------------------------------------\n")
    print("Solution (Relaxation): {:7.3f} \n".format(val))
    print("---------------------------------------\n")

    for i in range(n):
        newpat = x[i].x;
        if (newpat>1e-4):
            print("* Pattern {:3.0f}: [".format(i));
            for j in range(n-1):
                print("{:3.0f}, ".format(A[i,j]))
            print("{:3.0f}]   .......   used {:7.3f} times \n".format(A[i,n-1],newpat))

    for i in range(len(newcols)):
        newpat= newcols[i].x;
        if (newpat>1e-4):
            print("* Pattern {:3.0f}: [".format(i+n));
            for j in range(n-1):
                print("{:3.0f}, ".format(round(newpatterns[i,j])))
            print("{:3.0f}]   .......   used {:7.3f} times \n".format(round(newpatterns[i,n-1]),newpat))

    print("---------------------------------------\n")

    # Step 11: Calculate solution time
    te = time.time();
    Deltat = (te-ts);
    if (verbsol>=1):
        print("-----> Run time:      {:6.4f}\n\n".format(Deltat))

    # Step 12: return values
    return(val)

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

# Step 1: Define dimensions and parameters

n,d,w,u,W,nraw = GenerateCuttingStockData();

# Step 2: Solve column generation
vallp3 = SolveCuttingStockModelCG(n,d,w,u,W,nraw,"IP", 0)

# Step 3:
#vallp1 = SolveCuttingStockModel(n,d,w,u,W,nraw,"LP");
#valip1 = SolveCuttingStockModel(n,d,w,u,W,nraw,"IP");