import numpy as np
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
from sklearn import decomposition
from scipy import linalg as la

def kmeans(data,n_clusters,init='random',max_iter=300,normalize=False):
    n,m = data.shape
    norms = np.zeros((n,n_clusters))
    if init=='random':
        means = data.mean(axis=0)+np.random.randn(n_clusters,m)*data.std()
    else:
        means=init
    if normalize:
        means /= np.linalg.norm(means,axis=1)[:,np.newaxis]
    for i in xrange(n_clusters):
        norms[:,i] = ((data-means[i,:])**2).sum(axis=1)
    labels = np.argmin(norms,axis=1)
    it =0
    for j in xrange(max_iter):
        it += 1
        means = np.array([data[labels==i].mean(axis=0) for i in xrange(n_clusters)])
        if normalize:
            means /= np.linalg.norm(means,axis=1)[:,np.newaxis]
        for i in xrange(n_clusters):
            norms[:,i] = ((data-means[i,:])**2).sum(axis=1)
        new_labels = np.argmin(norms,axis=1)
        if not np.allclose(labels,new_labels):
            labels=new_labels
        else:
            break

    inertia = 0
    for i in xrange(n_clusters):
        inertia += ((data[labels==i]-means[i,:])**2).sum()
    return means,labels,inertia

def clusterIris():
    iris = load_iris()

    X = iris.data
    # pre-process
    Y = X - X.mean(axis=0)
    # get SVD
    U,S,VT = la.svd(Y,full_matrices=False)
    # project onto the first two principal components
    Yhat = U[:,:2].dot(np.diag(S[:2]))

    # cluster 10 times, retaining the best
    means = None
    labs = None
    inertia=np.inf

    for j in xrange(10):
        m,l,i = kmeans(Yhat,3)
        if i < inertia:
            inertia=i
            means=m
            labs=l

    setosa = iris.target==0
    versicolor = iris.target==1
    virginica = iris.target==2
    p1, p2 = Yhat[:,0], Yhat[:,1]
    mrkr = []
    for flower, m,n in zip([setosa,versicolor,virginica],['*','.','^'],['Setosa','Versicolor','Virginica']):
        mr = plt.scatter([],[],color='k',marker=m,label=n)
        mrkr.append(mr)
        for i,c in enumerate(['cyan','red','green']):
            msk = np.where(labs[flower]==i)[0]
            if msk.any():
                plt.scatter(p1[flower][msk],p2[flower][msk], marker=m, color=c)
    mrkr.append(plt.scatter(means[:,0],means[:,1],marker='+',s=100,linewidths=2,label="Means"))

    plt.legend(handles=mrkr, loc=2)
    plt.ylim([-4,5])
    plt.xlim([-4,4])
    plt.xlabel("First Principal Component")
    plt.ylabel("Second Principal Component")
    plt.show()

def loadEarthquakes(path):
    latitudes = []
    longitudes = []
    for i in xrange(1,7):
        with open(path.format(i),'r') as f:
            for line in f:
                la = float(line[20:25])/1000
                s = line[25]=='S'
                lo = float(line[26:32])/1000
                w = line[32]=='W'
                latitudes.append(la*(-1)**s)
                longitudes.append(lo*(-1)**w)
    latitudes=np.array(latitudes)
    longitudes = np.array(longitudes)
    return latitudes,longitudes

def sphericalToEuclidean(r, theta, phi):
    """
    theta and phi must be in radians!!
    """
    eucl = np.zeros((len(theta),3))
    eucl[:,0] = np.sin(phi)*np.cos(theta)
    eucl[:,1] = np.sin(phi)*np.sin(theta)
    eucl[:,2] = np.cos(phi)
    return r*eucl
def euclideanToSpherical(pts):
    """
    returns answers in radians.
    """
    x = pts[:,0]
    y = pts[:,1]
    z = pts[:,2]
    phi=np.arccos(z)
    theta=np.arctan2(y,x)
    return phi, theta

def clusterEarthquakes(path):
    # load in the data
    latitudes, longitudes = loadEarthquakes(path)
    # convert to euclidean coordinates
    eucl = sphericalToEuclidean(1.,longitudes*np.pi/180,(90-latitudes)*np.pi/180)
    # cluster 10 times, retaining best
    best_i = np.inf
    best_m = None
    best_l = None
    for j in xrange(10):
        m,l,i = kmeans(eucl,15,normalize=True)
        if i < best_i:
            best_i=i
            best_m = m
            best_l = l
    # get means back into latitude and longitude coordinates
    m_phi, m_longitude = euclideanToSpherical(best_m)
    m_latitude = np.pi/2-m_phi
    return latitudes, longitudes, m_latitude, m_longitude, best_l

def plotClusters(lats, longs, m_lats, m_longs, labs):
    for i in xrange(15):
        msk=labs==i
        c=np.random.rand(3)
        plt.scatter(longs[msk],lats[msk],marker='.',
                    color=c)
    plt.scatter(m_longs*180/np.pi,m_lats*180/np.pi,marker='+',s=100,linewidths=2,color='k')
    plt.show()


import numpy as np
from numpy import linalg as LA
from scipy import optimize

a = np.array([[1, 0], [0, 2]])
b = np.array([1, -1])

def f(x):
    return (lambda x : np.matmul(np.matmul(np.dot(0.5, x.transpose()), a), x) - np.matmul(x.transpose(), b) + 7)(x)

def g(x):
    return (lambda x : np.matmul(a, x) - b)(x)

# only pass np values - no internal checking
def quad_alpha(x, d):
    return np.matmul(np.dot(-1, g(x)), d) / np.matmul(np.matmul(d.transpose(), a), d)

# rank one correction formula
def rank_one(x0, max, tol):
    # step 1
    k = 0
    x0 = np.array(x0)
    H = np.array([[1, 0], [0, 1]])

    while k < max:
        # step 2
        print(LA.norm(g(x0)))
        if LA.norm(g(x0)) < tol:
            return x0
        else:
            d = np.matmul(np.dot(-1, H), g(x0))

        # step 3
        alpha = quad_alpha(x0, d)
        delta_x = np.dot(alpha, d)
        x1 = x0 + delta_x

        # step 4
        delta_x = np.dot(alpha, d)
        delta_g = g(x1) - g(x0)

        num = np.matmul((delta_x - np.matmul(H, delta_g)), (delta_x - np.matmul(H, delta_g)).transpose())
        denom = np.matmul(delta_g.transpose(), (delta_x - np.matmul(H, delta_g)))

        H1 = H + (num / denom)
        # loop back
        H = H1
        x0 = x1
        k = k + 1

# # SciPy offers BFGS as part of its library
# print(optimize.fmin_bfgs(f, np.array([0, 0])), g)

# # NOTE: x must be entered as a 2-D array
# # there is a ZeroDivisionError increasing the error range
# print(rank_one([0, 0], 10, 0.5))


import numpy as np

# Funciton
def f(x):
    y = 1 / np.sqrt(2 * np.pi) * np.exp(-x ** 2 / 2)
    return y

# Adaptable Simpson Method
def adSimpson(a, b, tol):

    # Simpson Rule for 2 intervals
    def S(n, m):
        value = (m-n)/6 * (f(n) + f(m) + 4*f(0.5*(n + m)))
        return value

    # Compute Integral with Simpson Rule in left half and right hald interval
    Il, Ir = S(a, 0.5*(a+b)), S(0.5*(a+b), b)
    # Compute Integral wth Simpson Rule
    I = S(a, b)

    # Integral Error
    dI = np.abs((I - (Il+Ir)))/15

    # Check If We Get the Desired Tolerance
    if dI < tol:
        I = Il + Ir
    # If we Don't get it Reiterate
    else:
        Il = adSimpson(a, 0.5*(a+b), 0.5*tol)
        Ir = adSimpson(0.5*(a+b), b, 0.5*tol)

    I = Il + Ir

    return I

if __name__ == "__main__":
    print('\nComputed with Adaptable Simpson Method:')
    print(f'\tI = {adSimpson(-5, 5, 0.001):.8f}')


"""
Course: ME/MF F342 Computer Aided Design
Author: Bhavya Bhatia

Topic: Bsplines

Description:
-------------
The Bspline functions can take the degree and control vectors to return a set of coordinates which plot the actual bspline curve.
"""

import numpy as np
import scipy.interpolate as si

cv = np.array([[ 50.,  25.],
   [ 59.,  12.],
   [ 50.,  10.],
   [ 57.,   2.],
   [ 40.,   4.],
   [ 40.,   14.]])

def bspline(cv, n=100, degree=3, periodic=False):
    """
    Parameters :
    ------------
    cv : Array ov control vertices
    n  : Number of samples to return
    degree: Curve degree
    periodic: True - Curve is closed
              False - Curve is open
    Returns :
    ---------
    Returns array of x,y coordinates of the spline, which can be used to plot bspline graphs usin Matplotlib

    """

    # If periodic, extend the point array by count+degree+1
    cv = np.asarray(cv)
    count = len(cv)

    if periodic:
        factor, fraction = divmod(count+degree+1, count)
        cv = np.concatenate((cv,) * factor + (cv[:fraction],))
        count = len(cv)
        degree = np.clip(degree,1,degree)

    # If opened, prevent degree from exceeding count-1
    else:
        degree = np.clip(degree,1,count-1)

    # Calculate knot vector
    kv = None
    if periodic:
        kv = np.arange(0-degree,count+degree+degree-1,dtype='int')
    else:
        kv = np.concatenate(([0]*degree, np.arange(count-degree+1), [count-degree]*degree))

    # Calculate query range
    u = np.linspace(periodic,(count-degree),n)

    #print(u)

    # Calculate result
    return np.array(si.splev(u, (kv,cv.T,degree))).T

'''
Example :
---------

from gbot.bsplines import bspline
import matplotlib.pyplot as plt
import numpy as np

colors = ('b', 'g', 'r', 'c', 'm', 'y', 'k')

cv = np.array([[ 50.,  25.],
   [ 59.,  12.],
   [ 50.,  10.],
   [ 57.,   2.],
   [ 40.,   4.],
   [ 40.,   14.]])

plt.plot(cv[:,0],cv[:,1], 'o-', label='Control Points')

d = 4
p = bspline(cv,n=100,degree=4,periodic=False)
x,y = p.T
plt.plot(x,y,'k-',label='Degree %s'%d,color=colors[d%len(colors)])

plt.minorticks_on()
plt.legend()
plt.xlabel('x')
plt.ylabel('y')
plt.xlim(35, 70)
plt.ylim(0, 30)
plt.gca().set_aspect('equal', adjustable='box')
plt.show()

Out : Matplotlib figure
'''

########################################################################
########################################################################
# End of File


# Behnam Asadi
# http://ros-developer.com
# To see the function that we are working on visit:
# http://ros-developer.com/2017/05/07/gradient-descent-method-for-finding-the-minimum/
# or simply put the following latex code in a latex doc:
# $$ z= -( 4 \times e^{- ( (x-4)^2 +(y-4)^2 ) }+ 2 \times e^{- ( (x-2)^2 +(y-2)^2 ) } )$$

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

def objective_function(x,y):
    z=-( 4*np.exp(-(x-4)**2 - (y-4)**2)+2*np.exp(-(x-2)**2 - (y-2)**2) )
    return z

def f_prim(x,y):
    f_x=-( (-2)*(x-4)*4*np.exp(-(x-4)**2 - (y-4)**2)    +   (-2)*(x-2)*2*np.exp(-(x-2)**2 - (y-2)**2) )
    f_y=-( (-2)*(y-4)*4*np.exp(-(x-4)**2 - (y-4)**2)    +   (-2)*(y-2)*2*np.exp(-(x-2)**2 - (y-2)**2) )
    return [f_x,f_y]

x = np.linspace(-2,10,200)
y = np.linspace(-2,10,200)

X, Y = np.meshgrid(x,y)

Z=objective_function(X,Y)

#Make a 3D plot
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.plot_surface(X, Y, Z,linewidth=0,cmap='coolwarm')

ax.set_xlabel('X axis')
ax.set_ylabel('Y axis')
ax.set_zlabel('Z axis')

X_old=-2
Y_old=0

# The starts point for the algorithm:
X_new=4
Y_new=2.2

# step size
epsilon=0.1

# stop criteria
precision = 0.00001

x_path_to_max=[]
y_path_to_max=[]
z_path_to_max=[]

while np.sqrt( (X_new-X_old)**2 + (Y_new-Y_old)**2 ) > precision:
    X_old=X_new
    Y_old=Y_new

    #[X_new,Y_new]=f_prim(X_new,Y_new)
    #print f_prim(X_new,Y_new)
    x_path_to_max.append(X_new )
    y_path_to_max.append(Y_new )
    z=objective_function(X_new,Y_new)
    z_path_to_max.append(z)

    ret_val=f_prim(X_old,Y_old)
    X_new=X_old-epsilon*ret_val[0]
    Y_new=Y_old-epsilon*ret_val[1]
#    print X_new
#    print Y_new

line1=plt.plot(x_path_to_max,y_path_to_max,z_path_to_max)
plt.setp(line1,color='g',linewidth=0.5)

#print X_new
#print Y_new
plt.show()


import math
import sympy as sp
from sympy.abc import x

def lagrange_interpolation(t, y):
    """ Lagrange Interpolation Polinomial

    Args:
        t: list/ndarray, the interpolation points
        y: list/ndarray, the values of the function at every t

    Returns:
        L: symbol object, Lagrange interpolation Polinomial
    """
    # the number of the points
    n = len(t)

    # Lagrange interpolation polinomial
    L = 0
    for i in range(n):
        # the base function of Lagrange interpolation polinomial
        l = y[i]
        for j in range(n):
            if i == j:
                continue
            else:
                l *= (x - t[j])/(t[i] - t[j])
        L += l

    return sp.simplify(L)

