import matplotlib.pyplot as plt
from statistics import mean
import numpy as np
import random

class Kmeans():
    '''Centroid based clustering model, represent each cluster by a single mean vector
        k: int, the number of clusters required to provide
        max_iter: int, max iteration loops to stop
        tol: tolerance'''
    def __init__(self, k, max_iter, tol):
        self.k = k
        self.m = max_iter
        self.t = tol
        self.fl = 8
        self.fw = 8

    def fit(self, X):
        # randomly select K samples as the initial K central vectors
        point_label = [0]*len(X)
        full_set = [i for i in range(len(X))]
        centroids_label = []
        centroids = []
        centroids_old = []
        for i in range(self.k):
            centroids_label.append(random.choice(full_set))
            centroids.append(X[centroids_label[i]])
        count = 0
        clusters_label = []
        clusters = []
        # repeat until converge
        while count <= self.m:
            # initialize clusters
            for i in range(self.k):
                clusters.append([])
                clusters_label.append([])
            # expand clusters by calculating distances
            for i in range(len(X)):
                distances = [np.linalg.norm(np.array(X[i]) - np.array(centroid)) for centroid in centroids]
                clusters_label[distances.index(min(distances))].append(i)
                clusters[distances.index(min(distances))].append(X[i])
            # update cluster centroids
            centroids_old = centroids
            for i in range(self.k):
                # evaluate new central vectors
                centroids[i] = list(np.mean(np.array(clusters[i]), axis=0))
            difference = 0.
            for i in range(self.k):
                difference = difference + np.linalg.norm(np.array(centroids_old[i]) - \
                                                         np.array(centroids[i]))
            # stop if all centroids are not updated
            if difference <= self.t:
                break
            count = count + 1

        return centroids, clusters, self.k

    def Kmeans_visualize(self, centroids, clusters, n):
        # 2-D plot

        plt.figure(figsize = (self.fl, self.fw))
        # plot centroids
        for i in range(self.k):
            x, y = [], []
            x.append(centroids[i][0])
            y.append(centroids[i][1])
        # plot clusters
            x2, y2 = [], []
            for j in range(len(clusters[i])):
                x2.append(clusters[i][j][0])
                y2.append(clusters[i][j][1])

            if(i==0):
                plt.scatter(x2, y2, alpha=0.6, marker='.', cmap=plt.cm.jet, s=200, \
                        label = 'cluster '+str(i))
                plt.scatter(x, y, c='k', alpha=1, marker='*', s=200, label = 'Centroids')
            else:
                plt.scatter(x2, y2, alpha=0.8, marker='.', cmap=plt.cm.jet, s=200, \
                        label = 'cluster '+str(i))
                plt.scatter(x, y, c='k', alpha=1, marker='*', s=200)

        plt.grid(True)
        plt.legend()
        plt.show()

from sklearn.datasets import load_iris

def main():

    data = load_iris()
    X = data['data']
    X.tolist()
    #X = readin_csv_data_clustering("concentric_circles.csv")
    #X = normalization(X, 2)
    model = Kmeans(3, 500, 0.001)
    centroids, clusters, k = model.fit(X)
    #print('cluster number is: ',k)
    #print('cluster centroids are: ',centroids)
    #print('cluster samples are',clusters)
    model.Kmeans_visualize(centroids, clusters, k)
if __name__ == "__main__":
    main()


import numpy as np

'''
4.2 손실함수
비용함수 cost function or 손실함수 loss functions
 신경망에서 학습이 제대로 이루어졌는지 평가하는 하나의 지표.
 비용함수의 값을 최소로 하는 가중치를 찾는것이 학습의 목표.
    1. 평균제곱오차 mean squared error - MSE
    2. 교차 엔트로피 오차 cross entropy error - CEE
'''

t = np.array([0, 0, 1, 0, 0, 0, 0, 0, 0, 0])  # 답 : 2
y1 = np.array([.1, .05, .6, .0, .05, .1, .0, .1, .0, .0])    # 예측 : 2 (확률 0.6)
y2 = np.array([.1, .05, .1, .0, .05, .1, .0, .6, .0, .0])    # 예측 : 7 (확률 0.6)

#####
# 4.2.1 평균 제곱 오차 MSE
# E = (1/2) * (sum( (y - e)**2 ))
#   (y = 신경망의 예측값, t = 실제라벨, y, t 모두 one-hot-encoding)
#
# ex) mnist
# y = [ 0.1,    0.05,   0.6,    0.0,    0.05,   0.1,    0.0,    0.1,    0.0,    0.0 ]
# t = [ 0,      0,      1,      0,      0,      0,      0,      0,      0,      0   ]
# y 는 신경망이 예측한 입력데이터가 0~9 까지 각 숫자일 확률
# t 는 실제 입력데이터의 라벨
# MSE 는 대응하는 y, t 값의 차를 제곱하여 모두 더한 후 2로 나눈 값
#####

def MeanSquaredError(y, t):
    return np.sum((y - t) ** 2)/2

# print(MeanSquaredError(y1, t))   # 정답 예측 - 0.0975
# print(MeanSquaredError(y2, t))   # 오답 예측 - 0.5975

    # 잘못된 예측을 하는 경우 MSE 값이 커지게된다.
    # 정확한 값을 예측할뿐만 아니라 더 높은 확률을 부여할수록 오차는 작아지게된다.

#####
# 4.2.2 교차 엔트로피 오차 CEE
# E = - sum(t * log(y))
# log 는 자연로그 ln, y 와 t 모두 one-hot-encoding
# 정답인 레이블만 제외하면 t 의 각 요소는 모두 0이 된다.
# 따라서 하나의 입력 데이터에 대해서 E = -log(예측확률), 정답일때의 출력이 전체 값을 정하게 된다.
# 로그 함수는 입력이 0에 가까울수록 급격하게 증가하므로 잘못된 예측을 하거나
# 예측이 맞아도 예측확률이 낮다면 급격하게 에러가 증가한다.
# 이상적이게 예측이 완벽하다면 ( 확률 : 1.0 ) 에러는 0이 된다.
#####

# def CrossEntropyError(y, t):
#     delta = 1e-7    # 로그함수에는 0 이 입력될 수 없음. y 에 0 값이 존재할 수 있으니 작은 값을 넣어줌.
#     return -np.sum(t * np.log(y + delta))

def CrossEntropyError(y, t):    # t 가 one-hot-encoding 일때
    delta = 1e-7
    if y.ndim == 1: # 벡터를 (1, 벡터길이) 의 매트릭스로 변한
        t = t.reshape(1, t.size)
        y = y.reshape(1, y.size)
    batch_size = y.shape[0] # 입력된 미니배치가 몇건의 데이터묶음인지 확인
    return -np.sum(t * np.log(y + delta)) / batch_size  # 미니배치의 평균 교차엔트로피 오차를 구한다.

def CrossEntropyError_label(y, t):
    delta = 1e-7
    if y.ndim == 1:
        t = t.reshape(1, t.size)
        y = y.reshape(1, y.size)
    batch_size = y.shape[0]
    return -np.sum(np.log(y[np.arange(batch_size), t])) / batch_size
        # y[np.arange(batch_size), t] :
        # np.arange(batch_size) 는 0 ~ (y 미니배치의 데이터 수-1) 범위의 수가 담긴 벡터 생성
        # t 는 정답 라벨이 담긴 벡터(one-hot-encoding 이 아니므로 숫자가 나열되있음)
        # y[벡터x, 벡터z] 는 y[(x1,z1), (x2,z2), (x3,z3), ...] 이고 이는
        # y=np.array([y(x1,z1), y(x2,z2), y(x3,z3), ...])
        # 벡터x, 벡터z 의 대응하는 원소들을 인덱스로 갖는 y의 요소들이 나열된 벡터를 얻게된다.
        # 정리하자면 y[np.arange(batch_size), t] 는 t 에 담긴 라벨에 해당하는 신경망의 출력(확률값)이 나열된 벡터

# print(CrossEntropyError(y1, t))
#     # 정답 예측 - 0.510825457099
# print(CrossEntropyError(y2, t))
#     # 오답 예측 - 2.30258409299
#     # 이 값은 오답에 높은 확률을 부여했기때문이 아닌 정답에 낮은 확률을 부여했기 때문에 계산되는 오차.

'''
비용함수, 손실함수를 사용하는 이유
 = 신경망의 분류결과와 실제라벨링을 비교하여 계산한 정확도를 신경망의 정확함을 나타내는 지표로 사용하지 않는 이유?

매개변수를 매우 조금씩 조정할때 정확도 (ex - 100 개의 입력 데이터중 32 개를 정확히 분류했다 = 정확도 : 32 %) 는 변하지 않음.
 매개변수를 매우 조금씩 바꾼다고 신경망의 분류결과가 크게 바뀌지 않으며
 바뀐다고 해도 정확도는 연속적인 값이 아닌 이산적인 값. 33 % -> 34 %.
 계단함수처럼 대부분의 구간에서 미분값이 0 이 된다.

손실함수은 매개변수를 매우 조금씩 조정할때마다 수치가 계속 바뀌며 이는 연속적인 실수값을 갖는다.

매개변수의 작은 변화에 따른 신경망의 정확도를 나타내는 지표의 변화를 포착하여 가중치를 개선해야하는데
 정확도는 계단함수처럼 매개변수의 작은 변화에도 반응이 거의 없다시피 하여 신경망의 개선이 힘들다.

'''


# -*- coding: utf-8 -*-
"""numpy.ipynb

Automatically generated by Colaboratory.

Original file is located at
    https://colab.research.google.com/drive/1GEdAiYS6cqOIaKMJVy4ca4A0Z1q-T-bk
"""

import numpy as np

"""Numpy to biblioteka służąca do operacji na macierzach """

a = np.arange(15).reshape(3, 5)
print(a)

print("a size is {}".format(a.shape))
print('\n')

b = a.reshape(5,3)
print(b)
print('\n')

zero_matrix = np.zeros((2,3))
print(zero_matrix)
print('\n')
one_matrix = np.ones((4,2))
print(one_matrix)

"""Podstawowe operacje na macierzach:"""

A = np.array( [[1,1],
               [0,1]] )
print("A")
print(A)
print('\n')

B = np.array( [[2,0],
               [3,4]] )

print("B")
print(B)
print('\n')

print('B\'')
print(B.T)
print('\n')

print("A * B")
print(A * B)
print('\n')

print("A x B")
print(A.dot(B))
print('\n')

print("B x A")
print(B.dot(A))
print('\n')

print("A - B")
print(A-B)
print('\n')

print("A + B")
print(A+B)
print('\n')

print("A + 3")
print(A+3)
print('\n')

print("B^2")
print(B*2)
print('\n')

"""### Zadanie

Korzystając z powyższych informacji  i mnożenia macieży skonstruować "tabliczkę mnożenia".
"""


import numpy as np

def dot_product(a, b):
    """Implement dot product between the two vectors: a and b.

    (optional): While you can solve this using for loops, we recommend
    that you look up `np.dot()` online and use that instead.

    Args:
        a: numpy array of shape (x, n)
        b: numpy array of shape (n, x)

    Returns:
        out: numpy array of shape (x, x) (scalar if x = 1)
    """
    out = None
    ### YOUR CODE HERE
    out = np.dot(a, b)
    ### END YOUR CODE
    return out

def complicated_matrix_function(M, a, b):
    """Implement (a.T * b) * (M * a.T).

    (optional): Use the `dot_product(a, b)` function you wrote above
    as a helper function.

    Args:
        M: numpy matrix of shape (x, n).
        a: numpy array of shape (1, n).
        b: numpy array of shape (n, 1).

    Returns:
        out: numpy matrix of shape (x, 1).
    """
    out = None
    ### YOUR CODE HERE
    out = dot_product(a.T, b) * dot_product(M, a)
    ### END YOUR CODE

    return out

def svd(M):
    """Implement Singular Value Decomposition.

    (optional): Look up `np.linalg` library online for a list of
    helper functions that you might find useful.

    Args:
        M: numpy matrix of shape (m, n)

    Returns:
        u: numpy array of shape (m, m).
        s: numpy array of shape (k).
        v: numpy array of shape (n, n).
    """
    u = None
    s = None
    v = None
    ### YOUR CODE HERE
    u, s, v = np.linalg.svd(M)
    ### END YOUR CODE

    return u, s, v

def get_singular_values(M, k):
    """Return top n singular values of matrix.

    (optional): Use the `svd(M)` function you wrote above
    as a helper function.

    Args:
        M: numpy matrix of shape (m, n).
        k: number of singular values to output.

    Returns:
        singular_values: array of shape (k)
    """
    singular_values = None
    ### YOUR CODE HERE
    u, s, v = svd(M)
    singular_values = s[:k]
    ### END YOUR CODE
    return singular_values

def eigen_decomp(M):
    """Implement eigenvalue decomposition.

    Args:
        matrix: numpy matrix of shape (m, n)

    Returns:
        w: numpy array of shape (m, m) such that the column v[:,i] is the eigenvector corresponding to the eigenvalue w[i].
        v: Matrix where every column is an eigenvector.
    """
    w = None
    v = None
    ### YOUR CODE HERE
    w, v = np.linalg.eig(M)
    ### END YOUR CODE
    return w, v

def get_eigen_values_and_vectors(M, k):
    """Return top k eigenvalues and eigenvectors of matrix M. By top k
    here we mean the eigenvalues with the top ABSOLUTE values.

    (optional): Use the `eigen_decomp(M)` function you wrote above
    as a helper function.

    Args:
        M: numpy matrix of shape (m, m).
        k: number of eigen values and respective vectors to return.

    Returns:
        eigenvalues: list of length k containing the top k eigenvalues
        eigenvectors: list of length k containing the top k eigenvectors
            of shape (m, 1)
    """
    eigenvalues = []
    eigenvectors = []
    ### YOUR CODE HERE
    w, v = eigen_decomp(M)

    eigenvalues = sorted(w, reverse=True)[:k]
    eigenvectors = [v[np.where(w == value)[0]] for value in eigenvalues]

    ### END YOUR CODE
    return eigenvalues, eigenvectors


