import numpy as np

def SGD(w, dw, lr, *wargs):
	# Stochastic Gradient descent
    w -= dw * lr
    return w, None

def Rmsprop(x, dx, lr, config):
    """
    Uses the RMSProp update rule, which uses a moving average of squared
    gradient values to set adaptive per-parameter learning rates.
    config format:
    - decay_rate: Scalar between 0 and 1 giving the decay rate for the squared
    - cache: Moving average of second moments of gradients.
    """
    if config is 0: config = {}
    config.setdefault('cache', np.zeros_like(x))
    cache = config['cache']
    decay_rate = 0.9

    cache = decay_rate * cache + (1 - decay_rate) * dx**2
    next_x = x - lr * dx / (np.sqrt(cache) + 1e-8)

    config['cache'] = cache
    return next_x, config

def Momentum(w, dw, lr, config):
    """
    Performs stochastic gradient descent with momentum.
    config format:
    - momentum: Scalar between 0 and 1 giving the momentum value.
    """
    if config is 0: config = {}
    config.setdefault('v', np.zeros_like(w))

    v = .9 * config['v'] - lr * dw
    w += v

    config['v'] = v
    return w, config

def Nestrov_momentum(w, dw, lr, config):
    """ or nag
    Performs stochastic gradient descent with momentum.
    config format:
    - momentum: Scalar between 0 and 1 giving the momentum value.
    """
    if config is 0: config = {}
    config.setdefault('v', np.zeros_like(w))
    mu = .9
    v_pred = config['v']

    v = mu * config['v'] - lr * dw
    w += -mu * v_pred + (1+mu) * v
    config['v'] = v

    return w, config

def Adam(x, dx, lr, config):
    """ config format:
    - beta1: Decay rate for moving average of first moment of gradient.
    - beta2: Decay rate for moving average of second moment of gradient.
    - m: Moving average of gradient.
    - v: Moving average of squared gradient.
    - t: Iteration number. """

    if config is 0: config={}
    config.setdefault('t', 1)
    config.setdefault('m', np.zeros_like(x))
    config.setdefault('v', np.zeros_like(x))

    # read params from dictionary
    beta1, beta2 = .9, .999
    m, v, t = config['m'], config['v'], config['t']

    t += 1
    # apply adam update rule
    m =  beta1 * m + (1 - beta1) * dx
    v =  beta2 * v + (1 - beta2) * dx ** 2
    mb = m / (1 - beta1 ** t)
    vb = v / (1 - beta2 ** t)
    next_x = x - lr * mb / (np.sqrt(vb) + 1e-8)

    # store new params in the dictionary
    config['m'], config['v'] = m, v
    return next_x, config


from typing import Optional, Sequence

import numpy as np
from scipy.signal import convolve, filtfilt, iirnotch
from statsmodels.nonparametric.smoothers_lowess import lowess as sm_lowess

# supported kernels for convolution smoothing
kernels = {
    "hanning"  : np.hanning,
    "hamming"  : np.hamming,
    "bartlett" : np.bartlett,
    "blackman" : np.blackman,
    "uniform"  : np.ones
}

def notched_smoothing(window: int = 7):
    """ Removes weekly and twice-weekly periodicity before convolving a time-reversed padded signal with a uniform moving average window"""
    fs, f0, Q = 1, 1/7, 1
    b1, a1 = iirnotch(f0, Q, fs)
    b2, a2 = iirnotch(2*f0, 2*Q, fs)
    # Frequency response
    b = convolve(b1, b2)
    a = convolve(a1, a2)
    kernel = np.ones(window)/window
    def smooth(data: Sequence[float]):
        notched = filtfilt(b, a, data)
        return convolve(np.concatenate([notched, notched[:-window-1:-1]]), kernel, mode="same")[:-window]
    return smooth

def notch_filter():
    """ implements a notch filter with notches at 1/week and 2/week; assuming input signal sampling rate is 1/week"""
    fs, f0, Q = 1, 1/7, 1
    b1, a1 = iirnotch(f0, Q, fs)
    b2, a2 = iirnotch(2*f0, 2*Q, fs)
    # Frequency response
    b = convolve(b1, b2)
    a = convolve(a1, a2)
    def filter_(data: Sequence[float]):
        return filtfilt(b, a, data)
    return filter_

def convolution(key: str = "hamming", window: int = 7):
    """ entry point for all convolution operations """
    kernel = kernels[key](window)
    def smooth(data: Sequence[float]):
        # pad the data with time reversal windows of signal at ends since all kernels here are apodizing
        padded = np.r_[data[window-1:0:-1], data, data[-2:-window-1:-1]]
        return np.convolve(kernel/kernel.sum(), padded, mode="valid")[:-window+1]
    return smooth

def box_filter_local(window: int = 5, local_smoothing: Optional[int] = 3):
    """ implement a box filter smoother with additional LOWESS-like smoothing for data points at the end of the timeseries"""
    def smooth(data: Sequence[float]):
        smoothed = np.convolve(data, np.ones(window)/window, mode='same')
        if local_smoothing and len(data) > (local_smoothing + 1):
            for i in range(local_smoothing-1, 0, -1):
                smoothed[-i] = np.mean(data[-i-local_smoothing+1: -i+1 if i > 1 else None])
        return smoothed
    return smooth

def lowess(**kwargs):
    """ wrapper over statsmodels lowess implementation to return a callable """
    return lambda data: sm_lowess(data, list(range(len(data))), **kwargs)

import numpy,from scipy,from statsmodels


import numpy as np
import matplotlib.pyplot as plt
import sympy as sym
from matplotlib import cm

def grid(a, T, N, M):
    h = a / N
    tau = T / M
    x_p = np.array([i * h for i in range(N + 1)])
    t_p = np.array([i * tau for i in range(N + 1)])
    return x_p, t_p

def conditions(u, a, x, t, kappa):
    f = sym.diff(u, t, 1) - kappa * sym.diff(u, x, 2)
    mu = u.subs(t, 0)
    mu1 = u.subs(x, 0)
    mu2 = u.subs(x, a)
    return f, mu, mu1, mu2

def explicit_scheme(a, T, kappa, N, M, x, t, f, mu, mu1, mu2):
    x_p, t_p = grid(a, T, N, M)
    u_p = np.zeros([N + 1, M + 1], dtype=float)
    h = a / N
    tau = T / M
    for k in range(1, M + 1):
        for i in range(0, N + 1):
            u_p[i, 0] = mu.subs(x, x_p[i])
        for i in range(1, N):
            u_p[i, k] = tau * kappa * (u_p[i + 1, k - 1] - 2 * u_p[i, k - 1] + u_p[i - 1, k - 1]) / (h ** 2) + \
                        tau * f.subs([(x, x_p[i]), (t, t_p[k - 1])]) + u_p[i, k - 1]
        u_p[0, k] = mu1.subs(t, t_p[k])
        u_p[N, k] = mu2.subs(t, t_p[k])
    X, T = np.meshgrid(x_p, t_p, indexing='ij')
    return X, T, u_p

def implicit_scheme(a, T, kappa, N, M, x, t, f, mu, mu1, mu2):
    x_p, t_p = grid(a, T, N, M)
    u_p = np.zeros((N + 1, M + 1), dtype=float)
    A, C = [np.zeros(N + 1, dtype=float) for _ in range(2)]
    B = np.ones(N + 1, dtype=float)
    D = np.zeros(N + 1, dtype=sym.Symbol)
    h, tau = a / N, T / M
    for k in range(1, M + 1):
        D[0] = mu1.subs(t, t_p[k])
        D[N] = mu2.subs(t, t_p[k])
        for i in range(0, N + 1):
            u_p[i, 0] = mu.subs(x, x_p[i])
        for i in range(1, N):
            A[i] = kappa / h ** 2
            B[i] = -2 * kappa / h ** 2 - 1 / tau
            C[i] = kappa / h ** 2
            D[i] = -u_p[i, k - 1] / tau - f.subs([(x, x_p[i]), (t, t_p[k])])
        s = np.zeros(N + 1, dtype=float)
        t1 = np.zeros(N + 1, dtype=float)
        s[0] = -C[0] / B[0]
        t1[0] = D[0] / B[0]
        for i in range(1, N + 1):
            s[i] = -C[i] / (A[i] * s[i - 1] + B[i])
            t1[i] = (D[i] - A[i] * t1[i - 1]) / (A[i] * s[i - 1] + B[i])
        u_p[N, k] = t1[N]
        for i in range(N - 1, -1, -1):
            u_p[i, k] = s[i] * u_p[i + 1, k] + t1[i]
    X, T = np.meshgrid(x_p, t_p, indexing='ij')
    return X, T, u_p

x = sym.symbols('x')
t = sym.symbols('t')

functions = [
    x ** 2 / 4 + t ** 2 / 4,
    t * x,
    x * t ** 3 - 2 * x + 25 - x ** 5
]

a = 10
T = 10
N = 100
M = 100

fig = plt.figure()

for i in range(3):
    kappa = 0.001
    f, mu, mu1, mu2 = conditions(functions[i], a, x, t, kappa)

    ax1 = plt.axes(projection='3d')
    ax1.set_title('Kappa = 0.001')
    x_coord, t_coord, result = explicit_scheme(a, T, kappa, N, M, x, t, f, mu, mu1, mu2)
    surf = ax1.plot_surface(x_coord, t_coord, result, cmap=cm.hot, linewidth=0, antialiased=False)
    fig.colorbar(surf, shrink=0.5, aspect=5)
    fig.savefig(str(i + 1) + '.1.png')
    plt.clf()

    ax2 = plt.axes(projection='3d')
    ax2.set_title('Kappa = 0.001')
    x_coord, t_coord, result = implicit_scheme(a, T, kappa, N, M, x, t, f, mu, mu1, mu2)
    surf = ax2.plot_surface(x_coord, t_coord, result, cmap=cm.hot, linewidth=0, antialiased=False)
    fig.colorbar(surf, shrink=0.5, aspect=5)
    fig.savefig(str(i + 1) + '.2.png')
    plt.clf()

    kappa = 0.1

    ax3 = plt.axes(projection='3d')
    ax3.set_title('Kappa = 0.1')
    x_coord, t_coord, result = explicit_scheme(a, T, kappa, N, M, x, t, f, mu, mu1, mu2)
    surf = ax3.plot_surface(x_coord, t_coord, result, cmap=cm.hot, linewidth=0, antialiased=False)
    fig.colorbar(surf, shrink=0.5, aspect=5)
    fig.savefig(str(i + 1) + '.3.png')


import numpy as np
from random import randint
import networkx as nx
import matplotlib.pyplot as plt

def draw_graph(relation,color): #to visualize the graph
    G=nx.Graph()
    G.add_edges_from(relation)
    co = [color[i] for i in G.nodes()]
    nx.draw(G, node_size=1000/(len(relation)/10),node_color=co,
            with_labels=True,font_color='white')
    plt.show()

def generateRandomGraph(nodes,a,b): #to generate random graph
    L = np.zeros((node,node),dtype=int)
    for i in range(nodes):
        for j in range(nodes):
            if i != j and L[i][j] == 0:
                v = int(np.random.choice(2, 1,p=[a,b]))
                if v == 1 :
                    L[i][j] = v
                    L[j][i] = v
    return L
#-------------------------- main program begin -------------------------
def isSafe(co,list_color,n):
    for i in range(node-1):
        if adj_mtx[n][i] == 1 and list_color[i] == co:
            return False
    return True

def findSolution(list_color,n):
    if(n == node):
        return True
    for co in range(1,mc+1):
        if(isSafe(co,list_color,n)):
            list_color[n] = co
            if findSolution(list_color,n+1):
                return True
            list_color[n] = 0
    return False

def graphColoring(mtx,mc):
    list_color = np.zeros(node,dtype=int)
    print('>> Maximum color :', mc)
    if not(findSolution(list_color,0)):
        print('>> Solution not found')
        return False
    print('>> Solution Found :',list_color)
    print('>> Minimum color needed :', max(list_color))
    draw_graph(relation,list_color)

#-------------------------- inti program ends ------------------------------

node = int(input("Generate Random Graph, numebr of nodes : "))
#the greater the probability, the more edges
p = float(input("probabilitas terbentuk koneksi (0.1 - 0.9): "))  #auto matically generates an edges with probabilities
adj_mtx = generateRandomGraph(node,1-p,p)
print(adj_mtx)
#find relation
relation = []
for i in range(len(adj_mtx)):
    for j in range(len(adj_mtx[i])):
        if adj_mtx[i][j] == 1 and (i,j) not in relation and (j,i) not in relation:
            relation.append((i,j))

