import casadi as cas
from ..casadi_helpers import sind, cosd

def solar_flux_outside_atmosphere_normal(day_of_year):
    """
    Normal solar flux at the top of the atmosphere (variation due to orbital eccentricity)
    :param day_of_year: Julian day (1 == Jan. 1, 365 == Dec. 31)
    :return: Solar flux [W/m^2]
    """
    # Space effects
    # # Source: https://www.itacanet.org/the-sun-as-a-source-of-energy/part-2-solar-energy-reaching-the-earths-surface/#2.1.-The-Solar-Constant
    # solar_flux_outside_atmosphere_normal = 1367 * (1 + 0.034 * cas.cos(2 * cas.pi * (day_of_year / 365.25)))  # W/m^2; variation due to orbital eccentricity
    # Source: https://www.pveducation.org/pvcdrom/properties-of-sunlight/solar-radiation-outside-the-earths-atmosphere
    return 1366 * (
            1 + 0.033 * cosd(360 * (day_of_year - 2) / 365))  # W/m^2; variation due to orbital eccentricity

def declination_angle(day_of_year):
    """
    Declination angle, in degrees, as a func. of day of year. (Seasonality)
    :param day_of_year: Julian day (1 == Jan. 1, 365 == Dec. 31)
    :return: Declination angle [deg]
    """
    # Declination (seasonality)
    # Source: https://www.pveducation.org/pvcdrom/properties-of-sunlight/declination-angle
    return -23.45 * cosd(360 / 365 * (day_of_year + 10))  # in degrees

def solar_elevation_angle(latitude, day_of_year, time):
    """
    Elevation angle of the sun [degrees] for a local observer.
    :param latitude: Latitude [degrees]
    :param day_of_year: Julian day (1 == Jan. 1, 365 == Dec. 31)
    :param time: Time after local solar noon [seconds]
    :return: Solar elevation angle [degrees] (angle between horizon and sun). Returns 0 if the sun is below the horizon.
    """

    # Solar elevation angle (including seasonality, latitude, and time of day)
    # Source: https://www.pveducation.org/pvcdrom/properties-of-sunlight/elevation-angle
    declination = declination_angle(day_of_year)

    solar_elevation_angle = cas.asin(
        sind(declination) * sind(latitude) +
        cosd(declination) * cosd(latitude) * cosd(time / 86400 * 360)
    ) * 180 / cas.pi  # in degrees
    solar_elevation_angle = cas.fmax(solar_elevation_angle, 0)
    return solar_elevation_angle

def incidence_angle_function(latitude, day_of_year, time, scattering=False):
    """
    What is the fraction of insolation that a horizontal surface will receive as a function of sun position in the sky?
    :param latitude: Latitude [degrees]
    :param day_of_year: Julian day (1 == Jan. 1, 365 == Dec. 31)
    :param time: Time since (local) solar noon [seconds]
    :param scattering: Boolean: include scattering effects at very low angles?
    """
    # Old description:
    # To first-order, this is true. In class, Kevin Uleck claimed that you have higher-than-cosine losses at extreme angles,
    # since you get reflection losses. However, an experiment by Sharma appears to not reproduce this finding, showing only a
    # 0.4-percentage-point drop in cell efficiency from 0 to 60 degrees. So, for now, we'll just say it's a cosine loss.
    # Sharma: https://www.ncbi.nlm.nih.gov/pmc/articles/PMC6611928/

    elevation_angle = solar_elevation_angle(latitude, day_of_year, time)

    theta = 90 - elevation_angle  # Angle between panel normal and the sun, in degrees
    cosine_factor = cosd(theta)

    if not scattering:
        return cosine_factor
    else:
        # Calculate scattering knockdown (See C:\Users\User\Google Drive\School\Grad School\2020 Spring\16-885\Solar Panel Scattering Rough Fit)
        theta_rad = theta * cas.pi / 180
        # Model 1
        c = (
            0.27891510500505767300438719757949,
            -0.015994330894744987481281839336589,
            -19.707332432605799255043166340329,
            -0.66260979582573353852126274432521
        )
        scattering_factor = c[0] + c[3] * theta_rad + cas.exp(
            c[1] * (
                    cas.tan(theta_rad - 1e-8) + c[2] * theta_rad
            )
        )
        # Model 2
        # c = (
        #     -0.04636,
        #     -0.3171
        # )
        # scattering_factor = cas.exp(
        #     c[0] * (
        #         cas.tan(theta_rad-1e-8) + c[1] * theta_rad
        #     )
        # )
        # Model 3
        # p1 = -21.74
        # p2 = 282.6
        # p3 = -1538
        # p4 = 1786
        # q1 = -923.2
        # q2 = 1456
        # x = theta_rad
        # scattering_factor = ((p1*x**3 + p2*x**2 + p3*x + p4) /
        #            (x**2 + q1*x + q2))

        # scattering_factor = cas.fmin(cas.fmax(scattering_factor, 0), 1)

        return cosine_factor * scattering_factor

def solar_flux_on_horizontal(latitude, day_of_year, time, scattering=False):
    """
    What is the solar flux on a horizontal surface for some given conditions?
    :param latitude: Latitude [degrees]
    :param day_of_year: Julian day (1 == Jan. 1, 365 == Dec. 31)
    :param time: Time since (local) solar noon [seconds]
    :param scattering: Boolean: include scattering effects at very low angles?
    :return:
    """
    return (
            solar_flux_outside_atmosphere_normal(day_of_year) *
            incidence_angle_function(latitude, day_of_year, time, scattering)
    )

if __name__ == "__main__":
    # Run some checks
    import plotly.graph_objects as go
    import numpy  as np

    latitudes = np.linspace(26, 49, 200)
    day_of_years = np.arange(0, 365) + 1
    times = np.linspace(0, 86400, 400)

    # Times, Latitudes = np.meshgrid(times, latitudes, indexing="ij")
    # fluxes = np.array(solar_flux_on_horizontal(Latitudes, 244, Times))
    # fig = go.Figure(
    #     data=[
    #         go.Surface(
    #             x=Times / 3600,
    #             y=Latitudes,
    #             z=fluxes,
    #         )
    #     ],
    # )
    # fig.update_layout(
    #     scene=dict(
    #         xaxis=dict(title="Time after Solar Noon [hours]"),
    #         yaxis=dict(title="Latitude [deg]"),
    #         zaxis=dict(title="Solar Flux [W/m^2]"),
    #         camera=dict(
    #             eye=dict(x=-1, y=-1, z=1)
    #         )
    #     ),
    #     title="Solar Flux on Horizontal",
    # )
    # fig.show()
    #
    # fig = go.Figure(
    #     data=[
    #         go.Contour(
    #             z=fluxes.T,
    #             x=times/3600,
    #             y=latitudes,
    #             colorbar=dict(
    #                 title="Solar Flux [W/m^2]"
    #             ),
    #             colorscale="Viridis",
    #         )
    #     ]
    # )
    # fig.update_layout(
    #     scene=dict(
    #         xaxis=dict(title="Hours after Solar Noon [hours]"),
    #         yaxis=dict(title="Latitude [deg]"),
    #     ),
    #     title="Solar Flux on Horizontal",
    #     xaxis_title="Time after Solar Noon [hours]",
    #     yaxis_title="Latitude [deg]",
    # )
    # fig.show()

    import matplotlib.pyplot as plt
    import matplotlib.style as style
    style.use("fivethirtyeight")
    # style.use("seaborn")
    # style.use("ggplot")

    plt.figure()
    lats_to_plot = [26, 49]
    lats_to_plot = np.linspace(0, 90, 7)
    colors = plt.cm.rainbow(np.linspace(0,1,len(lats_to_plot)))[::-1]
    [
        plt.plot(
            times/3600,
            solar_flux_on_horizontal(lats_to_plot[i], 244, times),
            label="%iN Latitude" % lats_to_plot[i],
            color=colors[i]
        ) for i in range(len(lats_to_plot))
    ]
    plt.grid(True)
    plt.legend()
    plt.title("Solar Flux on a Horizontal Surface (Aug. 31)")
    plt.xlabel("Time after Solar Noon [hours]")
    plt.ylabel(r"Solar Flux [W/m$^2$]")
    plt.tight_layout()
    plt.show()


# -*- coding: utf-8 -*-

import os, sys
import numpy as np

def build_cof_mat(num, poly_num=2):
    assert num > 2, "num should bigger than 2"
    cof_mat = np.zeros((num, num), dtype=np.float32)
    for i in range(0, num):
        for j in range(0, num):
            cof_mat[i, j] = np.power(np.absolute(i-j), poly_num)

    return cof_mat

def ordinal_mse(confusion_matrix, poly_num=2):
    category_num = confusion_matrix.shape[0]
    cof_mat = build_cof_mat(category_num, poly_num=poly_num)

    err_mat = np.multiply(confusion_matrix, cof_mat)
    mse_val = np.sum(err_mat) * 1.0 / np.sum(confusion_matrix)

    return mse_val


# tutorial on how to generate Poisson random numbers using the "direct method"
# if we have a sequence of random numbers E following an exp. distribution lam * exp(-x/lam)
# than the integer values where the cumulative sum \sum_{k=1}^n E_k > 1 follows a Poisson
# distribution (homogeneous Poisson point process)

