import numpy as np
import matplotlib.pyplot as plt

def f(x, y):
    return (np.sqrt(x**4-2*x**2*y**2+y**4+4)/(x+y))+x+y

def grad_x(x, y):
    return((x+y)**2*np.sqrt(x**4 - 2*x**2*y**2 + y**4 + 4) + x**4 + 2*x**3*y - 2*x*y**3 - y**4 - 4)/((x+y)**2*np.sqrt(x**4 - 2*x**2*y**2 + y**4 + 4))

def grad_y(x, y):
    return((x+y)**2*np.sqrt(x**4 - 2*x**2*y**2 + y**4 + 4) - x**4 - 2*x**3*y + 2*x*y**3 + y**4 - 4)/((x+y)**2*np.sqrt(x**4 - 2*x**2*y**2 + y**4 + 4))

x_min = 0.707106
y_min = x_min

x = [2.25]
y = [3]
pkt = [f(x[-1], y[-1])]
# x = np.array([2.25])
# y = np.array([3])
# pkt = np.array([f(x[-1], y[-1])])

krok = 1
dok = 1e-5
licz = 0
jeszcze = True
while jeszcze:
    licz += 1

    grx = grad_x(x[-1], y[-1])
    gry = grad_y(x[-1], y[-1])
    nx = x[-1] - grx * krok
    ny = y[-1] - gry * krok
    npkt = f(nx, ny)

    if npkt < pkt[-1]:
        x.append(nx)
        y.append(ny)
        pkt.append(npkt)
        # np.append(x, [nx])
        # np.append(y, [ny])
        # np.append(pkt, [npkt])
    else:
        x.append(x[-1])
        y.append(y[-1])
        pkt.append(pkt[-1])
        # np.append(x, [x[-1]])
        # np.append(y, [y[-1]])
        # np.append(pkt, [pkt[-1]])
        krok /= 2
        bl = npkt/(np.sqrt(grx**2+gry**2))

    if krok < dok :
        jeszcze = False

fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(x, y, '-k')
ax.plot(x_min, y_min, 'xb')
ax.plot(x[-1], y[-1], '+k')
ax.axis('equal')
x0 = 0.1
xk = 3
Nx = 500
x = np.linspace(x0, xk,Nx)
y0=0.1
yk= 2.5
Ny = 500
y = np.linspace(y0, yk, Ny)
X,Y = np.meshgrid(x,y)
Z= f(X,Y)
ax.contour(X,Y,Z,15)
plt.show()


# BSM Model

from math import sqrt

import numpy as np
from scipy.stats import norm

def N(x):
    return norm.cdf(x)

def d1(S0, K, r, sigma, T):
    return (np.log(S0 / K) + (r + sigma * sigma / 2) * T) / (sigma * sqrt(T))

def d2(S0, K, r, sigma, T):
    return d1(S0, K, r, sigma, T) - sigma * sqrt(T)

def bsm_call_price(S0, K, r=0.01, sigma=0.1, T=1):
    return S0 * N(d1(S0, K, r, sigma, T)) - K * np.exp(-r * T) * N(d2(S0, K, r, sigma, T))

def bsm_put_price(S0, K, r=0.01, sigma=0.1, T=1):
    return -S0 * N(-d1(S0, K, r, sigma, T)) + K * np.exp(-r * T) * N(-d2(S0, K, r, sigma, T))

def bsm_price(S0, K, r=0.01, sigma=0.1, T=1, is_call=True):
    if is_call:
        return bsm_call_price(S0, K, r, sigma, T)
    else:
        return bsm_put_price(S0, K, r, sigma, T)


from math import fabs
from typing import Union

import numpy as np
from numpy.lib.stride_tricks import sliding_window_view

from jesse.helpers import get_candle_source, slice_candles, same_length

def fwma(candles: np.ndarray, period: int = 5, source_type: str = "close", sequential: bool = False) -> Union[
    float, np.ndarray]:
    """
    Fibonacci's Weighted Moving Average (FWMA)

    :param candles: np.ndarray
    :param period: int - default: 5
    :param source_type: str - default: "close"
    :param sequential: bool - default: False

    :return: float | np.ndarray
    """

    # Accept normal array too.
    if len(candles.shape) == 1:
        source = candles
    else:
        candles = slice_candles(candles, sequential)
        source = get_candle_source(candles, source_type=source_type)

    fibs = fibonacci(n=period)
    swv = sliding_window_view(source, window_shape=period)
    res = np.average(swv, weights=fibs, axis=-1)

    return same_length(candles, res) if sequential else res[-1]

def fibonacci(n: int = 2) -> np.ndarray:
    """Fibonacci Sequence as a numpy array"""
    n = (int(fabs(n)) if n >= 0 else 2) - 1
    a, b = 1, 1

    result = np.array([a])

    for _ in range(n):
        a, b = b, a + b
        result = np.append(result, a)

    fib_sum = np.sum(result)
    return result / fib_sum if fib_sum > 0 else result


import matplotlib.pyplot as plt
import numpy as np

def logistic_map(x0, r, n):
    xs = [x0]
    x = x0
    for i in range(n):
        x = r * x * (1 - x)
        xs.append(x)
    return xs

def bifurcation_diagram(x0, r_min, r_max, r_step, n, k):
    fig, ax = plt.subplots()
    r_range = np.arange(r_min, r_max + r_step, r_step)
    cycles = {}
    for r in r_range:
        xs = logistic_map(x0=x0, r=r, n=n)[k:]
        xs = np.unique(xs)
        cycles[r] = len(xs)
        ax.scatter(np.full_like(xs, r), xs, c='b', s=r_step)
    return cycles

if __name__ == '__main__':
    # bifurcation_diagram(x0=0.2, r_min=2.8, r_max=3.9, r_step=0.01, n=1000, k=50)
    bifurcation_diagram(x0=0.5, r_min=3.84, r_max=3.8571, r_step=0.001, n=1000, k=50)
    plt.show()


    import numpy as np

    def find_distance(row_A, row_B):
        """
        Returns Euclidean distance between 2 rows of data.

            Parameters:
                    row_A (list): vector of numerical data
                    row_B (list): vector of numerical data

            Returns:
                 Euclidean Distance (float)
        """
        # row_A = np.array(row_A)
        # row_B = np.array(row_B)

        # set distance to start at 0
        dist = 0.0
        # iterate through row_A
        for i in range(len(row_A)):
            # subtract the rows element wise, square the difference, then add
            #. it to the distance
            dist += (row_A[i] - row_B[i])**2

        # return the square root of the total distance
        return np.sqrt(dist)


# We will learn how to write our own code for the Ks-neighbor algorithm
# starting with an introduction on the Euclidean distance
"""
Define:
Euclidean Distance - Practical Machine Learning

"""
from math import sqrt
import numpy as np
import warnings                     # to warn the user when they using a wrong number for k
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
from matplotlib import style
style.use('fivethirtyeight')
from collections import Counter     # to create the vote basically

# Simple example on programming the Euclidean distance
point1 = [1,3]
point2 = [2,5]
def euclidean_distance(point1,point2):
    return sqrt((point1[0]-point2[0])**2+(point1[1]-point2[1])**2)
ed1 = euclidean_distance(point1,point2)
print('euclidean_distance = ', ed1)

Machine_Learning_Old_Files/[15] Writing our own Ks-neighbor algorithm.py


# -*- coding: utf-8 -*-
"""
helper function: convert Hz to Mel scale

  Args:
    fInHz: The frequency to be converted, can be scalar or vector
    cModel: The name of the model ('Fant' [default], 'Shaughnessy', 'Umesh')

  Returns:
    Mel values of the input dimension
"""

import numpy as np
import math

def ToolFreq2Mel(fInHz, cModel = 'Fant'):
    def acaFant_scalar(f):
        return 1000 * math.log2(1 + f/1000)
    def acaShaughnessy_scalar(f):
        return 2595 * math.log10(1 + f/700)
    def acaUmesh_scalar(f):
        return f/(2.4e-4*f + 0.741)

    f = np.asarray(fInHz)
    if f.ndim == 0:
        if cModel == 'Shaughnessy':
            return acaShaughnessy_scalar(f)
        elif cModel == 'Umesh':
            return acaUmesh_scalar(f)
        else:
            return acaFant_scalar(f)

    fMel = np.zeros(f.shape)
    if cModel == 'Shaughnessy':
        for k,fi in enumerate(f):
            fMel[k] =  acaShaughnessy_scalar(fi)
    elif cModel == 'Umesh':
        for k,fi in enumerate(f):
            fMel[k] =  acaUmesh_scalar(fi)
    else:
        for k,fi in enumerate(f):
            fMel[k] =  acaFant_scalar(fi)

    return (fMel)


# Implementation of gaussInferParamsMean1d
# Author: Animesh Gupta

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

priorVar = [1, 5]
Sigma = 1 # Assumed to be known
X = 3
post_mu = 0
post_Sigma = 0

for i in priorVar:

    prior_Sigma = i
    prior_mu    = 0
    xbar = np.mean(X)
    n    = np.size(X)

    lik_Sigma = Sigma
    lik_mu    = xbar

    S0    = prior_Sigma
    S0inv = 1./S0
    mu0   = prior_mu
    S     = Sigma
    Sinv  = 1./S
    Sn    = 1./(S0inv + n*Sinv)

    post_mu    = Sn*(n*Sinv*xbar + S0inv*mu0)
    post_Sigma = Sn

    x = np.arange(-5,5.25, 0.25)

    fig = plt.figure(figsize = (4, 4))
    plt.ylim(0,0.6)
    plt.xlim(-5,5)
    plt.xticks([-5,0,5])
    plt.plot(x, multivariate_normal.pdf(x, mean = prior_mu, cov = prior_Sigma), color = "blue",
             label = "prior")
    plt.plot(x, multivariate_normal.pdf(x, mean = lik_mu, cov = lik_Sigma), color = "red",
             label = "lik",linestyle='dotted')
    plt.plot(x, multivariate_normal.pdf(x, mean = post_mu, cov = post_Sigma), color = "black",
             label = "post",linestyle='dashdot')
    plt.title(f"prior variance of {i}")
    plt.legend(loc="upper left")
    pml.savefig(f"gauss_infer_1d_{i}.pdf")


# Resolving sets of congruent equations using Chinese remainder theorem
# Rafał Lal 03/06/2021
import sys
from math import gcd, lcm, floor
from itertools import product
from functools import reduce
import numpy

nums = []
mods = []

def check_req():
    if numpy.prod(mods) != reduce(lcm, mods):
        return False
    prod = [x for x in product(mods, mods) if x[0] != x[1]]
    for i in range(len(prod)):
        if gcd(prod[i][0], prod[i][1]) != 1:
            return False
    return True

def extended_euclid(x, y):
    x0, x1, y0, y1 = 1, 0, 0, 1
    while y > 0:
        q, x, y = floor(x / y), y, x % y
        x0, x1 = x1, x0 - q * x1
        y0, y1 = y1, y0 - q * y1
    return q, x0, y0

def inversed_mod(a, m):
    g, x, y = extended_euclid(a, m)
    return x % m

def chinese_remainder_theorem():
    n = numpy.prod(mods)
    ni, mi, res = [], [], []
    for i in range(len(mods)):
        ni.append(int(n / mods[i]))
        mi.append(int(inversed_mod(ni[i], mods[i])))
        res.append(int(nums[i] * ni[i] * mi[i]))
    try:
        res = sum(res) % n
    except ArithmeticError:
        res = sum(res)
    return n, ni, mi, res

def main():
    print('Insert congruent equation (q to break): ')
    while True:
        char = input('x {} '.format(chr(0x2261)))
        if char == 'q':
            break
        nums.append(int(char))
        char = int(input('mod '))
        mods.append(int(char))

    if not check_req():
        print('Equation does not fulfil requirments')
        sys.exit()

    n, ni, mi, result, = chinese_remainder_theorem()
    print('\nResults:\nn =', n, sep=' ')
    print('Ni =', *ni, sep=' ')
    print('Mi =', *mi, sep=' ')
    print('x {} {} (mod {})'.format(chr(0x2261), result, n))

    return

