import numpy as np
import matplotlib.pyplot as plt

class LinearRegression:

    def __init__(self,csv_file):
        data = np.loadtxt(csv_file , delimiter = ',')
        X = data[: , :-1]
        self.y = data[: , -1]
        self.X = np.insert(X,0,values = np.ones(X.shape[0]) , axis =1)
        self.mu = np.mean(self.X[:,1:] , axis = 0)
        self.sigma = np.std(self.X[:,1:] , axis = 0)

    def head(self,n):
        print("Feature Data Is")
        print(self.X[:n,:])
        print("Target Data Is")
        print(np.array(self.y[:n]))

    def normalise(self):
        X_norm = (self.X[:,1:]-self.mu)/self.sigma
        X_norm = np.insert(X_norm,0,values = np.ones(self.X.shape[0]) , axis =1)
        return X_norm

    def computeCost(self,theta):
        """m represents number of training examples
           X is feature set with n features and is matrix of shape m*(n+1)
           theta is parameter set of shape (n+1)*1
           y is target data set of shape m*1
           X*theta represents our predicted value
           (X*theta-y)^2 is the squared error value
           J is sum of all the squared errors over 2*m
        """
        m = self.y.shape[0]
        HTheta_X = np.dot(self.X,theta)
        Error = HTheta_X-self.y
        J = (np.sum(Error**2))/(2*m)
        return J

    def gradientDescent(self,theta,alpha,iterations):
        theta = theta.copy()
        m = self.y.shape[0]
        J_values = []

        for num in range(iterations):
            for j in range(theta.size):
                Error = np.dot(self.X,theta)-self.y
                Derivative = np.dot(Error, self.X[:,j])
                theta[j] = theta[j] - (np.sum(Derivative))*(alpha/m)

            J_values.append(self.computeCost(theta))

        return theta,J_values

    def predict_value(self,values,theta , norm=False):
        if(norm == True):
            values = (values-self.mu)/self.sigma
        values = np.insert(values , 0 , np.ones(1) , axis = 0)
        predict = np.dot(values,theta)
        return predict

if __name__ == '__main__':

    LR = LinearRegression('ex1data2.txt')
    LR.X = LR.normalise()
    theta, J_history = LR.gradientDescent(np.zeros(LR.X.shape[1]), 0.15, 1000)
    print(LR.predict_value([1650,3],theta , norm = True)) #Prediction for 1650 sq. feet and 3 bedrooms

    '''For ex1data1 file , no need for normalisation'''
    LR2 = LinearRegression('ex1data1.txt')
    theta , J_history = LR2.gradientDescent(np.zeros(2) , 0.01 ,1500)
    print(LR2.predict_value([7] , theta))


import numpy as np
import pymc3 as pm
import matplotlib.pyplot as plt
from scipy.stats import binom
from scipy.stats import mode
import seaborn as sns
import collections

birth1 = np.array(
    [1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1,
     0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0,
     1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1])
birth2 = np.array([0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0,
                   1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
                   1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1,
                   0, 0, 0, 1, 1, 1, 0, 0, 0, 0])

boys = np.sum(birth1) + np.sum(birth2)

g1 = collections.Counter(birth1)
g2 = collections.Counter(birth2)

girls = g1[0]+g2[0]
print('overall boys:' + str(boys))
print('overall girls:' + str(girls))

size = len(birth1)+len(birth2)

priors = np.random.uniform(0.,1.,size)
p_grid = np.linspace(0.,1.,size)
likehood = binom.pmf(boys,size, p=p_grid)
posterior = likehood*priors
posterior = posterior/posterior.sum()
plt.plot(p_grid,posterior)
plt.show()

print('maximum posteriori %f at prob = %f '%(max(posterior),p_grid[posterior == max(posterior)]))

# likehood = binom.pmf(np.sum(birth2),len(birth2), p=p_grid)
# posterior = likehood*posterior
# posterior = posterior/posterior.sum()
#
# plt.plot(p_grid,posterior)
# plt.show()
sample_size=int(10000)
samples = np.random.choice(a=p_grid,p=posterior,size=sample_size)
sns.kdeplot(samples)
plt.show()

print('50% hpd: '+str(pm.hpd(samples,alpha=0.5)))
print('89% hpd: '+str(pm.hpd(samples,alpha=0.89)))
print('97% hpd: '+str(pm.hpd(samples,alpha=0.97)))

#model fits well, max is around 111/200 as is the observation
dummy_w = binom.rvs(n=200,p=samples,size=sample_size)
_,(ax0,ax1) = plt.subplots(1,2)
ax0.plot(posterior)
ax1.hist(dummy_w,bins=50)
plt.show()
means = [(dummy_w == i).mean() for i in range(200)]

print('boys = %f from number of births %f '%(boys,size))
plt.plot(means)
plt.show()

dummy_w = binom.rvs(n=100,p=samples,size=sample_size)
_,(ax0,ax1) = plt.subplots(1,2)
ax0.plot(posterior)
ax1.hist(dummy_w,bins=50)
plt.show()
means = [(dummy_w == i).mean() for i in range(100)]

print('boys = %f from number of births %f '%(np.sum(birth1),len(birth1)))
plt.plot(means)
plt.show()

#simulating boys(second born) folowing girls born first
dummy_w = binom.rvs(n=g1[0],p=samples,size=sample_size)
_,(ax0,ax1) = plt.subplots(1,2)
ax0.plot(posterior)
ax1.hist(dummy_w,bins=50)
plt.show()
means = [(dummy_w == i).mean() for i in range(200)]

print('boys = %f from number of births %f '%(boys,size))
plt.plot(means)
plt.show()

#simulating 51 boys from 1st birth
size = len(birth1)
priors = np.random.uniform(0.,1.,size)
p_grid = np.linspace(0.,1.,size)
likehood = binom.pmf(np.sum(birth1),size, p=p_grid)
posterior = likehood*priors
posterior = posterior/posterior.sum()
plt.plot(p_grid,posterior)
plt.show()

sample_size=int(10000)
samples = np.random.choice(a=p_grid,p=posterior,size=sample_size)
sns.kdeplot(samples)
plt.show()

print('50% hpd: '+str(pm.hpd(samples,alpha=0.5)))
print('89% hpd: '+str(pm.hpd(samples,alpha=0.89)))
print('97% hpd: '+str(pm.hpd(samples,alpha=0.97)))

dummy_w = binom.rvs(n=100,p=samples,size=sample_size)
_,(ax0,ax1) = plt.subplots(1,2)
ax0.plot(posterior)
ax1.hist(dummy_w,bins=50)
plt.show()
means = [(dummy_w == i).mean() for i in range(100)]

print('boys = %f from number of births %f '%(np.sum(birth1),len(birth1)))
plt.plot(means)
plt.show()


import numpy as np
from numpy import linalg as LA
import matplotlib.pyplot as plt
from pdb import set_trace
from scipy.linalg import eig

def get_laplacian(E, n_vertices, directed=False):
    L = np.zeros((n_vertices, n_vertices))
    for e in E:
        if directed:
            L[e[1]][e[1]] += 1
            L[e[1]][e[0]] = -1
        else:
            L[e[1]][e[1]] += 1
            L[e[0]][e[0]] += 1
            L[e[1]][e[0]] = -1
            L[e[0]][e[1]] = -1
    return L

def get_cycle_graph(N):
    n_vertices = N
    E = []

    for i in range(N-1):
        E.append([i, i+1])
    E.append([N-1, 0])

    return E, n_vertices

def simulate_consensus(x_0, T, L, dt=0.001):
    x_current = x_0
    t = 0
    ts = [t]
    xs = x_0
    converged = False
    t_converged = np.inf
    while t <= T:
        x_next = x_current - np.matmul(L, x_current) * dt
        xs = np.hstack((xs, x_next))
        x_current = x_next
        t += dt
        ts.append(t)

        if not converged and np.amax(x_current) - np.amin(x_current) <= 0.01:
            converged = True
            t_converged = t

    return xs, np.array(ts), t_converged

def get_complete_graph(N):
    n_vertices = N
    E = []
    for i in range(N):
        for j in range(N-i-1):
            E.append([i, j+i+1])

    return E, n_vertices

def main():
    # Exercise 1
    E = [[0, 1], [1, 2], [2, 0]]
    n_vertices = 3

    laplacian = get_laplacian(E, n_vertices, False)
    print(laplacian)
    laplacian = get_laplacian(E, n_vertices, True)
    print(laplacian)

    num = [5, 15, 199]

    for n in num:
        E, n_vertices = get_cycle_graph(n)
        laplacian = get_laplacian(E, n_vertices, False)
        eigvals = LA.eigvals(laplacian)
        eigvals = np.sort(eigvals)
        print("C_{}: {}, {}, {}, {}".format(n, eigvals[0], eigvals[1], eigvals[-2], eigvals[-1]))

    # Exercise 2
    x_0 = np.transpose(np.array([[10, 20, 12, 5, 30, 12, 15, 16, 25]]))
    n_vertices = 9
    E = [[0, 2], [1, 2], [2, 3], [2, 4], [2, 6], [4, 5], [4, 6], [4, 7], [5, 6], [6, 7], [6, 8]]
    L = get_laplacian(E, n_vertices, False)
    set_trace()
    xs, ts, t_converge = simulate_consensus(x_0, 20, L)

    # 2.b
    plt.figure()
    for i in range(xs.shape[0]):
        plt.plot(ts, xs[i, :])
    plt.title("Original Graph, t_converge = {}".format(t_converge))
    plt.xlabel('t / sec')
    plt.ylabel('state')
    plt.savefig('/home/bolun/Documents/swarmrobotics/series2/original_graph_sim.png')

    # 2.c
    E = [[0, 2], [1, 2], [2, 3], [4, 5], [4, 6], [4, 7], [5, 6], [6, 7], [6, 8]]
    L = get_laplacian(E, n_vertices, False)
    set_trace()
    xs, ts, t_converge = simulate_consensus(x_0, 20, L)

    plt.figure()
    for i in range(xs.shape[0]):
        plt.plot(ts, xs[i, :])
    plt.title("Disconnected Graph, t_converge = {}".format(t_converge))
    plt.xlabel('t / sec')
    plt.ylabel('state')
    plt.savefig('/home/bolun/Documents/swarmrobotics/series2/disconnected_graph_sim.png')

    # 2.d
    E, n_vertices = get_complete_graph(9)
    L = get_laplacian(E, n_vertices, False)
    set_trace()
    xs, ts, t_converge = simulate_consensus(x_0, 20, L)

    plt.figure()
    for i in range(xs.shape[0]):
        plt.plot(ts, xs[i, :])
    plt.title("Complete Graph, t_converge = {}".format(t_converge))
    plt.xlabel('t / sec')
    plt.ylabel('state')
    plt.savefig('/home/bolun/Documents/swarmrobotics/series2/complete_graph_sim.png')

    # 2.e
    E, n_vertices = get_cycle_graph(9)
    L = get_laplacian(E, n_vertices, False)
    set_trace()
    xs, ts, t_converge = simulate_consensus(x_0, 20, L)

    plt.figure()
    for i in range(xs.shape[0]):
        plt.plot(ts, xs[i, :])
    plt.title("Cycle Graph, t_converge = {}".format(t_converge))
    plt.xlabel('t / sec')
    plt.ylabel('state')
    plt.savefig('/home/bolun/Documents/swarmrobotics/series2/cycle_graph_sim.png')

    # Exercise 3
    # 3.c
    x_0 = np.transpose(np.array([[10, 5, 1, -5, -10]]))
    n_vertices = 5
    E = [[1, 0], [1, 2], [2, 4], [3, 2], [4, 1], [4, 3]]
    L = get_laplacian(E, n_vertices, True)
    vals, vl, vr = eig(L, left=True)
    xs, ts, t_converge = simulate_consensus(x_0, 10, L)
    plt.figure()
    for i in range(xs.shape[0]):
        plt.plot(ts, xs[i, :])
    plt.title("Original Graph, t_converge = {}".format(t_converge))
    plt.xlabel('t / sec')
    plt.ylabel('state')
    plt.savefig('/home/bolun/Documents/swarmrobotics/series2/unchanged_graph_sim.png')

    # 3.d
    x_0 = np.transpose(np.array([[10, 5, 1, -5, -10]]))
    n_vertices = 5
    E = [[0, 4], [1, 0], [2, 4], [3, 2], [4, 1], [4, 3]]
    L = get_laplacian(E, n_vertices, True)
    vals, vl, vr = eig(L, left=True)
    xs, ts, t_converge = simulate_consensus(x_0, 10, L)
    plt.figure()
    for i in range(xs.shape[0]):
        plt.plot(ts, xs[i, :])
    plt.title("Converge to Average Graph, t_converge = {}".format(t_converge))
    plt.xlabel('t / sec')
    plt.ylabel('state')
    plt.savefig('/home/bolun/Documents/swarmrobotics/series2/average_graph_sim.png')

    # 3.e
    x_0 = np.transpose(np.array([[10, 5, 1, -5, -10]]))
    n_vertices = 5
    E = [[0, 4], [1, 0], [2, 4], [3, 2], [4, 1]]
    L = get_laplacian(E, n_vertices, True)
    vals, vl, vr = eig(L, left=True)
    xs, ts, t_converge = simulate_consensus(x_0, 30, L)
    plt.figure()
    for i in range(xs.shape[0]):
        plt.plot(ts, xs[i, :])
    plt.title("Leader Graph, t_converge = {}".format(t_converge))
    plt.xlabel('t / sec')
    plt.ylabel('state')
    plt.savefig('/home/bolun/Documents/swarmrobotics/series2/leader_graph_sim.png')

