import math
import numpy as np
import random

# get a random point within a circile
def get_random_point(circle_x,circle_y,circle_r):
    # random angle
    alpha = 2 * math.pi * random.random()
    # random radius
    r = circle_r * math.sqrt(random.random())
    # calculating coordinates
    x = r * math.cos(alpha) + circle_x
    y = r * math.sin(alpha) + circle_y
    return x,y

# generate list of random points
def generate_position(num_points,circle_x,circle_y,circle_r):
    points_x=[]
    points_y=[]
    for i in range(num_points):
        x,y=get_random_point(circle_x,circle_y,circle_r)
        points_x.append(x)
        points_y.append(y)
    return np.c_[points_x,points_y]

# return the 9 nozzles position, use margin to control the distance between nozzles and edge of the circle
def get_nozzles_position(circle_x,circle_y, radius,margin=0.2):
    # random angle
    cluster_density=2 * math.pi/36
    cluster_alpha=[]
    for num in range(3):
        cluster_alpha.append(2 * math.pi*num/3-cluster_density)
        cluster_alpha.append(2 * math.pi*num/3)
        cluster_alpha.append(2 * math.pi*num/3+cluster_density)
    positions=[]
    for alpha in cluster_alpha:
        # random radius
        r = radius + margin
        # calculating coordinates
        x = r * math.sin(alpha) + circle_x
        y = r * math.cos(alpha) + circle_y
        rotation= math.degrees(alpha)
        positions.append((x,y,-rotation))
    return positions

# Get the nozzles_status
def get_nozzles_status(active_nozzles):
    active_num=int(9*active_nozzles/100)
    active_order=[0,3,6,1,4,7,2,5,8]
    active_nozzles_array=[False for i in range(9)]
    for i in range(active_num):
        active_nozzles_array[active_order[i]]=True
    return active_nozzles_array

def update_nozzles_status(nozzles,status):
    for index, status in enumerate(status):
        if status:
            nozzles[index].set_text("+")
        else:
            nozzles[index].set_text("-")


from __future__ import division
import numpy
import matplotlib.pyplot as pyplot

USER = "Maciej Grabias"
USER_ID = 'njgh39'

def f(x):
    return (x**2.0)*numpy.sin(x) # function returning product of x squared times sin(x)

def g(x):
    return 2.0*x*numpy.sin(x) - (x**2.0 - 2.0)*numpy.cos(x) # function returning indefinite integral of
    # function above

def integrate_numeric(x0, x1, n_panels): # function returning the area under the curve of f(x)
    # using Simpson's rule
    panel_width = (x1 - x0) / n_panels
    area = 0.0

    for ix in range(n_panels):
        a = x0 + ix * panel_width
        panel_area = (panel_width/6) * (f(a) + 4 * f(a + panel_width/2) + f(a + panel_width))
        area = area + panel_area
    return area

def integrate_analytic(x0, x1):
    return g(x1) - g(x0)

PANEL_COUNTS = [4, 8, 16, 32, 64, 128, 256, 512, 1024]

X0 = 0
X1 = 2

y_data = []
ref = integrate_analytic(X0, X1)

for n in PANEL_COUNTS:
    s = integrate_numeric(X0, X1, n)
    error = abs((s-ref)/ref)
    y_data.append(error)

pyplot.figure(figsize = (6,6))
pyplot.loglog()
pyplot.scatter(PANEL_COUNTS, y_data)
pyplot.ylabel('Percentage Error')
pyplot.xlabel('Number of Panels')
pyplot.title("Percentage Error in the Numerical Method VS Number of Panels Used \n (both scales logarithmic)")
pyplot.show()

ANSWER1 = "The increasing number of panels increases the accuracy of the numerical method because \
narrower panels enable more accurate iteration and summation"
ANSWER2 = "Increasing panel count will still increase the accuracy of the method, but not as much \
as it does while using Simpson's method.'"


import math
import cmath

import numpy as np

from src.dirac_notation.bra import Bra
from src.dirac_notation.ket import Ket
from src.dirac_notation.matrix import Matrix
from src.dirac_notation import functions as dirac

# Kets and Bras

# Computational vectors |0>, |1>, ..., |m>
comp_ket_x = lambda x, d : Ket([ 1 if i == x else 0 for i in range(0, d) ]) if d > x else None
comp_bra_x = lambda x, d : Bra([ 1 if i == x else 0 for i in range(0, d) ]) if d > x else None

# Fourier vectors |e0>, |e1>, ..., |en>
fourier_ket_x = lambda x, d : (1 / math.sqrt(d)) * np.sum([ cmath.rect(1, (2 * math.pi * i * x) / d) * comp_ket_x(i, d) for i in range(0, d) ])
fourier_bra_x = lambda x, d : dirac.adjoint(fourier_ket_x(x, d))

# Angle vectors |0, theta> and |1, theta>
angle_ket_0 = lambda theta : (math.cos(theta / 2) * comp_ket_x(0, 2)) + (math.sin(theta / 2) * comp_ket_x(1, 2))
angle_ket_1 = lambda theta : (math.sin(theta / 2) * comp_ket_x(0, 2)) - (math.cos(theta / 2) * comp_ket_x(1, 2))

# Computational vectors in 2D, |0> and |1>
ket_0 = comp_ket_x(0, 2)
ket_1 = comp_ket_x(1, 2)
bra_0 = dirac.adjoint(ket_0)
bra_1 = dirac.adjoint(ket_0)

# Fourier vectors in 2D, |+> and |->
ket_plus = fourier_ket_x(0, 2)
ket_minus = fourier_ket_x(1, 2)
bra_plus = dirac.adjoint(ket_plus)
bra_minus = dirac.adjoint(ket_minus)

# Computational vectors in C^4, |psi_00>, |psi_01>, |psi_10> and |psi_11>
ket_psi_00 = dirac.tensor(ket_0, ket_0)
ket_psi_01 = dirac.tensor(ket_0, ket_1)
ket_psi_10 = dirac.tensor(ket_1, ket_0)
ket_psi_11 = dirac.tensor(ket_1, ket_1)
bra_psi_00 = dirac.adjoint(ket_psi_00)
bra_psi_01 = dirac.adjoint(ket_psi_01)
bra_psi_10 = dirac.adjoint(ket_psi_10)
bra_psi_11 = dirac.adjoint(ket_psi_11)

# Bell vectors, |omega+>, |omega->, |psi+> and |psi->
ket_phi_plus = (1 / math.sqrt(2)) * ((dirac.tensor(ket_0, ket_0)) + (dirac.tensor(ket_1, ket_1)))
ket_phi_minus = (1 / math.sqrt(2)) * ((dirac.tensor(ket_0, ket_0)) - (dirac.tensor(ket_1, ket_1)))
ket_psi_plus = (1 / math.sqrt(2)) * ((dirac.tensor(ket_0, ket_1)) + (dirac.tensor(ket_1, ket_0)))
ket_psi_minus = (1 / math.sqrt(2)) * ((dirac.tensor(ket_0, ket_1)) - (dirac.tensor(ket_1, ket_0)))
bra_phi_plus = dirac.adjoint(ket_phi_plus)
bra_phi_minus = dirac.adjoint(ket_phi_minus)
bra_psi_plus = dirac.adjoint(ket_psi_plus)
bra_psi_minus = dirac.adjoint(ket_psi_minus)

# Unitary matrices

# Basic matrices
zero_matrix = lambda d1, d2 : Matrix(np.zeros((d1, d2)))
identity_matrix = lambda d : Matrix(np.identity(d))

# Pauli matrices
pauli_x_matrix = Matrix(np.array([[0, 1], [1, 0]]))
pauli_y_matrix = Matrix(np.array([[0, -1.0j], [1.0j, 0]]))
pauli_z_matrix = Matrix(np.array([[1, 0], [0, -1]]))
n_sigma_matrix = lambda nx, ny, nz : (nx * pauli_x_matrix) + (ny * pauli_y_matrix) + (nz * pauli_z_matrix)

# Fourier transform matrix
fourier_transform_matrix = lambda d : sum([ fourier_ket_x(i, d) * comp_bra_x(i, d) for i in range(0, d) ], zero_matrix(d, d))
hadamard_matrix = fourier_transform_matrix(2) # 2D is known as Hadamard gate (H)

# CNOT matrix
cnot_matrix = dirac.tensor((ket_0 * bra_0), identity_matrix(2)) + dirac.tensor((ket_1 * bra_1), pauli_x_matrix)

# Basis

comp_basis_vectors = lambda d : [comp_ket_x(i, d) for i in range(0, d)]
fourier_basis_vectors = lambda d : [fourier_bra_x(i, d) for i in range(0, d)]

comp_2d_basis_vectors = [ket_0, ket_1]
psi_basis_vectors = [ket_psi_00, ket_psi_01, ket_psi_10, ket_psi_11]
bell_basis_vectors = [ket_phi_plus, ket_phi_minus, ket_psi_plus, ket_psi_minus]


from scipy.special import comb

def alternate_splice(n,m):
    alternate_splice_count=0
    for k in range(m, n+1):
        alternate_splice_count = (alternate_splice_count + comb(n, k, exact=True))%1000000

    return alternate_splice_count

def main():
    with open('datasets/rosalind_aspc.txt') as input_file:
        n, m = input_file.read().strip().split()

    alternate_splice_count=alternate_splice(int(n),int(m))

    print(str(alternate_splice_count))

    with open('solutions/rosalind_aspc.txt', 'w') as output_file:
        output_file.write(str(alternate_splice_count))

if(__name__=='__main__'):
    main()


# In this tutorial we will see how to fit curves to give set of data points
# In the first part, we will see how to make a linear fit

import numpy as np
import matplotlib.pyplot as plt

# lets create an artificial data set of x and y points to which we will fit a straight line
# We will make x=y in which case the slope =1 and intercept will be 0

x = np.arange(0,10,1)
# you can also add some variations to y to simulate an experimental datapoint.
# one way is to add gaussian noise by sampling a number from a gaussian distribution and adding it to y.
# like this:
noise = np.random.normal(0,1,10)# check tutorials 04 to learn about random number generatio
y =  x+noise # in case noise is zero, y = x will be a straight line with slope exactly equal to 1 and intercept exaclty 0

plt.scatter(x,y)

fit_res = np.polyfit(x, y, 1) # 1 is the degree of fit, 1 is for linear fit, 2 for quadratic plot and so on

# z contains the results of the fit
# This is how you get the slope and intercept

print("The fit results are:{0}".format(fit_res)) # the first element in the array is the slope and the second element is the intercept
print("The first value is the slope and the second is the intercept")
#Now lets plot the line as estimated by the linear fit:
y_fit = fit_res[0]*x + fit_res[1] # y=mx + c
# enable the line below to see the fitted line
plt.plot(x,y_fit)

plt.show()

# assignments
# # in many cases, one might require the error on the slope and intercept
# in which case one can do the following:

# fit_res, error= np.polyfit(x, y, 1,full=False, cov=True) # 1 is the degree of fit, 1 is for linear fit, 2 for quadratic plot and so on
# print("The error matrix is {0}".format(error))
# print("The diagonal elements are the errors") # The first element in the diagonal is that of slope and the second element in the diagonal is that of the intercept
# Try the above code

# In case you want to fit a second order polynomial  change np.polyfit(x,y,1) to np.polyfit(x,y,2)

# Change the mean and sigma in the gaussian distribution and see how the error matrix changes.


"""
Example of solving Mini-Max Problem
max { (x0-15)^2+(x1-80)^2, (x1-15)^2 + (x2-8)^2, (x2-8)^2 + (x0-80)^2 } -> min
Currently nsmm is single OO solver available for MMP
It defines function F(x) = max_i {f[i](x)}
and solves NSP F(x) -> min using solver ralg.
It's very far from specialized solvers (like MATLAB fminimax),
but it's better than having nothing at all,
and allows using of nonsmooth and noisy funcs.
This solver is intended to be enhanced in future.
"""
from numpy import *
from openopt import *

f1 = lambda x: (x[0]-15)**2 + (x[1]-80)**2
f2 = lambda x: (x[1]-15)**2 + (x[2]-8)**2
f3 = lambda x: (x[2]-8)**2 + (x[0]-80)**2
f = [f1, f2, f3]

# you can define matrices as numpy array, matrix, Python lists or tuples

#box-bound constraints lb <= x <= ub
lb = [0]*3# i.e. [0,0,0]
ub = [15,  inf,  80]

# linear ineq constraints A*x <= b
A = mat('4 5 6; 80 8 15')
b = [100,  350]

# non-linear eq constraints Aeq*x = beq
Aeq = mat('15 8 80')
beq = 90

# non-lin ineq constraints c(x) <= 0
c1 = lambda x: x[0] + (x[1]/8) ** 2 - 15
c2 = lambda x: x[0] + (x[2]/80) ** 2 - 15
c = [c1, c2]
#or: c = lambda x: (x[0] + (x[1]/8) ** 2 - 15, x[0] + (x[2]/80) ** 2 - 15)

# non-lin eq constraints h(x) = 0
h = lambda x: x[0]+x[2]**2 - x[1]

x0 = [0, 1, 2]
p = MMP(f,  x0,  lb = lb,  ub = ub,   A=A,  b=b,   Aeq = Aeq,  beq = beq,  c=c,  h=h, xtol = 1e-6, ftol=1e-6)
#p = MMP(f,  x0, ftol=1e-8)
# optional, matplotlib is required:
#p.plot=1
r = p.solve('nsmm', iprint=1, NLPsolver = 'ralg', maxIter=1e3, minIter=1e2)
print 'MMP result:',  r.ff

