import numpy as np
from scipy.stats import norm

def black_scholes(t=40, r=4.00, v=32.00, K=60, St=62, type='c'):
    """
    Parameters:
    K : Excercise Price
    St: Current Stock Price
    v : Volatility in percentage
    r : Risk free rate in percentage
    t : Time to expiration in days
    type: Type of option 'c' for call 'p' for put
    default: 'c'
    """
    # if type = 'c' or 'C' call option else put option
    try:
        type=type.lower()
        if(type=='c'):
            option_type='call'
        else:
            option_type='put'
    except:
        option_type='put'

    #Check time
    try:
        #convert time in days to years
        t=t/365
    except:
        raise TypeError("Enter numerical value for time")

    #Check risk free rate
    try:
        #convert percentage to decimal
        r=r/100
    except:
        raise TypeError("Enter numerical value for risk free rate")

    #Check volatility
    try:
        #convert percentage to decimal
        v=v/100
    except:
        raise TypeError("Enter numerical value for volatility")

    #Check Stock Price
    try:
        St=St+0
    except:
        raise TypeError("Enter numerical value for stock price")

    #Check Exercise Price
    try:
        K=K+0
    except:
        raise TypeError("Enter numerical value for Exercise price")

    n1=np.log(St/K)
    n2=(r+(np.power(v,2)/2))*t
    d=v*(np.sqrt(t))

    d1=(n1+n2)/d
    d2=d1-(v*np.sqrt(t))

    if type=='c':
        N_d1=norm.cdf(d1)
        N_d2=norm.cdf(d2)
    else:
        N_d1=norm.cdf(-d1)
        N_d2=norm.cdf(-d2)

    A=(St*N_d1)
    B=(K*N_d2*(np.exp(-r*t)))

    if type=='c':
        val=A-B
        val_int=max(0,St-K)
    else:
        val=B-A
        val_int=max(0,K-St)
    val_time=val-val_int

    # Option values in dictionary
    value={'option value':val, 'intrinsic value':val_int, 'time value':val_time}

    #CALCULATE OPTION GREEKS
    if type=='c':
        delta=N_d1
        theta=(-((St*v*np.exp(-np.power(d1,2)/2))/(np.sqrt(8*np.pi*t)))-(N_d2*r*K*np.exp(-r*t)))/365
        rho=t*K*N_d2*np.exp(-r*t)/100
    else:
        delta=-N_d1
        theta=(-((St*v*np.exp(-np.power(d1,2)/2))/(np.sqrt(8*np.pi*t)))+(N_d2*r*K*np.exp(-r*t)))/365
        rho=-t*K*N_d2*np.exp(-r*t)/100

    gamma=(np.exp(-np.power(d1,2)/2))/(St*v*np.sqrt(2*np.pi*t))
    vega=(St*np.sqrt(t)*np.exp(-np.power(d1,2)/2))/(np.sqrt(2*np.pi)*100)

    #Option greeks in Dictionary
    greeks={'delta':delta, 'gamma':gamma, 'theta':theta, 'vega':vega, 'rho':rho}

    return {'value':value, 'greeks':greeks}


# -*- coding: utf-8 -*-
"""
Created on Sun May 23 02:08:50 2021

@author: Akshay Prakash
"""

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from sklearn.linear_model import LinearRegression
df = pd.read_csv(r'\96-20PL.csv')
df1 = pd.read_csv(r'\F2021PL.csv')
newframe = [df, df1]
NewResult = pd.concat(newframe)

print(df['Pts'])
y = df.Rk
x = df.Pts.values.reshape(-1,1) #.values with reshape because its DF
print(x.shape, y.shape) #(rows,colums)
model = LinearRegression().fit(x, y)
r_sqrt = model.score(x,y)
print('r_sqrt: ', r_sqrt)
intercept = model.intercept_
slope = model.coef_
print('intercept: ', intercept)
print('slope: ', slope)
y_predicted = intercept + slope* x #Linear Regression eqn

fig, ax = plt.subplots(figsize = (10,10))
plt.scatter(x,y, color = 'blue', label = 'Points')
plt.plot(x,y_predicted, color = 'red', label = 'Linear Regression')
plt.legend()
plt.ylim(0.5, 20.5)
plt.yticks([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20])
plt.gca().invert_yaxis()#rewrite because it should be from low to high
plt.xlabel('Points')
plt.ylabel('League Position (Rank)')
plt.title('Evaluating relationship between Points and league position')
plt.text(10, 3, "Slope: -0.332 ", color = 'red', size= '15')
plt.text(10, 3.7, "Intercept: 27.80 ", color = 'red', size= '15')
plt.text(10, 4.4, "r_sqrt: 0.89 ", color = 'red', size= '15')
plt.text(92, 1.4, "Man City 18/19 (98)", color ='red', size = '7')
plt.text(92, 2.4, "Liverpool 18/19 (97)", color ='red', size = '7')
plt.text(7, 19.6, "Derby County 2008/09 ", color = 'red', size= '7')

y = int(input("Enter a number: "))
x = (y- intercept)/slope
print(x)


import numpy as np
class NoiseGenerator:
    @staticmethod
    def power_law_noise(N,alpha,A_rms):
        """
            Generates a time-domain vector of power law noise samples.

            Parameters
            ----------
            N: int
                Number of samples to be generated from the power-law noise process.
            alpha: int
                Exponent paramter in the power-law noise process S(f)~h*f(-alpha).
            A_rms: float
                Root-mean squared (RMS) ampltiude of the generated time series

            Returns
            -------
            Y: nparray
                N-sample vector of samples from the specified noise process.

            Notes
            -----
        """

        N_nominal = N

        if(N%2 !=0):
            N_nominal = N+1

        halfSpectrumLength = N_nominal/2

        X_t = np.random.normal(0, 1, (N_nominal,1))
        X_omega = np.fft.fft(X_t)

        #shift the FFT to put the 0-frequency in the center
        X_omega = np.fft.fftshift(X_omega)

        #initialize colored noise spectrum
        X_omega_c = np.empty(N_nominal,np.dtype('c16'))

        #color the noise according to the exponent parameter
        #leave the DC component at X_omega[halfSpectrumLength] untouched
        for i in range(halfSpectrumLength+1,N_nominal):
            X_omega_c[i] = np.divide(X_omega[i],np.sqrt((i-halfSpectrumLength)**alpha))
        for i in range(halfSpectrumLength-1,-1,-1):
            X_omega_c[i] = np.divide(X_omega[i],np.sqrt((halfSpectrumLength-i)**alpha))

        #transform from frequency space back into real space
        Y = np.fft.ifft(np.fft.ifftshift(X_omega_c))
        Y = np.real(Y)

        #guarantee A_rms standard deviation and zero mean
        Y = Y - np.mean(Y)
        Y_rms = np.sqrt(np.mean(Y**2))
        Y = A_rms*np.divide(Y,Y_rms)

        return Y


import numpy as np
import math
#a = np.array([5.576091766357422, 8.455062866210938, -0.06940200179815292])
#b = np.array([6.994688034057617, 9.038549423217773, -0.23745399713516235])
#c = np.array([7.181145191192627, 10.318812370300293, -0.020966999232769012])
#d = np.array([8.561141967773438, 10.942671775817871, -0.06447900086641312])
def getNormedVector(a,b):
    return (b-a)/np.linalg.norm(b-a)

def getAngle(a,b):
    return np.rad2deg(np.arccos(np.dot(a/np.linalg.norm(a),b/np.linalg.norm(b))))

def getSign(a,b):
    b = b/np.linalg.norm(b)
    rec = np.dot(a,b)
    return rec

def getdih(a,b,c,d):
    #v1 = np.subtract(a,b)
    #v2 = np.subtract(b,c)
    #v3 = np.subtract(c,d)
    v1 = getNormedVector(a,b)
    v2 = getNormedVector(b,c)
    v3 = getNormedVector(c,d)
    v1v2 = np.cross(v1,v2)
    v2v3 = np.cross(v2,v3)
    d12 = np.dot(v1v2,v1v2)
    d23 = np.dot(v2v3,v2v3)
    rotdir = getSign(v1,-v2v3)
    #print d12, d23
    #print "sign", rotdir
    rec = getAngle(v1v2,v2v3)
    #print rec
    # if the rotdir is positive, the rotation is clockwise
    if rotdir > 0.0:
        #rec = -rec
        rec = 360.0 - rec
    #costheta = float(np.dot(d12,d23))/float((d12*d23))
    #theta = math.acos(costheta)/math.pi*180.0
    #print costheta, theta
    #return theta
    #if rec < 0.0:
    #    rec = 180.0 + rec
    return rec
#theta = getdih(a,b,c,d)
#print theta


from sympy import *
from sympy.physics.vector import *
from sympy.vector import *

R = CoordSys3D('R')
t = Symbol('t')
a = -sin(t)*R.i + cos(t)*R.j + 0*R.k
b = -cos(t)*R.i + -sin(t)*R.j + 0*R.k

# same
print(simplify(dot(a, b)))
print(simplify(a & b))

# same
print(simplify(a ^ b))
print(simplify(cross(a, b)))


import numpy as np

def dist(a, b, axis=1):
    return np.linalg.norm(a - b, axis=axis)

def getRandomCentroids(X, k):
    idx = np.random.choice(np.arange(X.shape[0]), size=k, replace=False)
    return X[idx]

def getLabels(X, centroids):
    labels = []
    for i, p in enumerate(X):
        dists = dist(p, centroids)
        labels.append(np.argmin(dists))
    return labels

def getCentroids(X, labels, k):
    centroids = []
    for i in range(k):
        points = [X[j] for j in range(len(X)) if labels[j] == i]
        centroids.append(np.mean(points, axis=0))
    return centroids

def getError(X, centroids, labels):
    error = 0
    for i, p in enumerate(X):
        error += dist(p, centroids[labels[i]], axis=0)
    return error

def KMeans(X, k, r):
    """
    :param X: data matrix(D * N)
    :param k: desired number of clusters
    :param r: number of repetitions with different random initializations
    :return: centroids: cluster centroids of the data(k * D)
             labels: clustering labels of the data(N)
    """
    X = X.T
    min_error, best_centroids, best_labels = float("inf"), None, None
    for i in range(r):
        labels = np.zeros(len(X))
        centroids = getRandomCentroids(X, k)
        while True:
            old_labels = np.copy(labels)
            labels = getLabels(X, centroids)
            if np.array_equal(labels, old_labels):
                error = getError(X, centroids, labels)
                if error < min_error:
                    min_error, best_centroids, best_labels = error, centroids, labels
                break
            centroids = getCentroids(X, labels, k)
    return best_centroids, best_labels


import numpy as np

class LinearRegression(object):
	def __init__(self, learning_rate = 1e-3):
		self.lr = learning_rate

	def predict(self, X):
		_, dim = X.shape
		if dim > 1:
			## Trying for multiple regression
			return X.dot(self.weights)
		else:
			return X.dot(self.weights) + self.biases

	def cost_function(self, y_pred, y):
		error = np.power(y_pred - y, 2)
		return 1/(2*self.n) * np.sum(error)

	def fit(self, X, y):

		early_stopping = False
		best_loss = np.inf

		self.n, self.feat = X.shape

		self.weights = np.zeros(self.feat)
		self.biases = 0
		self.X = X
		self.y = y

		## Until Convergen
		"""
		while not early_stopping:
			self.pred = self.predict(X)
			loss = self.cost_function(self.pred, y)

			if loss < best_loss:
				best_loss = loss
			else:
				early_stopping = True

			self.update_weights()
		"""

		for _ in range(10000):
			self.pred = self.predict(X)
			loss = self.cost_function(self.pred, y)
			if np.isnan(loss):
				break
			else:
				self.update_weights()

		return self

	def update_weights(self):
		dW = - (2 * (self.X.T.dot(self.y - self.pred))/self.n)
		db = - 2 * np.sum(self.y - self.pred)/self.n

		#dW = self.X.T.dot((self.pred - self.y))/self.n
		#db = np.sum(self.pred - self.y)/self.n

		self.weights = self.weights - self.lr * dW
		self.biases = self.biases - self.lr * db

		return self

class KNeighborsRegression():
	def __init__(self, p = 1, n_neighbors = 2):
		self.k = n_neighbors
		self.p = p

	def distance(self, a, b):
		return np.power(np.sum(np.power(abs( a - b), self.p)), 1/self.p)

	def fit(self, X, y):
		self.X_train = X
		self.y_train = y

	def predict(self, X):
		prediction = np.zeros(X.shape[0])
		for i in range(X.shape[0]):
			row = X[i]
			neighbors = self.get_neighbors(row)
			prediction[i] = np.mean(neighbors)

		return prediction

	def get_neighbors(self, X):
		distances = np.zeros(self.X_train.shape[0])
		for i, row in enumerate(self.X_train):
			distances[i] = self.distance(X, row)

		inds = distances.argsort()
		y_train = self.y_train[inds]
		return y_train[:self.k]

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 -= self.mean
		X_ = X - self.mean
		#sigma = 1/(X.shape[0]-1) * X.T.dot(X)
		sigma = 1/(X.shape[0] - 1) * X_.T.dot(X_)
		U, s, Vt = np.linalg.svd(X_)
		self.components = Vt.T[:, :self.n_components]

	def transform(self, X):
		X_ = X - self.mean
		return X_.dot(self.components)

	def fit_transform(self, X):
		self.fit(X)
		return self.transform(X)


