from math import sin, cos, radians, pow, sqrt

import numpy as np

class BadEllipseShape(Exception):
    def __init__(self, msg):
        self.msg = msg

class Ellipse:

    def __init__(self, rotated_rect, coeffs=None):

        """ Converts rotated_rect from cv2.findEllipse into conic equation
        Ellipse is isocontour at Q(x,y) = Ax^2 + Bxy + Cy^2 + Dx + Ey + F = 0
        """

        # Save rotated_rect for error calculation and easier cv2 drawing
        self.rotated_rect = rotated_rect

        if coeffs is None:
            (ell_x0, ell_y0), (ell_w, ell_h), angle = rotated_rect
            axis_x, axis_y = cos(radians(angle)), sin(radians(angle))
            a2 = ell_w * ell_w / 4
            b2 = ell_h * ell_h / 4

            # Initialise ellipse conic equation constants
            self.A = axis_x * axis_x / a2 + axis_y * axis_y / b2;
            self.B = 2 * axis_x * axis_y / a2 - 2 * axis_x * axis_y / b2;
            self.C = axis_y * axis_y / a2 + axis_x * axis_x / b2;
            self.D = (-2 * axis_x * axis_y * ell_y0 - 2 * axis_x * axis_x * ell_x0) / a2 + (2 * axis_x * axis_y * ell_y0 - 2 * axis_y * axis_y * ell_x0) / b2;
            self.E = (-2 * axis_x * axis_y * ell_x0 - 2 * axis_y * axis_y * ell_y0) / a2 + (2 * axis_x * axis_y * ell_x0 - 2 * axis_x * axis_x * ell_y0) / b2;
            self.F = (2 * axis_x * axis_y * ell_x0 * ell_y0 + axis_x * axis_x * ell_x0 * ell_x0 + axis_y * axis_y * ell_y0 * ell_y0) / a2 + (-2 * axis_x * axis_y * ell_x0 * ell_y0 + axis_y * axis_y * ell_x0 * ell_x0 + axis_x * axis_x * ell_y0 * ell_y0) / b2 - 1;
        else:
            self.A,self.B,self.C,self.D,self.E,self.F = coeffs

    def algebraic_distance(self, (px, py)):

        """ Returns value of Q at x,y
        """

        return self.A * px * px + self.B * px * py + self.C * py * py + self.D * px + self.E * py + self.F;

    def algebraic_gradient(self, (px, py)):

        """ Returns gradient of Q at x,y
        """

        grad_x = 2 * self.A * px + self.B * py + self.D
        grad_y = self.B * px + 2 * self.C * py + self.E

        if grad_x * grad_y == 0:
            raise BadEllipseShape('Gradient at point %d, %d = 0' % (px, py))

        return (2 * self.A * px + self.B * py + self.D,
                self.B * px + 2 * self.C * py + self.E);

    def algebraic_gradient_dir(self, (px, py)):

        """ Returns direction of gradient of Q at x,y
        """

        grad_x, grad_y = self.algebraic_gradient((px, py))
        length = sqrt(grad_x ** 2 + grad_y ** 2)
        return (grad_x / length, grad_y / length)

    def distance(self, p):

        """ EOF2 from Rosin '96: Q(x,y) / |grad.Q(x,y)|
        """

        dist = self.algebraic_distance(p)
        grad_x, grad_y = self.algebraic_gradient(p)
        sqgrad = grad_x ** 2 + grad_y ** 2
        return dist / pow(sqgrad, (0.45 / 2))

    ### --- Faster Numpy versions of methods for operating on all points at once ---
    # ##
    # ## requires pts_x ~ [ [x1] [x2] ... [xn] ]
    # ## requires pts_y ~ [ [y1] [y2] ... [yn] ]

    def algebraic_distances(self, pts_x, pts_y):

        """ Returns value of Q at for all points in numpy array [[pts_x pts_y] ... ]
        """

        return self.A * pts_x ** 2 + self.B * pts_x * pts_y + self.C * pts_y ** 2 + self.D * pts_x + self.E * pts_y + self.F;

    def algebraic_gradients(self, pts_x, pts_y):

        """ Returns gradient of Q at for all points in Numpy array
        """

        return (2 * self.A * pts_x + self.B * pts_y + self.D,
                self.B * pts_x + 2 * self.C * pts_y + self.E);

    def algebraic_gradient_dirs(self, pts_x, pts_y):

        """ Returns directions of gradients of Q in Numpy array
        """

        grads_x, grads_y = self.algebraic_gradients(pts_x, pts_y)
        lengths = np.sqrt(grads_x.dot(grads_x) + grads_y.dot(grads_y))
        return (grads_x / lengths, grads_y / lengths)

    def distances(self, pts_x, pts_y):

        """ Numpy array version of EOF2 from Rosin '96: Q(x,y) / |grad.Q(x,y)|
        """

        dists = self.algebraic_distances(pts_x, pts_y)
        grads_x, grads_y = self.algebraic_gradients(pts_x, pts_y)
        sqgrads = grads_x.dot(grads_x) + grads_y.dot(grads_y)
        return dists / (sqgrads ** (0.45 / 2))


import numpy as np

np.random.seed(1013)

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

outputs = [[0], [1], [1], [0]]

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

def sigmoid_derivative(x):
    sx = sigmoid(x)
    return sx * (1-sx)

weights = 2 * np.random.random((3,1)) - 1

print("\n\nLet's start with a simple array of observations.\n\
Each observation is generated by 3 characteristics.\n\
I'll call them 'OB1', 'OB2', & 'OB3'.")
print("\n")
print("Here's 4 observations to start:")
print("\n")
print("Observation 1 - OB1:", inputs[0][0])
print("Observation 1 - OB2:", inputs[0][1])
print("Observation 1 - OB3:", inputs[0][2])
print("\n")
print("Observation 2 - OB1:", inputs[1][0])
print("Observation 2 - OB2:", inputs[1][1])
print("Observation 2 - OB3:", inputs[1][2])
print("\n")
print("Observation 3 - OB1:", inputs[2][0])
print("Observation 3 - OB2:", inputs[2][1])
print("Observation 3 - OB3:", inputs[2][2])
print("\n")
print("Observation 4 - OB1:", inputs[3][0])
print("Observation 4 - OB2:", inputs[3][1])
print("Observation 4 - OB3:", inputs[3][2])
print("\n")
print("So a simple perceptron will resolve to 4 responses.\n\
Our outputs are as follows:")
print("\n")
print("Output 1:", outputs[0][0])
print("Output 2:", outputs[1][0])
print("Output 3:", outputs[2][0])
print("Output 4:", outputs[3][0])

for iteration in range(100000):

    # Weighted sum of inputs / weights
    weighted_sum = np.dot(inputs, weights)
    print("Weighted sum:", weighted_sum[0],
          weighted_sum[1], weighted_sum[2],
          weighted_sum[3])
    # Activate!
    activated_output = sigmoid(weighted_sum)
    print("Activated output:", activated_output[0],
          activated_output[1], activated_output[2],
          activated_output[3])
    # Cac error
    error = outputs - activated_output
    print("Errors:", error[0], error[1], error[2],
          error[3])

    adjustments = error * sigmoid_derivative(weighted_sum)
    print("Adjustments to weight based on error:",
          adjustments[0], adjustments[1], adjustments[2],
          adjustments[3])
    # Update the Weights
    weights += np.dot(inputs.T, adjustments)
    print("New weight:",
          weights)

print("Weights after training")
print(weights)

print("Output after training")
print(activated_output)


import numpy as np
import math

def get_grad_mad(grad,ref):
  dim=grad.size
  diff=ref-grad
  s=sum(np.absolute(diff))
  return s/dim

def unit_vec(v):
  # much faster than np.linalg.norm because safetly checks
  l=math.sqrt((v[0])**2+(v[1])**2+(v[2])**2)
  return [v[0]/l,v[1]/l,v[2]/l]

def np_angle(v1, v2):
    v1_u=unit_vec(v1)
    v2_u=unit_vec(v2)
    return np.arccos(np.clip(np.dot(v1_u, v2_u), -1.0, 1.0))

def get_grad_angle(grad,ref):
  ''' mean angle of all gradient components in deg'''
  import numpy as np
  g=np.reshape(grad,(3,-1))
  r=np.reshape(ref,(3,-1))
  dim=g.shape[1]
  angle=0.0
  for i in range(dim):
    angle+=np.degrees(np_angle(g[:,i],r[:,i]))
  return angle/dim


import numpy as np
from scipy.fftpack import fft
import matplotlib.pyplot as plt
from numpy import array
import sys
import os

file_name = sys.argv[1]
r_file = open(file_name, "r")
lines = r_file.readlines()

stripped = lines[3].strip()

data_in = stripped.split()
data_in = data_in[0:256]

for i in range(0,len(data_in)):
	data_in[i] = float(data_in[i])

### input data => np array conversion
fft_data_in = np.array(data_in)

### fft calculation
fft_data_complex = fft(fft_data_in)

### fft complex => real abs magnitude conversion
fft_data_abs = np.abs(fft_data_complex)

### fft abs magnitude => power spectral density conversion
fft_pden = np.square(fft_data_abs)/len(fft_data_abs)/len(fft_data_abs)

### fft plot range set ( 0~128) : 129ea
fft_pden = fft_pden[0:len(fft_pden)/2+1]

### fft => log conversion
fft_pden_log = 10*np.log10(fft_pden)

### max value find for normalization
fft_max_log=np.max(fft_pden_log[2:256])

### fft normalization to 0dB
fft_pden_log = fft_pden_log - fft_max_log

### plot fft
#plt.plot(fft_pden_log)
#plt.show()

fft_pden_sort = np.sort(fft_pden[1:])[::-1]

max_val = fft_pden_sort[0]
max_val_2nd = fft_pden_sort[1]
fft_sum = np.sum(fft_pden[1:])

sndr = 10*np.log10(max_val/(fft_sum-max_val-fft_pden[-1]/2))
sfdr = 10*np.log10(max_val/max_val_2nd)

print("sndr = %f\n"%sndr)
print("sfdr = %f\n"%sfdr)


import numpy as np
from scipy.optimize import minimize
from numpy.linalg import cholesky
from scipy.linalg import solve_triangular
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import animation, cm

def RBF(X1,X2,l,s):
    dist_norm = np.sum(X1**2, 1).reshape(-1, 1) + np.sum(X2**2, 1) - 2 * X1@X2.T
    return s**2*np.exp(-0.5*dist_norm/l**2)

def MLE(X_train,Y_train):
    Y_train = Y_train.ravel()
    def log_p(theta):
        K = RBF(X_train,X_train, theta[0], theta[1]) + theta[2]**2*np.eye(len(X_train))
        L = cholesky(K)
        A1 = solve_triangular(L, Y_train, lower=True)
        A2 = solve_triangular(L.T, A1, lower=False)
        objective =  np.sum(np.log(np.diagonal(L))) + 0.5 * Y_train.dot(A2) + 0.5*len(X_train) * np.log(2*np.pi)
        return objective
    return log_p

def plot_MLE_posterior_dist(gx,gy,X_train,Y_train,X_pred,theta,title):
    K = RBF(X_train,X_train, theta[0], theta[1]) + theta[2]**2*np.eye(len(X_train))
    L = cholesky(K)
    A1 = solve_triangular(L, Y_train, lower=True)
    A2 = solve_triangular(L.T, A1, lower=False)
    K_s = RBF(X_train,X_pred, theta[0], theta[1])
    mu = K_s.T@A2

    ax = plt.gcf().add_subplot(1, 1, 1, projection='3d')
    ax.plot_surface(gx, gy, mu.reshape(gx.shape), cmap=cm.jet, linewidth=0, alpha=0.35, antialiased=True)
    ax.scatter(X_train[:,0], X_train[:,1], Y_train, c=Y_train, cmap=cm.jet)
    ax.set_title(title)


import math

import numpy
import sympy

from ..helpers import article
from ._helpers import TetrahedronScheme

citation = article(
    authors=["P. Silvester"],
    title="Symmetric quadrature formulae for simplexes",
    journal="Math. Comp.",
    volume="24",
    pages="95-100",
    year="1970",
    url="https://doi.org/10.1090/S0025-5718-1970-0258283-6",
)

def _newton_cotes(n, point_fun):
    degree = n

    # points
    idx = numpy.array(
        [
            [i, j, k, n - i - j - k]
            for i in range(n + 1)
            for j in range(n + 1 - i)
            for k in range(n + 1 - i - j)
        ]
    )
    points = point_fun(idx, n)

    # weights
    if n == 0:
        weights = numpy.ones(1)
        return weights, points, weights, degree

    def get_poly(t, m, n):
        return sympy.prod(
            [
                sympy.poly((t - point_fun(k, n)) / (point_fun(m, n) - point_fun(k, n)))
                for k in range(m)
            ]
        )

    weights = numpy.empty(len(points))
    idx = 0
    for i in range(n + 1):
        for j in range(n + 1 - i):
            for k in range(n + 1 - i - j):
                L = n - i - j - k
                # Compute weight.
                # Define the polynomial which to integrate over the
                # tetrahedron.
                t = sympy.DeferredVector("t")
                g = (
                    get_poly(t[0], i, n)
                    * get_poly(t[1], j, n)
                    * get_poly(t[2], k, n)
                    * get_poly(t[3], L, n)
                )
                # The integral of monomials over a tetrahedron are well-known,
                # see Silvester.
                weights[idx] = numpy.sum(
                    [
                        c
                        * numpy.prod([math.factorial(k) for k in m])
                        * 6.0
                        / math.factorial(numpy.sum(m) + 3)
                        for m, c in zip(g.monoms(), g.coeffs())
                    ]
                )
                idx += 1
    return weights, points, degree, citation

