import numpy as np

def quantile(sample: np.ndarray, p: float, type: int = 7,
             sorted: bool = False, interpolate: bool = True) -> float:
    """
    Estimate a desired quantile of a univariate distribution from a vector of samples

    Parameters
    ----------
    sample
        A 1D vector of values
    p
        The desired quantile in (0,1)
    type
        The method for computing the quantile.
        See https://wikipedia.org/wiki/Quantile#Estimating_quantiles_from_a_sample
    sorted
        Whether or not the vector is already sorted into ascending order
    interpolate
        Whether to interpolate the desired quantile.

    Returns
    -------
    An estimate of the quantile

    """
    N = len(sample)
    if N == 0:
        raise ValueError("Cannot compute quantiles with zero samples.")

    if len(sample.shape) != 1:
        raise ValueError("Quantile estimation only supports vectors of univariate samples.")
    if not 1/N <= p <= (N-1)/N:
        raise ValueError(f"The {p}-quantile should not be estimated using only {N} samples.")

    sorted_sample = sample if sorted else np.sort(sample)

    if type == 6:
        h = (N+1)*p
    elif type == 7:
        h = (N-1)*p + 1
    elif type == 8:
        h = (N+1/3)*p + 1/3
    else:
        raise ValueError("type must be an int with value 6, 7 or 8.")
    h_floor = int(h)
    quantile = sorted_sample[h_floor-1]
    if h_floor != h and interpolate:
        quantile += (h - h_floor)*(sorted_sample[h_floor]-sorted_sample[h_floor-1])

    return float(quantile)


#!/usr/bin/env python
import numpy as np

x = np.array([[1,2],[3,4]], dtype=np.float64)
y = np.array([[5,6],[7,8]], dtype=np.float64)

# Elementwise sum; both produce the array
# [[ 6.0  8.0]
#  [10.0 12.0]]
print x + y
print np.add(x, y)

# Elementwise difference; both produce the array
# [[-4.0 -4.0]
#  [-4.0 -4.0]]
print x - y
print np.subtract(x, y)

# Elementwise product; both produce the array
# [[ 5.0 12.0]
#  [21.0 32.0]]
print x * y
print np.multiply(x, y)

# Elementwise division; both produce the array
# [[ 0.2         0.33333333]
#  [ 0.42857143  0.5       ]]
print x / y
print np.divide(x, y)

# Elementwise square root; produces the array
# [[ 1.          1.41421356]
#  [ 1.73205081  2.        ]]
print np.sqrt(x)

x = np.array([[1,2],[3,4]])
y = np.array([[5,6],[7,8]])

v = np.array([9,10])
w = np.array([11, 12])

# Inner product of vectors; both produce 219
print v.dot(w)
print np.dot(v, w)

# Matrix / vector product; both produce the rank 1 array [29 67]
print x.dot(v)
print np.dot(x, v)

# Matrix / matrix product; both produce the rank 2 array
# [[19 22]
#  [43 50]]
print x.dot(y)
print np.dot(x, y)

x = np.array([[1,2],[3,4]])

print np.sum(x)  # Compute sum of all elements; prints "10"
print np.sum(x, axis=0)  # Compute sum of each column; prints "[4 6]"
print np.sum(x, axis=1)  # Compute sum of each row; prints "[3 7]"

x = np.array([[1,2], [3,4]])
print x    # Prints "[[1 2]
           #          [3 4]]"
print x.T  # Prints "[[1 3]
           #          [2 4]]"

# Note that taking the transpose of a rank 1 array does nothing:
v = np.array([1,2,3])
print v    # Prints "[1 2 3]"
print v.T  # Prints "[1 2 3]"

# We will add the vector v to each row of the matrix x,
# storing the result in the matrix y
x = np.array([[1,2,3], [4,5,6], [7,8,9], [10, 11, 12]])
v = np.array([1, 0, 1])
y = np.empty_like(x)   # Create an empty matrix with the same shape as x

# Add the vector v to each row of the matrix x with an explicit loop
for i in range(4):
    y[i, :] = x[i, :] + v

# Now y is the following
# [[ 2  2  4]
#  [ 5  5  7]
#  [ 8  8 10]
#  [11 11 13]]
print y

# We will add the vector v to each row of the matrix x,
# storing the result in the matrix y
x = np.array([[1,2,3], [4,5,6], [7,8,9], [10, 11, 12]])
v = np.array([1, 0, 1])
vv = np.tile(v, (4, 1))  # Stack 4 copies of v on top of each other
print vv                 # Prints "[[1 0 1]
                         #          [1 0 1]
                         #          [1 0 1]
                         #          [1 0 1]]"
y = x + vv  # Add x and vv elementwise
print y  # Prints "[[ 2  2  4
         #          [ 5  5  7]
         #          [ 8  8 10]
         #          [11 11 13]]"

# We will add the vector v to each row of the matrix x,
# storing the result in the matrix y
x = np.array([[1,2,3], [4,5,6], [7,8,9], [10, 11, 12]])
v = np.array([1, 0, 1])
y = x + v  # Add v to each row of x using broadcasting
print y  # Prints "[[ 2  2  4]
         #          [ 5  5  7]
         #          [ 8  8 10]
         #          [11 11 13]]"

# Compute outer product of vectors
v = np.array([1,2,3])  # v has shape (3,)
w = np.array([4,5])    # w has shape (2,)
# To compute an outer product, we first reshape v to be a column
# vector of shape (3, 1); we can then broadcast it against w to yield
# an output of shape (3, 2), which is the outer product of v and w:
# [[ 4  5]
#  [ 8 10]
#  [12 15]]
print np.reshape(v, (3, 1)) * w

# Add a vector to each row of a matrix
x = np.array([[1,2,3], [4,5,6]])
# x has shape (2, 3) and v has shape (3,) so they broadcast to (2, 3),
# giving the following matrix:
# [[2 4 6]
#  [5 7 9]]
print x + v

# Add a vector to each column of a matrix
# x has shape (2, 3) and w has shape (2,).
# If we transpose x then it has shape (3, 2) and can be broadcast
# against w to yield a result of shape (3, 2); transposing this result
# yields the final result of shape (2, 3) which is the matrix x with
# the vector w added to each column. Gives the following matrix:
# [[ 5  6  7]
#  [ 9 10 11]]
print (x.T + w).T
# Another solution is to reshape w to be a row vector of shape (2, 1);
# we can then broadcast it directly against x to produce the same
# output.
print x + np.reshape(w, (2, 1))

# Multiply a matrix by a constant:
# x has shape (2, 3). Numpy treats scalars as arrays of shape ();
# these can be broadcast together to shape (2, 3), producing the
# following array:
# [[ 2  4  6]
#  [ 8 10 12]]
print x * 2


# coding: utf-8

# $\newcommand{\xv}{\mathbf{x}}
# \newcommand{\Xv}{\mathbf{X}}
# \newcommand{\yv}{\mathbf{y}}
# \newcommand{\zv}{\mathbf{z}}
# \newcommand{\av}{\mathbf{a}}
# \newcommand{\Wv}{\mathbf{W}}
# \newcommand{\wv}{\mathbf{w}}
# \newcommand{\tv}{\mathbf{t}}
# \newcommand{\Tv}{\mathbf{T}}
# \newcommand{\muv}{\boldsymbol{\mu}}
# \newcommand{\sigmav}{\boldsymbol{\sigma}}
# \newcommand{\phiv}{\boldsymbol{\phi}}
# \newcommand{\Phiv}{\boldsymbol{\Phi}}
# \newcommand{\Sigmav}{\boldsymbol{\Sigma}}
# \newcommand{\Lambdav}{\boldsymbol{\Lambda}}
# \newcommand{\half}{\frac{1}{2}}
# \newcommand{\argmax}[1]{\underset{#1}{\operatorname{argmax}}}
# \newcommand{\argmin}[1]{\underset{#1}{\operatorname{argmin}}}$

# # Assignment 1: Linear Regression

# *by Vignesh M. Pagadala*

# ## Overview

# Describe the objective of this assignment, and very briefly how you accomplish it.  Say things like "linear model", "samples of inputs and known desired outputs" and "minimize the sum of squared errors". DELETE THIS TEXT AND INSERT YOUR OWN.

# ## Method

# Define in code cells the following functions as discussed in class.  Your functions' arguments and return types must be as shown here.
#
#   * ```model = train(X, T)```
#   * ```predict = use(model, X)```
#   * ```error = rmse(predict, T)```
#
# Let ```X``` be a two-dimensional matrix (```np.array```) with each row containing one data sample, and ```T``` be a two-dimensional matrix of one column containing the target values for each sample in ```X```.  So, ```X.shape[0]``` is equal to ```T.shape[0]```.
#
# Function ```train``` must standardize the input data in ```X``` and return a dictionary with  keys named ```means```, ```stds```, and ```w```.
#
# Function ```use``` must also standardize its input data X by using the means and standard deviations in the dictionary returned by ```train```.
#
# Function ```rmse``` returns the square root of the mean of the squared error between ```predict``` and ```T```.
#
# Also implement the function
#
#    * ```model = trainSGD(X, T, learningRate, numberOfIterations)```
#
# which performs the incremental training process described in class as stochastic gradient descent (SGC).  The result of this function is a dictionary with the same keys as the dictionary returned by the above ```train``` function.

# In[16]:

import numpy as np
import matplotlib.pyplot as plt

def train(X, T):
    # Standardize input data (X)

    # Calculate mean and std.
    means = X.mean(axis = 0)
    std = X.std(axis = 0)

    Xs = (X - means) / std

    # Tack a column of 1s
    Xs = np.insert(Xs, 0, 1, 1)

    # Use Xs to generate model (w)
    w = np.linalg.lstsq(Xs.T @ Xs, Xs.T @ T, rcond = None)[0]

    # Return as a dictionary
    dict = {'means': means, 'stds': std, 'w': w}
    return dict

def use(model, X):
    # Use model and input X to predict.
    means = model['means']
    std = model['stds']
    w = model['w']

    # Standardize X
    Xs = (X - means) / std

    # Tack column of 1s
    Xs = np.insert(Xs, 0, 1, 1)

    # Predict
    predict = Xs @ w
    #print(Xs.shape)
    #print(w.shape)
    return predict

def rmse(predict, T):
    rmerr = np.sqrt(np.mean((T - predict) ** 2))
    return rmerr

def trainSGD(X, T, learningRate, numberOfIterations):
    # Standardize inputs X.
    means = X.mean(axis = 0)
    std = X.std(axis = 0)
    Xs = (X - means) / std

    nSamples = Xs.shape[0]
    ncolsT = T.shape[1]

    # Tack a column of 1s
    Xs = np.insert(Xs, 0, 1, 1)
    ncolsX = Xs.shape[1]
    # Initialize weights to zero.
    w = np.zeros((ncolsX, ncolsT))

    for i in range(numberOfIterations):
        for n in range(nSamples):
            predicted = Xs[n:n+1, :] @ w
            w += learningRate * Xs[n:n+1, :].T * (T[n:n+1, :] - predicted)

    dict = {'means': means, 'stds': std, 'w': w}
    return dict

# In this section, ilatex math formulas defining the formula that is being minimized, and the matrix calculation for finding the weights.
#
# In this section, include all necessary imports and the function definitions. Also include some math formulas using latex syntax that define the formula being minimized and the calculation of the weights using a matrix equation.  You do not need to include the math formulas showing the derivations.

# ## Examples

# In[13]:

# from A1mysolution import *

# In[18]:

import numpy as np

X = np.arange(10).reshape((5,2))
T = X[:,0:1] + 2 * X[:,1:2] + np.random.uniform(-1, 1,(5, 1))
print('Inputs')
print(X)
print('Targets')
print(T)

# In[19]:

model = train(X, T)
model

# In[20]:

predicted = use(model, X)
predicted

# In[21]:

rmse(predicted, T)

# In[22]:

modelSGD = trainSGD(X, T, 0.01, 100)
modelSGD

# In[23]:

predicted = use(modelSGD, X)
predicted

# In[24]:

rmse(predicted, T)

# ## Data

# Download ```energydata_complete.csv``` from the [Appliances energy prediction Data Set ](https://archive.ics.uci.edu/ml/datasets/Appliances+energy+prediction) at the UCI Machine Learning Repository. Ignore the first column (date and time), use the next two columns as target variables, and use all but the last two columns (named rv1 and rv2) as input variables.
#
# In this section include a summary of this data, including the number of samples, the number and kinds of input variables, and the number and kinds of target variables.  Also mention who recorded the data and how.  Some of this information can be found in the paper that is linked to at the UCI site for this data set.  Also show some plots of target variables versus some of the input variables to investigate whether or not linear relationships might exist.  Discuss your observations of these plots.

# ## Results

# Apply your functions to the data.  Compare the error you get as a result of both training functions.  Experiment with different learning rates for ```trainSGD``` and discuss the errors.
#
# Make some plots of the predicted energy uses and the actual energy uses versus the sample index.  Also plot predicted energy use versus actual energy use.  Show the above plots for the appliances energy use and repeat them for the lights energy use. Discuss your observations of each graph.
#
# Show the values of the resulting weights and discuss which ones might be least relevant for fitting your linear model.  Remove them, fit the linear model again, plot the results, and discuss what you see.

