import numpy as np
import matplotlib.pyplot as plt
from statsmodels.tsa.stattools import acf
from scipy.stats import norm
import pandas as pd

def error(phi, n, mae, eps=0.1, hist=False, verbose=False):
    """
    Generates a random error vector of length `n`, that results
    in the mean absolute error (MAE) equal to `mae`.
    The error vector is based on the AR(1) model. The autocorrelation is
    controlled by the parameter `phi`. The first element of the output
    vector is 0 (no error). The higher `phi`, the higher chance of
    getting a vector with some bias.

    Since the output of the AR model is random, the function
    generates sample vectors in a loop until it finds a vector with MAE = mae.
    The standard deviation of the noise in the AR model is adapted
    to the aimed MAE so that the expected number of iterations is minimal.

    :parameter phi: float from 0 to 1, autocorrelation coefficient
    :parameter n: int, output vector length
    :parameter mae: float, mean absolute error
    :parameter eps: float, mae tolerance
    :parameter hist: bool, return also history of all guesses as a list
    :parameter verbose: bool, if True, print info on screen
    :return: 1D numpy array or a tuple (1D numpy array, list) if hist is True
    """
    if mae == 0:
        return np.zeros(n)

    # Initialize `mae_sample` with a high value,
    # so that the condition in `while` is not met
    mae_sample = mae + 2 * eps

    # Initialize the loop counter
    count = 0

    # Analytically derived standard deviation of the noise in AR(1),
    # which is supposed to minimize the number of iterations needed
    # to find the error vector for which MAE = mae. According to my
    # calculations, which might be incorrect (see presentation/derivation.jpg),
    # the optimal scaling factor k = 0.674. 0.674 is the third quartile of
    # the normal distribution, or the median of abs(normal_distribution).
    # This factor, however, works fine with small vectors (up to n = 1000).
    # I noticed that depending on phi, the function error() yields biased MAE
    # (sometimes too high, sometimes too low), suggesting there is something
    # wrong with the equation for sdw.
    #
    # So I implemented the following workaround:
    #   1. Use k=0.674 as first guess.
    #   2. Try ten times to get a vector with requested MAE.
    #   3. If unsuccesful, every next iteration slightly increase/decrease k,
    #      depending on whether past MAEs were too high/low.
    #
    # It works fine so far!!!
    #
    def sdw(k=0.674):
        return mae / (k * (phi / np.sqrt(1 - phi ** 2) + 1))

    mae_hist = list()
    n_iter = 0
    k = 0.674  # First guess

    while abs(mae_sample - mae) >= eps:
        count += 1

        # Initialize error vector
        e = np.zeros(n)

        for i in range(1, n, 1):
            # Add next vector element using the AR(1) model
            e[i] = e[i-1] * phi + np.random.normal(loc=0, scale=sdw(k), size=1)[0]

        # Check tolerance
        mae_sample = np.mean(np.abs(e))

        mae_hist.append(mae_sample)

        # Too many unsuccessful iterations,
        # try to modify k
        if n_iter > 10:
            mean_mae = np.mean(mae_hist)
            # If past MAEs are too low, decrease k
            if mean_mae < mae:
                k = k * 0.99
            # If pas MAEs are too high, increase k
            elif mean_mae > mae:
                k = k * 1.01

            if verbose:
                print("New k = {}".format(k))

        if verbose:
            print("Error generation try number {} >> MAE = {}".format(count, mae_sample))

        n_iter += 1

    if hist:
        return e, mae_hist
    else:
        return e

if __name__ == "__main__":

    # ==============================================
    # DEMO
    # ==============================================

    # Generate and plot one error vector and its ACF
    # ==============================================
    phi1 = 0.99
    phi2 = 0.5
    n = 100
    mae = 1

    # Results for phi1
    e1 = error(phi1, n, mae, eps=0.01)
    mae1 = np.mean(np.abs(e1))
    ac1 = acf(e1, nlags=n)
    ci1 = norm.ppf((1 + 0.95)/2) / np.sqrt(e1.size)  # 95% confidence interval

    # Results for phi2
    e2 = error(phi2, n, mae, eps=0.01)
    mae2 = np.mean(np.abs(e2))
    ac2 = acf(e2, nlags=n)
    ci2 = norm.ppf((1 + 0.95)/2) / np.sqrt(e2.size)  # 95% confidence interval

    fig, axes = plt.subplots(2, 2, sharex=True, figsize=(10, 7))
    fig.set_dpi(120)

    ax = axes[0][0]
    ax.plot(e1)
    ax.set_title("Smooth error\n$\phi$ = {}, MAE = {:.2f}".format(phi1, mae1))
    ax.set_ylabel("Absolute error")
    ax.set_ylim(-5 * mae, 5 * mae)
    ax.plot(np.zeros(e1.size), 'k--', lw=1)

    ax = axes[1][0]
    ax.set_ylabel("ACF")
    ax.plot(ac1)
    ax.plot(np.full(e1.size, ci1), 'b--', lw=1)
    ax.plot(np.full(e1.size, -ci1), 'b--', lw=1)
    ax.plot(np.zeros(e1.size), 'k--', lw=1)
    ax.set_xlim(0, n)
    ax.set_xlabel('Error vector index')

    ax = axes[0][1]
    ax.plot(e2)
    ax.set_title("Almost white noise error\n$\phi$ = {}, MAE = {:.2f}".format(phi2, mae2))
    ax.set_ylabel("Absolute error")
    ax.set_ylim(-5 * mae, 5 * mae)
    ax.plot(np.zeros(e2.size), 'k--', lw=1)

    ax = axes[1][1]
    ax.set_ylabel("ACF")
    ax.plot(ac2)
    ax.plot(np.full(e2.size, ci2), 'b--', lw=1)
    ax.plot(np.full(e2.size, -ci2), 'b--', lw=1)
    ax.plot(np.zeros(e2.size), 'k--', lw=1)
    ax.set_xlim(0, n)
    ax.set_xlabel('Error vector index')

    plt.show()

    # PLOT HISTOGRAM OF MAEs FROM ALL ITERATIONS
    # =============================================
    # phi = 0.9
    # n = 10000
    # mae = 1

    # e, h = error(phi, n, mae, 0.01, hist=True, verbose=True)

    # plt.hist(h)
    # plt.show()

import numpy,from scipy,from statsmodels


# -*- coding: utf-8 -*-
"""
@Author: Zhixin Ling
@Description: Part of the project of Machine Learning: two PCA approaches.
"""
from sklearn.preprocessing import Normalizer
from numpy import linalg as LA
import numpy as np
import os

class PCA_ling:
    def __init__(self, alg='eig_solve', n_components=None):
        """
        @Param alg: should be one between 'eig_solve' and 'svd', using two approaches to solve PCA.
        @Param n_components: number of components to reduce the data to. If n_components is greater than dimension of
            the data or is not set, then n_components would be adjusted to the right dimensions of the data.
        """
        self.alg = alg
        self.normalizer = None
        self.n_components = n_components
        self.selected_var = None

    def _decentralize(self, data):
        means = np.repeat(np.mean(data,0),data.shape[0],0).reshape(data.shape[1],-1).T
        return data - means

    def _get_XtX(self, data):
        X = self._decentralize(data)
        return [np.matmul( X.T, X) * (1 / (data.shape[0]-1)) ,X]

    def _decide_components(self, data):
        self.n_components = data.shape[1] if self.n_components is None or self.n_components > data.shape[1] else self.n_components

    def _fit_transform_eig_solve(self, data):
        [XtX, X] = self._get_XtX(data)
        w, v = LA.eig(XtX)
        wv = list(zip(w, v.T))
        sorted_wv = sorted(wv, key=lambda x: -abs(x[0]))
        sorted_w = [abs(wv_i[0]) for wv_i in sorted_wv]
        sorted_v = [wv_i[1] for wv_i in sorted_wv]
        selected_vs = np.vstack(sorted_v[:self.n_components]).T
        self.selected_var = np.sum(sorted_w[:self.n_components]) / np.sum(sorted_w)
        return np.matmul(X, selected_vs)

    def _fit_transform_svd(self, data):
        [XtX, X] = self._get_XtX(data)
        U, ds, Vt = LA.svd(X)
        # XtX * V /{by_col} ds == U
        # eig vals of XtX are squared sigs of X
        # U are eigs of XXt, V are eigs of XtX
        selected_vs = Vt[:self.n_components].T
        ds **= 2
        self.selected_var = np.sum(ds[:self.n_components]) / np.sum(ds)
        return np.matmul(X, selected_vs)

    def fit_transform(self, data):
        self._decide_components(data)
        if self.alg == 'eig_solve':
            return self._fit_transform_eig_solve(data)
        elif self.alg == 'svd':
            return self._fit_transform_svd(data)
        else:
            raise Exception("Invalid algorithm alg="+self.alg+"\nalg should be one among eig_solve and svd\n")

    def selected_components(self):
        return self.selected_var

def _read_test_data():
    import data_helper
    import global_defs
    path_test_data = data_helper.npfilename(os.path.join(global_defs.PATH_SAVING_FOLDER, 'pca_test_data'))
    if os.path.exists(path_test_data):
        return np.load(path_test_data)
    test_data = data_helper.read_features(use_dl=True)[:20,:50]
    np.save(path_test_data, test_data)
    return test_data

def _test_PCA_ling():
    data = _read_test_data()
    n_components = 3
    print("ori_data.shape=", data.shape,"  n_components=", n_components)

    for alg in ['eig_solve', 'svd']:
        pca_ling = PCA_ling(alg=alg, n_components=n_components)
        reduced1 = pca_ling.fit_transform(data)
        print(alg, reduced1.shape, pca_ling.selected_components())
    #print(reduced1)

    from sklearn.decomposition import PCA
    pca_sklearn = PCA(n_components=n_components)
    reduced1 = pca_sklearn.fit_transform(data)
    print(reduced1.shape, np.sum(pca_sklearn.explained_variance_ratio_[:n_components]))
    #print(reduced1)

if __name__ == '__main__':
    _test_PCA_ling()


import tensorflow as tf
import matplotlib.pyplot as plt
from sklearn import linear_model
import numpy as np
import sys

def dz_dx(x,y):
    return (2*(x-2)) / ((25-(x-2)**2) - ((y - 3)**2))

def dz_dy(x,y):
    return (2*(y-3)) / ((25-(x-2)**2) - ((y - 3)**2))

def problem1():
    x_start = 0
    y_start = 0
    learning_rate = 0.01
    max_iterations = 10000
    epsilon = 0.000001
    history=[(x_start,y_start)]
    for i in range(max_iterations-1):
        dW = dz_dx(x_start,y_start)
        db = dz_dy(x_start,y_start)
        x_start1 = x_start - (learning_rate *dW)
        y_start1 = y_start - (learning_rate *db)
        if abs(x_start1 - x_start) <= epsilon and abs(y_start1 - y_start) <= epsilon:
            return i+1,x_start1, y_start1
        x_start = x_start1
        y_start = y_start1
    return -1,None,None

class Problem2():
    def __init__(self):
      pass
    def create_dataset(self):
        RANDOM_SEED = 42
        tf.set_random_seed(RANDOM_SEED)
        n_samples = 30
        self.train_x = np.linspace(0,20,n_samples)
        self.train_y = 3.7 * self.train_x + 14 + 4 * np.random.randn(n_samples)
        plt.plot(self.train_x,self.train_y)

    def sk_solution(self):
        x = np.reshape(self.train_x,(len(self.train_x),1))
        linreg = linear_model.LinearRegression()
        linreg.fit(x,self.train_y)
        return linreg.coef_,linreg.intercept_

    def dg_solution(self,m_target,b_target,learning_rate,epsilon):
        def dRSS_dm(m,b):
            return(-2*sum((self.train_y-m*self.train_x-b)*self.train_x))
        def dRSS_db(m,b):
            return(-2*sum((self.train_y-m*self.train_x-b)))
        m_start = 0
        b_start = 0
        iterations = 2500000
        history = [(m_start,b_start)]
        for i in range(iterations):
            dW = dRSS_dm(m_start,b_start)
            db = dRSS_db(m_start,b_start)
            m_start1 = m_start - (learning_rate * dW)
            b_start1 = b_start - (learning_rate * db)
            if abs(m_start1 - m_target) <= epsilon and abs(b_start1 - b_target) <= epsilon:
                return i,m_start1,b_start1
            m_start = m_start1
            b_start = b_start1
        return -1,None,None

def main():
    print("Problem 1")
    print("=========")
    iterations,slope,intercept = problem1()
    print("  ",iterations,"iterations to reach a slope =",slope,"and intercept =",intercept)

    print("Problem 2")
    print("=========")
    problem2 = Problem2()
    problem2.create_dataset()
    m_target,b_target = problem2.sk_solution()
    print("   Scikit-learn slope =",m_target[0],"and intercept =",b_target)
    learning_rate = 0.0001
    iterations,slope,intercept = problem2.dg_solution(m_target,b_target,learning_rate,0.0000000001)
    print("  ",iterations,"DG iterations with",("%f" % learning_rate),"learning rate to reach  m =",slope,"and b =",intercept)
    learning_rate = 0.00001
    iterations,slope,intercept = problem2.dg_solution(m_target,b_target,learning_rate,0.0000000001)
    print("  ",iterations,"DG iterations with",("%f" % learning_rate),"learning rate to reach  m =",slope,"and b =",intercept)
    learning_rate = 0.000001
    iterations,slope,intercept = problem2.dg_solution(m_target,b_target,learning_rate,0.0000000001)
    print("  ",iterations,"DG iterations with",("%f" % learning_rate),"learning rate to reach  m =",slope,"and b =",intercept)