def newton_cotes_closed(n):
    return TetrahedronScheme(
        f"Newton-Cotes (closed, {n})", *_newton_cotes(n, lambda k, n: k / float(n))
    )

def newton_cotes_open(n):
    scheme = TetrahedronScheme(
        f"Newton-Cotes (open, {n})",
        *_newton_cotes(n, lambda k, n: (k + 1) / float(n + 4)),
    )
    if n == 0:
        scheme.degree = 1
    return scheme


# Author: Meduri Venkata Shivaditya
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import minimize, line_search

def aokiFn(x):
    """
        F(x,y) = 0.5 x (x^2 - y)^2 + 0.5 x (x-1)^2
    """
    f = 0.5 * np.square(np.square(x[:][0]) - x[:][1]) + 0.5 * np.square(x[:][0] - 1)
    return f

def aoki(x):
    """
            F(x,y) = 0.5 x (x^2 - y)^2 + 0.5 x (x-1)^2
    """
    f = 0.5 * np.square(np.square(x[0]) - x[1]) + 0.5 * np.square(x[0] - 1)
    return f

def aoki_gd(x):
    """
            First-Order derivative of aoki function(Nabia - 1)
    """
    g_x = 2 * np.dot((np.square(x[0]) - x[1]), x[0]) + x[0] - 1
    g_y = -1 * (np.square(x[0]) - x[1])
    return np.array((g_x, g_y))

def aoki_hess(x):
    """
                Second-Order derivative - Hessian Matrix of aoki function(Nabia - 2)
    """
    g_xx = 6 * np.square(x[0]) - 2*x[1] + 1
    g_xy = -2 * x[0]
    g_yy = 1
    H = np.diag((2,2))
    H[0][0] = g_xx
    H[0][1] = g_xy
    H[1][0] = g_xy
    H[1][1] = g_yy
    return H
def gradient_descent(x0, f, f_prime, hessian=None, adaptative=False):
    """
                    Steepest-Descent algorithm with option for line search
    """
    x_i, y_i = x0
    all_x_i = list()
    all_y_i = list()
    all_f_i = list()

    for i in range(1, 100):
        all_x_i.append(x_i)
        all_y_i.append(y_i)
        all_f_i.append(f([x_i, y_i]))
        dx_i, dy_i = f_prime(np.asarray([x_i, y_i]))
        if adaptative:
            # Compute a step size using a line_search to satisfy the Wolf
            # conditions
            step = line_search(f, f_prime,
                                np.r_[x_i, y_i], -np.r_[dx_i, dy_i],
                                np.r_[dx_i, dy_i], c2=.05)
            step = step[0]
            if step is None:
                step = 0
        else:
            step = 1
        x_i += - step*dx_i
        y_i += - step*dy_i
        if np.abs(all_f_i[-1]) < 1e-16:
            break
    return all_x_i, all_y_i, all_f_i

def main():
    x1 = np.arange(0, 2, 0.1)
    x2 = np.arange(-0.5, 3, 0.1)
    x = np.meshgrid(x1, x2)
    z = aokiFn(np.array(x))
    plt.contour(x1, x2, z, 50)
    plt.plot(1, 1, 'go', MarkerSize=10)
    r = gradient_descent(np.array((0.0, 0.0)), aoki, aoki_gd, hessian = aoki_hess, adaptative = True)
    plt.scatter(r[0][:10], r[1][:10])
    plt.plot(r[0][:10], r[1][:10])
    plt.title('exact line search')
    plt.savefig("steepestDescentDemo.png", dpi = 300)
    plt.show()

if __name__ == "__main__":
    main()


# Class to setup root finding algorithm for practice. One could use
# the Scipy package for more optimized methods.

import numpy as np

class OptimalRoots(object):
    """
    Class to hold values from a root finding algorithm.

    Parameters
    ----------
    root_value: float
        Value of the root.
    func_value: float
        Value of the function.
        Should be close to 0.
    iteration: int
        Number of iterations.
    """

    def __init__(self, root_value, func_value, iteration):
        self.root_value = root_value
        self.func_value = func_value
        self.iteration = iteration

def brent(f, a, b, args=(), max_iteration=100, tolerance=1e-8):
    """
    Calculate roots using Brent's method.

    Parameters
    ----------
    f: function
        Objective function.
    a: float
        Lower bound for the roots.
    b: float
        Upper bound for the roots.
    args: tuple, optional
        Additional arguments for the function.
    max_iteration: int, optional
        Max number of iterations for the root finding algorithm.
    tolerance: float, optional
        Tolerance criteria for the root finding algorithm.

    Returns
    -------
    root_value: float
        Value of the root.
    func_value: float
        Value of the function.
        Should be close to 0.
    iteration: int
        Number of iterations.

    """

    # Calculate end points:
    fa = f(*((a,) + args))
    fb = f(*((b,) + args))

    # Check if fa is less than fb
    if abs(fa) < abs(fb):
        a, b = b, a
        fa, fb, = fb, fa

    # Create point c and d:
    c = a
    fc = fa
    d = 0.0

    # Loop until root found:
    loop_counter = 0
    mflag = 1

    while loop_counter <= max_iteration:
        # Update counter:
        loop_counter = loop_counter + 1

        # Update point:
        if (fa != fc) and (fb != fc):
            # Inverse quadratic interpolation:
            term_one = (a*fb*fc) / ((fa - fb)*(fa - fc))
            term_two = (b*fa*fc) / ((fb - fa)*(fb - fc))
            term_three = (c*fa*fb) / ((fc - fa)*(fc - fb))

            s = term_one + term_two + term_three
        else:
            # Secant method:
            s = b - (fb*(b - a)/(fb - fa))

        # Setup some conditions:
        between_numbers = np.sort([(3.0*a + b)/4.0, b])
        condition_one = (s < between_numbers[0]) and \
                        (s > between_numbers[1])
        condition_two = (mflag == 1) and \
                        (abs(s-b) >= (abs(b-c) * 0.5))
        condition_three = (mflag == 0) and \
                          (abs(s-b) >= (abs(c-d) * 0.5))
        condition_four = (mflag == 1) and (abs(b-c) < abs(tolerance))
        condition_five = (mflag == 0) and (abs(c-d) < abs(tolerance))

        # Check conditions:
        if condition_one or condition_two or condition_three or \
                condition_four or condition_five:
            # Bisection method:
            s = 0.5 * (a+b)
            mflag = 1
        else:
            mflag = 0

        # Calculate f(s):
        fs = f(*((s,) + args))

        # Update points:
        d = c
        c = b
        fc = fb

        if (fa * fs) < 0.0:
            b = s
            fb = fs
        else:
            a = s
            fa = fs

        # Check if fa is less than fb:
        if abs(fa) < abs(fb):
            a, b = b, a
            fa, fb, = fb, fa

        # Check convergence:
        if (abs(fb) <= tolerance) or (abs(fs) <= tolerance) or (
                abs(b-a) <= tolerance):
            return OptimalRoots(s, fs, loop_counter)

        if loop_counter == max_iteration:
            return OptimalRoots(s, fs, loop_counter)


"""
Version: 1.0
Last modified on: 17 November, 2014
Developers: Eduardo Nobre Luis, Fabricio Olivetti de Franca.
     email: eduardo_(DOT)_luis_(AT)_aluno_(DOT)_ufabc_(DOT)_edu_(DOT)_br
          : folivetti_(AT)_ufabc_(DOT)_edu_(DOT)_br
Based on source-code by Michael G. Epitropakis and Xiaodong Li
available at http://goanna.cs.rmit.edu.au/~xiaodong/cec15-niching/competition/
"""

import numpy as np
from numba.decorators import jit, autojit

@autojit
def sphere_func(x):
    return (x**2).sum()

@autojit
def rastrigin_func(x):
    return np.sum( (x**2)-(10.*np.cos( 2.*np.pi*x ))+10)

@autojit
def grienwank_func(x):
    i = np.sqrt(np.arange(x.shape[0])+1.0)
    return np.sum(x**2)/4000.0 - np.prod(np.cos(x/i)) + 1.0

@autojit
def weierstrass_func(x):
    alpha = 0.5
    beta = 3.0
    kmax = 20
    D = x.shape[0]

    c1 = alpha**np.arange(kmax+1)
    c2 = 2.0*np.pi*beta**np.arange(kmax+1)
    c = -D*np.sum(c1*np.cos(c2*0.5))

    C1 = np.tile(c1, (D,1))
    C2 = np.tile(c2, (D,1))
    X = np.reshape(np.repeat(x,kmax+1),(D,kmax+1)) + 0.5
    return (C1*np.cos(C2*X)).sum() + c

@autojit
def F8F2_func(x):
    f2 = 100.0 * ( (x[0]+1.0)**2 - x[1] - 1.0 )**2 + x[0]**2
    return 1.0 + (f2**2)/4000.0 - np.cos(f2)

@autojit
def FEF8F2_func(x):
    D = x.shape[0]
    fi = np.zeros(D)
    for i in np.arange(D-1):
        fi[i] = F8F2_func( (x[i],x[i+1]) )
    fi[D-1] = F8F2_func( (x[D-1],x[0]) )
    return fi.sum()
    '''
    return sum( F8F2_func(y) for y in zip( x[:D-1], x[1:D] ) ) + F8F2_func(
            (x[D-1],x[0]) )
    '''
@autojit
def RotMatrixCondition(D,c):
    A = np.random.randn( D, D )
    P,r = np.linalg.qr(A)
    A = np.random.randn( D, D )
    Q,r = np.linalg.qr(A)[0]
    u = np.random.rand(1,D)
    Dm = np.diag(c**( (u-np.min(u))(np.max(u)-np.min(u)) ))
    M = P.dot(Dm).dot(Q)
    return M

@autojit
def hybrid_comp( x, fn, f, o, sigma, lamb, bias, M ):

    D = x.shape[0]

    #criando w
    dx = x - o[:fn]
    w = np.exp(-(dx**2).sum(axis=1)/(2.0*D*sigma**2))
    '''
    w = np.zeros(fn)
    for i in range(fn):
        w[i] = np.exp(-np.sum( (x - o[i,:])**2 )/(2.0*D*sigma[i]**2))
    #w = np.array([ np.exp(-np.sum( (x - o[i,:])**2 )/(2.0*D*sigma[i]**2)) for i in range(fn) ])
    '''
    # cuidado aqui, como vamos alterar w, temos que armazenar o valor maximo, pois poderemos altera-lo
    # veja tambem que nao precisa do else!
    maxw = w.max()
    maxw10 = maxw**10 # para nao ter que calcular isso n vezes
    w[w != maxw] = w[w != maxw]*(1.0 - maxw10)
    w = w/w.sum()

    f_hat = np.zeros( fn )
    xx = np.ones(D)*5.0/lamb
    dx = dx/lamb

    for i in range(fn):
        f_hat[i] = f[i](dx[i,:].dot(M[i])) / f[i](xx[i,:].dot(M[i]))
    #f_hat = 2000.0*np.array([f[i](((x - o[i,:])/lamb[i,:]).dot(M[i])) / f[i]((xx/lamb[i,:]).dot(M[i])) for i in range(fn)])
    return -(2000*w*f_hat).sum()  # note que embora na documentacao ele retorna o valor positivo, no codigo exemplo eles retornam negativo


'''
This module provides functions for fitting
least-squares regression curves to water
level data.
'''

import datetime

import numpy as np
from matplotlib.dates import date2num

def polyfit(dates: list[datetime.datetime], levels: list[float],
        p: int = 3) -> tuple[np.poly1d, float, list[float]]:
    '''
    Finds a polynomial best-fit (least squares) curve to a given dataset.

    #### Arguments

    `dates` (list[datetime.datetime]): a list of dates where there is level data
    `levels` (list[float]): a list of level data points to fit
    `p` (int, default = 3): the degree of the polynomial curve to use

    #### Returns

    tuple[np.poly1d, float, list[float]]: the callable polynomial function, the constant
    offset used to shift the dates from their mean, and the original dates as numbers, respectively

    #### Raises

    `TypeError`: if inputs are not the specified type(s)
    `ValueError`: if degree is not integer
    '''

    # standard data type and bounds input checks
    if len(dates) != len(levels):
        raise ValueError(f'Inputs must be same length; sizes are {len(dates)} and {len(levels)}')
    if not all([isinstance(d, datetime.datetime) for d in dates]):
        raise TypeError('All dates must be datetime.datetime instances.')
    if not all([isinstance(lev, (float, int)) for lev in levels]):
        raise TypeError('All levels must be numbers.')
    if not (isinstance(p, int) and 0 <= p <= len(dates) - 1):
        raise ValueError('Polynomial degree must be integer, positive and less than the input length.')

    # convert datetime objects to floats
    date_nums = date2num(dates)

    # to minimise the size of the numbers inputted to np.polyfit, offset it by the mean of the dataset
    time_shift = np.mean(date_nums)
    p_coeff = np.polyfit(date_nums - time_shift * np.ones(len(date_nums)), levels, p)

    # convert to a callable polynomial function
    poly = np.poly1d(p_coeff)

    # return the polynomial, the shift and the input data (as floats)
    return (poly, time_shift, date_nums)

