import matplotlib.pyplot as plt
import numpy as np
class EulerNumericalSolution():
    def __init__(self, type, val, f, t_min, t_max, x_init):
        if type == "h":
            self.h = val
            self.n = int((t_max-t_min)/ self.h)
        elif type == "N":
            self.n = val
            self.h = (t_max - t_min) / self.n
        self.t_min = t_min
        self.t_max = t_max
        self.x_init = x_init
        self.f = f

    def simulate(self):
        ts, xs = [self.t_min], [self.x_init]
        cur_t = self.t_min
        cur_x = self.x_init

        for iteration in range(self.n):
            cur_x += self.f(cur_x, cur_t) * self.h
            cur_t += self.h
            ts.append(cur_t)
            xs.append(cur_x)

        return ts, xs

    def plot(self, legend):
        ts, xs = self.simulate()
        plt.plot(ts, xs, label=legend)
        plt.title("Comparison result")
        plt.xlabel("t")
        plt.ylabel("x")
        plt.grid()
        #plt.show()


# 2020.01.04
# Problem Statement:
# https://leetcode.com/problems/unique-binary-search-trees/

import numpy as np

# version 1: # the same idea as the last question, added a dp structure for faster speed
class Solution:
    def numTreesHelper(self, start, end):
        # initialize answer to return
        num = 0

        # what really matters about the number is the difference
        # eg: if 1-3 has 5 binary trees, 4-6 should has 5 binary trees as well
        difference = end - start

        # base case: can only form one kind of tree
        if difference <= 0:
            num = 1

        # already calculated, directly return
        elif self.dp_structure[difference] != 0:
            return self.dp_structure[difference]

        # should do the calculation
        else:
            for i in range(start, end+1):
                left_num = self.numTreesHelper(start, i-1)
                right_num = self.numTreesHelper(i+1, end)
                num = num + left_num * right_num
                # fill in the dp structure
                self.dp_structure[difference] = num

        return num

    def numTrees(self, n: int) -> int:
        self.dp_structure = np.zeros(n)
        return int(self.numTreesHelper(1, n))

############################################################
# version 2: idea is the same as version 1, but more generalized
# the answer for n would always equal to the sum of products of all 2-number combinations which add up to n-1
# eg: answer(7) = answer(0)*answer(6) + answer(1)*answer(5) + answer(2)*answer(4) + answer(3)*answer(3)
#               + answer(4)*answer(2) + answer(5)*answer(1) + answer(6)*answer(0)
# half of them do not to be calculated twice, but actually will not take much time because of the early return
# v2 is still slow, but it's the best I could think of :-(
import numpy as np
class Solution:
    def numTreesHelper(self, n):
        # already calculated, directly return
        if self.dp_structure[n] != 0: return self.dp_structure[n]

        num = 0
        for i in range(n):
            num = num + self.numTreesHelper(i) * self.numTreesHelper(n-1-i)

        ''' # alternatively: save half loop but not much faster
        if n % 2 == 1:
            for i in range((n-1)//2):
                num = num + self.numTreesHelper(i) * self.numTreesHelper(n-1-i)
            num = 2*num + self.numTreesHelper(n//2)**2
        else:
            for i in range(n//2):
                num = num + self.numTreesHelper(i) * self.numTreesHelper(n-1-i)
            num = 2*num
        '''

        self.dp_structure[n] = num
        return num

    def numTrees(self, n: int) -> int:
        self.dp_structure = np.zeros(n+1)
        # base cases
        self.dp_structure[0] = 1
        self.dp_structure[1] = 1
        return int(self.numTreesHelper(n))


# Name: Breno Maurício de Freitas Viana
# NUSP: 11920060
# Course Code: SCC5830
# Year/Semester: 2021/1
# Assignment 1: Image Generator

import math
import random
import numpy as np

# ----- (1) Read parameters

# Get image filename for the reference image `r`
r = input().rstrip()
# Get lateral size of the synthesized image `C`
C = int(input())
# Get the function `F` to be used
F = int(input())
# Get parameter `Q`
Q = int(input())
# Get lateral size of the sampled image `N`
N = int(input())
# Get number of bits per pixel `B`
B = np.uint8(input())
# Get seed `S`
S = int(input())

# ----- (2) Generating Synthetic Image

# --- Auxiliary Variables and Funtions

# Initialize random algoritm generation with the seed `S`
random.seed(S)

# n-root function
def root(x, n):
  return x ** (1 / n)

# --- Functions

def f1(x, y):
  return x * y + 2 * y

def f2(x, y):
  return abs(math.cos(x / Q) + 2 * math.sin(y / Q))

def f3(x, y):
  return abs(3 * (x / Q) - root(y / Q, 3))

def f4(x, y):
  return random.random()

def f5(f):
  # Initialize the first position
  x, y = 0, 0
  f[x, y] = 1
  # Initialize auxiliary variables
  i = 0
  max = 1 + C * C
  # Perform random walk
  while i < max:
    # Get next step
    dx = random.randint(-1, 1)
    dy = random.randint(-1, 1)
    x = (x + dx) % C
    y = (y + dy) % C
    f[x, y] = 1
    i += 1

fc = [f1, f2, f3, f4]

# --- Create synthetic image

# Initialize default synthetic image `f`
f = np.zeros((C,C), dtype=float)

# Applying function `F`
if F in [1, 2, 3]:
  for x in range(C): # Row
    for y in range(C): # Col
      f[x, y] = fc[F - 1](x, y)
elif F == 4:
  # Calculates in raster order
  for x in range(C): # Row
    for y in range(C): # Col
      f[y, x] = f4(x, y)
else:
  # Random Walk
  f5(f)

# Normalizing the pixels' values with 16 bits (65536)
f = (f - f.min()) / (f.max() - f.min())
f *= 65535

# ----- (3) Sampling image from `f` to create `g`

# --- Downsampling

# Calculing the downsampling operator
step = int(C / N)
g = f[::step,::step]

# --- Quantizing

# Normalizing the pixels' values with 8 bits (256)
g = (g - g.min()) / (g.max() - g.min())
g *= 255

# Convert o usigned integer of 8 bits
g = g.astype(np.uint8)

# Apply bitwise operations
B = 8 - B
g = g >> B << B

# ----- (4) Comparing `g` against reference image `r`

# Load image
R = np.load(r)

# --- Calculate the root squared error
rse = 0.0
for x in range(N):
  for y in range(N):
    rse += math.pow(float(g[x, y]) - float(R[x, y]), 2)
rse = math.sqrt(rse)

# --- Print the computed result of RSE
print(round(rse, 4))


#!/usr/bin/env python3

import numpy as np

#################################################
### Functions Definitions
#################################################

def heat_source(t, x, N, letter):
    """
    Calcula o valor da fonte de calor de um determinado problema,
    em um determinado instante, em uma determinada posição.
    """

    if letter == "a":
        result = 10 * np.cos(10 * t) * (x**2) * ((1 - x)**2) - (1 + np.sin(10 * t)) * (12 * (x**2) - 12 * x + 2)
    elif letter == "b":
        result = 5 * np.exp(t - x) * ((5 * (t**2) * np.cos(5 * t * x)) - (np.sin(5 * t * x) * (2 * t + x)))
    elif letter == "c":
        r = 10000 * (1 - 2 * (t**2))

        h = 1 / N
        p = 0.25

        if (p - h / 2) <= x <= (p + h / 2):
            gh = N
        else:
            gh = 0

        result = r * gh
    else:
        result = 0

    return result

def u_solution(t, x, letter):
    """
    Calcula o valor da solução de um determinado problema,
    em um determinado instante, em uma determinada posição.
    """

    if letter == "a":
        result = (1 + np.sin(10 * t)) * (x**2) * ((1 - x)**2)
    elif letter == "b":
        result = np.exp(t - x) * np.cos(5 * t * x)
    elif letter == "c":
        result = 0
    else:
        result = 0

    return result

def initial_condition(x, letter):
    """
    Retorna as condições iniciais de u determinado problema em
    uma determinada posição.
    """

    if letter == "a":
        result = (x**2) * ((1 - x)**2)
    elif letter == "b":
        # result = u_solution(0, x, letter)
        result = np.exp(-x)
    elif letter == "c":
        result = 0
    else:
        result = 0

    return result

def boundary_conditions(t, letter):
    """
    Retorna as condições de contorno de um determinado problema,
    em um determinado instante.

    Se retorna primeiro a condição de contorno em x = 0, depois
    em x = 1.
    """

    if letter == "a":
        cond_zero = 0
        cond_one = 0
    elif letter == "b":
        # cond_zero = u_solution(t, 0, letter)
        cond_zero = np.exp(t)
        # cond_one = u_solution(t, 1, letter)
        cond_one = np.exp(t - 1) * np.cos(5 * t)
    elif letter == "c":
        cond_zero = 0
        cond_one = 0
    else:
        cond_zero = 0
        cond_one = 0

    return cond_zero, cond_one

def create_u(times_array, positions_array, letter):
    """
    Cria uma matrix U, dado um vetor com a discretização do tempo
    e um vetor com a descretização do espaço.

    A matriz é inicializada com as condições de contorno e condições
    iniciais, dependendo do problema que se está analizando.
    """

    num_of_times = len(times_array)
    num_of_position = len(positions_array)

    # Create U matrix
    U = np.zeros((num_of_times, num_of_position))

    # Initial conditions calculation
    for i in range(0, num_of_position):
        U[0][i] = initial_condition(positions_array[i], letter)

    # Boundary conditions calculation
    for k in range(0, num_of_times):
        U[k][0], U[k][-1] = boundary_conditions(times_array[k], letter)

    return U


""" File containing PCA class """
import numpy as np
import matplotlib.pyplot as plt

class PCA:
    """ Performs Primary Component Analysis """
    def __init__(self, filename):
        """ Loads csv to array """
        self.col = 0
        self.dimensions = 0     # D in the assignment
        if filename == 'swiss_data.csv':
            self.col = 2000
            self.dimensions = 3
            self.choice = 0
        elif filename == 'digits.csv':
            self.col = 5620
            self.dimensions = 64
            self.choice = 1
            self.labels = np.genfromtxt('../csv/digits_label.csv', delimiter=',').tolist()

        if self.col == 0 and self.dimensions == 0:
            print("wrong filename")
        else:
            raw_data = np.genfromtxt('../csv/' + filename, delimiter=',')   # X
            self.small_x = np.reshape(raw_data, (self.col, self.dimensions))
            self.fit = 0    # F in the assignment

    def fit_data(self):
        """ Creates fit using PCA and assigns it to self.F """
        self.small_x = self.small_x - np.mean(self.small_x, axis=0)
        covmatrix = np.cov(self.small_x.T)

        [eigenvalues, eigenvectors] = np.linalg.eig(covmatrix)

        eig_pairs = [(np.abs(eigenvalues[i]), eigenvectors[:, i]) for i in range(len(eigenvalues))]
        eig_pairs.sort(key=lambda x: x[0], reverse=True)
        self.fit = np.hstack((eig_pairs[0][1].reshape(self.dimensions, 1),
                              eig_pairs[1][1].reshape(self.dimensions, 1)))

    def transform(self):
        """ Transforms data using fit (self.fit). Plots result. """
        small_y = self.fit.T @ (self.small_x.T)
        if self.choice == 0 :
            plt.scatter(small_y[1, :], small_y[0, :],
                        c=np.arange(self.col), cmap='gist_rainbow', s=7)
        elif self.choice == 1:
            plt.scatter(small_y[0, :], small_y[1, :],
                        c=self.labels, cmap='jet', s=7, marker=".")
        plt.show()


# -*- coding: utf-8 -*-
"""Principal Component Analysis

Created on Tue Sep 29 20:11:23 2009
Author: josef-pktd

TODO : add class for better reuse of results
"""

import numpy as np

def pca(data, keepdim=0, normalize=0, demean=True):
    '''principal components with eigenvector decomposition
    similar to princomp in matlab

    Parameters
    ----------
    data : ndarray, 2d
        data with observations by rows and variables in columns
    keepdim : integer
        number of eigenvectors to keep
        if keepdim is zero, then all eigenvectors are included
    normalize : boolean
        if true, then eigenvectors are normalized by sqrt of eigenvalues
    demean : boolean
        if true, then the column mean is subtracted from the data

    Returns
    -------
    xreduced : ndarray, 2d, (nobs, nvars)
        projection of the data x on the kept eigenvectors
    factors : ndarray, 2d, (nobs, nfactors)
        factor matrix, given by np.dot(x, evecs)
    evals : ndarray, 2d, (nobs, nfactors)
        eigenvalues
    evecs : ndarray, 2d, (nobs, nfactors)
        eigenvectors, normalized if normalize is true

    Notes
    -----

    See Also
    --------
    pcasvd : principal component analysis using svd

    '''
    x = np.array(data)
    #make copy so original doesn't change, maybe not necessary anymore
    if demean:
        m = x.mean(0)
    else:
        m = np.zeros(x.shape[1])
    x -= m

    # Covariance matrix
    xcov = np.cov(x, rowvar=0)

    # Compute eigenvalues and sort into descending order
    evals, evecs = np.linalg.eig(xcov)
    indices = np.argsort(evals)
    indices = indices[::-1]
    evecs = evecs[:,indices]
    evals = evals[indices]

    if keepdim > 0 and keepdim < x.shape[1]:
        evecs = evecs[:,:keepdim]
        evals = evals[:keepdim]

    if normalize:
        #for i in range(shape(evecs)[1]):
        #    evecs[:,i] / linalg.norm(evecs[:,i]) * sqrt(evals[i])
        evecs = evecs/np.sqrt(evals) #np.sqrt(np.dot(evecs.T, evecs) * evals)

    # get factor matrix
    #x = np.dot(evecs.T, x.T)
    factors = np.dot(x, evecs)
    # get original data from reduced number of components
    #xreduced = np.dot(evecs.T, factors) + m
    #print x.shape, factors.shape, evecs.shape, m.shape
    xreduced = np.dot(factors, evecs.T) + m
    return xreduced, factors, evals, evecs

