#!/usr/bin/python
"""Module for Cholesky Methods"""
import numpy as np
import scipy.sparse as sp

def CholeskyFactorization(A):
    """
    Runs a Cholesky Factorization on matrix A.

    The matrix A is expected to be:
    - Square
    - Tridiagonal
    - Symmetric
    - Positive Definite
    In the event the matrix fails one of these condition, a RuntimeError is raised.
    """

    # Check if the matrix is in dia_matrix format, and convert otherwise
    if sp.isspmatrix_dia(A):
        T = A
    else:
        T = sp.dia_matrix(A)

    # Check the dimensions
    (m, n) = T.shape
    if m != n:
        raise RuntimeError("Matrix must be square")

    # Check if the matrix is tridiagonal
    if not _isTridiagonal(T):
        raise RuntimeError("Matrix must be tridiagonal")

    # Check for symmetry - we already know it's tridiagonal, only 1 diag above and below to check
    if not all(T.diagonal(-1) == T.diagonal(1)):
        raise RuntimeError("Matrix must be symmetric")

    # Make copies of the necessary diagonals to prevent changing the original matrix
    L_main_diag = T.diagonal(0).copy()
    L_sub_diag = T.diagonal(-1).copy()

    # Build L column by column
    for i in range(n):
        # The first column doesn't need anything subtracted
        if i > 0:
            L_main_diag[i] -= L_sub_diag[i-1]*L_sub_diag[i-1]

        # Check if matrix is indefinite here to prevent complex arithmetic cost
        if L_main_diag[i] < 0:
            raise RuntimeError("Matrix must be positive definite")

        # Now that we know it's positive, can take the square root
        L_main_diag[i] = np.sqrt(L_main_diag[i])

        # Subdiagonal is one element shorter than main diagonal so can't assign at i == n-1
        if i < n-1:
            L_sub_diag[i] /= L_main_diag[i]

    L = sp.diags([L_main_diag, L_sub_diag], [0, -1], dtype=np.double, format="dia")
    return L

def _isTridiagonal(T):
    """
    Checks to see whether L is tridiagonal.
    L must be in scipy.sparse.dia_matrix form
    """
    # Make sure the matrix is in dia_matrix format
    if not sp.isspmatrix_dia(T):
        raise RuntimeError("Input to _isTridiagonal must be dia_matrix")

    # First check if there's 3 diagonals
    if len(T.offsets) != 3:
        return False

    # Check for the main diagonal
    if 0 not in T.offsets:
        return False

    # Check for the super diagonal
    if 1 not in T.offsets:
        return False

    # Check for the sub diagonal
    if -1 not in T.offsets:
        return False

    return True


import numpy as np

def Ent_Samp20200723(data, m, r):
  """
  function SE = Ent_Samp20200723(data,m,r)
  SE = Ent_Samp20200723(data,m,R) Returns the sample entropy value.
  inputs - data, single column time seres
          - m, length of vectors to be compared
          - r, radius for accepting matches (as a proportion of the
            standard deviation)

  output - SE, sample entropy
  Remarks
  - This code finds the sample entropy of a data series using the method
    described by - Richman, J.S., Moorman, J.R., 2000. "Physiological
    time-series analysis using approximate entropy and sample entropy."
    Am. J. Physiol. Heart Circ. Physiol. 278, H2039–H2049.
  - m is generally recommendation as 2
  - R is generally recommendation as 0.2
  May 2016 - Modified by John McCamley, unonbcf@unomaha.edu
           - This is a faster version of the previous code.
  May 2019 - Modified by Will Denton
           - Added code to check version number in relation to a server
             and to automatically update the code.
  Jul 2020 - Modified by Ben Senderling, bmchnonan@unomaha.edu
           - Removed the code that automatically checks for updates and
             keeps a version history.
  Define r as R times the standard deviation
  Copyright 2020 Nonlinear Analysis Core, Center for Human Movement
  Variability, University of Nebraska at Omaha

  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions are
  met:

  1. Redistributions of source code must retain the above copyright notice,
      this list of conditions and the following disclaimer.

  2. Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.

  3. Neither the name of the copyright holder nor the names of its
      contributors may be used to endorse or promote products derived from
      this software without specific prior written permission.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
  IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  """
  R = r * np.std(data)
  N = len(data)

  data = np.array(data)

  dij = np.zeros((N-m,m+1))
  dj =  np.zeros((N-m,1))
  dj1 = np.zeros((N-m,1))
  Bm = np.zeros((N-m,1))
  Am = np.zeros((N-m,1))

  for i in range(N-m):
    for k in range(m+1):
      dij[:,k] = np.abs(data[k:N-m+k]-data[i+k])
    dj = np.max(dij[:,0:m],axis=1)
    dj1 = np.max(dij,axis=1)
    d = np.where(dj <= R)
    d1 = np.where(dj1 <= R)
    nm = d[0].shape[0]-1 # subtract the self match
    Bm[i] = nm/(N-m)
    nm1 = d1[0].shape[0]-1 # subtract the self match
    Am[i] = nm1/(N-m)

  Bmr = np.sum(Bm)/(N-m)
  Amr = np.sum(Am)/(N-m)

  return -np.log(Amr/Bmr)


import numpy as np
from itertools import product

def count_matching_determinant_filled(x, y, d):
    """xとyのうち重複を許して埋めた3×3正方行列の行列式と与えられた行列式とが一致する総数を返す
    x : 任意の値
    y : 任意の値
    d : 行列式
    """
    repeated_permutation_square = fill_squares([x, y], 3)
    return count_matching_determinant(repeated_permutation_square, d)

def fill_squares(data, n):
    """"与えられたリストの数値から重複を許して埋めた正方行列を返す
    data : 任意を値を持つリスト
    n : 正方行列の行数(列数)
    """
    repeated_permutation = np.array(list(product(data, repeat=n*n)))
    return repeated_permutation.reshape(-1, n, n)

def count_matching_determinant(array, d):
    """配列の行列式と一致する数を返す
    array : 正方行列からなる2次元以上の配列
    d : 配列の行列式との一致を確認したい値
    """
    determinant = np.round(np.linalg.det(array))  # 微小な誤差が発生するため丸めた
    return np.sum(determinant == d)

if __name__ == "__main__":
    import os
    abs_path = os.path.dirname(os.path.abspath(__file__))

    with open(abs_path + "/q1_in.txt") as f:
        rows = f.readlines()

    outputs = [None] * len(rows)

    for i, row in enumerate(rows):
        x, y, d = map(int, row.split(" "))
        outputs[i] = count_matching_determinant_filled(x, y, d)

    with open(abs_path + "/q1_out.txt", "w") as f:
        f.write("\n".join(map(str, outputs)))


######
###BACKGROUND
#Below Section: Imports necessary functions
import numpy as np
import matplotlib.pyplot as graph
import random as rand
import time as watch
pi = np.pi

#FUNCTION: ishit
#PURPOSE: This function is meant to test whether or not a given 2d point is within the unit circle.
#INPUTS: x = x-axis location, y = y-axis location
#OUTPUT: Boolean
def ishit(x, y):
	#Below Section: Calculates (x, y) vector distance from origin using Pythagorean theorem
	hyplength = np.sqrt(x**2 + y**2)

	#Below Section: Returns whether or not length within unit circle or not
	if hyplength >= 1:
		return True
	else:
		return False

#FUNCTION: simdarts
#PURPOSE: This function is meant to simulate a dart game, with the goal of hitting the inner circle.
#INPUTS: Number of times to 'throw' a dart
#OUTPUTS: Arrays of x and y locations where the dart falls each time; estimated pi value
def simdarts(num):
	#Below Section: Simulates given number of dart throws
	xlocs = np.zeros(num) #X-axis locations
	ylocs = np.zeros(num) #Y-axis locations

	#Below generates the random numbers in [0, 1)
	for a in range(0, num):
		xlocs[a] = rand.random()
		ylocs[a] = rand.random()

	#Below Section: Estimates the value of pi using the results
	count = 0
	for b in range(0, num):
		if ishit(x=xlocs[b], y=ylocs[b]):
			count = count + 1

	#Below Section: Returns the calculated values
	return {'xs':xlocs, 'ys':ylocs, 'estpi':(count/1.0/num)}

#FUNCTION: histdarts
#PURPOSE: This function is meant to plot a histogram of the estimated pi value of several dart simulations.
#INPUTS: Number of histogram points; number of darts per simulation
def histdarts(numdarts, numsims):
	#Below Section: Generates several simulations of given number of darts each
	estpis = np.zeros(numsims)
	for c in range(0, numsims):
		estpis[c] = simdarts(num=numdarts)['estpi']

	#Below Section: Graphs results in histogram
	meanstring = 'Mean: {:.4f}, '.format(np.mean(estpis))
	stdstring = 'St. Dev: {.4f}'.format(np.std(estpis))
	graph.hist(estpis, histtype='step', alpha=.6)
	graph.title('Histogram of Pi Estimations')
	graph.suptitle(meanstring+stdstring)
	graph.xlabel('Estimated Pi Values')
	graph.ylabel('Frequencies')
	#graph.savefig('EstPiHist-'+numdarts'Darts-'+numsims+'Sims')
	graph.show()
	graph.close()

#FUNCTION: drawdarts
#PURPOSE: This function is meant to draw the darts for a given simulation within the unit square.
#INPUTS: X- and Y- locations of the simulated darts
def drawdarts(x, y):
	#Below Section: Scatters the simulated darts
	graph.scatter(x, y, color='blue', alpha=.4)

	#Below Section: Plots a circle as a guideline to show boundaries of unit circle
	xcircle = np.linspace(0, 1, 1000)
	ycircle = np.sqrt(1 - xcircle**2)
	graph.plot(xcircle, ycircle, color='cyan', alpha=.2, linestyle='--')
	#graph.savefig('EstPiDraw-'+str(len(x))+'Darts')
	graph.show()
	graph.close()

#FUNCTION: plottime
#PURPOSE: This function is meant to plot the dependence of time upon the number of darts.
#INPUTS: An array containing the number of darts for each simulation
def plottime(numdartsarray):
	#Below Section: Times the dart simulation for each number of darts given
	simtimes = np.zeros(len(numdartsarray))
	for d in range(0, len(numdartsarray)):
		starthere = watch.time() #Start time
		simhere = simdarts(num=numdartsarray[d]) #Simulation
		endhere = watch.time() #End time

		#Below records time taken for current simulation
		simtimes[d] = endhere - starthere

	#Below Section: Plots the time taken
	graph.plot(numdartsarray, simtimes, alpha=.4, color='purple')
	graph.title('Time Dependence of the Number of Darts')
	graph.xlabel('Number of Darts')
	graph.ylabel('Time Dependence')
	graph.show()
	graph.close()

#FUNCTION: plotacc
#PURPOSE: This function is meant to plot the dependence of accuracy upon the number of darts.
#INPUT: An array containing number of darts for each simulation
def plotacc(numdartsarray):
	#Below Section: Determines accuracy for each number of darts given
	simacc = np.zeros(len(numdartsarray))
	for e in range(0, len(numdartsarray)):
		esthere = simdarts(num=numdartsarray[e])['estpi']

		#Below calculates and records current accuracy
		simacc[e] = abs(esthere - pi)

	#Below Section: Graphs the accuracy of pi estimations
	graph.plot(numdartsarray, simacc, alpha=.4, color='orange')
	graph.title('Estimation Accuracy of the Number of Darts')
	graph.xlabel('Number of Darts')
	graph.ylabel('Accuracy of Pi Estimation')
	graph.show()
	graph.close()


import numpy as np
import pandas as pd
import warnings
import scipy
import iqplot
import scipy.stats as st

def log_like_iid_gamma_log_params(params, t):
    """Log likelihood for i.i.d. Gamma measurements with
    input being parameters and data

    Parameters
    ----------
    params : array
        parameters alpha and b.
    t : array
        Array of input data (times)

    Returns
    -------
    output : float
        Log-likelihood.
    """
    alpha, b = params
    if(alpha <= 0 or b <= 0):
        return -np.inf
    return np.sum(st.gamma.logpdf(t, alpha, scale = 1/b))

rg = np.random.default_rng(3252)
def mle_iid_gamma(t):
    """Perform maximum likelihood estimates for parameters for i.i.d.
    gamma measurements, parametrized by alpha, b=1/beta

    Parameters
    __________
    t : array
        data array

    Returns
    __________
    output : tuple
        Parameter estimates for alpha and beta

    """
    with warnings.catch_warnings():
        warnings.simplefilter("ignore")

        res = scipy.optimize.minimize(
            fun=lambda params, t: -log_like_iid_gamma_log_params(params, t),
            x0=np.array([3, 3]),
            args=(t,),
            method='Powell'
        )
    if res.success:
        return res.x
    else:
        raise RuntimeError('Convergence failed with message', res.message)

def gen_gamma(alpha, b, size):
    """
    Parameters
    __________
    alpha : float
        parameter value for alpha

    b : float
        parameter value for b = 1/beta

    size : int
        number of points to sample from the distribution

    Returns
    __________
    output : array
        distribution based on input parameters"""
    return rg.gamma(alpha, 1 / b, size=size)

def log_like(t, b_1, delta_b):
    """Log likelihood for exponential distribution

    Parameters
    __________
    t : array
        data array
    b_1 : parameter for first arrival time
        delta_b : parameter for difference between two arrival times

    Returns
    __________
    output : float
        Log-likelihood.
    """
    res = -b_1 * t + np.log(1 - np.exp(-delta_b*t)) + np.log(b_1 + delta_b) + np.log(b_1) - np.log(delta_b)
    return res
