import os

import numpy as np

def exp_func(x, a, b, c):
    #y = a * np.exp(-b * x) + c
    y = (a - c) * np.exp(-b * x) + c
    return y

def time_resolved_anisotropy_decay_func(t, r0, r_inf, transfer_rate):
    r_t = (r0-r_inf) * np.exp(-2 * transfer_rate * t) + r_inf
    return r_t

def two_phase_exp_decay_func(x, plateau, SpanFast, Kfast, SpanSlow, Kslow):
    """Function for two phase exponential decay.

    # variable names inspired by GraphPad
    https://www.graphpad.com/guides/prism/7/curve-fitting/reg_exponential_decay_2phase.htm?toc=0&printWindow

    Parameters
    ----------
    x : float
        x value
    plateau : float
        yvalue where curve is flat (i.e. y at infinity)
    SpanFast : float
        Parameter for fast component
    Kfast : float
        K parameter for fast component
    SpanSlow : float
        Parameter for slow component
    Kslow : float
        K parameter for fast component

    Returns
    -------
    y : float
        y value
    """
    y = plateau + SpanFast * np.exp(-Kfast * x) + SpanSlow * np.exp(-Kslow * x)
    return y

class FlourescentColours():
    """
    Object with fluorescent html colours from https://www.w3schools.com/colors/colors_crayola.asp
    """
    def __init__(self):
        self.red = "#FF355E"
        self.watermelon = "#FD5B78"
        self.orange = "#FF6037"
        self.tangerine = "#FF9966"
        self.carrot = "#FF9933"
        self.sunglow = "#FFCC33"
        self.lemon = "#FFFF66"
        self.yellow = "#FFFF66"
        self.lime = "#CCFF00"
        self.green = "#66FF66"
        self.mint = "#AAF0D1"
        self.blue = "#50BFE6"
        self.pink = "#FF6EFF"
        self.rose = "#EE34D2"
        self.magenta = "#FF00CC"
        self.pizzazz = "FF00CC"
        self.colourlist = ['#50BFE6', '#FF9933', '#66FF66', '#FFFF66', '#CCFF00', '#FF00CC', '#AAF01', '#FF6037', '#FF6EFF', 'FF00CC', '#FF355E', '#EE34D2', '#FFCC33', '#FF9966', '#FD5B78', '#FFFF66']

def get_fluorescent_colours():
    """
    DEPRECATED. Use the FlourescentColours object instead.

    Usage
    -----
    fc_dict, fl_col_keys, fl_col_list = blitzcurve.utils.get_fluorescent_colours()
    """
    # get dict and list of fluorescent colours
    #https://www.w3schools.com/colors/colors_crayola.asp
    fc_dict = {"Red":"#FF355E","Watermelon":"#FD5B78","Orange":"#FF6037","Tangerine":"#FF9966","Carrot":"#FF9933","Sunglow":"#FFCC33","Lemon":"#FFFF66","Yellow":"#FFFF66","Lime":"#CCFF00","Green":"#66FF66","Mint":"#AAF01","Blue":"#50BFE6","Pink":"#FF6EFF","Rose":"#EE34D2","Magenta":"#FF00CC","Pizzazz":"FF00CC"}
    fl_col_keys = sorted(fc_dict)
    fl_col_list = [fc_dict[k] for k in fl_col_keys]
    return fc_dict, fl_col_keys, fl_col_list

def setup_matplotlib_dark_background(plt):
    """ Initialises the dark background style of matplotlib, which applies to all following plots.
    Adds error caps to barcharts, sets figure size and dpi.

    Parameters
    ----------
    plt : matplotlib library
    """
    plt.style.use('dark_background')
    plt.rcParams['errorbar.capsize'] = 3
    plt.rcParams['figure.figsize'] = (5, 5)
    plt.rcParams["savefig.dpi"] = 240

# class OutFilepaths:
#     def __init__(self, data_dir, csv):
#         self.fits_dir = os.path.join(data_dir, "fits")
#         self.rotat_dir = os.path.join(self.fits_dir, "rotat")
#         self.savgol_dir = os.path.join(self.fits_dir, "savgol")
#         self.seg1_dir = os.path.join(self.fits_dir, "seg1")
#         self.seg2_dir = os.path.join(self.fits_dir, "seg2")
#         self.fitdata_dir = os.path.join(self.fits_dir, "fitdata")
#
#         for path in [self.fits_dir, self.rotat_dir, self.savgol_dir, self.seg1_dir, self.seg2_dir, self.fitdata_dir]:
#             if not os.path.isdir(path):
#                 os.makedirs(path)
#         self.filename = os.path.basename(csv)
#         self.rotat_fit_png = os.path.join(self.rotat_dir, self.filename[:-4] + "_rotat_fit.png")
#         self.savgol_fit_png = os.path.join(self.savgol_dir, self.filename[:-4] + "_savgol_fit.png")
#         self.savgol_fit_peak_png = os.path.join(self.savgol_dir, self.filename[:-4] + "_savgol_fit_peak.png")
#         self.savgol_fit_desc_png = os.path.join(self.seg1_dir, self.filename[:-4] + "_savgol_fit_desc.png")
#         self.exp_fit_seg1_png = os.path.join(self.seg1_dir, self.filename[:-4] + "_seg1.png")
#         self.exp_fit_seg2_png = os.path.join(self.seg2_dir, self.filename[:-4] + "_seg2.png")
#
#         self.fitdata_pickle = os.path.join(self.fitdata_dir, self.filename[:-4] + "_fitdata.pickle")

class OutDirPaths:
    """
    Creates paths for subdirectories and makes sure that they exist
    """
    def __init__(self, data_dir):
        self.fits_dir = os.path.join(data_dir, "fits")
        self.rotat_dir = os.path.join(self.fits_dir, "rotat")
        self.savgol_dir = os.path.join(self.fits_dir, "savgol")
        self.seg1_dir = os.path.join(self.fits_dir, "seg1")
        self.seg2_dir = os.path.join(self.fits_dir, "seg2")
        self.two_comp_exp_decay_dir = os.path.join(self.fits_dir, "two_phase_exp_decay")
        self.time_resolved_anisotropy_decay_dir = os.path.join(self.fits_dir, "time_resolved_anisotropy_decay")
        self.fitdata_dir = os.path.join(self.fits_dir, "fitdata")
        self.summary_figs_dir = os.path.join(data_dir, "summary", "figs")

        for path in [self.fits_dir, self.rotat_dir, self.savgol_dir, self.seg1_dir, self.seg2_dir, self.two_comp_exp_decay_dir, self.time_resolved_anisotropy_decay_dir, self.fitdata_dir, self.summary_figs_dir]:
            if not os.path.isdir(path):
                os.makedirs(path)

class FitFilePaths(OutDirPaths):
    """
    Adds file paths to the OutDirPaths object that are specific to a single sample, based on the original sample filename.
    """
    def __init__(self, data_dir, csv):
        # instantiate the parent OutDirPaths object, giving the relevant directories
        OutDirPaths.__init__(self, data_dir)
        # create various paths
        self.filename = os.path.basename(csv)
        self.rotat_fit_png = os.path.join(self.rotat_dir, self.filename[:-4] + "_rotat_fit.png")
        self.savgol_fit_png = os.path.join(self.savgol_dir, self.filename[:-4] + "_savgol_fit.png")
        self.savgol_fit_peak_png = os.path.join(self.savgol_dir, self.filename[:-4] + "_savgol_fit_peak.png")
        self.savgol_fit_desc_png = os.path.join(self.seg1_dir, self.filename[:-4] + "_savgol_fit_desc.png")
        self.exp_fit_seg1_png = os.path.join(self.seg1_dir, self.filename[:-4] + "_seg1.png")
        self.exp_fit_seg2_png = os.path.join(self.seg2_dir, self.filename[:-4] + "_seg2.png")
        self.two_comp_exp_decay_png = os.path.join(self.two_comp_exp_decay_dir, self.filename[:-4] + "_two_comp_exp_decay.png")
        self.time_resolved_anisotropy_decay_png = os.path.join(self.time_resolved_anisotropy_decay_dir, self.filename[:-4] + "_time_resolved_anisotropy_decay.png")
        self.fitdata_pickle = os.path.join(self.fitdata_dir, self.filename[:-4] + "_fitdata.pickle")

class CompareFilePaths(OutDirPaths):
    """
    Adds file paths to the OutDirPaths object that are specific to the compare function, e.g. for barcharts.
    """
    def __init__(self, data_dir):
        # instantiate the parent OutDirPaths object, giving the relevant directories
        OutDirPaths.__init__(self, data_dir)
        # barchart paths
        self.barchart_r_max = os.path.join(self.summary_figs_dir, "01_barchart_r_max.png")
        self.barchart_r_inf = os.path.join(self.summary_figs_dir, "02_barchart_r_inf.png")
        self.barchart_variable_a_png = os.path.join(self.summary_figs_dir, "03_barchart_a.png")
        self.barchart_variable_b_png = os.path.join(self.summary_figs_dir, "04_barchart_b.png")
        self.barchart_variable_c_png = os.path.join(self.summary_figs_dir, "05_barchart_c.png")
        # linechart paths
        self.linechart_savgol = os.path.join(self.summary_figs_dir, "06_linechart_savgol.png")
        self.linechart_seg1 = os.path.join(self.summary_figs_dir, "07_linechart_seg1.png")
        self.linechart_seg2 = os.path.join(self.summary_figs_dir, "08_linechart_seg2.png")


"""Probability Density function"""
import sys
import random
import matplotlib.pyplot as plt
import numpy as np

def data_generation(
        lower_range=None,
        higher_range=None,
        number_of_variables=None,
        generation_type=None
        ):
    """Data generation method"""
    if generation_type == 'linear':
        x_value = np.linspace(lower_range,higher_range,number_of_variables)
    elif generation_type == 'random':
        x_value = random.choices(range(lower_range,higher_range), k=number_of_variables)
    return x_value

def normal_dist(x_value, mean, std_dev):
    """Manually compute normal distribution"""
    prob_density = (np.pi*std_dev) * np.exp(-0.5*((x_value-mean)/std_dev)**2)
    return prob_density

def plotting_view(x_value, pdf, title):
    """Plotting code"""
    plt.plot(x_value,pdf , color = 'red')
    plt.xlabel('Data points')
    plt.ylabel('Probability Density')
    plt.title(title)
    plt.show()

def main():
    """Main operational flow"""
    lower_range = int( input("Enter the lower range (integer): ") )
    higher_range = int( input("Enter the higher range (integer): ") )
    number_of_variables = int(input("Enter the number of values (integer): "))
    generation_type = sys.argv[1]
    title = 'PDF (' + str(generation_type) + ')'

    x_value = data_generation(lower_range, higher_range, number_of_variables, generation_type)
    mean = np.mean(x_value)
    std_dev = np.std(x_value)
    pdf = normal_dist(x_value, mean, std_dev)
    plotting_view(x_value, pdf, title)

if __name__ == "__main__":
    main()


"""
Quick Question:

If we want to design an algorithm to recognize iris species, what might the data be?

Remember: we need a 2D array of size [n_samples x n_features].

    What would the n_samples refer to?

    What might the n_features refer to?

Remember that there must be a fixed number of features for each sample, and feature number i must be a similar kind of quantity for each sample.
Loading the Iris Data with Scikit-Learn

Scikit-learn has a very straightforward set of data on these iris species. The data consist of the following:

    Features in the Iris dataset:
        sepal length in cm
        sepal width in cm
        petal length in cm
        petal width in cm

    Target classes to predict:
        Iris Setosa
        Iris Versicolour
        Iris Virginica

scikit-learn embeds a copy of the iris CSV file along with a helper function to load it into numpy arrays:

"""

from sklearn.datasets import load_iris
iris = load_iris()
print(iris.keys())

n_samples, n_features = iris.data.shape
print((n_samples, n_features))
print(iris.data[0])
print(iris.data.shape)
print(iris.target.shape)
print(iris.target_names)

import numpy as np
import matplotlib.pyplot as plt

x_index = 0
y_index = 1

# this formatter will label the colorbar with the correct target names
formatter = plt.FuncFormatter(lambda i, *args: iris.target_names[int(i)])

plt.scatter(iris.data[:, x_index], iris.data[:, y_index],
            c=iris.target, cmap=plt.cm.get_cmap('RdYlBu', 3))
plt.colorbar(ticks=[0, 1, 2], format=formatter)
plt.clim(-0.5, 2.5)
plt.xlabel(iris.feature_names[x_index])
plt.ylabel(iris.feature_names[y_index]);
plt.show()


import numpy as np

import matplotlib.pyplot as plt

from multiprocessing import Pool

import os #to create temp directory

from PIL import Image #to join the images

import datetime #for timestamp
import time
start_time = time.time();

draw_single = 0;
#draw_single = False;
#draw_single = True;

mass_rows = 11;
mass_cols = 11;

temp_directory_name = "fractalgen_temp"

small_figure_size = 6;
large_figure_size = 10;
figure_dpi = 100; #100 makes for easy calculations when a divisor of img_x, img_y