from math import atan2, sqrt, degrees
import numpy as np
from math import radians, sin, cos

RADIUS = 6371.009

def get_centroid(points):
    xy = np.asarray(points)
    xy = np.radians(xy)
    lon, lat = xy[:, 0], xy[:, 1]
    avg_x = np.sum(np.cos(lat) * np.cos(lon)) / xy.shape[0]
    avg_y = np.sum(np.cos(lat) * np.sin(lon)) / xy.shape[0]
    avg_z = np.sum(np.sin(lat)) / xy.shape[0]
    center_lon = atan2(avg_y, avg_x)
    hyp = sqrt(avg_x * avg_x + avg_y * avg_y)
    center_lat = atan2(avg_z, hyp)
    return degrees(center_lon), degrees(center_lat)

def gc_distance_points(a, points):
    b = np.asarray(points)
    lat1, lng1 = radians(a[1]), radians(a[0])
    lat2, lng2 = np.radians(b[:, 1]), np.radians(b[:, 0])

    sin_lat1, cos_lat1 = sin(lat1), cos(lat1)
    sin_lat2, cos_lat2 = np.sin(lat2), np.cos(lat2)

    delta_lng = np.subtract(lng2, lng1)
    cos_delta_lng, sin_delta_lng = np.cos(delta_lng), np.sin(delta_lng)

    d = np.arctan2(np.sqrt((cos_lat2 * sin_delta_lng) ** 2 +
                 (cos_lat1 * sin_lat2 -
                  sin_lat1 * cos_lat2 * cos_delta_lng) ** 2),
                 sin_lat1 * sin_lat2 + cos_lat1 * cos_lat2 * cos_delta_lng)

    return RADIUS * d

def gc_dist(a, b):
        from math import radians, sin, cos, sqrt, atan2
        lat1, lng1 = radians(a[1]), radians(a[0])
        lat2, lng2 = radians(b[1]), radians(b[0])

        sin_lat1, cos_lat1 = sin(lat1), cos(lat1)
        sin_lat2, cos_lat2 = sin(lat2), cos(lat2)

        delta_lng = lng2 - lng1
        cos_delta_lng, sin_delta_lng = cos(delta_lng), sin(delta_lng)

        d = atan2(sqrt((cos_lat2 * sin_delta_lng) ** 2 +
                       (cos_lat1 * sin_lat2 -
                        sin_lat1 * cos_lat2 * cos_delta_lng) ** 2),
                  sin_lat1 * sin_lat2 + cos_lat1 * cos_lat2 * cos_delta_lng)

        return RADIUS * d

if __name__ == '__main__':

    import random
    random.seed(42)
    pts = [(random.uniform(-180, 180), random.uniform(-90, 90)) for _ in range(1000000)]

    centr = get_centroid(pts)

    import cProfile

    cProfile.runctx('gc_distance_points(centr, pts)', globals(), locals())
    cProfile.runctx('dists = [gc_dist(centr, b) for b in pts]', globals(), locals())


import sys
import numpy as np
import matplotlib.pyplot as plt
sys.path.append("..")
from utils import *
from linear_regression import *
from svm import *
from softmax import *
from features import *
from kernel import *

#######################################################################
# Load data
#######################################################################

# Load MNIST data:
train_x, train_y, test_x, test_y = get_MNIST_data()
# Plot the first 20 images of the training set.
# plot_images(train_x[0:20, :])

#######################################################################
# Linear Regression with Closed Form Solution
#######################################################################

def run_linear_regression_on_MNIST(lambda_factor=1):
    """
    Trains linear regression, classifies test data, computes test error on test set

    Returns:
        Final test error
    """
    train_x, train_y, test_x, test_y = get_MNIST_data()
    train_x_bias = np.hstack([np.ones([train_x.shape[0], 1]), train_x])
    test_x_bias = np.hstack([np.ones([test_x.shape[0], 1]), test_x])
    theta = closed_form(train_x_bias, train_y, lambda_factor)
    test_error = compute_test_error_linear(test_x_bias, test_y, theta)
    return test_error

print('Linear Regression test_error =', run_linear_regression_on_MNIST(lambda_factor=0.01))

#######################################################################
# Support Vector Machine
#######################################################################

def run_svm_one_vs_rest_on_MNIST():
    """
    Trains svm, classifies test data, computes test error on test set

    Returns:
        Test error for the binary svm
    """
    train_x, train_y, test_x, test_y = get_MNIST_data()
    train_y[train_y != 0] = 1
    test_y[test_y != 0] = 1
    pred_test_y = one_vs_rest_svm(train_x, train_y, test_x)
    test_error = compute_test_error_svm(test_y, pred_test_y)
    return test_error

print('SVM one vs. rest test_error:', run_svm_one_vs_rest_on_MNIST())

def run_multiclass_svm_on_MNIST():
    """
    Trains svm, classifies test data, computes test error on test set

    Returns:
        Test error for the binary svm
    """
    train_x, train_y, test_x, test_y = get_MNIST_data()
    pred_test_y = multi_class_svm(train_x, train_y, test_x)
    test_error = compute_test_error_svm(test_y, pred_test_y)
    return test_error

print('Multiclass SVM test_error:', run_multiclass_svm_on_MNIST())

#######################################################################
# Multinomial (Softmax) Regression and Gradient Descent
#######################################################################

def run_softmax_on_MNIST(temp_parameter=1):
    """
    Trains softmax, classifies test data, computes test error, and plots cost function

    Runs softmax_regression on the MNIST training set and computes the test error using
    the test set. It uses the following values for parameters:
    alpha = 0.3
    lambda = 1e-4
    num_iterations = 150

    Saves the final theta to ./theta.pkl.gz

    Returns:
        Final test error
    """
    train_x, train_y, test_x, test_y = get_MNIST_data()
    theta, cost_function_history = softmax_regression(train_x, train_y, temp_parameter, alpha=0.3, lambda_factor=1.0e-4, k=10, num_iterations=150)
    #plot_cost_function_over_time(cost_function_history)
    test_error = compute_test_error(test_x, test_y, theta, temp_parameter)
    # Save the model parameters theta obtained from calling softmax_regression to disk.
    write_pickle_data(theta, "./theta.pkl.gz")

    return test_error

print('softmax test_error=', run_softmax_on_MNIST(temp_parameter=1))

# You can change the temp_parameter = [.5, 1.0, 2.0]

#######################################################################
# Changing Labels
#######################################################################

def run_softmax_on_MNIST_mod3(temp_parameter=1):
    """
    Trains Softmax regression on digit (mod 3) classifications.

    See run_softmax_on_MNIST for more info.
    """
    train_x, train_y, test_x, test_y = get_MNIST_data()
    train_y_mod3, test_y_mode3 = update_y(train_y, test_y)
    theta, cost_function_history = softmax_regression(train_x, train_y_mod3, temp_parameter, alpha=0.3, lambda_factor=1.0e-4,
                                                      k=10, num_iterations=150)
    #plot_cost_function_over_time(cost_function_history)
    test_error = compute_test_error_mod3(test_x, test_y_mode3, theta, temp_parameter)
    # Save the model parameters theta obtained from calling softmax_regression to disk.
    write_pickle_data(theta, "./theta.pkl.gz")

    return test_error

print('softmax test_error_mod3=', run_softmax_on_MNIST_mod3(temp_parameter=1))

#######################################################################
# Classification Using Manually Crafted Features
#######################################################################

## Dimensionality reduction via PCA ##

n_components = 18
train_x_centered, feature_means = center_data(train_x)
pcs = principal_components(train_x_centered)
train_pca = project_onto_PC(train_x, pcs, n_components, feature_means)
test_pca = project_onto_PC(test_x, pcs, n_components, feature_means)

# train_pca (and test_pca) is a representation of our training (and test) data
# after projecting each example onto the first n_components principal components.

# Train softmax regression model using (train_pca, train_y)
# and evaluate its accuracy on (test_pca, test_y).
temp_parameter = 1
theta, cost_function_history = softmax_regression(train_pca, train_y, temp_parameter, alpha=0.3, lambda_factor=1.0e-4, k=10, num_iterations=150)
#plot_cost_function_over_time(cost_function_history)
test_error = compute_test_error(test_pca, test_y, theta, temp_parameter)
print('After PCA softmax test_error=', test_error)

#       Uncomment to use the plot_PC function in features.py to produce scatterplot
#       of the first 100 MNIST images, as represented in the space spanned by the
#       first 2 principal components found above.
#plot_PC(train_x[range(000, 100), ], pcs, train_y[range(000, 100)], feature_means)#feature_means added since release

#       Uncomment to use the reconstruct_PC function in features.py to show
#       the first and second MNIST images as reconstructed solely from
#       their 18-dimensional principal component representation.
#       Compare the reconstructed images with the originals.
#firstimage_reconstructed = reconstruct_PC(train_pca[0, ], pcs, n_components, train_x, feature_means)#feature_means added since release
#plot_images(firstimage_reconstructed)
#plot_images(train_x[0, ])

#secondimage_reconstructed = reconstruct_PC(train_pca[1, ], pcs, n_components, train_x, feature_means)#feature_means added since release
#plot_images(secondimage_reconstructed)
#plot_images(train_x[1, ])

## Cubic Kernel ##
#Find the 10-dimensional PCA representation of the training and test set
n_components = 10
train_x_centered, feature_means = center_data(train_x)
pcs = principal_components(train_x_centered)
train_pca10 = project_onto_PC(train_x, pcs, n_components, feature_means)
test_pca10 = project_onto_PC(test_x, pcs, n_components, feature_means)

train_cube = cubic_features(train_pca10)
test_cube = cubic_features(test_pca10)
# train_cube (and test_cube) is a representation of our training (and test) data
# after applying the cubic kernel feature mapping to the 10-dimensional PCA representations.

# Train softmax regression model using (train_cube, train_y)
# and evaluate its accuracy on (test_cube, test_y).
temp_parameter = 1
theta, cost_function_history = softmax_regression(train_cube, train_y, temp_parameter, alpha=0.3, lambda_factor=1.0e-4, k=10, num_iterations=150)
#plot_cost_function_over_time(cost_function_history)
test_error = compute_test_error(test_cube, test_y, theta, temp_parameter)
print('After Cubic Kernel softmax test_error=', test_error)


import numpy as np
# http://www.netlib.org/templates/matlab/
nx = 8
A = np.zeros((nx*nx,nx*nx))

def inDomain(i,j):
    if i < 0 or j < 0 or i >= nx  or j >= nx:
        return 0
    return 1
div = np.zeros((nx,nx))
b = np.zeros((nx * nx))
div[3,1] = 1
div[4,1] = 1
for i in range(nx):
    for j in range(nx):
        idx = j * nx + i
        b[idx] = div[i,j]
for k in range(nx*nx):
    A[k,k] = 4
    i = int(k % nx)
    j = int(k / nx)
    if inDomain(i-1, j):
        A[k,k-1] = -1
    if inDomain(i+1, j):
        A[k,k+1] = -1
    if inDomain(i, j-1):
        A[k,k-nx] = -1
    if inDomain(i, j+1):
        A[k,k+nx] = -1
x = np.zeros((nx * nx))
p = np.zeros((nx * nx)) # 方向
res = b - np.dot(A,x) # 残差
bnrm2 = np.linalg.norm(b)
tmax = 100
restld = res.copy()
rho1 = 1
for t in range(0,tmax):
    z = res # 预处理方程组的残差
    rho = np.dot(np.transpose(res),z)
    if rho == 0:
        break
    beta = 0
    if t > 0:
        beta = rho / rho1
    p = z + beta*p
    Ap = np.dot(A,p)
    alpha = rho / np.dot(np.transpose(p),Ap)
    x = x + alpha * p
    res = res - alpha * Ap
    error = np.linalg.norm(res) / bnrm2
    if error < 1e-8:
        break
    rho1 = rho

pre = np.zeros((nx,nx))
for i in range(nx):
    for j in range(nx):
        idx = j * nx + i
        pre[i,j] = x[idx]


try:
    from scipy.special import beta as _beta, betainc as _betainc, gamma as _gamma, digamma as _digamma
    import numpy as _np
    from typing import Union, Dict, List
    from math import sqrt as _sqrt, log as _log
    from univariate._base import SemiInfinite
except Exception as e:
    print(f"some modules are missing {e}")