if __name__ == "__main__":
    main()


# -*- coding: utf-8 -*-
"""
Created on Mon Jan  4 09:58:31 2021

@author: Larisa
"""

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

### Proceduri -> Ex1
def difFinProg(X, Y):
    """
    x oarecare  ->  f'(x) = (f(x+h) - f(x)) / h

    pt discretizare xi  ->  f'(xi) = (f(xi+1) - f(xi)) / (xi+1 - xi), unde
    xi + 1  => nodul i + 1 al vectorului x

    """
    n = len(X)
    df = np.zeros((n - 1, 1))

    for i in range(n - 1):
        df[i] = (Y[i+1] - Y[i]) / (X[i+1] - X[i])

    return df

def difFinReg(X, Y):
    """
    x oarecare  ->  f'(x) = (f(x) - f(x-h)) / h

    pt discretizare xi  ->  f'(xi) = (f(xi) - f(xi-1)) / (xi - xi-1), unde
    xi-1  => nodul i-1 al vectorului x

    """
    n = len(X)
    df = np.zeros((n, 1))

    for i in range(1, n):
        df[i] = (Y[i] - Y[i - 1]) / (X[i] - X[i - 1])

    return df

def difFinCen(X, Y):
    """
    x oarecare  ->  f'(x) = (f(x+h) - f(x-h)) / (2*h)

    pt discretizare xi  ->  f'(xi) = (f(xi+1) - f(xi-1)) / (xi+1 - xi-1), unde
    xi-1  => nodul i-1 al vectorului x

    """
    n = len(X)
    df = np.zeros((n - 1, 1))

    for i in range(1, n - 1):
        df[i] = (Y[i + 1] - Y[i - 1]) / (X[i + 1] - X[i - 1])

    return df

### Exercițiul 1

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

a = 0
b = np.pi
n = 100
x_graf = np.linspace(a, b, n)
y_graf = f(x_graf)

x = sym.symbols('x')
f_expr = sym.sin(x)
df = sym.diff(f_expr, x)
dfFunc = sym.lambdify(x, df)

plt.plot(x_graf, dfFunc(x_graf), linewidth = 2)
plt.grid(True)

dfaprox = difFinProg(x_graf, y_graf)
plt.plot(x_graf[0:n-1], dfaprox, linewidth = 2)
plt.show()

err = np.zeros((n - 1, 1))
for i in range(n - 1):
    err[i] = abs(dfFunc(x_graf[i]) - dfaprox[i])

plt.plot(x_graf[0:n-1], err, linewidth = 2)
plt.grid(True)
plt.show()

# Pasul
print(x_graf[1] - x_graf[0])

# Metoda Reg
dfaprox2 = difFinReg(x_graf, y_graf)
plt.plot(x_graf[1:n], dfaprox2[1:n], linewidth = 2)
plt.grid(True)
plt.show()

err = np.zeros((n, 1))
for i in range(1, n):
    err[i] = abs(dfFunc(x_graf[i]) - dfaprox2[i])

plt.plot(x_graf[1:n], err[1:n], linewidth = 2)
plt.grid(True)
plt.show()

# Metoda Cen
dfaprox3 = difFinCen(x_graf, y_graf)
plt.plot(x_graf[1:n-1], dfaprox3[1:n-1], linewidth = 2)
plt.grid(True)
plt.show()

err = np.zeros((n-1, 1))
for i in range(1, n-1):
    err[i] = abs(dfFunc(x_graf[i]) - dfaprox3[i])

plt.plot(x_graf[1:n-1], err[1:n-1], linewidth = 2)
plt.grid(True)
plt.show()

### Proceduri -> Ex2
def MetRichardson(phi, x, h, n):
    """
    Parameters
    ----------
    phi : formula de aproximare a derivatei cu un ordin inferior.
    x : punctul în care calculez derivata.
    h : pasul.
    n : ordinul de aproximare al derivatei (superior).

    Returns
    -------
    df = derivata aproximativă

    """
    Q = np.zeros((n, n))
    for i in range(n):
        Q[i, 0] = phi(x, h / 2 ** i)

    for i in range(1, n):
        for j in range(1, i + 1):
            Q[i, j] = Q[i, j - 1] + 1 / (2 ** j - 1) * (Q[i, j - 1] - Q[i - 1, j - 1])

    return Q[n - 1 , n - 1]

# Exercițiul 2
def phi(x, h):
    return (f(x + h) - f(x)) / h

df_richardson = np.zeros((n, 1))
N = 3 # ordinul de aproximare la care dorim să ajungem cu met Richardson

for i in range(len(x_graf)):
    # pas echidistant
    df_richardson[i] = MetRichardson(phi, x_graf[i], x_graf[1] - x_graf[0], N)

plt.plot(x_graf, df_richardson, linewidth = 2)
plt.show()

err = np.zeros((n, 1))
for i in range(n):
    err[i] = abs(dfFunc(x_graf[i]) - df_richardson[i])
plt.plot(x_graf, err, linewidth = 2)
plt.show()

# d.
# Aproximeaza a doua derivata si are ordinul de aproximare h^2
def phi2(x, h):
    return (f(x + h) - 2 * f(x) + f(x - h)) / h ** 2

N = 5 # eroarea creste din cauza rotunjirilor făcute de pc (erori interne)
d2f_richardson = np.zeros((n, 1))
for i in range(len(x_graf)):
    d2f_richardson[i] = MetRichardson(phi2, x_graf[i], (x_graf[1] - x_graf[0]), N - 1)

plt.figure(9)
plt.plot(x_graf, d2f_richardson, linewidth=3)
plt.show()

d2f = sym.diff(df, x)
d2f_func = sym.lambdify(x, d2f)

err2 = np.zeros((n, 1))
for i in range(n):
    err2[i] = np.abs(d2f_func(x_graf[i]) - d2f_richardson[i])

plt.figure(10)
plt.plot(x_graf, err2, linewidth=3)
plt.show()

Calcul Numeric (CN)/Laborator/Laborator 12/lab12.py


import numpy as np

def hms2dec(h,m,s):
  return 15*(h + (m/60) + (s/3600))

def dms2dec(d,m,s):
  if d>=0:
    return (d + (m/60) + (s/3600))
  return (d - (m/60) - (s/3600))

def angular_dist(r1, d1, r2, d2):
  r1= np.radians(r1)
  r2= np.radians(r2)
  d1= np.radians(d1)
  d2= np.radians(d2)
  a = (np.sin(np.abs(d1 - d2)/2))**2
  b = np.cos(d1)*np.cos(d2)*np.sin(np.abs(r1 - r2)/2)**2
  d = np.degrees(2*np.arcsin(np.sqrt(a + b)))
  return d

def import_bss():
  cat = np.loadtxt('bss.dat', usecols=range(1, 7))
  loaded = []
  count = 1
  for i in cat:
    loaded.append((count, hms2dec(i[0],i[1],i[2]), dms2dec(i[3],i[4],i[5])))
    count+=1
  return loaded

def import_super():
  cat = np.loadtxt('super.csv', delimiter=',', skiprows=1, usecols=[0, 1])
  loaded = []
  count = 1
  for i in cat:
    loaded.append((count, i[0], i[1]))
    count+=1
  return loaded

def find_closest(catalogue,target_ra, target_dec):
  min_distance = 9999999
  ID = 0
  for i in catalogue:
    distance = angular_dist(i[1], i[2], target_ra, target_dec)
    if distance<min_distance:
      min_distance = distance
      ID = i[0]
  return (ID,min_distance)

def crossmatch(bss_cat, super_cat, max_dist):
  matches = []
  no_matches = []

  for i in bss_cat:
      best = (0,0,max_dist+1)

      for j in super_cat:
        dist = angular_dist(i[1], i[2], j[1], j[2])
        if dist<=best[2]:
          best = (i[0],j[0],dist)

      if best[2]<=max_dist:
         matches.append(best)
      else:
         no_matches.append(i[0])

  return (matches, no_matches)

if __name__ == '__main__':
  bss_cat = import_bss()
  super_cat = import_super()

  max_dist = 40/3600
  matches, no_matches = crossmatch(bss_cat, super_cat, max_dist)
  print(matches[:3])
  print(no_matches[:3])
  print(len(no_matches))

  max_dist = 5/3600
  matches, no_matches = crossmatch(bss_cat, super_cat, max_dist)
  print(matches[:3])
  print(no_matches[:3])
  print(len(no_matches))


#!/bin/env python

"""
Module for approximating sqrt
"""

def sqrt(x,debug=False):
   """
   Module implementing newton's method for approximating sqrt
   """
   from numpy import nan

   if x == 0.:
       return 0.
   elif x<0:
       print "***Error, x must be positive"
       return nan
   assert x>0, "Should not get here!"
   s = 1.
   kmax = 100
   tol = 1.e-14

   for k in range(kmax):
       if debug:
           print "Before iteration {}, s = {:20.15f}".format(k,s)
       s0 = s
       s = 0.5 * ( s + x/s )
       delta_s = s - s0
       if abs(delta_s/x) < tol:
           break
   if debug:
       print "After {} iterations, s = {:20.15f}".format(k+1,s)
   return s


#!/usr/bin/env python3

import numpy as np

import problems as pb
import plotter

#################################################
### Public Variables
#################################################

ENABLE_ERRORS_CALCULATION = True
ENABLE_SOLUTION_PLOTTING = False

ENABLE_GRAPHS_VIEW = False
SAVE_GRAPHS_IMAGE = True

#################################################
### Functions Definitions
#################################################

def matrix_decomposition(a_matrix_diag, a_matrix_subdiag):
    """
    Decompõe uma matrix A tridiagonal simétrica em três matrizes
    L, D e Lt, retornando apenas dois vetores que representam as
    matrizes L e D.
    """

    array_size = len(a_matrix_diag)

    l_matrix_array = np.zeros(array_size, dtype=float)
    d_matrix_array = np.zeros(array_size, dtype=float)

    l_matrix_array[0] = 0
    d_matrix_array[0] = a_matrix_diag[0]

    for i in range(1, array_size):
        l_matrix_array[i] = a_matrix_subdiag[i] / d_matrix_array[i - 1]

        d_matrix_array[i] = a_matrix_diag[i] - d_matrix_array[i - 1] * ((l_matrix_array[i])**2)

    return l_matrix_array, d_matrix_array

def solve_system(a_matrix_diag, a_matrix_subdiag, b_array):
    """
    Soluciona um sistema Ax = b, onde A é uma matrix tridiagonal
    simétrica.

    Para a resolução do sistema, é feita a decomposição de A para L*D*Lt.

    É feita a divisão do problema em três sistemas menores:
    L * y = b
    D * z = y
    Lt * x = z
    """

    array_size = len(a_matrix_diag)

    l_matrix_array, d_matrix_array = matrix_decomposition(a_matrix_diag, a_matrix_subdiag)

    # First system solution -> L * y = b
    y_array = np.zeros(array_size, dtype=float)

    y_array[0] = b_array[0]

    for i in range(1, array_size):
        y_array[i] = b_array[i] - l_matrix_array[i] * y_array[i - 1]

    # Second system solution -> D * z = y
    z_array = np.zeros(array_size, dtype=float)

    for i in range(0, array_size):
        z_array[i] = y_array[i] / d_matrix_array[i]

    # Third system solution -> Lt * x = z
    x_array = np.zeros(array_size, dtype=float)

    x_array[-1] = z_array[-1]

    for i in reversed(range(0, array_size - 1)):
        x_array[i] = z_array[i] - l_matrix_array[i + 1] * x_array[i + 1]

    return x_array