"""
Content under Creative Commons Attribution license CC-BY 4.0,
code under MIT license (c)2018 Sergio Rojas (srojas@usb.ve)

http://en.wikipedia.org/wiki/MIT_License
http://creativecommons.org/licenses/by/4.0/

Created on april, 2018
Last Modified on: may 15, 2018

  This program finds the solution of the equation
     3/5 = 5.2x + 9 written as 0.6 = 5.2x + 9
"""
from sympy import symbols, Eq, solveset, N, nsimplify

x = symbols('x')

LHS = 0.6
RHS = 5.2*x + 9

# convert numerical values on each part of the equation to fractions
LHS = nsimplify(LHS)
RHS = nsimplify(RHS)

thesol = list( solveset( Eq(LHS, RHS), x) )
#print('The found solution is x =', thesol[0])

# Check the found solution by substitution
newLHS = LHS - RHS #rearrange the equation to read: LHS - RHS = 0
#print('newLHS =', newLHS)

newLHS = newLHS.subs(x, thesol[0])
if newLHS.simplify() == 0:
   print('x = {2} is solution of {0} = {1}'.format(LHS,RHS,thesol[0]))
   print('\t Which using decimals, is equivalent to saying that: ')
   print('x = {2} is solution of {0} = {1}'.format(N(LHS),N(RHS),N(thesol[0])))


# Copyright 2021 Peizhi Yan. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
from __future__ import absolute_import

import numpy as np

def mean_squared_loss(y_pred, y_targ):
    """mean squared error loss function"""
    #  (1/2) || y_pred - y_targ ||^2
    # || . || is the Euclidean norm
    return (1. / 2) * np.square(y_targ - y_pred)

def d_mean_squared_loss(y_pred, y_targ):
    """derivative of loss function w.r.t. y_targ"""
    return y_pred - y_targ

def binary_cross_entropy(y_pred, y_targ):
    """binary cross-entropy loss"""
    # y_targ and y_pred suppose to have shape [n,1]
    return -(np.multiply(y_targ, np.log(y_pred)) + np.multiply(1-y_targ, np.log(1-y_pred)))

def d_binary_cross_entropy(y_pred, y_targ):
    """derivative of binary cross-entropy loss"""
    return (y_pred - y_targ) / (y_pred * (1 - y_pred))


# ガンマ分布

# 利用するライブラリ
import numpy as np
from scipy.stats import gamma, norm, poisson # ガンマ分布, 1次元ガウス分布, ポアソン分布
import scipy.special as sp # ガンマ関数, 対数ガンマ関数
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

#%%

### 確率密度の計算

# パラメータを指定
a = 2.0
b = 2.0

# 確率変数の値を指定
lmd = 2.0

# 定義式により確率密度を計算
C = b**a / sp.gamma(a)
dens = C * lmd**(a - 1.0) * np.exp(-b * lmd)
print(dens)

# 対数をとった定義式により確率密度を計算
log_C = a * np.log(b) - sp.loggamma(a)
log_dens = log_C + (a - 1.0) * np.log(lmd) - b * lmd
dens = np.exp(log_dens)
print(dens, log_dens)

# ガンマ分布の関数により確率密度を計算
dens = gamma.pdf(x=lmd, a=a, scale=1.0 / b)
print(dens)

# ガンマ分布の対数をとった関数により確率密度を計算
log_dens = gamma.logpdf(x=lmd, a=a, scale=1.0 / b)
dens = np.exp(log_dens)
print(dens, log_dens)

#%%

### 統計量の計算

# パラメータを指定
a = 2.0
b = 2.0

# 計算式により平均を計算
E_lambda = a / b
print(E_lambda)

# 計算式により分散を計算
V_lambda = a / b**2
print(V_lambda)

# 計算式により最頻値を計算
mode_lambda = (a - 1.0) / b
print(mode_lambda)

# ガンマ分布の関数により平均を計算
E_lambda = gamma.mean(a=a, scale=1.0 / b)
print(E_lambda)

# ガンマ分布の関数により分散を計算
V_lambda = gamma.var(a=a, scale=1.0 / b)
print(V_lambda)

#%%

### 分布の可視化

## 分布の計算

# パラメータを指定
a = 2.0
b = 2.0

# 作図用のlambdaの点を作成
lambda_vals = np.linspace(start=0.0, stop=5.0, num=250)

# ガンマ分布を計算
density = gamma.pdf(x=lambda_vals, a=a, scale = 1.0 / b)

#%%

## 分布の作図

# ガンマ分布を作図
plt.figure(figsize=(12, 9)) # 図の設定
plt.plot(lambda_vals, density, color='#00A968') # 折れ線グラフ
plt.xlabel('$\lambda$') # x軸ラベル
plt.ylabel('density') # y軸ラベル
plt.suptitle('Gamma Distribution', fontsize=20) # 全体のタイトル
plt.title('$a=' + str(a) + ', b=' + str(b) + '$', loc='left') # タイトル
plt.grid() # グリッド線
plt.show() # 描画

#%%

# 統計量を計算
E_lmd = a / b
s_lmd = np.sqrt(a / b**2)
mode_lmd = (a - 1.0) / b

# 統計量を重ねたガンマ分布を作図
plt.figure(figsize=(12, 9)) # 図の設定
plt.plot(lambda_vals, density, color='#00A968') # 分布
plt.vlines(x=E_lmd, ymin=0.0, ymax=np.max(density), color='orange', linestyle='--', label='$E[\lambda]$') # 平均
plt.vlines(x=E_lmd - s_lmd, ymin=0.0, ymax=np.max(density), color='orange', linestyle=':', label='$E[\lambda] - \\sqrt{V[\lambda]}$') # 平均 - 標準偏差
plt.vlines(x=E_lmd + s_lmd, ymin=0.0, ymax=np.max(density), color='orange', linestyle=':', label='$E[\lambda] + \\sqrt{V[\lambda]}$') # 平均 + 標準偏差
plt.vlines(x=mode_lmd, ymin=0.0, ymax=np.max(density), color='chocolate', linestyle='--', label='$mode[\lambda]$') # 最頻値
plt.xlabel('$\lambda$') # x軸ラベル
plt.ylabel('density') # y軸ラベル
plt.suptitle('Gamma Distribution', fontsize=20) # 全体のタイトル
plt.title('$a=' + str(a) + ', b=' + str(b) + '$', loc='left') # タイトル
plt.legend() # 凡例
plt.grid() # グリッド線
plt.show() # 描画

#%%

### パラメータと分布の形状の関係

## a, bの影響

# パラメータとして利用する値を指定
a_vals = np.arange(start=0.1, stop=10.1, step=0.1)
b_vals = np.arange(start=0.1, stop=10.1, step=0.1)
print(len(a_vals)) # フレーム数

# 固定するパラメータを指定
a = 2.0
b = 2.0

# 作図用のlambdaの点を作成
lambda_vals = np.linspace(start=0.0, stop=5.0, num=250)

# y軸(確率密度)の最大値を設定
dens_max = 4.0

# 図を初期化
fig = plt.figure(figsize=(12, 9)) # 図の設定
fig.suptitle('Gamma Distribution', fontsize=20) # 全体のタイトル

# 作図処理を関数として定義
def update(i):
    # 前フレームのグラフを初期化
    plt.cla()

    # i番目のパラメータを取得
    a = a_vals[i]
    #b = b_vals[i]

    # ガンマ分布を計算
    density = gamma.pdf(x=lambda_vals, a=a, scale = 1.0 / b)

    # ガンマ分布を作図
    plt.plot(lambda_vals, density, color='#00A968') # 折れ線グラフ
    plt.xlabel('$\lambda$') # x軸ラベル
    plt.ylabel('density') # y軸ラベル
    plt.title('$a=' + str(np.round(a, 1)) + ', b=' + str(np.round(b, 1)) + '$', loc='left') # タイトル
    plt.grid() # グリッド線
    plt.ylim(ymin=-0.1, ymax=dens_max) # y軸の表示範囲

# gif画像を作成
anime_dens = FuncAnimation(fig, update, frames=len(a_vals), interval=100)

# gif画像を保存
anime_dens.save('ProbabilityDistribution/Gamma_dens.gif')

#%%

### 乱数の生成

## 乱数の生成

# パラメータを指定
a = 2.0
b = 2.0

# データ数(サンプルサイズ)を指定
N = 1000

# ガンマ分布に従う乱数を生成
lambda_n = np.random.gamma(shape=a, scale=1.0 / b, size=N)

# 作図用のlambdaの点を作成
lambda_vals = np.linspace(start=0.0, stop=np.max(lambda_n) + 1.0, num=250)

# ガンマ分布を計算
density = gamma.pdf(x=lambda_vals, a=a, scale=1.0 / b)

#%%

## 乱数の可視化

# サンプルのヒストグラム(頻度)を作成
plt.figure(figsize=(12, 9)) # 図の設定
plt.hist(x=lambda_n, bins=30, range=(lambda_vals.min(), lambda_vals.max()), color='#00A968') # ヒストグラム
plt.xlabel('$\lambda$') # x軸ラベル
plt.ylabel('density') # y軸ラベル
plt.suptitle('Gamma Distribution', fontsize=20) # 全体のタイトル
plt.title('$a=' + str(a) + ', b=' + str(b) + ', N=' + str(N) + '$', loc='left') # タイトル
plt.grid() # グリッド線
plt.ylim(ymin=-0.01) # y軸の表示範囲
plt.show() # 描画

# サンプルのヒストグラム(密度)を作成
plt.figure(figsize=(12, 9)) # 図の設定
plt.hist(x=lambda_n, bins=30, range=(lambda_vals.min(), lambda_vals.max()), density=True, color='#00A968') # ヒストグラム
plt.plot(lambda_vals, density, color='green', linestyle='--') # 元の分布
plt.xlabel('$\lambda$') # x軸ラベル
plt.ylabel('density') # y軸ラベル
plt.suptitle('Gamma Distribution', fontsize=20) # 全体のタイトル
plt.title('$a=' + str(a) + ', b=' + str(b) + ', N=' + str(N) + '$', loc='left') # タイトル
plt.grid() # グリッド線
plt.ylim(ymin=-0.01) # y軸の表示範囲
plt.show() # 描画

#%%

## アニメーションによる可視化:(頻度)

# フレーム数を指定
N_frame = 100

# 図を初期化
fig = plt.figure(figsize=(12, 9)) # 図の設定
fig.suptitle('Gaussian Distribution', fontsize=20) # 全体のタイトル

# y軸(頻度)の最大値を設定
freq_max = np.max(
    np.histogram(a=lambda_n[:N_frame], bins=30, range=(lambda_vals.min(), lambda_vals.max()))[0],
) + 1.0

# 作図処理を関数として定義
def update(n):
    # 前フレームのグラフを初期化
    plt.cla()

    # サンプルのヒストグラムを作成
    plt.hist(x=lambda_n[:(n+1)], bins=30, range=(lambda_vals.min(), lambda_vals.max()), color='#00A968', zorder=1) # ヒストグラム
    plt.scatter(x=lambda_n[n], y=0.0, s=100, color='orange', zorder=2) # サンプル
    plt.xlabel('$\lambda$') # x軸ラベル
    plt.ylabel('frequency') # y軸ラベル
    plt.suptitle('Gamma Distribution', fontsize=20) # 全体のタイトル
    plt.title('$a=' + str(a) + ', b=' + str(b) + ', N=' + str(n + 1) + '$', loc='left') # タイトル
    plt.grid() # グリッド線
    plt.ylim(ymin=-0.5, ymax=freq_max) # y軸の表示範囲

# gif画像を作成
anime_freq = FuncAnimation(fig, update, frames=N_frame, interval=100)

# gif画像を保存
anime_freq.save('ProbabilityDistribution/Gamma_freq.gif')

#%%

## アニメーションによる可視化:(密度)

# フレーム数を指定
N_frame = 100

# 図を初期化
fig = plt.figure(figsize=(12, 9)) # 図の設定
fig.suptitle('Gaussian Distribution', fontsize=20) # 全体のタイトル

# y軸(確率密度)の最大値を設定
dens_max = np.max(
    np.hstack([
        np.histogram(a=lambda_n[:N_frame], bins=30, range=(lambda_vals.min(), lambda_vals.max()), density=True)[0],
        density
    ])
) + 0.1

# 作図処理を関数として定義
def update(n):
    # 前フレームのグラフを初期化
    plt.cla()

    # サンプルのヒストグラムを作成
    plt.hist(x=lambda_n[:(n+1)], bins=30, range=(lambda_vals.min(), lambda_vals.max()), density=True, color='#00A968', zorder=1) # ヒストグラム
    plt.plot(lambda_vals, density, color='green', linestyle='--', zorder=2) # 元の分布
    plt.scatter(x=lambda_n[n], y=0.0, s=100, color='orange', zorder=3) # サンプル
    plt.xlabel('$\lambda$') # x軸ラベル
    plt.ylabel('density') # y軸ラベル
    plt.suptitle('Gamma Distribution', fontsize=20) # 全体のタイトル
    plt.title('$a=' + str(a) + ', b=' + str(b) + ', N=' + str(n + 1) + '$', loc='left') # タイトル
    plt.grid() # グリッド線
    plt.ylim(ymin=-0.01, ymax=dens_max) # y軸の表示範囲

# gif画像を作成
anime_freq = FuncAnimation(fig, update, frames=N_frame, interval=100)

# gif画像を保存
anime_freq.save('ProbabilityDistribution/Gamma_prop.gif')

#%%

### 分布の生成

## パラメータの生成

# パラメータを指定
a = 5.0
b = 2.0

# サンプルサイズを指定
N = 10

# ガウス分布・ポアソン分布のパラメータを生成
lambda_n = np.random.gamma(shape=a, scale=1.0 / b, size=N)

#%%

## 分布の作図:(1次元ガウス分布)

# 平均パラメータを指定
mu = 0.0

# 精度パラメータの期待値を計算
E_lambda = a / b

# 標準偏差の期待値を計算
E_sigma = np.sqrt(1.0 / E_lambda)

# 作図用のxの点を作成
x_vals = np.linspace(start=mu - E_sigma*4.0, stop=mu + E_sigma*4.0, num=250)

