import numpy as np
import matplotlib.pyplot as plt
plt.style.use('seaborn-whitegrid')

def f(x) :

    s = np.power(x,3)  - x - 1

    return s

def f_p(x):

    s = 3*np.power(x,2)  - 1

    return s

def Newton_Raphson(x0,Tol,Nmax) :

    x=x0 - f(x0)/f_p(x0)
    e=abs(x-x0)
    n=1
    while n < Nmax and e > Tol :
          x0 = x
          x = x0 - f(x0)/f_p(x0)
          e=abs(x-x0)
          n += 1

    return x,n,f(x)

#------------ MAIN PROGRAMME -------------#

x0=1.5 ;Tol=10**(-8) ;Nmax=100
x,n,fx = Newton_Raphson(x0,Tol,Nmax)

print ('Approximate Solution is ' , x )
print ('number of steps is ' , n)
print ('Value at approximate solution is ' , fx)

t=np.linspace(0,2,100)
y=f(t)

plt.plot(t,y,'grey')
plt.legend(['f(x)'])
plt.ylabel('y')
plt.xlabel('x')
plt.show()


#################################################################################
#        DERIVATIVE ERROR ESTIMATION AND THE RICHARDSON EXTRAPOLATION METHOD
#    This program computes:
#    1) the Richardson derivative of f(x) = sin(x) at x = 1 using an adaptative scheme,
#    2) for a relative error < 1e-7,
#
#    (Marina von Steinkirch, spring 2013)
#
#################################################################################

import math
import numpy
import pylab

""" Functions """

def func(x):
    """ the function to be plotted """
    fx = numpy.sin(x)
    return fx

def fprime(x):
    """ the analytic derivative of func(x) """
    fp = numpy.cos(x)
    return fp

def machine_precision():
    """ calculates the machine precision, 2*eps"""
    x = 1.0
    eps = 1.0
    while (not x + eps == x):
        eps = eps/2.0
    return 2.0*eps

def delta1(h, x):
    """ calculates the second-order centered difference """
    d1 = (func(x+h) - func(x-h))/(2.0*h)
    return d1

def relative_error(fa, fn):
    """ calculates the relative error """
    e = abs( ( fa - fn ) / fa )
    return e

def absolute_error(fa, fn):
    """ calculates the relative error """
    e = abs(  fa - fn  )
    return e

def richardson_derivative(h, x):
    """ calculate the derivative using delta1 for h and h/2 """
    d = delta1(h, x)
    h = h/2
    d1 = delta1(h, x)
    f1 =  (4*d1-d)/3

    # MZ -- why is there an h**2 here?
    epsilon = h*h*abs(d-d1)
    return f1, epsilon

def printing(n, h, f1, fp, e, er, ea):
    print "\n________________________________________________________________________________"
    print "Iteration n = ", int(n)
    print "The distance between two points in this grid (h) is:    ", h
    print "Calculated (Richardsob) Derivative of f(x)=sin(x) at x = 1:    ", f1
    print "Analytic First Derivative of f(x)=sin(x) at x = 1:    ", fp
    print "Truncated Error (from the Taylor approx):    ", e
    print "Relative Error:    ", er
    print "Absolute Error:    ", ea
    return 0

""" Variables """

CONST_EPS = 10**(-6)            # relative error
CONST_XL = 0.0              # most left point
CONST_XR = math.pi          # most right point: 3.14 > x=1,
CONST_X = 1.0               # point where we are calculating the derivative

h_array = []
er_array = []
ea_array = []
epsilon_array = []

epsilon = 1.1*10**(-6)
n = 0.0

""" Main Program """
while (epsilon >= CONST_EPS):
    n += 1
    h = abs((CONST_XR - CONST_XL)/(2*float(n)))        # distance in the grid

    # MZ -- the error you are asked to monitor is the error between
    # the difference with h and the one with h/2
    f1, epsilon = richardson_derivative(h, CONST_X)
    error_r = relative_error(f1, fprime(CONST_X))
    error_a = absolute_error(f1, fprime(CONST_X))

    printing(int(n), h, f1, fprime(CONST_X), epsilon, error_r, error_a)

    h_array.append(h)
    er_array.append(error_r)
    ea_array.append(error_a)
    epsilon_array.append(epsilon)

""" Plotting the Errors vs h """
pylab.loglog(h_array, er_array, 'go',  label="Relative Error")
pylab.loglog(h_array, ea_array, 'r*',  label="Absolute Error")
pylab.loglog(h_array, epsilon_array,'b--',  label="Truncated Error $O(h^4)$")

leg = pylab.legend(loc=4,labelspacing=0.1)
ltext = leg.get_texts()
pylab.setp(ltext, fontsize='small')
leg.draw_frame(1)

pylab.xlabel('Log( Griding Distance h)')
pylab.ylabel('Log (Error)')
pylab.grid(True)

pylab.savefig("der_error.png")

""" Plotting the Errors vs n """
pylab.clf()
pylab.cla()
nlist = list(range(1, int(n)+1, 1))
pylab.loglog(nlist, er_array, 'go',  label="Relative Error")
pylab.loglog(nlist, ea_array, 'r*',  label="Absolute Error")
pylab.loglog(nlist, epsilon_array,'b--',  label="Truncated Error $O(h^4)$")

leg = pylab.legend(loc=1,labelspacing=0.1)
ltext = leg.get_texts()
pylab.setp(ltext, fontsize='small')
leg.draw_frame(1)

pylab.xlabel('Log (Number of Steps n)')
pylab.ylabel('Log (Error)')
pylab.grid(True)

pylab.savefig("der_error2.png")

print "\nMachine precision: ", machine_precision()
print "Done!"


#!/usr/bin/python3

"""
The riddle:
Given an array of N integer numbers, where all the numbers appear an even
amount of time except for a single number that appears an odd amount of times.
Find the number that appears an odd amount of times in the array, using O(1) memory space
and O(N) running time efficiency.

Some examples:
[2,2,1] --> 1
[14,14,14,14,4,4,4] --> 4
[1,2,1,4,2,4,5,1,1] --> 5

"""

import numpy as np

N = 2000

def solution(arr):
  """
	Apply xor on all of the numbers, numbers that appear even amount of times cancel themselves
	using xor and the only number that appear an odd amount of times stays.

	@param List[int] arr : the array as described in the riddle documentation.
	@return int : the unique odd number.
  """
  if len(arr) == 0:
    raise ValueError('Wrong input format')
  stored = arr[0]   # O(1) space for integer storage
  for number in arr[1:]:  # linear traverse (O(N))
    stored ^= number
  return stored