#
### let's check result via comparison with NSP solution
F= lambda x: max([f1(x),  f2(x),  f3(x)])
p = NSP(F,  x0, lb = lb, ub = ub,  c=c,  h=h,  A=A,  b=b,  Aeq = Aeq,  beq = beq, xtol = 1e-6, ftol=1e-6)
#p = NSP(F,  x0)
r_nsp = p.solve('ralg')
#print 'NSP result:',  r_nsp.ff,  'difference:', r_nsp.ff - r.ff
#"""
#starting solver nsmm (license: BSD)  with problem  unnamed
#  iter       ObjFun        log10(maxResidual)
#     0       6.4660e+03   +1.89
#   10       6.4860e+03   -0.68
#   20       6.4158e+03   -1.23
#   30       6.4119e+03   -3.08
#   40       6.3783e+03   -2.95
#   50       6.3950e+03   -4.05
#   60       6.3951e+03   -6.02
#   70       6.3938e+03   -6.02
#   78       6.3936e+03   -6.00
#nsmm has finished solving the problem unnamed
#istop:  3 (|| X[k] - X[k-1] || < xtol)
#Solver:   Time Elapsed = 0.41   CPU Time Elapsed = 0.38
#objFunValue: 6393.6196095379446 (feasible, max constraint =  9.95421e-07)
#MMP result: 6393.6196095379446
#starting solver ralg (license: BSD)  with problem  unnamed
#itn 0 : Fk= 6466.0 MaxResidual= 78.0
#itn 10  Fk: 6485.9728487666425 MaxResidual: 2.07e-01 ls: 2
#itn 20  Fk: 6415.8358391383163 MaxResidual: 5.92e-02 ls: 1
#itn 30  Fk: 6411.9310394431113 MaxResidual: 8.22e-04 ls: 3
#itn 40  Fk: 6378.3471060481961 MaxResidual: 1.12e-03 ls: 2
#itn 50  Fk: 6394.9848936519056 MaxResidual: 8.94e-05 ls: 0
#itn 60  Fk: 6395.054402295913 MaxResidual: 9.57e-07 ls: 1
#itn 70  Fk: 6393.8314202292149 MaxResidual: 9.63e-07 ls: 1
#itn 78  Fk: 6393.6196095379446 MaxResidual: 9.95e-07 ls: 1
#ralg has finished solving the problem unnamed
#istop:  3 (|| X[k] - X[k-1] || < xtol)
#Solver:   Time Elapsed = 0.44   CPU Time Elapsed = 0.32
#objFunValue: 6393.6196095379446 (feasible, max constraint =  9.95421e-07)
#NSP result: 6393.6196095379446 difference: 0.0
#"""


import numpy as np
from numpy import random
from numpy.linalg import norm
import numpy.linalg as LA
import argparse
import logging
import os
from matplotlib import pyplot as plt
import cvxpy as cp
from gl_cvx_gurobi import gl_cvx_gurobi
from gl_cvx_mosek import gl_cvx_mosek
from gl_gurobi import gl_gurobi
from gl_mosek import gl_mosek
from gl_SGD_primal import gl_SGD_primal
from gl_GD_primal import gl_GD_primal
from gl_ProxGD_primal import gl_ProxGD_primal
from gl_FGD_primal import gl_FGD_primal
from gl_FProxGD_primal import gl_FProxGD_primal
from pytablewriter import MarkdownTableWriter
from gl_ADMM_primal import gl_Admm_primal
from gl_ADMM_dual import gl_Admm_dual
from gl_ALM_dual import gl_Alm_dual
from pytablewriter import typehint

# min 0.5 * ||A * x - b||_2^2 + mu * ||x||_{1,2}
# Credit to
#   http://niaohe.ise.illinois.edu/IE598_2016/lasso_demo/index.html

def obj_func(x: np.ndarray):
    temp = np.matmul(A, x) - b
    fro_term = 0.5 * np.sum(np.multiply(temp, temp))
    regular_term = np.sum(np.apply_along_axis(func1d=lambda row: LA.norm(row), axis=1, arr=x))
    return fro_term + mu * regular_term

def gen_data(seed=97006855):
    n, m, l = 512, 256, 2
    mu = 1e-2
    generator = random.Generator(random.MT19937(seed=seed))
    A = generator.standard_normal(size=(m, n))
    k = round(n * 0.1)
    p = generator.permutation(n)[:k]
    u = np.zeros(shape=(n, l))
    u[p, :] = generator.standard_normal(size=(k, l))  # ground truth
    b = np.matmul(A, u)
    x0 = generator.standard_normal(size=(n, l))
    errfun = lambda x1, x2: norm(x1 - x2, 'fro') / (1 + norm(x1, 'fro'))
    errfun_exact = lambda x: norm(x - u, 'fro') / (1 + norm(x, 'fro'))
    sparsity = lambda x: np.sum(np.abs(x) > 1e-6 * np.max(np.abs(x))) / (n * l)
    return n, m, l, mu, A, b, u, x0, errfun, errfun_exact, sparsity

def setup_logger(logger_name, log_file, level=logging.INFO):
    log_setup = logging.getLogger(logger_name)
    formatter = logging.Formatter('%(asctime)s: %(levelname)-5s %(message)s')
    fileHandler = logging.FileHandler(log_file, mode='a')
    fileHandler.setFormatter(formatter)
    streamHandler = logging.StreamHandler()
    streamHandler.setFormatter(formatter)
    log_setup.setLevel(level)
    log_setup.addHandler(fileHandler)
    log_setup.addHandler(streamHandler)

def plot_result(mode: str, file_name: str, ground_truth, cvx_mosek_rv, cvx_gurobi_rv, x):
    plt.clf()
    fig = plt.figure(1)

    plt.subplot(2, 1, 1)
    plt.plot(np.arange(0, n, 1), ground_truth[:, 0], 'r*', label='ground truth')
    # plt.plot(np.arange(0, n, 1), cvx_mosek_rv[ :, 0 ], 'g^', label='CVX-Mosek')
    # plt.plot(np.arange(0, n, 1), cvx_gurobi_rv[ :, 0 ], 'bv', label='CVX-Gurobi')
    plt.plot(np.arange(0, n, 1), x[:, 0], 'mo', label=mode)
    plt.xlim(0, n)
    plt.title('{}: Results on the 1st dimension'.format(mode))

    plt.subplot(2, 1, 2)
    plt.plot(np.arange(0, n, 1), ground_truth[:, 1], 'r*', label='ground truth')
    # plt.plot(np.arange(0, n, 1), cvx_mosek_rv[ :, 1 ], 'g^', label='CVX-Mosek')
    # plt.plot(np.arange(0, n, 1), cvx_gurobi_rv[ :, 1 ], 'bv', label='CVX-Gurobi')
    plt.plot(np.arange(0, n, 1), x[:, 1], 'mo', label=mode)
    plt.xlim(0, n)
    plt.title('{}: Results on the 2nd dimension'.format(mode))

    plt.tight_layout()
    plt.show()
    plt.savefig(file_name)

cvx_mosek_rv, cvx_gurobi_rv = None, None

def write_to_table(log_dicts: dict):
    table_name = "Statistics"
    headers = None
    value_matrix = []
    for mode, log_dict in log_dicts.items():
        if headers is None:
            headers = ['solver'] + list(log_dict.keys())
        value_matrix.append([mode] + list(log_dict.values()))
    assert headers is not None
    type_hints = [typehint.String] * len(headers)
    writer = MarkdownTableWriter(
        table_name=table_name,
        headers=headers,
        value_matrix=value_matrix,
        type_hints=type_hints
    )
    writer.write_table()

def solve_routine(mode: str, func, x0, A, b, mu, opts, u, errfun, errfun_exact, sparsity):
    x, num_iters, out = func(x0, A, b, mu, opts)
    solve_time = out["tt"]
    fval = out["fval"]
    log_dict = {
        "cpu": "%5.2f" % solve_time,
        "iter": "%5d" % (-1 if num_iters is None else num_iters),
        "optval": "%6.5E" % fval,
        "sparsity": "%6.4f" % sparsity(x),
        "err-to-exact": "%3.2E" % errfun_exact(x),
        "err-to-cvx-mosek": "%3.2E" % errfun(cvx_mosek_rv, x),
        "err-to-cvx-gurobi": "%3.2E" % errfun(cvx_gurobi_rv, x)
    }
    log_str = ("[%-10s]: " % mode) + ', '.join(map(lambda x: x[0] + ": " + x[1], log_dict.items()))
    logger = logging.getLogger('opt')
    logger.info(log_str)
    plot_result(mode, os.path.join(destination_directory, "%s.svg" % mode), u, cvx_mosek_rv, cvx_gurobi_rv, x)
    return x, num_iters, out, log_dict

if __name__ == '__main__':
    parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter,
                                     description='A demo that solves the optimization problem '
                                                 '$\min_x{0.5 * ||A * x - b||_2^2 + mu * ||x||_{1,2}}$')
    parser.add_argument('--log', type=str, default='opt.log', help='Path to the logging file.')
    parser.add_argument('--dest_dir', type=str, default='figures', help='Destination directory.')
    args = parser.parse_args()

    log_file_path = args.log
    destination_directory = args.dest_dir

    setup_logger("opt", log_file_path, level=logging.INFO)
    logger = logging.getLogger('opt')
    logger.info("========================== New Log ========================================")

    if not os.path.isdir(destination_directory):
        os.makedirs(destination_directory)
        logger.info("Create directory: %s" % destination_directory)

    installed_solvers = cp.installed_solvers()
    logger.info("Installed solvers for cvxpy: " + str(installed_solvers))

    n, m, l, mu, A, b, u, x0, errfun, errfun_exact, sparsity = gen_data()

    fig = plt.figure(1)
    plt.subplot(2, 1, 1)
    plt.plot(np.arange(0, n, 1), u[:, 0], '*')
    plt.plot(np.arange(0, n, 1), u[:, 1], 'o')
    plt.xlim(0, n)
    plt.title(r'(1) exact solution $u$')
    # plt.show( )
    plt.savefig(os.path.join(destination_directory, 'ground_truth.svg'))

    cvx_mosek_rv, _, _ = gl_cvx_mosek(x0, A, b, mu, {})
    cvx_gurobi_rv, _, _ = gl_cvx_gurobi(x0, A, b, mu, {})

    solvers = {
        'CVX-Mosek': gl_cvx_mosek,
        'CVX-Gurobi': gl_cvx_gurobi,
        # 'Mosek': gl_mosek,
        # 'Gurobi': gl_gurobi,
        'SGD Primal': gl_SGD_primal,
        "GD Primal": gl_GD_primal,
        "FGD Primal": gl_FGD_primal,
        "ProxGD Primal": gl_ProxGD_primal,
        "FProxGD Primal": gl_FProxGD_primal,
        "ALM Dual": gl_Alm_dual,
        "ADMM Dual": gl_Admm_dual,
        "ADMM Primal": gl_Admm_primal,
    }

    solvers_opts = {
        'CVX-Mosek': {},
        'CVX-Gurobi': {},
        'Mosek': {},
        'Gurobi': {},
        'SGD Primal': {},
        "GD Primal": {},
        "ProxGD Primal": {},
        "FGD Primal": {},
        "FProxGD Primal": {},
        "ADMM Dual": {},
        "ADMM Primal": {},
        "ALM Dual": {},
    }

    f_hists = {}
    log_dicts = {}
    for mode, solver in solvers.items():
        _, _, out, log_dict = solve_routine(mode, solver, x0, A, b, mu, solvers_opts[mode],
                                            u, errfun, errfun_exact, sparsity)
        if 'f_hist' in out:
            f_hists[mode] = out["f_hist"]
        log_dicts[mode] = log_dict

    write_to_table(log_dicts)

    plot_solver_color = {
        'SGD Primal': 'g',
        "GD Primal": 'b',
        "ProxGD Primal": 'r',
        "FGD Primal": 'y',
        "FProxGD Primal": 'k',
        "ADMM Dual": 'm',
        "ADMM Primal": 'c',
        "ALM Dual": 'peachpuff'
    }

    plt.clf()
    file_name = os.path.join(destination_directory, 'relative_objective.svg')
    fig, ax = plt.subplots(figsize=(9, 6))
    f_star = obj_func(u)
    for mode, f_hist in f_hists.items():
        f_hist = (f_hist - f_star) / f_star
        plt.semilogy(np.arange(0, len(f_hist)), np.array(f_hist), color=plot_solver_color[mode], linewidth=2, label=mode)
    plt.legend(prop={'size': 12})
    plt.ylabel('$(f(x^k)-f^*)/f^*$')
    plt.xlabel('Iteration')
    plt.savefig(file_name)

    plt.clf()
    file_name = os.path.join(destination_directory, 'objective_function.svg')
    fig, ax = plt.subplots(figsize=(9, 6))
    f_star = obj_func(u)
    for mode, f_hist in f_hists.items():
        plt.semilogy(np.arange(0, len(f_hist)), np.array(f_hist), plot_solver_color[mode], linewidth=2, label=mode)
    plt.legend(prop={'size': 12})
    plt.ylabel('$f(x^k)$')
    plt.xlabel('Iteration')
    plt.savefig(file_name)


from numpy import *

def compute_error_for_line_given_points(b, m, points):

    # initialize it at 0
    totalerror = 0
    # for every point
    for i in range(0, len(points)):
        # get the x value
        x = points[i, 0]

        # get the y value
        y = points[i, 1]

        # get the difference, square it and add it to the total
        totalerror += (y - (m*x + b)) ** 2

    # get the average of the total error
    return totalerror / float(len(points))

def gradient_descent_runner(points, initial_b, initial_m, learning_rate, num_iterations):
    # starting b and m
    b = initial_b
    m = initial_m

    # gradient descent
    for i in range(num_iterations):
        [b, m] = step_gradient(b, m, array(points), learning_rate)
    return [b, m]

def step_gradient(b_current, m_current, points, learningrate):
    # Starting points for our gradients
    b_gradient = 0
    m_gradient = 0

    n = float(len(points))
    for i in range(0, len(points)):
        x = points[i, 0]
        y = points[i, 1]
        # direction with respect to b and m
        # computing partial derivatives with our error function
        b_gradient += -(2/n) * (y - (m_current*x + b_current))
        m_gradient += -(2/n) * x * (y - (m_current * x) + b_current)

    # update our b and m values using our partial derivatives
    new_b = b_current - (learningrate * b_gradient)
    new_m = m_current - (learningrate * m_gradient)
    return [new_b, new_m]