if __name__ == '__main__':
    # the interpolation points(degrees)
    t = [11, 12, 13]
    # the interpolation points(radians)
    t = list(map(math.radians, t))
    y = [0.190809, 0.207912, 0.224951]
    L = lagrange_interpolation(t, y)
    print(f"The Lagrange interpolation polinomial is L = {L}")
    # calculate the value of sin11.5 by Lagrange interpolation
    x0_deg = 11.5
    x0_rad = math.radians(x0_deg)
    true_value = math.sin(x0_rad)
    pre_value = L.subs(x, x0_rad)
    print(f"The predict value of sin{x0_deg} is {pre_value:.7f}")
    print(f"The true value of sin{x0_deg} is {true_value:.7f}")
    print(f"The error is {math.fabs(true_value - pre_value):.7f}")


import numpy as np
import matplotlib.pyplot as plt

def logistic_map(r, x_0, n):
    final_x = x_0

    for step in range(n):
        final_x = (4 * r) * final_x * (1 - final_x)

    return final_x

def bifurcation_diagram(r_start, r_end, r_samples, sample_numbers, max_n):
    r_s = np.linspace(r_start, r_end, r_samples)
    x_0_s = np.random.rand(sample_numbers)
    x_r_s = np.zeros((r_samples, sample_numbers))

    for r_index, r in enumerate(r_s):
        x_r_s[r_index] = logistic_map(r, x_0_s, max_n)

    data = {
        'r_start': r_start,
        'r_end': r_end,
        'r_samples': r_samples,
        'sample_numbers': sample_numbers,
        'max_n': max_n,
        'x_r_s': x_r_s
    }
    file_name = f'data/q4_{r_start}_{r_end}_{r_samples}_{sample_numbers}_{max_n}.npy'
    np.save(file_name, data)

    return file_name

def show(file_name, fig_width = 10, fig_height=5, r_lim=None, x_lim=None, show_x_m=False, save=True):
    data = np.load('data/' + file_name, allow_pickle=True).tolist()
    r_start = data['r_start']
    r_end = data['r_end']
    r_samples = data['r_samples']
    sample_numbers = data['sample_numbers']
    max_n = data['max_n']
    x_r_s = data['x_r_s']

    r_lim_start = r_start if r_lim is None else r_lim[0]
    r_lim_end = r_end if r_lim is None else r_lim[1]
    r_lim_ratio = (r_lim_end - r_lim_start) / 1

    r_s = np.linspace(r_start, r_end, r_samples)
    ones = 4 * np.ones(sample_numbers)

    plt.figure(figsize=(fig_width, fig_height))
    for r_index, r in enumerate(r_s):
        plt.plot(r * ones, x_r_s[r_index], linestyle='', marker='.', color='black',
                 markersize=133 * fig_width / r_samples * r_lim_ratio)

    if show_x_m:
        plt.axhline(0.5)
    if r_lim is not None:
        plt.xlim(r_lim)
    if x_lim is not None:
        plt.ylim(x_lim)
    plt.xlabel(r'$r$')
    plt.ylabel(r'$x$')
    if save:
        plt.savefig(f'images/q4_{r_lim_start}_{r_lim_end}_{r_samples}_{sample_numbers}_{max_n}.jpg')
    plt.show()

if __name__ == "__main__":
    file_name = bifurcation_diagram(0, 1, 10000, 100, 10000)
    show(file_name=file_name, fig_height=5, fig_width=10)


import numpy as np
import matplotlib.pyplot as plt

# Primera parte

# Soluciones a los ejercicios de la seccion 6.2.5
# del libro A Survey of Computational Physics Introductory Computational Science
# de Landau, Paez, Bordeianu (Python Multimodal eTextBook Beta4.0)

#1. Write a double-precision program to integrate an arbitrary function numerically
# using the trapezoid rule, the Simpson rule, and Gaussian quadrature.
def integra(f, a, b, n_points=10, metodo="trapecio"):
    # Genera siempre un numero impar de puntos
    if n_points%2 == 0:
        n_points = n_points + 1

    if metodo=="trapecio":
        x = np.linspace(a, b, n_points)
        h = x[1] - x[0]
        w = np.ones(n_points) * h
        w[0] = h/2
        w[-1] = h/2
    elif metodo=="simpson":
        x = np.linspace(a, b, n_points)
        h = x[1] - x[0]
        w = np.ones(n_points)
        ii = np.arange(n_points)
        w[ii%2!=0] = 4.0*h/3.0
        w[ii%2==0] = 2.0*h/3.0
        w[0] = h/3
        w[-1] = h/3
    elif metodo=="cuadratura":
        y, wprime = np.polynomial.legendre.leggauss(n_points)
        x = 0.5*(b+a) + 0.5*(b-a)*y
        w = 0.5*(b-a)*wprime
    else:
        print('metodo no implementado')
        x = np.zeros(n_points)
        y = np.zeros(n_points)

    return np.sum(f(x)*w)

def func(x):
    return np.sin(x)

def error(x):
    return np.abs(2-x)/2

# 2 Compute the relative error (epsilon=abs(numerical-exact)/exact) in each case.
# Present your data in tabular form for N=2,10,20,40,80,160

N = [2,10,20,40,80,160]
print("Primera Parte")
out = open("tabla_resultados.dat", "w")
print("# N\t e_T\t e_S \t e_G")
for n_points in N:
    a = integra(func, 0, np.pi, n_points=n_points, metodo="trapecio")
    b = integra(func, 0, np.pi, n_points=n_points, metodo="simpson")
    c = integra(func, 0, np.pi, n_points=n_points, metodo="cuadratura")
    print("{:d}\t {:.1e} {:.1e} {:.1e}".format(n_points, error(a), error(b), error(c)))
    out.write("{:d}\t {:.1e} {:.1e} {:.1e}\n".format(n_points, error(a), error(b), error(c)))
out.close()
print("")

# 3 Make a log-log plot of relative error versus N
data = np.loadtxt("tabla_resultados.dat")
plt.figure()
plt.plot(data[:,0], data[:,1], label="Trapecio")
plt.plot(data[:,0], data[:,2], label="Simpson")
plt.plot(data[:,0], data[:,3], label="Cuadratura")

plt.xlabel('N')
plt.ylabel('|error|')
plt.loglog()
plt.legend()
plt.savefig("loglogplot.png")

# 4. Use your plot or table to estimate the power-law dependence of the error on N and
# to determine the nuber of decimal places of precision.

for i,m in zip([1,2,3],["Trapecio", "Simpson", "Cuadratura"]):
    power_law = (np.log(data[2,i]) - np.log(data[0,i]))/(np.log(data[2,0]) - np.log(data[0,0]))
    decimal_places = -np.log10(data[-1,i])
    print("Metodo {}".format(m))
    print("\t Power Law: {:.1f}".format(power_law))
    print("\t Decimal Places: {:d}".format(int(decimal_places)))

print("")

# Segunda parte.
# Calcule la integral de la función Gamma (https://en.wikipedia.org/wiki/Gamma_function) para z>1. Imprima los resultados Gamma(2), Gamma(3) y Gamma(4).

def gamma(z, n_points=20):
    def fun(z, x):
        return x**(z-1) * np.exp(-x)
    # Usando la formula de transformacion (6.36) del libro.
    y, wprime = np.polynomial.legendre.leggauss(n_points)
    x = (1+y)/(1-y)
    w = wprime * 2/(1-y)**2
    return np.sum(fun(z,x)*w)
print("Segunda Parte")
print("Gamma(2): {}\nGamma(3): {}\nGamma(4): {}\t".format(gamma(2), gamma(3), gamma(4)))

def gamma2(z, n_points=20):
    # Usando el cambio de variable u = exp(-x)
    def fun(z,u):
        return (-np.log(u))**(z-1)    # diverge para u=0
    a = 0.0
    b = 1.0
    y, wprime = np.polynomial.legendre.leggauss(n_points)
    x = 0.5*(b+a) + 0.5*(b-a)*y
    w = 0.5*(b-a)*wprime
    return np.sum(fun(z,x)*w)
print("Segunda Parte")
print("Gamma(2): {}\nGamma(3): {}\nGamma(4): {}\t".format(gamma2(2), gamma2(3), gamma2(4)))


#!/usr/bin/env python3
"""
Clustering Module
"""
import numpy as np
kmeans = __import__('1-kmeans').kmeans
variance = __import__('2-variance').variance

def optimum_k(X, kmin=1, kmax=None, iterations=1000):
    """
    Tests for the optimum number of clusters by variance:

    X is a numpy.ndarray of shape (n, d) containing the data set
    kmin is a positive integer containing the minimum number of clusters to
    check for (inclusive)
    kmax is a positive integer containing the maximum number of clusters to
    check for (inclusive)
    iterations is a positive integer containing the maximum number of
    iterations for K-means
    This function should analyze at least 2 different cluster sizes
    You may use at most 2 loops

    Returns: results, d_vars, or None, None on failure
        results is a list containing the outputs of K-means for each cluster
        size
        d_vars is a list containing the difference in variance from the
        smallest cluster size for each cluster size
    """
    if type(X) is not np.ndarray or len(X.shape) != 2:
        return (None, None)
    if type(kmin) is not int:
        return (None, None)
    if type(iterations) is not int:
        return (None, None)
    if kmax is not None and type(kmax) is not int:
        return (None, None)
    n, _ = X.shape
    if kmax is None:
        kmax = n
    if kmin <= 0 or kmax <= 0 or iterations <= 0:
        return (None, None)
    if kmin >= kmax:
        return (None, None)

    d_vars = []
    results = []
    for i in range(kmin, kmax + 1):
        center, klss = kmeans(X, i, iterations)
        results.append((center, klss))
        if i == kmin:
            kmin_var = variance(X, center)
        cvar = variance(X, center)
        d_vars.append(kmin_var - cvar)
    return (results, d_vars)


"""Utilities functions for prime numbers."""
import unittest
from numpy import sqrt

def is_prime(n):
    """Checks if n is prime (pretty naive algo)"""
    if n < 2:
        return False
    elif not n%2:
        return n == 2
    for i in range(3, int(sqrt(n))+1, 2):
        if not n%i:
            return False
    return True

def find_prime_factors(n):
    """Return prime factors of n"""
    p_factors = []
    current = n
    test = 2
    while test <= current:
        if not current%test:
            p_factors.append(test)
            current = current // test
            test = 2
        else:
            test += 1
    return p_factors

def find_prime_factors_table(n):
    """Returns prime factors and powers in table form"""
    table = {}
    for pf in find_prime_factors(n):
        if pf in table:
            table[pf] += 1
        else:
            table[pf] = 1
    return table

def seive_of_erat(n):
    """Generates all primes up to n using Seive of Erat"""
    marked = [1]*(n-1)
    for i in range(2,int(sqrt(n)+1)):
        if marked[i-2]:
            j = i*i
            while j <= n:
                marked[j-2] = 0
                j += i
    return [i for i in range(2,n) if marked[i-2]]

class TestPrimes(unittest.TestCase):
    def test_is_prime(self):
        self.assertEqual(is_prime(0), False)
        self.assertEqual(is_prime(1), False)
        self.assertEqual(is_prime(2), True)
        self.assertEqual(is_prime(17), True)
        self.assertEqual(is_prime(253), False)
        self.assertEqual(is_prime(15485867), True)

    def test_prime_factors(self):
        self.assertEqual(find_prime_factors(2), [2])
        self.assertEqual(find_prime_factors(10), [2, 5])
        self.assertEqual(find_prime_factors(13195), [5, 7, 13, 29])

    def test_prime_factors_table(self):
        self.assertEqual(find_prime_factors_table(2), {2: 1})
        self.assertEqual(find_prime_factors_table(10), {2: 1, 5: 1})
        self.assertEqual(find_prime_factors_table(8), {2: 3})
        self.assertEqual(find_prime_factors_table(1800), {2: 3, 3: 2, 5:2})

    def test_seive(self):
        self.assertEqual(seive_of_erat(2), [])
        self.assertEqual(seive_of_erat(4), [2, 3])
        self.assertEqual(seive_of_erat(10), [2, 3, 5, 7])

if __name__ == "__main__":
    unittest.main()


from fractions import Fraction
import numpy as np

# These functions are written based on the exercises in
# Xanadu Quantum Codebook nodes S.3 and S.4
# https://codebook.xanadu.ai

def fractional_binary_to_float(sample):
    """Convert an n-bit sample [k1, k2, ..., kn] to a floating point
    value using fractional binary representation,

        k = (k1 / 2) + (k2 / 2 ** 2) + ... + (kn / 2 ** n)

    Args:
        sample (list[int] or array[int]): A list or array of bits, e.g.,
            the sample output of quantum circuit.

    Returns:
        float: The floating point value corresponding computed from the
        fractional binary representation.
    """
    return np.sum(
        [int(sample[bit]) / 2 ** (bit + 1) for bit in range(len(sample))]
    )

def phase_to_order(phase, max_denominator):
    """Estimating which integer values divide to produce a float.

    Given some floating-point phase, estimate integers s, r such
    that s / r = phase, where r is no greater than some specified value.

    Args:
        phase (float): Some fractional value (here, will be the output
            of running QPE).
        max_denominator (int): The largest r to be considered when looking
            for s, r such that s / r = phase.

    Returns:
        int: The estimated value of r.
    """
    s_over_r = Fraction(phase)
    return s_over_r.limit_denominator(max_denominator).denominator

