import numpy as np
import matplotlib.pylab as plt

#1) almacene los datos de WDBC.dat. Los contenidos de cada columna estan descritos en WDBC.doc. En la columna de benigno y maligno se cambio en m y b por 1 y 0 (http://ftp.cs.wisc.edu/math-prog/cpo-dataset/machine-learn/cancer/WDBC/)

entrada=np.genfromtxt("WDBC.dat",delimiter=',')

datos=entrada[:,2:]
muestras,variables=np.shape(datos)

#2) Usando su propia implementacion, calcule e imprima la matriz de covarianza de sus datos. Revise que su matriz sea correcta comprarando con el resultado de np.cov.

def covarianza(v1,v2):
    N=len(v1)
    cov=(np.sum((v1-np.mean(v1))*(v2-np.mean(v2))))/(N-1)
    return cov

def covmatrix(matriz):                    # Entra matriz con #filas=#muestras y #columnas=#variables
    m,v=np.shape(matriz)
    A=np.zeros((v,v))

    for i in range(v):
        for j in range(v):
            A[i,j]=(covarianza(datos[:,i],datos[:,j]))

    return A

M=covmatrix(datos)
print('La matriz de covarianza obtenida es:')
print(M)

#3) Usando los paquetes de numpy calcule los autovalores y autovectores de las variables originales.
evalues,evectors=np.linalg.eig(M)
porcentajes=np.zeros(len(evalues))

for i in range(len(evalues)):
    porcentajes[i]=(evalues[i]*100)/np.sum(evalues)

# 4) Organice sus autovalores y autovectores e imprima el autovector con mayor autovalor (puede usar paquetes de numpy para encontrar autovalores y autovectores. lea cuidadosamente la documentacion.)
print('')
for i in range(len(evalues)):
    print('PC',i+1)
    print('Eigenvalue:',evalues[i],'. Percentage:',porcentajes[i],'%')
    print('Corresponding eigenvector:')
    print(evectors[:,i])

# 5) Grafique sus datos en el nuevo sistema de referencia (PC1 PC2), usando colores distintos si el tumor es maligno o beningno.

PC1=np.dot(datos,evectors[:,0])
PC2=np.dot(datos,evectors[:,1])

lista=np.array([entrada[:,1],PC1,PC2])
lista=lista.transpose()
malignos=[]
benignos=[]
for i in range(len(PC1)):
    if(entrada[i,1]==1):
        benignos.append([PC1[i],PC2[i]])
    else:
        malignos.append([PC1[i],PC2[i]])

m=np.array(malignos)
b=np.array(benignos)

plt.figure()
plt.scatter(b[:,0],b[:,1],c='b',s=10,label='Benignos')
plt.scatter(m[:,0],m[:,1],c='r',s=10,label='Malignos')
plt.title('PCA')
plt.xlabel('PC1')
plt.ylabel('PC2')
plt.legend()
plt.grid()
plt.savefig('PCA.png')
plt.close()

# 6) Imprima un mensaje diciendo si usted cree que el metodo de PCA puede ser util para hacer la clasificacion entre tumores malignos y benignos y ayudar al diagnostico para ciertos pacientes, argumentando claramente su posicion.
print('Los datos se pueden observar claramente diferenciados en el diagrama de PCA, por lo que realmente sí es útil a la hora de clasificar tumores malignos y benignos')
#


import numpy as np
import matplotlib.pyplot as plt

# Part A: Numerical Differentiation Closure
def numerical_diff(f,h):
    def inner(x):
        return (f(x+h) - f(x))/h
    return inner

# Part B:
f = np.log
x = np.linspace(0.2, 0.4, 500)
h = [1e-1, 1e-7, 1e-15]
y_analytical = 1/x
result = {}

for i in h:
    y = numerical_diff(f,i)(x)
    result[i] = y

# Plotting
plt.figure(figsize = (8,5))
plt.plot(x, y_analytical, 'x-', label='Analytical Derivative')
for i in h:
    plt.plot(x, result[i], label='Estimated derivative h = '+str(i))
plt.xlabel("X value")
plt.ylabel("Derivative Value at X")
plt.title("Differentiation Value at X on various h value")
plt.legend()

# Part C:
print("Answer to Q-a: When h value is 1e-7, it most closely approximates the true derivative. \n",
      "When h value is too small: The approximation is jumping around stepwise and not displaying a smooth curve approximation, it amplifies floating point errors in numerical operation such as rounding and division\n",
      "When h value is too large: The approximation is lower than the true value, it doesn't provide a good approximation to the derivative\n")
print("Answer to Q-b: Automatic differentiation avoids the problem of not choosing a good h value. \n"
      "The finite difference approach is quick and easy but suffers from accuracy and stability problems.\n"
      "Symbolic derivatives can be evaluated to machine precision, but can be costly to evaluate.\n"
      "Automatic differentiation (AD) overcomes both of these deficiencies. It is less costly than symbolic differentiation while evaluating derivatives to machine precision.\n"
      "AD uses forward or backward modes to differentiate, via Computational Graph, chain rule and evaluation trace.")

# Show plot
plt.show()
# plt.savefig('P1_fig.png')


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

import numpy as np
import mimetypes
import argparse
import matplotlib.pyplot as plt
import csv

def check_file_ext(filename):
    """
    Check file extension
    """
    if mimetypes.guess_type(filename)[0] != 'text/csv':
        raise argparse.ArgumentTypeError('wrong filetype or path')
    return filename

def normalize(x, min, max):
    """
    Data min-max normalization:
    """
    return (x - min) / (max - min)

def denormalize(x, min, max):
    """
    Data min-max denormalization:
    """
    return x * (max - min) + min

def estimate_price(mileage, theta0, theta1):
    """
    Estimate price function:
    """
    return theta0 + theta1 * mileage

def gradient_descent(X, Y, curr_t0, curr_t1, lr):
    """
    Gradient descent and its derivate
    """
    M = len(X)
    deriv_theta0 = 0
    deriv_theta1 = 0
    for i in range(M):
        deriv_theta0 += (1 / M) * ((curr_t0 + (curr_t1 * X[i])) - Y[i])
        deriv_theta1 += (1 / M) * (((curr_t0 + (curr_t1 * X[i])) - Y[i]) * X[i])
    tmp_theta0 = curr_t0 - lr * deriv_theta0
    tmp_theta1 = curr_t1 - lr * deriv_theta1
    return tmp_theta0, tmp_theta1

def cost_function(X, Y, theta0, theta1):
    """
    Evaluate the loss
    """
    M = len(X)
    err = 0.0
    for i in range(M):
        err += (Y[i] - estimate_price(X[i], theta0, theta1)) ** 2
    return err / M

def linear_regression(X, Y, lr, epochs):
    """
    The linear regression function to train the model
    """
    theta0 = 0
    theta1 = 0
    loss = []
    for _ in range(epochs):
        theta0, theta1 = gradient_descent(X, Y, theta0, theta1, lr)
        loss.append(cost_function(X, Y, theta0, theta1))
    return theta0, theta1, loss

def plot(x, y, t0, t1, loss):
    line_x = [0, 1]
    line_y = [(t1 * i) + t0 for i in line_x]
    # plt.figure(1)
    plt.subplot(211)
    plt.plot(loss)
    plt.xlabel('epochs')
    plt.ylabel('loss')
    plt.title('Loss')
    plt.grid(True)
    plt.subplot(212)
    plt.plot(line_x, line_y)
    plt.scatter(x, y, None, 'orange')
    plt.title('Mileage vs. Price')
    plt.xlabel('mileage')
    plt.ylabel('price')
    plt.grid(True)
    plt.subplots_adjust(top=0.92, bottom=0.10, left=0.10, right=0.95, hspace=0.5, wspace=0.35)
    plt.show()

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("filename", type=check_file_ext, help="CSV file path")
    args = parser.parse_args()
    data = np.genfromtxt(args.filename, delimiter=',', skip_header=1)
    X = data[:, 0]
    Y = data[:, 1]
    xmin = np.min(X)
    xmax = np.max(X)
    ymin = np.min(Y)
    ymax = np.max(Y)
    normalized_X = [normalize(x, xmin, xmax) for x in X]
    normalized_Y = [normalize(y, ymin, ymax) for y in Y]
    learning_rate = 0.01
    epochs = 10000
    theta0, theta1, loss = linear_regression(normalized_X, normalized_Y, learning_rate, epochs)
    plot(normalized_X, normalized_Y, theta0, theta1, loss)
    print('Final loss = ', loss[-1])
    with open('out.csv', mode='w') as out:
        out_writer = csv.writer(out, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL)
        out_writer.writerow(['theta0', 'theta1', 'xmin', 'xmax', 'ymin', 'ymax'])
        out_writer.writerow([theta0, theta1, xmin, xmax, ymin, ymax])

if __name__ == "__main__":
    main()


from sympy import Symbol, symbols
from sympy import simplify
from sympy import sin, cos, atan
from sympy.matrices import Matrix
from sympy import pprint
from sympy import factor,expand,diff
from sympy import sqrt
from sympy import latex
from sympy.vector import CoordSys3D

a,b,x,y,r,t,Vrr,Vrt,Vtt = symbols('a,b,x,y,r,t,V_rr,V_rt,V_tt', real=True)
s11,s12,s13,s14,s15,s21,s22,s23,s24,s25,s31,s32,s33,s34,s35,s41,s4,s43,s44,s45,s51,s52,s53,s54,s55 = symbols('s1:6(1:6)', real=True)
Vxx, Vxy, Vxz, Vyx, Vyy, Vyz, Vzx, Vzy, Vzz = symbols('V_x:z(x:z)', real=True)
x11, x12, x21, x22 = symbols('x_1:3(1:3)')
y11, y12, y21, y22 = symbols('y_1:3(1:3)')
(x1, x2) = symbols('x_1:3')
(y1, y2) = symbols('y_1:3')
(Vr1r1, Vr1r2, Vr2r1, Vr2r2) = symbols('V_r1:3(1:3)')
(Vt1t1, Vt1t2, Vt2t1, Vt2t2) = symbols('V_t1:3(1:3)')
(Vr1t1, Vr1t2, Vr2t1, Vr2t2) = symbols('V_r1:3t1:3')
(Vt1r1, Vt1r2, Vt2r1, Vt2r2) = symbols('V_t1:3r1:3')
(r1,r2) = symbols('r_1:3')

PRINT_LATEX = 1

def printCentral(expression):
  if PRINT_LATEX:
    pprint(latex(expression, mode='equation', mat_str='smallmatrix'))
  else:
    pprint(expression)

if PRINT_LATEX:
    print("\\documentclass[8pt]{article}")
    print("\\usepackage{amsmath}")
    print("\\usepackage[landscape]{geometry}")
    print("\\begin{document}")

# Let's study the simple case u = a*x + b*y, with Cov(x,y)
# Jacobian = [df/dx, df/dy] = [a, b]
# Hence the Cov(u) = J * Cov(x,y) * JT

J = Matrix([[diff(a*x+b*y, x), diff(a*x+b*y, y)]])
Cov_xy = Matrix([[Vxx, Vxy], [Vxy, Vyy]])
print("\nFor the case u = a*x + b*y we have a Cov(u):")
printCentral(expand(J * Cov_xy * J.T))

# Case of cartesian to polar transformation
# x = r cos(t) = f1
# y = r sin(t) = f2
# Rotation matrix R = [[cos(t), -sin(t)], [sin(t), cos(t)]]

V_xy = Matrix([[Vxx, Vxy], [Vxy, Vyy]])
J = Matrix([[cos(t), -sin(t)], [sin(t), cos(t)]])

print("\nFor the case of going from x,y to r,theta(t), using a Rotation")
printCentral(V_xy)

print("\nRotation Matrix:")
printCentral(J)

print("\nFinal(destination) covariance Matrix:")
printCentral(expand(J * V_xy * J.T))

# Case of cartesian to polar transformation
# x = r cos(t) = f1
# y = r sin(t) = f2
# Jacobian = [[df1/dr, df1/dt], [df2/dr, df2/dt]] = [[cos(t), -r*sin(t)], [sin(t), r*cos(t)]]

print("\n\n\nCase of cartesian to polar transformation")
Var_xy = Matrix([[Vxx, Vxy], [Vxy, Vyy]])
print("\nCartesian Variance:")
printCentral(Var_xy)

Jacobian_cart_to_polar = Matrix([[diff(r*cos(t),r) , diff(r*cos(t), t)], [diff(r*sin(t), r), diff(r*sin(t), t)]])
print("\nJacobian-cart-to-polar:")
printCentral(Jacobian_cart_to_polar)

print("\nPolar Variance:")
printCentral(expand(Jacobian_cart_to_polar * Var_xy * Jacobian_cart_to_polar))

# Case of polar coordinates to cartesian transtormation
# r = sqrt(x^2+y^2) = f1
# t = atan(y/x) = f2
# Jacobian = [[df1/dx, df1/dy], [df2/dx, df2/dy]] = [[x/r, y/r], [-y/r^2, x/r^2]]

print("\n\n\nCase of polar to cartesian transformation")
Var_rt = Matrix([[Vrr, Vrt], [Vrt, Vtt]])
print("\nPolar Variance:")
printCentral(Var_rt)