def moving_average(dates: list[datetime.datetime], levels: list[float],
        interval: int = 3) -> tuple[list[float], np.ndarray]:
    '''
    Calculates a moving average of the given data, extending the domain to use
    smaller intervals at the endpoints. If the interval is odd, the output domain
    will be the same; if the interval is even, the output domain will be at the
    midpoints of the original.

    #### Arguments

    `dates` (list[datetime.datetime]): list of dates where there is level data
    `levels` (list[float]): list of levels to be averaged
    `interval` (int, default = 3): symmetric window size over which to average

    #### Returns

    tuple[list[float], np.ndarray]: the new domain and averaged values at these points, respectively

    #### Raises

    `ValueError`: if interval is not between 1 and the input length
    '''

    if not (isinstance(interval, int) and 1 <= interval <= len(dates)):
        raise ValueError('Interval size must between 1 and the input length')

    date_nums = date2num(dates)

    # find the moving average, ignoring the ends
    averages = np.convolve(levels, np.ones(interval), mode='valid') / interval

    if interval % 2 == 1:
        # odd number of points: append original values to either end
        half_end = interval // 2
        averages = np.insert(averages, 0, levels[:half_end])
        averages = np.insert(averages, len(averages), levels[-1 * half_end:])
        return date_nums, averages

    else:
        # even number of points: append moving averages of the missing intervals to either end,
        # and move date values over by half an interval to account for this
        half_end = round((interval / 2) - 1)
        averages = np.insert(averages, 0, np.convolve(levels[:half_end + 1], (0.5, 0.5), mode='valid'))
        averages = np.insert(averages, len(averages),
            np.convolve(levels[-1 * half_end - 1:], (0.5, 0.5), mode='valid'))
        date_interval = date_nums[1] - date_nums[0]
        date_nums = [dn + date_interval / 2 for dn in date_nums]
        date_nums.pop()
        return date_nums, averages

def identify_potentially_bad_data(station_name: str, levels: list[float],
        data_origin_type: str = None, **kwargs: dict) -> set[str]:
    '''
    Check for suspicious values within a station's water level records. Tests for:

    1. If a level is given as a tuple instead of a float
    2. If negative (when not a tidal station) or extremely large values
    3. If the level fluctuates significantly between readings

    and returns appropriate warning messages or an empty set if there are none.

    #### Arguments

    `station_name` (str): string name of a station to be displayed. Does not have to be the official name.
    `levels` (list[float]): list of level data to be checked
    `data_origin_type` (str, default = None): either 'River Level', 'Tidal', 'Groundwater',
        'Rainfall', representing what this data input is from, or unspecified (None)

    #### Returns

    set[str]: a set of string messages detailing the potential errors in the data.
    '''

    NEGATIVE_LEVEL_TO_ZERO      = kwargs.get('negative_level_to_zero',      True)   # noqa
    REPLACE_TUPLE_WITH_INDEX    = kwargs.get('replace_tuple_with_index',    1)      # noqa
    TOO_HIGH_CUTOFF             = kwargs.get('too_high_cutoff',             2500)   # noqa
    TOO_HIGH_TO_PREVIOUS_LEVEL  = kwargs.get('too_high_to_previous_level',  True)   # noqa

    IS_TIDAL        = getattr(kwargs.get('station_obj', None), 'is_tidal', False)   # noqa

    flags = set()

    for i in range(len(levels)):

        # Check for erroneous case: level was a tuple instead of float
        if not isinstance(levels[i], (float, int)):

            if isinstance(correct_level := levels[i][REPLACE_TUPLE_WITH_INDEX], (float, int)):
                warn_str = f"Data for {station_name} station on date may be unreliable. "
                warn_str += f"Found water level value {levels[i]}. \n"
                warn_str += f"This has been replaced with {correct_level}."
                flags.add(warn_str)
                levels[i] = correct_level
            else:
                warn_str = f"Data for {station_name} station on date may be unreliable. "
                warn_str += f"Found water level value {levels[i]}. This item was \n"
                warn_str += "unable to be resolved into a numerical value, and has not been altered."
                flags.add(warn_str)

        # Check for potentially invalid values: negative or impossibly high
        if levels[i] < 0 and not IS_TIDAL:

            warn_str = f"Data for {station_name} station may be unreliable. "
            warn_str += "Some water levels were found to be negative, \nand this station is not tidal. "
            if NEGATIVE_LEVEL_TO_ZERO:
                warn_str += "These have been set to 0 m."
                levels[i] = 0

            flags.add(warn_str)

        if levels[i] > TOO_HIGH_CUTOFF and len(levels) >= 2:

            warn_str = f"Data for {station_name} station may be unreliable. "
            warn_str += f"Some water levels were found to be very high, above {TOO_HIGH_CUTOFF} m.\n"
            if TOO_HIGH_TO_PREVIOUS_LEVEL:
                warn_str += "These have been set to whatever the value before the spike."
                levels[i] = levels[i - 1]

            flags.add(warn_str)

    # Check for potentially invalid values: many sudden changes (allow for rainfall etc)
    if data_origin_type == 'River Level':
        if has_rapid_fluctuations(levels):

            warn_str = f"Data for {station_name} station may be unreliable. "
            warn_str += "There are many sudden spikes between consecutive measurements. \n"
            warn_str += "These values have not been altered."

            flags.add(warn_str)

    return flags

def has_rapid_fluctuations(levels: list[float], interval: int = 3, tol: float = 5e-3) -> bool:
    '''
    Applies a simple statistical estimate to check whether a
    dataset has rapid changes between values. Aims to allow daily
    cycles which are natural for tidal stations.

    The test applied is: mean squared error of the levels relative
    to the moving (interval-specified) average must be less than a
    (tol-specified) fraction of the squared mean level.

    #### Arguments

    `levels` (list[float]): list of level data to analyse
    `interval` (int, default = 3): the interval of the moving average used in the computation
    `tol` (float, default = 5e-3): the relative tolerance when compared to the mean square level

    #### Returns

    bool: True if there are rapid fluctuations detected; False otherwise
    '''

    import numpy as np

    lv = np.array(levels)
    _, avg = moving_average(*zip(*enumerate(levels)), interval)
    mse = ((lv - avg) ** 2).mean()

    return mse / (lv.mean() ** 2) >= tol


# -*- coding: utf-8 -*-
# author: ysoftman
# python version : 3.x
# desc : numpy 테스트

# pip install np
import numpy as np

# start=1 부터 stop=10 까지 num=20개 값을 균일하게 생성
l = np.linspace(1, 10, 20)
print(l)
# start=1 부터 stop=10 까지 num=20개 값을 균일하게 생성, int 값으로
l = np.linspace(1, 10, 20, dtype=int)
print(l)


import numpy as np

import pyfma

# See <https://www.johndcook.com/blog/2020/05/31/kahan-determinant/>
def test_determinant():
    a = np.pi
    b = np.e
    c = 355 / 113
    d = 23225 / 8544
    naive = a * d - b * c

    # This dot-product is hard to compute, cf.
    # ```
    # accupy.cond([np.pi, np.e], [23225 / 8544, -355 / 113])
    # ```
    # ```
    # 48525073.17687677
    # ```
    # When computing all values a,b,c,d with many digits,  mpmath and wolframalpha both
    # give the same result
    # ```
    # mp.fdot([mp.pi, mp.e], [mp.mpf("23225 / 8544"), -mp.mpf("355 / 113")])
    # https://www.wolframalpha.com/input/?i=det%28%5B%5Bpi%2C+e%5D%2C+%5B+355+%2F+113%2C+23225+%2F+8544%5D%5D%29
    # ```
    # ```
    # -7.0394408762183022632862048647535951845717763186391e-7
    # -7.039440876218302263286204864753595184571776388044517499102e-7
    # ```
    # When starting with the np/Python rounding of a,b,c,d, accupy and mpmath give the
    # same result:
    # ```
    # mp.fdot([np.pi, np.e], [23225 / 8544, -355 / 113])
    # accupy.fdot([np.pi, np.e], [23225 / 8544, -355 / 113]).item()
    # ```
    # ```
    # [-7.0394408]801519439901320097075016572198321233241348743e-7
    # [-7.0394408]80151944e-07
    # ```
    # (The brackets mark the correct digits.)
    # The naive computation gives
    # ```
    # -7.039440870215685e-07
    # ```
    # which actually isn't much further from the real value either.
    ref = -7.0394408762183022632862048647535951845717763186391e-7
    print("naive:", naive, naive - ref)
    assert abs(naive - ref) < 1.0e-8 * abs(ref)

    w = b * c
    e = pyfma.fma(-b, c, w)
    f = pyfma.fma(a, d, -w)
    kahan = f + e
    print("kahan:", kahan, kahan - ref)

    assert abs(kahan - ref) < 1.0e-9 * abs(ref)


import numpy

def compute_fundamental(x1, x2):
  '''Computes the fundamental matrix from corresponding points x1, x2 using
  the 8 point algorithm.'''
  n = x1.shape[1]
  if x2.shape[1] != n:
    raise ValueError('Number of points do not match.')

  # Normalization is done in compute_fundamental_normalized().
  A = numpy.zeros((n, 9))
  for i in range(n):
    A[i] = [x1[0, i] * x2[0, i],  x1[0, i] * x2[1, i],  x1[0, i] * x2[2, i],
            x1[1, i] * x2[0, i],  x1[1, i] * x2[1, i],  x1[1, i] * x2[2, i],
            x1[2, i] * x2[0, i],  x1[2, i] * x2[1, i],  x1[2, i] * x2[2, i],
           ]

  # Solve A*f = 0 using least squares.
  U, S, V = numpy.linalg.svd(A)
  F = V[-1].reshape(3, 3)

  # Constrain F to rank 2 by zeroing out last singular value.
  U, S, V = numpy.linalg.svd(F)
  S[2] = 0
  F = numpy.dot(U, numpy.dot(numpy.diag(S), V))
  return F / F[2, 2]

def compute_fundamental_normalized(x1, x2):
  '''Computes the fundamental matrix from corresponding points x1, x2 using
  the normalized 8 point algorithm.'''
  n = x1.shape[1]
  if x2.shape[1] != n:
    raise ValueError('Number of points do not match.')

  # normalize.
  x1 = x1 / x1[2]
  mean_1 = numpy.mean(x1[:2], axis=1)
  S1 = numpy.sqrt(2) / numpy.std(x1[:2])
  T1 = numpy.array([[S1, 0, -S1 * mean_1[0]],
                    [0, S1, -S1 * mean_1[1]],
                    [0, 0, 1]])
  x1 = numpy.dot(T1, x1)

  x2 = x2 / x2[2]
  mean_2 = numpy.mean(x2[:2], axis=1)
  S2 = numpy.sqrt(2) / numpy.std(x2[:2])
  T2 = numpy.array([[S2, 0, -S2 * mean_2[0]],
                    [0, S2, -S2 * mean_2[1]],
                    [0, 0, 1]])
  x2 = numpy.dot(T2, x2)

  F = compute_fundamental(x1, x2)

  # denormalize.
  F = numpy.dot(T1.T, numpy.dot(F, T2))
  return F / F[2, 2]

def compute_right_epipole(F):
  '''Returns e with F * e = 0 (call with F.T for left epipole).'''
  U, S, V = numpy.linalg.svd(F)
  e = V[-1]  # S is diag([l1, l2, 0]). e's scale is arbitrary.
  return e / e[2]

def plot_epipolar_line(im, F, x, epipole=None, show_epipole=True):
  '''Plot the epipole and epipolar line F*x = 0.'''
  import pylab

  m, n = im.shape[:2]
  line = numpy.dot(F, x)

  t = numpy.linspace(0, n, 100)
  lt = numpy.array([(line[2] + line[0] * tt) / (-line[1]) for tt in t])

  ndx = (lt >= 0) & (lt < m)
  pylab.plot(t[ndx], lt[ndx], linewidth=2)

  if show_epipole:
    if epipole is None:
      epipole = compute_right_epipole(F)
    pylab.plot(epipole[0] / epipole[2], epipole[1] / epipole[2], 'r*')

def triangulate_point(x1, x2, P1, P2):
  '''Given two image coordinates x1, x2 of the same point X under different
  projections P1, P2, recovers X.'''
  M = numpy.zeros((6, 6))
  M[:3, :4] = P1
  M[:3, 4] = -x1

  M[3:, :4] = P2
  M[3:, 5] = -x2  # Intentionally 5, not 4.

  U, S, V = numpy.linalg.svd(M)
  X = V[-1, :4]
  return X / X[3]