vec_log_like = np.vectorize(log_like)

def log_like_iid_exp_log_params(params, t):
    """Log likelihood for i.i.d. exponential measurements with
    input being parameters.

    Parameters
    ----------
    params : array
        Parameters beta and delta_beta
    t : array
        Array of times.

    Returns
    -------
    output : float
        Log-likelihood.
    """
    b_1, delta_b = params
    # For the calculation, we need to take the values of b and delta_b and sum
    # the log PDF for each value in the data set
    result = 0
    if(b_1 <= 0 or delta_b <= 0):
        return -np.inf
    result = vec_log_like(t, b_1, delta_b)
    return np.sum(result)

def gen_exponential(b, delta_b, size):
    '''Generates exponential values given b and delta_b
    Parameters
    __________
    b : float
        parameter for first arrival time
    delta_b : float
        parameter for difference between two arrival times

    Returns
    __________
    output : float
        Log-likelihood.
    '''
    return (rg.exponential(1/b, size=size) + rg.exponential(1/(b + delta_b), size = size))

def mle_iid_exp(t):
    """Perform maximum likelihood estimates for parameters for i.i.d.
   exponentially distributed measurements, parametrized by beta, delta_beta

   Parameters
   ___________
   t : array
       input array of times

   Returns
   ___________
   output : tuple
       parameters
   """

    with warnings.catch_warnings():
        warnings.simplefilter("ignore")

        res = scipy.optimize.minimize(
            fun=lambda params, t: -log_like_iid_exp_log_params(params, t),
            x0=np.array([1, 1]),
            args=(t,),
            method='Powell'
        )
    if res.success:
        return res.x
    else:
        raise RuntimeError('Convergence failed with message', res.message)

def gamma_cdf(t, alpha, beta, loc = 0):
    """Calculate the cdf for a gamma distribution
    Parameters
    _________
    t : array
        input array of points to calculate cdf for
    alpha : float
        alpha parameter
    beta : float
        1/b for gamma distribution
    loc : float (optional), default = 0
        center of distribution

    Returns
    _________
    output : array
        cdf for each point in t
    """

    cdf = st.gamma.cdf(t, alpha, loc=0, scale=1/beta)
    return cdf

def model_cdf(t, beta_1, delta_beta):
    """calculates cdf for our custom exponential distribution
    Parameters
    _________
    t : array
        input array of points to calculate cdf for
    beta_1 : float
        parameter for first arrival time
    beta_2 : float
        parameter for second arrival time

    Returns
    _________
    output : array
        cdf for each point in t
    """
    beta_2 = beta_1 + delta_beta
    if np.isclose(beta_1, beta_2):
        return st.gamma.cdf(2, loc=0, scale=1 / beta_1)

    cdf = (1 - np.exp(-beta_1 * t)) / beta_1 - (1 - np.exp(-beta_2 * t)) / beta_2

    return beta_1 * beta_2 * cdf / (beta_2 - beta_1)

def overlay_models(data, q, mle_params, cdf_fun = gamma_cdf, exp_color = 'green', theor_color = 'gray'):
    """plots a comparison between experimental ECDF and theoretical
    Parameters
    _________
    data : array
        input data array
    q : string
        quantiative axis label for plot
    mle_params : tuple
        parameter estimates to be used for given model
    cdf_fun : function (optional), default = gamma_cdf
        function to use to calculate CDFs
    exp_color : string (optional), default = 'green'
        color to use for experimental ECDF
    theor_color : string (optional), default = 'gray'
        color to use for theoretical CDF using our model

    Returns
    _________
    output : bokeh figure
        figure containing the ECDFs overlaid
    """

    p = iqplot.ecdf(data, q=q, conf_int=True, palette = [exp_color])

    t_theor = np.linspace(0, 2000, 200)
    cdf = cdf_fun(t_theor, *mle_params)
    p.line(t_theor, cdf, line_width=1, color= theor_color)
    return p


import pandas as pd
import numpy as np
import tensorflow as tf
import random as rd
import matplotlib.pyplot as plt

def generate_fake_data():
	df = pd.DataFrame(columns=[0,1,2])
	for x in range(100):
		x1 = float(rd.randint(1000,5*10**5))
		x2 = float(rd.randint(1,5))
		df2 = pd.DataFrame([[x1,x2,(0.5*x1)+(2*x2)+1]])
		df = df.append(df2, ignore_index=True)
	return df

def read_csv_data(filename):
	df = pd.read_csv(filename, header=None, delimiter=",",dtype=np.float64)
	return df

def plot_data(df, col):
	y = df.columns[-1]
	plt.plot(df[col],df[y],'ro')
	plt.show()

def normalize(df):
	res = df.copy()
	for c in df.columns:
		mean = df[c].mean()
		std = df[c].std()
		res[c] = (res[c]-mean)/std
	return res

def linear_regression(df):
	print(df)
	print(np.array(df[0].values))
	tf.logging.set_verbosity(tf.logging.INFO)
	# Declare list of features. We only have one real-valued feature. There are many
	# other types of columns that are more complicated and useful.
	features = []
	input_dict = {}
	col_mean_std = {}
	for c in df.columns[:-1]:
		mean = df[c].mean()
		std = df[c].std()
		col_mean_std[c] = (mean,std)
		features.append(tf.contrib.layers.real_valued_column("x"+str(c), dimension=1,
							normalizer=lambda x:(x-mean)/std))
		input_dict["x"+str(c)]=np.array(df[c].values)

	# An estimator is the front end to invoke training (fitting) and evaluation
	# (inference). There are many predefined types like linear regression,
	# logistic regression, linear classification, logistic classification, and
	# many neural network classifiers and regressors. The following code
	# provides an estimator that does linear regression.
	estimator = tf.contrib.learn.LinearRegressor(feature_columns=features)

	# TensorFlow provides many helper methods to read and set up data sets.
	# Here we use `numpy_input_fn`. We have to tell the function how many batches
	# of data (num_epochs) we want and how big each batch should be.
#	x1 = np.array(df[0].values)
#	x2 = np.array(df[1].values)
	mean = df[len(df.columns)-1].mean()
	std = df[len(df.columns)-1].std()
	df[len(df.columns)-1] = (df[len(df.columns)-1]-mean)/std
	y = np.array(df[len(df.columns)-1].values)
	col_mean_std[len(df.columns)-1]=(mean,std)
	input_fn = tf.contrib.learn.io.numpy_input_fn(input_dict, y, batch_size=4, num_epochs=1000)

	# We can invoke 1000 training steps by invoking the `fit` method and passing the
	# training data set.
	estimator.fit(input_fn=input_fn, steps=1000)

	# Here we evaluate how well our model did. In a real example, we would want
	# to use a separate validation and testing data set to avoid overfitting.
	print(estimator.evaluate(input_fn=input_fn))

	variables = estimator.get_variable_names()
	for v in variables:
		print(v,estimator.get_variable_value(v))
	return estimator,col_mean_std

if __name__ == "__main__":
	filename = "data/ex1data2"
	df = read_csv_data(filename)
	dfp = normalize(df)
	plot_data(dfp,0)
	plot_data(dfp,1)

#	df = generate_fake_data()
	estimator, col_mean_std = linear_regression(df)
	x1 = np.array([(1600-col_mean_std[0][0])/col_mean_std[0][1]])
	x2 = np.array([(3-col_mean_std[1][0])/col_mean_std[1][1]])
#	print("Evaluating --------------")
	for p in estimator.predict({"x0":x1,"x1":x2},as_iterable=True):
		print(x1,x2,p*col_mean_std[2][1]+col_mean_std[2][0])


import math
from sympy.core.symbol import symbols
from sympy.functions.elementary.exponential import exp
from sympy.codegen.rewriting import optimize
from sympy.codegen.approximations import SumApprox, SeriesApprox

def test_SumApprox_trivial():
    x = symbols('x')
    expr1 = 1 + x
    sum_approx = SumApprox(bounds={x: (-1e-20, 1e-20)}, reltol=1e-16)
    apx1 = optimize(expr1, [sum_approx])
    assert apx1 - 1 == 0

def test_SumApprox_monotone_terms():
    x, y, z = symbols('x y z')
    expr1 = exp(z)*(x**2 + y**2 + 1)
    bnds1 = {x: (0, 1e-3), y: (100, 1000)}
    sum_approx_m2 = SumApprox(bounds=bnds1, reltol=1e-2)
    sum_approx_m5 = SumApprox(bounds=bnds1, reltol=1e-5)
    sum_approx_m11 = SumApprox(bounds=bnds1, reltol=1e-11)
    assert (optimize(expr1, [sum_approx_m2])/exp(z) - (y**2)).simplify() == 0
    assert (optimize(expr1, [sum_approx_m5])/exp(z) - (y**2 + 1)).simplify() == 0
    assert (optimize(expr1, [sum_approx_m11])/exp(z) - (y**2 + 1 + x**2)).simplify() == 0

def test_SeriesApprox_trivial():
    x, z = symbols('x z')
    for factor in [1, exp(z)]:
        x = symbols('x')
        expr1 = exp(x)*factor
        bnds1 = {x: (-1, 1)}
        series_approx_50 = SeriesApprox(bounds=bnds1, reltol=0.50)
        series_approx_10 = SeriesApprox(bounds=bnds1, reltol=0.10)
        series_approx_05 = SeriesApprox(bounds=bnds1, reltol=0.05)
        c = (bnds1[x][1] + bnds1[x][0])/2  # 0.0
        f0 = math.exp(c)  # 1.0

        ref_50 = f0 + x + x**2/2
        ref_10 = f0 + x + x**2/2 + x**3/6
        ref_05 = f0 + x + x**2/2 + x**3/6 + x**4/24

        res_50 = optimize(expr1, [series_approx_50])
        res_10 = optimize(expr1, [series_approx_10])
        res_05 = optimize(expr1, [series_approx_05])

        assert (res_50/factor - ref_50).simplify() == 0
        assert (res_10/factor - ref_10).simplify() == 0
        assert (res_05/factor - ref_05).simplify() == 0

        max_ord3 = SeriesApprox(bounds=bnds1, reltol=0.05, max_order=3)
        assert optimize(expr1, [max_ord3]) == expr1


# -*- coding: utf-8 -*-
import numpy as np

def flip_signs(A, B):
    """
    utility function for resolving the sign ambiguity in SVD
    http://stats.stackexchange.com/q/34396/115202
    """
    signs = np.sign(A) * np.sign(B)
    return A, B * signs

class PCA(object):
    """
    Principal Component Analysis

    Parameters
    ----------
    n_components : int
        Number of components to keep.
        if n_components is not set all components are kept

    Attributes
    ----------
    explained_variance : array, [n_components]
        The amount of variance explained by each of the selected components.

    explained_variance_cumsum : array, [n_components]
        Cumulative sum of explained variance for every component.

    total_var : float
        Total variance explained. Equal to `sum(explained_variance)`

    mean_ : array, [n_features]
        Per-feature empirical mean, estimated from the training set.
        Equal to `X.mean(axis=0)`.

    n_components_ : int
        The estimated number of components.

    Example usage
    -------------
    X = np.random.rand(14,88)  # 2D ndarray
    model = PCA(2)
    eig_projection = model.eigen(X)
    svd_projection = model.svd(X)
    svd_projection2D = model(X)  # callable class
    eig_projection2D = model(X, method='eig')  # eigenvector method
    svd_projection2D[0]  # first component with max variance
    svd_projection2D[-1]  # last component with least variance
    """
    def __init__(self, n_components=None):
        self.n_components = n_components

    def __call__(self, X, method=None):
        if method is None:
            return self.svd(X)
        return self.eigen(X)

    @staticmethod
    def scale(X):
        """
        Standardize features by removing the mean and scaling to unit variance
        """
        X_std = (X - np.mean(X, axis=0)) / np.std(X, axis=0)
        return X_std

    def eigen(self, X):
        """
        Compute the eigenvalues and right eigenvectors of a square array.
        Project them to new feature space
        """
        assert X.ndim == 2
        n_samples, n_features = X.shape
        # Estimate if n_components not specified
        self.n_components_ = self.n_components or n_features
        self.mean_ = np.mean(X, axis=0)

        k = self.n_components

        # Standardizing the data
        X = self.scale(X)

        # Eigendecomposition - Computing Eigenvectors and Eigenvalues

        # Covariance matrix after standardizing the data
        # equals to correlation matrix of raw or std data
        cov_mat = np.corrcoef(X, rowvar=False)

        print('Covariance matrix: \n%s' % cov_mat)

        eig_vals, eig_vecs = np.linalg.eig(cov_mat)

        # Sorting eigenpairs wrt. eigenvalues
        idx = eig_vals.argsort()[::-1]
        eig_vals, eig_vecs = eig_vals[idx], eig_vecs[:, idx]

        # the eigenvalues in decreasing order
        print("Eigenvalues:\n", eig_vals[:k])
        # a matrix of eigenvectors (each column is an eigenvector)
        print("Eigenvectors:\n", eig_vecs[:k])

        # Explained Variance
        self.explained_variance = (eig_vals / np.sum(eig_vals))[:k]
        self.explained_variance_cumsum = np.cumsum(self.explained_variance)
        self.total_var = np.sum(self.explained_variance)

        print('Total variance explained:\n', self.total_var)
        print('Explained variance:\n', self.explained_variance)
        print('Explained variance cumsum:\n', self.explained_variance_cumsum)

        # Projection matrix and dimensionality reduction
        W = eig_vecs[:, 0:k]
        assert W.shape == (n_features, self.n_components_)

        # projections of X on the principal axes are called principal components
        PC_k = X.dot(W)
        assert PC_k.shape == (n_samples, self.n_components_)

        # expose variables as attributes for unit testing
        self.X = X
        self.cov_mat = cov_mat
        self.eig_vals = eig_vals
        self.eig_vecs = eig_vecs

        return PC_k

    def svd(self, X):
        """
        Singular Value Decomposition
        """
        assert X.ndim == 2
        n_samples, n_features = X.shape
        # Estimate if n_components not specified
        self.n_components_ = self.n_components or n_features
        self.mean_ = np.mean(X, axis=0)

        k = self.n_components

        # Standardizing the data
        X = self.scale(X)

        # we now perform singular value decomposition of X
        # "economy size" (or "thin") SVD
        U, s, Vt = np.linalg.svd(X, full_matrices=False)
        V = Vt.T
        S = np.diag(s)

        # 1) columns of V are principal directions/axes.
        # np.testing.assert_allclose(*flip_signs(V, principal_axes))

        # 2) columns of US are principal components
        # np.testing.assert_allclose(*flip_signs(U.dot(S), principal_components), rtol=1e-04)

        # 3) singular values are related to the eigenvalues of covariance matrix
        # np.testing.assert_allclose((s ** 2) / (n - 1), eig_vals)

        eig_vals = (s ** 2) / (n_features - 1)

        # Explained Variance
        self.explained_variance = (eig_vals / np.sum(eig_vals))[:k]
        self.explained_variance_cumsum = np.cumsum(self.explained_variance)
        self.total_var = np.sum(self.explained_variance)

        # dimensionality reduction
        US_k = U[:, 0:k].dot(S[0:k, 0:k])
        assert US_k.shape == (n_samples, self.n_components_)

        # expose variables as attributes for unit testing
        self.X = X
        self.U = U
        self.s = s
        self.Vt = Vt
        self.V = V
        self.S = S

        return US_k