class F(SemiInfinite):
    """
    This class contains methods concerning the F-distribution [#]_ [#]_ [#]_.

    .. math::
        \\text{F}(x;d_1, d_2) = \\frac{1}{\\text{B}(d_1/2,d_2/2)} \\Big( \\frac{d_1}{d_2} \\Big)^{d_1/2} x^{d_1/2 - 1} \\Big(1 + \\frac{d_1}{d_2}x\\Big) ^{-(d_1+d_2)/2}

    Args:

        df1(int): first degrees of freedom where df1 > 0
        df2(int): second degrees of freedom where df2 > 0
        x(float): random variable where x > 0

    References:
        .. [#] Mood, Alexander; Franklin A. Graybill; Duane C. Boes (1974). Introduction to the Theory of Statistics (Third ed.). McGraw-Hill. pp. 246–249. ISBN 0-07-042864-6.
        .. [#] Weisstein, Eric W. "F-Distribution." From MathWorld--A Wolfram Web Resource. https://mathworld.wolfram.com/F-Distribution.html
        .. [#] NIST SemaTech (n.d.). F-Distribution. Retrived from https://www.itl.nist.gov/div898/handbook/eda/section3/eda3665.htm
    """

    def __init__(self, df1: int, df2: int):
        if (type(df1) is not int) or (df1 <= 0):
            raise TypeError(
                f'degrees of freedom(df) should be a whole number.')
        if (type(df1) is not int) or (df2 <= 0):
            raise TypeError(
                f'degrees of freedom(df) should be a whole number.')

        self.df1 = df1
        self.df2 = df2

    def pdf(self, x: Union[List[float], _np.ndarray, float]) -> Union[float, _np.ndarray]:
        """
        Args:
            x (Union[List[float], _np.ndarray, float]): random variable(s)

        Raises:
            ValueError: when there exist a value such that x < 0

        Returns:
            Union[float, _np.ndarray]: evaluation of pdf at x
        """

        def __generator(x: Union[float, _np.ndarray], df1: int, df2: int) -> Union[float, _np.ndarray]:
            x0 = (1 / _beta(df1 / 2, df2 / 2)) * pow(df1 / df2, df1 / 2)
            if type(x) is _np.ndarray:
                return x0 * \
                    _np.power(x, df1 / 2 - 1) * _np.power(1 +
                                                          (df1 / df2) * x, -((df1 + df2) / 2))
            return x0 * \
                pow(x, df1 / 2 - 1) * \
                pow(1 + (df1 / df2) * x, -((df1 + df2) / 2))

        if isinstance(x, (_np.ndarray, List)):
            if not type(x) is _np.ndarray:
                x = _np.array(x)
            if _np.any(x < 0):
                raise ValueError(
                    'random variables are expected to be greater than 0.')
            return __generator(x, self.df1, self.df2)

        if x < 0:
            raise ValueError(
                'random variable is expected to be greater than 0.')
        return __generator(x, self.df1, self.df2)

    def cdf(self, x: Union[List[float], _np.ndarray, float]) -> Union[float, _np.ndarray]:
        """
        Args:

            x (List[float], numpy.ndarray): random variable or list of random variables

        Returns:
            either cumulative distribution evaluation for some point or plot of F-distribution.
        """
        df1, df2 = self.df1, self.df2

        if isinstance(x, (_np.ndarray, List)):
            if not type(x) is _np.ndarray:
                x = _np.array(x)
            return _betainc(df1/2, df2/2, df1*x/(df1*x + df2))

        k = df1*x/(self.df2 + self.df1*x)  # not sure why
        return _betainc(df1/2, df2/2, k)

    def mean(self) -> Union[float, str]:
        """
        Returns: Mean of the F-distribution.
        """
        if self.df2 > 2:
            return self.df2 / (self.df2 - 2)
        return "undefined"

    def mode(self) -> Union[float, str]:
        """
        Returns: Mode of the F-distribution.
        """
        df1 = self.df1
        df2 = self.df2
        if df1 > 2:
            return (df2 * (df1 - 2)) / (df1 * (df2 + 2))
        return "undefined"

    def var(self) -> Union[float, str]:
        """
        Returns: Variance of the F-distribution.
        """
        df1 = self.df1
        df2 = self.df2
        if df2 > 4:
            return (2 * pow(df2, 2) * (df1 + df2 - 2)) / (df1 * (pow(df2 - 2, 2) *
                                                                 (df2 - 4)))
        return "undefined"

    def std(self) -> Union[float, str]:
        """
        Returns: Standard deviation of the F-distribution.
        """
        df1 = self.df1
        df2 = self.df2
        if df2 > 4:
            return _sqrt((2 * pow(df2, 2) * (df1 + df2 - 2))/(df1 * (pow(df2 - 2, 2) * (df2 - 4))))
        return 'undefined'

    def skewness(self) -> Union[float, str]:
        """
        Returns: Skewness of the F-distribution.
        """
        df1 = self.df1
        df2 = self.df2
        if df2 > 6:
            return ((2 * df1 + df2 - 2) * _sqrt(8 * (df2 - 4))) / ((df2 - 6) * _sqrt(df1 * (df1 + df2 - 2)))
        return "undefined"

    def entropy(self) -> Union[float, int]:
        """
        Returns: differential entropy of F-distribution.

        Reference: Lazo, A.V.; Rathie, P. (1978). "On the entropy of continuous probability distributions". IEEE Transactions on Information Theory
        """
        df1 = self.df1
        df2 = self.df2
        return _log(_gamma(df1/2)) + _log(_gamma(df2/2)) -\
            _log(_gamma((df1+df2)/2)) + (1-df1/2)*_digamma(1+df1/2) -\
            (1-df2/2) * _digamma(1+df2/2) + (df1+df2) /\
            2*_digamma((df1+df2)/2) + _log(df1/df2)

    def summary(self) -> Dict[str, Union[float, str]]:
        """
        Returns:
            Dictionary of F distirbution moments. This includes standard deviation.
        """
        return {
            'mean': self.mean(), 'median': self.median(), 'mode': self.mode(),
            'var': self.var(), 'std': self.std(), 'skewness': self.skewness(), 'kurtosis': self.kurtosis()
        }


import numpy as np
from PIL import Image
import matplotlib.pyplot as plt
from functools import reduce

def blockMatrix(blocks):
    """creates a bloack 0-1 matrix.
    param blocks: list of non-negative integers which is the size of the blocks.
    a 0 block size corresponds to a 0 on the main diagonal.
    """
    blocks = np.array(blocks).astype("int64")

    def f(x):
        return 1 if x == 0 else x

    n = np.sum([f(x) for x in blocks])
    n = int(n)
    A = np.zeros((n, n))
    pos = 0
    for i in range(len(blocks)):
        b = blocks[i]
        if b > 0:
            A[pos : pos + b, pos : pos + b] = np.ones((b, b))
        pos += f(b)
    return A

def permutationMatrix(ls):
    """returns a permutation matrix of size len(ls)^2.
    param ls: should be a reordering of range(len(l s)), which defines the
    permutation on the ROWS.
    returns a permutation matrix P.
    np.dot(P,A) should be rearrangement of the rows of A according to P.
    To permute the columns of a matrix A use:
    Q = np.transpose(P),
     then: np.dot(A,Q).
    """
    n = len(ls)
    P = np.zeros((n, n))
    for i in range(n):
        P[i, ls[i]] = 1
    return P

def shuffleCopyMatrix(lins, louts, msize):
    """Returns a matrix P that represents switch and copy operations
    on the rows of a matrix.
    param msize: the size (of the square matrix).
    param lins: row indices to be replaced.
    param louts: row that replace the ones listed in lins.
    lins and louts must be of the same length and contain indiced within
    range(msize).
    These operations are performed on the identity matrix, and the result
    is the return value P.
    """
    # P = np.zeros((msize,msize))
    P = np.identity(msize)
    I = np.identity(msize)
    if not len(lins) == len(louts):
        return P
    for i in range(len(lins)):
        P[lins[i]] = I[louts[i]]
    return P

def scoreMatrix(n):
    """The score function of the matrix. The assumption is that the true
    arrangement maximized the interaction close to the main diagonal.
    The total sum of the interaction is an invariant, preserved by permuations.
    param n: size of ca 2-d n over n array.
    returns the score matrix, which is used to calculate the score of any given
    n^2 matrix.
    """
    s = np.arange(n)
    s = np.exp(-s)
    S = np.zeros((n, n))
    for i in range(n):
        S[i][i:] = s[: n - i]
    return S

def score(A, S):
    """returns the weighted sum (by the score matrix S) of
    the matrix A
    """
    return np.sum(A * S)

def constrainMatrix(ls, A):
    """Returns a matrix of the same dimension as A, but every entry with
    an index (either row or column) not in ls is 0.
    """
    B = np.zeros_like(A)
    # B[np.ix_(ls,ls)] = 1
    B[np.ix_(ls, ls)] = A[np.ix_(ls, ls)]
    # B[ls][:,ls] = A[ls][:,ls]
    return B

def resetIndices(ls, A):
    """essentially returns the constraint of A to the complement indices of ls,
    by reseting all the indices in ls to 0.
    """
    B = A.copy()
    B[ls, :] = 0
    B[:, ls] = 0
    return B

def reindexMatrix(iss, jss, A):
    """iss and jss are lists of indices of equal size, representing
    a permuation: iss[i] is replaced with jss[i]. all other indices which are
    not in the lists left unchanged.
    """
    n = len(A)
    B = np.zeros_like(A)
    tss = [i for i in range(n)]
    for i in range(len(iss)):
        tss[iss[i]] = jss[i]
    for i in range(n):
        for j in range(n):
            B[i, j] = A[tss[i], tss[j]]
    return B

# example
x = np.arange(25).reshape((5, 5))
reindexMatrix([1, 3], [3, 1], x)

def scorePair(iss, jss, refmat, scoremat):
    A = np.zeros_like(refmat)
    l = iss + jss
    n = len(l)
    for i in range(n):
        for j in range(n):
            A[i, j] = refmat[l[i], l[j]]
    return score(A, scoremat)

def scorePair2(iss, jss, refmat):
    """scores the interaction of two segments
    iss and jss. weighted by the ideal diagonal distribution.
    """
    s = 0
    temp = 0
    for i in range(len(iss)):
        for j in range(len(jss)):
            temp = np.exp(-np.abs(j + len(iss) - i))
            # we only care about interaction between the 2 segments and not
            # inside each one of them which wouldn't be affected by
            # rearrangement.
            s += refmat[iss[i], jss[j]] * temp
    return s

def scorePair3(iss, jss, refmat, lreverse=False, rreverse=False):
    """iss, jss must be lists of segments of the index range of refmat,
    our reference matrix.
    reurns the interaction score of iss and jss as if reindexed the matrix so
    that they will be adjuscent to each other.
    """
    s = 0
    temp = 0
    for i in range(len(iss)):
        for j in range(len(jss)):
            x = iss[i]
            y = jss[j]
            if lreverse:
                x = iss[-1 - i]
            if rreverse:
                y = jss[-1 - j]
            # temp = np.exp(-np.abs(i-j))
            # temp = np.exp(-np.abs(x - y))
            temp = np.exp(-np.abs(j + len(iss) - i))
            # we only care about interaction between the 2 segments and not
            # inside each one of them which wouldn't be affected by
            # rearrangement.
            s += refmat[x, y] * temp
    return s

# and the corrsponding indices are:
def articulate(l):
    """l is a list of positive integers.
    returns the implied articulation, meaning a list of lists (or 1d arrays)
    ls, such that ls[0] it the numbers 0 to l[0]-1, ls[1] is a list of the
    numbers ls[1] to ls[2]-1 etc.
    """
    # ls = [np.arange(l[0]).astype('uint64')]
    ls = []
    offsets = np.cumsum([0] + l)
    for i in range(0, len(l)):
        xs = np.arange(l[i]).astype("uint64") + offsets[i]
        ls.append(xs)
    return ls

# example:
articulate([5, 7, 3])

plt.ion()

blocks = [4, 4, 4, 4, 4]
segs = [5, 5, 4, 6]
articulate(blocks)
articulate(segs)

# the block matirx
X = blockMatrix(blocks)
X
plt.imshow(X)

ls = articulate(segs)
ls

scorePair2(ls[0], ls[0], X)
scorePair2(ls[0], ls[1], X)
scorePair2(ls[0], ls[2], X)
scorePair2(ls[0], ls[3], X)

plt.matshow(constrainMatrix(ls[0], X))
plt.matshow(constrainMatrix(ls[1], X))
plt.matshow(constrainMatrix(ls[2], X))
plt.matshow(constrainMatrix(ls[3], X))

# lets shuffle the segments
rs = articulate(segs)

# lets reverse segments 0 and 2
Y = blockMatrix(blocks)
Y

rs = articulate(segs)
rs[2] = np.flip(rs[2])
rs
#

Y = reindexMatrix(ls[2], rs[2], Y)

plt.matshow(X)
plt.matshow(Y)

rs[0] = np.flip(rs[0])
rs

Y = reindexMatrix(ls[0], rs[0], Y)

plt.matshow(X)
plt.matshow(Y)

plt.matshow(constrainMatrix(ls[0], Y))
plt.matshow(constrainMatrix(ls[1], Y))
plt.matshow(constrainMatrix(ls[2], Y))
plt.matshow(constrainMatrix(ls[3], Y))

# now lets change the orfer of the segments
neworder = [1, 3, 0, 2]

newindices = [ls[i] for i in neworder]
newindices
newindices = reduce(lambda x, y: x + list(y), newindices, [])
newindices

Z = reindexMatrix(list(range(len(Y))), newindices, Y)

plt.matshow(X)
plt.matshow(Y)
plt.matshow(Z)

plt.matshow(constrainMatrix(ls[0], Z))
plt.matshow(constrainMatrix(ls[0], X))

plt.matshow(constrainMatrix(ls[1], Z))
plt.matshow(constrainMatrix(ls[2], Z))
plt.matshow(constrainMatrix(ls[3], Z))

newsegments = [len(ls[i]) for i in neworder]
newsegments
zs = articulate(newsegments)
zs

plt.matshow(constrainMatrix(ls[0], Z))
plt.matshow(constrainMatrix(zs[1], Z))
plt.matshow(constrainMatrix(zs[2], Z))
plt.matshow(constrainMatrix(zs[3], Z))

scorePair2(zs[0], zs[0], Z)
scorePair2(zs[0], zs[1], Z)
scorePair2(zs[0], zs[2], Z)
scorePair2(zs[0], zs[3], Z)

