import tensorflow as tf
import numpy as np
import sys
import math

# Polynomial basis functions
def basis0(x):
    return tf.convert_to_tensor([x])

def basis1(x):
    return tf.convert_to_tensor([-0.5 * (x - 1.0), 0.5 * (x + 1.0)])

# quadratic polynomial
def basis2(xIn):
    x = tf.convert_to_tensor(xIn)
    return tf.convert_to_tensor(
        [0.5 * x * (x - 1), -1.0 * (x + 1) * (x - 1), 0.5 * x * (x + 1)])

# cubic polynomial
def basis3(xIn):
    x = xIn
    eta = x
    powEta2 = eta * x
    powEta3 = powEta2 * x

    temp = tf.convert_to_tensor([-0.6666666666666666 * powEta3 + 0.6666666666666666 * powEta2 +
                                 0.1666666666666667 * eta - 0.1666666666666667,
                                 1.333333333333333 * powEta3 - 0.6666666666666666 * powEta2 -
                                 1.333333333333333 * eta + 0.6666666666666666,
                                 -1.333333333333333 * powEta3 - 0.6666666666666666 * powEta2 +
                                 1.333333333333333 * eta + 0.6666666666666666,
                                 0.6666666666666666 * powEta3 + 0.6666666666666666 * powEta2 -
                                 0.1666666666666667 * eta - 0.1666666666666667])
    return temp

# quartic polynomial
def basis4(xIn):
    x = xIn

    eta = x
    powEta2 = x * x
    powEta3 = powEta2 * x
    powEta4 = powEta3 * x

    temp = tf.convert_to_tensor([powEta4 -
                                 powEta3 -
                                 0.5 *
                                 powEta2 +
                                 0.5 *
                                 eta, -
                                 2.0 *
                                 powEta4 +
                                 1.414213562373095 *
                                 powEta3 +
                                 2.0 *
                                 powEta2 -
                                 1.414213562373095 *
                                 eta, 1 -
                                 3 *
                                 powEta2 +
                                 2 *
                                 powEta4, -
                                 2 *
                                 powEta4 -
                                 1.414213562373095 *
                                 powEta3 +
                                 2.0 *
                                 powEta2 +
                                 1.414213562373095 *
                                 eta, powEta4 +
                                 powEta3 -
                                 0.5 *
                                 powEta2 -
                                 0.5 *
                                 eta])

    return temp

# quintic polynomial

def basis5(xIn):
    #x = np.tanh(xIn)
    x = xIn
    eta = x
    powEta2 = eta * eta
    powEta3 = eta * powEta2
    powEta4 = eta * powEta3
    powEta5 = eta * powEta4

    temp = tf.convert_to_tensor([(0.1 -
                                  0.1 *
                                  eta -
                                  1.2 *
                                  powEta2 +
                                  1.2 *
                                  powEta3 +
                                  1.6 *
                                  powEta4 -
                                  1.6 *
                                  powEta5), 3.2 *
                                 powEta5 -
                                 2.588854381999832 *
                                 powEta4 -
                                 3.505572809000084 *
                                 powEta3 +
                                 2.83606797749979 *
                                 powEta2 +
                                 0.3055728090000842 *
                                 eta -
                                 0.247213595499958, -
                                 3.2 *
                                 powEta5 +
                                 0.9888543819998319 *
                                 powEta4 +
                                 5.294427190999916 *
                                 powEta3 -
                                 1.63606797749979 *
                                 powEta2 -
                                 2.094427190999916 *
                                 eta +
                                 0.6472135954999582, 3.2 *
                                 powEta5 +
                                 0.9888543819998319 *
                                 powEta4 -
                                 5.294427190999916 *
                                 powEta3 -
                                 1.63606797749979 *
                                 powEta2 +
                                 2.094427190999916 *
                                 eta +
                                 0.6472135954999582, -
                                 3.2 *
                                 powEta5 -
                                 2.588854381999832 *
                                 powEta4 +
                                 3.505572809000084 *
                                 powEta3 +
                                 2.83606797749979 *
                                 powEta2 -
                                 0.3055728090000842 *
                                 eta -
                                 0.247213595499958, (0.1 +
                                                     0.1 *
                                                     eta -
                                                     1.2 *
                                                     powEta2 -
                                                     1.2 *
                                                     powEta3 +
                                                     1.6 *
                                                     powEta4 +
                                                     1.6 *
                                                     powEta5)])

    return temp

'''
Discontinuous polynomial, 2 parts
'''
def basisDG(x, basis) :
    xr = tf.where(x > 0, 2.0 * (x - 0.5), 0 * x)
    xl = tf.where(x <= 0, 2.0 * (x + 0.5), 0 * x)
    pos = tf.where(x > 0, 1.0, 0.0)
    neg = tf.where(x <= 0, 1.0, 0.0)

    fr = pos*basis(xr)
    fl = neg*basis(xl)
    f = tf.concat([fl,fr],axis=0)

    return f

def basis1DG(x):
    return basisDG(x, basis1)

def basis2DG(x):
    return basisDG(x, basis2)

def basis3DG(x):
    return basisDG(x, basis3)

def basis4DG(x):
    return basisDG(x, basis4)

def basis5DG(x):
    return basisDG(x, basis5)

'''
Continuous piecewise polynomials
'''
def basisCG(x, basis, width) :
    #Add one additional dimension for the batch, I believe.
    dims = len(x.get_shape().as_list())+1

    lR = [ [0]*2 for _ in range(dims) ]
    lL = [ [0]*2 for _ in range(dims) ]

    lR[0][0] = width-1
    lL[0][1] = width-1

    paddingsR = tf.convert_to_tensor(lR)
    paddingsL = tf.convert_to_tensor(lL)

    xr = tf.where(x > 0, 2.0 * (x - 0.5), 0 * x)
    xl = tf.where(x <= 0, 2.0 * (x + 0.5), 0 * x)
    pos = tf.where(x > 0, 1.0, 0.0)
    neg = tf.where(x <= 0, 1.0, 0.0)

    fr = tf.pad(pos*basis(xr),paddingsR)
    fl = tf.pad(neg*basis(xl),paddingsL)

    f = fr+fl

    return f

def basis1CG(x):
    return basisCG(x, basis1, 2)

def basis2CG(x):
    return basisCG(x, basis2, 3)

def basis3CG(x):
    return basisCG(x, basis3, 4)

def basis4CG(x):
    return basisCG(x, basis4, 5)

def basis5CG(x):
    return basisCG(x, basis5, 6)


"""
this python file includes some basic functions of Black-Scholes-Merton formulas
"""
def bsm_call_value(S0,K,T,r,sigma):
    '''
    Valuation of European call option in BSM model
    :param S0: initial stock level
    :param K: strike price
    :param T: maturity date
    :param r: constant risk-free short rate
    :param sigma: volatility factor in diffusion term
    :return: the call value
    '''

    from math import log,sqrt,exp
    from scipy import stats

    S0 = float(S0)
    d1 = (log(S0 / K) + (r + 0.5 * sigma ** 2) * T) / (sigma * sqrt(T))
    d2 = (log(S0 / K) + (r - 0.5 * sigma ** 2) * T) / (sigma * sqrt(T))
    value = (S0 * stats.norm.cdf(d1,0.0,1.0)) - K * exp(-r * T) * stats.norm.cdf(d2,0.0,1.0)
    return value

def bsm_vega(S0,K,T,r,sigma):
    '''
    calculating Vega of European option in BSM model
    :param S0: initial stock level
    :param K: strike price
    :param T: maturity date
    :param r: constant risk-free short rate
    :param sigma: volatility factor in diffusion term
    :return: Vega
    '''

    from math import log,sqrt
    from scipy import stats

    S0 = float(S0)
    d1 = (log(S0 / K) + (r + 0.5 * sigma ** 2) * T) / (sigma * sqrt(T))
    vega = S0 * stats.norm.cdf(d1,0.0,1.0) * sqrt(T)
    return vega

def bsm_call_imp_vol(S0,K,T,r,C0,sigma_est, it=100):
    '''
    implied volatility of European call option in BSM model
    :param S0: initial stock level
    :param K: strike price
    :param T: maturity date
    :param r: constant risk-free short rate
    :param C0:
    :param sigma_est: estimate of impl. volatility
    :param it: number of iterations
    :return:
    '''

    for i in range(it):
        sigma_est -= ((bsm_call_value(S0,K,T,r,sigma_est)-C0)/bsm_vega(S0,K,T,r,sigma_est))
    return sigma_est


import numpy as np

"""
Conceito: Z score é uma medida estatística que calcula quantos
desvios padrão um determinado dado está afastado da media da distribuição a qual pertence.

https://www.investopedia.com/terms/z/zscore.asp#:~:text=A%20Z%2Dscore%20is%20a,identical%20to%20the%20mean%20score.
"""

def z_score(distribuicao):

    """
    >>> distribuicao = [1, 3, 5, 7]
    >>> z_score(distribuicao)
    [-1.3416407864998738, -0.4472135954999579, 0.4472135954999579, 1.3416407864998738]

    :param distribuição: sequência python com qualquer distribuição de dados de tipo quantitativo discreto ou contínuo
    :return: uma lista com os z scores da distribuição
    """

    media = np.mean(distribuicao) #Media da distribuição

    desvio_padrao = np.std(distribuicao) #Desvio padrão da distribuição

    # Calculando o z_scores de cada valor da distribuição com list comprehension
    z_scores = [(x - media) / desvio_padrao for x in distribuicao]

    return z_scores # lista com os z_scores calculados


from scipy import integrate
import numpy as np
import matplotlib.pyplot as plt

def sys(q,t):
    yi = q[0]
    xi = q[1]
    f0 = xi
    f1 = (g - ((cD/m)*(xi**2)))
    return [f0,f1]

# Constants
g =  9.80665
cD =  0.2028
m = 80

# ICS
x0 = 0
y0 = 0
ics = [y0,x0]

t = np.linspace(0,100,10000)
sol = integrate.odeint(sys,ics,t)
y = sol[:,0]
x = sol[:,1]

plt.figure(1)
plt.plot(t,y, label='Position')
plt.plot(t,x, label='Velocity')
plt.ylim(0,5000)
plt.legend(loc=0)
plt.plot()

print "Time to fall 5000m is between:"
t5000_b1 = np.min(np.where(y>5000))-1
t5000_b2 = np.min(np.where(y>5000))
print str((t5000_b1/100)) + " seconds; here y = " + str(y[t5000_b1])
print "and"
print str((t5000_b2/100)) + " seconds; here y = " + str(y[t5000_b2])

plt.figure(2)
plt.plot(t,x, label='Velocity')
plt.legend(loc=0)
plt.title('Skydiver velocity vs time')
plt.xlabel(r'$t$')
plt.ylabel(r'$v$')
plt.plot()

xmax = np.max(x)
print "maximum speed is " + str(xmax)

# and we are dooooon

ExamPrep/Shit Comp/ShitComp/(Past paper) 1314/Q1.py


import numpy as np
###################################################################################
D2R = np.pi/180.
R2D = 1./D2R
###################################################################################

def sind(degrees):
    return np.sin(D2R*degrees)

def cosd(degrees):
    return np.cos(D2R*degrees)

def sincosd(deg):
    r = np.deg2rad(deg)
    return np.sin(r), np.cos(r)

def rot2deg(deg):
    '''Returns a 2-d rotation matrix by angle 'deg'.'''
    sa,ca=sincosd(deg)
    return np.array([[ca,-sa],[sa,ca]])

def tand(degrees):
    return np.tan(D2R*degrees)

def put360(degrees):  # Puts an angle into range 0 to 360.
    return degrees % 360

def arctan2d(y, x):
    return put360(np.arctan2(y, x)*R2D)

def arcsind(x):
    return np.arcsin(x)*R2D

def getXYZ(lon,lat):  # Convert spherical angles (in degrees) into xyz triplet
    return np.array([cosd(lon)*cosd(lat), sind(lon)*cosd(lat), sind(lat)])

def getNorm(xyz):
    return np.sqrt(np.sum(xyz**2,axis=0))

def getNormalized(xyz):
    return xyz/getNorm(xyz)

def average_angles_deg(a1, a2):
    '''Computes the average of two angles (in degrees),
    handling wrap-around.  That is, if the two angles are thought of as
    directions in the plane, this gives the midpoint between them.'''
    x = cosd(a1) + cosd(a2)
    y = sind(a1) + sind(a2)
    return arctan2d(y, x)


import numpy as np

class PCA:

    def __init__(self, n_components):
        self.n_components = n_components
        self.components = None
        self.mean = None

    def fit(self, X):
        self.mean = np.mean(X, axis=0)
        X = X - self.mean

        # covariance, function needs samples as columns
        cov = np.cov(X.T)

        # eigenvalues, eigenvectors
        eigenvalues, engenvectors = np.linalg.eig(cov)

        # -> eigenvector v = [:,i] column vector, transpose for easier calculations
        # sort eigenvectors
        engenvectors = engenvectors.T
        idxs = np.argsort(eigenvalues)[::-1]
        eigenvalues = eigenvalues[idxs]
        eigenvectors = engenvectors[idxs]

        # sort first n eigenvectors
        self.components = engenvectors[0:self.n_components]

    def transform(self, X):
        # project data
        X = X - self.mean
        return np.dot(X, self.components.T)


