import matplotlib.pyplot as plt
import numpy as np

def feval(funcName, *args):
    return eval(funcName)(*args)

def RK4thOrder(func, yinit, x_range, h):
    m = len(yinit)
    n = int((x_range[-1] - x_range[0]) / h)

    x = x_range[0]
    y = yinit

    # Containers for solutions
    xsol = np.empty(0)
    xsol = np.append(xsol, x)

    ysol = np.empty(0)
    ysol = np.append(ysol, y)

    for i in range(n):
        k1 = feval(func, x, y)

        yp2 = y + k1 * (h / 2)

        k2 = feval(func, x + h / 2, yp2)

        yp3 = y + k2 * (h / 2)

        k3 = feval(func, x + h / 2, yp3)

        yp4 = y + k3 * h

        k4 = feval(func, x + h, yp4)

        for j in range(m):
            y[j] = y[j] + (h / 6) * (k1[j] + 2 * k2[j] + 2 * k3[j] + k4[j])

        x = x + h
        xsol = np.append(xsol, x)

        for r in range(len(y)):
            ysol = np.append(ysol, y[r])

    return [xsol, ysol]

#########################       INPUT GOES HERE

def myFunc(x, y):
    dy = np.zeros((len(y)))

    n = y[0]
    p = y[1]
    K = 100
    r = 0.6
    a = 0.04
    muy = 1.2

    dy[0] = r * n * (1 - n / K) - a * n * p
    dy[1] = -muy * p + a * n * p
    return dy

h = 0.01
t0 = 0.0
tn = 100

y01 = 70.0
y02 = 20.0

##########################       \INPUT GOES HERE

x = np.array([t0, tn])
yinit = np.array([y01, y02])

[ts, ys] = RK4thOrder('myFunc', yinit, x, h)

node = len(yinit)
ys1 = ys[0::node]
ys2 = ys[1::node]

##########################       PRINT RESULT

for i in range(len(ts)):
    print("t = ", ts[i])
    print("y1 = ", ys1[i], ";   y2 = ", ys2[i])

##########################       \PRINT RESULT

##########################       Plot y1, y2 with respect to t

plt.plot(ts, ys1, 'r')
plt.plot(ts, ys2, 'b')
plt.xlim(x[0], x[1])
plt.legend(["y(1)", "y(2)"], loc=2)
plt.xlabel('x', fontsize=17)
plt.ylabel('y', fontsize=17)
plt.tight_layout()
plt.show()

##########################       \Plot y1, y2 with respect to t

##########################       plot y1 - y2 as x - y

plt.plot(ys1, ys2, 'r')
plt.xlabel('con mồi', fontsize=17)
plt.ylabel('thú săn mồi', fontsize=17)
plt.show()

##########################       \plot y1 - y2

# Uncomment the following to print the figure:
#plt.savefig('Fig_ex7_RK4th.png', dpi=600)

Topic 5 - Solving Differential Equations/28.Runge_Kutta/R-K system of equation/RK4.py


import numpy as np
import random
import scipy

def generate_markov_seq(n_states, transition_matrix, len_seq, init_state=None):
    states = [k for k in range(n_states)]
    seq = []
    if init_state:
        x0 = init_state
    else:
        x0 = np.random.choice(states) #add initial probabilities
    x_prev = x0
    seq.append(x_prev)
    for i in range(len_seq):
        x_succ = np.where(np.random.multinomial(1, transition_matrix[x_prev, :], size=1) == 1)[1][0]
        seq.append(x_succ)
        x_prev = x_succ
    return seq

def generate_transtion_matrix(n_states):
    mat = []
    for k in range(n_states):
        row = np.random.random(n_states)
        row = row / np.sum(row)
        mat.append(list(row))
    return np.array(mat)

def generate_series(hidden_seq, params):
    T = len(hidden_seq)
    y = []
    for t in range(T):
        mu_step = params[hidden_seq[t]][0]
        sigma_step = params[hidden_seq[t]][1]
        y.append(np.random.normal(mu_step, sigma_step))
    return y

def generate_samples(n_sample, lengths_range, P, params, noise=0., init_state=None):
    Y = []
    for sample in range(n_sample):
        n_states = P.shape[0]
        T = np.random.randint(lengths_range[0], lengths_range[1])
        hidden_seq = generate_markov_seq(n_states, P, T, init_state) #hidden states sequence
        y = generate_series(hidden_seq, params) #time series following HMM model with hidden states and params
        y = np.array(y) + np.random.random(len(y)) * noise #adding noise to series
        Y.append(y)
    return Y


import numpy as np

def unit_range(arr, data_min=None, data_max=None, samples_in='row'):
    """
    Normalize the data to have unit range. Return the normalized data, and
    the min,  and max used.
    Data is assumed to be in row samples
    If data is arranged in column samples, use samples_in='col'

    Computes norm_arr = (arr - data_min) / (data_max - data_min)

    Returns:
    [normalized_data, data_min, data_max]
    """
    samplesIn = 1 if samples_in == 'col' else 0
    dimsIn = int(not samplesIn)
    nSamples = arr.shape[samplesIn]
    nDims = arr.shape[dimsIn]
    theshape = [1, 1]
    theshape[dimsIn] = nDims
    if data_max is None:
        data_max = arr.max(axis=samplesIn)
    if data_min is None:
        data_min = arr.min(axis=samplesIn)
    norma = (arr - data_min.reshape(theshape) ) / (data_max - data_min).reshape(theshape)
    return norma, data_min, data_max

def mean_zero(arr, mean_vector=None, samples_in='row'):
    """
    Normalize input data to have zero mean.  Return the normalized data and the
    mean which was used to normalize the data

    [normalized, meanvec] = mean_zero(data)

            or

    [normalize, meanvec] = mean_zero(data, provided_mean_vector)
    """
    samplesIn = 1 if samples_in == 'col' else 0
    dimsIn = int(not samplesIn)
    nSamples = arr.shape[samplesIn]
    nDims = arr.shape[dimsIn]
    theshape = [1, 1]
    theshape[dimsIn] = nDims
    if not mean_vector:
        mean_vector = arr.mean(axis=samplesIn).reshape(theshape)
    amean = arr - mean_vector
    return amean, mean_vector

def mean_zero_unit_variance(arr, mean_vector=None, std_vector=None, samples_in='row'):
    """
    Normalize input data to have zero mean and unit variance.
    Return the normalized data, the mean, and the calculated standard
    deviation which was used to normalize the data

    [normalized, meanvec, stddev] = mean_zero_unit_variance(data)
            or
    [normalized, meanvec, stddev] = mean_zero(data, mean_vector=provided_mean_vector)
       etc.
    """
    samplesIn = 1 if samples_in == 'col' else 0
    dimsIn = int(not samplesIn)
    nSamples = arr.shape[samplesIn]
    nDims = arr.shape[dimsIn]
    theshape = [1, 1]
    theshape[dimsIn] = nDims

    if not mean_vector:
        mean_vector = arr.mean(axis=samplesIn).reshape(theshape)
    if not std_vector:
        std_vector  = arr.std(axis=samplesIn).reshape(theshape)
    # If you have a row with absolutely no information, you will divide by zero. Hence...
    std_vector[std_vector < 1e-6] = 1
    norma = (arr - mean_vector) / std_vector
    return norma, mean_vector, std_vector

def labels_to_one_hot(labels):
    '''
    Given a 1-dimensional array-like of labels, convert to a one-hot representation
    '''
    #TODO - Data checks
    n_samples = len(labels)
    dlabels = {}
    ix = 0
    for label in labels:
        if label not in dlabels:
            dlabels[label] = ix
            ix += 1
    n_dims = len(dlabels)
    arr = np.zeros((n_samples, n_dims))
    for i in xrange(n_samples):
        arr[i, dlabels[labels[i]]] = 1.0
    return arr, dlabels


#!/usr/bin/env python

from sympy import Function, lambdify, diff, hessian, pprint
from sympy.abc import x, y

''' Hessian matrix
    Compute Hessian matrix
    for a function f wrt parameters in varlist
    which may be given as a sequence or a row/column vector.
    A list of constraints may optionally be given.

    References:  https://en.wikipedia.org/wiki/Hessian_matrix
        http://people.duke.edu/~ccc14/sta-663-2016/12_MultivariateOptimizationAlgorithms.html
        https://www.youtube.com/watch?v=tWh1S8oCglA
        https://www.youtube.com/watch?v=H4rwPpfkPHw
'''

def hessian_inverse(function):
    '''  Hessian: init and set  '''
    h = (hessian(function, (x, y))).inv()
    return (lambdify([x,y], h))

if __name__ == "__main__" :

    # f = 2*x - y + 2*x*y - x**2 - y**2
    f = x**2 * y**3

    h = hessian_inverse(f)
    print(h(5, 2))

    # pprint((hessian(f, (x, y))).inv())


#! /usr/bin/env python
"""
@author : Luc ardaillon
created : 17/01/2019
last revision : 10/04/2019

conversion from the ground truth f0 value to the target vector of pitch classes to be predicted by the models (for training)
"""

import numpy as np

def freq2cents(f0, f_ref = 10.):
    '''
    Convert a given frequency into its corresponding cents value, according to given reference frequency f_ref
    :param f0: f0 value (in Hz)
    :param f_ref: reference frequency for conversion to cents (in Hz)
    :return: value in cents
    '''
    c = 1200 * np.log2(f0/f_ref)
    return c

def cents2freq(cents, f_ref = 10.):
    '''
    conversion from cents value to f0 in Hz

    :param cents: pitch value in cents
    :param fref: reference frequency used for conversion
    :return: f0 value
    '''
    f0 = f_ref * 2 ** (cents / 1200)
    return f0

def f0_to_target_vector(f0, vecSize = 486, fmin = 30., fmax = 1000., returnFreqs = False):
    '''
    convert from target f0 value to target vector of vecSize pitch classes (corresponding to the values in cents_mapping) that is used as output by the CREPE model
    Unlike the original CREPE model, the first class corresponds to a frequency of 0 (for unvoiced segments).
    If the frequency is 0, all values are 0, except for the 1st value that is = 1.
    For all other cases, the values are gaussian blurred around the target_pitch class, with a maximum value of 1
    :param f0: target f0 value
    :return: target vector of vecSize pitch classes (regularly spaced in cents, from fmin to fmax)
    '''

    fmin_cents = freq2cents(fmin)
    fmax_cents = freq2cents(fmax)
    mapping_cents = np.linspace(fmin_cents, fmax_cents, vecSize)

    target_vec = np.zeros(vecSize)

    #get the idx corresponding to the closest pitch
    f0_cents = freq2cents(f0)

    #gaussian-blur the vector auround the taget pitch idx as stated in the paper :
    sigma = 25
    for i in np.arange(vecSize):
        target_vec[i] = np.exp(-((mapping_cents[i] - f0_cents)**2)/(2*(sigma**2)))

    if(returnFreqs):
        return target_vec, mapping_cents
    else:
        return target_vec

def f0_to_target_vector_crepe(f0):
    '''
    convert from target f0 value to target vector of 360 pitch classes (corresponding to the values in cents_mapping) that is used as output by the CREPE model
    Unlike the original CREPE model, the first class corresponds to a frequency of 0 (for unvoiced segments).
    If the frequency is 0, all values are 0, except for the 1st value that is = 1.
    For all other cases, the values are gaussian blurred around the target_pitch class, with a maximum value of 1
    :param f0: target f0 value
    :return: target vector of 360 pitch classes (1st class is for f0=0. All other correspond to frequencies spaced by 20 cents
    '''

    vecSize = 360
    mapping = (np.linspace(0, 7180, vecSize) + 1997.3794084376191)

    target_vec = np.zeros(vecSize)

    #get the idx corresponding to the closest pitch
    f0_cents = freq2cents(f0)
    sigma = 25

    #gaussian-blur the vector auround the taget pitch idx as stated in the paper :
    for i in np.arange(vecSize):
        target_vec[i] = np.exp(-((mapping[i] - f0_cents)**2)/(2*(sigma**2)))

    return target_vec

if __name__ == '__main__':

    f0 = 100.

    CREPE = False

    if(CREPE):
        tv = f0_to_target_vector_crepe(f0)
    else:
        tv = f0_to_target_vector(f0)

    from matplotlib import pyplot as plt
    plt.figure()
    plt.plot(tv)
    plt.show()


"""ЛР 1.4, Ларькин Владимир, М8О-303Б-18"""

import numpy as np
import fire  # CLI
import matplotlib.pyplot as plt

from utilities import parse_matrix  # парсинг матрицы из файла

def max_el_indices(matrix):
    i_max = 0
    j_max = 1
    a_max = matrix[i_max, j_max]
    for i in range(matrix.shape[0]):
        for j in range(i + 1, matrix.shape[0]):
            if abs(matrix[i, j]) > a_max:
                a_max = abs(matrix[i, j])
                i_max, j_max = i, j
    return i_max, j_max