mc = node #max color
graphColoring(adj_mtx,mc)


import numpy as np
import pickle
from .exceptions import EmptyCentroidsError, EmptyDataError

class KMeans():
    def __init__(self, k=2, tol=0.001, epochs=300):
        self.k = k
        self.tol = tol
        self.epochs = epochs
        self.centroids = None
    # Does the unsupervised training of the kmeans algorithm by finding all the points that correspond to
    # a potential centroid and finding the average point between them
    # finishes at a stopping condition of maximum number of epochs or
    # when the training error is very low
    def fit(self,X,Y, save = False, file_path=""):
        if X.size == 0:
            raise EmptyDataError("Can not train the data is empty")
        print("#####################")
        print("Beginning training on data of shape: {}, max epochs: {} and error tolerance: {}".format(X.shape, self.epochs, self.tol))

        self.centroids = {}
        epochs =0
        for i in range(self.k):
            self.centroids[i] = X[i]

        for i in range(self.epochs):
            self.classifications = {}

            for i in range(self.k):
                self.classifications[i] = []
            for index in range(X.shape[0]):
                distances = [np.linalg.norm(X[index]-self.centroids[centroid]) for centroid in self.centroids]
                classification = distances.index(min(distances))
                self.classifications[classification].append(X[index])
            prev_centroids = dict(self.centroids)
            for classification in self.classifications:
                self.centroids[classification] = np.average(self.classifications[classification],axis = 0)
            optimized = True
            for c in self.centroids:
                original_centroid = prev_centroids[c]
                current_centroid = self.centroids[c]
                if np.sum((current_centroid-original_centroid)/original_centroid*100.0)>self.tol:
                    optimized = False
            epochs+=1
            if optimized == True:
                break
        print("#####################")
        print("Finished training in {} epochs".format(epochs))
        if save:
            print("Saving model in {}".format(file_path))
            self.save(file_path)

    # Pickles the centroids
    def save(self, file_path):
        with open(file_path, 'wb') as handle:
            print("Saving model in {}".format(file_path))
            pickle.dump((self.centroids,self.k), handle, protocol=pickle.HIGHEST_PROTOCOL)
            return True

    # Loads the centroids
    def load(self, file_path):
        with open(file_path, 'rb') as handle:
            print("Loading model in {}".format(file_path))
            (self.centroids,self.k) = pickle.load(handle)
        return True

    # Classifies the data
    def classify(self,X):
        if self.centroids == None:
            raise EmptyCentroidsError("No centroids were trained or loaded!")
        distances = [np.linalg.norm(X - self.centroids[centroid]) for centroid in self.centroids]
        classification = distances.index(min(distances))
        return classification
    # Calculate the accuracy against validation data
    def test(self,X,Y):
        error = 0
        for index in range(X.shape[0]):
            distances = [np.linalg.norm(X[index] - self.centroids[centroid]) for centroid in self.centroids]
            classification = distances.index(min(distances))
            if classification!=Y[index]:
                error+=1
        print("Accuracy of this run: {}, Test set: {} examples".format(1-error/len(X),len(X)))


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Tue Jan 29 11:24:09 2019

@author: alankar
"""
import numpy as np

def NR_sec(func,x0,N_max,*args): #Newton Raphson
    x_new = 0.
    x_old = x0
    counter = 0
    while(counter<N_max):
        counter += 1
        #x_new = x_old[1]-func(x_old[1],*args)*((x_old[1]-x_old[0])/(func(x_old[1],*args)-func(x_old[1],*args)))
        x_new = (x_old[0]*func(x_old[1],*args)-x_old[1]*func(x_old[0],*args))/(func(x_old[1],*args)-func(x_old[0],*args))
        x_old[0] = x_old[1]
        x_old[1] = x_new
    return x_new

def f(x):
    return 5*x**3-7*x**2-40*x+100
"""
def f(x):
    return x**2-9
"""
N = 27
XNp1 = NR_sec(f,[4.,5.],N+1)
XN = NR_sec(f,[4.,5.],N)
XNm1 = NR_sec(f,[4.,5.],N-1)
XNm2 = NR_sec(f,[4.,5.],N-2)

alpha = np.log10(np.abs((XNp1-XN)/(XN-XNm1)))/np.log10(np.abs((XN-XNm1)/(XNm1-XNm2)))
print("Rate of convergence of secant method is %.2f"%alpha)

"""
Output

Rate of convergence of secant method is 1.62
"""


import jax
from jax import numpy as jnp
import math
from flax import linen as nn

def get_timestep_embedding(timesteps, embedding_dim, max_positions=10000):
    assert len(timesteps.shape) == 1  # and timesteps.dtype == tf.int32
    half_dim = embedding_dim // 2
    # magic number 10000 is from transformers
    emb = math.log(max_positions) / (half_dim - 1)
    # emb = math.log(2.) / (half_dim - 1)
    emb = jnp.exp(jnp.arange(half_dim, dtype=jnp.float32) * -emb)
    # emb = tf.range(num_embeddings, dtype=jnp.float32)[:, None] * emb[None, :]
    # emb = tf.cast(timesteps, dtype=jnp.float32)[:, None] * emb[None, :]
    emb = timesteps[:, None] * emb[None, :]
    emb = jnp.concatenate([jnp.sin(emb), jnp.cos(emb)], axis=1)
    if embedding_dim % 2 == 1:  # zero pad
        emb = jnp.pad(emb, [[0, 0], [0, 1]])
    assert emb.shape == (timesteps.shape[0], embedding_dim)
    return emb

def ff_embedding(seed, embedding_dim, input_dim, std):
    rng = jax.random.PRNGKey(seed)
    frequency_matrix = jax.random.normal(rng, (embedding_dim, input_dim))*jnp.sqrt(std)
    def embedder(coordinates):
        prefeatures = jnp.dot(coordinates, frequency_matrix.T)
        # Calculate cosine and sine features
        cos_features = jnp.cos(2 * jnp.pi * prefeatures)
        sin_features = jnp.sin(2 * jnp.pi * prefeatures)
        # Concatenate sine and cosine features
        return jnp.concatenate((cos_features, sin_features), axis=1)
    return embedder

class GaussianFourierProjection(nn.Module):
  """Gaussian random features for encoding time steps."""
  embed_dim: int
  scale: float = 30.
  @nn.compact
  def __call__(self, x):
    # Randomly sample weights during initialization. These weights are fixed
    # during optimization and are not trainable.
    W = self.param('W', jax.nn.initializers.normal(stddev=self.scale),
                 (self.embed_dim // 2, ))
    W = jax.lax.stop_gradient(W)
    x_proj = x[:, None] * W[None, :] * 2 * jnp.pi
    return jnp.concatenate([jnp.sin(x_proj), jnp.cos(x_proj)], axis=-1)

def fourier_encode(x: jnp.ndarray, num_encodings=4):
    x = jnp.expand_dims(x, -1)
    orig_x = x
    scales = 2 ** jnp.arange(num_encodings)
    x /= scales
    x = jnp.concatenate([jnp.sin(x), jnp.cos(x)], axis=-1)
    x = jnp.concatenate([x, orig_x], axis=-1)
    return x


# -*- coding: utf-8 -*-
"""
Created on Fri Aug 20 07:50:21 2021