if __name__ == '__main__':
    main()


"""
    MÉTODO PUNTO MEDIO O EULER MEJORADO
    __ funciones__
    complete_integrate: devuelve soluciones de nodos y soluciones
    succ_integrate: dado un nodo devuelve la siguiente solución

    __ significado variables __
    X,Y     = integrate(F,x0,y0,xfinal,N).
    {y}'    = {F(x,{y})}, donde
    {y}     = {y[0],y[1],...y[N-1]}.
    x0,y0   = condiciones iniciales
    xfinal  = valor final de la variable x
    h       = incremento de x usado para la integración
    F       = función suplida por el usuario que devuelve
            el array F(x,y) = {y'[0],y'[1],...,y'[N-1]}.
"""
import numpy as np
def succ_euler(F, x,y,h):
    '''método de euler explícito
    F (x,y) función derivada
    x, y valores de los que se parte
    incremento
    Salida : y_{n+1}
    '''
    return y + h* F(x,y)

def succ_integrate(F,x,y,h):
    '''PUNTO MEDIO siguiente solución
    '''
    return y + h*F(x+h/2, succ_euler(F,x,y,h/2))

def complete_integrate(F,x0, y0,xfinal, N ):
    X = np.linspace(x0,xfinal,N+1)
    Y = [y0]
    h = (xfinal - x0)/N

    for n in range(N):
        Y.append(succ_integrate(F,X[n],Y[n],h))
    return X,Y


from sympy import *
from quaternion_R_utils import *
from plucker_line_utils import *

fx,fy,cx,cy = symbols('fx fy cx cy');
px, py, pz = symbols('px py pz')
q0, q1, q2, q3 = symbols('q0 q1 q2 q3')
mx_w, my_w, mz_w, lx_w, ly_w, lz_w = symbols('mx_w my_w mz_w lx_w ly_w lz_w')
u_s,v_s = symbols('u_s v_s')
u_e,v_e = symbols('u_e v_e')

position_symbols = [px, py, pz]
quaternion_symbols = [q0, q1, q2, q3]
plucker_line_symbols = [mx_w, my_w, mz_w]
all_symbols = position_symbols + quaternion_symbols + plucker_line_symbols

RT_wc = matrix44FromQuaternion(px, py, pz, q0, q1, q2, q3)
K=plucker_line_K(fx, fy, cx, cy)
mm_cw = plucker_line_motion_matrix_cw(RT_wc)
l_w = Matrix([[mx_w],[my_w],[mz_w],[lx_w],[ly_w],[lz_w]])
l_c = mm_cw*l_w
m_c=Matrix([[l_c[0,0]],[l_c[1,0]],[l_c[2,0]]])
l=K*m_c

x_s=Matrix([[u_s,v_s,1]])
x_e=Matrix([[u_e,v_e,1]])
xs_delta = Matrix([[0]]) - (x_s*l)/sqrt(l[0,0]*l[0,0] + l[1,0]*l[1,0])
xe_delta = Matrix([[0]]) - (x_e*l)/sqrt(l[0,0]*l[0,0] + l[1,0]*l[1,0])

obs_eq = Matrix([xs_delta, xe_delta]).vec()
obs_eq_jacobian = obs_eq.jacobian(all_symbols)

print(obs_eq)
print(obs_eq_jacobian)

with open("perspective_camera_plucker_line_quaternion_wc_jacobian.h",'w') as f_cpp:
    f_cpp.write("inline void observation_equation_perspective_camera_plucker_line_quaternion_wc(Eigen::Matrix<double, 2, 1> &delta, double fx, double fy, double cx, double cy, double px, double py, double pz, double q0, double q1, double q2, double q3, double mx_w, double my_w, double mz_w, double lx_w, double ly_w, double lz_w, double u_s, double v_s, double u_e, double v_e)\n")
    f_cpp.write("{")
    f_cpp.write("delta.coeffRef(0,0) = %s;\n"%(ccode(obs_eq[0,0])))
    f_cpp.write("delta.coeffRef(1,0) = %s;\n"%(ccode(obs_eq[1,0])))
    f_cpp.write("}")
    f_cpp.write("\n")
    f_cpp.write("inline void observation_equation_perspective_camera_plucker_line_quaternion_wc_jacobian(Eigen::Matrix<double, 2, 7, Eigen::RowMajor> &j, double fx, double fy, double cx, double cy, double px, double py, double pz, double q0, double q1, double q2, double q3, double mx_w, double my_w, double mz_w, double lx_w, double ly_w, double lz_w, double u_s, double v_s, double u_e, double v_e)\n")
    f_cpp.write("{")
    for i in range (2):
        for j in range (7):
            f_cpp.write("j.coeffRef(%d,%d) = %s;\n"%(i,j, ccode(obs_eq_jacobian[i,j])))
    f_cpp.write("}")


import numpy as np
# B/B,B/W,W/W,B/B

# inspired by https://github.com/cavaunpeu/statistical-rethinking/blob/master/chapter-2/homework.R
#[b/b, b/w, w/w,b/b]
# in which ways can they produce 1st black (cause there are 2x b/b they can produce 2*2 first black)
ways_producing_black = np.array([4.,1.,0.])

priors = ways_producing_black / ways_producing_black.sum()
print(priors)

#[b/b,b/w,w/w]
# and can produce second black
ways_can_produce_black_on_other_side = np.array([4., 0., 0.])
posterior = ways_can_produce_black_on_other_side*priors/ways_can_produce_black_on_other_side.sum()
print(posterior)
prob_black_other_side = posterior[0]
print('prob that other side is black = ' + str(prob_black_other_side))

# inspired by https://rpubs.com/andersgs/my_solutions_chapter2_statrethink
# b1/b2, b1/w1, w1/w2, b3/b4
#
# card |pick|other side
#
# b1/b2| 1 | 1
# b2/b1| 1 | 1
# b1/w1| 1 | 0
# w1/b1| 0 | 0
# w1/w2| 0 | 0
# w2/w1| 0 | 0
# b3/b4| 1 | 1
# b4/b3| 1 | 1
#
# to produce black on other side 4
# from to produce black on the first side 5
# 4/5 ~ 0.8

#bayes theorem
# probability that the second side is black given that the first side is black
# P(2black|1black) = P(1black|2black)*P(2black)/P(1black)
# probability that a card has black on both sides is 2 card from 4 cards
# P(2black) = 2/4 = 1/2
# probability that a card has 1 black side from 8 producing ways of card settings
# is 5, so 5/8 = 5/8
# P(1black) = 5/8
# probability that 1st side is black given the second side is black, e.g.
# that both sides are black is 1
# P(1black|2black) = 1 (hm)
# 1*1/2 / 5/8 ~ 0.8


import pandas as pd
from matplotlib import pyplot as plt
import scipy.stats as stats
from pandas import DataFrame, Series
import seaborn as sns
import pymc3 as pm

true_prob_a = 0.05
n_a = 1500
true_prob_b = 0.04
n_b = 750

obs_a_data = Series(stats.bernoulli.rvs(true_prob_a, size=n_a), index=range(0, n_a))
obs_b_data = Series(stats.bernoulli.rvs(true_prob_b, size=n_b), index=range(0, n_b))

# set prior on a
# note: can take samples from this
with pm.Model() as model:
    p_a = pm.Uniform('p_a', lower=0, upper=1)
    p_b = pm.Uniform('p_b', lower=0, upper=1)

# now update with actual obs
# CANNOT take samples from this
with model:
    # what does it mean that pm.Bernoulli takes p_a?
    # i guess that we're chaining these things together
    obs_a = pm.Bernoulli('obs_a', p_a, observed=obs_a_data)
    obs_b = pm.Bernoulli('obs_b', p_b, observed=obs_b_data)

# now run model (explained later)
# note: not referring obs_a or obs_b anywhere in here
# fits whole system
with model:
    step = pm.Metropolis()
    trace = pm.sample(18000, step=step)
    burned_trace = trace[1000:]

# now this is a bunch of samples from the posterior
ps_a = Series(burned_trace['p_a'])
ps_b = Series(burned_trace['p_b'])
diff = ps_a - ps_b

ps = pd.concat([
    ps_a.to_frame('p').assign(test = 'a'),
    ps_b.to_frame('p').assign(test = 'b'),
    diff.to_frame('p').assign(test = 'diff')], ignore_index=True)

# now plot
# note: should re-anki FacetGrid thing, didn't remember
plt.clf()
g = (sns.FacetGrid(ps, hue='test', col='test')
     .map(sns.distplot, 'p'))
g.add_legend()


#!/usr/bin/env python3

import numpy as np
from tqdm import tqdm

def step(x):
    """step function:
    outputs 1 if x > 0
    and -1 otherwise"""
    xout = np.ones_like(x)
    xout[x<0] = -1
    return xout

def sigmoid(x):
    """sigmoid function"""
    return 1.0/(1.0 + np.exp(-x))

class cross_entropy_loss:
    def __init__(self):
        self.name = 'cross-entropy'

    def loss(self, y, y_pred, zerotol=1e-10):
        """the cross-entropy loss for each
        data instance

        Args:
            y (array): labels for each instance (0 or 1)
            y_pred (array): predicted probabilty that
                each instance belongs to class 1
        """
        loss = -(y * np.log(y_pred + zerotol) + (1 - y) * np.log(1 - y_pred + zerotol))
        return loss

    def derivative_loss(self, y, y_pred):
        """the derivative of the cross-entropy loss w.r.t.
        to sigmoid activation function (we will get to
        this later)

        Args:
            y (array): labels for each instance (0 or 1)
            y_pred (array): predicted probabilty that
                each instance belongs to class 1
        """
        return y_pred - y

class Perceptron:

    def __init__(self, n_in, activation=sigmoid, loss=cross_entropy_loss, b=None):
        """A simple Perceptron implementation;
        This implementation also contains the
        gradient descent algorithm (see the
        gradient_descent_step and train
        functions).

        Args:
            n_in (int): number of input features
            activation (function): activation function of the Perceptron;
                should only take x as input
            loss (function): the used loss function;
                This should be the cross_entropy
                loss for a sigmoid activation
            b (float): bias term; if a value is specified, the
                bias term is fixed at this value. if not,
                the bias will be estimated during training.
        """
        self.n_in = n_in
        self.w = np.random.uniform(-1,1,n_in)
        if b is None:
            self.b = np.random.uniform(-1,1,1)
            self.fit_b = True
        else:
            self.b = b
            self.fit_b = False
        self.activation = activation
        self.loss = loss().loss
        self.derivative_loss = loss().derivative_loss

    def predict(self, x):
        """Predict probability that each
        instance of x (with shape n_instances x n_features)
        belongs to class 1

        Args:
            x (ndarray): input data (n_instances x n_features)

        Returns:
            predicted probability for each instance
        """
        self.Z = np.dot(x, self.w) + self.b
        self.A = self.activation(self.Z)
        return self.A

    def gradient_descent_step(self, x, y, learning_rate):
        """A single gradient descent step.

        Args:
            x (ndarray): input data (n_instances x n_features)
            y (array): label of each instance (0 or 1)
            learning_rate (float): learning rate of the
                gradient descent algorithm
        """
        # compute derivative of loss wrt Z
        dZ = self.derivative_loss(y, self.predict(x))
        dW = np.dot(dZ, x)
        # subtract average derivative from weights
        self.w -= learning_rate * 1.0/dW.shape[0] * dW
        if self.fit_b:
            self.b -= learning_rate * (1.0/x.shape[0] * np.sum(dZ))

    def train(self, X, y, batch_size=8, learning_rate=1, n_steps=100):
        """Apply gradient descent algorithm.
        At each iteration, the algorithm will draw
        a random sample from x and perform a weight
        update with the partial derivatives that
        are computed by the use of this sample.

        Args:
            x (ndarray): input data (n_instances x n_features)
            y (array): label of each instance (indicated by 0 and 1)
            learning_rate (float): learning rate of the
                gradient descent algorithm
            n_steps (int): number of gradient descent
                iterations to perform during training
        """
        self.training_w = np.zeros((n_steps, self.n_in+1))
        self.training_loss = np.zeros(n_steps)
        for s in tqdm(range(n_steps)):
            # draw a random batch
            batch_idx = np.random.choice(X.shape[0], batch_size, replace=False)
            # compute and store mean loss
            self.training_loss[s] = np.mean(self.loss(y[batch_idx], self.predict(X[batch_idx])))
            # store current weights
            self.training_w[s,:self.n_in] = self.w
            self.training_w[s,-1] = self.b
            # perform a gradient descent step
            self.gradient_descent_step(X[batch_idx], y[batch_idx], learning_rate)