# 精度パラメータの期待値による1次元ガウス分布を計算
E_dens = norm.pdf(x=x_vals, loc=mu, scale=E_sigma)

# サンプルによる分布を作図
plt.figure(figsize=(12, 9)) # 図の設定
plt.plot(x_vals, E_dens, color='blue', linestyle='--', label='$E[\lambda]=' + str(np.round(E_lambda, 2)) + '$') # 期待値による分布
for n in range(N):
    tmp_dens = norm.pdf(x=x_vals, loc=mu, scale=np.sqrt(1.0 / lambda_n[n]))
    plt.plot(x_vals, tmp_dens, alpha=0.5, label='$\lambda=' + str(np.round(lambda_n[n], 2)) + '$') # サンプルによる分布
plt.xlabel('x') # x軸ラベル
plt.ylabel('density') # y軸ラベル
plt.suptitle('Gaussian Distribution', fontsize=20) # 全体のタイトル
plt.title('$a=' + str(a) + ', b=' + str(b) + '$', loc='left') # タイトル
plt.legend() # 凡例
plt.grid() # グリッド線
plt.show() # 描画

#%%

## 分布の作図:(ポアソン分布)

# パラメータの期待値を計算
E_lambda = a / b

# 作図用のxの点を作成
x_vals = np.arange(np.ceil(E_lambda) * 4.0)

# パラメータの期待値によるポアソン分布を計算
E_prob = poisson.pmf(k=x_vals, mu=E_lambda)

# サンプルによる分布を作図
plt.figure(figsize=(12, 9)) # 図の設定
plt.step(x=x_vals, y=E_prob, where='mid',
         color='blue', linestyle='--', label='$E[\lambda]=' + str(np.round(E_lambda, 2)) + '$') # 期待値による分布
for n in range(N):
    tmp_prob = poisson.pmf(k=x_vals, mu=lambda_n[n])
    plt.step(x=x_vals, y=tmp_prob, where='mid',
             alpha=0.5, label='$\lambda=' + str(np.round(lambda_n[n], 2)) + '$') # サンプルによる分布
plt.xlabel('x') # x軸ラベル
plt.ylabel('density') # y軸ラベル
plt.suptitle('Poisson Distribution', fontsize=20) # 全体のタイトル
plt.title('$a=' + str(a) + ', b=' + str(b) + '$', loc='left') # タイトル
plt.legend() # 凡例
plt.grid() # グリッド線
plt.show() # 描画

#%%


import numpy as np
import sys
import matplotlib.pyplot as plt
from sklearn.metrics import accuracy_score, precision_score, recall_score, roc_auc_score

class LogisticRegression:

    def __init__(self, learning_rate=0.01, num_iterations=1000):
        """
        Initialize the class

        :param learning_rate: learning rate of gradient descent
        :param num_iterations: number of iterations of gradient descent
        :return None
        """
        self.learning_rate = learning_rate
        self.prev_iterations = 0
        self.num_iterations = num_iterations
        self.params = {}
        self.costs = {}
        self.able_to_restart = False

    def sigmoid(self, z):
        """
        Compute the sigmoid of z

        :param z: A scalar or numpy array of any size
        :return s: sigmoid (z)
        """
        s = 1. / (1. + np.exp(-z))

        return s

    def initialize_weights(self, dim):
        """
        This function initialize the weight vector, w, and the bias scalar, b with zeros

        :param dim: size of w vector, i.e., = nx = number of features in a data point
        :return: None
        """
        w = np.zeros((dim, 1))
        b = 0.

        self.params = {"w": w, "b": b}

    def propagate(self, w, b, X, Y):
        """
        Calculate the cost function, J, in a forward propagate step and the gradient of J over w and b in a back
        propagate step.

        :param w: weights, size = (nx, 1)
        :param b: bias, scalar
        :param X: data matrix, size = (nx, m), where m is the total number of sample data points
        :param Y: label vector, size = (1, m), the elements are binary
        :return grads: a dictionary, {"dw: dw, "db":db}, that contains the gradient of the loss
                       with respect to w and b
        :return cost: average loss of all samples
        """
        # forward propagate
        m = X.shape[1]
        Z = np.dot(w.T, X) + b
        A = self.sigmoid(Z)
        loss = Y * np.log(A) + (1. - Y) * np.log(1. - A)
        cost = (-1. / m) * np.sum(loss)

        # backward propagate
        dZ = A - Y
        dw = 1. / m * np.dot(X, dZ.T)
        db = 1. / m * np.sum(dZ)
        grads = {"dw": dw, "db": db}

        return grads, cost

    def optimize(self, X, Y, print_cost=True):
        """
        Optimize self.params, i.e., w and b, using gradient descent algorithm

        :param X: data X, size = (nx, m)
        :param Y: data labels, size = (1, m)
        :param print_cost: print total cost during iterations if true
        """
        w = self.params["w"]
        b = self.params["b"]
        for i in range(self.prev_iterations, self.num_iterations):
            grads, cost = self.propagate(w, b, X, Y)
            dw = grads["dw"]
            db = grads["db"]
            w -= self.learning_rate * dw
            b -= self.learning_rate * db
            if i % 100 == 0:
                self.costs[i] = cost
                if print_cost:
                    print("Iteration {0}, cost = {1}".format(i, cost))

        # renew self.params using updated w and b
        self.params["w"] = w
        self.params["b"] = b

    def train(self, X_train, Y_train, restart=False, num_iterations=None, print_cost=True):
        """
        Train the model from zero coefficients and bias

        :param X_train: training data X, shape = (nx, m)
        :param Y_train: training data Y, shape = (1, m)
        :param restart: if true, the model will train from lasted saved state, i.e., self.params and self.costs
        :param num_iterations: only useful when restart is True
        :param print_cost: print total cost during iterations if true
        :return: None
        """
        if restart:
            if not self.able_to_restart:
                print("Error: cannot restart from a non-trained model. Exiting ...")
                sys.exit(-1)
            if not num_iterations:
                num_iterations = self.num_iterations
            self.prev_iterations = self.num_iterations
            self.num_iterations += num_iterations
        else:
            self.initialize_weights(X_train.shape[0])

        self.optimize(X_train, Y_train, print_cost)
        self.able_to_restart = True

    def predict(self, X_test, Y_test=None, threshold=0.5, print_performance=True):
        """

        :param X_test: test data X, shape = (nx, m_test)
        :param Y_test: test data Y, shape = (1, m_test)
        :param threshold: the probability threshold to transform probabilities to a binary outputs
        :param print_performance: print the performance of the classification model if True
        :return Y_predictions: a numpy array containing binary predictions for the data points in X_test
        """
        # check X_test data shape
        if self.params["w"].shape[0] != X_test.shape[0]:
            print("Error: shape[0] of test data, i.e., number of features is not equal to that of the training data. "
                  "Exiting ...")
            sys.exit(-1)

        m = X_test.shape[1]
        Z_predictions = np.dot(self.params["w"].T, X_test) + self.params["b"]
        Y_predictions_prob = self.sigmoid(Z_predictions)
        Y_predictions = (Y_predictions_prob >= threshold) * 1.0

        assert(Y_predictions_prob.shape == (1, m))
        assert(Y_predictions.shape == (1, m))

        if print_performance and Y_test is not None:
            self.print_model_performance(np.squeeze(Y_test), np.squeeze(Y_predictions), np.squeeze(Y_predictions_prob))

        return Y_predictions, Y_predictions_prob

    def print_loss_curve(self):
        """
        Draw the curve of cost as a function of iterations (per hundreds)

        :return: None
        """
        plt.figure()
        plt.plot(self.costs.keys(), self.costs.values())
        plt.show()

    @staticmethod
    def print_model_performance(Y_test, Y_test_pred, Y_test_pred_prob):
        """
        Print out the model performance

        :param Y_test: Y of test data, shape = (1, m)
        :param Y_test_pred: binary prediction test data, shape = (1, m)
        :param Y_test_pred_prob: prediction probability of test data, shape = (1, m)
        :return: None
        """
        print("-"*40 + "Accuracy" + "-"*40)
        print(accuracy_score(Y_test, Y_test_pred))

        print("-" * 40 + "Recall of positive class" + "-" * 40)
        print(recall_score(Y_test, Y_test_pred))

        print("-" * 40 + "Precision of positive class" + "-" * 40)
        print(precision_score(Y_test, Y_test_pred))

        print("-" * 40 + "ROC AUC of Score" + "-" * 40)
        print(roc_auc_score(Y_test, Y_test_pred_prob))


"""Module containing implementation of linear models.

"""

from math import exp

import numpy as np

from .regression_model import RegressionModel
from .math import sigmoid

class LinearRegression(RegressionModel):
    """Class implementing simple weighted _hypothesis and linearly
    fitting the data.

    """

    def _hypothesis(self, X, params):
        """Method applying sum of products between X (the input data) and
        the parameters of the model.

        """
        return X.dot(params)

class LogisticRegression(RegressionModel):
    """Class implementing simple weighted _hypothesis and applying the sigmoid
    function over the result resulting in linear binary classification.

    """

    def __init__(self, *args):
        super().__init__(*args)

        # Setting the decision boundary threshold
        self.boundary_threshold = 0.5

    def _hypothesis(self, X, params):
        """Method applying sum of products between X (the input data) and
        the parameters of the model and then applying the sigmoid function.

        """
        return sigmoid(X.dot(params))

    def fit(self, *args):
        """Method running the fitter and then setting the
        threshold predict function.

        """
        predict = super(LogisticRegression, self).fit(*args)
        self.predict = predict
        return lambda inp, params=self.params: 1 if predict(
            inp, params) > self.boundary_threshold else 0


import numpy as np
import numerical_analysis as na

import datetime

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

def df_dx(x):
    return 2 * x - 5

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

bezier = na.splines.Bezier(cp)
bezier.plot(0.01)

comp_bezier = na.splines.CompositeQuadraticBezier(cp)
comp_bezier.plot(0.01)

xa = 0.
xb = 10.
n = 10000
level = 10

a = datetime.datetime.now()

print(na.integration.trapezoid(f, xa, xb, n))
print(na.integration.simpson1_3(f, xa, xb, n))
print(na.integration.simpson3_8(f, xa, xb, n))
print(na.integration.romberg(f, xa, xb, level))
print(na.integration.gauss_legendre(f, xa, xb, 8))

b = datetime.datetime.now()
c = b - a
print("Time taken by function: {} milliseconds".format(1e3 * c.total_seconds()))

print(na.root_finding.secant(f, 0, 1))
print(na.root_finding.newton_raphson(f, df_dx, 0))


#!/usr/bin/env python3

import tensorflow as tf
from tensorflow.python import debug as tf_debug
import numpy as np
import argparse
import os.path as path

def build_gregory_leibniz_series():
    """
    Calculate Pi using Gregory–Leibniz series
            oo
            __        n
            \     (-1)
    pi = 4  /   -------
            --   2n + 1
            n=0
    """
    with tf.name_scope('gls'):
        n = tf.Variable(0, name='n')
        pi = tf.Variable(0, name='pi', dtype=tf.float64)
        gregory_leibniz = tf.multiply(np.float64(4), tf.divide(tf.pow(-1, n), tf.add(tf.multiply(2, n), 1)))

        update_pi = tf.assign_add(pi, gregory_leibniz)

        with tf.control_dependencies([update_pi]):
            update_n = tf.assign_add(n, 1)

        tf.summary.scalar('gls_pi', update_pi)
        merged = tf.summary.merge_all()

        return 'Gregory Leibniz series', [update_pi, update_n, merged]

def build_bbp():
    """
    Calculate Pi using BBP digit extraction
            oo
            __
            \     1     /    4            2            1             1       \
    pi =    /   ----   |  --------  -  --------  -  --------  -  ---------   |
            --   16^k   \  8k + 1       8k + 4       8k + 5        8k + 6    /
            k=0
    """
    with tf.name_scope('bbp'):
        k = tf.Variable(0, name='k', dtype=tf.float64)
        pi = tf.Variable(0, name='pi', dtype=tf.float64)
        first_part = tf.divide(np.float64(4), tf.add(tf.multiply(np.float64(8), k), 1))
        second_part = tf.divide(np.float64(2), tf.add(tf.multiply(np.float64(8), k), 4))
        third_part = tf.divide(np.float64(1), tf.add(tf.multiply(np.float64(8), k), 5))
        fourth_part = tf.divide(np.float64(1), tf.add(tf.multiply(np.float64(8), k), 6))
        subtract_result = tf.subtract(tf.subtract(tf.subtract(first_part, second_part), third_part), fourth_part)
        bailey = tf.multiply(tf.pow(tf.reciprocal(np.float64(16)), k), subtract_result)

        update_pi = tf.assign_add(pi, bailey)
        with tf.control_dependencies([update_pi]):
            update_k = tf.assign_add(k, 1)
        tf.summary.scalar('bbp_pi', update_pi)
        merged = tf.summary.merge_all()

        return 'BBP digit extraction', [update_pi, update_k, merged]

def run_pi_calculation(description, graphs, iteration=15000, print_every=1000, debug=False, log_path=None):
    with tf.Session() as sess:
        train_writer = None
        if log_path:
            train_writer = tf.summary.FileWriter(path.join(log_path, description.split(' ')[0]), sess.graph)
        if debug:
            sess = tf_debug.LocalCLIDebugWrapperSession(sess)
        sess.run(tf.global_variables_initializer())
        print('Running pi approximation algorithm {}:'.format(description))
        for i in range(1, iteration+1):
            result, _, summary = sess.run(graphs)
            if train_writer:
                train_writer.add_summary(summary, i)
            if i % print_every == 0:
                print('iteration {:>10}:  Pi = {}'.format(i, result))
        print()
        if train_writer:
            train_writer.close()

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("-d", "--debug", help="Enables TensorFlow debugger.", action="store_true")
    parser.add_argument("-l", "--logPath", help="Saves TensorBoard logs at path.")
    args = parser.parse_args()

    gls = build_gregory_leibniz_series()
    bbp = build_bbp()

    run_pi_calculation(gls[0], gls[1], debug=args.debug, log_path=args.logPath)
    run_pi_calculation(bbp[0], bbp[1], iteration=15, print_every=1, debug=args.debug, log_path=args.logPath)