import numpy as np
import matplotlib.pyplot as plt

from scipy.special import factorial

def poisson_generator(lam):
  exp_rand_sum     = 0
  poisson_rand_num = 0

  while True:
    # draw exponential random number with exponent 1/lambda
    exp_rand_num = np.random.exponential(scale = 1./lam, size = 1)[0]
    # add to the sum
    exp_rand_sum += exp_rand_num

    if exp_rand_sum <= 1:
      poisson_rand_num += 1
    else:
      break

  return poisson_rand_num

def poisson_distribution(lam, n):
  return np.exp(-lam)*(lam**n) / factorial(n)

#--------------------------------------------------------------------------------------
#--------------------------------------------------------------------------------------

np.random.seed(1)

# Poisson rate
lam  = 10.5
# number of poisson random variables to simulate
n = 100000

# generatate poisson random variables
poisson_random_variables = np.zeros(n, dtype = np.uint64)

for i in range(n):
  print(i, end = '\r')
  poisson_random_variables[i] = poisson_generator(lam)

#--------------------------------------------------------------------------------------

# plot the histogram
bin_edges   = np.arange(poisson_random_variables.max() + 2) - 0.5
bin_centers = 0.5*(bin_edges[:-1] + bin_edges[1:])

fig, ax = plt.subplots()
# plot the histogram of our poisson random variables
ax.hist(poisson_random_variables, bins = bin_edges, density = True)
# add a plot of the true Poisson distribution
ax.plot(bin_centers, poisson_distribution(lam, bin_centers), 'ro')

fig.tight_layout()
fig.show()


"""System modules"""
import numpy as np
import unittest

class TestNumpy(unittest.TestCase):

    def test_find_magnitude(self):
        arr_1 = np.array([[3.0, 4.0], [-5.0, -12.0]])
        norm_1 = np.linalg.norm(arr_1, ord=None, axis=1)
        assert np.array_equal(norm_1, np.array([5.0, 13.0])) is True

        arr_2 = np.array([[-6.0, 8.0], [7.0, -24.0]])
        norm_2 = np.linalg.norm(arr_2, ord=None, axis=1)
        assert np.array_equal(norm_2, np.array([10.0, 25.0])) is True

    def test_find_atan(self):
        arr = np.array([[1.0,1.0], [-1.0,1.0], [-1.0, -1.0], [1.0, -1.0]])
        x_arr = arr[:,0]
        y_arr = arr[:,1]
        angle = np.arctan2(y_arr, x_arr) * 180 / np.pi
        assert np.array_equal(angle, np.array([45.0, 135.0, -135.0, -45.0])) is True

    def test_horizontal_stack(self):
        x_arr = np.array([1,2,3,4])
        y_arr = np.array([-1,-2,-3,-4])
        arr = np.stack((x_arr, y_arr), axis=1)
        assert np.array_equal(arr, np.array([[1,-1],[2,-2],[3,-3],[4,-4]])) is True

if __name__ == '__main__':
    unittest.main()


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

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

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

    Xs = (X - means) / std

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

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

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

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

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

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

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

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

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

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

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

    for i in range(numberOfIterations):
        for n in range(nSamples):
            predicted = Xs1[n:n+1, :] @ w
            w += learningRate * Xs1[n:n+1, :].T * (T[n:n+1, :] - predicted)
            #print(w)
    dict = {'means': means, 'stds': std, 'w': w}
    return dict

# Load the csv data.
dframe=pd.read_csv('Data\\energydata_complete.csv', sep=',',header=None)
# Filter out required columns.
dframe = dframe.drop(dframe.columns[[0, -2, -1]], axis=1)
# Input data columns
Xlabels = []
Xl = dframe.iloc[0, 2:]
for i in Xl:
	Xlabels.append(i)
# Target data columns
Tlabels = []
Tl = dframe.iloc[0, :2]
for i in Tl:
	Tlabels.append(i)

# Get target.
Td = dframe.iloc[1:, [0,1]]
Td = Td.as_matrix()
Tenergy = Td.astype(float)

# Get input.
Xd = dframe.iloc[1:, 2:]
Xd = Xd.as_matrix()
Xenergy = Xd.astype(float)

# Split into training (80 %) and testing data (20 %).
nRows = Xenergy.shape[0]
nTrain = int(round(0.8*nRows))
nTest = nRows - nTrain

# Shuffle row numbers
rows = np.arange(nRows)
np.random.shuffle(rows)

trainIndices = rows[:nTrain]
testIndices = rows[nTrain:]

Xtrain = Xenergy[trainIndices, :]
Ttrain = Tenergy[trainIndices, :]
Xtest = Xenergy[testIndices, :]
Ttest = Tenergy[testIndices, :]
'''
# Use functions to predict.
# 1. Using first function.
# Train
model = train(Xtrain, Ttrain)
# Use on test data.
predict = use(model, Xtest)
# Calculate RMSE
rmerr = rmse(predict, Ttest)

print("The RMSE with first function: ")
print(rmerr)
'''
# 2. Using the second function.
model2 = trainSGD(Xtrain, Ttrain, 0.01, 10)
predict2 = use(model2, Xtest)
rmerr2 = rmse(predict2, Ttest)

#print(model2)
#print("The RMSE with the second function: ")
print(predict2[:,0])


import numpy as np
from typing import Optional

class LRScheduler:

    def __init__(self, initial_lr: float, total_steps: int):
        self.initial_lr = initial_lr
        self.total_steps = total_steps

    def anneal_lr(self, cur_step: int):
        """Implemented by subclass"""
        pass

    def cur_lr(self, cur_step):
        lr = self.anneal_lr(cur_step)
        return lr

class CosineLRScheduler(LRScheduler):

    def __init__(self, initial_lr: float, total_steps: int):
        super().__init__(initial_lr, total_steps)

    def anneal_lr(self, cur_step: int, min_to_decay_to: float = 0.0):
        lr = 0.5 * self.initial_lr * (1 + np.cos(np.pi * cur_step / self.total_steps))
        lr = np.max([lr, min_to_decay_to])
        return lr

class StepDecay(LRScheduler):

    def __init__(self, initial_lr: float, total_steps: Optional[int] = None, decay_rate: float = 0.5, decay_after_n_steps: int = 5, min_lr: float = 1e-7):
        super().__init__(initial_lr, total_steps)
        assert decay_rate is not None and decay_after_n_steps is not None
        self.decay_rate = decay_rate
        self.decay_after_n_steps = decay_after_n_steps
        self.min_lr = min_lr

    def anneal_lr(self, cur_step: int, decay_rate: Optional[float] = None, decay_after_n_steps: Optional[int] = None):
        if decay_after_n_steps is None:
            decay_after_n_steps = self.decay_after_n_steps
        if decay_rate is None:
            decay_rate = self.decay_rate
        m = cur_step // decay_after_n_steps
        lr = self.initial_lr * (decay_rate ** m)
        lr = self.min_lr if lr < self.min_lr else lr
        return lr

supported_learning_rate_schedulers = {"cosine_anneal": CosineLRScheduler, "fixed": None, "constant": None,
                                      "step": StepDecay, "step_decay": StepDecay}


#!/usr/bin/env python

"""
History:
P.T.Wallace Starlink    November 1984
2002-07-11 ROwen    Rewrote in Python.
2007-04-24 ROwen    Converted from Numeric to numpy.
"""
__all__ = ["vn"]

import numpy
import opscore.RO.MathUtil
import opscore.RO.SysConst

def vn(vec):
    """
    Normalises a vector.

    Inputs:
    - vec   vector

    Returns a tuple containing:
    - the unit vector as a numpy.array
    - the magnitude of the vector

    If the magnitude of vec is too small to compute,
    the unit vector is all zeros and the magnitude is zero.
    """
    vec = numpy.asarray(vec, dtype=float)

    vecMag = opscore.RO.MathUtil.vecMag(vec)

    if vecMag < opscore.RO.SysConst.FSmallNum:
        # this odd construct is a silly way of
        # returning the correct number of zeros
        return (vec * 0.0, 0.0)

    return (vec / vecMag, vecMag)

if __name__ == "__main__":
    import opscore.RO.SeqUtil
    print("testing vn")
    # test data is formatted as follows:
    # a list of entries, each consisting of:
    # - the input argument
    # - the expected result
    testData = (
        ((1, 2, 3), ((0.267261241912424, 0.534522483824849, 0.801783725737273), 3.74165738677394)),
        ((0, 3, 4), ((0.000000000000000, 0.600000000000000, 0.800000000000000), 5.00000000000000)),
        ((0, 0, 0), ((0.000000000000000, 0.000000000000000, 0.000000000000000), 0.000000000000000)),
        ((-1, -2, -3), ((-0.267261241912424, -0.534522483824849, -0.801783725737273), 3.74165738677394)),
    )
    for testInput, expectedOutput in testData:
        actualOutput = vn(testInput)
        actualFlat = opscore.RO.SeqUtil.flatten(actualOutput)
        expectedFlat = opscore.RO.SeqUtil.flatten(expectedOutput)
        if opscore.RO.SeqUtil.matchSequences(actualFlat, expectedFlat, rtol=1e-15):
            print("failed on input:", testInput)
            print("expected output:\n", expectedOutput)
            print("actual output:\n", actualOutput)