def run():
    # Step 1: Collect our data
    points = genfromtxt('../data/data.csv', delimiter=',')

    # Step 2: Define our hyperparameters
    # how fast our model should converge
    learning_rate = 0.0001
    # y = mx + b - Slope formula
    initial_b = 0
    initial_m = 0
    num_iterations = 1000

    # Step 3: Train our model
    print("Starting gradient descent at b = {0}, m = {1}, error = {2}".format(initial_b, initial_m,
                                                                              compute_error_for_line_given_points(
                                                                                initial_b, initial_m, points)))
    [b, m] = gradient_descent_runner(points, initial_b, initial_m, learning_rate, num_iterations)
    print("final gradient descent parameters: b = {1}, m = {2}, error = {3}".format(num_iterations, b, m,
                                                                                compute_error_for_line_given_points(
                                                                                b, m, points)))

if __name__ == '__main__':
    run()


import numpy

class MyModel:

    def __init__(self):
        '''
        Creates a new MyModel
        '''
        # Straight lines described by two parameters:
        # The slop is the angle of the line
        self.slope = 0
        # The intercept moves the line up or down
        self.intercept = 0
        # The history of cost per iteration
        self.cost_history = []

    def predict(self, x):
        '''
        Estimates the target variable from the value of x
        '''
        return x * self.slope + self.intercept

    def get_summary(self):
        '''
        Returns a string that summarises the model
        '''
        return f"y = {self.slope} * x + {self.intercept}"

def cost_function(actual, predicted):
    # use the mean squared differences
    return numpy.average((actual - predicted)**2)

def calculate_gradient(x, actual, predicted):
    """
    This calculates gradient for a linear regession
    using the Mean Squared Error cost function
    """
    # The partial derivatives of MSE are as follows
    # You don't need to be able to do this just yet but
    # it is important to note these give you the two gradients
    # that we need to train our model
    error = predicted - actual
    grad_intercept = numpy.mean(error) * 2
    grad_slope = (x * error).mean() * 2

    return grad_intercept, grad_slope

def gradient_descent(x, y, learning_rate, number_of_iterations):
    """
    Performs gradient descent for a two-parameter function.

    learning_rate: Larger numbers follow the gradient more aggressively
    number_of_iterations: The maximum number of iterations to perform
    """

    model = MyModel()
    # set the initial parameter guess to 0
    model.intercept = 0
    model.slope = 0
    model.cost_history = []

    last_cost = float('inf')

    for i in range(number_of_iterations):
        # Calculate the predicted values
        predicted = model.predict(x)

        # == OPTIMISER ===
        # Calculate the gradient
        grad_intercept, grad_slope = calculate_gradient(x, y, predicted)
        # Upx the estimation of the line
        model.slope -= learning_rate * grad_slope
        model.intercept -= learning_rate * grad_intercept

        estimate = model.predict(x)
        cost = cost_function(y, estimate)
        # Upx the history of costs
        model.cost_history.append(cost_function(y, estimate))

        # Print the current estimation and cost every 100 iterations
        if( i % 100 == 0):

            print("Iteration", i, " Current estimate:", model.get_summary(), f"Cost: {cost}")

            if (cost + 0.001) >= last_cost:
                print("Model training complete after",i, "iterations")
                break
            last_cost = cost

    if i == (number_of_iterations - 1):
        print("Maximum number of iterations reached. Stopping training")

    # # Print the final model
    # print(f"Final estimate:", model.get_summary())
    return model


""" Statistical functions and utilities. """
import numpy as np
from scipy.stats import describe
import pandas as pd
import math
from collections import namedtuple
from math import sqrt

def rss(actual, forecast):
    ''' Computes the residuals squares sum (RSS).

    .. math:: RSS = \sum_{i=1}^n (y_i - f(x_i))^2

    :param actual: The target values.
    :param forecast: Predicted values.

    :returns: The RSS.
    '''
    return ((actual - forecast) ** 2).sum()

def mse(actual, forecast):
    ''' Computes the mean squared error.

    .. math:: \operatorname{MSE}=\\frac{1}{n}\\sum_{i=1}^n(\\hat{Y_i} - Y_i)^2

    :param actual: The target values.
    :param forecast: Predicted values.

    :returns: The MSE.

    '''
    return ((actual - forecast) ** 2).mean()

def mpe(actual, forecast):
    ''' Computes the mean percentage error. \
        Non-finite values due to zero division are ignored.

    .. math:: \\text{MPE} = \\frac{100\\%}{n}\\sum_{t=1}^n \\frac{a_t-f_t}{a_t}

    :param actual: The target values.
    :param forecast: Predicted values.

    :returns: The MPE.
    '''
    ratio = (actual - forecast)/actual
    ratio = ratio[np.isfinite(ratio)]

    return 100 * ratio.mean()

def mape(actual, forecast):
    ''' Computes the mean absolute percentage error. \
        Non-finite values due to zero division are ignored.

    .. math::  \mbox{MAPE} = \\frac{1}{n}\\sum_{t=1}^n  \
        \\left|\\frac{A_t-F_t}{A_t}\\right|

    :param actual: The target values.
    :param forecast: Predicted values.

    :returns: The MAPE.
    '''
    ratio = (actual - forecast)/actual
    ratio = ratio[np.isfinite(ratio)]

    return np.abs(ratio).mean()

def wssse(point, center):
    ''' Computes the **Within Set Sum of Squared Error(WSSSE)**.

    :param point: A point for which to calculate the error.
    :param center: The center of a cluster.

    :returns: The WSSSE.
    '''
    return sqrt(sum([x**2 for x in (point - center)]))

def trimean(arr):
    """ Calculates the trimean.

    .. math:: TM=\\frac{Q_1 + 2Q_2 + Q_3}{4}

    :param arr: An array containing numbers.

    :returns: The trimean for the array.

    >>> import numpy as np
    >>> from dautils import stats
    >>> stats.trimean(np.arange(9))
    4.0

    """
    q1 = np.percentile(arr, 25)
    q2 = np.percentile(arr, 50)
    q3 = np.percentile(arr, 75)

    return (q1 + 2 * q2 + q3)/4

def outliers(arr, method='IQR', factor=1.5, percentiles=(5, 95)):
    """ Gets the limits given an array for values to be considered
    outliers.

    :param arr: An array containing numbers.
    :param method: IQR (default) or percentiles.
    :param factor: Factor for the IQR method.
    :param percentiles: A tuple of percentiles.

    :returns: A namedtuple with upper and lower limits.

    >>> import numpy as np
    >>> from dautils import stats
    >>> stats.outliers(a)
    Outlier(min=-48.5, max=149.5)
    """
    Outlier = namedtuple('Outlier', ['min', 'max'])
    outlier = None

    if method == 'IQR':
        q1 = np.percentile(arr, 25)
        q3 = np.percentile(arr, 75)
        iqr = q3 - q1
        outlier = Outlier(q1 - factor * iqr, q3 + factor * iqr)
    elif method == 'percentiles':
        minmax = [np.percentile(arr, p) for p in percentiles]
        outlier = Outlier(minmax[0], minmax[1])
    else:
        raise AssertionError('Unknown method expected IQR or percentiles')

    return outlier

def clip_outliers(arr):
    """ Clips values using limits for values
    to be considered outliers.

    :param arr: An array containing numbers.

    :returns: A clipped values array.

    >>> import numpy as np
    >>> from dautils import stats
    >>> arr = list(range(5))
    >>> outliers = [-100, 100]
    >>> arr.extend(outliers)
    >>> arr
    [0, 1, 2, 3, 4, -100, 100]
    >>> stats.clip_outliers(arr)
    array([ 0.,  1.,  2.,  3.,  4., -4.,  8.])
    """
    amin, amax = outliers(arr)

    return np.clip(arr, amin, amax)

def zscores(x):
    return (x - x.mean())/x.std()

def sqrt_bins(arr):
    """ Uses a rule of thumb to calculate
    the appropriate number of bins for an array.

    :param arr: An array containing numbers.

    :returns: An integer to serve as the number of bins.
    """
    return int(math.sqrt(len(arr)))

def ci(arr, alpha=0.95):
    """ Computes the confidence interval.

    :param arr: An array containing numbers.
    :param alpha: A value in the range 0 - 1\
    that serves as percentiles.

    :returns: The confidence interval.
    """
    interval = 1 - alpha

    minmax = [interval/2, alpha + interval/2]

    return np.percentile(arr, 100 * np.array(minmax))

def jackknife(arr, func, alpha=0.95):
    """ Jackknifes an array with a supplied function.

    :param arr: An array containing numbers.
    :param func: The function to apply.
    :param alpha: A number in the range 0 - 1 used\
    to calculate the confidence interval.

    :returns: Three numbers - the function value for arr,\
    the lower limit of the confidence interval and the\
    upper limit of the confidence interval.
    """
    n = len(arr)
    idx = np.arange(n)

    low, high = ci([func(arr[idx != i]) for i in range(n)], alpha)

    return func(arr), low, high

class Distribution():
    """ Wraps `scipy.stats` distribution classes.
    Most of the methods are only appropriate for continuous distributions.

    :ivar nbins: The number of bins for a histogram plot.
    :ivar train: The train data.
    :ivar test: The test data.
    """
    def __init__(self, data, dist, nbins=20, cutoff=0.75, range=None):
        self.nbins = nbins
        self.train, self.test = self.split(data, cutoff)
        self.hist_values, edges = np.histogram(self.test, bins=self.nbins,
                                               range=range, density=True)
        self.x = 0.5 * (edges[1:] + edges[:-1])
        self.dist = dist
        self.residuals = None
        self.pdf_values = None

    def describe_residuals(self, *args, **kwds):
        """ Describes the residuals of a fit to a distribution.
        Only appropriate for continuous distributions.

        :returns: Statistics for the residuals as a dict.
        """
        _, _, mean, var, skew, kurtosis = describe(self.error(*args, **kwds))
        result = {}
        result['Mean'] = mean
        result['Var'] = var
        result['Skew'] = skew
        result['Kurtosis'] = kurtosis
        result = pd.DataFrame([result])
        result.index = ['Residuals Statistics']

        return result

    def error(self, *args, **kwds):
        """ Computes the residuals of a fit to a distribution.
        Only appropriate for continuous distributions.

        :returns: The residuals of the fit.
        """
        if self.residuals is None:
            self.residuals = self.hist_values - self.pdf(*args, **kwds)

        return self.residuals

    def fit(self, *args):
        """ Fits data to a distribution.
        Only appropriate for continuous distributions.

        :returns: The result of the fit.
        """
        return self.dist.fit(self.train, *args)

    def mean(self):
        return self.train.mean()

    def mean_ad(self, *args, **kwds):
        """ Computes the mean absolute deviation.

        :returns: The mean absolute deviation.
        """
        abserror = np.abs(self.error(*args, **kwds))

        return abserror.mean()

    def pdf(self, *args, **kwds):
        """ Computes the probability distribution function.
        Only appropriate for continuous distributions.

        :returns: The probability distribution function.
        """
        if self.pdf_values is None:
            self.pdf_values = self.dist.pdf(self.x, *args, **kwds)

        return self.pdf_values

    def plot(self, ax):
        """ Plots a histogram of the data and a fit.
        Only appropriate for continuous distributions.
        """
        ax.hist(self.train, bins=self.nbins, normed=True, label='Data')
        ax.plot(self.x, self.pdf_values, label='PDF')
        ax.set_ylabel('Probability')
        ax.legend(loc='best')

    def rmse(self, *args, **kwds):
        """ Computes the root mean square error of the distribution fit.
        Only appropriate for continuous distributions.

        :returns: The RMSE of the fit.
        """
        se = self.error(*args, **kwds) ** 2

        return np.sqrt(se.mean())

    def rvs(self, *args, **kwds):
        """ Generates random values.

        :returns: The generated values.
        """
        return self.dist.rvs(*args, **kwds)

    def split(self, data, cutoff):
        """ Splits data into test and train data.

        :param data: An array containing numbers.
        :param cutoff: A value in the range 0 - 1 used\
        to split the data.

        :returns: Two arrays - the train data and the test data.
        """
        n = len(data)
        n = int(cutoff * n)

        return data[:n], data[n:]

    def var(self):
        return self.train.var()

# TODO Decide on interpolation
class Box():
    ''' Implements the concept of a box we know from box plots. '''
    def __init__(self, arr):
        self.arr = arr
        self.q1 = np.percentile(arr, 25, interpolation='nearest')
        self.q3 = np.percentile(arr, 75, interpolation='nearest')

    def calc_iqr(self):
        """ Calculates the Inter Quartile Range.

        :returns: The interquartile range of the data.

        >>> import numpy as np
        >>> from dautils import stats
        >>> arr = np.array([7, 15, 36, 39, 40, 41])
        >>> box = stats.Box(arr)
        >>> box.calc_iqr()
        25
        """
        return self.q3 - self.q1

    def iqr_from_box(self):
        """ Calculates the number (0 or more) of
        inter quartile ranges (IQR) from the box formed
        by the first and third quartile.

        :returns: A float representing the number of IQRs
        """
        iqr = self.calc_iqr()
        from_q1 = np.abs(self.arr - self.q1)/iqr
        from_q3 = np.abs(self.arr - self.q3)/iqr

        return np.minimum(from_q1, from_q3)


from numpy import zeros

# Define rk4_set function
def rk4_set(f,t0,tf,y0,v0,n):
    h = (tf - t0)/n
    t = zeros(n+1)
    y = zeros(n+1)
    v = zeros(n+1)
    t[0] = t0
    y[0] = y0
    v[0] = v0
    for i in range(n):
        M0 = h * f(t[i],y[i],v[i])
        M1 = h * f(t[i] + h/2.0, y[i] + h/2.0 * v[i], v[i] +  M0/2.0)
        M2 = h * f(t[i] + h/2.0, y[i] + h/2.0 * v[i] + h/4.0 * M0, v[i] + M1/2.0)
        t[i+1] = t[i] + h
        M3 = h * f(t[i+1], y[i] + h * v[i] + h/2.0 * M1, v[i] + M2)
        y[i+1] = y[i] + h * v[i] + h/6.0 * (M0 + M1 + M2)
        v[i+1] = v[i] + (M0 + 2.0 * M1 + 2.0 * M2 + M3)/6.0
    return t,y,v

# Define fuctions
def f(t,y,v):
    return (t**2 - 1.0) * y

