import os
from typing import Text, Tuple

from absl import logging
import matplotlib
import matplotlib.pyplot as plt
import pandas as pd
from scipy.stats import pearsonr
import statsmodels
import statsmodels.api as sm
from statsmodels.formula.api import ols

class Simulation(object):
  """Chapter 3, Exercise 14."""

  def __init__(self, data_path: Text="../data/ch3_q14.csv",
               figsize: Tuple[int, int]=(10, 10)):
    """Constructor

    Attrubutes:
      sim_data: A pd.DataFrame containing the simulation data to analyze.
      figsize: A tuple specifying the default size for figures.
    """
    self.data_path = data_path
    assert os.path.exists(self.data_path), "ch3_q14 dataset not found."
    logging.info("\tLoading %s", self.data_path)
    self.sim_data = pd.read_csv(self.data_path)
    self.figsize = figsize

  def part_b1(self) -> float:
    """
    Using the pearsonr() function from the scipy.stats module, estimate the
    Pearson correlation coefficient.

    Returns:
      A float value containing the Pearson correlation coefficient.
    """
    # YOUR CODE STARTS HERE
    corr, _ = pearsonr(self.sim_data.x1, self.sim_data.x2)
    # raise NotImplementedError
    # YOUR CODE ENDS HERE
    return corr

  def part_b2(self) -> matplotlib.collections.PathCollection:
    """
    Use the scatter() method from matplotlib to return a scatter plot of
    x1 vs x2. Comment on what you see.

    Returns:
      A matplotlib.collections.PathCollection object containing the scatter plot.
    """
    # YOUR CODE STARTS HERE
    _, ax = plt.subplots(figsize=self.figsize)
    ax.set_xlabel("x2")
    ax.set_ylabel("x1")
    figure = ax.scatter(self.sim_data.x2, self.sim_data.x1)
    # raise NotImplementedError
    # YOUR CODE ENDS HERE
    return figure

  def part_c(self) -> statsmodels.iolib.summary.Summary:
    """
    Use the statsmodels ols() function to perform a multiple linear regression with y
    as the response and x1, x2 as the predictors. Return the results using the
    summary() method. Comment on the output.

    Returns:
      A statsmodels Summary object, containing the summary results of the ols
      fit.
    """
    # YOUR CODE STARTS HERE
    formula = ("y ~ x1 + x2")
    ols_fit = ols(formula, data=self.sim_data).fit()
    ols_summary = ols_fit.summary()
    # YOUR CODE ENDS HERE
    return ols_summary

  def part_d(self) -> statsmodels.iolib.summary.Summary:
    """
    Use the sm.OLS() function to perform a multiple linear regression with y
    as the response and x1 as the predictor. Return the results using the
    summary() method. Comment on the output.
    """
    # YOUR CODE STARTS HERE
    formula = ("y ~ x1")
    ols_fit = ols(formula, data=self.sim_data).fit()
    ols_summary = ols_fit.summary()
    # YOUR CODE ENDS HERE
    return ols_summary

  def part_e(self) -> statsmodels.iolib.summary.Summary:
    """
    Use the statsmodels ols() function to perform a multiple linear regression with y
    as the response and x2 as the predictor. Return the results using the
    summary() method. Comment on the output.
    """
    # YOUR CODE STARTS HERE
    formula = ("y ~ x2")
    ols_fit = ols(formula, data=self.sim_data).fit()
    ols_summary = ols_fit.summary()
    # YOUR CODE ENDS HERE
    return ols_summary

  def part_g1(self) -> statsmodels.iolib.summary.Summary:
    """
    Use the statsmodels ols() function to perform a multiple linear regression with y
    as the response and x1, x2 as the predictors on the updated data. Return
    the results using the summary() method. Comment on the output.

    Returns:
      A statsmodels Summary object, containing the summary results of the ols
      fit.
    """
    extra_obs = pd.DataFrame({"x1": [0.1], "x2":[0.8], "y": [6]})
    updated_sim_data = self.sim_data.append(extra_obs)
    # YOUR CODE STARTS HERE
    formula = ("y ~ x1 + x2")
    ols_fit = ols(formula, data=updated_sim_data).fit()
    ols_summary = ols_fit.summary()
    # YOUR CODE ENDS HERE
    return ols_summary

  def part_g2(self) -> statsmodels.iolib.summary.Summary:
    """
    Use the sm.OLS() function to perform a multiple linear regression with y
    as the response and x1 as the predictors on the updated data. Return the
    results using the summary() method. Comment on the output.

    Returns:
      A statsmodels Summary object, containing the summary results of the ols
      fit.
    """
    extra_obs = pd.DataFrame({"x1": [0.1], "x2":[0.8], "y": [6]})
    updated_sim_data = self.sim_data.append(extra_obs)
    # YOUR CODE STARTS HERE
    formula = ("y ~ x1")
    ols_fit = ols(formula, data=updated_sim_data).fit()
    ols_summary = ols_fit.summary()
    # YOUR CODE ENDS HERE
    return ols_summary

  def part_g3(self) -> statsmodels.iolib.summary.Summary:
    """
    Use the statsmodels ols() function to perform a multiple linear regression with y
    as the response and x2 as the predictors on the updated data. Return the
    results using the summary() method. Comment on the output.

    Returns:
      A statsmodels Summary object, containing the summary results of the ols
      fit.
    """
    extra_obs = pd.DataFrame({"x1": [0.1], "x2":[0.8], "y": [6]})
    updated_sim_data = self.sim_data.append(extra_obs)
    # YOUR CODE STARTS HERE
    formula = ("y ~ x2")
    ols_fit = ols(formula, data=updated_sim_data).fit()
    ols_summary = ols_fit.summary()
    # YOUR CODE ENDS HERE
    return ols_summary

def ch3_ex14():
  """Wrapper function for Chapter 3, Exercise 14."""
  logging.info("Chapter 3, Exercise 14.")
  sim = Simulation()

  logging.info("\tPart b.")
  corr = sim.part_b1()
  with open("../results/python/ch3_ex14_part_b1.csv", "w") as f:
    f.write(str(corr))

  sim.part_b2()
  plt.savefig("../results/python/ch3_ex14_part_b2.png")

  logging.info("\tPart c.")
  ols = sim.part_c()
  with open("../results/python/ch3_ex14_part_c.csv", "w") as f:
    f.write(ols.as_text())

  logging.info("\tPart d.")
  ols = sim.part_d()
  with open("../results/python/ch3_ex14_part_d.csv", "w") as f:
    f.write(ols.as_text())

  logging.info("\tPart e.")
  ols = sim.part_e()
  with open("../results/python/ch3_ex14_part_e.csv", "w") as f:
    f.write(ols.as_text())

  logging.info("\tPart g.")
  ols = sim.part_g1()
  with open("../results/python/ch3_ex14_part_g1.csv", "w") as f:
    f.write(ols.as_text())
  ols = sim.part_g2()
  with open("../results/python/ch3_ex14_part_g2.csv", "w") as f:
    f.write(ols.as_text())
  ols = sim.part_g3()
  with open("../results/python/ch3_ex14_part_g3.csv", "w") as f:
    f.write(ols.as_text())

from scipy,import statsmodels,from statsmodels


# Box Muller

from collections import Counter
from matplotlib import pyplot

import numpy as np

def box_muller():
    a, b = np.random.rand(2)
    ta = np.math.sin(2 * np.pi * b)
    tb = np.math.cos(2 * np.pi * b)

    t = np.math.sqrt(-np.log(a**2))
    return t * ta, t * tb

l = []
for i in range(100000):
    l += box_muller()

l2 = [sum(np.random.rand(40)) / 40 for _ in range(100000)]

fig = pyplot.figure()

ax = fig.add_subplot(2, 1, 1)
ax.hist(l, bins=1000)
ax.set_title('Box Muller')

bx = fig.add_subplot(2, 1, 2)
bx.hist(l2, bins=1000)
bx.set_title('Central limit theorem')

pyplot.show()


from sympy.core.sympify import _sympify
from sympy.matrices.expressions import MatrixExpr
from sympy import S, I, sqrt, exp

class DFT(MatrixExpr):
    """ Discrete Fourier Transform

    DFT creates the matrix associated with the Discrete Fourier Transform of
    size n in the standard basis.

    Examples
    ========
    >>> from sympy.core.symbol import symbols
    >>> from sympy.matrices.expressions.fourier import DFT
    >>> M = DFT(4)
    >>> N.as_mutable().shape
    (4, 4)
    >>> M.as_mutable()
    Matrix([
    [1/2,  1/2,  1/2,  1/2],
    [1/2, -I/2, -1/2,  I/2],
    [1/2, -1/2,  1/2, -1/2],
    [1/2,  I/2, -1/2, -I/2]])
    >>> N, i, j = symbols('N, i, j')
    >>> DFT(N)[i, j] == DFT(N)[j, i]
    True
    See also:
    ============

    sympy.discrete
    https://en.wikipedia.org/wiki/DFT_matrix
"""
    def __new__(cls, n):
        n = _sympify(n)
        cls._check_dim(n)

        obj = super().__new__(cls, n)
        return obj

    n = property(lambda self: self.args[0])  # type: ignore
    shape = property(lambda self: (self.n, self.n))

    def _entry(self, i, j, **kwargs):
        w = exp(-2*S.Pi*I/self.n)
        return w**(i*j) / sqrt(self.n)

    def _eval_inverse(self):
        return IDFT(self.n)

class IDFT(DFT):
    """ Inverse Discrete Fourier Transform
    IDFT creates the inverse matrix from the above.

    Examples
    ========

    >>> from sympy.matrices.expressions.fourier import DFT
    >>> Inv = IDFT(4)
    >>> Inv.as_mutable()
    Matrix([
    [1/2,  1/2,  1/2,  1/2],
    [1/2,  I/2, -1/2, -I/2],
    [1/2, -1/2,  1/2, -1/2],
    [1/2, -I/2, -1/2,  I/2]])
    """

    def _entry(self, i, j, **kwargs):
        w = exp(-2*S.Pi*I/self.n)
        return w**(-i*j) / sqrt(self.n)

    def _eval_inverse(self):
        return DFT(self.n)


# Modules:
## enviroment:
import random as rd
import numpy as np

## local:

def genCigar(R1, R2, c, n_points, rot_angle=0.0):
	'''
	Generate cigar shape cluster with:
		r1 :: max radius
		r2 :: min radius
		theta :: inclination angle
		c :: center
		'''

	# rotation matrix
	rotation = getRotationMatrix(rot_angle)

	points = np.empty([n_points,2])
	for i in range(n_points):
		r1 = rd.uniform(0, R1)
		r2 = rd.uniform(0, R2)
		theta = rd.uniform(0, 2*np.pi)
		points[i] = rotation @ np.array([
			r1*np.cos(theta),
			r2*np.sin(theta)]) + c

	return points

def genDisk(R, c, n_points):

	return genCigar(R, R, c, n_points, rot_angle=0.0)

def genAnnulus(
	R1, R2, c, n_points,
	theta_start=0, theta_stop=2*np.pi):
	'''
	Generate an annulus cluster of points in [0,1]^2 with:
		c :: ring center
		R1 :: inner radius
		R2 :: outer radius
	'''
	points = np.empty([n_points,2])
	for i in range(n_points):
		r = rd.uniform(R1, R2)
		theta = rd.uniform(theta_start, theta_stop)
		points[i] = np.array([
			r*np.cos(theta) + c[0],
			r*np.sin(theta) + c[1]])

	return points

def genCross(w, h, t, c, n_points, rot_angle=0.0):
	'''
	Generate a cross with
		w :: width
		h :: height
		t :: thickness
		c :: center (2-tuple)
	'''

	# rotation matrix
	rotation = getRotationMatrix(rot_angle)

	c2 = (c[0],c[1]+(h+t)/4)
	c3 = (c[0],c[1]-(h+t)/4)

	Vol = w*t + (h-t)*t
	f1 = w*t/Vol
	f2 = 0.5*t*(h-t)/Vol

	n2 = int(f2*n_points)
	n1 = n_points - 2*n2

	points = np.concatenate(
		(genRect(w,t,c,n1),
			genRect(t,(h-t)/2,c2,n2),
			genRect(t,(h-t)/2,c3,n2)),
		axis=0)

	for i in range(n_points):
		points[i] = rotation @ (points[i]-c) + c

	return points

def genRect(w, h, c, n_points):
	'''
	Generate a rectange, with
		w :: width
		h :: height
		c :: center
	'''
	points = np.empty([n_points,2])

	for i in range(n_points):
		x = rd.uniform(c[0]-0.5*w, c[0]+0.5*w)
		y = rd.uniform(c[1]-0.5*h, c[1]+0.5*h)

		points[i] = [x,y]

	return points

def genS(h, t, c, n_points):
	'''
	Generate S shape with:
		w :: width
		h :: height
		t :: thickness
		c :: center
	'''

	w = 0.5*h

	ctop = (c[0],c[1]+(h-t)/4)
	cbot = (c[0],c[1]-(h-t)/4)

	n1 = int(0.5*n_points)
	n2 = n_points - n1

	points = np.concatenate(
		(
			genAnnulus((h-3*t)/4,(h+t)/4,ctop,n1, theta_start=0, theta_stop=1.5*np.pi),
			genAnnulus((h-3*t)/4,(h+t)/4,cbot,n2, theta_start=np.pi, theta_stop=2.5*np.pi)),
		axis=0)

	return points

