import numpy as np

def norm(x, axis=None):
    return np.linalg.norm(x, axis=axis)

#-------------------------
#----- Poincaré Disk -----
#-------------------------

# NOTE: POSSIBLE ISSUE WITH DIFFERENT WAYS TO SPECIFY MINKOWSKI DOT PRODUCT
# arbritray sign gives different signatures (+, +, +, -), (+, -, -, -)

# distance in poincare disk
def poincare_dist(u, v, eps=1e-5):
    d = 1 + 2 * norm(u-v)**2 / ((1 - norm(u)**2) * (1 - norm(v)**2) + eps)
    return np.arccosh(d)

# compute symmetric poincare distance matrix
def poincare_distances(embedding):
    n = embedding.shape[0]
    dist_matrix = np.zeros((n, n))
    for i in range(n):
        for j in range(i+1, n):
            dist_matrix[i][j] = poincare_dist(embedding[i], embedding[j])
    return dist_matrix

# convert array from poincare disk to hyperboloid
def poincare_pts_to_hyperboloid(Y, eps=1e-6, metric='lorentz'):
    mink_pts = np.zeros((Y.shape[0], Y.shape[1]+1))
    # print('Minkowski pts shape: {}'.format(mink_pts.shape))
    r = norm(Y, axis=1)
    if metric == 'minkowski':
        mink_pts[:, 0] = 2/(1 - r**2 + eps) * (1 + r**2)/2
        for i in range(1, mink_pts.shape[1]):
            mink_pts[:, i] = 2/(1 - r**2 + eps) * Y[:, i - 1]
    else:
        mink_pts[:, Y.shape[1]] = 2/(1 - r**2 + eps) * (1 + r**2)/2
        for i in range(0, Y.shape[1]):
            mink_pts[:, i] = 2/(1 - r**2 + eps) * Y[:, i]
    """
    if metric == 'minkowski':
        mink_pts[:, 0] = 2/(1 - r**2 + eps) * (1 + r**2)/2
        mink_pts[:, 1] = 2/(1 - r**2 + eps) * Y[:, 0]
        mink_pts[:, 2] = 2/(1 - r**2 + eps) * Y[:, 1]
    else:
        mink_pts[:, 0] = 2/(1 - r**2 + eps) * Y[:, 0]
        mink_pts[:, 1] = 2/(1 - r**2 + eps) * Y[:, 1]
        mink_pts[:, 2] = 2/(1 - r**2 + eps) * (1 + r**2)/2
    """
    return mink_pts

# convert single point to hyperboloid
def poincare_pt_to_hyperboloid(y, eps=1e-6, metric='lorentz'):
    mink_pt = np.zeros((y.shape[0] + 1, ))
    # print('mink_pt.shape: {}'.format(mink_pt.shape))
    r = norm(y)

    if metric == 'minkowski':
        mink_pt[0] = 2/(1 - r**2 + eps) * (1 + r**2)/2
        for i in range(1, mink_pt.shape[0]):
            mink_pt[i] = 2/(1 - r**2 + eps) * y[i - 1]
    else:
        mink_pt[y.shape[0]] = 2/(1 - r**2 + eps) * (1 + r**2)/2
        for i in range(0, y.shape[0]):
            mink_pt[i] = 2/(1 - r**2 + eps) * y[i]
    """
    mink_pt = np.zeros((3, ))
    r = norm(y)
    if metric == 'minkowski':
        mink_pt[0] = 2/(1 - r**2 + eps) * (1 + r**2)/2
        mink_pt[1] = 2/(1 - r**2 + eps) * y[0]
        mink_pt[2] = 2/(1 - r**2 + eps) * y[1]
    else:
        mink_pt[0] = 2/(1 - r**2 + eps) * y[0]
        mink_pt[1] = 2/(1 - r**2 + eps) * y[1]
        mink_pt[2] = 2/(1 - r**2 + eps) * (1 + r**2)/2
    """
    return mink_pt

#------------------------------
#----- Hyperboloid Model ------
#------------------------------

# NOTE: POSSIBLE ISSUE WITH DIFFERENT WAYS TO SPECIFY MINKOWSKI DOT PRODUCT
# arbritray sign gives different signatures (+, +, +, -), (+, -, -, -)

# define hyperboloid bilinear form
def hyperboloid_dot(u, v):
    # print('U dim: {}'.format(u.shape))
    # print('V dim: {}'.format(v.shape))
    return np.dot(u[:-1], v[:-1]) - u[-1]*v[-1]

# define alternate minkowski/hyperboloid bilinear form
def minkowski_dot(u, v):
    return u[0]*v[0] - np.dot(u[1:], v[1:])

# hyperboloid distance function
def hyperboloid_dist(u, v, eps=1e-6, metric='lorentz'):
    if metric == 'minkowski':
        dist = np.arccosh(-1*minkowski_dot(u, v))
    else:
        dist = np.arccosh(-1*hyperboloid_dot(u, v))
    if np.isnan(dist):
        #print('Hyperboloid dist returned nan value')
        return eps
    else:
        return dist

# compute symmetric hyperboloid distance matrix
def hyperboloid_distances(embedding):
    n = embedding.shape[0]
    dist_matrix = np.zeros((n, n))
    for i in range(n):
        for j in range(i+1, n):
            dist_matrix[i][j] = hyperboloid_dist(embedding[i], embedding[j])
    return dist_matrix

# convert array to poincare disk
def hyperboloid_pts_to_poincare(X, eps=1e-6, metric='lorentz'):
    poincare_pts = np.zeros((X.shape[0], X.shape[1]-1))
    if metric == 'minkowski':
        for i in range(0, X.shape[1]):
            poincare_pts[:, i] = X[:, i + 1] / ((X[:, 0] + 1) + eps)
    else:
        for i in range(0, X.shape[1]):
            poincare_pts[:, i] = X[:, i] / ((X[:, -1] + 1) + eps)
    """
    if metric == 'minkowski':
        poincare_pts[:, 0] = X[:, 1] / ((X[:, 0]+1) + eps)
        poincare_pts[:, 1] = X[:, 2] / ((X[:, 0]+1) + eps)
    else:
        poincare_pts[:, 0] = X[:, 0] / ((X[:, 2]+1) + eps)
        poincare_pts[:, 1] = X[:, 1] / ((X[:, 2]+1) + eps)
    """
    return poincare_pts

# project within disk
def proj(theta,eps=1e-3):
    if norm(theta) >= 1:
        theta = theta/norm(theta) - eps
    return theta

# convert single point to poincare
def hyperboloid_pt_to_poincare(x, eps=1e-6, metric='lorentz'):
    poincare_pt = np.zeros((x.shape[0] - 1, ))
    if metric == 'minkowski':
        for i in range(0, poincare_pt.shape[0]):
            poincare_pt[i] = x[i + 1] / ((x[0] + 1) + eps)
    else:
        for i in range(0, poincare_pt.shape[0]):
            poincare_pt[i] = x[i] / ((x[-1] + 1) + eps)
    """
    poincare_pt = np.zeros((2, ))
    if metric == 'minkowski':
        poincare_pt[0] = x[1] / ((x[0]+1) + eps)
        poincare_pt[1] = x[2] / ((x[0]+1) + eps)
    else:
        poincare_pt[0] = x[0] / ((x[2]+1) + eps)
        poincare_pt[1] = x[1] / ((x[2]+1) + eps)
    """
    return proj(poincare_pt)

# helper function to generate samples
def generate_data(n, radius=0.7, hyperboloid=False):
    theta = np.random.uniform(0, 2*np.pi, n)
    u = np.random.uniform(0, radius, n)
    r = np.sqrt(u)
    x = r * np.cos(theta)
    y = r * np.sin(theta)
    init_data = np.hstack((x.reshape(-1,1), y.reshape(-1,1)))
    if hyperboloid:
        return poincare_pts_to_hyperboloid(init_data)
    else:
        return init_data


from typing import Union, cast

import numpy as np

def angle_between_vectors(v1: np.ndarray, v2: np.ndarray) -> float:
    """angle_between_vectors returns the angle in radians between two vectors.

    Args:
        v1 (np.ndarray): Vector 1 of shape (N)
        v2 (np.ndarray): Vector 2 of same shape as ``v1``

    Returns:
        float: angle in radians
    """
    cos_ang = np.dot(v1, v2)
    sin_ang = np.linalg.norm(np.cross(v1, v2))
    return cast(float, np.arctan2(sin_ang, cos_ang))