f1 = sqrt(x**2+y**2)
f2 = atan(y/x)
Jacobian_rad_to_cart = Matrix([[f1.diff(x).subs(x**2+y**2,r**2), f1.diff(y).subs(x**2+y**2,r**2)],
                               [factor(f2.diff(x)).subs(x**2+y**2,r**2), factor(f2.diff(y)).subs(x**2+y**2,r**2)]])
print("\nJacobian-rad-to-cart:")
printCentral(Jacobian_rad_to_cart)

print("\nCartesian Variance:")
printCentral(expand(Jacobian_rad_to_cart * Var_rt * Jacobian_rad_to_cart.T))

# Case of polar to cartesian coordinates, with several points using a block matrix.
# Input Cov is: [[Vr1r1, Vr1r2, Vr1t1, Vr1t2],
#                [Vr1r2, Vr2r2, Vr2t1, Vr2t2],
#                [Vt1r1, Vt1r2, Vt1t1, Vt1t2],
#                [Vt2r1, Vt2r2, Vt2t1, Vt2t2]]
#
# Also the Jacobian becomes block-ed
#
# Jacobian_block = [[x1/r1, 0, y1/r1, 0],
#                   [0, x2/r2, 0, y2/r2],
#                   [-y1/r1**2, 0, x1/r1**2, 0],
#                   [0, -y2/r2**2, 0, x2/r2**2]]

print("\n\n\nPolar to Cartesian transformation, using multiple points with block matrices.")

print("\nInput Covariance block matrix:")
Var_block = Matrix([[Vr1r1, Vr1r2, Vr1t1, Vr1t2],
                    [Vr1r2, Vr2r2, Vr2t1, Vr2t2],
                    [Vt1r1, Vt1r2, Vt1t1, Vt1t2],
                    [Vt2r1, Vt2r2, Vt1t2, Vt2t2]])
printCentral(Var_block)

print("\nJacobian block matrix:")
Jacobian_block = Matrix([[x1/r1, 0, y1/r1, 0],
                         [0, x2/r2, 0, y2/r2],
                         [-y1/r1**2, 0, x1/r1**2, 0],
                         [0, -y2/r2**2, 0, x2/r2**2]])
printCentral(Jacobian_block)

print("\nCartesian Covariance block matrix:")
printCentral(expand(Jacobian_block * Var_block * Jacobian_block.T))

# Now we compute the Jacobian for the s variable for the  line fit.

x0,y0,xi,yi,q,R = symbols('X_0, Y_0 x_i, y_i q R')
N = CoordSys3D('N')
o = x0*N.i + y0*N.j
tmp = xi*N.i + yi*N.j
a = -o
b = tmp - o

print("\n\n\nInput values:")
print("\na:")
printCentral(a)
print("\nb:")
printCentral(b)

c_ = expand((a ^ b) & N.k)
d_ = expand(a & b)
c2_d2 = factor(c_**2+d_**2)
k = -q*R/c2_d2

print("\ncross:")
printCentral(c_)

print("\ndot:")
printCentral(d_)

c2_d2 = factor(c_**2+d_**2)

km = k*(x0**2+y0**2)
print("\nkm:")
printCentral(km)

print("\nk:")
printCentral(k)
print("\nk also as:")
printCentral(k.subs(c2_d2, 'dot**2+cross**2'))
print("\ni.e. k:")
printCentral(k.subs(km,'km'))

s = -q * R * atan(c_/d_)
print("\ns:")
printCentral(s.subs(d_,'dot').subs(c_,'cross'))

# compute the Jacobian

Jacobian_s = Matrix([
    factor(diff(s,x0)).subs(k, 'k'),
    factor(diff(s,y0)).subs(k, 'k'),
    simplify(factor(diff(s,R))).subs(d_,'dot').subs(c_,'cross'),
    factor(diff(s,xi)).subs(km, 'km'),
    factor(diff(s,yi)).subs(-km, '-km')
    ])

print("\nJacobian [x0, y0, R, xi, yi]:")
printCentral(Jacobian_s)

print("\nNote that -(b_y - a_y)*dot + (b_x + a_x)*cross is:")
printCentral(factor(-(b.components[N.j] - a.components[N.j])*d_ + (b.components[N.i] + a.components[N.i])*c_))

print("\nNote that (b_x - a_x)*dot - (-a_y - b_y)*cross is:")
printCentral(factor((b.components[N.i] - a.components[N.i])*d_ - (-a.components[N.j] - b.components[N.j])*c_))

print("\nNote that -a_y*dot - a_x*cross is:")
printCentral(factor(-a.components[N.j]*d_ - a.components[N.i]*c_))

print("\nNote that a_x*dot-a_y*cross is:")
printCentral(factor(a.components[N.i]*d_ - a.components[N.j]*c_))

if PRINT_LATEX:
    print("\\end{document}")


# Cross entropy is one out of many possible loss functions (another
# popular one is SVM hinge loss). These loss functions are typically
# written as J(theta) and can be used within gradient descent, which
# is an iterative algorithm to move the parameters (or coefficients)
# towards the optimum values.

# For multi-class problem

# our y needs to be one-hot encoded
# apply softmax

# Average the losses over the entire test set, and you get the cross entropy loss.
import torch
import torch.nn as nn
import numpy as np

# squashes output from 0 to 1
def softmax(x):
    return np.exp(x) / np.sum(np.exp(x), axis = 0)

x = np.array([2.0, 1.0, 0.1])
output = softmax(x)
print('softmax numpy:', output)

x = torch.tensor([2.0, 1.0, 0.1])
output = torch.softmax(x, dim=0)
print(output)

# a lot of times softmax is used with cross entropy
# loss increases as predicted probabiliy diverges from true
# high cross entropy for very wrong prediction

def cross_entropy(actual, predicted):
    loss = -np.sum(actual * np.log(predicted))
    return loss

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

Y_pred_good = np.array([.7, .2, .1])
Y_pred_bad = np.array([.1, .3, .6])
l1 = cross_entropy(Y, Y_pred_good)
l2 = cross_entropy(Y, Y_pred_bad)
print(f'Loss1 numpy: {l1:.4f}')
print(f'Loss2 numpy: {l2:.4f}')


# Import the necessary modules.
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

# This script will demonstrate the usefulness of using the taylor expansion
# to approximate the local shape of a curve. We will look at a simple function,
# y = cos(x), but it may be useful for you to try more complicated functions.

# To start, we'll make an array of x values at which we will evaluate cos(x).
x = np.linspace(-4 * np.pi, 4 * np.pi, 500)
y = np.cos(x)

# Let's evaluat eh first handful of orders.
order_0  = np.ones_like(x)
order_2 = 1 - x**2 / np.math.factorial(2)
order_4 = order_2 + x**4 / np.math.factorial(4)
order_6 = order_4 - x**6 / np.math.factorial(6)

# Now let's plot all of them together.
plt.figure()
plt.plot(x, y, label='cos(x)')
plt.plot(x, order_0, label='0th order')
plt.plot(x, order_2, label='2nd order')
plt.plot(x, order_4, label='4th order')
plt.plot(x, order_6, label='6th order')

# Add the legend and labels.
plt.legend()
plt.xlabel('x')
plt.ylabel('y')

# Change the yrange so it's easier to see.
plt.ylim([-1.5, 1])
plt.show()

# With this, we can see that even the first few expansions are a great
# approximation of the actual behavior.


import numpy as np
from scipy import stats
import math

x = np.array([25,35,10,40,85,75,60,45,50])
y = np.array([63,68,72,62,65,46,51,60,55])
n = x.size

mean_x = np.mean(x)
mean_y = np.mean(y)
print("mean x : ",mean_x)
print("mean y : ",mean_y)

x_i= x - mean_x
y_i = y - mean_y
print("\nxi : ",x_i)
print("yi : ",y_i)

x_i_square = np.square(x_i)
y_i_square = np.square(y_i)
xi_yi = x_i * y_i
print("\nxi**2 : ",x_i_square)
print("yi**2 : ",y_i_square)
print("xi*yi : ",xi_yi)

sum_x_i = np.sum(x_i)
sum_y_i = np.sum(y_i)
sum_xi_square = np.sum(x_i_square)
sum_yi_square = np.sum(y_i_square)
sum_xi_yi = np.sum(xi_yi)

print("\nsum_x_i : ",sum_x_i)
print("sum_y_i : ",sum_y_i)
print("sum_xi_square : ",sum_xi_square)
print("sum_yi_square : ",sum_yi_square)
print("sum_xi_yi : ",sum_xi_yi)

coeff_numerator  = ((n*sum_xi_yi) - (sum_x_i*sum_y_i))
coeff_denominator_1 = math.sqrt((n*sum_xi_square) - math.pow(sum_x_i,2))
coeff_denominator_2 = math.sqrt((n*sum_yi_square) - math.pow(sum_y_i,2))

coeff = coeff_numerator/(coeff_denominator_1*coeff_denominator_2)
print("\n coefficent of corelation : ",coeff)


"""
Problem 7:

What is the 10001st prime number?
"""

import numpy as np

# We will solve this using the sieve of Eratosthenes
# The pseudocode can be found at https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes#Pseudocode

n = 200000
sieve = np.array([True for _ in range(n)])
sieve[0:2] = False
upper_bound = int(np.sqrt(n))

for i in range(2, upper_bound+1):
    if sieve[i]:
        j = i * i
        counter = 0
        while j < n:
            sieve[j] = False
            j = (i * i) + (counter * i)
            counter += 1

primes = np.where(sieve == True)[0]

# The solution is at index 10000 since we start at index 0
solution = primes[10000]
print('solution: ', solution)


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Mon Jan 17 15:05:05 2022

@author: bobrokerson