import numpy as np

def denormalize(x, x_min, x_max):

    if x_max is None:
        _range = 1
    else:
        _range = (x_max - x_min)

    return x * _range + x_min

def normalize(x, x_min=None, x_max=None, return_bounds=False, estimate_bounds_if_none=True):

    # if the bounds should be estimated if none do it for both
    if estimate_bounds_if_none and x_min is None:
        x_min = np.min(x, axis=0)
    if estimate_bounds_if_none and x_max is None:
        x_max = np.max(x, axis=0)

    # if they are still none set them to default to avoid exception
    if x_min is None:
        x_min = np.zeros()
    if x_max is None:
        x_max = np.ones()

    # calculate the denominator
    denom = x_max - x_min

    # we can not divide by zero -> plus small epsilon
    denom += 1e-30

    # normalize the actual values
    N = (x - x_min) / denom

    # return with or without bounds
    if not return_bounds:
        return N
    else:
        return N, x_min, x_max

def standardize(x, return_bounds=False):
    mean = np.mean(x, axis=0)
    std = np.std(x, axis=0)

    # standardize
    val = (x - mean) / std

    if not return_bounds:
        return val
    else:
        return val, mean, std

def destandardize(x, mean, std):
    return (x * std) + mean


import numpy as np
from scipy.optimize import minimize
from collections import namedtuple

def coordinate_descent_minimize(f, x0, max_iters=10, max_tol=1e-9, **kw_minimize_args):
    """
    coordinate descent minimization, using scipy.minimize for each coordinate in
    consecutive order while fixing the rest.

    f,x0: just like scipy.minimize
    max_iter: maximum number of iterations. each iteration will loop over all coordinates
              calling scipy.minimize each time.
    max_tol:  maximum solution vector change under which iterations will continue until
              reaching max_iter. if the change of the solution vector in consecutive
              iterations is <max_tol optimization will finish.

    returns:  a dict object with optimization information. "x" will contain the final solution.

    """
    x = np.r_[x0].copy()
    x_hist = [x.copy()]
    message = "reached max_iters=%d"%max_iters
    for t in range(max_iters):
        last_x = x.copy()
        rset = []
        for i in range(len(x)):
            def fi(xi):
                x[i] = xi
                return f(x)
            r = minimize(fi, x[i], **kw_minimize_args)
            x[i] = r.x
            rset.append(r)
        x_hist.append(x.copy())
        print (np.linalg.norm(x-last_x) )
        if np.linalg.norm(x-last_x) < max_tol:
            message = "finished after %s iterations and convergence reached at %1.e tol"%(t+1,max_tol)
            break
    ret = {"coord_success": [r.success for r in rset],
           "coord_messages": [r.message for r in rset],
           "x": x,
           "x_history": np.r_[x_hist],
           "success": np.alltrue([r.success for r in rset]),
           "message": message}

    rs = namedtuple("coordinate_descent_result",  " ".join(list(ret.keys())))
    ret = rs(**ret)
    return ret


import numpy as np

def normalize_transform(z, match_array=None, total=1.):
    """
    Takes an array and turns them into positive weights.  This transform is scale invariant.
    """
    if match_array is not None:
        z, _ = np.broadcast_arrays(z, match_array)
    w = z**2.
    return total*w/w.sum()

def normalize_alternate_transform(z, total=1., add_element=False):
    """
    Takes an array and turns them into positive weights.  This transform is sensitive to scale.
    Due to this, the last element is determined by the rest of the array as the sum must be 1.
    add_element=False -- ignores the last given value and overwrites it with the remainder
    add_element=True -- adds an element to the end of the array for the remainder
    """
    s = np.sin(z)**2.
    if add_element:
        s = np.append(s, 1.)
    else:
        s[-1] = 1.
    c = np.cumprod(np.cos(z)**2.)
    if add_element:
        c = np.append(1., c)
    else:
        c = np.roll(c, 1)
        c[0] = 1.
    return total*s*c

def squared_transform(z, offset=0.):
    """
    Takes an array and makes it non-negative (or >= offset) via squaring.
    """
    return z**2. + offset

def ascending_nonnegative_transform(z, offset=0., nonnegative_transform=squared_transform):
    """
    Takes an array and makes it ascend in value by nonnegative_transform and incrementing by the previous element.
    """
    x = nonnegative_transform(z)
    y = np.roll(x, 1)
    y[0] = offset
    return x + y.cumsum()

def sine_bounded_transform(z, lower=-1., upper=1.):
    """
    Takes an array and makes it bound by lower and upper limits via sine:
    z = -1 corresponds to the lower and z = 1 the upper.
    """
    center = (upper+lower) / 2.
    full_width = upper - lower
    return full_width/2.*np.sin(np.pi/2.*z) + center

def sigmoid_bounded_transform(z, lower=-1., upper=1.):
    """
    Takes an array and makes it bound by lower and upper limits via sigmoid:
    z = -1 corresponds to the lower and z = 1 the upper.
    """
    center = (upper+lower) / 2.
    full_width = upper - lower
    return full_width*(1./(1.+np.exp(-z))-0.5) + center

def reciprocal_quadratic_bounded_transform(z, lower=-1., upper=1.):
    center = (upper+lower) / 2.
    full_width = upper - lower
    return full_width*(1./(z**2+1)-0.5) + center

def ascending_bounded_box_transform(z, lower=-1., upper=1., bounded_transform=sine_bounded_transform):
    """
    Takes an array and makes each element succesively lower bounded by the previous value via sine (by default).
    lower and upper correspond to the global lower and upper limit on the vector, i.e. lower affects the
    first element and upper bounds all of them.
    """
    x = np.zeros_like(z, dtype=np.float)
    for i, val in enumerate(z):
        if i == 0:
            prev = lower
        else:
            prev = x[i-1]
        x[i] = bounded_transform(val, lower=prev, upper=upper)
    return x

# TODO write unit tests


# import the important modules.
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import skimage.io

# This script will simulate diffusion of a particle over time. While we can
# solve the diffusion equation analytically, this will serve as a fun way to
# learn how to write stochastic simulations.

# We'll begin by considering the diffusion of a single particle in one
# dimension. We will say that at any point in time, the particle has an equal
# probability of taking a step to the left as it can take a step to the right.
# Let's takea  look at the trajectory of a single particle.

# We'll seed the random number generator such that our results will be
# the same each time we run it.
#np.random.seed(42)

# Define the nubmer of steps the simulation will run.
n_steps = 1000

# Define the stepping probability.
p = 0.5  # Equal probability of stepping right or left.

# We can simulate the random walk by flipping a coin at each step and testing
# its value with the probability.

position = 0
displacement = np.zeros(n_steps)

for i in range(n_steps):
    # Flip a coin.
    flip = np.random.rand()

    # Test what the value of this flip is.
    if flip < p:
        position = position + 1
    else:
        position = position - 1

    # We want some way to store the position of our walker at this step.
    displacement[i] = position

# And that's it! Let's plot eh trajectory as a function of time (step number)
# and see where it went.

plt.figure()
time = np.arange(0, n_steps, 1)
plt.plot(time, displacement)
plt.xlabel('time (number of steps)')
plt.ylabel('position')
plt.show()

# It's not very useful to see where a single particle diffused. Let's up our
# number of walkers to something huge. That will be representative of the
# behavior of our system. Let's modify our above loop to do this.
n_simulations = 1000
displacement = np.zeros((n_simulations, n_steps))
for i in range(n_simulations):

    # Reset the position for each simulation
    position = 0

    # Loop through each step.
    for j in range(n_steps):

        # Flip a coin.
        flip = np.random.rand()
        if flip < p:
            position = position + 1
        else:
            position = position - 1

        # Keep track of the displacement for each walker.
        displacement[i, j] = position

# Now, let's loop through each simulation and plot the trajectory.
plt.figure()
for i in range(n_simulations):
    plt.plot(time, displacement[i, :])

plt.xlabel('time (number of steps)')
plt.ylabel('position')
plt.show()

# Now we can see a bunch of different trajectories. Let's look at the
# distribution of ending positions. We can make a new array using the
# sotred displacements from the previous simulation.
ending_position = np.zeros(n_simulations)
for i in range(n_simulations):
    ending_position[i] = displacement[i, -1]

# Plot the distribution.
plt.figure()
plt.hist(ending_position, bins=100)
plt.xlabel('ending position')
plt.ylabel('counts')
plt.show()

# Let's see how the displacemtn would change as a function of the step number.
# In our simulation, we are storing positions that are both positive and
# netative. If we wanted to get an idea of the average distance traveled, we
# can simply square the final position and take the average of all of the
# simulations.
n_steps = [10, 100, 500, 1000, 5000, 10000]
final_position = np.zeros(n_simulations)
msd = np.zeros_like(n_steps)
for i in range(len(n_steps)):
    for j in range(n_simulations):
        position = 0
        for k in range(n_steps[i]):
            # Perorm the coin flip.
            flip = np.random.rand()
            if flip < p:
                position = position + 1
            else:
                position = position - 1

        # Save the final position.
        final_position[j] = position

    # Compute and store the mean square displacement.
    msd[i] = np.mean(final_position**2)

# Let's look at how it scales with time.
plt.figure()
plt.loglog(n_steps, np.sqrt(msd), 'o', label='simulation')

# We predict the root mean squared displacement would scale linear with the
# root of the number of steps we've taken
n_vec = np.logspace(-1, 5, 500)
plt.loglog(n_vec, np.sqrt(n_vec), '-', label='data')
plt.xlabel('time (number of steps)')
plt.ylabel('mean squared displacement')
plt.show()


import numpy as np

def eq2ang(ra, dec):
    """
    convert equatorial ra,dec in degrees to angular theta, phi in radians
    parameters

    ----------
    ra: scalar or array
        Right ascension in degrees
    dec: scalar or array
        Declination in degrees
    returns
    -------
    theta,phi: tuple
        theta = pi/2-dec*D2R # in [0,pi]
        phi   = ra*D2R       # in [0,2*pi]
    """
    dec = dec*np.pi/180
    ra  = ra*np.pi/180
    theta = np.pi/2 - dec
    phi  = ra
    return theta, phi

def radec2xy(ra0, dec0, ra, dec, radians=False):
    """
    Gnomonic projection of (ra, dec) coordinates to tangent plane about
    the point (ra0, dec0).

    Parameters
    ----------
    ra0 : float
        RA coordinate of projection origin [degrees]
    dec0 : float
        DEC coordinate of projection origin [degrees]
    ra : array_like
        RA value(s) to project [degrees]
    dec : array_like
        DEC value(s) to project [degrees]
    radians : bool, optional
        If True, return radian values with origin at (0,0) instead of RADEC

    Returns
    -------
    x : array_like
        Projected RA value(s) [degrees]
    y : array_like
        Projected DEC value(s) [degrees]
    """
    ra = np.atleast_1d(ra)
    dec = np.atleast_1d(dec)

    # Convert the input coordinates to radians
    x0 = np.deg2rad(ra0)
    y0 = np.deg2rad(dec0)
    alpha = np.deg2rad(ra)
    delta = np.deg2rad(dec)

    # Compute projected values
    denom = (np.cos(y0) * np.cos(delta) * np.cos(alpha - x0) +
             np.sin(y0) * np.sin(delta))
    x = np.cos(delta) * np.sin(alpha - x0) / denom
    y = ((np.cos(y0) * np.sin(delta) -
          np.sin(y0) * np.cos(delta) * np.cos(alpha - x0)) / denom)

    if radians:
        return x, y

    return np.rad2deg(x), np.rad2deg(y)

