import numpy as np
from itertools import combinations

def matrix_minor(A, r_indxs, c_indxs):
    '''This function returns the minor of a matrix A with rows indexed by r_indxs and
    columns by c_indxs. r_indxs and c_indxs are lists (or 1D numpy arrays), and these indexes
    start from 0 (which is the first row/column index).'''
    return np.linalg.det(A[np.ix_(r_indxs, c_indxs)]) if len(r_indxs)==len(c_indxs) else None

def compute_MC_matrix( A, p ):
    '''This function computes the p'th order multiplicative
    compound matrix of the given matrix A. It returns the MC
    matrix and the lexicography order (with 0 as the first index)'''
    x = np.arange(np.minimum(*A.shape), dtype=int)  # 0, .., n-1, where n:=min(matrix dimensions)
    lp = np.array(list(combinations(x, p))) # lexicography order of the p inxedes in x
    lp_len = len(lp)
    Q = np.array([matrix_minor(A, lp[r], lp[c]) for r in range(lp_len) for c in range(lp_len)]).reshape(lp_len, lp_len)
    return Q, lp


import sympy as sp
import numpy as np

if __name__ == '__main__':
    x = sp.Symbol('x')
    theta, phi = sp.symbols("theta phi")

    # Let's say we are given a symbolic function f and we want to know his primitive
    f = sp.cos(x + theta) + sp.sin(x + phi)
    print(f"f = {f}")

    # To obtain his primitive we can use the integrate method of sympy. It will compute a symbolic integral
    # with mathematical identities.
    F = sp.integrate(f, x)
    print(f"F = {F}")

    # We also able to obtain his evalution in x for 0 to pi.
    F_0_to_pi = sp.integrate(f, (x, 0, np.pi))
    print(f"F_0_to_pi = {F_0_to_pi}")

    # And we can eval it for theta = 0 and phi = pi/4
    F_0_to_pi_evalf = F_0_to_pi.evalf(subs={theta: 0, phi: np.pi/4})
    print(f"F_0_to_pi_evalf = {F_0_to_pi_evalf}")

    # Another way to find the primitive of f is to create an Integral object
    F = sp.Integral(f, x)
    print(f"F = {F}")

    # After we can find the primitive
    F = F.doit()
    print(f"F = {F}")

    # Let's say we are given another function with two independant variables
    y = sp.Symbol('y')
    g = sp.sin(x + theta)*sp.cos(y + phi) + sp.tan(x + theta) + sp.acos(y + phi)
    G = sp.integrate(g, x, y)
    print(f"G = {G}")
    # We are able to do a multiple definite integral
    G_evalf = sp.integrate(g, (x, 0, np.pi), (y, 0, 2*np.pi)).evalf(subs={theta: np.pi/2, phi: np.pi/6})
    print(f"G_evalf = {G_evalf}")


# Demonstrate criterion of Chauvenet to exclude poor data
from scipy.special import erfc

import numpy as np
import pandas as pd

def chauvenet(y, mean=None, stdv=None) -> np.Array:
    """
    #-----------------------------------------------------------
    # Input:  NumPy arrays x, y that represent measured data
    #         A single value of a mean can be entered or a
    #         sequence of means with the same length as
    #         the arrays x and y. In the latter case, the
    #         mean could be a model with best-fit parameters.
    # Output: It returns a boolean array as filter.
    #         The False values correspond to the array elements
    #         that should be excluded
    #
    # First standardize the distances to the mean value
    # d = abs(y-mean)/stdv so that this distance is in terms
    # of the standard deviation.
    # Then the  CDF of the normal distr. is given by
    # phi = 1/2+1/2*erf(d/sqrt(2))
    # Note that we want the CDF from -inf to -d and from d to +inf.
    # Note also erf(-d) = -erf(d).
    # Then the threshold probability = 1-erf(d/sqrt(2))
    # Note, the complementary error function erfc(d) = 1-erf(d)
    # So the threshold probability pt = erfc(d/sqrt(2))
    # If d becomes bigger, this probability becomes smaller.
    # If this probability (to obtain a deviation from the mean)
    # becomes smaller than 1/(2N) than we reject the data point
    # as valid. In this function we return an array with booleans
    # to set the accepted values.
    #
    # use of filter:
    # xf = x[filter]; yf = y[filter]
    # xr = x[~filter]; yr = y[~filter]
    # xf, yf are cleaned versions of x and y and with the valid entries
    # xr, yr are the rejected values from array x and y
    #-----------------------------------------------------------
    source: https://www.astro.rug.nl/software/kapteyn/
    :param x:
    :param y:
    :param mean:
    :param stdv:
    :return:
    """
    if mean is None:
        mean = y.mean()           # Mean of incoming array y
    if stdv is None:
        stdv = y.std()            # Its standard deviation

    N = len(y)                   # Lenght of incoming arrays
    criterion = 1.0/(2*N)        # Chauvenet's criterion
    d = abs(y-mean)/stdv         # Distance of a value to mean in stdv's
    d /= 2.0**0.5                # The left and right tail threshold values

    prob = erfc(d)               # Area normal dist.
    mask = prob >= criterion   # The 'accept' filter array with booleans

    return mask                # Use boolean array outside this function

def reject_outliers(data: pd.Series) -> pd.Series:
    return data[chauvenet(data)].reset_index(drop=True)


# Assignment on normal distribution

import numpy as np
from scipy.stats import norm

'''
1.Find the area under the standard normal curve which lie
a)To the right of Z=2.70
b)To the left of Z=1.73
c)To the right of Z=-0.66
d)To the left of Z=-1.88
e)Between Z=-0.90 and Z=-1.85
f)Between Z=-1.45 and Z=1.45
g)Between Z=-0.90 and Z=1.58
'''
print("Assignment 1")

print("Area to the right of z=2.70:",1-norm.cdf(x=2.70))
print("Area to the left of z=1.73:",norm.cdf(x=1.73))
print("Area to the right of z=-0.66:",1-norm.cdf(x=-0.66))
print("Area to the left of z=-1.88:",norm.cdf(x=-1.88))
print("Area Between Z=-0.90 and Z=-1.85:",norm.cdf(x=-0.90)-norm.cdf(x=-1.85))
print("Area Between Z=-1.45 and Z=1.45:",norm.cdf(x=1.45)-norm.cdf(x=-1.45))
print("Area Between Z=-0.90 and Z=1.58:",norm.cdf(x=1.58)-norm.cdf(x=-0.90))

print("\n")

'''
2.The life of a certain kind of electronic device has a mean of 300 hours and a standard deviation of 25 hours. Assuming that the distribution of life times which are measured to the nearest hours can be approximated closely with a normal curve
a)Find the probability that any one of these devices will have a lifetime of more than 350 hours.
b)What percentage will have life time from 220 to 260 hours?
'''
print("Assignment 2")

print("Probability of lifetime More than 350 Hours:",
        1 - norm.cdf(x=350,loc=300,scale=25))
print("Percentage of lifetime from 220 to 260 Hours:",
        (norm.cdf(x=260,loc=300,scale=25)\
        -norm.cdf(x=220,loc=300,scale=25))*100)
print("\n")

'''
3.The customer accounts of a certain departmental store have an average balance of Rs.120 and standard deviation of Rs.40. Assuming that the account balances are normally distributed, find
a)What proportion of accounts is over Rs.150 ?
b)What proportion of accounts is between Rs.100 and Rs.150?
c)What proportion of accounts is between Rs.60 and Rs.90?
'''
print("Assignment 3")

print("Proportion of accounts is over Rs.150:",\
        1 - norm.cdf(x=150,loc=120,scale=40))
print("Proportion of accounts is between Rs.100 and Rs.150:",\
        norm.cdf(x=150,loc=120,scale=40) - norm.cdf(x=100,loc=120,scale=40))
print("Proportion of accounts is between Rs.60 and Rs.90:",\
        norm.cdf(x=90,loc=120,scale=40) - norm.cdf(x=60,loc=120,scale=40))

print("\n")


import numpy as np
import matplotlib.pyplot as plt
PI = np.pi

# =========================define sinc
# ---------------normalized

def sinc1(x):
    PI = np.pi
    x = np.array(x)
    y = np.where(np.abs(PI * x) < 1e-38, 1.0, np.sin(PI * x) / (PI * x))
    return y

def sinc_interpolation(x, t, T):
    ns = np.arange(x.size)
    print(ns, "============")

    y = []
    for tt in t:
        y.append(np.sum(x * sinc1((tt - ns * T) / T)))

    return np.array(y)

# =========================test sinc definition
f0 = 100

Ns = 2000
Tp = 20.0 / Ns
t = np.linspace(-10, 10, Ns)
t2 = np.linspace(-10, 10, Ns * 2)
y1 = sinc1(t / Tp)

x = np.sin(2 * PI * f0 * t)
print(x.shape)

y = sinc_interpolation(x, t2, Tp)

print(y.shape, "===")

yfft = np.fft.fftshift(np.fft.fft(y))

plt.figure()
plt.subplot(131)
plt.plot(t, x, '^b')
plt.plot(t2, y, '+r')
plt.legend(['original', 'sinc interpolated'])
plt.title('sinc(t/Tp), ' + "Tp=" + str(Tp))
plt.xlabel('Time/s')
plt.ylabel('Amplitude')
plt.grid()

plt.show()


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

hists = np.zeros((3,7))

for row, fn in enumerate(['train_rev.json', 'dev_rev.json', 'test.json']):
  scores = []
  with open(fn) as f:
    items = json.load(f)
    for i in items:
      score = int(math.floor(i['score'] + 0.5))
      scores.append(score)
  hists[row] = np.bincount(scores)[1:]

print hists

# Original:
#array([[ 202.,  328.,  204.,   37.,   20.,   13.,    2.],
#       [  43.,   59.,   35.,    6.,    9.,    4.,    6.],
#       [  59.,   75.,   44.,    7.,    5.,    1.,    1.]])
# Revised:
#array([[ 199.,  309.,  177.,   15.,    9.,    4.,    6.],
#       [  46.,   78.,   62.,   28.,   20.,   13.,    2.],
#       [  59.,   75.,   44.,    7.,    5.,    1.,    1.]])

num_per_set = np.sum(hists, axis=1)
print num_per_set
print np.round(num_per_set / np.sum(num_per_set),2)

# Original:
# [ 806.  162.  192.]
# [ 0.69,  0.14,  0.17]
# Revised:
# [ 719.  249.  192.]
# [ 0.62,  0.21,  0.17]

normed_hist = hists / num_per_set[:,np.newaxis]
print np.round(normed_hist,3)

# Original:
#[[ 0.251  0.407  0.253  0.046  0.025  0.016  0.002]
# [ 0.265  0.364  0.216  0.037  0.056  0.025  0.037]
# [ 0.307  0.391  0.229  0.036  0.026  0.005  0.005]]
# Revised:
#[[ 0.277  0.43   0.246  0.021  0.013  0.006  0.008]
# [ 0.185  0.313  0.249  0.112  0.08   0.052  0.008]
# [ 0.307  0.391  0.229  0.036  0.026  0.005  0.005]]

labels = ["Train (n=719)", "Dev (n=249)", "Test (n=192)"]

fig = plt.figure()
fig.patch.set_facecolor('white')
handles = []
for i,row in enumerate(normed_hist):
  plt.plot(range(1,8), row, "-D", label=labels[i])

plt.xlabel("Integrative Complexity Score")
plt.ylabel("Proportion of Data")
plt.title("Distribution of Complexity Scores")
plt.legend()
plt.show()


import cv2
import numpy as np
from matplotlib import pyplot as plt
import Image

# Reading color Image and converting it to a Grayscale Image
colorImg = cv2.imread("color.jpg")
grayImg = cv2.cvtColor(colorImg, cv2.COLOR_BGR2GRAY)
cv2.imwrite('gray.jpg',grayImg)

# Reading the converted Grayscale Image
grayImg = cv2.imread('gray.jpg')
origImg = Image.open('gray.jpg')
pix = origImg.load()

# Step 1 : Initializing N, M, G and H
N = origImg.size[0]
M = origImg.size[1]
G = 256
hist = [0] * G

# Step 2 : Calculate the Histogram (H) values
for x in range(0, N):
    for y in range(0, M):
        hist[pix[x,y]] = hist[pix[x,y]] + 1

# Step 3 : Generating Cumulative Histogram (Hc) from the Histogram (H)
cumHist = [0] * G
cumHist[0] = hist[0]
for p in range(1, G):
    cumHist[p] = cumHist[p-1] + hist[p]

# Step 4 : Generating Lookup Table (Tp) as per the forumula in Algorithm 5.1
Tp = [0] * G
for p in range(0, G):
    Tp[p] = np.rint(((G-1)*cumHist[p])/(N*M))

# Step 5 : Rescanning the Original Image and generating Output Image from Lookup Table (Tp)
equMatrix = np.zeros((M,N))
for x in range(0, N):
    for y in range(0, M):
       equMatrix[y,x] = Tp[pix[x,y]]

equImg = Image.fromarray(equMatrix)
cv2.imwrite("equImg.jpg", equMatrix)

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

# Printing results

# Generating Cumulative Histogram values for Transformation Function (Tp)
cumHistTp = [0] * G
cumHistTp[0] = Tp[0]
for p in range(1, G):
    cumHistTp[p] = cumHistTp[p-1] + Tp[p]

plt.subplot(331),plt.imshow(colorImg),plt.title('Color Image')
plt.xticks([]), plt.yticks([])

plt.subplot(332),plt.imshow(grayImg),plt.title('Grayscale Image')
plt.xticks([]), plt.yticks([])

plt.subplot(333),plt.imshow(equImg),plt.title('Equalized Image')
plt.xticks([]), plt.yticks([])

plt.subplot(334),plt.hist(grayImg.ravel(),256,[0,256])
plt.title('Original Histogram')

plt.subplot(335),plt.plot(cumHist)
plt.title('Cumulative Histogram')