if __name__ == "__main__":
    main()


"""
MÉTODO DE BISECCIÓN
Blanca Cano Camarero
Granada 10/IV/20
"""

import numpy as np

def calcIterations (a:float, b:float, error:float, maxIter:int = 10000 ) -> int:
    """Devuele el mínimo entre el número de iteraciones necesarias para alcanzar como máximo un error  en bisección
    y un máximo intruducido (que por defecto es 10000
    """
    return min(maxIter,np.ceil(np.log((b-a)/error)/np.log(2)))

## bisection method

def bisection(f,a:float,b:float,error = 10**(-5), tolerance=10**(-13)) -> tuple:
    n = int(calcIterations(a,b,error)  )
    for i in range (n):
        xs = (b+a)/2
        evaluation = f(xs)
        if abs(evaluation) < tolerance:
            return xs, 'tolerance'
        elif evaluation * f(a) > 0:
            a = xs
        else:
            b = xs
    return xs,'iteration'


import math
from scipy.optimize import brentq
from scipy.stats import norm

def compute_european_price_Buehler(strike:float, maturity:float, volatility:float, is_call: bool=True)->float:
    """Compute a call/put option price for the Buehler model (w.r.t. x-process), i.e. no dividends, rates etc.
    Args:
        strike (float): strike
        maturity (float): maturity
        volatility (float): volatility
        is_call (bool): if option is call or put
    Returns:
        float: Black-Scholes call price
    """
    sqrt_mat = math.sqrt(maturity)
    d1 = ( math.log( 1.0 / strike ) + 0.5*volatility*volatility*maturity ) / ( volatility *  sqrt_mat)
    d2 = d1 - volatility * sqrt_mat
    #print(d1,d2)
    if is_call:
        return  norm.cdf(d1) - strike * norm.cdf(d2)
    return -norm.cdf(-d1) + strike*norm.cdf(-d2)

def compute_implied_vol_Buehler(strike: float, maturity:float, price:float,
                                min_vol = 0.05, max_vol = 2.0, is_call=True, **kwargs)->float:
    """Computes the implied volatility for a given cal/putl price using brentq from scipy. It throws an exception if no implied vol can be determined.

    Args:
        strike (float): [description]
        maturity (float): [description]
        price (float): [description]
        min_vol (float, optional): [description]. Defaults to 0.05.
        max_vol (float, optional): [description]. Defaults to 2.0.
         is_call (bool): if option is call or put

    Returns:
        float: [description]
    """
    def error(vol:float):
        return price - compute_european_price_Buehler(strike, maturity, vol, is_call= is_call)
    return brentq(error,a=min_vol, b=max_vol, **kwargs)


import numpy as np
import seaborn as sns

def coefficient(X, Y):
    """ Pearson correlation coefficient
                         n * sum(XY) - sum(X)sum(Y)
    r_XY =  ---------------------------------------------------------
            sqrt([n * sum(X^2) - sum(X)^2] [n * sum(Y^2) - sum(Y)^2])
    """
    def deno(data):
        """denominator helper"""
        n = data.shape[0]
        sum_of_square = np.sum(data ** 2)
        square_of_sum = np.sum(data) ** 2
        return n * sum_of_square - square_of_sum
    n = X.shape[0]
    numerator = n * np.sum(X * Y) - np.sum(X) * np.sum(Y)
    denominator = np.sqrt(deno(X)) * np.sqrt(deno(Y)) # avoid overflow
    return numerator / denominator

def demo():
    X = np.array([2, 4, 5, 6, 4, 7, 8, 5, 6, 7])
    Y = np.array([3, 2, 6, 5, 3, 6, 5, 4, 4, 5])
    R_xy = coefficient(X, Y)
    sns.scatterplot(x=X, y=Y)

def correlation_coefficient_test():
    """if we check two variable (not two group) relationship, we can
    use correlation coefficient test"""
    marriage_quality = np.array([
        76, 81, 78, 76, 76, 78, 76, 78, 98, 88, 76, 66, 44,
        67, 65, 59, 87, 77, 79, 85, 68, 76, 77, 98, 99, 98,
        87, 67, 78,
    ])
    parent_children_relation = np.array([
        43, 33, 23, 34, 31, 51, 56, 43, 44, 45, 32, 33, 28,
        39, 31, 38, 21, 27, 43, 46, 41, 41, 48, 56, 55, 45,
        68, 54, 33,
    ])
    coefficient_ = coefficient(marriage_quality, parent_children_relation)
    # left the same problem with anova.py
    # never mind
    return coefficient_

if __name__ == '__main__':
    demo()


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

def find_cri_x(alpha1, alpha2):
    x_star=(alpha1-1)/(alpha1+alpha2-2)
    return x_star

def find_fx(alpha1, alpha2,x):
    beta=(math.gamma(alpha1)*math.gamma(alpha2))/math.gamma(alpha1+alpha2)
    fx=(pow(x,alpha1-1)*pow(1-x,alpha2-1))/beta
    return fx

def acc_rej_for_betadist(alpha1, alpha2):
    Ran_list=[]
    c=find_fx(alpha1,alpha2,find_cri_x(alpha1,alpha2)) # the maxima pt value of PDF of Beta Dist.
    for i in range(0,100000): #generating 100000 values
        while(1):
            u1=random.uniform(0,1) #u1~U[0,1]
            u2=random.uniform(0,1) #u2~U[0,1]
            fu1=find_fx(alpha1,alpha2,u1) #f(u1) for u1~U[0,1]
            if(c*u2<=fu1):
                Ran_list.append(u1)
                break
    hist_for_ranlist(Ran_list)
    plt.show()

def hist_for_ranlist(l):
    l.sort()
    plt.title('RVs generated from Beta distribution function')
    plt.xlabel('Values')
    plt.ylabel('Frequency')
    counts,bins,_=plt.hist(l,bins=50,rwidth=0.2)
    bins=(bins[:-1]+bins[1:])/2
    plt.plot(bins,counts)

alpha1=[1,2,3,4,5]
alpha2=[5,4,3,2,1]

for i in range(0,5):
    acc_rej_for_betadist(alpha1[i],alpha2[i])

Semester 5/MA 323 (Monte Carlo)/Lab Submissions/Lab 4/180123062_ABSatyaprakash_q1.py


"""
The clustering coefficient of a node measures the interconnectivity between the
nodes nearby (here, nearby means connected by an edge). In effect, it measures
how close the neighboring nodes are to forming a complete network or clique.

The clustering coefficient of a node measures the proportion of the adjacent
nodes that are connected by an edge; that is, two adjacent nodes form a
triangle with the given node. We count the number of triangles and divide
this by the total number of possible triangles that could be formed, given
the degree of the node. Numerically, the clustering coefficient at a node,
u, in a simple unweighted network is given by the following equation:

    c_u = (2 * T_u) / (deg(u) * deg(u) - 1)

Where T_u is the number of triangles at u and the denominator is the total
possible number of triangles at u.
"""
import networkx as nx
import matplotlib.pyplot as plt

complete_part = nx.complete_graph(4)
cycle_part = nx.cycle_graph(range(4, 9))

G = nx.Graph()
G.update(complete_part)
G.update(cycle_part)
G.add_edges_from([(0, 8), (3, 4)])

fig, ax = plt.subplots()
nx.draw_circular(G, ax=ax, with_labels=True)
ax.set_title("Network with different clustering behaviour")

cluster_coeffs = nx.clustering(G)

for i in [0, 2, 6]:
    print(f"Node {i}, clustering {cluster_coeffs[i]}")
    # Node 0, clustering 0.5
    # Node 2, clustering 1.0
    # Node 6, clustering 0

av_clustering = nx.average_clustering(G)
print(av_clustering)
# 0.3333333333333333

plt.show()


# -*- coding: utf-8 -*-
"""
Created on Mon Sep 30 10:29:18 2019

@author: hamil
"""

import numpy as np
import matplotlib.pyplot as plt
import numpy.linalg as lin
import scipy.linalg as sclin

#create matrix
A = [[4,-2,1],[3,6,-4],[2,1,8]]
A = np.array(A)
print("Initial Matrix:", A)
#compute determinate of matrix
a = A[0][0]
b = A[0][1]
c = A[0][2]
d = A[1][0]
e = A[1][1]
f = A[1][2]
g = A[2][0]
h = A[2][1]
i = A[2][2]
det_one = a*((e*i) - (f*h))
det_two = b*((d*i)-(f*g))
det_three = c*((d*h)-(e*g))
det = det_one - det_two + det_three
print("The determinant:", det)

#Transpose the matrix
A_t = np.transpose(A)
print("Transpose of A:",A_t )
A_inverse = (1/det)*A_t
print("Inverse of A:", A_inverse)

#Computing inverse with built-in function
A_inv = lin.inv(A)
print("Function calculation of Inverse:", A_inv)

#part b
B = [[52,17,2], [-32,30, 19], [-9,-8,30]]
np.array(B)
c= B/det
print("Part B to check in equal in values:", c)

#Exercise 2 to find values of x in correlation to values in different values of B vector
b1 = np.array([12,-25,32])
b2 = np.array([4,-10,22])
b3 = np.array([20,-30,40])

#Solving for x1
x1 = lin.solve(A, b1)
print("This is the solution for the x-values with B1:", x1)
x2 = lin.solve(A, b2)
print("This is the solution for the x-values with B2:", x2)
x3 = lin.solve(A, b3)
print("This is the solution for the x-values with B3:", x3)

#Exercise 3
#use eigenproblem solver to determine if the eigenvalues and eigenvectors are complex conjugates
D = [[8,2], [-2,8]]
dd, ddd = lin.eig(D)
print("Value of eigenvalues:", dd)
print("Values of eigenvectors:", ddd)

#exercise 4
#determine the eigenvalues and vectors for a matrix and verify
Mat = [[-2,2,-3], [2,1,-6], [-1,-2,0]]
mm, mmm = lin.eig(Mat)
print("Eigenvalues for Mat Matrix:", mm)
print("Eigenvectors for Mat Matrix:", mmm)

#check if eigenvalue for 5 is proprot to the following
x1 = np.array([-1,-2,1])
eigg = x1*(1/np.sqrt(6))
print("Verify they match:", eigg)

#check how the are related, the linearly indep ones and the depent one
x2 = np.array([-2,1,0])
eigg2 = x2*(1/np.sqrt(5))
x3 = np.array([3,0,1])
eigg3 = x3*(1/np.sqrt(10))
print(eigg2, eigg3)
print(eigg, eigg2, eigg3)

#I am not sure how there eigenvectors and the eigen value -3 are related.

#Exercise 5
matr = sclin.hilbert(100)
b_vect = matr[:,0]
print(matr)
print(b_vect)
#I am honestly not sure what to be solving for this exercise...

#exercise 6


import numpy as np
import math
from itertools import product

def gini_coefficient(values):
    gini_numerator = np.sum( np.sum(np.abs(values - val)) for val in values )
    gini_denominator = 2 * len(values) * np.sum(values)
    return gini_numerator / gini_denominator

def theil_index(values, adjust_for_neg=False):
    mean = np.mean(values)

    values = [abs(v) + (abs(mean) if v * mean < 0 else 0) for v in values]
    mean = abs(mean)

    theil = sum((v / mean * np.log(v / mean) if v != 0 \
            else 0.) for v in values)
    return theil / len(values)

def ge_2_index(values):
    mean = np.mean(values)
    if mean == 0:
        print("Warning: 0 benefit mean in GE_2 computation")
        return 0.
    ge_2 = np.sum(np.square(values / mean) - 1)
    ge_2 /= (len(values) * 2)
    return ge_2

def evaluate_inequality(diffs, use_indices, transformations, min_diff=None):
    """ Computes inequality for multiple distances and transformation functions used to make the benefit values positive """
    trans_diffs = {}
    for transform in transformations:
        if transform == 'none':
            trans_diffs['raw'] = diffs
        elif transform == 'shift':
            shifted_diffs = diffs - min_diff if min_diff < 0 else diffs
            trans_diffs['shifted'] = shifted_diffs
        elif transform == 'sigmoid':
            sigmoid_diffs = 1 / (1 + np.exp(-diffs))
            trans_diffs['sigmoid'] = sigmoid_diffs
        else:
            assert False, "Unrecognized transformation {}".format(transform)

    ineq_indices = {'Gini': gini_coefficient,
            'Theil': theil_index,
            'GE_2': ge_2_index}
    indices = {}
    for index in use_indices:
        indices[index] = ineq_indices[index]

    result = []
    col_names = []
    for (transform, diff), (index, index_func) in product(
            trans_diffs.items(), indices.items()):
        ind_val = index_func(diff)
        result.append(index_func(diff))
        col_names.append("{} ({})".format(index, transform))

    return result, col_names