def xy2radec(center_ra, center_dec, x, y):
    """
    Inverse projection from tangent plane (x, y) coordinates to the sphere
    about the point (center_ra, center_dec)

    Parameters
    ----------
    center_ra : float
        RA coordinate of projection center [degrees]
    center_dec : float
        DEC coordinate of projection center [degrees]
    x : float array_like
        RA value(s) to deproject [degrees]
    y : float array_like
        DEC value(s) to deproject [degrees]

    Returns
    -------
    ra : float array_like
        Deprojected RA value(s) [degrees]
    dec : float array_like
        Deprojected DEC value(s) [degrees]
    """
    # Convert projection center to radians
    x0 = np.deg2rad(center_ra)
    y0 = np.deg2rad(center_dec)

    x = np.atleast_1d(np.deg2rad(x))
    y = np.atleast_1d(np.deg2rad(y))

    # Compute deprojected coordinates
    z = np.sqrt(x * x + y * y)
    c = np.arctan(z)

    # Prevent division by zero
    factor = np.ones(len(z))
    inds = (z != 0)
    factor[inds] = y[inds] / z[inds]

    delta = np.arcsin(np.cos(c) * np.sin(y0) + factor * np.cos(y0) * np.sin(c))
    denom = z * np.cos(y0) * np.cos(c) - y * np.sin(y0) * np.sin(c)
    alpha = x0 + np.arctan2(x * np.sin(c), denom)

    return np.rad2deg(alpha), np.rad2deg(delta)


import tensorflow as tf
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler

# Data preprocessing
data = pd.read_csv("RealEstate.csv")

# Converting Pandas dataframe to numpy array
X_train = data.Size.values.reshape(-1, 1)
Y_train = data.Price.values.reshape(-1, 1)

# Feature scaling so that Gradient Descent works well
sc = StandardScaler()
X_train = sc.fit_transform(X_train)
Y_train = sc.fit_transform(Y_train)

# Parameters
alpha = 0.01  # learning rate
iterations = 1000  #  number of iteration
m = X_train.shape[0]  # sample count in training data

# TF Placeholders
X = tf.placeholder(tf.float32, name='X')
Y = tf.placeholder(tf.float32, name='Y')

# Theta variables
theta0 = tf.Variable(np.random.normal(), name="theta0")
theta1 = tf.Variable(np.random.normal(), name="theta1")

# Hypothesis
hypothesis = tf.add(tf.multiply(X, theta1), theta0)

# Cost Function
cost = tf.reduce_sum(tf.pow(hypothesis-Y, 2))/(2*m)

# Gradient Descent Algorithm
optimizer = tf.train.GradientDescentOptimizer(alpha).minimize(cost)

# Initializing the variables and creating session
init = tf.global_variables_initializer()
sess = tf.Session()
sess.run(init)

# Batch Gradient Descent
# feed data to algorithm
for iter in range(iterations):
    for (x, y) in zip(X_train, Y_train):
        sess.run(optimizer, feed_dict={X: x, Y: y})

# Printing coefficients
print(f"Coefficients theta0 = {sess.run(theta0)}, theta1 = {sess.run(theta1)}")

# Predicted Values for plotting
Y_pred = sess.run(theta0) + sess.run(theta1) * X_train

# Model Evaluation
def rmse(Y, Y_pred):
    rmse = np.sqrt(sum((Y - Y_pred) ** 2) / Y.shape[0])
    return rmse

def r2_score(Y, Y_pred):
    mean_y = np.mean(Y)
    ss_tot = sum((Y - mean_y) ** 2)
    ss_res = sum((Y - Y_pred) ** 2)
    r2 = 1 - (ss_res / ss_tot)
    return r2

# Print Scores
print("RMSE = ", rmse(Y_train, Y_pred))
print("R2 Score = ", r2_score(Y_train, Y_pred))

# Reverse Feature Scaling so that graph will result in real values
X_train = sc.inverse_transform(X_train)
Y_train = sc.inverse_transform(Y_train)
Y_pred = sc.inverse_transform(Y_pred)

# Visualization
# Ploting Line
plt.plot(X_train, Y_pred, color='#c93e4e', label='Regression Line')
# Ploting Scatter Points
plt.scatter(X_train, Y_train, c='#54a774', label='Scatter Plot')
plt.xlabel('Size')
plt.ylabel('Price')
plt.legend()
plt.show()

# Close TF session
sess.close()


import numpy as np
from scipy import special

def psilog(x: np.ndarray) -> np.ndarray:
    """
    psi(x) - log(x)
    needed when calculating E[ln[x]] when x is a Gamma variable
    """
    return special.digamma(x) - np.log(x)

def grad_psilog(x: np.ndarray) -> np.ndarray:
    """d_x (psi(x) - log(x)) = psi^1(x) - 1/x

    needed when calculating the inverse of psilog(x)
    by using Newton-Raphson

    see:
    invpsilog(c)
    """
    return special.polygamma(1, x) - 1 / x

def invpsilog(c: np.ndarray) -> np.ndarray:
    """
    Solves the equation

    psi(x) - log(x) = c

    where psi is the digamma function. c must be negative.
    The function calculates an approximate inverse which it uses as
    a starting point to 4 iterations of the Newton-Raphson algorithm.
    """
    c = np.asanyarray(c)

    if not np.all(c < 0):
        raise ValueError("values passed must be negative")

    # approximate starting guess
    # -1/x < psilog(x) < -1/(2x)
    A, beta, gamma = 0.38648347, 0.89486989, 0.78578843
    x0 = -(1 - 0.5 * (1 + A * (-c) ** beta) ** -gamma) / c

    # do 4 iterations of Newton Raphson to refine estimate
    for _ in range(4):
        f0 = psilog(x0) - c
        x0 = x0 - f0 / grad_psilog(x0)

    return x0


import matplotlib.pyplot as plt
import numpy as np

def tunediagram(order=range(1,4),integer=[0,0],lines=[1,1,1,1],colors='ordered',linestyle='-',fig=plt.gcf()):
    '''
    plot resonance diagram up to specified order
    mx + ny = p
    x = (p-ny)/m
    x = 1 where y = (p-m)/n

    EXAMPLE:
       tunediagram(order=[1,2,3])
       tunediagram([1,2,3],integer=[6,8],lines=[0,0,1,1])
       tunediagram([1,2,3],integer=[6,8],lines=[0,0,1,1], colors='black3', linestyle='--')

    INPUT:
      1. order - array of tune orders to plot. e.g. up to 3rd order, [1,2,3]
      2. integers - integer part of the tune to plot in x,y, default is [0,0]. e.g. plot from 6-7 and 9-10, integer=[6,9]
      2. lines - a boolean of which resonance lines to plot. e.g. [vertical,horizontal,sum,diff]. e.g. plot only vert/horz lines, lines = [1,1,0,0]
      4. colors - option to plot lines in different colors. default is ordered. color options only go up to 10th order
        ordered - each order resonance is a different color
        black - all lines are black
        blackX - X here is a number. all resonances X+ will be in black. e.g. black3 means plot resonances 1-2 in color and 3,4,5,... in black
      6. linestyle - linestyle option from matplotlib
      7. fig - pass in a handle to a figure, otherwise things will be plotted in the current figure.

    Written by Levon Dovlatyan
    University of Maryland, Department of Physics
    Oct 2018
    '''
    # define some variables
    pval = 40 # increase for more/higher order lines
    p = np.linspace(0,pval,pval+1)

    qxmin,qymin = integer[0],integer[1]
    qxmax,qymax = qxmin+1,qymin+1

    # define different colors, up to 10th order
    color = ['C0','C1','C2','C3','C4','C5','C6','C7','C8','C9']
    if colors == 'black':
        color = ['k']*10
    elif colors[0:-1] == 'black':
        idx = int(colors[-1])
        color = color[0:idx-1] + (['k']*10)[idx-1:]

    # adjust plot limits
    plt.xlim((qxmin-0.01, qxmax+0.01))
    plt.ylim((qymin-0.01, qymax+0.01))

    # Plotting formula
    # we plot resonances in reverse order
    for i in order[::-1]:
        m = np.linspace(-i,i,2*i+1)
        n1 = (i-np.abs(m))
        n2 = -1*n1
        for j in range(0,m.size,1):
            # check to see equation is divided by 0 or not
            # ver & hor res lines
            if ((n1[j] == 0 and lines[1]) or (m[j] == 0 and lines[0])):
                # vertical lines
                if n1[j] == 0 and lines[1]:
                    plt.vlines(p/m[j],qymin,qymax,color=color[i-1],linestyle=linestyle);
                # horizontal lines
                if m[j] == 0 and lines[0]:
                    plt.hlines(p/n1[j],qxmin,qxmax,color=color[i-1],linestyle=linestyle);
                    plt.hlines(p/n2[j],qxmin,qxmax,color=color[i-1],linestyle=linestyle);
            # sum and dif res lines
            elif not(n1[j] == 0) and not(m[j] == 0):
                # resonance sum lines
                if lines[2]:
                    if np.sign(m[j]) > 0:
                        plt.plot([[qxmin]*p.size,[qxmax]*p.size],[p/n2[j] - np.array(m[j]*qxmin/n2[j]), p/n2[j] - np.array(m[j]*qxmax/n2[j])],color=color[i-1],linestyle=linestyle);
                    else:
                        plt.plot([[qxmin]*p.size,[qxmax]*p.size],[p/n1[j] - np.array(m[j]*qxmin/n1[j]), p/n1[j] - np.array(m[j]*qxmax/n1[j])],color=color[i-1],linestyle=linestyle);
                # resonance dif lines
                if lines[3]:
                    if np.sign(m[j]) > 0:
                        plt.plot([[qxmin]*p.size,[qxmax]*p.size],[p/n1[j] - np.array(m[j]*qxmin/n1[j]), p/n1[j] - np.array(m[j]*qxmax/n1[j])],color=color[i-1],linestyle=linestyle);
                    else:
                        plt.plot([[qxmin]*p.size,[qxmax]*p.size],[p/n2[j] - np.array(m[j]*qxmin/n2[j]), p/n2[j] - np.array(m[j]*qxmax/n2[j])],color=color[i-1],linestyle=linestyle);


import pickle
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import style
import math
from statistics import mean
style.use('fivethirtyeight')

#please change the dataset here
f = open('data1_new.pkl','rb')
#f = open('data2_new.pkl','rb')
#f = open('data3_new.pkl','rb')
data1 = pickle.load(f)
f.close()
da = np.asarray(data1)
X = da[:,0]
Y = da[:,1]

def best_fit_slope_and_intercept(X,Y):
    m = (np.multiply(mean(X),mean(Y)) -mean(np.multiply(X,Y) ))/((mean(X))**2-mean((X)**2))
    b = mean(Y) - m*mean(X)
    return m,b
m,b = best_fit_slope_and_intercept(X,Y)
#print(m,b)
regression_line = [(m*x)+b for x in X]
plt.scatter(X,Y)
plt.plot(X,regression_line)

matrix_x = np.ones((X.shape[0],2))
matrix_x[:,0] = np.ones(X.shape[0])
matrix_x[:,1] = X
L = 5
LI = [[0,0],[0,L]]
model_parameters = np.matmul(np.matmul(np.linalg.inv(np.matmul(matrix_x.T,matrix_x) + LI),matrix_x.T),Y)
print(model_parameters)

plt.plot(X, model_parameters[0] + model_parameters[1]*X, color = 'black')

plt.title("line fitting with Regularization")
#plt.legend(loc = 2)
plt.axis([-150,150,-100,100])
plt.show()


import random
import numpy as np
import pandas as pd
import numpy.matlib
import matplotlib.pyplot as plt
from scipy.stats import multivariate_normal
from mpl_toolkits.mplot3d import Axes3D
np.set_printoptions(threshold=np.inf)

# Initialize the data distribution
N_labels = 4
N = 100000
priors = np.array([[0.25,0.25,0.25,0.25]])

# Create mean and covariance matrices
mean_matrix = np.zeros((4,3))
mean_matrix[0, :] = [0, 0, 0]
mean_matrix[1, :] = [0, 0, 3]
mean_matrix[2, :] = [0, 3, 0]
mean_matrix[3, :] = [3, 0, 0]
covariance_matrix = np.zeros((4, 3, 3))
covariance_matrix[0,:,:] = np.array([[1, 0, -3], [0, 1, 0], [-3, 0, 15]])
covariance_matrix[1,:,:] = np.array([[8, 0, 0], [0, .5, 0], [0, 0, .5]])
covariance_matrix[2,:,:] = np.array([[1, 0, -3], [0, .5, 0], [-3, 0, 15]])
covariance_matrix[3,:,:] = np.array([[8, 0, 0], [0, 1, 0], [3, 0, 15]])

# Import dataset
dataset = pd.read_csv('Dtest_100000.csv')
# Split features and labels
X = dataset.iloc[:, 0:3].values
y = dataset.iloc[:, 3].values

# Define 0-1 Loss Matrix
loss_matrix = np.ones(shape = [N_labels, N_labels]) - np.eye(N_labels)

# Compute Class conditional PDF
P_x_given_L = np.zeros(shape = [N_labels, N])
for i in range(N_labels):
    P_x_given_L[i, :] = multivariate_normal.pdf(X,mean = mean_matrix[i, :], cov = covariance_matrix[i, :,:])