# ## Grading
#
# Your notebook will be run and graded automatically.  Test this grading process by first downloading [A1grader.tar](http://www.cs.colostate.edu/~anderson/cs445/notebooks/A1grader.tar) and extract `A1grader.py` from it. Run the code in the following cell (after deleting the one containing A1mysolution) to demonstrate an example grading session.  You should see a perfect execution score of 70/70 if your functions are defined correctly. The remaining 30 points will be based on the results you obtain from the energy data and on your discussions.
#
# A different, but similar, grading script will be used to grade your checked-in notebook.  It will include additional tests.  You need not include code to test that the values passed in to your functions are the correct form.

# In[25]:

get_ipython().run_line_magic('run', '-i "A1grader.py"')

# ## Check-in

# Do not include this section in your notebook.
#
# Name your notebook ```Lastname-A1.ipynb```.  So, for me it would be ```Anderson-A1.ipynb```.  Submit the file using the ```Assignment 1``` link on [Canvas](https://colostate.instructure.com/courses/41327).
#
# Grading will be based on
#
#   * correct behavior of the required functions listed above,
#   * easy to understand plots in your notebook,
#   * readability of the notebook,
#   * effort in making interesting observations, and in formatting your notebook.

# ## Extra Credit

# Download a second data set and repeat all of the steps of this assignment on that data set.


# coding: utf-8
import numpy as np

def softmax(x):
    if x.ndim == 2:
        x = x.T
        x = x - np.max(x, axis=0)
        y = np.exp(x) / np.sum(np.exp(x), axis=0)
        return y.T

    x = x - np.max(x) # 오버플로 대책
    return np.exp(x) / np.sum(np.exp(x))

x = np.array([-1, 0, 1.0])
y = softmax(x)

p = np.argmax(y) # 확률이 가장 높은 원소의 인덱스를 얻는다.

print(x)
print(y)
print(p)
'''
[-1.  0.  1.]
[ 0.09003057  0.24472847  0.66524096]
2

from Java
v = -1.000000, 0.000000, 1.000000
v2 = 0.090031, 0.244728, 0.665241

'''


from scipy.optimize import newton
import matplotlib.pyplot as plt
import numpy as np

# Objective function formalization
def obj_fun():
    return lambda x: 2 * (x ** 4) - 4 * (x ** 2) + x - 0.5

# Plot objective function
def plot_obj_fun(x):
    return 2 * x ** 4 - 4 * (x ** 2) + x - 0.5

# First derivative
def der_obj_fun(x):
    return (8 * (x ** 3)) - (8 * x) + 1

# Second derivative
def sec_der_obj_func(x):
    return (24 * (x ** 2)) - (8 * x)

# DESCENT GRADIENT
print("SOLVING WITH GRADIENT DESCENT METHOD")
# Initial guess
x0s = [-2, -0.5, 0.5, 2]
# stop criterion
accuracy = pow(10, -4)
for x0 in x0s:
    label_x0 = "initial point [" + str(x0) + ", " + str(plot_obj_fun(x0)) + "]"
    plt.scatter(x0, plot_obj_fun(x0), color='yellow', edgecolor='black', label=label_x0)
    # maximum number of iterations
    max_iters = 1000
    # iteration count
    iters = 0
    previous_step_size = 1
    # Learning rate
    rate = 0.01
    current = x0
    while previous_step_size > accuracy and iters < max_iters:
        # Current x will be previous in next step
        previous = current
        # Gradient descent
        current = current - rate * der_obj_fun(previous)
        # Distance moved
        previous_step_size = abs(current - previous)
        # iteration count
        iters += 1
        # plot
        plt.scatter(current, plot_obj_fun(current), color='red', edgecolor='black')
    print("Initial point: ", x0)
    print("Local minimum: ", current)
    print("Number of iterations: ", iters)
    # Plot
    # define range for input
    x0 = np.linspace(-2, 2)
    plt.plot(x0, plot_obj_fun(x0))
    # plot minimal point
    label_min = "minimal point [" + str(current) + ", " + str(plot_obj_fun(current)) + "]"
    plt.scatter(current, plot_obj_fun(current), color='green', edgecolor='black', label=label_min)
    plt.legend(prop={'size': 6}, loc='best')
    plt.show()

# NEWTON'S METHOD
print("SOLVING WITH NEWTON'S METHOD")
x0s = [-2, -0.5, 0.5, 2]
for x0 in x0s:
    g = newton(func=der_obj_fun, x0=x0, fprime=sec_der_obj_func, tol=0.0001, full_output=True)
    # print(g)
    r = g[1]
    print("initial point:", x0)
    print("Value:", r.root)
    print("Number of iterations: ", r.iterations)


import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import tensorflow as tf
import cv2
print(tf.__version__)

"""## Let's discuss the Convolution operation

We have an overlay, multiply and add process encapsulated in the term called convolution. We have kernels that convolve themselves with images to give us the output image. The nitty gritty gets us into the world of matrix sizes. We have:

`output_height = input_height - kernel_height + 1`

and

`output_width = input_width - kernel_width + 1`
"""

from google.colab.patches import cv2_imshow
input_image = cv2.imread("sample_data/original.jpg", 0)
cv2_imshow(input_image)

"""## I'm sick of these zeros and those kernels so let's talk about it

I can either be valid to get a smaller output, same to equal the input size, or be full and get a larger image.

### Output size

`valid = N - K + 1`

`same = N`

`full = N + K - 1`
"""

def convolution(input_image, kernel_image):
  # So we'll coded the convolution operation here; from scratch !
  input_width = input_image.shape[1]
  input_height = input_image.shape[0]
  kernel_width = kernel_image.shape[1]
  kernel_height = kernel_image.shape[0]
  output_height = input_height - kernel_height + 1
  output_width = input_width - kernel_width + 1
  output = np.zeros((output_height, output_width))
  for k in range(0, output_height):
    for j in range(0, output_width):
      for w in range(0, kernel_height):
        for v in range(0, kernel_width):
          output[k, j] += input_image[k+w, j+v] * kernel_image[w, v]
  return output

def convolution_flipped(input_image, kernel_image):
  # So we'll coded the convolution operation here; from scratch !
  input_width = input_image.shape[1]
  input_height = input_image.shape[0]
  kernel_width = kernel_image.shape[1]
  kernel_height = kernel_image.shape[0]
  output_height = input_height - kernel_height + 1
  output_width = input_width - kernel_width + 1
  output = np.zeros((output_height, output_width))
  for k in range(0, output_height):
    for j in range(0, output_width):
      for w in range(0, kernel_height):
        for v in range(0, kernel_width):
          output[k, j] += input_image[k-w, j-v] * kernel_image[w, v]
  return output

kernel_image = np.array([-5, -5, -5, 0, 0, 0, 5, 5, 5]).reshape(3,3)
kernel_image

output = convolution(input_image, kernel_image)
cv2_imshow(output)

output = convolution_flipped(input_image, kernel_image)
cv2_imshow(output)

"""## Do we have to write such complicated equations?

Well no, scipy has convolve2d, which does all the convolutions for us!,
BUT WAIT
Do they flip it? let's find out
"""

from scipy.signal import convolve2d, correlate2d
output = convolve2d(input_image, kernel_image, mode = 'valid')
cv2_imshow(output)

# The output shows a flipped filter, let's try correlation
output = correlate2d(input_image, kernel_image, mode = 'valid')
cv2_imshow(output)

output = convolve2d(input_image, np.fliplr(np.flipud(kernel_image)), mode = 'valid')
cv2_imshow(output)

"""## Let's talk about color convolution

Suppose:

input image = 28 x 28 x 3

filter = 3 x 5 x 5 x 64

## the filter shape 3 x 5 x 5 x 64 = 4800 is the number of weights

Output image =  (28 - 5 + 1) x (28 - 5 + 1) x 64
"""

"""## CONVOLUTION IN COLORS"""

from google.colab.patches import cv2_imshow
input_image = cv2.imread("sample_data/original.jpg")
cv2_imshow(input_image)

input_image.shape

filters = np.array([-5, -5, -5, 0, 0, 0, 5, 5, 5]).reshape(3,3)
kernel_image = np.array([filters, filters, filters])
kernel_image

kernel_image.shape

def convolution3d(input_image, kernel_image):
  # So we'll coded the convolution operation here; from scratch !
  input_width = input_image.shape[1]
  input_height = input_image.shape[0]
  kernel_width = kernel_image.shape[1]
  kernel_height = kernel_image.shape[0]
  output_height = input_height - kernel_height + 1
  output_width = input_width - kernel_width + 1
  output = np.zeros((output_height, output_width, input_image.shape[2]))
  for c in range(0, input_image.shape[2]):
    for k in range(0, output_height):
      for j in range(0, output_width):
        for w in range(0, kernel_height):
          for v in range(0, kernel_width):
            output[k, j, c] += input_image[k+w, j+v, c] * kernel_image[w, v, c]
  return output

def correlation3d(input_image, kernel_image):
  # So we'll coded the convolution operation here; from scratch !
  input_width = input_image.shape[1]
  input_height = input_image.shape[0]
  kernel_width = kernel_image.shape[1]
  kernel_height = kernel_image.shape[0]
  output_height = input_height - kernel_height + 1
  output_width = input_width - kernel_width + 1
  output = np.zeros((output_height, output_width, input_image.shape[2]))
  for c in range(0, input_image.shape[2]):
    for k in range(0, output_height):
      for j in range(0, output_width):
        for w in range(0, kernel_height):
          for v in range(0, kernel_width):
            output[k, j, c] += input_image[k-w, j-v, c] * kernel_image[w, v, c]
  return output

output = convolution3d(input_image, kernel_image)
cv2_imshow(output)

output = correlation3d(input_image, kernel_image)
cv2_imshow(output)

filters = np.array([0, -1, 0, -1, 2, -1, 0, -1, 0]).reshape(3,3)
kernel_image = np.array([filters, filters, filters])
kernel_image

output = convolution3d(input_image, kernel_image)
cv2_imshow(output)


import matplotlib
matplotlib.rcParams = matplotlib.rc_params_from_file('../../matplotlibrc')

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

def basins_1d():
    f = lambda x:x**2-1
    Df = lambda x:2*x
    x0 = np.linspace(-1.5, 1.5, 40)

    xold=x0
    n = 0
    while n <= 6:
        xnew = xold - f(xold)/Df(xold)
        xold = xnew
        n += 1

    plt.scatter(x0, np.zeros_like(x0), marker='s', c=xnew, edgecolor='None', cmap='bwr')
    plt.plot(x0, f(x0), c='black', linewidth=3)
    plt.savefig('basins1d.pdf', bbox_inches='tight')
    plt.close()

def fractal_1d():
    f = lambda x:x**3-x
    Df = lambda x:3*x**2-1
    x0 = np.linspace(-1.5, 1.5, 500)

    xold=x0
    n = 0
    while n <= 50:
        xnew = xold - f(xold)/Df(xold)
        xold = xnew
        n += 1

    y = np.array([-.1, .1])
    X, Y = np.meshgrid(x0, y)
    plt.pcolormesh(X, Y, np.atleast_2d(xnew).repeat(2, axis=0), cmap='brg')
    plt.plot(x0, f(x0), c='black', linewidth=3)
    plt.savefig('fractal1d.pdf', bbox_inches='tight')
    plt.close()

def plot_basins(f, Df, roots, xmin, xmax, ymin, ymax, numpoints=100, iters=15, colormap='brg', name='name.png', dpinum=150):
    xreal = np.linspace(xmin, xmax, numpoints)
    ximag = np.linspace(ymin, ymax, numpoints)
    Xreal, Ximag = np.meshgrid(xreal, ximag)
    Xold = Xreal + 1j * Ximag
    for i in xrange(iters):
        Xnew = Xold - f(Xold)/Df(Xold)
        Xold = Xnew
    m,n = Xnew.shape
    for i in xrange(m):
        for j in xrange(n):
            Xnew[i,j] = np.argmin(np.abs(Xnew[i,j]-roots))
    plt.pcolormesh(Xreal, Ximag, Xnew, cmap=colormap)
    plt.savefig(name, bbox_inches='tight', dpi=dpinum)

if __name__ == "__main__":
    basins_1d()
    fractal_1d()


import numpy as np

def Tanh(z):
	z = np.array(z)
	ez = np.exp(-2*z)
	sigz = (1.0/(1.0+ez))
	return 2*sigz - 1

def TanhGradient(z):
	th = Tanh(z)
	return 1 - th**2

def InverseTanh(a):
	sigz = (a + 1.0)/2.0
	ez = 1.0/sigz - 1.0
	z = np.log(ez)/(-2.0)
	return z

def InverseTanhGradient(a):
	z = InverseTanh(a)
	return TanhGradient(z)


#  ___________________________________________________________________________
# version with multiple solutions
# introducing the 2nd furniture factory problem
# https://youtu.be/kGs32O2rhx8?t=171

# Solution 1:
# Profit =  2250.0  per week
# c =  24.0  chairs per week
# t =  14.0  tables per week

# Solution 2:
# Profit =  2250.0  per week
# c =  45.0  chairs per week
# t =  0.0  tables per week

#  ___________________________________________________________________________

#
# Imports
#
import pyomo.environ as pyo

model = pyo.ConcreteModel()

# decision variables:
# number of chairs
# number of tables
model.c = pyo.Var(domain=pyo.NonNegativeIntegers)
model.t = pyo.Var(domain=pyo.NonNegativeIntegers)
# model.c = pyo.Var(domain=pyo.NonNegativeReals)
# model.t = pyo.Var(domain=pyo.NonNegativeReals)