def triangulate(x1, x2, P1, P2):
  '''Given n pairs of points, returns their 3d coordinates.'''
  n = x1.shape[1]
  if x2.shape[1] != n:
    raise ValueError('Number of points do not match.')

  X = [triangulate_point(x1[:, i], x2[:, i], P1, P2) for i in range(n)]
  return numpy.array(X).T

def compute_P(x, X):
  '''Computes camera matrix from corresponding (homogeneous)
  2D and 3D points.'''
  n = x.shape[1]
  if X.shape[1] != n:
    raise ValueError('Number of points do not match.')

  M = numpy.zeros((3 * n, 12 + n))
  for i in range(n):
    M[3 * i          , 0:4] = X[:, i]
    M[3 * i + 1      , 4:8] = X[:, i]
    M[3 * i + 2      , 8:12] = X[:, i]
    M[3 * i:3 * i + 3, i + 12] = -x[:, i]

  U, S, V = numpy.linalg.svd(M)
  return V[-1, :12].reshape((3, 4))

def skew(a):
  '''Skew matrix A such that a x v = A*v for any v.'''
  return numpy.array([[0, -a[2], a[1]],
                      [a[2], 0, -a[0]],
                      [-a[1], a[0], 0]])

def compute_P_from_fundamental(F):
  '''Computes second camera matrix, assuming P1 = [I 0].
  Only up to a homography, since no calibration is given.'''
  e = compute_right_epipole(F.T)  # left epipole
  Te = skew(e)
  return numpy.vstack((numpy.dot(Te, F.T).T, e)).T

def compute_P_from_essential(E):
  # make sure E is rank 2
  U, S, V = numpy.linalg.svd(E)
  if numpy.linalg.det(numpy.dot(U, V)) < 0:
    V = -V
  E = numpy.dot(U, numpy.dot(numpy.diag([1, 1, 0]), V))

  # create matrices ("Hartley p 258" XXX)
  Z = skew([0, 0, -1])  # FIXME: Unused?
  W = numpy.array([[0, -1, 0], [1, 0, 0], [0, 0, 1]])

  P2 = [numpy.vstack((numpy.dot(U, numpy.dot(W, V)).T,  U[:,2])).T,
        numpy.vstack((numpy.dot(U, numpy.dot(W, V)).T, -U[:,2])).T,
        numpy.vstack((numpy.dot(U, numpy.dot(W.T, V)).T,  U[:,2])).T,
        numpy.vstack((numpy.dot(U, numpy.dot(W.T, V)).T, -U[:,2])).T]
  return P2

class RansacModel(object):
  def fit(self, data):
    data = data.T
    x1 = data[:3, :8]
    x2 = data[3:, :8]
    return compute_fundamental_normalized(x1, x2)

  def get_error(self, data, F):
    data = data.T
    x1 = data[:3]
    x2 = data[3:]

    # Sampson distance as error.
    Fx1 = numpy.dot(F, x1)
    Fx2 = numpy.dot(F, x2)
    denom = Fx1[0]**2 + Fx1[1]**2 + Fx2[0]**2 + Fx2[1]**2
    err = (numpy.diag(numpy.dot(x1.T, numpy.dot(F, x2))))**2 / denom
    return err

def F_from_ransac(x1, x2, model, maxiter=5000, match_threshold=1e-6):
  import ransac
  data = numpy.vstack((x1, x2))
  F, ransac_data = ransac.ransac(data.T, model, 8, maxiter, match_threshold, 20,
                                 return_all=True)
  return F, ransac_data['inliers']


import numpy as np

class GaussianMLE(object):
    # http://www.sciencedirect.com/science/article/pii/0024379585900497
    def __init__(self, X):
        # X is a numpy array of size (observations) x (variables)
        self.X = X
        self.N = X.shape[1]
        self.M = X.shape[0]
        self.mu = np.zeros(X.shape[1], dtype=np.float)
        self.sigma = np.zeros((X.shape[1],X.shape[1]), dtype=np.float)
        self.fit = False
        # index mask for conditional probability
        self.mask = np.ones(X.shape[1], dtype=bool)
        self.reverse_mask = np.zeros(X.shape[1], dtype=bool)
        self.conditional_fit = False

    def _mean(self):
        # estimate the sample mean for each variable
        mean = np.mean(self.X, axis=0, dtype=np.float)
        self.mu = mean
        return mean

    def _sigma(self):
        # estimate the variance covariance matrix
        diffs = self.X - self.mu
        dot = np.dot(diffs.T, diffs)
        sigma = dot/self.M
        self.sigma = sigma
        return sigma

    def estimate(self):
        # estimate the distribution
        self._mean()
        self._sigma()
        print('Multivariate Gaussian distribution fit with MLE')
        print('The mean vector shape is:')
        print(self.mu.shape)
        print('The variance-covariance matrix shape is:')
        print(self.sigma.shape)
        self.fit = True

    def draw(self, size=1):
        # generate a random draw from the estimated distribution
        if not self.fit:
            self.estimate()
        return np.random.multivariate_normal(self.mu, self.sigma, size)

    def _conditional_sigma(self):
        # want to partition the variance covariance matrix into
        # dependent and indepenedent var covar parts

        ind_ind = self.sigma[self.mask, :][:, self.mask]
        dep_dep = self.sigma[self.reverse_mask, :][:, self.reverse_mask]
        ind_dep = self.sigma[self.mask, :][:, self.reverse_mask]
        dep_ind = self.sigma[self.reverse_mask, :][:, self.mask]

        # pseudo inverse
        invert_ind_ind = np.linalg.pinv(ind_ind)

        self.conditional_sigma = dep_dep - np.dot(dep_ind, np.dot(invert_ind_ind, ind_dep))
        return self.conditional_sigma

    def _conditional_mean(self, independent):
        # create conditional mean

        # partition means
        dep_means = self.mu[self.reverse_mask]
        ind_means = self.mu[self.mask]

        # slice var covar
        dep_ind = self.sigma[self.reverse_mask, :][:, self.mask]
        ind_ind = self.sigma[self.mask, :][:, self.mask]
        # pseudo inverse
        invert_ind_ind = np.linalg.pinv(ind_ind)

        diff = independent - ind_means

        cond_mean = dep_means + np.dot(dep_ind, np.dot(invert_ind_ind, diff))
        self.conditional_mu = cond_mean
        return cond_mean

    def conditional_estimate(self, dependent, independent):
        # estimate a conditional distribution
        # <dependent> is the list of indices of variables that are to be drawn
        # <independent> is np array of realizations of the variables that <dependent>
        # are being conditioned on

        assert len(independent) + len(dependent) == self.N

        self.mask[dependent] = False
        self.reverse_mask = np.logical_xor(np.ones(self.X.shape[1], dtype=bool), self.mask)

        if not self.fit:
            self.estimate()

        self._conditional_sigma()
        self._conditional_mean(independent)

        print('The conditional distribution estimated. The conditional means are:')
        print(self.conditional_mu)
        print('The conditional var-covar matrix is:')
        print(self.conditional_sigma)

        self.conditional_fit = True

    def conditional_draw(self, size=1):
        # generate a random draw from the estimated conditional distribution
        if not self.conditional_fit:
            raise RuntimeError('Conditional distribution not estimated yet')
        return np.random.multivariate_normal(self.conditional_mu, self.conditional_sigma, size)


import numpy as np

def init_centroid(X, n_data, k):
    # 各データ点の中からクラスタの重心となる点をk個ランダムに選択
    idx = np.random.permutation(n_data)[:k]
    centroids = X[idx]
    return centroids

def compute_distances(X, k, n_data, centroids):
    distances = np.zeros((n_data, k))
    for idx_centroids in range(k):
        dist = np.sqrt(np.sum((X - centroids[idx_centroids]) ** 2, axis=1))
        distances[:, idx_centroids] = dist
    return distances

def k_means(k, X, max_iter=300):
    """
    X.shape = (データ数, 次元数)
    k = クラスタ数
    """
    n_data, n_features = X.shape

    # 重心の初期値
    centroids = init_centroid(X, n_data, k)

    # 新しいクラスタを格納するための配列
    new_cluster = np.zeros(n_data)

    # 各データの所属クラスタを保存する配列
    cluster = np.zeros(n_data)

    for epoch in range(max_iter):
        # 各データ点と重心との距離を計算
        distances = compute_distances(X, k, n_data, centroids)

        # 新たな所属クラスタを計算
        new_cluster = np.argmin(distances, axis=1)

        # すべてのクラスタに対して重心を再計算
        for idx_centroids in range(k):
            centroids[idx_centroids] = X[new_cluster == idx_centroids].mean(axis=0)

        # クラスタによるグループ分けに変化がなかったら終了
        if (new_cluster == cluster).all():
            break

        cluster = new_cluster

    return cluster

if __name__=="__main__":
    X = np.arange(10*5).reshape(10, 5)
    print("X\n", X)
    k = 3
    print("k=",k)

    cluster = k_means(k, X, max_iter=300)
    print("cluster", cluster)


#######################
#Jose Barreiros, 2016 #
#Complex Systems      #
#Cornell University   #
#######################

#plot return map with Rugge Kutta
import math, operator, random
import numpy as np
import matplotlib.pyplot as plt

dataX0 = []
dataY0 = []
dataZ0 = []
yList = []
xList = []
zList = []
y_v=[]
z_v=[]

count = 1
t = 1
##
x = 0
y = 0
z = 0

xList.append(x)
yList.append(y)
zList.append(z)

h = .01

a = .398
b = 2
c = 4

def f(x,y,z):
	global a,b,c
	dxdt = -y-z
	return dxdt

def g(x,y,z):
	global a,b,c
	dydt = x + a * y
	return dydt

def e(x,y,z):
	global a,b,c
	dzdt = b + z * (x - c)
	return dzdt

def rk4o(x, y, z):
	global h
	k1x = h*f(x, y, z)
	k1y = h*g(x, y, z)
	k1z = h*e(x, y, z)

	k2x = h*f(x + k1x/2.0, y + k1y/2.0, z + k1z/2.0)
	k2y = h*g(x + k1x/2.0, y + k1y/2.0, z + k1z/2.0)
	k2z = h*e(x + k1x/2.0, y + k1y/2.0, z + k1z/2.0)

	k3x = h*f(x + k2x/2.0, y + k2y/2.0, z + k2z/2.0)
	k3y = h*g(x + k2x/2.0, y + k2y/2.0, z + k2z/2.0)
	k3z = h*e(x + k2x/2.0, y + k2y/2.0, z + k2z/2.0)

	k4x = h*f(x + k3x, y + k3y, z + k3z)
	k4y = h*g(x + k3x, y + k3y, z + k3z)
	k4z = h*e(x + k3x, y + k3y, z + k3z)

	x = x + k1x/6.0 + k2x/3.0 + k3x/3.0 + k4x/6.0
	y = y + k1y/6.0 + k2y/3.0 + k3y/3.0 + k4y/6.0
	z = z + k1z/6.0 + k2z/3.0 + k3z/3.0 + k4z/6.0

	return [x,y,z]

X0 = []
Y0 = []
Z0 = []
previousLocalMax = x
localMax = x
t = 1
changeInTime = h
avgChange = []
LastPoint = changeInTime
while changeInTime < 20000 and len(dataX0) < 1000:

	[x,y,z] = rk4o(xList[t-1], yList[t-1], zList[t-1])

	xList.append(x)
	yList.append(y)
	zList.append(z)
	if 201 < changeInTime:
                # Print y and z points when x is between 0.3 and 0.4
		if x <= 0.4 and x >= 0.3:
			print(y , "," , z)
			y_v.append(y)
			z_v.append(z)

		if x < xList[t-1] and xList[t-2] < xList[t-1]:
			previousLocalMax = localMax
			localMax = xList[t-1]
			dataX0.append(localMax)
			dataY0.append(previousLocalMax)
			# Calculate the change between this and last point
			avgChange.append(changeInTime - LastPoint)
			LastPoint = changeInTime

	t = t + 1
	changeInTime += h
#plt.plot(dataY0, dataX0, 'bo', y_v, z_v, 'mo')

ax=plt.figure(facecolor='white')
plt.plot(dataY0, dataX0, 'b.', markersize=2)
ax.suptitle('Return map - Rossler attractor\n a=%s b=%s c=%s'%(str(a),str(b),str(c)),fontsize=18)
plt.xlabel('X[n]', fontsize=14)
plt.ylabel('X[n+1]', fontsize=14)
plt.show()

#plt.plot(y_v, z_v, 'mo')
#plt.show()


""" Example of ordinary Monte Carlo rejection sampling integrating circle area """
import numpy as np
import scipy.stats
from matplotlib.colors import Normalize
from pylab import *; ion()
import probayes as pb

# PARAMETERS
radius = 1.
set_size = {-10000}

# SETUP CIRCLE FUNCTION AND RVs
def inside(x, y):
  return np.array(x**2 + y**2 <= radius**2, dtype=float)

def norm2d(x, y, loc=0., scale=radius):
  return scipy.stats.norm.pdf(x, loc=loc, scale=scale) * \
         scipy.stats.norm.pdf(y, loc=loc, scale=scale)