def pcasvd(data, keepdim=0, demean=True):
    '''principal components with svd

    Parameters
    ----------
    data : ndarray, 2d
        data with observations by rows and variables in columns
    keepdim : integer
        number of eigenvectors to keep
        if keepdim is zero, then all eigenvectors are included
    demean : boolean
        if true, then the column mean is subtracted from the data

    Returns
    -------
    xreduced : ndarray, 2d, (nobs, nvars)
        projection of the data x on the kept eigenvectors
    factors : ndarray, 2d, (nobs, nfactors)
        factor matrix, given by np.dot(x, evecs)
    evals : ndarray, 2d, (nobs, nfactors)
        eigenvalues
    evecs : ndarray, 2d, (nobs, nfactors)
        eigenvectors, normalized if normalize is true

    See Also
    -------
    pca : principal component analysis using eigenvector decomposition

    Notes
    -----
    This doesn't have yet the normalize option of pca.

    '''
    nobs, nvars = data.shape
    #print nobs, nvars, keepdim
    x = np.array(data)
    #make copy so original doesn't change
    if demean:
        m = x.mean(0)
    else:
        m = 0
##    if keepdim == 0:
##        keepdim = nvars
##        "print reassigning keepdim to max", keepdim
    x -= m
    U, s, v = np.linalg.svd(x.T, full_matrices=1)
    factors = np.dot(U.T, x.T).T #princomps
    if keepdim:
        xreduced = np.dot(factors[:,:keepdim], U[:,:keepdim].T) + m
    else:
        xreduced = data
        keepdim = nvars
        "print reassigning keepdim to max", keepdim

    # s = evals, U = evecs
    # no idea why denominator for s is with minus 1
    evals = s**2/(x.shape[0]-1)
    #print keepdim
    return xreduced, factors[:,:keepdim], evals[:keepdim], U[:,:keepdim] #, v

__all__ = ['pca', 'pcasvd']


# Question: https://projecteuler.net/problem=588

# https://oeis.org/A247649

"""
    Let f(x) = x^4 + x^3 + x^2 + x + 1
    Then Q(N) = (f(x) ** N) (mod 2)
    Some notes:
        * We can do polynomial multiplication via np.convolve, as np.convolve(a,v,'full') is essentially
    polynomial multiplication.
        * Using a method similar to "exponentiation by squaring" (https://en.wikipedia.org/wiki/Modular_exponentiation#Right-to-left_binary_method), we can calculate Q(N) in O(log(N)) rather than in O(N).
        * From the FORMULA section in https://oeis.org/A247649, we can quickly find Q(n) as followed,
            + In the binary form of n, split the binary form by '00'. For example, split('01100010') = ('11', '10').
            + Let (a_1, a_2, ..., a_m) be the splitted form, then
                Q(N) = Q(a_1) * Q(a_2) * ... * Q(a_m)
            + For n that couldn't be furtherly splitted into more than 1 element, we have to manually calculate Q(n).
            + Using the relation, we "only" need to use polynomial multiplication to calculate Q(n) of n upto around 27000.
"""

import numpy as np

N = 18

def Q_brute_force(n):
    f = np.array([1])
    g = np.array([1,1,1,1,1])
    while n > 0:
        bit = n % 2
        if bit == 1:
            f = np.convolve(f, g)
        g = np.convolve(g, g)
        n //= 2
    return np.sum(f%2)

""" # This is also correct
def Q_brute_force(n):
    f = np.array([1,1,1,1,1])
    g = np.array([1,1,1,1,1])
    for _ in range(n-1):
        f = np.convolve(f, g)
    return np.sum(f%2)
"""

def Q(n, cache):
    binary_form_n = bin(n)[2:]
    parts = binary_form_n.split('00')
    ans = 1
    if len(parts) == 1:
        return Q_brute_force(int(parts[0], 2))
    for part in parts:
        if not part:
            continue
        next_n = int(part, 2)
        if not next_n in cache:
            cache[next_n] = Q(next_n, cache)
        ans *= cache[next_n]
    return ans

if __name__ == "__main__":
    ans = 0
    cache = {0:1, 1:5}
    for exp in range(1, N+1):
        n = 10**exp
        ans += Q(n, cache)
    print(ans)


# Figure 20.6, 20.7, 20.8

# PCA train set and test set reconstruction error vs K
# Reconstruction error on test set gets lower as K increased
# Screeplot and fraction of variance explained
# likelihood of PCA model shows “knee” or “elbow” in the curve

import superimport

import matplotlib.pyplot as plt
import numpy as np
from sklearn.decomposition import PCA
from scipy.stats import multivariate_normal
from tensorflow import keras
import warnings
from sklearn.metrics import mean_squared_error

warnings.simplefilter('ignore', RuntimeWarning) #for some NaN values

# Function to calculate log likelihood of PCA from eigenvalues
# Implemented equations from the book:
# "Probabilistic Machine Learning: An Introduction"

def log_likelihood(evals):

  Lmax = len(evals)
  ll = np.arange(0.0,Lmax)

  for L in range(Lmax):

    group1 = evals[0:L+1] #Divide Eigenvalues in two groups
    group2 = evals[L+1:Lmax]

    mu1 = np.mean(group1)
    mu2 = np.mean(group2)

    # eqn (20.30)
    sigma = (np.sum((group1-mu1)**2 ) + np.sum((group2-mu2)**2)) / Lmax

    ll_group1 = np.sum(multivariate_normal.logpdf(group1, mu1, sigma))
    ll_group2 = np.sum(multivariate_normal.logpdf(group2, mu2, sigma))

    ll[L] = ll_group1 + ll_group2 #eqn (20.31)

  return ll

# Standard mnist dataset

mnist = keras.datasets.mnist
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
train_images = train_images[train_labels == 3] # select images of digit '3'

n_images = 1000
train_images = train_images[0:n_images,:,:]
n_samples, n_rows, n_cols = train_images.shape
X = np.reshape(train_images, (n_samples, n_rows*n_cols))

X_train = X[0:int(n_images/2),:] #500 images in train set
X_test = X[int(n_images/2): ,:] #500 images in test set

#Reconstruction error on MNIST vs number of latent dimensions used by PCA

X_rank = np.linalg.matrix_rank(X_train)
K_linspace = np.linspace(1, 0.75*X_rank, 10, dtype=int)
Ks = np.unique(np.append([1, 5, 10, 20], K_linspace))

RMSE_train= np.arange(len(Ks))
RMSE_test = np.arange(len(Ks))

for index,K in enumerate(Ks):
   pca = PCA(n_components = K)

   Xtrain_transformed = pca.fit_transform(X_train)
   Xtrain_proj = pca.inverse_transform(Xtrain_transformed)
   RMSE_train[index] = mean_squared_error(X_train, Xtrain_proj, squared=False)

   Xtest_transformed = pca.transform(X_test)
   Xtest_proj = pca.inverse_transform(Xtest_transformed)
   RMSE_test[index] = mean_squared_error(X_test, Xtest_proj, squared=False)

#profile log likelihood for PCA

n_samples, n_features = X_train.shape
Kmax = min(n_samples, n_features)

pca = PCA(n_components = Kmax)
X_transformed = pca.fit_transform(X_train)
evals = pca.explained_variance_ #eigenvalues in descending order

ll = log_likelihood(evals)

#Fraction of variance explained

fraction_var = np.cumsum(evals[0:50]/np.sum(evals))

#Figure 20.6(a) train set reconstruction error

fig, ax = plt.subplots()
xs = Ks
ys = RMSE_train
plt.title('train set reconstruction error')
plt.xlabel('num PCs')
plt.ylabel('rmse')
ax.plot(xs, ys, marker = 'o')
plt.show()

#Figure 20.6(b) test set reconstruction error

fig, ax = plt.subplots()
xs = Ks
ys = RMSE_test
plt.title('test set reconstruction error')
plt.xlabel('num PCs')
plt.ylabel('rmse')
ax.plot(xs, ys, marker = 'o')
plt.show()

#Figure 20.7(a) Scree plot for training set

fig, ax = plt.subplots()
xs = np.arange(1, 51)
ys = evals[0:50]
plt.title('screeplot')
plt.xlabel('num PCs')
plt.ylabel('eigenvalues')
plt.ticklabel_format(axis="y", style="sci", scilimits=(0,0))
ax.plot(xs, ys)
plt.show()

#Figure 20.7(b) Fraction of variance explained

fig, ax = plt.subplots()
xs = np.arange(1, 51)
ys = fraction_var
plt.xlabel('num PCs')
plt.ylabel('proportion of variance explained')
ax.plot(xs, ys)
plt.show()

#Figure 20.8 Profile likelihood corresponding to PCA model

fig, ax = plt.subplots()
xs = np.arange(1, 51)
ys = ll[0:50]
plt.xlabel('num PCs')
plt.ylabel('profile log likelihood')

ax.plot(xs, ys)
plt.show()


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

def create_plotting_parameters_for_normal_distribution(mean, variance, num_std_dev_to_plot=3):
    """Create parameters to easily plot a normal distribution

    Arguments:
        mean {float} -- Mean of the normal distribution
        variance {float} -- Variance of the normal distribution

    """
    sigma = sqrt(variance)
    # I set the default range of x to be plus or minus 3 sigma since that has
    # the important visual part of the normal distribution
    x = np.linspace(mean - num_std_dev_to_plot*sigma,
                    mean + num_std_dev_to_plot*sigma, 100)
    y = stats.norm.pdf(x, mean, sigma)
    return x, y

if __name__ == "__main__":
    x, y = create_plotting_parameters_for_normal_distribution(0, 3)
    plt.plot(x, y)
    plt.show()


"""
Implementation of the bigClAM algorithm.
Throughout the code, we will use tho following variables
  * F refers to the membership preference matrix. It's in [NUM_PERSONS, NUM_COMMUNITIES]
   so index (p,c) indicates the preference of person p for algorithms c.
  * A refers to the adjency matrix, also named friend matrix or edge set. It's in [NUM_PERSONS, NUM_PERSONS]
    so index (i,j) indicates is 1 when person i and person j are friends.
"""

import numpy as np
import networkx as nx

def sigm(x):
    return np.divide(np.exp(-1.0 * x), 1.0 - np.exp(-1.0 * x))

def log_likelihood(F, A):
    """implements equation 2 of
    https://cs.stanford.edu/people/jure/pubs/bigclam-wsdm13.pdf"""
    A_soft = F.dot(F.T)

    # Next two lines are multiplied with the adjacency matrix, A
    # A is a {0,1} matrix, so we zero out all elements not contributing to the sum
    FIRST_PART = A * np.log(1.0 - np.exp(-1.0 * A_soft))
    sum_edges = np.sum(FIRST_PART)
    SECOND_PART = (1 - A) * A_soft
    sum_nedges = np.sum(SECOND_PART)

    log_likeli = sum_edges - sum_nedges
    return log_likeli

def gradient(F, A, i):
    """Implements equation 3 of
    https://cs.stanford.edu/people/jure/pubs/bigclam-wsdm13.pdf

      * i indicates the row under consideration

    The many forloops in this function can be optimized, but for
    educational purposes we write them out clearly
    """
    N, C = F.shape
    neighbours = np.where(A[i])
    nneighbours = np.where(1 - A[i])

    sum_neigh = np.zeros((C,))
    for nb in neighbours[0]:
        dotproduct = F[nb].dot(F[i])
        sum_neigh += F[nb] * sigm(dotproduct)

    sum_nneigh = np.zeros((C,))
    # Speed up this computation using eq.4
    for nnb in nneighbours[0]:
        sum_nneigh += F[nnb]

    grad = sum_neigh - sum_nneigh
    return grad

def train(A, C, iterations=100):
    # initialize an F
    N = A.shape[0]
    F = np.random.rand(N, C)

    for n in range(iterations):
        for person in range(N):
            grad = gradient(F, A, person)

            F[person] += 0.005 * grad

            F[person] = np.maximum(0.001, F[person])  # F should be nonnegative
        log_likelihood(F, A)
    return F

def big_Clam(graph, number_communities):
    adj = nx.to_numpy_matrix(graph)
    F = train(adj, number_communities)
    F_argmax = np.argmax(F, 1)
    dict_communities = {}
    for i in range(0, number_communities):
        dict_communities[i] = []
    for node, com in zip(graph.nodes(), F_argmax):
        dict_communities[com].append(node)

    list_communities = []
    for com in dict_communities:
        list_communities.append(dict_communities[com])

    return list_communities


import numpy as np

def dydx(x, y):
    return 2*x + np.sin( x * y )

x0 = float(input("Enter value of x0:- "))
y0 = float(input("Enter value oy y0:- "))
h = float(input("Enter step size:- "))
n = int(input("Enter number of points."))