def get_U_Na(N, a):
    """Computes the unitary matrix U_(N, a) used in the order-finding
    portion of Shor's algorithm.

    U_(N, a) multiples a computational basis state by a modulo N, i.e.,
        U_(N, a) |k> = |ak mod N>

    In Shor's algorithm, we try to find its order, i.e., the smallest
    m such that
        U_(N, a)^m |k> = |k mod N> = |k>

    Args:
        N (int): The modulus. In Shor's algorithm, this is the number
            we are trying to find the prime factors of.
        a (int): The candidate value a which we are testing to try and
            find a non-trivial square root (which will then allow us to
            recover the prime factors of N).

    Returns:
        array[int]: The matrix representation U_(N, a).
    """
    # Compute size of the matrix; we need at least log2(N) qubits
    # because we are looking at computational basis states modulo N
    n_qubits = int(np.ceil(np.log2(N)))

    U_Na = np.zeros([2 ** n_qubits, 2 ** n_qubits])

    # U_Na is a permutation matrix; for each k < N, need to compute
    # |l> = |a k mod N>, and then set the value of U_Na[l, k] = 1
    for k in range(N):
        U_Na[(k * a) % N, k] = 1

    # We might have more basis states than we need, if N < 2 ** n_qubits
    # so we set the remaining rows to identity rows
    for extra in range(N, 2 ** n_qubits):
        U_Na[extra, extra] = 1

    return U_Na


# -*- coding: UTF-8 -*-
from __future__ import division

import numpy as np

x = np.array([1, 2, 3])
y = np.array([4, 5, 6])

print(x + y)  # elementwise addition     [1 2 3] + [4 5 6] = [5  7  9]
print(x - y)  # elementwise subtraction  [1 2 3] - [4 5 6] = [-3 -3 -3]
print(x * y)  # elementwise multiplication  [1 2 3] * [4 5 6] = [4  10  18]
print(x / y)  # elementwise divison         [1 2 3] / [4 5 6] = [0.25  0.4  0.5]
print(x ** 2)  # elementwise power  [1 2 3] ^2 =  [1 4 9]

#  Dot Product
print(x.dot(y))  # dot product  1*4 + 2*5 + 3*6 = 32

# T
z = np.array([y, y ** 2])
print(len(z))  # number of rows of array # 2
print(z)
# [[ 4  5  6]
#  [16 25 36]]
print(z.shape)  # (2L, 3L)
print(z.T)
# [[ 4 16]
#  [ 5 25]
#  [ 6 36]]
print(z.T.shape)  # (3L, 2L)

# dtype
print(z.dtype)  # int32
# convert data type to float
z = z.astype('f')
print(z.dtype)  # float32


import random

import numpy as np
import matplotlib.pyplot as plt

phi = lambda vec: np.sin(np.product(vec))
h = lambda x: np.sqrt(1 + np.sin(np.product(x)) ** 2)

# Section 1.1.5 - Analytical Evaluation

# SubSection 1
def gradient_phi(vec: np.matrix):
    grad_vec = np.matrix(np.array([[vec[1] * vec[2]], [vec[0] * vec[2]], [vec[0] * vec[1]]]), dtype=np.float128)
    grad_vec = grad_vec.transpose()
    return np.cos(np.product(vec)) * grad_vec

def hessian_phi(mat: np.matrix, vec: np.matrix):
    nabla = np.zeros((3, 3))
    nabla[0, 0] = (-((vec[1] * vec[2]) ** 2) * np.sin(np.product(vec)))
    nabla[0, 1] = vec[2] * np.cos(np.product(vec)) - np.product(vec) * vec[2] * np.sin(np.product(vec))
    nabla[0, 2] = vec[1] * np.cos(np.product(vec)) - np.product(vec) * vec[1] * np.sin(np.product(vec))
    nabla[1, 0] = vec[2] * np.cos(np.product(vec)) - np.product(vec) * vec[2] * np.sin(np.product(vec))
    nabla[1, 1] = (-((vec[0] * vec[2]) ** 2) * np.sin(np.product(vec)))
    nabla[1, 2] = vec[0] * np.cos(np.product(vec)) - np.product(vec) * vec[0] * np.sin(np.product(vec))
    nabla[2, 0] = vec[1] * np.cos(np.product(vec)) - np.product(vec) * vec[1] * np.sin(np.product(vec))
    nabla[2, 1] = vec[0] * np.cos(np.product(vec)) - np.product(vec) * vec[0] * np.sin(np.product(vec))
    nabla[2, 2] = (-((vec[0] * vec[1]) ** 2) * np.sin(np.product(vec)))
    return mat.transpose() * nabla * mat

# SubSection 2
def gradient_h(vec: np.matrix):
    return 0.5 * phi(vec) / ((1 + (phi(vec) ** 2)) ** 0.5) \
           * gradient_phi(vec)

def hessian_h(vec: np.matrix):
    # Calculating the 2nd derivative of h of phi
    derivative = (np.cos(np.product(vec)) ** 2 -
                  (1 + np.sin(np.product(vec)) ** 2) * np.sin(np.product(vec)) ** 2) / \
                 (1 + np.sin(np.product(vec)) ** 2) ** 1.5

    return derivative * hessian_phi(np.matrix(np.identity(3)), vec)

# Section 1.2.1 - Numerical Differentiation

def numerical_diff_gradient(func, vector: np.matrix, epsilon):
    vec_len = vector.shape[0]
    assert vector.shape[1] == 1
    assert vec_len > 0
    gradient = np.matrix(np.zeros((vec_len, 1)), dtype=np.float128)
    for i in range(vec_len):
        base_vector = np.matrix(np.zeros((vec_len, 1)), dtype=np.float128)
        base_vector[i, 0] = 1
        func_plus = func(vector + (epsilon * base_vector))
        func_minus = func(vector - (epsilon * base_vector))
        gradient[i, 0] = ((func_plus - func_minus) / (2 * epsilon))
    return gradient

def numerical_diff_hessian(vec: np.matrix, epsilon, hess_phi=False, hess_h=False,):
    vec_len = vec.shape[0]
    assert vec.shape[1] == 1
    assert vec_len > 0
    hessian = np.matrix(np.zeros((vec_len, vec_len)), dtype=np.float128)
    for i in range(vec_len):
        base_vector = np.matrix(np.zeros((vec_len, 1)))
        base_vector[i, 0] = 1
        if hess_phi is True:
            v1 = gradient_phi(vec + epsilon * base_vector)
            v2 = gradient_phi(vec - epsilon * base_vector)
        elif hess_h is True:
            v1 = gradient_h(vec + epsilon * base_vector)
            v2 = gradient_h(vec - epsilon * base_vector)
        else:
            return None
        value = (v1 - v2)
        hessian[0:vec_len, i] = value / (2 * epsilon)
    return hessian

# Section 1.3 - Comparison plot
def compare_grad():
    vec = np.matrix(np.random.rand(3), dtype=np.float128).transpose()
    A = np.matrix(np.random.rand(3, 3), dtype=np.float128)
    epsilon = []
    values = []
    for i in range(61):
        epsilon.append(2 ** -i)

    # Comparison for f1 gradient
    analytical_grad = gradient_phi(A * vec)
    for i in range(61):
        numerical_grad = numerical_diff_gradient(phi, A * vec, epsilon[i])
        x = np.abs(analytical_grad - numerical_grad)
        values.append(np.linalg.norm(x, np.inf))
    show_plot("f1 gradient", values)

    # Comparison for f1 hessian
    analytical_hessian = hessian_phi(A, vec)
    values = []
    for i in range(61):
        numerical_hessian = numerical_diff_hessian(A * vec, epsilon[i], hess_phi=True)
        x = np.abs(analytical_hessian - numerical_hessian)
        values.append(np.linalg.norm(x, np.inf))
    show_plot("f1 hessian", values)

    # Comparison for f2
    analytical_grad = gradient_h(vec)
    values = []
    for i in range(61):
        numerical_grad = numerical_diff_gradient(h, vec, epsilon[i])
        x = np.abs(analytical_grad - numerical_grad)
        y = max(x).item()
        values.append(y)
    show_plot("f2 gradient", values)

def show_plot(str, y_axis):
    fig = plt.figure()
    ax = fig.add_subplot()
    fig.subplots_adjust(top=0.85)
    fig.suptitle(str, fontsize=14, fontweight='bold')
    ax.set_xlabel('epsilon')
    ax.set_ylabel('differentiation')
    plt.plot(list(range(61)), y_axis)
    plt.xscale("linear")
    plt.yscale("log")
    plt.show()

compare_grad()


"""
demo_constrained_optimization_nonlinear_equality_constraints.py

Minimize a (quadratic) function of two variables:
Minimize f(X), where X = [x0, x1].T;
X* is MIN;

m = number of equality constraints;
n = number of parameters;

Objective function:
    Minimize a (quadratic) function of two variables: f(X);
    X = [x1, ..., xn].T is vector of variables;

Box constraints:
    X_lower = [x1_lower, ..., xn_lower].T; lower bounds for X;
    X_upper = [x1_upper, ..., xn_upper].T; upper bounds for X;

(Non)linear equality constraints:
    Subject to (non)linear equality constraints: h(X);
    h(X) = [h1(X), ..., hn(X)].T is vector of constraints;
    m < n;

First order Lagrange algorithm

Lagrangian function:
l(X, Lambda) = f(X) + Lambda.T @ h(X);
Lambda = [lambda1, ..., lambdan].T is vector of langrange multipliers;

Update equation for X:
    X = X_old + alpha * d;
    alpha is step size (<<1);
    d is direction:
        d = - (Df(X_old) + Dh(X_old).T @ Lambda);

Update equation for Lambda:
    Lambda = Lambda_old + beta * d;
    beta is step size (<<1);
    d is direction:
        d = h(X_old);

If alpha and beta are small enough, the algorithm will converge to a fixed point, which satisfies the Lagrange condition.
"""

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FormatStrFormatter
from mpl_toolkits.mplot3d import Axes3D
from first_order_lagrangian_algorithm import first_order_lagrangian_algorithm
from print_report import print_report
from plot_progress_y import plot_progress_y
from plot_progress_x import plot_progress_x
import time

reg_coeff =10; # regularization coefficient
# Minimize the following objective function:
# X = [x0, x1].T;
# f(X) = 1 / 2 * X.T @ Q @ X + X.T @ B + C + reg_coeff * X.T @ X;
Q = np.array([[8, 2 * np.sqrt(2)], [2 * np.sqrt(2), 10]]);
B = np.array([[3], [6]]);
C = 24;
func = lambda X : 1 / 2 * X.T @ Q @ X + X.T @ B + C + reg_coeff * X.T @ X;
Dy = lambda X : Q @ X + B + 2 * reg_coeff * X;

# Box constraints:
X_lower = np.array([[-20], [-20]]); # X lower bound
X_upper = np.array([[20], [20]]); # X upper bound

# (Non)linear equality constraints:
# h(X) = x1 * x2 - 20 = 0
h = lambda X : X[0] * X[1] - 20;
Dh = lambda X : np.array([X[1], X[0]]);
# Initial guess (subject to the equality constraints):
X0 = np.array([[-50], [-0.4]]);
Lambda0 = np.array([[0]]);

fig = plt.figure();
X_data = np.arange(-100, 100, 5);
Y_data = np.arange(-100, 100, 5);
Z_data = np.zeros(shape = (X_data.size,Y_data.size));
for iX in range (X_data.size):
    for iY in range (Y_data.size):
        Z_data[iX][iY] = func(np.array([X_data[iX], Y_data[iY]]));
X_data, Y_data = np.meshgrid(X_data, Y_data);

# Plot the surface
ax = fig.add_subplot(1, 2, 1, projection ='3d')
surf = ax.plot_surface(X_data, Y_data, Z_data, cmap = cm.coolwarm,
                       linewidth = 0, antialiased = False)
ax.set_xlabel('X')
ax.set_xlim(-100, 100)
ax.set_ylabel('Y')
ax.set_ylim(-100, 100)
plt.title('Objective function: surface plot')

# Plot the contour
ax = fig.add_subplot(1, 2, 2)
cset = ax.contour(X_data, Y_data, Z_data, 50, cmap = cm.coolwarm);
ax.set_xlabel('X')
ax.set_xlim(-100, 100)
ax.set_ylabel('Y')
ax.set_ylim(-100, 100)
plt.title('Objective function: contour plot');
plt.show();

# First order Lagrangian algorithm
print('***********************************************************************');
print('First order Lagrangian algorithm');
N_iter_max = 10000;
tolerance_x = 10e-6;
tolerance_y = 10e-6;
options = {'tolerance_x' : tolerance_x, 'tolerance_y' : tolerance_y, 'N_iter_max' : N_iter_max, 'x_lower' : X_lower, 'x_upper' : X_upper};
start = time.time();
X, report = first_order_lagrangian_algorithm(X0, Lambda0, func, Dy, Dh, h, options);
end = time.time();
print_report(func, report);
# Plot path to X* for Y
algorithm_name = 'First order Lagrangian algorithm';
plot_progress_y(algorithm_name, report);
# Plot path to X* for X
plot_progress_x(X_data, Y_data, Z_data, algorithm_name, report);
print('Elapsed time [s]: %0.5f' % (end - start));
print('***********************************************************************\n');


# -*- coding: utf-8 -*-
#
#  Activate Function Model
#
#  @auth whuang022ai
#

import math
import numpy as np
from abc import ABCMeta, abstractmethod
import matplotlib.pyplot as plt
from enum import Enum

class Activate_Function_Type(Enum):

    sigmoid = 'sigmoid'
    tanh = 'tanh'
    relu = 'relu'
    leaky_relu = 'leaky-relu'
    identity = 'identity'