def run(letter, task_result_dir):
    """
    Soluciona o problema da equação de calor a partir de dois métodos implícitos,
    Euler implícito e Crank-Nicolson.
    """

    # Input parameters
    method = (input("Qual método executar: Euler implícito ou (e) ou Crank-Nicolson (c)? ")).lower()

    while method not in ("e", "c"):
        print("Método não encontrado, escolha entre 'e' e 'c'")
        method = (input("Qual método executar: Euler implícitou (e) ou Crank-Nicolson (c)? ")).lower()

    if method == "e":
        method_name = "EULER_IMP"
    else:
        method_name = "CN"

    N = int(input("Insira o valor de N: "))
    M = N

    Δx = 1 / float(N)
    Δt = Δx

    λ = Δt / (Δx**2)

    print(f"N: {N}, λ: {λ}, Δx: {Δx} e Δt: {Δt}")

    # Resuls file
    results_file_name = f"{task_result_dir}/2{letter.capitalize()}_{method_name}_{N}_ERRORS.txt"

    if letter != "c" and ENABLE_ERRORS_CALCULATION == True:
        results_file = open(results_file_name, 'w')

    # A Matrix creation
    if method == "e":
        diag_value = 1 + 2 * λ
        subdiag_value = -λ
    else:
        diag_value = 1 + λ
        subdiag_value = -λ / 2

    a_matrix_diag = np.full(N - 1, diag_value, dtype=float)
    a_matrix_subdiag = np.full(N - 1, subdiag_value, dtype=float)

    # B array creation
    b_array = np.zeros(N - 1, dtype=float)

    # Create and initializes scale array
    scale_array = np.zeros(N + 1, dtype=float)

    for i in range(0, N + 1):
        scale_array[i] = i / N

    # Create U matrix
    U = pb.create_u(scale_array, scale_array, letter)

    # Inside points calculation
    for k in range(0, M):
        # b array calculation
        for i in range(1, N):
            if method == "e":
                b_array[i - 1] = U[k][i] + Δt * pb.heat_source(scale_array[k + 1], scale_array[i], N, letter)
            else:
                b_array[i - 1] = (U[k][i] + (λ / 2) * (U[k][i - 1] - 2 * U[k][i] + U[k][i + 1])
                               + (Δt / 2) * (pb.heat_source(scale_array[k], scale_array[i], N, letter)
                               + pb.heat_source(scale_array[k + 1], scale_array[i], N, letter)))

        g1, g2 = pb.boundary_conditions(scale_array[k + 1], letter)

        if method == "e":
            b_array[0] += λ * g1
            b_array[-1] += λ * g2
        else:
            b_array[0] += (λ / 2) * g1
            b_array[-1] += (λ / 2) * g2

        solution = solve_system(a_matrix_diag, a_matrix_subdiag, b_array)

        for i in range(1, N):
            U[k + 1][i] = solution[i - 1]

    # Plotting u(t, x)
    approx_image_name = f"2{letter.capitalize()}_{method_name}_{N}_APPROX"

    plotter.u_2d_graph(U, scale_array, scale_array, 11, approx_image_name, ENABLE_GRAPHS_VIEW, SAVE_GRAPHS_IMAGE, task_result_dir)

    plotter.u_3d_graph(U, scale_array, scale_array, N, approx_image_name, ENABLE_GRAPHS_VIEW, SAVE_GRAPHS_IMAGE, task_result_dir)

    if letter != "c":
        if ENABLE_SOLUTION_PLOTTING == True:
            # Plotting the u solution
            u_sol = np.zeros((M + 1, N + 1))

            for k in range(0, M + 1):
                for i in range(0, N + 1):
                    u_sol[k][i] = pb.u_solution(scale_array[k], scale_array[i], letter)

            sol_image_name = f"2{letter.capitalize()}_{method_name}_{N}_SOL"

            plotter.u_2d_graph(u_sol, scale_array, scale_array, 11, sol_image_name, ENABLE_GRAPHS_VIEW, SAVE_GRAPHS_IMAGE, task_result_dir)

            plotter.u_3d_graph(u_sol, scale_array, scale_array, N, sol_image_name, ENABLE_GRAPHS_VIEW, SAVE_GRAPHS_IMAGE, task_result_dir)

        if ENABLE_ERRORS_CALCULATION == True:
            if method == "e":
                # Truncation error calculation
                max_truncation_error = 0

                for k in range(0, M):
                    for i in range(1, N):
                        first_term = (pb.u_solution(scale_array[k + 1], scale_array[i], letter)
                                   - pb.u_solution(scale_array[k], scale_array[i], letter)) / Δt
                        second_term = (pb.u_solution(scale_array[k + 1], scale_array[i - 1], letter)
                                    - 2 * pb.u_solution(scale_array[k + 1], scale_array[i], letter)
                                    + pb.u_solution(scale_array[k + 1], scale_array[i + 1], letter)) / (Δx**2)

                        current_truncation_error = abs(first_term - second_term - pb.heat_source(scale_array[k + 1], scale_array[i], N, letter))

                        if current_truncation_error > max_truncation_error:
                            max_truncation_error = current_truncation_error

                max_truncation_error_result = f"O erro máximo de truncamento é {max_truncation_error}"
                print(max_truncation_error_result)
                results_file.write(max_truncation_error_result + "\n")

            # Approximation error calculation for T = 1
            max_approx_error = 0

            for i in range(0, N):
                current_approx_error = abs(pb.u_solution(scale_array[M], scale_array[i], letter) - U[M][i])

                if current_approx_error > max_approx_error:
                    max_approx_error = current_approx_error

            max_approx_error_result = f"O erro máximo de aproximação é {max_approx_error}"
            print(max_approx_error_result)
            results_file.write(max_approx_error_result)

        # End task
        results_file.close()


import probayes as pb
import numpy as np
"""
Consider a disease with a prevalence 1\% in a given population.
Of those with the disease, 98\% manifest a particular symptom,
that is present only in 10\% of those without the disease.
What is the probability someone with symptoms has the disease?
Answer: \approx 9%
"""

# PARAMETERS
prevalence = 0.01
sym_if_dis = 0.98
sym_if_undis = 0.1

# SET UP RANDOM VARIABLES
dis = pb.RV('dis', prob=prevalence)
sym = pb.RV('sym')

# SET UP STOCHASTIC CONDITION
sym_given_dis = sym | dis
sym_given_dis.set_prob(np.array([1-sym_if_undis, 1-sym_if_dis, \
                                 sym_if_undis,   sym_if_dis]).reshape((2,2)))

# APPLY BAYES' RULE
p_dis = dis()
p_sym_given_dis = sym_given_dis()
p_dis_and_sym = p_dis * p_sym_given_dis
p_sym = p_dis_and_sym.marginal('sym')
p_dis_given_sym = p_dis_and_sym / p_sym
inference = p_dis_given_sym({'dis': True, 'sym': True})
print(inference)
assert abs(inference.prob-0.09) < 0.01, \
    "Expected around 0.09 but evaluated {}".format(inference.prob)


import numpy as np

def relu(x):
    """
    Calculate element-wise Rectified Linear Unit (ReLU)
    :param x: Input array
    :return: Rectified output
    """
    return np.maximum(x, 0)

def relu_bw(x):
    """
    Calculate element-wise ReLU derivative
    :param x: Input array
    :return: For each element in x: 1 if x>0, 0 if x<0, N length array
    """
    return np.greater(x, 0).astype(int)

def sigmoid(x):
    """
    Calculate element-wise sigmoid
    :param x: N length array
    :return: Output 1/(1+exp(-x)), N length array
    """
    return 1 / (1 + np.exp(-x))

def sigmoid_bw(x):
    """
    Calculate element-wise sigmoid derivative
    :param x: N length array
    :return: Output sigm(x)*(1-sigm(x)), N length array
    """
    return sigmoid(x) * (1 - sigmoid(x))

def softmax(x):
    """
    Compute softmax for each value in x
    :param x: N length array
    :return: Softmax for ach value between 0-1, N length array
    """
    max_val = 700
    # this custom implementation gives overflow errors when used with momentum, so we clip...
    np.clip(x, -max_val, max_val, out=x)
    return np.exp(x) / np.sum(np.exp(x), axis=0)

def softmax_bw(softmax_output, label):
    """
    Cross entropy error with softmax output, backpropagation of deritivative w.r.t the inputs to the softmax function.
    dE/dzk = softmax_output_k - label_k
    :param softmax_output: Softmax output vector of length 10.
    :param label: One-hot encoded label vector of length 10. Example: [0 0 0 0 0 0 1 0 0 0] = 7
    :return: softmax_output - label
    """
    return softmax_output - label

def cross_entropy(predictions, label):
    """
    Calculate the cross entropy.
    :param predictions: Predicted values, N length array
    :param label: One-hot encoded label vector. Example: [0 0 0 0 0 0 1 0 0 0] = 7
    :return: Cross-entropy
    """
    n = np.size(label)
    eps = 1e-12 # for numerical stability
    ce = -np.dot(label, np.log(predictions + eps)) / n
    return ce


import numpy as np

# Gradient based methods for linear systems

def compute_mse(y, tx, w):
    """
    Compute the Mean Square Error as defined in class.
    Takes as input the targeted y, the sample matrix X and the feature fector w.
    """
    e = y - tx@w
    mse = e.T.dot(e) /(2*len(e))
    return mse

def least_squares(y, tx):
    """
    Compute an esimated solution of the problem y = tx @ w, and the associated error. This method is equivalent
    to the minimization problem of finding w such that |y-tx@w||^2 is minimal. Note that this methods provides the global optimum.
    The error is the mean square error of the targeted y and the solution produced by the least square function.
    Takes as input the targeted y, and the sample matrix X.
    """
    w = np.linalg.solve (tx.T.dot(tx),tx.T.dot(y))
    mse = compute_mse(y, tx, w)
    return w, mse

def least_squares_gradient(y, tx, w):
    """
    Compute the gradient of the mean square error with respect to w, and the current error vector e.
    Takes as input the targeted y, the sample matrix w and the feature vector w.
    This function is used when solving gradient based method, such that least_squares_GD() and least_squares_SGD().
    """
    e = y - tx.dot(w)
    grad = -tx.T.dot(e) / len(e)
    return grad, e

def least_squares_GD(y, tx, initial_w=None, max_iters=50, gamma=0.1):
    """
    Compute an estimated solution of the problem y = tx @ w and the associated error using Gradient Descent.
    This method is equivalent to the minimization problem of finding w such that |y-tx@w||^2 is minimal. Note that
    this method may output a local minimum, while least_squares() provides the global minimum.
    Takes as input:
        * the targeted y
        * the sample matrix w
        * the initial guess for w, by default set as a vector of zeros
        * the number of iterations for Gradient Descent
        * the learning rate gamma
    """
    # Define parameters to store w and loss
    if np.all(initial_w == None): initial_w = np.zeros(tx.shape[1])
    ws = [initial_w] # Initial guess w0 generated randomly
    losses = []
    w = ws[0]
    for n_iter in range(max_iters):
        # compute loss, gradient
        grad, err = least_squares_gradient(y, tx, w)
        loss = compute_mse(y,tx,w)
        # gradient w by descent update
        w = w - gamma * grad
        # store w and loss
        ws.append(w)
        losses.append(loss)
        #if (n_iter % int(max_iters/5)) == 0:
            #print("Gradient Descent({bi}/{ti}): loss={l}".format(bi=n_iter, ti=max_iters,l=loss))
    return w,loss

def batch_iter(y, tx, batch_size, num_batches=1, shuffle=True):
    """
    Generate a minibatch iterator for a dataset.
    Takes as input two iterables (here the output desired values 'y' and the input data 'tx')
    Outputs an iterator which gives mini-batches of `batch_size` matching elements from `y` and `tx`.
    Data can be randomly shuffled to avoid ordering in the original data messing with the randomness of the minibatches.
    Example of use :
    for minibatch_y, minibatch_tx in batch_iter(y, tx, 32):
        <DO-SOMETHING>
    """
    data_size = len(y)

    if shuffle:
        shuffle_indices = np.random.permutation(np.arange(data_size))
        shuffled_y = y[shuffle_indices]
        shuffled_tx = tx[shuffle_indices]
    else:
        shuffled_y = y
        shuffled_tx = tx
    for batch_num in range(num_batches):
        start_index = batch_num * batch_size
        end_index = min((batch_num + 1) * batch_size, data_size)
        if start_index != end_index:
            yield shuffled_y[start_index:end_index], shuffled_tx[start_index:end_index]

def least_squares_SGD(y, tx, initial_w=None, batch_size=1, max_iters=50, gamma=0.00005):
    """
    Compute an estimated solution of the problem y = tx @ w and the associated error using Stochastic Gradient Descent.
    Takes as input:
        * the targeted y
        * the sample matrix w
        * the initial guess for w, by default set as a vector of zeros
        * the batch_size, which is the number of samples on which the new gradient is computed. If set to 1 it corresponds
        to Stochastic Gradient Descent, to the full number of samples it is identifical to least_squares_GD().
        * the number of iterations for Gradient Descent
        * the learning rate gamma
    """
    # Define parameters to store w and loss
    if np.all(initial_w == None): initial_w = np.zeros(tx.shape[1])
    losses = []
    w = initial_w
    for n_iter in range(max_iters):
        for y_batch, tx_batch in batch_iter(y, tx, batch_size=batch_size, num_batches=1):
            # compute a stochastic gradient and loss
            grad, _ = least_squares_gradient(y_batch, tx_batch, np.array(w))
            # update w through the stochastic gradient update
            w = w - gamma * grad
            # calculate loss
            loss = compute_mse(y, tx, w)
            # store w and loss
            losses.append(loss)

        #if n_iter % int(max_iters/5) == 0:
            #print("SGD({bi}/{ti}): loss={l}".format(bi=n_iter, ti=max_iters - 1, l=loss))
    return w,loss
# RIDGE REGRESSION