def decompose_inequality(group_benefits):
    """
    Computes the decomposition of population benefits for the GE_2 index (which is subgroup-decomposable) into
    - overall inequality
    - between-group inequality
    - within-group inequality for each group
    - unweighted intermediate components
    """
    overall_benefits = np.concatenate(group_benefits)
    overall_ineq = ge_2_index(overall_benefits)
    mean_util = np.mean(overall_benefits)

    sub_inequalities = []
    unweighted_inequalities = []
    intergroup_components = []
    intergroup_inequality = 0
    for i, sub_ut in enumerate(group_benefits):
        sub_mean_util = np.mean(sub_ut)
        means_sqr = np.square(sub_mean_util / mean_util)
        sub_inequality_weight = (len(sub_ut) / len(overall_benefits)) * means_sqr

        sub_inequality = ge_2_index(sub_ut)
        sub_inequalities.append(sub_inequality_weight * sub_inequality)
        unweighted_inequalities.append(sub_inequality)

        intergroup_component = len(sub_ut) / (2 * len(overall_benefits)) * \
                (means_sqr - 1)
        intergroup_inequality += intergroup_component
        intergroup_components.append(intergroup_component)

    decomposition_sum = sum(sub_inequalities) + intergroup_inequality
    assert math.isclose(overall_ineq, decomposition_sum, rel_tol=0.1), "overall inequality {} and decomposition sum {} not close".format(overall_ineq, decomposition_sum)

    return {'overall_ineq': overall_ineq,
            'intergroup_ineq': intergroup_inequality,
            'subgroup_ineqs': sub_inequalities,
            'unweighted_ineqs': unweighted_inequalities,
            'intergroup_components': intergroup_components}

def compute_benefits(predictions, labels, binary=False):
    """ Converts predictions and labels into positive benefits """
    if binary:
        return predictions
    else:
        return (predictions - labels) + 1


#!/usr/bin/env python3
import numpy as np
import scipy.special as special
import matplotlib.pyplot as plt

#  Format text used in plots to match LaTeX
plt.rc('text', usetex=True)
plt.rc('font', family='serif')

def pvalue_from_zscore(z_score):
    return 0.5 * (1 - special.erf(z_score / np.sqrt(2)))

def zscore_from_pvalue(p_value):
    return np.sqrt(2) * special.erfinv(1 - 2 * p_value)

def plot_pvalue(z_range, fontsize=14):
    fig, ax = plt.subplots(1, 1)
    ax.set_yscale('log')

    x = np.linspace(min(z_range), max(z_range), 10000)
    ax.plot(x, pvalue_from_zscore(x), color='black')

    ax.set_xlim(left=-0.1)

    ax.set_xlabel(r'$z$-score', fontsize=fontsize)
    ax.set_ylabel(r'$p$\,-value', fontsize=fontsize)

    return fig

def plot_zscore(p_range, fontsize=14):
    fig, ax = plt.subplots(1, 1)

    x = np.linspace(min(p_range), max(p_range), 10000)
    ax.plot(x, zscore_from_pvalue(x), color='black')

    ax.invert_xaxis()
    ax.set_xscale('log')

    ax.set_xlabel(r'$p$\,-value', fontsize=fontsize)
    ax.set_ylabel(r'$z$-score', fontsize=fontsize)

    return fig

def main():
    pvalues_plot = plot_pvalue([0, 5])

    image_write_path = 'figures/results/'
    extensions = ['pdf', 'png', 'eps']
    for ext in extensions:
        pvalues_plot.savefig(f'{image_write_path}pvalues_from_zscores.{ext}')

if __name__ == '__main__':
    main()


import numpy as np

def SST(y: np.ndarray) -> float:
    """
    Computes the deviations of from its mean Total Sum of Squares.
    Used to judge the goodness to fit of the feature to predict.

    :param y: real data
    :return: Total Sum of Squares
    """
    y_avg = np.mean(y)
    return float(np.sum(np.square(y - y_avg)))

def M_zero(n: int) -> np.ndarray:
    """
    Computes the M_0 matrix.
    M_0 * x = x - x_bar

    :param n: dimension of the matrix
    :return: squared matrix M_0
    """
    return np.eye(n) - np.ones((n, n)) / n

def SSR(y_hat: np.ndarray) -> float:
    """
    Computes the regression sum of squares.

    :param y_hat: data matrix
    :return: regression sum of squares
    """
    n = y_hat.shape[0]
    return float(y_hat.T @ M_zero(n) @ y_hat)

def SSE(y: np.ndarray, y_hat: np.ndarray) -> float:
    """
    Computes the sum of squared errors.

    :param y: real data
    :param y_hat: prediction
    :return: sum of squares errors
    """
    e = y - y_hat
    return float(np.sum(e * e))

def R_squared(y: np.ndarray, y_hat: np.ndarray) -> float:
    """
    Computes the coefficient of determination (R²).

    :param y: real data
    :param y_hat: prediction
    :return: coefficient of determination (R²)
    """
    return 1 - SSE(y, y_hat) / SST(y)

def adjusted_R_squared(y: np.ndarray, y_hat: np.ndarray, n_features: int) -> float:
    """
    Computes the adjusted coefficient of determination (R²).

    :param y: real data
    :param y_hat: prediction
    :param n_features: number of features (K)
    :return: adjusted coefficient of determination (R²)
    """
    n = len(y)
    assert (n > n_features + 1)  # making sure that N > K + 1
    r_squared = R_squared(y, y_hat)
    return 1 - (1 - r_squared) * (n - 1) / (n - n_features - 1)

def MSE(y: np.ndarray, y_hat: np.ndarray) -> float:
    """
    Computes the Mean Square Error.

    :param y: real data
    :param y_hat: prediction
    :return: MSE
    """
    return SSE(y, y_hat) / len(y)

def RMSE(y: np.ndarray, y_hat: np.ndarray) -> float:
    """
    Compute the Root Mean Square Error.

    :param y: real data
    :param y_hat: prediction
    :return: RMSE
    """
    return np.sqrt(MSE(y, y_hat))

def MAE(y: np.ndarray, y_hat: np.ndarray) -> float:
    """
    Computes the Mean Absolute Error.

    :param y: real data
    :param y_hat: prediction
    :return: MAE
    """
    e = y - y_hat
    return np.sum(np.abs(e)) / len(e)

def theil_U(y: np.ndarray, y_hat: np.ndarray) -> float:
    """
    Computes the Theil U statistic (scale invariant measure).

    :param y: real data
    :param y_hat: prediction
    :return: Theil U statistic
    """
    return np.sqrt(MSE(y, y_hat) / MSE(y, np.zeros_like(y)))

def information_criteria(y: np.ndarray, y_hat: np.ndarray, n_features: int, ic_type=None) -> float:
    """
    Computes information criteria, either 'akaike' or 'bayesian'.

    :param y: real data
    :param y_hat: prediction
    :param n_features: number of features (K)
    :param ic_type: information criteria type, either 'akaike' or 'bayesian'
    :return: ic_type information criteria
    """
    # Raise an error if no type is specified
    if ic_type is None:
        raise ValueError("Information Criteria type not defined. `ic_type` is None, must be 'akaike' or 'bayesian'.")

    e = y - y_hat
    n = len(e)

    # Return the Akaike IC
    if ic_type == 'akaike':
        return np.log(e @ e.T / n) + 2 * n_features / n

    # Return the Bayesian IC
    elif ic_type == 'bayesian':
        return np.log(e @ e.T / n) + n_features * np.log(n) / n

    # Raise an error if IC type is neither Akaike od Bayesian
    else:
        raiseValueError("Invalid Information Criteria type. `ic_type` must be 'akaike' or 'bayesian'.")


import numpy as np

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

def softmax(X):
    if X.ndim == 1:
        exp = np.exp(X)
        return exp / np.sum(exp)
    elif X.ndim == 2:
        exp = np.exp(X)
        sum = np.sum(exp, axis=1, keepdims=True)
        return exp / sum

def cross_entropy(Y, T):
    EPSION = 1e-8
    return np.mean(np.sum(-np.log(Y+EPSION) * T, axis=1))

if __name__ == '__main__':
    # print(sigmoid(3))
    # print(sigmoid(0))
    # print(sigmoid(-2))
    # print(sigmoid(-30))
    # print(sigmoid(np.array([-3, -0.1, 0, 0.8, 4])))

    one_dim = np.array([-0.5, 0.1, 0.3, 1])
    two_dim = np.array([
        [-0.5, 0.1, 0.3, 1],
        [3.0, 8.0, 0.4, 3.0]
    ])
    # print(softmax(one_dim))
    # print(softmax(two_dim))

    logits = softmax(two_dim)
    labels = np.array([
        [1, 0, 0, 0],
        [0, 1, 0, 0]
    ])
    print(cross_entropy(logits, labels))


"""
This is the solution to the quadratic regression exercise.
"""

import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt
import seaborn as sns

# Some sample data
train_X = np.arange(-1, 1, 0.01)
rand = np.random.uniform(-0.02, 0.02, train_X.shape)
train_Y = 1.2 * train_X**2 + 0.5*train_X + 1 + rand

num_samples = train_X.shape[0]

# Parameters
learning_rate = 0.01
iterations = 10000

### ------ ** Creating the graph ** -------

# Placeholders - where the data can come into the graph
X = tf.placeholder(tf.float32, [None])
Y = tf.placeholder(tf.float32, [None])

# Creating the parameters
a_tf = tf.Variable(np.random.randn(), name='a')
b_tf = tf.Variable(np.random.randn(), name='b')
c_tf = tf.Variable(np.random.randn(), name='c')

# Creating the model: y = ax^2 + bx + c
model = tf.multiply(a_tf, tf.pow(X, 2)) + tf.multiply(b_tf, X) + c_tf

# Creating the cost function
cost_function = 0.5 * (1.0/num_samples) * tf.reduce_sum(tf.pow(model - Y, 2))

# Defining the method to do the minimisation of the cost function
optimiser = tf.train.GradientDescentOptimizer(learning_rate).minimize(cost_function)

### -------- ** Initialising all the variables ** --------

init = tf.global_variables_initializer()

### -------- ** Starting the session ** ----------

with tf.Session() as sess:
    sess.run(init)

    for i in range(iterations):
        sess.run(optimiser, feed_dict={X: train_X, Y: train_Y})

        if (i+1)%50 == 0:
            c = sess.run(cost_function, feed_dict={X: train_X, Y: train_Y})
            print("Step:", '%04d' % (i+1), "cost=", "{:.9f}".format(c))

    a = sess.run(a_tf)
    b = sess.run(b_tf)
    c = sess.run(c_tf)

### -------- ** Plotting the results ** ------------

sns.set()
fig, ax = plt.subplots()
ax.scatter(train_X, train_Y, marker=".")
y_pred = a*(train_X**2) + b*train_X + c     # Change a, b, c to whatever you have called your parameters!
ax.plot(train_X, y_pred, color=sns.xkcd_rgb["medium green"])
ax.set_xlabel('x')
ax.set_ylabel('y')

plt.show()


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

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

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

    @staticmethod
    def eval(sol):
        term1 = 2 * sol[0] ** 2
        term2 = -1.05 * sol[0] ** 4
        term3 = sol[0] ** 6 / 6
        term4 = sol[0] * sol[1]
        term5 = sol[1] ** 2

        return term1 + term2 + term3 + term4 + term5


r'''
unit test describing the hyperbolic half-plane with the Poincare metric. This
is a basic model of hyperbolic geometry on the (positive) half-space

{(x,y) \in R^2 | y > 0}

with the Riemannian metric

ds^2 = (dx^2 + dy^2)/y^2

It has constant negative scalar curvature = -2

https://en.wikipedia.org/wiki/Poincare_half-plane_model
'''
from sympy.matrices.dense import diag
from sympy.diffgeom import (twoform_to_matrix,
                            metric_to_Christoffel_1st, metric_to_Christoffel_2nd,
                            metric_to_Riemann_components, metric_to_Ricci_components)
import sympy.diffgeom.rn
from sympy.tensor.array import ImmutableDenseNDimArray

def test_H2():
    TP = sympy.diffgeom.TensorProduct
    R2 = sympy.diffgeom.rn.R2
    y = R2.y
    dy = R2.dy
    dx = R2.dx
    g = (TP(dx, dx) + TP(dy, dy))*y**(-2)
    automat = twoform_to_matrix(g)
    mat = diag(y**(-2), y**(-2))
    assert mat == automat

    gamma1 = metric_to_Christoffel_1st(g)
    assert gamma1[0, 0, 0] == 0
    assert gamma1[0, 0, 1] == -y**(-3)
    assert gamma1[0, 1, 0] == -y**(-3)
    assert gamma1[0, 1, 1] == 0

    assert gamma1[1, 1, 1] == -y**(-3)
    assert gamma1[1, 1, 0] == 0
    assert gamma1[1, 0, 1] == 0
    assert gamma1[1, 0, 0] == y**(-3)

    gamma2 = metric_to_Christoffel_2nd(g)
    assert gamma2[0, 0, 0] == 0
    assert gamma2[0, 0, 1] == -y**(-1)
    assert gamma2[0, 1, 0] == -y**(-1)
    assert gamma2[0, 1, 1] == 0

    assert gamma2[1, 1, 1] == -y**(-1)
    assert gamma2[1, 1, 0] == 0
    assert gamma2[1, 0, 1] == 0
    assert gamma2[1, 0, 0] == y**(-1)

    Rm = metric_to_Riemann_components(g)
    assert Rm[0, 0, 0, 0] == 0
    assert Rm[0, 0, 0, 1] == 0
    assert Rm[0, 0, 1, 0] == 0
    assert Rm[0, 0, 1, 1] == 0

    assert Rm[0, 1, 0, 0] == 0
    assert Rm[0, 1, 0, 1] == -y**(-2)
    assert Rm[0, 1, 1, 0] == y**(-2)
    assert Rm[0, 1, 1, 1] == 0

    assert Rm[1, 0, 0, 0] == 0
    assert Rm[1, 0, 0, 1] == y**(-2)
    assert Rm[1, 0, 1, 0] == -y**(-2)
    assert Rm[1, 0, 1, 1] == 0

    assert Rm[1, 1, 0, 0] == 0
    assert Rm[1, 1, 0, 1] == 0
    assert Rm[1, 1, 1, 0] == 0
    assert Rm[1, 1, 1, 1] == 0

    Ric = metric_to_Ricci_components(g)
    assert Ric[0, 0] == -y**(-2)
    assert Ric[0, 1] == 0
    assert Ric[1, 0] == 0
    assert Ric[0, 0] == -y**(-2)

    assert Ric == ImmutableDenseNDimArray([-y**(-2), 0, 0, -y**(-2)], (2, 2))

    ## scalar curvature is -2
    #TODO - it would be nice to have index contraction built-in
    R = (Ric[0, 0] + Ric[1, 1])*y**2
    assert R == -2

    ## Gauss curvature is -1
    assert R/2 == -1