class Activate_Function(metaclass=ABCMeta):

    @abstractmethod
    def f(self, x):
        pass

    @abstractmethod
    def df(self, y):
        pass

    def plot_fx_dfx(self, min, max, inter, title):

        x = np.arange(min, max, inter)
        f_vect = np.vectorize(self.f)
        df_vect = np.vectorize(self.df)
        plt.figure(title)
        plt.subplot(211)
        plt.ylabel('f(x)')
        plt.plot(x, f_vect(x))
        plt.grid(True)
        plt.subplot(212)
        plt.ylabel('d f(x)')
        plt.plot(x, df_vect(f_vect(x)), color='red',
                 linewidth=1.0, linestyle='--')
        plt.grid(True)
        plt.show()

    @abstractmethod
    def show_plot(self):
        pass

class Activate_Function_Sigmoid(Activate_Function):
    # ref: https://timvieira.github.io/blog/post/2014/02/11/exp-normalize-trick/
    def f(self,x):

        if x >= 0:
            z = np.exp(-x)
            return 1 / (1 + z)
        else:
            z = np.exp(x)
            return z / (1 + z)

    def df(self, y):
        return y*(1-y)

    def show_plot(self):
        self.plot_fx_dfx(-5.0, 5.0, 0.1, 'The Sigmoid Function')

class Activate_Function_Tanh(Activate_Function):

    def f(self, x):
        return np.tanh(x)

    def df(self, y):
        return 1-y**2

    def show_plot(self):
        self.plot_fx_dfx(-5.0, 5.0, 0.1, 'The Tanh Function')

class Activate_Function_Relu(Activate_Function):

    def __init__(self):
        self.lrelu = Activate_Function_LeakyRelu()
        self.lrelu.alpha = 0.0

    def f(self, x):
        return self.lrelu.f(x)

    def df(self, y):
        return self.lrelu.df(y)

    def show_plot(self):
        self.plot_fx_dfx(-5.0, 5.0, 0.01, 'The ReLU Function')

class Activate_Function_LeakyRelu(Activate_Function):

    def __init__(self):
        self.alpha = 0.1

    def f(self, x):
        if x > 0:
            return x
        else:
            return (self.alpha*x)

    def df(self, y):
        if y > 0:
            return 1.0
        else:
            return self.alpha

    def show_plot(self):
        self.plot_fx_dfx(-5.0, 5.0, 0.01, 'The Leaky ReLU Function')

class Activate_Function_Identity(Activate_Function):

    def f(self, x):
        return x

    def df(self, y):
        return 1.0

    def show_plot(self):
        self.plot_fx_dfx(-5.0, 5.0, 0.01, 'The Identity Function')

class Activate_Function_Generator():

    def __init__(self, name):

        if isinstance(name, Activate_Function_Type):
            name = str(name.value)
            self.__genfromname(name)
        else:
            self.__genfromname(name)

    def __genfromname(self, name):

        if name == 'sigmoid':
            self.get = Activate_Function_Sigmoid()
        elif name == 'tanh':
            self.get = Activate_Function_Tanh()
        elif name == 'relu':
            self.get = Activate_Function_Relu()
        elif name == 'leaky-relu':
            self.get = Activate_Function_LeakyRelu()
        elif name == 'identity':
            self.get = Activate_Function_Identity()


import numpy as np
import matplotlib.pyplot as plt
#
# test the fourier transform and inverse transform in the numpy.fft library
# define a simple function, compute and plot its fft,
# then compute and plot the inverse fft of the fft
# this is a good way to test the fft normalization
#
pathout1='/Users/riser/Desktop/ocean.569A/fft.test.out.jpg'
pathout2='/Users/riser/Desktop/ocean.569A/inverse.fft.test.out.jpg'
#
# set the number of points and number of frequencies
#
nn=1000
mm=int(nn/2+1)
#
# define the arrays
#
tt0=np.zeros(nn)
tt1=np.zeros(nn)
yy0=np.zeros(nn)
yy1=np.zeros(nn)
zz=np.zeros(mm)
freq_rad=np.zeros(mm)
freq=np.zeros(mm)
#
# define some constants
#
pi=np.pi
amp=1.0
n_fac=50.
omega0=2.*pi/nn
#
# set the time and amplitude coordinates
#
for i in range(0,nn):
    tt0[i]=i # t
    yy0[i]=amp*np.sin(2.*pi*tt0[i]/n_fac)    #y_sin
#
# set the frequency values
#
for i in range(0,mm):
    freq_rad[i]=i*omega0
    freq[i]=freq_rad[i]/(2.*pi)
#
# carry out the fourier transform
# convert from real-imaginary parts to amplitude and phase
#
zz=np.fft.rfft(yy0,n=nn)/nn
zz_real=zz.real
zz_imag=zz.imag
zz_mag=np.sqrt(zz_real**2+zz_imag**2)
zz_phase=np.arctan(zz_imag/zz_real)
#
# plot the magnitude of the transform as a function of frequency
#
sz=15
fig=plt.figure(figsize=(10,5))
plt.xlim([0,0.5])
plt.ylim([0,1])
plt.plot(freq_rad,zz_mag,color='firebrick')
plt.plot([0,pi],[0.5,0.5],'--k')
plt.plot([0.12566,0.12566],[0,1],'--k')
plt.xlabel('$\omega$ (cycles)  (continues to $\pi$)',fontsize=sz)
plt.ylabel('Fourier coefficient magnitude (FFT/$\it{n}$)',fontsize=0.85*sz)
plt.text(0.132,0.55,'$\omega$=0.12566, $\it{f}$=0.02')
plt.grid(color='blue')
plt.title('FFT of sin(2$\pi$$\it{t}$/50)')
#plt.savefig(pathout1)
plt.show()
#
# now test the inverse transform
#
yy1=nn*np.fft.irfft(zz,n=nn)
#
fig=plt.figure(figsize=(10,5))
plt.xlim([0,1000])
plt.ylim([-1,1])
plt.plot(yy1)
plt.plot([0,1000],[0,0],'--m')
per=50.
plt.plot([per,per],[-1,1],'--k')
plt.xlabel('Time',fontsize=sz)
plt.ylabel('Amplitude',fontsize=sz)
plt.text(35,-1.12,'50',fontsize=15)
plt.title('Inverse FFT of [FFT of sin(2$\pi$$\it{t}$/50)]')
#plt.savefig(pathout2)
plt.show()


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

# Exercise 1

A = np.array([[0.3,0.6,0.1],[0.5,0.2,0.3],[0.4,0.1,0.5]])
v = np.array([1/3,1/3,1/3])

v_dist = []
v_next = v
for i in range(1,25):
    v_current = v_next
    v_next = v_next*A
    v_dist.append(np.linalg.norm(v_next-v_current))

plt.plot(v_dist)
plt.show

# Exercise 2

N = 1000
Ys = np.random.randn(10000,N)
Ysum = np.sum(Ys, axis=1)
plt.hist(Ysum, bins=100)
plt.show()

Ysum.mean()
Ysum.var()

# Exercise 3

df = pd.read_csv("train.csv")

digit = 7
D = df[df['label'] == digit]
D_mean = D.mean().values
im = D_mean[1:]
im = im.reshape(28,28)
plt.imshow(im, cmap="gray")
plt.show()

# Exercise 4

im90 = np.rot90(im,3)
plt.imshow(im90, cmap="gray")
plt.show()

# Exercise 5

def is_symmetric(M):
    return np.abs(M-M.T).sum() == 0

M1 = np.array([[1,2],[3,4]])
M2 = np.array([[1,2],[2,1]])

is_symmetric(M1)
is_symmetric(M2)

# Exercise 6

XOR = np.random.random((5000,2))*2-1
z = (np.sign(XOR[:,0]*XOR[:,1])+1)/2
z = np.expand_dims(z, axis=0).T
XOR = np.append(XOR, z, axis=1)
plt.scatter(XOR[:,0], XOR[:,1], c=XOR[:,2], cmap=plt.cm.RdBu, alpha=0.5)
plt.axis('equal')
plt.show()

# Exercise 7

N = 2000
C = np.random.random((N,2))
s = np.sign(C[:,1]-0.5)
r0 = np.random.randn(N)
r = (s-3)/2*10+r0
x = np.cos(C[:,0]*2*np.pi)*r
y = np.sin(C[:,0]*2*np.pi)*r
plt.scatter(x, y, c=s, cmap=plt.cm.RdBu, alpha=0.5)
plt.axis('equal')
plt.show()

# Exercise 8

N = 2000
t6 = np.random.randint(6, size=N)
s = np.mod(t6,2)
t = 0.5+np.random.random(N)*2+np.random.randn(N)*0.1
r = t
a = (t+t6)*np.pi/3
r0 = r+np.random.randn(N)*0.1
x = np.cos(a)*r0
y = np.sin(a)*r0
plt.scatter(x, y, c=s, cmap=plt.cm.RdBu, alpha=0.5)
plt.axis('equal')
plt.show()

df = pd.DataFrame(data={'x1': x, 'x2': y, 'y': s})
df.to_csv('spiral.csv', index=False)


import math
import itertools
import numpy as np
import pandas as pd
from typing import Dict
from IPython.display import display

def cg_coefficient(
    j1, j2,
    m1, m2,
    j, m
):
    '''
    Compute the Clebsch-Gordan coefficient :math:`\langle j_1, j_2; m_1, m_2 | j_1, j_2; j, m \rangle`.
    References:
        - Section 3.8 of J. J. Sakurai and Jim Napolitano, "Quantum Mechanics", 2nd ed., Cambridge, 2017
        - https://en.wikipedia.org/wiki/Table_of_Clebsch–Gordan_coefficients
    '''

    if m != m1 + m2:
        return 0
    if j < abs(j1 - j2):
        return 0
    if j > j1 + j2:
        return 0

    # coefficient outside of the summation
    numerator = 2 * j + 1
    try:
        numerator *= math.factorial(j + j1 - j2)
        numerator *= math.factorial(j - j1 + j2)
        numerator *= math.factorial(j1 + j2 - j)
        numerator *= math.factorial(j + m)
        numerator *= math.factorial(j - m)
        numerator *= math.factorial(j1 - m1)
        numerator *= math.factorial(j1 + m1)
        numerator *= math.factorial(j2 - m2)
        numerator *= math.factorial(j2 + m2)
    except ValueError:  # invalid j1, j2, m1, m2, j, or m -> 0
        return 0

    denominator = math.factorial(j1 + j2 + j + 1)

    c = math.sqrt(numerator / denominator)

    # summation
    summation = 0
    # ranges of k
    k_min = max(0, -(j - j2 + m1), -(j - j1 - m2))
    k_max = min(j1 + j2 - j, j1 - m1, j2 + m2)
    k_max = max(0, k_max)  # avoid negative k's
    k_list = np.linspace(k_min, k_max, int(k_max - k_min + 1))

    for k in k_list:
        numerator = (-1) ** k

        denominator = math.factorial(k)
        denominator *= math.factorial(j1 + j2 - j - k)
        denominator *= math.factorial(j1 - m1 - k)
        denominator *= math.factorial(j2 + m2 - k)
        denominator *= math.factorial(j - j2 + m1 + k)
        denominator *= math.factorial(j - j1 - m2 + k)

        summation += numerator / denominator

    return c * summation

def cg_table(j1, j2, m) -> pd.DataFrame:
    '''
    Returns the Clebsch-Gordan table given :math:`j_1, j_2, m`.
    '''
    table = dict()
    m1_list = np.linspace(-j1, j1, int(2*j1 + 1))
    m2_list = np.linspace(-j2, j2, int(2*j2 + 1))
    j_list = list(set(
        abs(m1 + m2)
        for m1, m2 in list(itertools.product(m1_list, m2_list))
        if abs(m1 + m2) >= abs(m)  # constraints
    ))

    for m1, m2 in list(itertools.product(m1_list, m2_list)):
        if m1 + m2 != m:
            continue
        key = (m1, m2)
        value = []
        for j in j_list:
            coefficient = cg_coefficient(
                j1=j1, j2=j2,
                m=m1+m2,
                j=j,
                m1=m1, m2=m2,
            )
            value.append(coefficient)
        table[key] = value

    df = pd.DataFrame.from_dict(table, orient='index', columns=j_list)
    df.index.name = '(m1, m2)'
    df.columns.name = 'j'
    return df

def cg_matrix(j1, j2, m, return_indices=False):
    '''
    Returns the Clebsch-Gordan matrix given :math:`j_1, j_2, m` in matrix form.
    Returns row indices (`m1, m2`) and column indices (`j`) if `return_indices` is `True`.
    '''
    table = cg_table(j1, j2, m)

    if return_indices:
        return table.to_numpy(), table.index.to_numpy(), table.columns.to_numpy()

    return table.to_numpy()

def cg_tables_all_m(j1, j2, display_tables=False) -> Dict:
    '''
    Returns a dictionary of CClebsch-Gordan table given :math:`j_1, j_2`
    with all possible :math:`m`.
    '''
    tables = dict()
    m_max = j1 + j2
    m_min = - m_max
    m_list = np.linspace(m_min, m_max, int((m_max - m_min) + 1))

    for m in m_list:
        tables[m] = cg_table(j1, j2, m)

    if display_tables:
        print(f'{j1 = }, {j2 = }')
        for m, table in tables.items():
            print(f'{m = }:')
            display(table)

    return tables


# Problem: https://projecteuler.net/problem=323

import numpy as np