scorePair2(ls[1], ls[0], Y)
scorePair2(ls[1], ls[1], Y)
scorePair2(ls[1], ls[2], Y)
scorePair2(ls[1], ls[3], Y)

scorePair3(ls[2], ls[3], Y)
scorePair3(ls[2], ls[3], X)
scorePair3(ls[2], ls[3], Y, lreverse=True)

# def improve(A, xs):
#    temp = np.random.randint(1, len(xs))
#    iss = xs[0]
#    jss = xs[temp]
#    # we're going to see if iss and jss belong together in some configuration
#    sl = scorePair3(iss,jss, A)
#    slrv = scorePair3(iss,jss, A, lreverse=True)
#    sr = scorePair3(jss,iss, A)
#    srrv = scorePair3(jss,iss, A, rreverse=True)
#    t = np.max([sl, slrv, sr, srrv])
#    neworder = [len(x) for x in xs]
#    neworder[1] = len(xs[temp])
#    neworder[temp] = len(xs[1])
#    B = np.zeros_like(A)
#    if t == 0:
#        return xs, A
#    if t == sl:
#        newindices = [xs[i] for i in neworder]
#        newindices = reduce(lambda x,y: x + list(y), newindices, [])
#        B = reindexMatrix(list(range(len(A))), newindices, A)
#    elif t == sr:
#        p = neworder[0]
#        neworder[0] = neworder[1]
#        neworder[1] = p
#        newindices = [xs[i] for i in neworder]
#        newindices = reduce(lambda x,y: x + list(y), newindices, [])
#        B = reindexMatrix(list(range(len(A))), newindices, A)
#    elif t == slrv:
#        # first flip the segment 0
#        rs = xs.copy()
#        rs[0] = np.flip(rs[0])
#        B = reindexMatrix(xs[0], rs[0], A)
#        # then switch segment temp with 1
#        newindices = [xs[i] for i in neworder]
#        newindices = reduce(lambda x,y: x + list(y), newindices, [])
#        B = reindexMatrix(list(range(len(B))), newindices, B)
#    else:
#        # first flip the segment 0
#        rs = xs.copy()
#        rs[0] = np.flip(rs[0])
#        B = reindexMatrix(xs[0], rs[0], A)
#        # then make the switch
#        p = neworder[0]
#        neworder[0] = neworder[1]
#        neworder[1] = p
#        newindices = [xs[i] for i in neworder]
#        newindices = reduce(lambda x,y: x + list(y), newindices, [])
#        B = reindexMatrix(list(range(len(B))), newindices, B)
#    newsegs = [len(xs[i]) for i in neworder if i != 0]
#    newsegs[0] += len(xs[0])
#    newsegs = articulate(newsegs)
#    return newsegs, B

plt.matshow(Z)
zs

ws, W = improve(Z, zs)

# The basic idea is that we are given a matrix and its associated articulation,
# and we need to perform a possible reverese of on segmentm, and relocate
# a segment or two so they will be neighbors. we need to return the new
# articulation structure.

def flip1(s, A, arts):
    """flips (reverses) the s'th segment, as listed by arts.
    returns new matrix.
    param s: the segment to flip.
    param A: the matrix.
    param arts: the articulation of A.
    """
    myarts = arts.copy()
    myarts[s] = np.flip(myarts[s])
    B = reindexMatrix(arts[s], myarts[s], A)
    return B

# example
fooblocks = [4, 6, 2, 7, 11, 6]
foo = blockMatrix(fooblocks)

foosegs = articulate([9, 15, 12])

plt.matshow(foo)
plt.matshow(constrainMatrix(foosegs[1], foo))
plt.matshow(constrainMatrix(foosegs[0], foo))

# flip segment 2
bar = flip1(1, foo, foosegs)
plt.matshow(foo)

plt.matshow(bar)

plt.matshow(constrainMatrix(foosegs[1], foo))
plt.matshow(constrainMatrix(foosegs[1], bar))

def indexing(arts):
    return reduce(lambda x, y: x + list(y), arts, [])

def swap2(s, r, A, arts):
    """swaps segments s and r, and returns the new
    matrix and the new segmentation.
    """
    myarts = arts.copy()
    myarts[s] = arts[r]
    myarts[r] = arts[s]
    B = reindexMatrix(indexing(arts), indexing(myarts), A)
    newarts = articulate([len(x) for x in myarts])
    return newarts, B

barsegs, bar = swap2(0, 2, bar, foosegs)

def improve(A, xs):
    """param: A: matrix.
    param xs: associated articulation.
    checks if segments 0 and 1 should be attached in some particular order.
    """
    if len(xs) == 1:
        return xs, A
    iss = xs[0]
    jss = xs[1]
    # we're going to see if iss and jss belong together in some configuration
    sl = scorePair3(iss, jss, A)
    slrv = scorePair3(iss, jss, A, lreverse=True)
    sr = scorePair3(jss, iss, A)
    srrv = scorePair3(jss, iss, A, rreverse=True)
    t = np.max([sl, slrv, sr, srrv])
    mysegs = [len(xs[i]) for i in range(1, len(xs))]
    mysegs[0] += len(xs[0])
    mysegs = articulate(mysegs)
    if t == 0:
        return xs, A
    if t == sl:
        # nothin to change
        return mysegs, A
    elif t == sr:
        # swap 0 and 1 segments
        _, B = swap2(1, 0, A, xs)
        return mysegs, B
    elif t == slrv:
        # first flip the segment 0
        B = flip1(0, A, xs)
        return mysegs, B
    else:
        # first flip the segment 0
        B = flip1(0, A, xs)
        # then make the switch
        _, B = swap2(1, 0, B, xs)
        return mysegs, B

# tests

# example
fooblocks = [4, 6, 2, 7, 11, 6]
foo = blockMatrix(fooblocks)

foosegs = articulate([9, 15, 12])

plt.matshow(foo)
plt.matshow(constrainMatrix(foosegs[1], foo))
plt.matshow(constrainMatrix(foosegs[0], foo))

# flip segment 2
bar = flip1(1, foo, foosegs)
plt.matshow(foo)
plt.matshow(bar)

plt.matshow(constrainMatrix(foosegs[1], foo))
plt.matshow(constrainMatrix(foosegs[1], bar))

barsegs, bar = improve(bar, foosegs)

plt.matshow(foo)

plt.matshow(bar)

barsegs, bar = improve(bar, barsegs)
plt.matshow(bar)

# now flip and replace
bar = flip1(0, foo, foosegs)
barsegs, bar = swap2(0, 2, bar, foosegs)
plt.matshow(foo)
plt.matshow(bar)

barsegs, bar = improve(bar, barsegs)
plt.matshow(bar)

# a bigger experiment

fooblocks = [15, 17, 19, 20, 10, 21, 30, 21, 40, 9, 27, 19]
foo = blockMatrix(fooblocks)

np.sum(fooblocks)

foosegs = [38, 34, 32, 38, 37, 34, 35]
np.sum(foosegs)
np.sum(fooblocks)

foosegs = articulate(foosegs)

plt.matshow(foo)

plt.matshow(constrainMatrix(foosegs[1], foo))
plt.matshow(constrainMatrix(foosegs[0], foo))

# now perform some flips and swaps
bar = flip1(1, foo, foosegs)
bar = flip1(5, foo, foosegs)
barsegs, bar = swap2(0, 3, bar, foosegs)
barsegs, bar = swap2(1, 2, bar, barsegs)
barsegs, bar = swap2(5, 2, bar, barsegs)
barsegs, bar = swap2(5, 0, bar, barsegs)
barsegs, bar = swap2(4, 1, bar, barsegs)

plt.matshow(foo)
plt.matshow(bar)

while len(barsegs) > 1:
    x = np.random.randint(1, len(barsegs))
    barsegs, bar = swap2(1, x, bar, barsegs)
    barsegs, bar = improve(bar, barsegs)
plt.matshow(foo)
plt.matshow(bar)
# yeshhhhh!!!!!!!


import numpy as np

def mean_square_error(y_true: np.ndarray, y_pred: np.ndarray) -> float:
    """
    Calculate MSE loss

    Parameters
    ----------
    y_true: ndarray of shape (n_samples, )
        True response values
    y_pred: ndarray of shape (n_samples, )
        Predicted response values

    Returns
    -------
    MSE of given predictions
    """
    res = (y_pred - y_true) ** 2
    final = np.mean(res)
    return final

def misclassification_error(y_true: np.ndarray, y_pred: np.ndarray, normalize: bool = True) -> float:
    """
    Calculate misclassification loss

    Parameters
    ----------
    y_true: ndarray of shape (n_samples, )
        True response values
    y_pred: ndarray of shape (n_samples, )
        Predicted response values
    normalize: bool, default = True
        Normalize by number of samples or not

    Returns
    -------
    Misclassification of given predictions
    """
    single_loss = np.vectorize(lambda x, y: 1 if x * y < 0 else 0)
    res = single_loss(y_true, y_pred)
    sum_all = np.sum(res)
    if not normalize:
        return sum_all
    return sum_all / (len(y_true) * 1.0)

def accuracy(y_true: np.ndarray, y_pred: np.ndarray) -> float:
    """
    Calculate accuracy of given predictions

    Parameters
    ----------
    y_true: ndarray of shape (n_samples, )
        True response values
    y_pred: ndarray of shape (n_samples, )
        Predicted response values

    Returns
    -------
    Accuracy of given predictions
    """
    P = (y_pred == 1).sum()
    N = (y_pred != 1).sum()
    TP = ((y_pred == 1) & (y_true == 1)).sum()
    TN = ((y_pred != 1) & (y_true != 1)).sum()
    return (TP + TN) / (P + N)

def cross_entropy(y_true: np.ndarray, y_pred: np.ndarray) -> float:
    """
    Calculate the cross entropy of given predictions

    Parameters
    ----------
    y_true: ndarray of shape (n_samples, )
        True response values
    y_pred: ndarray of shape (n_samples, )
        Predicted response values

    Returns
    -------
    Cross entropy of given predictions
    """
    raise NotImplementedError()


import numpy as np
from scipy.interpolate import CubicSpline
from scipy.fftpack import fft  # built-in fft

# 1.  Using DFT
#     Return c_k series (k = 0, 1, -1, 2, -2, ..., order, -order)
def coeffs_mydft(x, order):
    """Use DFT."""
    coeffs = [my_dft(x, 0)]
    for k in range(1, order+1):
        coeffs.extend([my_dft(x, k), my_dft(x, -k)])
    coeffs = np.array(coeffs) / len(coeffs)
    return coeffs

# 2. Using linear interpolation & power-2 FFT
#    Return c_k series (k = 0, 1, -1, 2, -2, ..., order, -order)
def coeffs_myfft(x, order):
    """Use linear interpolation & power-2 FFT."""
    # Linear interpolation
    x = np.asarray(x)
    N = np.power(2, int(np.rint(np.log2(x.shape[0]))))
    t = np.linspace(0, 1, num=x.shape[0])
    t_new = np.linspace(0, 1, num=N)
    x_new = lin_interp(t_new, t, x)

    # Power-2 FFT
    coeffs = my_fft_pow_two(x_new) / len(x_new)
    coeffs = sort_coeffs(coeffs, N=2*order+1)
    return coeffs

# 3. Using cubic interpolation & power-2 FFT
#    Return c_k series (k = 0, 1, -1, 2, -2, ..., order, -order)
def coeffs_myfft_plus(x, order):
    """Use cubic spline interpolation & power-2 FFT."""
    x = np.asarray(x)
    N = np.power(2, int(np.rint(np.log2(x.shape[0]))))
    t = np.linspace(0, 1, num=x.shape[0])
    t_new = np.linspace(0, 1, num=N)
    # x_new = cubic_spline_prd(t_new, t, x)
    cs = CubicSpline(t, x)
    x_new = cs(t_new)

    # Power-2 FFT
    coeffs = my_fft_pow_two(x_new) / len(x_new)
    coeffs = sort_coeffs(coeffs, N=2*order+1)
    return coeffs

# 4. Using built-in FFT
#    Return c_k series (k = 0, 1, -1, 2, -2, ..., order, -order)
def coeffs_fft(x, order):
    """Use built-in FFT."""
    coeffs = sort_coeffs(fft(x), N=2*order+1) / len(x)
    return coeffs

# Real coordinates convert to complex number
def real2complex(x, y):
    """return: x + y * j"""
    x, y = np.asarray(x), np.asarray(y)
    return x + 1j * y

# DFT implementation
def my_dft(x, k):
    """Return k-th coeff for DFT."""
    x = np.asarray(x)
    N = x.shape[0]
    exp_term = np.exp(-2j * np.pi * k * np.arange(N) / N)
    coeff = np.dot(exp_term, x)
    return coeff

# Power-2 FFT implementation
def my_fft_pow_two(x):
    """Compute power-2 FFT using iteration."""
    x = np.asarray(x)
    N = x.shape[0]
    x_even = x[::2]
    x_odd = x[1::2]

    if N >= 4:
        coeff_even = my_fft_pow_two(x_even)
        coeff_odd = my_fft_pow_two(x_odd)
        coeff_odd = coeff_odd * np.exp(-2j * np.pi * np.arange(int(N/2)) / N)
        coeffs = np.concatenate([coeff_even + coeff_odd, coeff_even - coeff_odd])
    else:
        coeffs = np.dot(np.array([[1, 1], [1, -1]]), x)
    return coeffs