# Set initial conditions
t0 = 0
tf = 1
y0 = 1.0
v0 = 0.0
n = 10

# Execute RK4_SET
t, y, v = rk4_set(f,t0,tf,y0,v0,n)

# Print results
print('%5s %8s %8s' % ('t','y','v'))
for i in range(n+1):
    print("%9.4f %9.4f %9.4f" % (t[i],y[i],v[i]))


import numpy

def get_seasonal_forcing(
        period: float, shift: float, prop_amplitude: float, average_value: float
):
    """
    Factory function to get a trigonometric/sinusoidal function (using cosine) to represent seasonal forcing of
    transmission in a model.
    Note that the time unit is not specified (as elsewhere in the repository), so the period is not assumed.

    :param period: float
        Time to complete an entire cycle of forcing
    :param shift: float
        Time at which the peak value will be reached
    :param prop_amplitude: float
        Amplitude of the forcing function relative to the average value
        Note that the amplitude is HALF of the total variation in the function
    :param average_value: float
        Average value of the function, mid-way between peak and trough values
    :return:
        The seasonal forcing function
    """

    amplitude = prop_amplitude * average_value

    def seasonal_forcing(time):
        return \
            numpy.cos(
                (time - shift) * 2. * numpy.pi / period
            ) * \
            amplitude + \
            average_value

    return seasonal_forcing

if __name__ == "__main__":
    period, shift, amplitude, average = 365., 173., 0.5, 0.04
    x_values = numpy.linspace(0., period, 20)
    forcing_function = get_seasonal_forcing(period, shift, amplitude, average)
    for i in x_values:
        print(f"date: {i}, \t value: {forcing_function(i)}")


import numpy as np
from collections import Counter
import itertools
from visual import show_tfidf

docs = [
    "it is a good day, I like to stay here",
    "I am happy to be here",
    "I am bob",
    "it is sunny today",
    "I have a party today",
    "it is a dog and that is a cat",
    "there are dog and cat on the tree",
    "I study hard this morning",
    "today is a good day",
    "tomorrow will be a good day",
    "I like coffee, I like book and I like apple",
    "I do not like it",
    "I am kitty, I like bob",
    "I do not care who like bob, but I like kitty",
    "It is coffee time, bring your cup",
]

docs_words = [d.replace(",", "").split(" ") for d in docs]
vocab = set(itertools.chain(*docs_words))
v2i = {v: i for i, v in enumerate(vocab)}
i2v = {i: v for v, i in v2i.items()}

def safe_log(x):
    mask = x != 0
    x[mask] = np.log(x[mask])
    return x

tf_methods = {
        "log": lambda x: np.log(1+x),
        "augmented": lambda x: 0.5 + 0.5 * x / np.max(x, axis=1, keepdims=True),
        "boolean": lambda x: np.minimum(x, 1),
        "log_avg": lambda x: (1 + safe_log(x)) / (1 + safe_log(np.mean(x, axis=1, keepdims=True))),
    }
idf_methods = {
        "log": lambda x: 1 + np.log(len(docs) / (x+1)),
        "prob": lambda x: np.maximum(0, np.log((len(docs) - x) / (x+1))),
        "len_norm": lambda x: x / (np.sum(np.square(x))+1),
    }

def get_tf(method="log"):
    # term frequency: how frequent a word appears in a doc
    _tf = np.zeros((len(vocab), len(docs)), dtype=np.float64)    # [n_vocab, n_doc]
    for i, d in enumerate(docs_words):
        counter = Counter(d)
        for v in counter.keys():
            _tf[v2i[v], i] = counter[v] / counter.most_common(1)[0][1]

    weighted_tf = tf_methods.get(method, None)
    if weighted_tf is None:
        raise ValueError
    return weighted_tf(_tf)

def get_idf(method="log"):
    # inverse document frequency: low idf for a word appears in more docs, mean less important
    df = np.zeros((len(i2v), 1))
    for i in range(len(i2v)):
        d_count = 0
        for d in docs_words:
            d_count += 1 if i2v[i] in d else 0
        df[i, 0] = d_count

    idf_fn = idf_methods.get(method, None)
    if idf_fn is None:
        raise ValueError
    return idf_fn(df)

def cosine_similarity(q, _tf_idf):
    unit_q = q / np.sqrt(np.sum(np.square(q), axis=0, keepdims=True))
    unit_ds = _tf_idf / np.sqrt(np.sum(np.square(_tf_idf), axis=0, keepdims=True))
    similarity = unit_ds.T.dot(unit_q).ravel()
    return similarity

def docs_score(q, len_norm=False):
    q_words = q.replace(",", "").split(" ")

    # add unknown words
    unknown_v = 0
    for v in set(q_words):
        if v not in v2i:
            v2i[v] = len(v2i)
            i2v[len(v2i)-1] = v
            unknown_v += 1
    if unknown_v > 0:
        _idf = np.concatenate((idf, np.zeros((unknown_v, 1), dtype=np.float)), axis=0)
        _tf_idf = np.concatenate((tf_idf, np.zeros((unknown_v, tf_idf.shape[1]), dtype=np.float)), axis=0)
    else:
        _idf, _tf_idf = idf, tf_idf
    counter = Counter(q_words)
    q_tf = np.zeros((len(_idf), 1), dtype=np.float)     # [n_vocab, 1]
    for v in counter.keys():
        q_tf[v2i[v], 0] = counter[v]

    q_vec = q_tf * _idf            # [n_vocab, 1]

    q_scores = cosine_similarity(q_vec, _tf_idf)
    if len_norm:
        len_docs = [len(d) for d in docs_words]
        q_scores = q_scores / np.array(len_docs)
    return q_scores

def get_keywords(n=2):
    for c in range(3):
        col = tf_idf[:, c]
        idx = np.argsort(col)[-n:]
        print("doc{}, top{} keywords {}".format(c, n, [i2v[i] for i in idx]))

tf = get_tf()           # [n_vocab, n_doc]
idf = get_idf()         # [n_vocab, 1]
tf_idf = tf * idf       # [n_vocab, n_doc]
print("tf shape(vecb in each docs): ", tf.shape)
print("\ntf samples:\n", tf[:2])
print("\nidf shape(vecb in all docs): ", idf.shape)
print("\nidf samples:\n", idf[:2])
print("\ntf_idf shape: ", tf_idf.shape)
print("\ntf_idf sample:\n", tf_idf[:2])

# test
get_keywords()
q = "I get a coffee cup"
scores = docs_score(q)
d_ids = scores.argsort()[-3:][::-1]
print("\ntop 3 docs for '{}':\n{}".format(q, [docs[i] for i in d_ids]))

show_tfidf(tf_idf.T, [i2v[i] for i in range(len(i2v))], "tfidf_matrix")


import numpy as np

x = np.array([-1, 1, 2.569858113, 6])  # x coordinates in space
y = np.array([1, 3, 1, -2])  # f(x)

print("Column  0 ")
print(y)

n = len(y)

table = np.zeros([n, n])  # Create a square matrix to hold table
table[::, 0] = y  # first column is y

results = {"table": [], "coefficient": []}

def newtonInterpolation(x, y):
    """ Creates Newton table and extracts coefficients """
    for j in range(1, n):
        results["table"].append([])
        for i in range(n - j):
            # create table by updating other columns
            table[i][j] = (table[i + 1][j - 1] - table[i][j - 1]) / (x[i + j] - x[i])
            results["table"][len(results["table"]) - 1].append(table[i][j])

    coeff = table[0]  # return first row
    for c in coeff:
        results["coefficient"].append(c)

    index = 1
    for i in results["table"]:
        print("Column ", index)
        print(i)
        index += 1

    return table[0]

coeff_vector = newtonInterpolation(x, y)

print("The newton polynom is: ")
for i in range(n):
    print(coeff_vector[i], end=" ")
    for j in range(i):
        print("( x -", x[j], ")", end=" ")
    if (i != n - 1):
        print("+", end=" ")
print()


import math
import numpy as np
import scipy.io

# Import wordVecV.mat matrix
V = scipy.io.loadmat('wordVecV.mat')["V"]

# Get the “raw” term-by-document matrix
M = np.zeros((1651, 10))
for i in range(1651):
    for j in range(10):
        if V[i][j] > 0:
            M[i][j] = 1

# Get normalized M matrix
M_tilda = np.zeros((1651, 10))
norms_of_columns = np.zeros(10)

for j in range(10):
    squared_column_sum = 0
    for i in range(1651):
        squared_column_sum += pow(M[i][j], 2)
    norms_of_columns[j] = np.sqrt(squared_column_sum)

for i in range(1651):
    for j in range(10):
        M_tilda[i][j] = M[i][j]/norms_of_columns[j]

# Singular value decomposition of M_tilda
U, S, V_transpose = np.linalg.svd(M_tilda)

# Function that returns projection of vector onto subspace of u(k)
def projection_onto_u(vector, k):
    projection = np.zeros((1651, 1))
    for i in range(k):
        # Add projection of vector onto kth vector in subspace
        alpha = (np.inner(vector, U[:, i])/np.inner(U[:, i], U[:, i]))
        u_vector = U[:, i]
        # Add projection component
        for j in range(1651):
            projection[j] = projection[j] + (alpha * u_vector)[j]
    return projection

# Function that returns cosine similarity between two vectors
def cosine_similarity(d, q):
    # Term A is numerator, term B is denominator part, term C is other denominator part
    A = B = C = 0
    for i in range(1651):
        A += d[i] * q[i]
        B += d[i] * d[i]
        C += q[i] * q[i]
    similarity = A / (np.sqrt(B) * np.sqrt(C))
    return float(similarity)

# For every pair of d's(matrix columns of M_tilda) find the cosine similarity. Repeat for decreasing k
cosine_similarities = np.zeros((45, 9))

for k in range(9,0,-1):
    row = 0
    for i in range(10):
        for j in range(i + 1, 10):
            # Project document vectors onto subspace spanned by u(k)
            d_projection = projection_onto_u(M_tilda[:, i], k)
            q_projection = projection_onto_u(M_tilda[:, j], k)

            cosine_similarities[row][9 - k] = (cosine_similarity(d_projection, q_projection))
            row += 1

print("The most similar documents are the last two, Barack Obama and George W. Bush")
print("The lowest k can be without changing the answer is k = 3")


import sys

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

from scipy.misc import imread

def get_alignment(src_points, tgt_points):
    """Computes similarity matrix between src_points and tgt_points.

    Returns:
        a 2x3 numpy array, similarity matrix.
    """
    N = len(src_points)

    # We would like to solve: Ax=b
    ''' Build A and b '''
    A = np.empty((2*N, 4))
    b = np.empty((2*N, 1))
    i = 0
    for x, y in src_points:
        A[i,:]  = np.array([x, -y, 1, 0])
        A[i+1,:]= np.array([y, x, 0, 1])
        i += 2

    i = 0
    for x, y in tgt_points:
        b[i,:]  = np.array([x])
        b[i+1,:]  = np.array([y])
        i += 2

    ''' Inverse A and solve for x '''
    A_inv = np.linalg.pinv(A)
    x = np.matmul(A_inv, b)

    ''' Create Similarity Matrix from x '''
    '''         | a_1   -a_2   a_3 | '''
    ''' s_mat = | a_2    a_1   a_4 | '''
    '''         |  0      0     1  | '''
    #print('----------------')
    a_1 = x[0][0]
    a_2 = x[1][0]
    a_3 = x[2][0]
    a_4 = x[3][0]
    s_mat = np.array([[a_1, -a_2, a_3], [a_2, a_1, a_4], [0, 0, 1]])
    return s_mat

def test_alignment():
    """Tests our alignment matrix with 90 deg counter-clockwise rotation."""
    lena = imread('lena.png')

    #
    # Obtain mapping.
    #

    # Square
    src_points_x = [100, 100, 400, 400, 450]
    src_points_y = [100, 400, 100, 400, 450]
    tgt_points_x = [150, 150, 350, 350, 300]
    tgt_points_y = [150, 350, 150, 350, 300]

    src_points = zip(src_points_x, src_points_y)
    tgt_points = zip(tgt_points_x, tgt_points_y)
    s_mat = get_alignment(list(src_points), list(tgt_points))

    #
    # Apply on a test point
    #
    x, y = 400, 100
    src_point = np.array([x, y, 1])
    tgt_point = s_mat.dot(src_point)
    tgt_point_x = tgt_point[0]
    tgt_point_y = tgt_point[1]
    print('**** tgt_point ****')
    print(tgt_point)

    implot = plt.imshow(lena)
    plt.scatter(src_points_x, src_points_y, c='r', s=20)
    plt.scatter(tgt_points_x, tgt_points_y, c='b', s=20)
    plt.scatter(tgt_point_x, tgt_point_y, c='g', s=20)
    plt.show()

def test_rotate():
    # Obtain mapping.
    src_points = np.array([[100, 100], [400, 100], [400, 400], [100, 400]])
    dst_points = np.roll(src_points, 1, axis=0)
    s_mat = get_alignment(src_points, dst_points)
    M = s_mat[:-1]

    # Apply mapping.
    #lena = imread('lena.png')
    #implot = plt.imshow(lena)
    #plt.show()
    img = cv2.imread('lena.png')
    rows,cols,ch = img.shape
    #M = cv2.getAffineTransform(pts1,pts2)

    dst = cv2.warpAffine(img,M,(cols,rows))
    plt.subplot(121),plt.imshow(img),plt.title('Input')
    plt.subplot(122),plt.imshow(dst),plt.title('Output')
    plt.show()

def test_identity():
    # Obtain mapping.
    src_points = np.array([[100, 100], [400, 100], [400, 400], [100, 400]])
    dst_points = src_points
    s_mat = get_alignment(src_points, dst_points)
    M = s_mat[:-1]

    # Apply mapping.
    #lena = imread('lena.png')
    #implot = plt.imshow(lena)
    #plt.show()
    img = cv2.imread('lena.png')
    rows,cols,ch = img.shape
    #M = cv2.getAffineTransform(pts1,pts2)

    dst = cv2.warpAffine(img,M,(cols,rows))
    plt.subplot(121),plt.imshow(img),plt.title('Input')
    plt.subplot(122),plt.imshow(dst),plt.title('Output')
    plt.show()