@author: Gabriel
"""
from utils import C, integral, summation, fact, generate_range, ceil
import numpy as np
from math import gamma
from scipy.special import hyp2f1
import matplotlib.pyplot as plt

# Basic concepts

class SampleSpace:
    '''
    Defines a sample space for an arbitrary random experiment.

    It is expected that strings or numbers as well as tuples of those are used to define the elements of the set.
    '''
    def __init__(self, data: set):
        self.elements = data
        self.cardinality = len(self.elements)

    def __repr__(self):
        return f"{self.elements}"

class Event:
    '''
    Defines a subset of a sample space.

    '''
    def __init__(self, data: set, sample_space: SampleSpace):
        self.elements = data
        self.cardinality = len(self.elements)
        assert self.isSubset(sample_space), "The event must be a subset of the sample space."
        self.sample_space = sample_space

    def isSubset(self, Omega: SampleSpace) -> bool:
        return all([i in Omega.elements for i in self.elements])

def prob(event: Event) -> float:
    '''
    P(E) = NFC(E) / NCT(S)
    where,
     - NFC : Number of favorable cases to event E
     - TNC : Cardinality of the sample space S

    Parameters
    ----------
    event : Event

    Returns
    -------
    float
        P(E).

    '''
    return event.cardinality / event.sample_space.cardinality

def probGiven(A: Event, B: Event) -> float:
    '''
    P(A|B) = NCF(A) / NCF(B)
    where,
     - NFC : Number of favorable cases to the event

    Parameters
    ----------
    A : Event

    B : Event

    Returns
    -------
    float
        P(A|B).

    '''
    assert B.isSubset(A.sample_space), "The events must be from the same sample space"
    return A.cardinality / B.cardinality

def probOr(A: Event, B: Event) -> float:
    '''
    P(A or B) = P(A) + P(B) - P(A and B)

    Parameters
    ----------
    A : Event

    B : Event

    Returns
    -------
    float
        P(A or B).

    '''
    return np.round(prob(A) + prob(B) - probAnd(A, B), 4)

def probAnd(A: Event, B: Event) -> float:
    '''
    P(A and B) = P(A)P(B|A) = P(B)P(A|B)

    Parameters
    ----------
    A : Event

    B : Event

    Returns
    -------
    float
        P(A and B).

    '''
    return np.round(prob(B) * probGiven(A, B), 4)

def checkDisjointUnion(P: list) -> bool:
    '''
    Returns True if the sets are a disjoint union (partition of a bigger set).

    Parameters
    ----------
    P : list
        List of events.

    Returns
    -------
    bool
        Is disjoint union

    '''
    sum1 = sum([len(E) for E in P])
    union = P[0]
    for i in range(1, len(P)):
        union = union.union(P[i])
    return sum1 == len(union)

# Probability distributions

class DiscreteDistribution:
    '''
    Contains the implementation of the most relevant discrete distributions.

    Each one can be called returning the probability of assuming value x,
    each one has the mean and variance (var) available.
    A cdf method is also available and calculate F(X <= x).
    '''

    class Bernoulli:
        '''
        Bernoulli distribution.

        Distribution for a binary random value with a given probability of success p.
        '''
        def __init__(self, p: float):
            assert 0 <= p <= 1, "Probability of success must be between 0 and 1"
            self.p = p
            self.q = 1 - self.p
            self.mean = self.p
            self.var = self.p * self.q

        def __repr__(self):
            return f"Bernoulli with probability {self.p} \nmean = {self.mean} and variance = {self.var}"

        def __call__(self, x: int) -> float:
            if not(x in (0, 1)):
                raise Exception("Invalid outcome for a bernoulli trial (x must be binary)")
            return self.p**x * self.q**(1 - x)

        def cdf(self, x: int) -> float:
            if x < 0:
                return 0
            elif x >= 1:
                return 1
            else:
                return self.q

        def plot(self):
            figure = plt.figure()
            axis = figure.add_subplot()
            plt.scatter([0, 1], [self.q, self.p])
            axis.set_ylim([-0.025, 1])
            plt.show()

    class Binomial:
        '''
        Binomial Distribution

        Distribuition for n trials of Bernoulli experiment with probability of success p
        '''
        def __init__(self, n: int, p: float):
            assert 0 <= p <= 1, "Probability of success must be between 0 and 1"
            self.n = n
            self.p = p
            self.q = 1 - self.p
            self.mean = np.round(self.n * self.p, 4)
            self.var = np.round(self.n * self.p * self.q, 4)

        def __repr__(self):
            return f"Binomial with {self.n} number of trials and probability {self.p} \
                     \nmean = {self.mean} and variance = {self.var}"

        def __call__(self, x: int) -> float:
            if not(x in range(self.n+1)):
                raise Exception(f"x must be in [0, {self.n}] range")
            return np.round(C(self.n, x) * self.p**x * self.q**(self.n-x), 4)

        def cdf(self, x: int) -> float:
            if x < 0:
                return 0
            elif x >= self.n:
                return 1
            else:
                f = lambda t: (t**(self.n - x - 1)) * (1 - t)**x
                return np.round((self.n - x) * C(self.n, x) * integral(f, 0, self.q, 10**5), 4)

        def plot(self):
            figure = plt.figure()
            axis = figure.add_subplot()
            to_plot = [i for i in range(self.n+1)]
            plt.scatter(to_plot, [self(i) for i in to_plot])
            axis.set_ylim([-0.025, 1])
            plt.show()

    class Geometric:
        '''
        Geometric Distribution

        Distribution for number consecutive trials to get the first success
        '''
        def __init__(self, p: float):
            assert 0 <= p <= 1, "Probability of success must be between 0 and 1"
            self.p = p
            self.q = 1 - self.p
            self.mean = np.round(self.q / self.p, 4)
            self.var = np.round(self.mean / self.p, 4)

        def __repr__(self):
            return f"Geometric with a success probability {self.p} \
                     \nmean = {self.mean} and variance = {self.var}"

        def __call__(self, x: int) -> float:
            if not(x >= 0):
                raise Exception("x must be greater or equal to 0")
            return np.round(self.p * (self.q**x), 4)

        def cdf(self, x: int) -> float:
            if x < 0:
                return 0
            else:
                return np.round(1 - self.q**(x + 1), 4)

        def plot(self):
            figure = plt.figure()
            axis = figure.add_subplot()
            to_plot = [i for i in range(30)]
            probs = [self(i) for i in to_plot]
            plt.scatter(to_plot, probs)
            axis.set_ylim([-0.025, min(1, max(probs) + 0.1)])
            plt.show()

    class Poisson:
        '''
        Poisson Distribution

        Distribution for the number of ocurrences in a given time/space interval
        '''
        def __init__(self, lam: int):
            assert (0 < lam), "Average success rate must be a positive integer"
            self.lam = lam
            self.mean = lam
            self.var = lam

        def __repr__(self):
            return f"Poisson with average success rate {self.lam} \
                     \nmean = {self.mean} and variance = {self.var}"

        def __call__(self, x: int) -> float:
            if not(x >= 0):
                raise Exception("x must be greater or equal to 0")
            return np.round((((self.lam**x) * (np.e**(-self.lam))) / fact(x)), 4)

        def cdf(self, x: int) -> float:
            if x < 0:
                return 0
            else:
                return np.round(summation(self, 0, x) , 4)

        def plot(self, max_x: int= None):
            if max_x == None:
                max_x = self.lam * 3
            figure = plt.figure()
            axis = figure.add_subplot()
            to_plot = [i for i in range(max_x)]
            probs = [self(i) for i in to_plot]
            plt.scatter(to_plot, probs)
            axis.set_ylim([-0.025, min(1, max(probs) + 0.1)])
            plt.show()

    class DiscreteUniform:
        '''
        Discrete Uniform Distribution

        Distribution for events with uniform probability of happening
        '''
        def __init__(self, a: int, b: int):
            assert 0 <= a < b, "Invalid interval"
            self.a = a
            self.b = b
            self.n = b - a + 1
            self.p = 1 / self.n
            self.mean = np.round((self.a + self.b) / 2, 4)
            self.var = np.round((self.n**2 - 1) / 12, 4)

        def __repr__(self):
            return f"Discrete uniform with probability {self.p:.4f} in [{self.a}, {self.b}] \
                     \nmean = {self.mean} and variance = {self.var}"

        def __call__(self, x: int) -> float:
            if not(self.a <= x <= self.b):
                raise Exception(f"x must be a integer in [{self.a}, {self.b}]")
            return np.round(self.p, 4)

        def cdf(self, x: int) -> float:
            if x < 0:
                return 0
            elif x >= self.b:
                return 1
            else:
                return np.round((x - self.a + 1) / self.n , 4)

        def plot(self):
            figure = plt.figure()
            axis = figure.add_subplot()
            to_plot = [i for i in range(self.a, self.b + 1)]
            probs = [self(i) for i in to_plot]
            plt.scatter(to_plot, probs)
            axis.set_ylim([-0.005, 0.6])
            plt.show()

class ContinuousDistribution:
    '''
    Contains the implementation of the most relevant continuous distributions.

    Each one can be called returning the probability of assuming values in an interval,
    each one has the mean and variance (var) available.
    A cdf method is also available and calculate F(X <= x).
    '''
    class Uniform:
        '''
        Uniform Distribution

        Distribution for events with uniform probability of happening
        '''
        def __init__(self, a: float, b: float):
            assert 0 <= a < b, "Invalid interval"
            self.a = a
            self.b = b
            self.n = b - a
            self.p = 1 / self.n
            self.mean = np.round((self.a + self.b) / 2, 4)
            self.var = np.round((self.n**2) / 12, 4)

        def __repr__(self):
            return f"Uniform with probability {self.p:.4f} in [{self.a}, {self.b}] \
                     \nmean = {self.mean} and variance = {self.var}"

        def __call__(self, x1: float, x2: float) -> float:
            assert x1 <= x2, "Insert a valid interval"
            p = self.cdf(x2) - self.cdf(x1)
            return np.round(p, 4)

        def cdf(self, x: float) -> float:
            if x < 0:
                return 0
            elif x >= self.b:
                return 1
            else:
                return np.round((x - self.a) / self.n , 4)

        def plot(self):
            figure = plt.figure()
            axis = figure.add_subplot()
            to_plot = [self.a, self.b]
            probs = [self.p] * 2
            plt.plot(to_plot, probs)
            axis.set_ylim([-0.005, 0.6])
            plt.show()

    class Exponential:
        '''
        Exponential Distribution

        Distribution for time between events
        '''
        def __init__(self, lam: float):
            assert 0 <= lam, "Invalid lambda"
            self.lam = lam
            self.mean = np.round(1 / lam, 4)
            self.var = np.round(self.mean**2, 4)

        def __repr__(self):
            return f"Exponential with occurence rate {self.lam} \
                     \nmean = {self.mean} and variance = {self.var}"

        def __call__(self, x1: float, x2: float) -> float:
            assert x1 <= x2, "Insert a valid interval"
            p = self.cdf(x2) - self.cdf(x1)
            return np.round(p, 4)

        def call(self, x: float) -> float:
            return self.lam * np.e**(-self.lam * x)

        def cdf(self, x: float) -> float:
            if x < 0:
                return 0
            else:
                return np.round(1 - np.e**(-self.lam * x) , 4)

        def plot(self, max_x: int= None):
            if max_x == None:
                max_x = (self.lam**-1) * 3
            figure = plt.figure()
            axis = figure.add_subplot()
            to_plot = generate_range(0, max_x, 10**4)
            probs = [self.call(i) for i in to_plot]
            plt.plot(to_plot, probs)
            axis.set_ylim([-0.0125, max(probs) + 0.075])
            plt.show()

    class Normal:
        '''
        Normal Distribution

        Do I really need to describe it?
        '''
        def __init__(self, mean: float= 0, sigma: float= 1):
            assert 0 <= sigma, "Invalid variance"
            self.mean = mean
            self.var = sigma**2
            self.std = sigma

        def __repr__(self):
            return f"Normal distribution \
                     \nmean = {self.mean} and variance = {self.var}"

        def __call__(self, x1: float, x2: float) -> float:
            assert x1 <= x2, "Insert a valid interval"
            p = self.cdf(x2) - self.cdf(x1)
            return np.round(p, 5)

        def call(self, x: float) -> float:
            return (1 / (self.std * (2 * np.pi)**0.5)) * np.e**(-0.5 * (((x - self.mean) / self.std)**2))

        def cdf(self, x: float) -> float:
            o = 1 / (self.std * np.sqrt(2 * np.pi))
            z = lambda x: (x - self.mean) / self.std
            f = lambda x: o * np.e**(-0.5*(z(x)**2))
            return np.round(integral(f, self.mean - 7*self.std, x),  5)

        def plot(self):
            max_range = self.mean + (self.std * 7)
            min_range = self.mean - (self.std * 7)
            figure = plt.figure()
            axis = figure.add_subplot()
            to_plot = generate_range(min_range, max_range, 10**4)
            probs = [self.call(i) for i in to_plot]
            plt.plot(to_plot, probs)
            axis.set_ylim([-0.0125, max(probs) + 0.075])
            plt.show()


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Wed Aug 12 13:34:49 2020

@author: lukepinkel
"""
import numpy as np
import scipy as sp
import scipy.special
SQRT2 = np.sqrt(2)
ROOT2PI = np.sqrt(2.0 * np.pi)

def poisson_logp(x, mu, logp=True):
     p = sp.special.xlogy(x, mu) - sp.special.gammaln(x + 1) - mu
     if logp==False:
         p = np.exp(p)
     return p

def log1p(x):
    return np.log(1+x)

def norm_cdf(x, mean=0.0, sd=1.0):
    z = (x - mean) / sd
    p = (sp.special.erf(z/SQRT2) + 1.0) / 2.0
    return p

def norm_pdf(x, mean=0.0, sd=1.0):
    z = (x - mean) / sd
    p = np.exp(-z**2 / 2.0) / (ROOT2PI * sd)
    return p


# -*- coding: utf-8 -*-
"""
Created on Wed Apr 11 23:31:51 2018

@author: Marcus
"""

import numpy as np
import time

N = 2**4
samples = np.random.normal(0, 1, N) + 1j * np.random.normal(0, 1, N)

pre_calc = np.zeros(N, dtype=np.complex)

pre_m = np.zeros((N, N), dtype=np.complex)

for n in range(N):
    pre_calc[n] = (1j * 2 * np.pi * n) / N

for m in range(N):
    pre_m[m] = np.exp(pre_calc * m)

def my_idft(x, pre_calc_vals):
    N = len(x)

    output = np.zeros(N, dtype=np.complex)

    for m in range(N):
        intermediate_sum = 0
        for n in range(N):
            intermediate_sum += x[n] * pre_calc_vals[m][n]

        output[m] = intermediate_sum

    return output / N

my_t0 = time.clock()
my_result = my_idft(samples, pre_m)
my_t1 = time.clock()

np_t0 = time.clock()
np_result = np.fft.ifft(samples)
np_t1 = time.clock()

if(np.allclose(my_result, np_result)):
    print("Results match!")
else:
    print("Error: Results do not match!")

my_time = my_t1 - my_t0
np_time = np_t1 - np_t0

print("My IDFT:", my_time, "s")
print("NP IDFT:", np_time, "s")

print("NP is", my_time / np_time, "times faster.")


"""
Functions to calculate various statistical properties.
"""

import numpy as np

def calc_median(v, w):
    """Calculate median of a weighted dataset.

    Parameters
    ----------
    v : ndarray, shape (N,)
        Array of values for which median will be found.
    w : ndarray, shape (N,)
        Array of weights.

    Returns
    -------
    float
        Median of the `v` array.
    """
    # pylint: disable=len-as-condition
    if len(v) == 0:
        return 0

    sorted_indices = np.argsort(v)

    v = v[sorted_indices]
    w = w[sorted_indices]

    cumsum     = np.cumsum(w)
    median_idx = np.searchsorted(cumsum, cumsum[-1] / 2)

    return v[median_idx]

def calc_stderr(v, w):
    """Calculate standard error of a weighted dataset.

    This is the unbiased version of the standard error as defined in
    https://en.wikipedia.org/wiki/Weighted_arithmetic_mean

    Parameters
    ----------
    v : ndarray, shape (N,)
        Array of values for which standard error will be found.
    w : ndarray, shape (N,)
        Array of weights.

    Returns
    -------
    float
        Standard error of the `v` array.
    """

    w = w / sum(w)

    mean   = np.sum(w * v)
    result = np.sum(w**2 * (v - mean)**2)

    if len(v) > 1:
        result = len(v) / (len(v) - 1) * result

    return np.sqrt(result)

def calc_all_stats(data, weights):
    """Calculate multiple statistics for a weighted dataset.

    Parameters
    ----------
    data : ndarray, shape (N,)
        Array of values.
    w : ndarray, shape (N,)
        Array of weights.

    Returns
    -------
    dict
        A dictionary where labels are the names of statistical properties
        and the values are the values of corresponding statistical properties.

    Notes
    -----
    This function calculates the following statistical properties of `data`:
        - "mean"   -- average value
        - "rms"    -- root mean squared value
        - "stdev"  -- standard deviation
        - "stderr" -- unbiased standard error
        - "median" -- median
        - "m1"     -- First moment, same as "mean"
        - "m2"     -- Second moment, same as "rms"**2
        - "m4"     -- Fourth moment
        - "x1var"  -- Biased variance of `data`.
        - "x2var"  -- Biased variance of `data**2`
        - "m1var"  -- Biased variance of the first moment. Aka biased "stderr".
        - "m2var"  -- Biased variance of the second moment.

    I have derived "[mx]1var" and "[mx]2var" myself. Need to find some credible
    reference and verify values. And replace them by the unbiased versions.
    """

    weights = weights / sum(weights)

    m1 = np.sum(weights * data)
    m2 = np.sum(weights * data**2)
    m4 = np.sum(weights * data**4)

    # Variances of x and x^2
    # TODO: find unbiased versions
    x1var = m2 - m1**2
    x2var = m4 - m2**2

    # Variances of m1 and m2
    m1var = np.sum(weights**2) * x1var
    m2var = np.sum(weights**2) * x2var

    mean   = m1
    rms    = np.sqrt(m2)
    stdev  = np.sqrt(x1var)
    stderr = np.sqrt(m1var)
    median = calc_median(data, weights)

    return {
        'mean'   : mean,
        'rms'    : rms,
        'stdev'  : stdev,
        'stderr' : stderr,
        'median' : median,
        'm1'     : m1,
        'm2'     : m2,
        'm4'     : m4,
        'x1var'  : x1var,
        'x2var'  : x2var,
        'm1var'  : m1var,
        'm2var'  : m2var,
    }