if (draw_single):
	plt.figure(figsize=(large_figure_size, large_figure_size), dpi=figure_dpi)
else:
	plt.figure(figsize=(small_figure_size, small_figure_size), dpi=figure_dpi)

img_x = int(400 * 1.0)
img_y = int(400 * 1.0)

#img_x = 3000;
#img_y = 3000;

if (draw_single == False):
	img_x = int(400 * 0.25)
	img_y = int(400 * 0.25)

#img_x = 32
#img_y = 18

phi = 1.6180339887498948482;
pi = 3.1415926535897932384626433;
euler = 2.7182818284590452353602874;

#params = (0.285, 0.01, 2, 120, 5, 2.5); #default
#params = (0.6250, -0.5625, 6, 120, 5, 2.5); #dd

#c_r, c_i, exponent, iterations, limit_sq_dist, zoom
params = (0.4, -0.1, 2, 120, 5, 2.5);

c_r_low = -1.0;
c_r_high = 1.0;

c_i_low = -1.0;
c_i_high = 1.0;

if (0):
	c_r_low = -4.0;
	c_r_high = 4.0;

	c_i_low = -4.0;
	c_i_high = 4.0;

if (0):
	c_r_low = -2.0;
	c_r_high = 2.0;

	c_i_low = -2.0;
	c_i_high = 2.0;

if (0):
	c_r_low = -1.0;
	c_r_high = 1.0;

	c_i_low = -1.0;
	c_i_high = 1.0;

def draw(c_r, c_i, exponent, iterations, limit_squared_distance, zoom, filename, index, total):

	current_figure_start_time = time.time();

	c_constant = np.complex(c_r, c_i);

	data = np.zeros((img_y, img_x), np.complex64)
	display_data = np.zeros((img_y, img_x))

	# Initialize data array
	for i in range(img_y):
		for j in range(img_x):
			data[i, j] = np.complex( \
				j / img_x * zoom - (zoom/ 2), \
				i / img_y * zoom - (zoom / 2))

	# Iteration
	# ============================================

	#speed_zeros = np.zeros((img_y, img_x));
	#speed_true = np.ones((img_y, img_x), dtype=bool);

	squared_distances = np.zeros((img_y, img_x))
	iteration_data = np.full((img_y, img_x), np.nan)

	for i in range(iterations):

		#if (i % 25 == 0):
			#print("Iteration " + str(i) + " of " + str(iterations))

		squared_distances = np.add(np.power(np.real(data), 2), \
			np.power(np.imag(data), 2))

		# Will return a warning for comparing with np.nan but that's what we're trying to do
		try:
			fmin_result = np.fmin(np.full((img_y, img_x), i, dtype=int), iteration_data)
		except np.RuntimeWarning:
			pass

		iteration_data = np.select( \
			(np.greater(squared_distances, limit_squared_distance), np.ones((img_y, img_x), dtype=bool)), \
			(fmin_result, np.full((img_y, img_x), np.nan)) \
			)

#		a_constant = np.complex(-1.0, 1.0);
		a_constant = np.complex(1.0, 0.0);

		# The line that performs the iteration. Change the formula to obtain new classes of fractals.
		data = np.select( \
			(np.less_equal(squared_distances, limit_squared_distance), np.ones((img_y, img_x), dtype=bool)), \
			(np.power(data, exponent) + c_constant, data) \
			)

		#data = np.select( \
	#		(np.less_equal(squared_distances, limit_squared_distance), np.ones((img_y, img_x), dtype=bool)), \
	#		(np.exp(np.complex(0.0, 1.0) * np.power(data, exponent)) + c_constant, data) \
	#		)

	# Sets remaining values for coordinates didn't hit the limit to iteration (one above max of others)
	iteration_data = np.select( \
		(np.isnan(iteration_data), np.ones((img_y, img_x), dtype=bool)),	\
		(np.full((img_y, img_x), iterations, dtype=np.int32), iteration_data) \
		)

	# ============================================

	#print(data[50, 60])

	display_data = np.copy(iteration_data)

	plt.title(format(c_r, '') + ", " + format(c_i, ''))

	plt.xticks([])
	plt.yticks([])

	plt.pcolor(display_data)

	plt.tight_layout()

	if (draw_single == False):
		plt.subplots_adjust(left=0.025, right=0.975, top=0.95, bottom=0.0)
	else:
		plt.subplots_adjust(left=0.03, right=0.97, top=0.97, bottom=0.03)

	#print("Finished drawing subplots after " + format(time.time() - start_time, '.2f') + " seconds.")

	#plt.colorbar()

	#plt.ylabel('y')
	#plt.show()

	plt.savefig(filename)
	plt.clf();

#	draw_rate = float(index + 1.0) / (time.time() - start_time);
#	time_remaining = (total - index - 1) / draw_rate;

	# We include the time it took to draw each subplot because of a past incident where there was slowdowns due to memory leakage
	print("Drew " + filename + " in " + format(time.time() - current_figure_start_time, '.2f') + " seconds. Time elapsed: " + format(time.time() - start_time, '.2f') + " seconds.")

#	print ("Drawing " + str(index + 1) + " of " + str(total))
#	print("Drew " + filename + " in " + format(time.time() - current_figure_start_time, '.2f') + " seconds. Time remaining: " + format(time_remaining, '.2f') + " seconds.")

if __name__ == '__main__':

	if (draw_single):
		mass_rows = 1;
		mass_cols = 1;

		c_r_low = params[0];
		c_r_high = params[0];

		c_i_low = params[1];
		c_i_high = params[1];

	print("mass_rows: " + str(mass_rows));
	print("mass_cols: " + str(mass_cols));

	call_list = [];

	for c_r_counter in range(mass_rows):

		c_constant_real = 0.0;
		if (mass_rows == 1):
			c_constant_real = (c_r_low + c_r_high) / 2;
		else:
			c_constant_real = (float(c_r_counter) / (mass_rows - 1)) * (c_r_high - c_r_low) + c_r_low

		for c_i_counter in range(mass_cols):

			c_constant_imag = 0.0;
			if (mass_cols == 1):
				c_constant_imag = (c_i_low + c_i_high) / 2;
			else:
				c_constant_imag = (float(c_i_counter) / (mass_cols - 1)) * (c_i_high - c_i_low) + c_i_low

			exponent = params[2];
			iterations = params[3];
			limit_squared_distance = params[4];
			zoom = params[5]

			filename = temp_directory_name + "/out_" + str(c_r_counter) + "," + str(c_i_counter) + ".png";

			call_list.append((c_constant_real, c_constant_imag, exponent, iterations, limit_squared_distance, zoom, filename, \
				c_r_counter * mass_cols + c_i_counter, mass_cols * mass_rows))

	if not os.path.exists(temp_directory_name):
		os.makedirs(temp_directory_name)

#	print(call_list);
	p = Pool(12);
	p.starmap(draw, call_list)

	# ToDO: Get this from the plt.figure
	each_image_size_x = small_figure_size * figure_dpi;
	each_image_size_y = small_figure_size * figure_dpi;

	if (draw_single):
		each_image_size_x = large_figure_size * figure_dpi;
		each_image_size_y = large_figure_size * figure_dpi;

	joined_im = Image.new('RGB', (each_image_size_x * mass_cols, each_image_size_y * mass_rows));

	# Join the images
	for c_r_counter in range(mass_rows):

		for c_i_counter in range(mass_cols):

			current_index = c_r_counter * mass_cols + c_i_counter;
			current_image_filename = call_list[current_index][6];
			current_im = Image.open(current_image_filename);

			joined_im.paste(current_im, (c_i_counter * each_image_size_x, c_r_counter * each_image_size_y));

	timestamp = format(datetime.datetime.now(), "%Y%m%d-%H%M%S");
	filename = "";
	if (params[2]) < 0:
		filename = "output-expneg" + str(abs(params[2])) + "-" + timestamp + ".png";
	else:
		filename = "output-exp" + str(params[2]) + "-" + timestamp + ".png";
	joined_im.save(filename)

	print("Saved to " + filename)


'''
File to perform common conversions between different conventions (rates quoting, day counts...)
'''
import numpy as np
import pandas as pd
import datetime
from datetime import date
from dateutil.relativedelta import relativedelta
import calendar

def iseom(date):
    ''' given a datetime.date, returns whether that date is at the end of a month '''
    y = date.year
    m = date.month
    return calendar.monthrange(y, m)[1] == date.day

def count_days(date_1, date_2, method='30I/360', T=None):
    '''
    :param date_1: datetime.date objects: date_1 and date_2, start date
    :param date_2: end date
    :param method: String: method, desired convention ('30I/360', 'actual')
    :param T: datetime.date object (maturity)
    :return: int: the day count between dates, using given method
    '''
    if (method == 'actual'):
        delta = date_2 - date_1
        return abs(delta.days)

    if (method == '30I/360'):
        y2 = date_2.year
        y1 = date_1.year
        m2 = date_2.month
        m1 = date_1.month
        d2 = date_2.day
        d1 = date_1.day
        if iseom(date_1):  # If D1 is last day of month...
            d1 = 30  # ... change D1 to 30
        # If D2 is last day of month and (date 2 is not maturity or M2 is not February)...
        if iseom(date_2) and (date_2 != T or m2 != 2):
            d2 = 30
        return abs(360 * (y2 - y1) + 30 * (m2 - m1) + (d2 - d1))

    if (method == '30/360'):
        # TODO: add special cases here
        y2 = date_2.year
        y1 = date_1.year
        m2 = date_2.month
        m1 = date_1.month
        d2 = date_2.day
        d1 = date_1.day
        return abs(360 * (y2 - y1) + 30 * (m2 - m1) + (d2 - d1))

def discount_from_zero(date_1, date_2, zero_rate, make_pct=True, met='30I/360', freq=2):
    '''
    given the (annual) rate listed and dates between which the rate applies, returns corresponding discount factor
    (implied by the zero rate)
    :param date_1: the settlement date for the given zero (beginning of the zero)
    :param date_2: the maturity date we will have the discount factor for
    :param zero_rate: the corresponding zero rate (double)
    :param make_pct: boolean, whether we give the formula some percents
    :param met: the method for computing days, '30I/360 by default
    :param freq: int, how often compounded / year (by default 2, semiannually-compounded)
    :return: double, the corresponding zero rate
    '''
    if make_pct:
        zero_rate /= 100
    num_days = count_days(date_1, date_2, method=met, T=date_2)
    return 1 / (1 + zero_rate / freq) ** (freq * num_days / 360)

def forward_from_discount(discounts):
    '''
    :param discounts: Pandas series indices keys being dates, and values being the discount factor that applies
        from date1 to that key
    :return: Pandas series with the forward rate implied by the discount, for each date in index
    '''
    fwd = pd.Series()
    for i in range(len(discounts) - 1):
        fwd_rate = (discounts.iloc[i] / discounts.iloc[i + 1] - 1) * 360 / count_days(discounts.index[i],
                                                                                discounts.index[i + 1],
                                                                                method='actual') * 100
        to_append = pd.Series(data=[fwd_rate],
                              index=[discounts.index[i]])
        fwd = fwd.append(to_append)
    return fwd


# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.

from typing import Optional

import networkx as nx
import numpy as np
import scipy as sp

from .embedding_container import EmbeddingContainer
from .spectral_embedding import _create_augmented_adjacency_matrix, \
    _generate_embedding
from .. import assertions