def compute_yaw_around_north_from_direction(direction_vector: np.ndarray) -> float:
    """compute_yaw_from_direction computes the yaw as angle between a 2D input direction vector and
the y-axis direction vector (0, 1).

    Args:
        direction_vector (np.ndarray): Vector of shape (2,)

    Returns:
        float: angle to (0,1) vector in radians
    """
    return angle_between_vectors(direction_vector, np.array([0.0, 1.0]))

def angular_distance(angle_a: Union[float, np.ndarray], angle_b: Union[float, np.ndarray]) -> Union[float, np.ndarray]:
    """ A function that takes two arrays of angles in radian and compute the angular distance, wrap the angular
    distance such that they are always in the [-pi, pi) range.

    Args:
        angle_a (np.ndarray, float): first array of angles in radians
        angle_b (np.ndarray, float): second array of angles in radians

    Returns:
        angular distance in radians between two arrays of angles
    """

    return (angle_a - angle_b + np.pi) % (2 * np.pi) - np.pi


# -*- coding: utf-8 -*-
"""
Created on Tue Jul 10 16:35:22 2018

@author: SilverDoe
"""

'''

The scipy.spatial package can compute Triangulations, Voronoi Diagrams and Convex Hulls of
a set of points, by leveraging the Qhull library. Moreover, it contains KDTree implementations
for nearest-neighbor point queries and utilities for distance computations in various metrics.

'''

'''======================= Delaunay Triangulations ============================

In mathematics and computational geometry, a Delaunay triangulation for a given set P of
discrete points in a plane is a triangulation DT(P) such that no point in P is inside the
circumcircle of any triangle in DT(P).

'''
import matplotlib.pyplot as plt
import numpy as np
from scipy.spatial import Delaunay

points = np.array([[0, 4], [2, 1.1], [1, 3], [1, 2]])
tri = Delaunay(points)

tri.points
tri.convex_hull

plt.triplot(points[:,0], points[:,1], tri.simplices.copy())
plt.plot(points[:,0], points[:,1], 'o')
plt.show()

#=================== finding coplanar points =================================
from scipy.spatial import Delaunay
points = np.array([[0, 0], [0, 1], [1, 0], [1, 1], [1, 1]])
tri = Delaunay(points)
print(tri.coplanar)

'''==================== Convex Hulls ==========================================

In mathematics, the convex hull or convex envelope of a set of points X in the Euclidean plane
or in a Euclidean space (or, more generally, in an affine space over the reals) is the smallest
convex set that contains X.

'''
import matplotlib.pyplot as plt
from scipy.spatial import ConvexHull
points = np.random.rand(10, 2) # 30 random points in 2-D
hull = ConvexHull(points)

plt.plot(points[:,0], points[:,1], 'o')
for simplex in hull.simplices:
    plt.plot(points[simplex,0], points[simplex,1], 'k-')
plt.show()

'''

>> The conventional 2D or 3D (or for that matter nD) space that we regularly use is called the Euclidean space,
   also called the flat space.

>> Why is it called so? Because all the classical laws of geometry that we are aware of (Pythagoras theorem,
   properties of polygons, etc.) are only valid (in the way we know them) in flat spaces. What it means is
   that that the line joining any two points in this space is straight.

>> Imagine your computer screen. Draw two points anywhere on it (key : ‘Imagine’). Now the shortest way of
   reaching one point from the other is a straight line, because this space is flat.

>> Now imagine those two points on the surface of a basketball. For simplicity, on diametrically opposite
   ends (on poles, if you prefer it). There are infinite ways to reach one point from the other, but the
   shortest is the semicircle that connects the points; since this ‘space’ is curved. Obviously, the
   Pythagorean theorem wouldn’t work here (since no straight lines connecting three non-linear points).

>> Therefore this concept of flat space as opposed to curved space exists. And the flat space is conventionally
   called the Euclidean space because all of Euclidean geometry was done in flat space.

'''


import time
import numpy as np
from scipy import special
import matplotlib.pyplot as plt
pi = np.pi

def ExactValueOfSphereVolume(Dimension = 3, Radius = 1.0):
    Dim = Dimension
    ExactValue = ( pi**(Dim/2.0) * Radius**Dim ) / special.gamma( Dim/2.0+1.0 )
    return ExactValue
#-----------------------

def getEstimateOfSphereVolume(NumSamples, Dimension = 3, Radius = 1.0, InitialSeed = -1):
    Dim = Dimension
    if InitialSeed >= 0:
        np.random.seed(InitialSeed)
    else:
        np.random.seed()
    BoxVolume = (2.0 * Radius)**Dim
    estimate = []
    inside = 0
    total = 0
    for i in range(0,NumSamples):
        sum = 0.0
        for d in range(Dim):
            x = np.random.uniform(-Radius,Radius)
            sum += x*x
        sum = np.sqrt(sum)
        if(sum <= Radius): inside += 1
        total += 1
        ratio = np.float(inside)/np.float(total)
        value = ratio * BoxVolume
        estimate.append(value)
    estimate = np.array(estimate)
    return np.copy(estimate)
#----------------------------

NumRuns = 20
NumSamples = 1000000
Dimension = 5
ExactValue = ExactValueOfSphereVolume(Dimension = Dimension)

FigOffset = 0.5

y_min = ExactValue - FigOffset
y_max = ExactValue + FigOffset
plt.axis([0, NumSamples, y_min, y_max])

for i in range(NumRuns):
    estimate = getEstimateOfSphereVolume(NumSamples = NumSamples, Dimension = Dimension)
    plt.plot(estimate)
    if (i+1) % 10 == 0: print 'Run {:4d} of {:4d}'.format(i+1,NumRuns)
plt.plot([0, NumSamples], [ExactValue, ExactValue], 'r--')
plt.show()


# -----------------------------------------------------------------------------
# From https://en.wikipedia.org/wiki/Minkowski–Bouligand_dimension:
#
# In fractal geometry, the Minkowski–Bouligand dimension, also known as
# Minkowski dimension or box-counting dimension, is a way of determining the
# fractal dimension of a set S in a Euclidean space Rn, or more generally in a
# metric space (X, d).
# -----------------------------------------------------------------------------
# code taken from https://gist.github.com/rougier/e5eafc276a4e54f516ed5559df4242c0
import numpy as np

def fractal_dimension(b_image):
    """
    Calculates the fractal dimension of the given binary image Z
    :param b_image: a binary 2d image
    :return: the Minkowski–Bouligand dimension of the image
    """

    # Only for 2d image
    assert(len(b_image.shape) == 2)

    # From https://github.com/rougier/numpy-100 (#87)
    def boxcount(Z, k):
        S = np.add.reduceat(
            np.add.reduceat(Z, np.arange(0, Z.shape[0], k), axis=0),
                               np.arange(0, Z.shape[1], k), axis=1)

        # We count non-empty (0) and non-full boxes (k*k)
        return len(np.where((S > 0) & (S < k*k))[0])

    # Minimal dimension of image
    p = min(b_image.shape)

    # Greatest power of 2 less than or equal to p
    n = 2**np.floor(np.log(p)/np.log(2))

    # Extract the exponent
    n = int(np.log(n)/np.log(2))

    # Build successive box sizes (from 2**n down to 2**1)
    sizes = 2**np.arange(n, 1, -1)

    # Actual box counting with decreasing size
    counts = []
    for size in sizes:
        counts.append(boxcount(b_image, size))

    # Fit the successive log(sizes) with log (counts)
    coeffs = np.polyfit(np.log(sizes), np.log(counts), 1)
    return -coeffs[0]

# I = scipy.misc.imread("sierpinski.png")/256.0
# print("Minkowski–Bouligand dimension (computed): ", fractal_dimension(I))
# print("Haussdorf dimension (theoretical):        ", (np.log(3)/np.log(2)))


#!/usr/bin/python

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

matplotlib.rcParams.update({'font.size': 12})

circle1 = plt.Circle((0,0),1, color='magenta', alpha=0.4)

fig,ax= plt.subplots()

ax.add_artist(circle1)

ax.set_xlim(-5.2,5.2)
ax.set_ylim(-5.2,5.2)
ax.set_xlabel('X', fontsize=12)
ax.set_ylabel('Y', fontsize=12)

xp = np.linspace(-10,10,num=1000)

print xp

kval = [-3,-2,-1,0,1,2,4,8.125,11]