def generate_random_test_array(n=N):
  """ returns a random array that fits the input requirements and the expected
      solution for that generated array. """
  assert n > 1
  half = np.random.randint(100, size=n//2)
  odd = half[0]
  full = np.hstack((half, half, [odd]))
  np.random.shuffle(full)
  return full, odd

def test_solution():
  if not solution([2,2,1]) == 1:
    return False
  if not solution([14,14,14,14,4,4,4]) == 4:
    return False
  if not solution([1,2,1,4,2,4,5,1,1]) == 5:
    return False
  for _ in range(10):
    arr, expected = generate_random_test_array()
    if not solution(arr) == expected:
      return False
  return True


import sympy
from typing import Tuple

x = sympy.symbols('x')

def bisection_root_finder(eqn: sympy.Basic, low: float, high: float, tolerance: float) -> Tuple[float, int, float]:
    """
    Given a single-variable sympy equation with x as the defined symbol, the low end of an interval, the high end of the interval,
    and the tolerance, it will calculate the root contained in the interval using the bisection method.
    It returns a tuple containing the root and the iterations taken

    If it takes more than 100 iterations, it breaks and returns early
    """
    root = 0

    if eqn.evalf(subs={x: root}) == 0:
        return root
    low_answer = eqn.evalf(subs={x: low})
    high_answer = eqn.evalf(subs={x: high})

    root = (low + high)/2
    iterations = 0

    while high-low > tolerance:
        low_answer = eqn.evalf(subs={x: low})
        high_answer = eqn.evalf(subs={x: high})

        if low_answer*high_answer >= 0:  # They're both on the same side of the axis, this is an issue
            return None
        else:
            mid = (low+high)/2
            root = mid
            if high_answer * eqn.evalf(subs={x: mid}) >= 0:
                high = mid
            else:
                low = mid
        iterations += 1
        if iterations > 100:
            break
    return root, iterations

def newton_raphson_root_finder(equation: sympy.Basic, first_guess: float, tolerance: float) -> Tuple[float, int]:
    """
    Given a single-variable sympy equation with x as the defined symbol, an initial guess, and a tolerance,
    this function will return a tuple containing the root and the iterations taken to find the root.
    It uses the Newton-Raphson method to find the root.
    """
    error = 1_000  # Starting with a high initial error
    x_o = first_guess
    f = equation
    f_prime = sympy.diff(equation, x)
    iterations = 0
    while error > tolerance:
        root = x_o - f.evalf(subs={x: x_o}, chop=True) / f_prime.evalf(subs={x: x_o}, chop=True)
        error = abs(root - x_o)
        x_o = root
        iterations += 1
    return root, iterations

def secant_root_finder(eqn: sympy.Basic, x0: float, x1: float, tolerance: float) -> Tuple[float, int]:
    """
    Given a sympy equation with x as the symbol, two initial guesses on the x-axis and a tolerance,
    It will find the root using the secant method

    Returns tuple containing root, number of iterations taken
    """
    error = 1_000  # High initial error
    iterations = 0

    while error > tolerance:
        # Calculating slope of the line between the two points
        slope = (eqn.evalf(subs={x: x1}) - eqn.evalf(subs={x: x0})) / (x1 - x0)

        # Calculating the next point
        root = x0 - ((eqn.evalf(subs={x: x0}))/slope)

        # Prep for next iteration
        error = abs(x0 - root)
        x0 = x1
        x1 = root
        iterations += 1
    return root, iterations


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

### Defining theta
theta = math.pi/4

### Generates count number of random values in the range [0, 1]
def getU(count):
	u = []
	for i in range(count):
		key = random.rand()
		u.append(key)

	return u

def getX(u):
	x = []
	for t in u:
		res = 0.50 - 0.50*math.cos(math.pi*t)
		x.append(res)

	return x

def getSampleMeanVariance(x):
	sum = 0.00
	for i in x:
		sum += i

	avg = sum/(len(x))

	cnt = 0.00
	for i in x:
		cnt += (i-avg)**2

	variance = cnt/(len(x)-1)
	return avg, variance

def plotCDF(data):
	data_size = len(data)

	data_set = sorted(set(data))
	bins = np.append(data_set, data_set[-1]+1)

	counts, bin_edges = np.histogram(data, bins=bins, density=False)

	counts = counts.astype(float)/data_size

	cdf = np.cumsum(counts)

	plt.plot(bin_edges[0:-1], cdf, linestyle='--', marker='o', color='b')
	plt.ylim((0, 1))
	plt.ylabel("CDF")
	plt.grid(True)

	plt.show()

# Plots y = (2/pi) arc sin(root(x))
def plotActualDistributionFunction():
	in_array = np.linspace(-1.00, 1.00, 10000)
	out_array = np.arcsin(in_array)

	# print("in_array : ", in_array)
	# print("\nout_arraywith arcsin : ", out_array)

	# red for numpy.arcsin()
	plt.plot(in_array, out_array,
	            color = 'blue', marker = "*")

	plt.title("Y = F(x)")
	plt.xlabel("X")
	plt.ylabel("Y")
	plt.show()

def execute(cnt):
	print("For input size of : " + str(cnt))
	u = getU(cnt)
	u.sort()
	# print(u)
	x = getX(u)
	# print(x)

	sMean, sVariance = getSampleMeanVariance(x)
	print("Sample Mean: " + str(sMean))
	print("Sample Variance: " + str(sVariance))
	print()
	plotCDF(x)

def main():
	plotActualDistributionFunction()

	execute(10)
	execute(100)
	execute(1000)
	execute(10000)
	execute(100000)

if __name__ == '__main__':
	main()


# -*- coding:utf-8 -*-
# =========================================================================== #
# Project : MLStudio                                                          #
# File    : \regression.py                                                    #
# Python  : 3.8.3                                                             #
# --------------------------------------------------------------------------- #
# Author  : John James                                                        #
# Company : nov8.ai                                                           #
# Email   : jjames@nov8.ai                                                    #
# URL     : https://github.com/nov8ai/MLStudio                                #
# --------------------------------------------------------------------------- #
# Created       : Thursday, July 16th 2020, 2:25:57 am                        #
# Last Modified : Thursday, July 16th 2020, 2:25:58 am                        #
# Modified By   : John James (jjames@nov8.ai)                                 #
# --------------------------------------------------------------------------- #
# License : BSD                                                               #
# Copyright (c) 2020 nov8.ai                                                  #
# =========================================================================== #
import math
import numpy as np

from mlstudio.supervised.metrics.base import BaseRegressionMetric
# --------------------------------------------------------------------------- #
class ResidualSumSquaredError(BaseRegressionMetric):
    """Computes sum squared residuals given"""

    _mode  = 'min'
    _code = 'SSR'
    _name  = 'residual_sum_squared_error'
    _label  = "Residual Sum Squared Error"

    _best  = np.min
    _better  = np.less
    _worst  = np.Inf
    _epsilon_factor  = -1

    def __call__(self, y, y_pred, *args, **kwargs):
        e = y - y_pred
        return np.sum(e**2)

class TotalSumSquaredError(BaseRegressionMetric):
    """Computes total sum of squares"""

    _mode  = 'min'
    _code = "SST"
    _name  = 'total_sum_squared_error'
    _label  = "Total Sum Squared Error"

    _best  = np.min
    _better  = np.less
    _worst  = np.Inf
    _epsilon_factor  = -1

    def __call__(self, y, y_pred, *args, **kwargs):
        y_avg = np.mean(y)
        e = y-y_avg
        return np.sum(e**2)

class R2(BaseRegressionMetric):
    """Computes coefficient of determination."""

    _mode  = 'max'
    _code = "R2"
    _name  = 'R2'
    _label  = 'Coefficient of Determination (R2)'

    _best  = max
    _better  = np.greater
    _worst  = -np.Inf
    _epsilon_factor  = 1

    def __call__(self, y, y_pred, *args, **kwargs):
        self._ssr = ResidualSumSquaredError()
        self._sst = TotalSumSquaredError()
        r2 = 1 - (self._ssr(y, y_pred)/self._sst(y, y_pred))
        return r2

class AdjustedR2(BaseRegressionMetric):
    """Computes adjusted coefficient of determination."""

    _mode  = 'max'
    _code = "AR2"
    _name  = 'adjusted_r2'
    _label  = "Adjusted R2"

    _best  = max
    _better  = np.greater
    _worst  = -np.Inf
    _epsilon_factor  = 1

    def __call__(self, y, y_pred, n_features, *args, **kwargs):
        r2_scorer = R2()
        r2 = r2_scorer(y, y_pred)
        n = y.shape[0]
        p = n_features
        ar2 = 1 - (1 - r2) * (n-1) / (n-p-1)
        return ar2

class PercentVarianceExplained(BaseRegressionMetric):
    """Computes proportion of variance explained."""

    _mode  = 'max'
    _code = "PVE"
    _name  = 'percent_variance_explained'
    _label  = "Percent Variance Explained"

    _best  = max
    _better  = np.greater
    _worst  = -np.Inf
    _epsilon_factor  = 1

    def __call__(self, y, y_pred, *args, **kwargs):
        var_explained = 1 - (np.var(y-y_pred) / np.var(y))
        return var_explained * 100

class MeanAbsoluteError(BaseRegressionMetric):
    """Computes mean absolute error given data and parameters."""

    _mode  = 'min'
    _code = "MAE"
    _name  = 'mean_absolute_error'
    _label  = "Mean Absolute Error"

    _best  = np.min
    _better  = np.less
    _worst  = np.Inf
    _epsilon_factor  = -1

    def __call__(self, y, y_pred, *args, **kwargs):
        e = abs(y-y_pred)
        return np.mean(e)

class MeanSquaredError(BaseRegressionMetric):
    """Computes mean squared error given data and parameters."""

    _mode  = 'min'
    _code = "MSE"
    _name  = 'mean_squared_error'
    _label  = "Mean Squared Error"

    _best  = np.min
    _better  = np.less
    _worst  = np.Inf
    _epsilon_factor  = -1

    def __call__(self, y, y_pred, *args, **kwargs):
        e = y - y_pred
        return np.mean(e**2)

class NegativeMeanSquaredError(BaseRegressionMetric):
    """Computes negative mean squared error given data and parameters."""

    _mode  = 'max'
    _code = "NMSE"
    _name  = 'negative_mean_squared_error'
    _label  = "Negative Mean Squared Error"

    _best  = max
    _better  = np.greater
    _worst  = -np.Inf
    _epsilon_factor  = 1

    def __call__(self, y, y_pred, *args, **kwargs):
        e = y - y_pred
        return -np.mean(e**2)

class RootMeanSquaredError(BaseRegressionMetric):
    """Computes root mean squared error given data and parameters."""

    _mode  = 'min'
    _code = "RMSE"
    _name  = 'root_mean_squared_error'
    _label  = "Root Mean Squared Error"

    _best  = np.min
    _better  = np.less
    _worst  = np.Inf
    _epsilon_factor  = -1

    def __call__(self, y, y_pred, *args, **kwargs):
        e = y-y_pred
        return np.sqrt(np.mean(e**2))

class NegativeRootMeanSquaredError(BaseRegressionMetric):
    """Computes negative root mean squared error given data and parameters."""

    _mode  = 'max'
    _code = "NRMSE"
    _name  = 'negative_root_mean_squared_error'
    _label  = "Negative Root Mean Squared Error"

    _best  = max
    _better  = np.greater
    _worst  = -np.Inf
    _epsilon_factor  = 1

    def __call__(self, y, y_pred, *args, **kwargs):
        e = y-y_pred
        return -np.sqrt(np.mean(e**2))

class MeanSquaredLogError(BaseRegressionMetric):
    """Computes mean squared log error given data and parameters."""

    _mode  = 'min'
    _code = "MSLE"
    _name  = 'mean_squared_log_error'
    _label  = "Mean Squared Log Error"

    _best  = np.min
    _better  = np.less
    _worst  = np.Inf
    _epsilon_factor  = -1

    def __call__(self, y, y_pred, *args, **kwargs):
        e = np.log(y+1)-np.log(y_pred+1)
        y = np.clip(y, 1e-15, 1-1e-15)
        y_pred = np.clip(y_pred, 1e-15, 1-1e-15)
        e = np.log(y)-np.log(y_pred)
        return np.mean(e**2)

class MedianAbsoluteError(BaseRegressionMetric):
    """Computes median absolute error given data and parameters."""

    _mode  = 'min'
    _code = "MdAE"
    _name  = 'median_absolute_error'
    _label  = "Median Absolute Error"

    _best  = np.min
    _better  = np.less
    _worst  = np.Inf
    _epsilon_factor  = -1

    def __call__(self, y, y_pred, *args, **kwargs):
        return np.median(np.abs(y_pred-y))

class MeanAbsolutePercentageError(BaseRegressionMetric):
    """Computes mean absolute percentage given data and parameters."""
    _mode  = 'min'
    _code = "MAPE"
    _name  = 'mean_absolute_percentage_error'
    _label  = "Mean Absolute Percentage Error"

    _best  = np.min
    _better  = np.less
    _worst  = np.Inf
    _epsilon_factor  = -1

    def __call__(self, y, y_pred, *args, **kwargs):
        return 100*np.mean(np.abs((y-y_pred)/y))


# This pattern is the same for more inputs and weights. Weights and inputs have the same length
# inputs = [1, 2, 4]
# weights = [0.2, 0.8, -0.5]
# bias = 2

# output = inputs[0] * weights[0] + inputs[1] * weights[1] + inputs[2] * weights[2] + bias

# print(output)

# With multiple layers
# inputs = [1, 2, 3, 2.5]

# weights1 = [0.2, 0.8, -0.5, 1]
# weights2 = [0.5, -0.91, 0.26, -0.5]
# weights3 = [-0.26, -0.27, 0.17, 0.87]

# bias1 = 2
# bias2 = 3
# bias3 = 0.5

# outputs = [
#     # Neuron 1:
#     inputs[0] * weights1[0]
#     + inputs[1] * weights1[1]
#     + inputs[2] * weights1[2]
#     + inputs[3] * weights1[3]
#     + bias1,
#     # Neuron 2:
#     inputs[0] * weights2[0]
#     + inputs[1] * weights2[1]
#     + inputs[2] * weights2[2]
#     + inputs[3] * weights2[3]
#     + bias2,
#     # Neuron 3:
#     inputs[0] * weights3[0]
#     + inputs[1] * weights3[1]
#     + inputs[2] * weights3[2]
#     + inputs[3] * weights3[3]
#     + bias3,
# ]

# print(outputs)

# A more organized way
# inputs = [1, 2, 3, 2.5]
# weights = [[0.2, 0.8, -0.5, 1], [0.5, -0.91, 0.26, -0.5], [-0.26, -0.27, 0.17, 0.87]]
# biases = [2, 3, 0.5]

# # Output of current layer
# layer_outputs = []
# # For each neuron
# for neuron_weights, neuron_bias in zip(weights, biases):
#     # Zeroed output of given neuron
#     neuron_output = 0
#     # For each input and weight to the neuron
#     for n_input, weight in zip(inputs, neuron_weights):
#         # Multiply this input by associated weight
#         # and add to the neuron’s output variable
#         neuron_output += n_input * weight
#     # Add bias
#     neuron_output += neuron_bias
#     # Put neuron’s result to the layer’s output list
#     layer_outputs.append(neuron_output)

# print(layer_outputs)

# How to calculate a dot product
# a = [1, 2, 3]
# b = [2, 3, 4]

# dot_product = a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
# print(dot_product)

# With NumPy
# import numpy as np

# inputs = [1.0, 2.0, 3.0, 2.5]
# weights = [0.2, 0.8, -0.5, 1.0]
# bias = 2.0

# outputs = np.dot(weights, inputs) + bias

# print(outputs)

# Three neurons
# import numpy as np

# inputs = [1.0, 2.0, 3.0, 2.5]
# weights = [[0.2, 0.8, -0.5, 1], [0.5, -0.91, 0.26, -0.5], [-0.26, -0.27, 0.17, 0.87]]
# biases = [2.0, 3.0, 0.5]

# layer_outputs = np.dot(weights, inputs) + biases

# print(layer_outputs)

# import numpy as np

# a = [1, 2, 3]
# print(np.array([a]))

# a = [1, 2, 3]
# print(np.expand_dims(np.array(a), axis=0))

# import numpy as np

# a = [1, 2, 3]
# b = [2, 3, 4]

# a = np.array([a])
# b = np.array([b]).T

# print(np.dot(a, b))

# import numpy as np

# inputs = [[1.0, 2.0, 3.0, 2.5], [2.0, 5.0, -1.0, 2.0], [-1.5, 2.7, 3.3, -0.8]]
# weights = [[0.2, 0.8, -0.5, 1.0], [0.5, -0.91, 0.26, -0.5], [-0.26, -0.27, 0.17, 0.87]]
# biases = [2.0, 3.0, 0.5]

# layer_outputs = np.dot(inputs, np.array(weights).T) + biases

# print(layer_outputs)

# import numpy as np
# import nnfs

# nnfs.init()

# print(np.random.randn(2, 5))
# print(np.zeros((2, 5)))

import numpy as np
import nnfs

nnfs.init()

n_inputs = 2
n_neurons = 4

weights = 0.01 * np.random.randn(n_inputs, n_neurons)
biases = np.zeros((1, n_neurons))

print(weights)
print(biases)


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Mon Sep 16 10:05:07 2019

@author: amandaash
"""
import numpy as np
import matplotlib.pyplot as plt
import random as rand

#first derivatives
x = np.arange(-2*np.pi, 2*np.pi, 0.01)
f_x = np.sin(x)
f_prime_numpy = np.gradient(f_x, 0.01)
f_prime = []

for step in range(len(list(x))):
    if step >= 0:
        derivative = (f_x[step]-f_x[step-1])/(x[step]-x[step-1])
        f_prime.append(derivative)
    else:
        continue
plt.title('first derivative sin(x)')
plt.plot(x[1:], f_prime[1:], '.', label = 'forward difference', alpha = 0.2, color = 'b')
plt.plot(x, f_prime_numpy, '.', label = 'numpy', alpha = 0.2, color = 'r')
plt.plot(x, np.cos(x), '.', label = 'exact', alpha = 0.2, color = 'g')
plt.legend()
plt.savefig('first_derivative.pdf')
plt.show()

error_forward = (np.cos(x)[1:] - f_prime[1:])/np.cos(x)[1:]
plt.title('first derivative error forward difference')
plt.plot(x[1:], error_forward, '.')
plt.savefig('forward_diff_err.pdf')
plt.show()

error_numpy = (np.cos(x) - f_prime_numpy)/(np.cos(x))
plt.title('first derivative error numpy')
plt.plot(x[:-1], error_numpy[:-1], '.')
plt.savefig('grad_err.pdf')
plt.show()

#second derivatives
f_prime_prime_numpy = np.gradient(f_prime_numpy, 0.01)
f_prime_prime = []
for step in range(len(list(x))):
    if step >= 0:
        derivative = (f_prime[step]-f_prime[step-1])/(x[step]-x[step-1])
        f_prime_prime.append(derivative)
    else:
        continue
plt.title('second derivative sin(x)')
plt.plot(x[2:], f_prime_prime[2:], '.', label = 'forward difference', alpha = 0.2, color = 'b')
plt.plot(x, f_prime_prime_numpy, '.', label = 'numpy', alpha = 0.2, color = 'r')
plt.plot(x, -np.sin(x), '.', label = 'exact', alpha = 0.2, color = 'g')
plt.legend()
plt.savefig('2nd_derivative.pdf')
plt.show()

error_forward_2 = (-np.sin(x)[2:] - f_prime_prime[2:])/(-np.sin(x)[2:])
plt.title('second derivative error forward difference')
plt.plot(x[2:], error_forward_2, '.')
plt.savefig('2nd_derivative_err_forward_diff.pdf')
plt.show()

error_numpy_2 = (-np.sin(x) - f_prime_prime_numpy)/-np.sin(x)
plt.title('second derivative error numpy')
plt.plot(x[1:-2], error_numpy_2[1:-2], '.')
plt.savefig('2nd_derivative_err_numpy.pdf')
plt.show()

#random noise
epsilon_array = []
for n in range(len(list(x))):
    epsilon_array.append(rand.random()*0.001)
f_x = np.sin(x) + epsilon_array
f_prime_numpy = np.gradient(f_x, 0.01)
f_prime = []

for step in range(len(list(x))):
    if step >= 0:
        derivative = (f_x[step]-f_x[step-1])/(x[step]-x[step-1])
        f_prime.append(derivative)
    else:
        continue
plt.title('first derivative sin(x) + $\epsilon$')
plt.plot(x[1:], f_prime[1:], '.', label = 'forward difference', alpha = 0.2, color = 'b')
plt.plot(x, f_prime_numpy, '.', label = 'numpy', alpha = 0.2, color = 'r')
plt.plot(x, np.cos(x), '.', label = 'exact', alpha = 0.2, color = 'g')
plt.legend()
plt.savefig('noisy_derivative.pdf')
plt.show()

error_forward = np.cos(x)[1:] - f_prime[1:]
plt.title('first derivative error forward difference')
plt.plot(x[1:], error_forward, '.')
plt.show()

error_numpy = np.cos(x) - f_prime_numpy
plt.title('first derivative error numpy')
plt.plot(x[:-1], error_numpy[:-1], '.')
plt.show()

#second derivatives
f_prime_prime_numpy = np.gradient(f_prime_numpy, 0.01)
f_prime_prime = []
for step in range(len(list(x))):
    if step >= 0:
        derivative = (f_prime[step]-f_prime[step-1])/(x[step]-x[step-1])
        f_prime_prime.append(derivative)
    else:
        continue
plt.title('second derivative sin(x) + $\epsilon$')
plt.plot(x[2:], f_prime_prime[2:], '.', label = 'forward difference', alpha = 0.2, color = 'b')
plt.plot(x, f_prime_prime_numpy, '.', label = 'numpy', alpha = 0.2, color = 'r')
plt.plot(x, -np.sin(x), '.', label = 'exact', alpha = 0.2, color = 'g')
plt.legend()
plt.show()

error_forward_2 = -np.sin(x)[2:] - f_prime_prime[2:]
plt.title('second derivative error forward difference')
plt.plot(x[2:], error_forward_2, '.')
plt.show()

error_numpy_2 = -np.sin(x) - f_prime_prime_numpy
plt.title('second derivative error numpy')
plt.plot(x[1:-2], error_numpy_2[1:-2], '.')
plt.show()


import numpy as np

versicolor_petal_length = [4.7, 4.5, 4.9, 4.0,  4.6, 4.5, 4.7, 3.3, 4.6, 3.9, 3.5, 4.2, 4.0,  4.7, 3.6, 4.4, 4.5, 4.1,
     4.5, 3.9, 4.8, 4.0,  4.9, 4.7, 4.3, 4.4, 4.8, 5.0,  4.5, 3.5, 3.8, 3.7, 3.9, 5.1, 4.5, 4.5,
     4.7, 4.4, 4.1, 4.0,  4.4, 4.6, 4.0,  3.3, 4.2, 4.2, 4.2, 4.3, 3.0,  4.1]

# Array of differences to mean: differences
difference = versicolor_petal_length - np.mean(versicolor_petal_length)
#print(difference)

# Square the differences: diff_sq
diff_sq = difference **2

# Compute the mean square difference: variance_explicit
variance_explicit = np.mean(diff_sq)

# Compute the variance using NumPy: variance_np
variance_np = np.var(versicolor_petal_length)

# Print the results
print(variance_explicit, variance_np)

standard_dev_exp = np.sqrt(variance_explicit)
standard_dev_np = np.std(versicolor_petal_length)

print(standard_dev_exp, standard_dev_np)


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

# OBJECTIVE : The dataset contains detailed attributes for every player registered in the latest edition of FIFA 19 database. Our objective is to create Linear, Multiple and Polynomail Regression models to predict the potential of a player based on several attributes.

# In[88]:

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

# In[89]:

# reading dataset
data=pd.read_csv("../../../input/karangadiya_fifa19/data.csv")

# In[90]:

# displaying first 5 rows
data.head()

# In[91]:

data.shape #(no. of rows, no. of columns)

# In[92]:

data.describe()

# In[93]:

# finding any null values in data
data.isnull().any()

# #  Linear Regression - Predicting Potential based on Age of the player

# In[94]:

# x = Age(independent variable)
x=data.iloc[:,3]

# In[95]:

x.head()

# In[96]:

x.isnull().any()

# In[97]:

# y = Potential(dependent variable)
y=data.iloc[:,8]

# In[98]:

y.head()

# In[99]:

y.isnull().any()

# In[100]:

plt.bar(data["Age"],data["Potential"])
plt.xlabel("Age of Player")
print()

# In[101]:

# splitting data into train and tet set
from sklearn.model_selection import train_test_split

# In[102]:

x_train,x_test,y_train,y_test=train_test_split(x,y,test_size=0.2,random_state=0)

# In[103]:

from sklearn.linear_model import LinearRegression

# In[104]:

# making object regressor of class LinearRegression
regressor=LinearRegression()

# I was facing errors with fitting the data so we reshape x_train and y_train by first converting them into ndarray.

# In[105]:

type(x_train)
type(y_train)

# In[106]:

x_train=np.array(x_train)
y_train=np.array(y_train)

# In[107]:

type(x_train)
type(y_train)

# In[108]:

x_train=x_train.reshape(-1,1)
y_train=y_train.reshape(-1,1)

# In[109]:

# fitting training set into object regressor
regressor.fit(x_train,y_train)

# To avoid error in prediting we reshape x_test also.

# In[110]:

x_test=np.array(x_test)

# In[111]:

x_test=x_test.reshape(-1,1)

# In[112]:

# Predicting y from test set
y_pred= regressor.predict(x_test)

# In[113]:

# Visualising training dataset
plt.scatter(x_train,y_train,color="red")
plt.xlabel("Age of Player")
plt.ylabel("Potential of Player")
plt.plot(x_train, regressor.predict(x_train),color="blue") # To draw line of regression
print()

# In[114]:

# Visualising test dataset
plt.scatter(x_test,y_test,color="red")
plt.xlabel("Age of Player")
plt.ylabel("Potential of Player")
plt.plot(x_train, regressor.predict(x_train),color="blue")
print()

# In[115]:

# Finding intercept of linear regression line
regressor.intercept_

# In[116]:

# Finding coefficient of linear regression line
regressor.coef_

# In[117]:

# Finding mean squared error of linear regression model
from sklearn.metrics import mean_squared_error

# In[118]:

mean_squared_error(y_test,y_pred)

# # Multiple regression - Predicting potential based on age, agility, balance, stamina, strength, composure
#

# In[119]:

# independent variables are - Age, Agility, Balance, stamina, Strength, Composure
x=data.iloc[:,[3,66,68,71,72,79]]

# In[120]:

x.head()

# In[121]:

# checking if there are null values in x and then filling them.
x.isnull().any()

# In[122]:

x=x.fillna(method='ffill')

# In[123]:

x.isnull().any()

# In[124]:

# dependent variable = Potential
y=data.iloc[:,8]

# In[125]:

y.head()

# In[126]:

y.isnull().any()

# In[127]:

sns.lineplot(x="Potential", y="Age",data=data,label="Age", ci= None)
sns.lineplot(x="Potential", y="Agility",data=data,label="Agility", ci= None)
sns.lineplot(x="Potential", y="Balance",data=data,label="Balance", ci= None)
sns.lineplot(x="Potential", y="Stamina",data=data,label="Stamina", ci= None)
sns.lineplot(x="Potential", y="Strength",data=data,label="Strength", ci= None)
sns.lineplot(x="Potential", y="Composure",data=data,label="Composure", ci= None)

# In[128]:

x_train,x_test,y_train,y_test=train_test_split(x,y,test_size=0.2)

# In[129]:

regressor=LinearRegression()

# In[130]:

regressor.fit(x_train,y_train)

# In[131]:

regressor.predict(x_test)

# In[132]:

# Visualising Actual and predicted values of Potential of player
plt.scatter(y_test,y_pred)
plt.xlabel("Actual Potential")
plt.ylabel("Predicted Potential")
print()

# Seems like the actual and predicted values are very close to each other.

# In[133]:

regressor.intercept_

# In[134]:

regressor.coef_

# Backward Elimination - Making optimal regression model by finding the statistical significance of all independent variables

# In[135]:

# let us take the significance level (SL)= 0.05
import statsmodels.formula.api as sm

# In[136]:

# fitting all variables in the model
regressor_OLS=sm.OLS(endog=y,exog=x).fit()

# In[137]:

# Finding statistical summary of all variables
regressor_OLS.summary()

# As we see all the P- values are less than SL(0.05), that means all the variables are significant and none of them can be removed.
# t-value shows the statistical significane of each variable.
# F-static shows us how significant the fit is.
# Adjusted- R is 0.986 that means our model explains 98.6% variables in dependent variables.

# # Polynomial Regression - Predicting potential based on the age of player

# In[138]:

# independent variable= age
x=data.iloc[:,3]

# In[139]:

x.head()

# In[140]:

# dependent variable = potential
y=data.iloc[:,8]

# In[141]:

y.head()

# In[142]:

x_train,x_test,y_train,y_test=train_test_split(x,y,test_size=0.2,random_state=42)

# In[143]:

x_train=np.array(x_train)
y_train=np.array(y_train)

# In[144]:

x_train=x_train.reshape(-1,1)
y_train=y_train.reshape(-1,1)

# In[145]:

lin_reg_1=LinearRegression()

# In[146]:

lin_reg_1.fit(x_train,y_train)

# In[147]:

x_test=np.array(x_test)

# In[148]:

x_test=x_test.reshape(-1,1)

# In[149]:

y_pred_1=lin_reg_1.predict(x_test)

# In[150]:

# Making polynomail regression model
from sklearn.preprocessing import PolynomialFeatures

# In[151]:

poly_reg=PolynomialFeatures(degree=3)

# In[152]:

x=np.array(x)

# In[153]:

x=x.reshape(-1,1)

# In[154]:

# Making polynomial matrix of x of degree 3
x_poly=poly_reg.fit_transform(x)

# In[155]:

x_poly

# In[156]:

x_poly_train,x_poly_test,y_train,y_test=train_test_split(x_poly,y,test_size=0.2, random_state=42)

# In[157]:

# Making another object to fit polynomial set
lin_reg_2=LinearRegression()

# In[158]:

lin_reg_2.fit(x_poly_train,y_train)

# In[159]:

y_pred_2=lin_reg_2.predict(x_poly_test)

# In[160]:

# Visualizing Linear Regression Model
plt.scatter(x_test,y_test,color='red')
plt.xlabel("Age of Player")
plt.ylabel("Potential of Player")
plt.title("Linear Regression Curve ")
plt.plot(x_train,lin_reg_1.predict(x_train),color='blue')
print()

# In[161]:

# Visualizing Polynomial Regression Model
plt.scatter(x_test,y_test,color='red')
plt.xlabel("Age of Player")
plt.ylabel("Potential of Player")
plt.title("Polynomial Regression Curve ")
plt.plot(x_train,lin_reg_2.predict(poly_reg.fit_transform(x_train)),color='blue')
print()

# In[162]:

mean_squared_error(y_test,y_pred_2)

# We can see the mean squared error of polynomail regression model < mean squared error of linear regression model. So polynomail regression model is more accurate.


import numpy as np
import scipy as sp
from scipy import spatial

def paired_points_matching(source, target):
    """
    Calculates the transformation T that maps the source to the target
    :param source: A N x 3 matrix with N 3D points
    :param target: A N x 3 matrix with N 3D points
    :return:
        T: 4x4 transformation matrix mapping source onto target
        R: 3x3 rotation matrix part of T
        t: 1x3 translation vector part of T
    """

    T = np.eye(4)
    R = np.eye(3)
    t = np.zeros((1, 3))

    N = source.shape[0]

    centroidSource = np.mean(source, axis=0)
    centroidTarget = np.mean(target, axis=0)

    srcCentrAlign = source - np.tile(centroidSource, (N, 1))
    trgCentrAlign = target - np.tile(centroidTarget, (N, 1))

    covMatrix = np.dot(srcCentrAlign.T, trgCentrAlign)

    uMat, sMat, vMat = np.linalg.svd(covMatrix)

    R = np.dot(vMat.T, uMat.T)

    t = - (np.dot(R, centroidSource)) + centroidTarget

    T[:3, :3] = R
    T[:3, 3] = t

    return T, R, t

def find_nearest_neighbor(src, dst):
    """
    Finds the nearest neighbor of every point in src in dst
    :param src: A N x 3 point cloud
    :param dst: A N x 3 point cloud
    :return: the
    """
    tree = sp.spatial.KDTree(dst)
    distance, index = tree.query(src)

    return distance, index

def icp(source, target, init_pose=None, max_iterations=1000, tolerance=0.0001):
    """
    Iteratively finds the best transformation that mapps the source points onto the target
    :param source: A N x 3 point cloud
    :param target: A N x 3 point cloud
    :param init_pose: A 4 x 4 transformation matrix for the initial pose
    :param max_iterations: default 10
    :param tolerance: maximum allowed error
        :return: A 4 x 4 rigid transformation matrix mapping source to target
            the distances and the error
    """
    # T = np.eye(4)
    # distances = 0
    # error = 0

    # Your code goes here

    src_init = np.dot(init_pose[:3, :3], source.T).T
    src_init = src_init + np.tile(init_pose[:3, 3], (source.shape[0], 1))

    tmp_trg = np.zeros_like(source)

    if init_pose is None:
        T = np.eye(4)
    else:
        T = init_pose

    tmp_tol = np.inf
    error = np.inf

    k = 0

    while tmp_tol > tolerance and k < max_iterations:
        distance, idx = find_nearest_neighbor(src_init, target)
        for ii, el in enumerate(idx):
            tmp_trg[ii] = target[el]

        T_tmp, R_tmp, t_tmp = paired_points_matching(src_init, tmp_trg)

        src_init = np.dot(R_tmp, src_init.T).T
        src_init = src_init + np.tile(t_tmp, (source.shape[0], 1))
        T = np.dot(T_tmp, T)

        err_tmp = error
        error = np.sum(distance) / distance.shape[0]
        error = np.sqrt(error)
        tmp_tol = err_tmp - error

        k += 1

    print("Iterations: ", k)
    return T, distance, error

def get_initial_pose(template_points, target_points):
    """
    Calculates an initial rough registration
    (Optionally you can also return a hand picked initial pose)
    :param source:
    :param target:
    :return: A transformation matrix
    """
    T = np.eye(4)

    # Your code goes here

    centr_tmpl = np.mean(template_points, axis=0)
    centr_target = np.mean(target_points, axis=0)
    t = centr_target - centr_tmpl

    T[:3, 3] = t

    return T


import numpy as np
from .integrate import Integrate

class Simpson(Integrate):
    """
    Approximate the integral of f(x) from a to b by Simpson's rule.

    Simpson's rule approximates the integral of f(x) dx by the sum:
    (dx/3) sum(f(x_{2i-2} + 4f(x_{2i-1}) + f(x_{2i})) from i = 1 to N/2 where N is the number of periods.
    dx = (b - a)/N
    x_i = a + i*dx

    Parameters
    ----------
    f : function
        A single variable function f(x), ex: lambda x:np.exp(x**2)
    """

    def __init__(self, f):
        Integrate.__init__(self, f)
        self.N = 250

    def compute_integral(self, a, b, N=250):
        """
        Parameters
        ----------
        a , b : numbers
        Interval of integration [a,b] defult to [-1,1]

        N : even integer
        Number of sub-intervals of [a,b]

        Returns
        -------
        float
            Approximation of the integral of f(x) from a to b using
            Simpson's rule with N subintervals of equal length.

        Examples
        --------
        >>> compute_integral(0,1,10), f = lambda x : 3*x**2
        1.0
        """
        self.a = a
        self.b = b
        self.N = N

        if self.N % 2 == 1:
            raise ValueError("N must be an even integer.")
        dx = (self.b - self.a) / self.N
        x = np.linspace(self.a, self.b, self.N+1)
        y = [self.f(i) for i in x]
        ans = dx/3 * np.sum(y[0:-1:2] + 4*y[1::2] + y[2::2])
        return ans


import math
import numpy as np

def basic_sigmoid(x):
    """
    Compute sigmoid of x.

    Arguments:
    x -- A scalar

    Return:
    s -- sigmoid(x)
    """

    s = 1 / (1+math.exp(-x))

    return s

def sigmoid(x):
    """
    Compute the sigmoid of x

    Arguments:
    x -- A scalar or numpy array of any size

    Return:
    s -- sigmoid(x)
    """

    s = 1 / (1+np.exp(-x))

    return s

def sigmoid_derivative(x):
    """
    Compute the gradient (also called the slope or derivative) of the sigmoid function with respect to its input x.
    You can store the output of the sigmoid function into variables and then use it to calculate the gradient.

    Arguments:
    x -- A scalar or numpy array

    Return:
    ds -- Your computed gradient.
    """

    s = sigmoid(x)
    ds =s * (1 - s)

    return ds

def image2vector(image):
    """
    Argument:
    image -- a numpy array of shape (length, height, depth)

    Returns:
    v -- a vector of shape (length*height*depth, 1)
    """

    v = image.reshape((image.shape[0]*image.shape[1]*image.shape[2], 1))

    return v

def normalizeRows(x):
    """
    Implement a function that normalizes each row of the matrix x (to have unit length).

    Argument:
    x -- A numpy matrix of shape (n, m)

    Returns:
    x -- The normalized (by row) numpy matrix. You are allowed to modify x.
    """

    x_norm = np.linalg.norm(x, ord = 2, axis = 1, keepdims = True)

    # Divide x by its norm.
    x = x / x_norm

    return x

def softmax(x):
    """Calculates the softmax for each row of the input x.

    Your code should work for a row vector and also for matrices of shape (n, m).

    Argument:
    x -- A numpy matrix of shape (n,m)

    Returns:
    s -- A numpy matrix equal to the softmax of x, of shape (n,m)
    """

    # Apply exp() element-wise to x.
    x_exp = np.exp(x)

    # Create a vector x_sum that sums each row of x_exp.
    x_sum =  np.sum(x_exp, axis = 1, keepdims = True)

    # Compute softmax(x) by dividing x_exp by x_sum.
    s = x_exp / x_sum

    return s

def any():
    x1 = [9, 2, 5, 0, 0, 7, 5, 0, 0, 0, 9, 2, 5, 0, 0]
    x2 = [9, 2, 2, 9, 0, 9, 2, 5, 0, 0, 9, 2, 5, 0, 0]

    ### VECTORIZED DOT PRODUCT OF VECTORS ###
    dot = np.dot(x1,x2)

    ### VECTORIZED OUTER PRODUCT ###
    outer = np.outer(x1,x2)

    ### VECTORIZED ELEMENTWISE MULTIPLICATION ###
    mul = np.multiply(x1,x2)

    ### VECTORIZED GENERAL DOT PRODUCT ###
    W = np.random.rand(3,len(x1))
    dot = np.dot(W,x1)

def L1(yhat, y):
    """
    Arguments:
    yhat -- vector of size m (predicted labels)
    y -- vector of size m (true labels)

    Returns:
    loss -- the value of the L1 loss function defined above
    """

    loss =  np.sum(np.abs(yhat-y))

    return loss

def L2(yhat, y):
    """
    Arguments:
    yhat -- vector of size m (predicted labels)
    y -- vector of size m (true labels)

    Returns:
    loss -- the value of the L2 loss function defined above
    """

    loss = sum((y-yhat)**2)

    return loss

# GRADED FUNCTION: propagate

def propagate(w, b, X, Y):
    """
    Implement the cost function and its gradient for the propagation explained above

    Arguments:
    w -- weights, a numpy array of size (num_px * num_px * 3, 1)
    b -- bias, a scalar
    X -- data of size (num_px * num_px * 3, number of examples)
    Y -- true "label" vector (containing 0 if non-cat, 1 if cat) of size (1, number of examples)

    Return:
    cost -- negative log-likelihood cost for logistic regression
    dw -- gradient of the loss with respect to w, thus same shape as w
    db -- gradient of the loss with respect to b, thus same shape as b

    Tips:
    - Write your code step by step for the propagation. np.log(), np.dot()
    """

    m = X.shape[1]

    A = sigmoid(np.dot(w.T,X) + b)
    cost = -1 / m * np.sum(Y*np.log(A)+(1-Y)*np.log(1-A), axis = 1, keepdims = True)

    dw = 1 / m * np.dot(X,(A-Y).T)
    db = 1 / m * np.sum(A-Y)

    assert(dw.shape == w.shape)
    assert(db.dtype == float)
    cost = np.squeeze(cost)
    assert(cost.shape == ())

    grads = {"dw": dw,
             "db": db}

    return grads, cost

    # GRADED FUNCTION: optimize

def optimize(w, b, X, Y, num_iterations, learning_rate, print_cost = False):
    """
    This function optimizes w and b by running a gradient descent algorithm

    Arguments:
    w -- weights, a numpy array of size (num_px * num_px * 3, 1)
    b -- bias, a scalar
    X -- data of shape (num_px * num_px * 3, number of examples)
    Y -- true "label" vector (containing 0 if non-cat, 1 if cat), of shape (1, number of examples)
    num_iterations -- number of iterations of the optimization loop
    learning_rate -- learning rate of the gradient descent update rule
    print_cost -- True to print the loss every 100 steps

    Returns:
    params -- dictionary containing the weights w and bias b
    grads -- dictionary containing the gradients of the weights and bias with respect to the cost function
    costs -- list of all the costs computed during the optimization, this will be used to plot the learning curve.

    Tips:
    You basically need to write down two steps and iterate through them:
        1) Calculate the cost and the gradient for the current parameters. Use propagate().
        2) Update the parameters using gradient descent rule for w and b.
    """

    costs = []

    for i in range(num_iterations):

        grads, cost = propagate(w, b, X, Y)

        dw = grads["dw"]
        db = grads["db"]

        w = w - learning_rate * dw
        b = b - learning_rate * db

        if i % 100 == 0:
            costs.append(cost)

        if print_cost and i % 100 == 0:
            print ("Cost after iteration %i: %f" %(i, cost))

    params = {"w": w,
              "b": b}

    grads = {"dw": dw,
             "db": db}

    return params, grads, costs

    def predict(w, b, X):
    '''
    Predict whether the label is 0 or 1 using learned logistic regression parameters (w, b)

    Arguments:
    w -- weights, a numpy array of size (num_px * num_px * 3, 1)
    b -- bias, a scalar
    X -- data of size (num_px * num_px * 3, number of examples)

    Returns:
    Y_prediction -- a numpy array (vector) containing all predictions (0/1) for the examples in X
    '''

    m = X.shape[1]
    Y_prediction = np.zeros((1,m))
    w = w.reshape(X.shape[0], 1)

    # Compute vector "A" predicting the probabilities of a cat being present in the picture
    A = sigmoid(np.dot(w.T,X) + b)

    for i in range(A.shape[1]):

        Y_prediction[0,i] = np.where(A[0,i]>0.5,1,0)

    assert(Y_prediction.shape == (1, m))

    return Y_prediction


#!/usr/bin/env python
# -*- coding: utf-8 -*-
import numpy as np

class CoordinatesConverter:

    @staticmethod
    def cart2pol(x, y):
        rho = np.sqrt(x ** 2 + y ** 2)
        phi = np.arctan2(y, x)
        return rho, phi

    @staticmethod
    def pol2cart(rho, phi):
        x = rho * np.cos(phi)
        y = rho * np.sin(phi)
        return x, y


import numpy as np
from scipy import stats

# generate data
N = 10
a = np.random.randn(N) + 2 # mean 2, variance 1
b = np.random.randn(N) # mean 0, variance 1

# roll your own t-test:
var_a = a.var(ddof=1) # unbiased estimator, divide by N-1 instead of N
var_b = b.var(ddof=1)
s = np.sqrt( (var_a + var_b) / 2 ) # balanced standard deviation
t = (a.mean() - b.mean()) / (s * np.sqrt(2.0/N)) # t-statistic
df = 2*N - 2 # degrees of freedom
p = 1 - stats.t.cdf(np.abs(t), df=df) # one-sided test p-value
print("t:\t", t, "p:\t", 2*p) # two-sided test p-value

# built-in t-test:
t2, p2 = stats.ttest_ind(a, b)
print("t2:\t", t2, "p2:\t", p2)


import numpy as np

from scipy.linalg import hilbert,lu

n = 10

H = hilbert(10)

#------------------------Q1-----------------------------
L = np.array([[0. for i in range(n)] for i in range(n)])
for i in range(0,n):
	L[i][i] = 1.

U = np.array([[0. for i in range(0,n)] for i in range(n)])
for i in range(0,n):
	for j in range(0,n):
		U[i][j] = H[i][j]

for i in range(n):
    for j in range(i+1,n):
        e = np.divide(U[j][i],U[i][i])
        L[j][i] = e
        to = np.array(U[i])
        to = e * to
        for k in range(n):
            U[j][k] -= to[k]

print("L")
for i in range(n):
    for j in range(n):
        print("%.12f" % L[i][j], end=" ")
    print()
print("\nU")

for i in range(n):
    for j in range(n):
        if(i > j):
            U[i][j] = 0.
        print("%.12f" % U[i][j], end=" ")
    print()

#------------------------Q2-----------------------------
b = np.array([1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0])

y = np.array([0. for i in range(n)])
x = np.array([0. for i in range(n)])

for i in range(n):
    t = b[i]
    for j in range(i):
        t -= y[j] * L[i][j]
    y[i] = t / L[i][i]

for i in range(n-1,-1,-1):
    t = y[i]
    for j in range(n-1,i-1,-1):
        t -= x[j] * U[i][j]
    x[i] = t / U[i][i]

for i in range(n):
    print("x%2d = %.10f" % (i+1,x[i]))
#------------------------Q3------------------------------
#Determinant of Upper-Triangular is the product of the diagonal components.
#And, Determinant of Orignal Matrix and Matrix U is same.
#So, we can obtain determinant by using the Matrix U.

det = 1.

for i in range(n):
    det = np.multiply(det,U[i][i])

print("Det of H = ",det)

#------------------------Q4------------------------------
#[Hx1,Hx2, ... , Hxn] = [e1,e2, ... ,en]
inv = [[],[],[],[],[],[],[],[],[],[]]

for p in range(n):
    b_inv = np.array([0. for j in range(n)])
    y_inv = np.array([0. for j in range(n)])
    x_inv = np.array([0. for j in range(n)])
    b_inv[p] = 1.
    for i in range(n):
        t = b_inv[i]
        for j in range(i):
            t -= y_inv[j] * L[i][j]
        y_inv[i] = t / L[i][i]

    for i in range(n-1,-1,-1):
        t = y_inv[i]
        for j in range(n-1,i-1,-1):
            t -= x_inv[j] * U[i][j]
        x_inv[i] = t / U[i][i]

    for i in range(n):
        inv[i].append(x_inv[i])

inv = np.array(inv)

print("H^-1")

for i in range(n):
    for j in range(n):
        print("%18.6f" % inv[i][j], end=" ")
    print()


'''
Finding the best fit linear slope for a dataset example
'''

from statistics import mean
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import style

style.use('fivethirtyeight')

# test data
xs = np.array([1,2,3,4,5,6], dtype=np.float64)
ys = np.array([5,4,6,5,6,7], dtype=np.float64)

# generate best fit slope based on averages and square means
def best_fit_slope_and_intercept(xs, ys):
    m = ((mean(xs) * mean(ys)) - mean(xs * ys)) / ((mean(xs) ** 2) - mean(xs ** 2))
    b = mean(ys) - m*mean(xs)

    return m, b

# y = mx + c
m,b = best_fit_slope_and_intercept(xs, ys)

regression_line = [(m * x) + b for x in xs] # create list of y values

# predictions
predict_x = 8
predict_y = (m * predict_x) + b

# plot
plt.scatter(xs, ys)
plt.scatter(predict_x, predict_y, color='g')
plt.plot(xs, regression_line)
plt.show()


'''
Demonstration of how to execute some basic operations:
    - Root finding
    - Interpolation
    - Integration

Shows how the functions in the old "ClimateUtilities.py" can be replaced
with numpy and scipy routines.

Dependencies
------------
numpy, matplotlib, scipy

License
-------
BSD 3-clause (see https://www.w3.org/Consortium/Legal/2008/03-bsd-license.html)

'''

import string
import numpy as np
import matplotlib.pyplot as plt

#==============================================
#---Section: Math utilities-------------------
#==============================================

if __name__ == '__main__':

    # Usage Examples:
    # ===============

    # 1. Finding roots of a function (replaces the old "newtSolv")
    # ------------------------------------------------------------
    from scipy.optimize import newton

    def show_root(initial_guess, root):
        '''Show initial guess and resulting root value'''
        print('With initial guess {0}, we get the root {1}.'.format(
            initial_guess, root) )

    # Example 1.1: Function without parameters
    print('--- Example 1 ---')
    def g(x):
        return x*x - 1.

    initial_guess = 2.
    root = newton(g, initial_guess)
    show_root(initial_guess, root)

    # Example 1.2: Function with parameters
    print('--- Example 2 ---')
    def g(x, a, b):
        return a*x*x - b

    initial_guess = 2.
    a, b = 1, 2
    root = newton(g, initial_guess, args=(a, b))
    show_root(initial_guess, root)

    initial_guess = 1.
    root = newton(g, initial_guess, args=(a, b))
    show_root(initial_guess, root)

    # 2. Interpolation (replaces the old "interp" and "polint")
    # ---------------------------------------------------------
    from scipy.interpolate import interp1d

    # Generate some data
    x = np.linspace(0, 10, num=11, endpoint=True)
    y = np.cos(-x**2/9.0)
    xnew = np.linspace(0, 10, num=41, endpoint=True)

    # Make a "linear" and a "cubic" interpolation object
    f = interp1d(x, y)
    f2 = interp1d(x, y, kind='cubic')

    # Calculate and plot the interpolated data
    plt.plot(x, y, 'o', label='data')
    plt.plot(xnew, f(xnew), '-', label='linear')
    plt.plot(xnew, f2(xnew),'--', label='cubic')
    plt.legend()
    plt.show()

    # 3. Romberg Integration (replaces the old "romberg")
    # ---------------------------------------------------
    from scipy.integrate import romberg

    # Define a function
    def f(x):
        return x**2

    # Integrate the function, from -1 to 2:
    a, b = -1, 2
    integral = romberg(f, a, b)

    print('The integral of f(x) between {0} and {1} is: {2}'.format(a, b, integral))


#! /usr/bin/env python

"""
File: centered_diff.py
Copyright (c) 2016 Austin Ayers
License: MIT

