from pyomo.environ import *

# Creates two vectors that represent the number of A and B ordered machines respectively per week.
# eg. consA[2] is the number of A ordered machines ordered for the second week.
consA = [0, 55, 55, 44, 0, 45, 45, 36, 35, 35]
consB = [0, 38, 38, 30, 0, 48, 48, 58, 57, 58]

week = range(1, 10)
weekZ = range(0, 10)

# Creates linear model.
mdl = ConcreteModel()

# Creates model's constraint list.
mdl.conList = ConstraintList()

# Adds two variable vectors each representing the production of A or B machines per week.
mdl.prodA = Var(week, domain=NonNegativeIntegers)
mdl.prodB = Var(week, domain=NonNegativeIntegers)

# Adds a binary variable vector that represents the state of the production line per week.
# 0->A machines, 1->B machines
mdl.deci = Var(weekZ, domain=Binary)
# Initializes state of production line's to A machines.
mdl.conList.add(expr = mdl.deci[0] == 0)

# Adds a binary variable vector that represents a change of the state of the production line's per week.
# 0->no-change, 1->change eg. chan[2] = 0 means that the state during the second week is the same as last week's.
mdl.chan = Var(week, domain=Binary)

# Adds two variable vectors that represent the stock of A or B machines respectively per week.
mdl.storA = Var(weekZ, domain=NonNegativeIntegers)
mdl.storB = Var(weekZ, domain=NonNegativeIntegers)
# Initializes stock of A machines to 125 and B machines to 143.
mdl.conList.add(expr = mdl.storA[0] == 125)
mdl.conList.add(expr = mdl.storB[0] == 143)

# Constraint setup loop.
for i in week:
    # Constraints the production of A machines per week to 100.
    mdl.conList.add(expr = mdl.prodA[i] <= 100)

    # Constraints the production of B machines per week to 80.
    mdl.conList.add(expr = mdl.prodB[i] <=  80)

    # Constraints the stock of A machines to be more than 80% of next week's order.
    mdl.conList.add(expr = mdl.storA[i-1] >= 0.8*consA[i])

    # Constraints the stock of B machines to be more than 80% of next week's order.
    mdl.conList.add(expr = mdl.storB[i-1] >= 0.8*consB[i])

    # Constraints the production to be able to fulfill current week's order of A machines.
    # Not needed since stock can't be negative.
    #mdl.conList.add(expr = mdl.prodA[i] + mdl.storA[i-1] >= consA[i])

    # Constraints the production to be able to fulfill current week's order of B machines.
    # Not needed since stock can't be negative.
    #mdl.conList.add(expr = mdl.prodB[i] + mdl.storB[i-1] >= consB[i])

    # Defines that next week's stock of A machines is equal to:
    # last week's stock + current week's produced machines - current week's ordered machines.
    mdl.conList.add(expr = mdl.storA[i] == mdl.storA[i-1] + mdl.prodA[i] - consA[i])

    # Defines that next week's stock of B machines is equal to:
    # last week's stock + current week's produced machines - current week's ordered machines.
    mdl.conList.add(expr = mdl.storB[i] == mdl.storB[i-1] + mdl.prodB[i] - consB[i])

    # Constraints the state of the production line to be either A or B using the big-M method.
    mdl.conList.add(expr = mdl.prodA[i] <= 1000*(1-mdl.deci[i]))
    mdl.conList.add(expr = mdl.prodB[i] <= 1000*mdl.deci[i])

    # Sets the value of the variable mdl.chan.
    mdl.conList.add(expr = mdl.deci[i] - mdl.deci[i-1] <= mdl.chan[i])
    mdl.conList.add(expr = mdl.deci[i-1] - mdl.deci[i] <= mdl.chan[i])

# Creates the model's cost function.
mdl.totalCost = Objective(expr =
    sum(mdl.prodA[i]*225 + mdl.prodB[i]*310 for i in week) +
    sum(mdl.storA[i]*0.00375*225 + mdl.storB[i]*0.195/52*310 for i in week) +
    500*sum (mdl.chan[i] for i in week),
    sense = minimize)

# Solves model with glpk solver.
SolverFactory('glpk').solve(mdl).write()

# Prints cost.
print("Cost = ", mdl.totalCost())

# Prints week number.
print("week#:", end = " ")
for i in weekZ:
    print("{:6}".format(weekZ[i]), end = " ")
print()

# Prints A machines order.
print("consA:", end = " ")
print("{:6}".format(''), end = " ")
for i in week:
    print("{:6}".format(consA[i]), end = " ")
print()

# Prints B machines order.
print("consB:", end = " ")
print("{:6}".format(''), end = " ")
for i in week:
    print("{:6}".format(consB[i]), end = " ")
print()

# Prints A machines production.
print("prodA:", end = " ")
print("{:6}".format(''), end = " ")
for i in week:
    print("{:6}".format(int(mdl.prodA[i].value)), end = " ")
print()

# Prints B machines production.
print("prodB:", end = " ")
print("{:6}".format(''), end = " ")
for i in week:
    print("{:6}".format(int(mdl.prodB[i].value)), end = " ")
print()

# Prints A machines stock.
print("storA:", end = " ")
for i in weekZ:
    print("{:6}".format(int(mdl.storA[i].value)), end = " ")
print()

# Prints B machines stock.
print("storB:", end = " ")
for i in weekZ:
    print("{:6}".format(int(mdl.storB[i].value)), end = " ")
print()

# Prints state of the production line. 0->A machines, 1->B machines
print("deci: ", end = " ")
for i in weekZ:
    print("{:6}".format(int(mdl.deci[i].value)), end = " ")
print()

# Prints change of the state of the production line. 0->no-change, 1->change
print("chan: ", end = " ")
print("{:6}".format(''), end = " ")
for i in week:
    print("{:6}".format(int(mdl.chan[i].value)), end = " ")
print()


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

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

fig_path = "/Users/gabriel/Documents/Research/USGS_Work/gmprocess/figs/spectra/"

#%%

def get_fft(series, fs, nfft):
    ft = np.fft.fft(series,fs)
    freq = np.fft.fftfreq(nfft)

    return freq, ft

def get_ifft(series, fs, nfft, real=True):
    if real:
        ift = np.fft.ifft(series,fs).real
    else:
        ift = np.fft.ifft(series,fs)

    return ift

def ps_psd_difference(ps,psd):
    diff = []
    if len(ps) == len(psd):
        diff = np.zeros(len(ps))
        for i in range(len(ps)):
            diff[i] = psd[i]/ps[i]
    else:
        print("Spectra must be of the same size")

    return diff

#%%

# Make a test signal
np.random.seed(0)
delta = .01
fs = 1/ delta
time_vec = np.arange(0, 70, delta)

#%% Sine wave

delta = .01
fs = 1/ delta
t = np.arange(0,256,delta)
wndw_factor=500
overlap_factor=2
nfft = len(t)
nperseg=len(t)/wndw_factor
noverlap=nperseg/overlap_factor

# filename = "sine_wave_input-del_.01-nfft_256k"
# plt.plot(t,np.sin(t))
# plt.title("Test sine wave, ∆=0.01, N=256000")
# plt.savefig(fname=fig_path + filename + ".png", dpi=500)
# plt.show()

#%% Calculate PSD of test signal with Welch's Method
freqs_psd, psd = signal.welch(np.sin(t),fs=fs, nfft=nfft, nperseg=nperseg, noverlap=noverlap, scaling="density")
# freqs_psd, psd = signal.welch(np.sin(t),fs=fs, nfft=nfft, nperseg=nperseg, scaling="density")
# freqs_psd, psd = signal.welch(np.sin(t),fs=fs, nfft=nfft, scaling="density")
# freqs_psd, psd = signal.welch(np.sin(t),fs=fs, scaling="density")
# freqs_psd, psd = signal.welch(np.sin(t), scaling="density")

freqs_ps, ps = signal.welch(np.sin(t),fs=fs, nfft=nfft, nperseg=nperseg, noverlap=noverlap, scaling="spectrum")
# freqs_ps, ps = signal.welch(np.sin(t),fs=fs, nfft=nfft, nperseg=nperseg, scaling="spectrum")
# freqs_ps, ps = signal.welch(np.sin(t),fs=fs, nfft=nfft, scaling="spectrum")
# freqs_ps, ps = signal.welch(np.sin(t),fs=fs,scaling="spectrum")
# freqs_ps, ps = signal.welch(np.sin(t),scaling="spectrum")

diff = ps_psd_difference(ps,psd)

# Note: freqs are the same for both psd and ps

# filename = "sine_wave-default_fs_and_nfft"
# filename = "sine_wave-default_fs_and_nfft_nperseg"
# filename = "sine_wave-set_fs_and_default_nfft_nperseg"
filename = "sine_wave-using_fs_and_nfft_nperseg->" + str(wndw_factor) + "->" + str(100/overlap_factor) + "perc_overlap"

# No Scaling
plt.plot(freqs_psd, psd, label="PSD")
plt.plot(freqs_ps, ps, "--", label="PS")
plt.title("Parameter Testing for Welch's Method")
plt.legend()
plt.savefig(fname=fig_path + filename + "-no_scaling.png", dpi=500)
plt.show()

# Log x
plt.semilogx(freqs_psd, psd, label="PSD")
plt.semilogx(freqs_ps, ps, "--", label="PS")
plt.title("Parameter Testing for Welch's Method")
plt.legend()
plt.savefig(fname=fig_path + filename + "-logx.png", dpi=500)
plt.show()

# Log Y
plt.semilogy(freqs_psd, psd, label="PSD")
plt.semilogy(freqs_ps, ps, "--", label="PS")
plt.title("Parameter Testing for Welch's Method")
plt.legend()
plt.savefig(fname=fig_path + filename + "-logy.png", dpi=500)
plt.show()

#%% Scaling differences

# PSD / PS scaling ratios
# plt.figure(figsize=(4, 6))
plt.semilogy(freqs_psd, diff, label="PSD/PS ratio")
ax = plt.gca()
# plt.annotate("Variation due to rounding error", xy=(2, 1), xytext=(3, 1.5))
plt.title("Differencing PSD and PS")
plt.legend()
plt.savefig(fname=fig_path + "diff-" + filename + "-logy.png", dpi=500)
plt.show()

#%% Calculate PSD of test signal with just a periodogram

# freqs_ps, ps = signal.periodogram(np.sin(t),fs)
# freqs_psd, psd = signal.periodogram(np.sin(t),fs,scaling="density")

# # No Scaling
# plt.plot(freqs_psd, psd, label="PSD")
# plt.plot(freqs_ps, ps, "--", label="PS")
# plt.legend()
# plt.show()
# # Log x
# plt.semilogx(freqs_psd, psd, label="PSD")
# plt.semilogx(freqs_ps, ps, "--", label="PS")
# plt.legend()
# plt.show()
# # Log Y
# plt.semilogy(freqs_psd, psd, label="PSD")
# plt.semilogy(freqs_ps, ps, "--", label="PS")
# plt.legend()
# plt.show()

#%% Forward FFT

# # Forward transform
# t = np.arange(256)
# sp = np.fft.fft(np.sin(t))
# freq = np.fft.fftfreq(t.shape[-1])
# # plt.plot(freq, sp.real, freq, sp.imag)
# plt.plot(freq, sp.real)
# plt.show()

# # Forward transform, ortho normalization
# t = np.arange(256)
# sp = np.fft.fft(np.sin(t),norm='ortho')
# freq = np.fft.fftfreq(t.shape[-1])
# # plt.plot(freq, sp.real, freq, sp.imag)
# plt.plot(freq, sp.real)
# plt.show()

# Forward transform finer spacing (larger NFFT)
t = np.arange(0,256,0.1)
sp = np.fft.fft(np.sin(t))
freq = np.fft.fftfreq(t.shape[-1],0.1)
# plt.plot(freq, sp.real, freq, sp.imag)
# plt.plot(freq, sp.real)
# plt.show()

# # Forward transform, finer spacing, ortho normalization
# t = np.arange(0,256,0.1)
# sp = np.fft.fft(np.sin(t),norm='ortho')
# freq = np.fft.fftfreq(t.shape[-1],0.1)
# # plt.plot(freq, sp.real, freq, sp.imag)
# plt.plot(freq, sp.real)
# plt.show()

#
# Inverse FFT
#

# # Inverse transform
# t = np.arange(256)
# sig = np.fft.ifft(sp)
# plt.plot(t, sig)
# # plt.show()

# # Inverse transform, ortho normalization
# t = np.arange(256)
# sig = np.fft.ifft(sp, norm='ortho')
# plt.plot(t, sig)
# plt.show()

# Inverse transform, finer spacing (larger NFFT)
t = np.arange(0,256,0.1)
sig = np.fft.ifft(sp)
plt.plot(t, sig)
plt.show()

# Inverse transform, finer spacing, ortho normalization
# t = np.arange(0,256,0.1)
# sig = np.fft.ifft(sp, norm='ortho')
# plt.plot(t, sig)
# plt.show()


#coding: utf-8
import time
import timeit
import numpy as np
from random import randint, uniform
from csv import writer

def iniciar_matriz_randomicamente(tamanho):
	M = []
	for i in range(tamanho):
		#for (j=0;j<N;j++){
		linha=[]
		for j in range(tamanho):
			#x=rand()%10;
			x = randint(0,10)
			if x<5:
				linha.append(0)
			elif x<8:
				linha.append(1)
			else:
				linha.append(2)
		M.append(linha)

	return M

def dividir(matriz):
	linha, coluna = matriz.shape #retorna o tamanho de linhas e coluna
	linha_metade, coluna_metade = linha//2, coluna//2 #prof. a barra simples dupla, faz uma divisao desconsiderando fracao
	return matriz[:linha_metade, :coluna_metade], matriz[:linha_metade, coluna_metade:], matriz[linha_metade:, :coluna_metade], matriz[linha_metade:, coluna_metade:]