def laplacian_embedding(
        graph: nx.Graph,
        maximum_dimensions: int = 100,
        elbow_cut: Optional[int] = 1,
        weight_column: str = 'weight',
        svd_seed: Optional[int] = None,
        num_iterations: int = 5,
        power_iteration_normalizer: str = 'QR',
        num_oversamples: int = 10
) -> EmbeddingContainer:
    """
    Generates a spectral embedding based upon the Laplacian matrix of the graph.

    See also: https://csustan.csustan.edu/~tom/Clustering/GraphLaplacian-tutorial.pdf

    :param networkx.Graph graph: A networkx Graph object containing no more than one connected component.  Note that if
        the graph is a directed graph, the resulting dimensionality of the embedding will be twice that of an
        undirected graph
    :param int maximum_dimensions: Maximum dimensions of embeddings that will be returned - defaults to 100.  Actual
        dimensions of resulting embeddings should be significantly smaller, but will never be over this value.
    :param Optional[int] elbow_cut: scree plot elbow detection will detect (usually) many elbows.  This value specifies
        which elbow to use prior to filtering out extraneous dimensions. If None, then an embedding of size
        `maximum_dimensions` will be returned.
    :param str weight_column: The weight column to use in the Graph.
    :param Optional[int] svd_seed: If not provided, uses a random number every time, making consistent results difficult
        Set this to a random int if you want consistency between executions over the same graph.
    :param int num_iterations: The number of iterations to be used in the svd solver.
    :param int num_oversamples: Additional number of random vectors to sample the range of M so as
        to ensure proper conditioning. The total number of random vectors
        used to find the range of M is n_components + n_oversamples. Smaller
        number can improve speed but can negatively impact the quality of
        approximation of singular vectors and singular values.
    :param Optional[str] power_iteration_normalizer:
        Whether the power iterations are normalized with step-by-step
        QR factorization (the slowest but most accurate), 'none'
        (the fastest but numerically unstable when `n_iter` is large, e.g.
        typically 5 or larger), or 'LU' factorization (numerically stable
        but can lose slightly in accuracy). The 'auto' mode applies no
        normalization if `num_iterations` <= 2 and switches to LU otherwise.

        Options: 'auto' (default), 'QR', 'LU', 'none'
    :return: EmbeddingContainer containing a matrix, which itself contains the embedding for each node.  the tuple also
        contains a vector containing the corresponding vertex labels for each row in the matrix.  the matrix and vector
        are positionally correlated.
    :rtype: EmbeddingContainer
    """
    assertions.assert_single_connected_components(
        graph,
        "Run this algorithm over the largest connected component (see: topologic.largest_connected_component()) or " +
        "run it over every connected component separately."
    )

    working_graph = graph.copy()

    augmented_graph, graph_matrix, sorted_vertices = _create_augmented_adjacency_matrix(weight_column, working_graph)

    minimum_matrix_dimension = min(graph_matrix.shape)

    # make Laplacian matrix (DAD)
    in_degree = graph_matrix.sum(axis=0).astype(float)
    out_degree = graph_matrix.sum(axis=1).T.astype(float)

    in_degree_array = np.squeeze(np.asarray(in_degree))
    out_degree_array = np.squeeze(np.asarray(out_degree))

    in_diagonal = sp.sparse.diags(in_degree_array ** (-0.5))
    out_diagonal = sp.sparse.diags(out_degree_array ** (-0.5))

    lse_matrix = out_diagonal.dot(graph_matrix).dot(in_diagonal)

    embedding = _generate_embedding(
        elbow_cut,
        nx.is_directed(graph),
        lse_matrix,
        maximum_dimensions,
        minimum_matrix_dimension,
        num_oversamples,
        num_iterations,
        power_iteration_normalizer,
        svd_seed
    )

    result = EmbeddingContainer(embedding=embedding, vertex_labels=sorted_vertices)

    return result

import numpy,import scipy,import networkx


import numpy as np
from scipy import optimize
import matplotlib.pyplot as plt

def adjiman(x):
    F = np.cos(x[0])*np.sin(x[1]) - ((x[0])/(x[1]**2 +1.0))
    return F

# optimization bounds
bounds = ((-10.0,10.0),
          (-10.0,10.0))
# run differential evolution
res = optimize.differential_evolution(adjiman, bounds, maxiter=1000, popsize=50, disp=True)

# this l bfgs b won't find the optimum
res2 = optimize.fmin_l_bfgs_b(adjiman, (-2,-2), approx_grad=True, bounds=bounds)

# hower this l bfgs b will
res3 = optimize.fmin_l_bfgs_b(adjiman, (2,2), approx_grad=True, bounds=bounds)

x0 = np.linspace(-10,10,100)
x1 = np.linspace(-10,10,100)
x0, x1 = np.meshgrid(x0,x1)

F = adjiman((x0,x1))

plt.figure()
plt.contourf(x0,x1,F)
plt.colorbar()
plt.show()


# -*- coding: utf-8 -*-
"""
Created on Sun Dec 12 15:59:10 2021
@author: joelr
Estudos da Biblioteca Numpy
"""
import numpy as np

my_array = np.array([[1, 2, 3, 4 , 5], [6, 7, 8, 9, 10 ]])

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

x = my_array.copy()

print(my_array)
print(my_array.shape[0]) #Mostra n elementos
print(np.arange(10)) #Cria vetor sequencial
print(my_array[1,1])

my_array = np.empty([2,5]) #cria valores aleatórios
for i in range(2):
    for j in range(5):
        my_array[i,j] = i*j
        print(my_array[i,j])

my_array = np.zeros([2,5]) #cria matriz de zeros
my_array = np.ones([2,5]) #cria matriz de 1
aleatorio = np.random.random()
my_array = np.random.random([2,5])
print(my_array[1,0:4])
print(my_array[:,3])
print(my_array[1][2])
print(type(my_array))

a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6], [7, 8]])

soma = a+b
difference = a-b
product = a*b
quotient = a/b

matrix_product = a.dot(b)

for i in range(my_array.shape[0]):
    for j in range(my_array.shape[1]):
        for k in range(my_array.shape[2]):
            print(my_array[i,j,k])

my_array = np.array([1, 2, 3, 4], ndmin=5)
print(my_array.ndim)

print(my_array[1, -1])#indice negativo acessa o final do vetor
print(my_array[:,0:4:2])
print(my_array[:,-3:-1])
print(my_array[:,::2])

my_array2 = np.array([1, 2, 3, 4], dtype='S')
my_array2= np.array(['banana', 'maçã','3'])
print(my_array.dtype)

for i in my_array:
    for j in i:
        print(j)

arr = np.array([[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]])

for x in arr:
  print("x represents the 2-D array:")
  print(x)

arr1 = np.array([1, 2, 3])

arr2 = np.array([4, 5, 6])

arr = np.concatenate((arr1, arr2))

print(arr)

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

arr2 = np.array([[5, 6], [7, 8]])

arr = np.concatenate((arr1, arr2), axis=1)

print(arr)

arr1 = np.array([1, 2, 3])

arr2 = np.array([4, 5, 6])

arr = np.stack((arr1, arr2), axis=1)

print(arr)

word = "apartamento"
for i in range(len(word)-1,-1,-1):
    print(word[i])

np.array_split(my_array,2)

x = np.where(my_array == 4) #tuple com linha e coluna

x = np.where(my_array[0,:]%2 == 0) #posição do número é impar
x = np.where(my_array[0,:]%2 == 1) #posição do número par

arr = np.array(['banana', 'cherry', 'apple'])
arr = np.array([True, False, True])
arr = np.array([[3, 2, 4], [5, 0, 1]])

print(np.sort(arr))

#Filtros

arr = np.array([41, 42, 43, 44])#mask

x = [True, False, True, False]

newarr = arr[x]

print(newarr)

my_array = np.array([41, 42, 43, 44])

# Create an empty list
filter_arr = []

# go through each element in arr
for element in arr:
  # if the element is higher than 42, set the value to True, otherwise False:
  if element > 42:
    filter_arr.append(True)
  else:
    filter_arr.append(False)

new_array = my_array[filter_arr]

print(filter_arr)
print(new_array)

arr = np.array([41, 42, 43, 44])

filter_arr = arr > 42

newarr = arr[filter_arr]

print(filter_arr)
print(newarr)

#Comandos Ramdom
from numpy import random
x = random.randint(100)#Random inteiro
x = random.rand()
x = random.rand(5)
x = random.rand(3,5)
x=random.randint(100, size=(5,5))
x = random.choice([3, 5, 7, 9]) #retorna um valor do vetor
x = random.choice([3,4,5, 6,7,8], size=(3,5)) #Constrói a matrix puxando números da base referida

#configura probabolidade de escolha dos números
x = random.choice([3, 5, 7, 9], p=[0.1, 0.3, 0.6, 0.0], size=(100))
x = random.choice([3, 5, 7, 9], p=[0.1, 0.3, 0.6, 0.0], size=(3, 5))

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

random.shuffle(my_array) #embaralha os números e altera a matriz
random.permutation(my_array) #embaralha os números criando uma nova matriz

import matplotlib.pyplot as plt
import seaborn as sns

sns.distplot([0, 1, 2, 3, 4, 5], hist = True)
plt.show()

#Distribuição Normal Analógica
# loc - (Mean) where the peak of the bell exists.

# scale - (Standard Deviation) how flat the graph distribution should be.

# size - The shape of the returned array.

x = random.normal(size=(100, 1000))
x = random.normal(loc=0, scale=5, size=(100, 100))
sns.distplot(x, hist = True)
plt.show()

#distribuição binomial (Normal Digital)
# n - number of trials.
# p - probability of occurence of each trial (e.g. for toss of a coin 0.5 each).

# size - The shape of the returned array.
x = random.binomial(n=10, p=0.5, size=10)
x = random.binomial(n=10, p=0.9, size=1000)
sns.distplot(x, hist=True, kde=False)
plt.show()

#comparação entre métodos
sns.distplot(random.normal(loc=50, scale=5, size=1000), hist=False, label='normal')
sns.distplot(random.binomial(n=100, p=0.5, size=1000), hist=False, label='binomial')
plt.show()

# Poisson Distribution is a Discrete Distribution.

# It estimates how many times an event can happen in a specified time. e.g. If someone eats twice a day what is probability he will eat thrice?

# It has two parameters:

# lam - rate or known number of occurences e.g. 2 for above problem.

# size - The shape of the returned array.

x = random.poisson(lam=2, size=10)
sns.distplot(random.poisson(lam=7, size=10000), kde=False)
plt.show()

#comparação entre normal e poisson
sns.distplot(random.normal(loc=50, scale=7, size=1000), hist=False, label='normal')
sns.distplot(random.poisson(lam=50, size=1000), hist=False, label='poisson')
plt.show()

# Uniform Distribution
# Used to describe probability where every event has equal chances of occuring.

# E.g. Generation of random numbers.

# It has three parameters:

# a - lower bound - default 0 .0.

# b - upper bound - default 1.0.

# size - The shape of the returned array.

sns.distplot(random.uniform(size=100), hist=False)
plt.show()

# Logistic Distribution
# Logistic Distribution is used to describe growth.

# Used extensively in machine learning in logistic regression, neural networks etc.

# It has three parameters:

# loc - mean, where the peak is. Default 0.

# scale - standard deviation, the flatness of distribution. Default 1.

# size - The shape of the returned array.

sns.distplot(random.logistic(size=1000), hist=False)
plt.show()

#gaussiana x logistic distribuition
sns.distplot(random.normal(scale=2, size=1000), hist=False, label='normal')
sns.distplot(random.logistic(size=1000), hist=False, label='logistic')
plt.show()

# Pareto Distribution
# A distribution following Pareto's law i.e. 80-20 distribution (20% factors cause 80% outcome).

# It has two parameter:

# a - shape parameter.

# size - The shape of the returned array.

sns.distplot(random.pareto(a=2, size=1000), kde=False)
plt.show()

### Functions
#Somar elementos entre vetores
x = [1, 2, 3, 4] #lista
y = [4, 5, 6, 7]

x = np.array([1, 2, 3])
y = np.array([5, 6, 7])
z = np.add(x, y)
z = np.sum([x,y])
z = np.subtract(x, y)
z = np.multiply(x, y)
z = np.divide(x, y)
z = np.power(x,y)
z = np.mod(x,y)
z = np.remainder(x,y)
z = np.divmod(x,y)
z = np.absolute(x,y)
print(z)

x = np.array([-1, 2.555, 3.9])
z = np.trunc(x)
z = np.fix(x)
z = np.around(x)
z = np.floor(x)
z = np.ceil(x)

x = np.arange(1,10)#não inclui o 10
z = np.log2(x)
z = np.log10(x)
z = np.log()

x = np.array([1, 2, 3])
z = np.prod(x)
z = np.prod([x,x])

# Finding LCM (Lowest Common Multiple)
num1 = 4
num2 = 6
x = np.lcm(num1, num2)

#Finding GCD (Greatest Common Denominator)
num1 = 6
num2 = 15
x = np.gcd(num1,num2)
x = np.gcd.reduce([3,5,8,9,24])

x = np.sin(np.pi/2)
arr = np.array([np.pi/2, np.pi/3, np.pi/4, np.pi/5])
x = np.sin(arr)

arr = np.array([90, 180, 270, 360])
x = np.deg2rad(arr) #converte para radianos

x = np.sinh(np.pi/2) #função hiperbólica
x = np.arcsinh(1.0)


'''
Code given from professor Michael Plexousakis, instructor of the course.
http://users.tem.uoc.gr/~plex/mem253-Fall2017/
'''
import numpy as np

def trislv(d, e, f, b):
	"""
	Solve the tridiagonal system

	[ d1    f1                          ] [     x_1 ]   [     b_1 ]
	[ e2    d2    f2                    ] [     x_2 ]   [     b_2 ]
	[       ..    ..   ..               ] [     ... ] = [     ... ]
	[         e_{N-1}  d_{N-1}  f_{N-1} ] [ x_{N-1} ]   [ b_{N-1} ]
        [	               e_N      d_N ] [     x_N ]   [     b_N ]
	"""
	n = b.size
	x = np.zeros(n)

	for i in range(1,n):
		e[i] = e[i] / d[i-1]
		d[i] = d[i] - f[i-1] * e[i]

	for i in range(1,n):
		b[i] = b[i] - e[i] * b[i-1]

	x[n-1] = b[n-1] / d[n-1]
	for i in range(n-2,-1,-1):
		x[i] = (b[i] - f[i] * x[i+1]) / d[i]

	return x

# Test the solution algorithm

if __name__ ==  "__main__":
	n = 20

	d =    4*np.ones(n)
	e = (-1)*np.ones(n)
	f = (-1)*np.ones(n)

	b = np.ones(n)
	x = trislv(d, e, f, b)

