import numpy as np
import pandas as pd
from minepy import MINE
from eda.tools import validait

## test if it is an array is a uniform distribution for numeric data
def test_uniform_num(data:np.array, alpha:float = .05, verbose:bool = False)->bool:
    """
    Test if it is an array is a uniform distribution for numeric data (Kolmogorov–Smirnov test).
    data -- 1D data to be tested.
    alpha -- Significance level (default, 0.05).
    verbose -- Display extra information (default, False).
    return -- boolean according test result.
    """
    from scipy.stats import uniform, ks_2samp
    # remove nan values
    data = data[~(np.isnan(data))]
    # get extremes
    dismin=np.amin(data)
    dismax=np.amax(data)
    try:
        # build an uniform distribution
        T = uniform(dismin,dismax-dismin).rvs(data.shape[0])
        # test if both have same distribution
        stat, p = ks_2samp(data, T)
    except:
        # manage exception
        if verbose:
            print('[error-Uniform num] It was not possible get a result.')
        return np.nan

    # display
    if verbose:
        print('stat=%.3f, p=%.3f' % (stat, p))
    # results
    if p > alpha:
        # display
        if verbose:
            print('Probably is Uniform')
        # return
        return True
    else:
        # display
        if verbose:
            print('Probably is not Uniform')
        # return
        return False

## test if it is an array is a uniform distribution for categorial data
def test_uniform_cat(data:np.array, alpha:float = .05, verbose:bool = False)->bool:
    """
    Test if it is an array is a uniform distribution for categorial data (Kolmogorov–Smirnov test).
    data -- 1D data to be tested.
    alpha -- Significance level (default, 0.05).
    verbose -- Display extra information (default, False).
    return -- boolean according test result.
    """
    from scipy.stats import ks_2samp
    try:
        # number of categories
        cats = np.unique(data)
        # resize if data is too large
        if len(data)>1000 and len(cats)*1000 < len(data):
            data = np.random.choice(data, size = len(cats)*1000)
        # create artificial data with uniform distribution
        data_uniform = np.random.choice(cats, size = len(data), p = np.ones(len(cats)) / len(cats))
        # cat to num of input data
        temp = list()
        for ii, ic in enumerate(cats):
            temp += list(np.ones(len(data[data==ic])) * ii)
        data_modif = np.array(temp)
        # cat to num of artificial data
        temp = list()
        for ii, ic in enumerate(cats):
            temp += list(np.ones(len(data_uniform[data_uniform==ic])) * ii)
        data_uniform_modif = np.array(temp)
        # test
        stat, p = ks_2samp(data, data_uniform)
    except:
        # manage exception
        if verbose:
            print('[error-Uniform cat] It was not possible get a result.')
        return np.nan
    if verbose:
        print('stat=%.3f, p=%.3f' % (stat, p))
    if p > alpha:
        if verbose:
            print('Probably is Uniform')
        return True
    else:
        if verbose:
            print('Probably is not Uniform')
        return False

## test if distribution is Gaussian (for N < 5000)
def test_shapiro(data:np.array, alpha:float = .05, verbose:bool = False)->bool:
    """
    Test if distribution is Gaussian (Shapiro–Wilk test).
    data -- 1D data to be tested.
    alpha -- Significance level (default, 0.05).
    verbose -- Display extra information (default, False).
    return -- boolean according test result.
    """
    from scipy.stats import shapiro
    # remove nan values
    data = data[~(np.isnan(data))]
    try:
        # test
        stat, p = shapiro(data)
    except:
        # manage exception
        if verbose:
            print('[error-Shapiro] It was not possible get a result.')
        return np.nan
    # display
    if verbose:
        print('stat=%.3f, p=%.3f' % (stat, p))
    # results
    if p > alpha:
        # display
        if verbose:
            print('Probably Gaussian')
        # return
        return True
    else:
        # display
        if verbose:
            print('Probably not Gaussian')
        # return
        return False

## test if distribution is Gaussian (large sample size)
def test_anderson(data:np.array, alpha:float = .05, verbose:bool = False)->bool:
    """
    Test if distribution is Gaussian (Agostino and Pearson’s test).
    data -- 1D data to be tested.
    alpha -- Significance level (default, 0.05).
    verbose -- Display extra information (default, False).
    return -- boolean according test result.
    """
    from scipy.stats import normaltest
    # remove nan values
    data = data[~(np.isnan(data))]
    try:
        # test
        stat, p = normaltest(data)
    except:
        # manage exception
        if verbose:
            print('[error-Anderson] It was not possible get a result.')
        return np.nan
    # display
    if verbose:
        print('stat=%.3f, p=%.3f' % (stat, p))
    # results
    if p > alpha:
        # display
        if verbose:
            print('Probably Gaussian')
        # return
        return True
    else:
        # display
        if verbose:
            print('Probably not Gaussian')
        # return
        return False

## test if is unimodal
def test_dip(data:np.array, alpha:float = 0.05, verbose:bool = False)->bool:
    """
    Test if is unimodal (Hartigan’s test).
    data -- 1D data to be tested.
    alpha -- Significance level (default, 0.05).
    verbose -- Display extra information (default, False).
    return -- boolean according test result.
    """
    import unidip.dip as dip
    data = data[~(np.isnan(data))]
    try:
        # sort data
        data = np.msort(data)
        # test
        stat, p, _ = dip.diptst(data)
    except:
        # manage exception
        if verbose:
            print('[error-Unimodal] It was not possible get a result.')
        return np.nan
    if p is None:
        return np.nan
    # display
    if verbose:
        print('stat=%.3f, p=%.3f' % (stat, p))
    if p > alpha:
        if verbose:
            print('Probably unimodal')
        return True
    else:
        if verbose:
            print('Probably not unimodal.')
        return False

## Test if two numerical variables are independents (Pearson's)
def correlation_pearson(data1:np.array,
                        data2:np.array,
                        alpha:float = 0.05,
                        return_corr:bool = False,
                        verbose:bool = False)->bool:
    """
    Test if two numerical variables are independents (Pearson's).

    data1, date2 -- 1D data to be tested.
    alpha -- Significance level (default, 0.05).
    return_corr -- If is True, return correlation value and his p-value (default, False).
    verbose -- Display extra information (default, False).
    return -- boolean according test result.
    """
    from scipy.stats import pearsonr
    try:
        # calculate Pearson's correlation
        corr, p = pearsonr(data1, data2)
    except:
        # manage exception
        if verbose:
            print('[error-Pearson] It was not possible get a result.')
        if return_corr:
            return np.nan, np.nan
        else:
            return np.nan
    # display
    if verbose:
        print('corr=%.3f, p=%.3f' % (corr, p))
    # check result and return
    if p > alpha:
        # display
        if verbose:
            print('Probably independent')
        # return
        if return_corr:
            return corr, p
        else:
            return True
    else:
        # display
        if verbose:
            print('Probably dependent')
        # return
        if return_corr:
            return corr, p
        else:
            return False

## Test if two numerical variables are independents (Spearman's)
def correlation_spearman(data1:np.array,
                         data2:np.array,
                         alpha:float = 0.05,
                         return_corr:bool = False,
                         verbose:bool = False)->bool:
    """
    Test if two numerical variables are independents (Spearman's).

    data1, date2 -- 1D data to be tested.
    alpha -- Significance level (default, 0.05).
    return_corr -- If is True, return correlation value and his p-value (default, False).
    verbose -- Display extra information (default, False).
    return -- boolean according test result.
    """
    from scipy.stats import spearmanr
    try:
        # calculate Pearson's correlation
        corr, p = spearmanr(data1, data2)
    except:
        # manage exception
        if verbose:
            print('[error-Spearman] It was not possible get a result.')
        if return_corr:
            return np.nan, np.nan
        else:
            return np.nan
    # display
    if verbose:
        print('corr=%.3f, p=%.3f' % (corr, p))
    # check result and return
    if p > alpha:
        # display
        if verbose:
            print('Probably independent')
        # return
        if return_corr:
            return corr, p
        else:
            return True
    else:
        # display
        if verbose:
            print('Probably dependent')
        # return
        if return_corr:
            return corr, p
        else:
            return False

## Calculate Kendall’s tau, a correlation measure for ordinal data
def correlation_kendalltau(data1:np.array,
                           data2:np.array,
                           alpha:float = 0.05,
                           return_corr:bool = False,
                           verbose:bool = False)->float:
    """
    Test if two numerical/ordinal variables are independents (Kendall’s tau).

    data1, date2 -- 1D data to be tested.
    alpha -- Significance level (default, 0.05).
    return_corr -- If is True, return correlation value and his p-value (default, False).
    verbose -- Display extra information (default, False).
    return -- boolean according test result.
    """
    from scipy.stats import kendalltau
    # test
    try:
        corr, p = kendalltau(data1, data2)
    except:
        # manage exception
        if verbose:
            print('[error-Kendall Tau] It was not possible get a result.')
        if return_corr:
            return np.nan, np.nan
        else:
            return np.nan
    # visualize
    if verbose:
        print('corr=%.3f, p=%.5f' % (corr, p))
    # check result and return
    if p > alpha:
        # display
        if verbose:
            print('Probably independent')
        # return
        if return_corr:
            return corr, p
        else:
            return True
    else:
        # display
        if verbose:
            print('Probably dependent')
        # return
        if return_corr:
            return corr, p
        else:
            return False

## Linear correlation analysis to test independence for numerical / ordinal variables
@validait
def analysis_linear_correlation(data1:np.array,
                                data2:np.array,
                                alpha:float = .05,
                                return_corr:bool = True,
                                verbose:bool = False)->bool:
    """
    ## Linear correlation analysis to test independence for numerical / ordinal variables.

    data1, date2 -- 1D data to be tested.
    alpha -- Significance level (default, 0.05).
    return_corr -- If is True, return correlation value and his p-value (default, False).
    verbose -- Display extra information (default, False).
    return -- boolean according test result.
    """

    # get types
    type1 = data1.dtype
    type2 = data2.dtype
    # get size
    n = len(data1)

    # ord - ord
    if type1 == "int64" and type2 == "int64":
        # number of categories
        ncat1 = len(np.unique(data1))
        ncat2 = len(np.unique(data2))
        # analysis
        if ncat1 >= 5 and ncat2 >= 5:
            result = correlation_spearman(data1, data2, alpha = alpha, return_corr = return_corr, verbose = verbose)
        else:
            result = correlation_kendalltau(data1, data2, alpha = alpha, return_corr = return_corr, verbose = verbose)

    # num - num
    if type1 == "float64" and type2 == "float64":
        # test if variables are gaussian
        if n >= 5000:
            is_normal1 = test_anderson(data1, alpha = alpha)
            is_normal2 = test_anderson(data2, alpha = alpha)
        else:
            is_normal1 = test_shapiro(data1, alpha = alpha)
            is_normal2 = test_shapiro(data2, alpha = alpha)
        # analysis
        if n >= 100:
            result = correlation_pearson(data1, data2, alpha = alpha, return_corr = return_corr, verbose = verbose)
        else:
            if is_normal1 and is_normal2:
                result = correlation_pearson(data1, data2, alpha = alpha, return_corr = return_corr, verbose = verbose)
            else:
                result = correlation_spearman(data1, data2, alpha = alpha, return_corr = return_corr, verbose = verbose)

    # num - ord
    if (type1 == "float64" and type2 == "int64") or (type1 == "int64" and type2 == "float64"):
        # number of categories
        if type1 == "int64":
            ncat = len(np.unique(data1))
        else:
            ncat = len(np.unique(data2))
        # analysis
        if ncat < 5:
            result = correlation_kendalltau(data1, data2, alpha = alpha, return_corr = return_corr, verbose = verbose)
        else:
            if n >= 100:
                result = correlation_pearson(data1, data2, alpha = alpha, return_corr = return_corr, verbose = verbose)
            else:
                result = correlation_spearman(data1, data2, alpha = alpha, return_corr = return_corr, verbose = verbose)

    # return
    return result