def genEight(h, t, c, n_points):
	'''
	Generate figure 8 with:
		w :: width
		h :: height
		t :: thickness
		c :: center
	'''

	w = 0.5*h

	ctop = (c[0],c[1]+(h-t)/4)
	cbot = (c[0],c[1]-(h-t)/4)

	n1 = int(0.5*n_points)
	n2 = n_points - n1

	points = np.concatenate(
		(
			genAnnulus((h-3*t)/4,(h+t)/4,ctop,n1, theta_start=-0.77*np.pi, theta_stop=1.5*np.pi),
			genAnnulus((h-3*t)/4,(h+t)/4,cbot,n2, theta_start=0.75*np.pi, theta_stop=2.5*np.pi)),
		axis=0)

	return points

def genGauss(c, st0, st1, n_points, rot_angle=0.0):

	points = np.empty([n_points,2])

	points[:,0] = np.random.normal(c[0],st0,n_points)
	points[:,1] = np.random.normal(c[1],st1,n_points)

	# rotation matrix
	rotation = getRotationMatrix(rot_angle)
	for i in range(n_points):
		points[i] = rotation @ (points[i]-c) + c

	return points

def getRotationMatrix(rot_angle):
	# rotation matrix
	return np.array(
		[[np.cos(rot_angle), np.sin(rot_angle)],
		[-np.sin(rot_angle), np.cos(rot_angle)]])


import numpy as np
from cho_util.math.common import *

def from_matrix(x, out=None):
    x = np.asarray(x)
    if out is None:
        out = np.empty(shape=np.shape(x)[:-2] + (3, 3))
    np.copyto(out, x)
    return out

def from_quaternion(x, out=None):
    x = np.asarray(x)
    if out is None:
        out = np.empty(shape=np.shape(x)[:-1] + (3, 3))

    qx, qy, qz, qw = [x[..., i] for i in range(4)]

    tx = 2.0 * qx
    ty = 2.0 * qy
    tz = 2.0 * qz
    twx = tx * qw
    twy = ty * qw
    twz = tz * qw
    txx = tx * qx
    txy = ty * qx
    txz = tz * qx
    tyy = ty * qy
    tyz = tz * qy
    tzz = tz * qz

    out[..., 0, 0] = 1.0 - (tyy + tzz)
    out[..., 0, 1] = txy - twz
    out[..., 0, 2] = txz + twy

    out[..., 1, 0] = txy + twz
    out[..., 1, 1] = 1.0 - (txx + tzz)
    out[..., 1, 2] = tyz - twx

    out[..., 2, 0] = txz - twy
    out[..., 2, 1] = tyz + twx
    out[..., 2, 2] = 1.0 - (txx + tyy)

    return out

def from_euler(x, out=None):
    x = np.asarray(x)
    if out is None:
        out = np.empty(shape=np.shape(x)[:-1] + (3, 3))

    c, s = np.cos(x), np.sin(x)
    cx, cy, cz = [c[..., i] for i in range(3)]
    sx, sy, sz = [s[..., i] for i in range(3)]

    out[..., 0, 0] = cy * cz
    out[..., 0, 1] = (sx * sy * cz) - (cx * sz)
    out[..., 0, 2] = (cx * sy * cz) + (sx * sz)
    out[..., 1, 0] = cy * sz
    out[..., 1, 1] = (sx * sy * sz) + (cx * cz)
    out[..., 1, 2] = (cx * sy * sz) - (sx * cz)
    out[..., 2, 0] = -sy
    out[..., 2, 1] = sx * cy
    out[..., 2, 2] = cx * cy

    return out

def from_axis_angle(x, out=None):
    x = np.asarray(x)
    if out is None:
        out = np.empty(shape=np.shape(x)[:-1] + (3, 3))

    if x.shape[-1] == 3:
        # format : angle * axis
        angle = norm(x, keepdims=True)
        axis = x / angle[..., None]
    elif x.shape[-1] == 4:
        # format : (axis, angle)
        axis = x[..., :3]
        angle = x[..., 3:]
    else:
        raise ValueError("Invalid Input Shape : {}".format(x.shape))

    sin_axis = np.sin(angle) * axis
    cos_angle = np.cos(angle)
    cos1_axis = (1.0 - cos_angle) * axis
    axis_y, axis_z = [axis[..., i] for i in (1, 2)]
    cos1_axis_x, cos1_axis_y = [cos1_axis[..., i] for i in (0, 1)]
    sin_axis_x, sin_axis_y, sin_axis_z = [sin_axis[..., i] for i in range(3)]
    tmp = cos1_axis_x * axis_y
    m01 = tmp - sin_axis_z
    m10 = tmp + sin_axis_z
    tmp = cos1_axis_x * axis_z
    m02 = tmp + sin_axis_y
    m20 = tmp - sin_axis_y
    tmp = cos1_axis_y * axis_z
    m12 = tmp - sin_axis_x
    m21 = tmp + sin_axis_x
    diag = cos1_axis * axis + cos_angle
    diag_x, diag_y, diag_z = [diag[..., i] for i in range(3)]

    out[..., 0, 0] = diag_x
    out[..., 0, 1] = m01
    out[..., 0, 2] = m02

    out[..., 1, 0] = m10
    out[..., 1, 1] = diag_y
    out[..., 1, 2] = m12

    out[..., 2, 0] = m20
    out[..., 2, 1] = m21
    out[..., 2, 2] = diag_z

    return out

def rotate(r, x, out=None):
    x = np.asarray(x)
    if out is None:
        out = np.empty_like(x)
    np.einsum('...ij,...j->...i', r, x, out=out)
    return out

def random(size=(), *args, **kwargs):
    # TODO(yycho0108): more efficient randomization
    size = tuple(np.reshape(size, [-1])) + (3, 3)
    out = np.random.normal(size=size, *args, **kwargs)
    out = [np.linalg.qr(o)[0] for o in out.reshape(-1, 3, 3)]
    out = np.reshape(out, size)
    return out

def inverse(r, out=None):
    if out is None:
        out = np.empty_like(r)
    np.copyto(out, r.swapaxes(-2, -1))
    return out

def identity(dtype=np.float64):
    return np.eye(3, dtype=dtype)


import numpy as np

from manim_engine.constants import OUT
from manim_engine.constants import RIGHT

from functools import reduce

# Matrix operations

def get_norm(vect):
    return sum([x**2 for x in vect])**0.5

def thick_diagonal(dim, thickness=2):
    row_indices = np.arange(dim).repeat(dim).reshape((dim, dim))
    col_indices = np.transpose(row_indices)
    return (np.abs(row_indices - col_indices) < thickness).astype('uint8')

def rotation_matrix(angle, axis):
    """
    Rotation in R^3 about a specified axis of rotation.
    """
    about_z = rotation_about_z(angle)
    z_to_axis = z_to_vector(axis)
    axis_to_z = np.linalg.inv(z_to_axis)
    return reduce(np.dot, [z_to_axis, about_z, axis_to_z])

def rotation_about_z(angle):
    return [
        [np.cos(angle), -np.sin(angle), 0],
        [np.sin(angle), np.cos(angle), 0],
        [0, 0, 1]
    ]

def z_to_vector(vector):
    """
    Returns some matrix in SO(3) which takes the z-axis to the
    (normalized) vector provided as an argument
    """
    norm = get_norm(vector)
    if norm == 0:
        return np.identity(3)
    v = np.array(vector) / norm
    phi = np.arccos(v[2])
    if any(v[:2]):
        # projection of vector to unit circle
        axis_proj = v[:2] / get_norm(v[:2])
        theta = np.arccos(axis_proj[0])
        if axis_proj[1] < 0:
            theta = -theta
    else:
        theta = 0
    phi_down = np.array([
        [np.cos(phi), 0, np.sin(phi)],
        [0, 1, 0],
        [-np.sin(phi), 0, np.cos(phi)]
    ])
    return np.dot(rotation_about_z(theta), phi_down)

def rotate_vector(vector, angle, axis=OUT):
    return np.dot(rotation_matrix(angle, axis), vector)

def angle_between(v1, v2):
    return np.arccos(np.dot(
        v1 / get_norm(v1),
        v2 / get_norm(v2)
    ))

def angle_of_vector(vector):
    """
    Returns polar coordinate theta when vector is project on xy plane
    """
    z = complex(*vector[:2])
    if z == 0:
        return 0
    return np.angle(complex(*vector[:2]))

def angle_between_vectors(v1, v2):
    """
    Returns the angle between two 3D vectors.
    This angle will always be btw 0 and TAU/2.
    """
    l1 = get_norm(v1)
    l2 = get_norm(v2)
    return np.arccos(np.dot(v1, v2) / (l1 * l2))

def project_along_vector(point, vector):
    matrix = np.identity(3) - np.outer(vector, vector)
    return np.dot(point, matrix.T)

def normalize(vect):
    norm = get_norm(vect)
    if norm > 0:
        return vect / norm
    else:
        return np.zeros(len(vect))

def cross(v1, v2):
    return np.array([
        v1[1] * v2[2] - v1[2] * v2[1],
        v1[2] * v2[0] - v1[0] * v2[2],
        v1[0] * v2[1] - v1[1] * v2[0]
    ])

def get_unit_normal(v1, v2):
    return normalize(cross(v1, v2))

###

def compass_directions(n=4, start_vect=RIGHT):
    angle = 2 * np.pi / n
    return np.array([
        rotate_vector(start_vect, k * angle)
        for k in range(n)
    ])

def complex_to_R3(complex_num):
    return np.array((complex_num.real, complex_num.imag, 0))

def R3_to_complex(point):
    return complex(*point[:2])

def complex_func_to_R3_func(complex_func):
    return lambda p: complex_to_R3(complex_func(R3_to_complex(p)))

def center_of_mass(points):
    points = [np.array(point).astype("float") for point in points]
    return sum(points) / len(points)

def line_intersection(line1, line2):
    """
    return intersection point of two lines,
    each defined with a pair of vectors determining
    the end points
    """
    x_diff = (line1[0][0] - line1[1][0], line2[0][0] - line2[1][0])
    y_diff = (line1[0][1] - line1[1][1], line2[0][1] - line2[1][1])

    def det(a, b):
        return a[0] * b[1] - a[1] * b[0]

    div = det(x_diff, y_diff)
    if div == 0:
        raise Exception("Lines do not intersect")
    d = (det(*line1), det(*line2))
    x = det(d, x_diff) / div
    y = det(d, y_diff) / div
    return np.array([x, y, 0])


from scipy.optimize import minimize, Bounds
import matplotlib.pyplot as plt
from numpy.linalg import inv
from tqdm import tqdm
import pandas as pd
import numpy as np