"""
    T: transistion matrix where,
        T[i][j] is the probability of (a state where i bits are set) transition to (a state where j bits are set).
        We have:
            T[i][j] = C(N-i, j-i) * 2^i / 2^N
        Why?
            Assume S' = S | y, where S has i bits set, while S' has j bits set.
            For a state S where i bits are set, there are (N-i) are not set.
            To transition to S' where 2^j bits are set, we need to pick (j-i) bits from (N-i) bits.
            There are C(N-i, j-i) ways to pick (j-i) bits from (N-i) bits.
            Also, y might have bits that are already set in S, and there are 2^i ways to choose those bits.
            Therefore, there are C(N-i, j-i) * 2^i ways to choose y.
            Also, there are 2^N different N-bit numbers.
            Therefore, T[i][j] = C(N-i, j-i) * 2^i / 2^N.
    S: state vector where,
        S[i] means the probability of the current number has i bits set.

    Apply Markov chain, we can figure out the probability of having 2^N bits set after applying bitwise-OR k times as follows,
        S_{k} = S * T^k
    To calculate the expected value of N, and due to linearity of expectations, we can follow the following formula to calculate E(N):
        E(N) = sum_{i = 1 to inf} (S[N] - prev_S[N]) * i
    We expect this series to converge, so we calculate the sum for each K as in sum_{i = 1 to K} (S[N] - prev_S[N]) * i until it converges.
"""

N = 32

factorials = [1]
for i in range(1, N+1):
    factorials.append(factorials[-1] * i)

def C(n, k):
    return factorials[n] / factorials[k] / factorials[n-k]

T = np.zeros((N+1, N+1), dtype = np.float)

for alpha in range(N+1):
    for beta in range(alpha, N+1):
        T[alpha][beta] = 1.0 * C(N - alpha, beta - alpha) / (2**(N - alpha))

S = np.zeros((N+1), dtype = np.float)
S[0] = 1.0

ans = 0
prev_ans = -1
stop_threshold = 10
stop_counter = 0
tol = 10**-12

prev_SN = 0.0
n_trials = 0
while True:
    n_trials = n_trials + 1
    S = S.dot(T)
    ans = ans + (S[N]-prev_SN) * n_trials
    prev_SN = S[N]
    if abs(ans - prev_ans) < tol:
        stop_counter = stop_counter + 1
        if stop_counter == stop_threshold:
            break
    else:
        stop_counter = 0
    prev_ans = ans

print(ans)


# import modules
import numpy as np

'''
# Description.
Obtains the unit vector of an input vector.

# Input(s).
Array nx1 with size different to unit (vector)

# Example
vector = np.random.rand(2)

---
uVector = unitvector(vector)
'''
def unitvector(vector):

    vecDist = np.sqrt(np.dot(vector,vector))
    uVector = vector/vecDist

    return uVector