yp0 = [(8*(x**2) - kval[0])/2.0 for x in xp]
yp1 = [(8*(x**2) - kval[1])/2.0 for x in xp]
yp2 = [(8*(x**2) - kval[2])/2.0 for x in xp]
yp3 = [(8*(x**2) - kval[3])/2.0 for x in xp]
yp4 = [(8*(x**2) - kval[4])/2.0 for x in xp]
yp5 = [(8*(x**2) - kval[5])/2.0 for x in xp]
yp6 = [(8*(x**2) - kval[6])/2.0 for x in xp]
yp7 = [(8*(x**2) - kval[7])/2.0 for x in xp]
yp8 = [(8*(x**2) - kval[8])/2.0 for x in xp]

#print yp

plt.plot(xp, yp0,color='hotpink',label=r'$f(x,y)=-3.0$')
plt.plot(xp, yp1,color='red',linestyle='-.',label=r'$f(x,y)=-2.0$')
plt.plot(xp, yp2,color='olive',label=r'$f(x,y)=-1.0$')
plt.plot(xp, yp3,color='lime',label=r'$f(x,y)=0.0$')
#plt.plot(xp, yp4,color='aqua',label=r'$f(x,y)=1.0$')
plt.plot(xp, yp5,color='aqua',label=r'$f(x,y)=2.0$')
#plt.plot(xp, yp5,color='deepskyblue',label=r'$f(x,y)=4.0$')
plt.plot(xp, yp7,color='navy',linestyle='--',label=r'$f(x,y)=8.125$')
plt.plot(xp, yp8,color='gray',label=r'$f(x,y)=11.0$')

plt.axhline(y=0, linestyle=':', linewidth=1, color='orange')
plt.axhline(y=1, linestyle=':', linewidth=1, color='orange')
plt.axhline(y=-1, linestyle=':', linewidth=1, color='orange')

plt.axvline(x=0, linestyle=':', linewidth=1, color='orange')
plt.axvline(x=1, linestyle=':', linewidth=1, color='orange')
plt.axvline(x=-1, linestyle=':', linewidth=1, color='orange')

plt.annotate('Minima',color='red',xy=(-0.49, 2.34), xytext=(-3.0,2.24), ha='center', arrowprops=dict(facecolor='red', edgecolor='red',shrink=0.05,  width=1))
plt.annotate('Maxima',color='navy',xy=(0.67, -2.), xytext=(3.0,-2.05), ha='center', arrowprops=dict(facecolor='navy', edgecolor='navy',shrink=0.05,  width=1))

#print  Ypara1

plt.title ('Example of Solving Equation with Constraint; Lagrange Multiplier')
plt.legend(fontsize=12)
plt.show()


from typing import Tuple

import numpy as np

def gamma_logpdf(x: float, k: float, theta: float) -> float:
    """Log-density of the Gamma distribution up to a constant factor.

    Args:
        x: Positive number at which to evaluate the Gamma distribution.
        k: Shape parameter of the Gamma distribution.
        theta: Scale parameter of the Gamma distribution.

    Returns:
        out: The log-density of the Gamma distribution.

    """
    return (k - 1.0)*np.log(x) - x / theta

def grad_gamma_logpdf(x: float, k: float, theta: float) -> float:
    """Gradient of the log-density of the Gamma distribution.

    Args:
        x: Positive number at which to evaluate the Gamma distribution.
        k: Shape parameter of the Gamma distribution.
        theta: Scale parameter of the Gamma distribution.

    Returns:
        out: The gradient of the log-density of the Gamma distribution.

    """
    return (k - 1.0) / x - np.reciprocal(theta)

def hess_gamma_logpdf(x: float, k: float, theta: float) -> float:
    """Hessian of the log-density of the Gamma distribution.

    Args:
        x: Positive number at which to evaluate the Gamma distribution.
        k: Shape parameter of the Gamma distribution.
        theta: Scale parameter of the Gamma distribution.

    Returns:
        out: The Hessian of the log-density of the Gamma distribution.

    """
    return -(k - 1.0) / np.square(x)

def grad_hess_gamma_logpdf(x: float, k: float, theta: float) -> float:
    """Third-order derivatives of the log-density of the Gamma distribution.

    Args:
        x: Positive number at which to evaluate the Gamma distribution.
        k: Shape parameter of the Gamma distribution.
        theta: Scale parameter of the Gamma distribution.

    Returns:
        out: The third-order derivative of the log-density of the Gamma
            distribution.

    """
    return 2.0*(k - 1.0) / np.power(x, 3.0)

def log_prior(sigmasq: float, beta: float) -> float:
    """The log-prior of the log-Gaussian Cox-Poisson model.

    Args:
        sigmasq: Amplitude of the Gaussian process kernel.
        beta: Length scale of the Gaussian process kernel.

    Returns:
        lp: The log-density of the prior distribution.

    """
    lp = gamma_logpdf(beta, 2.0, 0.5)
    lp += gamma_logpdf(sigmasq, 2.0, 0.5)
    return lp

def grad_log_prior(phis: float, phib: float) -> Tuple[float]:
    """Gradient of the log-prior with respect to the reparameterized model
    parameters that are unconstrained.

    Args:
        phis: Reparameterized aplitude of the Gaussian process kernel.
        phib: Reparameterized length scale of the Gaussian process kernel.

    Returns:
        out: The gradient of the log-prior with respect to the reparameterized
            model parameters.

    """
    sigmasq = np.exp(phis)
    beta = np.exp(phib)
    dphis = grad_gamma_logpdf(sigmasq, 2.0, 0.5) * sigmasq
    dphib = grad_gamma_logpdf(beta, 2.0, 0.5) * beta
    return np.array((dphis, dphib))

def hess_log_prior(phis: float, phib: float) -> np.ndarray:
    """Compute the hessian of the log-prior with respect to the reparameterized
    model parameters.

    Args:
        phis: Reparameterized aplitude of the Gaussian process kernel.
        phib: Reparameterized length scale of the Gaussian process kernel.

    Returns:
        H: The Hessian of the log-prior with respect to the reparameterized model
            parameters.

    """
    sigmasq = np.exp(phis)
    beta = np.exp(phib)
    H = np.array([
        [grad_gamma_logpdf(sigmasq, 2.0, 0.5)*sigmasq + np.square(sigmasq)*hess_gamma_logpdf(sigmasq, 2.0, 0.5), 0.0],
        [0.0, grad_gamma_logpdf(beta, 2.0, 0.5)*beta + np.square(beta)*hess_gamma_logpdf(beta, 2.0, 0.5)]
    ])
    return H

def grad_hess_log_prior(phis: float, phib: float) -> np.ndarray:
    """Compute the third-order derivatives of the log-prior with respect to the
    reparameterized model parameters.

    Args:
        phis: Reparameterized aplitude of the Gaussian process kernel.
        phib: Reparameterized length scale of the Gaussian process kernel.

    Returns:
        dH: The third-order derivatives of the log-prior.

    """
    sigmasq = np.exp(phis)
    beta = np.exp(phib)
    dH = np.zeros((2, 2, 2))
    a = sigmasq*grad_gamma_logpdf(sigmasq, 2.0, 0.5)
    a += np.square(sigmasq)*hess_gamma_logpdf(sigmasq, 2.0, 0.5)
    a += 2.0*sigmasq*hess_gamma_logpdf(sigmasq, 2.0, 0.5)
    a += np.square(sigmasq)*grad_hess_gamma_logpdf(sigmasq, 2.0, 0.5)
    b = beta*grad_gamma_logpdf(beta, 2.0, 0.5)
    b += np.square(beta)*hess_gamma_logpdf(beta, 2.0, 0.5)
    b += 2.0*beta*hess_gamma_logpdf(beta, 2.0, 0.5)
    b += np.square(beta)*grad_hess_gamma_logpdf(beta, 2.0, 0.5)
    dH = np.array([
        [[a, 0.0], [0.0, 0.0]],
        [[0.0, 0.0], [0.0, b]]
    ])
    return dH


import matplotlib.pyplot as plt
from matplotlib import patches
import numpy as np

########################################################################
# 3.1

def p1():

    x1 = [0 for i in range(n)]
    y1 = [0 for i in range(n)]

    radius1 = np.random.uniform(0, radius_limit, n)
    theta1 = np.random.uniform(0, 2*np.pi, n)

    for i in range(n):

        x1[i] = radius1[i] * np.cos(theta1[i])
        y1[i] = radius1[i] * np.sin(theta1[i])

    print("Variation in X coordinates 3.1 -> ", round(np.var(x1), 2))

    plt.scatter(x1, y1, s=1, alpha=1, c=colors, cmap='twilight_shifted')

    circ = plt.Circle((0, 0), radius_limit, color='black', fill=False)
    fig = plt.gcf()
    axes = fig.gca()
    axes.add_patch(circ)

    plt.xlim(-radius_limit*1.2, radius_limit*1.2)
    plt.ylim(-radius_limit*1.2, radius_limit*1.2)

    axes = plt.gca()
    axes.set_aspect('equal')

    plt.show()