import numpy as np

class MSELoss:
    def forward(self, y, target):
        self.diff = diff = y - target
        return np.sum(np.square(diff)) / diff.size

    def backward(self):
        assert hasattr(self, 'diff'), "The values passed to forward were not saved"
        diff = self.diff
        return (2 * diff) / diff.size

class Linear:
    def __init__(self, in_features, out_features):
        # Initialization: TODO: Research the best way.
        bound = 3 / np.sqrt(in_features)
        self.W = np.random.uniform(-bound, bound, (out_features, in_features))
        bound = 1 / np.sqrt(in_features)
        self.b = np.random.uniform(-bound, bound, out_features)

        self.grad_W = None
        self.grad_b = None

    def forward(self, x):
        self.x = x
        return np.matmul(x, self.W.transpose()) + self.b

    def backward(self, dy):
        assert hasattr(self, 'x'), "The values passed to forward were not saved"
        assert dy.ndim == 2 and dy.shape[1] == self.W.shape[0], f'{dy.ndim} != 2 or {dy.shape[1]} != {self.W.shape[0]}'
        self.grad_W = np.matmul(dy.transpose(), self.x)
        self.grad_b = np.sum(dy, axis=0)
        dx = np.matmul(dy, self.W)
        return dx

class Tanh:
    def forward(self, x):
        self.x = x
        return np.tanh(x)

    def backward(self, dy):
        assert hasattr(self, 'x'), "The values passed to forward were not saved"
        return dy * (1.0 - np.square(np.tanh(self.x)))

class MLP:
    "NumPy MLP"
    def __init__(self):
        self.layer_input = Linear(in_features=1, out_features=10)
        self.tanh_1 = Tanh()
        self.layer_hidden_1 = Linear(in_features=10, out_features=11)
        self.tanh_2 = Tanh()
        self.layer_output = Linear(in_features=11, out_features=1)

    def forward(self, x):
        x = self.layer_input.forward(x)
        x = self.tanh_1.forward(x)
        x = self.layer_hidden_1.forward(x)
        x = self.tanh_2.forward(x)
        x = self.layer_output.forward(x)
        return x

    def backward(self, dy):
        dx = self.layer_output.backward(dy)
        dx = self.tanh_2.backward(dx)
        dx = self.layer_hidden_1.backward(dx)
        dx = self.tanh_1.backward(dx)
        dx = self.layer_input.backward(dx)
        return dx

class Adam:
    "TODO: Proper implementation"
    def __init__(self, lr):
        self.lr = lr

    def step(self, model):
        for module in model.__dict__.values():
            if isinstance(module, Linear):
                module.W = module.W - module.grad_W * self.lr
                module.b = module.b - module.grad_b * self.lr

def train(X, Y, epochs, learning_rate):
    model = MLP()
    optimizer = Adam(lr=learning_rate)
    criterion = MSELoss()
    for i in range(epochs):
        # Forward computations
        Y_pred = model.forward(X)
        loss = criterion.forward(Y_pred, Y)

        # Backward computations
        dy = criterion.backward()
        dx = model.backward(dy)

        # Gradient decsent
        optimizer.step(model)

    return model

def predict(model, X):
    return model.forward(X)


#Python code for chapter 23 DSILT: Statistics

'''
-------------------------------------------------------------------------------
-----------------------Combinations and Permutations---------------------------
-------------------------------------------------------------------------------
'''

import math

def permutation(n, k):
    return math.factorial(n) / math.factorial(k)

def combination(n, k):
    return permutation(n, k) / math.factorial(k)

'''
-------------------------------------------------------------------------------
--------------------Bayes' Theorem and Bayesian Inference----------------------
-------------------------------------------------------------------------------
'''

import numpy as np
from scipy import stats
import matplotlib.pyplot as plt

#Simulate 1000 coin flips (Bernoulli trials) from a binomial dist
bernoulli_trials = np.random.binomial(n=1, p=0.5, size=1000)
print(np.mean(bernoulli_trials))

#PMF of Bernoulli dist gives probability of a positive hit for a single Bernoulli trial
print(stats.bernoulli.pmf(1, 0.5))

#Generate 1000 random binary numbers to simulate coin flipping data
d = np.random.randint(2, size=1000)

#For indpendent events, the probability of observing the data is the product of the PMF
print(np.product(stats.bernoulli.pmf(d, 0.5)))

#Look at the probability of the data on an x scale of 0-1
x = np.linspace(0, 1, 100)
prob = [np.product(stats.bernoulli.pmf(d, p)) for p in x]
print(prob)

#Plot distribution of a fair coin (prior)
fair_flips = bernoulli_flips = np.random.binomial(n=1, p=.5, size=1000)
p_fair = np.array([np.product(stats.bernoulli.pmf(fair_flips, p)) for p in x])
p_fair = p_fair / np.sum(p_fair)
plt.plot(x, p_fair)
plt.title('Prior Probability')
plt.show()

#Look at the probability of an unfair coin (sample)
d = np.random.binomial(n=1, p=0.8, size=1000)
prob = np.array([np.product(stats.bernoulli.pmf(d, p)) for p in x])
print(prob)

#Plot the posterior distribution after applying Bayes' Theorem
#This function stolen from http://dataconomy.com/2015/02/introduction-to-bayes-theorem-with-python/
def bayes(n_sample, n_prior=100, observed_p=0.8, prior_p=0.5):
    x = np.linspace(0, 1, 100)
    sample = np.random.binomial(n=1, p=observed_p, size=n_sample)
    observed_dist = np.array([np.product(stats.bernoulli.pmf(sample, p)) for p in x])
    prior_sample = np.random.binomial(n=1, p=prior_p, size=n_prior)
    prior = np.array([np.product(stats.bernoulli.pmf(prior_sample, p)) for p in x])
    prior = prior / np.sum(prior)
    posterior = [prior[i] * observed_dist[i] for i in range(prior.shape[0])]
    posterior = posterior / np.sum(posterior)

    fig, axes = plt.subplots(3, 1, sharex=True, figsize=(8,8))
    axes[0].plot(x, observed_dist)
    axes[0].set_title("Sampling Distribution")
    axes[1].plot(x, prior)
    axes[1].set_title("Prior Distribution")
    axes[2].plot(x, posterior)
    axes[2].set_title("Posterior Distribution")
    plt.tight_layout()
    plt.show()

    return posterior

bayes(100)
bayes(1000)

'''
-------------------------------------------------------------------------------
--------------------------------Markov Chains----------------------------------
-------------------------------------------------------------------------------
'''

'''
Example 1: Trump Speech Generator
'''

import numpy as np

#Trump speech generator
d = open('/home/dsilt/Desktop/dsilt-stats-code/23-24 Bayesian Statistics and Markov Models/trump_speeches.txt', encoding='utf8').read()
corpus = d.split()
#Function to generate all pairs of words
#Keys are all unique words and all of the words that appear after the key are stored as a list of values
def word_pairs(corpus):
    for i in range(len(corpus)-1):
        yield (corpus[i], corpus[i+1])
pairs = word_pairs(corpus)
words = {}
for w1, w2 in pairs:
    if w1 in words.keys():
        words[w1].append(w2)
    else:
        words[w1] = [w2]

#Choose a word to initialize the Markov chain and the length of the chain in number of words
first_word = np.random.choice(corpus)
nbr_words = 40
def markov_chain_text(initial_state, word_pair_dict, stop_time):
    state = initial_state
    chain = [state]
    for w in range(stop_time):
        state = np.random.choice(word_pair_dict[chain[-1]])
        chain.append(state)
    return ' '.join(chain)

print(markov_chain_text(first_word, words, nbr_words))

'''
Example 2: Markov Chain for voting in presidential elections
'''

#Model voting democrat vs republican in consecutive presidential elections
#Assume the following split of voting population for this elect (time t)
rep_1 = 0.55
dem_1 = 0.45
#Assume the following transition probabilities
rep_to_rep = 0.8
rep_to_dem = 0.2
dem_to_dem = 0.7
dem_to_rep = 0.3
#Calculate split of voting population at next election (t+1)
rep_2 = rep_1*rep_to_rep + dem_1*dem_to_rep
dem_2 = dem_1*dem_to_dem + rep_1*rep_to_dem
print('Republican vs Democrat Split for Next Election:', rep_2, 'vs', dem_2)
#Define initial state and transition matrix to make calculations simpler in the future
state_1 = np.array([rep_1, dem_1])
transm = np.array([[rep_to_rep, rep_to_dem], [dem_to_rep, dem_to_dem]])
#Define function to generalize the calculations above to any future time
def markov_chain(initial_state, transition_matrix, stop_time):
    state = initial_state
    chain = [state]
    for t in range(stop_time):
        state = np.round(np.dot(state, transm), 4)
        chain.append(state)
    return chain
print(markov_chain(state_1, transm, 10))

#Notice the proportion appears to converge around a rep to dem ratio of 60:40
#Steady state is when initial_state * transition_matrix = initial_state, meaning there are no more changes to state
#Adjust markov_chain to stop if a steady state has been reached
def markov_chain(initial_state, transition_matrix, stop_time):
    state = initial_state
    chain = [state]
    for t in range(stop_time):
        if (np.round(np.dot(state, transm), 4) == state).all():
            print('Steady state reached at time t =', t)
            break
        else:
            state = np.round(np.dot(state, transm), 4)
        chain.append(state)
    return chain
print(markov_chain(state_1, transm, 20))

'''
-------------------------------------------------------------------------------
-----------------------------Bayesian Regression-------------------------------
-------------------------------------------------------------------------------
'''

import pymc3 as pm
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

#Ignore all deprecation warnings from theano
import warnings
warnings.filterwarnings("ignore")

def glm_mcmc(df, formula, nbr_iters, likelihood=None, mcmc_sampler=None, target_accept=0.8, tune=500):
    '''
    This function is a wrapper for GLM Bayesian regression, using MCMC.  The
    default MCMC method is NUTS (No U-Turn Sampler), but others are available:
        Binary variables --> BinaryMetropolis
        Discrete variables --> Metropolis
        Continuous variables --> NUTS

    Parameters:
        df = pandas dataframe
        formula = regression formula as specified in R or Patsy
        nbr_iters = number of iterations to sample over in MCMC
        likelihood = the family of likelihood functions for the data (e.g. pm.glm.families.Normal())
        mcmc_sampler = the MCMC sampling method (e.g. pm.NUTS, pm.Metropolis), default NUTS
        target_accept = adapts the step size to make acceptance probability closer to target, higher target --> smaller steps, range(0,1)
        tune = number of tuning samples to use in each iteration, higher tune --> more probability space explored

    If acceptance probability is higher than the target, increase the tune
    parameters.  For more info about this, see:
        https://discourse.pymc.io/t/warning-when-nuts-probability-is-greater-than-acceptance-level/594

    If there are divergences after tuning, increase the target_accept
    parameter.  For more info about this, see:
        https://docs.pymc.io/notebooks/Diagnosing_biased_Inference_with_Divergences.html

    A great intro to GLM Bayesian regression with PYMC3 can be found here:
        https://docs.pymc.io/notebooks/GLM-linear.html
    '''
    bayesian_model = pm.Model()  #Initializes a Bayesian model container
    with bayesian_model:
        if likelihood is None:
            #Priors and observed sampling dist (likelihood) are automatically set
            pm.glm.GLM.from_formula(formula, df)
        else:
            pm.glm.GLM.from_formula(formula, df, family=likelihood)
        if mcmc_sampler is None:
            mcmc_sampler = pm.NUTS(target_accept=target_accept)
        else:
            mcmc_sampler = mcmc_sampler(target_accept=target_accept)
        #Perform inference to estimate the posterior, using nbr_iters MCMC sample posteriors
        trace = pm.sample(draws=nbr_iters, step=mcmc_sampler,
                          init='auto', n_init=200000,
                          cores=None, tune=tune, progressbar=True,
                          random_seed=14)
    return bayesian_model, trace

def patsy_formula(df, dependent_var, *excluded_cols):
    '''
    This function generates the R style formula for Patsy, omitting the
    dependent variable and any other specified variables from the right
    side of the equation.  This is useful because Patsy does not have
    a '.' operator.
    '''
    df_columns = list(df.columns.values)
    df_columns.remove(dependent_var)
    for col in excluded_cols:
        df_columns.remove(col)
    return dependent_var + ' ~ ' + ' + '.join(df_columns)

#GLM of mpg on disp
d = pd.read_csv('/home/dsilt/Desktop/dsilt-stats-code/23-24 Bayesian Statistics and Markov Models/mtcars.csv')
formula = patsy_formula(d, 'mpg')

#Bayesian regression
bayesian_reg, trace = glm_mcmc(d, formula, nbr_iters=2000)
print(pm.summary(trace))  #Regression results
#SD is the error term
pm.autocorrplot(trace)
plt.show()

#Plot the last half of the posterior estimates, as the first few traces are likely poor estimates of the parameters
#First few estimates are called "burn-in"
pm.traceplot(trace[1000:], lines={k: v['mean'] for k,v in pm.summary(trace[1000:]).iterrows()})
plt.show()
#The traces, produced to the right, should be stationary if the model converged
#The traces will show autocorrelation if there is any
#The distributions, produced to the left, can be used to get the mean of the parameter estimate, credible regions, and look at skew
#Distributions should look normal - if they do, and performance is not satisfactory, specifying normal priors might improve it