import argparse
import contextlib
import time

import cupy
import numpy

# This sample computes call and put prices for Europian options with
# Black-Scholes equation. It was based on a sample of the financial package
# in CUDA toolkit. For details, please see the corresponding whitepaper.
#
# The following code shows that CuPy enables us to write algorithms for GPUs
# without significantly modifying the existing NumPy's code.
# It also briefly describes how to create your own kernel with CuPy.
# If you want to speed up the existing code, please define the kernel
# with cupy.ElementwiseKernel.

# Naive implementation of the pricing of options with NumPy and CuPy.
def black_scholes(xp, s, x, t, r, v):
    sqrt_t = xp.sqrt(t)
    d1 = (xp.log(s / x) + (r + v * v / 2) * t) / (v * sqrt_t)
    d2 = d1 - v * sqrt_t

    def get_cumulative_normal_distribution(x):
        A1 = 0.31938153
        A2 = -0.356563782
        A3 = 1.781477937
        A4 = -1.821255978
        A5 = 1.330274429
        RSQRT2PI = 0.39894228040143267793994605993438
        W = 0.2316419

        k = 1 / (1 + W * xp.abs(x))
        cnd = RSQRT2PI * xp.exp(-x * x / 2) * \
            (k * (A1 + k * (A2 + k * (A3 + k * (A4 + k * A5)))))
        cnd = xp.where(x > 0, 1 - cnd, cnd)
        return cnd

    cnd_d1 = get_cumulative_normal_distribution(d1)
    cnd_d2 = get_cumulative_normal_distribution(d2)

    exp_rt = xp.exp(- r * t)
    call = s * cnd_d1 - x * exp_rt * cnd_d2
    put = x * exp_rt * (1 - cnd_d2) - s * (1 - cnd_d1)
    return call, put

# An example of calling the kernel via cupy.ElementwiseKernel.
# When executing __call__ method of the instance, it automatically compiles
# the code depending on the types of the given arrays, and calls the kernel.
# Other functions used inside the kernel can be defined by 'preamble' option.
black_scholes_kernel = cupy.ElementwiseKernel(
    'T s, T x, T t, T r, T v',  # Inputs
    'T call, T put',  # Outputs
    '''
    const T sqrt_t = sqrt(t);
    const T d1 = (log(s / x) + (r + v * v / 2) * t) / (v * sqrt_t);
    const T d2 = d1 - v * sqrt_t;

    const T cnd_d1 = get_cumulative_normal_distribution(d1);
    const T cnd_d2 = get_cumulative_normal_distribution(d2);

    const T exp_rt = exp(- r * t);
    call = s * cnd_d1 - x * exp_rt * cnd_d2;
    put = x * exp_rt * (1 - cnd_d2) - s * (1 - cnd_d1);
    ''',
    'black_scholes_kernel',
    preamble='''
    __device__
    inline T get_cumulative_normal_distribution(T x) {
        const T A1 = 0.31938153;
        const T A2 = -0.356563782;
        const T A3 = 1.781477937;
        const T A4 = -1.821255978;
        const T A5 = 1.330274429;
        const T RSQRT2PI = 0.39894228040143267793994605993438;
        const T W = 0.2316419;

        const T k = 1 / (1 + W * abs(x));
        T cnd = RSQRT2PI * exp(- x * x / 2) *
            (k * (A1 + k * (A2 + k * (A3 + k * (A4 + k * A5)))));
        if (x > 0) {
            cnd = 1 - cnd;
        }
        return cnd;
    }
    ''',
)

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--gpu-id', '-g', default=0, type=int, help='GPU ID')
    parser.add_argument('--n-options', '-n', default=10000000, type=int)
    args = parser.parse_args()

    cupy.cuda.Device(args.gpu_id).use()

    def rand_range(m, M):
        samples = cupy.random.rand(args.n_options)
        return (m + (M - m) * samples).astype(numpy.float64)

    print('initializing...')
    stock_price_gpu = rand_range(5, 30)
    option_strike_gpu = rand_range(1, 100)
    option_years_gpu = rand_range(0.25, 10)

    stock_price_cpu = stock_price_gpu.get()
    option_strike_cpu = option_strike_gpu.get()
    option_years_cpu = option_years_gpu.get()

    @contextlib.contextmanager
    def timer(message):
        cupy.cuda.Stream.null.synchronize()
        start = time.time()
        yield
        cupy.cuda.Stream.null.synchronize()
        end = time.time()
        print('%s:\t%f sec' % (message, end - start))

    print('start computation')
    risk_free = 0.02
    volatility = 0.3
    with timer(' CPU (NumPy, Naive implementation)'):
        call_cpu, put_cpu = black_scholes(
            numpy, stock_price_cpu, option_strike_cpu, option_years_cpu,
            risk_free, volatility)

    with timer(' GPU (CuPy, Naive implementation)'):
        call_gpu1, put_gpu1 = black_scholes(
            cupy, stock_price_gpu, option_strike_gpu, option_years_gpu,
            risk_free, volatility)

    with timer(' GPU (CuPy, Elementwise kernel)'):
        call_gpu2, put_gpu2 = black_scholes_kernel(
            stock_price_gpu, option_strike_gpu, option_years_gpu,
            risk_free, volatility)

    # Check whether all elements in gpu arrays are equal to those of cpus
    cupy.testing.assert_allclose(call_cpu, call_gpu1)
    cupy.testing.assert_allclose(call_cpu, call_gpu2)
    cupy.testing.assert_allclose(put_cpu, put_gpu1)
    cupy.testing.assert_allclose(put_cpu, put_gpu2)


from math import sin, pi
import numpy as np
from sympy import Symbol, integrate
# function for the trapezoidal rule
def Simpson(a,b,f,n):
   h = (b-a)/float(n)
   sum = f(a)/float(2);
   for i in range(1,n):
       sum = sum + f(a+i*h)*(3+(-1)**(i+1))
   sum = sum + f(b)/float(2)
   return sum*h/3.0
# function to integrate
def function(x):
    return sin(2*pi*x)
# define integration limits and integration points
a = 0.0; b = 0.5;
n = 100
Exact = 1./pi
print("Relative error= ", abs( (Simpson(a,b,function,n)-Exact)/Exact))


from collections import Counter
import numpy as np

def entropy(predictions, normalize=False, n_categories=None):
    counts = Counter(list(predictions)).values()

    if len(counts) <= 1:
        return 0.0

    total_count = float(len(predictions))

    if not total_count:
        return 0.0

    probabilities = []
    for count in counts:
        probabilities.append(count / total_count)

    probabilities = np.array(probabilities)
    value = - np.sum(probabilities * np.log(probabilities))
    if normalize is True:
        if n_categories is not None:
            value /= float(np.log(n_categories))
        else:
            value /= float(np.log(len(counts)))
    return value


from sympy import *

def rotX(a):
    return Matrix([[1, 0, 0, 0],
		[0, cos(a), -sin(a), 0],
		[0, sin(a), cos(a), 0],
		[0, 0, 0, 1]])
def rotY(a):
    return Matrix([[cos(a), 0, sin(a), 0],
		[0, 1, 0, 0],
		[-sin(a), 0, cos(a), 0],
		[0, 0, 0, 1]])
def rotZ(a):
    return Matrix([[cos(a), -sin(a), 0, 0],
		[sin(a), cos(a), 0, 0],
		[0, 0, 1, 0],
		[0, 0, 0, 1]])
def transXYZ(x, y, z):
    return Matrix([[1, 0, 0, x],
		[0, 1, 0, y],
		[0, 0, 1, z],
		[0, 0, 0, 1]])

def matrix44FromTaitBryan(px, py, pz, om, fi, ka):
    return transXYZ(px, py, pz) * rotX(om) * rotY(fi) * rotZ(ka)

def matrix44FromTaitBryanZYX(px, py, pz, om, fi, ka):
    return transXYZ(px, py, pz) * rotZ(ka) * rotY(fi) * rotX(om)

def taitBryanFromMatrix44Case1(m):
    omfika = Matrix([0, 0, 0])
    omfika[0] = atan2(-m[1,2], m[2,2])#om
    omfika[1] = asin(m[0,2])          #fi
    omfika[2] = atan2(-m[0,1], m[0,0])#ka
    return omfika

def taitBryanFromMatrix44Case2(m):
    omfika = Matrix([0, 0, 0])
    omfika[0] = -atan2(m[1,0], m[1,1])#om
    omfika[1] = -pi / 2.0             #fi
    omfika[2] = 0                     #ka
    return omfika

def taitBryanFromMatrix44Case3(m):
    omfika = Matrix([0, 0, 0])
    omfika[0] = atan2(m[1,0], m[1,1]) #om
    omfika[1] = pi / 2.0              #fi
    omfika[2] = 0                     #ka
    return omfika


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

import numpy as np

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