# maximize the profit
model.OBJ = pyo.Objective(expr = 50*model.c + 75*model.t, sense=pyo.maximize)

# weekly material/wood constraint
model.wood = pyo.Constraint(expr = 5*model.c + 20*model.t <= 400)
# weekly labor constraint
model.labor = pyo.Constraint(expr = 10*model.c + 15*model.t <= 450)

pyo.SolverFactory('glpk').solve(model)

# model.pprint()
print("Profit = ", model.OBJ(), " per week")
print("c = ", model.c(), " chairs per week")
print("t = ", model.t(), " tables per week")


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

# In[ ]:

import numpy as np
import matplotlib.pyplot as plt

# # Lab 6 Monte Carlo Methods

# ## Introduction: Random numbers and statistics

# ### Normal distribution

# A nomral distribution can be generated by `numpy.eandom.normal`
# See details [here](https://docs.scipy.org/doc/numpy-1.15.0/reference/generated/numpy.random.normal.html).
#
# The probability density function of the normal distribution follows a Gaussian function
# \begin{equation}
#     f(x|\mu,\sigma^2)=\frac{1}{\sqrt{2\pi\sigma^2}}e^{-\frac{(x-\mu)^2}{2\sigma^2}}
# \end{equation}

# In[ ]:

# mean
mu = 0
# Standard deviation
sigma = 1
# size of variables of the normal distribution, n can beint or tuple
# depending on if x is a vector, or a matrix, etc.
n = 10000
# n = (100, 100)
# Normal distribution
x_normal = np.random.normal(mu, sigma, n)
# plot
fig, axs = plt.subplots(2, 1, figsize=(8, 8))
axs[0].plot(x_normal, 'ro')
axs[0].set_title(r'Normal Distribution with $\mu=$'+str(mu)+' and $\sigma=$'+str(sigma))

count, bins, ignored = axs[1].hist(x_normal, 100, density=True)
axs[1].plot(bins, 1/(sigma * np.sqrt(2 * np.pi)) *
            np.exp( - (bins - mu)**2 / (2 * sigma**2) ),
            linewidth=2, color='r')
axs[1].set_title('Probability Density')

for i in range(2): axs[i].autoscale(enable=True, axis='both', tight=True)

# ### Uniform distribution

# Similarly, a uniform distribution can be constructed by `numpy.random.uniform`.
# See [here](https://docs.scipy.org/doc/numpy-1.15.0/reference/generated/numpy.random.uniform.html).

# In[ ]:

# the half-open interval [low, high)
low, high = 0, 1
# size of variables of the normal distribution, n can beint or tuple
# depending on if x is a vector, or a matrix, etc.
n = 500
# uniform distribution
x_uni = np.random.uniform(low, high, n)

# plot
fig, axs = plt.subplots(2, 1, figsize=(8, 8))
axs[0].plot(x_uni, 'ro')
axs[0].set_title(r'Uniform Distribution in $[$'+str(low)+','+str(high)+r'$)$')

count, bins, ignored = axs[1].hist(x_uni, 100, density=True)
axs[1].plot(bins, np.ones_like(bins), linewidth=2, color='r')
axs[1].set_title('Probability Density')
for i in range(2): axs[i].autoscale(enable=True, axis='both', tight=True)

# ## A simulation: Throw dice

# The uniform distribution random function `numpy.random.uniform` generate real numbers.
# In order to create only integers, one need to round the random value to the nearest integer with `numpy.floor` or `numpy.ceil`.

# Define the following function to simulate dice throw

# In[ ]:

def throwDice(N):
    '''
    To simulate throwing dice N times
    '''
    return np.floor(1 + 6*np.random.uniform(0, 1, size=N))

# In[ ]:

N = 1000
Nrepeat = 10000
r = [np.mean(throwDice(N)) for i in range(Nrepeat)]

# plot
fig, ax = plt.subplots(1, 1, figsize=(8, 4))
ax.hist(r, 100, density=True)
ax.autoscale(enable=True, axis='both', tight=True)

# __NOTE__: There is a random integers generator in `numpy.random.randint` which create discrete uniform random integers from _low (inclusive)_ to __high (exclusive)__.
# See [here](https://docs.scipy.org/doc/numpy-1.15.0/reference/generated/numpy.random.randint.html).

# In[ ]:

# Range of the dice
# NOTE!! high value is not included, for a dice with six sides, high=7
low, high = 1, 7
# Number of throws
n = 1000
x_dice = np.random.randint(low, high, n)

# Then, we can simulate the same event with the following code

# In[ ]:

N = 1000
Nrepeat = 10000
r = [np.mean(np.random.randint(low, high, n)) for i in range(Nrepeat)]

# plot
fig, ax = plt.subplots(1, 1, figsize=(8, 4))
ax.hist(r, 100, density=True)
ax.autoscale(enable=True, axis='both', tight=True)

# ## Application: Computing an integral with Monte Carlo

# An example use Monte Carlo method to compute the integration of multivariate normal distribution funciton.

# In[ ]:

def mcint(func, domain, N, M=30):
    """ Numerical integration using Monte Carlo method
    Parameters
    ----------
    func : function, function handler of the integrand;
    domain : numpy.ndarray, the domain of computation,
            domain = array([[-5, 5],
                            [-5, 5],
                            [-5, 5]])
            The dimensions of the domain is given by domain.shape[0];
    N : integer, the number of points in each realization;
    M : integer, the number of repetitions used for error estimation,
        (Recommendation, M = 30+).
        Total number of points used is thus M*N
    Returns
    -------
    r.mean() : the integral value of func in the domain
    r.std() : the error in the result (the standard deviation)
    """
    # Get the dimensions
    dim = domain.shape[0]
    # volume of the domain
    V = abs(domain.T[0] - domain.T[1]).prod()

    r = np.zeros(M)
    for i in range(M):
        # generate uniform distributed random numbers within the domain
        x = np.random.uniform(domain.T[0], domain.T[1], (N, dim))
        r[i] = V * np.mean(func(x), axis=0)

    return r.mean(), r.std()

def fnorm(x):
    """ Normal distribution function in d-dimensions
    Parameters
    ----------
    x : numpy.ndarray, of shape (N, d), where d is the dimension and
        N is the number of realizations
    Returns
    -------
    y : numpy.ndarray, of the shape (N, 1)
    """
    d = x.shape[1]
    y = 1/((2*np.pi)**(d/2))*np.exp(-0.5*np.sum(x**2, axis=1))
    return y

# Take the domain in $[-4,4]\times[-4,4]$, compute the integral using funtion `fnorm`

# In[ ]:

# numbers of samples
N = 1000
M = 50
# domain
domain = np.array([[-4,4],[-4,4]])
# integrate
intF, err = mcint(fnorm, domain, N, M)
print('The result of the integral with N=', str(N), 'is', '{:.5f}'.format(intF),',')
print('with standard deviation', '{:.5f}'.format(err), 'for', str(M), 'realizations.')

# ### Check the order of accuracy $p$ for the Monte Carlo method.

# In[ ]:

# Change the dimension to see different results
dim = 3

# domain [-5, 5]^dim
domain = np.array([[-5, 5] for i in range(dim)])

# take an array of N
n = 8
NList = 500 * 2**np.array(range(n))
M = 50

# Save the error
errList = np.zeros_like(NList, dtype=float)
for i in range(n):
    intF, err = mcint(fnorm, domain, NList[i], M)
    errList[i] = err

# Plot
fig, ax = plt.subplots(1, 1, figsize=(8, 4))
ax.loglog(NList, errList)
ax.autoscale(enable=True, axis='both', tight=True)
ax.set_xlabel('N')
ax.set_ylabel('Error')

# Compute the order
a = np.polyfit(np.log(NList), np.log(errList),1)
p = np.round(a[0], 1)
print('Order of accuracy is N^p, with p=', p)

# ## Programming: Brownian motion

# In[ ]:

def brownian(x0, tEnd, dt):
    """
    Generate an instance of Brownian motion
    Parameters
    ----------
    x0 : float or numpy array (or something that can be converted to a numpy array
         using numpy.asarray(x0)).
         The initial condition(s) (i.e. position(s)) of the Brownian motion.
    tEnd : float, the final time.
    dt : float, the time step.
    Returns
    -------
    x: A numpy array of floats with shape `x0.shape + (n,)`.
    """
    x0 = np.asarray(x0)
    n = int(tEnd/dt)

    # For each element of x0, generate a sample of n numbers from a
    # normal distribution.
    r = np.random.normal(size=x0.shape + (n,), scale=(dt**0.5))

    # This computes the Brownian motion by forming the cumulative sum of
    # the random samples.
    x = np.cumsum(r, axis=-1)

    # Add the initial condition.
    x += np.expand_dims(x0, axis=-1)

    return x

# In[ ]:

# Total time.
T = 10.0
# Number of steps.
N = 500
# Time step size
dt = T/N
# Initial values of x.
x = np.empty((2,N+1))
x[:, 0] = 0.0

# Brownian motion
x[:, 1:] = brownian(x[:,0], T, dt)

# Plot the 2D trajectory.
fig, ax = plt.subplots(1, 1, figsize=(8, 8))
ax.plot(x[0],x[1])
ax.plot(x[0,0],x[1,0], 'go')
ax.plot(x[0,-1], x[1,-1], 'ro')
ax.set_title('2D Brownian Motion')
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.axis('equal')
ax.grid(True)


# This is an midpoint method of solving the ODEs (a refinement of Euler's method)
# Advantage is that it has a local error of O(dt^3) and a global error of O(dt^2) making it similar to RK23
# Another advantage is that it is a fixed timestep solver which can take into account the control at every timestep

import glob
import matplotlib.pyplot as plt
import numpy
import os
import pandas
import scipy
import scipy.integrate
import sympy

# GLOBAL CONSTANTS
beta = 0.175 # Rate of Exposure
delta = 2.703e-5 # Natural Birth Rate (unrelated to disease)
gamma = 0.07 # Rate of Removal [days^-1]
mu = 2.403e-5 # Natural Death Rate (unrelated to disease)
sigma = 0.2 # Average Incubation Period [days^-1]

N = 3.57e6 # Total Population of CT [persons]
cfr = 0.022 # Case Fatality Rate [1.4% (NY) - 3%]
R = (beta*sigma)/((mu+gamma)*(mu+sigma)) # Basic Reproduction Number

beds = 1739 # Number of hospital beds available
icub = 100 # Number of ICU beds available

def midpoint():

    # ODE VARIABLES
    tf = 365
    dt = 1

    s = numpy.zeros(tf)
    e = numpy.zeros(tf)
    i = numpy.zeros(tf)
    r = numpy.zeros(tf)
    t = numpy.arange(0, tf, dt)

    # INITIAL CONDITIONS
    i[0] = 360/N    # Active Infected []
    e[0] = 250/N      # Exposed []
    r[0] = 400/N      # Recovered []
    s[0] = (N-r[0]-e[0]-i[0])/N # Susceptible []

    for index in range(1, tf):
        [fs, fe, fi, fr] = dynamics(s[index-1],
                                    e[index-1],
                                    i[index-1],
                                    r[index-1])
        [ds, de, di, dr] = dynamics(s[index-1] + dt/2*fs,
                                    e[index-1] + dt/2*fe,
                                    i[index-1] + dt/2*fi,
                                    r[index-1] + dt/2*fr)

        s[index] = s[index-1] + dt*ds
        e[index] = e[index-1] + dt*de
        i[index] = i[index-1] + dt*di
        r[index] = r[index-1] + dt*dr

    fig = plt.figure()
    plt.plot(t, s, label='Susceptible')
    plt.plot(t, e, label='Exposed')
    plt.plot(t, i, label='Infected')
    plt.plot(t, r, label='Removed')
    plt.legend()
    plt.show()

def dynamics(s, e, i, r):
    ds = delta - (beta*s*i) - (mu*s)
    de = (beta*s*i) - (mu*e) - (sigma*e)
    di = (sigma*e) - (gamma*i) - (mu*i)
    dr = (gamma*i) - (mu*r)
    return [ds, de, di, dr]

if __name__ == "__main__":
    midpoint()


from sympy import Matrix

"""
>Let $h(\mathbf{x}) \in \mathbb{R}$ be a smooth function, and $\mathbf{f}(\mathbf{x}) \in \mathbb{R}^n$ be a smooth vector field, the **Lie derivative** of $h$ with respect to $\mathbf{f}$ is given by directional derivative:
>\begin{equation}
L_\mathbf{f} h = \nabla h^T\mathbf{f} = \sum_{i=1}^n \frac{\partial h}{\partial x_i} f_i
\end{equation}
"""

def lie_diff(h, f, x):
    grad = Matrix([h]).jacobian(x)
    lf_h = grad * f
    return lf_h[0]

"""
The $n$-th order Lie derivative is defined recursevely as:
\begin{equation}
L^k_\mathbf{f} h = L_\mathbf{f} (L_\mathbf{f}^{k-1}h), \quad \text{for }k=1,\dots, n
\end{equation}
With $L^0_\mathbf{f} h = h$
"""

def lie_diff_n(h, f, x, n):
    lf_h_k = h
    for k in range(n):
        lf_h_k = lie_diff(lf_h_k, f, x)
    return lf_h_k