def ridge_regression(y, tx, lambda_):
    """
    Compute an esimated solution of the problem y = tx @ w , and the associated error. Note that this method
    is a variant of least_square() but with an added regularization term lambda_.
    This method is equivalent to the minimization problem of finding w such that |y-tx@w||^2 + lambda_*||w||^2 is minimal.
    The error is the mean square error of the targeted y and the solution produced by the least square function.
    Takes as input the targeted y, the sample matrix X and the regulariation term lambda_.
    """
    x_t = tx.T
    lambd = lambda_ * 2 * len(y)
    w = np.linalg.solve (np.dot(x_t, tx) + lambd * np.eye(tx.shape[1]), np.dot(x_t,y))
    loss = compute_mse(y, tx, w)

    return w,loss

#LOGISTIC REGRESSION

def sigmoid(t):
    """
    Apply the sigmoid function on t.
    """
    return np.exp(t)/(1+np.exp(t))

def calculate_loss(y, tx, w):
    """
    Compute the negative log likelihood as defined in class.
    Takes as input the targeted y, the sample matrix X and the feature fector w.
    """
    return np.sum(np.log(1+np.exp(tx@w))-y*(tx@w))

def calculate_gradient(y, tx, w):
    """
    Compute the gradient of the negative log likelihood with respect to w, and the current error vector e.
    Takes as input the targeted y, the sample matrix w and the feature vector w.
    This function is used when solving gradient based method, such that logistic_regression() and reg_logistic_regression().
    """
    return tx.T@(sigmoid(tx@w)-y)

def learning_by_gradient_descent(y, tx, w, gamma):
    """
    Compute one step of gradient descent for logistic regression.
    Takes as input the targeted y, the sample matrix w, the feature w and the learning rate gamma.
    Return the feature vector w and the error defined as the negative log likelihood.
    """
    loss = calculate_loss(y,tx,w)
    grad = calculate_gradient(y,tx,w)
    w = w-gamma*grad
    return w, loss

def logistic_regression(y, tx, initial_w=None, max_iters=100, gamma=0.009, batch_size=1):
    """
    Compute an estimated solution of the problem y = sigmoid(tx @ w) and the associated error using Gradient Descent.
    This method is equivalent to the minimization problem of finding w such that the negative log likelihood is minimal. Note that
    this method may output a local minimum.
    Takes as input:
        * the targeted y
        * the sample matrix w
        * the initial guess for w, by default set as a vector of zeros
        * the number of iterations for Stochastic Gradient Descent
        * the learning rate gamma
        * the batch_size, which is the number of samples on which the new gradient is computed. If set to 1 it corresponds
        to Stochastic Gradient Descent, to the full number of samples it is Gradient Descent.
    """
    # init parameters
    if np.all(initial_w == None): initial_w = np.zeros(tx.shape[1])
    threshold = 1e-8
    losses = []
    y = (1 + y) / 2
    # build tx
    w = initial_w

    # start the logistic regression
    for i in range(max_iters):
        # get loss and update w.
        for y_batch, tx_batch in batch_iter(y, tx, batch_size=batch_size, num_batches=1):
            w, _ = learning_by_gradient_descent(y_batch, tx_batch, w, gamma)
            # converge criterion
            losses.append(calculate_loss(y,tx,w))
            if len(losses) > 1 and np.abs(losses[-1] - losses[-2]) < threshold:
                break
            #if i % int(max_iters/5) == 0:
                #print(losses[-1],i,'/{tot}'.format(tot=max_iters))

    return w,losses[-1]

# Regularized LOGISTIC REGRESSION

def learning_by_penalized_gradient_descent(y, tx, w, gamma, lambda_):
    """
    Compute one step of gradient descent for regularized logistic regression.
    Takes as input the targeted y, the sample matrix w, the feature w and the learning rate gamma.
    Return the feature vector w and the error defined as the negative log likelihood.
    """
    loss = calculate_loss(y, tx, w) + lambda_ * np.squeeze(w.T.dot(w))
    grad = calculate_gradient(y, tx, w) + 2 * lambda_ * w
    w = w-gamma*grad
    return w, loss

def reg_logistic_regression(y, tx, lambda_, initial_w=None, max_iters=100, gamma=0.009, batch_size=1):
    """
    Compute an estimated solution of the problem y = sigmoid(tx @ w) and the associated error using Gradient Descent.
    Note that this method is a variant of logistic_regression() but with an added regularization term lambda_.
    This method is equivalent to the minimization problem of finding w such that the negative log likelihood is minimal. Note that
    this method may output a local minimum.
    Takes as input:
        * the targeted y
        * the sample matrix w
        * the initial guess for w, by default set as a vector of zeros
        * the number of iterations for Stochastic Gradient Descent
        * the learning rate gamma
        * the batch_size, which is the number of samples on which the new gradient is computed. If set to 1 it corresponds
        to Stochastic Gradient Descent, to the full number of samples it is Gradient Descent.
    """
    # init parameters
    if np.all(initial_w == None): initial_w = np.zeros(tx.shape[1])
    threshold = 1e-8
    losses = []
    y = (1 + y) / 2
    # build tx
    w = initial_w

    # start the logistic regression
    for iter in range(max_iters):
        # get loss and update w.
        for y_batch, tx_batch in batch_iter(y, tx, batch_size=batch_size, num_batches=1):
            w, loss = learning_by_penalized_gradient_descent(y_batch, tx_batch, w, gamma, lambda_)
            # converge criterion
            loss = calculate_loss(y, tx, w) + lambda_ * np.squeeze(w.T.dot(w))
            losses.append(loss)
            if len(losses) > 1 and np.abs(losses[-1] - losses[-2]) < threshold:
                break
            #if iter % int(max_iters/5) == 0:
                #print(losses[-1],iter,'/{tot}'.format(tot=max_iters))

    return w,losses[-1]


from numpy import exp, cos, linspace, zeros_like
import matplotlib.pyplot as plt
import os, time, glob, math

def damped_vibrations(t, A, b, w):
    return A*exp(-b*t)*cos(w*t)

def compute_vib(A, b, w, T, resolution=500):
    """Return filename of plot of the damped_vibration function."""
    t = linspace(0, T, resolution+1)
    y = damped_vibrations(t, A, b, w)
    plt.figure()  # needed to avoid adding curves in plot
    plt.plot(t, y)
    plt.title('A=%g, b=%g, w=%g' % (A, b, w))

    # Make Matplotlib write to BytesIO file object and grab
    # return the object's string
    from io import BytesIO
    figfile = BytesIO()
    plt.savefig(figfile, format='png')
    figfile.seek(0)  # rewind to beginning of file
    import base64
    figdata_png = base64.b64encode(figfile.getvalue())
    figfile = BytesIO()
    plt.savefig(figfile, format='svg')
    figfile.seek(0)
    figdata_svg = '<svg' + figfile.getvalue().split('<svg')[1]
    figdata_svg = unicode(figdata_svg,'utf-8')
    return figdata_png, figdata_svg

def gamma_density(x, a, h, A):
    # http://en.wikipedia.org/wiki/Gamma_distribution
    xA = x/float(A)
    return abs(h)/(math.gamma(a)*A)*(xA)**(a*h-1)*exp(-xA**h)

def gamma_cumulative(x, a, h, A):
    # Integrate gamma_density using the Trapezoidal rule.
    # Assume x is array.
    g = gamma_density(x, a, h, A)
    r = zeros_like(x)
    for i in range(len(r)-1):
        r[i+1] = r[i] + 0.5*(g[i] + g[i+1])*(x[i+1] - x[i])
    return r

def compute_gamma(a=0.5, h=2.0, A=math.sqrt(2), resolution=500):
    """Return plot and mean/st.dev. value of the gamma density."""
    gah = math.gamma(a + 1./h)
    mean = A*gah/math.gamma(a)
    stdev = A/math.gamma(a)*math.sqrt(
        math.gamma(a + 2./h)*math.gamma(a) - gah**2)
    x = linspace(0, 7*stdev, resolution+1)
    y = gamma_density(x, a, h, A)
    plt.figure()  # needed to avoid adding curves in plot
    plt.plot(x, y)
    plt.title('a=%g, h=%g, A=%g' % (a, h, A))
    # Make Matplotlib write to BytesIO file object and grab
    # return the object's string
    from io import BytesIO
    figfile = BytesIO()
    plt.savefig(figfile, format='png')
    figfile.seek(0)  # rewind to beginning of file
    import base64
    figdata_density_png = base64.b64encode(figfile.getvalue())
    figfile = BytesIO()
    plt.savefig(figfile, format='svg')
    figfile.seek(0)
    figdata_density_svg = '<svg' + figfile.getvalue().split('<svg')[1]
    figdata_density_svg = unicode(figdata_density_svg,'utf-8')

    y = gamma_cumulative(x, a, h, A)
    plt.figure()
    plt.plot(x, y)
    plt.grid(True)
    figfile = BytesIO()
    plt.savefig(figfile, format='png')
    figfile.seek(0)
    figdata_cumulative_png = base64.b64encode(figfile.getvalue())
    figfile = BytesIO()
    plt.savefig(figfile, format='svg')
    figfile.seek(0)
    figdata_cumulative_svg = '<svg' + figfile.getvalue().split('<svg')[1]
    figdata_cumulative_svg = unicode(figdata_cumulative_svg,'utf-8')
    return figdata_density_png, figdata_cumulative_png, \
           figdata_density_svg, figdata_cumulative_svg, \
           '%.2f' % mean, '%.2f' % stdev


import numpy as np

def compute_cost(x, y, size, theta):
    """
        Compute the cost function for linear regression.

        Parameters
        ----------
        x : array_like
            Shape (m, n+1), where m is the number of examples, and n is the number of features
            including the vector of ones for the zeroth parameter.

        y : array_like
            Shape (m,), where m is the value of the function at each point.

        size : int
            Number of total training points.

        theta : array_like
            Shape (n+1, 1). Starting parameters of the regression function.

        Returns
        -------
        cost : float
            The value of the regression cost function.
    """

    cost = np.sum((1 / (2 * size)) * (((np.dot(theta.T, x)) - y) ** 2))
    return cost


"""
Demonstrate Root Finding of Polynomials, Including Complex Roots
Learning Example for Dr. Stanier's classes
File: E8_show_numpy_roots

Author: Charles Stanier, charles-stanier@uiowa.edu
Date:  August 27, 2019
Written/Tested In: Python 3.7.3

Program Objective: Demonstrate how to solve for the roots of a polynomial
such as a parabola or a cubic equation

Modifications: none so far

"""

import numpy as np  # this is used for math functions
import pylab as pl  # this is used for plotting functions

# let's make a polynomial - starting with a line

p_array = [ 2, 1]  # [ 2 1 ] would be y = 2x + 1.  slope 2 and crosses x=0 at y=1

# and plot is from x -10 to 10
x_array = np.linspace(-10,10,100)
y_array = np.polyval(p_array,x_array)

pl.plot(x_array,y_array)
pl.plot([-10,10],[ 0,0 ]) # x=0 line
pl.plot([0,0],[-10,10]) # y=0 line
pl.xlim([-10,10])
pl.ylim([-10,10])
pl.show()

r = np.roots(p_array)
# print out the roots
ic=1 # counter
for rval in r:
    print('Root ',ic,'x= ',rval)
    ic+=1

# let's make a polynomial - now a parabola with real roots

p_array = [ 2, 1, -3 ]  # [ 2, 4, -3 ] would be y = 2x^2 + 4x - 3.

# and plot is from x -10 to 10
y_array = np.polyval(p_array,x_array)

pl.plot(x_array,y_array)
pl.plot([-10,10],[ 0,0 ]) # x=0 line
pl.plot([0,0],[-10,10]) # y=0 line
pl.xlim([-10,10])
pl.ylim([-10,10])
pl.show()

r = np.roots(p_array)
# print out the roots
ic=1 # counter
for rval in r:
    print('Root ',ic,'x= ',rval)
    ic+=1

# let's make a polynomial - now a parabola with complex conjugate roots

p_array = [ 2, 4, 3 ]  # [ 2, 4, 3 ] would be y = 2x^2 + 4x + 3.

# and plot is from x -10 to 10
y_array = np.polyval(p_array,x_array)

pl.plot(x_array,y_array)
pl.plot([-10,10],[ 0,0 ]) # x=0 line
pl.plot([0,0],[-10,10]) # y=0 line
pl.xlim([-10,10])
pl.ylim([-10,10])
pl.show()

r = np.roots(p_array)
# print out the roots
ic=1 # counter
for rval in r:
    print('Root ',ic,'x= ',rval)
    ic+=1

# let's make a polynomial - now a cubic

p_array = [ 1, 2, 4, 3 ]  # [ 1, 2, 4, 3 ] would be y = x^3 + 2x^2 + 4x + 3.

# and plot is from x -10 to 10
y_array = np.polyval(p_array,x_array)

pl.plot(x_array,y_array)
pl.plot([-10,10],[ 0,0 ]) # x=0 line
pl.plot([0,0],[-10,10]) # y=0 line
pl.xlim([-10,10])
pl.ylim([-10,10])
pl.show()

r = np.roots(p_array)
# print out the roots
ic=1 # counter
for rval in r:
    print('Root ',ic,'x= ',rval)
    ic+=1


import numpy as np
from scipy.linalg import solve