def identity_function(x):
    return x

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

    x = x - np.max(x)
    return np.exp(x) / np.sum(np.exp(x))

def mean_squared_error(y, t):
    return 0.5 * np.sum((y - t) ** 2)

def cross_entropy_error(y, t):
    if y.ndim == 1:
        t = t.reshape(1, t.size)
        y = y.reshape(1, y.size)

    if t.size == y.size:
        t = t.argmax(axis=1)

    batch_size = y.shape[0]

    return -np.sum(np.log(y[np.arange(batch_size), t] + 1e-7)) / batch_size


def hpd(data, level = .95) :
  """ The Highest Posterior Density (credible) interval of data at level level.

  :param data: sequence of real values
  :param level: (0 < level < 1)
  """

  d = list(data)
  d.sort()

  nData = len(data)
  nIn = int(round(level * nData))
  if nIn < 2 :
    raise RuntimeError("not enough data")

  i = 0
  r = d[i+nIn-1] - d[i]
  for k in range(len(d) - (nIn - 1)) :
    rk = d[k+nIn-1] - d[k]
    if rk < r :
      r = rk
      i = k

  assert 0 <= i <= i+nIn-1 < len(d)

  return (d[i], d[i+(nIn/2)-1],d[i+nIn-1])

import numpy as np
def HDI_from_MCMC(posterior_samples, credible_mass):
  # Computes highest density interval from a sample of representative values,
  # estimated as the shortest credible interval
  # Takes Arguments posterior_samples (samples from posterior) and credible mass (normally .95)
  sorted_points = sorted(posterior_samples)
  ciIdxInc = np.ceil(credible_mass * len(sorted_points)).astype('int')
  nCIs = len(sorted_points) - ciIdxInc
  ciWidth = [0]*nCIs
  for i in range(0, nCIs): ciWidth[i] = sorted_points[i + ciIdxInc] - sorted_points[i]
  HDImin = sorted_points[ciWidth.index(min(ciWidth))]
  HDImax = sorted_points[ciWidth.index(min(ciWidth))+ciIdxInc]
  return(HDImin, HDImax)


# Lab 08, Question 04
# AB Satyaprakash, 180123062

# imports
import numpy as np
import pandas as pd
from math import exp
import matplotlib.pyplot as plt

# functions

def P(x):
    return -10

def Q(x):
    return 0

def R(x):
    return 0

def y(x):
    return (exp(10*x)-1)/(exp(10)-1)

def genCentralMatrix():
    L = []
    xi = a+h
    n = int((b-a)/h) - 1
    for i in range(1, n+1):
        L1 = [0]*n
        if i-2 >= 0:
            L1[i-2] = 1 - h*P(xi)/2
        L1[i-1] = -2 + h*h*Q(xi)
        if i < n:
            L1[i] = 1+h*P(xi)/2
        L.append(L1)
        xi += h
    return np.array(L)

def genCentralVector():
    F = []
    xi = a+h
    n = int((b-a)/h) - 1
    for i in range(1, n+1):
        f = h*h*R(xi)
        if i == 1:
            f -= alpha*(1 - h*P(xi)/2)
        if i == n:
            f -= beta*(1 + h*P(xi)/2)
        F.append(f)
        xi += h
    return np.array(F)

def genBackwardMatrix():
    L = []
    xi = a+h
    n = int((b-a)/h) - 1
    for i in range(1, n+1):
        L1 = [0]*n
        if i-2 >= 0:
            L1[i-2] = 1 - h*P(xi)
        L1[i-1] = -2 + h*h*Q(xi) + h*P(xi)
        if i < n:
            L1[i] = 1
        L.append(L1)
        xi += h
    return np.array(L)

def genBackwardVector():
    F = []
    xi = a+h
    n = int((b-a)/h) - 1
    for i in range(1, n+1):
        f = h*h*R(xi)
        if i == 1:
            f -= alpha*(1 - h*P(xi))
        if i == n:
            f -= beta
        F.append(f)
        xi += h
    return np.array(F)

def genForwardMatrix():
    L = []
    xi = a+h
    n = int((b-a)/h) - 1
    for i in range(1, n+1):
        L1 = [0]*n
        if i-2 >= 0:
            L1[i-2] = 1
        L1[i-1] = -2 + h*h*Q(xi) - h*P(xi)
        if i < n:
            L1[i] = 1 + h*P(xi)
        L.append(L1)
        xi += h
    return np.array(L)

def genForwardVector():
    F = []
    xi = a+h
    n = int((b-a)/h) - 1
    for i in range(1, n+1):
        f = h*h*R(xi)
        if i == 1:
            f -= alpha
        if i == n:
            f -= beta*(1 + h*P(xi))
        F.append(f)
        xi += h
    return np.array(F)

# program body
# BVP : y" - 10y' = 0. Thus P = -10, Q = 0, R = 0
a, b = 0, 1
alpha, beta = 0, 1
h = 1/4

L = genCentralMatrix()
F = genCentralVector()
U = np.linalg.inv(L).dot(F)
Y_c = [alpha]
for u in U:
    Y_c.append(u)
Y_c.append(beta)
Y1 = [y(a+i*h) for i in range(len(Y_c))]
df = pd.DataFrame()
df['Nodal points'] = pd.Series(['0', '1/4', '2/4', '3/4', '1'])
df['Actual'] = pd.Series(Y1)
df['Central Approximation'] = pd.Series(Y_c)
df['Central Error'] = pd.Series(abs(np.array(Y1)-np.array(Y_c)))

L = genBackwardMatrix()
F = genBackwardVector()
U = np.linalg.inv(L).dot(F)
Y_b = [alpha]
for u in U:
    Y_b.append(u)
Y_b.append(beta)
df['Backward Approximation'] = pd.Series(Y_b)
df['Backward Error'] = pd.Series(abs(np.array(Y1)-np.array(Y_b)))

L = genForwardMatrix()
F = genForwardVector()
U = np.linalg.inv(L).dot(F)
Y_f = [alpha]
for u in U:
    Y_f.append(u)
Y_f.append(beta)
df['Forward Approximation'] = pd.Series(Y_f)
df['Forward Error'] = pd.Series(abs(np.array(Y1)-np.array(Y_f)))
print()
print(df)
print()

NodalPoints = [0, 1/4, 2/4, 3/4, 1]
plt.plot(NodalPoints, df['Central Error'], label='Central Scheme')
plt.plot(NodalPoints, df['Backward Error'], label='Backward Scheme')
plt.plot(NodalPoints, df['Forward Error'], label='Forward Scheme')
plt.xlabel('Nodal Points')
plt.ylabel('Error Magnitude')
plt.title('Errors using different schemes for dy/dx')
plt.legend()
plt.show()

Semester 6/MA 322 (Scientific Computing Theory)/Labs/Lab 8/Code/q4.py


from numpy import random,zeros_like

def multinomial(N,probabilities):
    '''
    works like  numpy.random.multinomial!

    You can draw from a multinomial using the following:
    1) get the probabilities for each state.
    That is a list with probabilities, [p1,p2,...,pn], each coresponding to an outcome

    2) calculate a list containing the cumulative probabilities (c=[p1, p1+p2, p1+p2+p3, ...,1])

    3) draw a random number, r, form 0 to 1

    4) if r<=c[i], return i.
    '''

    cumulatives=[]
    _cm=0
    for i in probabilities:
        _cm+=i
        cumulatives.append(_cm)

    state=zeros_like(probabilities)

    for i in range(N):
        _r=random.rand()
        for i,_p in enumerate(cumulatives):
            if _r<=_p:
                state[i]+=1
                break

    return state

if __name__=='__main__':
    '''Test run of the function'''
    _N=500
    _initial_p=[0.1,0.1,0.5,0.2,0.01,0.09]

    _m= multinomial(_N,_initial_p)

    print _initial_p,sum(_initial_p)

    _produced_p=[i/float(_N) for i in _m]
    print _produced_p, sum(_produced_p)

    print _m


#!/usr/bin/env python

# From: https://stackoverflow.com/a/13224592/1558655
# by doug

def PCA(data, dims_rescaled_data=2):
    """
    returns: data transformed in 2 dims/columns + regenerated original data
    pass in: data as 2D NumPy array
    """
    import numpy as NP
    from scipy import linalg as LA
    m, n = data.shape
    # mean center the data
    data -= data.mean(axis=0)
    # calculate the covariance matrix
    R = NP.cov(data, rowvar=False)
    # calculate eigenvectors & eigenvalues of the covariance matrix
    # use 'eigh' rather than 'eig' since R is symmetric,
    # the performance gain is substantial
    evals, evecs = LA.eigh(R)
    # sort eigenvalue in decreasing order
    idx = NP.argsort(evals)[::-1]
    evecs = evecs[:,idx]
    # sort eigenvectors according to same index
    evals = evals[idx]
    # select the first n eigenvectors (n is desired dimension
    # of rescaled data array, or dims_rescaled_data)
    evecs = evecs[:, :dims_rescaled_data]
    # carry out the transformation on the data using eigenvectors
    # and return the re-scaled data, eigenvalues, and eigenvectors
    return NP.dot(evecs.T, data.T).T, evals, evecs