"""

# Рассмотрим все ту же функцию из задания по линейной алгебре:
# f(x) = sin(x / 5) * exp(x / 10) + 5 * exp(-x / 2), но теперь уже на промежутке [1, 30]
# В первом задании будем искать минимум этой функции на заданном промежутке с помощью scipy.optimize.
# Разумеется, в дальнейшем вы будете использовать методы оптимизации для более сложных функций,
# а f(x) мы рассмотрим как удобный учебный пример.
# Напишите на Питоне функцию, вычисляющую значение f(x) по известному x.
# Будьте внимательны: не забывайте про то, что по умолчанию в питоне целые числа делятся нацело, и о том,
# что функции sin и exp нужно импортировать из модуля math.

from math import sin, exp
import numpy as np
import matplotlib.pyplot as plt

def func(x):
    return sin(x / 5.) * exp(x / 10.) + 5. * exp(-x/ 2.)

xarr = np.arange(1., 31.)
print(xarr)
print("x:", xarr.shape)
yarr = np.array([func(x) for x in xarr])
print(yarr)
print("y:", yarr.shape)

plt.plot(xarr, yarr)
plt.grid(True)
plt.axis([0, 30, -15, 5])
plt.show()

# Изучите примеры использования  scipy.optimize.minimize в документации Scipy (см. "Материалы")
# Попробуйте найти минимум, используя стандартные параметры в функции  scipy.optimize.minimize
# (т.е. задав только функцию и начальное приближение). Попробуйте менять начальное приближение и изучить, меняется ли результат.
# Укажите в scipy.optimize.minimize в качестве метода BFGS (один из самых точных в большинстве случаев градиентных методов оптимизации),
# запустите из начального приближения x=2. Градиент функции при этом указывать не нужно – он будет оценен численно.
# Полученное значение функции в точке минимума - ваш первый ответ по заданию 1, его надо записать с точностью до 2 знака после запятой.

from scipy.optimize import minimize

minFuncVal = minimize(func, 5)
print("Min f(x): ", round(minFuncVal.fun,2), "x = ", minFuncVal.x)
print("Number: ", minFuncVal.nit)

minFuncVal2 = minimize(func, 2, method = 'BFGS')
print("Min f(x) BFGS: ", round(minFuncVal2.fun, 2), "for x = ", minFuncVal2.x)
print("Number: ", minFuncVal2.nit)

minValR1 = np.zeros((2))
minValR1[0] = round(minFuncVal2.fun, 2)
print(minValR1)

# Теперь измените начальное приближение на x=30. Значение функции в точке минимума - ваш второй ответ по заданию 1,
# его надо записать через пробел после первого, с точностью до 2 знака после запятой.

minFuncVal3 = minimize(func, 30, method = 'BFGS')
print("Min f(x) BFGS method: ", minFuncVal3, "for x = ", minFuncVal3.x)
print("Number: ", minFuncVal3)

minValR1[1] = round(minFuncVal3.fun, 2)
print(minValR1)

minValR1[1] = round(minFuncVal3.fun, 2)
print(minValR1)

with open("docvalue.txt", "w") as file:
    for item in minValR1:
        file.write(str(item) + ' ')


import numpy

def tridiag(a, b, c, d):
    """ solve the linear system Ax = d where A has the form:

          a_i x_{i-1} + b_i x_i + c_i x_{i+1} = d_i

        for i = 0, n-1 with a_0 = 0 and c_{n-1} = 0

        In matrix form, b is the main diagonal, a is the subdiagonal and
        c is the superdiagonal. """

    N = len(a)
    if not (len(b) == len(c) == len(d) == N):
        print "ERROR: vectors not the right size"
        return None

    # forward elimination
    cprime = numpy.zeros((N), dtype=a.dtype)
    dprime = numpy.zeros((N), dtype=a.dtype)

    cprime[0] = c[0]/b[0]
    dprime[0] = d[0]/b[0]

    for i in range(1,N-1):
        cprime[i] = c[i]/(b[i] - cprime[i-1]*a[i])
        dprime[i] = (d[i] - dprime[i-1]*a[i])/(b[i] - cprime[i-1]*a[i])

    dprime[N-1] = (d[N-1] - dprime[N-2]*a[N-1])/(b[N-1] - cprime[N-2]*a[N-1])

    # back substitution
    x = numpy.zeros((N), dtype=a.dtype)

    x[N-1] = dprime[N-1]
    for i in reversed(range(0,N-1)):
        x[i] = dprime[i] - cprime[i]*x[i+1]

    return x

def triMultAx(a, b, c, x):
    """ multiply the tridiagonal matrix A by vector x and return the
        product vector d """

    N = len(a)
    if not (len(b) == len(c) == len(x) == N):
        print "ERROR: vectors not the right size"
        return None

    d = numpy.zeros((N), dtype=a.dtype)

    d[0] = b[0]*x[0] + c[0]*x[1]
    for i in range(1,N-1):
        d[i] = a[i]*x[i-1] + b[i]*x[i] + c[i]*x[i+1]

    d[N-1] = a[N-1]*x[N-2] + b[N-1]*x[N-1]

    return d


import numpy as np
from math import *
from sympy import *
from scipy import interpolate
from scipy.misc import comb
from matplotlib import pyplot as plt

def bernstein_poly(i, n, t):
    """
     The Bernstein polynomial of n, i as a function of t
    """

    return comb(n, i) * ( t**(n-i) ) * (1 - t)**i

def bezier_curve(points, nTimes=1000):
    """
       Given a set of control points, return the
       bezier curve defined by the control points.

       points should be a list of lists, or list of tuples
       such as [ [1,1],
                 [2,3],
                 [4,5], ..[Xn, Yn] ]
        However, we aware that the function takes in input as they come. So, if the points
        are not ordered correctly, you will get incorrect results.
        Should be: [ [x1,y1],
                     [x2,y2],
                     ........[xn,yn] ]
        nTimes is the number of time steps, defaults to 1000

    """

    nPoints = len(points)
    xPoints = np.array([p[0] for p in points])
    yPoints = np.array([p[1] for p in points])

    t = np.linspace(0.0, 1.0, nTimes)

    polynomial_array = np.array([ bernstein_poly(i, nPoints-1, t) for i in range(0, nPoints)   ])

    xvals = np.dot(xPoints, polynomial_array)
    yvals = np.dot(yPoints, polynomial_array)

    return xvals, yvals, xPoints, yPoints

def bezier_spline(xPoints, yPoints, numPoints):
    t = symbols('t')
    if numPoints <= 3:
        print("Enter atleast two control point and one pass through point!\n")
    elif numPoints == 4:
        bx = 3 * (xPoints[1] - xPoints[0])
        cx = 3 * (xPoints[2] - xPoints[1]) - bx
        dx = xPoints[3]-xPoints[0]-bx-cx
        by = 3 * (yPoints[1] - yPoints[0])
        cy = 3 * (yPoints[2] - yPoints[1]) - by
        dy = (yPoints[3]-yPoints[0])-by-cy
        print('x(t) = ' + latex(xPoints[0] + bx * t + cx * t ** 2 + dx * t ** 3))
        print('y(t) = ' + latex(xPoints[0]+by*t+cy*t**2+dy*t**3))
    else:
        print("This is an invalid entry. Sorry, please try again.\n")

# understand  you must enter 4 points.
# if you do not enter 4 points the function will
# reject. If you have equations to enter, please enter the points
# in the form
def bezier(user_input, n):
    point = '(';
    foundPoint = False;
    userString = str(user_input);
    try:
        if userString.find(point) != -1:
            foundPoint = True;
        if(foundPoint):
            points = np.array(user_input)
            xvals, yvals, xPoints, yPoints = bezier_curve(user_input, nTimes=1000)
            bezier_spline(xPoints, yPoints, n)
            plt.plot(xvals, yvals)
            plt.plot(xPoints, yPoints, "ro")
            for nr in range( len(user_input)):
                plt.text(xPoints[nr], yPoints[nr], nr)

            plt.show()
        else:
            length = len(user_input)
            xPoints = np.array(user_input[0])
            yPoints = np.array(user_input[1])
            if length == 3:
                zPoints = np.array(user_input[2])
                x1,y1,z1 = xPoints[0],yPoints[0],zPoints[0]
                bx = xPoints[1]
                x2 = eval('(bx+3*x1)/3')
                cx = xPoints[2]
                x3 = eval('(cx+3*x2+bx)/3')
                dx = xPoints[3]
                x4 = eval('dx+x1+bx+cx')
                by = yPoints[1]
                y2 = eval('(by+3*y1)/3')
                cy = yPoints[2]
                y3 = eval('(cy+3*y2+by)/3')
                dy = yPoints[3]
                y4 = eval('dy+y1+by+cy')
                bz = zPoints[1]
                z2 = eval('(bz+3*z1)/3')
                cz = zPoints[2]
                z3 = eval('(cz+3*z2+bz)/3')
                dz = zPoints[3]
                z4 = eval('dz+z1+bz+cz')
                print('Knots: (' + str(x1) + ',' + str(y1) + ',' + str(z1) + ')'+','\
                '(' + str(x4) + ',' + str(y4) + ',' + str(z4) + ')')
                print('Control Points: (' + str(x2) + ',' + str(y2) + ',' + str(z2) + ')'+','\
                '(' + str(x4) + ',' + str(y3) + ',' + str(z3) + ')')
            elif length == 2:
                x1, y1 = xPoints[0], yPoints[0]
                bx = xPoints[1]
                x2 = eval('(bx+3*x1)/3')
                cx = xPoints[2]
                x3 = eval('(cx+3*x2+bx)/3')
                dx = xPoints[3]
                x4 = eval('dx+x1+bx+cx')
                by = yPoints[1]
                y2 = eval('(by+3*y1)/3')
                cy = yPoints[2]
                y3 = eval('(cy+3*y2+by)/3')
                dy = yPoints[3]
                y4 = eval('dy+y1+by+cy')
                print('Knots: (' + str(x1) + ',' + str(y1) + ')' + ',' + '(' + str(x4) + ',' +
                str(y4) + ')')
                print('Control Points: (' + str(x2) + ',' + str(y2) +')' + ','+ '(' + str(x4) + ',' + str(
                    y3) + ')')
            else:
                print("Sorry, you can only use a maximum of 3 equations\n")

    except:
        e = sys.exc_info()[0]
        write_to_page("
Error: %s
" % e)
        print('Sorry, you have not entered correct input\n')
        return -1

def main():
    # nPoints = 4
    # points = np.random.rand(nPoints,2)*200
    nPoints = 4
    points = [[1,0,6,2],[1,-1,0,1],[1,1,6,0]]
    bezier(points,nPoints)

if __name__ == '__main__':
    main()


import numpy
import scipy.linalg
import matplotlib.pyplot as plt

def cspline(x, y, x_interp, B0, Bk):
    # h_i, v_i, d_i, i = 0, 1, ..., k - 1:
    h = x[1:] - x[:-1]
    v = y[1:] - y[:-1]
    d = v / h

    # 1 / h_i, i = 0, 1, ..., k - 1:
    off_diag = 1.0/h
    # 2 * ( 1 / h_i + 1 / h_{i + 1} ), i = 0, 1, ..., k - 2
    diag = 2.0*(off_diag[1:] + off_diag[:-1])
    # 1 / h_i, i = 1, 2, ..., k - 2:
    off_diag = off_diag[1:-1]

    # Compact tridiagonal hermitian matrix:
    A = numpy.zeros((2, diag.size))
    A[0,1:] = off_diag[:]
    A[1,:] = diag

    # Right-hand side:
    z = d/h
    b = 3.0 * (z[:-1] + z[1:])
    b[0] = b[0] - B0/h[0]
    b[-1] = b[-1] - Bk/h[-1]

    # B-coefficients:
    B = numpy.zeros( (b.size + 2, ))
    B[1:-1] = scipy.linalg.solve_banded(A, b)
    B[0] = B0
    B[-1] = Bk

    # C-coefficients:
    C = (3.0*d - 2.0*B[:-1] - B[1:])/h
    # D-coefficients:
    D = (B[:-1] + B[1:] - 2.0*d)/(h**2)

    # Interpolate:
    y_interp = numpy.zeros(x_interp.shape)
    k = 0
    for i in range( x_interp.size ):
        # Find appropriate segment:
        while k < C.size and x_interp[i] > x[k + 1]:
            k = k + 1
        # Compute cubic polinomial:
        h_x = x_interp[i] - x[k]
        y_interp[i] = y[k] + B[k]*h_x + C[k]*(h_x**2) + D[k]*(h_x**3)

    return y_interp

def runge(x):
    return 1.0/(1.0 + 25.0 * x**2)

# 1-) splines naturais: s'(x0) = B0 = 0 e s'(xk) = Bk = 0

B0 = 0
Bk = 0

ek = []
for k in range(1,15):
    x_interp = numpy.linspace(-1.1,1.1,100)

    x = numpy.zeros(k+1)
    for i in range(k+1):
        x[i] = -1 + 2*i/k

    sk = cspline( x, runge(x), x_interp, B0 , Bk )
    plt.plot(x_interp, runge(x_interp), 'r')
    plt.plot(x_interp, sk, 'b')
    plt.plot(x, runge(x), 'ko')
    plt.show()

    ek.append(numpy.amax(numpy.absolute(numpy.subtract(runge(x_interp), sk))))


'''
Library of quantized and non-quantized activation functions.
'''

import numpy as np

def tanh_activation(x):
    '''Tanh activation function.

    Parameters
    ----------
    x : float
        Value of the input pre-activation.

    Returns
    -------
    float
        The corresponding Tanh(x) value.

    '''
    return np.tanh(x)

def sigmoid_activation(x):
    r'''Sigmoid activation function defined as:

    .. math::
        y = \frac{1}{1+e^{-x}}

    Parameters
    ----------
    x : float
        Value of the input pre-activation.

    Returns
    -------
    float
        The result of the Sigmoid function.
    '''
    return 1 / (1 + np.exp(-x))

def sigmoid_tanh_activation(x):
    r'''A Sigmoid activation defined as:

    .. math::
        y = \frac{\tanh\left(\frac{x}{2}+1\right)}{2}

    Parameters
    ----------
    x : float
        Value of the input pre-activation.

    Returns
    -------
    float
        The result of the Sigmoid function.
    '''
    return (np.tanh(x / 2) + 1) / 2

def fast_sigmoid_activation(x):
    r'''Fast Sigmoid activation function defined as:

    .. math::
        y = \frac{x}{1+\lvert x \rvert}

    It can be used to reduce the amount of hardware resources needed
    to implement a Sigmoid function.

    Parameters
    ----------
    x : float
        Value of the input pre-activation.

    Returns
    -------
    float
        The result of the fast Sigmoid.
    '''
    return x / (1 + np.abs(x))

def relu_activation(x):
    '''Rectifier Linear Unit activation function.

    Parameters
    ----------
    x : float
        Value of the input pre-activation.

    Returns
    -------
    float
        Returns ``x`` if ``x`` is larger or equal to 0. Otherwise 0 is returned.
    '''
    return x * (x > 0)

def approximate_sigmoid_activation(x, alpha=0.001):
    r'''Approximate version of the Fast Sigmoid activation function defined as:

    .. math::
        y = 0.5 \frac{x \cdot \alpha}{1+\lvert x \cdot \alpha \rvert} + 0.5

    Parameters
    ----------
    x : float
        Value of the input pre-activation
    alpha : float, optional
        Noise factor, defaults to 0.001

    Returns
    -------
    float
        The result of the approximate Sigmoid.
    '''
    return 0.5 * (x * alpha / (1 + np.abs(x * alpha))) + 0.5

def ramp_activation(x, bound=1):
    '''A modified ReLU activation where the maximum (minimum) value
    for the input is capped at a specified value.

    Parameters
    ----------
    x : numpy.ndarray
        The input tensor
    bound : int, optional
        Upper bound of the activation, by default 1

    Returns
    -------
    numpy.ndarray
        A tensor having the same shape of the input tensor, where
        values are the result of the ramp activation.
    '''
    return np.maximum(-bound, np.minimum(bound, x))


# https://docs.sympy.org/latest/tutorial/matrices.html

from sympy import *
init_printing(use_unicode=False)
from myprint import spprint

spprint(Matrix([[1, -1], [3, 4], [0, 2]]))

spprint(Matrix([1, 2, 3]))

M = Matrix([[1, 2, 3], [3, 2, 1]])
N = Matrix([0, 1, 1])

spprint(M)
spprint(N)
spprint(M*N)

# shape

M = Matrix([[1, 2, 3], [-2, 0, 4]])
spprint(M)

# cannot find shape - is it in sympy-1.8
spprint(shape(M))

# rows and columns

spprint(M.row(0))
spprint(M.col(-1))

M.col_del(0)
spprint(M)

M.row_del(1)
spprint(M)

M = M.row_insert(1, Matrix([[0, 4]]))
spprint(M)

M = M.col_insert(0, Matrix([1, -2]))
spprint(M)

# math et al

M = Matrix([[1, 3], [-2, 3]])
N = Matrix([[0, 3], [0, 7]])
spprint(M + N)
spprint(M*N)
spprint(3*M)
spprint(M**2)
spprint(M**-1)

# this gets error:
# sympy.matrices.common.NonInvertibleMatrixError: Matrix det == 0; not invertible.
#spprint(N**-1)

# transpose

M = Matrix([[1, 2, 3], [4, 5, 6]])
spprint(M)

spprint(M.T)

# constructors

spprint(eye(3))

spprint(eye(4))

spprint(zeros(2, 3))

spprint(ones(3, 2))

spprint(diag(1, 2, 3))

spprint(diag(-1, ones(2, 2), Matrix([5, 7, 5])))

# advanced

M = Matrix([[1, 0, 1], [2, -1, 3], [4, 3, 2]])
spprint(M)

spprint(M.det())

M = Matrix([[1, 0, 1, 3], [2, 3, 4, 7], [-1, -3, -3, -4]])
spprint(M)

spprint(M.rref())

M = Matrix([[1, 2, 3, 0, 0], [4, 10, 0, 0, 1]])

spprint(M)

spprint(M.nullspace())

M = Matrix([[1, 1, 2], [2 ,1 , 3], [3 , 1, 4]])

spprint(M)

spprint(M.columnspace())

M = Matrix([[3, -2,  4, -2], [5,  3, -3, -2], [5, -2,  2, -2], [5, -2, -3,  3]])

spprint(M)

spprint(M.eigenvals())

spprint(M.eigenvects())

# diagonalize

spprint(M)

P, D = M.diagonalize()

spprint(P)

spprint(D)

spprint(P*D*P**-1)

print(P*D*P**-1 == M)

lamda = symbols('lamda')

p = M.charpoly(lamda)

spprint(p)

spprint(factor(p.as_expr()))

# zero testing

from sympy import *
q = Symbol("q", positive = True)
m = Matrix([
[-2*cosh(q/3),      exp(-q),            1],
[      exp(q), -2*cosh(q/3),            1],
[           1,            1, -2*cosh(q/3)]])

spprint(m.nullspace())

import warnings
def my_iszero(x):
    try:
        result = x.is_zero
    except AttributeError:
        result = None

    # Warnings if evaluated into None
    if result is None:
        warnings.warn("Zero testing of {} evaluated into None".format(x))
    return result

spprint(m.nullspace(iszerofunc=my_iszero))

def my_iszero(x):
    try:
        result = x.rewrite(exp).simplify().is_zero
    except AttributeError:
        result = None

    # Warnings if evaluated into None
    if result is None:
        warnings.warn("Zero testing of {} evaluated into None".format(x))
    return result

spprint(m.nullspace(iszerofunc=my_iszero))


import numpy as np

# First 20 countries with employment data
countries = np.array([
    'Afghanistan', 'Albania', 'Algeria', 'Angola', 'Argentina',
    'Armenia', 'Australia', 'Austria', 'Azerbaijan', 'Bahamas',
    'Bahrain', 'Bangladesh', 'Barbados', 'Belarus', 'Belgium',
    'Belize', 'Benin', 'Bhutan', 'Bolivia',
    'Bosnia and Herzegovina'
])

# Employment data in 2007 for those 20 countries
employment = np.array([
    55.70000076, 51.40000153, 50.5, 75.69999695,
    58.40000153, 40.09999847, 61.5, 57.09999847,
    60.90000153, 66.59999847, 60.40000153, 68.09999847,
    66.90000153, 53.40000153, 48.59999847, 56.79999924,
    71.59999847, 58.40000153, 70.40000153, 41.20000076
])

# Change False to True for each block of code to see what it does

# Element types
if False:
    print(countries.dtype)
    print(employment.dtype)
    print(np.array([0, 1, 2, 3]).dtype)
    print(np.array([1.0, 1.5, 2.0, 2.5]).dtype)
    print(np.array([True, False, True]).dtype)
    print(np.array(['AL', 'AK', 'AZ', 'AR', 'CA']).dtype)

# Looping
if False:
    for country in countries:
        print('Examining country {}'.format(country))

    for i in range(len(countries)):
        country = countries[i]
        country_employment = employment[i]
        print('Country {} has employment {}'.format(country,
                                                    country_employment))

# Numpy functions
if False:
    print(employment.mean())
    print(employment.std())
    print(employment.max())
    print(employment.sum())

def max_employment(countries, employment):
    """
    Fill in this function to return the name of the country
    with the highest employment in the given employment
    data, and the employment in that country.
    """
    i = np.argmax(employment)
    max_value = employment[i]
    max_country = countries[i]

    return max_country, max_value


from typing import List

import numpy as np

from Common.Primes import sieve_of_atkin

def divisors(number: int) -> List[int]:
    """
    This function calculates and returns a list of all divisors that a given number has, e.g.
    f(number=24) -> [1, 2, 3, 4, 6, 8, 12, 24]
    :param number: The number whose divisors are to be calculated
    :return: A list of integer divisors
    """
    if number < 1:
        raise ValueError(f"The input number must be greater than one ({number} !> 1).")
    if number == 1:
        return [1]

    divisor_list = set()
    sqrt_ceil = int(np.floor(np.sqrt(number)) + 1)
    for i in range(1, sqrt_ceil):
        if number % i == 0:
            divisor_list.add(i)
            divisor_list.add(int(number/i))

    return sorted(list(divisor_list))

def prime_factorization(number: int) -> List[int]:
    """
    This function calculates and returns the prime factorization of an input number. This algorithm doesn't condense
    factor multiples, e.g.
    f(number=24) -> [2, 2, 2, 3] (and not [2, 3])
    :param number: The number whose prime factors are to be calculated
    :return: A list of integer prime factors
    """
    if number <= 1:
        return [number]

    factors_list = []
    primes_list = sieve_of_atkin(number)

    for prime in primes_list:
        while number % prime == 0:
            factors_list += [prime]
            number /= prime

    if int(number) != 1:
        raise ArithmeticError(f"The prime factorization for the {number}; the number, {number} != 1")
    else:
        return factors_list


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

Task
Given an array, , of  integers, calculate and print the respective mean, median, and mode on separate lines. If your array contains more than one modal value, choose the numerically smallest one.

Note: Other than the modal value (which will always be an integer), your answers should be in decimal form, rounded to a scale of  decimal place (i.e., ,  format).

Input Format

The first line contains an integer, , denoting the number of elements in the array.
The second line contains  space-separated integers describing the array's elements.

Constraints

, where  is the  element of the array.
Output Format

Print  lines of output in the following order:

Print the mean on a new line, to a scale of  decimal place (i.e., , ).
Print the median on a new line, to a scale of  decimal place (i.e., , ).
Print the mode on a new line; if more than one such value exists, print the numerically smallest one.
Sample Input

10
64630 11735 14216 99233 14470 4978 73429 38120 51135 67060
Sample Output

43900.6
44627.5
4978
Explanation

Mean:
We sum all  elements in the array, divide the sum by , and print our result on a new line.

Median:
To calculate the median, we need the elements of the array to be sorted in either non-increasing or non-decreasing order. The sorted array . We then average the two middle elements:

and print our result on a new line.
Mode:
We can find the number of occurrences of all the elements in the array:

 4978 : 1
11735 : 1
14216 : 1
14470 : 1
38120 : 1
51135 : 1
64630 : 1
67060 : 1
73429 : 1
99233 : 1
Every number occurs once, making  the maximum number of occurrences for any number in . Because we have multiple values to choose from, we want to select the smallest one, , and print it on a new line.
'''

# Enter your code here. Read input from STDIN. Print output to STDOUT
import numpy as np
from scipy import stats

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

x = np.array(x)

print(round(np.mean(x),1))
print(round(np.median(x),1))
print(int(stats.mode(x)[0]))

10 Days of Statistics/Day 0/Mean-Median-Mode.py


# %% [markdown]
# # Least squares
# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/YoniChechik/AI_is_Math/blob/master/c_04a_curve_fitting/least_squares.ipynb)
# ## Linear LS
# Let's generate some noisy data
# %%
import numpy as np
import matplotlib.pyplot as plt

figsize = (10, 10)

np.random.seed(123)

# %%
x_max = 10
x_step = 0.01
x = np.arange(0, x_max, x_step)
y = 3 * x - 3

# add noise to data
std = 1
y = y + np.random.normal(scale=std, size=x.shape)

plt.figure(figsize=figsize)
plt.plot(x, y, "*")
plt.title("noisy data")
plt.show()

# %% [markdown]
# ### calc LS matrices and result:
# $$ Xb = y $$
# %%
x_vec = x.reshape(-1, 1)
X = np.concatenate((x_vec, np.ones(x_vec.shape)), axis=1)
print(X.shape)
y_vec = y.reshape(-1, 1)

# %%
b = np.linalg.inv(X.T @ X) @ X.T @ y_vec
print(b)

# plot fit results
plt.figure(figsize=figsize)
plt.plot(x, y, "*")
plt.plot(x, b[0] * x + b[1], "r")
plt.title("noisy data + best LS fit. $b^T$=" + str(b.T))
plt.show()

# %% [markdown]
# ## Comparison to np.linalg.lstsq
# %%
b_np = np.linalg.lstsq(X, y_vec, rcond=None)[0]
mse = np.mean((b - b_np) ** 2)
print(mse)
# %% [markdown]
# ## vertical dataset
# As mentioned in the lecture, LS is not goog at fitting vertical dataset.
# Let's generate data:
# %%
data_sz = 100
x = np.ones(data_sz)
y = np.arange(data_sz)

# add noise to data
std = 0.1
x = x + np.random.normal(scale=std, size=x.shape)

plt.figure(figsize=figsize)
plt.plot(x, y, "*")
axes = plt.gca()
axes.set_xlim([-3, 3])
plt.title("noisy vertical data")
plt.show()

# %% [markdown]
# Calc LS matrices and result
# %%
x_vec = x.reshape(-1, 1)
X = np.concatenate((x_vec, np.ones(x_vec.shape)), axis=1)
y_vec = y.reshape(-1, 1)

# %%
b = np.linalg.inv(X.T @ X) @ X.T @ y_vec
print(b)

# plot fit results
plt.figure(figsize=figsize)
plt.plot(x, y, "*")
x_axis = np.arange(3)
plt.plot(x_axis, b[0] * x_axis + b[1], "r")
plt.title("vertical data + best LS fit. $b^T$=" + str(b.T))
plt.show()

# %% [markdown]
# ## TLS
# Same vertical data, now with total least squares
# %%
X = np.concatenate((x.reshape(-1, 1) - np.mean(x), y.reshape(-1, 1) - np.mean(y)), axis=1)

def linear_tls(X):
    w, v = np.linalg.eig(X.T @ X)
    return v[:, np.argmin(w)]

tls_res = linear_tls(X)

a = tls_res[0]
b = tls_res[1]

c = -a * np.mean(x) - b * np.mean(y)
x_fit = np.array([x.min(), x.max()])
y_fit = -a / b * x_fit - c / b

plt.figure(figsize=figsize)
plt.plot(x, y, "*")
plt.plot(x_fit, y_fit)
axes = plt.gca()
axes.set_xlim([-3, 3])
axes.set_ylim([0, 100])
plt.title("noisy vertical data + linear TLS fit")
plt.show()

# %% [markdown]
# ## Example for non linear LS
# %%
x_step = 0.01
x = np.arange(-10, 10 + x_step, x_step)

y = 0.5 * x ** 2 + 2 * x + 5

# add noise to data
std = 5
y = y + np.random.normal(scale=std, size=y.shape)

plt.figure(figsize=figsize)
plt.plot(x, y, "*")
plt.title("noisy parabola data")
plt.show()

# %%
# calc LS matrices
x_vec = x.reshape(-1, 1)
X = np.concatenate((x_vec ** 2, x_vec, np.ones(x_vec.shape)), axis=1)
y_vec = y.reshape(-1, 1)

b = np.linalg.lstsq(X, y_vec, rcond=None)[0]
print(b)
# %%
plt.figure(figsize=figsize)
plt.plot(x, y, "*")
plt.plot(x, b[0] * x ** 2 + b[1] * x + b[2], "r")
plt.title("data + best LS fit. $b^T$=" + str(b.T))
plt.show()

# %% [markdown]
# ## Outliers
# As mentioned, LS has a problem with outliers:
# %%
x_max = 10

x = np.arange(10)
y = 4 * x + 2

# let's change the last data point
y[-1] -= 20

plt.figure(figsize=figsize)
plt.plot(x, y, "*")
plt.title("data with outlier")
plt.show()

# %%
# calc LS matrices
x_vec = x.reshape(-1, 1)
X = np.concatenate((x_vec, np.ones(x_vec.shape)), axis=1)
y_vec = y.reshape(-1, 1)

# %%
b = np.linalg.inv(X.T @ X) @ X.T @ y_vec
print(b)

# plot fit results
plt.figure(figsize=figsize)
plt.plot(x, y, "*")
x_axis = np.arange(x_max)
plt.plot(x_axis, b[0] * x_axis + b[1], "r")
plt.title("data with outlier + best LS fit. $b^T$=" + str(b.T))
plt.show()

# %% [markdown]
# ## RANSAC
# ### arrange data
# %%

x = np.arange(0, 10, 0.1)
y = 3 * x - 3

# add noise to data
std = 1
y = y + np.random.normal(scale=std, size=x.shape)

# add random noise unrelated to noisy line
noise_sz = int(x.shape[0] * 1)
x_noise = np.random.uniform(x.min(), x.max(), size=noise_sz)
y_noise = np.random.uniform(y.min(), y.max(), size=noise_sz)

plt.figure(figsize=figsize)
plt.plot(x, y, "*")
plt.plot(x_noise, y_noise, "*")
plt.show()

# %%
x = np.concatenate((x, x_noise))
y = np.concatenate((y, y_noise))

# %% [markdown]
# ### Run RANSAC
# %%

def basic_ransac(x, TH):
    # ====== choose 2 random inds
    rand_indices = np.random.choice(x.shape[0], size=2)

    # ====== build LS data:
    x_vec = x[rand_indices].reshape(-1, 1)
    X = np.concatenate((x_vec, np.ones(x_vec.shape)), axis=1)
    y_vec = y[rand_indices].reshape(-1, 1)

    b = np.linalg.lstsq(X, y_vec, rcond=None)[0].flatten()

    # ====== build fitted line
    line_p1 = np.array([x.min(), b[0] * x.min() + b[1]])
    line_p2 = np.array([x.max(), b[0] * x.max() + b[1]])
    inliers_ind = []

    # ====== distance of fit line from each sample to determine inliers
    for j in range(x.shape[0]):
        p_j = np.array([x[j], y[j]])

        # https://en.wikipedia.org/wiki/Cross_product#Geometric_meaning
        # |a X b| = |a||b|sin(t) -> |a X b|/|b| = |a|sin(t)
        d_j = np.linalg.norm(np.cross(line_p1 - p_j, line_p2 - line_p1)) / np.linalg.norm(line_p2 - line_p1)
        if d_j <= TH:
            inliers_ind.append(j)

    inliers_ind = np.array(inliers_ind)
    return b, inliers_ind

# %%
TH = 1
num_cycles = 10

num_best_inliers = 0
best_cycle_ind = -1
inliers_ind_list = []
b_list = []

for i in range(num_cycles):
    b, inliers_ind = basic_ransac(x, TH)
    inliers_ind_list.append(inliers_ind)
    b_list.append(b)

    # ====== save best model
    if num_best_inliers < inliers_ind.shape[0]:
        num_best_inliers = inliers_ind.shape[0]
        best_cycle_ind = i
# %%
# plot best fit
plt.rcParams["figure.figsize"] = [20, 20]

for i in range(num_cycles):
    plt.subplot(int(num_cycles / 2), 2, i + 1)
    ax = plt.gca()
    x_axis = np.arange(11)
    ax.plot(x_axis, b_list[i][0] * x_axis + b_list[i][1], "r")
    ax.plot(x, y, "*")
    ax.plot(x[inliers_ind_list[i]], y[inliers_ind_list[i]], "*k")
    ax.set_xlim([0, 10])
    ax.set_ylim([0, 25])
    if i == best_cycle_ind:
        plt.title("!!! BEST FIT !!! num inliers: " + str(inliers_ind_list[i].shape[0]))
    else:
        plt.title("num inliers: " + str(inliers_ind_list[i].shape[0]))
plt.show()

# %% [markdown]
# ### Test RANSAC with sklearn package (a known machine learning package in python)
# Code taken from: https://scikit-learn.org/stable/auto_examples/linear_model/plot_ransac.html
# %%
from sklearn import linear_model

X = x.reshape(-1, 1)

lr = linear_model.LinearRegression()
lr.fit(X, y)

# Robustly fit linear model with RANSAC algorithm
ransac = linear_model.RANSACRegressor()
ransac.fit(X, y)
inlier_mask = ransac.inlier_mask_
outlier_mask = np.logical_not(inlier_mask)

# Predict data of estimated models
line_X = np.arange(X.min(), X.max())[:, np.newaxis]
line_y = lr.predict(line_X)
line_y_ransac = ransac.predict(line_X)

plt.figure()
plt.scatter(X[inlier_mask], y[inlier_mask], color="yellowgreen", marker=".", label="Inliers")
plt.scatter(X[outlier_mask], y[outlier_mask], color="red", marker=".", label="Outliers")
plt.plot(line_X, line_y, color="navy", linewidth=2, label="Linear regressor")
plt.plot(line_X, line_y_ransac, color="cornflowerblue", linewidth=2, label="RANSAC regressor")
plt.legend(loc="lower right")
plt.title("RANSAC using sklearn package")
plt.show()
# %%


import numpy as np
import numpy.linalg as la

small_number= 1e-14

def gram_schmidt_basis(A):

    # set B as a copy of A, since its values are going to be altered
    B = np.array(A, dtype=np.float_)

    # looping over the vectors, starting with zero, label them with i
    for i in range(B.shape[1]) :

        # Inside the above loop, looping all previous j vectors to substract
        for j in range(i):

            # Substracting the overlap with previous vectors using the corresponding dot product
            # current vector is B[:, i], previous vector is B[:, j]
            B[:, i] = B[:,i] - B[:,i] @ B[:,j] * B[:,j]

        # normalisation test for B[:, i]
        if la.norm(B[:, i]) > small_number:
            B[:, i] = B[:, i] / la.norm(B[:, i])
        else :
            B[:, i] = np.zeros_like(B[:, i])

    # returning the result
    return B

# using the Gram-schmidt process to calculate the dimension spanned by a list of vectors
# the sum of all the norms will be the final number of dimensions (since each vector is normalized to 1 or 0)
def calc_dimensions(A):
    return np.sum(la.norm(gram_schmidt_basis(A), axis=0))

# test the function
vector_v = np.array([[1,0,2,6],
                    [0,1,8,2],
                    [2,8,3,1],
                    [1,-6,2,3]], dtype=np.float_)

gram_schmidt_basis(vector_v)

# find number of dimensions of "vector_v"
calc_dimensions(vector_v)


"""
General mathematical transformations and statistical methods.