import numpy as np
import pandas as pd

def numpy_dot():
    '''
    Imagine a point system in which each country is awarded 4 points for each
    gold medal,  2 points for each silver medal, and one point for each
    bronze medal.

    Using the numpy.dot function, create a new dataframe called
    'olympic_points_df' that includes:
        a) a column called 'country_name' with the country name
        b) a column called 'points' with the total number of points the country
           earned at the Sochi olympics.

    You do not need to call the function in your code when running it in the
    browser - the grader will do that automatically when you submit or test it.
    '''

    countries = [
        'Russian Fed.', 'Norway', 'Canada', 'United States', 'Netherlands',
        'Germany', 'Switzerland', 'Belarus', 'Austria', 'France', 'Poland',
        'China', 'Korea', 'Sweden', 'Czech Republic', 'Slovenia', 'Japan',
        'Finland', 'Great Britain', 'Ukraine', 'Slovakia', 'Italy', 'Latvia',
        'Australia', 'Croatia', 'Kazakhstan'
    ]

    gold = [
        13, 11, 10, 9, 8, 8, 6, 5, 4, 4, 4, 3, 3, 2, 2, 2, 1, 1, 1, 1, 1, 0, 0,
        0, 0, 0
    ]
    silver = [
        11, 5, 10, 7, 7, 6, 3, 0, 8, 4, 1, 4, 3, 7, 4, 2, 4, 3, 1, 0, 0, 2, 2,
        2, 1, 0
    ]
    bronze = [
        9, 10, 5, 12, 9, 5, 2, 1, 5, 7, 1, 2, 2, 6, 2, 4, 3, 1, 2, 1, 0, 6, 2,
        1, 0, 1
    ]

    # YOUR CODE HERE
    olympic_medal_counts = {
        'country_name':
        pd.Series(countries),
        'points':
        pd.Series(
            np.dot(np.array([gold, silver, bronze]).T, np.array([4, 2, 1])))
    }

    olympic_points_df = pd.DataFrame(olympic_medal_counts)
    return olympic_points_df

res = numpy_dot()
print res


#
#	Solved Problems in Geostatistics
#
# ------------------------------------------------
#	Script for lesson 2.3
#	"Standartization & probability intervals"
# ------------------------------------------------

from numpy import *
from scipy import *

import sys
sys.path.append(r'../shared')

from gaussian_cdf import *
from statistics import *
from gslib import *

# ---------------------------------------------------
#	Problem:
#
#	Consider 10 data that are available over 2D area of interest, that is nominally 50 x 50 distance units square. Assume, that the data is known to be a normal distribution with mean of 12.0 and a variance of 16.0
#
# ----------------------------------------------------

print "----------------------------------------------------"
print "Loading data & initializing..."

# Loading sample data from file
x = 10
y = 4
dict = load_gslib_file("samples.txt")

# Mean and variance
mean = 12.0
var = 16.0

# Standart deviation
st_dev = sqrt(var)

print "Done."
print "----------------------------------------------------"

data_points_variable = dict["Variable"]
data_points_easting = dict["Easting"]
data_points_northing = dict["Northing"]
# ----------------------------------------------------
#	(1) Calculate 95% centered probability interval for the mean, that is, the 0.025 quantile and the 0.975 quantile of the theoretical distribution for the mean.
# ----------------------------------------------------

# Probability interval
p = 0.95

# Calculating quantiles
quantiles = zeros( (2), order = 'F', dtype = float32)
# 1: 0.025 quantile
quantiles[0] = (1-p)/2
# 2: 0.975 quantile
quantiles[1] = (1+p)/2

# Retrieving probability intervals
probs = zeros( (2), order = 'F', dtype = float32)
probs[0] = inverse_normal_score(quantiles[0], mean, var)
probs[1] = inverse_normal_score(quantiles[1], mean, var)

# Printing result on screen
print "Probability interval is: [", probs[0], ",", probs[1], "]"
print "----------------------------------------------------"

# ----------------------------------------------------
#	(2) Transform the data to be standart normal and recalculate the 95% probability interval.
# ----------------------------------------------------

# Transforming data to standart normal form
for i in xrange(x):
	data_points_variable = stand(data_points_variable, mean, st_dev)

# Retrieving probability intervals of transformed data
probs_trans = zeros( (2), order = 'F', dtype = float32)
probs_trans[0] = inverse_normal_score(quantiles[0])
probs_trans[1] = inverse_normal_score(quantiles[1])

# Printing result on screen
print "Probability interval for standartized normal data is: [", probs_trans[0], ",", probs_trans[1], "]"
print "----------------------------------------------------"

# ----------------------------------------------------
#	(3) Consider now that the data are spatially dependent, and this dependece is characterized by a correlation function specified as C(h) = exp(-0.2h) where h is calculated as the distance between the samples. Note that this function applies to the standart variable, that is, C(0) = 1.0 (the variance of a standart variable). These values would have to be multiplied by 16 to establish the actual covariance values. Calculate variance of the mean in this case and calculate 95% centered probability interval.
# ----------------------------------------------------

# Function for calculating covariance with model C(h) = exp(-0.2h)
def calc_cov(h):
	cov_h = exp(-0.2*h)
	return cov_h

# Calculating covariance matrix
cov_h = zeros( (x,x), order = 'F', dtype = float32)

for i in xrange(x):
	for j in xrange(x):

		# First point coordinates
		x1 = data_points_easting[i]
		y1 = data_points_northing[i]

		# Second point coordinates
		x2 = data_points_easting[j]
		y2 = data_points_northing[j]

		# Calculating distance
		distance = calc_distance(x1,y1,x2,y2)

		# Calculating covariance
		cov_h [i,j] = calc_cov(distance)

# 1. Standartized variable

# Calculating variance of the mean
var_cov = cov_h.mean()
print "Variance of standartized data in presense of corellation is: ", var_cov

# Retrieving probability intervals for corellated data
probs_cov = zeros( (2), order = 'F', dtype = float32)
probs_cov[0] = inverse_normal_score(quantiles[0], var_cov, var)
probs_cov[1] = inverse_normal_score(quantiles[1], var_cov, var)

print "Probability interval for standartized data in presense of corellation is: [", probs_cov[0], ",", probs_cov[1], "]"
print "----------------------------------------------------"

# 2. Original variable

# Calculating variance of the mean
var_cov_orig = cov_h.mean() * var
print "Variance of data in presense of corellation is: ", var_cov_orig

# Retrieving probability intervals for corellated data
probs_cov = zeros( (2), order = 'F', dtype = float32)
probs_cov[0] = inverse_normal_score(quantiles[0], var_cov_orig, var)
probs_cov[1] = inverse_normal_score(quantiles[1], var_cov_orig, var)

print "Probability interval for data in presense of corellation is: [", probs_cov[0], ",", probs_cov[1], "]"

# Print covariance matrix
# print "Covariance matrix: \n", cov_h

src/sample-scripts/solved_problems/2.3. Standartization and Probability Intervals/problem_2.3.py


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

r = 0.08
init_s = 100
sigma_s = 0.3
T = 1
K = 100
barrier = 150
init_f = 200
sigma_f = 0.25
debt = 175
recovery = 0.25
corr = 0.2
corr_mat = np.array([[1, corr], [corr, 1]])
L = np.linalg.cholesky(corr_mat)
n_steps = 12  # monthly
dT = 1/n_steps
S = np.array([[init_s], [init_f]])
sigma = np.array([[sigma_s], [sigma_f]])

def a_path(S, r, sigma, Z, dT):
    return S*np.exp(np.cumsum((r-0.5*sigma*sigma)*dT+sigma*np.sqrt(dT)*Z, 1))

def UpOutCall(p, K, L, r, T):
    if max(p) > L:
        return 0
    else:
        return max(0, p[-1]-K)*np.exp(-r*T)

np.random.seed(10)
opt_est = [None]*50
opt_std = [None]*50
d_opt_est = [None]*50
d_opt_std = [None]*50
cva_est = [None]*50
cva_std = [None]*50
for i in range(1, 51):
    p_array = np.zeros(1000*i)
    l_array = np.zeros(1000*i)
    for j in range(1000*i):
        Z = np.matmul(L, norm.rvs(size=(2, 12)))
        p_path = a_path(S, r, sigma, Z, dT)
        s_path = p_path[0]
        f_path = p_path[1]
        p_array[j] = UpOutCall(s_path, K, barrier, r, T)
        l_array[j] = np.exp(-r*T)*(1-recovery)*(f_path[-1] < debt)*p_array[j]
    opt_est[i-1] = p_array.mean()
    opt_std[i-1] = p_array.std()/np.sqrt(1000*i)
    cva_est[i-1] = l_array.mean()
    cva_std[i-1] = l_array.std()/np.sqrt(1000*i)
    d_opt_est[i-1] = opt_est[i-1]-cva_est[i-1]
    d_opt_std[i-1] = np.std(p_array-l_array)/np.sqrt(1000*i)

plt.plot(opt_est, '.')
plt.plot(opt_est+3*np.array(opt_std))
plt.plot(opt_est-3*np.array(opt_std))
plt.ylabel('Price')
plt.xlabel('N*1000 Sample Size')
plt.title('Option Price (Default Free)')
plt.show()
plt.plot(cva_est, '.')
plt.plot(cva_est+3*np.array(cva_std))
plt.plot(cva_est-3*np.array(cva_std))
plt.ylabel('Price')
plt.xlabel('N*1000 Sample Size')
plt.title('Credit Valuation Adjustment')
plt.show()
plt.plot(d_opt_est, '.')
plt.plot(d_opt_est+3*np.array(d_opt_std))
plt.plot(d_opt_est-3*np.array(d_opt_std))
plt.ylabel('Price')
plt.xlabel('N*1000 Sample Size')
plt.title("Option Price with CVA")
plt.show()


import numpy as np
import pandas as pd

def PCA_from_SVD(A):
    """
    Función para PCA a partir de la SVD de numpy
    params: A			matriz de datos
            num_componentes 	número de componentes deseados

    return: valores_singulares	Los valores singulares de la descomposición SVD
	    componentes		Los coeficientes para calcular los componentes principales
	    Z			Los datos transformados (componentes principales)
	    varianza_explicada	La varianza explicada por cada componente principal
    """

    # Centrar los datos
    A = np.array(A) # convertir los datos a un numpy array por si vienen de un DataFrame
    A_centered = A - A.mean(axis=0)

    # Calcular SVD
    U, S, Vt = np.linalg.svd(A_centered, full_matrices=False)

    # Los valores singulares
    valores_singulares = S

    # Los componentes (coeficientes)
    componentes = ((Vt))

    # Los datos transformados (componentes principales)
    Z = A_centered@np.transpose(Vt)

    # La varianza explicada
    varianza_explicada = S**2/np.sum(S**2)

    # Calcula número de componentes de manera automatica de acuerdo a la variana explicada
    # Threshold de 60%
    n = A.shape[1] #numero de columnas
    varianza_acumulada = varianza_explicada.cumsum()
    conteo = (varianza_acumulada)  <  0.8
    num_componentes = conteo.sum() + 1

    # regresar 4 objetos
    return valores_singulares[:num_componentes], componentes[:num_componentes].T, Z[:,:num_componentes], varianza_explicada[:num_componentes]


import math
import numpy as np

def sigmoid_activation(z):
    z = np.clip(z,-60.0,60.0)
    return 1.0 / (1.0 + np.exp(-z))

def tanh_activation(z):
    z = np.clip(z,-60.0,60.0)
    return np.tanh(z)

def sin_activation(z):
    z = np.clip(z,-60.0,60.0)
    return np.sin(z)

def gauss_activation(z):
    z = np.clip(z,-60.0,60.0)
    return np.exp(-0.5 * z**2) / np.sqrt(2 * math.pi)

def relu_activation(z):
    return z if z > 0.0 else 0

def identity_activation(z):
    return z

def clamped_activation(z):
    return np.clip(z,-1.0,1.0)

def inv_activation(z):
    if z == 0:
        return 0.0

    return 1.0 / z

def log_activation(z):
    z = np.clip(z,1e-7,1.0e99)
    return np.log(z)

def exp_activation(z):
    z = np.clip(z,-60.0,60.0)
    return np.exp(z)

def abs_activation(z):
    return np.abs(z)

def hat_activation(z):
    return np.clip(1.0-np.abs(z),0.0,1.5)

def square_activation(z):
    return z ** 2

def cube_activation(z):
    return z ** 3

activations = {'sigmoid':sigmoid_activation,
               'tanh': tanh_activation,
               'sin': sin_activation,
               'gauss': gauss_activation,
               'relu': relu_activation,
               'identity': identity_activation,
               'clamped': clamped_activation,
               'inv': inv_activation,
               'log': log_activation,
               'exp': exp_activation,
               'abs': abs_activation,
               'hat': hat_activation,
               'square': square_activation,
               'cube': cube_activation}

class InvalidActivationFunction(Exception):
    pass

class ActivationFunctionSet(object):
    def __init__(self):
        self.functions = {}

    def add(self, config_name, function):
        # TODO: Verify that the given function has the correct signature.
        self.functions[config_name] = function

    def get(self, config_name):
        f = self.functions.get(config_name)
        if f is None:
            raise InvalidActivationFunction("No such function: {0!r}".format(config_name))

        return f

    def is_valid(self, config_name):
        return config_name in self.functions


import numpy as np

def Usolve(U, y):
    """Backward solve an upper triangular system Ux = y for x
    Parameters:
      U: the matrix, must be square, upper triangular, with nonzeros on the diagonal
      y: the right-hand side vector
    Output:
      x: the solution vector to U @ x == y
    """
    m, n = U.shape
    assert m == n, "matrix U must be square"
    assert np.all(np.triu(U) == U), "matrix U must be Upper triangular"
    assert np.all(np.diag(U) != 0), "matrix U must have ones on the diagonal"
    yn, = y.shape
    assert yn == n,"must be same size as U"
    # Make a copy of y that we will transform into the solution
    x = y.astype(np.float64).copy()
    # back solve
    for col in reversed(range(n)):
        x[col] = x[col]/U[col,col]
        x[:col] = x[:col] - x[col]*U[:col,col]
    return x