# Compare with solve from numpy.linalg

	d =    4*np.ones(n);
	e = (-1)*np.ones(n-1)
	A = np.diag(e, -1) + np.diag(d, 0) + np.diag(e, 1)

	b = np.ones(n)
	y = np.linalg.solve(A,b)

	for j in range(n): print("x[%2d] = %f  y[%2d] = %f  Error = %e" % (j, x[j], j, y[j], abs(x[j]-y[j])))

Finite Elements Method (given code)/trisolv.py


import numpy as np
from functools import reduce
from propagate import _propagate_forward, _cost

def check_gradient(parameters, gradients, X, Y, epsilon=1e-7):
  parameters_values = _dictionary_to_vector(parameters)
  grad = _dictionary_to_vector(gradients, key_bases=["dW", "db"])
  num_parameters = parameters_values.shape[0]
  J_plus = np.zeros((num_parameters, 1))
  J_minus = np.zeros((num_parameters, 1))
  gradapprox = np.zeros((num_parameters, 1))
  for i in range(num_parameters):
    # Compute J_plus[i]. Inputs: "parameters_values, epsilon". Output = "J_plus[i]".
    thetaplus = np.copy(parameters_values)
    thetaplus[i][0] = thetaplus[i][0] + epsilon
    AL = _propagate_forward(_vector_to_dictionary(thetaplus, parameters), X)[0]
    J_plus[i] = _cost(AL, Y)

    # Compute J_minus[i]. Inputs: "parameters_values, epsilon". Output = "J_minus[i]".
    thetaminus = np.copy(parameters_values)
    thetaminus[i][0] = thetaminus[i][0] - epsilon
    AL = _propagate_forward(_vector_to_dictionary(thetaminus, parameters), X)[0]
    J_minus[i] = _cost(AL, Y)

    # Compute gradapprox[i]
    gradapprox[i] = (J_plus[i] - J_minus[i]) / (2*epsilon)

  # Compare gradapprox to backward propagation gradients by computing difference.
  # print(gradapprox)
  numerator = np.linalg.norm(grad - gradapprox)
  denominator = np.linalg.norm(grad) + np.linalg.norm(gradapprox)
  difference = numerator/denominator

  if difference > 2e-7:
      print ("\033[93m" + "There is a mistake in the backward propagation! difference = " + str(difference) + "\033[0m")
  else:
      print ("\033[92m" + "Your backward propagation works perfectly fine! difference = " + str(difference) + "\033[0m")

  return difference

def _dictionary_to_vector(dictionary, key_bases=["W", "b"]):
  vector = np.zeros((0,1))
  L = len(dictionary) // 2
  for l in range(L):
    for key in key_bases:
      new_vector = np.reshape(dictionary[key + str(l + 1)], (-1, 1))
      vector = np.concatenate((vector, new_vector), axis=0)

  return vector

def _vector_to_dictionary(vector, parameters, key_bases=["W", "b"]):
  result = {}
  L = len(parameters) // 2
  count = 0
  for l in range(L):
    for key in key_bases:
      shape = parameters[key + str(l + 1)].shape
      size = reduce(lambda x, y: x*y, shape)
      result[key + str(l + 1)] = vector[count: count + size].reshape(shape)
      count += size

  return result


from scipy.integrate import odeint
import pandas as pd
import numpy as np

# The SIR model differential equations.
def SEIR(y, t, N, beta, gamma, sigma):

    S, E, I, R = y
    dSdt = - beta * S * I / N
    dEdt = beta * S * I / N - sigma * E
    dIdt = sigma * E - gamma * I
    dRdt = gamma * I

    return dSdt, dEdt, dIdt, dRdt

def entrypoint(current_state, model_parameters):
    """
        model_parameters:
            seir:
                sick_days:
                i2_percentage: Severe infection
                i3_percentage: Critical infection
                days_from_t0: Days of simulation
                sigma: rate of progression from the exposed to infected class

        current_state:
            N: Poupulation
            S: Initial Susceptible individuals
            E: Initial Exposed
            I: Initial Infected
            R: Initial Recovered individuals, who have recovered from disease and are now immune
        MODEL PARAMETERS:
            * beta:  rate at which infected individuals in class I contact Susceptibles and Infect them
            * gamma: rate at which infected individuals in class I Recover from disease and become immune
            * sigma: rate of progression from the exposed to infected

        OBS: TODO
            Exposed is being estimated given diseases growth of 33% per day.
    """
    args = {
        'y0': (current_state['suceptible'],                                         # S
               current_state['exposed'],                                            # E
               current_state['current_infected'],                                   # I
               current_state['recovered']                                           # R
              ),
        't': np.linspace(0, model_parameters['days_from_t0'], model_parameters['days_from_t0']+1),
        'args': (current_state['population'],                            # N
                 model_parameters['R0'] / model_parameters['sick_days'], # beta
                 1. / model_parameters['sick_days'],                     # gamma
                 model_parameters['sigma'],                              # sigma
                )
    }

    result = odeint(SEIR, **args)
    result = pd.DataFrame(result, columns=['S' ,'E' ,'I', 'R'])

    result['days'] = args['t']
    result['I2'] = result['I'] * model_parameters['i2_percentage'] / 100 # severe cases
    result['I3'] = result['I'] * model_parameters['i3_percentage'] / 100 # critical cases
    result['I1'] = result['I'] - result['I2'] - result['I3']             # mild cases

    return result


#NumPy Operations:
#   -Array with Array
#   -Array with Scalars
#   -Universal Array Functions

import numpy as np

#Instantiate 1D-array
arr = np.arange(0,11)

#Adding array values
arr + arr #Note: Array index value will add up together.
          #e.g, arr[1,2,3,4,5] + arr[1,2,3,4,5] = arr[2,4,6,8,10]
          #Warning, both arrays have equal-sizing, otherwise an error will be thrown

#Substracting array values
arr - arr #Note: Array index value will subtract each other's value.
          #e.g, arr[1,2,3,4,5] - arr[1,2,3,4,5] = arr[0,0,0,0,0]
          #Warning, both arrays have equal-sizing, otherwise an error will be thrown

#Multiplying array values
arr * arr #Note: Each Array index value will multiply together.
          #e.g, arr[1,2,3,4,5] * arr[1,2,3,4,5] = arr[1,4,9,16,25]
          #Warning, both arrays have equal-sizing, otherwise an error will be thrown

#Array with Scalar Operations
#Scalar means a single number; basically means if we do arr + 1, it will increase all of the index's value by one.

#Scalar Operation: Addition
arr + 100 # arr[1,2,3,4,5] + 100 = arr[101,102,103,104,105]

#Scalar Operation: Substraction
arr - 100 # arr[1,2,3,4,5] - 100 = arr[-99,-98,-97,-96,-95]

#Scalar Operation: Multiplication
arr * 100 # arr[1,2,3,4,5] * 100 = arr[100,200,300,400,500]

#Universal Array Functions

#Squareroot
np.sqrt(arr)#Retrieves a square of everything on all of the array

#Retrieve highest value of the array
np.max(arr)

#Retrieves lowest value of the aray
np.min(arr)

#Sin
np.sin(arr)

#Log
np.log(arr)

#For more information regarding this topic, go-to https://docs.scipy.org/doc/numpy/reference/ufuncs.html


import matplotlib.pyplot as plt
import numpy as np
from scipy.stats.distributions import chi2

def drawEllipse(C, x):
    alpha = 0.95
    # Calculate unscaled half axes
    sxx = C[0,0]
    syy = C[1,1]
    sxy = C[0,1]
    a = np.sqrt(0.5*(sxx + syy + np.sqrt((sxx-syy)**2 + 4*sxy**2)))   # always greater
    b = np.sqrt(0.5*sxx + syy - np.sqrt((sxx-syy)**2 + 4*sxy**2))   # always smaller

    # Remove imaginary parts in case of neg. definite C
    a = np.real(a)
    b = np.real(b)

    # Scaling in order to reflect specified probability
    a = a*np.sqrt(chi2.ppf(alpha,2))
    b = b*np.sqrt(chi2.ppf(alpha,2))

    # Look where the greater half axis belongs to
    if sxx < syy:
        swap = a
        a = b
        b = swap

    # Calculate inclination (numerically stable)
    if sxx != syy:
        angle = 0.5*np.arctan(2*sxy/(sxx-syy))
    elif sxy == 0:
        angle = 0     # angle doesn't matter
    elif sxy > 0:
        angle =  pi/4
    elif sxy < 0:
        angle = -pi/4

    # Draw ellipse
    # Constants
    NPOINTS = 100                   # point density or resolution

    # Compose point vector
    ivec = np.arange(0,2*np.pi,2*np.pi/NPOINTS)     # index vector
    p = np.zeros((2,ivec.size))

    p[0,:] = a*np.cos(ivec)           # 2 x n matrix which
    p[1,:] = b*np.sin(ivec)           # hold ellipse points

    # Translate and rotate
    xo = x[0]
    yo = x[1]
    R  = np.array([[np.cos(angle), -np.sin(angle)], [np.sin(angle), np.cos(angle)]])
    T  = np.array([xo, yo]).reshape(-1,1)*np.ones((1,ivec.size))
    p = R.dot(p) + T
    return p

mu0 = np.array([1,2])
C0 = np.eye(2)*0.1

mu1 = np.array([0.33, -0.67])
C1 = np.array([[0.336018, 0.150879],[0.150879,  2.83917]])

p0 = drawEllipse(C0, mu0)
p1 = drawEllipse(C1, mu1)

# Plot
plt.axis("equal")
plt.plot(p0[0,:],p0[1,:],'r',label='original dist')
plt.plot(mu0[[0]],mu0[[1]],'ro')
plt.plot(p1[0,:],p1[1,:],'b',label='new dist')
plt.plot(mu1[[0]],mu1[[1]],'bo')
plt.legend()
plt.grid()
plt.xlabel("X")
plt.ylabel("Y")
plt.title("Unscented Transform")

plt.show()


import numpy as np

"""
Source:
https://stackoverflow.com/a/238558
"""

# Semi-axes of WGS-84 geoidal reference
WGS84_a = 6378137.0  # Major semiaxis [m]
WGS84_b = 6356752.3  # Minor semiaxis [m]

def WGS84EarthRadius(lat):
    """
    Earth radius at a given latitude, according to the WGS-84 ellipsoid [m]

    Parameters
    ----------
    lat: latitude coordinate in degrees

    Returns
    -------
    estimated Earth radius at the given latitude
    """
    # http://en.wikipedia.org/wiki/Earth_radius
    An = WGS84_a*WGS84_a * np.cos(lat)
    Bn = WGS84_b*WGS84_b * np.sin(lat)
    Ad = WGS84_a * np.cos(lat)
    Bd = WGS84_b * np.sin(lat)
    return np.sqrt( (An*An + Bn*Bn)/(Ad*Ad + Bd*Bd) )

def boundingBox(latitudeInDegrees, longitudeInDegrees, halfSideInKm):
    """
    Bounding box surrounding the point at given coordinates, assuming local approximation of Earth surface as a sphere of radius given by WGS84.

    Parameters
    ----------
    latitudeInDegrees: latitude coordinate in degrees
    longitudeInDegrees: longitude coordinate in degrees

    Returns
    -------
    tuple of 4 items:
      - latitude coordinate of the North of the bounding box
      - longitude coordinate of the West of the bounding box
      - latitude coordinate of the South of the bounding box
      - longitude coordinate of the East of the bounding box
    """
    lat = np.deg2rad(latitudeInDegrees)
    lon = np.deg2rad(longitudeInDegrees)
    halfSide = 1000*halfSideInKm
    # Radius of Earth at given latitude
    radius = WGS84EarthRadius(lat)
    # Radius of the parallel at given latitude
    pradius = radius*np.cos(lat)
    latMin = lat - halfSide/radius
    latMax = lat + halfSide/radius
    lonMin = lon - halfSide/pradius
    lonMax = lon + halfSide/pradius
    return (np.rad2deg(latMin), np.rad2deg(lonMin), np.rad2deg(latMax), np.rad2deg(lonMax))


#!/usr/bin/env python3

import numpy as np