def calculate_phi(a_ii, a_ij, a_jj):
    return np.pi / 4 if a_ii == a_jj else 0.5 * np.arctan(2 * a_ij / (a_ii - a_jj))

def t(matrix: np.ndarray) -> float:
    """Корень из суммы квадратов недиагональных элементов"""
    return np.sqrt((np.triu(matrix, k=1) ** 2).sum() + (np.tril(matrix, k=-1) ** 2).sum())

def rotation_eig(matrix: np.ndarray, eps: float = .01, benchmark=False) -> (np.ndarray, np.ndarray):
    """Метод вращений Якоби для нахождения собственных значений и собственных векторов симметрической матрицы

    :param matrix: матрица
    :param eps: точность
    :param benchmark: возвращать ли количество итераций и погрешность
    :return: собственные значения и вектора, а если benchmark=True, то ещё количество итераций и погрешность
    """
    a_i = np.copy(matrix)
    n = a_i.shape[0]
    eigenvectors = np.identity(n)

    c = 0
    while True:
        u = np.identity(n)
        i, j = max_el_indices(a_i)
        phi = calculate_phi(a_i[i, i], a_i[i, j], a_i[j, j])
        u[i, j] = -np.sin(phi)
        u[j, i] = np.sin(phi)
        u[i, i] = u[j, j] = np.cos(phi)

        a_i = u.T @ a_i @ u

        eigenvectors = eigenvectors @ u

        if t(a_i) < eps:
            break

        c += 1

    eigenvalues = np.diag(a_i)

    return (eigenvalues, eigenvectors) if not benchmark else (eigenvalues, eigenvectors, c, t(a_i))

def main(src, test=False, eps=0.01):
    """Нахождение собственных значений и собственных векторов симметрической матрицы методом вращений Якоби

    :param src: путь к текстовому файлу с матрицей
    :param test: флаг, запускающий тестирование
    :param eps: точность вычисления
    """

    np.random.seed(42)

    # чтение файла
    with open(src, "r") as file:
        s = file.readlines()

    matrix = parse_matrix(s)

    print(f"Матрица:\n{matrix}")
    print(f"\neps={eps}")

    print("\nnumpy.linalg.eig:", *np.linalg.eig(matrix), sep="\n")
    print("\nrotation_eig:", *rotation_eig(matrix, eps), sep="\n")

    if test:
        powers = np.arange(0, 15, .1)  # порядки эпсилонов (eps = 10 ** -p)
        counts = []
        errors = []
        for p in powers:
            temp = rotation_eig(matrix, 10 ** -p, benchmark=True)
            counts.append(temp[2])
            errors.append(temp[3])

        plt.plot(counts, errors, "-*")
        plt.title("Зависимость погрешности вычислений от числа итераций")
        plt.xlabel("Число итераций")
        plt.ylabel("Погрешность (лог. шкала)")
        plt.grid(True)
        plt.yticks(np.arange(0, max(errors) + .05, .05))
        plt.yscale("log")
        plt.savefig("benchmark.jpg", dpi=300)
        plt.show()

if __name__ == "__main__":
    fire.Fire(main)


import numpy as np
import matplotlib.pyplot as plt

def tanh(x):
    return ((np.exp(x))-(np.exp(-x)))/((np.exp(x))+(np.exp(-x)))

def generate_graph(x, save_graph=False):
    plt.plot(x,tanh(x))
    if save_graph:
        plt.savefig("tanh.jpg")
    else:
        plt.show()

def main():
    print("Generating graph")
    save_graph = False # save the graph (true) or show (false)
    generate_graph(np.linspace(-10,10,100),save_graph=save_graph)

if __name__ == '__main__':
    main()


"""
Utility functions for curvature computations

Author: Santiago Cadena
Date:   11.19
email:  santiago.cadena@uni-tuebingen.de
"""
import numpy as np
from numpy.linalg import norm

def consecutive_difference(x):
    '''
    Computes the difference of consecutive elements along the first axis
    '''
    return np.diff(x, n=1, axis=0)

def normalize_arrays(x):
    '''
    Normalize each array along the first axis
    '''
    return np.array([a / norm(a.flatten()) for a in x])

def compute_curvature(x, deg=True):
    """
    Compute curvature by computing arccosine of consecutive normalized
    difference vectors [Hennaff et. al 2019]

    Args:
        x    : array with at least two dims.
        deg  : bool  True (default) for curvature output in degrees. Otherwise in rads.
    Returns:
        curvature : array with (1xM) with the curvature trajectory
    """

    x_diff   = consecutive_difference(x)
    x_diff_n = normalize_arrays(x_diff)

    # Flatten arrays along the first axis
    T      = x_diff_n.shape[0]
    x_flat = x_diff_n.reshape(T, -1)

    # shifted arrays
    x0 = x_flat[:-1,]
    x1 = x_flat[1:,]

    cosine    = np.diag(np.dot(x0, x1.T)) # dot product consecutive vectors
    cosine    = [c if c <= 1 else 1 for c in cosine] # sanity check to clip values at 1

    # curvature computation
    curvature = np.arccos(cosine)

    if deg:
        curvature = np.degrees(curvature)

    return curvature


import numpy
import itertools

def get_sieve(size=1000):
    arr = numpy.full(size, True, dtype=bool)
    arr[0] = arr[1] = False
    # Eliminate even numbers (except 2)
    for i in xrange(4, size, 2):
        arr[i] = False

    for i in xrange(2, size):
        if arr[i]:  # is prime
            for j in xrange(i * i, size, i):
                arr[j] = False

    return arr

if __name__ == '__main__':
    # https://brilliant.org/weekly-problems/2017-12-25/intermediate/?p=3
    # Are there any three distinct digits such that all six permutations are prime numbers?
    size = 1000
    sieve = get_sieve(size=size)
    primes = [x[0] for x in zip(range(size), sieve) if x[1]]
    print "primes:\n%s" % (primes,)
    digits = (1, 3, 7, 9)  # no even digits and 5
    three_digit_permutation_primes_exist = False
    for abc in itertools.combinations(digits, 3):
        is_prime = True
        for a1, b1, c1 in itertools.permutations(abc, 3):
            x = a1 * 100 + b1 * 10 + c1
            if not sieve[x]:
                print "not prime %s, counterexample: %s" % (abc, x)
                is_prime = False
                break
        if is_prime:
            print "prime: %s" % (abc,)
            three_digit_permutation_primes_exist = True
    if three_digit_permutation_primes_exist:
        print "Yes"
    else:
        print "No"
    # For combination with repetition the answer would be yes with:
    # (1, 1, 3), (1, 9, 9) and (3, 3, 7)


import numpy as np

def getminor(mat,temp,n,p,q):
  i,j = 0,0
  for row in range(n):
    for col in range(n):
      if (row!=p and col!=q):
        temp[i][j] = mat[row][col]
        j += 1
        if (j==n-1):
          j = 0
          i = i+1
  return temp

def determinant(mat,n):
  D = 0
  sign = 1
  temp = [[0,0,0,0],
          [0,0,0,0],
          [0,0,0,0],
          [0,0,0,0]]
  if (n==1):
    return mat[0][0]
  for f in range(n):
    temp = getminor(mat,temp,n,0,f)
    D += sign*mat[0][f]*determinant(temp,n-1)
    sign = -sign
  return D

N = 4
matrix = [[1, 0, 2, -1],
          [3, 0, 0, 5 ],
          [2, 1, 4, -3],
          [1, 0, 5, 0 ]]
matrix = np.array(matrix)
print("Determinant of the matrix is : {}".format(determinant(matrix, N)))


import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
plt.rcParams['figure.figsize']= (20.0,10.0)

#reading data
data=pd.read_csv("headbrain.csv")
print(data.shape)
data.head()

#Callecting X & Y
X =data["Head Size(cm^3)"].values
Y =data["Brain Weight(grams)"].values

# Mean X & Y
MeanX = np.mean(X)
MeanY = np.mean(Y)

#total number of values
n= len(X)

# using the formula to calculate B1 and B0
numer = 0
denom  = 0
for i in range(n):
    numer +=(X[i]-MeanX)*(Y[i]-MeanY)
    denom +=(X[i]-MeanX)**2
B1 = numer /denom
B0 = MeanY - (B1 * MeanX)

#print Cofficients
print(B1, B0)

#plotting Values and Regression Line
MaxX= np.max(X)+100
MinX=np.min(X)-100

#Calculating Line values X and Y
x = np.linspace(MinX,MaxX,1000)
y =B0+B1*x

#ploting line
plt.plot(x,y,color='#58b970',label="Regression")
#ploting scatter points
plt.scatter(X,Y,color="#ef5423", label="Scatter Plot")

plt.xlabel("Head Size in Cm3")
plt.ylabel("Brain Weght in grams")
plt.legend()
plt.show()

ss_t = 0
ss_r = 0
for i in range(n):
    y_pred = B0 +B1 * X[i]
    ss_t += (Y[i] - MeanY) **2
    ss_r += (Y[i] - y_pred) **2
r2 = 1- (ss_r/ss_t)
print(r2)

from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error

#Cannot use Rank 1 matrix in scikit learn
X= X.reshape((n,1))
#creating Model
reg=LinearRegression()
#fitting training data
reg = reg.fit(X,Y)
#Y prediction
y_pred = reg.predict(X)
#calculating R2 Score
R2_score=reg.score(X,Y)
print(R2_score)


import numpy as np
# from sklearn.metrics.pairwise import euclidean_distances

D = 2
S = 1000
K = 3
max_iterations = 10

# 1000x2
data = np.random.rand(S, D)

# 3x2
centroids = np.random.rand(K, D)

def euclidean_distances(data, centroids):
    return np.array([np.sum((d - centroids)**2, 1)
        for d in data])

def calc_error(data, centroids, labels):
    total = 0
    for c in range(centroids.shape[0]):
        assigned = data[np.where(labels == c)]
        if assigned.shape[0] > 0:
            total += np.sum(euclidean_distances(assigned, centroids[c].reshape((1, D))))
    return total

# # 1000x3
for iterations in range(max_iterations):
    # result = np.dot(data, centroids.T)
    distances = euclidean_distances(data, centroids)

    # distances is: [SxK], per S_i find min K
    labels = np.argmin(distances, 1)

    print calc_error(data, centroids, labels)
    print centroids

    for c in range(centroids.shape[0]):
        assigned = data[np.where(labels == c)]
        if assigned.shape[0] > 0:
            centroids[c] = np.mean(assigned)


"""
Uses the Python interface to PETSc (petsc4py) to solve the transient 1D heat
diffusion with Dirichlet Boundary Conditions. This is a super simple example
showcase of the linear iterative solvers PETSc has to offer. It runs
sequentially without MPI.

Consider the 1D heat equation defining how a temperature is distributed e.g.
over a one-dimensional rod.

    ∂u/∂t = ∂²u/∂x²

with Dirichlet Boundary Conditions at both ends of a unit domain

    u(t, x=0) = 0 = u(t, x=1)

and the following initial condition

         ┌────────────────────────────────────────┐
         │⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⠒⠒⠒⠒⠒⠒⢲⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ │ y1
         │⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⠀│
         │⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⠀│
         │⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⠀│
         │⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⠀│
         │⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⠀│
         │⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⠀│
         │⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⠀│
         │⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⠀│
         │⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⠀│
         │⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⠀│
         │⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⠀│
         │⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡸⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⠀│
         │⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⠀│
         │⢼⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠧⠤⠤⠤⠤⠤⠤⠼⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤│
         └────────────────────────────────────────┘
          0.0                                   1.0

-----

Solution strategy (BTCS):

Denote with û the solution at the next point in time. Then use
Finite Difference discretization on a uniform mesh. Discretize
the time derivative by implicit Euler and the second spatial
derivative by central differences.

    (û[i] − u[i])/Δt = (û[i−1] − 2 û[i] + û[i+1])/Δx²

with the conditions on the Dirichlet Boundary points

    û[0] = 0.0

    û[-1] = 0.0

Move all points at next time step to the lhs and all terms at
previous time step to rhs.

    û[i] + Δt/Δx² (− û[i−1] + 2 û[i] − û[i+1]) = u[i]

This can be expressed as a linear system of equations

    A x = b

with A being

    +-----                                                ------+
    |   1.0                                                     |
    | −Δt/Δx²  1 + 2Δt/Δx²  −Δt/Δx²                             |
    |           −Δt/Δx²  1 + 2Δt/Δx²  −Δt/Δx²
A = |                       −Δt/Δx²  1 + 2Δt/Δx²  −Δt/Δx²
    |                           ⋱         ⋱         ⋱
    |                             ⋱         ⋱         ⋱
    |                            −Δt/Δx²  1 + 2Δt/Δx²  −Δt/Δx²  |
    |                                                    1.0    |
    +-----                                                ------+

A therefore has the following sparsity structure

    +---------------+
    |*              |
    |* * *          |
    |  * * *        |
A = |    * * *      |
    |      * * *    |
    |        * * *  |
    |          * * *|
    |              *|
    +---------------+

which is not fully tri-diagonal, because we did not eliminate the
Dirichlet DoF. Hence, it is also not symmetric.

-----

The PETSc linear solver can be set at runtime.

Use this command to monitor the solution process:

    python petsc_python_simple_heat_diffusion.py -ksp_monitor

Use this flag to swap the linear solver

    python petsc_python_simple_heat_diffusion.py -ksp_type [SOLVER_NAME]

"""
import petsc4py
import sys
import numpy as np
import matplotlib.pyplot as plt