y_vec = np.zeros((n, 1))
y_vec[0] = y0
x_vec = np.zeros((n, 1))
x_vec[0] = x0
for i in range(n-1):
    xi, yi = x_vec[i], y_vec[i]
    k1 = dydx(xi, yi)
    k2 = dydx(xi + h/2, yi + h*k1/2)
    k3 = dydx(xi + h/2, yi + h*k2/2)
    k4 = dydx(xi + h, yi + k3)
    x_vec[i+1] = xi + h
    y_vec[i+1] = yi + h / 6 * (k1 + 2*k2 + 2*k3 + k4)

print("--- Results ---")
print("x    |   y")
print(np.array([x_vec, y_vec]).T)


#!/usr/bin/env python

if __name__ == '__main__':

    from argparse import ArgumentParser
    from math import exp
    import numpy as np

    min_val = -1.0
    max_val = 1.0
    default_points = 10
    default_a = 1.0
    arg_parser = ArgumentParser(description='produce a 3D scalar field')
    arg_parser.add_argument('-x', type=int, default=default_points,
                            help='number of x points')
    arg_parser.add_argument('-y', type=int, default=default_points,
                            help='number of y points')
    arg_parser.add_argument('-z', type=int, default=default_points,
                            help='number of z points')
    arg_parser.add_argument('-a_x', type=float, default=default_a,
                            help='alpha_x')
    arg_parser.add_argument('-a_y', type=float, default=default_a,
                            help='alpha_y')
    arg_parser.add_argument('-a_z', type=float, default=default_a,
                            help='alpha_z')
    options = arg_parser.parse_args()
    print 'x,y,z,value'
    for x in np.linspace(min_val, max_val, num=options.x, endpoint=True):
        for y in np.linspace(min_val, max_val, num=options.y, endpoint=True):
            for z in np.linspace(min_val, max_val, num=options.z, endpoint=True):
                val = exp(-(options.a_x*x**2 +
                            options.a_y*y**2 +
                            options.a_z*z**2))
                print '{x:.4f},{y:.4f},{z:.4f},{val:.8f}'.format(x=x,
                                                                 y=y,
                                                                 z=z,
                                                                 val=val)


import pandas_datareader.data as pdr
import numpy as np
import datetime

# Retrieve historic data from required stocks..
ticker = "AAPL"

# Ensure the data is daily
Snp = pdr.get_data_yahoo(ticker , datetime.date.today() - datetime.timedelta(1825) , datetime.date.today())

def CAGR(dataFrame):
    dataFrame["DailyReturn"] = dataFrame["Adj Close"].pct_change()
    dataFrame["CumulativeReturn"] = (1 + dataFrame["DailyReturn"]).cumprod()

    # Getting the number of years given daily data
    n = len(dataFrame) / 252
    CAGR = (dataFrame["CumulativeReturn"][-1]) ** (1/n) - 1

    return CAGR

def Maxdrawdown (dataFrame):
    dataFrame["DailyReturn"] = dataFrame["Adj Close"].pct_change()
    dataFrame["CumulativeReturn"] = (1 + dataFrame["DailyReturn"]).cumprod()
    dataFrame["CumulativeRollMax"] = dataFrame["CumulativeReturn"].cummax()

    dataFrame["DrawDown"] = dataFrame["CumulativeRollMax"] - dataFrame["CumulativeReturn"]
    dataFrame["DrawDown_PCT"] = dataFrame["DrawDown"].max()/dataFrame["CumulativeRollMax"]
    maximumDrawDown = dataFrame["DrawDown_PCT"].max()

    dataFrame["CumulativeReturn"].plot()
    plt.show()

    return maximumDrawDown

def Calmar (dataFrame):
    calmar = CAGR(dataFrame) / Maxdrawdown(dataFrame)
    return calmar


from os import name
import numpy as np

def golden_section_search(fun, eps=1e-5, interval=None):
    if interval is None:
        print('暂时不处理这一功能')
        return
    key = (np.sqrt(5)-1)/2
    len_interval = interval[1]-interval[0]
    right = interval[0]+key*len_interval
    left = interval[1]-key*len_interval
    while len_interval > eps:
        len_interval *= key
        if fun(left) <= fun(right):
            interval[1] = right
            right = left
            left = interval[1]-key*len_interval
        else:
            interval[0] = left
            left = right
            right = interval[0]+key*len_interval
        print(interval)
    return (interval[1]+interval[0])/2

if __name__ == '__main__':
    def foo(x): return 1-x*np.exp(-x**2)
    print('0.618法', golden_section_search(
        foo, 0.01, interval=[0, 1]), '解析解', np.sqrt(0.5))


import numpy as np

#Tricycle Robot Model
#Input:
#Input Vehicle parameters:
#r = radius of the front wheel
#d = distance between the front wheel and the two back wheels
#L = distance between the two back wheels (not relevant for the model's behavior, but it might be for visualization purposes)
#Control Input parameters:
#u = front wheel angular velocity
#alpha = steering angle of the front wheel
#Robot State Input parameters
#(x,y) = initial robot position in 2D, measured at the center between the two back wheels
#theta = initial robot orientation, angle counterclockwise from the x-axis

#Output: New robot state
#(x,y) = position in 2D
#theta = angle with respect to the x-axis

#The following input parameters should be reflected in the visualization of the robot model:
#r, alpha, d

#this method is the same for all robot models
def forwardKinematics(R, omega, ICC, x, y, theta, t):
    x_new = (np.cos(omega * t) * (x - ICC[0])) + (-np.sin(omega * t) * (y - ICC[1])) + ICC[0]
    y_new = (np.sin(omega * t) * (x - ICC[0])) + (np.cos(omega * t) * (y - ICC[1])) + ICC[1]
    theta_new = theta + (omega * t)
    return [x_new, y_new, theta_new]

#this method is the same for all robot models
def straightMotion(v, x, y, theta, t):
    x_new = x + (v * t * np.cos(theta))
    y_new = y + (v * t * np.sin(theta))
    theta_new = theta
    return [x_new, y_new, theta_new]

#this is the only part that's different from the differential drive model
def robotStep(r, d, u, alpha, x, y, theta, t):
    v = u * r                                 #linear velocity of the front wheel

    if alpha == 0:                            #driving straight
        [x,y,theta] = straightMotion(v, x, y, theta, t)
    else:
        R = d * np.tan((np.pi/2) - alpha)
        omega = v/np.sqrt(d**2 + R**2)
        ICC = [x - R * np.sin(theta), y + R * np.cos(theta)] #Instantaneous Center of Curvature

        [x,y,theta] = forwardKinematics(R, omega, ICC, x,y, theta, t)

    return [x,y,theta]

def main(x, y, r, d, u, alpha, theta):

    robot_path = []

    t = 0
    t_step = 0.1                                       #update rate for the visualization

    #read all the input parameters described above from the user interface

    robot_path.append((x,y))

    #main loop
    loopy = 0

    while(loopy < 100):
        t = t+t_step
        [x, y , theta] = robotStep(r, d, u, alpha, x, y, theta, t_step)
        robot_path.append((x,y))
        loopy += 1;

    print(robot_path)


from __future__ import division
import numpy as np
import math

def mean_squared_error(y_true, y_pred):
    mse = np.mean((y_true - y_pred)**2)
    return (mse)

def euclidean_distance(x1, x2):
	''' Compute the euclidean (L2) distance between two vectors '''
	assert (len(x1) == len(x2)), 'Dimensions do not match'
	dist = 0
	for i in range(len(x1)) :
		dist += (x1[i] - x2[i])**2
	return (math.sqrt(dist))

def calculate_entropy(y):
    log2 = lambda x: math.log(x) / math.log(2)
    unique_labels = np.unique(y)
    entropy = 0
    for label in unique_labels:
        count = len(y[y == label])
        proba = count / len(y)
        entropy += -proba * log2(proba)
    return (entropy)

def accuracy_score(y_true, y_pred):
    accu_score = np.sum(y_true == y_pred, axis=0) / len(y_true)
    return (accu_score)

def compute_covariance_matrix(X, Y=None):
    ''' Covariance is a measure of the extent to which corresponding elements from TWO sets of ordered data move in the same direction '''
    if Y is None: # Y=None implicitly means we want to compute the variance, as we feed just one dataset into the function
        Y = X
    n_samples = np.shape(X)[0]
    covariance_matrix = (1 / (n_samples-1)) * (X - X.mean(axis=0)).T.dot(Y - Y.mean(axis=0)) # coeff : 1/(n-1) ==> unbiased estimate

    return (np.array(covariance_matrix, dtype=float))


import numpy as np
import random
from scipy.stats import norm
from scipy.stats import poisson
from scipy.stats import binom

class SampleDistributionClass:
    """This class will draw random numbers from a given distribution (Normal, Poisson, Binomial).

        It takes one argument for the number of samples and a second argument which specifies the
        distribution (Normal, Poisson or Binomial).

        The class can also handle additional parameters depending on the distribution chosen"""

    # Initializer / Instance attributes
    def __init__(self, samples=10000, distribution='Normal'):
        self.samples = samples
        self.distribution = distribution
        print ("----------------------------",
               "{} samples of a {} distribution selected".format(self.samples, self.distribution),
               "----------------------------",
               sep="\n")

    # instance draw method
    def draw(self, mean=0, sd=1, mu=3, n=10, p=0.5):
        self.mean = mean
        self.sd = sd
        self.mu = mu
        self.n = n
        self.p = p

        self.cls_sampled_distribution = np.zeros(shape=[self.samples, 1])

        if self.distribution == "Normal":
            self.cls_sampled_distribution = norm.rvs(size=self.samples, loc=self.mean, scale=self.sd)
            print ("----------------------------",
                   "Normal distribution parameters: mean = {}; standard deviation = {}".format(self.mean, self.sd),
                   "----------------------------",
                   sep="\n")
            return self.cls_sampled_distribution

        elif self.distribution == "Poisson":
            self.cls_sampled_distribution = poisson.rvs(size=self.samples, mu=self.mu)
            print ("----------------------------",
                   "Poisson distribution parameter: rate = {}".format(self.mu),
                   "----------------------------",
                   sep="\n")
            return self.cls_sampled_distribution

        elif self.distribution == "Binomial":
            self.cls_sampled_distribution = binom.rvs(size=self.samples, n=self.n, p=self.p)
            print ("----------------------------",
                   "Binomial distribution parameters: number of trials = {}; probability = {}".format(self.n, self.p),
                   "----------------------------",
                   sep="\n")
            return self.cls_sampled_distribution

        else:
            print ("----------------------------",
                   "ERROR!: {} distribution is not defined within function".format(self.distribution),
                   "----------------------------",
                   sep="\n")

     # instance summarise method
    def summarise(self):
        self.summary_min = self.cls_sampled_distribution.min()
        self.summary_max = self.cls_sampled_distribution.max()
        self.summary_mean = self.cls_sampled_distribution.mean()
        self.summary_sd = self.cls_sampled_distribution.std()

        print ("----------------------------",
                "Summary stats for {} samples of {} distribution:".format(self.samples, self.distribution),
               "----------------------------",
               "Min = {}".format(self.summary_min),
               "Max = {}".format(self.summary_max),
               "Mean = {}".format(self.summary_mean),
               "Standard Deviation = {}".format(self.summary_sd),
               "END",
               sep="\n")


from scipy import optimize
from math import exp

def f(x):
    return x*(x - 2)*exp(3 - x)

def fp(x):
    return -(x**2 - 4*x + 2)*exp(3 - x)

optimize.newton(f, 1., x1=1.5) # Using x1 = 1.5 and the secant method
# 1.9999999999999862
optimize.newton(f, 1., fprime=fp) # Using Newton-Raphson method
# 2.0


import matplotlib.pyplot as plt
import numpy as np

def f(E, l, kappa=1):
    """
    The implementation of the function f in the differential equation

    E' = f(E, l, k)

    with

    f(E, l, k) = k * E * l * (1 - E^2) / (1 + l*E^2)
    """
    return kappa * E * l * (1. - E**2) / (1. + l * E**2)

def plot_for_lambda(l):
    """
    Plot the vector field as a function of the parameter l.
    """
    e = np.linspace(-2, 2, 1000)
    e_dot = f(e, l)
    plt.plot(e, e_dot, label=rf"$\lambda={l}$")

if __name__ == "__main__":
    # plot_for_lambda(-2.)
    # plot_for_lambda(-1.05)
    plot_for_lambda(-1.)
    plot_for_lambda(-0.5)
    plot_for_lambda(0.)
    plot_for_lambda(1.)
    plt.legend()
    plt.show()


import numpy as np
import pandas as pd

def append_intercept(X: pd.DataFrame) -> pd.DataFrame:
    """
    Append a column of ones to the beginning of the dataset.

    Parameters
    ----------
    X : pd.DataFrame
        Observed features data

    Returns
    -------
    pd.DataFrame
        Design matrix
    """
    intercept = np.ones((len(X), 1))
    return np.hstack([intercept, X])

def estimate_coefficients(X: pd.DataFrame, y: np.ndarray) -> np.ndarray:
    """
    Returns the estimated beta hat vector.

    Parameters
    ----------
    X : pd.DataFrame
        Observed features data
    y : np.ndarray
        Target vector

    Returns
    -------
    np.ndarray
        Estimated coefficients
    """
    X = append_intercept(X)
    return np.linalg.inv(X.T @ X) @ (X.T @ y)