#Model PPC and RMSE for evaluation
ppc = pm.sample_ppc(trace[1000:], samples=500, model=bayesian_reg)
rmse = np.sqrt(np.sum((ppc['y'].mean(0).mean(0).T - d['mpg'])**2) / d.shape[0])

def get_r2(df, ppc, target):
    sse_model = np.sum((ppc['y'].mean(0).mean(0).T - df[[target]])**2)[0]
    sse_mean = np.sum((df[[target]] - df[target].mean())**2)[0]
    return 1-(sse_model / sse_mean)
print('R Squared:', get_r2(d, ppc, 'mpg'))

#Get the posterior predictions
traces = pm.trace_to_dataframe(trace)[['Intercept'] + list(d.drop(['mpg'], axis=1).columns)]
x = d.drop(['mpg'], axis=1)
x.insert(0, 'intercept', 1)
likelihoods = np.dot(x, traces.T)
likelihoods_sd = np.tile(pm.trace_to_dataframe(trace)[['sd']].T, (d.shape[0], 1))
likelihood = np.random.normal(likelihoods, likelihoods_sd)
print(likelihood.shape)  #Should have d.shape[0] rows and as many columns as MCMC samples

#Get credible intervals for the target variable
dfp = pd.DataFrame(np.percentile(likelihood,[2.5, 25, 50, 75, 97.5], axis=1).T,
                   columns=['0_25','25','50','75','97_5'])

#Plot target variable over index and the credible intervals from Bayesian regression
pal = sns.color_palette('Purples')
fig, ax = plt.subplots(1, 1, figsize=(7, 7))
ax.fill_between(d.index, dfp['0_25'], dfp['97_5'], alpha=0.7, color=pal[1], label='CR 95%')
ax.fill_between(d.index, dfp['25'], dfp['75'], alpha=0.5, color=pal[4], label='CR 50%')
ax.plot(d.index, dfp['50'], alpha=0.5, color=pal[5], label='Median')
plt.plot(d.index, d['mpg'], 'bo')
plt.legend()
plt.title('Bayesian Regression Predicted Credible Intervals vs Actual')
plt.show()

#Bayesian LASSO regression - this can be done by using a Laplacian prior
#First, look at Laplacian dist - the prior will force dist near 0 and only variables significantly different from 0 will escape
#Thus, by using Lapacian prior, only the variables with strong coefficients will stand out
from scipy.stats import norm, laplace

def plot_laplace_vs_normal(norm_sd=1., b=1.):

    dstrb = pd.DataFrame(index=np.linspace(-10, 10, 1000))
    dstrb['normal'] = norm.pdf(dstrb.index.values, loc=0, scale=norm_sd)
    b0 = max(b*.5,0)
    b2 = min(b*2,10)
    dstrb['laplace b={}'.format(b0)] = laplace.pdf(dstrb.index.values, loc=0, scale=b0)
    dstrb['laplace b={}'.format(b)] = laplace.pdf(dstrb.index.values, loc=0, scale=b)
    dstrb['laplace b={}'.format(b2)] = laplace.pdf(dstrb.index.values, loc=0, scale=b2)
    dstrb.plot(style=['--','-','-','-'], figsize=(12,4))
    plt.show()

plot_laplace_vs_normal()

def lasso_mcmc(df, formula, nbr_iters, likelihood=None, mcmc_sampler=None, target_accept=0.8, tune=500):
    '''
    This function is a wrapper for LASSO Bayesian regression, using MCMC.  The
    default MCMC method is Metropolis, but others are available:
        Binary variables --> BinaryMetropolis
        Discrete variables --> Metropolis
        Continuous variables --> NUTS (No U-Turn Sampler)

    Parameters:
        df = pandas dataframe
        formula = regression formula as specified in R or Patsy
        nbr_iters = number of iterations to sample over in MCMC
        likelihood = the family of likelihood functions for the data (e.g. pm.glm.families.Normal())
        mcmc_sampler = the MCMC sampling method (e.g. pm.NUTS, pm.Metropolis), default NUTS
        target_accept = adapts the step size to make acceptance probability closer to target, higher target --> smaller steps, range(0,1)
        tune = number of tuning samples to use in each iteration, higher tune --> more probability space explored

    If acceptance probability is higher than the target, increase the tune
    parameters.  For more info about this, see:
        https://discourse.pymc.io/t/warning-when-nuts-probability-is-greater-than-acceptance-level/594

    If there are divergences after tuning, increase the target_accept
    parameter.  For more info about this, see:
        https://docs.pymc.io/notebooks/Diagnosing_biased_Inference_with_Divergences.html

    A great intro to GLM Bayesian regression with PYMC3 can be found here:
        https://docs.pymc.io/notebooks/GLM-linear.html
    '''
    bayesian_model = pm.Model()  #Initializes a Bayesian model container
    with bayesian_model:

        priors = {"Intercept": pm.Laplace.dist(mu=0, b=0.1),
                  "Regressor": pm.Laplace.dist(mu=0, b=0.1)
                  }

        if likelihood is None:
            #Priors and observed sampling dist (likelihood) are automatically set
            pm.glm.GLM.from_formula(formula, df, priors=priors,
                                    family=pm.glm.families.Normal())
        else:
            pm.glm.GLM.from_formula(formula, df, priors=priors,
                                    family=likelihood)
        if mcmc_sampler is None:
            mcmc_sampler = pm.Metropolis(target_accept=target_accept)
        else:
            mcmc_sampler = mcmc_sampler(target_accept=target_accept)
        #Perform inference to estimate the posterior, using nbr_iters MCMC sample posteriors
        trace = pm.sample(draws=nbr_iters, step=mcmc_sampler,
                          init='auto', n_init=200000,
                          cores=None, tune=tune, progressbar=True,
                          random_seed=14)
    return bayesian_model, trace

lasso_reg, trace = lasso_mcmc(d, formula, nbr_iters=2000)
print(pm.summary(trace))
pm.traceplot(trace[1000:], lines={k: v['mean'] for k,v in pm.summary(trace[1000:]).iterrows()})
plt.show()
#So using Laplacian priors was a horrible idea

#Model PPC and RMSE for evaluation
ppc = pm.sample_ppc(trace[1000:], samples=500, model=lasso_reg)
rmse = np.sqrt(np.sum((ppc['y'].mean(0).mean(0).T - d['mpg'])**2) / d.shape[0])

print('R Squared:', get_r2(d, ppc, 'mpg'))  #R^2 shows the model does not fit the data at all

#Get the posterior predictions
traces = pm.trace_to_dataframe(trace)[['Intercept'] + list(d.drop(['mpg'], axis=1).columns)]
x = d.drop(['mpg'], axis=1)
x.insert(0, 'intercept', 1)
likelihoods = np.dot(x, traces.T)
likelihoods_sd = np.tile(pm.trace_to_dataframe(trace)[['sd']].T, (d.shape[0], 1))
likelihood = np.random.normal(likelihoods, likelihoods_sd)
print(likelihood.shape)  #Should have d.shape[0] rows and as many columns as MCMC samples

#Get credible intervals for the target variable
dfp = pd.DataFrame(np.percentile(likelihood,[2.5, 25, 50, 75, 97.5], axis=1).T,
                   columns=['0_25','25','50','75','97_5'])

#Plot target variable over index and the credible intervals from Bayesian regression
pal = sns.color_palette('Purples')
fig, ax = plt.subplots(1, 1, figsize=(7, 7))
ax.fill_between(d.index, dfp['0_25'], dfp['97_5'], alpha=0.7, color=pal[1], label='CR 95%')
ax.fill_between(d.index, dfp['25'], dfp['75'], alpha=0.5, color=pal[4], label='CR 50%')
ax.plot(d.index, dfp['50'], alpha=0.5, color=pal[5], label='Median')
plt.plot(d.index, d['mpg'], 'bo')
plt.legend()
plt.title('Bayesian Regression Predicted Credible Intervals vs Actual')
plt.show()

'''
-------------------------------------------------------------------------------
---------------------------------Naive Bayes-----------------------------------
-------------------------------------------------------------------------------
'''

from sklearn.naive_bayes import GaussianNB
import pandas as pd
from sklearn.metrics import confusion_matrix

d = pd.read_csv('/home/dsilt/Desktop/dsilt-stats-code/23-24 Bayesian Statistics and Markov Models/iris.csv')

#Train model with default hyperparameters
nb_model = GaussianNB()
nb_model.fit(d.drop(['Species'], axis=1), d['Species'])
preds = nb_model.predict(d.drop(['Species'], axis=1))
pred_probs = nb_model.predict_proba(d.drop(['Species'], axis=1))
print(confusion_matrix(d['Species'], preds))

23-24 Bayesian Statistics and Markov Models/23_bayesian_models.py

import numpy,from scipy,from theano,import pymc3


def cone(D,age):
# outputs:
# topo -- topographic elevation of the cone [m]
# distance -- distance along the cone profile [m]
# coneslope -- mean slope of cone [deg]
# inputs:
# D -- landscape diffusivity [m^2/kyr]
# age -- age of the cone [kyr]

    import numpy as np

    # Topo and Grid Parameters
    conediam=876
    craterdiam=292
    nx=600
    dx=5
    coneslope=0.575

    # Create Initial Cone Topography
    icenter=round(nx/2)
    cone_radius=conediam/2
    crater_radius=craterdiam/2
    topo=1000+np.zeros(nx)
    dist=np.zeros(nx)
    distance=np.zeros(nx)
    for i in range(nx):
        distance[i]=i*dx
        dist[i]=abs(i*dx-icenter*dx);
        if dist[i]<=crater_radius:
            topo[i]=topo[i]+dist[i]*coneslope;
        if ((dist[i]>crater_radius) and (dist[i]<cone_radius)):
            topo[i]=1000+crater_radius*coneslope-(dist[i]-crater_radius)*coneslope;
        if dist[i]>cone_radius:
            topo[i]=1000+crater_radius*coneslope-(cone_radius-crater_radius)*coneslope;

    topo=topo-min(topo);

    # 1d Diffusion
    tend=age           # kyr

    toponew=np.zeros(nx)
    dt=0.1*dx*dx/D
    slope=np.zeros(nx)

    t=0
    while t<tend:

        for i in range(1,nx-1):
            toponew[i]=topo[i]+D*dt/(dx*dx)*(topo[i+1]-2*topo[i]+topo[i-1])

        toponew[0]=topo[0]+D*dt/(dx*dx)*(topo[1]-2*topo[0]+topo[0])
        toponew[nx-1]=topo[nx-1]+D*dt/(dx*dx)*(topo[nx-1]-2*topo[nx-1]+topo[nx-2])

        t+=dt
        topo=toponew[:]

    slope=np.gradient(topo,dx)
    slope=180/np.pi*np.arctan(abs(slope))

    maxtopo=max(topo)

    # Find array index associated with lowest elevation on the cone
    coneedge=-1
    i=0
    while coneedge==-1:
        if slope[i]>3:
            coneedge=i
        i+=1

    # Find array index associated with highest elevation on the cone
    maxind=-1;
    i=0;
    while maxind==-1:
        if ((topo[i+1]<topo[i]) or (i==nx-1)):
            maxind=i
        i+=1

    coneslope=(maxtopo-topo[coneedge])/(dx*(maxind-coneedge))
    coneslope=180/np.pi*np.arctan(coneslope)

    return (topo,distance,coneslope);


'''
PyTorch has its own implementation of backward function for symmetric eigensolver https://github.com/pytorch/pytorch/blob/291746f11047361100102577ce7d1cfa1833be50/tools/autograd/templates/Functions.cpp#L1660
However, it assumes a triangular adjoint.
We reimplement it to return a symmetric adjoint
'''

import numpy as np
import torch

class EigenSolver(torch.autograd.Function):
    @staticmethod
    def forward(self, A):
        w, v = torch.symeig(A, eigenvectors=True)

        self.save_for_backward(w, v)
        return w, v

    @staticmethod
    def backward(self, dw, dv):
        w, v = self.saved_tensors
        dtype, device = w.dtype, w.device
        N = v.shape[0]

        F = w - w[:,None]
        # in case of degenerated eigenvalues, replace the following two lines with a safe inverse
        F.diagonal().fill_(np.inf);
        F = 1./F

        vt = v.t()
        vdv = vt@dv

        return v@(torch.diag(dw) + F*(vdv-vdv.t())/2) @vt

def test_eigs():
    M = 2
    torch.manual_seed(42)
    A = torch.rand(M, M, dtype=torch.float64)
    A = torch.nn.Parameter(A+A.t())
    assert(torch.autograd.gradcheck(DominantEigensolver.apply, A, eps=1e-6, atol=1e-4))
    print("Test Pass!")

if __name__=='__main__':
    test_eigs()


# Multiple Linear Regression

# Importing the libraries
import numpy as np
import pandas as pd

# Importing the dataset
dataset = pd.read_csv('Salary_Classification.csv')
temp = dataset.values
features = dataset.iloc[:, :-1].values
labels = dataset.iloc[:, -1].values

#Check if any NaN values in dataset
dataset.isnull().any(axis=0)

#check data types for each column
print dataset.dtypes