## Maximal Information Score to estimate non-linear correlation
def correlation_mic(x:np.array, y:np.array)->float:
    """
    Maximal Information Score to estimate non-linear correlation.
    x -- first array to be analyzed.
    y -- second array to be analyzed.
    return -- MIC.

    > DOC: https://minepy.readthedocs.io/en/latest/python.html
    """
    try:
        mine = MINE(alpha=0.6, c=15, est="mic_approx")
        mine.compute_score(x, y)
        return mine.mic()
    except:
        # manage exception
        if verbose:
            print('[error-MIC] It was not possible get a result.')
        return np.nan

## Test if two categorical variables are independents (Chi-Squared Test)
def chi_square(data1:np.array, data2:np.array, alpha:float = 0.05, verbose:bool = False)->bool:
    """
    Test if two categorical variables are independents (Chi-Squared Test).
    data1, date2 -- 1D data to be tested.
    alpha -- Significance level (default, 0.05).
    verbose -- Display extra information (default, False).
    return -- boolean according test result.
    """
    from scipy.stats import chi2_contingency
    try:
        # contingence table
        table = pd.crosstab(data1, data2, margins = False).values
        # test
        stat, p, dof, expected = chi2_contingency(table)
    except:
        # manage exception
        if verbose:
            print('[error-chi square] It was not possible get a result.')
        return np.nan
    # display
    if verbose:
        print('stat=%.3f, p=%.3f' % (stat, p))
    # results
    if p > alpha:
        # display
        if verbose:
            print('Probably independent')
        # return
        return True
    else:
        # display
        if verbose:
            print('Probably dependent')
        # return
        return False

## Leneve test (test if samples have same variance = Homoscedasticity)
@validait
def test_leneve(*args, alpha:float = 0.05, verbose:bool = False)->bool:
    """
    Leneve test (test if samples have same variance = Homoscedasticity).
    *args -- n groups of samples.
    alpha -- Significance level (default, 0.05).
    verbose -- Display extra information (default, False).
    return -- True / False, samples have same distribution.
    """
    from scipy.stats import levene
    # test
    stat, p = levene(*args)
    # interpret
    if p > alpha:
        # display
        if verbose:
            print(f'Probably all samples with equal variances (fail to reject H0 with alpha = {alpha})')
        # return
        return True
    else:
        # display
        if verbose:
            print(f'Probably all samples with different variances (reject H0 with alpha = {alpha})')
        # return
        return False

## ANOVA test
def ANOVA(*args, alpha:float = 0.05, verbose:bool = False)->bool:
    """
    The one-way ANOVA variance test (parametric) tests the null hypothesis that two or more groups
    have the same population mean. The test is applied to samples from two or more groups, possibly
    with differing sizes.
    *args -- n groups of samples.
    alpha -- Significance level (default, 0.05).
    verbose -- Display extra information (default, False).
    return -- True / False, samples have same distribution.
    """
    from scipy.stats import f_oneway
    try:
        # test
        stat, p = f_oneway(*args)
    except:
        # manage exception
        if verbose:
            print('[error-ANOVA] It was not possible get a result.')
        return np.nan
    # display
    if verbose:
        print('Statistics=%.3f, p=%.3f' % (stat, p))
    # interpret
    if p > alpha:
        # display
        if verbose:
            print(f'Same distributions (fail to reject H0 with alpha = {alpha})')
        # return
        return True
    else:
        # display
        if verbose:
            print(f'Different distributions (reject H0 with alpha = {alpha})')
        # return
        return False

## Kruskal-Wallis test
def test_kruskal(*args, alpha:float = 0.05, verbose:bool = False)->bool:
    """
    The Kruskal-Wallis variance test (non-parametric) tests the null hypothesis that two or more groups
    have the same population mean. The test is applied to samples from two or more groups, possibly
    with differing sizes.
    *args -- n groups of samples.
    alpha -- Significance level (default, 0.05).
    verbose -- Display extra information (default, False).
    return -- True / False, samples have same distribution.
    """
    from scipy import stats
    try:
        # test
        stat, p = stats.kruskal(*args)
    except:
        # manage exception
        if verbose:
            print('[error-Kruskal] It was not possible get a result.')
        return np.nan
    # display
    if verbose:
        print('Statistics=%.3f, p=%.3f' % (stat, p))
    # interpret
    if p > alpha:
        # display
        if verbose:
            print(f'Same distributions (fail to reject H0 with alpha = {alpha})')
        # return
        return True
    else:
        # display
        if verbose:
            print(f'Different distributions (reject H0 with alpha = {alpha})')
        # return
        return False

## Variance analysis: subsamples of num variable created by a cat variable are the same original sample
@validait
def analysis_variance(data_cat:np.array,
                      data_num:np.array,
                      alpha:float = .05,
                      verbose:bool = False)->bool:
    """
    ## Linear correlation analysis to test independence for numerical / ordinal variables.

    data_cat -- 1D categorical data to be tested.
    date_num -- 1D numerical data to be tested.
    alpha -- Significance level (default, 0.05).
    verbose -- Display extra information (default, False).
    return -- boolean according test result.
    """
    # validate
    assert len(data_cat) == len(data_num), "both data arrays must to have the same lenght."
    # store in df and remove nan values
    temp = pd.DataFrame({'vcat':data_cat, 'vnum':data_num}).dropna()
    # validate
    if len(temp) == 0:
        return np.nan
    # number of records
    n = len(temp)
    # test if numerical variable is gaussian
    if n >= 5000:
        is_normal = test_anderson(temp.vnum.values, alpha = alpha)
    else:
        is_normal = test_shapiro(temp.vnum.values, alpha = alpha)
    # collect groups data according categorical variable
    groups = temp.groupby("vcat")["vnum"]
    data_groups = [groups.get_group(c).values for c in temp.vcat.dropna().unique()]
    # validate number of groups
    if len(data_groups) == 1:
        return True
    # test Homoscedasticity inter-groups
    is_same_variance = test_leneve(*data_groups, alpha = alpha, verbose = verbose)
    # test if samples of numerical variable by categorical variable have same distribution
    if n >= 50:
        if is_same_variance:
            is_samples_same_distribution = ANOVA(*data_groups, alpha = alpha, verbose = verbose)
        else:
            is_samples_same_distribution = test_kruskal(*data_groups, alpha = alpha, verbose = verbose)

    else:
        if is_normal:
            if is_same_variance:
                is_samples_same_distribution = ANOVA(*data_groups, alpha = alpha, verbose = verbose)
            else:
                is_samples_same_distribution = test_kruskal(*data_groups, alpha = alpha, verbose = verbose)
        else:
            is_samples_same_distribution = test_kruskal(*data_groups, alpha = alpha, verbose = verbose)

    # return
    return is_samples_same_distribution


## HEAT DIFFUSION

import numpy as np
import matplotlib.pyplot as plt
import pylan as pn

## grid

dx = 0.1
Nx = int(1/dx+1)
x = np.arange(0,1+dx,dx)

dt = 0.01
T = 5
Nt = int(T/dt)
t = np.arange(0,T,dt)

xx,tt = np.meshgrid(x,t)

## Laplace operator

a = (1/np.pi)**2 #alpha**2
L = a*(-2*np.eye(Nx) + np.diag(np.ones(Nx-1),1) + np.diag(np.ones(Nx-1),-1))/dx**2
L = L[1:-1,1:-1] #apply only in the interior

## Euler forward

def forward(T):
    Nt = int(T/dt)
    phi = np.zeros((Nt,Nx))
    phi[0,:] = np.cos(np.pi*(x-.5))

    for i in range(Nt-1):
        phi[i+1,1:-1] = phi[i,1:-1] + dt*L.dot(phi[i,1:-1])

    return phi

def backward(T):
    Nt = int(T/dt)
    phi = np.zeros((Nt,Nx))
    phi[0,:] = np.cos(np.pi*(x-.5))

    for i in range(Nt-1):
        phi[i+1,1:-1] = np.linalg.solve(np.eye(Nx-2) - dt*L,phi[i,1:-1])

    return phi

def analytic(T):
    return np.exp(-tt)*np.cos(np.pi*(xx-.5))

## plotting

ferror = forward(T)-analytic(T)
berror = backward(T)-analytic(T)

levmax = pn.round1(abs(np.hstack((ferror,berror))).max())*1.1

lev1 = np.linspace(0,1,11)
lev2 = np.linspace(-levmax,levmax,13)

fig,((ax1,ax2),(ax3,ax4)) = plt.subplots(2,2,sharex=True,sharey=True)

im1 = ax1.contourf(xx,tt,forward(T),lev1)
ax2.contourf(xx,tt,backward(T),lev1)

im2 = ax3.contourf(xx,tt,ferror,lev2,cmap='RdBu_r')
ax4.contourf(xx,tt,berror,lev2,cmap='RdBu_r')

plt.colorbar(im1,ax=(ax1,ax2))
plt.colorbar(im2,ax=(ax3,ax4))

ax1.set_title('forward')
ax2.set_title('backward')
ax3.set_title('Error: forward - analytic')
ax4.set_title('Error: backward - analytic')

ax4.set_xlabel('x')
ax3.set_xlabel('x')
ax1.set_ylabel('t')
ax3.set_ylabel('t')

plt.show()


"""
  Name     : c9_18_sharpe_ratio.py
  Book     : Python for Finance (2nd ed.)
  Publisher: Packt Publishing Ltd.
  Author   : Yuxing Yan
  Date     : 6/6/2017
  email    : yany@canisius.edu
             paulyxy@hotmail.com
"""
import scipy as sp
import numpy as np
import pandas as pd
from scipy.optimize import fmin
from matplotlib.finance import quotes_historical_yahoo_ochl as getData
#
# Step 1: input area
ticker=('IBM','WMT','C')   # tickers
begdate=(1990,1,1)         # beginning date
enddate=(2012,12,31)       # ending date
rf=0.0003                  # annual risk-free rate

# Step 2: define a few functions
# function 1:
def ret_annual(ticker,begdate,enddte):
    x=getData(ticker,begdate,enddate,asobject=True,adjusted=True)
    logret =sp.log(x.aclose[1:]/x.aclose[:-1])
    date=[]
    d0=x.date
    for i in range(0,sp.size(logret)):
        date.append(d0[i].strftime("%Y"))
    y=pd.DataFrame(logret,date,columns=[ticker])
    return sp.exp(y.groupby(y.index).sum())-1
# function 2: estimate portfolio variance
def portfolio_var(R,w):
    cor = sp.corrcoef(R.T)
    std_dev=sp.std(R,axis=0)
    var = 0.0
    for i in xrange(n):
        for j in xrange(n):
            var += w[i]*w[j]*std_dev[i]*std_dev[j]*cor[i, j]
    return var
# function 3: estimate Sharpe ratio
def sharpe(R,w):
    var = portfolio_var(R,w)
    mean_return=sp.mean(R,axis=0)
    ret = sp.array(mean_return)
    return (sp.dot(w,ret) - rf)/sp.sqrt(var)
# function 4: for given n-1 weights, return a negative sharpe ratio
def negative_sharpe_n_minus_1_stock(w):
    w2=sp.append(w,1-sum(w))
    return -sharpe(R,w2)        # using a return matrix here!!!!!!