def test_PCA(data, dims_rescaled_data=2):
    '''
    test by attempting to recover original data array from
    the eigenvectors of its covariance matrix & comparing that
    'recovered' array with the original data
    '''
    _ , _ , eigenvectors = PCA(data, dim_rescaled_data=2)
    data_recovered = NP.dot(eigenvectors, m).T
    data_recovered += data_recovered.mean(axis=0)
    assert NP.allclose(data, data_recovered)

def plot_pca(data):
    from matplotlib import pyplot as MPL
    clr1 =  '#2026B2'
    fig = MPL.figure()
    ax1 = fig.add_subplot(111)
    data_resc, evals, evecs = PCA(data)
    ax1.plot(data_resc[:, 0], data_resc[:, 1], '.', mfc=clr1, mec=clr1)
    MPL.show()


import numpy as np

class LinearClassifier(object):

  def __init__(self):
    self.theta = None

  def train(self, X, y, learning_rate=1e-3, reg=1e-5, num_iters=100,batch_size=200,
             verbose=False):
    m,d = X.shape
    K  = np.max(y) + 1 # assume y takes values 0...K-1 where K is number of classes

    if self.theta is None:
      # lazily initialize theta
      if K == 2:
        self.theta = 0.001 * np.random.randn((d,))
      else:
        self.theta = 0.001 * np.random.randn(d, K)

    # Run gradient descent to optimize theta
    loss_history = []
    for it in range(num_iters):

      # draw batch_size examples from X
      if batch_size < X.shape[0]:
        rand_idx = np.random.choice(m, batch_size)
        X_batch = X[rand_idx,:]
        y_batch = y[rand_idx]
      else:
        X_batch = X
        y_batch = y

      # evaluate loss and gradient
      loss, grad = self.loss(self.theta, X_batch, y_batch, reg)
      loss_history.append(loss)

      # perform parameter update
      self.theta = self.theta - learning_rate * grad

      if verbose and it % 100 == 0:
        print('iteration %d / %d: loss %f' % (it, num_iters, loss))

    return loss_history

  def predict(self, X):
    """
    Use the coefficients of this linear classifier to predict labels for
    data points.

    Inputs:
    - X: m x d array of training data. Each row is a d-dimensional point.

    Returns:
    - y_pred: Predicted labels for the data in X. y_pred is a 1-dimensional
      array of length m, and each element is an integer giving the
      predicted class.
    """
    pass

  def loss(self, X, y, reg):
    """
    Compute the loss function and its derivative.
    Subclasses will override this.

    Inputs:
    - X: A numpy array of shape (m,d)
    - y: A numpy array of shape (m,) containing labels for X.

    - reg: (float) penalty term.

    Returns: A tuple containing:
    - loss as a single float
    - gradient with respect to self.theta; an array of the same shape as theta
    """
    pass

from linear_svm import binary_svm_loss, svm_loss_vectorized

class LinearSVM_twoclass(LinearClassifier):
  """ A subclass that uses the two-class SVM loss function """

  def loss(self, X, y, C):
    return binary_svm_loss(self.theta, X, y, C)

  def predict(self,X):
    y_pred = np.dot(X,self.theta)
    y_pred[y_pred < 0] = -1
    y_pred[y_pred > 0] = 1

    return y_pred

class LinearSVM(LinearClassifier):
  """ A subclass that uses the Multiclass SVM loss function """

  def loss(self, theta, X, y, reg):
    return svm_loss_vectorized(theta, X, y, reg)

  def predict(self,X,theta):
    y_pred = np.zeros(X.shape[0])
    y_pred = np.argmax(np.dot(X,theta), axis=1)

    return y_pred


from numpy.testing import assert_approx_equal
import os

def rataan(daftar_nilai):
    """
    Menerima masukan berupa list dan mengembalikan satu nilai rata-rata
    e.g. [90, 73, 82, 100, 36, 44, 60] => 69.285...
    """
    pass

def simpangan_baku(daftar_nilai):
    """
    Menerima masukan berupa list dan mengembalikan nilai simpangan bakunya
    Anda dapat memanfaatkan fungsi rataan() di sini
    e.g. [90, 73, 82, 100, 36, 44, 60] => 21.978...
    """
    pass

def median(daftar_nilai):
    """
    Menerima masukan berupa list dan mengembalikan nilai tengahnya
    e.g. [90, 73, 82, 100, 36, 44, 60] => 73
         [90, 73, 82, 36, 44, 60] => 66.5
    """
    pass

def indeks(nilai):
    """
    Menerima masukan berupa nilai dan mengembalikan indeksnya
    e.g. 70 => A
         45 => D
         27 => F
         60 => B
    """
    pass

def rekap(daftar_indeks):
    """
    Menerima masukan berupa daftar indeks dan mengembalikan dictionary berupa jumlah indeks
    e.g. ['A', 'D', 'F', 'B', 'A', 'C'] => {'A': 2, 'B': 1, 'C': 1, 'D': 1, 'F': 1}
    Perhatikan bahwa tidak ada nilai E dalam dictionary yang dihasilkan
    """
    pass

def tulis_laporan(filename):
    pass

def test():
    daftar_nilai = [90, 73, 82, 100, 36, 44, 60]
    assert_approx_equal(rataan(daftar_nilai), 69.285)
    assert_approx_equal(simpangan_baku(daftar_nilai), 21.978)
    assert_approx_equal(median(daftar_nilai), 73)
    assert_approx_equal(median(daftar_nilai[:-1]), 77.5)
    assert indeks(70) == 'A'
    assert indeks(45) == 'D'
    assert indeks(27) == 'F'
    assert indeks(60) == 'B'

def main():
    files = os.listdir('.')
    # Lengkapi kodenya di sini

if __name__ == '__main__':
    test()
    main()


import numpy as np
# from math import gamma
import matplotlib.pyplot as plt
from scipy.special import gamma
from scipy import stats
import numpy as np
from scipy.optimize import minimize
import matplotlib.pyplot as plt
from scipy.stats import beta

x = np.arange (0.01, 1, 0.01)
y = beta.pdf(x,1,1)
plt.plot(x,y, label="alpha=1, beta=1")
y = beta.pdf(x,5,5)
plt.plot(x,y, label="alpha=5, beta=5")
y = beta.pdf(x,10,10)
plt.plot(x,y, label="alpha=10, beta=10")
plt.title("Beta Distributions")
plt.legend()

from scipy.stats import beta
x = np.arange (0.01, 1, 0.01)
y = beta.pdf(x,1,2)
plt.plot(x,y, label="alpha=1, beta=2")
y = beta.pdf(x,5,5)
plt.plot(x,y, label="alpha=5, beta=5")
y = beta.pdf(x,10,11)
plt.plot(x,y, label="alpha=10, beta=11")
plt.title("Beta Distributions")
plt.legend()


import numpy as np

def rosenbrock(X):
	x = X[0]
	y = X[1]
	f = ((1-x)**2 + 100*(y-x**2)**2)
	return f

def kenny(X):
	x=X[0]
	y=X[1]
	F = np.sin(x+y) +(x-y)**2-(1.5*x)+(2.5*y)+1
	return F


import jax.numpy as jnp
import numpy as np

class KalmanFilter:
    def __init__(self, A, B, Q, C, R):
        self.params = dict()
        self._init_params(A, B, Q, C, R)

    def _init_params(self, A, B, Q, C, R):
        self.params["A"] = A # process matrix
        self.params["B"] = B # control matrix
        self.params["Q"] = Q # symetric postiive definite process noise covariance
        self.params["C"] = C # measurement matrix
        self.params["R"] = R # symetric postiive definite measurement noise covariance

    def dynamic(self, x, u):
        return jnp.add(jnp.add(jnp.dot(self.params["A"], x), jnp.dot(self.params["B"], u)), jnp.dot(jnp.linalg.cholesky(self.params["Q"]),np.random.randn(len(x))))

    def observation(self, x):
        return jnp.add(jnp.dot(self.params["C"],x), jnp.dot(jnp.linalg.cholesky(self.params["R"]),np.random.randn(len(x))))

    def prediction(self, s, u):
        mu, sigma = s # unroll states
        mu_hat = jnp.add(jnp.dot(self.params["A"], mu), jnp.dot(self.params["B"], u))
        sigma_hat = jnp.add(jnp.dot(jnp.dot(self.params["A"], sigma), jnp.transpose(self.params["A"])), self.params["Q"])
        return (mu_hat, sigma_hat)

    def correction(self, s, y):
        mu, sigma = s # unroll states
        v = jnp.subtract(y, jnp.dot(self.params["C"], mu)) # innovation
        S = jnp.add(jnp.dot(jnp.dot(self.params["C"], sigma), jnp.transpose(self.params["C"])), self.params["R"]) # innovation covariance
        K = jnp.dot(jnp.dot(sigma, jnp.transpose(self.params["C"])), jnp.linalg.inv(S)) # Kalman gain
        new_mu = jnp.add(mu, jnp.dot(K, v)) # update state estimation
        new_sigma = jnp.dot(jnp.subtract(jnp.eye(len(mu)), jnp.dot(K, self.params["C"])), sigma) # update state covariance estimation
        return (new_mu, new_sigma)

    def update(self, s, u, y):
        mu, sigma = s
        s = self.prediction(s, u)
        s = self.correction(s, y)
        return s

    def mse_loss(self, s_hat, s_post):
        mu_hat, sigma_hat = s_hat
        mu_post, sigma_post = s_post
        # ex = jnp.linalg.norm(jnp.subtract(mu, jnp.add(jnp.dot(self.params["A"], mu), jnp.dot(self.params["B"], u))))
        # ey = jnp.linalg.norm(jnp.subtract(y, jnp.dot(self.params["C"], mu)))
        return jnp.linalg.norm(mu_hat - mu_post)