# Encoding categorical data
from sklearn.preprocessing import LabelEncoder
labelencoder = LabelEncoder()
features[:, 0] = labelencoder.fit_transform(features[:, 0])

from sklearn.preprocessing import OneHotEncoder
onehotencoder = OneHotEncoder(categorical_features = [0])
features = onehotencoder.fit_transform(features).toarray()

# Avoiding the Dummy Variable Trap
# dropping first column
features = features[:, 1:]

"""
With linear regression, or generalized linear models
estimated by maximum likelihood (or least squares)
you need to leave out one column.
Otherwise you will get a message about some
columns "left out because of singularities".

But if you estimate such models with regularization,
for example ridge, lasso er the elastic net,
then you should not leave out any columns.
The regularization takes care of the singularities,
and more important, the prediction obtained
may depend on which columns you leave out.
That will not happen when you do not use regularization.
"""

# Splitting the dataset into the Training set and Test set
from sklearn.model_selection import train_test_split
features_train, features_test, labels_train, labels_test = train_test_split(features, labels, test_size = 0.2, random_state = 0)

# Fitting Multiple Linear Regression to the Training set
from sklearn.linear_model import LinearRegression
regressor = LinearRegression()
regressor.fit(features_train, labels_train)

#To see the value of the intercept and slop calculated by the linear regression algorithm for our dataset, execute the following code.
print(regressor.intercept_)
print (regressor.coef_)

# Predicting the Test set results
Pred = regressor.predict(features_test)

import pandas as pd
import numpy as np
print (pd.DataFrame(Pred, labels_test))

# Prediction for a new values
# make this accorifng to the data csv
# Development is replaced by 1,0,0 to 0,0 to remove dummy trap

x = [0,0,1150,3,4]
x = np.array(x)
regressor.predict(x.reshape(1, -1))

le = labelencoder.transform(['Development'])
ohe = onehotencoder.transform(le.reshape(1,1)).toarray()
x = [ohe[0][1],ohe[0][2],1150,3,4]
x = np.array(x)
regressor.predict(x.reshape(1, -1))

# Getting Score for the Multi Linear Reg model
Score = regressor.score(features_train, labels_train)
Score = regressor.score(features_test, labels_test)

"""
Evaluating the Algorithm
The final step is to evaluate the performance of algorithm. We'll do this by finding the values for MAE, MSE and RMSE. Execute the following script:
"""
from sklearn import metrics
print('Mean Absolute Error:', metrics.mean_absolute_error(labels_test, Pred))
print('Mean Squared Error:', metrics.mean_squared_error(labels_test, Pred))
print('Root Mean Squared Error:', np.sqrt(metrics.mean_squared_error(labels_test, Pred)))

print np.mean(labels)
"""
You can see that the value of root mean squared error is 4379.8, which is lower than 10% of the mean value of the salaries (760038x10/100 = 7600). This means that our algorithm was not very accurate but can still make reasonably good predictions.

There are many factors that may have contributed to this inaccuracy, a few of which are listed here:

Need more data: Only one year worth of data isn't that much, whereas having multiple years worth could have helped us improve the accuracy quite a bit.
Bad assumptions: We made the assumption that this data has a linear relationship, but that might not be the case. Visualizing the data may help you determine that.
Poor features: The features we used may not have had a high enough correlation to the values we were trying to predict.
"""

"""
What is multiple regression?
//same as simple regression but multiple independent variables

//Assumptions of linear regression - do not blindly follow but check if the given problem is linear regression problem

// In above problem, profit is our dependent variables and others are independent variables

//but state (location) is a categorical variable and we need to handle this.
//For this you need to create dummy variables
//Dummy variables trap ( include one less dummy variables in predictor)
// Whenever you are building a model, always omit one dummy variables.
// So beware of dummy variable trap
//Show the image (dummy variable trap)
// Now lets start buidling a the model

// Some times you have to miss some of independent variable for model buidling (elimination) [selecting the right variables)
// All possible methods are given below
1. All in
2. Backward elimination
3. Forward selection
4. Bidirection elimination
5. Score comparison

"""
"""
About one hot encoding

Most of the machine learning algorithms such as linear regression, logistic regression, neural network, support vector machine works better with numerical features.

Quantitative features come with a numerical value and they can be directly used (Sometimes data preprocessing, normalization may have to use) as the input features of ML algorithms.

Ordinal features can be easily represented in numbers (Ex. First = 1, Second = 2, Third = 3 …). This is called Integer Encoding. Representing ordinal features using numbers makes sense because the dependency between each representation can be notated in a numerical way.

There are some algorithms that can directly deal with joint discrete distribution such as Markov chain / Naive Bayes / Bayesian network, tree based, etc. These algorithms can work with categorical data without any encoding; while we should encode the categorical features in a way to represent in a numerically to use as the input features for other ML algorithms. That means it’s better to change the categorical features to numerical most of the times
"""


"""
@file:          constrainedOptimization.py
@description:   answers to problems in exercises 7.3
                (Linear Algebra and its applications, David C. Lay 3ed)
@author:        Hiu-kong Dan
@date:          June 20, 2021
"""

import numpy as np
from numpy import linalg
from sympy import Matrix

def common(A):
    print(Matrix.eigenvects(Matrix(A)))
    print(Matrix.eigenvals(Matrix(A)))
    vecs = np.linalg.eig(A)
    print("unit eigenvectors:")
    for vec in vecs[1]:
        print(vec / np.linalg.norm(vec))
    print("eigenvalues: ", vecs[0])
    print("-"*20)

def problem14():
    A = np.array([[0, .5, 1.5, 15],
                  [.5, 0, 15, 1.5],
                  [1.5, 15, 0, .5],
                  [15, 1.5, .5, 0]])
    common(A)

def problem15():
    A = np.array([[0, 1.5, 2.5, 3.5],
                  [1.5, 0, 3.5, 2.5],
                  [2.5, 3.5, 0, 1.5],
                  [3.5, 2.5, 1.5, 0]])
    common(A)

def problem16():
    A = np.array([[4, -3, -5, -5],
                  [-3., 0, -3, -3],
                  [-5, -3, 0, -1],
                  [-5, -3, -1, 0]])
    common(A)

def problem17():
    A = np.array([[-6, -2, -2, -2],
                  [ -2, -10, 0, 0],
                  [ -2, 0, -13, 3],
                  [ -2, 0, 3, -13]])
    common(A)

if __name__ == '__main__':
    problem14()
    problem15()
    problem16()
    problem17()


"""Normalize features"""

import numpy as np

def normalize(features):
    """Normalize features.

    Normalizes input features X. Returns a normalized version of X where the mean value of
    each feature is 0 and deviation is close to 1.

    :param features: set of features.
    :return: normalized set of features.
    """

    # Copy original array to prevent it from changes.
    features_normalized = np.copy(features).astype(float)

    # Get average values for each feature (column) in X.
    features_mean = np.mean(features, 0)

    # Calculate the standard deviation for each feature.
    features_deviation = np.std(features, 0)

    # Subtract mean values from each feature (column) of every example (row)
    # to make all features be spread around zero.
    if features.shape[0] > 1:
        features_normalized -= features_mean

    # Normalize each feature values so that all features are close to [-1:1] boundaries.
    # Also prevent division by zero error.
    features_deviation[features_deviation == 0] = 1
    features_normalized /= features_deviation

    return features_normalized, features_mean, features_deviation


import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
from matplotlib.colors import Normalize

def hist_match(input_image, spec_hist=None):
    """
    Specify a histogram for image Q3_2.tif, such that by matching the histogram of Q3_2.tif to the specified one,
    the image is enhanced. Implement the specified histogram matching to the input image Q3_2.tif. You may refer to
    the histogram given in the Lecture Notes 3 page 49, but not necessary to use the same one. in your report.
    """

    raw_img = cv.imread(input_image, cv.IMREAD_GRAYSCALE)
    L = 2 ** 8
    if spec_hist is None:
        spec_hist = np.array([1 / L] * L)
    # spec_hist = spec_hist / sum(spec_hist)

    bins = range(L + 1)

    input_hist, _ = np.histogram(raw_img.flat, bins=bins, density=True)
    s = np.array([(L - 1) * input_hist[:k + 1].sum() for k in range(L)], int)
    g = np.array([(L - 1) * spec_hist[:q + 1].sum() for q in range(L)], int)
    # the index of g is implicitly z
    # z = np.array([np.where(g == e)[0][0] if len(np.where(g == e)[0]) != 0 else 0 for e in s], int)
    z = np.array([np.abs(g - e).argmin() for e in s], int)

    # z = []
    # for e in s:
    #     index = np.where(g == e)
    #     lower = upper = e
    #     while index.size is 0:
    #         lower -= 1
    #         upper += 1
    #         index = np.append(np.where(g == lower), np.where(g == upper))

    out_img = np.array([z[r] for r in raw_img], int).reshape(raw_img.shape)
    output_hist, _ = np.histogram(out_img.flat, bins=bins, density=True)

    # %% plots
    norm = Normalize(vmin=0, vmax=255)

    plt.subplot(121)
    plt.imshow(raw_img, cmap='gray', norm=norm)
    plt.title("Raw " + input_image)

    plt.subplot(122)
    plt.imshow(out_img, cmap='gray', norm=norm)
    plt.title("Enhanced " + input_image)
    # plt.savefig(file_name + "_comparison.png")
    plt.show()

    plt.figure(figsize=(9.6, 4.8))
    plt.subplot(131)
    plt.title("Histogram of raw " + input_image)
    plt.bar(range(L), input_hist)
    # plt.xticks([])

    plt.subplot(132)
    plt.title("Specified histogram")
    plt.bar(range(L), spec_hist)
    # plt.xticks([])

    plt.subplot(133)
    plt.title("Histogram of enhanced " + input_image)
    plt.bar(range(L), output_hist)
    # plt.legend(('raw image', 'specified histogram', 'matched image'))
    # plt.savefig(file_name + "_histogram.png")
    plt.show()

    plt.plot(z)
    plt.title("Histogram matching transformation for " + input_image)
    plt.xlabel('$r_k$')
    plt.ylabel('$z_k$')
    plt.show()

    return out_img, output_hist, input_hist  # , z

# %%
out_img_1, out_hist_1, *_ = hist_match('Q3_2.tif')
# out_img_1, out_hist_1, _, z1 = hist_match('Q3_2.tif')

# %%
hist = np.concatenate((np.linspace(0, 7, 8 - 0),
                       np.linspace(7, 0.75, 16 - 8),
                       np.linspace(0.75, 0, 184 - 16),
                       np.linspace(0, 0.5, 200 - 184),
                       np.linspace(0.5, 0, 256 - 200)), axis=0)
hist = hist / hist.sum()
plt.plot(hist)
plt.title("Histogram specified")
plt.xlabel('intensity')
plt.ylabel('percentage')
plt.show()

out_img_2, *_ = hist_match('Q3_2.tif', hist)
# out_img_2, *_, z2 = hist_match('Q3_2.tif', hist)
norm = Normalize(vmin=0, vmax=255)
plt.subplot(121)
plt.imshow(out_img_1, cmap='gray', norm=norm)
plt.title("Histogram equalized")

plt.subplot(122)
plt.imshow(out_img_2, cmap='gray', norm=norm)
plt.title("Enhanced with a specified histogram")
# plt.savefig(file_name + "_comparison.png")
plt.show()

# plt.plot(z1)
# plt.plot(z2)
# plt.title("Histogram matching transformation")
# plt.legend(('equalized histogram', 'specified histogram'))
# plt.show()


import numpy as np

from ezmodel.core.transformation import Transformation

class Plog(Transformation):

    def __init__(self) -> None:
        super().__init__()

    def forward(self, y):
        yp = np.zeros_like(y)

        larger = y >= 0
        yp[larger] = np.log(1 + y[larger])
        yp[~larger] = - np.log(1 - y[~larger])

        return yp

    def backward(self, yp):
        y = np.zeros_like(yp)

        larger = yp >= 0
        y[larger] = np.exp(yp[larger]) - 1
        y[~larger] = 1 - np.exp(-yp[~larger])

        return y


import math
import matplotlib.pyplot as plt
import numpy as np
import random
from .GeneralDistribution import Distribution