xy_range = [-radius, radius]
x = pb.RV("x", xy_range)
y = pb.RV("y", xy_range)

# DEFINE STOCHASTIC CONDITION
xy = x & y
xy.set_prob(inside)

# DEFINE PROPOSAL DENSITY AND COEFFICIENT VARIABLE
xy.set_prop(norm2d)
coef_max = float(norm2d(radius, 1.))
coef = pb.RV('coef', {0., coef_max})
coefs = coef(set_size)
p_prop = xy.propose({(x,y): set_size}, suffix=False)
thresholds = coefs['coef'] * p_prop.prob

# CALL TARGET DENSITY AND APPLY REJECTION SAMPLING
xy_vals = p_prop('marg')
p_xy = xy(xy_vals)
accept = p_xy.prob >= thresholds
reject = np.logical_not(accept)
expectation = np.mean(accept)
square_area = 4. * radius**2
circle_area = square_area * expectation

# PLOT DATA
figure()
x_accept, x_reject = xy_vals['x,y'][0][accept], xy_vals['x,y'][0][reject]
y_accept, y_reject = xy_vals['x,y'][1][accept], xy_vals['x,y'][1][reject]
c_norm = Normalize(vmin=np.min(p_prop.prob), vmax=np.max(p_prop.prob))
c_map = cm.jet(c_norm(p_prop.prob))
c_accept, c_reject = c_map[accept], c_map[reject]
scatter(x_accept, y_accept, color=c_accept, marker='.', linewidths=0.5)
scatter(x_reject, y_reject, color=c_reject, marker='x', linewidths=0.5)
xlabel('x')
ylabel('y')
title('Est. circle area={}'.format(circle_area))


# importing a "secure" pRNG source
# note: the `random` stdlib also exists and would be perfectly applicable here.
#       `secrets` was chosen out of personal preference.
from secrets import randbelow

import numpy as np

# Libraries in the SciPy stack tend to shorthand their module names upon import.
# In an effort to be consistent with existing documentation, I will follow that convention.

# Generating some test data
vanilla_list = [randbelow(2 ** 32) for _ in range(500)]

# Declaring an 1d array from the vanilla list.
array = np.array(vanilla_list)

# Taking the sum via vanilla means
print(sum(vanilla_list))
# Taking the sum via numpy
print(array.sum())

# Taking the subset of values that are even
evens = array[array % 2 == 0]
# and the equivalent pure-python list:
evens_list = [value for value in vanilla_list if value % 2 == 0]  # "list comprehension"

# multiplying all values of the array by a scalar
double_array = array * 2
double_list = [value * 2 for value in vanilla_list]  # "list comprehension"

# Taking the dot product of two arrays:
v1 = np.array([1, 2, 3])
v2 = np.array([4, 5, 6])
dot = v1.dot(v2)
# alternatively,
dot = v1 @ v2
print(dot)

# Note: the pure-python equivalent to this is not clean.


import numpy as np
from scipy.optimize import fmin_l_bfgs_b

def simpSample(f, numTest, xMin, xMax, verb=False, maxIter=1000):
    '''
    Simple Rejection Sampling

    Args:
        f (callable): Probability density function (may be
            unnormalized).  Function or lambda function.
        numTest (int): Number of samples to generate
        xMin (float): Minimum sampling range
        xMax (float): Maximum sampling range
        verb (Optional[bool]): Print number of iterations taken
            (default False)
        maxIter (Optional[int]): Maximum number of iterations to
            take (default 1000).

    Returns:
        X (ndarray): Array of samples from given PDF

    Raises:
        ArithmeticError: if maximum number of iterations is reached
            before enough samples are collected

    Example:
        #sample from a log-uniform distribution
        X = simpSample(lambda x: 1./x, 1000, 0.1,100)
    '''

    #find max value
    #first do a coarse grid to get ic
    dx = np.linspace(xMin,xMax,100)
    ic = np.argmax(f(dx))
    g = lambda x: -f(x)
    M = fmin_l_bfgs_b(g,[dx[ic]],approx_grad=True,bounds=[(xMin,xMax)])
    M = f(M[0])

    #initialize
    n = 0
    X = np.zeros(numTest);
    numIter = 0;

    nSamp = np.max([2*numTest,1e6])
    while n < numTest and numIter < maxIter:
        xd = np.random.random(nSamp) * (xMax - xMin) + xMin
        yd = np.random.random(nSamp) * M
        pd = f(xd)

        xd = xd[yd < pd]
        X[n:min(n+len(xd), numTest)] = xd[:min(len(xd),numTest-n)]
        n += len(xd)
        numIter += 1

    if numIter == maxIter:
        raise ArithmeticError("Failed to converge.")

    if verb:
        print 'Finished in '+repr(numIter)+' iterations.'

    return X


import numpy as np

def squared(x):
    """
    Input:
    x, float or int
        Base value
    Output:
        float, base to the power 2.
    """

    return float(x) * float(x)

def pow(x, exponent):
    """
    Input:
        x: float or int
            Base value
        exponent: float or int
            Exponent value

    Output:
        float, base to the power exponent.
    """
    return float(x) ** n

def CentralDiff(f, x, dx):
    """
    Computes the 1st derivative of a function of a single variable f(x)
    by central differencing:

    \begin{equation}
    f'(x) \approx \frac{f(x + dx) - f(x - dx)}{2 * dx}
    \end{equation}

    Input:
        f: function
            Function of a single variable that returns a float
        x:
            Point at which the derivative will be computed
        dx:
            Step size in the central difference algorithm
    """
    return (f(x + dx) - f(x - dx)) / (2*dx)

def Newton(f, x0, tol=1e-5, dx=1e-8):
    """
    Root finding using Newton's method.
    Uses central differencing to find the derivative in the formula:

    \begin{equation}
    x_n+1 = x_n - f(x) / f'(x)
    \end{equation}

    Input:
        f: function
            Function of a single variable that returns a float.
        x0: Float
           Initial guess at root.
        tol: Float
           Algorithm stops when abs(x - x0) >= tol.
        dx: Float
           Step size for central differencing
    """

    x = x0 - f(x0)/CentralDiff(f, x0, dx)

    while(np.abs(x - x0) >=tol):
        x0 = x
        x = x0 - f(x0)/CentralDiff(f, x0, dx)

    return x


"""
"""

import cvxpy as cp
import fire
import numpy as np

from swissknife import utils

N = 10
n = 100

q = [(np.exp(-(i - 30) ** 2 / 100) + 2 * np.exp(-(i - 68) ** 2 / 100)) for i in range(1, 101)]
q = np.array(q)
r = np.array([np.exp(-(i - 50) ** 2 / 100) for i in range(1, 101)])

q = q / sum(q)
r = r / sum(r)

def plot(p):
    utils.plot_wrapper(
        plots=[
            dict(x=np.arange(n), y=p.value[i], label=f'{i}')
            for i in range(N)
        ]
    )

def parta():
    p = cp.Variable(shape=(N, n))
    cons = [
        p[0] == q,
        p[N - 1] == r,
    ]
    cons += [p[i] >= 0. for i in range(N)]
    cons += [sum(p[i]) == 1. for i in range(N)]

    obj = cp.Minimize(
        sum([
            cp.norm2(_next - _this) ** 2 for _next, _this in zip(p[1:], p[:-1])
        ])
    )
    cp.Problem(objective=obj, constraints=cons).solve()

    plot(p)

def partb():
    p = cp.Variable(shape=(N, n))
    cons = [
        p[0] == q,
        p[N - 1] == r,
    ]
    cons += [p[i] >= 0. for i in range(N)]
    cons += [sum(p[i]) == 1. for i in range(N)]

    loss = 0
    for _next, _this in zip(p[1:], p[:-1]):
        loss = loss + sum(_next) + sum(_this)

        for ui, vi in zip(_next, _this):
            loss = loss - 2. * cp.geo_mean(
                cp.vstack([ui, vi])
            )

    obj = cp.Minimize(loss)
    cp.Problem(objective=obj, constraints=cons).solve(solver=cp.ECOS)
    for i in range(N):
        print(sum(p[i].value))

    plot(p)

def partc():
    p = cp.Variable(shape=(N, n))
    cons = [
        p[0] == q,
        p[N - 1] == r,
    ]
    cons += [p[i] >= 0. for i in range(N)]
    cons += [sum(p[i]) == 1. for i in range(N)]

    pcum = cp.cumsum(p, axis=1)
    obj = cp.Minimize(
        sum([
            cp.norm_inf(_next - _this) for _next, _this in zip(pcum[1:], pcum[:-1])
        ])
    )
    cp.Problem(objective=obj, constraints=cons).solve()

    plot(p)

def main():
    # parta()
    partb()
    # partc()

if __name__ == "__main__":
    fire.Fire(main)


# coding: utf-8
import numpy as np
import scipy as sp
import math
from scipy.misc import comb
import sys

def detect_burst(relevant_list, total_list):
    """ Assume (num. of relevant docs, total num. docs) as the two input lists
        returns state costs
    """

    state_costs = [(0.0, 0.0)] # for the initial step
    s = 2
    R = sum(relevant_list)
    D = sum(total_list)
    p_0 = (1.0 * R) / D
    p_1 = p_0 * s
    n = len(relevant_list)
    for i in range(n):
        r_t = relevant_list[i]
        d_t = total_list[i]
        nonburst_state_cost = calc_state_cost(0, r_t, d_t, p_0)
        burst_state_cost = calc_state_cost(1, r_t, d_t, p_1)
        state_costs.append((nonburst_state_cost, burst_state_cost))

    print state_costs
    best_state_sequence = calc_best_state_sequence(state_costs)

    return best_state_sequence

def calc_state_cost(i, r_t, d_t, p_i):
    coef = comb(d_t, r_t, exact=True) # binomial coefficient
    #print coef, p_i
    return -1.0 * (math.log(coef) + r_t * math.log(p_i) + (d_t - r_t) * math.log(1 - p_i))

#def calculate_weight():
#    """ Calculating the weight of the bursts. Used for ranking the detected bursts
#    """

def calc_transition_cost(current_state, next_state, r):
    cost = 0
    if next_state > current_state:
        cost = r * (next_state - current_state)
    return cost

def calc_best_state_sequence(state_costs, r = 1):
    """ State costs: (non-burst state cost, burst state cost)
    """
    n = len(state_costs)
    subtotal_cost = 0 # unnecessary?
    current_state = 0 # non-burst state
    best_state_sequence = []

    for j in range(n):
        #current_state_cost = state_costs[j][current_state]
        to_burst_state = state_costs[j][1] + calc_transition_cost(current_state, 1, r)
        to_nonburst_state = state_costs[j][0] + calc_transition_cost(current_state, 0, r)
        #import ipdb; ipdb.set_trace()

        best_cost = 0
        if to_burst_state < to_nonburst_state:
            best_cost = to_burst_state
            current_state = 1
        else:
            best_cost = to_nonburst_state
            current_state = 0

        best_state_sequence.append(current_state)
        subtotal_cost += best_cost

    return best_state_sequence

def main():
    f = open(sys.argv[1])
    f_out = open(sys.argv[1] + "_predicted_burst_timestamps", "w")
    relevant_list = []
    total_list = []

    for line in f:
        relevant, total = map(int, line[:-1].split("\t"))
        relevant_list.append(relevant)
        total_list.append(total)

    best_state_sequence = detect_burst(relevant_list, total_list)
    print best_state_sequence

    for i in range(1, len(best_state_sequence)):
      f_out.write(str(best_state_sequence[i]) + "\n")

if __name__ == "__main__":
    main()


# -*- coding: utf-8 -*-
"""Exercise 2.

Grid Search
"""

import numpy as np

def generate_w(num_intervals):
    """Generate a grid of values for w0 and w1."""
    w0 = np.linspace(-100, 200, num_intervals)
    w1 = np.linspace(-150, 150, num_intervals)
    return w0, w1

def grid_search(y, tx, w0, w1):
    """Algorithm for grid search."""
    losses = np.zeros((len(w0), len(w1)))
    # compute loss for each combination of w0 and w1.
    for ind_row, row in enumerate(w0):
        for ind_col, col in enumerate(w1):
            w = np.array([row, col])
            e = y - tx.dot(w)
            losses[ind_row, ind_col] = 1/2*np.mean(e**2)
    return losses

def get_best_parameters(w0, w1, losses):
    """Get the best w from the result of grid search."""
    min_row, min_col = np.unravel_index(np.argmin(losses), losses.shape)
    return losses[min_row, min_col], w0[min_row], w1[min_col]


import numpy as np