import numpy as np
import matplotlib.pyplot as pl

def compute_mean(x):
    """
    computes and returns the mean vector of the input data
    """
    m = np.mean(x, axis=1)[:, np.newaxis]
    return m

def compute_covariance(X):
    """
    computes and returns the covariance matrix of the input data x
    """
    X = X.copy()
    m = compute_mean(X)
    N = X.shape[1]
    X = X - m
    S = 1.0/(N-1) * np.dot(X, X.T)
    return S

def compute_principal_components(X):
    """
    computes and returns the principal components
    """
    # compute the covariance matrix
    S = compute_covariance(X)
    # compute eigenvalues and eigenvectors
    l, p = np.linalg.eigh(S)
    # sort the eigenvectors in descending order
    li = np.argsort(l)[::-1]
    L = np.diag(l[li])
    P = p[:,li]
    return P, L

def select_principal_components(P, L, delta):
    """
    selects and returns principal components considering
    desired retained variation
    """
    l = np.diagonal(L)
    d = np.argmin(np.abs(delta - np.cumsum(l) / np.sum(l))) + 1
    Pd = P[:,0:d]
    Ld = L[0:d, 0:d]
    return Pd, Ld, d

def project_onto_eigenvector_space(X, Pd, m):
    """
    projects data matrix onto eigenvector space
    """
    W = np.dot(Pd.T, (X-m))
    return W

def reconstruct_data(Pd, W, m):
    """
    reconstructs data matrix using d-rank approximation
    """
    Xhat = m + np.dot(Pd, W)
    return Xhat

# pl.close('all')
# # generate a synthetic data set1
# x = generate_data()
# # compute the mean vector of the data set
# m = compute_mean(x)
# # compute the covariance matrix of the data set
# S = compute_covariance(x)
# # compute all principal components
# P, L = compute_principal_components(x)
# # select principal components according to desired variation
# delta = 1.0
# Pd, Ld, d = select_principal_components(P, L, delta)
# # plot data and principal components
# plot_principal_components(x, Pd, Ld, '../../slides/docs/principal_components4.png')


#pythran export arc_distance(float [], float[], float[], float[])
#runas import numpy as np; arc_distance(np.array([12.4,0.5,-5.6,12.34,9.21]),np.array([-5.6,3.4,2.3,-23.31,12.6]),np.array([3.45,1.5,55.4,567.0,43.2]),np.array([56.1,3.4,1.34,-56.9,-3.4]))
#bench import numpy.random; N=5000000; a, b, c, d = numpy.random.rand(N), numpy.random.rand(N), numpy.random.rand(N), numpy.random.rand(N); arc_distance(a, b, c, d)

import numpy as np
def arc_distance(theta_1, phi_1,
                 theta_2, phi_2):
    """
    Calculates the pairwise arc distance between all points in vector a and b.
    """
    temp = np.sin((theta_2-theta_1)/2)**2+np.cos(theta_1)*np.cos(theta_2)*np.sin((phi_2-phi_1)/2)**2
    distance_matrix = 2 * (np.arctan2(np.sqrt(temp),np.sqrt(1-temp)))
    return distance_matrix


# Plots various neural net activation functions.

import matplotlib.pyplot as plt
import numpy as np
from pyprobml_utils import save_fig

e = np.exp(1)
x = np.linspace(-4, 4, 1000)
y = e**x / (e**x + 1)
fig, ax = plt.subplots()
ax.plot(x, y)
plt.title('sigmoid function')
save_fig('sigmoidPlot.pdf')
plt.show()

y = np.tanh(x)
fig, ax = plt.subplots()
ax.plot(x, y)
plt.title('tanh function')
save_fig('tanhPlot.pdf')
plt.show()

y = np.log(1+np.exp(x))
fig, ax = plt.subplots()
ax.plot(x, y)
plt.title('softplus function')
save_fig('softplusPlot.pdf')
plt.show()

y = 1.0*(x>0)
fig, ax = plt.subplots()
ax.plot(x, y)
plt.title('Heaviside function')
save_fig('heavisidePlot.pdf')
plt.show()

y = np.maximum(0, x)
fig, ax = plt.subplots()
ax.plot(x, y)
plt.title('ReLU function')
save_fig('reluPlot.pdf')
plt.show()

lam = 0.5
y = np.maximum(0, x) + lam*np.minimum(0, x)
fig, ax = plt.subplots()
ax.plot(x, y)
plt.title('Leaky ReLU function')
save_fig('leakyReluPlot.pdf')
plt.show()

lam = 0.5
y = np.maximum(0, x) + np.minimum(0, lam*(e**x - 1))
fig, ax = plt.subplots()
ax.plot(x, y)
plt.title('ELU function')
save_fig('eluPlot.pdf')
plt.show()


import math
from scipy.stats import norm
from dateutil.parser import *
from datetime import *

def BlackSholes(currentPrice, strikePrice, volatility, rate, expiration = '12/31/2020'):
    if type(expiration) is str:
        expiration = parse(expiration)
    term = (expiration.date() - date.today()).days
    return _BlackSholes(currentPrice,strikePrice,volatility,rate, term, termUnits='days')

def _BlackSholes(currentPrice, strikePrice, volatility, rate, term, termUnits='days'):
    # this is the log of the current price / strike price term
    logStockStrike = math.log(currentPrice/strikePrice)
    # represents the sigma-squared divided by 2 term
    sigmaDiv2 = (volatility**2)/2
    # Convert days to part of years
    T = term/365 if termUnits == 'days' else term
    ert = math.exp(-rate*T)

    d1 = (logStockStrike + (rate+sigmaDiv2)*T) / (volatility*math.sqrt(T))
    d2 = d1 - volatility*math.sqrt(T)
    Nd1 = norm.cdf(d1)
    Nd2 = norm.cdf(d2)
    callPrice = currentPrice*Nd1 - strikePrice*ert*Nd2

    Nnegd1 = norm.cdf(-d1)
    Nnegd2 = norm.cdf(-d2)
    putPrice = strikePrice*ert*Nnegd2 - currentPrice*Nnegd1

    return (callPrice, putPrice)

if __name__ == "__main__":
    import numpy as np
    import pandas as pd

    df = pd.DataFrame({'vol':np.arange(15,25,0.1)})
    df['call'] = df['vol'].apply(lambda x: BlackSholes(166.36, 175, x, 0.017,'12/31/2020')[0])
    df


from sympy import symbols, hessian, Matrix
from sympy.functions import exp

def gradient(f, v): return Matrix([f]).jacobian(v)

x, b1, b2, b3, b4, b5, b6, b7, b8 = symbols('x,b1,b2,b3,b4,b5,b6,b7,b8')

print('NIST Low Difficulty')
print('\nChwirut')
print('y = exp(-b1*x)/(b2+b3*x)  +  e')

r = exp(-b1*x)/(b2+b3*x)
v = [b1, b2, b3]

print('Jacobian:')
print(gradient(r, v))

print('Hessian:')
print(hessian(r, v))

print('\nChwirut2')
print('y = exp(-b1*x)/(b2+b3*x)  +  e')

r = exp(-b1*x)/(b2+b3*x)
v = [b1, b2, b3]

print('Jacobian:')
print(gradient(r, v))

print('Hessian:')
print(hessian(r, v))

print('\nDanWood')
print('y  = b1*x**b2  +  e')

r = b1*x**b2
v = [b1, b2]

print('Jacobian:')
print(gradient(r, v))

print('Hessian:')
print(hessian(r, v))

print('\nGauss1')
print('y = b1*exp( -b2*x ) + b3*exp( -(x-b4)**2 / b5**2) '
      '+ b6*exp( -(x-b7)**2 / b8**2 ) + e')

r = b1*exp(-b2*x) + b3*exp(-(x-b4)**2 / b5**2) + b6*exp(-(x-b7)**2 / b8**2)
v = [b1, b2, b3, b4, b5, b6, b7, b8]

print('Jacobian:')
print(gradient(r, v))

print('Hessian:')
print(hessian(r, v))

print('\nGauss2')
print('y = b1*exp( -b2*x ) + b3*exp( -(x-b4)**2 / b5**2) '
      '+ b6*exp( -(x-b7)**2 / b8**2 ) + e')

r = b1*exp(-b2*x) + b3*exp(-(x-b4)**2 / b5**2) + b6*exp(-(x-b7)**2 / b8**2)
v = [b1, b2, b3, b4, b5, b6, b7, b8]

print('Jacobian:')
print(gradient(r, v))

print('Hessian:')
print(hessian(r, v))

print('\nLanczos3')
print('y = b1*exp(-b2*x) + b3*exp(-b4*x) + b5*exp(-b6*x)  +  e')

r = b1*exp(-b2*x) + b3*exp(-b4*x) + b5*exp(-b6*x)
v = [b1, b2, b3, b4, b5, b6]

print('Jacobian:')
print(gradient(r, v))

print('Hessian:')
print(hessian(r, v))

print('\nMisra1a')
print('y = b1*(1-exp(-b2*x))  +  e')

r = b1*(1-exp(-b2*x))
v = [b1, b2]

print('Jacobian:')
print(gradient(r, v))

print('Hessian:')
print(hessian(r, v))

print('\nMisra1b')
print('y = b1 * (1-(1+b2*x/2)**(-2))  +  e')

r = b1 * (1-(1+b2*x/2)**(-2))
v = [b1, b2]

print('Jacobian:')
print(gradient(r, v))

print('Hessian:')
print(hessian(r, v))


## Translation of mlb's barras_linear.m to Python
## cacs@thorus-scisoft.com.br

import numpy as np

## Physical data
Lb=1.0        # bar length
E  = 210e9       # Young modulus
A  = 1e-4        # cross sectional area
EA = E*A
p0 = 100         # distributed load
Pc = 200         # concentrated load at x = L, b.c. at x = L

## FEM setup: all element with the same length
Nel   = 4        # number of elements
Nvert = Nel+1    # number of nodes (1-D, linear interpolation)
he    = Lb/Nel   # element lenght
Ndofs = Nel+1    # number of d.o.f. (1 d.o.f. per node)

# Allocation of the stifness matrix
Kg = np.zeros((Ndofs,Ndofs))

# Allocation of the load vector
Fg = np.zeros(Ndofs)

# Allocation of the solution vector
ug = np.zeros(Ndofs)

# Allocation of incidence matrix
Incid = np.zeros((Nel,2), dtype=np.int)

# Auxiliary matrix for accessing submatrix (more comments below)
TIncid = np.zeros((2,1), dtype=np.int)

# Nodes coordinates
X = np.linspace(0, Lb, Nvert)

# Bar element stiffness matrix
Ke = (EA/he)*np.array([[1,-1],[-1,1]])

# Bar element load vector due to the distributed load p0
Fe = (p0*he/2)*np.array([1, 1])

# Assembly of global matrix and global load vector without b.c.'s
for e in range(0,Nel):
	Incid[e,:]=np.array([e,e+1])
	Fg[Incid[e,:]]=Fg[Incid[e,:]]+Fe
	# numpy requires a column matrix to specify submatrix columns
	TIncid[:,0]=Incid[e,:]
	# The general mechanism to access submatrices in numpy returns the
    # transpost of the submatrix. Despite all matrices being symmetric
	# I am transposing Ke to be coherent.
	Kg[Incid[e,:], TIncid] = Kg[Incid[e,:], TIncid] + np.transpose(Ke);

# Application of force b.c. at x = L
Fg[-1] = Fg[-1] + Pc

# Application of b.c. disp. condition at x = 0 (elimination of the first line
# and first column of the global system) and linear system solution
ug[1:] = np.linalg.solve( Kg[1:,1:],Fg[1:] )


import json
import os.path
from typing import List

import math
import sympy

from utils import get_root_dir

# Reading the primes file is slower than calculating the first primes. Where this cutoff point exactly lies depends
# on many factors, among which how big the primes file is. With a file with 1,000,000 primes, this cutoff point
# lies somewhere between 1,000 and 10,000.
CUTOFF_TO_LOOKUP = 1000

PRIMES_FILE = os.path.join(get_root_dir(), 'json', 'primes.json')

def is_prime(n: int) -> bool:
    """
    Return wether the input argument is a prime number
    """

    return sympy.isprime(n)

def get_primes(n: int) -> List[int]:
    """
    Return a list of the first n primes

    The results are cached on file, so the primes are only calculated once.
    """

    if n > CUTOFF_TO_LOOKUP and os.path.exists(PRIMES_FILE):
        with open(PRIMES_FILE, 'r') as f:
            known_primes = json.loads(f.read())
        if n <= len(known_primes):
            return known_primes[:n]
    else:
        known_primes = [1, 2, 3]  # The first primes are known

    num = max(known_primes)
    while len(known_primes) < n:
        # Check if num is divisible by any prime before it
        num_is_prime = True
        for p in known_primes[1:]:  # Skip the first prime, which is 1
            # If there is no remainder dividing the number then the number is not a prime
            if num % p == 0:
                # Break to stop testing more numbers, we know it's not a prime
                num_is_prime = False
                break

        # If it is a prime, then add it to the dictionary of known primes.
        if num_is_prime:
            known_primes.append(num)

        # Don't check even numbers
        num += 2

    if n > CUTOFF_TO_LOOKUP:
        with open(PRIMES_FILE, 'w') as f:
            f.write(json.dumps(known_primes))
    return known_primes

def nth_prime(n: int) -> int:
    """
    Return the n'th prime number

    The results are cached on file, so the primes are only calculated once.
    """

    return get_primes(n)[n - 1]

def prime_factorize(n: int) -> List[int]:
    """
    Return the list of divisors for the given integer

    >>> prime_factorize(15485857 * 15485863)
    [15485857, 15485863]
    """

    result = []
    for prime, multiplicity in sympy.factorint(n).items():
        result += [prime] * multiplicity
    return result

if __name__ == '__main__':
    print(nth_prime(10000))
    print(prime_factorize(15485857 * 15485863))