class Binomial(Distribution):
    """
    Binomial distribution class for calculating and visualizing a Binomial distribution.

    Attributes:
        mean (float) representing the mean value of the distribution
        stdev (float) representing the standard deviation of the distribution
        data_list (list of floats) a list of floats to be extracted from the data file
        p (float) representing the probability of an event occurring
        n (int) number of trials
    """

    def __init__(self, prob = random.uniform(0,1), size = 20):

        self.n = size
        self.p = prob
        Distribution.__init__(self, self.calculate_mean(), self.calculate_stdev())
        self.data = np.random.binomial(self.n, self.p, 500)

    def calculate_mean(self):

        """
        Function to calculate the mean from p and n

        Args:
            None

        Returns:
            float: mean of the data set

        """

        self.mean = self.p * self.n

        return self.mean

    def calculate_stdev(self):

        """
        Function to calculate the standard deviation from p and n.

        Args:
            None

        Returns:
            float: standard deviation of the data set

        """

        self.stdev = math.sqrt(self.n * self.p * (1 - self.p))

        return self.stdev

    def read_data_file(self, file_name):

        """
        Function to read in data from a txt file. The txt file should have
        one number (float) per line. The numbers are stored in the data attribute.
        After reading in the file, the mean and standard deviation are calculated

        Args:
            file_name (string): name of a file to read from

        Returns:
            float: the p value
            float: the n value

        """

        Distribution.read_data_file(self, file_name)

        try:
            npdata = np.array(self.data)
            assert np.array_equal(npdata, npdata.astype(bool)), 'data contains values other than 0 or 1'
        except AssertionError as error:
            raise

        self.n = len(self.data)
        self.p = 1.0 * sum(self.data) / self.n
        self.mean = self.calculate_mean()
        self.stdev = self.calculate_stdev()

        return self.p, self.n

    def plot_raw_data(self):

        """
        Function to output a histogram of the instance variable data using
        matplotlib pyplot library.

        Args:
            None

        Returns:
            None
        """

        plt.bar(x = ['0', '1'], height = [(1 - self.p) * self.n, self.p * self.n])
        plt.title('Bar Chart of Data')
        plt.xlabel('Outcome')
        plt.ylabel('Count')
        plt.show()

    def pmf(self, k):

        """
        Probability mass function calculator for the binomial distribution.

        Args:
            k (unsigned int): point for calculating the probability mass function. k is in support (the set{0, 1, ..., n})

        Returns:
            float: probability density function output
        """

        try:
            assert k in range(self.n + 1), 'k isn\'t in support'
        except AssertionError as error:
            raise

        n_choose_k = math.factorial(self.n) / (math.factorial(k) * (math.factorial(self.n - k)))
        prob_of_k_successes = (self.p ** k)
        prob_rest_are_failures = (1 - self.p) ** (self.n - k)

        return n_choose_k * prob_of_k_successes * prob_rest_are_failures

    def cdf(self, k):

        """
        Cumulative distribution function calculator for the binomial distribution.

        Args:
            k (unsigned float): point for calculating the cumulative distribution function.

        Returns:
            float: cumulative density function output
        """

        try:
            assert k >= 0, 'k isn\'t nonnegative'
        except AssertionError as error:
            raise

        return sum([self.pmf(x) for x in range(int(math.floor(k)) + 1)])

    def plot(self):

        """
        Function to plot the pmf and cdf of the binomial distribution

        Args:
            None

        Returns:
            list: x values for the both plot
            list: y1 values for the pmf plot
            list: y2 values for cdf plot

        """

        x = []
        y1 = []
        y2 = []

        # calculate the x values to visualize
        for i in range(self.n + 1):
            x.append(i)
            y1.append(self.pmf(i))
            y2.append(self.cdf(i))

        # make the plots
        fig, axes = plt.subplots(2,sharex=True)
        fig.subplots_adjust(hspace=.5)

        axes[0].plot(x, y1)
        axes[0].set_title('Probability Distribution of Outcomes')
        axes[0].set_ylabel('Probability Density')

        axes[1].plot(x, y2)
        axes[1].set_title('Cumulative Distribution of Outcomes')
        axes[1].set_ylabel('Probability')

        plt.show()

        return x, y1, y2

    def __add__(self, other):

        """
        Function to add together two Binomial distributions with equal p

        Args:
            other (Binomial): Binomial instance

        Returns:
            Binomial: Binomial distribution

        """

        try:
            assert self.p == other.p, 'p values are not equal'
        except AssertionError as error:
            raise

        result = Binomial()
        result.n = self.n + other.n
        result.p = self.p
        result.calculate_mean()
        result.calculate_stdev()

        return result

    def __repr__(self):

        """
        Function to output the characteristics of the Binomial instance

        Args:
            None

        Returns:
            string: characteristics of the Binomial

        """

        return "mean = {}, standard deviation = {}, p = {}, n = {}".\
        format(self.mean, self.stdev, self.p, self.n)


import matplotlib.pyplot as plt
import numpy as np

def generate_coin_flips(size=1000, p_heads=0.5, seed=42):
    rng = np.random.default_rng(seed)
    p_tails = 1 - p_heads
    return rng.choice(("H", "T"), size=size, p=(p_heads, p_tails))

def plot_coin_flips(number_of_flips, p_heads):
    fig, ax = plt.subplots()

    flips = generate_coin_flips(number_of_flips, p_heads)
    proportion = np.cumsum(flips == "H") / np.arange(1, number_of_flips + 1)
    xs = np.arange(1, number_of_flips + 1)

    ax.plot(xs, proportion, linewidth=1.2)
    ax.set(xlabel="Number of coin tosses", ylabel="Proportion of heads")
    return fig

plot_coin_flips(1000, 0.3).savefig("01-21a.png", bbox_inches="tight")
plot_coin_flips(1000, 0.03).savefig("01-21b.png", bbox_inches="tight")


import numpy as np

def ramp_filter(order):
    """
    Coefficients for even ordered FIR ramp filter
    """
    if order % 2 == 1:
        raise ValueError('Order is not an even number.')
    # construct ramp filter in the frequency domain
    fr = np.linspace(0, 1, order / 2 + 1)
    fr = np.concatenate((fr, fr[-2:0:-1]))
    # real part from the response
    # imaginary part due to loss of precision
    fr = np.real(np.fft.ifftshift(np.fft.ifft(fr)))
    # normalise
    return list(fr / np.max(fr))

if __name__ == '__main__':
    print ramp_filter(64)


#!python
#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
Multiplies two square matrices together using a *single* block of threads and
global memory only. Each thread computes one element of the resulting matrix.
"""

import numpy as np
from pycuda import driver, compiler, gpuarray, tools

# -- initialize the device
import pycuda.autoinit

kernel_code_template = """
__global__ void MatrixMulKernel(float *a, float *b, float *c)
{
    // 2D Thread ID (assuming that only *one* block will be executed)
    int tx = threadIdx.x;
    int ty = threadIdx.y;

    // Pvalue is used to store the element of the matrix
    // that is computed by the thread
    float Pvalue = 0;

    // Each thread loads one row of M and one column of N,
    //   to produce one element of P.
    for (int k = 0; k < %(MATRIX_SIZE)s; ++k) {
        float Aelement = a[ty * %(MATRIX_SIZE)s + k];
        float Belement = b[k * %(MATRIX_SIZE)s + tx];
        Pvalue += Aelement * Belement;
    }

    // Write the matrix to device memory;
    // each thread writes one element
    c[ty * %(MATRIX_SIZE)s + tx] = Pvalue;
}
"""

# define the (square) matrix size
#  note that we'll only use *one* block of threads here
#  as a consequence this number (squared) can't exceed max_threads,
#  see http://documen.tician.de/pycuda/util.html#pycuda.tools.DeviceData
#  for more information on how to get this number for your device
MATRIX_SIZE = 2

# create two random square matrices
a_cpu = np.random.randn(MATRIX_SIZE, MATRIX_SIZE).astype(np.float32)
b_cpu = np.random.randn(MATRIX_SIZE, MATRIX_SIZE).astype(np.float32)

# compute reference on the CPU to verify GPU computation
c_cpu = np.dot(a_cpu, b_cpu)

# transfer host (CPU) memory to device (GPU) memory
a_gpu = gpuarray.to_gpu(a_cpu)
b_gpu = gpuarray.to_gpu(b_cpu)

# create empty gpu array for the result (C = A * B)
c_gpu = gpuarray.empty((MATRIX_SIZE, MATRIX_SIZE), np.float32)

# get the kernel code from the template
# by specifying the constant MATRIX_SIZE
kernel_code = kernel_code_template % {
    'MATRIX_SIZE': MATRIX_SIZE
    }

# compile the kernel code
mod = compiler.SourceModule(kernel_code)

# get the kernel function from the compiled module
matrixmul = mod.get_function("MatrixMulKernel")

# call the kernel on the card
matrixmul(
    # inputs
    a_gpu, b_gpu,
    # output
    c_gpu,
    # (only one) block of MATRIX_SIZE x MATRIX_SIZE threads
    block = (MATRIX_SIZE, MATRIX_SIZE, 1),
    )

# print the results
print("-" * 80)
print("Matrix A (GPU):")
print(a_gpu.get())

print("-" * 80)
print("Matrix B (GPU):")
print(b_gpu.get())

print("-" * 80)
print("Matrix C (GPU):")
print(c_gpu.get())

print("-" * 80)
print("CPU-GPU difference:")
print(c_cpu - c_gpu.get())

np.allclose(c_cpu, c_gpu.get())


"""
Contains various functions for computing statistics over 3D volumes
"""
import numpy as np

def Dice3d(a, b):
    """
    This will compute the Dice Similarity coefficient for two 3-dimensional volumes
    Volumes are expected to be of the same size. We are expecting binary masks -
    0's are treated as background and anything else is counted as data

    Arguments:
        a {Numpy array} -- 3D array with first volume
        b {Numpy array} -- 3D array with second volume

    Returns:
        float
    """
    if len(a.shape) != 3 or len(b.shape) != 3:
        raise Exception(f"Expecting 3 dimensional inputs, got {a.shape} and {b.shape}")

    if a.shape != b.shape:
        raise Exception(f"Expecting inputs of the same shape, got {a.shape} and {b.shape}")

    # Write implementation of Dice3D. If you completed exercises in the lessons
    # you should already have it.

    intersection = np.sum((a > 0) * (b > 0))
    sum_volumes = np.sum(a > 0) + np.sum(b > 0)
    if sum_volumes == 0:
        return -1
    return 2.0 * float(intersection) / float(sum_volumes)

def Jaccard3d(a, b):
    """
    This will compute the Jaccard Similarity coefficient for two 3-dimensional volumes
    Volumes are expected to be of the same size. We are expecting binary masks -
    0's are treated as background and anything else is counted as data

    Arguments:
        a {Numpy array} -- 3D array with first volume
        b {Numpy array} -- 3D array with second volume

    Returns:
        float
    """
    if len(a.shape) != 3 or len(b.shape) != 3:
        raise Exception(f"Expecting 3 dimensional inputs, got {a.shape} and {b.shape}")

    if a.shape != b.shape:
        raise Exception(f"Expecting inputs of the same shape, got {a.shape} and {b.shape}")

    # Write implementation of Jaccard similarity coefficient. Please do not use
    # the Dice3D function from above to do the computation ;)
    intersection = np.sum((a > 0) * (b > 0))
    sum_volumes = np.sum(a > 0) + np.sum(b > 0) - intersection
    if sum_volumes == 0:
        return -1
    return float(intersection) / float(sum_volumes)

def Spec3d(a, b):
    '''
    This will compute the Sensitivity for two 3-dimensional volumes
    Volumes are expected to be of the same size. We are expecting binary masks -
    0's are treated as background and anything else is counted as data

    Arguments:
        a {Numpy array} -- prediction 3D array with first volume
        b {Numpy array} -- ground truth 3D array with second volume

    Returns:
        float
    '''

    if len(a.shape) != 3 or len(b.shape) != 3:
        raise Exception(f"Expecting 3 dimensional inputs, got {a.shape} and {b.shape}")

    if a.shape != b.shape:
        raise Exception(f"Expecting inputs of the same shape, got {a.shape} and {b.shape}")

    tn = float(np.sum((b==0)*(a==0)))
    fp = float(np.sum((b==0)*(a > 0)))

    if (tn + fp) == 0:
        return -1
    return tn / (tn + fp)

def Sens3d(a, b):
    '''
    This will compute the Sensitivity for two 3-dimensional volumes
    Volumes are expected to be of the same size. We are expecting binary masks -
    0's are treated as background and anything else is counted as data

    Arguments:
        a {Numpy array} -- prediction 3D array with first volume
        b {Numpy array} -- ground truth 3D array with second volume

    Returns:
        float
    '''

    if len(a.shape) != 3 or len(b.shape) != 3:
        raise Exception(f"Expecting 3 dimensional inputs, got {a.shape} and {b.shape}")

    if a.shape != b.shape:
        raise Exception(f"Expecting inputs of the same shape, got {a.shape} and {b.shape}")

    tp = float(np.sum((b > 0)*(a > 0)))
    fn = float(np.sum((b > 0)*(a == 0)))

    if (tp + fn) == 0:
        return -1
    return tp / (tp + fn)


"""
Implementing the exact Heston Scheme by Broadie and Kaya (2003) (WIP)