petsc4py.init(sys.argv)

from petsc4py import PETSc

N_POINTS = 1001
TIME_STEP_LENGTH = 0.005
N_TIME_STEPS = 10

def main():
    element_length = 1.0 / (N_POINTS - 1)
    mesh = np.linspace(0.0, 1.0, N_POINTS)

    # Create a new sparse PETSc matrix, fill it and then assemble it
    A = PETSc.Mat().createAIJ([N_POINTS, N_POINTS])
    A.setUp()

    diagonal_entry = 1.0 + 2.0 * TIME_STEP_LENGTH / element_length**2
    off_diagonal_entry = - 1.0 * TIME_STEP_LENGTH / element_length**2

    A.setValue(0, 0, 1.0)
    A.setValue(N_POINTS-1, N_POINTS-1, 1.0)

    for i in range(1, N_POINTS - 1):
        A.setValue(i, i, diagonal_entry)
        A.setValue(i, i-1, off_diagonal_entry)
        A.setValue(i, i+1, off_diagonal_entry)

    A.assemble()

    # Define the initial condition
    initial_condition = np.where(
        (mesh > 0.3) & (mesh < 0.5),
        1.0,
        0.0,
    )

    # Assemble the initial rhs to the linear system
    b = PETSc.Vec().createSeq(N_POINTS)
    b.setArray(initial_condition)
    b.setValue(0, 0.0)
    b.setValue(N_POINTS-1, 0.0)

    # Allocate a PETSc vector storing the solution to the linear system
    x = PETSc.Vec().createSeq(N_POINTS)

    # Instantiate a linear solver: Krylow subspace linear iterative solver
    ksp = PETSc.KSP().create()
    ksp.setOperators(A)
    ksp.setFromOptions()

    chosen_solver = ksp.getType()
    print(f"Solving with {chosen_solver:}")

    plt.plot(mesh, initial_condition)
    for iter in range(N_TIME_STEPS):
        ksp.solve(b, x)

        # Re-assemble the rhs to move forward in time
        current_solution = x.getArray()
        b.setArray(current_solution)
        b.setValue(0, 0.0)
        b.setValue(N_POINTS - 1, 0.0)

        # Visualize
        plt.plot(mesh, current_solution)
        plt.draw()
        plt.pause(0.5)

    plt.show()

if __name__ == "__main__":
    main()


import numpy as np
from scipy.special import gamma
import pandas as pd
from sklearn.metrics import mutual_info_score
from typing import Union

# def n_ball_volume(dim, norm):
#     if norm == 1:
#         return 2**dim/np.math.factorial(dim)
#     elif norm == 2:
#         return np.pi**(dim/2.) / (gamma(dim/2. + 1))
#     elif norm == float('inf') or norm == "inf":
#         return 2 ** dim

def n_ball_volume(dim, norm_p):
    if norm_p == 1:
        from scipy.special import factorial
        return 2.**dim / factorial(dim)
    elif norm_p == 2:
        return (np.sqrt(np.pi))**dim / gamma(dim/2 + 1)
    elif norm_p == float('inf') or norm_p == "inf":
        return 2 ** dim
    elif 0 < norm_p < float('inf'):
        return (2 * gamma(1 / norm_p + 1))**dim / gamma(dim/norm_p + 1)

def n_sphere_area(dim, norm):
    if norm == 1:
        return 2 ** dim / np.math.factorial(dim - 1)
    elif norm == 2:
        return 2 * np.pi ** ((dim + 1) / 2.) / (gamma((dim + 1) / 2.))
    elif norm == float('inf') or norm == "inf":
        return dim * 2 ** dim

def nd_rand_init(*tuples_lo_hi):
    return np.random.uniform(low=[t[0] for t in tuples_lo_hi], high=[t[1] for t in tuples_lo_hi])

def make_uniform_kernel(dim, norm_p):
    return lambda u: 1./n_ball_volume(dim, norm_p) if u <= 1 else 0.

def mutual_information(x, y, size_orig, n_bins="smooth") -> float:
    assert(x.size > 0 and y.size > 0)
    assert(n_bins in [None, 'smooth'] or isinstance(n_bins, int))
    if n_bins is None:
        n_bins = int(np.floor(np.sqrt(size_orig / 5)))

    elif n_bins is 'smooth':
        n_bins = int(np.floor(np.sqrt(size_orig / 3)))

    c_xy = np.histogram2d(x, y, n_bins)[0]
    return mutual_info_score(None, None, contingency=c_xy)

def lagged_mi(x, lag=1, *args, **kwargs) -> float:
    assert(lag >= 0)
    if lag > 0:
        return mutual_information(x[:-lag], x[lag:], x.shape[0], *args, **kwargs)
    else:
        return mutual_information(x, x, x.shape[0], *args, **kwargs)

def _lstsqr_design_matrix(x, degree=1):
    if degree > 1:
        raise NotImplementedError()
    return np.concatenate([x[:,None], np.ones((x.shape[0], 1))], axis=1)

def lstsqr(X, y):
    Xt = np.transpose(X)
    return np.linalg.pinv(Xt.dot(X)).dot(Xt).dot(y)

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


import numpy as np
import pandas as pd
from scipy.spatial import distance

def find_cluster(data_frame, data_frame_cluster):
    cluster = {}
    for i, center in enumerate(data_frame_cluster.values):
        cluster[i] = []

    for j, point in enumerate(data_frame.values):
        euclDist = float('inf')
        euclCenter = 0
        for i, center in enumerate(data_frame_cluster.values):
            dist = distance.euclidean(point, center)
            if dist < euclDist:
                euclDist = dist
                euclCenter = i

        # cluster[euclCenter] = []
        if cluster[euclCenter]:
            cluster[euclCenter].append(point)
        else:
            cluster[euclCenter] = [point]
    # print(cluster)
    return cluster

def mykmeans(X, k):
    # DONE create k random centroid from dataset.
    # TODO count euclidean distance from each centroid.
    # TODO we find the new centroid by taking the average of all the points assigned to that cluster.
    # TODO we repeat step 2 and 3 until none of the cluster assignments change. That means until our clusters remain stable, we repeat the algorithm
    try:
        data_frame = pd.DataFrame(data=X)
        # data_frame = data_frame
        data_frame_cluster = data_frame.sample(n=k)
        # print(data_frame_cluster)

        prev_centers = []
        while True:
            # Group data in clusters
            cluster = find_cluster(data_frame, data_frame_cluster)

            # Calculate new centroid
            centers = []
            for clusterKey, clusterValue in cluster.items():
                df = pd.DataFrame(clusterValue)
                center = []
                for column in df:
                    center.append(df[column].mean())
                centers.append(center)

            # Breaking condition, if prev centers and current centers are same
            if prev_centers == centers:
                break
            data_frame_cluster = pd.DataFrame(centers)
            prev_centers = centers
        print("For k = " + str(k) + " centers are: ")
        print(centers)
    except Exception as e:
        print(e)


"""
## =========================================================================== ##
MIT License
Copyright (c) 2020 Roman Parak
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
## =========================================================================== ##
Author   : Roman Parak
Email    : Roman.Parak@outlook.com
Github   : https://github.com/rparak
File Name: bezier_curve_example.py
## =========================================================================== ##
"""

# System (Default Lib.)
import sys
# Numpy (Array computing Lib.) [pip3 install numpy]
import numpy as np
# Mtaplotlib (Visualization Lib.) [pip3 install matplotlib]
import matplotlib.pyplot as plt

class bezier_ctrl(object):
    """
    Description:
        A Bézier curve is a parametric curve used in computer graphics and related fields.

        The class shows several types of Bézier curves (Linear, Quadratic, Cubic).
    """
    def __init__(self, p_0, p_1, p_2, p_3, step):
        # << PUBLIC >> #
        # Time t ∈ [0, 1]
        self.t = np.linspace(0.0, 1.0, step)
        # Points
        self.p = [p_0, p_1, p_2, p_3]
        # Remove empty (None) parts of the array.
        self.p = [x for x in self.p if x is not None]
        # << PRIVATE >> #
        # Display (Plot) variable.
        self.__plt = plt

    @staticmethod
    def __linear_curve(p_0, p_1, t):
        """
        Description:
            Given two control points p_{0} and p_{1} we define the linear Bezier curve to be the curve parametrized by:

            p(t) = (1 - t)*p_{0} + t*p_{1}, t ∈ [0, 1]

        Args:
            (1 - 2) p_0, p_0 [Float Array]: Multiple points to create a curve.
            (3) t [Float Array]: Time variable.
        Returns:
            (1 - 2) x, y [Float Array]: Results of curve values.

        Examples:
            self.__linear_curve([1.0, 1.0], [2.0, 2.0])
        """

        x = (1 - t) * p_0[0] + t * p_1[0]
        y = (1 - t) * p_0[1] + t * p_1[1]

        return x, y

    @staticmethod
    def __quadratic_curve(p_0, p_1, p_2, t):
        """
        Description:
            Given three control points p_{0}, p_{1} and p_{2} we define the quadratic Bezier curve (degree 2 Bezier curve)
            to be the curve parametrized by:

            p(t) = ((1 - t)^2)*p_{0} + 2*t*(1 - t)*p_{1} + (t^2)*p_{2}, t ∈ [0, 1]

        Args:
            (1 - 2) p_0, p_0, p_2 [Float Array]: Multiple points to create a curve.
            (3) t [Float Array]: Time variable.
        Returns:
            (1 - 2) x, y [Float Array]: Results of curve values.

        Examples:
            self.__quadratic_curve([1.0, 1.0], [2.0, 2.0], [3.0, 2.0])
        """

        x = (1 - t)**2 * p_0[0] + 2 * t * (1 - t) * p_1[0] + t**2 * p_2[0]
        y = (1 - t)**2 * p_0[1] + 2 * t * (1 - t) * p_1[1] + t**2 * p_2[1]

        return x, y

    @staticmethod
    def __cubic_curve(p_0, p_1, p_2, p_3, t):
        """
        Description:
            Given four control points p_{0}, p_{1}, p_{2} and p_{3} we define the cubic Bezier curve (degree 3 Bezier curve) to
            be the curve parametrized by:

            p(t) = ((1 - t)^3)*p_{0} + 3*t*((1 - t)^2)*p_{1} + (3*t^2)*(1 - t)*p_{2} + (t^3) * p_{3}, t ∈ [0, 1]

        Args:
            (1 - 2) p_0, p_0, p_2 [Float Array]: Multiple points to create a curve.
            (3) t [Float Array]: Time variable.
        Returns:
            (1 - 2) parameter{1}, parameter{2} [Float Array]: Results of curve values.

        Examples:
            self.__cubic_curve([1.0, 1.0], [2.0, 2.0], [3.0, 2.0], [4.0, 1.0])
        """

        x = ((1 - t)**3) * (p_0[0]) + (3 * t * (1 - t)**2) * (p_1[0]) + 3 * (t**2) * (1 - t) * p_2[0] + (t**3) * p_3[0]
        y = ((1 - t)**3) * (p_0[1]) + (3 * t * (1 - t)**2) * (p_1[1]) + 3 * (t**2) * (1 - t) * p_2[1] + (t**3) * p_3[1]

        return x, y

    def __switch_dCtrl(self, s_index):
        """
        Description:
            Function to obtain a string with the number of points used in the calculation of the curve. (Figure Legend -> Label Name)

        Args:
            (1) s_index [INT]: Number of points for calculation.
        Returns:
            (1) param 1 [String]: The resulting string for the label.
        """

        # Switch Variable
        switch_var={
                2: r'Points: $p_{0}, p_{1}$',
                3: r'Points: $p_{0}, p_{1}, p_{2}$',
                4: r'Points: $p_{0}, p_{1}, p_{2}, p_{3}$',
        }

        # Return Result (Get the string with number of points for the Legend Label)
        return switch_var.get(s_index, "Wrong Input!")

    def __two_points(self):
        """
        Description:
            Function to create a multiple linear Bézier curve from two points.
        """

        x, y = self.__linear_curve(self.p[0], self.p[1], self.t)

        # Display the Linear Bézier Curve p(t) -> x, y
        self.__plt.plot(x, y, 'r--', label=r'Linear Bezier Curve: [$p_{0}$, $p_{1}$]' , linewidth=2.5)

    def __three_points(self):
        """
        Description:
            Function to create multiple linear Bézier curves and a quadratic Bézier curve from three points.
        """

        for i in range(len(self.p) - 1):
            x, y = self.__linear_curve(self.p[i], self.p[i + 1], self.t)

            if i == (len(self.p) - 1) - 1:
                self.__plt.plot(x, y, 'r--', label=r'Linear Bezier Curve: [$p_{0}$, $p_{1}$]; [$p_{1}$, $p_{2}$]' , linewidth=2.5)
            else:
                self.__plt.plot(x, y, 'r--', linewidth=2.5)

        x, y = self.__quadratic_curve(self.p[0], self.p[1], self.p[2], self.t)

        self.__plt.plot(x, y, 'g--', label=r'Quadratic Bezier Curve: [$p_{0}$, $p_{1}$, $p_{2}$]', linewidth=2.5)

    def __four_points(self):
        """
        Description:
            Function to create multiple linear / quadratic Bézier curves and a cubic four-point Bézier curve.
        """

        for i in range(len(self.p) - 1):
            x, y = self.__linear_curve(self.p[i], self.p[i + 1], self.t)

            if i == (len(self.p) - 1) - 1:
                self.__plt.plot(x, y, 'r--', label=r'Linear Bezier Curve: [$p_{0}$, $p_{1}$]; [$p_{1}$, $p_{2}$]; [$p_{2}$, $p_{3}$]' , linewidth=2.5)
            else:
                self.__plt.plot(x, y, 'r--', linewidth=2.5)

        for i in range(len(self.p) - 2):
            x, y = self.__quadratic_curve(self.p[i], self.p[i + 1], self.p[i + 2], self.t)

            if i == (len(self.p) - 2) - 1:
                self.__plt.plot(x, y, 'g--', label=r'Quadratic Bezier Curve: [$p_{0}$, $p_{1}$, $p_{2}$]; [$p_{1}$, $p_{2}$, $p_{3}$]', linewidth=2.5)
            else:
                self.__plt.plot(x, y, 'g--', linewidth=2.5)

        x, y = self.__cubic_curve(self.p[0], self.p[1], self.p[2], self.p[3], self.t)

        self.__plt.plot(x, y, 'b--', label=r'Cubic Bezier Curve: [$p_{0}$, $p_{1}$, $p_{2}$, $p_{3}$]', linewidth=2.5)

    def __display_aux_result(self):
        """
        Description:
            Function for displaying points and text labeling.
        """

        for i in range(len(self.p)):
            self.__plt.text(self.p[i][0] + 0.01, self.p[i][1] + 0.01, '$p_{' + str(i) + '}$ = [' + str(self.p[i][0]) + ', ' + str(self.p[i][1]) + ']', fontsize=20)

            if i != len(self.p) - 1:
                self.__plt.plot(self.p[i][0], self.p[i][1], marker = 'o', ms = 15, mfc = [1,1,1], markeredgecolor = [0,0,0], mew = 5)
            else:
                self.__plt.plot(self.p[i][0], self.p[i][1], label=self.__switch_dCtrl(len(self.p)), marker = 'o', ms = 15, mfc = [1,1,1], markeredgecolor = [0,0,0], mew = 5)

    def display_result(self):
        """
        Description:
            Function for calculating and displaying the results of Bézier curves.
        """

        try:
            assert len(self.p) > 1

            # Select a calculation method based on the number of points in the array (p).
            if len(self.p) == 2:
                self.__two_points()
            elif len(self.p) == 3:
                self.__three_points()
            elif len(self.p) == 4:
                self.__four_points()

            self.__display_aux_result()

            # Set additional features for successful display of the Bézier curves.
            self.__plt.grid()
            self.__plt.xlabel('x axis [Unit]', fontsize = 20, fontweight ='normal')
            self.__plt.ylabel('y axis [Unit]', fontsize = 20, fontweight ='normal')
            self.__plt.title('Bezier Curve', fontsize = 50, fontweight ='normal')
            self.__plt.legend(loc=0,fontsize=20)

            # Display a figure. Wait for the user to close the window.
            self.__plt.show()

        except AssertionError as error:
            print('[INFO] Insufficient number of entry points.')
            print('[INFO] The minimum number of entry points is 2.')