def calculate_predictions(X: pd.DataFrame, beta_hat: np.ndarray) -> np.ndarray:
    """
    Returns the predicted values for a test set of observations (X) using a
    vector of the estimated coefficients.

    Parameters
    ----------
    X : pd.DataFrame
        Test features data
    beta_hat : np.ndarray
        Estimated coefficients vector

    Returns
    -------
    np.ndarray
        Estimated coefficients
    """
    X = append_intercept(X)
    return X @ beta_hat

class ManualLinearRegression:
    """
    Manual implementation of an Ordinary Least Squares (OLS) linear regression
    model.
    """

    def __init__(self) -> None:
        """
        Initializes a new instance of the ManualLinearRegression model.
        """
        self.beta_hat = None

    def append_intercept(self, X: pd.DataFrame) -> pd.DataFrame:
        """
        Append a column of ones to the beginning of the dataset.

        Parameters
        ----------
        X : pd.DataFrame
            Observed features data

        Returns
        -------
        pd.DataFrame
            Design matrix
        """
        intercept = np.ones((len(X), 1))
        return np.hstack([intercept, X])

    def estimate_coefficients(
        self, X: pd.DataFrame, y: np.ndarray
    ) -> np.ndarray:
        """
        Returns the estimated beta hat vector.

        Parameters
        ----------
        X : pd.DataFrame
            Observed features data
        y : np.ndarray
            Target vector

        Returns
        -------
        np.ndarray
            Estimated coefficients
        """
        X = self.append_intercept(X)
        return np.linalg.inv(X.T @ X) @ (X.T @ y)

    def fit(self, X: pd.DataFrame, y: np.ndarray) -> None:
        """
        Calculate the estimated coefficients vector (beta hat).

        Parameters
        ----------
        X : pd.DataFrame
            Observed features data
        y : np.ndarray
            Target vector
        """
        self.beta_hat = self.estimate_coefficients(X, y)

    def predict(self, X: pd.DataFrame):
        """
        Returns the predicted values for a test set of observations (X) using a
        vector of the estimated coefficients.

        Parameters
        ----------
        X : pd.DataFrame
            Test features data
        beta_hat : np.ndarray
            Estimated coefficients vector

        Returns
        -------
        np.ndarray
            Estimated coefficients
        """
        if self.beta_hat is not None:
            X = self.append_intercept(X)
            return X @ self.beta_hat
        raise RuntimeError("Please fit the model before calling predict().")


"""
Plot the beta distribution.
"""

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

def Beta(x, alpha, beta):
    pmf = x ** (alpha - 1)  *  (1 - x) ** (beta - 1)
    norm_const = np.trapz(pmf, x)
    pmf /= norm_const
    return pmf

def Gaussian(x, mu, sigma):
    pmf = (1 / np.sqrt(2 * math.pi * sigma ** 2)) * np.exp(- (x - mu ) ** 2 / (2 * sigma ** 2))
    return pmf

# The Beta distribution is defined for a range of x between 0 and 1.
# The smaller the step size in x, the better the normalisation.

x = np.arange(0.0001, 1, 0.0001)    # Exclude 0 and 1 from the range of x for alpha, beta < 1, otherwise we get divide by zero errors

#plt.plot(x, Beta(x, 0.5, 1))        # pmf tends to infinity at x = 0, and 0.5 at x = 1
#plt.plot(x, Beta(x, 0.5, 0.5))      # U-shaped pmf, tending to infinity at x = 0 and x = 1

x = np.arange(0, 1.0001, 0.0001)    # Include 0 and 1 in the range of x for values of alpha and beta >= 1

#plt.plot(x, Beta(x, 1, 1))          # Uniform pmf, equal to 1 for all x
#plt.plot(x, Beta(x, 2, 1))          # Straight line with positive gradient, from 0 at x = 0 to 2 at x = 1
#plt.plot(x, Beta(x, 1, 2))          # Straight line with negative gradient, from 2 at x = 0 to 0 at x = 1
#plt.plot(x, Beta(x, 2, 2))          # Upside-down parabola centred on x = 0.5
#plt.plot(x, Beta(x, 3, 2))          # Broad, skewed distribution with bump on right
#plt.plot(x, Beta(x, 3, 3))          # Broad symmetric distribution centred on x = 0.5
plt.plot(x, Beta(x, 10, 10))        # Symmetric distribution centred on x = 0.5 (medium width)
#plt.plot(x, Beta(x, 100, 100))      # Narrow symmetric distribution centred on x = 0.5
#plt.plot(x, Beta(x, 30, 100))       # Almost symmetric distribution centred on x ~ 0.226
#plt.plot(x, Beta(x, 30, 50))        # Almost symmetric distribution centred on x ~ 0.372

plt.plot(x, Gaussian(x, 0.5, 0.112)) # Gaussian distribution most similar to Beta(10, 10)

print(np.trapz(Gaussian(x, 0.5, 0.3), x))

plt.show()


#sampling fro distribution is great way of testing methods
#common sample form disributions:
#1.Using scipy - from scipy.stats import norm
#scipy can't detect combinational distribution or complete arbitary dist
#2.Rejection sampling - difference in good and bad samples
#when we have distribution from -5 to 5, in rejection sampling most of the
#data is wasted and CPU time is wasted too,so we use inversion sampling
#for empirically derived distribution in 1D, use inversion
#3. Inversion sampling
#you can get cdf by integrating pdf,next invert cdf

#Central Limit Theorem - a distribution of sample means approxs a normal dist
#eg: if you paid 100 people to measure height of 10 people,100 samples of mean
#height values and 100 sample of mean values will have a distribution around it.

#using scipy - rvs is only required (random value sample)

from scipy.stats import norm,uniform
from scipy.integrate import simps
import numpy as np
import matplotlib.pyplot as plt

plt.title("Sampling")
plt.hist(norm.rvs(loc=10,scale=2,size=1000))#loc=mean,size=sample of 1000,wdth=2
plt.show()

#for rolling 3 die

sample= np.ceil(uniform.rvs(loc=0,scale=6,size=(1000,3))).sum(axis=1)#size of 2D
#in 1D, therefore 1000 samples
#print(sample)

plt.hist(sample)
plt.show()

#Rejection sampling:
#1) Uniformly sample x value
#2) Generate uniform y value from 0 to max probability in PDF
#3) If y>p(x), then throw the point

#eg: p(x) = sin(x^2)+1 from 0->4

def pdf(x):
  return np.sin(x**2)+1

xs = np.linspace(0,4,200)
ps= pdf(xs)
plt.title("Rejection Sampling")
plt.plot(xs,ps)
plt.fill_between(xs,0,ps,alpha=0.1)
plt.xlim(0,4)
plt.ylim(0,2)
plt.show()
#low value around dips and high number of values around peak

n = 100 #(samples)
random_x = uniform.rvs(loc=0,scale=4,size=n)
random_y = uniform.rvs(loc=0,scale=2,size=n)

plt.scatter(random_x,random_y)
plt.plot(xs,ps,c="g")
plt.fill_between(xs,0,ps,color="b",alpha=0.1)
plt.xlim(0,4),plt.ylim(0,2)
plt.show()
#blue points are proposal points
#to find the position whether they're above green line or not ->
passed = random_y <= pdf(random_x)
plt.scatter(random_x[passed],random_y[passed])
#this passes the points that did not pass the curve
plt.scatter(random_x[~passed],random_y[~passed],marker='x',s=30,alpha=0.5)
plt.plot(xs,ps,c="r")
plt.fill_between(xs,0,ps,color="b",alpha=0.1)#psuedo probability function
plt.xlim(0,4),plt.ylim(0,2)
plt.show()

from scipy.integrate import simps
n2=100000
x_test = uniform.rvs(scale=4,size=n2)
x_final = x_test[uniform.rvs(scale=2,size=n2) <= pdf(x_test)]
print()
print(len(x_final))
print()
plt.hist(x_final,density=True,histtype="step",label="Sample Distribution")
plt.plot(xs, ps / simps(ps,x=xs),c="b",marker="x",label = "Empirical Dist")
plt.legend(loc=2)
plt.show()
#not actual pdf as it is divided by simps

#INVERSION SAMPLING
#How to -> start with PDF, turn it into CDF, invert the CDF,uniformly sample 0-1
#trace it back to x
#eg: if p(x) = 3x^2 from 0->1
#                                            x
#To convert PDF -> CDF, integrate, eg: CDF = |p(x)dx = x^3
#                                            0
#Then invert it, eg: x^3 = y; y^3 = x
#y = x^1/3, therefore, x = CDF^1/3

#code
def pdf(x):
  return 3*x **2

def cdf(x):
  return x**3

def icdf(cdf):
  return cdf**(1/3)

xs = np.linspace(0,1,100)
pdfs = pdf(xs)
cdfs = cdf(xs)

n=2000
u_samps = uniform.rvs(size=n)
x_samps = icdf(u_samps)

fig,axes = plt.subplots(ncols=2,figsize=(10,4))

axes[0].plot(xs,pdfs,color="b",label="PDF")
axes[0].hist(x_samps,density="True",histtype="step",label="Sample Dist")

axes[1].plot(xs,cdfs,color="g",label="CDF")
axes[1].hlines(u_samps,0,x_samps,linewidth=0.1,alpha=0.3)
axes[1].vlines(x_samps,0,u_samps,linewidth=0.1,alpha=0.3)

axes[0].legend()
axes[1].legend()

axes[0].set_xlim(0,1)
axes[0].set_ylim(0,3)

axes[1].set_xlim(0,1)
axes[1].set_ylim(0,1)

plt.show()

#using the same function on previous PDF
#this might not always run as it does not come under the given range,
#so keep ruunning until you get lucky :D  :)
from scipy.interpolate import interp1d

def pdf(x):
  return np.sin(x**2)+1

xs = np.linspace(0,4,10000)
pdfs = pdf(xs)
cdfs = pdfs.cumsum() / pdfs.sum() #dangerous because it will never be 0
#code has potential to break as 1st term is 1/10000

u_samps = uniform.rvs(size=4000)
x_samps = interp1d(cdfs,xs)(u_samps)

fig,axes = plt.subplots(ncols=2,figsize=(10,4))

axes[0].plot(xs,pdfs/4.747,color="b",label="Analytic PDF")#pdf/4.747=normalizing
axes[0].hist(x_samps,density="True",histtype="step",label="Sample Dist")
axes[0].legend(loc=3), axes[0].set_xlim(0,4)

axes[1].plot(xs,cdfs,color="g",label="Analytic CDF")
axes[1].hlines(u_samps,0,x_samps,linewidth=0.1,alpha=0.3)
axes[1].vlines(x_samps,0,u_samps,linewidth=0.1,alpha=0.3)
axes[1].legend(loc=2)

axes[1].set_xlim(0,4)
axes[1].set_ylim(0,1)

plt.show()


import numpy as np
from matplotlib import pyplot as plt

def get_2d_point_colours(points):
    points_norm = (points - points.min(axis=0, keepdims=True)) / (points.max(axis=0, keepdims=True) - points.min(axis=0, keepdims=True))
    return [(y, x, 1-x) for x, y in points_norm]

def plot_2D_mapping(old_xy_points, new_xy_points, axes = None, old_title = 'x', new_title = 'f(x)'):
    """
    :param old_xy_points: (N,2) array
    :param new_xy_points: (Nx2) array
    """

    colours = get_2d_point_colours(old_xy_points)

    ax = plt.subplot(1, 2, 1) if axes is None else axes[0]
    ax.scatter(old_xy_points[:, 0], old_xy_points[:, 1], c=colours)
    ax.set_title(old_title)

    ax = plt.subplot(1, 2, 2) if axes is None else axes[1]
    ax.scatter(new_xy_points[:, 0], new_xy_points[:, 1], c=colours)
    ax.set_title(new_title)

if __name__ == '__main__':
    n_x = 40
    n_y = 30

    # Generate a grid of points
    old_xy_points = np.array([v.flatten() for v in np.meshgrid(np.linspace(0, 1, n_y), np.linspace(0, 1, n_x))]).T

    # Apply some transformation
    theta = 5*np.pi/6
    transform_matrix = np.array([[np.cos(theta), -np.sin(theta)], [np.sin(theta), np.cos(theta)]])
    new_xy_points = np.tanh(np.dot(old_xy_points, transform_matrix))

    plot_2D_mapping(old_xy_points, new_xy_points)


    import seaborn as sns

iris = sns.load_dataset('iris')
iris.head()

sns.set()
sns.pairplot(iris, hue='species', size=1.5)

#feature matrix
X_iris = iris.drop('species', axis=1)
X_iris.shape

y_iris = iris['species']
y_iris

# target vector
y_iris.shape

import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np

# four features in iris dataset

# reduce dimentionality of iris dataset
# it used more often to visualize the data

# PCA - Principle component Analysis
# and return 2 dimentional data to return

from sklearn.decomposition import PCA

model = PCA(n_components=2) # reduce dimentions to 2 from 4

model.fit(X_iris)
X_2D = model.transform(X_iris)
X_2D

iris['PCA1'] = X_2D[:,0]
iris['PCA2'] = X_2D[:,1]
iris

sns.lmplot("PCA1", "PCA2", hue='species', data=iris, fit_reg=False);