"""

import numpy as np

def generate_CIR_paths(n_samples, alpha, b, sigma, dt, v_0, T, seed = 2021):
    """
    Implements exact Cox-Ingersoll-Ross / square-root diffusion process
    based on Glasserman (2004)

    Exact simulation of the process V_t with dynamics given by the SDE:

    $$dV_{t} = alpha(b - V_{t}) dt + sigma sqrt(V_{t}) dW_{t}$$

    Inputs:

    + n_samples : Number of Vts to simulate
    + alpha : speed of mean reversion
    + b: mean level of variance
    + sigma: volatility of the variance
    + dt: time-increment, e.g. dt = 1 / 250 years
    + v_0: initial variance
    + T: (integer) number of time steps
    + seed: numpy seed for reproducibility

    Outputs:

    + Vts: A n_samples by (T + 1) numpy array, where in each row corresponds
    to a path V_{i, t} from 0 .. T
    """
    rng = np.random.default_rng(seed)

    Vts = np.zeros((n_samples, T + 1))
    Vts[:,0] = v_0
    d = 4 * b * alpha / sigma ** 2
    c = sigma ** 2  * (1 - np.exp(-alpha * dt)) / (4 * alpha)

    if d > 1:
        Zs = rng.random_normal(size=(n_samples, T + 1))
        chi_sqs = rng.chisquare (df = d - 1, size=(n_samples, T + 1))
    else:
        poissons = np.zeros((n_samples, T + 1))
        chi_sqs = np.zeros((n_samples, T + 1))

    for i in range(n_samples):
        for t in range(T):
            l = Vts[i, t] * np.exp(-alpha * dt) / c
            if d > 1:
                Vts[i, t + 1] = c * ((Zs[i, t] + np.sqrt(l)) ** 2 + chi_sqs[i, t])
            else:
                poissons[i, t] = rng.poisson(lam = l / 2)
                chi_sqs[i, t] = rng.chisquare(df = d + 2 * poissons[i, t])
                Vts[i, t + 1] = c * chi_sqs[i, t]
    return Vts

def generate_Heston_paths(n_samples, S0, rho, r, alpha, b, sigma, dt, v_0, T, seed = 2021):
    """
    Almost Exact Heston scheme for the process with SDE

    $$dS_{t} = rS_{t} dt + S_{t} sqrt(V_{t}) dW_{t}$$

    $$dV_{t} = alpha(b - V_{t}) dt + sigma sqrt(V_{t}) dZ_{t}$$

    $$corr(dW_{t}, dZ_{t}) = rho$$

    Inputs:

    + n_samples : Number of Vtss to simulate
    + S0: Initial Stock Price
    + rho: correlation
    + r: risk-free rate
    + alpha : speed of mean reversion
    + b: mean level of variance
    + sigma: volatility of the variance
    + dt: time-increment, e.g. dt = 1 / 250 years
    + v_0: initial variance
    + T: (integer) number of time steps
    + seed: numpy seed for reproducibility

    Outputs:

    + Vts: A n_samples by T numpy array, where in each row corresponds
    to a path
    """

    rng = np.random.default_rng(seed)
    Zs = rng.standard_normal(size = (n_samples, T)) # random normal increments
    Zs = (Zs - Zs.mean(axis= 0)) / Zs.std(axis = 0)

    # pre-generate Vts
    Vts = generate_CIR_paths(n_samples = n_samples, alpha = alpha,
                             b = b, sigma = sigma,
                             dt = dt, v_0 = v_0, T = T, seed = seed)

    # simulate the log-price process
    log_Sts = np.zeros((n_samples, T + 1))
    log_Sts[:, 0] = np.log(S0)

    k0 = (r - rho / sigma * alpha * b) * dt
    k1 = (rho * alpha / sigma - 0.5) * dt - rho / sigma
    k2 = rho / sigma

    for i in range(T):
        log_Sts[:, i + 1] = (log_Sts[:,i] + k0 + k1 * Vts[:,i] + k2 * Vts[:,i+1] +
                              np.sqrt((1.0 - rho ** 2) * Vts[:,i]) * np.sqrt(dt) * Zs[:, i])

    return np.exp(log_Sts), Vts


# -*- coding: utf-8 -*-
"""
Created on Wed Sep 18 09:59:36 2019