# Linear interpolation
def lin_interp(x, xp, fp):
    """Linear interplation."""
    x, xp, fp = np.asarray(x), np.asarray(xp), np.asarray(fp)
    f_list = []
    for xx in x:
        idx = np.argmax(xp>xx)
        f_list.append(
            (fp[idx-1]*(xp[idx]-xx) + fp[idx]*(xx-xp[idx-1])) / (xp[idx]-xp[idx-1]))
    return np.array(f_list)

# Periodic cubic spline interpolation
def cubic_spline_prd(x, xp, fp):
    """Periodic cubic spline interpolation."""
    # To be done
    pass

# Sort the coefficients in order 0, 1, -1, 2, -2, ..., order, -order
def sort_coeffs(coeffs, N=None):
    """Sort coefficients in order 0, 1, -1, 2, -2, ..., order, -order."""
    coeffs = np.asarray(coeffs)
    if N is None:
        N = coeffs.shape[0]
    sorted_list = [coeffs[0]]
    for i in range(1, int((N+1)/2)):
        sorted_list.extend([coeffs[i], coeffs[-i]])
    if N % 2 == 0:
        sorted_list.append(coeffs[int(N/2)])
    return np.array(sorted_list)


# Conversion from Mathlab to Python:
# http://www2.meteo.uni-bonn.de/staff/venema/themes/surrogates/pmodel/pmodel.m

# *** function varargout = pmodel(noValues, p, slope) ***
# This function generates a multifractal time series using the p-model.
# Optionally you can also filter the result from the p-model in Fourier
# space to give it another fractal slope, e.g. to make it continuous and
# nonstationary. This is also called fractional integration. The p-model
# itself can only produce stationary time series, i.e time series where
# the variance is finite if you would extrapolate its power spectrum to
# infinite large scales.
#
# The parameter of the p-model is p. With p values close to 1 or 0 the time
# series is very peaked. With values close to 0.5 the p-model is much
# calmer; p=0.5 results in a constant unity vector.
# The parameter for the fractal integration is the slope of the power
# spectrum. Davis et al. calls slopes flatter than -1 stationary, and
# slopes between -1 and -3 nonstationary, with stationary increments.
# These nonstationary cases are at least continuous, but not
# differentiable. Slopes steeper than -3 are nonstionary and
# differentiable.
#
# Possible calls:
# [pModelTimeSeries] = pmodel
# [pModelTimeSeries] = pmodel(noValues)
# [pModelTimeSeries] = pmodel(noValues, p)
# [fractionalIntegratedTimeSeries] = pmodel(noValues, p, slope)
# [fractionalIntegratedTimeSeries, pModelTimeSeries] = pmodel(noValues, p, slope)
# If no number of values (noValues) is specified or it is empty, the
# default value of 256 is used.
# If no p is specified or it is empty, the default value of 0.375 is used.
# If no slope is specified, you will get a time series without fractional
# integration.
# If a slope is specified you will get the fractionally integrated time
# series, or if you use two output variables you will get both the p-model
# time series as well as its fractionally integrated version.
#
# This Matlab function is based on the article: Davis, A., A. Marshak, R.
# Cahalan, and W. Wiscombe, The landsat scale break in stratocumulus as a
# three-dimensional radiative transfer effect: implications for cloud
# remote sensing. Journal of the Atmospheric Sciences, Vol. 54, no. 2, 1997.
# First version by Victor Venema, Victor.Venema@uni-bonn.de, 25 January 2006.

# NumPy is the fundamental package for scientific computing with Python
import numpy as np
# This module provides access to the math functions defined by the C standard
import math

# function y2 = next_step_1d(y, p)
def next_step_1d(y, p):
    length = len(y)                         # len = numel(y);
    y2 = np.zeros(length * 2)               # y2 = zeros(1,len*2);
    sign = np.random.uniform(0, 1, length) - 0.5  # sign=rand(1, len)-0.5;
    sign = sign / abs(sign)                 # sign = sign./abs(sign);
    y2[::2] = y + sign * (1 - 2 * p) * y    # y2(1:2:2*len-1)=y+sign.*(1-2*p).*y;
    y2[1::2] = y - sign * (1 - 2 * p) * y   # y2(2:2:2*len)  =y-sign.*(1-2*p).*y;
    return y2

#function a=fractal_spectrum_1d(noValues, slope)
def fractal_spectrum_1d(noValues, slope):
# If you want to make a large number of time series, please rewrite this
# part to get rid of the for-loop. :-)
    ori_vector_size = noValues              # ori_vector_size = noValues;
    ori_half_size = int(ori_vector_size / 2)  # ori_half_size=ori_vector_size/2;
    # The magnitudes of the Fourier coefficients
    a = np.zeros(ori_vector_size)           # a = zeros(ori_vector_size,1);
    for t2 in range(1, (ori_half_size+1)+1, 1):   # for t2 = 1:ori_half_size+1
        index = t2 - 1                      # index = t2-1;
        t4 = 2 + ori_vector_size - t2       # t4 = 2 + ori_vector_size - t2;
        if t4 > ori_vector_size :           # if ( t4 > ori_vector_size )
            t4 = t2                         # t4 = t2;
        with np.errstate(divide='ignore') :
            coeff = np.array([index])**slope  # coeff = index.^slope;
        a[t2-1] = coeff                     # a(t2) = coeff;
        a[t4-1] = coeff                     # a(t4) = coeff;
    a[0] = 0    # The DC-component of the Fourier spectrum should be zero
    return a

def pmodel(**kwargs) :
    noValues = kwargs.get('noValues')
    p = kwargs.get('p')
    slope = kwargs.get('slope')

    # Check input
    # if ( nargin < 1 | isempty(noValues) )
    if len(kwargs) < 1 or kwargs.get('noValues') == 'None' :
        noValues = 256                  # noValues = 256;
    # if ( nargin < 2 | isempty(p) )
    if len(kwargs) < 2 or kwargs.get('p') == 'None' :
        p = 0.375                       # p = 0.375;
    if len(kwargs) < 3 :                # if ( nargin < 3 )
        slope = []                      # slope = [];

    # Calculate length of time series
    # noOrders=ceil(log2(noValues));
    noOrders = int(math.ceil(math.log(noValues, 2)))

    # noValuesGenerated = 2.^(noOrders); <== not used

    # y is the time series generated with the p-model.
    y = [1]                             # y = 1;
    for n in range(1, noOrders + 1, 1): # for n=1:noOrders
        y = next_step_1d(y, p)          # y = next_step_1d(y, p);

    # If a slope if specified also a fractionally integrated time series(x)
    # is calculated from y.
    if not slope == [] :                # if ( ~isempty(slope) )
        # Calculate the magnitudes of the coefficients of the Fourier spectrum.
        # The Fourier slope is half of the slope of the power spectrum.
        # fourierCoeff = fractal_spectrum_1d(noValues, slope/2)';
        fourierCoeff = fractal_spectrum_1d(noValues, slope / 2.0)
        meanVal = np.mean(y)            # meanVal = mean(y);
        stdy = np.std(y)                # stdy = std(y);
        # Calculate the Fourier coefficients of the original p-model time series
        x = np.fft.ifft(y - meanVal)    # x = ifft(y-meanVal);
        # Calculate the phases, as these are kept intact, should not be changed
        # by the Fourier filter
        phase = np.angle(x)             # phase = angle(x);
        # Calculate the complex Fourier coefficients with the specified
        # spectral slope, and the phases of the p-model
        x = fourierCoeff * np.exp(1j * phase)  #x = fourierCoeff .* exp(i*phase);
        # Generate the fractionally integrated time series.
        x = np.real(np.fft.fft(x))      # x = real(fft(x));
        x = x * stdy / np.std(x)        # x = x * stdy / std(x);
        x = x + meanVal                 # x = x + meanVal;
    else:
        x = y                           # x = y;

# not implented:
# Reduce the sizes of the time series and put them in the right output variable.
# if (nargout == 1 )
#    y=y(1:noValues);
#    varargout{1} = x;
# else
#    y=y(1:noValues);
#    x=x(1:noValues);
#    varargout{1} = x;
#    varargout{2} = y;
# end

    x = x[0:noValues+1]
    return x

# REFERENCES
# https://stackoverflow.com/questions/25087769/runtimewarning-divide-by-zero-error-how-to-avoid-python-numpy/25088239
# https://stackoverflow.com/questions/20161899/scipy-curve-fit-error-divide-by-zero-encountered
# https://docs.scipy.org
# https://pastebin.com/CxxVrwZF accessed 2019-05-31
# http://www2.meteo.uni-bonn.de/staff/venema/themes/surrogates/pmodel/pmodel.m
# https://www.python.org/
# https://www.gnu.org/software/octave/


import numpy as np
from scipy import misc
import pandas as pd
import os

# 1) Examen

## 2) Crear un vector de ceros de tamaño 10

vector_zeros_2 = np.zeros(10)
print('2) --> ', vector_zeros_2)

## 3) Crear un vector de ceros de tamaño 10 y el de la posicion 5 sea igual a 1

vector_zeros_3 = np.zeros(10)
vector_zeros_3[5] = 1

print('3) --> ', vector_zeros_3)

## 4) Cambiar el orden de un vector de 50 elementos, el de la posicion 1 es el de la 50 etc.

vector_zeros_4 = np.arange(50)
vector_zeros_4 = vector_zeros_4[::-1]

print('4) --> ', vector_zeros_4)

## 5) Crear una matriz de 3 x 3 con valores del cero al 8

matriz = np.arange(9)
matriz = matriz.reshape((3,3))

print('5) --> ', matriz)

## 6) Encontrar los indices que no sean cero en un arreglo

arreglo_indices = [1,2,0,0,4,0]
arreglo_indices = np.array(arreglo_indices)

resultado = np.where(arreglo_indices != 0)[0]

print('6) --> ', resultado)

## 7) Crear una matriz de identidad 3 x 3

matriz_identidad = np.eye(3)
print('7) --> ', matriz_identidad)

## 8) Crear una matriz 3 x 3 x 3 con valores randomicos

matriz_randomica = np.random.randint(27, size=27).reshape(3,3,3)
print('8) --> ', matriz_randomica)

## 9) Crear una matriz 10 x 10 y encontrar el mayor y el menor

matriz_diez = np.arange(100).reshape(10,10)

menor_valor = matriz_diez.min()
mayor_valor = matriz_diez.max()

print('9) menor --> ', menor_valor)
print('9) mayor --> ', mayor_valor)

## 10) Sacar los colores RGB unicos en una imagen (cuales rgb existen ej: 0, 0, 0 - 255,255,255 -> 2 colores)

imagen = misc.face()

resultado = len(np.unique(imagen, axis=0))
print('10) --> ', resultado)

## 11) ¿Como crear una serie de una lista, diccionario o arreglo?

mylist = list('abcdefghijklmnsopqrstuvwxyz')
myarr = np.arange(26)
mydict = dict(zip(mylist, myarr))

serie = pd.Series(mylist)
serie_diccionario = pd.Series(mydict)
serie_arreglo = pd.Series(myarr)

print('11) --> Serie de lista: ', serie, '\n')
print('11) --> Serie de diccionario: ', serie_diccionario, '\n')
print('11) --> Serie de arreglo ', serie_arreglo, '\n')

## 12) ¿Como convertir el indice de una serie en una columna de un DataFrame?

mylist = list('abcedfghijklmnopqrstuvwxyz')
myarr = np.arange(26)
mydict = dict(zip(mylist, myarr))
ser = pd.Series(mydict)

df = pd.DataFrame(ser).reset_index()

# Transformar la serie en dataframe y hacer una columna indice

df1 = pd.DataFrame(ser, index=['a'])

## 13) ¿Como combinar varias series para hacer un DataFrame?

ser1 = pd.Series(list('abcedfghijklmnopqrstuvwxyz'))
ser2 = pd.Series(np.arange(26))

df_combinado = pd.concat([ser1, ser2], axis = 1)
df_combinado = pd.DataFrame(df_combinado)

print('13) --> ', df_combinado)

## 14) ¿Como obtener los items que esten en una serie A y no en una serie B?

ser1 = pd.Series([1, 2, 3, 4, 5])
ser2 = pd.Series([4, 5, 6, 7, 8])

items_diferencia = np.setdiff1d(ser1, ser2)
print('14) --> ', items_diferencia)

## 15) ¿Como obtener los items que no son comunes en una serie A y serie B?

ser1 = pd.Series([1, 2, 3, 4, 5])
ser2 = pd.Series([4, 5, 6, 7, 8])

items_conjuncion = set(ser1) ^ set(ser2)
items_conjuncion = list(items_conjuncion)
items_conjuncion = pd.Series(items_conjuncion)

print('15) --> ', items_conjuncion, '\n')

## 16) ¿Como obtener el numero de veces que se repite un valor en una serie?

ser = pd.Series(np.take(list('abcdefgh'), np.random.randint(8, size=30)))

repeticiones, contador = np.unique(ser, return_counts=True)
repeticiones = dict(zip(repeticiones, contador))

print(repeticiones)
print(contador)

print('16) --> ', repeticiones, '\n')

## 17) ¿Como mantener los 2 valores mas repetidos de una serie, y a los demas valores cambiarles por 0 ?

np.random.RandomState(100)
ser = pd.Series(np.random.randint(1, 5, [12]))

valores_repetidos, contador = np.unique(ser, return_counts=True)