# We see that in the two-dimensional representation, the species are fairly well separated, even though the PCA algorithm had no knowledge of the species labels! This indicates to us that a relatively straightforward classification will probably be effective on the dataset, as we saw before.


'''
Routines for working with rotation matrices
'''

'''
comment
author :  Thomas Haslwanter
date :    Dec-2014
version : 1.7
'''

import numpy as np
import sympy
from collections import namedtuple

def R1(psi):
    '''Rotation about the 1-axis.
    The argument is entered in degree.

    Parameters
    ----------
    psi : rotation angle about the 1-axis [deg]

    Returns
    -------
    R1 : rotation matrix, for rotation about the 1-axis

    Examples
    --------
    >>> rotmat.R1(45)
    array([[ 1.        ,  0.        ,  0.        ],
           [ 0.        ,  0.70710678, -0.70710678],
           [ 0.        ,  0.70710678,  0.70710678]])

    '''

    # convert from degrees into radian:
    psi = psi * np.pi/180;

    R = np.array([[1, 0, 0],
            [0, np.cos(psi), -np.sin(psi)],
            [0, np.sin(psi),  np.cos(psi)]])

    return R

def R2(phi):
    '''Rotation about the 2-axis.
    The argument is entered in degree.

    Parameters
    ----------
    phi : rotation angle about the 2-axis [deg]

    Returns
    -------
    R2 : rotation matrix, for rotation about the 2-axis

    Examples
    --------
    >>> thLib.rotmat.R2(45)
    array([[ 0.70710678,  0.        ,  0.70710678],
           [ 0.        ,  1.        ,  0.        ],
           [-0.70710678,  0.        ,  0.70710678]])

    '''

    # convert from degrees into radian:
    phi = phi * np.pi/180;

    R = np.array([[np.cos(phi), 0, np.sin(phi)],
            [0, 1, 0],
            [  -np.sin(phi), 0, np.cos(phi)]])

    return R

def R3(theta):
    '''Rotation about the 3-axis.
    The argument is entered in degree.

    Parameters
    ----------
    theta : rotation angle about the 3-axis [deg]

    Returns
    -------
    R3 : rotation matrix, for rotation about the 3-axis

    Examples
    --------

    >>> thLib.rotmat.R3(45)
    array([[ 0.70710678, -0.70710678,  0.        ],
           [ 0.70710678,  0.70710678,  0.        ],
           [ 0.        ,  0.        ,  1.        ]])

    '''

    # convert from degrees into radian:
    theta = theta * np.pi/180;

    R = np.array([[np.cos(theta), -np.sin(theta), 0],
               [np.sin(theta),  np.cos(theta), 0],
               [0, 0, 1]])

    return R

def rotmat2Euler(R):
    '''
    This function takes a rotation matrix, and calculates
    the corresponding Euler-angles.
    R_Euler = R3(gamma) * R1(beta) * R3(alpha)

    Parameters
    ----------
    R : rotation matrix

    Returns
    -------
    alpha : first rotation(about 3-axis)
    beta : second rotation (about 1-axis)
    gamma : third rotation (about 3-axis)

    Notes
    -----
    The following formulas are used:

    .. math::
        \\beta   = -arcsin(\\sqrt{ R_{13}^2 + R_{23}^2 }) * sign(R_{23})

        \\gamma = arcsin(\\frac{R_{13}}{sin\\beta})

        \\alpha   = arcsin(\\frac{R_{31}}{sin\\beta})

    '''

    epsilon = 1e-6
    beta = - np.arcsin(np.sqrt(R[0,2]**2 + R[1,2]**2)) * np.sign(R[1,2])
    if beta < 1e-6:
        beta = 0
        gamma = 0
        alpha = np.arcsin(R[1,0])
    else:
        gamma = np.arcsin(R[0,2]/np.sin(beta))
        alpha = np.arcsin(R[2,0]/np.cos(beta))

    Euler = namedtuple('Euler', ['alpha', 'beta', 'gamma'])
    return Euler(alpha, beta, gamma)

def rotmat2Fick(R):
    '''
    This function takes a rotation matrix, and calculates
    the corresponding Fick-angles.

    Parameters
    ----------
    R : rotation matrix

    Returns
    -------
    psi : torsional  position (rotation about 1-axis)
    phi : vertical   position (rotation about 2-axis)
    theta : horizontal position (rotation about 3-axis)

    Notes
    -----
    The following formulas are used:

    .. math::

        \\theta   = arctan(\\frac{R_{21}}{R_{11}})

        \\phi = arcsin(R_{31})

        \\psi   = arctan(\\frac{R_{32}}{R_{33}})

    Note that it is assumed that psi < pi !
    '''

    theta = np.arctan2(R[1,0], R[0,0])
    psi = np.arctan2(R[2,1], R[2,2])
    phi = -np.arcsin(R[2,0])
    #phi = -np.arcsin(R[2,0])
    #theta = np.arcsin(R[1,0]/np.cos(phi))
    #psi = np.arcsin(R[2,1]/np.cos(phi))

    Fick = namedtuple('Fick', ['psi', 'phi', 'theta'])
    return Fick(psi, phi, theta)

def rotmat2Helmholtz(R):
    '''
    This function takes a rotation matrix, and calculates
    the corresponding Helmholtz-angles.

    Parameters
    ----------
    R : rotation matrix

    Returns
    -------
    psi : torsional  position (rotation about 1-axis)
    phi : vertical   position (rotation about 2-axis)
    theta : horizontal position (rotation about 3-axis)

    Notes
    -----
    The following formulas are used:

    .. math::
        \\theta = arcsin(R_{21})

        \\phi = -arcsin(\\frac{R_{31}}{cos\\theta})

        \\psi = -arcsin(\\frac{R_{23}}{cos\\theta})

    Note that it is assumed that psi < pi

    '''

    theta = np.arcsin(R[1,0])
    phi = -np.arcsin(R[2,0]/np.cos(theta))
    psi = -np.arcsin(R[1,2]/np.cos(theta))

    Helm = namedtuple('Helm', ['psi', 'phi', 'theta'])
    return Helm(psi, phi, theta)

def R1_s():
    '''
    Symbolic rotation matrix about the 1-axis, by an angle psi

    Returns
    -------
        R1_s : symbolic matrix for rotation about 1-axis

    Examples
    --------

    >>> R_Fick = R3_s() * R2_s() * R1_s()

    '''

    psi = sympy.Symbol('psi')

    return sympy.Matrix([[1,0,0],
                         [0, sympy.cos(psi), -sympy.sin(psi)],
                         [0, sympy.sin(psi), sympy.cos(psi)]])

def R2_s():
    '''
    Symbolic rotation matrix about the 2-axis, by an angle phi

    Returns
    -------
        R2_s : symbolic matrix for rotation about 2-axis

    Examples
    --------

    >>> R_Fick = R3_s() * R2_s() * R1_s()

    '''

    phi = sympy.Symbol('phi')

    return sympy.Matrix([[sympy.cos(phi),0, sympy.sin(phi)],
                         [0,1,0],
                         [-sympy.sin(phi), 0, sympy.cos(phi)]])

def R3_s():
    '''
    Symbolic rotation matrix about the 3-axis, by an angle theta

    Returns
    -------
        R3_s : symbolic matrix for rotation about 3-axis

    Examples
    --------

    >>> R_Fick = R3_s() * R2_s() * R1_s()

    '''

    theta = sympy.Symbol('theta')

    return sympy.Matrix([[sympy.cos(theta), -sympy.sin(theta), 0],
                         [sympy.sin(theta), sympy.cos(theta), 0],
                         [0, 0, 1]])

if __name__ == '__main__':
    testmat = np.array([[np.sqrt(2)/2, -np.sqrt(2)/2, 0],
                   [np.sqrt(2)/2,  np.sqrt(2)/2, 0],
                   [0, 0, 1]])
    Fick = rotmat2Fick(testmat)
    correct = np.r_[[0,0,np.pi/4]]
    print('Done testing')


import sympy as sp
import numpy as np
import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d as plt3d
from .support_funcs import default_range_2
from .exceptions import MinDimensionError

"""
Test function types are:
  1.Unimodal function.
  2.Functions with significant null-space effects.
  3.Essentially unimodal functions.
  4.Functions with a huge number of significant local optima.
  5.Functions with a small number of significant local optima
  6.Functions whose global structure provides no useful information about its optima.
"""
#TODO put each test function in it's type python file

class TestFunction:
    """
    TestFunction class describe a test functions for unconstrained optimization.
    It based on sympy function representation.
    So it consist of function variables, function form, default x0 and minimum dimension.
    You can lamdify
    """
    def __init__(self, name, function, x0, min_dimesion):
        self.variables = function.free_symbols
        self.dimension = len(self.variables)
        if self.dimension < min_dimesion:
            raise MinDimensionError(min_dimesion, self.dimension)
        self.name = name
        self.sympy_function = function
        self.x0 = x0

    def lambdify(self):
        f = sp.lambdify(self.variables, self.sympy_function)
        return lambda x: f(*x)

    def plot_surface_3d(self, x1, x2):
        if self.dimension == 2:
            fig = plt.figure(self.name)
            ax = fig.gca(projection='3d')
            X = x1
            Y = x2
            func = self.lambdify()
            X, Y = np.meshgrid(X, Y)
            Z = np.array([func((x, y)) for x, y in zip(X.ravel(), Y.ravel())]).reshape(X.shape)
            ax.plot_surface(X, Y, Z)
        else:
            print("The dimesion is too high for plotting")

    @property
    def get_name(self):
        return self.name

    @property
    def get_variables(self):
        return self.variables

    @property
    def get_function(self):
        return self.sympy_function

    @property
    def get_dimension(self):
        return self.dimension

    @property
    def get_x0(self):
        return self.x0

    def __str__(self):
        result = "Test function name: {self.name}\n" \
                 "Function dimension: {self.dimension}\n" \
                 "Function form: {self.sympy_function}\n" \
                 "Default x0: \n{self.x0}"
        return result.format(self=self)

def xi(i):
    return sp.Symbol('x%i' % i)

def summation(series_member, series_range):
    return sum([series_member(i) for i in series_range])

def create_test_function(name, n, series_member, x0,
                         first=None, range_func=default_range_2, limits=None,
                         min_dimesion=2):
    if limits:
        series_range = range_func(*limits)
    else:
        series_range = range_func(n)
    func = summation(series_member, series_range)
    if first:
        func += first()
    return TestFunction(name, func, x0, min_dimesion)


# Università di Bologna
# Corso di laurea in Informatica
# 02023 - Calcolo numerico
# 13/10/2021
#
# Stefano Volpe #969766
# lab2.py

import sys
import matplotlib.pyplot as plt
import numpy as np
import scipy.linalg.decomp_lu as LUdec
from scipy.linalg import hilbert

# Matrici e norme

def es0():
  """
  Considera la matrice A
  A=(1     2     )
    (0.499 1.001 )
  Calcola la norma 1, la norma 2, la norma Frobenius e la norma infinito di A
  con  numpy.linalg.norm() (guarda l'help della funzione).
  Calcola il numero di condizionamento di A con numpy.linalg.cond() (guarda
  l'help della funzione).
  Considera il vettore colonna x=(1,1)T e calcola il corrispondente termine
  noto b per il sistema lineare Ax=b.
  Considera ora il vettore b~=(3,1.4985)T e verifica che x~=(2,0.5)T è
  soluzione del sistema Ax~=b~.
  Calcola la norma 2 della perturbazione sui termini noti Δb=∥b−b~∥2 e la
  norma 2 della perturbazione sulle soluzioni Δx=∥x−x~∥2.
  Confronta Δb con Δx.
  """
  A = np.array([[1, 2], [0.499, 1.001]])
  print("Norma-1: ", np.linalg.norm(A, 1));
  print("Norma-2: ", np.linalg.norm(A, 2));
  print("Norma di Frobenius: ", np.linalg.norm(A, 'fro'));
  print("Norma-infinito: ", np.linalg.norm(A, np.inf));
  print("K(A): ", np.linalg.cond(A));
  x = np.array([1, 1])
  b = A @ x
  x2 = np.array([2, 0.5])
  b2 = np.array([3, 1.4985])
  print("A @ x2 = ", A @ x2, " = ", b2, " = b2")
  print("|| Δb || = ", np.linalg.norm(b - b2));
  print("|| Δx || = ", np.linalg.norm(x - x2));

# Metodi diretti

def es1(A = np.array ([ [3,-1, 1,-2], [0, 2, 5, -1], [1, 0, -7, 1], [0, 2, 1, 1]
  ], dtype=np.float_)):
  """
  Considera la matrice
  A = ⎛ 3 -1  1 -2⎞
      | 0  2  5 -1|
      | 1  0 -7  1|
      ⎝ 0  2  1  1⎠

  Crea il problema test in cui il vettore della soluzione esatta è
  x=(1,1,1,1)T e il vettore termine noto è b=Ax. Guarda l'help del modulo
  scipy.linalg.decomp_lu e usa una delle sue funzioni per calcolare la
  fattorizzazione LU di A con pivolting. Verifica la correttezza dell'output.
  Risolvi il sistema lineare con la funzione lu_solve del modulo decomp_lu
  oppure con scipy.linalg.solve_triangular. Visualizza la soluzione calcolata
  e valutane la correttezza.
  """
  print("K(A) = ", np.linalg.cond(A))
  x = np.array(np.ones(A.shape[0]))
  b = A @ x
  lu, piv = LUdec.lu_factor(A)
  l = np.tril(lu, -1) + np.diag(np.ones(lu.shape[0]))
  u = np.triu(lu)
  print("l @ u =\n", l, "\n@\n", u, "\n=\n", l @ u, "\n=\n", A, "\n = A");
  my_x = LUdec.lu_solve((lu, piv), b)
  print("Soluzione calcolata: ", my_x)
  print("Errore relativo: ", np.linalg.norm(abs(x - my_x)) / np.linalg.norm(x))