"""

import numpy as np
from scipy.stats import zscore

def modified_zscore(x: np.ndarray) -> np.ndarray:
    """
    Modified z-score transformation.

    The modified z score might be more robust than the standard z-score because
    it relies on the median for calculating the z-score. It is less influenced
    by outliers when compared to the standard z-score.

    Parameters
    ----------
    x: (N,) np.ndarray
        numbers

    Returns
    -------
    z: (N,) np.ndarray
        z-scored numbers computed using modified z-score

    """
    med = np.median(x)
    med_abs_dev = np.median(np.abs(x - med))
    return (x - med) / (1.486 * med_abs_dev)

def find_outliers(scores: np.ndarray,
                  threshold: float = 3.0,
                  max_iter: int = 5,
                  tail: int = 0) -> np.ndarray:
    """
    Find outliers via iterated z-scoring.

    This procedure compares absolute z-scores against the threshold.
    After excluding outliers, the comparison is repeated until no
    outliers are present.

    Parameters
    ----------
    scores : (N,) np.ndarray
        The scores for which to find outliers.
    threshold : float, optional
        The value above which a feature is classified as outlier.
    max_iter : int, optional
        The maximum number of iterations.
    tail : one of {0, 1, -1}, optional
        Whether to search for outliers on both extremes of the z-scores (0),
        or on just the positive (1) or negative (-1) side.

    Returns
    -------
    bad_idx : (M,) np.ndarray[int]
        The indices of outliers found in `scores`.

    Notes
    -----
    This code adapted from mne.preprocessing.bads._find_outliers

    """
    bad_idx = list()
    remaining_idx = list(range(len(scores)))

    for _ in range(max_iter):
        x = scores[remaining_idx]
        if tail == 0:
            this_z = np.abs(zscore(x))
        elif tail == 1:
            this_z = zscore(x)
        elif tail == -1:
            this_z = -zscore(x)
        else:
            raise ValueError("Tail parameter %s not recognised." % tail)

        local_bad = this_z > threshold
        if not np.any(local_bad):
            break

        ix_to_remove = [remaining_idx[i] for i in np.where(local_bad)[0]]
        for ix in ix_to_remove:
            bad_idx.append(ix)
            remaining_idx.remove(ix)

    return np.array(bad_idx, dtype=int)

def rms(x: np.ndarray) -> float:
    """ Compute root-mean-square. """
    return np.sqrt(np.mean(np.power(x, 2)))

def logistic4(x: np.ndarray, a: float, b: float, c: float, d: float) -> np.ndarray:
    """
    4PL logistic equation.

    Parameters
    ----------
    x: (N,) np.ndarray
        scalar array, eg logarithmic concentration of a drug
    a: float
        response as x -> 0
    b: float
        slope
    c: float
        inflection point, eg EC50
    d: float
        response as x -> inf

    Returns
    -------
    (N,) np.ndarray
        transformation of `x`

    """
    return d + ((a - d) / (1.0 + ((-x / c) ** b)))


#!/usr/bin/env python

from sympy import sieve
import time

def primality_test_simple(n):
    """素数判定（単純な実装）"""

    n = int(n)
    if n < 2:
        return False
    elif n == 2:
        return True
    else:
        for i in range(2, n, 1):
            if n % i == 0:
                return False
    return True

def do_2(start, end, test_func=primality_test_simple):
    """素直な実装"""
    print("\n単純なforループ")
    #t1 = time.time()
    start = int(start)
    end = int(end)
    prime_list = []
    print("start ...")

    for n in range(start, end+1, 1):
        if test_func(n):
            prime_list.append(n)

    #print('prime number is ...')
    #print(prime_list)
    print(start, "から", end, "に素数は", len(prime_list), "個ある")
    #print("time = ", time.time() - t1)
    return prime_list

def primality_test_using_sieve(n):
    """素数判定（sieve使用）
    """

    n = int(n)
    if n in sieve:
        return True
    else:
        return False

def do_2_using_sieve(start, end):
    """sieve使用"""

    print("\nsieve使用")
    #t1 = time.time()
    start = int(start)
    end = int(end)
    print("start ...")

    prime_list = [i for i in sieve.primerange(start, end+1)]

    #print('prime number is ...')
    #print(prime_list)
    print(start, "から", end, "に素数は")
    print("Total = ", len(prime_list), "個ある")
    #print("time = ", time.time() - t1)
    return prime_list

if __name__ == '__main__':
    start = time.time()
    #print(primality_test_simple(999961))  # 100万以下で最大の素数
    do_2(1, 100000)
    #do_2_using_sieve(2, 100000)
    print("実行時間 = ", time.time() - start, " [秒]\n")


import math

from pathlib import Path
from numpy import array
import numpy as np

from manim import *

ANGLE_IN_DEGREES = -90
angle = math.radians(ANGLE_IN_DEGREES)

TRANSFORMATION_MATRIX = np.array([
    [math.cos(angle), math.sin(angle)],
    [-math.sin(angle), math.cos(angle)]
])

FONT_SIZE = 75

def convert_to_3d(vect):
    return np.array([*list(vect), 0])

def get_transform_matrix_text(matrix_2d):
    return MathTex(
        f"\\begin{{bmatrix}}{round(matrix_2d[0][0])} && {round(matrix_2d[0][1])} \\\\ {round(matrix_2d[1][0])} && {round(matrix_2d[1][1])}\\end{{bmatrix}}", font_size=FONT_SIZE)

class TransformationMatrix(Scene):
    def construct(self):
        VECT1 = np.array([3, 2])
        VECT1_COLOR = "#b9b28b"

        TRANS_VEC = TRANSFORMATION_MATRIX @ VECT1
        VECT2_COLOR = "#b98b99"

        VECT3_COLOR = "#8ba7b9"

        vect1 = Line(start=ORIGIN, end=convert_to_3d(VECT1), stroke_color=VECT1_COLOR).add_tip()

        vect1_name = MathTex(r"\begin{bmatrix}a \\ b\end{bmatrix}").next_to(vect1.get_end(), UP + RIGHT * 2, buff=0.1).set_color(VECT1_COLOR)

        trans_vect1 = Line(start=ORIGIN, end=convert_to_3d(TRANS_VEC), stroke_color=VECT1_COLOR, stroke_width=10).add_tip().set_color(VECT2_COLOR)

        # self.camera.frame_center = np.array([0, 1, 0])

        numberplane = NumberPlane(
            background_line_style={
                "stroke_opacity": 0.4
            }
        )

        matrix_text = get_transform_matrix_text(TRANSFORMATION_MATRIX)
        matrix_text.set_color("#222")

        vector_part = MathTex(f"\\begin{{bmatrix}}{VECT1[0]} \\\\ {VECT1[1]}\end{{bmatrix}}", font_size=FONT_SIZE).set_color(VECT1_COLOR)
        equals_part = MathTex(" = ", font_size=FONT_SIZE).set_color("#222")
        trans_vect_part = MathTex(f'\\begin{{bmatrix}}{round(TRANS_VEC[0], 2)} \\\\ {round(TRANS_VEC[1], 2)}\\end{{bmatrix}}', font_size=FONT_SIZE).set_color(VECT2_COLOR)

        group = VGroup(matrix_text, vector_part, equals_part, trans_vect_part).arrange()
        group.move_to(ORIGIN)
        group.shift(DOWN * 2)

        self.add(vect1, trans_vect1, numberplane, group)

if __name__ == '__main__':
    # Generate animated gif.
    config.background_color = WHITE

    config.frame_height = 8
    config.frame_width = 8

    config.pixel_width = 300
    config.pixel_height = 300

    config.output_file = Path(__file__).resolve().parent.parent.parent / Path('notes/_media/transformation-matrix-cover')
    config.save_last_frame = True

    scene = TransformationMatrix()
    scene.render()


import math
from scipy.fftpack import fft, ifft

def conv(signal1, signal2):
    """Discrete convolution by definition"""

    n = len(signal1) + len(signal2) - 1
    out = []

    for i in range(n):
        s = 0

        for j in range(i + 1):
            if j < len(signal1) and i - j  < len(signal2):
                s += signal1[j] * signal2[i - j]

        out.append(s)

    return out

def conv_fft(signal1, signal2):
    """Convolution using fft and convolutional theorem"""

    signal1 = signal1.copy()
    signal2 = signal2.copy()

    # pad signals to same len
    max_len = max(len(signal1), len(signal2))

    for i in range(max_len - len(signal1)):
        signal1.append(0)
    for i in range(max_len - len(signal2)):
        signal2.append(0)

    fft_s1 = fft(signal1)
    fft_s2 = fft(signal2)
    out = []

    for i in range(len(signal1)):
        out.append(fft_s1[i] * fft_s2[i])

    return list(ifft(out))

def main():
    # Example convolution with sin and cos
    s1 = [math.sin(x) for x in range(5)]
    s2 = [math.cos(x) for x in range(5)]

    print("Discrete Convolution")
    print(conv(s1, s2))

    print("FFT Convolution")
    print(conv_fft(s1, s2))

if __name__ == "__main__":
    main()


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

def demo_simple():
    # ref: http://mathworld.wolfram.com/HeartCurve.html
    t = np.linspace(0, 2 * np.pi, 180)
    x = 16 * np.sin(t) ** 3
    y = 13 * np.cos(t) - 5 * np.cos(2 * t) - 2 * np.cos(3 * t) - np.cos(4 * t)
    plt.plot(x, y)
    plt.show()

def demo_ani():
    def update_line(num):
        chart.set_data(data[..., :num])
        return chart,

    # ref: http://mathworld.wolfram.com/HeartCurve.html
    t = np.linspace(0, 2 * np.pi, 180)
    x = 16 * np.sin(t) ** 3
    y = 13 * np.cos(t) - 5 * np.cos(2 * t) - 2 * np.cos(3 * t) - np.cos(4 * t)
    data = np.vstack((x, y))  # 2 x 180
    fig = plt.figure()
    padding = 1.1
    plt.xlim(np.min(x) * padding, np.max(x) * padding)
    plt.ylim(np.min(y) * padding, np.max(y) * padding)
    chart, = plt.plot([], [], color="pink", linewidth=5)
    ani = animation.FuncAnimation(fig, update_line, interval=10, frames=x.size,
                                  repeat=False)
    # required ffmpeg
    ani.save("ani_demo2.mp4")
    plt.show()

def demo1():
    # heart
    # t = np.linspace(0, 2 * np.pi, 180)
    # x = 16 * np.sin(t) ** 3
    # y = 13 * np.cos(t) - 5 * np.cos(2 * t) - 2 * np.cos(3 * t) - np.cos(4 * t)

    # butterfly: https://en.wikipedia.org/wiki/Butterfly_curve_(transcendental)
    t = np.linspace(0, 5 * np.pi, 180 * 5)
    x = np.sin(t) * (np.exp(np.cos(t)) - 2 * np.cos(4 * t) - np.sin(t / 12) ** 5)
    y = np.cos(t) * (np.exp(np.cos(t)) - 2 * np.cos(4 * t) - np.sin(t / 12) ** 5)
    plot_ani(x, y, lw=3, interval=10)

def plot_ani(x, y, color="green", interval=20, lw=2):
    def update_line(num):
        chart.set_data(data[..., :num])
        return chart,

    # ref: http://mathworld.wolfram.com/HeartCurve.html
    data = np.vstack((x, y))  # 2 x 180
    fig = plt.figure()
    padding = 1.1
    plt.xlim(np.min(x) * padding, np.max(x) * padding)
    plt.ylim(np.min(y) * padding, np.max(y) * padding)
    chart, = plt.plot([], [], color=color, linewidth=lw)
    ani = animation.FuncAnimation(fig, update_line, interval=interval, frames=x.size,
                                  repeat=False)
    # required ffmpeg
    # ani.save("ani_demo2.mp4")
    plt.show()

if __name__ == '__main__':
    # demo_simple()
    # demo_ani()
    demo1()


import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim

import numpy as np

# Classfication losses

## prepare the data

# we simulate 3 class classification problem, our model retunrs from last layer this output
np_output1 = np.array([[0.5, 0.1, 0.4 ]], dtype=np.float32)
np_output2 = np.array([[0.1, 0.5, 0.3 ]], dtype=np.float32)
output1 = torch.from_numpy(np_output1)
output2 = torch.from_numpy(np_output2)
target1 = 0
target2 = 1
print(f'output1={output1} target={target1}')
print(f'output1={output2} target={target2}')

# but we know our target class, encoded as one hot encoding
np_target = np.array([[1, 0, 0 ]], dtype=np.float32)
cls_target = torch.from_numpy(np_target)

# L1 loss
print("L1")
loss = nn.L1Loss()
loss_value = loss(output1, cls_target)
print(loss_value) # (|0.5-1| + |0.1-0| + |0.4-0|)/3 = (0.5+0.1+0.4)/3 = 1.0/3 = 0.333

# for second wrong output the loss should be higher
loss_value = loss(output2, cls_target)
print(loss_value) # (|0.1-1| + |0.5-0| + |0.3-0|)/3 = (0.9+0.5+0.3)/3 = 1.7/3 = 0.5667

# CrossEntropy - This criterion combines nn.LogSoftmax() and nn.NLLLoss() in one single class.
# It is useful when training a classification problem with C classes.

print("CrossEntropyLoss")
loss = nn.CrossEntropyLoss()

class_number =torch.tensor([target1], dtype=torch.long)
loss_value = loss(output1, class_number)
print(loss_value) # =

class_number =torch.tensor([target2], dtype=torch.long)
loss_value = loss(output2, class_number)
print(loss_value) # =

# CrossEntropy for mini batch of size 2
print("CrossEntropyLoss - batch")
loss = nn.CrossEntropyLoss(reduction='none')
# input = torch.randn(3, 5)
# target = torch.empty(3, dtype=torch.long).random_(5)

# now we have 3 output classes and 2 examples in mini-batch
#batch_output = torch.stack([output1, output2])
batch_output = torch.cat([output1, output2])
# target for each mini-batch exmaple, should be from [0,2] -
batch_target = torch.tensor( [target1, target2], dtype= torch.int64)
loss_value = loss(batch_output, batch_target)
print(loss_value) # =

# regression problem

# we simulate 3 variable regression problem, our model returns from last layer this output
np_output1 = np.array([-0.9, 3.3, 4.5 ], dtype=np.float32)
np_output2 = np.array([5., -1, 3. ], dtype=np.float32)
# but we know our target is
np_target = np.array([-1., 3., 4. ], dtype=np.float32)

# make tensors
output1 = torch.from_numpy(np_output1)
output2 = torch.from_numpy(np_output2)
reg_target = torch.from_numpy(np_target)

# L2 loss - MSE - mean square error
print("MSE Loss")
print(f'output1={output1}')
print(f'output2={output2}')
print(f'target={reg_target}')

loss = nn.MSELoss()
loss_value = loss(output1, reg_target)
print(f'loss(output1, target)={loss_value}') # ((-0.9- -1)^2 + (3.3-3)^2 + (4.5-4)^2)/3 = 0.1167

# for second wrong output the loss should be higher
loss_value = loss(output2, reg_target)
print(f'loss(output2, target)={loss_value}')  # = 17.666

print("MSEs - without reduction")
loss = nn.MSELoss(reduction='none')
loss_value = loss(output1, reg_target)
print(f'loss(output1, target)={loss_value}') # [(-0.9- -1)^2 ,  (3.3-3)^2,  (4.5-4)^2 ] = [ 0.01, 0.09, 0.25]

# Multi-label

# BCE loss

# we have batch_size=3 (one example in a row), multi label problem
# this should be returned in the last layer of our model
# data shouldbe numbers from [0,1]
np_output1 = np.array([[0.1, 0.9], [0.6, 0.8], [0.8, 1.]], dtype=np.float32)
np_output2 = np.array([[0.4, 0.5], [0.1, 0.9], [0.1, 0.9]], dtype=np.float32)
# but we know our target is
np_target = np.array([[0., 1.],[1., 0.],[1., 1.]], dtype=np.float32)

# make tensors
output1 = torch.from_numpy(np_output1)
output2 = torch.from_numpy(np_output2)
target = torch.from_numpy(np_target)

# Binary Cross Entropy
print("BCELoss")
print(f'output1={output1}')
print(f'output2={output2}')
print(f'target={target}')

loss = nn.BCELoss()

# L= sum_i { l_i = -1*[ y_i * log(x_i) + (1-y_i)*log(1-x_i) ]}
loss_value = loss(output1, target)
print(f'loss(output1, target)={loss_value}') #

# for second wrong output the loss should be higher
loss_value = loss(output2, target)
print(f'loss(output2, target)={loss_value}')  # =

print("BCELoss - without reduction")
loss = nn.BCELoss(reduction='none')
loss_value = loss(output1, target)
print(f'loss(output1, target)={loss_value}') # =

print("BCELoss - reduction sum")
loss = nn.BCELoss(reduction='sum')
loss_value = loss(output1, target)
print(f'loss(output1, target)={loss_value}') # =

lst = []
x = output1.numpy()
y = target.numpy()
for i in range(len(x)):
    lst.append(-np.log(x[i])*y[i] + -np.log(1-x[i])*(1-y[i]))
print(lst, np.mean(lst))


import math
import numpy as np

def euclidian_distance(first: "List[float]", second: "List[float]") -> float:
    """
    Given two vectors, returns the euclidian distance between them
    Requires that they are the same dimension

    :param first: a vector
    :param second: a vector

    :return: the distance as a float
    """
    if len(first) != len(second):
        raise Exception("These vectors must be the same size")
    return math.sqrt(sum([pow(x - y, 2) for x, y in zip(first, second)]))

def minkowski_distance(
    first: "List[float]", second: "List[float]", order: int
) -> float:
    """
    Given two vectors, returns the Minkowski distance between them
    Requires that they are the same dimension

    :param first: a vector
    :param second: a vector
    :param order: int

    :return: the distance as a float
    """
    if len(first) != len(second):
        raise Exception("These vectors must be the same size")
    return pow(
        sum([pow(np.abs(x - y), order) for x, y in zip(first, second)]), 1 / order
    )

def manhatten_distance(first: "List[float]", second: "List[float]") -> float:
    """
    Given two vectors, returns the manhatten distance between them
    Requires that they are the same dimension

    :param first: a vector
    :param second: a vector

    :return: the distance as a float
    """
    if len(first) != len(second):
        raise Exception("These vectors must be the same size")

    return sum([abs(x - y) for x, y in zip(first, second)])

def cosine_similarity(first: "List[float]", second: "List[float]") -> float:
    """
    Given two vectors, returns the cosine similarity between them
    Requires that they are the same dimension

    :param first: a vector
    :param second: a vector

    :return: the similarity as a float
    """
    if len(first) != len(second):
        raise Exception("These vectors must be the same size")
    numerator = sum(x * y for x, y in zip(first, second))
    denominator = math.sqrt(sum(pow(x, 2) for x in first)) * math.sqrt(
        sum(pow(y, 2) for y in second)
    )
    if denominator == 0:
        return 0
    return numerator / denominator

def jaccard_similarity(first: "Set[object]", second: "Set[object]") -> float:
    """
    Given two sets, returns the jaccard similarity between them

    :param first: a set
    :param second: a set

    :return: the similarity as a float
    """
    return len(first & second) / len(first | second)


#!/usr/bin/env python

import numpy as np
import matplotlib.pyplot as plt
from numpy.linalg import inv
from numpy.linalg import norm
from math import isnan

def jacobian_1(x):
    """Jacobiano do sistema 5.7.2 da pagina 175 do livro
    Numerical Methods in Economics, de Keneth Judd."""
    return np.array([[.2 * x[0] ** (-.8), .2 * x[1] ** (-.8)],
                     [.1 * x[0] ** (-.9), .4 * x[1] ** (-.6)]])

def jacobian_2(x):
    """Jacobiano do sistema 5.7.3 da pagina 175 do livro
    Numerical Methods in Economics, de Keneth Judd."""
    return np.array([[5. * (x[0] ** .2 + x[1] ** .2) ** 4. * .2 * x[0] ** (-.8),
                      5. * (x[0] ** .2 + x[1] ** .2) ** 4. * .2 * x[1] ** (-.8)],
                     [4. * (x[0] ** .1 + x[1] ** .4) ** 3. * .1 * x[0] ** (-.9),
                      4. * (x[0] ** .1 + x[1] ** .4) ** 3. * .4 * x[1] ** (-.6)]])

def function_1(x):
    """Sistema 5.7.2 da pagina 175 do livro
    Numerical Methods in Economics, de Keneth Judd."""
    return np.array([x[0] ** .2 + x[1] ** .2 - 2.,
                     x[0] ** .1 + x[1] ** .4 - 2.])

def function_2(x):
    """Sistema 5.7.2 da pagina 175 do livro
    Numerical Methods in Economics, de Keneth Judd."""
    return np.array([(x[0] ** .2 + x[1] ** .2) ** 5. - 32.,
                     (x[0] ** .1 + x[1] ** .4) ** 4. - 16.])

def newton(function, jacobian, initial, e=0.00001, d=0.00001, print_iter=True,
           plot_iter = False):
    """Funcao que aplica o metodo de Newton-Raphson a funcao 'function' com
    jacobiano 'jacobian' inseridos como funcoes do Python. 'initial' e o ponto
    inicial para as iteracoes. 'e' e o nivel de precisao desejado e d e a
    precisao desejada em relacao ao resultado. 'print_iter' escolhe se quer
    que imprima os pontos intermediarios das iteracoes e 'plot_iter' escolhe
    se quer imprimir estes pontos num grafico."""
    x = initial
    iter = 0
    while True:
        try:
            if print_iter:
                print(x)
            if plot_iter:
                plt.scatter(x[0], x[1])
            s = np.dot(-inv(jacobian(x)), function(x)) # Equacao do metodo
            x_ante = x
            x = x + s
            iter += 1
            if norm(x_ante - x) <= e * (1 + norm(x)) or isnan(x[0]) or\
               isnan(x[1]): # Verifica criterio de convergencia
                break
        except:
            print("Erro")
            break
    if norm(function(x)) <= d: # Verifica se a solucao atende criterio
        print("Convergiu em " + str(iter) + " iterações iniciando em " +\
              str(np.round(initial, 2)))
        return x
    else:
        print("Método não convergiu iniciando em " + str(np.round(initial, 2)))

# O loop abaixo roda o metodo no retangulo [0,2]**2 para function_1
for i in (np.array(range(20))+1.)/10:
    for j in (np.array(range(20))+1.)/10:
        newton(function_1, jacobian_1, [i, j], print_iter=False)

# O loop abaixo roda o metodo no retangulo [0,2]**2 para function_2
for i in (np.array(range(20))+1.)/10:
    for j in (np.array(range(20))+1.)/10:
        newton(function_2, jacobian_2, [i, j], print_iter=False)

###############################################################################
# Note que os loops acima mostram que a transformacao da function_2 tornou    #
# a convergencia mais rapida, exigindo em media uma iteracao a menos no       #
# dominio testado                                                             #
###############################################################################

# O comando abaixo imprime um grafico com os pontos da iteracao da function_1
newton(function_1, jacobian_1, [2, 2], plot_iter=True)

# O comando abaixo imprime um grafico com os pontos da iteracao da function_2
newton(function_2, jacobian_2, [2, 2], plot_iter=True)


# -*- coding: utf-8 -*-
"""aula_python_4.ipynb