def conquistar_mult(x, y):
	#multiplicando matriz AxB de forma recursiva, dividindo e conquistando
	if len(x) == 1:
		return x * y

	a, b, c, d = dividir(x)
	e, f, g, h = dividir(y)

	p1 = conquistar_mult(a, f - h)
	p2 = conquistar_mult(a + b, h)
	p3 = conquistar_mult(c + d, e)
	p4 = conquistar_mult(d, g - e)
	p5 = conquistar_mult(a + d, e + h)
	p6 = conquistar_mult(b - d, g + h)
	p7 = conquistar_mult(a - c, e + f)

	c11 = p5 + p4 - p2 + p6
	c12 = p1 + p2
	c21 = p3 + p4
	c22 = p1 + p5 - p3 - p7

	c = np.vstack((np.hstack((c11, c12)), np.hstack((c21,c22))))

	return c

if __name__ == "__main__":

	grade = lambda: round(uniform(0.0, 10.0), 2)
	data = ((i + 1, grade(), grade()) for i in range(10))

	with open("Tmp_Exe.csv", "w") as f:
		w = writer(f)
		w.writerow([" - ", "N", "Divide_Conquer_T_Exec"])
		N = [32, 64, 128, 256, 512, 1024]

		for i in range(len(N)):
			print N[i]

			A = np.array(iniciar_matriz_randomicamente(32))
			B = np.array(iniciar_matriz_randomicamente(32))
			inicio = timeit.default_timer()
			c = conquistar_mult(A,B)
			fim = timeit.default_timer()
			w.writerow([i, N[i], (fim - inicio)])

			if N[i] == 64:
				print("Matriz A")
				print(A)
				print("\n")
				print("Matriz B")
				print(B)
				print("\n")
				print("Matriz C")
				print(c)


from functools import lru_cache
from typing import Dict, List, Optional, Tuple

import numpy as np

def solve_tsp_dynamic_programming(
    distance_matrix: np.ndarray,
    maxsize: Optional[int] = None,
) -> Tuple[List, float]:
    """
    Solve TSP to optimality with dynamic programming.

    Parameters
    ----------
    distance_matrix
        Distance matrix of shape (n x n) with the (i, j) entry indicating the
        distance from node i to j. It does not need to be symmetric

    maxsize
        Parameter passed to ``lru_cache`` decorator. Used to define the maximum
        size for the recursion tree. Defaults to `None`, which essentially
        means "take as much space as needed".

    Returns
    -------
    permutation
        A permutation of nodes from 0 to n that produces the least total
        distance

    distance
        The total distance the optimal permutation produces

    Notes
    -----
    Algorithm: cost of the optimal path
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    Consider a TSP instance with 3 nodes: {0, 1, 2}. Let dist(0, {1, 2}) be the
    distance from 0, visiting all nodes in {1, 2} and going back to 0. This can
    be computed recursively as:

        dist(0, {1, 2}) = min(
            c_{0, 1} + dist(1, {2}),
            c_{0, 2} + dist(2, {1}),
        )

    wherein c_{0, 1} is the cost from going from 0 to 1 in the distance matrix.
    The inner dist(1, {2}) is computed as:

        dist(1, {2}) = min(
            c_{1, 2} + dist(2, {}),
        )

    and similarly for dist(2, {1}). The stopping point in the recursion is:

        dist(2, {}) = c_{2, 0}.

    This process can be generalized as:

        dist(ni, N) =   min   ( c_{ni, nj} + dist(nj, N - {nj}) )
                      nj in N

    and

        dist(ni, {}) = c_{ni, 0}

    With starting point as dist(0, {1, 2, ..., tsp_size}). The notation
    N - {nj} is the difference operator, meaning set N without node nj.

    Algorithm: compute the optimal path
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    The previous process returns the distance of the optimal path. To find the
    actual path, we need to store in a memory the following key/values:

        memo[(ni, N)] = nj_min

    with nj_min the node in N that provided the smallest value of dist(ni, N).
    Then, the process goes backwards starting from
    memo[(0, {1, 2, ..., tsp_size})].

    In the previous example, suppose memo[(0, {1, 2})] = 1.
    Then, look for memo[(1, {2})] = 2.
    Then, since the next step would be memo[2, {}], stop there. The optimal
    path would be 0 -> 1 -> 2 -> 0.

    Reference
    ---------
    https://en.wikipedia.org/wiki/Held%E2%80%93Karp_algorithm#cite_note-5
    """
    # Get initial set {1, 2, ..., tsp_size} as a frozenset because @lru_cache
    # requires a hashable type
    N = frozenset(range(1, distance_matrix.shape[0]))
    memo: Dict[Tuple, int] = {}

    # Step 1: get minimum distance
    @lru_cache(maxsize=maxsize)
    def dist(ni: int, N: frozenset) -> float:
        if not N:
            return distance_matrix[ni, 0]

        # Store the costs in the form (nj, dist(nj, N))
        costs = [
            (nj, distance_matrix[ni, nj] + dist(nj, N.difference({nj})))
            for nj in N
        ]
        nmin, min_cost = min(costs, key=lambda x: x[1])
        memo[(ni, N)] = nmin
        return min_cost

    best_distance = dist(0, N)

    # Step 2: get path with the minimum distance
    ni = 0  # start at the origin
    solution = [0]
    while N:
        ni = memo[(ni, N)]
        solution.append(ni)
        N = N.difference({ni})

    return solution, best_distance


import numpy as np

class Markowitz:
    """Solver of Markowitz optimization problem
    """

    def __init__(self, expected_returns, cov_matrix):
        self._n_assets = len(expected_returns)
        self._cov_matrix = cov_matrix
        self._expected_returns = expected_returns.reshape((self._n_assets, 1))

        A, B, C, D, We, Wm = self._build_magic_numbers()

        self._A = A
        self._B = B
        self._C = C
        self._D = D
        self._We = We
        self._Wm = Wm

    def optimal_risk(self, target_risk):
        """Calculate optimization solution given target risk
        Args:
            - target_risk (float): risk tolerance
        Returns:
            - (np.array) optimal weights
        """
        x_min, z = self._solve()

        weights = x_min + (z * target_risk / 2)

        return weights

    def optimal_return(self, target_return):
        """Calculate optimization solution given target return
        Args:
            - target_return (float): target return
        Returns:
            - (np.array) optimal weights
        """

        target_risk = 2 * (target_return - self._A /
                           self._C) * self._C / self._D

        return self.optimal_risk(target_risk)

    def get_efficient_curve(self, n_points):
        """Generates risk return curve with optimal weights solution
        Args:
            - n_points (int): number of points
        Return:
            - Tuple(np.array, np.array, np.array) returns, risks and optimal weights, respectively
        """

        start_return = self._A / self._C
        end_return = self._expected_returns.max()
        step_size = (end_return - start_return)/n_points

        returns = np.arange(start_return, end_return, step_size)
        weights = np.array([self.optimal_return(target_return) for target_return in returns])
        weights = weights.squeeze()

        risks = np.sqrt(np.array([np.dot(w.T, np.dot(self._cov_matrix, w)) for w in weights]))

        return (returns, risks, weights)

    def get_start_return(self):
        """Get initial return value of efficient curve
        """

        return self._A / self._C

    def _build_useful_matrices(self):
        W = np.linalg.inv(self._cov_matrix)
        e = np.ones((self._n_assets, 1))

        We = np.dot(W, e)
        Wm = np.dot(W, self._expected_returns)

        return e, We, Wm

    def _build_magic_numbers(self):
        e, We, Wm = self._build_useful_matrices()

        A = np.dot(e.T, Wm)
        B = np.dot(self._expected_returns.T, Wm)
        C = np.dot(e.T, We)
        D = B * C - A**2

        return A, B, C, D, We, Wm

    def _solve(self):

        x_min = self._We / self._C
        z = self._Wm - (self._A/self._C) * self._We

        return x_min, z

    def _optimal_curve(self, target_return):

        hyperbola = self._C * target_return ** 2 - 2 * self._A * target_return + self._B

        risk = np.sqrt((1 / self._D) * hyperbola)

        return float(risk)


import numpy as np

class KMeans:
    '''Consiste à trouver n_clusters partitions qui minimisent la variance au sein d'une même partition pour un ensemble de points donnés
    '''
    def __init__(self, n_clusters):
        self.n_clusters = n_clusters
        self._cluster_centroids = None
        self._labels = None
        self._score = np.inf

    def _init_centroids(self, lower, upper, n_features):
        '''Initialisation naive: les barycentres des clusters sont tirés d'une distribution uniforme définie par lower et upper
        '''
        return (upper - lower) * np.random.random((self.n_clusters, n_features)) + lower

    def fit(self, X, max_iter=300, tol=1e-4, n_trials=10):
        '''Assigne un unique cluster à chaque point de X:

        Initialise les barycentres aléatoirement,
        Jusqu'à convergence ou que le nombre maximal d'itérations soit atteint:
            1 - assigne le cluster avec le barycentre le plus proche
            2 - recalcule les barycentres en prenant la moyenne des points qui sont assigné au cluster correspondant

        Repète l'opération n_trials fois car l'agorithme est heuristique et ne garantit pas de trouver une solution optimale globale.
        '''
        for _ in range(n_trials):
            cluster_centroids = self._init_centroids(np.min(X), np.max(X), X.shape[1])
            labels = np.zeros(X.shape[0])
            score = np.inf

            for _ in range(max_iter):
                labels, score = self._cluster_assignement(X, cluster_centroids)
                new_centroids = self._compute_centroids(X, cluster_centroids, labels)

                if  np.sum((new_centroids - cluster_centroids)**2) < tol:
                    break

                cluster_centroids = new_centroids

            if score < self._score:
                self._cluster_centroids = cluster_centroids
                self._labels = labels
                self._score = score

        return self._labels

    def _cluster_assignement(self, X, cluster_centroids):
        '''Calcule les distances euclidiennes au carré entre chaque point et les barycentres: matrice de dimensions (n_points, n_clusters) et retourne
        le cluster avec le barycentre le plus proche: vecteur de dimension (n_points,) ainsi que le score: somme des distances au carré au cluster le plus proche

        L'utilisation de la distance euclidienne permet de calculer la matrice de façon efficiente en utilisant les fonctions optimisées de numpy (pas de boucle)
        '''
        squarred_distances = -2 * X @ cluster_centroids.T + np.sum(cluster_centroids**2, axis=1) + np.sum(X**2, axis=1)[:, None]
        labels = squarred_distances.argmin(axis=1)
        score = squarred_distances.min(axis=1).sum()

        return labels, score

    def _compute_centroids(self, X, old_cluster_centroids, labels):
        '''Calcule les nouveaux barycentres en prenant la moyenne des points appartenant au cluster du barycentre, pour chaque barycentre
        '''
        new_cluster_centroids = old_cluster_centroids.copy()

        for i in range(self.n_clusters):
            label_mask = (labels == i)

            if X[label_mask].shape[0] > 0:
                new_cluster_centroids[i] = X[label_mask].mean(axis=0)

        return new_cluster_centroids


import numpy as np

from prml.linear._classifier import Classifier
from prml.preprocess.label_transformer import LabelTransformer

class SoftmaxRegression(Classifier):
    """Softmax regression model.

    aka
    multinomial logistic regression,
    multiclass logistic regression,
    maximum entropy classifier.

    y = softmax(X @ W)
    t ~ Categorical(t|y)
    """

    @staticmethod
    def _softmax(a):
        a_max = np.max(a, axis=-1, keepdims=True)
        exp_a = np.exp(a - a_max)
        return exp_a / np.sum(exp_a, axis=-1, keepdims=True)

    def fit(
        self,
        x_train: np.ndarray,
        y_train: np.ndarray,
        max_iter: int = 100,
        learning_rate: float = 0.1,
    ):
        """Maximum likelihood estimation of the parameter.

        Parameters
        ----------
        X : (N, D) np.ndarray
            training independent variable
        t : (N,) or (N, K) np.ndarray
            training dependent variable
            in class index or one-of-k encoding
        max_iter : int, optional
            maximum number of iteration (the default is 100)
        learning_rate : float, optional
            learning rate of gradient descent (the default is 0.1)
        """
        if y_train.ndim == 1:
            y_train = LabelTransformer().encode(y_train)
        self.n_classes = np.size(y_train, 1)
        w = np.zeros((np.size(x_train, 1), self.n_classes))
        for _ in range(max_iter):
            w_prev = np.copy(w)
            y = self._softmax(x_train @ w)
            grad = x_train.T @ (y - y_train)
            w -= learning_rate * grad
            if np.allclose(w, w_prev):
                break
        self.w = w

    def proba(self, x: np.ndarray):
        """Return probability of input belonging each class.

        Parameters
        ----------
        x : np.ndarray
            Input independent variable (N, D)

        Returns
        -------
        np.ndarray
            probability of each class (N, K)
        """
        return self._softmax(x @ self.w)

    def classify(self, x: np.ndarray):
        """Classify input data.

        Parameters
        ----------
        x : np.ndarray
            independent variable to be classified (N, D)

        Returns
        -------
        np.ndarray
            class index for each input (N,)
        """
        return np.argmax(self.proba(x), axis=-1)


import numpy as np

from deepy.autograd import Autograd

class ArcTan(Autograd):
    """ Applies the arctan function element-wise. """
    def forward(self, ctx: Autograd.Context, tensor: np.array):
        ctx.save_for_back(tensor)
        return np.arctan(tensor)

    def backward(self, ctx: Autograd.Context, grad: np.array):
        tensor = ctx.data_for_back
        return grad / (tensor * tensor + 1)

class ReLU(Autograd):
    def forward(self, ctx: Autograd.Context, tensor: np.array):
        ctx.save_for_back(tensor)
        return np.clip(tensor, a_min=0, a_max=None)

    def backward(self, ctx: Autograd.Context, grad: np.array):
        t = ctx.data_for_back
        return np.where(t < 0, 0, grad)