def es2():
  """
  Ripeti l'esercizio 1 sulla matrice di Hilbert, creata con
  A=scipy.linalg.hilbert(n) per n=5,…,10. In particolare:
  - calcola il numero di condizionamento di A
  - considera il vettore colonna  x=(1,…,1)T
  - calcola il corrispondente termine noto b per il sistema lineare Ax=b e la
    relativa soluzione x~ usando la fattorizzazione LU come nel caso
    precedente.
  """
  for n in range(5, 11):
    print("\nHilbert(", n, ")")
    es1(hilbert(n))

def LTrisol(L, b):
  n = b.size
  x = np.empty(n)
  for i in range(n):
    x[i] = (b[i] - L[i, 0 : i] @ x[0 : i] ) / L[i, i]
  return x

def UTrisol(U, b):
  n = b.size
  x = np.empty(n)
  for i in range(n - 1, -1, -1):
    x[i] = (b[i] - U[i, i + 1 : n] @ x[i + 1 : n] ) / U[i, i]
  return x

def es3():
  """
  Scrivi le due funzioni LTrisol() e UTrisol() per implementare i metodi di
  sostituzione all'avanti e all'indietro, poi:
  - usa la fattorizzazione P,L,U=LUdec.lu(A) sulla matrice degli esercizi
    precedenti;
  - risolvi i sistemi triangolari usando la tue funzioni.
  """
  A = np.array([ [3,-1, 1,-2], [0, 2, 5, -1], [1, 0, -7, 1], [0, 2, 1, 1] ])
  x = np.ones(A.shape[0])
  b = A @ x
  P, L, U = LUdec.lu(A)
  inv_P = np.linalg.inv(P)
  print(UTrisol(U, LTrisol(L, inv_P @ b)))

def es4():
  """
  Comprendere i seguenti codici che implementano la fattorizzazione LU senza
  pivoting.
  """
  def LU_fact_NOpiv(A):
    a = np.copy(A)
    n=a.shape[1]
    for k in range(n-1):
      if a[k, k] != 0:
        a[k+1:, k] = a[k+1:, k]/a[k,k]
        a1 = np.expand_dims(a[k+1:, k], 1)
        a2 = np.expand_dims(a[k, k+1:], 0)
        a[k+1:, k+1:] = a[k+1:, k+1:] - (a1 * a2)
    return a

def es5():
  """
  Calcola la fattorizzazione di Choleski sulla matrice A generata come
  A=np.array([[3,−1,1,−2],[0,2,5,−1],[1,0,−7,1],[0,2,1,1]],dtype=np.float)
  A=np.matmul(A,np.transpose(A))
  usando la funzione np.linalg.cholesky.
  Verifica la correttezza della fattorizzazione.
  Risolvi il sistema lineare Ax = b dove  x = (1,1,1,1)T.
  """
  A = np.array([ [3, -1, 1, -2], [0, 2, 5, -1], [1, 0, -7, 1], [0, 2, 1, 1] ], \
    np.float)
  A = A @ np.transpose(A)
  L = np.linalg.cholesky(A)
  print("L @ L^T =\n", L, "\n@\n", L.T,  "\n=\n", L @ L.T, "\n=\n", A, "\n= A")
  x = np.ones(A.shape[0])
  b = A @ x
  print(UTrisol(L.T, LTrisol(L, b)))

# Metodi iterativi

def IterativeMethod(A, b, x0, maxit, tol, xTrue, formula):
  n = np.size(x0)
  ite = 0 # number of iterations so far
  x = np.copy(x0) # estimated result
  relErr = np.empty((maxit, 1)) # relative errors for every attempt
  relErr[0] = np.linalg.norm(x0 - xTrue) / np.linalg.norm(xTrue)
  currRelDiff = tol + 1
  while currRelDiff > tol and ite < maxit:
    x_old = np.copy(x)
    for i in range(n):
      formula(A, b, x_old, x, i)
    ite += 1
    relErr[ite] = np.linalg.norm(x - xTrue) / np.linalg.norm(xTrue)
    currRelDiff = np.linalg.norm(x - x_old) / np.linalg.norm(x)
  return x, ite, relErr[:ite]

def JacobiFormula(A, b, x_old, x, i):
  n = b.size
  x[i] = (b[i] - A[i, 0 : i] @ x_old[0 : i] - A[i, i + 1 : n] \
         @ x_old[i + 1 : n]) / A[i, i]

def GaussSeidelFormula(A, b, x_old, x, i):
  n = b.size
  x[i] = (b[i] - A[i, 0 : i] @ x[0 : i] - A[i, i + 1 : n] \
         @ x_old[i + 1 : n]) / A[i, i]

def Jacobi(A, b, x0, maxit, tol, xTrue):
  return IterativeMethod(A, b, x0, maxit, tol, xTrue, JacobiFormula)

def GaussSeidel(A, b, x0, maxit, tol, xTrue):
  return IterativeMethod(A, b, x0, maxit, tol, xTrue, GaussSeidelFormula)

def es6():
  """
  Scrivi le funzioni Jacobi(A, b, x0, maxit, tol, xTrue) e
  GaussSeidel(A, b, x0, maxit, tol, xTrue) per implementare i metodi di Jacobi e
  di Gauss Seidel per la risoluzione di sistemi lineari con matrice a diagonale
  dominante. In particolare:
  - x0  sia l'iterato iniziale;
  - la condizione d'arresto sia dettata dal numero massimo di iterazioni
    consentite maxit e dalla tolleranza tol sulla differenza relativa fra due
    iterati successivi.
  Si preveda in input la soluzione esatta xTrue per calcolare l'errore relativo
  ad ogni iterazione. Entrambe le funzioni restituiscano in output:
  - la soluzione x;
  - il numero  k  di iterazioni effettuate;
  - il vettore  relErr  di tutti gli errori relativi.
  """
  pass

def es7():
  """
  Testa le due funzioni dell'esercizio precedente per risolvere il sistema
  lineare Ax = b dove A è la matrice 10x10
  A=⎛ 5  1  0  0 ... 0 ⎞
    | 1  5  1  0 ... 0 |
    | 0  1  ⋱  ⋱  ⋮  ⋮ |
    | 0  0  ⋱  5  1  0 |
    | 0  0 ... 1  5  1 |
    ⎝ 0  0 ... 0  1  5 ⎠
  e x=(1,1,...,1)T la soluzione esatta.
  Confronta i due metodi e grafica in un unico plot i due vettori relErr.
  """
  n = 10
  A = 5*np.eye(n)+ np.diag(np.ones(n - 1), 1) + np.diag(np.ones(n - 1), -1)
  xTrue = np.ones(n)
  b = A @ xTrue
  x0 = np.zeros(n)
  maxit = 200
  tol = 1.e-6
  xJacobi, kJacobi, relErrJacobi = Jacobi(A, b, x0, maxit, tol, xTrue)
  xGS, kGS, relErrGS, = GaussSeidel(A, b, x0, maxit, tol, xTrue)
  print("Jacobi: ", xJacobi, "\nGaussSeidel: ", xGS)
  fig, ax = plt.subplots()
  rangeJabobi = range (0, kJacobi)
  rangeGS = range(0, kGS)
  ax.plot(rangeJabobi, relErrJacobi, label='Jacobi', color = 'blue')
  ax.plot(rangeGS, relErrGS, label='Gauss Seidel', color = 'red')
  legend = ax.legend()
  plt.xlabel('Iterations')
  plt.ylabel('Relative error')
  plt.title('Iterative methods comparison')
  plt.show()


import numpy as np
from math import sqrt

X = [
    [0, 2]
]

Y = [
    [6, 10]
]

np_x = np.arange(9) - 2
np_y = np_x.reshape((3, 3))

print "np_x: {}".format(np_x)
print "np_y: {}".format(np_y)

def euclidean_distance(dx, dy):
    sum_ = 0
    for idx, value in enumerate(dx):
        sum_ += (abs(value - dy[idx]))**2
    return sqrt(sum_)

def euclidean_distance_np(dx):
    return np.linalg.norm(dx)

if __name__ == '__main__':
    print "Custom fn euclidean distance: {}".format(euclidean_distance(X[0], Y[0]))
    print "numpy linalg fn: {}".format(euclidean_distance_np(np_x))


import sympy
from sympy import *
from sympy.ntheory import isprime
import random
def choose_modulus(k,l,m):
	p1 = find_prime(k,l)
	p2 = find_prime(k,l)
	print(log(p1,2)+m < log(p2,2))
	return p1,p2
def choose_encryption_key(m):
	e = random.randrange(1,m);
	while not(gcd(e,totient(m)) == 1):
		e = random.randrange(1,m);
	return e
def compute_decryption_key(e,p1,p2):
	#d = e inverse mod phi(m) m = p1*p2
	mathStuff = (p1-1)*(p2-1) #they're prime numbers so magic, no pphi things
	d = mod_inverse(e,mathStuff)
	return d

def rsa_encrypt(P,e,m):
	return power_mod(P, e, m)

def rsa_decrypt(C,d,m): ####
	return power_mod(C,d,m)

def rsa_crack(C,e,m):
	phiModulusValue = totient(m) #magic
	decryptionKey = mod_inverse(e, phiModulusValue)
	return rsa_decrypt(C, decryptionKey, m)

def power_mod(a,b,m):
	#x = (a%m)**(b%m) #mod each before power it also works
	#return x % m

	return pow(a,b,m)
def string_to_int(s):
	#returns an integer from the ASCII encoding of s
	return int.from_bytes(s.encode(),'big')