'''
BSD 2 license.

Copyright (c) 2016, Universidad Nacional de Colombia, Ludger O.
   Suarez-Burgoa and Exneyder Andrés Montoya Araque.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''


#   A quick guide to sympy
from sympy import *

#   INITIALIZE
x,y,z = symbols('x y z')
f = x**3 + exp(x) + cos(3*x) + log(sin(x))
print('function: '); print(f)

    #initialize with string:
expression = "x**3 + exp(x) + cos(3*x) + log(sin(x))"
f = sympify(expression)

print()
#   VALUE
value = f.subs(x,3)
print('symbolic f(3): '); print(value)

value = N(value)    # or evalf()
print('numeric f(3): '); print(value)

value = value.evalf(100)    # up to 1e-100 precision ?!
print('numagic f(3): '); print(value)

print('1/6: ') # don't test 1/7
value = (x/6).subs(x,1); print(value.evalf(100))

    #multivariable
f1 = x*y*z
print('with x=2, y=4, z=3 then f1 = x*y*z = ')
value = f.subs( [(x,2),(y,4),(z,3)] )
print(value)

print()
#   DERIVATIVE
df = diff(f,x)
print('first order derivative: '); print(df)

df = diff(f,x,2)    # or diff(f, x, x)
print('second order derivative: '); print(df)

    #multivariable
df = diff(x*y*z + x**y, x, y)   #derivative with respect to x then y
print(df)

print(value)

print()
#   INTEGRAL

print()
#   MATRIX

print()
#   SOLVER  f(x) = 0
fx = x**2 - 5
solution = solveset(fx, x, domain=S.Reals)
print(solution)

fx = sin(x) - 1
solution = solveset(fx, x, domain=S.Reals)
print(solution)

#


import numpy as np
import matplotlib.pyplot as plt
import calculus.integ as numint
import scipy.integrate as spint
from interpolation.lagrange import lagrangepoly
#Real value 7.32473
def f(x):
	return -x**5 + 5*x**4 - 7*x**3 + x**2 + 4*x + 0.5

x = np.linspace(-5,5, 150)
y = f(x)
a = 0.5
b = 2.6
x2 = np.linspace(a, b)
fig, ax = plt.subplots()
ax.plot(x, y)
ax.set_xlim(-3, 5)
ax.set_ylim(-4, 7)
ax.axhline(0, color='black')
ax.legend(['f(x)'])
ax.fill_between(x2, 0, f(x2), color = 'g', alpha=0.3)
plt.title('Integral of f(x)')

#Trap rule
fig, ax = plt.subplots()
ax.plot(x, y)
ax.set_xlim(-3, 5)
ax.set_ylim(-4, 7)
ax.axhline(0, color='black')
ax.legend(['f(x)'])

h = b-a

y2 = f(a) + (f(b) - f(a))/(b - a) * (x2 - a)
ax.plot(x2, y2, '-g')
ax.fill_between(x2, 0, y2, color = 'g', alpha=0.3)
ax.grid(True)
textstr = str(numint.trap(h, f(a), f(b)))
props = dict(boxstyle='round', facecolor='white')
# place a text box in upper left in axes coords
ax.text(0.05, 0.95, textstr, transform=ax.transAxes, fontsize=12,
        verticalalignment='top', bbox=props)

plt.title('Trapezoid Rule')

#Trap rule with multiple segs
fig, ax = plt.subplots()
ax.plot(x, y)
numpts = 5
n = 5-1
ax.set_xlim(-3, 5)
ax.set_ylim(-4, 7)
ax.axhline(0, color='black')
ax.legend(['f(x)'])
x3 = np.linspace(a, b, numpts)
x4 = np.linspace(a, b, numpts)
x3 = x3[1:]
lastx = a
for i in x3:
	xn = np.linspace(lastx, i, 15)
	yn = f(lastx) + (f(i) - f(lastx))/(i - lastx) * (xn - lastx)
	ax.plot(xn, yn, '-g')
	ax.fill_between(xn, 0, yn, color='g', alpha=0.3)
	lastx = i

textstr = str(numint.trapm(h/n, f(x4)))
props = dict(boxstyle='round', facecolor='white')
# place a text box in upper left in axes coords
ax.text(0.05, 0.95, textstr, transform=ax.transAxes, fontsize=12,
        verticalalignment='top', bbox=props)
ax.grid(True)
plt.title('Trapezoid Rule w/ Multiple Segments')

#Simps 1/3 rule
fig, ax = plt.subplots()
ax.plot(x, y)
ax.set_xlim(-3, 5)
ax.set_ylim(-4, 7)
ax.axhline(0, color='black')
ax.legend(['f(x)'])
a = 0.5
b = 2.6
h = b-a
xx = np.linspace(a, b, 3)
x2 = np.linspace(a, b)
y2 = lagrangepoly(xx, f(xx), x2)
ax.plot(x2, y2, '-g')
ax.fill_between(x2, 0, y2, color = 'g', alpha=0.3)
ax.grid(True)
textstr = str(numint.simp13(h, f(xx[0]), f(xx[1]), f(xx[2])))
props = dict(boxstyle='round', facecolor='white')
# place a text box in upper left in axes coords
ax.text(0.05, 0.95, textstr, transform=ax.transAxes, fontsize=12,
        verticalalignment='top', bbox=props)

plt.title('Simpson 1/3 Rule')

#Simps 1/3 rule with multiple segs
fig, ax = plt.subplots()
ax.plot(x, y)
numpts = 5
n = 5-1
ax.set_xlim(-3, 5)
ax.set_ylim(-4, 7)
ax.axhline(0, color='black')
ax.legend(['f(x)'])
x3 = np.linspace(a, b, numpts)
x4 = np.linspace(a, b, numpts)
x3 = x3[1:]
lastx = a
for i in x3:
	xn = np.linspace(lastx, i, 15)
	xx2 = np.linspace(lastx, i, 3)
	yn = lagrangepoly(xx2, f(xx2), xn)
	ax.plot(xn, yn, '-g')
	ax.fill_between(xn, 0, yn, color='g', alpha=0.3)
	lastx = i

textstr = str(numint.simp13m(h/n, f(x4)))
props = dict(boxstyle='round', facecolor='white')
# place a text box in upper left in axes coords
ax.text(0.05, 0.95, textstr, transform=ax.transAxes, fontsize=12,
        verticalalignment='top', bbox=props)
ax.grid(True)
plt.title('Simpson Rule 1/3 w/ Multiple Segments')
plt.show()


from numba import njit
import numpy as np

def py_l2_diff(f1, f2):
    """
    Computes the l2-norm of the difference
    between a function f1 and a function f2

    Parameters
    ----------
    f1 : array of floats
        function 1
    f2 : array of floats
        function 2

    Returns
    -------
    diff : float
        The l2-norm of the difference.
    """
    l2_diff = np.sqrt(np.sum((f1 - f2)**2))/f1.size

    return l2_diff

@njit
def l2_diff(f1, f2):
    """
    Computes the l2-norm of the difference
    between a function f1 and a function f2

    Parameters
    ----------
    f1 : array of floats
        function 1
    f2 : array of floats
        function 2

    Returns
    -------
    diff : float
        The l2-norm of the difference.
    """
    l2_diff = np.sqrt(np.sum((f1 - f2)**2)) / f1.size

    return l2_diff


from __future__ import division
import numpy
import matplotlib.pyplot as pyplot

USER = 'Maciej Grabias'
USER_ID = 'njgh39'

# constants used
T_HALF = 20.8 #half-time
TAU = T_HALF / numpy.log(2) # average lifetime

def f(n):
    return -n / TAU

def analytic(N0, ts):
    return N0 * numpy.exp(-ts / TAU)

def solve_euler(N0, dt, n_panels):

    #initial parameters
    n, t = N0, 0

    # array for iteration
    n_t = numpy.zeros((n_panels,))

    for i in range(n_panels):
        n_t[i] = n
        t = i * dt
        n = n + f(n) * dt
    return n_t

def solve_heun(N0, dt, n_panels):

    # initial parameters
    n, t = N0, 0

    # array for iteration
    n_t = numpy.zeros((n_panels,))

    for i in range(n_panels):
        n_t[i] = n
        k0 = f(n)
        k1 = f(n + k0 * dt)
        n = n + (k0 + k1) * dt /2
    return n_t

# constants for plots
T1 = 60 # time range
N_PANELS = 15 # number of panels
N0 = 1500 # initial numbe of nuclei

dt = T1 / N_PANELS
ts = numpy.arange(0, T1, dt)

# functions for plots

n_analytic  = analytic(N0, ts)
n_euler     = solve_euler(N0, dt, N_PANELS)
n_heun      = solve_heun(N0, dt, N_PANELS)

#plot

pyplot.figure()
pyplot.subplot(211) # count VS time for methods
pyplot.plot(ts, n_euler, label='Euler Method', color ='red')
pyplot.plot(ts, n_heun, label='Heun Method', color = 'blue', linestyle = '--')
pyplot.plot(ts, n_analytic, label='Analytic', color = 'grey')
pyplot.xlabel("Time, h")
pyplot.ylabel("Number of nuclei")
pyplot.title("Number of nuclei for different methods used VS time")
pyplot.legend()

pyplot.subplot(212) # error VS time for numerics
pyplot.semilogy()
err_euler = abs(n_euler - n_analytic) / n_analytic
err_heun = abs(n_heun - n_analytic) / n_analytic
pyplot.plot(ts, err_euler, color="red", label =" Euler")
pyplot.plot(ts, err_heun, color ="blue", linestyle="--", label="Heun")
pyplot.xlabel("Time, h")
pyplot.ylabel("Error")
pyplot.title("Error between methods used and the analytically determined solution")
pyplot.legend()

pyplot.show()

ANSWER1 = """Heun's method is more accurate than Euler's method because it integrates a differential
equation over trapeziums rather than rectangles"""


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

def smooth_product(u, v, domain=(0, 1)):
    approx, _ = quad(lambda x: u(x)*v(x), *domain)  # Discard error
    return approx

def least_squares(func, basis, inner, **kwargs):
    # Take the inner product of each pair of basis
    lhs = np.array([
        [inner(p, q, **kwargs) for p in basis]
        for q in basis
    ])

    # The the inner product of the function with each basis
    rhs = np.array([
        inner(p, func, **kwargs) for p in basis
    ])

    # Create the approximation
    return np.linalg.solve(lhs, rhs)

def Q1():
    legendre = ('1', '2*x - 1', '6*x**2 - 6*x + 1')
    basis    = [  # Generate the functions based off strings
        np.vectorize(eval('lambda x:' + poly))
        for poly in legendre
    ]

    coeffs = least_squares(
        np.exp, basis, smooth_product, domain=(0, 1))

    domain = np.linspace(0, 1)
    approx = coeffs @ [p(domain) for p in basis]

    fig, ax = plt.subplots()
    ax.plot(domain, np.exp(domain), label='Exact')
    ax.plot(domain, approx, label='Approx')

def Q2():
    basis_funcs = [  # Generate the functions based off strings
        np.vectorize(eval('lambda x:' + poly))
        for poly in ('1', 'x', 'x**2')
    ]

    x_data, y_data = np.loadtxt('data_points.txt', unpack=True)
    basis_data = [p(x_data) for p in basis_funcs]

    coeffs = least_squares(
        y_data, basis_data, np.inner)

    domain = np.linspace(x_data.min(), x_data.max())
    approx = coeffs @ [p(domain) for p in basis_funcs]

    fig, ax = plt.subplots()
    ax.scatter(x_data, y_data, label='Exact')
    ax.plot(domain, approx, 'k-', label='Approx')

def Q3():
    basis = [  # Generate the functions based off strings
        eval('lambda x:' + func)
        for func in ('np.sin(x)', 'np.sin(2*x)', 'np.sin(3*x)')
    ]

    def function(x):
        return x*(np.pi-x)

    coeffs = least_squares(
        function, basis,
        smooth_product, domain=(0, np.pi)
    )

    for coeff, exact in zip(coeffs, (8/np.pi, 0, 8/(27*np.pi))):
        print(f'Coefficient error: {abs(coeff-exact):.3e}')

    domain = np.linspace(0, np.pi)
    approx = coeffs @ [p(domain) for p in basis]

    fig, ax = plt.subplots()
    ax.plot(domain, function(domain), label='Exact')
    ax.plot(domain, approx, label='Approx')

if __name__ == '__main__':
    questions = (Q1, Q2, Q3)
    for question in questions:
        input(f'Press `Enter` to run {question.__name__} ')

        plt.close('all')        # <- Close all existing figures
        question()
        plt.show(block=False)   # <- Allow code execution to continue

    input('Press `Enter` to quit the program.')


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

'''
ComputeCost computes the cost function
'''
def computeCost(X, y, theta):
	#computeCost Compute cost for linear regression
	#   J = computeCost(X, y, theta) computes the cost of using theta as the
	#   parameter for linear regression to fit the data points in X and y

	# Initialize some useful values
	m = len(y); # number of training examples

	# You need to return the following variables correctly
	J = 0;

	# ====================== YOUR CODE HERE ======================
	# Instructions: Compute the cost of a particular choice of theta
	# =========================================================================
	# You should set J to the cost.

	X_product 	= np.matmul(X,theta) 		# X*theta
	X_diff	  	= np.subtract(X_product, y)	# X*theta - y
	X_square	= np.square(X_diff)			# Square each element of the matrix computed above
	X_sum		= np.sum(X_square)			# Sum all the elements
	J 			= (1.0/(2.0*m))*X_sum		# Cost Function

	return J

'''
gradientDescent function iterates till it finds a minima
'''
def gradientDescent(X, y, theta, alpha, num_iters):
	#function [theta, J_history] = gradientDescent(X, y, theta, alpha, num_iters)
	#GRADIENTDESCENT Performs gradient descent to learn theta
	#   theta = GRADIENTDESENT(X, y, theta, alpha, num_iters) updates theta by
	#   taking num_iters gradient steps with learning rate alpha

	# Initialize some useful values
	m = len(y); # number of training examples
	J_history = np.zeros((num_iters, 1));

	for iter in range(num_iters):

		# ====================== YOUR CODE HERE ======================
		# Instructions: Perform a single gradient step on the parameter vector
		#               theta.
		#
		# Hint: While debugging, it can be useful to print out the values
		#       of the cost function (computeCost) and gradient here.
		#
		X_0 			= X[:,0].reshape((m,1));
		X_1 			= X[:,1].reshape((m,1));
		X_0_tr			= np.transpose(X_0)
		X_1_tr			= np.transpose(X_1)
		X_theta_prod	= (np.matmul(X,theta)).reshape((m,1))
		X_theta_y_diff	= np.subtract(X_theta_prod,y)
		theta_0 = theta.item(0) - (float(alpha)/float(m))*(np.matmul(X_0_tr, X_theta_y_diff)).item(0)
		theta_1 = theta.item(1) - (float(alpha)/float(m))*(np.matmul(X_1_tr, X_theta_y_diff)).item(0)
		#print X_0.shape, X_0_tr.shape, theta.shape, X.shape, X_theta_prod.shape, y.shape, X_theta_y_diff.shape
		theta = np.array([theta_0, theta_1]).reshape((2,1))

		# Plot the linear fit
		if(iter%200==0):
			plt.scatter(X_data, y_data, marker='o',  color='g', label='orig')
			y_data_predicted = np.matmul(X,theta)
			plt.plot(X_data, y_data_predicted, marker='*', linestyle='-', color='b', label='pred')
			plt.legend(loc='lower right')
			plt.show(block=False)
			time.sleep(3)
			plt.close()

		# ============================================================

		# Save the cost J in every iteration
		J_history[iter] = computeCost(X, y, theta)
		print "Cost @ iteration: ",iter, " = ", J_history[iter]
	return theta

data 	= pd.read_csv('ex1data1.txt', header =  None, names = ['Population', 'Profits'])
y_data 	= data.iloc[:,1]
X_data 	= data.iloc[:,0]

m 		= len(y_data)						  #Number of training samples
y 		= np.array(y_data).reshape(m,1)
X 		= np.c_[np.ones(m), np.array(X_data)] # Add a column of ones to x

fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_title('Population - Profit Scatter Plot')
ax.set_xlabel('Population in 10000s')
ax.set_ylabel('Profit in 10000$')

theta 	= np.zeros((2, 1)).reshape((2,1)) # initialize fitting parameters
theta 	= np.array([40,40]).reshape((2,1))# Try initializing from a different point. The convergence will be seen easily

print "Cost Function Value is:", computeCost(X, y, theta)

# Some gradient descent settings
iterations = 1500;
alpha = 0.01;

# run gradient descent
theta = gradientDescent(X, y, theta, alpha, iterations);

# print theta to screen
print 'Theta found by gradient descent: ', theta.item(0), theta.item(1)

# Plot the linear fit
plt.scatter(X_data, y_data, marker='o',  color='g', label='orig')
y_data_predicted = np.matmul(X,theta)
plt.plot(X_data, y_data_predicted, marker='*', linestyle='-', color='b', label='pred')
plt.legend(loc='lower right')

# Predict values for population sizes of 35,000 and 70,000
#predict1 = [1, 3.5] *theta;
#fprintf('For population = 35,000, we predict a profit of %f\n',...
#    predict1*10000);
#predict2 = [1, 7] * theta;
#fprintf('For population = 70,000, we predict a profit of %f\n',...
#   predict2*10000);


import pytest
import numpy as np
from claude.information import information_entropy

SAMPLE_SIZE = 10000
SAMPLE_SIZE_SMALL = 100

def within_tolerance(value1, value2):
    TOLERANCE = 1e-3
    return np.abs(value1 - value2) < TOLERANCE

def test_zero_entropy():
    x = np.ones(SAMPLE_SIZE_SMALL)
    assert information_entropy(x) == 0

def test_max_entropy_1():
    x0 = np.zeros(SAMPLE_SIZE_SMALL)
    x1 = np.ones(SAMPLE_SIZE_SMALL)
    x = np.concatenate((x0, x1))
    assert information_entropy(x) == 1

def test_max_entropy_2():
    x = np.concatenate([i * np.ones(SAMPLE_SIZE_SMALL)
                        for i in range(4)])
    assert information_entropy(x) == 2

def test_entropy_1():
    x = np.random.random(SAMPLE_SIZE) > 0.5
    assert within_tolerance(information_entropy(x), 1)

def test_entropy_2():
    sample_size = 10
    x = np.arange(sample_size)
    assert within_tolerance(information_entropy(x),
                            np.log2(sample_size))


#!/usr/bin/python
""" Calculation of Pi via Monte-Carlo """

import random
import numpy as np
from time import process_time
MAX_STEPS = 1000000
REPEATS = 100

def method_1() -> float:
    """
    Generate two random numbers between 0 and 1 (as x and y coordinates).
    If x*x + y*y < 1, that is (x, y) inside the unit circle (segment).
    Pi is calculated by the fraction of points in the circle vs total number.
    """
    for i in range(REPEATS):
        step = 0
        sum = 0
        while step < MAX_STEPS:
            x = random.random()
            y = random.random()
            if x*x + y*y < 1.0:
                sum += 1
            step += 1
        estimate = 4.0 * sum / MAX_STEPS
    return estimate

def method_2() -> float:
    """
    Generates one random number [0, 1) as x and calculates the corresponding
    y value to put (x, y) on the unit circle.
    """
    for i in range(REPEATS):
        step = 0
        sum = 0.0
        while step < MAX_STEPS:
            x = random.random()
            y = (1.0 - x*x) ** 0.5
            sum = sum + y
            step += 1
        estimate = 4.0 * sum / MAX_STEPS
    return estimate

def method_3() -> float:
    """
    Generates a periodic x-grid and generates the corresponding y values to form a unit circle.
    """
    step = 1.0 / MAX_STEPS
    for i in range(REPEATS):
        sum = 0.0
        x = 0.0
        while x <= 1.0:
            y = (1.0 - x*x) ** 0.5
            sum = sum + y
            x += step
        estimate = 4.0 * sum / MAX_STEPS
    return estimate

def method_4() -> float:
    """
    Basically the same as method3, but utiliting the numpy module.
    """
    x_dom = np.linspace(0, 1, MAX_STEPS)
    for i in range(REPEATS):
        y     = np.sqrt(1-np.power(x_dom, 2))
        estimate    = 4.0 * y.sum() / MAX_STEPS
    return estimate

if __name__ == '__main__':
    lst_methods = [
        method_1,
        method_2,
        method_3,
        method_4,
        ]
    for method in lst_methods:
        t1 = process_time()
        pi = method()
        t2 = process_time()
        print("Pi was approximated to {} using {} steps and {} repeats in {} seconds.".format(pi, MAX_STEPS, REPEATS, round(t2-t1, 5)))


# Polynomial Regression

# Importing the libraries
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd

# Importing the dataset
dataset = pd.read_csv('Position_Salaries.csv')
X = dataset.iloc[:, 1:-1].values
Y = dataset.iloc[:, -1].values

## Splitting the dataset into the training set and test set
# Only 10 observations, thus doesn't make sense to split dataset
"""from sklearn.cross_validation import train_test_split
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size= 0.2, random_state= 0)"""

"""# Feature scaling
from sklearn.preprocessing import StandardScaler
sc_X = StandardScaler()
X_train = sc_X.fit_transform(X_train)
X_test = sc_X.transform(X_test)"""

# Fit linear regression to dataset
from sklearn.linear_model import LinearRegression
LinReg = LinearRegression()
LinReg.fit(X,Y)

# Fit polynomial regression to dataset
from sklearn.preprocessing import PolynomialFeatures
Degree = 3
PolyReg = PolynomialFeatures(degree = Degree)
X_poly = PolyReg.fit_transform(X)
LinReg2 = LinearRegression()
LinReg2.fit(X_poly, Y)

# Calculate R^2 and adjusted R^2
def calculateRSquared(y_orig, y_pred, X):
    Resid = sum((y_orig - y_pred)**2)
    Total = sum((y_orig - np.mean(y_orig))**2)
    R_squared = 1 - (float(Resid))/Total
    R_squared_adjusted = 1 - (1-R_squared)*(len(y_orig)-1)/(len(y_orig)-X.shape[1]-1)
    return R_squared, R_squared_adjusted

# Visualize regression fits
# Visualize linear fit
Y_Lin = LinReg.predict(X)
[R, R_adjust] = calculateRSquared(Y,Y_Lin,X)
plt.scatter(X, Y, color = 'red')
plt.plot(X,Y_Lin, color = 'blue')
plt.title('Salary vs Position (Linear Regression)')
plt.text(0.05, 0.85, '$R^2$ = {}'.format(round(R,2))+'\n$R^2$ adjusted = {}'.format(round(R_adjust,2)), transform=plt.gca().transAxes)
plt.xlabel('Position grade')
plt.ylabel('Salary ($)')

X_grid = np.arange(min(X), max(X), 0.1)
X_grid = X_grid.reshape(len(X_grid), 1)
Y_Poly = LinReg2.predict(PolyReg.fit_transform(X_grid))
[R, R_adjust] = calculateRSquared(Y,LinReg2.predict(PolyReg.fit_transform(X)),X)
plt.figure()
plt.scatter(X, Y, color = 'red')
plt.plot(X_grid, Y_Poly, color = 'blue')
plt.title('Salary vs Position (Polynomial Regression, D = ' + str(Degree) + ')')
plt.text(0.05, 0.85, '$R^2$ = {}'.format(round(R,2))+'\n$R^2$ adjusted = {}'.format(round(R_adjust,2)), transform=plt.gca().transAxes)
plt.xlabel('Position grade')
plt.ylabel('Salary ($)')
plt.show()

# Predicting a new result with linear regression
print(LinReg.predict(6.5))

# Predicting a new result with polynomial regression
print(LinReg2.predict(PolyReg.fit_transform(6.5)))


import numpy as np

#############################
# SINGLE DIMENSIONAL ARRAYS #
#############################

# This creates our array
np_array = np.array([5, 10, 15, 20, 25, 30])
print("--0--")

# Gets the unique values
print(np.unique(np_array))
print("--1--")

# Calculates the standard deviation
print(np.std(np_array))
print("--2--")

# Calculates the maximum
print(np_array.max())
print("--3--")

# Squares each value in the array
print(np_array ** 2)
print("--4--")

# Adds the arrays together element wise
print(np_array + np_array)
print("--5--")

# The sum of the squares of the elements
print(np.sum(np_array ** 2))
print("--6--")

# Gives you the shape: (rows, columns)
print(np_array.shape)

##########################
# TWO DIMENSIONAL ARRAYS #
##########################
# Create 2d array
print("--0--")
np_2d_array = np.array([[1,2,3],
                        [4,5,6]])
print(np_2d_array)

# Calculate the transpose, which is when you swap the columns and rows.
print("--1--")
np_2d_array_T = np_2d_array.T
print(np_2d_array_T)

# Print the shape of the array as (number of rows, number of columns)
print("--3--")
print(np_2d_array.shape)

# Access elements in the 2d array by index.
# First index is the row number
# Second index is the column number
# Index numbers start from 0
print("--4--")
print(np_2d_array[1,1])
print(np_2d_array[0,2])

###########################
# CALCULATING DOT PRODUCT #
###########################
np_array = np.array([5, 10, 15, 20, 25, 30])
dot_product = np.dot(np_array, np_array)
print(dot_product)

############################
# GENERATING RANDOM VALUES #
############################
# Generage a single random number in range [0,1)
print("--0--")
print(np.random.rand())

# Generate a matrix of random numbers in range [0,1) with shape (3,2)
print("--1--")
print(np.random.rand(3,2))

# Low=5, High=15, Size=2. Generate 2 values between 5 and 15 (exclusive)
print("--0--")
print(np.random.randint(5, 15, 2))

# Low=5, High=15, Size=(3,2). Generate a matrix of shape (3,2) with values between 5 and 15 (exclusive)
print("--1--")
print(np.random.randint(5, 15, (3,2)))

#####################
# SAMPLING THE DATA #
#####################
array = np.array([1,2,3,4,5])

# Sample 10 data points with replacement.
print("--0--")
print(np.random.choice(array, 10, replace=True))

# Sample 3 data points without replacement.
# Sampling without replacement means the same value can’t be sampled more than once
print("--1--")
print(np.random.choice(array, 3, replace=False))

#############################
# RANDOMLY SHUFFLING VALUES #
#############################
x = [1,2,3,4,5]  # Create a list of 5 elements
np.random.shuffle(x)  # Randomly shuffle the order of the elements in the list

print(x)

# if you want the random selections to be the same every time, you need to set a seed
np.random.seed(42)


# TheoIV
# 1. Exercise Sheet
#  -> 4. Shooting Pi

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

# num_shoots is N from the exercise sheet.
def shoot_pi(num_shoots = 10, verbose = True):

    if verbose:
        print('Shooting PI with ' + str(num_shoots) + ' samples...')

    # shoot randomly (uniform) at 1x1 area and count hits on inner quarter circle.
    num_hits = 0
    for _ in range(num_shoots):
        point = np.random.rand(2)
        if (np.linalg.norm(point) < 1):
            num_hits += 1

    # compute pi as ratio of hits to all samples
    # (factor 4 necessary, since we're only shooting at a quarter circle)
    pi = (num_hits / num_shoots) * 4

    if verbose:
        print('PI is approximately ', str(pi), '!', sep='')

    return pi

# num_samples is M from the exercise sheet.
def sample_pi_shoots(num_samples = 500, num_shoots_per_sample = 10, verbose = True):

    # shoot pi M times and compute mean + std of results
    pi_samples = []
    for _ in range(num_samples):
        pi_samples.append(shoot_pi(num_shoots_per_sample, False))
    pi_mean = np.mean(pi_samples, axis=0)
    pi_std = np.std(pi_samples, axis=0)
    pi_var = pi_std**2

    if verbose:
        print('Mean:', round(pi_mean, 3))
        print('Std: ', round(pi_std, 3))
        print('Var: ', round(pi_var, 3))

    return (pi_mean, pi_std, pi_var)

# show proportionality between standard deviation and 1/sqrt(N)
def show_dependency():

    M = 500
    Ns = []
    stds = []

    t = time.time()

    for N in range(500, 8_001, 500):
        (_, std, _) = sample_pi_shoots(M, N, False)
        Ns.append(N)
        stds.append(std)

    print('Runtime:', time.time()-t, 'seconds')

    fig, axs = plt.subplots(3)
    fig.canvas.set_window_title('Homework 1')
    fig.suptitle(r'Showing: $\Delta x \sim \frac{1}{\sqrt{N}}$, M=' + str(M))
    axs[0].set_xlabel('N', fontsize=15)
    axs[0].set_ylabel(r'$\Delta x$', rotation=0, fontsize=15)
    axs[0].plot(Ns, np.array(stds))
    axs[1].set_xlabel(r'$log(N)$', fontsize=15)
    axs[1].set_ylabel(r'$log(\Delta x)$', rotation=0, fontsize=15)
    axs[1].plot(np.log(np.array(Ns)), np.log(np.array(stds)))
    axs[2].set_xlabel(r'$\frac{1}{\sqrt{N}}$', fontsize=15)
    axs[2].set_ylabel(r'$\Delta x$', rotation=0, fontsize=15)
    axs[2].plot(1/np.sqrt(np.array(Ns)), np.array(stds))
    plt.show()

if __name__ == '__main__':
    # 4a):
    # shoot_pi(10_000)

    # 4b):
    show_dependency()


#!/usr/bin/env python3
import sys
import numpy as np

np.random.seed(2017)

def inside_circle(total_count):

    x = np.float32(np.random.uniform(size=total_count))
    y = np.float32(np.random.uniform(size=total_count))

    radii = np.sqrt(x*x + y*y)

    count = len(radii[np.where(radii<=1.0)])

    return count

def estimate_pi(n_samples):

    return (4.0 * inside_circle(n_samples) / n_samples)

if __name__=='__main__':

    n_samples = 10000
    if len(sys.argv) > 1:
        n_samples = int(sys.argv[1])

    my_pi = estimate_pi(n_samples)
    sizeof = np.dtype(np.float32).itemsize

    print("[serial version] required memory %.3f MB" % (n_samples*sizeof*3/(1024*1024)))
    print("[serial version] pi is %f from %i samples" % (my_pi,n_samples))


"""
One of the central problems in statistics is to make estimations — and quantify
how good these estimations are — of the distribution of an entire population
given only a small (random) sample. A classic example is to estimate the average
height of all the people in a country when measuring the height of a randomly
selected sample of people. These kinds of problems are particularly interesting
when the true population distribution, by which we usually mean the mean of the
whole population, cannot feasibly be measured. In this case, we must rely on our
knowledge of statistics and a (usually much smaller) randomly selected sample to
estimate the true population mean and standard deviation, and also quantify how
good our estimations are. It is the latter that is the source of confusion,
misunderstanding, and misrepresentation of statistics in the wider world.

This module illustrates how to estimate the population mean and give a
confidence interval fo these estimates.
"""
import math
import pandas as pd

from scipy import stats

sample_data = pd.Series([
    172.3, 171.3, 164.7, 162.9, 172.5, 176.3, 174.8, 171.9,
    176.8, 167.8, 164.5, 179.7, 157.8, 170.6, 189.9, 185. ,
    172.7, 165.5, 174.5, 171.5])

sample_mean = sample_data.mean()
sample_std = sample_data.std()

print(f"Mean: {sample_mean}, st. dev: {sample_std}")
# Mean: 172.15, st. dev: 7.473778724383846

N = sample_data.count()
std_err = sample_std/math.sqrt(N)

cv_95, cv_99 = stats.t.ppf([0.975, 0.995], df=N-1)

pm_95 = cv_95 * std_err
pm_99 = cv_99 * std_err
conf_interval_95 = [sample_mean - pm_95, sample_mean + pm_95]
conf_interval_99 = [sample_mean - pm_99, sample_mean + pm_99]

print(f"95% confidence: {conf_interval_95}")
print(f"99% confidence: {conf_interval_99}")
# 95% confidence: [168.65216388659374, 175.64783611340627]
# 99% confidence: [167.36884119608774, 176.93115880391227]


import numpy as np

def softmax_numpy(x):
    """
    :param x:  numpy array of shape (n_samples, n_features)
    :return: numpy array of shape (n_samples, n_features)
    Things that are important in this code:
    1. delegate looping into numpy and don't use for loops in python
    2. Use broadcasting to to operations on vectors/matrices with different shapes
    a good article on broadcasting can be found here:
    http://eli.thegreenplace.net/2015/broadcasting-arrays-in-numpy/
    3. Direct computation of exp(x) can lead to overflow, use the property:
        softmax(x) = softmax(x+c) for any constant c
    """
    x_max = np.max(x, axis=1)
    x_exp = np.exp((x.transpose() - x_max).transpose())
    x_exp_sum = np.sum(x_exp, axis=1)
    return x_exp / x_exp_sum[:, None]

def test_softmax_numpy():
    """
     Warning: these are not exhaustive.
    """
    print "Running basic tests..."
    test1 = softmax_numpy(np.array([[1001, 1002], [3, 4]]))
    assert np.amax(np.fabs(test1 - np.array(
        [0.26894142, 0.73105858]))) <= 1e-6
    test2 = softmax_numpy(np.array([[-1001, -1002]]))
    assert np.amax(np.fabs(test2 - np.array(
        [0.73105858, 0.26894142]))) <= 1e-6
    print "softmax tests pass\n"

if __name__ == "__main__":
    test_softmax_numpy()


Vamos continuar nossa trilha em computação simbólica estendendo o conhecimento sobre o tipo `bool`, expressões e testes lógicos.

## Operadores lógicos

Vimos que `True` e `False` são os dois valores atribuíves a um objeto de tipo `bool`. Eles são úteis para testar condições, realizar verificações e comparar quantidades. Vamos estudar *operadores de comparação*, *operadores de pertencimento* e *operadores de identidade*.

### Operadores de comparação

A tabela abaixo resume os operadores de comparação utilizados em Python.

| operador | significado | símbolo matemático |
|---|---|---|
| `<` | menor do que | $<$ |
| `<=` | menor ou igual a | $\leq$ |
| `>` | maior do que | $>$ |
| `>=` | maior ou igual a | $\geq$ |
| `==` | igual a | $=$ |
| `!=` | diferente de | $\neq$ |

Podemos usá-los para comparar objetos.

**Nota:** `==` está relacionado à igualdade, ao passo que `=` é uma atribuição. São conceitos operadores com finalidade distinta.

2 < 3 # o resultado é um 'bool'

5 < 2 # isto é falso

2 <= 2 # isto é verdadeiro

4 >= 3 # isto é verdadeiro

6 != -2

4 == 4 # isto não é uma atribuição!

Podemos realizar comparações aninhadas:

x = 2
1 < x < 3

3 > x > 4

2 == x > 3

As comparações aninhadas acima são resolvidas da esquerda para a direita e em partes. Isso nos leva a introduzir os seguintes operadores.

| operador | símbolo matemático | significado | uso relacionado a |
|---|---|---|---|
| `or` | $\vee$ | "ou" booleano | união, disjunção |
| `and` | $\wedge$ | "e" booleano | interseção, conjunção |
| `not` | $\neg$ | "não" booleano | exclusão, negação |

# parênteses não são necessários aqui
(2 == x) and (x > 3) # 1a. comparação: 'True'; 2a.: 'False'. Portanto, ambas: 'False'

# parênteses não são necessários aqui
(x < 1) or (x < 2) # nenhuma das duas é True. Portanto,

not (x == 2) # nega o "valor-verdade" que é 'True'

not x + 1 > 3 # estude a precedência deste exemplo. Por que é 'True'?

not (x + 1 > 3) # estude a precedência deste exemplo. Por que também é 'True'?

### Operadores de pertencimento

A tabela abaixo resume os operadores de pertencimento.

| operador | significado | símbolo matemático
|---|---|---|
| `in` | pertence a | $\in$ |
| `not in` | não pertence a | $\notin$ |

Eles terão mais utilidade quando falarmos sobre sequências, listas. Neste momento, vejamos exemplos com objetos `str`.

'2' in '2 4 6 8 10' # o caracter '2' pertence à string

frase_teste = 'maior do que'
'maior' in frase_teste

'menor' in frase_teste # a palavra 'menor' está na frase

1 in 2 # 'in' e 'not in' não são aplicáveis aqui

### Operadores de identidade

A tabela abaixo resume os operadores de identidade.

| operador | significado
|---|---|
| `is` | "aponta para o mesmo objeto"
| `is not` | "não aponta para o mesmo objeto" |

Esses operadores são úteis para verificar se duas variáveis se referem ao mesmo objeto. Exemplo:

```python
a is b
a is not b
```

- `is` é `True` se `a` e `b` se referem ao mesmo objeto; `False`, caso contrário.
- `is not` é `False` se `a` e `b` se referem ao mesmo objeto; `True`, caso contrário.

a = 2
b = 3
a is b # valores distintos

a = 2
b = a
a is b # mesmos valores

a = 2
b = 3
a is not b # de fato, valores não são distintos

a = 2
b = a
a is not b # de fato, valores são distintos

## Equações simbólicas

Equações simbólicas podem ser formadas por meio de `Eq` e não com `=` ou `==`.

# importação
from sympy.abc import a,b
import sympy as sy
sy.init_printing(pretty_print=True)

sy.Eq(a,b) # equação simbólica

sy.Eq(sy.cos(a), b**3) # os objetos da equação são simbólicos

### Resolução de equações algébricas simbólicas

Podemos resolver equações algébricas da seguinte forma:

```python
solveset(equação,variável,domínio)
```

**Exemplo:** resolva $x^2 = 1$ no conjunto $\mathbb{R}$.

from sympy.abc import x
sy.solveset( sy.Eq( x**2, 1), x,domain=sy.Reals)

Podemos reescrever a equação como: $x^2 - 1 = 0$.

sy.solveset( sy.Eq( x**2 - 1, 0), x,domain=sy.Reals)

Com `solveset`, não precisamos de `Eq`. Logo, a equação é passada diretamente.

sy.solveset( x**2 - 1, x,domain=sy.Reals)

**Exemplo:** resolva $x^2 + 1 = 0$ no conjunto $\mathbb{R}$.

sy.solveset( x**2 + 1, x,domain=sy.Reals) # não possui solução real

**Exemplo:** resolva $x^2 + 1 = 0$ no conjunto $\mathbb{C}$.

sy.solveset( x**2 + 1, x,domain=sy.Complexes) # possui soluções complexas

**Exemplo:** resolva $\textrm{sen}(2x) = 3 + x$ no conjunto $\mathbb{R}$.

sy.solveset( sy.sin(2*x) - x - 3,x,sy.Reals) # a palavra 'domain' também pode ser omitida.

O conjunto acima indica que nenhuma solução foi encontrada.

**Exemplo:** resolva $\textrm{sen}(2x) = 1$ no conjunto $\mathbb{R}$.

sy.solveset( sy.sin(2*x) - 1,x,sy.Reals)

## Expansão, simplificação e fatoração de polinômios

Vejamos exemplos de polinômios em uma variável.

a0, a1, a2, a3 = sy.symbols('a0 a1 a2 a3') # coeficientes
P3x = a0 + a1*x + a2*x**2 + a3*x**3 # polinômio de 3o. grau em x
P3x

b0, b1, b2, b3 = sy.symbols('b0 b1 b2 b3') # coeficientes
Q3x = b0 + b1*x + b2*x**2 + b3*x**3 # polinômio de 3o. grau em x
Q3x

R3x = P3x*Q3x # produto polinomial
R3x

R3x_e = sy.expand(R3x) # expande o produto
R3x_e

sy.simplify(R3x_e) # simplify às vezes não funciona como esperado

sy.factor(R3x_e) # 'factor' pode funcionar melhor

# simplify funciona para casos mais gerais
ident_trig = sy.sin(x)**2 + sy.cos(x)**2
ident_trig

sy.simplify(ident_trig)

## Identidades trigonométricas

Podemos usar `expand_trig` para expandir funções trigonométricas.

sy.expand_trig( sy.sin(a + b) ) # sin(a+b)

sy.expand_trig( sy.cos(a + b) ) # cos(a+b)

sy.expand_trig( sy.sec(a - b) ) # sec(a-b)

## Propriedades de logaritmo

Com `expand_log`, podemos aplicar propriedades válidas de logaritmo.

sy.expand_log( sy.log(a*b) )

A identidade não foi validada pois `a` e `b` são símbolos irrestritos.

a,b = sy.symbols('a b',positive=True) # impomos que a,b > 0

sy.expand_log( sy.log(a*b) ) # identidade validada

sy.expand_log( sy.log(a/b) )

m = sy.symbols('m', real = True) # impomos que m seja um no. real
sy.expand_log( sy.log(a**m) )

Com `logcombine`, compactamos as propriedades.

sy.logcombine( sy.log(a) + sy.log(b) ) # identidade recombinada

## Fatorial

A função `factorial(n)` pode ser usada para calcular o fatorial de um número.

sy.factorial(m)

sy.factorial(m).subs(m,10) # 10!

sy.factorial(10) # diretamente

**Exemplo:** Sejam $m,n,x$ inteiros positivos. Se $f(m) = 2m!$, $g(n) = \frac{(n + 1)!}{n^2!}$ e $h(x) = f(x)g(x)$, qual é o valor de $h(2)$?

from sympy.abc import m,n,x

f = 2*sy.factorial(m)
g = sy.factorial(n + 1)/sy.factorial(n**2)

h = (f.subs(m,x)*g.subs(n,x)).subs(x,4)
h

## Funções anônimas

A terceira classe de funções que iremos aprender é a de *funções anônimas*. Uma **função anônima** em Python consiste em uma função cujo nome não é explicitamente definido e que pode ser criada em apenas uma linha de código para executar uma tarefa específica.

Funções anônimas são baseadas na palavra-chave `lambda`. Este nome tem inspiração em uma área da ciência da computação chamada de cálculo-$\lambda$.

Uma função anônima tem a seguinte forma:

```python
lambda lista_de_parâmetros: expressão
```

Funções anônimas podem são bastante úteis para tornar um código mais conciso.

Por exemplo, na aula anterior, definimos a função

```python
def repasse(V):
    return 0.0103*V
```

para calcular o repasse financeiro ao corretor imobiliário.

Com uma função anônima, a mesma função seria escrita como:

repasse = lambda V: 0.0103*V

Não necessariamente temos que atribui-la a uma variável. Neste caso, teríamos:

lambda V: 0.0103*V

Para usar a função, passamos um valor:

repasse(100000) # repasse sobre R$ 100.000,00

O modelo completo com "bonificação" seria escrito como:

r3 = lambda c,V,b: c*V + b # aqui há 3 parâmetros necessários

Redefinamos objetos simbólicos:

from sympy.abc import b,c,V
r3(b,c,V)

O resultado anterior continua sendo um objeto simbólico, mas obtido de uma maneira mais direta. Podemos usar funções anônimas para tarefas de menor complexidade.

## "Lambdificação" simbólica

Usando `lambdify`, podemos converter uma expressão simbólica do *sympy* para uma expressão que pode ser numericamente avaliada em outra biblioteca. Essa função desempenha papel similar a uma função *lambda* (anônima).

expressao = sy.sin(x) + sy.sqrt(x) # expressão simbólica
f = sy.lambdify(x,expressao,"math") # lambdificação para o módulo math
f(0.2) # avalia

Para avaliações simples como a anterior, podemos usar `evalf` e `subs`. A lambdificação será útil quando quisermos avaliar uma função em vários pontos, por exemplo. Na próxima aula, introduziremos sequencias e listas. Para mostrar um exemplo de lambdificação melhor veja o seguinte exemplo.

from numpy import arange # importação de função do módulo numpy

X = arange(40) # gera 40 valores de 0 a 39

X

f = sy.lambdify(x,expressao,"numpy")(X) # avalia 'expressao' em X
f


################################################################## clase ######################################################################################
# Ejercicio 1:

import numpy as np
from numpy.linalg import inv
from numpy.linalg import solve
# 1a.) Tiene un sistema de 3 ecuaciones lineales Ax=B, donde:

A=np.matrix([[8.,2.,1.],[1.,-2.,-3.],[-1.,1.,2,]])
print(A)

B=np.array([-8.,0.,3.])

# Implemente el algoritmo de eliminacion gaussiana para resolver este sistema de ecuaciones. IMPRIMA la matriz aumentada paso a paso. IMPRIMA su vector solucion
def GJ(A,B):
    NA=len(A)
    paso=0
    for c in range(NA):
        for r in range(NA):
            if(r==c):
                B[r]=B[r]/A[r,c]
                A[r,:]=A[r,:]/A[r,c]
                paso+=1
                print('Paso',paso)
                print(A)
            if(r>c):
                B[r]=B[r]-(B[c]*A[r,c])
                A[r,:]=A[r,:]-(A[c,:]*A[r,c])
                paso+=1
                print('Paso',paso)
                print(A)

    for c in range(NA):
        for r in range(NA):
            if(r<c):
                B[r]=B[r]-(B[c]*A[r,c])
                A[r,:]=A[r,:]-(A[c,:]*A[r,c])
                paso+=1
                print('Paso',paso)
                print(A)
    print('El vector solución es:',B)

GJ(A,B)

# IMPRIMA la solucion encontrada usando los paquetes de numpy
print('Usando los paquetes de numpy la solución da:',solve(A,B))

# 1b). Repita lo anterior para un sistema de ecuaciones mas general: Imprimiendo los pasos intermedios.
#Escriba aca un codigo GENERAL de eliminacion Gaussiana para resolver el sistema Ax=B.

print('')
print('PUNTO 1B')
print('')

N=np.random.randint(3 , 8)
print ('Matriz de tamaño:',N,'x',N)
Arreglo=(np.random.random((N,N))*10.0)-5.0
B=(np.random.random((N,1))*10.0)-5.0

print ('Matriz A =')
print (Arreglo)
print ('B =')
print (B)
print('')
print('Solución')
GJ(Arreglo,B)

# IMPRIMA la solucion encontrada usando los paquetes de numpy

print('Usando los paquetes de numpy la solución da:',solve(Arreglo,B))


'''
Objective
In the previous challenge, we calculated a mean. In this challenge, we practice calculating a weighted mean. Check out the Tutorial tab for learning materials and an instructional video!