@author: BSWOOD9321
"""
import numpy as np
import matplotlib.pyplot as plt

x = np.arange(0,10,.01)
y = [0]
i=1
h=.01

while i <=999:
    func = x[i]**2
    y.append(func)
    i=i+1

Summation = h*np.sum(y)
print("---------------------------")
print("Function: x^2, from 0-10")
print("Limit integration: ",Summation)

Trapzsum = np.trapz(x**2,dx=.01)

print("Trapz integration: ",Trapzsum)

import scipy.integrate as ig
f = lambda x: x**2
ig.quad(f,0,10)
print("SciPy Integrate (Quadratic): ",ig.quad(f,0,10))

Simps=ig.simps(x**2,x,.01)
print("SciPy Integrate (Simpsons Rule): ",Simps)

print("The reason our summation formula is not very precise is that it is creating boxes of height x that are then added together to find the value of the integral." )
print("This means the width of the box is a finite value, instead in infinitessimal, and may cross over the function, giving us a value that is not very precise.")
print("-------------------------------------")
print("Function sin(100x) from 0-2Pi")

x1=np.arange(0,2*np.pi,np.pi/500)
y1=[]
i1=0
h1=(np.pi/500)
while i1<=999:
    func1=np.sin(100*x1[i1])
    y1.append(func1)
    i1=i1+1

Sum1=h1*np.sum(y1)
print("Limit Integration: ",Sum1)
Trapzsum1= np.trapz(np.sin(100*x1),dx=(np.pi/500))
print("Trapz Integration: ",Trapzsum1)
f1 = lambda x1: np.sin(100*x1)
print("SciPy Integrate (Quadratic): ",ig.quad(f1,0,np.pi*2))
print("SciPy Integrate (Simpsons Rule): ",ig.simps(np.sin(100*x1),x,np.pi/500))
print("--------------------------------------")

x2=np.arange(0,2*np.pi,np.pi/500)
y2=[]
i2=0
h2=(np.pi/500)
while i2<=999:
    a=x2[i2]
    func2=(np.sin(100*a))**a
    y2.append(func2)

    i2=i2+1

Sum2=h2*np.sum(np.nan_to_num(y2))
print("sin^x(100x)")
print("Limit Integration: ",Sum2)
print("This is the only type of integration I have been able to do, as I keep getting NaN and double scalar errors")
print("-------------------------------")


import os
import numpy as np
import matplotlib.pyplot as plt
from scipy.io import loadmat
from svm import SVM

#  Exercise 6 | Support Vector Machines

def linearKernel(x1, x2):
    return x1.T @ x2

def gaussianKernel(x1, x2, sigma):
    diff = x1 - x2
    return np.exp(-(diff.T @ diff)/(2*sigma*sigma))

def plotData(x, y):
    pos = np.where(y==1)
    neg = np.where(y==0)
    plt.plot(x[pos[0],0], x[pos[0],1], 'b+', lw = 1.5, ms = 4)
    plt.plot(x[neg[0],0], x[neg[0],1], 'ko', mfc = 'y', ms = 4)

def visualizeBoundaryLinear(X, y, model):
    w = model.w
    b = model.b
    xp = np.linspace(min(X[:,0]), max(X[:,0]), 100)
    yp = - (w[0] * xp + b)/w[1]
    plotData(X, y)
    plt.plot(xp, yp, '-b')

def visualizeBoundary(X, y, model):
    #VISUALIZEBOUNDARY plots a non-linear decision boundary learned by the SVM

    # Plot the training data on top of the boundary
    plotData(X, y)

    # Make classification predictions over a grid of values
    x1plot = np.linspace(min(X[:,0]), max(X[:,0]), 100)
    x2plot = np.linspace(min(X[:,1]), max(X[:,1]), 100)
    X1, X2 = np.meshgrid(x1plot, x2plot)
    vals = np.zeros(X1.shape)
    for i in range(X1.shape[1]):
        this_X = np.c_[X1[:, i], X2[:, i]]
        vals[:, i] = model.svmPredict(this_X)

    # Plot the SVM boundary
    plt.contour(X1, X2, vals, [0.5])

def dataset3Params(X, y, Xval, yval):
    Cs = np.array([0.01, 0.03, 0.1, 0.3, 1, 3, 10, 30])
    sigmas = Cs
    model = SVM()

    costs = np.zeros((Cs.size, sigmas.size))
    for i in range(Cs.size):
        for j in range(sigmas.size):
            sigma = sigmas[j]
            #need to define gaussianKernelLambda lambda here again to capture sigma
            gaussianKernelLambda = lambda x1, x2: (gaussianKernel(x1, x2, sigma))
            gaussianKernelLambda.__name__ = 'gaussianKernel'

            model.svmTrain(X, y.astype(float), Cs[i], gaussianKernelLambda)
            predictions = model.svmPredict(Xval)
            costs[i, j] = np.mean(predictions != yval)
    i, j = np.unravel_index(np.argmin(costs), costs.shape)
    return (Cs[i], sigmas[j])

## =============== Part 1: Loading and Visualizing Data ================
print('Loading and Visualizing Data ...')

# Load from ex6data1:
# You will have X, y in your environment
scriptdir = os.path.dirname(os.path.realpath(__file__))
data = loadmat(scriptdir + '//ex6data1.mat')
X = data['X']
y = data['y'].ravel()
# Plot training data
plotData(X, y)
plt.show()

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

## ==================== Part 2: Training Linear SVM ====================
#  The following code will train a linear SVM on the dataset and plot the
#  decision boundary learned.

print('\nTraining Linear SVM ...')

# You should try to change the C value below and see how the decision
# boundary varies (e.g., try C = 1000)
C = 1
model = SVM()
#we passed y.astype(float), firstly to be sure y -1 can be assigned to singed int.
#secondly we want to preserve original y to 0 and 1
model.svmTrain(X, y.astype(float), C, linearKernel, 1e-3, 20)

visualizeBoundaryLinear(X, y, model)
plt.show()
input('Program paused. Press enter to continue.\n')

## =============== Part 3: Implementing Gaussian Kernel ===============

print('\nEvaluating the Gaussian Kernel ...')

x1 = np.array([1, 2, 1])
x2 = np.array([0, 4, -1])
sigma = 2

#use lambda to be able to pass sigma
gaussianKernelLambda = lambda x1, x2: (gaussianKernel(x1, x2, sigma))
gaussianKernelLambda.__name__ = 'gaussianKernel'

sim = gaussianKernelLambda(x1, x2)

print(f'Gaussian Kernel between x1 = [1c 2, 1], x2 = [0, 4, -1], sigma = {sigma} :')
print(f'\t{sim}\n(for sigma = 2, this value should be about 0.324652)\n')

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

## =============== Part 4: Visualizing Dataset 2 ================

print('Loading and Visualizing Data ...')

# Load from ex6data2:
# You will have X, y in your environment
data = loadmat(scriptdir + '//ex6data2.mat')
X = data['X']
y = data['y'].ravel()

# Plot training data
plotData(X, y)
plt.show()

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

## ========== Part 5: Training SVM with RBF Kernel (Dataset 2) ==========

print('\nTraining SVM with RBF Kernel (this may take 1 to 2 minutes) ...')

# SVM Parameters
C = 1; sigma = 0.1

# We set the tolerance and max_passes lower here so that the code will run
# faster. However, in practi ce, you will want to run the training to
# convergence.
model.svmTrain(X, y.astype(float), C, gaussianKernelLambda)
visualizeBoundary(X, y, model)
plt.show()
input('Program paused. Press enter to continue.')

## =============== Part 6: Visualizing Dataset 3 ================

print('Loading and Visualizing Data ...')

# Load from ex6data3:
# You will have X, y in your environment
data = loadmat(scriptdir + '//ex6data3.mat')

X = data['X']
y = data['y'].ravel()
Xval = data['Xval']
yval = data['yval'].ravel()

# Plot training data
plotData(X, y)
plt.show()

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

## ========== Part 7: Training SVM with RBF Kernel (Dataset 3) ==========

#  This is a different dataset that you can use to experiment with. Try
#  different values of C and sigma here.

# Try different SVM Parameters here
C, sigma = dataset3Params(X, y, Xval, yval)
# Train the SVM
model.svmTrain(X, y.astype(float), C, gaussianKernelLambda)
visualizeBoundary(X, y, model)
plt.show()
input('Program paused. Press enter to continue.\n')


import numpy as np

def qr_householder(A):
    """
    Dekomposisi QR menguraikan matriks A berbentuk (m, n) menjadi an
    matriks ortogonal Q bentuk (m, m) dan matriks segitiga atas R dari
    bentuk (m, n). Perhatikan bahwa matriks A tidak harus persegi. Ini
    metode penguraian A menggunakan refleksi Householder, yaitu
    stabil secara numerik dan kompleksitas O(n^3).

    informasi lebih lanjut
    https://en.wikipedia.org/wiki/QR_decomposition#Using_Householder_reflections

    >>> A = np.array([[12, -51, 4], [6, 167, -68], [-5, 24, -14]], dtype=float)
    >>> Q, R = qr_householder(A)

    >>> # cek apabila dekomposisi benar
    >>> np.allclose(Q@R, A)
    True
    """
    m, n = A.shape
    t = min(m, n)
    Q = np.eye(m)
    R = A.copy()

    for k in range(t - 1):
        x = R[k:, [k]]
        e1 = np.zeros_like(x)
        e1[0] = 1.0
        alpha = np.linalg.norm(x)
        v = x + np.sign(x[0]) * alpha * e1
        v /= np.linalg.norm(v)

        Q_k = np.eye(m - k) - 2.0 * v @ v.T
        Q_k = np.block([[np.eye(k), np.zeros((k, m - k))], [np.zeros((m - k, k)), Q_k]])

        Q = Q @ Q_k.T
        R = Q_k @ R

    return Q, R

if __name__ == "__main__":
    import doctest

    doctest.testmod()


import numpy as np
import scipy.linalg as spla

def gen_diag_dom(n, a, b):
    """ Generates a diagonally dominant random matrix"""
    m = (b-a) * np.random.rand(n,n) + a
    for i in range(n):
        tmp = np.sum(np.abs(m[i,:]))
        m[i,i] = (tmp-(10*tmp)) * np.random.rand() + tmp
    return m
def gen_tridiag(n, a, b):
    m = np.zeros((n, n))
    m[0, 0:2] = (b-a) * np.random.rand(2) + a
    m[n-1, n-2:n] = (b-a) * np.random.rand(2) + a
    for i in range(1, n-1):
        m[i, i-1:i+2] = (b-a)* np.random.rand(3) + a
    return m

def gauss(a, b, allow_overwrite=False):
    """
        Solves  ax=b using gaussian elimation and returns x.

        Parameters:
        a: (n,n) array
        b: (n)  array
        allow_overwrite: Allow the code to overwrite the source matrices
    """
    n = a.shape[0]
    x = np.empty(n)
    pvtv = np.arange(0, n, 1)
    if allow_overwrite:
        a1 = a
        b1 = b
    else:
        a1 = np.copy(a)
        b1 = np.copy(b)
    s = np.amax(abs(a1), axis=1)
    #print('Maximum of each row is {0}'.format(s))
    #For each column
    for i in range(n-1):
        #print('Before elimination our matrix looks like')
        #print(a1)
        gpivot(a1, pvtv, i, s)
        #Check for division by 0
        if np.isclose(a[pvtv[i]/s[pvtv[i]], i], 0):
            #print('Coefficient a[{0}, {1}] with value {2} is too close to 0'.format(pvtv[i], i, a[pvtv[i], i]))
            raise ZeroDivisionError

        geliminate(a1, b1, pvtv, i)
        #print('After elimination our matrix looks like')
        #print(a1)
    #print('Our matrix now looks like')
    #print(a1)
    #print('Starting the substitution process')
    gsubstitute(a1, b1, pvtv, x, n-1)
    return x

def gpivot(a, pvtv, ind, s):
    #pvtv[ind] is the original row
    p = pvtv[ind]
    v = abs(a[p, ind])/s[pvtv[ind]]
    #print('The current pivot element is {0} on row {1}'.format(v, p))
    #print('Searching for a new pivot in rows {0} while excluding {1}'.format(pvtv[ind+1:], pvtv[:ind]))
    #i is the actual index in the pivot vector
    for i in range(ind+1, len(pvtv)):
        vv = abs(a[pvtv[i], ind])/s[pvtv[i]]
        #print('Comparing {0} and {1} for a new pivot'.format(vv, v))
        if vv > v:
            #print('Found a new potential pivot at row {0}'.format(pvtv[i]))
            v = vv
            p = i
    if p != pvtv[ind]:
        #print('The new pivot is row {0} and the old was {1}'.format(pvtv[p], pvtv[ind]))
        tmp = pvtv[ind]
        pvtv[ind] = pvtv[p]
        pvtv[p] = tmp
    #print('The pivot vector looks like: {0}'.format(pvtv))

def geliminate(a, b, pvtv, ind):
    for i in pvtv[ind+1:]:
        #print('Eliminating row {0} with row {1}'.format(i, pvtv[ind]))
        c = a[i, ind]/a[pvtv[ind], ind]
        a[i, ind:] -= c*a[pvtv[ind], ind:]
        b[i] -= c*b[pvtv[ind]]

def lueliminate(a, pvtv, ind):
    for i in pvtv[ind+1:]:
        #print('Eliminating row {0} with row {1}'.format(i, pvtv[ind]))
        c = a[i, ind]/a[pvtv[ind], ind]
        a[i, ind:] -= c*a[pvtv[ind], ind:]
        # print('Putting the value of c ({3}) at index a[{0},{1}] the current value is {2}'.format(i, ind, a[i,ind], c))
        a[i, ind] = c
        # print('Normally we would perform b[{0}] -= c*b[{1}]'.format(i, pvtv[ind]))

def gsubstitute(a, b, pvtv, x, n):
    x[n] = b[pvtv[n]]/a[pvtv[n], n]
    #print('The first x value is {0}'.format(x[n]))
    for i in range(n-1, -1, -1):
        #print("We're on column {0} and we're starting on row {1}".format(i, pvtv[i]))
        #print('Starting with b = {0}'.format(b[i]))
        acc = b[pvtv[i]]
        for j in range(n , i, -1):
            acc -= a[pvtv[i], j] * x[j]
        x[i] = acc/a[pvtv[i], i]
def lu_factor(a, allow_overwrite=False):
    """
        Factors a such that the returned matrix and pvtv table can be passed to lu_solve to solve for a given
        b vector.

        Parameters:
        a: (n,n) array
        allow_overwrite: Allow the code to overwrite the source matrices
    """
    n = a.shape[0]
    pvtv = np.arange(0, n, 1)
    if allow_overwrite:
        a1 = a
    else:
        a1 = np.copy(a)
    s = np.amax(abs(a1), axis=1)
    #print('Maximum of each row is {0}'.format(s))
    #For each column
    for i in range(n-1):
        #print('Before elimination our matrix looks like')
        #print(a1)
        gpivot(a1, pvtv, i, s)
        #Check for division by 0
        if np.isclose(a[pvtv[i]/s[pvtv[i]], i], 0):
            #print('Coefficient a[{0}, {1}] with value {2} is too close to 0'.format(pvtv[i], i, a[pvtv[i], i]))
            raise ZeroDivisionError

        lueliminate(a1, pvtv, i)
        #print('After elimination our matrix looks like')
        #print(a1)
    #print('Our matrix now looks like')
    #print(a1)
    #print('Starting the substitution process')
    return (a1,pvtv)
def lu_solve(a, b, pvtv, allow_overwrite=False):
        n = a.shape[0]
        x = np.empty(n)
        if allow_overwrite:
            a1 = a
            b1 = b
        else:
            a1 = np.copy(a)
            b1 = np.copy(b)
        #Eliminate the b's as we would have before
        for i in range(n-1):
            #i is our current column
            for j in range(i+1, n):
                # print('Grabbing c value from a[{0},{1}]'.format(pvtv[j], i))
                # print('Performing b[{0}] - c*b[{1}] with c = {2}]'.format(pvtv[j], pvtv[i], a[pvtv[j], i]))
                b1[pvtv[j]] -= a1[pvtv[j], i] * b1[pvtv[i]]
        # print('The b1 vector before after forward looks like {0}'.format(b1))
        #Now back substitute
        gsubstitute(a1, b1, pvtv, x, n-1)
        return x
def inv(a):
    """ Returns the inverse of the matrix a"""
    aa, pvt = lu_factor(a)
    n = a.shape[0]
    ai = np.empty(a.shape)
    for i in range(n):
        b = np.zeros(n)
        b[i] = 1
        ai[:,i] = lu_solve(aa, b, pvt)
    return ai

def gauss_seidel(A, b, x, tol, maxi, lam):
    """ x should contain initial guesses (can be 0)"""
    dim = A.shape[0]
    #Divide everything by each row by its diagnol element
    for i in range(dim):
        tmp = A[i,i]
        for j in range(dim):
            A[i,j] /= tmp
        b[i] /= tmp
        # print(A)
    for i in range(dim):
        acc = b[i]
        for j in range(dim):
            if i == j:
                # print("Skipping i = {0} and j = {1}".format(i, j))
                continue
            else:
                acc -= A[i, j] * x[j]
        # print("Old x = {0}, new x = {1}".format(x[i], acc))
        x[i] = acc
    for i in range(maxi):
        flag = 1
        for k in range(dim):
            acc = b[k]
            oldx = x[k]
            for j in range(dim):
                if k == j:
                    continue
                else:
                    # print('k = {0}, j={1}'.format(k, j))
                    acc -= (A[k,j] * x[j])
                    # print(acc)
            # print("Old x = {0}, new x = {1}".format(oldx, (lam * acc) + ((1-lam) * oldx)))
            x[k] = (lam * acc) + ((1-lam) * oldx)
            if flag ==1 and x[k] != 0:
                ea = abs((x[k] - oldx)/x[k]) * 100
                # print("Error is equal to {0}".format(ea))
                if ea > tol:
                    flag = 0
        if flag == 1:
            print('Breaking with ea = {0} and num iterations: {1}'.format(ea, i))
            break
    return x
def thomas_factor(a, allow_overwrite=False):
    """ Returns a LU matrix for a tridiagnol system"""
    n = a.shape[0]
    if allow_overwrite:
        a1 = a
    else:
        a1 = np.copy(a)
    for i in range(1,n):
        a1[i,i-1] /= a1[i-1, i-1]
        a1[i,i] -= a1[i,i-1] * a1[i-1, i]
    return a1
def thomas_solve(a, b, allow_overwrite=False):
    """Solves the factored matrix from thomas_factor for  given b vector"""
    n = a.shape[0]
    x = np.empty(n)
    if allow_overwrite:
        a1 = a
        b1 = b
    else:
        a1 = np.copy(a)
        b1 = np.copy(b)
    #Forward substitute
    for i in range(1,n):
        b1[i] -= a1[i, i-1] * b1[i-1]
    x[n-1] = b1[n-1]/a1[n-1, n-1]
    for i in range(n-2, -1, -1):
        x[i] = (b1[i]-a[i, i+1] * x[i+1])/ a1[i, i]
    return x
if __name__ == "__main__":
    n = 40
    # m = (-200) * np.random.rand(3,3) + 100
    # mm = (-200) * np.random.rand(3) + 100
    # #print("The answers are {0}".format(mm))
    # x1 = spla.solve(m,mm)
    # x2 = gauss(m,mm)
    # aa, pvt = lu_factor(m)
    # x3 = lu_solve(aa, mm, pvt)
    # print("x vector according to scipy is {0}".format(x1))
    # print("x vector according to my gauss is {0}".format(x2))
    # print('x vector according my lu is {0}'.format(x3))
    # print('Scipy gauss is close to my gauss? {0}'.format(np.isclose(x1,x2)))
    # print('Scipy gauss is close to my LU? {0}'.format(np.isclose(x1,x3)))
    a1 = gen_diag_dom(n,-100,100)
    b1 = (-200) * np.random.rand(n) + 100
    # ai = inv(m)
    # print('A * A^-1 = \\n{0}'.format(np.dot(m, ai)))
    x1 = spla.solve(a1,b1)
    x2 = gauss(a1,b1)
    aa, pvt = lu_factor(a1)
    x3 = lu_solve(aa, b1, pvt)
    x4 = gauss_seidel(a1,b1, np.zeros(n), 0.0001, 25, 1)
    # print("x vector according to scipy is {0}".format(x1))
    # print("x vector according to my gauss is {0}".format(x2))
    # print('x vector according my lu is {0}'.format(x3))
    # print('x vector according to my GS is {0}'.format(x4))
    a2 = gen_tridiag(n, -100, 100)
    print(a2)
    aa2 = thomas_factor(a2)
    print(aa2)
    x5 = thomas_solve(aa2, b1)
    x6 = spla.solve(a2,b1)
    print("x vector according to scipy is {0}".format(x6))
    print('The results of the thomas algorithm are: {0}'.format(x5))


#!/usr/bin/env python

import numpy as np
#import matplotlib.pyplot as plt
#from mpl_toolkits.mplot3d import Axes3D

def unit_vector(vector):
    """ Returns the unit vector of the vector."""
    return vector / np.linalg.norm(vector)

def angle_between(v1, v2):
    """Finds angle between two vectors"""
    v1_u = unit_vector(v1)
    v2_u = unit_vector(v2)
    return np.arccos(np.clip(np.dot(v1_u, v2_u), -1.0, 1.0))

def x_rotation(vector,theta):
    """Rotates 3-D vector around x-axis"""
    R = np.array([[1,0,0],[0,np.cos(theta),-np.sin(theta)],[0, np.sin(theta), np.cos(theta)]])
    return np.dot(R,vector)

def y_rotation(vector,theta):
    """Rotates 3-D vector around y-axis"""
    R = np.array([[np.cos(theta),0,np.sin(theta)],[0,1,0],[-np.sin(theta), 0, np.cos(theta)]])
    return np.dot(R,vector)

def z_rotation(vector,theta):
    """Rotates 3-D vector around z-axis"""
    R = np.array([[np.cos(theta), -np.sin(theta),0],[np.sin(theta), np.cos(theta),0],[0,0,1]])
    return np.dot(R,vector)

def translation(vector, xyz):
    return vector + np.array(xyz)

if __name__=="__main__":
    vector = np.array([1,0,0])

    print z_rotation(vector, np.pi/4)

    print translation(vector, [1,1,1])


"""
This method creates a single instance of the FlatLambdaCDM in astropy and uses it to
evolve magnitudes with redshift (via the luminosity distance relation).

FlatLambdaCDM model with constraints of H0 = 67.6, Om0 = 0.3089 (values from SDSS BOSS, published 2016-07-13 and
        1-Omega_Lambda = 1 - 0.6911 = 0.3089 from the Planck Collaboration in 2015)