class Sigmoid(Autograd):
    def forward(self, ctx: Autograd.Context, tensor: np.array) -> np.array:
        sig = 1 / (1 + np.exp(-tensor))
        ctx.save_for_back(sig)
        return sig

    def backward(self, ctx: Autograd.Context, grad: np.array = None):
        sig = ctx.data_for_back
        return sig * (1 - sig) * grad

class Softmax(Autograd):
    def forward(self, ctx: Autograd.Context, x: np.array) -> np.array:
        e_x = np.exp(x - np.max(x, axis=-1, keepdims=True))
        res = e_x / np.sum(e_x, axis=-1, keepdims=True)
        ctx.save_for_back(res)
        return res

    def backward(self, ctx: Autograd.Context, grad: np.array = None):
        res = ctx.data_for_back
        return grad * res * (1 - res)

class Softplus(Autograd):
    """ Applies the softplus function element-wise:

    Softplus(x) = ln(1 + e^x)
    Softplus'(x) = 1 / (1 + e^-x)
    """
    def forward(self, ctx: Autograd.Context, tensor: np.array) -> np.array:
        ctx.save_for_back(1 + np.exp(-tensor))
        return np.log(1 + np.exp(tensor))

    def backward(self, ctx: Autograd.Context, grad: np.array = None):
        denominator = ctx.data_for_back
        return grad / denominator

class Softsign(Autograd):
    """ Applies the softsign function element-wise:

    Softsign(x) = 1 / (1 + |x|)
    Softsign'(x) = 1 / (1 + |x|)^2
    """
    def forward(self, ctx: Autograd.Context, tensor: np.array) -> np.array:
        denominator = 1 + np.abs(tensor)
        ctx.save_for_back(denominator)
        return tensor / denominator

    def backward(self, ctx: Autograd.Context, grad: np.array = None):
        denominator = ctx.data_for_back
        return grad / (denominator * denominator)