plt.subplot(336),plt.hist(equMatrix.ravel(),256,[0,256])
plt.title('Equalized Histogram')

plt.subplot(337),plt.plot(Tp)
plt.title('Transformation Function')

plt.show()


import sys
import math
import numpy as np
import scipy as sp
from scipy import linalg

A = np.array([
    [-2,1,0,1,0,0,0,0,0],
    [1,-3,1,0,1,0,0,0,0],
    [0,1,-2,0,0,1,0,0,0],
    [1,0,0,-3,1,0,1,0,0],
    [0,1,0,1,-4,1,0,1,0],
    [0,0,1,0,1,-3,0,0,1],
    [0,0,0,1,0,0,-2,1,0],
    [0,0,0,0,1,0,1,-3,1],
    [0,0,0,0,0,1,0,1,-2]])
b = np.array([1, 1, 1, 1, 0, -1, -1, -1, -1])

# Provide some information: not necessary to the method
print("# matrix:")
for r in A:
    print("   ", r)
print("  eigenvalues:", ", ".join(["{:.6g}".format(e) for e in np.linalg.eigvals(A)]))
rankA = np.linalg.matrix_rank(A)
print("  rank:", rankA)

# Verify that problem is well-posed when nullspace has dimension 1
if rankA == len(b) - 1:
    kerA = [a[0] for a in sp.linalg.null_space(A)]
    print("  nullspace: <", kerA, '>')
    print("  kerA . b:", kerA @ b)

##### Actual solver starts here #####

# Initialize containers and values
print("# intialization:")
x = np.array([0., 0., 0., 0., 0., 0., 0., 0., 0.])
print("  initial guess:", x)

residual = b - A @ x
rms2 = residual @ residual
print("  RMS error:", math.sqrt(rms2))

direction = residual
print("  new direction:", direction)

# Iterate
for k in range(min(A.shape)):
    kp1 = k + 1
    print("# iteration:", kp1)

    # Compute step
    alpha = rms2 / (direction @ A @ direction)
    print("  alpha[{}]: {}".format(k, alpha))

    # Update solution
    x += alpha * direction
    print("  updated solution:", x)

    # Update residual
    residual = b - A @ x
    new_rms2 = residual @ residual
    print("  RMS error:", math.sqrt(new_rms2))

    # Terminate early when possible
    if new_rms2 < 1e-6:
        print("# CG converged to solution: {} with residual norm: {}".format(
            x, math.sqrt(new_rms2)))
        sys.exit(0)

    # Compute new direction
    beta = new_rms2 / rms2
    print("  beta[{}]: {}".format(k, beta))
    direction = residual - beta * direction
    print("  new direction:", direction)

    # Update residual squared L2
    rms2 = new_rms2


from math import exp, cos

from numpy import polynomial, ndarray

def f(_x: float) -> float:
    return exp(-_x ** 2) * cos(_x)

def fGauss(a: float, b: float, n: int) -> float:
    result = 0
    for i in range(n):
        x = ((b + a) + (b - a) * t[i]) / 2
        result += + A[i] * f(x)
    return (b - a) / 2 * result

def fSimpson(a: float, b: float, z: int) -> float:
    # formula Cotes
    h = (b - a) / z
    _n = int(z / 2)
    result = 0
    for i in range(_n):
        x = h * (2 * i) + a
        result += 2 * f(x)
    for j in range(_n):
        x = h * (2 * j + 1) + a
        result += 4 * f(x)
    result += f(a) + f(b)
    return result * h / 3

if __name__ == '__main__':
    # iteration counter C
    C = 0
    n = 6
    # t - Legendre points, A - Legendre weights
    t: ndarray = polynomial.legendre.leggauss(n)[0]
    A: ndarray = polynomial.legendre.leggauss(n)[1]
    print(f"Функция: f(x)=exp(-x^2)*cos(x), a={-float('inf')},b={float('inf')}")
    print(f"Результат полученный методом Гаусса:"
          f" {round(fGauss(-1.97, 1.97, n), n)}")
    # Simpson variables
    eps = 0.001
    a, b = -10, 10
    n = 2
    R1, R2 = 0, fSimpson(a, b, n)
    while (abs(R1 - R2) / R2) > eps:
        n += 2
        R1, R2 = R2, fSimpson(a, b, n)
        C += 1
    print(f"Результат полученный методом Симпсона:"
          f" {round(R2, 6)}, кол-во интервалов: {C + 1}")


def lorenz_system_solver(h, N, s=10.0, r=28.0, b=8.0/3.0):
    # Need one more for the initial values
    empty_vector  = lambda size   : zeros((size + 1,), dtype=float)
    lorenz_system = lambda x, y, z: (s*(y - x), x*(r -z) - y , x*y - b*z)
    next          = lambda p_old, slope   : p_old + (slope * h) # euler's Method
    x, y, z       = empty_vector(N), empty_vector(N), empty_vector(N)

    x[0], y[0], z[0] = -14.0, -15.0, 20.0
    for i in range(1, N+1):
        dx  , dy  , dz   = lorenz_system(x[i-1], y[i-1], z[i-1])
        x[i], y[i], z[i] = next(x[i-1], dx), next(y[i-1], dy), next(z[i-1], dz)
    return x, y, z

def plot_lorenz_system_solution(x, y, z):
    ax = plt.figure().gca(projection='3d')
    ax.plot(x, y, z, 'co',lw=2.1)
    ax.plot(x, y, z, 'm--',lw=.5)
    ax.set_xlabel("X")
    ax.set_ylabel("Y")
    ax.set_zlabel("Z")
    ax.set_title("Lorenz System Solution")
    plt.show()

if __name__ == "__main__":
    from numpy import zeros
    import matplotlib.pyplot as plt
    from mpl_toolkits.mplot3d import Axes3D
    x, y, z = lorenz_system_solver(0.01, 10000)
    plot_lorenz_system_solution(x, y, z)


import numpy as np

class Kmeans():
    '''
        Clusters data using the K-means algorithm.
    '''
    def __init__(self, n_clusters=3, max_iterations=2000, init=None):
        self.n_clusters = n_clusters;
        self.max_iterations = max_iterations;
        self.init = None;

    def _init_centroids(self, X):
        '''
        We have two ways to initialize the initial centroids.
        Default initialization: Chooses the centroids by selecting K random points.
        kmeans++ initialization: Chooses the centroids so that they are spread out as much as possible.
        :param X: Data to cluster
        :return: Inicial centroids
        '''
        m, n = X.shape;
        if (self.init is None):
            rand_ind = np.random.choice(m, self.n_clusters, replace=False);
            return X[rand_ind];
        elif (self.init == 'Kmeans++'):
            centroids = np.full((self.n_clusters, n), np.inf);
            rand_ind = np.random.choice(m);
            centroids[0] = X[rand_ind];
            for i in range(1, self.n_clusters):
                dists = np.array([np.linalg.norm(x - centroids[self._closest_centroid(x, centroids)]) for x in X]);
                norm = dists / np.sum(dists);
                rand_ind = np.random.choice(m, p=norm);
                centroids[i] = X[rand_ind];
            return centroids[i];

    #Given the clusters calculate the new centroids.
    def _calculate_centroids(self, X, clusters):
        return np.array([X[cluster].mean(axis=0) for cluster in clusters]);

    #Given the centroids calculate the new clusters.
    def _create_clusters(self, X, centroids):
        clusters = [[] for i in range(self.n_clusters)];

        for x_ind, x in enumerate(X):
            closest_centroid = self._closest_centroid(x, centroids);
            clusters[closest_centroid].append(x_ind);

        return clusters;

    #Finds the centroid that is closest to x.
    def _closest_centroid(self, x, centroids):
        return np.argmin(np.linalg.norm(x - centroids, axis=1))

    #Calculates the distance between each point in X with all the centroids.
    def transform(self, X):
        return np.array([np.linalg.norm(x - self.centroids_, axis=1) for x in X]);

    def fit(self, X):
        m = len(X);
        centroids = self._init_centroids(X);
        for i in range(self.max_iterations):
            prev_centroids = centroids;
            clusters = self._create_clusters(X, prev_centroids);
            centroids = self._calculate_centroids(X, clusters);
            if not (centroids - prev_centroids).any():
                break;

        self.centroids_ = centroids;
        clusters = self._create_clusters(X, centroids)
        self.labels_ = np.zeros(m, int);
        for cluster_ind, cluster in enumerate(clusters):
            for x_ind in cluster:
                self.labels_[x_ind] = cluster_ind;

        dists = self.transform(X);

        #The inertia is a way to measure the performance of a clustering algorithm.
        #It is the squared sum of the distances of each point to its corresponding cluster.
        self.inertia_ = np.sum(dists[range(m), self.labels_] ** 2)


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

# code starts here
df = pd.read_csv(path)
total = len(df)
print('Total is ', total)
p_a = len(df[df['fico'] > 700]) / total
print('Probability p(A) for the event that fico is ', p_a)
p_b = len(df[df['purpose'] == 'debt_consolidation']) / total
print('Probabilityp(B) for the event that purpose is ', p_b)
df1 = df[df['purpose'] == 'debt_consolidation'].copy()
total_ab = len(df1)
print('Length of new dataframe is ', total_ab)
p_a_b = len(df1[df1['fico'] > 700]) / total_ab
print('Probablity p(B|A) for the event purpose is ', p_a_b)
result = p_a_b == p_a
print(result)
# code ends here

# --------------
# code starts here
#print(df)
prob_lp = len(df[df['paid.back.loan'] == 'Yes']) / len(df)
print('Probability p(A) for the event that paid.back.loan is ', prob_lp)
prob_cs = len(df[df['credit.policy'] == 'Yes']) /len(df)
print('Probability p(B) for the event that credit.policy is ', prob_cs)
new_df = df[df['paid.back.loan'] == 'Yes']
prob_pd_cs = len(new_df[new_df['credit.policy'] == 'Yes']) / len(new_df)
print('Probablity p(B|A) for the event paid.back.loan is ', prob_pd_cs)
bayes = prob_pd_cs * prob_lp / prob_cs
print('Conditional probability of the event P(A|B) is ',bayes)
# code ends here

# --------------
# code starts here
#print(df)
#value_pur = df['purpose'].value_counts()
value_pur = df.groupby(['purpose']).size()
value_pur.plot(kind='bar')
plt.show()

df1 = df[df['paid.back.loan'] == 'No'].copy()
new_value_pur = df1.groupby(['purpose']).size()
new_value_pur.plot(kind='bar')
plt.show()
# code ends here

# --------------
# code starts here
inst_median = df['installment'].median()
print('Median for installment is ', inst_median)
inst_mean = df['installment'].mean()
print('Mean for installment is ', inst_mean)
plt.hist(df['installment'], normed=True, bins=30)
plt.axvline(inst_median, color='k', linestyle='dashed', linewidth=1)
plt.axvline(inst_mean, color='g', linestyle='dashed', linewidth=1)
plt.show()

incm_median = df['log.annual.inc'].median()
print('Median for log.annual.inc is ', incm_median)
incm_mean = df['log.annual.inc'].mean()
print('Mean for log.annual.inc is ', incm_mean)
plt.hist(df['log.annual.inc'], normed=True, bins=30)
plt.axvline(incm_median, color='k', linestyle='dashed', linewidth=1)
plt.axvline(incm_mean, color='g', linestyle='dashed', linewidth=1)
plt.show()
# code ends here


import numpy as np
from numpy.linalg import inv

def plu(A):
    n = A.shape[0]

    P = np.eye(n, dtype=float)
    L = np.eye(n, dtype=float)
    U = A.copy()

    for i in range(n):
        # pivoting, row swapping, change P if needed
        max_el = abs(U[i, i])
        max_row = i
        for k in range(i + 1, n):
            pivot = abs(U[i, i])
            if pivot > max_el:
                max_el = pivot
                max_row = k

        if max_row != i:
            U[[max_row, i]] = U[[i, max_row]]
            P[[max_row, i]] = P[[i, max_row]]

        # forward loop, fill L and U
        factor = U[i + 1:, i] / U[i, i]
        L[i + 1:, i] = factor
        U[i + 1:] -= factor[:, np.newaxis] * U[i]

    return P, L, U

def forward_substitution(L, b):
    n = L.shape[0]

    y = np.zeros(n, dtype=np.float)
    for i in range(n):
        y[i] = b[i] - np.dot(L[i, :i], y[:i])

    return y

def back_substitution(U, y):
    n = U.shape[0]

    x = np.zeros(n)
    for i in range(n - 1, -1, -1):
        x[i] = (1. / U[i, i]) * (y[i] - np.dot(U[i, i:], x[i:]))

    return x

def inverse(A: np.ndarray) -> np.ndarray:
    """
    Computes the inverse of the A matrix, using the LU decomposition and
    solving linear equations to solve AX = B, where X is the inverse of A and
    B is an identity matrix.

    :param A: square matrix of shape (n, n)
    :return: inverse of X, it it is not singular (ValueError then)
    """
    A = A.astype(float)

    P, L, U = plu(A)
    n = A.shape[0]

    if (np.diagonal(U) == 0).any():
        raise ValueError("A is a singular value")

    A_inv = np.zeros(A.shape, dtype=float)

    for i in range(n):
        b = np.zeros(n)
        b[i] = 1

        y = forward_substitution(L, P @ b)
        x = back_substitution(U, y)

        A_inv[:, i] = x

    return A_inv

if __name__ == '__main__':
    A = np.array([[2, 2, 3],
                  [4, 5, 6],
                  [7, 8, 9]])

    print(inverse(A))


import sympy as sym