def gauss_elim(A, b):
    # don't destroy the old arrays
    A = A.copy()
    b = b.copy()
    # there are n equations and variables
    n = b.size
    eq_num = n
    var_num = n
    # the equations are the rows and each column is a variable
    # eliminate all variables i in 0..n-1 in the equations i+1..n-1
    for var_index_to_elim in range(0, var_num):
        # find the pivot (max abs) in the row below (and including the main diagonal
        avl = A[var_index_to_elim:, var_index_to_elim]
        best_index = np.argmax(np.abs(avl))
        best_index_in_A = var_index_to_elim + best_index
        # if no pivot != 0 was found fail
        best_pivot_val = A[best_index_in_A, var_index_to_elim]
        if best_pivot_val == 0.0:
            raise ValueError("SINGULAR MATRIX")
        # swap pivot in A and b
        best_row = A[best_index_in_A, :]
        original_row = A[var_index_to_elim, :]
        A[best_index_in_A, :] = best_row
        A[var_index_to_elim, :] = original_row
        # now is var_index_to_elim also the index of the equation to elimintate with
        eq_index_to_elim_with = var_index_to_elim
        # eliminate the variable `var_index_to_elim` in the equations var_index_to_elim+1..n-1
        for eq_index_to_elim_in in range(var_index_to_elim + 1, eq_num):
            # elimintate the variable var_index_to_elim in eq_index_to_elim_in
            # I: equation to eliminate with = A[eq_index_to_elim_with, :]
            eq_to_elim_with = A[eq_index_to_elim_with, :]
            # II: equation to eliminate in = A[eq_index_to_elim_in, :]
            eq_to_elim_in = A[eq_index_to_elim_in, :]
            # the variable to use to elimintate with is at I[var_index_to_elim] =
            # A[eq_index_to_elim_with, var_index_to_elim] = A[var_index_to_elim, var_index_to_elim]
            # = coeff_to_elim_with
            coeff_to_elim_with = eq_to_elim_with[var_index_to_elim]
            # the variable to elimintate is at II[var_index_to_elim] = coeff_to_elim =
            # A[eq_index_to_elim_in, var_index_to_elim]
            coeff_to_elim = eq_to_elim_in[var_index_to_elim]
            # alpha = coeff_to_elim/coeff_to_elim_with
            alpha = coeff_to_elim/coeff_to_elim_with
            # II <- II - alpha*I
            eliminated_eq = eq_to_elim_in - alpha*eq_to_elim_with
            # but the equation back
            A[eq_index_to_elim_in, :] = eliminated_eq
            # b[eq_index_to_elim_in] -= b[eq_index_to_elim_with]*alpha
            b[eq_index_to_elim_in] -= alpha*b[eq_index_to_elim_with]
    return A, b

def backsubst(A, b):
    n = b.size
    x = np.zeros(n)
    for var_to_solve in range(n - 1, -1, -1):
        other_subs = 0.0
        for var_to_sub in range(var_to_solve + 1, n):
            other_subs += x[var_to_sub]*A[var_to_solve, var_to_sub]
        x[var_to_solve] = (b[var_to_solve] - other_subs)/A[var_to_solve, var_to_solve]
    return x

A = np.random.rand(3, 3)
b = np.random.rand(3)
print("A:")
print(A)
print("b:")
print(b)
A, b = gauss_elim(A, b)
print("Gaussian Elimination:")
print(A)
print(b)
x = backsubst(A, b)
print("solution x:")
print(x)
print("solution using scipy:")
print(solve(A, b))


import numpy as np

def rotation_matrix2d_ccw(angle:float):
    """
    Function that returns the 2d rotation matrix (counter-clockwise)

    Args:
    -----
        angle(float)    : The angle passed in degrees

    Returns:
    --------
        rot_mat(np.ndarray) : The rotation matrix returned in (2,2)
    """
    rot_mat = np.zeros((2,2))
    angle2rad = np.pi/180

    theta   = angle*angle2rad
    costheta = np.cos(theta)
    sintheta = np.sin(theta)
    rot_mat[0,0] = costheta
    rot_mat[0,1] = -sintheta
    rot_mat[1,0] = sintheta
    rot_mat[1,1] = costheta

    return rot_mat

def rotation_matrix2d_cw(angle:float):
    """
    Function that returns the 2d rotation matrix (clockwise)

    Args:
    -----
        angle(float)    : The angle passed in degrees

    Returns:
    --------
        rot_mat(np.ndarray) : The rotation matrix returned in (2,2)
    """
    rot_mat = rotation_matrix2d_ccw(angle)
    rot_mat[1,0] = -rot_mat[1,0]
    rot_mat[0,1] = -rot_mat[0,1]

    return rot_mat


from math import gamma  # * see also scipy.special
from random import random

import numpy as np
import numpy.linalg as la

from sdia_python.lab2.utils import get_random_number_generator

class BallWindow:
    """Creates a Ball window, meaning a volume around a center point with a radius"""

    def __init__(self, center, radius=1.0):
        """Initialize the BallWindows from the center and the radius

        Args:
            center (list) : Gives the coordinates of the center
            radius (float) : Gives the radius of the ball. Default to 1.
        """

        self.center = np.array(center)
        self.radius = radius

    def __str__(self):
        """Display the BallWindow as a string

        Returns:
            string: BallWindows center and radius
        """

        description = (
            f"BallWindow: center = {list(self.center)} & radius = {self.radius}"
        )
        return description

    def __len__(self):
        """Returns the dimension of the space of the BallWindow

        Returns:
            int: Size of the space containing the BallWindow
        """

        return self.center.size

    def __contains__(self, point):
        """Indicates whether the argument given is inside the Ball Window of not.
        Assertion error if the dimension of the point is not equal to the dimension of the BallWindow

        Args:
            point (np.array): [list of coordinates]

        Returns:
            boolean: True if the point is inside, else returns False
        """

        # ? readability: len(self) => self.dimension()
        assert (
            len(point) == self.dimension()
        )  ##Test if the point has the same dimension
        return la.norm(self.center - point) <= self.radius

    def dimension(self):
        """Gives the dimension of the BallWindows, see __len__

        Returns:
            int: The dimension of the BallWindow
        """

        return len(self)

    def volume(self):
        """Gives the volume of the BallWindow

        Returns:
            int: The volume of the BallWindow
        """
        n = self.dimension()
        R = self.radius
        return (np.pi ** (n / 2) * R ** (n)) / gamma(1 + n / 2)

    def indicator_function(self, array_points):
        """Gives the result of the indicator function of the BallWindows given some points of the same dimension

        Args:
            args ([int]): 1 if the argument is inside the BallWindow, else 0
        """
        # * same remarks as in BoxWindow.indicator_function
        if array_points.ndim > 1:
            return np.array([int(p in self) for p in array_points], dtype=int)
        return int(array_points in self)

    def rand(self, n=1, rng=None):
        """Generate ``n`` points uniformly at random inside the :py:class:`BallWindow`.

        Args:
            n (int, optional): Number of random points to generate. Defaults to 1.
            rng (type, optional): Defaults to None.

        Returns: Array which contains n points randomly uniformly generated

        """
        dim = self.dimension()
        r = self.radius
        rng = get_random_number_generator(rng)

        points_l = np.empty((n, dim))
        for index in range(n):
            # an array of dim normally distributed random variables
            u = np.random.normal(0, 1, dim)
            point_radius = r * random() ** (1.0 / dim)
            points_l[index] = np.reshape(point_radius * u / la.norm(u), (1, 2))
        return points_l + self.center


"""
Problem Statement:
Say that you are a traveler on a 2D grid.
You begin in the top-left corner and your goal is to travel to the bottom-right corner. You may only move down or right.
You may only move down or right.

In how many ways can you travel to the goal on a grid with dimensions m*n?
"""
from functools import lru_cache, partial
from utils.decorators import time_this
import numpy as np

class GridTraveller:
    def __init__(self, m, n):
        self.solutions = {
            "recursive": partial(self.recursive, m, n),
            "dp_traverse_child": partial(self.dp_traverse_child, m, n),
            "dp_reduce_grid": partial(self.dp_reduce_grid, m, n),
            "dp_lru_cache": partial(self.dp_lru_cache, m, n),
            "dp_tabulation": partial(self.dp_tabulation, m, n),
        }

    @staticmethod
    def recursive(m, n):
        """
        Time complexity: O(2^(n+m))
        Space Complexity: O(n+m)

        :param m:
        :param n:
        :return:
        """
        if m == 1 and n == 1:
            return 1
        if m == 0 or n == 0:
            return 0
        return GridTraveller.recursive(m - 1, n) + GridTraveller.recursive(m, n - 1)

    @staticmethod
    def dp_traverse_child(m, n, current=None, grid_value=None):
        start = [0, 0]
        goal = [m - 1, n - 1]
        actions = ('d', 'r')
        if current is None:
            current = start
            grid_value = {}
        if tuple(current) in grid_value.keys():
            return grid_value[tuple(current)]
        value = 0
        if current == goal:
            return 1
        for action in actions:
            if action == "d":
                child = [current[0] + 1, current[1]]
            else:
                child = [current[0], current[1] + 1]

            if child == goal:
                return 1
            if child[0] >= m or child[1] >= n:
                continue
            else:
                value += GridTraveller.dp_traverse_child(m, n, child, grid_value)

        grid_value[tuple(current)] = value
        return grid_value[tuple(current)]

    @staticmethod
    def dp_reduce_grid(m, n, grid_value=None):
        """
        grid_traveller(m,n) == grid_traveller(n,m) (Symmetric)
        Time Complexity: O(nm)
        Space Complexity: O(nm)
        """
        if grid_value is None:
            grid_value = {}
        value = grid_value.get((m, n))
        if value:
            return value
        value = grid_value.get((n, m))
        if value:
            return value
        if m == 1 and n == 1:
            grid_value[(m, n)] = 1
            return 1
        if m == 0 or n == 0:
            grid_value[(m, n)] = 0
            return 0
        grid_value[(m, n)] = GridTraveller.dp_reduce_grid(m - 1, n, grid_value) + GridTraveller.dp_reduce_grid(m, n - 1, grid_value)
        return grid_value[(m, n)]

    @staticmethod
    @lru_cache
    def dp_lru_cache(m, n):
        if m == 1 and n == 1:
            return 1
        if m == 0 or n == 0:
            return 0
        return GridTraveller.dp_lru_cache(min(m - 1, n), max(m - 1, n)) + \
               GridTraveller.dp_lru_cache(min(m, n - 1), max(m, n - 1))

    @staticmethod
    def dp_tabulation(m, n, grid_value=None):
        """
        grid_traveller(m,n) == grid_traveller(n,m) (Symmetric)
        Time Complexity: O(nm)
        Space Complexity: O(nm)
        """
        grid_value_table = np.zeros((m + 1, n + 1), int)
        grid_value_table[1][1] = 1

        for i in range(m + 1):
            for j in range(n + 1):
                if i + 1 <= m:
                    grid_value_table[i + 1][j] += grid_value_table[i][j]
                if j + 1 <= n:
                    grid_value_table[i][j + 1] += grid_value_table[i][j]
        # print(grid_value_table)
        return grid_value_table[m][n]

    @staticmethod
    @time_this()
    def run(func):
        print(f"Solution: {func()}")

    def execute_all(self):

        print("\nSolutions to Grid Traveller\n")

        for name, solution in self.solutions.items():
            print(f'Algo-Name: {name} {" -" * 90}')
            self.run(solution)
            print('-' * 100)

GridTraveller(10, 10).execute_all()


'''
2D Arithmetic
100xp
Remember how you calculated the Body Mass Index for all baseball players?
numpy was able to perform all calculations element-wise (i.e. element by element).
For 2D numpy arrays this isn't any different! You can combine matrices with single
numbers, with vectors, and with other matrices.

Execute the code below in the IPython shell and see if you understand:

import numpy as np
np_mat = np.array([[1, 2],
                   [3, 4],
                   [5, 6]])
np_mat * 2
np_mat + np.array([10, 10])
np_mat + np_mat
np_baseball is coded for you; it's again a 2D numpy array with 3 columns representing
height, weight and age.

Instructions
-You managed to get hold of the changes in weight, height and age of all baseball players.
It is available as a 2D numpy array, updated. Add np_baseball and updated and print out the result.
-You want to convert the units of height and weight. As a first step, create a numpy array
with three values: 0.0254, 0.453592 and 1. Name this array conversion.
-Multiply np_baseball with conversion and print out the result.
'''
# baseball is available as a regular list of lists
# updated is available as 2D numpy array

# Import numpy package
import numpy as np

# Create np_baseball (3 cols)
np_baseball = np.array(baseball)

# Print out addition of np_baseball and updated
print(np_baseball + updated)

# Create numpy array: conversion
conversion = np.array([0.0254, 0.453592, 1.0])

# Print out product of np_baseball and conversion
print(np_baseball * conversion)

Intro to Python for Data Science/NumPy/2d-arithmetic.py


#Determining the Principal Axis of Inertia & the Principal Moments of Inertia from the Inertia Tensor