class Activator:
    """Contains Activation functions for NeuralNet.

    Currently contains implementations of sigmoid, tanh, elu,
    and softmax functions
    """

    @staticmethod
    def sigmoid(signal, deriv=False):
        """Sigmoid Activation.

        Args:
             signal (ndarray): A layer of neuron values multiplied by their weights.
             deriv (bool): Whether to compute the derivative of the activation function.
        Returns:
            Takes signals from neurons in layer and applies a sigmoid transformation.
        """
        if deriv:
            return np.multiply(signal, 1 - signal)
        activation = 1 / (1 + np.exp(-signal))
        return activation

    @staticmethod
    def tanh(signal, deriv=False):
        """tanh Activation.

        Known as the Hyperbolic tanget, it's similar to sigmoid but it instead approximates
        the identity near the origin.

        --> 2 / (1 + np.exp(-2*signal)) - 1

        Args:
             signal (ndarray): A layer of neuron values multiplied by their weights.
             deriv (bool): Whether to compute the derivative of the activation function.
        Returns:
            Takes signals from neurons in layer and applies a tanh transformation.
        """
        if deriv:
            return 1 - np.power(np.tanh(signal), 2)
        activation = np.tanh(signal)
        return activation

    @staticmethod
    def elu(signal, deriv=False, alpha=1.0):
        """Exponential Learning Unit

        https://arxiv.org/abs/1511.07289

        Args:
            signal (ndarray): A layer of neuron values multiplied by their weights.
            deriv (bool): Whether to compute the derivative of the activation function.
        Returns:
            Takes signals from neurons in layer and applies a linear/exponential transformation.
        """
        activation = (signal >= 0).astype(int) * signal + \
                     (signal < 0).astype(int) * (alpha * (np.exp(signal) - 1))

        if deriv:
            derivation = (signal >= 0).astype(int) + \
                         (signal < 0) * (activation + alpha)
            return derivation

        return activation

    @staticmethod
    def softmax(signal, deriv=False):
        """Softmax Categorical Function

        Gives the probability of the output signal being in given classes. [0, 1]

        Args:
            signal (ndarray): A layer of neuron values multiplied by their weights.
            deriv (bool): Whether to compute the derivative of the activation function.
        Returns:
            Takes signals from neurons in layer and applies a SoftMax Regression.
        """
        signal = signal - np.max(signal)
        activation = np.exp(signal) / \
            np.array([np.sum(np.exp(signal), axis=1)]).T

        if deriv:  # Derivation looks fishy.
            jacobian = - activation[..., None] * activation[:, None, :]
            iy, ix = np.diag_indices_from(jacobian[0])
            jacobian[:, iy, ix] = activation * (1 - activation)

            return jacobian.sum(axis=1)

        return activation


'''
This is a simple script that fits a Multivariate Gaussian Model to the observed data.
=========================
Author  :  Muhan Zhao

Date    :  Aug. 17, 2019

Location:  West Hill, LA, CA
=========================
'''
import numpy as np
import matplotlib.pyplot as plt

class MultiGaussian:
    def __init__(self, data):
        # load the data
        self.data = data
        # initialize the size
        self.n = data.shape[0]
        self.m = data.shape[1]
        # initialize the parameters
        self.mu = np.zeros((self.n, 1))
        self.sigma = np.zeros((self.n, self.n))
        # determine the minimum and maximum range of each coordinates
        self.range = np.zeros((self.n, 2))
        # the first column is the minimum - 0.1 * range
        # the second column is the maximum + 0.1 * range
        for i in range(self.n):
            coordinate_range = np.max(self.data[i, :]) - np.min(self.data[i, :])
            self.range[i, 0] = np.min(self.data[i, :]) - .1 * coordinate_range
            self.range[i, 1] = np.max(self.data[i, :]) + .1 * coordinate_range

    def fit(self):
        '''
        MLE method to update mean and covariance of Gaussian model
        :return:
        '''
        self.mu = np.mean(self.data, axis=1).reshape(-1, 1)
        for i in range(self.m):
            current_data = self.data[:, i].reshape(-1, 1)
            self.sigma += np.dot(current_data - self.mu, (current_data - self.mu).T)
        self.sigma /= self.m

    def plot2D(self):
        '''
        2D plot function
        :return:
        '''
        assert self.data.shape[0] == 2, 'This is a 2D data visualizer'
        plt.figure()
        # scatter plot the data
        plt.scatter(self.data[0, :], self.data[1, :])
        # generate the meshgrid
        X, Y = np.meshgrid(np.linspace(self.range[0, 0], self.range[0, 1], 200),
                           np.linspace(self.range[1, 0], self.range[1, 1], 200))
        # plot the estimated gaussian distribution
        pos = np.stack((X, Y), axis=2)
        # np.einsum reference link:
        # http://ajcr.net/Basic-guide-to-einsum/
        value = np.einsum('...k,kl,...l->...', pos - self.mu.T[0], np.linalg.inv(self.sigma), pos - self.mu.T[0])
        Z = np.exp(- value / 2) / (np.sqrt(np.linalg.det(self.sigma) * (2 * np.pi) ** self.n))
        plt.contour(X, Y, Z)
        plt.show()

if __name__ == "__main__":
    # Random sample the data
    d = np.random.multivariate_normal(.9 * np.ones(2), np.array([[.1, 0], [0, 12]]), 10).T
    # Fit and plot the model
    multigaus = MultiGaussian(d)
    multigaus.fit()
    multigaus.plot2D()


"""
Defined loss functions for our custom neural network implementation
"""

import numpy as np

def cross_entropy(target: np.ndarray, output: np.ndarray) -> np.float64:
    """
    Computes cross entropy between target and output
    """
    return np.mean(-np.sum(target * np.log(output)))

def binary_cross_entropy(target: np.ndarray, output: np.ndarray) -> np.float64:
    """
    Computes binary cross entropy between target and output
    """
    return np.mean(-(target * np.log(output) + (1-target) * np.log(1-output)))

def MSE(target: np.ndarray, output: np.ndarray) -> np.float64:
    """
    Computes mean squared error between target and output
    """
    return np.mean((output - target)**2)

def MAE(target: np.ndarray, output: np.ndarray) -> np.float64:
    """
    Computes mean absolute error between target and output
    """
    return np.mean(np.abs(output - target))

def kl_divergence(target: np.ndarray, output: np.ndarray) -> np.float64:
    """
    Computes KL divergence between target and output
    """
    return np.sum(target * np.log(target / output), axis=-1)


"""In such an equation system, where the previous values of some variables are required in the evolution equation of other variables, you could define your function as follows:"""

import numpy as np

def fun(RHS, t):

    # get initial boundary condition values
    discrete_S0 = RHS[0]
    discrete_I0 = RHS[1]
    discrete_Q0 = RHS[2]
    discrete_H0 = RHS[3]
    discrete_D0 = RHS[4]

    # calculte rate of respective variables
    discrete_S0dt = - v * discrete_S0 * discrete_I0 / (N - discrete_Q0 - discrete_H0 - discrete_D0)
    discrete_I0dt = v * discrete_S0 * discrete_I0 / (N - discrete_Q0 - discrete_H0 - discrete_D0) - gamma * discrete_I0 - alpha * discrete_I0 - psi * discrete_I0
    discrete_Q0dt = alpha * discrete_I0 - eta_q * discrete_Q0 - k_h *discrete_Q0 + k_q * discrete_H0
    discrete_H0dt = psi * discrete_I0 - eta_h * discrete_H0 + k_h * discrete_Q0 - k_q * discrete_H0 - zeta * discrete_H0
    discrete_D0dt = eta_q * discrete_Q0 + eta_h * discrete_H0

    # Left-hand side of ODE
    LHS = np.zeros([5,])

    LHS[0] = discrete_S0dt
    LHS[1] = discrete_I0dt
    LHS[2] = discrete_Q0dt
    LHS[3] = discrete_H0dt
    LHS[4] = discrete_D0dt

    return LHS

# Afterward, you can solve it (according to your boundary conditions) as follows:

from scipy.integrate import odeint

v=0.1
alpha = 0.3
gamma = 1/21
psi = 0.2
k_h=0.1
k_q=0.1
eta_h=0.3
eta_q=0.3
y0 = [99, 1, 0, 0, 0]
t = np.linspace(0,13,14)

res = odeint(fun, y0, t)

# Here y0 is the initial boundary condition for all the variables defined in the function fun at t=0. That's why the variable t starts from 0.
# Also, you can get the result of all the variables as follows:

print(res[:,0])
print(res[:,1])
print(res[:,2])
print(res[:,3])
print(res[:,4])

4 - Accessing earlier values in odeint/code.py


#!/usr/bin/env python

"""Generates the Faure and pmj02 xor_values, used in xor_values.h.

This works by constructing the generator matrices, and then "reversing" those
matrices as explained in Section 4.4 of the paper.
"""

import numpy as np
import scipy.linalg

def get_xor_values(gen_matrix, base=2):
  """Compute xor-values from a non-singular upper triangular matrix.

  Listing 3 from the paper.
  """
  # Invert the matrix.
  m_inv = np.linalg.inv(gen_matrix).astype(int) % base
  # Truncate the diagonal.
  m_inv -= np.identity(m_inv.shape[0], dtype=int)

  # Compute the xor-values from the negated columns.
  # For base 2, base_pow = (1,2,4,8,16...)
  base_pow = np.power(base, np.arange(0, gen_matrix.shape[0]))
  return [np.sum((-col % base)*base_pow) for col in m_inv.T]

def print_hex(vals):
  """For an array of numbers, print them as hexadecimal values."""
  array_elements = ", ".join([hex(val) for val in vals])
  string = "{{{0}}}".format(array_elements)
  print(string)

def get_stirling_matrix(size, base=2):
  """Construct a matrix of unsigned Stirling numbers of the first kind."""
  m = np.identity(size).astype(int)
  for k in range(size):
    for n in range(k, size):
      if (n == 0) and (k == 0): m[k][n] = 1
      elif (k == 0): m[k][n] = ((n - 1) * m[k][n-1]) % base
      else:
        m[k][n] = (m[k-1][n-1] + (n - 1) * m[k][n-1]) % base
  return m

def print_pmj02_xors(n):
  """Print pmj02 xor-values using Stirling numbers.

  See section 1.1 of the supplemental materials.
  """
  s = get_stirling_matrix(n+1)
  xor_vals = get_xor_values(s[:n, :n], base=2)
  print_hex(xor_vals)
  # The second matrix is shifted up and to the left by one.
  xor_vals = get_xor_values(s[1:n+1, 1:n+1], base=2)
  print_hex(xor_vals)

def print_faure_xors(base, n):
  """Print Faure sequences xor-values"""
  print("Base {0} Faure (0,{0}) xor-values: ".format(base))
  m = np.identity(n)
  # Faure sequences are constructed from exponentiated Pascal matrices.
  pascal_matrix = scipy.linalg.pascal(n, kind='upper') % base

  for i in range(base):
    xor_vals = get_xor_values(m, base)
    print_hex(xor_vals)
    m = np.matmul(m, pascal_matrix) % base

print_pmj02_xors(32)
print_faure_xors(2, 32)
print_faure_xors(3, 21)
print_faure_xors(5, 14)
print_faure_xors(7, 12)
print_faure_xors(11, 10)


'''
Product of two expressions
'''

from sympy import expand, sympify
from sympy.core.sympify import SympifyError

def product( expr1, expr2 ):
	prod = expand( expr1 * expr2 )
	print( prod )

if __name__ == '__main__':
	expr1 = input( 'Enter the first expression: ' )
	expr2 = input( 'Enter the second expression: ' )

try:
	expr1 = sympify( expr1 )
	expr2 = sympify( expr2 )
except:
	print( 'Invalid Input' )
else:
	product( expr1, expr2 )


from math import log
import numpy as np
from numpy import linalg as la

class LogisticProblem:
    """Class for the logistic regression method for classification."""
    def __init__(self, feat_mtx, y):
        """Create a Logistic Problem with matrix `feat_mtx` n by p and vector `y` of 0s and 1s with size n.
        A bias is added to the model as the first variable."""
        self._feat_mtx = feat_mtx
        self._y = y
        p = feat_mtx.shape[1]
        self.beta = np.zeros(p + 1)

    def sigmoid(self, v):
        """Compute sigmoid(v) = 1 / (1 + exp(-v)"""
        return 1 / (1 + np.exp(-v))

    def predict(self, feat_mtx=None, beta=None):
        if feat_mtx is None:
            feat_mtx = self._feat_mtx
        if beta is None:
            beta = self.beta
        return self.sigmoid(beta[0] + np.dot(feat_mtx, beta[1:]))

    def cross_entropy(self, yhat):
        """Compute the cross entropy, given by

            sum y[i] * log(yhat[i]) + (1 - y[i]) * log(1 - yhat[i])"""
        n = len(self._y)
        c = 0.0
        for i in range(0, n):
            c += self._y[i] * log(
                yhat[i]) + (1 - self._y[i]) * log(1 - yhat[i])

        return c

    def cross_entropy_gradient(self, yhat):
        """Assuming yhat_i = sigmoid(x_i^T beta), returns

            sum (y[i] - yhat) * x_i
        """
        n = len(self._y)
        p = len(self.beta)
        g = np.zeros(p)
        for i in range(0, n):
            g = g + (self._y[i] - yhat[i]) * np.array(
                [1.0, *self._feat_mtx[i, :]])

        return g

    def solve(self):
        """Solve the logistic regression problem"""
        max_iter = 1000
        iter_count = 0
        yhat = self.predict()
        loss = self.cross_entropy(yhat)
        gradloss = self.cross_entropy_gradient(yhat)
        while la.norm(gradloss) > 1e-6 and iter_count < max_iter:
            alpha = 1.0
            slope = la.norm(gradloss)**2
            beta_new = self.beta + alpha * gradloss
            yhat = self.predict(beta=beta_new)
            loss_new = self.cross_entropy(yhat)
            while loss_new < loss + 1e-4 * alpha * slope:
                alpha = alpha / 2
                beta_new = self.beta + alpha * gradloss
                yhat = self.predict(beta=beta_new)
                loss_new = self.cross_entropy(yhat)
            self.beta = beta_new
            loss = loss_new
            gradloss = self.cross_entropy_gradient(yhat)
            iter_count += 1