def main():
    #test_alignment()
    #test_rotate()
    test_identity()

if __name__ == '__main__':
    sys.exit(main())


"""Softmax."""

import numpy as np

scores = np.array([[1, 2, 3, 6],
                   [2, 4, 5, 6],
                   [3, 8, 7, 6]])

def softmax(x):
    """Compute softmax values for each sets of scores in x."""
    return np.exp(x)/np.sum(np.exp(x),axis=0)

print('Softmax matrix for scores: {}'.format(softmax(scores)))
print('Softmax matrix for scores*10: {}'.format(softmax(scores*10)))
print('Softmax matrix for scores/10: {}'.format(softmax(scores/10)))

# Plot softmax curves
import matplotlib.pyplot as plt
x = np.arange(-2.0, 6.0, 0.1)
scores = np.vstack([x, np.ones_like(x), 0.2 * np.ones_like(x)])

plt.plot(x, softmax(scores).T, linewidth=2)
plt.show()


# -- Imports --------------------------------------------------------------------------

from typing import (
    List
)
from sympy import simplify, symbols, sqrt

# -------------------------------------------------------------------------- Imports --

# -- Functions --------------------------------------------------------------------------

def fibonacci_loop(n: int) -> int:
    a, b = 0, 1
    if n == 1:
        return a
    elif n == 2:
        return b
    else:
        for _ in range(n - 2):
            a, b = b, a + b
        return b

def fibonacci_sym(n: int) -> int:
    x = symbols('x', nonnegative=True, integer=True)
    fib = 1 / sqrt(5) * (((1+sqrt(5))/2)**(n-1) - ((1-sqrt(5))/2)**(n-1))
    result = fib.subs(x, n)
    result = simplify(result)
    return result

def fibonacci_recursion(n: int) -> int:
    if n == 1:
        return 0
    elif n == 2:
        return 1
    else:
        return fibonacci_recursion(n-1) + fibonacci_recursion(n-2)

def fibonacci_list_loop(n: int) -> List[int]:
    return [fibonacci_loop(i) for i in range(1, n)]

def fibonacci_list_sym(n: int) -> List[int]:
    return [fibonacci_sym(i) for i in range(1, n)]

def fibonacci_list_recursion(n: int) -> List[int]:
    return [fibonacci_recursion(i) for i in range(1, n)]

# -------------------------------------------------------------------------- Functions --


import numpy as np
import pandas as pd
from scipy import stats
from scipy.fft import dct
import matplotlib.pyplot as plt
from sklearn.mixture import GaussianMixture

# Help taken from these websites below on how to apply the DCT
# https://docs.scipy.org/doc/scipy/reference/generated/scipy.fft.dct.html
# https://stackoverflow.com/questions/40104377/issiue-with-implementation-of-2d-discrete-cosine-transform-in-python
# including some parts of the algorithm from the matlab
# numericalFeatureGeneratorExample.m file provided in the course material

def featureGenerator(trainMatrix, n):
    features = np.zeros((n,13))

    i = 0
    while i < n:
        img = np.reshape(trainMatrix[i,1:len(trainMatrix[0])], (28,28))
        imgDCT = dct((dct(img, norm='ortho')).T, norm='ortho')
        diagDCT = np.diag(imgDCT) #extract diagonal
        rowDCT = imgDCT[0,:] # extract first row
        colDCT = imgDCT[:,0] #extract first column
        features[i,:]= [np.mean(diagDCT), np.std(diagDCT,ddof=1), stats.skew(diagDCT), stats.kurtosis(diagDCT,fisher=False),
            np.mean(rowDCT), np.std(rowDCT,ddof=1), stats.skew(rowDCT), stats.kurtosis(rowDCT,fisher=False),
            np.mean(colDCT), np.std(colDCT,ddof=1), stats.skew(colDCT), stats.kurtosis(colDCT,fisher=False),
            int(trainMatrix[i][0])]
        i+=1
    return features

def plot(trainMatrix, index):
    sub = 1
    i=0
    plt.figure()
    while sub <= len(index):
        plt.subplot(3,4,sub)
        img = np.reshape(trainMatrix[index[i],1:len(trainMatrix[0])], (28,28))
        plt.imshow(img, cmap='gray')
        sub+=1
        i+=1

    plt.show()

    return

if __name__ == '__main__':
    train = pd.read_csv("train.csv",header=0)
    trainMatrix = train.to_numpy()

    test = featureGenerator(trainMatrix,100)
    np.savetxt("test.csv", test, delimiter=",", fmt='%5.4f')

    x = [1, 2, 4, 7, 8, 9, 11, 12, 17, 22]
    plot(trainMatrix, x)

Algorithms for Data Science/Homework 2/Homework2_Q3_Q4.py


import numpy as np
from matplotlib import pyplot as plt
from sklearn.datasets import load_iris

def pca_use_org(data):
    cov_matrix = np.cov(data, rowvar=False)
    l, v = np.linalg.eig(cov_matrix)
    l_index = np.argsort(l)[::-1]
    l_ = l[l_index]
    v_ = v[:, l_index]
    data_trans = np.dot(data, v_)
    return data_trans, v_

if __name__ == "__main__":
    d_index = [0, 2]
    iris = load_iris()
    data = iris.data
    data = data[:, d_index]

    print('data=', data)
    data -= data.mean(axis=0)
    print('data=', data)

    data_trans, v = pca_use_org(data)

    vec_s = [0, 0]
    vec_1st_e = [2 * v[0, 0], 2 * v[0, 1]]
    vec_2nd_e = [2 * v[1, 0], 2 * v[1, 1]]

    plt.figure(figsize=[8, 8])
    plt.xlim(-4, 4)
    plt.ylim(-4, 4)
    plt.quiver(vec_s[0], vec_s[1], vec_1st_e[0], vec_1st_e[1],
               angles='xy', scale_units='xy', scale=1, color='r', label='1st')
    plt.quiver(vec_s[0], vec_s[1], vec_2nd_e[0], vec_2nd_e[1],
               angles='xy', scale_units='xy', scale=1, color='b', label='2nd')
    plt.grid()
    plt.legend()
    plt.scatter(data[:, 0], data[:, 1], c=iris.target)
    # plt.savefig('charts/fig-3.png')

    plt.figure(figsize=[8, 8])
    plt.subplot2grid((4, 1), (0, 0), rowspan=2)
    plt.title('1st principal - 2nd principal')
    plt.grid()
    plt.scatter(data_trans[:, 0], data_trans[:, 1], c=iris.target)
    plt.subplot2grid((4, 1), (2, 0))
    plt.tick_params(labelleft="off", left="off")
    plt.title('1st principal')
    plt.grid()
    plt.scatter(data_trans[:, 0], np.zeros(len(data_trans[:, 0])), c=iris.target)
    plt.subplot2grid((4, 1), (3, 0))
    plt.title('2nd principal')
    plt.grid()
    plt.tick_params(labelleft="off", left="off")
    plt.scatter(data_trans[:, 1], np.zeros(len(data_trans[:, 1])), c=iris.target)
    plt.tight_layout()
    plt.show()


# Gaussian (a.k.a. normal) distribution
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import norm

# Probability Density Function (PDF)
r = np.random.randn(1000)
p = norm.pdf(r)
plt.scatter(r,p)
plt.title("Probability Density Function (PDF)")
plt.xlabel("r")
plt.ylabel("p")
plt.show()

# Log Probability Density Function (PDF)
r = np.random.randn(1000)
p = norm.logpdf(r)
plt.scatter(r,p)
plt.title("Log Probability Density Function (PDF)")
plt.xlabel("r")
plt.ylabel("p")
plt.show()

# Cumulative Probability Density Function (PDF)
r = np.random.randn(1000)
p = norm.cdf(r)
plt.scatter(r,p)
plt.title("Cumulative Probability Density Function (PDF)")
plt.xlabel("r")
plt.ylabel("p")
plt.show()

# Log Cumulative Probability Density Function (PDF)
r = np.random.randn(1000)
p = norm.logcdf(r)
plt.scatter(r,p)
plt.title("Log Cumulative Probability Density Function (PDF)")
plt.xlabel("r")
plt.ylabel("p")
plt.show()

# Sampling from Gaussian Distribution (1D)
# Standard Deviation=1, Mean=0
r = np.random.randn(1000)
plt.hist(r, bins=100)
plt.title("Sampling from Gaussian Distribution (1D) - Standard Deviation=1, Mean=0")
plt.xlabel("r")
plt.ylabel("frequency")
plt.show()

# Sampling from Gaussian Distribution (1D)
# Standard Deviation=10, Mean=5
r= 10*np.random.randn(1000) + 5
plt.hist(r, bins=100)
plt.title("Sampling from Gaussian Distribution (1D) - Standard Deviation=10, Mean=5")
plt.xlabel("r")
plt.ylabel("frequency")
plt.show()

# Sample from Gaussian Distribution (2D)
# Standard Deviation=1, Mean=0
r = np.random.randn(1000, 2)
plt.scatter(r[:, 0], r[:, 1])
plt.title("Sampling from Gaussian Distribution (2D)")
plt.xlabel("r[:, 0]")
plt.ylabel("r[:. 1]")
plt.show()


from pandas import read_csv
import numpy as np
from matplotlib import pyplot
from scipy.optimize import curve_fit
url='https://raw.githubusercontent.com/jbrownlee/Datasets/master/longley.csv'
dataframe= read_csv(url,header=None)
data=dataframe.values
xdata,ydata=data[:,4],data[:,-1]

def objective(x, a, b, c, d, e, f, g, k):
	return (a * x) + (b * x**2) + (c * x**3) + (d * x**4) + (e * x**5) + (f * x**6) + (g * x**7) + k

def objective2(x, a, k):
	return (a * x) + k

def objective3(x, a,b,c,k):
	return (a * x) + (b * x**2) + (c * x**3) +  k

def objective4(x, a,b,c,d,e,k):
	return (a * x) + (b * x**2) + (c * x**3) + (d * x**4) + (e * x**5) + k

popt,_ =curve_fit(objective,xdata,ydata,method='dogbox')

popt2,_ =curve_fit(objective2,xdata,ydata,method='dogbox')

popt3,_ =curve_fit(objective3,xdata,ydata,method='dogbox')

popt4,_ =curve_fit(objective4,xdata,ydata,method='dogbox')

pyplot.subplot(2,2,1)
pyplot.scatter(xdata, ydata)
x_line = np.arange(min(xdata), max(xdata), 1)
y_line = objective(x_line, *popt)
pyplot.plot(x_line, y_line, '--', color='red')
pyplot.title('7th order polynomial linear regression')
pyplot.xlabel("Population size ")
pyplot.ylabel("Employed people ")

pyplot.subplot(2,2,2)
pyplot.scatter(xdata, ydata)
x_line3 = np.arange(min(xdata), max(xdata), 1)
y_line3 = objective4(x_line3, *popt4)
pyplot.plot(x_line3, y_line3, '--', color='red')
pyplot.title('5th order polynomial linear regression')
pyplot.xlabel("Population size ")
pyplot.ylabel("Employed people ")

pyplot.subplot(2,2,3)
pyplot.scatter(xdata, ydata)
x_line2 = np.arange(min(xdata), max(xdata), 1)
y_line2 = objective3(x_line2, *popt3)
pyplot.plot(x_line2, y_line2, '--', color='red')
pyplot.title('3rd order polynomial linear regression')
pyplot.xlabel("Population size ")
pyplot.ylabel("Employed people ")

pyplot.subplot(2,2,4)
pyplot.scatter(xdata, ydata)
x_line1 = np.arange(min(xdata), max(xdata), 1)
y_line1 = objective2(x_line1, *popt2)
pyplot.plot(x_line1, y_line1, '--', color='red')
pyplot.title('1st order polynomial linear regression')
pyplot.xlabel("Population size ")
pyplot.ylabel("Employed people ")
pyplot.show()


"""Capabilities for sampling of random input parameters.

This module contains functions to sample random input parameters.

"""
import numpy as np

def cond_mvn(
    mean,
    cov,
    dependent_ind,
    given_ind=None,
    given_value=None,
    check_cov=True,
):
    r"""Conditional mean and variance function.

    This function provides the conditional mean and variance-covariance matrix of
    [:math:`Y` given :math:`X`],
    where :math:`Z = (X,Y)` is the fully-joint multivariate normal distribution with
    mean equal to ``mean`` and covariance matrix ``cov``.

    This is a translation of the main function of R package condMVNorm_.

    .. _condMVNorm: https://cran.r-project.org/package=condMVNorm

    Parameters
    ----------
    mean : array_like
           The mean vector of the multivariate normal distribution.

    cov : array_like
            Symmetric and positive-definite covariance matrix of
            the multivariate normal distribution.

    dependent_ind : int or array_like
                    The indices of dependent variables.

    given_ind : array_like, optional
                The indices of independent variables (default value is `None`).
                If not specified return unconditional values.

    given_value : array_like, optional
              The conditioning values (default value is `None`). Should be the same length as
              ``given_ind``, otherwise throw an error.

    check_cov : bool, optional
                  Check that ``cov`` is symmetric, and all eigenvalue is positive
                  (default value is `True`).

    Returns
    -------
    cond_mean : numpy.ndarray
                The conditional mean of dependent variables.

    cond_cov : numpy.ndarray
               The conditional covariance matrix of dependent variables.

    Examples
    --------
    >>> mean = np.array([1, 1, 1])
    >>> cov = np.array([[4.0677098, -0.9620331, 0.9897267],
    ...                   [-0.9620331, 2.2775449, 0.7475968],
    ...                   [0.9897267, 0.7475968, 0.7336631]])
    >>> dependent_ind = [0, ]
    >>> given_ind = [1, 2]
    >>> given_value = [1, -1]
    >>> cond_mean, cond_cov = cond_mvn(mean, cov, dependent_ind, given_ind, given_value)
    >>> np.testing.assert_almost_equal(cond_mean, -4.347531, decimal=6)
    >>> np.testing.assert_almost_equal(cond_cov, 0.170718, decimal=6)
    """
    dependent_ind_np = np.array(dependent_ind)
    mean_np = np.array(mean)
    cov_np = np.array(cov)
    given_ind_np = np.array(given_ind, ndmin=1)
    given_value_np = np.array(given_value, ndmin=1)

    # Check `cov` is symmetric and positive-definite:
    if check_cov:
        if not np.allclose(cov_np, cov_np.T):
            raise ValueError("cov is not a symmetric matrix")
        elif np.all(np.linalg.eigvals(cov_np) > 0) == 0:
            raise ValueError("cov is not positive-definite")

    # When `given_ind` is None, return mean and variances of dependent values:
    if given_ind is None:
        cond_mean = np.array(mean_np[dependent_ind_np])
        cond_cov = np.array(cov_np[dependent_ind_np, :][:, dependent_ind_np])
        return cond_mean, cond_cov

    # Make sure that `given_value` aligns with `given_len`. This includes the case that
    # `given_value` is empty.
    if len(given_value_np) != len(given_ind_np):
        raise ValueError("lengths of given_value and given_ind must be the same")

    b = cov_np[dependent_ind_np, :][:, dependent_ind_np]
    c = cov_np[dependent_ind_np, :][:, given_ind]
    d = cov_np[given_ind, :][:, given_ind]
    c_dinv = c @ np.linalg.inv(d)

    cond_mean = mean_np[dependent_ind_np] + c_dinv @ (given_value - mean_np[given_ind])
    cond_cov = b - c_dinv @ c.T

    return cond_mean, cond_cov