def calc_stat(v, w, stat):
    """Calculate statistical property of the weighted dataset.

    Parameters
    ----------
    data : ndarray, shape (N,)
        Array of values.
    w : ndarray, shape (N,)
        Array of weights.
    stat : { 'mean', 'rms', 'stdev', 'stderr', 'median' }
        Type of the statistical property to calculate.

    Returns
    -------
    float
        Value of the statistical property `stat`.
    """
    # pylint: disable=len-as-condition
    if len(v) == 0:
        return np.nan

    w = w / sum(w)

    if stat == 'mean':
        return np.sum(w * v)

    elif stat == 'rms':
        return np.sqrt(np.sum(w * v**2))

    elif stat == 'std':
        return np.sqrt(calc_stat(v, w, 'rms')**2 - calc_stat(v, w, 'mean')**2)

    elif stat == 'stderr':
        return calc_stderr(v, w)

    elif stat == 'median':
        return calc_median(v, w)

    else:
        raise ValueError("Unknown stat: %s" % stat)

def get_stat_err(stats_dict, stat):
    """Get estimate of error of the statistical property `stat`.

    Parameters
    ----------
    stats_dict : dict
        Dictionary of statistical properties as returned by `calc_all_stats`.
    stat : { 'mean', 'rms' } or str
        Name of the statistical property which error will be estimated.
        If some generic str than this function will return None.

    Returns
    -------
    float or None
        Estimated error of the statistical property `stat`.

    Notes
    -----
    I have derived error of the "rms" myself. Need to find some credible
    reference and verify it. Also this error is biased, need to find unbiased
    version.
    """

    if stat == 'mean':
        return stats_dict['stderr']

    # NOTE: about error calculation
    #    rms      = sqrt(E[x^2]) == sqrt(m2)
    #    err(rms) = err(m2) * (D[sqrt(m2)]/D[m2])
    #             = m2err * 1/(2 * sqrt(m2))

    if stat == 'rms':
        m2    = stats_dict['m2']
        m2err = np.sqrt(stats_dict['m2var'])

        return (1/2) * (m2err / np.sqrt(m2))

    return None


from longstaff_schwartz.algorithm import longstaff_schwartz
from longstaff_schwartz.stochastic_process import GeometricBrownianMotion
import numpy as np

# Model parameters
t = np.linspace(0, 5, 100)  # timegrid for simulation
r = 0.01  # riskless rate
sigma = 0.15  # annual volatility of underlying
n = 50  # number of simulated paths

# Simulate the underlying
gbm = GeometricBrownianMotion(mu=r, sigma=sigma)
rnd = np.random.RandomState(1234)
x = gbm.simulate(t, n, rnd)  # x.shape == (t.size, n)

# Payoff (exercise) function
strike = 0.95

def put_payoff(spot):
        return np.maximum(strike - spot, 0.0)

# Discount factor function
def constant_rate_df(t_from, t_to):
        return np.exp(-r * (t_to - t_from))

# Approximation of continuation value
def fit_quadratic(x, y):
        return np.polynomial.Polynomial.fit(x, y, 2, rcond=None)

# Selection of paths to consider for exercise
# (and continuation value approxmation)
def itm(payoff, spot):
        return payoff > 0

# Run valuation of American put option
npv_american = longstaff_schwartz(x, t, constant_rate_df,
                                        fit_quadratic, put_payoff, itm)

# European put option for comparison
npv_european = constant_rate_df(t[0], t[-1]) * put_payoff(x[-1]).mean()

# Check results
assert np.round(npv_american, 4) == 0.0702
assert np.round(npv_european, 4) == 0.0598
assert npv_american > npv_european

# import pdb
# breakpoint()


#!/usr/bin/env python3

import numba as nb
import numpy as np
import pandas as pd
from abscplane import AbsComplexPlane

class ComplexPlaneNP(AbsComplexPlane):
	"""Abstract subclass that inherit from base class AbsComplexPlane for complex plane.

    A complex plane is a 2D grid of complex numbers, having
    the form (x + y*1j), where 1j is the unit imaginary number in
    Python, and one can think of x and y as the coordinates for
    the horizontal axis and the vertical axis of the plane,
    respectively. Recall that (1j)*(1j) == -1. Also recall that
    the FOIL rule for multiplication still works:
		(x + y*1j)*(v + w*1j) = (x*v - y*w + (x*w + y*v)*1j)
    You can check these results in an interpreter.

    In addition to generating the 2D grid of numbers (x + y*1j),
    we wish to easily support transformations of the plane with
    an arbitrary function f. Done properly, the attribute self.plane
    should store a 2D grid of numbers f(x + y*1j) such that the
    parameter x ranges from self.xmin to self.xmax with self.xlen
    total points, while the parameter y ranges from self.ymin to
    self.ymax with self.ylen total points. By default, the function
    f should be the identity function (lambda x : x), which does
    nothing to the bare complex plane.
	"""
	def __init__(self, xmin, xmax, xlen, ymin, ymax, ylen,plane=[], f=lambda x: x):
		"""
		Attributes:
		xmax (float) : maximum horizontal axis value
		xmin (float) : minimum horizontal axis value
		xlen (int)   : number of horizontal points
		ymax (float) : maximum vertical axis value
		ymin (float) : minimum vertical axis value
		ylen (int)   : number of vertical points
		plane        : stored complex plane implementation
		f    (func)  : function displayed in the plane
		"""

		self.xmin = xmin
		self.xmax = xmax
		self.xlen = xlen
		self.ymin = ymin
		self.ymax = ymax
		self.ylen = ylen
		self.plane = plane
		self.f = f

	def refresh(self):
		"""
		Regenerate complex plane.
		For every point (x + y*1j) in self.plane, replace
		the point with the value self.f(x + y*1j).
		"""
		x = np.linspace(self.xmin, self.xmax, self.xlen)  #np.linspace(start, end, total points)
		y = np.linspace(self.ymin, self.ymax, self.ylen)
		X,Y=np.meshgrid(x,y)
		Z=X+Y*1j
		#fv=np.vectorize(self.f)
		fv=self.f
		self.plane=fv(Z)
		return 0

	def zoom(self, xmin, xmax, xlen, ymin, ymax, ylen):
		"""
		Reset self.xmin, self.xmax, and/or self.xlen.
		Also reset self.ymin, self.ymax, and/or self.ylen.
		Zoom into the indicated range of the x- and y-axes.
		Refresh the plane as needed.
		"""
		self.xmin = xmin
		self.xmax = xmax
		self.xlen = xlen
		self.ymin = ymin
		self.ymax = ymax
		self.ylen = ylen

		self.refresh()
		return 0

	def set_f(self,f):
		"""
		Reset the transformation function f.
		Refresh the plane as needed.
		"""
		self.f = f
		self.refresh()
		return 0

def julia(c,max=100):
	'''
	input: complex parameter c and an optional positive integer max, return a the result of function func():
	In func(), when the absolute value of z(z=z^2+c) is greater than 2, then return 1. when it exceed max times, return 0. Otherwise, it returns the times of loop.
	'''
	@nb.vectorize([nb.int32(nb.complex128)])
	def func(z):
		n=0
		if np.absolute(z)>2:
			return 1
		while np.absolute(z) <=2:
			n=n+1
			z=z**2+c
			if n >= max:
				n=0
				break
		return n
	return func

'''
#test
p1=ComplexPlaneNP(1,2,3,4,6,4)
p1.refresh()
print(p1.plane)
'''

def test_julia1():
	'''
	test function julia. c=-1.037 + 0.17j, z=-1.00-0.2j
	'''
	f1 = julia( -1.037 + 0.17j )
	expected = 0
	computed = f1(-1.00-0.2j)
	success = computed == expected
	message = 'Computed %s, expected %s' % (computed, expected)
	assert success, message

def test_julia2():
	'''
	test function julia. c=-1.037 + 0.17j, z=-1.01 - 0.2j
	'''
	f1 = julia( -1.037 + 0.17j )
	expected = 20
	computed = f1(-1.01 - 0.2j)
	success = computed == expected
	message = 'Computed %s, expected %s' % (computed, expected)
	assert success, message

def test_julia3():
	'''
	test function julia. c=-1.037 + 0.17j, z=-1.02 - 0.2j
	'''
	f1 = julia( -1.037 + 0.17j )
	expected = 13
	computed = f1(-1.02 - 0.2j)
	success = computed == expected
	message = 'Computed %s, expected %s' % (computed, expected)
	assert success, message

def test_julia4():
	'''
	test function julia. c=-1.037 + 0.17j, z=-1.03 - 0.2j
	'''
	f1 = julia( -1.037 + 0.17j )
	expected = 10
	computed = f1(-1.03 - 0.2j)
	success = computed == expected
	message = 'Computed %s, expected %s' % (computed, expected)
	assert success, message

test_julia1()
test_julia2()
test_julia3()
test_julia4()


# figure-3.11-spring_mass_simulation.py - forced spring–mass simulation approx
# RMM, 28 Aug 2021
#
# Figure 3.11: Simulation of the forced spring–mass system with
# different simulation time constants. The solid line represents the
# analytical solution. The dashed lines represent the approximate
# solution via the method of Euler integration, using decreasing step
# sizes.
#

import numpy as np
import matplotlib.pyplot as plt
import control as ct
ct.use_fbs_defaults()

# Parameters defining the system
m = 250                         # system mass
k = 40                          # spring constant
b = 60                          # damping constant

# System matrices
A = [[0, 1], [-k/m, -b/m]]
B = [0, 1/m]
C = [1, 0]
sys = ct.ss(A, B, C, 0)

#
# Discrete time simulation
#
# This section explores what happens when we discretize the ODE
# and convert it to a discrete time simulation.
#
Af = 20                         # forcing amplitude
omega = 0.5                     # forcing frequency

# Sinusoidal forcing function
t = np.linspace(0, 100, 1000)
u = Af * np.sin(omega * t)

# Simulate the system using standard MATLAB routines
response = ct.forced_response(sys, t, u)
ts, ys = response.time, response.outputs

#
# Now generate some simulations manually
#

# Time increments for discrete approximations
hvec = [1, 0.5, 0.1]                # h must be a multiple of 0.1
max_len = int(t[-1] / min(hvec))    # maximum number of time steps

# Create arrays for storing results
td = np.zeros((len(hvec), max_len)) # discrete time instants
yd = np.zeros((len(hvec), max_len)) # output at discrete time instants

# Discrete time simulations
maxi = []                           # list to store maximum index
for iter, h in enumerate(hvec):
    maxi.append(round(t[-1] / h))   # save maximum index for this h
    x = np.zeros((2, maxi[-1] + 1)) # create an array to store the state

    # Compute the discrete time Euler approximation of the dynamics
    for i in range(maxi[-1]):
        offset = int(h/0.1 * i)     # input offset
        x[:, i+1] = x[:, i] + h * (sys.A @ x[:, i] + (sys.B * u[offset])[:, 0])
        td[iter, i] = (i-1) * h
        yd[iter, i] = sys.C @ x[:, i]

# Plot the results
plt.subplot(2, 1, 1)

simh = plt.plot(
    td[0, 0:maxi[0]], yd[0, 0:maxi[0]], 'g+--',
    td[1, 0:maxi[1]], yd[1, 0:maxi[1]], 'ro--',
    td[2, 0:maxi[2]], yd[2, 0:maxi[2]], 'b--',
    markersize=4, linewidth=1
)
analh = plt.plot(ts, ys, 'k-', linewidth=1)

plt.xlabel("Time [s]")
plt.ylabel("Position $q$ [m]")
plt.axis([0, 50, -2, 2])
plt.legend(["$h = %g$" % h for h in hvec] + ["analytical"], loc="lower left")

# Save the figure
plt.savefig("figure-3.11-spring_mass_simulation.png", bbox_inches='tight')


# Para visualizar gráficos en la terminal interactiva
from IPython import get_ipython
get_ipython().run_line_magic('matplotlib', 'ipympl')