Course: PHYS227
Assignment: 3.18
Date: Feb 11, 2016
Email: ayers111@mail.chapman.edu
Name: Austin Ayers
Description: Approxiates many derivatives
"""

import sympy as sp
import math

def quad(x):
    return (x**2)
def exponent(x):
    return math.exp(x)
def exponent2(x):
    return math.exp(-2*x**2)
def cosine(x):
    return sp.cos(x)
def ln(x):
    return sp.log(x)

def diff(f,x, h=1E-5):
    """
    Approximates a derivative of a function at a point
    """
    return ((f(x+h)-f(x-h))/(2*float(h)))
def test_diff():
    print(diff(quad, 5))
    assert(diff(quad, 5) - 10 < 0.00001)

def application():
    print("error of e^x @ (x=0): " + str(abs(diff(exponent, 0, .01) - 1)))
    print("error of e^(-2x^2) @ (x=0): " + str(abs(diff(exponent2, 0, .01))))
    print("error of cos(x) @ (x=2pi): " + str(abs(diff(cosine, 2*sp.pi, .01))))
    print("error of ln(x) @ (x=1): " + str(abs(diff(ln, 1, .01))))


# -*- coding: utf-8 -*-
import numpy as np
from playML.metrics import r2_score

class SimpleLinearRegression1(object):
    """
    自己手写的简陋实现简单线性回归算法
    """
    def __init__(self):
        self.a_ = None
        self.b_ = None

    def fit(self, x_train, y_train):
        """

        :param x_train:
        :param y_train:
        :return:
        """
        assert x_train.ndim == 1, 'Simple Linear Regressor can only solve single feature training data.'
        assert len(x_train) == len(y_train), 'the size of x_train must be equal to the size of y_train'

        x_mean = np.mean(x_train)
        y_mean = np.mean(y_train)

        # 分子
        numerator = 0.0

        # 分母
        denominator = 0.0

        for x_i, y_i in zip(x_train, y_train):
            numerator += (x_i - x_mean)*(y_i - y_mean)
            denominator += (x_i - x_mean)**2

        self.a_ = numerator / denominator
        self.b_ = y_mean - self.a_ * x_mean

        return self

    def predict(self, x_predict):
        assert x_predict.ndim == 1, 'Simple Linear Regressor can only solve single feature training data.'
        assert self.a_ is not None and self.b_ is not None, 'must fit before predict'
        return np.array([self._predict(x) for x in x_predict])

    def _predict(self, x_single):
        return self.a_ * x_single + self.b_

    def __repr__(self):
        return 'SimpleLinearRegression1()'

class SimpleLinearRegression2(object):
    """
    自己手写的简陋实现简单线性回归算法，把for循环改为向量化运算，提升效率
    """
    def __init__(self):
        self.a_ = None
        self.b_ = None

    def fit(self, x_train, y_train):
        """

        :param x_train:
        :param y_train:
        :return:
        """
        assert x_train.ndim == 1, 'Simple Linear Regressor can only solve single feature training data.'
        assert len(x_train) == len(y_train), 'the size of x_train must be equal to the size of y_train'

        x_mean = np.mean(x_train)
        y_mean = np.mean(y_train)

        # 分子
        numerator = (x_train - x_mean).dot(y_train - y_mean)

        # 分母
        denominator = (x_train - x_mean).dot(x_train - x_mean)

        self.a_ = numerator / denominator
        self.b_ = y_mean - self.a_ * x_mean

        return self

    def predict(self, x_predict):
        assert x_predict.ndim == 1, 'Simple Linear Regressor can only solve single feature training data.'
        assert self.a_ is not None and self.b_ is not None, 'must fit before predict'
        return np.array([self._predict(x) for x in x_predict])

    def _predict(self, x_single):
        return self.a_ * x_single + self.b_

    def score(self, x_test, y_test):
        """
        根据测试数据集x_test和y_test 确定当前模型的准确度
        :param x_test:
        :param y_test:
        :return:
        """
        y_predict = self.predict(x_test)
        return r2_score(y_test, y_predict)

    def __repr__(self):
        return 'SimpleLinearRegression2()'

class SimpleLinearRegression(SimpleLinearRegression2):
    pass


import numpy as np
from random import randint

class Ham_15_11:
    """
    The class is used for hamming code [15,11,3]
    """

    def __init__(self,sourceCode=None):
        self._H = np.matrix([[1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1],
                             [0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1],
                             [0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1],
                             [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1]])
        self._G = np.matrix([[1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                             [1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                             [0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                             [1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
                             [1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0],
                             [0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0],
                             [1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0],
                             [0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0],
                             [1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0],
                             [0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0],
                             [1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1]])
        self._sourceCode = sourceCode
        self._codeRes = None

    def updateSourceCode(self,sourceCode):
        if not isinstance(sourceCode, str):
            raise TypeError("sourceCode: expected string, but got %r" % type(sourceCode).__name__)
        assert len(sourceCode) == 11
        sourceCode = np.matrix([int(e) for e in list(sourceCode)])
        self._sourceCode = sourceCode

    def updateCodeRes(self, codeRes):
        if not isinstance(codeRes, str):
            raise TypeError("codeRes: expected string, but got %r" % type(codeRes).__name__)
        assert len(codeRes) == 15
        codeRes = np.matrix([int(e) for e in list(codeRes)])
        self._codeRes = codeRes

    def encode(self):
        codeRes = self._sourceCode * self._G % 2
        self._codeRes = codeRes
        codeRes = codeRes.tolist()[0]
        codeRes = ''.join([str(e) for e in codeRes])
        return codeRes

    def decode(self):
        z = self._H * self._codeRes.T % 2
        mul = np.matrix([1,2,4,8])
        error = (mul * z).sum()
        codeRes = self._codeRes.tolist()[0]
        if not error == 0:
            codeRes[error-1] = (codeRes[error-1]+1) % 2
        codeRes = codeRes[2:3]+codeRes[4:7] + codeRes[8:]
        return ''.join([str(e) for e in codeRes])

if __name__ == '__main__':
    ham = Ham_15_11()
    with open('hamming_15_11.txt','w') as f:
        for i in range(2**11):
            sourceCode = bin(i).replace('0b','').rjust(11,'0')
            sourceCode = sourceCode[::-1]
            ham.updateSourceCode(sourceCode)
            codeRes = ham.encode()
            line = ''.join([sourceCode,', ',codeRes,'\n'])
            if i == 2**11-1:
                line=line[:-1]
            f.write(line)
        f.close()

    #this part used to check decode
    with open('hamming_15_11.txt', 'r') as f:
        checkpoint = 0
        for line in f:
            codeRes = line.split(' ')[1].replace('\n','')
            ham.updateCodeRes(codeRes)
            expectedAns = line.split(',')[0]
            ans = ham.decode()
            if not ans == expectedAns:
                print('check failed in {} -> {},{} expected'.format(codeRes,ans,expectedAns))
                exit(-1)
            checkpoint += 1
        print('%d points checked, decode test pass!!!' % checkpoint)
        f.close()

    #this part used to simulate error and check decode
    with open('hamming_15_11.txt', 'r') as f:
        checkpoint = 0
        for line in f:
            codeRes = line.split(' ')[1].replace('\n','')
            codeRes = [int(e) for e in codeRes]
            errorP = randint(0, 14)
            codeRes[errorP] = (codeRes[errorP]+1) % 2
            codeRes = ''.join(str(e) for e in codeRes)
            ham.updateCodeRes(codeRes)
            expectedAns = line.split(',')[0]
            ans = ham.decode()
            if not ans == expectedAns:
                print('check failed in {} -> {},{} expected'.format(codeRes,ans,expectedAns))
                exit(-1)
            checkpoint += 1
        print('%d points checked, decode( with one error) test pass!!!' % checkpoint)


"""Ordinary least squares and related problems."""
from typing import Optional

import numpy as np

from optimus.types import Function

class LeastSquares(Function):
    """Ordinary least squares of the form min ||Ax - b||^2."""

    is_c2 = True

    def __init__(self, inputs: np.ndarray, targets: np.ndarray):
        """Initialize a least squares problem.

        `inputs` is `A` (i.e. the matrix of features), and targets is
        `b` (i.e. the vector of observed values)."""
        self.inputs = inputs
        self.targets = targets
        self._cached_hessian: Optional[np.ndarray] = None

    def __call__(self, parameters: np.ndarray) -> float:
        difference_vector = self.inputs.dot(parameters) - self.targets
        return np.dot(difference_vector, difference_vector)

    def gradient(self, parameters: np.ndarray) -> np.ndarray:
        return 2 * (self.inputs.dot(parameters) - self.targets).T.dot(self.inputs)

    def partial_second_derivative(
        self, parameters: np.ndarray, first_variable: int, second_variable: int
    ) -> float:
        # This is reasonable because the hessian is constant.
        return self.hessian(parameters)[first_variable, second_variable]

    def hessian(self, parameters: np.ndarray) -> np.ndarray:
        if self._cached_hessian is None:
            self._cached_hessian = self.inputs.T.dot(self.inputs)
        return self._cached_hessian


import numpy as np

def index(a, weights = None):
    """
    Compute the Gini Coefficient.
    Thanks Gaëtan de Menten: https://stackoverflow.com/a/49571213/5890574

    Parameters
    ----------
    a : array_like
        1-D array containing the values of the distribution.
    weights : array_like, optional
        1-D array of integer weights associated with the values in `a`. Each value in
        `a` contributes to the average according to its associated weight.
        If `weights=None`, then all data in `a` are assumed to have a
        weight equal to one.

    Returns
    -------
    gini : float
        Returns the Gini Coefficient of the distribution provided.
    """
    a = np.asarray(a)
    if weights is not None:
        weights = np.asarray(weights)
        sorted_indices = np.argsort(a)
        sorted_x = a[sorted_indices]
        sorted_w = weights[sorted_indices]
        # Force float dtype to avoid overflows
        cumw = np.cumsum(sorted_w, dtype=float)
        cumxw = np.cumsum(sorted_x * sorted_w, dtype=float)
        return (np.sum(cumxw[1:] * cumw[:-1] - cumxw[:-1] * cumw[1:]) /
                (cumxw[-1] * cumw[-1]))
    else:
        sorted_x = np.sort(a)
        n = len(a)
        cumx = np.cumsum(sorted_x, dtype=float)
        # The above formula, with all weights equal to 1 simplifies to:
        return (n + 1 - 2 * np.sum(cumx) / cumx[-1]) / n


# -*- coding: utf-8 -*-
"""Linear regression for machine learning