"""
>The **Lie Bracket** of two vector fields $\mathbf{f}(\mathbf{x}),\mathbf{g}(\mathbf{x})$ is vector field defined as follows:
>
>\begin{equation}
[\mathbf{f},\mathbf{g}] = \frac{\partial \mathbf{g}}{\partial \mathbf{x}}\mathbf{f} -
\frac{\partial \mathbf{f}}{\partial \mathbf{x}}\mathbf{g}
\end{equation}
"""

def lie_bracket(f, g, x):
    return g.jacobian(x) * f - f.jacobian(x) * g

"""
>We denote $ad_\mathbf{f}(\mathbf{g}) = [\mathbf{f},\mathbf{g}]$ ($ad$ - for *adjoint*) while $ad^k_\mathbf{f}(\mathbf{g})$  defined recursevely as:
\begin{equation}
ad^k_\mathbf{f}(\mathbf{g}) = [\mathbf{f},ad^{k-1}_\mathbf{f}(\mathbf{g})]
\end{equation}
with $ad^0_\mathbf{f}(\mathbf{g}) = \mathbf{g}$
"""

def adf_g(f, g, x, n):
    ad_k = g
    for k in range(n):
        ad_k = lie_bracket(f, ad_k, x)
    return ad_k


# fit to y(x) = a_1 exp(a2 x) using a nonlinear fitting technique that
# reduces to a multivariate root finding problem
#
# This is very sensitive to our initial guess
#
# M. Zingale (2013-03-10)

import numpy
import numpy.linalg
import pylab

tol = 1.e-5

def fun(a, x, y):
    """ the derivatives of our fitting function wrt each parameter:

        Q = sum_{i=1}^N (y_i = a0 exp(a1 x_i) )**2

        -- these are what we zero """

    # dQ/da0
    f0 = numpy.sum(numpy.exp(a[1]*x)*(a[0]*numpy.exp(a[1]*x) - y))

    # dQ/da1
    f1 = numpy.sum(x*numpy.exp(a[1]*x)*(a[0]*numpy.exp(a[1]*x) - y))

    return numpy.array([f0, f1])

def jac(a, x, y):
    """ return the Jacobian of fun """

    # df0/da0
    df0da0 = numpy.sum(numpy.exp(2.0*a[1]*x))

    # df0/da1
    df0da1 = numpy.sum(x*numpy.exp(a[1]*x)*(2.0*a[0]*numpy.exp(a[1]*x) - y))

    # df1/da0
    df1da0 = numpy.sum(x*numpy.exp(2.0*a[1]*x))

    # df1/da1
    df1da1 = numpy.sum(x**2*numpy.exp(a[1]*x)*(2.0*a[0]*numpy.exp(a[1]*x) - y))

    return numpy.array([ [df0da0, df0da1], [df1da0, df1da1] ])

def fRoots(aguess, x, y):
    """ aguess is the initial guess to our fit parameters.  x and y
        are the vector of points that we are fitting to """

    avec = aguess.copy()

    err = 1.e100
    while err > tol:

        # get the jacobian
        J = jac(avec, x, y)

        print "condition number of J: ", numpy.linalg.cond(J)

        # get the current function values
        f = fun(avec, x, y)

        # solve for the correction: J dx = -f
        da = numpy.linalg.solve(J, -f)

        avec += da
        err = numpy.max(numpy.abs(da))

    return avec

# make up some experimental data
a0 = 2.5
a1 = 2./3.
sigma = 2.0

x = numpy.linspace(0.0, 4.0, 25)
y = a0*numpy.exp(a1*x) + sigma*numpy.random.randn(len(x))

pylab.scatter(x,y)
pylab.errorbar(x, y, yerr=sigma, fmt=None, label="_nolegend_")

# initial guesses
aguess = numpy.ones(2)

# fit
afit = fRoots(aguess, x, y)

print afit

p = pylab.plot(x, afit[0]*numpy.exp(afit[1]*x),
           label=r"$a_0 = $ %f; $a_1 = $ %f" % (afit[0], afit[1]))

pylab.legend(numpoints=1, frameon=False)

pylab.savefig("nonlinear-fit.png")


import numpy as np

#      We've got our DEM into python, we've projected our DEM onto a regular grid so that we can easily do some operations on it, and we've seen a little bit about how to use matplotlib to plot.  Now lets start actually doing some things.  In this post we'll calculate slope on the grid we just read in, and use that to create a hillshade. We will then superimpose that hillshade image on our slope map to create an image like the one above.  Specifically we're going to make a second order, or centered, finite-difference approximation of slope. We will calculate the hillshade grid using ESRI's algorithm. To check our work we'll plot both,  with some transparency on the hillshade so that it just gives the slope grid texture.

#      First lets, take a second to review this finite difference approximation.  We can approximate the first derivative at the point Y6 (dashed line) as the difference in the values of Y at the adjacent points (7 and 5) divided by the horizontal distance between those adjacent points.  Think about this as the slope (rise over run) of the little gray triangle in the figure below. Notice that as the spacing between out points increases, we will do an increasingly poor job of approximating the derivative in places where the function curves around a lot.  As mentioned above, this is a 'second-order' finite difference approximation, but we won't get into why this is. One thing that is handy about this approximation is that the slope calculation is centered on the point we are interested in, Y6 in the highlighted example. If we only looked at two neighboring points (e.g. Y7 and Y6) to approximate the first derivative, that approximation would instead be centered between these nodes. There are times that this is useful, but since we are dealing with georeferenced raster data, it would be nice to have the pixels of our slope grid centered at the same points as the original dataset.  To do this in basic python might look something like whats below. Notice that because we are using our neighbors to calculate slope, we can not calculate the slope for the first and final items in our array - leaving us with an array of slope thats two smaller than the one we started with.

def IterateCenteredSlope(y,dx):
    #Function to calculate second order finite difference
    dydx = []  # Initialize an empty list
    for i in range(1,len(y)-1):  # iterate through all pts not at boundaries
        # append the current slope calculation to the list
        dydx.append((y[i+1]-y[i-1])/(2*dx))
    return dydx

#  One thing you might notice about this operation, is that instead of going through the data point by point and differencing the neighboring points we can actually just subtract two vectors shifted in opposite directions.  This is shown schematically with the math in the top right of the figure above and it is an easily accomplished operation with numpy arrays. In python it might look something like this:
def npCenteredSlope(y,dx):
    # Where y is a numpy array,
    # Calculate slope by differencing shifted vectors
    dydx = (y[2:] - y[:-2])/(2*dx)
    return dydx

#  This is nice and clean, on large arrays it turns out to be a touch faster too (even after the overhead associated with turning your array into a numpy array.  Our grid is two dimensional, so we can calculate slope in both the row (y) and column (x) directions.  We can do this with the above technique after transforming our dataset to a numpy array (see pt2 near the end, gdalDataset.readAsArray().asType(np.float) ) with a function that looks something like this:
def calcFiniteSlopes(elevGrid, dx):
    # sx,sy = calcFiniteDiffs(elevGrid,dx)
    # calculates finite differences in X and Y direction using the
    # 2nd order/centered difference method.
    # Applies a boundary condition such that the size and location
    # of the grids in is the same as that out.

    # Assign boundary conditions
    Zbc = assignBCs(elevGrid)

    #Compute finite differences
    Sx = (Zbc[1:-1, 2:] - Zbc[1:-1, :-2])/(2*dx)
    Sy = (Zbc[2:,1:-1] - Zbc[:-2, 1:-1])/(2*dx)

    return Sx, Sy

#  Here I called a function that we have yet to define, 'assignBCs'. This function takes a numpy array and returns a numpy array that has an additional row and column before and after those specified in the input array. Its nice to create a seperate function for this, as we may want to get smarter with how we define our boundary conditions later. For now, since we are just trying to visualize slopes, lets just repeat the values on the edges of the array. This isn't a great approach (we'll fix it later) - but it only effects things at the margins. Here is what that would look like:
def assignBCs(elevGrid):
    # Pads the boundaries of a grid
    # Boundary condition pads the boundaries with equivalent values
    # to the data margins, e.g. x[-1,1] = x[1,1]
    # This creates a grid 2 rows and 2 columns larger than the input

    ny, nx = elevGrid.shape  # Size of array
    Zbc = np.zeros((ny + 2, nx + 2))  # Create boundary condition array
    Zbc[1:-1,1:-1] = elevGrid  # Insert old grid in center

    #Assign boundary conditions - sides
    Zbc[0, 1:-1] = elevGrid[0, :]
    Zbc[-1, 1:-1] = elevGrid[-1, :]
    Zbc[1:-1, 0] = elevGrid[:, 0]
    Zbc[1:-1, -1] = elevGrid[:,-1]

    #Assign boundary conditions - corners
    Zbc[0, 0] = elevGrid[0, 0]
    Zbc[0, -1] = elevGrid[0, -1]
    Zbc[-1, 0] = elevGrid[-1, 0]
    Zbc[-1, -1] = elevGrid[-1, 0]

    return Zbc

#  Sweet, we've got ourselves a slope grid (well, two slope grids actually one in the x and y direction). Lets get to visualizing. ESRI nicely summarizes the calculation of hillshades on their website. This is how we could calculate one in python, using our newly created function to find slopes and given the dem, grid spacing, and information about the lighting angle:
def calcHillshade(elevGrid,dx,az,elev):
    #Hillshade = calcHillshade(elevGrid,az,elev)
    #Esri calculation for generating a hillshade, elevGrid is expected to be a numpy array

    # Convert angular measurements to radians
    azRad, elevRad = (360 - az + 90)*np.pi/180, (90-elev)*np.pi/180
    Sx, Sy = calcFiniteSlopes(elevGrid, dx)  # Calculate slope in X and Y directions

    AspectRad = np.arctan2(Sy, Sx) # Angle of aspect
    SmagRad = np.arctan(np.sqrt(Sx**2 + Sy**2))  # magnitude of slope in radians

    return 255.0 * ((np.cos(elevRad) * np.cos(SmagRad)) + (np.sin(elevRad)* np.sin(SmagRad) * np.cos(azRad - AspectRad)))


import numpy as np
import pandas as pd

from string import Template

np.set_printoptions(suppress=True)

template = Template('#' * 10 + ' $string ' + '#' * 10)

# Generating section starts #

print(template.substitute(string='Generate array from 3 to 25 with step 2'))
array = np.arange(3, 25, 2)
print(array)

print(template.substitute(string='Generate array of 13 elements, filled with ones'))
array = np.ones(13, dtype='float')
print(array)

print(template.substitute(string='Generate matrix 3x7x3, filled with zeros'))
array = np.zeros((3, 7, 3), dtype='uint')
print(array)

print(template.substitute(string='Generate evenly spaced numbers from 3 to 8 over a specified interval'))
array = np.linspace(3, 8, 5)
print(array)

print(template.substitute(string='Generate random floats in the half-open interval [0.0, 1.0)'))
array = np.random.random((2, 2, 2))
print(array)

print(template.substitute(string='Generate random integers from low (inclusive) to high (exclusive).'))
array = np.random.randint(23.1, 50, (5, 5))
print(array)

print(template.substitute(string='Generate a new array of given shape and type, without initializing entries.'))
array = np.empty(10)
print(array)

# Generating section ends #
# Indexing section starts #

array = np.arange(12)
array.shape = (3, 4)

print(template.substitute(string='Generate the ndarray'))
print(array)

print(template.substitute(string='Indexing the last row'))
print(array[2])
print(array[-1])

print(template.substitute(string='Indexing the third row'))
print(array[:, 2])

print(template.substitute(string='Indexing the element'))
print(array[2, 2])
print(array[-1, -2])

print(template.substitute(string='Create the sub-array'))
print(array[0][0:3:2])

# Indexing section ends #
# Arithmetic operations section starts #

array = np.arange(1, 9, dtype='float')
array_1 = np.arange(1, 9, dtype='int')
print(template.substitute(string='Generate the ndarray'))
print(array)

print(template.substitute(string='Adding'))
print(array + 3.1)
print(np.add(array, array_1 + np.ones(8)))

print(template.substitute(string='Subtracting'))
print(array - 3)
print(np.subtract(array, array_1 + np.ones(8)))

print(template.substitute(string='Multiplying'))
print(array * 2.5)
print(np.multiply(array, array_1 + np.ones(8)))

print(template.substitute(string='Dividing'))
print(array / .5)
print(np.divide(array, array_1))

print(template.substitute(string='Powering'))
print(array ** 2)
print(np.power(array, np.ones(8)))
print(np.power(np.array([10, 100, 1000]), np.array([3, 2, 1])))

print(template.substitute(string='Remainder of division'))
print(array % 2)
print(np.mod(array, array_1))

print(template.substitute(string='Convert to negative values'))
print(-array)
print(np.negative(array - 3))

print(template.substitute(string='Complex operations'))
print(((4 * array + 2) ** 1.5))

print(template.substitute(string='Reduce'))
print(np.add.reduce(array))
print(np.subtract.reduce(array))
print(np.multiply.reduce(array))
print(np.divide.reduce(array))
print(np.power.reduce(array))

print(template.substitute(string='Accumulate'))
print(np.add.accumulate(array))
print(np.subtract.accumulate(array))
print(np.multiply.accumulate(array))
print(np.divide.accumulate(array))
print(np.power.accumulate(array))

print(template.substitute(string='Outer'))
array = np.arange(1, 10)
print(np.multiply.outer(array, array))

# Arithmetic operations section ends #
# Operations with data section starts #