import numpy as np

def mean_squared_error(y_hat, y):
    return np.mean(np.power(y_hat - y, 2))

def d_mean_squared_error(y_hat, y):
    return 2 * (y - y_hat) / np.size(y_hat)


import numpy as np
import matplotlib.pyplot as plt

plt.style.use('ggplot')
plt.rcParams['figure.figsize'] = (12, 8)

# 特征分解
# https://robot.czxy.com/docs/pcl/chapter03/point_cloud_math/
# 特征分解(旋转矩阵和特征向量矩阵相关，缩放矩阵与特征值矩阵相关)
sigma = np.array([
    [19 / 16, -15 * np.sqrt(3) / 16,],
    [-15 * np.sqrt(3) / 16, 49 / 16]
])

eVa, eVe = np.linalg.eig(sigma)
print("eigen value:\n", eVa)
print("eigen vector:\n", eVe)

# plt.scatter(Y[:, 0], Y[:, 1])
for value, eigen in zip(eVa, eVe.T):
    plt.plot(
        [0, 3 * np.sqrt(value) * eigen[0]],
        [0, 3 * np.sqrt(value) * eigen[1]],
        lw=5)

plt.title('Transformed Data')
plt.axis('equal')
plt.show()

"""
一个多元正态分布的概率密度函数中，其协方差矩阵 Σ 包含了分布的旋转角度和缩放信息，其均值向量
→
μ
 包含了密度中心的位置信息。
"""


from aerosandbox.tools.fitting import fit
import pytest
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

sns.set(palette=sns.color_palette("husl"))

plot = False  # Should we plot during testing?

def test_single_dimensional_polynomial_fitting():
    np.random.seed(0)  # Set a seed for repeatability.

    ### Make some data
    x = np.linspace(0, 10, 50)
    noise = 5 * np.random.randn(len(x))
    y = x ** 2 - 8 * x + 5 + noise

    ### Fit data
    def model(x, p):
        return (
                p["a"] * x["x1"] ** 2 +
                p["b"] * x["x1"] +
                p["c"]
        )

    x_data = {
        "x1": x
    }

    fitted_parameters = fit(
        model=model,
        x_data=x_data,
        y_data=y,
        param_guesses={
            "a": 1,
            "b": 1,
            "c": 1,
        },
        param_bounds={
            "a": (None, None),
            "b": (None, None),
            "c": (None, None),
        },
    )

    ### Plot data and fit
    if plot:
        fig, ax = plt.subplots(1, 1, figsize=(6.4, 4.8), dpi=200)
        plt.plot(x, y, ".", label="Data")
        plt.plot(x, model(x_data, fitted_parameters), "-", label="Fit")
        plt.xlabel(r"$x$")
        plt.ylabel(r"$y$")
        plt.title(r"Automatic-Differentiable Fitting")
        plt.tight_layout()
        plt.legend()
        # plt.savefig("C:/Users/User/Downloads/temp.svg")
        plt.show()

    ### Check that the fit is right
    assert fitted_parameters["a"] == pytest.approx(1.046091, abs=1e-3)
    assert fitted_parameters["b"] == pytest.approx(-9.166716, abs=1e-3)
    assert fitted_parameters["c"] == pytest.approx(9.984351, abs=1e-3)

def test_multidimensional_power_law_fitting():
    np.random.seed(0)  # Set a seed for repeatability.

    ### Make some data z(x,y)
    x = np.logspace(0, 3)
    y = np.logspace(0, 3)
    X, Y = np.meshgrid(x, y, indexing="ij")
    noise = np.random.lognormal(mean=0, sigma=0.05)
    Z = 0.5 * X ** 0.75 * Y ** 1.25 * noise

    ### Fit data
    def model(x, p):
        return (
                p["multiplier"] *
                x["X"] ** p["X_power"] *
                x["Y"] ** p["Y_power"]
        )

    x_data = {
        "X": X.flatten(),
        "Y": Y.flatten(),
    }

    fitted_parameters = fit(
        model=model,
        x_data=x_data,
        y_data=Z.flatten(),
        param_guesses={
            "multiplier": 1,
            "X_power"   : 1,
            "Y_power"   : 1,
        },
        param_bounds={
            "multiplier": (None, None),
            "X_power"   : (None, None),
            "Y_power"   : (None, None),
        },
        put_residuals_in_logspace=True
        # Putting residuals in logspace minimizes the norm of log-error instead of absolute error
    )

    ### Check that the fit is right
    assert fitted_parameters["multiplier"] == pytest.approx(0.546105, abs=1e-3)
    assert fitted_parameters["X_power"] == pytest.approx(0.750000, abs=1e-3)
    assert fitted_parameters["Y_power"] == pytest.approx(1.250000, abs=1e-3)

def test_deviation_single_dimensional_fit():
    np.random.seed(0)  # Set a seed for repeatability

    ### Making data
    hour = np.linspace(1, 10, 100)
    noise = 0.1 * np.random.randn(len(hour))
    temperature_c = np.log(hour) + noise

    ### Fit
    def model(x, p):
        return p["m"] * x["hour"] + p["b"]

    x_data = {
        "hour": hour,
    }
    y_data = temperature_c
    solved_parameters = fit(
        model=model,
        x_data=x_data,
        y_data=y_data,
        param_guesses={
            "m": 0,
            "b": 0,
        },
        # residual_norm_type="SSE",
        residual_norm_type="deviation",
    )
    temperature_model = model(x_data, solved_parameters)
    ### Plotting
    if plot:
        fig, ax = plt.subplots(1, 1, figsize=(6.4, 4.8), dpi=200)
        plt.plot(hour, temperature_c, ".")
        plt.plot(hour, temperature_model, "-")
        plt.xlabel(r"$hour$")
        plt.ylabel(r"$temp$")
        plt.title(r"Data and Fit")
        plt.tight_layout()
        plt.legend()
        # plt.savefig("C:/Users/User/Downloads/temp.svg")
        plt.show()

    # Check that the fit is right
    assert solved_parameters["m"] == pytest.approx(0.247116, abs=1e-5)
    assert solved_parameters["b"] == pytest.approx(0.227797, abs=1e-5)

def test_deviation_without_x_in_dict():
    np.random.seed(0)  # Set a seed for repeatability

    ### Making data
    hour = np.linspace(1, 10, 100)
    noise = 0.1 * np.random.randn(len(hour))
    temperature_c = np.log(hour) + noise

    ### Fit
    def model(x, p):
        return p["m"] * x + p["b"]

    x_data = hour
    y_data = temperature_c

    solved_parameters = fit(
        model=model,
        x_data=x_data,
        y_data=y_data,
        param_guesses={
            "m": 0,
            "b": 0,
        },
        # residual_norm_type="SSE",
        residual_norm_type="deviation",
    )
    temperature_model = model(x_data, solved_parameters)
    ### Plotting
    if plot:
        fig, ax = plt.subplots(1, 1, figsize=(6.4, 4.8), dpi=200)
        plt.plot(hour, temperature_c, ".")
        plt.plot(hour, temperature_model, "-")
        plt.xlabel(r"$hour$")
        plt.ylabel(r"$temp$")
        plt.title(r"Data and Fit")
        plt.tight_layout()
        plt.legend()
        # plt.savefig("C:/Users/User/Downloads/temp.svg")
        plt.show()

    # Check that the fit is right
    assert solved_parameters["m"] == pytest.approx(0.247116, abs=1e-5)
    assert solved_parameters["b"] == pytest.approx(0.227797, abs=1e-5)

if __name__ == '__main__':
    pytest.main()


import numpy as np

class Perceptron:
    def __init__(self, N, alpha=0.1):
        # Initialise the weight matrix and store the learning rate
        self.W = np.random.randn(N + 1) / np.sqrt(N)
        self.alpha = alpha

    def step(self, x):
        # Apply the step function
        return 1 if x > 0 else 0

    def fit(self, X, y, epochs=10):
        # Insert a column of 1's as the last entry of the feature matrix
        # This allows us the treat the bias as a trainable parameter with the weight matrix
        X = np.c_[X, np.ones((X.shape[0]))]

        # Loop over the desired number of epochs
        for epoch in np.arange(0, epochs):
            # Loop over each individual data point
            for (x, target) in zip(X, y):
                # Pass the dot product of the input features and weight matrix through the step function
                pred = self.step(np.dot(x, self.W))

                # Only performs a weight update if our prediction does not match the target
                if pred != target:
                    # Calculate the error
                    error = pred - target

                    # Update the weight matrix
                    self.W += -self.alpha * error * x

    def predict(self, X, add_bias=True):
        # Ensure our input is a matrix
        X = np.atleast_2d(X)

        # Check to see if the bias column should be added
        if add_bias:
            # Insert a column of 1's as the last entry in the feature matrix
            X = np.c_[X, np.ones((X.shape[0]))]

        # Pass the dot product of the input features and weight matrix through the step function
        return self.step(np.dot(X, self.W))