Automatically generated by Colaboratory.

Original file is located at
    https://colab.research.google.com/drive/10JwswxJwA6xL3sSl9tIH0HMp3-ZLC5gH

# Revisão

def inicia a definição de uma função
"""

def parabola(x, a, b, c):
    y = a * x ** 2 + b * x + c
    return y

parabola(1, 5, 2, 4)

"""Funções com argumentos com valor padrão:"""

def parabola_2(x, a=5, b=2, c=4):
    y = a * x ** 2 + b * x + c
    return y

parabola_2(1, 6, 7, 8)

"""Funções anônimas podem ser criadas com a palavra-chave lambda. As funções lambdas são restritas a uma única (em geral pequena) expressão. """

f2 = lambda x: x ** 2

f2(5)

f_soma = lambda a, b: a + b
f_soma(5, 9)

"""# Estrutura de dados

Veja a seguir mais métodos de objetos do tipo lista:
"""

l = [5, 18, 43, 77, 91, 18, 77]

"""Adicionar um elemento ao final da lista"""

l.append(100)
print(l)

"""Inserir um elemento em uma dada posição. Nesse caso insere o elemento 9 na posição de índice 1. """

l.insert(1, 9)
print(l)

"""Remover o primeiro elemento da lista que tem valor igual ao argumento (nesse caso remove o elemento 18)"""

l.remove(18)
print(l)

"""Contar o número de vezes que um elemento aparece na lista"""

l.count(77)

print(l)

"""Remover o elemento em uma dada posição. Esse método remove o elemento e o retorna.