data = pd.read_csv('../../data/iris.csv')
petal = np.array(data['petal_width'])

print(template.substitute(string='Dataframe from pandas'))
print(petal)

print(template.substitute(string='Minimum and maximum from dataframe'))
print(petal.min(), petal.max())

print(template.substitute(string='Arithmetic mean'))
print(petal.mean())

print(template.substitute(string='Median'))
print(np.median(petal))

print(template.substitute(string='Standard deviation'))
print(np.std(petal))

print(template.substitute(string='Dispersion'))
print(np.var(petal))

print(template.substitute(string='Percentiles'))
print(np.percentile(petal, 25))
print(np.percentile(petal, 75))
# Operations with data section ends #


"""
Problem 1:
"""
import time
from scipy.special import factorial
import numpy as np
from matplotlib import pyplot as plt

class SineExpand:
    things = 'terms'

    def __init__(self, num_terms):
        t0 = time.perf_counter()
        n = np.arange(num_terms)
        self.coeff = (-1)**n / factorial(2*n+1)
        self.power = 2*n + 1
        t1 = time.perf_counter()
        self.overhead = t1 - t0

    def evaluate(self, x):
        """x must be a 1D numpy array"""
        t0 = time.perf_counter()
        coeff = np.tile(self.coeff, (x.shape[0], 1)).T
        power = np.tile(self.power, (x.shape[0], 1)).T
        t1 = time.perf_counter()
        result = np.sum(coeff[:] * x**power[:], axis=0)
        t2 = time.perf_counter()
        rmserror = np.sqrt(np.mean((np.sin(x)-result)**2))
        overhead = self.overhead
        evaltime = t2 - t0
        return rmserror, evaltime/x.shape[0], overhead

class SineInterp:
    things = 'points'

    def __init__(self, num_terms):
        t0 = time.perf_counter()
        self.x = np.linspace(0, 2*np.pi, num_terms)
        self.y = np.sin(self.x)
        t1 = time.perf_counter()
        self.overhead = t1 - t0

    def evaluate(self, x):
        """x must be a 1D numpy array"""
        t0 = time.perf_counter()
        result = np.interp(x, self.x, self.y)
        t1 = time.perf_counter()
        rmserror = np.sqrt(np.mean((np.sin(x)-result)**2))
        evaltime = t1 - t0
        return rmserror, evaltime/x.shape[0], self.overhead

def test_sine(sine_class, N_vals):
    x_vals = np.random.default_rng().random(10**6) * 2 * np.pi
    rmserror = np.zeros(N_vals.shape[0])
    evaltime = np.zeros(N_vals.shape[0])
    overhead = np.zeros(N_vals.shape[0])
    for i, N in enumerate(N_vals):
        sine = sine_class(N)
        rmserror[i], evaltime[i], overhead[i] = sine.evaluate(x_vals)
    plt.figure()
    plt.subplot(121, title='Accuracy', xlabel=f'Number of {sine_class.things}', ylabel='RMS Error')
    plt.yscale('log')
    plt.plot(N_vals, rmserror)
    plt.subplot(122, title='Speed', xlabel=f'Number of {sine_class.things}', ylabel='Time (sec)')
    plt.plot(N_vals, evaltime, label='Evaluation')
    plt.plot(N_vals, overhead, label='Overhead')
    plt.yscale('log')
    plt.legend()
    return rmserror, evaltime, overhead

if __name__ == '__main__':
    print('COMMIT!!!')
    err1, et1, ot1 = test_sine(SineExpand, np.arange(1, 11))
    err2, et2, ot2 = test_sine(SineInterp, np.geomspace(3, 500, 100, dtype='i'))
    plt.figure()
    plt.subplot(121, title='Error vs evaluation time', ylabel='Evaluation time (s)', xlabel='Absolute error',
                xscale='log', yscale='log')
    plt.plot(err1, et1, label='Taylor expansion')
    plt.plot(err2, et2, label='Linear interpolation')
    plt.legend()
    plt.subplot(122, title='Error vs overhead time', ylabel='Overhead time (s)', xlabel='Absolute error',
                xscale='log', yscale='log')
    plt.plot(err1, ot1, label='Taylor expansion')
    plt.plot(err2, ot2, label='Linear interpolation')
    plt.legend()
    plt.show()


""" Implements the activation functions used by artificial neurons.

@author: Gabriel G. Nogueira (Talendar)
"""

import numpy as np
from abc import ABC, abstractmethod

class ActivationFunction(ABC):
    """ Abstract class defining the basic interface of an activation function. """

    @abstractmethod
    def __call__(self, z, derivative=False):
        """ Computes the function. """
        raise NotImplementedError("This method wasn't implemented!")

class SigmoidActivation(ActivationFunction):
    """ Sigmoid activation function. """

    def __call__(self, z, derivative=False):
        """ Computes the function. """
        if not derivative:
            return 1 / (1 + np.exp(-z))
        return np.exp(-z) / ( (1 + np.exp(-z))**2 )

class ReluActivation(ActivationFunction):
    """ Rectifier activation function, used by ReLU (rectified linear unit) neurons. """

    def __call__(self, z, derivative=False):
        """ Computes the function. """
        if not derivative:
            return np.maximum(0, z)
        return np.ceil(np.clip(z, 0, 1))

class LinearActivation(ActivationFunction):
    """ Linear activation function. """

    def __call__(self, z, derivative=False):
        """ Computes the function. """
        if not derivative:
            return z
        return 1

def create_by_name(name):
    """ Creates an instance of the cost function with the given name. """
    name = name.lower()
    if name == "sigmoid":
        return SigmoidActivation()
    if name == "relu":
        return ReluActivation()
    if name == "linear":
        return LinearActivation()

    raise NameError("Activation function with name \"" + name + "\" not found!")


#!/usr/bin/python3
# -*- coding: utf-8 -*-

# scipy模块stats文档
# http://www.cnblogs.com/ttrrpp/p/6822214.html

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

def testNormal():# {{{
    """
    Normal Distribution (正态分布)
    正态分布是一种连续分布，其函数可以在实线上的任何地方取值。
    正态分布由两个参数描述：分布的平均值μ和方差σ2 。

    mu ---> loc
    sigma ---> scale

    """

    mu = 2
    sigma = 4
    xs = np.linspace(
            norm.ppf(0.01, loc=mu, scale=sigma),
            norm.ppf(0.99, loc=mu, scale=sigma),
            num=1000)

    # E(X) = mu, D(X) = sigma**2
    mean, var, skew, kurt = norm.stats(loc=mu, scale=sigma, moments='mvsk')
    print("mean: %.2f, var: %.2f, skew: %.2f, kurt: %.2f" % (mean, var, skew, kurt))

    fig, axs = plt.subplots(2, 2)

    # 显示pdf (norm.pdf)
    ys =norm.pdf(xs, loc=mu, scale=sigma)
    axs[0][0].plot(xs, ys, 'bo', markersize=5, label='norm.pdf')
    axs[0][0].legend()
    axs[0][0].set_title('mu = %.2f, sigma = %.2f' % (mu, sigma))

    # 显示pdf (manual)
    ys = np.exp(-((xs - mu)**2) / (2* sigma**2)) / (sigma * np.sqrt(2*np.pi))
    axs[0][1].plot(xs, ys, 'bo', markersize=5, label='cmp pdf')
    axs[0][1].legend()
    axs[0][1].set_title('mu = %.2f, sigma = %.2f' % (mu, sigma))

    # 显示cdf
    ys =norm.cdf(xs, loc=mu, scale=sigma)
    axs[1][0].plot(xs, ys, 'bo', markersize=5, label='norm.pdf')
    axs[1][0].legend()
    axs[1][0].set_title('mu = %.2f, sigma = %.2f' % (mu, sigma))

    # 随机变量RVS
    data = norm.rvs(loc=mu, scale=sigma, size = 1000)
    data = np.around(data, decimals=1)
    import sys
    sys.path.append("../../thinkstats")
    import Pmf
    pmf = Pmf.MakePmfFromList(data)
    xs, ys = pmf.Render()
    #  axs[1][1].plot(xs, ys, 'bo', markersize=5, label='rvs pmf')
    axs[1][1].scatter(xs, ys, label='rvs pmf')
    axs[1][1].legend()

    plt.show()
# }}}

if __name__ == "__main__":
    testNormal()


"""Softmax.
For a given scores as n-dimensional array of where each column represents a sample
the softmax should return the probabilities of same n-dimensional array with same shape.
The probabilities for each sample (column) must sum to 1

Softmax Function S(yi) = exp(yi) / Sigsum(exp(yi))
"""

scores = [3.0, 1.0, 0.2]
import numpy as np
import math

def softmax(x):
    """Compute softmax values for each sets of scores in x."""
    # The longest way, some bug in code even though the approach is same to calculate softmax
    """
    probabilities_array = list()
    x_contains_array_el = False
    x_denom = 0
    for ele in x:
        if isinstance(ele, np.ndarray):
            x_contains_array_el = True
            tmp_probabilities_array = list()
            tmp_el_denom = 0
            for el in ele:
                tmp_el_denom += math.exp(el)
            for el in ele:
                tmp_prb = math.exp(el) / tmp_el_denom
                tmp_probabilities_array.append(tmp_prb)
            probabilities_array.append(tmp_probabilities_array)
        # Assuming either it is just a single value list or its a pure numpy array with defined shape.
        else:
            #x_denom += math.exp(ele)
            x_denom += ele
    if not x_contains_array_el:
        for ele in x:
            probability = math.exp(ele) / x_denom
            probabilities_array.append(probability)
    return np.array(probabilities_array)
    """
    # The shortest way and the efficient way as per tutorial.
    return np.exp(x) / np.sum(np.exp(x), axis=0)

print(softmax(scores))
#scores = np.array([3.0, 1.0, 0.2])

# Multiply Scores with 10
# If we multiply the scores by 10, the probabilites go close to 1.0 or 0.0
#print(softmax(scores * 10))

# Divide Scores with 10
# If we divide the scores by 10, the probabilities go to uniform distribution,
# reaching to value of 1/len(array). Ex: for scores with 3 elements, it gets close to 1/3 => 0.333
#print(softmax(scores / 10))

# Plot softmax curves
import matplotlib.pyplot as plt
x = np.arange(-2.0, 6.0, 0.1)
scores = np.vstack([x, np.ones_like(x), 0.2 * np.ones_like(x)])
plt.plot(x, softmax(scores).T, linewidth=2)
plt.show()


import numpy as np
import torch
import itertools

def spearman_footrule_distance(s, t, normalized=True):
    """
    Computes the Spearman footrule distance between two full lists of ranks:

    F(s,t) = sum[ |s(i) - t(i)| ]/S,

    the normalized sum over all elements in a set of the absolute difference between
    the rank according to s and t. As defined, 0 <= F(s,t) <= 1.

    S is a normalizer which is equal to 0.5*len(s)^2 for even length ranklists and
    0.5*(len(s)^2 - 1) for odd length ranklists.

    If s,t are *not* full, this function should not be used. s,t should be array-like
    (lists are OK).
    From: https://github.com/thelahunginjeet/kbutil/blob/master/statistics.py
          @author: Kevin S. Brown, University of Connecticut
    """
    # print("*"*20)
    # print(s)
    # print(t)
    # print("*"*20)
    if isinstance(s, list) and isinstance(t, list):
        # check that size of intersection = size of s,t?
        assert len(s) == len(t)
        s_len = len(s)
        sdist = sum(abs(np.asarray(s) - np.asarray(t)))
    elif isinstance(s, np.ndarray) and isinstance(t, np.ndarray):
        assert s.size == t.size
        s_len = s.size
        #sdist = sum(abs(s - t))
        sdist = np.abs(s - t).sum()
    elif isinstance(s, torch.Tensor) and isinstance(t, torch.Tensor):
        assert s.size == t.size
        s_len = s.size()[0]
        sdist = (s - t).abs().sum().item()
    else:
        raise TypeError(
            "Boot inputs should be of type 'list', 'array' or 'tensor'."
        )
    # c will be 1 for odd length lists and 0 for even ones
    if normalized:
        c = s_len % 2
        normalizer = 0.5 * (s_len ** 2 - c)
        sdist = sdist / normalizer

    return sdist

def kendall_tau_distance(s, t, normalized=True, both=False):
    """
    Computes the Kendall's tau distance bertween two full list of ranks:
    K(s,t) =
    """
    assert len(s) == len(t)
    pairs = itertools.combinations(range(0, len(s)), 2)
    kt_distance = 0
    for x, y in pairs:
        a = s.index(x) - s.index(y)
        b = t.index(x) - t.index(y)
        if (a*b < 0):
            kt_distance += 1

    normalizer = len(s)*(len(s)-1)/2
    if normalized and both:
        return (kt_distance,  kt_distance/normalizer)
    elif normalized:
        return kt_distance/normalizer

    return kt_distance


"""
File: integrate_exp.py
Copyright (c) 2016 Andrew Malfavon
License: MIT
Exercise B.6
Description: approximate an integral with the Trapezoidal method.
"""

import numpy as np
import matplotlib.pyplot as plt
import pandas as pd

#graphs the function to visually see the symmetry
def plot(n):
    x = np.linspace(-10, 10, n)
    y = np.exp(-x**2)
    plt.plot(x, y)
    plt.xlabel('x')
    plt.ylabel('y')
    plt.title('$e^{-x^2}$')