class TaylorSeries:
    """Class for symbolic Taylor series."""
    def __init__(self, f, num_terms=4):
        self.f = f
        self.N = num_terms
        # Introduce symbols for the derivatives
        self.df = [f]
        for i in range(1, self.N+1):
            self.df.append(sym.Symbol('D%d%s' % (i, f.name)))

    def __call__(self, h):
        """Return the truncated Taylor series at x+h."""
        terms = self.f
        for i in range(1, self.N+1):
            terms += sym.Rational(1, sym.factorial(i))*self.df[i]*h**i
        return terms

class DiffOp:
    """Class for discrete difference operators."""
    def __init__(self, f, independent_variable='x',
                 num_terms_Taylor_series=4):
        self.Taylor = TaylorSeries(f, num_terms_Taylor_series)
        self.f = self.Taylor.f
        self.h = sym.Symbol('d%s' % independent_variable)

        # Finite difference operators
        h, f, f_T = self.h, self.f, self.Taylor # short names
        theta = sym.Symbol('theta')
        self.diffops = {
            'Dtp':  (f_T(h) - f)/h,
            'Dtm':  (f - f_T(-h))/h,
            'Dt':   (f_T(h/2) - f_T(-h/2))/h,
            'D2t':  (f_T(h) - f_T(-h))/(2*h),
            'DtDt': (f_T(h) - 2*f + f_T(-h))/h**2,
            'barDt': (f_T((1-theta)*h) - f_T(-theta*h))/h,
            }
        self.diffops = {diffop: sym.simplify(self.diffops[diffop])
                        for diffop in self.diffops}

        self.diffops['weighted_arithmetic_mean'] = \
             self._weighted_arithmetic_mean()
        self.diffops['geometric_mean'] = self._geometric_mean()
        self.diffops['harmonic_mean'] = self._harmonic_mean()

    def _weighted_arithmetic_mean(self):
        # The expansion is around n*h + theta*h
        h, f, f_T = self.h, self.f, self.Taylor
        theta = sym.Symbol('theta')
        f_n = f_T(-h*theta)
        f_np1 = f_T((1-theta)*h)
        a_mean = theta*f_np1 + (1-theta)*f_n
        return sym.expand(a_mean)

    def _geometric_mean(self):
        h, f, f_T = self.h, self.f, self.Taylor
        f_nmhalf = f_T(-h/2)
        f_nphalf = f_T(h/2)
        g_mean = f_nmhalf*f_nphalf
        return sym.expand(g_mean)

    def _harmonic_mean(self):
        h, f, f_T = self.h, self.f, self.Taylor
        f_nmhalf = f_T(-h/2)
        f_nphalf = f_T(h/2)
        h_mean = 2/(1/f_nmhalf + 1/f_nphalf)
        return sym.expand(h_mean)

    def D(self, i):
        """Return the symbol for the i-th derivative."""
        return self.Taylor.df[i]

    def __getitem__(self, operator_name):
        return self.diffops.get(operator_name, None)

    def operator_names(self):
        """Return all names for the operators."""
        return list(self.diffops.keys())

def truncation_errors():
    # Make a table
    u, theta = sym.symbols('u theta')
    diffop = DiffOp(u, independent_variable='t',
                    num_terms_Taylor_series=5)
    D1u = diffop.D(1)   # symbol for du/dt
    D2u = diffop.D(2)   # symbol for d^2u/dt^2
    print('R Dt:', diffop['Dt'] - D1u)
    print('R Dtm:', diffop['Dtm'] - D1u)
    print('R Dtp:', diffop['Dtp'] - D1u)
    print('R barDt:', diffop['barDt'] - D1u)
    print('R DtDt:', diffop['DtDt'] - D2u)
    print('R weighted arithmetic mean:', diffop['weighted_arithmetic_mean'] - u)
    print('R arithmetic mean:', diffop['weighted_arithmetic_mean'].subs(theta, sym.Rational(1,2)) - u)
    print('R geometric mean:', diffop['geometric_mean'] - u)
    dt = diffop.h
    print('R harmonic mean:', (diffop['harmonic_mean'] - u).\
          series(dt, 0, 3).as_leading_term(dt))

if __name__ == '__main__':
    truncation_errors()


import functional as F
import math
import copy
from sympy import Symbol, pprint, solve

def shape(matrix):
    if not F.is_sequence(matrix): return None

    shapes = []
    acc_matrix = matrix

    while True:
        matrix_length = len(acc_matrix)
        shapes.append(matrix_length)

        if not F.is_sequence(acc_matrix[0]):
            return shapes
        acc_matrix = acc_matrix[0]

def mat_mul(X, Y):
    shape_X, shape_Y = shape(X), shape(Y)

    if shape_X[1] != shape_Y[0]:
        error_message = "Shape of X [1] and B [0] must be same. X: {}, Y: {}".format(shape_X, shape_Y)
        raise ValueError(error_message)

    return [[sum(a*b for a, b in zip(X_row,Y_col)) for Y_col in zip(*Y)] for X_row in X]

def mat_add(X, Y):
    shape_X, shape_Y = shape(X), shape(Y)

    if shape_X != shape_Y:
        error_message = "Shape of X and B must be same. X: {}, Y: {}".format(shape_X, shape_Y)
        raise ValueError(error_message)

    return [x + y for x, y in zip(X, Y)] if len(shape_X) == 1 \
        else [mat_add(x, y) for x, y in zip(X, Y)]

def sca_mul(a, X):
    shape_X = shape(X)

    return [a * x for x in X] if len(shape_X) == 1 \
        else [sca_mul(a, x) for x in X]

def linear_combination(V, basis):
    len_V = shape(V)[0]
    shape_basis = shape(basis)

    if F.any(shape_basis, lambda b: b != len_V):
        error_message = "Length of V and basis must be same.";
        raise ValueError(error_message)

    S = [Symbol('S{}'.format(i)) for i in range(len_V)]
    expr = [F.reduce([S[j] * basis[i][j] for j in range(len_V)], lambda x,y: x+y, -V[i]) for i in range(len_V)]
    solution = solve(expr, dict=True)

    if len(solution) == 0:
        error_message = "There is linear independent basis.";
        raise ValueError(error_message)

    s = solution[0]
    return [s[S[i]] for i in range(len_V)]

def is_linear_independent(basis):
    shape_basis = shape(basis)
    V = F.reduce(basis, lambda a,b: mat_add(a,b), [0 for _ in range(0,shape_basis[0])])

    try:
        return len(linear_combination(V, basis)) > 0
    except ValueError:
        return False

def determinant(A, x = -1):
    shape_A = shape(A)
    len_A = shape_A[0]

    if len_A != shape_A[1]:
        error_message = "Matrix A must be square matrix : {}".format(shape_A)
        raise ValueError(error_message)

    if len_A == 1:
        return A[0][0]

    if x == -1:
        result = 0
        sign = -1
        for i in range(len_A):
            sign *= -1
            result += determinant(A, i) * sign
        return result
    else:
        return A[0][x] * determinant(adj_sub_matrix(A, x))

def adj_sub_matrix(A, x, y = 0):
    shape_A = shape(A)
    len_A = shape_A[0]

    if len_A != shape_A[1]:
        error_message = "Matrix A must be square matrix : {}".format(shape_A)
        raise ValueError(error_message)

    return [[value for i, value in enumerate(row) if i != x] for j, row in enumerate(A) if j != y]

def mat_transpose(A):
    shape_A = shape(A)

    t_A = []
    for i in range(shape_A[1]):
        row = []
        for j in range(shape_A[0]):
            row.append(A[j][i])
        t_A.append(row)
    return t_A

def mat_inverse(A):
    shape_A = shape(A)
    len_A = shape_A[0]

    if len_A != shape_A[1]:
        error_message = "Matrix A must be square matrix : {}".format(shape_A)
        raise ValueError(error_message)

    det_A = determinant(A)
    adj_A = [[determinant(adj_sub_matrix(A, j, i)) for j in range(len_A)] for i in range(len_A)]
    t_adj_A = mat_transpose(adj_A)

    sign = -1 / det_A
    for i in range(len_A):
        for j in range(len_A):
            sign *= -1
            t_adj_A[i][j] *= sign

    return t_adj_A

def validate_same_length_two_vector(A, B):
    shapes = [shape(A), shape(B)]

    if F.any(shapes, lambda s: len(s) > 1) or shapes[0][0] != shapes[1][0]:
        error_message = "A and B must be vector and same length : {}".format(shapes)
        raise ValueError(error_message)

    return shapes[0][0]

def dot_product(A, B):
    validate_same_length_two_vector(A, B)

    return sum([a * b for a, b in zip(A, B)])

def cross_product(A, B):
    # 일반식이 복잡함. 2,3차만 따로 구현
    shapes = validate_same_length_two_vector(A, B)

    if shapes == 2:
        return determinant([[a, b] for a, b in zip(A, B)])
    elif shapes == 3:
        M = [[0, a, b] for a, b in zip(A, B)]
        return [determinant(adj_sub_matrix(M, 0, y)) for y in range(shapes)]
    else:
        raise NotImplementedError("cross_product is enable for 2D and 3D vectors.")

if __name__ == '__main__':
    pass


from numpy import array, sqrt
from benchmarks.benchmark import Benchmark

class Bukin6(Benchmark):
    """dim: 2"""
    def __init__(self, lower=(-15, -3), upper=(-5, 3), dimension=2):
        super(Bukin6, self).__init__(lower, upper, dimension)

    def get_optimum(self):
        return array([[-10, 1]]), 0.0

    @staticmethod
    def eval(sol):
        x1 = sol[0]
        x2 = sol[1]
        term1 = 100 * sqrt(abs(x2 - 0.01 * x1 ** 2))
        term2 = 0.01 * abs(x1 + 10)

        return term1 + term2

if __name__ == '__main__':
    bukin6 = Bukin6(lower=(-15, -3), upper=(-5, 3), dimension=2)
    print(bukin6.get_optimum())
    bukin6.plot(scale=0.32)


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

n=1000
x_list=np.random.rand(n)
y_list=np.random.rand(n)

count=0
for x,y in zip(x_list, y_list):
    if x**2.+y**2.<1:
        count=count+1

pi=4.*float(count)/float(n)

print('pi='+str(pi))

theta=np.arange(0,7,0.001)

plt.figure(figsize=(5,5),dpi=96)
plt.scatter(x_list,y_list,alpha=0.2)
plt.plot(np.cos(theta),np.sin(theta))
plt.xlim(0,1.2)
plt.ylim(0,1.2)
plt.show()


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

# Let's create a function to model and create data
def func(x, a0, b0, c0, a1, b1, c1):
    return a0 * np.exp(-(x - b0) ** 2 / (2 * c0 ** 2))\
    + a1 * np.exp(-(x - b1) ** 2 / (2 * c1 ** 2))

# Generating clean data
x = np.linspace(0, 20, 200)
y = func(x, 1, 3, 1, -2, 15, 0.5)
# y2 = func(x[np.where(x > 10)], 0, 1, 1, -2, 15,0.5)

# Adding noise to the data
yn = y + 0.2 * np.random.normal(size=len(x))

# Plot out the current state of the data and model
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(x, y, c='k', label='Function')
ax.scatter(x, yn)

# Executing curve_fit on noisy data
popt, pcov = curve_fit(func, x, yn, p0=[1, 3, 1, 1, 15, 1])

#popt returns the best fit values for parameters of the given model (func)
print popt

ym = func(x, popt[0], popt[1], popt[2], popt[3], popt[4], popt[5])
ax.plot(x, ym, c='r', label='Best fit')
ax.legend()
fig.savefig('scipy_311_ex3.pdf', bbox_inches='tight')


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

square_size = 1
points_inside_square = 0
points_inside_circle = 0
sample_size = 3000

arc = np.linspace(0, np.pi/2, 100)
def generate_points(size):
    x = random.random()*size
    y = random.random()*size
    return (x, y)

def is_in_circle(point, size):
    return math.sqrt(point[0]**2+point[1]**2) <= size

def compute_pi(points_inside_circle, points_inside_square):
    return 4*points_inside_circle / points_inside_square

plt.axes().set_aspect('equal')
plt.plot(1*np.cos(arc), 1*np.sin(arc), 'r')

for i in range(sample_size):
    point = generate_points(square_size)
    plt.plot(point[0], point[1], 'c.')
    points_inside_square += 1
    if is_in_circle(point, square_size):
        points_inside_circle += 1
plt.show()
print("Approximate value of pi is {}" .format(compute_pi(points_inside_circle,points_inside_square)))


import numpy as np
import matplotlib.pyplot as plt
from numpy import linalg as la
from sklearn.linear_model import LinearRegression

X=2* np.random.rand(100,1)
y=4+3*X+np.random.rand(100,1)

Xb=np.c_[np.ones((100,1)),X]

theta=la.inv(Xb.T.dot(Xb)).dot(Xb.T).dot(y) #Normal Equation

print('Theta : \n',theta)

print('\nLets predict with this theta and input \n[[0]\n[2]]')

Xnew=np.array([[0],[2]])

Xnewb=np.c_[np.ones((2,1)),Xnew]

prediction=Xnewb.dot(theta)
print('\nPrediction : \n',prediction)

plt.figure(figsize=(12,8))
plt.plot(Xnew,prediction,'r-')
plt.plot(X,y,'b.')
plt.axis([0,2,0,15])
plt.show()

print(' \n Lets do the same with sklearn.linear_model.LinearRegression() \n')

lr=LinearRegression()
lr.fit(X,y)
print('\ntheta : \n',lr.intercept_,lr.coef_)

print('\nPrediction \n',lr.predict(Xnew))


import numpy as np
import statistics
import time

def hms2dec(h, m, s):
  return 15*(h + m/60 + s/3600)

def dms2dec(d, m, s):
  if (d >= 0):
    return d + m/60 + s/3600
  else:
    return d - m/60 - s/3600

def angular_dist(a1, d1, a2, d2):
  p1 = np.square(np.sin(np.absolute(d1-d2)/2))
  p2 = np.cos(d1)*np.cos(d2)*np.square(np.sin(np.absolute(a1-a2)/2))
  return 2*np.arcsin(np.sqrt(p1+p2))