import tensorflow as tf
import numpy as np

#100 phony y, x data points are created in NumPy, y = 0.3 + x * 0.1
x_data = np.random.rand(100).astype(np.float32)
y_data = x_data * 0.1 + 0.3

# calculate y_data = b + W * x_data, by finding the values for b and W
# Knowing that b should be 0.3 and W 0.1, but it will be figured out by  the tensorflow
W = tf.Variable(tf.random_uniform([1], -1.0, 1.0))
b = tf.Variable(tf.zeros([1]))
y = W * x_data + b

# Minimize the mean squared errors.
loss = tf.reduce_mean(tf.square(y - y_data))
optimizer = tf.train.GradientDescentOptimizer(0.5)
train = optimizer.minimize(loss)

# Before starting, initialize the variables.  We will 'run' this first.
init = tf.initialize_all_variables()

# Launch the graph.
sess = tf.Session()
sess.run(init)

# Fit the line.
for step in xrange(201):
    sess.run(train)
    if step % 20 == 0:
        print(step, sess.run(W), sess.run(b))

# Learns best fit is W: [0.1], b: [0.


from sklearn.metrics import pairwise_distances
import numpy as np
from scipy.linalg import eigh
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import warnings
import networkx as nx

class LE:

    def __init__(self, X:np.ndarray, dim:int, k:int = 2, eps = None, graph:str = 'k-nearest', weights:str = 'heat kernel',
                 sigma:float = 0.1, laplacian:str = 'unnormalized', opt_eps_jumps:float = 1.5):
        """
        LE object
        Parameters
        ----------

        X: nxd matrix

        dim: number of coordinates

        k: number of neighbours. Only used if graph = 'k-nearest'

        eps: epsilon hyperparameter. Only used if graph = 'eps'.
        If is set to None, then epsilon is computed to be the
        minimum one which guarantees G to be connected

        graph: if set to 'k-nearest', two points are neighbours
        if one is the k nearest point of the other.
        If set to 'eps', two points are neighbours if their
        distance is less than epsilon

        weights: if set to 'heat kernel', the similarity between
        two points is computed using the heat kernel approach.
        If set to 'simple', the weight between two points is 1
        if they are connected and 0 otherwise. If set to 'rbf'
        the similarity between two points is computed using the
        gaussian kernel approach.

        sigma: coefficient for gaussian kernel or heat kernel

        laplacian: if set to 'unnormalized', eigenvectors are
        obtained by solving the generalized eigenvalue problem
        Ly = λDy where L is the unnormalized laplacian matrix.
        If set to 'random', eigenvectors are obtained by decomposing
        the Random Walk Normalized Laplacian matrix. If set to
        'symmetrized', eigenvectors are obtained by decomposing
        the Symmetrized Normalized Laplacian

        opt_eps_jumps: increasing factor for epsilon
        """

        self.X = X
        self.dim = dim
        self.k = k
        self.eps = eps
        if graph not in ['k-nearest', 'eps']:
            raise ValueError("graph is expected to be a graph name; 'eps' or 'k-nearest', got {} instead".format(graph))
        self.graph = graph
        if weights not in ['simple', 'heat kernel', 'rbf']:
            raise ValueError("weights is expected to be a weight name; 'simple' or 'heat kernel', got {} instead".format(weights))
        self.weights = weights
        self.sigma = sigma
        self.n = self.X.shape[0]
        if laplacian not in ['unnormalized', 'random', 'symmetrized']:
            raise ValueError("laplacian is expected to be a laplacian name; 'unnormalized', 'random' or 'symmetrized', got {} instead".format(laplacian))
        self.laplacian = laplacian
        self.opt_eps_jumps = opt_eps_jumps
        if self.eps is None and self.graph == 'eps':
            self.__optimum_epsilon()

    def __optimum_epsilon(self):
        """
        Compute epsilon

        To chose the minimum epsilon which guarantees G to be
        connected, first, epsilon is set to be equal to the distance
        from observation i = 0 to its nearest neighbour. Then
        we check if the Graph is connected, if it's not, epsilon
        is increased and the process is repeated until the Graph
        is connected
        """
        dist_matrix = pairwise_distances(self.X)
        self.eps = min(dist_matrix[0,1:])
        con = False
        while not con:
            self.eps = self.opt_eps_jumps * self.eps
            self.__construct_nearest_graph()
            con = self.cc == 1
            print('[INFO] Epsilon: {}'.format(self.eps))
        self.eps = np.round(self.eps, 3)

    def __heat_kernel(self, dist):
        """
        k(x, y) = exp(- ||x-y|| / sigma )
        """
        return np.exp(- (dist*dist)/self.sigma)

    def __rbf(self, dist):
        """
        k(x, y) = exp(- (1/2*sigma^2) * ||x-y||^2)
        """
        return np.exp(- dist**2/ (2* (self.sigma**2) ) )

    def __simple(self, *args):
        return 1

    def __construct_nearest_graph(self):
        """
        Compute weighted graph G
        """
        similarities_dic = {'heat kernel': self.__heat_kernel,
                            'simple':self.__simple,
                            'rbf':self.__rbf}

        dist_matrix = pairwise_distances(self.X)
        if self.graph == 'k-nearest':
            nn_matrix = np.argsort(dist_matrix, axis = 1)[:, 1 : self.k + 1]
        elif self.graph == 'eps':
            nn_matrix = np.array([ [index for index, d in enumerate(dist_matrix[i,:]) if d < self.eps and index != i] for i in range(self.n) ])
        # Weight matrix
        self._W = []
        for i in range(self.n):
            w_aux = np.zeros((1, self.n))
            similarities = np.array([ similarities_dic[self.weights](dist_matrix[i,v]) for v in nn_matrix[i]] )
            np.put(w_aux, nn_matrix[i], similarities)
            self._W.append(w_aux[0])
        self._W = np.array(self._W)
        # D matrix
        self._D = np.diag(self._W.sum(axis=1))
        # Check for connectivity
        self._G = self._W.copy() # Adjacency matrix
        self._G[self._G > 0] = 1
        G = nx.from_numpy_matrix(self._G)
        self.cc = nx.number_connected_components(G) # Multiplicity of lambda = 0
        if self.cc != 1:
            warnings.warn("Graph is not fully connected, Laplacian Eigenmaps may not work as expected")

    def __compute_unnormalized_laplacian(self):
        self.__construct_nearest_graph()
        self._L = self._D - self._W
        return self._L

    def __compute_normalized_random_laplacian(self):
        self.__construct_nearest_graph()
        self._Lr = np.eye(*self._W.shape) - (np.diag(1/self._D.diagonal())@self._W)
        return self._Lr

    def __compute_normalized_symmetrized_laplacian(self):
        self.__construct_nearest_graph()
        self.__compute_unnormalized_laplacian()
        d_tilde = np.diag(1/np.sqrt(self._D.diagonal()))
        self._Ls = d_tilde @ ( self._L @ d_tilde )
        return self._Ls

    def transform(self):
        """
        Compute embedding
        """

        m_options = {
            'unnormalized':self.__compute_unnormalized_laplacian,
            'random':self.__compute_normalized_random_laplacian,
            'symmetrized':self.__compute_normalized_symmetrized_laplacian
        }

        L = m_options[self.laplacian]()

        if self.laplacian == 'unnormalized':
            eigval, eigvec = eigh(L, self._D) # Generalized eigenvalue problem
        else:
            eigval, eigvec = np.linalg.eig(L)

        order = np.argsort(eigval)
        self.Y = eigvec[:, order[self.cc:self.cc+self.dim + 1]]

        return self.Y

    def plot_embedding_2d(self, colors, grid = True, dim_1 = 1, dim_2 = 2, cmap = None, size = (15, 10)):
        if self.dim < 2 and dim_2 <= self.dim and dim_1 <= self.dim:
            raise ValueError("There's not enough coordinates")

        plt.style.use('seaborn-whitegrid')
        fig = plt.figure(figsize=size)
        plt.axhline(c = 'black', alpha = 0.2)
        plt.axvline(c = 'black', alpha = 0.2)
        if cmap is None:
            plt.scatter(self.Y[:, dim_1 - 1], self.Y[:, dim_2 - 1], c = colors)

        plt.scatter(self.Y[:, dim_1 - 1], self.Y[:, dim_2 - 1], c = colors, cmap=cmap)
        plt.grid(grid)
        if self.graph == 'k-nearest':
            title = 'LE with k = {} and weights = {}'.format(self.k, self.weights)
        else:
            title = 'LE with $\epsilon$ = {} and weights = {}'.format(self.eps, self.weights)
        plt.title(title)
        plt.xlabel('Coordinate {}'.format(dim_1))
        plt.ylabel('Coordinate {}'.format(dim_2))
        plt.show()

    def plot_embedding_3d(self, colors, grid = True, dim_1 = 1, dim_2 = 2, dim_3 = 3, cmap = None, size = (15, 10)):
        if self.dim < 3 and dim_2 <= self.dim and dim_1 <= self.dim and dim_3 <= self.dim:
            raise ValueError("There's not enough coordinates")

        plt.style.use('seaborn-whitegrid')
        fig = plt.figure(figsize=size)
        ax = fig.add_subplot(111, projection="3d")
        if cmap is None:
            ax.scatter(self.Y[:, dim_1 - 1], self.Y[:, dim_2 - 1], self.Y[:, dim_3 - 1], c = colors)
        ax.scatter(self.Y[:, dim_1 - 1], self.Y[:, dim_2 - 1], self.Y[:, dim_3 - 1], c = colors, cmap = cmap)
        plt.grid(grid)
        ax.axis('on')
        if self.graph == 'k-nearest':
            title = 'LE with k = {} and weights = {}'.format(self.k, self.weights)
        else:
            title = 'LE with $\epsilon$ = {} and weights = {}'.format(self.eps, self.weights)
        plt.title(title)
        ax.set_xlabel('Coordinate {}'.format(dim_1))
        ax.set_ylabel('Coordinate {}'.format(dim_2))
        ax.set_zlabel('Coordinate {}'.format(dim_3))
        plt.show()