"""

# Actividad en clases: FFT con scipy

Objetivos:

- Aprender a utilizar las funciones de `scipy.fft`
- Visualizar el espectro de distintas señales
- Ver como el ruido afecta el espectro

"""

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

Fs = 50
t = np.arange(0, 10, step=1/Fs)
s = np.cos(2.0*np.pi*t*1.2)

S = scipy.fft.rfft(s)
f = scipy.fft.rfftfreq(n=len(t), d=1/Fs)

fig, ax = plt.subplots(figsize=(5, 3), tight_layout=True)
ax.plot(f, np.absolute(S))
ax.set_xlabel('Frecuencia [Hz]')

"""

I) Obtenga y visualice el espectro de magnitud para cada una de las siguientes señales. Discuta con sus compañeros acerca de las diferencias que observa entre los espectros.
1. Una suma de dos sinusoides de distinta frecuencia fundamental
2. Una señal periódica triangular, se recomienda usar scipy.signal.sawtooth

II) Considere ahora sólo la señal suma de dos sinusoides. Súmele un vector de ruido blanco gaussiano usando

s += np.random.randn(len(t))*sigma

donde sigma es la desviación estándar del ruido. Considere los casos sigma=0.5, 1 y 2

Visualice el espectro para cada caso y discuta con sus compañeros respecto al efecto del ruido blanco en el espectro.

"""


import numpy as np
import matplotlib.pyplot as plt

def euclidean(vector1,vector2):
    return np.sqrt(np.sum(np.power(vector1-vector2,2)))

def randomcent(k):
    n = dataset.shape[1]
    cent = np.mat(np.zeros((k, n)))
    for j in range(n):
        lolimit = min(dataset[:, j])
        datarange = float(max(dataset[:, j]) -lolimit)
        cent[:,j] = np.array(lolimit + datarange * np.random.rand(k, 1))
    return cent

def k_means(dataset,k,cent=randomcent):
    m=dataset.shape[0]
    centreplot=cent(k)
    evaluate=np.mat(np.zeros([m,2]))
    clusterchange = True
    while clusterchange:
        clusterchange = False
        for i in range(m):
            mineuclid = np.inf
            mincentidx=-1
            for j in range(k):
                eucliJ=euclidean(dataset[i,], centreplot[j, :])
                if eucliJ<mineuclid:
                    mincentidx=j
                    mineuclid=eucliJ
            if evaluate[i, 0] != mincentidx:
                clusterchange = True
            evaluate[i, :] = [mincentidx, mineuclid **2]
        for elfa in range(k):
            ptsInClust = dataset[np.nonzero(evaluate[:, 0].getA() == elfa)[0]]
            centreplot[elfa, :] = np.mean(ptsInClust, axis=0)
    return  evaluate

def affPoint(pt1,pt2,alpha):
    return  np.exp(-alpha*(np.linalg.norm(pt1-pt2)**2))

def calc_affinity(Mat):
    Msize = Mat.shape[0]
    affMat = np.zeros([Msize,Msize])
    for i in range(Msize):
        for j in range(Msize):
            affMat[i,j] = affPoint(Mat[i,:],Mat[j,:],0.5)

    return  affMat
def calcLapM(affMat,mode=1):
    if mode== 0:
        LaplMat  = np.diag(np.sum(affMat,axis=1)) - affMat  #row
    elif mode ==1:
        D = np.diag(np.sqrt(np.sum(affMat, axis=1)))
        LaplMat =  np.dot(np.dot(np.linalg.inv(D), affMat), D)
    return  LaplMat

def calc_eigen(Mat):
    eigVal,eigeVec = np.linalg.eig(Mat)
    return  eigeVec

def project(Mat,vec):
    Msize = Mat.shape
    ProjMat = np.dot(Mat,vec)
    return  ProjMat

def NormFeature(FMat):
    rows = FMat.shape[0]
    for k in range(rows):
        FMat[k,:] = FMat[k,:] / np.sqrt(np.sum(FMat[k:]**2))
    return FMat
def spectrum_clustring(Mat,mode):
    affMat = calc_affinity(Mat)
    LaplaseMat = calcLapM(affMat,mode)
    eigVec = calc_eigen(LaplaseMat)
    #prjval = project(Mat,eigVec)
    return  eigVec[:,:10]

if __name__=='__main__':
    sampleNo = 200
    mu1 = np.array([[1, -1]])
    sigma1 = np.array([[1, 0], [0, 1]])
    R1 = np.linalg.cholesky(sigma1)
    s1 = np.dot(np.random.randn(sampleNo, 2), R1) + mu1

    mu2 = np.array([[5.5, -4.5]])
    sigma2 = np.array([[1, 0], [0, 1]])
    R2 = np.linalg.cholesky(sigma2)
    s2 = np.dot(np.random.randn(sampleNo, 2), R2) + mu2

    mu3 = np.array([[1, 4]])
    sigma3 = np.array([[1, 0], [0, 1]])
    R3 = np.linalg.cholesky(sigma3)
    s3 = np.dot(np.random.randn(sampleNo, 2), R3) + mu3

    mu4 = np.array([[6, 4.5]])
    sigma4 = np.array([[1, 0], [0, 1]])
    R4 = np.linalg.cholesky(sigma4)
    s4 = np.dot(np.random.randn(sampleNo, 2), R4) + mu4

    mu5 = np.array([[9, 0.0]])
    sigma5 = np.array([[1, 0], [0, 1]])
    R5 = np.linalg.cholesky(sigma5)
    s5 = np.dot(np.random.randn(sampleNo, 2), R5) + mu5

    plt.scatter(s1[:, 1], s1[:, 0], marker='D', color='c')
    plt.scatter(s2[:, 1], s2[:, 0], marker='*', color='m')
    plt.scatter(s3[:, 1], s3[:, 0], marker='o', color='b')
    plt.scatter(s4[:, 1], s4[:, 0], marker='+', color='r')
    plt.scatter(s5[:, 1], s5[:, 0], marker='x', color='g')

    dataset = np.zeros([1000, 2])
    dataset[:200, ] = np.array([s1])
    dataset[200:400, ] = np.array([s2])
    dataset[400:600, ] = np.array([s3])
    dataset[600:800, ] = np.array([s4])
    dataset[800:, ] = np.array([s5])
    k = np.int16(5)
    eigvec = spectrum_clustring(dataset[::5,:],mode=0)
    #np.savetxt('eigenvec.txt',eigvec,fmt='%.2e')

    plt.figure()

    Feature = NormFeature(eigvec[:,:2].copy())
    plt.scatter(Feature[:, 1], Feature[:, 0])
    np.savetxt('posrnorn.txt',Feature,fmt='%.2e')
    label = k_means(Feature,k)
    center = randomcent(k)

    # for elfa in range(k):
    #     ptsInClust = dataset[np.nonzero(label[:, 0].getA() == elfa)[0]]
    #     center[elfa, :] = np.mean(ptsInClust, axis=0)
    # plt.scatter(center[:, 1], center[:, 0], s=300, marker='H', color='k')

    #print center
    plt.figure()
    plt.subplot(230)
    plt.plot(eigvec[:,0])
    plt.subplot(231)
    plt.plot(eigvec[:,1])
    plt.subplot(232)
    plt.plot(eigvec[:,2])
    plt.subplot(233)
    plt.plot(eigvec[:,3])
    plt.subplot(234)
    plt.plot(eigvec[:,4])

    plt.show()


import numpy as np
import matplotlib.pyplot as plt

def initial(delta_x=5.0):
    L = 300.0
    N = L/delta_x + 1
    x = np.linspace(0.0, L, N)
    u = np.zeros(N)
    ii = (x>=50) & (x<=110)
    u[ii] = 100 * np.sin(np.pi * (x[ii]-50)/60.0)
    return x, u

def FTFS(u, t_max=0.45, delta_t=0.015, delta_x=5.0, a =  300.0):
    N_t = int(t_max/delta_t)
    u_final = u.copy()
    for i in range(N_t):
        u_final[1:-1] = u[1:-1] - (a*delta_t/delta_x)*(u[2:]-u[1:-1])
        u = u_final.copy()
    return u_final

def FTCS(u, t_max=0.45, delta_t=0.015, delta_x=5.0, a =  300.0):
    N_t = int(t_max/delta_t)
    u_final = u.copy()
    for i in range(N_t):
        u_final[1:-1] = u[1:-1] - 0.5*(a*delta_t/delta_x)*(u[2:]-u[0:-2])
        u = u_final.copy()
    return u_final

x, u_init = initial()
u_final_A = FTFS(u_init,a=-40.0)

x, u_init = initial()
u_final_B = FTCS(u_init, a=-40.0)

plt.plot(x,u_init)
plt.plot(x, u_final_A)
plt.plot(x, u_final_B)

plt.show()


import numpy as np
import pandas as pd
from fractions import Fraction
from IPython.display import display, HTML
import matplotlib.pyplot as plt
from math import log2

def PCA(data,IO):
    corr = data.corr()
    eigen = np.linalg.eig(corr)
    if IO==1:

        print("Correlation Matrix")
        display(corr)
        print("PCA")
        print("Eigenvalues of correlation matrix")
        PCA = np.array([eigen[0],eigen[0].cumsum(),eigen[0]/(eigen[0].sum()),(eigen[0]/(eigen[0].sum())).cumsum()]).T
        display(pd.DataFrame(PCA,columns = ("Eigen Value","cumative sum","Proportion","Cumulative")))
        display(pd.DataFrame(eigen[1],columns=list(data)))

        plt.plot([x for x in range(1,1+len(eigen[0]))],(eigen[0]/(eigen[0].sum())).cumsum(), label='Culmatie', lw=2, marker='o')
        plt.plot([x for x in range(1,1+len(eigen[0]))],1-(eigen[0]/(eigen[0].sum())).cumsum(), label='Missing var', lw=2, marker='s')
        plt.grid()
        plt.legend(loc='upper right')
        plt.title("PCA")
        plt.show()
    if IO!=0:
        return corr,eigen

def testPCA(data,m,IO):
    eigen =  PCA(data,2)
    #test on PCA
    k=len(eigen[0])
    n=len(data)
    np=n-m-1/6*(2*(k-m)+1+2/(k-m))
    Z_1=-np*log2(sum(eigen[0][m:])/(sum(eigen[0])/(k-m)**(k-m)))
    Z_2=-n*log2(sum(eigen[0][m:])/(sum(eigen[0])/(k-m)**(k-m)))
    print("n' = {0}-{1}-1/6*(2*({2}-{1})+1+2/({2}-{1})={3}".format(len(data),m,k,np))
    print("Z_1 = -{0:.3}*log2({1:.3}/({2:.3})/({3}-{4})^({3}-{4})))={5}".format(np,sum(eigen[0][m:]),sum(eigen[0]),k,m,Z_1))
    print("Z_2 = {0}*log2({1:.3}/({2:.3})/({3}-{4})^({3}-{4})))={5}".format(n,sum(eigen[0][m:]),sum(eigen[0]),k,m,Z_2))

def varimax(Phi, gamma = 1, q = 20, tol = 1e-6):
    #from numpy import eye, asarray, dot, sum, diag
    from numpy.linalg import svd
    p,k = Phi.shape
    R = eye(k)
    d=0
    for i in range(q):
        d_old = d
        Lambda = np.dot(Phi, R)
        u,s,vh = svd(dot(Phi.T,np.asarray(Lambda)**3 - (gamma/p) * np.dot(Lambda, np.diag(np.diag(np.dot(Lambda.T,Lambda))))))
        R = np.dot(u,vh)
        d = sum(s)
        if d/d_old < tol: break
    return np.dot(Phi, R)


import xarray as xr
import numpy as np
from numba import njit, prange
from math import sqrt

EUCLIDEAN = 0
GREAT_CIRCLE = 1
MANHATTAN = 2

PROXIMITY = 0
ALLOCATION = 1
DIRECTION = 2

def _distance_metric_mapping():
    DISTANCE_METRICS = {}
    DISTANCE_METRICS['EUCLIDEAN'] = EUCLIDEAN
    DISTANCE_METRICS['GREAT_CIRCLE'] = GREAT_CIRCLE
    DISTANCE_METRICS['MANHATTAN'] = MANHATTAN

    return DISTANCE_METRICS

# create dictionary to map distance metric presented by string and the
# corresponding metric presented by integer.
DISTANCE_METRICS = _distance_metric_mapping()