def crossmatch(cat1, cat2, max_dist):
  matches, nomatches = [], []
  cat1id, cat2id = 0, 0

  start = time.perf_counter()

  # convert everything to radians
  cat1 = np.radians(cat1)
  cat2 = np.radians(cat2)
  max_dist = np.radians(max_dist)

  ra2s = cat2[:,0]
  dec2s = cat2[:,1]

  for cat1line in cat1:
    dists = angular_dist(cat1line[0], cat1line[1], ra2s, dec2s)
    min_dist = np.min(dists)
    if(min_dist <= max_dist):
      matches.append((cat1id,np.argmin(dists),min_dist))
    else:
      nomatches.append(cat1id)

    cat1id += 1

  return (matches, nomatches, time.perf_counter() - start)

# You can use this to test your function.
# Any code inside this `if` statement will be ignored by the automarker.
if __name__ == '__main__':
  # The example in the question
  ra1, dec1 = np.radians([180, 30])
  cat2 = [[180, 32], [55, 10], [302, -44]]
  cat2 = np.radians(cat2)
  ra2s, dec2s = cat2[:,0], cat2[:,1]
  dists = angular_dist(ra1, dec1, ra2s, dec2s)
  print(np.degrees(dists))

  cat1 = np.array([[180, 30], [45, 10], [300, -45]])
  cat2 = np.array([[180, 32], [55, 10], [302, -44]])
  matches, no_matches, time_taken = crossmatch(cat1, cat2, 5)
  print('matches:', matches)
  print('unmatched:', no_matches)
  print('time taken:', time_taken)

  # A function to create a random catalogue of size n
  def create_cat(n):
    ras = np.random.uniform(0, 360, size=(n, 1))
    decs = np.random.uniform(-90, 90, size=(n, 1))
    return np.hstack((ras, decs))

  # Test your function on random inputs
  np.random.seed(0)
  cat1 = create_cat(10)
  cat2 = create_cat(20)
  matches, no_matches, time_taken = crossmatch(cat1, cat2, 5)
  print('matches:', matches)
  print('unmatched:', no_matches)
  print('time taken:', time_taken)

wk2/Crossmatching with k-d trees/02_vectorization.py


from __future__ import print_function
import numpy as np

def uniqueStates(states,rates):
    """
    Returns unique states and sums up the corresponding rates.
    States should be a 2d numpy array with on each row a state, and rates a 1d numpy array with length equal to the number of rows in states.

    This may be helpful in the transition function for summing up the rates of different transitions that lead to the same state
    """
    order     = np.lexsort(states.T)
    states    = states[order]
    diff      = np.ones(len(states), 'bool')
    diff[1:]  = (states[1:] != states[:-1]).any(-1)
    sums      = np.bincount(diff.cumsum() - 1, rates[order])
    return states[diff], sums

def number_of_partitions(max_range, max_sum):
    '''
    Returns an array arr of the same shape as max_range, where
    arr[j] = number of admissible partitions for
             j summands bounded by max_range[j:] and with sum <= max_sum
    '''
    M = max_sum + 1
    N = len(max_range)
    arr = np.zeros(shape=(M,N), dtype = int)
    arr[:,-1] = np.where(np.arange(M) <= min(max_range[-1], max_sum), 1, 0)
    for i in range(N-2,-1,-1):
        for j in range(max_range[i]+1):
            arr[j:,i] += arr[:M-j,i+1]
    return arr.sum(axis = 0)

def partition_zero(max_range, max_sum, out = None, n_part = None):
    '''
    Function that can be helpful for obtaining the state space of a discrete Markov chain or Markov decision processes.
    Returns a 2d-array with on the rows all possible partitions of the ranges `0,...,max_range[j]` that add up to at most `max_sum`.

    Code due to ptrj, see http://stackoverflow.com/a/36563744/1479342.

    Parameters
    ----------
    max_range : array or list of ints
        Gives the ranges for each element in the output array. Element `j` has range `np.arange(max_range[j]+1)`.
    max_sum : int
        The maximum sum for each partition in the output array.

    Returns
    -------
    out : array
        2d array with all possible partitions of the ranges `0,...,max_range[j]` summing up to at most `max_sum`.

    Example
    -------
    >>> max_range=np.array([1,3,2])
    >>> max_sum = 3
    >>> partition_zero(max_range,max_sum)
    array([[0, 0, 0],
           [0, 0, 1],
           [0, 0, 2],
           [0, 1, 0],
           [0, 1, 1],
           [0, 1, 2],
           [0, 2, 0],
           [0, 2, 1],
           [0, 3, 0],
           [1, 0, 0],
           [1, 0, 1],
           [1, 0, 2],
           [1, 1, 0],
           [1, 1, 1],
           [1, 2, 0]])
    '''
    if out is None:
        max_range = np.asarray(max_range, dtype = int).ravel()
        n_part = number_of_partitions(max_range, max_sum)
        out = np.zeros(shape = (n_part[0], max_range.size), dtype = int)

    if(max_range.size == 1):
        out[:] = np.arange(min(max_range[0],max_sum) + 1, dtype = int).reshape(-1,1)
        return out

    P = partition_zero(max_range[1:], max_sum, out=out[:n_part[1],1:], n_part = n_part[1:])

    S = np.minimum(max_sum - P.sum(axis = 1), max_range[0])
    offset, sz  = 0, S.size
    out[:sz,0] = 0
    for i in range(1, max_range[0]+1):
        ind, = np.nonzero(S)
        offset, sz = offset + sz, ind.size
        out[offset:offset+sz, 0] = i
        out[offset:offset+sz, 1:] = P[ind]
        S[ind] -= 1
    return out

def partition(min_range,max_range,max_sum=None):
    '''
    Function that can be helpful for obtaining the state space of a discrete Markov chain or Markov decision processes.
    Returns a 2d-array with on the rows all possible partitions of the ranges `min_range[j],...,max_range[j]` for each j.
    If the argument `max_sum` is specified, the numbers will add up to at most `max_sum`

    Parameters
    ----------
    min_range : array or list of ints
        `min_range[j]` gives the minimum value for element `j` in the output array.
    max_range : array or list of ints
        `max_range[j]` gives the maximum value for element `j` in the output array.
    max_sum : int
        Optional argument. The maximum sum for each partition in the output array.

    Returns
    -------
    out : array
        2d array with all possible partitions of the ranges `min_range[j],...,max_range[j]` summing up to at most `max_sum`.

    Example
    -------
    >>> min_range=np.array([1,2,1])
    >>> max_range=np.array([2,4,3])
    >>> max_sum = 6
    >>> partition(min_range,max_range,max_sum)
    array([[1, 2, 1],
           [1, 2, 2],
           [1, 2, 3],
           [1, 3, 1],
           [1, 3, 2],
           [1, 4, 1],
           [2, 2, 1],
           [2, 2, 2],
           [2, 3, 1]])
    '''
    max_range = np.asarray(max_range, dtype = int).ravel()
    min_range = np.asarray(min_range, dtype = int).ravel()
    full_range = max_range-min_range
    if any(full_range<0):
        raise ValueError("max_range needs to be larger than min_range")
    if max_sum == None:
        max_sum = np.sum(full_range)
    else:
        max_sum -= np.sum(min_range)
    out = partition_zero(full_range,max_sum)
    out += min_range
    return out


'''
Based on the video: https://www.youtube.com/watch?v=GiCJS9sUQts
'''
import matplotlib.pyplot as plt
import numpy as np
import numpy.polynomial.polynomial as poly

def plot_graphs():
    # fine
    x_start = -2.0
    x_stop = 2.1
    increment = 0.1
    x = np.arange(start=x_start, stop=x_stop, step=increment)
    y = x**2
    plt.plot(x, y)

    # course
    x_start = -2
    x_stop = 3
    increment = 1
    x = np.arange(start=x_start, stop=x_stop, step=increment)
    y = x**2
    plt.plot(x, y, '-o')

    plt.title('y(x)')
    plt.show()

def plot_solution():
    # course
    x_start = -2
    x_stop = 2.1
    increment = 0.1
    x = np.arange(start=x_start, stop=x_stop, step=increment)
    y = x**2

    # exact/analytical solution
    dydx = 2*x
    print(f'dydx={dydx}')
    plt.plot(x, dydx, 'o-')

    # numeric solution
    dydx_num = np.diff(y) / np.diff(x)
    print(f'dydx_num={dydx_num}')
    x_start = -2
    x_stop = 2
    x = np.arange(start=x_start, stop=x_stop, step=increment)
    plt.plot(x, dydx_num, 'o-')

    plt.title('dy/dx')
    plt.legend(['Exact', 'Numeric'])
    plt.show()

def polynomial_differentiation():
    # p(x) = x^3 + 2x^2 - x + 3
    # p(x) = 3 - x + 2x^2 + x^3
    p = [3, -1, 2, 1]
    dpdx = poly.polyder(p)
    print(f'dpdx = {dpdx}')

    # p(x) = x^3 + 2
    # p(x) = 2 + x^3
    # p(x) = 2 + 0x + 0x^2 + x^3
    p = [2, 0, 0, 1]
    dpdx = poly.polyder(p)
    print(f'dpdx = {dpdx}')

def main():
    # plot_graphs()
    # plot_solution()
    polynomial_differentiation()

if __name__ == "__main__":
    main()


import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

def european_opt_payoff(typ,S, K):
#typ ="type" when "c"=call, "p=put"
    if (typ=='c'):
        payoff=(abs(S-K)+(S-K))/2
    else:
        payoff=(abs(K-S)+(K-S))/2

    return payoff

def bullspread_payoff(S,K1,K2):
    typ='c'
    payoff = european_opt_payoff(typ,S, K1)-european_opt_payoff(typ,S, K2)
    return payoff

def bearspread_payoff(S,K1,K2):
    typ='p'
    payoff = european_opt_payoff(typ,S, K2)-european_opt_payoff(typ,S, K1)
    return payoff

def straddle_payoff(S,K):
    payoff = european_opt_payoff('c',S, K)+european_opt_payoff('p',S, K)
    return payoff

def strangle_payoff(S,K1,K2):
    payoff = european_opt_payoff('c',S, K2)+european_opt_payoff('p',S, K1)
    return payoff

def riskreversal_payoff(S,K1,K2):
    payoff = european_opt_payoff('c',S, K2)-european_opt_payoff('p',S, K1)
    return payoff

def butterfly_payoff(S,K,E):
    payoff = european_opt_payoff('c',S, K-E)-2*european_opt_payoff('c',S, K)+ \
    european_opt_payoff('c',S, K+E)
    return payoff

def condor_payoff(S,K1,K2,E):
    payoff = european_opt_payoff('c',S, K1-E)- \
    european_opt_payoff('c',S, K1)- \
    european_opt_payoff('c',S, K2)+ \
    european_opt_payoff('c',S, K2+E)
    return payoff

cc=8
rr=2

S=np.arange(1,15,0.01) #underlying asset prices
K1=3.0 #strike price
K2 =12.0
typ='c'
pay_off = bullspread_payoff(S, K1, K2)

plt.figure(figsize=(10,30))
plt.subplot(cc,rr,1)
#plt.axis([xmin,xmax,ymin,ymax])
plt.axhline(0, color='black', lw=2)
plt.plot(S,pay_off,color = '#1B9E77', linewidth=1.5)
plt.title('Long a Bull Spread')
plt.grid(True)

S=np.arange(0,15,0.01) #underlying asset prices
K1=3 #strike price
K2=12 #strike price
pay_off = bearspread_payoff(S,K1,K2);

plt.subplot(cc,rr,2)
plt.axhline(0, color='black', lw=2)
plt.plot(S,pay_off,color = '#1B9E77', linewidth=1.5)
plt.title('Long a Bear Spread')
plt.grid(True)

S=np.arange(0,10,0.01) #underlying asset prices
K =5 #strike price

pay_off = straddle_payoff(S,K)

plt.subplot(cc,rr,3)
plt.axhline(0, color='black', lw=2)
plt.plot(S,pay_off,color = '#1B9E77', linewidth=1.5)
plt.title('Long a Stradlle')
plt.grid(True)

plt.subplot(cc,rr,4)
plt.axhline(0, color='black', lw=2)
plt.plot(S,-pay_off,color = '#1B9E77', linewidth=1.5)
plt.title('Short a Stradlle')
plt.grid(True)

S=np.arange(0,10,0.01) #underlying asset prices
K1 = 8.0 #strike price
K2 = 5.0 #strike price
pay_off = strangle_payoff(S,K1,K2)
plt.subplot(cc,rr,5)
plt.axhline(0, color='black', lw=2)
plt.plot(S,pay_off,color = '#1B9E77', linewidth=1.5)
plt.title('In-the-Money Strangle Option')
plt.grid(True)

S=np.arange(0,10,0.01) #underlying asset prices
K1 = 5.0 #strike price
K2 = 8.0 #strike price
pay_off = strangle_payoff(S,K1,K2)
plt.subplot(cc,rr,6)
plt.axhline(0, color='black', lw=2)
plt.plot(S,pay_off,color = '#1B9E77', linewidth=1.5)
plt.title('Out-of-Money Strangle Option')
plt.grid(True)

S=np.arange(0,10,0.01) #underlying asset prices
K1 = 3.0 #strike price
K2 = 8.0 #strike price
if K1>=K2:
    print('Sorry, it is improper setting')
else:
    pay_off = riskreversal_payoff(S, K1, K2)

plt.subplot(cc,rr,7)
plt.axhline(0, color='black', lw=2)
plt.plot(S,pay_off,color = '#1B9E77', linewidth=1.5)
plt.title('Long a Risk Reversal Option')
plt.grid(True)

plt.subplot(cc,rr,8)
plt.axhline(0, color='black', lw=2)
plt.plot(S,-pay_off,color = '#1B9E77', linewidth=1.5)
plt.title('Short a Risk Reversal Option')
plt.grid(True)