def quantile_1D(data, weights, quantile):
    """
    Compute the weighted quantile of a 1D numpy array.
    Parameters
    ----------
    data : ndarray
        Input array (one dimension).
    weights : ndarray
        Array with the weights of the same size of `data`.
    quantile : float
        Quantile to compute. It must have a value between 0 and 1.
    Returns
    -------
    quantile_1D : float
        The output value.
    """
    # Check the data
    if not isinstance(data, np.matrix):
        data = np.asarray(data)
    if not isinstance(weights, np.matrix):
        weights = np.asarray(weights)
    nd = data.ndim
    if nd != 1:
        raise TypeError("data must be a one dimensional array")
    ndw = weights.ndim
    if ndw != 1:
        raise TypeError("weights must be a one dimensional array")
    if data.shape != weights.shape:
        raise TypeError("the length of data and weights must be the same")
    if ((quantile > 1.) or (quantile < 0.)):
        raise ValueError("quantile must have a value between 0. and 1.")
    # Sort the data
    ind_sorted = np.argsort(data)
    sorted_data = data[ind_sorted]
    sorted_weights = weights[ind_sorted]
    # Compute the auxiliary arrays
    Sn = np.cumsum(sorted_weights)
    # TODO: Check that the weights do not sum zero
    # assert Sn != 0, "The sum of the weights must not be zero"
    Pn = (Sn-0.5*sorted_weights)/Sn[-1]
    # Get the value of the weighted median
    return np.interp(quantile, Pn, sorted_data)

def quantile(data, weights, quantile):
    """
    Weighted quantile of an array with respect to the last axis.
    Parameters
    ----------
    data : ndarray
        Input array.
    weights : ndarray
        Array with the weights. It must have the same size of the last
        axis of `data`.
    quantile : float
        Quantile to compute. It must have a value between 0 and 1.
    Returns
    -------
    quantile : float
        The output value.
    """
    # TODO: Allow to specify the axis
    nd = data.ndim
    if nd == 0:
        TypeError("data must have at least one dimension")
    elif nd == 1:
        return quantile_1D(data, weights, quantile)
    elif nd > 1:
        n = data.shape
        imr = data.reshape((np.prod(n[:-1]), n[-1]))
        result = np.apply_along_axis(quantile_1D, -1, imr, weights, quantile)
        return result.reshape(n[:-1])

def median(data, weights):
    """
    Weighted median of an array with respect to the last axis.
    Alias for `quantile(data, weights, 0.5)`.
    """
    return quantile(data, weights, 0.5)


#############################################################################################################
# Visualize the data
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
import statsmodels.api as sm
# Load a statsmodels sample data using macro data
df = sm.datasets.macrodata.load_pandas().data
print(sm.datasets.macrodata.NOTE)
df.head()
# tsa is time series analysis
# index needs to have a date, so how to create using statsmodels functions (similar to matplotlib and datetime)
# use head and tail to see what the range should be
index = pd.Index(sm.tsa.datetools.dates_from_range('1959Q1','2009Q3') )
df.index = index
df.head()
df['realgdp'].plot()
result = sm.tsa.filters.hpfilter(df['realgdp'])
type(result) # returns a tuple of (cycle and trend), trend line tends upwards
# tuple unpacking to grab the individual series
gdp_cycle, gdp_trend = sm.tsa.filters.hpfilter(df['realgdp'])
# grab trend value and plot with realgdp data
df['trend'] = gdp_trend
df[['realgdp','trend']].plot()
# plot that is zoomed into a time you're interested in
df[['realgdp','trend']]['2000-03-31':].plot(figsize=(12,8))
# ETS Decomposition
# Break down a Time Series into ETS = Error, Trend, Seasonality attributes
# Used for Exponential Smoothing, Trend Method Models, ETS Decomposition
# Then Observed = Trend + Seasonal + Residual terms

#############################################################################################################
# Objective: Plot exponentially weighted moving average with your existing time series
# Using the EWMA Model
#
# Exponentially Weighted Moving Average models
# better than simple moving average weaknesses (averaging effects and extreme value effects, doesn't inform future behavior)
# reduce lag effect by putting more weights on recent values
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
airline = pd.read_csv('airline_passengers.csv',index_col='Month')
# now look at index
airline.index
# seems gappy/data clean up issues - get rid of missing values
airline.dropna(inplace=True)
# now modify index to a datetime object
airline.index=pd.to_datetime(airline.index)
# calculate simple 6 month MA
airline['6-month-SMA'] = airline['Thousands of Passengers'].rolling(window=6).mean()
airline['12-month-SMA'] = airline['Thousands of Passengers'].rolling(window=12).mean()
airline.plot(figsize=(10,8))
# now calculate EWMA, can use tab for column names and shift tab for ewm features
# different ways to calculate alpha: span, center of mass, half life, or if you want, alpha explicitly set
airline['EWMA-12'] = airline['Thousands of Passengers'].ewm(span=12).mean()

# other calibrations:
# s is span: n-day exponentially weighted moving average period (ex. 7 day or 14 day)
# c is center of mass: c = (span-1)/2  (ex. inverse with span)
# half life = period of time for exponential weight to reduce to one half
# alpha = smoothing factor
airline['EWMA-0.5 Alpha'] = airline['Thousands of Passengers'].ewm(alpha=0.5,adjust=True).mean()
airline['EWMA-2 COM'] = airline['Thousands of Passengers'].ewm(com=2,adjust=True).mean()
airline['EWMA-12 Halflife'] = airline['Thousands of Passengers'].ewm(halflife=12,adjust=True).mean()
# plot it:
airline[['Thousands of Passengers','EWMA-12','EWMA-0.5 Alpha','EWMA-2 COM','EWMA-12 Halflife']].plot(figsize=(10,8))

# more here: https://pandas.pydata.org/pandas-docs/stable/user_guide/computation.html
# When adjust is True (default), weighted averages are calculated using weights (1-alpha)**(n-1), (1-alpha)**(n-2), …, 1-alpha, 1.
# When adjust is False, weighted averages are calculated recursively as: weighted_average[0] = arg[0]; weighted_average[i] = (1-alpha)*weighted_average[i-1] + alpha*arg[i].
# One must have 0 < α <= 1, and while since version 0.18.0 it has been possible to pass α directly:
# it's often easier to think about either the span, center of mass (com) or half-life of an EW moment
# what does adjust do?
# Adjust is True is default
# not sure, not much diff in this example
# when Adjust is True, x0 is not an ordinary value but rather an exponentially weighted moment of the infinite series up to that point
# some other different calibrations:
airline['EWMA-12'] = airline['Thousands of Passengers'].ewm(span=12).mean()
airline['EWMA-0.5 Alpha'] = airline['Thousands of Passengers'].ewm(alpha=0.5,adjust=False).mean()
airline['EWMA-2 COM'] = airline['Thousands of Passengers'].ewm(com=2,adjust=False).mean()
airline['EWMA-12 Halflife'] = airline['Thousands of Passengers'].ewm(halflife=12,adjust=False).mean()
airline[['Thousands of Passengers','EWMA-12','EWMA-0.5 Alpha','EWMA-2 COM','EWMA-12 Halflife']].plot(figsize=(10,8))

#############################################################################################################
# Objective: ETS Decomposition to see Seasonality
#
# ETS Decomposition Code
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
airline = pd.read_csv('airline_passengers.csv',index_col='Month')
# now look at index, seems gappy - get rid of missing values
airline.index
airline.dropna(inplace=True)
# now modify index to a datetime object
airline.index=pd.to_datetime(airline.index)
airline.plot()
from statsmodels.tsa.seasonal import seasonal_decompose
# run the model: is it exponential growth or linear? if exponential use multiplicative, use 'additive' model for linear
result = seasonal_decompose(airline['Thousands of Passengers'], model='multiplicative')
result.trend.plot()
fig = result.plot() # may show twice in jupyter notebook

#############################################################################################################
# Objective: First ARIMA Model
#
# Not a scientific explanation of choosing p,d,q
# ARIMA Model = Auto Regressive Integrated Moving Average
# has 2 types of ARIMA models: Non-Seasonal and Seasonal

# arima(p,d,q)
# p=auto-regression to model dependent relationship
# d=integration is differencing to make series stationary
# q=moving average to model dependency on moving average error

# stationary - can we visually see it?
# stationary = good, nonstationary = bad
# stationary series has constant mean and variance over time (good = mean and variance will be same in future periods)
# 1) mean has to be constant to be stationary (no upward trend)
# 2) variance should not be changing as a function of time (bad = variance big and small over diff time periods)
# 3) covariance (change in variance) should not be a function of time (good = nice accordion, bad = partly squished accordian)
# don't need to visualize all the time, just use mathematically (Augmented Dickey Fuller Test)

# conversion from non-stationarity to stationarity can be done through differencing
# differencing = (first difference = t minus t-1, second difference = first diff t minus first diff t-1)
# also could try: differencing then seasonal differencing
# each differencing step loses a row of data

# Choosing Values for p,d,q (ARIMA model) and additional P, D, Q (seasonal ARIMA model)

# Autocorrelation Plots (ACF) - MA model identification
#
# Shows the correlation of a time series with "itself lagged by x time units"
# y axis is correlation coefficient, x axis is # of units of time lag (ex. 1D, 2D, ... 10D)
#
# ACF two common plots:
#
# 1) Gradual decline
# 2) Large drop off
# if ACF shows positive autocorrelation in first lag (lag at time 1) then suggest to use the AR term in relation to the lag
# if ACF shows negative autocorrelation in first lag (lag at time 1) then suggest to use the MA term

# Partial Correlation (PACF) - AR model identification
#
# Calculate partial correlation between the variables (y and x3)
# Partial correlation is the correlation between how y and x3 each are related to x1 and x2
# Partial autocorrelation plot would show some sharp drop off after lag "k", suggesting AR-k should be used
# If gradual decline in PACF, then choose MA

# So AR or MA?
#
# Identify an AR model usually using PACF (Partial Autocorrelation Function)
# Identify an MA model usually best done with ACF (Autocorrelation Function) rather than PACF
# Typically would choose MA or AR, rarely both (choose p or choose q)
# p is # of lag observations included in the model
# d is # of times raw observations are differenced
# q is size of moving average window (also called "order of MA")

# ARIMA Code Along
# 1) Visualize the Time Series
# 2) Make the time series stationary
# 3) Plot Correlation and Autocorrelation Charts
# 4) Construct ARIMA model
# 5) Use model to make predictions (forecasting)

import numpy as np
import pandas as pd
import statsmodels.api as sm
import matplotlib.pyplot as plt
%matplotlib inline
#Step 1############################################################################################################
df = pd.read_csv('monthly-milk-production-pounds-p.csv')
df.head()

# rename that long column
df.columns = ['Month','Milk in Pounds per Cow']

# check tail
df.tail()
df.drop(168,axis=0,inplace=True)
df.tail()

# get rid of normal index, change month to datetime and index,
df['Month'] = pd.to_datetime(df['Month'])
df.set_index('Month',inplace=True)
df.head()

# see the statistics
df.describe().transpose()

# visualize the data
# has a trend aspect and has a seasonality aspect
df.plot()

# plot moving averages
time_series = df['Milk in Pounds per Cow']
type(time_series) # pandas.core.series.Series
time_series.rolling(12).mean().plot(label='12 Month Rolling Mean')
time_series.rolling(12).std().plot(label='12 Month Rolling Std')
time_series.plot()
plt.legend()

#Step 2############################################################################################################
# plot ETS to see seasonal or not
# ETSD Decomposition Plot
from statsmodels.tsa.seasonal import seasonal_decompose
decomp = seasonal_decompose(time_series,freq=12,model='additive') # the trend does not appear multiplicative
fig = decomp.plot()
fig.set_size_inches(15,8)

# test for stationarity
# try and test if need first difference or second difference
# Test for stationarity using Augmented Dickey Fuller Test
df.head() # shows us datetime index of month and some milk
# null hypothesis = is not stationary
# alternative = is stationary
# p value less than 0.05 to reject the null hypothesis
from statsmodels.tsa.stattools import adfuller
# calculate adfuller
# returns a tuple, hard to handle
result = adfuller(df['Milk in Pounds per Cow'])
# returns an ugly format: with p value, number of lags used, critical values
# use a function to unwrap it, see below

# build an adf_check function to pull out p value results
# will succeed if p <= 0.05
def adf_check(time_series):
    result = adfuller(time_series)
    print('Augmented Dicky Fuller Test')
    print('***************************')
    labels = ['ADF Test Statistics','p-value','# of lags used', '# of Observations Used']
    for value,label in zip(result,labels):
        print(label + ' : ' +str(value))

    # result[1] is the p-value
    if result[1] <= 0.05:
        print('Strong evidence against null hypothesis')
        print('Reject null hypothesis')
        print('Data has no unit root and is stationary')
        print('Good')
    else:
        print('Weak evidence against null hypothesis')
        print('Fail to reject null hypothesis')
        print('Data has a unit root and is nonstationary')
        print('Bad')

# usage of the function
# run adf check on the original data
adf_check(df['Milk in Pounds per Cow'])     # seasonality has clearly shown that it is not stationary

# Since non stationary, take a first difference and try again
# shift it over one time step
df['First Difference'] = df['Milk in Pounds per Cow'] - df['Milk in Pounds per Cow'].shift(1)
df['First Difference'].plot()
plt.show()
# dropna because issue with shift(1) missing one value that messes up the adf function
adf_check(df['First Difference'].dropna()) # looks stationary