from mandelbrotset.monte import *
import scipy.stats as st
import math

def conf_int(mean, var, n, p=0.95):
    pnew = (p+1)/2
    zval = st.norm.ppf(pnew)
    sigma = math.sqrt(var)
    alambda = (zval*sigma)/math.sqrt(n)
    min_lambda = mean - alambda
    plus_lambda = mean + alambda
    return f"Confidence interval: [{min_lambda:.4f} < X < {plus_lambda:.4f}] with p = {p}"

### Control Variets ###

## Simulations
z = 0.0j
step = 0.01
xmax= 0.5
xmin= -2.0
ymax= 1.1j
ymin= -1.1j

# amount of samples has to be a int if square root!
# in orther for orthogonal sampling to work
samples = 1024
simulations = 50
p_value = 0.95
maxiter = 200

EX = mandelbrot(xmax, xmin, ymax, ymin, maxiter) # gives 1.5139

# Simulations
rs_samples = []
lhs_samples = []
orth_samples = []
for n in range(1, simulations+1):
    r_sampling = random_sampling(xmax, xmin, ymax, ymin, maxiter, samples)
    lhs_sim = LHS(xmax, xmin, ymax, ymin, maxiter, samples, plot=False)
    orth_sim = orthogonal_sampling(xmax, xmin, ymax, ymin, maxiter, samples, plot=False)
    print(f"Simulation {n}, s={samples}, i={maxiter}, RS: {r_sampling}, LHS: {lhs_sim}, Orthogonal: {orth_sim}")
    rs_samples.append(r_sampling)
    lhs_samples.append(lhs_sim)
    orth_samples.append(orth_sim)

print()
print("NORMAL ESTIMATES: ")

print(f"\nEstimated Mandelbrot Area {EX}")
print(f"Simulations: {simulations}, Samples: {samples}\nVariance RS: {np.var(rs_samples)}\nVariance LHS: {np.var(lhs_samples)}\nVariance Orthogonal Sampling: {np.var(orth_samples)}\n ")
print(f"Estimated Mandelbrot Area E[X]: {EX:.4f}")
print(conf_int(np.mean(rs_samples), np.var(rs_samples), simulations, p=0.95))
print(conf_int(np.mean(lhs_samples), np.var(lhs_samples), simulations, p=0.95))
print(conf_int(np.mean(orth_samples), np.var(orth_samples), simulations, p=0.95))

print()
print("CONTROL VARIATES ESTIMATES: ")

mid = int(simulations/2)
cv_rs_x = rs_samples[:mid]
cv_rs_y = rs_samples[mid:]
cv_lhs_x = lhs_samples[:mid]
cv_lhs_y = lhs_samples[mid:]
cv_orth_x = orth_samples[:mid]
cv_orth_y = orth_samples[mid:]

cv_rs = [a_i - b_i + EX for a_i, b_i in zip(cv_rs_x, cv_rs_y)]
cv_lhs = [a_i - b_i + EX for a_i, b_i in zip(cv_lhs_x, cv_lhs_y)]
cv_orth = [a_i - b_i + EX for a_i, b_i in zip(cv_orth_x, cv_orth_y)]

print(f"\nEstimated Mandelbrot Area {EX}")
print(f"Simulations: {simulations}, Samples: {samples}\nVariance RS: {np.var(cv_rs)}\nVariance LHS: {np.var(cv_lhs)}\nVariance Orthogonal Sampling: {np.var(cv_orth)}\n ")
print(f"Estimated Mandelbrot Area E[X]: {EX:.4f}")
print(conf_int(np.mean(cv_rs), np.var(cv_rs), simulations, p=0.95))
print(conf_int(np.mean(cv_lhs), np.var(cv_lhs), simulations, p=0.95))
print(conf_int(np.mean(cv_orth), np.var(cv_orth), simulations, p=0.95))


import numpy as np
from scipy.stats import binom
from scipy.optimize import fmin

def compute_sigmoid(array):
    """
    """
    sigmoid = 1/(1 + np.exp(-array))
    return sigmoid

def compute_binom_neg_log_likelihood(p, n=16, k=9):
    """
    """
    negative_log_likelihood = - binom.logpmf(
        k=k,
        n=n,
        p=p
    )
    return negative_log_likelihood

def compute_binom_max_likelihood(start, n=16, k=9):
    """
    """
    solution, iterations = fmin(
        compute_binom_neg_log_likelihood,
        start,
        retall=True
    )
    solution = solution[0]
    iterations = [iter[0] for iter in iterations]
    return solution, iterations

def compute_MC_sampling(frozen_distro, max_sample=1000, iterations=100):
    """
    """
    sampled = {}
    samples = np.linspace(1, max_sample, iterations, dtype=int)
    for sample_size in samples:

        sampled[sample_size] = np.random.choice(frozen_distro, sample_size)

    return sampled

def compute_grid(para_1, para_2):
    """
    """
    grid = []
    for p_1 in para_1:

        for p_2 in para_2:

            grid.append([p_1, p_2])

    grid = np.array(grid)
    return grid

def compute_grid_growth(n_para, n_approx_points):
    """
    """
    grid_growth = [n_approx_points ** power for power in range(1, n_para)]
    return grid_growth

def compute_stats(array, ci=0.95, precis=2, sampling_prob=None):
    """
    """
    if sampling_prob is not None:
        sampled_array = np.random.choice(
            array,
            size=len(array),
            p=sampling_prob / sampling_prob.sum()
        )
    else:
        sampled_array = np.array(array)
    quantiles = np.quantile(sampled_array, [1.0 - ci, ci])
    stats = {
        'mean': sampled_array.mean(),
        'std': sampled_array.std(),
        'median': np.median(sampled_array),
        'lower': min(quantiles),
        'upper': max(quantiles)
    }
    for stat, value in stats.items():

        stats[stat] = np.round(value, precis)

    return stats

def compute_sp_posterior(prior, likelihood):
    """ Compute posterior probability

    Args:
        - prior:
        - likelihood:
    Returns:
        - posterior:
    """
    posterior = prior * likelihood
    posterior = posterior / np.sum(posterior)
    return posterior


from math import log10
import numpy as np

class EquationsSolver:

	def __init__(self, f, df = None):

		self.df = df

		if isinstance(f, (int, float)):

			self.f = lambda x: f

		elif callable(f):

			self.f = f

		else:

			raise Exception("The function is not defined")

	def __str__(self):

		try:

			return f"The interval or point: {self.x0}"

		except:

			raise Exception("Parametrs is not defined")

	def initial_conditions(self, x0, tol, maxiter = None):

		if tol <=0 or maxiter <=0:

			raise Exception("La toleracia o las iteraciones estan mal definidas")

		if isinstance(x0, (int, float)):

			self.point, self.interval = True, False
			x0 = float(x0)

		elif isinstance(x0, (list, tuple)):

			self.point, self.interval = False, True
			x0 = list(x0)

		else:

			raise Exception("The interval or point is not defined")

		self.x0, self.tol, self.maxiter = x0, tol, maxiter

class Bisection(EquationsSolver):

	def Solve(self):

		if isinstance(self.x0, (int, float)):

			raise ValueError("The interval is not defined")

		a, b  = self.x0
		f, tol = self.f, self.tol

		if a > b:

        		raise ValueError("Intervalo mal definido")

		if f(a) * f(b) >= 0.0:

			raise ValueError("La función debe cambiar de signo en el intervalo")

		if tol <= 0:

        		raise ValueError("La cota de error debe ser un número positivo")

		x = (a + b) / 2.0

		while True:

			if b - a < tol:

				return x

			elif np.sign(f(a)) * np.sign(f(x)) > 0:

            			a = x

			else:
				b = x

			x = (a + b) / 2.0

class Newton_Rhapson(EquationsSolver):

	def Solve(self):

		x, f, df, maxiter, tol = self.x0, self.f, self.df, self.maxiter, self.tol

		if df(x) == 0:

			raise ValueError("Division by zero. The method ended.")

		for i in range(maxiter):

		        dx = -f(x) / df(x)

		        x = x + dx

		        if abs(dx / x) < tol and abs(f(x)) < tol:

		            return x

		raise RuntimeError(f"There was no convergence after {maxiter} iterations")

class Secant(EquationsSolver):

	def Solve(self):

		f, maxiter, tol = self.f, self.maxiter, self.tol
		a, b = self.x0

		i = 0

		while i < maxiter:

			try:

				x = b - float(f(b))/(float(f(b) - f(a))/(b - a))

				er = abs(x-b)/abs(x)

			except ZeroDivisionError:

				print("Error en el denominador, para x = ", x)

				break

			if er < tol:

				return x

			a = b
			b = x
			i += 1

		return x


import numpy as np

def centroid(points):
    """
    Calculate the center of mass over a collection of points

    :param points: The points, expressed as numpy.array
    :type points: numpy.array

    :return: The center of mass for the given points
    :rtype: numpy.array
    """
    com = np.mean(points, 0)
    return com

def closest_point(v, w, p):
    """
    Find the point closest to p on the line between v and w
    Modified from StackOverflow at http://stackoverflow.com/a/1501725

    Returns the distance and the point on the line segment between v and w
    """
    vw = w - v
    len_squared = np.dot(vw, vw)
    if 0. == len_squared:
        # Handles the case when v == w
        projection = v
    else:
        t = max(0., min(1., np.dot((p - v), vw) / len_squared))
        projection = v + t * vw
    return np.linalg.norm(projection - p), projection


import matplotlib.pyplot
from matplotlib.pyplot import *

import numpy
from numpy import linspace, ndarray

import sympy
from sympy import symbols, sympify, solve

# supply & demand functions
p, q = symbols( 'p,q' )
dF = 9 - 0.75*q
sF = 0.75*q

supply_factor = 1000

def get_demand( price ):
	sln = solve( dF - price, q )[ 0 ]

	return round( sln, 2 )

def get_supply( qty ):
	sln = solve( sF - qty, q )[ 0 ] * supply_factor
	return round( sln, 2 )

def graphit():
	len = linspace( 0, 20, 50 )

	figure()

	y = []
	for x in len:
		y.append( dF.subs( { q: x } ) )

	plot( len, y )

	y = []
	for x in len:
		y.append( sF.subs( { q: x } ) )

	plot( len, y )

	legend (('cost','revenue'),loc=2)
	title( 'Breakeven Analysis' )
	show()

if __name__ == '__main__':

	# a
	price = 5.25
	print( 'demand at {0} is {1}.'.format( price, get_demand( price ) ) )

	# b
	print( 'supply at {0}: {1}'.format( price, get_supply( price ) ) )
	price = 3
	print( 'supply at {0}: {1}'.format( price, get_supply( price ) ) )

	# c
	graphit()

import numpy,from numpy,import sympy,from sympy


import numpy as np

def compute_psnr(y_original, y_estimated):
    # COMPUTE_PSNR Computes the PSNR between two images
    #
    # Input:
    #  y_original  - The original image
    #  y_estimated - The estimated image
    #
    # Output:
    #  psnr_val - The Peak Signal to Noise Ratio (PSNR) score

    y_original = np.reshape(y_original, (-1))
    y_estimated = np.reshape(y_estimated, (-1))

    # TODO: Compute the dynamic range
    # Write your code here... dynamic_range = ????
    dynamic_range = y_original.max() - y_original.min()

    # TODO: Compute the Mean Squared Error (MSE)
    # Write your code here... mse_val = ????
    mse_val = np.mean((y_original - y_estimated) ** 2)

    # TODO: Compute the PSNR
    # Write your code here... psnr_val = ????
    psnr_val = 10 * np.log10(dynamic_range ** 2 / mse_val)

    return psnr_val


"""
Author: Rahul G. Krishnan
Divergences used between probability measures
"""
import theano.tensor as T
def KLGaussian(mu_1,cov_1,mu_2,cov_2,logCov=False):
    """
    Estimate the KL divergence between two gaussians with diagonal covariance
    KL(q||p)=0.5*(log|Sigma_2|-log|Sigma_1| -D + tr(Sigma_2^-1 Sigma_1) + (mu_2-mu_1)^T Sigma_2^-1 (mu_2-mu_1))
    """
    if type(mu_1) is list:
        assert type(mu_2) is list and type(cov_1) is list and type(cov_2) is list,'Expecting lists'
        div = 0
        for m1,c1,m2,c2 in zip(mu_1,cov_1,mu_2,cov_2):
            div+=KLGaussian(m1,c1,m2,c2,logCov=logCov)
        return div
    else:
        diff   = mu_2-mu_1
        if logCov:
            logcov_1 = cov_1
            logcov_2 = cov_2
            return 0.5*(logcov_2-logcov_1-1+T.exp(logcov_1-logcov_2)+(diff**2)*T.exp(-1*logcov_2)).sum()
        else:
            return 0.5*(T.log(cov_2)-T.log(cov_1)-1+cov_1/cov_2+(diff**2)/cov_2).sum()