# Compute Class Posteriors using priors and class conditional PDF
P_x = np.matmul(priors, P_x_given_L)
ClassPosteriors = (P_x_given_L * (np.matlib.repmat(np.transpose(priors), 1, N))) / np.matlib.repmat(P_x, N_labels, 1)

# Evaluate Expected risk and decisions based on minimum risk
ExpectedRisk = np.matmul(loss_matrix, ClassPosteriors)
Decision = np.argmin(ExpectedRisk, axis = 0)
print("Average Expected Risk", np.sum(np.min(ExpectedRisk, axis = 0)) / N)

# Estimate Confusion Matrix
ConfusionMatrix = np.zeros(shape = [N_labels, N_labels])

for d in range(N_labels):
    for l in range(N_labels):
        ConfusionMatrix[d, l] = (np.size(np.where((d == Decision) & (l == y)))) / np.size(np.where(y == l))

print("Confusion Matrix-")
print(ConfusionMatrix)


import numpy as np
from scipy.stats import (skew, kurtosis)
from sklearn.pipeline import FeatureUnion, Pipeline
from sklearn.preprocessing import (FunctionTransformer)

from classification.features.pipeline.utils import (
    get_transformer,
    get_transformer_list,
    get_data_from_epochs,
)

def _get_zero_crossing_rate(signal):
    """
    Multiplies signal by itself shifted by one.
    If the signal crosses the horizontal axis,
    the sign will be negative and vice-versa.

    Returns nb of time the signal crossed the horizontal axis
    """
    return ((signal[:-1] * signal[1:]) < 0).sum()

def _get_mean_crossing_rate(signal):
    return _get_zero_crossing_rate(signal - np.mean(signal))

def _hjorth(X):
    """ Compute _hjorth mobility and complexity of a time series from either two
    [source: https://github.com/forrestbao/pyeeg]
    cases below:
        1. X, the time series of type list (default)
        2. D, a first order differential sequence of X (if D is provided,
           recommended to speed up)
    In case 1, D is computed using Numpy's Difference function.
    Notes
    -----
    To speed up, it is recommended to compute D before calling this function
    because D may also be used by other functions whereas computing it here
    again will slow down.
    Parameters
    ----------
    X
        list
        a time series
    D
        list
        first order differential sequence of a time series
    Returns
    -------
    As indicated in return line (mobility, complexity)
    """
    D = np.diff(X)
    D = D.tolist()

    D.insert(0, X[0])  # pad the first difference
    D = np.array(D)

    n = len(X)

    M2 = float(sum(D ** 2)) / n
    TP = sum(np.array(X) ** 2)
    M4 = 0
    for i in range(1, len(D)):
        M4 += (D[i] - D[i - 1]) ** 2
    M4 = M4 / n

    # hjorth Mobility and Complexity
    mobility = np.sqrt(M2 / TP)
    complexity = np.sqrt(
        float(M4) * TP / M2 / M2
    )
    return [mobility, complexity]

def get_time_domain_pipeline():
    get_data_from_epochs_transformer = FunctionTransformer(
        get_data_from_epochs, validate=False)
    mean_transformer = FunctionTransformer(
        get_transformer(np.mean), validate=True)
    std_transformer = FunctionTransformer(
        get_transformer(np.std), validate=True)
    skew_transformer = FunctionTransformer(
        get_transformer(skew), validate=True)
    kurtosis_transformer = FunctionTransformer(
        get_transformer(kurtosis), validate=True)
    mean_crossing_rate_transformer = FunctionTransformer(
        get_transformer(_get_mean_crossing_rate), validate=True)
    hjorth_transformer = FunctionTransformer(
        get_transformer_list(_hjorth), validate=True)

    return Pipeline([
        ('epochs_to_data', get_data_from_epochs_transformer),
        ('time_domain_features', FeatureUnion([
            ('mean', mean_transformer),
            ('std', std_transformer),
            ('skew', skew_transformer),
            ('kurtosis', kurtosis_transformer),
            ('mean-crossing-rate', mean_crossing_rate_transformer),
            ('_hjorth', hjorth_transformer)
        ], n_jobs=1))
    ])


import numpy as np
from sklearn import datasets

iris = datasets.load_iris()

X = iris["data"][:, (2, 3)]  # length, width
y = iris["target"]
X_with_bias = np.c_[np.ones([len(X), 1]), X]

test_ratio = 0.2
validation_ratio = 0.2
total_size = len(X_with_bias)

test_size = int(total_size * test_ratio)
validation_size = int(total_size * validation_ratio)
train_size = total_size - test_size - validation_size

rnd_indices = np.random.permutation(total_size)

X_train = X_with_bias[rnd_indices[:train_size]]
y_train = y[rnd_indices[:train_size]]
X_valid = X_with_bias[rnd_indices[train_size:-test_size]]
y_valid = y[rnd_indices[train_size:-test_size]]
X_test = X_with_bias[rnd_indices[-test_size:]]
y_test = y[rnd_indices[-test_size:]]

def to_one_hot(y):
    n_classes = y.max() + 1
    m = len(y)
    Y_one_hot = np.zeros((m, n_classes))
    Y_one_hot[np.arange(m), y] = 1
    return Y_one_hot

def softmax(logits):
    exps = np.exp(logits)
    exp_sums = np.sum(exps, axis=1, keepdims=True)
    return exps / exp_sums

Y_train_one_hot = to_one_hot(y_train)
Y_valid_one_hot = to_one_hot(y_valid)
Y_test_one_hot = to_one_hot(y_test)

n_inputs = X_train.shape[1]
n_outputs = len(np.unique(y_train))

eta = 0.1
n_iterations = 5001
m = len(X_train)
epsilon = 1e-7
alpha = 0.1  # regularization hyperparameter
best_loss = np.infty

Theta = np.random.randn(n_inputs, n_outputs)

for iteration in range(n_iterations):
    logits = X_train.dot(Theta)
    Y_proba = softmax(logits)
    error = Y_proba - Y_train_one_hot
    gradients = 1/m * X_train.T.dot(error) + np.r_[np.zeros([1, n_outputs]), alpha * Theta[1:]]
    Theta = Theta - eta * gradients

    logits = X_valid.dot(Theta)
    Y_proba = softmax(logits)
    xentropy_loss = -np.mean(np.sum(Y_valid_one_hot * np.log(Y_proba + epsilon), axis=1))
    l2_loss = 1/2 * np.sum(np.square(Theta[1:]))
    loss = xentropy_loss + alpha * l2_loss
    if iteration % 500 == 0:
        print(iteration, loss)
    if loss < best_loss:
        best_loss = loss
    else:
        print(iteration - 1, best_loss)
        print(iteration, loss, "early stopping!")
        break

logits = X_valid.dot(Theta)
Y_proba = softmax(logits)
y_predict = np.argmax(Y_proba, axis=1)

accuracy_score = np.mean(y_predict == y_valid)
print(accuracy_score)


import numpy as np
import pandas as pd
from math import log

def entropia(df_dados: pd.DataFrame, nom_col_classe: str) -> float:

    ser_count_col = df_dados[nom_col_classe].value_counts()
    num_total = len(df_dados)
    entropia = 0

    for val_atr, count_atr in ser_count_col.iteritems():
        val_prob = count_atr/num_total
        entropia -= val_prob * (log(val_prob, 2))

    return entropia

def ganho_informacao_condicional(
    df_dados: pd.DataFrame,
    val_entropia_y: float,
    nom_col_classe: str,
    nom_atributo: str,
    val_atributo: float
) -> float:

    """
    Calcula o GI(Y|nom_atributo=val_atributo), ou seja,
    calcula o ganho de informação do atributo 'nom_atributo'
    quando ele assume o valor 'val_atributo'.
    O valor de Entropia(Y) já foi
     e está armazenado em val_entropia_y.
    Dica: A entropia condicional pode ser calculada filtrando
    o DataFrame df_dados.

    df_dados: Dataframe com os dados a serem analisados.
    val_entropia_y: Entropia(Y) (ver slides)
    nom_col_classe: nome da coluna que representa a classe
    nom_atributo: atributo a ser calculado o ganho de informação
    val_atributo: valor do atributo a ser considerado para este calculo
    """
    # substitua os "None"/0 quando necessario para completar o código
    # .em df_dados_filtrado, filtre o df_dados da forma correta -
    # pensando quais
    # elementos considerar na entropia condicional
    # (Y|nom_atributo=val_atributo).
    df_dados_filtrado = df_dados[df_dados[nom_atributo] == val_atributo]

    # use df_dados_filtrado para obter o valor de
    # Entropia(Y|nom_atributo=val_atributo)
    val_ent_condicional = entropia(df_dados_filtrado, nom_col_classe)
    # use val_ent_condicional para calcular o GI(Y|nom_atributo=val_atributo)
    val_gi = val_entropia_y - val_ent_condicional

    # para testes:
    # print("GI({classe}| {atr}={val}) =
    # {val_gi}".format(classe=nom_col_classe,atr=nom_atributo,val=val_atributo,val_gi=val_gi))

    return val_gi

def ganho_informacao(
    df_dados: pd.DataFrame,
    nom_col_classe: str,
    nom_atributo: str
) -> float:

    """
        Calcula GI(Y| nom_atributo), ou seja, o ganho de informação do
        atributo nom_atributo.

        df_dados: DataFrame com os dados a serem analisados.
        nom_col_classe: nome da coluna que representa a classe
        nom_atributo: atributo a ser calculado o ganho de informação
        val_atributo: valor do atributo a ser considerado para este calculo
    """
    # Muito similar ao codigo da entropia, mas aqui você deverá navegar sobre
    # os possiveis valores do atributo nom_atributo para calcular o infoGain
    # (usando a função ganho_informacao_condicional)
    # substitua os "None"/0 quando necessario para completar o código
    # atenção nessa linha abaixo, qual é o valor que temos que colocar em None?
    # o que precisamos contabilizar dessa vez?
    # media ponderada do ganho de informação de cada valor de um atributo
    ser_count_col = df_dados[nom_atributo].value_counts()
    val_entropia_y = entropia(df_dados, nom_col_classe)
    num_total = len(df_dados)
    val_info_gain = 0
    for val_atr, count_atr in ser_count_col.iteritems():
        val_prob = count_atr/num_total
        val_info_gain += val_prob * (ganho_informacao_condicional(
            df_dados,
            val_entropia_y,
            nom_col_classe,
            nom_atributo,
            val_atr
            ))
        # print("GI("+nom_col_classe+"| "+nom_atributo+"="+val_atr+") =
        # "+str(val_gi_val))

    return val_info_gain


# Dynamic programming
# maximize 16a + 19b + 23c + 28d
# subject to 2a + 3b + 4c + 5d <= 7
# a,b,c,d in {0, 1}
import numpy as np
import time

def DP(k, j, v, w):
    DP.counter += 1
    if j == 0:
        return 0
    elif w[j-1] <= k:
        return max(DP(k, j - 1, v, w), v[j-1] + DP(k - w[j-1], j - 1, v, w))
    else:
        return DP(k, j - 1, v, w)

DP.counter = 0
values = np.array([16, 19, 23, 28, 18, 24, 5, 15, 17, 37, 21, 14, 9, 24, 11, 58, 28, 22, 18,
                   13, 33,16, 19, 23, 28, 18, 24, 5, 15, 17, 37, 21, 14, 9, 24, 11, 58, 28, 22, 18, 13, 33,
                   16, 19, 23, 28, 18, 24, 5, 15, 17, 37, 21,  13, 33,16, 19, 23, 28, 18, 24, 5, 15, 17])
weights = np.array([2, 3, 4, 5, 3, 4, 1, 2, 2, 6, 4, 1, 1, 4, 1, 7, 5, 4, 3, 2, 6, 2, 3, 4, 5, 3, 4, 1, 2, 2, 6,
                    4, 1, 1, 4, 1, 7, 5, 4, 3, 2, 6,
                    2, 3, 4, 5, 3, 4, 1, 2, 2, 6, 4, 1, 1, 4, 1, 7, 5, 4, 3, 2, 6, 2, 3, 4, 5, 3, 4, 1, 2, 2])

K = 7
N = values.size
selection = np.zeros(N)

start_time = time.time()
x = DP(K, N, values, weights)

time_taken = (time.time()*1000 - start_time*1000)
print("The optimal value is: %i." % x)
print("The function DP(k, j, v, w) has been called: %i times." % DP.counter)
print("Time taken: %s milliseconds" % time_taken)


import numpy as np
from typing import List

from pynet.functional.abstract import Function
from pynet.tensor import Tensor

class Matmul(Function):
    """Function represents matrix multiplication.

    Function takes two tensors as inputs (i. e. len(x) == 2 in the forward method)
    and outputs an tensor representing the matrix multiplication operation result.
    At the current version, this operation supports only tensors with dimensions <= 2.

    If x[0] is an m × n matrix and x[1] is an n × p matrix, the result is defined to be
    the m × p matrix.
    """

    def __init__(self) -> None:
        """Ctor"""
        super().__init__()

    def forward(self, x: List[Tensor]) -> Tensor:
        assert len(x) == 2, "Matmul function takes 2 parameters"
        assert (
            x[0].ndarray.ndim <= 2 and x[1].ndarray.ndim <= 2
        ), "Forward -> Matrix multiplication of tensors with dimensions > 2 are not supported, yet"

        y = np.matmul(x[0].ndarray, x[1].ndarray)

        self._stored_results["a"] = x[0]
        self._stored_results["b"] = x[1]

        return Tensor(y)

    def backward(self, y: Tensor) -> List[Tensor]:
        assert (
            y.ndarray.ndim <= 2
        ), "Backward -> Matrix multiplication of tensors with dimensions > 2 are not supported, yet"

        a = self._stored_results["a"]
        b = self._stored_results["b"]

        da = np.matmul(y.ndarray, b.ndarray.T)
        db = np.matmul(a.ndarray.T, y.ndarray)

        return [Tensor(da), Tensor(db)]