class Tanh(Autograd):
    """ Applies the tanh function element-wise. """
    def forward(self, ctx: Autograd.Context, tensor: np.array) -> np.array:
        tanh = np.tanh(tensor)
        ctx.save_for_back(tanh)
        return tanh

    def backward(self, ctx: Autograd.Context, grad: np.array = None):
        tanh = ctx.data_for_back
        return (1 - tanh * tanh) * grad


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Perceptron
Original Source: https://github.com/Honghe/perceptron/tree/master/perceptron
Author: Honghe, modified by JVS.
"""

import matplotlib.pyplot as plt
import numpy as np

# set random seed so get same sequence of random numbers each time prog is run.
# np.random.seed(20)

class Perceptron(object):
    """docstring for Perceptron"""
    def __init__(self):
        super(Perceptron, self).__init__()
        self.w = [np.random.rand() * 2 - 1 for _ in range(2)] # weights
        self.learningRate = 0.001

    def response(self, x):
        # perceptron output
        y = x[0] * self.w[0] + x[1] * self.w[1] # dot product between w and x
        # y = sum([i * j for i, j in zip(self.w, x)]) # more pythonic
        #y = np.dot(x[0:1],self.w)
        y=y*1.0
        if y >= 0:
            res = 1.0
        else:
            res = -1.0
        return res

    def updateWeights(self, x, iterError):
        """
        upates the wights status, w at time t+1 is
        w(t+1) = w(t) + learningRate * (d - r) * x
        iterError is (d - r)
        """
        self.w[0] += self.learningRate * iterError * x[0]
        self.w[1] += self.learningRate * iterError * x[1]
        #self.w = \
            #[i + self.learningRate * iterError * j for i, j in zip(self.w, x)]

    def train(self, data):
        """
        Trains all the vector in data.
        Every vector in data must three elements.
        The third element of x (ie x[2]) is the label(desired output)
        """
        learned = False
        iteration = 0
        while not learned:
            numcorrect = 0
            globalError = 0.0
            for x in data: # for each input vector
                r = self.response(x)
                if x[2] != r: # if have a wrong response
                    iterError = x[2] - r # desired response - actual response
                    self.updateWeights(x, iterError)
                    globalError += abs(iterError)
                else:
                    numcorrect += 1
            print('num correctly classified = %s' % numcorrect)
            iteration += 1
            if globalError == 0.0 or iteration >= 100: # stop criteria
                print('iterations = %s' % iteration)
                learned = True # stop learning

            ########## Plot ##########
            plotinterval = 2
            if (iteration % plotinterval == 0):
                plotData(data,self.w)
                plt.pause(0.001)

def generateData(n):
    """
    generates a 2D linearly separable dataset with 2n samples.
    The third element of the sample is the correct response.
    """
    # class A=blue dots has all y values above 0
    xb = (np.random.rand(n) * 2 -1)
    yb = (np.random.rand(n) * 2 -1) / 2 + 0.55

    # class B=red dots has all y values below 0
    xr = (np.random.rand(n) * 2 -1)
    yr = (np.random.rand(n) * 2 -1) / 2 - 0.55

    inputs = []
    inputs.extend([[xb[i], yb[i], 1] for i in range(n)])
    inputs.extend([[xr[i], yr[i], -1] for i in range(n)])
    return inputs

def plotData(data,w):
    plt.clf()
    for x in data:
        if x[1] < 0:
            plt.plot(x[0], x[1], 'ob')
        else:
            plt.plot(x[0], x[1], 'or')
    # plot the decision boundary.
    # The decision boundary is orthogonal to w.
    n = np.linalg.norm(w) # aka the length of p.w vector
    ww = w / n # a unit vector
    ww1 = [ww[1], -ww[0]]
    ww2 = [-ww[1], ww[0]]
    plt.ion()
    plt.plot([ww1[0], ww2[0]], [ww1[1], ww2[1]], '--k')
    plt.title('Two classes separated by a line orthogonal to the weight vector')
    plt.show()

trainset = generateData(80) # train set generation
# testset = generateData(20) # test set generation

p = Perceptron()

plotData(trainset,p.w)
p.train(trainset)
plotData(trainset,p.w)

########## The End ##########


# coding: utf-8

# In[1]:

import numpy as np

# In[2]:

from scipy.stats import norm

def rnorm(n,mean,sd):
    """
    same functions as rnorm in r
    r: rnorm(n, mean=0, sd=1)
    py: rvs(loc=0, scale=1, size=1, random_state=None)
    """
    return norm.rvs(loc=mean,scale=sd,size=n)

def dnorm(x,mean,sd, log=False):
    """
    same functions as dnorm in r
    dnorm(x, mean=0, sd=1, log=FALSE)
    pdf(x, loc=0, scale=1)
    """
    if log:
        return np.log(norm.pdf(x=x,loc=mean,scale=sd))
    else:
        return norm.pdf(x=x,loc=mean,scale=sd)

def runif(n,min=0, max=1):
    """
    r: runif(n, min = 0, max = 1)
    py: random.uniform(low=0.0, high=1.0, size=None)
    """
    return np.random.uniform(min,max,size=n)

# In[3]:

s2 = 1
t2 = 10
mu = 5
n = 5

# In[4]:

y = rnorm(n,10,1)
y

# In[5]:

# mean of the normal posterior
mu_n = (np.mean(y)*n/s2 + mu/float(t2))/(n/float(s2)+1/float(t2))
mu_n

# In[6]:

# variance of the normal posterior
# t2.n<-1/(n/s2+1/t2)

t2_n = 1.0/(n/float(s2)+1.0/t2)
t2_n

# In[7]:

# defining the data
# y<-c(9.37, 10.18, 9.16, 11.60, 10.33)

y = [9.37, 10.18, 9.16, 11.60, 10.33]

# In[8]:

mu_n = (np.mean(y)*n/s2 + mu/float(t2))/(n/float(s2)+1/float(t2))
mu_n

# In[9]:

####metropolis part####
##S = total num of simulations
# theta<-0 ; delta<-2 ; S<-10000 ; THETA<-NULL ; set.seed(1)

theta = 0
delta = 2

S = 10000

theta_v = []

# In[ ]:

for s in range(S):
    theta_star = norm.rvs(theta,np.sqrt(delta),1)
    logr = (sum(dnorm(y,theta_star,np.sqrt(s2),log=True)) +
            sum(dnorm(theta_star,mu,np.sqrt(t2),log=True)))-
            (sum(dnorm(y,theta,np.sqrt(s2),log=True)) +
             sum(dnorm([theta],mu,np.sqrt(t2),log=True)))
    #print(logr)
    if np.log(runif(1))<logr:
        theta = theta_star
    #print(theta)
    theta_v.append(theta)

# In[ ]:

import matplotlib.mlab as mlab
import matplotlib.pyplot as plt

plt.figure(figsize=(20, 8))

plt.subplot(1, 2, 1)
plt.plot(theta_v,'b-.')

plt.subplot(1, 2, 2)
#bins = np.arange(0, S, 10)
plt.hist(theta_v, density=True,bins='auto')
x = np.linspace(min(theta_v),max(theta_v),100)
y = norm.pdf(x,mu_n,np.sqrt(t2_n))
plt.plot(x,y,'y-.')
plt.xlim(right=12)  # adjust the right leaving left unchanged
plt.xlim(left=8)  # adjust the left leaving right unchanged
plt.show()

# In[ ]:


from utils import calculate_variance
import numpy as np
from math import sqrt

def display_as_percentage(val):
  return '{:.1f}%'.format(val * 100)

returns_disney = [0.10, 0.12, 0.15, 0.05, 0.04]
returns_cbs = [-0.80, -0.15, 0.31, -0.06, -0.29]

stddev_disney = np.std(returns_disney)
stddev_cbs = np.std(returns_cbs)

dataset = [10, 8, 9, 10, 12]

def calculate_stddev(dataset):
  variance = calculate_variance(dataset)
  stddev = sqrt(variance)
  return stddev

stddev_disney = calculate_stddev(returns_disney)
stddev_cbs = calculate_stddev(returns_cbs)

print('The standard deviation of Disney stock returns is', display_as_percentage(stddev_disney))
print('The standard deviation of CBS stock returns is', display_as_percentage(stddev_cbs))


import numpy as np
import matplotlib.pyplot as plt

class Regression():
    def __init__(self, x, y, add_constant=True, theta=None):
        self.x, self.y, self.theta = _pretreatment_params(x, y, add_constant, theta)

    def _hypothesis(self, x, theta):
        pass

    def _cost_function(self, theta):
        return _square_cost_function(self._hypothesis, self.y, self.x, theta)

    def gradient_descent(self, learning_rate=0.1, max_iteration=2000, delta_theta_rel=1e-6):
        print('starting gradient descent...')
        last_cost = self._cost_function(self.theta)
        current_iteration = 0
        while current_iteration < max_iteration:
            current_iteration += 1
            current_theta = []
            for i, theta_i in enumerate(self.theta):
                theta_i -= learning_rate * _partial_derivative(self._cost_function, self.theta, i, learning_rate)
                current_theta.append(theta_i)
            last_theta = self.theta
            current_theta = np.array(current_theta)
            self.theta = current_theta

            current_cost = self._cost_function(self.theta)
            yield current_cost

            delta_cost_actual = last_cost - current_cost
            if delta_cost_actual < 0:
                print("warning: on iteration {0} cost function increased".format(current_iteration))
            last_cost = current_cost

            if np.max(np.abs((current_theta - last_theta) / last_theta)) < delta_theta_rel:
                break
        print("complete on iteration {0}".format(current_iteration))
        print("theta: {0}".format(self.theta))

    def predict(self, x):
        x = np.array(x)
        if x.size + 1 == self.theta.size:
            x = np.insert(x, 0, [1])
        return self._hypothesis(x, self.theta)

class LinearRegression(Regression):
    def _hypothesis(self, x, theta):
        return linear_hypothesis(x, theta)

    def normal_equation(self):
        self.theta = np.dot(np.dot(np.linalg.pinv(np.dot(self.x.T, self.x)), self.x.T), self.y)
        return self.theta

class LogisticRegression(Regression):
    def _hypothesis(self, x, theta):
        return logistic_hypothesis(x, theta)

def linear_hypothesis(x, theta):
    return np.dot(x, theta)

def logistic_hypothesis(x, theta):
    return 1 / (1 + np.exp(-np.dot(x, theta)))

def _pretreatment_params(x, y, add_constant=True, theta=None):
    if not isinstance(x, np.ndarray):
        x = np.array(x)
    if x.ndim == 1:
        x = x.reshape(x.size, 1)
    if add_constant:
        x = _add_constant(x)
    if y and y.ndim != 1:
        y = y.reshape(y.size)
    if not theta:
        theta = np.random.randn(x.shape[1])
    return x, y, theta

def _add_constant(x):
    return np.concatenate((np.ones((x.shape[0], 1)), x), axis=1)

def _partial_derivative(cost_function, theta, i, learing_rate):
    theta_pos_delta = theta.copy()
    theta_pos_delta[i] += learing_rate
    theta_neg_delta = theta.copy()
    theta_neg_delta[i] -= learing_rate
    return (cost_function(theta_pos_delta) - cost_function(theta_neg_delta)) / 2 / learing_rate

def _square_cost_function(hypothesis, y, x, theta):
    return np.sum((hypothesis(x, theta) - y) ** 2) / 2 / y.size

if __name__ == '__main__':
    x = np.array([1.1, 2.1, 2.9, 3.9, 5.2])
    y = np.array([3, 5, 7, 9, 11])
    reg = LinearRegression(x, y)
    fig = plt.subplot(111)

    for it in reg.gradient_descent():
        print(it)

    print(reg.normal_equation())
    print(reg.predict(1))


#!/usr/bin/env/python3
import numpy as np
from sklearn.metrics import pairwise as pw
from math import sqrt as sq

'''This script computes sample distance covariance for a set of paired samples. See pg 8 of https://arxiv.org/pdf/1903.01051.pdf'''

def paired_sample_covar(a, b):
    #a,b pairwise distance matrices of equal size
    npoints = len(a[0])
    #Compute means
    a_row_means = np.mean(a, axis=1)
    a_col_means = np.mean(a, axis=0)
    b_row_means = np.mean(b, axis=1)
    b_col_means = np.mean(b, axis=0)
    a_tot_mean = np.mean(a)
    b_tot_mean = np.mean(b)

    #Compure dcov (and double centred matrix entries)
    products = []
    for k in range(npoints):
        for l in range(npoints):
            products.append((a.item((k,l)) - a_row_means.item(k) - a_col_means.item(l) + a_tot_mean)*(b.item((k,l)) - b_row_means.item(k) - b_col_means.item(l) + b_tot_mean))
    dcov = np.mean(np.array(products))
    return(sq(dcov))

def d_cor(a, b):
    #a,b pairwise distance matrices of equal size
    dvar_a =  paired_sample_covar(a,a)
    dvar_b = paired_sample_covar(b,b)
    if dvar_a*dvar_b == 0:
        return(0)
    dcor = (paired_sample_covar(a, b))/sq(dvar_a*dvar_b)
    return(dcor)

if __name__ == "__main__":
    npoints=3
    X = np.random.rand(npoints,2)
    Y = np.random.rand(npoints,2)
    #Pairwise distance matrices
    a = pw.euclidean_distances(X)
    b = pw.euclidean_distances(Y)
    dcov = paired_sample_covar(a,b)
    print(dcov)


import math
from klampt import vectorops
from klampt import so3
#import numpy as np

def solve_2R_inverse_kinematics(x,y,L1=1,L2=1):
    """For a 2R arm centered at the origin, solves for the joint angles
    (q1,q2) that places the end effector at (x,y).

    The result is a list of up to 2 solutions, e.g. [(q1,q2),(q1',q2')].
    """
    D = vectorops.norm((x,y))
    thetades = math.atan2(y,x)
    if D == 0:
        raise ValueError("(x,y) at origin, infinite # of solutions")
    c2 = (D**2-L1**2-L2**2)/(2.0*L1*L2)
    q2s = []
    if c2 < -1:
        print "solve_2R_inverse_kinematics: (x,y) inside inner circle"
        return []
    elif c2 > 1:
        print "solve_2R_inverse_kinematics: (x,y) out of reach"
        return []
    else:
        if c2 == 1:
            q2s = [math.acos(c2)]
        else:
            q2s = [math.acos(c2),-math.acos(c2)]
    res = []
    for q2 in q2s:
        thetaactual = math.atan2(math.sin(q2),L1+L2*math.cos(q2))
        q1 = thetades - thetaactual
        res.append((q1,q2))
    return res

def solve_3R_forward_kinematics(q1,q2,q3,L1=1,L2=1,L3=1):
    """Returns a list of (x,y,theta) triples for each link
    for a planar, 3R manipulator with link lengths L1, L2, L3.
    It also returns the end effector transform."""
    T1 = (0,0,q1)
    dx1 = (L1*math.cos(T1[2]),L1*math.sin(T1[2]))
    T2 = vectorops.add((T1[0],T1[1]),dx1)+[T1[2]+q2]
    dx2 = (L2*math.cos(T2[2]),L2*math.sin(T2[2]))
    T3 = vectorops.add((T2[0],T2[1]),dx2)+[T2[2]+q3]
    dx3 = (L3*math.cos(T3[2]),L2*math.sin(T3[2]))
    T4 = vectorops.add((T3[0],T3[1]),dx3)+[T3[2]]
    return [T1,T2,T3,T4]

def solve_3R_inverse_kinematics(x,y,theta,L1=1,L2=1,L3=1):
    """IMPLEMENT ME: for a planar, 3R manipulator with link lengths L1, L2, L3,
    solve for the joint angles (q1,q2,q3) such that the end effector
    is placed at x,y and is oriented along the angle theta.

    The current implementation is incorrect, and only tries to reach x,y
    with the second and third joint angle, using 2R manipulator routine
    presented in class.  Your implementation should correctly place the end
    effector to be oriented at the angle theta (which will require using all
    three joint angles).

    In general there will be up to two solutions.  The result is a list of
    solutions.  Your implementation should correctly return 0 solutions
    if the request is infeasible.
    """
    q1,q2,q3 = 0,0,0
    (x1,y1) = (L1*math.cos(q1),L1*math.sin(q1))
    q23s = solve_2R_inverse_kinematics(x-x1,y-y1,L2,L3)
    res = []
    for q2,q3 in q23s:
        res.append((q1,q2,q3))
    return res

def run_ex1():
    print solve_3R_forward_kinematics(0,0,0)

    #test 1
    xdes = (3,0,0)
    qs = solve_3R_inverse_kinematics(*xdes)
    print "xdes =",xdes,
    if len(qs)==0:
        print "failed"
    else:
        print len(qs),"solutions:"
    for q in qs:
        print "    q =",q,"fk =",solve_3R_forward_kinematics(*q)[3]

    #note the orientation error
    xdes = (2.0,0.5,0)
    qs = solve_3R_inverse_kinematics(*xdes)
    print "xdes =",xdes,
    if len(qs)==0:
        print "failed"
    else:
        print len(qs),"solutions:"
    for q in qs:
        print "    q =",q,"fk =",solve_3R_forward_kinematics(*q)[3]

    #note the orientation error
    xdes = (2.0,0.0,math.pi*0.5)
    qs = solve_3R_inverse_kinematics(*xdes)
    print "xdes =",xdes,
    if len(qs)==0:
        print "failed"
    else:
        print len(qs),"solutions:"
    for q in qs:
        print "    q =",q,"fk =",solve_3R_forward_kinematics(*q)[3]

    #this one is feasible, but the current implementation fails
    xdes = (-3.0,0.0,0)
    qs = solve_3R_inverse_kinematics(*xdes)
    print "xdes =",xdes,
    if len(qs)==0:
        print "failed"
    else:
        print len(qs),"solutions:"
    for q in qs:
        print "    q =",q,"fk =",solve_3R_forward_kinematics(*q)[3]

    #this one is infeasible
    xdes = (-2.0,0.0,-math.pi*0.5)
    qs = solve_3R_inverse_kinematics(*xdes)
    print "xdes =",xdes,
    if len(qs)==0:
        print "failed"
    else:
        print len(qs),"solutions:"
    for q in qs:
        print "    q =",q,"fk =",solve_3R_forward_kinematics(*q)[3]

if __name__ == "__main__":
    run_ex1()


import numpy as np

def eigenvalues_2d_symmetric(a, b, d):
    """This function returns the eigenvalues of a 2x2 symmetric matrix given by:
    | a  b |
    | b  d |

    Note that this a special case of a 2x2 symmetric matrix where every element of the matrix is passed as an image.
    This allows the evaluation of eigenvalues to be vectorized over the entire image. This is much more efficient
    than calling the numpy functions for computing the eigenvalues for each pixel of the image.

    Parameters
    ----------
    a, b, d: array_like
        2D images containing the values of the matrix elements a b and d.
    """
    t = a + d
    bsq4 = 4 * b ** 2
    amdsq = (a - d) ** 2
    ht = 0.5 * t
    half_sqrt_discriminant = 0.5 * np.sqrt(bsq4 + amdsq)
    eig1 = ht + half_sqrt_discriminant
    eig2 = ht - half_sqrt_discriminant

    return np.stack((eig1, eig2), axis=len(eig1.shape))

def eigenvector_2d_symmetric(a, b, d, eig, eps=1e-8):
    """Returns normalized eigenvector corresponding to the provided eigenvalue.

    Note that this a special case of a 2x2 symmetric matrix where every element of the matrix is passed as an image.
    This allows the evaluation of eigenvalues to be vectorized over the entire image. This is much more efficient
    than calling the numpy function for computing the eigenvectors for each pixel of the image.

    This function solves:
    | a-lambda  b        |
    | b         d-lambda | [x, y] = 0

    Which means that:
      bx = (lambda - d) y
    or
      y = (lambda - a)/b x

    This solution is invalid for b == 0. Here we expect orthogonal vectors [1 0] and [0 1].
        ax + by = l x
        bx + dy = l y

    so x = 1 iff b = 0 and l = a
    and y = 1 iff b = 0 and l = d
    """
    ex = np.zeros(a.shape)
    ey = np.zeros(a.shape)
    ex[np.abs(a - eig) < eps] = 1
    ey[np.abs(d - eig) < eps] = 1

    mask = np.abs(b) > eps
    tx = b[mask]
    ty = eig[mask] - a[mask]

    length = np.sqrt(tx * tx + ty * ty)
    tx = tx / length
    ty = ty / length

    ex[mask] = tx
    ey[mask] = ty

    return ex, ey


from src.sdia_python.lab2.window import BallWindow, BoxWindow, UnitBoxWindow
import numpy as np

def calcul_pi(n):
    """compute a simple estimation of pi with the Monte Carlo method
    generates a square and a quadrant inscribed within it
    random points are generated uniformly inside the square
    the proportion of point which are in the circle is an estimation of pi/4

    Args:
        n (int): numbers of random points to be generated

    Returns:
        [float]: estimation of pi
    """
    unitBox = UnitBoxWindow(np.array([0.5, 0.5]))
    unitBall = BallWindow(np.array([0, 0]), 1)
    pointsAlea = unitBox.rand(n)
    piEstim = 0
    for point in pointsAlea:
        if point in unitBall:
            piEstim += 1
    return 4 * piEstim / n


import numpy as np

def circumference_circle(radius: float):
    """Calculate for a given radius the circumference of a circle

    :param radius: radius of circle
    :type radius: float in cm
    """
    if radius < 0.:
        raise ValueError("The radius must be >=0")
    circumference = 2 * np.pi * radius
    print("the circumference is C = %fcm" % circumference)
    return circumference

def euclidean_distance(list_ref, list_comp, vectors):
    """Calculates the Euclidean distance (L2 norm) between pairs of vectors.

    Args:
        list_ref (integer list): A list with the indices of the reference vectors.
        list_comp (integer list): A list with the indices of the vectors to\
        compare to.
        data (numpy array): The data object.

    Returns:
        numpy array: The Euclidean distance (L2 norm) for comparison vs. reference\
        vectors."""
    distances = np.zeros(len(list_ref))
    for i in range(len(list_ref)):
        distances[i] = np.linalg.norm(vectors[list_comp[i]] - vectors[list_ref[i]])
    return distances


import math
import numpy as np
from numba import jit, njit

@njit
def SinalBasico(Tempo, Frequencia, Amplitude):
    return Amplitude*np.sin(Frequencia*2*np.pi*Tempo)

@jit
def EnergiaWale(arrayWavelet):
    return np.trapz((arrayWavelet)**2)

@jit
def WaleNormaliza(funcWavelet,tempo,dilat,posic):
    wavelet = funcWavelet(tempo,dilat,posic)
    Energia = EnergiaWale(wavelet)
    return 1/(Energia**(1/2))*(wavelet)

@njit
def WaleNotNormaliza(funcWavelet,tempo,dilat,posic):
    wavelet = funcWavelet(tempo,dilat,posic)
    return wavelet

@jit
def HermitianHat (tempo,dilat,posic ):
    """Retornar um array das posições de uma OndaLet(Chápeu de Hermitian) em relação ao tempo

    Argumentos:
        tempo: um Numpy Array com os pontos do sinal que serão exibido
        dilat: O valor de dilatação da Wavelet
        posic: O valor da posição em relação ao tempo da Wavelet

    Retorno:
        Retorna um Array 2D com os valores de Amplitude em relação ao tempo."""

    t = ((tempo-posic)/dilat)
    return 2/(5**(1/2))*math.pi**-(1/4)*t*(1+t)*math.e**((-1/2)*t**2)

@njit
def MexicanHat (tempo,dilat,posic):
    """Retornar um array das posições de uma OndaLet(Chápeu de mexicano) em relação ao tempo

    Argumentos:
        tempo: um Numpy Array com os pontos do sinal que serão exibido
        dilat: O valor de dilatação da Wavelet
        posic: O valor da posição em relação ao tempo da Wavelet

    Retorno:
        Retorna um Array 2D com os valores de Amplitude em relação ao tempo."""
    t = ((tempo-posic)/dilat)
    return (1-t**2)*math.e**((t**2/2)*-1)

@njit
def MeyerWale (tempo,dilat,posic ):
    """Retornar um array das posições de uma OndaLet(Meyer) em relação ao tempo

    Argumentos:
        tempo: um Numpy Array com os pontos do sinal que serão exibido
        dilat: O valor de dilatação da Wavelet
        posic: O valor da posição em relação ao tempo da Wavelet

    Retorno:
        Retorna um Array 2D com os valores de Amplitude em relação ao tempo.

        Formando a imagem correta(provavelmente), mas com problemas no deslocamento do eixo do tempo, indo da direita pra esquerda"""

    t = ((tempo-posic)/dilat)
    outputArray = np.array([])
    for x in t:
        if x == 0:
            output = 2/3 + 4/(3*math.pi)
        else:
            output = ((np.sin((2*math.pi)/(3)*x)+(4/3)*x*np.cos(((4*math.pi)/3)*x))/(math.pi*x-(16*math.pi/9)*(x**3)))
            output = output
        outputArray = np.append(output, outputArray)
    return outputArray

@njit
def HermitianWale1(tempo,dilat,posic):

    t = ((tempo-posic)/dilat)
    return (2**(1/2))*math.pi**(-1/4)*t*math.e**((-t**2)/2)

@njit
def MorletWavelet(tempo,dilat,posic):

    t = ((tempo-posic)/dilat)
    return math.e**((-t**2)/2)*math.e**(1j*6*t)


'''Author: Bryce Trabucco, Copyright 2019'''

import numpy as np
from numpy import random, dot, array

inputs = np.array([
    [0,0],
    [0,1],
    [1,0],
    [1,1]])

labels = np.array([
    [0],
    [1],
    [1],
    [0]])

# This is the activation function
def sigmoid(a):
    return 1 / (1 + np.exp(-a))

# This is the derivative of the activation function.
def dsigmoid_dx(a):
    return a * (1 - a)

class Layer(object):

    # This function initializes all of the values that will be used repeatedly in the class.
    def __init__(self, inputs, outputs, learning_rate = 0.1):

        self.weights = np.random.uniform(size = (outputs, inputs))
        self.bias = np.zeros(outputs)
        self.learning_rate = learning_rate

    # The forward function takes the weighted sum of the weights and inputs at each neuron. Then,
    # the weighted sum is passed through the Sigmoid activation function.
    def forward(self, inputs):
        self.last_inputs = inputs
        w_sum_bias = (np.dot(self.weights, inputs) + self.bias)
        activation = sigmoid(w_sum_bias)
        self.last_activation = activation
        return activation

    # The backward function takes the derivative of the output from the forward function. Then, it
    # updates the weights and biases.
    def backward(self, grad_out):
        grad = grad_out * dsigmoid_dx(self.last_activation)
        self.bias = self.bias - self.learning_rate * grad
        self.weights = self.weights - self.learning_rate * np.array(np.matrix(grad).T @ np.matrix(self.last_inputs))
        grad = np.matrix(grad) @ self.weights
        grad = np.array(grad)[0, :]
        return grad

# This is setting up the two layers of neurons.
first_neuron_layer = Layer(2, 2, learning_rate = 0.1)
second_neuron_layer = Layer(2, 1, learning_rate = 0.1)

# This loop is the training loop
for i in range(40000):

    k = random.randint(len(inputs))
    x = inputs[k]
    desired_response = labels[k]

    # This block is running through forward and backpropagation once
    hidden_layer = first_neuron_layer.forward(x)
    output_layer = second_neuron_layer.forward(hidden_layer)
    loss = (desired_response - output_layer) ** 2
    dloss_doutput_layer = - 2 * (desired_response - output_layer)
    back_output = second_neuron_layer.backward(dloss_doutput_layer)
    back_hidden = first_neuron_layer.backward(back_output)

# This is printing off the results
print(" Network output ")
for i in range(len(inputs)):
    x = inputs[i]
    response = labels[i]
    final_hidden = first_neuron_layer.forward(x)
    final_output = second_neuron_layer.forward(final_hidden)
    print("{} -> {} = {}".format(x, response, final_output))


# call scipy package
import numpy as np
from scipy import stats

# input data
x = [77, 50, 99, 60, 90, 82, 93, 75, 86, 99, 55, 79]

# numpy.std method
get_sample_std = np.std(x, ddof=1)
get_population_mean = np.sum(x) / (len(x))

# calculate coefficient of variation
get_cv = (get_sample_std * 100) / get_population_mean

# output
print("Sample of mean of x : ", get_population_mean)
print("Coeefficient of variation: {0}".format(get_cv))


import numpy as np

def quaternion(v):
	# Take 3d input and turn into quaternion.
	x,y,z = v
	return np.array(([0,x,y,z]))

def rotation(theta,axis=None):
	# Rotation quaternion of angle theta by axis x,y,z.
	# Axis input should be of type [1,0,0].
	if axis is None:
		axis = np.array(([1,1,1]))

	theta = np.deg2rad(theta)
	i,j,k = np.sin(theta/2)*axis

	return np.array([np.cos(theta/2),i,j,k])

def magnitude(q):
	# Calculate magnitude of quaternion.
	return np.sqrt(q[0]**2 + q[1]**2 + q[2]**2 + q[3]**2)

def normalise(q):
	# Normalise a quaternion.
	q /= magnitude(q)
	return q

def multiply(a,b):
	# Product of two quarternions, b operating on a.
	a0,a1,a2,a3 = a
	b0,b1,b2,b3 = b

	q0 = b0*a0 - b1*a1 - b2*a2 - b3*a3
	q1 = b0*a1 + b1*a0 - b2*a3 + b3*a2
	q2 = b0*a2 + b1*a3 + b2*a0 - b3*a1
	q3 = b0*a3 - b1*a2 + b2*a1 + b3*a0

	q = np.array(([q0,q1,q2,q3]))

	return q

def product(q):
	''' q as list'''
	qout = multiply(q[0],q[1])
	for i in range(2,len(q)):
		qout = multiply(qout,q[i])

	return qout

def intrinsicRotation(q):
	''' q as list'''
	return product(q)

def globalRotation(q):
	''' q as list'''
	e = len(q)-1
	qout = multiply(q[e],q[e-1])
	for i in range(e-2,-1,-1):
		qout = multiply(qout,q[i])

	return qout

def inverse(a):
	# Inverse of quaternion a
	a0,a1,a2,a3 = a
	q = (np.array(([a0,-a1,-a2,-a3])) / (a0**2 + a1**2 + a2**2 + a3**2))

	return q

def euler(q):
	R = np.array([[(q[0]**2+q[1]**2-q[2]**2-q[3]**2), 2*(q[1]*q[2]-q[0]*q[3]), 2*(q[1]*q[3]+q[0]*q[2])],
		[2*(q[2]*q[1]+q[0]*q[3]), (q[0]**2-q[1]**2+q[2]**2-q[3]**2), 2*(q[2]*q[3]-q[0]*q[1])],
		[2*(q[3]*q[1]-q[0]*q[2]), 2*(q[3]*q[2]+q[0]*q[1]), (q[0]**2-q[1]**2-q[2]**2+q[3]**2)]])

	return R

def rotate(a,r):
	# Rotate quaternion a, by quaternion r.
	ri = inverse(r)
	b = product(product(r,a),ri)

	return b


import matplotlib.pyplot as plt
from scipy import *
from scipy import integrate
from scipy.integrate import ode, odeint
import numpy as np

# Initial setup
xx0 = np.array([.25,1.5])
mu_ =[-.5,0,.07,1]
xmin,xmax = -2,2
ymin,ymax = -2,2
lw = 1.5
plt.close()
fig,([ax0,ax1],[ax2,ax3]) = plt.subplots(2,2,
                                                            figsize=(12,8),
                                                            sharex=True,
                                                            sharey=True)
ax0.set_facecolor(plt.cm.gray(.95))
ax1.set_facecolor(plt.cm.gray(.95))
ax2.set_facecolor(plt.cm.gray(.95))
ax3.set_facecolor(plt.cm.gray(.95))
#ax.axis('equal')

#Vector field
x1,y1 = np.linspace(xmin,xmax,200),np.linspace(ymin,ymax,200)
X,Y = np.meshgrid(x1,y1 )
#Plot1
#====================================
mu=mu_[0]
U = mu*X - Y +X*Y**2
V = X + mu*Y + Y**3
start = [[0.3,0],[1.4,-.35]]

#Plotting
strm = ax0.streamplot( X,Y,U, V,linewidth=.2)
try:
    strmS = ax0.streamplot(x1,y1, U, V, start_points=start, color="crimson", linewidth=.5)
except: # IndexError:
    print('Unable to plot initial value trajectories')
fig.suptitle('Hopf Bifurcation /n Strogatz 8.2.1, p.256', size=16)
ax0.set_title( 'a) Stable Spiral', size=12)
ax0.text(xmin+.25,ymax-.25, r'$\mu = {0}$'.format(mu),size=14)
ax0.plot(y1/(mu+y1**2),x1, 'k-.',lw=lw, label=r'x-nullcline: $x = \frac{y}{(\mu+y^2)}$')
ax0.plot(-mu*y1+y1**3,x1, 'k:', lw=lw, label=r'y-nullcline: $x =  -\mu y + y^3}$')
ax0.set_xlim([xmin,xmax])
ax0.set_ylim([ymin,ymax])
ax0.set_xlabel(r"$ x$",size=14)
ax0.set_ylabel(r"$ y$",size=14)
plt.grid(True)

#Plot 2
#===================================
mu= mu_[1]
U = mu*X - Y +X*Y**2
V = X + mu*Y + Y**3
start = [[0.3,0],[.6,.5]]

#Plotting
strm = ax1.streamplot( X,Y,U, V,linewidth=.2)
try:
    strmS = ax1.streamplot(x1,y1, U, V, start_points=start, color="crimson", linewidth=.5)
except: # IndexError:
    print('Unable to plot initial value trajectories')
ax1.set_title( 'b) Critical Point', size=12)
ax1.text(xmin+.25,ymax-.25, r'$\mu = {0}$'.format(mu),size=14)
ax1.plot(y1/(mu+y1**2),x1, 'k-.',lw=lw, label=r'x-nullcline: $x = \frac{y}{(\mu+y^2)}$')
ax1.plot(-mu*y1+y1**3,x1, 'k:', lw=lw, label=r'y-nullcline: $x =  -\mu y + y^3}$')
ax1.set_xlim([xmin,xmax])
ax1.set_ylim([ymin,ymax])
ax1.set_xlabel(r"$ x$",size=14)
ax1.set_ylabel(r"$ y$",size=14)
plt.grid(True)
#Plot 3
#===================================
mu= mu_[2]
U = mu*X - Y +X*Y**2
V = X + mu*Y + Y**3
start = [[0.3,0],[.6,.5]]

#Plotting
strm = ax2.streamplot( X,Y,U, V,linewidth=.2)
try:
    strmS = ax2.streamplot(x1,y1, U, V, start_points=start, color="crimson", linewidth=.5)
except: # IndexError:
    print('Unable to plot initial value trajectories')
ax2.set_title( 'c) Unstable Spiral', size=12)
ax2.text(xmin+.25,ymax-.25, r'$\mu = {0}$'.format(mu),size=14)
ax2.plot(y1/(mu+y1**2),x1, 'k-.',lw=lw, label=r'x-nullcline: $x = \frac{y}{(\mu+y^2)}$')
ax2.plot(-mu*y1+y1**3,x1, 'k:', lw=lw, label=r'y-nullcline: $x =  -\mu y + y^3}$')
ax2.set_xlim([xmin,xmax])
ax2.set_ylim([ymin,ymax])
ax2.set_xlabel(r"$ x$",size=14)
ax2.set_ylabel(r"$ y$",size=14)
plt.grid(True)
#Plot 4
#===================================
mu= mu_[3]
U = mu*X - Y +X*Y**2
V = X + mu*Y + Y**3
start = [[0.3,0],[.6,.5]]

#Plotting
strm = ax3.streamplot( X,Y,U, V,linewidth=.2)
try:
    strmS = ax3.streamplot(x1,y1, U, V, start_points=start, color="crimson", linewidth=.5)
except: # IndexError:
    print('Unable to plot initial value trajectories')
ax3.set_title( 'd) Unstable Spiral', size=12)
ax3.text(xmin+.25,ymax-.25, r'$\mu = {0}$'.format(mu),size=14)
ax3.plot(y1/(mu+y1**2),x1, 'k-.',lw=lw, label=r'x-nullcline: $x = \frac{y}{(\mu+y^2)}$')
ax3.plot(-mu*y1+y1**3,x1, 'k:', lw=lw, label=r'y-nullcline: $x =  -\mu y + y^3}$')
ax3.set_xlim([xmin,xmax])
ax3.set_ylim([ymin,ymax])
ax3.set_xlabel(r"$ x$",size=14)
ax3.set_ylabel(r"$ y$",size=14)
ax3.legend()
plt.grid(True)
plt.show()


import cv2
import numpy as np
from matplotlib import pyplot as plt
from pyplot_template import plt_arch

img = cv2.imread('../res/lenna.bmp', cv2.IMREAD_GRAYSCALE)

""" 고속 푸리에 변환(FFT) """
fft = np.fft.fft2(img)  # 고속 푸리에 변환을 한다.
fft_shift = np.fft.fftshift(fft)  # 셔플링을 한다.
# 스펙트럼 영상을 구한다. D(u, v) = c x log(1 + |F(u, v)| )
spectrum = 20 * np.log(1 + np.abs(fft_shift))

""" 역 고속 푸리에 변환(IFFT) """
hpf_ishift = np.fft.ifftshift(fft_shift)  # 셔플링 되었던 것을 역셔플링한다.
ifft = np.fft.ifft2(hpf_ishift)  # 역 고속 푸리에 변환을 한다.
ifft = np.abs(ifft)  # 절대값 적용, 복소수를 실수로 변환

""" 저주파 통과 필터링(LPF) """
rows, cols = img.shape
center_row, center_col = rows // 2, cols // 2  # 이미지의 중심 좌표
size = 30  # 통과시킬 저주파 영역의 크기 (n x n)
# 가운데 = 1, 외곽 = 0
lpf_mask = np.zeros((rows, cols), np.uint8)
cv2.circle(lpf_mask, (center_row, center_col), size, (255, 255, 255), cv2.FILLED)
lpf = fft_shift * lpf_mask  # 마스크를 적용한다. (요소별 곱셈: n x 1 = n, n x 0 = 0)
lpf_spectrum = 20 * np.log(1 + np.abs(lpf))

""" 고주파 통과 필터링(HPF) """
rows, cols = img.shape
center_row, center_col = rows // 2, cols // 2  # 이미지의 중심 좌표
size = 30  # 제거시킬 저주파 영역의 크기 (n x n)
# 가운데 = 0, 외곽 = 1
hpf_mask = np.ones((rows, cols), np.uint8)
cv2.circle(hpf_mask, (center_row, center_col), size, (0, 0, 0), cv2.FILLED)
hpf = fft_shift * hpf_mask  # 마스크를 적용한다. (요소별 곱셈: n x 1 = n, n x 0 = 0)
hpf_spectrum = 20 * np.log(1 + np.abs(hpf))

""" LPF가 적용된 역 고속 푸리에 변환(HPF with IFFT) """
lpf_ishift = np.fft.ifftshift(lpf)  # 셔플링 되었던 것을 역셔플링한다.
lpf_ifft = np.fft.ifft2(lpf_ishift)  # 역 고속 푸리에 변환을 한다.
lpf_ifft = np.abs(lpf_ifft)  # 절대값 적용, 복소수를 실수로 변환

""" HPF가 적용된 역 고속 푸리에 변환(HPF with IFFT) """
hpf_ishift = np.fft.ifftshift(hpf)  # 셔플링 되었던 것을 역셔플링한다.
hpf_ifft = np.fft.ifft2(hpf_ishift)  # 역 고속 푸리에 변환을 한다.
hpf_ifft = np.abs(hpf_ifft)  # 절대값 적용, 복소수를 실수로 변환

# 결과물을 출력한다.
plt_arch(331, 'Input Image', img)
plt_arch(332, 'FFT', spectrum)
plt_arch(333, 'IFFT', ifft)
plt_arch(334, 'IFFT(LPF apply)', lpf_ifft)
plt_arch(335, 'FFT(LPF apply)', lpf_spectrum)
plt_arch(336, 'LPF Mask', lpf_mask)
plt_arch(337, 'IFFT(HPF apply)', hpf_ifft)
plt_arch(338, 'FFT(HPF apply)', hpf_spectrum)
plt_arch(339, 'HPF Mask', hpf_mask)
plt.show()


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

def unimodal_func(x):
    return math.cos(x) * math.tanh(x)

def multymodal_func(x):
    return unimodal_func(x) * math.sin(5 * x)

x_ = np.linspace(-2, 0, 100)
y1 = [unimodal_func(i) for i in x_]
y2 = [multymodal_func(i) for i in x_]

def show_graphic(name, x=None, y=None):
    fig = plt.figure(figsize=(7, 7))
    plt.plot(x, y)
    plt.title(name, fontsize=15)  # заголовок
    plt.xlabel("x", fontsize=14)  # ось абсцисс
    plt.ylabel("y", fontsize=14)  # ось ординат
    plt.grid(True)  # включение отображение сетки
    plt.show()
    fig.savefig(name)

show_graphic("Унимодальная функция", x_, y1)
show_graphic("Мультимодальная функция", x_, y2)

def is_transition(delta, t_i, iteration):
    if delta <= 0:
        iteration.append(1.)
        iteration.append(True)
        return True
    else:
        value = random.uniform(0, 1)
        p = math.exp(-delta / t_i)
        iteration.append(p)
        iteration.append(value <= p)
        return value <= p

def print_data(dataset):
    print(' N      T      x    f(x)      P     Transition')
    for i in dataset:
        print(f'{i[0]}  {i[1]:.3f}   {i[2]:.3f}   {i[3]:.3f}  {i[4]:.3f} {i[5]}')

def annealing(f, t_max, t_min, begin, end):
    t_cur = t_max
    all_iters = []
    counter = 1
    x = random.uniform(begin, end)
    while t_cur > t_min:
        one_iter = [counter, t_cur, x, f(x)]
        new_x = random.uniform(begin, end)
        delta = f(new_x) - f(x)
        if is_transition(delta, t_cur, one_iter):
            x = new_x
        counter += 1
        t_cur *= 0.95
        all_iters.append(one_iter)
    print(f'Result: x_min = {x:.3f} F_min = {f(x):.3f}')
    return all_iters

unimodal_results = annealing(unimodal_func, 10000, 0.01, -2, 0)
print_data(unimodal_results)
print('_________________________________________')
multymodal_results = annealing(multymodal_func, 10000, 0.01, -2, 0)
print_data(multymodal_results)


import numpy as np

def f_ackley(x, a, b, c):
    """
    Define the benchmark Ackley function.

    :param numpy.ndarray x:
        The function's argument array.

    :param float a:
        Function's constant.

    :param float b:
        Function's constant.

    :param float c:
        Function's constant.

    :return:
        The evaluated function at the given input array.
    :rtype: float
    """
    d = len(x)
    f = - a * np.exp(-b * np.sqrt((1.0 / d) * np.sum(x * x))) \
        - np.exp((1.0 / d) * np.sum(np.cos(c * x))) + a + np.exp(1)
    return f

def f_rosenbrock(x):
    """
    Define the benchmark Rosenbrock function.

    :param numpy.ndarray x:
        The function's argument array.

    :return:
        The evaluated function at the given input array.
    :rtype: float
    """
    dim = len(x)
    f = 0.0
    for i in range(dim-1):
        left_term = 100. * (x[i + 1] - x[i] * x[i]) * (x[i + 1] - x[i] * x[i])
        right_term = (1. - x[i]) * (1. - x[i])
        f += left_term + right_term
    return f


import math
import numpy as np

#
# line segment intersection using vectors
# see Computer Graphics by F.S. Hill
#
def segPerp(a) :
    b = np.empty_like(a)
    b[0] = -a[1]
    b[1] = a[0]
    return b

# line segment a given by endpoints a1, a2
# line segment b given by endpoints b1, b2
# return
def seg_intersect(a1,a2, b1,b2):
    da = a2-a1
    db = b2-b1
    dp = a1-b1
    dap = segPerp(da)
    denom = np.dot( dap, db)
    num = np.dot( dap, dp )
    if denom == 0:
        return (False, (0,0))
    return (True, (num / denom.astype(float))*db + b1)

# Find if two lines are more or less orthogonal - can't be too precise as the
# image may be warped by perspective etc
def isOrthogonal(l1, l2):
    ang = (l1["theta"] - l2["theta"] + np.pi * 2) % np.pi
    return abs(ang - np.pi/2) < np.pi/4

# Calculate square of distance from a point to a line (for sorting)
def distSqPtToLineXY(x1, y1, x2, y2, xPt, yPt):
    dx = x2-x1
    dy = y2-y1
    sqdiff = dx*dx + dy*dy
    u = ((xPt - x1) * dx + (yPt - y1) * dy) / float(sqdiff)
    u = 0 if u < 0 else (1 if u > 1 else u)
    x = x1 + u * dx
    y = y1 + u * dy
    ddx = x - xPt
    ddy = y - yPt
    distSq = ddx*ddx + ddy*ddy
    return distSq

def distSqPtToLine(linePt1, linePt2, pt):
    return distSqPtToLineXY(linePt1[0], linePt1[1], linePt2[0], linePt2[1], pt[0], pt[1])

# Find the mid point of a line
def lineMidPt(line):
    return (((line["p1"][0]+line["p2"][0])/2, (line["p1"][1]+line["p2"][1])/2))

# Distance from pt to pt
def distPtToPt(p1, p2):
    dx = p1[0]-p2[0]
    dy = p1[1]-p2[1]
    return math.sqrt(dx*dx+dy*dy)

# Find separation distance of the mid-points of two lines
def lineSeparation(l1, l2):
    mid1 = lineMidPt(l1)
    mid2 = lineMidPt(l2)
#	print("Mids", l1, l2, mid1, mid2)
    return distPtToPt(mid1, mid2)


import numpy as np

X = [[0, 1, 3], [1, 2, 3], [4, 5, 6], [1, 4, 5]]
y = [11, 14, 31, 24]

def fun(x1, x2, x3):
    return x1 + 2 * x2 + 3 * x3

X = np.asmatrix(X)
y = np.asmatrix(y).T

# Normal Equation: (XT * X)**-1  * XT * y
ne = (X.T.dot(X) ** -1) * X.T * y
print(ne)

print(fun(1, 2, 3))
print(1 * ne[0][0] + 2 * ne[1][0] + 3 * ne[2][0])


import sys, getopt
import random
from array import array
import sympy

def gcd(a, b):
    while b != 0:
        a, b = b, a % b
    return a

def multiplicative_inverse(a, b):
    mult_inv = 3

    while (a * mult_inv) % b != 1:
        mult_inv += 1

    return mult_inv

def generate_keypair(p, q):
    if not (sympy.isprime(p) and sympy.isprime(q)):
        raise ValueError('Both numbers must be prime.')
    elif p == q:
        raise ValueError('p and q cannot be equal')
    n = p * q

    phi = (p-1) * (q-1)

    e = sympy.randprime(3, phi)

    g = gcd(e, phi)
    while g != 1:
        e = sympy.randprime(3, phi)
        g = gcd(e, phi)

    d = multiplicative_inverse(e, phi)

    return ((e, n), (d, n))

def encrypt(pk, plaintext):
    key, n = pk
    cipher = [pow(ord(char), key, n) for char in plaintext]
    return cipher

def decrypt(pk, ciphertext):
    key, n = pk
    plain = [chr(pow(char, key, n)) for char in ciphertext]
    return ''.join(plain)

def bytes_from_file(filename, chunksize=8192):
    with open(filename, "rb") as f:
        return f.read()

def writeToBinaryFile(path, text):
    try:
        with open(path, "wb") as file_handler:
            file_handler.write(text)
            return True
    except IOError:
        print("Some error")
        return False

def encrypt_and_write(inputfile, outputfile, key):
    some_str = bytes_from_file(inputfile)
    decode_str = some_str.decode("latin-1")
    encrypted_msg = encrypt(key, decode_str)
    encrypted_msg_str = ','.join([str(x) for x in encrypted_msg])
    encode_result = encrypted_msg_str.encode('latin-1')
    writeToBinaryFile(outputfile, encode_result)

def decrypt_and_write(inputfile, outputfile, key):
    some_str = bytes_from_file(inputfile)
    decode_str = some_str.decode("latin-1").split(',')
    decode_str = [int(x) for x in decode_str]
    encrypted_msg = decrypt(key, decode_str)
    encode_result = encrypted_msg.encode('latin-1')
    writeToBinaryFile(outputfile, encode_result)

def generate_primes(a, b):
    prime1 = prime2 = 0
    while prime1 == prime2:
        prime1 = sympy.randprime(a, b)
        prime2 = sympy.randprime(a, b)

    if prime2 < prime1:
        return prime2, prime1

    return prime1, prime2


# %% [markdown]
# # Histogram equalization
# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/YoniChechik/AI_is_Math/blob/master/c_02a_basic_image_processing/histogram_equalization.ipynb)

# %%
# to run in google colab
import sys
if 'google.colab' in sys.modules:
    import subprocess
    subprocess.call('apt-get install subversion'.split())
    subprocess.call('svn export https://github.com/YoniChechik/AI_is_Math/trunk/c_02a_basic_image_processing/Unequalized_Hawkes_Bay_NZ.jpg'.split())

# %%
import numpy as np
import matplotlib.pyplot as plt
import cv2

figsize = (10,10)
# %% [markdown]
# First, read the image as grayscale

# %%
# read as grayscale
I = cv2.imread("Unequalized_Hawkes_Bay_NZ.jpg",0)

plt.figure(figsize=figsize)
plt.imshow(I, cmap='gray', vmin=0, vmax=255)
plt.title("Original image")
# %% [markdown]
# Let's start by calculating and showing the original histogram
# %%
bins_edges_min_max = [0,256]
num_bins=256
bin_count,bins_edges = np.histogram(I,num_bins,bins_edges_min_max)
bins_start = bins_edges[:-1]
# %%
def draw_hist(x_axis,input):
    fig,ax = plt.subplots(figsize=figsize)
    # why not using plt.hist? because we want to plot also some derivations of this hist, so this is easier
    plt.bar(x_axis, input, width=input.shape[0]/(x_axis[-1]-x_axis[0]+1))
    return fig,ax

draw_hist(bins_start,bin_count)
plt.title("Original histogram")

# %% [markdown]
# Normalize the histogram to gat a discrete PDF
# %%
pdf = bin_count/np.sum(bin_count)

draw_hist(bins_start,pdf)
plt.title("Original PDF")

# %% [markdown]
# Get the CDF by calculating the cumulative sum of the pdf data
# %%
cdf = np.cumsum(pdf)

plt.figure(figsize=figsize)
plt.plot(cdf)
plt.title("Original CDF")

# %%
fig,ax = draw_hist(bins_start,pdf)
ax.plot(cdf*np.max(pdf),'r')
plt.title("Original PDF+ const*CDF to show the connection between the two")

# %% [markdown]
# The final step is to un-normalize the CDF to become the equalization function
# %%
f_eq = np.round(cdf*255).astype(int)

f_eq
# %% [markdown]
# Use the equalization function to get the equalized image
# %%
I_eq = f_eq[I]

plt.figure(figsize=figsize)
plt.imshow(I_eq, cmap='gray', vmin=0, vmax=255)
plt.title("equalized image")
# %% [markdown]
# Plot the equalized histogram, PDF and CDF
# %%
bin_count,bins_edges = np.histogram(I_eq,num_bins,bins_edges_min_max)
bins_start = bins_edges[:-1]

draw_hist(bins_start,bin_count)
plt.title("equalized histogram")
# %%
pdf = bin_count/np.sum(bin_count)
cdf = np.cumsum(pdf)

fig,ax = draw_hist(bins_start,pdf)
ax.plot(cdf*np.max(pdf),'r')
plt.title("equalized PDF and const*CDF")
# %% [markdown]
# ## cv2 histogram equalization function
# %%
I_eq_cv2 = cv2.equalizeHist(I)

plt.figure(figsize=figsize)
plt.imshow(I_eq_cv2, cmap='gray', vmin=0, vmax=255)
plt.title("cv2.equalizeHist() result")

# %%


'''
Classifier        :   Linear Regression with User Defined Algorithm
Dataset           :   Head Brain Dataset
Features          :   Gender, Age, Head Size, Brain Weight
Labels            :   ----
Training Dataset  :   237
Testing Dataset   :   ----
Author            :   Prasad Dangare
Function Name     :   MarvellousHeadBrainPredictor

'''

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

def MarvellousHeadBrainPredictor():

    # Load data
    data = pd.read_csv('HeadBrain.csv')
    print("Size Of Dataset", data.shape)

    X = data['Head Size(cm^3)'].values
    Y = data['Brain Weight(grams)'].values

    # Least Square Method
    mean_x = np.mean(X)
    mean_y = np.mean(Y)

    n = len(X)

    numerator = 0
    denomenator = 0

    # Equation of line is y = mx + c

    for i in range(n):
        numerator += (X[i] - mean_x) * (Y[i] - mean_y)
        denomenator += (X[i] - mean_x) ** 2

    m = numerator / denomenator

    c = mean_y - (m * mean_x)

    print("slope of Regression Line is ", m)
    print("Y Intercept of Regression Line is ", c)

    max_x = np.max(X) + 100
    min_x = np.min(X) - 100

    # Display Plotting Of Above points

    x = np.linspace(min_x, max_x, n)
    y = c + m * x

    plt.plot(x, y, color = '#58b970', label = 'Regression Line')
    plt.scatter(X,Y, color = '#ef5423', label = 'Scatter Plot')

    plt.xlabel('Head size in cm3')
    plt.ylabel('Brain Weight in gram')

    plt.legend()
    plt.show()

    # findout goodness of fit ie R Square

    ss_t = 0
    ss_r = 0

    for i in range(n):
        y_pred = c + m * X[i]
        ss_t += (Y[i] - mean_y) ** 2
        ss_r += (Y[i] - y_pred) ** 2

    r2 = 1 - (ss_r/ss_t)
    print(r2)

def main():

    print("------Supervised Machine Learning------")
    print("Linear Regression on Haead And Brain Size Dataset")

    MarvellousHeadBrainPredictor()

if __name__ == "__main__":
    main()


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

from mpl_toolkits.mplot3d import axes3d
from warmUpExercise import warmUpExercise
from computeCost import computeCost
from gradientDescent import gradientDescent
from plotData import plotData

# Machine Learning Online Class - Exercise 1: Linear Regression

#  Instructions
#  ------------
#
#  This file contains code that helps you get started on the
#  linear exercise. You will need to complete the following modules
#  in this exericse:
#
#     warmUpExercise.py
#     plotData.py
#     gradientDescent.py
#     computeCost.py
#     gradientDescentMulti.py
#     computeCostMulti.py
#     featureNormalize.py
#     normalEqn.py
#
#  For this exercise, you will not need to change any code in this file,
#  or any other files other than those mentioned above.
#
# x refers to the population size in 10,000s
# y refers to the profit in $10,000s

# ==================== Part 1: Basic Function ====================
# Complete warmUpExercise.py
print('\n -------------------------- \n')
print('Running warmUpExercise ...')
print('5x5 Identity Matrix:')
warmup = warmUpExercise()
print(warmup)

# ======================= Part 2: Plotting =======================

# Read data using pandas
path = 'ex1data1.txt'
data = pd.read_csv(path, header=None, names=['Population', 'Profit'])
data.head()

# Résumé des données
data.describe()

# set X (training data) and y (target variable)
nbCol = data.shape[1]
X = data.iloc[:,0:nbCol-1]
y = data.iloc[:,nbCol-1:nbCol]

# convert from data frames to numpy arrays
X = np.array(X.values)
y = np.array(y.values)

# Plot Data
# Note: You have to complete the code in plotData.py
plotData(X,y)

# =================== Part 3: Gradient descent ===================
m = X.shape[0]

# Add intercept term to X
#X = np.concatenate((np.ones((m, 1)), X), axis=1)
X = np.column_stack((np.ones((m, 1)), X)) # works fine too

# initialize theta
theta = np.array([[0.,0.]]).T

# compute and display initial cost
# Note: You have to complete the code in computeCost.py
J = computeCost(X, y, theta)
print('\n -------------------------- \n')
print('cost: %0.4f ' % J)
print('Expected cost value (approx) 32.07')

# further testing of the cost function
J = computeCost(X, y, np.array([[-1, 2]]).T)
print('\n -------------------------- \n')
print('With theta = [-1 ; 2] Cost computed = %f' %J)
print('Expected cost value (approx) 54.24')

# compute Descent gradient
# initialize variables for learning rate and iterations
alpha = 0.01
iters = 1500

# perform gradient descent to "fit" the model parameters
# Note: You have to complete the code in gradientDescent.py
theta, cost_history, theta_history = gradientDescent(X, y, theta, alpha, iters)

# print theta to screen
print('\n -------------------------- \n')
print('Theta found by gradient descent: ')
print('%s %s' % (theta[0,0], theta[1,0]))
print('Expected theta values (approx)')
print(' -3.6303  1.1664')

# Checking the convergence
# Evolution du coût
fig= plt.figure(figsize=(12,8))
ax = plt.gca()
ax.plot(np.arange(iters), cost_history, color="blue", linewidth=2.0, linestyle="-")
ax.set_xlabel('iteration number')
ax.set_ylabel(r'Cost J($\theta$)')
ax.set_title('Error vs. Training Epoch (number of iters)')
ax.grid()
ax.set_xlim([-20,1600])
ax.set_ylim([4,7])

# Checking the goodness-of-fit
# Fit: calcul de la droite de régression
x = np.linspace(data.Population.min(), data.Population.max(), 100)
f = theta[0, 0] + (theta[1, 0] * x)

# Plot the linear fit
fig = plt.figure(figsize=(12,8))
ax = plt.gca()
ax.plot(x, f, 'r', label='Linear regression: h(x) = %0.2f + %0.2fx'%(theta[0,0],theta[1,0]))
ax.scatter(data.Population, data.Profit, label='Training Data')
ax.legend(loc=2)
ax.set_xlabel('Population')
ax.set_ylabel('Profit')
ax.set_title('Predicted Profit vs. Population Size')
ax.grid()
fig.show()

# Predict values for population sizes of 35,000 and 70,000
predict1 = np.array([[1, 3.5]]).dot(theta)
predict2 = np.array([[1, 7]]).dot(theta)
#predict1 = np.array([[1, 3.5]])@theta
#predict2 = np.array([[1, 7]])@theta

print('\n -------------------------- \n')
print('For population = 35,000, we predict a profit of {:.4f}'.format(predict1[0,0]*10000))
print('For population = 70,000, we predict a profit of {:.4f}'.format(predict2[0,0]*10000))

# ============= Part 4: Visualizing J(theta_0, theta_1) =============
print('\n -------------------------- \n')
print('Visualizing J(theta_0, theta_1) ...')

# Create grid coordinates for plotting
theta0 = np.linspace(-10, 10, 100)
theta1 = np.linspace(-1, 4, 100)
theta0, theta1 = np.meshgrid(theta0, theta1, indexing='xy')

Z = np.zeros((theta0.shape[0],theta1.shape[0]))

# Calculate Z-values (Cost) based on grid of coefficients
for (i,j),v in np.ndenumerate(Z):
    t = np.array([[theta0[i,j], theta1[i,j]]]).T
    Z[i,j] = computeCost(X,y, t)

fig = plt.figure(figsize=(15,6))
ax1 = fig.add_subplot(121)
ax2 = fig.add_subplot(122, projection='3d')

# Left plot
CS = ax1.contour(theta0, theta1, Z, np.geomspace(Z.min(),Z.max(),10), cmap=plt.cm.jet)
plt.clabel(CS, inline=1, fontsize=10)
ax1.scatter(theta_history[0,:],theta_history[1,:], c='r')
ax1.grid()

# Right plot
ax2.plot_surface(theta0, theta1, Z, rstride=1, cstride=1, alpha=0.6, cmap=plt.cm.jet, linewidth=0, antialiased=True)
ax2.set_zlabel('Cost')
ax2.set_zlim(Z.min(),Z.max())
ax2.view_init(elev=15, azim=230)
ax2.grid()

# settings common to both plots
for ax in fig.axes:
    ax.set_xlabel(r'$\theta_0$', fontsize=17)
    ax.set_ylabel(r'$\theta_1$', fontsize=17)

plt.show()


"""
Binning
-------