class Markowitz(object):
    # TODO Make the 'risk_aversion' parameter optional
    # TODO change inputs to covariance

    def __init__(self, mu, sigma, corr, rf, risk_aversion=None, short_sell=True):
        """
        Receives informations about the set of assets available for allocation and computes the following
        measures as atributes:
            - cov: Covariance matrix of the assets
            - mu_p: expected return of the optimal risky portfolio (tangency portfolio)
            - sigma_p: risk of the optimal risky portfolio (tangency portfolio)
            - risky_weights: pandas series of the weights of each risky asset on the optimal risk portfolio
            - sharpe_p: expected sharpe ratio of the optimal risky portfolio.
            - mu_mv: expected return of the minimal variance portfolio
            - sigma_mv: risk of the minimal variance portfolio
            - mv_weights: pandas series of the weights of each risky asset on the minimal variance portfolio
            - sharpe_mv: expected sharpe ratio of the minimal variance portfolio.
            - weight_p: weight of the risk porfolio on the investor's portfolio. The remaining 1-weight_p is
                        allocated on the risk-free asset
            - complete_weights: weights of the risky and risk-free assets on the investor's portfolio.
            - mu_c: expected return of the investor's portfolio
            - sigma_c: risk of the investor's portfolio
            - certain_equivalent: the hypothetical risk-free return that would make the investor indiferent
                                  to its risky portfolioo of choice (portfolio C)

        Computations involving the investor's preference use the following utility function:
            U = mu_c - 0.5 * risk_aversion * (sigma_c**2)

        The class also has support for plotting the classical risk-return plane.

        :param mu: pandas series of expected returns where the index contains the names of the assets.
        :param sigma: pandas series of risk where the index contains the names of the assets
                      (index must be the same as 'mu')
        :param corr: pandas DataFrame of correlations (index and columns must contain the same index)
        :param rf: float, risk-free rate
        :param risk_aversion: coefficient of risk aversion of the investor's utility function.
        :param short_sell: If True, short-selling is allowed. If False, weights on risky-assets are
                           constrained to be between 0 and 1
        """

        # Asseert data indexes and organize
        self._assert_indexes(mu, sigma, corr)

        # Save inputs as attributes
        self.mu = mu
        self.sigma = sigma
        self.corr = corr
        self.rf = rf
        self.risk_aversion = risk_aversion
        self.short_selling = short_sell

        # Compute atributes
        self.n_assets = self._n_assets()
        self.cov = self._get_cov_matrix()

        # Get the optimal risky porfolio
        self.mu_p, self.sigma_p, self.risky_weights, self.sharpe_p = self._get_optimal_risky_portfolio()

        # get the minimal variance portfolio
        self.mu_mv, self.sigma_mv, self.mv_weights, self.sharpe_mv = self._get_minimal_variance_portfolio()

        # Get the investor's portfolio and build the complete set of weights
        self.weight_p, self.complete_weights, self.mu_c, self.sigma_c, self.certain_equivalent \
            = self._investor_allocation()

    def plot(self, figsize=None, save_path=None):

        plt.figure(figsize=figsize)
        # assets
        plt.scatter(self.sigma, self.mu, label='Assets')

        # risk-free
        plt.scatter(0, self.rf, label='Risk-Free')

        # Optimal risky portfolio
        plt.scatter(self.sigma_p, self.mu_p, label='Optimal Risk')

        # Minimal Variance Portfolio
        plt.scatter(self.sigma_mv, self.mu_mv, label='Min Variance')

        # Minimal variance frontier
        if not self.n_assets == 1:
            mu_mv, sigma_mv = self.min_var_frontier()
            plt.plot(sigma_mv, mu_mv, marker=None, color='black', zorder=-1, label='Min Variance Frontier')

        # Capital allocation line
        max_sigma = self.sigma.max() + 0.05
        x_values = [0, max_sigma]
        y_values = [self.rf, self.rf + self.sharpe_p * max_sigma]
        plt.plot(x_values, y_values, marker=None, color='green', zorder=-1, label='Capital Allocation Line')

        # Investor's portfolio
        plt.scatter(self.sigma_c, self.mu_c, label="Investor's Portfolio", color='purple')

        # Indiference Curve
        max_sigma = self.sigma_p + 0.1
        x_values = np.arange(0, max_sigma, max_sigma / 100)
        y_values = self.certain_equivalent + 0.5 * self.risk_aversion * (x_values ** 2)
        plt.plot(x_values, y_values, marker=None, color='purple', zorder=-1, label='Indiference Curve')

        # legend
        plt.legend(loc='best')

        # adjustments
        plt.xlim((0, self.sigma.max() + 0.1))
        plt.xlabel('Risk')
        plt.ylabel('Return')
        plt.tight_layout()

        # Save as picture
        if save_path is not None:
            plt.savefig(save_path)

        plt.show()

    def _n_assets(self):
        """
        Makes sure that all inputs have the correct shape and returns the number of assets
        """
        shape_mu = self.mu.shape
        shape_sigma = self.sigma.shape
        shape_corr = self.corr.shape

        max_shape = max(shape_mu[0], shape_sigma[0], shape_corr[0], shape_corr[1])
        min_shape = min(shape_mu[0], shape_sigma[0], shape_corr[0], shape_corr[1])

        if max_shape == min_shape:
            return max_shape
        else:
            raise AssertionError('Mismatching dimensions of inputs')

    def _get_optimal_risky_portfolio(self):

        if self.n_assets == 1:  # one risky asset (analytical)
            mu_p = self.mu.iloc[0]
            sigma_p = self.sigma.iloc[0]
            sharpe_p = (mu_p - self.rf)/sigma_p
            weights = pd.Series(data={self.mu.index[0]: 1},
                                name='Risky Weights')

        else:  # multiple risky assets (optimization)

            # define the objective function (notice the sign change on the return value)
            def sharpe(x):
                return -self._sharpe(x, self.mu.values, self.cov.values, self.rf, self.n_assets)

            # budget constraint
            constraints = ({'type': 'eq',
                           'fun': lambda w: w.sum() - 1})

            # Create bounds for the weights if short-selling is restricted
            if self.short_selling:
                bounds = None
            else:
                bounds = Bounds(np.zeros(self.n_assets), np.ones(self.n_assets))

            # initial guess
            w0 = np.zeros(self.n_assets)
            w0[0] = 1

            # Run optimization
            res = minimize(sharpe, w0,
                           method='SLSQP',
                           constraints=constraints,
                           bounds=bounds,
                           options={'ftol': 1e-9, 'disp': False})

            if not res.success:
                raise RuntimeError("Convergence Failed")

            # Compute optimal portfolio parameters
            mu_p = np.sum(res.x * self.mu.values.flatten())
            sigma_p = np.sqrt(res.x @ self.cov @ res.x)
            sharpe_p = -sharpe(res.x)
            weights = pd.Series(index=self.mu.index,
                                data=res.x,
                                name='Risky Weights')

        return mu_p, sigma_p, weights, sharpe_p

    def _get_minimal_variance_portfolio(self):

        def risk(x):
            return np.sqrt(x @ self.cov @ x)

        # budget constraint
        constraints = ({'type': 'eq',
                        'fun': lambda w: w.sum() - 1})

        # Create bounds for the weights if short-selling is restricted
        if self.short_selling:
            bounds = None
        else:
            bounds = Bounds(np.zeros(self.n_assets), np.ones(self.n_assets))

        # initial guess
        w0 = np.zeros(self.n_assets)
        w0[0] = 1

        # Run optimization
        res = minimize(risk, w0,
                       method='SLSQP',
                       constraints=constraints,
                       bounds=bounds,
                       options={'ftol': 1e-9, 'disp': False})

        if not res.success:
            raise RuntimeError("Convergence Failed")

        # Compute optimal portfolio parameters
        mu_mv = np.sum(res.x * self.mu.values.flatten())
        sigma_mv = np.sqrt(res.x @ self.cov @ res.x)
        sharpe_mv = (mu_mv - self.rf) / sigma_mv
        weights = pd.Series(index=self.mu.index,
                            data=res.x,
                            name='Minimal Variance Weights')

        return mu_mv, sigma_mv, weights, sharpe_mv

    def _get_cov_matrix(self):

        cov = np.diag(self.sigma) @ self.corr.values @ np.diag(self.sigma)

        cov = pd.DataFrame(index=self.sigma.index,
                           columns=self.sigma.index,
                           data=cov)

        return cov

    def _investor_allocation(self):
        weight_p = (self.mu_p - self.rf) / (self.risk_aversion * (self.sigma_p**2))
        complete_weights = self.risky_weights * weight_p
        complete_weights.loc['Risk Free'] = 1 - weight_p

        mu_c = weight_p * self.mu_p + (1 - weight_p) * self.rf
        sigma_c = weight_p * self.sigma_p

        ce = self._utility(mu_c, sigma_c, self.risk_aversion)

        return weight_p, complete_weights, mu_c, sigma_c, ce

    def min_var_frontier(self, n_steps=100):

        if self.short_selling:
            E = self.mu.values
            inv_cov = inv(self.cov)

            A = E @ inv_cov @ E
            B = np.ones(self.n_assets) @ inv_cov @ E
            C = np.ones(self.n_assets) @ inv_cov @ np.ones(self.n_assets)

            def min_risk(mu):
                return np.sqrt((C * (mu ** 2) - 2 * B * mu + A)/(A * C - B ** 2))

            min_mu = min(self.mu.min(), self.rf) - 0.01
            max_mu = max(self.mu.max(), self.rf) + 0.05

            mu_range = np.arange(min_mu, max_mu, (max_mu - min_mu) / n_steps)
            sigma_range = np.array(list(map(min_risk, mu_range)))

        else:

            sigma_range = []

            # Objective function
            def risk(x):
                return np.sqrt(x @ self.cov @ x)

            # initial guess
            w0 = np.zeros(self.n_assets)
            w0[0] = 1

            # Values for mu to perform the minimization
            mu_range = np.linspace(self.mu.min(), self.mu.max(), n_steps)

            for mu_step in tqdm(mu_range, 'Finding Mininmal variance frontier'):

                # budget and return constraints
                constraints = ({'type': 'eq',
                                'fun': lambda w: w.sum() - 1},
                               {'type': 'eq',
                                'fun': lambda w: sum(w * self.mu) - mu_step})

                bounds = Bounds(np.zeros(self.n_assets), np.ones(self.n_assets))

                # Run optimization
                res = minimize(risk, w0,
                               method='SLSQP',
                               constraints=constraints,
                               bounds=bounds,
                               options={'ftol': 1e-9, 'disp': False})

                if not res.success:
                    raise RuntimeError("Convergence Failed")

                # Compute optimal portfolio parameters
                sigma_step = np.sqrt(res.x @ self.cov @ res.x)

                sigma_range.append(sigma_step)

            sigma_range = np.array(sigma_range)

        return mu_range, sigma_range

    @staticmethod
    def _sharpe(w, mu, cov, rf, n):
        er = np.sum(w*mu)

        w = np.reshape(w, (n, 1))
        risk = np.sqrt(w.T @ cov @ w)[0][0]

        sharpe = (er - rf) / risk
        return sharpe

    @staticmethod
    def _utility(mu, sigma, risk_aversion):
        return mu - 0.5 * risk_aversion * (sigma ** 2)

    @staticmethod
    def _assert_indexes(mu, sigma, corr):
        cond1 = sorted(mu.index) == sorted(sigma.index)
        cond2 = sorted(mu.index) == sorted(corr.index)

        cond = cond1 and cond2

        assert cond, "elements in the input indexes do not match"

class BlackLitterman(object):
    # TODO implement qualitative view-setting

    def __init__(self, sigma, estimation_error, views_p, views_v, w_equilibrium=None, avg_risk_aversion=1.2,
                 mu_historical=None, mu_shrink=1, overall_confidence=1, relative_uncertainty=None):
        """
        Black-Litterman model for asset allocation. The model combines model estimates and views
        from the asset allocators. The views are expressed in the form of

            views_p @ mu = views_v

        where 'views_p' is a selection matrix and 'views_v' is a vector of values.

        :param sigma: pandas.DataFrame, robustly estimated covariance matrix of the assets.
        :param estimation_error: float, Uncertainty of the estimation. Recomended value is
                                 the inverse of the sample size used in the covariance matrix.
        :param views_p: pandas.DataFrame, selection matrix of the views.
        :param views_v: pandas.DataFrame, value matrix of the views.  # TODO allow for pandas.Series
        :param w_equilibrium: pandas.DataFrame, weights of each asset in the equilibrium
        :param avg_risk_aversion: float, average risk aversion of the investors
        :param mu_historical: pandas.DataFrame, historical returns of the asset class (can
                              be interpreted as the target of the shrinkage estimate)
        :param mu_shrink: float between 0 and 1, shirinkage intensity. If 1 (default),
                          best guess of mu is the model returns. If 0, bet guess of mu
                          is 'mu_historical'.  # TODO assert domain of 0 to 1
        :param overall_confidence: float, the higher the number, the more weight the views have in te posterior
        :param relative_uncertainty: pandas.DataFrame, the higher the value the less certain that view is.  # TODO allow for pandas series
        """

        # TODO assert input types (DataFrames)
        # TODO assert input shapes and names
        # TODO assert covariances are positive definite

        self.sigma = sigma.sort_index(0).sort_index(1)
        self.asset_names = list(self.sigma.index)
        self.n_assets = sigma.shape[0]
        self.estimation_error = estimation_error
        self.avg_risk_aversion = avg_risk_aversion
        self.mu_shrink = mu_shrink

        self.w_equilibrium = self._get_w_equilibrium(w_equilibrium)
        self.equilibrium_returns = self._get_equilibrium_returns()
        self.mu_historical = self._get_mu_historical(mu_historical)
        self.mu_best_guess = self._get_mu_best_guess()

        self.views_p = views_p.sort_index(0).sort_index(1)
        self.views_v = views_v.sort_index(0).sort_index(1)
        self.n_views = views_p.shape[0]
        self.view_names = list(self.views_p.index)
        self.overall_confidence = overall_confidence
        self.relative_uncertainty = self._get_relative_uncertainty(relative_uncertainty)
        self.omega = self._get_views_covariance()

        self.mu_bl, self.sigma_mu_bl = self._get_mu_bl()
        self.sigma_bl = self.sigma + self.sigma_mu_bl

    def _get_w_equilibrium(self, w_equilibrium):
        """
        In case 'w_equilibrium' is not passed, assumes the equilibrium is equal weighted.
        """
        if w_equilibrium is None:
            w_equilibrium = (1 / self.n_assets) * np.ones(self.n_assets)
            w_equilibrium = pd.DataFrame(data=w_equilibrium,
                                         index=self.asset_names,
                                         columns=['Equilibrium Weights'])
        else:
            w_equilibrium = w_equilibrium.sort_index()

        return w_equilibrium

    def _get_equilibrium_returns(self):
        """
        Computes the equilibrium returns based on the equilibrium weights and
        average risk aversion.
        """
        sigma = self.sigma.values
        w_equilibrium = self.w_equilibrium.values
        pi = 2 * self.avg_risk_aversion * sigma @ w_equilibrium
        pi = pd.DataFrame(data=pi,
                          index=self.asset_names,
                          columns=['Equilibrium Returns'])
        return pi

    def _get_mu_historical(self, mu_historical):
        """
        In case 'mu_historical' is not passed, uses zeros as the shrinking target.
        """
        if mu_historical is None:
            mu_historical = np.zeros(self.n_assets)
            mu_historical = pd.DataFrame(data=mu_historical,
                                         index=self.asset_names,
                                         columns=['Historical Returns'])
        else:
            mu_historical = mu_historical.sort_index()

        return mu_historical

    def _get_mu_best_guess(self):
        """
        Uses shrinkage to estimate the best guess for mu by balancing between
        the model equilibrium returns and the historical returns.
        """
        best_guess = self.mu_shrink * self.equilibrium_returns.values + (1-self.mu_shrink) * self.mu_historical.values
        best_guess = pd.DataFrame(data=best_guess,
                                  index=self.asset_names,
                                  columns=['Best Guess of mu'])
        return best_guess

    def _get_relative_uncertainty(self, relative_uncertainty):
        """
        In case 'relative_uncertainty' is not passed, uses ones for every asset.
        """
        if relative_uncertainty is None:
            relative_uncertainty = np.ones(self.n_views)
            relative_uncertainty = pd.DataFrame(data=relative_uncertainty,
                                                columns=['Relative Uncertainty'],
                                                index=self.view_names)
        else:
            relative_uncertainty = relative_uncertainty.sort_index()

        return relative_uncertainty

    def _get_views_covariance(self):
        """
        Computes Omega, the covariance of the views.
        """
        c = self.overall_confidence
        u = np.diag(self.relative_uncertainty.values.flatten())
        P = self.views_p.values
        Sigma = self.sigma.values

        omega = (1/c) * u @ P @ Sigma @ P.T @ u

        if np.linalg.det(omega) < np.finfo(float).eps:
            n, m = omega.shape
            omega = omega + 1e-16 * np.eye(n, m)

        omega = pd.DataFrame(data=omega,
                             index=self.view_names,
                             columns=self.view_names)

        return omega

    def _get_mu_bl(self):
        """
        Computes 'mu_bl', the vector of returns that combines the best guess
        for mu (equilibrium and empirical) with the views from the asset allocators
        """
        tau = self.estimation_error
        sigma = self.sigma.values
        P = self.views_p.values
        pi = self.mu_best_guess.values
        v = self.views_v.values
        omega = self.omega.values

        sigma_mu_bl = inv(inv(tau * sigma) + P.T @ inv(omega) @ P)
        B = inv(tau * sigma) @ pi + P.T @ inv(omega) @ v
        mu_bl = sigma_mu_bl @ B

        sigma_mu_bl = pd.DataFrame(data=sigma_mu_bl, index=self.asset_names, columns=self.asset_names)
        mu_bl = pd.DataFrame(data=mu_bl, index=self.asset_names, columns=['Expected Returns'])

        return mu_bl, sigma_mu_bl