########################################################################
# 3.2

def p2():

    x2 = [0 for i in range(n)]
    y2 = [0 for i in range(n)]

    for i in range(n):

        tempx = np.random.uniform(-radius_limit, radius_limit, 1)
        tempy = np.random.uniform(-radius_limit, radius_limit, 1)

        while (np.sqrt(tempx**2 + tempy**2) > radius_limit):

            tempx = np.random.uniform(-radius_limit, radius_limit, 1)
            tempy = np.random.uniform(-radius_limit, radius_limit, 1)

        x2[i] = tempx
        y2[i] = tempy

    print("Variation in X coordinates 3.2-> ", round(np.var(x2), 2))

    plt.scatter(x2, y2, s=1, alpha=1, c=colors, cmap='twilight_shifted')

    circ = plt.Circle((0, 0), radius_limit, color='black', fill=False)
    fig = plt.gcf()
    axes = fig.gca()
    axes.add_patch(circ)

    plt.xlim(-radius_limit*1.2, radius_limit*1.2)
    plt.ylim(-radius_limit*1.2, radius_limit*1.2)

    axes = plt.gca()
    axes.set_aspect('equal')

    plt.show()

########################################################################
# 3.3

def p3():

    x3 = [0 for i in range(n)]
    y3 = [0 for i in range(n)]

    theta3 = np.random.uniform(0, 2*np.pi, n)

    for i in range(n):

        radius3 = np.sqrt(np.random.random()) * radius_limit

        x3[i] = radius3 * np.cos(theta3[i])
        y3[i] = radius3 * np.sin(theta3[i])

    print("Variation in X coordinates 3.3 -> ", round(np.var(x3), 2))

    plt.scatter(x3, y3, s=1, alpha=1, c=colors, cmap='twilight_shifted')

    circ = plt.Circle((0, 0), radius_limit, color='black', fill=False)
    fig = plt.gcf()
    axes = fig.gca()
    axes.add_patch(circ)

    plt.xlim(-radius_limit*1.2, radius_limit*1.2)
    plt.ylim(-radius_limit*1.2, radius_limit*1.2)

    axes = plt.gca()
    axes.set_aspect('equal')

    plt.show()

########################################################################
# Question 3

n = 50000

colors = np.array([i for i in range(n)])

radius_limit = int(input("Input radius limit for all questions: "))

p1()
p2()
p3()


import numpy as np
import matplotlib.pyplot as plt

def model_A(x, params):
    y = params[0] + x*params[1] + params[2]*x**2
    return y

def model_B(x, params):
    y = params[0]*(np.exp(-0.5*(x-params[1])**2/params[2]**2))
    return y

def model_C(x, params):
    y = params[0]*(np.exp(-0.5*(x-params[1])**2/params[2]**2))
    y += params[0]*(np.exp(-0.5*(x-params[3])**2/params[4]**2))
    return y

def logprior_model_A(params):
    return 0

def logprior_model_B(params):
    if params[2]<0:
        return -np.inf
    else:
        return 0

def logprior_model_C(params):
    if params[2]<0 or params[4]<0 or params[1]<0 or params[3]<0:
        return -np.inf
    else:
        return 0

def loglike(x_obs, y_obs, sigma_y_obs, model, params):
    l = np.sum(-0.5*(y_obs -model(x_obs, params))**2/sigma_y_obs**2)
    return l