def main():
    # Initialization of the Class (Control Manipulator)
    # Input:
    #   (1 - 4) Points [Float Array]
    #   (5) Time Step  [INT]
    # Example:
    #   x = bezier_ctrl([1.0, 1.0], [1.25, 2.0], None, None, 100)

    # Try the calculation of the Bézier curve:
    # Select one of these options: 2 - Linear Curve, 3 - Quadratic Curve, 4 - Cubic Curve
    test = 4

    if test == 2:
        # Linear Curve
        bezier = bezier_ctrl([1.0, 1.0], [1.25, 2.0], None, None, 100)
    elif test == 3:
        # Quadratic Curve
        bezier = bezier_ctrl([1.0, 1.0], [1.25, 2.0], [1.75, 2.0], None, 100)
    elif test == 4:
        # Cubic Curve
        bezier = bezier_ctrl([1.0, 1.0], [1.25, 2.0], [1.75, 2.0], [2.0, 1.0], 100)

    # Display the result of the calculation -> figure with the resulting Bézier curves
    bezier.display_result()

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


import numpy as np

def lsq(X, y):
    """
    Least squares linear regression
    :param X: Input data matrix
    :param y: Target vector
    :return: Estimated coefficient vector for the linear regression
    """

    # add column of ones for the intercept
    ones = np.ones((len(X), 1))
    X = np.concatenate((ones, X), axis=1)

    # calculate the coefficients
    beta = np.dot(np.linalg.inv(np.dot(X.T, X)), np.dot(X.T, y))

    return beta

def fit_to_model(beta, X):
    """
    Applies the result of the least squares linear regression to the data.

    Parameters
    ----------
    beta : TYPE np.array
        Contains the linear model coefficients.
    X : TYPE np.array
        Contains dataset features (column with ones not included).

    Returns
    -------
    None.

    """

    # add column of ones for the intercept
    ones = np.ones((len(X), 1))
    X = np.concatenate((ones, X), axis=1)

    # apply the coefficients to the data
    y = np.dot(X, beta)
    return y

def mean_squared_error(y_true, y_pred):
    """
    Calculates the mean squared error from the true target values and the
    predicted target values.

    Parameters
    ----------
    y_true : TYPE np.array
        Contains the true targets.
    y_pred : TYPE np.array
        Contains the predicted targets.

    Returns
    -------
    MSE : TYPE float
        Returns the mean squared error value.

    """

    # check if shapes of the arrays match
    if y_true.shape != y_pred.shape:
        print('y_true and y_pred should have the same shape!')
        return None

    # calculate MSE
    MSE = np.sum((y_true-y_pred)**2);
    MSE /= len(y_true)
    return MSE

def wlsq(X, y, d):
    """
    Least squares linear regression
    :param X: Input data matrix
    :param y: Target vector
    :param d: Weight vector
    :return: Estimated coefficient vector for the linear regression
    """

    # add column of ones for the intercept
    ones = np.ones((len(X), 1))
    X = np.concatenate((ones, X), axis=1)

    # normalize weights
    #d = (1/np.max(d))*np.array(d)

    # calculate the coefficients
    xw = X.T * d
    beta = np.dot(np.linalg.inv(np.dot(xw, X)), np.dot(xw, y))

    return beta

def normalize(X):
    """
    Normalizes the feature array. The normalization step is done per feature.

    Parameters
    ----------
    X : TYPE np.array
        Contains the features of the dataset.

    Returns
    -------
    X : TYPE np.array
        Contains the normalized features of the dataset.
    """

    # loop over every feature independently
    for i in range(X.shape[1]):

        # for every value, subtract by the average value of that feature and
        # divide by the standard deviation.
        X[:,i] = (X[:,i] - np.mean(X[:,i])) / np.std(X[:,i]);

    return X

def kNN(X_train, y_train, X_test, k=5, mode='classification'):
    """
    Applies the k-Nearest Neighbor method to a dataset

    Parameters
    ----------
    X_train : TYPE np.array
        Contains the features of the training dataset.
    y_train : TYPE np.array
        Contains the targets of the training dataset.
    X_test : TYPE np.array
        Contains the features of the test dataset.
    k : TYPE int, optional
        The number of considered 'nearest neighbors'. The default is 5.
    mode : TYPE string, optional
        Choose between 'classification' and 'regression' mode. Determines the
        result of this function. The default is 'classification'.

    Returns
    -------
    y_test : TYPE
        Predicted targets of the test dataset.

    """

    # initialize y_test array
    if mode == 'classification':
        y_test = np.zeros((X_test.shape[0], 1))
    elif mode == 'regression':
        y_test = np.zeros((X_test.shape[0], y_train.shape[1]))
    else:
        print("False parameter for 'mode'; should be either 'classification' or 'regression'.")
        return None

    for i in range(X_test.shape[0]):
        # calculate distance between test object and all train objects
        distances = np.linalg.norm(X_train - X_test[i], axis=1)

        # retrieve the indices of the closest neighbors
        ids_of_NN = distances.argsort()[:k]

        # retrieve the targets of the closest 5 neighbors
        NN_targets = y_train[ids_of_NN]

        if mode == 'classification':
            # find the most occuring target and add this to y_test
            new_target = np.bincount(NN_targets.T[0]).argmax()
            y_test[i] = new_target

        elif mode == 'regression':
            # find the average of the training targets and add to y_test
            y_test[i,:] = np.mean(NN_targets, axis=0)

    return y_test


"""
Matrix Inverse Method
# Linear Regression: Inverse Matrix Method
#----------------------------------
#
# This function shows how to use Tensorflow to
# solve linear regression via the matrix inverse.
#
# Given Ax=b, solving for A:  y=a1*x1+a2*x2, a1=1
  for the sample  number of m
  XA=Y     shapel X:[m 2]  A:[2 1] Y:[m 1]
  X.T*X*A=X.T*Y
  A=(X.T*X)^(-1)*X.T*Y

#  A = (t(x) * x)^(-1) * t(x) * b
#  where t(x) is the transpose of x
"""
import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf
tf.reset_default_graph()

x_vals=np.linspace(0,10,100)
y_vals=x_vals+np.random.normal(0,1,100)
x_vals_column=np.transpose(np.matrix(x_vals))
ones_column=np.matrix(np.ones((100,1)))#ones_column = np.transpose(np.matrix(np.repeat(1, 100)))
x=np.column_stack((ones_column,x_vals_column))
y=np.matrix(y_vals).T#y=np.transpose(np.matrix(y_vals))

#create tensors
x_tensor=tf.constant(x)
y_tensor=tf.constant(y)
#matrix inverse
x_xt=tf.matmul(tf.transpose(x_tensor),x_tensor)
x_xt_inv=tf.matrix_inverse(x_xt)
product=tf.matmul(x_xt_inv,tf.transpose(x_tensor))
A=tf.matmul(product,y_tensor)

sess=tf.Session()
A_eval=sess.run(A)

print("A:\n",A_eval)
#get the best fit line
best_fit_y=[]
for i in x_vals:
    best_fit_y.append(A_eval[0]+i*A_eval[1])
#best_fit_y=(np.dot(x,np.matrix(A_eval))).tolist()

#plot the fit line
plt.plot(x_vals,y_vals,"o",label="Data")
plt.plot(x_vals,best_fit_y,'r-',label="Best fit line",linewidth=3)
plt.legend(loc='upper letf')
plt.show()

cookbook/3 Linear Regression/Matrix Inverse Method.py


import numpy as np
import scipy.linalg as spl