"""

import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import mglearn
from IPython.display import display
from sklearn.linear_model import LinearRegression
from sklearn.tree import DecisionTreeRegressor
from sklearn.preprocessing import OneHotEncoder

X, y = mglearn.datasets.make_wave(n_samples=100)
line = np.linspace(-3, 3, 1000, endpoint=False).reshape(-1, 1)
reg = DecisionTreeRegressor(min_samples_split=3).fit(X, y)
plt.plot(line, reg.predict(line), label="Decision tree")
reg = LinearRegression().fit(X, y)
plt.plot(line, reg.predict(line), label="Linear regression")
plt.plot(X[:, 0], y, 'o', c='k')
plt.ylabel('Regression output')
plt.xlabel('Input feature')
plt.legend(loc='Best')
plt.show()
# Comparing decision tree with linear regresssion
# Decision trees can model the data much more closely

bins = np.linspace(-3, 3, 11)
print 'bins: {}'.format(bins)
# Separate the linear space into 10 bins where
# the feature values range from -3 to -2.4, -2.4 to -1.8, etc

which_bin = np.digitize(X, bins=bins)
print '\nData points:\n{}'.format(X[:5])
print '\nBin membership:\n{}'.format(which_bin[:5])
# np.digitize encoded the continuous input feature
# into a discrete feature representing which bin the
# each point belongs to

encoder = OneHotEncoder(sparse=False).fit(which_bin)
X_binned = encoder.transform(which_bin)
print X_binned[:5]
print 'X_binned.shape: {}'.format(X_binned.shape)
# One hot encoder does the same thing as pandas.get_dummies (see categoricalvars.py)
# except it only works on integer categorical variables
# It separates the categorical feature into multiple binary features

line_binned = encoder.transform(np.digitize(line, bins=bins))
reg = LinearRegression().fit(X_binned, y)
plt.plot(line, reg.predict(line_binned), label='Linear regression binned')
reg = DecisionTreeRegressor(min_samples_split=3).fit(X_binned, y)
plt.plot(line, reg.predict(line_binned), label='Decision tree binned')
plt.plot(X[:,0], y, 'o', c='k')
plt.vlines(bins, -3, 3, linewidth=1, alpha=.2)
plt.legend(loc='best')
plt.ylabel('Regression output')
plt.xlabel('Input feature')
plt.show()
# Now the two lines are right on top of each other
# Binning data seems to help linear models make better predictions
# but it made the decision tree perform worse
# Decision trees in general can learn what bins to put data in
# based on multiple features

X_combined = np.hstack([X, X_binned])
print X_combined.shape
# One way to improve the linear model after binning is to add
# slopes back to the bins using np.hstack

reg = LinearRegression().fit(X_combined, y)
line_combined = np.hstack([line, line_binned])
plt.plot(line, reg.predict(line_combined), label='Linear regression combined')
for b in bins:
  plt.plot([b, b], [-3, 3], ':', c='k', linewidth=1)
plt.legend(loc='best')
plt.ylabel('Regression output')
plt.xlabel('Input feature')
plt.plot(X[:,0], y, 'o', c='k')
plt.show()
# The linear regression learned to separate the
# data into each bin and it learned a shared slope
# across all bins. This is less preferable than
# finding a separate slope across all bins

X_product = np.hstack([X_binned, X * X_binned])
print X_product.shape
# We can achieve separate slopes for each bin by
# taking a product of each bin indicator with
# the original input feature
# Each point has 20 features now, 10 indicate
# which bin its in and the other 10 are the corresponding
# products with the original input feature

reg = LinearRegression().fit(X_product, y)
line_product = np.hstack([line_binned, line * line_binned])
plt.plot(line, reg.predict(line_product), label='linear regression product')
for b in bins:
  plt.plot([b, b], [-3, 3], ':', c='k', linewidth=1)
plt.plot(X[:, 0], y, 'o', c='k')
plt.ylabel('Regression output')
plt.xlabel('Input feature')
plt.legend(loc='best')
plt.show()
# This time, each bin has its own slope


import numpy as np
import sys

class PartyNN(object):

    def __init__(self, learning_rate=0.1):
        self.weights_0_1 = np.random.normal(0.0, 2 ** -0.5, (2, 3))
        self.weights_1_2 = np.random.normal(0.0, 1, (1, 2))
        self.signoid_mapper = np.vectorize(self.sigmoid)
        self.learning_rate = np.array([learning_rate])

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

    def predict(self, inputs):
        inputs_1 = np.dot(self.weights_0_1, inputs)
        outputs_1 = self.signoid_mapper(inputs_1)

        inputs_2 = np.dot(self.weights_1_2, outputs_1)
        outputs_2 = self.signoid_mapper(inputs_2)
        return outputs_2

    def train(self, inputs, expected_predict):
        inputs_1 = np.dot(self.weights_0_1, inputs)
        outputs_1 = self.signoid_mapper(inputs_1)

        inputs_2 = np.dot(self.weights_1_2, outputs_1)
        outputs_2 = self.signoid_mapper(inputs_2)
        actual_predict = outputs_2[0]

        error_layer_2 = np.array([actual_predict - expected_predict])
        gradient_layer_2 = actual_predict * (1 - actual_predict)
        weights_delta_layer_2 = error_layer_2 * gradient_layer_2
        self.weights_1_2 -= (np.dot(weights_delta_layer_2,
                                    outputs_1.reshape(1, len(outputs_1)))) * self.learning_rate

        error_layer_1 = weights_delta_layer_2 * self.weights_1_2
        gradient_layer_1 = outputs_1 * (1 - outputs_1)
        weights_delta_layer_1 = error_layer_1 * gradient_layer_1
        self.weights_0_1 -= np.dot(inputs.reshape(len(inputs), 1),
                                   weights_delta_layer_1).T * self.learning_rate

def MSE(y, Y):
    return np.mean((y-Y)**2)

train = [
    ([0, 0, 0], 0),
    ([0, 0, 1], 1),
    ([0, 1, 0], 0),
    ([0, 1, 1], 0),
    ([1, 0, 0], 1),
    ([1, 0, 1], 1),
    ([1, 1, 0], 0),
    ([1, 1, 1], 1),
]

epochs = 5000
learning_rate = 0.05

network = PartyNN(learning_rate=learning_rate)

for e in range(epochs):
    inputs_ = []
    correct_predictions = []
    for input_stat, correct_predict in train:
        network.train(np.array(input_stat), correct_predict)
        inputs_.append(np.array(input_stat))
        correct_predictions.append(np.array(correct_predict))

    train_loss = MSE(network.predict(np.array(inputs_).T),
                     np.array(correct_predictions))
    sys.stdout.write("\rProgress: {}, Training loss: {}".format(
        str(100 * e/float(epochs))[:4], str(train_loss)[:5]))

for input_stat, correct_predict in train:
    print("For input: {} the prediction is: {}, expected: {}".format(
        str(input_stat),
        str(network.predict(np.array(input_stat)) > .5),
        str(correct_predict == 1)
    ))


def f(x):
    ret = x
    for _ in range(1, 4):
        ret = 4 * ret * (1 - ret)
    return ret

# Symbolic
import sympy  # noqa: E402

symbolic_x = sympy.symbols("x")
symbolic_f = f(symbolic_x)
symbolic_fprime = symbolic_f.diff(symbolic_x)
print(symbolic_fprime)
print(f"f'(x=2): {symbolic_fprime.subs(symbolic_x, 2)}")

# Nuermic
def df_numeric(x, delta=1e-8):
    return (f(x + delta) - f(x)) / delta

print(f"f'(x=2): {df_numeric(x=2)}")

# Automatic
import jax  # noqa: E402

print(f"f'(x=2): {jax.grad(f)(2.0)}")
print(f"f''(x=2): {jax.grad(jax.grad(f))(2.0)}")
print(f"f'''(x=2): {jax.grad(jax.grad(jax.grad(f)))(2.0)}")


import numpy as np
import math

def sum_squared_error( outputs, targets, derivative=False ):
    if derivative:
        return outputs - targets
    else:
        return 0.5 * np.sum( np.power(outputs - targets,2) )
#end cost function

def hellinger_distance( outputs, targets, derivative=False ):
    """
    The output signals should be in the range [0, 1]
    """
    root_difference = np.sqrt( outputs ) - np.sqrt( targets )

    if derivative:
        return root_difference/( np.sqrt(2) * np.sqrt( outputs ))
    else:
        return np.sum( np.power(root_difference, 2 )) / math.sqrt( 2 )
#end cost function

def cross_entropy_cost( outputs, targets, derivative=False ):
    """
    The output signals should be in the range [0, 1]
    """
    if derivative:
        return (outputs - targets) / (outputs * (1 - outputs))
    else:
        return np.mean(-np.sum(targets * np.log( outputs ) + (1 - targets) * np.log(1 - outputs), axis=1))
#end cost function

def softmax_cross_entropy_cost( outputs, targets, derivative=False ):
    """
    The output signals should be in the range [0, 1]
    """
    if derivative:
        return outputs - targets
    else:
        return np.mean(-np.sum(targets * np.log( outputs ), axis=1))
#end cost function

def exponential_cost( outputs, targets, derivative=False, tau = 2.0 ):
    diff = outputs - targets
    cost = tau * np.exp( np.sum( diff**2 ) / tau )

    if derivative:
        return 2/tau * diff * cost

    return np.sum(cost) - tau
#end cost function


import numpy as np
from bisect import bisect_right

def list_prime(target: int) -> np.ndarray:
    smallest_odd_multiple_of_n_geq_m: int = lambda n, m : m + (n-m) % (2*n)
    primes = np.array([2, 3, 5, 7], dtype=np.uint64)
    index_segment_max = 3
    extend = 7
    segment_min, segment_max = 9, 49

    while segment_max < target:
        is_prime = np.full(shape=(segment_max-segment_min)//2, fill_value=True, dtype=bool)
        for p in primes[1:index_segment_max]:
            p = int(p)
            is_prime[(smallest_odd_multiple_of_n_geq_m(p, segment_min) - segment_min) // 2 :: p] = False
        new_primes = np.arange(segment_min, segment_max, 2, dtype=np.uint64)[is_prime]
        old_len = len(primes)
        primes.resize(old_len + len(new_primes))
        primes[old_len:] = new_primes
        index_segment_max += extend
        segment_min, segment_max = segment_max, int(primes[index_segment_max]) ** 2

    is_prime = np.full(shape=(target-segment_min+3)//2, fill_value=True, dtype=bool)
    for p in primes[1:bisect_right(primes, target**.5)]:
        p = int(p)
        is_prime[(smallest_odd_multiple_of_n_geq_m(p, segment_min) - segment_min) // 2 :: p] = False
    new_primes = np.arange(segment_min, target+2, 2, dtype=np.uint64)[is_prime]
    old_len = len(primes)
    primes.resize(old_len + len(new_primes))
    primes[old_len:] = new_primes

    return primes

def main():
    from time import time
    target = 10**9
    start = time()
    out = list_prime(target)
    elapsed = time() - start
    print(len(out))
    print('Done in: ', elapsed, 'seconds')

if __name__ == '__main__':
    main()


""" ReLU Layer """

import numpy as np
def Relu(x):
	y=np.copy(x)
	y[x<0]=0
	return y
def Relu_deriv(x):
	y=np.copy(x)
	y[x<0]=0
	y[x>0]=1
	return y
class ReLULayer():
	def __init__(self):
		"""
		Applies the rectified linear unit function element-wise: relu(x) = max(x, 0)
		"""
		self.trainable = False # no parameters

	def forward(self, Input, **kwargs):

		############################################################################
	    # TODO: Put your code here
		# Apply ReLU activation function to Input, and return results.
		self.Input=Input
		return Relu(Input)
	    ############################################################################

	def backward(self, delta):

		############################################################################
	    # TODO: Put your code here
		# Calculate the gradient using the later layer's gradient: delta

		return np.multiply(delta,Relu_deriv(self.Input))
	    ############################################################################


import numpy as np

def binomialCoefficient(n, k):
    if k < 0 or k > n:   return 0
    if k == 0 or k == n: return 1

    k = min(k, n - k)  # Take advantage of geometry

    k_range = np.arange(k)
    c = np.prod((n - k_range) / (k_range + 1))

    return c

def bernstein(i, n, t):
    return binomialCoefficient(n, i) * (t**i) * ((1 - t)**(n - i))

def catmull(ps, t):
    # https://github.com/ppy/osu-framework/blob/050a0b8639c9bd723100288a53923547ce87d487/osu.Framework/Utils/PathApproximator.cs#L449
    t2 = t * t
    t3 = t2 * t
    return 0.5 * (
        2 * ps[1]
        + (ps[2] - ps[0]) * t
        + (2 * ps[0] - 5 * ps[1] + 4 * ps[2] - ps[3]) * t2
        + (ps[3] - 3 * ps[2] + 3 * ps[1] - ps[0]) * t3
    )

def bound(min_val, max_val, value):
    return min(max(value, min_val), max_val)

def frange(start, stop, step):
    curr = start
    while curr < stop:
        yield curr
        curr += step

def binary_search(values, value):
    low = 0
    high = len(values)
    while low < high:
        mid = (low + high) // 2
        if values[mid] < value:
            low = mid + 1
        elif values[mid] > value:
            high = mid
        else:
            return mid
    return low

def value_to_percent(min_val, max_val, value):
    return 1.0 - ((max_val - bound(min_val, value, max_val)) / (max_val - min_val))

# Returns a triangle wave function
def triangle(val, amplitude):
    return np.abs((np.fmod(val + (amplitude / 2.0), amplitude)) - (amplitude / 2.0))

# Linear interpolation
# Percent: 0.0 -> 1.0
def lerp(low, high, percent):
    return low * (1.0 - percent) + high*percent

def dist(a, b):
    dx = a[0] - b[0]
    dy = a[1] - b[1]
    return (dx*dx + dy*dy)**0.5

def intersect(a, ta, b, tb, precision):
    des = tb[0]*ta[1] - tb[1]*ta[0]
    if abs(des) < precision: return None

    u = ((b[1] - a[1])*ta[0] + (a[0] - b[0])*ta[1]) / des
    return b + tb*u


# John Mourad (jmm5kg)
# Canny Edge Detector
# Import some libraries
import numpy, skimage.io, pylab, math, sys, skimage.color
import scipy.ndimage.filters, scipy.signal, scipy.misc

def canny_edges(A):
    # Convert image to float
    A = skimage.img_as_float(A)
    A = skimage.color.rgb2gray(A)

    # Build Gaussian filter
    gauss_dim = 15
    gauss_row = scipy.signal.gaussian(gauss_dim, 1)
    gauss_filter = numpy.array([[gauss_row[x] * gauss_row[y] for x in range(gauss_dim)] for y in range(gauss_dim)])

    # Convolve our image with the Gaussian filter
    C = scipy.ndimage.filters.convolve(A, gauss_filter)

    # Compute the gradient
    gradient = numpy.gradient(C)
    x_gradient = gradient[0]
    y_gradient = gradient[1]

    height = gradient[0].shape[0]
    width = gradient[0].shape[1]

    # Initialize magnitude and direction arrays
    magnitudes = numpy.array([[0.0 for w in range(width)] for h in range(height)])
    directions = numpy.array([[0.0 for w in range(width)] for h in range(height)])

    # PI constant
    pi = math.pi

    # Compute magnitude and direction of gradient
    for i in range(height):
        for j in range(width):
            mag = math.sqrt((x_gradient[i][j] ** 2) + (y_gradient[i][j] ** 2))
            direction = math.atan2(y_gradient[i][j], x_gradient[i][j])
            if direction < 0:
                direction = direction + pi
            magnitudes[i][j] = mag
            directions[i][j] = direction

    # Categorize direction of gradient
    for i in range(height):
        for j in range(width):
            if 0 <= directions[i][j] < (pi / 8):
                directions[i][j] = 0
            if (pi / 8) <= directions[i][j] < (3 * pi / 8):
                directions[i][j] = (pi / 4)
            if (3 * pi / 8) <= directions[i][j] < (5 * pi / 8):
                directions[i][j] = (pi / 2)
            if (5 * pi / 8) <= directions[i][j] < (7 * pi / 8):
                directions[i][j] = (3 * pi / 4)
            if (7 * pi / 8) <= directions[i][j] <= pi:
                directions[i][j] = pi

    # Create copy of magnitudes for thinned image
    thinned_image = numpy.copy(magnitudes)

    # Non-maximum suppression
    for i in range(1, height - 1):
        for j in range(1, width - 1):
            if directions[i][j] == 0 or directions[i][j] == pi:
                if magnitudes[i][j] < magnitudes[i - 1][j] or magnitudes[i][j] < magnitudes[i + 1][j]:
                    thinned_image[i][j] = 0
            if directions[i][j] == (pi / 4):
                if magnitudes[i][j] < magnitudes[i - 1][j - 1] or magnitudes[i][j] < magnitudes[i + 1][j + 1]:
                    thinned_image[i][j] = 0
            if directions[i][j] == (pi / 2):
                if magnitudes[i][j] < magnitudes[i][j - 1] or magnitudes[i][j] < magnitudes[i][j + 1]:
                    thinned_image[i][j] = 0
            if directions[i][j] == (3 * pi / 4):
                if magnitudes[i][j] < magnitudes[i - 1][j + 1] or magnitudes[i][j] < magnitudes[i + 1][j - 1]:
                    thinned_image[i][j] = 0

    # Establish high and low thresholds
    # flower.jpg thresholds
    t_low = 0.1
    t_high = 0.5

    # building.jpg thresholds
    # t_low = 0.2
    # t_high = 0.5

    # Initialize status array
    status = numpy.array([["" for w in range(width)] for h in range(height)])

    # Initialize array to hold strong edge pixels
    strong = []

    # Hysteresis thresholding
    for i in range(height):
        for j in range(width):
            if thinned_image[i][j] < t_low:
                status[i][j] = "N"  # not strong edge
                thinned_image[i][j] = 0
            if thinned_image[i][j] > t_high:
                status[i][j] = "S"  # strong edge
                thinned_image[i][j] = 1
                strong.append((i, j))
            if t_low <= thinned_image[i][j] <= t_high:
                status[i][j] = "W"  # weak edge

    # Iterative DFS for connected neighbor algorithm
    visited = {}

    while len(strong) > 0:
        v = strong.pop()  # pop from stack

        if v not in visited:
            visited[v] = 1  # mark as visited

            # if a weak edge, mark as strong
            if status[v[0]][v[1]] == "W":
                status[v[0]][v[1]] = "S"
                thinned_image[v[0]][v[1]] = 1

            # visit bottom row
            if 0 <= v[0] + 1 < height and 0 <= v[1] + 1 < width:
                if (v[0] + 1, v[1] + 1) not in visited:
                    strong.append((v[0] + 1, v[1] + 1))

            if 0 <= v[0] + 1 < height and 0 <= v[1] < width:
                if (v[0] + 1, v[1]) not in visited:
                    strong.append((v[0] + 1, v[1]))

            if 0 <= v[0] + 1 < height and 0 <= v[1] - 1 < width:
                if (v[0] + 1, v[1] - 1) not in visited:
                    strong.append((v[0] + 1, v[1] - 1))

            # visit middle row
            if 0 <= v[0] < height and 0 <= v[1] + 1 < width:
                if (v[0], v[1] + 1) not in visited:
                    strong.append((v[0], v[1] + 1))

            if 0 <= v[0] < height and 0 <= v[1] - 1 < width:
                if (v[0], v[1] - 1) not in visited:
                    strong.append((v[0], v[1] - 1))

            # visit top row
            if 0 <= v[0] - 1 < height and 0 <= v[1] + 1 < width:
                if (v[0] - 1, v[1] + 1) not in visited:
                    strong.append((v[0] - 1, v[1] + 1))

            if 0 <= v[0] - 1 < height and 0 <= v[1] < width:
                if (v[0] - 1, v[1]) not in visited:
                    strong.append((v[0] - 1, v[1]))

            if 0 <= v[0] - 1 < height and 0 <= v[1] - 1 < width:
                if (v[0] - 1, v[1] - 1) not in visited:
                    strong.append((v[0] - 1, v[1] - 1))

    # Mark any remaining weak edges as no edges
    for i in range(height):
        for j in range(width):
            if status[i][j] == "W":
                status[i][j] = "N"
                thinned_image[i][j] = 0

    return thinned_image

if __name__ == "__main__":
    img_file = sys.argv[1]
    save_file = sys.argv[2]

    #  Load an image
    A = skimage.io.imread(img_file, True)

    thinned_image = canny_edges(A)
    # Show the result
    # pylab.imshow(thinned_image, cmap="gray")
    # pylab.show()

    scipy.misc.imsave(save_file, thinned_image)