print('serie --> ', ser)
print('contador --> ', ser)
indice = np.argsort(-contador)
print('indice --> ', indice)
valores_repetidos = valores_repetidos[indice]

valores_repetidos[2:] = 0

print('17) --> Valores repetidos', valores_repetidos)

## 18) ¿Como transformar una serie de un arreglo de numpy a un DataFrame con un `shape` definido?

ser = pd.Series(np.random.randint(1, 10, 35))

df_shape = pd.DataFrame(ser.values.reshape(7,5))

print('18) --> ', df_shape)

## 19) ¿Obtener los valores de una serie conociendo la posicion por indice?

ser = pd.Series(list('abcdefghijklmnopqrstuvwxyz'))
pos = [0, 4, 8, 14, 20]
# a e i o u

resultado = ser[pos]

print('19) --> ', resultado)

## 20) ¿Como anadir series vertical u horizontalmente a un DataFrame?

ser1 = pd.Series(range(5))
ser2 = pd.Series(list('abcde'))

#Verical
df1 = pd.concat([pd.DataFrame(),ser2], ignore_index = True)

#Horizontal
df2 = pd.DataFrame().append(ser1, ignore_index=True)

print('20) Vertical --> ', df1)
print('21) Horizontal --> ', df2)

## 21)¿Obtener la media de una serie agrupada por otra serie?

#`groupby` tambien esta disponible en series.

frutas = pd.Series(np.random.choice(['manzana', 'banana', 'zanahoria'], 10))
pesos = pd.Series(np.linspace(1, 10, 10))
print(pesos.tolist())
print(frutas.tolist())
#> [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]
#> ['banana', 'carrot', 'apple', 'carrot', 'carrot', 'apple', 'banana', 'carrot', 'apple', 'carrot']

# Los valores van a cambiar por ser random
# apple     6.0
# banana    4.0
# carrot    5.8
# dtype: float64

media_agrupada = pd.concat([frutas, pesos], axis = 1)
media_agrupada = media_agrupada.groupby(media_agrupada[0], as_index=False)[1].mean()

print('21) --> \n', media_agrupada)

## 22)¿Como importar solo columnas especificas de un archivo csv?

#https://raw.githubusercontent.com/selva86/datasets/master/BostonHousing.csv.

path = "./archivo.csv"

data_csv = pd.read_csv(
    path,
    nrows = 10)

columnas = ['crim', 'zn', 'indus']

data_tres_columnas = pd.read_csv(path, nrows=10, usecols=columnas)

print('22) --> ', data_tres_columnas)


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

# Let’s run the basic SIR model

# describe the model
def deriv(y, t, N, beta, k, delta, m ):
    S, E, I, R, D = y
    dSdt = -beta  * S * I / N
    dEdt = (beta  * S * I / N - k * E)
    dIdt = delta * E - k * I
    dRdt = k * I
    dDdt = m * I
    return  dSdt, dEdt, dIdt, dRdt, dDdt

# describe the parameters
N =  1000000 # population
m = 0.02    #percentage of the contaminated that dies
delta = 0.2
P = 0.9 #the effect of restrictions in percent, 1 = no effect 0 = full effect
beta = P * 2.5
k=1/6
S0, E0, I0, R0, D0= N-1, 1, 0, 0, 0 # initial conditions: one infected, rest susceptible

t = np.linspace(0, 99, 100) # Grid of time points (in days)
y0 = S0, E0, I0, R0, D0 # Initial conditions vector

    # Integrate the SIR equations over the time grid, t.
ret = odeint(deriv, y0, t, args=(N, beta, k, delta, m, ))
S, E, I, R , D= ret.T

def plotsir(t, S, E, I, R, D):
    f, ax = plt.subplots(1,1,figsize=(10,4))
    ax.plot(t, S, 'b', alpha=0.7, linewidth=2, label='Susceptible')
    ax.plot(t, E, 'y', alpha=0.7, linewidth=2, label='Exposed')
    ax.plot(t, I, 'r', alpha=0.7, linewidth=2, label='Infected')
    ax.plot(t, R, 'g', alpha=0.7, linewidth=2, label='Recovered')
    ax.plot(t, D , 'k', alpha=0.7, linewidth=2, label='Deceased')
    ax.set_xlabel('Time (days)')

    ax.yaxis.set_tick_params(length=0)
    ax.xaxis.set_tick_params(length=0)
    ax.grid(b=True, which='major', c='w', lw=2, ls='-')
    legend = ax.legend()
    legend.get_frame().set_alpha(0.5)
    for spine in ('top', 'right', 'bottom', 'left'):
        ax.spines[spine].set_visible(False)
    #plt.savefig('C:/Users/albin/OneDrive/Dokument/GitHub/SIR_model_SEE070/SIR_model_SEE070/Lecture2dec.png')
    plt.show()
plotsir(t, S, E, I, R, D)


import pandas as pd
import numpy as np
from scipy.stats import entropy

def total_entropy(df : pd.DataFrame) -> float:
    '''Gets the entropy of the entire dataset'''
    label = df.keys()[-1]

    # Gets the total entropy of the dataframe
    ent = entropy(df[label].value_counts(),base=2)
    return ent

def attribute_entropy(df : pd.DataFrame,attribute : str) -> float:
    '''Gets the entropy of a specific attribute'''
    label = df.keys()[-1]
    variables = df[attribute].unique()

    # Gets the entropy of the attribute
    ent = np.array([-(len(df[df[attribute] == variable]) / len(df)) * entropy(df[df[attribute]==variable][label].value_counts(),base=2) for variable in variables]).sum()
    return abs(ent)

def create_tree(df : pd.DataFrame) -> dict:
    '''ID3 decision tree creating algorithm. Requires df to be a pd.DataFrame, and that the label of the data is the last column'''
    label = df.keys()[-1]
    #Get attribute with maximum information gain
    node = df.keys()[:-1][np.argmax([total_entropy(df)-attribute_entropy(df,key) for key in df.keys()[:-1]])]

    #Get distinct value of that attribute
    att_values = df[node].unique()
    # Create the tree
    tree={}
    tree[node] = {}

    for att_value in att_values:
        # Create table with specific attribute
        subtable = df[df[node] == att_value].reset_index(drop=True)
        # Get how many outcomes there are for the attribute
        table_values,counts = np.unique(subtable[label],return_counts=True)

        # If there is only one outcome
        if len(counts) == 1:
            tree[node][att_value] = table_values[0]
        # If there is more than one outcome, need to create another node
        else:
            tree[node][att_value] = create_tree(subtable)
    return tree

def is_number(s) -> bool:
    try:
        float(s)
        return True
    except ValueError:
        return False

def go(dic : dict, last_key : None, current_level : None) -> None:
    '''Prints the tree, best used with the wrapper. ONLY WORKS ON TREES WITH NUMERIC ENTRIES'''
    for key, value in dic.items():
        if is_number(key):
            for i in range(current_level - 1):
                print("| ", end="")

            print(last_key, "= ", end="")
            print(key, ": ", end="")
        else:
            if current_level > 0:
                print("")

            current_level = current_level + 1

        if isinstance(value, dict):
            go(value, key, current_level)
        else:
            print(value)

def print_tree(tree : dict) -> None:
    '''Prints the tree created with ID3 algorithm
       ONLY PRINTS PROPERLY WITH TREES WITH NUMERIC NODE ENTRIES'''
    go(tree,None,0)

def classify_series(data : pd.Series,tree : dict):
    '''Given a series, returns the tree's classification of the series
       data is a pd.Series object, and tree is a dict created with the ID3 algorithm'''
    label = tree
    while isinstance(label,dict):
        key = list(label.keys())[0]
        value = data[key]
        label = label[key][value]
    return label


"""CNOT matrix."""
import numpy as np

def cnot() -> np.ndarray:
    r"""
    Produce the CNOT matrix [WikCNOT]_.

    The CNOT matrix is defined as

    .. math::
        \text{CNOT} =
        \begin{pmatrix}
            1 & 0 & 0 & 0 \\
            0 & 1 & 0 & 0 \\
            0 & 0 & 0 & 1 \\
            0 & 0 & 1 & 0
        \end{pmatrix}.

    Examples
    ==========

    >>> from toqito.matrices import cnot
    >>> cnot()
    [[1 0 0 0]
     [0 1 0 0]
     [0 0 0 1]
     [0 0 1 0]]

    References
    ==========
    .. [WikCNOT] Wikipedia: Controlled NOT gate
        https://en.wikipedia.org/wiki/Controlled_NOT_gate

    :return: The CNOT matrix.
    """
    return np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]])


import numpy as np
from scipy.constants import mu_0, pi

def MagneticDipoleFields(txLoc, obsLoc, component, dipoleMoment=1.):
    """
        Calculate the vector potential of a set of magnetic dipoles
        at given locations 'ref. <http://en.wikipedia.org/wiki/Dipole#Magnetic_vector_potential>'

        :param numpy.ndarray txLoc: Location of the transmitter(s) (x, y, z)
        :param numpy.ndarray obsLoc: Where the potentials will be calculated (x, y, z)
        :param str component: The component to calculate - 'x', 'y', or 'z'
        :param numpy.ndarray dipoleMoment: The vector dipole moment (vertical)
        :rtype: numpy.ndarray
        :return: The vector potential each dipole at each observation location
    """

    if component=='x':
        dimInd = 0
    elif component=='y':
        dimInd = 1
    elif component=='z':
        dimInd = 2
    else:
        raise ValueError('Invalid component')

    txLoc = np.atleast_2d(txLoc)
    obsLoc = np.atleast_2d(obsLoc)
    dipoleMoment = np.atleast_2d(dipoleMoment)

    nFaces = obsLoc.shape[0]
    nTx = txLoc.shape[0]

    m = np.array(dipoleMoment).repeat(nFaces, axis=0)
    B = np.empty((nFaces, nTx))
    for i in range(nTx):
        dR = obsLoc - txLoc[i, np.newaxis].repeat(nFaces, axis=0)
        r = np.sqrt((dR**2).sum(axis=1))
        if dimInd == 0:
            B[:, i] = +(mu_0/(4*pi)) /(r**3) * (3*dR[:,2]*dR[:,0]/r**2)
        elif dimInd == 1:
            B[:, i] = +(mu_0/(4*pi)) /(r**3) * (3*dR[:,2]*dR[:,1]/r**2)
        elif dimInd == 2:
            B[:, i] = +(mu_0/(4*pi)) /(r**3) * (3*dR[:,2]**2/r**2-1)
        else:
            raise Exception("Not Implemented")
    if nTx == 1:
        return B.flatten()
    return B


import pandas as pd
import numpy as np

def IS(l,u,y,q):
    """
    calculation of the Interval Score

    l:  left interval limit
    u:  right interval limit
    y:  argument of IS
    q:  quantile level corresponding to limits

    Output
    IS value in y for given l,u,q
    """
    if y<l:
        return 2*(l-y) + q*(u-l)
    elif y>u:
        return 2*(y-u) + q*(u-l)
    else:
        return q*(u-l)

def WIS_calc(ci, target):
    """
    compute WIS in the point target in for 21 quantiles q_vals(below)

    ci:     array with empirical quantiles of the forecast
    target: ground truth at which to estimate WIS

    Output:
    WIS value
    """
    q_vals = [0.01,0.025] + list(np.arange(0.05, 0.5, 0.05))
    WIS_qpart = np.sum([IS(ci[i], ci[len(ci)-i-1], target, q_vals[i]) for i in range(len(q_vals))])
    WIS = (0.5*np.abs(ci[len(q_vals)]-target) + WIS_qpart)/(2*len(q_vals)+1)
    return WIS

def compute_WIS(df, model, target_type="day"):
    """
    create a DataFrame with WIS estimates for each time point in df
    df: DataFrame with empirical quantiles for the forecast and ground truth value
    model: model name
    target_type: "cumulative" or "day" depending on the data in df

    Output:
    DataFrame with WIS for each time point
    """
    WIS_ = []
    q = [0.01,0.025] + list(np.arange(0.05,1,0.05)) +  [0.975,0.99]
    q = [str(round(x,3)) for x in q]
    if target_type=="day":
        for index, row in df[q].iterrows():
            WIS_.append(WIS_calc(row,df["target_uptodate"][index]))
    if target_type=="cumulative":
        for index, row in df[q].iterrows():
            WIS_.append(WIS_calc(row, df["target_cumul"][index]))
    df[model+"WIS"] = WIS_
    return df

def coverage_point(ci, target):
    """
    average coverage over 21 quantiles
    ci:     array with empirical quantiles of the forecast
    target: ground truth

    Output:
    coverage averaged over 21 quantiles
    """
    q_vals = [0.01,0.025] + list(np.arange(0.05, 0.5, 0.05))
    coverage = np.sum([1*((ci[i]<=target)&(ci[len(ci)-i-1]>=target)) for i in range(len(q_vals))])
    return coverage

def compute_coverage(df, model, target_type="day"):

    """
    create a DataFrame with average coverage estimates for each time point in df
    df: DataFrame with empirical quantiles for the forecast and ground truth value
    model: model name
    target_type: "cumulative" or "day" depending on the data in df

    Output:
    DataFrame with average coverage for each time point
    """
    coverage = []
    q = [0.01,0.025] + list(np.arange(0.05,1,0.05)) +  [0.975,0.99]
    q = [str(round(x,3)) for x in q]
    if target_type=="day":
        for index, row in df[q].iterrows():
            coverage.append(coverage_point(row, df["target_uptodate"][index]))
    if target_type=="cumulative":
        for index, row in df[q].iterrows():
            coverage.append(coverage_point(row, df["target_cumul"][index]))

    df[model+"_coverage"] = coverage
    return df