# Try second difference and see
df['Second Difference'] = df['First Difference'] - df['First Difference'].shift(1)
df['Second Difference'].plot()
plt.show()
adf_check(df['Second Difference'].dropna()) # try again if first one does not look stationary

# can also take a seasonal difference
df['Seasonal Difference'] = df['Milk in Pounds per Cow'] - df['Milk in Pounds per Cow'].shift(12) # seasonal diff is 12
df['Seasonal Difference'].plot()
plt.show()
adf_check(df['Seasonal Difference'].dropna())

# can also take a seasonal first difference
df['Seasonal First Difference'] = df['First Difference'] - df['First Difference'].shift(12) # seasonal first diff is 12
df['Seasonal First Difference'].plot()
plt.show()
adf_check(df['Seasonal First Difference'].dropna())

#Step 3############################################################################################################
# ACF and PACF Plots (Autocorrelation)
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
fig_first = plot_acf(df['First Difference'].dropna(),title='First Difference')
fig_seasonal_first = plot_acf(df['Seasonal First Difference'].dropna(), title='Seasonal First Difference')

# PACF Plot
# https://www.itl.nist.gov/div898/handbook/pmc/section4/pmc4463.htm
result = plot_pacf(df['Seasonal First Difference'].dropna()) # common to see sudden drop off then use AR, if gradual then use MA

# Final plots
fig = plt.figure(figsize=(12,8))
ax1 = fig.add_subplot(211)
fig = sm.graphics.tsa.plot_acf(df['Seasonal First Difference'].iloc[13:], lags=40, ax=ax1)
ax2 = fig.add_subplot(212)
fig = sm.graphics.tsa.plot_pacf(df['Seasonal First Difference'].iloc[13:], lags=40, ax=ax2)

#Step 4############################################################################################################
# ARIMA Model
# Now that we have a stationary model
from statsmodels.tsa.arima_model import ARIMA

# SARIMAX = S is for seasonal, X is for exogeneous for external variables
# for full documentation string --> help(ARIMA)
# Some additional docs https://www.statsmodels.org/dev/examples/notebooks/generated/statespace_sarimax_stata.html
"""
What does ARIMA(1, 0, 12) mean?
Specifically for your model, ARIMA(1, 0, 12) means that it you are describing some response variable (Y) by combining a 1st order Auto-Regressive model and a 12th order Moving Average model. A good way to think about it is (AR, I, MA). This makes your model look the following, in simple terms:
Y = (Auto-Regressive Parameters) + (Moving Average Parameters)
The 0 in the between the 1 and the 12 represents the 'I' part of the model (the Integrative part) and it signifies a model where you're taking the difference between response variable data - this can be done with non-stationary data and it doesn't seem like you're dealing with that, so you can just ignore it.

What values can be assigned to p, d, q?
Lots of different whole numbers. There are diagnostic tests you can do to try to find the best values of p,d,q (see part 3).

What is the process to find the values of p, d, q?
There are a number of ways, and I don't intend this to be exhaustive:
look at an autocorrelation graph of the data (will help if Moving Average (MA) model is appropriate)
look at a partial autocorrelation graph of the data (will help if AutoRegressive (AR) model is appropriate)
look at extended autocorrelation chart of the data (will help if a combination of AR and MA are needed)
try Akaike's Information Criterion (AIC) on a set of models and investigate the models with the lowest AIC values
try the Schwartz Bayesian Information Criterion (BIC) and investigate the models with the lowest BIC values

Resource for choosing p,d,q?
# https://people.duke.edu/~rnau/arimrule.htm
"""

# Here we have seasonal so use seasonal ARIMA model
# p=auto-regression to model dependent relationship
# d=integration is differencing to make series stationary
# q=moving average to model dependency on moving average error
# p is # of autoregressive lag observations included in the model
# d is # of times raw observations are differenced
# q is size of moving average window (also called "order of MA")
# seasonal_order is (Seasonal AR spec, Seasonal Integration Order, Seasonal MA, Seasonal Periodicity)
# (p = 0, d = 1, q = 0)  and (P = 1, D = 1, Q = 1) Periodicity = 12
model = sm.tsa.statespace.SARIMAX(df['Milk in Pounds per Cow'], order=(0,1,0), seasonal_order=(1,1,1,12))

# This warning basically says it assumes your time series periods are assumed and have no gaps in data:
# ValueWarning: No frequency information was provided, so inferred frequency MS will be used.

# This function may take a while
results = model.fit()
print(results.summary())

# to see the residual value
results.resid
results.resid.plot()

# to see distribution of our errors
results.resid.plot(kind='kde')
# note we see it is around zero, which is good

#Step 5############################################################################################################
# forecast on yourself on data that you already know
df['forecast'] = results.predict(start=150, end=168)
df[['Milk in Pounds per Cow','forecast']].plot(figsize=(12,8))
# see how good the forecast is on in sample data

# Forecast beyond current time period
df.tail()

# Add on more months with empty values, date increment for date range
from pandas.tseries.offsets import DateOffset
# df.index[-1] is the last date then ADD an offset of 1 month, 2 month... not +0 month because duplicate = not 0,24 but 1,24
future_dates = [df.index[-1] + DateOffset(months=x) for x in range(1,24)]
future_dates

# Create a new dataframe
future_df = pd.DataFrame(index=future_dates,columns=df.columns)
final_df = pd.concat([df,future_df])
final_df.head() # see if old data still there and has a new column
final_df.tail() # see if the new time series were created

# Populate a forecast into the dataframe
final_df['forecast'] = results.predict(start=168,end=192)
final_df.tail()

# Plot the new forecast
final_df[['Milk in Pounds per Cow','forecast']].plot(figsize=(12,8))
# why ARIMA works well?
# time has a clear impact on y value
# ARIMA assumes time is an important component
# why ARIMA doesn't work well for stocks?
# Financial stock prices tend to be more random, so more Brownian Motion and Monte Carlo

import numpy,import statsmodels,from statsmodels


#!/usr/bin/python
"""
    disclaimer: I acquired this function the course website or
    directly from: http://blue.math.buffalo.edu/sauer2py/romberg.py
"""
# Program 5.1 Romberg integration
# Translated to Python by JR 2/17/2012.
# Computes approximation to definite integral
# Inputs: Matlab inline function specifying integrand f,
#    a,b integration interval, n=number of rows
# Output: Romberg tableau r
from numpy import *
from math import sin

def romberg(f,a,b,n):
	h=(b-a)/2.**linspace(0,n-1,n)
	r = zeros((n,n))
	r[0,0]=(b-a)*(f(a)+f(b))/2.
	for j in range(2,n+1):
		subtotal = 0.
		for i in range(1,2**(j-2)+1):
			subtotal = subtotal + f(a+(2*i-1)*h[j-1])
		r[j-1,0] = r[j-2,0]/2. + h[j-1]*subtotal
		for k in range(2,j+1):
			r[j-1,k-1] = (4**(k-1)*r[j-1,k-2]-r[j-2,k-2])/(4.**(k-1)-1)
	return r
if __name__ == "__main__":
    f = lambda x: sin(1.7*x)-x**2.5
    set_printoptions(precision=5)
    print romberg(f,1,3,5)


from typing import Optional

import numpy as np

from mockseries.noise.noise import Noise

class RedNoise(Noise):
    """Gaussian noise generator.

    This class adds uncorrelated, additive white noise to your signal.

    Args:
        mean: mean of the noise
    std : float
        standard deviation of the noise

    Notes:
        https://atmos.washington.edu/~breth/classes/AM582/lect/lect8-notes.pdf
        https://rts-gen.readthedocs.io/en/latest/generators.html#gaussian-noise-generator
    """

    def __init__(
        self,
        mean: float = 0.0,
        std: float = 1.0,
        correlation: float = 0.5,
        random_seed: Optional[int] = None,
    ) -> None:
        super().__init__(random_seed)
        self.mean = mean
        self.std = std
        if not (0 < correlation < 1):
            raise ValueError("Correlation coefficient r has to be between 0 and 1.")
        self.correlation = correlation

    def _sample_at(self, time_points: np.ndarray) -> np.ndarray:
        """Samples a red noise."""
        # todo optimize
        if self.random_seed:
            np.random.seed(self.random_seed)

        white_noise = np.random.normal(
            loc=self.mean, scale=self.std, size=len(time_points)
        )
        red_noise = np.zeros(len(time_points))
        red_noise[0] = white_noise[0]

        for i in range(1, len(red_noise)):
            red_noise[i] = (
                self.correlation * red_noise[i - 1]
                + np.sqrt((1 - self.correlation * self.correlation)) * white_noise[i]
            )

        return red_noise


# ====================================
# Creating and using a quadrature rule
# ====================================
#
# This demo shows how quadrature rules can be obtained from Basix and how these
# can be used to compute the integrals of functions. A quadrature rule uses a
# set of points (:math:`p_0` to :math:`p_{n-1}`) and weights (:math:`w_0` to
# :math:`w_{n-1}`), and approximates an integral as the weighted sum of the
# values of the function at these points, ie
#
# .. math::
#
#    \int f\,\mathrm{d}x \approx \sum_i w_if(p_i).
#
# First, we import Basix and Numpy.

import basix
import numpy as np
from basix import ElementFamily, CellType, LagrangeVariant

# To get a quadrature rule on a triangle, we use the function `make_quadrature`.
# This function takes two or three three inputs. We want to use the default
# quadrature, pass in two inputs: a cell type and an order. The order of the rule
# is equal to the degree of the highest degree polynomial that will be exactly
# integrated by this rule. In this example, we use an order 4 rule, so all quartic
# polynomials will be integrated exactly.
#
# `make_quadrature` returns two values: the points and the weights of the
# quadrature rule.

points, weights = basix.make_quadrature(CellType.triangle, 4)

# If we want to control the type of quadrature used, we can pass in three
# inputs to `make_quadrautre`. For example, the following code would force basix
# to use a Gauss-Jacobi quadrature rule:

points, weights = basix.make_quadrature(
    basix.QuadratureType.gauss_jacobi, CellType.triangle, 4)

# We now use this quadrature rule to integrate the functions :math:`f(x,y)=x^3y`
# and :math:`g(x,y)=x^3y^2` over the triangle. The exact values of these integrals
# are 1/120 (0.00833333333333...) and 1/420 (0.00238095238095...) respectively.
#
# As :math:`f` is a degree 4 polynomial, we expect our quadrature rule to be able
# to compute its integral exactly (within machine precision). :math:`g` on the
# other hand is a degree 5 polynomial, so its integral will not be computed exactly.
#
# We define Python functions that compute :math:`f` and :math:`g` for every point.
# These functions use features of Numpy to compute all the values at once.

def f(points):
    return points[:, 0] ** 3 * points[:, 1]

def g(points):
    return points[:, 0] ** 3 * points[:, 1] ** 2

# We can now use Numpy features to compute the integrals.

print(np.sum(weights * f(points)))
print(np.sum(weights * g(points)))

# We obtain the values 0.00833333333333334 and 0.002393509368731209. As expected,
# the integral of :math:`f` has been computed to within machine precision, while
# the integral of :math:`g` is correct to 4 decimal places, but is not exact.
#
# Integrating a basis function
# ============================
#
# We next use the quadrature rule to compute the integral of a basis function in
# a degree 3 Lagrange space. We first create the space and tabulate its basis
# functions at the quadrature points.

lagrange = basix.create_element(
    ElementFamily.P, CellType.triangle, 3, LagrangeVariant.equispaced)

values = lagrange.tabulate(0, points)

# We compute the integral of the third (note that the counting starts at 0) basis
# function in this space. We can obtain the values of this basis function from
# `values` by using the indices `[0, :, 3, 0]`. The integral can therefore
# computed as follows. As this basis function will be degree three, the result
# will again be exact (withing machine precision).

print(np.sum(weights * values[0, :, 3, 0]))


from numpy import sum,asarray,abs,sign

def spearman_footrule_distance(s,t):
    """
    Computes the Spearman footrule distance between two full lists of ranks:

        F(s,t) = (2/|S|^2)*sum( |s(i) - t(i)| )

    the normalized sum over all elements in a set of the absolute difference
    between the rank according to two different lists s and t.  As defined,
    0 <= F(s,t) <= 1.

    s,t should be array-like (lists are OK).

    If s,t are *not* full, this function should not be used.
    """
    # check that the lists are both full
    assert len(s) == len(t)
    return (2.0/len(s)**2)*sum(abs(asarray(s) - asarray(t)))

def kendall_tau_distance(s,t):
    """
    Computes the Kendall tau distance between two full lists of ranks,
    which counts all discordant pairs (where s(i) < s(j) but t(i) > t(j),
    or vice versa) and divides by:

            k*(k-1)/2

    This is a slow version of the distance; a faster version can be
    implemented using a version of merge sort (TODO).

    s,t should be array-like (lists are OK).

    If s,t are *not* full, this function should not be used.
    """
    numDiscordant = 0
    for i in range(0,len(s)):
        for j in range(i+1,len(t)):
            if (s[i] < s[j] and t[i] > t[j]) or (s[i] > s[j] and t[i] < t[j]):
                numDiscordant += 1
    return 2.0*numDiscordant/(len(s)*(len(s)-1))