'''
calc_performance_metrics_for_proba_predictions.py

Provides implementation of common metrics for assessing a binary classifier's
*probabilistic* predictions against true binary labels, including:

* binary cross entropy from probabilities
* binary cross entropy from scores (real-values, to be fed into sigmoid)
'''

import numpy as np

from scipy.special import logsumexp as scipy_logsumexp
from scipy.special import expit as sigmoid

def calc_mean_binary_cross_entropy_from_probas(ytrue_N, yproba1_N):
    ''' Compute average cross entropy for given binary classifier's predictions

    Consumes probabilities ("probas"), values between 0.0 and 1.0.

    Computing BCE uses *base-2* logarithms, so the resulting number is a valid
    upper bound of the zero-one loss (aka error rate) when we threshold at 0.5.

    Notes
    -----
    Given a binary label $y_n \in \{0, 1}$ and a probability $p_n \in (0,1)$,
    we define binary cross entropy as:
    $$
        BCE(y_n, p_n) = - y_n \log_2 p_n - (1-y_n) \log_2 (1-p_n)
    $$
    Given $N$ labels and their predicted probas, we define the mean BCE as:
    $$
        mean_BCE(y, p) = \frac{1}{N} \sum_{n=1}^N BCE(y_n, p_n)
    $$

    Args
    ----
    ytrue_N : 1D array, shape (n_examples,) = (N,)
        All values must be either 0 or 1. Will be cast to int dtype.
        Each entry represents the binary 'true' label of one example
        One entry per example in current dataset
    yproba1_N : 1D array, shape (n_examples,) = (N,)
        All values must be within the interval 0.0 - 1.0, inclusive.
        Will be truncated to (eps, 1 - eps) to keep log values from extremes,
        with small value eps equal to 10^{-14}.
        Each entry is probability that that example should have positive label.
        One entry per example in current dataset.
        Needs to be same size as ytrue_N.

    Returns
    -------
    bce : float234erdf
        Binary cross entropy, averaged over all N provided examples

    Examples
    --------
    >>> N = 8
    >>> ytrue_N = np.asarray([0., 0., 0., 0., 1., 1., 1., 1.])

    # Try perfect predictions
    >>> perfect_proba1_N = np.asarray([0., 0., 0., 0., 1., 1., 1., 1.])
    >>> perfect_bce = calc_mean_binary_cross_entropy_from_probas(
    ...     ytrue_N, perfect_proba1_N)
    >>> print("%.4f" % perfect_bce)
    0.0000

    # Try some confident scores
    >>> good_proba1_N = np.asarray([0.01, 0.05, 0.1, 0.1, 0.9, 0.9, 0.95, 0.97])
    >>> good_bce = calc_mean_binary_cross_entropy_from_probas(
    ...     ytrue_N, good_proba1_N)
    >>> print("%.4f" % good_bce)
    0.1018

    # Try some decent but underconfident scores
    >>> ok_proba1_N = np.asarray([0.3, 0.4, 0.46, 0.47, 0.5, 0.6, 0.7, 0.71])
    >>> ok_bce = calc_mean_binary_cross_entropy_from_probas(
    ...     ytrue_N, ok_proba1_N)
    >>> print("%.4f" % ok_bce)
    0.7253

    # Try some mistakes that are way over confident
    >>> bad_pr1_N  = np.asarray([0., 0., 1., 0., 1., 1., 0., 0.])
    >>> bad = calc_mean_binary_cross_entropy_from_probas(ytrue_N, bad_pr1_N)
    >>> print("%.4f" % bad)
    17.4403

    # Try empty
    >>> empty_bce = calc_mean_binary_cross_entropy_from_probas([], [])
    >>> np.allclose(0.0, empty_bce)
    True
    '''

    if(len(ytrue_N) < 1):
        return 0
    if(len(yproba1_N) < 1):
        return 0
    # Cast labels to integer just to be sure we're getting what's expected
    ytrue_N = np.asarray(ytrue_N, dtype=np.int32)
    N = int(ytrue_N.size)
    # Cast probas to float and be sure we're between zero and one
    yproba1_N = np.asarray(yproba1_N, dtype=np.float64)           # dont touch
    yproba1_N = np.maximum(1e-14, np.minimum(1-1e-14, yproba1_N)) # dont touch

    return np.sum(- ytrue_N * np.log2(yproba1_N) - (1-ytrue_N) * np.log2(1-yproba1_N))/ytrue_N.shape[0]

def calc_mean_binary_cross_entropy_from_scores(ytrue_N, scores_N):
    ''' Compute average cross entropy for given binary classifier's predictions

    Consumes "scores", real values between (-np.inf, np.inf),
    and then conceptually produces probabilities by feeding into sigmoid,
    and then into the BCE function to get the result.

    In practice, we compute BCE directly from scores using an implementation
    that avoids the numerical issues of the sigmoid (saturation, if underflows
    to zero would become negative infinity). This clever implementation uses
    the "logsumexp" trick.

    Computing BCE uses *base-2* logarithms, so the resulting number is a valid
    upper bound of the zero-one loss (aka error rate) when we threshold at 0.5.

    Args
    ----
    ytrue_N : 1D array, shape (n_examples,) = (N,)
        All values must be either 0 or 1. Will be cast to int dtype.
        Each entry represents the binary 'true' label of one example
        One entry per example in current dataset
    scores_N : 1D array, shape (n_examples,) = (N,)
        One entry per example in current dataset.
        Each entry is a real value, could be between -infinity and +infinity.
        Large negative values indicate strong probability of label y=0.
        Zero values indicate probability of 0.5.
        Large positive values indicate strong probability of label y=1.
        Needs to be same size as ytrue_N.

    Returns
    -------
    bce : float
        Binary cross entropy, averaged over all N provided examples

    Examples
    --------
    >>> N = 8
    >>> ytrue_N = np.asarray([0., 0., 0., 0., 1., 1., 1., 1.])

    # Try some good scores for these 8 points.
    >>> good_scores_N = np.asarray([-4., -3., -2., -1., 1, 2., 3., 4.])
    >>> good_bce = calc_mean_binary_cross_entropy_from_scores(
    ...     ytrue_N, good_scores_N)
    >>> print("%.6f" % (good_bce))
    0.182835

    # Try some near-perfect scores for these 8 points.
    >>> perfect_scores_N = np.asarray([-9., -9., -9,  -9., 9., 9., 9., 9.])
    >>> perfect_bce = calc_mean_binary_cross_entropy_from_scores(
    ...     ytrue_N, perfect_scores_N)
    >>> print("%.6f" % (perfect_bce))
    0.000178

    # Check same computation with "probas". Should match exactly.
    >>> perfect_bce2 = calc_mean_binary_cross_entropy_from_probas(
    ...     ytrue_N, sigmoid(perfect_scores_N))
    >>> print("%.6f" % (perfect_bce2))
    0.000178

    # Try some extreme scores (should get worse BCE with more extreme scores)
    >>> ans = calc_mean_binary_cross_entropy_from_scores([0], [+99.])
    >>> print("%.6f" % (ans))
    142.826809
    >>> ans = calc_mean_binary_cross_entropy_from_scores([0], [+999.])
    >>> print("%.6f" % (ans))
    1441.252346
    >>> ans = calc_mean_binary_cross_entropy_from_scores([0], [+9999.])
    >>> print("%.6f" % (ans))
    14425.507714

    # Try with "probas": sigmoid saturates! using scores is *better*
    >>> a = calc_mean_binary_cross_entropy_from_probas([0], sigmoid([+999.]))
    >>> print("%.6f" % (a))
    46.508147
    >>> a = calc_mean_binary_cross_entropy_from_probas([0], sigmoid([+9999.]))
    >>> print("%.6f" % (a))
    46.508147

    # Try empty
    >>> empty_bce = calc_mean_binary_cross_entropy_from_scores([], [])
    >>> np.allclose(0.0, empty_bce)
    True
    '''
    if(len(ytrue_N) < 1):
        return 0.0
    if(len(scores_N) < 1):
        return 0.0

    # Cast labels to integer just to be sure we're getting what's expected
    ytrue_N = np.asarray(ytrue_N, dtype=np.int32)
    N = int(ytrue_N.size)
    if N == 0:
        return 0.0 # special case: empty array should be 0

    # Convert binary y values so 0 becomes +1 and 1 becomes -1
    # See HW2 instructions on website for the math

    yflippedsign_N = -1 * np.sign(ytrue_N-0.001)

    sum = 0

    for i in range(len(scores_N)):
        sum += scipy_logsumexp(a=np.append(yflippedsign_N[i] * scores_N[i], 0))/np.log(2)
    # TODO fixme
    # Be sure to use scipy_logsumexp were needed
    return sum/len(scores_N)

# N = 8
# ytrue_N = np.asarray([0., 0., 0., 0., 1., 1., 1., 1.])
#
# # Try perfect predictions
# perfect_proba1_N = np.asarray([0., 0., 0., 0., 1., 1., 1., 1.])
# perfect_bce = calc_mean_binary_cross_entropy_from_probas(ytrue_N, perfect_proba1_N)
# print("%.4f" % perfect_bce)
# # 0.0000
#
# # Try some confident scores
# good_proba1_N = np.asarray([0.01, 0.05, 0.1, 0.1, 0.9, 0.9, 0.95, 0.97])
# good_bce = calc_mean_binary_cross_entropy_from_probas(ytrue_N, good_proba1_N)
# print("%.4f" % good_bce)
# # 0.1018
#
# # Try some decent but underconfident scores
# ok_proba1_N = np.asarray([0.3, 0.4, 0.46, 0.47, 0.5, 0.6, 0.7, 0.71])
# ok_bce = calc_mean_binary_cross_entropy_from_probas(ytrue_N, ok_proba1_N)
# print("%.4f" % ok_bce)
# # 0.7253
#
# # Try some mistakes that are way over confident
# bad_pr1_N  = np.asarray([0., 0., 1., 0., 1., 1., 0., 0.])
# bad = calc_mean_binary_cross_entropy_from_probas(ytrue_N, bad_pr1_N)
# print("%.4f" % bad)
# # 17.4403
#
# # Try empty
# empty_bce = calc_mean_binary_cross_entropy_from_probas([], [])
# print(np.allclose(0.0, empty_bce))
# # True

# N = 8
# ytrue_N = np.asarray([0., 0., 0., 0., 1., 1., 1., 1.])
#
# # Try some good scores for these 8 points.
# good_scores_N = np.asarray([-4., -3., -2., -1., 1, 2., 3., 4.])
# good_bce = calc_mean_binary_cross_entropy_from_scores(ytrue_N, good_scores_N)
# print("%.6f" % (good_bce))
# # 0.182835
#
# # Try some near-perfect scores for these 8 points.
# perfect_scores_N = np.asarray([-9., -9., -9,  -9., 9., 9., 9., 9.])
# perfect_bce = calc_mean_binary_cross_entropy_from_scores(ytrue_N, perfect_scores_N)
# print("%.6f" % (perfect_bce))
# # 0.000178
#
# # Check same computation with "probas". Should match exactly.
# perfect_bce2 = calc_mean_binary_cross_entropy_from_probas(ytrue_N, sigmoid(perfect_scores_N))
# print("%.6f" % (perfect_bce2))
# # 0.000178
#
# # Try some extreme scores (should get worse BCE with more extreme scores)
# ans = calc_mean_binary_cross_entropy_from_scores([0], [+99.])
# print("%.6f" % (ans))
# # 142.826809
# ans = calc_mean_binary_cross_entropy_from_scores([0], [+999.])
# print("%.6f" % (ans))
# # 1441.252346
# ans = calc_mean_binary_cross_entropy_from_scores([0], [+9999.])
# print("%.6f" % (ans))
# # 14425.507714
#
# # Try with "probas": sigmoid saturates! using scores is *better*
# a = calc_mean_binary_cross_entropy_from_probas([0], sigmoid([+999.]))
# print("%.6f" % (a))
# # 46.508147
# a = calc_mean_binary_cross_entropy_from_probas([0], sigmoid([+9999.]))
# print("%.6f" % (a))
# # 46.508147
#
# # Try empty
# empty_bce = calc_mean_binary_cross_entropy_from_scores([], [])
# np.allclose(0.0, empty_bce)
# True