This file demonstrate knowledge of linear regression. By building the algorithm
from scratch.The idea of linear regression is to take continuous data and find
the best fit of it to a line.
"y=mx+b" is the equation of a line, and we need to figure out the
best slope (m) and y-intercept (b) to fit the line best.

This is for simple 2d regression.

Example:

        $ python howItWorksLinearRegression.py

Todo:
    *
"""
import random

from statistics import mean

from matplotlib import style
import matplotlib.pyplot as plt

import numpy as np

style.use('fivethirtyeight')

# simple_x = np.array([1, 2, 3, 4, 5, 6], dtype=np.float64)
# simple_y = np.array([5, 4, 6, 5, 6, 7], dtype=np.float64)

def create_dataset(howmany, variance, step=2, correlation=False):
    # for test-data
    val = 1
    ys = []
    for i in range(howmany):
        y = val + random.randrange(-variance, variance)
        ys.append(y)
        if correlation and correlation == 'pos':
            val += step
        elif correlation and correlation == 'neg':
            val -= step
    xs = [i for i in range(len(ys))]
    return np.array(xs, dtype=np.float64), np.array(ys, dtype=np.float64)

simple_x, simple_y = create_dataset(40, 10, 2, correlation='pos')

def best_fit_slope_and_intercept(xs, ys):
    # Best fit m(slope)
    m = (mean(xs) * mean(ys) - mean(xs * ys)) / (mean(xs)**2 - mean(xs**2))
    # Best fit y(intercept)
    b = mean(ys) - m * mean(xs)
    return m, b

def squared_error(ys_orig, ys_line):
    # Figuring out the squared_error that we need to calculate R^2
    return sum((ys_line - ys_orig)**2)

def coefficient_of_determination(ys_orig, ys_line):
    # Figuring out R^2 = 1 - (squared_error(y_hat) / squared_error(mean(y)))
    y_mean_line = [mean(ys_orig) for y in ys_orig]  # line of mean(y)s
    squared_error_regressionline = squared_error(ys_orig, ys_line)
    squared_error_y_mean = squared_error(ys_orig, y_mean_line)
    return 1 - (squared_error_regressionline / squared_error_y_mean)

m, b = best_fit_slope_and_intercept(simple_x, simple_y)
regression_line = [(m * x) + b for x in simple_x]  # "y=mx+b"

predict_x = 8
predict_y = (m * predict_x) + b
r_squared = coefficient_of_determination(simple_y, regression_line)

print(r_squared)

plt.scatter(simple_x, simple_y)
plt.plot(simple_x, regression_line, color='g')
plt.scatter(predict_x, predict_y, color='r')
plt.show()


# euclid norm
# https://numpy.org/doc/stable/reference/generated/numpy.linalg.norm.html
from numpy import linalg as la
import numpy as np

a = np.arange(9) - 4
# print(np.arange(9))
# print(a)
b = a.reshape((3, 3))
# print(b)
'''
print(la.norm(a))
print(la.norm(b))
print(la.norm(b, 'fro'))
print(la.norm(a, np.inf))
print(la.norm(b, np.inf))
print(la.norm(a, -np.inf))
print(la.norm(b, -np.inf))
print(la.norm(a, 1))
print(la.norm(b, 1))
# print(la.norm(a, -1))
print(la.norm(b, -1))
print(la.norm(a, 2))
print(la.norm(b, 2))
# print(la.norm(a, -2))
print(la.norm(b, -2))
print(la.norm(a, 3))
# print(la.norm(a, -3))
'''
c = np.array([[1, 2, 3], [-1, 1, 4]])
# # using axis compute vector norms
# print(la.norm(c, axis=0))
# print(la.norm(c, axis=1))
# print(la.norm(c, ord=1, axis=1))

# using axis compute matrix norms
m = np.arange(12).reshape(2, 3, -1)
# print(la.norm(m, axis=(1, 2)))
# print(m[0, :, :])
# print(m[1, :, :])
# print(la.norm(m[0, :, :]), la.norm(m[1, :, :]))

m = np.arange(3) - 1
# print(m)
# print(m/la.norm(m))


import numpy
# reference: https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm
def line_low(x0, y0, x1, y1):
    res = []
    dx = x1 - x0
    dy = y1 - y0
    yi = 1
    if dy < 0:
        yi = -1
        dy = -dy
    D = 2*dy - dx
    y = y0
    for x in range(x0, x1 + 1):
        res.append((x,y))
        if D > 0:
            y = y + yi
            D = D - 2*dx
        D = D + 2*dy
    return res

def line_high(x0, y0, x1, y1):
    res = []
    dx = x1 - x0
    dy = y1 - y0
    xi = 1
    if dx < 0:
        xi = -1
        dx = -dx
    D = 2*dx - dy
    x = x0

    for y in range(y0, y1 + 1):
        res.append((x,y))
        if D > 0:
            x = x + xi
            D = D - 2*dy
        D = D + 2*dx
    return res

def bresenham_line(A, B):
    x0 = int(A[0])
    y0 = int(A[1])
    x1 = int(B[0])
    y1 = int(B[1])

    if abs(y1 - y0) < abs(x1 - x0):
        if x0 > x1:
            return line_low(x1, y1, x0, y0)
        else:
            return line_low(x0, y0, x1, y1)
    else:
        if y0 > y1:
            return line_high(x1, y1, x0, y0)
        else:
            return line_high(x0, y0, x1, y1)

# reference: https://en.wikipedia.org/wiki/Midpoint_circle_algorithm
def bresenham_circle(C):
    x0 = int(C[0])
    y0 = int(C[1])
    radius = int(C[2])
    res = []
    x = int(radius-1)
    y = int(0)
    dx = int(1)
    dy = int(1)
    err = int(dx - (radius << 1))

    while (x >= y):
        res.append((x0 + x, y0 + y))
        res.append((x0 + y, y0 + x))
        res.append((x0 - y, y0 + x))
        res.append((x0 - x, y0 + y))
        res.append((x0 - x, y0 - y))
        res.append((x0 - y, y0 - x))
        res.append((x0 + y, y0 - x))
        res.append((x0 + x, y0 - y))

        if (err <= 0):
            y += 1
            err += dy
            dy += 2

        if (err > 0):
            x -= 1
            dx += 2
            err += dx - (radius << 1)

    return res


import numpy as np

from math import copysign, hypot

def gram_schmidt_process(A):
    """
    Perform QR decomposition of matrix A using Gram-Schmidt process.
    """
    (num_rows, num_cols) = np.shape(A)

    # Initialize empty orthogonal matrix Q.
    Q = np.empty([num_rows, num_rows])
    cnt = 0

    # Compute orthogonal matrix Q.
    for a in A.T:
        u = np.copy(a)
        for i in range(0, cnt):
            proj = np.dot(np.dot(Q[:, i].T, a), Q[:, i])
            u -= proj

        e = u / np.linalg.norm(u)
        Q[:, cnt] = e

        cnt += 1  # Increase columns counter.

    # Compute upper triangular matrix R.
    R = np.dot(Q.T, A)

    return (Q, R)

def householder_reflection(A):
    """
    Perform QR decomposition of matrix A using Householder reflection.
    """
    (num_rows, num_cols) = np.shape(A)

    # Initialize orthogonal matrix Q and upper triangular matrix R.
    Q = np.identity(num_rows)
    R = np.copy(A)

    # Iterative over column sub-vector and
    # compute Householder matrix to zero-out lower triangular matrix entries.
    for cnt in range(num_rows - 1):
        x = R[cnt:, cnt]

        e = np.zeros_like(x)
        e[0] = copysign(np.linalg.norm(x), -A[cnt, cnt])
        u = x + e
        v = u / np.linalg.norm(u)

        Q_cnt = np.identity(num_rows)
        Q_cnt[cnt:, cnt:] -= 2.0 * np.outer(v, v)

        R = np.dot(Q_cnt, R)
        Q = np.dot(Q, Q_cnt.T)

    return Q, R

def givens_rotation(A):
    """
    Perform QR decomposition of matrix A using Givens rotation.
    """
    (num_rows, num_cols) = np.shape(A)

    # Initialize orthogonal matrix Q and upper triangular matrix R.
    Q = np.identity(num_rows)
    R = np.copy(A)

    # Iterate over lower triangular matrix.
    (rows, cols) = np.tril_indices(num_rows, -1, num_cols)
    for (row, col) in zip(rows, cols):

        # Compute Givens rotation matrix and
        # zero-out lower triangular matrix entries.
        if R[row, col] != 0:
            (c, s) = _givens_rotation_matrix_entries(R[col, col], R[row, col])

            G = np.identity(num_rows)
            G[[col, row], [col, row]] = c
            G[row, col] = s
            G[col, row] = -s

            R = np.dot(G, R)
            Q = np.dot(Q, G.T)

    return Q, R

def _givens_rotation_matrix_entries(a, b):
    """
    Compute matrix entries for Givens rotation.
    """
    r = hypot(a, b)
    c = a/r
    s = -b/r

    return c, s


# import modules
import numpy as np

'''
# Description.
With the extreme points of the defined slope, this function obtains the
lower possible boundary in where a slip circle can be, i.e. obtains the
maximum depth computed from the slope toe.

# Input(s).
Height of the slope (slopeHeight).

Slope dip, can be given by an angle in gradians, angle in radians, a
horizontal distance value relative to a unitary vertical distance (i.e.
horz:1), or a bidimensional vector which represents a horizontal distance and
a vertical distance that not necessary representing the real slope distances
[horzDist, vertDist], (slopeDip).

Crown horizontal plane distance (crownDist).

Toe horizontal plane distance (toeDist).

# Output(s):
Vertical distance from the slide--toe to downwards (toeDepth).

# Example1: By giving next values:
slopeHeight = 12; slopeDip = np.array([2.5, 1]); crownDist = 10.0;\
toeDist = 10.0; it is obtained a toeDepth = 14.44.

---
toeDepth = obtainmaxdepthdist(slopeHeight, slopeDip, crownDist, toeDist)
'''
def obtainmaxdepthdist(slopeHeight, slopeDip, crownDist, toeDist):

    #Calculation assuming coordinates origin at the slip--toe
    extremeToePointVec = np.array([toeDist, 0])

    #slope vertical projection (horizontal distance)
    slopeDist = slopeHeight*slopeDip[0]/slopeDip[1]

    extremeCrownPointVec = np.array([-(slopeDist +crownDist), slopeHeight])

    #distance between the two extreme points
    differenceVec = extremeToePointVec-extremeCrownPointVec
    distExtrPts = np.sqrt(np.dot(differenceVec, differenceVec))
    maximumCircleRadius = distExtrPts/2*distExtrPts/differenceVec[0]

    #the toe depth is the difference between the maximum--circle radius and the
    #slope height
    toeDepth = maximumCircleRadius-slopeHeight

    return toeDepth
'''
BSD 2 license.

Copyright (c) 2016, Universidad Nacional de Colombia, Ludger O.
   Suarez-Burgoa and Exneyder Andrés Montoya Araque.
All rights reserved.

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

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

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

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


import numpy as np

def sigmoid_forward(x):
    """
    Sigmoid function, sigmoid(x) = 1/(1+exp(-x))
    :param x: input
    :return: output and cache
    """
    return 1 / (1 + np.exp(-x)), x

def sigmoid_backward(dout, cache):
    """
    backward of sigmoid function, dx = x(x-1)
    :param dout: grad of outputs
    :param cache: cache stored before
    :return: dx
    """
    x = cache
    return x * (x - 1) * dout

def tanh_forward(x):
    """
    Tanh function, tanh(x) = (exp(x) - exp(-x))/ (exp(x) + exp(-x))
    :param x: input
    :return: output
    """
    y = (np.exp(x) - np.exp(-x)) / (np.exp(x) + np.exp(-x))
    return y, y

def tanh_backward(dout, cache):
    """
    backward of tanh, dx = 1 - (tanh(x))^2
    :param dout:
    :param cache:
    :return:
    """
    tanh_x = cache
    return (1 - tanh_x ** 2) * dout

def softmax_forward(x):
    """
    Softmax function, softmax(x) = exp(x[i]) / sum(exp(x[i]))
    :param x: input
    :return: output
    """
    temp = np.sum(np.exp(x))
    return np.exp(x) / temp, x

def softmax_backward(dout, cache):
    """
    backward of softmax, if i=j dx=x[j](1-x[j]) if i!=j dx=-x[i]x[j]
    x.shape=(N, 1)
    :param dout: grad of outputs
    :return: grads of input
    """
    x = cache
    dx_1 = x * (1 - x)
    # sum_x_2 = np.sum(np.dot(x.T, x), axis=0, keepdims=True)
    # sum_x_2 -= x ** 2
    dx = dx_1 * dout

    return dx

def relu_forward(x):
    """
    ReLu function, relu(x) = max(0, x)
    :param x: input
    :return: output and cache
    """
    return np.maximum(0, x), x

def relu_backward(dout, cache):
    x = cache
    dx = dout
    dx[x <= 0] = 0
    return dx

def leaky_relu_forward(x, alpha=0.01):
    """
    Leaky ReLu function, leaky_relu(x)=max(alpha * x, x)
    :param x: input
    :param alpha: hyperparameter of leaky relu
    :return: output
    """
    return np.maximum(alpha * x, x)

if __name__ == '__main__':
    # x = np.random.randn(1, 10)
    # y, cache = relu_forward(x)
    # dy = np.random.randn(1, 10)
    # dx = relu_backward(dy, cache)
    # print(dx)
    x = np.ones((1, 2))
    y = np.array([1,2]).reshape(1,2)
    print(x[y<2])


import numpy as np

g = list(range(10))

# sum of array
g_array = np.sum(g)
print(g_array)

# Constructor of array
a = np.array([1,2,3,4])
# a.shape = (4,)
b = np.array([10,11,12,13])
# a + b => array([11,13,15,17])
"""
In normal python: a + b -> [1,2,3,4,10,11,12,13]
"""

a.fill(1)
# a = [1,1,1,1]

##########################################################
# Control the type of array
b= np.array([1,2,3,4.0], dtype = int32)

c = np.array([[10,11,12],[20,21,22]])
"""
c.dtype dtype('int64')
c.ndim = 2
c.shape = (2,3)
"""
# Transport matrix
c.T = [[10,20],
        [11,21],
        [12,22]]
"""
c.size = 6
c.nbytes = 48
c[0,0] = 10  -> don't like in normal python c[0][0]
"""

##########################################################
# Slicing:  var[lower:upper:step]
a = np.array([10,11,12,13,14])
# a[::2] -> array([10,12,14])

a = [[0,1,2,3,4,5],
    [10,11,12,13,14,15],
    [20,21,22,23,24,25],
    [30,31,32,33,34,35],
    [40,41,42,43,44,45],
    [50,51,52,53,54,55]]

"""
a[0, 3:5] -> array([]3,4)
a[4:, 4:] -> array([44,45],[54,55])
a[:, 2]   -> array([2,12,22,32,42,52])
a[2::2, ::2]    -> array([20,22,24],[40,42,44])
"""

##########################################################
a = np.arange(25).reshape(5,5)
"""
array([[ 0,  1,  2,  3,  4],
    [ 5,  6,  7,  8,  9],
    [10, 11, 12, 13, 14],
    [15, 16, 17, 18, 19],
    [20, 21, 22, 23, 24]])