def run_mcmc(x_obs, y_obs, sigma_y_obs,
             model, logprior, n_params=3, n_iterations=20000, scale=0.1):

    params = np.ones([n_iterations, n_params])
    loglike_values = np.ones(n_iterations)*5.0
    loglike_values[0] = loglike(x_obs, y_obs, sigma_y_obs, model, params[0,:])
    for i in range(1, n_iterations):
        current_params = params[i-1,:]
        next_params = current_params + np.random.normal(scale=scale, size=n_params)

        loglike_current = loglike(x_obs, y_obs, sigma_y_obs, model, current_params) + logprior(current_params)
        loglike_next = loglike(x_obs, y_obs, sigma_y_obs, model, next_params) + logprior(next_params)

        r = np.min([np.exp(loglike_next - loglike_current), 1.0])
        alpha = np.random.random()

        if alpha < r:
            params[i,:] = next_params
            loglike_values[i] = loglike_next
        else:
            params[i,:] = current_params
            loglike_values[i] = loglike_current

    params = params[n_iterations//2:,:]
    loglike_values = loglike_values[n_iterations//2:]
    return {'params':params, 'x_obs':x_obs, 'y_obs':y_obs, 'loglike_values':loglike_values}

def BIC(params_model):
    max_loglike = np.max(params_model['loglike_values'])
    n_dim = np.shape(params_model['params'])[1]
    n_points = len(params_model['y_obs'])
    return 2.0*(-max_loglike + 0.5*n_dim*np.log(n_points))

def plot_model(params_model, model, model_name):
    n_dim = np.shape(params_model['params'])[1]
    n_points = len(params_model['y_obs'])

    plt.figure(figsize = (4*(n_dim//2+1),6))
    for i in range(n_dim):
        plt.subplot(2, n_dim//2+1, i+1)
        plt.hist(params_model['params'][:,i], density=True, bins=30)
        plt.title(r"$\beta_{}= {:.2f}\pm {:.2f}$".format(i,np.mean(params_model['params'][:,i]), np.std(params_model['params'][:,i])))
        plt.xlabel(r"$\beta_{}$".format(i))

    plt.subplot(2,n_dim//2+1, i+2)
    best = np.mean(params_model['params'], axis=0)
    x = params_model['x_obs']
    x_model = np.linspace(x.min(), x.max(), 100)
    y_model = model(x_model, best)
    plt.plot(x_model, y_model)
    plt.errorbar(x, y, yerr=sigma_y, fmt='o')
    plt.title("BIC={:.2f}".format(BIC(params_model)))

    plt.subplots_adjust(hspace=0.5)
    plt.savefig(model_name+".png")

# Lee los datos
data = np.loadtxt('data_to_fit.txt')
x = data[:,0]
y = data[:,1]
sigma_y = data[:,2]

# ajusta los modelos
params_model_A = run_mcmc(x, y, sigma_y, model_A, logprior_model_A, scale=0.05, n_iterations=1000000)
params_model_B = run_mcmc(x, y, sigma_y, model_B, logprior_model_B, scale=0.05, n_iterations=1000000)
params_model_C = run_mcmc(x, y, sigma_y, model_C, logprior_model_C, n_params=5, scale=0.05, n_iterations=1000000)

# hace las graficas
plot_model(params_model_A, model_A, 'model_A')
plot_model(params_model_B, model_B, 'model_B')
plot_model(params_model_C, model_C, 'model_C')


import matplotlib as mpl
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.image as mpimg

mpl.rcParams['legend.fontsize'] = 10
fig = plt.figure()
ax = fig.gca(projection='3d')
theta = np.linspace(-4 * np.pi, 4 * np.pi, 200)
z = np.linspace(-2, 2, 200)
r = z**2 + 1
x = r * np.sin(2*theta)
y = r * np.cos(2*theta)
ax.plot(x, y, z, label='parametric curve')
ax.legend()

plt.savefig('3d_parametric_curve.png')
plt.show()


import matplotlib.pyplot as plt
import scipy
import numpy as np
import networkx as nx
import random
import scipy.io
import scipy.spatial.distance as sd

def is_connected(adj,n):
# Uses the fact that multiplying the adj matrix to itself k times give the
# number of ways to get from i to j in k steps. If the end of the
# multiplication in the sum of all matrices there are 0 entries then the
# graph is disconnected. Computationally intensive, but can be sped up by
# the fact that in practice the diameter is very short compared to n, so it
# will terminate in order of log(n)? steps.
    adjn=np.zeros((n,n))
    adji=adj.copy()
    for i in range(n):
        adjn+=adji
        adji=adji.dot(adj)
    return len(np.where(adjn == 0)[0])==0

def max_span_tree(adj):
    n=adj.shape[0]
    if not(is_connected(adj,n)):
        print('This graph is not connected. No spanning tree exists')
    else:
        tr=np.zeros((n,n))
        adj[adj==0]=-np.inf
        conn_nodes = [0]
        rem_nodes = [i+1 for i in range(n-1)]
        while len(rem_nodes)>0:
            L=np.zeros(n)
            L[conn_nodes]=1
            L=L.reshape(n,1)
            C=np.zeros(n)
            C[rem_nodes]=1
            C=C.reshape(1,n)
            B=L.dot(C)
            A=B*adj
            i=np.where(A==np.max(A))[0][0]
            j=np.where(A==np.max(A))[1][0]
            tr[i,j]=1
            tr[j,i]=1
            conn_nodes+=[j]
            rem_nodes.remove(j)
    return tr.astype(int)

def build_similarity_graph(X, var=1, eps=0, k=0):
#      Computes the similarity matrix for a given dataset of samples.
#
#  Input
#  X:
#      (n x m) matrix of m-dimensional samples
#  k and eps:
#      controls the main parameter of the graph, the number
#      of neighbours k for k-nn, and the threshold eps for epsilon graphs
#  var:
#      the sigma value for the exponential function, already squared
#
#
#  Output
#  W:
#      (n x n) dimensional matrix representing the adjacency matrix of the graph
#  similarities:
#      (n x n) dimensional matrix containing
#      all the similarities between all points (optional output)

  assert eps + k != 0, "Choose either epsilon graph or k-nn graph"

#################################################################
#  build full graph
#  similarities: (n x n) matrix with similarities between
#  all possible couples of points.
#  The similarity function is d(x,y)=exp(-||x-y||^2/var)
#################################################################
  # euclidean distance squared between points
  dists = sd.squareform(sd.pdist(X, "sqeuclidean"))
  similarities = np.exp(-dists.astype(float) / (2.*var))

#################################################################
#################################################################

  if eps:
#################################################################
#  compute an epsilon graph from the similarities               #
#  for each node x_i, an epsilon graph has weights              #
#  w_ij = d(x_i,x_j) when w_ij > eps, and 0 otherwise           #
#################################################################
    similarities[similarities < eps] = 0

    return similarities

#################################################################
#################################################################

  if k:
#################################################################
#  compute a k-nn graph from the similarities                   #
#  for each node x_i, a k-nn graph has weights                  #
#  w_ij = d(x_i,x_j) for the k closest nodes to x_i, and 0      #
#  for all the k-n remaining nodes                              #
#  Remember to remove self similarity and                       #
#  make the graph undirected                                    #
#################################################################
    sort = np.argsort(similarities)[:, ::-1]  # descending
    mask = sort[:, k + 1:]  # indices to mask
    for i, row in enumerate(mask): similarities[i, row] = 0
    np.fill_diagonal(similarities, 0)  # remove self similarity
    return (similarities + similarities.T) / 2.  # make the graph undirected

    return similarities

#################################################################
#################################################################

def build_laplacian(W, laplacian_normalization=""):
#  laplacian_normalization:
#      string selecting which version of the laplacian matrix to construct
#      either 'unn'normalized, 'sym'metric normalization
#      or 'rw' random-walk normalization

#################################################################
# build the laplacian                                           #
# L: (n x n) dimensional matrix representing                    #
#    the Laplacian of the graph                                 #
#################################################################
  degree = W.sum(1)
  if not laplacian_normalization:
    return np.diag(degree) - W
  elif laplacian_normalization == "sym":
    aux = np.diag(1 / np.sqrt(degree))
    return np.eye(*W.shape) - aux.dot(W.dot(aux))
  elif laplacian_normalization == "rw":
    return np.eye(*W.shape) - np.diag(1 / degree).dot(W)
  else: raise ValueError

#################################################################
#################################################################

def plot_edges_and_points(X,Y,W,title=''):
    colors=['go-','ro-','co-','ko-','yo-','mo-']
    n=len(X)
    G=nx.from_numpy_matrix(W)
    nx.draw_networkx_edges(G,X)
    for i in range(n):
        plt.plot(X[i,0],X[i,1],colors[int(Y[i])])
    plt.title(title)
    plt.axis('equal')

def plot_graph_matrix(X,Y,W):
    plt.figure()
    plt.clf()
    plt.subplot(1,2,1)
    plot_edges_and_points(X,Y,W)
    plt.subplot(1,2,2)
    plt.imshow(W, extent=[0, 1, 0, 1])
    plt.show()

def plot_clustering_result(X,Y,W,spectral_labels,kmeans_labels,normalized_switch=0):
    plt.figure()
    plt.clf()
    plt.subplot(1,3,1)
    plot_edges_and_points(X,Y,W,'ground truth')
    plt.subplot(1,3,2)
    if normalized_switch:
        plot_edges_and_points(X,spectral_labels,W,'unnormalized laplacian')
    else:
        plot_edges_and_points(X,spectral_labels,W,'spectral clustering')
    plt.subplot(1,3,3)
    if normalized_switch:
        plot_edges_and_points(X,kmeans_labels,W,'normalized laplacian')
    else:
        plot_edges_and_points(X,kmeans_labels,W,'k-means')
    plt.show()

def plot_the_bend(X, Y, W, spectral_labels, eigenvalues_sorted):
    plt.figure()
    plt.clf()
    plt.subplot(1,3,1)
    plot_edges_and_points(X,Y,W,'ground truth')

    plt.subplot(1,3,2)
    plot_edges_and_points(X,spectral_labels,W,'spectral clustering')

    plt.subplot(1,3,3)
    plt.plot(np.arange(0,len(eigenvalues_sorted),1),eigenvalues_sorted,'v:')
    plt.show()

def plot_classification(X, Y,labels,  var=1, eps=0, k=0):

    plt.figure()

    W = build_similarity_graph(X, var=var, eps=eps, k=k)

    plt.subplot(1, 2, 1)
    plot_edges_and_points(X, Y, W, 'ground truth')

    plt.subplot(1, 2, 2)
    plot_edges_and_points(X, labels, W, 'HFS')
    plt.show()

def label_noise(Y, alpha):

    ind=np.arange(len(Y))
    random.shuffle(ind)
    Y[ind[:alpha]] = 3-Y[ind[:alpha]]
    return Y

def plot_classification_comparison(X, Y,hard_labels, soft_labels,var=1, eps=0, k=0):

    plt.figure()

    W = build_similarity_graph(X, var=var, eps=eps, k=k)

    plt.subplot(1,3,1)
    plot_edges_and_points(X, Y, W, 'ground truth')

    plt.subplot(1,3,2)
    plot_edges_and_points(X, hard_labels, W, 'Hard-HFS')

    plt.subplot(1,3,3)
    plot_edges_and_points(X, soft_labels, W, 'Soft-HFS')
    plt.show()

import numpy,import scipy,import networkx


import numpy as np
from sympy import *

def tx(x = 0):
  """
    Translation on «x» axis. Returns homogeneous transformation matrix
  """
  return np.array([[1, 0, 0, x],
                   [0, 1, 0, 0],
                   [0, 0, 1, 0],
                   [0, 0, 0, 1]])

def symbolicTx(x = 0):
  """
    Translation on «x» axis. Returns homogeneous transformation matrix
  """
  return Matrix([[1, 0, 0, x],
                 [0, 1, 0, 0],
                 [0, 0, 1, 0],
                 [0, 0, 0, 1]])

def ty(y = 0):
  '''
  Translation on «y» axis. Returns homogeneous transformation matrix
  '''
  return np.array([[1, 0, 0, 0],
                   [0, 1, 0, y],
                   [0, 0, 1, 0],
                   [0, 0, 0, 1]])

def symbolicTy(y = 0):
  """
    Translation on «y» axis. Returns homogeneous transformation matrix
  """
  return Matrix([[1, 0, 0, 0],
                 [0, 1, 0, y],
                 [0, 0, 1, 0],
                 [0, 0, 0, 1]])

def tz(z = 0):
  '''
  Translation on «z» axis. Returns homogeneous transformation matrix
  '''
  return np.array([[1, 0, 0, 0],
                   [0, 1, 0, 0],
                   [0, 0, 1, z],
                   [0, 0, 0, 1]])

def symbolicTz(z = 0):
  """
    Translation on «z» axis. Returns homogeneous transformation matrix
  """
  return Matrix([[1, 0, 0, 0],
                 [0, 1, 0, 0],
                 [0, 0, 1, z],
                 [0, 0, 0, 1]])

def rx(x = 0):
  '''
    Rotation on «x» axis. Returns homogeneous transformation matrix
  '''
  return np.array([[1, 0, 0, 0],
                   [0, np.cos(x), -np.sin(x), 0],
                   [0, np.sin(x), +np.cos(x), 0],
                   [0, 0, 0, 1]])

def symbolicRx(x = 0):
  """
    Rotation on «x» axis. Returns homogeneous transformation matrix
  """
  return Matrix([[1, 0, 0, 0],
                 [0, cos(x), -sin(x), 0],
                 [0, sin(x), +cos(x), 0],
                 [0, 0, 0, 1]])

def ry(y = 0):
  '''
    Rotation on «y» axis. Returns homogeneous transformation matrix
  '''
  return np.array([[np.cos(y), 0, +np.sin(y), 0],
                   [0, 1, 0, 0],
                   [-np.sin(y), 0, np.cos(y), 0],
                   [0, 0, 0, 1]])

def symbolicRy(y = 0):
  """
    Rotation on «y» axis. Returns homogeneous transformation matrix
  """
  return Matrix([[cos(y), 0, +sin(y), 0],
                 [0, 1, 0, 0],
                 [-sin(y), 0, cos(y), 0],
                 [0, 0, 0, 1]])

def rz(z = 0):
  '''
    Rotation on «z» axis. Returns homogeneous transformation matrix
  '''
  return np.array([[np.cos(z), -np.sin(z), 0, 0],
                   [np.sin(z), +np.cos(z), 0, 0],
                   [0, 0, 1, 0],
                   [0, 0, 0, 1]])

def symbolicRz(z = 0):
  """
    Rotation on «z» axis. Returns homogeneous transformation matrix
  """
  return Matrix([[cos(z), -sin(z), 0, 0],
                 [sin(z), +cos(z), 0, 0],
                 [0, 0, 1, 0],
                 [0, 0, 0, 1]])

def axisAngle(H):
  """
    This function computes the axis - angle vector «X» using the Homogeneous Transformation Matrix of a reference frame
    H: np.array (two - dimensional)
  """
  theta = np.arccos((np.trace(H[0 : 3, 0 : 3]) - 1)/2)
  n = (1/(2 * np.sin(theta))) * np.array([[H[2, 1] - H[1, 2]],
                                          [H[0, 2] - H[2, 0]],
                                          [H[1 ,0] - H[0, 1]]])
  x = np.append(H[0 : 3, 3], theta * n)
  return x.reshape((6, 1))

def symbolicAxisAngle(H):
  """
    This function computes the axis - angle vector «X» using the Homogeneous Transformation Matrix of a reference frame
    H: np.array (two - dimensional)
  """
  theta = simplify(acos((H[0 : 3, 0 : 3].trace() - 1) / 2))
  n = (1 / (2 * sin(theta))) * Matrix([[H[2, 1] - H[1, 2]],
                                       [H[0, 2] - H[2, 0]],
                                       [H[1 ,0] - H[0, 1]]])
  X = H[0 : 3, 3].row_insert(3, simplify(theta * n))
  return X


import numpy as np

def Bspline(i, k, u, node_vector):
    ''' 计算k次规范B样条基函数'''

    if k == 0:  # 0次B样条
        if node_vector[i] <= u < node_vector[i + 1]:
            Nik_u = 1.0
        else:
            Nik_u = 0.0

    else:
        length1 = node_vector[i + k] - node_vector[i]
        length2 = node_vector[i + k + 1] - node_vector[i + 1]
        if length1 == 0.0:   # 规定 0/0 = 0
            length1 = 1.0
        if length2 == 0.0:
            length2 = 1.0

        Nik_u_1 = (u - node_vector[i]) / length1 * \
            Bspline(i, k - 1, u, node_vector)
        Nik_u_2 = (node_vector[i + k + 1] - u) / \
            length2 * Bspline(i + 1, k - 1, u, node_vector)
        Nik_u = Nik_u_1 + Nik_u_2

    return Nik_u

def U_uniform(n, k):
    t = np.linspace(0, 1, n + k + 2, endpoint=True)
    return t

def U_quasi_uniform(n, k):
    '''
        准均匀B样条的节点向量计算, n+1个控制节点,k次B样条
    '''
    t = np.linspace(0, 1, n - k + 2, endpoint=True)  # 内结点 均匀分布
    t = np.append(np.zeros(k), t)  # 左边 k个结点 置0
    t = np.append(t, np.ones(k))  # 右边 k个结点 置1
    return t

def U_piecewise_Bezier(n, k):
    # 分段Bezier曲线的节点向量计算，共n+1个控制顶点，k次B样条
    # 分段Bezier端节点重复度为k+1，内间节点重复度为k,且满足n/k为正整数

    # 0...k  k+1...n+1  n+2...n+k+1
    # if (n % k) == 0 and (k % 1) == 0 and k >= 1:
    node_vector = np.zeros(n + k + 2)
    node_vector[n + 1: n + k + 2] = 1  # 右端节点置1
    # print(n, k)
    # print(node_vector)

    piecewise = int(n / k)
    flag = 0
    print(piecewise)
    if piecewise > 1:
        for i in range(piecewise - 1):  # 共 piecewise-1组
            for j in range(k):  # k 重复度
                node_vector[k + flag * k + j + 1] = (i + 1) / piecewise
            flag += 1
    else:
        print("error")
        # return None
    return node_vector

#-------------------- Bspline dots -----------------
def bspline(n, k, points, fns):
    if(fns == "uniform"):
        node_vector = U_uniform(n, k)  # n+k+2
    elif(fns == "quasi"):
        node_vector = U_quasi_uniform(n, k)
    elif(fns == "piecewise" and (n % k) == 0 and (k % 1) == 0 and k >= 1):
        node_vector = U_piecewise_Bezier(n, k)

    else:
        return None
    # n = t-k-1 =>　n+1?
    Nik = np.zeros((n + 1, 1))
    P_u_x = list()
    P_u_y = list()

    U = np.linspace(k / (n + k + 1), (n + 1) / (n + k + 1), 500,
                    endpoint=True) if fns == "uniform" else np.linspace(0, 1, 200, endpoint=False)

    for u in U:
        for i in range(0, n + 1, 1):
            Nik[i, :] = Bspline(i, k, u, node_vector)

        p_u = np.dot(points.T, Nik)  # 曲线上点的坐标
        P_u_x.append(p_u[0, 0])  # 所有点的横坐标
        P_u_y.append(p_u[1, 0])  # 所有点的纵坐标

    # P_u_x = np.asarray(P_u_x)
    # P_u_y = np.asarray(P_u_y)
    return P_u_x, P_u_y

def echo():
    return "from bspline echo"

def get_points(raw_points: list):
    # points = np.array([len(raw_points), 2])
    points = np.array([[raw_points[0]["x"], raw_points[0]["y"]]])
    for point in raw_points[1:]:
        points = np.vstack((points, np.array([point["x"], point["y"]])))

    return points


# Implementation taken from
# https://stackoverflow.com/questions/43238173/python-convert-matrix-to-positive-semi-definite/43244194#43244194

import numpy as np
import torch

from .is_pos_def import is_pos_def

def nearest_pd(A, f=np.spacing):
    """Find the nearest positive-definite matrix to input

    A Python/Numpy port of John D'Errico's `nearestSPD` MATLAB code [1], which credits [2]

    [1] https://www.mathworks.com/matlabcentral/fileexchange/42885-nearestspd

    [2] https://doi.org/10.1016/0024-3795(88)90223-6
    """

    B = (A + A.T) / 2
    _, s, V = torch.svd(B)

    # For a comparison with kanga, see the following:
    # https://github.com/pytorch/pytorch/issues/16076#issuecomment-477755364
    H = torch.matmul(V, torch.matmul(torch.diag(s), V.T))

    A2 = (B + H) / 2

    A3 = (A2 + A2.T) / 2

    if is_pos_def(A3):
        return A3

    spacing = f(torch.norm(A).item())
    I = torch.eye(A.shape[0])
    k = 1
    while not is_pos_def(A3):
        eigenvals = torch.eig(A3, eigenvectors=False)[0][:, 0]
        mineig = eigenvals.min().item()
        A3 += I * (-mineig * k**2 + spacing)
        k += 1

    return A3


"""
ackley.py by Adhisha Gammanpila (https://github.com/adhishagc/Ackley-Function)

-----------------------------------

MIT License

Copyright (c) 2019 Adhisha Gammanpila

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

"""

#importing libraries
import numpy as np

def ackley_function(x1,x2):
  #returns the point value of the given coordinate
  part_1 = -0.2*np.sqrt(0.5*(x1*x1 + x2*x2))
  part_2 = 0.5*(np.cos(2*np.pi*x1) + np.cos(2*np.pi*x2))
  value = np.exp(1) + 20 -20*np.exp(part_1) - np.exp(part_2)
  #returning the value
  return value


from sympy import Matrix, Float, Symbol
from BMSS.strike_goldd_simplified import*

###############################################################################
#Model
###############################################################################

x, s, h = [Symbol(x) for x in ['x', 's', 'h']]
mu_max, Ks, Y, synh, Kind = [Symbol(x) for x in ['mu_max', 'Ks', 'Y', 'synh', 'Kind']]
Ind = Symbol('Ind')

mu   = mu_max*s/(s+Ks)

dx  =  x*mu
ds  = -dx/Y
dh  =  synh*Ind/(Ind+Kind) -h*mu

variables = {'x': x, 's': s, 'h': h, 'mu_max': mu_max, 'Ks': Ks, 'Y': Y, 'synh': synh, 'Kind': Kind, 'Ind': Ind}

###############################################################################
#Specify Input for IA
###############################################################################
#User makes changes here
measured_states    = Matrix([x, s, h])
states             = Matrix([x, s, h])
unknown_parameters = Matrix([synh, Kind])
diff               = Matrix([dx, ds, dh])
input_conditions   = {Ind: 6}
init_conditions    = {x: Float(1, 3), s: Float(1, 3), h: Float(1, 3)}
decomposition      = [[x, s, h]
                      ]

known_parameters = {Y : 3, Ks : 0.15, mu_max : 0.012}
diff = diff.subs(known_parameters.items())

###############################################################################
#Call Strike-Goldd
###############################################################################
def run_strike_goldd():
    start_time = time()
    x_aug_dict = strike_goldd(measured_states,
                              states,
                              unknown_parameters,
                              input_conditions,
                              diff,
                              init_conditions,
                              decomposition)
    print("Total time: ",time()-start_time)
    print(x_aug_dict)
    return x_aug_dict

if __name__ == '__main__':
    pass


#!/usr/bin/env python3
# -*- coding: utf-8 -*-

###
# Name: Nikki and Riley
# Student ID: 2267883 2274503
# Email: schwa218@mail.chapman.edu keda106@mail.chapman.edu
# Course: PHYS220/MATH220/CPSC220 Fall 2017
# Assignment: Classwork 4
###

"""Classwork 04
This classwork introduces numpy arrays and compares their performance to
python lists.
"""

import math
import numpy as np

#GAUSSIAN LIST

def gen_gaussian_list(a, b, n=1000):
    """gen_gaussian_list(a, b, n=1000)
    Generate a discrete approximation of a Gaussian function, including its
    domain and range, stored as a pair of vanilla python lists.

    Args:
        a (float) : Lower bound of domain
        b (float) : Upper bound of domain
        n (int, optional) : Number of points in domain, defaults to 1000.

    Returns:
        (x, g) : Pair of lists of floats
            x  : [a, ..., b] List of n equally spaced floats between a and b
            g  : [g(a), ..., g(b)] List of Gaussian values matched to x
    """
    dx = (b-a)/(n-1)                         # spacing between points
    x = [a + k*dx for k in range(n)]         # domain list

    # Local implementation of a Gaussian function
    def gauss(x):
        return (1/math.sqrt(2*math.pi))*math.exp(-x**2/2)

    g = [gauss(xk) for xk in x]                  # range list
    return (x, g)

#NEW GAUSS ARRAY USING NUMPY

def gen_gaussian_array(a, b, n=1000):
    """gen_gaussian_array(a, b, n=1000)
    Generate a discrete approximation of a Gaussian function, including its
    domain and range, stored as a pair of numpy arrays.

    Args:
        a (float) : Lower bound of domain
        b (float) : Upper bound of domain
        n (int, optional) : Number of points in domain, defaults to 1000.

    Returns:
        (x, g) : Pair of numpy arrays of float64
            x  : [a, ..., b] Array of n equally spaced float64 between a and b
            g  : [g(a), ..., g(b)] Array of Gaussian values matched to x
    """
    ### No need for np.array here. np.linspace already returns an array.
    x = np.array(np.linspace(a,b,n),dtype=np.float64)
    def gauss(x):
        return (1/math.sqrt(2*math.pi))*math.exp(-x**2/2)  ### Do not use math if using numpy. Use np.sqrt etc.
    ### Vectorize is not needed here since all operations are already vectorized (if np.exp and np.pi and np.sqrt are used)
    fx = np.vectorize(gauss)
    g = fx(x)  ### No need to redefine g here. Just return gauss(x) directly.
    return(x,g)

#SINC LIST

def sinc_list(a, b, n=10000):
    """sinc_list(a, b, n=10000)
    Generate a discrete approximation of a Sinc function, including its
    domain and range, stored as a pair of vanilla python lists.

    Args:
        a (float) : Lower bound of domain
        b (float) : Upper bound of domain
        n (int, optional) : Number of points in domain, defaults to 10000.

    Returns:
        (x, g) : Pair of lists of floats
            x  : [a, ..., b] List of n equally spaced floats between a and b
            g  : [g(a), ..., g(b)] List of Gaussian values matched to x
    """
    dx = (b-a)/(n-1)                         # spacing between points
    x = [a + k*dx for k in range(n)]         # domain list

    # Local implementation of a Sinc function
    def sinc(x):
        if x == 0:
           return 1
        else:
            return (math.sin(x)/x)

    g = [sinc(xk) for xk in x]                  # range list
    return (x, g)

#NEW SINC ARRAY USING NUMPY

def sinc_array(a, b, n=10000):
    """sinc_array(a, b, n=10000)
    Generate a discrete approximation of a Sinc function, including its
    domain and range, stored as a pair of numpy arrays.

    Args:
        a (float) : Lower bound of domain
        b (float) : Upper bound of domain
        n (int, optional) : Number of points in domain, defaults to 10000.

    Returns:
        (x, g) : Pair of numpy arrays of float64
            x  : [a, ..., b] Array of n equally spaced float64 between a and b
            g  : [g(a), ..., g(b)] Array of Gaussian values matched to x
    """
    x = np.array(np.linspace(a,b,n),dtype=np.float64)  ### no need for np.array
    def sinc(x):
        if x == 0:
            return 1
        else:
            return (math.sin(x)/x)
    fx = np.vectorize(sinc)  ### Vectorize IS needed here, since the if statement in sinc is not vectorized yet.
    g = fx(x)  ### no need for g, just return fx(x) directly.
    return(x,g)

def main(a,b,n=1000):
    """main(a, b, n=1000)
    Main function for command line operation. Prints result of Gaussian to screen.

    Args:
        a (float) : Lower bound of domain
        b (float) : Upper bound of domain
        n (int, optional) : Number of points in domain, defaults to 1000.

    Returns:
        None

    Effects:
        Prints Gaussian to screen.
    """
    # You can unpack tuples as return values easily
    x, g = gen_gaussian_list(a,b,n)
    # The zip function takes two lists and generates a list of matched pairs
    for (xk, gk) in zip(x, g):
        # The format command replaces each {} with the value of a variable
        print("({}, {})".format(xk, gk))

# Protected main block for command line operation
if __name__ == "__main__":
    # The sys module contains features for running programs
    import sys
    # The sys.argv list variable contains all command line arguments
    #    sys.argv[0] is the program name always
    #    sys.argv[1] is the first command line argument, etc
    if len(sys.argv) == 4:
        a = float(sys.argv[1])
        b = float(sys.argv[2])
        n = int(sys.argv[3])
        main(a,b,n)
    elif len(sys.argv) == 3:
        a = float(sys.argv[1])
        b = float(sys.argv[2])
        main(a,b)
    else:
        print("Usage: cw04.py a b [n]")
        print("  a : float, lower bound of domain")
        print("  b : float, upper bound of domain")
        print("  n : integer, number of points in domain")
        exit(1)


import numpy as np
import math

from corai_util.tools import function_iterable

# defines the coefficients for fractional ADAMS method in order to compute a SDE path.
# it needs the number of coefficients as well as the alpha of roughness.
def fractional_ADAMS(k, alpha, DELTA):
    # a needs k+2 elements, because j \in 0,k+1.
    # b doesn't start at 0, so only k+1 elements.
    a = np.zeros(k + 2)
    b = np.zeros(k + 1)
    for i in range(k + 2):
        if i == 0:
            a[i] = k ** (alpha + 1) - (k - alpha) * (k + 1) ** alpha
        if i == k + 1:
            a[i] = 1
        if (i != k + 1) and (i != 0):
            a[i] = (k - i + 2) ** (alpha + 1) + (k - i) ** (alpha + 1) - 2 * (k - i + 1) ** (alpha + 1)
        if i != k + 1:
            b[i] = (k + 1 - i) ** alpha - (k - i) ** alpha
    a = 1 / math.gamma(alpha + 2) * DELTA ** alpha * a
    b = 1 / math.gamma(alpha + 1) * DELTA ** alpha * b
    return a, b

def system_ODE_solver(tt, starting_point, function_evolution, left_or_right="left"):
    """
    Semantics:
        ODE solver with Euler's method. It solves liner systems of ODEs of the first oder.
        left to right flow.

    Args:
        tt: grid of times. Assume regularity for the grid. The same grid for all functions.
        starting_point: LIST of lower boundary, starting condition
                        time and previous points for each function.
                        Same order as in function_evolution.
        function_evolution: LIST of "1 + len(function_evolution)" dimensional function.
        left_or_right: starting point on left or right. Changes the equations, either backwards of forwards equations.

    Notes:
        function_evolution and starting_point as lists type for optimization of the code
        (access constant and small overhead)

    Returns:
        A list with the values of the function, over the grid tt. format: [[x1,y1...], [x2,y2...] ... ]

    """
    assert function_iterable.is_iterable(function_evolution), "Function Evolution is not iterable."
    assert function_iterable.is_iterable(starting_point), "Starting Point is not iterable."

    L = len(tt)
    J = len(function_evolution)
    yy = [[0] * J for _ in range(L)]  # creates a L by J matrix.
    # List comprehension in order to avoid copy of mutable element

    if left_or_right == "left":
        yy[0] = starting_point
        a = 0  # beg range
        b = L - 1  # end range; skipping first step
        step = 1  # how increment
        DELTA = tt[1] - tt[0]

    else:
        yy[-1] = starting_point
        a = L - 1  # beg range
        b = 0  # end range; skipping first step
        step = -1  # how increment
        DELTA = tt[0] - tt[1]  # delta changes because of the direction

    for i in range(a, b, step):
        for j in range(J):
            yy[i + step][j] = yy[i][j] + DELTA * function_evolution[j](tt[i], *yy[i])
    return yy

"""

def support(sigma1, sigma2):  # gives back when the normal given by sigma 1 is lower than the normal given by sigma2
    xm = np.sqrt(2) * sigma1 * sigma2 * np.sqrt(np.log(sigma1 / sigma2) / (sigma1 - sigma2)) / np.sqrt(sigma1 + sigma2)
    return -xm, xm  # by symmetry

################ DATA:
SIGMA = [1., 2.2]
tm, tM = support(SIGMA[0], SIGMA[1])
tm *= 0.95
tM *= 0.95
# times 0.9 so one can be inside the interval, all the time.
# Otherwise we face boundary issues.
NB_GRID_POINT = int(1E4)  # conversion float to int for linspace.
tt = np.linspace(tm, tM, NB_GRID_POINT)

density_mu = lambda tt: norm.pdf(tt, 0, SIGMA[0])  # probably not the fastest choice for single evaluation, but great for vectorization !
density_nu = lambda tt: norm.pdf(tt, 0, SIGMA[1])  # probably not the fastest choice for single evaluation, but great for vectorization !

# for x \ihow_much_rotate E !
density_eta = lambda tt: np.maximum(density_mu(tt) - density_nu(tt), 0)
density_gamma = lambda tt: np.maximum(density_nu(tt) - density_mu(tt), 0)

# FIRST PLOT:
plt.figure(figsize=(10, 4))
plt.plot(tt, density_mu(tt), 'b', label=r'$\mu$')
plt.plot(tt, density_nu(tt), 'k', label=r'$\nu$')
plt.plot(tt, density_eta(tt), 'r', label=r'$\eta$')
plt.plot(tt, density_gamma(tt), 'g', label=r'$\gamma$')
plt.title("The support of E is (%.3f,%.3f)" % (support(SIGMA[0], SIGMA[1])))
plt.legend(loc="best")

# SOLVING ODE FROM LEFT !!!
func_p = lambda t, x, y: (t - y) / (y - x) * density_eta(t) / density_gamma(x)
func_q = lambda t, x, y: (x - t) / (y - x) * density_eta(t) / density_gamma(y)
STARTING_POINT = [tm / 0.95 * 1.05,  # tm /0.95 is equal to the true tm (we narrowed the interval for simulation)
                  # and *1.05 so we are not at the boundary.
                  8.]  # p(a), q(a), remember p < q. For that reason we take q(a) large (\approx to inf).

my_nested_results = system_ODE_solver(tt, STARTING_POINT, [func_p, func_q], "left")

"""


# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, print_function
import numpy as np

def get_r2(y_true, y_pred):
    """R2 or the error.

    Parameters
    ----------
    y_true : numpy.ndarray or list
        Ground truth (correct) values

    y_pred : numpy.ndarray or list
        Predicted values, as returned by a regression estimator.

    Returns
    -------
    loss : float
        R2 value.
    """
    SStot = np.sum((y_true-y_true.mean())**2)
    SSres = np.sum((y_true-y_pred)**2)
    return 1 - SSres/SStot

def get_rmse(y_true, y_pred):
    """Compute maximum absolute error.

    Parameters
    ----------
    y_true : numpy.ndarray or list
        Ground truth (correct) values.

    y_pred : numpy.ndarray or list
        Predicted values, as returned by a regression estimator.

    Returns
    -------
    loss : float
        The maximum absolute error times the sign of the error.
    """
    SSres = np.mean((y_true-y_pred)**2)
    return SSres**0.5

def get_max_error(y_true, y_pred):
    """Compute maximum absolute error.

    Parameters
    ----------
    y_true : numpy.ndarray or list
        Ground truth (correct) values.

    y_pred : numpy.ndarray or list
        Predicted values, as returned by a regression estimator.

    Returns
    -------
    loss : float
        The maximum absolute error.
    """
    return np.array(y_pred-y_true)[np.argmax(np.abs(y_pred-y_true))]


import numpy as np

def exMax(flips, theta_A=None, theta_B=None, maxiter=10):
    # Initial Guess
    theta_A = theta_A or random.random()
    theta_B = theta_B or random.random()
    thetas = [(theta_A, theta_B)]
    # Iterate
    for c in range(maxiter):
        print("#%d:\t%0.2f %0.2f" % (c, theta_A, theta_B))
        heads_A, tails_A, heads_B, tails_B = e_step(flips, theta_A, theta_B)
        theta_A, theta_B = m_step(heads_A, tails_A, heads_B, tails_B)

    thetas.append((theta_A, theta_B))
    return thetas, (theta_A, theta_B)

"""Produce the expected value for heads_A, tails_A, heads_B, tails_B
    over the rolls given the coin biases"""
def e_step(rolls, theta_A, theta_B):

    heads_A, tails_A = 0, 0
    heads_B, tails_B = 0, 0
    for trial in rolls:
        likelihood_A = coin_likelihood(trial, theta_A)
        likelihood_B = coin_likelihood(trial, theta_B)
        p_A = likelihood_A / (likelihood_A + likelihood_B)
        p_B = likelihood_B / (likelihood_A + likelihood_B)
        heads_A += p_A * trial.count("H")
        tails_A += p_A * trial.count("T")
        heads_B += p_B * trial.count("H")
        tails_B += p_B * trial.count("T")
    return heads_A, tails_A, heads_B, tails_B

"""Produce the values for theta that maximize the expected number of heads/tails"""
def m_step(heads_A, tails_A, heads_B, tails_B):

    # Replace dummy values with your implementation
    theta_A = heads_A / (heads_A + tails_A)
    theta_B = heads_B / (heads_B + tails_B)
    return theta_A, theta_B

def coin_likelihood(roll, bias):
    # P(X | Z, theta)
    numHeads = roll.count("H")
    flips = len(roll)
    return pow(bias, numHeads) * pow(1 - bias, flips - numHeads)

flips = [ "HTTTHHTHTH", "HHHHTHHHHH", "HTHHHHHTHH",
          "HTHTTTHHTT", "THHHTHHHTH" ]
thetas, _ = exMax(flips, 0.6, 0.5, maxiter=6)


# 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