import numpy as np
import matplotlib.pyplot as plt
from numpy.linalg import inv

N = 25
X = np.reshape(np.linspace(0, 0.9, N), (N, 1))
Y = np.cos(10*X**2) + 0.1 * np.sin(100*X)
# K = 12  # order of the func
listTrainning = []
for i in np.linspace(0, 0.9, N):
    listTrainning.append(np.cos(10*i**2) + 0.1 * np.sin(100*i))

def PsiiFill(K):
    Psii = np.zeros((N, 2*K + 1))
    for i in range(N):
        for j in range(0, 2*K+1):
            if j % 2 == 0:
                Psii[i][j] = np.cos(2 * np.pi * (j/2) * X[i][0])
            else:
                Psii[i][j] = np.sin(2 * np.pi * (j+1)/2 * X[i][0])

    theta = np.dot(np.dot(inv(np.dot(Psii.T, Psii)), Psii.T), Y)
    return theta

def leave_one_out(K, index):
    Psii = np.zeros((N, 2 * K + 1))
    for i in range(N):
        if i != index:
            for j in range(0, 2*K+1):
                if j % 2 == 0:
                    Psii[i][j] = np.cos(2 * np.pi * (j/2) * X[i][0])
                else:
                    Psii[i][j] = np.sin(2 * np.pi * (j+1)/2 * X[i][0])

    theta = np.dot(np.dot(inv(np.dot(Psii.T, Psii)), Psii.T), Y)
    return theta