import numpy as np
np.set_printoptions(precision=4, suppress=True) #It is set to display & secure the output with 4 decimals

I = np.array([[30,5,5],
             [5,20,5],
             [5,5,10]])
print("Inertia Tensor = \n",I)

I_p, P = np.linalg.eig(I) #This function is used to calculate EigenValues & EigenVectors (I_p = EigenValues, P = EigenVectors)

print("Principal Moments of Inertia using EigenValues = \n",I_p)
print("Principal Axis of Inertia = \n",P.T)

#Determining Principal Moment of Inertia vai Diagonalization

I_pp = np.linalg.inv(P)@I@P # @ Operator represents the product of the matrix. (In NumPy * operator attention to become a product of the elements to each other.)
I_pp = np.diag(I_pp) #Using the diag function and retrieve only the diagonal components.
print("Principal Moments of Inertia using Diagonalization = \n",I_pp)


import matplotlib.pyplot as plt
import numpy as np
while True:
    try:
        print('-'*111) # for decoration purpose
        x = input('<< TO CONTINUE PRESS "ENTER" OR TO KILL THE APPLICATION PRESS "0" >>     ')
        if x == '':
            print()
            print('<< THIS APPLICATION CALCULATES ROOTS FOR A GIVEN QUADRATIC EQUATION >>')
            print()
            print('>>> THE EQUATION WILL BE IN FORM OF << a.X^2 + b.X + c >>')
            print()
            a = int(input('enter value of a: '))
            b = int(input('enter value of b: '))
            c = int(input('enter value of c: '))
            d = ((b**2)-4*a*c)**(1/2)
            discriminant = ((b**2)-4*a*c)

            roots_1= ((-b)+d)/(2*a)
            roots_2 = ((-b)-d)/(2*a)
            root_declare = f'>>> The roots are  X = {roots_1}  and  X = {roots_2}.'
            print()
            print(f'>>> THE EQUATION IS ( {a}.X^2 + {b}.X + {c} ).')
            print()
            print(root_declare)
            print()
            print(f'>>> The value of discriminant is {discriminant}.')
            print()

            def root_indicator():                     # tell's root charateristic
                if discriminant > 0:
                    return('The roots of given equation are real.')
                elif discriminant < 0:
                    return('The roots of given equation are imaginary.')
                elif discriminant == 0:
                    return('There is one real root.')
            print(f'>>> {root_indicator()}')
            print()

            print('')
            plot_enable = input('<<< To show the plot press " y " OR To cancel plotting press ENTER >>>     ')
            if plot_enable == 'y':
                def plot_show():
                    # 100 linearly spaced numbers
                    x = np.linspace(-10**2,10**2,10**2)
                    y = np.linspace(-10**2,10**2,10**2)

                    # the function, which is y = x^2 here
                    y = (a*(x**2))+(b*x)+c

                    # setting the axes at the centre
                    fig = plt.figure()
                    ax = fig.add_subplot(1, 1, 1)
                    ax.spines['left'].set_position('center')
                    ax.spines['bottom'].set_position('zero')
                    ax.spines['right'].set_color('none')
                    ax.spines['top'].set_color('none')
                    ax.xaxis.set_ticks_position('bottom')
                    ax.yaxis.set_ticks_position('left')

                    # plot the function
                    plt.plot(x,y, 'r')

                    # show the plot
                    plt.show()
                plot_show()
            elif plot_enable == '':
                print()
                print('>>> Graphing Cancelled')
        elif int(x) == 0:
            break
    except:
        if a == 0:
            print(f'>> a value must be larger than "zero" and enter numerals only.  <<')
        else:
            print()
            print(f'>> Please check the entered value, enter numerals only. <<')


import sys
import numpy as np
import time
import logging
from matplotlib import pyplot as plt
import seaborn as sns

kmax = int(sys.argv[1])
r = int(sys.argv[2])
amin = sys.argv[3]
amax = sys.argv[4]

def plotResults(k, tNaive, tList, tStrassen):
    x = list(range(1, k+1))
    a = sns.lineplot(x, tNaive, label="Naive")
    a = sns.lineplot(x, tList, label="List Comprehesion")
    a = sns.lineplot(x, tStrassen, label="Strassen")
    a.set(xlabel='Tamanho da entrada 2^x', ylabel='Tempo de execução médio (s)')
    a.legend()
    plt.show()

def MultiplyMatrix(A, B):
    """Multiply two matrices using naive algorithm"""
    C = np.zeros((len(A), len(B)), dtype=int)

    for i in range(len(A)):
        for j in range(len(B)):
            for k in range(len(B)):
                C[i][j] += A[i][k] * B[k][j]
    return C

def addMatrix(A,B):
    """Add two matrices"""
    return [[A[i][j] + B[i][j] for j in range(len(A))] for i in range(len(B))]

def subMatrix(A,B):
    """subtraction two matrices"""
    return [[A[i][j] - B[i][j] for j in range(len(A))] for i in range(len(B))]

def Strassen(A, B):
    """Strassen Algorithm to squared matrices"""
    if len(A) <= 2:
        return  matrixMultiplicationListComprehesion(A, B)
    else:
        n = int(len(A)/2)

        a11 = [[A[i][j] for j in range(len(A)) if j < n  ] for i in range(len(A)) if i < n]
        a12 = [[A[i][j] for j in range(len(A)) if j >= n ] for i in range(len(A)) if i < n]
        a21 = [[A[i][j] for j in range(len(A)) if j < n  ] for i in range(len(A)) if i >= n]
        a22 = [[A[i][j] for j in range(len(A)) if j >= n ] for i in range(len(A)) if i >= n]

        b11 = [[B[i][j] for j in range(len(B)) if j < n  ] for i in range(len(B)) if i < n]
        b12 = [[B[i][j] for j in range(len(B)) if j >= n ] for i in range(len(B)) if i < n]
        b21 = [[B[i][j] for j in range(len(B)) if j < n  ] for i in range(len(B)) if i >= n]
        b22 = [[B[i][j] for j in range(len(B)) if j >= n ] for i in range(len(B)) if i >= n]

        m1 = Strassen(addMatrix(a11,a22), addMatrix(b11,b22))
        m2 = Strassen(addMatrix(a21,a22), b11)
        m3 = Strassen(a11, subMatrix(b12, b22))
        m4 = Strassen(a22, subMatrix(b21, b11))
        m5 = Strassen(addMatrix(a11, a12), b22)
        m6 = Strassen(subMatrix(a21, a11), addMatrix(b11, b12))
        m7 = Strassen(subMatrix(a12, a22), addMatrix(b21, b22))

        c11 = addMatrix(addMatrix(m1, m4), subMatrix(m7, m5))
        c12 = addMatrix(m3, m5)
        c21 = addMatrix(m2, m4)
        c22 = addMatrix(subMatrix(m1, m2), addMatrix(m3, m6))

        C = np.concatenate((np.concatenate((c11, c12), axis=1), np.concatenate((c21, c22), axis=1)), axis = 0 )

        return C

def matrixMultiplicationListComprehesion(A, B):
    """Multiply two squared matrices using list comprehesion"""
    return [[sum([x*y for (x, y) in zip(row, col)]) for col in zip(*B)] for row in A]

if __name__ == "__main__":

    tNaive = [0] * kmax
    tList = [0] * kmax
    tStrassen = [0] * kmax

    format = "%(asctime)s: %(message)s"
    logging.basicConfig(format=format, level=logging.INFO, datefmt="%Y-%m-%d %H:%M:%S")

    for n in range(1, kmax+1):
        for _ in range(r):
            A = np.random.randint(low = amin, high = amax, size = (2**n,2**n))
            B = np.random.randint(low = amin, high = amax, size = (2**n,2**n))

            start_time = time.time()
            C = MultiplyMatrix(A,B)
            end_time = time.time()
            tNaive[n-1] += (end_time - start_time)

            start_time = time.time()
            D = matrixMultiplicationListComprehesion(A,B)
            end_time = time.time()
            tList[n-1] += (end_time - start_time)

            start_time = time.time()
            E = Strassen(A,B)
            end_time = time.time()
            tStrassen[n-1] += (end_time - start_time)

        logging.debug("2^%d: {Naive: %f}, {List: %f}, {Strassen: %f}", n, tNaive[n-1], tList[n-1], tStrassen[n-1])
        logging.info("2^%d: Average {Naive: %f}, {List: %f}, {Strassen: %f}", n, tNaive[n-1]/r, tList[n-1]/r, tStrassen[n-1]/r)

    plotResults(kmax, [x/r for x in tNaive], [x/r for x in tList], [x/r for x in tStrassen])


import numpy as np

def NW_corner_rule(a, b):
    """
    North-West Corner rule for discrete optimal transport:
        Finding the vertex of the polytope of the feasible set U(a, b),
        where the minimum is attained.

    Reference: P44, Sec 3.4.2

    @input a, b: np.ndarray, of shape (n, ) and (m, ), the source histogram and the target histogram
    @return P: np.ndarray, of shape (n, m), one of the vertex of the polytope, P[i, j]: the flow from a[i] to b[j]

    >>> a = np.array([0.2, 0.5, 0.3])
    >>> b = np.array([0.5, 0.1, 0.4])
    >>> NW_corner_rule(a, b)
    array([[0.2, 0. , 0. ],
           [0.3, 0.1, 0.1],
           [0. , 0. , 0.3]])
    """
    P = np.zeros((a.shape[0], b.shape[0]))
    i = j = 0
    r = a[0]
    c = b[0]
    while i < a.shape[0] and j < b.shape[0]:
        P[i, j] = min(r, c)
        r -= P[i, j]
        c -= P[i, j]
        if r == 0:
            i += 1
            if i < a.shape[0]:
                r = a[i]
        if c == 0:
            j += 1
            if j < b.shape[0]:
                c = b[j]
    return P

def compute_NW_solutions(a, b, sigma1, sigma2):
    """
    compute an arbitrary NW solution by permutation

    Reference: Page 45, Section 3.4.2

    @param a, b: the source histogram and the target histogram
    @param sigma1, sigma2: the permutation vector for a and b respectively
    @return P_permutation: the permutated NW solution by
        P_permutation = sigma1_inv(sigma2_inv(P, column), row)
        where P is the transport matrix of sigma1(a) and sigma2(b)

    >>> a = np.array([0.2, 0.5, 0.3])
    >>> b = np.array([0.5, 0.1, 0.4])
    >>> sigma1 = np.array([2, 0, 1])
    >>> sigma2 = np.array([2, 1, 0])
    >>> compute_NW_solutions(a, b, sigma1, sigma2)
    array([[0., 0.1, 0.1],
        [0.5, 0., 0.],
        [0., 0., 0.3]])
    """
    a = a[sigma1]
    b = b[sigma2]
    P = NW_corner_rule(a, b)
    print(P)
    sigma1_inv = np.argsort(sigma1)
    sigma2_inv = np.argsort(sigma2)
    P = P[:, sigma2_inv]
    P = P[sigma1_inv, :]
    return P

if __name__ == "__main__":
    import doctest
    doctest.testmod()


"""
Exercise 3
Find the area enclosed by two curves between two points.
"""

from sympy import Integral, Symbol, SympifyError, sympify

def find_area(f1x, f2x, var, a, b):
    return Integral(f1x - f2x, (var, a, b)).doit()

def validate_function(function):

    try:
        return sympify(function)
    except SympifyError:
        print("Invalid function entered")
        sys.exit(1)

    return None

if __name__ == "__main__":
    f1 = input("Enter the upper function in one variable: ")
    f1 = validate_function(f1)

    f2 = input("Enter the lower upper function in one variable: ")
    f2 = validate_function(f2)

    variable = input("Enter the variable: ")
    variable = Symbol(variable)

    lower_bound = float(input("Enter the lower bound of the enclosed region: "))
    upper_bound = float(input("Enter the upper bound of the enclosed region: "))

    print(
        "Area enclosed by {} and {} is {} ".format(
            f1, f2, find_area(f1, f2, variable, lower_bound, upper_bound)
        )
    )


import numpy as np

def calc_weights(x, y):
    """
    Calculating weights in least square

    Parameters
    ----------
    x: numpy.ndarray or shape (n_samples, n_features)
        the data to be finding the weights for features.

    y: numpy.ndarray or shape (n_samples, )
        the target data of x.

    Returns
    -------
    output: numpy.ndarray or shape (n_features, )
        the weights of x
    """

    x = np.array(x)
    y = np.array(y)

    mean_x = x.mean(axis=0)
    mean_y = y.mean(axis=0)

    distance_x = x - mean_x
    distance_y = y - mean_y

    distance_xy = distance_x * distance_y.reshape((distance_y.shape[0], 1))

    x_square = (x - mean_x) ** 2

    numerator = np.sum(distance_xy, axis=0)
    denumerator = np.sum(x_square, axis=0)

    output = numerator / denumerator

    return output