import cocos.device
import cocos.numerics as cn
import numpy as np
import pytest

test_data = [(2, 2), (3, 3)]

@pytest.mark.parametrize("n, m", test_data)
def test_identity(n: int, m: int):
    cocos.device.init()

    # eye
    A_numpy = np.eye(n, m)
    A_cocos = cn.eye(n, m)

    assert np.allclose(A_numpy, A_cocos)

    # identity
    B_numpy = np.identity(n)
    B_cocos = cn.identity(n)

    assert np.allclose(B_numpy, B_cocos)


#!/usr/bin/env python3
"""
Clustering Module
"""
import numpy as np

def initialize(X, k):
    """
    Initializes cluster centroids for K-means:

    X is a numpy.ndarray of shape (n, d) containing the dataset that will be
    used for K-means clustering
        n is the number of data points
        d is the number of dimensions for each data point
    k is a positive integer containing the number of clusters
    The cluster centroids should be initialized with a multivariate uniform
    distribution along each dimension in d:
        The minimum values for the distribution should be the minimum values
        of X along each dimension in d
        The maximum values for the distribution should be the maximum values
        of X along each dimension in d
        You should use numpy.random.uniform exactly once
    You are not allowed to use any loops

    Returns: a numpy.ndarray of shape (k, d) containing the initialized
    centroids for each cluster, or None on failure
    """
    if not isinstance(X, np.ndarray) or len(X.shape) != 2:
        return None
    if type(k) != int or k <= 0 or k >= X.shape[0]:
        return None

    max = np.max(X, axis=0)
    min = np.min(X, axis=0)
    return np.random.uniform(min, max, size=(k, X.shape[1]))


"""
Concider the MIQCQP problem
0.5 * (x1^2 + 2x2^2 + 3x3^2) + 15x1 + 8x2 + 80x3 -> min        (1)
subjected to
x1 + 2x2 + 3x3 <= 150            (2)
8x1 +  15x2 +  80x3 <= 800    (3)
x2 - x3 = 25.5                           (4)
x1 <= 15                                  (5)
x1^2 + 2.5 x2^2 + 3 x3^2 + 0.1 x1 + 0.2 x2 + 0.3 x3 - 1000 <= 0 (6)
2 x1^2 + x2^2 + 3 x3^2 + 0.1 x1 + 0.5 x2 + 0.3 x3  <= 1000 (7)
x1, x3 are integers
"""

from numpy import diag, matrix, inf
from openopt import QP

H = diag([1.0, 2.0,3.0])
f = [15,8,80]
A = matrix('1 2 3; 8 15 80')
b = [150, 800]

# QC should be list or tuple of triples (P, q, s): 0.5 x^T P x + q x + s <= 0
QC = ((diag([1.0, 2.5, 3.0]), [0.1, 0.2, 0.3], -1000), (diag([2.0, 1.0, 3.0]), [0.1, 0.5, 0.3], -1000))

p = QP(H, f, A = A, b = b, Aeq = [0, 1, -1], beq = 25.5, ub = [15,inf,inf], QC = QC, name='OpenOpt QCQP example 1')
# or p = QP(H=diag([1,2,3]), f=[15,8,80], ...)

r = p.solve('cplex', iprint = 0, plot=1)
f_opt, x_opt = r.ff, r.xf
# x_opt = array([ -2.99999999,   9.5       , -16.        ])
# f_opt = -770.24999989134858


from itertools import product

from numpy import einsum, zeros
from objects.grtensors.metrictensor import MetricTensor
from objects.simplifyobjects import Simplify
from sympy import Array, MutableDenseNDimArray, diff

def derivative_of_metric(coord_sys, metric_tensor, i, j, k):
    """
    Taking the partial derivative of a given metric tensor [g_jk] with respect to the
    [i]'th component

    Args:
        coord_sys [list]: The coordinate system given as a list (e.g., [t,x,y,z])
        metric_tensor [sympy.tensor]: Given metric tensor, g_jk
        i,j,k [int]: Coordinate indices; (0-ndim)
    """
    return Simplify(diff(metric_tensor[j, k], coord_sys[i]))

class ChristoffelSymbol(MetricTensor):
    def __init__(self, metric_tensor, coord_sys):
        """
        Creating the christoffel symbol object

        Args:
            metric_tensor [list]: The metric tensor, provided by the user
            coord_sys [list]: The coordinate system given as a list (e.g., [t,x,y,z])

        Returns:
            self.chris_type [str]: Type of the christoffel symbol. Default type is 'udd'
            self.chris_obj [sympy.tensor]: The christoffel symbol, Gamma^m_ij
        """
        MetricTensor.__init__(self, metric_tensor, coord_sys)
        self.chris_type = 'udd'
        chris_sym = MutableDenseNDimArray(zeros((self.ndim,)*3))
        for m, i, j in product(range(self.ndim), repeat=3):
            einstein_sum = 0
            for k in range(self.ndim):
                I1 = derivative_of_metric(
                    self.coord_sys, self.metric_obj, j, k, i)
                I2 = derivative_of_metric(
                    self.coord_sys, self.metric_obj, i, k, j)
                I3 = derivative_of_metric(
                    self.coord_sys, self.metric_obj, k, i, j)
                S = I1 + I2 - I3
                einstein_sum += 1/2 * self.inverse_metric_obj[m, k] * S
            chris_sym[m, i, j] = einstein_sum
        self.chris_obj = chris_sym

    def get_christoffelsymbol(self):
        """
        Returns the christoffel symbol object
        """
        return Simplify(self.chris_obj)

    def get_christoffelsymbol_type(self):
        """
        Returns the type of the christoffel symbol
        """
        return self.chris_type

    def lower_index(self, xchris_symbol):
        """
        Lowering the index of the christoffel symbol

        Args:
            xchris_symbol [sympy.tensor]: Given christoffel symbol
        """
        return Array(einsum('ijk,il->ljk', xchris_symbol, self.metric_obj, optimize='optimal'))

    def raise_index(self, xchris_symbol):
        """
        Raising the index of the christoffel symbol

        Args:
            xchris_symbol [sympy.tensor]: Given christoffel symbol
        """
        return Array(einsum('ikj,jm->imk', xchris_symbol, self.inverse_metric_obj, optimize='optimal'))

    def raise_index1(self, xchris_symbol):
        """
        Raising the second index of the christoffel symbol

        Args:
            xchris_symbol [sympy.tensor]: Given christoffel symbol
        """
        return Array(einsum('imk,kn->imn', xchris_symbol, self.inverse_metric_obj, optimize='optimal'))

    def vary_christoffelsymbol_type(self, xchris_symbol, new_type):
        """
        Varying the type of the christoffel symbol

        Args:
            xchris_symbol [sympy.tensor]: Given christoffel symbol
            new_type [str]: The new type of the christoffel symbol. It should be given
            in terms of 'u': contravariant (upper-indices) and 'd': covariant (lower-indices)

        Returns:
            The new christoffel symbol for a given type
        """
        self.chris_type = new_type
        if new_type == 'ddd':
            return Simplify(self.lower_index(xchris_symbol))
        elif new_type == 'udd':
            return Simplify(self.chris_obj)
        elif new_type == 'uud':
            return Simplify(self.raise_index(xchris_symbol))
        elif new_type == 'uuu':
            return Simplify(self.raise_index1(self.raise_index(xchris_symbol)))


"""
///////////////////////// TOP OF FILE COMMENT BLOCK ////////////////////////////
//
// Title:           CRR pricing model
// Course:          Master's thesis, 2020
//
// Author:          Peter Pommergård Lind
// Email:           ppl_peter@protonmail.com
// Encoding:        utf-8
///////////////////////////////// CITATIONS ////////////////////////////////////
//
// Option Pricing: A Simplified Approach
// by John C. Cox, Stephen A. Ross, and Mark Rubinstein
//
/////////////////////////////// 80 COLUMNS WIDE ////////////////////////////////
"""
#Binomial tree in Hull p 453
#packages
import numpy as np
import datetime

#Make tree dictionary
def binStockPath(stepSize, steps, spot, vol):
    STree = {}
    #stepSize = maturity/steps
    u = np.exp(vol*stepSize**0.5)
    d = np.exp(-vol*stepSize**0.5)
    for i in range(steps+1):
        if i==0:
            STree["{}".format(i*stepSize)] = spot
        else:
            list1 = []
            [list1.append(spot*u**j*d**(i-j)) for j in range(i+1)]

            STree["{}".format(i*stepSize)] = list1
    return STree

#intrinsic value dictionary for american put option
def findIntrinsicTree(stepSize, steps, spot, vol, strike):
    #stepSize = maturity/steps
    STree = binStockPath(stepSize, steps, spot, vol)
    intrinsicTree = {}
    for i in reversed(range(steps+1)):
        if i==0:
            intrinsicTree["{}".format(i*stepSize)] = max(strike-STree["{}".format(i*stepSize)],0)
        else:
            intrinsicTree["{}".format(i*stepSize)] = [max(strike-item,0) for item in STree["{}".format(i*stepSize)]]
    return intrinsicTree

#find condition expection
def findPayoff(steps, maturity, spot, vol, strike, r):
    start = datetime.datetime.now()
    stepSize = maturity/steps
    intrinsicTree = findIntrinsicTree(stepSize, steps, spot, vol, strike)
    u = np.exp(vol*stepSize**0.5)
    d = np.exp(-vol*stepSize**0.5)
    a = np.exp(r*stepSize)
    # risk neutral probabilities
    p = (a-d)/(u-d)
    expecTree = {}
    for i in reversed(range(steps+1)):
        if i==steps:
            expecTree["{}".format(i*stepSize)] = intrinsicTree["{}".format(i*stepSize)]
            continue
        else:
            list1 = [None]*(i+1)
            for count in range(i+1):
                condExp = 0
                for k in range(count, count+1):
                    condExp += (1-p)*expecTree["{}".format((i+1)*stepSize)][k]
                    k+=1
                    condExp += (p) * expecTree["{}".format((i+1)*stepSize)][k]
                    condExp = condExp*np.exp(-r*stepSize) # discount back
                if (i==0):
                    list1[count] = condExp
                elif (condExp > intrinsicTree["{}".format(i*stepSize)][count]):
                    list1[count] = condExp
                else:
                    list1[count] = intrinsicTree["{}".format(i*stepSize)][count]
        expecTree["{}".format(i*stepSize)]=list1
    finish = datetime.datetime.now()
    print (finish-start)
    return expecTree

if __name__ == '__main__':
    print(findPayoff(steps=50, maturity=1, spot=36, vol=0.2, strike=40, r=0.06)['0.0'])

    #print(findPayoff(steps=200, maturity=2, spot=S, vol=0.4, strike=40, r=0.06)['0.0'])


import numpy as np
import matplotlib.pyplot as plt
from pandas.io.parsers import read_csv

def load_csv(file_name):
    values = read_csv(file_name, header=None).values

    return values.astype(float)

def gradient_descent(X, Y, alpha, iterations):
    m = np.shape(X)[0]
    n = np.shape(X)[1]
    thetas = np.zeros((n, 1), dtype=float)
    costs = np.empty(iterations)
    thetasHistory = np.empty((iterations, np.shape(thetas)[0]))

    for i in range(iterations):
        H = h(X, thetas)
        thetas = thetas - (alpha * (1/float(m)) * (X.T.dot(H - Y)))

        thetasHistory[i] = thetas.T
        costs[i] = cost(X, Y, thetas)
        print("Cost [", i, "]: ", costs[i])

    return thetasHistory, costs