Nesse caso remove o elemento de índice 1.
"""

l.pop(1)

print(l)

"""Devolver o índice do primeiro elemento cujo valor é igual ao argumento (nesse caso 91)

"""

l.index(91)

l.index(77)

l.index(53)

"""Ordenar os elementos da lista"""

print("lista:", l)
l.sort()
print("lista ordenada:", l)

"""Ordenar em ordem reversa"""

l = [5, 43, 77, 91, 18, 77, 100]  # definindo novamente a lista l
l.sort(reverse=True)
print(l)

"""Inverter a ordem dos elementos"""

l = [5, 43, 77, 91, 18, 77, 100]  # definindo novamente a lista l
l.reverse()
print(l)

"""Copiar os elementos da lista"""

l_c = l.copy()
print(l_c)

"""Para iterar sobre uma lista, a posição e o valor podem ser obtidos simultaneamente com a função enumerate()"""

for i, v in enumerate(l_c):
    print(i, v)

"""Para percorrer duas ou mais listas ao mesmo tempo, as entradas podem ser pareadas com a função zip()"""

l1 = [2.5, 3.9, 8.7]  # definindo lista l1
l2 = [8.7, 23.4, 12.5]  # definindo lista l2
for a, b in zip(l1, l2):
    print(a, b)

"""Uma lista pode ser percorrida na ordem inversa com a função reversed()"""

for z in reversed(range(10)):
    print(z)

"""**Compreensões de listas**