def partial_kendall(s1,s2,p=0.5):
    """
    Kendall distance with penalty p between two partial rank lists (lists with ties).
    This could be sped up with more efficient computation of the size of the sets R1,
    R2, and D. I normalize the distance by dividing by k*(k-1)/2.
    """
    cardD = 0
    cardR1 = 0
    cardR2 = 0
    k = len(s1)
    for i in range(len(s1)):
        for j in range(len(s2)):
            if s1[i] < s1[j] and s2[i] > s2[j]:
                cardD += 1
            elif s1[i] == s1[j] and s2[i] != s2[j]:
                cardR1 += 1
            elif s2[i] == s2[j] and s1[i] != s1[j]:
                cardR2 += 1
    return (2/(k*(k-1)))*(cardD + p*(cardR1 + cardR2))

def partial_hausdorff(s1,s2):
    """
    Hausdorff distance for partial rank lists; as in partial_kendall, could be
    sped up by more efficient set size calculation.  I normalize the distance by
    dividing by k*(k-1)/2.
    """
    cardD = 0
    cardR1 = 0
    cardR2 = 0
    k = len(s1)
    for i in range(len(s1)):
        for j in range(len(s2)):
            if s1[i] < s1[j] and s2[i] > s2[j]:
                cardD += 1
            elif s1[i] == s1[j] and s2[i] != s2[j]:
                cardR1 += 1
            elif s2[i] == s2[j] and s1[i] != s1[j]:
                cardR2 += 1
    return (2./(k*(k-1)))*(cardD + max(cardR1,cardR2))


import numpy as np
import matplotlib.pyplot as plt
from math import floor
from functools import partial
from typing import List, Union, Tuple

def find_offsets(minimum: int, maximum: int) -> Tuple[int]:
    '''
    Finds the required coefficient and offset to map a random number generator that
    produces between (0, 1) to a given minimum/maximum range
    '''
    # Find range
    coefficient_range = maximum - minimum

    # Find offset
    offset = abs(maximum - coefficient_range)
    if maximum < coefficient_range:
        offset *= -1

    return coefficient_range, offset

def random_polynomial_coefficients(minimum: int, maximum: int, degree: int) -> List[float]:
    '''
    Create a polynomial of form:
    ax^n + bx^n-1 + cx^n-2 .... + yx^1 + z

    ::param min:: Minimum coefficient value
    ::param max:: Maximum coefficient value

    ::return:: coefficients should be interpreted as in ascending order for simplicity
    '''
    if maximum <= minimum:
        raise ValueError('Maximum must be larger than minimum')

    coefficient_range, offset = find_offsets(minimum, maximum)
    coefficients = np.random.random((degree + 1)) * coefficient_range + offset

    return coefficients

def apply_polynomial(coefficients: List[float], x: Union[float, int]) -> Union[float, int]:
    '''
    Determine the return value of a polynomial with given coefficients at value x
    '''
    y = sum(coefficients[i] * (x**i) for i in range(len(coefficients)))

    return y

def create_data(minimum: int, maximum: int,
                coefficients: List[float], percent_training_data: float =0.5,
                data_point_count: int=10000) -> Tuple[List[float]]:
    '''
    Creates a sample set of data for testing and training based on the polynomial
    with given coefficients.

    ::params minimum, maximum:: the min/max values on the x domain to evaluate
    the polynomial
    ::return train_x, train_y, test_x, test_y:: -> ([x_data], [y_data], [x_data], [y_data])
    '''

    if percent_training_data <= 0:
        raise ValueError('Percent Training Data Must Be Positive')
    elif percent_training_data >= 1:
        raise ValueError('Percent Training Data Must Be Less Than 1')
    elif maximum <= minimum:
        raise ValueError('Maximum Must Be Larger Than Minimum')

    apply_poly_partial = partial(apply_polynomial, coefficients)
    training_data_count = floor(data_point_count * percent_training_data)
    coefficient_range, offset = find_offsets(maximum, minimum)

    x_points = np.random.random((data_point_count)) * coefficient_range + offset
    y_points = np.array(list(map(apply_poly_partial, x_points)))

    training_x_points = x_points[:training_data_count]
    training_y_points = y_points[:training_data_count]
    testing_x_points = x_points[training_data_count:]
    testing_y_points = y_points[training_data_count:]

    return training_x_points, training_y_points, testing_x_points, testing_y_points

def plot_data(data: List[List[float]]):
    '''
    Graph the training and testing data

    ::param data:: training/testing x and y arrays
    '''
    plt.scatter(data[0], data[1], color='blue', s=2, label='training')
    plt.scatter(data[2], data[3], color='red', s=2, label='testing')
    plt.legend()
    plt.show()

coefs = random_polynomial_coefficients(-1, 1, 4)
data = create_data(-6, 6, coefs, 0.5, data_point_count=100)
plot_data(data)


# -*- coding: UTF-8 -*-
import numpy as np

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

# 统计数组最大值/最小值
print(np.amin(a))
print(np.amin(a, 0))
print(np.amin(a, 1))
print(np.amax(a))
print(np.amax(a, 0))
print(np.amax(a, 1))

# 统计最大值与最小值之差
print(np.ptp(a))
print(np.ptp(a, 0))
print(np.ptp(a, 1))

# 统计数组的百分位数
print(np.percentile(a, 50))
print(np.percentile(a, 50, axis=0))
print(np.percentile(a, 50, axis=1))

# 统计中位数/平均数
print(np.median(a))
print(np.median(a, axis=0))
print(np.median(a, axis=1))
print(np.mean(a))
print(np.mean(a, axis=0))
print(np.mean(a, axis=1))

# 统计加权平均值
b = np.array([1, 2, 3, 4])
wts = np.array([1, 2, 3, 4])
print(np.average(b))
print(np.average(b, weights=wts))

# 统计标准差/方差
print(np.std(b))
print(np.var(b))


#!/usr/bin/env python3

import numpy as np
import matplotlib.pyplot as plt

def lissajous(b=1, a=1, A=2, B=1, d=np.pi/2, col='r-'):
    xs = np.linspace(0, 30, 600)
    plt.plot(A*np.sin(a*xs + d), B*np.sin(b*xs), col, linewidth=1)

lissajous(b=1, col='r-')
lissajous(b=2, col='g-')
lissajous(b=3, col='b-')
lissajous(b=np.pi, col='y-')
lissajous(b=np.e, col='c-')
plt.show()


import numpy as np

class BinaryLogisticRegression:
    """
    Binary logistic regression classifier.

    Uses gradient descent to determine the parameters of the logistic
    regression classifier.

    Parameters
    ----------
    max_iter : int, default=1000
        Maximum number of iterations allowed in gradient descent.
    eta : float, default=0.1
        Learning rate of gradient descent. Must be greater than 0.
    alpha : float, default=0.1
        Regularization hyperparameter. Larger alpha results in stronger
        regularization. Should be greater than 0.
    reg : {'l1', 'l2'}, default='l2'
        Determines whether L1 or L2 regularization will be used.
    eps : float, default=1E-6
        Training termination criterion. If a norm of a gradient during
        gradient descent is less than eps then training is finished.
    log : bool, default=False
        If set to True training progress will be printed to console.

    Attributes
    ----------
    w : ndarray of shape (n_features,)
        Vector of weights in the decision function. n_features is the
        number of features as given in fit method.
    b : float
        Bias used in the decision function.
    """

    L1 = 1
    L2 = 2

    def __init__(self, max_iter=1000, eta=0.1, alpha=0.1, reg='l2', eps=1E-6,
                 log=False):
        self._max_iter = max_iter
        self._eta = eta
        self._alpha = alpha
        self._reg = None
        if reg == 'l2':
            self._reg = self.L2
        elif reg == 'l1':
            self._reg = self.L1
        else:
            raise ValueError('invalid reg value: ' + reg)
        self._eps = eps
        self._log = log
        self._w, self._b = None, None

    @property
    def w(self):
        return self._w.copy()

    @property
    def b(self):
        return self._b

    def fit(self, X, y):
        """
        Fits this model to the given training data.

        Parameters
        ----------
        X : ndarray of shape (n_samples, n_features)
            Training datapoints. n_samples is the number of samples and
            n_features is the number of features.
        y : ndarray of shape (n_samples,)
            Target vector. Since this is a binary classifier classes
            must be encoded by values 0 and 1.
        """
        N, d = X.shape
        w = np.random.randn(d)
        b = 0
        for i in range(self._max_iter):
            scores = X @ w + b
            e = np.exp(scores)
            probs = e / (1 + e)

            if self._log and i % 10 == 0:
                loss = -1/N * np.sum(np.log(np.abs(1 - y - probs)))
                if self._reg == self.L1:
                    loss += self._alpha * np.sum(np.abs(w))
                else:
                    loss += self._alpha * 1/2 * w @ w
                print('Iteration {}, loss = {}'.format(i, loss))

            gs = probs - y
            # .T not needed since gs is a 1D array
            grad_w = 1/N * gs.T @ X
            grad_b = 1/N * np.sum(gs)

            if self._reg == self.L1:
                grad_w += (self._alpha *
                    np.array([1 if wi > 0 else -1 for wi in w]))
            else:
                grad_w += self._alpha * w

            if np.linalg.norm(np.append(grad_w, grad_b)) < self._eps:
                break

            w -= self._eta * grad_w
            b -= self._eta * grad_b

        self._w = w
        self._b = b

    def decision_function(self, X):
        """
        Returns the values of the decision function for given data.

        Parameters
        ----------
        X : ndarray of shape (n_samples, n_features)
            Datapoints for which the decision function values should be
            computed. n_samples is the number of samples and n_features
            is the number of features.

        Returns
        -------
        y : ndarray of shape (n_samples,)
            Decision function values of given datapoints.
        """
        return X @ self._w + self._b

    def predict_proba(self, X):
        """
        Predicts the probabilities of given data.

        Parameters
        ----------
        X : ndarray of shape (n_samples, n_features)
            Array of datapoints. Classification probabilitiy of each
            datapoint is computed and returned. n_samples is the
            number of samples and n_features is the number of features.

        Returns
        -------
        probs : ndarray of shape (n_samples, 2)
            Classification probabilities. probs[i, 0] is the
            probability of the datapoint X[i] belonging to class
            encoded by 0 and probs[i, 1] is the probability of
            datapoint X[i] belonging to class encoded by 1.
        """
        if self._w is None:
            raise RuntimeError(
                'fit must be called before a call to predict_proba')
        scores = self.decision_function(X)
        scores.resize(X.shape[0], 1)
        e = np.exp(scores)
        probs = e / (1 + e)
        return np.hstack((1 - probs, probs))

    def predict(self, X):
        """
        Predicts the classes of given datapoints.

        Parameters
        ----------
        X : ndarray of shape (n_samples, n_features)
            Datapoints to be classified. n_samples is the number of
            samples and n_features is the number of features.

        Returns
        -------
        y : ndarray of shape (n_samples,)
            Vector of predicted classes. y[i] is the class of the
            datapoint X[i]. Classes are encoded by 0s and 1s (as given
            in fit method).
        """
        if self._w is None:
            raise RuntimeError('fit must be called before a call to predict')
        probs = self.predict_proba(X)[:, 1]
        return (probs > 0.5).astype(np.int)

def _print_performance(y, y_predicted):
    accuracy, precision, recall = performance_binary(y, y_predicted)
    print('Accuracy:  {:.5f}'.format(accuracy))
    print('Precision: {:.5f}'.format(precision))
    print('Recall:    {:.5f}'.format(recall))

def _get_range(X, margin=0.2):
    xmin, ymin = tuple(np.min(X, axis=0))
    xmax, ymax = tuple(np.max(X, axis=0))
    xrange = (xmin - margin * (xmax-xmin), xmax + margin * (xmax-xmin))
    yrange = (ymin - margin * (ymax-ymin), ymax + margin * (ymax-ymin))
    return xrange, yrange

def _plot_results(clf, X, y, y_predicted):
    xrange, yrange = _get_range(X)
    plot_surface(lambda x: clf.predict_proba(x)[:, 1],
                 xrange=xrange, yrange=yrange, offset=0.5)
    cmap = lambda x: (0.2, 0.2, 0.2, 1) if x == 0 else (1, 1, 1, 1)
    plot_classification(X, y, y_predicted, cmap=cmap)
    plt.xlim(xrange[0], xrange[1])
    plt.ylim(yrange[0], yrange[1])
    plt.show()

def main():
    seed = np.random.randint(1E9)
    print('Using seed {}.'.format(seed))
    np.random.seed(seed)

    X, y = sample_gaussian_2d(2, 100)
    clf = BinaryLogisticRegression(alpha=0.2)
    clf.fit(X, y)
    y_predicted = clf.predict(X)

    w, b = clf.w, clf.b
    print('w = ({:.3f}, {:.3f}), b = {:.3f}'.format(w[0], w[1], b))
    _print_performance(y, y_predicted)
    _plot_results(clf, X, y, y_predicted)

if __name__ == '__main__':
    from data import sample_gaussian_2d
    from metrics import performance_binary
    from plotting import plot_classification, plot_surface
    import matplotlib.pyplot as plt
    main()