def cost(X, Y, thetas):
    m = np.shape(X)[0]
    H = h(X, thetas)
    aux = H-Y
    return np.dot(aux.T, aux) / (2*m)

def h(X, thetas):
    return np.dot(X, thetas)

def main():
    # DATA PREPROCESSING
    datos = load_csv("data/ex1data2.csv")
    X = datos[:, :-1]
    X = normalize(X)[0]
    Y = datos[:, -1][np.newaxis].T

    m = np.shape(X)[0]
    n = np.shape(X)[1]

    X = np.hstack([np.ones([m, 1]), X])
    alpha = 0.01

    # TRAINING
    thetas, costs = gradient_descent(X, Y, alpha, 500)

    # PREDICTION
    prediction = h(X, thetas[-1,:][np.newaxis].T)

    # i changes the case you output (for test purposes)
    i = 40
    print("Case %i | %f:%f = %f | Real value: %f" %(i, X[i, 1], X[i, 2], prediction[0], Y[i]))

    # RESULTS
    plt.figure()
    plt.plot(costs)
    plt.savefig('images/regresion_multi_gradient_descent.png')
    plt.show()
    plt.close()

def normalize(X):
    mean = np.mean(X, 0)
    deviation = np.std(X, 0)
    normalized = (X-mean)/deviation

    return (normalized, mean, deviation)

if __name__ == "__main__":
    main()


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

# code starts here
# loading dataset
df = pd.read_csv(path)
total = len(df)

# calculating probability
p_a = df[df['fico'].astype(float) >700].shape[0]/df.shape[0]
print('Probability of fino credit score > 700 :',p_a)
p_b = len(df[df['purpose'] == 'debt_consolidation'])/total
print('Probability of debt_consolidation :',p_b)
df1 = df[df['purpose'] == 'debt_consolidation']
p_a_b = df1[df1['fico'].astype(float) >700].shape[0]/df1.shape[0]
print('probaility of dept_consolidation given fino :',p_a_b)

# check for independency
result = p_a_b == p_a
print('Independancy :',result)
# code ends here

# --------------
# code starts here

# calculations using bayes theorem
prob_lp = len(df[df['paid.back.loan'] == 'Yes'])/total
print('Probability of loan paid back :',prob_lp)
prob_cs = len(df[df['credit.policy'] == 'Yes'])/total
print('Probability of credit policy :',prob_cs)
new_df = df[df['paid.back.loan'] == 'Yes']
prob_pd_cs = len(new_df[new_df['credit.policy'] == 'Yes'])/len(new_df)
bayes = (prob_pd_cs*prob_lp)/prob_cs
print('Probability of the Loan Defaulters :',bayes)

# code ends here

# --------------
# code starts here

# plotting bar graph
df.purpose.value_counts(normalize=True).plot(kind='bar')
df1 = df[df['paid.back.loan'] == 'No']
df1.purpose.value_counts(normalize=True).plot(kind='bar')
# code ends here

# --------------
# code starts here

# calculating median & mean
inst_median = df.installment.median()
print('Median of installment :',inst_median)
inst_mean = df.installment.mean()
print('Mean of installment :',inst_mean)

# plotting histogram
plt.hist(df.installment)
plt.hist(df['log.annual.inc'])
# code ends here


import pandas as pd
import random
import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
from mpl_toolkits.mplot3d import Axes3D

cust_df = pd.read_csv("Cust_Segmentation.csv")
df = cust_df.drop('Address', axis=1)

X = df.values[:,1:]
X= np.nan_to_num(X)
Clus_dataSet = StandardScaler().fit_transform(X)

clusterNum = 3
k_means = KMeans(init = "k-means++", n_clusters = clusterNum, n_init = 12)
k_means.fit(X)
labels = k_means.labels_
print(labels)
k_means_cluster_centers = k_means.cluster_centers_
print(k_means_cluster_centers)

df["Clus_km"] = labels
df.head(5)
df.groupby('Clus_km').mean()
area = np.pi * ( X[:, 1])**2
plt.scatter(X[:, 0], X[:, 3], s=area, c=labels.astype(np.float), alpha=0.5)
plt.xlabel('Age', fontsize=18)
plt.ylabel('Income', fontsize=16)
plt.show()

fig = plt.figure(1, figsize=(8, 6))
plt.clf()
ax = Axes3D(fig, rect=[0, 0, .95, 1], elev=48, azim=134)
plt.cla()
# plt.ylabel('Age', fontsize=18)
# plt.xlabel('Income', fontsize=16)
# plt.zlabel('Education', fontsize=16)
ax.set_xlabel('Education')
ax.set_ylabel('Age')
ax.set_zlabel('Income')
ax.scatter(X[:, 1], X[:, 0], X[:, 3], c= labels.astype(np.float))


def eratosthenes(n):
	i = 1
	multiples = set()
	while i < n-1:
		i += 1
		if i not in multiples:
			yield i
			for j in range(i*i, n, i):
				multiples.add(j)

from sympy import sieve
import numpy as np
import time
start_time = time.time()
#print(sum(eratosthenes(2000000)))
print(sum(sieve.primerange(1,2000000)))
print("--- %s seconds ---" % (time.time() - start_time))

# I used sieve of eratosthenes, but apparantly python (ofcourse) has a package for this. So I actually have two solutions, and I've put the slowest one in comment
# after all, we don't have to re-invent the wheel do we?


from __future__ import annotations
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`
        """
        m = X.shape[0]

        def sample_mean_estimator(X: np.ndarray):
            return np.sum(X) / m

        def sample_variance_estimator(X: np.ndarray):
            sum_elems = np.array(
                [np.power(X[i] - self.mu_, 2) for i in range(m)])
            divisor = m if self.biased_ else m + 1
            return (np.sum(sum_elems)) / divisor

        self.mu_ = sample_mean_estimator(X)
        self.var_ = sample_variance_estimator(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")

        m = X.shape[0]

        def formula(x):
            exp = np.exp(-(np.power(x - self.mu_, 2) / (2 * self.var_)))
            return exp / (np.sqrt(2 * np.pi * self.var_))

        return np.array([formula(X[i]) for i in range(m)])

    @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
        """
        m = X.shape[0]
        exp_sum = np.sum([np.power(X[i] - mu, 2) for i in range(m)])
        # following formula calculated in class
        return -(m / 2) * np.log(2 * np.pi) \
               - (m / 2) * np.log(sigma) - (exp_sum / (2 * sigma))

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`
        """
        m = X.shape[0]

        def sample_mean_estimator(X: np.ndarray):
            return (np.sum(X, axis=0)) / m

        def sample_covariance_estimator(X: np.ndarray):
            # first subtract the empirical mean from each sample
            X_centered = np.array([X[i] - self.mu_ for i in range(m)])
            # then follow the definition of sample covariance matrix
            return np.dot(X_centered.T, X_centered) / (m - 1)

        self.mu_ = sample_mean_estimator(X)
        self.cov_ = sample_covariance_estimator(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")

        m = X.shape[0]
        d = X.shape[1]

        def formula(x: np.ndarray):
            # following formula from course book - definition 1.2.5
            exp = np.exp(
                -(1 / 2) * np.dot(
                    (x - self.mu_).T,
                    np.dot(np.linalg.inv(self.cov_), (x - self.mu_))))
            divisor = np.sqrt(
                np.power((2 * np.pi), d) * np.linalg.det(self.cov_))
            return exp / divisor

        return np.array([formula(X[i]) for i in range(m)])

    @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
        """

        m, d = X.shape[0], X.shape[1]
        # following formula calculated in Ex01 Q09
        exp_sum = np.sum((X - mu) @ np.linalg.inv(cov) * (X - mu))
        return -(m * d / 2) * np.log(2 * np.pi) \
               - (m / 2) * np.log(np.linalg.det(cov)) \
               - (exp_sum / 2)


import numpy as np
import matplotlib.pyplot as plt

N = 1000
n = int(N/2)
D = 2

R_inner = 5
R_outer = 10

R1 = np.random.randn(n) + R_inner
theta = 2 * np.pi * np.random.random(n)
X_inner = np.concatenate([[R1 * np.cos(theta)], [R1 * np.sin(theta)]]).T

R2 = np.random.randn(n) + R_outer
theta = 2 * np.pi * np.random.random(n)
X_outer = np.concatenate([[R2 * np.cos(theta)], [R2 * np.sin(theta)]]).T

X = np.concatenate([X_inner, X_outer])
T = np.array([0] * (n) + [1] * (n))

plt.scatter(X[:, 0], X[:, 1], c=T)
plt.show()

ones = np.array([[1] * N]).T

r = np.zeros((N, 1))
for i in range(N):
    r[i] = np.sqrt(X[i, :].dot(X[i, :]))

Xb = np.concatenate((ones, r, X), axis=1)
w = np.random.randn(D + 2)
z = Xb.dot(w)

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

Y = sigmoid(z)

def cross_entropy(T, Y):
    E = 0
    for i in range(N):
        if T[i] == 1:
            E -= np.log(Y[i])
        else:
            E -= np.log(1 - Y[i])
    return E

learning_rate = 0.0001
error = []
for i in range(5000):
    e = cross_entropy(T, Y)
    error.append(e)
    if i % 100 == 0:
        print(e)

    w += learning_rate * (np.dot((T - Y).T, Xb) - 0.01 * w)

    Y = sigmoid(Xb.dot(w))

plt.plot(error)
plt.show()

print('Final w', w)
print('Final classification rate', 1 - np.abs(T - np.round(Y)).sum() / N)


# CELL 0

# Import necessary libraries
import numpy as np
import pandas as pd
import tensorflow as tf
import matplotlib.pyplot as plt
import seaborn as sns

from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras.layers.experimental import preprocessing
from sklearn.preprocessing import Normalizer
import sklearn.preprocessing

# CELL 1

# Linear Regression Model Using Tensor Flow

# Input (temp, rainfall, humidity)
inputs = np.array([[73, 67, 43], [91, 88, 64], [87, 134, 58],
                   [102, 43, 37], [69, 96, 70], [73, 67, 43],
                   [91, 88, 64], [87, 134, 58], [102, 43, 37],
                   [69, 96, 70], [73, 67, 43], [91, 88, 64],
                   [87, 134, 58], [102, 43, 37], [69, 96, 70]], dtype='float32')
# Targets (apples)
targets = np.array([[56], [81], [119], [22], [103],
                    [56], [81], [119], [22], [103],
                    [56], [81], [119], [22], [103]], dtype='float32')
n = len(inputs) # Number of data points

# CELL 2

from sklearn.model_selection import train_test_split
main_data = pd.DataFrame(inputs, columns = ['temp', 'rainfall', 'humidity'])
Y_rows = pd.DataFrame(targets, columns = ['apples'])

X_train, X_test, Y_train, Y_test = train_test_split(main_data, Y_rows, test_size = 0.25, random_state = 94)

# CELL 3

test_results = {}

norm_X_test = np.linalg.norm(X_test['rainfall'])
norm_X_train = np.linalg.norm(X_train['rainfall'])
norm_Y_train = np.linalg.norm(Y_train['apples'])

normal_array_X_test = X_test['rainfall']/norm_X_test
normal_array_X_train = X_train['rainfall']/norm_X_train
normal_array_Y_train = Y_train['apples']/norm_Y_train

print(normal_array_X_train)
print(normal_array_X_test)
print(normal_array_Y_train)

# CELL 4

normalizer = preprocessing.Normalization(axis = -1)
normalizer.adapt(np.array(X_train))
linear_model = tf.keras.Sequential([
    normalizer,
    layers.Dense(units = 1)
])
print(linear_model.predict(X_train[ : 9]))

# CELL 5

linear_model.compile(
    optimizer=tf.optimizers.Adam(learning_rate = 0.1),
    loss='mean_absolute_error')