# Step 3: generate a return matrix (annul return)
n=len(ticker)              # number of stocks
x2=ret_annual(ticker[0],begdate,enddate)
for i in range(1,n):
    x_=ret_annual(ticker[i],begdate,enddate)
    x2=pd.merge(x2,x_,left_index=True,right_index=True)

# using scipy array format
R = sp.array(x2)
print('Efficient porfolio (mean-variance) :ticker used')
print(ticker)
print('Sharpe ratio for an equal-weighted portfolio')
equal_w=sp.ones(n, dtype=float) * 1.0 /n
print(equal_w)
print(sharpe(R,equal_w))
# for n stocks, we could only choose n-1 weights
w0= sp.ones(n-1, dtype=float) * 1.0 /n
w1 = fmin(negative_sharpe_n_minus_1_stock,w0)
final_w = sp.append(w1, 1 - sum(w1))
final_sharpe = sharpe(R,final_w)
print ('Optimal weights are ')
print (final_w)
print ('final Sharpe ratio is ')
print(final_sharpe)


#To test an individual's use of a certain mineral, a researcher injects small amount of a radioactive form of that mineral into the person's bloodstream.
#The mineral remaining in the bloodstream is measured each day for several days.
#Suppose the amount of the mineral remaining in the bloodstream (in milligrams per cubic centimeter) t days after the initial injection is approximated by

# C(t) = 1/2 ( 4t + 4) ^ -1/2

# Find the rate of change of the mineral level with respect to time for 7.5 days.

from sympy import symbols, solve, pprint, Derivative, init_printing, pretty
import math

init_printing()

def disp_fun( f ):
	pprint( '\n{0}\n\n'.format( pretty( f ) ) )

t = symbols( 't' )
C = .5*( 4*t + 4 ) ** -(.5)

disp_fun( C )

dC = Derivative( C, t ).doit()

# The rate of change of the mineral level with respect to time for 7.5 days is approximately
# nothing milligrams per cubic centimeter per day.
# (Round to two decimal places as needed.)

round( dC.subs( { t: 7.5 } ), 2 )


from math import cos, sqrt, acos, atan, pi, sin
import numpy as np
from scipy.optimize import fsolve
''' This program determines the distance necessary for a plane at (x1,y1) and bearing (degrees) to travel to point (x2,y2)
	based on its turning radius r in a 2-D coordiante system. This assumes the destination is 'in front' of the plane's direction,
	i.e. its destination is within +- 90 degrees relative to forward bearing'''

# (x1,y1), (x2,y2), turning radius, and bearing (in degrees) to be input
x1 = 0.; y1 = 0.; x2 = 4.; y2 = 2.; r = 2; bearing = 90.
bearing = bearing*pi/180

# normalize the coordinate system so the plane is at (0,0) facing the positive y-axis and (x2,y2) becomes point (i,j)
dxy = sqrt((x2-x1)**2+(y2-y1)**2)
mxy = (y2-y1)/(x2-x1)
phi = atan(abs(y2-y1)/abs(x2-x1))
theta = abs(bearing - phi)

# if (x2,y2) are right relative to the plane's bearing
if mxy > 0:
	i = dxy*sin(theta)
	j = dxy*cos(theta)
	print "(i,j) is (%f, %f)." %(i,j)

# if (x2,y2) are left relative to the plane's bearing
if mxy < 0:
	i = -dxy*sin(theta)
	j = dxy*cos(theta)
	print "(i,j) is (%f, %f)." %(i,j)

''' Solving for point (x,y), the point on the turning radius tangent to (i,j)'''

# use if (i,j) is left of the plane
def func_left(p, *data):
	x,y = p
	i,j,r = data
	return ((x+r)*(i-x)+(j-y)*y,(x+r)**2+y**2-r**2)

# use if (i,j) is right of plane
def func_right(p, *data):
	x,y = p
	i,j,r = data
	return ((x-r)*(i-x)+(j-y)*y,(x-r)**2+y**2-r**2)

data = (i,j,r)
m = j/i

# if (i,j) to the right
if m > 0:
	x,y = fsolve(func_right, [1,1], args=data)
	print "(x,y) is (%f, %f)." %(x,y)

# if (i,j) to the left
if m < 0:
	x,y = fsolve(func_left, [1,1], args=data)
	print "(x,y) is (%f, %f)." %(x,y)

# distance bewteen (x,y) and (i,j)
d1 = sqrt((i-x)**2+(j-y)**2)

# straight line distance between starting point (origin) and (x,y)
d2 = sqrt(x**2+y**2)

# arclength distance traveled based on turning radius
theta = acos((2*r**2-d2**2)/(2*r**2))
s = r*theta

# total distance plane needs to travel to reach (i,j)
D = d1 + s
print "Total distance is %f." %D


import math
import numpy as np

print("VEKTORAUFGABEN (Aufgabe 8, S. 116)")
print("==================================")
print()
print("Gegeben sind die Vektoren")
print()
print("     [[2]      [[3]")
print(" v =  [5], w =  [-1]")
print("      [6]]      [2]]")
print()
v = np.array([[2], [5], [6]])
w = np.array([[3], [-1], [2]])
print(f"v + w =\n{v + w}")
print()
print(f"-2 * v =\n{-2 * v}")
print()
print(f"(1/2) * w =\n{(1/2.0) * w}")
print()
print("Hinweis: Die folgenden Operationen werden in NumPy überwiegend mit Zeilen- statt Spaltenvektoren gerechnet")
print()
v = np.array([2, 5, 6])
w = np.array([3, -1, 2])
sp = np.dot(v, w)
print(f"Skalarprodukt von v und w: {sp}")
print(f"Winkel zwischen v und w: {math.degrees(math.acos(sp / (np.linalg.norm(v) * np.linalg.norm(w))))}°")
print()
cp = np.cross(v, w)
print(f"Kreuzprodukt von v und w: {cp}")
print(f"Flächeninhalt des Parallelogramms zwischen v und w (Betrag des Kreuzprodukts): {np.linalg.norm(cp)}")
print()
print("MATRIXAUFGABEN (Aufgabe 9, S. 116 f.)")
print("=====================================")
print()
print("Gegeben sind die Matrizen")
print()
print(" A = [[4 3 -2]   B = [[2 5]")
print("      [1 2 -3]]       [1 -3]]")
print()
a = np.array([[4, 3, -2], [1, 2, -3]])
b = np.array([[2, 5], [1, -3]])
print("Transposition von A und B:")
print(a.transpose())
print(b.transpose())
print()
print("(-1 / 3) * A =")
print((-1 / 3) * a)
print()
print("3 * B =")
print(3 * b)
print()
print("Matrixmultiplikation (Falk-Schema) B * A:")
print(np.dot(b, a))
print()
print("Matrixmultiplikation (Falk-Schema) A[T] * B:")
print(np.dot(a.transpose(), b))
print()
print(f"Determinante von B: {np.linalg.det(b)}")
print()
print("Inverse Matrix von B:")
print(np.linalg.inv(b))
print()
print("Lösung von Gleichungssystemen mithilfe der inversen Matrix:")
print("2x + 5y = 7, x - 3y = -2")
print(np.linalg.solve(b, np.array([7, -2])))
print("2x + 5y = 6, x - 3y = 14")
print(np.linalg.solve(b, np.array([6, 14])))


import time
import pandas as pd
import numpy as np
from scipy import stats
import string
import matplotlib.pyplot as plt
import cvxopt as cp
from cvxopt import blas, solvers
import scipy.optimize as sco
from scipy.optimize import Bounds, LinearConstraint

num_of_assets = 10
num_of_periods = 10

data = pd.DataFrame(np.random.uniform(0,.2,size=(num_of_periods,num_of_assets)),
                            columns=list(string.ascii_uppercase[0:num_of_assets]))

def get_portfolio_sd(weights,covariance):
    #get standard deviation of portfolio weight combo given weights and covariance matrix
    Std_Dev = np.sqrt(np.dot(weights.T,np.dot(covariance,weights)))

    return Std_Dev

def ret_risk(W, exp_ret, cov):
    return -((W.T@exp_ret) / (W.T@cov@W)**0.5)

def optimize(func, W, exp_ret, cov, target_return):
    opt_bounds = Bounds(0,1)
    opt_constraints = ({'type': 'eq',
                       'fun': lambda W: 1.0- np.sum(W)},
                       {'type':'eq',
                        'fun': lambda W: target_return-W.T@exp_ret})
    optimal_weights = sco.minimize(func,W,
                        args=(exp_ret,cov),
                        method='SLSQP',
                        bounds = opt_bounds,
                        constraints = opt_constraints)

    return optimal_weights['x']

def get_frontier_weights(mean,cov,min_ret,max_ret):

    step = (max_ret-min_ret)/1000
    ret_array = np.arange(min_ret,max_ret+step,step)
    optimal_portfolios = []

    for ret_level in ret_array:
        W = np.ones(10)
        optimal_portfolios.append(optimize(ret_risk,W,mean,cov,target_return=ret_level))

    return optimal_portfolios

def build_portfolio_stats(return_data,weights_table):
    """
    Generate scatterplot of portfolios and relevant statistics corresponding to a given increment
    """
    weights_table = weights_table.div(100)

    cov = return_data.cov(ddof=0)

    mean = return_data.apply(stats.gmean,axis=0)
    portfolio_return = weights_table.multiply(np.array(mean), axis = 'columns')

    portfolios = pd.DataFrame()
    portfolios['Expected_Return'] = portfolio_return.sum(axis = 'columns')
    portfolios['Std_Dev'] = weights_table.apply(lambda x: get_portfolio_sd(x,cov), axis = 'columns')
    portfolios['Sharpe'] = portfolios['Expected_Return']/portfolios['Std_Dev']

    #get positional arguments for max sharpe ratio portfolio
    max_sr_ret = portfolios['Expected_Return'][portfolios['Sharpe'].argmax()]
    max_sr_vol = portfolios['Std_Dev'][portfolios['Sharpe'].argmax()]

    min_vol_ret = portfolios['Expected_Return'][portfolios['Std_Dev'].argmin()]
    max_ret_ret = portfolios['Expected_Return'][portfolios['Expected_Return'].argmax()]

    optimal_portfolios = pd.DataFrame.from_dict(get_frontier_weights(mean,cov,min_vol_ret,max_ret_ret))
    optimal_returns = optimal_portfolios.multiply(np.array(mean), axis = 'columns')
    optimal_portfolios['Std_Dev'] = optimal_portfolios.apply(lambda x: get_portfolio_sd(x,cov), axis = 'columns')
    optimal_portfolios['Expected_Return'] = optimal_returns.sum(axis = 'columns')
    optimal_portfolios['Sharpe'] = optimal_portfolios['Expected_Return']/optimal_portfolios['Std_Dev']

    #plot portfolios
    plt.figure(figsize=(12,12))
    plt.scatter(portfolios['Std_Dev'], portfolios['Expected_Return'], c=portfolios['Sharpe'], cmap='viridis')
    plt.colorbar(label='Sharpe Ratio')
    plt.xlabel('Volatility')
    plt.ylabel('Return')
    plt.plot(optimal_portfolios['Std_Dev'],optimal_portfolios['Expected_Return'])
    plt.scatter(max_sr_vol, max_sr_ret,c='red', s=50) # red dot
    plt.show()

def main():
    combos = pd.read_csv('combos10.csv',index_col=False)
    build_portfolio_stats(data,combos)

if __name__ == '__main__':
    main()


import itertools
import numpy as np
from .normalizations import sum_normalization