from typing import Union

import numpy as np

from pyrometrics._helpers import (
    _error,
    _percentage_error,
    _geometric_mean,
    _naive_forecasting,
    _relative_error,
    _bounded_relative_error,
    _EPSILON,
)

def me(actual: np.ndarray, predicted: np.ndarray):
    """
    ME - Mean Error

    This is an arithmetic average of error

    Args:
        actual (np.ndarray): array of actual values
        predicted (np.ndarray): array of predicted values

    Returns:
        Mean Error
    """

    return np.mean(_error(actual, predicted))

def mae(actual: np.ndarray, predicted: np.ndarray):
    """
    MAE - Mean Absolute Error

    This is an arithmetic average of absolute value of error

    Args:
        actual (np.ndarray): array of actual values
        predicted (np.ndarray): array of predicted values

    Returns:
        Mean Absolute Error
    """

    return np.mean(np.abs(_error(actual, predicted)))

def nae(actual: np.ndarray, predicted: np.ndarray):
    """
    NAE - Normalized Absolute Error

    This is normalized version of mean of absolute error.

    Args:
        actual (np.ndarray): array of actual values
        predicted (np.ndarray): array of predicted values

    Returns:
        Normalized Absolute Error
    """

    __mae = mae(actual, predicted)

    return np.sqrt(
        np.sum(np.square(_error(actual, predicted) - __mae)) / (len(actual) - 1)
    )

def mase(
    actual: np.ndarray,
    predicted: np.ndarray,
    seasonality: int = 1,
):
    """
    MASE - Mean Absolute Scaled Error

    This is a scaled version of mean absolute error

    Args:
        actual (np.ndarray): array of actual values
        predicted (np.ndarray): array of predicted values
        seasonality (int, optional): User provided seasonality for calculating benchmark forecast

    Returns:
        Mean Absolute Scaled Error
    """

    return mae(actual, predicted) / mae(
        actual[seasonality:], _naive_forecasting(actual, seasonality)
    )

def mdae(actual: np.ndarray, predicted: np.ndarray):
    """
    MDAE - Median Absolute Error

    This is median of absolute value of error.

    Args:
        actual (np.ndarray): array of actual values
        predicted (np.ndarray): array of predicted values

    Returns:
        Median Absolute Error
    """

    return np.median(np.abs(_error(actual, predicted)))

def gmae(actual: np.ndarray, predicted: np.ndarray):
    """
    GMAE - Geometric Mean Absolute Error

    This is a geometric mean of absolute value of error.

    Args:
        actual (np.ndarray): array of actual values
        predicted (np.ndarray): array of predicted values

    Returns:
        Geometric Mean Absolute Error
    """

    return _geometric_mean(np.abs(_error(actual, predicted)))

def mse(actual: np.ndarray, predicted: np.ndarray):
    """
    MSE - Mean Square Error

    This is an arithmetic average of squared error.

    Args:
        actual (np.ndarray): array of actual values
        predicted (np.ndarray): array of predicted values

    Returns:
        Mean Square Error
    """

    return np.mean(np.square(_error(actual, predicted)))

def rmse(actual: np.ndarray, predicted: np.ndarray):
    """
    RMSE - Root Mean Square Error

    This is a square root from mean of squared error.

    Args:
        actual (np.ndarray): array of actual values
        predicted (np.ndarray): array of predicted values

    Returns:
        Root Mean Square Error
    """

    return np.sqrt(mse(actual, predicted))

def rmsse(
    actual: np.ndarray,
    predicted: np.ndarray,
    seasonality: int = 1,
):
    """
    RMSSE - Root Mean Square Scaled Error

    This is scaled version of root from the mean squared error.

    Args:
        actual (np.ndarray): array of actual values
        predicted (np.ndarray): array of predicted values
        seasonality (int, optional): User provided seasonality

    Returns:
        Root Mean Square Scaled Error
    """

    q = np.abs(_error(actual, predicted)) / mae(
        actual[seasonality:], _naive_forecasting(actual, seasonality)
    )

    return np.sqrt(np.mean(np.square(q)))

def nrmse(actual: np.ndarray, predicted: np.ndarray):
    """
    NRMSE - Normalized Root Mean Square Error

    This is normalized version of root mean square error.

    Args:
        actual (np.ndarray): array of actual values
        predicted (np.ndarray): array of predicted values

    Returns:
        Normalized Root Mean Square Error
    """
    return rmse(actual, predicted) / (actual.max() - actual.min())

def inrse(actual: np.ndarray, predicted: np.ndarray):
    """
    INRSE - Integral Normalized Root Squared Error

    Args:
        actual (np.ndarray): array of actual values
        predicted (np.ndarray): array of predicted values

    Returns:
        Integral Normalized Root Squared Error
    """

    return np.sqrt(
        np.sum(np.square(_error(actual, predicted)))
        / np.sum(np.square(actual - np.mean(actual)))
    )

# Percentage Errors

def mpe(actual: np.ndarray, predicted: np.ndarray):
    """
    MPE - Mean Percentage Error

    This is an arithmetic average of percentage error

    Note: The result is not multiplied by 100

    Args:
        actual (np.ndarray): array of actual values
        predicted (np.ndarray): array of predicted values

    Returns:
        Mean Percentage Error
    """

    return np.mean(_percentage_error(actual, predicted))

def mape(actual: np.ndarray, predicted: np.ndarray):
    """
    MAPE - Mean Absolute Percentage Error

    This is an arithmetic average of absolute value of percentage error

    Note: The result is not multiplied by 100

    Args:
        actual (np.ndarray): array of actual values
        predicted (np.ndarray): array of predicted values

    Returns:
        Mean Absolute Percentage Error
    """

    return np.mean(np.abs(_percentage_error(actual, predicted)))

def smape(actual: np.ndarray, predicted: np.ndarray):
    """
    SMAPE - Symmetric Mean Absolute Percentage Error

    This is symmetric version of mean absolute percentage error.

    Note: The result is not multiplied by 100

    Args:
        actual (np.ndarray): array of actual values
        predicted (np.ndarray): array of predicted values

    Returns:
        Symmetric Mean Absolute Percentage Error
    """

    return np.mean(
        2.0
        * np.abs(actual - predicted)
        / ((np.abs(actual) + np.abs(predicted)) + _EPSILON)
    )

def mdape(actual: np.ndarray, predicted: np.ndarray):
    """
    MDAPE - Median Absolute Percentage Error

    Median value of absolute percentage error.

    Note: The result is not multiplied by 100

    Args:
        actual (np.ndarray): array of actual values
        predicted (np.ndarray): array of predicted values

    Returns:
        Median Absolute Percentage Error
    """

    return np.median(np.abs(_percentage_error(actual, predicted)))

def smdape(actual: np.ndarray, predicted: np.ndarray):
    """
    SMDAPE - Symmetric Median Absolute Percentage Error

    This is a symmetric version of median absolute percentage error.

    Note: The result is not multiplied by 100

    Args:
        actual (np.ndarray): array of actual values
        predicted (np.ndarray): array of predicted values

    Returns:
        Symmetric Median Absolute Percentage Error
    """

    return np.median(
        2.0
        * np.abs(actual - predicted)
        / ((np.abs(actual) + np.abs(predicted)) + _EPSILON)
    )

def maape(actual: np.ndarray, predicted: np.ndarray):
    """
    MAAPE - Mean Arctangent Absolute Percentage Error

    This is mean of arctangent of absolute percentage error.

    Note: The result is not multiplied by 100

    Args:
        actual (np.ndarray): array of actual values
        predicted (np.ndarray): array of predicted values

    Returns:
        Mean Arctangent Absolute Percentage Error
    """

    return np.mean(np.arctan(np.abs((actual - predicted) / (actual + _EPSILON))))

def rmspe(actual: np.ndarray, predicted: np.ndarray):
    """
    RMSPE - Root Mean Squared Percentage Error

    This is a square root from the mean of the percentage error.

    Note: The result is not multiplied by 100

    Args:
        actual (np.ndarray): array of actual values
        predicted (np.ndarray): array of predicted values

    Returns:
        Root Mean Squared Percentage Error
    """

    return np.sqrt(np.mean(np.square(_percentage_error(actual, predicted))))

def rmdspe(actual: np.ndarray, predicted: np.ndarray):
    """
    RMDSPE - Root Median Squared Percentage Error

    This is square root from the meadian value of squared percentage error

    Note: The result is not multiplied by 100

    Args:
        actual (np.ndarray): array of actual values
        predicted (np.ndarray): array of predicted values

    Returns:
        Root Median Squared Percentage Error
    """

    return np.sqrt(np.median(np.square(_percentage_error(actual, predicted))))

def nape(actual: np.ndarray, predicted: np.ndarray):
    """
    NAPE - Normalized Absolute Percentage Error

    This is normalized version of absolute percentage error

    Args:
        actual (np.ndarray): array of actual values
        predicted (np.ndarray): array of predicted values

    Returns:
        Normaized Absolute Percentage Error
    """

    __mape = mape(actual, predicted)

    return np.sqrt(
        np.sum(np.square(_percentage_error(actual, predicted) - __mape))
        / (len(actual) - 1)
    )

# Relative Errors

def mre(actual: np.ndarray, predicted: np.ndarray, benchmark: Union[np.ndarray, int]):
    """
    MRE - Mean Relative Error

    This is mean of relative error.

    Args:
        actual (np.ndarray): array of actual values
        predicted (np.ndarray): array of predicted values
        benchmark (Union[np.ndarray, int]): array of benchmark values

    Returns:
        Mean Relative Error
    """

    return np.mean(_relative_error(actual, predicted, benchmark))

def rae(actual: np.ndarray, predicted: np.ndarray):
    """
    RAE - Relative Absolute Error

    A.K.A - Approximation Error

    Args:
        actual (np.ndarray): array of actual values
        predicted (np.ndarray): array of predicted values

    Returns:
        Relative Absolute Error
    """

    return np.sum(np.abs(actual - predicted)) / (
        np.sum(np.abs(actual - np.mean(actual))) + _EPSILON
    )

def mrae(actual: np.ndarray, predicted: np.ndarray, benchmark: Union[np.ndarray, int]):
    """
    MRAE - Mean Relative Absolute Error

    This is the mean of relative absolute error.

    Args:
        actual (np.ndarray): array of actual values
        predicted (np.ndarray): array of predicted values
        benchmark (Union[np.ndarray, int]): array of benchmark values

    Returns:
        Mean Relative Absolute Error
    """

    return np.mean(np.abs(_relative_error(actual, predicted, benchmark)))

def mdrae(
    actual: np.ndarray,
    predicted: np.ndarray,
    benchmark: Union[np.ndarray, int],
):
    """
    MDRAE - Median Relative Absolute Error

    This is median of relative absolute error.

    Args:
        actual (np.ndarray): array of actual values
        predicted (np.ndarray): array of predicted values
        benchmark (Union[np.ndarray, int]): array of benchmark values

    Returns:
        Median Relative Absolute Error
    """

    return np.median(np.abs(_relative_error(actual, predicted, benchmark)))

def rrse(actual: np.ndarray, predicted: np.ndarray):
    """
    RRSE - Root Relative squared Error

    This is square root from relative squared error.

    Args:
        actual (np.ndarray): array of actual values
        predicted (np.ndarray): array of predicted values

    Returns:
        Root Relative Squared Error
    """

    return np.sqrt(
        np.sum(np.square(actual - predicted))
        / np.sum(np.square(actual - np.mean(actual)))
    )

def gmrae(
    actual: np.ndarray, predicted: np.ndarray, benchmark: Union[np.ndarray, int] = None
):
    """
    GMRAE - Geometric Mean Relative Absolute Error

    This is the geometric mean of relative absolute error.

    Args:
        actual (np.ndarray): array of actual values
        predicted (np.ndarray): array of predicted values
        benchmark (Union[np.ndarray, int]): array of benchmark values

    Returns:
        Geometric Mean Relative Absolute Error
    """

    return _geometric_mean(np.abs(_relative_error(actual, predicted, benchmark)))

def mbrae(
    actual: np.ndarray,
    predicted: np.ndarray,
    benchmark: Union[np.ndarray, int] = None,
):
    """
    MBRAE - Mean Bounded Relative Absolute Error

    This is mean of bounded relative absolute error.

    Args:
        actual (np.ndarray): array of actual values
        predicted (np.ndarray): array of predicted values
        benchmark (Union[np.ndarray, int]): array of benchmark values

    Returns:
        Mean Bounded Relative Absolute Error
    """

    return np.mean(_bounded_relative_error(actual, predicted, benchmark))

def umbrae(
    actual: np.ndarray,
    predicted: np.ndarray,
    benchmark: Union[np.ndarray, int] = None,
):
    """
    UMBRAE - Unscaled Mean Bounded Relative Absolute Error

    Args:
        actual (np.ndarray): array of actual values
        predicted (np.ndarray): array of predicted values
        benchmark (Union[np.ndarray, int]): array of benchmark values

    Returns:
        Unscaled Mean Bounded Relative Absolute Error
    """

    __mbrae = mbrae(actual, predicted, benchmark)

    return __mbrae / (1 - __mbrae)