from __future__ import annotations

import math

import numpy
import numpy as np
from numpy.linalg import inv, det, slogdet

class UnivariateGaussian:
    """
    Class for univariate Gaussian Distribution Estimator
    """

    def __init__(self, biased_var: bool = False) -> UnivariateGaussian:
        """
        Estimator for univariate Gaussian mean and variance parameters

        Parameters
        ----------
        biased_var : bool, default=False
            Should fitted estimator of variance be a biased or unbiased estimator

        Attributes
        ----------
        fitted_ : bool
            Initialized as false indicating current estimator instance has not been fitted.
            To be set as True in `UnivariateGaussian.fit` function.

        mu_: float
            Estimated expectation initialized as None. To be set in `UnivariateGaussian.fit`
            function.

        var_: float
            Estimated variance initialized as None. To be set in `UnivariateGaussian.fit`
            function.
        """
        self.biased_ = biased_var
        self.fitted_, self.mu_, self.var_ = False, None, None

    def fit(self, X: np.ndarray) -> UnivariateGaussian:
        """
        Estimate Gaussian expectation and variance from given samples

        Parameters
        ----------
        X: ndarray of shape (n_samples, )
            Training data

        Returns
        -------
        self : returns an instance of self.

        Notes
        -----
        Sets `self.mu_`, `self.var_` attributes according to calculated estimation (where
        estimator is either biased or unbiased). Then sets `self.fitted_` attribute to `True`
        """
        # raise NotImplementedError()

        self.mu_ = np.mean(X)
        if self.biased_:
            self.var_ = np.var(X) * (len(X) - 1) / len(X)
        else:
            self.var_ = np.var(X)

        self.fitted_ = True
        return self

    def pdf(self, X: np.ndarray) -> np.ndarray:
        """
        Calculate PDF of observations under Gaussian model with fitted estimators

        Parameters
        ----------
        X: ndarray of shape (n_samples, )
            Samples to calculate PDF for

        Returns
        -------
        pdfs: ndarray of shape (n_samples, )
            Calculated values of given samples for PDF function of N(mu_, var_)

        Raises
        ------
        ValueError: In case function was called prior fitting the model
        """
        if not self.fitted_:
            raise ValueError("Estimator must first be fitted before calling `pdf` function")

        # raise NotImplementedError()

        pdfs = np.zeros(len(X))
        for index, x in enumerate(X):
            prefix = 1 / (2 * math.pi * self.var_) ** 0.5
            exponent = math.exp(-(((x - self.mu_) ** 2) / (2 * self.var_)))

            pdfs[index] = prefix * exponent

        return pdfs

    @staticmethod
    def log_likelihood(mu: float, sigma: float, X: np.ndarray) -> float:
        """
        Calculate the log-likelihood of the data under a specified Gaussian model

        Parameters
        ----------
        mu : float
            Expectation of Gaussian
        sigma : float
            Variance of Gaussian
        X : ndarray of shape (n_samples, )
            Samples to calculate log-likelihood with

        Returns
        -------
        log_likelihood: float
            log-likelihood calculated
        """
        # raise NotImplementedError()

        log_pdfs = np.zeros(len(X))
        for index, x in enumerate(X):
            prefix = 1 / (2 * math.pi * sigma) ** 0.5
            exponent = math.exp(-(((x - mu) ** 2) / (2 * sigma)))

            log_pdfs[index] = np.log(prefix * exponent)

        return log_pdfs.sum()

class MultivariateGaussian:
    """
    Class for multivariate Gaussian Distribution Estimator
    """

    def __init__(self):
        """
        Initialize an instance of multivariate Gaussian estimator

        Attributes
        ----------
        fitted_ : bool
            Initialized as false indicating current estimator instance has not been fitted.
            To be set as True in `MultivariateGaussian.fit` function.

        mu_: ndarray of shape (n_features,)
            Estimated expectation initialized as None. To be set in `MultivariateGaussian.fit`
            function.

        cov_: ndarray of shape (n_features, n_features)
            Estimated covariance initialized as None. To be set in `MultivariateGaussian.fit`
            function.
        """
        self.mu_, self.cov_ = None, None
        self.fitted_ = False

    def fit(self, X: np.ndarray) -> MultivariateGaussian:
        """
        Estimate Gaussian expectation and covariance from given samples

        Parameters
        ----------
        X: ndarray of shape (n_samples, n_features)
            Training data

        Returns
        -------
        self : returns an instance of self

        Notes
        -----
        Sets `self.mu_`, `self.cov_` attributes according to calculated estimation.
        Then sets `self.fitted_` attribute to `True`
        """
        # raise NotImplementedError()
        self.mu_ = np.mean(X, axis=0)
        self.cov_ = np.cov(np.transpose(X))

        self.fitted_ = True
        return self

    def pdf(self, X: np.ndarray):
        """
        Calculate PDF of observations under Gaussian model with fitted estimators

        Parameters
        ----------
        X: ndarray of shape (n_samples, n_features)
            Samples to calculate PDF for

        Returns
        -------
        pdfs: ndarray of shape (n_samples, )
            Calculated values of given samples for PDF function of N(mu_, cov_)

        Raises
        ------
        ValueError: In case function was called prior fitting the model
        """
        if not self.fitted_:
            raise ValueError("Estimator must first be fitted before calling `pdf` function")
        # raise NotImplementedError()

        pdfs = np.zeros(X.shape)
        for index, x in enumerate(X):
            two_pi_powered = (2 * math.pi) ** len(self.mu_)
            prefix = 1 / (two_pi_powered * np.linalg.det(self.cov_)) ** 0.5
            exponent = np.exp(-0.5 * ((np.transpose(x - self.mu_)).dot(np.linalg.inv(self.cov_)).dot(x - self.mu_)))

            pdfs[index] = prefix * exponent

        return pdfs

    # @staticmethod
    # def calc_pdf(mu: np.ndarray, cov: np.ndarray, X: np.ndarray, func):
    #     func_pdfs = np.zeros(X.shape)
    #     for index, x in enumerate(X):
    #         two_pi_powered = (2 * math.pi) ** len(mu)
    #         prefix = 1 / (two_pi_powered * np.linalg.det(cov)) ** 0.5
    #         exponent = np.exp(-0.5 * ((np.transpose(x - mu)).dot(np.linalg.inv(cov)).dot(x - mu)))
    #
    #         func_pdfs[index] = func(prefix * exponent)
    #
    #     return func_pdfs

    @staticmethod
    def log_likelihood(mu: np.ndarray, cov: np.ndarray, X: np.ndarray) -> float:
        """
        Calculate the log-likelihood of the data under a specified Gaussian model

        Parameters
        ----------
        mu : ndarray of shape (n_features,)
            Expectation of Gaussian
        cov : ndarray of shape (n_features, n_features)
            covariance matrix of Gaussian
        X : ndarray of shape (n_samples, n_features)
            Samples to calculate log-likelihood with

        Returns
        -------
        log_likelihood: float
            log-likelihood calculated over all input data and under given parameters of Gaussian
        """
        # raise NotImplementedError()
        inv_cov = np.linalg.inv(cov)
        det_cov = np.linalg.det(cov)

        k = np.shape(X)[1]
        num_samples = np.shape(X)[0]

        log_likelihood_sum = 0
        for index, x in enumerate(X):
            log_likelihood_sum += (x - mu) @ inv_cov @ (x - mu).T

        return -0.5 * (num_samples * np.log(det_cov) + log_likelihood_sum + k * num_samples * np.log(2 * np.pi))
        # return IMLearn.learners.MultivariateGaussian.calc_pdf(mu, cov, X, lambda q: np.log(q)).sum()


"""
Module containing functions that replicate some useful matlab routines

Created on 8 March 2019
@authors:
    * Francesco Della Santa (FDS), Politecnico di Torino, ITALY

Updates:
dd Mon YYYY:
    * ...

"""

import numpy as np
import scipy as sp  # Still not used

def cart2pol(x, y):
    """
    Transform Cartesian to polar coordinates (look at car2pol help on matlab)
    :param x:
    :param y:
    :return theta: radians of the angle between [x,y] and x axis (anti-clockwise with x=east, y=north)
    :return rho: radius, norm of [x,y]
    """
    theta = np.arctan2(y, x)
    rho = np.hypot(x, y)
    return theta, rho

def pol2cart(theta, rho):
    """
    Transform polar to Cartesian coordinates (look at the pol2cart help on matlab)
    :param theta: radians of the angle between [x,y] and x axis (anti-clockwise with x=east, y=north)
    :param rho: radius, norm of [x,y]
    :return: x, y
    """
    x = rho * np.cos(theta)
    y = rho * np.sin(theta)
    return x, y

def cart2sph(x, y, z):
    """
    Transform Cartesian to spherical coordinates (look at the cart2sph help on matlab)
    :param x:
    :param y:
    :param z:
    :return az: radians of the angle between projection on (x,y) plane of [x,y,z] and x axis (anti-clockwise with
    respect to z)
    :return el: radians of the angle between [x,y,z] and (x,y) plane (el >/=/< 0 if z >/=/< 0). then -pi/2 <= el <= pi/2
    :return r: radius, norm of [x,y,z]
    """
    hxy = np.hypot(x, y)
    r = np.hypot(hxy, z)
    el = np.arctan2(z, hxy)
    az = np.arctan2(y, x)
    return az, el, r

def sph2cart(az, el, r):
    """
    Transform spherical to Cartesian coordinates (look at the sph2cart help on matlab)
    :param az: radians of the angle between projection on (x,y) plane of [x,y,z] and x axis (anti-clockwise with
    respect to z)
    :param el: radians of the angle between [x,y,z] and (x,y) plane (el >/=/< 0 if z >/=/< 0). then -pi/2 <= el <= pi/2
    :param r: radius, norm of [x,y,z]
    :return: x, y, z
    """
    rcos_theta = r * np.cos(el)
    x = rcos_theta * np.cos(az)
    y = rcos_theta * np.sin(az)
    z = r * np.sin(el)
    return x, y, z

def rotx(ang):
    """
    Create a rotation matrix (3 x 3 np.array) in the 3D space with respect to x axis
    :param ang: radians. If ang <(>) 0, rotation is (anti-)clockwise with respect to x.
    :return: Rx: np.array of shape (3, 3) representing the rotation matrix
    """
    Rx = np.array(
        [
            [1, 0, 0],
            [0, np.cos(ang), -np.sin(ang)],
            [0, np.sin(ang), np.cos(ang)]
        ]
    )

    return Rx

def roty(ang):
    """
    Create a rotation matrix (3 x 3 np.array) in the 3D space with respect to y axis
    :param ang: radians. If ang <(>) 0, rotation is (anti-)clockwise with respect to y.
    :return: Rx: np.array of shape (3, 3) representing the rotation matrix
    """
    Ry = np.array(
        [
            [np.cos(ang), 0, np.sin(ang)],
            [0, 1, 0],
            [-np.sin(ang), 0, np.cos(ang)]
        ]
    )

    return Ry

def rotz(ang):
    """
    Create a rotation matrix (3 x 3 np.array) in the 3D space with respect to z axis
    :param ang: radians. If ang <(>) 0, rotation is (anti-)clockwise with respect to z.
    :return: Rz: np.array of shape (3, 3) representing the rotation matrix
    """
    Rz = np.array(
        [
            [np.cos(ang), -np.sin(ang), 0],
            [np.sin(ang), np.cos(ang), 0],
            [0, 0, 1]
        ]
    )

    return Rz


import numpy as np

def sin_normal_noise(N_samples, N_true=100, std=0.3):
    """
    Returns synthetic data of a sinusoidal signal with added
    normal noise.
    Args:
        N_samples: int, number of points
        N_true: int, number of points from signal without noise
        std: float, standard deviation of the added normal noise
    Returns:
        x_true: numpy array of size (N, 1) containing x data of signal
            without noise.
        t_true: numpy array of size (N, 1) containing target data of signal
            without noise.
        x: numpy array of size (N, 1) containing x data of signal
        t: numpy array of size (N, 1) containing target data of signal
    """
    # True function
    x_true = np.linspace(0, 1, N_true)[:, np.newaxis]
    t_true = np.sin(2*np.pi*x_true)

    # Training data
    x = np.linspace(0, 1, N_samples)[:, np.newaxis]
    t = np.sin(2*np.pi*x) + np.random.normal(0, std, N_samples)[:, np.newaxis]

    return x_true, t_true, x, t

def gaussian_mixture(N_samples):
    """Sample data from a Gaussian mixture with two components of mean -2 and 2
    and unit variance.
    Args:
        N_samples: int, the number of samples
    Returns:
        X: array, shape (N_samples,), the sampled data
    """
    X = np.empty(N_samples)
    # Choose from components with equal probability
    idx = np.random.choice([True, False], N_samples)
    n_1 = np.sum(idx)
    n_2 = N_samples - n_1
    # Fill array with samples from both components
    X[idx] = np.random.normal(-2, size=n_1)
    X[np.logical_not(idx)] = np.random.normal(2, size=n_2)
    return X


import numpy as np

'''
    From the name you can guess the calculation of this distance.
    Imagine that you are driving from an intersection to another intersection in Manhattan.
    Is the driving distance a straight line between two points? Obviously not,
    unless you can cross the building. The actual driving distance is this "Manhattan distance" (L1 norm).
    This is also the source of the Manhattan distance name, which is also known as the City Block distance
'''

objA = [22, 1, 42, 10]

objB = [20, 0, 36, 8]

npA = np.array(objA)

npB = np.array(objB)

manhattan = np.sum(np.abs(npA - npB))

# manhattan = np.linalg.norm(npA - npB, ord=1)

print(manhattan)