#trapezoidal method to approximate the integral
def T(n, L):
    x = np.linspace(0, L, n)#takes just the positive x-values
    y = np.exp(-x**2)
    h = L / float(2*n)
    endpoints = y[0] + y[n - 1]
    f = 0
    for i in range(1, n - 1):
        f +=  2 * y[i]
    approx = h * (endpoints + f)
    return 2 * approx#multiplies by two to account for the negative x-values

def table():
    L_array = np.array(['L = 2', 'L = 4', 'L = 6', 'L = 8', 'L = 10'])#used to label the table
    n_array = np.array(['n = 100', 'n = 200', 'n = 300', 'n = 400', 'n = 500'])#used to label the table
    n = [100, 200, 300, 400, 500]
    L = [2, 4, 6, 8, 10]
    T_matrix = np.zeros([5, 5])#a five-by-five matrix with all zeros
    array = []
    counter = 0
    for elem_n in n:
        for elem_L in L:
            array.append(T(elem_n, elem_L))#plugs in each value of n and L and puts them in an array
    for i in range(5):
        for j in range(5):
            T_matrix[i, j] = array[counter]#arranges the array into a five-by-five matrix
            counter += 1
    table = pd.DataFrame(T_matrix, index = n_array, columns = L_array)#puts the matrix into a table and lables the axes
    return table

#same thing as before except it displays the error
def error_table():
    L_array = np.array(['L = 2', 'L = 4', 'L = 6', 'L = 8', 'L = 10'])
    n_array = np.array(['n = 100', 'n = 200', 'n = 300', 'n = 400', 'n = 500'])
    n = [100, 200, 300, 400, 500]
    L = [2, 4, 6, 8, 10]
    T_error_matrix = np.zeros([5, 5])
    array = []
    counter = 0
    for elem_n in n:
        for elem_L in L:
            array.append(np.sqrt(np.pi) - T(elem_n, elem_L))#error from the known analytic solution
    for i in range(5):
        for j in range(5):
            T_error_matrix[i, j] = array[counter]
            counter += 1
    error_table = pd.DataFrame(T_error_matrix, index = n_array, columns = L_array)
    return error_table

#Test for each value of n and L the error between the known solution and the approximation is less than 0.1
def test():
    n = [100, 200, 300, 400, 500]
    L = [2, 4, 6, 8, 10]
    test_array = []
    for elem_n in n:#creates an array with the error for each combination of n and L
        for elem_L in L:
            test_array.append(abs(np.sqrt(np.pi)- T(elem_n, elem_L)))
    for i in range(len(test_array)):#tests each spot in the array
        assert test_array[i] < 0.1


import numpy as np

def gini(values_arg, n = None):
    """
    Calculates the gini_coeff using the formula shown in assets/gini_formula.png

    Remind that len(values) here corresponds to n+1 there in that formula and remind that idx here corresponds to i-1 in the formula,
    since python lists are zero-indexed and the formula is one-indexed
    this can be confusing.

    @type  values: a Python list or np.array of numbers to calculate gini coefficient on
    """

    if n is None:
        n = len(values_arg)

    values = np.array(values_arg)

    values.sort() # sort in-place in ascending order

    sum_numerator = 0
    sum_denominator = 0
    for idx in range(0, n):
        i = idx + 1 # idx here corresponds to i-1 in the formula, since python lists are zero-indexed
        sum_numerator += (n + 1 - i) * values[idx]
        sum_denominator += values[idx]
    if sum_denominator == 0:
        return np.NaN

    g_coeff = n + 1 - 2*(sum_numerator/sum_denominator)

    return g_coeff

def gini_corrected(values_arg, n = None):
    """
    Calculates the gini_coeff with a correction for small datasets

    @type  values: a Python list or np.array of numbers to calculate gini coefficient on
    """

    if n is None:
        n = len(values_arg)

    if n < 2: # Don't calculaute Gini for populations with less than one individual
        return np.NaN

    # compute gini coefficient
    g_coeff = gini(values_arg, n)

    # Now, apply (Deltas, 2003 correction) for small datasets:
    # (https://doi.org/10.1162/rest.2003.85.1.226)
    g_coeff *= (1.0 / (n - 1))

    return g_coeff


"""
Created on Sat Jan 30 19:50:14 2016
"""
#------------------------------------------------------------------------------
#CHAPTER 5:  The Pseudospectral Method
#------------------------------------------------------------------------------
import numpy as np
from numpy.fft import *
import matplotlib.pyplot as plt

# Basic parameters
nx = 128
xo = np.pi
#%% CODE 03: Listing 5.1 Fourier Derivative - Pag 107
# [...]
# Fourier 1st derivative
def fourier_derivative(f, dx):
    # Length of vector f
    nx = f.size
    # Initialize k vector up to Nyquist wavenumber
    kmax = np.pi/dx
    dk = kmax/(nx/2)
    k = np.arange(float(nx))
    k[: nx/2] = k[: nx/2] * dk
    k[nx/2 :] = k[: nx/2] - kmax
    # Fourier derivative
    ff = fft(f); ff = 1j*k*ff
    df_num = ifft(ff).real
    return df_num
# [...]
# Initialize Gauss function
x, dx = np.linspace(2*np.pi/nx, 2*np.pi, nx, retstep=True)
sigma = .5
f = np.exp(-1/sigma**2 * (x - xo)**2)
# Calculate derivative of vector f
df_num = fourier_derivative(f, dx)

# Analytical derivative
df_ana = -2*(x-xo)/sigma**2 * np.exp(-1/sigma**2 * (x-xo)**2)

# Plot Fuctions
plt.subplot(2,1,1)
plt.plot(x,f,color="blue", lw = 1.5)
plt.xlabel('$x$')
plt.ylabel('$f(x)$')

plt.subplot(2,1,2)
plt.plot(x,df_ana,color="blue", lw = 1.5, label='Analytical')
plt.plot(x,df_num,color="black", lw = 1.5, label='Numerical')
plt.plot(x,1e13*(df_ana-df_num),color="red", lw = 1.5, label='Difference')
plt.legend(loc='upper right', shadow=True)
plt.xlabel('$x$')
plt.ylabel('$\partial_x f(x)$')
plt.axis([2*np.pi/nx,2*np.pi,-2,2])

plt.show()
plt.savefig('Fig_5.9.png')


import sys
import scipy
import numpy as np
from scipy.stats import chi2_contingency

def chi2Proportions(count,nobs):
    """
    A wrapper for the chi2 testing proportions based upon the chi-square test

    Args:
        count (:obj `list` of :obj`int` or a single `int`):  the number of successes in nobs trials. If this is
        array_like, then the assumption is that this represents the number of successes
        for each independent sample

        nobs (:obj `list` of :obj`int` or a single `int`):  The number of trials or observations, with the same length as count.

    Returns:
        chi2  (:obj `float`): The test statistic.

        p (:obj `float`): The p-value of the test

        dof (int) : Degrees of freedom

        expected (:obj `list`): list same shape as observed. The expected frequencies, based on the marginal sums of the table

    References:
    [1] "scipy.stats.chi2_contingency" https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.chi2_contingency.html
    [2] "statsmodels.stats.proportion.proportions_chisquare"  https://www.statsmodels.org/dev/generated/statsmodels.stats.proportion.proportions_chisquare.html
    [3]	(1, 2) “Contingency table”, https://en.wikipedia.org/wiki/Contingency_table
    [4]	(1, 2) “Pearson’s chi-squared test”, https://en.wikipedia.org/wiki/Pearson%27s_chi-squared_test
    [5]	(1, 2) Cressie, N. and Read, T. R. C., “Multinomial Goodness-of-Fit Tests”, J. Royal Stat. Soc. Series B, Vol. 46, No. 3 (1984), pp. 440-464.

    Sample use:
        input:
        [10,10,20] - number of successes in trial
        [20,20,20] - number of trials
        chi2Proportions([10,10,20], [20,20,20])

        output:
        (2.7777777777777777,
        0.24935220877729619,
        2,
        array([[ 12.,  12.,  16.],
            [ 18.,  18.,  24.]]))
    """

    obs = np.array([count, nobs])
    print(obs)
    try:
        return chi2_contingency(obs, correction=False)

    except Exception as e:
        print("Exception: {}, returning int max array".format(e))
        int_max  = sys.maxsize
        return [int_max, int_max,int_max,int_max]

if __name__ == "__main__":
    print(chi2Proportions([10,10,20], [20,20,20]))


import numpy as np
# from rnn_utils import *

def softmax(x):
    e_x = np.exp(x - np.max(x))
    return e_x / e_x.sum(axis=0)

def rnn_cell_forward(xt,a_prev,parameters):

    Waa = parameters['Waa']
    Wax = parameters['Wax']
    Way = parameters['Wya']
    ba = parameters['ba']
    by = parameters['by']

    a_next = np.tanh(np.dot(Waa,a_prev) + np.dot(Wax,xt) + ba)
    yt_pred = softmax(np.dot(Way,a_next) + by)
    cache = (a_next, a_prev, xt, parameters)

    return a_next, yt_pred, cache

# np.random.seed(1)
# xt_tmp = np.random.randn(3,10)
# a_prev_tmp = np.random.randn(5,10)
# parameters_tmp = {}
# parameters_tmp['Waa'] = np.random.randn(5,5)
# parameters_tmp['Wax'] = np.random.randn(5,3)
# parameters_tmp['Wya'] = np.random.randn(2,5)
# parameters_tmp['ba'] = np.random.randn(5,1)
# parameters_tmp['by'] = np.random.randn(2,1)

# a_next_tmp, yt_pred_tmp, cache_tmp = rnn_cell_forward(xt_tmp, a_prev_tmp, parameters_tmp)
# print("a_next[4] = \n", a_next_tmp[4])
# print("a_next.shape = \n", a_next_tmp.shape)
# print("yt_pred[1] =\n", yt_pred_tmp[1])
# print("yt_pred.shape = \n", yt_pred_tmp.shape)

def rnn_forward(x, a0, parameters):
    caches=[]
    n_x, m, T_x = x.shape
    n_y, n_a = parameters['Wya'].shape

    a = np.zeros(shape=(n_a, m, T_x))
    y_pred = np.zeros(shape=(n_y, m, T_x))

    a_next = a0

    for t in range(T_x):
        xt = x[:,:,t]
        a_next, yt_pred, cache = rnn_cell_forward(xt, a_next, parameters)
        a[:,:,t] = a_next
        y_pred[:,:,t] = yt_pred
        caches.append(cache)
    caches = (caches, x)

    return a, y_pred, caches

np.random.seed(1)
x_tmp = np.random.randn(3,10,4)
a0_tmp = np.random.randn(5,10)
parameters_tmp = {}
parameters_tmp['Waa'] = np.random.randn(5,5)
parameters_tmp['Wax'] = np.random.randn(5,3)
parameters_tmp['Wya'] = np.random.randn(2,5)
parameters_tmp['ba'] = np.random.randn(5,1)
parameters_tmp['by'] = np.random.randn(2,1)

a_tmp, y_pred_tmp, caches_tmp = rnn_forward(x_tmp, a0_tmp, parameters_tmp)
print("a[4][1] = \n", a_tmp[4][1])
print("a.shape = \n", a_tmp.shape)
print("y_pred[1][3] =\n", y_pred_tmp[1][3])
print("y_pred.shape = \n", y_pred_tmp.shape)
print("caches[1][1][3] =\n", caches_tmp[1][1][3])
print("len(caches) = \n", len(caches_tmp))


"""Linear Regression Algorithm"""
from statistics import mean
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import style
import random

style.use('fivethirtyeight')

def create_data(r, variance, step=2, correlation=False):
    """Generate a random data

    -- Arguments
    range: int, required
        range of the data

    variance: float, required
        range of variance of the random data

    step: int, optional
        value to generate correlational data

    correlation: 'pos', 'neg' or False, optional, default False
        Determines if the data will have
        a positive or negative correlation or None

    -- Returns
        The generated data as a float64 numpy array
    """
    val = 1
    ys = []
    for _ in range(r):
        y = val + random.randrange(-variance, variance)
        ys.append(y)
        if correlation and correlation == 'pos':
            val += step
        elif correlation and correlation == 'neg':
            val -= step

    xs = [i for i in range(len(ys))]

    return np.array(xs, dtype=np.float64), np.array(ys, dtype=np.float64)

def best_fit_slope_and_intercept(xs, ys):
    """Calculate the slope and the Y intercept of the best fit line

    -- Returns
        m: slope
        b: Y intercept
    """
    m = ( (mean(xs) * mean(ys) - (mean(ys*xs)))
        / (mean(xs)*mean(xs) - mean(xs*xs)) )
    b = mean(ys) - m * mean(xs)
    return m, b

def squared_error(ys_orig, ys_line):
    return sum((ys_line-ys_orig)**2)

def coefficient_of_determination(ys, regression_line):
    y_mean_line = [mean(ys) for y in ys]
    squared_error_regr = squared_error(ys, regression_line)
    squared_error_y_mean = squared_error(ys, y_mean_line)
    return 1 - (squared_error_regr/squared_error_y_mean)