@njit(nogil=True)
def euclidean_distance(x1: float, x2: float, y1: float, y2: float) -> float:
    """
    Calculates Euclidean (straight line) distance between (x1, y1) and
    (x2, y2).

    Parameters:
    ----------
    x1: float
        x-coordinate of the first point.
    x2: float
        x-coordinate of the second point.
    y1: float
        y-coordinate of the first point.
    y2: float
        y-coordinate of the second point.

    Returns:
    ----------
    distance: float
        Euclidean distance between two points.

    Notes:
    ----------
    Algorithm References:
    - Wikipedia, Euclidean Distance, https://en.wikipedia.org/wiki/Euclidean_distance#:~:text=In%20mathematics%2C%20the%20Euclidean%20distance,being%20called%20the%20Pythagorean%20distance, Accessed Apr. 22, 2021. # noqa

    Examples:
    ----------
    Imports
    >>> from xrspatial import euclidean_distance as ed
    >>> point_a = (142.32, 23.23)
    >>> point_b = (312.54, 432.01)

    Calculate Euclidean Distance
    >>> dist = ed(point_a[0], point_b[0], point_a[1], point_b[1])
    >>> print(dist)
    442.80462599209596
    """

    x = x1 - x2
    y = y1 - y2
    return np.sqrt(x * x + y * y)

@njit(nogil=True)
def manhattan_distance(x1: float, x2: float,
                       y1: float, y2: float) -> float:
    """
    Calculates Manhattan distance (sum of distance in x and
    y directions) between (x1, y1) and (x2, y2).

    Parameters:
    ----------
    x1: float
        x-coordinate of the first point.
    x2: float
        x-coordinate of the second point.
    y1: float
        y-coordinate of the first point.
    y2: float
        y-coordinate of the second point.

    Returns:
    ----------
    distance: float
        Manhattan distance between two points.

    Notes:
    ----------
    Algorithm References:
    - Wikipedia, Taxicab Geometry, https://en.wikipedia.org/wiki/Taxicab_geometry, Accessed Apr. 22, 2021. # noqa

    Examples:
    ----------
    Imports
    >>> from xrspatial import manhattan_distance as md
    >>> point_a = (142.32, 23.23)
    >>> point_b = (312.54, 432.01)

    Calculate Euclidean Distance
    >>> dist = md(point_a[0], point_b[0], point_a[1], point_b[1])
    >>> print(dist)
    196075.9368
    """

    x = x1 - x2
    y = y1 - y2
    return x * x + y * y

@njit(nogil=True)
def great_circle_distance(x1: float, x2: float,
                          y1: float, y2: float,
                          radius: float = 6378137) -> float:
    """
    Calculates great-circle (orthodromic/spherical) distance between
    (x1, y1) and (x2, y2), assuming each point is a longitude, latitude pair.

    Parameters:
    ----------
    x1: float (between -180 and 180)
        x-coordinate (latitude) of the first point.
    x2: float (between -180 and 180)
        x-coordinate (latitude) of the second point.
    y1: float (between -90 and 90)
        y-coordinate (longitude) of the first point.
    y2: float (between -90 and 90)
        y-coordinate (longitude) of the second point.
    radius: float (default = 6378137)
        Radius of sphere (earth).

    Returns:
    ----------
    distance: float
        Great-Circle distance between two points.

    Notes:
    ----------
    Algorithm References:
    - Wikipedia, Great-circle Distance, https://en.wikipedia.org/wiki/Great-circle_distance#:~:text=The%20great%2Dcircle%20distance%2C%20orthodromic,line%20through%20the%20sphere, Accessed Apr. 22, 2021. # noqa

    Examples:
    ----------
    Imports
    >>> from xrspatial import great_circle_distance as gcd
    >>> point_a = (123.2, 82.32)
    >>> point_b = (178.0, 65.09)

    Calculate Euclidean Distance
    >>> dist = gcd(point_a[0], point_b[0], point_a[1], point_b[1])
    >>> print(dist)
    2378290.489801402
    """

    if x1 > 180 or x1 < -180:
        raise ValueError('Invalid x-coordinate of the first point.'
                         'Must be in the range [-180, 180]')

    if x2 > 180 or x2 < -180:
        raise ValueError('Invalid x-coordinate of the second point.'
                         'Must be in the range [-180, 180]')

    if y1 > 90 or y1 < -90:
        raise ValueError('Invalid y-coordinate of the first point.'
                         'Must be in the range [-90, 90]')

    if y2 > 90 or y2 < -90:
        raise ValueError('Invalid y-coordinate of the second point.'
                         'Must be in the range [-90, 90]')

    lat1, lon1, lat2, lon2 = (np.radians(y1), np.radians(x1),
                              np.radians(y2), np.radians(x2))
    dlon = lon2 - lon1
    dlat = lat2 - lat1
    a = np.sin(dlat / 2.0) ** 2 + \
        np.cos(lat1) * np.cos(lat2) * np.sin(dlon / 2.0) ** 2

    # earth radius: 6378137
    return radius * 2 * np.arcsin(np.sqrt(a))

@njit(nogil=True)
def _distance(x1, x2, y1, y2, metric):
    if metric == EUCLIDEAN:
        return euclidean_distance(x1, x2, y1, y2)

    if metric == GREAT_CIRCLE:
        return great_circle_distance(x1, x2, y1, y2)

    if metric == MANHATTAN:
        return manhattan_distance(x1, x2, y1, y2)

    return -1.0

@njit(nogil=True)
def _process_proximity_line(source_line, x_coords, y_coords,
                            pan_near_x, pan_near_y, is_forward,
                            line_id, width, max_distance, line_proximity,
                            nearest_xs, nearest_ys,
                            values, distance_metric):
    # Process proximity for a line of pixels in an image
    #
    # source_line: 1d ndarray, input data
    # pan_near_x:  1d ndarray
    # pan_near_y:  1d ndarray
    # is_forward: boolean, will we loop forward through pixel?
    # line_id: np.int64, index of the source_line in the image
    # width: np.int64, image width. It is the number of
    #                  pixels in the source_line
    # max_distance: np.float64, maximum distance considered.
    # line_proximity: 1d numpy array of type
    #                 np.float64, calculated proximity from
    #                 source_line
    # values: 1d numpy array of type np.uint8,
    #                 A list of target pixel
    #                 values to measure the distance from. If
    #                 this option is not provided proximity will
    #                 be computed from non-zero pixel values.
    #                 Currently pixel values are internally
    #                 processed as integers
    # Return: 1d numpy array of type np.float64.
    #          Corresponding proximity of source_line.

    start = width - 1
    end = -1
    step = -1
    if is_forward:
        start = 0
        end = width
        step = 1

    n_values = len(values)
    for pixel in prange(start, end, step):
        is_target = False
        # Is the current pixel a target pixel?
        if n_values == 0:
            if source_line[pixel] != 0 and np.isfinite(source_line[pixel]):
                is_target = True
        else:
            for i in prange(n_values):
                if source_line[pixel] == values[i]:
                    is_target = True

        if is_target:
            line_proximity[pixel] = 0.0
            nearest_xs[pixel] = pixel
            nearest_ys[pixel] = line_id
            pan_near_x[pixel] = pixel
            pan_near_y[pixel] = line_id
            continue

        # Are we near(er) to the closest target to the above (below) pixel?
        near_distance_square = max_distance ** 2 * 2.0
        if pan_near_x[pixel] != -1:
            # distance_square
            dist = _distance(x_coords[pan_near_x[pixel]], x_coords[pixel],
                             y_coords[pan_near_y[pixel]], y_coords[line_id],
                             distance_metric)
            dist_sqr = dist ** 2
            if dist_sqr < near_distance_square:
                near_distance_square = dist_sqr
            else:
                pan_near_x[pixel] = -1
                pan_near_y[pixel] = -1

        # Are we near(er) to the closest target to the left (right) pixel?
        last = pixel - step
        if pixel != start and pan_near_x[last] != -1:
            dist = _distance(x_coords[pan_near_x[last]], x_coords[pixel],
                             y_coords[pan_near_y[last]], y_coords[line_id],
                             distance_metric)
            dist_sqr = dist ** 2
            if dist_sqr < near_distance_square:
                near_distance_square = dist_sqr
                pan_near_x[pixel] = pan_near_x[last]
                pan_near_y[pixel] = pan_near_y[last]

        #  Are we near(er) to the closest target to the
        #  topright (bottom left) pixel?
        tr = pixel + step
        if tr != end and pan_near_x[tr] != -1:
            dist = _distance(x_coords[pan_near_x[tr]], x_coords[pixel],
                             y_coords[pan_near_y[tr]], y_coords[line_id],
                             distance_metric)
            dist_sqr = dist ** 2
            if dist_sqr < near_distance_square:
                near_distance_square = dist_sqr
                pan_near_x[pixel] = pan_near_x[tr]
                pan_near_y[pixel] = pan_near_y[tr]

        # Update our proximity value.
        if pan_near_x[pixel] != -1 \
                and max_distance * max_distance >= near_distance_square \
                and (line_proximity[pixel] < 0 or
                     near_distance_square <
                     line_proximity[pixel] * line_proximity[pixel]):
            line_proximity[pixel] = sqrt(near_distance_square)
            nearest_xs[pixel] = pan_near_x[pixel]
            nearest_ys[pixel] = pan_near_y[pixel]
    return

@njit
def _calc_direction(x1, x2, y1, y2):
    # Calculate direction from (x1, y1) to a source cell (x2, y2).
    # The output values are based on compass directions,
    # 90 to the east, 180 to the south, 270 to the west, and 360 to the north,
    # with 0 reserved for the source cell itself

    if x1 == x2 and y1 == y2:
        return 0

    x = x2 - x1
    y = y2 - y1
    d = np.arctan2(-y, x) * 57.29578
    if d < 0:
        d = 90.0 - d
    elif d > 90.0:
        d = 360.0 - d + 90.0
    else:
        d = 90.0 - d
    return d

@njit(nogil=True)
def _process_image(img, x_coords, y_coords, target_values,
                   distance_metric, process_mode):
    max_distance = _distance(x_coords[0], x_coords[-1],
                             y_coords[0], y_coords[-1],
                             distance_metric)

    height, width = img.shape

    pan_near_x = np.zeros(width, dtype=np.int64)
    pan_near_y = np.zeros(width, dtype=np.int64)

    # output of the function
    img_distance = np.zeros(shape=(height, width), dtype=np.float64)
    img_allocation = np.zeros(shape=(height, width), dtype=np.float64)
    img_direction = np.zeros(shape=(height, width), dtype=np.float64)

    # Loop from top to bottom of the image.
    for i in prange(width):
        pan_near_x[i] = -1
        pan_near_y[i] = -1

    # a single line of the input image @img
    scan_line = np.zeros(width, dtype=img.dtype)
    # indexes of nearest pixels of current line @scan_line
    nearest_xs = np.zeros(width, dtype=np.int64)
    nearest_ys = np.zeros(width, dtype=np.int64)

    for line in prange(height):
        # Read for target values.
        for i in prange(width):
            scan_line[i] = img[line][i]

        line_proximity = np.zeros(width, dtype=np.float64)

        for i in prange(width):
            line_proximity[i] = -1.0
            nearest_xs[i] = -1
            nearest_ys[i] = -1

        # left to right
        _process_proximity_line(scan_line, x_coords, y_coords,
                                pan_near_x, pan_near_y, True, line,
                                width, max_distance, line_proximity,
                                nearest_xs, nearest_ys,
                                target_values, distance_metric)
        for i in prange(width):
            if nearest_xs[i] != -1 and line_proximity[i] >= 0:
                img_allocation[line][i] = img[nearest_ys[i], nearest_xs[i]]
                d = _calc_direction(x_coords[i], x_coords[nearest_xs[i]],
                                    y_coords[line], y_coords[nearest_ys[i]])
                img_direction[line][i] = d

        # right to left
        for i in prange(width):
            nearest_xs[i] = -1
            nearest_ys[i] = -1

        _process_proximity_line(scan_line, x_coords, y_coords,
                                pan_near_x, pan_near_y, False, line,
                                width, max_distance, line_proximity,
                                nearest_xs, nearest_ys,
                                target_values, distance_metric)

        for i in prange(width):
            img_distance[line][i] = line_proximity[i]
            if nearest_xs[i] != -1 and line_proximity[i] >= 0:
                img_allocation[line][i] = img[nearest_ys[i], nearest_xs[i]]
                d = _calc_direction(x_coords[i], x_coords[nearest_xs[i]],
                                    y_coords[line], y_coords[nearest_ys[i]])
                img_direction[line][i] = d

    # Loop from bottom to top of the image.
    for i in prange(width):
        pan_near_x[i] = -1
        pan_near_y[i] = -1

    for line in prange(height - 1, -1, -1):
        # Read first pass proximity.
        for i in prange(width):
            line_proximity[i] = img_distance[line][i]

        # Read pixel target_values.
        for i in prange(width):
            scan_line[i] = img[line][i]

        # Right to left
        for i in prange(width):
            nearest_xs[i] = -1
            nearest_ys[i] = -1

        _process_proximity_line(scan_line, x_coords, y_coords,
                                pan_near_x, pan_near_y, False, line,
                                width, max_distance, line_proximity,
                                nearest_xs, nearest_ys,
                                target_values, distance_metric)

        for i in prange(width):
            if nearest_xs[i] != -1 and line_proximity[i] >= 0:
                img_allocation[line][i] = img[nearest_ys[i], nearest_xs[i]]
                d = _calc_direction(x_coords[i], x_coords[nearest_xs[i]],
                                    y_coords[line], y_coords[nearest_ys[i]])
                img_direction[line][i] = d

        # Left to right
        for i in prange(width):
            nearest_xs[i] = -1
            nearest_ys[i] = -1

        _process_proximity_line(scan_line, x_coords, y_coords,
                                pan_near_x, pan_near_y, True, line,
                                width, max_distance, line_proximity,
                                nearest_xs, nearest_ys,
                                target_values, distance_metric)

        # final post processing of distances
        for i in prange(width):
            if line_proximity[i] < 0:
                line_proximity[i] = np.nan
            else:
                if nearest_xs[i] != -1 and line_proximity[i] >= 0:
                    img_allocation[line][i] = img[nearest_ys[i],
                                                  nearest_xs[i]]
                    d = _calc_direction(x_coords[i],
                                        x_coords[nearest_xs[i]],
                                        y_coords[line],
                                        y_coords[nearest_ys[i]])
                    img_direction[line][i] = d

        for i in prange(width):
            img_distance[line][i] = line_proximity[i]

    if process_mode == PROXIMITY:
        return img_distance
    elif process_mode == ALLOCATION:
        return img_allocation
    elif process_mode == DIRECTION:
        return img_direction