def mda(actual: np.ndarray, predicted: np.ndarray):
    """
    MDA - Mean Directional Accuracy

    Args:
        actual (np.ndarray): array of actual values
        predicted (np.ndarray): array of predicted values

    Returns:
        Mean Directional Accuracy
    """

    return np.mean(
        (
            np.sign(actual[1:] - actual[:-1]) == np.sign(predicted[1:] - predicted[:-1])
        ).astype(int)
    )


from math import erf
import matplotlib.pyplot as plt
import numpy as np
import numpy.random as nr
import pandas as pd
from statsmodels.distributions.empirical_distribution import ECDF

def cdf_n_std(t):
    return (1 + erf(t/(2. ** 0.5)))/2.

def cdf_normal(t, m=0, sd=1):
    return cdf_n_std((t - m)/sd)

def quartiles(x):
    # returns lower and upper quartiles
    s = np.sort(x)
    q2 = np.median(x)
    return np.median(s[s <= q2]), np.median(s[s >= q2])

def out(x):
    # returns mask for those values in sample, that are not outliers
    q1, q3 = quartiles(x)
    iqr = q3 - q1
    return (q1 - 1.5 * iqr <= x) & (x <= q3 + 1.5 * iqr)

mu, sigma = 0, 1
N = 2000

if __name__ == '__main__':
    X = nr.normal(mu, sigma, N)
    Z = nr.standard_cauchy(N)

    S = pd.DataFrame({
        'X': X, 'Z': Z
    })

    M1, M2 = abs(S) > 1, abs(S) > 3

    C1, C2 = np.sum(M1), np.sum(M2)
    print(C1, C2, sep='\n')

    X_F = X[out(X)]
    Z_F = Z[out(Z)]

    N_BINS = 42

    plt.hist(X_F, bins=N_BINS)
    plt.title('N(%.2f, %.2f) - filtered' % (mu, sigma))
    plt.show()

    plt.hist(Z_F, bins=N_BINS)
    plt.title('Cauchy - filtered')
    plt.show()

    E_CDF_X, E_CDF_Z = ECDF(X), ECDF(Z)

    LS_X, LS_Z = np.linspace(np.min(X_F), np.max(X_F)), np.linspace(np.min(Z_F), np.max(Z_F))

    E_X, E_Z = E_CDF_X(LS_X), E_CDF_Z(LS_Z)

    plt.step(LS_X, E_X)
    plt.title('N(%.2f, %.2f) - Empirical CDF' % (mu, sigma))
    plt.show()

    plt.step(LS_Z, E_Z)
    plt.title('Cauchy - Empirical CDF')
    plt.show()

    f, ax = plt.subplots(1, 1)
    f.suptitle('Cauchy and Normal Empirical CDF', y=1)
    ax.step(LS_X, E_X, color='purple', label='Normal Empirical CDF', where='post')
    ax.step(LS_Z, E_Z, color='orange', label='Cauchy Empirical CDF', where='post')
    ax.legend()
    plt.show()

    S_MEAN, S_SD = S.mean(), S.std()

    print(S_MEAN, S_SD, sep='\n')

    P1_E = 1 - E_CDF_X(S_MEAN.X + S_SD.X * 3) + E_CDF_X(S_MEAN.X - S_SD.X * 3)
    P1_T = 1 - cdf_normal(S_MEAN.X + S_SD.X * 3) + cdf_normal(S_MEAN.X - S_SD.X * 3)

    print('E Normal CDF: %.4f\n'
          'T Normal CDF: %.4f' % (P1_E, P1_T))


import numpy as np
import scipy.sparse as sp
import scipy.optimize as spopt
from scipy.stats import pearsonr

#Logistic regression prediction
def get_y_pred(X, w, w_0) :
    return 1. / (1. + np.exp(-1. * (X.dot(w) + w_0)))

#Safe log for NLL
def safe_log(x, minval=0.01):
    return np.log(x.clip(min=minval))

#Logistic regression NLL loss
def log_loss(w_bundle, *fun_args) :
    (X, y, lambda_penalty) = fun_args
    w = w_bundle[1:]
    w_0 = w_bundle[0]
    N = float(X.shape[0])

    log_y_zero = safe_log(1. - get_y_pred(X, w, w_0))
    log_y_one = safe_log(get_y_pred(X, w, w_0))

    log_loss = (1. / 2.) * lambda_penalty * np.square(np.linalg.norm(w)) - (1. / N) * np.sum(y * log_y_one + (1. - y) * log_y_zero)

    return log_loss

#Logistic regression NLL gradient
def log_loss_gradient(w_bundle, *fun_args) :
    (X, y, lambda_penalty) = fun_args
    w = w_bundle[1:]
    w_0 = w_bundle[0]
    N = float(X.shape[0])

    y_pred = get_y_pred(X, w, w_0)

    w_0_gradient = - (1. / N) * np.sum(y - y_pred)
    w_gradient = 1. * lambda_penalty * w - (1. / N) * X.T.dot(y - y_pred)

    return np.concatenate([[w_0_gradient], w_gradient])


import numpy as np
from scipy.signal import square
from matplotlib import pyplot as plt

#Define the square wave to be approximated by specifying frequency f and the mean value
f = 5 #Frequency
T = 1/f
omega = (2*np.pi)/T

for series in range (1, 10, 2):

	t = np.linspace(0, 1, 5000, endpoint=False)
	fourier_terms = series

	def bn(n):
		if(n%2 != 0):
			return 4/(np.pi*n)
		else:
			return 0

	mean = 1
	sq_wave = mean + square(omega*t)

	#a0 = (1/T)*(integral of f(t)dt from 0 to T)
	a0 = mean #Integral of f(t)dt from 0 to T is value of (mean*2) from 0 to T/2

	fourier_y = []

	for tn in t:
		f_series = a0 #Sum of terms in f at each t
		for j in range(1,fourier_terms): #Number of terms in the Fourier series
			f_series += bn(j)*np.sin(omega*j*tn)
		fourier_y.append(f_series)

	plt.plot(t, sq_wave, color='blue')
	plt.ylim(-1,3)

	plt.plot(t, fourier_y, color='red')

	#plt.show()

	plt.savefig('fourier_sq_%d.png'%series)

	plt.clf()


import math
import numpy as np

def crossform(a):
    """
    Convert a three element vector into a 3 x 3 skew matrix as per
    Hartley and Zisserman pg. 581

    """
    return np.array([[0, a[2], -a[1]],
                     [-a[2], 0, a[0]],
                     [a[1], -a[0], 0]])

def normalize(a):
    """
    Normalize a set of coordinates such that the origin is
    translated to the center and then scaled isotropically such
    that the average distance from the origin is $\sqrt{2}$.

    Parameters
    ----------
    a : arraylike
        (n,2) array of x,y or (n,3) homogeneous coordinates

    Returns
    -------
    normalizer : ndarray
                 (3,3) transformation matrix
    """

    a = np.asarray(a)

    # Compute the normalization matrix
    centroid = a[:, :2].mean(axis=0)
    dist = np.sqrt(np.sum(((a[:, :2] - centroid)**2), axis=1))
    mean_dist = np.mean(dist)
    sq2 = math.sqrt(2)

    normalizer = np.array([[sq2 / mean_dist, 0, -sq2 / mean_dist * centroid[0]],
                          [0, sq2 / mean_dist,  -sq2 / mean_dist * centroid[1]],
                          [0, 0, 1]])

    return normalizer


import numpy as np

def four_param_logistic(p):
    """4p logistic function maker.

    Returns a function that accepts x and returns y for
    the 4-parameter logistic defined by p.

    The 4p logistic is defined by:
    y = A + (K - A) / (1 + exp(-B*(x-M)))

    Args:
        p: an iterable of length 4
            A, K, B, M = p

    Returns:
        A function that accepts a numpy array as an argument
        for x values and returns the y values for the defined 4pl curve.

    Marvin Thielk 2016
    mthielk@ucsd.edu
    """
    A, K, B, M = p
    def f(x):
        return A + (K - A) / (1 + np.exp(-B*(x-M)))
    return f

def normalized_four_param_logistic(p):
    A, K, B, M = p
    def f(x):
        return 1. / (1. + np.exp(-B*(x-M)))
    return f


from numpy import dot
from numpy.linalg import norm

# cosine similarity between vectors
def cos(v1, v2):
    if norm(v1) > 0 and norm(v2) > 0:
        return dot(v1, v2) / (norm(v1) * norm(v2))
    else:
        return 0.0


import numpy as np
from numba import jit

# Tridiag solver from Carnahan
# Not used in main program
def TDMAsolver_carnahan(A, B, C, D):
    """
    Our solution for the TDMA solver based on carnahan (not used in main program)
    """
    # send the vectors a, b, c, d with the coefficents

    vector_len = D.shape[0]  # defines the length of the coefficent vector (including a = 0)
    V = np.zeros(vector_len)  # solution vector
    beta = np.zeros(vector_len)  # temp vector
    gamma = np.zeros(vector_len)  # temp vector

    beta[0] = B[0]
    gamma[0] = D[0] / beta[0]

    for i in range(1, vector_len):
        beta[i] = B[i] - (A[i] * C[i - 1]) / beta[i - 1]
        gamma[i] = (D[i] - A[i] * gamma[i - 1]) / beta[i]

    # compute the final solution vector sv
    V[vector_len - 1] = gamma[vector_len - 1]
    for k in np.flip(np.arange(vector_len - 1)):
        # k = vector_len - i
        V[k] = gamma[k] - C[k] * V[k + 1] / beta[k]

    return V

## Tri Diagonal Matrix Algorithm(a.k.a Thomas algorithm) solver
# https://gist.github.com/cbellei/8ab3ab8551b8dfc8b081c518ccd9ada9
# Not used in main program (slow)
def TDMAsolver_no_vec(a, b, c, d):
    """
    TDMA solver, a b c d can be NumPy array type or Python list type.
    refer to http://en.wikipedia.org/wiki/Tridiagonal_matrix_algorithm
    and to http://www.cfd-online.com/Wiki/Tridiagonal_matrix_algorithm_-_TDMA_(Thomas_algorithm)
    """

    # a = coeffs[1:, 0]
    # b = coeffs[:, 1]
    # c = coeffs[:-1, 2]
    # d = coeffs[:, 3]

    nf = len(d)  # number of equations
    ac, bc, cc, dc = map(np.array, (a, b, c, d))  # copy arrays
    for it in range(1, nf):
        mc = ac[it - 1] / bc[it - 1]
        bc[it] = bc[it] - mc * cc[it - 1]
        dc[it] = dc[it] - mc * dc[it - 1]

    xc = bc
    xc[-1] = dc[-1] / bc[-1]

    for il in range(nf - 2, 1, -1):
        xc[il] = (dc[il] - cc[il] * xc[il + 1]) / bc[il]

    return xc

# Compiled TDMA solver
# https://stackoverflow.com/questions/8733015/tridiagonal-matrix-algorithm- \
# tdma-aka-thomas-algorithm-using-python-with-nump
# Used in main program
@jit
def TDMAsolver(a, b, c, d):
    # Set up diagonal coefficients
    n = len(d)
    w = np.zeros(n - 1)
    g = np.zeros(n)
    p = np.zeros(n)

    w[0] = c[0] / b[0]
    g[0] = d[0] / b[0]

    for i in range(1, n - 1):
        w[i] = c[i] / (b[i] - a[i - 1] * w[i - 1])
    for i in range(1, n):
        g[i] = (d[i] - a[i - 1] * g[i - 1]) / (b[i] - a[i - 1] * w[i - 1])
    p[n - 1] = g[n - 1]
    for i in range(n - 1, 0, -1):
        p[i - 1] = g[i - 1] - w[i - 1] * p[i]
    return p


from pyspark import SparkContext, SparkConf
import re
import numpy as np
import copy as cp
import itertools
import operator

# The K Means Methods
kmeansKernels = 4 # K-means parameter: Number of clusters
kmeansEta = 20.0 # K-means parameter: Convergence criteria
kmeansMaxIterations = 120 # K-means parameter: Maximum iterations

# Sample the vectors
def sampleVectors(vectors):
    return vectors.takeSample(False, kmeansKernels, 1)

# Main K Means Computation
def kmeans(means, vectors, iter=1):
    newMeans = cp.copy(means)
    centroids = vectors.map(lambda v: (findClosest(v, means), v)).groupByKey().mapValues(lambda vs: averageVectors(vs)).collect()
    for i, centroid in centroids:
        newMeans[i] = centroid
    distance = euclideanDistanceArray(means, newMeans)
    if converged(distance):
        return newMeans
    elif iter < kmeansMaxIterations:
        return kmeans(newMeans, vectors, iter + 1)
    else:
        print('Reached max iterations!')
        return newMeans

# Decide whether the kmeans clustering converged
def converged(distance):
    return distance < kmeansEta