if __name__ == '__main__':
    #Data
    xs, ys = create_data(40, 20, 2, correlation='pos')

    #Calculation
    m, b = best_fit_slope_and_intercept(xs, ys)
    regression_line = [(m*x) + b for x in xs]

    #Prediction
    predict_x = 41
    predict_y = (m*predict_x) + b

    #Accuracy
    r_squared = coefficient_of_determination(ys, regression_line)
    print('Acuracy:', r_squared)

    plt.scatter(xs,ys)
    plt.scatter(predict_x, predict_y, s=100, color='g')
    plt.plot(xs, regression_line)
    plt.show()


import numpy as np

class GD:
    def __init__(self, lr=0.1):
        self.lr = lr

    def apply_grads(self, W, grad_W):
        W = W - self.lr * grad_W
        return W

class LR:
    def __init__(self, num_features=1, optimizer=GD(0.1)):
        self.W = np.zeros((num_features, 1))
        self.optimizer = optimizer

    def predict(self, X):
        y = X @ self.W
        return y

    def one_step_opt(self, X, y_true):
        grads = - X.T @ (y_true - X @ self.W) / X.shape[0]
        self.W = self.optimizer.apply_grads(self.W, grads)
        loss = (y_true - self.predict(X)).T @ (y_true - self.predict(X))
        return loss, grads

    def fit(self, X, y_true, grad_tol=0.0001, n_iters=1000):
        grad_norm = np.inf
        n_iter = 0
        losses = []
        while (grad_norm > grad_tol) and (n_iter < n_iters):
            loss, grads = self.one_step_opt(X, y_true)
            grad_norm = np.linalg.norm(grads)
            n_iter += 1
            losses.append(loss[0][0])
        return losses

    def fit_closed_form(self, X, y_true):
        self.W = np.linalg.inv(X.T @ X) @ X.T @ y_true
        loss = (y_true - self.predict(X)).T @ (y_true - self.predict(X)) / X.shape[0]
        return loss


import numpy as np
import math
import matplotlib.pyplot as plt
from numpy import linalg as LA

def ellipse_gen(a,b,center):
    len = 50
    theta = np.linspace(0,2*np.pi,len)
    x_ellipse = np.zeros((2,len))
    x_ellipse[0,:] = a*np.cos(theta) + center[0]   #acos(t)+h
    x_ellipse[1,:] = b*np.sin(theta) + center[1]   #asin(t)+k
    return x_ellipse

#setting up plot
fig = plt.figure()
ax = fig.add_subplot(111, aspect='equal')
len = 100
y = np.linspace(-5,5,len)

#Ellipse parameters
V = np.array(([13,-9],[-9,37]))
u = np.array(([1,7]))
f = -2

#Computation
utV_1u = np.matmul(np.matmul(u.T,np.linalg.inv(V)),u)
c = -np.linalg.inv(V)@u

#Eigenvalues and eigenvectors
D_vec,P = np.linalg.eig(V)
D = np.diag(D_vec)
a = np.sqrt((utV_1u-f)/D_vec[0])
b = np.sqrt((utV_1u-f)/D_vec[1])
xStandardEllipse = ellipse_gen(a,b,[0,0])

#Major and Minor Axes
MajorStandard = np.array(([a,0]))
MinorStandard = np.array(([0,b]))

#Affine transform
Cs = np.array([[c[0],c[1]] for i in range(50)]).T
xActualEllipse = P@xStandardEllipse + Cs # x = Py + c (Affine Transformation)
MajorActual = P@MajorStandard+c[0]
MinorActual = P@MinorStandard+c[1]

#Plotting the standard ellipse
plt.plot(xStandardEllipse[0,:],xStandardEllipse[1,:],label='Standard Ellipse')

#Plotting the actual ellipse
plt.plot(xActualEllipse[0,:],xActualEllipse[1,:],label='Actual Ellipse')

#Labeling the coordinates
tri_coords = np.vstack((MajorStandard,MinorStandard,MajorActual,MinorActual,c)).T
plt.scatter(tri_coords[0,:], tri_coords[1,:])
vert_labels = ['$a$','$b$','$a^{\prime}$','$b^{\prime}$','$\mathbf{c}$']
for i, txt in enumerate(vert_labels):
    plt.annotate(txt, # this is the text
                 (tri_coords[0,i], tri_coords[1,i]), # this is the point to label
                 textcoords="offset points", # how to position the text
                 xytext=(0,10), # distance from text to points (x,y)
                 ha='center') # horizontal alignment can be left, right or center

# Axis Plots
x_ = np.linspace(-0.9,0.9,50)
y_ = np.linspace(-0.6,0.6,50)
y_1 = [0 for i in range(50)]
x_1 = [0 for i in range(50)]
plt.plot(x_, y_1, 'black')
plt.plot(x_1,y_, 'black')

plt.xlabel('$X Axis$')
plt.ylabel('$Y Axis$')
plt.legend(loc='best')
plt.grid()
plt.axis('equal')

plt.show()


import math
import random
import numpy as np

class KMeans:
    def __init__(self, num_categories, tolerance=0.0001, max_iterations=500):
        self._classes = dict()
        self._centroids = dict()
        self._k = num_categories
        self._tolerance = tolerance
        self._max_iterations = max_iterations

    def predict(self, inputs):
        """
        Predict class for given inputs
        :param inputs: list(float) -> input values for prediction
        :return: int -> classification
        """
        inputs = [np.array(inp) for inp in inputs]
        distances = [np.linalg.norm(inputs - self._centroids[centroid]) for centroid in self._centroids]
        classification = distances.index(min(distances))
        return classification

    def cluster(self, inputs):
        """
        Generate unsupervised classification clusters based on K-Means using euclidean distance
        :param inputs: list(float) -> input values for clustering
        """
        if len(inputs) < self._k:
            raise Exception('length of x must be larger than num_categories for method: KMeans.cluster')
        self._instantiate_centroids(inputs)
        for _ in range(self._max_iterations):
            self._classes = dict()
            for k in range(self._k):
                self._classes[k] = list()
            self._update_classes(inputs)
            for classification in self._classes:
                self._centroids[classification] = np.average(self._classes[classification], axis=0)
            if self._is_optimal():
                break

    def _is_optimal(self):
        """
        Determine if the given centroids are optimal
        :return: bool -> if centroids are optimal
        """
        is_optimal = True
        for centroid in self._centroids:
            original_centroid = dict(self._centroids)[centroid]
            curr = self._centroids[centroid]
            if np.sum((curr - original_centroid) / original_centroid * 100.0) > self._tolerance:
                is_optimal = False
        return is_optimal

    def _update_classes(self, inputs):
        """
        Update classes based on given inputs
        :param inputs: list(float) -> list of inputs to update classes on
        """
        for item in inputs:
            distances = [np.linalg.norm(item - self._centroids[centroid]) for centroid in self._centroids]
            classification = distances.index(min(distances))
            self._classes[classification].append(item)

    def _instantiate_centroids(self, inputs):
        """
        Instantiate centroids from random samples of inputs
        :param inputs: list(float) -> list of input values
        """
        inputs = [np.array(inp) for inp in inputs]
        random.shuffle(inputs)
        for cluster in range(self._k):
            self._centroids[cluster] = inputs[cluster]

    @staticmethod
    def _euclidean_distance(vector_1, vector_2):
        """
        Determine the euclidean distance between two vectors represented as lists
        :param vector_1: list(float) -> first vector in our equation
        :param vector_2: list(float) -> second vector in our equation
        :return: float -> euclidean distance
        """
        if len(vector_1) != len(vector_2):
            raise Exception('vectors must be same size for method: KMeans._euclidean_distance')
        return math.sqrt(sum((vector_1[i] + vector_2[i])**2 for i in range(len(vector_1))))


import numpy as np

__all__ = ['convolve']

def convolve(f,g,fft=True, periodic=True):
    """Compute the linear or circular convolution of two discrete data sets.

    For small problems, it is usually faster to perform the convolution
    directly. In this case, `convolve` defaults to the ``numpy.convolve``
    routine. For circular convolution, a copy of the smaller of the
    two arrays must be made. For larger problems, convolution is more
    efficiently performed as a multiplication in Fourier space followed by
    inversion. These transforms are performed using Fast Fourier Transform
    (FFT) methods. FFTs are fastest for intelligently chosen problem sizes
    (for example, powers of 2). If convolution performance is critical, both
    methods should be timed, and the optimal one selected.

    For linear convolutions with FFTs, `f` and `g` are zero-padded to
    ``len(f)+len(g)-1``. For circular convolutions, the period is taken to be
    the longer of `f` and `g`. If one is shorter than the other, it is
    zero-padded with ``numpy.pad``.

    Parameters
    ----------
    f : array_like
        First function to convolve

    g : array_like
        Second function to convolve

    fft : bool, optional
        If true, perform the convolution using FFTs.

    periodic : bool, optional
        If true, perform the circular convolution (periodic data).

    Returns
    -------
    out : ndarray
        The convolution of `f` and `g`

    Examples
    --------
    Take the linear convolution of two boxcar functions of even and odd lengths.
    >>> f = np.ones(5)
    >>> g = np.ones(2)
    >>> flyft.fft.convolve(f,g,periodic=False)
    [ 1.  2.  2.  2.  2.  1.]

    The convolution is returned in the proper order.
    >>> g[1] = 2
    >>> flyft.fft.convolve(f,g,periodic=False)
    [ 1.  3.  3.  3.  3.  2.]

    The convolution operator commutes.
    >>> flyft.fft.convolve(g,f,periodic=False)
    [ 1.  3.  3.  3.  3.  2.]

    """

    if not fft:
        if not periodic:
            return np.convolve(f,g)
        else:
            # replicate the smaller data set for circular convolution without FFT
            if len(f) < len(g):
                ff = np.concatenate((f,f))
                gg = g
                min_idx = len(f)
                max_idx = -len(f)+1
            else:
                ff = f
                gg = np.concatenate((g,g))
                min_idx = len(g)
                max_idx = -len(g)+1
            c = np.convolve(ff,gg)
            period = max(len(f),len(g))
            return c[period:-period+1]
    else:
        if not periodic:
            # the minimum length needed to pad to for linear convolution
            input_len = len(f) + len(g) - 1
        else:
            input_len = max(len(f),len(g))

        # fft the data
        F = np.fft.fft(f, n=input_len)
        G = np.fft.fft(g, n=input_len)

        # multiply and take the inverse
        c = np.fft.ifft(F*G)

        # extract the relevant parts
        return np.real(c)


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Sat Jul 10 15:20:56 2021