import numpy as np
class Solution:
    def numWays(self, steps: int, arrLen: int) -> int:
        arrLen = min(arrLen, steps + 1)
        dp = np.zeros((steps, arrLen),int)#dp[i][j] how many ways ith step reaches j
        dp[0][0], dp[0][1] = 1, 1
        for i in range(1, steps):
            for j in range(arrLen):
                if j-1 >= 0 and j+1 < arrLen:
                    dp[i][j] = (dp[i][j] + dp[i - 1][j - 1] + dp[i - 1][j] + dp[i - 1][j + 1]) % (10**9 + 7)
                elif j-1 >= 0:
                    dp[i][j] = (dp[i][j] + dp[i - 1][j - 1] + dp[i - 1][j]) % (10**9 + 7)
                elif j+1 < arrLen:
                    dp[i][j] = (dp[i][j] + dp[i - 1][j] + dp[i - 1][j + 1]) % (10**9 + 7)
                else:
                    dp[i][j] = dp[i - 1][j] % (10**9 + 7)
        return dp[steps-1][0]

    ans = 0
    def numWays2(self, steps: int, arrLen: int) -> int:
        #remain0 left -1 right 1
        self.ans = 0
        self.helper(steps, arrLen,0,0)
        return self.ans

    def helper(self, steps: int, arrLen: int, currSum: int, currStep: int):
        if currStep < steps:
            for i in [-1, 0, 1]:
                if currSum+i < arrLen and currSum+i >= 0:
                    self.helper(steps, arrLen, currSum+i, currStep+1)
        elif currSum == 0:
            self.ans += 1

solution = Solution()
res = solution.numWays(430, 148488)
print(res)

#res = solution.numWays2(27,7)
#print(res)


# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: percent
#       format_version: '1.3'
#       jupytext_version: 1.13.6
#   kernelspec:
#     display_name: Python 3 (ipykernel)
#     language: python
#     name: python3
# ---

# %% [markdown]
# ---
# #  [Pyomo](http://www.pyomo.org/)
#
# ### A Python-based, open-source optimization modeling language
#
# ---
#
# + [Pyomo documentation](https://pyomo.readthedocs.io/en/stable/)
# + Pyomo is a modeling language, not a solver. It interfaces to solvers such as [glpk](https://www.gnu.org/software/glpk/)
#
# ## Standard import

# %%
import pyomo.environ as pyo
import pandas as pd
import numpy as np

# %% [markdown]
# # [The Diet Problem](https://notebook.community/Pyomo/PyomoGallery/diet/DietProblem)
#
# The goal of the Diet Problem is to select foods that satisfy daily nutritional requirements at minimum cost.
#
#
#
# ### Sets
#
# | Symbol | Definition |
# | :----- | ---------------: |
# |  $F$   | set of foods     |
# |  $N$   | set of nutrients |
#
# ### Parameters
#
# | Symbol | Definition                                              | Size               |
# | :----- | :------------------------------------------------------ | :---------------- |
# | $$c_i$$  | cost per serving of food $i$ | $$\forall i \in F$$ |
# | $$a_{ij}$$   |  amount of nutrient $j$ in food $i$ | $$\forall i \in F, \forall j \in N$$ |
# | $$Nmin_j$$  | minimum level of nutrient $j$  |  $$\forall j \in N$$   |
# | $$Nmax_j$$  | maximum level of nutrient $j$  | $$\forall j \in N$$ |
# | $$V_i$$  | the volume per serving of food $i$  | $$\forall i \in F$$   |
# | $$Vmax$$  |  maximum volume of food consumed |  |
#
#
# ### Variables
#
# | Symbol | Definition |
# | :----- | ---------------: |
# |$$x_i$$ | number of servings of food $i$ to consume |
#
# ### Objective
#
# + Minimize the total cost of the food
#  $$\min \sum_{i \in F} c_i x_i$$
#
# ### Constraints
#
# + Limit nutrient consumption for each nutrient $j \in N$.
#  $$Nmin_j \leq \sum_{i \in F} a_{ij} x_i \leq Nmax_j\hspace{3em}\forall j \in N$$
#
# + Limit the volume of food consumed
#  $$\sum_{i \in F} V_i x_i \leq Vmax$$
#
# + Consumption lower bound
#  $$x_i \geq 0\hspace{3em}\forall i \in F$$

# %% [markdown]
# ## Pyomo Modeling Components
#
# + Sets
# + Parameters
# + Variables
# + Objectives
# + Constraints
# + Expressions
# + Suffixes
#
#
#

# %% [markdown]
# ### 0. Load the data

# %%
Vmax = 75.0

# %%
Bounds = pd.DataFrame([['Cal',2000, np.inf],
        ['Carbo'   ,  350  ,  375],
        ['Protein' ,   55 , np.inf],
        ['VitA'    ,  100 , np.inf],
        ['VitC'    ,  100 , np.inf],
        ['Calc'    ,  100  , np.inf],
        ['Iron'    ,  100 , np.inf]],
       columns=['nutrient','min','max'] )
Bounds = Bounds.set_index('nutrient')
Bounds

# %%
Foods = pd.DataFrame([[  "Cheeseburger",1.84 ,  4.0,510,     34,     28 ,    15 ,     6 ,   30 ,   20  ],
        [  "Ham Sandwich", 2.19 ,  7.5 ,370,     35,     24 ,    15 ,    10 ,   20 ,   20 ],
        [  "Hamburger",1.84 ,  3.5 ,500,     42,     25 ,     6 ,     2 ,   25 ,   20 ],
        [  "Fish Sandwich" , 1.44 ,  5.0 ,370,     38,     14 ,     2 ,     0 ,   15 ,   10 ],
        [  "Chicken Sandwich", 2.29,7.3,400,     42,     31 ,     8 ,    15 ,   15 ,    8],
        [  "Fries"  , .77 ,  2.6 ,220,     26,      3 ,     0 ,    15 ,    0 ,    2 ],
        [  "Sausage Biscuit", 1.29 ,  4.1 ,345,     27,     15 ,     4 ,     0 ,   20 ,   15 ],
        [  "Lowfat Milk" , .60,8.0 ,110,     12,      9 ,    10 ,     4 ,   30 ,    0],
        [  "Orange Juice", .72 , 12.0 ,80 ,    20 ,     1  ,    2  ,  120  ,   2  ,   2 ]],
                 columns = ['food','c','V','Cal' , 'Carbo','Protein' ,  'VitA'  , 'VitC' , 'Calc' , 'Iron'])

Foods.set_index('food' ,inplace=True)
Foods

# %% [markdown]
# ### 1. Create a model object.

# %%
model = pyo.ConcreteModel()

# %% [markdown]
# ### 2. Define sets

# %%
# foods set
model.F = pyo.Set(initialize = Foods.index)

# nutrients set
model.N = pyo.Set(initialize = Bounds.index)

# %% [markdown]
# ### 3. Define parameters

# %%
model.c       = pyo.Param(model.F,initialize=Foods['c'].to_dict())
model.V       = pyo.Param(model.F,initialize=Foods['V'].to_dict())
model.Cal     = pyo.Param(model.F,initialize=Foods['Cal'].to_dict())
model.Carbo   = pyo.Param(model.F,initialize=Foods['Carbo'].to_dict())
model.Protein = pyo.Param(model.F,initialize=Foods['Protein'].to_dict())
model.VitA    = pyo.Param(model.F,initialize=Foods['VitA'].to_dict())
model.VitC    = pyo.Param(model.F,initialize=Foods['VitC'].to_dict())
model.Calc    = pyo.Param(model.F,initialize=Foods['Calc'].to_dict())
model.Iron    = pyo.Param(model.F,initialize=Foods['Iron'].to_dict())

# %% [markdown]
# ### 4. Define the decision variables

# %%
model.x = pyo.Var(model.F, within=pyo.NonNegativeIntegers)

# %% [markdown]
# ### 5. Define the objective function

# %%
obj = sum(model.c[food]*model.x[food] for food in model.F)
model.cost = pyo.Objective(rule=obj, sense = pyo.minimize)

# %% [markdown]
# ### 6. Define the constraints

# %% [markdown]
# #### Limit nutrient consumption for each nutrient

# %%
def nutrient_rule(model, nutrient):
    value = sum(Foods.loc[food,nutrient]*model.x[food] for food in model.F)
    return pyo.inequality(Bounds.loc[nutrient,'min'], value, Bounds.loc[nutrient,'max'])

model.bounds = pyo.Constraint(model.N, rule=nutrient_rule)

# %% [markdown]
# #### sdasdfasdf

# %%
expr = sum(Foods.loc[food,'V']*model.x[food] for food in model.F) <= Vmax
model.volume = pyo.Constraint(rule=expr)

# %% [markdown]
# ## 7. Request the dual, ie Lagrange multiplliers (optional)

# %%
model.dual = pyo.Suffix(direction=pyo.Suffix.IMPORT)

# %% [markdown]
# ## Check the model

# %%
model.pprint()

# %% [markdown]
# ## Solve
#
# Cnnect to a solver with `SolverFactory`. Then run the solver with the `solve` method. This returns an object with information about the solution process. The optimal values of the decision variables are written to the `model` object.

# %%
solver = pyo.SolverFactory('ipopt')  # can also use `glpk`

results = solver.solve(model)

print("The solver returned a status of: " + str(results.solver.status))

# %% [markdown]
# Use the `pyo.value` method to extract the results from the model object.

# %% [markdown]
# ### Extract solution values

# %% [markdown]
# #### Optimal cost

# %%
pyo.value(model.cost)

# %% [markdown]
# #### Print optimal decision variables

# %%
{food:pyo.value(model.x[food]) for food in model.F}

# %% [markdown]
# #### Lagrange multipliers

# %%
for c in model.component_objects(pyo.Constraint):
    print ("\n   Constraint",c)
    for index in c:
        value = round(model.dual[c[index]],5)
        print ("\t{:.5s}\t{:.5f}".format(index if index!=None else "None",value))


import sympy as sym
import numpy as np

# The code in vib_undamped_verify_mms.py is here generalized
# to treat the model m*u'' + f(u') + c*u = F(t), where the
# damping term f(u') = 0, b*u' or b*V*abs(V).

def ode_source_term(u, damping):
    """Return the terms in the ODE that the source term
    must balance, here m*u'' + f(u') + c*u.
    u is a symbolic Python function of t."""
    if damping == 'zero':
        return m*sym.diff(u(t), t, t) + c*u(t)
    elif damping == 'linear':
        return m*sym.diff(u(t), t, t) + \
               b*sym.diff(u(t), t) + c*u(t)
    else:  # damping is nonlinear
        return m*sym.diff(u(t), t, t) + \
               b*sym.diff(u(t), t)*abs(sym.diff(u(t), t)) + c*u(t)

def residual_discrete_eq(u, damping):
    """Return the residual of the discrete eq. with u inserted."""
    if damping == 'zero':
        R = m*DtDt(u, dt) + c*u(t) - F
    elif damping == 'linear':
        R = m*DtDt(u, dt) + b*D2t(u, dt) + c*u(t) - F
    else:   # damping is nonlinear
        R = m*DtDt(u, dt) + b*Dt_p_half(u, dt)*\
            abs(Dt_m_half(u, dt)) + c*u(t) - F
    return sym.simplify(R)

def residual_discrete_eq_step1(u, damping):
    """Return the residual of the discrete eq. at the first
    step with u inserted."""
    half = sym.Rational(1,2)
    if damping == 'zero':
        R = u(t+dt) - u(t) - dt*V - \
            half*dt**2*(F.subs(t, 0)/m) + half*dt**2*(c/m)*I
    elif damping == 'linear':
        R = u(t+dt) - (I + dt*V + \
            half*(dt**2/m)*(-b*V - c*I + F.subs(t, 0)))
    else:   # damping is nonlinear
        R = u(t+dt) - (I + dt*V + \
            half*(dt**2/m)*(-b*V*abs(V) - c*I + F.subs(t, 0)))
    R = R.subs(t, 0)  # t=0 in the rhs of the first step eq.
    return sym.simplify(R)

def DtDt(u, dt):
    """Return 2nd-order finite difference for u_tt.
    u is a symbolic Python function of t.
    """
    return (u(t+dt) - 2*u(t) + u(t-dt))/dt**2

def D2t(u, dt):
    """Return 2nd-order finite difference for u_t.
    u is a symbolic Python function of t.
    """
    return (u(t+dt) - u(t-dt))/(2.0*dt)

def Dt_p_half(u, dt):
    """Return 2nd-order finite difference for u_t, sampled at n+1/2,
    i.e, n pluss one half... u is a symbolic Python function of t.
    """
    return (u(t+dt) - u(t))/dt

def Dt_m_half(u, dt):
    """Return 2nd-order finite difference for u_t, sampled at n-1/2,
    i.e, n minus one half.... u is a symbolic Python function of t.
    """
    return (u(t) - u(t-dt))/dt

def main(u, damping):
    """
    Given some chosen solution u (as a function of t, implemented
    as a Python function), use the method of manufactured solutions
    to compute the source term f, and check if u also solves
    the discrete equations.
    """
    print '=== Testing exact solution: %s ===' % u(t)
    print "Initial conditions u(0)=%s, u'(0)=%s:" % \
          (u(t).subs(t, 0), sym.diff(u(t), t).subs(t, 0))

    # Method of manufactured solution requires fitting F
    global F  # source term in the ODE
    F = sym.simplify(ode_source_term(u, damping))

    # Residual in discrete equations (should be 0)
    print 'residual step1:', residual_discrete_eq_step1(u, damping)
    print 'residual:', residual_discrete_eq(u, damping)

def linear(damping):
    def u_e(t):
        """Return chosen linear exact solution."""
        # General linear function u_e = c*t + d
        # Initial conditions u(0)=I, u'(0)=V require c=V, d=I
        return V*t + I

    main(u_e, damping)

def quadratic(damping):
    # Extend with quadratic functions
    q = sym.Symbol('q')  # arbitrary constant in quadratic term

    def u_e(t):
        return q*t**2 + V*t + I

    main(u_e, damping)

def cubic(damping):
    r, q = sym.symbols('r q')

    main(lambda t: r*t**3 + q*t**2 + V*t + I, damping)