'''
An example of optimization in SpaceFuncs with olympic rings
Let's search 5 points (1 from each ring) that form polygon with maximum area
'''
from SpaceFuncs import *
from FuncDesigner import *
from openopt import *
from numpy import linspace

centers = [(-2, 0), (0, 0), (2, 0), (1, -1), (-1, -1)]
colors = ['b', 'k', 'r', 'g', 'y']
radius = 0.9
circles = [Circle(center, radius, edgecolor = colors[i], linewidth = 3) for i, center in enumerate(centers)]

n = len(circles)
x = oovars(n)
y = oovars(n)

Points = [Point(x[i], y[i]) for i in range(n)]
P = Polygon(*Points)
objective = P.S # area

# we will deal with disks instead of circles because handling nonlinear equalities is more difficult then inequalities
# you can switch via d = circle.disk, c = disk.circle (without brackets)
constraints = [circles[i].disk.contains(Points[i], tol = 1e-5) for i in range(n)]

X, Y = linspace(-1, 1, n), [0]*n
startPoint = {x: X, y: Y}

p = NLP(objective, startPoint, constraints = constraints)

solver = 'scipy_cobyla' # requires SciPy installed; see openopt.org for other available solvers
p.debug = 1
r = p.maximize(solver)
''' Intel Atom 1.6 GHz result:
------------------------- OpenOpt 0.39 -------------------------
solver: scipy_cobyla   problem: unnamed    type: NLP   goal: maximum
 iter   objFunVal   log10(MaxResidual/ConTol)
    0  0.000e+00                      4.22
   10  1.063e+01                      2.08
   20  1.069e+01                      0.64
   30  1.068e+01                     -5.60
   40  1.068e+01                     -8.15
istop: 1000
Solver:   Time Elapsed = 5.0 	CPU Time Elapsed = 4.99
objFunValue: 10.684673 (feasible, max(residuals/requiredTolerances) = 7.09988e-09)
'''

# some basic plotting is available in SpaceFuncs API:
for circle in circles:
    circle.plot()

# For features beyond it use matplotlib:
import pylab
xx, yy = x(r).tolist(), y(r).tolist()
xx.append(xx[0])
yy.append(yy[0])
pylab.plot(xx, yy, marker = (5, 1, 0), markersize = 15)
pylab.xlim([-3, 3])
pylab.ylim([-3, 3])
pylab.show()
# see http://openopt.org/Image:olympic.png


import numpy as np
import scipy.sparse as sp
import time

def power_iteration(A, num_simulations):
	tol = 10**(-9)
	Ashape = A.get_shape()
        eigvec = np.random.rand(Ashape[0])
	eigval_old = np.dot(np.transpose(eigvec),A.dot(eigvec))/np.dot(np.transpose(eigvec),eigvec)
	for i in range(num_simulations):
        	# calculate the matrix-by-vector product Ab
        	eigvec1 = A.dot(eigvec)
        	# calculate the norm
        	eigvec1_norm = np.linalg.norm(eigvec1)
        	# re normalize the vector
        	eigvec = eigvec1 / eigvec1_norm

		eigval_new = np.dot(np.transpose(eigvec),A.dot(eigvec))/np.dot(np.transpose(eigvec),eigvec)
		if (abs(eigval_new-eigval_old)/eigval_new) < tol:
			return eigval_new
		eigval_old = eigval_new

	return eigval_new

def get_maxeigval(A):
	niter = 200000
	#maxeigval = power_iteration(A,niter)
	#UNSTABLE
	#rc = np.array([i for i in xrange(Ashape[0])])
	#Atemp = A - maxeigval*sp.coo_matrix((np.ones(Ashape[0]),(rc,rc)),shape=Ashape)
	#mineigval = power_iteration(Atemp,niter) + maxeigval

	return power_iteration(A,niter)

#stime=time.time()
#A = sp.load_npz('ham_kin.npz')
#print 'Time to load..'+str(time.time()-stime)+' seconds...'
#print 'Total time...'+str(time.time()-stime)+' seconds...'


# Acceptance-Rejection method using Monte Carlo

import numpy as np
import matplotlib.pyplot as plt

def pdf(costh, P_mu):
    # define our probability density function
    return 0.5 * (1.0 - 1.0 / 3.0 * P_mu * costh)

N_measures=10000000
P_mu=1.0

x = np.random.uniform(-1.0, 1.0, size=N_measures)

x_axis = np.linspace(-1.0,1.0, 1000)
fmax= np.amax(pdf(x_axis,P_mu)) #find the maximum of the function

u = np.random.uniform(0, fmax, size= N_measures)

# we use a mask in order to reject the values we don't want
data_pdf = x[u < pdf(x,P_mu)]

# look out, the number of bins cannot be very large, otherwise the histogram doesn't fit the function.
plt.hist(data_pdf, bins=100, normed=True, histtype="step", color="black")
plt.plot(x_axis,pdf(x_axis,P_mu), color="orange", linewidth="1")
plt.xlabel(r'$cos \ \theta$')
plt.ylabel('f')
plt.xlim(-1.,1.)
plt.ylim(0.3,0.7)

print(len(data_pdf))
# plt.savefig(r"C:\Users\Aleix López\Desktop\acc_rej.jpg")
plt.show()

Projects/Probability and Monte Carlo Techniques/_2_acceptance_rejection_MC.py


import numpy as np

from optimizer import Optimizer

class Nesterov(Optimizer):
    """
    Accelerated gradient descent with constant learning rate.
    For details, see, e.g., http://mpawankumar.info/teaching/cdt-big-data/nesterov83.pdf

    Arguments:
        lr (float, optional): an estimate of the inverse smoothness constant
    """
    def __init__(self, lr=None, strongly_convex=False, mu=0, *args, **kwargs):
        super(Nesterov, self).__init__(*args, **kwargs)
        self.lr = lr
        if mu < 0:
            raise ValueError("Invalid mu: {}".format(mu))
        if strongly_convex and mu == 0:
            raise ValueError("""Mu must be larger than 0 for strongly_convex=True,
                             invalid value: {}""".format(mu))
        self.strongly_convex = strongly_convex
        if self.strongly_convex:
            self.mu = mu

    def step(self):
        if not self.strongly_convex:
            alpha_new = 0.5 * (1 + np.sqrt(1 + 4*self.alpha**2))
            self.momentum = (self.alpha - 1) / alpha_new
            self.alpha = alpha_new
        self.x_nest_old = self.x_nest.copy()
        self.grad = self.loss.gradient(self.x)
        self.x_nest = self.x - self.lr*self.grad
        self.x = self.x_nest + self.momentum*(self.x_nest-self.x_nest_old)

    def init_run(self, *args, **kwargs):
        super(Nesterov, self).init_run(*args, **kwargs)
        if self.lr is None:
            self.lr = 1 / self.loss.smoothness()
        self.x_nest = self.x.copy()
        if self.strongly_convex:
            kappa = (1/self.lr)/self.mu
            self.momentum = (np.sqrt(kappa)-1) / (np.sqrt(kappa)+1)
        else:
            self.alpha = 1.


import numpy as np
from scipy import linalg as la
import scipy.sparse as spar
from scipy.sparse import linalg as sparla

def problem1(l):
    '''
    print the answer to problem 1 in the Beam buckling lab.
    Inputs:
        l -- length of the beam in feet
    '''
    # initialize constants, do unit conversion
    r = 1.0
    E = 4.35*r*12**2*10**6
    L = l
    I = np.pi*r**4/4
    n = 1000
    h = L/n

    # build the tri-diagonal matrix
    diag = -2*np.ones(n)*E*I/h**2
    odiag = np.ones(n-1)*E*I/h**2
    band = np.zeros((2,n))
    band[0,1:] = -odiag
    band[1,:] = -diag

    # calculate and print smallest eigenvalue
    evals = la.eigvals_banded(band)
    print evals[0]

    # print the analytically calculated answer
    print np.pi**2*E*I/L**2

def problem2(l):
    '''
    print the solution to the second problem in Beam Buckling
    Inputs:
        l -- length of beam in feet
    '''
    # initialize constants, unit conversions
    r = 1.0
    E1 = r*12**2*10**7
    E2 = 4.35*r*12**2*10**6
    E3 = 5*r*12**2*10**5
    L = 20.0
    I = np.pi*r**4/4
    n = 100
    h = L/n

    # build the sparse matrix B
    b_diag = np.ones(n)
    b_diag[0:n/3] = E1*I/h**2
    b_diag[n/3:n/3+n/3] = E2*I/h**2
    b_diag[n/3+n/3:] = E3*I/h**2
    B = spar.spdiags(b_diag, np.array([0]), n, n, format='csc')

    # build the sparse matrix A
    diag = -2*np.ones(n)
    odiag = np.ones(n)
    A = spar.spdiags(np.vstack((-odiag, -odiag, -diag)),
                     np.array([-1,1,0]), n, n, format='csc')

    # calculate and print the smallest eigenvalue
    evals = sparla.eigs(B.dot(A), which='SM')
    print evals[0].min()


import numpy as np
from scipy.misc import derivative
from scipy.special import erf, softmax

class Activation:
	"""
	Class for several activation functions.
	Implemented activation functions are.

	- RELU
	- tanh (Hyperbolic Tangent)
	- Sigmoid (Logistic Function)
	- Linear
	- arctan
	- Error Function (erf)
	- Softmax
	"""

	@classmethod
	def relu(cls, x):
		return np.maximum(0, x)

	@classmethod
	def tanh(cls, x):
		return np.tanh(x)

	@classmethod
	def sigmoid(cls, x):
		return 1 / (1 + np.exp(-x))

	@classmethod
	def linear(cls, x):
		return x

	@classmethod
	def arctan(cls, x):
		return np.arctan(x)

	@classmethod
	def erf(cls, x):
		return erf(x)

	@classmethod
	def softmax(cls, x):
		return softmax(x)

class Loss:
	"""
	Class for several loss functions.
	Implemented loss functions are:

	- MSE (Mean Squared Error)
	- MAE (Mean Absolute Error)
	- Binary Cross Entropy
	"""

	@classmethod
	def mse(cls, t_p, o_p):
		m = t_p.shape[0]
		return (1/m) * (t_p - o_p)**2

	@classmethod
	def mae(cls, t_p, o_p):
		m = t_p.shape[0]
		return (1/m) * np.abs(t_p - o_p)

	@classmethod
	def binary_crossentropy(cls, t_p, o_p):
		return -(t_p*np.log(o_p) + (1-t_p)*np.log(1-o_p))

class NeuralNet:
	"""
	Simple feed-forward neural network model.

	Parameters
	----------
	layers : list<int> or list<tuple>
		List of layer sizes or list of tuples of layer sizes and activations.
	activations : str, function, list<str>, list<functions> or list of mixed str or functions, default=None
		Activation functions for each layer. If None, linear activation is applied.

	 Examples
	--------

	>>> my_layers = [10, 5, 1]
	>>> my_activations = ['relu', np.tanh, my_activation_func]
	>>> model = NeuralNet(my_layers, my_activations)
	>>> model.fit(X, y, **kwargs)
	>>>
	>>> # Make predicitons by using `predict` method
	>>> model.predict(X, **kwargs)
	"""

	def __init__(self, layers: list, activations=None):

		if activations is not None:
			assert len(layers) == len(activations)
		else:
			activations = ['linear' for _ in range(len(layers))]

		self.L = len(layers)

		check_all_tuple = all([isinstance(l, tuple) and len(l) == 2 for l in layers])
		if check_all_tuple:
			layers = [l[0] for l in layers]
			activations = [l[1] for l in layers]

		_activations = self._check_activations(activations)

		self.layers = layers.copy()
		self.activations = _activations

	@property
	def weights(self) -> (list, list):
		"""
		Return a tuple containing list of weights and biases
		"""
		return self.__W, self.__T

	@weights.setter
	def weights(self, val):
		raise ValueError("Weights does not support assignment.")

	def _check_activations(self, activations):
		_activations = activations.copy()
		check_type = [isinstance(a, str) or callable(a) for a in activations]

		if not check_type:
			raise ValueError("'activations' must be a list of callables or strings")

		for i, a in enumerate(_activations):
			if isinstance(a, str):
				try:
					activation = getattr(Activation, a)
				except:
					raise NotImplementedError(f"Activation function {a} is not implemented, try using a udf instead.")
				else:
					del _activations[i]
					_activations.insert(i, activation)

		return _activations

	def error(self, t_p, o_p):
		E_p = self.loss(t_p, o_p)
		return np.sum(E_p)

	def initialize_weights(self, layers: list):
		self.__W = [np.random.normal(0, 0.05, size=(layers[i], s)) for i, s in enumerate(layers[1:])]
		self.__T = [np.zeros((1, s)) for s in layers[1:]]

	@staticmethod
	def forward_pass(o_p, w, t, activation_func):
		net_p = np.matmul(o_p, w) + t
		o_p = activation_func(net_p)
		return o_p, net_p

	def forward_propagate(self, input_data):
		o_ps, net_ps = [input_data], [input_data]

		o_p = input_data

		for layer in range(self.L):
			w, t = self.__W[layer], self.__T[layer]
			o_p, net_p = self.forward_pass(o_p, w, t, activation_func=self.activations[layer])
			o_ps.append(o_p); net_ps.append(net_p)

		return o_ps, net_ps

	@staticmethod
	def backward_pass(f, net_p, delta_p, w):
		dnet_p = derivative(f, net_p)
		delta_w = np.matmul(delta_p, w.T)
		return dnet_p * delta_w

	def backward_propagate(self, o_ps, net_ps, t_p):
		m = t_p.shape[0]
		dW, dT = [], []

		# Initialize the recursion with the output layer
		do_p = derivative(lambda x: self.loss(t_p, x), o_ps[-1])
		dnet_p = derivative(self.activations[-1], net_ps[-1])
		delta_p = do_p * dnet_p

		for layer in reversed(range(self.L)):
			dw = np.matmul(o_ps[layer].T, delta_p)
			dt = np.sum(delta_p, axis=0, keepdims=True)

			if layer != 0: # No need to compute delta_0 (skipped for performance)
				w = self.__W[layer]
				delta_p = self.backward_pass(f=self.activations[layer], net_p=net_ps[layer], delta_p=delta_p, w=w)

			dW.insert(0, dw); dT.insert(0, dt)

		return dW, dT

	def update_weights(self, dW, dT, eta):
		i = 0
		while i < len(self.__W):
			self.__W[i] -= eta * dW[i]
			self.__T[i] -= eta * dT[i]

			i += 1

	def fit(self, X, y, epochs: int = 50, learning_rate: float = 0.1, loss='mse', verbose=1):
		input_size = X.shape[1]
		self.history = {}
		self.history['costs'] = []

		if callable(loss):
			self.loss = loss
		else:
			if isinstance(loss, str):
				self.loss = getattr(Loss, loss)
			else:
				raise NotImplementedError(f"Loss function {loss} is not implemented, try using a udf instead.")

		self.layers.insert(0, input_size)
		self.activations.insert(0, Activation.linear)
		self.initialize_weights(self.layers)

		y = y.reshape(len(y), 1)

		for epoch in range(epochs):
			o_ps, net_ps = self.forward_propagate(X)
			E = self.error(t_p=y, o_p=o_ps[-1])
			self.history['costs'].append(E)
			dW, dT = self.backward_propagate(o_ps, net_ps, y)
			self.update_weights(dW, dT, learning_rate)

			if verbose >= 1 and epoch % int(epochs / np.log(epochs)) == 0:
				print(f'Epoch {epoch} \t Cost: {E}')

	def predict(self, X, return_classes=False):
		o_ps, _ = self.forward_propagate(X)

		if return_classes:
			return np.vectorize(lambda x: 1 if x>0.5 else 0)(o_ps[-1])

		return o_ps[-1]


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