class Sequency():

    def __init__(self):
        pass

    def _get_sequency(self,input_array):

        input_shift = np.roll(input_array,1)
        input_shift[0]= 0
        diffs = input_array + input_shift
        return np.sum(np.where(diffs==0,1,0))

    def walsh_matrix(self,scale):
        """
        Generate sequency order Walsh functions and return in a matrix.

        Parameters
        ==============
        scale : int
            The length of the Walsh functions, must be a power of 2, else will raise an exception.

            .. note::
                A scale = 8 means each Walsh function will have 8 data points and therefore be 8 units in length.

        Returns
        ============
        tuple(array, array(scale x scale))
            sequencies, scale x scale matrix of Walsh functions

        """

        hadamard = spl.hadamard(scale,dtype=int)
        sequencies = np.apply_along_axis(self._get_sequency, 1, hadamard)
        return np.sort(sequencies),hadamard[np.argsort(sequencies)]

    def fwt(self,input_values,basis_span='post',pad_mode='constant', **kargs):
        """
        Perform a Fast Discrete Walsh Transform on the input data. This will decompose the data using Walsh basis functions and create a vector of
        weighting values that represent the amplitude of each Walsh basis within the data. This is the discrete step decomposition equivalent of
        a Discrete Fast Fourier Transform, whereby the Sine and Cosine basis functions are replaced by Walsh functions.

        Parameters
        =============
        input_values : array_like
            The data to be decomposed via Walsh functions.

        basis_span : {'post','pre'}, Optional
            How long the Walsh basis functions should be relative to the input data.
             - **post** will extend the Walsh basis to the next length beyond the input data length that is a power of 2.
             - **pre** will truncate the Walsh basis to the next length less than the input data length that is a power of 2.

        pad_mode : {None, 'edge','constant'}, Optional
            This is how the input data is to be padded to match the requirement of each Walsh basis being a length which is a power of 2.

            .. note::
                The allowable parameters are the same as those for the Numpy.pad function and therefore any valid mode string or function
                can be used here and it will be passed to the Numpy.pad function.

        kargs : any
            Key-value arguments that will be passed to the Numpy.pad function, please see the Numpy.pad documentation for further details of
            allowable kargs for this function.

        Returns
        ==========
        tuple(array, array, int)
            sequencies, spectrum_amplitudes, walsh function range

        See Also
        ==========
        Sequency.fft

        References
        ==========
        .. https://mathworld.wolfram.com/WalshTransform.html

        """

        l = len(input_values)

        if basis_span == 'post':
            scale = 2**(1 + int(np.trunc(np.log2(l))))
        else:
            scale = 2**(int(np.trunc(np.log2(l))))

        if l % scale != 0 and scale > l:
            input_scaled = np.pad(input_values,(0,scale - l),pad_mode, **kargs)
        else:
            input_scaled = input_values[:scale]

        sf, wm = self.walsh_matrix(scale)
        wt = np.dot(wm,input_scaled)/scale

        return sf, wt,scale

    def ifwt(self,fwt_data, scale=None, return_length=None):
        """
        Perform an inverse Fast Discrete Walsh Transform on the input data. The input data is expected to be an array representing the transform amplitudes from a Walsh transform of a dataset.
        This method will use the amplitude data to reconstruct the original dataset using the provided ammplitudes matrix. The scale of the Walsh basis functions can be specified explcitly using the **scale** parameter, else if
        this parameter is not explicitly set, the scale value will be derived from the length of the fwt_data (amplitude) array.

        Parameters
        ==============
        fwt_data : array_like
            The Walsh transform amplitudes to use to reconstruct the dataset.

        scale : int, Optional
            The scale (length) of the Walsh functions to use when performing the inverse Walsh transform.

            .. note::
                This value must be a multiple of 2 or an exception will be raised.

        return_length : int, Optional
            The length of the reconstructed dataset, as the length of the original data may not be a multiple of 2, the scale of the Walsh functions used in thr transform (fwt) method may be shorter or longer, therefore when returning a reconstruction, you can specify a length to truncate the reconstuction at.

        Returns
        ==========
        array
            reconstructed dataset

        References
        =============
        .. https://mathworld.wolfram.com/WalshTransform.html

        """

        if scale is None:
            scale = len(fwt_data)

        _, wm = self.walsh_matrix(scale)
        iwt = np.dot(wm,fwt_data[:scale])

        if return_length is None:
            return iwt
        else:
            return iwt[:return_length]

    def denoise(self,input_values,method='walsh', basis_span='post',pad_mode='constant', denoise_mode='value',denoise_strength=0.5):
        """
        Perform a denoise operation on the input_values using either Walsh or Fourier method.

        Parameters
        ============
        input_values : array_like
            The data to be denoised via Walsh transform.

        method : {'walsh', 'fourier}, Optional
            The denoise method to apply to the data.
             - **walsh**, use a Walsh decomposition to produce the components to modify for denoising.
             - **fourier**, use a Fourier decomposition to produce the components to modify for denoising.

        basis_span : {'post','pre'}, Optional
            How long the Walsh basis functions should be relative to the input data.
             - **post** will extend the Walsh basis to the next length beyond the input data length that is a power of 2.
             - **pre** will truncate the Walsh basis to the next length less than the input data length that is a power of 2.

        pad_mode : {None, 'edge','constant'}, Optional
            This is how the input data is to be padded to match the requirement of each Walsh basis being a length which is a power of 2.

            .. note::
                The allowable parameters are the same as those for the Numpy.pad function and therefore any valid mode string or function
                can be used here and it will be passed to the Numpy.pad function.

        denoise_mode : {'range', 'value'}, Optional
            The type of filtering to use.
             - **range** will remove a proportion of the higher sequency/frequency components based on the denoise_strength value.
             - **value** will remove the sequency/frequency components that are below the value based on the denoise_strength.

        denoise_strength : float, Optional
            The strength of the denoising, this parameter controls how the components are altered in order to denoise the input_data.

        Returns
        =============
        array
            denoised data

        """

        if method == 'walsh':
            return self._walsh_denoise(input_values=input_values,basis_span=basis_span,pad_mode=pad_mode, denoise_mode=denoise_mode,denoise_strength=denoise_strength)
        else:
            return self._fourier_denoise(input_values=input_values,denoise_mode=denoise_mode,denoise_strength=denoise_strength)

    def _walsh_denoise(self,input_values,basis_span='post',pad_mode='constant', denoise_mode='value',denoise_strength=0.5):
        """
        Perform a Fast Discrete Walsh Transform on the input data, selectively removing sequency components by setting their amplitudes to zero, then reconstruct the original dataset by performing an inverse Walsh transform on the remaining amplitudes.

        Parameters
        ============
        input_values : array_like
            The data to be denoised via Walsh transform.

        basis_span : {'post','pre'}, Optional
            How long the Walsh basis functions should be relative to the input data.
             - **post** will extend the Walsh basis to the next length beyond the input data length that is a power of 2.
             - **pre** will truncate the Walsh basis to the next length less than the input data length that is a power of 2.

        pad_mode : {None, 'edge','constant'}, Optional
            This is how the input data is to be padded to match the requirement of each Walsh basis being a length which is a power of 2.

            .. note::
                The allowable parameters are the same as those for the Numpy.pad function and therefore any valid mode string or function
                can be used here and it will be passed to the Numpy.pad function.

        denoise_mode : {'range', 'value'}, Optional
            The type of filtering to use.
             - **range** will remove a proportion of the higher sequency components based on the denoise_strength value.
             - **value** will remove the sequency components that are below the value based on the denoise_strength.

        denoise_strength : float, Optional
            The strength of the denoising, this parameter controls how the components are altered in order to denoise the input_data.

        Returns
        =============
        array
            denoised data

        """

        l = len(input_values)

        _,wt,sc = self.fwt(input_values=input_values,basis_span=basis_span,pad_mode=pad_mode)

        if denoise_strength >= 0 and denoise_strength < 1:
            if denoise_mode == 'range':
                filter_idx = int(denoise_strength*sc)
                wt[-filter_idx:] = 0
            else:
                denoise_threshold = np.amax(np.absolute(wt))*denoise_strength
                wt = np.where(np.absolute(wt)>denoise_threshold, wt, 0)

        return self.ifwt(wt,scale=sc,return_length=l)

    def _fourier_denoise(self,input_values, denoise_mode='value',denoise_strength=0.5):
        """
        Perform a Fast Discrete Fourier Transform on the input data, selectively removing frequency components by setting their amplitudes to zero, then reconstruct the original dataset by performing an inverse Fourier transform on the remaining amplitudes.

        Parameters
        ============
        input_values : array_like
            The data to be denoised via Walsh transform.

        denoise_mode : {'range', 'value'}, Optional
            The type of filtering to use.
             - **range** will remove a proportion of the higher frequency components based on the denoise_strength value.
             - **value** will remove the frequency components that are below the value based on the denoise_strength.

        denoise_strength : float, Optional
            The strength of the denoising, this parameter controls how the components are altered in order to denoise the input_data.

        Returns
        =============
        array
            denoised data

        """

        l = len(input_values)

        _,ft = self.fft(input_values)

        if denoise_strength >= 0 and denoise_strength < 1:
            if denoise_mode == 'range':
                    filter_idx = int(denoise_strength*len(ft))
                    ft[-filter_idx:] = 0
            else:
                denoise_threshold = np.amax(np.absolute(ft))*denoise_strength
                ft = np.where(np.absolute(ft)>denoise_threshold, ft, 0)

        return self.ifft(ft)

    def sequency_spectrum(self, input_values,pad_mode='constant',**kargs):
        """
        A convenient Sequency power spectrum representation of the input_values data, this will be normalised by the sum of squares of all amplitude values, thus representing a proportional power spectrum.

        Parameters
        ===========
        input_values : array_like
            The data (best to use step change values) to be analysed to generate a sequency power spectrum.

        pad_mode : {None, 'edge','constant'}, Optional
            This is how the input data is to be padded to match the requirement of each Walsh basis being a length which is a power of 2.

            .. note::
                The allowable parameters are the same as those for the Numpy.pad function and therefore any valid mode string or function
                can be used here and it will be passed to the Numpy.pad function.

        kargs : any
            Key-value arguments that will be passed to the Numpy.pad function, please see the Numpy.pad documentation for further details of
            allowable kargs for this function.

        Returns
        =============
        tuple(array, array, int)
            sequencies, spectrum_amplitudes, walsh function range

        """

        sf,wm,sc = self.fwt(input_values,pad_mode=pad_mode,**kargs)
        return sf, np.power(wm,2)/(np.sum(np.power(wm,2))),sc

    def frequency_spectrum(self,data,sampling_frequency=1):
        """
        A basic implementation of the Fast Fourier Transform power spectrum as implemented in Numpy.fft.fft.

        Parameters
        =============
        data : array_like
            The data that the frequency spectrum is generated from.

        sampling_frequency : int, Optional
            The sampling frequency used to scale the frequency spectrum, specifically this will need to be tuned based on your data as different scalings will be required to suit the type and density of data points.

        Returns
        ===========
        tuple(array, array)
            frequencies, spectrum_amplitudes

        See Also
        ===========
        Sequency.sequency_spectrum

        References
        =================
        .. https://numpy.org/doc/stable/reference/routines.fft.html

        """

        fourierTransform = np.fft.fft(data)/len(data)
        fourierTransform = fourierTransform[range(int(len(data)/2))]

        tpCount=len(data)
        values=np.arange(int(tpCount/2))
        timePeriod=tpCount/sampling_frequency
        frequencies=values/timePeriod

        return frequencies, np.absolute(fourierTransform)/(np.sum(np.absolute(fourierTransform)))

    def fft(self,data,sampling_frequency=1):
        """
        A basic implementation of the Fast Fourier Transform as implemented in Numpy.fft.fft.

        Parameters
        =============
        data : array_like
            The data that is to be transformed using the fft method from Numpy.

        sampling_frequency : int, Optional
            The sampling frequency used to scale the frequency spectrum, specifically this will need to be tuned based on your data as different scalings will be required to suit the type and density of data points.

        Returns
        ===========
        tuple(array, array)
            frequencies, transform_amplitudes

        See Also
        ===========
        Sequency.sequency_spectrum

        References
        =================
        .. https://numpy.org/doc/stable/reference/routines.fft.html

        """

        fourierTransform = np.fft.fft(data)
        #fourierTransform = fourierTransform[range(int(len(data)/2))]

        tpCount=len(data)
        values=np.arange(int(tpCount/2))
        timePeriod=tpCount/sampling_frequency
        frequencies=values/timePeriod

        return frequencies, fourierTransform

    def ifft(self,data):
        """
        A basic implementation of the inverse Fast Fourier Transform as implemented in Numpy.fft.ifft.

        Parameters
        =============
        data : array_like
            The Fourier transform data that is to be inverted back into the time domain.

        Returns
        ===========
        array
            data

        See Also
        ===========
        Sequency.ifwt

        References
        =================
        .. https://numpy.org/doc/stable/reference/routines.ifft.html

        """

        ifourierTransform = np.fft.ifft(data)

        return ifourierTransform


import numpy as np
import math

import matplotlib.pyplot as plt

def volume_ellipsoid(Q):
    """
    volume of set x.T @ Q^{-1} @ x <= 1

    see https://faculty.fuqua.duke.edu/~psun/bio/MVEpaper_accept.pdf
    or
        Grötschel, M., L. Lovasz, A. Schrijver. 1998.
        Geometric Algorithms and Combinatorial Optimization.
        Springer-Verlag, Berlin, Germany.
    """
    n = Q.shape[0]
    m_pi, gamma = math.pi, math.gamma(n/2. + 1)
    det_Qinv = 1. / np.linalg.det(Q)
    vol = (m_pi**(n / 2.) / gamma) * (1./np.sqrt(det_Qinv))
    return vol