def BhattacharryaGaussian(mu_1,cov_1,mu_2,cov_2,logCov=False):
    """
    Estimate the Bhattacharyya distance between two gaussians with diagonal covariance
    D_B(q||p)
    See: http://like.silk.to/studymemo/PropertiesOfMultivariateGaussianFunction.pdf
    https://en.wikipedia.org/wiki/Bhattacharyya_distance
    """
    if type(mu_1) is list:
        assert type(mu_2) is list and type(cov_1) is list and type(cov_2) is list,'Expecting lists'
        div = 0
        for m1,c1,m2,c2 in zip(mu_1,cov_1,mu_2,cov_2):
            div+=BhattacharryaGaussian(m1,c1,m2,c2,logCov=logCov)
        return div
    else:
        diff = mu_1-mu_2
        if logCov:
            logcov_1= cov_1
            logcov_2= cov_2
            P       = (T.exp(logcov_2)+T.exp(logcov_1))/2
            logdet  = T.log(P) - 0.5*(logcov_2+logcov_1)
        else:
            P       = (cov_2+cov_1)/2
            logdet  =T.log(P) -0.5*T.log(cov_2*cov_1)
        D_B  = (1/8.)*(diff**2)/P + (1/2.)*logdet
        return D_B.sum()


"""Clustering-related mathematical functions.
"""

from typing import Optional

import numpy as np

import py4research.math.random as r

def kmeans(x: np.ndarray,
           n_clusters: Optional[int] = 1,
           max_iterations: Optional[int] = 100,
           tol: Optional[float] = 1e-4) -> np.ndarray:
    """Performs the K-Means clustering over the input data.

    Args:
        x: Input array with a shape equal to (n_samples, n_variables, n_dimensions).
        n_clusters: Number of clusters.
        max_iterations: Maximum number of clustering iterations.
        tol: Tolerance value to stop the clustering.

    Returns:
        (np.ndarray): Assigned cluster per input sample.

    """

    # Gathers the corresponding dimensions
    n_samples, n_variables, n_dimensions = x.shape[0], x.shape[1], x.shape[2]

    # Creates an array of centroids and labels
    centroids = np.zeros((n_clusters, n_variables, n_dimensions))
    labels = np.zeros(n_samples)

    for i in range(n_clusters):
        # Chooses a random sample to compose the centroid
        idx = r.generate_integer_random_number(0, n_samples)
        centroids[i] = x[idx]

    for _ in range(max_iterations):
        # Calculates the euclidean distance between samples and each centroid
        dists = np.squeeze(np.array([np.linalg.norm(x - c, axis=1) for c in centroids]))

        # Gathers the minimum distance as the cluster that conquers the sample
        updated_labels = np.squeeze(np.array(np.argmin(dists, axis=0)))

        # Calculates the difference ratio between old and new labels
        ratio = np.sum(labels != updated_labels) / n_samples

        if ratio <= tol:
            break

        # Updates the old labels with the new ones
        labels = updated_labels

        for i in range(n_clusters):
            # Gathers the samples that belongs to current centroid
            centroid_samples = x[labels == i]

            # If there are samples that belongs to the centroid
            if centroid_samples.shape[0] > 0:
                # Updates the centroid position
                centroids[i] = np.mean(centroid_samples, axis=0)

    return labels


import numpy as np

class ShapeFunction:
    """Class which contains functions to calculate the N matrix and the B matrix
    Inputs:
    1) (self.xi, self.eta) = natural coordinates
    2) vertex_coord = coordinates of the vertex points
    3) el_type = type of element used in meshing
       el_type = 0 (4 noded quadrilateral)
       el_type = 1 (8 noded quadrilateral)"""

    def __init__(self, xi, eta, el_type):
        self.xi = xi
        self.eta = eta
        self.el_type = el_type
        if self.el_type == 0:
            self.N = np.zeros((2, 8))
        elif self.el_type == 1:
            self.N = np.zeros((2, 16))
        self.J = np.zeros((2, 2))
        if self.el_type == 0:
            self.dpN = np.zeros((2, 4))
        elif self.el_type == 1:
            self.dpN = np.zeros((2, 8))

    def get_N_matrix(self):
        """Calculate the N matrix"""

        if self.el_type == 0:
            # shape functions for 4 noded quadrilateral
            N1 = (1 - self.xi) * (1 - self.eta) / 4
            N2 = (1 + self.xi) * (1 - self.eta) / 4
            N3 = (1 + self.xi) * (1 + self.eta) / 4
            N4 = (1 - self.xi) * (1 + self.eta) / 4

            self.N[0, ::2] = np.array([N1, N2, N3, N4])
            self.N[1, 1::2] = np.array([N1, N2, N3, N4])
            return self.N

        elif self.el_type == 1:
            # shape function for 8 noded quadrilateral element
            N1 = -(1 - self.xi) * (1 - self.eta)*(1 + self.xi + self.eta) / 4
            N2 = (1 + self.xi) * (1 - self.eta) * (-1 + self.xi - self.eta) / 4
            N3 = (1 + self.xi) * (1 + self.eta) * (-1 + self.xi + self.eta) / 4
            N4 = (1 - self.xi) * (1 + self.eta) * (-1 - self.xi + self.eta) / 4
            N5 = (1 - self.xi ** 2) * (1 - self.eta) / 2
            N6 = (1 + self.xi) * (1 - self.eta ** 2) / 2
            N7 = (1 - self.xi ** 2) * (1 + self.eta) / 2
            N8 = (1 - self.xi) * (1 - self.eta ** 2) / 2

            self.N[0, ::2] = np.array([N1, N2, N3, N4, N5, N6, N7, N8])
            self.N[1, 1::2] = np.array([N1, N2, N3, N4, N5, N6, N7, N8])
            return self.N

    def compute_Jacobian(self, vertex_coord):
        """Calculate the Jacobian matrix for 4 noded and 8 noded quadrilateral element"""

        # dpN = gradient of shape functions wrt natural coordinates (xi, eta)

        if self.el_type == 0:
            self.dpN[0, 0] = -(1 - self.eta) / 4
            self.dpN[1, 0] = -(1 - self.xi) / 4
            self.dpN[0, 1] = (1 - self.eta) / 4
            self.dpN[1, 1] = -(1 + self.xi) / 4
            self.dpN[0, 2] = (1 + self.eta) / 4
            self.dpN[1, 2] = (1 + self.xi) / 4
            self.dpN[0, 3] = -(1 + self.eta) / 4
            self.dpN[1, 3] = (1 - self.xi) / 4

        elif self.el_type == 1:
            self.dpN[0, 0] = -(1-self.eta)*(-2*self.xi-self.eta)/4
            self.dpN[1, 0] = -(1-self.eta)*(-self.xi-2*self.eta)/4
            self.dpN[0, 1] = (1 - self.eta) * (2*self.xi - self.eta) / 4
            self.dpN[1, 1] = -(1 - self.eta) * (-self.xi - 2 * self.eta) / 4
            self.dpN[0, 2] = (1 + self.eta) * (2*self.xi + self.eta) / 4
            self.dpN[1, 2] = (1 + self.eta) * (self.xi + 2 * self.eta) / 4
            self.dpN[0, 3] = (1 + self.eta) * (2*self.xi - self.eta) / 4
            self.dpN[1, 3] = (1 - self.eta) * (-self.xi + 2 * self.eta) / 4
            self.dpN[0, 4] = -2*self.xi * (1- self.eta) / 2
            self.dpN[1, 4] = (-1 + self.xi**2) / 2
            self.dpN[0, 5] = (1- self.eta**2)/2
            self.dpN[1, 5] = -2*self.eta*(1+self.xi) / 2
            self.dpN[0, 6] = -2*self.xi*(1+self.eta) / 2
            self.dpN[1, 6] = (1 - self.xi**2) / 2
            self.dpN[0, 7] = -(1 - self.eta**2) / 2
            self.dpN[1, 7] = (1 - self.xi)*(-2*self.eta) / 2

        self.J = np.dot(self.dpN, vertex_coord)

    def get_B_matrix(self):
        """Calculates the strain displacement matrix (B)"""

        # dN = spatial gradient of shape functions
        dN = np.dot(np.linalg.inv(self.J), self.dpN)
        B = np.zeros((3, 2*self.dpN.shape[1]))

        B[0, ::2] = dN[0, :]
        B[1, 1::2] = dN[1, :]
        B[2, ::2] = dN[1, :]
        B[2, 1::2] = dN[0, :]

        return B


"""
Kindly install these libraries before executing this code:
  1. numpy
  2. matplotlib
  3. scipy
"""

import numpy as np
import matplotlib.pyplot as plt
import cmath
import math
from scipy import stats
from mpl_toolkits.mplot3d.axes3d import Axes3D
from matplotlib import cm
from scipy.stats import multivariate_normal
from mpl_toolkits.mplot3d import Axes3D

# if using a Jupyter notebook, kindly uncomment following line:
%matplotlib inline

def genRandomNumbers(a):
  Z1 = np.random.normal(0, 1, 1000)
  Z2 = np.random.normal(0, 1, 1000)

  X1 = []
  for idx in range(0, len(Z1)):
    X1.append(5 + Z1[idx])

  X2 = []
  for idx in range(0, len(Z1)):
    X2.append(8 + 2*a*Z1[idx] + math.sqrt(1 - a*a)*2*Z2[idx])

  return X1, X2

def plot3D(X1, X2, a):
  x = np.array(X1)
  y = np.array(X2)

  fig = plt.figure()
  ax = fig.add_subplot(111, projection='3d')

  hist, xedges, yedges = np.histogram2d(x, y, bins = (30, 30))
  xpos, ypos = np.meshgrid(xedges[:-1] + xedges[1:], yedges[:-1] + yedges[1:])

  xpos = xpos.flatten()/2.
  ypos = ypos.flatten()/2.
  zpos = np.zeros_like (xpos)

  dx = xedges [1] - xedges [0]
  dy = yedges [1] - yedges [0]
  dz = hist.flatten()

  cmap = cm.get_cmap('jet')
  max_height = np.max(dz)
  min_height = np.min(dz)

  col = [cmap((k-min_height)/max_height) for k in dz]

  ax.bar3d(xpos, ypos, zpos, dx, dy, dz, color = col, zsort = 'average')
  plt.title("Frequency of Generated values for a = {}".format(a))
  plt.xlabel("Z1")
  plt.ylabel("Z2")
  plt.show()

def actualDensity(mu_x, mu_y, variance_x, variance_y, cov):
  x = np.linspace(2, 8, 1000)
  y = np.linspace(2, 14, 1000)
  X, Y = np.meshgrid(x, y)
  pos = np.empty(X.shape + (2,))
  pos[:, :, 0] = X; pos[:, :, 1] = Y
  rv = multivariate_normal([mu_x, mu_y], [[variance_x, cov], [cov, variance_y]])

  fig = plt.figure()
  ax = fig.gca(projection='3d')
  ax.plot_surface(X, Y, rv.pdf(pos), cmap = 'viridis', linewidth = 0)
  ax.set_xlabel('X1')
  ax.set_ylabel('X2')
  ax.set_zlabel('f(X)')
  plt.title('Actual Density for a = {}'.format(cov/2))
  plt.show()

  plt.contour(X, Y, rv.pdf(pos))
  plt.title('Contour plot of Multivariate Normal for a = {}'.format(cov/2))
  plt.xlim([2, 8])
  plt.ylim([3, 13])
  plt.xlabel('X axis')
  plt.ylabel('Y axis')
  plt.show()

def simulatedDensity(X1, X2, a):
  Z = []
  cov = np.array( [[1, 2*a], [2*a, 4]])
  u = np.array([5, 8])
  det = np.linalg.det(cov)

  for idx in range(0, len(X1)):
    x = np.array([X1[idx], X2[idx]])
    Z.append(1/(2*math.pi*math.sqrt(det)) * math.exp(-0.5*np.dot( np.dot(np.transpose(x - u), np.linalg.inv(cov)), (x - u))))

  fig = plt.figure()
  ax = fig.add_subplot(111, projection = '3d')
  ax.scatter(X1, X2, Z)
  ax.set_xlabel('X1')
  ax.set_ylabel('X2')
  ax.set_zlabel('f(X)')
  plt.title('Simulated Density for a = {}'.format(a))
  plt.show()

def marginalDensity(X, mean, var, rho, idx):
  x = np.arange(mean - 5, mean + 5, 0.1)
  y = stats.norm.pdf(x, mean, math.sqrt(var))
  plt.plot(x, y, color = 'red', label = 'Theoretical PDF')

  counts, bin_edges = np.histogram(X, bins = 150, density = 1)
  plt.title('Marginal Density - X{} for a = {}'.format(idx, rho))
  plt.ylabel('f(X)')
  plt.xlabel('X (generated values)')
  plt.plot(bin_edges[1:], counts, color = 'blue', label = "generated PDF")
  plt.legend(loc = "upper left")
  plt.show()

def main():
  a = [-0.5, 0, 0.5, 1]
  num = 1

  for rho in a:
    X1, X2 = genRandomNumbers(rho)
    print('********************** Case {}: a = {} **********************\n'.format(num, rho))
    print('X1 : ')
    print(X1)
    print('Total no of Random numbers in X1 = {}'.format(len(X1)))
    print()
    print('X2 : ')
    print(X2)
    print('Total no of Random numbers in X2 = {}'.format(len(X2)))
    print()

    if rho != 1:
      plot3D(X1, X2, rho)
      simulatedDensity(X1, X2, rho)
      actualDensity(5, 8, 1, 4, 2*rho)

    marginalDensity(X1, 5, 1, rho, 1)
    marginalDensity(X2, 8, 4, rho, 2)
    num += 1

if __name__ == "__main__":
    main()


import numpy as np
import matplotlib.pyplot as plt

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

def stable_coeff(alpha_1, alpha_2):
    a_1 = 2*np.tanh(alpha_1)
    a_2 = np.abs(a_1) + (2 - np.abs(a_1))*sigmoid(alpha_2) - 1
    return a_1, a_2