def _process(raster, x='x', y='y', target_values=[],
             distance_metric='EUCLIDEAN', process_mode=PROXIMITY):
    raster_dims = raster.dims
    if raster_dims != (y, x):
        raise ValueError("raster.coords should be named as coordinates:"
                         "({0}, {1})".format(y, x))

    # convert distance metric from string to integer, the correct type
    # of argument for function _distance()
    distance_metric = DISTANCE_METRICS.get(distance_metric, None)

    if distance_metric is None:
        distance_metric = DISTANCE_METRICS['EUCLIDEAN']

    target_values = np.asarray(target_values).astype(np.uint8)

    img = raster.values
    y_coords = raster.coords[y].values
    x_coords = raster.coords[x].values

    output_img = _process_image(img, x_coords, y_coords, target_values,
                                distance_metric, process_mode)
    return output_img

# ported from
# https://github.com/OSGeo/gdal/blob/master/gdal/alg/gdalproximity.cpp
def proximity(raster: xr.DataArray,
              x: str = 'x',
              y: str = 'y',
              target_values: list = [],
              distance_metric: str = 'EUCLIDEAN') -> xr.DataArray:
    """
    Computes the proximity of all pixels in the image
    to a set of pixels in the source image based on
    Euclidean, Great-Circle or Manhattan distance.

    This function attempts to compute the proximity
    of all pixels in the image to a set of pixels in
    the source image. The following options are used
    to define the behavior of the function. By default
    all non-zero pixels in ``raster.values`` will be
    considered the "target", and all proximities will
    be computed in pixels. Note that target pixels
    are set to the value corresponding to a distance of zero.

    Parameters:
    ----------
    raster: xarray.DataArray
        2D array image with shape = (height, width)
    x: str (default = "x")
        Name of x-coordinates.
    y: str (default = "y")
        Name of y-coordinates.
    target_values: list
        Target pixel values to measure the distance from.
        If this option is not provided,
        proximity will be computed from non-zero pixel values.
        Currently pixel values are internally processed as integers.
    distance_metric: str (default = "EUCLIDEAN")
        The metric for calculating distance between 2 points.
        Valid distance_metrics: 'EUCLIDEAN', 'GREAT_CIRCLE', and 'MANHATTAN'

    Returns:
    ----------
    proximity: xarray.DataArray
        2D proximity array, of the same type as the input
        All other input attributes are preserved.

    Notes:
    ---------
    Algorithm References:
    - OSGeo/gdal, https://github.com/OSGeo/gdal/blob/master/gdal/alg/gdalproximity.cpp, Apr. 22, 2021. # noqa

    Example:
    ----------
    Imports
    >>> from xrspatial import proximity
    >>> import pandas as pd
    >>> from datashader.transfer_functions import shade
    >>> from datashader.transfer_functions import stack
    >>> from datashader.transfer_functions import dynspread
    >>> from datashader.transfer_functions import set_background
    >>> from datashader.colors import Elevation

    Load Data and Create Canvas
    >>> df = pd.Dataframe({
    >>>     'x': [-13, -11, -5, 4, 9, 11, 18, 6],
    >>>     'y': [-13, -5, 0, 10, 7, 2, 5, -5]
    >>> })
    >>> cvs = ds.Canvas(plot_width=800, plot_height=600,
    >>>                 x_range=(-20, 20), y_range=(-20,20))

    Create Proximity Aggregate
    >>> points_agg = cvs.points(df, x='x', y='y')
    >>> points_shaded = dynspread(shade(points_agg,
    >>>                                 cmap=['salmon',  'salmon']),
    >>>                                 threshold=1,
    >>>                                 max_px=5)
    >>> set_background(points_shaded, 'black')

    Create Proximity Grid for All Non-Zero Values
    >>> prox_agg = proximity(points_agg)
    >>> stack(shade(prox_agg, cmap=['darkturquoise', 'black'], how='linear'),
    >>>       points_shaded)
    """

    proximity_img = _process(raster,
                             x=x,
                             y=y,
                             target_values=target_values,
                             distance_metric=distance_metric,
                             process_mode=PROXIMITY)

    result = xr.DataArray(proximity_img,
                          coords=raster.coords,
                          dims=raster.dims,
                          attrs=raster.attrs)
    return result

def allocation(raster: xr.DataArray,
               x: str = 'x',
               y: str = 'y',
               target_values: list = [],
               distance_metric: str = 'EUCLIDEAN'):
    """
    Calculates, for all pixels in the input raster,
    the nearest source based on a set of target
    values and a distance metric.

    This function attempts to produce the value of
    nearest feature of all pixels in the image to
    a set of pixels in the source image. The following
    options are used to define the behavior of the
    function. By default all non-zero pixels in
    ``raster.values`` will be considered as
    "target", and all allocation will be computed
    in pixels.

    Parameters:
    ----------
    raster: xarray.DataArray
        2D image array
    x: str (default = "x")
        Name of x-coordinates.
    y: str (default = "y")
        Name of y-coordinates.
    target_values: list
        Target pixel values to measure the distance from.
        If this option is not provided, allocation will be computed
        from non-zero pixel values. Currently pixel values are internally
        processed as integers.
    distance_metric: str (default = "EUCLIDEAN")
        The metric for calculating distance between 2 points.
        Valid distance_metrics: 'EUCLIDEAN', 'GREAT_CIRCLE', and 'MANHATTAN'

    Returns:
    ----------
    allocation: xarray.DataArray
        2D proximity allocation array, of the same type as the input
        All other input attributes are preserved.

    Notes:
    ---------
    Algorithm References:
    - OSGeo/gdal, https://github.com/OSGeo/gdal/blob/master/gdal/alg/gdalproximity.cpp, Apr. 22, 2021. # noqa
    """

    allocation_img = _process(raster,
                              x=x,
                              y=y,
                              target_values=target_values,
                              distance_metric=distance_metric,
                              process_mode=ALLOCATION)
    # convert to have same type as of input @raster
    result = xr.DataArray((allocation_img).astype(raster.dtype),
                          coords=raster.coords,
                          dims=raster.dims,
                          attrs=raster.attrs)
    return result

def direction(raster: xr.DataArray,
              x: str = 'x',
              y: str = 'y',
              target_values: list = [],
              distance_metric: str = 'EUCLIDEAN'):
    """
    Calculates, for all pixels in the input raster,
    the direction to nearest source based on a set
    of target values and a distance metric.

    This function attempts to calculate for each cell,
    the the direction, in degrees, to the nearest source.
    The output values are based on compass directions,
    where 90 is for the east, 180 for the south, 270 for
    the west, 360 for the north, and 0 for the source cell
    itself. The following options are used to define the
    behavior of the function. By default all non-zero pixels
    in ``raster.values`` will be considered as "target", and
    all allocation will be computed in pixels.

    Parameters:
    ----------
    raster: xarray.DataArray
        2D array image with shape = (height, width)
    x: str (default = "x")
        Name of x-coordinates.
    y: str (default = "y")
        Name of y-coordinates.
    target_values: list
        Target pixel values to measure the distance from.
        If this option is not provided, allocation will
        be computed from non-zero pixel values.
        Currently pixel values are internally processed as integers.
    distance_metric: str (default = "EUCLIDEAN")
        The metric for calculating distance between 2 points.
        Valid distance_metrics: 'EUCLIDEAN', 'GREAT_CIRCLE', and 'MANHATTAN'

    Returns:
    ----------
    direction: xarray.DataArray
        2D proximity direction array, of the same type as the input
        All other input attributes are preserved.

    Notes:
    ---------
    Algorithm References:
    - OSGeo/gdal, https://github.com/OSGeo/gdal/blob/master/gdal/alg/gdalproximity.cpp, Apr. 22, 2021. # noqa
    """

    direction_img = _process(raster,
                             x=x,
                             y=y,
                             target_values=target_values,
                             distance_metric=distance_metric,
                             process_mode=DIRECTION)

    result = xr.DataArray(direction_img,
                          coords=raster.coords,
                          dims=raster.dims,
                          attrs=raster.attrs)
    return result


"""
Created on Oct 2, 2012

@author: Georgiana Dinu, Pham The Nghia
"""
import numpy as np

from composes.utils.py_matrix_utils import nonzero_invert

from composes.similarity.similarity import Similarity
from composes.similarity.dot_prod import DotProdSimilarity

class CosSimilarity(Similarity):
    """
    Computes the cosine similarity of two vectors.

    :math:`sim(\\vec{u},\\vec{v}) = \\frac{<\\vec{u},\\vec{v}>}{\\sqrt{||\\vec{u}||||\\vec{v}||}}`

    """

    def _sim(self, v1, v2):
        if v1.norm() == 0 or v2.norm() == 0:
            return 0.0
        s = DotProdSimilarity()._sim(v1, v2) / np.double(v1.norm() * v2.norm())
        return s

    def _sims_to_matrix(self, vector, matrix_):
        sims = DotProdSimilarity()._sims_to_matrix(vector, matrix_)

        vector_norm = vector.norm()
        row_norms = vector_norm * matrix_.norm(1)
        row_norms = nonzero_invert(row_norms)

        return sims.scale_rows(row_norms)


import numpy as np
import copy
import matplotlib.pyplot as plt
def matrix_mul(matrix1,matrix2):
	new_matrix = [[0,0,0],[0,0,0],[0,0,0]]
	for i in range(len(matrix1)):
	    for j in range(len(matrix2[0])):
	        for k in range(len(matrix2)):
	            new_matrix[i][j] += matrix1[i][k]*matrix2[k][j]
	return np.around(new_matrix,decimals=2)
Tx = 4
Ty = 5
TranMatrix = np.zeros((3,3))
TranMatrix[0][0]=1
TranMatrix[0][2]=Tx
TranMatrix[1][1]=1
TranMatrix[1][2]=Ty
TranMatrix[2][2]=1

original = [[1,2], [20,10], [-2,4]]
translated=matrix_mul(TranMatrix, original)
l = []
for i in range(len(translated)):
	l.append([])
	for j in range(len(translated[0])-1):
		l[i].append(translated[i][j])
print(original)
print(l)
X = np.array(original + l)

# X = np.array([[1,1], [2,2.5], [3, 1], [8, 7.5], [7, 9], [9, 9]])
r = 'red'
b = 'blue'
Y = []
Y += len(original)*[r]
Y += len(original)*[b]

plt.figure()
plt.scatter(X[:, 0], X[:, 1], s = 170, color = Y[:])

t1 = plt.Polygon(X[:len(original),:], color=Y[0])
plt.gca().add_patch(t1)

t2 = plt.Polygon(X[len(original):2*len(l),:], color=Y[3])
plt.gca().add_patch(t2)

plt.show()
# print(translated)