def int_to_string(n):
	#returns a string from the integer n
	return n.to_bytes((n.bit_length()+7)//8,'big').decode()
def find_prime(k,l):
	num = random.randrange(k,l)  #generate a random integer in the range
	# if it's even
	if(num % 2 == 0):
		num = num + 1;
	while not(isprime(num)):
		num = num + 2
		if(num > l):
			temp = num % k
			num = k + temp
	if(num >= k and num <= l):
		#print(num)
		return num

def mod_inverse(b, n):
	""" Return the modular inverse of b mod n
	 or None if gcd(b,n) > 1 """
	g, x, _ = xgcd(b, n)
	if g == 1:
		return x % n

modulus = (17*19)
encryptionKey = 23
number = 2

#d = e inverse mod phi (15) - 5 * 3, so we can find phi(15), (4*2) = 8
#d = mod_inverse(encryptionKey, totient(modulus))
d = compute_decryption_key(encryptionKey, 17, 19)
string1 = rsa_encrypt(number, encryptionKey, modulus)
print(string1)
string2 = rsa_decrypt(string1, d, modulus)
print(string2)
decryptMe = rsa_crack(string1, encryptionKey, modulus)
print(decryptMe)


# import matplotlib as mpl
# mpl.use('Qt5Agg')
import matplotlib.pyplot as plt
import pandas as pd
from math import log, exp
import numpy as np
from matplotlib.ticker import MultipleLocator, FormatStrFormatter

def decay(half_life, curr_nuclii, max_time, timestep, current_time, original_nuclii, rad_list=[]):
    decay_const = log(0.5) / half_life
    if current_time <= max_time:
        remaining_nuclii = curr_nuclii * exp(decay_const * timestep)
        rad_list.append((remaining_nuclii / original_nuclii))
        return decay(half_life=half_life, curr_nuclii=remaining_nuclii, max_time=max_time, timestep=timestep,
                     current_time=current_time + timestep, original_nuclii=original_nuclii, rad_list=rad_list)
    else:
        return rad_list

hf_half_life = 8.9 * 10**6
al_half_life = 7.17 * 10**5
fe_half_life = 3.0 * 10**5
max_time = 100 * 10**6
original_hf = 100
original_al = 100
original_fe = 100
timestep = 1 * 10**6
time_list = [i / (1 * 10**6) for i in np.arange(0, max_time + timestep, timestep)]

hf_decay = decay(half_life=hf_half_life, curr_nuclii=original_hf, max_time=max_time, timestep=timestep,
                 current_time=timestep, original_nuclii=original_hf, rad_list=[original_hf / original_hf])
al_decay = decay(half_life=al_half_life, curr_nuclii=original_al, max_time=max_time, timestep=timestep,
                 current_time=timestep, original_nuclii=original_al,  rad_list=[original_al / original_al])
fe_decay = decay(half_life=fe_half_life, curr_nuclii=original_fe, max_time=max_time, timestep=timestep,
                 current_time=timestep, original_nuclii=original_fe,  rad_list=[original_fe / original_fe])

fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(time_list, hf_decay, linewidth=2.0, label='182Hf (hl = 8.9 * 10^6 y)')
ax.plot(time_list, al_decay, linewidth=2.0, label='26Al (hl = 7.17 * 10^5 y)')
ax.plot(time_list, fe_decay, linewidth=2.0, label='60Fe (hl = 3.0 * 10^5 y)')
ax.set_xlabel("Time (my)")
ax.set_ylabel("Relative Isotope Abundance (remaining/original)")
ax.set_title("Isotope Decay")
ax.legend(loc='upper right')
minorLocator = MultipleLocator((timestep * 5) / 10**6)
ax.xaxis.set_minor_locator(minorLocator)
ax.grid()

plt.show()


# Regularized Logistic Regression
import config
from sklearn.preprocessing import PolynomialFeatures
import numpy as np

# Hypothesis Function (Sigmoid)
def hypothesis(thetarg, x):
    return 1 / (1 + np.exp(-np.dot(x, thetarg)))

# Cost function used to calculate the error between hypothetic and actual value over m training examples
def cost(x, y, thetarg, msize, reg):
    return float((1 / msize) * (
            -np.dot(y.T, np.log(hypothesis(thetarg, x))) - np.dot((1 - y).T, np.log(1 - hypothesis(thetarg, x))))) \
           + float((reg / (2 * m)) * np.dot(theta[1:].T, theta[1:]))

# Gradient Descent method to minimize cost function in configurable alpha and iterations
def gradient_descent(x, y, thetarg, msize, reg):
    jvec = []
    theta_history = []
    for i in range(config.num_iterations):
        theta_history.append(list(thetarg[:, 0]))
        jvec.append(cost(x, y, thetarg, msize, reg))

        thetarg[0] = thetarg[0] - (config.alpha / msize) * np.sum((hypothesis(thetarg, x) - y) *
                                                                  np.array(x[:, 0]).reshape(msize, 1))
        for j in range(1, len(thetarg)):
            thetarg[j] = thetarg[j] - config.alpha * (
                    (1 / msize) * np.sum((hypothesis(thetarg, x) - y) * np.array(x[:, j]).reshape(msize, 1)) +
                    (reg / msize) * thetarg[j])
    return thetarg

# Prediction to classify cancer as benign or malignant
def predict(theta_final, input_matrix):
    h = hypothesis(theta_final, input_matrix)
    pred = []
    for i in h:
        if i > 0.5:
            pred.append(1)
        else:
            pred.append(0)
    return pred

# Accuracy check on the training dataset
def accuracy(prediction, decision):
    count = 0
    for i, j in zip(prediction, decision):
        if i == j:
            count = count + 1
    return 100 * (count / len(prediction))

# Loading test1 and test2 as features for training data with decision label
data = np.loadtxt(config.path, delimiter=',', usecols=[0, 1, 2], unpack=True)
X = np.transpose(np.array(data[:-1]))

# Converts input feature to 28 features using PolynomialFeatures
poly = PolynomialFeatures(6)
XX = poly.fit_transform(X)
inmatrix = np.insert(X, 0, 1, axis=1)
outmatrix = np.transpose(np.array(data[-1:]))
m = outmatrix.size
theta = np.zeros([XX.shape[1], 1])

# Getting best parameters by running gradient descent
final_theta = gradient_descent(XX, outmatrix, theta, m, config.reg)

# Prediction and accuracy of the classifier
predict_label = predict(final_theta, XX)
accuracy = accuracy(predict_label, outmatrix)


#!/usr/bin/env python
"""
Consider a “closed population” without demographics (no births, deaths, or migration). The scenario we have in mind is
a large naive population into which a low level of infectious agent is introduced and where the resulting epidemic
occurs sufficiently quickly that demographic processes are not influential. We also assume homogeneous mixing,
whereby intricacies affecting the pattern of contacts are discarded, yielding βSI as the transmission term. Given the
premise that underlying epidemiological rates are constant, we get the following SIR equations:
SIR equations

Parameters
β 	is the transmission rate and incorporates the encounter rate between susceptible and infectious individuals together with the probability of transmission.
γ 	is called the removal or recovery rate, though often we are more interested in its reciprocal (1/γ) which determines the average infectious period.
S(0) 	is the initial proportion of the population that are susceptible.
I(0) 	is the initial proportion of the population that are infectious.
All rates are specified in days.

Requirements.
All parameters must be positive, and S(0)+I(0) ≤ 1

"""
####################################################################
###    This is the PYTHON version of program 2.1 from page 19 of   #
### "Modeling Infectious Disease in humans and animals"            #
### by Keeling & Rohani.										   #
###																   #
### It is the simple SIR epidemic without births or deaths.        #
####################################################################

###################################
### Written by Ilias Soumpasis    #
### ilias.soumpasis@ucd.ie (work) #
### ilias.soumpasis@gmail.com	  #
###################################

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

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)

# Plotting
pl.subplot(211)
pl.plot(RES[:, 0], "-g", label="Susceptibles")
pl.plot(RES[:, 2], "-k", label="Recovereds")
pl.legend(loc=0)
pl.title("Program_2_1.py")
pl.xlabel("Time")
pl.ylabel("Susceptibles and Recovereds")
pl.subplot(212)
pl.plot(RES[:, 1], "-r", label="Infectious")
pl.xlabel("Time")
pl.ylabel("Infectious")
pl.show()


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

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

from utilities import str2fun

def get_q(a, b, dphi):
    return max(abs(dphi(a)), abs(dphi(b)))

def iteration_method(init_dict, count_it=False):
    interval, eps = init_dict['interval'], init_dict['eps']
    a, b = interval[0], interval[1]

    phi, dphi = str2fun(init_dict["phi"], der_num=1)

    x_prev = (b - a) / 2
    q = get_q(a, b, dphi)
    cnt_iter = 0

    while True:
        cnt_iter += 1

        x = phi(x_prev)

        finish_iter = abs(x - x_prev) * q / (1 - q)
        if finish_iter <= eps:
            break

        x_prev = x

    return x if not count_it else (x, cnt_iter)

def newton_method(init_dict, count_it=False):
    interval, eps = init_dict['interval'], init_dict['eps']
    a, b = interval[0], interval[1]

    x_prev = b
    cnt_iter = 0

    f, df = str2fun(init_dict["f"], der_num=1)

    while True:
        cnt_iter += 1

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

        finish_iter = abs(f(x) - f(x_prev))
        if finish_iter <= eps:
            break

        x_prev = x

    return x if not count_it else (x, cnt_iter)

def main(eps=0.01, test=False):
    """Решение нелинейного уравнения методами простой итерации и Ньютона

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

    init_dict = {
        "f": "exp(x) - 2 * x - 2",
        "phi": "log(2 + 2 * x)",
        "interval": (1, 2),
        "eps": eps
    }

    print("eps =", init_dict["eps"])

    print("Функция f(x) =", init_dict["f"])
    print("Функция phi(x) =", init_dict["phi"])
    x, it = newton_method(init_dict, count_it=True)
    print(f"\nРешение методом Ньютона:\t\t\t{x:.15f} за {it} ит.")
    x, it = iteration_method(init_dict, count_it=True)
    print(f"Решение методом простой итерации:\t{x:.15f} за {it} ит.")

    if test:
        powers = np.arange(0, 11, .5)  # порядки эпсилонов (eps = 10 ** -p)
        mi = []
        mn = []
        for p in powers:
            init_dict["eps"] = 10 ** -p
            mi.append(iteration_method(init_dict, count_it=True)[1])
            mn.append(newton_method(init_dict, count_it=True)[1])

        plt.plot(powers, mi, label="Метод простых итераций")
        plt.plot(powers, mn, label="Метод Ньютона")
        plt.legend()
        plt.title("Зависимость числа итераций от точности")
        plt.xlabel("Порядок точности ($\\epsilon = 10^{-порядок}}$)")
        plt.ylabel("Число итераций")
        plt.xticks(powers[::2])
        plt.yticks(range(0, max(mi[-1], mn[-1]), 5))
        plt.grid(True)
        plt.savefig("benchmark.jpg", dpi=300)
        plt.show()

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


#!/usr/bin/python
"""
author  : Bhekimpilo Ndhlela
author  : 18998712
module  : Applied Mathematics(Numerical Methods) TW324
task    : computer assignment 04 question 1(a)-(b)
since   : Friday-23-03-2018
"""
def question_a(steps, x=0.0, debug=False):
    fp = lambda x, h : ((-f(x+2*h)+8*f(x+h)-8*f(x-h)+f(x-2*h))/(12*h))
    f = lambda x : sqrt(1 - 2 * sin(x))
    exact = [abs(fp(x, h) + 1.0) for h in steps]
    if debug is True:
        print "DEBUG MODE: [ON] QUESTION 1 a.)"
        print "i","\t" ,"Step Size", "\t", "Exact Error"
        for i, (h, err) in enumerate(zip(steps, exact)):
            print (i+1), "\t","{:.7f}".format(h), "\t", "{:.10f}".format(err)
    return exact

def question_b(steps, M=11.0, debug=False):
    machine_eps = finfo(float).eps
    bound = [(18.0 * machine_eps)/(12.0*h) + M*(h**4) for h in steps]
    if debug is True:
        print "DEBUG MODE: [ON] QUESTION 3 bii.)"
        print "i","\t" ,"Step Size", "\t", "Bound"
        for i, (h, err) in enumerate(zip(steps, bound)):
            print (i+1), "\t","{:.7f}".format(h), "\t", "{:.10f}".format(err)
    return bound

def plot_err_functs(steps, exact, bound):
    #loglog plot to display the error as function of the step size
    plt.title("Plot of the Exact Error and Bound as h(Step size) Changes")
    plt.xlabel("h (Step Size)")
    plt.ylabel("Exact vs Bound")
    plt.yscale('log')
    plt.xscale('log')
    plt.plot(steps, exact, "k-", label="Exact")
    plt.plot(steps, bound, "k--", label="Bound")
    plt.legend(bbox_to_anchor=(.65, .9))
    plt.show()

if __name__ == "__main__":
    from numpy import (abs, cos, sin, sqrt, logspace)
    from numpy import (finfo, float)
    import matplotlib.pyplot as plt
    from math import pow

    steps = logspace(-7, -1, num=100)
    exact, bound = question_a(steps), question_b(steps)
    plot_err_functs(steps, exact, bound)
else:
    from sys import exit
    exit("USAGE: python q3.py")


import numpy as np

# Do not import sklearn!

def compute_confusion_matrix(actual, predictions):
    """
    Given predictions (an N-length numpy vector) and actual labels (an N-length
    numpy vector), compute the confusion matrix. The confusion
    matrix for a binary classifier would be a 2x2 matrix as follows:

    [
        [true_negatives, false_positives],
        [false_negatives, true_positives]
    ]

    YOU DO NOT NEED TO IMPLEMENT CONFUSION MATRICES THAT ARE FOR MORE THAN TWO
    CLASSES (binary).

    Compute and return the confusion matrix.

    Args:
        actual (np.array): predicted labels of length N
        predictions (np.array): predicted labels of length N

    Output:
        confusion_matrix (np.array): 2x2 confusion matrix between predicted and actual labels

    """

    if predictions.shape[0] != actual.shape[0]:
        raise ValueError("predictions and actual must be the same length!")

    true_positives = 0
    true_negatives = 0
    false_positives = 0
    false_negatives = 0

    for i in range(len(predictions)):
        if predictions[i] == 1 and actual[i] == 1:
            true_positives += 1
        elif predictions[i] == 0 and actual[i] == 0:
            true_negatives += 1
        elif predictions[i] == 1 and actual[i] == 0:
            false_positives += 1
        else:
            false_negatives += 1

    confusion_matrix = np.array([[true_negatives, false_positives], [false_negatives, true_positives]])

    return confusion_matrix

def compute_accuracy(actual, predictions):
    """
    Given predictions (an N-length numpy vector) and actual labels (an N-length
    numpy vector), compute the accuracy:

    Hint: implement and use the compute_confusion_matrix function!

    Args:
        actual (np.array): predicted labels of length N
        predictions (np.array): predicted labels of length N

    Output:
        accuracy (float): accuracy
    """
    if predictions.shape[0] != actual.shape[0]:
        raise ValueError("predictions and actual must be the same length!")

    matrix = compute_confusion_matrix(actual, predictions)
    true_positives = matrix[1, 1]
    true_negatives = matrix[0, 0]

    accuracy = (true_positives + true_negatives) / np.sum(matrix)

    return accuracy

def compute_precision_and_recall(actual, predictions):
    """
    Given predictions (an N-length numpy vector) and actual labels (an N-length
    numpy vector), compute the precision and recall:

    https://en.wikipedia.org/wiki/Precision_and_recall

    You MUST account for edge cases in which precision or recall are undefined
    by returning np.nan in place of the corresponding value.

    Hint: implement and use the compute_confusion_matrix function!

    Args:
        actual (np.array): predicted labels of length N
        predictions (np.array): predicted labels of length N

    Output:
        precision (float): precision
        recall (float): recall
    """
    if predictions.shape[0] != actual.shape[0]:
        raise ValueError("predictions and actual must be the same length!")

    matrix = compute_confusion_matrix(actual, predictions)
    true_positives = matrix[1, 1]
    false_positives = matrix[0, 1]
    false_negatives = matrix[1, 0]

    # if the denominator sum is 0 then pass nan
    precision = true_positives / (true_positives + false_positives) if true_positives + false_positives else np.nan
    recall = true_positives / (true_positives + false_negatives) if true_positives + false_negatives else np.nan

    return precision, recall

def compute_f1_measure(actual, predictions):
    """
    Given predictions (an N-length numpy vector) and actual labels (an N-length
    numpy vector), compute the F1-measure:

    https://en.wikipedia.org/wiki/Precision_and_recall#F-measure

    Because the F1-measure is computed from the precision and recall scores, you
    MUST handle undefined (NaN) precision or recall by returning np.nan. You
    should also consider the case in which precision and recall are both zero.

    Hint: implement and use the compute_precision_and_recall function!

    Args:
        actual (np.array): predicted labels of length N
        predictions (np.array): predicted labels of length N

    Output:
        f1_measure (float): F1 measure of dataset (harmonic mean of precision and
        recall)
    """
    if predictions.shape[0] != actual.shape[0]:
        raise ValueError("predictions and actual must be the same length!")

    # 2 * p*r/p+r
    precision, recall = compute_precision_and_recall(actual, predictions)

    f1_measure = 2 * (precision * recall)/(precision + recall) if precision + recall else np.nan

    return f1_measure


#Code written by Zachary Montgomery.
#Altered by Cory Goates to handle "NaN" values.
#Altered by Cory Goates to allow the user to specify if some power coefficients
#should be set to zero.

import numpy as np
from scipy.optimize import least_squares

def poly_fit(n,xx,yy,forcezero=None):
	good = np.where(~(np.isnan(xx)) & ~(np.isnan(yy)))
	x = np.copy(xx[good])
	y = np.copy(yy[good])

	if forcezero is None:
		# initialize variables
		A = np.zeros((n,n))
		b = np.zeros(n)
		powers = range(n)
	else:
		size = n - len(forcezero)
		A = np.zeros((size,size))
		b = np.zeros(size)
		powers = np.delete(range(n), forcezero)

	for i,e in enumerate(powers):
		for j,f in enumerate(powers):
			A[i,j] = sum( x ** float(e + f) )
		b[i] = sum( y * x ** float(e) )

	a = np.linalg.solve(A,b)

	if forcezero is not None:
		for zero in forcezero:
			a = np.insert(a,zero,0.)
	r = r2(a,x,y)
	return a, r

def poly_func(a,x):
	f = 0.
	n = len(a)
	for i in range(n):
		f += a[i] * x ** float(i)
	#print(f)
	return f

def r2(aa,xx,yy):
	a = np.copy(aa)
	x = np.copy(xx)
	y = np.copy(yy)
	n = len(y)
	y_ = sum(y) / float(n)
	SSt = sum((y-y_)**2.)
	f = np.zeros(n)
	for i in range(n):
		f[i] = poly_func(a,x[i])
	SSr = sum((y-f)**2.)
	return 1. - SSr / SSt

def force_good_poly_fit(n,xx,yy,tol,sym=False):
	x = np.copy(xx)
	y = np.copy(yy)

	a, r = poly_fit(n,x,y,sym=sym)
	x_bad = []
	y_bad = []
	print('R2 is {:7.5f}'.format(r))
	while r < tol:

		e_loc = 0
		l = y.size
		e_max = 0.
		for i in range(l):
			e = abs(poly_func(n,a,x[i]) - y[i])
			if e > e_max:
				e_max = e
				e_loc = i
		x_bad.append(x[e_loc])
		y_bad.append(y[e_loc])
		x = np.delete(x,e_loc)
		y = np.delete(y,e_loc)

		a, r = poly_fit(n,x,y)
		print('R2 is {:7.5f}'.format(r))

	mx = max(y)

	return a, x, y, x_bad, y_bad, r, mx

def constrainedFit(n,xx,yy,forcezero=None):
# Fits a polynomial, ensuring the highest-order coefficient is negative so that end behavior is as expected.
	def fun(a):
		res = poly_func(a,xx)-yy
		res[np.isnan(res)] = 0
		return res

	x0 = -0.01*np.ones(n).astype(float)
	upperBounds = np.full(n,np.inf)
	upperBounds[-1] = 0 # Costrain last coef to be negative
	result = least_squares(fun,x0,bounds=(-np.inf,upperBounds))
	return result.x, result.cost


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

import numpy as np

def spatial_kernel(X, Y, x, y, sigma_center, sigma_surround, inverse=1):
    """Creates a spatial kernel for the LGN cells' receptive field.

    The kernel is defined as the difference of Gaussians.

    Reference: Eq 2.45, Theoretical Neuroscience Dayan & Abbot (2011).

    Args:
        X (ndarray of floats): x coordinates of the mesh grid.
        Y (ndarray of floats): y coordinates of the mesh grid.
        x (float): x coordinate of the position of the kernel on the grid.
        y (float): y coordinate of the position of the kernel on the grid.
        sigma_center (float): size of the central region (should be less than
            sigma_surround).
        sigma_surround (float): size of the surround region (should be greater
            than sigma_center).
        inverse (int, optional): determines if the type of the kernel (ON/OFF).
            Takes values -1 or 1 only. If the given value is 1 the kernel is of
            type ON, otherwise OFF. Defaults to 1.

    Returns:
        ndarray of float: the spatial kernel, normalized so that for the perfect
            stimulus (for the case of type ON:1 in the center and -1 in the
            surround region) the firing rate is equal to 1.
    """

    Z = ((X - x)**2 + (Y - y)**2)  # move center

    center = (17.0 / (2*np.pi * (sigma_center**2))) * \
        np.exp(-Z / (2*(sigma_center**2)))

    surround = (16.0 / (2*np.pi * (sigma_surround**2))) * \
        np.exp(-Z / (2*(sigma_surround**2)))

    Z = center - surround

    # points outside circle/receptive field center are set to zero
    pts = (X-x)**2+(Y-y)**2 >= (2*sigma_surround)**2
    Z[pts] = 0

    # normalize by multiplying with scale factor(dividing number of points/area)
    return (Z * inverse) / np.sum(np.absolute(Z))

if __name__ == "__main__":
    import plots
    from matplotlib import pyplot as plt

    dx = dy = 0.05
    lx = ly = 5.0
    x = np.arange(-lx/2, lx/2, dx)
    y = np.arange(-ly/2, ly/2, dy)
    X, Y = np.meshgrid(x, y)
    xp = yp = 0

    sigma_center = 10.6 * (1/60)
    sigma_surround = 31.8 * (1/60)

    k = spatial_kernel(X, Y, xp, yp, sigma_center, sigma_surround, inverse=1)
    plots.plot_kernel_3D(k, X, Y)
    plt.show()


"""
Locally Weighted Regression

