#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Thu Jul  8 16:55:51 2021

@author: alessandro
"""

import math
import numpy as np
import scipy as sp
import scipy.linalg as spl

# a)

def lu_nopivot(A):
    m, n = A.shape
    U = A.copy()
    for k in range(n - 1):
        if U[k, k] == 0:
            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

# b)
def solve_u(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) / L[i, i]
    return x, True

# c)
def solve_l(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, True

# d)

def lu_solve(L, U, b):
    y, flag = solve_l(L, b)
    return solve_u(U, y)

def lulu_solve(L,U,b):
    #Soluzione del sistema lineare A**2 x= c che equivale a L U L U x =b
    y3, flag = solve_l(L, b)
    y2, flag = solve_u(U, y3)
    y1, flag = solve_l(L, y2)
    x, flag = solve_u(U, y1)
    return x

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

    # (A^T)x=b
    L, U, flag = lu_nopivot(A.transpose())
    x1 = lu_solve(L, U, b)[0].transpose()
    print(f"n = {n} => sistema 1 = {x1}")

    # (A^2)x=c = LULUx=c
    # Conviene fare LULUx=c invece che A^2x=b
    # perchè A^2 è molto mal condizionata
    x2 = lulu_solve(L, U, c).transpose()
    xx2 = spl.solve(np.dot(A, A), c)
    print(f"n = {n} => sistema 2 = {xx2}")


#
#	root-newton-raphson.py
#	Finding root using Newton-Raphson method
#
#	Sparisoma Viridi | https://butiran.github.io/
#
#	Execute: py root-newton-raphson.js
#
#	20210131
#	1850 Start this program from root-bisection.py.
#	1954 Continue make test_function and its derivative.
#	2006 Test it and ok, also for SHOW_PROGRESS.
#	2009 Correct maxstep to maxstep-1 in while.
#	20210210
#	0527 Typo node --> py.
#

# Import necessary libraries
import numpy as np

# Define a test function
def test_function(x):
	y3 = 0.01 * x * x * x
	y2 = -0.2192 * x * x
	y1 = 0.3056 * x
	y0 = 1.568
	y = y3 + y2 + y1 + y0
	return y

# Define derivative of the test function
def derivative_test_function(x):
	y2 = 3 * 0.01 * x * x
	y1 = 2 * -0.2192 * x
	y0 = 0.3056
	y = y2 + y1 + y0
	return y

# Define input
f = test_function
dfdx = derivative_test_function
xinit = 2
eps = 1E-10
n = 0
maxstep = 40

# Define default message and parameter
xroot = "not found"
SHOW_PROGRESS = False

# Do iteration
Nstep = 0
x = []
x.append(xinit)
froot = np.abs(f(x[n]))

while froot > eps and n < maxstep - 1:
	x.append(x[n] - f(x[n]) / dfdx(x[n]))

	froot = np.abs(f(x[n+1]))
	if froot < eps:
		xroot = x[n+1]

	if SHOW_PROGRESS:
		if n == 0:
			fn = f(x[n])
			print("n\tx\tf(x)")
			print(n, x[n], f(x[n]), sep="\t")
		print(n+1, x[n+1], f(x[n+1]), sep="\t")

	n += 1

Nstep = n+1

if SHOW_PROGRESS:
	print()

# Display result
print("f(x)  0.01x^3 - 0.2192x^2 + 0.3056x + 1.568");
print("xinit ", xinit, sep="")
print("ε     ", eps, sep="")
print("Nstep ", Nstep, sep="")
print("xroot ", xroot, sep="")


# means two successive elements

# [1, 2, 3, 4], the discrete difference would be
# [2 - 1, 3 - 2, 4 - 3] = [1, 1, 1]

# diff()

import numpy as np

arr = np.array([10, 15, 25, 5])

newarr = np.diff(arr)

print(newarr)

"""
We can perform this operation repeatedly by giving parameter n.
E.g. for [1, 2, 3, 4], the discrete difference
with n = 2 would be [2-1, 3-2, 4-3] = [1, 1, 1] , then,
since n=2, we will do it once more,
with the new result: [1-1, 1-1] = [0, 0]
"""
newarr = np.diff(arr, n = 2)

print(newarr)


# -*- coding: utf-8 -*-
"""
European call option by Monte Carlo simulation

test for vectorized calculation