history = linear_model.fit(
    X_train, Y_train,
    epochs = 1000,
    # Suppress logging
    verbose = 0,
    # Calculate validation results on 25% of the training data
    validation_split = 0.25)

test_results['linear_model'] = linear_model.evaluate(
    X_test, Y_test, verbose = 0)

test_results['linear_model']


import numpy as np

# Perform numerous numpy operations

def run():
    # create a column vector
    col_vec = np.array([[1], [2]])
    print "column vector"
    print col_vec

    # create a row vector
    row_vec = np.array([[1, 2]])
    print "row vector"
    print row_vec

    # create a matrix
    mat = np.array([[1, 2], [3, 4]])
    print "matrix"
    print mat

    # inspect dimensions
    print "row vector dimensions", row_vec.ndim
    shape = row_vec.shape
    print "row vector rows", shape[0], "columns", shape[1]

    print "matrix dimensions", mat.ndim
    shape = mat.shape
    print "matrix rows", shape[0], "columns", shape[1]

    # transpose
    vec_t = row_vec.transpose()  # or row_vec.T
    print "transposed vector"
    print vec_t

    mat_t = mat.transpose()  # or mat.T
    print "transposed matrix"
    print mat_t

    a = np.array([[2], [-4], [1]])
    b = np.array([[2], [1], [-2]])

    # addition
    print "a + b"
    print a + b

    # subtraction
    print "a - b"
    print a - b

    # scalar multiplication
    print "1.2 * a"
    print 1.2 * a

    # element wise multiplication
    print "a * b"
    print a * b

    # vector scalar product
    print "a . b"
    print np.dot(a.transpose(), b)

    # vector cross product
    print "a x b"
    print np.cross(a, b, axis=0)  # or np.cross(a.T, b.T).T

    identity = np.array([[1, 0], [0, 1]])

    # matrix vector product
    print "identity . col_vec"
    print np.dot(identity, col_vec)

    # matrix product
    print "identity . mat"
    print np.dot(identity, mat)

# this is executed when we run this script from commandline
if __name__ == "__main__":
    run()


#coding:utf-8
import numpy as np
import matplotlib.pyplot as plt

"""
勾配降下法で線形回帰による曲線フィッティング
PRML1章の多項式曲線フィッティングと同じだが、
正規方程式ではなく、勾配降下法で解く点が異なる
"""

def plotData(X, y):
    plt.scatter(X, y, c='red', marker='o', label="Training data")
    plt.xlabel("x")
    plt.ylabel("y")

def computeCost(X, y, theta):
    m = len(y)  # 訓練データ数
    tmp = np.dot(X, theta) - y
    J = 1.0 / (2 * m) * np.dot(tmp.T, tmp)
    return J

def gradientDescent(X, y, theta, alpha, iterations):
    m = len(y)      # 訓練データ数
    J_history = []  # 各更新でのコスト
    for iter in range(iterations):
        theta = theta - alpha * (1.0 / m) * np.dot(X.T, np.dot(X, theta) - y)
        J_history.append(computeCost(X, y, theta))
    return theta, J_history

if __name__ == "__main__":
    # 訓練データを作成
    # sin(2 * pi * x) の値にガウス分布に従う小さなランダムノイズを加える
    X = np.linspace(0, 1, 100)
    y = np.sin(2 * np.pi * X) + np.random.normal(0, 0.2, X.size)

    # 訓練データ数
    m = len(y)

    # 訓練データをプロット
    plt.figure(1)
    plotData(X, y)

    # 訓練データの1列目に1を追加
    # 元の変数の二乗、三乗の項も加える
    # 多項式回帰になり曲線フィッティングも可能
    X = X.reshape((m, 1))
    X = np.hstack((np.ones((m, 1)), X, X**2, X**3))

    # パラメータを0で初期化
    # yとthetaはわざわざ縦ベクトルにreshapeしなくてもOK
    # np.dot()では自動的に縦ベクトル扱いして計算してくれる
    theta = np.zeros(4)
    iterations = 100000
    alpha = 0.2

    # 初期状態のコストを計算
    initialCost = computeCost(X, y, theta)
    print "initial cost:", initialCost

    # 勾配降下法でパラメータ推定
    theta, J_history = gradientDescent(X, y, theta, alpha, iterations)
    print "theta:", theta
    print "final cost:", J_history[-1]

    # コストの履歴をプロット
    plt.figure(2)
    plt.plot(J_history)
    plt.xlabel("iteration")
    plt.ylabel("J(theta)")

    # 曲線をプロット
    plt.figure(1)
    plt.plot(X[:, 1], np.dot(X, theta), 'b-', label="Linear regression")
    plt.xlim((0, 1))
    plt.legend()
    plt.show()


# example of a stiff non-linear system -- this is the example defined in
# the VODE source code
#
# y1' = -0.04 y1 + 1.e4 y2 y3
# y2' =  0.04 y1 - 1.e4 y2 y3 - 3.e7 y2**2
# y3' =                         3.e7 y2**2
#
# y1(0) = 1
# y2(0) = 0
# y3(0) = 0
#
# long term behavior: y1 -> 0, y2 -> 0, y3 -> 1
#
# here we solve the system twice: once taking a small step initially
# and then increasing by 10x until we reach tmax -- this lets us see
# the intermediate behavior.  The second call has VODE handle the
# timestep throughout the entire integration and does 0 to tmax in one
# call.
#
# M. Zingale (2013-02-23)

import numpy
import pylab
import scipy
from scipy.integrate import ode

def rhs(t, Y):
    """ RHS of the system -- using 0-based indexing """
    y1 = Y[0]
    y2 = Y[1]
    y3 = Y[2]

    dy1dt = -0.04*y1 + 1.e4*y2*y3
    dy2dt =  0.04*y1 - 1.e4*y2*y3 - 3.e7*y2**2
    dy3dt =                         3.e7*y2**2

    return numpy.array([dy1dt, dy2dt, dy3dt])

def jac(t, Y):
    """ J_{i,j} = df_i/dy_j """

    y1 = Y[0]
    y2 = Y[1]
    y3 = Y[2]

    df1dy1 = -0.04
    df1dy2 = 1.e4*y3
    df1dy3 = 1.e4*y2

    df2dy1 = 0.04
    df2dy2 = -1.e4*y3 - 6.e7*y2
    df2dy3 = -1.e4*y2

    df3dy1 = 0.0
    df3dy2 = 6.e7*y2
    df3dy3 = 0.0

    return numpy.array([ [ df1dy1, df1dy2, df1dy3 ],
                         [ df2dy1, df2dy2, df2dy3 ],
                         [ df3dy1, df3dy2, df3dy3 ] ])

def VODEIntegrate(Y0, dt, tmax):
    """ integrate using the VODE method, start with a timestep of dt and
        increase by 10x after each call until we reach tmax.  This is
        the behavior used in the DVODE Fortran source. """

    r = ode(rhs, jac).set_integrator("vode", method="bdf",
                                     with_jacobian=True,
                                     atol=1.e-10, rtol=1.e-10,
                                     nsteps = 15000, order=5) #, min_step=dt)

    t = 0.0
    r.set_initial_value(Y0, t)

    tout = [t]
    y1out = [Y0[0]]
    y2out = [Y0[1]]
    y3out = [Y0[2]]

    while r.successful() and r.t < tmax:
        r.integrate(r.t+dt)

        tout.append(r.t)
        y1out.append(r.y[0])
        y2out.append(r.y[1])
        y3out.append(r.y[2])

        dt = 10.0*dt

    return numpy.array(tout), \
        numpy.array(y1out), numpy.array(y2out), numpy.array(y3out)

Y0 = numpy.array([1.0, 0.0, 0.0])
tmax = 4.e7

# first call -- give an initial small timestep and have our integrate
# routine increase it by 10x each call to VODE to get to tmax
dt_init = 4.e-6

t, y1, y2, y3 = VODEIntegrate(Y0, dt_init, tmax)

# second call -- give an initial timestep of tmax -- VODE will handle
# everything
t_single, y1_single, y2_single, y3_single = VODEIntegrate(Y0, tmax, tmax)

ax = pylab.gca()
ax.set_xscale('log')
ax.set_yscale('log')

pylab.plot(t, y1, label=r"$y_1$", color="r")
pylab.plot(t, y2, label=r"$y_2$", color="g")
pylab.plot(t, y3, label=r"$y_3$", color="b")

pylab.scatter(t_single, y1_single, color="r",
              label="r$y_1$ from single VODE call")

pylab.scatter(t_single, y2_single, color="g",
              label="r$y_2$ from single VODE call")

pylab.scatter(t_single, y3_single, color="b",
              label="r$y_3$ from single VODE call")

pylab.legend(loc=8, frameon=False, fontsize="small")

pylab.xlabel("$t$")
pylab.ylabel(r"$Y(t)$")
pylab.title("VODE reference stiff nonlinear system")

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


#@ Model: Lasso
#@ Description: Machine Learning: Lasso Regression. Example from cvxpy.org/examples/machine_learning/lasso_regression.html  $$\min (\beta X - Y)+\lambda||\beta||$$ Find the regression coefficients (beta) that minimize the above regularized function

import cvxpy as cp
import numpy as np
import matplotlib.pyplot as plt
import json

np.random.seed(1)

#@ Helper Object: m
#@ Description: number of data points
m = 100

#@ Helper Object: n
#@ Description: number of features
n = 20

#@ Helper Object: sigma
#@ Description: $$\sigma$$ parameter for generating random dataset
sigma = 5

#@ Helper Object: density
#@ Description: density parameter for generating random dataset
density = 0.2

#@ Helper Object: X
#@ Description: data matrix X
X = np.random.randn(m,n)

#@ Helper Object: Y
#@ Description: observations Y
beta_star = np.random.randn(n)
idxs = np.random.choice(range(n), int((1-density)*n), replace=False)
for idx in idxs:
    beta_star[idx] = 0
Y = X.dot(beta_star) + np.random.normal(0, sigma, size=m)

#@ Helper Object: X_train
#@ Description: training data
X_train = X[:50, :]

#@ Helper Object: Y_train
#@ Description: training observations
Y_train = Y[:50]

#@ Helper Object: X_test
#@ Description: test data
X_test = X[50:, :]

#@ Helper Object: Y_test
#@ Description: test observations
Y_test = Y[50:]

#@ Input Object: lambd
#@ Description: $$\lambda$$: regularization parameter
lambd = .1

#@ Variable: beta
#@ Description: $$\beta$$: regression coefficients
#@ Labels: labels_beta
beta = cp.Variable(n)

labels_beta = dict([(i, 'Feature %d' %i) for i in range(n)])

#@ Function: loss_fn
#@ Description: loss function $$\beta X_{train} - Y_{train}$$
loss_fn = cp.norm2(X_train @ beta - Y_train)**2

#@ Function: regularizer
#@ Description: regularization component of objective function $$||\beta||$$
regularizer = cp.norm1(beta)

#@ Function: objectivefn
#@ Description: objective function $$loss_{fn}+\lambda||\beta||$$
objectivefn = loss_fn + lambd * regularizer

#@ Problem: problem
problem = cp.Problem(cp.Minimize(objectivefn))

#@ Solver: solver
solver = "ECOS"

problem.solve(solver=solver, verbose=True)

#@ Helper Object: training_mse
#@ Description: training mean squared error
training_mse = (1.0 / X_train.shape[0]) * loss_fn.value

#@ Helper Object: testing_mse
#@ Description: testing mean squared error
testing_mse = (1.0 / X_test.shape[0]) * (cp.norm2(X_test @ beta.value - Y_test)**2).value


import numpy as np

def normalize(a):
    ms = np.mean(a, axis = 0, keepdims = True)
    return np.divide(np.subtract(a, ms), np.sqrt(2.0*np.multiply(ms/2.0, 1-ms/2.0)*a.shape[1]))

g = np.pad(np.diag([1.0,1,2]),((0,1),(0,0)),mode='constant')

print 'genotypes without a missing entry \n', g, '\n'

g[1,0] = 1.0/3

print 'genotypes with an mean-imputed missing entry \n', g, '\n'

n = normalize(g)