def solver(I, V, F, b, c, m, dt, T, damping):
    """
    Solve m*u'' + f(u') + c*u = F for t in (0,T], u(0)=I and u'(0)=V,
    by a central finite difference method with time step dt.
    F(t) is a callable Python function.
    """
    dt = float(dt)
    Nt = int(round(T/dt))
    u = np.zeros(Nt+1)
    t = np.linspace(0, Nt*dt, Nt+1)

    if damping == 'zero':
        u[0] = I
        u[1] = u[0] - 0.5*dt**2*(c/m)*u[0] + \
               0.5*dt**2*F(t[0])/m + dt*V
        for n in range(1, Nt):
            u[n+1] = 2*u[n] - u[n-1] - \
                     dt**2*(c/m)*u[n] + dt**2*F(t[n])/m
    elif damping == 'linear':
        u[0] = I
        u[1] = u[0] + dt*V + \
               0.5*(dt**2/m)*(-b*V - c*u[0] + F(t[0]))
        for n in range(1, Nt):
            u[n+1] = (2*m*u[n] + (b*dt/2.-m)*u[n-1] + \
                     dt**2*(F(t[n])-c*u[n]))/(m+b*dt/2.)
    else:    # damping is quadratic
        u[0] = I
        u[1] = u[0] + dt*V + \
               0.5*(dt**2/m)*(-b*V*abs(V) - c*u[0] + F(t[0]))
        for n in range(1, Nt):
            u[n+1] = 1./(m+b*abs(u[n]-u[n-1])) * \
                     (2*m*u[n] - m*u[n-1] + b*u[n]*\
                     abs(u[n]-u[n-1])+dt**2*(F(t[n])-c*u[n]))
    return u, t

def test_quadratic_exact_solution(damping):
    # Transform global symbolic variables to functions and numbers
    # for numerical computations

    global p, V, I, b, c, m
    p, V, I, b, c, m = 2.3, 0.9, 1.2, 2.1, 1.6, 1.3 # i.e., as numbers
    global F, t
    u_e = lambda t: p*t**2 + V*t + I
    F = ode_source_term(u_e, damping) # fit source term
    F = sym.lambdify(t, F)            # ...numerical Python function

    from math import pi, sqrt
    dt = 2*pi/sqrt(c/m)/10   # 10 steps per period 2*pi/w, w=sqrt(c/m)
    u, t = solver(I=I, V=V, F=F, b=b, c=c, m=m, dt=dt,
                  T=(2*pi/sqrt(c/m))*2, damping=damping)
    u_e = u_e(t)
    error = np.abs(u - u_e).max()
    tol = 1E-12
    assert error < tol
    print 'Error in computing a quadratic solution:', error

if __name__ == '__main__':
    damping = ['zero', 'linear', 'quadratic']
    for e in damping:
        V, t, I, dt, m, b, c = sym.symbols('V t I dt m b c')  # global
        F = None  # global variable for the source term in the ODE
        print '---------------------------------------Damping:', e
        linear(e)  	# linear solution used for MMS
        quadratic(e)   	# quadratic solution for MMS
        cubic(e)       	# ... and cubic
        test_quadratic_exact_solution(e)


from __future__ import division

import numpy as np
from scipy.interpolate import interp1d
from scipy.sparse.linalg import spsolve
from scipy.sparse import spdiags, coo_matrix
from matplotlib import pyplot as plt

def ode_fe(func,c=-1.,d=0.,a=0.,b=1.,alpha=2.,beta=4.,x=np.linspace(0.,1.,5+1)):
	# A Simple Finite Element Scheme to solve BVP's of the form
	# u''(x) + c*u'(x) + d*u(x) = f(x), x \in [a,b]
	# u(a) = alpha
	# u(b) = beta
	# Dirichlet boundary conditions
	#
	# U_0 = alpha, U_1, U_2, ..., U_m, U_{m+1} = beta
	# We use m+1 subintervals, giving m algebraic equations

	N = len(x)
	# Number of finite elements is N-1
	# Number of basis functions is N

	rows, columns, data = np.zeros(3*(N-2)+2), np.zeros(3*(N-2)+2), np.zeros(3*(N-2)+2)
	for j in range(0,N-2):
		rows[3*j:3*(j+1)] = np.array([j+1,j+1,j+1])
		columns[j*3:(j+1)*3] = np.array(range(0,3))+j
	rows[-2:]= np.array([0,N-1])
	columns[-2:]= np.array([0,N-1])

	data[-1], data[-2] = 1,1
	for i in range(1,N-1):
		data[3*(i-1)+2] = 1./(x[i+1]-x[i]) + c*1./2. + d*(x[i+1]-x[i])/6. # i, i+1 location
		data[3*(i-1)+1] = -( 1./(x[i+1]-x[i]) + 1./(x[i]-x[i-1])
												) + c*0. + d*(x[i+1]-x[i-1])/3. # i, i location
		data[3*(i-1)] = 1./(x[i]-x[i-1]) - c*1./2. + d*(x[i]-x[i-1])/6. # i, i-1 location
	A = coo_matrix((data, (rows,columns)), shape=(N,N))

	B = np.zeros(N)
	B[0], B[-1] = alpha, beta
	for j in range(1,N-1):
		B[j]= ( (x[j]-x[j-1])*func((x[j]+x[j-1])/2.)*(1./2.) +
				(x[j+1]-x[j])*func((x[j+1]+x[j])/2.)*(1./2.)   )

	solution = spsolve(A.asformat('csr'),B)
	return x, solution

def Example3(N,epsilon):
	x, y = ode_fe(func=lambda x:-1./epsilon,c=-1./epsilon,x=np.linspace(0,1,N))
	plt.plot(x,y,'-ro')
	plt.axis([0.,1.1,.8,3.2])
	plt.show()
	return x, y

def NonlinearGrid():
	def asol(x,epsilon):
		A = []
		for item in x:
			A.append(alpha+item + (beta-alpha-1.)*(np.exp(item/epsilon) -1.)/(np.exp(1./epsilon) -1.)	)
		return x, np.array(A)

	epsilon = .01
	N=15
	X = np.linspace(0,1,N)
	x1, y1 = ode_fe(func=lambda x:-1./epsilon,c=-1./epsilon,x=X)
	x2, y2 = ode_fe(func=lambda x:-1./epsilon,c=-1./epsilon,x=X**(1./8.))
	alpha, beta = 2.,4.
	# Analytic solution
	Z =asol(np.linspace(0,1,500),epsilon)

	plt.plot(Z[0],Z[1],'-k',label='Solution',linewidth=3)
	plt.plot(x1,y1,'--bo',label='Evenly spaced grid points',linewidth=1.5)
	# plt.plot(Z[0],Z[1],'-k',mfc="None",label='Solution',linewidth=2)
	plt.plot(x2,y2,'--ro',label='Clustered grid points',linewidth=1.5)
	plt.axis([0.,1.1,1.8,4.2])
	plt.legend(loc='best')
	# plt.savefig('FEM_compare_methods.pdf')
	plt.show()
	plt.clf()

	X = np.linspace(0,1,500)
	plt.plot(X,abs(Z[1]-interp1d(x2,y2)(X)),'-r')
	plt.plot( X,abs( Z[1]-interp1d(x1,y1)(X) ), '-b' )

	# print "Max Error = ", np.max(np.abs(Z[1]-interp1d(x2,y2)(X) ))
	plt.show()

def cheb(N):
	def p(j1):
		if (j1==0 or j1 == N): return 2.
		else: return 1.

	x = np.cos(np.pi*np.arange(N+1)/N)
	D = np.zeros((N+1,N+1))
	# j represents column index
	for j in range(0,N+1):
		for i in range(0,j)+range(j+1,N+1):
			D[i,j] = ((-1.)**(i+j))*p(i)/( p(j)*(x[i]- x[j]) )

	# Values on the main diagonal
	for j in xrange(1,N):
		D[j,j] = -x[j]/(2.*(1-x[j]**2.))
	D[0,0] = (1.+2.*N**2.)/6.
	D[N,N] = -(1.+2.*N**2.)/6.
	return D,x

def barycentric_interpolation_Chebychev(u,x,N,xx):
	uu = np.zeros(xx.shape)
	a = np.ones(N+1); a[1::2] = -1
	for x0 in range(len(xx)):
		flag = False
		for item1,item2 in zip(x,u):
			if abs(xx[x0] - item1)<1e-15:
					flag = True
					uu[x0] = item2
		if flag == False:
			b = xx[x0] - x
			b[0] *= 2.
			b[-1] *= 2.
			uu[x0] = np.sum((a*u)/b)/np.sum(a/b)
	return uu

class ODESolverSpectral:
	def __init__(self,N,order,lift,ode,dim=1):
		# N grid points, N-2 interior grid points,
		# and N-1 subintervals, so self.N-1 will index
		# the Nth grid point using Python indexing.
		# The interior grid points will be indexed by
		# 1:self.N-1
		self.N = N
		self.order = order
		self.lift = lift
		# ode must be a function with five arguments,
		# u,x, y= list of differentiation matrices,
		# g = lifting function, and lmbda = parameter.
		self.ode = ode
		self.dim = dim
		self.guess = np.zeros(self.dim*(self.N-2))

	def derivs(self):
		(D,x) = cheb(self.N-1)
		self.cheb_points = x
		self.operators = [[]]
		operator = D
		# print operator[1:self.N-1,1:self.N-1].shape
		# print self.cheb_points[1:self.N-1].shape
		for j in range(1,self.order+1):
			self.operators.append(operator[1:self.N-1,1:self.N-1])
			operator = operator.dot(D)
		# A_, D_, x_ = np.dot(D,D)[1:N-1,1:N-1], D[1:N-1,1:N-1], x[1:N-1]

	def ode_solve(self,output_grid):
		self.ode_func = (lambda u,x_=self.cheb_points[1:self.N-1],
										y=self.operators,G=self.lift: self.ode(u,x_,y,G)
										)
		sol = root(self.ode_func, self.guess )
		# self.sol = np.zeros((self.dim, len(self.cheb_points)))
		self.sol = np.zeros(self.cheb_points.shape)
		self.sol[1:self.N-1] = sol.x
		# print sol.x.shape, self.sol.shape
		# for j in range(self.dim):
		#	print sol.x[0:self.N-2].shape
				# print self.dim, j
				# self.sol[j:1:self.N-1] = sol.x[(j)*(self.N-2):(j+1)*(self.N-2)]
		output_values = barycentric_interpolation_Chebychev(self.sol,
																self.cheb_points,self.N-1,output_grid)
		return output_values

	def new_guess(self,new_guess):
		self.guess = new_guess

# Example3(N=100,epsilon=.02)
# NonlinearGrid()


import numpy as np
import numpy.linalg as ln
from tabulate import tabulate
import matplotlib.pyplot as plt

def makePlot(fun,c,trueData):
    x = np.linspace(-5,5)
    f = fun(x)
    # np.polyval
    # cF = lambda a: np.sum([np.power(a,i) * c[i] for i in range(len(c))])
    cF = np.polyval(np.flip(c),x)
    # print(x)
    plt.title("f_theta VS. interpolating polynomial")
    plt.xlabel("x-axis")
    plt.ylabel("y-axis")
    plt.plot(x,f,'r',label="Original Function")
    plt.plot(x,cF,'b',label="Poly Function using Coeffiecients")
    plt.legend(loc="upper left")
    for i in trueData:
        plt.plot(i[0],i[1], 'bo')
    plt.show()

def main(theta):
    print("Running with theta value of: {}".format(theta))
    #Part 1
    f = lambda x : 1/(1+np.exp(-theta*x))
    tData = np.linspace(-5,5,num=7)
    trueTable = [[t,f(t)] for t in tData]
    header = [["x","f(x)"]]
    print("Part 1: Input and Output of Function")
    print(tabulate(header+ trueTable,headers="firstrow",tablefmt='grid') + "\n")

    #Part 2
    a = [[np.power(trueTable[i][0],t) for t in range(len(trueTable))] for i in range(len(trueTable))]
    b = [[t[1]]for t in trueTable]
    c = ln.solve(a,b)
    print("Part 2: Coeffiecients for Polynomial")
    header = [["x^n","C"]]
    print(tabulate(header + [[i,c[i]]for i in range(len(c))],headers="firstrow",tablefmt='grid') + "\n")
    # makePlot(f,c,trueTable)
    #TODO Make Table

    #Part 3
    nX = np.linspace(-5,5,num=101)
    nY = [[f(t)]for t in nX]
    print("The Mean for Part 3: {}".format(np.average(nY)))
    print("The Standard Deviation for Part 3: {}".format(np.std(nY)))

    #Part 4
    err = 0
    nCF = [np.polyval(np.flip(c),i) for i in nX]
    # cF = lambda a: np.polyval(np.flip(c),a)
    for i in range(len(nX)):
        temp = np.abs(nY[i][0] - nCF[i][0])/np.abs(nY[i][0])
        if temp > err:
            err = temp
    print("Maximum Error: {}".format(err))

main(1)
# print("\n\n\n")
#Part 5
main(10)


# Authored by Johan Blomberg and Gustav Grännsjö, 2020

"""
A collection of functions for generating specific envelope shapes.
"""

import numpy as np
from scipy.stats import norm

def sin2(nr_samples):
    x = np.linspace(0.0, 1.0, nr_samples)
    return np.sin(np.pi * x)**2

def sin_p(p, nr_samples):
    x = np.linspace(0.0, 1.0, nr_samples)
    return np.sin(np.pi * x)**p

def sinc(lim, nr_samples):
    x = np.linspace(-lim, lim, nr_samples)
    return np.sinc(x)

def triangle(nr_samples):
    if nr_samples % 2 == 0:
        t1 = np.linspace(0.0, 1.0, nr_samples // 2)
        t2 = np.linspace(1.0, 0.0, nr_samples // 2)
        return np.concatenate((t1, t2))
    else:
        t1 = np.linspace(0.0, 1.0, nr_samples // 2, endpoint=False)
        t2 = np.flip(t1)
        return np.concatenate((t1, [1], t2))

def cool(nr_samples):
    x = np.linspace(0.0, 1.0, nr_samples)
    s = np.sin(4 * np.pi * x)
    t = triangle(nr_samples)
    return t * s

def gaussian(nr_samples, trunc):
    x = np.linspace(-trunc, trunc, nr_samples)
    y = norm.pdf(x, 0, 1)
    # Normalise
    return y / y.max()