# Entropy weighting
def entropy_weighting(matrix):
    """
    Calculate criteria weights using objective Entropy weighting method.

    Parameters
    -----------
        matrix : ndarray
            Decision matrix with performance values of m alternatives and n criteria

    Returns
    --------
        ndarray
            Vector of criteria weights

    Examples
    ----------
    >>> weights = entropy_weighting(matrix)
    """
    # normalize the decision matrix with sum_normalization method from normalizations as for profit criteria
    types = np.ones(np.shape(matrix)[1])
    pij = sum_normalization(matrix, types)
    # Transform negative values in decision matrix X to positive values
    pij = np.abs(pij)
    m, n = np.shape(pij)
    H = np.zeros((m, n))

    # Calculate entropy
    for j, i in itertools.product(range(n), range(m)):
        if pij[i, j]:
            H[i, j] = pij[i, j] * np.log(pij[i, j])

    h = np.sum(H, axis = 0) * (-1 * ((np.log(m)) ** (-1)))

    # Calculate degree of diversification
    d = 1 - h

    # Set w as the degree of importance of each criterion
    w = d / (np.sum(d))
    return w


# -*- coding: utf-8 -*-
"""Assignment-OR.ipynb

- **Transportation problems** deal with the movement of commodities from different sources to different destinations, with the overall objective of minimizing transportation costs

Prerequisites
- n = The number of sources.
- m = The number of destinations.
- supply = The total quantity available at each source.
- demand = The total quantity required at each destination.
- cost_mat = The cost of transportation of one unit of the commodity from each source to each destination.

## **INPUT**
"""

n = int(input("Number of supply centers : "))
supply = list(map(int,input("Availablity at each centers: ").strip().split()))[:n]
# supply = [150,160,90]
print(supply)

m = int(input("\nNumber of destination centers : "))
demand = list(map(int,input("Demand at each centers: ").strip().split()))[:m]
# demand = [140, 120, 90, 50]
print(demand)

import numpy as np
print("\nEnter the cost matrix : ")
cost_mat = np.array(list(map(int, input().split())) ).reshape(n, m)
# cost_mat = 16 18 21 12 17 19 14 13 32 11 15 10
print(cost_mat)

print( "Balanced Transportation problem" if sum(supply) == sum(demand) else "Unbalanced Transportation problem" )

"""## **North West Corner Method**

- Select North-West Corner cell

"""

print(supply)
print(demand)
print(cost_mat)

def north_west_corner(supply,demand):

  supply_c = supply.copy()
  demand_c = demand.copy()

  i,j = 0,0
  mat = []

  while len(mat) < len(supply) + len(demand) - 1 : # n + m - 1 constraints

    s = supply_c[i]
    d = demand_c[j]
    value = min(s,d)

    supply_c[i] -= value
    demand_c[j] -= value
    mat.append([(i,j),value])

    if supply_c[i] == 0 and i < len(supply)-1:
      i+=1
    if demand_c[j] == 0 and j < len(demand)-1:
      j+=1

  return mat

mat = north_west_corner(supply,demand)
print(mat)

cost = 0
for i in range(len(mat)):
  cost += ( cost_mat[ mat[i][0][0] ][ mat[i][0][1] ] * mat[i][1] )
print(cost)

"""## **Least Cost Method**

- Select the least value among all the costs
"""

print(supply)
print(demand)
print(cost_mat)

#Helper function which returns the minimum value alongwith its index

def get_min(c):

  i_min,j_min = 0,0
  minimum = 10**18
  maxx = 10**18

  for i in range(n):
    for j in range(m):
      if c[i][j] < minimum and c[i][j] is not maxx :
        i_min, j_min, minimum = i, j, c[i][j]

  return [i_min,j_min,minimum]

def least_cost(supply,demand,cost_mat):

  mat = []
  supply_c = supply.copy()
  demand_c = demand.copy()
  cost_mat_c = cost_mat.copy()

  i,j= 0,0
  maxx = 10**18

  while len(mat) < len(supply) + len(demand) - 1 :

    result = get_min(cost_mat_c) # Get the index of minimum value in the cost matrix

    i = result[0]
    j = result[1]
    cost = result[2]
    s = supply_c[i]
    d = demand_c[j]
    value = min(s,d)

    supply_c[i] -= value
    demand_c[j] -= value

    mat.append([(i,j), value, cost ])

    if supply_c[i] == 0:    #Mark that row
      for k in range(m):
        cost_mat_c[i][k] = maxx

    if demand_c[j] == 0:  #Mark that column
      for k in range(n):
        cost_mat_c[k][j] = maxx
  return mat

mat = least_cost(supply,demand,cost_mat)
print(mat)

cost = 0
for i in range(len(mat)):
    cost += ( mat[i][1] * mat[i][2] )
print(cost)

"""## **Vogel’s Approximation Method**

- Find in the differences between the two lowest costs in each row and column. These are known as penalties/ extra costs.
- Find the maximum value among the penalties
- Look into the respective row or column accordingly and select the least value among all the costs
"""

print(supply)
print(demand)
print(cost_mat)

def calculate_row_penalty(c):
  maxx = 10**18
  r_penalty = []
  for i in range(n):
    # first, second = maxx, maxx
    a = []
    for j in range(m):
    	a.append(c[i][j])
    a.sort()
    first, second = maxx, maxx
    if a[0]==first:
    	first=0
    else:
    	first=a[0]
    if a[1]==second:
    	second=0
    else:
    	second=a[1]
    r_penalty.append(abs(second-first))
      # if c[i][j] < first and c[i][j] != maxx:
      #   second = first
      #   first = c[i][j]
      # elif ( c[i][j] < second and c[i][j] != first and c[i][j] != maxx  ):
      #   second = c[i][j]

    # if second == first and first == maxx :
    #   r_penalty.append(-maxx)
    # else:
    #   r_penalty.append(second-first)

  return r_penalty

def calculate_col_penalty(c):
  maxx = 10**18
  c_penalty = []

  for j in range(m):
    # first, second = maxx, maxx
    a = []
    for i in range(n):
    	a.append(c[i][j])
    a.sort()
    # print(a)
    first, second = maxx, maxx
    if a[0]==first:
    	first=0
    else:
    	first=a[0]
    if a[1]==second:
    	second=0
    else:
    	second=a[1]
    c_penalty.append(abs(second-first))

  # for j in range(m):
  #   first, second = maxx, maxx
  #   for i in range(n):
  #     if c[i][j] < first and c[i][j] != maxx:
  #       second = first
  #       first = c[i][j]
  #     elif ( c[i][j] < second and c[i][j] != first and c[i][j] != maxx ):
  #       second = c[i][j]

  #   if second == first and first == maxx :
  #     c_penalty.append(-maxx)
  #   else:
  #     c_penalty.append(second-first)

  return c_penalty

def vogels_approx(supply,demand,cost_mat):

  supply_c = supply.copy()
  demand_c = demand.copy()
  cost_mat_c = cost_mat.copy()

  row_penalty = []
  col_penalty = []
  mat = []

  maxx = 10**18

  while len(mat) < len(supply) + len(demand) - 1 :

    row_penalty = calculate_row_penalty(cost_mat_c)
    col_penalty = calculate_col_penalty(cost_mat_c)

    row_max = max(row_penalty)
    col_max = max(col_penalty)

    i_index, j_index = 0, 0
    s,d,value = [],[],0

    if col_max > row_max:

      #Get the column index
      col_index = col_penalty.index(col_max)
      minimum = 10**18

      #Select minimum from the column
      for i in range(n):
        if cost_mat_c[i][col_index] < minimum:
          minimum = cost_mat_c[i][col_index]
          i_index = i
          j_index = col_index

    else:

      #Get the row index
      row_index = row_penalty.index(row_max)
      minimum = 10**18

      #Select minimum from the row
      for j in range(m):
        if cost_mat_c[row_index][j] < minimum :
          minimum = cost_mat_c[row_index][j]
          i_index = row_index
          j_index = j

    #Check respective supply and demand at index of minimum value
    s = supply_c[i_index]
    d = demand_c[j_index]

    value = min(s,d)
    supply_c[i_index] -= value
    demand_c[j_index] -= value

    mat.append([(i_index,j_index),value])

    # If supply is 0 cross out the particular row
    if supply_c[i_index] == 0 :
      for j in range(m):
        cost_mat_c[i_index][j] = maxx

    # Else if demand is 0 cross out the particular column
    elif demand_c[j_index] == 0 :
      for i in range(n):
        cost_mat_c[i][j_index] = maxx

  return mat

mat = vogels_approx(supply,demand,cost_mat)
print(mat)

cost = 0
for i in range(len(mat)):
  cost += ( cost_mat[ mat[i][0][0] ][ mat[i][0][1] ] * mat[i][1] )
print(cost)


import numpy as np

def optimal_bst(e, n):
	'''
	Computes the optimal binary search tree with minimal cost, based
	on estimates of access through of each element given by the array e.
	n is the size of e array. We sum up 1 to this parameter to keep
	the algorithm like the original.
	Pay attention to the dimensions of the c matrix and some indices
	that differs from original.

	Algorithm by Cris Fernandes lectures - IME-USP
	'''
	n += 1 # we are gonna need one extra line/column
	c = [[-1 for j in range(n)] for i in range(n+1)] # initializing the c matrix of size n+1 x n
	t = [[-1 for j in range(n)] for i in range(n)] # initializing the t matrix of size n x n which keep track of the optimal BST strucutre
	s = [None] * (n) # keep the sum of acces at e[1..j]
	s[0] = 0

	for i in range(1, n):
		s[i] = s[i-1] + e[i-1]

	for i in range(1, n+1):
		c[i][i-1] = 0

	for l in range(1, n):
		for i in range(1, n-l+1):
			j = i+l-1
			c[i][j] = c[i+1][j]

			k = i+1
			t[i][j] = k-1
			while k <= j:
				x = c[i][k-1] + c[k+1][j]
				if x < c[i][j]:
					c[i][j] = x
					t[i][j] = k-1
				k += 1

			c[i][j] = c[i][j] + s[j] - s[i-1]

	return s, c, t

def construct_optimal_bst(root, i, j, parent):
    '''
    Prints out the optimal BST structure given by the root matrix.
    Result of exercise 15.5-1 CLRS 2ed.
    '''
    if j == i-1:
        print 'd' + str(j),
        if j < parent:
            print 'left child of k'+ str(parent)
        else:
            print 'right child of k'+ str(parent)
        return

    r = root[i][j]
    print 'k' + str(r),
    if parent is None:
        print 'is the root'
    elif r < parent:
        print 'is left child of k' + str(parent)
    else:
        print 'is right child of k'+ str(parent)
    construct_optimal_bst(root, i, r-1, r)
    construct_optimal_bst(root, r+1, j, r)

def main():
	e = [10,20,30,15,30]
	print
	print '==============================================='
	print 'Optimal binary search tree'
	print '==============================================='
	print 'e = ', e
	s, c, t = optimal_bst(e, len(e))
	print 's = ', s

	c = np.array(c)
	print 'c = '
	print c
	print 'tree = '
	print np.array(t)
	h,w = c.shape

	print 'Optimal BST has cost ', c[1][w-1]

	construct_optimal_bst(t, 1, len(e), None)

if __name__=="__main__":
    main()


import numpy as np

# Write a function that takes as input a list of numbers, and returns
# the list of values given by the softmax function.
def softmax(L):
    expL = np.exp(L)
    sumExpL = sum(expL)

    results = []
    for i in expL:
        results.append(1.0*i/sumExpL)

    return results

# Note: The function np.divide can also be used here, as follows:
# def softmax(L):
#     expL = np.exp(L)
#     return np.divide (expL, expL.sum())

Udacity/PyTorch Scholarship Challenge Nanodegree Program/Lesson-2-Introduction to Neural Networks/SoftMax/softmax.py