def calc_intercept(X, y, slope):
    """
    Calculating intercept in least square

    Parameters
    ----------
    x: numpy.ndarray or shape (n_samples, n_features)
        the data to be finding the weights for features.

    y: numpy.ndarray or shape (n_samples, )
        the target data of x.

    slope: numpy.ndarray or shape (n_features, )
        the weighted of x

    Returns
    -------
    output: int
        the result of calculating intercept in least square

    """

    X = np.array(X)
    y = np.array(y)

    mean_y = y.mean(axis=0)
    mean_x = X.mean(axis=0)

    weighted_mean_x = slope * mean_x

    summing_weighted_mean_x = np.sum(weighted_mean_x)

    output = mean_y - summing_weighted_mean_x

    return output

def calc_with_svd(a, b):
    """
    Finding x with svd in equation ax = b.
    where a and b are known.

    Parameters
    ----------
    a: numpy.ndarray or shape (n, f)
        The independent variables to be calculate with svd

    b: numpy.ndarray or shape (n, )
        The dependent variable of a

    Returns
    -------
    x: numpy.ndarray or shape (f, )
        Solved to find x of ax = b equation

    """
    # change to numpy.array
    a = np.array(a)
    b = np.array(b)

    # raises the error
    if a.shape[0] != b.shape[0]:
        raise ValueError("the rows of a and b supposed to be same")
    elif len(b.shape) != 1:
        raise ValueError("for now the shape of b supposed to be (n, ")

    # solving least square with svd
    x, residuals, rank, s = np.linalg.lstsq(a, b, rcond=None)

    return x


import numpy as np
from numpy import ndarray

def linear_score_function(n: int) -> ndarray:
    """
    With the linear score function the "points" awarded scale linearly from first place
    through last place. For example, improving from 2nd to 1st place has the same sized
    benefit as improving from 5th to 4th place.

    :param n: number of players
    :return: array of the points to assign to each place (summing to 1)
    """
    return np.array([(n - p) / (n * (n - 1) / 2) for p in range(1, n + 1)])

def create_exponential_score_function(base: float):
    """
    With an exponential score function with base > 1, more points are awarded to the top
    finishers and the point distribution is flatter at the bottom. For example, improving
    from 2nd to 1st place is more valuable than improving from 5th place to 4th place. A
    larger base value means the scores will be more weighted towards the top finishers.

    :param base: base for teh exponential score function (> 1)
    :return: a function that takes parameter n for number of players and returns an array
    of the points to assign to each place (summing to 1)
    """
    return lambda n: _exponential_score_template(n, base)

def _exponential_score_template(n: int, base: float) -> ndarray:
    if base < 1:
        raise ValueError("base must be >= 1")
    if base == 1:
        return linear_score_function(n)  # it converges to this as base -> 1

    out = np.array([base ** (n - p) - 1 for p in range(1, n + 1)])
    return out / sum(out)


import numpy as np
import matplotlib .pyplot as plt

N = 50

X = np.linspace(0, 10, N)
Y = 0.5 * X + np.random.randn(N)
# outlier
Y[-1] += 30
Y[-2] += 30

plt.scatter(X, Y)
plt.show()

X = np.vstack([np.ones(N), X]).T

w_ml = np.linalg.solve(X.T.dot(X), X.T.dot(Y))
y_hat_ml = X.dot(w_ml)
plt.scatter(X[:, 1], Y)
plt.plot(X[:, 1], y_hat_ml)
plt.show()

l2 = 1000
w_map = np.linalg.solve(l2 * np.eye(2) + X.T.dot(X), X.T.dot(Y))
y_hat_map = X.dot(w_map)
plt.scatter(X[:, 1], Y)
plt.plot(X[:, 1], y_hat_ml, label='maxmimum likelihood')
plt.plot(X[:, 1], y_hat_map, label='map')
plt.legend()
plt.show()


"""
reference : https://en.wikipedia.org/wiki/PageRank
"""
import numpy as np

def pr(adjacency_arr, N_iter = 20, d = 0.85):
    M = np.zeros(adjacency_arr.shape)
    for m in range(M.shape[0]):
        M[:, m] = adjacency_arr[:, m] / adjacency_arr[:, m].sum()

    e = np.ones(adjacency_arr.shape[0])*1.0 / adjacency_arr.shape[0]
    v0 = e.copy()
    for i in range(N_iter):
        vn = np.dot(M, v0)*d + e*(1-d)
        v0 = vn
    return vn

if __name__ == "__main__":
	adjacency_arr = np.array([[0, 0, 0, 0, 1],
	              [0.5, 0, 0, 0, 0],
	              [0.5, 0, 0, 0, 0],
	              [0, 1, 0.5, 0, 0],
	              [0, 0, 0.5, 1, 0]])
	print("ground truth\n", [[0.25419178],
	       [0.13803151],
	       [0.13803151],
	       [0.20599017],
	       [0.26375504]])
	vn = pr(adjacency_arr, N_iter = 100, d = 0.85)
	print("result\n", vn)


import numpy as np
from scipy import integrate

conturs_areas = np.array([194135, 136366, 79745, 38335, 18450, 9635, 3895])
x = np.array([0,25,50,75,100,125,150])

vol_traps = integrate.trapz(conturs_areas, x)
vol_simps = integrate.simps(conturs_areas, x)

print('The trapezoidal rule returns a volume of {:.0f} cubic meters'.format(vol_traps))
print('The composite Simpson rule returns a volume of {:.0f} cubic meters'.format(vol_simps))

'''
Output:
The trapezoidal rule returns a volume of 9538650 cubic meters
The composite Simpson rule returns a volume of 9431367 cubic meters
'''


from math import *
import operator as op
import numpy as np

dim  = 512
k = 8
q = 12289
bound_CC = 34000    # Bound n*mu+beta to use in Cramer-Chernoff Ineq.
n = 2 * dim / 2      # Number of samples in Cramer-Chernoff Ineq.

check_each_y = True
# Check each distribution phi_y (slower)
#experimentally, they are always equal probably due to symmetries

subg_param = sqrt(k/2)
tau = 17.6

subg_value = sqrt(bound_CC) * subg_param * tau
p_subg = exp(-tau**2/2 )

print "#### "
print "#### Correctness check :"
print "#### "

print "target bound (before +e'')=", subg_value
print "target bound =", subg_value + 2*k
print "(sqrt(2)/2.) * 3q/4 =", (sqrt(2)/2.) * 3*q/4.
print  "Correct ?", subg_value + 2*k < (sqrt(2)/2.) * 3*q/4.
assert subg_value + 2*k < (sqrt(2)/2.) * 3*q/4.

print
print "#### "
print "#### Lemma 6 Verification :"
print "#### "

def rot(v):
	return [-v[1],v[0]]

if check_each_y:
	y_list = [
		[ 1, 1],
		[ 1,-1],
		[-1, 1],
		[-1,-1]
	]
else:
	y_list = [[ 1, 1]]

### Construct the binomial law
supp = xrange(-k,k+1)

def binomial(n, r):
    r = min(r, n-r)
    if r == 0: return 1
    numer = reduce(op.mul, xrange(n, n-r, -1))
    denom = reduce(op.mul, xrange(1, r+1))
    return numer//denom

def pdf_binom(x):
	return binomial(2*k,x+k) / 2.**(2*k)

# Union bound on each application of Chernoff-Cramer
Union_CC = 0.

for y in y_list:
	print "y=",y
	### Construct a 4*4 matrix associated to y
	v1 = y
	v2 = rot(v1)
	mat = np.matrix([v1,v2])

	### Initialize a table to store the pdf of \varphi_y
	### the range of the above distribution
	ima = xrange(4 * (k*4)**2 )
	varphi_y_Table = [0 for i in ima]

	### Brute force computation of the pdf of || x*y ||^2
	s = 0.
	for x1 in supp:
		for x2 in supp:
			x = np.array([x1,x2])
			mx = x * mat
			s = mx[0,0]**2 + mx[0,1]**2
			p = pdf_binom(x1) * pdf_binom(x2)
			varphi_y_Table[s] += p                   ### The result is always a multiple of 4

	avg = sum([i*varphi_y_Table[i] for i in ima])
	print "varphi_y has average ",avg

	def Moment(t):
		return sum([varphi_y_Table[i] * exp(t*(i - avg)) for i in range(len(ima))])

	def ChernoffCramer(n,t,bound):
		beta = bound - n*avg
		p =  exp(- t*beta + n*log(Moment(t)))
		return p

	t_CC = 0.0060
	p_CC = ChernoffCramer(n,t_CC,bound_CC)

	print "Chernoff-Cramer Bound", p_CC, " = 2^", log(p_CC)/log(2)

	if check_each_y:
		Union_CC += p_CC

else:
	Union_CC = 4*p_CC

print
print "#### "
print "#### Conclusion of Lemma 6 :"
print "#### "

print "with parameter k=",k, "and parameter n = ",n,"(v has dimension 4n)"
print "The bound ||v||_2^2 <=", bound_CC, " fails with probability less than 2^", log(Union_CC)/log(2)

print "#### "
print "#### Corollary 1 :"
print "#### "

print "subgaussian parameter sigma = ", subg_param
print "tailcut parameter tau = ", tau
print "tailcut value ||v||* sigma * tau = ", subg_value

print "sub-gaussian tail-bound (Lemma 5): 2^", log(p_subg) /log(2)
print "(failure proba per bit-agreement and per y)"
print
print "#### "
print "#### Conclusion of Corollary 1 :"
print "#### "
print "Partial bound ||(es'-e's )_i||_1 <=", subg_value

print "Final bound ||(es'-e's +e'')_i||_1 <=", subg_value + 2*k
print "fails with probability at most 2^",
print log(Union_CC + 4 * 256 * p_subg)/log(2)


import numpy as np

### Functions for you to fill in ###

def polynomial_kernel(X, Y, c, p):
    """
        Compute the polynomial kernel between two matrices X and Y::
            K(x, y) = (<x, y> + c)^p
        for each pair of rows x in X and y in Y.

        Args:
            X - (n, d) NumPy array (n datapoints each with d features)
            Y - (m, d) NumPy array (m datapoints each with d features)
            c - a coefficient to trade off high-order and low-order terms (scalar)
            p - the degree of the polynomial kernel

        Returns:
            kernel_matrix - (n, m) Numpy array containing the kernel matrix
    """
    kernel_matrix = (np.inner(X, Y) + c) ** p
    return kernel_matrix

def rbf_kernel(X, Y, gamma):
    """
        Compute the Gaussian RBF kernel between two matrices X and Y::
            K(x, y) = exp(-gamma ||x-y||^2)
        for each pair of rows x in X and y in Y.

        Args:
            X - (n, d) NumPy array (n datapoints each with d features)
            Y - (m, d) NumPy array (m datapoints each with d features)
            gamma - the gamma parameter of gaussian function (scalar)

        Returns:
            kernel_matrix - (n, m) Numpy array containing the kernel matrix
    """
    XT_X = np.mat([np.matmul(row, row.T) for row in X]).T
    YT_Y = np.mat([np.matmul(row, row.T) for row in Y]).T

    XTX_matrix = np.repeat(XT_X, Y.shape[0], axis=1)
    YTY_matrix = np.repeat(YT_Y, X.shape[0], axis=1).T

    distance_matrix = np.asarray(XTX_matrix + YTY_matrix - 2 * (np.matmul(X, Y.T)), dtype='float64')
    kernel_matrix = np.exp(-gamma * distance_matrix)
    return kernel_matrix


"""
Problem 4
"""

import numpy as np

s_inf = np.pi/4

def term(n):
    return (-1) ** n / (2 * n + 1)

def basic_sum(tol):
    n = 0
    val = 0
    while True:
        val += term(n)
        if np.abs(s_inf - val) < tol:
            return val, n
        n += 1

def aitkin_sum(tol):
    n = 0
    while True:
        sn2 = np.sum(term(np.arange(0, n+3)))
        sn1 = sn2 - term(n+2)
        sn0 = sn1 - term(n+1)
        s_aitkin = sn0 - (sn1 - sn0)**2 / (sn2 - 2*sn1 + sn0)
        if np.abs(s_inf - s_aitkin) < tol:
            return s_aitkin, n
        n += 1

if __name__ == '__main__':
    print(basic_sum(1e-7))
    print(aitkin_sum(1e-7))


"""This module creates the contour plots for the bivariate normal distribution."""
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import multivariate_normal

from fig_config import OUTPUT_DIR

plt.style.use("resources/grmpy.mplstyle")

y_min, y_max = -4, 4

x = np.linspace(y_min, y_max, 100)
y = np.linspace(y_min, y_max, 100)
X, Y = np.meshgrid(x, y)
pos = np.dstack((X, Y))

mean = np.tile(0.2, 2)
cov = np.identity(2) * 2

rv = multivariate_normal(mean, cov)
rv.pdf(pos)

ax = plt.figure().add_subplot(111)

levels = np.linspace(0.1, 1.0, 10, endpoint=True)
cns = plt.contourf(X, Y, rv.pdf(pos) / np.max(rv.pdf(pos)), levels=levels)

ax.set_ylabel("$Y_1$")
ax.set_xlabel("$Y_0$")
ax.set_ylim(y_min, y_max)
ax.set_xlim(y_min, y_max)

ax.set_yticklabels([])
ax.set_xticklabels([])