import numpy as np
from decimal import *
from matplotlib.patches import Ellipse
import matplotlib.transforms as transforms

def gaussian_dec(x, m, var):
    '''
    Computes the Gaussian pdf value (Decimal type) at x.
    x: value at which the pdf is computed (Decimal type)
    m: mean (Decimal type)
    var: variance
    '''
    p = np.exp(-(x-m)**Decimal(2)/(Decimal(2)*Decimal(var)))/(np.sqrt(Decimal(2)*Decimal(np.pi)*Decimal(var)))
    return p

def gaussian(x, m, var):
    '''
    Computes the Gaussian pdf value at x.
    x: value at which the pdf is computed (Decimal type)
    m: mean
    var: variance
    '''
    p = np.exp(-(x-m)**2/(2*var))/(np.sqrt(2*np.pi*var))
    return p

def laplace_dec(x, m, b):
    '''
    Computes the Laplace pdf value (Decimal type) at x.
    x: value at which the pdf is computed (Decimal type)
    m: mean
    b: scale parameter
    '''
    p = np.exp(-np.abs(x-m)/(Decimal(b)))/(Decimal(2)*Decimal(b))
    return p

def laplace(x, m, b):
    '''
    Computes the Laplace pdf value at x.
    x: value at which the pdf is computed
    m: mean
    b: scale parameter
    '''
    p = np.exp(-np.abs((x-m)/b))/(2*b)
    return p

def bayesian_update(L, mu):
    '''
    Computes the Bayesian update.
    L: likelihoods matrix
    mu: beliefs matrix
    '''
    aux = L*mu
    bu = aux/aux.sum(axis = 1)[:, None]
    return bu

def asl_bayesian_update(L, mu, delta):
    '''
    Computes the adaptive Bayesian update.
    L: likelihoods matrix
    mu: beliefs matrix
    delta: step size
    '''
    aux = L**(delta)*mu**(1-delta)
    bu = aux/aux.sum(axis = 1)[:, None]
    return bu

def DKL(m,n,dx):
    '''
    Computes the KL divergence between m and n.
    m: true distribution in vector form
    n: second distribution in vector form
    dx : sample size
    '''
    mn = m/n
    mnlog = np.log(mn)
    return np.sum(m*dx*mnlog)

def decimal_array(arr):
    '''
    Converts an array to an array of Decimal objects.
    arr: array to be converted
    '''
    if len(arr.shape) == 1:
        return np.array([Decimal(y) for y in arr])
    else:
        return np.array([[Decimal(x) for x in y] for y in arr])

def float_array(arr):
    '''
    Converts an array to an array of float objects.
    arr: array to be converted
    '''
    if len(arr.shape)==1:
        return np.array([float(y) for y in arr])
    else:
        return np.array([[float(x) for x in y] for y in arr])