#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time     : 2018/10/04 14:23
# @Author   : Iydon
# @File     : course3.5.py

import numpy as np
from Poly import *
import matplotlib.pyplot as plt

def natural_cubic_spline(xs:list, fxs:list, display:bool=False):
    """
    Cubic spline interpolation.
    """
    n  = len(xs)
    hs = [xs[i+1]-xs[i] for i in range(n-1)]
    A  = np.diag([0]+hs[1:],1) + np.diag(hs[:-1]+[0],-1)
    A += np.diag([1]+[2*(hs[i+1]+hs[i]) for i in range(n-2)]+[1])
    bs = [0]+[3/hs[i+1]*(fxs[i+2]-fxs[i+1])-3/hs[i]*(fxs[i+1]-fxs[i]) for i in range(n-2)]+[0]
    # a, b, c, d: end with 'x'.
    cx = [i[0] for i in (np.linalg.inv(A) * np.matrix(bs).transpose()).tolist()]
    bx = [1/hs[i]*(fxs[i+1]-fxs[i])-hs[i]/3*(2*cx[i]+cx[i+1]) for i in range(n-1)]
    dx = [1/3/hs[i]*(cx[i+1]-cx[i]) for i in range(n-1)]
    # S_i(x)
    Ss = [fxs[i]+bx[i]*Poly([1,-xs[i]])+cx[i]*Poly([1,-xs[i]])**2+dx[i]*Poly([1,-xs[i]])**3 for i in range(n-1)]
    if display: print(fxs, bx, cx, dx, sep="\n\n\n")
    return Ss

def clamped_cubic_spline(xs:list, fxs:list, boundray:list=[0,0]):
    """
    Cubic spline interpolation.
    """
    n  = len(xs)
    hs = [xs[i+1]-xs[i] for i in range(n-1)]
    A  = np.diag(hs,1) + np.diag(hs,-1)
    A += np.diag([2*hs[0]]+[2*(hs[i+1]+hs[i]) for i in range(n-2)]+[2*hs[-1]])
    head = [3/hs[0]*(fxs[1]-fxs[0]) - 3*boundray[0]]
    tail = [3*boundray[-1] - 3/hs[-1]*(fxs[-1]-fxs[-2])]
    bs = head+[3/hs[i+1]*(fxs[i+2]-fxs[i+1])-3/hs[i]*(fxs[i+1]-fxs[i]) for i in range(n-2)]+tail
    # a, b, c, d: end with 'x'.
    cx = [i[0] for i in (np.linalg.inv(A) * np.matrix(bs).transpose()).tolist()]
    bx = [1/hs[i]*(fxs[i+1]-fxs[i])-hs[i]/3*(2*cx[i]+cx[i+1]) for i in range(n-1)]
    dx = [1/3/hs[i]*(cx[i+1]-cx[i]) for i in range(n-1)]
    # S_i(x)
    Ss = [fxs[i]+bx[i]*Poly([1,-xs[i]])+cx[i]*Poly([1,-xs[i]])**2+dx[i]*Poly([1,-xs[i]])**3 for i in range(n-1)]
    return Ss

def cubic_spline_lambdify(S:str, xs:list):
    """
    Lambdify the cubic spline function.
    """
    f = ["%s[%d].lambdify()(x)*(%s<=x<%s)"%(S, i, xs[i], xs[i+1]) for i in range(len(xs)-1)]
    return eval("lambda x: %s"%"+".join(f))

xs  = [0.9,1.3,1.9,2.1,2.6,3.0,3.9,4.4,4.7,5.0,6.0,7.0,8.0,9.2,10.5,11.3,11.6,12.0,12.6,13.0,13.3]
fxs = [1.3,1.5,1.85,2.1,2.6,2.7,2.4,2.15,2.05,2.1,2.25,2.3,2.25,1.95,1.4,0.9,0.7,0.6,0.5,0.4,0.25]

S = natural_cubic_spline(xs, fxs)

f = cubic_spline_lambdify("S", xs)
plt.plot(xs, fxs, marker="*", color="orange")

x = np.linspace(0.9, 13.29, 100)
y = [f(x) for x in x]
plt.plot(x, y, color="blue")
plt.axis("equal")
plt.grid()
plt.show()


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

import numpy as np

# Note: careful as np.multiply does an elementwise multiply on numpy arrays
#       asterisk (*) does the same but will perfom matrix multiplication on mat (numpy matrices)

class L1Regularization:

    """
    **Lasso Regression (L1Regularization)**

    L1Regularization adds sum  of the absolute value magnitudes of parameters as
    penalty term to the loss function

    References:
        [1] Regularization (mathematics)
            * [Wikipedia Article] https://en.wikipedia.org/wiki/Regularization_(mathematics)

        [2] Regression shrinkage and selection via the lasso
            * [R Tibshirani, 1996] https://goo.gl/Yh9bBU
            * [PDF] https://goo.gl/mQP5mA

        [3] Feature selection, L1 vs. L2 regularization, and rotational invariance
            * [Andrew Y. Ng, ] [PDF] https://goo.gl/rbwNCt

    Args:
        _lambda  (float32): controls the weight of the penalty term
    """

    def __init__(self, _lambda, **kwargs):
        self._lambda = _lambda

    def regulate(self, weights):
        return np.multiply(self._lambda, np.linalg.norm(weights))

    def derivative(self, weights):
        return np.multiply(self._lambda, np.sign(weights))

    @property
    def regulation_name(self):
        return self.__class__.__name__

class L2Regularization:

    """
    **Lasso Regression (L2Regularization)**

    L1Regularization adds sum of the squared magnitudes of parameters as penalty
    term to the loss function

    References:
        [1] Regularization (mathematics)
            * [Wikipedia Article] https://en.wikipedia.org/wiki/Regularization_(mathematics)

        [2] Regression shrinkage and selection via the lasso
            * [R Tibshirani, 1996] https://goo.gl/Yh9bBU
            * [PDF] https://goo.gl/mQP5mA

        [3] Feature selection, L1 vs. L2 regularization, and rotational invariance
            * [Andrew Y. Ng, ] [PDF] https://goo.gl/rbwNCt

    Args:
        _lambda (float32): controls the weight of the penalty term
    """

    def __init__(self, _lambda, **kwargs):
        self._lambda = _lambda

    def regulate(self, weights):
        return np.multiply(self._lambda, (0.5 * weights.T.dot(weights)))

    def derivative(self, weights):
        return np.multiply(self._lambda, weights)

    @property
    def regulation_name(self):
        return self.__class__.__name__

class ElasticNetRegularization:

    """
    **Elastic Net Regularization (ElasticNetRegularization)**

    ElasticNetRegularization  adds both absolute  value of magnitude and squared
    magnitude of coefficient as penalty term to the loss function

    References:
        [1] Regularization (mathematics)
            * [Wikipedia Article] https://en.wikipedia.org/wiki/Regularization_(mathematics)

    Args:
        _lambda  (float32): controls the weight of the penalty term
        l1_ratio (float32): controls the value l1 penalty as a ratio of total penalty added to the loss function
    """

    def __init__(self, _lambda, l1_ratio):
        self._lambda  = _lambda
        self.l1_ratio = l1_ratio

    def regulate(self, weights):
        return np.multiply(self._lambda, (((self.l1_ratio * 0.5) * weights.T.dot(weights)) + ((1 - self.l1_ratio) * np.linalg.norm(weights))))

    def derivative(self, weights):
        return np.multiply(self._lambda, (((self.l1_ratio * 0.5) * weights) + ((1 - self.l1_ratio) *  np.sign(weights))))

    @property
    def regulation_name(self):
        return self.__class__.__name__

class RegularizationFunction:

    _regularizers = {
        'l1'          : L1Regularization,
        'lasso'       : L1Regularization,
        'l2'          : L2Regularization,
        'ridge'       : L2Regularization,
        'elastic'     : ElasticNetRegularization,
        'elastic_net' : ElasticNetRegularization
    }

    def __init__(self, name = 'lasso', _lambda = 0.5, l1_ratio = 0.5):
        if name not in self._regularizers.keys():
            raise Exception('Regularization function must be either one of the following: {}.'.format(', '.join(self._regularizers.keys())))
        self.regularization_func = self._regularizers[name](_lambda, l1_ratio = l1_ratio)

    @property
    def name(self):
        return self.regularization_func.regularization_name

    def regulate(self, weights):
        return self.regularization_func.regulate(weights)

    def derivative(self, weights):
        return self.regularization_func.derivative(weights)


import numpy as np
import matplotlib.pyplot as plt
from matplotlib import ticker, cm

def bivariate_norm_distr(X,Y):
    """
    Evaluates on a bivariate gaussian distribution
    :param X: ndarray (pass heights)
    :param Y: ndarray (pass weights)
    :return: ndarray of probabilities for (x,y) in zip(X,Y)
    """
    std_x = std_h
    std_y = std_w
    c = np.sqrt(1 - rho ** 2) * 2 * np.pi * std_x * std_y
    c = 1 / c
    ret = []
    for x, y in zip(X,Y):

        q = ((y/std_y)**2)/2 + (((x - rho*(std_x/std_y)*y)**2)/(2*(1-rho**2)*std_x**2))
        ret.append(c*np.exp(-q))
    return np.array(ret)

def biv_norm_condn_exp(X):
    """
    Calculates E[Y|x0] for a bivariate gaussian distribution
    :param X: Values to be evaluated
    :return: ndarray of condiitional expectations
    """
    std_x = std_h
    std_y = std_w
    mu_x = mu_h
    mu_y = mu_w
    # print(X)
    def f(x):
        return mu_y + rho*(std_y/std_h)*(x-mu_x)
    E_y_x = np.apply_along_axis(f, 0, X)
    return E_y_x

if __name__ == "__main__":
    # read data as 2D array of data type 'object'
    data = np.loadtxt('whData.dat', dtype=np.object, comments='#', delimiter=None)
    # Removing rows with missing weights

    data_clean = data[data[:, 0] != '-1', :]

    # read weight data into 1D array (i.e. into a vector)
    W = data_clean[:, 0].astype(np.float)

    # read height data into 1D array (i.e. into a matrix)
    H = data_clean[:, 1].astype(np.float)

    rho = np.corrcoef(H, W)[0,1]

    std_h = np.std(H)
    std_w = np.std(W)

    mu_h = np.mean(H)
    mu_w = np.mean(W)

    print("Parameters of Bivariate Gaussian Distribution")
    print("Correlation={}, std_X = {}, std_W = {}, mu_X = {}, mu_Y={}".format(rho, std_h, std_w, mu_h, mu_w))

    fig, ax = plt.subplots()

    # Creating contour representing distribution
    # Note: Joint distribution formulas require zero mean
    N = 1000
    x_h = np.linspace(140, 200, N)
    y_w = np.linspace(40, 100, N)

    H, W = np.meshgrid(x_h, y_w)

    W = (W - mu_w)# / std_w
    H = (H - mu_h)# / std_h

    Z = bivariate_norm_distr(H, W)
    # Z = (Z-Z.mean())/(Z.std()+1e-18)
    # print(np.max(Z))

    W = (W + mu_w)
    H = H + mu_h
    cs = ax.contourf(H, W, Z, cmap='viridis', alpha=0.6)
    cbar = fig.colorbar(cs)
    plt.ylabel("Weight")
    plt.xlabel("Height")

    # Plotting the data and missing values later to have proper visualization
    # read weight data into 1D array (i.e. into a vector)
    W = data_clean[:, 0].astype(np.float)

    # read height data into 1D array (i.e. into a matrix)
    H = data_clean[:, 1].astype(np.float)

    ax.scatter(H,W, color='black', label='data')

    # Formula for conditional expectation seems to not require zero-mean values
    missing_H = data[data[:, 0] == '-1', 1].astype(np.float)
    missing_W = biv_norm_condn_exp(missing_H)

    ax.scatter(missing_H,missing_W, color='red', label='missing')

    print("Missing Data Prediction:")
    print("Weights and Heights:")
    for i in zip(missing_W, missing_H):
        print(i)

    plt.legend()
    plt.title("Bivariate Gaussian Distribution with missing value prediction")
    plt.savefig("biv_gaussian.pdf", facecolor='w', edgecolor='w',
                papertype=None, format='pdf', transparent=False,
                bbox_inches='tight', pad_inches=0.1)

    plt.show()