def euclideanDistance(a1, a2):
    part1 = (a1[0]-a2[0])**2
    part2 = (a1[1]-a2[1])**2
    return part1 + part2

# Return the euclidean distance between two points
def euclideanDistanceArray(a1, a2):
    len1 = len(a1)
    len2 = len(a2)
    total = 0.0
    index = 0
    while (index < len1):
        total += euclideanDistance(a1[index], a2[index])
        index += 1
    return total

# Return the closest point
def findClosest(p, centers):
    bestIndex = 0
    closest = float('inf')
    for i in range(len(centers)):
        tempDist = euclideanDistance(p, centers[i])
        if tempDist < closest:
            closest = tempDist
            bestIndex = i
    return bestIndex

# Average the vectors
def averageVectors(ps):
    count = 0
    comp1 = 0
    comp2 = 0
    for p in ps:
        comp1 += p[0]
        comp2 += p[1]
        count += 1
    return (int(comp1/count), int(comp2/count))

# Utils for clustering results
def pyGroupByKey(keyIterList):
    it = itertools.groupby(keyIterList, operator.itemgetter(0))
    return [(key, sum(1 for item in subiter)) for key, subiter in it]

def pyMapValues(tupleList):
    dictMap = {}
    for k, v in tupleList:
        if dictMap.get(k):
            dictMap.update({k: dictMap.get(k) + v})
        else:
            dictMap.update({k: v})
    return dictMap

def pyMaxBy(dictMap):
    return max(dictMap.iterkeys(), key=(lambda key: dictMap.get(key)))

# Displaying results
def clusterResults(means, vectors):
    closest = vectors.map(lambda p: (findClosest(p[1], means), p)) # (clusters, (userId, (Key, Value)))
    inmem0 = closest.persist()
    inmem0.saveAsTextFile('coursework2/support_closest')
    closestGrouped = closest.groupByKey() # (clusters, List<(userId, (Key, Value))>)
    median = closestGrouped.mapValues(lambda vs: calMedian(vs))
    return sorted([v for k,v in median.collect()], key=lambda x: x[3])

def calMedian(vs): # (userId, (Key, Value))
    # most common language in the cluster
    vsv = (zip(*list(vs))[1])
    langMode = pyMaxBy(pyMapValues(pyGroupByKey(list(vsv))))  # {10: 1000, 20:100} -> 10
    clusterSize = float(len(list(vsv)))
    # percent of the questions in the most common language
    langPercent = len(filter(lambda arrPair: arrPair[0] == langMode, list(vsv))) / clusterSize * 100
    # scores = np.sort(np.array(zip(*list(zip(*list(list(vs)[1]))[1]))[1]))
    scores = np.sort(np.array(list(zip(*list(vsv))[1])))
    medianScore = calMedianScore(scores)
    return (langMode, langPercent, clusterSize, medianScore)

def calMedianScore(scores):
    mid = len(scores) / 2
    if (len(scores) % 2 == 0):
        return (scores[mid] + scores[mid - 1]) / 2
    else:
        return scores[mid]

def printResults(results):
    print('Resulting clusters:')
    print('    Score, Mode, Percent, Questions')
    print('================================================')
    for (mode, percent, size, score) in results:
        print('{:9}, {:4}, {:7.3f}, {:9}'.format(score, mode, percent, size))

# Main Function
conf = SparkConf().setAppName('Large-Scale Clustering').set('spark.hadoop.validateOutputSpecs', 'false')
sc = SparkContext(conf = conf)

lines = sc.textFile('coursework2/support_vector.txt')
userIdVectors = lines.map(lambda line: eval(line))
vectors = lines.map(lambda line: np.array([float(x) for x in eval(line)[1]]))
vectors.persist()

means = kmeans(sampleVectors(vectors), vectors, 1)

inmem1 = sc.parallelize(means)
inmem1.saveAsTextFile('coursework2/support_means')

results = clusterResults(means, userIdVectors)
inmem2 = sc.parallelize(results)
inmem2.saveAsTextFile('coursework2/support_results')

printResults(results)

sc.stop()


import numpy as np

#TODO
def imsharpen():
    """
    Doc string.
    """
    pass

def sobel_filter(type):
    assert type in ['ver', 'hor'], 'imagelib.sobel_filter: Invalid filter type'
    if type == 'ver':
        return np.array([[-1, 0, 1],
                         [-2, 0, 2],
                         [-1, 0, 1]])
    elif type == 'hor':
        return np.array([[1, 2, 1],
                         [0, 0, 0],
                         [-1, -2, -1]])

def laplassian_filter():
    filter = np.array([[ 0, -1,  0],
                       [-1,  4, -1],
                       [ 0, -1,  0]])
    return filter

def mean_blur_filter(filt_shape):
    return np.ones(filt_shape) / np.prod(filt_shape)

def imblur_mean(img, filt_shape):
    """
    Mean blur of an image.
    """
    filt = mean_blur_filter(filt_shape)
    return convolve(img, filt)

# TODO
def imblur_gaussian():
    pass

def convolve(img, filt):
    """
    img(numpy.ndarray): grayscale image
    filt(numpy.ndarray): 2D filter
    """
    assert len(img.shape) == 2, 'imagelib.convolve: Invalid image format'
    assert len(filt.shape) == 2, 'imagelib.convolve: Invalid filter format'
    assert np.prod(filt.shape) % 2 == 1, 'imagelib.convolve: Invalid filter format'

    img_h, img_w = img.shape
    filt_h, filt_w = filt.shape

    padding_w = (filt_w - 1) // 2
    padding_h = (filt_h - 1) // 2

    vertical_padding = np.zeros((img_h, padding_w))
    horizontal_padding = np.zeros((padding_h, img_w + 2 * padding_w))

    padded_img = np.concatenate([vertical_padding, img, vertical_padding], axis=-1)
    padded_img = np.concatenate([horizontal_padding, padded_img, horizontal_padding], axis=0)

    filtered_image = np.zeros(img.shape)

    for i in range(img_h):
        for j in range(img_w):
            # image patch of size [<padding_h>, <padding_w>] centered in [i, j].
            image_patch = padded_img[i : 2*padding_h+i+1, j : 2*padding_w+j+1]

            # apply filter to the patch
            filtered_image[i, j] = np.sum(filt * image_patch)

    return filtered_image

def sobel_edge_det(img, blur_filt_shape):
    img = imblur_mean(img, blur_filt_shape)

    filt_ver = sobel_filter('ver')
    edges_ver = convolve(img, filt_ver)

    filt_hor = sobel_filter('hor')
    edges_hor = convolve(img, filt_hor)

    edges_img = np.sqrt(edges_ver**2 + edges_hor**2)
    edges_img = edges_img / np.max(edges_img) * 255.
    edges_dir = np.arctan(edges_hor / edges_ver)

    return edges_img, edges_dir

def non_max_supression(edges, gradient):
    h, w = edges.shape

    Z = np.zeros(edges.shape)

    for i in range(1, h - 1):
        for j in range(1, w - 1):
            p, q = 255, 255

            if 3*np.pi/8 <= gradient[i][j] <= np.pi/2 or\
                -np.pi/2 <= gradient[i][j] < -3*np.pi/8:
                p = edges[i, j-1]
                q = edges[i, j+1]
            elif np.pi/8 <= gradient[i][j] < 3*np.pi/8:
                p = edges[i-1, j+1]
                q = edges[i+1, j-1]
            elif -np.pi/8 <= gradient[i][j] < np.pi/8:
                p = edges[i, j-1]
                q = edges[i, j+1]
            elif -3*np.pi/8 <= gradient[i][j] < -np.pi/8:
                p = edges[i-1, j-1]
                q = edges[i+1, j+1]

            if edges[i, j] >= p and edges[i, j] >= q:
                Z[i, j] = edges[i, j]

    return Z

def dual_threshold(edges, high, low):
    edges[edges < low] = 0.
    edges[edges >= high] = high

    threshold = np.zeros(edges.shape)
    threshold[edges > high] = 1.

    h, w = edges.shape

    def dfs(i, j):
        threshold[i][j] = 1.

        stack = []
        stack.append((i, j))

        while not len(stack) == 0:
            u, v = stack[-1]
            stack = stack[:-1]

            threshold[u][v] = 1.

            for x in range(-1, 2):
                for y in range(-1, 2):
                    newU = u + x
                    newV = v + y

                    if newU >= h or newU < 0 or newV >= w or newV < 0:
                        continue

                    if threshold[newU][newV] == 1:
                        continue

                    if low <= edges[newU][newV] <= high:
                        stack.append((newU, newV))

    for i in range(h):
        for j in range(w):
            if threshold[i, j] != 1 and edges[i, j] >= high:
                dfs(i, j)

    edges =  edges * threshold
    edges[edges > 0] = 255.

    return edges

def canny_edge(img, blur_filt_shape, high, low):
    """
    Canny edge detection algorithm implementation.
    """
    edges, edges_dir = sobel_edge_det(img, blur_filt_shape)
    edges = non_max_supression(edges, edges_dir)
    canny_edges = dual_threshold(edges, high, low)

    return canny_edges

# TODO
def histeq():
    """
    Histogram equalization.
    """
    pass

def hough_transform(edge, h, w, t):
    """
    Performs Hough transform.

    Args:
        edge(numpy.ndarray): edge[x, y] = 1 if pixel [x, y] contains an edge,
            otherwise it is 0.
        h(int): Number of bins with respect to the distance of the line from
            top left corner.
        w(int): Number of bins with respect to the angle of the normal from
            top left corner onto the line.
        t(int): Number of necessery votes in order to declare an existing line.
    Ret:
        List of tuples (rho, theta), one for every declared line.
    """

    voting_grid = [[0 for _ in range(w)] for _ in range(h)]

    d_theta = np.pi / w
    d_rho = np.hypot(edge.shape[0], edge.shape[1]) / h

    def vote(x, y):
        for i in range(w):
            theta = d_theta * i
            rho = np.cos(theta) * x + np.sin(theta) * y
            j = int(rho / d_rho)

            voting_grid[i][j] += 1

    for x in range(edge.shape[0]):
        for y in range(edge.shape[1]):
            if edge[x, y]:
                vote(x, y)

    lines = []

    for i in range(h):
        for j in range(w):
            if (voting_grid[i][j] >= t):
                lines.append((i * d_theta, d_rho * j))

    line_img = np.zeros(edge.shape, dtype=np.int32)
    for (theta, rho) in lines:
        for x in range(edge.shape[0]):
            y = int((rho - np.cos(theta) * x) / np.sin(theta))

            if 0 <= y < edge.shape[1]:
                line_img[x-1:x+1, y-1:y+1] = 1

        for y in range(edge.shape[1]):
            x = int((rho - np.sin(theta) * y) / np.cos(theta))

            if 0 <= x < edge.shape[0]:
                line_img[x-1:x+1, y-1:y+1] = 1

    return lines, line_img

#TODO:
def circle_hough_transform(edge, h, w, t):
    pass


import numpy as np
from numpy import sqrt

def mean(x: list) -> float:
    """given x can be an array or list output is the mean value"""
    mean_x = sum(x)/len(x)
    return mean_x

def mean_normalise(x: list) -> list:
    """Returns the mean normalized list for a given input list"""
    normalised_x = [xi - mean(x) for xi in x]
    return normalised_x

def std_dev(x: list) -> float:
    """Returns the standard deviation value for an input list"""
    norm_x = mean_normalise(x)
    var_x = sum(list(map(lambda x: x**2, norm_x)))
    return sqrt(var_x/len(x))

def mean_normalization(x: list) -> np.ndarray:
    """Returns the mean normalized array divided by the std.dev of the entries"""
    mean = np.mean(x, axis=0, keepdims=True)
    std = np.std(x, axis=0, keepdims=True)
    return (x - mean)/std

def r_sqr(y: np.ndarray, y_pred: np.ndarray) -> float:
    """Returns the R2 or the coefficient of determination
    for given Y and predicted Y arrays"""
    num = sum((y-y_pred)**2)
    den = sum((y-mean(y))**2)
    r_sqr = 1 - (num/den)
    return r_sqr[0]

def train_test_split(X: np.ndarray, Y: np.ndarray, test_split,shuffle=True):
    """Given the Independent (X) and Dependent (Y) variables,
    perform train-test split by random shuffling(default)"""
    try:
        dataset = np.hstack((X, Y.reshape(X.shape[0], 1)))  #create the dataset by concating X,Y
        shuffled_dataset = np.random.permutation(dataset)  #shuffle the dataset
        testset_length = np.int(len(shuffled_dataset)*test_split)  # length of test set
        trainset_length = len(shuffled_dataset) - testset_length   # length of training set
        X_train = shuffled_dataset[:trainset_length, :-1]
        Y_train = shuffled_dataset[:trainset_length, -1]
        X_test = shuffled_dataset[trainset_length:, :-1]
        Y_test = shuffled_dataset[trainset_length:, -1]

        return X_train, Y_train, X_test, Y_test

    except:
        print('Error !') # need to specify the kind and methods to change input
        print('Shape of Input X and Y arrays dont match !')

        return None