References
----------
CS229 Lecture notes1, Chapter 3 Locally weighted linear regression, Prof. Andrew Ng
http://cs229.stanford.edu/notes/cs229-notes1.pdf

weighted least squares and locally weighted linear regression
http://www.dsplog.com/2012/02/05/weighted-least-squares-and-locally-weighted-linear-regression/
"""

import matplotlib.pyplot as plt
import numpy as np

from datasets import load

class LocallyWeightedRegression:
    def __init__(self, samples, targets):
        """
        :param samples: {array-like, sparse matrix}, shape = [n_samples, n_features]
                        Training vectors, where n_samples is the number of samples and
                        n_features is the number of features.
        :param targets: array-like, shape = [n_samples]
                        Target values.
        """
        self.samples = samples
        self.targets = targets
        self.thetas = []
        self.estimation = []

    def fit_predict(self, estimation_samples, tau=1):
        """
        fit estimation_samples with locally weighted regression according to samples and target

        :param estimation_samples : {array-like, sparse matrix}, shape = [n_samples, n_features]
            Estimation vectors, where n_samples is the number of samples and
            n_features is the number of features.

        :param tau : float, tau >= 0
            The the bandwidth parameter tau controls how quickly the weight of a training example falls off with
             distance of its x(i) from the query point x.
        """
        if tau < 0:
            print "tau should be greater than 0"
            return [], []

        self.thetas = []
        self.estimation = []

        for x in estimation_samples:
            # calculate weights that depend on the particular vector x
            weights = np.exp((-(self.samples - x) * (self.samples - x)).sum(axis=1) / (2 * tau ** 2))
            diagonal_weights = np.diag(weights)  # diagonal matrix with weights
            samples_times_weights = np.dot(self.samples.T, diagonal_weights)
            a = np.dot(samples_times_weights, self.samples)
            b = np.dot(samples_times_weights, self.targets)

            self.thetas.append(np.linalg.lstsq(a, b)[0])  # calculate thetas for given x with: A^-1 * b
            self.estimation.append(np.dot(x, self.thetas[-1]))  # calculate estimation for given x and thetas

if __name__ == '__main__':
    samples, targets, _, _, _ = load.lwlr()
    plt.scatter(samples[:, 1], targets)  # plot train data

    lwr = LocallyWeightedRegression(samples, targets)

    taus = [1, 10, 25]
    color = ["r", "g", "b"]
    for i, tau in enumerate(taus):
        lwr.fit_predict(samples, tau=tau)
        plt.plot(samples[:, 1], lwr.estimation, c=color[i])  # plot estimations
    plt.show()


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

Numpy FFT example

Based on:
    https://www.ritchievink.com/blog/2017/04/23/understanding-the-fourier-transform-by-example/

"""

import numpy as np
import matplotlib.pyplot as plt

# Create sinusoidal data (two mixed signals)
time_points = np.linspace(0, 0.5, 500)  # seconds
f1 = 40  # Hz
f2 = 90  # Hz
amplitude1 = 1
amplitude2 = 0.5
signal = amplitude1 * np.sin(f1 * 2 * np.pi * time_points) + amplitude2 * np.sin(f2 * 2 * np.pi * time_points)

plt.ylabel("Amplitude")
plt.xlabel("Time [s]")
plt.plot(time_points, signal)
plt.show()

fft = np.fft.fft(signal)

# Second half of the fft result is the conjugate of the first half (because fft input is real, not complex)
# Hence, contains no additional information
for i in range(2):
    print("Value at index {}:\t{}".format(i, fft[i + 1]), "\nValue at index {}:\t{}".format(fft.size -1 - i, fft[-1 - i]))

# Create array of frequency points
dt = time_points[1] - time_points[0]  # sampling interval
N = signal.size
f = np.linspace(0, 1 / dt, N) # 1/dt = frequency

# Take first half of fft data:
f_half = f[:N // 2]
fft_half = np.abs(fft)[:N // 2]
fft_half = fft_half * 1 / N  # normalise

# Plot
plt.ylabel("Amplitude")
plt.xlabel("Frequency [Hz]")
plt.bar(f_half, fft_half, width=1.5)
plt.show()


#  Newton's method in 1D
import numpy as np

def f(x):
    return 2.0 - x*x

def d(x):
    return -2.0*x

x = 1.0

for i in range(5):
    x = x - f(x)/d(x)
    print("%2d: %0.16f" % (i+1,x))

print()
print("NumPy says sqrt(2) = %0.16f for a deviation of %0.16f" % (np.sqrt(2), np.abs(np.sqrt(2)-x)))
print()


# -*- coding: utf-8 -*-
"""
Created on Mon Sep 21 22:40:38 2020

@author: artmenlope

Examples using the cosine function and the
complex function from the Wikipedia Domain
Coloring example found at
https://en.wikipedia.org/wiki/File:Domain_coloring_x2-1_x-2-i_x-2-i_d_x2%2B2%2B2i.xcf
"""

import numpy as np
import matplotlib.pyplot as plt
import cplotting_tools as cplt

plt.close("all")

#============================== Wiki function.

N = 100
lim = 3
x, y = np.meshgrid(np.linspace(-lim,lim,N),
                   np.linspace(-lim,lim,N))

z = x + 1j*y

f = (z**2-1)*(z-2-1j)**2/(z**2+2+2j)
pts = [-1, 1, 2+1j, 2**(3/4)*np.exp(1j*(5*np.pi/8)), 2**(3/4)*np.exp(1j*(5*np.pi/8+np.pi))]

cplt.domain_coloring(x, y, f, cmap="hsv")
# cplt.domain_coloring(x, y, f, cmap="twilight_r")

# cplt.domain_coloring_illuminated(x, y, f)
# cplt.domain_coloring_illuminated(x, y, f, log_brightness=False)

# cplt.complex_plot3D(x, y, f, log_mode=False)
# cplt.complex_plot3D(x, y, f, log_mode=True)

# cplt.plot_re_im(x, y, f, cmap="twilight", contour=False, alpha=0.9)
# cplt.plot_re_im(x, y, f, cmap="twilight", contour=True, alpha=0.9)

# cplt.complex_streamplot(x, y, f, mod_as_linewidths=True, cmap="twilight_r", density=2, scatterpoints=pts) #twilight_r

# cplt.complex_contour(x, y, f, mode="modulus", levels=np.arange(0,21,1), lw=2, cmap="coolwarm", scatterpoints=pts) # "coolwarm" imshow with "copper" contour looks good usually. "bone" imshow with "coolwarm" contour is also fine

#============================== cosine function.

# N = 40
# lim = 6
# x, y = np.meshgrid(np.linspace(-lim,lim,N),
#                    np.linspace(-lim,lim,N))

# z = x + 1j*y

# f = np.cos(z)

# cplt.complex_vector_field(x, y, f, norm=False)
# cplt.complex_vector_field(x, y, f, norm=True)
# cplt.complex_vector_field(x, y, f, cmap="hsv", norm=False)
# cplt.complex_vector_field(x, y, f, cmap="hsv", norm=True)