import numpy as np

# 1-d cdf9-7 wavelet transform
def cdf97(row_vector):
    l = row_vector.shape[0]
    low_filter = [0.026748, -0.016864, -0.078223, 0.26686, 0.6029, 0.26686, -0.078223, -0.016864, 0.026748]
    high_filter = [0.04564, -0.02877, -0.29564, 0.55754, -0.29564, -0.02877, 0.04564]
    # add [0, 0, 0] to the begining and the end of the vector
    tmp_vector = np.insert(row_vector, 0, [0, 0, 0])
    row_vector = np.insert(tmp_vector, tmp_vector.shape[0], [0, 0, 0])
    low_vector = np.convolve(row_vector, low_filter, mode="same")
    high_vector = np.convolve(row_vector, high_filter, mode="same")
    return (low_vector, high_vector)

# 1-d inverse cdf9-7 wavelet transform
def inverse_cdf97(low_vector, high_vector):
    low_filter = [0.026748, -0.016864, -0.078223, 0.26686, 0.6029, 0.26686, -0.078223, -0.016864, 0.026748]
    high_filter = [0.04564, -0.02877, -0.29564, 0.55754, -0.29564, -0.02877, 0.04564]
    lpr = np.multiply(high_filter, [-1, 1, -1, 1, -1, 1, -1]) * 2
    hpr = np.multiply(low_filter, [1, -1, 1, -1, 1, -1, 1, -1, 1]) * 2
    l = low_vector.shape[0] + high_vector.shape[0]
    tmp_low_vector = np.zeros((l))
    tmp_low_vector[::2] = low_vector
    tmp_high_vector = np.zeros((l))
    tmp_high_vector[1::2] = high_vector
    return np.convolve(tmp_low_vector, lpr, mode="same") + np.convolve(tmp_high_vector, hpr, mode="same")

# apply 1-d cdf9-7 wavelet transform to each row of a matrix
def cdf97_matrix(img_matrix):
    low_pass = None
    high_pass = None
    (h, _) = img_matrix.shape
    for i in range(h):
        (low_vector, high_vector) = cdf97(img_matrix[i, :])
        # for testing
        # tmp_row_vector = inverse_cdf97(low_vector[::2], high_vector[1::2])
        # print((tmp_row_vector[3:-3] - img_matrix[i, :]).max(), (tmp_row_vector[3:-3] - img_matrix[i, :]).min())
        if i == 0:
            low_pass = low_vector
            high_pass = high_vector
        else:
            low_pass = np.vstack([low_pass, low_vector])
            high_pass = np.vstack([high_pass, high_vector])
    return np.hstack([low_pass[:, ::2], high_pass[:, 1::2]])

# apply 1-d inverse cdf9-7 wavelet transform to each row of a matrix
def inverse_cdf97_matrix(wavelet_img):
    img_matrix = None
    (h, w) = wavelet_img.shape
    for i in range(h):
        row = inverse_cdf97(wavelet_img[i, 0:int(w/2)], wavelet_img[i, int(w/2):])
        if i ==0:
            img_matrix = row
        else:
            img_matrix = np.vstack([img_matrix, row])
    return img_matrix

# 2-d cdf9-7 wavelet transform
def cdf97_2d(img_matrix):
    # apply the wavelet transform to rows of a matrix
    img_1d = cdf97_matrix(img_matrix)
    # then to columns
    img_1d = img_1d.T
    img_2d = cdf97_matrix(img_1d)
    return img_2d.T

# 2-d inverse cdf9-7 wavelet transform
def inverse_cdf97_2d(wavelet_img):
    # apply the wavelet transform to columns
    img_1d = inverse_cdf97_matrix(wavelet_img.T)
    # then to rows
    img_1d = img_1d.T
    img_2d = inverse_cdf97_matrix(img_1d)
    return img_2d


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

def logdet(Sigma):
    return np.log2(Sigma)

def gaussProb(X, mu, Sigma):
    d = 1
    X = X.reshape(X.shape[0], d)
    X = X - np.transpose(mu)
    logp = -0.5*np.sum(np.multiply((X/(Sigma)), X), 1)
    logZ = (d/2)*np.log(2*np.pi) + 0.5*logdet(Sigma)
    logp = logp - logZ
    p = np.exp(logp)
    return p

def postDensityIntervals():
    def f(x): return gaussProb(x, 0, 1) + gaussProb(x, 6, 1)
    domain = np.arange(-4, 10.001, 0.001)
    plt.plot(domain, f(domain), '-r')
    plt.fill_between(domain, f(domain), color='blue')
    plt.fill_between(np.arange(-4, -1.999, 0.001),
                     f(np.arange(-4, -1.999, 0.001)), color='white')
    plt.fill_between(np.arange(8, 10.001, 0.001), f(
        np.arange(8, 10.001, 0.001)), color='white')
    plt.annotate(r'$\alpha /2$', xytext=(-3.5, 0.11), xy=(-2.3, 0.015),
                 arrowprops=dict(facecolor='black'))
    plt.annotate(r'$\alpha /2$', xytext=(9.5, 0.11), xy=(8.3, 0.015),
                 arrowprops=dict(facecolor='black'))
    plt.ylim(0, 0.5)
    plt.savefig('../figures/centralInterval')
    plt.show()

    plt.plot(domain, f(domain), '-r')
    plt.fill_between(domain, f(domain), color='blue')
    plt.fill_between(np.arange(-4, -1.43992, 0.001),
                     f(np.arange(-4, -1.43992, 0.001)), color='white')
    plt.fill_between(np.arange(7.37782, 10.001, 0.001), f(
        np.arange(7.37782, 10.001, 0.001)), color='white')
    plt.plot(domain, [0.15 for i in range(0, 14001)], 'b-')
    plt.fill_between(np.arange(1.3544, 4.5837, 0.001), f(
        np.arange(1.3544, 4.5837, 0.001)), color='white')
    plt.yticks([0.15], ["pMIN"])
    plt.ylim(0, 0.5)
    plt.savefig('../figures/HPD')

    plt.show()

def main():
    postDensityIntervals()

if __name__ == '__main__':
    if os.path.isdir('scripts'):
        os.chdir('scripts')
    main()


import matplotlib.pyplot as plt
import numpy as np
from statistics import stdev, mean
from math import sqrt

expected = [3.4, 3.1, 3.0, 2.8, 3.7,
            3.5, 2.9, 3.7, 3.5, 3.2,
            3.0, 3.5, 3.3, 3.1, 3.3,
            3.9, 2.9, 3.2, 3.4, 3.4]

real = [4.1, 3.4, 3.3, 3.0, 4.7,
        4.6, 3.0, 4.6, 4.6, 3.6,
        3.5, 4.0, 3.6, 3.1, 3.3,
        4.5, 2.8, 3.7, 3.8, 3.9]

# 1) построить диаграмму рассеяния
# (x – ожидаемая, y – полученная оценка),
#  добавить линию тренда (использовать линейную функцию)
def build_diagram(x, y):
    z = np.polyfit(x, y, 1)
    p = np.poly1d(z)
    plt.scatter(x, y)
    plt.plot(x, p(x), color='orange')
    plt.grid(True)
    plt.title('диаграммa рассеяния')
    plt.show()

# 2) рассчитайте линейный коэффициент корреляции Пирсона,
#  оцените его значимость при α=0,05;

def koef_pirsona(x, y):
    # x,y - list of values
    avg_x = mean(x)
    avg_y = mean(y)
    std_x = stdev(x)
    std_y = stdev(y)
    avg_xy = mean([i*j for i, j in zip(x, y)])
    result = (avg_xy - avg_x * avg_y) / (std_x * std_y)
    return result

# 3) проверить гипотезу о равенстве средних значений этих
#  двух наборов данных (ожидаемая и получаемая оценки)
# с помощью t-критерия Стьюдента для связных выборок

def t_test(x, y):
    # средняя разность значений
    avg_d = mean(x) - mean(y)
    # cтандартное отклонение разностей
    std_d = stdev(x) - stdev(y)
    # количество наблюдений
    n = 20
    t = avg_d / (std_d / sqrt(n))
    return t

k_pirsona = koef_pirsona(expected, real)
t_test = t_test(expected, real)
print('коэффициент корреляции Пирсона: {}\nt-критерия Стьюдента: {}'.format(k_pirsona, t_test))
build_diagram(expected, real)


import numpy as np
from collections import defaultdict

def feasible_newtonLS(F, ls, alpha0, x0, tol, max_iter):
    """
    Feasible Newton method: Newton method which starts at a feasible point
    and subsequently enforces the equality constraints on the step maintaining feasibility.
    Reference: Algorithm 10.1 from Boyd, Convex Optimization.

    Parameters:
    -----------
    F : `function`
        Objective function.
    ls : `function`
        Specifies line search algorithm.
    alpha0 : `float`
        initial step length.
    x0 : `numpy.ndarray`
        (d,1) Feasible initial iterate.
    tol : `float`
        Stopping condition on minimal allowed step.
    max_iter : `int`
        Maximum number of iterations.

    Returns:
    --------
    x_min : `numpy.ndarray`
        Minimum of objective function F
    f_min : `float`
        Value of minimum objective function F
    nIter : `int`
        Number of iterations.
    info : `dict` of `list`
        Information about iteration.
    """
    # Initialisation
    nIter = 0
    x_k  = x0
    d    = x0.shape[0]
    info = defaultdict(list)
    info['xs'].append(x0)
    info['alphas'].append(alpha0)

    # Loop until convergence or maximum number of iterations
    while nIter < max_iter:
        # Increment iterations
        nIter += 1
        # Compute descent direction
        try:
            # Exact method
            p_k = -np.linalg.solve(F.d2f(x_k), F.df(x_k))
        except:
            # Approximate
            p_k = -np.linalg.pinv(F.d2f(x_k))@F.df(x_k)
        if p_k.T@F.df(x_k)>0:
            # Force to be descent direction (only active if F.d2f(x_k) not pos.def.)
            p_k = -p_k
        # Get Newton step
        delta_x_k = p_k[:d,:]

        # Compute Newton decrement
        lambda_k = np.squeeze(delta_x_k.T@F.d2f(x_k)[:d,:d]@delta_x_k)**0.5
        # Stopping condition
        if 0.5*lambda_k**(2) <= tol:
            break

        # Call line search given by handle ls for computing step length
        alpha_k, infoLS = ls(F, x_k, delta_x_k, alpha0)
        # Update x_k
        x_k = x_k + alpha_k*delta_x_k

        # Store iteration info
        info['xs'].append(x_k)
        info['alphas'].append(alpha_k)

    return x_k, F.f(x_k), nIter, info