print 'normalized genotype matrix \n', n, '\n'

U, s, V = np.linalg.svd(n, full_matrices=0)

print 'scores \n', U.dot(np.diag(s)), '\n'
print 'loadings \n', V.transpose(), '\n'
print 'eigenvalues \n', np.multiply(s,s), '\n'
print 'roundoff error \n', U.dot(np.diag(s).dot(V)) - n, '\n'

print 'flattened arrays of scores, loadings and eigenvalues, respectively: \n'

print tuple(U.dot(np.diag(s)).transpose().flatten()), '\n'
print tuple(V.transpose().flatten()), '\n'
print tuple(np.multiply(s,s).flatten())


import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from math import sqrt, pi, exp, log, e
from sklearn.metrics import confusion_matrix

DATA_ROOT = './data/'
COL_NAMES = ['sepal-length', 'sepal-width', 'petal-length', 'petal-width', 'class']

# Univariate normal density
def u_n_d(x, mean, variance):
    '''
        Return the univariate density of given set x
    '''
    return 1/(sqrt(2*pi*variance)) * exp(-((x - mean)**2 / (2*variance)))

# Finds the Baysian decision boundary
def b_d_b(x, mean1, mean2, var1, var2, prior1, prior2):
    '''
        Return a point on the decision boundary
    '''
    return (log(var2/var1, e) - (x - mean1)**2/(2*var1) + (x - mean2)**2/(2*var2) - log(prior2/prior1, e))

# Find the accuracy of the classification
def find_accuracy(actual, predicted):
    '''
        Divide the number of truly predicted labels to the total number of labels
    '''
    return (100.0 * np.sum(actual['class'] == predicted['class']) / actual.size)

# Read the train and test data
df = pd.read_fwf(DATA_ROOT+'Iris_train.dat')
df2 = pd.read_fwf(DATA_ROOT+'Iris_test.dat')

# Rename the columns
df.columns = COL_NAMES
df2.columns = COL_NAMES

# ax = plt.Figure()
# ax.add_subplot(111)

train_data = df[['sepal-width', 'class']]
test_data = df2[['sepal-width', 'class']]

# plt.hist(pd)
class_1 = df.loc[train_data['class'] == 0.0]
class_2 = df.loc[train_data['class'] == 1.0]

class_1 = class_1['sepal-width']
class_2 = class_2['sepal-width']

# Find the distributions
class_1_mean = np.mean(class_1)
class_2_mean = np.mean(class_2)

class_1_variance = np.var(class_1)
class_2_variance = np.var(class_2)

print("Setosa mean: ", class_1_mean)
print("Setosa variance: ", class_1_variance)
print("Veriscolor mean: ", class_2_mean)
print("Veriscolor variance: ", class_2_variance)

# Config bin size and intervals
bin_size = 0.2; min_edge = 1; max_edge = 5
N = (max_edge-min_edge)/bin_size; Nplus1 = N + 1
bin_list = np.linspace(min_edge, max_edge, Nplus1)

fig, ax = plt.subplots(nrows=1, ncols=2)
ax[0].hist(class_1, bins=bin_list, color='darkblue', label='Iris Setosa')
ax[0].legend(loc='upper right')
ax[0].set_xlabel('sample')
ax[0].set_ylabel('sepal-width')
ax[1].hist(class_2, bins=bin_list, color='green', label='Iris Veriscolor')
ax[1].legend(loc='upper right')
ax[1].set_xlabel('sample')
ax[1].set_ylabel('sepal-width')

# plt.show()

# # Class Prior probabilities
prior_1 = class_1.size/df.size
prior_2 = class_2.size/df.size
print("Prior probability of Setosa: ", prior_1)
print("Prior probability of Veriscolor: ", prior_2)

# Plot the density functions of two classes
ax2 = plt.subplot()
x_range = np.linspace(1, 6, 100)

yy1 = [u_n_d(x, class_1_mean, class_1_variance) for x in x_range]
yy2 = [u_n_d(x, class_2_mean, class_2_variance) for x in x_range]
yy3 = [b_d_b(x, class_1_mean, class_2_mean, class_1_variance, class_2_variance, prior_1, prior_2) for x in x_range]

ax2.plot(x_range, yy1, label='Setosa Density', color='darkblue')
ax2.plot(x_range, yy2, label='Veriscolor Density', color='green')
ax2.set_xlabel('sepal-width')
ax2.set_ylabel('probability density')

# Plot the Bayesian decision boundary
ax2.plot(x_range, yy3, label='Decision Boundary', color='red')
ax2.set_ylim(0, 1.5)
ax2.legend(loc='upper right')

# plt.show()

# Classify the training data
cols = ['sepal-width', 'class']
train_prediction = pd.DataFrame(columns=cols)

for index, x in train_data.iterrows():
    cond = b_d_b(x['sepal-width'], class_1_mean, class_2_mean, class_1_variance, class_2_variance, prior_1, prior_2)
    if(cond >= 0):
        # Classify as class 1
        train_prediction = train_prediction.append({'sepal-width': x['sepal-width'], 'class': 0.0}, ignore_index=True)
    else:
        # Classify as class 2
        train_prediction = train_prediction.append({'sepal-width': x['sepal-width'], 'class': 1.0}, ignore_index=True)

# Classify the test data
test_prediction = pd.DataFrame(columns=cols)

for index, x in test_data.iterrows():
    cond = b_d_b(x['sepal-width'], class_1_mean, class_2_mean, class_1_variance, class_2_variance, prior_1, prior_2)
    if(cond >= 0):
        # Classify as class 1
        test_prediction = test_prediction.append({'sepal-width': x['sepal-width'], 'class': 0.0}, ignore_index=True)
    else:
        # Classify as class 2
        test_prediction = test_prediction.append({'sepal-width': x['sepal-width'], 'class': 1.0}, ignore_index=True)

# Calculate the accuracy of precition
train_acc = find_accuracy(train_data, train_prediction)
test_acc = find_accuracy(test_data, test_prediction)
print("Training Accuracy: ", train_acc)
print("Test Accuracy: ", test_acc)
print("Training Error: ", 1.0 - train_acc)
print("Test Error: ", 1.0 - test_acc)
print("Training Confusion Matrix: ")
print(confusion_matrix(train_data['class'], train_prediction['class']))
print("Testing Confusion Matrix: ")
print(confusion_matrix(test_data['class'], test_prediction['class']))


# constrained optimization

import numpy as np
import matplotlib.pyplot as plt

mu = 2
eta = 1e-2
err = 1e-4

def line(t):
    return np.array([np.cos(t), np.sin(t)])

def f_origin(x):
    return x[0] + x[1]

def f(x, mu):
    return x[0] + x[1] - mu*(x[0]**2 + x[1]**2 - 1)

def df(x, mu):
    return np.array([1-2*mu*x[0], 1-2*mu*x[1]])

x0 = np.array([-1, 0])
f0 = f(x0, mu)
for i in range(1000):
    gd = df(x0, mu)
    x = x0 - eta*gd
    f1 = f(x, mu)
    if abs(f1 - f0)<err:
        print('convergency reached after %d of steps' %(i))
        x0 = x
        print(x0)
        break
    x0 = x
    f0 = f1

X = Y = np.linspace(-1, 1, 20)
xx, yy = np.meshgrid(X, Y)
Params = np.c_[xx.reshape((-1, 1)), yy.reshape((-1, 1))]
zz = np.array([f_origin(xy) for xy in Params]).reshape(xx.shape)
# theta = np.linspace(0, 2*np.pi, 30)

plt.contourf(xx, yy, zz, 30)
plt.plot(x0[0], x0[1], 'ko')
# plt.plot(X, y, 'r')
plt.ylim([-1, 1])
plt.show()


import numpy as np

def getA_k(phi_k: float, # roll angle at time step k
            u1_k: float, # thrust force at time step k
            m: float,    # mass of the quadrotor
            dt: float):  # sample time
    A_k = np.array([
        [1, 0,                                   0,       dt,       0,       0],
        [0, 1,                                   0,        0,      dt,       0],
        [0, 0,                                   1,        0,       0,      dt],
        [0, 0,      -u1_k / m * np.cos(phi_k) * dt,        1,       0,       0],
        [0, 0,      -u1_k / m * np.sin(phi_k) * dt,        0,       1,       0],
        [0, 0,                                   0,        0,       0,       1]
    ])

    return A_k

def getB_k(phi_k: float, # roll angle at time step k
            Ixx: float,
            m: float,
            dt: float):
    B_k = np.array([
        [0,                       0],
        [0,                       0],
        [0,                       0],
        [-np.sin(phi_k) / m * dt, 0],
        [ np.cos(phi_k) / m * dt, 0],
        [0,                dt / Ixx]
    ])

    return B_k


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

def iexp(n):
    return complex(math.cos(n), math.sin(n))

if __name__ == "__main__":
    # style.use('ggplot')
    fig, axes = plt.subplots(ncols=1, nrows=2)
    ax1, ax2 = axes.ravel()

    # generate an input signal
    Num = 1024
    fs = 1000
    t = np.linspace(0, Num/fs, Num)
    x_t = np.sin(2*np.pi*1*t) + 0.5*np.sin(2*np.pi*5*t) + 0.2*np.sin(2*np.pi*10*t)

    # Generate the twiddle factors for a 32p DFT
    N = 1024
    W = np.zeros([N, N], dtype=complex)
    for k in range(N):
        for i in range(N):
            W[k, i] = iexp(-2 * math.pi * i * k / N)

    # Compute the N point DFT with time measurement
    start_time = time.time()
    X_f = np.zeros(Num, dtype=complex)
    for k in range(N):
        temp_sum = complex(0)
        for i in range(N):
            temp_sum += x_t[i]*W[k, i]
        X_f[k] = temp_sum
    stop_time = time.time()
    # Compute the magnitude and frequency
    A_f = np.zeros(Num, dtype=float)
    for k in range(N):
        A_f[k] = np.sqrt(np.power(X_f[k].imag, 2) + np.power(X_f[k].real, 2))

    f = np.linspace(0, fs/2, int(Num/2))

    elapsed_time = stop_time - start_time
    print("The time of execution of our DFT is: %.2f s." % elapsed_time)

    # plot input signal
    ax1.plot(t, x_t)
    ax1.margins(0)
    ax1.grid()
    # plot output signal
    ax2.plot(f, A_f[0:int(Num/2)])
    plt.xscale("log")
    ax2.margins(0)
    ax2.grid()
    plt.show()


import math
import numpy as np

def radial_coord(G, beta, zeta=1):
    """
    Nodes are first sorted i = 1,2, ... N according to their degree
    (descending).

    Then r_i is calculated by the formula in pg. 3

    r_i = \frac{2}{\zeta} ( \beta ln i + (1 - \beta) ln N )

    Args:
        G: networkx.Graph
        beta: float, Exponent of the power-law degree distribution.
        zeta: float, sqrt(-K), where K is the curature of the hyperbolic space.

    Yields: Tuple[hashable, float]
        Node label and radial coordinate.
    """
    sorted_node_ids = [i for i, _ in sorted(G.degree(), key=lambda x: x[1], reverse=True)]

    for i, n in enumerate(sorted_node_ids):
        r = 2 / zeta * (beta * math.log(i) + (1 - beta) * math.log(len(G)))
        yield n, r

def radial_coord_deg(G, beta, zeta=1):
    """
    Nodes are first sorted i = 1,2, ... N according to their degree
    (descending).

    Then r_i is calculated by the formula in pg. 3

    r_i = \frac{2}{\zeta} ( \beta ln i + (1 - \beta) ln N )

    Args:
        G: networkx.Graph
        beta: float, Exponent of the power-law degree distribution.
        zeta: float, sqrt(-K), where K is the curature of the hyperbolic space.

    Returns: numpy.ndarray
        Radial coordinate for each node in a graph.
    """
    degrees = np.array(G.degree())[:, 1].astype(float)
    inds = np.argsort(degrees)
    return 2 / zeta * (beta * np.log(inds + 1) + (1 - beta) * np.log(len(inds)))


import cvxpy as cp
import numpy
import matplotlib.pyplot as plt