@author: alessandro
"""

import numpy as np
import scipy.linalg as spl

def lunopivot(A):
    m, n = A.shape

    U = A.copy()
    for k in range(n - 1):
        if U[k, k] == 0:
            print("elemento diagonale nullo")
            return [],[], False
        for i in range(k + 1, n):
            U[i, k] /= U[k, k]
            for j in range(k + 1, n):
                U[i, j] -= U[i, k] * U[k, j]
    L = np.tril(U, -1) + np.eye(n)
    U = np.triu(U)
    return L, U, True

def lsolve(L, b):
    m, n = L.shape
    x = np.zeros((n, 1))
    for i in range(n):
        s = np.dot(L[i, :i], x[:i])
        x[i] = (b[i] - s) / L[i, i]
    return x

def usolve(U, b):
    m, n = U.shape
    x = np.zeros((n, 1))
    for i in range(n - 1, -1, -1):
        s = np.dot(U[i, i + 1:], x[i + 1:])
        x[i] = (b[i] - s) / U[i, i]
    return x

def lusolve(A, b):
    L, U, flag = lunopivot(A)
    y = lsolve(L, b)
    x = usolve(U, y)
    return x

def lulusolve(L, U, b):
    y1 = lsolve(L, b)
    y2 = usolve(U, y1)
    y3 = lsolve(L, y2)
    y4 = usolve(U, y3)
    return y4

for n in range(5, 11):
    A = spl.pascal(n)
    b = np.dot(A.T, np.ones((n, 1)))
    c = np.dot(np.dot(A, A), np.ones((n, 1)))

    print(f"b = {b}")

    s1 = lusolve(A, b).T
    print(f"soluzione 1 = {s1}")

    #s2 = lusolve(np.dot(A, A), c).T
    #print(f"soluzione 2 = {s2}")

    L, U, flag = lunopivot(A)
    s2 = lulusolve(L, U, c).T
    print(f"soluzione 2 = {s2}")


# Raphson Method for solving equations
import numpy as np
# Given:
x0 = 1.0
e = 0.0001
N = 5
def f( x ):
	return np.exp(x) - (4 * x)
# Derivative of the above function
def g( x ):
	return np.exp(x) - 4
# Implementing Newton Raphson Method
def newtonRaphson(x0,E,N):
    print('\n\n*** NEWTON RAPHSON METHOD IMPLEMENTATION ***')
    step = 1
    flag = 1
    condition = True

    print('| Iteration |    x1         |     f(x1)    |')

    while condition:
        if g(x0) == 0.0:
            print('Divide by zero error!')
            break

        x1 = x0 - f(x0)/g(x0)

        print('| %d         |   %0.6f    |    %0.6f  |'  % (step, x1, f(x1)))

        x0 = x1
        step = step + 1

        if step > N:
            flag = 0
            break

        condition = abs(f(x1)) > e

    if flag==1:
        print('\nRequired root is: %0.8f' % x1)
    else:
        print('\nNot Convergent.')

# Starting Newton Raphson Method
newtonRaphson(x0,e,N)


from pylab import *
from scipy import interpolate, optimize
from numpy import *

# construct data point arrays first
h = array([0.0,1.525,3.050,4.575,6.100,7.625,9.150])
rho = array([1.0,0.8617,0.7385,0.6292,0.5328,0.4481,0.3741])

# Part A
# use Barycentric initally as the most direct path to an answer
print "At 2km, the polynomial, interpolated value is:"
print(str(interpolate.barycentric_interpolate(h,rho,2.0)))

print "And at 4km, the interpolated value is:"
print(str(interpolate.barycentric_interpolate(h,rho,4.0)))

print "And at 8km, the interpolated value is:"
print(str(interpolate.barycentric_interpolate(h,rho,8.0)))

# B: cubic spline
fit = interpolate.interp1d(h,rho,kind='cubic') #set up fit, called as fit(value)

print "At h=2, h=4 and h=8 respectively, using a cubic spline, rho="
print fit(2.0), ", ", fit(4.0), ", ",fit(8.0)

# C: errors
rho_actual = 0.67

print "Absolute error for polynomial interpolation:"
err_abso_poly = np.abs(interpolate.barycentric_interpolate(h,rho,4.0)-rho_actual)
print err_abso_poly
print "Relative error for polynomial interpolation:"
print np.abs((err_abso_poly)/rho_actual)

print "\nAbsolute error for cubic spline:"
err_abso_spline = np.abs((fit(4.0)-rho_actual))
print err_abso_spline
print "Relative error for cubic spline:"
print np.abs((err_abso_spline)/rho_actual)

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


import cmath
import matplotlib.pyplot as plt
import numpy as np
import scipy.signal as signal

# Datos del a)
fc = 1e3                      ; print('fc: {}'.format(fc))
w0 = 2. * np.pi * fc          ; print('w0: {}'.format(w0))
Q = 1. / np.sqrt(2.)          ; print('Q:  {}'.format(Q))
fs = 100e3                    ; print('fs: {}'.format(fs))

# Constantes utiles
alpha = w0 ** 2.              ; print('alpha: {}'.format(alpha))
beta = w0 * 2. * fs / Q       ; print('beta: {}'.format(beta))
gamma = 4. * (fs ** 2.)       ; print('gamma: {}'.format(gamma))

# Coeficientes del filtro
k = alpha / (alpha + beta + gamma) ; print('k: {}'.format(k))
num = k * np.array([1., 2., 1.]) ; print('Num: {}'.format(num))
den = np.array([1., 2*(alpha - gamma)/(alpha + beta + gamma), (alpha - beta + gamma)/(alpha + beta + gamma)]) ; print('Den: {}'.format(den))

# Respuesta del filtro
w, h = signal.freqz(num, den, worN = 512, whole = False)

# Respuesta en frecuencia del filtro
fig, axs = plt.subplots(2)
fig.suptitle('Respuesta de un Butterworth de 2do orden fc={}KHz y fs={}KHz'.format(fc, fs))
axs[0].set_title('Respuesta de modulo')
axs[0].semilogx(w, 20 * np.log10(abs(h)), 'b')
axs[0].set_ylabel('|H| [dB]', color='b')
axs[0].set_xlabel('Ω [rad/sample]')
axs[0].grid()

axs[1].set_title('Respuesta de fase')
axs[1].semilogx(w, [cmath.phase(hh) for hh in h], 'b')
axs[1].set_ylabel('arg(H) [rad]', color='b')
axs[1].set_xlabel('Ω [rad/sample]')
axs[1].grid()
plt.show()

# Polos y ceros del filtro
zeros = np.roots(num)
poles = np.roots(den)
print('Ceros: {}'.format(zeros))
print('Polos: {}'.format(poles))

fig, ax = plt.subplots()
fig.suptitle('Polos y ceros')
unit_circle = plt.Circle((0.,0.), 1., fill=False, color='black')
ax.add_patch(unit_circle)
ax.scatter([hh.real for hh in zeros], [hh.imag for hh in zeros], marker='o', color='b')
ax.scatter([hh.real for hh in poles], [hh.imag for hh in poles], marker='x', color='b')
ax.set_xlabel('x')
ax.set_ylabel('jy')
ax.grid()
plt.show()

# Ufff, cuantas lineas de codigo. Siempre es a mano???
num_s = [w0**2.]
den_s = [1., w0/Q, w0**2]

num_z, den_z = signal.bilinear(num_s, den_s, fs)
print('Num: {}'.format(num_z))
print('Den: {}'.format(den_z))


import numpy as np

# y = { 0 (b + w1x1 + w2x2 <= 0)
#       1 (b + w1x1 + w2x2 >  0)
def AND(x1, x2):
    # 入力
    x = np.array([x1, x2])
    # 重み
    w = np.array([0.5, 0.5])
    # バイアス
    b = -0.7
    tmp = np.sum(w*x) + b
    if tmp <= 0:
        return 0
    else:
        return 1

print("AND -----------------")
print("0, 0 -> " + str(AND(0, 0)))
print("0, 1 -> " + str(AND(0, 1)))
print("1, 0 -> " + str(AND(1, 0)))
print("1, 1 -> " + str(AND(1, 1)))

# 重みとバイアスがANDと異なる
def NAND(x1, x2):
    # 入力
    x = np.array([x1, x2])
    # 重み
    w = np.array([-0.5, -0.5])
    # バイアス
    b = 0.7
    tmp = np.sum(w*x) + b
    if tmp <= 0:
        return 0
    else:
        return 1

print("NAND ----------------")
print("0, 0 -> " + str(NAND(0, 0)))
print("0, 1 -> " + str(NAND(0, 1)))
print("1, 0 -> " + str(NAND(1, 0)))
print("1, 1 -> " + str(NAND(1, 1)))

# 重みとバイアスがANDと異なる
def OR(x1, x2):
    # 入力
    x = np.array([x1, x2])
    # 重み
    w = np.array([0.5, 0.5])
    # バイアス
    b = -0.2
    tmp = np.sum(w*x) + b
    if tmp <= 0:
        return 0
    else:
        return 1

print("OR ------------------")
print("0, 0 -> " + str(OR(0, 0)))
print("0, 1 -> " + str(OR(0, 1)))
print("1, 0 -> " + str(OR(1, 0)))
print("1, 1 -> " + str(OR(1, 1)))

# XORは非線形なので層を重ねて実現する(多層パーセプトロン)
def XOR(x1, x2):
    s1 = NAND(x1, x2)
    s2 = OR(x1, x2)
    y = AND(s1, s2)
    return y

print("XOR -----------------")
print("0, 0 -> " + str(XOR(0, 0)))
print("0, 1 -> " + str(XOR(0, 1)))
print("1, 0 -> " + str(XOR(1, 0)))
print("1, 1 -> " + str(XOR(1, 1)))


import numpy as np
import torch
from typing import Optional
from scipy.optimize import linear_sum_assignment

def cluster_accuracy(y_true, y_predicted, cluster_number: Optional[int] = None):
    """
    Calculate clustering accuracy after using the linear_sum_assignment function in SciPy to
    determine reassignments.

    :param y_true: list of true cluster numbers, an integer array 0-indexed
    :param y_predicted: list  of predicted cluster numbers, an integer array 0-indexed
    :param cluster_number: number of clusters, if None then calculated from input
    :return: reassignment dictionary, clustering accuracy
    """
    if cluster_number is None:
        cluster_number = max(y_predicted.max(), y_true.max()) + 1  # assume labels are 0-indexed
    count_matrix = np.zeros((cluster_number, cluster_number), dtype=np.int64)
    for i in range(y_predicted.size):
        count_matrix[y_predicted[i], y_true[i]] += 1

    row_ind, col_ind = linear_sum_assignment(count_matrix.max() - count_matrix)
    reassignment = dict(zip(row_ind, col_ind))
    accuracy = count_matrix[row_ind, col_ind].sum() / y_predicted.size
    return reassignment, accuracy

def target_distribution(batch: torch.Tensor) -> torch.Tensor:
    """
    Compute the target distribution p_ij, given the batch (q_ij), as in 3.1.3 Equation 3 of
    Xie/Girshick/Farhadi; this is used the KL-divergence loss function.

    :param batch: [batch size, number of clusters] Tensor of dtype float
    :return: [batch size, number of clusters] Tensor of dtype float
    """
    weight = (batch ** 2) / torch.sum(batch, 0)
    return (weight.t() / torch.sum(weight, 1)).t()


from scipy.stats import chi2_contingency
from scipy.stats import chi2

# Game on 327
#          Ctrl, V1, V2, V3
# Game       15   5   8  10
# Not Game   55  37  41  33

# Game on 544
#          Ctrl, V1, V2, V3
# Game       9   3   3  6
# Not Game   30  34  41  32
table = [[15, 5, 8, 10],
         [70-15, 42-5, 49-8, 43-10]]

table1 = [[9, 3, 3, 6], [30, 34, 41, 32]]
stat, p, dof, expected = chi2_contingency(table1)
print('* Degree of Freedom: %d' % dof)

# 解释结果
prob = 0.95
critical = chi2.ppf(prob, dof)
print('* Accept Probability = %.3f' % prob)
print('* Critical = %.3f' % critical)

print('* Interpretation as follows:')
print('- Chi^2 = %.3f' % stat)
print('- P-val = %.3f' % p)
if abs(stat) >= critical:
    print('- Dependent (reject H0)')
else:
    print('- Independent (fail to reject H0)')

alpha = 1.0 - prob
if p <= alpha:
    print('- p-value(%.2f) <= %.2f, dependent (reject H0)' % (p, alpha))
else:
    print('- p-value(%.2f) > %.2f, independent (fail to reject H0)' % (p, alpha))


import numpy as np
from Exercise5.lu_decomposition import lu

def splines_interpolation(x, y):
    """
        Computes c of the natural cubic spline interpolating the points given.

        Parameters
        ----------
        x : list
            The values of the independent variable. Must be sorted.
        y : list
            The values of f(x) for every x, where x is the independent variable

        Returns
        -------
        c : list
           coefficients of interpolating polynomial
    """
    # create the values for a(i)
    a = []
    d = []
    delta = []
    for i in range(len(x) - 1):
        a.append(y[i])
        d.append(x[i + 1] - x[i])
        delta.append(y[i + 1] - y[i])

    # create the system matrix
    system_matrix = [[0.0 for j in range(len(x))] for i in range(len(x))]
    system_matrix[0][0] = 1
    system_matrix[len(x) - 1][len(x) - 1] = 1
    for row in range(1, len(x) - 1):
        system_matrix[row][row - 1] = d[row - 1]
        system_matrix[row][row] = 2 * (d[row - 1] + d[row])
        system_matrix[row][row + 1] = d[row]

    # create the constant vector of the system
    constant_vector = [0.0 for i in range(len(x))]
    for row in range(1, len(x) - 1):
        constant_vector[row] = 3 * ((delta[row] / d[row]) - (delta[row - 1] / d[row - 1]))

    # solve the system using lu decomposition, c_vector contains the values for c(i)
    c_vector = lu(system_matrix, constant_vector)

    # find the c for b(i) and d(i)
    b = list(d)
    final_d = list(d)
    for i in range(len(x) - 1):
        final_d[i] = (c_vector[i + 1] - c_vector[i]) / (3 * d[i])
        b[i] = (delta[i] / d[i]) - (d[i] / 3) * (2 * c_vector[i] + c_vector[i + 1])

    return a, b, c_vector, final_d

def find_interval_index(array, low, high, search_value):
    """
        Finds the position index that the search_value should be inserted in the array.

        Parameters
        ----------
        array : list
            The array for searching where to insert the search_value. Must be sorted.
        low : int
            The low index of the interval that we are going to search in the array.
        high : int
            The max index of the interval that we are going to search in the array.
        search_value : float
            The value for which we search to find where it should be inserted.

        Returns
        -------
        int
            The index that should be the search_value inserted in the array.
    """
    while low < high:
        mid = int((high+low)//2)
        if array[mid] == search_value:
            break
        elif array[mid] > search_value:
            high = mid - 1
        else:
            low = mid + 1

    mid = int((high+low)//2)
    if search_value <= array[mid]:
        return mid

    return mid + 1

def calculate_polynomial(a, b, c, d, x_values, x):
    """
        Computes the value of the natural cubic spline.

        Parameters
        ----------
        a, b, c, d : list
            The essential c of the natural cubic spline.
        x_values : list
            The values of the independent variable in the data points that were used when calculating the c
            of the natural cubic spline.
        x : int
            The point at which the polynomial will be calculated

        Returns
        -------
        value : float
            The value of the interpolating polynomial at point x.
    """
    i = find_interval_index(x_values, 0, len(x_values)-1, x) - 1
    return a[i] + b[i]*(x-x_values[i]) + c[i]*((x-x_values[i])**2) + d[i]*((x-x_values[i])**3)

def custom_sin(value):
    """
        Approximates sin curve with natural cubic splines.

        Parameters
        ----------
        value : float
            The point at which the sin will be approximated

        Returns
        -------
        float
            The approximation value of the sin curve at point x.
    """
    x = [0.0, 0.65, 1.3, 1.9500000000000002, 2.6, 3.25, 3.9000000000000004, 4.55, 5.2, 2*np.pi]
    y = [0.0, 0.6051864057, 0.9635581854, 0.9289597150, 0.5155013718, -0.1081951345, -0.6877661591, -0.9868438585,
         -0.8834546557, 0]
    a, b, c, d = splines_interpolation(x, y)

    value = value % (2*np.pi)
    return calculate_polynomial(a, b, c, d, x, value)