def f(x, order):
    fx = 0
    theta_tem = PsiiFill(order)
    #print(theta_tem)
    for k in range(2*order+1):
        if k % 2 == 0:
            fx = fx + np.dot((np.cos(2 * np.pi * (k//2) * x)), theta_tem[k][0])
        else:
            fx = fx + np.dot((np.sin(2 * np.pi * (k + 1)//2 * x)), theta_tem[k][0])

    return fx

def g(x, order, index):
    fx = 0
    theta_tem = leave_one_out(order, index)
    #print(theta_tem)
    for k in range(2*order+1):
        if k % 2 == 0:
            fx = fx + np.dot((np.cos(2 * np.pi * (k//2) * x)), theta_tem[k][0])
        else:
            fx = fx + np.dot((np.sin(2 * np.pi * (k + 1)//2 * x)), theta_tem[k][0])

    return fx

testingX = np.linspace(-1, 1.2, 200)

# list_Y_1 = []
# for i in testingX:
#     # print(f(i))
#     list_Y_1.append(f(i, 1))
# print(list_Y_1)
#
#
# list_Y_11 = []
# for i in testingX:
#     # print(f(i))
#     list_Y_11.append(f(i, 11))

def sigmaSquare(order):
    trainning_Y = []
    for i in X:
        # print(f(i))
        trainning_Y.append(f(i, order))
    S = 0
    # order 0 to 10
    for i in range(1, N+1):
        S = S + (Y[i-1] - trainning_Y[i-1])**2

    return S/N

def Average_Square_Test_err(order):

    SqualError = 0
    # global N
    # global X
    # global Y

    #N = N - 1
    for expIndex in range(N):
        testing_data = X[expIndex]
        SqualError = SqualError + (np.cos(10 * testing_data ** 2) + 0.1 * np.sin(100 * testing_data) - g(X[expIndex], order, expIndex)) ** 2

        # order 0 to 10

    return SqualError/25

# # order 0 to 10
lsitSigmaS = []
orderList = []
for ord in range(11):
    orderList.append(ord)
    lsitSigmaS.append(sigmaSquare(ord))

listAverage_Squared_test_error = []
for ord in range(11):
    listAverage_Squared_test_error.append(Average_Square_Test_err(ord))
font = {'family': 'serif',
        'color':  'black',
        'weight': 'normal',
        'size': 12,
        }

fontBlue = {'family': 'serif',
        'color':  'blue',
        'weight': 'normal',
        'size': 12,
        }

fontGreen = {'family': 'serif',
        'color':  'green',
        'weight': 'normal',
        'size': 12,
        }
# print(testingX)
# plt.plot(np.linspace(0, 0.9, N), listTrainning, 'ro')
# plt.text(0.2, 1.5, r'Pink Dot: tranning data', fontdict=fontDot)
#
#

print(lsitSigmaS)
plt.plot(orderList, lsitSigmaS)
plt.text(4, 0.5, r'Blue: maximum likelihood estimate', fontdict=fontBlue)

plt.plot(orderList, listAverage_Squared_test_error, 'g-')
plt.text(4, 0.45, r'Green: leave-one-out cross validation', fontdict=fontGreen)

plt.title('maximum likelihood value for $\sigma_{ML}^2$', fontdict=font)
plt.xlabel('Order', fontdict=font)
plt.ylabel(r'$\sigma_{ML}^2$', fontdict=font)

# plt.plot(testingX, list_Y_11, 'm')
# plt.text(-0.2, -1.0, r'order 11: magenta', fontdict=font)

# plt.axis([-0.3, 1.3, -2, 2])
plt.show()


import numpy as np
import math

def f(func_value, x):
    """
    returns the value of the function for some given x

    Inputs:
    1.x : Input value
    2.func_value : number indicator to show which of the 2 functions we choose

    Output: Function value
    """
    if int(func_value) == 1 :
        return (x**3 - 3*(x**2) - x + 9)
    elif int(func_value) == 2 :
        return (x**3 - 3*(x**2) - x + 9)*(math.exp(x))
    else:
        print("Error Message")

def g(func_value, x):
    """
    Defining derivative of function

    Inputs:
    1.x : Input value
    2.func_value : number indicator to show which of the 2 functions we choose

    Output: Function value
    """
    if int(func_value) == 1 :
        return (3*x*x - 6*x - 1)
    elif int(func_value) == 2 :
        return ((x**3 - 3*x*x - x + 9 + (3*x*x - 6*x - 1))*(math.exp(x)))
    else:
        print("Error Message")

def bisection(func_num,a, b, e, N_Max):
    """
    approximates a root, R, of f bounded by a and b
    with root being accurate enough such that |f(R)| < e

    Inputs:
    1. func_num : number indicator to show which of the 2 functions we choose
    2. a : lower bound on the root
    3. b : upper bound on the root
    4. e : permissible error in the value of root obtained
    5. N_max : maximum number of iterations after which function stops.

    Outputs: Root found under given constraints
    """
    counter = N_Max
    # this is the maximum number of times the function can be called

    # check if a and b bound a root
    # if they are of the same sign, then they do not bound a root
    if ((f(func_num,a))*(f(func_num,b)))>0 :
        raise Exception("The scalars a and b do not bound a root")
    # this works assuming that the function is monotonous in the range (a,b)

    # the midpoint of a and b
    m = (a + b)/2

    # if the maximum number of iterations has not been reached, continue
    while(counter>0):
        # if the found midpoint satisfies the accuracy requirement
        if np.abs(f(func_num,m)) < e:
            # stopping condition, report m as root
            return m
        # if the midpoint 'm' lies between the root and a,
        # we know that 'm' is closer to the root than a
        # this is the case where m is an improvement on a
        elif np.sign(f(func_num,a)) == np.sign(f(func_num,m)):
            # Make recursive call with a = m
            return bisection(func_num, m, b, e, counter-1)
        # if the midpoint 'm' lies between the root and b,
        # we know that 'm' is closer to the root than b
        # this is the case where 'm' is an improvement on b
        elif np.sign(f(func_num,b)) == np.sign(f(func_num,m)):
            # Make recursive call with b = m
            return bisection(func_num, a, m, e, counter-1)

    if(counter==0):
        return m

def newtonRaphson(func_num,x0,e,N):
    """
    Implementing Newton Raphson Method of finding the root

    Inputs:
    1. func_num : number indicator to show which of the 2 functions we choose
    2. x0 : Starting point for the root search
    3. e : permissible error in the value of root obtained
    4. N : maximum number of iterations after which function stops.

    Outputs: function values at the roots found under given constraints
    """
    func_value_at_roots = []
    counter = 1
    flag = 1
    condition = True
    while condition:
        if g(func_num,x0) == 0.0:
            print('Divide by zero error!')
            break

        x1 = x0 - f(func_num,x0)/g(func_num,x0)
        func_value_at_roots.append((f(func_num,x1)))
        x0 = x1
        counter = counter + 1

        if counter > N:
            flag = 0
            break

        condition = abs(f(func_num,x1)) > e
    return func_value_at_roots

def secant(f_num,a,b,e,N_Max):
    """
    Implementing Secant Method of finding the root

    Inputs:
    1. f_num : number indicator to show which of the 2 functions we choose
    2. a : first point to initiate root search
    3. b : first point to initiate root search
    4. e : permissible error in the value of root obtained
    5. N_max : maximum number of iterations after which function stops.

    Outputs: function value at the root found under given constraints
    """
    f_a = f(f_num,a)
    f_b = f(f_num,b)
    counter = 0
    while abs(f_b)>e and counter<N_Max :
        try:
            numerical_derivate = (f_b - f_a)/(b-a)
            t2 = b - f(f_num,b)/numerical_derivate
        except:
            print("error")
        a = b
        b = t2
        f_a = f_b
        f_b = f(f_num,b)
        counter += 1
    return f_b


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

Automatically generated by Colaboratory.

Original file is located at
    https://colab.research.google.com/drive/1OM6DjnR0yQBTljAjxNZRpbzCU7tS6Pv6
"""

import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import pandas as pd
from sklearn.model_selection import train_test_split
from numpy import shape
from numpy import mat
from numpy import arange
from matplotlib.colors import ListedColormap

"""Q1.Generate two clusters of data points with 100 points each, by sampling from Gaussian distributions centered at (0.5, 0.5) and (−0.5, −0.5)."""

cmap_bold = ListedColormap(['darkorange', 'c'])
standard_deviation = [[0.1,0],[0,0.1]]

mean1 = [0.5,0.5]
data1 = np.random.multivariate_normal(mean1, standard_deviation,100)
mean2 = [-0.5,-0.5]
data2 = np.random.multivariate_normal(mean2, standard_deviation, 100)

plt.ylim(-1.0, 1.0)
plt.xlim(-1.0, 1.0)

plt.scatter(data1[:, 0], data1[:, 1], c='blue')
plt.scatter(data2[:, 0], data2[:, 1], c='red')
plt.show()

"""Q2.Implement the Perceptron algorithm as discussed in class. Choose the initial weights to be zero and the maximum number of epochs as T = 100, and the learning rate α = 1. How quickly does your implementation converge?"""

# add label to data1, data2
d1 = np.insert(data1, 2, values=1, axis=1)
d2 = np.insert(data2, 2, values=-1, axis=1)
data = np.vstack((d1, d2))
X = data[:, :-1] # except last col
Y = data[:, -1] # last col

# split data into 4 parts
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.30, random_state=43)
# print(X_test[0].shape)
# print(Y_test.shape)

# initial w to 1 row 2 cols [w1, w2]
W = np.zeros((1, 2))

def perceptron(W, X_train, X_test, Y_train, Y_test, epochs, learning_rate):
  # loop and compare
  for epoch in range(epochs):
    for idx in range(len(X_train)):
      # update weights if the prediction is wrong
      # W [1,2] * [2,1] we need to use np.dot to do matrix calculation but not *
      if (np.dot(np.sign(np.dot(W, X_train[idx])), Y_train[idx])) <= 0:
          W = W + np.dot(learning_rate, np.dot(Y_train[idx], X_train[idx].T))

    if epoch % 10 == 0:
      Y_test_pred = np.dot(X_test, W.T)
      acc = 0
      for _ in range(len(Y_test)):
        if Y_test[_] == np.sign(Y_test_pred[_]):
          acc = acc + 1
      f_acc = acc / len(Y_test)
      print(f"Epoch: {epoch}, Test Accuracy: {f_acc}")

perceptron(W, X_train, X_test, Y_train, Y_test, 100, 1)

"""according to the result, the converge speed is very fast. (The dataset has low variance)

Q3. Now, repeat the above experiment with a second synthetic dataset; this time, increase the variance (radius) of the two Gaussians such that the generated data points from different classes now overlap. What happens to the behavior of the algorithm? Does it converge? Show the classification regions obtained at the end of T epochs.
"""

standard_deviation = [[0.5,0],[0,0.5]]

mean1 = [0.5,0.5]
data1 = np.random.multivariate_normal(mean1, standard_deviation,100)
mean2 = [-0.5,-0.5]
data2 = np.random.multivariate_normal(mean2, standard_deviation, 100)

plt.ylim(-1.0, 1.0)
plt.xlim(-1.0, 1.0)

plt.scatter(data1[:, 0], data1[:, 1], c='blue')
plt.scatter(data2[:, 0], data2[:, 1], c='red')
plt.show()

# add label to data1, data2
d1 = np.insert(data1, 2, values=1, axis=1)
d2 = np.insert(data2, 2, values=-1, axis=1)
data = np.vstack((d1, d2))
X = data[:, :-1] # except last col
Y = data[:, -1] # last col

# split data into 4 parts
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.30, random_state=43)
W = np.zeros((1, 2))

perceptron(W, X_train, X_test, Y_train, Y_test, 100, 1)

"""according to the result, we can find that when the dataset has a lot of noise, it is more difficult to converge."""


import numpy as np
from math import sqrt

def givens(A, tol=1E-15):
    # Make R a copy of A and Q an
    # identity array of the appropriate size.
    R = np.array(A, order="C")
    Q = np.eye(A.shape[0])
    # Make an empty 2x2 array G that will
    # be used to apply the Givens rotations.
    G = np.empty((2,2))
    # For each column:
    for j in xrange(A.shape[1]-1):
        # For each row below the main diagonal
        # (starting at the bottom of the column):
        for i in xrange(A.shape[0]-1, j, -1):
            # If the leading entry of this row is
            # not zero (i.e. if its absolute value
            # is within a given tolerance):
            if tol <= abs(R[i,j]):
                # Compute c and s using the entry
                # in the current row and column
                # and the entry immediately above it.
                c = R[i-1,j]
                s = - R[i,j]
                n = sqrt(c**2 + s**2)
                c /= n
                s /= n
                # Use c and s to construct the matrix G.
                G[0,0] = c
                G[1,1] = c
                G[0,1] = - s
                G[1,0] = s
                # Get a slice of $R$ of the current row
                # and the row above it that includes the
                # columns from the current column onward.
                # Multiply it in place by $G$ to zero out
                # the leading nonzero entry of the current row.
                R[i-1:i+1,j:] = G.dot(R[i-1:i+1,j:])
                # Get a slice of $Q$ of the current row and
                # the row above it and apply $G$ to it as well.
                # Here we use the fancy slicing to avoid extra
                # computations in creating Q.
                # The column slicing is not in the lab.
                Q[i-1:i+1,min(i-1-j,0):] = G.dot(Q[i-1:i+1,min(i-1-j,0):])
    # Return Q^T and R.
    return Q.T, R

def givens_hess(A, tol=1E-15):
    # Make R a copy of A and Q an
    # identity array of the appropriate size.
    R = np.array(A, order="C")
    Q = np.eye(A.shape[0])
    # Make an empty 2x2 array G that will
    # be used to apply the Givens rotations.
    G = np.empty((2,2))
    # Iterate along first subdiagonal.
    # Run index along columns.
    for j in xrange(A.shape[0]-1):
        # If the leading entry of this row is
        # not zero (i.e. if its absolute value
        # is within a given tolerance):
        if tol <= abs(R[i,j]):
            # Compute c and s using the entry
            # in the current row and column
            # and the entry immediately above it.
            c = R[j,j]
            s = - R[j+1,j]
            n = sqrt(c**2 + s**2)
            c /= n
            s /= n
            # Use c and s to construct the matrix G.
            G[0,0] = c
            G[1,1] = c
            G[0,1] = -s
            G[1,0] = s
            # Apply rotation to proper portion of R.
            R[j:j+2,j:] = G.dot(R[j:j+2,j:])
            # Apply rotation to proper portion of Q
            Q[j:j+2,:j+2] = G.dot(Q[j:j+2,:j+2])
    # Return Q^T and R.
    return Q.T, R

def givens_hess_modified(A, tol=1E-15):
    # Make R a copy of A and Q an
    # identity array of the appropriate size.
    R = np.array(A, order="C")
    # Store each Givens Rotation along
    # the first axis of Q
    Q = np.empty((A.shape[0]-1, 2, 2))
    # Iterate along first subdiagonal.
    # Run index along columns.
    for j in xrange(A.shape[0]-1):
        # If the leading entry of this row is
        # not zero (i.e. if its absolute value
        # is within a given tolerance):
        if tol <= abs(R[j+1,j]):
            # Compute c and s using the entry
            # in the current row and column
            # and the entry immediately above it.
            c = R[j,j]
            s = - R[j+1,j]
            n = sqrt(c**2 + s**2)
            c /= n
            s /= n
            # Use c and s to construct the
            # needed Givens rotation
            Q[j,0,0] = c
            Q[j,1,1] = c
            Q[j,0,1] = -s
            Q[j,1,0] = s
            # Apply rotation to proper portion of R.
            R[j:j+2,j:] = Q[j].dot(R[j:j+2,j:])
    # Return Q and R.
    return Q, R

def apply_QT(Q, B):
    # array to return
    C = np.array(B, order="C")
    # apply Givens rotations in order
    for i in xrange(Q.shape[0]):
        C[i:i+2] = Q[i].dot(C[i:i+2])
    return C

def apply_Q(Q, B):
    # array to return
    C = np.array(B, order="C")
    # Apply transpose of each Givens
    # rotation to the corresponding
    # portion of C in reversed order.
    for i in xrange(Q.shape[0]-1, -1, -1):
        C[i:i+2] = Q[i].T.dot(C[i:i+2])
    return C


'''
This file contains sample code about how to use Gauss–Hermite quadrature to compute a specific type of integral numerically.

The general form of this type of integral is:( see https://en.wikipedia.org/wiki/Gauss%E2%80%93Hermite_quadrature for more details)

F = int_{ -inf}^{+inf} e^{-x*x) f(x) dx,  (1)

in which we're calculating the integral of f(x) in the range ( -inf, +inf) weighted by e^(-x*x ).
Note that for f(x) being polynomial function, this integral is guaranteed to converge. But for some others convergence is not guaranteed.
'''

import numpy as np

def gass_hermite_quad( f, degree):
    '''
    Calculate the integral (1) numerically.
    :param f: target function, takes a array as input x = [x0, x1,...,xn], and return a array of function values f(x) = [f(x0),f(x1), ..., f(xn)]
    :param degree: integer, >=1, number of points
    :return:
    '''

    points, weights = np.polynomial.hermite.hermgauss( degree)

    #function values at given points
    f_x = f( points)

    #weighted sum of function values
    F = np.sum( f_x  * weights)

    return F

if __name__ == '__main__':

    # Example 1, f(x) = x^2, degree = 3, whose closed form solution is sqrt(pi) / 2
    def x_square( x):
        return x* x
    F = gass_hermite_quad( x_square,3 )
    print( F)

    # Example 2, f(x) = x * sin x, degree = 10, whose closed form solution is sqrt( pi) / e^(1/4) / 2)
    def my_func(x):
        return x * np.sin( x)

    F = gass_hermite_quad( my_func, degree=10)
    print(F)


# CS231A Homework 0, Problem 2
import numpy as np
import numpy.matlib
import matplotlib.pyplot as plt

def main():
    # ===== Problem 2a =====
    # Define Matrix M and Vectors a,b,c in Python with NumPy

    M, a, b, c = None, None, None, None

    # BEGIN YOUR CODE HERE
    M = np.array([[1,2,3], [4,5,6], [7,8,9], [0,2,2]])
    a = np.array([1,1,0])
    b = np.array([-1,2,5])
    c = np.array([0,2,3,2])
    # END YOUR CODE HERE

    # ===== Problem 2b =====
    # Find the dot product of vectors a and b, save the value to aDotb

    aDotb = None

    # BEGIN YOUR CODE HERE
    aDotb = a.dot(b)
    print "(b) aDotb =", aDotb
    # END YOUR CODE HERE

    # ===== Problem 2c =====
    # Find the element-wise product of a and b

    # BEGIN YOUR CODE HERE
    aMultib = np.multiply(a, b)
    print "(c) aMultib =", aMultib
    # END YOUR CODE HERE

    # ===== Problem 2d =====
    # Find (a^T b)Ma

    # BEGIN YOUR CODE HERE
    dResult = aDotb * M.dot(a)
    print "(d) (a^T b)Ma =", dResult
    # END YOUR CODE HERE

    # ===== Problem 2e =====
    # Without using a loop, multiply each row of M element-wise by a.
    # Hint: The function repmat() may come in handy.

    newM = None

    # BEGIN YOUR CODE HERE
    a_repmat = np.matlib.repmat(a, 4, 1)
    newM = np.multiply(M, a_repmat)

    # with broadcasting, we can simply calculate: newM = np.multiply(M, a)
    print "(e) result =\n", newM
    # END YOUR CODE HERE

    # ===== Problem 2f =====
    # Without using a loop, sort all of the values
    # of M in increasing order and plot them.
    # Note we want you to use newM from e.

    # BEGIN YOUR CODE HERE
    sortedM = np.sort(newM, axis=None)
    N = len(sortedM)
    x = range(N)

    # plot settings
    plt.xlabel("Vector Index")
    plt.ylabel("Values")
    plt.title("Plot of Values in Sorted M")
    plt.plot(x, sortedM, 'ro--')
    plt.axis([0, N, np.min(sortedM), np.max(sortedM)])
    plt.show()
    # END YOUR CODE HERE

if __name__ == '__main__':
    main()


"""Hilbert-Schmidt metric."""
import numpy as np

from toqito.matrix_props import is_density

def hilbert_schmidt(rho: np.ndarray, sigma: np.ndarray) -> float:
    r"""
    Compute the Hilbert-Schmidt distance between two states [WikHS]_.

    The Hilbert-Schmidt distance between density operators :math:`\rho` and :math:`\sigma` is
    defined as

    .. math::
        D_{\text{HS}}(\rho, \sigma) = \text{Tr}((\rho - \sigma)^2) = \left\lVert \rho - \sigma
        \right\rVert_2^2.

    Examples
    ==========

    One may consider taking the Hilbert-Schmidt distance between two Bell states. In :code:`toqito`,
    one may accomplish this as

    >>> from toqito.states import bell
    >>> from toqito.state_metrics import hilbert_schmidt
    >>> rho = bell(0) * bell(0).conj().T
    >>> sigma = bell(3) * bell(3).conj().T
    >>> hilbert_schmidt(rho, sigma)
    1

    References
    ==========
    .. [WikHS] Wikipedia: Hilbert-Schmidt operator.
        https://en.wikipedia.org/wiki/Hilbert%E2%80%93Schmidt_operator

    :param rho: An input matrix.
    :param sigma: An input matrix.
    :return: The Hilbert-Schmidt distance between `rho` and `sigma`.
    """
    if not is_density(rho) or not is_density(sigma):
        raise ValueError("Hilbert-Schmidt is only defined for density " "operators.")
    return np.linalg.norm(rho - sigma, ord=2) ** 2


from factorization.matrixtools import Mattools
from factorization.qr_factorization import householder
import numpy as np
import copy

class URV(Mattools):
    def __init__(self, mat):
        super().__init__(mat)

    def URV_factorization(self):
        '''
        For the URV factorization of any matrix

        Example
        ========
        >>>from factorization import Matrix
        >>>import numpy as np
        >>>a = np.array([[4,8,12,-8],[2,3,2,1],[-3,-1,1,-4]])
        >>>test_object = Matrix(a)
        >>>test_object.URV_factorization()

        array([[ 0.97372899,  0.20462466,  0.02942979, -0.09546984],
        [-0.18257419,  0.9636314 , -0.03927159,  0.19115159],
        [ 0.06085806, -0.06428777, -0.97949468,  0.18097945],
        [ 0.12171612, -0.15940626,  0.19540167,  0.95999636]]),
         array([[ 1.35918623e+01, -2.70100613e-16,  9.30409510e-16,
                  7.99440812e-16],
                [ 1.67486275e+01,  7.45697467e+00, -1.70048074e-17,
                 -1.09712297e-16],
                [ 6.36668910e+00,  4.29004562e+00,  2.53348854e+00,
                 -7.21644966e-16],
                [-3.61921181e-01, -6.10522177e-01, -1.50162168e-02,
                 -5.50147470e-01]]),
         array([[ 0.30223373,  0.50417063,  0.70148075, -0.4029783 ],
                [-0.67882758, -0.26245638,  0.09059456, -0.67978113],
                [ 0.3899655 , -0.82256059,  0.41357157, -0.01671969],
                [ 0.54385181, -0.01796399, -0.57330266, -0.61255739]])

        See alse
        ========

        URV(Mattools)
        '''
        result_mat = copy.deepcopy(self.mat)
        P, R1 = householder(result_mat)
        r = self.rank()
        B = R1[:r, :]
        Q, R2 = householder(B.T)
        T = R2[:r, :r]
        R = np.zeros(result_mat.shape)
        R[:r, :r] = T.T
        return P.T, R, Q

if __name__ == "__main__":
    pass


import numpy as np
import os
from Node import Node

def plurity_value(examples):
    if len(examples) < 1: return "Example list is empty. No plurity value"
    class_count = {} # class : number of appearances
    for ex in examples:
        target = ex[-1]
        class_count[target] = class_count.get(target, 0) + 1

    return max(class_count, key=class_count.get)

def all_same_class(examples):
    target = examples[0][-1]
    for ex in examples:
        if ex[-1] != target:
            return False
    return True

'''
Entropy is how much uncertainty in the dataset (examples) S
examples - the current examples for which entropy is calculated. NOTE this changes every iteration of ID3
X = set of classes / all the different targets in current dataset (examples)
p(x) = the proportion of the number of elements in class x (target) to the number of elements in the dataset (examples)

When H(S)=0, the set S is perfectly classified (i.e. all elements in current dataset are of the same class).

In ID3, entropy is calculated for each remaining attribute.

The attribute with the smallest entropy is used to split the set S on this iteration.

The higher the entropy, the higher the potential to improve the classification here.

Entropy is also often called H(S)

H(S) = entropy(S) = sum[x in X] -p(x) * log2(p(x)) ; NOTE THE minus sign in the first led!
'''
def entropy(examples):
    # find X (all the different classes \ target values in examples)
    classes = {} # class : number of examples with this class
    for ex in examples: # examples has only examples with the attribute set to the value we are testing
       classes[ex[-1]] = classes.get(ex[-1], 0) + 1

    total_number_of_examples = len(examples)
    probabilities_px = [cls / total_number_of_examples for cls in classes.values()]

    entropy = sum([-px * np.log2(px) for px in probabilities_px])
    return entropy

def informationGain(examples, attribute):
    # number of values for attribute
    # going to need to find out number of different classes
    # number of examples with a given attribute value belongs to each class

    attribute_idx = attributes.index(attribute)
    values = {}
    for ex in examples:
        values[ex[attribute_idx]] = values.get(ex[attribute_idx], 0) + 1

    subsets = []
    for value in values:
        subset_after_split = []
        for ex in examples:
            if ex[attribute_idx] == value:
                subset_after_split.append(ex)
        subsets.append(subset_after_split)

    sum_subsets_entropy = sum([ (len(subset) / len(examples)) * entropy(subset) for subset in subsets ])

    gain = entropy(examples) - sum_subsets_entropy
    return gain

def decisionTree(examples, attributes, parent_examples):
    if len(examples) < 1: return plurity_value(parent_examples)
    elif all_same_class(examples): return examples[-0][-1]
    elif len(attributes) < 1: return plurity_value(examples)

    #A <- argmax(a, Attributes)[IMPORTANCE(a,examples) : GAIN(a, examples)]
    gains = [informationGain(examples, attribute) for attribute in attributes]
    max_value = max(gains)
    max_idx = gains.index(max_value)
    attribute = attributes[max_idx] # remember this is a string, not a integer or index

    #tree <- a new deccision tree with root test A
    tree = Node(attribute)

    # for each value of attribute A
    # first find all the different attribute values
    attribute_idx = attributes.index(attribute)
    values = []
    for ex in examples:
        if ex[attribute_idx] not in values: values.append(ex[attribute_idx])

    # now we can do the foreach
    for value in values:
        ## exs_subset = e : e in examples and e.A = value
        subset_after_split = []
        for ex in examples:
            if ex[attribute_idx] == value:
                subset_after_split.append(ex)
        ## subtree = decisionTree(exs, attributes - A, examples)
        ### remove the attribute we test for in attributes for the next test
        attributs_moded = attributes[:attribute_idx] + attributes[attribute_idx:]
        subtree = decisionTree(subset_after_split, attributs_moded, examples)
        ## a a branch to tree with test (A = value) and subtree (child) = subtree
        tree.add_branch(edge=value, child=subtree)
    # return tree
    return tree

if __name__ == "__main__":
    # Preparations  START
    #examples = np.loadtxt("data/data_same_class.txt", dtype=str, comments="#")
    examples = np.loadtxt("data/data_real.txt", dtype=str, comments="#")
    attributes = ["outlook", "windy", "avalance_risk"]

    print("Examples  peak (head-5)\n{}".format(examples[:5]))
    # Preparations  END

    # Run the algorithm START
    decisionTree = decisionTree(examples, attributes, parent_examples=[])
    # Run the algorithm END

    # Print results Start
    print(decisionTree)
    # print results END


import numpy as np

def AND(x1, x2):
    x = np.array([x1, x2])
    w = np.array([0.5, 0.5])
    b = -0.7
    tmp = np.sum(w*x) + b

    if tmp <= 0:
        return 0

    return 1

def NAND(x1, x2):
    x = np.array([x1, x2])
    w = np.array([-0.5, -0.5])
    b = 0.7
    tmp = np.sum(w*x) + b

    if tmp <= 0:
        return 0

    return 1

def OR(x1, x2):
    x = np.array([x1, x2])
    w = np.array([0.5, 0.5])
    b = -0.2
    tmp = np.sum(w*x)+b

    if tmp <= 0:
        return 0

    return 1

if __name__ == "__main__":
    print('AND')
    y = AND(0, 0)
    print(y)
    y = AND(1, 0)
    print(y)
    y = AND(0, 1)
    print(y)
    y = AND(1, 1)
    print(y)
    print('--------------------')

    print('NAND')
    y = NAND(0, 0)
    print(y)
    y = NAND(1, 0)
    print(y)
    y = NAND(0, 1)
    print(y)
    y = NAND(1, 1)
    print(y)
    print('--------------------')

    print('OR')
    y = OR(0, 0)
    print(y)
    y = OR(1, 0)
    print(y)
    y = OR(0, 1)
    print(y)
    y = OR(1, 1)
    print(y)
    print('--------------------')

Deep Learning from Scratch/02 perceptron/AND_NAND_OR.py


# code adopted from Pedro Alcocer,
# https://github.com/pealco/python-mutual-information/blob/master/entropy.py

# not actually sure if this is correct, should probably check that

import numpy as np
import scipy.stats as st

def entropy(counts):
    '''Compute entropy.'''
    ps = counts / float(counts.sum())  # coerce to float and normalize
    ps = ps[np.nonzero(ps)]            # toss out zeros
    H = - (ps * np.log2(ps)).sum()   # compute entropy

    return H

def mi(x, y, bins, normalized=False):
    '''Compute mutual information'''
    counts_xy = np.histogram2d(x, y, bins=bins)[0]
    counts_x  = np.histogram(x, bins=bins)[0]
    counts_y  = np.histogram(y, bins=bins)[0]

    H_xy = entropy(counts_xy)
    H_x  = entropy(counts_x)
    H_y  = entropy(counts_y)

    if normalized:
        return (H_x + H_y - H_xy) / np.sqrt(H_x * H_y)
    else:
        return H_x + H_y - H_xy

def nmi(x, y, bins):
    return mi(x, y, bins, True)

def get_part(x, y, c):
    return ((c[0] <= x) & (x < c[1]) & (c[2] <= y) & (y < c[3]))

def mutinA(x, y):
    assert x.shape[0] > 2 and y.shape[0] > 2

    x0,x1 = np.min(x), np.max(x) + 1e-6
    y0,y1 = np.min(y), np.max(y) + 1e-6

    def calc_t(N):
        return ((N - N.mean())**2).sum() / N.mean() > 7.81

    cells = [(x0,x1,y0,y1)]
    m = []

    if get_part(x, y, cells[0]).sum() <= 2:
        return cells

    while cells:
        c = cells.pop()
        x0,x1,y0,y1 = c
        i = get_part(x, y, c)

        xm = np.median(x[i])
        ym = np.median(y[i])

        new_cells = [(x0, xm, y0, ym),
                     (xm, x1, y0, ym),
                     (x0, xm, ym, y1),
                     (xm, x1, ym, y1)]

        cell_i = [get_part(x, y, mc) for mc in new_cells]

        if calc_t(np.array([i.sum() for i in cell_i])):
            for i,mc in zip(cell_i, new_cells):
                if i.sum() <= 2:
                    m.append(mc)
                else:
                    cells.append(mc)
        else:
            m.append(c)

    return m

def mutin_mI(x, y, cells=None):
    mi = 0.0
    N = x.shape[0]
    logN = np.log(N)

    if cells is None:
        cells = mutinA(x, y)

    for k,c in enumerate(cells):
        N_k = get_part(x, y, c).sum()
        if N_k:
            N_xk = ((c[0] <= x) & (x < c[1])).sum()
            N_yk = ((c[2] <= y) & (y < c[3])).sum()
            mi += N_k * (np.log(N_k) - np.log(N_xk) - np.log(N_yk) + logN)

    return mi / N

def mi_dist(x, y, bins):
    '''Compute the MI-based distance metric'''
    counts_xy = np.histogram2d(x, y, bins=bins)[0]
    counts_x  = np.histogram(x, bins=bins)[0]
    counts_y  = np.histogram(y, bins=bins)[0]

    H_xy = entropy(counts_xy)
    H_x  = entropy(counts_x)
    H_y  = entropy(counts_y)

    return 2 - (H_x + H_y) / H_xy

# if __name__ == '__main__':
    # for s in (1.0, 2.0, 5.0):
    #     print s
    #     for n in (10, 100, 1000, 10000):
    #         x = np.random.randint(0, n, n)
    #         y = x + np.random.normal(scale=s, size=n)
    #
    #         print 'mutual information n=%d (x,y): %g\t%g' % (n, mi(x, y, np.floor(np.sqrt(n))),
    #                                                          nmi(x, y, np.floor(np.sqrt(n))))
    #
    # for n in (10, 100, 1000, 10000):
    #     x = np.random.normal(size=n)
    #     y = np.random.normal(size=n)
    #
    #     print 'mutual information n=%d (x,y): %g\t%g' % (n, mi(x, y, np.floor(np.sqrt(n))),
    #                                                      nmi(x, y, np.floor(np.sqrt(n))))


'''
    Maximum predictability evaluation module described in
    Zhao, Kai, et al. "Predicting taxi demand at high spatial resolution:
    Approaching the limit of predictability." Big Data (Big Data),
    2016 IEEE International Conference on. IEEE, 2016.

    Given the number of unique values N in the time series and its time-correlated entropy S
    the module approximates the theoretical limit of the series predictability Pmax.
'''

import numpy as np

def Function(x, N, S):
    # Calculates the value of the maximum predictability function.
    # inputs:
    #   x (evaluated maximum predictability, Pmax) real number,
    #   N number of unique values in the time series,
    #   S time-correlated entropy of the time series.
    # output:
    #   real value of the function.
    return 1.0*(-x*np.log(x)-(1-x)*np.log(1-x)+(1-x)*np.log(N-1)-S*np.log(2))

def FirstDerivative(x, N):
    # Calculates the value of the first derivative of the maximum predictability function.
    # inputs:
    #   x (evaluated maximum predictability, Pmax) real number,
    #   N number of unique values in the time series.
    # output:
    #   real value of the first derivative.
    return 1.0*(np.log(1-x)-np.log(x)-np.log(N-1))

def SecondDerivative(x):
    # Calculates the value of the second derivative of the maximum predictability function.
    # inputs:
    #   x (evaluated maximum predictability, Pmax) real number.
    # output:
    #   real value of the second derivative.
    return 1.0/((x-1)*x)

def CalculateNewApproximation(x, N, S):
    # Calculates a one-step approximation of the value of the maximum predictability function.
    # inputs:
    #   x (evaluated maximum predictability, Pmax) real number,
    #   N number of unique values in the time series,
    #   S time-correlated entropy of the time series.
    # output:
    #   real value of the function.
    function = Function(x, N, S)
    first_derivative = FirstDerivative(x, N)
    second_derivative = SecondDerivative(x)
    return 1.0*function/(first_derivative-function*second_derivative/(2*first_derivative))

def maximum_predictability(N, S):
    # Evaluates the value of the maximum predictability of the series.
    # inputs:
    #   N number of unique values in time series, integer,
    #   S time-correlated entropy of the time series, real.
    # output:
    #   value of the maximum predictability, Pmax.
    S = round(S, 9)
    if S > round(np.log2(N), 9):
        return "No solutions"
    else:
        if S <= 0.01:
            return 0.999
        else:
            x = 1.0000000001/N
            while abs(Function(x, N, S))>0.00000001:
                x = x - CalculateNewApproximation(x, N, S)
    return round(x, 10)


import numpy as np

from .general_method import Method

class Secant(Method):
    """Secant class for Secant root-finding method.
    """

    def __init__(self, f, x0, x1, tol=1e-2, max_iter=1000):
        Method.__init__(self, f=f, tol=tol, max_iter=max_iter)

        self.x0 = x0
        self.x1 = x1

    def solve(self,):
        """run the secant root-finding method starting with points x0 and x1.
        Stops if tolerance is matched or raise error if max iteration reached.

        x_n = x_(n-1) - f(x_(n-1)) * [ x_(n-1) - x_(n-2) ] / [ f(x_(n-1)) - f(x_(n-2)) ]

        Returns:
            float: computed root within interval.
        """

        ## init
        self.epochs = [self.x0, self.x1]

        ## lucky initial guess
        if 0 == self.f(self.epochs[-1]):
            return self.epochs[-1]
        if 0 == self.f(self.epochs[-2]):
            return self.epochs[-2]

        ## loop until max iter
        for _ in range(self.max_iter):
            dx = self.epochs[-1] - self.epochs[-2]
            df_x = self.f(self.epochs[-1]) - self.f(self.epochs[-2])
            self.epochs.append(self.epochs[-1] - self.f(self.epochs[-1]) * dx / df_x)

            convergence = np.abs(self.f(self.epochs[-2]) - self.f(self.epochs[-1]))
            if self.tol >= convergence:
                return self.epochs[-1]

        raise Exception(
            "Convergence error. Reached maximum number of iterations without finding a stable enough solution"
        )


from numpy import zeros,sqrt,linspace,unique
from scipy import linalg

#======================================================================
# python function to calculate element mass and stiffness matrices for
# vertical bending given beam mass per unit span "m" (mass/length) and
# flexural stiffness "EI" (Force length^2) and element size "le" (length)
#======================================================================

def element_matrices(m,EI,le):

	M 	 	= zeros((4,4))
	K 	 	= zeros((4,4))

#======================================================================
# mass matrix entries for element with uniform mass along its length
#======================================================================

	M[0,0] 	= m*13.0*le/35.0
	M[0,1] 	= m*11.0*le*le/210.0
	M[0,2]  = m*9.0*le/70.0
	M[0,3] 	= m*(-13.0)*le*le/420.0

	M[1,0] 	= M[0,1]
	M[1,1] 	= m*le*le*le/105.0
	M[1,2] 	=-M[0,3]
	M[1,3]  = m*(-le*le*le)/140.0

	M[2,0]  = M[0,2]
	M[2,1]  = M[1,2]
	M[2,2]  = M[0,0]
	M[2,3]  =-M[0,1]

	M[3,0]  = M[0,3]
	M[3,1]  = M[1,3]
	M[3,2]  = M[2,3]
	M[3,3]  = M[1,1]

#======================================================================
# stiffness matrix entries
#======================================================================

	linv 	= 1.0/le
	linsq  	= linv*linv
	lincu  	= linsq*linv
	K[0,0] 	= EI*12.0*lincu
	K[0,1]  = EI* 6.0*linsq
	K[0,2] 	=-K[0,0]
	K[0,3] 	= K[0,1]

	K[1,0] 	= K[0,1]
	K[1,1] 	= EI*4.0*linv
	K[1,2]  =-K[1,0]
	K[1,3] 	= 0.5*K[1,1]

	K[2,0] 	= K[0,2]
	K[2,1] 	= K[1,2]
	K[2,2] 	= K[0,0]
	K[2,3] 	= K[2,1]

	K[3,0] 	= K[0,3]
	K[3,1] 	= K[1,3]
	K[3,2] 	= K[2,3]
	K[3,3] 	= K[1,1]

	return M,K

#======================================================================
# python function to assemble matrices for cantilever beam vibration analysis
#======================================================================

def assemble(A_elem):

	ne 		= len(A_elem)
	ndof 	= 2*ne+2
	nbc 	= 2
	nred 	= ndof-nbc
	Ared 	= zeros((nred,nred)) 		# for a cantilever beam, 2n dof
	for k in range(ne):
		for i in range(4):
			iglobal 	= i + 2*k - nbc
			for j in range(4):
				jglobal = j + 2*k - nbc

				if(iglobal >= 0 and jglobal >= 0):
					Ared[iglobal,jglobal] = Ared[iglobal,jglobal] + A_elem[k][i,j]

	return Ared

#======================================================================
# python function to generate elements so that lumped masses are placed
# at the right ends of certain elements; also tag those elements so we
# know which elements we have to augment the mass matrix for
#======================================================================

def generate_elements(xposn,L,reqd_node=0.0):

	nodes  	= list(linspace(0,L,6))
	for x in xposn:
 		nodes.append(x)
	nodes.append(reqd_node)
	nodes 	= sorted(unique(nodes))
	ne 		= len(nodes)-1
	le 		= []
	ie 		= []
	for i in range(ne): 			 	# loop over elements
		le.append(nodes[i+1]-nodes[i])
		if(nodes[i+1] in xposn):
			ie.append(i) 				# tag element with tip mass at right end
	# print(nodes)
	# print(le)
	# print(ie)
	# quit()
	return le,ie

#======================================================================
# python function to build M,K matrices and return natural frequencies
# for a nonrotating cantilever beam
# inputs are element lengths "le", element mass/span "m" and element
# flexural stiffnesses "EI" as 3 lists
# "elem_id" = element indices which have lumped masses "Masses" at the right
# ends of those elements
#======================================================================

def beam_freq_FEM(m_list, EI_list, le_list, elem_id, Masses):
	Me      = []
	Ke 		= []

#======================================================================
# first build the regular matrix
#======================================================================
	# print('have to add tip mass for this element:',elem_id)
	ie 		= -1
	im 		= 0 						# counter for lumped masses
	# print(m_list,EI_list,le_list)
	for m,EI,le in zip(m_list,EI_list,le_list):
		ie  	= ie + 1
		M,K 	= element_matrices(m,EI,le)

#======================================================================
# append mass matrix with lumped mass at right end: modify 2,2 entry
#======================================================================

		if(ie in elem_id):
			M[2,2] 	= M[2,2] + Masses[im]
			im 		= im + 1
		Me.append(M)
		Ke.append(K)

#======================================================================
# get assembled and reduced matrices for eigenvalue solution
#======================================================================

	wn 		= nat_freq(Me,Ke)

	return wn

#======================================================================
# get natural frequencies from element matrices
#======================================================================

def nat_freq(Me, Ke):
	Mred  	= assemble(Me)
	Kred 	= assemble(Ke)
	a,b  	= linalg.eig(Kred, Mred)
	a 		= sqrt(abs(a))[-1:0:-1] 	 # sorted in ascending order
	return a


import numpy as np

def sigmoid(x):
    """Computes sigmoid of x

    Parameters
    ----------
    x : np.ndarray
        Vector for sigmoid computation

    Returns
    -------
    np.ndarray
        Sigmoid function of x
    """
    return 1 / (1 + np.exp(-x))

def mean_squared_loss(y, y_predicted):
    """Counts mean squared loss of the model

    Parameters
    ----------
    y : np.ndarray
        Class labels
    y_predicted : np.ndarray
        Values predicted by the model

    Returns
    -------
    float
        Mean squared loss of the model
    """
    N: int = y.shape[0]
    y_difference: np.ndarray = y - y_predicted

    return y_difference.dot(y_difference) / (2 * N)

def logistic_loss(y, y_predicted):
    """Counts logistic loss of Logistic regression model

    Parameters
    ----------
    y : np.ndarray
        Class labels
    y_predicted : np.ndarray
        Values predicted by the model

    Returns
    -------
    float
        Logistic loss of the regression model
    """
    N: int = y.shape[0]
    return (1 / N) * np.sum(np.log(1 + np.exp(y_predicted)) - y * y_predicted)

def compute_gradient(y, y_predicted, tx, N=1, regularization=0):
    """Computes gradient of a linear regression model with squared loss function

    Parameters
    ----------
    y : np.ndarray
        Class labels
    y_predicted : np.ndarray
        Labels predicted by model
    tx : np.ndarray
        Data
    N : int
        The size of the dataset
    regularization : int or np.ndarray

    Returns
    -------
    np.ndarray
        Gradient of a mean square loss for linear model
    """
    return -tx.T.dot(y - y_predicted) / N + regularization

def least_squares_GD(y, tx, initial_w, max_iters, gamma):
    """Linear regression using gradient descent

    Parameters
    ----------
    y : np.ndarray
        Class labels
    tx : np.ndarray
        Train data
    initial_w : np.ndarray
        Initial weights of a linear model
    max_iters : int
        Maximal number of iterations
    gamma : float
        Learning rate

    Returns
    -------
    w : np.ndarray
        The parameters of the linear model trained by GD
    float
        The mean squared loss of the model
    """
    w: np.ndarray = initial_w
    N: int = tx.shape[1]

    for _ in range(max_iters):
        w -= gamma * compute_gradient(y, tx.dot(w), tx, N)

    return w, mean_squared_loss(y, tx.dot(w))

def least_squares_SGD(y, tx, initial_w, max_iters, gamma):
    """Linear regression using stochastic gradient descent

    Parameters
    ----------
    y : np.ndarray
        Class labels
    tx : np.ndarray
        Train data
    initial_w : np.ndarray
        Initial weights of the linear model
    max_iters : int
        Maximal number of iterations
    gamma : float
        Learning rate

    Returns
    -------
    w : np.ndarray
        The parameters of the linear model trained by SGD
    float
        The mean squared loss of the final model
    """
    w: np.ndarray = initial_w
    N: int = tx.shape[1]
    random_samples: np.ndarray = np.random.randint(N, size=max_iters)

    for sample in random_samples:
        w -= gamma * compute_gradient(y[sample], tx[sample, :].dot(w), tx[sample, :])

    return w, mean_squared_loss(y, tx.dot(w))

def least_squares(y, tx):
    """Least squares regression using normal equations

    Parameters
    ----------
    y : np.ndarray
        Class labels
    tx : np.ndarray
        Train data

    Returns
    -------
    np.ndarray
        The parameters of a model
    float
        The mean squared loss of the final model
    """
    return ridge_regression(y, tx, 0)

def ridge_regression(y, tx, lambda_):
    """Ridge regression using normal equations

    Parameters
    ----------
    y : np.ndarray
        Class labels
    tx : np.ndarray
        Train data
    lambda_ : float
        The penalty term

    Returns
    -------
    w : np.ndarray
        The parameters of the linear model trained by SGD
    float
        The mean squared loss of the final model
    """
    penalization_matrix = lambda_ * np.identity(tx.shape[1])
    txT_tx_inv = np.linalg.pinv(np.dot(tx.T, tx) - penalization_matrix)
    txT_tx_inv_txT = np.dot(txT_tx_inv, tx.T)
    w = np.dot(txT_tx_inv_txT, y)

    return w, mean_squared_loss(y, tx.dot(w))

def logistic_regression(y, tx, initial_w, max_iters, gamma):
    """Logistic regression using gradient descent

    Parameters
    ----------
    y : np.ndarray
        Class labels
    tx : np.ndarray
        Train data
    initial_w : np.ndarray
        Initial weights
    max_iters : int
        Maximum number of iteration for training
    gamma : float
        Learning rate

    Returns
    -------
    w : np.ndarray
        The parameters of the linear model trained by LR
    float
        The logistic loss of the final model
    """
    w: np.ndarray = initial_w
    N: int = tx.shape[1]

    for _ in range(max_iters):
        y_predicted = sigmoid(tx.dot(w))
        w -= gamma * compute_gradient(y, y_predicted, tx, N)

    return w, logistic_loss(y.T, y_predicted)

def reg_logistic_regression(y, tx, initial_w, max_iters, gamma, lambda_):
    """Regulated logistic regression using gradient descent

    Parameters
    ----------
    y : np.ndarray
        Class labels
    tx : np.ndarray
        Train data
    initial_w : np.ndarray
        Initial weights
    max_iters : int
        Maximum number of iteration for training
    gamma : float
        Learning rate
    lambda_ : float
        Penalty for complexity

    Returns
    -------
    w : np.ndarray
        The parameters of the linear model trained by L2 logistic regression
    float
        The logistic loss with the penalization of the final model
    """
    w: np.ndarray = initial_w
    N: int = tx.shape[1]

    for _ in range(max_iters):
        y_predicted = sigmoid(tx.dot(w))
        reg_parameter = lambda_ * w
        w -= gamma * compute_gradient(y, y_predicted, tx, N, reg_parameter)

    return w, logistic_loss(y.T, y_predicted) + lambda_ / (2 * N * w.T.dot(w))


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

def Eul(func, X0 ,tmin,tmax,h):
    N=int((tmax-tmin)/h)
    t = np.linspace(tmin,tmax,N)
    X  = np.zeros([N, len(X0)])
    X[0] = X0
    for i in range(N-1):
        X[i+1] = X[i] + func(t[i],X[i]) * h
    return X,t

def RK2(func, X0,tmin,tmax,h):
    N=int((tmax-tmin)/h)
    t = np.linspace(tmin,tmax,N)
    X  = np.zeros([N, len(X0)])
    X[0] = X0
    for i in range(N-1):
        k1 =h* func(t[i],X[i])
        k2 = h*func( t[i] + h,X[i] +  k1)
        X[i+1] = X[i] +  (k1 +k2 )/2
    return X,t

def RK4(func, X0,tmin,tmax,h):
    N=int((tmax-tmin)/h)
    t = np.linspace(tmin,tmax,N)
    X  = np.zeros([N, len(X0)])
    X[0] = X0
    for i in range(N-1):
        k1 = func(t[i],X[i])
        k2 = func( t[i] + h/2,X[i] + (h* k1)/2)
        k3 = func( t[i] + h/2,X[i] + h/2* k2)
        k4 = func(t[i] + h,X[i] + h   * k3)
        X[i+1] = X[i] + h / 6. * (k1 + 2. * k2 + 2. * k3 + k4)
    return X,t

def func(t,X):
    x,y=X
    dx_dt=y+x-x**3
    dy_dt=-x
    return np.array([dx_dt, dy_dt])

IC=[[0,-1],[0,-2],[0,-3],[0,-4]];tmax=15;tmin=0;h=0.1
EA=[];R2A=[];R4A=[]
def main1():
    for d in range(0,4):
        res1=RK4(func,IC[d],tmin,tmax,h)
        w1,w2 = res1[0].T
        eu1=Eul(func,IC[d],tmin,tmax,h)
        f1,f2 = eu1[0].T
        re1=RK2(func,IC[d],tmin,tmax,h)
        k1,k2= re1[0].T
        EA.append(f1);EA.append(f2)
        R2A.append(k1);R2A.append(k2)
        R4A.append(w1);R4A.append(w2)
    t=res1[1]
    return EA,R2A,R4A,t
L=main1()
EA,R2A,R4A,t=L[0],L[1],L[2],L[3]
data1= {"t":t,"x (Eul)":EA[0],"x (RK2)":R2A[0],"x (RK4)":R4A[0],"y (Eul)":EA[1],"y (RK2)":R2A[1],"y(RK4)":R4A[1]}
data2= {"t":t,"x (Eul)":EA[2],"x (RK2)":R2A[2],"x (RK4)":R4A[2],"y (Eul)":EA[3],"y (RK2)":R2A[3],"y (RK4)":R4A[3]}
data3= {"t":t,"x (Eul)":EA[4],"x (RK2)":R2A[4],"x (RK4)":R4A[4],"y (Eul)":EA[5],"y (RK2)":R2A[5],"y (RK4)":R4A[5]}
data4= {"t":t,"x (Eul)":EA[6],"x (RK2)":R2A[6],"x (RK4)":R4A[6],"y (Eul)":EA[7],"y (RK2)":R2A[7],"y (RK4)":R4A[7]}

print("-----For x(0) = 0 and y(0) = -1-----")
print(pd.DataFrame(data1))
print("-----For x(0) = 0 and y(0) = -2-----")
print(pd.DataFrame(data2))
print("-----For x(0) = 0 and y(0) = -3-----")
print(pd.DataFrame(data3))
print("-----For x(0) = 0 and y(0) = -4-----")
print(pd.DataFrame(data4))

def xyt_g(t,x1_1,x1_2,x1_3,y1_1,y1_2,y1_3,x2_1,x2_2,x2_3,y2_1,y2_2,y2_3,x3_1,x3_2,x3_3,y3_1,y3_2,y3_3,x4_1,x4_2,x4_3,y4_1,y4_2,y4_3,tit1,tit2,tit3,tit4,titm):
     fig1, axs = plt.subplots(2, 2)
     axs[0, 0].plot(t, y1_1,marker="*",label="y (eul)")
     axs[0, 0].plot(t,x1_1,marker="*",label="x(eul)")
     axs[0, 0].plot(t,y1_2,marker="*",label="y(rk2")
     axs[0, 0].plot(t,x1_2,marker="*",label="x(rk2)")
     axs[0, 0].plot(t,y1_3,marker="*",label="y(rk4)")
     axs[0, 0].plot(t,x1_3,marker="*",label="x(rk4)")
     axs[0,0].set(xlabel = "time",title=tit1)
     axs[0,0].legend(loc = "best")
     axs[0,0].grid()
     axs[1, 0].plot(t,y2_1,marker="*",label="y (eul)")
     axs[1, 0].plot(t,x2_1,marker="*",label="x(eul)")
     axs[1, 0].plot(t,y2_2,marker="*",label="y(rk2")
     axs[1, 0].plot(t,x2_2,marker="*",label="x(rk2)")
     axs[1, 0].plot(t,y2_3,marker="*",label="y(rk4)")
     axs[1, 0].plot(t,y2_3,marker="*",label="x(rk4)")
     axs[1,0].set(xlabel = "time",title=tit2)
     axs[1,0].legend(loc = "best")
     axs[1,0].grid()
     axs[0, 1].plot(t, y3_1,marker="*",label="y (eul)")
     axs[0, 1].plot(t,x3_1,marker="*",label="x(eul)")
     axs[0, 1].plot(t,y3_2,marker="*",label="y(rk2")
     axs[0, 1].plot(t,x3_2,marker="*",label="x(rk2)")
     axs[0, 1].plot(t,y3_3,marker="*",label="y(rk4)")
     axs[0, 1].plot(t,x3_3,marker="*",label="x(rk4)")
     axs[0,1].set(xlabel = "time",title=tit3)
     axs[0,1].legend(loc = "best")
     axs[0,1].grid()
     axs[1, 1].plot(t,y4_1,marker="*",label="y (eul)")
     axs[1, 1].plot(t,x4_1,marker="*",label="x(eul)")
     axs[1, 1].plot(t,y4_2,marker="*",label="y(rk2")
     axs[1, 1].plot(t,x4_2,marker="*",label="x(rk2)")
     axs[1, 1].plot(t,y4_3,marker="*",label="y(rk4)")
     axs[1, 1].plot(t,x4_3,marker="*",label="x(rk4)")
     axs[1,1].set(xlabel = "time",title=tit4)
     axs[1,1].legend(loc = "best")
     axs[1,1].grid()
     fig1.suptitle(titm,c="brown")
     plt.show()
xyt_g(t,EA[0],R2A[0],R4A[0],EA[1],R2A[1],R4A[1],EA[2],R2A[2],R4A[2],EA[3],R2A[3],R4A[3],EA[4],R2A[4],R4A[4],EA[5],R2A[5],R4A[5],EA[6],R2A[6],R4A[6],EA[7],R2A[7],R4A[7],"x(0) = 0 and y(0) = -1","x(0) = 0 and y(0) = -2","x(0) = 0 and y(0) = -3","x(0) = 0 and y(0) = -4","x/y vs t plot")
def xy_g(x1_1,x1_2,x1_3,y1_1,y1_2,y1_3,x2_1,x2_2,x2_3,y2_1,y2_2,y2_3,x3_1,x3_2,x3_3,y3_1,y3_2,y3_3,x4_1,x4_2,x4_3,y4_1,y4_2,y4_3,tit1,tit2,tit3,tit4,main):
    fig2, axs = plt.subplots(2, 2)
    axs[0, 0].plot(x1_1, y1_1,marker=".",label="Eul")
    axs[0, 0].plot(x1_2, y1_2,marker=".",label="RK2")
    axs[0, 0].plot(x1_3, y1_3,marker=".",label="RK4")
    axs[0,0].set(xlabel = "x",ylabel="y",title=tit1)
    axs[0,0].grid()
    axs[0,0].legend()
    axs[1, 0].plot(x2_1, y2_1,marker=".",label="Eul")
    axs[1, 0].plot(x2_2, y2_2,marker=".",label="RK2")
    axs[1, 0].plot(x2_3, y2_3,marker=".",label="RK4")
    axs[1,0].set(xlabel = "x",ylabel="y",title=tit2)
    axs[1,0].grid()
    axs[1,0].legend()
    axs[0, 1].plot(x3_1, y3_1,marker=".",label="Eul")
    axs[0, 1].plot(x3_2, y3_2,marker=".",label="RK2")
    axs[0, 1].plot(x3_3, y3_3,marker=".",label="RK4")
    axs[0,1].set(xlabel = "x",ylabel="y",title=tit3)
    axs[0,1].grid()
    axs[0,1].legend()
    axs[1, 1].plot(x4_1, y4_1,marker=".",label="Eul")
    axs[1, 1].plot(x4_2, y4_2,marker=".",label="RK2")
    axs[1, 1].plot(x4_3, y4_3,marker=".",label="RK4")
    axs[1,1].set(xlabel = "x",ylabel="y",title=tit4)
    axs[1,1].grid()
    axs[1,1].legend()
    fig2.suptitle(main,c="brown")
    plt.show()
xy_g(EA[0],R2A[0],R4A[0],EA[1],R2A[1],R4A[1],EA[2],R2A[2],R4A[2],EA[3],R2A[3],R4A[3],EA[4],R2A[4],R4A[4],EA[5],R2A[5],R4A[5],EA[6],R2A[6],R4A[6],EA[7],R2A[7],R4A[7],"x(0) = 0 and y(0) = -1","x(0) = 0 and y(0) = -2","x(0) = 0 and y(0) = -3","x(0) = 0 and y(0) = -4","y vs x plot")
def func2(t,X):
    x,y=X
    dx_dt=-4*x+4*y+12
    dy_dt=-1.6*x+1.2*y+4.8
    return np.array([dx_dt, dy_dt])
IC=[0,0];tmax=6;tmin=0;h=0.1
re1=Eul(func2,IC,tmin,tmax,h)
k1,k2= re1[0].T
r1=RK2(func2,IC,tmin,tmax,h)
g1,g2= r1[0].T
e1=RK4(func2,IC,tmin,tmax,h)
f1,f2= e1[0].T
N=int((tmax-tmin)/h)
t=np.linspace(0,6,N,endpoint=True)
A1=[ ];A2=[ ]
for i in t :
    I1=-8*np.exp(-2*i)+5*np.exp(-0.8*i)+3
    I2=-4*np.exp(-2*i)+4*np.exp(-0.8*i)
    A1.append(I1)
    A2.append(I2)

fig, (ax1, ax2) = plt.subplots(2)
fig.suptitle('Ques. 2 (Coupled equation in Linear Electric Circuit)',c="r")
ax1.plot(t,A1,marker="*",label=" I1 Ana")
ax1.plot(t,A2,marker="*",label="I2 Ana")
ax1.plot(t,f1,marker="*",label=" I1 RK4")
ax1.plot(t,f2,marker="*",label="I2 RK4")
ax1.plot(t,g1,marker="*",label=" I1 RK2")
ax1.plot(t,g2,marker="*",label="I2 RK2")
ax1.plot(t,k1,marker="*",label=" I1 Eul")
ax1.plot(t,k2,marker="*",label="I2 Eul")
ax1.set(xlabel="time (in sec)",ylabel="Current(in A)",title="Current vs Time plot")
ax1.legend()
ax1.grid()
ax2.plot(A1,A2,marker="*",label="Ana")
ax2.plot(k1,k2,marker="*",label="Eul")
ax2.plot(g1,g2,marker="*",label="RK2")
ax2.plot(f1,f2,marker="*",label="RK4")
ax2.set(xlabel="I1",ylabel="I2",title="I2 vs I1 plot")
ax2.legend()
ax2.grid()
plt.show()
data={"t":re1[1],"I1 (Euler)":k1,"I2(Euler)":k2,"I1 (RK2)":g1,"I2(RK2)":g2,"I1 (RK4)":f1,"I2(RK4)":f2,"I1 (Ana)":A1,"I2(Ana)":A2,}
print(pd.DataFrame(data))


from typing import Optional, Tuple

import numpy as np
import scipy as sp
import scipy.sparse
import scipy.sparse.linalg

def basis(adj: sp.sparse.spmatrix, d: Optional[int] = None) -> Tuple[np.ndarray, np.ndarray]:
    """
    :param adj: (n, n) adjacency matrix
    :param d: () dimension, default n
    :return: (n, d) graph laplacian basis
    """
    n = adj.shape[0]
    if d is None:
        d = n
    # normalized lap
    adj = adj.tocsr()
    adj = abs((adj + adj.T) / 2)  # make adj symmetric, return csr_matrix
    deg_vec = np.array(np.sum(adj, axis=0))[0]
    deg = sp.sparse.diags(deg_vec)
    lap = deg - adj
    if d < n:
        v, w = sp.sparse.linalg.eigsh(lap, k=d, which="SM")
    else:
        v, w = sp.linalg.eigh(lap.toarray())
    w = w.astype(np.float64)
    return w.T, w

def create_grid_adj_matrix(grid_h: int, grid_w: int) -> sp.sparse.coo_matrix:
    """
    :return: adjacency of the grid graph
    """
    coord_list = []
    for h in range(grid_h):
        for w in range(grid_w):
            coord_list.append((h, w))
    n = len(coord_list)

    c2i = {coord: i for i, coord in enumerate(coord_list)}

    def is_inside(h: int, w: int) -> bool:
        if h < 0 or h >= grid_h or w < 0 or w >= grid_h:
            return False
        return True

    adj = np.zeros(shape=(n, n), dtype=bool)
    for h0, w0 in coord_list:
        for dh, dw in [(0, 1), (0, -1), (1, 0), (-1, 0)]:
            h1, w1 = h0 + dh, w0 + dw
            if is_inside(h1, w1):
                i0, i1 = c2i[(h0, w0)], c2i[(h1, w1)]
                adj[i0, i1] = True
    adj = sp.sparse.coo_matrix(adj)
    return adj


import numpy as np
from robustsp import propform
'''
 elemfits compute the Nx(N-1)/2 elemental fits, i.e., intercepts b_{0,ij}
 and slopes b_{1,ij}, that define a line y = b_0+b_1 x that passes through
 the data points (x_i,y_i) and (x_j,y_j), i<j, where i, j in {1, ..., N}.
 and the respective weights | x_i - x_j |
 INPUT:
    y : (numeric) 1darray of real-valued outputs (response vector)
    x : (numeric) 1darray vector of inputs (feature vector)
 OUTPUT:
    beta: (numeric) N*(N-1)/2 matrix of elemental fits
    w: (numeric) N*(N-1)/2 matrix of weights

 Note: Numpy uses C memory order whereas Matlab uses Fortran ordering, thus the
     indices of the solution elements are different
'''
def elemfits(y,x):

    y = np.asarray(y)
    x = np.asarray(x)

    N = len(x)
    B = np.repeat(np.arange(1,N+1)[:,np.newaxis],N,axis=1)
    A = B.T
    a = A[A<B] # order of elements different from matlab
    b = B[A<B] #
    w = x[a-1] - x[b-1]
    beta = np.zeros([2,len(a)])
    beta[1,:] = (y[a-1]-y[b-1])/w
    beta[0,:] = (x[a-1]*y[b-1] - x[b-1]*y[a-1])/w
    w= np.abs(w)

    return beta,w