import unittest
import smithwilson as sw
import numpy as np

class TestSmithWilson(unittest.TestCase):

    def test_ufr_discount_factor(self):
        """Test creation of UFR discount factor vector"""

        # Input
        ufr = 0.029
        t = np.array([0.25, 1.0, 5.0, 49.5, 125.0])

        # Expected Output
        expected = np.array([0.992878614, 0.971817298, 0.866808430, 0.242906395, 0.028059385])

        # Actual Output
        actual = sw.ufr_discount_factor(ufr=ufr, t=t)

        # Assert
        self.assertEqual(type(actual), type(expected), "Returned types not matching")
        self.assertTupleEqual(actual.shape, expected.shape, "Shapes not matching")
        np.testing.assert_almost_equal(actual, expected, decimal=8, err_msg="UFR discount factors not matching")

    def test_calculate_prices(self):
        """Test calculation of zero-coupon bond price vector"""

        # Input
        r = np.array([0.02, 0.025, -0.033, 0.01, 0.0008])
        t = np.array([0.25, 1.0, 5.0, 49.5, 125.0])

        # Expected Output
        expected = np.array([0.995061577, 0.975609756, 1.182681027, 0.611071456, 0.904873593])

        # Actual Output
        actual = sw.calculate_prices(rates=r, t=t)

        # Assert
        self.assertEqual(type(actual), type(expected), "Returned types not matching")
        self.assertTupleEqual(actual.shape, expected.shape, "Shapes not matching")
        np.testing.assert_almost_equal(actual, expected, decimal=8, err_msg="Prices not matching")

    def test_wilson_function_symmetric(self):
        """Test creation of a symmetric Wilson-function matrix (t1 = t2)"""

        # Input
        t = np.array([0.25, 1.0, 5.0, 49.5, 125.0]).reshape((-1, 1))
        ufr = 0.029
        alpha = 0.2

        # Expected Output
        expected = np.array([[0.00238438, 0.00872884, 0.02719467, 0.01205822, 0.00139298],
                             [0.00872884, 0.03320614, 0.10608305, 0.04720974, 0.00545372],
                             [0.02719467, 0.10608305, 0.42652097, 0.2105409 , 0.02432211],
                             [0.01205822, 0.04720974, 0.2105409 , 0.55463306, 0.06747646],
                             [0.00139298, 0.00545372, 0.02432211, 0.06747646, 0.01928956]])

        # Actual Output
        actual = sw.wilson_function(t1=t, t2=t, ufr=ufr, alpha=alpha)

        # Assert
        self.assertEqual(type(actual), type(expected), "Returned types not matching")
        self.assertTupleEqual(actual.shape, expected.shape, "Shapes not matching")
        np.testing.assert_almost_equal(actual, expected, decimal=8, err_msg="Wilson functions not matching")

    def test_wilson_function_asymmetric_t1_lt_t2(self):
        """Test creation of a symmetric Wilson-function matrix (t1 != t2) with length of t1 > length of t2"""

        # Input
        t_obs = np.array([0.25, 1.0, 5.0, 49.5, 125.0]).reshape((-1, 1))
        t_target = np.array([0.25, 0.5, 1.0, 2.0, 2.5, 3.5, 5.0, 10.0, 20.0, 49.5, 125.0]).reshape((-1, 1))
        ufr = 0.029
        alpha = 0.2

        # Expected Output
        expected = np.array([[0.00238438, 0.00872884, 0.02719467, 0.01205822, 0.00139298],
                             [0.00463874, 0.01723526, 0.0539627 , 0.0239447 , 0.00276612],
                             [0.00872884, 0.03320614, 0.10608305, 0.04720974, 0.00545372],
                             [0.015444  , 0.05969492, 0.20375322, 0.0917584 , 0.01060004],
                             [0.01817438, 0.07046799, 0.24880429, 0.11307011, 0.013062  ],
                             [0.02260267, 0.08794588, 0.33012767, 0.15383656, 0.01777143],
                             [0.02719467, 0.10608305, 0.42652097, 0.2105409 , 0.02432211],
                             [0.03225016, 0.12614043, 0.54769846, 0.36498556, 0.04216522],
                             [0.02751232, 0.10770227, 0.47881259, 0.54833094, 0.06336226],
                             [0.01205822, 0.04720974, 0.2105409 , 0.55463306, 0.06747646],
                             [0.00139298, 0.00545372, 0.02432211, 0.06747646, 0.01928956]])

        # Actual Output
        actual = sw.wilson_function(t1=t_target, t2=t_obs, ufr=ufr, alpha=alpha)

        # Assert
        self.assertEqual(type(actual), type(expected), "Returned types not matching")
        self.assertTupleEqual(actual.shape, expected.shape, "Shapes not matching")
        np.testing.assert_almost_equal(actual, expected, decimal=8, err_msg="Wilson functions not matching")

    def test_wilson_function_asymmetric_t2_lt_t1(self):
        """Test creation of a symmetric Wilson-function matrix (t1 != t2) with length of t2 > length of t1"""

        # Input
        t_target = np.array([0.50, 1.5, 7.0, 22.5]).reshape((-1, 1))
        t_obs = np.array([0.25, 1.0, 2.0, 2.5, 5.0, 10.0, 20.0]).reshape((-1, 1))
        ufr = 0.032
        alpha = 0.15

        # Expected Output
        expected = np.array([[0.00263839, 0.00990704, 0.01791847, 0.02129457, 0.03324991, 0.04184617, 0.03736174],
                             [0.00714378, 0.02751832, 0.05096578, 0.06087744, 0.09600535, 0.12138299, 0.1085669 ],
                             [0.01939785, 0.07563626, 0.14568738, 0.17843321, 0.31674624, 0.45088288, 0.42190812],
                             [0.01768861, 0.06909389, 0.13384921, 0.16464728, 0.3035725 , 0.51271549, 0.69668792]])

        # Actual Output
        actual = sw.wilson_function(t1=t_target, t2=t_obs, ufr=ufr, alpha=alpha)

        # Assert
        self.assertEqual(type(actual), type(expected), "Returned types not matching")
        self.assertTupleEqual(actual.shape, expected.shape, "Shapes not matching")
        np.testing.assert_almost_equal(actual, expected, decimal=8, err_msg="Wilson functions not matching")

    def test_fit_parameters(self):
        """Test estimation of Smith-Wilson parameter vector ζ"""

        # Input
        r = np.array([0.02, 0.025, -0.033, 0.01, 0.0008]).reshape((-1, 1))
        t = np.array([0.25, 1.0, 5.0, 49.5, 125.0]).reshape((-1, 1))
        ufr = 0.029
        alpha = 0.2

        # Expected Output
        expected = np.array([-42.78076209, 23.4627511, -3.96498616, 8.92604195, -75.22418515]).reshape((-1, 1))

        # Actual Output
        actual = sw.fit_parameters(rates=r, t=t, ufr=ufr, alpha=alpha)

        # Assert
        self.assertEqual(type(actual), type(expected), "Returned types not matching")
        self.assertTupleEqual(actual.shape, expected.shape, "Shapes not matching")
        np.testing.assert_almost_equal(actual, expected, decimal=8, err_msg="Parameter not matching")

    def test_fit_smithwilson_rates_actual(self):
        """Test estimation of yield curve fitted with the Smith-Wilson algorithm.
           This example uses an actual example from EIOPA. Deviations must be less than 1bps (0.01%).
           Source: https://eiopa.europa.eu/Publications/Standards/EIOPA_RFR_20190531.zip
                   EIOPA_RFR_20190531_Term_Structures.xlsx; Tab: RFR_spot_no_VA; Switzerland
        """

        # Input
        r = np.array([-0.00803, -0.00814, -0.00778, -0.00725, -0.00652,
                      -0.00565, -0.0048, -0.00391, -0.00313, -0.00214,
                      -0.0014, -0.00067, -0.00008, 0.00051, 0.00108,
                      0.00157, 0.00197, 0.00228, 0.0025, 0.00264,
                      0.00271, 0.00274, 0.0028, 0.00291, 0.00309]).reshape((-1, 1))
        t = np.array([float(y + 1) for y in range(len(r))]).reshape((-1, 1)) # 1.0, 2.0, ..., 25.0
        ufr = 0.029
        alpha = 0.128562

        t_target = np.array([float(y + 1) for y in range(65)]).reshape((-1, 1))

        # Expected Output
        expected = np.array([-0.00803, -0.00814, -0.00778, -0.00725, -0.00652,
                             -0.00565, -0.0048, -0.00391, -0.00313, -0.00214,
                             -0.0014, -0.00067, -0.00008, 0.00051, 0.00108,
                             0.00157, 0.00197, 0.00228, 0.0025, 0.00264,
                             0.00271, 0.00274, 0.0028, 0.00291, 0.00309,
                             0.00337, 0.00372, 0.00412, 0.00455, 0.00501,
                             0.00548, 0.00596, 0.00644, 0.00692, 0.00739,
                             0.00786, 0.00831, 0.00876, 0.00919, 0.00961,
                             0.01002, 0.01042, 0.01081, 0.01118, 0.01154,
                             0.01189, 0.01223, 0.01255, 0.01287, 0.01318,
                             0.01347, 0.01376, 0.01403, 0.0143, 0.01456,
                             0.01481, 0.01505, 0.01528, 0.01551, 0.01573,
                             0.01594, 0.01615, 0.01635, 0.01655, 0.01673]).reshape((-1, 1))

        # Actual Output
        actual = sw.fit_smithwilson_rates(rates_obs=r, t_obs=t, t_target=t_target, ufr=ufr, alpha=alpha)

        # Assert - Precision of 4 decimal points equals deviatino of less than 1bps
        self.assertEqual(type(actual), type(expected), "Returned types not matching")
        self.assertTupleEqual(actual.shape, expected.shape, "Shapes not matching")
        np.testing.assert_almost_equal(actual, expected, decimal=4, err_msg="Fitted rates not matching")

    def test_fit_smithwilson_rates_random(self):
        """Test estimation of yield curve fitted with the Smith-Wilson algorithm
           This test uses random data points.
        """

        # Input
        r = np.array([0.02, 0.025, -0.033, 0.01, 0.0008]).reshape((-1, 1))
        t = np.array([0.25, 1.0, 5.0, 20.0, 25.0]).reshape((-1, 1))
        ufr = 0.029
        alpha = 0.12

        t_target = np.array([0.25, 0.5, 1.0, 2.0, 2.5, 3.5, 5.0, 10.0, 20.0, 49.5, 125.0]).reshape((-1, 1))

        # Expected Output
        expected = np.array([0.02, 0.02417656, 0.025, 0.00361999, -0.00733027,
                            -0.02345319, -0.033, -0.01256218, 0.01, 0.00715949, 0.02015626]).reshape((-1, 1))

        # Actual Output
        actual = sw.fit_smithwilson_rates(rates_obs=r, t_obs=t, t_target=t_target, ufr=ufr, alpha=alpha)

        # Assert
        self.assertEqual(type(actual), type(expected), "Returned types not matching")
        self.assertTupleEqual(actual.shape, expected.shape, "Shapes not matching")
        np.testing.assert_almost_equal(actual, expected, decimal=8, err_msg="Fitted rates not matching")


#!/usr/bin/python

from sympy import *
init_printing()

import matplotlib.pyplot as plt

t, s = symbols('t, s')
a = symbols('a', real=True, positive=True)
f = exp(-a*t)

def lt(f):
	return laplace_transform(f, t, s, noconds=True)

def inv_lt(F):
	return inverse_laplace_transform(F, s, t)

res_lt = laplace_transform(f, t, s, noconds=True)
print(res_lt)

res_invlt = inv_lt(res_lt).subs({a: 2})
print(res_invlt)

res0 = res_invlt.subs({t: 0.0})
res1 = res_invlt.subs({t: 0.1})
res2 = res_invlt.subs({t: 0.2})
res3 = res_invlt.subs({t: 0.3})

print(res0)
print(res1)
print(res2)
print(res3)


from __future__ import division
from math import *
import numpy as np