Task
Given an array, , of  integers and an array, , representing the respective weights of 's elements, calculate and print the weighted mean of 's elements. Your answer should be rounded to a scale of  decimal place (i.e.,  format).

Input Format

The first line contains an integer, , denoting the number of elements in arrays  and .
The second line contains  space-separated integers describing the respective elements of array .
The third line contains  space-separated integers describing the respective elements of array .

Constraints

, where  is the  element of array .
, where  is the  element of array .
Output Format

Print the weighted mean on a new line. Your answer should be rounded to a scale of decimal place (i.e.,  format).

Sample Input

5
10 40 30 50 20
1 2 3 4 5
Sample Output

32.0
Explanation

We use the following formula to calculate the weighted mean:

And then print our result to a scale of  decimal place () on a new line.
'''

# Enter your code here. Read input from STDIN. Print output to STDOUT

# import numpy as np

n = int(input())
x = list(map(int, input().split()))
y = list(map(int, input().split()))

# print(round(np.average(x, weights = y), 1))

print(round(sum(map(lambda a: a[0] * a[1], zip(x,y)))/sum(y), 1))

10 Days of Statistics/Day 0/Weighted Mean.py


import numpy as np
import matplotlib.pyplot as plt

N = 101
L = 2.0
mesh = np.linspace(-L,L,N)

# Plot quadratic loss for comparison
PLOT_QUADRATIC = True

# Plot sequence of hyperparameter values
PLOT_SEQ = True

normal_loss = lambda x, s:  np.power(x,2)/(2*np.power(s,2)) + 0.5*np.log(2*np.pi*np.power(s,2))
laplace_loss = lambda x, b:  np.abs(x)/b + np.log(2*b)
cauchy_loss = lambda x, g: np.log( np.pi*g*(1.0 + np.power(x,2)/np.power(g,2)) )
quadratic_loss = lambda x: x*x

theta = 0.5
normal_vals = np.array([normal_loss(x,theta) for x in mesh])
laplace_vals = np.array([laplace_loss(2*x,theta) for x in mesh])
cauchy_vals = np.array([cauchy_loss(2*x,theta) for x in mesh])
quadratic_vals = np.array([quadratic_loss(x) for x in mesh])

fig = plt.figure()
ax = fig.gca()
plt.plot(mesh, normal_vals, c='C0', label="Normal")
plt.plot(mesh, laplace_vals, c='C1', label="Laplace")
plt.plot(mesh, cauchy_vals, c='C2', label="Cauchy")
if PLOT_QUADRATIC:
    plt.plot(mesh, quadratic_vals, c='C3', label="Quadratic")
ax.legend(fontsize=24)
plt.show()

if PLOT_SEQ:

    normal_vals = np.array([normal_loss(x,theta) for x in mesh])
    laplace_vals = np.array([laplace_loss(2*x,theta) for x in mesh])
    cauchy_vals = np.array([cauchy_loss(2*x,theta) for x in mesh])

    for rate, title in [[0.9,"Decreasing"],[1.1,"Increasing"]]:
        for loss, label, color in [[normal_loss, "Normal", "C0"],[laplace_loss, "Laplace", "C1"],[cauchy_loss, "Cauchy", "C2"]]:
            if label == "Normal":
                theta = 1.0
            elif label == "Laplace":
                theta = 0.5
            else:
                theta = 0.5
            vals = np.array([loss(x,theta) for x in mesh])
            plt.plot(mesh, vals, c=color, label=label, linewidth=3.0)
            plt.plot(mesh, quadratic_vals, c='C3', label="Quadratic")

            for k in range(1,11):
                theta = theta*rate
                vals = np.array([loss(x,theta) for x in mesh])
                plt.plot(mesh, vals, c=color, linestyle="dashed", label=None)
            plt.title(title + " " + label + " Paramater", fontsize=24)
            plt.show()


import numpy as np
from scipy import signal

# source: https://stackoverflow.com/questions/40703751/using-fourier-transforms-to-do-convolution?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa

x = [[1, 0, 0, 0], [0, -1, 0, 0], [0, 0, 3, 0], [0, 0, 0, 1]]
x = np.array(x)
y = [[4, 5], [3, 4]]
y = np.array(y)

standard_conv = signal.convolve2d(x, y, 'full')

print("conv:", standard_conv)

s1 = np.array(x.shape)
s2 = np.array(y.shape)
print("s1: ", s1)
print("s2: ", s2)

size = s1 + s2 - 1
print("size: ", size)

fsize = 2 ** np.ceil(np.log2(size)).astype(int)
fslice = tuple([slice(0, int(sz)) for sz in size])
print("fslice: ", fslice)

# Along each axis, if the given shape (fsize) is smaller than that of the input, the input is cropped.
# If it is larger, the input is padded with zeros. if s is not given, the shape of the input along the axes
# specified by axes is used.
new_x = np.fft.fft2(x, fsize)

new_y = np.fft.fft2(y, fsize)
result = np.fft.ifft2(new_x * new_y)
print("first result: ", result)

result = np.fft.ifft2(new_x * new_y)[fslice].copy()
result_int = np.array(result.real, np.int32)

my_result = np.array(result, np.double)
print("my_result (doubles): ", my_result)

print("fft for my method (ints):", result_int)
print("is my method correct (for ints): ", np.array_equal(result_int, standard_conv))
print("fft for my method (doubles):", result)

print("fft with int32 output:", np.array(signal.fftconvolve(x, y), np.int32))
lib_result = np.array(signal.fftconvolve(x, y), np.double)
print("fft with double output:", np.allclose(my_result, lib_result, atol=1e-12))

# the correct way is to take the amplitude:  the abs of a complex number gives us its amplitude/mangnitude
lib_magnitude = np.abs(signal.fftconvolve(x, y))
print("lib_magnitude: ", lib_magnitude)
my_magnitude = np.abs(result)
print("is the magnitude correct: ", np.allclose(my_magnitude, lib_magnitude, atol=1e-12))