"""
from math import log10
from typing import List, Tuple, Union

from astropy import units
from astropy.cosmology import FlatLambdaCDM

__cosmo = FlatLambdaCDM( H0=67.6, Om0=0.3089 )

def luminsoity_distance_from_redshift( z: float ) -> float:
    """
    Calculates the luminosity distance (in parsecs) at redshift z using the Flat Lambda CDM

    :param z: Redshift at which to calculate luminosity distance
    :type z: float
    :return: Luminosity distance at redshift z in terms of parsecs
    :rtype: float
    """
    return __cosmo.luminosity_distance( z ).to( units.parsec ).value

def magnitude_evolution( m0: float, z0: float, zrange: tuple = (0.46, 0.82), step: float = 0.01,
                         splitLists: bool = False ) -> Union[ List[
                                                                  Tuple[ float, float, float ] ], Tuple[
                                                                  tuple, tuple, tuple ] ]:
    """
    Generates the apparent magnitude evolution of a spectrum given its magnitude m0 at redshift z0.  Default zrange = ( 0.46, 0.82 ), step size of 0.01.

    Returns a list from lowest to highest redshift of tuples ( z, apparent magnitude at z, luminosity distance )

    If splitList is True, returns a single tuple of three lists ( ( z ), ( magnitude ), ( ld ) )

    :param m0: Initial apparent magnitude
    :param z0: Initial redshift at which apparent magnitude was observed
    :param zrange: Range of redshifts to calculate evolution over.  Defaults to ( 0.46, 0.82 ).
        Will run loop from low value to high value + step such that the high value is included
    :param step: step size to iterate over zrange.  Defaults to 0.01
    :param splitLists: If True, returns a single tuple of ( ( z_data ), ( magnitude_data ), ( luminosity_distance ) ) lists
    :type m0: float
    :type z0: float
    :type zrange: tuple
    :type step: float
    :type splitLists: bool
    :return: List of tuples [ ( redshift, apparent_magnitude at z, luminosity_distance), ... ] or Tuple of tuples ( ( z ), ( mag ), ( ld ) )
    :rtype: list or tuple
    """
    from numpy import arange
    zlow, zhigh = zrange

    M_init = m0 - 5 * (log10( luminsoity_distance_from_redshift( z0 ) ) - 1)

    evolist = list()
    for z in arange( zlow, zhigh + step, step ):
        dL = luminsoity_distance_from_redshift( z )
        m = M_init + 5 * (log10( dL ) - 1)

        evolist.append( (z, m, dL) )
    if splitLists:
        return tuple( zip( *evolist ) )
    return evolist

def absolute_magnitude( m0, z0 ):
    """
    Returns absolute magnitude of a spectrum given apparent magnitude m0 at redshift z0
    via the relation

    M_abs = m_app - 5 * log10( Luminosity Distance - 1 )

    :param m0: Apparent magnitude
    :param z0: Redshift
    :type m0: float
    :type z0: float
    :return: Absolute magnitude
    :rtype: float
    """
    return m0 - 5 * (log10( luminsoity_distance_from_redshift( z0 ) ) - 1)

def apparent_magnitude_at_redshift( m0, z0, z ):
    """
    Given a known apparent magnitude m0 and redshift z0, returns the apparent magnitude of an object if
    it were moved to redshift z using the Flat Lambda CDM.

    :param m0: Known apparent magnitude
    :param z0: Known redshift
    :param z: Redshift at which apparent magnitude is desired
    :type m0: float
    :type z0: float
    :type z: float
    :return: Apparent magnetude at redshift z
    :rtype: float
    """
    return absolute_magnitude( m0, z0 ) + 5 * (log10( luminsoity_distance_from_redshift( z ) ) - 1)


# -*- coding: utf-8 -*-
"""
Created on Sat Sep 18 02:37:53 2021

@author: Mahfuz_Shazol
"""

import numpy as np

A=np.array([
    [-1,4],
    [2,-2]
    ])

lambdas,V=np.linalg.eig(A)
print('lambdas',lambdas)

#each column is separate eivactor V
print('V',V)

#A**v==Lamda**v

v=V[:,0]
print(v)
Lamda=lambdas[0]

r1=np.dot(A,v)
print(r1)

r2=np.dot(Lamda,v)
print(r2)


import math
import numpy as np

a = [ [-7,2,-3,4,-12],
        [5,-1,14,-1,13],
        [1,9,-7,13,31],
        [-12,13,-8,-4,-32]
        ]

n = len(a)
marks = [i for i in range(0,n)]

def changeRows(majorRow,k):
    for i in range(0,len(a)+1):
        aux = a[k][i]
        a[k][i] = a[majorRow][i]
        a[majorRow][i]= aux

def changeColumns(majorColumn,k):
    aux = marks[k]
    marks[k] = marks[majorColumn]
    marks[majorColumn] = aux
    for j in range(0,len(a)):
        aux = a[j][k]
        a[j][k] = a[j][majorColumn]
        a[j][majorColumn] = aux

def totalPivoting(k):
    major = 0
    majorRow = k
    majorColumn = k
    for r in range(k,n):
        for s in range (k,n):
            if (math.fabs(a[r][s]) > major):
                major = math.fabs(a[r][s])
                majorRow = r
                majorColumn = s

    if(major == 0):
        print("division 0")
        return;
    else:
        if(majorRow != k):
            changeRows(majorRow,k)
        if(majorColumn != k):
            changeColumns(majorColumn,k)

def elimination():
    for k  in range(0,n-1):
        totalPivoting(k)
        for i  in range (k + 1, n):
            multiplicator = a[i][k]/a[k][k]
            for j in range (k,n + 1):
                a[i][j] = a[i][j]-(multiplicator*a[k][j])

    sustitution()

def sustitution():
    n = len(a) -1
    x = [i for i in range(n+1)]
    x[len(x)-1] = a[n][n+1]/a[n][n]
    for i in range(0,n+1):
        summation = 0
        auxi = n - i
        summation = 0
        for p in range(auxi+1,n+1):
            summation = summation + a[auxi][p]*x[p]
        x[auxi]=(a[auxi][n+1]-summation)/a[auxi][auxi]
    marks.reverse()
    x.reverse()
    for i in range(0,len(x)):
        print("X",marks[i]," =",x[i])

elimination()
for i in a:
    print(i)


import numpy as np

def n_bar(wf):
	n = np.arange(1,len(wf.count)+1)
	sum_yn = np.sum(n*wf.count)
	return sum_yn / np.sum(wf.count)

def moment(wf, m):
	n_minus_nbar = wf.count - n_bar(wf)

	return np.sum(wf.count * n_minus_nbar**m) / np.sum(wf.count)

def skewness(wf):
	return moment(wf,3) / moment(wf,2)**1.5

def kurtosis(wf):
	return moment(wf,4) / moment(wf,2)**2

def mode(wf):
	peak = np.where(wf.count == wf.count.max())[0][-1]
	return wf.height[peak]

def mean(wf):
	n = np.arange(1,len(wf.count)+1)
	return np.sum(n*wf.count) / np.sum(wf.count)

def sum_squares(wf):
	n = np.arange(1,len(wf.count)+1)
	return np.sum(n**2 * wf.count)

def variance(wf):
	n = np.arange(1,len(wf.count)+1)

	mean_square = sum_squares(wf) / np.sum(wf.count)
	return mean_square - mean(wf)**2

def stdev(wf):
	return np.sqrt(variance(wf))

def median(wf):
	n_halves = np.sum(wf.count) / 2
	sub_sums = [np.sum(wf.count[:i]) for i in range(len(wf.count))]
	i = np.where(sub_sums > n_halves)[0][0]

	return i + (n_halves - sub_sums[i-1]) / wf.count[i]

def pearson_skewness(wf, n):
	if n == 1:
		return (mean(wf) - mode(wf)) / stdev(wf)
	elif n == 2:
		return 3 * (mean(wf) - median(wf)) / stdev(wf)
	else:
		raise ValueError("Pearson Skew Coefficient must be 1 or 2 (i.e. n=1 or n=2)")

def AUC(wf):
	return np.trapz(wf.count)


#!/usr/bin/env python3
# coding=utf-8

# from http://lotabout.me/2018/Logistic-Regression-Notes/

import numpy as np
import matplotlib.pyplot as plt

samples = [(-5, 1), (-20, 0), (-2, 1)]

def sigmoid(theta, x):
    sigmoid_value = 1.0 / (1 + np.exp(- theta * x))
    # print('sigmoid theta=%s x=%s result=%s' % (theta, x, sigmoid_value))
    return sigmoid_value

def cost(theta):
    diffs = [(sigmoid(theta, x) - y) for x, y in samples]
    return sum(diff * diff for diff in diffs) / len(samples) / 2

def log_loss(theta, x, y):
    tmp_sigmoid = sigmoid(theta, x)
    first = -y * np.log(tmp_sigmoid)
    second = (1 - y) * np.log(1 - tmp_sigmoid)
    # print('1-y=%s np_log 1 - s = %s' % (1 - y, np.log(1 - tmp_sigmoid)))
    # print('log loss theta=%s second=%s' % (theta, second))
    return first - second

def cost2(theta):
    diffs = [log_loss(theta, x, y) for x, y in samples]
    # print(diffs)
    return sum(diff for diff in diffs) / len(samples)

X = np.arange(-1, 1, 0.01)
Y = np.array([cost(theta) for theta in X])
plt.plot(X, Y)
plt.show()


import numpy as np
import pandas as pd
import math

def sigmoid(inX) :
    return 1.0/(1+ math.exp(-inX))

def P1(beta, x) :
    return math.exp(np.dot(beta.T, x))/(1 + math.exp(np.dot(beta.T, x)))

def P2(beta, x) :
    return 1/(1 + math.exp(np.dot(beta.T, x)))

def fx(X, Y, beta) :
    sum = 0
    for i in range(len(Y)) :
        Xi = X[:,i]
        Xi.shape = (len(X),1)
        sum += np.dot(Xi,(Y.loc[i] - P1(beta,Xi)))
    return -sum

def dx(X,Y, beta) :
    sum = 0
    for i in range(len(Y)) :
        Xi = X[:,i]
        Xi.shape = (len(X),1)

        Xx = np.dot(Xi, Xi.T)
        Xxp = np.dot(Xx, P1(beta, Xi))
        dx1 = np.dot(Xxp,(1-P1(beta, Xi)))
        sum += dx1
    return sum

filename = './data/data_Ernest/trainDataCleanByTed.csv'
testfilename = './data/data_Ernest/testDataCleanByTed.csv'
dataSet = pd.read_csv(filename)
testDataSet = pd.read_csv(testfilename)

X = dataSet.drop('Attrition', axis=1)
addOne = np.ones((len(X),1))
X = np.c_[X,addOne]

X = X.T
Y = dataSet['Attrition']
#x0 = np.array(X.loc[0],dtype=np.float)

beta = np.zeros((len(X), 1))
lastBeta = beta.copy()

for i in range(5000) :
    if i == 0 :
       fx1 = fx(X, Y, beta)
       dx1 = dx(X, Y, beta)
       newBeta = beta - np.dot(np.linalg.inv(dx1), fx1)
    else :
        if(np.linalg.norm(newBeta - lastBeta) > 0.000000001) :
            lastBeta = newBeta.copy()
            fx2 = fx(X, Y, lastBeta)
            dx2 = dx(X, Y, lastBeta)
            newBeta = lastBeta - np.dot(np.linalg.inv(dx2), fx2)
            #print(np.linalg.norm(lastBeta - newBeta))
        else :
            break

beta = newBeta

X = testDataSet.drop('Attrition', axis=1)
addOne = np.ones((len(X),1))
X = np.c_[X,addOne]

X = X.T
Y = testDataSet['Attrition']
correct = 0
List = Y.value_counts(normalize=True)

for i in range(len(Y)) :
    Xi = X[:,i]
    Xi.shape = (len(X),1)
    inX = np.dot(beta.T, Xi)
    value = sigmoid(inX)

    rate = value / (1 - value)
    reviseRate = rate * (List[1]/List[0])

    if reviseRate > 1 and Y[i] == 1:
        correct += 1
    elif reviseRate <= 1 and Y[i] == 0 :
        correct += 1

print(correct/len(Y))


import math
import numpy as np
import pylab
def harmonic_wavefunction(x, N):
    psi=[0, (math.pi)**(-1./4)*math.exp(-x**2/2.)]
    for n in range(2, N+1):
        psi+=[math.sqrt(2./n)*x*psi[n-1]-math.sqrt((n-1)/n)*psi[n-2], ]
    return psi[1:]

def E(n): return n+0.5
def Z(Beta): return 0.5/math.sinh(Beta/2.)

def harmonic_density(xmin, xmax, N, Beta):
    L=np.linspace(xmin, xmax, 15)
    l=len(L)
    rho=[[0.0 for i in range(l)] for j in range(l)]
    for i in range(l):
        for j in range(l):
            for n in range(N):
                rho[i][j]+=harmonic_wavefunction(L[i], N)[n]*harmonic_wavefunction(L[j], N)[n]*math.exp(-Beta*E(n))
    return rho

def Prob(x, xmin, xmax, N, Beta):
    L=np.linspace(xmin, xmax, 15)
    L=L.tolist()
    i=L.index(x)
    return np.array(harmonic_density(xmin, xmax, N, Beta))[i, i]/Z(Beta)
#print(Prob(0., -2., 2., 100, 8.))

X=np.linspace(-2., 2., 15)
P=[Prob(i, -2., 2., 100, 8) for i in X]
pylab.plot(X, P)
pylab.show()