''' The problem is: dy/dt = f(t,y).'''

def f(t, y):
    return 0.5*y

# a,b: edges of the interval, w0:ititial value, h:step
def Euler(a, b, w0, h):
    n = ceil((b-a)/h)

    w, t = np.zeros(n+1), np.zeros(n+1)
    w[0], t[0] = w0, a

    for i in range(n):
        w[i+1] = w[i] + h*f(t[i], w[i])
        t[i+1] = t[i] + h

    return w, t

if __name__ == '__main__':

    a, b, w0, h = 0, 2, 1, 0.2

    print(Euler(a, b, w0, h)[0])

    # Visualization
    x = np.linspace(0, 2, 100)

    y = np.zeros(len(x))

    for j in range(len(x)):
        y[j] = exp(0.5*x[j])

    plt.plot(x, y, color="black", label="Solution")
    plt.plot(Euler(a, b, w0, h)[1], Euler(a, b, w0, h)[0], label="Euler")
    plt.xlabel("t")
    plt.ylabel("y")
    plt.legend()
    plt.show()


import numpy as np
import multiprocessing
from multiprocessing.dummy import freeze_support
import parmap
import matplotlib.pyplot as plt
from scipy import special

num_cores = multiprocessing.cpu_count()

"""
BER for BPSK in Rayleigh channel
y = hx + n
y: recived symbol
h: Rayleigh multipath channel, var=1
x: BPSK symbol(+1, -1)
n: Gaussian Noise

REF: http://www.dsplog.com/2008/08/10/ber-bpsk-rayleigh-channel/
"""

# number of BPSK symbol
n = 1_000_000

# SNR[dB] values
SNR = np.linspace(-3,35,100)

# generating BPSK symbol
def generate_BPSK(len_symbol):
    return np.random.randint(2, size=len_symbol) * 2 - 1

# Rayleigh Channel
def empirical_rayleigh_channel_BER(SNR, N):
    """
    SNR: Eb/No
    N: number of symbol
    return: n_error(number of errors)
    """
    x = generate_BPSK(N)
    n_error = []

    for i in range(0,len(SNR)):
        n = (1/np.sqrt(2)) * (np.random.randn(1,N) + 1j * np.random.randn(1,N)) # white gaussian noise, 0dB variance
        h = (1/np.sqrt(2)) * (np.random.randn(1,N) + 1j * np.random.randn(1,N)) # Rayleigh channel

        # Rx
        y = (h*x) + (10**(-SNR[i]/20))*n

        # equalization
        y_hat = y / h

        # recevier - hard Decision
        y_ = (np.real(y_hat) > 0).reshape((N,))
        decoding = lambda x_: 1 if x_ else -1
        decode_y = np.array([decoding(tmp_y) for tmp_y in y_])
        #print(f'decode: {decode_y}')

        # counting the errors
        n_error.append(list(decode_y != x).count(True))

    return n_error

def plot_figure(SNR, N, num_cores):
    """
    SNR: Eb/No
    N: number of symbol
    """
    #############################################################################################################
    # Multiprocessing for empirical rayleigh channel

    splited_data = np.array_split(SNR, num_cores)
    n_error = parmap.map(empirical_rayleigh_channel_BER, splited_data, N, pm_pbar=True, pm_processes=num_cores)
    n_error = np.array([item for l in n_error for item in l])

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

    empirical_BER = n_error / N
    theory_AWGN_BER = 0.5 * special.erfc(np.sqrt(10**(SNR/10)))
    SNR_ = 10**(SNR/10)
    thoery_rayleigh_BER = 0.5 * ( 1 - np.sqrt(SNR_/(SNR_ + 1)) )

    # plot
    plt.plot(SNR,theory_AWGN_BER, '-.g', label='AWGN-Theory')
    plt.plot(SNR, empirical_BER, '-XC4', label='Rayleigh-Empirical')
    plt.plot(SNR, thoery_rayleigh_BER, '-1C5', label='Ralyleigh-Thoery')
    plt.semilogy(); plt.xlim(-3,35); plt.ylim(1e-5, 0.5); plt.grid(); plt.legend()
    plt.ylabel('Bit Error Rate'); plt.xlabel('SNR[dB]')
    plt.title("BER for BPSK in Rayleigh channel")
    plt.show()

if __name__ == '__main__':
    freeze_support()
    plot_figure(SNR, n, num_cores)


import matplotlib.pyplot as plt
from sympy import *
x = Symbol('x')

f=sin(2*x)/(x)

def minGradientSearch(f,cur_x):
	gamma = 0.01
	precision = 0.00001
	previous_step_size = 1
	max_iters = 10000
	iters = 0

	df = diff(f,x)
	error = []

	while (previous_step_size > precision) & (iters < max_iters):
	    prev_x = cur_x

	    # df/dx
	    cur_x -= gamma * df.evalf(subs={x:prev_x})

	    previous_step_size = abs(cur_x - prev_x)
	    iters+=1
	    error.append(previous_step_size)

	print( df.evalf(subs={x:cur_x}) )

	return cur_x,error

cur_x,error = minGradientSearch(f,.1)

print("Local minimum: " + str (cur_x) )

plt.plot(error)
plt.show()


##########################################################
# to turn off warnings                                   #
import warnings                                          #
warnings.filterwarnings('ignore')                        #
                                                         #
import os                                                #
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'                 #
                                                         #
import tensorflow.python.util.deprecation as deprecation #
deprecation._PRINT_DEPRECATION_WARNINGS = False          #
                                                         #
##########################################################

import numpy as np
import tensorflow as tf

#############
# example 1 #
x = tf.constant(3.0)

with tf.GradientTape() as tape:
    tape.watch(x) # If you want to differentiate about a constant tensor,
                  # you should use taple.watch() function
                  # to change the constant tensor like a variable tensor.
    y = (2 * x * x) + (3 * x) + 1

result = tape.gradient(y, x) # = dy/dx
print(result.numpy()) # 15.0

#############
# example 2 #
x = tf.Variable(3.0)

with tf.GradientTape() as tape:
    y = x * x

result = tape.gradient(y, x)
print(result.numpy()) # 6.0

#############
# example 3 #
def diff_func(x):
    x = tf.Variable(x)
    with tf.GradientTape() as tape:
        if x <= 0:
            y = 2 * x * x
        else:
            y = 3 * x * x
    result = tape.gradient(y, x)
    return result.numpy()

print(diff_func(-3.0)) # -12.0
print(diff_func( 0.0)) #   0.0
print(diff_func( 3.0)) #  18.0

#############
# example 4 #
x = tf.Variable(2.0)
y = tf.Variable(4.0)

with tf.GradientTape() as tape:
    f = 2 * x + 3 * y

result = tape.gradient(f, [x, y])
print(result[0].numpy(), result[1].numpy()) # 2.0 3.0


#!/usr/bin/env python3

# Fast Fourier Transform snippets
#
# Documentation:
# - Numpy implementation: http://docs.scipy.org/doc/numpy/reference/routines.fft.html
# - Scipy implementation: http://docs.scipy.org/doc/scipy/reference/fftpack.html

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

fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(14, 8))

# MAKE DATA ##########################################################

pattern = np.zeros((4, 4))
pattern[1:3,1:3] = 1

signal = np.tile(pattern, (2, 2))

ax1.imshow(signal, interpolation='nearest', cmap=cm.gray)
ax1.set_title("Original image")

# FOURIER TRANSFORM WITH NUMPY #######################################

# Do the fourier transform #############

transformed_signal = np.fft.fft2(signal)

ax2.imshow(abs(transformed_signal), interpolation='nearest', cmap=cm.gray)
ax2.set_title("Fourier coefficients before filtering")

#shifted_transformed_signal = np.fft.fftshift(transformed_signal)
#shifted_filtered_signal = np.fft.ifftshift(transformed_signal)

# Filter ###############################

max_value = np.max(abs(transformed_signal))
filtered_transformed_signal = transformed_signal * (abs(transformed_signal) > max_value*0.5)

ax3.imshow(abs(filtered_transformed_signal), interpolation='nearest', cmap=cm.gray)
ax3.set_title("Fourier coefficients after filtering")

# Do the reverse transform #############

filtered_signal = np.fft.ifft2(filtered_transformed_signal)

ax4.imshow(abs(filtered_signal), interpolation='nearest', cmap=cm.gray)
ax4.set_title("Filtered image")

# SAVE FILES ######################

#plt.savefig("fft2_basic_test.png")
#plt.savefig("fft2_basic_test.svg")
plt.savefig("fft2_basic_test.pdf")

# PLOT ############################

plt.show()


# -*- coding: utf8 -*-

import math
import pandas as pd
import numpy as np
import scipy.stats
from collections import defaultdict

def shannon_entropy(labels, base=2):
    """ Computes entropy of label distribution. """
    n_labels = len(labels)
    if n_labels <= 1:
        return 0

    value, counts = np.unique(labels, return_counts=True)
    probs = counts / n_labels
    n_classes = np.count_nonzero(probs)

    if n_classes <= 1:
        return 0

    ent = 0.

    # Compute entropy
    base = e if base is None else base
    for i in probs:
        ent -= i * math.log(i, base)

    return ent

# Input a pandas series
def scipy_entropy(data, base=2):
    # Calculate the probabilities.
    p_data = data.value_counts() / len(data)
    # Input probabilities to get the entropy.
    return scipy.stats.entropy(p_data, base=base)

def binary_entropy(nr_labels, nr_target_labels, base=2):
    p = nr_target_labels / nr_labels
    if p == 1.0 or p == 0.0:
        return 0.0
    return -p * math.log(p, base) - (1 - p) * math.log(1 - p, base)

def lzw_encode(symbols):
    """Compress a string to a list of output symbols."""

    # Build the dictionary.
    dictionary = {c:i for i, c in enumerate(set(symbols))}
    dict_size = len(dictionary)

    w = ""
    result = []
    seq_sizes = []
    for c in symbols:
        wc = w + c
        if wc in dictionary:
            w = wc
        else:
            result.append(dictionary[w])
            seq_sizes.append(len(w))
            # Add wc to the dictionary.
            dictionary[wc] = dict_size
            dict_size += 1
            w = c

    # Output the code for w.
    if w:
        seq_sizes.append(len(w))
        result.append(dictionary[w])

    return result, seq_sizes

def lzw_entropy(labels):
    _, seq_sizes = lzw_encode(labels)
    return (1 / (sum(seq_sizes) / len(seq_sizes))) * math.log(len(seq_sizes))

def compute_pi_fano(S, N):
    if S == 0.0 or N <= 1:
        return 1.0
    for p in np.arange(0.001, 1.000, 0.001):
        h = -p * math.log2(p) - (1 - p) * math.log2(1 - p)
        pi_fano = h + (1 - p) * math.log2(N - 1) - S
        if pi_fano <= 0.001:
            return p
    return 0.0