def asl_markov(mu_0, csi, A, N_ITER, theta, b, delta = 0):
    '''
    Executes the adaptive social learning algorithm with Laplace likelihoods
    for the Markovian example
    mu_0: initial beliefs
    csi: observations
    A: list of combination matrices
    N_ITER: number of iterations
    theta: vector of means for the Laplace likelihoods
    b: scale parameter of the Laplace likelihoods
    delta: step size
    '''
    mu = mu_0.copy()
    N = len(A[0])
    MU = [mu]
    for i in range(N_ITER):
        L_i = np.array([laplace(csi[:,i], t, b) for t in theta]).T

        # unindentifiability
        L_i[:N//3, 1] = L_i[:N//3, 0]
        L_i[N//3:2*N//3, 1] = L_i[N//3:2*N//3, 2]
        L_i[2*N//3:, 2] = L_i[2*N//3:, 0]

        psi = asl_bayesian_update(L_i, mu, delta)
        decpsi = np.log(psi)

        mu = np.exp((A[i].T).dot(decpsi)) / np.sum(np.exp((A[i].T).dot(decpsi)), axis=1)[:, None]
        MU.append(mu)
    return MU

def sl_markov(mu_0, csi, A, N_ITER, thetadec, b):
    '''
    Executes the social learning algorithm with Laplace likelihoods
    for the Markovian example
    mu_0: initial beliefs
    csi: observations
    A: Combination matrix
    N_ITER: number of iterations
    thetadec: vector of means for the Laplace likelihoods (Decimal type)
    b: scale parameter of the Laplace likelihoods
    is_gaussian: flag indicating if the likelihoods are Gaussian
    '''
    mu = decimal_array(mu_0)
    N = len(A[0])
    MU = [mu]
    for i in range(N_ITER):
        L_i = np.array([laplace_dec(csi[:,i], t, b) for t in thetadec]).T

        # unindentifiability
        L_i[:N//3, 1] = L_i[:N//3, 0]
        L_i[N//3:2*N//3, 1] = L_i[N//3:2*N//3, 2]
        L_i[2*N//3:, 2] = L_i[2*N//3:, 0]

        psi = bayesian_update(L_i, mu)
        decpsi = np.array([[x.ln() for x in y] for y in psi])
        C = decimal_array(A[i])
        mu = np.exp((C.T).dot(decpsi)) / np.sum(np.exp((C.T).dot(decpsi)), axis=1)[:, None]
        MU.append(mu)
    return MU

def asl(mu_0, csi, A, N_ITER, theta, var, delta = 0, is_gaussian = False):
    '''
    Executes the adaptive social learning algorithm with Gaussian or Laplace likelihoods.
    mu_0: initial beliefs
    csi: observations
    A: Combination matrix
    N_ITER: number of iterations
    theta: vector of means for the likelihoods
    var: variance of Gaussian likelihoods/ scale parameter of the Laplace likelihoods
    delta: step size
    is_gaussian: flag indicating if the likelihoods are Gaussian
    '''
    mu = mu_0.copy()
    N = len(A)
    MU = [mu]
    for i in range(N_ITER):
        if is_gaussian:
            L_i = np.array([gaussian(csi[:, i], t, var) for t in theta]).T
        else:
            L_i = np.array([laplace(csi[:, i], t, var) for t in theta]).T

        # unindentifiability
        L_i[:N//3, 1] = L_i[:N//3, 0]
        L_i[N//3:2*N//3, 1] = L_i[N//3:2*N//3, 2]
        L_i[2*N//3:, 2] = L_i[2*N//3:, 0]

        psi = asl_bayesian_update(L_i, mu, delta)
        decpsi = np.log(psi)
        mu = np.exp((A.T).dot(decpsi)) / np.sum(np.exp((A.T).dot(decpsi)), axis=1)[:, None]
        MU.append(mu)
    return MU

def sl(mu_0, csi, A, N_ITER, thetadec, var, is_gaussian = False):
    '''
    Executes the social learning algorithm with Gaussian or Laplace likelihoods.
    mu_0: initial beliefs
    csi: observations
    A: Combination matrix
    N_ITER: number of iterations
    thetadec: vector of means for the likelihoods (Decimal type)
    var: variance of Gaussian likelihoods/ scale parameter of the Laplace likelihoods
    is_gaussian: flag indicating if the likelihoods are Gaussian
    '''
    mu = mu_0.copy()
    N = len(A)
    MU = [mu]
    for i in range(N_ITER):
        if is_gaussian:
            L_i = np.array([gaussian_dec(csi[:, i], t, var) for t in thetadec]).T
        else:
            L_i = np.array([laplace_dec(csi[:, i], t, var) for t in thetadec]).T

        # unindentifiability
        L_i[:N//3, 1] = L_i[:N//3, 0]
        L_i[N//3:2*N//3, 1] = L_i[N//3:2*N//3, 2]
        L_i[2*N//3:, 2] = L_i[2*N//3:, 0]

        psi = bayesian_update(L_i, mu)
        decpsi = np.array([[x.ln() for x in y] for y in psi])
        mu = np.exp((A.T).dot(decpsi)) / np.sum(np.exp((A.T).dot(decpsi)), axis =1)[:, None]
        MU.append(mu)
    return MU

def comb_matrix_from_p(p, G):
    '''
    Computes combination matrix from the Perron eigenvector and graph specification.
    G: adjacency matrix
    p: Perron eigenvector
    '''
    A = np.where(G > 0, (p.T * np.ones((10,1))).T, 0)
    np.fill_diagonal(A, 1 - np.sum(A - np.diag(np.diag(A)), axis=0))
    return A

def lmgf(thetazero, theta1, t):
    '''
    Computes the LMGF for the family of Laplace distributions, specified in section VIII. C.
    thetazero: true state of nature
    theta1: state different than true state
    t: vector of real values
    '''
    alpha = theta1 - thetazero
    if thetazero >= theta1:
        f = np.log(np.exp(alpha * (t + 1)) + np.exp(-alpha * t) - np.exp(alpha / 2)
                   * np.sinh(alpha*(t + 1/2))/(t + 1/2)) - np.log(2)
    elif thetazero<theta1:
        f = np.log(np.exp(-alpha * (t + 1)) + np.exp(alpha * t) + np.exp(-alpha / 2)
                   * np.sinh(alpha*(t + 1/2))/(t + 1/2)) - np.log(2)
    return f

def Cave(L, t1, t2, dt):
    '''
    Computes the covariance between the log likelihood ratios for two hypotheses.
    L: likelihood Functions
    t1: hypothesis 1
    t2: hypothesis 2
    dt: step size
    '''
    aux = L[0] / L[t1]
    auxlog = np.log(aux)
    aux1 = L[0] / L[t2]
    auxlog1 = np.log(aux1)
    return np.sum(L[0] * dt * auxlog1 * auxlog) - DKL(L[0], L[t1], dt) * DKL(L[0], L[t2], dt)

def confidence_ellipse(x, y, ax, n_std=3.0, facecolor='none', **kwargs):
    '''
    Create a plot of the covariance confidence ellipse of *x* and *y*. Inspired on the code in:
    https://matplotlib.org/devdocs/gallery/statistics/confidence_ellipse.html
    '''
    if x.size != y.size:
        raise ValueError("x and y must be the same size")
    cov = np.cov(x, y)
    pearson = cov[0, 1]/np.sqrt(cov[0, 0] * cov[1, 1])
    ell_radius_x = np.sqrt(1 + pearson)
    ell_radius_y = np.sqrt(1 - pearson)
    ellipse = Ellipse((0, 0),
        width=ell_radius_x * 2,
        height=ell_radius_y * 2,
        facecolor=facecolor,
        **kwargs)
    scale_x = np.sqrt(cov[0, 0]) * n_std
    mean_x = np.mean(x)
    scale_y = np.sqrt(cov[1, 1]) * n_std
    mean_y = np.mean(y)
    transf = transforms.Affine2D() \
        .rotate_deg(45) \
        .scale(scale_x, scale_y) \
        .translate(mean_x, mean_y)
    ellipse.set_transform(transf + ax.transData)
    return ax.add_patch(ellipse)

def gaussian_ellipse(C, Ave, ax, n_std=3.0, facecolor='none', **kwargs):
    '''
    Create a plot of the covariance confidence ellipse relative to the Covariance matrix C.
    C: covariance matrix
    Ave: average vector
    n_std: number of standard deviation ellipses
    '''
    cov = C
    pearson = cov[0, 1]/np.sqrt(cov[0, 0] * cov[1, 1])
    ell_radius_x = np.sqrt(1 + pearson)
    ell_radius_y = np.sqrt(1 - pearson)
    ellipse = Ellipse((0, 0),
        width = ell_radius_x * 2,
        height = ell_radius_y * 2,
        facecolor = facecolor,
        **kwargs)
    scale_x = np.sqrt(cov[0, 0]) * n_std
    mean_x = Ave[0]
    scale_y = np.sqrt(cov[1, 1]) * n_std
    mean_y = Ave[1]
    transf = transforms.Affine2D() \
        .rotate_deg(45) \
        .scale(scale_x, scale_y) \
        .translate(mean_x, mean_y)
    ellipse.set_transform(transf + ax.transData)
    return ax.add_patch(ellipse)


import numpy as np
import matplotlib.pyplot as plt

a = [[1], [2], [3], [4], [5]] # a list
b = [[1], [2], [3], [4], [5]]
print('Type of a is ',type(a))

a_array = np.array(a, dtype=float) # convert list to np array of type X
b_array = np. array(b, dtype=float)
print('Type of a_array is ',type(a_array))

data = np.hstack((a_array, b_array))
print('Type of data is ',type(data))
# print(data)
print(np.shape(data))

data = np.concatenate((data,[[5, 3]]))
# print(data)
print(np.shape(data))
print('Type of data is ',type(data))

# initalize empty array
data = np.array([], dtype=float).reshape(0,2)
print('data = ', data)
for i in range(3):
    # data = np.concatenate((data,[[i, i]]),axis=0) # use concatenate
    data = np.vstack([data, [[i, -i]]])              # or use vstack
print(data)
print(data.shape)

print(data[:,1])

ar =  np.full((2,10), np.nan)
ar[0][0] = 0
ar[1][0] = 1
ar[0][1] = 1
ar[1][1] = 2

print(ar)

ar[:, 5] = 5
ar[:, 6] = 6

print(ar)
print(ar[0,:])

fig, ax = plt.subplots()
art, = ax.plot(ar[0,:], ar[1,:])

arr = np.arange(5)
print(arr)
print(arr.size)
print(arr[:arr.size-1])

data = np.random.rand(3,2)
print(data)

print(data[:, 0])

data[:, 0] = data[:, 0] * 2
print(data)


import numpy as np
import input_data
from sklearn.utils import shuffle

np.random.seed(9999)

mnist = input_data.read_data_sets('../MNIST_data', one_hot=True)

X_train = mnist.train.images
t_train = mnist.train.labels

X_test = mnist.test.images
t_test = mnist.test.labels

X_train, t_train = shuffle(X_train, t_train)

# Model
W = np.random.randn(784, 10) * 0.01

def softmax(x):
    ex = np.exp(x - np.max(x, axis=1)[:, None])
    return ex / ex.sum(axis=1)[:, None]

def NLL(z, t):
    return -np.mean(np.sum(t*np.log(softmax(z)), axis=1))

m = 64  # mb size
alpha = 0.001
r = np.zeros_like(W)
eps = 1e-8

# Visualization stuffs
losses = []

# Training
for i in range(1, 5000):
    X_mb, t_mb = mnist.train.next_batch(m)
    t_mb_idx = t_mb.argmax(axis=1)

    # Forward
    z = X_mb @ W
    loss = NLL(z, t_mb)

    # Loss
    if (i-1) % 100 == 0:
        print(f'Iter-{i}; Loss: {loss:.3f}')

    losses.append(loss if i == 1 else 0.99*losses[-1] + 0.01*loss)

    m = z.shape[0]

    # Gradients
    dz = softmax(z)
    dz[range(m), t_mb_idx] -= 1
    dz /= m
    g = X_mb.T @ dz

    # Moments
    r = r + g*g

    # Step
    delta = g / (np.sqrt(r) + eps)
    W = W - alpha * delta

y = softmax(X_test @ W).argmax(axis=1)
acc = np.mean(y == t_test.argmax(axis=1))

print(f'Accuracy: {acc:.3f}')
np.save('adagrad_losses.npy', losses)


import numpy as np

def gamma_hrf(duration,A=1.,tau=1.08,n=3,delta=2.05,Fs=1.0):

    r"""A gamma function hrf model, with two parameters, based on [Boynton1996]_

    Parameters
    ----------

    duration: float, the length of the HRF (in the inverse units of the sampling
    rate)

    A: float, a scaling factor, sets the max of the function, defaults to 1

    tau: float The time constant of the gamma function, defaults to 1.08

    n: int, the phase delay of the gamma function, defaults to 3

    delta: a pure delay, allowing for an additional delay from the onset of the
    time-series to the beginning of the gamma hrf, defaults to 2.05

    Fs: float, the sampling rate, defaults to 1.0

    Returns
    -------

    h: the gamma function hrf, as a function of time

    Notes
    -----
    This is based on equation 3 in [Boynton1996]_:

    .. math::

        h(t) = \frac{(\frac{t-\delta}{\tau})^{(n-1)}e^{-(\frac{t-\delta}{\tau})}}{\tau(n-1)!}

    .. [Boynton1996] Geoffrey M. Boynton, Stephen A. Engel, Gary H. Glover and
       David J. Heeger (1996). Linear Systems Analysis of Functional Magnetic
       Resonance Imaging in Human V1. J Neurosci 16: 4207-4221

    """
    # XXX Maybe change to take out the time (Fs, duration, etc) from this and
    # instead implement this in units of sampling interval (pushing the time
    # aspect to the higher level)?
    if type(n) is not int:
        print ('gamma_hrf received unusual input, converting n from %s to %i'
               %(str(n),int(n)))

        n=int(n)

    sampling_interval = 1/float(Fs)

    #Prevent negative delta values:
    if delta<0:
        raise ValueError('in gamma_hrf, delta cannot be smaller than 0')

    #Prevent cases in which the delta is larger than the entire hrf:
    if delta>duration:
     raise ValueError('in gamma_hrf, delta cannot be larger than the duration')

    t_max = duration - delta

    t = np.hstack([np.zeros((delta*Fs)),np.linspace(0,t_max,t_max*Fs)])

    t_tau = t/tau

    h = (t_tau**(n-1) * np.exp(-1*(t_tau)) /
         (tau * factorial(n-1) ) )

    return A*h/max(h)

def polonsky_hrf(A, B, tau1, f1, tau2, f2,t_max,Fs=1.0):
    r""" HRF based on [Polonsky2000]_

    .. math::

       H(t) = exp(\frac{-t}{\tau_1}) sin(2\cdot\pi f_1 \cdot t) -a\cdot exp(-\frac{t}{\tau_2})*sin(2\pi f_2 t)

    .. [Polonsky2000] Alex Polonsky, Randolph Blake, Jochen Braun and David
       J. Heeger. Neuronal activity in human primary visual cortex correlates
       with perception during binocular rivalry. Nature Neuroscience 3: 1153-1159

    """
    sampling_interval = 1/float(Fs)

    t = np.arange(0,t_max,sampling_interval)

    h = (np.exp(-t/tau1) * np.sin(2*np.pi * f1 * t) -
            (B * np.exp(-t/tau2) * np.sin(2 * np.pi * f2 * t)))

    return A*h/max(h)


# import standard modules

# import third party modules
import numpy as np

# import project related modules
from simple_numpy_net.general import Layer

class Sigmoid(Layer):
    """
    create a sigmoid function which can be used within a neuronal network due to the fact that also the derivative
    is available. Sigmoid inherits form Layer
    """

    def __init__(self):
        super().__init__()  # call to parent class

    def __str__(self):
        """
        string representation of the layer.

        :return: str: returns the name of the activation function and it's function
        """
        return f"Activation: Sigmoid"

    def forward(self, x: np.array):
        """
        performs a sigmoid(x) operation of parameter x.

        :param x: numpy.array: data which does into the sigmoid function
        :return: numpy.array of sigmoid(x)
        """

        # line of code to prevent overflow in numpy since this is a common issue
        signal = np.clip(x, -150000, 150000)

        # perform the sigmoid operation with the overflow signal and data x
        # safe the result in global attribute cache, since the result will be needed for back propagation as well
        self.cache = 1.0 / (1.0 + np.exp(-1.0 * signal))
        return self.cache

    def backward(self, z: np.array):
        """
        takes an array with error values and performs a back propagation step for the Sigmoid activation function.
        will will return a new error array with respect to the Sigmoid function.

        :param z: np.array: array of error values
        :return: returns the derivative of sigmoid with respect to it's previous calculated output and error z
        """

        return (self.cache * (1.0 - self.cache)) * z

class RelU(Layer):
    """"
    RelU or Rectified Linear Unit function as activation function which will output the input directly if it's
    positive or put it to zero if not. RelU inherits form Layer
    """

    def __init__(self):
        super().__init__()  # call to parent class

    def __str__(self):
        """
        string representation of the activation layer and it's function
        :return: str: Type and Function
        """
        return "Activation: RelU"

    def forward(self, x: np.array):
        """
        RelU(x) operation - will out put the same array but with values < 0 as 0
        :param x: numpy.array: data to which goes into the RelU function

        :return: numpy.array: returns the numpy array with modified values < 0
        """

        # make a copy of x for the back propagation process
        self.cache = x.copy()

        # apply function and return new array
        return np.maximum(0, x)

    def backward(self, z: np.array):
        """
        creates a derivative of the RelU with respect to it's input and multiplies it with the incoming error array.

        :param z: numpy.array: array of error values to be back propagated
        :return: numpy.array: returns an array of new errors with the RelU layer taken into account
        """

        # as usual values smaller 0 will be set to zero. But since it's the derivative values greater 0 will be set to
        # 1 and the error is multiplied in order to propagate the error back.
        return np.where(self.cache < 0, 0, 1) * z

class Softmax:
    """
    provides only one function with is forward since backward is not needed. Produces a probability distribution over
    k input values.
    """

    def forward(self, x: np.array):
        """
        performs a Softmax(x) operation.
        :param x: numpy.array: data which will be feed into the Softmax function
        :return: numpy.array for a smooth probability distribution over input x
        """

        # operation to avoid overflow in numpy
        x = np.clip(x, -150000, 150000)

        # create an exponential of x and subtract the max value of x in order to make the function more stable
        exps = np.exp(x - np.max(x))

        # return results of softmax function
        return exps / np.sum(exps)


from matplotlib import pyplot as plt
import numpy as np
from scipy.stats import beta
import seaborn as sns

def main():
    a =  81
    b = 219
    stats = beta.stats(a, b, moments='mvsk')
    print('Mean: %.3f, Variance: %.3f, Skew: %.3f, Kurtosis: %.3f' % stats)
    x = np.linspace(0, 1, 1000)
    y = beta.pdf(x, a, b)
    plt.plot(x, y, 'r-', lw=2, alpha=0.6, label='beta pdf')
    plt.legend()
    plt.show()

if __name__ == '__main__':
    main()


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

def product(expr1, expr2):
    '''Product of two expressions'''
    prod = expand(expr1 * expr2)
    print(prod)

if __name__ == '__main__':
    try:
        expr1 = sympify(input('Enter the first expression: '))
        expr2 = sympify(input('Enter the second expression: '))
    except SympifyError:
        print('Invalid input')
    else:
        product(expr1, expr2)


#---------------------------------------------------------------------------------
# Generate a distribution of points following the Soneira-Peebles fractal model
# Author: Pablo Villanueva Domingo
# Last update: 25/6/20
#---------------------------------------------------------------------------------

import numpy as np
import matplotlib.pyplot as plt

#--- PARAMETERS ---#

# Reduction factor for the radii
lamb = 2
# Number of levels (not including the zero level)
n_levels = 5
# Maximum radius
R0 = 1000
# Size of the window
dimx = R0 + R0/lamb
# Mean number of points per cluster
eta = 5
# If 1, the number of points per cluster is a poisson process with eta as average number
eta_random = 0
# If 1, show scatter points
show_scatter = 1
# If 1, show scatter points
show_circles = 0
# Colors per level
cols = ["cyan", "r", "b", "g", "purple", "m", "k"]
#cols = []
#for i in range(n_levels+1):
#    cols.append("k")
# Random seed
seed = 0
# Size scatter points
s0 = 100
# Transparency for scatter points
alphapoints = .5

#--- FUNCTIONS ---#

# Poisson process in a disk
def poisson_process(eta, originx, originy, Rparent):

    if eta_random:
        num_points = np.random.poisson(eta)
    else:
        num_points = eta

    theta = 2.*np.pi*np.random.uniform(0, 1, num_points)
    rho = Rparent*np.sqrt(np.random.uniform(0, 1, num_points))

    xx = originx + rho*np.cos(theta)
    yy = originy + rho*np.sin(theta)

    return xx, yy

# Soneira-Peebles point process
def soneira_peebles_model(n_levels, eta, lamb, R0, seed):

    fig, ax = plt.subplots()
    margins = {  #     vvv margin in inches
    "left"   :     0.,
    "bottom" :     0.,
    "right"  :  1.,
    "top"    :  1.}
    fig.subplots_adjust(**margins)

    np.random.seed(seed=seed)
    Rparent = R0
    xparents, yparents = [0.], [0.]

    if show_scatter:
        ax.scatter(0., 0., s=s0, color=cols[0], alpha = alphapoints)
    if show_circles:
        circle = plt.Circle((0., 0.), Rparent, color=cols[0], alpha = 0.1)
        ax.add_artist(circle)

    for n in range(n_levels):
        R = Rparent/lamb
        pointsx, pointsy = [], []
        for i, j, in zip(xparents,yparents):
            originx, originy = i, j
            xx, yy = poisson_process(eta, originx, originy, Rparent)
            pointsx.extend(xx); pointsy.extend(yy)

        if show_scatter:
            ax.scatter(pointsx, pointsy, s=s0*(R/R0)**(2.), color=cols[n+1], alpha = alphapoints)
        if show_circles:
            for x, y in zip(pointsx, pointsy):
                circle = plt.Circle((x, y), R, color=cols[n+1], alpha = 0.3)
                ax.add_artist(circle)

        xparents, yparents = pointsx, pointsy
        Rparent = R

    if show_circles:
        plt.xlim([-dimx,dimx])
        plt.ylim([-dimx,dimx])
    plt.axis("off")
    plt.gca().set_aspect('equal', adjustable='box')
    plt.savefig("Plots/soneirapeebles_levels_%d_eta_%d_lambda_%d_seed_%d_eta_random_%d.png"%(n_levels, eta, lamb, seed, eta_random), bbox_inches='tight', dpi=300)
    plt.show()
    plt.close(fig)

#--- MAIN ---#

for seed in range(5):
    soneira_peebles_model(n_levels, eta, lamb, R0, seed)


import numpy as np
import pandas as pd
from sklearn.metrics.regression import _check_reg_targets
from sklearn.utils import check_consistent_length

def cumulated_absolute_percentage_error(y_true, y_pred):
    """
    CAPE (Cumulated Absolute Percentage Error)
    Function used by CNR for the evaluation of predictions

    Parameters
    ----------
    y_true: array-like of shape = (n_samples)
        Ground truth (correct) target values.
    y_pred: array-like of shape = (n_samples)
        Estimated target values.

    Returns
    -------
    cape: float
        The metric evaluated with the two dataframes. This must not be NaN.

    References
    ----------
    """

    return 100 * np.sum(np.abs(y_pred - y_true)) / np.sum(y_true)

def theils_u1_error(y_true, y_pred, sample_weight=None, multioutput='uniform_average'):
    """
    Ref: http://www.forecastingprinciples.com/data/definitions/theil's%20u.html
    Theil, H. (1958), Economic Forecasts and Policy. Amsterdam: North Holland.

    This formula is implemented but not used because considered ambiguous by Bliemel.

    :param y_true: true value
    :param y_pred: predicted value
    :param sample_weight:
    :param multioutput:

    :return:
    """

    if isinstance(y_true, pd.DataFrame):
        y_true = y_true.values
    # return mean_absolute_error(y_true, y_pred)
    if len(y_true) != len(y_pred):
        raise ValueError('The real values and the prediction must have the same lenght,'
                         ' {} != {}'.format(len(y_true), len(y_pred)))

    _, y_true, y_pred, multioutput = _check_reg_targets(y_true, y_pred, multioutput)
    check_consistent_length(y_true, y_pred, sample_weight)

    # Root Mean Squared Error
    rmse = np.sqrt(np.average((y_true - y_pred) ** 2, weights=sample_weight, axis=0))

    b = np.sqrt(np.average(y_pred ** 2, weights=sample_weight, axis=0))
    c = np.sqrt(np.average(y_true ** 2, weights=sample_weight, axis=0))

    output_error = rmse / (b + c)

    if isinstance(multioutput, str):
        if multioutput == 'raw_values':
            return output_error
        elif multioutput == 'uniform_average':
            # pass None as weights to np.average: uniform mean
            multioutput = None
    return np.average(output_error, weights=multioutput)

def theils_u2_error(y_true, y_pred, sample_weight=None, multioutput='uniform_average'):
    """
    Ref: http://www.forecastingprinciples.com/data/definitions/theil's%20u.html

    Thiel, H. (1966), Applied Economic Forecasting. Chicago: Rand McNally.

    This formula is implemented because considered non-ambiguous by Bliemel.

    :param y_true: true value
    :param y_pred: predicted value
    :param sample_weight:
    :param multioutput:

    :return:
    """

    if isinstance(y_true, pd.DataFrame):
        y_true = y_true.values
    # return mean_absolute_error(y_true, y_pred)
    if len(y_true) != len(y_pred):
        raise ValueError('The real values and the prediction must have the same lenght,'
                         ' {} != {}'.format(len(y_true), len(y_pred)))

    _, y_true, y_pred, multioutput = _check_reg_targets(y_true, y_pred, multioutput)
    check_consistent_length(y_true, y_pred, sample_weight)

    # Root Squared Error
    rse = np.sqrt(((y_true - y_pred) ** 2).sum())
    c = np.sqrt((y_true ** 2).sum())

    output_error = rse / c

    if isinstance(multioutput, str):
        if multioutput == 'raw_values':
            return output_error
        elif multioutput == 'uniform_average':
            # pass None as weights to np.average: uniform mean
            multioutput = None
    return np.average(output_error, weights=multioutput)


import numpy as np
from scipy.optimize import fmin_cg

# Subtracting the mean of each Product's rating from given array of user ratings
def normalize_ratings(ratings):
    mean_ratings = np.nanmean(ratings, axis=0)
    return ratings - mean_ratings, mean_ratings

# Cost function for low rank matrix factorization
def cost(X, *args):

    num_users, num_products, num_features, ratings, mask, regularization_amount = args

    # Unroll P and Q
    P = X[0:(num_users * num_features)].reshape(num_users, num_features)
    Q = X[(num_users * num_features):].reshape(num_products, num_features)
    Q = Q.T

    # Calculate current cost
    return (np.sum(np.square(mask * (np.dot(P, Q) - ratings))) / 2) + ((regularization_amount / 2.0) * np.sum(np.square(Q.T))) + ((regularization_amount / 2.0) * np.sum(np.square(P)))

# Calculate the cost gradients with the current P and Q.
def gradient(X, *args):
    num_users, num_products, num_features, ratings, mask, regularization_amount = args

    # Unroll P and Q
    P = X[0:(num_users * num_features)].reshape(num_users, num_features)
    Q = X[(num_users * num_features):].reshape(num_products, num_features)
    Q = Q.T

    # Calculating current gradients for both P and Q
    P_grad = np.dot((mask * (np.dot(P, Q) - ratings)), Q.T) + (regularization_amount * P)
    Q_grad = np.dot((mask * (np.dot(P, Q) - ratings)).T, P) + (regularization_amount * Q.T)

    # Return the gradients as one rolled-up array as expected by fmin_cg
    return np.append(P_grad.ravel(), Q_grad.ravel())

# Factorising rating arrays into two latent featured arrays (User features & Product Features)
def low_rank_matrix_factorization(ratings, mask=None, num_features=15, regularization_amount=0.01):
    num_users, num_products = ratings.shape

    # If no mask is provided, consider all 'NaN' elements as missing and create a mask.
    if mask is None:
        mask = np.invert(np.isnan(ratings))

    # Replace NaN values with zero
    ratings = np.nan_to_num(ratings)

    # Create P and Q and fill with random numbers to start
    np.random.seed(0)
    P = np.random.randn(num_users, num_features)
    Q = np.random.randn(num_products, num_features)

    # Roll up P and Q into a contiguous array as fmin_cg expects
    initial = np.append(P.ravel(), Q.ravel())

    # Create an args array as fmin_cg expects
    args = (num_users, num_products, num_features, ratings, mask, regularization_amount)

    # Call fmin_cg to minimize the cost function and this find the best values for P and Q
    X = fmin_cg(cost, initial, fprime=gradient, args=args, maxiter=3000)

    # Unroll the new P and new Q arrays out of the contiguous array returned by fmin_cg
    nP = X[0:(num_users * num_features)].reshape(num_users, num_features)
    nQ = X[(num_users * num_features):].reshape(num_products, num_features)

    return nP, nQ.T

# Calculating Root Mean Squared Error between matrix of real & predicted ratings
def RMSE(real, predicted):
    return np.sqrt(np.nanmean(np.square(real - predicted)))


import numpy as np
import matplotlib.pyplot as graph

a = np.arange(0,210,30)
x = np.cos(a)
y = np.sin(a)

graph.title("SIN AND COS GRAPH")
graph.xlabel("Degrees")
graph.ylabel("Values")
graph.plot(a,x,color = 'r', label = "Cos")
graph.plot(a,y,color = 'g',label = "Sin")
graph.legend()
graph.show()

Numerical Computation/assets/Graphs/Comparing Sin and Cos.py


import csv
import numpy as np
import matplotlib.pyplot as plt
import sys
import warnings

if not sys.warnoptions:
    warnings.simplefilter("ignore")

path_X = "logisticX.csv";
path_Y = "logisticY.csv";

read_1 = [];
with open(path_X) as csvfile:
    reader = csv.reader(csvfile, quoting=csv.QUOTE_NONNUMERIC) # change contents to floats
    m = 0;
    for row in reader: # each row is a list
        read_1.append(row)
        m+=1;

X_rawest = np.asarray(read_1)
mu = np.mean(X_rawest);
sigma = np.std(X_rawest);
X_raw = (X_rawest - mu)/sigma;
X_log = np.c_[ np.ones((m,1)), X_raw]

read_2 = [];
with open(path_Y) as csvfile:
    reader = csv.reader(csvfile, quoting=csv.QUOTE_NONNUMERIC) # change contents to floats
    for row in reader: # each row is a list
        read_2.append(row)

Y_log = np.asarray(read_2)

def sigmoid(z):
    a = 1.0/(1+ np.exp(-1.0*z));
    return a;

def compute_cost(X,Y,theta):
    m = Y.shape[0];
    h = sigmoid(np.matmul(X,theta));
    J =  (-1.0/m) * np.sum(Y * np.log(h) + (1-Y)* np.log(1- h));
    return J;

def der_cal(X,Y,theta,alpha):
    f1 = np.zeros(theta.shape);
    for i in range(theta.shape[0]):
        theta_plus = np.copy(theta);
        theta_minus = np.copy(theta);
        theta_plus[i] = theta_plus[i] + alpha;
        theta_minus[i] = theta_minus[i] - alpha;
        J_plus = compute_cost(X,Y,theta_plus);
        J = compute_cost(X,Y,theta);
        f1[i] = (J_plus - J)/(alpha);
    return f1;

def hessian_cal(X,Y,theta, alpha):
    H = np.zeros([theta.shape[0],theta.shape[0]]);
    for i in range (theta.shape[0]):
        theta_plus = np.copy(theta);
        theta_minus = np.copy(theta);
        theta_plus[i] = theta_plus[i] + alpha;
        theta_minus[i] = theta_minus[i] - alpha;
        J_plus = der_cal(X,Y,theta_plus,alpha);
        J = der_cal(X,Y,theta,alpha);
        H[i] = np.reshape((J_plus - J)/(alpha),3);
    return H;

def newton_method( X, Y, theta, alpha, num_iter):
    theta = np.zeros((X_log.shape[1],1));
    for i in range(num_iter):
        f1 = der_cal(X_log, Y_log, theta, alpha);
        H = hessian_cal(X_log, Y_log, theta, alpha);
        delta = np.matmul(np.linalg.pinv(H),f1);
        theta = theta - delta;
        if(np.linalg.norm(delta)<np.power(10.0,-5)):
            break;
    return theta;

alpha = 0.1;
num_iter = 500;
theta = np.zeros((X_log.shape[1],1));

theta_final = newton_method(X_log, Y_log, theta, alpha, num_iter);
print("Theta0 = ",theta_final[0,0]);
print("Theta1 = ",theta_final[1,0]);

input("Press Enter to go to part (b)")

plt.scatter(X_log[:,1], X_log[:,2], c=Y_log.reshape(-1))
ax = plt.gca()
x1 = np.arange(np.min(X_log[:,1]), np.max(X_log[:,1]), 0.01);
y1 = -(theta_final[0] + theta_final[1]*x1)/theta_final[2];
plt.plot(x1, y1, '-', c="red", linewidth=2.0)
plt.title('Logistical Regression');
plt.xlabel('x1');
plt.ylabel('x2');
plt.show();


import numpy as np

def generateMatrix(rank,seed,singular=False):
    np.random.seed(seed)
    while True:
        matrix = np.random.randint(-10,10, size=(rank, rank))
        if (np.linalg.matrix_rank(matrix) != rank) ^ (not singular):
            return matrix

def printInMatrixFormat(Ab,padding=7,truncating=3):
    rank = len(Ab)
    rowFormat = ','.join(["{{:>{}.{}f}}".format(padding,truncating)] * rank) + " || {{:^{}.{}f}}".format(padding,truncating)
    matrixFormat = '\n'.join([rowFormat] * rank)

    flattern = [e for row in Ab for e in row]

    print(matrixFormat.format(*flattern))

def generatePoints(seed,num=100):
    np.random.seed(seed)
    m = np.random.random() * 10 - 5 # -5 ~ 5
    b = np.random.random() * 10 + 5 # 5 ~ 15

    x = np.random.random(size=num) * 10 - 5
    y = x * m + b
    y += np.random.normal(size=num)

    return x.tolist(),y.tolist()


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

# In[20]:

# Import libraries
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
get_ipython().run_line_magic('matplotlib', 'inline')
from statsmodels.tsa.arima_model import ARMA

# In[21]:

# Import modules
# Load in the time series
df = pd.read_excel('eu_data_fix2020.xlsx')
df.head(10)

# In[22]:

df.period

# In[23]:

#if u have another data with a lot colum and want to clean
data = df.dropna()
data.index = pd.to_datetime(data.period)
data = data["EU"]['2018-01-01':'2020-10-01']

# In[26]:

data.describe()

# In[27]:

# Data Exploration

plt.figure(figsize=(16,7))
fig = plt.figure(1)
ax1 = fig.add_subplot(111)

ax1.set_xlabel('Period')
ax1.set_ylabel('Number of people confirmed COVID-19')
ax1.plot(data)

# # Checking Stationarity
# * Method 1 - Rolling Statistics
# * Method 2 - Dickey Fuller Test

# In[28]:

# METHOD 1
#Determining rolling statistics
# WINDOW = 12 -- number of month, if days use 365
rolmean = data.rolling(window=12).mean()
rolstd = data.rolling(window=12).std()

plt.figure(figsize=(16,7))
fig = plt.figure(1)

# In[29]:

#plot rolling statistics :

orig = plt.plot(data,color='blue',label='Original') #originaldata
mean = plt.plot(rolmean,color='red', label='Rolling Mean')
std = plt.plot(rolstd, color='green',label='Rolling Std')
plt.legend(loc='best')
plt.title('Rolling Mean & Standar Deviation')
plt.show(block=False)

# We can see from data above
# * Rolling standar deviation (green) more or less constant over the time
# but rolling mean (red) is not constant
# so we can say this data series in not stationary

# In[34]:

# METHOD 2
# Perform Dickey-Fuller test
from statsmodels.tsa.stattools import adfuller

print('Results of Dickey-Fuller Test :')
dftest = adfuller(df["EU"], autolag='AIC') #AIC Akaike Info Criteria

dfoutput = pd.Series(dftest[0:4], index=['Test statistics','p-value','#Lags Used','Number of Observation Used'])
for key,value in dftest[4].items():
    dfoutput['Critical Value(%s)'%key] = value

print(dfoutput)

# ### 0 hipotesis = nonstasionary,
# to make it stasionary it should pass these criterias
# * p value < 0,05
# * critical value should > Test statistic
#
# We can see from data above, we have hight p-value 0.126642 , it's higer than 0.05
# Critical value < Test statistic
#    ###### so we can not reject null hypothesis - the data is non statsionary

# # MAKING Series Stationary

# In[36]:

#Let's transform this LOG
plt.figure(figsize=(16,7))
fig = plt.figure(1)

import numpy as np
ts_log = np.log(data)
plt.plot(ts_log)

# In[37]:

#DECOMPOSITION -- this decomposition will give us difference component of our time series
from statsmodels.tsa.seasonal import seasonal_decompose
decomposition = seasonal_decompose(ts_log,freq=1,model='multiplicative')

trend = decomposition.trend
seasonal = decomposition.seasonal
residual = decomposition.resid

plt.figure(figsize=(16,8))
fig = plt.figure(1)

plt.subplot(411)
plt.plot(ts_log,label ='Original')
plt.legend(loc='best')
plt.subplot(412)
plt.plot(trend,label='Trend')
plt.legend(loc='best')
plt.subplot(413)
plt.plot(seasonal,label ='Seasonality')
plt.legend(loc='best')
plt.subplot(414)
plt.plot(residual,label ='Residuals')
plt.legend(loc='best')
plt.tight_layout()

# #### From the data above we can see even after log transformation it's still non-stationary

# In[38]:

#Let's Try differencing --- we will SHIFT timeseries by 1 and substact timeseries by original series

plt.figure(figsize=(16,8))
fig = plt.figure(1)

ts_log_diff = ts_log - ts_log.shift()
plt.plot(ts_log_diff)

# METHOD 1
#Determining rolling statistics using DIFFERENCE
# WINDOW = 12 -- number of month, if days use 365
rolmean = ts_log_diff.rolling(window=12).mean()
rolstd = ts_log_diff.rolling(window=12).std()

#plot rolling statistics DIFFERENCE :
orig = plt.plot(ts_log_diff,color='blue',label='Original') #originaldata
mean = plt.plot(rolmean,color='red', label='Rolling Mean')
std = plt.plot(rolstd, color='green',label='Rolling Std')
plt.legend(loc='best')
plt.title('Rolling Mean & Standar Deviation')
plt.show(block=False)

# #### We can see from data above
# * Blue - our difference to timeseries
# * Rolling standar deviation (green) & rolling mean (red) are now no upward pattern in the mean also in standar deviation.
# so by the definition of stationarity , if i take mean by these 2 point , there will not be too much difference between these . So I can say/assume now this time series will be stationary time series

# # ACF AND PACF PLOT

# In[39]:

data.sort_index(inplace=True)

# In[40]:

from statsmodels.tsa.stattools import acf, pacf
lag_acf = acf(ts_log_diff, nlags=10)
lag_pacf = pacf(ts_log_diff, nlags=10)

# In[41]:

import statsmodels.api as sm
fig = plt.figure(figsize=(12,8))
ax1 = fig.add_subplot(211)
fig = sm.graphics.tsa.plot_acf(ts_log_diff.dropna(),lags=15,ax=ax1) #nlags should be < 50% of sample size our observarion 34 so should be < 16

ax2 = fig.add_subplot(212)
fig = sm.graphics.tsa.plot_pacf(ts_log_diff.dropna(),lags=15,ax=ax2)

# * the thumb rule is area with highligthed called confidence intervals
# * The first line crosses this chart is the line of our order
# * AR-auto regression ( Auto correlation ) , p = 1 #Inside highlegthed conv interval
# * MA-moving average (Parcial Autocorellation ), q = 1
# * d = 1
#
# because we don'nt know the combination we just try

# # MAKING ARIMA MODEL

# In[42]:

# Import the ARMA model
from statsmodels.tsa.arima_model import ARIMA

plt.figure(figsize=(16,8))
# Instantiate the model ARMA(p=1,d=0,q=1)
model = ARIMA(ts_log, order=(1,1,0))
results_ARIMA = model.fit(disp=-1)

plt.plot(ts_log_diff)
plt.plot(results_ARIMA.fittedvalues, color='red')
print('Plotting ARIMA model')

# # Taking result to original Scale

# In[43]:

ARIMA_diff_predictions = pd.Series(results_ARIMA.fittedvalues,copy=True)
print(ARIMA_diff_predictions.head())

# In[44]:

ARIMA_diff_predictions_cumsum = ARIMA_diff_predictions.cumsum()
print(ARIMA_diff_predictions_cumsum.head())

# In[45]:

ARIMA_log_prediction = pd.Series(ts_log.iloc[0],index=ts_log.index)
ARIMA_log_prediction = ARIMA_log_prediction.add(ARIMA_diff_predictions_cumsum,fill_value=0)
ARIMA_log_prediction.head()

# In[46]:

plt.figure(figsize=(12,8))
predictions_ARIMA = np.exp(ARIMA_log_prediction)
plt.plot(data)
plt.plot(predictions_ARIMA)
plt.title('RMSE : %.4f'% np.sqrt(sum((predictions_ARIMA-data)**2)/len(data)))

# * Blue is original
# * Orange is prediction

# In[47]:

results_ARIMA.predict(10,20)

# In[49]:

get_ipython().system('pip install pmdarima')

# In[50]:

import pmdarima as pm
def arimamodel(timeseries):
    automodel = pm.auto_arima(timeseries,
                             start_p=3,
                             start_q=3,
                             max_p=5,
                             max_q=5,
                             test="adf",
                             seasonal=True,
                             trace=True)
    return automodel

# In[51]:

arimamodel(ts_log)

# ### So the best combination showing from arima is 2,2,2

# In[66]:

# Import the ARMA model
from statsmodels.tsa.arima_model import ARIMA

plt.figure(figsize=(16,8))
# Instantiate the model ARMA(p=2,d=2,q=0)
model = ARIMA(ts_log, order=(2,2,0))
results_ARIMA = model.fit(disp=-1)

plt.plot(ts_log_diff)
plt.plot(results_ARIMA.fittedvalues, color='red')
plt.title('RSS: %.4f'% sum((results_ARIMA.fittedvalues - ts_log_diff["EU"])**2))
print('Plotting ARIMA model')

# After trying combination we get the combination 2,2,1 is best result

# In[67]:

print(results_ARIMA.summary())

# In[68]:

predictions_ARIMA_diff = pd.Series(results_ARIMA.fittedvalues,copy=True)
print(predictions_ARIMA_diff.head())

# In[72]:

#CONVERT TO CUMULATIVE SUM
predictions_ARIMA_diff_cumsum = predictions_ARIMA_diff.cumsum()
print(predictions_ARIMA_diff_cumsum.head)

# In[79]:

predictions_ARIMA_log = pd.Series(ts_log[1],index=ts_log.index)
predictions_ARIMA_log = predictions_ARIMA_log.add(predictions_ARIMA_diff_cumsum,fill_value=0)
predictions_ARIMA_log.head()

# In[81]:

plt.figure(figsize=(12,8))
predictions_ARIMA= np.exp(predictions_ARIMA_log)
plt.plot(data)
plt.plot(predictions_ARIMA)

# In[82]:

ts_log

# In[88]:

# Import libraries
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
get_ipython().run_line_magic('matplotlib', 'inline')
from statsmodels.tsa.arima_model import ARMA

# In[104]:

plt.figure(figsize=(25,20))
fig = plt.figure(1)
#result ARIMA ( I want to predict 3 year , 34 + 36 = 70 )
results_ARIMA.plot_predict(10,70)
x=results_ARIMA.forecast(steps=36)

# In[ ]:

# In[ ]:

import numpy,import statsmodels,from statsmodels


#!/usr/bin/env/ python
# -*- coding: utf8 -*-

# 0 - import os
#     os.system('echo "0 5\n5 1\n12 1\n17 5\n22 5\n22 10\n29 10\n29 5\n34 5\n" > punti.dat')

# 1 - Leggere la serie di punti dal file appena creato con numpy

from __future__ import division # no more zero integer divisions
import os
import sys
import numpy as np
import matplotlib.pyplot as plt

print "Writing points..."

#os.system('echo "0 5\n5 0\n12 0\n17 5\n22 5\n23 7\n27 7\n28 5\n34 5\n" > punti.dat')
os.system('echo "0 5\n5 0\n20 0\n25 5\n40 5\n41 7\n44 7\n45 5\n50 5\n" > punti.dat')

print "Reading points from file..."
points_a = np.genfromtxt("punti.dat")
print "Read \n", points_a

# 2 - Passare da array a lista

print "Converting point array to list..."
points_l = []
for i in points_a:
	points_l.append([i[0], i[1]])

print "Points list \n", points_l

# 3 - Creare una funzione che interpola i punti in modo che tra due punti ce ne siano altri n
# inserendo i punti in mezzo con la sintassi delle liste; interpolare aggiungendo 3 punti
# ad ogni intervallo

def get_y(p1, p2, x):
	"""This function return the y value given a x value and two points
	to compute a straight line
	"""
	m = (p2[1]-p1[1])/(p2[0]-p1[0])
	q = -m*p2[0] + p2[1]
	y = m*x + q
	return y

def interp(l, n):
	"""Interpolate two points in a straight line inserting n new points
	"""
	temp = l
	for i in range(len(temp)-1):
		p1 = temp[i]
		p2 = temp[i+1]
		x = np.linspace(p1[0],p2[0], n+2)[1:-1]
		nl = []
		for j in range(len(x)):
			new_y = get_y(p1, p2, x[j])
			nl.append([x[j], new_y])
			#print "new sub list ", nl
		l = l[:i+i*n+1]+nl+l[i+i*n+1:]
	return l

new_points_l = interp(points_l, 3)

print "New points list \n", new_points_l

# 5 - Riconvertire da liste a vettore

new_points_a = np.array(new_points_l)

print "Interpolated list size ", new_points_a.shape[0]

# 6 - Trovare il minimo e il massimo dei punti in x e y con numpy e a mano con
# dei cicli (creando una funzione apposta) e confrontare il risultato

numpy_min_x = new_points_a[:,0].min()
numpy_min_y = new_points_a[:,1].min()
numpy_max_x = new_points_a[:,0].max()
numpy_max_y = new_points_a[:,1].max()

def find_min_max(l):
	l_min = 0
	l_max = 0
	for i in l:
		if i < l_min: l_min = i
		if i > l_max: l_max = i
	return l_min, l_max

hm_min_x, hm_max_x = find_min_max(new_points_a[:,0])
hm_min_y, hm_max_y = find_min_max(new_points_a[:,1])

print "numpy values ", numpy_min_x, numpy_max_x, numpy_min_y, numpy_max_y
print "handmade values ", hm_min_x, hm_max_x, hm_min_y, hm_max_y

# 7 - simmetrizzare le x dei punti rispetto a quello di indice 3 (= il quarto),
# equivalente a tracciare la retta x = coord_x[3] e riflettere tutti i punti
# con ascissa maggiore rispetto a tale retta

pivot_idx = int(new_points_a.shape[0]/2-4)
pivot = new_points_a[pivot_idx][0]

for i in range(new_points_a.shape[0]):
	if new_points_a[i][0] > pivot:
		new_points_a[i][0] = pivot - (new_points_a[i][0] - pivot)

# 7 - fare il plot dei punti e unirli con segmenti di colore nero e della retta passante per
# [0, 3] [35, 3] di colore blu e salvarlo con
# plt.savefig("plot.png")

plt.plot(new_points_a[:,0], new_points_a[:,1], linestyle = "-", marker = ".", markersize = 2, color = "black")
plt.plot([0, 35], [3, 3], linestyle = "-", marker = "", color = "blue")

plt.savefig("plot.png")

# 8 - Calcolare con bisezione e newmann dove la funzione data interseca y = 3 per x < 7
# e valutare chi ci mette meno con la funzione scritta prima


import numpy as np
from scipy import stats

class GaussianMixtureModel():
    """Density estimation with Gaussian Mixture Models (GMM).

    You can add new functions if you find it useful, but **do not** change
    the names or argument lists of the functions provided.
    """
    def __init__(self, X, K):
        """Initialise GMM class.

        Arguments:
          X -- data, N x D array
          K -- number of mixture components, int
        """
        self.X = X
        self.n = X.shape[0]
        self.D = X.shape[1]
        self.K = K

    def E_step(self, mu, S, pi):
        """Compute the E step of the EM algorithm.

        Arguments:
          mu -- component means, K x D array
          S -- component covariances, K x D x D array
          pi -- component weights, K x 1 array

        Returns:
          r_new -- updated component responsabilities, N x K array
        """
        # Assert that all arguments have the right shape
        assert(mu.shape == (self.K, self.D) and\
               S.shape  == (self.K, self.D, self.D) and\
               pi.shape == (self.K, 1))
        r_new = np.zeros((self.n, self.K))

        # Task 1: implement the E step and return updated responsabilities
        # Write your code from here...

        # ... to here.
        assert(r_new.shape == (self.n, self.K))
        return r_new

    def M_step(self, mu, r):
        """Compute the M step of the EM algorithm.

        Arguments:
          mu -- previous component means, K x D array
          r -- previous component responsabilities,  N x K array

        Returns:
          mu_new -- updated component means, K x D array
          S_new -- updated component covariances, K x D x D array
          pi_new -- updated component weights, K x 1 array
        """
        assert(mu.shape == (self.K, self.D) and\
               r.shape  == (self.n, self.K))
        mu_new = np.zeros((self.K, self.D))
        S_new  = np.zeros((self.K, self.D, self.D))
        pi_new = np.zeros((self.K, 1))

        # Task 2: implement the M step and return updated mixture parameters
        # Write your code from here...

        # ... to here.
        assert(mu_new.shape == (self.K, self.D) and\
               S_new.shape  == (self.K, self.D, self.D) and\
               pi_new.shape == (self.K, 1))
        return mu_new, S_new, pi_new

    def train(self, initial_params):
        """Fit a Gaussian Mixture Model (GMM) to the data in matrix X.

        Arguments:
          initial_params -- dictionary with fields 'mu', 'S', 'pi' and 'K'

        Returns:
          mu -- component means, K x D array
          S -- component covariances, K x D x D array
          pi -- component weights, K x 1 array
          r -- component responsabilities, N x K array
        """
        # Assert that initial_params has all the necessary fields
        assert(all([k in initial_params for k in ['mu', 'S', 'pi']]))

        mu = np.zeros((self.K, self.D))
        S  = np.zeros((self.K, self.D, self.D))
        pi = np.zeros((self.K, 1))
        r  = np.zeros((self.n, self.K))

        # Task 3: implement the EM loop to train the GMM
        # Write your code from here...

        # ... to here.
        assert(mu.shape == (self.K, self.D) and\
               S.shape  == (self.K, self.D, self.D) and\
               pi.shape == (self.K, 1) and\
               r.shape  == (self.n, self.K))
        return mu, S, pi, r

if __name__ == '__main__':
    np.random.seed(43)

    ##########################
    # You can put your tests here - marking
    # will be based on importing this code and calling
    # specific functions with custom input.
    # Do not write code outside the class definition or
    # this if-block.
    ##########################


import numpy as np
from exercises.nnCostFunction import getCost
from exercises.sigmoid import sigmoidGradient
from exercises.randomInitialize import rand

input_layer_size  = 400  # 20x20 Input Images of Digits
hidden_layer_size = 25   # 25 hidden units
num_labels = 10          # 10 labels, from 1 to 10
                          # (note that we have mapped "0" to label 10)
# =========== Part 1: Loading data ===============

X_org = np.genfromtxt('../../ex3/X_input.csv', delimiter=',')
y_org = np.genfromtxt('../../ex3/y_output.csv', delimiter=',')

y_org = y_org.reshape((len(y_org), 1))

m, n = X_org.shape

print('X_org shape', X_org.shape)
print('y_org shape', y_org.shape)

# ================ Part 2: Loading Parameters ================

Theta1 = np.genfromtxt('../data/Theta1.csv', delimiter=',')
print('Loaded Theta1 shape', Theta1.shape)

Theta2 = np.genfromtxt('../data/Theta2.csv', delimiter=',')
print('Loaded Theta2 shape', Theta2.shape)

# unrolling parameters
# First transpose, then normal flatten
tmpTheta1 = Theta1.flatten()
tmpTheta1 = tmpTheta1.reshape((len(tmpTheta1), 1))
print('Unrolling Theta1 shape', tmpTheta1.shape)

tmpTheta2 = Theta2.flatten()
tmpTheta2 = tmpTheta2.reshape((len(tmpTheta2), 1))
print('Unrolling Theta2 shape', tmpTheta2.shape)

nn_params = np.concatenate((tmpTheta1, tmpTheta2), axis=0)

# ================ Part 3: Compute Cost (Feedforward) ================
'''
lambd = 0
theta1 = np.arange(1,7).reshape((2, 3))
print('Original t1', theta1)
theta2 = np.arange(1,13).reshape((4, 3))
print('original t2', theta2)
tmpTheta1 = theta1.flatten()
tmpTheta1 = tmpTheta1.reshape((len(tmpTheta1), 1))

tmpTheta2 = theta2.flatten()
tmpTheta2 = tmpTheta2.reshape((len(tmpTheta2), 1))
nn_params = np.concatenate((tmpTheta1, tmpTheta2), axis=0)
cost = getCost(nn_params, 2, 2, 4, 1, 1, lambd)
'''
lambd = 0 # With out regularization
#print('nn_params 656', nn_params[655], '')
cost = getCost(np.copy(nn_params), input_layer_size, hidden_layer_size, num_labels, X_org, y_org, lambd)
print('Cost at parameters (loaded from ex4weights):', cost, ' value should be 0.287629')

# Implementing Regularization
print('\nChecking Cost Function (w/ Regularization) ... \n')

lambd = 1 # with regularization of lambda = 1

cost = getCost(np.copy(nn_params), input_layer_size, hidden_layer_size, num_labels, X_org, y_org, lambd)

# After regularization cost
print('Cost at parameters (loaded from ex4weights):', cost, '\n(this value should be about 0.383770)\n')

# Part 5 Evaluating Sigmoid Gradient

tmpArray = np.array([-1, -0.5, 0, 0.5, 1])
sigmGResults = sigmoidGradient(tmpArray)

print('Sigmoid Gradient Results', sigmGResults)

# Part 6 Random Initialization
initial_Theta1 = rand(input_layer_size, hidden_layer_size)
initial_Theta2 = rand(hidden_layer_size, num_labels)


import numpy as np

def relative_angle_distance_aux(road_points1, road_points2):
    """ Relative angle distance, similar to the approach in
    Rotation Invariant Distance Measures for Trajectories (http://alumni.cs.ucr.edu/~mvlachos/pubs/kdd04.pdf)
    """

    k = np.min([road_points1.shape[0], road_points2.shape[0]])
    dxs1 = road_points1[1:k, 0] - road_points1[0:k-1, 0]
    dys1 = road_points1[1:k, 1] - road_points1[0:k-1, 1]

    dxs2 = road_points2[1:k, 0] - road_points2[0:k-1, 0]
    dys2 = road_points2[1:k, 1] - road_points2[0:k-1, 1]

    delta_thetas1 = np.arctan2(dys1, dxs1)
    delta_thetas2 = np.arctan2(dys2, dxs2)

    delta_ss1 = np.sqrt(dxs1 * dxs1 + dys1 * dys1)
    delta_ss2 = np.sqrt(dxs2 * dxs2 + dys2 * dys2)

    ss1 = np.zeros(delta_ss1.shape[0] + 1)
    ss2 = np.zeros(delta_ss2.shape[0] + 1)
    ss1[1:] = np.cumsum(delta_ss1)
    ss2[1:] = np.cumsum(delta_ss2)

    # we want to take integral of |\Delta\Theta_1(s) - \Delta\Theta_2(s)|

    # print(ss1)
    # print(delta_thetas1)
    # print("")
    # print(ss2)
    # print(delta_thetas2)
    # print("")

    all_ss = [0.0]
    diff_delta_thetas = []

    index1 = 0
    index2 = 0
    while True:
        # print(all_ss[-1])
        # print(delta_thetas1[index1])
        # print(delta_thetas2[index2])
        # print("")
        if index1 < delta_thetas1.shape[0] and index2 < delta_thetas2.shape[0]:
            diff_delta_thetas.append(np.abs(delta_thetas1[index1] - delta_thetas2[index2]))
        else:
            break

        if ss1[index1 + 1] < ss2[index2 + 1]:
            index1 += 1
            all_ss.append(ss1[index1])
        else:
            index2 += 1
            all_ss.append(ss2[index2])

    integral = 0.0
    total_s = all_ss[-1]
    for i in range(len(all_ss) - 1):
        integral += diff_delta_thetas[i] * (all_ss[i + 1] - all_ss[i])

    return (integral, total_s)

def relative_angle_distance(road_points1, road_points2):
    (integral, total_s) = relative_angle_distance_aux(road_points1, road_points2)
    return integral

def normalized_relative_angle_distance(road_points1, road_points2):
    (integral, total_s) = relative_angle_distance_aux(road_points1, road_points2)
    return integral / total_s

# relative_angle_distance_aux(np.array([[0, 0], [2, 7], [2.5, 9], [3, 15], [10, 15]]),
#                             np.array([[0, 3], [2, 5], [1, 10], [-1, 9], [-2, 6], [-4.0, 2]]))
#
# print(relative_angle_distance(np.array([[0, 0], [2, 7], [2.5, 9], [3, 15], [10, 15]]),
#                               np.array([[0, 3], [2, 5], [1, 10], [-1, 9], [-2, 6], [-4.0, 2]])))
#
# print(normalized_relative_angle_distance(np.array([[0, 0], [2, 7], [2.5, 9], [3, 15], [10, 15]]),
#                                          np.array([[0, 3], [2, 5], [1, 10], [-1, 9], [-2, 6], [-4.0, 2]])))


import numpy as np
from scipy.integrate import solve_ivp
import matplotlib.pyplot as plt

def Lorenz63(t, y, sig, rho, beta):
# Lorenz '63 model
    out = np.zeros_like(y)
    out[0] = sig * ( y[1] - y[0] )
    out[1] = y[0] * ( rho - y[2] ) - y[1]
    out[2] = y[0] * y[1] - beta * y[2]
    return out

def forwardModel_r( xt0, time, rayleigh, prandtl, b):
#   perform integration of Lorentz63 model
#   default integrator for solve_ivp is RK4

    rho  = rayleigh
    beta = b
    sig  = prandtl

    myParams = np.array( [sig, rho, beta], dtype=float )
    tstart = time[0]
    tend = time[-1]
    y0 = np.array( xt0, dtype=float )
    sol = solve_ivp( Lorenz63, [tstart,tend], y0, args=myParams, dense_output=True)
    xt = sol.sol(time)

    return xt


import numpy as np

num_points = 1000
vectors_set = []

for i in xrange(num_points):
    x1 = np.random.normal(0.0, 0.55)
    y1 = x1 * 0.1 + 0.3 + np.random.normal(0.0, 0.03)
    vectors_set.append([x1, y1])

x_data = [v[0] for v in vectors_set]
y_data = [v[1] for v in vectors_set]

import matplotlib.pyplot as plt

# Graphic Display
plt.plot(x_data, y_data, 'ro')
plt.legend()
plt.show()

import tensorflow as tf

W = tf.Variable(tf.random_uniform([1], -1.0, 1.0))
b = tf.Variable(tf.zeros([1]))
y = W * x_data + b

loss = tf.reduce_mean(tf.square(y - y_data))
optimizer = tf.train.GradientDescentOptimizer(0.5)
train = optimizer.minimize(loss)

init = tf.initialize_all_variables()

sess = tf.Session()
sess.run(init)

for step in xrange(15):
    sess.run(train)
    print(step, sess.run(W), sess.run(b))
    print(step, sess.run(loss))

    # Graphic Display
    plt.plot(x_data, y_data, 'ro')
    plt.plot(x_data, sess.run(W) * x_data + sess.run(b))
    plt.xlabel('x')
    plt.xlim(-2, 2)
    plt.ylim(0.1, 0.6)
    plt.ylabel('y')
    plt.legend()
    plt.show()

02-First Contact With Tensorflow/01-Linear Regression/Linear Regression.py


import numpy as np
import os

def laplacian(Z, dx):
    """
    Calculates laplace operator for particular matrix
    :param Z: Combination of two initial matrices
    :param dx: float space step
    :return: Boundaries of matrix
    """
    Ztop = Z[0:-2, 1:-1]
    Zleft = Z[1:-1, 0:-2]
    Zbottom = Z[2:, 1:-1]
    Zright = Z[1:-1, 2:]
    Zcenter = Z[1:-1, 1:-1]
    return (Ztop + Zleft + Zbottom + Zright -
            4 * Zcenter) / dx**2

def turing(a, b, time):
    """
    calculates the turing patterns over time
    :param a: concentration of a
    :param b: concentration of b
    :param time: time that we want result for
    :return: turing pattern for particular time
    """
    size = 100
    dx = 2. / size  # space step
    tau = 2.00
    k = 0

    T = 6.0  # total time
    dt = .001  # time step
    n = int(T / dt)  # number of iterations
    step_plot = n // 6

    U = np.load(os.path.join('initialmatrices', 'U_initial.npy'))
    V = np.load(os.path.join('initialmatrices', 'V_initial.npy'))

    for i in range(n):

        # We compute the Laplacian of u and v.
        deltaU = laplacian(U, dx)
        deltaV = laplacian(V, dx)
        # We take the values of u and v inside the grid.
        Uc = U[1:-1, 1:-1]
        Vc = V[1:-1, 1:-1]
        # We update the variables.
        U[1:-1, 1:-1], V[1:-1, 1:-1] = \
            Uc + dt * (a * deltaU + Uc - Uc ** 3 - Vc + k), \
            Vc + dt * (b * deltaV + Uc - Vc) / tau

        # Neumann conditions: derivatives at the edges
        # are null.
        for Z in (U, V):
            Z[0, :] = Z[1, :]
            Z[-1, :] = Z[-2, :]
            Z[:, 0] = Z[:, 1]
            Z[:, -1] = Z[:, -2]

        if i * dt == time:
            return U


import numpy as np
import tensorflow as tf

def orthogonal(shape, scale=1., name=None, rng=None, type='float32'):
    if rng is None:
        rng = np.random

    rndn = rng.normal(0., 1., shape).astype(type)
    u, _, v = np.linalg.svd(rndn, full_matrices=False)
    if u.shape == shape:
        return scale * u
    else:
        return scale * v


import scipy as sp

def coeffExpand(M):
    if M.shape[0] != M.shape[1]:
        raise ValueError("Matrix must be square")

    M = sp.atleast_2d(M)
    if M.shape == (1,1):
        return M.item(0,0)
    else:
        det = sum(M[0,i]*(((-1)**i)*coeffExpand(sp.delete(M[1:],i,1))) for i in range(len(M)))
        return det