def descentLS(F, descent, ls, alpha0, x0, tol, max_iter, stop_type):
    """
    Wrapper function executing  descent with line search.
    Descent Methods:
    1. Steepest descent direction
    2. Newton direction

    Parameters:
    -----------
    F : `function`
        Objective function.
    descent : `str`
        Specifies descent direction {'steepest', 'newton'}
    ls : `function`
        Specifies line search algorithm.
    alpha0 : `float`
        initial step length.
    x0 : `numpy.ndarray`
        Initial iterate.
    tol : `float`
        Stopping condition on minimal allowed step.
    max_iter : `int`
        Maximum number of iterations.
    stop_type : `str`
        The stopping condition {'step','grad'}

    Returns:
    --------
    x_min : `numpy.ndarray`
        Minimum of objective function F
    f_min : `float`
        Value of minimum objective function F
    nIter : `int`
        Number of iterations.
    info : `dict` of `list`
        Information about iteration.
    """
    # Initialisation
    nIter = 0
    x_k = x0
    stopCond = False

    info = defaultdict(list)
    info['xs'].append(x0)
    info['alphas'].append(alpha0)

    # Loop until convergence or maximum number of iterations
    while stopCond is False:
        # Increment iterations
        nIter += 1

        # Compute descent direction
        if descent == "steepest":
            p_k = -F.df(x_k) # steepest descent direction
        elif descent == "newton":
            p_k = -np.linalg.pinv(F.d2f(x_k))@F.df(x_k) # Newton direction
            if p_k.T@F.df(x_k)>0:
                # Force to be descent direction (only active if F.d2f(x_k) not pos.def.)
                p_k = -p_k

        # Call line search given by handle ls for computing step length
        alpha_k = ls(x_k, p_k, alpha0)

        # Update x_k and f_k
        x_k_1 = x_k
        x_k = x_k + alpha_k*p_k

        # Store iteration info
        info['xs'].append(x_k)
        info['alphas'].append(alpha_k)

        if stop_type == 'step':
            # Compute relative step length
            norm_step = np.linalg.norm(x_k - x_k_1) / np.linalg.norm(x_k_1)
            stopCond = (normStep < tol)
        elif stop_type == 'grad':
            stopCond = (np.linalg.norm(F.df(x_k)) <= tol)

    return x_k, F.f(x_k), nIter, info


# This import registers the 3D projection, but is otherwise unused.
from mpl_toolkits.mplot3d import Axes3D  # noqa: F401 unused import

import matplotlib.pyplot as plt
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FormatStrFormatter
import numpy as np

fig = plt.figure()
ax = fig.gca(projection='3d')
plt.title("Tempurature Distribution across x, y")
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('T(x, y)\xb0C')

# Make data.
X = np.arange(0, 1.1, 0.01)
Y = np.arange(0, 1.1, 0.01)
X, Y = np.meshgrid(X, Y)
K = .5
#Z = np.cos(np.sqrt(X**2 + Y**2))
Z = 100 * np.sinh(K*np.pi*Y)*np.sin(np.pi*X) / np.sinh(K*np.pi)
print(Z)
print("Analytical outputs:")
print(X)
print(Y)
print(Z)

# Plot the surface.
surf = ax.plot_surface(X, Y, Z, cmap=cm.inferno,
                       linewidth=0, antialiased=False)
# Customize the z axis.
ax.set_zlim(0, 100)
ax.zaxis.set_major_locator(LinearLocator(10))
ax.zaxis.set_major_formatter(FormatStrFormatter('%.02f'))

# Add a color bar which maps values to colors.
fig.colorbar(surf, shrink=0.5, aspect=5)
plt.show()

K = 2

def analyticalHeatFunction(x, y):
    return 100 * np.sinh(K*np.pi*y)*np.sin(np.pi*x) / np.sinh(K*np.pi)

X = np.arange(0, 1, 0.01)
Y = np.arange(0, 1, 0.01)
X, Y = np.meshgrid(X, Y)
#Z = np.cos(np.sqrt(X**2 + Y**2))
Z = analyticalHeatFunction(X, Y)
fig,ax=plt.subplots(1,1)
cp = ax.contour(X, Y, Z, 12)
fig.colorbar(cp) # Add a colorbar to a plot
ax.clabel(cp, inline=1, fontsize=10)
ax.set_title('Contours Plot Temperature across infinite plate T(x, y)\xb0C')
ax.set_xlabel('X')
ax.set_ylabel('Y')
plt.show()

fig = plt.figure()
ax = fig.gca(projection='3d')
plt.title("Tempurature Distribution across x, y")
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('T(x, y)\xb0C')

print(analyticalHeatFunction(1-.125, .125))


'''
Module for estimate analog noise on the images.

Contains implementation of paper:
J. Immerkær, “Fast Noise Variance Estimation”, Computer Vision and Image
Understanding, Vol. 64, No. 2, pp. 300-302, Sep. 1996

You can estimate noise in whole image (as in original idea) just using
@estimate_noise function or estimate noise in small boxes separately using
@estimate_in_boxes and next merge estimations into single result (for example
using @rating function).
merge
'''
import math

import numpy as np
from numpy.fft  import fft2, ifft2

def np_fftconvolve(A, B):
    '''
    Convolve 2D implementation in Numpy. Doesn't produce the same result
    as scipy.signal.convolve2d function, but results are close (<1% difference).
    See: https://laurentperrinet.github.io/sciblog/posts/2017-09-20-the-fastest-2d-convolution-in-the-world.html
    As described above it should be fastest method (in 2017), but in my opinion
    it is slower then Scipy solution.
    '''
    return np.real(ifft2(fft2(A)*fft2(B, s=A.shape)))

def estimate_noise(I, L, conv2d=np_fftconvolve):
    '''
    Estimate the variance of additive zero mean Gaussian noise .
    See: https://stackoverflow.com/a/25436112
    Reference: J. Immerkær, “Fast Noise Variance Estimation”, Computer Vision
        and Image Understanding, Vol. 64, No. 2, pp. 300-302, Sep. 1996

    Parameters
    ==========
    I : 2D array
        Input, greyscale image
    L : List of list of integers
        Mask, should be smaller then image (best 3x3)
    conv2d : function
        Convolve 2D implementation to use. Accept 2D array and 2D mask,
        returns new 2D array.
    '''
    L = np.array(L)
    H, W = I.shape
    Lh, Lw = L.shape
    LhalfH = math.ceil(Lh / 2)
    LhalfW = math.ceil(Lw / 2)

    matrix_factor = math.sqrt(np.sum(np.power(L.flatten(), 2)))
    sigma = np.sum(np.sum(np.absolute(conv2d(I, L))))
    sigma = sigma * math.sqrt(0.5 * math.pi) / (matrix_factor * (W-LhalfW) * (H-LhalfH))

    return sigma

def estimate_in_boxes(I, L, conv2d=np_fftconvolve, box=25):
    '''
    Split input I image and boxes with @box size (in pixels) and perform
    estimation in each.
    Returns list of dictionaries with 'row' and 'col'  coordinates of left, upper
    pixel of box and 'noise' with estimated noise sigma value (sigma from Gauss
    distribution of noise).
    '''
    rows, cols = I.shape
    results = []
    for rowIdx in range(0, rows, box):
        for colIdx in range(0, cols, box):
            subimage = I[rowIdx:rowIdx+box, colIdx:colIdx+box]
            noise = estimate_noise(subimage, L, conv2d)
            results.append({
                'row': rowIdx,
                'col': colIdx,
                'noise': noise
            })
    return results


from collections import namedtuple
import pandas as pd
import numpy as np
from scipy.stats import norm

class FlexibleProbabilities(object):
    """
    Flexible Probabilities
    """
    def __init__(self, data):
        self.x = data
        self.p = np.ones(len(data))/len(data)

    def shape(self):
        return self.x.shape

    def mean(self):
        """
        Sample mean with flexible probabilities
        """
        return np.dot(self.p, self.x)

    def cov(self):
        """
        Sample covariance with flexible probabilities
        """
        x_ = self.x - np.mean(self.x, axis=0)
        return np.dot(np.multiply(np.transpose(x_), self.p), x_)

    def equal_weight(self):
        """
        Equally weighted probabilities
        """
        self.p = np.ones(len(self.x))/len(self.x)

    def exponential_decay(self, tau):
        """
        Exponentail decay probabilities
        """
        t_ = len(self.x)
        self.p = np.exp(-np.log(2)/tau*(t_-np.arange(0,t_)))
        self.p = self.p /np.sum(self.p)

    def smooth_kernel(self, z=None, z_star=None, h=None, gamma=2):
        """
        Smooth kernel probabilities
        """
        if z is None:
            z = self.x[:,0]

        if z_star is None:
            z_star = np.mean(z)

        if h is None:
            h = np.std(z)

        self.p = np.exp(-(np.abs(z - z_star)/h)**gamma)
        self.p = self.p /np.sum(self.p)

    def effective_scenarios(self, Type=None):
        """
        This def computes the Effective Number of Scenarios of Flexible
        Probabilities via different types of defs

        INPUTS
        Type    : [struct] type of def: 'ExpEntropy', 'GenExpEntropy'

        OUTPUTS
        ens     : [scalar] Effective Number of Scenarios

        NOTE
        The exponential of the entropy is set as default, otherwise
        Specify Type.ExpEntropy.on = true to use the exponential of the entropy
        or
        Specify Type.GenExpEntropy.on = true and supply the scalar
        Type.ExpEntropy.g to use the generalized exponential of the entropy

        For details on the exercise, see here: https://www.arpm.co/lab/redirect.php?permalink=EBEffectNbScenFun
        """
        if Type is None:
            Type = namedtuple('type',['Entropy'])
            Type.Entropy = 'Exp'
        if Type.Entropy != 'Exp':
            Type.Entropy = 'GenExp'

        ## Code
        p_ = self.p
        if Type.Entropy == 'Exp':
            p_[p_==0] = 10**(-250)    #avoid log(0) in ens computation
            ens = np.exp(-p_@np.log(p_.T))
        else:
            ens = np.sum(p_ ** Type.g) ** (-1 / (Type.g - 1))

        return ens


from abc import ABC, abstractmethod
import numpy as np

'''
tensorflow implementation
  https://github.com/tensorflow/tensorflow/blob/master/tensorflow/python/ops/init_ops.py
Deeplearning.ai
 https://www.coursera.org/learn/deep-neural-network/lecture/RwqYe/weight-initialization-for-deep-networks
'''

class Initializer(ABC):
    @abstractmethod
    def __call__(self, num_input, num_output):
        pass

class Constant(Initializer):
    def __init__(self, value):
        self._value = value

    def __call__(self, num_input, num_output):
        return np.full((num_input, num_output), self._value)

class GlorotNormal(Initializer):
    def __call__(self, num_input, num_output):
        scale = 2/(num_input + num_output)
        stddev = np.sqrt(scale) / .87962566103423978
        return np.random.randn(num_input, num_output) * np.sqrt(stddev)

class GlorotUniform(Initializer):
    """The Glorot uniform initializer, also called Xavier uniform initializer.
      It draws samples from a uniform distribution within [-limit, limit]
      where `limit` is `sqrt(6 / (fan_in + fan_out))`
      where `fan_in` is the number of input units in the weight tensor
      and `fan_out` is the number of output units in the weight tensor.
      Reference: http://jmlr.org/proceedings/papers/v9/glorot10a/glorot10a.pdf
    """

    def __call__(self, num_input, num_output):
        scale = 2/(num_input + num_output)
        limit = np.sqrt(3.0 * scale)
        return np.random.uniform(-limit, limit, (num_input, num_output))