"""
red = a[:, ::2]
yellow = a[4]
blue = [1::2, 0:3:2]

##########################################################
a = np.array([3,-1,-2,4,-6,8])
"""
a < 0
-> array([False,  True,  True, False,  True, False])
a[a<0]  -> array([-1,-2,-6])
(a < 8).any()   -> true
(a , 8).all()   -> false

&(and) , |(or) , ~(not) , ^(xor)  -> bitwise operators
and or not -> binary operators

(a > 3) & (a < 8)   -> array([False,False,False,True,False,False])

a.nonzero(arr) -> get the array which is built with the numbers which are not equal 0
"""


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

def gmres(A,b, k=100, tol=1e-8):
    """
    Calculate approximate solution of Ax = b using GMRES algorithm.
    Inputs:
        A -- callable function that calculates Ax for any input vector x.
        b -- numpy array of length m
        k -- Maximum number of iterations of the GMRES algorithm. Defaults to 100.
        tol -- Stop iterating if the residual is less than `tol'. Defaults to 1e-8.
    Returns:
        Return (y, res) where 'y' is an approximate solution to Ax=b and 'res'
    is the residual.
    """
    # initialization steps
    m = b.size
    Q = np.empty((m,k))
    H = np.zeros((k+1,k))
    bnorm = la.norm(b,2)
    rhs = np.zeros(k+1)
    rhs[0] = bnorm
    Q[:,0] = b/bnorm

    for j in xrange(k-1):
        # Arnoldi iteration
        q = A(Q[:,j])
        for i in xrange(j+1):
            H[i,j] = np.inner(Q[:,i],q)
            q -= H[i,j]*Q[:,i]
        H[j+1,j] = la.norm(q,2)
        if H[j+1,j] > 1e-10:
            # don't divide by zero!
            q /= H[j+1,j]
        Q[:,j+1] = q

        # solve the least squares problem
        y, r = la.lstsq(H[:j+2,:j+1], rhs[:j+2])[:2]

        # compute the residual.
        r = math.sqrt(r)/bnorm
        if r < tol:
            # if we are sufficiently close to solution, return
            return Q[:,:j+1].dot(y), r
    return Q[:,:j+1].dot(y.flatten()), r


import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import Normalize

def plot_column_vectors_2d(matrix):
    """
    Plots column vectors from the supplied matrix in the 2D plane.  The matrix must have shape (2,X), where X >= 1.
    :param matrix: a (2,X) matrix; x >= 1
    :return: None.  Displays 2D plot.
    """
    if matrix.shape[0] != 2:
        raise ValueError("Matrix must have 2d column space")

    origin = np.zeros_like(matrix)

    fig, ax = plt.subplots()

    xmin, xmax = min(np.min(matrix[0]), 0), max(np.max(matrix[0]), 0)
    ymin, ymax = min(np.min(matrix[1]), 0), max(np.max(matrix[1]), 0)

    ax.axis(list(map(int, [xmin - 1, xmax + 1, ymin - 1, ymax + 1])))
    ax.grid(True)

    colors = np.linalg.norm(matrix, axis=0)
    colormap = plt.get_cmap('jet')

    norm = Normalize()
    norm.autoscale(colors)

    ax.quiver(origin[0], origin[1], matrix[0], matrix[1], scale=1, color=colormap(norm(colors)), angles='xy',
              scale_units='xy')

def plot_column_vectors_with_transform_2d(matrix, transform):
    """
    Displays a side-by-side plot of the column vectors in the supplied matrix and the column vectors in the transformed matrix matrix * transform.

    The matrix must have shape (2,X), where X >= 1.
    The transform must have shape (2,2)

    :param matrix: a (2,X) matrix; x >= 1
    :param transform: a (2,2) transformation matrix.
    :return: None. Displays 2 2D subplots.
    """
    if matrix.shape[0] != 2:
        raise ValueError("Matrix must have 2d column space")

    if transform.shape != (2, 2):
        raise ValueError("Transform matrix must have shape (2,2)")

    origin = np.zeros_like(matrix)

    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 6))

    colors = np.linalg.norm(matrix, axis=0)
    colormap = plt.get_cmap('jet')

    norm = Normalize()
    norm.autoscale(colors)

    trans_matrix = np.dot(transform, matrix)

    xmin, xmax = min(np.min(matrix[0]), 0), max(np.max(matrix[0]), 0)
    ymin, ymax = min(np.min(matrix[1]), 0), max(np.max(matrix[1]), 0)

    ax1.axis(list(map(int, [xmin - 1, xmax + 1, ymin - 1, ymax + 1])))
    ax1.set_title("$A$")
    ax1.grid(True)
    ax1.quiver(origin[0], origin[1], matrix[0], matrix[1], color=colormap(norm(colors)), scale=1, angles='xy',
               scale_units='xy')

    xmin, xmax = min(np.min(trans_matrix[0]), 0), max(np.max(trans_matrix[0]), 0)
    ymin, ymax = min(np.min(trans_matrix[1]), 0), max(np.max(trans_matrix[1]), 0)

    ax2.axis(list(map(int, [xmin - 1, xmax + 1, ymin - 1, ymax + 1])))
    ax2.set_title("$TA$")
    ax2.grid(True)
    ax2.quiver(origin[0], origin[1], trans_matrix[0], trans_matrix[1], color=colormap(norm(colors)), scale=1,
               angles='xy', scale_units='xy')


#These are the python libraries that are going to help us to create a plot
import matplotlib.pyplot as plt
import numpy as np

#This is the function that will be intergrated
def f(x):
    #x**4-2*x+1
    return x**4-2*x+1

#This 'Trapizoid_Rule' function will require a function,
#'b' value so the lower limit of a integral
#'a' value so the upper limit of a integral
# The amount of NUMERICAL PLOTS.

def Trapizoid_Rule(f, xmin, xmax, N):
    #the area has been initially set to 0
    area = 0
    #h is the step size
    h = (xmax-xmin)/N

    #the 'arange' function return evenly spaced numbers over a specified interval
    #It takes in the a & b limit parameters and Amount of NUMERICAL PLOTS
    npoints = np.arange(xmin, xmax, h)
    del_dis = np.delete(npoints, 0)
    area = h*((f(xmin)/2)+(f(xmax)/2)+sum(f(xmin+del_dis)))

    return area

#-----ENTER THE FOLLOWING----
#Function f
#upper limit (a)
#lower limit (b)
#Amount of NUMERICAL PLOTS

def integrate(f,a,b,N):

    integral = Trapizoid_Rule(f,a,b,N)
    integral2 = Trapizoid_Rule(f,a,b,N/2)

    x = np.linspace(a, b, int(N))
    #size of graph
    plt.figure(figsize=(14,7))
    #graph plotting function
    plt.plot(x,f(x),'--o',label=r'$f(x)$')

    #this function fills in the area between the a and b limits
    plt.fill_between(x,f(x), where =[(x>=a)and(x<=b) for x in x],color= 'gray', alpha = '0.7')

    #this function produces lines from the NUMERICAL PLOTS to the x-axis
    plt.vlines(x, 0, f(x), color='purple', linestyle=':')

    ###aixs lines##############
    plt.axhline(color='black')
    plt.axvline(color = 'black')
    ############################

    ###These are the axis labels######
    plt.ylabel("f(x)",fontsize=16)
    plt.xlabel("x",fontsize = 16)

    #this is the little box that displayed the colour of the function
    plt.legend(loc='upper right')

    plt.grid(color = 'gray', linewidth=1)
    plt.show()

    error = abs(integral-integral2)/3
    return integral,error

#This is the integrate function, the following parameters can be changed
#first--function--
#second--start time
#third--end time
#fourth--Numerical Plots
ans,err_est = integrate(f,0,2,1000)
#N=10, Error=0.10613333333333348
#N=100, Error=0.001066613333333244
#N=1000, Error=1.0666661333758043e-05

print('EXACT INTERGRAL=',ans,'||','ESTIMATED ERROR =',err_est)


# Question 2 Lab 03
# AB Satyaprakash (180123062)

# imports ----------------------------------------------------------------------
import numpy as np
# ------------------------------------------------------------------------------
# functions --------------------------------------------------------------------

def forwardDiff(fArray):
    sz = len(fArray)
    fdArray = [fArray]
    for i in range(1, sz):
        temp = []
        for j in range(sz-i):
            temp.append(fdArray[i-1][j+1]-fdArray[i-1][j])
        fdArray.append(temp)
    return fdArray

def newtonBDPoly(fArray, xArray):
    sz = len(fArray)
    xn, xn_1 = xArray[sz-1], xArray[sz-2]
    h = xn-xn_1
    # print(h)
    v = np.array([1/h, -xn/h])
    # print(v)
    fdArray = forwardDiff(fArray)
    # print(fdArray)
    px = np.array([0])

    for i in range(sz):
        term = np.array([1])
        for j in range(i):
            term = np.polymul(term, np.polyadd(v, np.array([j])))
            term = term/(j+1)
        term = term*fdArray[i][sz-i-1]
        px = np.polyadd(px, term)
    return px

# ------------------------------------------------------------------------------
# (i) f(−1/3) if f(−0.75) = −0.07181250, f(−0.5) = −0.02475000, f(−0.25) = 0.33493750, f(0) = 1.10100000
print('Part (i)-----------------------------------------------------------------\n')
X = [-0.75, -0.5, -0.25, 0]
F = [-0.07181250, -0.02475000, 0.33493750, 1.10100000]
val = -1/3  # since we need to approximate f(1/3)
# (A) Degree 1:

xArray = [X[1], X[2]]
fArray = [F[1], F[2]]
px = newtonBDPoly(fArray, xArray)
print("Newton's interpolating polynomial of degree 1 using nodes: {}, {} is {}".format(
    xArray[0], xArray[1], np.poly1d(px)))
print("Approximated value of f({}) using the above is {}".format(val, np.polyval(px, val)))

print('\n')

# (B) Degree 2:

xArray = [X[0], X[1], X[2]]
fArray = [F[0], F[1], F[2]]
px = newtonBDPoly(fArray, xArray)
print("Newton's interpolating polynomial of degree 2 using nodes: {}, {}, {} is \n{}".format(
    xArray[0], xArray[1], xArray[2], np.poly1d(px)))
print("Approximated value of f({}) using the above is {}".format(val, np.polyval(px, val)))

print('\n')

# (C) Degree 3:
xArray = [X[0], X[1], X[2], X[3]]
fArray = [F[0], F[1], F[2], F[3]]
px = newtonBDPoly(fArray, xArray)
print("Newton's interpolating polynomial of degree 3 using nodes: {}, {}, {}, {} is \n{}".format(
    xArray[0], xArray[1], xArray[2], xArray[3], np.poly1d(px)))
print("Approximated value of f({}) using the above is {}".format(val, np.polyval(px, val)))

# (ii) f(0.25) if f(0.1) = −0.62049958 , f(0.2) = −0.28398668 , f(0.3) = 0.00660095, f(0.4) = 0.24842440
print('\nPart (ii)--------------------------------------------------------------\n')
X = [0.1, 0.2, 0.3, 0.4]
F = [-0.62049958, -0.28398668, 0.00660095, 0.24842440]
val = 0.25  # since we need to approximate f(0.25)
# (A) Degree 1:

xArray = [X[1], X[2]]
fArray = [F[1], F[2]]
px = newtonBDPoly(fArray, xArray)
print("Newton's interpolating polynomial of degree 1 using nodes: {}, {} is {}".format(
    xArray[0], xArray[1], np.poly1d(px)))
print("Approximated value of f({}) using the above is {}".format(val, np.polyval(px, val)))

print('\n')

# (B) Degree 2:
xArray = [X[0], X[1], X[2]]
fArray = [F[0], F[1], F[2]]
px = newtonBDPoly(fArray, xArray)
print("Newton's interpolating polynomial of degree 2 using nodes: {}, {}, {} is \n{}".format(
    xArray[0], xArray[1], xArray[2], np.poly1d(px)))
print("Approximated value of f({}) using the above is {}".format(val, np.polyval(px, val)))

print('\n')

# (C) Degree 3:
xArray = [X[0], X[1], X[2], X[3]]
fArray = [F[0], F[1], F[2], F[3]]
px = newtonBDPoly(fArray, xArray)
print("Newton's interpolating polynomial of degree 3 using nodes: {}, {}, {}, {} is \n{}".format(
    xArray[0], xArray[1], xArray[2], xArray[3], np.poly1d(px)))
print("Approximated value of f({}) using the above is {}".format(val, np.polyval(px, val)))

# Question 2 ends --------------------------------------------------------------

Semester 6/MA 322 (Scientific Computing Theory)/Labs/Lab 3/Code/q2.py


# Author: Leland McInnes <leland.mcinnes@gmail.com>
#
# License: BSD 3 clause

# still to do here:
# add dot distance
# change cosine to angular in the right fashion, since angular is the preferred input of annoy

import numpy as np
import numba

@numba.njit(fastmath=True)
def euclidean(x, y):
    """Standard euclidean distance.

    ..math::
        D(x, y) = \sqrt{\sum_i (x_i - y_i)^2}
    """
    result = 0.0
    for i in range(x.shape[0]):
        result += (x[i] - y[i]) ** 2
    return np.sqrt(result)

@numba.njit(fastmath=True)
def manhattan(x, y):
    """Manhattan, taxicab, or l1 distance.

    ..math::
        D(x, y) = \sum_i |x_i - y_i|
    """
    result = 0.0
    for i in range(x.shape[0]):
        result += np.abs(x[i] - y[i])

    return result

@numba.njit(fastmath=True)
def hamming(x, y):
    result = 0.0
    for i in range(x.shape[0]):
        if x[i] != y[i]:
            result += 1.0

    return float(result) / x.shape[0]

@numba.njit(fastmath=True)
def cosine(x, y):
    result = 0.0
    norm_x = 0.0
    norm_y = 0.0
    for i in range(x.shape[0]):
        result += x[i] * y[i]
        norm_x += x[i] ** 2
        norm_y += y[i] ** 2

    if norm_x == 0.0 or norm_y == 0.0:
        return 1.0
    else:
        return 1.0 - (result / np.sqrt(norm_x * norm_y))

# is correlation dot product / inner distance ?
# if not, add dot product / innder distance :)
@numba.njit(fastmath=True)
def correlation(x, y):
    mu_x = 0.0
    mu_y = 0.0
    norm_x = 0.0
    norm_y = 0.0
    dot_product = 0.0

    for i in range(x.shape[0]):
        mu_x += x[i]
        mu_y += y[i]

    mu_x /= x.shape[0]
    mu_y /= x.shape[0]

    for i in range(x.shape[0]):
        shifted_x = x[i] - mu_x
        shifted_y = y[i] - mu_y
        norm_x += shifted_x ** 2
        norm_y += shifted_y ** 2
        dot_product += shifted_x * shifted_y

    if dot_product == 0.0:
        return 1.0
    else:
        return 1.0 - (dot_product / np.sqrt(norm_x * norm_y))

named_distances = {
    # general minkowski distances
    "euclidean": euclidean,
    "l2": euclidean,
    "manhattan": manhattan,
    "taxicab": manhattan,
    "l1": manhattan,
    # Standardised/weighted distances
    # Other distances
    "cosine": cosine, # input to annoy = angular
    # Binary distances
    "hamming": hamming,
    # "dot": dot,

}


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

import numpy as np
import matplotlib.pyplot as pl

#%% Q1 setup
def binomial(n, p, N):
    return np.math.factorial(N)/(np.math.factorial(n)*np.math.factorial(N-n))*(p**n)*((1-p)**(N-n))

def uniform(n, p, N):
    return 1/(N+1)

def KL(p, N, distp, distq):
    kl = 0
    for n in range(N):
        kl -= distp(n, p, N)*np.log2(distq(n, p, N)/distp(n, p, N))
    return kl

def align_yaxis(ax1, v1, ax2, v2):
    """adjust ax2 ylimit so that v2 in ax2 is aligned to v1 in ax1"""
    _, y1 = ax1.transData.transform((0, v1))
    _, y2 = ax2.transData.transform((0, v2))
    inv = ax2.transData.inverted()
    _, dy = inv.transform((0, 0)) - inv.transform((0, y1-y2))
    miny, maxy = ax2.get_ylim()
    ax2.set_ylim(miny+dy, maxy+dy)

#%% Q1(c)
KL_N10 = []
KL_N100 = []

for p in np.linspace(0, 1, 1000):
    KL_N10.append(KL(p, 10, uniform, binomial))
    KL_N100.append(KL(p, 100, uniform, binomial))

fig, ax = pl.subplots(1,1)
ax.plot(np.linspace(0, 1, 1000), KL_N10, 'b-', label="N=10")
ax2 = ax.twinx()
ax2.plot(np.linspace(0, 1, 1000), KL_N100, 'r-', label="N=100")
ax.set_xlabel("p")
ax.set_ylabel("KL divergence (N=10)")
ax2.set_ylabel("KL divergence (N=100)")
align_yaxis(ax, 0, ax2, 0)
ax.legend()
ax2.legend()

#%% Q1(d)
def poisson(n, p, N):
    lam = p*N
    return (lam**n)*(np.exp(-lam))/np.math.factorial(n)

KL_lam_N10 = []
KL_lam_N100 = []

for p in np.linspace(0, 1, 1000):
    KL_lam_N10.append(KL(p, 10, poisson, binomial))
    KL_lam_N100.append(KL(p, 100, poisson, binomial))

fig1, ax1 = pl.subplots(1,1)
ax1.plot(np.linspace(0, 1, 1000), KL_lam_N10, 'b-', label="N=10")
ax3 = ax1.twinx()
ax3.plot(np.linspace(0, 1, 1000), KL_lam_N100, 'r-', label="N=100")
ax1.set_xlabel("p")
ax1.set_ylabel("KL divergence (N=10)")
ax3.set_ylabel("KL divergence (N=10000)")
align_yaxis(ax1, 0, ax3, 0)
ax1.legend(loc=0)
ax3.legend(loc=6)

#%% Q5(a)
import math
def eff(N):
    return np.log2(N)/math.ceil(np.log2(N))

N_arr = []

for n in range(2,101):
    N_arr.append(eff(n))

fig2, ax4 = pl.subplots(1,1)
ax4.plot(np.arange(2, 101), N_arr, 'b-')
ax4.set_xlabel(r"$N$")
ax4.set_ylabel(r"Efficiency")

#%%
N_arr = np.array(N_arr)
print(np.arange(2,101)[np.argmin(N_arr)]) # N = 5 gives smallest efficiency
print(np.min(N_arr))
print(N_arr[-1])


import numpy as np

a = np.array([20,30,40,50])     # [20 30 40 50]
b = np.arange(4)        # [0 1 2 3]
print(a)
print(b)

c = a-b
print(c)

""" [20 30 40 50]
    [0 1 2 3]
    [20 29 38 47]
    """

d = b**2    # [0 1 4 9]
print(d)

e = 10 * np.sin(a)      # [ 9.12945251 -9.88031624  7.4511316  -2.62374854]
print(e)

print(e < 7)        # [False  True False  True]
print(a*b)     # [  0  30  80 150]

print(a@b)     # matris çarpımı sonucu : 260
print(a.dot(b)) # matris çarpımı sonucu : 260

f = np.ones((2,4))

""" [[1. 1. 1. 1.]
 [1. 1. 1. 1.]] """

g = np.zeros((2,4))

""" [[0. 0. 0. 0.]
 [0. 0. 0. 0.]] """

h = np.random.random((2,4))

""" [[0.24448762 0.30569748 0.38721877 0.42359117]
 [0.74705078 0.64296611 0.87855028 0.44236936]] """

i = np.sum(b)     # 6

j  = np.min(c)    # 20

k = np.max(h)     # 0.8785502826111841

l =np.sqrt(b)     # [0.         1.         1.41421356 1.73205081]

print(f)
print('----------------------')
print(g)
print('----------------------')
print(h)
print('----------------------')
print(i)
print('----------------------')
print(j)
print('----------------------')
print(k)
print('----------------------')
print(l)


"""
Simulating a black body curve

"""

from scipy.optimize import curve_fit
import pylab as plt
import numpy as np

def blackbody_lam(lam, T):
    """ Blackbody as a function of wavelength (um) and temperature (K).

    returns units of erg/s/cm^2/cm/Steradian
    """
    from scipy.constants import h,k,c
    lam = 1e-6 * lam # convert to metres
    return 2*h*c**2 / (lam**5 * (np.exp(h*c / (lam*k*T)) - 1))

wa = np.linspace(0.1, 2, 100)   # wavelengths in um
T1 = 5000.
T2 = 8000.
y1 = blackbody_lam(wa, T1)
y2 = blackbody_lam(wa, T2)
ytot = y1 + y2

np.random.seed(1)

# make synthetic data with Gaussian errors

sigma = np.ones(len(wa)) * 1 * np.median(ytot)
ydata = ytot + np.random.randn(len(wa)) * sigma

# plot the input model and synthetic data

plt.figure()
plt.plot(wa, y1, ':', lw=2, label='T1=%.0f' % T1)
plt.plot(wa, y2, ':', lw=2, label='T2=%.0f' % T2)
plt.plot(wa, ytot, ':', lw=2, label='T1 + T2\n(true model)')
plt.plot(wa, ydata, ls='steps-mid', lw=2, label='Fake data')
plt.xlabel('Wavelength (microns)')
plt.ylabel('Intensity (erg/s/cm$^2$/cm/Steradian)')

# fit two blackbodies to the synthetic data

def func(wa, T1, T2):
    return blackbody_lam(wa, T1) + blackbody_lam(wa, T2)

# Note the initial guess values for T1 and T2 (p0 keyword below). They
# are quite different to the known true values, but not *too*
# different. If these are too far away from the solution curve_fit()
# will not be able to find a solution. This is not a Python-specific
# problem, it is true for almost every fitting algorithm for
# non-linear models. The initial guess is important!

popt, pcov = curve_fit(func, wa, ydata, p0=(1000, 3000), sigma=sigma)

# get the best fitting parameter values and their 1 sigma errors
# (assuming the parameters aren't strongly correlated).

bestT1, bestT2 = popt
sigmaT1, sigmaT2 = np.sqrt(np.diag(pcov))

ybest = blackbody_lam(wa, bestT1) + blackbody_lam(wa, bestT2)

print 'True model values'
print '  T1 = %.2f' % T1
print '  T2 = %.2f' % T2

print 'Parameters of best-fitting model:'
print '  T1 = %.2f +/- %.2f' % (bestT1, sigmaT1)
print '  T2 = %.2f +/- %.2f' % (bestT2, sigmaT2)

degrees_of_freedom = len(wa) - 2
resid = (ydata - func(wa, *popt)) / sigma
chisq = np.dot(resid, resid)

print degrees_of_freedom, 'dof'
print 'chi squared %.2f' % chisq
print 'nchi2 %.2f' % (chisq / degrees_of_freedom)

# plot the solution

plt.plot(wa, ybest, label='Best fitting\nmodel')
plt.legend(frameon=False)

plt.show()


"""
Some code for B-Spline curve

S(x) = Sum_{j=0}^{n-1} c[j] * B[j,k;t](x)

B[i,0](x) = 1 if t[i] <= x <= t[i+1], otherwise 0
B[i,k](x) = (x - t[i]) / (t[i+k] - t[i]) * B[i, k-1](x) + (t[i+k+1] - x) / (t[i+k+1] - t[i+1]) * B[i+1, k-1](x)

Ref:
[1] https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.BSpline.html
"""

from scipy.interpolate import BSpline
import matplotlib.pyplot as plt
import numpy as np

def basic(x, i, k, t):
    """Basic elements in B-Spline"""
    if k == 0:
        return 1.0 if t[i] <= x < t[i + 1] else 0.0

    if t[i + k] == t[i]:
        c1 = 0.0
    else:
        c1 = (x - t[i]) / (t[i + k] - t[i]) * basic(x, i, k - 1, t)

    if t[i + k + 1] == t[i + 1]:
        c2 = 0.0
    else:
        c2 = (t[i + k + 1] - x) / (t[i + k + 1] - t[i + 1]) * basic(x, i + 1, k - 1, t)

    return c1 + c2

def bspline(x, t, c, k):
    """B-Spline function"""
    n = len(t) - k - 1
    assert (n >= k + 1) and (len(c) >= n)
    return sum(c[i] * basic(x, i, k, t) for i in range(n))

def main():
    k = 2
    t = [0, 1, 2, 3, 4, 5, 6]
    c = [-1, 2, 0, -1]
    sp1 = BSpline(t, c, k, True)  # if extrapolate is False, they are the same
    print(f'BSpline(2.5) = {sp1(2.5)}, bspline(2.5) = {bspline(2.5, t, c, k)}')
    xx = np.linspace(1.5, 4.5, 50)
    xx_fine = np.linspace(1.5, 4.5, 500)

    # figure, B-Spline basic function
    fig = plt.figure('B-Spline Basic Function')
    ax = fig.add_subplot(111)
    ax.plot(xx_fine, [basic(x, 2, 0, t) for x in xx_fine], 'k-', label='degree = 0')
    ax.plot(xx_fine, [basic(x, 2, 1, t) for x in xx_fine], 'b-', label='degree = 1')
    ax.plot(xx_fine, [basic(x, 2, 2, t) for x in xx_fine], 'r-', label='degree = 2')
    ax.grid(True)
    ax.set_title('B-Spline Basic Function B[2, k]')
    ax.legend(loc='best')
    plt.show(block=False)

    # figure: B-Spline
    fig = plt.figure('B-Spline')
    ax = fig.add_subplot(111)
    ax.plot(xx, [bspline(x, t, c, k) for x in xx], 'r-', label='naive')
    ax.plot(xx, sp1(xx), 'b.-', label='BSpline')
    ax.grid(True)
    ax.set_title('B-Spline')
    ax.legend(loc='best')
    plt.show(block=True)

if __name__ == '__main__':
    main()