Compreensões de listas são uma maneira concisa de criar uma lista. Uma aplicação comum é criar uma nova lista onde cada elemento é resultante da avaliação de uma expressão no contexto das cláusulas for e if.

Por exemplo, podemos criar uma lista com os quadrados desta forma:

"""

quadrados = []  # cria uma lista vazia e armazena na variável quadrados
for x in range(10):
    quadrados.append(x ** 2)

print(quadrados)

"""Alternativamente, podemos fazer:"""

quadrados2 = [x ** 2 for x in range(10)]
print(quadrados2)

quadrados3 = list(map(lambda x: x ** 2, range(10)))
print(quadrados3)

"""**Tuplas**

Uma tupla consiste em uma sequência de valores separados por vírgulas. Na sua criação os valores de uma tupla podem ser envolvidos ou não por parênteses. Na saída do console tuplas são sempre envolvidas por parênteses. Apesar de serem similares às listas, as tuplas são frequentemente utilizadas em situações e com propósitos distintos. O principal motivo é que tuplas são imutáveis, enquanto listas são mutáveis.
"""

t = 5, 87, 'bla'
print(t)

"""Tuplas podem ser indexadas

"""

print(t[2])

"""É possível criar uma tupla contendo outras tuplas

"""

u = t, (56, 89, 346)
print(u[0][0])

"""Tuplas são **imutáveis**!"""

t[0] = 65

v = (27.6, 45.9)  # 'empacotamento' de dois valores em uma tupla
vx, vy = v  # processo inverso de 'desempacotamento' também é possível
print(vx)

"""
**Dicionários**

Uma estrutura de dados muito útil é o dicionário. Eles são indexados por palavras-chaves (keys), que podem ser de qualquer tipo imutável. Os dicionários são delimitados por {} e contém uma lista de pares chave:valor separados por vírgulas. O dicionário vazio é {}. Os dicionários são utilizados para armazenar e recuperar valores a partir das palavras-chaves. É possivel substituir uma valor de uma chave por um novo valor. """

ramais = {'Cristina': 6473, 'Ana': 6486, 'Marina': 7256, 'Allan': 7257, 'Gustavo': 7257}
print(ramais['Marina'])

"""Remover um par chave:valor"""

del (ramais['Allan'])
print(ramais)

"""Incluir novo par chave:valor"""

ramais['Charles'] = 6486
print(ramais)

"""Alterar o valor de uma chave"""

ramais['Cristina'] = 6478
print(ramais)

"""Listar todas as chaves presentes no dicionário """

list(ramais)

"""Verificar se uma palavra-chave está ou não no dicionário"""

'Cristina' in ramais

'Sandra' in ramais

"""O construtor dict cria um dicionário a partir de sequências de pares chave,valor"""

dict([('Cristina', 6473), ('Ana', 6486), ('Marina', 7256)])

dict(Allan=7257, Charles=6486)

"""As compreensões de dicionário também podem ser usadas para criar dicionários a partir de expressões"""

dici = {x: x ** 2 for x in range(10)}
print(dici)

"""Ao iterar sobre dicionários, a chave e o valor podem ser obtidos simultaneamente usando o método items()"""

for nome, fone in ramais.items():
    print(nome, fone)

"""# Módulos

Uma boa prática ao escrever códigos maiores é dividí-lo em arquivos menores, o que pode facilitar a manutenção.

Um módulo é um arquivo contendo definições e instruções Python. O nome do arquivo é o nome do módulo acrescido do sufixo .py.

Um módulo também pode ser muito útil para escrever funções que são usadas em vários programas diferentes. É preferível usar um arquivo separado para uma função do que copiá-la para dentro de vários programas.

Por exemplo, usando um editor de texto, foi criado um arquivo chamado fibonacci.py, contendo as funções fib e fib_lista, apresentadas na última aula. Ao importar um módulo, as funções definidas no arquivo são colocadas diretamente na tabelas de símbolos atual e poderão ser chamadas e executadas.
"""

import fibonacci

fibonacci.fib(20)

fibonacci.fib_lista(50)

"""Módulos podem importar outros módulos. É costume colocar todos os comandos de import no início do módulo ou de um programa.

Outras maneiras de importar as definições de um módulo são:
"""

from fibonacci import fib, fib_lista

from fibonacci import *
# Importa todos os nomes definidos em um módulo
# Nem sempre aconselhado, pois introduz um conjunto desconhecido de nomes no ambiente,
# podendo levar a conflitos com outros nomes previamente definidos

import fibonacci as fibo

fibo.fib(10)

from fibonacci import fib_lista as f_l

f_l(30)

"""O Python guarda versões compiladas de cada módulo no diretório \__ pycache__ com o nome modulo.versão.pyc para acelerar o carregamento de módulos.

# Pacotes

Os pacotes são uma maneira de estruturar o “espaço de nomes” (namespace) dos módulos Python. Eles são uma coleção de módulos. Nos referimos a um módulo dentro de um pacote como nomedopacote.nomedomodulo.

Os arquivos \__ init__.py são necessários para que o Python trate diretórios contendo o arquivo como pacotes. \__ init__.py pode ser apenas um arquivo vazio.

O pacote **math** contém todas as funções matemáticas padrão (sqrt,log, log10, exp, funções trigonométricas).
"""

from math import log

x = log(3)
print(x)

from math import pi

print(pi)

from math import exp
from math import sin, cos

from math import *

# Algoritmo para converter a posição de um ponto dada em coordenadas polares (r,theta) para coordenadas
# Cartesianas (x,y)

from math import sin, cos, pi

r = float(input('Entre o valor de r: '))
d = float(input('Entre o valor de theta em graus: '))

theta = d * pi / 180.  # conversão de graus para radianos
x = r * cos(theta)
y = r * sin(theta)

print('x=', x, 'y=', y)

"""O pacote **numpy** é fundamental para computação científica com Python. Ele oferece funções matemáticas, geradores de números aleatórios, rotinas de álgebra linear, transformada de Fourier e muito mais. Mas o seu poder está em permitir cálculos em arrays multidimensionais.

O numpy executa facilmente cálculos numéricos que são muito utilizados em problemas de machine learning (aprendizado de máquina) e de processamento de imagem (imagens no computador são representadas como arrays multidimensionais de números). Além disso, o numpy oferece várias outras tarefas matemáticas.

Para mais informações, consulte https://numpy.org/.

# Arrays

Um array é um conjunto ordenado de valores. Mas há diferenças importantes entre listas e arrays:
* O número de elementos de um array é fixo e não é possível adicionar ou remover elementos de um array após criá-lo;
* Os elementos de um array devem ser todos de um mesmo tipo, como float ou inteiros.

As principais vantagens de usar arrays em vez de listas são:
* Arrays podem ter qualquer número de dimensões. Arrays unidimensionais e bidimensionais são, respectivamente, como vetores e matrizes em álgebra linear. Listas são sempre unidimensionais.
* Como se comportam como vetores ou matrizes, podemos fazer qualquer aritmética com eles, ao contrário de listas.
* Arrays funcionam mais rápido que listas em Python.