@author: Minhyun Yoo
"""
import time
import numpy as np
from math import exp, sqrt, log
from scipy import stats

def exc_call(S0, E, T, r, sig):
    d1 = (log(S0/E) + (r + 0.5*sig**2)*T) / (sig*sqrt(T));
    d2 = d1 - sig * sqrt(T);

    call = ( S0 * stats.norm.cdf(d1, 0.0, 1.0)
        - E * exp(-r * T) * stats.norm.cdf(d2, 0.0, 1.0) )

    print 'Exact call Price : %.5f' % call

def mc_call(S0, E, T, r, sig, numSim, numStep):
    dt = T / numStep;
    t0 = time.clock();
    z = np.random.normal(size = [numSim, numStep]);
    s = S0 * np.ones([numSim]);
    # vectorized calculation
    for j in xrange(numStep):
        s[:] = s[:] * np.exp((r - 0.5*sig**2)*dt + sig*sqrt(dt)*z[:, j]);
    payoff = np.maximum(s - E, 0);
    call = exp(-r * T) * np.mean(payoff);
    t1 = time.clock();
    del z;

    print 'Monte Carlo Call Price : %.5f' % call
    print 'CPU time in Python(sec) : %.4f' % (t1-t0)

def mc_call_var_reduc(S0, E, T, r, sig, numSim, numStep):
    dt = T / numStep;
    t0 = time.clock();
    z = np.random.normal(size = [numSim, numStep]);
    sp = S0 * np.ones([numSim]);
    sm = S0 * np.ones([numSim]);
    for j in xrange(numStep):
        sp[:] = sp[:] * np.exp((r - 0.5*sig**2)*dt + sig*sqrt(dt)*z[:, j]);
        sm[:] = sm[:] * np.exp((r - 0.5*sig**2)*dt - sig*sqrt(dt)*z[:, j]);
    payoff1 = np.maximum(sp - E, 0);
    payoff2 = np.maximum(sm - E, 0);
    call = exp(-r * T) * np.mean(0.5*(payoff1+payoff2));
    t1 = time.clock();
    del z;

    print 'Monte Carlo Call Price : %.5f' % call
    print 'CPU time in Python(sec) : %.4f' % (t1-t0)

S0 = 100.0; # underlying price
E = 100.0; # strike price
T = 1.0; # maturity
r = 0.03; # riskless interest rate
sig = 0.3; # volatility
ns = 100000;  # # of simulations
nStep = 1; # # of time steps (In this example, nStep does not have to over 1 due to European option pricing.)

# functions call

exc_call(S0, E, T, r, sig); # exact solution

mc_call(S0, E, T, r, sig, ns, nStep); # Monte Carlo simulation

mc_call_var_reduc(S0, E, T, r, sig, ns, nStep);


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

def to_matrix(filename,n):
    '''
    Return the nxn adjacency matrix described by datafile.
    INPUTS:
    datafile (.txt file): A .txt file describing a directed graph. Lines
        describing edges should have the form '<from node>\t<to node>\n'.
        The file may also include comments.
    n (int): The number of nodes in the graph described by datafile
    RETURN:
        Return a SciPy sparse `dok_matrix'.
    '''
    adj = spar.dok_matrix((n,n))
    with open(filename, 'r') as myfile:
        for line in myfile:
            line = line.strip().split()
            try:
                x,y = int(line[0]),int(line[1])
                adj[x,y] = 1
            except:
                continue
    return adj

def calculateK(A,N):
    '''
    Compute the matrix K as described in the lab.
    Input:
        A (array): adjacency matrix of an array
        N (int): the datasize of the array
    Return:
        K (array)
    '''
    n = A.shape[0]
    D = np.zeros(n)
    for row in range(n):
        D[row] = A[row].sum()
    for i in range(n):
        if D[i] == 0:
            D[i] = n
            A[i] = np.ones(n)
    K = (A.T/D)
    return K[:N,:N]

def iter_solve(adj, N=None, d=.85, tol=1E-5):
    '''
    Return the page ranks of the network described by `adj`.
    Iterate through the PageRank algorithm until the error is less than `tol'.
    Inputs:
    adj - A NumPy array representing the adjacency matrix of a directed graph
    N (int) - Restrict the computation to the first `N` nodes of the graph.
            Defaults to N=None; in this case, the entire matrix is used.
    d     - The damping factor, a float between 0 and 1.
            Defaults to .85.
    tol  - Stop iterating when the change in approximations to the solution is
        less than `tol'. Defaults to 1E-5.
    Returns:
    The approximation to the steady state.
    '''
    if N is None:
        N = adj.shape[1]
    pt = np.ones((N,1))/N
    K = calculateK(adj,N)
    pt1 = d*np.dot(K,pt) + ((1.-d)/N)*np.ones((N,1))
    while la.norm(pt1-pt) > tol:
        pt = pt1
        pt1 = d*np.dot(K,pt) + ((1.-d)/N)*np.ones((N,1))
    return pt1

def eig_solve( adj, N=None, d=.85):
    '''
    Return the page ranks of the network described by `adj`. Use the
    eigenvalue solver in \li{scipy.linalg} to calculate the steady state
    of the PageRank algorithm
    Inputs:
    adj - A NumPy array representing the adjacency matrix of a directed graph
    N - Restrict the computation to the first `N` nodes of the graph.
            Defaults to N=None; in this case, the entire matrix is used.
    d     - The damping factor, a float between 0 and 1.
            Defaults to .85.
    Returns:
    The approximation to the steady state.
    '''
    if N is None:
        N = adj.shape[1]
    K = calculateK(adj,N)
    B = d*K + ((1.-d)/N)*np.ones((N,N))
    evalues,evectors = la.eig(B)
    i = np.argsort(evalues)[-1] #Find index of largest eigenvalue (which should be 1)
    pt = (evectors[:,i].real)
    pt = pt*1/pt.sum()
    return pt

def team_rank(filename='ncaa2013.csv'):
    '''
    Use your iterative PageRank solver to predict the rankings of the teams in
    the given dataset of games.
    The dataset should have two columns, representing winning and losing teams.
    Each row represents a game, with the winner on the left, loser on the right.
    Parse this data to create the adjacency matrix, and feed this into the
    solver to predict the team ranks.
    Inputs:
    filename (optional) - The name of the dataset.
    Returns:
    ranks - A list of the ranks of the teams in order "best" to "worst"
    teams - A list of the names of the teams, also in order "best" to "worst"
    '''
    # Create adj. matrix
    teams = set()
    wins = []
    with open(filename, 'r') as f:
        f.readline() #read the header
        for line in f:
            data = line.strip().split(',') #split on commas
            teams.add(data[0])
            teams.add(data[1])
            wins.append(data)
    n = len(teams)
    team_list = list(teams)
    team_number = dict()
    for i, t in enumerate(team_list):
        team_number[t] = i
    adj = spar.dok_matrix((n,n)) #adjacency matrix
    for match in wins:
        win_number = team_number[match[0]]
        lose_number = team_number[match[1]]
        adj[lose_number,win_number] = 1

    # Solve for ranks
    p = iter_solve(adj.todense(), d=0.7)
    p = np.array(p).squeeze()
    idx = np.argsort(p)[::-1]
    n_out = 5
    return [p[j] for j in idx[:n_out]], [team_list[j] for j in idx[:n_out]]

def problemOne():
    print to_matrix('datafile.txt',8).todense()

def problemTwo():
    A = to_matrix('datafile.txt',8).todense()
    print calculateK(A,8)

def problemThree():
    A = to_matrix('datafile.txt',8).todense()
    print iter_solve(A,N = 8)

def problemFour():
    A = to_matrix('datafile.txt',8).todense()
    a =iter_solve(A,N = 8)
    b = eig_solve(A,N = 8)
    print a
    print eig_solve(A,N=8)

if __name__ == '__main__':
    print "Testing 1"
    problemOne()
    print "Testing 2"
    problemTwo()
    print "Testing 3"
    problemThree()
    print "Testing 4"
    problemFour()
    print "Testing team rank"
    ranks, teams = team_rank()
    print ranks
    print teams


import numpy as np

def objective_function_F1(X):
    # Sphere function (minimum at 0)
    return - np.sum(X**2, axis=1)

def objective_function_F1a(X):
    # Sphere function - modified
    return - (X[:, 0]**2 + 9*X[:, 1]**2)

def objective_function_F1b(X):
    # Sphere function - modified
    return - (X[:, 0]**2 + 625*X[:, 1]**2)

def objective_function_F1c(X):
    # Sphere function - modified
    return - (X[:, 0]**2 + 2*X[:, 1]**2 - 2 * X[:, 0] * X[:, 1])

def objective_function_F6(X):
    # Rastrigin function (minimum at 0)
    return - 10.0 * X.shape[1] - np.sum(X**2, axis=1) + 10.0 * np.sum(np.cos(2 * np.pi * X), axis=1)

def objective_function_F7(X):
    # Schwefel function (minimum at 420.9687)
    # (REMARK: should be considered only on [-500, 500]^d, because there are better minima outside)
    return - 418.9829 * X.shape[1] + np.sum(X * np.sin(np.sqrt(np.abs(X))), axis=1)

def objective_function_F8(X):
    # Griewank function (minimum at 0)
    return - 1 - np.sum(X**2 / 4000, axis=1) + np.prod(np.cos(X / np.sqrt(np.linspace(1, X.shape[1], X.shape[1]))), axis=1)


import numpy as np
import scipy.linalg as la

## Descompunere LU

# Matricea dată la intrare
A = np.array([
    [2, -1, -2],
    [4, 2, 0],
    [0, -2, -1],
], dtype=np.float64)

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

N = A.shape[0]
P = np.eye(N)
L = np.zeros((N, N))
U = np.copy(A)

partial_pivot = True

for k in range(N - 1):
    if partial_pivot:
        # Găsesc indicele elementului de magnitudine maximă
        index = k + np.argmax(np.abs(U[k:, k]))

        # Pivotez
        U[[k, index]] = U[[index, k]]
        L[[k, index]] = L[[index, k]]

        # Interschimb în permutare
        P[[k, index]] = P[[index, k]]

    # Selectez coloana pe care lucrez
    ratios = U[k + 1:, k]

    # Determin raportul pentru fiecare rând
    ratios = ratios / U[k, k]

    # Actualizez matricea inferior triunghiulară
    L[k + 1:, k] = ratios

    # Selectez rândul pe care vreau să-l actualizez
    row = U[k, :]

    # Înmulțesc fiecare raport cu primul rând
    difference = np.outer(ratios, row)

    # Actualizez matricea superior triunghiulară
    U[k + 1:, :] -= difference

L += np.eye(N)

print("L = ")
print(L)
print("U = ")
print(U)
print("L @ U = ")
print(L @ U)
print("P @ A = ")
print(P @ A)
print()

b = np.array([[1, 2, 3]], dtype=np.float64).T
print("Rezolv pentru b = ", b.T)

# Permut numerele din vector
b = P @ b

y = np.zeros(N)

# Merg de la prima linie în jos,
# și rezolv pe rând ecuațiile prin substituție
for i in range(0, N):
    coefs = L[i, :i + 1]
    values = y[:i + 1]

    y[i] = (b[i] - coefs @ values) / L[i, i]

print("Obțin y = ", y)

x = np.zeros(N)

# Merg de la ultima linie în sus,
# și rezolv pe rând ecuațiile prin substituție
for i in range(N - 1, -1, -1):
    coefs = U[i, i + 1:]
    values = x[i + 1:]

    x[i] = (y[i] - coefs @ values) / U[i, i]

print("Obțin x = ", x)
print("Verificare: A @ x = ", A @ x)


# first skill: generate random numbers following particular distributions
import matplotlib.pyplot as plt
import numpy as np
from scipy.stats import norm
plt.rcParams['mathtext.fontset'] = 'cm'

# Method 1: instance of the random state class
random_seed = 31416
random_state = np.random.RandomState(seed=random_seed)
number_samples = 1000
gaussian_sample = random_state.normal(loc=0, scale=2, size=number_samples)

# ground truth gaussian
y = norm(loc=0, scale=2)
x = np.linspace(y.ppf([0.01])[0], y.ppf([0.99])[0], 100)

with plt.xkcd():
    plt.figure(figsize=(8, 6))
    plt.hist(gaussian_sample, bins=10, histtype='step', density=True)
    plt.plot(x, y.pdf(x))
    plt.title(r'$\mu = {} \quad \sigma = {}$'.format(0, 2))
    plt.xlabel(r'$X$')
    plt.ylabel(r'$P(X)$')
    plt.savefig('random_samples.png', dpi=128)
    plt.close()

# Method 2: Inverse transform method
# set up functions to measure pdf, cdf and icdf
def exponential_pdf(x_array, lambda_parameter=1):
    """PDF of exponential distribution"""
    return lambda_parameter*np.exp(-lambda_parameter * x_array)

def exponential_cdf(x_array, lambda_parameter=1):
    """CDF of exponential distribution."""
    return 1 - np.exp(-lambda_parameter * x_array)

def exponential_icdf(p, lambda_parameter=1):
    """Inverse CDF of exponential distribution: quantile estimation"""
    return -np.log(1-p)/lambda_parameter

# generate instance of the distribution with default lambda parameter
# auxiliar arrays to manipulate
xi = np.linspace(0, 4, 100)
yi = np.linspace(0, 1, 100)

# visual inspection
with plt.xkcd():
    plt.figure(figsize=(12, 6))
    # left panel
    plt.subplot(121)
    # plot the cumulative distribution
    plt.plot(xi, exponential_cdf(xi))
    plt.axis([0, 4, 0, 1])
    # highlight values at cdf points of 50% and 80% (say)
    for q in [0.5, 0.8]:
        plt.arrow(0, q, exponential_icdf(q) - 0.1, 0, head_width=0.05, head_length=0.1, fc='b', ec='b')
        plt.arrow(exponential_icdf(q), q, 0, -q + 0.1, head_width=0.1, head_length=0.05, fc='b', ec='b')
    # labels
    plt.ylabel('1: Generate a (0,1) uniform PRNG')
    plt.xlabel('2: Find the inverse CDF')
    plt.title('Inverse transform method')
    # right panel
    plt.subplot(122)
    # samples from the uniform distribution
    number_uniform_samples = 10000
    u = np.random.random(number_uniform_samples)
    # get the inverse cumulative distribution
    v = exponential_icdf(u)
    # visualise the distribution from the samples
    plt.hist(v, histtype='step', bins=100, density=True, linewidth=2)
    # compare with real pdf
    plt.plot(x, exponential_pdf(x), linewidth=2)
    plt.axis([0, 4, 0, 1])
    plt.title('Histogram of exponential PRNGs')
    plt.savefig('random_inverse_transform.png', dpi=128)
    plt.close()


#!/usr/bin/env python
# coding: utf-8
# In[1]:
import numpy as np
import pandas as pd
from pandas_datareader import data as web
from scipy.stats import norm
import matplotlib.pyplot as plt
get_ipython().run_line_magic('matplotlib', 'inline')
# In[2]:
data = pd.read_csv('D:/Python/PG_2007_2017.csv', index_col = 'Date')
# In[3]:
log_returns = np.log(1 + data.pct_change())
# In[4]:
log_returns.tail()
# In[5]:
data.plot(figsize=(10, 6));
# In[6]:
r = 0.025
# In[7]:
stdev = log_returns.std() * 250 ** 0.5
stdev
# In[8]:
type(stdev)
# In[9]:
stdev = stdev.values
stdev
# In[10]:
T = 1.0
t_intervals = 250
delta_t = T / t_intervals
iterations = 10000
# In[11]:
Z = np.random.standard_normal((t_intervals + 1, iterations))
S = np.zeros_like(Z)
S0 = data.iloc[-1]
S[0] = S0
for t in xrange(1, t_intervals + 1):
    S[t] = S[t-1] * np.exp((r - 0.5 * stdev ** 2) * delta_t + stdev * delta_t ** 0.5 * Z[t])
# In[12]:
S
# In[13]:
S.shape
# In[14]:
plt.figure(figsize=(10, 6))
plt.plot(S[:, :10]);
# ******
# In[15]:
p = np.maximum(S[-1] - 110, 0)
# In[16]:
p
# In[17]:
p.shape
# In[18]:
C = np.exp(-r * T) * np.sum(p) / iterations
C


# Cox-Ross-Rubinstein for Eurpean and American style options

#Code is straightforward and only numpy is required

import numpy as np

#S        -> Spot price of the underlying
#K        -> Strike price for the option
#sigma    -> Annualized volatility of the underlying
#r        -> continously compounded risk-free rate
#T        -> option's time to expiration in years
#steps    -> number of steps to be calculated in the tree
#q        -> continously compounded dividend yield (default is 0)
#call     -> specify put or call option (default is call)
#american -> specify if early exercise (default is true, false for European style)

def crr(S,K,sigma,r,T,steps,q=0,call=True,american=True):
    # Assigning paramter values
    dt=T/steps
    u=np.exp(dt**0.5 *sigma)
    d=np.exp(-(dt**0.5 *sigma))
    p=(np.exp((r-q)*dt)-d)/(u-d)

    #Pre-allocating the underlying's price array
    pricetree=np.zeros([steps+1,steps+1],dtype='float64')
    pricetree[0,0]=S

    #The tree is calculated for the underlying's price
    for i in range(1,steps+1):
       pricetree[:i,i]=pricetree[:i,i-1]*u
       pricetree[i,i]=pricetree[i-1,i-1]*d

    #Tree for the option value
    optiontree=np.zeros([steps+1,steps+1],dtype='float64')
    if call:
        optiontree[:,steps]=np.maximum(pricetree[:,-1 ]-K,0)
    else:
         optiontree[:,steps]=np.maximum(K-pricetree[:,-1 ],0)

    for i in range(steps-1,-1,-1):
        optiontree[:i+1,i]=np.exp(-(r*dt))*(optiontree[:i+1,i+1]*p+\
                                            (1-p)*optiontree[1:i+2:1,i+1])
        #Early exercise
        if american :
            if call :
                optiontree[:i+1,i]=np.maximum( pricetree[:i+1,i]-K, \
                                       optiontree[:i+1,i])
            else:
                optiontree[:i+1,i]=np.maximum(K-pricetree[:i+1,i], \
                                              optiontree[:i+1,i])

    #Finally, the function outputs the option value, and the price and
    # option value trees.
    return optiontree[0,0], pricetree, optiontree

##### Using the function with some given parameters ######

S=50;
K=30;
sigma= 0.3;
r=0.05;
T=1;
steps=200;

price,pricearray,optionarray=crr(S,K,sigma,r,T,steps)


"""
Correlation based distances and various modifications (angular, absolute, squared) described in Cornell lecture notes:
Codependence: https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3512994&download=yes
"""

import numpy as np
import pandas as pd
from scipy.spatial.distance import squareform, pdist

# pylint: disable=invalid-name

def angular_distance(x: np.array, y: np.array) -> float:
    """
    Returns angular distance between two vectors. Angular distance is a slight modification of Pearson correlation which
    satisfies metric conditions.

    Formula used for calculation:

    Ang_Distance = (1/2 * (1 - Corr))^(1/2)

    Read Cornell lecture notes for more information about angular distance:
    https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3512994&download=yes.

    :param x: (np.array/pd.Series) X vector.
    :param y: (np.array/pd.Series) Y vector.
    :return: (float) Angular distance.
    """

    corr_coef = np.corrcoef(x, y)[0][1]
    return np.sqrt(0.5 * (1 - corr_coef))

def absolute_angular_distance(x: np.array, y: np.array) -> float:
    """
    Returns absolute angular distance between two vectors. It is a modification of angular distance where the absolute
    value of the Pearson correlation coefficient is used.

    Formula used for calculation:

    Abs_Ang_Distance = (1/2 * (1 - abs(Corr)))^(1/2)

    Read Cornell lecture notes for more information about absolute angular distance:
    https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3512994&download=yes.

    :param x: (np.array/pd.Series) X vector.
    :param y: (np.array/pd.Series) Y vector.
    :return: (float) Absolute angular distance.
    """

    corr_coef = np.corrcoef(x, y)[0][1]
    return np.sqrt(0.5 * (1 - abs(corr_coef)))

def squared_angular_distance(x: np.array, y: np.array) -> float:
    """
    Returns squared angular distance between two vectors. It is a modification of angular distance where the square of
    Pearson correlation coefficient is used.

    Formula used for calculation:

    Squared_Ang_Distance = (1/2 * (1 - (Corr)^2))^(1/2)

    Read Cornell lecture notes for more information about squared angular distance:
    https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3512994&download=yes.

    :param x: (np.array/pd.Series) X vector.
    :param y: (np.array/pd.Series) Y vector.
    :return: (float) Squared angular distance.
    """

    corr_coef = np.corrcoef(x, y)[0][1]
    return np.sqrt(0.5 * (1 - corr_coef ** 2))

def distance_correlation(x: np.array, y: np.array) -> float:
    """
    Returns distance correlation between two vectors. Distance correlation captures both linear and non-linear
    dependencies.

    Formula used for calculation:

    Distance_Corr[X, Y] = dCov[X, Y] / (dCov[X, X] * dCov[Y, Y])^(1/2)

    dCov[X, Y] is the average Hadamard product of the doubly-centered Euclidean distance matrices of X, Y.

    Read Cornell lecture notes for more information about distance correlation:
    https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3512994&download=yes.

    :param x: (np.array/pd.Series) X vector.
    :param y: (np.array/pd.Series) Y vector.
    :return: (float) Distance correlation coefficient.
    """

    x = x[:, None]
    y = y[:, None]

    x = np.atleast_2d(x)
    y = np.atleast_2d(y)

    a = squareform(pdist(x))
    b = squareform(pdist(y))

    A = a - a.mean(axis=0)[None, :] - a.mean(axis=1)[:, None] + a.mean()
    B = b - b.mean(axis=0)[None, :] - b.mean(axis=1)[:, None] + b.mean()

    d_cov_xx = (A * A).sum() / (x.shape[0] ** 2)
    d_cov_xy = (A * B).sum() / (x.shape[0] ** 2)
    d_cov_yy = (B * B).sum() / (x.shape[0] ** 2)

    coef = np.sqrt(d_cov_xy) / np.sqrt(np.sqrt(d_cov_xx) * np.sqrt(d_cov_yy))

    return coef

def kullback_leibler_distance(corr_a, corr_b):
    """
    Returns the Kullback-Leibler distance between two correlation matrices, all elements must be positive.

    Formula used for calculation:

    kullback_leibler_distance[X, Y] = 0.5 * ( Log( det(Y) / det(X) ) + tr((Y ^ -1).X - n )

    Where n is the dimension space spanned by X.

    Read Don H. Johnson's research paper for more information on Kullback-Leibler distance:
    `<https://scholarship.rice.edu/bitstream/handle/1911/19969/Joh2001Mar1Symmetrizi.PDF>`_

    :param corr_a: (np.array/pd.Series/pd.DataFrame) Numpy array of the first correlation matrix.
    :param corr_b: (np.array/pd.Series/pd.DataFrame) Numpy array of the second correlation matrix.
    :return: (np.float64) the Kullback-Leibler distance between the two matrices.
    """

    # Check if input type is pd.DataFrame
    if isinstance(corr_a, pd.DataFrame) and isinstance(corr_b, pd.DataFrame):
        corr_a = corr_a.to_numpy()
        corr_b = corr_b.to_numpy()

    n = corr_a.shape[0]
    dist = 0.5 * (np.log(np.linalg.det(corr_b) / np.linalg.det(corr_a)) +
                  np.trace(np.linalg.inv(corr_b).dot(corr_a)) - n)

    return dist

def norm_distance(matrix_a, matrix_b, r_val=2):
    """
    Returns the normalized distance between two matrices.

    This function is a wrap for numpy's linear algebra method (numpy.linalg.norm).
    Link to documentation: `<https://numpy.org/doc/stable/reference/generated/numpy.linalg.norm.html>`_.

    Formula used to normalize matrix:

    norm_distance[X, Y] = sum( abs(X - Y) ^ r ) ^ 1/r

    Where r is a parameter. r=1 City block(L1 norm), r=2 Euclidean distance (L2 norm),
    r=inf Supermum (L_inf norm). For values of r < 1, the result is not really a mathematical ‘norm’.

    :param matrix_a: (np.array/pd.Series/pd.DataFrame) Array of the first matrix.
    :param matrix_b: (np.array/pd.Series/pd.DataFrame) Array of the second matrix.
    :param r_val: (int/str) The r value of the normalization formula. (``2`` by default, Any Integer)
    :return: (np.float64) The Euclidean distance between the two matrices.
    """

    # Check if input type is pd.DataFrame
    if isinstance(matrix_a, pd.DataFrame) and isinstance(matrix_b, pd.DataFrame):
        matrix_a = matrix_a.to_numpy()
        matrix_b = matrix_b.to_numpy()

    return np.linalg.norm(matrix_b - matrix_a, r_val)


import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
path='ex1data1.txt'
data=pd.read_csv(path,header=None,names=['Population','Profits'])
data.head()
data.describe()

data.plot(kind='scatter',x='Population',y='Profits',figsize=(12,8))
plt.show()

def computeCost(X,y,theta):
    inner=np.power(((X*theta.T)-y),2)
    return np.sum(inner)/(2*len(X))
data.insert(0,'Ones',1)
cols=data.shape[1]#shape0是列数，shape1是行数
X=data.iloc[:,0:cols-1]
y=data.iloc[:,cols-1:cols]

X=np.matrix(X.values)
y=np.matrix(y.values)
theta=np.matrix(np.array([0,0]))

computeCost(X,y,theta)

#batch gradient decent批量梯度下降
def gradientDescent(X,y,theta,alpha,iters):
    temp=np.matrix(np.zeros(theta.shape))#ravel展平到一行
    parameters=int(theta.ravel().shape[1])#有几个theta
    cost=np.zeros(iters)#储存每次迭代的cost

    for i in range(iters):
        error=(X*theta.T)-y
        for j in range(parameters):
            term=np.multiply(error,X[:,j])
            temp[0,j]=theta[0,j]-((alpha/len(X))*np.sum(term))
        theta=temp
        cost[i]=computeCost(X,y,theta)
    return theta,cost
alpha=0.01
iters=1000
g,cost=gradientDescent(X,y,theta,alpha,iters)
g;cost#g就是模型的theta值
computeCost(X,y,g)#训练模型的代价
#绘图看拟合情况
x=np.linspace(data.Population.min(),data.Population.max(),100)
f=g[0,0]+(g[0,1]*x)#y=ax+b
fig,ax=plt.subplots(figsize=(12,8))
ax.plot(x,f,'r',label='Prediction')
ax.scatter(data.Population,data.Profits,label='Traning Data')
ax.legend(loc=2)
ax.set_xlabel('Population')
ax.set_ylabel('Profits')
ax.set_title('Predicted Profits vs.Population Size')
plt.show()
#绘制代价降低情况
fig,bx=plt.subplots(figsize=(12,8))
bx.plot(np.arange(iters),cost,'r')
bx.set_xlabel('Iterations')
bx.set_ylabel('Cost')
bx.set_title('Error vs. Training Epoch')
plt.show()

#多变量线性回归情况
path2='ex1data2.txt'
data2=pd.read_csv(path2,header=None,names=['Size','Bedrooms','Price'])
data2.head()
#特征归一化
data2=(data2-data2.mean())/data2.std()
data2.head()
#加上“1”列
data2.insert(0,'Ones',1)
cols=data2.shape[1]
X2=data2.iloc[:,0:cols-1]
y2=data2.iloc[:,cols-1:cols]
X2=np.matrix(X2.values)
y2=np.matrix(y2.values)
theta2=np.matrix(np.array([0,0,0]))
g2,cost2=gradientDescent(X2,y2,theta2,alpha,iters)
computeCost(X2,y2,g2)
#快速查看一下训练过程
fig,ax=plt.subplots(figsize=(12,8))#(111,figsize=...)
ax.plot(np.arange(iters),cost2,'r')
ax.set_xlabel('Iterations')
ax.set_ylabel('Cost')
ax.set_title('Error vs. Training Epoch')
plt.show()

#直接用scikit-learn
from sklearn import linear_model
model=linear_model.LinearRegression()
model.fit(X,y)
x=np.array(X[:,1].A1)#什么意思？
f=model.predict(X).flatten()
fig,ax=plt.subplots(figsize=(12,8))
ax.plot(x,f,'r',label='Prediction')
ax.scatter(data.Population,data.Profits,label='Traning Data')
ax.legend(loc=4)#1234为四个角落
ax.set_xlabel('Population')
ax.set_ylabel('Profit')
ax.set_title('Predicted Profit vs. Population Size')
plt.show()

#正规方程法
def normalEqn(X,y):
    theta=np.linalg.inv(X.T@X)@X.T@y#X.T@X即X.T.dot(X)
    return theta
theta3=normalEqn(X,y)
theta3
g#与梯度下降对比一下


import numpy as np
import math as m
from matplotlib import pyplot as plt

pi=round(m.pi,3)

x=np.arange(0,10, 0.001)
A=1.0
C=2*pi
B=0.0
D=0.0

def sine(A,B,C,D):

    y=A*(np.sin((2*pi*(x-B))/C))+ D

    plt.plot(x,y) #plot the graph of y function of x
    plt.axhline(y=0, color='k')  #plot the x axis
    plt.axvline(x=0, color='k') #plot the y axis
    plt.grid(True)          #plot the gridlines
    plt.xlabel('x')   #label the x axis
    plt.ylabel('sin(x)')        #label the y axis

    plt.show()
    return;

def cosine(A,B,C,D):

    y=A*(np.cos((2*pi*(x-B))/C))+ D

    plt.plot(x,y) #plot the graph of y function of x
    plt.axhline(y=0, color='k')  #plot the x axis
    plt.axvline(x=0, color='k') #plot the y axis
    plt.grid(True)          #plot the gridlines
    plt.xlabel('x')   #label the x axis
    plt.ylabel('cos(x)')        #label the y axis

    plt.show()
    return;

print("\n\t\t Sine and Cosine Trigonometry graph calculator!")
print("\t","-"*55)

end_program = 1

while end_program:
    c=int(input("Press 1 for sine or 0 for cosine: "))
    if c==1:
        print("You chose to graph the function sine, the form:")
        print("f(x)=A*sin((2*pi*(x-B))/C))+ D")
        print("Enter the graph properties when prompted:")
        A=float(input("Amplitude (A)= "))
        B=float(input("Phase shift (B)= "))
        C=float(input("Period (can't be 0)(C)= "))
        D=float(input("Vertical shift (D)= "))
        sine(A,B,C,D)
        end_program=int(input("Do you want to do another graph? Press 1 for Yes and 0 for No \n"))

    elif c==0:
        print("You chose to graph the function cosine, the form:")
        print("f(x)=A*cos((2*pi*(x-B))/C))+ D")
        print("Enter the graph properties when prompted:")
        A=float(input("Amplitude (A)= "))
        B=float(input("Phase shift (B)= "))
        C=float(input("Period (can't be 0)(C)= "))
        D=float(input("Vertical shift (D)= "))
        cosine(A,B,C,D)
        end_program=int(input("Do you want to do another graph? Press 1 for Yes and 0 for No\n"))

    else:
        print("Oops Wrong choice!!")
        continue


#!/usr/bin/python
"""
Cw5 the pinacle of grupwork and efficienceyc and comroodery

Calculus module implementing
discrete function function
differentiation function
and trapezoidal integration function

"""

import numpy as np
import math

def diff(f, a, b, n):
    """An iterative version of a forward differentiation method."""
    x = np.linspace(a, b, n+1)
    y = np.zeros(len(x))
    z = np.zeros(len(x))
    h = (b-a)/float(n)
    for i in xrange(len(x)):
        y[i] = f(x[i])
    for i in xrange(len(x)-1):
        z[i] = (y[i+1] - y[i])/h
    z[n] = (y[n] - y[n-1])/h
    return y, z

def discrete_func(f, a, b, n):
    """Generates a vectorized linspace and applies a function to a similar data structure."""
    x = np.linspace(a, b, n+1)
    g = np.vectorize(f)
    y = g(x)
    return x, y

def diff2(f, a, b, n):
    """A matrix version of a forward differentiation method."""
    x, y = discrete_func(f, a, b, n - 1)
    matrix = np.zeros((n,n))
    h = ( b - a ) / float(n)
    count = -1
    for i in range(n):
        if(count >= 0 and count < n-2):
            matrix[i][count] = 1 / (2 * h)
            matrix[i][count + 2] = -1 / (2 * h)
        count += 1
    matrix[0][0] = -1 / h
    matrix[0][1] = 1 / h
    matrix[-1][-1] = 1 / h
    matrix[-1][-2] = -1 / h
    return np.dot(matrix, y)

def test_diff():
    apt = math.fabs(diff(math.sin, 0, 1, 100000)[1][-1] - math.cos(1)) < 1e-3
    msg = 'That aint how the sine function do.'
    assert apt, msg

def test_diff2():
    apt = math.fabs(diff(math.sin, 0, 1, 100000)[1][-1] - math.cos(1)) < 1e-3
    msg = 'That aint how the sine function do.'
    assert apt, msg

def trapezoidal_matrix(f, a, b, n):
    """Trapezoidal integration via matrix multiplication."""
    h = (b-a)/float(n)
    indexer = np.linspace(a, b, n)
    values = f(indexer)
    matrixer = np.zeros(n)
    matrixer.fill(h)
    matrixer[0] = h/2.0
    matrixer[n - 1] = h/2.0
    I = np.dot(values, matrixer)
    return I

def test_trap_matrix():
    """Trapezoidal integration via matrix multiplication verified by integrating
    the sine function on the integral 0 to pi over 2."""
    apt = np.abs(trapezoidal_matrix(np.sin, 0, np.pi/2.0, 10000) - 1) < 1e-3
    msg = 'That aint how the sine do.'
    assert apt, msg


import time
import numpy as np
x1 = [9, 2, 5, 0, 0, 7, 5, 0, 0, 0, 9, 2, 5, 0, 0]
x2 = [9, 2, 2, 9, 0, 9, 2, 5, 0, 0, 9, 2, 5, 0, 0]

### CLASSIC DOT PRODUCT OF VECTORS IMPLEMENTATION ###
tic = time.process_time()
dot = 0
for i in range(len(x1)):
    dot+= x1[i]*x2[i]
toc = time.process_time()
print ("dot = " + str(dot) + "\n ----- Computation time = " + str(1000*(toc - tic)) + "ms")

### CLASSIC OUTER PRODUCT IMPLEMENTATION ###
tic = time.process_time()
mul = np.zeros(len(x1))
for i in range(len(x1)):
    mul[i] = x1[i]*x2[i]
toc = time.process_time()
print("elementwise multiplication = " + str(mul) + "\n ----- Computation time = " + str(1000*(toc - tic)) + "ms")

### CLASSIC GENERAL DOT PRODUCT IMPLEMENTATION ###
W = np.random.rand(3,len(x1)) # Random 3*len(x1) numpy array
print("W is \n",W)
tic = time.process_time()
gdot = np.zeros(W.shape[0])
for i in range(W.shape[0]):
    for j in range(len(x1)):
        gdot[i] += W[i,j]*x1[j]
toc = time.process_time()
print ("gdot = " + str(gdot) + "\n ----- Computation time = " + str(1000*(toc - tic)) + "ms")

### VECTORIZED DOT PRODUCT OF VECTORS ###
tic = time.process_time()
dot = np.dot(x1,x2)
toc = time.process_time()
print ("dot = " + str(dot) + "\n ----- Computation time = " + str(1000*(toc - tic)) + "ms")

### VECTORIZED OUTER PRODUCT ###
tic = time.process_time()
outer = np.outer(x1,x2)
toc = time.process_time()
print ("outer = " + str(outer) + "\n ----- Computation time = " + str(1000*(toc - tic)) + "ms")

### VECTORIZED ELEMENTWISE MULTIPLICATION ###
tic = time.process_time()
mul = np.multiply(x1,x2)
toc = time.process_time()
print ("elementwise multiplication = " + str(mul) + "\n ----- Computation time = " + str(1000*(toc - tic)) + "ms")

### VECTORIZED GENERAL DOT PRODUCT ###
tic = time.process_time()
dot = np.dot(W,x1)
toc = time.process_time()
print ("gdot = " + str(dot) + "\n ----- Computation time = " + str(1000*(toc - tic)) + "ms")


import numpy as np
import sympy as sp
from sympy.core import S, Dummy
from sympy.polys.orthopolys import (legendre_poly, laguerre_poly,
                                    hermite_poly, jacobi_poly)
from sympy.polys.rootoftools import RootOf

def gauss_lobatto(n, n_digits):
    r"""
    Computes the Gauss-Lobatto quadrature [1]_ points and weights.

    The Gauss-Lobatto quadrature approximates the integral:

    .. math::
        \int_{-1}^1 f(x)\,dx \approx \sum_{i=1}^n w_i f(x_i)

    The nodes `x_i` of an order `n` quadrature rule are the roots of `P'_(n-1)`
    and the weights `w_i` are given by:

    .. math::
        &w_i = \frac{2}{n(n-1) \left[P_{n-1}(x_i)\right]^2},\quad x\neq\pm 1\\
        &w_i = \frac{2}{n(n-1)},\quad x=\pm 1

    Parameters
    ==========

    n : the order of quadrature

    n_digits : number of significant digits of the points and weights to return

    Returns
    =======

    (x, w) : the ``x`` and ``w`` are lists of points and weights as Floats.
             The points `x_i` and weights `w_i` are returned as ``(x, w)``
             tuple of lists.

    Examples
    ========

    >>> from sympy.integrals.quadrature import gauss_lobatto
    >>> x, w = gauss_lobatto(3, 5)
    >>> x
    [-1, 0, 1]
    >>> w
    [0.33333, 1.3333, 0.33333]
    >>> x, w = gauss_lobatto(4, 5)
    >>> x
    [-1, -0.44721, 0.44721, 1]
    >>> w
    [0.16667, 0.83333, 0.83333, 0.16667]

    See Also
    ========

    gauss_legendre,gauss_laguerre, gauss_gen_laguerre, gauss_hermite, gauss_chebyshev_t, gauss_chebyshev_u, gauss_jacobi

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Gaussian_quadrature#Gauss.E2.80.93Lobatto_rules
    .. [2] http://people.math.sfu.ca/~cbm/aands/page_888.htm
    """
    x = Dummy("x")
    p = legendre_poly(n-1, x, polys=True)
    pd = p.diff(x)
    xi = []
    wi = []
    for r in pd.real_roots():
        if isinstance(r, RootOf):
            r = r.eval_rational(S(1)/10**(n_digits+2))
        xi.append(r.n(n_digits))
        wi.append((2/(n*(n-1) * p.subs(x, r)**2)).n(n_digits))

    xi.insert(0, -1)
    xi.append(1)
    wi.insert(0, (S(2)/(n*(n-1))).n(n_digits))
    wi.append((S(2)/(n*(n-1))).n(n_digits))
    return xi, wi

digits = 36
npoints = list(range(2,17))

for n in npoints:
    xi, wi = gauss_lobatto(n, digits)

    print('\nN = %3d'%(n))
    for i in range(0,n):
        print('%3d %40.36f %40.36f'%(i+1,xi[i],wi[i]))


import numpy as np
import matplotlib.pyplot as plt
from scipy.signal import hamming
from scipy.fftpack import fft, fftshift

plt.figure(1, figsize=(9.5, 6))
M = 8
N1 = 8
N2 = 16
N3 = 32
x = np.cos(2*np.pi*2/M*np.arange(M)) * np.hanning(M)

plt.subplot(4,1,1)
plt.title('x, M=8')
plt.plot(np.arange(-M/2.0,M/2), x, 'b', marker='x', lw=1.5)
plt.axis([-M/2,M/2-1,-1,1])

mX = 20 * np.log10(np.abs(fftshift(fft(x, N1))))
plt.subplot(4,1,2)
plt.plot(np.arange(-N1/2.0,N1/2), mX, marker='x', color='r', lw=1.5)
plt.axis([-N1/2,N1/2-1,-20,max(mX)+1])
plt.title('magnitude spectrum: mX1, N=8')

mX = 20 * np.log10(np.abs(fftshift(fft(x, N2))))
plt.subplot(4,1,3)
plt.plot(np.arange(-N2/2.0,N2/2),mX,marker='x',color='r', lw=1.5)
plt.axis([-N2/2,N2/2-1,-20,max(mX)+1])
plt.title('magnitude spectrum: mX2, N=16')

mX = 20 * np.log10(np.abs(fftshift(fft(x, N3))))
plt.subplot(4,1,4)
plt.plot(np.arange(-N3/2.0,N3/2),mX,marker='x',color='r', lw=1.5)
plt.axis([-N3/2,N3/2-1,-20,max(mX)+1])
plt.title('magnitude spectrum: mX3, N=32')

plt.tight_layout()
plt.savefig('zero-padding.png')
plt.show()


from numpy import random, pi
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt

Ntrials, Nhits = 1_000_000, 0
for n in range(Ntrials):
    x, y, z = random.uniform(-1, 1, 3) # draw 2 samples, each uniformly distributed over (-1,1)
    if x**2 + y**2 + z**2 < 1:
        Nhits += 1

print("Monte Carlo estimator of V(3): %.5f" % ((2**3)*(Nhits / Ntrials)))
print("Actual value of V(3) up to 5 decimal digits: %.5f" % (4*pi/3))
print("The relative error is %.5f%%" % (100 * abs((2**3)*(Nhits / Ntrials) - (4*pi/3))))


import numpy as np

def unpaired_equal_variance(a, b):
	a, b = np.array(a), np.array(b)
	a_mean, b_mean = np.mean(a), np.mean(b)
	a_var, b_var = np.var(a), np.var(b)
	t = (a_mean-b_mean)/(np.sqrt((a_var/len(a))+(b_var/len(b))))
	df = ((a_var/len(a)+b_var/len(b))**2) / (((a_var/len(a))**2)/(len(a)-1) + ((b_var/len(b))**2)/(len(b)-1) )
	return t, df

def unpaired_not_equal_variance(a, b):
	a, b = np.array(a), np.array(b)
	a_mean, b_mean = np.mean(a), np.mean(b)
	a_var, b_var = np.var(a), np.var(b)
	sp = ((len(a)-1)*a_var+(len(b)-1)*b_var)/(len(a) + len(b) - 2)
	t = (a_mean-b_mean) / (np.sqrt((sp/len(a))+(sp/len(b))))
	df = len(a) + len(b) - 2
	return t, df

def paired(a, b):
	a, b = np.array(a), np.array(b)
	a_mean, b_mean = np.mean(a), np.mean(b)
	a_var, b_var = np.var(a), np.var(b)
	c, cc = list(), list()

	for i in range(len(a)):
		c.append(a[i]-b[i])
		cc.append((a[i]-b[i])*(a[i]-b[i]))
	c, cc = np.array(c), np.array(cc)

	t = np.sum(c) / (np.sqrt((len(c)*np.sum(cc)-(np.sum(c)**2))/(len(c)-1)))
	df = len(c)-1
	return t, df

def two_samples(a, b, isPaired):
	if isPaired:
		t, df = paired(a, b)
	else:
		if np.round(np.var(np.array(a)), 5) == np.round(np.var(np.array(b)), 5):
			t, df = unpaired_equal_variance(a, b)
		else:
			t, df = unpaired_not_equal_variance(a, b)
	return t, df

def one_sample(a, mu):
	a = np.array(a)
	a_mean = np.mean(a)
	s = np.sqrt(sum([ (i-a_mean)**2 for i in a])/(len(a)-1))
	t = (a_mean-mu)/(s/np.sqrt(len(a)))
	df = len(a)-1
	return t, df

def main():
	a = [26.3, 26.43, 26.28, 26.19, 26.49]
	b = [26.22, 26.32, 26.2, 26.11, 26.42]

	t, df = one_sample(a, mu=0.5)
	print(t, df)

	t, df = two_samples(a, b, isPaired=True)
	print(t, df)

	t, df = unpaired_not_equal_variance(a, b)
	print(t, df)

	t, df = unpaired_equal_variance(a, b)
	print(t, df)

if __name__ == '__main__':
	main()


import numpy as np

#######################################
# AND, OR, NAND, XOR using PERCEPTRON #
#######################################

def step_function(x):
    y = x > 0
    return y.astype(np.int)

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

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

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

def XOR(x1, x2):
    x = np.array([x1, x2])
    w1 = np.array([[0.5, 0.5], [-0.5, -0.5]])
    b1 = np.array([-0.2, 0.7])
    w2 = np.array([0.5, 0.5])
    b2 = -0.7

    s = np.dot(x, w1) + b1
    s_activated = step_function(s)
    y = np.sum(w2 * s_activated) + b2
    if y <= 0:
        return 0
    else:
        return 1

def test_perceptrons(x):
    print('#####################')
    print('TESTING PERCEPTRONS')
    print('#####################')
    for data in x:
        print("AND(%d,%d): %d" % (data[0], data[1], AND(data[0], data[1])))

    for data in x:
        print("NAND(%d,%d): %d" % (data[0], data[1], NAND(data[0], data[1])))

    for data in x:
        print("OR(%d,%d): %d" % (data[0], data[1], OR(data[0], data[1])))

    for data in x:
        print("XOR(%d,%d): %d" % (data[0], data[1], XOR(data[0], data[1])))
    print('#####################')

if __name__=='__main__':
    test_data = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
    test_perceptrons(test_data)


import math
import numpy as np

import time

def factorial(n):
    total = n
    for i in range(1, n):
        total *= i
    if (n==0):
        return 1
    return total

def choose(n,k):
    """Standard Choose function.

    :param n: The total sample size.
    :type n: int
    :param k: The number of elements you're choosing.
    :type k: int
    :return: n choose k
    :rtype: int
    """
    return (math.factorial(n)/(math.factorial(k)*math.factorial(n-k)))

def probability(p, n, k):
    """Binomial probability function.

    :param p: Odds of success. (0.5 for a coin flip)
    :type p: float
    :param n: total sample size.
    :type n: int
    :param k: The number of elements you're choosing.
    :type k: int
    :return: The probability of the inputs.
    :rtype: float
    """
    return choose(n, k)*p**(n-k)*(1-p)**k

def nth_derivative(f, x, n):
    """Calculates the nth derivative using Newton's difference quotient method.

    :param f: input function.
    :type f: lambda
    :param x: Particular value at which f is evaluated.
    :type x: float
    :param n: The particular derivative requested.
    :type n: int
    :return: The nth derivative of f(x) at the given x.
    :rtype: float
    """
    h = 10e-2
    out_h = 1/(h**n)
    out = 0
    for k in range(0, n+1):
        out += (-1)**(k+n)*choose(n,k)*f(x +k*h)
    return out_h*out

def rectangular_integral(f, xrange, intervals):
    """Standard Riemann sum integral function.

    :param f: Input function to calculate the integral of.
    :type f: lambda
    :param xrange: List of the begin and end points for x.
    :type xrange: List
    :param intervals: How many rectangles.
    :type intervals: int
    :return: The value of the integral between the xrange.
    :rtype: float
    """
    int_out = 0
    delta_x = (max(xrange)-min(xrange))/intervals
    new_xrange = np.linspace(min(xrange), max(xrange), intervals)
    for x in new_xrange:
        int_out += f(x)
    return delta_x*int_out

def trapezoid_integral(f, xrange, intervals):
    """Calculates an integral using the trapezoidal rule for integration.

    :param f: Function to evaluate the integral on.
    :type f: lambda
    :param xrange: The range on which to evaluate f(x)
    :type xrange: list
    :param intervals: The total number of subdivisions.
    :type intervals: int
    :return: The value for the integral at x.
    :rtype: float
    """

    a, b = min(xrange), max(xrange)
    delta_x = (b-a)/intervals
    x = np.arange(1, intervals)

    int_out = f(a)
    int_out += f(b)
    int_out += sum(2*f(a+x*delta_x))

    return delta_x/2*int_out

def maclaurin_expansion(f, x, N):
    sum = f(0)
    for i in range(1, N+1):
        sum += nth_derivative(f, 0, i)/factorial(i)*x**i
    return sum

def create_graph_from_lambda(f, xrange):
    """Takes a function as an input with a specific interval xrange then creates a list with the output
    y-points. Inefficient, but useful if f is a simple math function not involving NumPy.

    :param f: The function to evaluate.
    :type f: lambda
    :param xrange: The interval on which f(x) is evaluated.
    :type xrange: list
    :return: The list of f(x) points for all x in xrange.
    :rtype: list of floats
    """
    out = []
    for x in xrange:
        out.append(f(x))
    return out

def create_derivative_graph(f, xrange, n):
    """Takes a function as an input with a specific interval xrange, then creates a list with the ouput
    y-points for the nth derivative of f.

    :param f: Input function that we wish to take the derivative of.
    :type f: lambda
    :param xrange: The interval on which to evaluate f^n(x).
    :type xrange: list
    :param n: The derivative (1st, 2nd, 3rd, etc)
    :type n: int
    :return: A list of all f^n(x) points for all x in xrange.
    :rtype: list of floats
    """
    plot_points = []
    for x in xrange:
        plot_points.append(nth_derivative(f, x, n))
    return plot_points

def eulers_method(f, y, dx, range):
    """ The Eulers method for a first order differential equation.

    :param f: First order differential equation to approximate the solution for.
    :type f: lambda
    :param y: The initial condition for the y-value.
    :type y: float, int
    :param dx: Step size. Smaller is better.
    :type dx: float
    :param range: List containing the beginning and end points for our domain.
    :type range: list
    :return: Returns a tuple for the x coordinates corresponding to a set of y coordinates,
    which approximate the solution to f.
    :rtype: list
    """
    x = min(range)
    y_space = [y]
    x_space = [x]
    while x<=max(range):
        y += f(x, y)*dx
        x += dx
        x_space.append(x)
        y_space.append(y)
    return (x_space, y_space)

def eulers_cromer_method(f, dx, y, yp, range, cromer=True):
    """ The Eulers method, and Eulers-Cromer method for second order
    differential equations.

    :param f: Second order differential equation to approximate the solution for.
    :type f: lambda
    :param dx: Step size. Smaller is better.
    :type dx: float
    :param y: The initial value of y given by initial condition.
    :type y: int, float
    :param yp: The initial value of y' given by initial condition.
    :type yp: int, float
    :param range: List containing the beginning and end points for our domain.
    :type range: list
    :param cromer: Use Cromer method or just regular Eulers, defaults to True
    :type cromer: bool, optional
    :return: Returns a tuple for the x coordinates corresponding to a set of y coordinates,
    which approximate the solution to f.
    :rtype: list
    """
    x=min(range)
    y_space = [y]
    x_space = [x]
    while x<=max(range):
        if cromer:
            yp += f(x,y,yp)*dx
            y += yp*dx
        else:
            y += yp*dx
            yp += f(x,y,yp)*dx

        x += dx
        x_space.append(x)
        y_space.append(y)
    return (x_space, y_space)

def eulers_richardson_method(f, dx, y, yp, range, return_yp = False):
    """ The Eulers Richardson method for solving a differential equation of second
    order. Works by taking the Euler method, but uses the average values instead.
    This produces a better approximation to the solution faster (in theory) than
    just the plain Eulers method.

    :param f: The input math function derivative whom to approximate its solution
    :type f: lambda
    :param dx: The step size to use. Smaller is better.
    :type dx: float
    :param y: The initial value of y given by initial condition.
    :type y: float, int
    :param yp: The initial value of y' given by initial condition.
    :type yp: float, int
    :param range: A list which specifies the beginning and the ending of our domain.
    :type range: list
    :return: Returns a tuple for the x coordinates corresponding to a set of y coordinates,
    which approximate the solution to f.
    :rtype: list
    """
    x       = min(range)
    y_space = [y]
    yp_space = [yp]
    x_space = [x]

    while x<=max(range):
        yp_mid  = yp + 1/2*f(x,y,yp)*dx
        y_mid   = y  + 1/2*yp*dx
        ypp_mid = f(1/2*x*dx, y_mid, yp_mid)
        yp      += ypp_mid*dx
        y       += yp_mid*dx

        x       += dx
        x_space.append(x)
        y_space.append(y)
        yp_space.append(yp)
    if (return_yp):
        return (x_space, y_space, yp_space)
    return (x_space, y_space)

def euler_richardson_method_2system_ode2(f, g, dt, x, y, xp, yp, range):
    """ The Euler-Richardson method working on a two-coupled system. Required
    for chapter 6, problem 5 in order to express two coupled parameterized
    functions as a single output y(x).

    :param f: The first second order diffeq expressed as a lambda.
    :type f: lambda
    :param g: The second second order diffeq expressed as a lambda.
    :type g: lambda
    :param dt: Step size. Smaller is better.
    :type dt: float
    :param x: The initial condition for x.
    :type x: float,int
    :param y: The initial condition for y.
    :type y: float,int
    :param xp: The initial condition for xp.
    :type xp: float,int
    :param yp: The initial condition for yp.
    :type yp: float,int
    :param range: A list which specifies the beginning and the ending of our domain.
    :type range: list
    :return: Returns a tuple for the t,x,y,xp,yp values as lists.
    :rtype: 5-tuple(list)
    """
    # f = x'' and g = y''
    # both requires (t, x, y, x', y')
    # get initial conditions and setup arrays
    t = min(range)
    t_space = [t]
    x_space = [x]
    y_space = [y]
    xp_space = [xp]
    yp_space = [yp]

    while t <= max(range):
        # find get midpoints
        t_mid = t + (1/2)*dt
        xp_mid = xp + 1/2*f(t, x, y, xp, yp)*dt
        yp_mid = yp + 1/2*g(t, x, y, xp, yp)*dt
        x_mid = x + (1/2)*xp*dt
        y_mid = y + (1/2)*yp*dt

        # get slopes
        xp_s = f(t_mid, x_mid, y_mid, xp_mid, yp_mid)
        yp_s = g(t_mid, x_mid, y_mid, xp_mid, yp_mid)
        x_s = xp_mid
        y_s = yp_mid

        # update values
        t += dt
        x += x_s*dt
        y += y_s*dt
        xp += xp_s*dt
        yp += yp_s*dt

        # append values
        t_space.append(t)
        x_space.append(x)
        xp_space.append(xp)
        y_space.append(y)
        yp_space.append(yp)

    return (t_space, x_space, y_space, xp_space, yp_space)

def rk2_first_order_method(f, y, dx, range):
    """ Runge-Kutta 2 method for a first order differential
    equation.

    :param f: Input first order derivative to appromixate.
    :type f: lambda
    :param y: The initial value given for y.
    :type y: float, int
    :param dx: Step size. Smaller is better.
    :type dx: float
    :param range: A list which specifies the beginning and the ending of our domain.
    :type range: list
    :return: Returns a tuple for the x coordinates corresponding to a set of y coordinates,
    which approximate the solution to f.
    :rtype: tuple(list, list)
    """
    x = min(range)

    x_space = [x]
    y_space = [y]

    while x<=max(range):
        yp_mid = f(x+1/2*dx, y + 1/2*dx*f(x,y))
        y += yp_mid*dx

        x += dx
        x_space.append(x)
        y_space.append(y)
    return (x_space, y_space)

def rk4_first_order_method(f, y, dx, range):
    """Runge-Kutta 4 method for a first order differential
    equation.

    :param f: Input first order derivative to appromixate.
    :type f: lambda
    :param y: The initial value given for y.
    :type y: float, int
    :param dx: Step size. Smaller is better.
    :type dx: float
    :param range: A list which specifies the beginning and the ending of our domain.
    :type range: list
    :return: Returns a tuple for the x coordinates corresponding to a set of y coordinates,
    which approximate the solution to f.
    :rtype: list
    """
    x = min(range)

    x_space = [x]
    y_space = [y]

    while x<=max(range):
        k_1 = f(x, y)*dx

        k_2 = f(x+1/2*dx, y + 1/2*k_1)*dx

        k_3 = f(x+1/2*dx, y + 1/2*k_2)*dx

        k_4 = f(x + dx, y + k_3)*dx

        y   += 1/6*(k_1+2*(k_2+k_3)+k_4)

        x += dx
        x_space.append(x)
        y_space.append(y)
    return (x_space, y_space)

def rk4_second_order_method(f, y, z, dx, range):
    """Runge-Kutta 4 method for a second order differential
    equation.

    :param f: Input first order derivative to appromixate.
    :type f: lambda
    :param y: The initial value given for y.
    :type y: float, int
    :param z: The initial value given for z.
    :type z: float, int
    :param dx: Step size. Smaller is better.
    :type dx: float
    :param range: A list which specifies the beginning and the ending of our domain.
    :type range: list
    :return: Returns a tuple for the x coordinates corresponding to a set of y coordinates,
    which approximate the solution to f.
    :rtype: list
    """
    x = min(range)

    x_space = [x]
    y_space = [y]
    z_space = []

    while x<=max(range):
        k_1 = z*dx
        l_1 = f(x, y, z)*dx

        k_2 = (z+1/2*l_1)*dx
        l_2 = f(x+1/2*dx, y + 1/2*k_1, z + 1/2*l_1)*dx

        k_3 = (z + 1/2*l_2)*dx
        l_3 = f(x+1/2*dx, y + 1/2*k_2, z + 1/2*l_2)*dx

        k_4 = (z + l_3)*dx
        l_4 = f(x + dx, y + k_3, z + l_3)*dx

        y += 1/6*(k_1+2*k_2+2*k_3+k_4)
        z += 1/6*(l_1+2*l_2+2*l_3+l_4)

        x += dx
        x_space.append(x)
        y_space.append(y)
        z_space.append(z)
    return (x_space, y_space, z_space)

def build_extracted_list(input_list, subinterval):
    """ A utility function to extract a number of elements from a list, leaving only a certain subset.
    Generates a new list with just the subset. Creates the subset by specifying a sub-interval.

    :param input_list: The list to be extracted
    :type input_list: list
    :param subinterval: How many other elements to keep (for example, 10 means keep every 10 elements).
    :type subinterval: int
    :return: The extracted list.
    :rtype: list
    """
    out = []
    wait = subinterval
    for i in input_list:
        if wait == subinterval:
            out.append(i)
            wait = 0
        else:
            wait += 1
    return out

def newtons_law_of_cooling(room_temp, object_temp, t, k):
    return room_temp + (object_temp-room_temp)*math.exp(-k*t)

def diffusion_monte_carlo(num_of_gas, time_total, t_min=None):
    """ Generates a list of points to which a gas of num_of_gas
    molecules diffuses through a membrane and into a separate
    container within a total time total_time.

    :param num_of_gas: The amount of gas.
    :type num_of_gas: int
    :param time_total: The total time to run the simulation for.
    :type time_total: int
    :param t_min: Start at t=0 or not, defaults to None
    :type t_min: int, float, optional
    :return: Returns a list of diffusion of molecules per
    time.
    :rtype: tuple(time, molecules)
    """
    if t_min:
        time_array  = np.linspace(t_min, time_total, time_total+1, dtype=int)
    else:
        time_array  = np.linspace(1, time_total, time_total+1, dtype=int)

    gas_left    = np.array([num_of_gas])
    random_gas  = np.random.randint(1, num_of_gas+1, size=time_total)

    current_gas = num_of_gas

    for x in random_gas:
        if x <= current_gas:
            current_gas -= 1
        else:
            current_gas += 1
        gas_left = np.append(gas_left, current_gas)
    return (time_array, gas_left)

def nuclear_decay_monte_carlo(initial_amount, probability, t_max, ret_half=False):
    """ Generates a list of points to which can be graphed, tracing out the
    nuclear decay of an element given an initial amount initial_amount, probability,
    a maximum time interval.

    :param initial_amount: The starting mass.
    :type initial_amount: int, float
    :param probability: The likelyhood for decay to occur.
    :type probability: float 0 to 1.
    :param t_max: The maximum time to go for.
    :type t_max: int
    :param ret_half: Should half-life be returned or not, defaults to False.
    :type ret_half: bool, optional
    :return: Returns the amount of nuclei decayed per time and the half-life
    if specified by the optional variable.
    :rtype: tuple(time, nuclei, [optional] half-life)
    """
    particle_array = np.arange(1, initial_amount+1, dtype=int)

    nuclei_left    = np.array([initial_amount+1])

    half_life      = 0

    # Array math.
    for t in range(0, t_max):
        random_array   = np.random.uniform(0, 1, size=particle_array.size)
        random_bool    = np.where(random_array <= probability, True, False)
        particle_array = particle_array[random_bool != True]
        nuclei_left    = np.append(nuclei_left, particle_array.size)
        if not half_life and particle_array.size <= initial_amount/2:
            # Average.
            half_life  = (2*t-1)/2

    time_array = np.linspace(1, nuclei_left.size, nuclei_left.size, dtype=int)
    if ret_half:
        return (time_array, nuclei_left, half_life)
    return (time_array, nuclei_left)

def virus_monte_carlo(initial_infected, population, k):
    """ Generates a list of points to which some is infected
    at a given value k starting with initial_infected infected.
    There is no mechanism to stop the infection from reaching
    the entire population.

    :param initial_infected: The amount of people whom are infected at the
    start.
    :type initial_infected: int
    :param population: The total population sample.
    :type population: int
    :param k: The rate of infection.
    :type k: float
    :return: An array of the amount of people per time infected.
    :rtype: tuple(time, infected)
    """
    people_array    = np.arange(1, population+1, dtype=int)
    current_infected = initial_infected
    people_infected = np.array([current_infected])
    time_array      = np.array([0])

    # Array math.
    counter = 0
    for _ in people_array:
        probability      = (k)*current_infected/population
        random_array     = np.random.uniform(0, 1, size=people_array.size)
        random_bool      = np.where(random_array <= probability, True, False)
        people_array     = people_array[random_bool != True]
        if people_array.size != population:
            current_infected = (population-people_array.size)
        people_infected  = np.append(people_infected, current_infected)
        counter+=1
        time_array = np.append(time_array, counter)
        if people_infected.size == population:
            break

    return (time_array, people_infected)

def random_walk(n, p):
    """ Based on the number of times n, with a probability
    to move to the right p, this function calculates the mean
    of the end value along a one-dimensional axis to which it
    'walked'.

    :param n: Number of steps to take.
    :type n: int
    :param p: Probability from 0 to 1
    :type p: float
    :return: The expected value on the number line to which we
    end up.
    :rtype: float
    """
    random_array = np.random.uniform(0, 1, n)
    left = random_array[random_array > p].size
    right = n - left

    return (right-left)

def monte_carlo_integration(f, n, a, b, ret_arrays=False):
    """ Calculate the integral of a function f
    from a to b with a number of random points n. Tried to
    optimize by removing for loops in favor of NumPy arrays with
    conditional indexing due to their cache efficiency vs Python
    list pointer dereferences.
    Fastest speed I've gotten is about 130ms.

    :param f: The input math function.
    :type f: lambda
    :param n: The total number of points.
    :type n: int
    :param a: Starting position.
    :type a: int, float
    :param b: Ending position.
    :type b: int, float
    :param ret_arrays: Return arrays in order to graph, defaults to False.
    :type ret_arrays: Boolean, defaults to False.
    :return: The estimated value of the integral of f.
    :rtype: float
    """
    x = np.random.uniform(0, 1, n)*(b-a)+a
    f_array = f(x)

    positive_x = x[f_array >= 0]
    negative_x = x[f_array < 0]
    if positive_x.size > 0:
        h = np.max(f_array)
    else:
        h = np.max(-f_array)

    y_positive = np.random.uniform(0, 1, positive_x.size)*h
    y_negative = np.random.uniform(0, 1, negative_x.size)*h

    xy_indices_below = y_positive <= f(positive_x)
    xy_indices_above = y_negative <= -f(negative_x)
    n_inside_below = y_positive[xy_indices_below]
    n_inside_above = -y_negative[xy_indices_above]

    if ret_arrays:
        n_inside_x = np.append(positive_x[xy_indices_below],negative_x[xy_indices_above])
        n_inside_y = np.append(n_inside_below, n_inside_above)
        return n_inside_x, n_inside_y

    return h*(b-a)*(n_inside_below.size-n_inside_above.size)/(n)


'''
Implementation of the (unsupervised) K-means clustering algorithm
'''
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs
class KMeansClustering():
    def __init__(self, X, k):
        self.X = X
        self.num_data_points = self.X.shape[0]
        self.num_features = self.X.shape[1]
        self.k = k
        self.max_iter = 100
        self.plot_fig = True

    def initialize_random_centroids(self, X):
        '''
        randomly assign each cluster as one of the randomly selected data points features
        '''
        centroids = np.zeros((self.k, self.num_features))
        for i in range(self.k):
            centroids[i] = X[np.random.choice(range(self.num_data_points))]
        return centroids

    def create_clusters(self, X, centroids):
        '''
        assign each point to the closest cluster in Euclidean distance
        '''
        clusters = [[] for _ in range(self.k)]

        for i in range(self.num_data_points):
            closest_centroid = np.argmin(np.sqrt(np.sum((X[i] - centroids)**2, axis=1)))
            clusters[closest_centroid].append(i)

        return clusters

    def calclulate_new_centroids(self, clusters, X):
        '''
        calculate new cluster means
        '''
        centroids = np.zeros((self.k, self.num_features))
        for i in range(self.k):
            new_centroid = np.mean(X[clusters[i]], axis=0)
            centroids[i] = new_centroid
        return centroids
    def predict_cluster(self, clusters, X):
        '''
        for each point assign its cluster label
        '''
        y_pred = np.zeros((self.num_data_points))

        for i in range(len(clusters)):
            cluster = clusters[i]
            for ii in range(len(cluster)):
                sample_idx = cluster[ii]
                y_pred[sample_idx] = i
        return y_pred
    def plot_figure(self, X, y):
        '''
        plot the assigned clusters
        '''
        plt.scatter(X[:,0], X[:,1], cmap=plt.cm.Spectral, c=y, s=40)
        plt.show()

    def fit(self, X):
        '''
        train the clustering algorithm and iteratively improve the clusters
        '''
        centroids = self.initialize_random_centroids(X)

        for it in range(self.max_iter):
            clusters = self.create_clusters(X, centroids)
            previous_centroids = centroids
            centroids = self.calclulate_new_centroids(clusters, X)

            diff = centroids - previous_centroids

            if not diff.any():
                print('Done')
                break
        y_pred = self.predict_cluster(clusters, X)
        if self.plot_fig:
            self.plot_figure(X, y_pred)
        return y_pred

if __name__ == '__main__':
    np.random.seed(10)
    num_clusters = 5
    X, _ = make_blobs(n_samples=500, n_features=2, centers=num_clusters) # create some data

    Kmeans = KMeansClustering(X, num_clusters)
    y_pred = Kmeans.fit(X)


from numpy import exp, tanh, maximum

def calculate_identity(input_sum_array):
    """Applies identity function to input."""
    return input_sum_array

def calculate_sigmoid(input_sum_array):
    """Applies sigmoid function to input."""
    return 1 / (1 + exp(-input_sum_array))

def calculate_tanh(input_sum_array):
    """Applies hyperbolic tangent function to input."""
    return tanh(input_sum_array)

def calculate_relu(input_sum_array):
    """Applies rectified linear unit function to input."""
    return maximum(input_sum_array, 0, input_sum_array)

def calculate_output(input_sum_array, activation_function_id):
    """Applies activation function to input, based on determined id."""
    activation_function = _ACTIVATION_FUNCTIONS.get(activation_function_id)
    return activation_function(input_sum_array)

_ACTIVATION_FUNCTIONS = {
    'identity': calculate_identity,
    'sigmoid': calculate_sigmoid,
    'tanh': calculate_tanh,
    'relu': calculate_relu
}

#===============================================================================
# _NON_LINEAR_ACTIVATION_FUNCTIONS = {
#     'sigmoid': calculate_sigmoid,
#     'tanh': calculate_tanh,
#     'relu': calculate_relu
# }
#===============================================================================

_NON_LINEAR_ACTIVATION_FUNCTIONS = {
    'sigmoid': calculate_sigmoid,
    'tanh': calculate_tanh
}

# Creating random activation functions on the fly.


import numpy as np

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

def log_likelihood(y, y_hat):
    return np.sum(y * np.log(y_hat) + (1-y) * np.log(1-y_hat))

class LogisticRegression:
    """Classification model based on the logistic function.

    Classification model that maps a linear combination of the
    input features to a probability using the logistic function.

    Parameters
    ----------
    n_features : `int`
        Dimensionality, or number of features, of the training data.

    """

    def __init__(self, n_features):
        self.n_features = n_features
        self.w = self._initialize_weights(self.n_features+1)

    def _initialize_weights(self, n_features):
        return np.zeros(n_features)

    def fit(self, X, y, learning_rate=1e-3, iterations=1000):
        """Trains the model with gradient descent.

        Parameters
        ----------
        x : `numpy.ndarray` (n_examples, n_features)
            Independant variables of the training examples from which to
            estimate the parameters of the model.

        y : `numpy.ndarray` (n_examples, n_features)
            Labels corresponding to each training example in ``x``.

        learning_rate : `int`, optional
            Size of the step to take using the negative of the gradient.
            Defaults to 1e-3

        iterations : `int`, optional
            Number of iterations to perform gradient descent. Defaults to 1000.

        Returns
        -------
        loss : `list`
            List containing the loss on the training set at each iteration

        acc : `list`
            List containing the accuracy on the training set at each iteraton

        """

        # Add a first dimension of ones corresponding to the intercept
        x = np.hstack((np.ones([X.shape[0], 1]), X))

        loss = []
        acc = []
        for _ in range(iterations):
            # Forward pass
            z = np.matmul(x, self.w)
            y_hat = sigmoid(z)

            loss.append(self.loss(y, y_hat))
            acc.append(np.mean(np.around(y_hat) == y))

            # Gradients
            dz = y_hat - y
            dw = np.matmul(x.T, dz)

            self.w -= learning_rate*dw

        return loss, acc

    def predict(self, X):
        """Predicts the response using the current estimands.

        Parameters
        ----------
        X : `numpy.ndarray` (n_examples, n_features)
            Data from where to infer a prediction

        Returns
        -------
        predicted : `numpy.ndarray` (n_examples,)
            Predicted probabilities for each data example

        """

        X = np.atleast_2d(X)
        X = np.reshape(X, [-1, self.n_features])

        # Add a first dimension of ones corresponding to the intercept
        X = np.hstack((np.ones([X.shape[0], 1]), X))
        return sigmoid(np.matmul(X, self.w))

    def loss(self, y, y_hat):
        # Return loss to minimize by gradient descent.
        return -(1.0/len(y))*log_likelihood(y, y_hat)


from sympy import *

# If an object is dropped from a 155-foot-high building, its position (in feet above the ground) is given by s(t), where t is the time in seconds since it was dropped.

t = symbols( 't' )
S = -16*t**2 + 186

dS = diff( S, t )

# The object's velocity 1 second after being dropped is
one_s = dS.subs( { t: 1 } )
one_s

# The object will hit the ground in:
ground = solve( S, t )[ 1 ].evalf()
# (Round to the nearest tenth as needed.)
round( ground, 1 )

# The object's velocity upon impact is:
impact_velocity = dS.subs( { t: ground } )
# (Round to the nearest tenth as needed.)
round( impact_velocity, 1 )


import numpy as np

# Collection of activation functions
# Reference: https://en.wikipedia.org/wiki/Activation_function

class Sigmoid():
    def __call__(self, x):
        return 1 / (1 + np.exp(-x))

    def gradient(self, x):
        return self.__call__(x) * (1 - self.__call__(x))

class Softmax():
    def __call__(self, x):
        e_x = np.exp(x - np.max(x, axis=-1, keepdims=True))
        return e_x / np.sum(e_x, axis=-1, keepdims=True)

    def gradient(self, x):
        p = self.__call__(x)
        return p * (1 - p)

class TanH():
    def __call__(self, x):
        return 2 / (1 + np.exp(-2*x)) - 1

    def gradient(self, x):
        return 1 - np.power(self.__call__(x), 2)

class ReLU():
    def __call__(self, x):
        return np.where(x >= 0, x, 0)

    def gradient(self, x):
        return np.where(x >= 0, 1, 0)

class LeakyReLU():
    def __init__(self, alpha=0.2):
        self.alpha = alpha

    def __call__(self, x):
        return np.where(x >= 0, x, self.alpha * x)

    def gradient(self, x):
        return np.where(x >= 0, 1, self.alpha)

class ELU():
    def __init__(self, alpha=0.1):
        self.alpha = alpha

    def __call__(self, x):
        return np.where(x >= 0.0, x, self.alpha * (np.exp(x) - 1))

    def gradient(self, x):
        return np.where(x >= 0.0, 1, self.__call__(x) + self.alpha)

class SELU():
    # Reference : https://arxiv.org/abs/1706.02515,
    # https://github.com/bioinf-jku/SNNs/blob/master/SelfNormalizingNetworks_MLP_MNIST.ipynb
    def __init__(self):
        self.alpha = 1.6732632423543772848170429916717
        self.scale = 1.0507009873554804934193349852946

    def __call__(self, x):
        return self.scale * np.where(x >= 0.0, x, self.alpha*(np.exp(x)-1))

    def gradient(self, x):
        return self.scale * np.where(x >= 0.0, 1, self.alpha * np.exp(x))

class SoftPlus():
    def __call__(self, x):
        return np.log(1 + np.exp(x))

    def gradient(self, x):
        return 1 / (1 + np.exp(-x))


import numpy as np

class NumMethods:
    """
    Methods of numerical differentiation and integration.

        - Differentiation: Forward differences, endpoint and midpoint 3 and 5 point differences,
                           second derivative midpoint

        - Integration: Trapezoid rule (composite), Simpson's rule (composite + adaptive),
                       Gaussian quadrature (adaptive)
    """

    def __init__(self, func=None):
        self.func = func

    def for_diff(self, x, h=10**(-5)):
        """
          Forward Differences: Approximates f'(x) using (f(x+h)-f(x))/h for very small h.

          Args:
              x (float): Approximation point
              h (float): Increment

          Returns:
              float: f'(x) approximation
          """
        return (self.func(x + h) - self.func(x))/h

    def end_3diff(self, x, h=10**(-5)):
        """
          Three-Point Endpoint: Approximates f'(x)

          Args:
              x (float): Approximation point
              h (float): Increment

          Returns:
              float: f'(x) approximation
          """
        return (-3*self.func(x) + 4*self.func(x+h) - self.func(x+2*h))/(2*h)

    def mid_3diff(self, x, h=10**(-5)):
        """
          Three-Point Midpoint: Approximates f'(x)

          Args:
              x (float): Approximation point
              h (float): Increment

          Returns:
              float: f'(x) approximation
          """
        return (self.func(x+h) - self.func(x-h))/(2*h)

    def end_5diff(self, x, h=10**(-5)):
        """
          Five-Point Endpoint: Approximates f'(x)

          Args:
              x (float): Approximation point
              h (float): Increment

          Returns:
              float: f'(x) approximation
          """
        return (-25*self.func(x) + 48*self.func(x+h) - 36*self.func(x+2*h) +
                16*self.func(x+3*h) - 3*self.func(x+4*h))/(12*h)

    def mid_5diff(self, x, h=10**(-5)):
        """
          Five-Point Midpoint: Approximates f'(x)

          Args:
              x (float): Approximation point
              h (float): Increment

          Returns:
              float: f'(x) approximation
          """
        return (self.func(x-2*h) - 8*self.func(x-h) + 8*self.func(x+h) -
                self.func(x+2*h))/(12*h)

    def second_diff(self, x, h=10**(-5)):
        """
          Second Derivative Midpoint: Approximates f''(x)

          Args:
              x (float): Approximation point
              h (float): Increment

          Returns:
              float: f''(x) approximation
          """
        return (self.func(x-h) - 2*self.func(x) + self.func(x+h))/(h**2)

    def trapezoid_rule(self, a, b):
        """
          Approximate area under f(x) within [a,b] using a trapezoid.

          Args:
              a (float): Defines [a,b] integration bounds
              b (float): Defines [a,b] integration bounds

          Returns:
              float: int_(a,b)f(x)dx approximation
          """
        return ((b-a)/2) * (self.func(a) + self.func(b))

    def trap_comp(self, a, b, n=10):
        """
          Composite trapezoid rule approximation of int_(a,b)f(x)dx by summing
          trapezoidal approximations for n sub intervals.

          Args:
              a (float): Defines [a,b] integration bounds
              b (float): Defines [a,b] integration bounds
              n (int): Number of sub intervals. Must be an even integer

          Returns:
              float: int_(a,b)f(x)dx approximation
          """
        h = (b-a)/n
        mid_terms = 0
        for i in range(n):
            mid_terms += self.func(a+i*h)
        return (h/2) * (self.func(a) + 2*mid_terms + self.func(b))

    def simpsons_rule(self, a, b):
        """
          Simpson's rule approximation of int_(a,b)f(x)dx.

          Args:
              a (float): Defines [a,b] integration bounds
              b (float): Defines [a,b] integration bounds

          Returns:
              float: int_(a,b)f(x)dx approximation
          """
        h = (b-a)/2
        return (h/3) * (self.func(a) + 4*self.func((b+a)/2) + self.func(b))

    def simp_comp(self, a, b, n=10):
        """
          Composite Simpson's rule approximation of int_(a,b)f(x)dx by applying
          Simpson's rule over n sub intervals.

          Args:
              a (float): Defines [a,b] integration bounds
              b (float): Defines [a,b] integration bounds
              n (int): Number of sub intervals. Must be an even integer

          Returns:
              float: int_(a,b)f(x)dx approximation
          """
        h = (b-a)/n
        x_0 = self.func(a) + self.func(b)
        x_odd = 0
        x_even = 0
        for i in range(1, n):
            x = a + i*h
            if i % 2 == 0:
                x_even += self.func(x)
            else:
                x_odd += self.func(x)
        xi = h*(x_0 + 2*x_even + 4*x_odd)/3
        return xi

    def simp_adpt(self, a, b, tol=10**(-5), n_0=20):
        """
          Adaptive Simpson's rule approximation of int_(a,b)f(x)dx.

          Args:
              a (float): Defines [a,b] integration bounds
              b (float): Defines [a,b] integration bounds
              tol (float): Error tolerance
              n_0 (int): Max sub interval depth

          Returns:
              float: int_(a,b) f(x)dx approximation
          """
        approx = 0
        i = 0
        e, a0, h, fa, fc, fb, s, l = np.zeros((8, n_0))
        e[i] = 10*tol
        a0[i] = a
        h[i] = (b-a)/2
        fa[i] = self.func(a)
        fc[i] = self.func(a+h[i])
        fb[i] = self.func(b)
        s[i] = h[i]*(fa[i] + 4*fc[i] + fb[i])/3
        l[i] = 1
        while i > -1:
            fd = self.func(a0[i] + h[i]/2)
            fe = self.func(a0[i] + 3*h[i]/2)
            s1 = h[i]*(fa[i] + 4*fd + fc[i])/6
            s2 = h[i]*(fc[i] + 4*fe + fb[i])/6
            v1 = a0[i]
            v2 = fa[i]
            v3 = fc[i]
            v4 = fb[i]
            v5 = h[i]
            v6 = e[i]
            v7 = s[i]
            v8 = l[i]
            i -= 1

            if abs(s1 + s2 - v7) < v6:
                approx += s1 + s2
            elif v8 >= n_0:
                print("Level exceeded")
                return approx
            else:
                i += 1

                a0[i] = v1 + v5
                fa[i] = v3
                fc[i] = fe
                fb[i] = v4
                h[i] = v5/2
                e[i] = v6/2
                s[i] = s2
                l[i] = v8 + 1

                i += 1

                a0[i] = v1
                fa[i] = v2
                fc[i] = fd
                fb[i] = v3
                h[i] = h[i-1]
                e[i] = e[i-1]
                s[i] = s1
                l[i] = l[i-1]
        return approx

    def gquad(self, a, b):
        """
          Two-point Gaussian quadrature formula for approximating int_(a,b)f(x)dx where
          [a,b] is a general interval. Derived from the approximation
          int_(-1,1) f(x)dx (approximately)= f( -sqrt(3)/3 ) - f( sqrt(3)/3 )

          Args:
              a (float): Defines [a,b] integration bounds
              b (float): Defines [a,b] integration bounds

          Returns:
              float: int_(a,b)f(x)dx approximation
          """
        return (self.func((1/2) * ((b-a) * (-np.sqrt(3)/3) + a+b)) +
                self.func((1/2) * ((b-a) * (np.sqrt(3)/3) + a+b))) * (b-a)/2

    def gquad_adpt(self, a, b, level=0, current_sum=0, n_0=20, tol=10**(-7)):
        """
          Adaptive two-point Gaussian quadrature. Applies two-point Gaussian quadrature on sub intervals
          from splitting [a,b] until specified level of precision is reached.

          Args:
              a (float): Defines [a,b] integration bounds
              b (float): Defines [a,b] integration bounds
              level (int): Counts interval split depth
              current_sum (float): Current interval approximation
              n_0 (int): Max depth
              tol (float): Error tolerance

          Returns:
              float: int_(a,b)f(x)dx approximation
          """
        level += 1
        one_gauss = self.gquad(a, b)
        c = (a+b)/2
        two_gauss = self.gquad(a, c) + self.gquad(c, b)
        if level > n_0:
            print("Max depth reached")
        else:
            if abs(one_gauss - two_gauss) < tol:
                current_sum += two_gauss
            else:
                current_sum = self.gquad_adpt(a, c, level=level, current_sum=current_sum, n_0=n_0)
                current_sum = self.gquad_adpt(c, b, level=level, current_sum=current_sum, n_0=n_0)
        return current_sum


import numpy as np

def tanh(x):
    t = (np.exp(x) - np.exp(-x)) / (np.exp(x) + np.exp(-x))
    dt = 1 - t ** 2
    return t, dt

x1 = -0.928
t1, dt1 = tanh(x1)
print("------------------------------------------------")
print("The tansigmoid of x1: ", t1)
print("The derivative of tansigmoid of x1: ", dt1)
print("------------------------------------------------")


#!/usr/bin/env python
# Author: Andrew Jewett (jewett.aij at g mail)
# License: MIT License  (See LICENSE.md)
# Copyright (c) 2020, Scripps Research
# All rights reserved.

"""
   A crude command line utility which reads the coordinates of 3 points,
   finds the circle which passes through them, and returns the center
   and the radius of that circle.

   Usage:

     curvature3pts.py < coordinates.txt

   Details:

     This program reads a text file from the standard input in multi-column
   format, with N numbers on each line delimited by spaces. (Usually N=2 or 3.)
   This file should contain exactly 3 lines (corresponding to the 3 points
   on the circle).

   This program works in an arbitrary number of dimensions.
   (The number of dimensions is specified by the number of columns in the file.)

"""

from math import *
import sys
import numpy as np

def CircleFrom3Points2D(r1, r2, r3):
    """
    3 points pass through a circle.  Find the center of that circle
    and its radius.  3 eqns (below) with 3 unknowns (x0, y0, r)
       (x1 - x0)^2 + (y1 - y0)^2  =  r^2
       (x2 - x0)^2 + (y2 - y0)^2  =  r^2
       (x3 - x0)^2 + (y3 - y0)^2  =  r^2
    Solve for (x0, y0) using A * (x0, y0) = B where:
    """
    B = np.array([r2[0]**2 - r1[0]**2 +
                  r2[1]**2 - r1[1]**2,
                  r3[0]**2 - r2[0]**2 +
                  r3[1]**2 - r2[1]**2])
    A = np.array([[2.0 * (r2[0] - r1[0]),
                   2.0 * (r2[1] - r1[1])],
                  [2.0 * (r3[0] - r2[0]),
                   2.0 * (r3[1] - r2[1])]])
    x0, y0 = np.linalg.solve(A,B)
    r = sqrt((r1[0] - x0)**2 + (r1[1] - y0)**2)
    return r, x0, y0

def CircleFrom3Points(r1, r2, r3):
    """
    This is the N-dimensional generalization of CircleFrom3Points2D().
    (It works in 3D and also higher dimensions.
     This function is not necessary for "sabl.py" which is a 2D program.
     Consequently, I never got around to testing it carefully.
     Hopefully this function works, but test it first.  -A 2020-6-10)
    """

    # Decompose this into a 2D problem using Graham-Schmidt decomposition
    # of the original vectors into the basis defined by va=r1-r2 and vb=r3-r2.
    # Then apply CircleFrom3Points2D() to find the radius of curvature
    # and the central point.

    va = r1-r2
    vb = r3-r2

    ea = va / np.linalg.norm(va)
    eb = vb - np.inner(vb,ea)*ea
    eb /= np.linalg.norm(eb)

    # Now express the vectors r1-r2, r2-r2, and r3-r2
    # in the basis formed by unit vectors ea and eb.
    # The resutling _r1, _r2, _r3 vectors are 2D vectors.

    _r1 = np.array([np.inner(va, ea), np.inner(va, eb)])
    _r2 = np.array(r2-r2)  # (this should be the zero vector)
    _r3 = np.array([np.inner(vb, ea), np.inner(vb, eb)])

    # Now invoke "CircleFrom3Points2D()" to calculate the radius and center
    # of the circle in this 2D coordinate system
    r, x0, y0 =  CircleFrom3Points2D(_r1, _r2, _r3)

    # Now convert x0, y0 back into the original coordinate system
    r0 = r2 + x0*ea + y0*eb

    # Now return the results to the caller
    return r, r0

def main():
    lines = sys.stdin.readlines()

    # r1 r2 and r3 are arrays containing the xyz coordinates of the 3 points
    r1 = np.array(list(map(float, lines[0].strip().split())))  #x,y,z of point 1
    r2 = np.array(list(map(float, lines[1].strip().split())))  #x,y,z of point 2
    r3 = np.array(list(map(float, lines[2].strip().split())))  #x,y,z of point 3

    radius, center = CircleFrom3Points(r1, r2, r3)

    sys.stdout.write('circle_center =')
    for i in range(0, center.size):
        sys.stdout.write(' '+str(center[i]))
    sys.stdout.write('\n')

    sys.stdout.write('radius = '+str(radius)+'\n')

if __name__ == '__main__':
    main()


# https://github.com/llSourcell/Intro_to_the_Math_of_intelligence
# Create a linear regression model and train using Gradient Descent from scratch.

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

datapath = "data.csv"
alpha = 3e-5         # learning rate
num_iter = 100     # number of iterations for gradient descent

def data_process(path):
    "Import and preprocess data"
    df = pd.read_csv(path)
    df = df.reindex(np.random.permutation(df.index))
    return df
'''
def randomize(df):
    "Randomly shuffle the items of data"
    r_data = df.reindex(np.random.permutation(df.index))
    return r_data
'''
def gradient_descent(x, y, N, m, b, alpha, num_iter):
    "Optimize the model using Gradient Descent algorithm"
    loss_info = []
    inter_loss = 0
    for i in range(num_iter):
        b_loss = 0
        m_loss = 0
        for j in range(N):
            y_pred = m * x[j] + b
            b_loss += (y[j] - y_pred)
            m_loss += (y[j] - y_pred) * x[j]

        m = m - alpha * (-2/float(N) * m_loss)
        b = b - alpha * (-2/float(N) * b_loss)

        for k in range(N):
            y_pred = m * x[k] + b
            inter_loss += (y_pred - y[k])**2
        inter_loss = inter_loss / float(N)
        loss_info.append([i, inter_loss])

    return m, b, loss_info

def linear_regressor(r_data):
    "Create a linear regression model"

    x = r_data["Distance cycled"]
    y = r_data["Calories burnt"]
    N = len(r_data)
    m_init = 0    #1.4
    b_init = 0
    loss = 0

    m, b, loss_info = gradient_descent(x, y, N, m_init, b_init, alpha, num_iter)
    print "m, b, alpha = %0.7s, %0.7s, %s" % (m, b, alpha)

    for i in range(N):
        #y_pred = m_init * x[i] + b_init
        y_pred = m * x[i] + b
        loss += (y_pred - y[i])**2

    final_loss = loss / float(N)
    return final_loss, loss_info, m, b

def plot_loss(loss_info):
    iter_no = []
    loss_value = []
    for item in loss_info:
        iter_no.append(item[0])
        loss_value.append(item[1])

    plt.plot(iter_no, loss_value)
    plt.show()

def main():
    data = data_process(datapath)
    avg_loss, loss_info, slope, intercept = linear_regressor(data)
    #plot_loss(loss_info)

    x = data["Distance cycled"]
    y = data["Calories burnt"]
    y_pred = slope * x + intercept

    x_new = [72, 73.5, 74.9, 76.5, 78.5, 80]
    y_new = [(slope * i + intercept) for i in x_new]

    plt.plot(x, y, 'ro', x, y_pred, x_new, y_new, 'bs')
    plt.show()

    print "Final avg loss = %s" % avg_loss

if __name__ == '__main__':
    main()

'''
Results-
m, b, alpha = 1.478566841371454, 0.047075978779471006, 0.0003
Final loss = 112.639852759
'''


# Copyright 2016 Enthought, Inc. All Rights Reserved
"""
Topics: NumPy array indexing and array math.

Use array slicing and math operations to calculate the
numerical derivative of ``sin`` from 0 to ``2*pi``.  There is no
need to use a for loop for this.

Plot the resulting values and compare to ``cos``.

Bonus
~~~~~

Implement integration of the same function using Riemann sums or the
trapezoidal rule.

"""
from numpy import linspace, pi, sin, cos, cumsum
from matplotlib.pyplot import plot, show, subplot, legend, title

# calculate the sin() function on evenly spaced data.
x = linspace(0,2*pi,101)
y = sin(x)

# calculate the derivative dy/dx numerically.
# First, calculate the distance between adjacent pairs of
# x and y values.
dy = y[1:]-y[:-1]
dx = x[1:]-x[:-1]

# Now divide to get "rise" over "run" for each interval.
dy_dx = dy/dx

# Assuming central differences, these derivative values
# centered in-between our original sample points.
centers_x = (x[1:]+x[:-1])/2.0

# Plot our derivative calculation.  It should match up
# with the cos function since the derivative of sin is
# cos.
subplot(1,2,1)
plot(centers_x, dy_dx,'rx', centers_x, cos(centers_x),'b-')
title(r"$\rm{Derivative\ of}\ sin(x)$")

# Trapezoidal rule integration.
avg_height = (y[1:]+y[:-1])/2.0
int_sin = cumsum(dx * avg_height)

# Plot our integration against -cos(x) - -cos(0)
closed_form = -cos(x)+cos(0)
subplot(1,2,2)
plot(x[1:], int_sin,'rx', x, closed_form,'b-')
legend(('numerical', 'actual'))
title(r"$\int \, \sin(x) \, dx$")
show()


import numpy as np
import matplotlib.pyplot as plt

def estimate_coefficients(x, y):
    # size of the dataset OR number of observations/points
    n = np.size(x)

    # mean of x and y
    # Since we are using numpy just calling mean on numpy is sufficient
    mean_x, mean_y = np.mean(x), np.mean(y)

    # calculating cross-deviation and deviation about x
    SS_xy = np.sum(y*x - n*mean_y*mean_x)
    SS_xx = np.sum(x*x - n*mean_x*mean_x)

    # calculating regression coefficients
    b_1 = SS_xy / SS_xx
    b_0 = mean_y - b_1*mean_x

    return(b_0, b_1)

    # x,y are the location of points on graph
    # color of the points change it to red blue orange play around

def plot_regression_line(x, y, b):
    # plotting the points as per dataset on a graph
    plt.scatter(x, y, color = "m",marker = "o", s = 30)

    # predicted response vector
    y_pred = b[0] + b[1]*x

    # plotting the regression line
    plt.plot(x, y_pred, color = "g")

    # putting labels for x and y axis
    plt.xlabel('Size')
    plt.ylabel('Cost')

    # function to show plotted graph
    plt.show()

def main():
    # Datasets which we create
    x = np.array([ 1,   2,   3,   4,   5,   6,   7,   8,    9,   10])
    y = np.array([300, 350, 500, 700, 800, 850, 900, 900, 1000, 1200])

    # estimating coefficients
    b = estimate_coefficients(x, y)
    print("Estimated coefficients:\nb_0 = {} \nb_1 = {}".format(b[0], b[1]))

    # plotting regression line
    plot_regression_line(x, y, b)

if __name__ == "__main__":
    main()


# The population of salmon next year is given by
# f(S) = Se^r(1 - S / P ),
# where:
# S is this years' salmon population,
# P is the equilibrium population,
# r is a constant that depends upon how fast the population grows.

# Find f'(S0) and solve the equation f'( S0 ) = 1.
# Graph f'(S0) and y = 1.

# Find the population for which the maximum sustainable harvest occurs if r = 0.4 and P = 850

import math
from sympy import solve, lambdify, symbols, diff, pprint, pretty, simplify, exp
import matplotlib.pyplot as plt
import numpy as np

S = symbols( 'S' )
r, P = 0.4, 850

F = S * exp( r *( 1 - S / P ) )

dF = diff( F, S )

# intersection = round( solve( dF - 1, S )[ 0 ], 3 )

# Graph

domain_end = 20

g_xlim = [ 0, 500 ]
g_ylim = [-5, 10 ]

lam_x = lambdify( S, dF, np )

x_vals = np.linspace( g_xlim[0], g_xlim[1], 1000, endpoint=True )
y_vals = lam_x( x_vals )

plt.plot( x_vals, y_vals, label = '' )
plt.hlines( y = 1, xmin = g_xlim[ 0 ], xmax = g_xlim[ 1 ], color = 'Orange', zorder = 1 )
plt.scatter( intersection, 1, color = 'R' )
plt.show()


import numpy as numpy
from  scipy.special import factorial

def fdcoeffV(k,xbar,x):
    """
    fdcoeffV routine modified from Leveque (2007) matlab function

    Params:
    -------

    k: int
        order of derivative
    xbar: float
        point at which derivative is to be evaluated
    x: ndarray
        numpy array of coordinates to use in calculating the weights

    Returns:
    --------
    c: ndarray
        array of floats of coefficients.

    Compute coefficients for finite difference approximation for the
    derivative of order k at xbar based on grid values at points in x.

    WARNING: This approach is numerically unstable for large values of n since
    the Vandermonde matrix is poorly conditioned.  Use fdcoeffF.m instead,
    which is based on Fornberg's method.

     This function returns a row vector c of dimension 1 by n, where n=length(x),
     containing coefficients to approximate u^{(k)}(xbar),
     the k'th derivative of u evaluated at xbar,  based on n values
     of u at x(1), x(2), ... x(n).

     If U is an array containing u(x) at these n points, then
     c.dot(U) will give the approximation to u^{(k)}(xbar).

     Note for k=0 this can be used to evaluate the interpolating polynomial
     itself.

    Requires len(x) > k.
    Usually the elements x(i) are monotonically increasing
    and x(1) <= xbar <= x(n), but neither condition is required.
    The x values need not be equally spaced but must be distinct.

    Modified rom  http://www.amath.washington.edu/~rjl/fdmbook/  (2007)
    """

    n = x.shape[0]
    assert  k < n, " The order of the derivative must be less than the stencil width"

    # Generate the Vandermonde matrix from the Taylor series
    A = numpy.ones((n,n))
    xrow = (x - xbar)  # displacements x-xbar
    for i in range(1,n):
        A[i,:] = (xrow**(i))/factorial(i);

    b = numpy.zeros(n)    # b is right hand side,
    b[k] = 1              # so k'th derivative term remains

    c = numpy.linalg.solve(A,b)          # solve n by n system for coefficients

    return c


import random

import numpy as np

def poisson_disk(r, width, height, k=30):
    """
    Poisson-disc sampling according to https://www.cs.ubc.ca/~rbridson/docs/bridson-siggraph07-poissondisk.pdf and
    similar to the JavaScript implementation from https://www.jasondavies.com/poisson-disc/.

    :param r: points are generated in the radius interval [r, 2r] from their neighbors (see paper)
    :param width: width of the domain (e.g. image)
    :param height: height of the domain (e.g. image)
    :param k: number of attempts to generate valid neighbors for each point (see paper)
    :return: numpy array of Poisson-disc sampled points
    """
    # Step 0: Initialization
    cell_size = r / np.sqrt(2)

    points = []
    grid = np.ones((np.ceil(height / cell_size).astype(np.int), np.ceil(width / cell_size).astype(np.int)), dtype=np.int) * -1
    active = []

    # Normalizing constant for annulus sampling
    r_sqr = r ** 2
    a = 3 * r_sqr

    def emit_sample(point):
        points.append(point)
        index = len(points) - 1
        int_point = (point // cell_size).astype(np.int)
        assert(grid[int_point[0], int_point[1]] == -1)
        grid[int_point[0], int_point[1]] = index
        active.append(index)

    def generate_around(point):
        theta = random.random() * 2 * np.pi
        radius = np.sqrt(random.random() * a + r_sqr)

        return point + np.array([radius * np.sin(theta), radius * np.cos(theta)])

    def check_extents(point):
        return 0 < point[0] < height and 0 < point[1] < width

    def check_neighborhood(point):
        iy, ix = (point // cell_size).astype(np.int)
        y0 = max(0, iy - 1)
        x0 = max(0, ix - 1)
        y1 = min(iy + 2, grid.shape[0])
        x1 = min(ix + 2, grid.shape[1])

        for ny in range(y0, y1):
            for nx in range(x0, x1):
                grid_index = grid[ny, nx]
                if grid_index == -1:
                    continue
                grid_point = points[grid_index]
                if np.square(grid_point - point).sum() < r_sqr:
                    return False

        return True

    def check_valid(point):
        return check_extents(point) and check_neighborhood(point)

    # Step 1: Initial sample
    emit_sample(np.random.random_sample((2,)) * np.array([height, width]))

    # Step 2: Sampling
    while len(active) > 0:
        i = active[random.randint(0, len(active) - 1)]

        candidate_found = False
        for j in range(k):
            new_point = generate_around(points[i])
            if check_valid(new_point):
                candidate_found = True
                emit_sample(new_point)
                break

        if not candidate_found:
            active.remove(i)

    return np.array(points)