def is_in_ellipse(x, mu, Q):
#     return (x-mu).T @ np.linalg.inv(Q) @ (x-mu) < 1.
    return (x-mu).T @ np.linalg.solve(Q, (x-mu)) < 1.

def count_nb_in_ellipse(Xs, mu, Q):
    if mu.size != Q.shape[0] or Xs.shape[1] != mu.size:
        raise ValueError("Xs (%d,%d), mu (%d) and Q (%d,%d) must be the same size" %(Xs.shape[0], Xs.shape[1], mu.size, Q.shape[0], Q.shape[1]))

    nb_in_ellipse = 0.
    for xi in Xs:
        if is_in_ellipse(xi, mu, Q) == True:
            nb_in_ellipse += 1
    return nb_in_ellipse

def count_nb_in_ellipse_and_get_idx_inside(Xs, mu, Q):
    if mu.size != Q.shape[0] or Xs.shape[1] != mu.size:
        raise ValueError("Xs (%d,%d), mu (%d) and Q (%d,%d) must be the same size" %(Xs.shape[0], Xs.shape[1], mu.size, Q.shape[0], Q.shape[1]))

    nb_in_ellipse = 0.
    indices_inside = []
    for idx,xi in enumerate(Xs):
        if is_in_ellipse(xi, mu, Q) == True:
            indices_inside.append(idx)
            nb_in_ellipse += 1
    return nb_in_ellipse, indices_inside

def percentage_in_ellipse(Xs, mu, Q):
    if mu.size != Q.shape[0] or Xs.shape[1] != mu.size:
        raise ValueError("Xs (%d,%d), mu (%d) and Q (%d,%d) must be the same size" %(Xs.shape[0], Xs.shape[1], mu.size, Q.shape[0], Q.shape[1]))

    return count_nb_in_ellipse(Xs, mu, Q) / Xs.shape[0]

def sample_pts_ellipsoid_surface(mu, Q, NB_pts, random=True):
    """
    Uniformly samples points on the surface of an ellipsoid, specified as
    (xi-mu)^T Q^{-1} (xi-mu) == 1
    arguments: mu      - mean [dim]
                Q      - Q [dim x dim]
                NB_pts - nb of points
                random - True: Uniform sampling.
                         False: Uniform deterministic grid
    output:    ell_pts - points on the boundary of the ellipse [xdim x NB_pts]
    """
    dim = mu.shape[0]
    if dim != Q.shape[0] or dim != Q.shape[1]:
        raise ValueError("mu (%d) and Q (%d,%d) must be the same size" %(mu.shape[0], Q.shape[0], Q.shape[1]))
    if (Q == np.zeros((dim,dim))).all():
        return np.zeros((dim,NB_pts))

    if random == False and dim > 2:
        raise ValueError("sample_pts_ellipsoid_surface: non random sampling not implemented")

    mut = np.array([mu])
    pts = sample_pts_unit_sphere(dim, NB_pts, random=random).T
    E   = np.linalg.cholesky(Q)

    ell_pts = (mut + pts @ E.T).T

    return ell_pts

def sample_pts_unit_sphere(dim, NB_pts, random=True):
    """
    Uniformly samples points on a d-dimensional sphere (boundary of a ball)
    Points characterized by    ||x||_2 = 1
    arguments:  dim    - nb of dimensions
                NB_pts - nb of points
                random - True: Uniform sampling.
                         False: Uniform deterministic grid
    output:     pts    - points on the boundary of the sphere [xdim x NB_pts]
    Reference: http://extremelearning.com.au/how-to-generate-uniformly-random-points-on-n-spheres-and-n-balls/
    """
    if dim == 2 and random == False:
        angles = np.linspace(0., 2*np.pi, num=NB_pts, endpoint=False)
        pts = np.array([np.cos(angles),np.sin(angles)])
        return pts

    if random == False and dim > 2:
        raise ValueError("sample_pts_unit_sphere: non random sampling not implemented")

    u = np.random.normal(0, 1, (dim,NB_pts))
    d = np.sum(u**2,axis=0) **(0.5)
    pts = u/d
    return pts

def sample_pts_unit_ball(dim, NB_pts):
    """
    Uniformly samples points in a d-dimensional sphere (in a ball)
    Points characterized by    ||x||_2 < 1
    arguments:  dim    - nb of dimensions
                NB_pts - nb of points
    output:     pts    - points sampled uniformly in ball [xdim x NB_pts]
    Reference: http://extremelearning.com.au/how-to-generate-uniformly-random-points-on-n-spheres-and-n-balls/
    """

    us    = np.random.normal(0,1,(dim,NB_pts))
    norms = np.linalg.norm(us, 2, axis=0)
    rs    = np.random.random(NB_pts)**(1.0/dim)
    pts   = rs*us / norms

    return pts

def sample_pts_in_ellipsoid(mu, Q, NB_pts):
    """
    Uniformly samples points in an ellipsoid, specified as
            (xi-mu)^T Q^{-1} (xi-mu) <= 1
    arguments: mu - mean [dim]
                Q - Q [dim x dim]
    output:     pts - points sampled uniformly in ellipsoid [xdim x NB_pts]
    """
    xs = sample_pts_unit_ball(mu.shape[0], NB_pts)

    E  = np.linalg.cholesky(Q)
    ys = (np.array(E@xs).T + mu).T

    return ys

def sample_pts_in_hyperrectangle(center, widths, NB_pts_each_dim, random=False):
    """
    arguments:  center          - (dim,  ) center
                widths          - (dim,  ) total widths from and to edges of rectangle
                NB_pts_each_dim - number of points used for sampling along each dimension
                random          - deterministic or random (uniform) sampling (not implemented yet)
    output:     uniformly sampled dim-dimensional points in the rectangle:
                        |xi - center| < width/2
                    size: [xdim x NB_total_points)]
    """
    if random==True:
        print('[sample_pts_in_hyperrectangle] non deterministic (meshgrid) sampling not implemented.')

    dim = center.shape[0]
    M   = NB_pts_each_dim

    edges = np.array([center-widths/2, center+widths/2]).T

    out = np.mgrid[[np.s_[edges[d,0]:edges[d,1]:(M*1j)] for d in range(dim)]]
    out = out.T.reshape(-1,dim).T
    return out

def get_hyperrectangle_volume(widths):
    return np.prod(widths)

def test_ellipsoid_function(NB_pts=100):

    mu = np.array([1,2])
    Q = np.array([[1 ,0.2], [0.2 ,1.6]])

    pts = sample_pts_ellipsoid_surface(mu, Q, NB_pts)

    plt.scatter(pts[0,:], pts[1,:])

def bound_rectangle_with_ellipsoid(widths):
    dim = widths.shape[0]
    Q = dim * np.diag(widths)
    return Q


# Copyright 2018 - Jonathan Alcantara e Osmar Fernandes

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#     http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import numpy as np
from math import pow
from math import sqrt

def cholesky(A):
    # if not np.all(np.linalg.eigvals(A) > 0):
    #     print "Matriz nao e Positiva Definida"
    #     return false

    L = [[0.0] * len(A) for i in range(len(A))]

    for row in range(len(A)):
        sum_row = 0
        for k in range(row):
            sum_row += pow(L[row][k], 2)

        L[row][row] = sqrt(A[row][row] - sum_row)
        print('%s, %s: %s' % (row+1, row+1, L[row][row]))
        for column in range(row + 1, len(A)):
            sum_column = 0
            for k in range(row):
                sum_column += L[row][k]*L[column][k]

            L[column][row] = (A[row][column] - sum_column)/L[row][row]
            print('%s, %s: %s' % (row+1, column+1, L[column][row]))

    A = np.array(A)
    L = np.array(L)
    transposed_L = np.transpose(L)

    print '\nA : \n', A
    print '\nL : \n', L
    print '\nL_T :\n', transposed_L

    return A, L, transposed_L

def cholesky_solution(L, B):
    print 'Cholesky Solution: ', np.matmul(np.linalg.inv(np.matmul(L, np.transpose(L))), np.transpose(B))

def cholesky_det(L):
    print 'Cholesky Determinant: ',  np.power(np.prod(L.diagonal()), 2)


import numpy as np
import numpy.linalg as la
import matplotlib.pyplot as plt
import pandas as ps

# Determinant
A = np.array([[0,-1],[1,0]])
print("\n Determinant of 2x2: ",round(la.det(A)))

B = np.array([[2,4,3],[3,5,2],[5,4,2]])
print("\n Determinant of 3x3 matrix: ",round(la.det(B)))

print("\n Matrix: ")
print(A)
print("\n Inverse of A: ")
print(la.inv(A))
print("\n Transpose: ")
print(A.T)

#Eigen Values and Eigen Vectors
C= np.array([[2,4],[6,7]])
eigenvals = la.eigvals(C)
eigval, eigvec = la.eig(C)

print("\n Original matrix: ")
print(C)
print("\n Just the Eigen values: ")
print(eigenvals)
print("\n Eigen Values: ")
print(eigval)
print("\n Eigen vectors: ")
print(eigvec)

D=np.array([[1,2,3,4],[2,2,2,2],[4,3,2,5],[3,2,4,3]])
eigenValss = la.eigvals(D)
print("\n Original 4x4 Matrix: ")
print(D)
print("\n Eigen values of a 4x4 matrix: ")
print(eigenValss)

F = np.array([[1,4],[4,1]])
eigval, eigvec = la.eig(A)

v1=eigvec[:,0]
v2=eigvec[:,1]

Av1 = F @ v1
Av2 = F @ v2

v1_mag = np.sqrt(np.sum(np.square(eigvec[:,0])))
v2_mag = np.sqrt(np.sum(np.square(eigvec[:,1])))

print("Original Matrix: ")
print(F)
print("\n The Eigen Decomposition")
print("The Eigen values: ")
print(eigval)
print("The Eigen vectors: ")
print(eigvec)
print("\n The Magnitude of v1 and v2: ")
print(round(v1_mag), round(v2_mag))

plt.plot([0, v1[0]], [0, v1[1]], 'black', label='v1', linewidth=2)
plt.plot([0, v2[0]], [0, v2[1]], 'yellow',label='v2', linewidth=2)
plt.plot([0, Av1[0]], [0, Av1[1]], 'r:')
plt.plot([0, Av2[0]], [0, Av2[1]], 'b:')

plt.legend()
plt.grid()
plt.axis('square')
plt.axis([-2,4,-3,4])
plt.show()


# -*- coding: utf-8 -*-
"""
Created on Mon Aug  3 23:15:37 2020

@author: elewah
"""
# =============================================================================
# #Reference of algorthim that is used distribute the point over circumferece
# #https://stackoverflow.com/questions/48081690/how-to-find-points-on-a-circumference
# #get_2Dpoints function over a circumference,
# #input variable radius of the circle and number of points, the output is (nX2) cordinates-points matrix in dataframe format
# #where n is number of points and 2 is two columns (x,y)
# =============================================================================
import numpy as np
def get_2Dpoints(radius, number_of_points):
    radians_between_each_point = 2*np.pi/number_of_points
    list_of_points = []
    for p in range(0, number_of_points):
        list_of_points.append( (radius*np.cos(p*radians_between_each_point),radius*np.sin(p*radians_between_each_point)) )
    return list_of_points


# encoding=utf8

"""

Module containing functions and methods related to sequences in number theory such as
the Fibonacci sequence, the 3n + 1 problem, and more.

"""

import numpy as np
from mathpy.numtheory.integers import iseven
from decimal import Decimal, localcontext

def fibonacci(n, output='last', prec=100):
    r"""
    Computes the Fibonacci sequence up to given value of n.

    Parameters
    ----------
    n : int
        Integer designating the stopping point of the Fibonacci sequence
    output : {'last', 'array'}, optional
        If 'last', the last integer in the Fibonacci sequence up to n is returned,
        if 'array', the entire sequence is returned. Defaults to 'last'.
    prec : int default 100, optional
        Defines level of precision for factorials over 100
        for use by the decimal package

    Returns
    -------
    fn : numpy ndarray or int
        Last integer in Fibonacci sequence up to :math:`n` or the an array of the
        Fibonacci sequence up to :math:`n`.

    Notes
    -----
    The Fibonacci sequence is defined by a recurrence relation where :math:`f_n` denotes
    the series up to :math:`n` points.

    .. math::

        f_n = f_{n-1} + f_{n-2}

    With initial conditions:

    .. math::

        f_1 = 1, \qquad f_2 = 2

    Examples
    --------
    >>> fibonacci(10)
    55.0
    >>> fibonacci(10, 'array')
    array([  0.,   1.,   1.,   2.,   3.,   5.,   8.,  13.,  21.,  34.,  55.])

    References
    ----------
    Moler, C. (2011). Numerical computing with MATLAB (1st ed.).
        Philadelphia, Pa: Society for Industrial & Applied Mathematics.

    """
    fn = np.empty(n + 1)

    fn[0] = 0
    fn[1] = 1
    if n >= 100:
        with localcontext() as ctx:
            ctx.prec = prec
            for i in np.arange(2, n + 1):
                fn[i] = Decimal(fn[i - 1]) + Decimal(fn[i - 2])
    else:
        for i in np.arange(2, n + 1):
            fn[i] = fn[i - 1] + fn[i - 2]

    if output is 'last' or output is not 'array':
        return fn[-1]
    else:
        return fn