S=np.arange(5,15,0.01) #underlying asset prices
K = 10.0 #strike price
E = 4.0 #strike price
if E>K:
    print('Sorry, it is improper setting')
else:
    pay_off = butterfly_payoff(S, K, E)

plt.subplot(cc,rr,9)
plt.axhline(0, color='black', lw=2)
plt.plot(S,pay_off,color = '#1B9E77', linewidth=1.5)
plt.title('Long a Butterfly Option')
plt.grid(True)

S=np.arange(0,15,0.01) #underlying asset prices
K1 = 6.0 #strike price
K2 = 10.0
E = 4.0 #strike price
if ((E>K)or(K1>K2)or(E>K2)):
    print('Sorry, it is improper setting')
else:
    pay_off = condor_payoff(S, K1, K2, E)

plt.subplot(cc,rr,10)
plt.axhline(0, color='black', lw=2)
plt.plot(S,pay_off,color = '#1B9E77', linewidth=1.5)
plt.title('Long a Condor Option')
plt.grid(True)


# Copyright (C) 2018 by Joon Jung
#
# This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
#

# Strassen Multiplication

# M1 = (A11 + A22)(B11 + B22)
# M2 = (A21 + A22)B11
# M3 = A11(B12 - B22)
# M4 = A22(B21 - B11)
# M5 = (A11 + A12)B22
# M6 = (A21 - A11)(B11 + B12)
# M7 = (A12 - A22)(B21 + B22)

# C11 = M1 + M4 - M5 + M7
# C12 = M3 + M5
# C21 = M2 + M4
# C22 = M1 - M2 + M3 + M6

import numpy as np

# INPUT: This implementation only allows square
# even numbered matrix. The user of
# this function is expected to pad or
# do something similar before feeding
# the data.
# OUTPUT: Multiplied Matrix