class LinearSolver():
    methods = ['GE', 'GE_pivoted', 'doolittle', 'crout', 'cholesky']

    def __init__(self, a, b):
        self.a = a
        self.b = b
        self.L = None
        self.U = None
        self.x = None

    @staticmethod
    def pivot(a, b, i):
        '''Partial Pivioting at i, i'''

        m = max(a[i:, i])
        for j in range(i + 1, a[0].size):
            if m == a[j, i]:
                a[..., i:][[i, j]] = a[..., i:][[j, i]]
                b[[i, j]] = b[[j, i]]
                break

    @staticmethod
    def solve(a, b):
        '''Expected a to be upper triangular square matrix'''

        n = b.size
        x = np.ndarray(shape=b.shape, dtype=b.dtype)
        for i in range(n - 1, -1, -1):
            x[i] = (b[i] - a[i, i + 1:].dot(x[i + 1:])) / a[i, i]
        return x

    @staticmethod
    def LU(a, l, u):
        '''LU decomposition by diag(1) as ones'''

        n = a[0].size
        for i in range(n):
            u[i, i:] = a[i, i:] - l[i, :i].dot(u[:i, i:])
            l[i + 1:, i] = a[i + 1:, i] - l[i + 1:, :i + 1].dot(u[:i + 1, i])
            l[i + 1:, i] /= u[i, i]

    @staticmethod
    def solveLU(l, u, b):
        '''Solves for x when L & U are given
        Depends on LinearSolver.solve'''

        tempx = LinearSolver.solve(l[::-1, ::-1], b[::-1])
        return LinearSolver.solve(u, tempx[::-1])

    def GE(self, pivot=0):
        a = self.a.copy()
        b = self.b.copy()
        n = b.size
        for i in range(0, n - 1):
            if pivot == 1:
                LinearSolver.pivot(a, b, i)

            for j in range(i + 1, n):
                b[j] -= b[i] / a[i, i] * a[j, i]
                a[j] -= a[i] / a[i, i] * a[j, i]

        self.x = LinearSolver.solve(a, b)

    def GE_pivoted(self):
        self.GE(1)

    def doolittle(self):
        self.L, self.U = np.identity(self.b.size), np.identity(self.b.size)

        LinearSolver.LU(self.a, self.L, self.U)
        self.x = LinearSolver.solveLU(self.L, self.U, self.b)

    def crout(self):
        self.L, self.U = np.identity(self.b.size), np.identity(self.b.size)

        LinearSolver.LU(self.a.transpose(), self.L, self.U)
        self.L, self.U = self.U.transpose(), self.L.transpose()
        self.x = LinearSolver.solveLU(self.L, self.U, self.b)

    def cholesky(self):
        n = self.b.size
        self.L = np.identity(n)
        a = self.a
        l, u = self.L, self.L.transpose()
        for i in range(n):
            l[i, i] = sqrt(a[i, i] - np.sum(l[i, :i]**2))
            l[i + 1:, i] = a[i + 1:, i] / l[i, i]
        self.x = LinearSolver.solveLU(l, u, self.b)

def main():
    method_names = ['GE; without pivoting', 'GE; with patial pivoting',
                    'LU decomposition by Doolittle method',
                    'LU decomposition by Crout method',
                    'Cholesky decomposition (for symmetric positive definite matrix)']

    with open("q1.txt", "r") as f:
        n = int(f.readline())
        a, b = np.ndarray(shape=(n, n)), np.ndarray(shape=(n))
        for i, line in enumerate(f):
            a[i] = map(float, line.split()[:-1])
            b[i] = float(line.split()[-1])

    for i, name in enumerate(method_names):
        print 'press ', i, 'for ', name
    choice = int(raw_input())

    solver = LinearSolver(a, b)
    getattr(solver, LinearSolver.methods[choice])()

    with open("out.txt", "w") as fout:
        if solver.x is not None:
            np.savetxt(fout, solver.x, fmt='%f',
                       header='x', footer='\n', comments='')

        if solver.L is not None:
            np.savetxt(fout, solver.L, fmt='%f',
                       header='L', footer='\n', comments='')

        if solver.U is not None:
            np.savetxt(fout, solver.U, fmt='%f',
                       header='U', footer='\n', comments='')

if __name__ == '__main__':
    main()


"""
60. How to create a new column that contains the row number of nearest column by euclidean distance?
"""
"""
Create a new column such that, each row contains the row number of nearest row-record by euclidean distance.
"""
"""
Difficulty Level: L3
"""
"""
Input
"""
"""
df = pd.DataFrame(np.random.randint(1,100, 40).reshape(10, -1), columns=list('pqrs'), index=list('abcdefghij'))
df
#     p   q   r   s
# a  57  77  13  62
# b  68   5  92  24
# c  74  40  18  37
# d  80  17  39  60
# e  93  48  85  33
# f  69  55   8  11
# g  39  23  88  53
# h  63  28  25  61
# i  18   4  73   7
# j  79  12  45  34
"""
"""
Desired Output
"""
"""
df
#    p   q   r   s nearest_row   dist
# a  57  77  13  62           i  116.0
# b  68   5  92  24           a  114.0
# c  74  40  18  37           i   91.0
# d  80  17  39  60           i   89.0
# e  93  48  85  33           i   92.0
# f  69  55   8  11           g  100.0
# g  39  23  88  53           f  100.0
# h  63  28  25  61           i   88.0
# i  18   4  73   7           a  116.0
# j  79  12  45  34           a   81.0
"""

df = pd.DataFrame(np.random.randint(1,100, 40).reshape(10, -1), columns=list('pqrs'), index=list('abcdefghij'))

# Solution
import numpy as np

# init outputs
nearest_rows = []
nearest_distance = []

# iterate rows.
for i, row in df.iterrows():
    curr = row
    rest = df.drop(i)
    e_dists = {}  # init dict to store euclidean dists for current row.
    # iterate rest of rows for current row
    for j, contestant in rest.iterrows():
        # compute euclidean dist and update e_dists
        e_dists.update({j: round(np.linalg.norm(curr.values - contestant.values))})
    # update nearest row to current row and the distance value
    nearest_rows.append(max(e_dists, key=e_dists.get))
    nearest_distance.append(max(e_dists.values()))

df['nearest_row'] = nearest_rows
df['dist'] = nearest_distance


from __future__ import print_function
# or integrate(y, (x, -3.25, 7.32))

from sympy import *
from pyDOE import lhs
import matplotlib.pyplot as plt

# ========== HW10 SOLUTION [Python2/3] ========== #

# ========== 1 ========== #

x, y = symbols('x y')
w = 3.0*x**2*y + 2*y + y*sin(x)
ans = w.diff(x)
# or diff(w, x)
print(ans)

# ========== 2 ========== #

y = 3.3*x**6 + 1.7*x**4 - 7.3*x**3 + 6.7*x
ans = y.integrate((x, -3.25, 7.32))
# or integrate(y, (x, -3.25, 7.32))
print(ans)

# ========== 3 ========== #

# option 1

y = 5.0*x**8 + 3.0*x**3 + 3.0*x**2 - 2.5*x + 1.1
ans = solve(y, x)

# option 2

eqn = Eq(5.0*x**8 + 3.0*x**3 + 3.0*x**2 - 2.5*x + 1.1, 0)
ans = solve(eqn, x)

print(ans)

# ========== 4 ========== #

m = Symbol('m')

M = Matrix([[m**2, 2.0*m, 1.0, 2.3],
            [m, 2.3, -5.0, 2.1],
            [2.1, 73.0, -56.0, 1.1],
            [-12.0, -1.0, 13.0, m]])

ans = M.det()
# or det(M)
print(ans)

# optionally, this is how to fix the rounding issue:

M_det_fix = M.det()
for x in postorder_traversal(M.det()):
    if isinstance(x, Float):
        M_det_fix = M_det_fix.subs(x, round(x, 1))

# ========== 5 ========== #

lhd = lhs(2, samples=10, criterion='c')
x_1 = lhd[:,0]
x_2 = lhd[:,1]

plt.figure()
plt.plot(x_1,x_2, 'o')
plt.grid(True)
plt.xlim((0,1))
plt.ylim((0,1))
plt.show()


import numpy as np
import matplotlib.pyplot as plt

import matplotlib as mpl
import matplotlib.animation as an

import networkx as nx