def three_n_one(n):
    r"""
    Computes the famous :math:`3n + 1` sequence, also known as the Collatz conjecture.

    Parameters
    ----------
    n : int
        Starting integer to begin :math:`3n + 1` process.

    Returns
    -------
    sequence : list
        The sequence generated by the Collatz conjecture. If n is 1, 1 is returned.

    Notes
    -----
    The Collatz conjecture, also known as the :math:`3n + 1` problem, is a currently
    unsolved problem in number theory that is stated as:

    - Start with a positive integer :math:`n`
    - If :math:`n` is even, divide :math:`n` by 2
    - If :math:`n` is odd, multiply by 3 and add 1 to obtain :math:`3n + 1`
    - Repeat this process until the sequence reaches 1

    References
    ----------
    3x + 1 problem. (2017, May 13). In Wikipedia, The Free Encyclopedia.
        from https://en.wikipedia.org/w/index.php?title=3x_%2B_1_problem&oldid=780191927

    Moler, C. (2011). Numerical computing with MATLAB (1st ed.).
        Philadelphia, Pa: Society for Industrial & Applied Mathematics.

    """
    if n == 1:
        return 1.0

    sequence = []

    while n > 1:
        if iseven(n):
            n /= 2.0
            sequence.append(n)
        else:
            n = 3 * n + 1
            sequence.append(n)

    return sequence


import numpy as np
import matplotlib.pyplot as plt
#Solution of tridiagonal amtrix from 2.6 Eperson "An Introduction to Numerical Methods and Analysis"
#Of the form Ax=f
#l is lower diagonal, d is diagonal and u is the upper diagonal

#n=4
#l=np.array([np.NaN,1.0,1.0,1.0])
#d=np.array([4.0,4.0,4.0,4.0])
#u=np.array([1.0,1.0,1.0,np.NaN])
#f=np.array([6.0,12.0,18.0,19.0])
#x=np.array([0.0,0.0,0.0,0.0])
#
##Elimination stage
#for i in range(1,(n)):
#    d[i] = d[i] - u[i-1] *l[i] / d[i-1]
#    f[i] = f[i] - f[i-1] *l[i] / d[i-1]
#
##backsolve stage
#x[(n-1)]= f[(n-1)]/d[(n-1)]
#for i in range((n-2),-1,-1):
#    x[i] = (f[i] - u[i] * x[i+1])/d[i]
#
#print(x)

#Setting up the problem
m=20
h=1/(m+1)
u=(1/(h**2))*np.ones(m-1)
d=(1/(h**2))*(-2.0)*np.ones(m)
l=(1/(h**2))*np.ones(m-1)
F=np.empty(m)
U=np.empty(m)
x=np.arange(0,(1+h),h)
alpha=0
beta=1
#Edit for given function
def f(x):
    return (np.pi**2.0)*np.sin(np.pi*x)

#Creating F
F[0]=f(x[1]) - ( alpha/(h **2) )
F[m-1]=f(x[m-1]) - ( beta/(h **2) )
for i in range(1,m-1):
    F[i]=f(x[i+1])

#Solving the tridiagonal problem
#Elimination stage
for i in range(1,(m)):
    d[i] = d[i] - u[i-1] *l[i-1] / d[i-1]
    F[i] = F[i] - F[i-1] *l[i-1] / d[i-1]

#backsolve stage
U[(m-1)]= F[(m-1)]/d[(m-1)]
for i in range((m-2),-1,-1):
    U[i] = (F[i] - u[i] * U[i+1])/d[i]

#Insert end values
U=np.insert(U,0,alpha)
U=np.insert(U,m+1,beta)
#Print answer
print(U)


import scipy.integrate as spi
import numpy as np
import pylab as pl

beta=1.4247
gamma=0.14286
TS=1.0
ND=70.0
S0=1-1e-6
I0=1e-6
INPUT = (S0, I0, 0.0)

def diff_eqs(INP,t):
    '''The main set of equations'''
    Y=np.zeros((3))
    V = INP
    Y[0] = - beta * V[0] * V[1]
    Y[1] = beta * V[0] * V[1] - gamma * V[1]
    Y[2] = gamma * V[1]
    return Y   # For odeint

t_start = 0.0; t_end = ND; t_inc = TS
t_range = np.arange(t_start, t_end+t_inc, t_inc)
RES = spi.odeint(diff_eqs,INPUT,t_range)

print(RES)

#Ploting
pl.plot(RES[:,0], '-bs', label='Susceptibles')  # I change -g to g--  # RES[:,0], '-g',
pl.plot(RES[:,2], '-g^', label='Recovereds')  # RES[:,2], '-k',
pl.plot(RES[:,1], '-ro', label='Infectious')
pl.legend(loc=0)
pl.title('SIR epidemic without births or deaths')
pl.xlabel('Time')
pl.ylabel('Susceptibles, Recovereds, and Infectious')
pl.savefig('2.1-SIR-high.png', dpi=900) # This does, too
pl.show()


import numpy as np
from matplotlib import pyplot as plt

T=60 # final simulation time
N=100 # Step count
h=T/N # Step size

beta = 0.5 # S->I growth
gamma = 0.25 # I->R growth
rho1 = 0.01 # Group 1->2 growth
rho2 = 0.01 # Group 2->1 growth

Phi = np.zeros((N+1,6)) # Array for storing all step results
t = np.linspace(0,T,N+1) # All t values

Phi[0]=np.array([.95,.05,0,1,0,0]) # Initial value
S1, I1, R1, S2, I2, R2 = Phi[0] # ...

def f(t,Phi): # f(t,y) = y'
    S1, I1, R1, S2, I2, R2 = Phi # Extract pre-step values
    f = np.zeros(6) # Output array
    f[0] = -beta*S1*I1-rho1*S1+rho2*S2          # New S1
    f[1] = beta*S1*I1-gamma*I1-rho1*I1+rho2*I2  # New I1
    f[2] = gamma*I1-rho1*R1+rho2*R2             # New R1
    f[3] = -beta*S2*I2-rho2*S2+rho1*S1          # New S2
    f[4] = beta*S2*I2-gamma*I2-rho2*I2+rho1*I1  # New I2
    f[5] = gamma*I2-rho2*R2+rho1*R1             # New R2
    return f   # Return new values

for j in np.arange(0,N):  # For each step
    k1 = f(t[j],Phi[j])   # Compute k1
    k2 = f(t[j]+h/2,Phi[j]+h/2*k1) # Compute k2
    k3 = f(t[j]+h/2,Phi[j]+h/2*k2) # Compute k3
    k4 = f(t[j]+h,Phi[j]+h*k3) # Compute k4
    k = (k1+2*k2+2*k3+k4)/6 # Weighted average of ks
    Phi[j+1] = Phi[j] + h*k # Perform the step

plt.figure()  # Start a plot
plt.plot(t,Phi[:,0],'-',linewidth=1)   # Plot S1
plt.plot(t,Phi[:,1],'-',linewidth=1)   # Plot I1
plt.plot(t,Phi[:,2],'-',linewidth=1)   # Plot R1
plt.plot(t,Phi[:,3],'--',linewidth=1)   # Plot S2
plt.plot(t,Phi[:,4],'--',linewidth=1)   # Plot I2
plt.plot(t,Phi[:,5],'--',linewidth=1)   # Plot R2
plt.legend(['S_1','I_1','R_1','S_2','I_2','R_2'])  # Label each plot accordingly
plt.xlabel('t') # Label x axis
plt.show() # Finish and show the plot


#!/usr/local/bin/python3

import numpy as np

def load_data(data_path):
    points = list()
    with open(data_path, 'r') as f:
        lines = f.readlines()
        for line in lines:
            point_str = line.strip("\n").split("\t")
            points.append([float(point_str[0]), float(point_str[1])])
        points = np.array(points)
    return points

def ssei(cluster):
    # print(cluster)
    c = np.average(cluster, axis=0)
    # print(c)
    sse = 0
    for x in cluster:
        sse += np.square(np.linalg.norm(x-c))
    return sse

def sse(clusters):
    sse = 0
    for cluster in clusters:
        sse += ssei(cluster)
    return sse

def ssb(clusters, points):
    c = np.average(points, axis=0)
    ssb = 0
    for cluster in clusters:
        ci = np.average(cluster, axis=0)
        ssb += len(cluster) * np.square(np.linalg.norm(c-ci))
    return ssb

def tss(clusters, points):
    c = np.average(points, axis=0)
    tss = 0
    for point in points:
        tss += np.square(np.linalg.norm(point-c))
    return tss

def main():
    points = load_data("./6_points")
    print("================ a. ================")
    print("Cluster 1:")
    print(points[:3])
    print("Cluster 2:")
    print(points[3:])
    se1 = ssei(points[:3])
    print("SSE1 =",se1)
    se2 = ssei(points[3:])
    print("SSE2 =",se2)
    se = sse([points[:3], points[3:]])
    print("SSE =",se)
    sb = ssb([points[:3], points[3:]], points)
    print("SSB =",sb)
    ts = tss([points[:3], points[3:]], points)
    print("TSS =",ts)
    # print(se+sb)
    print("================ b. ================")
    cluster1 = np.array([points[0],points[5]])
    cluster2 = np.array([points[1],points[3]])
    cluster3 = np.array([points[2],points[4]])
    print("Cluster 1:")
    print(cluster1)
    print("Cluster 2:")
    print(cluster2)
    print("Cluster 3:")
    print(cluster3)
    se1 = ssei(cluster1)
    print("SSE1 =",se1)
    se2 = ssei(cluster2)
    print("SSE2 =",se2)
    se3 = ssei(cluster3)
    print("SSE3 =",se3)
    se = sse([cluster1,cluster2,cluster3])
    print("SSE =",se)
    sb = ssb([cluster1,cluster2,cluster3], points)
    print("SSB =",sb)
    ts = tss([cluster1,cluster2,cluster3], points)
    print("TSS =",ts)
    # print(se+sb)

if __name__ == "__main__":
    main()


import numpy as np
import math

def combination(n, r):
    return math.factorial(n) / math.factorial(r) / math.factorial(n - r)

def pascal_matrix(n):
    """
    computes a pascal matrix such as
    [[1,1,1,1],
     [1,2,3,4],
     [1,3,6,10],
     [1,4,10,20]]
    """
    return np.matrix([[combination(x+y-2, y-1) for y in range(1,n+1)]
                      for x in range(1,n+1)], dtype='double')

def harmonic_vector(n):
    """
    create a vector in the form [1,1/2,1/3,...1/n]
    """
    return np.array([[1.0 / i] for i in range(1, n + 1)], dtype='double')

def mult(a, b):
    """
    assumes the given matrices are the correct shape
    """
    result = np.zeros((a.shape[0], b.shape[1]))
    for i in range(result.shape[0]):
        for j in range(result.shape[1]):
            # result[i,j] = a col i * b row j
            result[i,j] = np.dot(a[i], b[:,j])
    return result

def norm_inf(matrix):
    return max(abs(sum(np.nditer(row))) for row in matrix)

def norm(vector):
    return math.sqrt(sum(float(x)**2 for x in np.nditer(vector)))

def lu_fact(matrix):  # 1a
    """
    computes the LU factorization of a matrix
    """
    size = len(matrix)
    l_mat = np.zeros((size,size))
    u_mat = np.zeros((size,size)) + matrix
    for row in range(size):
        for col in range(size):
            if row == col:
                l_mat[row,col] = 1 / u_mat[row,col]
                u_mat[row] /= u_mat[row,col]
            elif row > col:
                l_mat[row, col] += u_mat[row,col] / u_mat[col,col]
                u_mat[row] -= (u_mat[row,col] / u_mat[col,col]) * u_mat[col]

    err = norm_inf(mult(l_mat, u_mat) - matrix)

    return l_mat, u_mat, err