# -*- coding: utf-8 -*-

import scipy as sp
import numpy as np
from numpy.polynomial import polynomial as P
import math
import scipy.integrate as integrate
import scipy.special as special

def rk0( f, a, b, n ):
     if(n==1):
          resultado = (f(b) + f(a))*(b-a)/2
     else:
     	  # Calculamos la separación entre nodos
          h = (b-a)/n
          # Calculamos los nodos para interpolar
          x=[ a+h*i for i in range(n+1)]
          fx = 0
          for i in range(n-1):
          	fx = fx + f(x[i+1])
          resultado = ( h *( ( f(x[0]) + f(x[n]) )/2 + fx ) )
     return resultado

def intPuntoMedio(f,a,b):
    return f((a+b)/2)*(b-a)

def rk( Rk, Rk1, j ):
     return Rk + (1/(4**j-1))*(Rk - Rk1)

#Rk = R_{k,j-1} RK1 = R_{k-1,j-1}
def metodoRomberg(f, a, b, k):
		resultados=[]
		resultados.append( rk0( f, a, b, 1 ) )
		for i in range(k):
			resultados.append( rk0( f, a, b, 2**(i+1) ) )
		for i in range(k):
			for j in range(k-i):
		    		resultados[j]=rk( resultados[j+1], resultados[j], i+1 )
		return resultados[0]

#Apartado c)
f = lambda x: 3*x/(x**2 - 4)
a=1
b=1.8
n = 0
diferencia = 1
integral = integrate.quad(f,a,b)
trapecio = rk0(f,a,b,1)
simpson = rk0(f,a,b,2)
medio = intPuntoMedio(f,a,b)

print("Función 3x/(x^2-4)\n")
print ("Iteracion\t |\t R{n-1},{n-1}\t|\t R{n},{n} \t| \t Diferencia")
while diferencia >= 10**(-6):
    Rk = metodoRomberg(f, a, b, n)
    Rk1 = metodoRomberg(f, a, b, n+1)
    diferencia = abs(Rk-Rk1)
    print( format(n) + "\t\t | \t"+ str( Rk)+ "\t|\t"+ str(Rk1)+ "\t|\t" + str(diferencia))
    n = n+1
print("Su integral real es:",integral)
print("Su valor por el trapecio es:", trapecio)
print("Su valor por simpson es:", simpson)
print("Su valor por el punto medio es:", medio)

# Apartado d)
f = lambda x: math.cos(x)*math.exp(3*x)
a=0
b=math.pi/4
n = 0
diferencia = 1
integral = integrate.quad(f,a,b)
trapecio = rk0(f,a,b,1)
simpson = rk0(f,a,b,2)
medio = intPuntoMedio(f,a,b)

print("Función cos(x)*e^(3x)\n")
print ("Iteracion\t |\t R{n-1},{n-1}\t|\t R{n},{n} \t| \t Diferencia")
while diferencia >= 10**(-6):
    Rk = metodoRomberg(f, a, b, n)
    Rk1 = metodoRomberg(f, a, b, n+1)
    diferencia = abs(Rk-Rk1)
    print( format(n) + "\t\t | \t"+ str( Rk)+ "\t|\t"+ str(Rk1)+ "\t|\t" + str(diferencia))
    n = n+1
print("Su integral real es:",integral)
print("Su valor por el trapecio es:", trapecio)
print("Su valor por simpson es:", simpson)
print("Su valor por el punto medio es:",medio)


"""
RSI CALCULATION

The very first calculations for average gain and average loss are simple n-period averages:

    First Average Gain = Sum of Gains over the past n periods / n.
    First Average Loss = Sum of Losses over the past n periods / n

The second, and subsequent, calculations are based on the prior averages and the current gain loss:

    Average Gain = [(previous Average Gain) x (n-1) + current Gain] / n.
    Average Loss = [(previous Average Loss) x (n-1) + current Loss] / n.

RS = Average Gain / Average Loss
RSI = 100 - (100 / (1 + RS))

"""

import numpy as np

def rsi(ohlcv, period=14, ohlcv_series="close"):
    _ohlcv = ohlcv[[ohlcv_series]].copy(deep=True)
    _ohlcv["diff"] = _ohlcv[ohlcv_series].diff(periods=1)
    _ohlcv["diff_up"] = np.where(_ohlcv["diff"] >= 0, _ohlcv["diff"], 0)
    _ohlcv["diff_down"] = np.where(_ohlcv["diff"] < 0, _ohlcv["diff"], 0)
    # Calculate Average Gain and Average Loss
    _ohlcv[["rsi_u", "rsi_d"]] = _ohlcv[["diff_up", "diff_down"]].ewm(alpha=1 / period, min_periods=period).mean()
    _ohlcv["rs"] = abs(_ohlcv["rsi_u"]) / abs(_ohlcv["rsi_d"])
    indicator_values = 100 - (100 / (1 + _ohlcv["rs"]))

    return indicator_values

def ADD_RSIs(ohlcv, periods=[10,20,30], ohlcv_series="close"):
  for p in periods:
    indicator_name = f'rsi_{p}_{ohlcv_series[0]}'
    ohlcv[indicator_name] = rsi(ohlcv, p, ohlcv_series)
  return ohlcv


from sklearn.linear_model import Ridge
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import make_pipeline
import numpy as np
import seaborn.apionly as sns
import matplotlib.pyplot as plt

iris2 = sns.load_dataset('iris')

ix = iris2['petal_width']
iy = iris2['petal_length']

# Generate point used to represent the fitted function
x_plot = np.linspace(0, 2.6, 100)

# Create matrix versions of these arrays
X = ix[:, np.newaxis]
X_plot = x_plot[:, np.newaxis]

plt.scatter(ix, iy, s=30, marker='o', label='training points')

for count, degree, in enumerate([3, 6, 20]):
    model = make_pipeline(PolynomialFeatures(degree), Ridge())
    model.fit(X, iy)
    y_plot = model.predict(X_plot)
    plt.plot(x_plot, y_plot, label='degree %d' % degree)

plt.legend(loc='upper left')
plt.show()
# The coefficient = 20 adjust to the example data, but after that,the curve diverge, this is against the goal to be
# used with further data.


import random
import matplotlib.pyplot as plt
from scipy.stats import norm

n = 100
rho = .8
num_sims = 10000
beta = rho**0.5
alpha = (1 -rho)**0.5
prob_default = 0.1
tranche_cutoffs = [0, 0.2, 1]
# tranche_cutoffs = [0, 0.03, 0.07, 0.1, 0.15, 0.30, 1 ]
tranche_to_watch = 2 #(1 to num tranches)

# run simulation
z_default = norm.ppf(prob_default) # quantile function
max_defaults_protection = n * tranche_cutoffs[tranche_to_watch - 1]
wiped_out = n * tranche_cutoffs[tranche_to_watch ]
print (max_defaults_protection, wiped_out)

# histogram = [0] * (n+1)
trial_results = []
tranche_values = []
for _ in range(num_sims):
    M = random.gauss(0, 1)
    K = 0 # number of variables that are > 0
    for _ in range(n):
        R_i = beta * M + alpha * random.gauss(0, 1)
        if R_i < z_default:
            K += 1
    # histogram[K] += 1
    trial_results.append(K)
    tranch_value = wiped_out - min(wiped_out, max(0, K - max_defaults_protection))
    tranche_values.append(tranch_value)

# print(histogram)
print('Mean tranche value = ' + str(sum(tranche_values)/ num_sims))
plt.hist(tranche_values, bins=n+1, normed=0, align='mid')
plt.title('Distribution of Tranche Value in ' + str(num_sims) + ' trials')
plt.xlabel('Value')
plt.ylabel('Frequency')
plt.show()


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))