# if dtype is None:
#       dtype = self.dtype
#     scale = self.scale
#     scale_shape = shape
#     if partition_info is not None:
#       scale_shape = partition_info.full_shape
#     fan_in, fan_out = _compute_fans(scale_shape)
#     if self.mode == "fan_in":
#       scale /= max(1., fan_in)
#     elif self.mode == "fan_out":
#       scale /= max(1., fan_out)
#     else:
#       scale /= max(1., (fan_in + fan_out) / 2.)
#     if self.distribution == "normal" or self.distribution == "truncated_normal":
#       # constant taken from scipy.stats.truncnorm.std(a=-2, b=2, loc=0., scale=1.)
#       stddev = math.sqrt(scale) / .87962566103423978
#       return random_ops.truncated_normal(
#           shape, 0.0, stddev, dtype, seed=self.seed)
#     elif self.distribution == "untruncated_normal":
#       stddev = math.sqrt(scale)
#       return random_ops.random_normal(
#           shape, 0.0, stddev, dtype, seed=self.seed)
#     else:
#       limit = math.sqrt(3.0 * scale)
#       return random_ops.random_uniform(
#           shape, -limit, limit, dtype, seed=self.seed)


# %% [markdown]
# # Creating NumPy Arrays

# %%
import numpy as np

# %% [markdown]
#
# Create the following NumPy arrays. Only use `np.array()` if you can find no
# other method to generate the array using methods discussed in the lecture:

# ```python
# array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
# ```

# %%

# %% [markdown]
# ```python
# array([0.  , 1.25, 2.5 , 3.75, 5.  ])
# ```

# %%

# %% [markdown]
# ```python
# array([ 1,  3, 12, 92])
# ```

# %%

# %% [markdown]
# ```python
# array([[1, 2, 3, 4],
#        [5, 6, 7, 8]])
# ```

# %%

# %% [markdown]
# ```python
# array([[[0, 2, 4, 6],
#         [1, 3, 5, 7]],
#
#        [[1, 2, 3, 4],
#         [5, 6, 7, 8]],
#
#        [[9, 8, 7, 6],
#         [5, 4, 3, 2]]])
# ```

# %%

# %% [markdown]
# A $2\times 8$ Array containing uniformly distributed random numbers in $[0, 1)$.

# %%

# %% [markdown]
# ```python
# array([[1., 0., 0.],
#        [0., 1., 0.],
#        [0., 0., 1.]])
# ```

# %%

# %% [markdown]
# A vector with 5 elements containing normally distributed random numbers.

# %%

# %% [markdown]
# A $3 \times 4$ array containing normally distributed numbers with mean $5$ and standard deviation $0.5$.

# %%

# %% [markdown]
# Create the following NumPy arrays:
#
# ```python
# array([[ 0,  2,  4,  6],
#        [ 8, 10, 12, 14],
#        [16, 18, 20, 22],
#        [24, 26, 28, 30],
#        [32, 34, 36, 38]])
# ```

# %%

# %% [markdown]
#
# ```python
# array([[10, 19, 28, 37, 46, 55],
#        [13, 22, 31, 40, 49, 58],
#        [16, 25, 34, 43, 52, 61]])
# ```

# %%

# %% [markdown]
# # Extrema
#
# Create a vector with 100 random elements, uniformly distributed in $[10, 20)$.
#
# Compute the minimum and maximum value, as well as the indices of these values.

# %%

# %% [markdown]
# # Mean value
#
# Create a $6 \times 8$ array, containing normally distributed numbers with mean
# $2$ and standard deviation $1$.
# %%

# %% [markdown]
# Compute mean value and standard deviation of the complete array.

# %%

# %% [markdown]
# Compute the mean value and standard deviation for each row and column.

# %%

# %% [markdown]
# Compute the mean values of the whole array as well as of each row and column
# without using the method `mean()`.
#
# (*Hint:* The mean value is the sum of all relevant values divided by the
# number of relevant elements.)

# %%


# Sebastian Raschka 04/2014

import numpy as np

def pdf_multivariate_gauss(x, mu, cov):
    '''
    Caculate the multivariate normal density (pdf)

    Keyword arguments:
        x = numpy array of a "d x 1" sample vector
        mu = numpy array of a "d x 1" mean vector
        cov = "numpy array of a d x d" covariance matrix
    '''
    assert(mu.shape[0] > mu.shape[1]), 'mu must be a row vector'
    assert(x.shape[0] > x.shape[1]), 'x must be a row vector'
    assert(cov.shape[0] == cov.shape[1]), 'covariance matrix must be square'
    assert(mu.shape[0] == cov.shape[0]), 'cov_mat and mu_vec must have the same dimensions'
    assert(mu.shape[0] == x.shape[0]), 'mu and x must have the same dimensions'
    part1 = 1 / ( ((2* np.pi)**(len(mu)/2)) * (np.linalg.det(cov)**(1/2)) )
    part2 = (-1/2) * ((x-mu).T.dot(np.linalg.inv(cov))).dot((x-mu))
    return float(part1 * np.exp(part2))

def test_gauss_pdf():
    from matplotlib.mlab import bivariate_normal

    x = np.array([[0],[0]])
    mu  = np.array([[0],[0]])
    cov = np.eye(2)

    mlab_gauss = bivariate_normal(x,x)
    mlab_gauss = float(mlab_gauss[0]) # because mlab returns an np.array
    impl_gauss = pdf_multivariate_gauss(x, mu, cov)

    print('mlab_gauss:', mlab_gauss)
    print('impl_gauss:', impl_gauss)
    assert(mlab_gauss == impl_gauss), 'Implementations of the mult. Gaussian return different pdfs'

if __name__ == '__main__':
    test_gauss_pdf()


import numpy as np
from matplotlib import pyplot as plt
from scipy import linalg as la

def plot_vec(v, *args, o=np.zeros(3)):
    '''Plot vector v with the base point at o'''
    w = v + o
    plt.gca().plot([w[0], o[0]], [w[1], o[1]], [w[2], o[2]], *args)

def plot_plane(v, w):
    ''''Plot a piece of the plane spanned by v and w'''
    v1 = v / la.norm(v)
    v2 = w - np.dot(w,v1) * v1
    v2 = v2 / la.norm(v2)
    ts = la.norm(v) * np.linspace(-0.2, 1.2)
    rs = la.norm(w) * np.linspace(-1.2, 1.2)
    [Ts, Rs] = np.meshgrid(ts, rs)
    Xs = v1[0] * Ts + v2[0] * Rs
    Ys = v1[1] * Ts + v2[1] * Rs
    Zs = v1[2] * Ts + v2[2] * Rs
    plt.gca().plot_surface(Xs, Ys, Zs, alpha=0.1)

def plot_coeffs(x, a0, a1, *args):
    '''Visualize coefficients x in the basis a0, a1'''
    plot_vec(x[0]*a0, *args)
    plot_vec(x[1]*a1, *args)
    plot_vec(x[0]*a0, *args, o=x[1]*a1)
    plot_vec(x[1]*a1, *args, o=x[0]*a0)

def set_equal_aspect():
    '''Set equal aspect ratio in all dimensions'''
    ax3d = plt.gca()
    wl = ax3d.get_w_lims()
    ax3d.set_box_aspect((wl[1]-wl[0],wl[3]-wl[2],wl[5]-wl[4]))

def hide_ticks():
    '''Prettify plot by hiding the ticks in all axes'''
    ax3d = plt.gca()
    ax3d.set_xticks([])
    ax3d.set_yticks([])
    ax3d.set_zticks([])


import numpy as np

def euler_step(u, f, dt, *args):
    """Returns the solution at the next time-step using
    the forward Euler method.

    Parameters
    ----------
    u : array of floats
        solution at the current time-step.
    f : function
        function to compute the right hand-side of the system of equation.
    dt : float
        time-increment.
    args : optional arguments for the rhs function

    Returns
    -------
    unp1 : array of floats
        approximate solution at the next time step.
    """
    unp1 = u + dt * f(u, *args)
    return unp1

def rk4_step(u, f, dt, *args):
    """Returns the solution at the next time-step using
    the RK4 method. Assumes that f is time independent

    Parameters
    ----------
    u : array of floats
        solution at the current time-step.
    f : function
        function to compute the right hand-side of the system of equation.
    dt : float
        time-increment.
    args : optional arguments for the rhs function

    Returns
    -------
    u_n_plus_1 : array of floats
        approximate solution at the next time step.
    """

    #substep 1
    k1 = f(u, *args)

    #substep 2
    k2 = f(u + dt/2.*k1, *args)

    #substep 3
    k3 = f(u + dt/2.*k2, *args)

    #substep 4
    k4 = f(u + k3*dt, *args)

    unp1 = u + dt/6.*(k1 + 2.0*k2 + 2.0*k3 + k4)

    return unp1


import time
import numpy as np

def squash(x, delta):
	return np.maximum(0, x + delta)

def L_i(x, y, W):
	"""
	unvectorized version. Compute the multiclass svm loss for a single example (x,y)
	- x is a column vector representing an image (e.g. 3073 x 1 in CIFAR-10)
	with an appended bias dimension in the 3073-rd position (i.e. bias trick)
	- y is an integer giving index of correct class (e.g. between 0 and 9 in CIFAR-10)
	- W is the weight matrix (e.g. 10 x 3073 in CIFAR-10)
	"""
	delta = 1.0 			# see notes about delta later in this section
	scores = W.dot(x) 		# scores becomes of size 10 x 1, the scores for each class
	correct_class_score = scores[y]
	D = W.shape[0] 			# number of classes, e.g. 10
	loss_i = 0.0
	for j in range(D): 	# iterate over all wrong classes
		if j == y:
	  		continue
		# accumulate loss for the i-th example
		loss_i += max(0, scores[j] - correct_class_score + delta)

	return loss_i

def L(X, y, W):
	"""
		Fully-vectorized implementation
		- X holds all the training examples as columns
		- y is array of integers specifying correct class
	  	- W holds the weights
	"""
	# grab number of images
	N = X.shape[1]
	# set desired threshold
	delta = 1.0

	# scores holds the score for each image as columns
	scores = W.dot(X)

	# grab scores of correct classes
	correct_classes = scores[y, np.arange(N)]

	# compute margins
	margins = np.maximum(0, scores - correct_classes + delta)

	# ignore the y-th position and only consider margin on max wrong class
	margins[y, np.arange(N)] = 0

	# compute loss column-wise
	losses = np.sum(margins, axis=0)

	# average out the loss
	loss = np.sum(losses) / N

	# return average loss
	return loss

"""
- 3 possible classes: dog cat horse
- 2 images of 4 pixels each
We thus have a matrix X of 4 rows and 2 columns
and a matrix W of 3 columns (3 classes) and 4 columns
Using the bias trick, we introduce a new dimension to X of 1's
and add the bias column to the weight matrix W
In conclusion, we have X (5x2) and W(3x5)
"""

# dim(X) = 5x2 = 2 training observations consisting of 5 pixel values each
X = np.array([
	[-15, 2],
	[22, 11],
	[-44, -35],
	[56, 12],
	[1.0, 1.0]
])
# dim(W) = 3x5
W = np.array([
	[0.01, -0.05, 0.1, 0.05, 0.0],
	[0.7, 0.2, 0.05, 0.16, 0.2],
	[0.0, -0.45, -0.2, 0.03, -0.3]
])

y = np.array([2, 1])

losses = 0.0

start_time = time.time()
print("loss: ", L(X, y, W))
time1 = time.time() - start_time
print('Vectorized takes: {}'.format(time1))

start_time = time.time()
for i in range(X.shape[1]):
	losses += L_i(X[:, i], y[i], W)
print('loss = {}'.format(losses / X.shape[1]))
time2 = time.time() - start_time
print('Non-vectorized takes: {}'.format(time2))

print( ((time1 - time2) / time2) * 100 )