# Big O bound
# Each call of strassen_multiplication is composed of
# 7f(n-1) + constant operations. Therefore the bound is
# O((7 + O(1))^n and n = log(2, N).
# This leads to O(pow(N, log(2, 7)+O(1))) and gives
# roughly O(pow(N, 2.8)) bound.
def strassen_multiplication(a, b, n):

  n1, n2 = a.shape
  if n != n1 or n != n2:
    print("Only square matrices are acceptted. Please pad your matrices.")
    return 0
  elif n%2 != 0:
    print("Only even matrices are acceptted. Please pad your matrices.")
    return 0

  if n == 2:
    m1 = (a[0][0] + a[1][1]) * (b[0][0] + b[1][1])
    m2 = (a[1][0] + a[1][1]) * b[0][0]
    m3 = a[0][0] * (b[0][1] - b[1][1])
    m4 = a[1][1] * (b[1][0] - b[0][0])
    m5 = (a[0][0] + a[0][1]) * b[1][1]
    m6 = (a[1][0] - a[0][0]) * (b[0][0] + b[0][1])
    m7 = (a[0][1] - a[1][1]) * (b[1][0] + b[1][1])

    c11 = m1 + m4 - m5 + m7
    c12 = m3 + m5
    c21 = m2 + m4
    c22 = m1 - m2 + m3 + m6

    a = np.stack((c11, c21), axis=0)
    b = np.stack((c12, c22), axis=0)
    c = np.stack((a, b), axis=1)

    return c

  a11 = a[0:n//2, 0:n//2]
  a12 = a[0:n//2, n//2:n]
  a21 = a[n//2:n, 0:n//2]
  a22 = a[n//2:n, n//2:n]

  b11 = b[0:n//2, 0:n//2]
  b12 = b[0:n//2, n//2:n]
  b21 = b[n//2:n, 0:n//2]
  b22 = b[n//2:n, n//2:n]

  m1 = strassen_multiplication(a11 + a22, b11 + b22, n//2)
  m2 = strassen_multiplication(a21 + a22, b11, n//2)
  m3 = strassen_multiplication(a11, b12 - b22, n//2)
  m4 = strassen_multiplication(a22, b21 - b11, n//2)
  m5 = strassen_multiplication(a11 + a12, b22, n//2)
  m6 = strassen_multiplication(a21 - a11, b11 + b12, n//2)
  m7 = strassen_multiplication(a12 - a22, b21 + b22, n//2)

  c11 = m1 + m4 - m5 + m7
  c12 = m3 + m5
  c21 = m2 + m4
  c22 = m1 - m2 + m3 + m6

  a = np.concatenate((c11, c12), axis=1)
  b = np.concatenate((c21, c22), axis=1)
  c = np.concatenate((a, b), axis=0)

  return c

n = 8

a = np.zeros((n, n))
k = 0
for i in range(n):
  for j in range(n):
    a[i, j] = k
    k += 1

b = np.zeros((n, n))
for i in range(n):
  for j in range(n):
    b[i, j] = k
    k += 1

c = a.dot(b)
print(a)
print(b)
print(c)

c = strassen_multiplication(a, b, n)
print("Result:")
print(c)


from sympy import *
from rodrigues_R_utils import *

x_1, y_1, z_1 = symbols('x_1 y_1 z_1')
px_1, py_1, pz_1 = symbols('px_1 py_1 pz_1')
sx_1, sy_1, sz_1 = symbols('sx_1 sy_1 sz_1')
x_2, y_2, z_2 = symbols('x_2 y_2 z_2')
px_2, py_2, pz_2 = symbols('px_2 py_2 pz_2')
sx_2, sy_2, sz_2 = symbols('sx_2 sy_2 sz_2')
scale = symbols('scale')
all_symbols = [scale]

point_1 = Matrix([x_1, y_1, z_1, 1]).vec()
point_2 = Matrix([x_2, y_2, z_2, 1]).vec()

transformed_point_1 = (matrix44FromRodrigues(scale * px_1, scale * py_1, scale * pz_1, sx_1, sy_1, sz_1) * point_1)[:-1,:]
transformed_point_2 = (matrix44FromRodrigues(scale * px_2, scale * py_2, scale * pz_2, sx_2, sy_2, sz_2) * point_2)[:-1,:]

delta=Matrix([0,0,0]).vec()-(transformed_point_1-transformed_point_2)
delta_jacobian=delta.jacobian(all_symbols)

print(delta)
print(delta_jacobian)

with open("point_to_point_vo_scale_rodrigues_wc_jacobian.h",'w') as f_cpp:
    f_cpp.write("inline void point_to_point_vo_scale_rodrigues_wc(double &delta_x, double &delta_y, double &delta_z, double px_1, double py_1, double pz_1, double sx_1, double sy_1, double sz_1, double px_2, double py_2, double pz_2, double sx_2, double sy_2, double sz_2, double x_1, double y_1, double z_1, double x_2, double y_2, double z_2, double scale)\n")
    f_cpp.write("{")
    f_cpp.write("delta_x = %s;\n"%(ccode(delta[0,0])))
    f_cpp.write("delta_y = %s;\n"%(ccode(delta[1,0])))
    f_cpp.write("delta_z = %s;\n"%(ccode(delta[2,0])))
    f_cpp.write("}")
    f_cpp.write("\n")
    f_cpp.write("inline void point_to_point_vo_scale_rodrigues_wc_jacobian(Eigen::Matrix<double, 3, 1> &j, double px_1, double py_1, double pz_1, double px_2, double py_2, double pz_2)\n")
    f_cpp.write("{")
    for i in range (3):
        for j in range (1):
            f_cpp.write("j.coeffRef(%d,%d) = %s;\n"%(i,j, ccode(delta_jacobian[i,j])))
    f_cpp.write("}")


# exercise 2.2.2

from matplotlib.pyplot import (figure, subplot, plot, xlabel, ylabel, title,
yticks, show,legend,imshow, cm)
from scipy.io import loadmat
import scipy.linalg as linalg
import numpy as np

# Digits to include in analysis (to include all, n = range(10) )
n = [0,1]
# Number of principal components for reconstruction
K = 16
# Digits to visualize
nD = range(6);

# Load Matlab data file to python dict structure
# and extract variables of interest
traindata = loadmat('../Data/zipdata.mat')['traindata']
X = traindata[:,1:]
y = traindata[:,0]

N,M = X.shape
C = len(n)

classValues = n
classNames = [str(num) for num in n]
classDict = dict(zip(classNames,classValues))

# Select subset of digits classes to be inspected
class_mask = np.zeros(N).astype(bool)
for v in n:
    cmsk = (y == v)
    class_mask = class_mask | cmsk
X = X[class_mask,:]
y = y[class_mask]
N=X.shape[0]

# Center the data (subtract mean column values)
Xc = X - np.ones((N,1))*X.mean(0)

# PCA by computing SVD of Y
U,S,V = linalg.svd(Xc,full_matrices=False)
#U = mat(U)
V = V.T

# Compute variance explained by principal components
rho = (S*S) / (S*S).sum()

# Project data onto principal component space
Z = Xc @ V

# Plot variance explained
figure()
plot(rho,'o-')
title('Variance explained by principal components');
xlabel('Principal component');
ylabel('Variance explained value');

# Plot PCA of the data
f = figure()
title('pixel vectors of handwr. digits projected on PCs')
for c in n:
    # select indices belonging to class c:
    class_mask = (y == c)
    plot(Z[class_mask,0], Z[class_mask,1], 'o')
legend(classNames)
xlabel('PC1')
ylabel('PC2')

# Visualize the reconstructed data from the first K principal components
# Select randomly D digits.
figure(figsize=(10,3))
W = Z[:,range(K)] @ V[:,range(K)].T
D = len(nD)
for d in range(D):
    digit_ix = np.random.randint(0,N)
    subplot(2, D, d+1)
    I = np.reshape(X[digit_ix,:], (16,16))
    imshow(I, cmap=cm.gray_r)
    title('Original')
    subplot(2, D, D+d+1)
    I = np.reshape(W[digit_ix,:]+X.mean(0), (16,16))
    imshow(I, cmap=cm.gray_r)
    title('Reconstr.');

# Visualize the pricipal components
figure(figsize=(8,6))
for k in range(K):
    N1 = np.ceil(np.sqrt(K)); N2 = np.ceil(K/N1)
    subplot(N2, N1, k+1)
    I = np.reshape(V[:,k], (16,16))
    imshow(I, cmap=cm.hot)
    title('PC{0}'.format(k+1));

# output to screen
show()

print('Ran Exercise 2.2.2')


import seaborn as sns
import pandas as pd
import matplotlib.pyplot as plt
import statsmodels.formula.api as sm

# ----------------------- Correlation analyses-------------------------------------------
def heatmap_numeric_w_dependent_variable(df, dependent_variable):
    """
    Takes df, a dependant variable as str
    Returns a heatmap of all independent variables' correlations with dependent variable
    """
    plt.figure(figsize=(10, 5.5))
    figure = sns.heatmap(
        df.corr()[[dependent_variable]].sort_values(by=dependent_variable),
        annot=True,
        cmap="coolwarm",
        vmin=-1,
        vmax=1,
    )
    return figure

def plot_correlated_features(df, threshold: float):
    corr = df.corr()
    colour_limits = corr[corr >= threshold]
    plt.figure(figsize=(10, 5.5))
    sns.heatmap(colour_limits, cmap="Greens")
    plt.title(f"Features with correlation above {threshold*100}%")

# Also try ideas from https://towardsdatascience.com/feature-selection-correlation-and-p-value-da8921bfb3cf
def drop_correlated_pairs(df, threshold: float):
    corr = df.corr().abs()
    corr_array = corr.unstack()
    sorted_corr_array = corr_array.sort_values(
        kind="quicksort", ascending=False
    ).drop_duplicates()
    sorted_corr = pd.DataFrame(sorted_corr_array).reset_index()
    sorted_corr.rename(
        columns={"level_0": "feature_1", "level_1": "feature_2", 0: "score"},
        inplace=True,
    )
    collinear_array = sorted_corr[sorted_corr["score"] >= threshold]
    exclude_collinear_feats = collinear_array["feature_2"].values
    # use correlations above 0.5
    # bug: excluding singular pairs with r=1
    return exclude_collinear_feats

def find_correlated_columns(df, threshold: float):
    corr = df.corr()
    columns = np.full((corr.shape[0],), True, dtype=bool)
    for i in range(corr.shape[0]):
        for j in range(i + 1, corr.shape[0]):
            if corr.iloc[i, j] >= threshold:
                if columns[j]:
                    columns[j] = False
    return df.columns[columns]

# selected_columns = selected_columns[1:].values
def backwardElimination(x, Y, sl, columns):
    numVars = len(x[0])
    for i in range(0, numVars):
        regressor_OLS = sm.OLS(Y, x).fit()
        maxVar = max(regressor_OLS.pvalues).astype(float)
        if maxVar > sl:
            for j in range(0, numVars - i):
                if regressor_OLS.pvalues[j].astype(float) == maxVar:
                    x = np.delete(x, j, 1)
                    columns = np.delete(columns, j)

    regressor_OLS.summary()
    return x, columns

# Example usage:
# SL = 0.05
# data_modeled, selected_columns = backwardElimination(data.iloc[:,1:].values, data.iloc[:,0].values, SL, selected_columns)

# TODO
# Augmented Dickey-Fuller


import numpy as np
import scipy.stats as stats

# ================================================================
# 95% CONFIDENCE INTERVAL FUNCTION
# ================================================================

def get_95_ci(x1, x2):
    #  Calculate a 95% CI for 2 1d numpy arrays
    signal = x1.mean() - x2.mean()
    noise = np.sqrt(x1.var() / x1.size + x2.var() / x2.size)
    ci_lo = signal - 1.96 * noise
    ci_hi = signal + 1.96 * noise
    return ci_lo, ci_hi

# ================================================================
# COMMON LANGUAGE EFFECT SIZE
# ================================================================

def cles_ind(x1, x2):
    """
    COMMON LANGUAGE EFFECT SIZE

    Interpret as the probability that a score sampled
    at random from one distribution will be greater than
    a score sampled from some other distribution.
    Based on: http://psycnet.apa.org/doi/10.1037/0033-2909.111.2.361

    :param x1: sample 1
    :param x2: sample 2
    :return: (float) common language effect size
    """
    x1 = np.array(x1)
    x2 = np.array(x2)
    diff = x1[:, None] - x2
    cles = max((diff < 0).sum(), (diff > 0).sum()) / diff.size
    return cles

# ================================================================
# 2 MEDIAN GROUP TEST CONFIDENCE INTERVAL
# ================================================================

def calc_non_param_ci(x1, x2, alpha=0.05):
    """Calc confidence interval for 2 group median test
    Process:
    * Find all pairwise diffs
    * Sort diffs
    * Find appropriate value of k
    * Choose lower bound from diffs as: diffs[k]
    * Choose upper bound from diffs as: diffs[-k]
    Based on: https://www.ncbi.nlm.nih.gov/pmc/articles/PMC2545906/

    :param x1: sample 1
    :param x2: sample 2
    :param alpha: significance level
    :return: (tuple) confidence interval bounds
    """

    x1 = np.array(x1)
    x2 = np.array(x2)

    n1 = x1.size
    n2 = x2.size
    cv = stats.norm.ppf(1 - alpha / 2)

    # Find pairwise differences for every datapoint in each group
    diffs = (x1[:, None] - x2).flatten()
    diffs.sort()

    # For an approximate (1-a)% confidence interval first calculate K:
    k = int(round(n1 * n2 / 2 - (cv * (n1 * n2 * (n1 + n2 + 1) / 12) ** 0.5)))

    # The Kth smallest to the Kth largest of the n x m differences
    # n1 and n2 should be > ~20
    ci_lo = diffs[k]
    ci_hi = diffs[-k]

    return ci_lo, ci_hi


import numpy as np
from random import shuffle

def sigmoid(x):
    """Sigmoid function implementation"""
    h = np.zeros_like(x)

    h=1/(1+np.exp(-x))
    return h

    #############################################################################
    # TODO: Implement sigmoid function.                                         #
    #############################################################################
    #############################################################################
    #                          START OF YOUR CODE                               #
    #############################################################################

    #############################################################################
    #                          END OF YOUR CODE                                 #
    #############################################################################

def logistic_regression_loss_naive(W, X, y, reg):
    """
      Logistic regression loss function, naive implementation (with loops)
      Use this linear classification method to find optimal decision boundary.

      Inputs have dimension D, there are C classes, and we operate on minibatches
      of N examples.

      Inputs:
      - W: a numpy array of shape (D, C) containing weights.
      - X: a numpy array of shape (N, D) containing a minibatch of data.
      - y: a numpy array of shape (N,) containing training labels; y[i] = c means
        that X[i] has label c, where c can be either 0 or 1.
      - reg: (float) regularization strength. For regularization, we use L2 norm.

      Returns a tuple of:
      - loss: (float) the mean value of loss functions over N examples in minibatch.
      - gradient: gradient wrt W, an array of same shape as W
    """
    # Set the loss to a random number
    loss = 0
    # Initialize the gradient to zero
    dW = np.zeros_like(W)
    num_train ,dimension = X.shape

    for i in range(num_train):
        xi = X[i,:]
        f_x = 0
        for j in range(xi.shape[0]):
            f_x += xi[j] * W[j]
        predict = sigmoid(f_x)
        loss += -(y[i] * np.log(predict) + (1 - y[i]) * np.log(1 - predict))
        b=np.array([[i] for i in xi])
        dW += -(int(y[i])-int(predict)) * b
    loss /= num_train
    loss += 0.5 * reg * np.sum(W * W) # add regularization

    dW =dW/num_train
    dW += reg * W # add regularization

    return loss, dW

def logistic_regression_loss_vectorized(W, X, y, reg):
    """
    Logistic regression loss function, vectorized version.
    Use this linear classification method to find optimal decision boundary.

    Inputs and outputs are the same as softmax_loss_naive.
    """
    # Set the loss to a random number
    loss = 0
    # Initialize the gradient to zero
    dW = np.zeros_like(W)
    num_train ,dimension = X.shape
    f_x_mat = X.dot(W)
    predict = 1.0 / (1.0 + np.exp(-f_x_mat)) # [N, 1]
    loss = -np.sum( np.log(predict)*y  +  np.log(1 - predict)*(1 - y) )
    loss = 1.0/num_train * loss + 0.5 * reg * np.sum(W * W)
    dW = (X.T).dot(predict - y) # [D, 1]
    dW = 1.0 / num_train * dW + reg * W

    return loss, dW


import numpy as np

# Basic array definition, and shortcuts for creating common arrays

array_1d = np.array([1, 2, 3])
array_2d = np.array([[1, 4, 3], [1, 5, 9]])   # 2 x 3 array
zeros = np.zeros((3, 4))
identity = np.identity(4)
rand = np.random.uniform(0, 1, size=(100, 3))
points = np.linspace(1, 2, 100)

# Basic info
array_1d.shape          # (3,)
array_2d.shape          # (3, 2)
array_2d.size           # 6

# Element access/assignment
array = np.array([[1,-1], [2, -2], [3, -3]])    # 3 x 2 array
array[0]        # [1, -1]               - Row access
array[:,1]      # [-1, -2, 3]           - Column access
array[:,0:1]    # [[-1], [-2], [-3]]    - Slicing notation - Note that you still have a 2D array of shape 3x1
array[0,0]      # 1
array[0,0] = 5                          # Single element assignment
array[1:,0:] = np.identity(2)           # Multiple element assignment - Must have consistent shape
                                        # Final array: [[5, -1], [1, 0], [0, 1]]

# Resizing array
# None of these operations are in-place, so you must assign to a new variable to have an effect!
mat_1 = np.random.uniform(size=(6, 4))      # 6x2 array
mat_1.T                                     # Transpose, 4x6 array
np.reshape(mat_1, (24,))                    # 24-vector
np.reshape(mat_1, (-1,))                    # -1 means "infer this dimension based off the others" - Also a 24-vector
np.reshape(mat_1, (4, -1))                  # Inferred dimension is 24/4 = 6, so get 4x6 array
np.reshape(mat_1, (-1, 2, 3))               # Inferred dimension is 24/(2*3) = 4, so get 4x2x3 array

# Basic arithmetic
array_a = np.array([1, 2, -3.5])
array_b = np.array([3, 1, 2])

array_a + array_b   # [4, 3, -1.5]
array_a * array_b   # [3, 2, -7.0]
array_a - 3         # [-2, -1, -6.5]
2 ** array_b        # [8, 2, 4]

# Arithmetic operations involving an NxD array and a D-vector
# Will do "row-wise" operations to the NxD array
array_2d - array_1d     # [[0, 2, 0], [0, 3, 6]]

# Matrix operations
mat_1 = np.random.uniform(size=(3, 2))
mat_2 = np.random.uniform(size=(2, 2))
np.linalg.inv(mat_2)    # Inverse
mat_1 @ mat_2           # Matrix multiplication - equivalent to mat_1.dot(mat_2)

# Truth operations and truth-based subsetting
truth_index = array_1d > 1.5        # Boolean array of [False, True, True]
array_1d[truth_index]               # [2, 3]
np.all(truth_index)                 # False, because not all of the values in the array are True
np.any(truth_index)                 # True, because at least one of the values in the array is True

# Index-based subsetting
vals = np.array([[2, 3], [4, 5], [1, 1]])   # 3x2 array
to_index = np.array([0, 2])
vals[to_index]                              # [[2, 3], [1, 1]]
sorting_index = np.array([1, 0, 2])
vals[sorting_index]                         # [[4, 5], [2, 3], [1, 1]]

# Summary stats
stats = np.array([[1, 2, 3], [-1, 5, 4], [3, 4, 1], [9, 8, 3]])     # 4x3 array
stats.mean()                    # 3.5                           - Mean over all 12 elements
stats.mean(axis=0)              # [3, 4.75, 2.75]               - Condenses column-wise to get a 3-vector
stats.mean(axis=1)              # [2, 2.667, 2.667, 6.667]      - Condenses row-wise to get a 4-vector
np.linalg.norm(stats, axis=1)   # [3.74, 6.48, 5.10, 12.41]     - Point norms


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

# Parte 2. Genere 1000 numeros aleatorias con una distribucion exponencial, grafique el histograma y compare con la PDF conocida de dicha distribucion.

# Luego Realice 1000 sumas de 1000 numeros aleatorios con una distribucion exponencial y compare (haga un fit) a una distribucion normal, verificando el teorema del limite central.

n=[]
for i in range(1000):
    n.append(np.random.exponential(10)) # Llenamos la lista "n" con numeros aleatorios con distribución exponencial de media 10.

loc1,scale1 = expon.fit(n)  # obetenemos los parámetros "Scale" y "loc" de un fit sobre los datos en la lista "n". Para este caso, la media de la distribución es igual a "scale".
print(scale1,loc1) #imrpimimos estos parámetros

x = np.linspace(0,50, 100)
y=expon.pdf(x,scale=scale1, loc=loc1)  # Graficamos una distribución exponencial con media 10.

f, fig1 = plt.subplots(1,1)

fig1.plot(x, y,'r-', lw=5, alpha=0.6, label='expon pdf') #Graficamos x vs y (distribución)
fig1.hist(n,bins=50,normed=True) #Hacemos el histograma de n. Es importante que esté normalizado.
f.savefig('graficas.png') #Guardamos en una archivo las gráficas.

#Hasta aca verificamos que los datos si pertenecen a la distribución dada. Ahora tenemos que repetir el proceso creando una variable que es la suma de las variables generadas.

sumas=[] #En cada elemento de la lista "sumas" guardamos la suma de 1000 varables aleatorias con distribución exponencial.
for i in range(1000):
    suma=0
    for j in range(1000):
        suma+=np.random.exponential(10)
    sumas.append(suma)

loc2,scale2 = norm.fit(sumas)   # obetenemos los parámetros "scale" y "loc" de un fit sobre los datos en la lista "n". Para este casp, la media de la distribuciíon es igual a "loc" y la desviación estandar a "scale".
print(scale2,loc2)

x2 = np.linspace(8000,11000,10000)
y2=norm.pdf(x2,loc=loc2,scale=scale2)  # Graficamos una distribución normal con media igual a "loc2" y desviación estandar igual a "scale".

f2, fig2 = plt.subplots(1,1)
fig2.plot(x2, y2,'r-', lw=5, alpha=0.6, label='norm pdf')
fig2.hist(sumas,bins=50, normed=True)
f2.savefig('limite.png')


"""Tool for solving systems of linear equations
using Gaussian elimination algorithm

Functions:
    solve_system(A, b, is_modified=False) -> ndarray
    forward_elimination(matrix) -> None
    forward_elimination_m(matrix) -> None
    back_substitution(matrix) -> ndarray
    print_log() -> None
"""

__all__ = [
    'solve_system', 'forward_elimination_m', 'forward_elimination',
    'back_substitution', 'print_log'
]

import numpy as np

def solve_system(A, b, is_modified=False):
    """Solve system of linear equations A x = b and return x.

    Arguments:
    A -- square matrix
    b -- vector
    Keyword arguments:
    is_modified -- selection flag of forward elimination(default False)
    """

    log['A'] = A
    log['b'] = b
    log['forward elimination is modified'] = is_modified
    extd_matrix = np.hstack((A, np.array([b]).T))
    log['extended matrix'] = extd_matrix.copy()
    if is_modified:
        forward_elimination_m(extd_matrix)
    else:
        forward_elimination(extd_matrix)

    x = back_substitution(extd_matrix)
    log['triangular matrix'] = extd_matrix
    log['solution of system'] = x
    return x

def forward_elimination_m(matrix):
    """Perform the forward elimination over the given matrix.

    This function applies a forward elimination using the selection
    of the main elements

    Arguments:
    matrix -- two-dimensional matrix
    """

    ctrl_vector = matrix.sum(axis = 1)
    log['control vector before forward elimination'] = ctrl_vector.copy()
    n = matrix.shape[0]
    for k in range(n):
        # search for the main elements(max by column) and rows swapping
        p = k + abs(matrix[k:,k]).argmax()
        matrix[p], matrix[k] = matrix[k], matrix[p].copy()
        ctrl_vector[p], ctrl_vector[k] = ctrl_vector[k], ctrl_vector[p]

        ctrl_vector[k] /= matrix[k][k]
        matrix[k] /= matrix[k][k]
        for i in range(k + 1, n):
            ctrl_vector[i] -= ctrl_vector[k] * matrix[i][k]
            matrix[i] -= matrix[k] * matrix[i][k]

    log['control vector after forward elimination'] = ctrl_vector

def forward_elimination(matrix):
    """Perform the forward elimination over the given matrix.

    Arguments:
    matrix -- two-dimensional matrix
    """

    ctrl_vector = matrix.sum(axis = 1)
    log['control vector before forward elimination'] = ctrl_vector.copy()
    n = matrix.shape[0]
    for k in range(n):
        ctrl_vector[k] /= matrix[k][k]
        matrix[k] /= matrix[k][k]
        for i in range(k + 1, n):
            ctrl_vector[i] -= ctrl_vector[k] * matrix[i][k]
            matrix[i] -= matrix[k] * matrix[i][k]

    log['control vector after forward elimination'] = ctrl_vector

def back_substitution(matrix):
    """Perform back substitution and return solution of system

    Arguments:
    matrix -- two-dimensional matrix

    Attention: given argument must be a upper triangular matrix
    with ones on main diagonal and have n rows and n + 1 columns
    (last column is vector b)
    """

    n = matrix.shape[0]
    x = np.zeros(n, complex)
    for i in range(n - 1, -1, -1):
        x[i] = matrix[i, n] - sum(matrix[i, :n] * x)

    return np.real_if_close(x)

def print_log():
    """Print a log that stores step-by-step actions"""
    for key, value in log.items():
        print(key + ':\n', value, end='\n\n')

log = dict()


import numpy as np
from numpy import random as rd
from joblib import Parallel, delayed
from math import gamma, pi
import matplotlib.pyplot as plt
rd.seed(12163)
def vol_Aux(d):
    x = rd.uniform(-1,1,d)
    return (sum(x ** 2) < 1)

def vol(N,d):
    # Parallel prossesing for more speed!!!
    results = Parallel(n_jobs=-1, backend="multiprocessing")(
             map(delayed(vol_Aux), [d for i in range(N)]))
    nhits = sum(results)
    return (2 ** d * nhits / N)

def Vol1(d):
    x = d/2
    return pi ** x / gamma(x + 1)

d = 2
N = 1_000_000
MonteCarlo = []
Actual = []
Error = []
while True:
    current = vol(N,d)
    if current == 0: break
    actual = Vol1(d)
    MonteCarlo.append(current)
    Actual.append(actual)
    Error.append(abs(actual - current))
    d += 1

plt.subplot()
plt.plot(MonteCarlo, color = 'b', label = 'Monte Carlo Est')
plt.plot(Actual, color = 'g', label = 'Actual')
plt.plot(Error, color = 'r', label = 'Error')
plt.xlabel(r'Dimensions d')
plt.ylabel(r'V($Sphere$)')
plt.title(r'$p(d) = \frac{\omega (d)}{2^d}$')
plt.legend()
plt.grid()
plt.show()
print( "Monte Carlo: ", np.array(MonteCarlo) )
print("Actual: ", np.array(Actual))
print("Error: ", np.array(Error))


import numpy as np
from scipy.special import expit

sigmoid = expit

def generate_orthogonal_matrix(N):
    """
    with random permutation of coordinate axes
    :param N: dimension of matrix
    :return: NxN orthogonal matrix contains 0 and 1

    Ref: http://stackoverflow.com/questions/33003341/how-to-randomly-generate-a-nonnegative-orthogonal-matrix-in-numpy
    """
    I = np.eye(N)
    p = np.random.permutation(N)
    return I[p]

def solve_df_lambda(X: np.ndarray, dimension=None, epsilon=1e-3, do_standardization=True) -> list:
    """
    Using Newton Method to solve df(lambda) = k, k in [1..p]
    :param X:
    :return: list of lambda

    ref
    ----
    https://en.wikipedia.org/wiki/Newton%27s_method
    """
    if dimension is None:
        dimension = X.shape[1]

    if do_standardization:
        X  = (X-np.mean(X, axis=0)) / np.std(X, axis=0, ddof=1)

    u, d_, vt = np.linalg.svd(X)
    ds = d_**2

    func_d = lambda lam, k: np.sum(ds/(ds + lam)) - k
    func_d_derivative = lambda lam: -np.sum(ds/(ds + lam)**2)

    # init lambda_p is 0
    lambdas = [0] * (dimension + 1)

    for k in reversed(range(1, dimension)):
        last_lam = lambdas[k+1]
        while True:
            new_lambda = last_lam - func_d(last_lam, k) / func_d_derivative(last_lam)
            if abs((new_lambda - last_lam)/new_lambda) < epsilon:
                lambdas[k] = new_lambda
                break
            last_lam = new_lambda

    lambdas[0] = np.inf
    return lambdas


# -*- coding: utf-8 -*-
"""
Created on Sat Mar  9 11:05:33 2019

@author: Ham

HackerRanch Challenge: Array Mathematics

Basic mathematical functions operate element-wise on arrays.
They are available both as operator overloads and as functions in the NumPy module.

import numpy

a = numpy.array([1,2,3,4], float)
b = numpy.array([5,6,7,8], float)

print a + b                     #[  6.   8.  10.  12.]
print numpy.add(a, b)           #[  6.   8.  10.  12.]

print a - b                     #[-4. -4. -4. -4.]
print numpy.subtract(a, b)      #[-4. -4. -4. -4.]

print a * b                     #[  5.  12.  21.  32.]
print numpy.multiply(a, b)      #[  5.  12.  21.  32.]

print a / b                     #[ 0.2         0.33333333  0.42857143  0.5       ]
print numpy.divide(a, b)        #[ 0.2         0.33333333  0.42857143  0.5       ]

print a % b                     #[ 1.  2.  3.  4.]
print numpy.mod(a, b)           #[ 1.  2.  3.  4.]

print a**b                      #[  1.00000000e+00   6.40000000e+01 2.187e+3 6.5536e+4]
print numpy.power(a, b)         #[  1.00000000e+00   6.40000000e+01 2.187e+3 6.5536e+4]

Task

You are given two integer arrays, A and B of dimensions NxM.
Your task is to perform the following operations:

Add (A + B)
Subtract (A - B)
Multiply (A * B)
Integer Division (A / B)
Mod (A % B)
Power (A ** B)

Input Format

The first line contains two space separated integers, N and M.
The next N lines contains M space separated integers of array A.
The following N lines contains M space separated integers of array B.

Output Format

Print the result of each operation in the given order under Task.

Sample Input

1 4
1 2 3 4
5 6 7 8

Sample Output

[[ 6  8 10 12]]
[[-4 -4 -4 -4]]
[[ 5 12 21 32]]
[[0 0 0 0]]
[[1 2 3 4]]
[[    1    64  2187 65536]]

Use // for division in Python 3.

"""

import io
import numpy

STDIN_SIO = io.StringIO("""
1 4
1 2 3 4
5 6 7 8
""".strip())

if __name__ == '__main__':
    #numpy.set_printoptions(legacy='1.13')
    # set_printoptions() above came from a Discussion forum
    #n, m = map(int, input().strip().split())
    #a = numpy.array([input().strip().split() for _ in range(n)], int)
    #b = numpy.array([input().strip().split() for _ in range(n)], int)
    n, m = map(int, STDIN_SIO.readline().strip().split())
    a = numpy.array([STDIN_SIO.readline().strip().split()
                     for _ in range(n)], int)
    b = numpy.array([STDIN_SIO.readline().strip().split()
                     for _ in range(n)], int)
    #print(a)
    #print(b)
    print(numpy.add(a, b))
    print(numpy.subtract(a, b))
    print(numpy.multiply(a, b))
    print(a // b)
    print(numpy.mod(a, b))
    print(numpy.power(a, b))


import numpy as np
from numpy import arctan2, fliplr, flipud
import matplotlib.pyplot as plt
import cv2

def gradient(image, same_size=False):
    """ Computes the Gradients of the image separated pixel difference

    Gradient of X is computed using the filter
        [-1, 0, 1]
    Gradient of X is computed using the filter
        [[1,
          0,
          -1]]
    Parameters
    ----------
    image: image of shape (imy, imx)
    same_size: boolean, optional, default is True
        If True, boundaries are duplicated so that the gradients
        has the same size as the original image.
        Otherwise, the gradients will have shape (imy-2, imx-2)

    Returns
    -------
    (Gradient X, Gradient Y), two numpy array with the same shape as image
        (if same_size=True)
    """
    sy, sx = image.shape
    if same_size:
        gx = np.zeros(image.shape)
        gx[:, 1:-1] = -image[:, :-2] + image[:, 2:]
        gx[:, 0] = -image[:, 0] + image[:, 1]
        gx[:, -1] = -image[:, -2] + image[:, -1]

        gy = np.zeros(image.shape)
        gy[1:-1, :] = image[:-2, :] - image[2:, :]
        gy[0, :] = image[0, :] - image[1, :]
        gy[-1, :] = image[-2, :] - image[-1, :]

    else:
        gx = np.zeros((sy-2, sx-2))
        gx[:, :] = -image[1:-1, :-2] + image[1:-1, 2:]

        gy = np.zeros((sy-2, sx-2))
        gy[:, :] = image[:-2, 1:-1] - image[2:, 1:-1]

    return gx, gy

def magnitude_orientation(gx, gy):
    """ Computes the magnitude and orientation matrices from the gradients gx gy
    Parameters
    ----------
    gx: gradient following the x axis of the image
    gy: gradient following the y axis of the image

    Returns
    -------
    (magnitude, orientation)

    """

    magnitude = np.sqrt(gx**2 + gy**2)
    orientation = (arctan2(gy, gx) * 180 / np.pi) % 360

    return magnitude, orientation

if __name__ == '__main__':
    image = cv2.cvtColor(cv2.imread("Q_3.jpg"), cv2.COLOR_BGR2GRAY)

    gx, gy = gradient(image)
    # print('==================value gx========')
    # # print(gx)
    # plt.hist(gx)
    # plt.show()
    # print('==================value gy========')
    # # print(gy)
    # plt.title('')
    # plt.hist(gy)
    # plt.show()

    magnitude, orientation = magnitude_orientation(gx, gy)
    print('==================magnitude========')
    print(magnitude)
    plt.title('Magnitude')
    plt.hist(magnitude)
    #plt.imsave('magnitude.png', magnitude, format='png')
    plt.show()
    print('==================orienftation========')
    print(orientation)
    plt.title('orientation')
    plt.hist(orientation)
    #plt.imsave('orientation.png', orientation, format='png')
    plt.show()


# IMPORTS
import numpy as np
import scipy.stats as stats
import matplotlib.pyplot as plt
import matplotlib.style as style
from IPython.core.display import HTML

# PLOTTING CONFIG
%matplotlib inline
style.use('fivethirtyeight')
plt.rcParams["figure.figsize"] = (14, 7)
HTML("""
<style>
.output_png {
    display: table-cell;
    text-align: center;
    vertical-align: center;
}
</style>
""")
plt.figure(dpi=100)

# PDF LAM = 1
plt.scatter(np.arange(20),
            (stats.poisson.pmf(np.arange(20), mu=1)),#/np.max(stats.poisson.pmf(np.arange(20), mu=1))),
            alpha=0.75,
            s=100
       )
plt.plot(np.arange(20),
         (stats.poisson.pmf(np.arange(20), mu=1)),#/np.max(stats.poisson.pmf(np.arange(20), mu=1))),
         alpha=0.75,
        )

# PDF LAM = 5
plt.scatter(np.arange(20),
            (stats.poisson.pmf(np.arange(20), mu=5)),
            alpha=0.75,
            s=100
       )
plt.plot(np.arange(20),
         (stats.poisson.pmf(np.arange(20), mu=5)),
         alpha=0.75,
        )

# PDF LAM = 10
plt.scatter(np.arange(20),
            (stats.poisson.pmf(np.arange(20), mu=10)),
            alpha=0.75,
            s=100
       )
plt.plot(np.arange(20),
         (stats.poisson.pmf(np.arange(20), mu=10)),
         alpha=0.75,
        )

# LEGEND
plt.text(x=3, y=.1, s="$\lambda = 1$", alpha=.75, rotation=-65, weight="bold", color="#008fd5")
plt.text(x=8.25, y=.075, s="$\lambda = 5$", alpha=.75, rotation=-35, weight="bold", color="#fc4f30")
plt.text(x=14.5, y=.06, s="$\lambda = 10$", alpha=.75, rotation=-20, weight="bold", color="#e5ae38")

# TICKS
plt.xticks(range(21)[::2])
plt.tick_params(axis = 'both', which = 'major', labelsize = 18)
plt.axhline(y = 0, color = 'black', linewidth = 1.3, alpha = .7)

# TITLE, SUBTITLE & FOOTER
plt.text(x = -2.5, y = .475, s = "Poisson Distribution - $\lambda$",
               fontsize = 26, weight = 'bold', alpha = .75)
plt.text(x = -2.5, y = .425,
         s = 'Depicted below are three Poisson distributed random variables with varying $\lambda $. As one can easily\nsee the parameter $\lambda$ shifts and flattens the distribution (the smaller $ \lambda $ the sharper the function).',
         fontsize = 19, alpha = .85)
plt.text(x = -2.5,y = -0.075,
         s = '   ©Joshua Görner                                                                                github.com/jgoerner   ',
         fontsize = 14, color = '#f0f0f0', backgroundcolor = 'grey');


# task II.10.6.п

import gauss
import seidel
import math_utils
import numpy as np

# set this to True if you want to see A, b, x
dump_matrices = False

def create_matrices(): # -> A, b
	n = 99
	A = np.zeros((n + 1, n + 1))

	for i in range(n):
		A[i][i] = 10 + (i + 1)
		A[i][i + 1] = 1
		A[i + 1][i] = 1
	for j in range(1, n + 1): # 1, 2, ..., n
		A[n][j] = 2
	A[n][0] = A[n][n] = 1

	b = np.fromfunction(lambda i: (i + 1) / n, (n + 1,))
	return A, b

A, b = create_matrices()
if dump_matrices:
	print("A =")
	print(A)
	print("B =", b)

print("condition number of A: ", math_utils.condition_number(A))
print("max eigen value of A: ", math_utils.maxabs_eigen_value(A))
print("min eigen value of A: ", math_utils.minabs_eigen_value(A))

x_gauss = gauss.solve_linear(A, b)
if dump_matrices:
	print("gauss: x =", x_gauss)
print("gauss: ||Ax - b|| =", math_utils.vec_norm_3(np.matmul(A, x_gauss) - b))

x_seidel = seidel.solve_linear(A, b, 1e-15)
if dump_matrices:
	print("seidel: x =", x_seidel)
print("seidel: ||Ax - b|| =", math_utils.vec_norm_3(np.matmul(A, x_seidel) - b))


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

epsilon = 0.01
x_min = -10
x_max = 60
x = np.linspace(x_min,x_max,10000)
theta_values = []

y_initial = 1.5
v_initial = 25
g = 9.81
x0 = 50
x02 = 0
theta_initial = m.radians(30)
y = m.tan(theta_initial)*x - g/(2*v_initial**2*(m.cos(theta_initial))**2)*x**2+y_initial

x1 = Symbol('x')
f = Function('f')(x)
f = m.tan(theta_initial)*x1 - (g/ (2 * v_initial**2 * m.cos(theta_initial)**2) * x1**2) + y_initial
f_prime = diff(f, x1)
f_prime2 = diff(f, x1, 2)

def y(x):
    return m.tan(theta_initial)*x - g/(2*v_initial**2*(m.cos(theta_initial))**2)*x**2+y_initial

def y_prime(x):
    return -0.659675690427106*x - 6.40533119664628

def y_prime_squared():
    return-0.659675690427106

def quadratic_approx(x):
    return y(x0)+y_prime(x0)*(x-x0)+0.5*y_prime_squared()*(x-x0)**2

y_approx_values = []
y_values = []

for i in x:
    y_approx_values.append(quadratic_approx(i))
    y_values.append(y(i))

# for i in x:
#     y_values.append(float(y))
plt.hlines(0,x_min,x_max)
# plt.plot(x,y_approx_values)
plt.plot(x,y_values)
plt.show()

for i in x:
    if -epsilon<=quadratic_approx(i)<=epsilon:
        print(float(quadratic_approx(i)),i)

def newton(f,Df,x0,epsilon,max_iter):
    '''Approximate solution of f(x)=0 by Newton's method.

    Parameters
    ----------
    f : function
        Function for which we are searching for a solution f(x)=0.
    Df : function
        Derivative of f(x).
    x0 : number
        Initial guess for a solution f(x)=0.
    epsilon : number
        Stopping criteria is abs(f(x)) < epsilon.
    max_iter : integer
        Maximum number of iterations of Newton's method.

    Returns
    -------
    xn : number
        Implement Newton's method: compute the linear approximation
        of f(x) at xn and find x intercept by the formula
            x = xn - f(xn)/Df(xn)
        Continue until abs(f(xn)) < epsilon and return xn.
        If Df(xn) == 0, return None. If the number of iterations
        exceeds max_iter, then return None.

    Examples
    --------
    >>> f = lambda x: x**2 - x - 1
    >>> Df = lambda x: 2*x - 1
    >>> newton(f,Df,1,1e-8,10)
    Found solution after 5 iterations.
    1.618033988749989
    '''
    xn = x0
    for n in range(0,max_iter):
        fxn = f(xn)
        if abs(fxn) < epsilon:
            # print('Found solution after',n,'iterations.')
            return xn
        Dfxn = Df(xn)
        if Dfxn == 0:
            # print('Zero derivative. No solution found.')
            return None
        xn = xn - fxn/Dfxn
    print('Exceeded maximum iterations. No solution found.')
    return None

f = lambda x: m.tan(theta_initial)*x - g/(2*v_initial**2*(m.cos(theta_initial))**2)*x**2+y_initial
df= lambda x: m.tan(theta_initial) - 2*g/(2*v_initial**2*(m.cos(theta_initial))**2)*x
dff = lambda : - 2*g/(2*v_initial**2*(m.cos(theta_initial))**2)

max_iter=10000
solution = newton(f,df,x0,epsilon,max_iter)
solution2 = newton(f,df,x02,epsilon,max_iter)

print(f'The first solution: {solution}')
print(f'The second solution: {solution2}')

"""Finding the maximum value of the graph"""
# set the expression, y, equal to 0 and solve
result1 = solve(f_prime,x1)
print(result1)

"""sensitivity part"""
theta_sensitivity = np.linspace(m.radians(30),m.radians(45),15)
new_x_values =  []
theta_values = []

while theta_initial <=m.radians(45):
    z = newton(f,df,x0,epsilon,max_iter)
    theta_values.append(theta_initial)
    theta_initial += m.radians(1)
    new_x_values.append(z)

n_theta_values = []
n_new_x_values = []
for element in theta_values:
    if element != None and element>0.06:
        n_theta_values.append(element)

for element in new_x_values:
    if element != None:
        n_new_x_values.append(element)
    # if element == None:
    #     n_new_x_values.append(0)

plt.plot(n_theta_values,n_new_x_values)
plt.show()


from sympy import *

print('----------------------------------------------------')
print('')
print('TORUS VOLUME CALCULATOR')
print('')
print('this python sheet is designed to compare different methods to calculate')
print('the volume of a rotated circle body, i.e. torus')
print('')

x = symbols('x') #define x as symbols

#basic informations (feel free to modify them)
circle_center_x = 0        #X-coordinate of the circle center
circle_center_y = 2        #X-coordinate of the circle center
circle_radius = 1          #radius of the circle (!!!!!! must be smaller than circle_center_y and positive)
number_of_divisions = 5    #number of divisions for the trapezoidal rule (the more the better approximation)

#function definition and bordersettings of the integral
f1 = circle_center_y + sqrt((circle_radius)**2 - (x-circle_center_x)**2)    #function 1 top part of circle
f2 = circle_center_y - sqrt((circle_radius)**2 - (x-circle_center_x)**2)    #function 2 bottom part of circle
integration_border_a = circle_center_x - circle_radius     #lower limit of the definite integral
integration_border_b = circle_center_x + circle_radius     #upper limit of the definite integral

def printVolumes():
    printExact() #without integral
    printNormal()
    printTrapeze()
    printSimpson()

def printExact():
    print('Exact volume without numeric integration')
    volume = calcExact()
    print('Volume as float: ' + str(float(volume)))
    print('Volume exact: ' + str(volume))
    print('')

def printNormal():
    print('Normal')
    volume = calcNormal()
    print('Volume as float: ' + str(float(volume)))
    print('Volume exact: ' + str(volume))
    print('Volumedifference from exact calculation: ' + str(float(calcExact() - volume)))
    print('')

def printTrapeze():
    print('Trapeze rule')
    volume = calcTrapeze(number_of_divisions)
    print('Volume as float: ' + str(float(volume)))
    print('Volume exact: ' + str(volume))
    print('Volumedifference from exact calculation: ' + str(float(calcExact() - volume)))
    print('Hint: Play arount with <<number_of_divisions>> variable value for better approximation')
    print('')

def printSimpson():
    print('Simpsons rule')
    volume = calcSimpson(number_of_divisions)
    print('Volume as float: ' + str(float(volume)))
    print('Volume exact: ' + str(volume))
    print('Volumedifference from exact calculation: ' + str(float(calcExact() - volume)))
    print('Hint: Play arount with <<number_of_divisions>> variable value for better approximation')
    print('')

def calcExact():
    volume = 2 * pi**2 * circle_center_y * circle_radius**2 #V = 2 * π² * R * r²
    return volume

def calcNormal():
    s1 = integrate(f1**2, (x, integration_border_a, integration_border_b)) #s1 is the surface of the integral from f1
    s2 = integrate(f2**2, (x, integration_border_a, integration_border_b)) #s2 is the surface of the integral from f2
    volume = pi * (s1 - s2) #volume of rotation body is defined as pi * surface of the body -> body is the circle so s1 - s2
    return volume

def calcTrapeze(n):
    s1 = trapezRule(f1**2, n) #s1 is the surface of the integral from f1 with trapezerule
    s2 = trapezRule(f2**2, n) #s2 is the surface of the integral from f2 with trapezerule
    volume = pi * (s1 - s2) #volume of rotation body is defined as pi * surface of the body -> body is the circle so s1 - s2
    return volume

def trapezRule(function, n):
    h = (integration_border_b - integration_border_a) / n   # h = (b-a)/n
    Sum = 0.5 * (function.subs(x, integration_border_a) + function.subs(x, integration_border_b))
    for i in range(1, n):
        Sum += function.subs(x, (integration_border_a + i*h)) #
    Integral = h * Sum
    return Integral

def calcSimpson(n):
    s1 = simpsonRule(f1**2, n) #s1 is the surface of the integral from f1 with simpsonrule
    s2 = simpsonRule(f2**2, n) #s2 is the surface of the integral from f2 with simpsonrule
    volume = pi * (s1 - s2) #volume of rotation body is defined as pi * surface of the body -> body is the circle so s1 - s2
    return volume

def simpsonRule(function, n):
    steper = (abs(integration_border_a) + abs(integration_border_b)) / n #partial interval size
    part_border_a = integration_border_a
    part_border_b = part_border_a + steper
    Sum = 0
    for i in range(n):
        h = (part_border_a + part_border_b) / 2 # h = (a+b)/2
        Sum += ((part_border_b - part_border_a)/6) * (function.subs(x, part_border_a) + (4*function.subs(x, h)) + function.subs(x, part_border_b))
        part_border_a = part_border_b
        part_border_b += steper

    return Sum

#start programm
printVolumes()


import superimport

import numpy as np
from numpy.linalg import cholesky
from matplotlib import pyplot as plt
from scipy.stats import multivariate_normal
from numpy.linalg import inv
import pyprobml_utils as pml

np.random.seed(10)

def gaussSample(mu, sigma, n):
    A = cholesky(sigma)
    Z = np.random.normal(loc=0, scale=1, size=(len(mu), n))
    return np.dot(A, Z).T + mu

mtrue = {}
prior = {}
muTrue = np.array([0.5, 0.5])
Ctrue = 0.1 * np.array([[2, 1], [1, 1]])
mtrue['mu'] = muTrue
mtrue['Sigma'] = Ctrue
xyrange = np.array([[-1, 1], [-1, 1]])
ns = [10]
X = gaussSample(mtrue['mu'], mtrue['Sigma'], ns[-1])

#fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(24, 8))
#fig.suptitle('gauss2dUpdateData')
fig, ax1 = plt.subplots()
ax1.plot(X[:, 0], X[:, 1], 'o', markersize=8, markerfacecolor='b')
ax1.set_ylim([-1, 1])
ax1.set_xlim([-1, 1])
ax1.set_title('data')
ax1.plot(muTrue[0], muTrue[1], 'x', linewidth=5, markersize=20, color='k')
pml.savefig("gauss_2d_update_data.pdf")

prior['mu'] = np.array([0, 0])
prior['Sigma'] = 0.1 * np.eye(2)

npoints = 100j
out = np.mgrid[xyrange[0, 0]:xyrange[0, 1]:npoints, xyrange[1, 0]:xyrange[1, 1]:npoints]
X1, X2 = out[0], out[1]
nr = X1.shape[0]
nc = X2.shape[0]
points = np.vstack([np.ravel(X1), np.ravel(X2)]).T
p = multivariate_normal.pdf(points, mean=prior['mu'], cov=prior['Sigma']).reshape(nr, nc)
fig, ax2 = plt.subplots()
ax2.contour(X1, X2, p)
ax2.set_ylim([-1, 1])
ax2.set_xlim([-1, 1])
ax2.set_title('prior')
pml.savefig("gauss_2d_update_prior.pdf")

post = {}
data = X[:ns[0], :]
n = ns[0]
S0 = prior['Sigma']
S0inv = inv(S0)
S = Ctrue
Sinv = inv(S)
Sn = inv(S0inv + n * Sinv)
mu0 = prior['mu']
xbar = np.mean(data, 0)
muN = np.dot(Sn, (np.dot(n, np.dot(Sinv, xbar)) + np.dot(S0inv, mu0)))

post['mu'] = muN
post['Sigma'] = Sn

p = multivariate_normal.pdf(points, mean=post['mu'], cov=post['Sigma']).reshape(nr, nc)
fig, ax3 = plt.subplots()
ax3.contour(X1, X2, p)
ax3.set_ylim([-1, 1])
ax3.set_xlim([-1, 1])
ax3.set_title('post after 10 observation')
pml.savefig("gauss_2d_update_post.pdf")

#fig.savefig("gauss2dUpdatePostSubplot.pdf")


# exercise 3.1.5
import numpy as np
import scipy.linalg as linalg
from similarity import similarity

from ex3_1_4 import *

# Query vector
q = np.array([0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0])
# notice, that you could get the query vector using the vectorizer, too:
#q = vectorizer.transform(['matrix rank solv'])
#q = np.asarray(q.toarray())
# or use any other string:
#q = vectorizer.transform(['Can I Google how to fix my problem?'])
#q = np.asarray(q.toarray())

# Method 1 ('for' loop - slow)
N = np.shape(X)[0]; # get the number of data objects
sim = np.zeros((N,1)) # allocate a vector for the similarity
for i in range(N):
    x = X[i,:] # Get the i'th data object (here: document)
    sim[i] = q/linalg.norm(q) @ x.T/linalg.norm(x) # Compute cosine similarity

# Method 2 (one line of code with no iterations - faster)
sim = (q @ X.T).T / (np.sqrt(np.power(X,2).sum(axis=1)) * np.sqrt(np.power(q,2).sum()))

# Method 3 (use the "similarity" function)
sim = similarity(X, q, 'cos');

# Display the result
print('Query vector:\n {0}\n'.format(q))
print('Similarity results:\n {0}'.format(sim))

print('Ran Exercise 3.1.5')


"""
A support suite of constraint functions that we can use to test whether an optimization algorithm is actually working.

These are relatively simple; they're good for constraining typical optimization routines.

Source: https://en.wikipedia.org/wiki/Test_functions_for_optimization --> "Test functions for constrained optimization."
"""
import numpy as np

def f1(x):
    """
    Name: First constraint for the Wikipedia Rosenbrock function.
    """
    return (x[0] - 1.0) ** 3 - x[1] + 1.0

def f2(x):
    """
    Name: Second constraint for the Wikipedia Rosenbrock function.
    """
    return x[0] + x[1] - 2.0

def f3(x):
    """
    Name: Third constraint for the Wikipedia Rosenbrock function.
    """
    return x[0]**2 + x[1]**2 - 2.0

def circle(x, a = 1.0, b = 1.0, r = 1.0):
    """
    Name: Generic circle.
    """
    return (x[0] - a)**2 + (x[1] - b)**2 - r**2

def line(x, m = 0.5, b = 1.0):
    """
    Name: Generic line.
    """
    return m*x[0] - x[1] + b