#Butcher Tableau
alpha = np.array([0.0, 1.0/4.0, 3.0/8.0, 12.0/13.0, 1.0, 1.0/2.0])
beta = np.array([[0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
		[1.0/4.0, 0.0, 0.0, 0.0, 0.0, 0.0],
		[3.0/32.0, 9.0/32.0, 0.0, 0.0, 0.0, 0.0],
		[1932.0/2197.0, (-7200.0)/2197.0, 7296.0/2197.0, 0.0, 0.0, 0.0],
		[439.0/216.0, -8.0, 3680.0/513.0, (-845.0)/4104.0, 0.0, 0.0],
		[(-8.0)/27.0, 2.0, (-3544.0)/2565.0, 1859.0/4104.0, (-11.0)/40.0, 0.0]])
c = np.array([25.0/216.0, 0.0, 1408.0/2565.0, 2197.0/4104.0, (-1.0)/5.0, 0.0]) # coefficients for 4th order method
c_star = np.array([16.0/135.0, 0.0, 6656.0/12825.0, 28561.0/56430.0, (-9.0)/50.0, 2.0/55.0]) # coefficients for 5th order method
cerr=c-c_star

def rkf(f,x,h,alpha,beta,cerr,c_star,*args,**kwargs):
    '''
    To perform integration stepwise using the Runge-Kutta-Fehlberg 4(5) method with adaptive step size control
    Parameters
    ----------
    f : function
        calculating the rhs of the ode
    x : numpy array
        initial state of the system
    h : float
        initial stepsize
    alpha : numpy array
        alpha values of the RK-4(5) method
    beta : numpy array
        beta values of the RK-4(5) method
    cerr : numpy array
        difference in the c cooefficients of the RK-4 and RK-5 methods
    c_star : numpy array
        c values of the RK-5 method
    *args : additional arguments to passdown to function f
    **kwargs : additional keywork arguments to pass down to function f
    Returns
    -------
    X : numpy array
        calculated values of the system for each time step
    T : numpy array
        time values corresponding to each time step in the system
    H : numpy array
        adaptive step size values for every calculated step
    EPS : numpy array
        error epsilon at each step(between the calculated values from RK-4 and RK-5 method)
    '''
    T = np.zeros(N_t)
    X = np.zeros((N,N_t))
    H = np.zeros(N_t)
    EPS = np.zeros(N_t)
    X[:,0] = x
    H[0] = h

    i = 0
    while i < N_t-1 :
        k1 = f(X[:,i],*args,**kwargs)
        k2 = rhs(X[:,i]+h*beta[1,0]*k1, *args,**kwargs)
        k3 = rhs(X[:,i]+h*(beta[2,0]*k1 + beta[2,1]*k2),*args,**kwargs)
        k4 = rhs(X[:,i]+h*(beta[3,0]*k1 + beta[3,1]*k2 + beta[3,2]*k3),*args,**kwargs)
        k5 = rhs(X[:,i]+h*(beta[4,0]*k1 + beta[4,1]*k2 + beta[4,2]*k3 + beta[4,3]*k4),*args,**kwargs)
        k6 = rhs(X[:,i]+h*(beta[5,0]*k1 + beta[5,1]*k2 + beta[5,2]*k3 + beta[5,3]*k4 + beta[5,4]*k5),*args,**kwargs)
        errorfield = h*(cerr[0]*k1 + cerr[1]*k2 + cerr[2]*k3 + cerr[3]*k4 + cerr[4]*k5 + cerr[5]*k6)
        max_error = np.absolute(errorfield).max()

        if (max_error <= tol):
            X[:,i+1] = X[:,i] + h*(c_star[0]*k1 + c_star[1]*k2 + c_star[2]*k3 + c_star[3]*k4 + c_star[4]*k5 + c_star[5]*k6)
            h = safe * h* (tol/max_error)**0.2
            H[i+1]=h
            T[i+1]=T[i]+h
            EPS[i+1]=max_error
            i+=1
        else:
            h=safe*h*(tol/max_error)**0.25

    return X,T,H,EPS

def rhs(x,A,d,u,al,gm,b):
    '''
    TO calculate the RHS of the given ode
    Parameters
    ----------
    x : numpy array
        curent state of the system
    A : numpy array
        the adjacency matrix of the system graph
    d : float
        parameter, resistance to becoming opinionated
    u : float
        control parameter, social influence
    al : float
        parameter, self reinforcement
    gm : float
        parameter, cooperative/competitive
    b : float
        parameter, input bias
    Returns
    -------
    x_dot : numpy array
        RHS of the ode. The derivative of the current state.
    '''
    x1=np.zeros(np.shape(x))
    for i in range(np.shape(A)[0]):
        for j in range(np.shape(A)[1]):
            if (i!=j):
                x1[i]+=gm*A[i,j]*x[j]
            else:
                x1[j]+=al*x[j]
    x_dot=-d*x+u*np.tanh(x1)+b
    return x_dot

N_t = 200  #no. of steps taken in the adaptive step size
N = 10   #no. of nodes
tol = 0.0000001     #desired accuracy/tolerance
safe = 0.84     #safety factor

#Adjacency Matrix :

A=np.zeros((N,N))

#TOPOLOGIES

# #Circle
# for i in range(N-1):
#     A[i,i+1]=1
#     A[i+1,i]=1

#     A[N-1,0]=1
#     A[0,N-1]=1

# #Wheel
# for i in range(N-2):
#     A[i,i+1]=1
#     A[i+1,i]=1
#     A[i,N-1]=1
#     A[N-1,i]=1
# A[N-2,N-1]=1
# A[N-1,N-2]=1
# A[N-2,0]=1
# A[0,N-2]=1

# #Path
# for i in range(N-1):
#     A[i,i+1]=1
#     A[i+1,i]=1

#Star
for i in range(N-1):
    A[i,N-1]=1
    A[N-1,i]=1

#Plotting :

h_in = 0.05 #initial stepsize
x0=(np.random.uniform(-1.0,1.0,size=N)) #inital random state of the system

x,t,h_val,err_val = rkf(rhs,x0,h_in,alpha,beta,cerr,c_star,A,d=0.5,u=0.26,al=1.2,gm=1.3,b=0.0)
#Disagreement when gm(gamma) is negative and Agreement when gm(gamma) is positive

# for i in range(N):
#     plt.plot(t,x[i,:])

# plt.xlabel("time t")
# plt.ylabel("opinions x")
# plt.title("Star Topology Disagreement")
# #plt.savefig("Star_Topology_Disagreement.jpg")
# plt.show()

# plt.plot(t,h_val)
# plt.xlabel("time t")
# plt.ylabel("stepsize h")
# plt.title("Adaptive Step Size")
# plt.show()

# plt.plot(t,err_val)
# plt.xlabel("time t")
# plt.ylabel("error $\epsilon$")
# plt.title("Change in error")
# plt.show()

#Graph :

rows,cols=np.where(A==1.)
edges=zip(rows.tolist(),cols.tolist())
G=nx.Graph()
G.add_edges_from(edges)
plot_positions=nx.drawing.spring_layout(G)

vmin=-1
vmax=1
norm=mpl.colors.Normalize(vmin=vmin,vmax=vmax)
cmap=plt.get_cmap('coolwarm')
sm=plt.cm.ScalarMappable(cmap=cmap,norm=norm)
sm.set_array([])

#Animation. Save Video

fig=plt.gcf()

def animate(i):
    '''
    Function to iterate over in order to animate the graphs
    Parameters
    ----------
    i : int
        interative variable for the FuncAnimation() function to animate the graph
    Returns
    -------
    None.
    '''
    nx.draw(G,pos=plot_positions,node_size=500,node_color=x[:,i],cmap='coolwarm',vmin=vmin,vmax=vmax)

plt.colorbar(sm)
anim = an.FuncAnimation(fig, animate, frames=200, blit=False)
writervideo = an.FFMpegWriter(fps=10)
anim.save('Star_Topology_Agreement.mp4', writer=writervideo)


from elegy import types
import typing as tp

import jax.numpy as jnp

from elegy.losses.loss import Loss, Reduction

def huber(y_true: jnp.ndarray, y_pred: jnp.ndarray, delta: float) -> jnp.ndarray:
    r"""
    Computes the Huber loss between labels and predictions.

    For each value x in error = y_true - y_pred:

    $$
    loss =
    \begin{cases}
    \ 0.5 \times x^2,\hskip8em\text{if } |x|\leq d\\
    0.5 \times d^2 + d \times (|x| - d),\hskip1.7em \text{otherwise}
    \end{cases}
    $$

    where d is delta. See: https://en.wikipedia.org/wiki/Huber_loss

    Usage:

    ```python
    rng = jax.random.PRNGKey(42)

    y_true = jax.random.randint(rng, shape=(2, 3), minval=0, maxval=2)
    y_pred = jax.random.uniform(rng, shape=(2, 3))

    loss = elegy.losses.huber(y_true, y_pred, delta=1.0)
    assert loss.shape == (2,)

    y_pred = y_pred.astype(float)
    y_true = y_true.astype(float)
    delta = 1.0
    error = jnp.subtract(y_pred, y_true)
    abs_error = jnp.abs(error)
    quadratic = jnp.minimum(abs_error, delta)
    linear = jnp.subtract(abs_error, quadratic)
    assert jnp.array_equal(loss, jnp.mean(
      jnp.add(
          jnp.multiply(
              0.5,
              jnp.multiply(quadratic, quadratic)
              ),
              jnp.multiply(delta, linear)), axis=-1
    ))
    ```

    Arguments:
        y_true: Ground truth values. shape = `[batch_size, d0, .. dN]`.
        y_pred: The predicted values. shape = `[batch_size, d0, .. dN]`.
        delta: A float, the point where the Huber loss function changes from a quadratic to linear.

    Returns:
          huber loss Values. If reduction is NONE, this has
         shape [batch_size, d0, .. dN-1]; otherwise, it is scalar.
         (Note dN-1 because all loss functions reduce by 1 dimension, usually axis=-1.)
    """
    y_pred = y_pred.astype(float)
    y_true = y_true.astype(float)
    delta = float(delta)
    error = jnp.subtract(y_pred, y_true)
    abs_error = jnp.abs(error)
    quadratic = jnp.minimum(abs_error, delta)
    linear = jnp.subtract(abs_error, quadratic)
    return jnp.mean(
        jnp.add(
            jnp.multiply(0.5, jnp.multiply(quadratic, quadratic)),
            jnp.multiply(delta, linear),
        ),
        axis=-1,
    )

class Huber(Loss):
    r"""
    Computes the Huber loss  between labels and predictions.

    For each value x in error = y_true - y_pred:

    $$
    loss =
    \begin{cases}
    \ 0.5 \times x^2,\hskip8em\text{if } |x|\leq d\\
    0.5 \times d^2 + d \times (|x| - d),\hskip1.7em \text{otherwise}
    \end{cases}
    $$

    where d is delta. See: https://en.wikipedia.org/wiki/Huber_loss

    Usage:

    ```python
    y_true = jnp.array([[0, 1], [0, 0]])
    y_pred = jnp.array([[0.6, 0.4], [0.4, 0.6]])

    # Using 'auto'/'sum_over_batch_size' reduction type.
    huber_loss = elegy.losses.Huber()
    assert huber_loss(y_true, y_pred) == 0.155

    # Calling with 'sample_weight'.
    assert (
        huber_loss(y_true, y_pred, sample_weight=jnp.array([0.8, 0.2])) == 0.08500001
    )

    # Using 'sum' reduction type.
    huber_loss = elegy.losses.Huber(
        reduction=elegy.losses.Reduction.SUM
    )
    assert huber_loss(y_true, y_pred) == 0.31

    # Using 'none' reduction type.
    huber_loss = elegy.losses.Huber(
        reduction=elegy.losses.Reduction.NONE
    )

    assert jnp.equal(huber_loss(y_true, y_pred), jnp.array([0.18, 0.13000001])).all()
    ```
    Usage with the Elegy API:

    ```python
    model = elegy.Model(
        module_fn,
        loss=elegy.losses.Huber(delta=1.0),
        metrics=elegy.metrics.Mean(),
    )
    ```
    """

    def __init__(
        self,
        delta: float = 1.0,
        reduction: tp.Optional[Reduction] = None,
        weight: tp.Optional[float] = None,
        on: tp.Optional[types.IndexLike] = None,
        **kwargs
    ):
        """
        Initializes `Mean` class.

        Arguments:
            delta: (Optional) Defaults to 1.0. A float, the point where the Huber loss function changes from a quadratic to linear.
            reduction: (Optional) Type of `elegy.losses.Reduction` to apply to
                loss. Default value is `SUM_OVER_BATCH_SIZE`. For almost all cases
                this defaults to `SUM_OVER_BATCH_SIZE`.
            weight: Optional weight contribution for the total loss. Defaults to `1`.
            on: A string or integer, or iterable of string or integers, that
                indicate how to index/filter the `y_true` and `y_pred`
                arguments before passing them to `call`. For example if `on = "a"` then
                `y_true = y_true["a"]`. If `on` is an iterable
                the structures will be indexed iteratively, for example if `on = ["a", 0, "b"]`
                then `y_true = y_true["a"][0]["b"]`, same for `y_pred`. For more information
                check out [Keras-like behavior](https://poets-ai.github.io/elegy/guides/modules-losses-metrics/#keras-like-behavior).
        """
        self.delta = delta
        return super().__init__(reduction=reduction, weight=weight, on=on, **kwargs)

    def call(
        self,
        y_true: jnp.ndarray,
        y_pred: jnp.ndarray,
        sample_weight: tp.Optional[
            jnp.ndarray
        ] = None,  # not used, __call__ handles it, left for documentation purposes.
    ) -> jnp.ndarray:
        """
        Invokes the `Huber` instance.

        Arguments:
            y_true: Ground truth values. shape = `[batch_size, d0, .. dN]`, except
                sparse loss functions such as sparse categorical crossentropy where
                shape = `[batch_size, d0, .. dN-1]`
            y_pred: The predicted values. shape = `[batch_size, d0, .. dN]`
            sample_weight: Optional `sample_weight` acts as a
                coefficient for the loss. If a scalar is provided, then the loss is
                simply scaled by the given value. If `sample_weight` is a tensor of size
                `[batch_size]`, then the total loss for each sample of the batch is
                rescaled by the corresponding element in the `sample_weight` vector. If
                the shape of `sample_weight` is `[batch_size, d0, .. dN-1]` (or can be
                broadcasted to this shape), then each loss element of `y_pred` is scaled
                by the corresponding value of `sample_weight`. (Note on`dN-1`: all loss
                functions reduce by 1 dimension, usually axis=-1.)

        Returns:
            Weighted loss float `Tensor`. If `reduction` is `NONE`, this has
                shape `[batch_size, d0, .. dN-1]`; otherwise, it is scalar. (Note `dN-1`
                because all loss functions reduce by 1 dimension, usually axis=-1.)

        Raises:
            ValueError: If the shape of `sample_weight` is invalid.
        """
        return huber(y_true, y_pred, self.delta)


import numpy as np
from matplotlib import pyplot as plt
import seaborn as sns
import pandas as pd

iris = sns.load_dataset("iris")

x = iris.iloc[:,0:4].values
print(x.shape)
y = iris.iloc[:,4].values
print(y.shape)
print(y)

label = []
# 0 : 'setosa'
# 1 : 'versicolor'
# 2 : 'virginica'

for name in y:
    if name == 'setosa':
        label.append(0)
    elif name == 'versicolor':
        label.append(1)
    elif name == 'virginica':
        label.append(2)

print(label)
print(x)

# one-hot encoding
num = np.unique(label, axis=0)
num = num.shape[0]

encoding = np.eye(num)[label]
print(encoding)

y = encoding.copy()

# 붓꽃 데이터 : 4개
# 클래스 개수 : 3개
dim = 4
nb_classes = 3

print('x shape: ', x.shape, 'y shape: ', y.shape)
# x : (150, 4)
# y : (150, 3)

# w : (4, 3)
# b : (3,)
w = np.random.normal(size=[dim, nb_classes])
b = np.random.normal(size=[nb_classes])

print('w shape: ', w.shape, 'b shape: ', b.shape)

# x (150, 4) * w (4, 3) + b (150, 3)(broad-casting) => predict(150, 3) <=> y_hot(150, 3)

def cross_entropy_error(predict, target):
    delta = 1e-7
    return -np.mean(target * np.log(predict + delta))

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

hypothesis = softmax(np.dot(x, w) + b)
print('hypothesis: ', hypothesis.shape)

# hypothesis:  (150, 3)

eps = 1e-7

# 방법 1
cost = y * np.log(hypothesis + eps)
cost = -cost.mean()
print('cost: ', cost)

# 방법 2
cost = cross_entropy_error(hypothesis, y)
print('cost: ', cost)


from __future__ import print_function

from sympy import Symbol, symbols, sin, cos, Rational, expand, simplify, collect
from sympy import Rational as Rat
from galgebra.printer import Format, Eprint
from galgebra.ga import Ga
from math import sqrt

global n,nbar,I

def radius(T):
    '''
    This retrieves the radius from a trivector representing a circle
    '''
    a=(T*T).scalar()
    b=((T^n)*(T^n)).scalar()
    return (-1*a/b)**0.5

def center(T):
    global n
    '''returns the center of a given circle trivector'''
    return T*n*T

def split_bivector(B):
    global ebar
    '''Implements the algorithm described in Doran and Lasenby to recover null vectors wedging to B'''
    print('B =',B)
    print('B**2 =',B*B)
    NB = B.norm()
    print('NB =',NB)
    Bh = B/NB
    ap = ebar - ((ebar^Bh)*Bh)
    a1 = ap + (ap*Bh)
    a2 = ap - (ap*Bh)
    #print '#a1 = ',a1
    #print '#a2 = ',a2
    return [a1,a2]

def norm(X):
    Y=sqrt((X*X).scalar())
    return Y

Eprint()

g='1 0 0 0, \
   0 1 0 0, \
   0 0 0 2, \
   0 0 2 0'

c2d = Ga('e_1 e_2 n \\bar{n}',g=g)
(e1,e2,n,nbar) = c2d.mv()

def F(x):
    global n,nbar
    Fx = ((x*x)*n+2*x-nbar) / 2
    return(Fx)

e = (n+nbar)/2
ebar = n - e
I=e1*e2*e*ebar

def intersect_lines(L1,L2):
    global I
    '''
    Computes the intersection bivector of two conformal lines L1, L2
    '''
    C = I*((I*L1)^(I*L2))
    return C

A=F(Rat(1,2)*e1)
B=F(2*e1)
C=F(Rat(4,5)*e1+Rat(3,5)*e2)
D=F(Rat(4,5)*e1-Rat(3,5)*e2)

print('A =',A)
print('B =',B)
print('C =',C)
print('D =',D)

T=A^B^C
print('T =',T)
U=F(e1)^(F(e2))^F(-1*e1)
print('U =',U)
inter=intersect_lines(U,T)
print('inter =',inter)

x,y = split_bivector(inter)

bases = (e1,e2)
print(x.proj(bases))
print(y.proj(bases))

print('One intersection point x = ',x)
print('The other intersection point y = ',y)
print('x**2 = ',x*x)
print('y**2 = ',y*y)
print('T^x = ',T^x)
print('T^y = ',T^y)
print('U^x = ',U^x)
print('U^y = ',U^y)