plt.plot(
    np.arange(y_min, y_max), np.arange(y_min, y_max), color="black", linestyle="--"
)

plt.colorbar(cns)

ax.text(3.3, 3.3, r"$45^o$")

plt.savefig(OUTPUT_DIR + "/fig-distribution-joint-potential.png", dpi=300)

# This plot shows the joint distribution of surplus and benefits.
x = np.linspace(y_min, y_max, 100)
y = np.linspace(y_min, y_max, 100)
X, Y = np.meshgrid(x, y)
pos = np.dstack((X, Y))

mean = np.tile(0.2, 2)
cov = np.identity(2) * 2

rv = multivariate_normal(mean, cov)
rv.pdf(pos)

ax = plt.figure().add_subplot(111)

levels = np.linspace(0.1, 1.0, 10, endpoint=True)
cns = plt.contourf(X, Y, rv.pdf(pos) / np.max(rv.pdf(pos)), levels=levels)

ax.set_ylabel("$B$")
ax.set_xlabel("$S$")
ax.set_ylim(y_min, y_max)
ax.set_xlim(y_min, y_max)

ax.set_yticklabels(["", "", "", "", 0])
ax.set_xticklabels(["", "", "", "", 0])

plt.colorbar(cns)

ax.axvline(x=0, color="black", linestyle="--")
ax.axhline(y=0, color="black", linestyle="--")

ax.axvline(x=-0.5, color="lightgray", linestyle="--")

ax.text(x=-3.5, y=3.5, s="I")

ax.text(x=3.5, y=3.5, s="II")

ax.text(x=3.5, y=-3.5, s="III")

ax.text(x=-3.5, y=-3.5, s="IV")

plt.savefig(OUTPUT_DIR + "/fig-distribution-joint-surplus.png", dpi=300)


"""

Nombre del archivo: mandelbrot.py
Fecha de creación: 19/09/2019
Fecha de modificación: 20/09/2019
Autores: Bryan Steven Biojó R.     1529879-2711
         Joel Alexander Ramírez N. 1528879-2711

"""

import numpy # Librería para computación científica.
import matplotlib.pyplot as plt # Librería para graficar.
from numba import jit # Compilador de JIT que mejora la rapidez del código.

# ******************************************* FUNCIÓN 1 *******************************************

@jit
def mandelbrot1(Re, Im, maxIteraciones): # Primera función. Pinta z^2+C
    z = 0.0j
    c = complex(Re, Im)

    for i in range(maxIteraciones):
        z = pow(z, 2) + c

        if((pow(z.real, 2) + pow(z.imag, 2)) >= 4):
            return i

    return maxIteraciones

# ******************************************* GRÁFICA 1 *******************************************

filas = 2000
columnas = 2000

resultado = numpy.zeros([filas, columnas])
for fila_index, Re in enumerate(numpy.linspace(-2, 1, num = filas)):
    for columna_index, Im in enumerate(numpy.linspace(-1, 1, num = columnas)):
        resultado[fila_index, columna_index] = mandelbrot1(Re, Im, 100)

plt.figure(dpi = 300)
plt.imshow(resultado.T, cmap = 'gray', interpolation = 'bilinear', extent = [-2, 1, -1, 1])
plt.xlabel('Eje Real')
plt.ylabel('Eje Imaginario')
plt.show()

# ******************************************* FUNCIÓN 2 *******************************************

@jit
def mandelbrot2(Re, Im, maxIteraciones): # Segunda función. Pinta z^3+C
    z = 0.0j
    c = complex(Re, Im)

    for i in range(maxIteraciones):
        z = pow(z, 3) + c

        if((pow(z.real, 2) + pow(z.imag, 2)) >= 4):
            return i

    return maxIteraciones

# ******************************************* GRÁFICA 2 *******************************************

filas = 2000
columnas = 2000

resultado = numpy.zeros([filas, columnas])
for fila_index, Re in enumerate(numpy.linspace(-2, 1, num = filas)):
    for columna_index, Im in enumerate(numpy.linspace(-1.5, 1.5, num = columnas)):
        resultado[fila_index, columna_index] = mandelbrot2(Re, Im, 100)

plt.figure(dpi = 300)
plt.imshow(resultado.T, cmap = 'hot', interpolation = 'bilinear', extent = [-2, 1, -1.5, 1.5])
plt.xlabel('Eje Real')
plt.ylabel('Eje Imaginario')
plt.show()

# ******************************************* FUNCIÓN 3 *******************************************

@jit
def mandelbrot3(Re, Im, maxIteraciones): # Tecera función. Pinta z^5+C
    z = 0.0j
    c = complex(Re, Im)

    for i in range(maxIteraciones):
        z = pow(z, 5) + c

        if((pow(z.real, 2) + pow(z.imag, 2)) >= 4):
            return i

    return maxIteraciones

# ******************************************* GRÁFICA 3 *******************************************

filas = 2000
columnas = 2000

resultado = numpy.zeros([filas, columnas])
for fila_index, Re in enumerate(numpy.linspace(-2, 1, num = filas)):
    for columna_index, Im in enumerate(numpy.linspace(-1, 1, num = columnas)):
        resultado[fila_index, columna_index] = mandelbrot3(Re, Im, 100)

plt.figure(dpi = 300)
plt.imshow(resultado.T, cmap = 'winter', interpolation = 'bilinear', extent = [-2, 1, -1, 1])
plt.xlabel('Eje Real')
plt.ylabel('Eje Imaginario')
plt.show()

# ******************************************* FUNCIÓN 4 *******************************************

@jit
def mandelbrot4(Re, Im, maxIteraciones): # Cuarta función. Pinta z^2+(1/C)
    z = 0.0j
    c = complex(Re, Im)

    for i in range(maxIteraciones):
        z = pow(z, 2) + (1 / c)

        if((pow(z.real, 2) + pow(z.imag, 2)) >= 4):
            return i

    return maxIteraciones

# ******************************************* GRÁFICA 4 *******************************************

filas = 2000
columnas = 2000

resultado = numpy.zeros([filas, columnas])
for fila_index, Re in enumerate(numpy.linspace(-2, 4, num = filas)):
    for columna_index, Im in enumerate(numpy.linspace(-3, 3, num = columnas)):
        resultado[fila_index, columna_index] = mandelbrot4(Re, Im, 100)

plt.figure(dpi = 300)
plt.imshow(resultado.T, cmap = 'Wistia', interpolation = 'bilinear', extent = [-2, 4, -3, 3])
plt.xlabel('Eje Real')
plt.ylabel('Eje Imaginario')
plt.show()

# ******************************************* FUNCIÓN 5 *******************************************

@jit
def mandelbrot5(Re, Im, maxIteraciones): # Quinta función. Pinta z^7+(1/C)
    z = 0.0j
    c = complex(Re, Im)

    for i in range(maxIteraciones):
        z = pow(z, 7) + (1 / c)

        if((pow(z.real, 2) + pow(z.imag, 2)) >= 4):
            return i

    return maxIteraciones

# ******************************************* GRÁFICA 5 *******************************************

filas = 2000
columnas = 2000

resultado = numpy.zeros([filas, columnas])
for fila_index, Re in enumerate(numpy.linspace(-2, 2, num = filas)):
    for columna_index, Im in enumerate(numpy.linspace(-2, 2, num = columnas)):
        resultado[fila_index, columna_index] = mandelbrot5(Re, Im, 100)

plt.figure(dpi = 300)
plt.imshow(resultado.T, cmap = 'autumn', interpolation = 'bilinear', extent = [-2, 2, -2, 2])
plt.xlabel('Eje Real')
plt.ylabel('Eje Imaginario')
plt.show()

# ******************************************* FUNCIÓN 6 *******************************************

@jit
def mandelbrot6(Re, Im, maxIteraciones): # Sexta función. Pinta z^2+(1/C^2)
    z = 0.0j
    c = complex(Re, Im)

    for i in range(maxIteraciones):
        z = pow(z, 2) + (1 / (c * c))

        if((pow(z.real, 2) + pow(z.imag, 2)) >= 4):
            return i

    return maxIteraciones

# ******************************************* GRÁFICA 6 *******************************************

filas = 2000
columnas = 2000

resultado = numpy.zeros([filas, columnas])
for fila_index, Re in enumerate(numpy.linspace(-2, 2, num = filas)):
    for columna_index, Im in enumerate(numpy.linspace(-3, 1.5, num = columnas)):
        resultado[fila_index, columna_index] = mandelbrot6(Re, Im, 100)

plt.figure(dpi = 300)
plt.imshow(resultado.T, cmap = 'bone', interpolation = 'bilinear', extent = [-2, 2, -3, 1.5])
plt.xlabel('Eje Real')
plt.ylabel('Eje Imaginario')
plt.show()

# ******************************************* FUNCIÓN 7 *******************************************

@jit
def mandelbrot7(Re, Im, maxIteraciones): # Séptima función. Pinta z^2+C+(1/C)
    z = 0.0j
    c = complex(Re, Im)

    for i in range(maxIteraciones):
        z = pow(z, 2) + c + (1 / c)

        if((pow(z.real, 2) + pow(z.imag, 2)) >= 4):
            return i

    return maxIteraciones

# ******************************************* GRÁFICA 7 *******************************************

filas = 2000
columnas = 2000

resultado = numpy.zeros([filas, columnas])
for fila_index, Re in enumerate(numpy.linspace(-2, 2, num = filas)):
    for columna_index, Im in enumerate(numpy.linspace(-3, 2, num = columnas)):
        resultado[fila_index, columna_index] = mandelbrot7(Re, Im, 100)

plt.figure(dpi = 300)
plt.imshow(resultado.T, cmap = 'magma', interpolation = 'bilinear', extent = [-2, 2, -3, 2])
plt.xlabel('Eje Real')
plt.ylabel('Eje Imaginario')
plt.show()

# ******************************************* FUNCIÓN 8 *******************************************

@jit
def mandelbrot8(Re, Im, maxIteraciones): # Octava función. Pinta [(z+C^2-1)/C^2]^2
    z = 0.0j
    c = complex(Re, Im)

    for i in range(maxIteraciones):
        z = pow(((z + (c * c) - 1) / (c * c)), 2)

        if((pow(z.real, 2) + pow(z.imag, 2)) >= 4):
            return i

    return maxIteraciones

# ******************************************* GRÁFICA 8 *******************************************

filas = 2000
columnas = 2000

resultado = numpy.zeros([filas, columnas])
for fila_index, Re in enumerate(numpy.linspace(-2, 2, num = filas)):
    for columna_index, Im in enumerate(numpy.linspace(-2, 2, num = columnas)):
        resultado[fila_index, columna_index] = mandelbrot8(Re, Im, 100)

plt.figure(dpi = 300)
plt.imshow(resultado.T, cmap = 'binary', interpolation = 'bilinear', extent = [-2, 2, -2, 2])
plt.xlabel('Eje Real')
plt.ylabel('Eje Imaginario')
plt.show()

# ******************************************* FUNCIÓN 9 *******************************************

@jit
def mandelbrot9(Re, Im, maxIteraciones): # Novena función. Pinta [z^2+(1/C) interseccción z^2+C (o eso parece).
    z = 0.0j
    c = complex(Re, Im)

    for i in range(maxIteraciones):
        z = pow(z, 2) + (1 / c)
        z = pow(z, 2) + c

        if((pow(z.real, 2) + pow(z.imag, 2)) >= 4):
            return i

    return maxIteraciones

# ******************************************* GRÁFICA 9 *******************************************

filas = 2000
columnas = 2000

resultado = numpy.zeros([filas, columnas])
for fila_index, Re in enumerate(numpy.linspace(-2, 2, num = filas)):
    for columna_index, Im in enumerate(numpy.linspace(-2, 2, num = columnas)):
        resultado[fila_index, columna_index] = mandelbrot9(Re, Im, 100)

plt.figure(dpi = 300)
plt.imshow(resultado.T, cmap = 'Greens', interpolation = 'bilinear', extent = [-2, 2, -2, 2])
plt.xlabel('Eje Real')
plt.ylabel('Eje Imaginario')
plt.show()

# ******************************************* FUNCIÓN 10 ******************************************

@jit
def mandelbrot10(Re, Im, maxIteraciones): # Décima función. Pinta z^3-z+C
    z = 0.0j
    c = complex(Re, Im)

    for i in range(maxIteraciones):
        z = pow(z, 3) - z + c

        if((pow(z.real, 2) + pow(z.imag, 2)) >= 4):
            return i

    return maxIteraciones

# ******************************************* GRÁFICA 10 ******************************************

filas = 2000
columnas = 2000

resultado = numpy.zeros([filas, columnas])
for fila_index, Re in enumerate(numpy.linspace(-2, 2, num = filas)):
    for columna_index, Im in enumerate(numpy.linspace(-1, 1, num = columnas)):
        resultado[fila_index, columna_index] = mandelbrot10(Re, Im, 100)

plt.figure(dpi = 300)
plt.imshow(resultado.T, cmap = 'YlOrBr', interpolation = 'bilinear', extent = [-2, 1, -1, 1])
plt.xlabel('Eje Real')
plt.ylabel('Eje Imaginario')
plt.show()