import math as m
import numpy as np
from matplotlib import pyplot as plt

from BDPoisson1D.FirstOrderLinear import dirichlet_first_order_solver_arrays
from BDFunction1D import Function
from BDFunction1D.Differentiation import NumericGradient

class TestFunction(Function):
    """
    Some known differentiable function
    """
    def evaluate_point(self, x):
        return -10 * m.sin(2 * m.pi * x**2) / (2 * m.pi) + 3 * x**2 + x + 5

class TestDerivative(Function):
    """
    Some known differentiable function
    """
    def evaluate_point(self, x):
        return -20 * x * m.cos(2 * m.pi * x**2) + 6 * x + 1

class MixFunction(Function):
    """
    Some known differentiable function
    """
    def evaluate_point(self, x):
        # return 1.0
        # return 0.0
        return x**2 * m.cos(x)

y = TestFunction()
p = MixFunction()
dy_numeric = NumericGradient(y)
dy_analytic = TestDerivative()

dy = dy_analytic

start = -1.0
stop = 2.0
bc1 = y.evaluate_point(start)  # left Dirichlet boundary condition
bc2 = y.evaluate_point(stop)  # right Dirichlet boundary condition

hundreds = []
errs = []
for i in range(1, 20):
    nodes = np.linspace(start, stop, num=100 * i + 1, endpoint=True)  # generate nodes
    p_nodes = np.asarray(p.evaluate(nodes))
    f_nodes = np.asarray(dy.evaluate(nodes)) + p_nodes * np.asarray(y.evaluate(nodes))
    result = dirichlet_first_order_solver_arrays(nodes, p_nodes, f_nodes, bc1, bc2, j=1.0)  # solve Poisson equation
    dy_solution = np.gradient(result[:], nodes, edge_order=2)
    hundreds.append(i)
    errs.append(np.sqrt(np.square(result[2:-2] - np.asarray(y.evaluate(nodes[2:-2]))).mean()))
    print(101 + i * 100, 'Mean Square ERR:', errs[-1])

fig, ax = plt.subplots()
ax.plot(hundreds, errs, 'r-o')
ax.set_xlabel('Points number x100')
ax.set_ylabel('Mean Square Error')
plt.show()

# Plot the result
fig, (ax1, ax2, ax3, ax4) = plt.subplots(4, sharex=True)
ax1.plot(nodes, f_nodes, 'r-', label='f(x)')
ax1.plot(nodes, dy_solution + np.asarray(result[:]) * np.asarray(p_nodes), 'b-', label='dy/dx + p(x)*y (solution)')
ax1.legend()

ax2.plot(nodes, dy_numeric.evaluate(nodes), 'r-', label='dy/dx')
ax2.plot(nodes, dy_solution, 'b-', label='dy/dx (solution)')
ax2.legend()

ax3.plot(nodes, y.evaluate(nodes), 'r-', label='y(x)')
ax3.plot(nodes, result[:], 'b-', label='solution')
ax3.legend()

ax4.plot(nodes[2:-2], result[2:-2] - np.asarray(y.evaluate(nodes[2:-2])), 'g-o', label='residual')
ax4.legend()
plt.show()


# pylint: disable=C0103
# pylint: disable=R0903
"""
This module contains various helper functions and classes.

"""
# %%
import numpy as np

# %%
def abc2complex(u):
    """
    Transform three-phase quantities to a complex space vector.

    Parameters
    ----------
    u : array_like, shape (3,)
        Phase quantities.

    Returns
    -------
    complex
        Complex space vector (peak-value scaling).

    Examples
    --------
    >>> y = abc2complex([1, 2, 3])
    >>> y
    (-1-0.5773502691896258j)

    """
    return (2/3)*u[0] - (u[1] + u[2])/3 + 1j*(u[1] - u[2])/np.sqrt(3)

# %%
def complex2abc(u):
    """
    Transform a complex space vector to three-phase quantities.

    Parameters
    ----------
    u : complex
        Complex space vector (peak-value scaling).

    Returns
    -------
    ndarray, shape (3,)
        Phase quantities.

    Examples
    --------
    >>> y = complex2abc(1-.5j)
    >>> y
    array([ 1.       , -0.9330127, -0.0669873])

    """
    return np.array([u.real,
                     .5*(-u.real + np.sqrt(3)*u.imag),
                     .5*(-u.real - np.sqrt(3)*u.imag)])

# %%
class Sequence:
    """
    This class represents a sequence generator. The time array must be
    increasing. The output values are interpolated between the data points.

    """

    def __init__(self, times, values, periodic=False):
        self.times = times
        self.values = values
        if periodic is True:
            self.__period = times[-1] - times[0]
        else:
            self.__period = None

    def __call__(self, t):
        """
        Interpolates the output.

        Parameters
        ----------
        t : float
            Time.

        Returns
        -------
        float or complex
            Interpolated output.

        """
        return np.interp(t, self.times, self.values, period=self.__period)

    def __str__(self):
        desc = ('Sequence:\n    times={}\n    values={}')
        return desc.format(self.times, self.values)

# %%
class Step:
    """
    This data class represents a step function.

    """

    def __init__(self, step_time, step_value, initial_value=0):
        self.step_time = step_time
        self.step_value = step_value
        self.initial_value = initial_value

    def __call__(self, t):
        """
        Step function.

        Parameters
        ----------
        t : float
            Time.

        Returns
        -------
        float
            Step output.

        """
        return self.initial_value + (t >= self.step_time)*self.step_value

    def __str__(self):
        desc = ('Step(step_time={}, initial_value={:.1f}, step_value={:.1f})')
        return desc.format(self.step_time,
                           self.initial_value, self.step_value)

# %%
class LUT:
    """
    This class represents a 1-D look-up table with linear interpolation.

    """

    def __init__(self, x_data, y_data):
        self.x_data = x_data
        self.y_data = y_data

    def __call__(self, x):
        """
        Interpolates the output.

        Parameters
        ----------
        x : float
            Reference.

        Returns
        -------
        complex
            Interpolated output value.

        """
        return np.interp(x, self.x_data, self.y_data)


def LagrangianBasis(x, degreeOfPolynomial, pointAt, xSet=None):
    import sympy as sp

    if degreeOfPolynomial <= 0:
        raise ValueError("degree of polynomial has to be greater than 0")

    numDataSet = degreeOfPolynomial+1

    if xSet is None:
        xSet = sp.symbols('x0:{:d}'.format(numDataSet))

    index = list(range(numDataSet))
    index.remove(pointAt)

    return sp.prod([(x-xSet[j])/(xSet[pointAt]-xSet[j]) for j in index])

def LagrangianPoly(x, xSet, fSet):
    if len(xSet) != len(fSet):
        raise ValueError(
            "The number of elements of xSet({:d}) and fSet({:d}) does not match."
            .format(len(xSet), len(fSet)))

    numDataSet = len(xSet)
    degreeOfPolynomial = numDataSet-1

    return sum([LagrangianBasis(x, degreeOfPolynomial, i, xSet)*fSet[i]
                for i in range(numDataSet)])

def Derivative(function, x, orderOfDifference=1):
    import sympy as sp

    return sp.simplify(
        sp.diff(function, x, orderOfDifference).subs([(x, 0)]))


from numpy import *
import matplotlib.pyplot as plt

x = arange(-1, 2, .01)
y = sin(2 * pi * x)

plt.plot(x, y)
# draw a thick red hline at y=0 that spans the xrange
plt.axhline(linewidth=2, color='r')
plt.axvline(linewidth=2, color='r')
plt.axis([-1, 2, -1, 2])

plt.show()
# plt.close()


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

import numpy as np
from scipy import stats
import matplotlib as mpl
import matplotlib.pyplot as plt
import warnings

mpl.rcParams['axes.unicode_minus'] = False
mpl.rcParams['font.sans-serif'] = 'SimHei'

def calc_pearson(x, y):
    std1 = np.std(x)
    # np.sqrt(np.mean(x**2) - np.mean(x)**2)
    std2 = np.std(y)
    cov = np.cov(x, y, bias=True)[0,1]
    return cov / (std1 * std2)

def intro():
    N = 10
    x = np.random.rand(N)
    y = 2 * x + np.random.randn(N) * 0.1
    print(x)
    print(y)
    print('系统计算：', stats.pearsonr(x, y)[0])
    print('手动计算：', calc_pearson(x, y))

def rotate(x, y, theta=45):
    data = np.vstack((x, y))
    # print data
    mu = np.mean(data, axis=1)
    mu = mu.reshape((-1, 1))
    # print mu
    data -= mu
    # print data
    theta *= (np.pi / 180)
    c = np.cos(theta)
    s = np.sin(theta)
    m = np.array(((c, -s), (s, c)))
    return m.dot(data) + mu

def pearson(x, y, tip):
    clrs = list('rgbmycrgbmycrgbmycrgbmyc')
    plt.figure(figsize=(10, 8), facecolor='w')
    for i, theta in enumerate(np.linspace(0, 90, 6)):
        xr, yr = rotate(x, y, theta)
        p = stats.pearsonr(xr, yr)[0]
        # print calc_pearson(xr, yr)
        print('旋转角度：', theta, 'Pearson相关系数：', p)
        str = '相关系数：%.3f' % p
        plt.scatter(xr, yr, s=40, alpha=0.9, linewidths=0.5, c=clrs[i], marker='o', label=str)
    plt.legend(loc='upper left', shadow=True)
    plt.xlabel('X')
    plt.ylabel('Y')
    plt.title('Pearson相关系数与数据分布：%s' % tip, fontsize=18)
    plt.grid(b=True)
    plt.show()

if __name__ == '__main__':
    # warnings.filterwarnings(action='ignore', category=RuntimeWarning)
    np.random.seed(0)

    intro()

    N = 1000
    tip = '一次函数关系'
    x = np.random.rand(N)
    y = np.zeros(N) + np.random.randn(N)*0.001

    # tip = u'二次函数关系'
    # x = np.random.rand(N)
    # y = x ** 2 #+ np.random.randn(N)*0.002

    # tip = u'正切关系'
    # x = np.random.rand(N) * 1.4
    # y = np.tan(x)

    # tip = u'二次函数关系'
    # x = np.linspace(-1, 1, 101)
    # y = x ** 2

    # tip = u'椭圆'
    # x, y = np.random.rand(2, N) * 60 - 30
    # y /= 5
    # idx = (x**2 / 900 + y**2 / 36 < 1)
    # x = x[idx]
    # y = y[idx]

    pearson(x, y, tip)


from math import cos, pi, sqrt
import numpy as np

def dct_2d(image, numberCoefficients=0):

    nc = numberCoefficients # passando para NC para melhorar a visibilidade da formula
    height = image.shape[0]
    width = image.shape[1]
    imageRow = np.zeros_like(image).astype(float)
    imageCol = np.zeros_like(image).astype(float)

    for h in range(height):
        imageRow[h, :] = dct_1d(image[h, :], nc) # aplicando IDCT na linhas
    for w in range(width):
        imageCol[:, w] = dct_1d(imageRow[:, w], nc) # aplicando IDCT nas colunas

    return imageCol

def dct_1d(image, numberCoefficients=0):

    nc = numberCoefficients
    n = len(image)
    newImage= np.zeros_like(image).astype(float)

    for k in range(n):
        sum = 0
        for i in range(n):
            sum += image[i] * cos(2 * pi * k / (2.0 * n) * i + (k * pi) / (2.0 * n))
        ck = sqrt(0.5) if k == 0 else 1
        newImage[k] = sqrt(2.0 / n) * ck * sum

    # salvando os N maiores numeros e zerandos todos os outros
    if nc > 0:
        newImage.sort()
        for i in range(nc, n):
            newImage[i] = 0

    return newImage # retorno de um VETOR

def idct_2d(image):

    height = image.shape[0]
    width =  image.shape[1]
    imageRow = np.zeros_like(image).astype(float)
    imageCol = np.zeros_like(image).astype(float)

    for h in range(height):
        imageRow[h, :] = idct_1d(image[h, :]) # aplicando IDCT na linhas
    for w in range(width):
        imageCol[:, w] = idct_1d(imageRow[:, w]) # aplicando IDCT nas colunas

    return imageCol

def idct_1d(image):

    n = len(image)
    newImage = np.zeros_like(image).astype(float)

    for i in range(n):
        sum = 0
        for k in range(n):
            ck = sqrt(0.5) if k == 0 else 1 # operador tenario para verificar o valor do CK
            sum += ck * image[k] * cos(2 * pi * k / (2.0 * n) * i + (k * pi) / (2.0 * n))

        newImage[i] = sqrt(2.0 / n) * sum

    return newImage