# Problem data.
n = 15
m = 10
numpy.random.seed(1)
A = numpy.random.randn(n, m)
b = numpy.random.randn(n)
# gamma must be nonnegative due to DCP rules.
gamma = cp.Parameter(nonneg=True)

# Construct the problem.
x = cp.Variable(m)
error = cp.sum_squares(A @ x - b)
obj = cp.Minimize(error + gamma*cp.norm(x, 1))
prob = cp.Problem(obj)

# Construct a trade-off curve of ||Ax-b||^2 vs. ||x||_1
sq_penalty = []
l1_penalty = []
x_values = []
gamma_vals = numpy.logspace(-4, 6)
for val in gamma_vals:
    gamma.value = val
    prob.solve()
    # Use expr.value to get the numerical value of
    # an expression in the problem.
    sq_penalty.append(error.value)
    l1_penalty.append(cp.norm(x, 1).value)
    x_values.append(x.value)

plt.rc('text')
plt.rc('font', family='serif')
plt.figure(figsize=(6,10))

# Plot trade-off curve.
plt.subplot(211)
plt.plot(l1_penalty, sq_penalty)
plt.xlabel(r'\|x\|_1', fontsize=16)
plt.ylabel(r'\|Ax-b\|^2', fontsize=16)
plt.title('Trade-Off Curve for LASSO', fontsize=16)

# Plot entries of x vs. gamma.
plt.subplot(212)
for i in range(m):
    plt.plot(gamma_vals, [xi[i] for xi in x_values])
plt.xlabel(r'\gamma', fontsize=16)
plt.ylabel(r'x_{i}', fontsize=16)
plt.xscale('log')
plt.title(r'\text{Entries of x vs. }\gamma', fontsize=16)

plt.tight_layout()
plt.show()


"""AyudaEnPython: https://www.facebook.com/groups/ayudapython
"""
from typing import List, Union

Matrix = List[List[Union[int, float]]]

def _determinante(arr: Matrix) -> Union[int, float]:
    if len(arr) == 1:
        return arr[0][0]
    else:
        det = 0
        for i in range(len(arr)):
            det += arr[0][i] * (-1) ** i * _determinante(
                [
                    [arr[j][k] for k in range(len(arr)) if k != i]
                    for j in range(1, len(arr))
                ]
            )
        return det

def determinante(arr: Matrix) -> Union[int, float]:
    for i in range(len(arr) - 1):
        for j in range(i + 1, len(arr)):
            f = arr[j][i] / arr[i][i]
            for k in range(i, len(arr[i])):
                arr[j][k] -= f * arr[i][k]
    d = 1
    for i in range(len(arr)):
        d *= arr[i][i]
    return d

if __name__ == "__main__":
    import numpy as np
    arr: Matrix = [
        [1, 2, 4],
        [3, 4, 7],
        [5, 6, 7],
    ]
    assert _determinante(arr) == determinante(arr) == np.linalg.det(arr)


#!/usr/bin/env python

import numpy as np
import tensorflow as tf
from sklearn.datasets import fetch_california_housing
from sklearn.preprocessing import StandardScaler

learning_rate = 0.01
n_epochs = 10000

def scaler_norm(a):
    return StandardScaler().fit(a).transform(a)

housing = fetch_california_housing()
m, n = housing.data.shape

housing_data_norm = scaler_norm(housing.data)
housing_data_plus_bias = np.c_[np.ones((m, 1)), housing_data_norm]
y_norm = scaler_norm(housing.target.reshape(-1, 1))

X = tf.constant(housing_data_plus_bias, dtype=tf.float32, name='X')
y = tf.constant(y_norm, dtype=tf.float32, name='y')
XT = tf.transpose(X)
theta = tf.Variable(tf.random_uniform([n+1, 1], -1.0, 1.0), dtype=tf.float32, name='theta')
y_pred = tf.matmul(X, theta)
error = y_pred - y
mse = tf.reduce_mean(tf.square(error), name='mse')
optimizer = tf.train.GradientDescentOptimizer(learning_rate=learning_rate)
training_op = optimizer.minimize(mse)

init = tf.global_variables_initializer()

print('#'*80)
print('## Autodiff Gradient descent')
print('#'*80)
with tf.Session() as sess:
    init.run()

    for epoch in range(n_epochs):
        if epoch % 100 == 0:
            print('Epoch', epoch, 'MSE = ', mse.eval())
        sess.run(training_op)

    best_theta = theta.eval()
    print(best_theta)


#import cplane_np as c_np
import numpy as np
from cplane_np import ArrayComplexPlane
#import csv
import matplotlib.pyplot as plt

class JuliaPlane(ArrayComplexPlane):
    def __init__(self, c):

        self.xmin = -2
        self.xmax = 2
        self.xlen = 1000
        self.ymin = -2
        self.ymax = 2
        self.ylen = 1000
        self.fs =[]

        self.c=c
        self.__initplane__()

    def __initplane__(self):
       # print("init plane as:")
        super().__initplane__() #call parent method
       # print(self.plane)
        fv = np.vectorize(julia(self.c,100))
        self.plane=fv(self.plane)
       # print("plane transformed as:")
       # print(self.plane)

    def refresh(self, c):
        """Regenerate complex plane.
        Populate self.plane with new points (x + y*1j), using
        the stored attributes of xmax, xmin, xlen, ymax, ymin,
        and ylen to set plane dimensions and resolution. Reset
        the attribute fs to an empty list so that no functions
        are transforming the fresh plane.
        """
        self.c=c
        self.fs = []
        self.__initplane__()

    def toCSV(self, filename):
        f = open(filename, "a")
        f.write(filename+"\n")
        f.write("Paratemters:")
        f.write("\n")
        f.write('c='+str(self.c)+"\n")
        f.write("xmin="+str(self.xmin)+"\n")
        f.write("xmax="+str(self.xmax)+"\n")
        f.write("xlen="+str(self.xlen)+"\n")
        f.write("ymin="+str(self.ymin)+"\n")
        f.write("ymax="+str(self.ymax)+"\n")
        f.write("ylen="+str(self.ylen)+"\n")
        f.write("\n")
        f.write("\n")
        f.write("The array of the transformed plane by Julia:\n")
        for i in range(0, len(self.plane)):
            rowval=''
            for j in range(0, len(self.plane[i])):
                rowval=rowval+str(self.plane[i][j])+','
            rowval=rowval[:-1] #get rid of last ','
            f.write(rowval)
            f.write("\n")

        f.close()

    def fromCSV(self, filename):
        #read paramets from CSV file and reset the plane to match the settings in the file
        f = open(filename,"r")
        #ignore the file header
        f.readline()
        f.readline()
        #get c
        r=f.readline()
        self.c=complex(r[2:])

        r=f.readline()
        self.xmin=int(r[5:])

        r=f.readline()
        self.xmax=int(r[5:])

        r=f.readline()
        self.xlen=int(r[5:])

        r=f.readline()
        self.ymin=int(r[5:])

        r=f.readline()
        self.ymax=int(r[5:])

        r=f.readline()
        self.ylen=int(r[5:])

        #  refresh the plane array to the values stored in the .csv file directly.
        self.refresh(self.c)

    def  show(self):
        plt.imshow(self.plane, interpolation = 'bicubic', cmap =('viridis'), extent = (self.xmin,self.xmax,self.ymin,self.ymax) )
        plt.title('c='+str(self.c))
        plt.show()
def julia(c, maxnumber):
    '''
    This function returns a function specified by the following algorithm:
    1. The returned function should take one complex parameter z as an input, and return one positive integer as an output.
    2. If the input number z has a magnitude abs(z) larger than 2, the function should output the integer 1.
    3. Otherwise, set a counter n=1.
    4. Increment n by 1, then transform the input z according to the formula z = z**2 + c.
        Check the resulting magnitude abs(z): If the magnitude now exceeds 2, then return the value of n;
        If the magnitude does not yet exceed 2, repeat this step.
    5. If the positive integer max is reached before the magnitude of z exceeds 2, the preceding loop should abort
        and return the output integer 0.

    input parameters:
        c: a complex
        maxnumber: an optional positive integer, if it is reached before the magnitude of z exceeds 2, the preceding loop should abort
        and return the output integer 0

    return:
        return a function with complex z as an input and return one postive interger as an output

   Examples of use of this function:

    >>> f1 = julia(c,100)
    >>> f2 = f1(z)
    '''
    if type(maxnumber) != int or maxnumber < 0:
        print ("max number needs to be positive integer")
        return

    def magnitude(z):
        n=0
        if abs(z)>2:
            #print("init abs(z)>2")
            return 1
        else:
            n=1

        while abs(z) < 2:
            z=z**2+c
            n +=1
            if n > maxnumber:
                return 0
        return n

    return magnitude

#jp=JuliaPlane(1+0.5*1j)
#jp.show()

'''
print("write para and transformed plane to CSV file")
jp.toCSV("jp.csv")

print("ream para and transformed plane from CSV file")
jp.fromCSV("jp.csv")

print("refresh the plane with para from CSV file")
print(jp.plane)

print("what happen when refreh by c=0.5+0.5*1j")
jp.refresh(0.5+0.5*1j)
print(jp.plane)

print("what happen when c=0")
jp=JuliaPlane(0+0*1j)
''''''
'''
#f1=julia(-0.01-0.01*1j,100)
#print(f1)

#f2=f1(1+2*1j)
#print(f2)


from sympy import *

ax, bx, cx, dx, ay, by, cy, dy, t = symbols('ax bx cx dx ay by cy dy t')
x1, y1, x2, y2, x3, y3, x4, y4 = symbols('x1 y1 x2 y2 x3 y3 x4 y4')

xt = ax*t**3 + bx*t**2 + cx*t + dx
yt = ay*t**3 + by*t**2 + cy*t + dy

the_x_system = [
    xt.subs(t, 0.) - x1,
    diff(xt, t).subs(t, 0.) - (x2 - x1) / 3.,
    xt.subs(t, 1.) - x4,
    diff(xt, t).subs(t, 1.) + (x3 - x4) / 3.
]

the_y_system = [
    yt.subs(t, 0.) - y1,
    diff(yt, t).subs(t, 0.) - (y2 - y1) / 3.,
    yt.subs(t, 1.) - y4,
    diff(yt, t).subs(t, 1.) + (y3 - y4) / 3.
]

resx = solve(the_x_system, (ax, bx, cx, dx), rational = True)
resy = solve(the_y_system, (ay, by, cy, dy), rational = True)

for var, exp in resx.items():
    print(var, srepr(exp))
    print('')

for var, exp in resy.items():
    print(var, srepr(exp))
    print('')


import numpy as np

def wiener(N, T=1, mu=0, sig=1):
    """
    Generates a path of a Wiener process of size N, with drift mu and diffusion
    sig. With the default values of mu and sigma, we have *the* Wiener process
    of size N, which is characterized by the following properties:

    1)  W0 = 0;
    2)  Wt is almost surely continuous;
    3)  Wt has independent incremenets;  Wt-Ws ~ N(0, t-s), or a normal
        distribution with mean 0 and variance (t-s)^2. (When t-s=1, this
        is the same as a draw from a standard normal.)
    """
    W = np.zeros(N, np.dtype(float))

    # each timestep is 1/N units, scaled accordingly
    dt = (T/N)**(1/2)

    for i in range(1, N):
        W[i] = W[i-1] + np.random.normal(mu, sig)*dt

    return dict(enumerate(W))

def geom_bm(N, T=1, mu=0, sig=1, S0=0):
    """
    Simulates a geometric Brownian Motion of size N, which comes from the
    solution of the SDE:
        dS_t = \mu S_t dt + \sigma S_t dW_t

    which is:

        S_t = S_0 exp((\mu-\sigma^2/2)t + \sigma W_t)

    which is log-normally distributed.
    """
    dt = (T/N)**(1/2)
    t = np.linspace(0, T, N)
    W = np.cumsum(np.random.normal(size=N))*dt

    X = (mu-(sig**2)/2)*t + sig*W
    S = S0*np.exp(X)

    return dict(enumerate(S))