Cada dimensão de um array é chamada axis. O número de axes é chamada de rank do array. A indexação dos arrays também começa em 0, com um número para cada axis do array.
"""

from numpy import array

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

print(a)

# Minha recomendação
import numpy as np

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

print(a)

type(a)  # checar que o tipo é um numpy array

a.dtype  # fornece o tipo dos elementos do array

a.shape  # dimensões do array

a.ndim  # número de dimensões

a.size  # número de elementos

"""Indexação de um array: o índice da linha vem primeiro e depois o da coluna"""

print(a)

print(a[2, 1])

a[1, 0:2]

"""Iterando sobre um array

"""

b = np.array([0, 2, 5, 8], dtype=int)
print(b)

for z in b:
    print(z)

for i in range(len(b)):
    print(b[i])

"""Outras formas de criar arrays

"""

a0 = np.zeros(5)
print(a0)

a0_2 = np.zeros([3, 4])
print(a0_2)

a1 = np.ones(7)
print(a1)

a1_2 = np.ones([2, 3])
print(a1_2)

a1_3 = np.ones([2, 3, 4])
print(a1_3)

"""A função arange retorna valores espaçados igualmente em um certo intervalo.
O espaçamento é definido pelo passo (terceiro argumento da função).

numpy.arange(início,fim,passo)
"""

a2 = np.arange(1., 4., 0.5)
print(a2)

"""A função linspace também retorna n valores espaçados igualmente em um certo intervalo.

numpy.linspace(início,fim,n)
"""

a3 = np.linspace(1., 10., 3)
print(a3)

"""É possível criar arrays com números aleatórios (entre 0 e 1)

"""

a5 = np.random.rand(3, 4, 2)
print(a5)

"""**Operações com arrays**

O poder do numpy vem da possibilidade de realizar operações com arrays.
"""

c = np.array([1, 2])
d = np.array([3, 4])

print(c + d)

print(c * d)

print(c ** d)

e = a1_2 + 6.5
print(e)

# Produto escalar de dois array é feito pela função dot
prod_esc = np.dot(c, d)
print(prod_esc)

log10_c = np.log10(a5)
print(log10_c)
print(log10_c[1, 0, 0])

"""Cuidado ao fazer cópias de arrays"""

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

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


import numpy as np
import time
import pandas as pd

def main():
    # initi conditions---------------------------------------------------------
    A=2
    B=0.01
    t0=0.0
    t_end=5
    #--------------------------------------------------------------------------

    # Analytic solution
    analytic = lambda t_ini, y_ini, t, a, b: 1/(b/a+(1/y_ini-b/a)*np.exp(-a*(t-t_ini)))
    #--------------------------------------------------------------------------
    # ODE
    f = lambda y: A*y-B*y*y
    #--------------------------------------------------------------------------
    # initial valuse
    y0_list = np.array([8, 10, 12])
    #y0_list = np.array([100, 190, 250])
    #--------------------------------------------------------------------------

    timesteps = np.array([0.05, 0.025, 0.0125, 0.00625, 0.003125])

    # maps of 6th order for different time steps (A=2, B=0.01)
    # M(0,:) is map for dt=0.05     (N=100)
    # M(1,:) is map for dt=0.025    (N=200)
    # M(2,:) is map for dt=0.0125   (N=400)
    # M(3,:) is map for dt=0.00625  (N=800)
    # M(4,:) is map for dt=0.003125 (N=1600)
    Maps = np.array([[-2.20674242163663e-17,7.2859273622565e-14,-1.57090670168903e-10,3.0491292885473e-7,-0.000581078444935817,1.10516564125989,0.000148534476733103],
                    [-8.20209311569985e-19,4.34779284638878e-15,-1.76489909946068e-11,6.90754305025356e-8,-0.000269497671189628,1.05127100148724,2.69139400711076e-6],
                    [-2.79644218719739e-20,2.60080775407912e-16,-2.07823660780399e-12,1.6426765170331e-8,-0.000129779855270288,1.02531511893295,4.53070556313535e-8],
                    [-9.12880385587037e-22,1.57930196254673e-17,-2.51879120712609e-13,4.00518610053792e-9,-6.3683344531535e-5,1.01257845151487,7.34983595234415e-10],
                    [-2.91577138323977e-23,9.70954867859659e-19,-3.09980538706333e-14,9.88849311608068e-10,-3.15443976782067e-5,1.00626957200335,1.16379709756459e-11]
                    ])

    #--------------------------------------------------------------------------
    # resulting table
    # result(:, 0) is N
    # result(:, 1) is err_RK4
    # result(:, 2) is err_TM6
    # result(:, 3) is time_RK4
    # result(:, 4) is time_TM6
    # result(:, 5) is time_ratio = time_RK4/time_TM6
    result = np.zeros((len(timesteps), 6))

    for k, dt in enumerate(timesteps):  # for each time stemp dt
        M = Maps[k, :]                 # get TM for this dt
        N = int((t_end-t0)/dt)
        t = np.arange(t0, t_end, dt)

        result[k, 0] = N

        for y0 in y0_list: # for each y0
            # analytic solution------------------------------------------------
            y_sol = analytic(t0, y0, t, A, B)
            #------------------------------------------------------------------

            # RK4 integration--------------------------------------------------
            y_rk4 = np.zeros(N)

            y_rk4[0]=y0
            tic = time.time()
            for i in range(N-1):
                y = y_rk4[i]
                k1 = f(y)
                k2 = f(y+dt*k1/2)
                k3 = f(y+dt*k2/2)
                k4 = f(y+dt*k3)
                y_rk4[i+1] = y + dt*(k1+2*k2+2*k3+k4)/6

            elapsed_time = time.time()-tic
            result[k, 3] += elapsed_time # time_RK4
            #------------------------------------------------------------------

            # Mapping----------------------------------------------------------
            y_map = np.zeros(N)
            y_map[0] = y0
            tic = time.time()
            for i in range(N-1):
                y=y_map[i]
                y2 = y*y
                y3 = y2*y
                y_map[i+1] = (y3*(M[0]*y3 + M[3]) +
                            y2*(M[1]*y3 + M[4]) +
                            y *(M[2]*y3 + M[5]) +
                            M[6])

            elapsed_time = time.time()-tic
            result[k, 4] += elapsed_time # time_RK4
            #------------------------------------------------------------------
            result[k, 1] += np.abs(y_rk4 - y_sol).max() # err_RK4
            result[k, 2] += np.abs(y_map - y_sol).max() # err_TM6

    result[:, 1:] /= len(y0_list) # get average results
    result[:, 5] = result[:, 3]/result[:, 4] # get time_ratio

    result = pd.DataFrame(data=result[:,1:], index=result[:,0], columns=np.array(['err_RK4', 'err_TM6', 'time_RK4', 'time_TM6', 'time_ratio']))
    return result

if __name__ == "__main__":
    print(main())


import numpy as np
from math import sqrt

def cubic_spline_interpolation(x0, x, y):

x = np.asfarray(x)
y = np.asfarray(y)
size = len(x)
xdiff = np.diff(x)
ydiff = np.diff(y)
Li = np.empty(size)
Li_1 = np.empty(size-1)
z = np.empty(size)
Li[0] = sqrt(2*xdiff[0])
Li_1[0] = 0
B0 = 0
z[0] = B0 / Li[0]

for i in range(1, size-1, 1):
Li_1[i] = xdiff[i-1] / Li[i-1]
Li[i] = sqrt(2*(xdiff[i-1]+xdiff[i]) - Li_1[i-1] * Li_1[i-1])
Bi = 6*(ydiff[i]/xdiff[i] - ydiff[i-1]/xdiff[i-1])
z[i] = (Bi - Li_1[i-1]*z[i-1])/Li[i]
i = size - 1
Li_1[i-1] = xdiff[-1] / Li[i-1]
Li[i] = sqrt(2*xdiff[-1] - Li_1[i-1] * Li_1[i-1])
Bi = 0
z[i] = (Bi - Li_1[i-1]*z[i-1])/Li[i]
i = size-1
z[i] = z[i] / Li[i]

for i in range(size-2, -1, -1):
z[i] = (z[i] - Li_1[i-1]*z[i+1])/Li[i]
index = x.searchsorted(x0)
np.clip(index, 1, size-1, index)
xi1, xi0 = x[index], x[index-1]
yi1, yi0 = y[index], y[index-1]
zi1, zi0 = z[index], z[index-1]
hi1 = xi1 - xi0
f0 = zi0/(6*hi1)*(xi1-x0)**3 + \
zi1/(6*hi1)*(x0-xi0)**3 + \
(yi1/hi1 - zi1*hi1/6)*(x0-xi0) + \
(yi0/hi1 - zi0*hi1/6)*(xi1-x0)
return f0

if __name__ == '__main__':
import matplotlib.pyplot as plt
x = np.linspace(0.9, 13.3, num=21)
y = np.array([1.3, 1.5, 1.85, 2.1, 2.6, 2.7, 2.4, 2.15, 2.05, 2.1, 2.25, 2.3, 2.25, 1.95,
1.4, 0.9, 0.7, 0.6, 0.5, 0.4, 0.25])
plt.scatter(x, y, color = 'red')
x_new = np.linspace(0.9, 13.3, num=50)
plt.plot(x_new, cubic_spline_interpolation(x_new, x, y), 'k--')
plt.ylim(-1,5)
plt.title('Cubic Spline Interpolation')
plt.xlabel('x')
plt.ylabel('f(x)')
plt.ylim(-1,5)
plt.show()


import numpy as np
import numpy.linalg as la

verySmallNumber = 1e-14 # 1×10⁻¹⁴ = 0.00000000000001

def gsBasis4(A) :
    B = np.array(A, dtype=np.float_)
    B[:, 0] = B[:, 0] / la.norm(B[:, 0])
    B[:, 1] = B[:, 1] - B[:, 1] @ B[:, 0] * B[:, 0]
    if la.norm(B[:, 1]) > verySmallNumber :
        B[:, 1] = B[:, 1] / la.norm(B[:, 1])
    else :
        B[:, 1] = np.zeros_like(B[:, 1])
    B[:, 2] = B[:, 2] - B[:, 2] @ B[:, 0] * B[:, 0]
    B[:, 2] = B[:, 2] - B[:, 2] @ B[:, 1] * B[:, 1]
    if la.norm(B[:, 2]) > verySmallNumber :
        B[:, 2] = B[:, 2] / la.norm(B[:, 2])
    else :
        B[:, 2] = np.zeros_like(B[:, 2])
    B[:, 3] = B[:, 3] - B[:, 3] @ B[:, 0] * B[:, 0]
    B[:, 3] = B[:, 3] - B[:, 3] @ B[:, 1] * B[:, 1]
    B[:, 3] = B[:, 3] - B[:, 3] @ B[:, 2] * B[:, 2]
    if la.norm(B[:, 3]) > verySmallNumber :
        B[:, 3] = B[:, 3] / la.norm(B[:, 3])
    else :
        B[:, 3] = np.zeros_like(B[:, 3])
    return B

def gsBasis(A) :
    B = np.array(A, dtype=np.float_)
    for i in range(B.shape[1]) :
        for j in range(i) :
            B[:, i] = B[:,i] - B[:,i] @ B[:,j] * B[:,j]
        if la.norm(B[:, i]) > verySmallNumber :
            B[:, i] = B[:, i] / la.norm(B[:, i])
        else :
            B[:, i] = np.zeros_like(B[:, i])
    return B

def dimensions(A) :
    return np.sum(la.norm(gsBasis(A), axis=0))

# Tests

V = np.array([[1,0,2,6],
              [0,1,8,2],
              [2,8,3,1],
              [1,-6,2,3]], dtype=np.float_)
gsBasis4(V)

U = gsBasis4(V)
gsBasis4(U)

gsBasis(V)

A = np.array([[3,2,3],
              [2,5,-1],
              [2,4,8],
              [12,2,1]], dtype=np.float_)
gsBasis(A)

dimensions(A)

B = np.array([[6,2,1,7,5],
              [2,8,5,-4,1],
              [1,-6,3,2,8]], dtype=np.float_)
gsBasis(B)

dimensions(B)

C = np.array([[1,0,2],
              [0,1,-3],
              [1,0,2]], dtype=np.float_)
gsBasis(C)

dimensions(C)


import numpy as np

def denormalize(x, x_min, x_max):
    return x * (x_max - x_min) + x_min

def normalize(x, x_min=None, x_max=None, return_bounds=False):
    if x_min is None:
        x_min = np.min(x, axis=0)
    if x_max is None:
        x_max = np.max(x, axis=0)

    denom = x_max - x_min
    denom += 1e-30

    res = (x - x_min) / denom
    if not return_bounds:
        return res
    else:
        return res, x_min, x_max

def standardize(x, return_bounds=False):
    mean = np.mean(x, axis=0)
    std = np.std(x, axis=0)

    # standardize
    val = (x - mean) / std

    if not return_bounds:
        return val
    else:
        return val, mean, std

def destandardize(x, mean, std):
    return (x * std) + mean