def roots_polynomial(a_1, a_2):
    delta = a_1**2 - 4 * a_2
    delta = delta.astype(np.complex)
    root_1 = (-a_1 + np.sqrt(delta))/2
    root_2 = (-a_1 - np.sqrt(delta))/2
    idx_real = delta > 0
    return root_1, root_2, idx_real

if __name__ == '__main__':

    N = 10000
    alpha_1 = np.random.randn(N)*1
    alpha_2 = np.random.randn(N)*1

    a_1, a_2 = stable_coeff(alpha_1, alpha_2)
    r_1, r_2, idx_real = roots_polynomial(a_1, a_2)

    fig, ax = plt.subplots()
    ax.plot(a_1, a_2, '*')
    ax.plot(a_1[idx_real], a_2[idx_real], 'k*')
    ax.set_xlabel('a_1')
    ax.set_ylabel('a_2')
    ax.set_xlim([-2, 2])
    ax.set_ylim([-2, 2])

    fig, ax = plt.subplots()
    ax.plot(np.real(r_1), np.imag(r_1), 'r*')
    ax.plot(np.real(r_2), np.imag(r_2), 'r*')
    ax.plot(np.real(r_1)[idx_real], np.imag(r_1)[idx_real], 'k*')
    ax.plot(np.real(r_2)[idx_real], np.imag(r_2)[idx_real], 'k*')
    ax.set_xlim([-1.2, 1.2])
    ax.set_ylim([-1.2, 1.2])

    perc_real = np.sum(idx_real) / N *100
    print(f"Real poles in {perc_real:.1f} cases")


import numpy as np

def Gini_index(Y_data):
    gini = 0
    #=========    Edit here    ==========
    #
    myset = list(set(Y_data))
    plist = [0]*len(myset)

    for y in Y_data:
        for s in myset:
            if y == s:
                plist[myset.index(s)] += 1

    for i in range(len(plist)):
        plist[i] /= len(Y_data)
        if plist[i] == 0:
            continue
        gini += plist[i]*(1-plist[i])

    #
    #
    #====================================
    return gini

def Entropy(Y_data):

    entropy = 0
    # =====    Edit here    ========
    #
    myset = list(set(Y_data))
    plist = [0]*len(myset)

    for y in Y_data:
        for s in myset:
            if y == s:
                plist[myset.index(s)] += 1
    for i in range(len(plist)):
        plist[i] /= len(Y_data)
        if plist[i] == 0:
            continue
        entropy -= plist[i]*np.log2(plist[i])

    #
    #
    # ==============================
    return entropy

def impurity_func(Y_data, criterion):

    if criterion == 'gini':
        return Gini_index(Y_data)

    elif criterion == 'entropy':
        return Entropy(Y_data)

def Finding_split_point(df, feature, criterion):

    col_data = df[feature]
    Y = df.values[:, -1]
    distinct_data = np.unique(col_data)

    split_point = distinct_data[0]
    min_purity = 1

    for idx, val in enumerate(distinct_data):
        less_idx = (col_data < val)

        y0 = Y[less_idx]
        y1 = Y[~less_idx]

        p0 = len(y0) / len(Y)
        p1 = len(y1) / len(Y)

        purity = np.sum([p0 * impurity_func(y0, criterion), p1 * impurity_func(y1, criterion)])

        if min_purity > purity:
            min_purity = purity
            split_point = val

    return split_point

def Gaussian_prob(x, mean, std):
    '''
    :param x: input value: X
    :param mean: the mean of X
    :param std: the standard deviation of X
    :return: probaility (X) ~ N(μ, σ^2)
    '''
    ret = 0
    # ========      Edit here         ==========
    pi = np.pi
    e = np.e
    ret = 1/((2*pi*std)**(1/2)) * (np.e)**((-1)*(x-mean)**2/(2*std))
    # =========================================
    return ret


#!/usr/bin/env python
"""stats_binom.py: Demonstrate how ``scipy.stats.binom`` works.
"""
from scipy.stats import binom

# pylint: disable=invalid-name

N = 6
p = 0.25
rv = binom(N, p)

# Compute moments.
mean, var, skew, kurt = rv.stats(moments='mvsk')

# mean == N * p
print(f"mean: {float(mean):.3f}")

# median == np.floor(N * p) or np.ceil(N * p) ?
print(f"median: {rv.median():.3f}")

# var == N * p * (1 - p)
print(f"var: {float(var):.3f}")

# skew == (1 - 2 * p)/np.sqrt(N * p * (1 - p))
print(f"skew: {float(skew):.3f}")

# kurt == (1 - 6 * p * (1 - p))/(N * p * (1 - p))
print(f"kurt: {float(kurt):.3f}")

# std == np.sqrt(var)
print(f"std: {rv.std():.3f}")

print("How many times you win:")
for i in range(N + 1):
    print(f"{i}/{N}: {float(rv.pmf(i)):.8f}")


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

epsilon = np.exp(-5)

# load the data and make the mean zero
x = np.loadtxt('/home/akhi/progs/pcaData.txt')
plt.figure(1)
plt.scatter(x[0,:], x[1,:])

avg = np.mean(x, 0)
x = x - np.matlib.repmat(avg, 2, 1)

"""compute covariance matrix, and then eigen values and eigen vectors of covariance
matrix using singular value decomposition"""
sigma = np.dot(x, np.transpose(x)) / x.shape[1]
U, S, V = np.linalg.svd(sigma) #np.dot(np.dot(U,np.diag(S)),V) gives the sigma back

#plot eigne vectors and verirfy their direction
plt.plot([0, U[0,0]],[0, U[1,0]])
plt.plot([0, U[0,1]],[0, U[1,1]])
plt.show()

"""compute the transformation to the new basis U
we can choose to retain k components in this stage"""
xRot = np.dot(np.transpose(U), x) # U'*x;
xTilde = np.dot(np.transpose(U[:,0:1]), x) # retains only one component

#PCA whitening
plt.figure(2)
xPCAWhite =  np.dot(np.diag(1 / np.sqrt(S + epsilon)), xRot)
# scale xPCAWhite[1](2nd row) with 1e+34 to get the same plot as in matlab,
# since the range of values is very small. if you didn't scale python will
# apptoximate it to xero and you will get a straight line parallel to x - axis
xPCAWhite[1] = xPCAWhite[1] * np.exp(34)
plt.scatter(xPCAWhite[0,:], xPCAWhite[1,:])
plt.show()

#ZCA whitening
plt.figure(3)
xZCAWhite = np.dot(U, xPCAWhite)
plt.scatter(xZCAWhite[0,:], xZCAWhite[1,:])
plt.show()


'''
Library of functions for Neural Networks
'''
import numpy as np

"""
Implements the gradient of the squared loss function
Parameters:
    -output_activations: a numpy ndarray of shape (2,1) containing the values of the output layer
    -y: a numpy ndarray of shape (2,1) containing the correct values for the output layer

Returns:
    a float value representing the gradient of the error with respect to the value of the output
    activations. This should be a numpy ndarray with the same shape as the inputs
"""
def squared_loss_gradient (output_activations, y):
    return - y + output_activations
    # return np.zeros((2,1))
#endDef

def sigmoid (z):
    return 1.0/(1.0 + np.exp(-z))
#endDef

def sigmoid_derivative (z):
    return sigmoid(z) * (1-sigmoid(z))
#endDef

def relu (z):
    return np.maximum(0, z)
#endDef

"""
Implements the derivative of the relu function, evaluated element-wise over the input vector
Parameters:
    -z: a numpy nd-array containing the output values of a layer of the neural network

Returns:
    a numpy nd-array having the same shape as the input where each index represents the derivative
    of the relu function applied to the corresponding coordinate of the input
"""
def relu_derivative (z):
    z[z > 0] = 1
    z[z <= 0] = 0
    return z
    # return np.zeros(z.shape)
#endDef

def tanh (z):
    return (np.exp(z) - np.exp(-z)) / (np.exp(z) + np.exp(-z))
#endDef

def tanh_derivative (z):
    return np.ones(z.shape) - np.square(tanh(z))
#endDef


#!/usr/bin/env python3
"""
Markov Module
"""
import numpy as np

def regular(P):
    """
    Determines the steady state probabilities of a regular markov chain:

    P is a is a square 2D numpy.ndarray of shape (n, n) representing the
    transition matrix
        P[i, j] is the probability of transitioning from state i to state j
        n is the number of states in the markov chain

    Returns: a numpy.ndarray of shape (1, n) containing the steady state
    probabilities, or None on failure
    """
    if type(P) is not np.ndarray or len(P.shape) != 2:
        return None
    if P.shape[0] != P.shape[1]:
        return None
    if np.sum(P, axis=1).all() != 1:
        return None

    s = np.ones((1, P.shape[0])) / P.shape[0]
    Ps = P.copy()
    while True:
        sprv = s
        s = np.matmul(s, P)
        Ps = P * Ps
        if np.any(Ps <= 0):
            return (None)
        if np.all(sprv == s):
            return (s)


import numpy as np
from numpy import cos, sin, pi, exp, log

#takes a vector [w] and returns the corresponding 3x3 skew symmetric matrix W
#such that for any other vector v, we have (w x v) = W.v
def to_skew(w):
    return np.array([[0, -w[2], w[1]],[w[2],0,-w[0]],[-w[1], w[0],0]])

#rotation around x (Returns 3x3 matrix)
def Rx(th):
    return np.array([[1,0,0],
                     [0,cos(th),-sin(th)],
                     [0,sin(th), cos(th)]])

#rotation around y (returns 3x3 matrix)
def Ry(th):
    return np.array([[cos(th), 0, sin(th)],
                    [0,1,0],
                    [-sin(th),0,cos(th)]])

#rotation around z (returns 3x3 matrix)
def Rz(th):
    return np.array([[cos(th), -sin(th), 0],
                     [sin(th),cos(th),0],
                     [0,0,1]])

#exponential map from [w] to 3x3 rotation matrix
def R_exp(w):
    w_hat = to_skew(w)
    norm = np.linalg.norm(w)
    if(norm > 0.001):
        return np.eye(3) + w_hat /norm * sin(norm) + w_hat.dot(w_hat)/(norm**2)*(1-cos(norm))
    else:
        return np.eye(3)

#exponential map from [v,w] to 4x4 homogenous transform
def T_exp(v,w):
    R = R_exp(w)
    T = np.eye(4)
    if(np.linalg.norm(w)<0.001):
        T[0:3,3] = v
    else:
        w_hat = to_skew(w)
        w_norm = np.linalg.norm(w)
        A = np.eye(3) + (1 - cos(w_norm))/(w_norm**2) * w_hat + (w_norm - sin(w_norm))/(w_norm**3) * w_hat.dot(w_hat)
        t = A.dot(v)
        T[0:3,0:3] = R
        T[0:3,3] = t
    return T

def hom(R,p):
    T = np.identity(4)
    T[0:3,0:3] = R
    T[0:3,3] = p
    return T

#returns the adjoint of homogeneous transform T
def adjoint(T):
    ad = np.zeros([6,6])
    ad[0:3,0:3] = T[0:3,0:3]
    ad[3:6,3:6] = T[0:3,0:3]
    ad[3:6,0:3] = to_skew(T[0:3,3]).dot(T[0:3,0:3])
    return ad


import numpy as np
import matplotlib.pyplot as plt

def knot_pad(Xi,p):
    """
    Repeats first and last knots of Xi
    """
    # Check on types
    assert isinstance(Xi,np.ndarray)
    # Pad knot vector with due repretitions
    return np.hstack((np.ones([p]) * Xi[0], Xi, np.ones([p]) * Xi[-1]))

def bspline(i, p, Xi, s):
    """
    Evaluation of univariate B-Splines by Cox-DeBoor recursion

    i: index of the B-spline
    p: B-spline degree
    Xi: knot vector
    s: evaluation point

    [1] Cox, M. G. (Oct. 1972). “The numerical evaluation of B-splines”. In: Journal of
        the Institute of Mathematics and its Applications 10.2, pp. 134–149. ISSN:
        0020-2932.
    [2] De Boor, C. (1972). “On calculating with B-splines”. In: Journal of
        Approximation Theory 6. Collection of articles dedicated to J. L. Walsh on
        his 75th birthday, V (Proc. Internat. Conf. Approximation Theory, Related
        Topics and their Applications, Univ. Maryland, College Park, Md., 1970),
        pp. 50–62. ISSN: 0021-9045.
    """

    # Check types
    assert isinstance(i,int)
    assert isinstance(p,int)
    assert isinstance(Xi,np.ndarray)

    # Initialize output to zero
    b = np.zeros(np.shape(s))

    # Define degree zero
    if p==0:
        if Xi[i+1] < Xi[-1]:
            # Internal nodes
            b[(Xi[i] <= s) & (s < Xi[i+1])] = 1
        else:
            # Last subinterval
            b[(Xi[i] <= s) & (s <= Xi[-1])] = 1
    else:
        d = Xi[i+p]-Xi[i] # side sx
        if d > 0:
            b = b + (s - Xi[i]) * bspline(i,p-1,Xi,s)/d

        d = Xi[i+p+1]-Xi[i+1] # side dx
        if d > 0:
            b = b + (Xi[i+p+1] - s) * bspline(i+1,p-1,Xi,s)/d

    return b

def bspline_space_dim(p,Xi):
    """
    Computes dimension of B-spline space span
    """
    assert isinstance(p,int)
    assert isinstance(Xi,np.ndarray)

    return np.size(Xi) - p - 1

def plot_bspline_basis(p,Xi):
    """
    Plots all B-spline basis functions
    """
    assert isinstance(p,int)
    assert isinstance(Xi,np.ndarray)

    # Evaluation points
    x = np.linspace(Xi[0],Xi[-1],20*np.size(Xi))

    for fdx in range(0,bspline_space_dim(p,Xi)):
        plt.plot(x,bspline(fdx,p,Xi,x))

    plt.show()

p = 6
# Xi = np.linspace(0, 1, 5)
Xi = np.sort(np.random.rand(5))
Xi = knot_pad(Xi,p)

# plot_bspline_basis(p,Xi)