def qr_fact_househ(matrix):  # 1b
    size = matrix.shape[1]
    big = max(matrix.shape)
    working = np.zeros(matrix.shape) + matrix  # copy
    hs = []
    for col in range(size-1):
        submatrix = working[col:,col:]  # work only on the submatrix
        v = submatrix[:,0]  # gets a column
        e = np.zeros(submatrix.shape[1])  # calculates e for the submatrix
        e[0] = 1
        u = v + norm(v) * e
        u /= norm(u)  # calculate u = (v + ||v|| * e) / ||(v + ||v|| * e)||
        H_part = np.eye(submatrix.shape[0]) - 2 * mult(np.matrix(u).T, np.matrix(u))
        H = np.zeros((big,big))
        for i in range(col):
            H[i,i] = 1
        H[col:,col:] = H_part
        working = mult(H, working)
        hs.append(H)

    q_mat = reduce(mult, hs, np.eye(size))
    r_mat = mult(reduce(mult, hs[::-1], np.eye(size)), matrix)
    err = norm_inf(mult(q_mat, r_mat) - matrix)

    return q_mat, r_mat, err

def qr_fact_givens(matrix):  # 1b
    size = matrix.shape[1]
    working = np.zeros(matrix.shape) + matrix
    gs = []
    for col in range(size):
        for row in range(col+1, size):
            x, y = working[col, col], working[row, col]
            sqrtx2y2 = math.sqrt(x**2 + y**2)
            c = x / sqrtx2y2
            s = -y / sqrtx2y2
            givensmat = np.eye(size)
            givensmat[col, col] = c
            givensmat[row, row] = c
            givensmat[col, row] = -s
            givensmat[row, col] = s
            working = mult(givensmat, working)
            gs.append(givensmat)

    q = reduce(mult, (g.T for g in gs), np.eye(size))
    r = mult(reduce(mult, gs[::-1], np.eye(size)), matrix)
    err = norm_inf(mult(q, r) - matrix)
    return q, r, err

def solve_lu_b(A, b):  # 1c
    l, u, err = lu_fact(A)
    b_copy = b.copy()
    c_vec = [0 for n in b]
    for row in range(len(b)):
        curr = b_copy[row]
        for col in range(row):
            curr -= l[row, col] * c_vec[col]
        c_vec[row] = curr / l[row, row]

    x_vec = [0 for n in b]
    for row in range(len(b)-1, -1, -1):
        curr = c_vec[row]
        col = len(b) - 1
        while col > row:
            curr -= u[row, col] * x_vec[col]
            col -= 1
        x_vec[row] = curr / u[row, row]

    x_vec = np.array(x_vec)
    sol_err = norm_inf(mult(A, x_vec) - b)
    return x_vec, err, sol_err

def solve_qr_b(A, b):  # 1c
    q, r, err = qr_fact_househ(A)
    c_vec = mult(q.T, b)

    x_vec = [0 for n in b]
    for row in range(len(b)-1, -1, -1):
        curr = c_vec[row]
        col = len(b) - 1
        while col > row:
            curr -= r[row, col] * x_vec[col]
            col -= 1
        x_vec[row] = curr / r[row, row]

    x_vec = np.array(x_vec)
    sol_err = norm_inf(mult(A, x_vec) - b)
    return x_vec, err, sol_err

def load_matrix(file):
    with open(file) as f:
        return np.matrix([line.split(",") for line in f.readlines()], dtype='double')

def convert_matrix(m):
    A = m[:, :-1]
    b = m[:, -1]
    return A, b

def main():  # 1d
    print "solving with lu"
    for i in range(2, 13):
        x, err, sol_err = solve_lu_b(pascal_matrix(i), harmonic_vector(i))
        print i
        print x
        print "norm(LU-P), norm(Px-b)"
        print err, sol_err
        print ""

    print "solving with qr"
    for i in range(2, 13):
        x, err, sol_err = solve_qr_b(pascal_matrix(i), harmonic_vector(i))
        print i
        print x
        print "norm(QR-P), norm(Px-b)"
        print err, sol_err
        print ""


import numpy as np
import random, math

def algoAllPairs(cost, A, n):
	for i in range(n):
		for j in range(n):
			A[i][j] = cost[i][j]

	for k in range(n):
		for i in range(n):
			for j in range(n):
				A[i][j] = min(A[i][k] + A[k][j], A[i][j])

	return np.matrix(A)

n = random.randint(5, 7)
e = [[0 for x in range(n)] for y in range(n)]
c = [[0 for x in range(n)] for y in range(n)]

for i in range(n):
	for j in range(n):
		if (i!= j):
			e[i][j] = 1
			c[i][j] = random.randint(10,40)

print ('Number of vertices: ', n)
print ('Edge Adjacency Matrix: \n',np.matrix(e))
print ('Cost Adjacency Matrix: \n',np.matrix(c))
A = [[0 for x in range(n)] for y in range(n)]

print ('Minimum Cost Adjacency Matrix: \n', algoAllPairs(c, A, n))


'''
Are We Confident This Stock is Mean Reverting?

In the last chapter, you saw that the autocorelation of MSFT's weekly stock returns was -0.16. That autocorrelation seems large, but is it statistically significant? In other words, can you say that there is less than a 5% chance that we would observe such a large negative autocorrelation if the true autocorrelation were really zero? And are there any autocorrelations at other lags that are significantly different from zero?

Even if the true autocorrelations were zero at all lags, in a finite sample of returns you won't see the estimate of the autocorrelations exactly zero. In fact, the standard deviation of the sample autocorrelation is 1/N‾‾√
1
/
N
 where N
N
 is the number of observations, so if N=100
N
=
100
, for example, the standard deviation of the ACF is 0.1, and since 95% of a normal curve is between +1.96 and -1.96 standard deviations from the mean, the 95% confidence interval is ±1.96/N‾‾√
±
1.96
/
N
. This approximation only holds when the true autocorrelations are all zero.

You will compute the actual and approximate confidence interval for the ACF, and compare it to the lag-one autocorrelation of -0.16 from the last chapter. The weekly returns of Microsodt is pre-loaded in a DataFrame called returns.

INSTRUCTIONS
100XP
INSTRUCTIONS
100XP
Recompute the autocorrelation of weekly returns in the Series 'Adj Close' in the returns DataFrame.
Find the number of observations in the returns DataFrame using the len() function.
Approximate the 95% confidence interval of the estimated autocorrelation. The math function sqrt() has been imported and can be used.
Plot the autocorrelation function of returns using plot_acf that was imported from statsmodels. Set alpha=0.05 for the confidence intervals (that's the default) and lags=20.
'''
# Import the plot_acf module from statsmodels and sqrt from math
from statsmodels.graphics.tsaplots import plot_acf
from math import sqrt

# Compute and print the autocorrelation of MSFT weekly returns
autocorrelation = returns['Adj Close'].autocorr()
print("The autocorrelation of weekly MSFT returns is %4.2f" %(autocorrelation))

# Find the number of observations by taking the length of the returns DataFrame
nobs = len(returns)

# Compute the approximate confidence interval
conf = 1.96/sqrt(nobs)
print("The approximate confidence interval is +/- %4.2f" %(conf))

# Plot the autocorrelation function with 95% confidence intervals and 20 lags using plot_acf
plot_acf(returns, alpha=0.05, lags=20)
plt.show()


#Code to produce values for cumulated curvature of the cosine function

import numpy as np

import scipy.integrate as integrate

import matplotlib.pyplot as plt

#############
#Functions for Curvature for Cosine
#############

def cos_curvature(delta):
	return np.absolute(-np.cos(delta))/(1+(np.sin(delta)**2))

#########################
#Functions for Partitioning the domain into Q pieces of equal curvature
#########################

def curvature_target_x_value(integration_lb, interval_lb, interval_ub, target_value, eps = 0.000001):
	#Finds an approximation for a value x in the domain where accumulated curvature is equal to a target value
	#The strategy is to recursively narrow down the given interval into smaller and smaller intervals until an acceptable point is found.
	x = np.linspace(interval_lb, interval_ub)
	i = 0
	for value in x:
		cumulated_curvature = integrate.quad(cos_curvature, integration_lb, value)[0]
		if np.abs(cumulated_curvature - target_value) <= eps:
			return value
		if target_value - cumulated_curvature > eps:
			i = i + 1
			continue
		if cumulated_curvature - target_value > eps:
			return curvature_target_x_value(integration_lb, x[i-1], x[i], target_value, eps)

def eq_curvature_partition(lb, ub, Q, eps = 0.000001):
	#Divides the domain [lb, ub] into Q pieces of (approximately) equal curvature
	if Q == 1:
		return [lb, ub]
	breakpoints = [lb]
	target_value = (integrate.quad(cos_curvature, lb, ub)[0])/Q
	for i in range(Q-1):
		breakpoints.append(curvature_target_x_value(breakpoints[i], breakpoints[i], ub, target_value, eps))
	breakpoints.append(ub)
	return breakpoints

def eq_cosine_partition(lb, ub, Q):
	#Divides the domain [lb, ub] into Q pieces of equal curvature for the cosine function. Domain must be contained in (-pi/2, pi/2)
	if Q==1:
		return [lb, ub]
	total_curvature = np.arctan(np.sin(ub)) - np.arctan(np.sin(lb))
	breakpoints = [lb]
	for i in range(Q-1):
		breakpoints.append(np.arcsin(np.tan(total_curvature/Q + np.arctan(np.sin(breakpoints[i])))))
	breakpoints.append(ub)
	return breakpoints

if __name__ == '__main__':

    partition = eq_curvature_partition(-np.pi/6, np.pi/6, 16)

    cos_partition = eq_cosine_partition(-np.pi/6, np.pi/6, 16)

    print("Unrefined partition:", partition)

    print("Cosine partition:", cos_partition)

    x = np.linspace(-np.pi/6, np.pi/6)

    y1 = np.cos(x)

    plt.plot(x, y1)

    plt.show()

    for i in range(len(partition) - 1):
        x = np.linspace(partition[i], partition[i+1])
        y = np.cos(x)
        plt.plot(x, y)

    plt.show()

    for i in range(len(cos_partition) - 1):
    	x = np.linspace(cos_partition[i], cos_partition[i+1])
    	y = np.cos(x)
    	plt.plot(x, y)

    plt.show()


from random import sample
from typing import List

import numpy as np
from scipy.spatial.distance import euclidean

Point = List[int]

def assign_cluster(point: Point, centroids: List[Point]) -> int:
    """
    return index of closest centroid for a given point
    """
    min_distance = float("inf")
    closest_centroid_idx = None
    for idx, centroid in enumerate(centroids):
        distance = euclidean(point, centroid)
        if distance < min_distance:
            min_distance = distance
            closest_centroid_idx = idx

    return closest_centroid_idx

def calculate_variation(points: List[Point]) -> float:
    """
    return the maximum distance between any two points in a list
    """
    max_distance = 0
    for i in range(len(points)):
        for j in range(len(points)):
            distance = euclidean(points[i], points[j])
            max_distance = distance if distance > max_distance else max_distance

    return max_distance

def is_within_threshold(
    centroids: List[Point], means: List[Point], threshold: float
) -> bool:
    for centroid, mean in zip(centroids, means):
        distance = euclidean(centroid, mean)
        if distance > threshold:
            return False

    return True

def kmeans(
    points: List[Point],
    k: int = 3,
    runs: int = 10,
    max_iter: int = 300,
    threshold: float = 0.0001,
) -> List[Point]:
    """
    return a list of cluster centroids, given a list of points and the
    number of clusters to identify
    """
    if k > len(points):
        raise ValueError("number of clusters (k) must be less than number of points")

    dimensions = {len(p) for p in points}
    if len(dimensions) > 1:
        raise ValueError("points must all have the same dimensions")

    min_variation: float = float("inf")
    best_cluster: List[Point] = None

    for i in range(runs):
        centroids = sample(points, k)
        for i in range(max_iter):
            clusters = [[] for c in centroids]
            for p in points:
                closest_centroid_idx = assign_cluster(p, centroids)
                clusters[closest_centroid_idx].append(p)

            means = [np.mean(c, axis=0).tolist() for c in clusters]
            if is_within_threshold(centroids, means, threshold):
                break

            centroids = means

        variation = sum([calculate_variation(c) for c in clusters])
        if variation < min_variation:
            min_variation = variation
            best_cluster = centroids

    return best_cluster

if __name__ == "__main__":
    # test data are one-dimensional points with three clear centroids at 2, 12, and 22
    points = [[1], [2], [3], [11], [12], [13], [21], [22], [23]]
    centroids = kmeans(points, k=3)
    assert len(centroids) == 3
    assert [2] in centroids
    assert [12] in centroids
    assert [22] in centroids

    # centroids and means with the same values should always be within threshold
    centroids = [[1], [2], [3]]
    means = [[1], [2], [3]]
    threshold = 0.0001
    assert is_within_threshold(centroids, means, threshold) == True

    # centroids and means not within threshold
    centroids = [[1], [2], [3]]
    means = [[100], [200], [300]]
    threshold = 0.0001
    assert is_within_threshold(centroids, means, threshold) == False